Commit Graph

480 Commits

Author SHA1 Message Date
lkn 143b8906bb wayland: fix view_table probe chain corruption on widget reopen
The WINGs view_table (open-addressing hash, size 256) used id=0 as both
"empty" and "deleted". Zeroing a slot on removal broke probe chains for
entries that hashed past that slot. After two dialog open/close cycles
(~40 views created and destroyed), enough chains were broken that
W_GetViewForWindow/W_GetViewBacking returned NULL for valid IDs.

Fix: use a tombstone marker (id=1) for deleted slots. Lookups skip
tombstones (continue probing), inserts reuse them. This is the standard
approach for deletion in open-addressing hash tables (Knuth Vol.3 §6.4).

Also:
- wl_destroy now calls view_table_remove + frees backing pixman image
- wl_frame_destroy cleans up child frame_bufs
- client_reparent existing path re-links backing and recreates scene_buf
- client_reparent to root destroys the child frame_buf
2026-06-22 00:21:42 +02:00
lkn 9f32dc4eef icon: look up freedesktop icon theme for native Wayland clients
Native Wayland clients have no _NET_WM_ICON or WM hints icon.  Before
falling back to the default icon, search the hicolor icon theme at
/usr/share/icons/hicolor/<size>/apps/<app_id>.png and /usr/share/pixmaps/
for a matching icon based on the xdg_toplevel app_id.
2026-06-21 19:24:59 +02:00
lkn bc0446aa7a wayland: account for CSD geometry offset in pointer hit testing
wlr_xdg_surface_surface_at expects surface-local coordinates (including
the shadow region). Add geometry.x/y back when converting from screen
coords to surface-local so the hit test aligns with the actual content.
2026-06-21 19:21:04 +02:00
lkn d7b2c70c7c wayland: sync scene node geometry offset on every commit
CSD clients like Firefox may not set window_geometry until after the
initial map.  Update the scene node offset on every surface commit so
the content stays aligned with the frame as geometry changes.
2026-06-21 19:19:54 +02:00
lkn bb95bd52cd wayland: apply CSD geometry offset in wl_window_move
Without this, moving a CSD client (Firefox) causes the surface to
"pop" out of the WM frame — the scene node was positioned at the
raw frame-relative coords without subtracting geometry.x/y.
2026-06-21 19:18:20 +02:00
lkn 1ee10d37f5 wayland: use xdg geometry instead of surface size for CSD clients
Firefox and other CSD clients include invisible shadow borders in their
surface buffer.  Use xdg_surface->geometry (the client-declared content
rect) for:
- reporting client width/height to the WM frame
- sizing the frame on commit
- offsetting the scene node so shadows are outside the frame bounds

Falls back to surface->current.width/height when geometry is unset.
2026-06-21 19:17:06 +02:00
lkn e0edd782d8 wayland: group all windows with same app_id into one appicon 2026-06-21 19:10:42 +02:00
lkn bebd22cbd7 wayland: group windows by app_id only when appicon is docked 2026-06-21 19:10:07 +02:00
lkn 58bb6f08b4 wayland: use app_id for both wm_class and wm_instance in PropGetWMClass
Previously wm_instance was set to the window title, but docked icons
store app_id as their wm_instance. This mismatch caused
wDockTrackWindowLaunch to fail matching the new window to the docked
icon, resulting in a duplicate free-floating appicon being created.
2026-06-21 19:08:07 +02:00
lkn 134594e0bc wayland: draw title background bar for miniwindow icons
Add the solid colored title background rectangle with light/dim bevel
edges that X11 draws via drawIconTitleBackground. This gives miniwindow
icons their characteristic "flat" title bar appearance, distinguishing
them from appicons which lack this bar.
2026-06-21 19:03:08 +02:00
lkn 69a57062b7 appicon: collapse wAppIconPaint into a thin wrapper over wIconPaint
wIconPaint already handles dock dots/stipple for docked appicons.
wAppIconPaint was redundantly calling appicon_paint_dock_dots again.
Reduce it to just wIconPaint(aicon->icon). Remove dead WS_INDICATOR
code (was #undef in wconfig.h).
2026-06-21 19:00:46 +02:00
lkn 162c0cc4ca client: use wIconPaint instead of raw backend icon_paint
For consistency, route through wIconPaint which handles dock dots
for docked appicons and ensures complete paint path.
2026-06-21 18:59:36 +02:00
lkn aee20f0425 route remaining wm_backend->appicon_paint calls through wAppIconPaint
Same pattern as 7530f1a: application.c, actions.c, client.c, and
defaults.c all called the raw backend paint, missing dock dots.
Now all go through wAppIconPaint for consistent indicator rendering.
2026-06-21 18:58:08 +02:00
lkn 7530f1a50b dock: route all appicon paints through wAppIconPaint
Multiple places in dock.c and dockedapp.c called
wm_backend->appicon_paint directly, bypassing wAppIconPaint which
adds dock dots and launching stipple. Replace all with wAppIconPaint
so indicators are always drawn.
2026-06-21 18:53:30 +02:00
lkn ec0a94e077 wayland: fix dock ghost shadow z-order (render below icon)
The dock_shadow was rendering above the dragged icon because:
1. It was in WL_LAYER_OVERLAY/DOCK, above the dock icons in WL_LAYER_FLOATING
2. wl_stacking_restack could not order nodes across different parent trees
3. wl_window_map auto-raised on map, undoing any prior restack

Fixes:
- Place dock_shadow in WL_LAYER_FLOATING (where WMDockLevel=5 maps)
- Rewrite wl_stacking_restack to reparent nodes into the same tree
  and use wlr_scene_node_place_below for guaranteed ordering
- Remove auto-raise from wl_window_map (explicit wRaiseFrame handles it)
2026-06-21 18:49:19 +02:00
lkn c90355b663 wayland: place dock_shadow below dock icons
The dock ghost shadow was in WL_LAYER_OVERLAY rendering above dock icons.
Move to WL_LAYER_NORMAL and lower to bottom so it renders beneath the
dock icons (WL_LAYER_DOCK) during icon drag, matching X11 behavior
where the ghost shows as a position marker below the real icon.
2026-06-21 18:43:43 +02:00
lkn 1d54ddf255 icon: draw dock dots/stipple at end of wIconPaint for docked appicons
Move the appicon_paint_dock_dots call into wIconPaint itself (after
icon_paint), so that ANY repaint of a docked appicon includes dots
and launching stipple. Previously, the dots were only drawn from
wAppIconPaint and the observers, but other repaint paths (pointer
events, window_clear) would erase them.

Remove the now-redundant calls from appearanceObserver and
tileObserver since wIconPaint handles it.
2026-06-21 18:36:24 +02:00
lkn 4c5855958f dock: call wAppIconPaint from dockIconPaint instead of raw backend
dockIconPaint was calling wm_backend->appicon_paint directly, which
bypasses wAppIconPaint and its appicon_paint_dock_dots call. This meant
docked icons never got their dots drawn at startup.

Call wAppIconPaint instead so the full paint path (tile + dots + stipple)
executes for all docked appicons.
2026-06-21 18:33:19 +02:00
lkn c982a29d2f icon: repaint dock dots after tile/appearance observer notifications
On Wayland there are no Expose events, so when tileObserver or
appearanceObserver repaints an icon (via update_icon_pixmap or
window_clear), the dock dots drawn by appicon_paint_dock_dots
were lost. Explicitly re-trigger dock dots painting for docked
appicons after the observer repaint.
2026-06-21 18:23:36 +02:00
lkn 6611dd4853 wayland: implement appicon dock dots and launching stipple
Replace the no-op wl_noop_appicon_paint_dock_dots with a real
implementation that draws:
- 3 white dots with black shadow for docked+not-running icons
- Checkerboard dimming (stipple) for launching icons

Dot geometry matches the X11 backend: positioned at 4m/9m/14m
horizontally, icon_size-6m vertically (m = icon_size/64, min 1).
2026-06-21 18:19:21 +02:00
lkn 8f45001ffc wayland: fix MakeGhostIcon by handling raw pixman_image_t* drawables
wl_create_rimage_from_drawable only looked up frame_buf IDs, but
icon->pixmap on Wayland is a raw pixman_image_t* from RConvertImage,
not a frame_buf ID. When frame_buf_find fails, fall back to treating
the drawable pointer as a pixman_image_t* directly.

This fixes the ghost dock shadow (semi-transparent overlay shown
during icon drag) which was silently failing because MakeGhostIcon
returned WM_NATIVE_NONE.
2026-06-21 18:15:36 +02:00
lkn 0ace31b8a4 wayland: fix clip icon rendering with filled triangle arrows
The clip icon was not rendering properly because:
1. An early return prevented painting when workspaces were not yet
   initialized (moved the check so base tile always paints)
2. The filled triangle arrows (top-right and bottom-left) drawn by
   X11 paintClipButtons were missing entirely

Add pixel-level triangle drawing matching the X11 implementation
geometry: CLIP_BUTTON_SIZE-based sizing with 5px margins.
2026-06-21 18:12:05 +02:00
lkn c9f0fc8370 wayland: recursive frame_buf_hit resolves WINGs views in dialogs
frame_buf_hit now walks children recursively (up to 4 levels) instead
of only checking immediate children of top-level frames. This resolves
WINGs views nested inside fake_leader frames (icon chooser, message
dialogs), enabling event delivery to the correct WINGs widget.
2026-06-21 17:51:41 +02:00
lkn 5325a51b64 wayland: fix double-free crash when destroying shared WApplication
When main_window_desc is the real WWindow (found via wWindowFor rather
than created by makeMainWindow), wApplicationDestroy must not call
wWindowDestroy on it — the window is still managed and will be destroyed
by wUnmanageWindow. Add shared_main_desc flag to track ownership.
2026-06-21 17:43:42 +02:00
lkn 29550b58cb wayland: fix WApplication to reuse existing WWindow with frame
- application.c: wApplicationCreate now tries wWindowFor(main_window)
  before creating a new frameless WWindow via makeMainWindow. For
  Wayland native clients where main_window == client_win, this returns
  the real WWindow with its frame, title, and updated wm_class/instance.

- window.c: Disable shared_appicon for Wayland native clients (no
  client_leader or group_id). The shared_appicon logic replaces
  main_window with a fake group leader ID, which breaks the
  wWindowFor lookup and produces a frameless duplicate WWindow.

Fixes the kill dialog showing "application" instead of the window title
for Wayland clients.
2026-06-21 17:37:31 +02:00
lkn c027f092c0 cleanup: remove debug wwarning/fprintf output
Remove diagnostic output from wl_framebuf.c (hit debug), wl_handlers.c
(button routing), wl_grabstate.c (grab/ungrab), wl_client.c (reparent),
wl_xdg.c (toplevel unmap), window.c (wWindowMap), wview_wl.c
(map/unmap), superfluous.c (MakeGhostIcon), and appicon.c (move loop).

Also simplify the wl_unmap logic in wview_wl.c.
2026-06-21 17:25:06 +02:00
lkn bf552c9be2 appicon: use wm_class (app_id) in kill dialog instead of wm_instance
For Wayland clients, wm_instance is the window title (often long and
non-descriptive for identification) while wm_class is the app_id which
is the application identifier. Prefer wm_class for the kill confirmation
dialog, falling back to basename(wm_instance) then "application".
2026-06-21 17:21:22 +02:00
lkn 611a886582 menu: fix appicon menu hit detection and edge scrolling during drag
- findMenu(): add icon_menu to the walk list so the appicon right-click
  menu responds to pointer motion during its modal drag loop.

- dragScrollMenuCallback: remove the scrollable_menus gate so menus
  scroll at screen edges during drag-select regardless of the
  scrollable_menus preference (that preference controls the non-modal
  scroll path in handleMotionNotify which does not run during the
  menu modal loop).
2026-06-21 17:06:09 +02:00
lkn 7114da35ba wayland: fix XWayland cursor position offset by titlebar height
view_surface_at() was double-subtracting the frame_rel offset. Since
v->x/v->y already represents the client content position (frame origin
+ frame_rel), the surface-local coordinates are simply px - v->x. The
bounds check now correctly tests against 0..width/height.
2026-06-21 16:59:58 +02:00
lkn 949fc9f94a wayland: destroy XWayland appicons on surface unmap
Call wApplicationDestroy after wUnmanageWindow in the XWayland unmap
handler, matching the XDG and X11 handleDestroyNotify paths. Fixes
XWayland appicons persisting after the application closes.
2026-06-21 16:57:16 +02:00
lkn 3d953339f8 wayland: fix XWayland titlebar actions and cursor
- wl_pointer_focus.c: In view_surface_at(), check frame bounds for
  XWayland views before returning the surface. When pointer is over
  frame decorations (titlebar, buttons, resizebar), continue the loop
  so the frame_buf handles the event. Fixes titlebar buttons (close,
  iconify, shade) not responding for XWayland applications.

- wl_xwayland.c: Set left_ptr cursor on the XWayland root window at
  startup via XDefineCursor. Fixes the default X cursor (big X) showing
  for X11 applications that do not set their own cursor.
2026-06-21 16:55:02 +02:00
lkn 29d183fe8f wayland: implement appicon dock/clip clamping
Enable the full appicon drag-to-dock/clip workflow for native Wayland
clients:

- window.c: Set main_window = client_win for Wayland clients that lack
  client_leader and window_group, so wApplicationCreate runs and
  produces a WAppIcon.

- wl_xdg.c: Call wApplicationDestroy after wUnmanageWindow on toplevel
  unmap, mirroring the X11 handleDestroyNotify path. Fixes appicons
  persisting after app close.

- wl_event.c: Commit the wlroots scene in wl_event_flush so
  slide_windows animation renders intermediate frames.

- backend.h, x11_backend.c, wl_icon.c, superfluous.c: Add
  create_rimage_from_drawable vtable method so MakeGhostIcon can read
  frame_buf pixels on Wayland for the dock shadow overlay.

- wl_icon.c, wl_stubs.c: Implement wl_icon_update_pixmap with
  shadowed (dimmed) overlay for attracted icons.

- wl_dock.c: Disable the placeholder shm_surface dock panel now that
  the real wmaker dock framework operates via frame_bufs.
2026-06-21 16:44:49 +02:00
Levi Neely 2afbf97b4c fix build 2026-06-08 16:10:59 +02:00
lkn d923d9fb83 wl_unmap: send NULL to wl_surface when W_ViewSceneNodeSetEnabledFn unavailable 2026-06-07 15:48:38 +02:00
lkn 405c481e38 clean up diagnostics in wl_unmap and wl_view_scene_node_set_enabled
Remove duplicate W_ViewSceneNodeSetEnabledFn call, fix malformed debug code.
wl_unmap now logs cleanup diagnostics once. wl_view_scene_node_set_enabled
logs proxy ID lookup, match result, and no-match case properly.
2026-06-07 15:38:19 +02:00
lkn 7b15ded31a add diagnostics to wl_view_scene_node_set_enabled for popup stale-pixel tracing 2026-06-07 15:35:25 +02:00
lkn 118a7c30a9 wl_unmap: recycle WINGs client wl_surface on popup close to eliminate stale pixels
The stale popup content visible under the main window after close comes from
the wl_redisplay composite of the popup into the parent backing (rootView).
Even though rootView is not committed to any wl_surface, the popup surface
itself retains its last committed buffer. Disabling the scene node is
insufficient for completely eliminating visual artifacts.

Fix: add wl_client_toplevel_recycle which destroys the xdg_toplevel,
xdg_surface, wl_surface, and SHM buffer but preserves the WLClientToplevel
struct and its WINGs window ID mapping. On unmap (wl_unmap), call recycle
for topLevel popups. On remap (wl_map), call wl_client_toplevel_remap which
recreates all Wayland protocol objects from scratch, triggering a fresh
configure cycle. This ensures no stale buffer or state persists between
open/close cycles.
2026-06-07 15:29:33 +02:00
lkn 60efe4db78 wl_view_scene_node_set_enabled: find WINGs client toplevels via wl_proxy_get_id
The function only handled wl_frame_buf entries. WINGs client popup menus have
their own xdg_toplevel surface with a scene_node but no wl_frame_buf. Their
WINGs view window IDs (0x20000000+) differ from compositor view IDs
(0x10000000+), so wl_find_view_by_id could not find them.

Fix: add wlc_top_get_surface_id() in wclient_wl.c which returns the
libwayland-client proxy ID for the wl_surface. In
wl_view_scene_node_set_enabled, use this ID to iterate the compositor
toplevel_list and find the matching wlr_surface resource by object ID.
Then enable/disable the view scene_node directly.
2026-06-07 15:00:08 +02:00
lkn b30cfa31d9 Revert "wl_unmap: send NULL buffer to WINGs client toplevel surfaces on unmap"
This reverts commit 38a0009e03.
2026-06-07 14:56:02 +02:00
lkn 38a0009e03 wl_unmap: send NULL buffer to WINGs client toplevel surfaces on unmap
When a WINGs client popup is unmapped (popDownMenu), the scene node is
disabled via W_ViewSceneNodeSetEnabledFn. But for WINGs client toplevels
(which have their own wl_surface + xdg_toplevel via libwayland-client),
the surface still holds its last committed buffer. Disabling the scene
node is not enough - the compositor still sees the surface as mapped.

Fix: add wl_client_toplevel_unmap which attaches NULL to the wl_surface
and commits, properly unmapping it from the compositor. Wire through
W_WLClientUnmapFn function pointer in wview_wl.c, called from wl_unmap
for topLevel views.
2026-06-07 14:51:30 +02:00
lkn 3e5c594d6f Revert "wl_view_scene_node_set_enabled: also handle WINGs client toplevels (popups)"
This reverts commit 971089b3d9.
2026-06-07 14:50:58 +02:00
lkn 971089b3d9 wl_view_scene_node_set_enabled: also handle WINGs client toplevels (popups)
The function only searched for wl_frame_buf entries to enable/disable
their scene nodes. WINGs client popup menus have their own xdg_toplevel
surface with a scene_node but no wl_frame_buf. On unmap, the scene node
was never disabled, so the popup content remained rendered underneath
other windows.

Fix: fall through to wl_find_view_by_id when no frame_buf is found,
and operate on the wl_toplevel_view->scene_node instead.
2026-06-07 13:45:50 +02:00
lkn 3a2f5e96db wl_pointer_grab: replace no-op stub with compositor-grab delegation
The WINGs view backend wl_pointer_grab was a no-op, meaning the
press-drag-release sequence for popup menus had no effect on compositor
state. On X11 this would call XGrabPointer to redirect events to the
menu window; the equivalent on Wayland is to activate the compositor
wl_pointer_grab which sets wl_state.grab_type and suppresses unwanted
cursor resets and axis leaks during the interactive drag.

Implementation: add function pointers W_WLPointerGrabFn and
W_WLPointerUngrabFn in wview_wl.c, initialized to NULL. The compositor
wires them to wl_pointer_grab / wl_pointer_ungrab at startup.
The WINGs view backend now delegates to the compositor instead of
silently returning.
2026-06-07 13:05:57 +02:00
lkn 69903b23a7 view_surface_at: skip shaded windows in pointer hit-testing
When a window is shaded (rolled up to titlebar only), the client surface
is hidden but view_surface_at would still return it for points over the
frame decorations. This caused two symptoms:

1. Pointer focus stayed on the client surface, showing I-beam cursor
   instead of default arrow over the titlebar.
2. Button events routed to the client surface instead of titlebar,
   preventing double-click-to-unshade from working.

Fix: after matching a toplevel view via scene graph, check whether the
associated WWindow has flags.shaded set and skip it if so.
2026-06-07 10:36:36 +02:00
lkn 6a37107716 wl_window_resize: update backing store after resizing pixman image
When wl_window_resize frees the old pixman image and creates a new one,
it must call W_RegisterBacking to update the backing store. Otherwise
W_GetViewBacking returns the freed old image pointer, causing a crash
when WMDrawString (via wl_font_resolve_drawable) tries to draw into
the stale backing. This manifests on workspace name badge display
in direct render mode.
2026-06-07 09:49:09 +02:00
lkn 34be4b1287 Alt+drag: validate WINGs fb hit via context_find
When handle_pointer_button finds a client surface at the pointer (_surf && _tv),
it searches frame_list for overlapping WINGs frame_bufs. Previously it used
any WINGs fb found, including stale entries with bogus IDs (e.g. 0x1).
Now it validates the WINGs fb via context_find() before using it as the
event window. If validation fails, the event routes to _tv->id.
2026-06-06 21:17:53 +02:00
lkn 0b12c7ad71 wayland: track real keyboard modifiers in wl_state.current_mods
The seat keyboard may be the XWayland virtual keyboard when a client
has focus, which carries no modifier state.  Reading
seat->keyboard_state.keyboard->modifiers.depressed for button events
then yields 0 even when the user holds Alt, so frameMouseDown's
MOD_MASK check always fails and Alt+drag never starts.

Add current_mods to wl_compositor_state.  Update it from the real
keyboard in handle_keyboard_modifiers, which fires on every modifier
change from the physical device regardless of seat focus.  Use
wl_state.current_mods in handle_pointer_button to populate
u.button.state and to gate the client-forwarding suppression.
2026-06-06 20:47:16 +02:00
lkn 16e07f751c wayland: fix modifier+drag grab — pass owner_events=1 in frameMouseDown
The pointer_grab call for Alt+drag (move/resize via MOD_MASK on client
surface) was passing owner_events=0.  wl_pointer_grab only sets
grab_type=3 and clears seat pointer focus when owner_events is True;
with 0 the grab returned success but left no grab state, so motion
events continued flowing to the client instead of the WM drag loop.

Pass 1 (True) to match the calls in resizebarMouseDown and
titlebarMouseDown, which work correctly.
2026-06-06 20:41:42 +02:00
lkn 10281cf2d1 wayland: suppress client forwarding on modifier+click for move/resize
When the WM modifier key (Alt by default, configurable via ModifierKey)
is held during a button press over a client surface, do not forward the
event to the client via wlr_seat_pointer_notify_button.  The event has
already been enqueued for the WM policy layer via W_WL_EnqueueEvent;
frameMouseDown will call wMouseMoveWindow (LMB) or wMouseResizeWindow
(RMB) as appropriate.  Without this suppression the client application
receives the Alt+click and may act on it instead of the WM.
2026-06-06 20:38:03 +02:00
lkn d17dc0d6ee wayland: fix window resize and populate button modifier state
Three related fixes for the Wayland backend:

wl_handlers.c: populate u.button.state with keyboard modifiers
  Button events were built with state=0 after memset, so all modifier-
  dependent logic in window.c (resizebarMouseDown, frameMouseDown,
  titlebarMouseDown) received no modifier information.  Read the current
  depressed modifier mask from the seat keyboard and copy it directly --
  wlroots WLR_MODIFIER_* bits match the WM_MOD_* layout exactly.

wl_framebuf.c: fix frame_buf_hit to resolve child sub-windows
  frame_buf_at() uses wlr_scene_node_at() which skips frame_buf children
  whose scene_buf has no committed buffer (resizebar, titlebar pixels are
  composited into the parent frame buffer).  The hit always resolved to
  the top-level frame, so events were dispatched to frameMouseDown instead
  of resizebarMouseDown, and resize never started.  After frame_buf_at
  returns a top-level frame, do a coordinate-based child search and return
  the smallest (most specific) child that contains the pointer -- the same
  strategy frame_buf_button_at uses for buttons.

wl_events.c: fix wl_coords_translate to return frame-relative coords
  The stub was a pass-through returning root-relative coordinates unchanged.
  wMouseResizeWindow calls coords_translate(root, frame->core->window, ...)
  to get frame-relative click coordinates for getResizeDirection().  Look up
  the dst window in the frame_buf list and subtract its absolute position.
2026-06-06 20:36:36 +02:00