Fix input dispatch: custom event loop + modal menu support

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.
This commit is contained in:
Levi Neely 2026-05-13 16:22:13 +02:00
parent ac739a1a5c
commit d93f4935ca
5 changed files with 52 additions and 20 deletions

View File

@ -150,7 +150,12 @@ wl_check_typed_window(void *ectx, WNativeWindow w, int type, WMEvent *ev)
static void
wl_flush(void *ectx, Bool discard)
{
(void)ectx; (void)discard;
WLEventCtx *ctx = ectx;
(void)discard;
/* Pump the wlroots event loop so pointer/key events arrive in our queue.
* This is called from waitForEvent/WMMaskEvent when the queue is empty. */
if (ctx->dispatch_fn)
ctx->dispatch_fn(0, ctx->dispatch_ud);
}
static int

View File

@ -194,12 +194,13 @@
#include "../../icon.h" /* WIcon, TILE_* */
#include "../../appicon.h" /* WAppIcon */
#include "../../menu.h" /* WMenu, WMenuEntry */
#include "../../balloon.h" /* WBalloon */
#include "../backend.h"
#include "wl_backend.h"
/* ------------------------------------------------------------------ */
/* WINGs event backend interface (declared in WINGs/WINGsP.h) */
/* Forward-declared here to avoid pulling the full private header. */
/* WINGs private header — needed for W_EventBackend, W_Screen fields */
#include <WINGs/WINGsP.h>
/* ------------------------------------------------------------------ */
#include <WINGs/WMEvent.h>
struct W_Screen;
@ -364,6 +365,7 @@ struct wl_compositor_state {
struct wl_event_source *x11_event_source; /* wl_event_loop fd watcher for x_display */
pthread_t startup_pump_thread; /* pumps Wayland event loop during StartUp() */
volatile int startup_pump_running; /* 1 while pump thread is active */
volatile int terminate; /* set to exit main loop */
/* Phase 20: real input devices from nested backend + their listeners */
struct wlr_input_device *real_keyboard_dev;
struct wlr_input_device *real_pointer_dev;
@ -545,8 +547,7 @@ wl_shutdown_pipe_handler(int fd, uint32_t mask, void *data)
/* drain the pipe so the fd does not stay readable */
while (read(fd, buf, sizeof(buf)) > 0)
;
if (wl_state.display)
wl_display_terminate(wl_state.display);
wl_state.terminate = 1;
return 0;
}
@ -554,13 +555,12 @@ static int
frame_timer_cb(void *data)
{
(void)data;
/* Skip wlroots rendering calls while the startup pump thread is running —
* wlr_output_send_frame involves EGL context operations that are not safe
* to call from a non-main thread. */
if (!wl_state.startup_pump_running) {
if (!wl_state.is_nested && wl_state.output)
wlr_output_send_frame(wl_state.output);
}
if (wl_state.frame_timer)
wl_event_source_timer_update(wl_state.frame_timer, 16);
return 0;
@ -2797,9 +2797,8 @@ wl_signalfd_handler(int fd, uint32_t mask, void *data)
/* Set the exit state flag so post-loop cleanup paths see it. */
SIG_WCHANGE_STATE(WSTATE_NEED_EXIT);
/* Terminate the event loop from within loop context. */
if (wl_state.display)
wl_display_terminate(wl_state.display);
/* Terminate the event loop. */
wl_state.terminate = 1;
return 0;
}
@ -2919,7 +2918,33 @@ wl_event_loop_run(void)
}
wwarning("wl_backend: event loop running");
wl_display_run(wl_state.display);
/* Custom event loop: pump wlroots, then drain WINGs events.
* We can't use wl_display_run() because WM event handlers (OpenRootMenu,
* etc.) call WMMaskEvent which re-enters the wlroots loop. By draining
* WINGs events BETWEEN dispatches (not inside a timer callback), modal
* loops in WMMaskEvent can safely call wl_event_loop_dispatch. */
{
struct wl_event_loop *loop = wl_display_get_event_loop(wl_state.display);
while (!wl_state.terminate) {
wl_display_flush_clients(wl_state.display);
wl_event_loop_dispatch(loop, 16);
/* Drain WINGs event queue */
WMScreen *wmscr = wl_get_wmscreen();
if (wmscr) {
const W_EventBackend *be = wmscr->eventBackend;
void *ectx = wmscr->eventBackendCtx;
WMEvent wev;
int limit = 64;
while (be->pending(ectx) && limit-- > 0) {
be->fetch(ectx, &wev);
WMHandleEvent(&wev);
}
}
}
}
wwarning("wl_backend: event loop exited");
}
@ -6414,7 +6439,10 @@ static void wl_noop_icon_select(WIcon *icon) { (void)icon; }
static void wl_noop_icon_change_title(WIcon *icon, WWindow *wwin) { (void)icon; (void)wwin; }
static void wl_clip_icon_paint(WAppIcon *aicon) { wl_appicon_paint(aicon); }
static void wl_drawer_icon_paint(WAppIcon *aicon) { wl_appicon_paint(aicon); }
static void wl_noop_balloon_init(WScreen *scr) { (void)scr; }
static void wl_balloon_init(WScreen *scr)
{
scr->balloon = wmalloc(sizeof(WBalloon));
}
static void wl_noop_do_kaboom(WScreen *scr, WNativeWindow win, int x, int y)
{ (void)scr; (void)win; (void)x; (void)y; }
static void wl_menu_paint_entry(WMenu *menu, int index, int selected)
@ -6887,7 +6915,7 @@ const WMBackend wl_backend = {
.icon_change_title = wl_noop_icon_change_title,
.clip_icon_paint = wl_clip_icon_paint,
.drawer_icon_paint = wl_drawer_icon_paint,
.balloon_init = wl_noop_balloon_init,
.balloon_init = wl_balloon_init,
.do_kaboom = wl_noop_do_kaboom,
.menu_paint_entry = wl_menu_paint_entry,
.menu_draw_frame = wl_menu_draw_frame,

View File

@ -262,7 +262,7 @@ void wBalloonEnteredObject(WScreen *scr, WObjDescriptor *object)
void wBalloonHide(WScreen *scr)
{
if (scr) {
if (scr && scr->balloon) {
if (scr->balloon->mapped) {
wm_backend->window_unmap((WNativeWindow)scr->balloon->window);
scr->balloon->mapped = 0;

View File

@ -149,6 +149,7 @@ static void wdelete_death_handler(WMagicNumber id)
void DispatchEvent(WMEvent * event)
{
if (deathHandlers)
handleDeadProcess();

View File

@ -379,11 +379,9 @@ void StartUp(Bool defaultScreenOnly)
/* handle X shutdowns and errors via backend */
wm_backend->register_error_handlers();
/* set hook for our event dispatcher in WINGs event dispatcher (X11 only).
* Skip for Wayland: we run a wlroots event loop, not a WINGs/X11 loop.
* dpy may be set (XWayland bridge), but X11 event dispatch is not used. */
if (!wm_backend->is_wayland)
WMHookEventHandler(DispatchEvent);
/* set hook for our event dispatcher in WINGs event dispatcher.
* In Wayland mode, events are drained from the frame timer. */
WMHookEventHandler(DispatchEvent);
/* initialize defaults stuff */
w_global.domain.wmaker = wDefaultsInitDomain("WindowMaker", True);