Advertise xdg_wm_dialog_v1 and mirror each dialog object onto its
window. A modal dialog demands attention: on map (or when set modal
while mapped) its tab is shown and it takes window focus, in both
--tab-groups modes; placement itself is unchanged and session lock
still wins.
wltest gains --dialog-modal/--extra-modal/--extra-delay; smoke tests
cover the global, the background-modal steal with tab-groups on, and
the own-tab steal with tab-groups off.
Advertise zwlr_gamma_control_manager_v1 and commit client ramps to
the single output (reset when the control goes away); other outputs
and failed commits fail the control so clients retry. Outputs with
no LUT (headless/nested) are failed by wlroots itself.
wltest gains a --gamma mode reporting gamma_size/failed; smoke tests
cover the global and the clean headless failure path.
Advertise zwp_text_input_manager_v3 + zwp_input_method_manager_v2 and
relay the focused field to the in-session IME and back (enter/leave on
seat keyboard focus, keyboard-grab forwarding, IME popups above tabs).
Also advertise wp_content_type_manager_v1 (logged on map) and
wp_single_pixel_buffer_manager_v1 (scaled via viewporter).
wltest gains --text-input/--input-method/--content-type/--single-pixel
modes; smoke tests cover globals, the IME round trip, focus follow,
single-pixel rendering and the content-type log line.
Add a --popup wltest mode (toplevel + popup P1 + nested sub-popup P2,
each buffer-backed so all three map) and a headless smoke test proving
a full chain maps, the client can be kill -9'd mid-chain, and a fresh
chain maps afterwards with the compositor alive and crash-free.
Writing the mode exposed two client-side protocol bugs in wltest
itself (both fixed here): the final get_popup+commit batch was never
flushed (dispatch_pending+poll neither sends), and the hold loop never
READ new events (dispatch_pending drains only the already-read queue),
so the nested popup's configure -- proven sent via server WAYLAND_DEBUG
-- starved unread and P2 never mapped. The hold loop now roundtrips
periodically, which reads, dispatches and flushes.
Investigated as a potential live_popups use-after-free (submenu scene
outliving its parent): wlroots cascades child-popup destruction on
parent teardown and kick removes entries on popup destroy, so no stale
scene reference survives; 6 kill cycles under ASan/UBSan stayed clean.
No compositor change needed.
Let launchers focus existing windows via xdg-activation-v1. On
request_activate, find the kick window owning the surface and switch to
its tab (set_active_tab + window_enter, both gated on session_locked so
activation can never bypass the lock screen).
Add a --activate wltest mode (two windows; second steals focus, then a
token activates the first) and two smoke tests proving the global is
advertised and the inactive window receives an activated configure.
Let synthesised input (wtype & co.) drive the compositor through the
exact same paths as real devices: virtual keys flow through
process_key_event (so Alt+Up tab-cycles, chords swallow under lock/
inhibit), virtual pointer motion/buttons drive native_ptr_* and the
seat. A missing client keymap falls back to the server's so modifiers
resolve; cleanup uses resource-destroy listeners (run before wlroots'
finish asserts).
Add --virtual-key (code:state sequence with xkbcommon us keymap),
--virtual-motion/--virtual-click, and --pointer-out (window logs pointer
events) wltest modes; three smoke tests prove the globals are advertised,
a virtual Alt+Up cycles tabs, and virtual motion+click reach the window.
Let a client (wlopm) blank/unblank the single output. OFF overwrites the
composited scene buffer with opaque black before commit (a dedicated
render-pass fill in both render paths), so the committed frame is truly
dark rather than a stale scene frame. ON forces a full scene repaint
(the swapchain recycles the black buffer, so without whole-damage it
would re-commit stale black) and resumes normal compositing.
Extend --screencopy to report full-buffer blackness (black vs content),
add a --power on|off wltest mode, and three smoke tests: the global is
advertised, and a power off/on cycle blanks then restores the visible
window (verified via screencopy).
Let a focused fullscreen client (game/VM) grab the compositor's keyboard
shortcuts. New inhibitors for the focused surface activate immediately;
a lazy sync at grab-chord time (de)activates across focus changes so all
focus paths are covered without extra listeners. While honored, the
tab/window grab chords are swallowed in process_key_event (quit and VT
switch stay live so a stuck client can't trap the session).
Add a --inhibit wltest mode (window + inhibitor, reports active) and two
smoke tests asserting the global is advertised and the inhibitor becomes
active on the focused surface.
Composite the scene and let wlroots run the whole capture flow off the
output commit path: the manager is created in server init and every
commit carrying a rendered buffer becomes a copyable frame (both the
standalone and nested render paths already commit a buffer, and
wlr_output_update_needs_frame drives the render gate).
Bundle the wlr-screencopy XML locally (a copy of the one vendored in the
pinned wlroots subproject) since Meson forbids the parent project from
referencing a nested subproject's files; add a --screencopy wltest mode
that captures the output into a shm buffer and reports the frame
geometry, plus two smoke tests asserting the global is advertised and a
rendered frame is delivered.
Blank the screen and hold input when a lock client (swaylock) grabs
ext_session_lock_manager_v1. While locked an opaque black rect covers the
output with the lock client's per-output surface floating above it; no
window may take keyboard focus and the compositor grab chords are swallowed
so tabs can't be cycled behind the lock. kick manages a single output, so a
single lock surface is tracked. If the lock client dies without unlocking
the screen stays blanked (a deliberate security choice); only a fresh lock
request may take over. On unlock the previously active tab is refocused.
Includes a wltest --lock mode (binds the manager, locks, draws a buffer on
the lock surface, and optionally --lock-unlock) and two smoke tests:
advertised-globals and lock/surface-config acceptance.
--layer <bg|bottom|top|overlay> creates a zwlr_layer_surface_v1 instead
of an xdg toplevel, with --layer-anchor/--exclusive-zone/--layer-size
control. Wire the wlr-layer-shell-unstable-v1 client protocol into the
wltest target. Lets the smoke tests exercise kick's exclusive-zone
work-area computation without depending on an external panel.
destroying the previous wl_buffer immediately after attaching its
replacement raced the compositor's use of the last committed frame and
pulled the rug from under data-pointer access to buffers still on
screen -- black frames exactly when buffers turned over. a few KB of
shm per resize is an acceptable price for a short-lived test client;
resizes are rare.
create_buffer() early-returned once a buffer existed, so the client kept
attaching its original 1280x800 pixels no matter what size the compositor
configured -- every resize/fullscreen test was silently verifying against
a client that cannot resize, painting old-extent content inside whatever
window state the compositor applied.
Kick advertised only pointer+keyboard and ignored every touch device, so
clients never saw a touchscreen and taps were silently dropped.
The seat now advertises WL_SEAT_CAPABILITY_TOUCH. Standalone mode wires
libinput touch devices through wlr_cursor with normalized-absolute warping
and delivers via the seat touch API with slot ids preserved (multi-touch).
Nested mode binds wl_touch when the host seat offers the capability,
forwards events through the same coordinate space as the pointer path, and
ends live sequences cleanly on capability loss or host cancel.
The ctl test feed gains CTL_TOUCH so headless tests can inject taps, and
wltest learns --touch-log to record received events; an end-to-end
regression test proves down/motion/up reach the client with correct slot
and coordinates.
A second toplevel from an already-connected client is a sub-window of
that client's tab. tabs (default) keeps today's behavior: its own slot
glued right of the parent. stack never creates a slot: the window rides
above the parent tab's surface as a scene-tree child, sharing its
visibility, holding the keyboard while the tab is shown, and falling
back to the tab's own surface when it closes. Stacked windows are
invisible to cycling and the overlay; closing the tab closes them.
Overlay rows follow what sits on top of the tab: the row title and icon
resolve from the topmost mapped stacked window and revert when it goes,
with a [title] log line per row-title change for observability.
wltest grows an 'X' key that closes its --extra toplevel so the unstack
path is testable.
Host-primary pastes into the nested seat used to hang waiting for EOF:
bridge_inner_primary_source_send forwarded the demander's fd to the host via
data-control receive but never closed it, so opposite-end reads blocked until
the compositor closed it elsewhere (wlroots only does that for its own
client_source_send). Close the fd after the direct receive, and eagerly
prefetch the mirrored text/plain payload in a separate buffer so the common
paste is served synchronously without an async fd round-trip.
wltest gains --primary-demand-expect <file> <text> so a nested test can wait
for a specific payload: the live host may already hold its own primary at
boot, which kick faithfully mirrors first, and wlroots only sends offers to
the focused client on the seat. The new test crowns the demander tab before
the host writes its primary via wl-copy --primary.
Mirror the nested seat's zwp_primary_selection_v1 into the host via the
existing wlr-data-control v2 device (same pipe the clipboard uses), so a
middle-click inside a terminal publishes its primary to the host and the
host's own primary selection shows up in the nested tabs.
- host->nested: a real primary_selection offer is mirrored into the nested
seat as a wlr_primary_selection_source whose send pulls from the host
offer's fd; our own publish is tagged as an echo and swallowed (with the
same NULL-before-echo guard the clipboard already uses).
- nested->host: a nested set publishes a fresh data-control source offered
with the client's mimes; host middle-click requests are served straight
from the originating nested client.
- guarded by the bound data-control version (v1 hosts get no primary
channel, flagged in the init log); the wl_data_device fallback stays
primary-less as documented.
- wltest --primary-set/--primary-demand now trace the offer/send path.
- new nested-mode smoke test exercises the round-trip and asserts the
bridge publishes exactly once (no echo loop); suites now 57 passed.
Advertises zwp_primary_selection_v1 so text editors can publish and
middle-click-paste without a full clipboard roundtrip. The device-manager
global hooks the seat; the request_set_primary_selection handler installs
the source on the seat (wlroots only emits the request), and the seat's
focus-change/set fan-out delivers the offer to the keyboard-focused tab's
device, mirroring the clipboard path. No host bridging: the selection
stays within hosted tabs for now.
wltest gains --primary-set <text> and --primary-demand <file>. The setter
defers its set_selection until a seat-granted serial arrives (wlroots
rejects serials it never handed out), the demander pulls the payload over a
pipe polling both the pipe and the display socket (the compositor forwards
the fd to the owning tab, whose reply arrives with no display event of
ours). The smoke test cycles between the two tabs so the setter gets a
serial and the demander is refocused to flush the offer, then verifies the
payload lands in the file.
Adds zwp_pointer_constraints_v1 and zwp_relative_pointer_v1 so games and
creative apps can grab the cursor and get raw relative motion. The
focused surface's constraint is activated/deactivated on seat pointer
focus change; LOCKED pins the pointer to its activation position and
CONFINED clamps it to the constraint region (empty region = the whole
surface), applied through wlr_cursor_warp so the rendered arrow follows.
Relative deltas are forwarded from the real input paths: native
standalone motion sends the raw delta, nested mode derives it from
consecutive host positions.
meson generates the pointer-constraints protocol header (the system
wlroots header quotes it) and the wltest client stacks. wltest gains
--constrain locked|confined plus --confine-rect, and two smoke tests
verify the cursor stays pinned under LOCKED and clamped to the region
under CONFINED.
Clients relied on these to run at their best and fell back otherwise:
wp_presentation gives frame pacing feedback, zxdg_output_manager_v1
logical output geometry for newer toolkits, wp_viewporter lets video
clients scale buffers instead of reallocating. All three are passive
wlroots globals. The output layout now exists in both modes so the
nested host-window output is part of it; without that, a client's
get_xdg_output request hit wlr_xdg_output_v1.c's layout_output assert
and aborted the compositor. wltest gains --globals-out to dump the
advertised interface list (CLI-tab stdout is /dev/null'd by kick), and
a smoke test asserts the new globals are advertised.
Nested clients that switch the pointer cursor's shape now keep a correct
cursor over kick's window: advertise wp_cursor_shape_manager_v1, apply the
requested shape to the compositor cursor in standalone mode, and forward it
to the host pointer in nested mode via a hand-rolled client call (kick stays
a single C file, so the generated client header is not included). Shapes
are cached per tab and reset to default on tab switch so a text cursor never
leaks between apps. Legacy wl_pointer.set_cursor surfaces are not bridged.
The ctl pointer feed now also drives the nested seat directly, so the harness
can give a nested client a pointer enter. wltest gains --cursor-shape and an
explicit flush (wl_display_dispatch does not flush queued requests), plus the
now-required frame handler. New tests cover apply, tab-switch reset, and
nested forwarding; tail of README documents the protocol and its limits.
Commands fork one at a time, each gated on the previous one's client
connecting, so tab order always mirrors the command-line order (no pid
matching, no /proc ancestry). The window stays black until the last
command's tab maps, which is then crowned active. A new tab never
steals focus on its own: only Ctrl+Shift+T terminals, sub-windows of
the tab currently shown, and the startup crown activate. Sub-windows
(a second toplevel from an already-connected client) get their own slot
glued right after their parent tab. A command that never connects
stalls the chain and the crown, leaving the window black (accepted).
Tests: wait_any_active waits for the single deterministic crown; an
externally-spawned client is now born hidden (cycle to it). Add wltest
--extra (same-client second toplevel) plus tests for sub-window
placement/focus and Ctrl+Shift+T focus-on-map. Update README.
Stop grabbing F9/F10/F11 so they reach the focused client (e.g. a
browser's F11); frame dumps move to SIGUSR1 only. Honor client
fullscreen requests and mirror state both ways: a fullscreen client
fullscreens the host window, and a fullscreen host window fullscreens
every tab (anything else re-maximizes them).
wltest gains a wl_keyboard binding that toggles fullscreen on 'F', and
the smoke tests cover the full round trip plus function-key passthrough.
Compositor: read raw keyboard records from stdin and dispatch them into
the real host_kbd_key/host_kbd_modifiers handlers, so keybindings (F11,
Ctrl+PageUp/Down, F9/F10 dumps) can be driven without a focused host
window or wtype. Binary wire: 1|u32 key|u32 state, 2|u32 mods|...
Tests: tests/wltest.c solid-colour xdg_toplevel client, plus a pytest
suite (tests/test_smoke.py) that launches nested instances per test and
feeds events through the new channel. test.sh runs it. F11 fullscreen
toggle now logs its new state.