`-nocursor`. The RFB client asks for Raw and CopyRect and nothing else, so
x11vnc has no cursor pseudo-encoding to send a shape through and composites
the pointer into the framebuffer instead. That is a second cursor in the
picture, a round trip behind the real one, and on a bare X server with no
window manager it is the big black X of the root cursor rather than an arrow.
Verified on the live socket by parking the pointer at 100,100 and counting
what is not stage-teal in a 48x48 patch around it: 960 pixels before, 0 after.
Not fixed by teaching the client the cursor encodings — the answer is not to
draw the remote cursor better, it is not to draw it at all.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Officer-Session: de3ed4e6-f88a-429c-94e6-a760336ba83b
── the stage ──
The framebuffer is now the picture. 800x600 whatever the difficulty, the game
centred in it by the sidecar, and the panel draws it entire — scaled to fit,
centred by its flex parent, never cropped. Changing difficulty moves the board
instead of resizing the panel.
What this replaces parked the window at the origin and had the client crop to
the window's size, polled every four seconds. Two things wrong with it. The
panel resized under the player mid-game; and wine puts the window at 3,29
rather than 0,0, so cropping from the origin showed a black strip on top and
cut a row of cells off the bottom — the "expert gets cut off" report.
Centring runs on a one-second timer because nothing announces a resize: not
RFB, which keeps the framebuffer, and not X without a window manager to listen
through. One chained `xdotool search … getwindowgeometry` per tick, one
process, and a `windowmove` only when the numbers actually changed.
The client loses the geometry poll, the offscreen canvas and the crop with it.
── the port ──
x11vnc was listening on 0.0.0.0:5900 and [::]:5900 with -nopw. Every account
on this machine, every device on the tailnet and everything on the LAN could
open it and take the display — which is precisely the hole the 5907 version
was replaced to close, reopened wider and documented as impossible.
`-unixsock` ADDS a unix socket. It does not remove the TCP one. Three flags do:
-rfbport 0 for IPv4, -rfbportv6 0 for IPv6, -noipv6 for outgoing. `-rfbport 0`
alone leaves [::]:5900 up, which is how this nearly shipped half-fixed.
Found with `ss -lntp` on the running process, and that is the only check that
was right about it at any point. Running the same argv by hand showed no IPv6
listener — most likely because the PM2 session still held [::]:5900 and the
hand-run copy failed to bind it silently. Comments in session.ts and index.ts
that asserted there was no port are corrected rather than deleted.
Verified on the live socket: the streamed framebuffer is 447k teal pixels
around a 152x214 window at 324,193 — dead centre — and window grey arrives as
c0 c0 c0, no TCP listener for the x11vnc pid.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Officer-Session: de3ed4e6-f88a-429c-94e6-a760336ba83b
A cold install could never work. `winetricks -q vb6run` runs Microsoft's
redistributable, and that installer creates a window even when told to be
quiet — so with no DISPLAY wine cannot load a graphics driver, the installer
dies, and winetricks reports `returned status 43`. That message names neither
wine nor a display, and reads like a corrupt download.
ensurePrefix ran before Xvfb started, because building a prefix looks like
preparation rather than something that needs a screen.
It never showed up in testing here: this machine's prefix was seeded from the
pre-extraction one, so the build path only ever ran against a prefix that was
already complete. The first genuine cold install — alpha — failed on the first
Start. Diagnosed from `err:winediag:nodrv_CreateWindow` in the wine output,
which is the only line that says what is actually wrong.
Order is now display, prefix, game. ensurePrefix takes the display explicitly
rather than reading the ambient environment, so it cannot silently run without
one again, and a failed build now tears the display down instead of leaving an
Xvfb behind.
Verified by moving this machine's prefix aside and starting cold: prefix built
from nothing, msvbvm60.dll installed, session up with the window at 152x214,
Z: confined, socket mode 700, and RFB 003.008 through the platform relay.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Officer-Session: 656f5c85-e49a-4f1f-b9f3-042a04b4d043
Two things the live install found.
The manifest was refused at discovery. `apt: 'wine wine32:i386'` is two
packages and an architecture suffix, and a package name may be neither — nor
should it be, since `dpkg --add-architecture i386` has to happen first and no
package name can express that. And Fedora's Xvfb package is
`xorg-x11-server-Xvfb`, with a capital X, which a manifest package name may not
have; the dnf entry is dropped rather than guessed, which turns into an
instruction to the owner instead of a wrong install.
More importantly: confining the prefix before wine starts is a confinement wine
can undo. Observed directly — after a run, every com* link had come back with a
fresh timestamp, because wine rebuilds dosdevices when it boots a prefix it
thinks needs updating, and a wine package upgrade is enough to make it think
so. Z: did not come back that time, but "the dangerous one happened not to
regenerate" is not a property to rely on. confineAndVerify now runs again once
the game is up and says so if Z: returned.
Verified on the live server: Z: and the three home symlinks are gone from a
prefix that had them, run/ is mode 700, nothing listens on 5907, and the RFB
handshake arrives through the platform relay with a valid owner token and
through nothing else.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Officer-Session: 656f5c85-e49a-4f1f-b9f3-042a04b4d043
Minesweeper X under wine on a headless display, as a plugin. It was
/api/minesweeper, a Dashboard screen, an officerdev panel, a permission
registry entry and a hand-written websocket provider in server.tsx.
The socket is why it had to live in the platform: Bun's route table is fixed
at serve(), so nothing installed later could add one. That ceiling is gone,
and this is the first plugin through the per-plugin relay.
Three things changed rather than moved:
The sidecar owns the session. The platform used to spawn bin/session.sh
detached, so the game outlived every restart, nothing supervised it, and a
crashed run left wine processes holding the prefix — the next start hung
rather than failed. PM2 supervises a sidecar, and stop() uses 'wineserver -k'
scoped by WINEPREFIX instead of 'pkill -f wineserver', which would have killed
every other wine prefix on the machine.
x11vnc moved from 127.0.0.1:5907 to a unix socket in a mode-0700 directory.
-nopw was only ever safe because of the machine, not the platform: any local
account could open that port and take the display. A fixed port is squattable
too. There is now no listener to authenticate against.
The wine prefix is confined to C:. This is the one that mattered. The platform
classified Minesweeper as kind:'execution' — never grantable — because a fresh
prefix maps the whole filesystem to Z: and symlinks the profile into the
owner's real home, and the game has Open and Save dialogs. A plugin permission
has no kind and is always grantable, so extracting it without this would have
converted a sealed capability into a file picker running as the owner.
Re-asserted on every start, because wine restores z: whenever it updates a
prefix.
Not solved: one display and one game, so two grantees share a board.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Officer-Session: 656f5c85-e49a-4f1f-b9f3-042a04b4d043