Widgets that paint directly to their view backing (fill_rect,
draw_line, W_DrawRelief, W_PaintText) without going through the
DOUBLE_BUFFER pixmap_copy path never triggered a surface commit.
The painted content stayed in the pixman buffer but was never
submitted to the compositor — making popup menus, radio buttons,
text fields, sliders, and other controls appear empty/stale.
Fix: W_WLClientCommitAllToplevels() iterates all mapped toplevels
and commits their surfaces. Called from wl_flush() which runs once
per event loop iteration (in waitForEvent). This ensures any
direct-paint operations become visible on the next frame regardless
of which render path was used.
Both were no-op stubs returning 0/False. Editmenu uses these to
compute the original screen position of an item before drag-and-drop,
then animates the item back to that position via slideWindow on
revert. With both returning 0, the slide animation moved popups
from where they were to (0, 0) instead of back to their origin —
producing the 'menus moving on their own' effect.
translate_coordinates: walk up from src view accumulating pos.x/y
to compute the position relative to the toplevel.
get_window_geometry: return the view's pos/size directly.
paintMenuEntry was only called for the highlighted item on expose.
On X11 the X server auto-filled the rest of the window with the
background pixel, so unpainted items looked grey-but-empty. On
Wayland the buffer starts transparent — unpainted items showed
through to whatever was behind, making the popup invisible.
Iterate all items in the expose handler so the menu's full content
is visible when it first appears.
When a popup toplevel is destroyed (e.g., menu editor submenu closes),
its WLClientToplevel struct is freed. If wlc_kbd_focused or
wlc_ptr_focused pointed to it, subsequent input events dereferenced
the dangling pointer and crashed (saw it in keyboard_handle_leave).
Clear both refs in wl_client_view_destroy_backing before freeing.
Two issues:
1. Popups rendered behind their parent toplevel because the scene node
wasn't raised on apply. Add wlr_scene_node_raise_to_top so popups
appear above the parent.
2. When the parent toplevel moved, popups stayed at their old screen
position. In wl_window_move (compositor-side), after a regular
toplevel's position changes, iterate all popups and re-apply those
whose popup_parent is this toplevel so they track the parent.
paintEditMenuItem always filled with scr->white. On X11 the per-window
background was rendered by the X server based on WMSetWidgetBackgroundColor.
On Wayland we have to draw it ourselves.
Use the view's stored backColor if set; default to white otherwise.
Also set the default background of non-title items to grey at creation
time (previously only title items got an explicit colour, so non-title
items had a NULL backColor and rendered white).
editmenu.c switched on event->type (which is a WMEventType enum) but
the case labels used X11 event names (Expose, DestroyNotify, ButtonPress,
ButtonRelease, MotionNotify). These are different numeric values, so
no case ever matched — menu items never painted, never received clicks,
never handled drag.
Replace with WME_EXPOSE, WME_DESTROY, WME_BUTTON_PRESS, WME_BUTTON_RELEASE,
WME_MOTION.
The previous version had set_position(x, y) in compositor-global
coordinates, but Wayland clients don't know their toplevel's screen
position — the editmenu and popup button code naturally compute
positions relative to the parent toplevel via accumulated view->pos.
Add set_parent(xdg_toplevel) request. When a parent is set,
set_position(x, y) is interpreted as offsets from the parent
toplevel's top-left corner.
Compositor side resolves the parent's wl_toplevel_view via
wlr_xdg_toplevel_from_resource → wl_find_view_by_xdg_surface,
then adds parent->x and parent->y to the requested offsets.
Client side (wclient_wl.c) tracks the first non-popup toplevel as
ctx->main_toplevel and calls set_parent on every popup wrapper to
anchor it to the application's main window.
Generated *-protocol.c and *-server-protocol.h files were accidentally
committed in the previous commit. The .gitignore was also excluding
the protocols/ source directory (where the XML lives). Fix both.
Standard xdg_shell does not allow Wayland clients to position toplevels —
the compositor decides placement. WINGs UI patterns require client-driven
positioning (e.g., a dropdown popup must appear directly below its trigger
button; menu editor items must be placed by the client at specific screen
coordinates).
This protocol lets the client wrap an xdg_toplevel with a wmaker_popup
object and call set_position(x, y) before the surface is mapped. The
compositor honours the requested position when the surface becomes visible
and on subsequent set_position calls.
Files:
- protocols/wmaker-popup-v1.xml: protocol definition
- src/Makefile.am, src/Makefile: server-protocol.h + protocol.c generation
- WINGs/Makefile.am, WINGs/Makefile: client-protocol.h + protocol.c generation
- src/backend/wayland/wl_backend.c: wmaker_popup_manager global, get_popup
handler that wraps an xdg_toplevel; set_position handler that updates
view->popup_x/y and calls wmaker_popup_apply to move the frame_buf
scene tree. Apply runs at toplevel_map time too for pre-map positions.
- WINGs/wclient_wl.c: bind manager in registry, wrap popup toplevels with
wmaker_popup, expose W_WLClientPopupSetPosition.
- WINGs/wview_wl.c: wl_move calls W_WLClientPopupSetPosition for popup
toplevels so WMMoveWidget propagates to the compositor.
- WINGs/wevent_wl.c: wl_move_window also routes to popup positioning.
WMPopUpButton's popUpMenu calls viewBackend->pointer_grab which was
NULL on Wayland — calling NULL function pointer crashed WPrefs when
clicking the popup dropdown. Provide no-op stubs (Wayland uses implicit
grab on button press).
Also mark W_CreateUnmanagedTopView views with flags.popup=1 so they
go through the same borderless/skip-taskbar path as editmenu standalone
windows.
Previous version used fprintf which buffers and may not flush before
_exit if the crash happened mid-libc. Use write() + fsync() so the
backtrace appears in the log even if the crash is deep in a library.
The WPrefs menu editor creates standalone draggable windows for each
menu item via W_CreateTopView. On X11 these are override-redirect
windows. On Wayland they were becoming framed XDG toplevels with
taskbar entries, cluttering the desktop with phantom 'WINGs...' tabs.
Implementation:
1. New W_View flags.popup field, set by editmenu's standalone mode
2. wclient_wl: when realizing a popup view, set xdg_toplevel app_id
to 'wings-popup' as a hint to the compositor
3. wmaker compositor: detect 'wings-popup' app_id in toplevel_map,
set borderless + skip-taskbar + skip-switchpanel flags, then call
wWindowConfigureBorders to rebuild the frame without decorations
4. New W_EventBackend->window_begin_move vtable method:
- X11: returns False (caller runs manual drag loop)
- Wayland: calls xdg_toplevel_move, returns True (compositor drives drag)
5. editmenu's dragMenu calls window_begin_move first; on Wayland the
compositor handles the entire drag, so the manual loop is skipped
WMEButton.x is at offset 40, WMEMotion.x is at offset 48. The
scroller widget reads event->u.button.x for motion events (legacy
X11 code where XButtonEvent and XMotionEvent have x at the same
offset). Set both union members so widgets reading either get
correct coordinates.
On X11, a button press creates an implicit pointer grab — all
subsequent motion and release events go to the window that received
the press, even if the pointer moves outside it. This is essential
for scroller knob dragging, window resize handles, etc.
Implement the same for Wayland client: on button press, record the
target view as wlc_ptr_grab. Motion events during the grab are
delivered to the grabbed view with coordinates translated relative
to it. On button release, deliver to the grabbed view and clear
the grab. Enter/leave synthesis is suppressed during grabs.
The viewport's event handler is registered for SUBSTRUCTURE_NOTIFY.
WMHandleEvent switches from STRUCTURE_NOTIFY to SUBSTRUCTURE_NOTIFY
when event.window != configure.event. The event was incorrectly
setting both to the parent window. Fix: set event.window to the
child (resized view) so the mismatch triggers the SubstructureNotify
path.
On X11, resizing a child window generates a ConfigureNotify event
delivered to the parent. Scroll views rely on this to update scroller
proportions when the content view is resized. On Wayland, wl_resize
never generated this event, so the scroller's documentFullyVisible
flag stayed at 1 (set during initial setup when content was small)
and paintScroller drew only a stippled 'full' indicator instead of
the knob/track.
Fix: when a mapped non-toplevel view is resized, dispatch a
WME_CONFIGURE event to its parent synchronously.
WMSetWindowMinSize/MaxSize were no-ops on Wayland. Implement
wl_window_set_geometry to call xdg_toplevel_set_min_size/max_size.
WPrefs sets min=max=520x390, making it non-resizable.
The configure event was enqueued for later processing but the redisplay
happened immediately, painting children at their old positions. Now
WMHandleEvent is called directly with the configure event so widget
delegates relayout children before the redisplay paints them.
Also removed scroller debug logging.
When the compositor sends a new size via xdg_surface.configure, the
client buffer was reallocated but content was never repainted into it.
The surface was committed with an empty buffer, making the window
content disappear after resize.
Fix: after resizing the buffer in xdg_surface_handle_configure, call
wl_redisplay on the toplevel view to repaint all widget content into
the new buffer before committing.
Non-toplevel views mapped after the toplevel (e.g., icon buttons added
during Init* calls) need to trigger a redisplay to become visible.
Instead of redisplaying the view itself (which crashed during
realization), find the toplevel ancestor and redisplay from there.
Guards:
- All ancestors must be realized (prevents crash during W_RealizeView)
- Toplevel must be mapped (prevents premature redisplay)
- wl_redisplay_depth==0 (prevents re-entrancy)
The parent-backing-check approach was too fragile. Simplify: wl_map
only triggers wl_redisplay for toplevel views. Non-toplevel views
become visible through:
1. render_wl_pixmap_copy composite-up (when widget paints itself)
2. wl_map_subviews (which redisplays the parent after mapping children)
3. Parent's recursive wl_redisplay (which iterates all mapped children)
This avoids crashes during W_RealizeView recursion where children are
mapped before the widget tree is fully constructed.
During W_RealizeView recursion, children are realized and mapped
before their parent's backing pixman image exists. wl_map triggered
wl_redisplay which sent an expose event, the widget painted itself,
and render_wl_pixmap_copy tried to composite up through the parent
chain — hitting a NULL pixman image in pixman_image_composite32.
Fix: before triggering wl_redisplay from wl_map, walk up the parent
chain and verify all ancestors have valid backings. If any ancestor
lacks a backing (still being realized), skip the redisplay — the
parent's wl_map will handle it once the full tree is ready.
WindowHandling.c and Icons.c called WidthOfScreen(DefaultScreenOfDisplay(
WMScreenDisplay(scr))) which dereferences NULL on Wayland (no X Display).
Use WMScreenWidth/WMScreenHeight instead — these work on both backends.
Also added temporary SIGSEGV backtrace handler to main.c for debugging.
When a panel's createWidgets callback maps child widgets, each
WMMapWidget triggered wl_redisplay which sent expose events. Those
expose handlers could create/destroy more widgets, causing re-entrant
modification of the view tree during iteration — segfault.
Fix: track wl_redisplay call depth. wl_map only triggers wl_redisplay
when depth==0 (not already inside a redisplay). Child views mapped
during a redisplay cycle will be painted by the parent's recursive
child iteration instead.
Also save nextSister before recursing into children to handle list
modifications during iteration.
When a button click triggers changeSection which destroys widgets
(nameL, versionL, statusL) and then maps new ones, the recursive
wl_redisplay and the composite-up in render_wl_pixmap_copy could
encounter views marked alreadyDead or with freed backings.
Add alreadyDead checks to:
- wl_redisplay entry (skip dead views)
- wl_redisplay child iteration (skip dead children)
- render_wl_pixmap_copy composite-up loop (stop at dead views)
The inner 'double sx, sy' declaration shadowed the outer one. When
view_surface_at succeeded inside the frame_buf check, the surface-local
coordinates were written to the inner variables and lost. The outer
sx/sy (used by wlr_seat_pointer_notify_enter/motion) remained
uninitialized, causing the client to receive garbage coordinates.
Also removed debug print from wclient_wl.c.
frame_buf_at() matches the entire window frame tree including the
client content area. This caused wl_update_pointer_focus to never
set seat pointer focus on the client surface, so Wayland clients
(like WPrefs) never received pointer enter/motion/button events.
Fix: when frame_buf_at returns a hit, also check view_surface_at.
If a client surface is found at the pointer position, use it instead
of treating the area as WM-internal decorations.
On X11, painting to a window pixmap (via XCopyArea in the DOUBLE_BUFFER
path) makes content immediately visible because the X server handles
display. On Wayland, writing to a view's backing pixman image has no
effect until it's composited up through the parent chain to the toplevel
surface and committed.
When render_wl_pixmap_copy detects the destination is a registered view
backing (ID in 0x10000000-0x2FFFFFFF range), it now walks up the parent
chain compositing each view into its parent, then commits the toplevel.
This makes widget paint operations (labels, buttons, frames) immediately
visible without requiring a full wl_redisplay cycle.
Rendering: wl_map only triggered wl_redisplay for topLevel views.
Non-toplevel views (buttons, frames, labels) were mapped but never
painted. Now wl_map calls wl_redisplay for any realized view.
wl_redisplay already recurses into children, so wl_map_subviews is
simplified to just set mapped flags then redisplay the parent.
Input: pointer events were delivered to the toplevel view because
Wayland has a single surface per window (unlike X11 where each widget
has its own window). Added hit-testing via W_FindViewAtPoint to route
button press/release and motion events to the correct child view.
Also synthesizes enter/leave events as the pointer moves between
child views (needed for button highlight on hover).
wl_redisplay fills the view's background then paints widget content
via expose, but never re-composited child views. Children painted
earlier were erased by the parent's background fill. Now recursively
calls wl_redisplay on all mapped+realized children after the parent's
expose handler, so the full widget tree composites bottom-up.
WPrefs passes display_name="" by default. wl_display_connect("")
treats the empty string as a literal socket name and fails. Pass NULL
instead so it uses $WAYLAND_DISPLAY. Also handle the literal string
"wayland" (from detect matching) the same way.
wlclient_open passed NULL as the RContext, causing a segfault in
RConvertImageMask (called from makePixmap during screen init) when
WPrefs.app launched under Wayland.
Fix: call RCreateContextWayland() to create a valid 32-bit ARGB
context before passing it to WMCreateScreenWithRContext.
The user-facing root menu's 'Background → Images → Tiled' menu item runs
'wmsetbg -u -t image.jpg', which internally invokes wdwrite TWICE in
rapid succession:
1. wdwrite WindowMaker SmoothWorkspaceBack NO
2. wdwrite WindowMaker WorkspaceBack '(tpixmap, ...)'
Both writes complete within the same wall-clock second. The previous
mtime check used time_t (1-second precision):
if (timestamp < stbuf.st_mtime)
After the first write, wmaker reads the file (only SmoothWorkspaceBack
changed; WorkspaceBack still old) and sets timestamp = current second.
The second write happens in the same second, so st_mtime equals
timestamp exactly — the check fails and the second write is silently
ignored. The user has to invoke the menu twice for the change to apply.
Fix: WDDomain.timestamp is now struct timespec (nanosecond precision).
New MTIME_NEWER() macro compares full timespec values. Two writes within
the same second now have distinct nanosecond components so both are
detected.
Also removed all debug logging.
The atomic-rename pattern (mkstemp + rename) generates two relevant
events per save: IN_CLOSE_WRITE on the temp file (e.g.
WindowMaker.AjuyyW), then IN_MOVED_TO on the destination (WindowMaker).
Previously we reacted to IN_CLOSE_WRITE first and set oneShotFlag,
which then suppressed the IN_MOVED_TO. But at the time of
IN_CLOSE_WRITE, the rename hadn't happened yet — stat(WindowMaker)
returned the OLD mtime — so we'd skip and never re-check on the
subsequent IN_MOVED_TO either.
Fix: skip events on temp files (filenames containing a dot) and
react on IN_MOVED_TO for the actual destination filename. Direct
in-place writes (rare) are still handled via IN_CLOSE_WRITE for
non-temp filenames.
Also removed debug logging.
WMWritePropListToFile uses an atomic write pattern: write to a temp
file, fsync, fclose, then rename to the destination. The inotify
directory watch sees:
1. IN_CLOSE_WRITE on the temp file (WindowMaker.XXXXXX)
2. IN_MOVED_TO on WindowMaker (the rename)
We were only handling IN_CLOSE_WRITE, but at that point the rename
hadn't happened yet, so stat(WindowMaker) returned the OLD mtime
and we skipped. The NEXT save's IN_CLOSE_WRITE would then catch
the PREVIOUS save's mtime change — hence the 'one click behind'
pattern users observed.
Now handles IN_MOVED_TO too, catching the actual atomic replacement.
Also removed debug logging.
IN_MODIFY fires during the write, before the file is fully written.
The Wayland event loop's 16ms dispatch catches this almost instantly,
reading stale/partial content. IN_CLOSE_WRITE fires after the writer
closes the file, guaranteeing the content is complete.
This fixes the 'one step behind' issue where background changes only
appeared on the second apply.
Two issues:
1. bg_image scene node wasn't re-enabled when switching from solid
color back to an image background
2. No frame was scheduled after background change, so the compositor
wouldn't repaint until the next natural frame tick
Now explicitly enables the bg_image node and calls
wlr_output_schedule_frame() after any background change.
- tpixmap (Tiled): repeat image across screen
- spixmap (Scaled): stretch to exact screen dimensions
- cpixmap (Centered): place at center with background color fill
- mpixmap (Maximized): scale preserving aspect ratio, letterbox
- fpixmap (Filled): scale preserving aspect ratio, crop to fill
All modes now use RSmoothScaleImage for quality, respect the
background color (3rd texture argument) for uncovered areas, and
handle transparent images via RCombineImageWithColor.
The named color table only had basic colors (black, white, red, etc.)
but Window Maker backgrounds use extended X11 color names like
deepskyblue4, tomato4, blue4. Now falls back to parsing
/usr/share/X11/rgb.txt for any unrecognized color name.
Fixes Sunset and other multi-color gradient backgrounds that use
X11 named colors.
The color parser only handled #RRGGBB and rgb:XX/XX/XX formats, but
Window Maker backgrounds use X11 named colors (deepskyblue4, black,
tomato4, etc.). Now uses wGetColor() which resolves any color format
through the backend's color lookup.
Also: solid backgrounds now update the scene rect directly (faster,
no buffer allocation needed).
Added mvgradient/mhgradient/mdgradient handling using wrlib's
RRenderMultiGradient(). This covers presets like 'Sunset' which use
more than 2 color stops.
Also cleaned up color parsing (removed empty else branches) and
removed debug logging.