fix(headless/kde): make libei input work headlessly — portal + pre-seeded RemoteDesktop grant
ci / web (push) Successful in 27s
ci / bench (push) Successful in 1m41s
ci / docs-site (push) Successful in 31s
apple / swift (push) Successful in 1m31s
ci / rust (push) Successful in 2m5s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 5s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 5s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 3s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 4s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 4s
deb / build-publish (push) Successful in 2m17s
rpm / build-publish (bazzite, punktfunk-fedora-rpm) (push) Successful in 4m56s
docker / deploy-docs (push) Successful in 17s
rpm / build-publish (fedora-44, punktfunk-fedora44-rpm) (push) Successful in 4m25s

On a headless KDE appliance, libei input injection silently failed: the EIS socket comes from the
xdg RemoteDesktop portal, which never came up, and even up it would pop an unanswerable "Allow
remote control?" dialog. Three fixes in run-headless-kde.sh, all idempotent + safe on the dev box:
- Reach graphical-session.target: xdg-desktop-portal is ordered behind it and its start job fails
  without it, but a headless linger session never gets there and Fedora's target has
  RefuseManualStart=yes — drop that in once, then start the target.
- Start the portal with `start` (the old `try-restart` is a no-op when inactive — the first-boot
  case), so it actually comes up.
- Pre-seed the RemoteDesktop grant: vendor the `kde-authorized` permission-store GVariant DB and
  copy it to ~/.local/share/flatpak/db/ (never clobbering an existing one), so the portal grants
  RemoteDesktop without a dialog. Shipped by the RPM + .deb.

Diagnosed + fixed live on the Fedora 44 KDE box: libei devices RESUME and emit (MouseMove/keys).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-13 21:22:20 +00:00
parent fb1443650b
commit e4b10f057a
4 changed files with 33 additions and 5 deletions
+1
View File
@@ -47,6 +47,7 @@ sed -i 's#%h/punktfunk/target/release/punktfunk-host#/usr/bin/punktfunk-host#' \
"$STAGE/usr/lib/systemd/user/punktfunk-host.service" "$STAGE/usr/lib/systemd/user/punktfunk-host.service"
install -Dm0755 scripts/headless/run-headless-kde.sh "$SHAREDIR/headless/run-headless-kde.sh" install -Dm0755 scripts/headless/run-headless-kde.sh "$SHAREDIR/headless/run-headless-kde.sh"
install -Dm0755 scripts/headless/run-headless-sway.sh "$SHAREDIR/headless/run-headless-sway.sh" install -Dm0755 scripts/headless/run-headless-sway.sh "$SHAREDIR/headless/run-headless-sway.sh"
install -Dm0644 scripts/headless/kde-authorized "$SHAREDIR/headless/kde-authorized"
install -Dm0644 scripts/host.env.example "$SHAREDIR/host.env.example" install -Dm0644 scripts/host.env.example "$SHAREDIR/host.env.example"
install -Dm0644 packaging/bazzite/host.env "$SHAREDIR/host.env.bazzite" install -Dm0644 packaging/bazzite/host.env "$SHAREDIR/host.env.bazzite"
install -Dm0644 docs/api/openapi.json "$SHAREDIR/openapi.json" install -Dm0644 docs/api/openapi.json "$SHAREDIR/openapi.json"
+2
View File
@@ -159,6 +159,8 @@ install -Dm0644 scripts/70-punktfunk-client.rules \
install -d %{buildroot}%{_datadir}/%{name}/headless install -d %{buildroot}%{_datadir}/%{name}/headless
install -Dm0755 scripts/headless/run-headless-kde.sh %{buildroot}%{_datadir}/%{name}/headless/run-headless-kde.sh install -Dm0755 scripts/headless/run-headless-kde.sh %{buildroot}%{_datadir}/%{name}/headless/run-headless-kde.sh
install -Dm0755 scripts/headless/run-headless-sway.sh %{buildroot}%{_datadir}/%{name}/headless/run-headless-sway.sh install -Dm0755 scripts/headless/run-headless-sway.sh %{buildroot}%{_datadir}/%{name}/headless/run-headless-sway.sh
# RemoteDesktop grant pre-seed for headless libei input (run-headless-kde.sh copies it in).
install -Dm0644 scripts/headless/kde-authorized %{buildroot}%{_datadir}/%{name}/headless/kde-authorized
install -Dm0644 scripts/host.env.example %{buildroot}%{_datadir}/%{name}/host.env.example install -Dm0644 scripts/host.env.example %{buildroot}%{_datadir}/%{name}/host.env.example
install -Dm0644 packaging/bazzite/host.env %{buildroot}%{_datadir}/%{name}/host.env.bazzite install -Dm0644 packaging/bazzite/host.env %{buildroot}%{_datadir}/%{name}/host.env.bazzite
install -Dm0644 packaging/kde/host.env %{buildroot}%{_datadir}/%{name}/host.env.kde install -Dm0644 packaging/kde/host.env %{buildroot}%{_datadir}/%{name}/host.env.kde
Binary file not shown.
+30 -5
View File
@@ -102,11 +102,36 @@ fi
# D-Bus-activated X11 apps inherit it — then restart. # D-Bus-activated X11 apps inherit it — then restart.
systemctl --user import-environment WAYLAND_DISPLAY XDG_CURRENT_DESKTOP DBUS_SESSION_BUS_ADDRESS XDG_RUNTIME_DIR ${DISPLAY:+DISPLAY} 2>/dev/null || true systemctl --user import-environment WAYLAND_DISPLAY XDG_CURRENT_DESKTOP DBUS_SESSION_BUS_ADDRESS XDG_RUNTIME_DIR ${DISPLAY:+DISPLAY} 2>/dev/null || true
dbus-update-activation-environment --systemd WAYLAND_DISPLAY XDG_CURRENT_DESKTOP DBUS_SESSION_BUS_ADDRESS ${DISPLAY:+DISPLAY} 2>/dev/null || true dbus-update-activation-environment --systemd WAYLAND_DISPLAY XDG_CURRENT_DESKTOP DBUS_SESSION_BUS_ADDRESS ${DISPLAY:+DISPLAY} 2>/dev/null || true
# --no-block: queue the restart and return immediately. A synchronous try-restart of the
# portal chain blocks bring-up ~30s (xdg-desktop-portal is Type=dbus and waits for its bus # Pre-seed the RemoteDesktop grant for headless input injection (libei). KWin's xdg portal would
# name); the portal only needs to be ready before the FIRST client streams (seconds later, # otherwise pop an "Allow remote control?" dialog on every session Start() — which a headless host
# user-driven), not before plasmashell starts. # can't answer, so EIS setup times out and input silently fails. The `kde-authorized` permission-
systemctl --user --no-block try-restart plasma-xdg-desktop-portal-kde.service xdg-desktop-portal-kde.service xdg-desktop-portal.service 2>/dev/null || true # store table (this tiny GVariant DB, shipped next to this script) pre-authorizes it. Copy it in if
# the user has none yet; never clobber an existing one.
DB="$HOME/.local/share/flatpak/db/kde-authorized"
SELF_DIR="$(cd "$(dirname "$0")" && pwd)"
if [[ ! -s "$DB" && -s "$SELF_DIR/kde-authorized" ]]; then
mkdir -p "$(dirname "$DB")" && cp "$SELF_DIR/kde-authorized" "$DB"
echo "seeded RemoteDesktop grant: $DB"
fi
# Reach graphical-session.target so xdg-desktop-portal (which is ordered After / fails its start
# job without it) can come up — a headless linger session never gets there on its own, and Fedora's
# target carries RefuseManualStart=yes, so drop that in once. Without the portal, libei input fails.
GST_DROPIN="$HOME/.config/systemd/user/graphical-session.target.d"
if [[ ! -f "$GST_DROPIN/10-punktfunk-headless.conf" ]]; then
mkdir -p "$GST_DROPIN"
printf '[Unit]\nRefuseManualStart=no\n' > "$GST_DROPIN/10-punktfunk-headless.conf"
systemctl --user daemon-reload 2>/dev/null || true
fi
systemctl --user start graphical-session.target 2>/dev/null || true
# Bring the portal up against the env imported above. `start` (not the old `try-restart`, a no-op
# when inactive — the headless first-boot case) so it actually comes up; `--no-block` since
# xdg-desktop-portal is Type=dbus and blocks ~30s waiting for its bus name, and it only needs to be
# ready before the FIRST client streams (seconds later), not before plasmashell.
systemctl --user --no-block restart plasma-xdg-desktop-portal-kde.service 2>/dev/null || true
systemctl --user --no-block start xdg-desktop-portal.service 2>/dev/null || true
# Polkit authentication agent: without it, Discover / PackageKit can't get authorization to # Polkit authentication agent: without it, Discover / PackageKit can't get authorization to
# install packages (polkitd is the policy engine; the *agent* is the GUI prompt). A full Plasma # install packages (polkitd is the policy engine; the *agent* is the GUI prompt). A full Plasma