File icons work

This commit is contained in:
Javier Feliz 2025-08-28 18:00:43 -04:00
parent 6f6b49bd21
commit e5b9c85355
4 changed files with 180 additions and 137 deletions

175
lib/IconUtil.hpp Normal file
View File

@ -0,0 +1,175 @@
#pragma once
#include <QString>
#include <QUrl>
#include <QIcon>
#include <QStandardPaths>
#include <QFile>
#include <QStringList>
#include <QMimeDatabase>
#include <QMimeType>
#include <filesystem>
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();
}
}

View File

@ -1,9 +1,7 @@
#include "DesktopAppListItem.hpp"
#include "IconUtil.hpp"
#include <QProcess>
#include <QRegularExpression>
#include <QIcon>
#include <QStandardPaths>
#include <QFile>
#include <QDebug>
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();
}

View File

@ -19,7 +19,5 @@ public:
QString itemType() const override;
private:
QUrl resolveIconUrl(const std::string &iconPath) const;
dmenu::DesktopEntry m_entry;
};

View File

@ -2,6 +2,7 @@
#include "PluginInterface.hpp"
#include "GenericListItem.hpp"
#include "IconUtil.hpp"
#include "files.hpp"
#include "fuzzy.hpp"
#include <QUrl>
@ -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<std::string> m_searchDirectories;