Commit graph

38 commits

Author SHA1 Message Date
595e9b95a0 add touchscreen support: nested forwarding and standalone native input
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.
2026-08-25 09:36:17 +10:00
87f25783d9 propagate host fullscreen to stacked tab-group members too
clients_set_fullscreen only told each tab's main surface, so members
stacked before a toggle never learned they were fullscreen, and members
that mapped during fullscreen were never released from it.  walk each
tab's member list with a member_set_fullscreen helper (mirroring the app
one) and give xdg members the same re-maximize/resize mains get on the
way out; X11 members keep their own geometry.  logged as
"members fullscreen=" next to the existing line.
2026-08-24 23:25:31 +10:00
86fe848acd toggle the kick window's fullscreen with prefix+F11
a grabbed prefix+F11 chord flips the host window's fullscreen state via
libdecor; the resulting configure fans out to every tab through the
usual host->clients propagation, exactly like a host-compositor
fullscreen.  latched against key repeats and a no-op standalone, where
there is no host window.  plain unprefixed F11 still passes through to
clients.
2026-08-24 23:09:43 +10:00
08202dc6c3 fix X11 app startup: no forced toolkit backends; X11 windows join tabs at first map
GDK_BACKEND/QT_QPA_PLATFORM forced to wayland stripped toolkits' fallback,
killing X11-only builds like emacs-gtk ('cannot open display' against a
healthy Xwayland). Children now self-select from WAYLAND_DISPLAY/DISPLAY,
and XAUTHORITY is cleared.

Separately, X11 membership decisions moved from window-creation time to
first map: GTK leader windows never map, yet claimed the tab slot at
creation, leaving a permanent black tab while the real frame was demoted
to a stacked member -- and Alt-cycling onto that phantom main member
crashed on wlr_seat_keyboard_notify_enter(NULL). Never-mapped windows now
free silently; keyboard-entry paths guard on live surfaces. New
tests/xleader.c probe (unmapped leader + mapped frame) locks the behaviour
in.
2026-08-24 10:43:41 +10:00
ff8a5bc23b auto-detect standalone mode when launched from a Linux virtual console 2026-08-24 08:51:51 +10:00
0868571a7f show each window of a tab with its own icon in the overlay
the tab overlay now lists every window of a tab (main plus stack members)
with a per-window icon and title instead of one icon and title per tab, and
highlights only the active window so the overlay cursor can move across all
of a tab's windows. titles are also centered within each row.
2026-08-22 23:32:03 +10:00
2263d1096a cycle the active tab's windows with Alt+Left / Alt+Right 2026-08-22 22:16:51 +10:00
39329347f6 close the active window with Alt+Delete, not the whole tab 2026-08-22 22:03:34 +10:00
118f5c276a rename --sub-windows to --tab-groups; document process-ancestry grouping 2026-08-22 13:32:04 +10:00
753b37ee7e add Alt+ backtick to cycle a tab's stacked windows backward 2026-08-22 08:19:08 +10:00
05189128df add XWayland support: X11 apps open as tabs beside Wayland clients 2026-08-21 12:37:28 +10:00
2dde2b6510 add --sub-windows tabs|stack: sub-windows can stack on their parent tab
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.
2026-08-21 11:35:21 +10:00
b9d1782938 un-mark fixed clipboard tests and retire dead flaky tests
test_clipboard_nested_to_host and test_clipboard_host_to_nested were
marked flaky because of the bridge stall now fixed; re-enable them in
the default run. Drop the host-colour readback and never-connecting
tests along with their helpers, and mark the primary-selection bridge
test flaky instead. Update the README's flaky-test note.
2026-08-21 07:44:00 +10:00
75735e38f4 mark flaky tests and exclude them from the default run 2026-08-20 21:22:25 +10:00
957f7c5749 skip host->nested primary test without a data-control host
test_nested_host_primary_to_kick only has a host->nested primary channel
when the host exposes wlr-data-control v2; a focus-based wl_data_device
host (GNOME/mutter) has no primary bridge at all, so on those desktops the
demand-expect tab can never receive the host primary and the test timed
out.  Wait for the bridge_init backend decision and skip unless the
data-control v2 backend is in use; the round-trip is still exercised by
./test-headless.sh, whose headless host is kick itself.
2026-08-20 21:22:25 +10:00
9e9f4e3174 apply and forward cursor-shape-v1 requests, with per-tab cache
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.
2026-08-19 15:08:31 +10:00
3ccae6cbe1 rename project to kick (repo, binary, socket, env) 2026-08-19 11:46:25 +10:00
ac386e6454 run nested tests against a headless host; fix standalone data-control selection 2026-08-19 10:27:43 +10:00
0f296d50d6 generate Wayland protocol stubs at build time 2026-08-19 09:54:11 +10:00
9683f17719 add --start-cmd-index to choose the startup-focused command
The last command-line command is active-and-black from the first frame by
default; --start-cmd-index N (1-based) instead makes command N the one the
program starts focused on.  The 1-based user value is converted to the
program's 0-based index as soon as it is parsed; a value outside 1..N is a
startup error (rc 2) naming the flag.
2026-08-19 09:21:46 +10:00
7f756ba8ee Launch CLI tabs in parallel; slot-claim by process group
- All command-line tabs fork at startup; the last one is active and shown
  black until its first frame maps, matching the existing README contract
  that the last command's window appears immediately.
- Claim incoming toplevels by process group, not exact pid, so wrapped
  commands (sh -c "foot -e htop") keep their own slot even though the
  sh wrapper forks the real client.
- Closed tabs are fully removed: remove_app() compacts the array, frees the
  slot, clears parent refs, and prefers the left neighbor as the successor
  (right on explicit close, mirroring the old behavior on destroy).
- Black (pending) tabs are live slots: cycle/reorder walk them, activation
  logs '[diag] active slot=N (black, awaiting map)', and Alt+Delete closes
  one outright.  A SIGCHLD-latched sweep drops black tabs whose whole
  process group exits before ever connecting (e.g. /bin/false).
- The map-reveal rule keys on the scene node still being disabled, since a
  slot's toplevel is set at claim time (before its map event).
- Update the standalone/headless and nested tests to wait for a mapped
  frame (the startup tab is active before it maps, so the old 'no tab was
  ever activated' wait no longer fires), and add two tests covering
  Alt+Delete on a black active tab and the never-connecting-command sweep.
2026-08-18 22:41:27 +10:00
5d840a09dd new-tab chord is Alt+Shift+T (prefix-based) and grabbed
Change Ctrl+Shift+T to Alt+Shift+T (Super+Shift+T under --modifier super),
matching the other tab-management chords.  It is now grabbed: process_key_event
returns true so the focused client never sees the chord, instead of passing it
through.
2026-08-18 13:47:00 +10:00
13154c78b5 Launch CLI tabs serially at the connection gate; crown the last command
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.
2026-08-18 11:20:29 +10:00
e0af748a4b add --modifier alt|super to switch tab chords from Alt to Super
Standalone/DRM and labwc-style hosts (e.g. RPi5) keep Super free, while
GNOME steals it, so Alt stays the default.  The tab-management prefix
modifier (hold=overlay, Up/Down=cycle, Shift+Up/Down=reorder,
Delete=close) is now chosen at startup from the xkb keymap (Mod1 or
Mod4) with a raw wl_keyboard bit fallback when xkb init fails;
Ctrl+Shift+T is unchanged.
2026-08-17 11:44:27 +10:00
f6c1a6b92e Grab Alt+arrows and rotate tabs when reorder wraps past an end
A focused terminal acted on Alt+Shift+Up/Down (selection/typing) because
the reorder chord was forwarded like plain Alt+Up/Down cycling was.  Both
are now grabbed: tabbed consumes them so the focused client never sees
them.

Reordering off an end previously swapped the moved tab with the far-end
tab, leaving the middle of the list in place ([A,B,C,D] moving D off the
end gave [D,B,C,A]).  reorder_active_tab now shifts every slot between the
two positions, so the whole list rotates: [D,A,B,C].  Adjacent moves are
unchanged (rotation is a swap there).

The reorder test now runs three tabs to distinguish swap from rotation
([R,G,B] moving B off the end must land [B,R,G]) and keeps the
Ctrl+Shift+T anchor-slot check.  The clipboard tests got more patience
(retry on stalled wl-paste; longer socket-start wait) since a stalled host
GNOME display was flaking them.

Full suite 5/5 green.
2026-08-17 11:12:28 +10:00
6677012a9a Alt+Shift+Up/Down reorder the active tab within the overlay list
Move the active tab's slot in the tab order without switching the active
surface: the tab stays active and only its position changes, wrapping from
either end, and holding the chord keeps moving it via the same synthesized
repeat as cycling.  Only live tabs (a toplevel, not mid-close) count as
positions, mirroring cycle_tab.

The repeat latch is generalized with a repeat_kind so one timer serves both
cycle and reorder.  Ctrl+Shift+T inside the moved tab anchors right after
its new position (add_tab uses the active slot's index), which the new
test verifies.

Tests: 5x standalone + full suite 3x green (40 passed, 1 skipped).
2026-08-17 10:45:21 +10:00
fb29f03dc0 Alt+Delete closes the active tab; kill only pgids the compositor owns
Close the active tab on Alt+Delete: the tab vanishes immediately (active
moves to the right neighbor, or the left one for a rightmost tab), the
client's whole process group gets SIGTERM with a short SIGKILL escalation
if it ignores the TERM. Grabbed and repeat-latched so a single hold closes
one tab.

Slots are claimed in connection order (not launch order), so close signals
the process group recorded at claim time. Only groups the compositor owns
are group-signaled; a client started from the compositor's own terminal
shares our group and is killed by pid instead, so teardown can't group-kill
the launcher's session (a pytest run died 143/137 for exactly this).

Tests: settle the active-tab log before cycling (wait_any_active +
cycle_ack quiesce), spawn the extra client in its own session, retry
wl-paste when the host clipboard stalls, and only skip the host-colour
assertion when the host pixel is unobservable. Full suite 5/5 green.
2026-08-17 10:26:48 +10:00
382809b126 Handle Ctrl-Alt-Fn virtual-terminal switching in standalone mode 2026-08-16 07:39:37 +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
5be0b23e68 Open a terminal tab by default and fail fast on missing commands 2026-08-15 11:29:53 +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
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
5052a9b3ba Fix Debian x64 build: drop nonexistent libwlroots-dev, build wlroots 0.19.1 without -Werror
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.
2026-08-10 09:25:52 +10:00
8acd384666 Document usage, keybindings, TABBED_LOG/dump semantics; fix F8->F9/F10 comment 2026-08-09 13:02:46 +10:00
6b049debf0 Build with meson; portable wlroots fallback (pinned wrap) + README 2026-08-09 12:46:14 +10:00