wayland: Phase 39 — wire toplevel map to wManageWindow

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 <noreply@anthropic.com>
This commit is contained in:
Levi Neely 2026-03-28 06:23:23 +01:00
parent 74a43f0180
commit ca09635129
7 changed files with 133 additions and 94 deletions

View File

@ -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);
}
}
}
/*

View File

@ -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);
}

View File

@ -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 {

View File

@ -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);

View File

@ -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];

View File

@ -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) {

View File

@ -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