mirror of
https://github.com/DeeJanuz/frametop.git
synced 2026-10-06 03:00:06 +02:00
Remote desktop: connect FreeRDP only while a VNC viewer is connected
vnc-bridge.sh kept FreeRDP connected to krdpserver from the moment remote desktop started, so krdp captured and H.264-encoded every KWin redraw in software (openh264) with nobody watching: krdpserver 55-78% of a core, xfreerdp 16-27%, Xvnc 6-11%, with 0 clients on :5900. krdp 6.7 creates its screencast session per RDP connection and drops it when the connection closes, so krdpserver itself idles without one and stays up. The bridge now counts established connections to Xvnc's port with ss, starts FreeRDP when a viewer appears (the desktop shows about 3 s later; the VNC screen is black until then) and stops it 45 s after the last one leaves (VNC_IDLE_SEC). Xvnc has no client hook, so its log output, which it writes for every connection, wakes the bridge early; otherwise it looks every 5 s while idle (0.1% of a core measured, against 0.9% for ss once a second) and every second while FreeRDP runs. The layout check runs only while FreeRDP runs. While a viewer is connected the bridge sends "watch 15" to ft-screens (@ft_screens) at once and every 5 s, so screens at a reduced frame rate (out of view, headset on a stand) stream at full rate; it lapses by itself if the bridge dies, and an ft-screens without the command just answers an error. The window search after starting FreeRDP now ends when FreeRDP exits instead of polling for 30 s. krdp on 127.0.0.1 with a fresh password, VNC on the tailnet address with VncAuth, and remote-ctl.sh start/stop (pause and resume) are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
06c4ff9556
commit
3e7248a04e
4 files changed
+98
-16
No files matched your search
+93
-15
@@ -11,6 +11,16 @@
|
||||
# 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.
|
||||
#
|
||||
# The RDP client runs only while someone watches. While connected, krdp captures and
|
||||
# H.264-encodes every redraw in software, about 60% of a core, with FreeRDP and Xvnc adding
|
||||
# about 30% more, even with no VNC viewer. krdp starts its capture per RDP connection and
|
||||
# stops it when the connection closes, so it idles without one. So FreeRDP starts when a
|
||||
# VNC client connects (the screen is black for the few seconds that takes) and stops
|
||||
# VNC_IDLE_SEC (45) seconds after the last one leaves. Xvnc has no hook for clients, so ss
|
||||
# counts them: whenever Xvnc writes to its log (it logs each connection), every second
|
||||
# while FreeRDP runs, and every 5 seconds otherwise. The log only wakes this script up;
|
||||
# what it says doesn't matter.
|
||||
set -eu
|
||||
|
||||
here=$(dirname "$(readlink -f "$0")")
|
||||
@@ -57,16 +67,45 @@ 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 \
|
||||
exec {xlog}< <(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)" &
|
||||
-AlwaysShared -desktop "Steam Frame (Frametop)" 2>&1)
|
||||
xvnc=$!
|
||||
sleep 2
|
||||
|
||||
# Keep an RDP connection open inside the VNC screen. Reconnect if it drops or the layout changes.
|
||||
# Wait up to $1 seconds, less if Xvnc logs something; its lines go on to this log.
|
||||
nap() {
|
||||
local line rc=0
|
||||
IFS= read -rt "$1" -u "$xlog" line || rc=$?
|
||||
if [ $rc -eq 0 ]; then
|
||||
printf '%s\n' "$line"
|
||||
while IFS= read -rt 0.1 -u "$xlog" line; do printf '%s\n' "$line"; done
|
||||
elif [ $rc -le 128 ]; then
|
||||
sleep 1 # Xvnc's output closed: it's exiting
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
nap 2
|
||||
|
||||
# A connected VNC client: an established TCP connection to Xvnc's port. Any connection
|
||||
# counts, authenticated or not; it's on the tailnet only.
|
||||
clients() { [ -n "$(ss -Htn state established "( sport = :$vnc_port )" 2>/dev/null)" ]; }
|
||||
|
||||
# ft-screens drops screens you aren't looking at to a low frame rate, and krdp would
|
||||
# stream that. "watch SECONDS" asks it for full rate on every screen for that long: sent
|
||||
# when a client connects and renewed every few seconds while one stays, so it lapses by
|
||||
# itself if this script dies. An older ft-screens just answers that it doesn't know it.
|
||||
watch() {
|
||||
if command -v socat >/dev/null; then
|
||||
printf 'watch 15' | socat -u - ABSTRACT-SENDTO:ft_screens 2>/dev/null
|
||||
else
|
||||
python3 -c 'import socket; socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM).sendto(b"watch 15", "\0ft_screens")' 2>/dev/null
|
||||
fi || true
|
||||
}
|
||||
|
||||
# Start FreeRDP inside the VNC screen, sized and shifted for $v.
|
||||
# /cert:ignore is fine here: the connection never leaves this host.
|
||||
while kill -0 $xvnc 2>/dev/null; do
|
||||
start_rdp() {
|
||||
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
|
||||
@@ -80,6 +119,7 @@ while kill -0 $xvnc 2>/dev/null; do
|
||||
rdp=$!
|
||||
# FreeRDP takes no negative position, so move its window once it is up.
|
||||
for _ in $(seq 60); do
|
||||
kill -0 $rdp 2>/dev/null || break
|
||||
win=$(xdotool search --class xfreerdp 2>/dev/null | tail -1)
|
||||
[ -n "$win" ] && break
|
||||
sleep 0.5
|
||||
@@ -88,14 +128,52 @@ while kill -0 $xvnc 2>/dev/null; do
|
||||
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
|
||||
}
|
||||
|
||||
idle_sec=${VNC_IDLE_SEC:-45}
|
||||
rdp= # FreeRDP's job while it runs
|
||||
seen=0 # when a client was last seen ($SECONDS)
|
||||
watched=-99 # when "watch" was last sent
|
||||
next_view=0 # next time to read the layout
|
||||
while kill -0 $xvnc 2>/dev/null; do
|
||||
if clients; then
|
||||
if [ $((SECONDS - watched)) -ge 5 ]; then watch; watched=$SECONDS; fi
|
||||
seen=$SECONDS
|
||||
if [ -z "$rdp" ]; then
|
||||
echo "VNC client connected, starting the RDP client"
|
||||
now=$(view) && [ -n "$now" ] && v=$now
|
||||
next_view=$((SECONDS + 5))
|
||||
start_rdp
|
||||
fi
|
||||
elif [ "$seen" -ne 0 ]; then
|
||||
watched=-99
|
||||
if [ $((SECONDS - seen)) -ge "$idle_sec" ]; then
|
||||
seen=0
|
||||
if [ -n "$rdp" ]; then
|
||||
echo "no VNC client for ${idle_sec}s, stopping the RDP client"
|
||||
stop_rdp
|
||||
wait "$rdp" 2>/dev/null || true
|
||||
rdp=
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
if [ -n "$rdp" ] && ! kill -0 "$rdp" 2>/dev/null; then
|
||||
# It dropped, or the layout changed: reconnect next round if a client is still there.
|
||||
wait "$rdp" 2>/dev/null || true
|
||||
rdp=
|
||||
nap 2
|
||||
continue
|
||||
fi
|
||||
if [ -n "$rdp" ]; then
|
||||
if [ "$SECONDS" -ge "$next_view" ]; then
|
||||
next_view=$((SECONDS + 5))
|
||||
now=$(view) || now=
|
||||
if [ -n "$now" ] && [ "$now" != "$v" ]; then
|
||||
echo "layout changed: $v -> $now"
|
||||
v=$now
|
||||
stop_rdp
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
if [ -n "$rdp" ] || [ "$seen" -ne 0 ]; then nap 1; else nap 5; fi
|
||||
done
|
||||
Reference in new issue
Block a user