Repository navigation
feat: show the app icon on Wayland without AppImage integration - #689
Merged
Merged
Conversation
Lolle2000la
force-pushed
the
feat/wayland-app-icon
branch
3 times, most recently
from
October 11, 2026 19:11
42216dd to
cf83da9
Compare
Wayland compositors never read the window icon from the client; they resolve a toplevel's icon through the desktop entry whose file name matches the toplevel's app_id. An AppImage that was never integrated by appimaged/AppImageLauncher/Gear Lever has no such entry, so the window showed a generic icon even though the AppImage ships a desktop entry and the icon files. The new linux_desktop module scans the XDG application directories for an entry whose Exec/TryExec launches the running $APPIMAGE and adopts its file name as the app id (covering every integration tool, and replacing the hardcoded appimagekit-<stem> guess). When nothing matches, it writes a hidden (NoDisplay=true) MediaSort.desktop plus the scalable MediaSort.svg into the user's XDG data directories, the same locations integration tools use, so the icon resolves on any compositor without adding a launcher entry. Only files carrying the X-MediaSort-Provisioned marker are ever rewritten or removed; packaged and hand-written entries always win (a provisioned entry is even removed again when a packaged one appears, since it would shadow it). Writes go through path_utils::atomic_write and are skipped when content is unchanged. Running instances coordinate through a shared flock on $XDG_DATA_HOME/media-sort/desktop-integration.lock; the last instance to exit removes the provisioned entry and icon again, so a cleanly closed app leaves no integration behind. The cleanup is best-effort: a crash or SIGKILL skips it and leaves the hidden files for the next run, and the lock file itself stays behind. The embedded icon is packaging/linux/icon.svg itself - the same scalable icon the AppImage ships - so it cannot drift from it.
Lolle2000la
force-pushed
the
feat/wayland-app-icon
branch
from
October 11, 2026 19:43
cf83da9 to
125108e
Compare
…ique id Replace the provision-then-remove lifecycle (shared flock + cleanup on the last exit) with a persistent desktop entry and icon named after a unique id. Because the provisioned name can never collide with or shadow a packaged, distro- or tool-managed MediaSort.desktop, keeping the files between runs is safe, and it avoids per-launch icon/menu cache churn and a window group that disappears across a restart. - Provision applications/mediasort-wayland.desktop and icons/hicolor/scalable/apps/mediasort-wayland.svg (PROVISIONED_ID) instead of the canonical MediaSort name; the canonical id is used as the app id only when a real packaged/hand-written MediaSort.desktop exists. - Drop IntegrationGuard/cleanup_provisioned; application_id() returns the id directly, matching what AGENTS.md already documented. - Never overwrite a foreign file at the provisioned entry path; remove the provisioned files again only when a canonical entry appears, and migrate an entry written under the canonical name by an earlier revision. - Ignore $APPDIR-bundled data dirs while scanning so an AppRun that prepends $APPDIR/usr/share to XDG_DATA_DIRS cannot silently disable provisioning. - 22 unit tests; escaping re-verified against desktop-file-validate and GLib.
On X11 the window icon is delivered by the window itself (window::Settings.icon), so the desktop-entry provisioning added nothing there and only left a permanent footprint in the user's XDG data directories. Gate the module on a Wayland session (WAYLAND_DISPLAY set); on other backends return the canonical app id, which matches the packaged/Gear Lever StartupWMClass=MediaSort used for X11 window grouping.
…reads Follow-ups from review of the persistent desktop-entry integration: - Adopt an existing desktop entry that launches the running executable even without an AppImage (a hand-written entry for a development build is now picked up instead of provisioning a second one). - Compare existing files with a bounded read (read_capped_bytes), so a large or hostile file at the provisioned entry/icon path is never read whole. - Write the icon before the entry, so a failed icon write cannot leave a marked entry pointing at a missing icon (which made the returned app id match neither the entry nor the icon). - Tolerate leading whitespace before Exec=/TryExec=, as a key-file does. - Tests for executable adoption, an indented Exec and a kept user-replaced icon (26 unit tests total).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
On Wayland the window icon is not a client property: compositors resolve it (and the app's display name / launcher grouping) through the desktop entry whose file name matches the toplevel's
app_id. An AppImage only gets such an entry when a desktop-integration tool (appimaged, AppImageLauncher, Gear Lever, ...) has "installed" it, so an unintegrated AppImage always shows a generic icon even though it shipsMediaSort.desktopand the icon files itself. The old app-id logic (appimagekit-<stem>.desktop/<stem>) only covered the naming schemes of some integration tools and can't help on its own.The
xdg-toplevel-icon-v1protocol would solve this properly, but iced 0.14 pins winit 0.30.13, which cannot speak it (winit only gained support on its main branch, rust-windowing/winit#4288). Until iced moves to a newer winit, the standard cross-compositor route is to make the desktop-entry lookup succeed.Solution
New
crates/media-sort-gui/src/linux_desktop.rs, called once frommain.rsto produce the window'sapplication_id:MediaSort.desktop(the canonical id) exists anywhere, it wins: its name is used as the app id and nothing is written.$XDG_DATA_HOME/applicationsand$XDG_DATA_DIRS/*/applicationsfor a*.desktopfile whoseExec/TryExeclaunches the running$APPIMAGE(or, when there is no AppImage, the running executable) - any integration tool, any file name; quotes,envprefixes and\sescapes are parsed and symlinked AppImages are resolved. Entries inside the running AppImage's own$APPDIRmount are ignored (the compositor cannot see the mount, so they must not count as "already registered"). If found, its file name is adopted as the app id and nothing is written.NoDisplay=true) desktop entry plus the scalableMediaSorticon are written into$XDG_DATA_HOME, under a name unique to this module (applications/mediasort-wayland.desktopandicons/hicolor/scalable/apps/mediasort-wayland.svg).The provisioned files are kept between runs - they are what gives the shell the icon, and removing them at exit would only churn icon/menu caches and break window grouping across a restart. Because their name is unique to this module, a self-provisioned file can never collide with or shadow a packaged, distro- or tool-managed
MediaSort.desktop; a foreign file found at one of those paths is left untouched. They are removed again only when a canonicalMediaSort.desktopappears (or when one was written under the canonical name by an earlier revision), so the packaged entry's icon and running state take over. Writes go throughpath_utils::atomic_writeand are skipped when content is unchanged.Nothing is provisioned on non-Wayland sessions: on X11 the icon is delivered by the window itself (
window::Settings.icon), so only the app id is set there - to the canonicalMediaSort, which matches the packaged and integration entries'StartupWMClass=MediaSort. This also givescargo runand the standalone binary an icon on Wayland (without an AppImage,Execpoints at the running executable).Paths that cannot be represented portably (control characters,
%,$, backticks) are skipped, so an invalid desktop entry is never written; spaces, quotes and backslashes are escaped for both the key-file and the shell parsing layers (verified againstdesktop-file-validate, GLib's loader and KDE'sKConfig/KShell).The embedded icon is
packaging/linux/icon.svgitself - the scalable SVG the AppImage also ships - so it cannot drift from the packaged icon. GNOME (librsvg) and KDE (QtSvg) render theme SVGs natively (Breeze ships SVG-only icons), so no raster renditions are needed.Testing
Exec/TryExec/symlink/envwrapper/plain executable/indentedExec, provisioning incl. the hidden-entry contents and the icon file, refresh when the executable moves, persistence and repair across runs, never overwriting a foreign entry or a user-replaced icon, removal when a canonical entry appears, canonical-name migration, quote/escape handling, rejection of unrepresentable characters,$XDG_DATA_DIRSparsing and the$APPDIRexclusion.Execescaping verified againstdesktop-file-validate, GLib's actualGDesktopAppInfoloader and KDE'sKConfig/KShell.WAYLAND_DEBUG=1showsxdg_toplevel.set_app_id(...)and the provisioned files appear under$XDG_DATA_HOME;desktop-file-validatepasses on the generated entry.cargo fmt --all -- --check,cargo clippy --workspace --all-targets -- -D warnings,cargo test --workspace.Notes
~/.local/share/applications/media_sort.desktopis adopted as-is when that AppImage runs (itsExecmatches), so no duplicate hidden entry is created for it.xdg-toplevel-icon-v1is only partially implemented across compositors (KWin yes, Mutter/GNOME currently no), so even after iced ships a newer winit the desktop-entry route remains necessary for GNOME. When iced does, this module can be reduced to that protocol or removed - noted in AGENTS.md.