Commit graph

53 commits

Author SHA1 Message Date
98879be1fc Build the standalone overlay glyph atlas so tab titles render next to icons
overlay_font_init() built the atlas only via the nested init_egl() path,
leaving overlay_font_ok false in standalone mode: overlay_text_width()
returned 0, so the overlay box sized to the icons alone, and
overlay_pass_text() drew no glyphs.  Call it from main() once the wlr
renderer exists (one-shot guard added so the two call sites never
rebuild the atlas), and cover it with a regression test asserting the
'[overlay] font' log line from a standalone headless session.
2026-08-16 10:29:10 +10:00
de252181af Guard nested-only host-window proxies so standalone exits cleanly on last tab close 2026-08-16 08:31:51 +10:00
872356eaa5 Per-device standalone pointer listeners so VT switch survives pointer teardown 2026-08-16 08:10:30 +10:00
131950d6e0 Survive session loss by detaching listeners before wlroots tears down output and input devices 2026-08-16 08:01:34 +10:00
382809b126 Handle Ctrl-Alt-Fn virtual-terminal switching in standalone mode 2026-08-16 07:39:37 +10:00
39151d2511 Route standalone keyboard events through the emitting device
The standalone libinput path kept a single global keyboard slot that was
overwritten for every attached device, so modifier state came from whichever
keyboard attached last. Hosts exposing several keyboard nodes (e.g. a
consumer-control or HDMI-CEC device beside the real keyboard) made the seat
deliver shift=0 permanently, so terminals only ever printed lowercase, and
Alt-based hotkeys never registered.

Attach one listener struct per libinput keyboard and resolve the device from
the listener in both handlers, then seat that device directly (tinywl-style)
so clients receive its real keymap and xkb-derived modifiers. Also fixes the
latent corruption of wl_signal_add()ing the same listener into many device
signals, and frees the per-device structs in shutdown_cleanup. The nested/ctl
virtual keyboard path is unchanged; 21 tests pass.
2026-08-16 07:24:19 +10:00
72b95f1204 Support standalone (native-output) mode alongside nested hosting
TABBED_MODE=standalone (or a visible WLR_BACKENDS) makes tabbed-wlroots its
own Wayland compositor rendering onto the first native output instead of a
nested host window. DRM+libinput is the default backend set; a headless
backend (created directly, since wlr_backend_autocreate wraps backends in a
multi backend that wlr_headless_add_output rejects) keeps the mode testable
in CI over the existing stdin feed.

Standalone:
- server_init creates a headless or autocreated backend and attaches a
  new_input listener; skips seat bridging (no host client to mirror).
- handle_new_output keeps the first output only, drives it at its preferred
  mode, and sizes the swapchain to the output instead of a host window.
- Font/icon overlays build CPU wlr_textures (premultiplied ARGB) alongside
  the existing GL textures so the tab overlay can be drawn by a second
  wlr_renderer pass over the committed output buffer.
- draw_tab_overlay_pass mirrors draw_tab_overlay's geometry with pass
  rects/textures; render_standalone builds scene state, applies the overlay,
  commits the buffer, and sends frame done. SIGUSR1 now also readbacks the
  composited frame (GLES2 FBO, or CPU data-ptr fallback) as /tmp/host_N.ppm.
- Native keyboards/pointers are attached to the seat and share the existing
  process_key_event/process_modifiers hotkey handling and the main loop's
  standalone branch (blocking dispatch + render on frame/redraw).

The nested mode is unchanged. Adds standalone smoke tests (boot/cycle and
Alt overlay readback under WLR_BACKENDS=headless); full suite 21 passing.
README documents the new mode.
2026-08-15 18:06:55 +10:00
33f8626bde Activate the left-neighbor tab when a tab's program dies 2026-08-15 12:35:51 +10:00
5be0b23e68 Open a terminal tab by default and fail fast on missing commands 2026-08-15 11:29:53 +10:00
c661117a71 Draw a 1px border inside the Alt overlay box edge 2026-08-15 10:28:14 +10:00
726838bfbc Truncate overlay rows so the menu stays narrower than the window 2026-08-15 10:28:14 +10:00
0632f8e3d0 Open a new terminal tab with Ctrl+Shift+T ($TERMINAL)
Adds a new-tab binding that launches a terminal (default alacritty,
overridable via the $TERMINAL environment variable, e.g.
TERMINAL="foot -e htop" tabbed-wlroots ...), inserting it right of the
active tab and activating it once it maps.

Also lets handle_new_toplevel claim exactly the add_tab()-prepared slot
instead of an earlier stale free one (a closed tab's hole), so the new
terminal keeps its position next to the tab it was launched from.
2026-08-15 09:16:09 +10:00
348c22ce4d Center popup overlay instead of top-left anchored 2026-08-15 07:57:08 +10:00
b8875f7292 Show per-tab app icons in the Alt-hold overlay 2026-08-14 12:55:42 +10:00
db6bbac84c Draw passive Alt-hold tab-title overlay with FreeType text 2026-08-14 12:39:51 +10:00
e52ac98afe Pass function keys through and mirror fullscreen between host and clients
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.
2026-08-14 11:31:32 +10:00
54c3c99e1d Cycle tabs with Alt+Up/Down matched by keysym
Ctrl+PageUp/PageDown and Ctrl+Tab are needed by real apps (browser tabs,
terminal scrollback), and a tabbed-wlroots hotkey consumes the combo before
the nested client ever sees it.  Alt+Up/Down is free on both the
GNOME host (no wm/mutter/shell binding) and labwc, so the keys reach the
compositor instead of being grabbed or swallowed. Alt+Up/Down is used in
some apps but in minor ways. I tried to use Alt+PageUp/PageDown but it
fails on some laptops like mine.
2026-08-14 10:31:50 +10:00
a86a21d76e Merge 'Drop redundant manual popup compositing' from origin/master 2026-08-14 08:19:19 +10:00
c2fdb7b6e1 Fix second N2H copy hang: ignore transient NULL dc selection
On the second nested->host copy, bridge_host_set_selection destroys the
previous data-control source before setting the new one.  The compositor
answers the destroy with a brief selection(NULL) which is delivered in the
same round-trip as the echo of the new source.  The old code treated that
NULL as a real clear: it cleared the pending-echo flags, so the genuine
echo arrived untagged, was misread as a real host selection and mirrored
back into a self-feeding set_selection/receive loop that left host
wl-paste spinning forever.

Ignore a NULL selection while our echo is still pending; a real host clear
never sets a pending flag, so it cannot be masked.  The dd path already
handles this via its focus-blip NULL early return.
2026-08-14 08:12:02 +10:00
b7e1c650dc Drop redundant manual popup compositing
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.
2026-08-12 10:40:05 +10:00
dc6f8f97c2 Harden pagedown-repeat test: TABBED_SYNTH_INPUT mode + stall-tolerant polling
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.
2026-08-11 21:51:52 +10:00
ea8770c887 Port [claim]/[launch] client-PID logging and the 13-test smoke suite
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.
2026-08-11 19:07:42 +10:00
3cc2ab38fd Fix SIGTERM shutdown deadlock: run teardown from an event-loop signal source
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).
2026-08-11 19:07:30 +10:00
8a46e92e03 Fix clipboard echo flood: keep pending-echo guards across focus blips
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.
2026-08-11 19:07:02 +10:00
ad46508962 Fix wlr-data-control clipboard: handle primary_selection and echo ordering
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.
2026-08-10 19:10:58 +10:00
d05ded946b Add bidirectional clipboard bridge with text prefetch
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.
2026-08-10 11:29:07 +10:00
2b034f990f Add stdin event feed and smoke test suite
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.
2026-08-10 07:29:10 +10:00
80f19c1836 Crop CSD gutter from client geometry instead of hardcoded 26px 2026-08-09 19:15:50 +10:00
df2e61a52f Forward discrete scroll wheel steps to clients 2026-08-09 18:43:10 +10:00
8acd384666 Document usage, keybindings, TABBED_LOG/dump semantics; fix F8->F9/F10 comment 2026-08-09 13:02:46 +10:00
49db6e9834 Remove tab limit: growable apps array (drop MAX_APPS and dead MAX_WIN defines) 2026-08-09 12:15:13 +10:00
18653c9031 Clean up stale Wayland socket on shutdown, crash, and startup 2026-08-09 12:02:21 +10:00
b80871c62c Handle SIGINT (Ctrl-C) like SIGTERM for graceful shutdown 2026-08-09 11:50:40 +10:00
3381193f11 Repeat tab cycling while Ctrl+PageUp/Down is held 2026-08-09 02:53:32 +10:00
b62e4d0386 Route xkbcommon logs through TABBED_LOG; robust host keymap read 2026-08-09 02:28:36 +10:00
3356e503f4 Quiet logging by default via TABBED_LOG; silence client chatter 2026-08-09 02:02:22 +10:00
4fce5e1e72 Disable direct scanout so nested frames composite fully
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.
2026-08-09 01:22:01 +10:00
95b13d29ff Add F11 toggle to fullscreen the host window
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.
2026-08-08 19:37:54 +10:00
43bca3dde8 Insert new tabs next to the tab that created them
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.
2026-08-08 18:31:52 +10:00
d149a1adf2 Advertise wl_surface at v4 to tolerate legacy client attach offsets
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.
2026-08-08 13:09:43 +10:00
570465c6e6 Pace host presents and add cross-context sync barrier
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.
2026-08-08 12:14:29 +10:00
3d2c627552 Kill client processes on compositor shutdown 2026-08-07 21:23:01 +10:00
3740fed066 Give dialogs/second windows their own tab 2026-08-07 20:31:24 +10:00
0b6a9eef75 Composite popups and fix sub-menu rendering 2026-08-07 19:07:40 +10:00
efecacacb1 Ignore maximize/fullscreen requests before first commit
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.
2026-08-07 11:15:53 +10:00
77cbf96ff2 Warn when host keymap fails to parse
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.
2026-08-07 11:02:09 +10:00
33956c7a18 Detach app listeners and switch tabs on client close
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.
2026-08-07 10:53:42 +10:00
729b72002f Keep all mapped clients maximized on host resize
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.
2026-08-07 10:38:52 +10:00
ec3ec08e11 Request server-side decorations from the host compositor
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.
2026-08-07 10:32:11 +10:00
b7faf9ec91 Crop CSD titlebar gutter instead of terminal bottom row
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.
2026-08-07 10:27:55 +10:00