screen.html + docs: migrate canvas-sizing + RFB design prose (#713 batch 3)
Expanded the /screen endpoint description in docs/web-ui.md to cover the substantive design rationale that lived in screen.html comments. screen.html shrinks correspondingly. Moved to docs/web-ui.md::Per-agent endpoints (GET /screen): - Deliberate thinness — minimal RFB renderer; noVNC vendoring path called out for production-grade replacement. - Fit-mode flex-item min-width:auto clamp: a flex item's automatic minimum size resolves to the canvas's intrinsic framebuffer resolution and clamps CSS max-* back up, making fit mode a silent no-op. The fix (flex: none + min-width: 0 + min-height: 0 + explicit px sizing via relayoutCanvas()) is now documented. - localStorage persistence for fit-mode (key screen-fit; default on). - Pointer rescale in sendPointer keeps clicks accurate. - ExtendedDesktopSize pseudo-encoding (-308 rect) gates the match-size button. Collapsed in screen.html: 8 #NNN cookies scrubbed across all inline comments. #133 (canvas-sizing fit bug — closed) ×5, #52 (noVNC vendoring — closed) ×1, #14 (relative URL — closed my piece) ×1, plus one CSS-block #133. Each comment shrinks to a brief pointer. screen.html: 8 → 0 #NNN cookies (100% reduction). Net ~26 lines of substantive prose moved into docs/web-ui.md.
This commit is contained in:
parent
b07d74b51b
commit
815f6561b7
2 changed files with 46 additions and 44 deletions
|
|
@ -1055,14 +1055,26 @@ shaped).
|
||||||
`wait_for_login` entry.
|
`wait_for_login` entry.
|
||||||
- `GET /events/history` — replay buffer for the terminal.
|
- `GET /events/history` — replay buffer for the terminal.
|
||||||
- `GET /screen` — VNC viewer page (minimal RFB-over-WebSocket
|
- `GET /screen` — VNC viewer page (minimal RFB-over-WebSocket
|
||||||
renderer). Only accessible when `hyperhive.gui.enable = true`
|
renderer — deliberately thin, just enough to display the
|
||||||
in the agent's `agent.nix`; the harness shows a 🖥 screen link
|
desktop + forward pointer + keyboard. A production-grade viewer
|
||||||
in the state row when `gui_vnc_port` is present. Toolbar:
|
would vendor noVNC; this file ships the minimal in-tree variant).
|
||||||
`⤢ fit` CSS-downscales the canvas to the window; `⤡ match size`
|
Only accessible when `hyperhive.gui.enable = true` in the agent's
|
||||||
sends an RFB `SetDesktopSize` request so the server (weston)
|
`agent.nix`; the harness shows a 🖥 screen link in the state row
|
||||||
changes its real output resolution to the window dimensions —
|
when `gui_vnc_port` is present. Toolbar: `⤢ fit` CSS-downscales
|
||||||
enabled once the server advertises the `ExtendedDesktopSize`
|
the canvas to the window via `relayoutCanvas()` setting explicit
|
||||||
pseudo-encoding (issue #133).
|
pixel dimensions on the canvas — *not* CSS `max-width/max-height`,
|
||||||
|
because a flex item's automatic minimum size (`min-width: auto`
|
||||||
|
resolves to the canvas's intrinsic framebuffer resolution) silently
|
||||||
|
clamps `max-*` back up, making fit mode a no-op that just centred +
|
||||||
|
clipped the oversized canvas. The fit-mode rules pin the canvas
|
||||||
|
with `flex: none; min-width: 0; min-height: 0` so the JS-set size
|
||||||
|
sticks. `⤡ match size` sends an RFB `SetDesktopSize` request so the
|
||||||
|
server (weston) changes its real output resolution to the window
|
||||||
|
dimensions; enabled once the server advertises the
|
||||||
|
`ExtendedDesktopSize` pseudo-encoding (`-308` rect in the header).
|
||||||
|
Fit-mode state persists in `localStorage` (`screen-fit`); default
|
||||||
|
is on. Pointer coordinates are rescaled in `sendPointer` so clicks
|
||||||
|
land on the right pixel regardless of CSS scale.
|
||||||
- `GET /screen/ws` — raw RFB byte relay: proxies WebSocket
|
- `GET /screen/ws` — raw RFB byte relay: proxies WebSocket
|
||||||
frames to the weston VNC server at `127.0.0.1:<vnc_port>`.
|
frames to the weston VNC server at `127.0.0.1:<vnc_port>`.
|
||||||
Transparent to any RFB variant. VNC port comes from
|
Transparent to any RFB variant. VNC port comes from
|
||||||
|
|
|
||||||
|
|
@ -62,13 +62,10 @@ html, body { height: 100%; background: var(--base); color: var(--text);
|
||||||
fit the wrap) and clip any sub-pixel rounding overflow. */
|
fit the wrap) and clip any sub-pixel rounding overflow. */
|
||||||
#canvas-wrap.fit { align-items: center; overflow: hidden; }
|
#canvas-wrap.fit { align-items: center; overflow: hidden; }
|
||||||
canvas { display: block; cursor: default; }
|
canvas { display: block; cursor: default; }
|
||||||
/* In fit mode relayoutCanvas() sets the canvas display size explicitly.
|
/* Pin the canvas to exactly the size relayoutCanvas() sets. See
|
||||||
The canvas is a flex item, and flex items default to
|
docs/web-ui.md::Per-agent endpoints (GET /screen) for the
|
||||||
min-width/min-height: auto — which resolves to the canvas's intrinsic
|
flex-item min-width:auto clamp that made fit mode a silent
|
||||||
framebuffer resolution and clamps the JS-set size straight back up,
|
no-op before this pinning. */
|
||||||
defeating the downscale (the bug behind #133 round 1). Pin the canvas
|
|
||||||
to exactly the size relayoutCanvas() sets: min-* 0 lifts the clamp,
|
|
||||||
flex: none stops flex grow/shrink from fighting it. */
|
|
||||||
#canvas-wrap.fit canvas { flex: none; min-width: 0; min-height: 0; }
|
#canvas-wrap.fit canvas { flex: none; min-width: 0; min-height: 0; }
|
||||||
#msg {
|
#msg {
|
||||||
position: fixed; bottom: 1rem; left: 50%; transform: translateX(-50%);
|
position: fixed; bottom: 1rem; left: 50%; transform: translateX(-50%);
|
||||||
|
|
@ -93,13 +90,11 @@ canvas { display: block; cursor: default; }
|
||||||
<div id="debug-log"></div>
|
<div id="debug-log"></div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// Minimal RFB-over-WebSocket renderer.
|
// Minimal RFB-over-WebSocket renderer. Connects to `screen/ws` on
|
||||||
// Connects to /screen/ws on the same host; the harness relays raw
|
// the same host; the harness relays raw RFB bytes to the VNC server
|
||||||
// RFB bytes to the VNC server running inside the container.
|
// running inside the container. See docs/web-ui.md::Per-agent
|
||||||
//
|
// endpoints (GET /screen) for the design rationale + canvas-sizing
|
||||||
// This is a deliberately thin implementation — enough to display the
|
// implementation notes.
|
||||||
// desktop and forward pointer + keyboard events. For a production-grade
|
|
||||||
// viewer, replace with noVNC (issue #52 vendors the full bundle).
|
|
||||||
|
|
||||||
(function () {
|
(function () {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
@ -123,18 +118,13 @@ canvas { display: block; cursor: default; }
|
||||||
});
|
});
|
||||||
|
|
||||||
// --- Fit-to-window toggle ---
|
// --- Fit-to-window toggle ---
|
||||||
// Scales the canvas down so the whole desktop is visible without
|
// See docs/web-ui.md::Per-agent endpoints (GET /screen) for the
|
||||||
// scrolling. The canvas's intrinsic resolution (width/height attrs)
|
// localStorage persistence + canvas-intrinsic-resolution +
|
||||||
// is untouched — only its CSS display size changes, set explicitly
|
// pointer-rescale model.
|
||||||
// by relayoutCanvas(). Pointer coordinates are rescaled in
|
|
||||||
// sendPointer to stay accurate. Persisted in localStorage; default
|
|
||||||
// is fit-on.
|
|
||||||
let fitMode = localStorage.getItem('screen-fit') !== 'off';
|
let fitMode = localStorage.getItem('screen-fit') !== 'off';
|
||||||
// Size the canvas. In fit mode, scale down (never up) to the wrap,
|
// Scale down (never up) to the wrap, preserving aspect ratio.
|
||||||
// preserving aspect ratio. Explicit px sizing rather than CSS
|
// Explicit px sizing — see GET /screen docs for the flex-item
|
||||||
// max-width/max-height: on a flex item those are overridden by the
|
// min-width:auto clamp this avoids.
|
||||||
// automatic minimum size, so fit mode was a silent no-op — the
|
|
||||||
// oversized canvas just got centred and clipped (issue #133).
|
|
||||||
function relayoutCanvas() {
|
function relayoutCanvas() {
|
||||||
if (fitMode && canvas.width && canvas.height
|
if (fitMode && canvas.width && canvas.height
|
||||||
&& canvasWrap.clientWidth && canvasWrap.clientHeight) {
|
&& canvasWrap.clientWidth && canvasWrap.clientHeight) {
|
||||||
|
|
@ -166,9 +156,9 @@ canvas { display: block; cursor: default; }
|
||||||
// --- Match-size: resize the remote desktop to this window ---
|
// --- Match-size: resize the remote desktop to this window ---
|
||||||
// Sends an RFB SetDesktopSize request so the VNC server (weston)
|
// Sends an RFB SetDesktopSize request so the VNC server (weston)
|
||||||
// changes its actual output resolution to match the browser
|
// changes its actual output resolution to match the browser
|
||||||
// viewport — sharper than fit-mode's CSS downscale. The button is
|
// viewport — sharper than fit-mode's CSS downscale. Gated on the
|
||||||
// enabled only once the server has advertised the ExtendedDesktopSize
|
// server's ExtendedDesktopSize advert; see docs/web-ui.md::Per-agent
|
||||||
// pseudo-encoding (a -308 rect). (issue #133)
|
// endpoints (GET /screen).
|
||||||
let extDesktopSupported = false;
|
let extDesktopSupported = false;
|
||||||
let screenId = 1; // captured from the server's ExtendedDesktopSize advert
|
let screenId = 1; // captured from the server's ExtendedDesktopSize advert
|
||||||
matchBtn.addEventListener('click', () => {
|
matchBtn.addEventListener('click', () => {
|
||||||
|
|
@ -206,10 +196,10 @@ canvas { display: block; cursor: default; }
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- WebSocket connection ---
|
// --- WebSocket connection ---
|
||||||
// Path-relative so the agent page mounted under a prefix
|
// Path-relative URL — `document.baseURI` resolves against the
|
||||||
// (e.g. /agent/<name>/screen via nginx, #14) still hits the right
|
// page's URL so the agent page mounted under a nginx prefix
|
||||||
// upstream. `document.baseURI` resolves against the page's URL;
|
// (e.g. /agent/<name>/screen) still hits the right upstream.
|
||||||
// swapping protocol on top gives ws(s)://host/<prefix>/screen/ws.
|
// Swap protocol on top gives ws(s)://host/<prefix>/screen/ws.
|
||||||
const proto = location.protocol === 'https:' ? 'wss' : 'ws';
|
const proto = location.protocol === 'https:' ? 'wss' : 'ws';
|
||||||
const wsUrl = new URL('screen/ws', document.baseURI);
|
const wsUrl = new URL('screen/ws', document.baseURI);
|
||||||
wsUrl.protocol = proto + ':';
|
wsUrl.protocol = proto + ':';
|
||||||
|
|
@ -573,8 +563,8 @@ canvas { display: block; cursor: default; }
|
||||||
canvas.height = fbH;
|
canvas.height = fbH;
|
||||||
relayoutCanvas();
|
relayoutCanvas();
|
||||||
setStatus('connected', 'connected');
|
setStatus('connected', 'connected');
|
||||||
// Advertise Raw + the ExtendedDesktopSize pseudo-encoding so the
|
// Advertise Raw + the ExtendedDesktopSize pseudo-encoding so
|
||||||
// server reports (and accepts) desktop-size changes. (issue #133)
|
// the server reports (and accepts) desktop-size changes.
|
||||||
sendSetEncodings([0, -308]);
|
sendSetEncodings([0, -308]);
|
||||||
// Request full framebuffer update
|
// Request full framebuffer update
|
||||||
requestUpdate(0, 0, 0, fbW, fbH);
|
requestUpdate(0, 0, 0, fbW, fbH);
|
||||||
|
|
@ -618,7 +608,7 @@ canvas { display: block; cursor: default; }
|
||||||
} else if (enc === EXT_DESKTOP_SIZE_U32) {
|
} else if (enc === EXT_DESKTOP_SIZE_U32) {
|
||||||
// ExtendedDesktopSize: w,h carry the new desktop dimensions;
|
// ExtendedDesktopSize: w,h carry the new desktop dimensions;
|
||||||
// the rect body is nScreens(1) + pad(3) + nScreens×16. The
|
// the rect body is nScreens(1) + pad(3) + nScreens×16. The
|
||||||
// header's x = change reason, y = request status. (issue #133)
|
// header's x = change reason, y = request status.
|
||||||
const nScreens = peekByte();
|
const nScreens = peekByte();
|
||||||
if (nScreens < 0) { chunks.unshift(b); totalBytes += 12; return false; }
|
if (nScreens < 0) { chunks.unshift(b); totalBytes += 12; return false; }
|
||||||
const body = drainTo(4 + nScreens * 16);
|
const body = drainTo(4 + nScreens * 16);
|
||||||
|
|
@ -693,7 +683,7 @@ canvas { display: block; cursor: default; }
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetDesktopSize (msg type 251): ask the server to change the desktop
|
// SetDesktopSize (msg type 251): ask the server to change the desktop
|
||||||
// resolution. One screen at the origin, sized to the request. (#133)
|
// resolution. One screen at the origin, sized to the request.
|
||||||
function sendSetDesktopSize(w, h) {
|
function sendSetDesktopSize(w, h) {
|
||||||
const b = new Uint8Array(24);
|
const b = new Uint8Array(24);
|
||||||
b[0] = 251; b[1] = 0; // message-type + padding
|
b[0] = 251; b[1] = 0; // message-type + padding
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue