This page documents how an installed web app becomes a first-class desktop citizen on Linux: the .desktop launcher, the icon set, the autostart entry, the window identity that ties them together, and the KDE window rule.
Desktop integration is the step that turns a stored Site into files the desktop environment understands. It runs on site install and site update, and is implemented per-platform; the Linux backend is src/integrations/implementation/linux.rs. Every file is keyed by the same identifier, classid = FFPWA-<ulid> (linux.rs:67), so an app’s launcher, icons, autostart entry, and KDE rule all share one stem.
| File | Path |
|---|---|
| Launcher | ~/.local/share/applications/FFPWA-<ulid>.desktop |
| Icons | ~/.local/share/icons/hicolor/<size>/apps/FFPWA-<ulid>.png (+ scalable/, symbolic/) |
| Autostart | ~/.config/autostart/FFPWA-<ulid>.desktop (only when launch-on-login) |
| KDE rule | ~/.config/kwinrulesrc → section [ffwebapps-FFPWA-<ulid>] (KDE only) |
The base directories come from the directories crate (~/.local/share, ~/.config), not from ffwebapps' own userdata — these live in the standard XDG locations so the desktop picks them up.
create_desktop_entry (linux.rs:199-303) writes the launcher. Its template is straightforward, but two lines carry the weight:
[Desktop Entry]
Type=Application
Name={name}
Comment={description}
Keywords={keywords};
Categories=GTK;{categories};
Icon=FFPWA-<ulid>
Exec={exe} site launch <ulid> --protocol %u
Actions={actions}
MimeType={protocols}
Terminal=false
StartupNotify=true
StartupWMClass=org.mozilla.firefox.webapp-<webapp_id>
The Exec line launches the app through ffwebapps itself — ffwebapps site launch <ulid> --protocol %u — not Firefox directly. That routes every menu/dock launch through the singleton check and tray spawn (see IPC & the Runtime-Owned Window). The %u lets the launcher carry a protocol-handler URL when the app is invoked as a handler.
The {exe} token is usually just the bare ffwebapps binary path, but for non-standard installs (a dev checkout, not /usr/bin + /usr/share/ffwebapps) it is wrapped to bake in the resolved directories so the launched binary finds its data:
Exec=env FFPWA_USERDATA=<userdata> FFPWA_SYSDATA=<sysdata> <bin> site launch <ulid> --protocol %u
For the desktop to treat a web app as its own application — distinct dock entry, correct icon, its own alt-tab slot — three identities have to line up. This is the single most important detail of the integration, and it is why webapp_id exists.
The chain (linux.rs:226-229, components/site.rs:284-299):
-taskbar-tab <webapp_id>, so Firefox gives the window the Wayland app_id org.mozilla.firefox.webapp-<webapp_id>..desktop file sets StartupWMClass=org.mozilla.firefox.webapp-<webapp_id> to that same string.app_id to the launcher’s StartupWMClass, so the window groups under the right launcher with the right icon.Separately, MOZ_APP_REMOTINGNAME=ffwebapps-<ulid> gives each app a unique Firefox remoting identity, which is what makes a relaunch focus the existing window instead of spawning a duplicate (see IPC & the Runtime-Owned Window). When webapp_id is absent (a very old app not yet relaunched), StartupWMClass falls back to FFPWA-<ulid>.
store_icons (linux.rs:92-186) writes the icon set into the hicolor theme, keyed on classid:
| Source | Destination |
|---|---|
SVG, purpose Any |
icons/hicolor/scalable/apps/FFPWA-<ulid>.svg |
SVG, purpose Monochrome |
icons/hicolor/symbolic/apps/FFPWA-<ulid>-symbolic.svg |
Raster (Any) |
icons/hicolor/<w>x<h>/apps/FFPWA-<ulid>.png at native size |
A 48×48 PNG is mandatory under the Icon Theme Spec; if the manifest supplies none, ffwebapps generates one. Icons are fetched and rendered by integrations/utils.rs: manifest icons are normalized and ranked (exact-size → Any-size → nearest-larger → nearest-smaller), SVGs are rasterized with resvg/usvg/tiny_skia (with external resource loading disabled), and rasters are resized with Lanczos3. When everything fails, generate_fallback_icon renders the app name’s first letter in white on a gray background using the bundled Metropolis-SemiBold.otf.
Categories= is built by mapping the site’s W3C/manifest categories to FreeDesktop menu categories through a compile-time phf map (categories.rs). For example music → Audio;AudioVideo, webdevelopment → WebDevelopment;Development;Network. Matches are accumulated, sorted, de-duplicated, and emitted with a leading GTK;.
Manifest shortcuts become [Desktop Action <i>] blocks appended to the .desktop file (linux.rs:270-296), each with its own Exec={exe} site launch <ulid> --url "<url>" and, optionally, its own icon stored under FFPWA-<ulid>-<i>. These are the right-click “jump list” entries a launcher shows for the app.
create_startup_entry (linux.rs:305-333) manages launch-on-login. When launch_on_login is set, it copies the already-written applications/FFPWA-<ulid>.desktop into ~/.config/autostart/. If start_hidden is also set, it rewrites the copied Exec line, appending --hidden so the login launch goes straight to the tray rather than popping the window:
Exec=… site launch <ulid> --hidden
When launch_on_login is false, any existing autostart entry is removed. The --hidden flag flows through to the runtime as FFWEBAPPS_START_HIDDEN=1, which the autoconfig reads to hide the window once it first maps (see The Firefox Runtime & Autoconfig). The tray’s “Start on login” checkbox toggles exactly this state via site update --launch-on-login --start-hidden.
On KDE, write_kwin_rule (linux.rs:372-446) adds a per-app rule to ~/.config/kwinrulesrc so KWin restores the window’s position across hide/show cycles. It surgically upserts a section and keeps [General]’s rules=/count= in sync:
[ffwebapps-FFPWA-<ulid>]
Description=ffwebapps: remember window position for <name>
positionrule=4
wmclass=org.mozilla.firefox.webapp-<webapp_id>
wmclassmatch=1
types=1
positionrule=4 is KWin’s “Remember” policy: it saves and restores the position of the matching window. No initial position= is written, so the first launch gets normal placement; KWin learns the position afterward. After writing, ffwebapps asks KWin to reload its config over D-Bus (qdbus6 → org.kde.KWin.reconfigure).
This rule only acts when a webapp_id exists, and only on KDE (XDG_CURRENT_DESKTOP contains KDE). It complements — but is independent of — the runtime’s own off-screen-move hide mechanism, which preserves position even without the rule. The handoff notes flag the rule as somewhat inert in practice and a possible cleanup candidate; the runtime mechanism is what actually guarantees position today.
After writing (or removing) files, update_application_cache (linux.rs:41-48) nudges the desktop to notice: it touches the icon directories and runs update-desktop-database, update-mime-database, gtk-update-icon-cache, and xdg-desktop-menu forceupdate. Without this, a freshly installed app might not appear in the menu until the next login.
Uninstall is the mirror image (linux.rs:522-543): remove the icons (globbed as …/apps/FFPWA-<ulid>*), the launcher, the autostart entry, and the KWin rule section, then refresh the caches. Because every artifact shares the FFPWA-<ulid> stem, the removal is exact and complete.