diff --git a/lib/IconUtil.hpp b/lib/IconUtil.hpp new file mode 100644 index 0000000..1940a0d --- /dev/null +++ b/lib/IconUtil.hpp @@ -0,0 +1,175 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace IconUtil +{ + /** + * Resolves an application icon using Qt's theme system and fallback searching. + * Handles both absolute paths and theme icon names. + * + * @param iconPath The icon path from a desktop entry or theme name + * @return QUrl pointing to the resolved icon file, or empty URL if not found + */ + QUrl resolveAppIcon(const std::string &iconPath); + + /** + * Gets an appropriate icon for a file based on its MIME type. + * Uses Qt's QMimeDatabase to determine the proper icon name, then resolves to actual file path. + * + * @param filePath The filesystem path to the file + * @return QUrl pointing to the resolved icon file, or empty URL if not found + */ + QUrl getFileIcon(const std::filesystem::path &filePath); + + /** + * Resolves a theme icon name to an actual file path. + * + * @param iconName The freedesktop.org standard icon name + * @return QUrl pointing to the resolved icon file, or empty URL if not found + */ + QUrl resolveThemeIconToPath(const QString& iconName); +} + +// Implementation +namespace IconUtil +{ + inline QUrl resolveAppIcon(const std::string &iconPath) + { + QString iconName = QString::fromStdString(iconPath); + + if (iconName.isEmpty()) + return QUrl(); + + // If it's already a full path, use it directly + if (iconName.startsWith('/')) { + if (QFile::exists(iconName)) { + return QUrl::fromLocalFile(iconName); + } + return QUrl(); + } + + // Use Qt's proper icon theme search which follows XDG spec + QIcon icon = QIcon::fromTheme(iconName); + bool themeFound = !icon.isNull(); + + // Qt doesn't expose the resolved file path directly, so let's use QStandardPaths + // to search in the proper system directories + QStringList dataDirs = QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation); + + // Common icon subdirectories and sizes (prioritized) + QStringList iconSubDirs = { + "icons/hicolor/scalable/apps", + "icons/hicolor/48x48/apps", + "icons/hicolor/64x64/apps", + "icons/hicolor/32x32/apps", + "icons/hicolor/128x128/apps", + "icons/Adwaita/scalable/apps", + "icons/Adwaita/48x48/apps", + "pixmaps" // This should search /path/to/share/pixmaps/ + }; + + QStringList extensions = {"", ".png", ".svg", ".xpm"}; + + for (const QString &dataDir : dataDirs) { + for (const QString &iconSubDir : iconSubDirs) { + QString basePath = dataDir + "/" + iconSubDir + "/"; + for (const QString &ext : extensions) { + QString fullPath = basePath + iconName + ext; + if (QFile::exists(fullPath)) { + return QUrl::fromLocalFile(fullPath); + } + } + } + } + + return QUrl(); + } + + inline QUrl getFileIcon(const std::filesystem::path &filePath) + { + QMimeDatabase db; + QString fileName = QString::fromStdString(filePath.string()); + + // Get MIME type for the file + QMimeType mime = db.mimeTypeForFile(fileName); + + // Get the icon name from the MIME type + QString iconName = mime.iconName(); + + // If no specific icon, try the generic icon name + if (iconName.isEmpty()) { + iconName = mime.genericIconName(); + } + + // Ultimate fallback + if (iconName.isEmpty()) { + iconName = "text-x-generic"; + } + + // Now resolve the icon name to an actual file path using the same logic as desktop apps + return resolveThemeIconToPath(iconName); + } + + inline QUrl resolveThemeIconToPath(const QString& iconName) + { + if (iconName.isEmpty()) + return QUrl(); + + // Use Qt's icon theme system first + QIcon icon = QIcon::fromTheme(iconName); + if (icon.isNull()) { + // Try with fallback + icon = QIcon::fromTheme("text-x-generic"); + } + + // Search in standard icon directories to find the actual file + QStringList dataDirs = QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation); + + // Icon subdirectories for MIME type icons (prioritized) + QStringList iconSubDirs = { + "icons/hicolor/scalable/mimetypes", + "icons/hicolor/48x48/mimetypes", + "icons/hicolor/32x32/mimetypes", + "icons/hicolor/64x64/mimetypes", + "icons/hicolor/24x24/mimetypes", + "icons/hicolor/16x16/mimetypes", + "icons/Adwaita/scalable/mimetypes", + "icons/Adwaita/48x48/mimetypes", + "icons/Adwaita/32x32/mimetypes", + "icons/breeze/mimetypes/22", // KDE Plasma + "icons/breeze-dark/mimetypes/22", + "icons/Papirus/48x48/mimetypes", // Popular icon theme + "icons/elementary/mimetypes/48", // Elementary OS + }; + + QStringList extensions = {".svg", ".png", ".xpm"}; + + for (const QString& dataDir : dataDirs) { + for (const QString& iconSubDir : iconSubDirs) { + QString basePath = dataDir + "/" + iconSubDir + "/"; + for (const QString& ext : extensions) { + QString fullPath = basePath + iconName + ext; + if (QFile::exists(fullPath)) { + return QUrl::fromLocalFile(fullPath); + } + } + } + } + + // Fallback: try to resolve text-x-generic if we couldn't find the specific icon + if (iconName != "text-x-generic") { + return resolveThemeIconToPath("text-x-generic"); + } + + return QUrl(); + } +} \ No newline at end of file diff --git a/src/plugins/DesktopAppPlugin/DesktopAppListItem.cpp b/src/plugins/DesktopAppPlugin/DesktopAppListItem.cpp index 2dd2576..c383d25 100644 --- a/src/plugins/DesktopAppPlugin/DesktopAppListItem.cpp +++ b/src/plugins/DesktopAppPlugin/DesktopAppListItem.cpp @@ -1,9 +1,7 @@ #include "DesktopAppListItem.hpp" +#include "IconUtil.hpp" #include #include -#include -#include -#include #include DesktopAppListItem::DesktopAppListItem(const dmenu::DesktopEntry &entry) @@ -24,7 +22,7 @@ QString DesktopAppListItem::description() const QUrl DesktopAppListItem::iconUrl() const { - return resolveIconUrl(m_entry.icon_path); + return IconUtil::resolveAppIcon(m_entry.icon_path); } void DesktopAppListItem::execute() @@ -44,54 +42,3 @@ QString DesktopAppListItem::itemType() const return "app"; } -QUrl DesktopAppListItem::resolveIconUrl(const std::string &iconPath) const -{ - QString iconName = QString::fromStdString(iconPath); - - if (iconName.isEmpty()) - return QUrl(); - - // If it's already a full path, use it directly - if (iconName.startsWith('/')) { - if (QFile::exists(iconName)) { - return QUrl::fromLocalFile(iconName); - } - return QUrl(); - } - - // Use Qt's proper icon theme search which follows XDG spec - QIcon icon = QIcon::fromTheme(iconName); - bool themeFound = !icon.isNull(); - - // Qt doesn't expose the resolved file path directly, so let's use QStandardPaths - // to search in the proper system directories - QStringList dataDirs = QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation); - - // Common icon subdirectories and sizes (prioritized) - QStringList iconSubDirs = { - "icons/hicolor/scalable/apps", - "icons/hicolor/48x48/apps", - "icons/hicolor/64x64/apps", - "icons/hicolor/32x32/apps", - "icons/hicolor/128x128/apps", - "icons/Adwaita/scalable/apps", - "icons/Adwaita/48x48/apps", - "pixmaps" // This should search /path/to/share/pixmaps/ - }; - - QStringList extensions = {"", ".png", ".svg", ".xpm"}; - - for (const QString &dataDir : dataDirs) { - for (const QString &iconSubDir : iconSubDirs) { - QString basePath = dataDir + "/" + iconSubDir + "/"; - for (const QString &ext : extensions) { - QString fullPath = basePath + iconName + ext; - if (QFile::exists(fullPath)) { - return QUrl::fromLocalFile(fullPath); - } - } - } - } - - return QUrl(); -} \ No newline at end of file diff --git a/src/plugins/DesktopAppPlugin/DesktopAppListItem.hpp b/src/plugins/DesktopAppPlugin/DesktopAppListItem.hpp index daba9aa..75bfedd 100644 --- a/src/plugins/DesktopAppPlugin/DesktopAppListItem.hpp +++ b/src/plugins/DesktopAppPlugin/DesktopAppListItem.hpp @@ -19,7 +19,5 @@ public: QString itemType() const override; private: - QUrl resolveIconUrl(const std::string &iconPath) const; - dmenu::DesktopEntry m_entry; }; \ No newline at end of file diff --git a/src/plugins/FileSearchPlugin.hpp b/src/plugins/FileSearchPlugin.hpp index ae0e076..1ac5a34 100644 --- a/src/plugins/FileSearchPlugin.hpp +++ b/src/plugins/FileSearchPlugin.hpp @@ -2,6 +2,7 @@ #include "PluginInterface.hpp" #include "GenericListItem.hpp" +#include "IconUtil.hpp" #include "files.hpp" #include "fuzzy.hpp" #include @@ -241,90 +242,12 @@ namespace plugins // Create description showing parent directory QString description = parentDir.isEmpty() ? fullPath : QString("%1/.../%2").arg(parentDir, fileName); - // Simple icon based on file extension - QUrl icon = getFileIcon(filePath); + // Get icon using shared utility + QUrl icon = IconUtil::getFileIcon(filePath); return ListItems::createFile(fileName, fullPath, icon); } - QUrl getFileIcon(const std::filesystem::path &filePath) const - { - QString ext = QString::fromStdString(filePath.extension().string()).toLower(); - QString iconName; - - // Map file extensions to freedesktop.org standard icon names - if (ext == ".txt" || ext == ".md" || ext == ".rst" || ext == ".readme") - { - iconName = "text-x-generic"; - } - else if (ext == ".pdf") - { - iconName = "application-pdf"; - } - else if (ext == ".png" || ext == ".jpg" || ext == ".jpeg" || ext == ".gif" || - ext == ".bmp" || ext == ".svg" || ext == ".webp" || ext == ".tiff") - { - iconName = "image-x-generic"; - } - else if (ext == ".mp3" || ext == ".wav" || ext == ".ogg" || ext == ".flac" || - ext == ".m4a" || ext == ".aac" || ext == ".wma") - { - iconName = "audio-x-generic"; - } - else if (ext == ".mp4" || ext == ".avi" || ext == ".mkv" || ext == ".webm" || - ext == ".mov" || ext == ".wmv" || ext == ".flv" || ext == ".m4v") - { - iconName = "video-x-generic"; - } - else if (ext == ".zip" || ext == ".tar" || ext == ".gz" || ext == ".bz2" || - ext == ".xz" || ext == ".7z" || ext == ".rar") - { - iconName = "package-x-generic"; - } - else if (ext == ".cpp" || ext == ".hpp" || ext == ".c" || ext == ".h") - { - iconName = "text-x-c++src"; - } - else if (ext == ".py") - { - iconName = "text-x-python"; - } - else if (ext == ".js" || ext == ".ts" || ext == ".json") - { - iconName = "text-x-javascript"; - } - else if (ext == ".html" || ext == ".htm" || ext == ".css") - { - iconName = "text-html"; - } - else if (ext == ".xml" || ext == ".xsl" || ext == ".xsd") - { - iconName = "text-xml"; - } - else if (ext == ".sh" || ext == ".bash" || ext == ".zsh") - { - iconName = "text-x-script"; - } - else if (ext == ".doc" || ext == ".docx" || ext == ".odt") - { - iconName = "application-vnd.oasis.opendocument.text"; - } - else if (ext == ".xls" || ext == ".xlsx" || ext == ".ods") - { - iconName = "application-vnd.oasis.opendocument.spreadsheet"; - } - else if (ext == ".ppt" || ext == ".pptx" || ext == ".odp") - { - iconName = "application-vnd.oasis.opendocument.presentation"; - } - else - { - iconName = "text-x-generic"; // Default fallback - } - - // Return the theme icon URL that QML can resolve automatically - return QUrl(QString("image://theme/%1").arg(iconName)); - } // Member variables std::vector m_searchDirectories;