Serve only the desktop's primary screen over VNC

krdp streams every screen, so the VNC screen is now the primary's size and the
FreeRDP window is shifted so the primary fills it (ft-layout remote-view gives
the offset). It resizes and reconnects when the layout changes. With remote
access on, KWin's D-Bus screenshot interface is open too, for scripts that look
at the screens without the headset.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 6f5a23c80f2ecd26e6a96c86b102be36d518ee25)
This commit is contained in:
DeeJanuz committed 2026-09-30 15:50:46 -06:00
1 parent 97f853130d
commit 9820e7996f
5 files changed
+102 -31

No files matched your search

+5 -4
View File
@@ -169,13 +169,14 @@ export XDG_STATE_HOME=$HOME/.local/state/frametop
mkdir -p "$XDG_CONFIG_HOME" "$XDG_STATE_HOME"
# Remote desktop over VNC: session/remote-desktop.sh captures the desktop with
# krdp on 127.0.0.1, and session/vnc-bridge.sh re-serves it over VNC. krdpserver runs from the container, so KWin can't
# krdp on 127.0.0.1, and session/vnc-bridge.sh re-serves its primary screen over VNC. krdpserver runs from the container, so KWin can't
# match it to an installed app. KWin's permission check for screencast and fake
# input is turned off for this nested session only.
# input is turned off for this nested session only, and so is the check on KWin's
# D-Bus screenshot interface, which scripts use to see the screens without the headset.
if [ "$remote" = 1 ]; then
export KWIN_WAYLAND_NO_PERMISSION_CHECKS=1
export KWIN_WAYLAND_NO_PERMISSION_CHECKS=1 KWIN_SCREENSHOT_NO_PERMISSION_CHECKS=1
"$here/remote-desktop.sh" "$runtime" > /tmp/frametop-remote.log 2>&1 &
"$here/vnc-bridge.sh" "$width" "$height" > /tmp/frametop-vnc.log 2>&1 &
"$here/vnc-bridge.sh" > /tmp/frametop-vnc.log 2>&1 &
fi
# ft-floatd (floating windows) runs inside the Plasma session, on its D-Bus: started from
+64 -18
View File
@@ -1,14 +1,19 @@
#!/bin/bash
# Runs on the Frame host. Serves the Frametop desktop over VNC for clients
# like RealVNC Viewer or macOS Screen Sharing. No VNC server here can capture
# KWin directly, so this bridges through krdp: Xvnc (a virtual X screen served
# over VNC) runs a full-screen FreeRDP client connected to krdpserver on
# 127.0.0.1. Both run in the dev container. VNC listens on the tailnet address only.
# Runs on the Frame host. Serves the Frametop desktop's primary screen (the one with the
# taskbar) over VNC for clients like RealVNC Viewer or macOS Screen Sharing. No VNC server
# here can capture KWin directly, so this bridges through krdp: Xvnc (a virtual X screen
# served over VNC) runs a FreeRDP client connected to krdpserver on 127.0.0.1. Both run in
# the dev container. VNC listens on the tailnet address only.
# Started by frametop-session.sh when REMOTE=1, after remote-desktop.sh.
#
# krdp streams the whole workspace (every screen). Its --monitor would stream one screen,
# but krdp 6.7 then maps the pointer as if that screen sat at 0,0, so clicks miss on a
# screen placed lower or further right. Instead the VNC screen is the primary's size, and
# the workspace-sized RDP window inside it is shifted so the primary fills it. The pointer
# maps 1:1. When the layout changes, the VNC screen resizes and the RDP client reconnects.
set -eu
width=${1:-1920}
height=${2:-1080}
here=$(dirname "$(readlink -f "$0")")
vnc_port=${VNC_PORT:-5900}
rdp_port=${RDP_PORT:-3390}
display=:20
@@ -32,24 +37,65 @@ for _ in $(seq 60); do
sleep 1
done
# "x y width height workspace_width workspace_height" of the primary screen, once Plasma is up.
view() { "$here/../layout/ft-layout" remote-view 2>/dev/null | grep -xE '[0-9]+( [0-9]+){5}'; }
v=
for _ in $(seq 90); do
v=$(view) && [ -n "$v" ] && break
v=
sleep 1
done
if [ -z "$v" ]; then
echo "couldn't read the desktop's screens, not starting VNC" >&2
exit 1
fi
read -r _ _ w h _ _ <<< "$v"
export XDG_RUNTIME_DIR=/run/user/$(id -u)
exec ~/.local/bin/distrobox enter dev -- bash -c '
set -eu
creds=$1 addr=$2 vnc_port=$3 rdp_port=$4 display=$5 width=$6 height=$7
vncpasswd -f < "$creds/vnc-password" > "$creds/vnc-passwd.bin"
chmod 600 "$creds/vnc-passwd.bin"
Xvnc "$display" -geometry "${width}x${height}" -depth 24 \
box() { "$HOME/.local/bin/distrobox" enter dev -- "$@"; }
stop_rdp() { pkill -f "[x]freerdp /v:127.0.0.1:$rdp_port " 2>/dev/null || true; }
trap 'stop_rdp; pkill -f "[X]vnc $display " 2>/dev/null || true' EXIT
box bash -c 'vncpasswd -f < "$1/vnc-password" > "$1/vnc-passwd.bin" && chmod 600 "$1/vnc-passwd.bin"' - "$creds"
box Xvnc "$display" -geometry "${w}x${h}" -depth 24 \
-interface "$addr" -rfbport "$vnc_port" \
-SecurityTypes VncAuth -PasswordFile "$creds/vnc-passwd.bin" \
-AlwaysShared -desktop "Steam Frame (Frametop)" &
xvnc=$!
trap "kill $xvnc 2>/dev/null" EXIT
sleep 2
# Keep an RDP connection open inside the VNC screen. Reconnect if it drops.
# Keep an RDP connection open inside the VNC screen. Reconnect if it drops or the layout changes.
# /cert:ignore is fine here: the connection never leaves this host.
while kill -0 $xvnc 2>/dev/null; do
DISPLAY=$display xfreerdp /v:"127.0.0.1:$rdp_port" /u:steamos /p:"$(cat "$creds/password")" \
/cert:ignore /size:"${width}x${height}" -decorations /f +clipboard >/dev/null 2>&1 || true
read -r x y w h ww wh <<< "$v"
box env DISPLAY=$display bash -c '
size=$1 x=$2 y=$3 ww=$4 wh=$5 creds=$6 rdp_port=$7
if [ "$(xrandr | sed -n "s/.*current \([0-9]*\) x \([0-9]*\),.*/\1x\2/p")" != "$size" ]; then
xrandr --newmode "$size" 0 "${size%x*}" 0 0 0 "${size#*x}" 0 0 0 2>/dev/null || true
xrandr --addmode VNC-0 "$size" 2>/dev/null || true
xrandr --fb "$size" --output VNC-0 --mode "$size"
fi
xfreerdp /v:127.0.0.1:"$rdp_port" /u:steamos /p:"$(cat "$creds/password")" \
/cert:ignore /size:"${ww}x${wh}" -decorations +clipboard >/dev/null 2>&1 &
rdp=$!
# FreeRDP takes no negative position, so move its window once it is up.
for _ in $(seq 60); do
win=$(xdotool search --class xfreerdp 2>/dev/null | tail -1)
[ -n "$win" ] && break
sleep 0.5
done
[ -n "$win" ] && xdotool windowmove "$win" "$((-x))" "$((-y))"
wait $rdp
' vnc-rdp "${w}x$h" "$x" "$y" "$ww" "$wh" "$creds" "$rdp_port" || true &
rdp=$!
while kill -0 $rdp 2>/dev/null; do
sleep 5
now=$(view) || continue
[ -n "$now" ] && [ "$now" != "$v" ] || continue
echo "layout changed: $v -> $now"
v=$now
stop_rdp
done
wait $rdp 2>/dev/null || true
sleep 2
done
' vnc-bridge "$creds" "$addr" "$vnc_port" "$rdp_port" "$display" "$width" "$height"