diff --git a/docs/design.md b/docs/design.md index a743a22..435baa9 100644 --- a/docs/design.md +++ b/docs/design.md @@ -177,7 +177,7 @@ The private runtime directory also moves the session's document portal to `$XDG_ A podman container's monitor process (conmon) stays in the cgroup of whatever started the container, and `distrobox enter` starts it on demand. When a Frametop service happened to start the `dev` container, stopping that service stopped the container and everything in it, including the desktop's compositor. `scripts/container-up.sh` starts the container in a systemd scope of its own before anything enters it. It then waits for distrobox-init to log `container_setup_done`, as `distrobox enter` does only for containers it starts itself. A new container's first start takes a minute or more (it installs distrobox's dependencies and sets up passwordless sudo), and an install that entered right away met a sudo password prompt with no terminal to answer it ([#9](https://github.com/DeeJanuz/frametop/issues/9)). -Remote desktop is a chain (krdp, then FreeRDP inside Xvnc) because nothing on SteamOS serves KWin over VNC directly. Kept connected all the time, it cost about a core with nobody watching: krdpserver 55 to 78% (it encodes H.264 in software with openh264: VA-API finds no driver for the Frame's GPU in the container), FreeRDP 16 to 27%, Xvnc 6 to 11%. krdp creates its screencast session per RDP connection and drops it when the connection closes (`SessionController::onNewConnection` in krdp 6.7), so an idle krdpserver costs nothing and can stay up; only the RDP connection has to go. The bridge connects FreeRDP when a VNC client appears and disconnects 45 seconds after the last one leaves. Xvnc has no hook for its clients, so the bridge counts established connections to its port with `ss`, woken early by Xvnc's log output; looking with `ss` once a second cost about 0.9% of a core in bash, against about 0.1% this way. `Xvnc -inetd` from a systemd socket would start a server per connection and lose sharing between viewers. +Remote desktop is a chain (krdp, then FreeRDP inside Xvnc) because nothing on SteamOS serves KWin over VNC directly. Kept connected all the time, it cost about a core with nobody watching: krdpserver 55 to 78% (it encodes H.264 in software with openh264: VA-API finds no driver for the Frame's GPU in the container), FreeRDP 16 to 27%, Xvnc 6 to 11%, and the bridge's layout check every 5 seconds another 4%. krdp creates its screencast session per RDP connection and drops it when the connection closes (`SessionController::onNewConnection` in krdp 6.7), so an idle krdpserver costs nothing and can stay up; only the RDP connection has to go. The bridge connects FreeRDP when a VNC client appears and disconnects 45 seconds after the last one leaves. Xvnc has no hook for its clients, so the bridge counts established connections to its port with `ss`, woken early by Xvnc's log output; looking with `ss` once a second cost about 0.9% of a core in bash, against about 0.1% this way. `Xvnc -inetd` from a systemd socket would start a server per connection and lose sharing between viewers. The layout check (`ft-layout remote-view`, which scans `/proc` for plasmashell and runs `kscreen-doctor -j`) now runs only while FreeRDP runs, and then only after `kwinoutputconfig.json` or `frametop-layout.json` changes, with one check a minute in case a change touched neither. Program names stay within 15 characters, because Linux truncates process names there and the scripts find programs with `pgrep -x` and `pkill -x`. That's why the prefix is `ft-`. diff --git a/docs/reference.md b/docs/reference.md index 46d9119..6bff2ae 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -259,7 +259,7 @@ It listens on port 5900 on the Frame's Tailscale address only, not the LAN, so i No VNC server can capture KWin on SteamOS directly: `krfb` needs `xdg-desktop-portal-kde`, which SteamOS doesn't ship, and `wayvnc` only works with wlroots compositors. So `session/remote-desktop.sh` captures the desktop with KDE's `krdpserver --plasma` on `127.0.0.1:3390`, and `session/vnc-bridge.sh` runs TigerVNC's `Xvnc` on display `:20` with a FreeRDP client inside it and serves that. Both run in the `dev` container, and the extra hop adds a little latency. krdp streams every screen; the VNC screen is the primary's size, and the FreeRDP window is shifted so the primary fills it (`ft-layout remote-view` gives the offset). krdp's own `--monitor` would stream just one screen, but it maps the pointer as if that screen sat at 0,0, so clicks would miss. When the layout changes, the VNC screen resizes and FreeRDP reconnects within a few seconds. -FreeRDP runs only while a VNC viewer is connected, because while it's connected krdp captures and encodes every redraw. With no viewer, krdp has no RDP connection and so captures nothing, and Xvnc shows a black screen. When a viewer connects, the bridge starts FreeRDP, and the desktop appears about 3 seconds later; FreeRDP stops 45 seconds after the last viewer leaves (`VNC_IDLE_SEC` in the bridge's environment). The bridge looks for viewers with `ss` whenever Xvnc logs something, as it does for every connection, and every 5 seconds otherwise. While a viewer is connected, the bridge asks ft-screens to draw every screen at full rate (`watch 15` on `@ft_screens`, renewed every 5 seconds), so screens you aren't looking at in the headset, or a headset on a stand, don't stream at a low rate. It checks the primary screen's place (`ft-layout remote-view`) every 5 seconds, only while FreeRDP runs. +FreeRDP runs only while a VNC viewer is connected, because while it's connected krdp captures and encodes every redraw. With no viewer, krdp has no RDP connection and so captures nothing, and Xvnc shows a black screen. When a viewer connects, the bridge starts FreeRDP, and the desktop appears about 3 seconds later; FreeRDP stops 45 seconds after the last viewer leaves (`VNC_IDLE_SEC` in the bridge's environment). The bridge looks for viewers with `ss` whenever Xvnc logs something, as it does for every connection, and every 5 seconds otherwise. While a viewer is connected, the bridge asks ft-screens to draw every screen at full rate (`watch 15` on `@ft_screens`, renewed every 5 seconds), so screens you aren't looking at in the headset, or a headset on a stand, don't stream at a low rate. It reads the primary screen's place again (`ft-layout remote-view`) only while FreeRDP runs, after `~/.config/frametop/kwinoutputconfig.json` or `~/.config/frametop-layout.json` changes, and once a minute. With remote access on, the nested KWin runs with `KWIN_WAYLAND_NO_PERMISSION_CHECKS=1` and `KWIN_SCREENSHOT_NO_PERMISSION_CHECKS=1`, so any app in the Frametop desktop could capture its screens or inject input. The second one lets scripts take screenshots through KWin's `org.kde.KWin.ScreenShot2` D-Bus interface. This applies only to that desktop, not the stock one. Port 3389 is SteamOS's own `xrdp`, which starts a separate X11 session rather than showing the VR desktop. diff --git a/session/vnc-bridge.sh b/session/vnc-bridge.sh index 02041db..d106b06 100755 --- a/session/vnc-bridge.sh +++ b/session/vnc-bridge.sh @@ -103,6 +103,12 @@ watch() { fi || true } +# What remote-view depends on: KWin's saved outputs (positions, scales, primary) and +# Frametop's layout (screen sizes). It's read again only after one of these changes, and +# once a minute in case a change didn't touch them, and only while FreeRDP runs. +layout_files=("$HOME/.config/frametop/kwinoutputconfig.json" "$HOME/.config/frametop-layout.json") +stamp() { stat -c %y "${layout_files[@]}" 2>/dev/null || true; } + # Start FreeRDP inside the VNC screen, sized and shifted for $v. # /cert:ignore is fine here: the connection never leaves this host. start_rdp() { @@ -134,7 +140,9 @@ 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 +stamp_v= # stamp() when $v was read +recheck=0 # read the layout every 2 seconds until then +next_view=0 # next time to read it while kill -0 $xvnc 2>/dev/null; do if clients; then if [ $((SECONDS - watched)) -ge 5 ]; then watch; watched=$SECONDS; fi @@ -142,7 +150,7 @@ while kill -0 $xvnc 2>/dev/null; do if [ -z "$rdp" ]; then echo "VNC client connected, starting the RDP client" now=$(view) && [ -n "$now" ] && v=$now - next_view=$((SECONDS + 5)) + stamp_v=$(stamp) recheck=0 next_view=$((SECONDS + 60)) start_rdp fi elif [ "$seen" -ne 0 ]; then @@ -165,8 +173,12 @@ while kill -0 $xvnc 2>/dev/null; do continue fi if [ -n "$rdp" ]; then + # A layout change shows up in KWin's outputs a few seconds after the files change. + s=$(stamp) + [ "$s" = "$stamp_v" ] || { stamp_v=$s; recheck=$((SECONDS + 10)) next_view=$SECONDS; } if [ "$SECONDS" -ge "$next_view" ]; then - next_view=$((SECONDS + 5)) + next_view=$((SECONDS + 60)) + [ "$SECONDS" -ge "$recheck" ] || next_view=$((SECONDS + 2)) now=$(view) || now= if [ -n "$now" ] && [ "$now" != "$v" ]; then echo "layout changed: $v -> $now"