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.
* 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.
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.
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.
- 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
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.
Three root causes fixed:
1. RCreateContext crash: wScreenInit called RCreateContext(dpy=NULL).
Fix: add RCreateContextWayland() to wrlib — creates a minimal 32-bit
ARGB context without X11. Wayland path in screen.c now always taken
when is_wayland (removed incorrect XWayland fallthrough).
2. Color parsing: parse_color and get_color_for_colormap were no-ops.
Fix: implement real color parser in wl_backend supporting #RGB,
#RRGGBB, #RRRRGGGGBBBB, rgb:RR/GG/BB, and common named colors.
Texture creation (solid, gradient) now works in Wayland mode.
3. RConvertImage crash: wrlib tried XCreateImage with NULL display.
Fix: RConvertImage/RConvertImageMask return success with pixmap=0
when context->dpy is NULL. Callers don't use pixmaps in Wayland
mode (rendering goes through vtable slots to pixman buffers).
4. Workspace crash: Wayland path skipped wWorkspaceNew(), so
workspace_count=0 when menu tried to access workspace entries.
Fix: add wWorkspaceNew + netwm_init to Wayland screen init path.
Result: compositor starts, event loop runs, window is visible.
WMAKER_USE_WAYLAND=1 wmaker now shows a compositor window.
- wmspec.c, xdnd.c, xmodifier.c: pure X11 mechanism code now compiled
as part of x11_backend.o (included via #include in x11_backend.c).
Removed from main link (Makefile modified).
- Policy file calls to NETWM/XDND functions replaced with vtable dispatch:
netwm_process_client_message, xdnd_process_client_message,
xdnd_process_selection, xdnd_make_awareness,
netwm_check_client_hint_change, netwm_position_splash,
netwm_check_initial_frame_state
- x11_prop_get_check_property now returns wfree-able memory (callers
use wfree instead of XFree)
RESULT: nm -u *.o | grep ' U X' returns ZERO for all non-backend .o files.
The backend abstraction is architecturally complete.
Add vtable slots: icon_select, icon_change_title, clip_icon_paint,
drawer_icon_paint, balloon_init, do_kaboom, menu_paint_entry,
menu_draw_frame.
Replace policy call sites with vtable dispatch. Make paintEntry and
drawFrame in menu.c non-static as wMenuPaintEntry/wMenuDrawFrame.
X11 backend calls the mechanism functions; Wayland backend is no-op.
wFrameWindowPaint now only reached through X11 backend's frame_paint.
Architecture: frames are pixman images committed to wlr_scene_buffer
nodes — no XWayland dependency for WM UI rendering.
Key changes:
- Add wl_frame_buf struct (pixman_image_t + wlr_buffer + scene_buffer)
- Implement wlr_buffer_impl for frame buffers (begin_data_ptr_access)
- frame_create_toplevel: allocates pixman buffer + scene node
- frame_create_child: returns synthetic ID (sub-regions of parent)
- frame_configure: repositions scene node, reallocates on resize
- frame_destroy: destroys scene node and drops buffer
- frame_paint: renders titlebar texture (via wTextureRenderImage),
title text (via WMDrawString on pixman WMDrawable), and resizebar
into the pixman buffer, then commits to scene graph
- Add frame_paint vtable slot to WMBackend (backend.h)
- wFrameWindowPaint: calls wm_backend->frame_paint first; falls
through to Xlib code only if backend returns 0
- window_map/unmap/move: handle frame buffers via scene node ops
- stacking_raise/lower: handle frame buffers
- window_get_rect: return frame buffer geometry
- Remove all XWayland calls from frame/window lifecycle
The OR (override_redirect) surface handling remains for XWayland
client windows that use override_redirect (tooltips, menus from
X11 apps). The WM's own UI is now pure Wayland.
Add client_close vtable slot to WMBackend:
- x11: XKillClient + XFlush
- wl: wlr_xdg_toplevel_send_close (politely asks client to close)
- wClientKill: use wm_backend->client_close() for Wayland path
Guard wClientSendProtocol with if (!dpy) return so it is safe to call
on Wayland windows (protocols.DELETE_WINDOW is always 0 for Wayland).
Wire XDG toplevel set_title signal: add title_listener to
wl_toplevel_view; handle_toplevel_title updates wwin->wm_class /
wm_instance when the client changes its title, and calls
wWindowUpdateName (which no-ops on frameless windows but posts
WMNChangedName when a frame is present).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add display_default_screen, atom_intern, and event_loop_run vtable slots.
Guard all X11-only paths in startup.c/screen.c/shutdown.c/event.c/xdnd.c
with if (!dpy) so the Wayland path reaches wl_display_run() and exits
cleanly (exit 0) on SIGTERM. wl-smoke-test.sh now PASSES.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add three display-lifecycle slots to WMBackend:
display_open(name) — open/init display connection, return Display*
(NULL for Wayland; compositor init side-effect)
display_close() — tear down display connection
display_screen_count() — number of logical screens
x11_backend: implement with XOpenDisplay (including close-on-exec fcntl),
XCloseDisplay, ScreenCount(dpy); remove fcntl/FD_CLOEXEC from main.c.
wl_backend: implement display_open() with full wlroots headless compositor
init — wl_display_create, wlr_headless_backend_create, wlr_renderer_autocreate,
wlr_backend_start. display_close() tears down in reverse order.
Compositor state held in a file-static struct. Adds -DWLR_USE_UNSTABLE
to WLROOTS_CFLAGS in configure.ac.
main.c: move backend selection before display_open() (was in StartUp()),
call wm_backend->display_open() instead of XOpenDisplay(); guard all
X11-specific post-open code (visual-id, DisplayName, wXModifierInitialize)
in if(dpy != NULL).
startup.c: replace ScreenCount(dpy) with wm_backend->display_screen_count();
remove now-redundant backend-selection block (owned by main.c).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>