完善最小核心架构设计和数据结构简化
主要变更: - 数据结构简化 - 合并窗口元数据到 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:
@@ -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[]= 操作补齐。
|
||||
|
||||
|
||||
Reference in New Issue
Block a user