From dbb514c648cca91e0c07da09e2d25f3e417be043 Mon Sep 17 00:00:00 2001 From: reionwong Date: Sat, 5 Sep 2026 00:35:10 -0400 Subject: [PATCH] fix(applications): apply XDG display rules to desktop entries --- applications/applicationregistry.cpp | 25 +-------- applications/desktopentry.cpp | 79 +++++++++++++++++++++++++++- applications/desktopentry.h | 26 +++++++++ 3 files changed, 106 insertions(+), 24 deletions(-) diff --git a/applications/applicationregistry.cpp b/applications/applicationregistry.cpp index 2bdbf0e..5045e86 100644 --- a/applications/applicationregistry.cpp +++ b/applications/applicationregistry.cpp @@ -79,27 +79,6 @@ QString normalized(const QString &value) return value.trimmed().toLower(); } -bool desktopMatches(const DesktopEntryData &entry, const QByteArray &desktop) -{ - const QStringList currentDesktops = QString::fromLocal8Bit(desktop) - .split(QLatin1Char(':'), Qt::SkipEmptyParts); - auto containsDesktop = [¤tDesktops](const QStringList &desktops) { - for (const QString &desktop : desktops) { - for (const QString ¤t : currentDesktops) { - if (desktop.compare(current, Qt::CaseInsensitive) == 0) - return true; - } - } - return false; - }; - - if (!entry.onlyShowIn.isEmpty() && !containsDesktop(entry.onlyShowIn)) - return false; - if (containsDesktop(entry.notShowIn)) - return false; - return true; -} - } // namespace DesktopEntryModel::DesktopEntryModel(QObject *parent) @@ -365,7 +344,7 @@ void ApplicationRegistry::applyScan(const QList &results) QHash newEntriesByPath; QList allEntries; QList visibleEntries; - const QByteArray currentDesktop = qgetenv("XDG_CURRENT_DESKTOP"); + const QStringList currentDesktops = DesktopEntry::currentDesktops(); for (const DesktopEntryData &data : results) { DesktopEntry *entry = oldEntries.take(data.id); @@ -377,7 +356,7 @@ void ApplicationRegistry::applyScan(const QList &results) newEntriesByPath.insert(QFileInfo(data.path).absoluteFilePath(), entry); allEntries.append(entry); - if (!data.hidden && !data.noDisplay && desktopMatches(data, currentDesktop)) + if (entry->shouldShow(currentDesktops)) visibleEntries.append(entry); } diff --git a/applications/desktopentry.cpp b/applications/desktopentry.cpp index c83704d..50d89f4 100644 --- a/applications/desktopentry.cpp +++ b/applications/desktopentry.cpp @@ -7,6 +7,7 @@ #include #include #include +#include namespace { @@ -34,6 +35,27 @@ bool parseBool(const QString &value) return value.compare(QStringLiteral("true"), Qt::CaseInsensitive) == 0; } +// TryExec may be a bare name to look up in PATH or an absolute path. +bool executableExists(const QString &program) +{ + if (program.contains(QLatin1Char('/'))) { + const QFileInfo info(program); + return info.exists() && info.isExecutable(); + } + return !QStandardPaths::findExecutable(program).isEmpty(); +} + +bool listContainsDesktop(const QStringList &list, const QStringList &desktops) +{ + for (const QString &wanted : list) { + for (const QString ¤t : desktops) { + if (wanted.compare(current, Qt::CaseInsensitive) == 0) + return true; + } + } + return false; +} + QString localizedValue(const QMap &values, const QMap &localized, const QString &key) @@ -148,15 +170,48 @@ QString DesktopEntry::genericName() const { return m_genericName; } QString DesktopEntry::comment() const { return m_comment; } QString DesktopEntry::icon() const { return m_icon; } QString DesktopEntry::exec() const { return m_exec; } +QString DesktopEntry::tryExec() const { return m_tryExec; } QStringList DesktopEntry::command() const { return m_command; } QString DesktopEntry::workingDirectory() const { return m_workingDirectory; } QString DesktopEntry::startupWMClass() const { return m_startupWMClass; } QStringList DesktopEntry::categories() const { return m_categories; } QStringList DesktopEntry::keywords() const { return m_keywords; } QStringList DesktopEntry::mimeTypes() const { return m_mimeTypes; } +QStringList DesktopEntry::onlyShowIn() const { return m_onlyShowIn; } +QStringList DesktopEntry::notShowIn() const { return m_notShowIn; } bool DesktopEntry::terminal() const { return m_terminal; } bool DesktopEntry::noDisplay() const { return m_noDisplay; } bool DesktopEntry::hidden() const { return m_hidden; } +bool DesktopEntry::dbusActivatable() const { return m_dbusActivatable; } + +QStringList DesktopEntry::currentDesktops() +{ + QString value = qEnvironmentVariable("XDG_CURRENT_DESKTOP"); + if (value.isEmpty()) + value = QStringLiteral("Cutefish"); + return value.split(QLatin1Char(':'), Qt::SkipEmptyParts); +} + +bool DesktopEntry::shouldShow() const +{ + return shouldShow(currentDesktops()); +} + +bool DesktopEntry::shouldShow(const QStringList &desktops) const +{ + // Hidden means the entry is deleted; it only exists to mask a system one. + if (m_hidden || m_noDisplay) + return false; + if (m_exec.isEmpty() && !m_dbusActivatable) + return false; + if (!m_tryExec.isEmpty() && !executableExists(m_tryExec)) + return false; + if (!m_onlyShowIn.isEmpty() && !listContainsDesktop(m_onlyShowIn, desktops)) + return false; + if (listContainsDesktop(m_notShowIn, desktops)) + return false; + return true; +} QStringList DesktopEntry::commandForArguments(const QStringList &arguments) const { @@ -227,6 +282,7 @@ bool DesktopEntry::parse(const QString &id, const QString &path, data.comment = localizedValue(values, localized, QStringLiteral("Comment")); data.icon = values.value(QStringLiteral("Icon")); data.exec = values.value(QStringLiteral("Exec")); + data.tryExec = values.value(QStringLiteral("TryExec")); data.workingDirectory = values.value(QStringLiteral("Path")); data.startupWMClass = values.value(QStringLiteral("StartupWMClass")); data.categories = splitList(values.value(QStringLiteral("Categories"))); @@ -237,6 +293,7 @@ bool DesktopEntry::parse(const QString &id, const QString &path, data.terminal = parseBool(values.value(QStringLiteral("Terminal"))); data.noDisplay = parseBool(values.value(QStringLiteral("NoDisplay"))); data.hidden = parseBool(values.value(QStringLiteral("Hidden"))); + data.dbusActivatable = parseBool(values.value(QStringLiteral("DBusActivatable"))); } else if (group.startsWith(QStringLiteral("Desktop Action "))) { const QString actionId = group.mid(QStringLiteral("Desktop Action ").size()); const auto action = actionValues.value(actionId); @@ -287,7 +344,11 @@ bool DesktopEntry::parse(const QString &id, const QString &path, } finishGroup(); - if (data.type != QStringLiteral("Application") || data.name.isEmpty()) + // A Hidden entry is kept without a Name so it still masks the system entry + // of the same id further down XDG_DATA_DIRS. + if (data.type != QStringLiteral("Application")) + return false; + if (data.name.isEmpty() && !data.hidden) return false; *result = data; @@ -316,6 +377,10 @@ void DesktopEntry::update(const DesktopEntryData &data) m_icon = data.icon; emit iconChanged(); } + if (m_tryExec != data.tryExec) { + m_tryExec = data.tryExec; + emit tryExecChanged(); + } const bool hasExecChanged = m_exec != data.exec; if (hasExecChanged) { m_exec = data.exec; @@ -341,6 +406,14 @@ void DesktopEntry::update(const DesktopEntryData &data) m_mimeTypes = data.mimeTypes; emit mimeTypesChanged(); } + if (m_onlyShowIn != data.onlyShowIn) { + m_onlyShowIn = data.onlyShowIn; + emit onlyShowInChanged(); + } + if (m_notShowIn != data.notShowIn) { + m_notShowIn = data.notShowIn; + emit notShowInChanged(); + } if (m_terminal != data.terminal) { m_terminal = data.terminal; emit terminalChanged(); @@ -353,6 +426,10 @@ void DesktopEntry::update(const DesktopEntryData &data) m_hidden = data.hidden; emit hiddenChanged(); } + if (m_dbusActivatable != data.dbusActivatable) { + m_dbusActivatable = data.dbusActivatable; + emit dbusActivatableChanged(); + } const QStringList command = commandForArguments(QStringList()); if (m_command != command) { diff --git a/applications/desktopentry.h b/applications/desktopentry.h index 2389e9c..6ea023d 100644 --- a/applications/desktopentry.h +++ b/applications/desktopentry.h @@ -22,6 +22,7 @@ struct DesktopEntryData QString comment; QString icon; QString exec; + QString tryExec; QString workingDirectory; QString startupWMClass; QStringList categories; @@ -33,6 +34,7 @@ struct DesktopEntryData bool terminal = false; bool noDisplay = false; bool hidden = false; + bool dbusActivatable = false; }; Q_DECLARE_METATYPE(DesktopEntryData) @@ -48,15 +50,20 @@ class DesktopEntry : public QObject Q_PROPERTY(QString comment READ comment NOTIFY commentChanged) Q_PROPERTY(QString icon READ icon NOTIFY iconChanged) Q_PROPERTY(QString exec READ exec NOTIFY execChanged) + Q_PROPERTY(QString tryExec READ tryExec NOTIFY tryExecChanged) Q_PROPERTY(QStringList command READ command NOTIFY commandChanged) Q_PROPERTY(QString workingDirectory READ workingDirectory NOTIFY workingDirectoryChanged) Q_PROPERTY(QString startupWMClass READ startupWMClass NOTIFY startupWMClassChanged) Q_PROPERTY(QStringList categories READ categories NOTIFY categoriesChanged) Q_PROPERTY(QStringList keywords READ keywords NOTIFY keywordsChanged) Q_PROPERTY(QStringList mimeTypes READ mimeTypes NOTIFY mimeTypesChanged) + Q_PROPERTY(QStringList onlyShowIn READ onlyShowIn NOTIFY onlyShowInChanged) + Q_PROPERTY(QStringList notShowIn READ notShowIn NOTIFY notShowInChanged) Q_PROPERTY(bool terminal READ terminal NOTIFY terminalChanged) Q_PROPERTY(bool noDisplay READ noDisplay NOTIFY noDisplayChanged) Q_PROPERTY(bool hidden READ hidden NOTIFY hiddenChanged) + Q_PROPERTY(bool dbusActivatable READ dbusActivatable NOTIFY dbusActivatableChanged) + Q_PROPERTY(bool shouldShow READ shouldShow NOTIFY changed) public: explicit DesktopEntry(const QString &id, QObject *parent = nullptr); @@ -68,15 +75,26 @@ public: QString comment() const; QString icon() const; QString exec() const; + QString tryExec() const; QStringList command() const; QString workingDirectory() const; QString startupWMClass() const; QStringList categories() const; QStringList keywords() const; QStringList mimeTypes() const; + QStringList onlyShowIn() const; + QStringList notShowIn() const; bool terminal() const; bool noDisplay() const; bool hidden() const; + bool dbusActivatable() const; + + // XDG display rules: Hidden, NoDisplay, TryExec, OnlyShowIn/NotShowIn. + bool shouldShow() const; + bool shouldShow(const QStringList &desktops) const; + + // XDG_CURRENT_DESKTOP split on ':', defaulting to this session. + static QStringList currentDesktops(); Q_INVOKABLE bool launch(const QStringList &arguments = QStringList()) const; @@ -95,15 +113,19 @@ signals: void commentChanged(); void iconChanged(); void execChanged(); + void tryExecChanged(); void commandChanged(); void workingDirectoryChanged(); void startupWMClassChanged(); void categoriesChanged(); void keywordsChanged(); void mimeTypesChanged(); + void onlyShowInChanged(); + void notShowInChanged(); void terminalChanged(); void noDisplayChanged(); void hiddenChanged(); + void dbusActivatableChanged(); void changed(); private: @@ -114,13 +136,17 @@ private: QString m_comment; QString m_icon; QString m_exec; + QString m_tryExec; QStringList m_command; QString m_workingDirectory; QString m_startupWMClass; QStringList m_categories; QStringList m_keywords; QStringList m_mimeTypes; + QStringList m_onlyShowIn; + QStringList m_notShowIn; bool m_terminal = false; bool m_noDisplay = false; bool m_hidden = false; + bool m_dbusActivatable = false; };