Commit Graph

761 Commits

Author SHA1 Message Date
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 2fca1e1df3 add diagnostics to wl_unmap and wl_view_scene_node_set_enabled for popup stale-pixel debugging 2026-06-07 15:34:51 +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 1b04023591 Revert "wl_unmap: force commit sibling toplevels to clear stale popup pixels"
This reverts commit a9397b8291.
2026-06-07 14:50:58 +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 a9397b8291 wl_unmap: force commit sibling toplevels to clear stale popup pixels
When a topLevel popup is unmapped, wl_redisplay(view-parent) clears stale popup pixels from the parent backing. But for rootView (common parent), the commit at the end of wl_redisplay is a no-op because rootView has no WLClientToplevel. The cleared backing was never pushed to any surface. Fix: iterate parent child list and force-commit every topLevel sibling, pushing the cleared backing to their wl_buffers.
2026-06-07 14:46:02 +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 ff86d8055f wl_map: raise popup to top on remap via wmaker-popup-v1
When a WINGs popup is unmapped (popDownMenu) and remapped (popUpMenu),
the scene node is re-enabled via W_ViewSceneNodeSetEnabledFn, but its
Z-order position stays at its original insertion point. Other surfaces
mapped while the popup was disabled may now sit above it, making the
popup invisible when it reappears.

handle_toplevel_map (which calls wmaker_popup_apply to raise the
popup) only fires on the initial xdg_surface map event, not on
scene-node re-enable. Fix by calling W_WLClientPopupSetPosition on
every topLevel popup map, which triggers wmaker_popup_v1_set_position
→ wmaker_popup_apply → wlr_scene_node_raise_to_top.
2026-06-07 13:42:25 +02:00
lkn 10a22a8484 wpopupbutton: only clear menu state on leave from menuView, not on bPtr->view
When the pointer leaves the main button view to enter the popup surface,
the WME_LEAVE event fires for bPtr->view (which shares the same
handleActionEvents handler). This unconditionally cleared insideMenu=0
and highlightedItem=-1, causing the subsequent button release to fail
the selection check and not fire the action.

Fix: guard the leave handler with event->window == bPtr->menuView->window
so that clearing only happens when the pointer actually leaves the menu
view, not when it just leaves the main button on its way to the popup.
2026-06-07 13:38:46 +02:00
lkn d43ceae081 wclient_wl: hit-test child views on pointer enter/leave during drag
pointer_handle_enter always sent WME_ENTER to the toplevel root view
with _view_target=NULL, so child views (e.g. menuView) never received
proper WME_ENTER with correct local coordinates when the pointer entered
the popup surface during a held drag.

Fix: hit-test against the newly-focused toplevel on enter, find the
deepest child at the pointer position, and deliver WME_ENTER to that
child view with proper local coordinates and _view_target set.

Also fix pointer_handle_leave to deliver WME_LEAVE to the deepest
child (via wlc_ptr_child_focus) instead of the toplevel root view,
matching the enter side.
2026-06-07 13:33:19 +02:00
lkn a63c97638d wclient_wl: fix cross-surface motion/release during implicit grab
When a popup menu is opened during a button-held drag, the compositor
changes pointer focus to the popup surface as the pointer enters it.
But pointer_handle_motion always delivered events to the originally
grabbed view (wlc_ptr_grab), never hit-testing inside the popup.
This meant wpopupbutton.c received motion events targeted at the
main button view with coordinates relative to that view, not the menu.

Fix: in both pointer_handle_motion and pointer_handle_button (release),
check whether the grab target is within the currently focused toplevel.
If not (the pointer entered a different surface), use wlc_hit_test
against wlc_ptr_focused to find the correct child view with proper
local coordinates.

Also enable enter/leave synthesis during grabs when target changes,
so child views receive proper WME_ENTER/WME_LEAVE as the pointer
moves between them across the cross-surface boundary.
2026-06-07 13:31:16 +02:00
lkn d2c9a59344 popUpMenu: init highlight from pointer position on open, not selectedItemIndex
When the popup opens via ButtonPress on the main button, compute the
highlighted item from the pointer position relative to the menu view,
matching upstream WINGs semantics. Previously highlightedItem was set
to selectedItemIndex, which might be -1 (no prior selection), causing
the popup to open with no visible highlight until the first motion event.

The fix uses event->u.button.y_root - menuView->pos.y divided by item
height to determine which item the pointer is over when the menu appears.
This item is painted as highlighted immediately on open.
2026-06-07 13:08:21 +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 7285ca6286 paintMenuEntry: redraw full row on non-highlight instead of just gray fill
The non-highlight path in paintMenuEntry only filled the row with gray
and returned early, erasing the item text, relief border, and indicator
pixmap. On Wayland this was immediately visible because the cascade+commit
on fill_rect commits every draw operation to the wl_surface.

Fix: after filling the gray background, redraw the relief border, the
item text (in black or darkGray depending on enabled state), and the
indicator pixmap for the selected item — matching what the highlight path
and makeMenuPixmap do.
2026-06-07 12:53:46 +02:00
lkn 3ccf87ff4f popup: fix click selection and blank second-open
Bug 1: Clicks on menu items did not select the correct item.
The WME_BUTTON_PRESS handler always called popUpMenu() which re-mapped
the already-open menu and reset highlightedItem to selectedItemIndex,
ignoring the click position. Since no motion event follows a
click-without-drag, the highlight was never corrected.
Fix: distinguish menuView vs main button events via event->window.
For menuView clicks, compute highlightedItem from click.y directly
without re-opening the menu.

Bug 2: Second popup open showed a blank gray box.
wl_unmap disabled the compositor scene node via W_ViewSceneNodeSetEnabledFn
but wl_map never re-enabled it. On re-map, the buffer was committed
to the wl_surface but the scene node stayed disabled.
Fix: re-enable scene node in wl_map before redisplay.
2026-06-07 12:47:24 +02:00
lkn 2c3a1764a4 popUpMenu: map before render, use makeMenuPixmap for robust initial content
Reorder popUpMenu to:
1. Resize menuView before map (correct backing dimensions)
2. W_MapView first (wl_map calls wl_redisplay which fills background
   and dispatches WME_EXPOSE — the old redisplay() before map was a
   no-op on Wayland because wl_redisplay returns early when !mapped)
3. Copy makeMenuPixmap() output into menuView->window backing as a
   safety net, ensuring the buffer has complete menu content
   independent of expose-timing correctness
4. paintMenuEntry for initial highlighted item

This ensures the first visible popup buffer has all menu items
rendered regardless of the order in which the Wayland configure
and commit events arrive.
2026-06-07 11:32:23 +02:00
lkn e063d8fa87 WINGs Wayland: fix popup dropdown blank gray box and motion highlight rendering
Three related fixes for WINGs popup menus on Wayland:

1. wl_client_toplevel_resize: update pending_w/h after resize
   (wclient_wl.c:545-546). Without this, the configure handler sees
   stale pending dimensions and creates a wrong-sized buffer,
   discarding the one containing the menu text.

2. Export wl_redisplay_depth (wview_wl.c:288) so render and font
   backends can detect when they are called from within a full
   redisplay cycle vs. from an event handler.

3. Add cascade+commit to render_wl_fill_rect (wrender_wl.c:162-183)
   and wl_draw_string (wfont_wl.c:183-208) when not inside a
   redisplay cycle. This makes motion-driven highlight changes
   (text color, background fill) immediately visible by compositing
   up the view tree and committing the toplevel wl_surface.
2026-06-07 11:24:00 +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
lkn 6a12e3ea5a wayland: fix direct keyboard input device selection 2026-06-06 20:12:20 +02:00
lkn 2c1dca387f multi monitor 2026-06-06 20:05:57 +02:00
lkn ad0871054c wayland: fix multi-output pointer coordinate bugs
Three interrelated bugs caused clicks to land in the wrong place when
the compositor spanned multiple outputs (e.g. two 1920x1080 monitors
side by side in nested Wayland mode):

1. real_pointer_update_global() added lo->x to coordinates that were
   already in layout space from wlr_cursor_absolute_to_layout_coords,
   double-counting the output offset. It also omitted lo->y entirely,
   breaking vertical monitor layouts. Fix: read position from
   wlr_cursor->x/y which is always authoritative layout-space.

2. real_pointer_handle_motion() accumulated ev->delta into local_x/y
   then set pointer_x/y, after which handle_pointer_motion() added
   the same delta again -- doubling every relative motion. Fix: let
   handle_pointer_motion() be the sole authority; use wlr_cursor_move()
   which respects output layout geometry.

3. Pointer devices from nested Wayland outputs (WL-1, WL-2) were never
   registered with wlr_cursor via wlr_cursor_attach_input_device() or
   mapped to their output via wlr_cursor_map_input_to_output(). Without
   this mapping, wlr_cursor_absolute_to_layout_coords treated the 0..1
   range as spanning the entire layout (0..3840) instead of the
   device's specific output (e.g. 1920..3840 for WL-2). A previous
   workaround disabled absolute motion entirely for nested multi-output,
   which left only relative deltas that had no concept of which host
   window the mouse was in. Fix: register devices with wlr_cursor and
   map them to their outputs; remove the early-return.

All pointer paths now follow the same pattern: mutate position via
wlr_cursor_move()/wlr_cursor_warp(), then read back cursor->x/y into
wl_state.pointer_x/y. Manual bounding-box clamping is retained only
for the no-cursor fallback (headless/test mode).
2026-06-06 20:05:34 +02:00
lkn b33774db14 phase 3: multi-output support — handle all wlr_outputs (not just first), per-output scene_output/bg_rect/frame_listener, refresh heads on hotplug 2026-06-06 18:02:18 +02:00
lkn 3fc416a9b6 fixup: wl_monitors_query returns scr->heads directly instead of making a copy (avoids leak) 2026-06-06 17:50:01 +02:00
lkn c5ba4baea0 phase 1-2: wl_screen_refresh_heads() enumerates all outputs from wlr_output_layout, populates heads/head_count/primary_head and sets correct scr_width/scr_height from bounding box 2026-06-06 17:49:10 +02:00
lkn 57a8973bc2 fix: wlclient_dispatch stops spinning when compositor closes the socket (NULLs ctx->display, making subsequent calls a no-op) 2026-06-06 16:53:13 +02:00
lkn 6fa2c78c2e wayland: avoid misclassifying WINGs toplevels as dialogs 2026-06-06 16:19:08 +02:00
lkn 74a4314621 fix: destroy frame_bufs in handle_toplevel_destroy so they do not leak between WPrefs launches 2026-06-06 14:19:33 +02:00
lkn f606558698 fix: also check scene_buf NULL to detect stale WINGs frame_bufs (parent frame can also leak, making parent_id check insufficient) 2026-06-06 12:03:35 +02:00
lkn fc27be21e3 fix: clean up stale WINGs frame_bufs on toplevel map so second WPrefs launch does not skip wManageWindow 2026-06-06 11:55:22 +02:00
lkn b26a4c733c fix: save host WAYLAND_DISPLAY before overwrite, restore on close so restart finds the host compositor 2026-06-06 11:27:38 +02:00
lkn bfa715a99e fix: unlink wayland socket manually instead of calling wl_display_destroy (wlroots 0.19 multi-backend aborts on display destroy during shutdown) 2026-06-06 11:14:08 +02:00
lkn 563b278268 fix: remove explicit wlr_backend_destroy before wl_display_destroy 2026-06-06 11:12:54 +02:00
lkn 6a9b509d77 fix: guard wl_list_remove in input destroy handlers against double-remove during wl_display_close 2026-06-06 11:11:30 +02:00
lkn 1c6c9d6e66 fix: call wl_display_destroy on close so restart works (stale socket was preventing execvp from re-binding) 2026-06-06 11:06:59 +02:00
lkn 1045bd6a77 fixup: remove build artifacts accidentally tracked 2026-06-06 11:01:10 +02:00
lkn a89859ab32 cleanup: remove WL_DECOMPOSITION_PLAN.md (decomposition complete) 2026-06-06 11:01:04 +02:00
lkn e78cef743e step 5: extract protocol init to wl_extensions.c (protocol globals + 5 handler relocations) 2026-06-06 09:47:08 +02:00
lkn b5aa99542c step 4: move wl_handle_x11_events from wl_backend.c to wl_xwayland.c (X11 event bridge) 2026-06-06 09:43:44 +02:00
lkn 3b3ab96597 step 3: move screen internals from wl_backend.c to wl_screen.c (alloc_gcs, create_internals, monitors_query, 293 lines) 2026-06-06 09:42:17 +02:00
lkn 9178f8a903 step 2: extract wl_background.c from wl_backend.c (desktop background rendering, 304 lines) 2026-06-06 09:40:20 +02:00