Skip to content

The home screen as a board of blocks, a pointer a game can hold, and Windows on ARM - #72

Merged
aodjo merged 52 commits into
developfrom
feature/block-board
Sep 26, 2026
Merged

aodjo merged 52 commits into
developfrom
feature/block-board

Conversation

@aodjo

@aodjo aodjo commented Sep 20, 2026 •

Copy link
Copy Markdown
Owner

Fifty-two commits. The branch was started for the home screen, then went to a Windows on ARM
machine for the first time, and ends with Linux as a third platform that can watch and be
watched.

What is in it

The home screen is a board of blocks. A pegboard on a 32px lattice; a block spans hole to
hole, so its border always sits on the holes and its size is a count of them. Six kinds — one
machine, the list of machines, this computer, recent sessions, the terms this machine is shared
on, the state of the link. Editing shows the lattice, moves and resizes by dragging, and gives
each block a panel for its label, the fields it shows and its accent, flat or a gradient with
as many stops as are wanted. The board is kept in the settings file, and a file written before
blocks existed reads as an empty board rather than failing. Onboarding is untouched.

Blocks show what the application actually knows. A machine is being watched, shared or off —
there is no "connected" as distinct from "waiting", because nothing records the difference.

Around it. A profile menu — edit the board, language, settings, sign out. The header is one
fixed bar that drags the window and maximises on a double-click the way a title bar does; the
dock is held to the bottom of the window whether or not the page scrolls. Figures that change
are set in Geist Mono. The bitrate can be dragged where it is shown. A machine's name carries
the mark of what it runs, which is a new platform column on devices — see below.

A pointer a game can hold. A game that hides the cursor wants movement, not a position, and
never wants the movement to stop. The stream window's three states — watching, controlling,
aiming — are now one control; aiming puts SDL in relative mode and sends MouseMove. The wire
says so with a caged flag, because the host has to know: it recentres its own pointer near an
edge only for caged motion, and clamps everything else as before, so a turn never runs out of
screen and an ordinary pointer still stops at the edge. vectors.json was changed first, and
both sides test against the new vector.

The stream window. Titled after the machine it shows. Opened when there is a picture to put
in it rather than before. It no longer falls back to view-only while a session is open. It asks
for no more frames a second than the fastest screen here can show — with no ceiling it asked
for more than it could present and stuttered — and the shell bounds the setting to 1–240.

Settings. The scrollbar is visible. A released build does not offer a choice of build
channel.

macOS. These windows are no longer merged into tabs, which could not be dragged apart.

Windows on ARM builds locally. publish-local finds the bundle by what it ends in and by
this build's version, starts pnpm and the approval link through a shell, and says up front
what the build would otherwise say late and badly: that there is no compiler on the path, or
that ring wants clang on this target.

And the shell runs there, which it did not. Four faults, found in the order they hid each
other:

  • The CSP had no connect-src, and Windows carries IPC over http://ipc.localhost, so no
    window could reach the shell at all.
  • The tray icon was handed over as a template image, which only macOS knows how to draw. It was
    invisible — on the one control that brings a closed window back or quits the application.
  • A window being replaced was destroyed before its replacement had loaded.
  • Windows were built inside synchronous commands and an event handler, which is the main
    thread, and WebView2 cannot finish a web view while that thread is held. The window came up
    black, had no web view to inspect, and could not be closed. The commands are
    #[tauri::command(async)] now and the tray builds from a thread of its own. Confirmed fixed
    on a Windows 11 ARM machine.

A build that is not a release opens its inspector when PRISM_INSPECT is set, which is how the
above was found.

And it can be watched, which it could not. Added after this was opened, from trying to
watch that machine from a Mac:

  • A Windows host encoded with NVENC or not at all. A machine with an AMD or Intel GPU, a
    Snapdragon, or none could turn sharing on and then end every session as it opened — and the
    client was told only that the host had disconnected. NVENC is still asked first; without it
    the host now encodes through Media Foundation, on a vendor's hardware transform where there
    is one, with the texture handed over as it is.

    Where there is none it falls to Microsoft's software transform, and that path reads the
    captured frame back into system memory
    — the one exception in the repository to "a captured
    texture is never brought down to the CPU". Deliberate, and decided rather than slipped in:
    the machines that reach it have no hardware encoder, so what the rule would give them instead
    is no picture. It is said on standard error when it is taken. The software path is
    confirmed working on a Windows 11 ARM virtual machine; the hardware path has not been run on
    anything
    , and says so in its module documentation.

  • Windows kept no log. keep_the_log() was empty there, and a windowed application has no
    standard error, so every line a host writes about itself went nowhere. It now goes to
    %USERPROFILE%\.prism\prism.log, as on macOS. A session that failed before it opened also
    wrote nothing on any platform, and now says why.

  • Windows forgot who was signed in. The session token was kept only in the macOS keychain;
    everywhere else the store was three empty functions, so every launch began at setup. It is
    kept in Credential Manager now. Linux still keeps nothing.

Linux, both ends. Until now a Linux build was the shell and nothing else: no capture, no
encoder session (the VAAPI module is rate-control policy with no session behind it), no decoder,
no injector, no stream window. All of it is here now, behind one feature, linux-desktop, and
on for the shell and the stream window when they are built for Linux:

  • Screen: xdg-desktop-portal, a remote desktop session with the screen cast attached, so
    the person at the machine sees one dialog. Asked for when sharing is switched on and
    persisted with a restore token, so later sessions open without asking. Frames come over
    PipeWire into memory.
  • Codec: OpenH264 built from source, on the processor, both directions. This is a second
    exception to "a captured texture never goes to the CPU"
    , and for the same reason as the
    Windows software path: it is what every Linux machine has.
  • Window: SDL, now built for Linux as well, drawing through its own renderer with the
    picture uploaded as BT.709 YUV; the stats panel uses SDL's debug font.
  • Input: the same portal session, as evdev codes.

What it does not do yet is written down in docs/linux-build.md: no sound from a Linux host,
the pointer arrives in the picture rather than as its own layer, and VAAPI is still unwired.

Checked by a linux/arm64 Docker container on a Mac, where it builds natively: clippy with
-D warnings, the unit tests, and two tests against a real PipeWire daemon with a GStreamer
source standing in for the screen — frames arrive, and survive capture → I420 → encode → decode.
pnpm package produced an arm64 .deb and AppImage with the stream window inside, and the
shell stays up under Xvfb. The portal itself has not been run: it asks a person, and no
container has one. The first real test is an Ubuntu desktop.

lint:cross now leaves prism-stream and prism-cli out on Linux too, for the reason it
already did on Windows — SDL builds C for the target — and CI's Linux jobs lint and test them.

Before the accounts worker is next deployed

The worker now selects devices.platform. The column is added by
pnpm --filter @prism/accounts migrate, and that has not been run against production.
Merging this changes nothing that is running — the worker is deployed by hand — but the
migration has to go first when it is.

Checked

pnpm lint, pnpm lint:cross, pnpm typecheck, cargo test --workspace, pnpm test:ts.
prism-tauri is outside lint:cross, so the native jobs here are the first compile of the
shell's changes on Linux; Windows has been built and run by hand.

Copilot AI lite review requested due to automatic review settings September 20, 2026 12:19

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@aodjo
aodjo merged commit 94d2833 into develop Sep 26, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants