Popup/menu surfaces are regular wlr_scene_xdg_surface nodes under their
parent's tree, so wlroots' scene renderer already composites them into the
output buffer like any other surface. The host-side second draw
(composite_popups/popup_draw_cb + popup_tex) was a redundant double-blend
that predated the scene nodes; with it removed, menus and sub-menus render
correctly through the scene path. Keep the popup scene-node wiring,
unconstrain, and commit/reposition/destroy listeners.
Count the active-transition baseline before sending the press so the
press's own immediate tab cycle always counts toward the >=3 transitions
instead of racing with the compositor processing the stdin record.
TABBED_SYNTH_INPUT=1 makes the stdin event feed the only source of
key/modifier input: host_kbd_key and host_kbd_modifiers drop real host
events (kbd != NULL) while enter/leave/repeat_info still drive the
clipboard bridge. A stray physical key (e.g. Alt) no longer cancels a
synthesized repeat or clobbers the injected modifier state mid-test.
The repeat test now polls for base+3 tab transitions with a 5s deadline
instead of a fixed 1.3s sleep, so a present/frame stall no longer causes
spurious failures.
Brings in master's client-PID logging ([claim] ties an app slot to the
connected client's PID, [launch] records the spawn PID) and its expanded
smoke suite, so the fixed tree can run and pass the full merged suite.
SIGTERM/SIGINT ran shutdown_cleanup() straight from a raw signal handler.
The teardown path calls malloc/free and libwayland proxy destroys, which
are not async-signal-safe: a SIGTERM landing inside launch_client()'s
fork() left libc's allocator lock held, and bridge_cleanup()'s calloc()
then blocked on it forever, so the compositor never exited and tests that
send SIGTERM timed out.
Route SIGTERM/SIGINT through wl_event_loop_add_signal() (signalfd) so the
teardown runs from a normal event-loop callback, where no allocator or
stdio lock can be held across it. Also make sigchld_handler() async-signal-
safe (snprintf+write instead of fprintf, which takes the FILE lock).
The nested wl_data_device selection handler treated the host's own echo of
our set_selection as a real selection whenever a focus blip (dd selection
with a NULL offer) had cleared the pending-echo flags first. That echoed
our selection back to the host and started a self-feeding set_selection /
dc_source_send loop that never converged and never timed out.
- bridge_dd_selection: a NULL offer is a focus blip - keep the last-known
selection and leave the pending-echo flags intact instead of clearing
them and mirroring back.
- dc/dd echo swallow: clear the matching pending-echo flag when we swallow
our own echo (previously only the dc path reset anything).
- host_kbd_enter: only reset the pending-echo flags for the focus-based
wl_data_device backend, where a stale serial can orphan a guard. The
data-control backend echoes independently of focus, so its in-flight
guard must survive keyboard-enter events.
labwc sends the primary_selection event (opcode 3) on bind; leaving the
listener unset made libwayland abort. Acknowledge and destroy the offer
(primary selection is not bridged).
Echo handling used one shared pending_echo flag: when the host selection
is set via data-control, the compositor echoes the seat change over both
the wl_data_device and data-control channels, so the first echo consumed
the shared flag and the second was misread as a real host selection,
clobbering the nested source via mirror_to_nested. Track the pending
echo per backend and clear both on any real selection.
Verified: clipboard tests pass on labwc (data-control) and GNOME/Mutter
(wl_data_device); full 7-test suite green on both hosts.
Two end-to-end tests driving the focus-based wl_data_device path with
wl-copy/wl-paste: nested->host (exercises bridge_host_set_selection and
the focus-serial defer/retry) and host->nested (exercises
bridge_dd_selection and the text prefetch mirror). Exact-byte matching
with unique tokens and a poll deadline; skipped when wl-clipboard tools
are absent.
Bridge the nested wlroots seat's clipboard with the host compositor.
Uses wlr-data-control when the host exposes it, otherwise a focus-based
wl_data_device fallback (Mutter). Text selections are eagerly prefetched
at mirror time so every paste is served instantly from a compositor-side
buffer instead of paying the host source's latency per paste; non-text
MIMEs keep the zero-copy live relay.
Also fix a critical bug: prefetch_fd was zero-initialized instead of -1,
so the first bridge_prefetch_cancel() closed stdin, breaking the test
event feed and spinning the main loop on EBADF.
Debian >= trixie packages wlroots versioned (libwlroots-0.20-dev,
pkg-config wlroots-0.20), which falls outside the >=0.19,<0.20 pin, so
the bundled 0.19.1 source build is used. Fix the README apt line
accordingly and build the wlroots subproject with werror=false so the
newer libinput (1.31.3) enum LIBINPUT_SWITCH_KEYPAD_SLIDE is only a
warning, not a fatal -Werror=switch error.
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.
wlroots was presenting the client's raw buffer straight to the headless
output via direct scanout, bypassing our swapchain, so the host blit
showed a stale/black window (seen with Firefox). Force composition into
the swapchain by setting WLR_SCENE_DISABLE_DIRECT_SCANOUT.
Keep F8/F9, SIGUSR1 and TABBED_AUTODUMP dumps; gate verbose per-frame
diag logging behind TABBED_DEBUG=1.
Track the host compositor's fullscreen state from the xdg_toplevel
configure states and toggle it with F11 (xdg_toplevel_set_fullscreen /
unset_fullscreen). Passing a NULL output lets the host compositor choose
the current output; the existing resize cascade re-sizes all tabs with
the fullscreen configure.
Instead of appending dialogs/second windows at the far right of the tab
list, insert the new tab immediately after the tab that created it, using
the protocol parent (wlr_xdg_toplevel.parent) first and falling back to
the active tab, then to appending at the far right.
VLC's Wayland vout places video using non-zero wl_surface.attach x/y
offsets, which is a protocol violation on surfaces bound at version 5 or
higher and got the client killed with an invalid_offset error during
resizes. Advertise wl_surface at v4 so those offsets are accepted and
ignored as before the v5 breaking change.
Gate host presents with the surface's wl_callback so at most one buffer is
outstanding at a time (coalescing bursts via back_dirty), add a glFinish
barrier between the wlr GLES2 renderer context and the host EGL context used
by the blit, and switch the scene clear colour to black after removing the
import-failure present. Keep temporary F8/F9 frame dumps (scene vs. host
back buffer) for grey-band diagnosis.
GTK clients like geany send xdg_toplevel.set_maximized before their
first commit. wlr_xdg_toplevel_set_maximized() schedules a configure,
which asserts the surface is initialized and aborts the compositor.
Guard the request handlers with the surface's initialized flag and let
the commit handler apply maximization once the client has committed.
Host keyboard keymaps occasionally fail to compile with xkbcommon, which
was silent and left clients on the fallback layout. Log a clear message
so it is diagnosable instead of confusing.
wlroots emits xdg_toplevel events.destroy with NULL data and asserts
every toplevel/surface event listener list is empty afterwards. We kept
8 listeners attached and never removed them, so closing any client
tripped destroy_xdg_toplevel's assertion and took down the whole
compositor. The active-tab check also compared against NULL data, so the
switch/exit path never ran.
Capture the destroyed toplevel before nulling it, remove all attached
listeners so wlroots' destroy assertions pass, activate the next live tab
when the focused one closes, and only exit when the last tab is closed.
output_resize only resized the active toplevel, so inactive tabs kept
their old (smaller) size and were still small when switched to. Loop
over every mapped client, maximizing and sizing them all whenever the
host window changes size, so tab switching needs no per-tab relayout.
The nested window is an xdg_toplevel client inside labwc, but tabbed-
wlroots never bound xdg-decoration-unstable-v1, so labwc added no title
bar. Generate the client protocol (wayland-scanner), register it in the
Makefile, and after creating the host toplevel request SERVER_SIDE mode
so labwc draws a real host title bar above the nested content. Adds an
informational configure listener that reports the effective mode.
For clients (kitty/foot) that reserve a titlebar gutter above their
content but never paint it, that area showed as a dark bar while their
content sat low enough that the bottom row/cursor was pushed out the
output edge. Shift such clients up by the gutter so the dark bar is
cropped away instead of the terminal's bottom. Clients with no gutter
(geometry.y >= 0, e.g. the shm test client) are left untouched.
On a headless backend wlr_allocator_autocreate yields a shm allocator,
so the scene swapchain buffer is shm, not a dmabuf. import_scene_buffer
only handled dmabufs, so the composited scene never reached the host
window. kitty only worked via accidental direct scanout of its own
dmabuf buffer.
Add a full-output background scene_rect so the render list always has
more than one entry (disabling single-buffer direct scanout), and add a
CPU-read fallback to import_scene_buffer for non-dmabuf buffers. Now the
whole window composites through for both shm and dmabuf clients, with
content reaching the exact bottom edge.
Periodic dumps filled /tmp with 3MB PPMs and stopped working once the
swapchain stopped being ARGB8888 dmabufs. Capture the presented host
EGL window with glReadPixels instead, triggered on demand with
kill -USR1 <pid>.
Nested compositor running on the wlroots 0.19 headless backend with a
host EGL window on labwc. Clients are always maximized to the host
content area and keyboard input is delivered via the seat (tinywl
pattern). Closing the last tab closes the host window and exits.
Track sub-surface roles and positions via the wl_subcompositor so foot's
client-side decorations render: toplevel -> titlebar -> controls. Draw the
active surface's buffer at the window-geometry offset and its sub-surfaces
recursively on top, clipping to the window. Sub-surface commits of the active
tab trigger redraws but never resize the host window.
Blit client buffers from their top-left origin instead of honoring
set_window_geometry offsets: clients like foot set a geometry that
extends above the buffer (CSD shadow, geom_y < 0), which made the
source row pointer start before the buffer and produced a blank
frame. Buffers are now copied 1:1 from origin, capped to canvas.
Also dispatch wl_callback.done on the first configure commit so
frame-throttled clients (foot) don't stall waiting for it.
Single-file Wayland compositor presenting client surfaces as tabs in a
GTK3 window. Implements wl_compositor, wl_shm/shared memory, xdg-shell,
wl_seat/keyboard/pointer, and launches apps from tab-commands.
- Validate client buffer geometry against pool size at create_buffer and
commit time to prevent out-of-bounds reads from malformed buffers.
- Release client shm buffers immediately after deep-copying pixels,
replacing the fragile foot/GTK3 deferred-release state machine.