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:
co-authored by
Claude Opus 5
parent
02662fc780
commit
f0a0166ecd
@@ -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',
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user