diff --git a/docs/min_core_draft/README.org b/docs/min_core_draft/README.org index 99edb06..8b00f0e 100644 --- a/docs/min_core_draft/README.org +++ b/docs/min_core_draft/README.org @@ -57,7 +57,7 @@ wm_workspace_t *ws = &state->workspaces[workspace_id]; // O(1) wm_output_t *output = &state->outputs[output_id]; // O(1) // 动态实体:线性查找(简单且足够) -wm_window_t *win = wm_state_find_window(state, window_id); // O(n) +const wm_window_t *win = wm_state_window_get(state, window_id); // O(n) ``` **设计优势**: diff --git a/docs/min_core_draft/WM_EVENT_ROUTING.org b/docs/min_core_draft/WM_EVENT_ROUTING.org index 10f3b9f..99f2128 100644 --- a/docs/min_core_draft/WM_EVENT_ROUTING.org +++ b/docs/min_core_draft/WM_EVENT_ROUTING.org @@ -53,27 +53,23 @@ void runtime_handle_metadata_event(wm_runtime_t *runtime, const wm_event_t *even const wm_window_metadata_changed_event_t *e = &event->as.window_metadata_changed; // 1. 查找窗口 - wm_window_t *win = wm_state_find_window(&runtime->state, e->window_id); + const wm_window_t *win = wm_state_window_get(&runtime->state, e->window_id); if (!win) { return; // 窗口已被销毁 } - // 2. 直接更新窗口元数据字段 + // 2. 通过 state 接口更新窗口元数据字段 if (e->changed_fields & WM_WINDOW_META_CHANGED_TITLE) { - free(win->title); - win->title = e->title ? strdup(e->title) : NULL; + wm_state_window_set_title(&runtime->state, e->window_id, e->title); } if (e->changed_fields & WM_WINDOW_META_CHANGED_APP_ID) { - free(win->app_id); - win->app_id = e->app_id ? strdup(e->app_id) : NULL; + wm_state_window_set_app_id(&runtime->state, e->window_id, e->app_id); } if (e->changed_fields & WM_WINDOW_META_CHANGED_CLASS) { - free(win->class_name); - win->class_name = e->class_name ? strdup(e->class_name) : NULL; + wm_state_window_set_class(&runtime->state, e->window_id, e->class_name); } if (e->changed_fields & WM_WINDOW_META_CHANGED_INSTANCE) { - free(win->instance_name); - win->instance_name = e->instance_name ? strdup(e->instance_name) : NULL; + wm_state_window_set_instance(&runtime->state, e->window_id, e->instance_name); } // 3. 标记 render 脏(状态栏需要更新) diff --git a/docs/min_core_draft/WM_POLICY_APPLY_COMMAND_SKELETON.org b/docs/min_core_draft/WM_POLICY_APPLY_COMMAND_SKELETON.org index d3fffbb..9dd92e3 100644 --- a/docs/min_core_draft/WM_POLICY_APPLY_COMMAND_SKELETON.org +++ b/docs/min_core_draft/WM_POLICY_APPLY_COMMAND_SKELETON.org @@ -177,7 +177,7 @@ static bool apply_manage_window(wm_state_t *state, wm_window_id_t id = command->as.manage_window.window_id; wm_workspace_id_t ws_id = command->as.manage_window.workspace_id; - if (wm_state_find_window_const(state, id)) return false; + if (wm_state_window_get(state, id)) return false; wm_workspace_t *ws = require_workspace(state, ws_id); if (!ws) return false; diff --git a/src/core/state.c b/src/core/state.c new file mode 100644 index 0000000..d6f9bb5 --- /dev/null +++ b/src/core/state.c @@ -0,0 +1,539 @@ +#include "core/state.h" + +#include +#include + +#include "core/types.h" +#include "utils.h" +#include "wm_desc.h" + +void wm_state_init(wm_state_t *state, const wm_output_info_t *outputs, + size_t output_count, const wm_workspace_desc_t *workspaces, + size_t workspace_count) { + p_clear(state, 1); + + state->outputs = p_new(wm_output_t, output_count); + for (size_t i = 0; i < output_count; i++) { + const wm_output_info_t *output_info = &outputs[i]; + wm_output_t *output = &state->outputs[i]; + + output->id = (wm_output_id_t)i; + output->current_workspace_id = WM_WORKSPACE_ID_INVALID; + output->name = p_strdup(output_info->name); + output->geometry = output_info->geometry; + output->workarea = output_info->geometry; + } + state->output_count = output_count; + + state->workspaces = p_new(wm_workspace_t, workspace_count); + for (size_t i = 0; i < workspace_count; i++) { + const wm_workspace_desc_t *workspace_desc = &workspaces[i]; + if (!wm_workspace_desc_valid(workspace_desc, output_count)) { + fatal("workspace desc at index %zu invalid", i); + } + + wm_output_t *output = &state->outputs[workspace_desc->output_index]; + wm_workspace_t *workspace = &state->workspaces[i]; + workspace->id = (wm_workspace_id_t)i; + workspace->output_id = output->id; + workspace->focused_window_id = WM_WINDOW_ID_INVALID; + workspace->available_layouts = + p_copy(workspace_desc->layout_ids, workspace_desc->layout_count); + workspace->layout_id = workspace_desc->initial_layout_id; + workspace->layout_count = workspace_desc->layout_count; + workspace->name = p_strdup(workspace_desc->name); + + if (output->current_workspace_id == WM_WORKSPACE_ID_INVALID) { + output->current_workspace_id = workspace->id; + } + } + state->workspace_count = workspace_count; + + for (size_t i = 0; i < state->output_count; i++) { + const wm_output_t *output = &state->outputs[i]; + if (output->current_workspace_id == WM_WORKSPACE_ID_INVALID) { + fatal("output at index %zu has no workspace", i); + } + } +} + +void wm_state_cleanup(wm_state_t *state) { + for (size_t i = 0; i < state->window_count; i++) { + wm_window_t *window = &state->windows[i]; + p_delete(&window->title); + p_delete(&window->app_id); + p_delete(&window->class_name); + p_delete(&window->instance_name); + } + state->window_count = 0; + p_delete(&state->windows); + p_delete(&state->stack_order); + state->window_capacity = 0; + + for (size_t i = 0; i < state->workspace_count; i++) { + wm_workspace_t *workspace = &state->workspaces[i]; + p_delete(&workspace->name); + p_delete(&workspace->available_layouts); + workspace->layout_count = 0; + } + p_delete(&state->workspaces); + state->workspace_count = 0; + + for (size_t i = 0; i < state->output_count; i++) { + wm_output_t *output = &state->outputs[i]; + p_delete(&output->name); + } + p_delete(&state->outputs); + state->output_count = 0; + + p_clear(state, 1); +} + +const wm_workspace_t *wm_state_workspace_get(const wm_state_t *state, + wm_workspace_id_t id) { + if (id < state->workspace_count) return &state->workspaces[id]; + return nullptr; +} + +const wm_workspace_t *wm_state_workspace_at(const wm_state_t *state, + size_t index) { + if (index < state->workspace_count) return &state->workspaces[index]; + return nullptr; +} + +static void wm_state_workspace_adjust_focused_window( + const wm_state_t *state, wm_workspace_id_t workspace_id) { + wm_workspace_t *workspace = + (wm_workspace_t *)wm_state_workspace_get(state, workspace_id); + if (!workspace) return; + + const wm_window_t *window = + wm_state_window_get(state, workspace->focused_window_id); + if (window && window->workspace_id == workspace_id) return; + + for (size_t i = state->window_count; i > 0; i--) { + wm_window_id_t window_id = state->stack_order[i - 1]; + const wm_window_t *window = wm_state_window_get(state, window_id); + if (window && window->workspace_id == workspace_id) { + workspace->focused_window_id = window_id; + return; + } + } + + workspace->focused_window_id = WM_WINDOW_ID_INVALID; +} + +bool wm_state_workspace_cycle_layout(wm_state_t *state, + wm_workspace_id_t workspace_id) { + wm_workspace_t *workspace = + (wm_workspace_t *)wm_state_workspace_get(state, workspace_id); + if (!workspace) return false; + + size_t next_index = 0; + bool matched = false; + for (size_t i = 0; i < workspace->layout_count; i++) { + if (workspace->layout_id == workspace->available_layouts[i]) { + next_index = (i + 1) % workspace->layout_count; + matched = true; + break; + } + } + + if (!matched) return false; + + auto next_layout_id = workspace->available_layouts[next_index]; + workspace->layout_id = next_layout_id; + return true; +} + +bool wm_state_workspace_set_layout_by_index(wm_state_t *state, + wm_workspace_id_t workspace_id, + size_t index) { + wm_workspace_t *workspace = + (wm_workspace_t *)wm_state_workspace_get(state, workspace_id); + if (!workspace) return false; + if (index >= workspace->layout_count) return false; + + workspace->layout_id = workspace->available_layouts[index]; + return true; +} + +bool wm_state_workspace_set_layout_by_id(wm_state_t *state, + wm_workspace_id_t workspace_id, + wm_layout_id_t layout_id) { + wm_workspace_t *workspace = + (wm_workspace_t *)wm_state_workspace_get(state, workspace_id); + if (!workspace) return false; + for (size_t i = 0; i < workspace->layout_count; i++) { + if (workspace->available_layouts[i] == layout_id) { + workspace->layout_id = layout_id; + return true; + } + } + + return false; +} + +void wm_state_workspace_set_focused_window(wm_state_t *state, + wm_workspace_id_t workspace_id, + wm_window_id_t window_id) { + wm_workspace_t *workspace = + (wm_workspace_t *)wm_state_workspace_get(state, workspace_id); + if (!workspace) return; + + for (size_t i = 0; i < state->window_count; i++) { + wm_window_t *window = &state->windows[i]; + if (window->id == window_id && window->workspace_id == workspace_id) { + workspace->focused_window_id = window_id; + return; + } + } + + wm_state_workspace_adjust_focused_window(state, workspace_id); +} + +size_t wm_state_workspace_count(const wm_state_t *state) { + return state->workspace_count; +} + +bool wm_state_workspace_valid(const wm_state_t *state, wm_workspace_id_t id) { + return id < state->workspace_count; + + /* 线性扫描作为备用 */ +#if 0 + if (id >= state->workspace_count) return false; + + for (size_t i = 0; i < state->workspace_count; i++) { + if (state->workspaces[i].id == id) return true; + } + + return false; +#endif +} + +const wm_output_t *wm_state_output_get(const wm_state_t *state, + wm_output_id_t id) { + if (id < state->output_count) return &state->outputs[id]; + return nullptr; +} + +const wm_output_t *wm_state_output_at(const wm_state_t *state, size_t index) { + if (index < state->output_count) return &state->outputs[index]; + return nullptr; +} + +void wm_state_output_set_workarea(wm_state_t *state, wm_output_id_t output_id, + wm_rect_t workarea) { + wm_output_t *output = (wm_output_t *)wm_state_output_get(state, output_id); + if (output) output->workarea = workarea; +} + +void wm_state_output_set_current_workspace(wm_state_t *state, + wm_output_id_t output_id, + wm_workspace_id_t workspace_id) { + wm_output_t *output = (wm_output_t *)wm_state_output_get(state, output_id); + if (!output) return; + const wm_workspace_t *workspace = wm_state_workspace_get(state, workspace_id); + if (!workspace || workspace->output_id != output_id) return; + + output->current_workspace_id = workspace_id; +} + +size_t wm_state_output_count(const wm_state_t *state) { + return state->output_count; +} + +bool wm_state_output_valid(const wm_state_t *state, wm_output_id_t id) { + return id < state->output_count; + + /* 线性扫描作为备用 */ +#if 0 + if (id >= state->output_count) return false; + + for (size_t i = 0; i < state->output_count; i++) { + if (state->outputs[i].id == id) return true; + } + + return false; +#endif +} + +const wm_window_t *wm_state_window_add(wm_state_t *state, + const wm_window_info_t *info) { + wm_window_id_t id = info->id; + wm_window_t *window = (wm_window_t *)wm_state_window_get(state, id); + if (!window) { + if (state->window_count == state->window_capacity) { + size_t capacity = next_capacity(state->window_capacity); + p_realloc(&state->windows, capacity); + p_realloc(&state->stack_order, capacity); + state->window_capacity = capacity; + } + window = &state->windows[state->window_count]; + p_clear(window, 1); + window->id = id; + window->workspace_id = WM_WORKSPACE_ID_INVALID; + + state->stack_order[state->window_count] = id; + state->window_count++; + } + + wm_state_window_set_geometry_mode(state, id, info->geometry_mode); + wm_state_window_set_urgent(state, id, info->urgent); + wm_state_window_set_fixed_size(state, id, info->fixed_size); + wm_state_window_set_frame_rect(state, id, info->frame_rect); + wm_state_window_set_title(state, id, info->title); + wm_state_window_set_app_id(state, id, info->app_id); + wm_state_window_set_class(state, id, info->class_name); + wm_state_window_set_instance(state, id, info->instance_name); + wm_state_window_set_skip_taskbar(state, id, info->skip_taskbar); + + return window; +} + +const wm_window_t *wm_state_window_get(const wm_state_t *state, + wm_window_id_t id) { + for (size_t i = 0; i < state->window_count; i++) { + if (state->windows[i].id == id) return &state->windows[i]; + } + + return nullptr; +} + +const wm_window_t *wm_state_window_at(const wm_state_t *state, size_t index) { + if (index < state->window_count) return &state->windows[index]; + + return nullptr; +} + +void wm_state_window_remove(wm_state_t *state, wm_window_id_t id) { + bool matched = false; + size_t index = 0; + for (size_t i = 0; i < state->window_count; i++) { + if (state->windows[i].id == id) { + matched = true; + index = i; + break; + } + } + if (!matched) return; + + wm_window_t *window = &state->windows[index]; + wm_workspace_id_t workspace_id = window->workspace_id; + p_delete(&window->title); + p_delete(&window->app_id); + p_delete(&window->class_name); + p_delete(&window->instance_name); + + for (size_t i = index + 1; i < state->window_count; i++) { + state->windows[i - 1] = state->windows[i]; + } + window = &state->windows[state->window_count - 1]; + p_clear(window, 1); + window->id = WM_WINDOW_ID_INVALID; + window->workspace_id = WM_WORKSPACE_ID_INVALID; + + matched = false; + for (size_t i = 0; i < state->window_count; i++) { + if (state->stack_order[i] == id) { + matched = true; + index = i; + break; + } + } + if (matched) { + for (size_t i = index + 1; i < state->window_count; i++) { + state->stack_order[i - 1] = state->stack_order[i]; + } + state->stack_order[state->window_count - 1] = WM_WINDOW_ID_INVALID; + } + + state->window_count--; + + wm_workspace_t *workspace = + (wm_workspace_t *)wm_state_workspace_get(state, workspace_id); + if (workspace && workspace->focused_window_id == id) { + wm_state_workspace_adjust_focused_window(state, workspace_id); + } +} + +size_t wm_state_window_count(const wm_state_t *state) { + return state->window_count; +} + +void wm_state_window_set_workspace(wm_state_t *state, wm_window_id_t window_id, + wm_workspace_id_t workspace_id) { + const wm_workspace_t *workspace = wm_state_workspace_get(state, workspace_id); + wm_window_t *window = (wm_window_t *)wm_state_window_get(state, window_id); + if (!workspace || !window || window->workspace_id == workspace_id) return; + + workspace = wm_state_workspace_get(state, window->workspace_id); + window->workspace_id = workspace_id; + + if (workspace && workspace->focused_window_id == window_id) { + wm_state_workspace_adjust_focused_window(state, workspace->id); + } + + wm_state_workspace_adjust_focused_window(state, workspace_id); +} + +void wm_state_window_set_geometry_mode( + wm_state_t *state, wm_window_id_t window_id, + wm_window_geometry_mode_t geometry_mode) { + wm_window_t *window = (wm_window_t *)wm_state_window_get(state, window_id); + if (!window) return; + + window->geometry_mode = geometry_mode; +} + +void wm_state_window_set_floating(wm_state_t *state, wm_window_id_t window_id, + bool floating) { + wm_window_t *window = (wm_window_t *)wm_state_window_get(state, window_id); + if (!window) return; + + window->floating = floating; +} + +void wm_state_window_set_sticky(wm_state_t *state, wm_window_id_t window_id, + bool sticky) { + wm_window_t *window = (wm_window_t *)wm_state_window_get(state, window_id); + if (!window) return; + + window->sticky = sticky; + if (window->sticky) wm_state_window_set_floating(state, window_id, true); +} + +void wm_state_window_set_urgent(wm_state_t *state, wm_window_id_t window_id, + bool urgent) { + wm_window_t *window = (wm_window_t *)wm_state_window_get(state, window_id); + if (!window) return; + + window->urgent = urgent; +} + +void wm_state_window_set_fixed_size(wm_state_t *state, wm_window_id_t window_id, + bool fixed_size) { + wm_window_t *window = (wm_window_t *)wm_state_window_get(state, window_id); + if (!window) return; + + window->fixed_size = fixed_size; + if (fixed_size) wm_state_window_set_floating(state, window_id, true); +} + +void wm_state_window_set_skip_taskbar(wm_state_t *state, + wm_window_id_t window_id, + bool skip_taskbar) { + wm_window_t *window = (wm_window_t *)wm_state_window_get(state, window_id); + if (!window) return; + + window->skip_taskbar = skip_taskbar; +} + +void wm_state_window_set_float_rect(wm_state_t *state, wm_window_id_t window_id, + wm_rect_t float_rect) { + wm_window_t *window = (wm_window_t *)wm_state_window_get(state, window_id); + if (!window) return; + + window->float_rect = float_rect; +} + +void wm_state_window_set_frame_rect(wm_state_t *state, wm_window_id_t window_id, + wm_rect_t frame_rect) { + wm_window_t *window = (wm_window_t *)wm_state_window_get(state, window_id); + if (!window) return; + + window->frame_rect = frame_rect; +} + +bool wm_state_window_set_title(wm_state_t *state, wm_window_id_t window_id, + const char *title) { + wm_window_t *window = (wm_window_t *)wm_state_window_get(state, window_id); + if (!window) return false; + + p_delete(&window->title); + if (title) window->title = p_strdup(title); + + return true; +} + +bool wm_state_window_set_app_id(wm_state_t *state, wm_window_id_t window_id, + const char *app_id) { + wm_window_t *window = (wm_window_t *)wm_state_window_get(state, window_id); + if (!window) return false; + + p_delete(&window->app_id); + if (app_id) window->app_id = p_strdup(app_id); + + return true; +} +bool wm_state_window_set_class(wm_state_t *state, wm_window_id_t window_id, + const char *class_name) { + wm_window_t *window = (wm_window_t *)wm_state_window_get(state, window_id); + if (!window) return false; + + p_delete(&window->class_name); + if (class_name) window->class_name = p_strdup(class_name); + + return true; +} +bool wm_state_window_set_instance(wm_state_t *state, wm_window_id_t window_id, + const char *instance_name) { + wm_window_t *window = (wm_window_t *)wm_state_window_get(state, window_id); + if (!window) return false; + + p_delete(&window->instance_name); + if (instance_name) window->instance_name = p_strdup(instance_name); + + return true; +} + +const wm_window_id_t *wm_state_stack_order(const wm_state_t *state) { + return state->stack_order; +} + +wm_window_id_t wm_state_stack_at(const wm_state_t *state, size_t index) { + if (index >= state->window_count) return WM_WINDOW_ID_INVALID; + return state->stack_order[index]; +} + +bool wm_state_stack_raise(wm_state_t *state, wm_window_id_t window_id) { + bool matched = false; + size_t index = 0; + for (size_t i = 0; i < state->window_count; i++) { + if (state->stack_order[i] == window_id) { + matched = true; + index = i; + break; + } + } + + if (!matched) return false; + + for (size_t i = index + 1; i < state->window_count; i++) { + state->stack_order[i - 1] = state->stack_order[i]; + } + state->stack_order[state->window_count - 1] = window_id; + + return true; +} + +bool wm_state_stack_lower(wm_state_t *state, wm_window_id_t window_id) { + bool matched = false; + size_t index = 0; + for (size_t i = 0; i < state->window_count; i++) { + if (state->stack_order[i] == window_id) { + matched = true; + index = i; + break; + } + } + + if (!matched) return false; + + for (size_t i = index; i > 0; i--) { + state->stack_order[i] = state->stack_order[i - 1]; + } + state->stack_order[0] = window_id; + + return true; +} diff --git a/src/core/state.h b/src/core/state.h index 2c3a216..de6aba0 100644 --- a/src/core/state.h +++ b/src/core/state.h @@ -69,18 +69,25 @@ typedef struct wm_state_t { } wm_state_t; -/* state 容器生命周期接口 */ -void wm_state_init(wm_state_t *state, const wm_output_info_t *outputs, - size_t output_count); -void wm_state_cleanup(wm_state_t *state); /* - * 根据 workspace 描述表构建 state.workspaces[]。 + * 调用约束: + * - state 必须是有效的非空指针 + * - wm_state_init() / wm_state_cleanup() 必须成对调用 + * - 同一个 state 生命周期内,wm_state_init() 不允许重复调用 + * - 除 wm_state_init() 外,其余 state 相关接口都要求 state 已初始化 + * - workspaces[i] 必须满足 wm_workspace_desc_valid(&workspaces[i], output_count) + * - 传入空指针、未初始化对象或违反前置条件属于调用方错误 * + * 初始化时根据描述表构建 outputs[] 与 workspaces[]。 * workspace_id 由 state 按描述表顺序分配并保持稳定,不从 desc 中读取。 + * 每个 output 的 current_workspace_id 会自动设置为首个归属到该 output 的 + * workspace;若某个 output 没有任何归属 workspace,wm_state_init() 会失败。 */ -void wm_state_set_workspaces(wm_state_t *state, - const wm_workspace_desc_t *workspaces, - size_t workspace_count); +void wm_state_init(wm_state_t *state, const wm_output_info_t *outputs, + size_t output_count, + const wm_workspace_desc_t *workspaces, + size_t workspace_count); +void wm_state_cleanup(wm_state_t *state); /* * state 持有的 workspace 集合接口 @@ -186,7 +193,3 @@ const wm_window_id_t *wm_state_stack_order(const wm_state_t *state); wm_window_id_t wm_state_stack_at(const wm_state_t *state, size_t index); bool wm_state_stack_raise(wm_state_t *state, wm_window_id_t window_id); bool wm_state_stack_lower(wm_state_t *state, wm_window_id_t window_id); - -/* 与 min_core_draft 中的查找命名保持一致 */ -const wm_window_t *wm_state_find_window(const wm_state_t *state, - wm_window_id_t id); diff --git a/src/utils.h b/src/utils.h index 69c3f54..71ac8c2 100644 --- a/src/utils.h +++ b/src/utils.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include #include @@ -12,6 +13,7 @@ #define p_new(type, count) ((type *)xmalloc(sizeof(type) * (count))) #define p_clear(p, count) ((void)memset((p), 0, sizeof(*(p)) * (count))) #define p_realloc(pp, count) xrealloc((void *)(pp), sizeof(**(pp)) * (count)) +#define p_copy(src, count) xmemcopy((src), sizeof(*(src)) * (count)) #define p_delete(mem_p) \ do { \ @@ -46,6 +48,15 @@ static inline void *__attribute__((malloc)) xmalloc(ssize_t size) { return ptr; } +static inline void *xmemcopy(const void *src, size_t n) { + if (!n) return nullptr; + + void *mem = xmalloc(n); + void *ret = memcpy(mem, src, n); + if (ret != mem) abort(); + return ret; +} + static inline void xrealloc(void **ptr, ssize_t newsize) { if (newsize <= 0) p_delete(ptr); diff --git a/src/wm_desc.h b/src/wm_desc.h index 381edb8..566c990 100644 --- a/src/wm_desc.h +++ b/src/wm_desc.h @@ -30,9 +30,35 @@ typedef struct wm_window_info_t { } wm_window_info_t; typedef struct wm_workspace_desc_t { - wm_output_id_t output_id; + size_t output_index; /* 对应 wm_state_init() 中 outputs[] 的索引 */ const char *name; const wm_layout_id_t *layout_ids; size_t layout_count; wm_layout_id_t initial_layout_id; } wm_workspace_desc_t; + +/* + * workspace 描述校验接口。 + * + * 这组接口只校验描述表自身的一致性,不访问 state。 + */ +static inline bool +wm_workspace_desc_layouts_valid(const wm_workspace_desc_t *workspace) { + if (!workspace || !workspace->layout_count || !workspace->layout_ids) { + return false; + } + + for (size_t i = 0; i < workspace->layout_count; i++) { + if (workspace->layout_ids[i] == workspace->initial_layout_id) return true; + } + + return false; +} + +static inline bool wm_workspace_desc_valid(const wm_workspace_desc_t *workspace, + size_t output_count) { + if (!workspace || !workspace->name) return false; + if (workspace->output_index >= output_count) return false; + + return wm_workspace_desc_layouts_valid(workspace); +}