Commit graph

63 commits

Author SHA1 Message Date
6adccd6ef6 rename --bar to --panel (argument, identifiers, and log tag) 2026-09-01 18:30:08 +10:00
fd8c41b71f add wlr-layer-shell bar support and fix popup-parent crash
--bar CMD launches an always-on-top layer-shell panel (e.g. waybar) in a
dedicated launch slot.  The bar's footprint reserves the usable area,
shrinking and offsetting maximized tabs below it even when the client never
sends an exclusive zone; the bar's scene tree is held above every window.

Fix a use-after-free in xdg popup handling exposed by panel clients: resolve
a popup's parent by address comparison against kick's live surfaces rather
than dereferencing popup->parent (wlroots may hand a popup whose parent
wl_surface was already freed outside the xdg teardown cascade).  Popups whose
parent is no longer live fall back to the active window's tree so they still
get their initial configure instead of hanging the client.

Bar child stdout/stderr is /dev/null'd unless KICK_LOG is at debug level, so
waybar's chatter no longer pollutes kick's console (same convention as CLI
clients).
2026-09-01 10:38:57 +10:00
fd7ed766ef confirm-before-quit: ask Y/Enter (quit) or N/Escape (cancel) for Ctrl+Alt+Q and host close when more than one window is open 2026-08-31 10:48:04 +10:00
a8e8106761 flaky tests: fix focus-follows and stacked-member Alt+Delete, un-flake the stable suite 2026-08-31 10:02:17 +10:00
4ee153aa00 tab-groups off: give a related window on its own tab real crown, geometry, and close semantics
With --tab-groups off a related window (e.g. gnome-terminal launched from
the shell inside a terminal tab) is given its own fresh tab, but inherited
the grouped-on-parent treatment of --tab-groups on.  It showed on screen
but never took the crown, so the overlay cursor stayed on the tab it was
launched from.  Apart the two cases with the structural ownership test
w->tab == w->parent_tab (on: shares the parent tab; off: owns its own):

- both map handlers promote the own-tab window (set_active_tab) instead of
  entering it without a transition, so the crown and overlay cursor follow
  what is shown;
- own-tab X11 windows get the maximized-tab geometry, not the transient
  float;
- closing an own-tab X11 window tears its tab down instead of ignoring the
  close;
- they log a real [claim] slot rather than [sub] stack, and are re-maximized
  when the host leaves fullscreen.
2026-08-31 09:34:42 +10:00
be45053631 refactor: unify kick_app/kick_tab_group_member into a single kick_window
Windows now own the scene wiring uniformly; a tab is a pure list head of
identical kick_window structs with no privileged "main" member.  X11 and
xdg paths share one map/focus/claim/destroy tail, the tab-group and
external-to-own-tab behaviours ride on a single origin flag, and tab
membership is just window list order.

Bugs fixed in the port:
- pick_successor took -1 as a truthy prefer_right, returning NULL when the
  rightmost tab died and tearing the session down; the parameter is now a
  bool so the misuse cannot recur.
- reorder_active_tab wrote a tab back into its old slot because
  wl_list_insert() inserts AFTER its anchor; the middle-slot case now
  anchors on the predecessor.
- claim_xdg_window left the freshly created scene tree ENABLED (unlike the
  X11 path's explicit disable), so a still-black window of the active tab
  read as "already drawn" and its first map skipped the seat keyboard
  enter -- the fix that makes forwarded keys (and the client fullscreen
  mirror) reach the client in nested sessions.
- the active tab's first map no longer re-announces a fresh active
  transition: the boot crown already owns the tab, so prefer_one stable
  active_count under load.
- restored the "[x11] member associate app=%d" log for grouped windows so
  the old log contract is preserved.
2026-08-31 09:02:47 +10:00
469e31115b tab-groups: let a tab survive when its main surface dies but sub-members live
When the main surface (X11 or XDG) is destroyed, remove the stale main
wrapper from the member list instead of tearing down the whole tab.
Sub-members are the tab's new content; only when the member list is
empty do we finish the destroy.

Changes:
- app_has_window(): also return true for mapped sub-members
- handle_toplevel_destroy / handle_xs_destroy: remove the main wrapper,
  keep app->stack alive, return early if sub-members remain
- member_destroy_common: tear down the tab when the last member goes and
  the main surface is also dead
- set_active_app: hide the old tab's stack unconditionally (not just when
  old->toplevel is set) so a survivor tab is properly hidden on switch

Fixes: libreoffice crash (splash screen destroy triggered exit)
Fixes: black window after closing libreoffice (sub-member teardown missing)
2026-08-27 11:49:54 +10:00
fc89b1ad27 refactor: eliminate three code duplications in kick.c
1. Four identical set_title/set_app_id handlers (toplevel, sub, xdg)
   collapsed into handle_overlay_refresh().

2. Map activation decision extracted to map_should_activate(), shared
   by both the xdg and X11 map handlers.

3. Timer cleanup removed from finish_app_destroy() -- remove_app()
   already handles it unconditionally.
2026-08-27 09:40:12 +10:00
0e8dd8b6f7 refactor: extract sub_detach_xdg/x11_listeners from duplicated listener removal
Three places detached signal listeners from tab-group members:
handle_sub_destroy, handle_sub_x_destroy, and remove_app.  The
remove_app copy had already drifted (missing set_title/set_app_id).
Extract two helpers so the logic lives in one place.
2026-08-27 09:19:28 +10:00
ac9a079fe3 fix: remove_app() must detach set_title and set_app_id listeners for XDG sub-members
When remove_app() force-cleans a tab's group members (e.g. the main
toplevel's destruction cascades before a sub-member's own destroy fires),
it removed destroy/map/unmap/commit but not set_title/set_app_id.  The
freed member's listener stayed on the sub-toplevel's signal list; when
wlroots later destroyed that toplevel the non-empty listener assertion
fired.

Crash repro: foot+foot tab, run gnome-calculator in one terminal, cycle
to the main surface, Alt+Delete.  Both processes die from SIGTERM;
remove_app() frees the calculator member without detaching set_title,
and wlroots asserts on the calculator toplevel teardown.
2026-08-27 09:09:30 +10:00
ad46761af3 remove stray [DBG] fprintf in sub-unmap handler 2026-08-26 23:35:48 +10:00
4a3f6e80f3 scope direct scanout to standalone; nested always composites
enabling direct scanout everywhere reintroduced the exact failure the
global disable once guarded against: in nested mode a "scanned-out"
buffer on the headless output is displayed nowhere by itself -- kick
must blit every presented buffer into the host window, importing the
client's dmabuf into the host EGL context per frame.  vlc's
scanout-feedback-tuned buffers reject that import per frame
(EGL_BAD_PARAMETER "modifier attributes not equal"), leaving black video
behind playing audio.

policy is now per mode:

- standalone (DRM/VT console): scanout enabled -- true zero-copy
  presentation with no composite or blit.  an attach-render lock is held
  while the tab overlay is visible so the overlay's second pass lands on
  the composited frame instead of being painted onto the client's own
  buffer, and recreated outputs inherit the lock.
- nested: always composites.  KICK_NESTED_SCANOUT=1 re-enables scanout
  there for testing.

also scope the software-cursor lock to headless standalone outputs:
their set_cursor hook pretends a hardware plane exists while drawing
nothing, so the lock was needed there -- but on real DRM it made direct
scanout permanently ineligible (a composited cursor cannot survive
scanout), defeating zero-copy fullscreen entirely.
2026-08-26 21:37:46 +10:00
90557c214f enable opportunistic direct scanout, suppressed while the overlay shows
direct scanout was globally disabled after nested-mode black frames
(Firefox): the scene handed the client's own buffer to the output, and
the host-window blit could not keep it in sync.  wlroots 0.19.1 forwards
explicit-sync timelines in the scanned-out state and re-evaluates
eligibility every frame, so the global kill is no longer warranted:

- remove the WLR_SCENE_DISABLE_DIRECT_SCANOUT env: scanout now engages
  whenever a single opaque client covers the output -- fullscreen video,
  full-bleed tabs -- and skips the scene composite entirely
- hold an attach-render lock while the tab overlay is visible so the
  overlay's second pass always lands on kick's composite (the same
  lock/unlock pattern labwc uses for its own panels), releasing it the
  moment the prefix is released
- recreated outputs inherit the lock if the overlay is up

verified under labwc: scan-out enable/disable logged around overlay
transitions during playback, and fullscreen placement probes fill the
monitor correctly with the resizing test client.
2026-08-26 21:09:56 +10:00
c10845f2c1 route the host window through xdg-shell directly on SSD compositors
libdecor sat on the critical path of every host-window transition even
where it drew nothing: on compositors honoring server-side decorations
(labwc/sway/KDE) it negotiated SSD, then still injected permanent
set_window_geometry hints and batched acks inside its own commit call.
labwc 0.9.8 expires a 100 ms configure deadline under load and answers
"move but keep old size" -- anchoring fullscreen views to stale geometry
forever.

the host window now selects its backend at startup: compositors
advertising zxdg_decoration_manager_v1 get kick-owned xdg_shell objects
with immediate configure acks, an explicit server-side decoration request,
and no geometry hints at all (KICK_LIBDECOR=1 forces the libdecor path);
hosts without the manager -- Mutter/GNOME forces CSD -- keep libdecor
unchanged.  this also restores mirrored fullscreen semantics: a client's
fullscreen request again fullscreens the host window in one step.
2026-08-26 18:19:59 +10:00
aafd2de5b7 contain client fullscreen inside kick; decouple from the host window
a client's fullscreen request used to be mirrored onto the host window
via libdecor, but labwc 0.9.8 expires its 100 ms configure deadline under
load and then applies "move but keep old size" permanently -- the
enlarged image anchored at the old origin with black margins.  the mirror
is gone: a client's fullscreen now fills kick's output only, leaving the
host window alone.  fullscreening the host remains an explicit act
(prefix+F11 or the compositor), and the host-to-clients propagation is
unchanged.
2026-08-26 10:11:18 +10:00
79ae788bfc harden the host-fullscreen configure handshake
three changes that make our side of the fullscreen round-trip as fast
and deterministic as the protocol allows:

- queue libdecor fullscreen requests until the present pipeline drains
  (frame callback received), so a throttled eglSwapBuffers started
  before the resize can no longer complete after the ack and marshal a
  stale-size frame past it; rapid requests collapse to the latest target
- flush the ack immediately instead of waiting for the end-of-loop
  flush, which can land after labwc's 100 ms configure-response deadline
- allocate the resized swapchain during host_resize rather than lazily
  at first render, moving the expensive GBM allocation ahead of the ack

labwc 0.9.8 can still expire its deadline under load and then applies
"move but keep old size" permanently; these shrink that window without
curing it.
2026-08-26 09:07:49 +10:00
15a24e7a3a advertise exactly one wl_shm and one zwp_linux_dmabuf_v1
wlr_renderer_init_wl_display() is the legacy all-in-one helper: it creates
its own wl_shm (v2) and linux-dmabuf (v4) globals, which duplicated the
explicit wl_shm v1 and linux-dmabuf v5 created further down.  clients that
keep per-instance state -- Qt's Wayland platform re-enters its dmabuf
feedback setup for every advertised instance, and VLC's wl_dmabuf video
output likewise -- misbehave on the duplicates: vlc's Qt UI segfaulted at
startup and, once running, its video stayed black behind playing audio.
drop the legacy call and keep the explicitly versioned pair.
2026-08-26 07:21:10 +10:00
21522a1bf8 fix xwayland assert crash: drop pending associate listener unconditionally
x11_pending_teardown only removed the pending's associate listener when
the surface had been hooked (associated).  the listener is attached
unconditionally at new_surface, so an X11 window destroyed before ever
associating -- VLC's short-lived probe windows do exactly that -- left
it linked on events.associate, and wlroots' xwayland_surface_destroy
asserted on the non-empty list, killing kick.
2026-08-25 22:30:34 +10:00
fd4099076c kick: add Alt+Shift+Left/Right to move focused tab-group member within its group
- Alt+Shift+Left/Right moves the focused member left/right in its group's
  window list, analogous to Alt+Shift+Up/Down tab reordering
- Wraps at ends (moving right from newest lands at front)
- Overlay summoned like other navigation chords
- Terminals never see Shift+arrow (chord is consumed)
- Wrap-ternary type-error eliminated; unused 'wrapped' variable removed
2026-08-25 10:35:27 +10:00
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
0b0c3526c7 fix nested fullscreen placement: no bare commit after acking a sized configure
host_frame_configure followed every libdecor_frame_commit (the ack) with
a bare wl_surface_commit, which re-committed the still-attached previous
frame.  labwc applies the acked state at that first commit, so entering
fullscreen anchored the new layout to the stale window size: the enlarged
image painted from the old origin with black margins top/left and cropped
bottom/right.  pre-libdecor, the ack went out on its own listener long
before any commit, so the state-applying commit was always the next
correctly sized frame -- restore that ordering by only flushing the empty
commit for size-less initial configures, where it is still needed to get
libdecor mapped.
2026-08-25 00:45:13 +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
cf4ad12600 kickstart output damage for every new tab-group member, any backend
e1b137b added the late-attach damage nudge only to the X11 map path, so
Wayland members -- GTK4 dialogs and menus -- raced their first frame:
black until an unrelated repaint (a tab switch) forced one.  The nudge
now fires for every member arrival on the shown tab regardless of
backend.  Found via git bisect with a deterministic same-client
second-toplevel pixel probe.
2026-08-24 12:47:34 +10:00
e1b137bf30 fix X11 menus and popups: replay late association, respect transient geometry
X11 windows now pair their Wayland surface after membership decisions run
(membership starts at first map), so instantly-mapped transients -- GTK
menu dropdowns -- attached without ever wiring their scene surface: emacs
menus did nothing, silently.  The associate handlers now replay the map
when the surface is already mapped, members join the owner's list before
that replay (show_current walks the list to pick visibility), X11 members
keep their own geometry instead of being stretched maximized, and late
attaches force whole-scene damage so the first frame is never black.

Members render through wlr_scene_subsurface_tree_create; the ctl test feed
gains CTL_CMD_PTR_BUTTON so tests can click for real; tests/xpopup.c
probes the exact menu shape (instantly-mapped managed transient), and a
regression test locks in the attach/replay/focus lifecycle.
2026-08-24 12:09:40 +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
6b955a2322 render the overlay through one wlr pass in both modes; drop the host-GL overlay path 2026-08-24 09:58:22 +10:00
7bd5cd7dbd unify overlay rendering: one shared layout behind host-GL and wlr-pass backends 2026-08-24 09:11:15 +10:00
ff8a5bc23b auto-detect standalone mode when launched from a Linux virtual console 2026-08-24 08:51:51 +10:00
d13af828b8 draw the overlay backdrop edge-to-edge with rim strokes; repaint fully while shown 2026-08-24 08:30:23 +10:00
8373192d48 tighten overlay spacing: 8px row gap and 8px visible gap between title chips 2026-08-24 07:51:36 +10:00
9cd117e12b restore the grey background behind each overlay title 2026-08-24 07:43:10 +10:00
62ac013df2 refresh the overlay when a window's title or app_id changes
title and app_id arrive as protocol events with no pixel damage, so the
overlay (which reads them live) never repainted while held -- a tab going
from black to content kept showing the command until the menu was closed
and reopened. subscribe to the xdg/x11 set_title and set_app_id signals
(and refresh on map) and repaint in place when the overlay is visible.
2026-08-23 23:06:45 +10:00
c7b5cf0dad show the launch command for still-black overlay tabs
a tab whose window has not opened yet has no mapped members, so its
overlay row drew nothing and showed a blank title. draw the launch
command instead, sized into the panel (which now measures and counts
pending rows) and truncated to the row width if it would overflow.
2026-08-23 22:38:11 +10:00
8edf196981 add a dark-grey background behind each overlay title with row spacing
each window title now sits in its own grey box matching the blue
highlight size, lightened so it separates from the panel, and rows are
spaced so the boxes no longer touch.
2026-08-23 22:16:10 +10:00
bf8252d7f4 widen the gap between member titles in the overlay
double the per-member spacing so stacked windows read as separate items.
2026-08-22 23:39:11 +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
bff05c197f restore the last-focused window when returning to a tab
set_active_app used to discard the tab's focus_member when leaving it,
so coming back always re-focused the newest mapped member instead of the
window the user was last on. keep the pointer on leave and restore it on
enter (falling back to the newest member only if it is no longer mapped).
2026-08-22 23:29:17 +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
884167bbb6 run Xwayland permissively instead of sharing the host Xauthority 2026-08-22 12:13:33 +10:00
aab41f286e make the tab's main surface an ordinary member of its window stack 2026-08-22 10:47:47 +10:00
753b37ee7e add Alt+ backtick to cycle a tab's stacked windows backward 2026-08-22 08:19:08 +10:00
1ef4e4423c draw only the current window of a stacked tab 2026-08-22 07:57:59 +10:00
9d3afadcf1 list key bindings in a Keys section of the -h output 2026-08-21 19:14:19 +10:00
d0851b1ad7 reformat -h output into sections with aligned options table 2026-08-21 19:05:49 +10:00
9e8e2208c3 explain dead startup commands on stderr instead of exiting silently 2026-08-21 13:10:56 +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