fix(host/kwin): authorize Desktop-mode streaming via a shipped .desktop
Streaming the KDE *Desktop* (KWin) session failed on a real interactive Plasma session with "KWin does not expose zkde_screencast_unstable_v1": KWin treats the screencast/virtual-output and fake_input globals as restricted and advertises them only to a client whose installed .desktop lists them under X-KDE-Wayland-Interfaces (matched by /proc/<pid>/exe -> Exec, and cached per-executable on first connect). The host shipped no .desktop, so it was permanently denied; it only ever worked on the headless dev box via KWIN_WAYLAND_NO_PERMISSION_CHECKS=1. Ship packaging/linux/io.unom.Punktfunk.Host.desktop (least-privilege: only the host, only zkde_screencast_unstable_v1 + org_kde_kwin_fake_input) and install it from the RPM/.deb/Arch host packaging so it is present before the host first connects. Drop the blunt session-wide NO_PERMISSION_CHECKS hack from kde-desktop-setup.sh (it now only seeds the RemoteDesktop input grant) and fix the now-misleading kwin.rs docs/errors. Validated live on a Bazzite Kinoite box (KWin 6.6.4): probe-compositor + spike --source kwin-virtual succeed against a KWin running WITHOUT the permission bypass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -6,8 +6,14 @@
|
|||||||
//! node for it. The node lives on the user's default PipeWire daemon, so [`VirtualOutput::remote_fd`]
|
//! node for it. The node lives on the user's default PipeWire daemon, so [`VirtualOutput::remote_fd`]
|
||||||
//! is `None` and capture connects to that daemon directly.
|
//! is `None` and capture connects to that daemon directly.
|
||||||
//!
|
//!
|
||||||
//! Requirements: KWin must expose the privileged `zkde_screencast` global — a real Plasma session
|
//! Requirements: KWin must expose the privileged `zkde_screencast` global. It is a *restricted*
|
||||||
//! authorizes it for its own clients; the headless test exposes it to bare clients via
|
//! protocol — KWin advertises it only to a client whose installed `.desktop` lists it under
|
||||||
|
//! `X-KDE-Wayland-Interfaces` (KWin maps the connecting client to a `.desktop` by resolving
|
||||||
|
//! `/proc/<pid>/exe` against `Exec=`, then caches the grant per-executable for the session's life).
|
||||||
|
//! So an interactive Plasma session does NOT hand it to a bare client — the host packages ship
|
||||||
|
//! `io.unom.Punktfunk.Host.desktop` (`Exec=/usr/bin/punktfunk-host`,
|
||||||
|
//! `X-KDE-Wayland-Interfaces=zkde_screencast_unstable_v1,…`) so it is present before the host first
|
||||||
|
//! connects. The headless test path instead exposes it to bare clients via
|
||||||
//! `KWIN_WAYLAND_NO_PERMISSION_CHECKS=1`. The compositor backend must implement
|
//! `KWIN_WAYLAND_NO_PERMISSION_CHECKS=1`. The compositor backend must implement
|
||||||
//! `createVirtualOutput`: the **DRM backend** (any version) or the **VirtualBackend since KWin
|
//! `createVirtualOutput`: the **DRM backend** (any version) or the **VirtualBackend since KWin
|
||||||
//! 6.5.6** (`kwin_wayland --virtual`); on `--virtual` < 6.5.6 the request fails with
|
//! 6.5.6** (`kwin_wayland --virtual`); on `--virtual` < 6.5.6 the request fails with
|
||||||
@@ -406,9 +412,11 @@ pub fn probe() -> Result<()> {
|
|||||||
queue.roundtrip(&mut state).context("registry roundtrip")?;
|
queue.roundtrip(&mut state).context("registry roundtrip")?;
|
||||||
if state.screencast.is_none() {
|
if state.screencast.is_none() {
|
||||||
bail!(
|
bail!(
|
||||||
"KWin is up but does not (yet) expose zkde_screencast_unstable_v1 — needs a real \
|
"KWin is up but does not expose zkde_screencast_unstable_v1 to this client — KWin gates \
|
||||||
KDE session (or KWIN_WAYLAND_NO_PERMISSION_CHECKS=1), and KWin ≥ 6.5.6 for the \
|
it on the host's .desktop X-KDE-Wayland-Interfaces (install \
|
||||||
headless virtual output"
|
io.unom.Punktfunk.Host.desktop with Exec=/usr/bin/punktfunk-host, then re-login so KWin \
|
||||||
|
re-reads it — the grant is cached per-exe on first connect), or set \
|
||||||
|
KWIN_WAYLAND_NO_PERMISSION_CHECKS=1 for the headless test; needs KWin ≥ 6.5.6"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -437,8 +445,9 @@ fn run(
|
|||||||
|
|
||||||
let screencast = state.screencast.clone().ok_or_else(|| {
|
let screencast = state.screencast.clone().ok_or_else(|| {
|
||||||
anyhow!(
|
anyhow!(
|
||||||
"KWin does not expose zkde_screencast_unstable_v1 (need a real KDE session, or run \
|
"KWin does not expose zkde_screencast_unstable_v1 to this client — install the host's \
|
||||||
KWin with KWIN_WAYLAND_NO_PERMISSION_CHECKS=1 for the headless test)"
|
.desktop (io.unom.Punktfunk.Host.desktop, X-KDE-Wayland-Interfaces) and re-login so \
|
||||||
|
KWin authorizes it, or run KWin with KWIN_WAYLAND_NO_PERMISSION_CHECKS=1 (headless test)"
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
|||||||
@@ -86,6 +86,12 @@ package_punktfunk-host() {
|
|||||||
install -Dm0644 "$R/scripts/punktfunk-kde-session.service" "$pkgdir/usr/lib/systemd/user/punktfunk-kde-session.service"
|
install -Dm0644 "$R/scripts/punktfunk-kde-session.service" "$pkgdir/usr/lib/systemd/user/punktfunk-kde-session.service"
|
||||||
sed -i 's#%h/punktfunk/scripts/headless/run-headless-kde.sh#/usr/share/punktfunk/headless/run-headless-kde.sh#' \
|
sed -i 's#%h/punktfunk/scripts/headless/run-headless-kde.sh#/usr/share/punktfunk/headless/run-headless-kde.sh#' \
|
||||||
"$pkgdir/usr/lib/systemd/user/punktfunk-kde-session.service"
|
"$pkgdir/usr/lib/systemd/user/punktfunk-kde-session.service"
|
||||||
|
# KWin Desktop-mode authorization: non-launcher .desktop whose X-KDE-Wayland-Interfaces lets the
|
||||||
|
# host bind KWin's restricted zkde_screencast (virtual output) + fake_input globals on an
|
||||||
|
# interactive Plasma session. Must ship with the host (KWin caches the per-exe grant on first
|
||||||
|
# connect). See the file's header comment.
|
||||||
|
install -Dm0644 "$R/packaging/linux/io.unom.Punktfunk.Host.desktop" \
|
||||||
|
"$pkgdir/usr/share/applications/io.unom.Punktfunk.Host.desktop"
|
||||||
# headless session helpers + env templates + OpenAPI doc
|
# headless session helpers + env templates + OpenAPI doc
|
||||||
install -Dm0755 "$R/scripts/headless/run-headless-kde.sh" "$pkgdir/usr/share/punktfunk/headless/run-headless-kde.sh"
|
install -Dm0755 "$R/scripts/headless/run-headless-kde.sh" "$pkgdir/usr/share/punktfunk/headless/run-headless-kde.sh"
|
||||||
install -Dm0755 "$R/scripts/headless/run-headless-sway.sh" "$pkgdir/usr/share/punktfunk/headless/run-headless-sway.sh"
|
install -Dm0755 "$R/scripts/headless/run-headless-sway.sh" "$pkgdir/usr/share/punktfunk/headless/run-headless-sway.sh"
|
||||||
|
|||||||
@@ -1,35 +1,36 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# One-shot setup so the punktfunk host can stream the Bazzite KDE *Desktop* session (KWin virtual
|
# One-shot setup so the punktfunk host can INJECT INPUT while streaming the Bazzite KDE *Desktop*
|
||||||
# output at the client's resolution). Run ONCE as the streaming user (no root needed). Gaming Mode
|
# session. Run ONCE as the streaming user (no root needed). Gaming Mode (gamescope) needs none of
|
||||||
# (gamescope) needs none of this — it auto-attaches. Idempotent: safe to re-run.
|
# this — it auto-attaches. Idempotent: safe to re-run.
|
||||||
#
|
#
|
||||||
# bash /usr/share/punktfunk/bazzite/kde-desktop-setup.sh
|
# bash /usr/share/punktfunk/bazzite/kde-desktop-setup.sh
|
||||||
#
|
#
|
||||||
# Two things a normal KDE login lacks that the headless host needs:
|
# The VIRTUAL OUTPUT (video) needs no setup: the host package ships io.unom.Punktfunk.Host.desktop,
|
||||||
# 1. KWIN_WAYLAND_NO_PERMISSION_CHECKS=1 — so KWin exposes the privileged `zkde_screencast`
|
# whose X-KDE-Wayland-Interfaces grants the host KWin's restricted zkde_screencast protocol on a
|
||||||
# virtual-output protocol to the host (an external client) at all.
|
# normal interactive Plasma session — least-privilege (only the host, only that interface), the same
|
||||||
# 2. The `kde-authorized` RemoteDesktop grant — so libei input setup auto-approves instead of
|
# mechanism krfb/krdp use. No session-wide KWIN_WAYLAND_NO_PERMISSION_CHECKS hack is needed. KWin
|
||||||
# popping an "Allow remote control?" dialog the headless host can't answer.
|
# caches the grant per-executable on first connect, so after a FRESH host install log out + back into
|
||||||
# After running, log out + back into the KDE Desktop session once (or reboot) so KWin restarts
|
# the Desktop session once so KWin re-reads the file.
|
||||||
# with the flag. Gaming Mode is unaffected.
|
#
|
||||||
|
# The one thing a normal KDE login still lacks is the `kde-authorized` RemoteDesktop grant — so the
|
||||||
|
# host's libei input setup auto-approves instead of popping an "Allow remote control?" dialog the
|
||||||
|
# headless host can't answer. That's what this script seeds.
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
GRANT_SRC="${PUNKTFUNK_GRANT_SRC:-/usr/share/punktfunk/headless/kde-authorized}"
|
GRANT_SRC="${PUNKTFUNK_GRANT_SRC:-/usr/share/punktfunk/headless/kde-authorized}"
|
||||||
ENVD="$HOME/.config/environment.d/10-punktfunk-kwin.conf"
|
|
||||||
GRANT_DST="$HOME/.local/share/flatpak/db/kde-authorized"
|
GRANT_DST="$HOME/.local/share/flatpak/db/kde-authorized"
|
||||||
|
# Older versions of this script wrote a session-wide KWIN_WAYLAND_NO_PERMISSION_CHECKS=1 env file to
|
||||||
|
# unlock screencast. The shipped .desktop replaces it; remove the stale, over-broad override.
|
||||||
|
STALE_ENVD="$HOME/.config/environment.d/10-punktfunk-kwin.conf"
|
||||||
|
|
||||||
echo "punktfunk: KDE Desktop-mode setup"
|
echo "punktfunk: KDE Desktop-mode input setup"
|
||||||
|
|
||||||
# 1. KWin permission-check bypass (persistent, picked up by the next KDE session via systemd).
|
if [[ -f "$STALE_ENVD" ]] && grep -q KWIN_WAYLAND_NO_PERMISSION_CHECKS "$STALE_ENVD" 2>/dev/null; then
|
||||||
mkdir -p "$(dirname "$ENVD")"
|
rm -f "$STALE_ENVD"
|
||||||
cat > "$ENVD" <<'EOF'
|
echo " removed stale $STALE_ENVD (screencast is now granted via the shipped .desktop)"
|
||||||
# punktfunk: let the streaming host bind KWin's privileged zkde_screencast (virtual output).
|
fi
|
||||||
# A dedicated streaming box; this relaxes KWin's Wayland permission checks for the desktop path.
|
|
||||||
KWIN_WAYLAND_NO_PERMISSION_CHECKS=1
|
|
||||||
EOF
|
|
||||||
echo " wrote $ENVD"
|
|
||||||
|
|
||||||
# 2. RemoteDesktop portal grant for headless libei input (never clobber an existing one).
|
# RemoteDesktop portal grant for headless libei input (never clobber an existing one).
|
||||||
if [[ -s "$GRANT_DST" ]]; then
|
if [[ -s "$GRANT_DST" ]]; then
|
||||||
echo " grant DB already present ($GRANT_DST) — leaving it"
|
echo " grant DB already present ($GRANT_DST) — leaving it"
|
||||||
elif [[ -s "$GRANT_SRC" ]]; then
|
elif [[ -s "$GRANT_SRC" ]]; then
|
||||||
@@ -44,5 +45,5 @@ else
|
|||||||
echo " WARN: grant source not found at $GRANT_SRC — input will need a manual portal approval" >&2
|
echo " WARN: grant source not found at $GRANT_SRC — input will need a manual portal approval" >&2
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "punktfunk: done. Log out + back into the KDE Desktop session (or reboot) so KWin restarts"
|
echo "punktfunk: done. On a fresh host install, log out + back into the KDE Desktop session once"
|
||||||
echo " with the flag, then connect a client while in Desktop Mode."
|
echo " (so KWin authorizes the host's virtual output), then connect a client in Desktop Mode."
|
||||||
|
|||||||
@@ -50,6 +50,13 @@ sed -i 's#%h/punktfunk/target/release/punktfunk-host#/usr/bin/punktfunk-host#' \
|
|||||||
install -Dm0644 scripts/punktfunk-kde-session.service "$STAGE/usr/lib/systemd/user/punktfunk-kde-session.service"
|
install -Dm0644 scripts/punktfunk-kde-session.service "$STAGE/usr/lib/systemd/user/punktfunk-kde-session.service"
|
||||||
sed -i 's#%h/punktfunk/scripts/headless/run-headless-kde.sh#/usr/share/punktfunk-host/headless/run-headless-kde.sh#' \
|
sed -i 's#%h/punktfunk/scripts/headless/run-headless-kde.sh#/usr/share/punktfunk-host/headless/run-headless-kde.sh#' \
|
||||||
"$STAGE/usr/lib/systemd/user/punktfunk-kde-session.service"
|
"$STAGE/usr/lib/systemd/user/punktfunk-kde-session.service"
|
||||||
|
|
||||||
|
# KWin Desktop-mode authorization: non-launcher .desktop whose X-KDE-Wayland-Interfaces lets the
|
||||||
|
# host bind KWin's restricted zkde_screencast (virtual output) + fake_input globals on an
|
||||||
|
# interactive Plasma session. Must ship with the host — KWin caches the per-exe grant on first
|
||||||
|
# connect, so it has to be present before the host ever connects. See the file's header comment.
|
||||||
|
install -Dm0644 packaging/linux/io.unom.Punktfunk.Host.desktop \
|
||||||
|
"$STAGE/usr/share/applications/io.unom.Punktfunk.Host.desktop"
|
||||||
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/headless/kde-authorized "$SHAREDIR/headless/kde-authorized"
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
[Desktop Entry]
|
||||||
|
Type=Application
|
||||||
|
Name=Punktfunk Host
|
||||||
|
Comment=punktfunk streaming host — KWin virtual-output / input authorization
|
||||||
|
Exec=/usr/bin/punktfunk-host
|
||||||
|
Terminal=false
|
||||||
|
NoDisplay=true
|
||||||
|
# This file is NOT a launcher — it exists so KWin authorizes the host to bind its restricted
|
||||||
|
# Wayland globals when streaming the *Desktop* (KWin) session. KWin maps a connecting client to a
|
||||||
|
# .desktop by resolving /proc/<pid>/exe against `Exec` (hence the absolute /usr/bin path), then
|
||||||
|
# grants only the interfaces listed here (the same mechanism krfb-virtualmonitor / krdpserver use):
|
||||||
|
# * zkde_screencast_unstable_v1 — create the per-session virtual output at the client's mode.
|
||||||
|
# * org_kde_kwin_fake_input — inject input directly (no RemoteDesktop portal dialog).
|
||||||
|
# Comma-separated, per KWin's parser. Without this file KWin never advertises these to the host and
|
||||||
|
# desktop-mode streaming fails with "KWin does not expose zkde_screencast_unstable_v1". Gaming Mode
|
||||||
|
# (gamescope) does not use this path. NOTE: KWin caches the per-executable grant on first connect,
|
||||||
|
# so this must be installed *before* the host first connects (a package install satisfies that; an
|
||||||
|
# already-running KWin session needs a re-login to pick it up).
|
||||||
|
X-KDE-Wayland-Interfaces=zkde_screencast_unstable_v1,org_kde_kwin_fake_input
|
||||||
@@ -196,6 +196,14 @@ sed -i 's#%h/punktfunk/target/release/punktfunk-host#%{_bindir}/punktfunk-host#'
|
|||||||
install -Dm0644 scripts/punktfunk-kde-session.service %{buildroot}%{_userunitdir}/punktfunk-kde-session.service
|
install -Dm0644 scripts/punktfunk-kde-session.service %{buildroot}%{_userunitdir}/punktfunk-kde-session.service
|
||||||
sed -i 's#%h/punktfunk/scripts/headless/run-headless-kde.sh#%{_datadir}/%{name}/headless/run-headless-kde.sh#' %{buildroot}%{_userunitdir}/punktfunk-kde-session.service
|
sed -i 's#%h/punktfunk/scripts/headless/run-headless-kde.sh#%{_datadir}/%{name}/headless/run-headless-kde.sh#' %{buildroot}%{_userunitdir}/punktfunk-kde-session.service
|
||||||
|
|
||||||
|
# KWin authorization for Desktop-mode (KWin) streaming: a non-launcher .desktop whose
|
||||||
|
# X-KDE-Wayland-Interfaces grants the host the restricted zkde_screencast (virtual output) +
|
||||||
|
# fake_input globals on an interactive Plasma session. Must ship with the host so it is present
|
||||||
|
# before the host first connects (KWin caches the per-exe grant). Replaces the old manual
|
||||||
|
# KWIN_WAYLAND_NO_PERMISSION_CHECKS hack for the screencast permission.
|
||||||
|
install -Dm0644 packaging/linux/io.unom.Punktfunk.Host.desktop \
|
||||||
|
%{buildroot}%{_datadir}/applications/io.unom.Punktfunk.Host.desktop
|
||||||
|
|
||||||
# --- client subpackage ---
|
# --- client subpackage ---
|
||||||
install -Dm0755 target/release/punktfunk-client %{buildroot}%{_bindir}/punktfunk-client
|
install -Dm0755 target/release/punktfunk-client %{buildroot}%{_bindir}/punktfunk-client
|
||||||
install -Dm0644 packaging/linux/io.unom.Punktfunk.desktop \
|
install -Dm0644 packaging/linux/io.unom.Punktfunk.desktop \
|
||||||
@@ -221,7 +229,8 @@ install -Dm0644 scripts/headless/punktfunk-sink.conf %{buildroot}%{_datadir}/%
|
|||||||
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
|
||||||
# Bazzite KDE Desktop-mode one-shot setup (KWIN_WAYLAND_NO_PERMISSION_CHECKS + RemoteDesktop grant).
|
# Bazzite KDE Desktop-mode one-shot setup (seeds the RemoteDesktop grant for libei input; the
|
||||||
|
# screencast/virtual-output grant ships as io.unom.Punktfunk.Host.desktop, installed above).
|
||||||
install -d %{buildroot}%{_datadir}/%{name}/bazzite
|
install -d %{buildroot}%{_datadir}/%{name}/bazzite
|
||||||
install -Dm0755 packaging/bazzite/kde-desktop-setup.sh %{buildroot}%{_datadir}/%{name}/bazzite/kde-desktop-setup.sh
|
install -Dm0755 packaging/bazzite/kde-desktop-setup.sh %{buildroot}%{_datadir}/%{name}/bazzite/kde-desktop-setup.sh
|
||||||
install -Dm0644 api/openapi.json %{buildroot}%{_datadir}/%{name}/openapi.json
|
install -Dm0644 api/openapi.json %{buildroot}%{_datadir}/%{name}/openapi.json
|
||||||
@@ -252,6 +261,7 @@ install -Dm0644 web/web.env.example %{buildroot}%{_datadir}/punkt
|
|||||||
%{_prefix}/lib/sysctl.d/99-punktfunk-net.conf
|
%{_prefix}/lib/sysctl.d/99-punktfunk-net.conf
|
||||||
%{_userunitdir}/punktfunk-host.service
|
%{_userunitdir}/punktfunk-host.service
|
||||||
%{_userunitdir}/punktfunk-kde-session.service
|
%{_userunitdir}/punktfunk-kde-session.service
|
||||||
|
%{_datadir}/applications/io.unom.Punktfunk.Host.desktop
|
||||||
%dir %{_datadir}/%{name}
|
%dir %{_datadir}/%{name}
|
||||||
%{_datadir}/%{name}/*
|
%{_datadir}/%{name}/*
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user