Skip to content

fix(icons): resolve window icons via StartupWMClass desktop entry field#175

Open
ImIvanGil wants to merge 1 commit into
Axenide:mainfrom
ImIvanGil:fix/icon-resolution-startup-wm-class
Open

fix(icons): resolve window icons via StartupWMClass desktop entry field#175
ImIvanGil wants to merge 1 commit into
Axenide:mainfrom
ImIvanGil:fix/icon-resolution-startup-wm-class

Conversation

@ImIvanGil
Copy link
Copy Markdown

Problem

Some apps show a generic / missing icon in the workspaces indicator and other icon-resolving widgets, even though their .desktop file declares an Icon= correctly. Most visible example: Brave.

Why it happens

AppSearch.getIconFromDesktopEntry() matches the window class against three .desktop fields:

  • Exec[0] (the executable basename)
  • Name
  • Keywords[]

But it does not check StartupWMClass, which is the field freedesktop defined exactly for this purpose — the bridge between window class and .desktop entry.

Brave's /usr/share/applications/brave-browser.desktop:

Name=Brave
Exec=brave %U
StartupWMClass=brave-browser
Icon=brave-desktop

The window's class on Wayland is brave-browser. None of Brave (Name), brave (Exec[0]), or Keywords equal brave-browser, so the function returns null and guessIcon falls through to image-missing.

Fix

Add app.startupClass === normalizedClassName as the first check in getIconFromDesktopEntry(). Quickshell's DesktopEntry already exposes startupClass as a first-class property — no new dependencies, no schema changes.

+if (app.startupClass && app.startupClass.toLowerCase() === normalizedClassName) {
+    return app.icon || "application-x-executable";
+}
 if (app.command && app.command.length > 0) {
     ...

Impact beyond Brave

Any .desktop entry that uses StartupWMClass to disambiguate its window class from its Exec/Name now resolves correctly. Other commonly-affected apps:

  • Spotify (spotify.desktopStartupWMClass=Spotify, Exec=spotify)
  • Steam (custom WMClass per game)
  • Slack / Discord flatpak variants
  • Most Electron apps that override their WMClass
  • VSCode-style apps with code-url-handler and friends (also why those have entries in substitutions)

The substitutions table in AppSearch.qml (line 107) is a manual workaround that this fix obviates for many of those entries. Could clean some of those up in a follow-up if you want, but kept this PR focused on the root-cause fix.

Testing

Before: Brave showed a generic / leaf icon in the workspaces indicator.
After: Brave shows its proper icon (brave-desktop).

GUI apps that don't use StartupWMClass (or where StartupWMClass matches a field already checked) behave identically — the new check is non-destructive and short-circuits to the existing branches when it doesn't match.

Diff stats

modules/services/AppSearch.qml | 7 +++++++
1 file changed, 7 insertions(+)

`getIconFromDesktopEntry()` matched window class against three .desktop
fields — `Exec[0]`, `Name`, and `Keywords` — but ignored `StartupWMClass`,
the field designed precisely for this matching.

Concrete example: Brave (`/usr/share/applications/brave-browser.desktop`)
has:
    Name=Brave
    Exec=brave %U
    StartupWMClass=brave-browser
    Icon=brave-desktop

The window's class on Wayland is `brave-browser`. None of the matched
fields equal `brave-browser` (Name=Brave → "brave", Exec[0]="brave"),
so icon resolution fell through to `image-missing`, and Brave showed
a generic icon in the workspace indicator.

Fix: prepend a `app.startupClass === normalizedClassName` check. Quickshell's
DesktopEntry exposes `startupClass` as a first-class property, so this is
trivial.

This is a generic improvement — any .desktop entry that uses
StartupWMClass to disambiguate its window class from its Exec/Name now
resolves correctly. Other affected apps in the wild: Spotify
(`spotify.desktop` → StartupWMClass=Spotify), Steam, Slack, Discord
flatpak variants, and most Electron apps with custom WMClass.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant