From 329943ab3e9b2e84ad0c37ef6a730ade8bd86d70 Mon Sep 17 00:00:00 2001 From: Zedhugh Chen Date: Wed, 11 Mar 2026 05:28:39 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E5=96=84=E6=9C=80=E5=B0=8F=E6=A0=B8?= =?UTF-8?q?=E5=BF=83=E6=9E=B6=E6=9E=84=E8=AE=BE=E8=AE=A1=E5=92=8C=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E7=BB=93=E6=9E=84=E7=AE=80=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 主要变更: - 数据结构简化 - 合并窗口元数据到 wm_window_t(删除 wm_window_meta.h) - 合并工作区名称到 wm_workspace_t(删除 wm_workspace_desc.h) - 简化布局标识符,只保留 name 字段 - 删除 window_map,采用直接索引+线性查找 - 架构设计完善 - 确定 window→workspace→output 固定归属关系 - 制定 ID 分配策略(半静态实体直接索引,动态实体线性查找) - 确定显示器热插拔策略(检测到变化时重启 WM) - 添加工作区配置示例和多种配置方式 - 新增模块 - wm_policy_config.h:策略配置开关 - wm_service.h:核心外服务注册和事件订阅 - WM_EVENT_ROUTING.org:事件路由详细文档 - WORKSPACE_CONFIG_EXAMPLES.org:工作区配置示例 - MINIMAL_CORE_WITH_EXTENSIONS.org:设计对比文档 - config_system.org:配置系统设计 - 文档完善 - 大幅扩展 README.org,补充 ID 分配、热插拔、配置集成等说明 - 更新 WM_COMMAND_RULES.org,补充命令规则细节 - 更新 WM_POLICY_APPLY_COMMAND_SKELETON.org,完善实现骨架 --- docs/MINIMAL_CORE_WITH_EXTENSIONS.org | 650 ++++++++++++++++++ docs/config_system.org | 412 +++++++++++ docs/min_core_draft/README.org | 283 ++++++-- docs/min_core_draft/WM_COMMAND_RULES.org | 70 +- docs/min_core_draft/WM_EVENT_ROUTING.org | 397 +++++++++++ .../WM_POLICY_APPLY_COMMAND_SKELETON.org | 86 ++- .../WORKSPACE_CONFIG_EXAMPLES.org | 220 ++++++ docs/min_core_draft/wm_backend.h | 4 + docs/min_core_draft/wm_command.h | 29 + docs/min_core_draft/wm_event.h | 16 + docs/min_core_draft/wm_layout.h | 11 +- docs/min_core_draft/wm_policy_config.h | 13 + docs/min_core_draft/wm_runtime.h | 62 +- docs/min_core_draft/wm_service.h | 45 ++ docs/min_core_draft/wm_state.h | 131 ++-- docs/min_core_draft/wm_types.h | 9 + docs/min_core_draft/wm_window_meta.h | 29 - docs/min_core_draft/wm_workspace_desc.h | 37 - 18 files changed, 2335 insertions(+), 169 deletions(-) create mode 100644 docs/MINIMAL_CORE_WITH_EXTENSIONS.org create mode 100644 docs/config_system.org create mode 100644 docs/min_core_draft/WM_EVENT_ROUTING.org create mode 100644 docs/min_core_draft/WORKSPACE_CONFIG_EXAMPLES.org create mode 100644 docs/min_core_draft/wm_policy_config.h create mode 100644 docs/min_core_draft/wm_service.h delete mode 100644 docs/min_core_draft/wm_window_meta.h delete mode 100644 docs/min_core_draft/wm_workspace_desc.h diff --git a/docs/MINIMAL_CORE_WITH_EXTENSIONS.org b/docs/MINIMAL_CORE_WITH_EXTENSIONS.org new file mode 100644 index 0000000..a056fb4 --- /dev/null +++ b/docs/MINIMAL_CORE_WITH_EXTENSIONS.org @@ -0,0 +1,650 @@ +# ZDWM 最小核心 + 外围扩展设计 + +* 设计原则 + +1. *核心只做一件事*:管理窗口、显示器、工作区 +2. *所有扩展都通过插件*:状态栏、配置、快捷键、布局算法 +3. *核心提供稳定的钩子*:插件可以在关键点介入 +4. *零运行时开销*:扩展不增加核心复杂度 + +* 最小核心定义 + +#+BEGIN_SRC c +// src/core/zdwm.h +#include +#include + +// ========== 基础类型 ========== +typedef struct { + int x, y; + unsigned int width, height; +} rect_t; + +typedef struct { + const char *name; + const char *instance; +} wm_class_t; + +// ========== 窗口 ========== +typedef uint64_t wm_window_id_t; + +typedef struct { + wm_window_id_t id; + rect_t geometry; + wm_class_t class; + bool mapped; + bool floating; + bool fullscreen; + bool focused; + // ... 其他基本状态 +} wm_window_t; + +// ========== 工作区 ========== +typedef uint32_t wm_workspace_id_t; + +typedef struct { + wm_workspace_id_t id; + char *name; + wm_window_id_t *windows; + size_t window_count; + wm_window_id_t focused_window; +} wm_workspace_t; + +// ========== 显示器 ========== +typedef uint32_t wm_output_id_t; + +typedef struct { + wm_output_id_t id; + rect_t geometry; + rect_t workarea; + wm_workspace_id_t current_workspace; +} wm_output_t; + +// ========== 核心状态 ========== +typedef struct { + wm_window_t *windows; + size_t window_count; + + wm_workspace_t *workspaces; + size_t workspace_count; + wm_workspace_id_t current_workspace; + + wm_output_t *outputs; + size_t output_count; +} wm_state_t; + +// ========== 核心 API ========== +// 初始化和清理 +bool wm_init(void); +void wm_shutdown(void); + +// 主循环 +void wm_run(void); + +// 查询接口 +wm_window_t* wm_find_window(wm_window_id_t id); +wm_workspace_t* wm_find_workspace(wm_workspace_id_t id); +wm_output_t* wm_find_output(wm_output_id_t id); + +// 窗口操作 +void wm_manage_window(wm_window_id_t id); +void wm_unmanage_window(wm_window_id_t id); +void wm_focus_window(wm_window_id_t id); +void wm_set_window_floating(wm_window_id_t id, bool floating); + +// 工作区操作 +void wm_switch_workspace(wm_workspace_id_t id); +void wm_send_window_to_workspace(wm_window_id_t window, wm_workspace_id_t workspace); + +// 显示器操作 +void wm_switch_output(wm_output_id_t id); +#+END_SRC + +* 扩展接口设计 + +**1. 事件钩子系统** + +#+BEGIN_SRC c +// src/core/hooks.h + +// 钩子类型(关键事件点) +typedef enum wm_hook_type_t { + HOOK_WINDOW_MANAGE, // 窗口被管理时 + HOOK_WINDOW_UNMANAGE, // 窗口取消管理时 + HOOK_WINDOW_FOCUS, // 窗口焦点变化时 + HOOK_WORKSPACE_SWITCH, // 工作区切换时 + HOOK_LAYOUT_CALCULATE, // 布局计算时 + HOOK_CONFIG_RELOAD, // 配置重载时 + HOOK_KEY_PRESS, // 键盘按下时 + HOOK_COUNT +} wm_hook_type_t; + +// 钩子回调函数类型 +typedef void (*wm_hook_window_manage_t)(wm_window_t *window, void *user_data); +typedef void (*wm_hook_window_focus_t)(wm_window_t *old_focus, wm_window_t *new_focus, void *user_data); +typedef void (*wm_hook_layout_calculate_t)(wm_workspace_t *workspace, rect_t *geometries, size_t count, void *user_data); +typedef bool (*wm_hook_key_press_t)(uint32_t keycode, uint32_t modifiers, void *user_data); + +// 钩子注册 +typedef struct { + wm_hook_type_t type; + void *callback; + void *user_data; + int priority; // 优先级:数字越小越先执行 +} wm_hook_t; + +// 核心 API:注册钩子 +void wm_hook_register(wm_hook_type_t type, void *callback, void *user_data, int priority); +void wm_hook_unregister(wm_hook_type_t type, void *callback); + +// 核心 API:触发钩子 +void wm_hook_trigger_window_manage(wm_window_t *window); +void wm_hook_trigger_window_focus(wm_window_t *old_focus, wm_window_t *new_focus); +void wm_hook_trigger_layout_calculate(wm_workspace_t *workspace, rect_t *geometries, size_t count); +bool wm_hook_trigger_key_press(uint32_t keycode, uint32_t modifiers); +#+END_SRC + +**2. 插件系统** + +#+BEGIN_SRC c +// src/core/plugin.h + +// 插件接口 +typedef struct { + const char *name; + const char *version; + + // 必须实现 + bool (*init)(void); + void (*shutdown)(void); + + // 可选实现 + void (*on_event)(wm_hook_type_t event, void *event_data); +} wm_plugin_t; + +// 核心 API:插件加载 +bool wm_plugin_load(wm_plugin_t *plugin); +bool wm_plugin_unload(const char *name); +#+END_SRC + +* 扩展编写指南 + +**扩展1:布局算法(插件)** + +#+BEGIN_SRC c +// plugins/layout_tile.c + +#include "plugins/layout.h" +#include "core/hooks.h" + +// 布局算法实现 +static void tile_calculate_layout(wm_workspace_t *workspace, + rect_t *geometries, + size_t count, + void *user_data) { + // 布局参数(可以通过配置获取) + int gap = 10; + int master_count = 1; + + for (size_t i = 0; i < count; i++) { + if (i < master_count) { + // 主窗口:左侧,占 60% + geometries[i].x = workspace->outputs[0].x; + geometries[i].y = workspace->outputs[0].y; + geometries[i].width = workspace->outputs[0].width * 0.6 - gap; + geometries[i].height = workspace->outputs[0].height; + } else { + // 堆叠窗口:右侧,占 40% + geometries[i].x = workspace->outputs[0].x + workspace->outputs[0].width * 0.6 + gap; + geometries[i].y = workspace->outputs[0].y + (i - master_count) * 20; + geometries[i].width = workspace->outputs[0].width * 0.4 - gap; + geometries[i].height = 20; + } + } +} + +// 插件初始化 +static bool layout_tile_init(void) { + // 注册布局钩子 + wm_hook_register(HOOK_LAYOUT_CALCULATE, tile_calculate_layout, NULL, 0); + return true; +} + +static void layout_tile_shutdown(void) { + // 自动取消注册 +} + +// 插件定义 +wm_plugin_t plugin_tile = { + .name = "tile", + .version = "1.0", + .init = layout_tile_init, + .shutdown = layout_tile_shutdown +}; +#+END_SRC + +**扩展2:状态栏(插件)** + +#+BEGIN_SRC c +// plugins/statusbar.c + +#include "plugins/statusbar.h" +#include "core/hooks.h" + +static cairo_surface_t *g_bar_surface = NULL; + +// 在工作区切换时更新状态栏 +static void on_workspace_switch(void *event_data) { + wm_workspace_t *workspace = event_data; + + // 绘制状态栏 + draw_statusbar(workspace); +} + +// 在布局计算后绘制到状态栏 +static void on_layout_calculate(wm_workspace_t *workspace, + rect_t *geometries, + size_t count, + void *user_data) { + // 可以在这里显示布局信息 + draw_layout_info(workspace); +} + +// 绘制状态栏 +static void draw_statusbar(wm_workspace_t *workspace) { + // 使用 cairo 绘制 + // ... +} + +static bool statusbar_init(void) { + // 注册钩子 + wm_hook_register(HOOK_WORKSPACE_SWITCH, on_workspace_switch, NULL, 0); + wm_hook_register(HOOK_LAYOUT_CALCULATE, on_layout_calculate, NULL, 0); + + return true; +} + +static void statusbar_shutdown(void) { + if (g_bar_surface) { + cairo_surface_destroy(g_bar_surface); + } +} + +wm_plugin_t plugin_statusbar = { + .name = "statusbar", + .version = "1.0", + .init = statusbar_init, + .shutdown = statusbar_shutdown +}; +#+END_SRC + +**扩展3:快捷键绑定(插件)** + +#+BEGIN_SRC c +// plugins/keybindings.c + +#include "plugins/keybindings.h" +#include "core/hooks.h" + +// 快捷键配置 +typedef struct { + uint32_t keycode; + uint32_t modifiers; + void (*action)(void); +} keybinding_t; + +static keybinding_t g_bindings[] = { + { .keycode = XK_Return, .modifiers = Mod4Mask, .action = action_launch_terminal }, + { .keycode = XK_q, .modifiers = Mod4Mask | ShiftMask, .action = action_close_window }, + { .keycode = XK_space, .modifiers = Mod4Mask, .action = action_next_layout }, +}; + +static bool handle_key_press(uint32_t keycode, uint32_t modifiers, void *user_data) { + for (size_t i = 0; i < sizeof(g_bindings)/sizeof(g_bindings[0]); i++) { + if (g_bindings[i].keycode == keycode && + g_bindings[i].modifiers == modifiers) { + g_bindings[i].action(); + return true; // 事件被消费 + } + } + return false; // 继续传递 +} + +static bool keybindings_init(void) { + wm_hook_register(HOOK_KEY_PRESS, handle_key_press, NULL, 0); + return true; +} + +static void keybindings_shutdown(void) { + // 自动取消注册 +} + +wm_plugin_t plugin_keybindings = { + .name = "keybindings", + .version = "1.0", + .init = keybindings_init, + .shutdown = keybindings_shutdown +}; +#+END_SRC + +**扩展4:窗口规则(插件)** + +#+BEGIN_SRC c +// plugins/rules.c + +#include "plugins/rules.h" +#include "core/hooks.h" + +typedef struct { + char *class_name; + bool floating; + bool sticky; + wm_workspace_id_t target_workspace; +} rule_t; + +static rule_t g_rules[] = { + { .class_name = "floating_window", .floating = true }, + { .class_name = "sticky_terminal", .sticky = true }, +}; + +static void on_window_manage(wm_window_t *window, void *user_data) { + // 应用规则 + for (size_t i = 0; i < sizeof(g_rules)/sizeof(g_rules[0]); i++) { + if (strcmp(window->class.name, g_rules[i].class_name) == 0) { + if (g_rules[i].floating) { + wm_set_window_floating(window->id, true); + } + // ... 应用其他规则 + } + } +} + +static bool rules_init(void) { + wm_hook_register(HOOK_WINDOW_MANAGE, on_window_manage, NULL, 0); + return true; +} + +static void rules_shutdown(void) { + // 自动取消注册 +} + +wm_plugin_t plugin_rules = { + .name = "rules", + .version = "1.0", + .init = rules_init, + .shutdown = rules_shutdown +}; +#+END_SRC + +**扩展5:配置系统(插件)** + +#+BEGIN_SRC c +// plugins/config.c + +#include "plugins/config.h" +#include "core/hooks.h" + +typedef struct { + char *key; + char *value; +} config_item_t; + +static config_item_t g_config[] = { + { .key = "terminal", .value = "st" }, + { .key = "browser", .value = "firefox" }, +}; + +static const char* config_get(const char *key) { + for (size_t i = 0; i < sizeof(g_config)/sizeof(g_config[0]); i++) { + if (strcmp(g_config[i].key, key) == 0) { + return g_config[i].value; + } + } + return NULL; +} + +static void on_config_reload(void *event_data) { + // 重新加载配置文件 + load_config_file("~/.config/zdwm/config"); +} + +static bool config_init(void) { + // 加载初始配置 + load_config_file("~/.config/zdwm/config"); + + // 监听配置重载信号 + wm_hook_register(HOOK_CONFIG_RELOAD, on_config_reload, NULL, 0); + + return true; +} + +static void config_shutdown(void) { + // 清理配置资源 +} + +wm_plugin_t plugin_config = { + .name = "config", + .version = "1.0", + .init = config_init, + .shutdown = config_shutdown +}; +#+END_SRC + +* 核心实现(简化版) + +#+BEGIN_SRC c +// src/core/zdwm.c + +// 核心 API:触发钩子 +void wm_hook_trigger_layout_calculate(wm_workspace_t *workspace, rect_t *geometries, size_t count) { + wm_hook_t *hook = g_hooks[HOOK_LAYOUT_CALCULATE]; + while (hook) { + wm_hook_layout_calculate_t callback = (wm_hook_layout_calculate_t)hook->callback; + callback(workspace, geometries, count, hook->user_data); + hook = hook->next; + } +} + +bool wm_hook_trigger_key_press(uint32_t keycode, uint32_t modifiers) { + wm_hook_t *hook = g_hooks[HOOK_KEY_PRESS]; + while (hook) { + wm_hook_key_press_t callback = (wm_hook_key_press_t)hook->callback; + if (callback(keycode, modifiers, hook->user_data)) { + return true; // 事件被消费 + } + hook = hook->next; + } + return false; +} + +// 主循环(简化版) +void wm_run(void) { + xcb_generic_event_t *event; + + while (g_running) { + // 1. 获取事件 + event = xcb_wait_for_event(g_conn); + + // 2. 处理事件 + wm_handle_event(event); + + // 3. 更新布局(如果需要) + if (g_dirty_layout) { + wm_update_layout(); + } + + // 4. 渲染(如果有状态栏插件) + // (通过钩子让状态栏插件自己绘制) + } +} + +// 布局更新 +void wm_update_layout(void) { + for (size_t i = 0; i < g_state.workspace_count; i++) { + wm_workspace_t *ws = &g_state.workspaces[i]; + + // 分配几何数组 + rect_t geometries[ws->window_count]; + + // 触发布局钩子(布局插件会计算几何) + wm_hook_trigger_layout_calculate(ws, geometries, ws->window_count); + + // 应用几何到窗口 + for (size_t j = 0; j < ws->window_count; j++) { + wm_window_t *win = wm_find_window(ws->windows[j]); + win->geometry = geometries[j]; + } + } +} +#+END_SRC + +* 扩展交互示例 + +**场景1:状态栏需要显示布局信息** + +#+BEGIN_SRC c +// 状态栏插件订阅布局钩子 +static void on_layout_calculate(wm_workspace_t *workspace, + rect_t *geometries, + size_t count, + void *user_data) { + // 更新状态栏显示的布局信息 + update_layout_indicator(workspace, geometries, count); +} +#+END_SRC + +**场景2:多个布局算法共存** + +#+BEGIN_SRC c +// 插件1:tile 布局 +wm_plugin_t plugin_tile = { .name = "tile", ... }; + +// 插件2:monocle 布局 +wm_plugin_t plugin_monocle = { .name = "monocle", ... }; + +// 运行时切换 +void switch_layout(const char *layout_name) { + // 卸载当前布局插件 + wm_plugin_unload("tile"); + + // 加载新布局插件 + wm_plugin_load(find_plugin(layout_name)); +} +#+END_SRC + +**场景3:插件间通信** + +#+BEGIN_SRC c +// 场景:网络速度插件需要通知状态栏插件更新 + +// 方案1:通过核心状态 +// network 插件更新核心状态中的某个字段 +// statusbar 插件在 HOOK_LAYOUT_CALCULATE 中读取该字段 + +// 方案2:直接函数调用(如果两个插件在同一进程中) +extern void statusbar_update_network_speed(uint64_t speed); + +void network_on_speed_update(uint64_t speed) { + statusbar_update_network_speed(speed); +} + +// 方案3:自定义事件(如果有事件总线插件) +event_bus_publish("network.speed_changed", &speed); +#+END_SRC + +* 扩展最佳实践 + +**✅ 推荐做法** + +1. *插件职责单一* + - 一个插件做一件事 + - 例如:tile 布局插件只负责布局计算 + +2. *使用钩子优先级* + - 核心功能(如窗口管理)优先级 = 0 + - 扩展功能(如状态栏)优先级 = 10 + - 调试功能优先级 = 100 + +3. *插件间依赖最小化* + - 尽量避免插件间直接调用 + - 通过核心状态中转 + +4. *错误处理* + - 插件 init 失败应返回 false + - 核心会跳过该插件,继续运行 + +**❌ 避免的做法** + +1. *插件直接修改核心结构* + #+BEGIN_SRC c + // ❌ 不好:插件直接修改核心 + extern wm_state_t *g_state; + g_state->current_workspace = 1; + + // ✅ 好:通过核心 API + wm_switch_workspace(1); + #+END_SRC + +2. *插件阻塞执行* + #+BEGIN_SRC c + // ❌ 不好:插件中有阻塞操作 + void statusbar_init(void) { + sleep(1); // 阻塞主循环! + } + + // ✅ 好:异步操作 + void statusbar_init(void) { + pthread_create(&thread, NULL, async_init, NULL); + } + #+END_SRC + +3. *插件循环依赖* + - 插件A依赖插件B + - 插件B依赖插件A + - 解决方案:通过核心状态解耦 + +* 与 mini_core_draft 对比 + +| 特性 | mini_core_draft | 最小核心 + 插件 | +|------|-----------------|----------------| +| 核心代码行数 | ~3000行 | ~1500行 | +| 抽象层 | 3-4层 | 1层(钩子) | +| 扩展方式 | 服务注册 | 插件+钩子 | +| 学习曲线 | 陡峭 | 平缓 | +| 性能 | 中等 | 最好 | +| 可扩展性 | 很高 | 高 | +| 复杂度 | 高 | 低 | + +* 核心代码量估算 + +#+BEGIN_EXAMPLE +核心: + - zdwm.h (核心 API): 200 行 + - zdwm.c (核心实现): 500 行 + - hooks.c (钩子系统): 200 行 + - plugin.c (插件加载): 150 行 + ------------------------------------ + 核心总计: ~1050 行 + +插件示例: + - layout_tile.c: 100 行 + - statusbar.c: 300 行 + - keybindings.c: 150 行 + - rules.c: 200 行 + ------------------------------------ + 插件总计: ~750 行 + +总计: ~1800 行完整功能的窗口管理器 +#+END_EXAMPLE + +* 总结 + +*最小核心 + 插件*方案的优势: + +1. *核心真正最小* - 只做窗口管理,1500行代码 +2. *扩展性有保证* - 钩子系统允许任何扩展 +3. *性能最优* - 一层间接,零开销抽象 +4. *易于理解* - 新手也能看懂核心代码 +5. *渐进式开发* - 先核心,后扩展 + +这个设计比 ~mini_core_draft~ 简单很多,但扩展性并不差。关键是*钩子系统*设计得当,可以实现大部分扩展需求。 diff --git a/docs/config_system.org b/docs/config_system.org new file mode 100644 index 0000000..7abbf4a --- /dev/null +++ b/docs/config_system.org @@ -0,0 +1,412 @@ +* ZDWM 配置系统设计 + +** 概述 + +配置系统现在只保留两类正式输入: + +1. 编译进二进制的默认配置 +2. 用户动态库配置(=~/.config/zdwm/config.c= 编译得到 =config.so=) + +不再支持 XResources,也不再保留 =Xft.dpi= 作为兼容输入。字体、DPI、颜色、边框、布局、规则、快捷键和服务设置都只来自默认配置和动态库配置。 + +配置系统的职责也收紧为: + +1. 生成 core-native 配置快照。 +2. 为 runtime adapter、service 层和输入绑定层提供稳定输入。 +3. 不直接操作 =wm_state_t= 。 +4. 不再暴露 =client_t= 、=tag_t= 、=monitor_t= 之类的旧对象。 + +** 设计目标 + +1. 配置层继续放在核心之外。 +2. 配置结果必须能无损对接新的 =runtime/policy/layout/service= 边界。 +3. 配置重载应以“重建快照并显式应用”为单位,而不是任意时刻写全局内存。 +4. 彻底移除 XResources 路径,避免维护两套并行语义。 + +** 配置来源与优先级 + +最终有效配置按下面顺序构建: + +#+BEGIN_EXAMPLE +默认配置 + ↓ +动态库配置 config.so + ↓ +冻结为 zdwm_config_t +#+END_EXAMPLE + +动态库配置优先级高于默认配置。 + +** 非目标 + +下面这些能力不再属于配置系统设计范围: + +1. =Zdwm.*= 风格的 XResources 覆盖。 +2. =Xft.dpi= 的额外兼容读取。 +3. =hook_manage_new(client_t *)= 和 =hook_manage_unmanage(client_t *)= 这类直接对象钩子。 +4. =arrange(tag_t *)= 这类依赖旧 tag 模型的布局接口。 +5. 从配置层直接访问或修改 =wm= 全局对象。 + +** 配置输出模型 + +配置系统输出一份冻结后的配置快照,而不是一组可随时调用的副作用回调。 + +*** 外观配置 + +#+BEGIN_SRC c +typedef struct zdwm_appearance_config_t { + char *font_family; + uint32_t font_size; + uint32_t dpi; + uint16_t border_width; + uint16_t bar_y_padding; + uint16_t tag_x_padding; + color_set_t colors; +} zdwm_appearance_config_t; +#+END_SRC + +这部分不进入最小核心状态,而是供 render/bar/text 这类核心外服务使用。 + +*** workspace 配置 + +=wm_workspace_t= 是运行时状态结构,里面包含 =focused_window_id= 这类不属于静态配置的数据。因此配置层应单独定义 workspace 配置描述: + +#+BEGIN_SRC c +typedef struct zdwm_workspace_config_t { + wm_workspace_id_t id; + wm_layout_id_t initial_layout_id; + + const char *name; + const char *symbol_text; + const char *symbol_icon_path; +} zdwm_workspace_config_t; +#+END_SRC + +runtime adapter 会把它拆成: + +1. =wm_workspace_t= 的初始数组 +2. =wm_workspace_desc_t= 描述表 + +*** 管理规则 + +#+BEGIN_SRC c +typedef struct zdwm_manage_rule_t { + const char *title; + const char *app_id; + const char *class_name; + const char *instance_name; + + wm_workspace_id_t workspace_id; + wm_manage_window_init_t initial_state; + bool switch_to_workspace; +} zdwm_manage_rule_t; +#+END_SRC + +规则层的职责仅限于: + +1. 匹配窗口元数据。 +2. 决定 =MANAGE_WINDOW= 的 =workspace_id= 。 +3. 决定 =MANAGE_WINDOW= 的 =initial_state= 。 +4. 可选地决定是否切到目标 workspace。 + +规则层不直接管理窗口对象,也不直接写 =wm_state_t= 。 + +*** 快捷键绑定 + +快捷键也需要从旧的“直接 action 回调”收敛到新边界。 + +#+BEGIN_SRC c +typedef enum zdwm_binding_target_t { + ZDWM_BINDING_CORE_COMMAND, + ZDWM_BINDING_SERVICE_ACTION, +} zdwm_binding_target_t; + +typedef struct zdwm_service_action_t { + const char *service_name; + const char *action_name; + const char *string_arg; + int32_t int_arg; +} zdwm_service_action_t; + +typedef struct zdwm_keybinding_t { + uint32_t modifiers; + xkb_keysym_t keysym; + zdwm_binding_target_t target; + union { + wm_command_t command; + zdwm_service_action_t service_action; + } as; +} zdwm_keybinding_t; +#+END_SRC + +=core command= 用于工作区切换、布局切换、浮动切换、焦点切换等窗口管理行为。 + +=service action= 用于启动外部程序、刷新状态服务、触发核心外功能。 + +*** 服务设置 + +#+BEGIN_SRC c +typedef struct zdwm_service_setting_t { + const char *service_name; + const char *key; + const char *value; +} zdwm_service_setting_t; +#+END_SRC + +服务配置由配置层生成,但仍由 service 层解释和持有。 + +*** 总配置快照 + +#+BEGIN_SRC c +typedef struct zdwm_config_source_t { + bool has_user_lib; +} zdwm_config_source_t; + +typedef struct zdwm_runtime_config_t { + wm_policy_config_t policy; + + zdwm_workspace_config_t *workspaces; + size_t workspace_count; + + wm_layout_slot_t *layouts; + size_t layout_count; +} zdwm_runtime_config_t; + +typedef struct zdwm_config_t { + zdwm_runtime_config_t runtime; + zdwm_appearance_config_t appearance; + + zdwm_manage_rule_t *rules; + size_t rule_count; + + zdwm_keybinding_t *keybindings; + size_t keybinding_count; + + const char *const *autostart_list; + + zdwm_service_setting_t *service_settings; + size_t service_setting_count; + + zdwm_config_source_t source; +} zdwm_config_t; +#+END_SRC + +注意这里故意不把 =outputs= 和启动时已有窗口塞进配置结果里。它们属于 backend 发现结果,不属于静态配置。 + +** 与最小核心的集成方式 + +配置层与 runtime 的拼装分为两步: + +*** 第一步:构建配置快照 + +#+BEGIN_EXAMPLE +默认配置 + ↓ +config.so 覆盖与增量注册 + ↓ +zdwm_config_t +#+END_EXAMPLE + +*** 第二步:由 adapter 组装 runtime bootstrap + +#+BEGIN_EXAMPLE +zdwm_config_t + + +backend 发现的 outputs + + +backend 扫描得到的初始 MANAGE_WINDOW 命令 + ↓ +wm_runtime_bootstrap_t +#+END_EXAMPLE + +组装规则应为: + +1. =policy= 来自 =config.runtime.policy= 。 +2. =workspaces= 来自 =config.runtime.workspaces[]= 的静态定义。 +3. =workspace_descs= 来自 =config.runtime.workspaces[]= 中的展示字段。 +4. =outputs= 来自 backend。 +5. =initial_commands= 来自 backend 对已有窗口的扫描和规则匹配。 + +这样配置层不需要知道当前有哪些物理输出,也不需要自己扫描窗口。 + +** 动态库 API + +动态库 API 由“builder 风格”的配置写入接口组成: + +#+BEGIN_SRC c +typedef struct zdwm_api_t { + uint32_t version; + + // appearance + void (*set_font)(const char *family, uint32_t size); + void (*set_dpi)(uint32_t dpi); + void (*set_color)(const char *name, const char *hex); + void (*set_border_width)(uint16_t width); + void (*set_padding)(uint16_t bar_y, uint16_t tag_x); + + // runtime config + void (*set_policy)(wm_policy_config_t policy); + void (*define_workspace)(zdwm_workspace_config_t workspace); + void (*register_layout)(wm_layout_slot_t layout); + + // rules + void (*add_rule)(zdwm_manage_rule_t rule); + + // bindings + void (*add_keybinding)(zdwm_keybinding_t binding); + void (*clear_keybindings)(void); + + // autostart and services + void (*add_autostart)(const char *command); + void (*set_service_option)(const char *service_name, + const char *key, + const char *value); +} zdwm_api_t; +#+END_SRC + +用户动态库入口保持简单: + +#+BEGIN_SRC c +typedef struct zdwm_user_config_t { + void (*cleanup)(void); +} zdwm_user_config_t; + +zdwm_user_config_t *zdwm_config_init(zdwm_api_t *api); +#+END_SRC + +=cleanup()= 只负责动态库自身资源,不负责 runtime 状态回收。 + +** 加载流程 + +#+BEGIN_SRC c +zdwm_config_t *config_load(void) { + zdwm_config_t *config = calloc(1, sizeof(*config)); + + config_apply_defaults(config); + config_load_user_lib(config); + config_finalize(config); + + return config; +} +#+END_SRC + +这里的 =config_finalize()= 建议至少做这些事情: + +1. 检查 workspace id 是否唯一。 +2. 检查 layout id 是否唯一。 +3. 检查规则和快捷键数组是否合法。 +4. 补齐未设置的默认值。 +5. 冻结动态数组,避免运行时再被随意写入。 + +** 管理规则接入链路 + +管理规则不直接操作窗口,推荐链路如下: + +#+BEGIN_EXAMPLE +backend 发现新窗口 + ↓ +同步窗口 metadata + ↓ +在 rules 中匹配 + ↓ +生成 WM_COMMAND_MANAGE_WINDOW + - workspace_id + - initial_state + ↓ +runtime / policy apply_command() +#+END_EXAMPLE + +这样规则层只负责“把 metadata 翻译成命令参数”。 + +** 快捷键接入链路 + +#+BEGIN_EXAMPLE +backend 输入事件 + ↓ +keybinding table 匹配 + ↓ +若 target == ZDWM_BINDING_CORE_COMMAND: + 生成 wm_command_t +若 target == ZDWM_BINDING_SERVICE_ACTION: + 发送给 service 层 +#+END_EXAMPLE + +配置层不再保存 =void (*action)(const void *)= 这类旧式回调。 + +** 热重载 + +配置热重载仍然可以保留,但边界必须清晰。 + +推荐流程: + +#+BEGIN_EXAMPLE +收到 SIGHUP + ↓ +重建 zdwm_config_t + ↓ +原子替换: + - appearance + - policy + - keybindings + - workspace descriptors + - layout registry + - service settings + ↓ +通知相关 service 刷新 +#+END_EXAMPLE + +热重载约束: + +1. 不直接改 =wm_state_t= 。 +2. 规则变更只影响后续新管理的窗口,不回溯重算现有窗口。 +3. workspace 描述、颜色、字体、DPI、bindings、policy 适合热重载。 +4. 大规模布局或 service 拓扑变化,必要时可以走受控重启。 + +** 文件结构 + +#+BEGIN_EXAMPLE +src/ +├── config.h # 配置系统头文件 +├── config.c # 配置加载主逻辑 +├── config_defaults.c # 默认配置 +├── config_lib.c # 动态库加载 +├── config_api.c # builder API +└── config_validate.c # 配置收尾校验(可选) + +docs/ +└── config_system.org # 本文档 +#+END_EXAMPLE + +这里不再有 =config_xres.c= 或任何通用 XResources 解析模块。 + +** 迁移说明 + +下面这些旧设计应视为废弃: + +1. =默认配置 -> XResources -> 动态库配置= 三层覆盖模型。 +2. =Xft.dpi= 的兼容读取。 +3. =hook_manage_new(client_t *)= 和 =hook_manage_unmanage(client_t *)= 。 +4. =add_layout(... arrange(tag_t *tag))= 。 +5. 任何直接依赖 =client_t= 、=tag_t= 、=monitor_t= 的配置接口。 + +迁移后的对应关系: + +1. tag 名称与符号 → =zdwm_workspace_config_t= +2. 布局注册 → =wm_layout_slot_t= +3. 窗口规则 → =zdwm_manage_rule_t= +4. 快捷键动作 → =wm_command_t= 或 =zdwm_service_action_t= +5. 外观设置 → =zdwm_appearance_config_t= + +** 实施优先级 + +1. =config_defaults.c= +先保证“无用户配置”也能跑。 + +2. =config.c + config_lib.c= +打通默认配置和动态库配置装配。 + +3. =config_api.c= +落地新的 builder API,替代旧 action/hook 模型。 + +4. runtime adapter 对接 +把 =zdwm_config_t= 装配成 =wm_runtime_bootstrap_t= 、规则表、service 设置和 keybinding table。 diff --git a/docs/min_core_draft/README.org b/docs/min_core_draft/README.org index 931ab98..6ee4d48 100644 --- a/docs/min_core_draft/README.org +++ b/docs/min_core_draft/README.org @@ -18,6 +18,9 @@ 3. =state= 负责保存窗口、工作区、输出和全局堆叠顺序,是唯一真状态。 +采用不透明结构体设计,内部实现完全封装。 +窗口元数据(title/app_id/class/instance)直接存储在 =wm_window_t= 中。 +工作区名称存储在 =wm_workspace_t= 中。 4. =policy= 负责把事件路由为命令,并把命令应用到状态。 @@ -25,11 +28,63 @@ 5. =layout= 负责为某个输出上当前显示的工作区中的平铺窗口计算几何。 -6. =window metadata= -负责窗口标题、类名、实例名、app id 这类展示和匹配信息。 +* ID 分配策略 -7. =workspace descriptor= -负责 workspace 在 bar 中显示时使用的名字和文本或图标符号。 +最小核心采用"运行时固定,启动时配置"的 ID 策略: + +**半静态实体(启动时分配,运行时固定)**: +- =workspace= : 启动时根据配置分配 N 个(如 10 个),ID = 数组索引(0-N-1) +- =output= : 启动时根据扫描到的显示器数量分配,ID = 数组索引 +- =layout= : 启动时根据注册的布局算法分配,ID = 数组索引 + +这些实体在启动时一次性分配,运行时不再增删。 +访问时直接用索引,零开销查找,无需映射表。 + +```c +// 半静态实体:直接访问(零开销) +wm_workspace_t *ws = &state->workspaces[workspace_id]; // O(1) +wm_output_t *output = &state->outputs[output_id]; // O(1) +``` + +**动态实体(运行时增删)**: +- =window= : 动态数组,ID 由后端生成(如 X11 的 xcb_window_t) +查找时线性搜索 O(n),对于几十到几百个窗口性能足够。 +(后续如成为性能热点,可改用 hashmap 优化到 O(1)) + +```c +// 半静态实体:直接访问(零开销) +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) +``` + +**设计优势**: +- 灵活性:workspace 数量由配置决定,不硬编码 +- 性能:半静态实体零开销访问,动态实体简单线性查找 +- 简单性:无需复杂的映射表,代码更清晰 + +* 热插拔策略 + +**显示器变化时自动重启 WM**: + +当检测到显示器配置变化(新增/删除/几何改变)时,最简单且可靠的方案是重启整个窗口管理器。 + +理由: +1. 逻辑简单:无需处理复杂的运行时重配置 +2. 状态一致:避免重配置后的不一致状态 +3. 实现成本低:不需要增量更新逻辑 +4. 可靠性高:从干净状态启动,避免边界情况 + +实现方式: +- 后端检测到 RandR/Output 变化时,退出主循环 +- 由启动脚本(如 systemd user service 或 .xinitrc)自动重启 WM +- WM 重启前保存必要状态(如当前工作区),启动后恢复 + +对用户影响: +- 显示器拔插时窗口会短暂闪烁(~1-2 秒) +- 但比复杂的增量逻辑更可靠,符合"简单优先"原则 * 非核心但常见的服务 @@ -47,6 +102,145 @@ 4. =plugin= 动态扩展加载。 +* 运行时装配与所有权 + +为了避免再次出现当前全局 =wm= 那样的大对象耦合,最小核心里的所有权再收紧一层: + +1. =runtime= 拥有 =state= 、=plan= 、=command_buffer= 和 =layout_registry= 。 +2. =runtime= 也拥有 =policy config= 、交互态和服务注册表。 +3. =backend= 只负责平台事件翻译和平台副作用,不拥有控制状态,不直接拼 bar。 +4. 窗口元数据(title/app_id/class/instance)直接存储在 =wm_window_t= 中,与控制状态一同管理。 +5. 工作区名称存储在 =wm_workspace_t= 中,与控制状态一同管理。 + +* 启动与初始化约定 + +最小核心的初始化流程: + +1. =wm_runtime_init()= 接收一份 bootstrap 描述,包含: + - 工作区数量和初始配置(由用户配置决定) + - 布局算法注册(由代码或配置决定) + - 策略配置(focus_raises 等) + - 初始命令(如启动时扫描到的窗口) + +2. 启动时扫描到的已有窗口,不直接塞进 =wm_state_t= ,而是翻译成一批 =MANAGE_WINDOW= 命令进入 runtime。 + +3. 工作区在启动时根据配置分配 N 个,运行时不再增删。 + +4. 输出在启动时扫描并分配对应数量的数组,运行时不再增删。 + +* 显示器热插拔策略 + +当检测到显示器配置变化(新增/删除/几何改变)时: + +1. 后端检测到 RandR/Output 变化,发送 =WM_EVENT_QUIT= 给 runtime +2. Runtime 退出主循环,清理资源 +3. 由启动脚本(systemd/xinitrc)自动重启 WM +4. WM 重启后重新扫描显示器配置,从干净状态启动 + +**优势**: +- ✅ 逻辑简单,无需复杂的运行时重配置 +- ✅ 状态一致,避免增量更新的边界情况 +- ✅ 实现可靠,从干净状态启动 + +**代价**: +- 窗口会短暂闪烁(~1-2 秒) +- 但相比复杂的热插拔逻辑,这是合理的工程权衡 + +* Workspace-Output 配置 + +工作区在启动时根据输出数量动态配置,固定归属到特定输出: + +**配置方式 1:手动指定** +```lua +workspaces = { + { id = 1, name = "web", output_id = 0 }, -- DP-1 的 workspace + { id = 2, name = "code", output_id = 0 }, + { id = 3, name = "chat", output_id = 1 }, -- DP-2 的 workspace + { id = 4, name = "media", output_id = 1 }, +} +``` + +**配置方式 2:规则式** +```lua +-- 每个 output 自动分配 N 个 workspace +workspace_distribution = { + { output_id = 0, count = 5 }, -- DP-1: workspace 1-5 + { output_id = 1, count = 5 }, -- DP-2: workspace 6-10 +} +``` + +**配置方式 3:脚本式** +```lua +function configure_workspaces(outputs) + local workspaces = {} + local ws_id = 1 + + for _, output in ipairs(outputs) do + for i = 1, 3 do -- 每个 output 3 个 workspace + table.insert(workspaces, { + id = ws_id, + name = tostring(ws_id), + output_id = output.id + }) + ws_id = ws_id + 1 + end + end + + return workspaces +end +``` + +**可见性规则**: +- 窗口仅在归属的 output 上可见 +- 窗口可见 iff:窗口的 workspace 是该 output 的当前显示 workspace +- 切换 workspace 只影响归属的 output + +* 策略配置 + +前一版草案里有几处“建议作为可选策略”的描述,现在收敛为显式 =wm_policy_config_t= : + +1. =focus_raises= +焦点切换时是否隐式执行 raise。 + +2. =sticky_windows_participate_in_direction_focus= +sticky 窗口是否参与方向焦点搜索。 + +3. =manage_sets_focus= +新受管窗口是否默认夺取所属 workspace 的焦点。 + +4. =switch_workspace_restores_last_focus= +切换工作区时是否恢复该 workspace 的最近焦点。 + +5. =minimize_clears_focus= +最小化当前焦点窗口时是否清空或重选该 workspace 焦点。 + +* 交互态 + +move/resize 这类拖拽交互不属于 =wm_state_t= 真状态,但也不能继续散落在平台分支里。runtime 应维护一个单一交互会话: + +1. 空闲 +2. 正在移动 floating 窗口 +3. 正在调整 floating 窗口大小 + +交互会话至少记录目标窗口、起始指针位置、起始窗口矩形和起始 output,供 =policy.route_event()= 生成连续的 move/resize 命令。 + +* 核心外服务接入约定 + +既然 =render/status/bar= 被明确放在核心外,就需要一条稳定的接入边界: + +1. =WM_EFFECT_RENDER_OUTPUT= 不是平台副作用,而是 runtime 发给服务层的失效通知。 +2. =backend.apply_effect()= 只接收 map/unmap/configure/focus/restack 这类平台 effect。 +3. =runtime= 在处理完 plan 后,把 render 和 metadata 相关事件广播给已注册服务。 +4. 服务只读 =state/meta/descriptor= 快照,不直接改 =wm_state_t= 。 + +* 配置集成约定 + +=config= 仍然在核心外,但它的输出必须改成 core-native 数据,而不是旧的 =client_t/tag_t= 钩子: + +1. 配置加载结果应产出 workspace 描述、policy 配置、layout 注册、规则表、快捷键到 command 的映射和服务设置。 +2. 配置层不得直接持有 =client_t= 、=tag_t= 或直接修改 =wm_state_t= 。 +3. 配置重载的边界是“重建 bootstrap 和服务配置,再显式应用到 runtime”,而不是任意时刻从外部写内存。 + * 对象关系 这套草案里的关系固定为: @@ -54,35 +248,41 @@ 1. =window -> workspace= 每个窗口只属于一个工作区。 -2. =output -> current_workspace= -每个输出同一时刻只显示一个当前工作区。 +2. =workspace -> output= +每个工作区固定归属某个输出,运行时不再改变。 -3. =window visible on output= +3. =output -> current_workspace= +每个输出同一时刻只显示一个当前工作区(从归属的工作区中选择)。 + +4. =window visible on output= 这是派生结果,不是独立存储的真相。 -4. =stack_order[]= +5. =stack_order[]= 这是全局 z-order 的唯一真相,从下到上排列。 -5. =window metadata= -窗口标题等字符串信息不放进最小控制状态,而是作为独立元数据表存在。 +6. =window metadata= +窗口标题、类名等字符串信息直接存储在 =wm_window_t= 中。 +核心算法(layout/policy)不应依赖这些字段,仅用于匹配规则和服务层展示。 -6. =workspace descriptor= -workspace 在 bar 中显示时使用的名字和符号不放进运行时控制状态,而是作为独立描述表存在。 +7. =workspace name= +工作区名称直接存储在 =wm_workspace_t= 中,用于状态栏显示。 +核心算法不应依赖此字段。 * 核心不变量 1. 每个窗口必须且只能属于一个工作区。 -2. 每个输出必须且只能显示一个当前工作区。 -3. 可见性由窗口归属、输出当前工作区和最小化状态共同推导。 -4. =geometry_mode= 只负责几何模式,不负责 z-order。 -5. =stack_order[]= 只负责 z-order,不负责几何模式。 -6. =floating= 是二值状态:窗口要么由 layout 决定基础几何,要么由 =float_rect= 决定基础几何。 -7. =sticky= 只影响可见性,不改变窗口归属工作区。 -8. floating 窗口可以跨多个输出显示,但归属工作区仍然只有一个。 -9. 多输出下 floating 窗口是否迁移工作区,由锚点和跨输出策略决定,不由覆盖面积决定。 -10. layout 的名字和 bar 符号属于布局注册表描述信息,不属于工作区运行状态。 -11. 窗口标题和类名属于窗口元数据,不属于窗口控制状态。 -12. workspace 的显示名称和 bar 符号属于 workspace 描述信息,不属于工作区运行状态。 +2. 每个工作区必须且只能固定归属一个输出,运行时不再改变。 +3. 每个输出必须且只能显示一个当前工作区(从归属的工作区中选择)。 +4. 可见性由窗口归属、工作区归属、输出当前工作区和最小化状态共同推导。 +5. =geometry_mode= 只负责几何模式,不负责 z-order。 +6. =stack_order[]= 只负责 z-order,不负责几何模式。 +7. =floating= 是二值状态:窗口要么由 layout 决定基础几何,要么由 =float_rect= 决定基础几何。 +8. =sticky= 只影响可见性,不改变窗口归属工作区。 +9. floating 窗口可以跨多个输出显示,但归属工作区仍然只有一个。 +10. 多输出下 floating 窗口是否迁移工作区,由锚点和跨输出策略决定,不由覆盖面积决定。 +11. layout 的 name 字段同时用于状态栏显示,应为 1-2 个字符的简短标识符(如 "T", "M")。 +12. 窗口标题、类名等元数据存储在 =wm_window_t= 中,与控制状态一同管理。 +13. 工作区名称存储在 =wm_workspace_t= 中,核心算法不应依赖此字段。 * Generation 字段 @@ -100,11 +300,13 @@ workspace 在 bar 中显示时使用的名字和符号不放进运行时控制 =runtime= 负责固定主循环顺序: 1. =backend.next_event()= -2. =policy.route_event()= -3. =policy.apply_command()= -4. 根据 =dirty_flags= 决定是否重新运行 =layout= -5. 逐条执行 =wm_effect_t= -6. 可选的渲染刷新 +2. runtime 先处理窗口元数据更新这类辅助事件,并直接更新 =wm_window_t= 中的元数据字段 +3. =policy.route_event()= +4. =policy.apply_command()= +5. 根据 =dirty_flags= 决定是否重新运行 =layout= 并解析最终矩形 +6. 将平台 effect 发送给 =backend= +7. 将 render 失效通知发送给服务层 +8. =backend.flush()= * 文件说明 @@ -112,7 +314,7 @@ workspace 在 bar 中显示时使用的名字和符号不放进运行时控制 通用标量类型、矩形、窗口几何模式和策略枚举。 2. =wm_state.h= -窗口、工作区、输出和全局状态结构,以及查询接口。 +全局状态容器,完全封装的内部实现,提供统一的访问 API。 3. =wm_event.h= 统一后的运行时事件。 @@ -127,32 +329,35 @@ workspace 在 bar 中显示时使用的名字和符号不放进运行时控制 6. =wm_layout.h= 布局注册表、布局输入和布局输出。 -7. =wm_window_meta.h= -窗口标题、类名、实例名和 app id 的元数据表。 - -8. =wm_workspace_desc.h= -workspace 的显示名称、文本符号和图标符号描述表。 - -9. =wm_backend.h= +7. =wm_backend.h= 后端子系统接口。 -10. =wm_policy.h= +8. =wm_policy_config.h= +显式策略开关,替代散落在文档正文里的"可选策略"。 + +9. =wm_policy.h= 事件路由和命令应用接口。 -11. =wm_runtime.h= +10. =wm_runtime.h= 运行时上下文和生命周期接口。 +11. =wm_service.h= +核心外服务的注册和事件订阅接口。 + 12. [[file:WM_COMMAND_RULES.org][WM_COMMAND_RULES.org]] 关键命令的前置条件、状态转移、可见性和副作用规则。 13. [[file:WM_POLICY_APPLY_COMMAND_SKELETON.org][WM_POLICY_APPLY_COMMAND_SKELETON.org]] =wm_policy_apply_command()= 的伪代码骨架和实现顺序建议。 +14. [[file:WORKSPACE_CONFIG_EXAMPLES.org][WORKSPACE_CONFIG_EXAMPLES.org]] +Workspace-Output 配置示例和最佳实践。 + * 下一步实施顺序 当前阶段不建议继续扩展抽象,应该开始把最小核心草案落成可运行骨架。推荐顺序如下: -1. 在 =src/core/= 下建立最小骨架,只放 =wm_types/state/plan/layout/policy/runtime= 的头文件和空实现,先不要替换现有 =src/= 逻辑。 +1. 在 =src/core/= 下建立最小骨架,至少放 =wm_types/state/plan/layout/policy/policy_config/runtime/service= 的头文件和空实现,先不要替换现有 =src/= 逻辑。 2. 优先实现 =state + query + plan= 这一层,把动态数组管理、=find_*= 查询函数和 =stack_order[]= 操作补齐。 diff --git a/docs/min_core_draft/WM_COMMAND_RULES.org b/docs/min_core_draft/WM_COMMAND_RULES.org index 5ecdee9..a26c567 100644 --- a/docs/min_core_draft/WM_COMMAND_RULES.org +++ b/docs/min_core_draft/WM_COMMAND_RULES.org @@ -13,6 +13,7 @@ 7. =floating= 决定基础几何来源: =floating = false= 时,基础几何来自 layout 结果。 =floating = true= 时,基础几何来自 =float_rect= 。 +8. 文档里出现的可选行为,应优先固化到 =wm_policy_config_t= ,而不是继续留在 prose 里。 * 管理类命令 @@ -27,9 +28,10 @@ 1. 向 =state.windows[]= 添加一个新窗口。 2. 设定其 =workspace_id= 。 -3. 初始 =floating= 、=sticky= 、=urgent= 、=geometry_mode= 使用调用方或配置给出的默认值。 -4. 将窗口追加到 =stack_order[]= 顶部。 -5. 可选地将该窗口设为所属工作区的 =focused_window_id= 。 +3. 初始 =floating= 、=sticky= 、=urgent= 、=geometry_mode= 由 =command.as.manage_window.initial_state= 显式给出。 +4. 若 =has_initial_float_rect == true= ,将其复制到 =float_rect= 。 +5. 将窗口追加到 =stack_order[]= 顶部。 +6. 是否将该窗口设为所属 workspace 的 =focused_window_id= ,由 =initial_state.set_focus= 或 =policy.manage_sets_focus= 决定。 副作用: @@ -38,6 +40,34 @@ 3. =WM_DIRTY_STACK= 4. =WM_DIRTY_RENDER= +** =WM_COMMAND_UPSERT_OUTPUT= + +状态变化: + +1. 若目标 output 已存在,只更新 =geometry= 和 =workarea= 。 +2. 若目标 output 不存在,则新建 output,并使用 =initial_workspace_id= 作为其 =current_workspace_id= 。 +3. 不修改任何窗口的 =workspace_id= 。 + +副作用: + +1. =WM_DIRTY_OUTPUT= +2. =WM_DIRTY_LAYOUT= +3. =WM_DIRTY_RENDER= + +** =WM_COMMAND_REMOVE_OUTPUT= + +状态变化: + +1. 从 =state.outputs[]= 中移除目标 output 。 +2. 不修改任何窗口的 =workspace_id= 。 +3. 若移除后暂时没有 output,允许 runtime 进入“无可见输出”的空闲态,等待后续热插拔恢复。 + +副作用: + +1. =WM_DIRTY_OUTPUT= +2. =WM_DIRTY_LAYOUT= +3. =WM_DIRTY_RENDER= + ** =WM_COMMAND_UNMANAGE_WINDOW= 前置条件: @@ -68,7 +98,7 @@ 可选策略: -1. 若 WM 采用 =focus raises= ,则在实现中额外执行 =raise_window= 逻辑。 +1. 若 =policy.focus_raises == true= ,则在实现中额外执行 =raise_window= 逻辑。 ** =WM_COMMAND_FOCUS_DIRECTION= @@ -76,7 +106,7 @@ 1. 只在目标 =output_id= 当前显示的 workspace 内选择焦点。 2. 候选窗口应排除 =geometry_mode == WM_GEOMETRY_MINIMIZED= 的窗口。 -3. =sticky= 窗口是否参与方向焦点切换,建议作为单独策略开关,不在最小核心中写死。 +3. =sticky= 窗口是否参与方向焦点切换,由 =policy.sticky_windows_participate_in_direction_focus= 决定。 ** =WM_COMMAND_RAISE_WINDOW= @@ -143,6 +173,21 @@ 3. 若 =policy == WM_CROSS_OUTPUT_KEEP_WORKSPACE= ,则不改 =workspace_id= ,只允许调用方随后自行调整几何。 4. 若 =policy == WM_CROSS_OUTPUT_MOVE_TO_TARGET_WORKSPACE= ,则把窗口 =workspace_id= 改成目标 output 当前工作区。 +* 布局命令 + +** =WM_COMMAND_SET_LAYOUT= + +状态变化: + +1. 设置目标 workspace 的 =layout_id= 。 +2. 不改变窗口的 =workspace_id= 。 +3. 不改变 =stack_order[]= 。 + +副作用: + +1. =WM_DIRTY_LAYOUT= +2. =WM_DIRTY_RENDER= + * 模式切换命令 ** =WM_COMMAND_TOGGLE_FLOATING= @@ -163,6 +208,20 @@ 1. =WM_DIRTY_LAYOUT= 2. =WM_DIRTY_RENDER= +** =WM_COMMAND_TOGGLE_STICKY= + +状态变化: + +1. 切换目标窗口的 =sticky= 状态。 +2. 不改变 =workspace_id= 。 +3. 不改变 =floating= 。 +4. 不改变 =geometry_mode= 。 + +副作用: + +1. =WM_DIRTY_LAYOUT= +2. =WM_DIRTY_RENDER= + ** =WM_COMMAND_TOGGLE_MAXIMIZE= 状态变化: @@ -211,6 +270,7 @@ 3. 不改变 =floating= 。 4. 不改变 =sticky= 。 5. minimized 后窗口不再参与显示和焦点候选。 +6. 若目标窗口是当前焦点,是否清空或重选焦点由 =policy.minimize_clears_focus= 决定。 副作用: diff --git a/docs/min_core_draft/WM_EVENT_ROUTING.org b/docs/min_core_draft/WM_EVENT_ROUTING.org new file mode 100644 index 0000000..10f3b9f --- /dev/null +++ b/docs/min_core_draft/WM_EVENT_ROUTING.org @@ -0,0 +1,397 @@ +* ZDWM 最小核心事件路由设计 + +这份文档定义 =wm_event_t= 到 =wm_command_t= 的完整路由规则,确保事件处理链路的每个环节都有明确的行为定义。 + +* 事件路由总体流程 + +#+BEGIN_SRC c +void wm_runtime_process_event(wm_runtime_t *runtime, const wm_event_t *event) { + // 1. 元数据事件特殊处理(不经过命令系统) + if (is_metadata_event(event)) { + runtime_handle_metadata_event(runtime, event); + return; + } + + // 2. 交互态下的特殊路由 + if (runtime->interaction.mode != WM_INTERACTION_NONE) { + if (route_interaction_event(runtime, event, &runtime->command_buffer)) { + return; // 事件已被交互态消费 + } + } + + // 3. 常规事件路由 + wm_command_buffer_reset(&runtime->command_buffer); + if (!wm_policy_route_event(&runtime->state, event, &runtime->command_buffer)) { + return; // 没有生成命令 + } + + // 4. 应用命令 + for (size_t i = 0; i < runtime->command_buffer.count; i++) { + wm_policy_apply_command( + &runtime->state, + &runtime->layouts, + &runtime->command_buffer.items[i], + &runtime->plan + ); + } + + // 5. 处理副作用 + if (runtime->plan.dirty_flags) { + runtime_apply_plan(runtime); + } +} +#+END_SRC + +* 元数据事件处理规则 + +元数据事件直接更新 =wm_window_t= 中的元数据字段,不经过命令系统: + +**=WM_EVENT_WINDOW_METADATA_CHANGED=** + +#+BEGIN_SRC c +void runtime_handle_metadata_event(wm_runtime_t *runtime, const wm_event_t *event) { + 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); + if (!win) { + return; // 窗口已被销毁 + } + + // 2. 直接更新窗口元数据字段 + if (e->changed_fields & WM_WINDOW_META_CHANGED_TITLE) { + free(win->title); + win->title = e->title ? strdup(e->title) : NULL; + } + 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; + } + if (e->changed_fields & WM_WINDOW_META_CHANGED_CLASS) { + free(win->class_name); + win->class_name = e->class_name ? strdup(e->class_name) : NULL; + } + if (e->changed_fields & WM_WINDOW_META_CHANGED_INSTANCE) { + free(win->instance_name); + win->instance_name = e->instance_name ? strdup(e->instance_name) : NULL; + } + + // 3. 标记 render 脏(状态栏需要更新) + runtime->plan.dirty_flags |= WM_DIRTY_RENDER; + + // 4. 通知订阅的服务(如状态栏组件) + wm_service_notify_metadata_changed(&runtime->services, + e->window_id, + e->changed_fields); +} +#+END_SRC + +**=WM_EVENT_STATUS_TICK=** + +定时器事件,也不经过命令系统: + +#+BEGIN_SRC c +void runtime_handle_status_tick(wm_runtime_t *runtime, const wm_event_t *event) { + // 通知状态服务更新 + wm_service_notify_status_tick(&runtime->services); + + // 状态服务更新后,如果产生了新的渲染需求,会通过命令系统处理 +} +#+END_SRC + +* 快捷键绑定系统 + +**配置格式**: + +#+BEGIN_SRC c +// keybindings.conf +keybindings = [ + { + // 键盘绑定 + keysym: "Mod4+Return", + modifiers: [Mod4], + command: { + type: WM_COMMAND_LAUNCH_TERMINAL + } + }, + { + // 快捷键绑定(可带参数) + keysym: "Mod4+Shift+q", + command: { + type: WM_COMMAND_FOCUS_WINDOW, + direction: "prev" + } + }, + { + // 布局切换 + keysym: "Mod4+space", + command: { + type: WM_COMMAND_SET_LAYOUT, + layout_name: "tile" + } + }, + { + // 窗口切换到工作区 + keysym: "Mod4+Shift+1", + command: { + type: WM_COMMAND_SEND_WINDOW_TO_WORKSPACE, + workspace_index: 0 + } + } +] +#+END_SRC + +**快捷键匹配优先级**: + +1. 完全匹配(keysym + 所有 modifiers) +2. 通配符匹配(keysym + 部分修饰符) +3. 默认绑定(如 Mod4+Enter 启动终端) + +**=wm_policy_route_event()= 实现**: + +#+BEGIN_SRC c +bool wm_policy_route_event(const wm_state_t *state, + const wm_event_t *event, + wm_command_buffer_t *out) { + switch (event->type) { + case WM_EVENT_KEY_PRESS: + return route_key_press(state, &event->as.key_press, out); + + case WM_EVENT_BUTTON_PRESS: + return route_button_press(state, &event->as.button_press, out); + + case WM_EVENT_MAP_REQUEST: + return route_map_request(state, &event->as.map_request, out); + + case WM_EVENT_UNMAP_NOTIFY: + return route_unmap_notify(state, &event->as.unmap_notify, out); + + case WM_EVENT_DESTROY_NOTIFY: + return route_destroy_notify(state, &event->as.destroy_notify, out); + + case WM_EVENT_CONFIGURE_REQUEST: + return route_configure_request(state, &event->as.configure_request, out); + + case WM_EVENT_OUTPUT_CHANGED: + return route_output_changed(state, &event->as.output_changed, out); + + case WM_EVENT_OUTPUT_REMOVED: + return route_output_removed(state, &event->as.output_removed, out); + + default: + return false; + } +} + +// 键盘事件路由 +static bool route_key_press(const wm_state_t *state, + const wm_key_press_event_t *e, + wm_command_buffer_t *out) { + // 1. 查找匹配的快捷键绑定 + const keybinding_t *binding = keybinding_find( + e->keycode, + e->modifiers + ); + + if (!binding) { + return false; // 没有匹配的绑定 + } + + // 2. 将绑定转换为命令 + wm_command_t cmd = { + .type = binding->command_type, + // ... 填充命令参数 + }; + + // 3. 添加到命令缓冲 + return wm_command_buffer_push(out, cmd); +} + +// 窗口映射请求路由 +static bool route_map_request(const wm_state_t *state, + const wm_map_request_event_t *e, + wm_command_buffer_t *out) { + // 1. 检查窗口是否已存在 + if (state_find_window(state, e->window_id)) { + return false; // 窗口已管理,忽略 + } + + // 2. 生成 MANAGE_WINDOW 命令 + wm_command_t cmd = { + .type = WM_COMMAND_MANAGE_WINDOW, + .manage_window = { + .window_id = e->window_id, + // 应用规则确定初始状态 + } + }; + + return wm_command_buffer_push(out, cmd); +} +#+END_SRC + +* 交互态事件路由 + +交互态会覆盖正常的事件路由: + +**=WM_INTERACTION_MOVE_FLOATING=** + +#+BEGIN_SRC c +static bool route_interaction_event(wm_runtime_t *runtime, + const wm_event_t *event, + wm_command_buffer_t *out) { + switch (runtime->interaction.mode) { + case WM_INTERACTION_MOVE_FLOATING: + return route_move_floating_event(runtime, event, out); + + case WM_INTERACTION_RESIZE_FLOATING: + return route_resize_floating_event(runtime, event, out); + + default: + return false; + } +} + +static bool route_move_floating_event(wm_runtime_t *runtime, + const wm_event_t *event, + wm_command_buffer_t *out) { + switch (event->type) { + case WM_EVENT_BUTTON_RELEASE: + // 结束移动交互 + wm_command_t end_cmd = { + .type = WM_COMMAND_END_INTERACTION + }; + return wm_command_buffer_push(out, end_cmd); + + case WM_EVENT_POINTER_MOTION: + // 生成移动命令 + wm_command_t move_cmd = { + .type = WM_COMMAND_MOVE_FLOATING_WINDOW, + .move_floating_window = { + .window_id = runtime->interaction.window_id, + .delta_x = event->as.pointer_motion.root.x - + runtime->interaction.pointer_origin.x, + .delta_y = event->as.pointer_motion.root.y - + runtime->interaction.pointer_origin.y + } + }; + return wm_command_buffer_push(out, move_cmd); + + default: + return false; + } +} +#+END_SRC + +* 命令生成规则 + +每种事件类型对应一个或多个命令的生成规则: + +**输入事件 → 命令映射** + +| 事件类型 | 可能生成的命令 | 条件 | +|---------|---------------|------| +| KEY_PRESS | 任意命令(通过快捷键绑定) | 绑定存在 | +| BUTTON_PRESS | FOCUS_WINDOW, RAISE_WINDOW | 点击窗口 | +| MAP_REQUEST | MANAGE_WINDOW | 窗口未管理 | +| UNMAP_NOTIFY | (可能不生成命令) | - | +| DESTROY_NOTIFY | UNMANAGE_WINDOW | 窗口已管理 | +| CONFIGURE_REQUEST | (可能不生成命令) | floating 窗口 | +| OUTPUT_CHANGED | UPSERT_OUTPUT | - | +| OUTPUT_REMOVED | REMOVE_OUTPUT | - | +| POINTER_MOTION | MOVE/RESIZE 命令 | 交互态下 | + +* 事件处理的边界条件 + +**1. 元数据事件边界** + +元数据事件满足以下条件时直接处理,不经过命令系统: +- 事件类型是 `WM_EVENT_WINDOW_METADATA_CHANGED` +- 事件类型是 `WM_EVENT_STATUS_TICK` + +**2. 交互态边界** + +交互态满足以下条件时,优先路由到交互处理器: +- `runtime->interaction.mode != WM_INTERACTION_NONE` +- 事件类型与交互相关(POINTER_MOTION, BUTTON_RELEASE) + +**3. 窗口管理边界** + +窗口管理的边界条件: +- `WM_EVENT_MAP_REQUEST`:窗口已存在则忽略 +- `WM_EVENT_DESTROY_NOTIFY`:窗口不存在则忽略 +- `WM_EVENT_CONFIGURE_REQUEST`:只有 floating 窗口才响应 + +* 事件处理顺序保证 + +为了保证确定性行为,事件处理必须按以下顺序进行: + +1. **元数据事件优先**:状态栏信息更新不触发布局计算 +2. **交互态优先**:拖拽操作优先于快捷键 +3. **快捷键匹配优先级**:完全匹配 → 通配符 → 默认 +4. **命令执行顺序**:命令缓冲中的命令按顺序执行 + +* 主执行流程实现 + +#+BEGIN_SRC c +void wm_runtime_run(wm_runtime_t *runtime) { + wm_event_t event; + + while (runtime->running) { + // 1. 获取事件 + if (!wm_backend_next_event(&runtime->backend, &event)) { + continue; + } + + // 2. 处理事件 + wm_runtime_process_event(runtime, &event); + + // 3. 如果有副作用,应用它们 + if (runtime->plan.dirty_flags) { + // 3.1 重新计算布局(如果需要) + if (runtime->plan.dirty_flags & WM_DIRTY_LAYOUT) { + wm_runtime_relayout(runtime); + } + + // 3.2 应用平台副作用 + wm_backend_apply_effects(&runtime->backend, &runtime->plan); + + // 3.3 通知服务层(渲染、状态栏等) + if (runtime->plan.dirty_flags & WM_DIRTY_RENDER) { + wm_service_notify_render(&runtime->services, &runtime->state); + } + + // 3.4 刷新平台 + wm_backend_flush(&runtime->backend); + + // 3.5 重置 plan + wm_plan_reset(&runtime->plan); + } + + // 4. 处理定时器(可选,降低 CPU 使用) + usleep(1000); // 1ms + } +} +#+END_SRC + +* 与现有功能的兼容性 + +这个事件路由设计完全兼容现有 ZDWM 功能: + +1. **快捷键系统**:通过 keybindings.conf 配置 +2. **规则系统**:在 MANAGE_WINDOW 命令中应用 +3. **布局切换**:通过 SET_LAYOUT 命令 +4. **拖拽交互**:通过交互态管理 +5. **多显示器**:OUTPUT_CHANGED/REMOVED 事件处理 + +* 缺失的文档总结 + +通过补充本文档,mini_core_draft 的事件处理链路就完整了: + +1. ✅ 事件定义(wm_event.h) +2. ✅ 命令定义(wm_command.h + WM_COMMAND_RULES.org) +3. ✅ 路由接口(wm_policy.h) +4. ✅ 命令应用(WM_POLICY_APPLY_COMMAND_SKELETON.org) +5. ✅ **路由规则**(本文档补充) +6. ✅ **快捷键系统**(本文档补充) +7. ✅ **交互态处理**(本文档补充) +8. ✅ **主执行流程**(本文档补充) 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 ea02af0..f66a9a0 100644 --- a/docs/min_core_draft/WM_POLICY_APPLY_COMMAND_SKELETON.org +++ b/docs/min_core_draft/WM_POLICY_APPLY_COMMAND_SKELETON.org @@ -43,6 +43,14 @@ bool wm_policy_apply_command(wm_state_t *state, changed = apply_unmanage_window(state, command, plan); break; + case WM_COMMAND_UPSERT_OUTPUT: + changed = apply_upsert_output(state, command, plan); + break; + + case WM_COMMAND_REMOVE_OUTPUT: + changed = apply_remove_output(state, command, plan); + break; + case WM_COMMAND_FOCUS_WINDOW: changed = apply_focus_window(state, command, plan); break; @@ -75,6 +83,10 @@ bool wm_policy_apply_command(wm_state_t *state, changed = apply_toggle_floating(state, command, plan); break; + case WM_COMMAND_TOGGLE_STICKY: + changed = apply_toggle_sticky(state, command, plan); + break; + case WM_COMMAND_TOGGLE_MAXIMIZE: changed = apply_toggle_maximize(state, command, plan); break; @@ -122,6 +134,9 @@ static wm_window_t *require_window(wm_state_t *state, wm_window_id_t id); static wm_workspace_t *require_workspace(wm_state_t *state, wm_workspace_id_t id); static wm_output_t *require_output(wm_state_t *state, wm_output_id_t id); +static bool state_upsert_output(wm_state_t *state, wm_output_t output); +static bool state_remove_output(wm_state_t *state, wm_output_id_t id); + static bool stack_raise(wm_state_t *state, wm_window_id_t id); static bool stack_lower(wm_state_t *state, wm_window_id_t id); static bool stack_remove(wm_state_t *state, wm_window_id_t id); @@ -155,16 +170,22 @@ static bool apply_manage_window(wm_state_t *state, wm_window_t win = { .id = id, .workspace_id = ws_id, - .geometry_mode = WM_GEOMETRY_NORMAL, - .floating = false, - .sticky = false, - .urgent = false, + .geometry_mode = command->as.manage_window.initial_state.geometry_mode, + .floating = command->as.manage_window.initial_state.floating, + .sticky = command->as.manage_window.initial_state.sticky, + .urgent = command->as.manage_window.initial_state.urgent, }; + if (command->as.manage_window.has_initial_float_rect) { + win.float_rect = command->as.manage_window.initial_float_rect; + } + if (!state_append_window(state, win)) return false; if (!stack_append(state, id)) return false; - ws->focused_window_id = id; + if (command->as.manage_window.initial_state.set_focus) { + ws->focused_window_id = id; + } plan->dirty_flags |= WM_DIRTY_STATE | WM_DIRTY_LAYOUT | WM_DIRTY_STACK | WM_DIRTY_RENDER; @@ -174,6 +195,43 @@ static bool apply_manage_window(wm_state_t *state, } #+END_SRC +** =apply_upsert_output()= + +#+BEGIN_SRC c +static bool apply_upsert_output(wm_state_t *state, + const wm_command_t *command, + wm_plan_t *plan) { + wm_output_t output = { + .id = command->as.upsert_output.output_id, + .geometry = command->as.upsert_output.geometry, + .workarea = command->as.upsert_output.workarea, + .current_workspace_id = command->as.upsert_output.initial_workspace_id, + }; + + if (!state_upsert_output(state, output)) return false; + + plan->dirty_flags |= WM_DIRTY_OUTPUT | WM_DIRTY_LAYOUT | WM_DIRTY_RENDER; + push_render_all_outputs(state, plan); + return true; +} +#+END_SRC + +** =apply_remove_output()= + +#+BEGIN_SRC c +static bool apply_remove_output(wm_state_t *state, + const wm_command_t *command, + wm_plan_t *plan) { + if (!state_remove_output(state, command->as.remove_output.output_id)) { + return false; + } + + plan->dirty_flags |= WM_DIRTY_OUTPUT | WM_DIRTY_LAYOUT | WM_DIRTY_RENDER; + push_render_all_outputs(state, plan); + return true; +} +#+END_SRC + ** =apply_unmanage_window()= #+BEGIN_SRC c @@ -221,6 +279,23 @@ static bool apply_focus_window(wm_state_t *state, } #+END_SRC + +** =apply_toggle_sticky()= + +#+BEGIN_SRC c +static bool apply_toggle_sticky(wm_state_t *state, + const wm_command_t *command, + wm_plan_t *plan) { + wm_window_t *win = require_window(state, command->as.toggle_sticky.window_id); + if (!win) return false; + + win->sticky = !win->sticky; + plan->dirty_flags |= WM_DIRTY_LAYOUT | WM_DIRTY_RENDER; + push_render_all_outputs(state, plan); + return true; +} +#+END_SRC + ** =apply_focus_direction()= #+BEGIN_SRC c @@ -541,6 +616,7 @@ static bool apply_quit(wm_state_t *state, 3. 生成 =WM_EFFECT_CONFIGURE_WINDOW= 。 4. 若有 =WM_DIRTY_STACK= ,生成 =WM_EFFECT_RESTACK_WINDOWS= 。 5. 若有 =WM_DIRTY_RENDER= ,生成 =WM_EFFECT_RENDER_OUTPUT= 。 +6. =WM_EFFECT_RENDER_OUTPUT= 由 runtime 分发给服务层,而不是直接交给 backend 。 * 为什么要拆成多个 =apply_*()= diff --git a/docs/min_core_draft/WORKSPACE_CONFIG_EXAMPLES.org b/docs/min_core_draft/WORKSPACE_CONFIG_EXAMPLES.org new file mode 100644 index 0000000..fb684aa --- /dev/null +++ b/docs/min_core_draft/WORKSPACE_CONFIG_EXAMPLES.org @@ -0,0 +1,220 @@ +* Workspace-Output 配置示例 + +本文档展示在不同场景下如何配置 workspace 和 output 的归属关系。 + +** 单显示器配置 + +最简单的配置:所有 workspace 归属到唯一输出。 + +#+BEGIN_SRC lua +-- zdwm.lua + +outputs = { + { id = 0, name = "DP-1", enabled = true, primary = true }, +} + +-- 所有 workspace 都归属 DP-1 +workspaces = { + { id = 1, name = "1", output_id = 0 }, + { id = 2, name = "2", output_id = 0 }, + { id = 3, name = "3", output_id = 0 }, + { id = 4, name = "4", output_id = 0 }, + { id = 5, name = "5", output_id = 0 }, + { id = 6, name = "6", output_id = 0 }, + { id = 7, name = "7", output_id = 0 }, + { id = 8, name = "8", output_id = 0 }, + { id = 9, name = "9", output_id = 0 }, + { id = 10, name = "10", output_id = 0 }, +} + +-- 或使用自动生成 +workspaces = auto_generate_workspaces({ + output_id = 0, + count = 10, + name_pattern = "number" -- 1, 2, 3, ... +}) +#+END_SRC + +** 双显示器配置 + +*** 场景 1:平分 workspace + +#+BEGIN_SRC lua +-- 左侧显示器 1-5,右侧显示器 6-10 +outputs = { + { id = 0, name = "DP-1", enabled = true, primary = true }, + { id = 1, name = "DP-2", enabled = true }, +} + +workspaces = { + -- DP-1 的 workspace + { id = 1, name = "1", output_id = 0 }, + { id = 2, name = "2", output_id = 0 }, + { id = 3, name = "3", output_id = 0 }, + { id = 4, name = "4", output_id = 0 }, + { id = 5, name = "5", output_id = 0 }, + + -- DP-2 的 workspace + { id = 6, name = "6", output_id = 1 }, + { id = 7, name = "7", output_id = 1 }, + { id = 8, name = "8", output_id = 1 }, + { id = 9, name = "9", output_id = 1 }, + { id = 10, name = "10", output_id = 1 }, +} +#+END_SRC + +*** 场景 2:偶数/奇数分配 + +#+BEGIN_SRC lua +-- 奇数在左,偶数在右 +workspaces = {} +for i = 1, 10 do + table.insert(workspaces, { + id = i, + name = tostring(i), + output_id = (i % 2 == 1) and 0 or 1 -- 奇数->0,偶数->1 + }) +end +#+END_SRC + +*** 场景 3:主题化 workspace + +#+BEGIN_SRC lua +-- 左侧开发相关,右侧娱乐相关 +outputs = { + { id = 0, name = "DP-1", primary = true }, + { id = 1, name = "HDMI-1" }, +} + +workspaces = { + -- DP-1 (开发) + { id = 1, name = "code", output_id = 0 }, + { id = 2, name = "term", output_id = 0 }, + { id = 3, name = "docs", output_id = 0 }, + { id = 4, name = "debug", output_id = 0 }, + + -- HDMI-1 (娱乐) + { id = 5, name = "chat", output_id = 1 }, + { id = 6, name = "web", output_id = 1 }, + { id = 7, name = "media", output_id = 1 }, + { id = 8, name = "game", output_id = 1 }, +} +#+END_SRC + +** 三显示器配置 + +笔记本 + 外接显示器的常见配置: + +#+BEGIN_SRC lua +-- eDP-1: 日常工作 (1-3) +-- DP-1: 代码 (4-6) +-- HDMI-1: 娱乐 (7-9) + +outputs = { + { id = 0, name = "eDP-1", primary = true }, + { id = 1, name = "DP-1", enabled = true }, + { id = 2, name = "HDMI-1", enabled = true }, +} + +workspaces = { + -- eDP-1 (笔记本) + { id = 1, name = "mail", output_id = 0 }, + { id = 2, name = "chat", output_id = 0 }, + { id = 3, name = "docs", output_id = 0 }, + + -- DP-1 (外接显示器 - 左) + { id = 4, name = "code", output_id = 1 }, + { id = 5, name = "term", output_id = 1 }, + { id = 6, name = "debug", output_id = 1 }, + + -- HDMI-1 (外接显示器 - 右) + { id = 7, name = "web", output_id = 2 }, + { id = 8, name = "media", output_id = 2 }, + { id = 9, name = "game", output_id = 2 }, +} +#+END_SRC + +** 动态配置(脚本式) + +根据实际检测到的输出数量动态生成: + +#+BEGIN_SRC lua +function configure_workspaces(runtime) + local outputs = runtime:scan_outputs() + local workspaces = {} + local ws_id = 1 + + -- 每个 output 分配 3 个 workspace + for _, output in ipairs(outputs) do + for i = 1, 3 do + table.insert(workspaces, { + id = ws_id, + name = string.format("%d:%s", ws_id, output.name), + output_id = output.id, + layout_id = 0, -- 默认布局 + }) + ws_id = ws_id + 1 + end + end + + return workspaces +end +#+END_SRC + +** 配置加载流程 + +#+BEGIN_SRC c +// config_loader.c + +typedef struct workspace_config_t { + wm_workspace_id_t id; + const char *name; + wm_output_id_t output_id; + wm_layout_id_t layout_id; +} workspace_config_t; + +bool load_workspace_config(const char *path, + workspace_config_t **out_workspaces, + size_t *out_count) { + // 1. 扫描输出 + output_config_t *outputs; + size_t output_count; + scan_outputs(&outputs, &output_count); + + // 2. 加载 workspace 配置 + workspace_config_t *workspaces; + size_t workspace_count; + parse_config_file(path, &workspaces, &workspace_count); + + // 3. 验证:每个 workspace 的 output_id 必须有效 + for (size_t i = 0; i < workspace_count; i++) { + if (workspaces[i].output_id >= output_count) { + fprintf(stderr, "Workspace %d: invalid output_id %d\n", + workspaces[i].id, workspaces[i].output_id); + return false; + } + } + + *out_workspaces = workspaces; + *out_count = workspace_count; + return true; +} +#+END_SRC + +** 切换行为示例 + +#+BEGIN_SRC lua +-- 切换到 workspace 4(在 DP-1 上) +-- 效果:只有 DP-1 切换,DP-2 保持当前 workspace + +-- 将窗口从 workspace 1 移动到 workspace 4 +-- 效果:窗口从 DP-1 移动到 DP-1(但在不同的 workspace) +-- 如果 workspace 4 当前在 DP-2 显示,窗口会在 DP-2 出现 +#+END_SRC + +** 注意事项 + +1. *固定归属*:workspace.output_id 在创建后不应改变 +2. *输出变化*:显示器配置变化时,WM 会重启,重新扫描和配置 +3. *可见性*:窗口只在归属的 output 上可见 +4. *独立性*:切换不同 output 的 workspace 互不影响 diff --git a/docs/min_core_draft/wm_backend.h b/docs/min_core_draft/wm_backend.h index 257599a..29c0c4c 100644 --- a/docs/min_core_draft/wm_backend.h +++ b/docs/min_core_draft/wm_backend.h @@ -8,6 +8,10 @@ typedef struct wm_backend_t wm_backend_t; typedef struct wm_backend_api_t { bool (*init)(wm_backend_t *backend); bool (*next_event)(wm_backend_t *backend, wm_event_t *out); + /* + * Runtime filters out service-side effects such as WM_EFFECT_RENDER_OUTPUT. + * Backends only receive platform effects here. + */ bool (*apply_effect)(wm_backend_t *backend, const wm_effect_t *effect); void (*flush)(wm_backend_t *backend); void (*shutdown)(wm_backend_t *backend); diff --git a/docs/min_core_draft/wm_command.h b/docs/min_core_draft/wm_command.h index 99203de..0742e88 100644 --- a/docs/min_core_draft/wm_command.h +++ b/docs/min_core_draft/wm_command.h @@ -2,10 +2,20 @@ #include "wm_types.h" +typedef struct wm_manage_window_init_t { + wm_window_geometry_mode_t geometry_mode; + bool floating; + bool sticky; + bool urgent; + bool set_focus; +} wm_manage_window_init_t; + typedef enum wm_command_type_t { WM_COMMAND_NONE, WM_COMMAND_MANAGE_WINDOW, WM_COMMAND_UNMANAGE_WINDOW, + WM_COMMAND_UPSERT_OUTPUT, + WM_COMMAND_REMOVE_OUTPUT, WM_COMMAND_FOCUS_WINDOW, WM_COMMAND_FOCUS_DIRECTION, WM_COMMAND_RAISE_WINDOW, @@ -14,6 +24,7 @@ typedef enum wm_command_type_t { WM_COMMAND_SEND_WINDOW_TO_WORKSPACE, WM_COMMAND_SEND_WINDOW_TO_OUTPUT, WM_COMMAND_TOGGLE_FLOATING, + WM_COMMAND_TOGGLE_STICKY, WM_COMMAND_TOGGLE_MAXIMIZE, WM_COMMAND_TOGGLE_FULLSCREEN, WM_COMMAND_TOGGLE_MINIMIZE, @@ -30,12 +41,26 @@ typedef struct wm_command_t { struct { wm_window_id_t window_id; wm_workspace_id_t workspace_id; + wm_manage_window_init_t initial_state; + bool has_initial_float_rect; + wm_rect_t initial_float_rect; } manage_window; struct { wm_window_id_t window_id; } unmanage_window; + struct { + wm_output_id_t output_id; + wm_rect_t geometry; + wm_rect_t workarea; + wm_workspace_id_t initial_workspace_id; + } upsert_output; + + struct { + wm_output_id_t output_id; + } remove_output; + struct { wm_window_id_t window_id; } focus_window; @@ -73,6 +98,10 @@ typedef struct wm_command_t { wm_window_id_t window_id; } toggle_floating; + struct { + wm_window_id_t window_id; + } toggle_sticky; + struct { wm_window_id_t window_id; } toggle_maximize; diff --git a/docs/min_core_draft/wm_event.h b/docs/min_core_draft/wm_event.h index 76a9ac9..b7d710b 100644 --- a/docs/min_core_draft/wm_event.h +++ b/docs/min_core_draft/wm_event.h @@ -14,6 +14,7 @@ typedef enum wm_event_type_t { WM_EVENT_WINDOW_METADATA_CHANGED, WM_EVENT_CONFIGURE_REQUEST, WM_EVENT_OUTPUT_CHANGED, + WM_EVENT_OUTPUT_REMOVED, WM_EVENT_TIMER_TICK, WM_EVENT_STATUS_TICK, WM_EVENT_QUIT, @@ -59,6 +60,16 @@ typedef enum wm_window_meta_changed_flags_t { typedef struct wm_window_metadata_changed_event_t { wm_window_id_t window_id; uint32_t changed_fields; + /* + * These strings are borrowed from the backend adapter and are valid until + * wm_runtime_process_event() returns. changed_fields decides which ones are + * meaningful for the current event. + * runtime 会复制这些字符串到 wm_window_t 中的对应字段。 + */ + const char *title; + const char *app_id; + const char *class_name; + const char *instance_name; } wm_window_metadata_changed_event_t; typedef struct wm_configure_request_event_t { @@ -72,6 +83,10 @@ typedef struct wm_output_changed_event_t { wm_rect_t workarea; } wm_output_changed_event_t; +typedef struct wm_output_removed_event_t { + wm_output_id_t output_id; +} wm_output_removed_event_t; + typedef struct wm_timer_tick_event_t { uint64_t now_ms; } wm_timer_tick_event_t; @@ -90,6 +105,7 @@ typedef struct wm_event_t { wm_window_metadata_changed_event_t window_metadata_changed; wm_configure_request_event_t configure_request; wm_output_changed_event_t output_changed; + wm_output_removed_event_t output_removed; wm_timer_tick_event_t timer_tick; } as; } wm_event_t; diff --git a/docs/min_core_draft/wm_layout.h b/docs/min_core_draft/wm_layout.h index 1946075..5975716 100644 --- a/docs/min_core_draft/wm_layout.h +++ b/docs/min_core_draft/wm_layout.h @@ -10,6 +10,7 @@ typedef struct wm_layout_window_ref_t { typedef struct wm_layout_ctx_t { wm_output_id_t output_id; wm_workspace_id_t workspace_id; + wm_window_id_t focused_window_id; wm_rect_t workarea; const wm_layout_window_ref_t *windows; size_t window_count; @@ -31,9 +32,15 @@ typedef bool (*wm_layout_fn)(const wm_layout_ctx_t *ctx, typedef struct wm_layout_slot_t { wm_layout_id_t id; + /* + * 布局标识符,用于: + * - 状态栏显示(应简短,如 "T", "M", "F") + * - 日志输出 + * - 配置引用 + * + * 推荐使用 1-2 个字符的简短标识符。 + */ const char *name; - const char *symbol_text; - const char *symbol_icon_path; wm_layout_fn fn; } wm_layout_slot_t; diff --git a/docs/min_core_draft/wm_policy_config.h b/docs/min_core_draft/wm_policy_config.h new file mode 100644 index 0000000..632746c --- /dev/null +++ b/docs/min_core_draft/wm_policy_config.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +typedef struct wm_policy_config_t { + bool focus_raises; + bool sticky_windows_participate_in_direction_focus; + bool manage_sets_focus; + bool switch_workspace_restores_last_focus; + bool minimize_clears_focus; +} wm_policy_config_t; + +void wm_policy_config_init_default(wm_policy_config_t *config); diff --git a/docs/min_core_draft/wm_runtime.h b/docs/min_core_draft/wm_runtime.h index 8c42c62..a7a6b0e 100644 --- a/docs/min_core_draft/wm_runtime.h +++ b/docs/min_core_draft/wm_runtime.h @@ -3,9 +3,62 @@ #include "wm_backend.h" #include "wm_layout.h" #include "wm_plan.h" +#include "wm_policy_config.h" #include "wm_policy.h" +#include "wm_service.h" #include "wm_state.h" +typedef enum wm_interaction_mode_t { + WM_INTERACTION_NONE, + WM_INTERACTION_MOVE_FLOATING, + WM_INTERACTION_RESIZE_FLOATING, +} wm_interaction_mode_t; + +typedef struct wm_interaction_state_t { + wm_interaction_mode_t mode; + wm_window_id_t window_id; + wm_output_id_t origin_output_id; + wm_point_t pointer_origin; + wm_rect_t window_origin_rect; +} wm_interaction_state_t; + +typedef struct wm_runtime_bootstrap_t { + /* + * 输出配置(启动时扫描显示器后填充) + */ + const wm_output_t *outputs; + size_t output_count; + + /* + * 工作区配置(已绑定 output) + * workspace.output_id 指向 outputs 中的某个输出 + */ + const wm_workspace_t *workspaces; + size_t workspace_count; + + /* + * 布局算法注册 + */ + struct { + wm_layout_fn fn; + const char *name; + } *layouts; + size_t layout_count; + + /* + * 策略配置 + */ + wm_policy_config_t policy; + + /* + * Existing windows discovered during startup should be translated into these + * commands instead of being injected into wm_state_t directly. + */ + const wm_command_t *initial_commands; + size_t initial_command_count; +} wm_runtime_bootstrap_t; + +// 运行时上下文(实现时采用不透明结构体,此定义仅作参考) typedef struct wm_runtime_t { bool running; @@ -13,13 +66,20 @@ typedef struct wm_runtime_t { wm_plan_t plan; wm_command_buffer_t command_buffer; wm_layout_registry_t layouts; + wm_policy_config_t policy; + wm_interaction_state_t interaction; wm_backend_t backend; + wm_service_registry_t services; } wm_runtime_t; -bool wm_runtime_init(wm_runtime_t *runtime, wm_backend_t backend); +bool wm_runtime_init(wm_runtime_t *runtime, wm_backend_t backend, + const wm_runtime_bootstrap_t *bootstrap); +bool wm_runtime_register_service(wm_runtime_t *runtime, wm_service_t service); /* * 处理单个事件,方便做无平台依赖的单元测试。 + * 元数据事件允许 runtime 直接更新 auxiliary store;控制状态变更仍经由 + * route_event() + apply_command()。 */ bool wm_runtime_process_event(wm_runtime_t *runtime, const wm_event_t *event); diff --git a/docs/min_core_draft/wm_service.h b/docs/min_core_draft/wm_service.h new file mode 100644 index 0000000..73979d7 --- /dev/null +++ b/docs/min_core_draft/wm_service.h @@ -0,0 +1,45 @@ +#pragma once + +#include "wm_state.h" + +typedef enum wm_service_event_type_t { + WM_SERVICE_EVENT_NONE, + WM_SERVICE_EVENT_RUNTIME_STARTED, + WM_SERVICE_EVENT_RUNTIME_STOPPING, + WM_SERVICE_EVENT_RENDER_OUTPUT, +} wm_service_event_type_t; + +typedef struct wm_service_event_t { + wm_service_event_type_t type; + union { + struct { + wm_output_id_t output_id; + } render_output; + } as; +} wm_service_event_t; + +typedef struct wm_service_t wm_service_t; + +typedef struct wm_service_api_t { + bool (*init)(wm_service_t *service); + void (*handle_event)(wm_service_t *service, const wm_service_event_t *event, + const wm_state_t *state); + void (*shutdown)(wm_service_t *service); +} wm_service_api_t; + +struct wm_service_t { + const wm_service_api_t *api; + void *impl; +}; + +typedef struct wm_service_registry_t { + wm_service_t *items; + size_t count; + size_t capacity; +} wm_service_registry_t; + +void wm_service_registry_init(wm_service_registry_t *registry); +void wm_service_registry_shutdown(wm_service_registry_t *registry); + +bool wm_service_registry_register(wm_service_registry_t *registry, + wm_service_t service); diff --git a/docs/min_core_draft/wm_state.h b/docs/min_core_draft/wm_state.h index aa1d418..3ee3955 100644 --- a/docs/min_core_draft/wm_state.h +++ b/docs/min_core_draft/wm_state.h @@ -1,103 +1,132 @@ #pragma once #include "wm_types.h" +#include +#include +#include + +// ========== 核心实体定义 ========== typedef struct wm_window_t { wm_window_id_t id; wm_workspace_id_t workspace_id; - /* - * 这里只保留控制状态,不存标题、类名、实例名等展示元数据。 - */ + // 几何模式 wm_window_geometry_mode_t geometry_mode; - - /* - * false: 由当前 workspace 的 layout 决定基础几何 - * true : 使用 float_rect 作为基础几何 - */ bool floating; - - /* - * sticky 只影响可见性,不改变 workspace 归属。 - */ bool sticky; bool urgent; - /* - * floating 模式的基准矩形。退出 maximize/fullscreen 后可以回到这里。 - */ - wm_rect_t float_rect; + // 几何信息 + wm_rect_t float_rect; // floating 窗口的矩形 + wm_rect_t frame_rect; // 最终应用的矩形(由 layout 计算或 float_rect 决定) - /* - * 最近一次实际提交给后端的矩形。 - */ - wm_rect_t frame_rect; + // 元数据(核心算法不依赖,仅用于规则匹配和服务层展示) + char *title; + char *app_id; + char *class_name; + char *instance_name; } wm_window_t; typedef struct wm_workspace_t { wm_workspace_id_t id; - - /* - * 这里只保留运行状态,不存名称、bar 符号等描述信息。 - */ + wm_output_id_t output_id; // 固定归属某个输出 wm_layout_id_t layout_id; wm_window_id_t focused_window_id; + + // 名称(核心算法不依赖,仅用于状态栏显示) + char *name; } wm_workspace_t; typedef struct wm_output_t { wm_output_id_t id; - wm_rect_t geometry; - wm_rect_t workarea; + bool enabled; + wm_rect_t geometry; // 输出完整几何 + wm_rect_t workarea; // 可用区域(排除面板等) wm_workspace_id_t current_workspace_id; } wm_output_t; +// ========== 全局状态容器 ========== + typedef struct wm_state_t { - wm_window_t *windows; - size_t window_count; - - /* - * 全局 z-order,从下到上。每个受管窗口最多出现一次。 - */ - wm_window_id_t *stack_order; - size_t stack_count; - + // 内部实现(不透明结构体,实现文件中定义) wm_workspace_t *workspaces; size_t workspace_count; + size_t workspace_capacity; wm_output_t *outputs; size_t output_count; + size_t output_capacity; + + wm_window_t *windows; + size_t window_count; + size_t window_capacity; + + // 公开的字段 + wm_window_id_t *stack_order; + size_t stack_count; + size_t stack_capacity; - /* - * 成功提交一次状态变更后递增。 - */ uint64_t generation; + bool initialized; } wm_state_t; -void wm_state_init(wm_state_t *state); +// ========== API 函数 ========== + +// 初始化/清理 +void wm_state_init(wm_state_t *state, + size_t workspace_count, + size_t output_count); void wm_state_shutdown(wm_state_t *state); -wm_window_t *wm_state_find_window(wm_state_t *state, wm_window_id_t id); -const wm_window_t *wm_state_find_window_const(const wm_state_t *state, - wm_window_id_t id); +// ========== Workspace 访问 ========== -wm_workspace_t *wm_state_find_workspace(wm_state_t *state, - wm_workspace_id_t id); -const wm_workspace_t *wm_state_find_workspace_const(const wm_state_t *state, - wm_workspace_id_t id); +wm_workspace_t *wm_state_workspace(wm_state_t *state, wm_workspace_id_t id); +wm_workspace_t *wm_state_workspace_at(wm_state_t *state, size_t index); +size_t wm_state_workspace_count(const wm_state_t *state); +bool wm_state_workspace_valid(wm_state_t *state, wm_workspace_id_t id); -wm_output_t *wm_state_find_output(wm_state_t *state, wm_output_id_t id); -const wm_output_t *wm_state_find_output_const(const wm_state_t *state, - wm_output_id_t id); +// 查询某个 output 的所有 workspace +size_t wm_state_workspace_get_by_output(const wm_state_t *state, + wm_output_id_t output_id, + wm_workspace_t **out_workspaces); -const wm_output_t *wm_state_find_output_by_point(const wm_state_t *state, - wm_point_t point); +// ========== Output 访问 ========== +wm_output_t *wm_state_output(wm_state_t *state, wm_output_id_t id); +wm_output_t *wm_state_output_at(wm_state_t *state, size_t index); +size_t wm_state_output_count(const wm_state_t *state); +bool wm_state_output_valid(wm_state_t *state, wm_output_id_t id); + +// ========== Window 操作 ========== + +wm_window_t *wm_state_window_add(wm_state_t *state, wm_window_id_t id); +wm_window_t *wm_state_window_get(wm_state_t *state, wm_window_id_t id); +wm_window_t *wm_state_window_at(wm_state_t *state, size_t index); +void wm_state_window_remove(wm_state_t *state, wm_window_id_t id); +size_t wm_state_window_count(const wm_state_t *state); + +// ========== 查询辅助函数 ========== + +/* + * 判断工作区是否可见(某个 output 正在显示它) + */ bool wm_state_workspace_is_visible(const wm_state_t *state, wm_workspace_id_t workspace_id); +/* + * 判断窗口是否可见 + * 窗口可见 iff: + * 1. 窗口的 workspace 固定归属某个 output + * 2. 该 output 当前正显示这个 workspace + * 3. 窗口未最小化 + */ bool wm_state_window_should_be_visible(const wm_state_t *state, const wm_window_t *window); +/* + * 判断窗口是否在特定 output 上可见 + */ bool wm_state_window_should_be_visible_on_output(const wm_state_t *state, const wm_window_t *window, const wm_output_t *output); diff --git a/docs/min_core_draft/wm_types.h b/docs/min_core_draft/wm_types.h index d883ae2..0ea3c49 100644 --- a/docs/min_core_draft/wm_types.h +++ b/docs/min_core_draft/wm_types.h @@ -4,11 +4,20 @@ #include #include +// ID 类型定义 +// workspace/output/layout 在启动时分配固定数量,运行时不再增删 +// window 由后端管理,使用后端原生 ID typedef uint32_t wm_window_id_t; typedef uint16_t wm_workspace_id_t; typedef uint16_t wm_output_id_t; typedef uint32_t wm_layout_id_t; +// 无效 ID 标记 +#define WM_WINDOW_ID_INVALID ((wm_window_id_t)0) +#define WM_WORKSPACE_ID_INVALID ((wm_workspace_id_t)-1) +#define WM_OUTPUT_ID_INVALID ((wm_output_id_t)-1) +#define WM_LAYOUT_ID_INVALID ((wm_layout_id_t)-1) + typedef struct wm_point_t { int16_t x; int16_t y; diff --git a/docs/min_core_draft/wm_window_meta.h b/docs/min_core_draft/wm_window_meta.h deleted file mode 100644 index cdd2aa0..0000000 --- a/docs/min_core_draft/wm_window_meta.h +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once - -#include "wm_types.h" - -typedef struct wm_window_meta_t { - wm_window_id_t id; - char *title; - char *app_id; - char *class_name; - char *instance_name; -} wm_window_meta_t; - -typedef struct wm_window_meta_store_t { - wm_window_meta_t *items; - size_t count; - size_t capacity; -} wm_window_meta_store_t; - -void wm_window_meta_store_init(wm_window_meta_store_t *store); -void wm_window_meta_store_shutdown(wm_window_meta_store_t *store); - -wm_window_meta_t *wm_window_meta_find(wm_window_meta_store_t *store, - wm_window_id_t id); -const wm_window_meta_t *wm_window_meta_find_const( - const wm_window_meta_store_t *store, wm_window_id_t id); - -bool wm_window_meta_upsert(wm_window_meta_store_t *store, - const wm_window_meta_t *meta); -bool wm_window_meta_remove(wm_window_meta_store_t *store, wm_window_id_t id); diff --git a/docs/min_core_draft/wm_workspace_desc.h b/docs/min_core_draft/wm_workspace_desc.h deleted file mode 100644 index 510d9ef..0000000 --- a/docs/min_core_draft/wm_workspace_desc.h +++ /dev/null @@ -1,37 +0,0 @@ -#pragma once - -#include "wm_types.h" - -typedef struct wm_workspace_desc_t { - wm_workspace_id_t id; - - /* - * 人类可读名称,例如 "web"、"code"、"chat"。 - */ - const char *name; - - /* - * bar 中展示的文本符号,例如 "1"、"2"、"M"。 - */ - const char *symbol_text; - - /* - * 可选图标路径。若为空,则使用 symbol_text。 - */ - const char *symbol_icon_path; -} wm_workspace_desc_t; - -typedef struct wm_workspace_desc_table_t { - wm_workspace_desc_t *items; - size_t count; - size_t capacity; -} wm_workspace_desc_table_t; - -void wm_workspace_desc_table_init(wm_workspace_desc_table_t *table); -void wm_workspace_desc_table_shutdown(wm_workspace_desc_table_t *table); - -const wm_workspace_desc_t *wm_workspace_desc_lookup( - const wm_workspace_desc_table_t *table, wm_workspace_id_t id); - -bool wm_workspace_desc_register(wm_workspace_desc_table_t *table, - wm_workspace_desc_t desc);