evidence · 8.2 KiB

card-composition-architecture.md

Committed at docs/research/card-composition-architecture.md. This is what grounds the requirements that cite it.

Open raw source

Card-composition architecture contract

Decision

The selected route is a source-built, opt-in patch to pinned Sway 1.12. It keeps Sway as the sole DRM/KMS owner and continues to use the current Pixman renderer. The normal shell service and its unpatched Sway package remain the default and recovery path. This decision follows the source audit in card-composition-source-audit.md: the existing launcher client has no access to another client’s live scene surface or to touch before Sway dispatches it, while pinned Sway owns views, scene trees, touch seat operations, focus, and close dispatch.

This is a selected experiment boundary, not card-feature delivery. The route now has board-injected capability proof in card-composition-board/README.md: live parent/subsurface content, drag, expand, close/refusal, keyboard focus and normal-shell restoration. Real-finger and optical proof remain open; product card UX and cost acceptance are separate requirements.

Route comparison

BoundaryExisting protocol clientOpt-in Sway patch
Live app surfaceSway exposes foreign-toplevel and image-copy/capture protocols, but capture creates copied images from a separate capture scene rather than sharing a live scene subtree.sway_view owns scene_tree/content_tree; patch stays at that owner.
Touch before app dispatchNo: a layer-shell client receives events after surface selection.Yes: Sway seatop_impl receives down/motion/up/cancel before dispatch.
Focus and keyboard returnClient may ask Sway over IPC, but cannot authoritatively restore seat state.Sway uses seat_set_focus_container; patch saves and restores its prior focus.
DismissalNo ownership of another client close request.Existing view_close is the request boundary; actual exit/refusal remains observable.
DRM/rendererDoes not provide composition ownership.Existing sole owner, Pixman, and output pipeline remain in Sway.

Contract for the selected probe and consuming product change

The patch adds no external compositor and opens no DRM node. It works only inside an explicitly started opt-in Sway session. A root-coordinated board session stops the normal shell service first, starts that package as the one DRM owner, and always restores the known normal service. This planned stop and start must not be represented as unchanged process invocation IDs.

Eligible surfaces and identity

The capability probe allowlists exactly two launched XDG toplevel test apps. For each it records a card ID owned by the Sway patch and the paired sway_container/sway_view; it never gives a client process a wlr_surface, buffer, DRM FD, or capture of a non-allowlisted app. The card uses the existing view scene subtree. The probe logs map, unmap, destroy, output-leave, and any format/stride observed by Sway.

Lifecycle and surface ownership

normal -> deck-entry -> attached -> dragging -> selected -> expanded is the selection path. dragging -> dismissal-requested -> refused -> restored and dragging -> dismissal-requested -> app-exit -> destroyed are distinct paths. An unmap, destroy, output leave, unsupported format, allocation failure, or session abort cancels the gesture, removes/disables the patch-owned card node, returns focus to a still-live container or workspace, and returns to normal Sway. A close request is not success: it becomes refused unless the observed app exit/destroy completes it.

The probe may position, clip, and order Sway’s existing scene nodes. wlroots has only per-buffer wlr_scene_buffer_set_dest_size, not a whole-XDG-subtree scale. It must apply the same scale and translated coordinates to every live content/subsurface buffer on every commit and show that full app content changes while shrunken, rather than a crop or saved static clone. If it cannot, this selected route is blocked for product work. It may not copy a client buffer into a client-owned thumbnail, promise DMA-BUF/zero-copy, or retain a buffer after Sway’s map/unmap/destroy boundary. It must log the observed Pixman format and stride instead of assuming RGB565 texture support.

Touch, focus, and keyboard

While a single accepted finger is in dragging, the Sway seat operation owns continuous down/motion/up/cancel routing and updates the card position on each motion. A second contact, session lock, or loss of the selected surface cancels the card gesture and restores ordinary Sway routing. During deck interaction, keyboard focus stays with the saved live container; after expand, refusal, exit, or abort, the patch calls Sway’s focus path to restore that saved container or a valid workspace. The probe must show there is no focus orphan.

Completion and measurements

For each run, evidence separates scene damage/commit, client frame-done, and presentation feedback. A frame callback is not panel presentation. It records renderer, observed format/stride, buffer-lifetime events, process CPU, RSS, and timestamps for the two-app drag. No GPU claim follows from this route.

Required proof before product work

The selected package must provide card-composition-probe, remain outside the default image, and pass host lifecycle fixtures. The board coordinator then needs an opt-in session proving two live app surfaces, shrink, continuous drag, adjacent expand, dismissal request plus refusal or exit, keyboard restoration, and restored normal Pixman shell. It must additionally show a content update while shrunken, proving live per-buffer scaling. Failure records the failed state and keeps this proposal open; it cannot be relabelled as metadata cards or as completed visual-card UX.

Implemented probe boundary (2026-09-23)

The opt-in patch mirrors each enabled content/subsurface buffer with wlr_scene_surface_create, rather than a raw saved buffer. Pinned wlroots types/scene/surface.c owns independent commit handling, client-buffer locks, acquire/release synchronization, output sampling and presentation feedback. Sway’s existing frame iterator recognizes these mirror scene surfaces, so hidden originals do not starve the clients. The mirror geometry pass copies source cropping, transform, opacity and color representation, applies one scale and coordinate translation to every descendant, and conservatively clears the opaque region. A before-render pass prevents an unscaled commit from being presented. A 16ms discovery timer finds new subsurfaces whose disabled original scene might not schedule a frame. That timer and conservative damage can cost CPU; neither is a performance claim.

Only exact SWAY_K230_CARD_COMPOSITION_PROBE=1 enables registration, and entry also verifies the renderer is Pixman. Only app IDs k230.card.one and k230.card.two are eligible; duplicate IDs, extra outputs and another workspace are outside this bounded experiment. Supported probe buffers are SHM XRGB8888, ARGB8888 and RGB565; other buffers abort. Both complete mirrors must exist before either original is hidden. Source-node/surface destruction removes listeners and releases mirror references; unmap, seat/output loss, lock and allocation failure return through the same cleanup path. The original Sway scene restores its previous enabled state and focus returns through the normal Sway seat API.

After both apps map on the active workspace, touch the bottom 48 logical pixels to enter. The two live cards occupy separate vertical slots. Tap selects and expands; upward motion beyond 120 logical pixels requests close. A still-mapped client after 1500ms is classified close-refused (a timeout observation, not a protocol-level refusal reply). An observed unmap after the request is logged app-exit; client process exit must additionally be checked in client/session logs. A second contact aborts and consumes remaining contacts through release.

k230_card_probe enter|down ID X Y|motion ID X Y|up ID|cancel is an explicit, opt-in IPC test interface using the same handlers and logging input=injected. fail-mirror N injects allocation failure after N new mirror allocations to exercise partial-setup rollback. Neither interface is evidence of physical finger interaction. The normal shell package contains none of these changes.