From e60dff8ffd0706e55913ff83d8e4a8094c4fa46a Mon Sep 17 00:00:00 2001 From: Levi Neely Date: Sat, 28 Mar 2026 09:38:07 +0100 Subject: [PATCH] =?UTF-8?q?wayland:=20Phase=2067=20=E2=80=94=20WWindow.wor?= =?UTF-8?q?kspace;=20safe=20workspace=20switching=20without=20frame?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add int workspace to WWindow so Wayland-managed windows (which have no WFrameWindow and therefore no frame->workspace slot) can track their workspace assignment. Set wwin->workspace = workspace in the Wayland branch of wManageWindow alongside the existing geometry recording. Guard wWorkspaceForceChange against NULL frame dereferences: introduce a tmp_workspace local that falls back to tmp->workspace when frame is NULL, and protect the omnipresent workspace assignment the same way. Also add a NULL guard for tmp->icon->core in the miniwindow-unmap path. This prevents NULL dereferences if workspace switching is triggered (e.g., via a root-menu action) while Wayland windows are managed. Co-Authored-By: Claude Sonnet 4.6 --- src/window.c | 6 +++++- src/window.h | 4 ++++ src/workspace.c | 12 +++++++++--- tests/wl-smoke-test.sh | 3 +++ 4 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/window.c b/src/window.c index 8e65ae0..1ca8490 100644 --- a/src/window.c +++ b/src/window.c @@ -1171,13 +1171,17 @@ WWindow *wManageWindow(WScreen *scr, WNativeWindow window) /* to make sure the window receives it's new position after reparenting */ wWindowSynthConfigureNotify(wwin); } else { - /* Wayland: record geometry from normal hints; frame stays NULL */ + /* Wayland: record geometry from normal hints; frame stays NULL. + * Phase 67: store workspace in wwin->workspace since there is no + * frame->workspace slot; wWorkspaceForceChange uses this field + * when frame is NULL to avoid NULL dereferences on WS switch. */ wwin->client.x = x; wwin->client.y = y; wwin->client.width = width; wwin->client.height = height; wwin->frame_x = x; wwin->frame_y = y; + wwin->workspace = workspace; } /* Setup descriptors and save window to internal lists */ diff --git a/src/window.h b/src/window.h index 96a6bc4..42c74c0 100644 --- a/src/window.h +++ b/src/window.h @@ -294,6 +294,10 @@ typedef struct WWindow { int icon_w, icon_h; RImage *net_icon_image; /* Window Image */ WNETWindowType window_type; /* _NET_WM_WINDOW_TYPE (WNW_TYPE_*) */ + /* Phase 67: workspace slot for Wayland path (frame is NULL, so + * frame->workspace is unavailable; workspace operations use this + * field instead when !frame). */ + int workspace; } WWindow; #define HAS_TITLEBAR(w) (!(WFLAGP((w), no_titlebar) || (w)->flags.fullscreen)) diff --git a/src/workspace.c b/src/workspace.c index 39b3669..dfe333e 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -467,7 +467,10 @@ void wWorkspaceForceChange(WScreen * scr, int workspace) * but will create annoyance for every other application */ while (tmp) { - if (tmp->frame->workspace != workspace && !tmp->flags.selected) { + /* Phase 67: Wayland windows have no frame; use wwin->workspace + * as the workspace slot when frame is NULL. */ + int tmp_workspace = tmp->frame ? tmp->frame->workspace : tmp->workspace; + if (tmp_workspace != workspace && !tmp->flags.selected) { /* unmap windows not on this workspace */ if ((tmp->flags.mapped || tmp->flags.shaded) && !IS_OMNIPRESENT(tmp) && !tmp->flags.changing_workspace) { @@ -480,7 +483,7 @@ void wWorkspaceForceChange(WScreen * scr, int workspace) } /* also unmap miniwindows not on this workspace */ if (!wPreferences.sticky_icons && tmp->flags.miniaturized && - tmp->icon && !IS_OMNIPRESENT(tmp)) { + tmp->icon && !IS_OMNIPRESENT(tmp) && tmp->icon->core) { wm_backend->window_unmap(tmp->icon->core->window); tmp->icon->mapped = 0; } @@ -488,7 +491,10 @@ void wWorkspaceForceChange(WScreen * scr, int workspace) if (IS_OMNIPRESENT(tmp)) { WApplication *wapp = wApplicationOf(tmp->main_window); - tmp->frame->workspace = workspace; + if (tmp->frame) + tmp->frame->workspace = workspace; + else + tmp->workspace = workspace; if (wapp) { wapp->last_workspace = workspace; diff --git a/tests/wl-smoke-test.sh b/tests/wl-smoke-test.sh index ca81d56..a5b4783 100755 --- a/tests/wl-smoke-test.sh +++ b/tests/wl-smoke-test.sh @@ -55,6 +55,9 @@ # 24. (Phase 66) XDG popup initial configure scheduled so context menus and # autocomplete dropdowns can commit first buffer without stalling # (build-only check — weston-simple-shm creates no popups). +# 25. (Phase 67) WWindow.workspace added; wWorkspaceForceChange uses it as +# fallback when frame==NULL so workspace switching is safe in Wayland +# (build-only check — workspace switch requires keyboard shortcuts). # # This test grows as wl_backend vtable slots are implemented. #