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_DISPLAY_NUM = 0;
|
||||||
const MIRROR_PORT = 5900;
|
const MIRROR_PORT = 5900;
|
||||||
const READY_TIMEOUT_MS = 5000;
|
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
|
// 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
|
// 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 };
|
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> {
|
async function isPortOpen(port: number): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
const socket = await Bun.connect({
|
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}`);
|
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({
|
const proc = Bun.spawn({
|
||||||
cmd: [
|
cmd: [
|
||||||
'x11vnc',
|
'x11vnc',
|
||||||
@@ -140,9 +166,7 @@ export async function startSession(params: VncStartParams): Promise<{ port: numb
|
|||||||
'-localhost',
|
'-localhost',
|
||||||
'-forever',
|
'-forever',
|
||||||
'-shared',
|
'-shared',
|
||||||
// :0 is 4K; halving the framebuffer keeps the stream sane over the tailnet
|
...scale,
|
||||||
'-scale',
|
|
||||||
'0.5',
|
|
||||||
'-noxdamage',
|
'-noxdamage',
|
||||||
'-quiet',
|
'-quiet',
|
||||||
],
|
],
|
||||||
|
|||||||
Reference in New Issue
Block a user