Skip to content

omarchy-webapp-install: set StartupWMClass so app switchers find the icon#5934

Open
andyjeffries wants to merge 1 commit into
basecamp:devfrom
andyjeffries:webapp-startup-wm-class
Open

omarchy-webapp-install: set StartupWMClass so app switchers find the icon#5934
andyjeffries wants to merge 1 commit into
basecamp:devfrom
andyjeffries:webapp-startup-wm-class

Conversation

@andyjeffries
Copy link
Copy Markdown

Problem

App switchers and launchers that look up window icons via StartupWMClass (e.g. hyprswitch, plank, others) show a generic fallback icon — typically a cog — for windows created by omarchy-webapp-install. The icon is configured in the .desktop file's Icon= line, but without a StartupWMClass= entry there's no way for the switcher to map the running window's class to the .desktop file, so the configured icon is never used.

Reproduction on current dev:

  1. omarchy-webapp-install Figma https://figma.com/ <icon-url>
  2. Launch Figma from the app launcher.
  3. Super+Tab (or any switcher that does icon lookup via XDG). The Figma window shows the fallback cog instead of the configured Figma icon.

hyprswitch debug search --class brave-figma.com__-Default confirms the cause:

Theme does not contain icon for class brave-figma.com__-Default
Failed to get icon name for class brave-figma.com__-Default

After this PR, the same lookup resolves:

Icon: ".../icons/Figma.png" from desktop file cache:
  DesktopFileStartupWmClass found by ".../Figma.desktop"

Fix

Chromium-family browsers derive WM_CLASS for --app=<URL> deterministically as

<browser-prefix>-<host>/<path>-Default

with the port stripped, every / replaced with _, and an empty path normalized to /. We compute that string from APP_URL and append StartupWMClass=<class> to the generated .desktop file.

The browser prefix is taken from xdg-settings get default-web-browser and mapped to one of the prefixes Chromium-family browsers use (brave, google-chrome, microsoft-edge, vivaldi, opera, helium, chromium). Unknown browsers are skipped silently so the .desktop file is unchanged in those cases — no regression risk.

The block is also skipped when CUSTOM_EXEC is set, since the resulting class is not predictable from APP_URL alone in that case.

Verification

Encoding was checked against eight live web-app windows (hyprctl clients) running under Brave 148:

URL Class produced by Brave Class computed by this PR
https://discord.com/channels/@me brave-discord.com__channels_@me-Default ✅ matches
https://www.icloud.com/calendar/ brave-www.icloud.com__calendar_-Default ✅ matches
https://web.whatsapp.com/ brave-web.whatsapp.com__-Default ✅ matches
https://claude.ai/ brave-claude.ai__-Default ✅ matches
http://media.local:8636 brave-media.local__-Default ✅ matches
https://calendar.google.com/calendar/u/1/r?pli=1 brave-calendar.google.com__calendar_u_1_r-Default ✅ matches
https://figma.com/ brave-figma.com__-Default ✅ matches
http://media.local:32400/web brave-media.local__web-Default ✅ matches

Implementation is pure bash parameter expansion — no new dependencies.

Known limitation

Chromium strips the port from WM_CLASS for --app= windows, and --class= is silently ignored when the browser is already running with the same user-data-dir. So two web apps on the same host that differ only by port will collide on a single StartupWMClass. The icon resolved will be whichever .desktop file the cache finds first. Window titles still differentiate them in the switcher; only the icon collides. This is unchanged from today's behavior (today it's broken for everyone; after this PR it's correct for the common single-host case and unchanged for the multi-port case).

A possible follow-up would be to opt into per-app --user-data-dir + --class for cases where shared login state isn't needed (e.g. internal services); happy to send a separate PR if that's of interest.

App switchers (hyprswitch, plank, etc.) and launchers resolve a window's
icon by matching its WM_CLASS against StartupWMClass= in installed
.desktop files. Web apps created by omarchy-webapp-install currently
omit StartupWMClass, so they show a generic fallback (cog) icon in the
switcher instead of the icon configured in the .desktop file.

Chromium-family browsers derive WM_CLASS for --app=<URL> as
  <prefix>-<host>/<path>-Default
with the port stripped, '/' replaced with '_', and an empty path
normalized to '/'. We compute that string from APP_URL and write it
into the .desktop file. The browser prefix is taken from the current
xdg default-web-browser; unknown browsers are skipped silently so the
.desktop file is unchanged from today's behavior in those cases.

Note: this is best-effort. Two web apps on the same host that differ
only by port will collide on a single WM_CLASS (a Chromium limitation
in --app= mode). For most webapps (one per host), the icon resolves
correctly.
Copilot AI review requested due to automatic review settings May 21, 2026 14:23
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot wasn't able to review any files in this pull request.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

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.

2 participants