Making a browser fill a screen: seven attempts on a live-boot appliance
Seven approaches to full-screening a kiosk browser on hardware that is whatever came through the door, six of which failed because they needed a package the live-boot image did not carry, and the one that shipped — libX11 through python ctypes — together with the status-bar readout naming which of five launch paths actually ran, without which every failure looked identical.
Status
Shipped, 19–27 July 2026.
Fifteen commits, most of them on one problem.
The problem
The audit station is an appliance. A technician boots a PC from a USB stick and should see one interface, full screen, with no desktop, no browser chrome and no way to end up somewhere else.
The machine it boots is whatever came through the door: any manufacturer, any graphics hardware, any panel resolution, sometimes no working display driver. There is no fixed target to develop against.
The attempts, in order
| Approach | Why it failed |
|---|---|
Firefox --kiosk | Hides the toolbar but opens at ~90% and never fills |
| Cage (Wayland kiosk compositor) | Correct when present — not on the image |
| Auto-install Cage at boot | Needs a network. The bench often has none |
X fallback with a window manager + xdotool | Another dependency not on the image |
xdotool window resize | Deterministic, still an install |
| python + libX11 via ctypes | Worked. No packages, any display |
| ctypes and resizes whatever top-level window appears to the exact screen size. |
The lesson, paid for over seven attempts: on a live-boot appliance, a dependency you have to install is a dependency you do not have. Every approach that needed a package worked on the development machine and failed on the bench. The one that shipped uses only what the image already carries — Xorg, python3 — and that constraint is now written into the session script's contract.
The diagnostics that made it tractable and added a Display readout to the status bar, showing
the resolution and which launch path was actually taken — cage, xinit or session.
Until then every failure looked the same: a window that was not full screen. Afterwards it was possible to tell which of five mechanisms had run. Most of the progress after that point is attributable to being able to see this.
A defect from the fix itself
the fullscreen enforcer must never touch popup windows.
The resizer grabbed every top-level window, including dropdown menus, and resized them to fill the screen — which made a dropdown snap shut the instant it was clicked. The station had a dropdown that could not be used, and the cause was the code that made the interface work at all.
What else landed alongside
Wi-Fi connection in the GUI, then Ethernet when it turned out the bench is often wired; saving the wipe to the batch and a Shutdown control; auto-fit for any resolution; and a rebuild to a two-column console design.
What was deliberately not built
No custom compositor or display manager. The station uses the stock Ubuntu X session and resizes one window inside it.
Open questions
Superseded in September by the casper layer and the dedicated kiosk session,
where GNOME never draws at all — see 2026-09-02-casper-layer-and-kiosk-session.md.
A published copy. Commit references and internal identifiers have been removed and the operator is not named; the engineering, the counts and the stated limits are unchanged.