From ca09635129c821f8d286834d283d2430c86b27aa Mon Sep 17 00:00:00 2001 From: Levi Neely Date: Sat, 28 Mar 2026 06:23:23 +0100 Subject: [PATCH] =?UTF-8?q?wayland:=20Phase=2039=20=E2=80=94=20wire=20topl?= =?UTF-8?q?evel=20map=20to=20wManageWindow?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Guard all dpy==NULL crash sites reached from wManageWindow: - wNETWMGetPidForWindow (wmspec.c) — XGetWindowProperty with NULL dpy - wNETWMCheckClientHints (wmspec.c) — XGetWindowProperty - wNETWMCheckInitialClientState (wmspec.c) — XChangeProperty chain - getMWMHints (motif.c) — XInternAtom before NULL dpy check - wClientSetState (client.c) — XChangeProperty - wColormapInstallForWindow (colormap.c) — XGetWindowAttributes - wWindowSetKeyGrabs (window.c) — frame->core->window dereference Wire handle_toplevel_map → wManageWindow(scr, view->id) so mapped XDG toplevels appear in scr->focused_window. Log success via "wl_backend: wManageWindow succeeded id=0x%lx". Smoke test: add Phase 39 check — grep "wManageWindow succeeded". Co-Authored-By: Claude Sonnet 4.6 --- src/backend/wayland/wl_backend.c | 13 ++- src/client.c | 5 + src/colormap.c | 3 + src/motif.c | 3 + src/window.c | 187 ++++++++++++++++--------------- src/wmspec.c | 9 ++ tests/wl-smoke-test.sh | 7 ++ 7 files changed, 133 insertions(+), 94 deletions(-) diff --git a/src/backend/wayland/wl_backend.c b/src/backend/wayland/wl_backend.c index 48b3ebe..2799901 100644 --- a/src/backend/wayland/wl_backend.c +++ b/src/backend/wayland/wl_backend.c @@ -15,6 +15,8 @@ * sent on new toplevel so clients complete their first frame. * Phase 38: per-toplevel view struct with map/unmap/destroy lifecycle; * synthetic WNativeWindow IDs; context_save/find/delete implemented. + * Phase 39: handle_toplevel_map wires XDG toplevel map to wManageWindow so + * Wayland surfaces appear in scr->focused_window list. */ #ifdef HAVE_CONFIG_H @@ -143,8 +145,15 @@ handle_toplevel_map(struct wl_listener *listener, void *data) view->xdg_surface->toplevel->app_id ? view->xdg_surface->toplevel->app_id : "(none)", view->xdg_surface->toplevel->title ? view->xdg_surface->toplevel->title : "(none)"); - /* TODO Phase 39: call wManageWindow(scr, view->id) once context - * store can resolve the synthetic ID to this view. */ + /* Phase 39: register the surface as a managed window. */ + { + WScreen *scr = wScreenWithNumber(0); + if (scr) { + WWindow *wwin = wManageWindow(scr, view->id); + if (wwin) + wwarning("wl_backend: wManageWindow succeeded id=0x%lx", view->id); + } + } } /* diff --git a/src/client.c b/src/client.c index ce63a64..34f48d9 100644 --- a/src/client.c +++ b/src/client.c @@ -87,6 +87,9 @@ void wClientSetState(WWindow * wwin, int state, Window icon_win) wwin->state = state; + if (!dpy) + return; /* Wayland: no X11 WM_STATE property */ + data[0] = (unsigned long)state; data[1] = (unsigned long)icon_win; @@ -720,6 +723,8 @@ wClientGetNormalHints(WWindow * wwin, WClientAttributes * wattribs, int geometry void GetColormapWindows(WWindow * wwin) { #ifndef NO_CRASHES + if (!dpy) + return; /* Wayland: no X11 colormaps */ if (wwin->cmap_windows) { XFree(wwin->cmap_windows); } diff --git a/src/colormap.c b/src/colormap.c index d283bf9..6fb81cd 100644 --- a/src/colormap.c +++ b/src/colormap.c @@ -38,6 +38,9 @@ void wColormapInstallForWindow(WScreen * scr, WWindow * wwin) int done = 0; Window xwin = None; + if (!dpy) + return; /* Wayland: no X11 colormaps */ + if (wwin) { xwin = wwin->client_win; } else { diff --git a/src/motif.c b/src/motif.c index 64c8203..59ce971 100644 --- a/src/motif.c +++ b/src/motif.c @@ -162,6 +162,9 @@ static int getMWMHints(Window window, MWMHints *mwmhints) unsigned long *data; int count; + if (!dpy) + return 0; /* Wayland: no X11 Motif hints */ + if (!_XA_MOTIF_WM_HINTS) _XA_MOTIF_WM_HINTS = XInternAtom(dpy, "_MOTIF_WM_HINTS", False); diff --git a/src/window.c b/src/window.c index 417abd2..4262f75 100644 --- a/src/window.c +++ b/src/window.c @@ -1082,94 +1082,104 @@ WWindow *wManageWindow(WScreen *scr, WNativeWindow window) wwin->flags.omnipresent ^= 1; } - /* Create frame, borders and do reparenting */ - foo = WFF_LEFT_BUTTON | WFF_RIGHT_BUTTON; + /* Create frame, borders and do reparenting (X11 only) */ + if (dpy) { + foo = WFF_LEFT_BUTTON | WFF_RIGHT_BUTTON; #ifdef XKB_BUTTON_HINT - if (wPreferences.modelock) - foo |= WFF_LANGUAGE_BUTTON; + if (wPreferences.modelock) + foo |= WFF_LANGUAGE_BUTTON; #endif - if (HAS_TITLEBAR(wwin)) - foo |= WFF_TITLEBAR; + if (HAS_TITLEBAR(wwin)) + foo |= WFF_TITLEBAR; - if (HAS_RESIZEBAR(wwin)) - foo |= WFF_RESIZEBAR; + if (HAS_RESIZEBAR(wwin)) + foo |= WFF_RESIZEBAR; - if (HAS_BORDER(wwin)) - foo |= WFF_BORDER; + if (HAS_BORDER(wwin)) + foo |= WFF_BORDER; - wwin->frame = wFrameWindowCreate(scr, window_level, - x, y, width, height, - &wPreferences.window_title_clearance, - &wPreferences.window_title_min_height, - &wPreferences.window_title_max_height, - foo, - scr->window_title_texture, - scr->resizebar_texture, scr->window_title_color, &scr->title_font, - wvisual.depth, wvisual.visual, (Colormap)wvisual.colormap); + wwin->frame = wFrameWindowCreate(scr, window_level, + x, y, width, height, + &wPreferences.window_title_clearance, + &wPreferences.window_title_min_height, + &wPreferences.window_title_max_height, + foo, + scr->window_title_texture, + scr->resizebar_texture, scr->window_title_color, &scr->title_font, + wvisual.depth, wvisual.visual, (Colormap)wvisual.colormap); - wwin->frame->flags.is_client_window_frame = 1; - wwin->frame->flags.justification = wPreferences.title_justification; + wwin->frame->flags.is_client_window_frame = 1; + wwin->frame->flags.justification = wPreferences.title_justification; - wNETWMCheckInitialFrameState(wwin); + wNETWMCheckInitialFrameState(wwin); - /* setup button images */ - wWindowUpdateButtonImages(wwin); + /* setup button images */ + wWindowUpdateButtonImages(wwin); - /* hide unused buttons */ - foo = 0; - if (WFLAGP(wwin, no_close_button)) - foo |= WFF_RIGHT_BUTTON; + /* hide unused buttons */ + foo = 0; + if (WFLAGP(wwin, no_close_button)) + foo |= WFF_RIGHT_BUTTON; - if (WFLAGP(wwin, no_miniaturize_button)) - foo |= WFF_LEFT_BUTTON; + if (WFLAGP(wwin, no_miniaturize_button)) + foo |= WFF_LEFT_BUTTON; #ifdef XKB_BUTTON_HINT - if (WFLAGP(wwin, no_language_button) || WFLAGP(wwin, no_focusable)) - foo |= WFF_LANGUAGE_BUTTON; + if (WFLAGP(wwin, no_language_button) || WFLAGP(wwin, no_focusable)) + foo |= WFF_LANGUAGE_BUTTON; #endif - if (foo != 0) - wFrameWindowHideButton(wwin->frame, foo); + if (foo != 0) + wFrameWindowHideButton(wwin->frame, foo); - wwin->frame->child = wwin; - wwin->frame->workspace = workspace; - wwin->frame->on_click_left = windowIconifyClick; + wwin->frame->child = wwin; + wwin->frame->workspace = workspace; + wwin->frame->on_click_left = windowIconifyClick; #ifdef XKB_BUTTON_HINT - if (wPreferences.modelock) - wwin->frame->on_click_language = windowLanguageClick; + if (wPreferences.modelock) + wwin->frame->on_click_language = windowLanguageClick; #endif - wwin->frame->on_click_right = windowCloseClick; - wwin->frame->on_dblclick_right = windowCloseDblClick; - wwin->frame->on_mousedown_titlebar = titlebarMouseDown; - wwin->frame->on_dblclick_titlebar = titlebarDblClick; - wwin->frame->on_mousedown_resizebar = resizebarMouseDown; + wwin->frame->on_click_right = windowCloseClick; + wwin->frame->on_dblclick_right = windowCloseDblClick; + wwin->frame->on_mousedown_titlebar = titlebarMouseDown; + wwin->frame->on_dblclick_titlebar = titlebarDblClick; + wwin->frame->on_mousedown_resizebar = resizebarMouseDown; - wm_backend->window_select_events(wwin->client_win, - wwin->event_mask & ~StructureNotifyMask); - wm_backend->client_reparent(wwin->client_win, wwin->frame->core->window, - 0, wwin->frame->top_width); - wm_backend->window_select_events(wwin->client_win, wwin->event_mask); + wm_backend->window_select_events(wwin->client_win, + wwin->event_mask & ~StructureNotifyMask); + wm_backend->client_reparent(wwin->client_win, wwin->frame->core->window, + 0, wwin->frame->top_width); + wm_backend->window_select_events(wwin->client_win, wwin->event_mask); - { - int gx, gy; + { + int gx, gy; - wClientGetGravityOffsets(wwin, &gx, &gy); + wClientGetGravityOffsets(wwin, &gx, &gy); - /* if gravity is to the south, account for the border sizes */ - if (gy > 0) - y -= wwin->frame->top_width + wwin->frame->bottom_width; + /* if gravity is to the south, account for the border sizes */ + if (gy > 0) + y -= wwin->frame->top_width + wwin->frame->bottom_width; + } + + /* + * wWindowConfigure() will init the client window's size + * (wwin->client.{width,height}) and all other geometry + * related variables (frame_x,frame_y) */ + wWindowConfigure(wwin, x, y, width, height); + + /* to make sure the window receives it's new position after reparenting */ + wWindowSynthConfigureNotify(wwin); + } else { + /* Wayland: record geometry from normal hints; frame stays NULL */ + wwin->client.x = x; + wwin->client.y = y; + wwin->client.width = width; + wwin->client.height = height; + wwin->frame_x = x; + wwin->frame_y = y; } - /* - * wWindowConfigure() will init the client window's size - * (wwin->client.{width,height}) and all other geometry - * related variables (frame_x,frame_y) */ - wWindowConfigure(wwin, x, y, width, height); - - /* to make sure the window receives it's new position after reparenting */ - wWindowSynthConfigureNotify(wwin); - /* Setup descriptors and save window to internal lists */ if (wwin->main_window != None) { WApplication *app; @@ -1205,10 +1215,12 @@ WWindow *wManageWindow(WScreen *scr, WNativeWindow window) } } - /* setup the frame descriptor */ - wwin->frame->core->descriptor.handle_mousedown = frameMouseDown; - wwin->frame->core->descriptor.parent = wwin; - wwin->frame->core->descriptor.parent_type = WCLASS_WINDOW; + if (wwin->frame) { + /* setup the frame descriptor */ + wwin->frame->core->descriptor.handle_mousedown = frameMouseDown; + wwin->frame->core->descriptor.parent = wwin; + wwin->frame->core->descriptor.parent_type = WCLASS_WINDOW; + } /* don't let windows go away if we die */ wm_backend->client_add_to_save_set(window); @@ -1227,35 +1239,22 @@ WWindow *wManageWindow(WScreen *scr, WNativeWindow window) else wClientSetState(wwin, NormalState, None); - if (wPreferences.superfluous && !wPreferences.no_animations && !scr->flags.startup && + if (wwin->frame && wPreferences.superfluous && !wPreferences.no_animations && !scr->flags.startup && (wwin->transient_for == None || wwin->transient_for == scr->root_win) && - /* - * The brain damaged idiotic non-click to focus modes will - * have trouble with this because: - * - * 1. window is created and mapped by the client - * 2. window is mapped by wmaker in small size - * 3. window is animated to grow to normal size - * 4. this function returns to normal event loop - * 5. eventually, the EnterNotify event that would trigger - * the window focusing (if the mouse is over that window) - * will be processed by wmaker. - * But since this event will be rather delayed - * (step 3 has a large delay) the time when the event occurred - * and when it is processed, the client that owns that window - * will reject the XSetInputFocus() for it. - */ (wPreferences.focus_mode == WKF_CLICK || wPreferences.auto_focus)) DoWindowBirth(wwin); - wWindowMap(wwin); + if (wwin->frame) + wWindowMap(wwin); } /* setup stacking descriptor */ - if (transientOwner) - wwin->frame->core->stacking->child_of = transientOwner->frame->core; - else - wwin->frame->core->stacking->child_of = NULL; + if (wwin->frame) { + if (transientOwner) + wwin->frame->core->stacking->child_of = transientOwner->frame->core; + else + wwin->frame->core->stacking->child_of = NULL; + } if (!scr->focused_window) { /* first window on the list */ @@ -1277,7 +1276,7 @@ WWindow *wManageWindow(WScreen *scr, WNativeWindow window) /* raise is set to true if we un-hid the app when this window was born. * we raise, else old windows of this app will be above this new one. */ - if (raise) + if (raise && wwin->frame) wRaiseFrame(wwin->frame->core); /* Update name must come after WApplication stuff is done */ @@ -1288,7 +1287,8 @@ WWindow *wManageWindow(WScreen *scr, WNativeWindow window) wm_backend->server_ungrab(); /* Final preparations before window is ready to go */ - wFrameWindowChangeState(wwin->frame, WS_UNFOCUSED); + if (wwin->frame) + wFrameWindowChangeState(wwin->frame, WS_UNFOCUSED); if (!wwin->flags.miniaturized && workspace == scr->current_workspace && !wwin->flags.hidden) { if (((transientOwner && transientOwner->flags.focused) @@ -2317,6 +2317,9 @@ void wWindowSetKeyGrabs(WWindow * wwin) int i; WShortKey *key; + if (!wwin->frame) + return; /* Wayland: no frame window to grab keys on */ + for (i = 0; i < WKBD_LAST; i++) { key = &wKeyBindings[i]; diff --git a/src/wmspec.c b/src/wmspec.c index 9500e15..faa0d77 100644 --- a/src/wmspec.c +++ b/src/wmspec.c @@ -1536,6 +1536,9 @@ void wNETWMCheckClientHints(WWindow *wwin, int *layer, int *workspace) unsigned long nitems_ret, bytes_after_ret; long *data = NULL; + if (!dpy) + return; /* Wayland: no X11 NETWM properties */ + if (XGetWindowProperty(dpy, wwin->client_win, net_wm_desktop, 0, 1, False, WA_CARDINAL, &type_ret, &fmt_ret, &nitems_ret, &bytes_after_ret, (unsigned char **)&data) == Success && data) { @@ -1642,6 +1645,9 @@ void wNETWMCheckInitialClientState(WWindow *wwin) wmessage("wNETWMCheckInitialClientState"); #endif + if (!dpy) + return; /* Wayland: no X11 NETWM initial state */ + wNETWMShowingDesktop(wwin->screen_ptr, False); updateWindowType(wwin); @@ -1873,6 +1879,9 @@ int wNETWMGetPidForWindow(Window window) long *data = NULL; int pid; + if (!dpy) + return 0; /* Wayland: no _NET_WM_PID property */ + if (XGetWindowProperty(dpy, window, net_wm_pid, 0, 1, False, WA_CARDINAL, &type_ret, &fmt_ret, &nitems_ret, &bytes_after_ret, (unsigned char **)&data) == Success && data) { diff --git a/tests/wl-smoke-test.sh b/tests/wl-smoke-test.sh index 11c8f37..7005652 100755 --- a/tests/wl-smoke-test.sh +++ b/tests/wl-smoke-test.sh @@ -18,6 +18,8 @@ # loop (stays alive for 3 s, compositor still alive afterwards) # 8. (Phase 38) Per-toplevel view lifecycle: compositor logs "toplevel # mapped" when weston-simple-shm creates its XDG toplevel surface +# 9. (Phase 39) wManageWindow called for mapped XDG toplevel: compositor +# logs "wManageWindow succeeded" for the synthetic surface ID # # This test grows as wl_backend vtable slots are implemented. # @@ -148,6 +150,11 @@ if command -v weston-simple-shm >/dev/null 2>&1; then grep -q 'toplevel mapped' /tmp/wl-smoke-stderr \ || die "compositor did not log 'toplevel mapped' for weston-simple-shm" echo " lifecycle check: toplevel mapped event logged" + + # -- 9. Phase 39: wManageWindow must have succeeded ------------------- + grep -q 'wManageWindow succeeded' /tmp/wl-smoke-stderr \ + || die "compositor did not log 'wManageWindow succeeded' (wManageWindow call failed)" + echo " manage check: wManageWindow succeeded for weston-simple-shm toplevel" else echo " client check: skipped (weston-simple-shm not installed)" fi