vnc: only halve the stream when the framebuffer is actually large

-scale 0.5 was hardcoded on the assumption that :0 is 4K. With no monitor
plugged in X falls back to something tiny — 800x480 on this box — and halving
that served an unreadable 400x240.

Read the framebuffer width from xrandr and scale only above 2560px. When the
width cannot be read, serve 1:1: too many pixels beats a thumbnail.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
brunorezio
2026-07-26 05:21:48 +01:00
co-authored by Claude Opus 5
parent 02662fc780
commit f0a0166ecd
+27 -3
View File
@@ -10,6 +10,8 @@ const MIRROR_DISPLAY = ':0';
const MIRROR_DISPLAY_NUM = 0;
const MIRROR_PORT = 5900;
const READY_TIMEOUT_MS = 5000;
// Above this framebuffer width the stream is halved; at or below it, pixels are served 1:1.
const SCALE_ABOVE_WIDTH = 2560;
// x11vnc reads :0's cookie from the logged-in user's X authority, so no root is needed. Where that
// cookie lives depends on the display manager: GDM (Ubuntu GNOME on Xorg) keeps it in the per-session
@@ -88,6 +90,20 @@ export async function ensureVncPassword(homeDir: string): Promise<{ password: st
return { password, passwdFile };
}
// Width of :0's framebuffer, or null when it cannot be read (xrandr missing, X not up). Callers
// treat null as "don't scale" — serving too many pixels beats serving an unreadable thumbnail.
function getFramebufferWidth(xauthority: string): number | null {
const proc = Bun.spawnSync({
cmd: ['xrandr', '--current'],
env: { ...process.env, DISPLAY: MIRROR_DISPLAY, XAUTHORITY: xauthority },
stdout: 'pipe',
stderr: 'ignore',
});
if (proc.exitCode !== 0) return null;
const match = proc.stdout.toString().match(/current\s+(\d+)\s*x\s*(\d+)/);
return match ? Number(match[1]) : null;
}
async function isPortOpen(port: number): Promise<boolean> {
try {
const socket = await Bun.connect({
@@ -126,6 +142,16 @@ export async function startSession(params: VncStartParams): Promise<{ port: numb
throw new Error(`No X authority found (GDM or ~/.Xauthority) — nobody is logged in on ${MIRROR_DISPLAY}`);
}
// Halving a 4K framebuffer keeps the stream sane over the tailnet, but the same 0.5 applied to a
// small screen is just lost detail — and with no monitor plugged in, X falls back to something
// tiny (800x480 here), which halves to an unreadable 400x240. Scale only when there is genuinely
// too much to send.
const width = getFramebufferWidth(xauthority);
const scale = width !== null && width > SCALE_ABOVE_WIDTH ? ['-scale', '0.5'] : [];
if (width !== null) {
console.log(`[vnc] :0 framebuffer is ${width}px wide — ${scale.length ? 'scaling to 50%' : 'serving 1:1'}`);
}
const proc = Bun.spawn({
cmd: [
'x11vnc',
@@ -140,9 +166,7 @@ export async function startSession(params: VncStartParams): Promise<{ port: numb
'-localhost',
'-forever',
'-shared',
// :0 is 4K; halving the framebuffer keeps the stream sane over the tailnet
'-scale',
'0.5',
...scale,
'-noxdamage',
'-quiet',
],