The menu frame was created at 1x1 and resized via wFrameWindowResize
which calls window_resize. But wl_window_resize didn't handle
frame_bufs — it only handled toplevel views. Now it reallocates the
pixman image when dimensions change, so the frame has the correct
size when wl_frame_paint renders the title bar.
- Entry separators: light line on top, dark line on bottom between entries
- Left/right bevel edges on each entry
- Cascade arrow (triangle) for submenu entries
- Paint menu frame title bar (wl_frame_paint) when menu is shown
- Matches X11 menu_draw_frame behavior
All dock/clip/appicon mouse handlers now use WMEvent directly:
- openDockMenu, iconDblClick, handleDockMove, handleClipChangeWorkspace
- wHandleAppIconMove (appicon.c)
- Removed x11_translate_event hack in dock menu forwarding
- dock.o and appicon.o verified X11-free with nm
In X11, child windows are only visible when the parent is mapped.
In Wayland, all frame_bufs are independent scene nodes. The menu
content window was being mapped at creation time (wMenuCreate),
making it visible even though the parent frame was offscreen/hidden.
Fix: wl_window_map skips enabling a child node if its parent's
scene node is not yet enabled.
Child windows (menu content area, titlebar, resizebar) need their own
pixman buffers for painting. Previously frame_create_child returned a
bare synthetic ID with no buffer, so menu_paint_entry found nothing.
Also add parent-child tracking: when a parent frame moves or maps,
all children are repositioned/enabled automatically. Children store
their relative offset and parent ID.
In X11, mapping a window triggers Expose which causes repainting.
In Wayland there are no Expose events, so wMenuPaint must be called
explicitly after the menu is mapped in wMenuMapAt.
The deadlock: frame_timer_cb called WMHandleEvent which entered
menuMouseDown's modal WMMaskEvent loop. That blocked inside
wl_event_loop_dispatch (reentrant), preventing new events.
Fix:
1. Replace wl_display_run() with custom loop that drains WINGs
events BETWEEN wl_event_loop_dispatch calls (not inside timer)
2. Make WINGs wl_flush() pump wlroots (call dispatch_fn) so
WMMaskEvent modal loops get new events without reentrancy
3. Fix wBalloonHide NULL dereference (scr->balloon not allocated)
4. Remove XEvent dependency from wSelectWindows (takes x,y now)
5. Remove debug traces
Right-click on background now opens the root menu.
- frame_buf_at(): hit-test finds which WM frame_buf is under pointer
- Button/motion events set window field to frame_buf ID or root_win
- Root window gets synthetic ID (0x0FFFFFFF) in Wayland mode
- Right-click on background → handleButtonPress → OpenRootMenu path
- wSelectWindows: remove XEvent dependency, take (x, y) coordinates
- Zero X11 symbols in event.o and moveres.o (verified with nm)
Text rendering uses the existing WINGs PangoFT2 backend — WMDrawString
renders directly into the frame buffer's pixman_image_t.
- Menu entries: real text via PangoFT2 (entry text + shortcut labels)
- Dock/clip icon titles: rendered with icon_title_font
- Frame titlebar: solid background + centered title text
- Frame resizebar: beveled grey bar
All text is anti-aliased (PangoFT2 alpha compositing onto pixman).
- Clip/drawer icons: reuse wl_appicon_paint (tile rendering)
- Menu entries: solid background fill (grey normal, blue selected)
with placeholder text line. Full text rendering TODO.
- Menu frame: beveled border (light top/left, dark bottom/right)
- All rendering commits to scene graph via wlr_scene_buffer_set_buffer
XWayland was started eagerly (at compositor init) which caused a
segfault when XWayland couldn't connect back to the Wayland socket
(race condition in nested mode). Since the WM UI is now pure Wayland
(no X11 rendering), XWayland is only needed for X11 client apps.
Switch to lazy mode: XWayland starts only when the first X11 client
connects. This eliminates the startup wait loop and the associated
crash. Also set xwayland_ready=true in the server destroy handler
as a safety net.
wraster.h is generated from wraster.h.in — the declaration was only
in the generated file, not the template. Also includes the Wayland
no-op for RConvertImage/RConvertImageMask (context->dpy == NULL).
- wl_appicon_paint: renders dock icons by compositing the icon tile
(RImage) with the app image, then blitting RGBA→ARGB32 into the
frame buffer's pixman image and committing to the scene graph.
- Color parser: add grayNN/greyNN pattern support (gray0-gray100)
and rgb:RR/GG/BB X11 color format. All default textures now parse.
- Dock paint trigger: add explicit dockIconPaint calls at end of
wDockCreate and wDockRestoreState since Wayland has no Expose
events on window map.
The dock icon tile is now visible in the compositor window.
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.
- motif.c: wGetMWMHints moved entirely to x11_backend.c (inlined into
x11_motif_get_hints)
- colormap.c: wColormapInstallForWindow inlined into x11_colormap_install;
policy file now contains only vtable dispatch stubs
- resources.c: wGetColorForColormap and wFreeColor now dispatch through
vtable; X11 implementation inlined into x11_backend.c
- xmodifier.c: wXModifierInitialize now called from x11_extensions_init
(removed from main.c)
motif.o, colormap.o, resources.o verified X11-free with nm.
xmodifier.o retains X11 symbols (mechanism code, only reached through
X11 backend — candidate for physical move to backend/ directory).
Replace XFreePixmap(dpy, p) in the FREE_PIXMAP macro with
wm_backend->pixmap_free((unsigned long)p). This eliminates a
hidden dpy dependency from every file that uses FREE_PIXMAP.
Replace dpy-based conditional in wScreenInit with root_win check.
Remaining dpy references in policy files are all in X11 mechanism
code (rendering functions, event handlers, XWayland setup) that is
only reached through the X11 backend path.
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.
- WMCreateScreenWithRContext: add Wayland path that skips all X11 calls
(no GCs, no atoms, no XListProperties, no XCreateGC)
- screen.c: skip RCreateContext in Wayland mode (pass NULL rcontext)
- Wayland path initializes: font/event/render backends, colors, fonts
Current issue: W_RealizeView tries to create X11 windows for WINGs
widgets. Need to make view realization work without X11 (views should
be pixman buffers in Wayland mode, not X windows).
The grey background + cursor render correctly. Startup reaches
WINGs init but crashes when widgets try to realize without a display.
- wScreenInit Wayland fast path now creates RContext on XWayland display
(needed for wTextureRenderImage and wraster image loading)
- Also creates WINGs screen (wmscreen) so WMDrawString/WMFontHeight work
- Add wl_get_x_display() accessor for screen.c to get XWayland Display*
- Remove if(dpy) guard around wScreenRestoreState in startup.c so dock,
clip, menus, and window management run in Wayland mode
Status: startup reaches menu creation but hangs in wMenuCreate due to
Xlib operations on XWayland during WINGs font/screen init. The pump
thread is running but something in the menu creation path is spinning.
Next step: identify and eliminate the remaining XWayland dependency in
the menu creation path.
The global 'dpy' variable was set to the XWayland Display*, causing
120+ direct Xlib calls in the WM core (XMapWindow, XClearWindow, etc.)
to pass synthetic frame buffer IDs to XWayland — hanging the startup.
Fix: wl_display_open returns NULL so global dpy=NULL. All existing
'if (dpy)' guards in the WM core now correctly skip Xlib calls.
The Wayland backend uses wl_state.x_display directly for the few
XWayland operations it needs (atom interning, screen info, GC alloc
for the no-focus window).
This unblocks startup — the event loop now runs successfully.
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.
Key changes in wl_backend.c:
- wl_atom_intern: call XInternAtom on XWayland display (was returning 0)
- Add wl_or_view struct to track override_redirect XWayland surfaces
- handle_xwayland_new_surface: composite OR surfaces via scene graph
- frame_create_toplevel/child: create real X11 windows on XWayland
- frame_destroy/configure: delegate to Xlib
- window_map/unmap/move/resize: fall through to Xlib for frame windows
- stacking_raise/lower: update scene graph + Xlib fallback
- event_flush/sync: call XFlush/XSync on XWayland display
- window_set_border/border_width/clear: delegate to Xlib
- window_get_rect: query XWayland geometry for non-toplevel windows
Note: frame creation via XWayland is a temporary bridge. The correct
architectural approach is native scene-buffer rendering with pixman.
wl_display_open now pumps the Wayland event loop until XWayland fires its
"ready" signal, then calls XOpenDisplay() and returns the Display* to the
core WM. This makes dpy non-NULL so screen.c takes the full X11 code path:
RCreateContext, WINGs screen, workspaces, root menus, and EWMH hints all
initialise via the WM's own XWayland connection.
- wl_handle_x11_events: new fd watcher registered on ConnectionNumber(dpy)
so WINGs menus, dialogs, and keyboard events are dispatched during
wl_display_run() via XPending/XNextEvent/WMHandleEvent
- wl_screen_open: sets root_win/depth/colormap from DefaultRootWindow(dpy)
when bridge is active so RCreateContext gets valid X11 screen values
- wl_screen_create_internals: creates a 1x1 info_window on XWayland for
_NET_SUPPORTING_WM_CHECK and clipboard selection ownership
- handle_xwayland_map: guard against w_global.screen_count==0 (fires before
StartUp() in edge cases) to avoid assert in wScreenWithNumber
- wl_display_close: removes fd watcher and calls XCloseDisplay before
killing XWayland to avoid stale fd events
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two bugs prevented nested Wayland mode (inside sway) from starting:
1. The wlr_wl_backend fires new_input during wlr_backend_start(), before
wl_state.seat exists (seat is created later in wl_display_open).
wlr_seat_set_keyboard(NULL, kb) → SIGSEGV. Guard with if (seat).
2. Sway's initial xdg-shell configure sends width=height=0 ("compositor
chooses"). Without an explicit mode, wlr_output_commit_state tries to
create a 0×0 GBM swapchain → crash in gbm_bo_create. Fall back to
1920×1080 when no preferred mode is available.
Also install XSetErrorHandler at the top of wl_display_open so the monitor
process's crash-dialog BadMatch (from sway's internal XWayland at :0) is
suppressed rather than printed in Xlib default format.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The Wayland backend runs with a live XWayland Display* once XWayland
fires its ready signal. Without an error handler, benign X errors
(BadMatch on X_SetInputFocus, BadWindow during window teardown) reach
Xlib's default handler and produce noisy output.
Add wl_catch_xwayland_error() — same permissive policy as the X11
backend's x11_catch_error() — and install it via XSetErrorHandler in
wl_register_error_handlers(). Include <X11/Xproto.h> for the
X_SetInputFocus request code constant.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two bugs prevented XWayland client windows from appearing:
1. flags.mapped never set for XWayland windows (window.c)
The condition `else if (!dpy)` is false when dpy is the XWayland
Display* (it is only true for pure native-Wayland mode with no X11).
Changed to `else if (!dpy || wm_backend->is_wayland)` so XWayland
windows also get flags.mapped=1 after wManageWindow, matching the
semantics of XDG toplevel windows.
2. scene node positioned at xw->x,xw->y before configure round-trip
handle_xwayland_map used xw->x,xw->y for the initial scene node
position. These values may not yet reflect the configure sent in
handle_new_xwayland_surface (the X11 round-trip is async). Changed
to use view->x,view->y which are set to output/4 in
handle_new_xwayland_surface and kept in sync by set_geometry listener.
Also added a log line when xw->surface is NULL at map time so the cause
is diagnosable from wmaker stderr.
Note: if xterm connects to the wrong DISPLAY (e.g. Sway's :1 instead of
wmaker's :2), no handle_xwayland_map fires at all. Check wmaker's
"XWayland ready on :X" log line for the correct display number.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Workspace switching (wl_window_map / wl_window_unmap):
wl_window_map and wl_window_unmap were stubs. Both now look up the
wl_toplevel_view by its WNativeWindow ID and call
wlr_scene_node_set_enabled(scene_node, true/false). The policy layer
(workspace.c wWorkspaceForceChange) already calls wWindowMap/wWindowUnmap
for each window on workspace switch, so no policy changes are needed —
the scene node enable/disable is the only missing piece.
Dock icon rendering (Phase 30+):
Added a 32-entry dock_apps[] array to wl_compositor_state tracking
each managed window's ID and ARGB8888 icon pixels.
wm_dock_load_icon() reads _NET_WM_ICON from XWayland windows via
get_window_image_from_x11() (wmspec.c), converts wrlib RImage RGBA
bytes to ARGB8888, and returns a heap buffer. XDG native Wayland
clients (no X11 connection) get NULL → empty tile placeholder.
wm_dock_refresh() repaints the dock background + tiles, then for each
managed app composites its icon into the tile using pixman BILINEAR
scaling (icon_w × icon_h → tile_sz-2 inset area), then calls
wm_shm_surface_repaint() which destroys and recreates the scene_buffer
node to force wlroots 0.15 to upload the fresh texture on the next
frame (wlroots caches textures by wlr_buffer pointer; destroy+recreate
is the safe invalidation path for this API version).
wm_dock_app_add() is called from handle_toplevel_map and
handle_xwayland_map after wManageWindow succeeds.
wm_dock_app_remove() is called from handle_toplevel_unmap and
handle_xwayland_unmap before wUnmanageWindow.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>