Watch
1
0
Fork
You've already forked SouveraineOS
0
SouveraineOS/docs/tasks/51-viewtop-to-daily-driver.md

8 KiB

TASK 51 — viewtop from "it is the session" to "it is the daily driver"

Status: open. Created 2026-08-02, after the day viewtop became the session and then had to survive being used. Repo: souveraine-viewtop unless noted.

This is the punch list that stands between runs and stable. Everything here was found by using the phone, not by reading the code. Ordered by what blocks daily use.

1. Windows stack, and nothing tells them apart

Every toplevel is configured to the same rectangle at the same origin, so two apps land exactly on top of each other. place and pose now work (276bb3e), so the mechanism to arrange them exists and nothing uses it: there is no default placement policy, no border or focus indication, and no occlusion — a fully covered window is still composited every frame, which on a phone is battery spent drawing what nobody can see.

Wanted, and named by Casey: multiple apps at once, boundaries, expandable windows. The prior art is right there — the reference compositor's window_placement_store.rs, window_grab.rs (interactive move/resize) and local_windows.rs. Read those before inventing a layout model.

Landed 2026-08-03: placement.rs (tile until tiling stops helping, capacity derived from minimums rather than written down), a focus border — server-side decoration was already promised by new_decoration and never drawn — and a three-finger drag that carries a window, floating it via place().

The window-action overlay, and why it is blocked on the wire

2026-08-05: the overlay itself is no longer blocked and now ships (TASK-55). It reaches windows through the control socket's scene verbs rather than the per-window-texture wire below, which is enough for close/kill/place and is what "no way to close an app" needed. What is still blocked on this section is the scaled/sliding visuals — cards, flick-to-dismiss, live miniatures. Note the compositor already has the harder half: pose scales a real window and unpose maps input back through the inverse (both tested), so a shrunken app stays touchable. Nothing drives pose yet, which is precisely why Casey has never seen it.

Casey, 2026-08-03: three fingers should raise an overlay with options — move, resize, close — with the scaled/sliding visuals the reference shell has.

Read how that shell actually does it before building ours, because the answer is structural. Its compositor publishes, per window, a texture_id and full geometry (surface_*, geometry_*, content_*, transform, scale_120) in one Window table on the wire. The shell then draws that texture wherever and at whatever scale it likes — which is the entire trick behind the overview's scaled cards, the flick-to-dismiss, and every sliding transition. The compositor composites the shell's scene; the shell decides where window content goes.

Ours cannot do that yet, and the gap is specific:

  • The registry publishes a surface's size and never its position — the comment on place() says so deliberately ("the space owns where a window sits"). A shell cannot draw a frame, a handle or a card around something whose location it is not told.
  • There is no way for the shell to draw window content at all. viewtop serves ext-image-copy-capture with an output capture source; the reference shell's equivalent is per-toplevel. ext-image-capture-source-v1 has a foreign-toplevel variant, and quickshell already speaks this protocol family.

So the overlay is two pieces of plumbing, in this order: publish window position alongside size, then a per-toplevel capture source. Doing the overlay in the compositor instead would be quicker and wrong — it is UI, layers.rs and the lock surface both argue the compositor does not author it, and it would be the one piece of chrome the agent could not restyle.

Two things to keep straight while building it:

  • a placement policy belongs in its own module, testable without a GPU, the way layers.rs and input.rs are. The compositor asks it; it does not decide in new_toplevel.
  • whatever it decides, place must still override it. Geometry is the agent's (§13's 60) and a policy that cannot be overridden is a window manager again.

2. Screen capture is written and unmerged, so nothing visual can be verified

ext-image-copy-capture-v1 + ext-output-image-capture-source-manager-v1 are done and sitting in a whole viewtop tree on archdev at ~/vt-capture: ImageCopyCaptureHandler around wayland.rs:1816, helpers capture_peer / copy_output_into / render_capture around 676-850. gate.rs's Act::Capture half is already merged into the live tree, so the port is the handler and its helpers only.

This is not cosmetic. Without screenshots every UI change ships unverified — which is exactly how an unverified change shipped on 2026-08-02. It renders the scene a second time off-screen rather than reading the scanout buffer, and that reason is worth preserving: handing a client a mapping of the swapchain also hands it whatever the next frame's allocation reuses.

3. Volume buttons — DONE 2026-08-02 (266af98)

Action::Volume is pushed on the button down edge (device_state.rs) and executed through sessiond's executor table (server.rs), with tests. It went the way §12 required — an Action, not a shell-out from the compositor — and acts on the press rather than on a recognised gesture, because the multi-tap window would otherwise put 300 ms between the press and the sound changing.

Still unbound: the gesture level. The recogniser resolves volume taps and holds from the same edges, so hold-to-ramp or double-tap costs a binding rather than a mechanism. That is where the Activator-shaped binding table lands.

4. furniture_at() returns None

So Route::Shell is unreachable and the pill cannot be addressed as furniture. Waiting on the engine publishing a region layout; inventing rectangles the shell has not declared would be a second, drifting idea of where the pill is.

5. Named holes that are one decision each

  • foreign_toplevel admits every client, and window titles cross while the session is locked — the registry would never allow that for a surface it owns. A disclosure gate on the window list is owed, and belongs with the one owed at data_control, virtual_keyboard and the capture boundary.
  • The control socket admits every caller. SO_PEERCRED is available there and unused, exactly as may_take_lock is still a uid check. TASK-41 lands at all of them or none. Today's ceiling is what any uid-1000 process could already do by running blueline-screen-toggle, so it is not a new hole — but scene widened what is reachable through it.
  • Navigation intents refuse. navigation.rs holds TASK-37's policy, both tiers and the predictive drag, and nothing calls it.
  • Xwayland — no X11 apps.
  • viewtop is not packaged (/usr/local/bin, hand-copied) — TASK-25's scope, and souveraine/CLAUDE.md names that pattern as the one that silently never updates.

6. Two smithay patches, and they are not the same kind

Keep the distinction visible or the fork becomes a place protocol violations go to be tolerated:

  • 8772275 is upstream's bug — the layer-shell pre-commit hook outliving the role object, which kills any client on normal teardown. Worth sending upstream.
  • 403a3d0 is a concession. ext-session-lock-v1 says committing before acking the first configure is a protocol error and Qt does it anyway. The real fix belongs in Qt; delete this when it lands.

Acceptance

The phone is used for a day without ssh: apps can be switched between and told apart, the screen sleeps and wakes on its own controls, volume works, and a screenshot can be taken of any of it.

Connects to

TASK-43 (viewtop, the parent), TASK-50 (her hand on the glass), TASK-25 (packaging), TASK-41 (attestation at every one of these boundaries), TASK-39 (audio as state), TASK-37 (navigation), LOCK-DPMS-LESSONS.md §1 (the ordering this must never regress).