Speculative fix for double-free crash when right-clicking a window
titlebar to open the window menu (WMaker's Options cascade brother
realize). Stack trace shows pixman_image_unref crashing with
'double free or corruption (out)' on a freshly-created 1x10 image.
This commit:
* Saves old_image, creates new fb->image, updates metadata, notifies
wlroots, THEN unrefs old_image — so wlroots' begin_data_ptr_access
only ever sees the new image during the resize transition.
* Adds wwarning logging in wl_frame_configure and the buffer destroy
callback to catch the crash sequence.
Diagnostics will be removed once the root cause is fixed.
E3 (Crash Dialog):
Showing a UI from a crashed compositor's child process is impossible
on Wayland — the child cannot connect to the dead parent's display.
Default to WMRestart instead of WMAbort, so the user's session
recovers automatically on a wmaker crash (the standard daily-driver
expectation). Removed the misleading 'cannot open connection'
message.
E2 (Info Panel) and E4 (Icon Chooser): both already use the standard
fake_leader_create + wManageInternalWindow pattern that the Exit and
Run dialogs use. No code changes; user can verify by opening Info
Panel from the root menu and Icon Chooser via right-click on a dock
icon → Settings → Browse.
Native Wayland clients had no way to be saved across restarts: the
backend's window_get_command vtable slot was a no-op, so session.c's
makeWindowState early-returned NULL for every native client.
Implementation:
* Native XDG client: return [app_id] as single-arg command. Most
app_ids match the binary name (foot, firefox, gedit, etc.) so
plain execvp() during session restore finds them on PATH.
* XWayland client: try WM_COMMAND via wl_state.x_display first
(most reliable), fall back to instance/class.
Limitation: reverse-DNS app_ids (org.gnome.Terminal, etc.) won't
restore — a follow-up patch should add a desktop-file lookup.
Both files implement X11-specific protocols (EWMH _NET_* and XDND).
They were sitting in shared src/ because they predated the backend
split; the only remaining users in shared code were now via
wm_backend-> dispatch.
To get there, four new vtable slots replace the last direct calls
from shared src/:
screen_get_head_usable_area (screen.c was calling wNETWMGetUsableArea)
window_get_icon_image (icon.c was calling get_window_image_from_x11)
netwm_set_frame_extents (window.c, winspector.c were calling wNETFrameExtents)
(window_fetch_name and window_get_icon_name extended to consult EWMH first)
Other changes:
* get_window_image_from_x11 now takes Display* explicitly so the
Wayland backend can pass wl_state.x_display for XWayland clients
instead of relying on a global (which is NULL on Wayland).
* Eight shared .c files lose their now-unused #include "wmspec.h"
or #include "xdnd.h".
* Makefile.am moves wmspec.{c,h} and xdnd.{c,h} into the backend/x11
listing.
* src/Makefile.am drops the obsolete --disable-xdnd Makefile branch
and unconditional sources list still references the new paths.
Final state: every Xlib call in the codebase lives in src/backend/x11/
or src/backend/wayland/. The only remaining "X*(" matches outside
backend/ are six XSetForeground/XDrawLine calls in src/moveres.c
inside an \#if 0 block (dead code; left for now). src/ has zero
_x11_raw references.
Crash on closing a toplevel: handle_toplevel_destroy called
wlr_scene_node_destroy on view->scene_node, but the node had
already been destroyed by wlroots a few stack frames earlier when
the WMaker frame's tree was destroyed (frame_destroy → tree
destroy cascades to children).
Fix: register a destroy listener on view->scene_node when it's
created. When wlroots destroys the node (typically because its
parent tree is destroyed during unmanage), the listener fires and
NULLs view->scene_node, so handle_toplevel_destroy's existing
'if (view->scene_node)' check correctly skips the redestroy.
handle_toplevel_destroy itself removes the listener before its
own wlr_scene_node_destroy call so we don't double-remove the
listener entry from the wlroots signal list.
Multi-head support comes through WScreen.heads[] (populated by the
backend's screen_open) and the wGetRectForHead / wGetUsableAreaForHead
helpers — both backend-agnostic. The USE_XINERAMA #ifdefs in shared
src/ were a build-time toggle for X11-extension presence and did not
belong in policy code.
Strip the guards in:
src/wmspec.c _NET_WM_FULLSCREEN_MONITORS atom now unconditional.
src/actions.c wFullscreenMonitorsWindow now unconditional.
src/dialog.c About-dialog 'Xinerama: N head(s) found' string now
unconditional. (The string still references the X11
extension by name; on Wayland this is somewhat
misleading but cosmetic — proper fix would route
'About' info through the backend.)
USE_XINERAMA remains in src/backend/x11/x11_monitor.c, where it
correctly gates the Xinerama-extension code at build time. The
configure --enable-xinerama option is unchanged.
The XDND-on-dock feature was a configure-time toggle (--disable-xdnd)
gating ~25 #ifdef sites across shared src/. This was a pre-Wayland
feature flag, not a backend distinction, but it polluted the policy
layer with build-time conditionals.
Make it unconditional:
configure.ac Remove --disable-xdnd and AM_CONDITIONAL. XDND on
the dock is always built.
src/Makefile.am Move xdnd.c from the if-USE_DOCK_XDND block into
the unconditional wmaker_SOURCES list.
src/dock.h, dock.c, dockedapp.c, appicon.h, appicon.c, event.c,
misc.c, startup.c, dialog.c, screen.h
Strip all #ifdef USE_DOCK_XDND/#endif pairs. In
dockedapp.c the #else branch (rendering an 'XDnD
support was not compiled in' label) is dead and
removed.
Backend routing: the X11 backend continues to drive the XDND protocol
through xdnd.c via the existing xdnd_process_client_message /
xdnd_process_selection / xdnd_make_awareness vtable slots. Wayland's
slots are no-ops (XWayland clients drop into the X11 path naturally
via wl_state.x_display).
src/ is now free of feature-flag guards related to DnD.
Three more vtable slots that took void* (intended for an XEvent*) widened
to take WMEvent*; the X11 impls extract _x11_raw internally:
event_handle_extensions(WMEvent*)
netwm_process_client_message(WMEvent*)
xdnd_process_client_message(WMEvent*)
Also added a new client_message_forward(WMEvent*, WNativeWindow) slot for
the OffiX DND ClientMessage redirect (icon frame -> real client_win).
The X11 backend rewrites xclient.window and XSendEvent's it; the Wayland
backend does the same on wl_state.x_display for XWayland clients.
After this change, src/event.c contains zero _x11_raw references — the
last X11 escape from the shared event dispatch layer is closed.
XSupportsLocale and XSetLocaleModifiers in main.c were the last
direct Xlib calls in src/main.c. Move them into x11_display_open
(guarded by I18N) where they naturally belong. The Wayland backend
does not need them — locale handling for native Wayland clients is
just standard libc, and XWayland clients still receive locale-aware
input via the wmaker-attached XWayland server's own setup.
main.c is now Xlib-free.
* usermenu.c notifyClient: replace XSendEvent KeyPress/KeyRelease
synthesis with two wm_backend->client_send_key calls.
* New vtable slot client_send_key(win, keycode, modifier_mask, press).
X11: synthesize XKeyEvent and XSendEvent.
Wayland: same but on wl_state.x_display (XWayland clients). Native
Wayland clients deferred (would need
zwp_virtual_keyboard_v1).
* misc.c: keysymToString was dead code inside #if 0 — removed.
After this change misc.c and usermenu.c carry zero direct Xlib calls.
xmodifier.c contained x_reset_modifier_mapping which walked the X
server's modifier map via XGetModifierMapping / XFreeModifiermap to
discover the X11 modifier indices for Meta/Hyper/Super/Alt keysyms.
This was 4 direct Xlib calls in shared src/.
Refactor:
src/xmodifier.c — now only holds the modifier-mask state and the
portable wXModifierFromKey / wXModifierToShortcut
Label / wSetModifierMasks helpers used by shared
policy code. No more Xlib symbols.
src/backend/x11/x11_backend.c — owns x11_reset_modifier_mapping,
called from x11_extensions_init. Calls
wSetModifierMasks at the end so shared code
answers modifier lookups correctly.
src/xmodifier.h — drops wXModifierInitialize. Wayland never needed
it; X11 calls the local x11_reset_modifier_mapping
directly.
The unused ModeMask static was dropped along the way.
The Window Inspector's 'select window' feature grabbed the pointer on
the root window then read XEvent.xbutton.subwindow to learn which
window was clicked. This was the last access to _x11_raw in shared
src/ code.
Add a subwindow field to WMEButton. The X11 backend's WMEvent
translation populates it from xbutton.subwindow. The Wayland backend
leaves it 0 (Wayland has no equivalent grab semantic — the click
reports the actual frame as event.window directly). winspector.c
now uses event.u.button.subwindow with fallback to
event.u.button.window, working on both backends.
Net: src/ has zero _x11_raw escapes from shared code. Remaining
references are inside the X11 backend (legitimately consuming the
field set by its own event translator).
Five _x11_raw accesses in shared event.c (the worst architectural leak)
removed by widening vtable signatures and moving X11-only logic into
the X11 backend:
WME_SELECTION_REQUEST / WME_SELECTION_CLEAR
* handle_selection_request/clear functions deleted from event.c
* Reimplemented in x11_backend.c as x11_handle_selection_request/clear
where they read _x11_raw internally and call XChangeProperty etc.
* New vtable slots handle_selection_request / handle_selection_clear
(WMEvent*). Wayland: no-ops (wmaker UI does not own a WM_Sn
selection on the X server).
WME_CONFIGURE
* event_randr_update vtable widened to (WMEvent*).
* Root-window check (DefaultRootWindow) moved into x11 impl.
WME_MAPPING
* Dropped the if (xev) guard — keyboard_mapping_refresh already
took WMEvent*.
WME_SELECTION
* xdnd_process_selection vtable widened to (WMEvent*).
Net: event.c is X11-free except for DefaultRootWindow uses elsewhere
that will be cleaned up in subsequent tasks.
A WMenu's brother is a parallel copy of the same menu used when the
menu is torn off / buttoned. The brother shares cascade pointers
that reference back into the original tree, so recursing into both
m->brother and m->cascades[i] cycled (brother->cascades[i] == m's
own cascade entry, which has its own brother, etc.) and overflowed
the stack on any cascaded menu.
Inline the brother hit-test instead of recursing into it. Cascades
themselves are walked recursively as before so sub-sub-menus remain
reachable.
The previous implementation only checked one level of cascades —
scr->root_menu and scr->root_menu->cascades[i]. Sub-sub-menus
(cascade->cascade->...) were never checked, so hovering over a
sub-sub-menu's entries did not return the menu, and selectEntry
therefore could not open the next-level cascade or react to clicks.
Replace with a recursive walk over each top-level menu's full
cascade tree. Pick the deepest mapped menu that contains the
pointer (deeper cascades render on top in the scene graph and
should win the hit-test).
X11 sidesteps this with XQueryPointer + XFindContext, which finds
any menu window via the X server's hit-test regardless of nesting.
wlr_scene_buffer_set_buffer treats same-pointer assignment as a
no-op — pixel updates to fb->image are not picked up. Every paint
function in the backend mutates fb->image (or s->wlr_buf) in place
and then commits with the same pointer, so wlroots never re-rendered
the changed contents.
Convert all 15 sites to wlr_scene_buffer_set_buffer_with_damage(...,
NULL) which forces a full rerender. This restores the menu paint
fix to its proper general form and addresses the clip workspace
number/title not appearing at startup, as well as latent staleness
in dock/drawer icon and balloon paint paths.
wFrameWindowUpdateBorders calls wFrameWindowResize at line 356,
which (since commit f88dfac) calls frame_paint. On X11,
frame_paint dispatches to wFrameWindowPaint which iterates
button descriptors and calls handleButtonExpose. But the
descriptors' parent field is set later in wFrameWindowUpdateBorders
(after the resize call) — so during this first paint, fwin
(desc->parent) is NULL and the function segfaults at the
left_button comparison.
The Wayland backend skips wFrameWindowPaint via wl_frame_paint
returning 1, so it never hit this path.
Top-level frame trees were enabled by default at creation. When
framewin.c then called wm_backend->window_map_raised(titlebar) at
frame creation time (necessary for X11 decoration semantics), the
titlebar's scene_buf became enabled. Combined with the always-enabled
tree, this made the titlebar visually rendered at the frame's
default spawn position — even for menus and dialogs that had not
been logically mapped yet. The visible orphan reflected whichever
submenu titlebar was painted last (e.g. 'Multimedia').
Fix: top-level frame trees now start DISABLED. wl_window_map and
wl_window_map_raised explicitly enable the tree when called on a
top-level frame_buf. This matches X11 semantics where a child window
is only visible if its parent window is mapped. Decoration children
(titlebar, buttons) being individually 'mapped' no longer produces
orphan visibility because the parent frame's tree gates their
visibility.
Frame_bufs that owned a wlr_scene_tree (top-level frames and the
fake_leader for dialogs) leaked their tree on destroy. The scene_buf
was destroyed but the tree remained in the scene graph as an orphan,
causing stale rendering — most visibly as ghost titlebars near the
clip after closing a menu, since the leaked menu frame trees stayed
in the scene.
Several related fixes for WINGs dialogs and menus that depend on
correct scene graph parenting and hit-testing:
1. fake_leader_create: now creates a wlr_scene_tree (not just a
scene_buf). When the fake_leader is reparented into a WMaker frame,
any content inside it (e.g. WINGs panel) is reparented as part of
the tree, so it follows the WMaker frame's position when moved.
2. wl_client_reparent (frame_buf path): if the source frame_buf has
a tree, reparent the tree (with all its children) instead of the
bare scene_buf.
3. frame_buf_update_children_abs: new recursive helper to walk the
entire descendant tree updating fb->x/y absolute tracking. Used
by wl_window_move, wl_frame_configure, and wl_client_reparent.
Without this, grandchildren (e.g. WINGs panel inside a fake_leader
inside a WMaker frame) had stale fb->x/y after the WMaker frame
was moved, causing click coordinate translation to be wrong and
buttons to stop responding after dragging the dialog.
4. frame_buf_button_at: now walks up the scene tree checking ancestor
enabled state. A frame_buf's scene_buf may be flagged enabled even
when an ancestor tree is disabled (e.g. menu unmapped). Without this
check, clicks could be incorrectly routed to ghost frame_bufs from
recently-unmapped menus, causing root-menu reopen to require
multiple clicks.
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.
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.
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.
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.
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
On shade, the client scene node was lowered and disabled, and the
frame_buf scene node was disabled. On unshade, the frame_buf node
was not re-enabled, and the client node was enabled but remained
behind the frame (at bottom of Z-order from stacking_lower).
Fix: re-enable frame_buf node when show_resizebar is true, and
raise the client scene node to top on window_map so it's above
the frame background.
On Wayland, scene buffer nodes are independently composited and not
clipped to their parent frame bounds. When shading collapses the frame
to titlebar height, the resizebar scene node remained visible because
nothing disabled it.
Two changes:
1. wl_frame_paint: skip resizebar rendering and disable both the
resizebar own scene node and the main frame scene buffer when the
frame height is <= the titlebar height (shaded). Re-enable both
when unshaded.
2. actions.c: call wm_backend->frame_paint() after wFrameWindowResize
in both wShadeWindow and wUnshadeWindow. This triggers the scene
graph update on Wayland and is a harmless decoration refresh on X11.
x11_fake_leader_create() creates a 10x10 parent window, but the
original code created each parent at the correct pwidth x pheight.
When the WINGs panel is reparented into a 10x10 parent, X11 clips
child windows to parent bounds, making dialog contents invisible.
wWindowConfigure never resizes the client because client.width is
pre-set to the correct value, so resize evaluates to false.
Add wm_backend->window_resize() on the parent immediately after
fake_leader_create in all 9 callers that reparent content into it:
dialog.c (7), winspector.c (1), dockedapp.c (1).
The window.c group-leader caller is unaffected (size irrelevant).
WMHandleEvent is the middle layer — it must not contain any
backend-specific code. Move view lookup to _view_target field
set by each event backend during fetch. Move XRefreshKeyboardMapping
and XFilterEvent into x11_fetch. Selection and DND handling removed
from middle layer (handled by extraEventHandler in the WM layer).
The _x11_raw field remains in WMEvent for now (used by src/event.c
and other WM-layer code that still needs the raw XEvent), but the
middle layer no longer touches it.
When raising a frame_buf, also raise its children (titlebar,
resizebar) and the client surface inside it. When raising a
client surface, also raise its containing frame. This ensures
click-on-titlebar raises the entire window assembly.
findMenu only checked root_menu and switch_menu, so the window
menu (right-click titlebar) was never found during mouse tracking.
Add window_menu and its cascades to the search.
Read XKB_DEFAULT_LAYOUT, XKB_DEFAULT_VARIANT, XKB_DEFAULT_OPTIONS,
XKB_DEFAULT_MODEL, and XKB_DEFAULT_RULES from environment when
creating the default keymap. Set these to match your layout
(e.g. XKB_DEFAULT_LAYOUT=us XKB_DEFAULT_VARIANT=dvorak).
When the session resumes after a VT switch, wlroots may destroy and
recreate input devices. Remove old listeners before adding new ones
to avoid assertion failure in wlr_keyboard_finish. Initialize all
listener links at startup so the first remove is safe.
- Store wlr_session from wlr_backend_autocreate.
- Skip frame commit when session is inactive (VT switched away).
- Wire ptr->events.motion (relative) for libinput devices so cursor
moves in standalone DRM/KMS mode.
- Use wlr_backend_autocreate for standalone mode (from TTY) instead of
headless-only. Gives real DRM/KMS output + libinput.
- wl_update_pointer_focus: skip client surface focus when pointer is
over an internal frame_buf (menu, titlebar). Prevents client from
stealing cursor/events from WM chrome.
- wl_focus_set: ensure seat keyboard is set and flush clients before
sending keyboard.enter so client has processed wl_seat.capabilities.
- Renderer and allocator use the autocreated backend in standalone mode.
- Add selection_acquire, selection_release, platform_init to
W_ViewBackend struct (were used but not declared).
- Add lookup_string to W_FontBackend struct (same issue).
- Guard selection_acquire call with NULL check in selection.c.
- Remove pixman_image_unref in wl_frame_configure resize path —
wlroots manages the buffer lifecycle via scene refcounting.
LSan confirms no leak.
- Remove #include of wmspec.c/xdnd.c/xmodifier.c from x11_backend.c
— they are already compiled as standalone objects, causing
multiple-definition linker errors on clean builds.
wMouseMoveWindow and wMouseResizeWindow took XEvent* but were
being passed WMEvent* via casts. The XEvent union layout differs
completely from WMEvent, so ev->xmotion.x_root read garbage —
causing the window to jump to a wrong position and the move to
become uncontrollable.
Changed both functions to take WMEvent*, accessing ev->u.button
fields directly. Removes all XEvent references from the core
policy layer (zero backend-specific types in the middle).
When a client (e.g. foot) sets its own cursor surface via
wl_pointer.set_cursor, the cursor disappears once the pointer
moves off the client surface because nobody resets it. Now
wl_update_pointer_focus resets to left_ptr when the pointer
leaves all surfaces or enters a new one (before the client
gets a chance to override via request_set_cursor).
Three issues fixed:
1. Frame and client surface moved independently during interactive
window move. The client surface (wl_toplevel_view) and frame
decorations (wl_frame_buf) are independent scene graph nodes —
unlike X11 where reparenting creates an implicit parent-child
relationship. Added frame_id/frame_rel_x/frame_rel_y to
wl_toplevel_view so wl_window_move keeps them in sync.
2. window_move(client_win, x, y) treated coordinates as absolute,
but the WM core passes relative-to-frame coordinates (matching
X11 child window semantics). Now detected via frame_id and
converted to absolute scene positions.
3. Titlebar scene node was offset by left_button_width because
updateTitlebar() configures it for X11 layout (separate
overlapping windows). Since the Wayland backend composites
buttons directly into the titlebar buffer, override the
titlebar position to frame origin during wl_frame_paint.
Remove every is_wayland / !is_wayland guard from WM policy files.
All backend-specific logic now lives exclusively in the backends:
- main.c: display_post_open vtable slot handles fatal check, visual ID,
display name, and multiHead parsing (X11) or no-ops (Wayland)
- event.c: EventLoop() unconditionally calls event_loop_run(); the X11
backend's implementation contains the WMNextEvent+inotify loop
- screen.c: screen_init_display vtable slot handles RContext/WMScreen/
color creation (each backend owns its full init path)
- monitor.c: show_crash_dialog vtable slot (X11 shows panel, Wayland
returns abort)
- startup.c: InitializeSwitchMenu called unconditionally (it's
backend-agnostic); defaults polling timer no longer guarded
- shutdown.c: wScreenSaveState called unconditionally
- window.c: client_reparent called unconditionally (Wayland no-ops);
dead frameless-window code removed
Policy code is now completely backend-agnostic. The only references to
is_wayland are the vtable field definitions in the two backend files.
- Create WM frames (titlebar, buttons, resizebar) for native XDG
toplevels — remove the is_wayland guard that skipped frame creation
- Set decoration mode to SERVER_SIDE so clients don't draw their own
titlebar (CSD)
- Skip client_reparent for Wayland (no X11 reparenting needed; the
client scene node is independent of the frame scene buffers)
- Set frame_border_width=0 in wl_screen_create_internals (Wayland has
no X11-style window border; the frame_buf renders all decoration)
- Use wlr_scene_subsurface_tree_create for client surface (avoids XDG
geometry offset issues)
Known issue: transient size mismatch on initial map when saved session
state or size constraints differ from the client's committed buffer.
Resolves after the client processes the configure event.
- Add wlr_subcompositor global (required by all modern Wayland clients)
- Switch from xdg_shell new_surface to new_toplevel signal (wlroots 0.19
fires new_surface with role=NONE before get_toplevel assigns the role)
- Defer initial xdg_surface configure to first client commit via
surface->events.commit listener (surface must be initialized first)
- Defer xdg_decoration set_mode to first commit (set_mode internally
calls schedule_configure which asserts initialized)
- Wire destroy_listener to toplevel->events.destroy instead of
xdg_surface->events.destroy (wlroots asserts toplevel event lists
are empty before the toplevel is freed)
- Log DISPLAY and WAYLAND_DISPLAY env vars at startup
- Add WAYLAND_ROADMAP.md
Native Wayland clients (foot, etc.) can now connect, render, and
disconnect without crashing the compositor.
- Only apply default XKB keymap as fallback when the host compositor
hasn't provided one yet (kb->xkb_state == NULL)
- The nested backend receives the host's keymap (Dvorak, etc.) via
wl_keyboard.keymap — don't overwrite it with QWERTY default
wlr_seat_keyboard_notify_key was being called unconditionally, which
caused re-entrancy that consumed WINGs events from the queue before
the modal loop could fetch them. Now only forwards to the seat when
there's an actual focused client surface.
Also use 16ms timeout in wl_flush so keyboard events from the nested
compositor are actually received during modal loop dispatch.
Warn and drop the event rather than silently swallowing it. This makes
the root cause visible — key events arriving before keyboard is fully
initialized.
- window.c: always call frame_paint for internal windows (title=NULL
skipped wFrameWindowChangeTitle which was the only paint trigger)
- wview_wl.c: fill view backing with background color before expose
(X11 server fills background automatically, Wayland needs explicit fill)
When fake_leader is reparented into the frame, its children (the WINGs
view frame_buf) weren't being repositioned. Now cascades position
updates to children of the reparented frame_buf.
The Wayland screen init path had 'goto screen_done' which skipped
gview creation. Now creates it before the goto. Also added safety
guard in moveGeometryDisplayCentered.
- wl_map: set mapped=1 before calling wl_redisplay (was bailing because
mapped flag wasn't set yet)
- wl_view_commit_backing_impl: enable scene node when content is committed
(frame_buf was created disabled, never enabled before frame was mapped)
- fake_leader_create: return a real frame_buf ID (was returning 0)
- client_reparent: handle frame_buf-to-frame_buf reparenting
- client_reparent: get WINGs view size from view table
- window_map: enable grandchildren (dialog content is grandchild of frame)
- window_move: move grandchildren when parent moves
- dialog.c: guard scr->w_visual for Wayland mode in Info panel
All WINGs render backend functions (draw_line, draw_rect, fill_rect,
pixmap_copy, composite_pixmap) now use resolve_drawable() to handle
WINGs view IDs (0x20000000+) by looking up the backing pixman image.
Also fixes:
- wfont_wl.c: resolve view IDs in draw_string/draw_image_string
- wtextfield.c paintCursor: use render backend draw_line (not XDrawLine)
- dialog.c wShowInfoPanel: guard scr->w_visual for Wayland mode
- wl_client_reparent: create child frame_buf for WINGs views, connect
backing pixman image so widgets paint into the scene graph
- View lookup table: map synthetic IDs to W_View* for event dispatch
- render_wl_pixmap_copy: detect view IDs and copy into backing image
- W_ViewCommitBacking callback: commit frame_buf after WINGs paint
- WMHandleEvent: use view table in Wayland mode (was always NULL)
- wtextfield.c paintCursor: use render backend draw_line instead of
XDrawLine (crash fix for Run dialog)
- wl_draw_string: remove ascent subtraction, y is TOP position (matches
X11 Pango behavior where pango_xft_render_layout takes top-left)
- Menu entry text: pass top position (y+3) not baseline (y+3+fh-1)
- Icon title: pass y=0 (top) not y=fh (baseline)
- frame_paint_titlebar: pass centered top not baseline
- frame_configure: compute absolute position from parent for children
(was setting absolute pos to relative coords, warping to 0,0)
- Titlebar buffer: always render full frame width, resize if needed
- Frame scene node: disable when no resizebar (transparent, not needed)
- Always call frame_paint after show/hide button (TS_NEW was skipping it)
wFrameWindowShowButton was only calling updateTitlebar (not frame_paint)
when new_style==TS_NEW. Now always calls frame_paint so the close button
icon is rendered into the frame buffer after pinning a menu.
frame_buf_at was returning the parent frame instead of child windows
(titlebar, content area) because the parent was later in the list.
Now prefers children (frame_bufs with parent_id set) over parents.
This fixes menu titlebar click detection — clicking the titlebar now
dispatches to titlebarMouseDown/menuTitleMouseDown instead of
menuMouseDown, enabling menu pinning and drag-to-move.
On SIGINT/SIGTERM:
- signalfd handler sets terminate flag and exits the event loop
- After loop exit, remove wlroots pointer listeners to prevent
assertion failure in wlr_pointer_finish
- Reset SIGTERM/SIGINT/SIGHUP to SIG_DFL after signalfd takes over
to prevent the old handleExitSig from competing
handleKeyPress passed event->_x11_raw (NULL in Wayland mode) to
wRootMenuPerformShortcut which dereferences it immediately. Skip
the call when _x11_raw is NULL.
pointer_get_position was a noop returning (0,0), causing findMenu to
always return NULL. This made the menu close on every button release
regardless of pointer position. Now returns wl_state.pointer_x/y which
are updated on every pointer motion event.
With this fix, the root menu stays open when the pointer is over it on
release (matching X11 behavior), allowing entry selection or titlebar
pinning.