From 3a669396270aa7a08626b4e82923026ffa3e8ce6 Mon Sep 17 00:00:00 2001 From: Javier Feliz Date: Thu, 28 Aug 2025 13:27:51 -0400 Subject: [PATCH] App icons --- lib/ui/AppListModel.cpp | 63 +++++++++++++++++++++++++++++++ lib/ui/AppListModel.hpp | 4 +- src/main.cpp | 84 ++++++++++++++++++++--------------------- ui/Main.qml | 22 +++++++---- 4 files changed, 123 insertions(+), 50 deletions(-) diff --git a/lib/ui/AppListModel.cpp b/lib/ui/AppListModel.cpp index 64bb535..8acaf19 100644 --- a/lib/ui/AppListModel.cpp +++ b/lib/ui/AppListModel.cpp @@ -2,6 +2,12 @@ #include #include #include +#include +#include +#include +#include +#include +#include AppListModel::AppListModel(QObject *parent) : QAbstractListModel(parent) @@ -30,6 +36,8 @@ QVariant AppListModel::data(const QModelIndex &index, int role) const return QString::fromStdString(app.exec); case IdRole: return QString::fromStdString(app.id); + case IconRole: + return getIconUrl(app); default: return QVariant(); } @@ -41,6 +49,7 @@ QHash AppListModel::roleNames() const roles[NameRole] = "name"; roles[ExecRole] = "exec"; roles[IdRole] = "id"; + roles[IconRole] = "icon"; return roles; } @@ -105,4 +114,58 @@ void AppListModel::updateFilteredApps() m_filteredIndexes.push_back(static_cast(i)); } } +} + +QUrl AppListModel::getIconUrl(const dmenu::DesktopEntry &app) const +{ + QString iconName = QString::fromStdString(app.icon_path); + + 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); + if (icon.isNull()) { + return QUrl(); + } + + // 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" + }; + + 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/lib/ui/AppListModel.hpp b/lib/ui/AppListModel.hpp index ea56f09..aaf719a 100644 --- a/lib/ui/AppListModel.hpp +++ b/lib/ui/AppListModel.hpp @@ -16,7 +16,8 @@ public: enum AppRoles { NameRole = Qt::UserRole + 1, ExecRole, - IdRole + IdRole, + IconRole }; explicit AppListModel(QObject *parent = nullptr); @@ -36,6 +37,7 @@ signals: private: void updateFilteredApps(); + QUrl getIconUrl(const dmenu::DesktopEntry &app) const; dmenu::DEVec m_apps; std::vector m_filteredIndexes; diff --git a/src/main.cpp b/src/main.cpp index 66042de..59f340a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -30,48 +30,48 @@ std::vector split(std::string s, char delimiter) int main(int argc, char *argv[]) { - dmenu::DEVec apps = dmenu::get_dmenu_app_data(); - for (auto &app : *apps.get()) - { - std::cout << app.iconPath() << std::endl; - // std::cout << std::format("---\nName: {}\nID: {}\nIcon: {}\nExec: {}\nDisp: {}\n---\n", app.id, app.name, app.iconPath(), app.exec, app.display ? "yes" : "no"); - } - - // QGuiApplication app(argc, argv); - // QCoreApplication::setApplicationName("waycast"); - - // // Enable system theme support - // app.setDesktopSettingsAware(true); - - // QQmlApplicationEngine engine; - - // // Register the AppListModel type with QML - // qmlRegisterType("WayCast", 1, 0, "AppListModel"); - - // // Set up layer shell before creating any windows - // QObject::connect(&engine, &QQmlApplicationEngine::objectCreationFailed, &app, []() - // { QCoreApplication::exit(-1); }); - - // engine.loadFromModule("WayCast", "Main"); - - // // Get the root objects and configure layer shell - // auto rootObjects = engine.rootObjects(); - // if (!rootObjects.isEmpty()) + // dmenu::DEVec apps = dmenu::get_dmenu_app_data(); + // for (auto &app : *apps.get()) // { - // QWindow *window = qobject_cast(rootObjects.first()); - // if (window) - // { - // LayerShellQt::Window *layerWindow = LayerShellQt::Window::get(window); - // if (layerWindow) - // { - // layerWindow->setLayer(LayerShellQt::Window::LayerTop); - // layerWindow->setAnchors({}); - // layerWindow->setKeyboardInteractivity(LayerShellQt::Window::KeyboardInteractivityOnDemand); - - // // Now show the window after layer shell is configured - // window->show(); - // } - // } + // std::cout << app.iconPath() << std::endl; + // // std::cout << std::format("---\nName: {}\nID: {}\nIcon: {}\nExec: {}\nDisp: {}\n---\n", app.id, app.name, app.iconPath(), app.exec, app.display ? "yes" : "no"); // } - // return app.exec(); + + QGuiApplication app(argc, argv); + QCoreApplication::setApplicationName("waycast"); + + // Enable system theme support + app.setDesktopSettingsAware(true); + + QQmlApplicationEngine engine; + + // Register the AppListModel type with QML + qmlRegisterType("WayCast", 1, 0, "AppListModel"); + + // Set up layer shell before creating any windows + QObject::connect(&engine, &QQmlApplicationEngine::objectCreationFailed, &app, []() + { QCoreApplication::exit(-1); }); + + engine.loadFromModule("WayCast", "Main"); + + // Get the root objects and configure layer shell + auto rootObjects = engine.rootObjects(); + if (!rootObjects.isEmpty()) + { + QWindow *window = qobject_cast(rootObjects.first()); + if (window) + { + LayerShellQt::Window *layerWindow = LayerShellQt::Window::get(window); + if (layerWindow) + { + layerWindow->setLayer(LayerShellQt::Window::LayerTop); + layerWindow->setAnchors({}); + layerWindow->setKeyboardInteractivity(LayerShellQt::Window::KeyboardInteractivityOnDemand); + + // Now show the window after layer shell is configured + window->show(); + } + } + } + return app.exec(); } diff --git a/ui/Main.qml b/ui/Main.qml index 99ea55f..f79046a 100644 --- a/ui/Main.qml +++ b/ui/Main.qml @@ -89,16 +89,24 @@ ApplicationWindow { anchors.margins: 10 spacing: 10 - Rectangle { + Image { width: 24 height: 24 - color: palette.button - radius: 4 + source: model.icon + fillMode: Image.PreserveAspectFit - Text { - anchors.centerIn: parent - text: "📱" - font.pixelSize: 16 + // Fallback if icon fails to load + Rectangle { + anchors.fill: parent + color: palette.button + radius: 4 + visible: parent.status === Image.Error || parent.status === Image.Null + + Text { + anchors.centerIn: parent + text: "📱" + font.pixelSize: 16 + } } }