Adds drag_begin slot to W_ViewBackend. WMDragImageFromView dispatches
through it. X11 backend sets NULL (falls through to existing XDND
code). Wayland backend is a no-op — prevents crash without guards.
Bridges WINGs internal clipboard to the Wayland seat selection so
copy/paste works between compositor-internal dialogs and real
Wayland clients. Store creates a wlr_data_source and sets seat
selection. Fetch reads from foreign selection sources via pipe.
Adds clipboard_store/clipboard_fetch to W_ViewBackend. Replaces
direct XFetchBuffer/XStoreBuffer calls in wtextfield.c and wtext.c
with vtable dispatch. X11 backend wraps Xlib, Wayland backend uses
an internal buffer.
Replaces all no-ops in wview_wl.c with real implementations:
- move: updates pos, triggers parent redisplay
- resize: reallocates pixman buffer, triggers redisplay
- unmap/unmap_subviews: clears mapped flag, triggers parent redisplay
- raise: moves view to end of sibling list (drawn last = on top)
- lower: moves view to front of sibling list (drawn first = on bottom)
- reparent: moves between parent children lists
Hardcoded white/dark bevel looked wrong against colored titlebars.
Derive highlight and shadow colors from the actual titlebar background
color (+33% for highlight, -33% for shadow) to match the titlebar
bevel style.
- Background: matches titlebar background (sampled)
- Foreground: uses title text color (the color parameter)
- Pressed state: swaps fg/bg (inverted colors)
- Border: raised bevel when normal, sunken when pushed
- Icon pixels rendered in fg color (not raw XPM colors)
Buttons and titlebar are siblings in the same scene tree. The titlebar
scene_buf is created after buttons, so it sits on top and occludes them.
Raise button nodes to top after enabling them in wl_frame_paint_button.
Buttons were rendered into the titlebar buffer with their own scene_buf
nodes disabled. This made them invisible to wlr_scene_node_at and
required a fragile coordinate-based hit-test (frame_buf_button_at).
Switch to rendering each button into its own frame_buf image and
enabling its scene_buf node. This makes buttons:
- Visible as independent scene nodes
- Directly hittable by wlr_scene_node_at
- Properly clickable via the normal frame_buf_at path
Update frame_buf_button_at to use a size threshold (<=30px) instead
of the disabled-node check to identify button sub-windows.
Replace the one-liner wl_drawer_icon_paint with a full implementation
that draws a filled triangle arrow pointing toward the drawer expansion
direction (left when on_right_side, right otherwise), matching X11.
Replace the one-liner wl_clip_icon_paint (which only painted the base
tile) with a full implementation that overlays the workspace number at
top-center and workspace name at bottom, matching the X11 behavior.
Also adds workspace.h and dock.h includes for WWorkspace/WDock types.
The dock icon drag code (appicon.c, dock.c) uses scr->dock_shadow as a
visual ghost showing the target dock slot. On X11 this is an InputOutput
window; on Wayland it was never created, making all drag shadow
operations no-ops.
Create dock_shadow as a wl_frame_buf with a scene_buffer in the overlay
layer, starting disabled. All existing vtable ops (window_move, map,
unmap, set_background_pixmap, stacking_restack) already handle
frame_bufs, so no additional plumbing needed.
The WINGs view ID is not in the context table — only the fake leader
(parent frame_buf) is registered. Walk up to parent_id for the
context_find lookup so the WWindow is found correctly.
Also removed the ID >= 0x20000000 guard so any frame_buf click
(including WM frames without a toplevel surface) triggers focus.
The click-to-focus code only checked wl_toplevel_view surfaces
(real Wayland/XWayland clients). WINGs dialogs are frame_bufs,
not toplevel views, so view_surface_at returned NULL and focus
was never set.
Add fallback: when no toplevel surface is under the pointer,
check if a WINGs dialog frame_buf (ID >= 0x20000000) was hit
and focus its associated WWindow.
On Wayland, all WINGs child widgets composite into a single scene_buf
for the toplevel dialog. Clicks always arrived with the toplevel window
ID, so WMHandleEvent delivered them to the toplevel view which has no
ButtonPressMask handler. Child widget handlers never fired.
Fix: add W_FindViewAtPoint() to walk the view tree and find the deepest
mapped child at click coordinates. In handle_pointer_button, resolve the
child view for WINGs surfaces and pass it as the direct target to
W_WL_EnqueueEvent. WMHandleEvent then delivers directly to the widget.
frame_buf_at() could not find WINGs dialog frame_bufs because they
have no tree wrapper (only a bare scene_buf in WL_LAYER_FLOATING).
After the walk-up stops at the level_tree bucket, Step 4 only checks
fb->tree, missing tree-less frame_bufs entirely.
Add Step 5: match tree-less frame_bufs whose scene_buf node is a
direct child of a level_tree bucket. This resolves clicks on WINGs
modal dialogs (Run dialog, info panels, etc.).
frame_buf_button_at() used fb->scene_buf->buffer as the filter to
distinguish invisible button sub-windows from visible titlebar/resizebar
nodes. But wlr_scene_buffer_create() sets an initial buffer reference,
so scene_buf->buffer was never NULL for buttons — causing the hit-test
to skip all of them.
The correct distinguisher is node.enabled: buttons have their scene
node disabled (they are composited into the titlebar buffer by
wl_frame_paint_button), while titlebars/resizebars have enabled nodes.
The Wayland backend already had full wl_appicon_paint and wl_icon_paint
implementations (tile + image composite + text + scene commit), but
appicon_show was a no-op stub. This meant app icons were never initially
placed or mapped when applications launched.
Fix: wl_appicon_show now calls paint_app_icon() (pure policy code that
handles PlaceIcon + wAppIconMove + wLowerFrame + window_map via vtable),
identical to the X11 backend.
Also mark A7 (placement) as verified — pure policy, zero backend calls.
Replace broken xev->u.unmap.send_event check (WMEvent has no unmap
member) with unconditional withdraw=True when event comes from root.
Also remove unused XEvent variable in handleExpose.
- x11_frame_paint_impl: delegate to wFrameWindowPaint() which handles
texture remake, background setup, bevel drawing, and title/button paint.
The old implementation only drew title text and resizebar lines, missing
all background/texture setup.
- x11_icon_paint: add recursion guard. wIconPaint() calls icon_paint()
which called wIconPaint() again — infinite recursion causing stack
overflow crash on first dock/clip icon paint (blank desktop).
- Remove debug wwarning lines from wevent_x11.c and menu.c.
Added permanent surface_commit_listener to wl_toplevel_view that calls wlr_output_schedule_frame on every client buffer commit.
Previously only the initial-commit listener existed, which removed itself after sending the first configure — subsequent buffer updates (typing in foot/xterm) were not triggering re-renders.
When a WINGs toplevel (modal dialog, info panel, etc.) is destroyed,
wl_destroy() now calls W_ViewDestroyBackingFn, which invokes
wl_view_destroy_backing_impl() to remove the frame_buf from the
frame list and destroy the wlr_scene_node. This ensures closed
WINGs windows actually disappear from screen.
- wevent_wl.c: W_IsValidView() validates _view_target against view_table
at fetch boundary (wl_fetch, wl_check_mask, wl_check_typed_window);
nulls target before returning event if view was destroyed
- wevent_wl.c: W_WL_InvalidateView() nulls queue entries at destroy time
- wview.c: set view->screen=NULL before wfree(view)
- wevent.c: defense-in-depth: check !view->screen || alreadyDead before use
Vtable slots implemented in wl_backend.c:
- window_set_background_pixmap: composite pixman image into frame_buf/WINGs view
- window_clear: fill frame_buf/WINGs view with transparent black
- pixmap_free: pixman_image_unref
- shape_combine_mask: wlr_scene_buffer_set_opaque_region from mask pixman image
- switchpanel_send_enter: focus-guard flag to suppress spurious focus on panel destroy
- keyboard_grab/keyboard_ungrab: suppress client key forwarding during alt-tab loop
WINGs view backing: wl_window_clear and wl_window_set_background_pixmap
fall back to W_GetViewBacking() for WINGs window IDs when frame_buf_find fails.
cycling.c: synthesize XEvent when _x11_raw is NULL (Wayland native events)
to prevent segfault in switchpanel event handling.
Alt-tab cycling works. Switch panel rendering still needs investigation.
Implement wl_pointer_grab (type 3 WM policy grab) that suppresses
client pointer focus delivery during WM-internal drags while letting
WME_MOTION events flow to WMMaskEvent. Implement wl_pointer_ungrab
and wl_pointer_change_cursor (maps WCUR_* to xcursor names).
Fix button release handler to not terminate type 3 grabs early —
the policy code calls pointer_ungrab itself.
Fix overlay surface blocking hit-testing: the iconify animation's
full-screen overlay (WL_LAYER_OVERLAY) was left enabled after the
animation completed, causing wlr_scene_node_at to hit it instead of
frame_bufs. Now server_ungrab disables the overlay node after flush.
Three bugs fixed:
1. Close and iconify buttons broken after frame rendering model change
Buttons are composited into the titlebar buffer, so their scene_buf
nodes carry no committed buffer and are invisible to wlr_scene_node_at.
frame_buf_at() returned the titlebar fb for all button-area clicks,
routing them to titlebarMouseDown instead of buttonMouseDown.
Fix: add frame_buf_button_at() -- coordinate walk of frame_list entries
with scene_buf->buffer == NULL -- and frame_buf_hit() which tries button
resolution first then falls back to the scene graph. Replace all
frame_buf_at() calls in button and motion handlers with frame_buf_hit().
Also emit synthetic WME_LEAVE/WME_ENTER events on sub-window boundary
crossings so buttonMouseDown's press-track loop works correctly.
2. Iconify crash (SIGSEGV in update_icon_title)
WMDrawString() was passed icon->core->window (a frame_buf synthetic ID,
0x10000000+) as the drawable. wl_font_resolve_drawable() only recognised
WINGs view IDs (0x20000000+), so it cast the frame_buf ID directly as a
pixman_image_t* -- wild pointer -- SIGSEGV.
Fix: add W_RegisterBacking()/W_UnregisterBacking() to wview_wl.c.
Register each frame_buf's pixman image in the backing table on creation
and unregister on destroy. Extend the ID range check in wfont_wl.c and
wrender_wl.c from 0x20000000 down to 0x10000000 so frame_buf IDs resolve
correctly via W_GetViewBacking().
3. Resizebar visible after iconify + miniwindow not rendering
wl_window_unmap() disabled fb->scene_buf->node individually, but a
subsequent wl_frame_paint() could re-enable and re-commit it while the
window was still iconified.
Fix: for top-level frames, disable fb->tree->node -- hides the entire
scene tree (frame buffer, titlebar, resizebar, client surface) atomically.
Symmetric enable in wl_window_map().
Miniwindow icons were invisible: icon_paint was a noop so the frame_buf
image was never committed to the scene graph. Add wl_icon_paint() mirroring
wl_appicon_paint(): tile + file_image composite + title text, committed
via wlr_scene_buffer_set_buffer(). Wire into wIconPaint() in icon.c so
the backend finalises the surface after policy-layer setup.
view_surface_at iterated toplevel_list in reverse using geometry alone
to find the view under the cursor. toplevel_list order is maintained
separately from scene Z-order: handle_pointer_button updated it on click
but stacking operations (wl_stacking_restack, wl_stacking_raise/lower)
did not. After any stacking operation the list order and visual order
diverged, so successive clicks at the same position returned different
views, causing focus to toggle between overlapping windows.
Fix: use wlr_scene_node_at as the authority on what is visually topmost,
then walk up to the fb->tree / window-root level (stopping when the
parent is a level_trees[] bucket), and scan toplevel_list only for the
ID-to-surface mapping of the matched node. This is the same approach
already used by frame_buf_at after the level-tree rewrite.
The scene graph is the single source of truth for Z-order.
Three interlocking problems were present in the Wayland stacking
implementation.
1. Wrong scene-graph nodes targeted by stacking operations
Each managed window groups its frame pixels and client surface under a
single wlr_scene_tree (fb->tree):
fb->tree
fb->scene_buf (frame decoration pixels)
child_frame_bufs (titlebar / resizebar sub-frames)
v->scene_node (client surface, reparented in)
wl_stacking_restack was raising fb->scene_buf -- a child inside fb->tree
-- instead of fb->tree itself, a no-op for inter-window ordering. Three
redundant loops then tried to separately raise the client surface and child
frames, also to no effect. wl_stacking_lower had the same bug.
Fix: raise/lower fb->tree in both functions so the entire window assembly
moves as one atomic unit.
2. Flat scene graph -- no structural level ordering
All nodes were direct children of scene->tree, so creation order and
raise_to_top calls determined Z-order. A normal window could accidentally
render above a floating window after any CommitStacking call.
Fix: introduce 9 wlr_scene_tree level buckets created at scene init time
in bottom-to-top order:
0 WL_LAYER_BACKGROUND background rect / wallpaper
1 WL_LAYER_DESKTOP WMDesktopLevel
2 WL_LAYER_SUNKEN WMSunkenLevel
3 WL_LAYER_NORMAL WMNormalLevel (default for new frames)
4 WL_LAYER_FLOATING WMFloatingLevel / WINGs dialogs
5 WL_LAYER_MENU WMMainMenuWindowLevel / OR-redirect windows
6 WL_LAYER_DOCK WMStatusWindowLevel / dock panel surface
7 WL_LAYER_FULLSCREEN WMFullscreenLevel
8 WL_LAYER_OVERLAY WMModalPanel / WMPopUp / XOR overlay
Every scene node is assigned to the correct bucket at creation.
A new stacking_set_level vtable slot called from AddToStackList moves
fb->tree to the right bucket whenever a window is added or its level
changes via ChangeStackingLevel. X11 backend implements it as a no-op.
3. XDG client surface not reparented into fb->tree
For XDG toplevels the map handler called wManageWindow before creating
view->scene_node, so the reparent inside wl_client_reparent was a no-op.
The client surface then lived as a flat sibling of fb->tree in
WL_LAYER_NORMAL, never moving with its frame during stacking operations.
Fix: after creating view->scene_node, check if frame_id is already set
and do the deferred reparent immediately.
4. Hit-testing broken by level-tree hierarchy
frame_buf_at walked up from the hit scene node to the first direct child
of scene->tree. With level buckets that stopped at the level_tree node
instead of fb->tree, making every window in the same bucket appear to be
the same window. Client surfaces were not blocking clicks on frames
beneath them, and titlebar sub-frames were not distinguished from the
top-level frame, breaking move and resize initiation.
Fix: rewrite frame_buf_at with a two-pass approach:
Pass 1 -- exact match: check if the topmost node is a child frame_buf
scene_buf such as titlebar, resizebar, or button; return it directly.
Pass 2 -- walk up stopping when parent is a level_tree bucket, then
match that window-root node to a top-level frame_buf tree.
Return NULL if the hit was on a client surface.
ROADMAP: mark A1 done.
frame_buf_at was preferring parent frame_bufs over smaller children
(buttons), so clicks on the close/iconify button area were dispatched
to the titlebar instead. Fix by preferring the smallest frame_buf
that contains the pointer position.
wl_window_move for frame_bufs was setting absolute x,y instead of
updating rel_x/rel_y and computing absolute position from parent.
This caused the close button to stay at its old position when the
frame was resized (e.g. on maximize).
Fix: set rel_x/rel_y and compute absolute position from parent,
matching the logic in wl_frame_configure.
frame_paint was never called after wWindowConfigure during maximize
because the X11 path relies on expose events to trigger repaints.
In Wayland there are no expose events, so explicitly call frame_paint
after wWindowConfigure in wMaximizeWindow and wUnmaximizeWindow.
Also:
- Guard client scene_node repositioning in frame_configure (NULL check)
- Move dispatch+flush+frame_done into wl_window_resize with reentrancy
guard and output frame listener disabled to avoid nested render crashes
- Remove debug wwarnings
- Send frame_done after configure so clients can render at new size
- Set activated state on toplevel configure
- Dispatch idle callbacks to flush configure immediately
- Add client_set_maximized vtable entry for XDG toplevel state
- Re-enable frame_buf scene node on unshade
- Raise client scene node on window_map (fixes unshade visibility)
- Reposition client and children in frame_configure
- Schedule output frame after frame_paint
- Remove debug wwarnings from wl_window_resize
This fixes:
- Window shade/unshade (resizebar + client surface visibility)
- Client surface receiving configure events and rendering
- Frame decoration updates on resize/maximize