After shading a window, clicks on the lower titlebar would be routed to
the resizebar handler, requiring multiple clicks before one landed in
a region where frame_buf_button_at didn't return the (disabled) resizebar.
Two related bugs:
1. frame_buf_button_at iterated all child frame_bufs without checking
enabled state. The shaded resizebar's bounds still recorded in
fb->x/y/width/height but its scene_buf was disabled. Since the
shaded resizebar was repositioned ON TOP of the titlebar, clicks
in the overlapping region returned the (smaller, disabled)
resizebar instead of the titlebar. Added an enabled-state check.
2. wlroots scene graph caches buffer dimensions for hit-testing.
Mutating fb->base.width/height directly leaves stale bounds. Use
wlr_scene_buffer_set_dest_size() whenever the buffer is resized.
Also fixes:
- WINGs dialog content was placed in WL_LAYER_FLOATING instead of
under the parent frame's tree, so the content didn't follow the
WM-managed decorations on move. Now parented under the frame tree
and positioned at relative offset.
- wl_window_map called wlr_scene_node_raise_to_top on the client
scene_node, which raised it above the titlebar within the frame
tree. After unshade this hid the titlebar. Removed.
- xdg_activation focus path: raise the frame tree, not the client
scene_node, for the same reason.
The scene graph was using absolute coordinates for all nodes with the
frame tree at (0,0). This caused the resizebar to be detached from the
window frame and resize to break because child nodes (titlebar, client
surface, resizebar) had to be manually repositioned on every move/resize.
Refactor to the correct wlroots model:
- Frame tree positioned at (frame_x, frame_y)
- All children use local offsets relative to the tree
- Moving the tree moves everything automatically
Additionally fix interactive resize:
- handle_toplevel_commit now syncs the WM frame to the client's actual
committed buffer size, ensuring frame and client stay in lockstep
- Ensure client scene_node is always parented under the frame tree
- Simplify wl_window_resize to just send xdg_toplevel.set_size without
blocking event loop dispatch
- Properly free old pixman images and detach stale buffers on resize
1. Resizebar: wl_frame_paint_resizebar was painting a flat color buffer
over the textured resizebar rendered by wl_frame_paint. Make it a
no-op since the parent frame buffer has the correct texture.
2. Resize gap: client_synth_configure updates the client scene_node
position but was not updating frame_rel_x/y. When frame_configure
ran next (e.g. on subsequent move), it would recompute the client
position using stale frame_rel values, causing the surface to jump.
Now updates frame_rel_x/y from the new absolute position.
On X11, resizing a frame triggers an Expose event which repaints the
decorations. On Wayland, no such event is generated, so the frame
(titlebar, resizebar) was never repainted after resize — leaving
stale decorations at the old width.
Call frame_paint after reconfigure in both wFrameWindowConfigure
and wFrameWindowResize.
frame_buf_button_at had a size threshold (width>30 || height>30) that
skipped resizebar frame_bufs during hit-testing. The resizebar is wide
but short (~8px), so it was always skipped. Clicks in the resizebar
area fell through to the parent frame, preventing resize.
Remove the size threshold. The smallest-area heuristic already ensures
buttons (small) are preferred over resizebars (large) when both match.
Client surfaces (e.g. foot terminal) were not updating after initial
map. Two issues:
1. wlr_scene_output_commit(scene_output, NULL) in wlroots 0.19 may
skip rendering if it determines no damage is pending, even when
client surfaces have committed new buffers.
2. Frame scheduling was dependent on host compositor timing.
Fix: Replace wlr_scene_output_commit with explicit build_state +
commit_state cycle which properly tracks damage. Also schedule a
frame unconditionally every loop iteration to ensure continuous
rendering in nested mode.
Implement wl_drag_begin as a simple internal drag-and-drop state machine
for WINGs-to-WINGs drag operations (color wells, text widgets).
On button press, waits for motion to confirm drag intent, then enters
a drag loop tracking the cursor. On release, finds the view under the
cursor and if it has drag destination procs + matching types, fetches
data from the source and calls performDragOperation on the destination.
This handles same-process DnD without needing wl_data_device protocol.
findMenu() only checked root_menu, switch_menu, and window_menu.
Dock and clip menus were never found, so pointer motion over them
could not highlight entries or activate on release.
Add CHECK_MENU calls for scr->dock->menu, scr->clip_menu, and
scr->dock_pos_menu.
The optimization that skipped pango_layout_set_text when the text
matched the previous layout content used pango_layout_get_text()
which returns an internal pointer. This pointer comparison with
arbitrary input text could read freed memory or trigger heap
corruption in Pango internals when the layout state was stale.
Always call pango_layout_set_text unconditionally. The cost is
negligible (Pango internally short-circuits if text is identical).
Add selection_fetch vtable slot for synchronous selection retrieval.
On Wayland, WMRequestSelection uses this to fetch clipboard/primary
selection data directly (via W_ClipboardFetchFn -> seat pipe read)
and calls the callback immediately. On X11, the async path
(selection_request -> SelectionNotify event) is unchanged.
This makes middle-click paste work in WINGs text widgets on Wayland.
Phase D (WINGs widget X11 elimination) is complete with 7 commits.
The src/ core audit confirms all X11 is already routed through
wm_backend-> dispatch. The entire codebase has zero X11 executing
on the Wayland path.
W_TopLevelOfView returns NULL for views without a topLevel ancestor
(e.g. geometry display widgets parented directly to root). On X11 this
never triggered because expose events only arrived for mapped windows
with proper widget hierarchy. On Wayland, wl_redisplay synthesizes
expose events for any view, exposing the NULL case.
Fix: fall back to the view itself when no topLevel ancestor exists.
The magnifier is X11-backend-specific (screen capture). Instead of a
disguised is_x11 check, the view backend provides the callback directly:
- X11: .magnify_callback = W_ColorPanelMagnify (the magnifier impl)
- Wayland: .magnify_callback = NULL (button does nothing)
The button wiring uses the vtable value as the action callback.
No conditionals, no capability flags, pure vtable dispatch.
Also removes the screen_grab_image vtable slot that was a disguised check.
Converts 60 X11 calls (XCreatePixmap, XFreePixmap, XCopyArea,
XFillRectangle) to render backend vtable dispatch. Remaining 40
calls are all in the magnifier feature (screen capture, inherently
platform-specific) and will be handled separately.
x11_clipboard_fetch now returns wmalloc copy (XFree internally).
Callers use wfree uniformly on both backends. Prevents X11 regression
where XFree was called on wmalloc memory or vice versa.
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.