wayland: Phase 69 — WIN_WORKSPACE macro; safe workspace reads without frame

Add WIN_WORKSPACE(w) macro to window.h that returns frame->workspace
when the window has a frame (X11 path) or wwin->workspace when it does
not (Wayland path, set in Phase 67).

Replace all unsafe frame->workspace reads with WIN_WORKSPACE() in:
  - window.c:   wWindowObscuresWindow (workspace comparison + geometry),
                wWindowFocusPrev/Next (same-workspace filter),
                wWindowSaveState (state serialization)
  - session.c:  session save (workspace name lookup)
  - switchmenu.c: all five switch-menu workspace comparisons/lookups

Also guard wWindowObscuresWindow's frame->core width/height reads with
a ternary that falls back to client.{width,height} for frameless windows.

This eliminates the last class of NULL frame->workspace dereferences
reachable from workspace-switch, focus-cycle, session-save, and
switch-menu code paths.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Levi Neely 2026-03-28 09:43:09 +01:00
parent 2c55725bb6
commit 36aa1c5725
5 changed files with 22 additions and 14 deletions

View File

@ -219,7 +219,7 @@ static WMPropList *makeWindowState(WWindow * wwin, WApplication * wapp)
name = WMCreatePLString(buffer);
cmd = WMCreatePLString(command);
workspace = WMCreatePLString(scr->workspaces[wwin->frame->workspace]->name);
workspace = WMCreatePLString(scr->workspaces[WIN_WORKSPACE(wwin)]->name);
shaded = wwin->flags.shaded ? sYes : sNo;
miniaturized = wwin->flags.miniaturized ? sYes : sNo;

View File

@ -160,7 +160,7 @@ static int menuIndexForWindow(WMenu * menu, WWindow * wwin, int old_pos)
move_down = 1;
if (IS_OMNIPRESENT(tw) || (tw != wwin &&
tw->frame->workspace >= wwin->frame->workspace))
WIN_WORKSPACE(tw) >= WIN_WORKSPACE(wwin)))
return idx - move_down;
}
@ -219,7 +219,7 @@ void UpdateSwitchMenu(WScreen * scr, WWindow * wwin, int action)
snprintf(entry->rtext, MAX_WORKSPACENAME_WIDTH, "[*]");
else
snprintf(entry->rtext, MAX_WORKSPACENAME_WIDTH, "[%s]",
scr->workspaces[wwin->frame->workspace]->name);
scr->workspaces[WIN_WORKSPACE(wwin)]->name);
if (wwin->flags.hidden) {
entry->flags.indicator_type = MI_HIDDEN;
@ -277,7 +277,7 @@ void UpdateSwitchMenu(WScreen * scr, WWindow * wwin, int action)
} else {
snprintf(entry->rtext, MAX_WORKSPACENAME_WIDTH,
"[%s]",
scr->workspaces[wwin->frame->workspace]->name);
scr->workspaces[WIN_WORKSPACE(wwin)]->name);
}
rt = entry->rtext;
@ -350,12 +350,12 @@ static void UpdateSwitchMenuWorkspace(WScreen *scr, int workspace)
for (i = 0; i < menu->entry_no; i++) {
wwin = (WWindow *) menu->entries[i]->clientdata;
if (wwin->frame->workspace == workspace && !IS_OMNIPRESENT(wwin)) {
if (WIN_WORKSPACE(wwin) == workspace && !IS_OMNIPRESENT(wwin)) {
if (IS_OMNIPRESENT(wwin))
snprintf(menu->entries[i]->rtext, MAX_WORKSPACENAME_WIDTH, "[*]");
else
snprintf(menu->entries[i]->rtext, MAX_WORKSPACENAME_WIDTH, "[%s]",
scr->workspaces[wwin->frame->workspace]->name);
scr->workspaces[WIN_WORKSPACE(wwin)]->name);
menu->flags.realized = 0;
}
}

View File

@ -445,13 +445,14 @@ Bool wWindowObscuresWindow(WWindow *wwin, WWindow *obscured)
{
int w1, h1, w2, h2;
w1 = wwin->frame->core->width;
h1 = wwin->frame->core->height;
w2 = obscured->frame->core->width;
h2 = obscured->frame->core->height;
/* Phase 69: Wayland windows have no frame; use client geometry. */
w1 = wwin->frame ? (int)wwin->frame->core->width : (int)wwin->client.width;
h1 = wwin->frame ? (int)wwin->frame->core->height : (int)wwin->client.height;
w2 = obscured->frame ? (int)obscured->frame->core->width : (int)obscured->client.width;
h2 = obscured->frame ? (int)obscured->frame->core->height : (int)obscured->client.height;
if (!IS_OMNIPRESENT(wwin) && !IS_OMNIPRESENT(obscured)
&& wwin->frame->workspace != obscured->frame->workspace)
&& WIN_WORKSPACE(wwin) != WIN_WORKSPACE(obscured))
return False;
if (wwin->frame_x + w1 < obscured->frame_x
@ -1704,7 +1705,7 @@ void wWindowFocusPrev(WWindow *wwin, Bool inSameWorkspace)
tmp = tmp->prev;
if (inSameWorkspace)
while (tmp && (tmp->frame->workspace != wwin->frame->workspace))
while (tmp && (WIN_WORKSPACE(tmp) != WIN_WORKSPACE(wwin)))
tmp = tmp->next;
wWindowSingleFocus(tmp);
@ -1719,7 +1720,7 @@ void wWindowFocusNext(WWindow *wwin, Bool inSameWorkspace)
tmp = wwin->prev;
if (inSameWorkspace)
while (tmp && (tmp->frame->workspace != wwin->frame->workspace))
while (tmp && (WIN_WORKSPACE(tmp) != WIN_WORKSPACE(wwin)))
tmp = tmp->prev;
wWindowSingleFocus(tmp);
@ -2239,7 +2240,7 @@ void wWindowSaveState(WWindow *wwin)
int i;
memset(data, 0, sizeof(long) * 10);
data[0] = wwin->frame->workspace;
data[0] = WIN_WORKSPACE(wwin);
data[1] = wwin->flags.miniaturized;
data[2] = wwin->flags.shaded;
data[3] = wwin->flags.hidden;

View File

@ -307,6 +307,10 @@ typedef struct WWindow {
#define IS_RESIZABLE(w) (!(WFLAGP((w), no_resizable) || (w)->flags.fullscreen))
#define IS_OMNIPRESENT(w) ((w)->flags.omnipresent | WFLAGP(w, omnipresent))
#define WINDOW_LEVEL(w) ((w)->frame->core->stacking->window_level)
/* Phase 69: safe workspace read for windows that may have no frame (Wayland).
* frame->workspace is the authoritative slot for X11 windows; wwin->workspace
* is the fallback set in wManageWindow for Wayland windows. */
#define WIN_WORKSPACE(w) ((w)->frame ? (w)->frame->workspace : (w)->workspace)
/*
* Changes to this must update wWindowSaveState/getSavedState

View File

@ -61,6 +61,9 @@
# 26. (Phase 68) placement.c guards: calcSumOfCoveredAreas/window_overlaps
# skip frameless (Wayland) windows safely; frame->workspace accesses
# in icon-placement helpers use wwin->workspace fallback.
# 27. (Phase 69) WIN_WORKSPACE(w) macro added; frame->workspace reads in
# wWindowFocusPrev/Next, wWindowObscuresWindow, wWindowSaveState,
# session.c, and switchmenu.c replaced with the macro.
#
# This test grows as wl_backend vtable slots are implemented.
#