cosmix-comp

cosmix-comp is the Wayland compositor used by the Cosmix desktop. It can run nested inside an existing Wayland session with cosmix-comp --nested, or use the KMS backend on a system seat.

KMS hardware cursor

The kms-live build (with the default Bus, frame-capture and XWayland features) uses an optional DRM cursor plane. native-quoin is not required. The live atomic presenter enumerates ARGB8888 cursor planes compatible with the selected CRTC, checks their atomic properties, and queries the driver's cursor width/height caps (64×64 for older drivers without these caps).

Cursor images use two transparent, pitch-aware ARGB dumb buffers. Image changes pass an atomic test-only check before a synchronous cursor-plane commit; hotspots, output scale, SHM source rectangles and transforms are applied to the uploaded image and placement. For an admitted image, motion retains only the latest desired position. The next primary atomic submission appends the cursor plane properties, including its current framebuffer and CRTC_X/CRTC_Y, to the same request. Both planes share one commit and one primary pageflip completion. Submitted cursor coordinates advance only when the ioctl succeeds; every busy retry picks up the latest desired position.

When the presentation pump proves the primary scene idle, it flushes pending motion with a cursor-only NONBLOCK commit and no pageflip event. EBUSY keeps the position pending for the next refresh-paced pump update, even if input has stopped. Motion neither rotates cursor buffers nor advances the primary scene revision. Image replacement, hide and teardown remain synchronised with the cursor submission lock and the existing blocking image commits. If a pending cursor commit makes the next primary commit busy, the presenter retries at bounded 2 ms intervals within its original deadline; it does not wait for a cursor pageflip event that was never requested.

Once hardware projection succeeds, the software cursor entity is hidden. The transition renders once to remove its old pixels. Missing planes, allocation/admission/commit errors restore the software path and log the reason. Oversized or GPU-only DMA-BUF cursor images use software projection; a later supported image can use the plane again. Cursor resources are retired with the output and re-enumerated after resume; revoked generations cannot submit cursor updates. Capture retains its separate cursor snapshot.

Offline tests cover cursor requests, pixel packing and scene fallback. Driver acceptance and motion smoothness still require a live session check: move the pointer over an animated wallpaper, change cursor shapes, drag a window, toggle panels, and exercise display sleep/wake. Check the hardware cursor log and frame trace; cursor motion alone should not create primary scene revisions or comp_render work after the scene settles.

Optional F9 Bus action

--f9-bus <service> <verb> arms an unmodified F9 press to send a native ABP request with an empty argument object to a local Bus service. For example, --f9-bus bg-showcase boing.kick connects the compositor key to the Boing scene's physical impulse. Both nested and live KMS input use this binding; the background surface keeps keyboard interactivity disabled. In a nested session the compositor's host window must have focus.

The binding is absent unless explicitly configured, honours --no-keybindings, and does not run while the session is locked. A held key fires once until released. Delivery runs on a bounded worker, so waiting for the service cannot stall keyboard input. Failed requests are logged and never automatically retried. --list-bindings --f9-bus <service> <verb> includes the armed binding. The flag requires a build with Bus support.

For small nested scene previews, COSMIX_COMP_SERIAL_SCHEDULES=1 selects single-threaded ECS schedule execution to reduce dispatch overhead. This is an opt-in performance experiment; normal compositor scheduling is unchanged.

Wayland fullscreen

Comp 0.51.1 honours xdg_toplevel fullscreen and unfullscreen requests. Fullscreen uses the primary logical output's complete rectangle, including space otherwise reserved for panels. The optional client output hint is not selected yet. State and geometry change together when the client commits its acknowledged configure; sending or acknowledging a configure alone does not move the visible window.

Compositor decorations disappear in fullscreen. While that window has keyboard focus, it is raised into the top band and native Quoin panels and hotspots are hidden. Switching focus restores its previous stacking band and Quoin, allowing other applications to remain usable. Leaving fullscreen restores the previous normal geometry, or the maximised layout if it was maximised first. Restored normal geometry is clamped to the current usable area after output changes.

Bus properties windows.<id>.fullscreen and surfaces.<id>.fullscreen expose the committed state and emit normal property observation deltas. Application commands such as media.fullscreen initiate the request through Wayland.

Window switching and X11 placement

Comp 0.51.0 includes an opt-in native-quoin feature. Together with COSMIX_COMP_HUD_PROBE=1 and COSMIX_COMP_NATIVE_QUOIN=1, this embeds Quoin's real panels alongside native Boing in the compositor renderer. Application windows remain Wayland clients; their existing server-side decorations, caption actions and interactive move/resize handling remain in comp. Pinned native panels reserve usable space for maximised windows. See Quoin's compositor host for scope and remaining acceptance work.

Alt+Tab cycles forward and Alt+Shift+Tab cycles backward through mapped, non-minimised managed windows in stable creation order. This is not an MRU switcher or a visual switcher overlay. Both nested and KMS profiles support these bindings when interception is enabled. Session locking and exclusive keyboard layers retain priority.

X11 _NET_ACTIVE_WINDOW requests use the same managed-window admission and focus path. Local automation is accepted without timestamp-based focus-stealing prevention; source identifiers are not authentication. Unmapped, minimised and override-redirect windows cannot be activated through this path. The root _NET_ACTIVE_WINDOW property follows the compositor seat's managed X11 focus, including mouse and keyboard switching. Native, lock or absent focus clears it to zero; delayed X focus events cannot replace it with a root or ancestor window ID. This keeps xdotool windowactivate --sync and getactivewindow consistent with compositor focus.

Initial X11 placement, including size-only configure requests before mapping, respects reserved panel space. Reserved-area changes reflow managed X11 windows; maximised windows use the usable area and fullscreen windows use the full output. Restore geometry is kept until restoration, then clamped to the current usable area. Override-redirect menus retain client-owned coordinates and do not gain priority over top-layer panels or lock surfaces.

Intentional XWayland shutdown is marked before connection teardown. Expected connection closure and exit code 1 are distinguished from runtime crashes; protocol errors, signal deaths and timeout escalation remain errors. The child reaper allows a two-second exit grace before kill/reap. It remains asynchronous: compositor exit does not wait for a positive child-reaped acknowledgement.

Bus control plane

The default bus feature gives the compositor an L2 Bus control plane. The seat/KMS compositor registers as comp; --nested registers as comp-nested. --bus-service NAME overrides either name and accepts ^[a-z][a-z0-9-]{1,30}$. A build without the bus feature rejects that flag instead of silently ignoring it. The broker independently enforces the same SPEC 10 service-name grammar at registration and rejects an invalid from with Bus rc 10.

The control plane exposes eight verbs:

  • comp.ping returns {"pong":true} without taking a compositor snapshot.
  • comp.info returns service/build/backend provenance plus output and surface counts and the property event counters.
  • comp.props.get path? returns one leaf or subtree, or the complete tree when path is omitted.
  • comp.props.list prefix? returns leaf paths. A prefix is matched by complete path segments, never by string prefix.
  • comp.props.describe path returns leaf metadata (type, mutable, sensitive, description, optional format/enum/range/persistence, and owner) or an object subtree with its immediate children.
  • comp.props.watch seeds the property-change baseline and returns {topic:"<service>.props.changed",event_seq,lost_count}, where service is the name this compositor instance actually registered. The reply is truthful only for a caller that subscribed to that topic before calling watch and remains subscribed.
  • comp.props.set {path,value} mutates the four corner properties, windows.s<id>.band, or xwayland.enabled and returns {path,old,new}; for the file-persisted xwayland.enabled the reply also carries persistedfalse means the in-memory change and the changed event stand but the write to disk failed and the value will not survive restart.
  • comp.pointer.watch renews a three-second local pointer observation lease and returns {version:1,topic:"<service>.pointer.changed",lease_ms:3000}. Subscribe before calling; renew about once per second while observation is wanted. This verb uses the same broker-proven local caller requirement as property writes. The acknowledgement contains no pointer coordinates.

The complete L2 read tree is:

info.{service,version,backend,engine,instance}
outputs.o_<slug>.{name,default,x,y,width,height,scale,refresh_mhz,
                  usable.{x,y,width,height}}
surfaces.s<id>.{id,role,mapped,visible,x,y,width,height,band,sequence,
                tree_index,parent,output,title,app_id,focused,activated,
                maximized,fullscreen,minimized,decoration,
                layer.{stratum,interactivity,exclusive_zone,binding},foreign_id}
windows.s<id>.{id,foreign_id,title,app_id,x,y,width,height,focused,
               maximized,fullscreen,minimized,output,band}
stack
focus.{keyboard,exclusive_latch,pointer,pointer_grab,session_lock}
decoration.{enabled,style}
bindings.{enabled,profile,table}
input.corners.{enabled,deadzone_px,dwell_ms,velocity_max_px_s}
xwayland.{enabled,persist_path}
port.{level,event_seq,lost_count,queue_depth,reply_timeouts,publish_timeouts,
      slug_collisions,broker}

Surface keys are s plus the decimal session-local surface ID. Output keys are o_ plus the lower-case output name with each non-alphanumeric character replaced by _; the raw output name remains in name. If output names collide after slugging, the first output wins, each omitted output increments port.slug_collisions, and the compositor logs each collision at debug level. band includes background, bottom, normal, top, overlay and lock. stack contains mapped roots from top to bottom. windows is a projection of mapped XDG toplevels. port.level is L2. port.event_seq is the live global sequence watermark across every topic, and port.lost_count is cumulative. port.broker is driven by connection-state edges and is connected or retrying. port.reply_timeouts and port.publish_timeouts count their separate bounded lanes; both abandon a sink wait after two seconds.

Window band writes accept bottom or normal. They move the complete window tree, including popups, behind normal windows or back into their normal band. Assignments last for the current session. Use the canonical ID returned by comp.props.get; aliases with leading zeroes are rejected. Other bands remain reserved for layer-shell and session-lock roles. Corner activation takes priority over client pointer constraints; reactivation waits for physical pointer motion out of the corner.

The compositor publishes non-retained messages under the registered service namespace. The seat instance therefore uses comp.*, the default nested instance uses comp-nested.*, and --bus-service NAME moves the complete namespace to NAME.*. Every inner command is the unprefixed suffix shown below, so handlers do not depend on the instance name.

TopicInner commandExact body
<service>.props.changedprops.changed{path,old,new,ts,cause,event_seq}
<service>.surface.mappedsurface.mapped{id,role,foreign_id?,event_seq}
<service>.surface.unmappedsurface.unmapped{id,role,foreign_id?,event_seq}
<service>.focus.changedfocus.changed{keyboard,previous,exclusive_latch,event_seq}
<service>.output.changedoutput.changed{output,geometry:{x,y,width,height},usable:{x,y,width,height},event_seq}
<service>.corner.enteredcorner.entered{output,corner,dwell_ms,event_seq}
<service>.corner.leftcorner.left{output,corner,dwell_ms,event_seq}
<service>.corner.clickedcorner.clicked{output,corner,dwell_ms,event_seq}
<service>.pointer.changedpointer.changed{version:1,instance,output,position,valid,timestamp_ms,event_seq}

corner.clicked observes a left-button press while a corner is engaged, carrying the same engagement dwell as entered/left. It does not consume the button event; clients can map it to a panel pin toggle.

For a reliable property bootstrap: subscribe to the instance topic (for example comp.props.changed on the seat or comp-nested.props.changed when nested), call comp.props.watch, verify its returned topic, then read the required tree or subtree. The watcher is itself the subscriber: while that subscription remains active, noded cannot send topic.idle for its subscriber generation. An idle delivered in the same control batch as watch therefore belongs to a previous generation; the next zero-to-one topic.active re-seeds the baseline. Mix handlers match the suffix: on props.changed, on surface.mapped, on focus.changed, and so on. Changes are reduced after each complete protocol dispatch. A leaf therefore appears at most once per cycle, with its cycle-start old, final new, lexical path order and one of wayland.map, wayland.unmap, wayland.focus, output.geometry, layer.arrange, session.lock or props.set as cause. Operational port.* leaves are readable but are not self-published as property changes.

Keyed row creation and removal are row-granular: an appearing surfaces.s<id>, windows.s<id> or outputs.o_<slug> emits one frame at the row path with old:null,new:<full row>, and removal emits the inverse. Mutations within an existing row remain leaf-granular.

The sequence is process-global, strictly increasing and shared by property, surface, focus, output, corner and pointer records. If it reaches u64::MAX, that value is offered once and observation enters a terminal exhausted state rather than reusing a sequence. The outbox is one bounded 256-entry lane. On overflow the producer evicts one oldest record in fixed time and carries that record's loss interval inside the next record it sends; if an evicted record already carries loss, both intervals coalesce. Once the publisher learns an interval, it emits a gap on each affected topic before the next record it publishes on that topic, or during the idle flush when the lane drains empty. Survivors produced before the carried loss reaches the publisher may therefore be published before its gap. The gap's Bus header is event_seq=<last lost seq> (the coalesced interval's last-lost sequence), which locates the hole, and its body is {gap:true,lost_count,cause:"outbox.overflow"}. lost_count is the same cumulative process-wide counter as port.lost_count, not a per-interval tally. Consecutive intervals coalesce while pending, bounding gap traffic to at most one gap per topic per published record plus the idle flush. A rejected or timed-out publication discards its uncertain backlog and recovers under the same ordering rule with cause:"publisher.loss". A failed pending gap retries immediately on broker connection-state edges and on a single one-shot backoff timer (1 second, doubling to a 30-second cap); that timer exists only while the gap remains pending. After either gap, read a fresh property tree.

Pointer observation reads the latest cursor state after protocol dispatch, with at least 34 ms between samples. Input handlers do not serialize or wait for the Bus. Renewing a lease requests a fresh sample even if the pointer is stationary; expiry leaves no publication timer or motion history. Multiple local observers share the bounded lease and publication lane.

Valid samples contain the output's advertised name and position:{x,y} in output-local logical pixels. timestamp_ms is monotonic elapsed time within the compositor instance, not wall-clock time. Locking, pausing the KMS session, leaving the output or an invalid position produces valid:false,output:null,position:null. Retained client output globals do not keep a paused session's pointer valid. Pause and resume request fresh samples within the existing rate limit even when coordinates stay unchanged. No key or button data is included. These transient events are not retained by comp.

Noded reserves <service>.pointer.changed publication to its registered owner and stamps broker_service on reserved event deliveries. Local consumers must check that stamp and broker_origin:local, plus instance and sequence; a topic header alone is not proof of publication. Keep only the latest sample, clear stale samples on disconnection/lock, and advance the snapshot fence when output geometry changes. Watch acknowledgements and samples may arrive in either order over separate connections; keep a bounded early sample with its receipt time until acknowledgement. A filtered global sequence need not be consecutive.

The desktop's calloop 0.14 channel patch prevents unrelated child-source tokens from generating new wakeups. This fixes a session-management busy loop in the deferred libseat notifier; an inactive VT should wait for genuine session/input/control events rather than occupy a CPU core.

Cursor projection compares resolved materials, transforms and visibility before writing them. Unchanged built-in and SHM client cursors therefore avoid repeated render-asset updates; new client content still invalidates its material binding when the image handle is reused. DMA-BUF cursors retain per-update material rebinding while their import owner reports pending render work. Live client-content rendering can skip unchanged frames; active-VT CPU costs still require measurement.

The renderer also retains a protocol/cursor/asset/component scene revision independently of capture subscriptions. KMS binds the extracted revision to an acquired frame and reports it with a successful presentation; later scene changes cannot relabel a retained frame. Idle admission combines this revision with asynchronous asset/pipeline readiness and output lifecycle checks.

Main-world observers run after the standard Last schedule for installed image, mesh, 2D material, shader and font assets. They track direct resource changes and read asset events independently of Bevy's renderer. Late edits and their delayed events each advance the revision; quiet asset maintenance does not. Bare asset stores are observed without replacing them or requiring an event plugin. This records demand, not GPU readiness or arbitrary component changes. Event-bearing turns require extraction before messages expire.

The same post-Main observation schedule tracks the installed 2D scene's transform, visibility, sprite, mesh/material binding, text layout/style and camera components. Additions, changes, removals and despawns advance demand without consuming the renderer’s removal notifications. This catches layout completion using glyphs already present in an atlas. The observers run on one thread because they share the revision writer. Camera and projection observers compare their rendered values, so target-refresh bookkeeping does not perpetually demand another frame. Global clear-colour changes also advance demand. New render features must extend the component inventory.

An idle turn still runs Main, including input, protocol, layout and capture maintenance. It skips extraction and rendering only for a ready output whose known scene revision matches a settled, genuinely presented frame. Pending render commands, acquired frames, capture/security presentations, DMA-BUF work or quiescence prevent idle. Checking queued commands retains them in their owner for normal processing or teardown. First-light animation and enabled diagnostic capture/DMA-BUF probes keep full rendering. Idle services the device-error hook, nonblocking device polling and Bevy's time handoff without acquiring another frame.

Live KMS readiness carries the output key and generation through startup and resume. Both supervisors validate each complete frame-event batch against that identity before watchdog, telemetry, security acknowledgement or callback effects. A stale submission or cancellation therefore cannot renew output health or acknowledge another output's presentation. Terminal render failures retain their existing handling. Update requests also carry a sequence starting at one for each ready generation. The renderer rejects stale or skipped requests; both supervisors and pause reconciliation require the reply to match the exact outstanding request. Reports distinguish a full Main/extract/render pass, with its demand revision sampled after Main, from proven idle and lifecycle maintenance. Neither maintenance nor an empty frame-event batch certifies healthy idle or relaxes the submission watchdog.

Each active renderer update has its own two-second response deadline. The coordinator waits against the earlier response or submission deadline; silent or late updates cannot pulse clients or acknowledge security presentation. A timely empty response does not extend the frame-submission budget. A response timeout has the distinct kms-live-update-response-timeout diagnostic. Validated idle suspends the submission requirement while keeping response checks active. New demand starts a fixed submission deadline that further empty replies cannot postpone. Idle waits use the interruptible coordinator mailbox at nominal output refresh cadence; callbacks occur at most once per interval. Idle does not count as a submitted frame or acknowledge security presentation. Registration, resume scene draining and transition budgets are unchanged.

GPU asset preparation has a separate internal snapshot. It observes extracted image, mesh and installed 2D material IDs without consuming Bevy's queues, and retains pending replacements/removals until preparation reflects them. Successful KMS frame reports include the post-preparation snapshot. Retry work cannot look complete merely because no new extraction arrived. Pipeline compilation, DMA-BUF ownership and the final idle-admission decision remain separate.

Pipeline settlement is sampled after drawing and capture, before presentation. One additional queue-processing pass exposes newly queued pipelines and completed asynchronous compilation; missing shaders/imports remain pending, while permanent shader errors are reported separately. New entries or pipelines becoming ready after drawing are flagged as requiring a later rendered frame. The snapshot is attached only to successful KMS presentation and is cleared during output teardown. This pass can start compilation and adds preparation cost; synchronous mode can compile within the call. It does not loop until pipelines settle or prove every intended draw was submitted. An independent 30-second settlement deadline prevents fallback presentations from indefinitely hiding pending assets or pipelines. The budget resets for a replacement output generation after resume.

Hot-corner detection is compositor-side and uses the current logical output. It emits one entered, then one left on deadzone exit, output or geometry change, session lock, disable, or config invalidation. corner is tl, tr, bl or br; left repeats the dwell measured by entered. Fast transit is not accepted until a velocity-qualified dwell, while continued slow outward motion constrained by the output edge can enter early. Defaults and inclusive ranges are:

PropertyDefaultRange
input.corners.enabledtrueboolean
input.corners.deadzone_px12.01.0..=256.0 logical px
input.corners.dwell_ms2000..=5000 ms
input.corners.velocity_max_px_s1500.01.0..=20000.0 logical px/s

The corner leaves and xwayland.enabled are the only mutable leaves. The corner descriptors say mutable:true and persistence:"none" (numeric leaves also carry the range above) and those values live for the compositor process only. xwayland.enabled is the one exception: its descriptor says persistence:"file" — the value is read once at compositor startup (whether to spawn XWayland at all; there is no live toggle) and a write persists it for the NEXT startup into a per-socket file under the COSMIX etc tree, whose resolved absolute path the read-only xwayland.persist_path leaf reports and the compositor logs at startup. The COSMIX_COMP_XWAYLAND environment variable (0/false/off/no or 1/true/on/yes) overrides both the file and the default at launch — the no-rebuild back-out that works even when the props surface is unreachable. Writes are admitted only when noded supplied exactly one case-insensitive broker_origin header whose value is local, the caller has a canonical registered service name, and the wire contains no source_peer, permissions or signed_ident claim. Otherwise the reply is rc 10 {"error":"not_local"} before calloop admission. Unknown and immutable paths return unknown_path and read_only; type/range failures return {error:"invalid_value",path,expected,range}. All four path/type/range checks run on the worker before admission and are repeated on calloop as defence-in-depth, so invalid writes consume no ingress or responder permit. A no-op write replies normally without a change record.

focus.session_lock is none, locking, locked, orphaned or unlocking. While a session lock is active, the read tree applies the same WaylandState::session_lock_active presentation boundary as the renderer and foreign-toplevel publication: ordinary surfaces retain ids, roles, bands and geometry, but report visible=false and null title/app_id; windows is empty. During the KMS unlock window (normal_scene_restricted()), the read tree stays redacted with focus.session_lock="unlocking" until the compositor's own presentation predicate lifts, at the same moment the renderer resumes. Unlock then restores the ordinary projection.

All application errors use Bus rc 10. In addition to the write errors above, read/dispatch errors include unknown_path, busy and unknown_verb, plus {"error":"too_large","limit_bytes":N,"hint":"read a subtree"} when a serialised reply would exceed the effective broker-path ceiling. N is 8,384,512 bytes: min(16 MiB Bus message, 8 MiB single WebSocket frame) minus 4 KiB of documented header/framing headroom. Immediately before sending, comp also measures the actual canonical response headers, correlation id and framing with the body and refuses any reply whose complete wire size would exceed that ceiling. Replies are never truncated. An absent broker never delays compositor startup: the port thread reports retrying and reconnects independently. A registration rejection (collision, invalid SPEC 10 name or admission) is logged once, ends the port worker without renaming, and leaves the compositor running.

The broker client lives on the named cosmix-comp-port OS thread with its own current-thread Tokio runtime. At most 16 accepted controls/reads cross a bounded calloop channel. The calloop callback only stages requests; after the current protocol transaction and popup cleanup, one owned snapshot is built and shared by all requests in that dispatch. Snapshot admission is released before reply I/O. Replies and publications use separate lanes and two-second deadlines, so a stalled topic sink does not stop incoming commands. Full-tree JSON is serialised once per snapshot on the blocking pool, with only one full-tree serialisation active process-wide; requests share the resulting string. Subtree reads serialise only the selected value.

The semantic observation reducer carries typed rows and scalar values across the bounded outbox; only the worker constructs topic JSON. Successful offers wake the publisher with an event notification, which drains the outbox to empty. There is no publisher polling timer or idle tick source. topic.idle drops the property baseline and a later topic.active seeds one at the next stable service point; both lifecycle directions coalesce latest-wins if the ingress is temporarily full.

The 16,384-surface cap bounds tree cardinality, not reply bytes. A full tree can still serialise far beyond the wire allowance, so comp measures the cached full-tree bytes once and returns too_large; callers can read a leaf or subtree from the same snapshot. Single-flight serialisation prevents same-snapshot multiplication.

Absent by design after P-1:

  • comp.surface.* control verbs, because focus/raise/close operations arrive in P-2 and move/resize in P-3;
  • render timings, because they are metrics rather than properties; and
  • a Bus screenshot verb, because it is a later control-plane slice; Arc 4's capture service is available through the Wayland protocol described below.

Supported Wayland protocols

The compositor advertises the core compositor, subcompositor, seat, output, shared-memory, DMA-BUF, explicit synchronisation, viewporter, fractional-scale, presentation-time, XDG shell and XDG decoration globals needed by its desktop clients.

ProtocolVersionCurrent support
zwlr_layer_shell_v14Layer surfaces and layer popups map, arrange and configure through Smithay's LayerMap; protocol strata, keyboard interactivity, input regions and exclusive usable-area effects are supported.
ext_idle_notifier_v12Per-seat notifications use Smithay's calloop timers; real pointer, keyboard, touch, pointer-gesture and tablet-tool activity resets the timeout and resumes an idle notification. Device-removal reconciliation does not count as activity.
ext_foreign_toplevel_list_v11Mapped XDG toplevels expose stable mapping identifiers, title and app ID updates; unmap or destruction closes the handle, and late clients receive the current mapped set.
ext_session_lock_v11Nested and live KMS modes support immediate output-sized lock-surface configures, secure blank-first presentation acknowledgement, lock-only input, VT pause/resume preservation and the locked/orphaned lifecycle.
zwlr_screencopy_manager_v13Compatibility output capture into exact-layout wl_shm buffers, plus eligible whole-output v3 DMA-BUF destinations; includes clipped SHM regions, real damage waiting, exact cursor inclusion and presentation-timestamped nested or KMS completion.

XWayland

The xwayland cargo feature is in the default set: every default build supervises one rootless Xwayland instance and acts as its X11 window manager. The runtime control is the xwayland.enabled property described above (startup-read, file-persisted per socket) with the COSMIX_COMP_XWAYLAND environment variable as the launch-time override — the cargo feature is no longer the switch. Normal X11 windows become managed toplevels on the existing scene, buffer, focus, stacking and server-side-decoration paths: association (via the xwayland-shell serial handshake) creates the window's surface record, the map grant makes it eligible, and its first committed buffer renders through exactly the renderer path a Wayland toplevel uses — the renderer has no X11 branch. Title/class metadata, focus (including the X SetInputFocus/WM_TAKE_FOCUS half), interactive and client-requested move/resize, maximise/minimise/fullscreen, EWMH state mirroring, close via WM_DELETE_WINDOW, and cross-protocol stacking in the normal band are supported.

DISPLAY is never set globally. After the XWM owns WM_S0, the compositor atomically publishes a mode-0600 per-socket descriptor at $XDG_RUNTIME_DIR/cosmix-comp/<WAYLAND_DISPLAY>.xwayland.env containing DISPLAY=:N and the XWayland generation; launchers read it once and pass DISPLAY explicitly to each X client. A missing Xwayland binary or a failed start degrades to a fully working native-Wayland compositor with a warning. An unexpected XWayland death destroys that generation's windows, removes the descriptor and arms a single 60-second one-shot restart backstop; one retry credit exists, and only a generation that then survives five minutes restores it. There is no readiness or liveness polling anywhere in the path.

Override-redirect windows — X11 menus, context menus, tooltips and combo-box drop-downs — render (X-2a): they get a surface record on the ordinary renderer path at their own absolute client coordinates (negative origins included; the compositor never places, clamps, configures, grants or decorates them — including for _NET_WM_MOVERESIZE, whose interactive move/resize is refused for override-redirect windows), stack in the normal band and are raised to its top when mapped (not tethered to a parent and not kept on top afterwards), and acquire none of the managed behaviours — no focus candidacy, no minimise/maximise, no foreign-toplevel export. Keyboard focus deliberately never moves to an override-redirect window — focus arbitration itself refuses them, so neither a click nor a touchscreen tap on one changes focus: the X client's own grab machinery routes keys. In the property tree they appear under surfaces.* with role:"x11-override-redirect" (managed X11 windows are "x11-toplevel"); neither appears in windows.*, which remains the xdg-toplevel projection. A window that changes its override-redirect flag between map cycles transitions by record destruction and rebirth in both directions. Known scope edges: relative sibling restacks are ignored, a menu overlapping a layer-shell panel draws under the panel, and dismissal is client-owned — a click on a pure-Wayland surface is invisible to the X grab and dismissal then depends on the client's grab-break handling.

Still not supported, by design:

  • Clipboard and primary selection are not bridged in either direction; selection access is refused at the XWM (X-2b).
  • Drag-and-drop across the X11/Wayland boundary (X-3).
  • HiDPI/fractional scaling for X11 clients: the X11 client scale is held at 1 and RandR primary-output changes are only logged (X-3).
  • KMS qualification: X11 rendering is proven on the nested backend; the live-KMS proof is a later slice (X-3).
  • Relative X restack requests (Above/Below/Bottom siblings) are refused with a log; the compositor scene stays the stacking authority and only raise-to-top is honoured.

Screen capture

SHM publication copies bounded chunks while continuing to dispatch clients. Queued chunks keep the protocol loop runnable: they never wait for unrelated input or surface commits between copies. Once publication finishes or is cancelled, the loop resumes its normal blocking wait. On x86-64 CPUs with SSE4.1, unrotated GPU readback uses aligned streaming loads into cached CPU memory to avoid slow generic copies from write-combining mappings. Other CPUs retain the portable copy path. Capture reservations account for source, staging and converted pixel storage. They are capped at 512 MiB per client and 1 GiB globally, allowing three active full 4K SHM captures plus one retiring request per client. Request count caps remain four per client and eight globally. This bounded memory allowance overlaps readback latency without bypassing reservation accounting.

Arc 4 provides wlr-screencopy-unstable-v1 output capture for existing clients such as grim. A frame advertises one opaque XRGB8888 shared-memory layout with an exact width * 4 stride. Whole-output and logically clipped region requests are converted into displayed physical pixels; invalid or stale outputs fail rather than falling back to another output. Plain copies are force-presented, including on an otherwise idle output. Damage copies wait for relevant base-scene damage, or cursor-only damage for a cursor-inclusive copy, without forcing a frame. The bounded journal is manager-scoped; its baseline advances only after ready, and history overflow conservatively reports the full captured region.

Version 3 frames may additionally advertise a DMA-BUF destination after the SHM buffer event and before buffer_done. The advertisement is immutable for that frame and exists only for a whole-output request whose transform is Normal, whose displayed and storage extents are equal, and whose underlying render texture has an exact copy-compatible opaque format. Its modifiers are the intersection of linux-dmabuf feedback and an exact Vulkan external-image TRANSFER_DST import query at that extent. Versions 1 and 2 never receive the DMA-BUF event; regions, transformed outputs and unsupported renderer states remain SHM-only.

A submitted DMA-BUF must have exactly one plane and match the advertised fourcc and extent. A kind, fourcc, extent or plane-count mismatch posts invalid_buffer on the frame and never enters renderer admission. A modifier outside the frame's stored transfer-destination set is instead an operational miss and produces recoverable failed: linux-dmabuf feedback can legitimately guide a client to a sampled-image modifier which this exact capture use cannot import. Other operational misses—fd cloning, import/acquire, capacity, deadline, cancellation, resize, completion or FOREIGN release—also produce one failed. The compositor never sends damage or flags before discovering such a failure.

A submitted wl_buffer does not carry the DRM device which allocated it. Version 4 linux-dmabuf feedback steers compliant allocators to the renderer's real main_device, and an import failure fails the frame, but the compositor also fail-closes advertisement if bridge and feedback renderer identities ever disagree. It does not pretend that Dmabuf::node() proves allocation identity. A cross-device import which succeeds is a residual hardware risk and belongs to the real-GBM gate.

Pixel completion and the matching output presentation form a two-part completion latch: ready is sent only after both arrive for the same frame. For SHM the completion half is mapped readback. For DMA-BUF, FOREIGN acquire is encoded in the same wgpu command buffer as the copy; the worker waits for that exact SubmissionIndex, submits the release barrier through wgpu's thread-safe queue, then waits for the exact release submission. No capture ownership barrier uses a raw queue submit or an infinite fence wait. Sampled-image ownership barriers also enter wgpu-owned command buffers, so every renderer-queue submission has one authority: wgpu::Queue. A single bounded completion authority owns those destination jobs. It retries a transient 250 ms GPU wait up to four bounded attempts, treats a full/disconnected job queue as terminal, and never waits on the render or protocol thread. The shared terminal gate closes the sole sender while excluding concurrent submissions, then drains the definitively closed channel. Any terminal worker failure fails and safely strands every live post-import job and clears future screencopy DMA-BUF advertisements. Nested records are bound to the exact acquired host window texture-view identity; a missing, unconsumed or mismatched acquisition fails that capture instead of rebinding it to a later presentation. Nested mode uses the completed host presentation time; KMS retains the exact tv_sec/tv_usec from the matching kernel page-flip event. The compositor bounds live and in-flight jobs, reserves a byte budget before allocation, performs conversion away from the render and protocol threads, then copies shared memory in bounded chunks across protocol loop iterations. Each client may bind at most eight live screencopy managers, with 64 live managers globally; exceeding either implementation limit is a fatal protocol error on the new manager object. Cancellation is checked at the protocol outbox, ECS queue, renderer-owned map and conversion worker. GPU map errors become the frame's failed event. The byte reservation remains charged until the last renderer-side request or packed-result holder drops. Once the renderer submits its COPY_SRC readback, the lease remains with the cancellable map job. Admission also requests a redraw, so a plain copy is presented even when the output has no animation or other damage. Every admitted copy has a five-second absolute request deadline: this is a deadline on that one client operation, not a periodic compositor timer.

Main-world maintenance retires expired or cancelled admissions before rendering, including PNG requests deferred for output readiness. It preserves live requests and their original deadlines, and leaves submitted jobs with their completion workers. Read-only demand checks match the capture source and KMS generation; waiting for damage or holding a PNG completion slot does not by itself require a render. The nested redirection path uses these checks to avoid preparing a target for stale work; live KMS idle admission uses the same non-consuming checks.

Completion does not depend on a later render tick. The retirement worker sends its result directly through the calloop-backed protocol command channel, which wakes an idle protocol loop; the destination can therefore reach ready on a static desktop without polling or a redraw timer. Teardown first puts every live job into failed/strand mode and closes the sole job sender. It performs a non-blocking worker acknowledgement check: an already-finished worker is joined, while an unacknowledged worker is detached. If a driver call never returns, that detached worker retains the in-flight job and all queued jobs, including every import, buffer token and reporter, until it returns or the process exits. The retained set is bounded by MAX_IN_FLIGHT_CAPTURES; renderer teardown itself does not wait. A pre-import failure releases the retained buffer token immediately. After an acquire/copy submission, only successful copy retirement plus successful FOREIGN hand-back may release it; an unprovable hand-back strands both import and token. fail_capture cancels publication but cannot release that renderer-owned half early. Sending wl_buffer.release after the client has already destroyed its object relies on Wayland's inert-object send behaviour.

The existing SIGUSR1/evidence PNG path shares the renderer-owned RGBA snapshot, deadline, cancellation sweep and conversion worker with wire capture. The PNG and wire consumers then create their own packed BGRA buffers for their distinct outputs. PNG capture retains its filename, atomic-rename and cadence contract; an unavailable output or an encode/write task which starts after the deadline releases the one-batch-in-flight latch. A genuinely blocked filesystem write cannot be cancelled safely and remains outside this deadline guarantee.

PNG requests which reach KMS before their camera or frame token is ready are retained for a later render frame within the original deadline. This does not extend the deadline or add a timer. Terminal PNG failures emit a structured reason, target and output path so a missing evidence image can be diagnosed; the batch latch is released exactly once on completion or failure. For unrotated output, pixel normalisation bulk-copies each mapped row into CPU memory before any BGRA channel swizzle. This avoids slow scalar reads from mapped GPU memory at high resolutions without extending capture deadlines.

The per-destination fd duplication and Vulkan image creation/bind syscalls run on the render thread. This is intentionally retained for S-2: admission is bounded to eight destinations per render batch, and moving import into the worker would break same-frame copy-out. Hardware-gate runs should continue to record this bounded syscall cost. Sampled-image cleanup now retains FOREIGN release batches across frames and checks their exact submission with a zero timeout. Acquire remains queue-ordered ahead of sampling. The retirement worker also polls without holding wgpu's fence lock across GPU progress waits. See the asynchronous ownership contract. Flag either failed to release DMA-BUF queue ownership or DMA-BUF release completion unproven: submission failure or an unproved release after the 250 ms deadline strands the backing and withholds wl_buffer.release. That cached image cannot be reused; another use requires a fresh import.

Live renderer reconstruction currently rebuilds the renderer, capture bridge, advertisement registry and retirement worker together, leaving DMA-BUF advertisement empty until the new render world republishes it. This whole restart path is structurally pinned by run_live_render_pump, but remains an explicit untested end-to-end path because the regression would require the forbidden real-seat live pump.

overlay_cursor=0 selects the cursor-free base. Every non-zero value selects an inclusive copy using the retained default, chrome or client cursor asset with its hotspot, clipping and hidden state. Nested mode copies its cursor-free scene to the host swapchain and composites the capture-only overlay into a separate temporary target, avoiding a doubled host cursor. The production cursor camera renders into an independent transparent target, so it cannot clear or swap the scene camera's base texture. SHM and imported DMA-BUF cursor assets remain retained and are sampled from that target on the GPU in both nested and KMS capture. KMS base copies precede the GPU overlay into scan-out and inclusive copies follow it.

Nested redirect and cursor-composed textures use an unsuffixed BGRA8/RGBA8 base with the matching sRGB view format. Rendering therefore keeps the sRGB view, while DMA-BUF copies compare and copy the exact linear base format used by the destination import.

Capture is deliberately default-open, including while the session is locked. Lock and unlock change the capture epoch: stale work fails, while newly admitted work captures the currently displayed lock surface or compositor-owned black fallback. This is an agentic desktop policy rather than a portal permission prompt.

KMS copies select the exact Ready OutputKey and generation, copy out within that frame without retaining a slot or storing a destination token in the scan-out pool, and latch completion against its acquisition token and kernel page-flip timestamp. Pause, unplug, generation replacement, cancellation and map failure fail the affected one-shot rather than returning another output or stale pixels. --first-light keeps the same capture feed and completion path while ignoring client scene content; every changed animation frame marks full-output damage, so copy_with_damage wakes. The wlr protocol is a compatibility surface; the planned ext-image-copy-capture-v1 implementation will become another consumer of the same capture service. The automated nested acceptance gate uses grim; the cosmix-screencopy-probe binary is a deadline-bounded manual diagnostic for the advertised layout, non-zero SHM offset, guard bytes and non-black pixels. Its --dmabuf --drm-node PATH mode waits for buffer_done, allocates an advertised modifier with GBM, submits that destination, maps it only after ready, and prints the presentation timestamp plus a content checksum. DMA-BUF advertisements are republished from live view targets after every output registration/re-registration and generation change. Reconstructing a renderer reconstructs its bridge, completion worker and advertisement registry; until that replacement has published fresh capabilities, new frames advertise SHM only. Automated tests cover readback, transforms, ordering and damage. A real Vulkan render-attachment gate uses the production GPU cursor-composite pass, reads back base and inclusive bytes, compares both with byte-exact references, and proves that channel-swap and shifted-hotspot mutants fail. These gates prefer a Vulkan fallback adapter. With COSMIX_REQUIRE_FALLBACK_ADAPTER=1, absence of one fails the test (the CI rule); otherwise the gate runs on an available Vulkan adapter and prints one line naming the adapter actually used. Physical driver behaviour, real kernel page-flip clock provenance and end-to-end grim -o on KMS remain manual-hardware-only checks. The automated Vulkan equivalence gate uses an ordinary Vulkan COPY_DST texture; it does not prove real GBM allocation, DMA-BUF import, cross-device behaviour or FOREIGN ownership on a physical driver. Those are explicitly hardware-gated.

Layer surfaces stack in the protocol order: Background, Bottom, normal XDG toplevels and popups, Top, then Overlay. Raising a surface changes its order only within its stratum. A layer popup stays in its parent's stratum, including when the parent changes layer. Session-lock surfaces use a sixth Lock stratum above Overlay. An opaque compositor-owned black element sits below Lock and above every client stratum while a lock is active.

keyboard_interactivity=None layers receive pointer and touch input but never take keyboard focus or raise; their popups can use pointer/touch grabs but not a keyboard grab. OnDemand layers take focus on a pointer press or first touch. The highest stacked mapped Exclusive layer latches keyboard focus until it unmaps, is destroyed, or commits a non-Exclusive policy. Normal-window clicks may still raise their window while that latch is active. Installing or transferring an Exclusive latch, changing interactivity, or otherwise moving keyboard focus dismisses any active XDG popup keyboard grab before the arbiter sets its chosen focus. While an Exclusive latch is held, an unrelated popup grab request is denied with popup_done, because xdg-shell requires the topmost grabbing popup to own keyboard focus; a popup belonging to the latched layer may grab normally. When a focused layer stops being eligible, focus moves to the next Exclusive layer, otherwise to the highest visible normal toplevel, or to no surface when neither exists. Keyboard focus inside an Exclusive layer's own active popup grab satisfies the layer's latch: ordinary panel redraws neither pull focus back to the layer root nor dismiss its menu.

Committed wl_surface input regions participate in hit testing for every surface role. An empty region makes a panel click-through, and regions are clipped to the surface's presented buffer bounds. A committed region change, map or unmap retargets a stationary pointer after its complete Smithay surface transaction applies. Committed stack-band, subsurface-order and LayerMap geometry changes use the same retargeting path. Synchronized descendants therefore produce one atomic leave/enter transition and one hit test for the transaction, never intermediate targets from partially applied sibling state. If rearrangement moves the currently focused surface without changing its identity, the compositor sends one motion with the corrected surface-local coordinates. Output resize and KMS topology changes batch layer arrangement, usable-area derivation and window clamping before that single reconciliation. Up to 256 region operations are retained exactly; larger regions use their added rectangles' bounding box so protocol-thread hit testing stays bounded.

Exclusive zones reduce the usable output rectangle. New-window cascade origins, maximised sizes and normal restore clamping all use that rectangle and are recalculated after layer map, unmap or destruction and after output size or KMS topology changes. Layers are arranged before maximised windows are reconfigured, so their wire configure uses the current usable rectangle.

Layer shell honours the requested output, including an explicit wl_output. A request with no output uses the backend's default output; if no output exists, the layer surface is closed and is never mapped.

Session locking

An accepted lock enters Locking, immediately removes ordinary client content from the renderer roster and installs the opaque black security scene. The compositor sends locked only after the nested renderer acquires a swapchain image containing that epoch, submits the frame, calls the winit/wgpu present path, and waits for the submitted GPU work to complete. A Bevy schedule turn is not presentation evidence: a minimised, occluded, skipped or failed frame leaves the epoch pending and locked withheld. The blank itself satisfies the barrier, so a slow or absent client lock surface never delays a frame that is actually presented.

Live KMS uses the corresponding atomic-display authority boundary. The renderer captures the security epoch when it acquires the output image, carries the exact output key and render generation through the submission, and acknowledges it only when atomic presentation returns Displayed. OutputReady, an enqueued frame, a frame-clock pulse, cancellation and a failed commit are not security presentation evidence. Every output in the epoch must report its exact current generation before locked is sent.

Lock ownership lives on the Wayland protocol thread and is independent of DRM, seat and VT authority. Losing authority never unlocks: Locking, Locked and OrphanedLocked survive the pause. Resume creates a fresh epoch, installs the opaque lock scene before the output becomes usable, and withholds client frame callbacks and physical input until that epoch is displayed. Before selecting its resume policy, the coordinator asks the protocol thread whether Locking, Locked or OrphanedLocked is active. Any active lock disables seamless resume on every output: the retained pre-pause framebuffer is discarded rather than page-flipped, and the first submitted frame after authority returns is a freshly rendered opaque lock surface or black fallback. A lock output with no current lock surface remains black. Unlock received while paused changes the protocol state exactly once but keeps the black scene and input gate in place; after authority returns, the compositor displays an ordinary-scene epoch before restoring focus, input and normal client delivery. Keys, pointer buttons, scroll sequences and touches held by the lock scene are ended before its surfaces are retired. Input arriving behind the display gate is quarantined until its matching physical release, so no held gesture or release crosses into the restored ordinary focus.

The live KMS Ctrl-Alt-F1 through Ctrl-Alt-F12 bindings are evaluated before the presentation gate. They are compositor-only and never reach a Wayland client; every other key, pointer and touch event remains gated until secure presentation completes. Gated key presses advance Smithay's private XKB state so the VT chord can be recognised. If the presentation barrier opens before a matching release, quarantine still feeds that release through the same intercepted XKB path while suppressing client delivery; pressed keys and modifiers therefore cannot leak across the barrier. Physical key releases matching a synthetic pause release are quarantined by device and keycode, and pause clears both physical and suppressed touch-slot state.

An output removed or replaced while locked does not change lock ownership. Its exact Smithay (output, lock-surface, lock) registration is retired, and a new output starts with the compositor-owned black scene until the lock client maps a new surface through the normal wl_output lifecycle. The KMS transition logs stable harness markers: session-lock-kms-resume-blank-first, session-lock-kms-normal-exposure-held, session-lock-kms-{initial,resume,unlock}-epoch-displayed, and session-lock-kms-normal-exposure-restored. The actual display path also emits exactly one marker for each output's first successful flip after resume: session-lock-kms-resume-first-flip scene=<lock|blank|retained|client> output=<key> epoch=<presentation-epoch|none> generation=<generation>. A locked resume is valid only with lock or blank and the epoch announced by that resume; retained, client, an absent epoch or a mismatched epoch are fail-closed evidence of exposure.

The private desk_vt_run.mix --arm session-lock-vt arm brackets a real VT run with the existing recovery timer and snapshots. It launches the release cosmix-lock-probe, which creates a solid SHM lock surface for each output and prints COSMIX_LOCK_PROBE checkpoint=... records, then proves initial lock, VT away/back blank-first resume, delayed normal exposure and displayed-epoch unlock. It requires the safe per-output first-flip marker and requests a KMS texture-view PNG through the compositor's SIGUSR1 frame-capture path; the image must contain only the solid lock colour or black fallback. The arm scopes resume markers after a journal cursor captured while away and matches the resume-start epoch as well as the output. It accepts the probe's terminal unlock markers after either an active observation or an exact successful exit; mid-run lock markers still require the probe to be alive. The arm is intentionally a manual hardware gate; ordinary development only builds the helper and lints the script.

Each physical output accepts one lock surface for the current generation. Its first configure is immediate and exactly matches the logical output; a buffer may map only after acknowledging the current configure and must have the acknowledged dimensions. Every responding commit is checked against its effective buffer: an empty first commit is a null-buffer error, while an empty commit after resize revalidates the retained buffer against the new size. A surface with any earlier attach or commit history cannot become a lock surface. Lock surfaces and their subsurface trees receive input above any Exclusive layer. Ordinary surfaces may continue committing, but receive no renderer publication, frame callbacks, focus or input until unlock. Lock entry also purges ordinary deltas already queued for the renderer. Unlock or a pre-locked abort sends complete upserts for every presentable surface, so unchanged static windows are recreated as well as listed in the roster.

Entering Locking dismisses popups, ends pointer and drag-and-drop grabs, cancels touch, reconciles pressed keys, hides the client cursor, clears data-device focus and suppresses ordinary compositor bindings. The KMS VT switch binding remains the sole exception. Foreign-toplevel handles close at entry and neither update nor replay while locked; unlock reannounces mapped toplevels with the same identifiers. Physical input still resets idle notifications. If the owner dies before locked, locking aborts. If it dies after locked, the compositor becomes orphan-locked, removes the dead lock surfaces and retains the opaque blank while swallowing input. Blank-only areas and holes in a lock surface's input region swallow pointer, keyboard and touch; the compositor does not scan hidden window chrome there, start move/resize or caption-button grabs, dispatch close, or reveal a chrome cursor.

The global is deliberately advertised to every connected client: any client may request the lock. The accepted generation is nevertheless bound to the exact ext_session_lock_v1 resource, so a rejected object from the same client cannot create surfaces or unlock it. Destroying the accepted resource during Locking aborts safely. An orphaned lock never auto-unlocks and persists until the compositor exits.

Explicit-sync fault policy

A permanently faulted explicit-sync retirement pipeline restarts the compositor loudly instead of degrading the session (since 0.57.0). Previously a retirement fault withdrew the linux-drm-syncobj-v1 global and the session kept running on implicit sync — DMA-BUF clients could then be scanned out mid-write, which shows as constant flicker, and nothing surfaced the degradation. Now every fault path (asynchronous worker report, worker channel closure, and the synchronous request-time fault) withdraws the global for the brief teardown window, then requests a RuntimeFailure shutdown: under KMS the process exits non-zero for its supervisor to restart with explicit sync intact; nested, the scene fails loudly.

Transient stalls get bounded patience before that happens: the retirement worker grants each batch one 3 s wait on its captured submission index (sized to outlast a GPU engine-reset recovery) before a timeout is terminal — a single fixed-target wait, not a retry loop, because re-invoking the wait submits a fresh empty batch and chases a moving target. Structural faults (adapter panic, wait failure) are terminal immediately.

Two read-only props leaves expose the state over the Bus: info.explicit_sync_advertised (the protocol global is currently offered to clients) and info.explicit_sync_healthy (the retirement pipeline has not permanently faulted). Reads are computed from live state per request. They are not covered by comp.props.watch diffs: after the loud-restart policy a fault's unhealthy window is at most one dispatch cycle, so the leaves are process-lifetime constants in practice — poll them, don't watch them.

Vendored changes

The vendored Smithay layer-surface handle has an additive reset_after_unmap helper so the compositor can clear Smithay's private configure queue while applying layer-shell's protocol-mandated post-unmap state reset. Smithay's CompositorHandler also has an additive transaction_applied callback so pointer hit testing can observe one complete synchronized-surface transaction instead of each surface's intermediate state. Its pointer handle has one additive no-focus-restore grab teardown used when a grabbed surface disappears inside that transaction, preventing stale cached focus from being replayed before the final hit test. Smithay's foreign-toplevel list has one additive constructor accepting a compositor-provided identifier. This lets the identifier include Cosmix's surface identity, mapping generation and an unpredictable per-compositor instance nonce while protocol dispatch and replay remain entirely delegated to Smithay. Smithay's session-lock surface has an additive serial-returning configure method. This lets the common Cosmix configure gate record the exact immediate and resize serials while Smithay's default initial send observes that the pending state was already consumed and does not duplicate it. Its session-lock handler also has additive originating-lock, lock-object destruction, lock-surface destruction and construction-history hooks. These bind surface creation and Locking lifetime to the accepted resource while letting Cosmix's attach/commit ledger enforce AlreadyConstructed. The session-lock registry also exposes a narrow exact-surface retirement helper for KMS output replacement; it removes only the originating protocol object and does not alter the accepted lock generation.

Smithay's X11Surface has one additive test-support setter (set_wl_surface_offline) that assigns the associated wl_surface directly. The xwayland-shell serial handshake owns the real association; the setter exists only so the compositor's deterministic tests can fabricate offline X11 surfaces whose focus forwarding and metadata lookups still reach a real wl_surface.

The vendored session-lock implementation also carries five marked fixes:

  • invalid unlock_and_destroy returns after posting InvalidUnlock, so a rejected object cannot fall through to the compositor's unlock handler;
  • a valid unlock_and_destroy consumes its locked state before calling the compositor, so the unlock transition can occur only once even while visible restoration is deferred across a VT pause;
  • AlreadyConstructed validation happens before the output is inserted into Smithay's duplicate registry, preventing failed constructions from leaking entries; and
  • each output registration records its owning lock-surface and lock object, so abort retires only that generation and a stale destructor cannot erase a newer generation's registration; and
  • commit validation tracks the effective retained buffer, rejecting null first commits and stale-sized buffers after a newly acknowledged resize.

The associated output resource is retained until lock-surface destruction, valid unlock or generation abort so Smithay's resource duplicate registry and Cosmix's physical-output ownership map are both released while the compositor-owned blank remains.

The vendored wgpu-core carries a hand-backport of upstream wgpu 385520f7 ("re-read the fence before the queue-empty assert in Device::maintain"). Without it, a timed-out fence wait racing another thread's queue-drain panicked the retirement worker on a defensive assert — the trigger for the explicit-sync fault policy above firing every session. Provenance and the inherited caveats are in src/desktop/vendor/README.md.