core: 定义按键和鼠标事件

This commit is contained in:
2026-03-16 00:03:22 +08:00
parent 58d1b09203
commit 4c96f2a1db
15 changed files with 623 additions and 362 deletions

View File

@@ -18,7 +18,8 @@
3. =state= 3. =state=
负责保存窗口、工作区、输出和全局堆叠顺序,是唯一真状态。 负责保存窗口、工作区、输出和全局堆叠顺序,是唯一真状态。
采用不透明结构体设计,内部实现完全封装。 当前头文件级草案先直接展示结构体字段,便于讨论;
实现阶段再收敛为不透明结构体也不迟。
窗口元数据title/app_id/class/instance直接存储在 =wm_window_t= 中。 窗口元数据title/app_id/class/instance直接存储在 =wm_window_t= 中。
工作区名称存储在 =wm_workspace_t= 中。 工作区名称存储在 =wm_workspace_t= 中。
@@ -65,26 +66,20 @@ const wm_window_t *win = wm_state_window_get(state, window_id); // O(n)
- 性能:半静态实体零开销访问,动态实体简单线性查找 - 性能:半静态实体零开销访问,动态实体简单线性查找
- 简单性:无需复杂的映射表,代码更清晰 - 简单性:无需复杂的映射表,代码更清晰
* 热插拔策略 * 固定集合约束
**显示器变化时自动重启 WM** 以下集合一经 bootstrap 确定,运行时不再增删或重建
当检测到显示器配置变化(新增/删除/几何改变)时,最简单且可靠的方案是重启整个窗口管理器。 1. =outputs=
2. =workspaces=
3. =layout registry=
4. 每个 workspace 的 =available_layouts=
理由 运行时允许变化的只是
1. 逻辑简单:无需处理复杂的运行时重配置
2. 状态一致:避免重配置后的不一致状态
3. 实现成本低:不需要增量更新逻辑
4. 可靠性高:从干净状态启动,避免边界情况
实现方式: 1. 某个 output 当前显示的 =current_workspace_id=
- 后端检测到 RandR/Output 变化时,退出主循环 2. 某个 workspace 当前选中的 =layout_id=
- 由启动脚本(如 systemd user service 或 .xinitrc自动重启 WM 3. 窗口集合及窗口运行态
- WM 重启前保存必要状态(如当前工作区),启动后恢复
对用户影响:
- 显示器拔插时窗口会短暂闪烁(~1-2 秒)
- 但比复杂的增量逻辑更可靠,符合"简单优先"原则
* 非核心但常见的服务 * 非核心但常见的服务
@@ -108,6 +103,7 @@ const wm_window_t *win = wm_state_window_get(state, window_id); // O(n)
为了避免再次出现当前全局 =wm= 那样的大对象耦合,最小核心里的所有权再收紧一层: 为了避免再次出现当前全局 =wm= 那样的大对象耦合,最小核心里的所有权再收紧一层:
1. =runtime= 拥有 =state==plan==command_buffer==layout_registry= 1. =runtime= 拥有 =state==plan==command_buffer==layout_registry=
其中 workspace/output/layout registry 集合在 init 完成后保持固定。
2. =runtime= 也拥有 =policy config= 、全局边框配置、交互态和服务注册表。 2. =runtime= 也拥有 =policy config= 、全局边框配置、交互态和服务注册表。
3. =backend= 只负责平台事件翻译和平台副作用,不拥有控制状态,不直接拼 bar。 3. =backend= 只负责平台事件翻译和平台副作用,不拥有控制状态,不直接拼 bar。
4. 窗口元数据title/app_id/class/instance直接存储在 =wm_window_t= 中,与控制状态一同管理。 4. 窗口元数据title/app_id/class/instance直接存储在 =wm_window_t= 中,与控制状态一同管理。
@@ -130,15 +126,21 @@ const wm_window_t *win = wm_state_window_get(state, window_id); // O(n)
4. 输出在启动时扫描并分配对应数量的数组,运行时不再增删。 4. 输出在启动时扫描并分配对应数量的数组,运行时不再增删。
5. layout registry 在启动时注册完成,运行时不再增删。
* 显示器热插拔策略 * 显示器热插拔策略
当检测到显示器配置变化(新增/删除/几何改变)时: 当检测到显示器配置变化(新增/删除/几何改变)时:
1. 后端检测到 RandR/Output 变化,发送 =WM_EVENT_QUIT= 给 runtime 1. 后端检测到 RandR/Output 变化,使 =backend.next_event()= 返回 =WM_BACKEND_NEXT_RESTART_REQUIRED=
2. Runtime 退出主循环,清理资源 2. Runtime 退出主循环,清理资源
3. 由启动脚本systemd/xinitrc自动重启 WM 3. 由启动脚本systemd/xinitrc自动重启 WM
4. WM 重启后重新扫描显示器配置,从干净状态启动 4. WM 重启后重新扫描显示器配置,从干净状态启动
注意:
- core 不处理 =output changed/removed= 这类增量事件
- core 内不存在运行时增删 output/workspace/layout 集合的命令
**优势** **优势**
- ✅ 逻辑简单,无需复杂的运行时重配置 - ✅ 逻辑简单,无需复杂的运行时重配置
- ✅ 状态一致,避免增量更新的边界情况 - ✅ 状态一致,避免增量更新的边界情况
@@ -150,7 +152,7 @@ const wm_window_t *win = wm_state_window_get(state, window_id); // O(n)
* Workspace-Output 配置 * Workspace-Output 配置
工作区在启动根据输出数量动态配置,固定归属到特定输出 工作区配置可以在启动阶段根据输出数量生成;一旦 bootstrap 完成,归属关系保持固定
**配置方式 1手动指定** **配置方式 1手动指定**
```lua ```lua
@@ -171,7 +173,7 @@ workspace_distribution = {
} }
``` ```
**配置方式 3脚本式** **配置方式 3启动阶段脚本生成**
```lua ```lua
function configure_workspaces(outputs) function configure_workspaces(outputs)
local workspaces = {} local workspaces = {}
@@ -193,9 +195,10 @@ end
``` ```
**可见性规则** **可见性规则**
- 窗口仅在归属 output 上可见 - 普通非 =sticky= 窗口是否进入可见集,先看它的 =workspace= 是否正被归属 output 选中
- 窗口可见 iff窗口的 workspace 是该 output 的当前显示 workspace - 多输出下,窗口最终是否出现在某个 output 上,还取决于 =frame_rect= 是否与该 output 的几何相交
- 切换 workspace 只影响归属的 output - =sticky= 窗口只放宽 workspace 可见性,不提供 output 复制语义;它同样按最终矩形与 output 的相交关系决定可见性
- 切换 workspace 只改变归属 output 的 =current_workspace_id= ,不改变 workspace/output 的固定绑定
* 策略配置 * 策略配置
@@ -283,9 +286,9 @@ move/resize 这类拖拽交互不属于 =wm_state_t= 真状态,但也不能继
核心算法不应依赖此字段。 核心算法不应依赖此字段。
8. =workspace layouts= 8. =workspace layouts=
每个 workspace 维护一个可用布局列表(=available_layouts=),在启动时根据配置分配,运行时固定 每个 workspace 维护一个可用布局列表(=available_layouts=),在启动时根据配置分配,之后固定不变
workspace 的 =layout_id= 字段指向当前活动的布局(列表中的一个)。 workspace 的 =layout_id= 字段指向当前活动的布局(列表中的一个),运行时允许切换
使用 =CYCLE_LAYOUT= 命令在可用布局间循环切换 使用 =CYCLE_LAYOUT= / =SET_LAYOUT= 命令切换当前活动布局
9. =window rectangles= 9. =window rectangles=
=frame_rect= 是窗口当前最终外框矩形;=float_rect= 是 floating 模式下记忆的外框矩形。 =frame_rect= 是窗口当前最终外框矩形;=float_rect= 是 floating 模式下记忆的外框矩形。
@@ -306,7 +309,7 @@ workspace 的 =layout_id= 字段指向当前活动的布局(列表中的一个
11. layout 的 =symbol= 字段用于状态栏显示,应为 1-2 个字符的简短标识符(如 "T", "M"=name= 应保持稳定、可读(如 "tile", "monocle")。 11. layout 的 =symbol= 字段用于状态栏显示,应为 1-2 个字符的简短标识符(如 "T", "M"=name= 应保持稳定、可读(如 "tile", "monocle")。
12. 窗口标题、类名等元数据存储在 =wm_window_t= 中,与控制状态一同管理。 12. 窗口标题、类名等元数据存储在 =wm_window_t= 中,与控制状态一同管理。
13. 工作区名称存储在 =wm_workspace_t= 中,核心算法不应依赖此字段。 13. 工作区名称存储在 =wm_workspace_t= 中,核心算法不应依赖此字段。
14. 每个 workspace 的可用布局列表在启动时分配,运行时固定,只能通过 =CYCLE_LAYOUT= 或 =SET_LAYOUT= 命令切换当前活动布局。 14. 每个 workspace 的可用布局列表在启动时分配,之后固定不变;运行时只允许通过 =CYCLE_LAYOUT= 或 =SET_LAYOUT= 命令切换当前活动布局。
15. =frame_rect= 和 =float_rect= 都表示包含边框后的外框矩形。 15. =frame_rect= 和 =float_rect= 都表示包含边框后的外框矩形。
16. =border_width= 不是 =wm_window_t= 真状态;它由 runtime 从全局配置和几何模式推导。 16. =border_width= 不是 =wm_window_t= 真状态;它由 runtime 从全局配置和几何模式推导。
@@ -334,16 +337,19 @@ workspace 的 =layout_id= 字段指向当前活动的布局(列表中的一个
7. 将 render 失效通知发送给服务层 7. 将 render 失效通知发送给服务层
8. =backend.flush()= 8. =backend.flush()=
若后端发现显示器配置变化,不进入 output 增量更新分支,而是让 =backend.next_event()=
返回 =WM_BACKEND_NEXT_RESTART_REQUIRED= ,由外层退出并整机重启。
* 文件说明 * 文件说明
1. =wm_types.h= 1. =wm_types.h=
通用标量类型、矩形、窗口几何模式和策略枚举。 通用标量类型、矩形、窗口几何模式和策略枚举。
2. =wm_state.h= 2. =wm_state.h=
全局状态容器,完全封装的内部实现,提供统一访问 API。 全局状态容器;当前草案同时保留公开字段和统一访问 API,便于先把语义定清楚
3. =wm_event.h= 3. =wm_event.h=
统一后的运行时事件 统一后的 runtime 输入事件;不承载 stop/restart 这类控制信号
4. =wm_command.h= 4. =wm_command.h=
强类型语义命令。 强类型语义命令。

View File

@@ -17,9 +17,11 @@
8. =floating= 决定基础几何来源: 8. =floating= 决定基础几何来源:
=floating = false= 时,基础几何来自 layout 结果。 =floating = false= 时,基础几何来自 layout 结果。
=floating = true= 时,基础几何来自 =float_rect= =floating = true= 时,基础几何来自 =float_rect=
9. 文档里出现的可选行为,应优先固化到 =wm_policy_config_t= ,而不是继续留在 prose 里。 9. output/workspace/layout registry 以及每个 workspace 的 =available_layouts=
10. =frame_rect==float_rect= 都表示包含边框后的外框矩形 都是 bootstrap 后固定集合;运行时不允许增删
11. =border_width= 不是 =wm_window_t= 真状态runtime 在提交 10. 文档里出现的可选行为,应优先固化到 =wm_policy_config_t= ,而不是继续留在 prose 里。
11. =frame_rect==float_rect= 都表示包含边框后的外框矩形。
12. =border_width= 不是 =wm_window_t= 真状态runtime 在提交
=WM_EFFECT_CONFIGURE_WINDOW= 时按全局配置解析它。 =WM_EFFECT_CONFIGURE_WINDOW= 时按全局配置解析它。
* 管理类命令 * 管理类命令
@@ -48,34 +50,6 @@
3. =WM_DIRTY_STACK= 3. =WM_DIRTY_STACK=
4. =WM_DIRTY_RENDER= 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= ** =WM_COMMAND_UNMANAGE_WINDOW=
前置条件: 前置条件:
@@ -144,6 +118,12 @@
** =WM_COMMAND_SWITCH_WORKSPACE= ** =WM_COMMAND_SWITCH_WORKSPACE=
前置条件:
1. 目标 output 存在。
2. 目标 workspace 存在。
3. 目标 workspace 的 =output_id= 必须等于目标 =output_id=
状态变化: 状态变化:
1. 将目标输出的 =current_workspace_id= 改为指定工作区。 1. 将目标输出的 =current_workspace_id= 改为指定工作区。
@@ -174,22 +154,41 @@
** =WM_COMMAND_SEND_WINDOW_TO_OUTPUT= ** =WM_COMMAND_SEND_WINDOW_TO_OUTPUT=
前置条件:
1. 目标窗口存在。
2. 目标 output 存在。
状态变化: 状态变化:
1. 这是工作区迁移命令的语法糖。 1. 这是工作区迁移命令的语法糖。
2. 取目标 output 当前的 =current_workspace_id= 2. 取目标 output 当前的 =current_workspace_id=
3.=policy == WM_CROSS_OUTPUT_KEEP_WORKSPACE= ,则不改 =workspace_id= ,只允许调用方随后自行调整几何 3.=policy == WM_CROSS_OUTPUT_KEEP_WORKSPACE= ,则该命令本身不修改 core 真状态
仅想保留 =workspace_id= 并跨 output 调整几何时,应使用 =WM_COMMAND_MOVE_FLOATING_WINDOW=
4.=policy == WM_CROSS_OUTPUT_MOVE_TO_TARGET_WORKSPACE= ,则把窗口 =workspace_id= 改成目标 output 当前工作区。 4.=policy == WM_CROSS_OUTPUT_MOVE_TO_TARGET_WORKSPACE= ,则把窗口 =workspace_id= 改成目标 output 当前工作区。
副作用:
1. 若最终没有修改 =workspace_id= ,则不产生状态副作用。
2. 若最终修改了 =workspace_id= ,则副作用与 =WM_COMMAND_SEND_WINDOW_TO_WORKSPACE= 一致。
* 布局命令 * 布局命令
** =WM_COMMAND_SET_LAYOUT= ** =WM_COMMAND_SET_LAYOUT=
前置条件:
1. 目标 workspace 存在。
2. 目标 =layout_id= 已在 layout registry 中注册。
3. 目标 =layout_id= 必须出现在该 workspace 的 =available_layouts[]= 中。
状态变化: 状态变化:
1. 设置目标 workspace 的 =layout_id= 1. 设置目标 workspace 的 =layout_id=
2. 不改变窗口的 =workspace_id= 2. 不改变窗口的 =workspace_id=
3. 不改变 =stack_order[]= 3. 不改变 =stack_order[]=
4. 不影响 floating 窗口的基础几何。
5. 影响所有 =floating == false==geometry_mode == WM_GEOMETRY_NORMAL= 的窗口。
副作用: 副作用:
@@ -202,6 +201,7 @@
1. 目标 workspace 存在。 1. 目标 workspace 存在。
2. Workspace 的可用布局列表非空(=layout_count > 0=)。 2. Workspace 的可用布局列表非空(=layout_count > 0=)。
3. 当前 =layout_id= 必须存在于 =available_layouts[]= 中。
状态变化: 状态变化:
@@ -356,6 +356,11 @@
4. 不改变 =geometry_mode= 4. 不改变 =geometry_mode=
5. =float_rect= 是外框矩形,因此缩放的是包含边框后的最终窗口外框。 5. =float_rect= 是外框矩形,因此缩放的是包含边框后的最终窗口外框。
副作用:
1. =WM_DIRTY_LAYOUT=
2. =WM_DIRTY_RENDER=
* 边框约定 * 边框约定
1. 当前最小核心只支持全局统一边框,不支持每窗口边框宽度。 1. 当前最小核心只支持全局统一边框,不支持每窗口边框宽度。
@@ -366,31 +371,6 @@
=WM_GEOMETRY_FULLSCREEN= -> 0 =WM_GEOMETRY_FULLSCREEN= -> 0
其余模式 -> 全局边框宽度 其余模式 -> 全局边框宽度
副作用:
1. =WM_DIRTY_LAYOUT=
2. =WM_DIRTY_RENDER=
* 布局命令
** =WM_COMMAND_SET_LAYOUT=
前置条件:
1. 目标 workspace 存在。
2. 目标 =layout_id= 已在 layout registry 中注册。
状态变化:
1. 修改 workspace 的 =layout_id=
2. 不影响 floating 窗口的基础几何。
3. 影响所有 =floating == false==geometry_mode == WM_GEOMETRY_NORMAL= 的窗口。
副作用:
1. =WM_DIRTY_LAYOUT=
2. =WM_DIRTY_RENDER=
* redraw 和 quit * redraw 和 quit
** =WM_COMMAND_REDRAW= ** =WM_COMMAND_REDRAW=

View File

@@ -6,92 +6,133 @@
#+BEGIN_SRC c #+BEGIN_SRC c
void wm_runtime_process_event(wm_runtime_t *runtime, const wm_event_t *event) { void wm_runtime_process_event(wm_runtime_t *runtime, const wm_event_t *event) {
// 1. 元数据事件特殊处理(不经过命令系统) bool consumed = false;
if (is_metadata_event(event)) { bool quit_requested = false;
runtime_handle_metadata_event(runtime, event);
wm_command_buffer_reset(&runtime->command_buffer);
// 1. 辅助事件特殊处理(不经过命令系统)
if (is_auxiliary_event(event)) {
runtime_handle_auxiliary_event(runtime, event);
return; return;
} }
// 2. 交互态下的特殊路由 // 2. 交互态下的特殊路由
if (runtime->interaction.mode != WM_INTERACTION_NONE) { if (runtime->interaction.mode != WM_INTERACTION_NONE) {
if (route_interaction_event(runtime, event, &runtime->command_buffer)) { consumed = route_interaction_event(runtime, event, &runtime->command_buffer);
return; // 事件已被交互态消费
}
} }
// 3. 常规事件路由 // 3. 常规事件路由
wm_command_buffer_reset(&runtime->command_buffer); if (!consumed) {
if (!wm_policy_route_event(&runtime->state, event, &runtime->command_buffer)) { wm_policy_route_event(&runtime->state, event, &runtime->command_buffer);
return; // 没有生成命令
} }
// 4. 应用命令 // 4. 应用命令
for (size_t i = 0; i < runtime->command_buffer.count; i++) { for (size_t i = 0; i < runtime->command_buffer.count; i++) {
const wm_command_t *command = &runtime->command_buffer.items[i];
if (command->type == WM_COMMAND_QUIT) {
quit_requested = true;
continue;
}
wm_policy_apply_command( wm_policy_apply_command(
&runtime->state, &runtime->state,
&runtime->policy,
&runtime->layouts, &runtime->layouts,
&runtime->command_buffer.items[i], command,
&runtime->plan &runtime->plan
); );
} }
// 5. 处理副作用 // 5. runtime 控制命令在命令应用之后统一处理
if (runtime->plan.dirty_flags) { if (quit_requested) {
runtime_apply_plan(runtime); wm_runtime_stop(runtime);
} }
} }
#+END_SRC #+END_SRC
* 元数据事件处理规则 * 辅助事件处理规则
元数据事件直接更新 =wm_window_t= 中的元数据字段,不经过命令系统: 辅助事件直接更新 runtime 或广播给服务层,不经过命令系统:
#+BEGIN_SRC c
static bool is_auxiliary_event(const wm_event_t *event) {
return event->type == WM_EVENT_WINDOW_METADATA_CHANGED ||
event->type == WM_EVENT_STATUS_TICK;
}
static void runtime_handle_auxiliary_event(wm_runtime_t *runtime,
const wm_event_t *event) {
switch (event->type) {
case WM_EVENT_WINDOW_METADATA_CHANGED:
runtime_handle_metadata_event(runtime, event);
return;
case WM_EVENT_STATUS_TICK:
runtime_handle_status_tick(runtime, event);
return;
default:
return;
}
}
#+END_SRC
**=WM_EVENT_WINDOW_METADATA_CHANGED=** **=WM_EVENT_WINDOW_METADATA_CHANGED=**
#+BEGIN_SRC c #+BEGIN_SRC c
void runtime_handle_metadata_event(wm_runtime_t *runtime, const wm_event_t *event) { 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; const wm_window_metadata_changed_event_t *e = &event->data.window_metadata_changed;
// 1. 查找窗口 // 1. 查找窗口
const wm_window_t *win = wm_state_window_get(&runtime->state, e->window_id); const wm_window_t *win = wm_state_window_get(&runtime->state, e->window);
if (!win) { if (!win) {
return; // 窗口已被销毁 return; // 窗口已被销毁
} }
// 2. 通过 state 接口更新窗口元数据字段 // 2. 通过 state 接口更新窗口元数据字段
if (e->changed_fields & WM_WINDOW_META_CHANGED_TITLE) { if (e->changed_fields & WM_WINDOW_META_CHANGED_TITLE) {
wm_state_window_set_title(&runtime->state, e->window_id, e->title); wm_state_window_set_title(&runtime->state, e->window, e->title);
} }
if (e->changed_fields & WM_WINDOW_META_CHANGED_APP_ID) { if (e->changed_fields & WM_WINDOW_META_CHANGED_APP_ID) {
wm_state_window_set_app_id(&runtime->state, e->window_id, e->app_id); wm_state_window_set_app_id(&runtime->state, e->window, e->app_id);
} }
if (e->changed_fields & WM_WINDOW_META_CHANGED_CLASS) { if (e->changed_fields & WM_WINDOW_META_CHANGED_CLASS) {
wm_state_window_set_class(&runtime->state, e->window_id, e->class_name); wm_state_window_set_class(&runtime->state, e->window, e->class_name);
} }
if (e->changed_fields & WM_WINDOW_META_CHANGED_INSTANCE) { if (e->changed_fields & WM_WINDOW_META_CHANGED_INSTANCE) {
wm_state_window_set_instance(&runtime->state, e->window_id, e->instance_name); wm_state_window_set_instance(&runtime->state, e->window, e->instance_name);
} }
// 3. 标记 render 脏(状态栏需要更新) // 3. 标记 render 脏(状态栏需要更新)
runtime->plan.dirty_flags |= WM_DIRTY_RENDER; runtime->plan.dirty_flags |= WM_DIRTY_RENDER;
// 4. 通知订阅的服务(如状态栏组件) // 4. 通知订阅的服务(如状态栏组件)
wm_service_notify_metadata_changed(&runtime->services, wm_service_event_t service_event = {
e->window_id, .type = WM_SERVICE_EVENT_WINDOW_METADATA_CHANGED,
e->changed_fields); .as.window_metadata_changed = {
.window_id = e->window,
.changed_fields = e->changed_fields,
},
};
wm_service_registry_emit(&runtime->services, &service_event, &runtime->state);
} }
#+END_SRC #+END_SRC
**=WM_EVENT_STATUS_TICK=** **=WM_EVENT_STATUS_TICK=**
定时器事件,也不经过命令系统: 状态服务更新事件,也不经过命令系统:
#+BEGIN_SRC c #+BEGIN_SRC c
void runtime_handle_status_tick(wm_runtime_t *runtime, const wm_event_t *event) { void runtime_handle_status_tick(wm_runtime_t *runtime, const wm_event_t *event) {
// 通知状态服务更新 (void)event;
wm_service_notify_status_tick(&runtime->services);
// 状态服务更新后,如果产生了新的渲染需求,会通过命令系统处理 // 通知状态服务更新
wm_service_event_t service_event = {
.type = WM_SERVICE_EVENT_STATUS_TICK,
};
wm_service_registry_emit(&runtime->services, &service_event, &runtime->state);
} }
#+END_SRC #+END_SRC
@@ -103,35 +144,37 @@ void runtime_handle_status_tick(wm_runtime_t *runtime, const wm_event_t *event)
// keybindings.conf // keybindings.conf
keybindings = [ keybindings = [
{ {
// 键盘绑定 // 退出 WM
keysym: "Mod4+Return", keysym: "SUPER+SHIFT+q",
modifiers: [Mod4],
command: { command: {
type: WM_COMMAND_LAUNCH_TERMINAL type: WM_COMMAND_QUIT
} }
}, },
{ {
// 快捷键绑定(可带参数) // 焦点切到前一个窗口
keysym: "Mod4+Shift+q", keysym: "SUPER+j",
command: { command: {
type: WM_COMMAND_FOCUS_WINDOW, type: WM_COMMAND_FOCUS_DIRECTION,
output_id: 0,
direction: "prev" direction: "prev"
} }
}, },
{ {
// 布局切换 // 循环切换布局
keysym: "Mod4+space", keysym: "SUPER+space",
command: { command: {
type: WM_COMMAND_SET_LAYOUT, type: WM_COMMAND_CYCLE_LAYOUT,
layout_name: "tile" workspace_id: 0,
direction: 1
} }
}, },
{ {
// 窗口切换到工作区 // 切换到 workspace 0
keysym: "Mod4+Shift+1", keysym: "SUPER+1",
command: { command: {
type: WM_COMMAND_SEND_WINDOW_TO_WORKSPACE, type: WM_COMMAND_SWITCH_WORKSPACE,
workspace_index: 0 output_id: 0,
workspace_id: 0
} }
} }
] ]
@@ -141,7 +184,20 @@ keybindings = [
1. 完全匹配keysym + 所有 modifiers 1. 完全匹配keysym + 所有 modifiers
2. 通配符匹配keysym + 部分修饰符) 2. 通配符匹配keysym + 部分修饰符)
3. 默认绑定(如 Mod4+Enter 启动终端 3. 默认绑定(如 SUPER+1 切换到预设工作区
配置加载阶段应先把 =SUPER+m= 这类字符串解析成规范化按键组合。
这里的 =keysym= 指规范化后的逻辑主键编码,不是平台原始扫描码:
事件里的 =keycode= 只是保留 backend 的物理键位信息;
常规绑定仍以 =keysym + modifiers= 为准。
#+BEGIN_SRC c
typedef struct keybinding_t {
wm_keysym_t keysym;
wm_modifier_mask_t modifiers;
wm_command_type_t command_type;
} keybinding_t;
#+END_SRC
**=wm_policy_route_event()= 实现** **=wm_policy_route_event()= 实现**
@@ -151,28 +207,22 @@ bool wm_policy_route_event(const wm_state_t *state,
wm_command_buffer_t *out) { wm_command_buffer_t *out) {
switch (event->type) { switch (event->type) {
case WM_EVENT_KEY_PRESS: case WM_EVENT_KEY_PRESS:
return route_key_press(state, &event->as.key_press, out); return route_key_press(state, &event->data.key_press, out);
case WM_EVENT_BUTTON_PRESS: case WM_EVENT_POINTER_BUTTON_PRESS:
return route_button_press(state, &event->as.button_press, out); return route_pointer_press(state, &event->data.pointer_button_press, out);
case WM_EVENT_MAP_REQUEST: case WM_EVENT_WINDOW_MAP_REQUEST:
return route_map_request(state, &event->as.map_request, out); return route_map_request(state, &event->data.window_map_request, out);
case WM_EVENT_UNMAP_NOTIFY: case WM_EVENT_WINDOW_UNMAP:
return route_unmap_notify(state, &event->as.unmap_notify, out); return route_unmap_notify(state, &event->data.window_unmap, out);
case WM_EVENT_DESTROY_NOTIFY: case WM_EVENT_WINDOW_DESTROY:
return route_destroy_notify(state, &event->as.destroy_notify, out); return route_destroy_notify(state, &event->data.window_destroy, out);
case WM_EVENT_CONFIGURE_REQUEST: case WM_EVENT_CONFIGURE_REQUEST:
return route_configure_request(state, &event->as.configure_request, out); return route_configure_request(state, &event->data.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: default:
return false; return false;
@@ -185,7 +235,7 @@ static bool route_key_press(const wm_state_t *state,
wm_command_buffer_t *out) { wm_command_buffer_t *out) {
// 1. 查找匹配的快捷键绑定 // 1. 查找匹配的快捷键绑定
const keybinding_t *binding = keybinding_find( const keybinding_t *binding = keybinding_find(
e->keycode, e->keysym,
e->modifiers e->modifiers
); );
@@ -205,18 +255,18 @@ static bool route_key_press(const wm_state_t *state,
// 窗口映射请求路由 // 窗口映射请求路由
static bool route_map_request(const wm_state_t *state, static bool route_map_request(const wm_state_t *state,
const wm_map_request_event_t *e, const wm_window_map_request_event_t *e,
wm_command_buffer_t *out) { wm_command_buffer_t *out) {
// 1. 检查窗口是否已存在 // 1. 检查窗口是否已存在
if (state_find_window(state, e->window_id)) { if (wm_state_window_get(state, e->window)) {
return false; // 窗口已管理,忽略 return false; // 窗口已管理,忽略
} }
// 2. 生成 MANAGE_WINDOW 命令 // 2. 生成 MANAGE_WINDOW 命令
wm_command_t cmd = { wm_command_t cmd = {
.type = WM_COMMAND_MANAGE_WINDOW, .type = WM_COMMAND_MANAGE_WINDOW,
.manage_window = { .as.manage_window = {
.window_id = e->window_id, .window_id = e->window,
// 应用规则确定初始状态 // 应用规则确定初始状态
} }
}; };
@@ -251,23 +301,21 @@ static bool route_move_floating_event(wm_runtime_t *runtime,
const wm_event_t *event, const wm_event_t *event,
wm_command_buffer_t *out) { wm_command_buffer_t *out) {
switch (event->type) { switch (event->type) {
case WM_EVENT_BUTTON_RELEASE: case WM_EVENT_POINTER_BUTTON_RELEASE:
// 结束移动交互 // 结束移动交互。这是 runtime 局部状态,不需要额外命令。
wm_command_t end_cmd = { runtime->interaction = (wm_interaction_state_t){0};
.type = WM_COMMAND_END_INTERACTION return true;
};
return wm_command_buffer_push(out, end_cmd);
case WM_EVENT_POINTER_MOTION: case WM_EVENT_POINTER_MOTION:
// 生成移动命令 // 生成移动命令
wm_command_t move_cmd = { wm_command_t move_cmd = {
.type = WM_COMMAND_MOVE_FLOATING_WINDOW, .type = WM_COMMAND_MOVE_FLOATING_WINDOW,
.move_floating_window = { .as.move_floating_window = {
.window_id = runtime->interaction.window_id, .window_id = runtime->interaction.window_id,
.delta_x = event->as.pointer_motion.root.x - .dx = event->data.pointer_motion.root.x -
runtime->interaction.pointer_origin.x, runtime->interaction.pointer_origin.x,
.delta_y = event->as.pointer_motion.root.y - .dy = event->data.pointer_motion.root.y -
runtime->interaction.pointer_origin.y runtime->interaction.pointer_origin.y
} }
}; };
return wm_command_buffer_push(out, move_cmd); return wm_command_buffer_push(out, move_cmd);
@@ -287,20 +335,18 @@ static bool route_move_floating_event(wm_runtime_t *runtime,
| 事件类型 | 可能生成的命令 | 条件 | | 事件类型 | 可能生成的命令 | 条件 |
|---------|---------------|------| |---------|---------------|------|
| KEY_PRESS | 任意命令(通过快捷键绑定) | 绑定存在 | | KEY_PRESS | 任意命令(通过快捷键绑定) | 绑定存在 |
| BUTTON_PRESS | FOCUS_WINDOW, RAISE_WINDOW | 点击窗口 | | POINTER_BUTTON_PRESS | FOCUS_WINDOW, RAISE_WINDOW | 点击窗口 |
| MAP_REQUEST | MANAGE_WINDOW | 窗口未管理 | | WINDOW_MAP_REQUEST | MANAGE_WINDOW | 窗口未管理 |
| UNMAP_NOTIFY | (可能不生成命令) | - | | WINDOW_UNMAP | (可能不生成命令) | - |
| DESTROY_NOTIFY | UNMANAGE_WINDOW | 窗口已管理 | | WINDOW_DESTROY | UNMANAGE_WINDOW | 窗口已管理 |
| CONFIGURE_REQUEST | (可能不生成命令) | floating 窗口 | | CONFIGURE_REQUEST | (可能不生成命令) | floating 窗口 |
| OUTPUT_CHANGED | UPSERT_OUTPUT | - |
| OUTPUT_REMOVED | REMOVE_OUTPUT | - |
| POINTER_MOTION | MOVE/RESIZE 命令 | 交互态下 | | POINTER_MOTION | MOVE/RESIZE 命令 | 交互态下 |
* 事件处理的边界条件 * 事件处理的边界条件
**1. 元数据事件边界** **1. 辅助事件边界**
元数据事件满足以下条件时直接处理,不经过命令系统: 辅助事件满足以下条件时直接处理,不经过命令系统:
- 事件类型是 `WM_EVENT_WINDOW_METADATA_CHANGED` - 事件类型是 `WM_EVENT_WINDOW_METADATA_CHANGED`
- 事件类型是 `WM_EVENT_STATUS_TICK` - 事件类型是 `WM_EVENT_STATUS_TICK`
@@ -308,20 +354,26 @@ static bool route_move_floating_event(wm_runtime_t *runtime,
交互态满足以下条件时,优先路由到交互处理器: 交互态满足以下条件时,优先路由到交互处理器:
- `runtime->interaction.mode != WM_INTERACTION_NONE` - `runtime->interaction.mode != WM_INTERACTION_NONE`
- 事件类型与交互相关POINTER_MOTION, BUTTON_RELEASE - 事件类型与交互相关POINTER_MOTION, POINTER_BUTTON_RELEASE
**3. 窗口管理边界** **3. 显示器变化边界**
显示器配置变化不进入 core 的增量更新逻辑:
- backend 检测到显示器增删或几何变化时,使 `next_event()` 返回 `WM_BACKEND_NEXT_RESTART_REQUIRED`
- runtime 退出并由外层重启,再重新 bootstrap 固定的 output/workspace/layout 集合
**4. 窗口管理边界**
窗口管理的边界条件: 窗口管理的边界条件:
- `WM_EVENT_MAP_REQUEST`:窗口已存在则忽略 - `WM_EVENT_WINDOW_MAP_REQUEST`:窗口已存在则忽略
- `WM_EVENT_DESTROY_NOTIFY`:窗口不存在则忽略 - `WM_EVENT_WINDOW_DESTROY`:窗口不存在则忽略
- `WM_EVENT_CONFIGURE_REQUEST`:只有 floating 窗口才响应 - `WM_EVENT_CONFIGURE_REQUEST`:只有 floating 窗口才响应
* 事件处理顺序保证 * 事件处理顺序保证
为了保证确定性行为,事件处理必须按以下顺序进行: 为了保证确定性行为,事件处理必须按以下顺序进行:
1. **元数据事件优先**状态栏信息更新不触发布局计算 1. **辅助事件优先**元数据和状态服务更新不走命令系统
2. **交互态优先**:拖拽操作优先于快捷键 2. **交互态优先**:拖拽操作优先于快捷键
3. **快捷键匹配优先级**:完全匹配 → 通配符 → 默认 3. **快捷键匹配优先级**:完全匹配 → 通配符 → 默认
4. **命令执行顺序**:命令缓冲中的命令按顺序执行 4. **命令执行顺序**:命令缓冲中的命令按顺序执行
@@ -334,12 +386,20 @@ void wm_runtime_run(wm_runtime_t *runtime) {
while (runtime->running) { while (runtime->running) {
// 1. 获取事件 // 1. 获取事件
if (!wm_backend_next_event(&runtime->backend, &event)) { wm_backend_next_result_t poll_result =
continue; runtime->backend.api->next_event(&runtime->backend, &event);
}
// 2. 处理事件 if (poll_result == WM_BACKEND_NEXT_EVENT) {
wm_runtime_process_event(runtime, &event); // 2. 处理事件
wm_runtime_process_event(runtime, &event);
} else if (poll_result == WM_BACKEND_NEXT_RESTART_REQUIRED) {
wm_runtime_stop(runtime);
break;
} else if (poll_result == WM_BACKEND_NEXT_STOP ||
poll_result == WM_BACKEND_NEXT_ERROR) {
wm_runtime_stop(runtime);
break;
}
// 3. 如果有副作用,应用它们 // 3. 如果有副作用,应用它们
if (runtime->plan.dirty_flags) { if (runtime->plan.dirty_flags) {
@@ -348,18 +408,32 @@ void wm_runtime_run(wm_runtime_t *runtime) {
wm_runtime_relayout(runtime); wm_runtime_relayout(runtime);
} }
// 3.2 应用平台副作用 // 3.2 依次提交 effect。RENDER_OUTPUT 留给服务层处理。
wm_backend_apply_effects(&runtime->backend, &runtime->plan); for (size_t i = 0; i < runtime->plan.effect_count; i++) {
const wm_effect_t *effect = &runtime->plan.effects[i];
// 3.3 通知服务层(渲染、状态栏等) if (effect->type == WM_EFFECT_RENDER_OUTPUT) {
if (runtime->plan.dirty_flags & WM_DIRTY_RENDER) { wm_service_event_t service_event = {
wm_service_notify_render(&runtime->services, &runtime->state); .type = WM_SERVICE_EVENT_RENDER_OUTPUT,
.as.render_output = {
.output_id = effect->as.render_output.output_id,
},
};
wm_service_registry_emit(&runtime->services,
&service_event,
&runtime->state);
continue;
}
runtime->backend.api->apply_effect(&runtime->backend, effect);
} }
// 3.4 刷新平台 // 3.3 刷新平台
wm_backend_flush(&runtime->backend); if (runtime->backend.api->flush) {
runtime->backend.api->flush(&runtime->backend);
}
// 3.5 重置 plan // 3.4 重置 plan
wm_plan_reset(&runtime->plan); wm_plan_reset(&runtime->plan);
} }
@@ -375,9 +449,9 @@ void wm_runtime_run(wm_runtime_t *runtime) {
1. **快捷键系统**:通过 keybindings.conf 配置 1. **快捷键系统**:通过 keybindings.conf 配置
2. **规则系统**:在 MANAGE_WINDOW 命令中应用 2. **规则系统**:在 MANAGE_WINDOW 命令中应用
3. **布局切换**:通过 SET_LAYOUT 命令 3. **布局切换**:通过 SET_LAYOUT / CYCLE_LAYOUT 命令
4. **拖拽交互**:通过交互态管理 4. **拖拽交互**:通过交互态管理
5. **多显示器**OUTPUT_CHANGED/REMOVED 事件处理 5. **多显示器**显示器配置变化时通过 `WM_BACKEND_NEXT_RESTART_REQUIRED` 触发重启
* 缺失的文档总结 * 缺失的文档总结

View File

@@ -34,39 +34,36 @@
#+BEGIN_SRC c #+BEGIN_SRC c
bool wm_policy_apply_command(wm_state_t *state, bool wm_policy_apply_command(wm_state_t *state,
const wm_policy_config_t *policy,
const wm_layout_registry_t *layouts, const wm_layout_registry_t *layouts,
const wm_command_t *command, const wm_command_t *command,
wm_plan_t *plan) { wm_plan_t *plan) {
bool changed = false; bool changed = false;
bool generation_relevant = false;
if (!state || !command || !plan) return false; if (!state || !policy || !command || !plan) return false;
generation_relevant = command->type != WM_COMMAND_REDRAW &&
command->type != WM_COMMAND_QUIT;
switch (command->type) { switch (command->type) {
case WM_COMMAND_NONE: case WM_COMMAND_NONE:
return false; return false;
case WM_COMMAND_MANAGE_WINDOW: case WM_COMMAND_MANAGE_WINDOW:
changed = apply_manage_window(state, command, plan); changed = apply_manage_window(state, policy, command, plan);
break; break;
case WM_COMMAND_UNMANAGE_WINDOW: case WM_COMMAND_UNMANAGE_WINDOW:
changed = apply_unmanage_window(state, command, plan); changed = apply_unmanage_window(state, command, plan);
break; 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: case WM_COMMAND_FOCUS_WINDOW:
changed = apply_focus_window(state, command, plan); changed = apply_focus_window(state, policy, command, plan);
break; break;
case WM_COMMAND_FOCUS_DIRECTION: case WM_COMMAND_FOCUS_DIRECTION:
changed = apply_focus_direction(state, command, plan); changed = apply_focus_direction(state, policy, command, plan);
break; break;
case WM_COMMAND_RAISE_WINDOW: case WM_COMMAND_RAISE_WINDOW:
@@ -106,7 +103,7 @@ bool wm_policy_apply_command(wm_state_t *state,
break; break;
case WM_COMMAND_TOGGLE_MINIMIZE: case WM_COMMAND_TOGGLE_MINIMIZE:
changed = apply_toggle_minimize(state, command, plan); changed = apply_toggle_minimize(state, policy, command, plan);
break; break;
case WM_COMMAND_MOVE_FLOATING_WINDOW: case WM_COMMAND_MOVE_FLOATING_WINDOW:
@@ -134,7 +131,7 @@ bool wm_policy_apply_command(wm_state_t *state,
break; break;
} }
if (changed) state->generation++; if (changed && generation_relevant) state->generation++;
return changed; return changed;
} }
#+END_SRC #+END_SRC
@@ -148,13 +145,15 @@ 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_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 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_append_window(wm_state_t *state, wm_window_t window);
static bool state_remove_output(wm_state_t *state, wm_output_id_t id); static bool state_remove_window(wm_state_t *state, wm_window_id_t id);
static bool stack_raise(wm_state_t *state, wm_window_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_lower(wm_state_t *state, wm_window_id_t id);
static bool stack_remove(wm_state_t *state, wm_window_id_t id); static bool stack_remove(wm_state_t *state, wm_window_id_t id);
static bool stack_append(wm_state_t *state, wm_window_id_t id); static bool stack_append(wm_state_t *state, wm_window_id_t id);
static bool workspace_has_layout(const wm_workspace_t *workspace,
wm_layout_id_t id);
static void mark_layout_dirty(wm_plan_t *plan); static void mark_layout_dirty(wm_plan_t *plan);
static void mark_stack_dirty(wm_plan_t *plan); static void mark_stack_dirty(wm_plan_t *plan);
@@ -164,6 +163,8 @@ static void mark_output_dirty(wm_plan_t *plan);
static void push_render_all_outputs(const wm_state_t *state, wm_plan_t *plan); static void push_render_all_outputs(const wm_state_t *state, wm_plan_t *plan);
static void push_restack_effect(const wm_state_t *state, wm_plan_t *plan); static void push_restack_effect(const wm_state_t *state, wm_plan_t *plan);
static void maybe_refocus_workspace(wm_state_t *state, wm_workspace_t *workspace); static void maybe_refocus_workspace(wm_state_t *state, wm_workspace_t *workspace);
static const wm_output_t *find_output_by_point(const wm_state_t *state,
wm_point_t point);
#+END_SRC #+END_SRC
* 命令分支骨架 * 命令分支骨架
@@ -172,6 +173,7 @@ static void maybe_refocus_workspace(wm_state_t *state, wm_workspace_t *workspace
#+BEGIN_SRC c #+BEGIN_SRC c
static bool apply_manage_window(wm_state_t *state, static bool apply_manage_window(wm_state_t *state,
const wm_policy_config_t *policy,
const wm_command_t *command, const wm_command_t *command,
wm_plan_t *plan) { wm_plan_t *plan) {
wm_window_id_t id = command->as.manage_window.window_id; wm_window_id_t id = command->as.manage_window.window_id;
@@ -201,7 +203,8 @@ static bool apply_manage_window(wm_state_t *state,
if (!state_append_window(state, win)) return false; if (!state_append_window(state, win)) return false;
if (!stack_append(state, id)) return false; if (!stack_append(state, id)) return false;
if (command->as.manage_window.initial_state.set_focus) { if (command->as.manage_window.initial_state.set_focus ||
policy->manage_sets_focus) {
ws->focused_window_id = id; ws->focused_window_id = id;
} }
@@ -213,43 +216,6 @@ static bool apply_manage_window(wm_state_t *state,
} }
#+END_SRC #+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()= ** =apply_unmanage_window()=
#+BEGIN_SRC c #+BEGIN_SRC c
@@ -280,6 +246,7 @@ static bool apply_unmanage_window(wm_state_t *state,
#+BEGIN_SRC c #+BEGIN_SRC c
static bool apply_focus_window(wm_state_t *state, static bool apply_focus_window(wm_state_t *state,
const wm_policy_config_t *policy,
const wm_command_t *command, const wm_command_t *command,
wm_plan_t *plan) { wm_plan_t *plan) {
wm_window_id_t id = command->as.focus_window.window_id; wm_window_id_t id = command->as.focus_window.window_id;
@@ -292,6 +259,11 @@ static bool apply_focus_window(wm_state_t *state,
ws->focused_window_id = id; ws->focused_window_id = id;
if (policy->focus_raises && stack_raise(state, id)) {
plan->dirty_flags |= WM_DIRTY_STACK;
push_restack_effect(state, plan);
}
plan->dirty_flags |= WM_DIRTY_RENDER; plan->dirty_flags |= WM_DIRTY_RENDER;
return true; return true;
} }
@@ -331,6 +303,7 @@ static bool apply_toggle_sticky(wm_state_t *state,
#+BEGIN_SRC c #+BEGIN_SRC c
static bool apply_focus_direction(wm_state_t *state, static bool apply_focus_direction(wm_state_t *state,
const wm_policy_config_t *policy,
const wm_command_t *command, const wm_command_t *command,
wm_plan_t *plan) { wm_plan_t *plan) {
wm_output_id_t output_id = command->as.focus_direction.output_id; wm_output_id_t output_id = command->as.focus_direction.output_id;
@@ -342,7 +315,8 @@ static bool apply_focus_direction(wm_state_t *state,
wm_window_id_t next = wm_window_id_t next =
find_focus_candidate_in_workspace(state, ws, find_focus_candidate_in_workspace(state, ws,
command->as.focus_direction.direction); command->as.focus_direction.direction,
policy->sticky_windows_participate_in_direction_focus);
if (!next || ws->focused_window_id == next) return false; if (!next || ws->focused_window_id == next) return false;
ws->focused_window_id = next; ws->focused_window_id = next;
@@ -382,15 +356,16 @@ static bool apply_switch_workspace(wm_state_t *state,
wm_output_t *out = wm_output_t *out =
require_output(state, command->as.switch_workspace.output_id); require_output(state, command->as.switch_workspace.output_id);
if (!out) return false; if (!out) return false;
if (!require_workspace(state, command->as.switch_workspace.workspace_id)) { wm_workspace_t *ws =
require_workspace(state, command->as.switch_workspace.workspace_id);
if (!ws) return false;
if (ws->output_id != out->id) return false;
if (out->current_workspace_id == ws->id) {
return false; return false;
} }
if (out->current_workspace_id == command->as.switch_workspace.workspace_id) { out->current_workspace_id = ws->id;
return false;
}
out->current_workspace_id = command->as.switch_workspace.workspace_id;
plan->dirty_flags |= WM_DIRTY_OUTPUT | WM_DIRTY_LAYOUT | WM_DIRTY_RENDER; plan->dirty_flags |= WM_DIRTY_OUTPUT | WM_DIRTY_LAYOUT | WM_DIRTY_RENDER;
push_render_all_outputs(state, plan); push_render_all_outputs(state, plan);
return true; return true;
@@ -436,6 +411,10 @@ static bool apply_send_window_to_output(wm_state_t *state,
if (command->as.send_window_to_output.policy == if (command->as.send_window_to_output.policy ==
WM_CROSS_OUTPUT_KEEP_WORKSPACE) { WM_CROSS_OUTPUT_KEEP_WORKSPACE) {
/*
* 保留 workspace 但跨 output 调整几何,不属于这个命令的职责;
* 调用方应使用 MOVE_FLOATING_WINDOW 只改 float_rect。
*/
return false; return false;
} }
@@ -522,16 +501,25 @@ static bool apply_toggle_fullscreen(wm_state_t *state,
#+BEGIN_SRC c #+BEGIN_SRC c
static bool apply_toggle_minimize(wm_state_t *state, static bool apply_toggle_minimize(wm_state_t *state,
const wm_policy_config_t *policy,
const wm_command_t *command, const wm_command_t *command,
wm_plan_t *plan) { wm_plan_t *plan) {
wm_window_t *win = wm_window_t *win =
require_window(state, command->as.toggle_minimize.window_id); require_window(state, command->as.toggle_minimize.window_id);
if (!win) return false; if (!win) return false;
wm_workspace_t *ws = require_workspace(state, win->workspace_id);
if (win->geometry_mode == WM_GEOMETRY_MINIMIZED) { if (win->geometry_mode == WM_GEOMETRY_MINIMIZED) {
win->geometry_mode = WM_GEOMETRY_NORMAL; win->geometry_mode = WM_GEOMETRY_NORMAL;
} else { } else {
win->geometry_mode = WM_GEOMETRY_MINIMIZED; win->geometry_mode = WM_GEOMETRY_MINIMIZED;
if (policy->minimize_clears_focus &&
ws &&
ws->focused_window_id == win->id) {
maybe_refocus_workspace(state, ws);
}
} }
plan->dirty_flags |= WM_DIRTY_LAYOUT | WM_DIRTY_RENDER; plan->dirty_flags |= WM_DIRTY_LAYOUT | WM_DIRTY_RENDER;
@@ -557,7 +545,7 @@ static bool apply_move_floating_window(wm_state_t *state,
if (command->as.move_floating_window.commit_workspace_change) { if (command->as.move_floating_window.commit_workspace_change) {
const wm_output_t *out = const wm_output_t *out =
wm_state_find_output_by_point(state, command->as.move_floating_window.anchor); find_output_by_point(state, command->as.move_floating_window.anchor);
if (out && if (out &&
command->as.move_floating_window.policy == command->as.move_floating_window.policy ==
WM_CROSS_OUTPUT_MOVE_TO_TARGET_WORKSPACE) { WM_CROSS_OUTPUT_MOVE_TO_TARGET_WORKSPACE) {
@@ -613,6 +601,9 @@ static bool apply_set_layout(wm_state_t *state,
if (!wm_layout_slot_get(layouts, command->as.set_layout.layout_id)) { if (!wm_layout_slot_get(layouts, command->as.set_layout.layout_id)) {
return false; return false;
} }
if (!workspace_has_layout(ws, command->as.set_layout.layout_id)) {
return false;
}
if (ws->layout_id == command->as.set_layout.layout_id) return false; if (ws->layout_id == command->as.set_layout.layout_id) return false;
ws->layout_id = command->as.set_layout.layout_id; ws->layout_id = command->as.set_layout.layout_id;
@@ -635,12 +626,15 @@ static bool apply_cycle_layout(wm_state_t *state,
// 找到当前布局在列表中的索引 // 找到当前布局在列表中的索引
size_t current_idx = 0; size_t current_idx = 0;
bool found_current = false;
for (size_t i = 0; i < ws->layout_count; i++) { for (size_t i = 0; i < ws->layout_count; i++) {
if (ws->available_layouts[i] == ws->layout_id) { if (ws->available_layouts[i] == ws->layout_id) {
current_idx = i; current_idx = i;
found_current = true;
break; break;
} }
} }
if (!found_current) return false;
// 计算下一个索引(支持前后循环) // 计算下一个索引(支持前后循环)
size_t next_idx; size_t next_idx;
@@ -683,11 +677,15 @@ static bool apply_quit(wm_state_t *state,
wm_plan_t *plan) { wm_plan_t *plan) {
(void)state; (void)state;
(void)command; (void)command;
plan->dirty_flags |= WM_DIRTY_NONE; (void)plan;
return true; return true;
} }
#+END_SRC #+END_SRC
通常情况下 =WM_COMMAND_QUIT= 会由 runtime 在命令应用外层直接拦截并调用
=wm_runtime_stop()= 。保留 =apply_quit()= 这个空实现,只是为了让 switch
结构保持完整。
* 推荐的后处理步骤 * 推荐的后处理步骤
=apply_*()= 返回后,建议由上层统一做这些事,而不是在每个分支里直接跑布局: =apply_*()= 返回后,建议由上层统一做这些事,而不是在每个分支里直接跑布局:

View File

@@ -10,7 +10,7 @@
-- zdwm.lua -- zdwm.lua
outputs = { outputs = {
{ id = 0, name = "DP-1", enabled = true, primary = true }, { id = 0, name = "DP-1", primary = true },
} }
-- 所有 workspace 都归属 DP-1 -- 所有 workspace 都归属 DP-1
@@ -42,8 +42,8 @@ workspaces = auto_generate_workspaces({
#+BEGIN_SRC lua #+BEGIN_SRC lua
-- 左侧显示器 1-5右侧显示器 6-10 -- 左侧显示器 1-5右侧显示器 6-10
outputs = { outputs = {
{ id = 0, name = "DP-1", enabled = true, primary = true }, { id = 0, name = "DP-1", primary = true },
{ id = 1, name = "DP-2", enabled = true }, { id = 1, name = "DP-2" },
} }
workspaces = { workspaces = {
@@ -112,8 +112,8 @@ workspaces = {
outputs = { outputs = {
{ id = 0, name = "eDP-1", primary = true }, { id = 0, name = "eDP-1", primary = true },
{ id = 1, name = "DP-1", enabled = true }, { id = 1, name = "DP-1" },
{ id = 2, name = "HDMI-1", enabled = true }, { id = 2, name = "HDMI-1" },
} }
workspaces = { workspaces = {
@@ -134,13 +134,12 @@ workspaces = {
} }
#+END_SRC #+END_SRC
** 动态配置(脚本式) ** 启动阶段脚本生成
根据实际检测到的输出数量动态生成: 根据启动阶段检测到的输出数量生成:
#+BEGIN_SRC lua #+BEGIN_SRC lua
function configure_workspaces(runtime) function configure_workspaces(outputs)
local outputs = runtime:scan_outputs()
local workspaces = {} local workspaces = {}
local ws_id = 1 local ws_id = 1
@@ -220,12 +219,13 @@ bool load_workspace_config(const char *path,
#+BEGIN_SRC lua #+BEGIN_SRC lua
-- 声明布局注册表 -- 声明布局注册表
-- layout_id 按注册顺序稳定分配tile=0, monocle=1, floating=2, ...
layouts = { layouts = {
{ id = 0, name = "T", fn = layout_tile }, { name = "tile", symbol = "T", fn = layout_tile },
{ id = 1, name = "M", fn = layout_monocle }, { name = "monocle", symbol = "M", fn = layout_monocle },
{ id = 2, name = "F", fn = layout_floating }, { name = "floating", symbol = "F", fn = layout_floating },
{ id = 3, name = "S", fn = layout_stack }, { name = "stack", symbol = "S", fn = layout_stack },
{ id = 4, name = "G", fn = layout_grid }, { name = "grid", symbol = "G", fn = layout_grid },
} }
-- 单显示器:每个 workspace 可用所有布局 -- 单显示器:每个 workspace 可用所有布局
@@ -280,7 +280,7 @@ workspaces = {
1. *固定归属*workspace.output_id 在创建后不应改变 1. *固定归属*workspace.output_id 在创建后不应改变
2. *输出变化*显示器配置变化时WM 会重启,重新扫描和配置 2. *输出变化*显示器配置变化时WM 会重启,重新扫描和配置
3. *可见性*窗口只在归属的 output 上可见 3. *可见性*普通非 =sticky= 窗口先受 workspace 可见性约束,再按最终矩形与 output 的相交关系决定具体显示在哪些 output 上;=sticky= 窗口只放宽 workspace 可见
4. *独立性*:切换不同 output 的 workspace 互不影响 4. *独立性*:切换不同 output 的 workspace 互不影响
5. *布局列表*workspace.layouts 在启动时分配,运行时固定 5. *布局列表*workspace.layouts 在启动时分配,运行时固定
6. *布局切换*:使用 CYCLE_LAYOUT 命令在可用布局间循环切换 6. *布局切换*:使用 =CYCLE_LAYOUT==SET_LAYOUT= 命令在可用布局集合内切换当前布局

View File

@@ -5,9 +5,24 @@
typedef struct wm_backend_t wm_backend_t; typedef struct wm_backend_t wm_backend_t;
typedef enum wm_backend_next_result_t {
// 成功产出一个 runtime 输入事件out 有效
WM_BACKEND_NEXT_EVENT,
// backend 请求 runtime 正常停止(例如连接关闭)
WM_BACKEND_NEXT_STOP,
// backend 检测到必须重建 bootstrap 的平台变化(例如显示器配置变化)
WM_BACKEND_NEXT_RESTART_REQUIRED,
// backend 遇到无法恢复的错误
WM_BACKEND_NEXT_ERROR,
} wm_backend_next_result_t;
typedef struct wm_backend_api_t { typedef struct wm_backend_api_t {
bool (*init)(wm_backend_t *backend); bool (*init)(wm_backend_t *backend);
bool (*next_event)(wm_backend_t *backend, wm_event_t *out); /*
* 返回一个 backend 轮询结果。
* 只有返回 WM_BACKEND_NEXT_EVENT 时out 才包含一个完整的 runtime 输入事件。
*/
wm_backend_next_result_t (*next_event)(wm_backend_t *backend, wm_event_t *out);
/* /*
* Runtime filters out service-side effects such as WM_EFFECT_RENDER_OUTPUT. * Runtime filters out service-side effects such as WM_EFFECT_RENDER_OUTPUT.
* Backends only receive platform effects here. * Backends only receive platform effects here.

View File

@@ -14,8 +14,6 @@ typedef enum wm_command_type_t {
WM_COMMAND_NONE, WM_COMMAND_NONE,
WM_COMMAND_MANAGE_WINDOW, WM_COMMAND_MANAGE_WINDOW,
WM_COMMAND_UNMANAGE_WINDOW, WM_COMMAND_UNMANAGE_WINDOW,
WM_COMMAND_UPSERT_OUTPUT,
WM_COMMAND_REMOVE_OUTPUT,
WM_COMMAND_FOCUS_WINDOW, WM_COMMAND_FOCUS_WINDOW,
WM_COMMAND_FOCUS_DIRECTION, WM_COMMAND_FOCUS_DIRECTION,
WM_COMMAND_RAISE_WINDOW, WM_COMMAND_RAISE_WINDOW,
@@ -51,17 +49,6 @@ typedef struct wm_command_t {
wm_window_id_t window_id; wm_window_id_t window_id;
} unmanage_window; } 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 { struct {
wm_window_id_t window_id; wm_window_id_t window_id;
} focus_window; } focus_window;

View File

@@ -2,52 +2,77 @@
#include "wm_types.h" #include "wm_types.h"
/*
* wm_event_t 表示“runtime 真正关心的输入事实”:
* - 它不是平台原始事件的逐字段照抄
* - 它允许 backend 对平台事件做最小必要的归一化 / 过滤 / 聚合
* - 它不承载 runtime 控制语义stop / restart / error 由 backend.next_event()
* 的返回结果表达,而不是伪装成一个事件类型
*/
typedef enum wm_event_type_t { typedef enum wm_event_type_t {
WM_EVENT_NONE,
WM_EVENT_KEY_PRESS, WM_EVENT_KEY_PRESS,
WM_EVENT_BUTTON_PRESS, WM_EVENT_POINTER_BUTTON_PRESS,
WM_EVENT_BUTTON_RELEASE, WM_EVENT_POINTER_BUTTON_RELEASE,
WM_EVENT_POINTER_MOTION, WM_EVENT_POINTER_MOTION,
WM_EVENT_MAP_REQUEST, WM_EVENT_WINDOW_MAP_REQUEST,
WM_EVENT_UNMAP_NOTIFY, WM_EVENT_WINDOW_UNMAP,
WM_EVENT_DESTROY_NOTIFY, WM_EVENT_WINDOW_DESTROY,
WM_EVENT_WINDOW_METADATA_CHANGED, WM_EVENT_WINDOW_METADATA_CHANGED,
WM_EVENT_CONFIGURE_REQUEST, WM_EVENT_CONFIGURE_REQUEST,
WM_EVENT_OUTPUT_CHANGED,
WM_EVENT_OUTPUT_REMOVED,
WM_EVENT_TIMER_TICK, WM_EVENT_TIMER_TICK,
WM_EVENT_STATUS_TICK, WM_EVENT_STATUS_TICK,
WM_EVENT_QUIT,
} wm_event_type_t; } wm_event_type_t;
typedef struct wm_key_press_event_t { typedef struct wm_key_press_event_t {
/*
* 规范化后的逻辑主键编码xkb keysym
* backend 应先结合 keymap / xkb state 把原始按键解析成逻辑键,再写入这里。
* 它适合与 "SUPER+m" 这类配置字符串解析结果做匹配。
*/
wm_keysym_t keysym;
// backend 归一化后的修饰键掩码
wm_modifier_mask_t modifiers;
/*
* backend 原始物理键编码。
* 它保留平台侧按键信息,便于调试、日志或未来按物理键位绑定;
* 常规快捷键匹配不应直接使用它。
*/
uint32_t keycode; uint32_t keycode;
uint32_t modifiers;
} wm_key_press_event_t; } wm_key_press_event_t;
typedef struct wm_button_event_t { typedef struct wm_pointer_button_event_t {
uint32_t button; wm_button_t button;
uint32_t modifiers; wm_modifier_mask_t modifiers;
// backend 原始按钮码,仅供调试或未来平台特化逻辑使用
uint32_t raw_button;
// 全局桌面坐标
wm_point_t root; wm_point_t root;
wm_window_id_t target_window_id; // 相对 window 的坐标;若 window == WM_WINDOW_ID_INVALID则该值未定义
} wm_button_event_t; wm_point_t local;
// 当前命中的窗口;若未命中窗口,则为 WM_WINDOW_ID_INVALID
wm_window_id_t window;
} wm_pointer_button_event_t;
typedef struct wm_pointer_motion_event_t { typedef struct wm_pointer_motion_event_t {
// 全局桌面坐标
wm_point_t root; wm_point_t root;
wm_window_id_t target_window_id; // 相对 window 的坐标;若 window == WM_WINDOW_ID_INVALID则该值未定义
wm_point_t local;
// 当前命中的窗口;若未命中窗口,则为 WM_WINDOW_ID_INVALID
wm_window_id_t window;
} wm_pointer_motion_event_t; } wm_pointer_motion_event_t;
typedef struct wm_map_request_event_t { typedef struct wm_window_map_request_event_t {
wm_window_id_t window_id; wm_window_id_t window;
} wm_map_request_event_t; } wm_window_map_request_event_t;
typedef struct wm_unmap_notify_event_t { typedef struct wm_window_unmap_event_t {
wm_window_id_t window_id; wm_window_id_t window;
} wm_unmap_notify_event_t; } wm_window_unmap_event_t;
typedef struct wm_destroy_notify_event_t { typedef struct wm_window_destroy_event_t {
wm_window_id_t window_id; wm_window_id_t window;
} wm_destroy_notify_event_t; } wm_window_destroy_event_t;
typedef enum wm_window_meta_changed_flags_t { typedef enum wm_window_meta_changed_flags_t {
WM_WINDOW_META_CHANGED_NONE = 0, WM_WINDOW_META_CHANGED_NONE = 0,
@@ -58,7 +83,7 @@ typedef enum wm_window_meta_changed_flags_t {
} wm_window_meta_changed_flags_t; } wm_window_meta_changed_flags_t;
typedef struct wm_window_metadata_changed_event_t { typedef struct wm_window_metadata_changed_event_t {
wm_window_id_t window_id; wm_window_id_t window;
uint32_t changed_fields; uint32_t changed_fields;
/* /*
* These strings are borrowed from the backend adapter and are valid until * These strings are borrowed from the backend adapter and are valid until
@@ -73,39 +98,33 @@ typedef struct wm_window_metadata_changed_event_t {
} wm_window_metadata_changed_event_t; } wm_window_metadata_changed_event_t;
typedef struct wm_configure_request_event_t { typedef struct wm_configure_request_event_t {
wm_window_id_t window_id; wm_window_id_t window;
wm_rect_t requested_rect; wm_rect_t requested_rect;
} wm_configure_request_event_t; } wm_configure_request_event_t;
typedef struct wm_output_changed_event_t {
wm_output_id_t output_id;
wm_rect_t geometry;
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 { typedef struct wm_timer_tick_event_t {
uint64_t now_ms; wm_time_ms_t now_ms;
} wm_timer_tick_event_t; } wm_timer_tick_event_t;
typedef struct wm_status_tick_event_t {
wm_time_ms_t now_ms;
} wm_status_tick_event_t;
typedef struct wm_event_t { typedef struct wm_event_t {
wm_event_type_t type; wm_event_type_t type;
uint64_t ts_ms; // backend 归一化后的事件时间值(毫秒),不是 wall-clock 时间戳
wm_time_ms_t time_ms;
union { union {
wm_key_press_event_t key_press; wm_key_press_event_t key_press;
wm_button_event_t button_press; wm_pointer_button_event_t pointer_button_press;
wm_button_event_t button_release; wm_pointer_button_event_t pointer_button_release;
wm_pointer_motion_event_t pointer_motion; wm_pointer_motion_event_t pointer_motion;
wm_map_request_event_t map_request; wm_window_map_request_event_t window_map_request;
wm_unmap_notify_event_t unmap_notify; wm_window_unmap_event_t window_unmap;
wm_destroy_notify_event_t destroy_notify; wm_window_destroy_event_t window_destroy;
wm_window_metadata_changed_event_t window_metadata_changed; wm_window_metadata_changed_event_t window_metadata_changed;
wm_configure_request_event_t configure_request; 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; wm_timer_tick_event_t timer_tick;
} as; wm_status_tick_event_t status_tick;
} data;
} wm_event_t; } wm_event_t;

View File

@@ -33,14 +33,22 @@ typedef bool (*wm_layout_fn)(const wm_layout_ctx_t *ctx,
typedef struct wm_layout_slot_t { typedef struct wm_layout_slot_t {
wm_layout_id_t id; wm_layout_id_t id;
/* /*
* 布局标识符,用于: * 布局稳定名称,用于:
* - 状态栏显示(应简短,如 "T", "M", "F"
* - 日志输出
* - 配置引用 * - 配置引用
* - 日志输出
* - 调试输出
*
* 推荐使用可读名字,如 "tile"、"monocle"、"floating"。
*/
const char *name;
/*
* 布局符号,用于:
* - 状态栏显示(应简短,如 "T", "M", "F"
* - 紧凑场景下的快速识别
* *
* 推荐使用 1-2 个字符的简短标识符。 * 推荐使用 1-2 个字符的简短标识符。
*/ */
const char *name; const char *symbol;
wm_layout_fn fn; wm_layout_fn fn;
} wm_layout_slot_t; } wm_layout_slot_t;
@@ -58,8 +66,18 @@ bool wm_layout_result_push(wm_layout_result_t *result, wm_layout_item_t item);
void wm_layout_registry_init(wm_layout_registry_t *registry); void wm_layout_registry_init(wm_layout_registry_t *registry);
void wm_layout_registry_shutdown(wm_layout_registry_t *registry); void wm_layout_registry_shutdown(wm_layout_registry_t *registry);
size_t wm_layout_registry_count(const wm_layout_registry_t *registry);
const wm_layout_slot_t *wm_layout_registry_at(
const wm_layout_registry_t *registry, size_t index);
bool wm_layout_register(wm_layout_registry_t *registry, wm_layout_id_t id, wm_layout_id_t wm_layout_register(wm_layout_registry_t *registry,
wm_layout_fn fn); const char *name, const char *symbol,
wm_layout_fn fn);
/*
* layout registry 只允许在 bootstrap / 初始化阶段注册。
* 运行时布局集合保持固定,只允许切换 workspace 当前选中的 layout_id。
*/
wm_layout_fn wm_layout_lookup(const wm_layout_registry_t *registry, wm_layout_fn wm_layout_lookup(const wm_layout_registry_t *registry,
wm_layout_id_t id); wm_layout_id_t id);
const wm_layout_slot_t *wm_layout_slot_get(const wm_layout_registry_t *registry,
wm_layout_id_t id);

View File

@@ -4,6 +4,7 @@
#include "wm_event.h" #include "wm_event.h"
#include "wm_layout.h" #include "wm_layout.h"
#include "wm_plan.h" #include "wm_plan.h"
#include "wm_policy_config.h"
#include "wm_state.h" #include "wm_state.h"
typedef struct wm_command_buffer_t { typedef struct wm_command_buffer_t {
@@ -22,5 +23,6 @@ bool wm_policy_route_event(const wm_state_t *state, const wm_event_t *event,
wm_command_buffer_t *out); wm_command_buffer_t *out);
bool wm_policy_apply_command(wm_state_t *state, bool wm_policy_apply_command(wm_state_t *state,
const wm_policy_config_t *policy,
const wm_layout_registry_t *layouts, const wm_layout_registry_t *layouts,
const wm_command_t *command, wm_plan_t *plan); const wm_command_t *command, wm_plan_t *plan);

View File

@@ -25,7 +25,8 @@ typedef struct wm_interaction_state_t {
typedef struct wm_runtime_bootstrap_t { typedef struct wm_runtime_bootstrap_t {
/* /*
* 输出配置(启动时扫描显示器后填充) * 输出配置(启动时扫描显示器后填充)
* 这是 bootstrap 后固定的 output 集合。
*/ */
const wm_output_t *outputs; const wm_output_t *outputs;
size_t output_count; size_t output_count;
@@ -33,16 +34,19 @@ typedef struct wm_runtime_bootstrap_t {
/* /*
* 工作区配置(已绑定 output * 工作区配置(已绑定 output
* workspace.output_id 指向 outputs 中的某个输出 * workspace.output_id 指向 outputs 中的某个输出
* 这是 bootstrap 后固定的 workspace 集合。
*/ */
const wm_workspace_t *workspaces; const wm_workspace_t *workspaces;
size_t workspace_count; size_t workspace_count;
/* /*
* 布局算法注册 * 布局算法注册
* layout registry 在 bootstrap 后固定;运行时只切换 workspace.layout_id。
*/ */
struct { struct {
wm_layout_fn fn; wm_layout_fn fn;
const char *name; const char *name;
const char *symbol;
} *layouts; } *layouts;
size_t layout_count; size_t layout_count;
@@ -67,7 +71,7 @@ typedef struct wm_runtime_bootstrap_t {
size_t initial_command_count; size_t initial_command_count;
} wm_runtime_bootstrap_t; } wm_runtime_bootstrap_t;
// 运行时上下文(实现时采用不透明结构体,此定义仅作参考) // 当前草案直接展示 runtime 结构,便于讨论;实现阶段可再封装
typedef struct wm_runtime_t { typedef struct wm_runtime_t {
bool running; bool running;
@@ -88,8 +92,8 @@ bool wm_runtime_register_service(wm_runtime_t *runtime, wm_service_t service);
/* /*
* 处理单个事件,方便做无平台依赖的单元测试。 * 处理单个事件,方便做无平台依赖的单元测试。
* 元数据事件允许 runtime 直接更新 auxiliary store控制状态变更仍经由 * 元数据事件允许 runtime 直接更新 wm_window_t 中的元数据字段;
* route_event() + apply_command()。 * 控制状态变更仍经由 route_event() + apply_command()。
*/ */
bool wm_runtime_process_event(wm_runtime_t *runtime, const wm_event_t *event); bool wm_runtime_process_event(wm_runtime_t *runtime, const wm_event_t *event);

View File

@@ -1,17 +1,26 @@
#pragma once #pragma once
#include <stdint.h>
#include "wm_state.h" #include "wm_state.h"
typedef enum wm_service_event_type_t { typedef enum wm_service_event_type_t {
WM_SERVICE_EVENT_NONE, WM_SERVICE_EVENT_NONE,
WM_SERVICE_EVENT_RUNTIME_STARTED, WM_SERVICE_EVENT_RUNTIME_STARTED,
WM_SERVICE_EVENT_RUNTIME_STOPPING, WM_SERVICE_EVENT_RUNTIME_STOPPING,
WM_SERVICE_EVENT_WINDOW_METADATA_CHANGED,
WM_SERVICE_EVENT_STATUS_TICK,
WM_SERVICE_EVENT_RENDER_OUTPUT, WM_SERVICE_EVENT_RENDER_OUTPUT,
} wm_service_event_type_t; } wm_service_event_type_t;
typedef struct wm_service_event_t { typedef struct wm_service_event_t {
wm_service_event_type_t type; wm_service_event_type_t type;
union { union {
struct {
wm_window_id_t window_id;
uint32_t changed_fields;
} window_metadata_changed;
struct { struct {
wm_output_id_t output_id; wm_output_id_t output_id;
} render_output; } render_output;
@@ -43,3 +52,6 @@ void wm_service_registry_shutdown(wm_service_registry_t *registry);
bool wm_service_registry_register(wm_service_registry_t *registry, bool wm_service_registry_register(wm_service_registry_t *registry,
wm_service_t service); wm_service_t service);
void wm_service_registry_emit(wm_service_registry_t *registry,
const wm_service_event_t *event,
const wm_state_t *state);

View File

@@ -26,6 +26,9 @@ typedef struct wm_window_t {
char *app_id; char *app_id;
char *class_name; char *class_name;
char *instance_name; char *instance_name;
// 任务栏可见性提示
bool skip_taskbar;
} wm_window_t; } wm_window_t;
typedef struct wm_workspace_t { typedef struct wm_workspace_t {
@@ -34,8 +37,8 @@ typedef struct wm_workspace_t {
wm_layout_id_t layout_id; // 当前活动布局(可用布局列表中的一个) wm_layout_id_t layout_id; // 当前活动布局(可用布局列表中的一个)
wm_window_id_t focused_window_id; wm_window_id_t focused_window_id;
// 可用布局列表(启动时根据配置分配,运行时固定 // 可用布局列表(启动时根据配置分配,之后固定不变
wm_layout_id_t *available_layouts; const wm_layout_id_t *available_layouts;
size_t layout_count; size_t layout_count;
// 名称(核心算法不依赖,仅用于状态栏显示) // 名称(核心算法不依赖,仅用于状态栏显示)
@@ -44,23 +47,20 @@ typedef struct wm_workspace_t {
typedef struct wm_output_t { typedef struct wm_output_t {
wm_output_id_t id; wm_output_id_t id;
bool enabled; wm_rect_t geometry; // 输出完整几何bootstrap 后固定)
wm_rect_t geometry; // 输出完整几何 wm_rect_t workarea; // 可用区域排除面板等bootstrap 后固定)
wm_rect_t workarea; // 可用区域(排除面板等)
wm_workspace_id_t current_workspace_id; wm_workspace_id_t current_workspace_id;
} wm_output_t; } wm_output_t;
// ========== 全局状态容器 ========== // ========== 全局状态容器 ==========
typedef struct wm_state_t { typedef struct wm_state_t {
// 内部实现(不透明结构体,实现文件中定义) // 头文件级草案当前直接公开字段,便于讨论;实现阶段可再封装
wm_workspace_t *workspaces; wm_workspace_t *workspaces;
size_t workspace_count; size_t workspace_count;
size_t workspace_capacity;
wm_output_t *outputs; wm_output_t *outputs;
size_t output_count; size_t output_count;
size_t output_capacity;
wm_window_t *windows; wm_window_t *windows;
size_t window_count; size_t window_count;
@@ -78,6 +78,7 @@ typedef struct wm_state_t {
// ========== API 函数 ========== // ========== API 函数 ==========
// 初始化/清理 // 初始化/清理
// workspace/output 集合大小在 init 时一次性确定,之后不再变化。
void wm_state_init(wm_state_t *state, void wm_state_init(wm_state_t *state,
size_t workspace_count, size_t workspace_count,
size_t output_count); size_t output_count);
@@ -89,8 +90,11 @@ 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); 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); 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); bool wm_state_workspace_valid(wm_state_t *state, wm_workspace_id_t id);
void wm_state_workspace_set_focused_window(wm_state_t *state,
wm_workspace_id_t workspace_id,
wm_window_id_t window_id);
// 查询某个 output 的所有 workspace // 查询某个 output 的所有 workspace(只读视图)
size_t wm_state_workspace_get_by_output(const wm_state_t *state, size_t wm_state_workspace_get_by_output(const wm_state_t *state,
wm_output_id_t output_id, wm_output_id_t output_id,
wm_workspace_t **out_workspaces); wm_workspace_t **out_workspaces);
@@ -98,15 +102,16 @@ size_t wm_state_workspace_get_by_output(const wm_state_t *state,
// ========== Workspace 布局操作 ========== // ========== Workspace 布局操作 ==========
/* /*
* 设置 workspace 的可用布局列表 * 设置 workspace 的可用布局列表
* 注意:应在 workspace 初始化调用运行时不应修改 * 仅允许在 bootstrap / 初始化阶段调用运行时不应修改
*/ */
void wm_workspace_set_layouts(wm_workspace_t *workspace, void wm_workspace_set_layouts(wm_workspace_t *workspace,
const wm_layout_id_t *layouts, const wm_layout_id_t *layouts,
size_t count); size_t count);
/* /*
* 获取 workspace 的可用布局列表 * 获取 workspace 的可用布局列表
* 该列表在 bootstrap 完成后保持不变。
*/ */
const wm_layout_id_t *wm_workspace_get_layouts(const wm_workspace_t *workspace, const wm_layout_id_t *wm_workspace_get_layouts(const wm_workspace_t *workspace,
size_t *out_count); size_t *out_count);
@@ -132,6 +137,14 @@ 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); 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); 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); bool wm_state_output_valid(wm_state_t *state, wm_output_id_t id);
/*
* 切换某个 output 当前显示的 workspace。
* 这不会修改 output 集合本身,也不会改变 workspace->output_id 绑定。
* 调用方必须保证目标 workspace 固定归属于该 output。
*/
void wm_state_output_set_current_workspace(wm_state_t *state,
wm_output_id_t output_id,
wm_workspace_id_t workspace_id);
// ========== Window 操作 ========== // ========== Window 操作 ==========
@@ -140,6 +153,32 @@ 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); 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); 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); size_t wm_state_window_count(const wm_state_t *state);
void wm_state_window_set_workspace(wm_state_t *state, wm_window_id_t window_id,
wm_workspace_id_t workspace_id);
void wm_state_window_set_geometry_mode(wm_state_t *state,
wm_window_id_t window_id,
wm_window_geometry_mode_t geometry_mode);
void wm_state_window_set_floating(wm_state_t *state, wm_window_id_t window_id,
bool floating);
void wm_state_window_set_sticky(wm_state_t *state, wm_window_id_t window_id,
bool sticky);
void wm_state_window_set_urgent(wm_state_t *state, wm_window_id_t window_id,
bool urgent);
void wm_state_window_set_skip_taskbar(wm_state_t *state,
wm_window_id_t window_id,
bool skip_taskbar);
void wm_state_window_set_float_rect(wm_state_t *state, wm_window_id_t window_id,
wm_rect_t float_rect);
void wm_state_window_set_frame_rect(wm_state_t *state, wm_window_id_t window_id,
wm_rect_t frame_rect);
bool wm_state_window_set_title(wm_state_t *state, wm_window_id_t window_id,
const char *title);
bool wm_state_window_set_app_id(wm_state_t *state, wm_window_id_t window_id,
const char *app_id);
bool wm_state_window_set_class(wm_state_t *state, wm_window_id_t window_id,
const char *class_name);
bool wm_state_window_set_instance(wm_state_t *state, wm_window_id_t window_id,
const char *instance_name);
// ========== 查询辅助函数 ========== // ========== 查询辅助函数 ==========
@@ -152,9 +191,9 @@ bool wm_state_workspace_is_visible(const wm_state_t *state,
/* /*
* 判断窗口是否可见 * 判断窗口是否可见
* 窗口可见 iff * 窗口可见 iff
* 1. 窗口的 workspace 固定归属某个 output * 1. 窗口是 sticky则只要求窗口未最小化
* 2. 该 output 当前正显示这个 workspace * 2. 否则,窗口的 workspace 固定归属某个 output
* 3. 窗口未最小化 * 3. 且该 output 当前正显示这个 workspace
*/ */
bool wm_state_window_should_be_visible(const wm_state_t *state, bool wm_state_window_should_be_visible(const wm_state_t *state,
const wm_window_t *window); const wm_window_t *window);

View File

@@ -11,23 +11,67 @@ typedef uint32_t wm_window_id_t;
typedef uint16_t wm_workspace_id_t; typedef uint16_t wm_workspace_id_t;
typedef uint16_t wm_output_id_t; typedef uint16_t wm_output_id_t;
typedef uint32_t wm_layout_id_t; typedef uint32_t wm_layout_id_t;
/*
* backend 归一化后的毫秒时间值。
* 它对齐 X11 / Wayland 常见的 32-bit ms 时间域,用于事件排序和短时差计算;
* 不是 wall-clock / Unix epoch基准也不保证可见因此允许回绕。
*/
typedef uint32_t wm_time_ms_t;
/*
* 逻辑键编码,使用 xkb keysym 值空间。
* 它表示“当前 keymap 下,这次按键对应的逻辑主键”,而不是平台原始扫描码。
*/
typedef uint32_t wm_keysym_t;
typedef uint32_t wm_modifier_mask_t;
// 无效 ID 标记 // 无效 ID 标记
#define WM_WINDOW_ID_INVALID ((wm_window_id_t)0) #define WM_WINDOW_ID_INVALID ((wm_window_id_t)0)
#define WM_WORKSPACE_ID_INVALID ((wm_workspace_id_t)-1) #define WM_WORKSPACE_ID_INVALID ((wm_workspace_id_t)-1)
#define WM_OUTPUT_ID_INVALID ((wm_output_id_t)-1) #define WM_OUTPUT_ID_INVALID ((wm_output_id_t)-1)
#define WM_LAYOUT_ID_INVALID ((wm_layout_id_t)-1) #define WM_LAYOUT_ID_INVALID ((wm_layout_id_t)-1)
#define WM_KEYSYM_INVALID ((wm_keysym_t)0)
/*
* 归一化后的修饰键掩码:
* - backend 应在产出 wm_event_t 之前合并 left/right 变体
* - CapsLock / NumLock 这类锁定位不应进入该掩码
*/
typedef enum wm_modifier_bit_t {
WM_MOD_NONE = 0,
WM_MOD_SHIFT = 1u << 0,
WM_MOD_CTRL = 1u << 1,
WM_MOD_ALT = 1u << 2,
WM_MOD_SUPER = 1u << 3,
} wm_modifier_bit_t;
/*
* 归一化后的指针按键语义。
* backend 应把平台原始按钮码映射到这些稳定枚举值;
* 若需要保留平台原始值,可放在事件里的 raw_button 字段。
*/
typedef enum wm_button_t {
WM_BUTTON_NONE = 0,
WM_BUTTON_LEFT,
WM_BUTTON_MIDDLE,
WM_BUTTON_RIGHT,
WM_BUTTON_WHEEL_UP,
WM_BUTTON_WHEEL_DOWN,
WM_BUTTON_WHEEL_LEFT,
WM_BUTTON_WHEEL_RIGHT,
WM_BUTTON_BACK,
WM_BUTTON_FORWARD,
} wm_button_t;
typedef struct wm_point_t { typedef struct wm_point_t {
int16_t x; int32_t x;
int16_t y; int32_t y;
} wm_point_t; } wm_point_t;
typedef struct wm_rect_t { typedef struct wm_rect_t {
int16_t x; int32_t x;
int16_t y; int32_t y;
uint16_t w; int32_t width;
uint16_t h; int32_t height;
} wm_rect_t; } wm_rect_t;
typedef enum wm_window_geometry_mode_t { typedef enum wm_window_geometry_mode_t {

63
src/core/event.h Normal file
View File

@@ -0,0 +1,63 @@
#pragma once
#include <stdint.h>
#include "core/types.h"
typedef enum wm_event_type_t {
WM_EVENT_KEY_PRESS,
WM_EVENT_POINTER_BUTTON_PRESS,
WM_EVENT_POINTER_BUTTON_RELEASE,
WM_EVENT_POINTER_MOTION,
WM_EVENT_WINDOW_MAP_REQUEST,
WM_EVENT_WINDOW_UNMAP,
WM_EVENT_WINDOW_DESTROY,
WM_EVENT_WINDOW_METADATA_CHANGED,
WM_EVENT_CONFIGURE_REQUEST,
} wm_event_type_t;
typedef enum wm_modifier_bit_t {
WM_MOD_SHIFT = 1u << 0,
WM_MOD_CTRL = 1u << 1,
WM_MOD_ALT = 1u << 2,
WM_MOD_SUPER = 1u << 3,
} wm_modifier_bit_t;
typedef uint32_t wm_modifier_mask_t;
typedef uint32_t wm_keysym_t;
typedef struct wm_key_press_event_t {
wm_modifier_mask_t modifiers;
wm_keysym_t keysym;
uint32_t keycode;
} wm_key_press_event_t;
typedef enum wm_button_t {
WM_BUTTON_LEFT,
WM_BUTTON_RIGHT,
WM_BUTTON_MIDDLE,
} wm_button_t;
typedef struct wm_pointer_button_event_t {
wm_modifier_mask_t modifiers;
wm_button_t button;
wm_window_id_t window;
wm_point_t root;
wm_point_t local;
} wm_pointer_button_event_t;
typedef struct wm_pointer_motion_event_t {
wm_window_id_t window;
wm_point_t root;
wm_point_t local;
} wm_pointer_motion_event_t;
typedef struct wm_event_t {
wm_event_type_t type;
uint32_t time_ms;
union {
wm_key_press_event_t key_press;
wm_pointer_button_event_t pointer_button_press;
wm_pointer_button_event_t pointer_button_release;
wm_pointer_motion_event_t pointer_motion;
} data;
} wm_event_t;