Problem
deckd runs on Linux today only from a source checkout: just setup-linux (uv + Python + Node), just build-client, then just install-service, which sed-substitutes @PROJECT_DIR@ into packaging/systemd/deckd.service and points the user unit at .venv/bin/deckd. On top of that the user must hand-install the udev rule + input group membership (packaging/udev/70-deckd-uinput.rules) and the desktop-specific focus watcher (GNOME Shell extension / KWin script). That needs git, Python, Node, uv, and a checkout.
Nix users are covered by the flake (#17: packages.deckd + NixOS/home-manager modules own the service, udev rule, and group), but that's a different audience and a different install path.
#165 gives macOS a zero-toolchain path (self-contained .app in a DMG, built and published by a release workflow). Linux has no equivalent.
Goal
Any Linux user can install deckd with zero toolchain: download an artifact from a GitHub release, install it, grant /dev/uinput access once, install the focus watcher for their desktop, and run. No Python, Node, or checkout required.
Follows
#165 (macOS .app + DMG + release-macos.yml). Mirror the shape: a build recipe that produces a distributable artifact, and a release workflow that attaches it to the tag. Reuse the DECKD_VERSION version seam (#165) so tag and artifact naming stay consistent across platforms.
Candidate channels (to decide)
- Tarball + install script — simplest, distro-agnostic, but nothing owns the udev rule / group / service. Good phase-0 target.
.deb / .rpm — the package can own the udev rule, the systemd user unit, and a desktop file, and post-install can add the user to input. Needs per-distro CI.
- AppImage — one file, zero deps; but it can't write
/etc/udev/rules.d or add groups, and ships no systemd unit. Would need a first-run helper for the privileged steps.
- Flatpak / Snap — sandboxing conflicts with the daemon's model: it injects global input via
/dev/uinput, reads other apps' windows through a compositor plugin, and talks to the session D-Bus bus. The GUIDE already rules out Docker for exactly these reasons, so this is likely a poor fit — but worth a sentence in the exploration.
- Distro repos / AUR / Copr — community path, later; out of scope for a first artifact.
Linux-specific complications
- uinput needs root (udev rule +
input group), so the artifact can't be fully unprivileged — unlike the macOS DMG.
- The focus watcher is desktop-specific (GNOME Shell extension, KWin script) and can't ship inside a desktop-neutral daemon package.
- The
[dbus] extra is required on Linux (the dbus: action primitive + MPRIS); the macOS bundle deliberately omits it.
- Python runtime: bundle a private interpreter (PyInstaller, as macOS does; or a
uv standalone CPython) vs depend on the distro's Python.
- Arch: x86_64 + aarch64 (
evdev-binary has no aarch64 wheel — source build, see setup-linux).
- Client + layouts must be bundled (
client/dist, layouts/, layouts.*).
- Auto-start: systemd user unit vs an XDG autostart
.desktop.
Open questions
- Which channel first? (Tarball → deb/rpm → AppImage? Or straight to
.deb?)
- PyInstaller
onedir (reuse the macOS spec shape) vs a uv-built standalone CPython + venv.
- How much can the package do for the user (udev rule, group, service, focus watcher) without sudo surprises?
- One artifact per desktop, or one artifact plus an "install the focus watcher" step that detects GNOME/KDE/X11?
- Do we ship a tray equivalent, or keep the systemd user unit? (macOS needed a menu-bar app only because AppKit owns the main thread.)
Plan (draft)
Phase 0 — doable anywhere, testable on a checkout
- A build recipe (
just build-linux-app / build-linux-tarball) that assembles the runtime + client + layouts into a relocatable tree.
- A first-run/install script that installs the udev rule, adds the
input group, and writes the systemd user unit.
- Docs: install steps and the uinput caveat.
Phase 1 — on a Linux desktop
4. Run the artifact, fix packaging issues, verify the service + focus watcher + input injection.
Phase 2 — release pipeline
5. A GitHub Actions job that builds the artifact and attaches it to the release (mirror release-macos.yml).
Related
Problem
deckd runs on Linux today only from a source checkout:
just setup-linux(uv + Python + Node),just build-client, thenjust install-service, which sed-substitutes@PROJECT_DIR@intopackaging/systemd/deckd.serviceand points the user unit at.venv/bin/deckd. On top of that the user must hand-install the udev rule +inputgroup membership (packaging/udev/70-deckd-uinput.rules) and the desktop-specific focus watcher (GNOME Shell extension / KWin script). That needs git, Python, Node, uv, and a checkout.Nix users are covered by the flake (#17:
packages.deckd+ NixOS/home-manager modules own the service, udev rule, and group), but that's a different audience and a different install path.#165 gives macOS a zero-toolchain path (self-contained
.appin a DMG, built and published by a release workflow). Linux has no equivalent.Goal
Any Linux user can install deckd with zero toolchain: download an artifact from a GitHub release, install it, grant
/dev/uinputaccess once, install the focus watcher for their desktop, and run. No Python, Node, or checkout required.Follows
#165 (macOS
.app+ DMG +release-macos.yml). Mirror the shape: a build recipe that produces a distributable artifact, and a release workflow that attaches it to the tag. Reuse theDECKD_VERSIONversion seam (#165) so tag and artifact naming stay consistent across platforms.Candidate channels (to decide)
.deb/.rpm— the package can own the udev rule, the systemd user unit, and a desktop file, and post-install can add the user toinput. Needs per-distro CI./etc/udev/rules.dor add groups, and ships no systemd unit. Would need a first-run helper for the privileged steps./dev/uinput, reads other apps' windows through a compositor plugin, and talks to the session D-Bus bus. The GUIDE already rules out Docker for exactly these reasons, so this is likely a poor fit — but worth a sentence in the exploration.Linux-specific complications
inputgroup), so the artifact can't be fully unprivileged — unlike the macOS DMG.[dbus]extra is required on Linux (thedbus:action primitive + MPRIS); the macOS bundle deliberately omits it.uvstandalone CPython) vs depend on the distro's Python.evdev-binaryhas no aarch64 wheel — source build, seesetup-linux).client/dist,layouts/,layouts.*)..desktop.Open questions
.deb?)onedir(reuse the macOS spec shape) vs auv-built standalone CPython + venv.Plan (draft)
Phase 0 — doable anywhere, testable on a checkout
just build-linux-app/build-linux-tarball) that assembles the runtime + client + layouts into a relocatable tree.inputgroup, and writes the systemd user unit.Phase 1 — on a Linux desktop
4. Run the artifact, fix packaging issues, verify the service + focus watcher + input injection.
Phase 2 — release pipeline
5. A GitHub Actions job that builds the artifact and attaches it to the release (mirror
release-macos.yml).Related
.app+ DMG release (the template)