Core session management infrastructure for Phase 3:
- Session module: SessionId, SessionToken, SessionState enum with
validated transitions (Creating→Active⇄Suspended→Destroyed),
Session struct with timeout tracking, SessionRegistry with O(1)
lookup by ID and token (10 unit tests)
- Protocol: ClientHello gains optional token field, ServerHello gains
resumed flag and token echo, SessionStatus/SessionEvent enums added
to ControlMessage for session state notifications
- Server: SessionRegistry in headless CalloopData, sessions created on
client connect (with token lookup for resumption), suspended on
disconnect (not destroyed), periodic cleanup of expired sessions
- Client: --token CLI flag, persistent token at ~/.config/wayray/token
with auto-generation (random 16-byte hex), token sent in ClientHello,
resumed state logged from ServerHello
Complete the Phase 2.5 protocol wiring so external WMs can actually
control the compositor:
- Add compositor action methods to WmProtocolHandler: configure_window,
focus_window, close_window, set_fullscreen, set_decoration
- Wire manage-phase requests (propose_dimensions, set_focus, close,
fullscreen grant/deny, decorations) through to Smithay ToplevelSurface
- Trigger manage_start on new toplevel, render_start each frame, and
notify_window_closed on toplevel destruction when external WM connected
- Apply external WM render commands in headless render loop
- Keybinding dispatch: bind_key/unbind_key storage, mode support,
binding_pressed/released events sent to WM seat
- Built-in floating WM: Alt+F4 close, Alt+Tab focus cycling with tests
Add a custom Wayland protocol (wayray_wm_v1) that allows external
window manager processes to control layout, focus, and keybindings
in the WayRay compositor, inspired by River's two-phase transaction
model.
New crates:
- wayray-wm-protocol: Wayland protocol XML + generated server/client
bindings via wayland-scanner for four interfaces (manager, window,
seat, workspace)
- wr-wm-tiling: Reference BSP tiling WM demonstrating the protocol
Compositor changes:
- WindowManager trait + WmState coordinator abstracts WM behavior
- Built-in floating WM (centered windows, click-to-focus, z-ordering)
- Protocol server with GlobalDispatch/Dispatch for all interfaces
- Hot-swap (replaced event) and crash resilience (fallback to built-in)
- new_toplevel delegates to WM instead of hardcoding 800x600 at (0,0)
- WM render phase integrated into headless frame pipeline
Replace ControlFlow::Poll (CPU-hungry busy loop) with an
EventLoopProxy that the network thread uses to wake the winit
event loop only when a new frame arrives. Zero CPU when idle,
instant wake on new frames.
The winit event loop defaulted to Wait mode, only processing frames
when user input arrived. Switch to Poll so the loop continuously
checks for new frames from the network thread.
- Keycodes: add +8 offset for XKB (evdev scancode + 8 = XKB keycode)
- Client: force exit on Cmd+Q/close (network thread may block)
- Server: force exit on Ctrl+C (network thread may block on accept)
Proper graceful shutdown with tokio CancellationToken deferred.
Root cause: Window::bbox() stayed at 0x0 because Window::on_commit()
was never called after surface commits. The Space then never associated
the window with the output (no overlap), so render_elements_for_output
returned empty.
Two fixes:
- Call window.on_commit() in CompositorHandler::commit() to update
the window's bounding box from the committed surface tree
- Call space.refresh() each frame to update output-element mappings
Also: send xdg_toplevel configure with suggested 800x600 size.
renderer_gl and backend_winit pulled in backend_egl, which changed the
ImportAll blanket impl to require ImportEgl — a trait PixmanRenderer
doesn't implement. This caused render_output to silently skip client
surface compositing (only the clear color rendered).
Fix: move renderer_gl and backend_winit behind a "winit" cargo feature.
Default build uses only renderer_pixman, which satisfies ImportAll via
the simpler ImportMemWl + ImportDmaWl blanket impl.
Winit backend: cargo build -p wrsrvd --features winit
Headless (default): cargo build -p wrsrvd
ExportMem::copy_framebuffer may not capture composited client surfaces.
Read pixels directly from the pixman Image's CPU memory after rendering,
since PixmanRenderer composites in-place. This should fix missing
client windows in the remote display.
Server (PixmanRenderer) outputs ARGB8888, client GPU texture expects
BGRA8. Added byte-order conversion in update_frame(). Also changed
texture format from Bgra8UnormSrgb to Bgra8Unorm for correct colors.
Start QUIC server in wrsrvd main.rs before dispatching to backend,
passing NetworkHandle to the headless backend. The headless render loop
now encodes each frame as XOR diff against the previous frame, compresses
damage regions with zstd, and sends FrameUpdate messages to connected
clients via the network channel.
Network input from remote clients is drained each iteration of the main
event loop and injected into the compositor via inject_network_input().
Connection state (client connected/disconnected) is tracked to avoid
encoding frames when no client is listening.
Implement client-side input capture (input.rs) that converts winit
WindowEvents to protocol InputMessages: keyboard via evdev keycode
mapping, pointer motion/buttons/axis. Wire into wrclient main.rs
event handler to send input over QUIC via the existing input channel.
Server-side: add inject_network_input() to WayRay state that accepts
protocol InputMessages and injects them into the Smithay seat, following
the same patterns as process_input_event for keyboard, pointer motion
with surface focus, click-to-focus, and axis scroll.
Also upgrade client frame handling to use persistent framebuffer with
XOR-diff decoding via wayray_protocol::encoding::apply_region.
Implement QUIC networking for wrsrvd (server) and wrclient (client) using
quinn over rustls with self-signed certificates. Three logical channels:
control (bidirectional), display (server->client unidirectional), and
input (client->server unidirectional).
Server runs tokio in a background thread, communicating with the compositor
via std::sync::mpsc channels. Client exposes an async connect() API that
returns a ServerConnection with methods for sending input and receiving
frames.
Key design note: quinn streams are lazily materialized -- accept_bi/
accept_uni on the peer won't resolve until data is written. The handshake
protocol accounts for this by having each side write immediately after
opening streams.
Restructure wrsrvd to support two backends: a headless PixmanRenderer
(default) for running without a display server, and the existing Winit
backend (via --backend winit). The render logic is split into per-backend
modules, and the old render.rs is removed.
Encoder: xor_diff + encode_region (per damage rectangle).
Decoder: apply_region (decompress + XOR apply to framebuffer).
Both live in wayray-protocol::encoding for shared access.
14 tests including 3 end-to-end round-trip integration tests.
Message types for control, display, and input channels with serde
derives. Length-prefixed postcard codec with encode/decode/framing.
Seven round-trip tests covering all message types and edge cases.
- Remove CapturedFrame struct and last_capture field (no consumer yet)
- Remove display_handle field (re-obtainable when needed)
- Prefix output_manager_state and xdg_decoration_state with _ (kept
alive for Wayland globals but not directly read)
- Keep framebuffer capture logging to verify ExportMem path works
- Change Transform::Flipped180 to Transform::Normal for virtual output
- Add TODO comment on resize handler for future output mode update
- Rename misleading JsonPointer* import aliases to *Trait
- Add PrimarySelectionState and delegate: terminals like foot expect
the primary selection protocol to be present
- Add XdgDecorationState and delegate: allows clients to negotiate
server-side vs client-side window decorations
- Send initial configure for popups so menus and tooltips render
- Flatten nested if-let chains in commit handler (clippy)
- Include framebuffer capture in render pipeline (from prior task):
copies rendered frame via ExportMem for future network transport
Initialize keyboard and pointer on the Wayland seat at startup, and
route Winit input events (keyboard, pointer motion, button, scroll)
through process_input_event to the Smithay seat so clients can receive
input. Click-to-focus raises the clicked window and sets keyboard focus.
- Create render module using Smithay's desktop::space::render_output
for damage-tracked frame rendering to the Winit backend window
- Store backend and damage_tracker in CalloopData so the Winit event
callback can trigger rendering on Redraw events
- Send initial xdg toplevel configure on first commit so clients can
begin drawing
- Send frame callbacks after each render so clients schedule redraws
Initialize WinitGraphicsBackend with GlesRenderer, create a virtual
Output matching the window size, set up a Wayland listening socket via
ListeningSocketSource, and run the main event loop through calloop with
WinitEventLoop as an event source. The compositor now opens a window
and accepts Wayland client connections.
Move handler trait impls and delegate macros from state.rs into
handlers/{compositor,xdg_shell,input,output}.rs. Flesh out
CompositorHandler::commit with on_commit_buffer_handler and
XdgShellHandler::new_toplevel with window mapping. Add DataDeviceHandler,
SelectionHandler, ClientDndGrabHandler, and ServerDndGrabHandler impls
with DataDeviceState in the WayRay struct. State.rs now contains only
the struct definition and constructor.
Define the central WayRay state struct holding all Smithay subsystem
states (compositor, xdg_shell, shm, seat, output, space, clock) and
wire Display creation into main.rs. Includes minimal handler trait
impls and delegate macros needed to compile; these will be expanded
and moved to a handlers module in Task 4.