完善最小核心架构设计和数据结构简化

主要变更:

- 数据结构简化
  - 合并窗口元数据到 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,完善实现骨架
This commit is contained in:
2026-03-11 05:28:39 +08:00
parent f106857cd8
commit 329943ab3e
18 changed files with 2335 additions and 169 deletions

View File

@@ -0,0 +1,650 @@
# ZDWM 最小核心 + 外围扩展设计
* 设计原则
1. *核心只做一件事*:管理窗口、显示器、工作区
2. *所有扩展都通过插件*:状态栏、配置、快捷键、布局算法
3. *核心提供稳定的钩子*:插件可以在关键点介入
4. *零运行时开销*:扩展不增加核心复杂度
* 最小核心定义
#+BEGIN_SRC c
// src/core/zdwm.h
#include <stdbool.h>
#include <stdint.h>
// ========== 基础类型 ==========
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
// 插件1tile 布局
wm_plugin_t plugin_tile = { .name = "tile", ... };
// 插件2monocle 布局
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~ 简单很多,但扩展性并不差。关键是*钩子系统*设计得当,可以实现大部分扩展需求。

412
docs/config_system.org Normal file
View File

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

View File

@@ -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[]= 操作补齐。

View File

@@ -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= 决定。
副作用:

View File

@@ -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. ✅ **主执行流程**(本文档补充)

View File

@@ -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_*()=

View File

@@ -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 互不影响

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,13 @@
#pragma once
#include <stdbool.h>
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);

View File

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

View File

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

View File

@@ -1,103 +1,132 @@
#pragma once
#include "wm_types.h"
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
// ========== 核心实体定义 ==========
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);

View File

@@ -4,11 +4,20 @@
#include <stddef.h>
#include <stdint.h>
// 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;

View File

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

View File

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