Stage 8a — Touch Application Launcher
Why this stage exists
0xin deliberately maps its top-edge gesture to an external command. Its phone profile previously spawned Fuzzel on a downward swipe and killed it on an upward swipe. That boundary means Patin can provide a replacement composition without becoming mandatory, depending on 0xin, or moving application policy into the compositor.
This first launcher stage is a useful touch target rather than a general app launcher framework: it discovers applications, renders a compact floating list, scrolls by pointer wheel or touch drag, starts one on tap, and exits. Keyboard search remains follow-up work.
Desktop entries and process launch
patin-launcher uses freedesktop-desktop-entry 0.8.1 with its optional
gettext feature disabled. apps::discover walks standard XDG application
locations, keeps the first occurrence of each desktop ID, applies localized
names and desktop visibility rules, skips hidden/non-application entries and
missing TryExec programs, then sorts names for a stable list.
Application::launch uses the library’s desktop Exec parser instead of a
shell. The resulting first argument is passed directly to Command and the
remaining arguments remain distinct, so desktop field codes are handled
without introducing shell interpolation. A declared working directory is
honored. Successful spawn closes the launcher; failure remains visible in the
overlay so the user can choose another app or dismiss it.
Floating layout, icons, scrolling, and lifecycle
The binary requests a full-output overlay-layer surface anchored on every edge,
reserves no exclusive zone, and requests no keyboard. Its buffer stays
transparent except for a centered 280×350 panel, so the launcher looks
floating and the rest of the output is not dimmed. The transparent part remains
inside the surface’s default input region: tapping it dismisses the launcher
and consumes the tap instead of activating the application beneath.
ui::Launcher::layout places a single column of application rows inside the
rounded dark panel and centers that panel from runtime logical output size.
The surface uses Patin’s deep-purple bar/lock palette. Its ten visible lines
use normal-weight 14px text and small 18px icons; there is no visible scrollbar
or per-row decoration.
Each row contains the localized application name and its desktop-entry icon.
freedesktop-icons 0.4.0 resolves the icon through the GTK-selected theme,
theme indexes and inheritance, XDG roots, hicolor, and pixmaps. The launcher
then uses a bounded exact-name traversal of the selected theme and hicolor
only when incomplete theme metadata hides a file that is actually installed.
Environment roots retain priority, followed by canonical system and Flatpak
roots so SSH or recovery-session environment differences do not hide icons.
The launcher
decodes PNG through image 0.25.10’s PNG-only feature and rasterizes SVG with
resvg 0.47.0 with all default features disabled; a neutral square is used
when no supported icon is available. The toolkit’s TextAlign::Start provides
the left-aligned names, while DrawCommand::Image keeps decoded RGBA rendering
behind Patin’s internal render boundary.
Shell::scroll_by is a defaulted vertical-scroll hook. The platform translates
pointer-axis values into it. Touch contacts now activate on release only when
they stayed within an eight-logical-pixel tap threshold; a drag instead emits
scroll deltas. ui::Launcher::scroll_by advances a clamped visible window.
This gives an fzf-like list motion without page controls, visible scroll
chrome, or accidental launches while swiping.
The toolkit gains one general lifecycle hook: Shell::close_requested defaults
to false, preserving every existing consumer. The platform checks it after
updates and activations and exits its Calloop loop when a finite composition
returns true. The launcher requests this after a successful spawn or a tap on
empty background.
The shared startup diagnostic now says it is waiting for the compositor to
configure a surface rather than a bar, since the same platform path also
runs overlays.
Installation and compositor mapping
Install the standalone user binary:
./scripts/install-launcher-user.sh
patin-launcher
0xin can replace only its swipe launcher while leaving other Fuzzel uses alone:
gesture = top-down, spawn, pgrep -x patin-launcher >/dev/null || patin-launcher
gesture = to-top, spawn, pkill -x patin-launcher
These are ordinary spawn mappings. Patin contains no 0xin branch, and another layer-shell compositor may bind the same executable however it prefers.
Changed files and important functions
crates/patin-launcherowns desktop discovery/launch, freedesktop theme icon resolution and loading, scrolling list state, hit-testing, rendering, and the standalone overlay entrypoint.src/ui.rsandsrc/render.rsadd start-aligned text and decoded RGBA image commands plus an explicit normal/semibold text-weight choice.src/platform.rsadds defaulted finite-composition and scroll hooks, pointer wheel translation, and tap-versus-drag touch handling.scripts/install-launcher-user.shbuilds the locked release package and installs only its executable under~/.local/bin.- The workspace manifest and lockfile pin the new consumer and parser; README, architecture, environment, and mdBook navigation document the boundary.
Verification
Verified on 31 July 2026:
$ cargo fmt --all -- --check
(no output, exit 0)
$ cargo test --workspace --all-targets
all passed
$ cargo clippy --workspace --all-targets --all-features -- -D warnings
Finished, no warnings
$ mdbook build
INFO HTML book written to `/home/vdzee/proj/patin/book`
$ git diff --check
(no output, exit 0)
Six launcher tests cover visible/hidden desktop entries, parsed launch
arguments, reverse-DNS icon-name preservation, centered fixed-panel containment
on a full-output surface, clamped scrolling, and outside-tap close requests. A
short local smoke run found 13
launchable applications and resolved 9 icons before reporting the expected
Could not find wayland compositor, because the
tool session does not inherit the laptop’s graphical Wayland environment.
The phone-native six-test suite and optimized build passed for the final
280×350 floating list. After removing the stale Kitty desktop entry, a live
run discovered 32 applications, resolved all 32 installed theme icons, connected
to 0xin, and exited before its timeout when the transparent area outside the
panel was tapped:
$ env -u LD_LIBRARY_PATH XDG_RUNTIME_DIR=/run/user/10000 \
WAYLAND_DISPLAY=wayland-0 timeout 30 ~/.local/bin/patin-launcher
patin-launcher: discovered 32 launchable applications (32 resolved icons)
patin: connected; waiting for the compositor to configure the surface
Physical tap-to-launch and the final 0xin gesture switch remain to be recorded.