core: 定义核心事件数据模型并更新其文档

This commit is contained in:
2026-03-21 02:51:43 +08:00
parent 4c96f2a1db
commit 931dc3d8d5
22 changed files with 1204 additions and 220 deletions

View File

@@ -14,17 +14,24 @@
负责主循环、模块装配和固定执行顺序。
2. =backend=
负责平台交互。它把平台原始事件转成 =wm_event_t= 执行 =wm_effect_t=
负责平台交互。它把平台原始事件转成 =wm_event_t= ,执行 =wm_effect_t=
并在需要时根据固定的键盘/鼠标绑定表安装平台侧被动抓键 / 抓按钮。
像 X11 的 =_NET_ACTIVE_WINDOW= 这类 =ClientMessage= 也应在这里先归一化成
request event而不是直接改 core 状态。
3. =state=
负责保存窗口、工作区、输出和全局堆叠顺序,是唯一真状态。
当前头文件级草案先直接展示结构体字段,便于讨论;
实现阶段再收敛为不透明结构体也不迟。
窗口元数据title/app_id/class/instance直接存储在 =wm_window_t= 中。
窗口 hint / capability=urgent= / =fixed_size= / =skip_taskbar=)也直接存储在
=wm_window_t= 中。
工作区名称存储在 =wm_workspace_t= 中。
4. =policy=
负责把事件路由为命令,并把命令应用到状态。
=WM_EVENT_KEY_PRESS= / =WM_EVENT_POINTER_BUTTON_PRESS= 的路由都使用 bootstrap
固定下来的输入绑定表。
5. =layout=
负责为某个输出上当前显示的工作区中的平铺窗口计算外框几何。
@@ -74,6 +81,8 @@ const wm_window_t *win = wm_state_window_get(state, window_id); // O(n)
2. =workspaces=
3. =layout registry=
4. 每个 workspace 的 =available_layouts=
5. =key binding table=
6. =pointer binding table=
运行时允许变化的只是:
@@ -104,7 +113,8 @@ const wm_window_t *win = wm_state_window_get(state, window_id); // O(n)
1. =runtime= 拥有 =state==plan==command_buffer==layout_registry=
其中 workspace/output/layout registry 集合在 init 完成后保持固定。
2. =runtime= 也拥有 =policy config= 、全局边框配置、交互态和服务注册表。
2. =runtime= 也拥有 =policy config=键盘/鼠标绑定表视图、全局边框配置、
交互态和服务注册表。
3. =backend= 只负责平台事件翻译和平台副作用,不拥有控制状态,不直接拼 bar。
4. 窗口元数据title/app_id/class/instance直接存储在 =wm_window_t= 中,与控制状态一同管理。
5. 工作区名称存储在 =wm_workspace_t= 中,与控制状态一同管理。
@@ -117,16 +127,23 @@ const wm_window_t *win = wm_state_window_get(state, window_id); // O(n)
- 工作区数量和初始配置(由用户配置决定)
- 布局算法注册(由代码或配置决定)
- 策略配置focus_raises 等)
- 键盘绑定表(规范化的 =keysym + modifiers -> wm_command_t= 模板)
- 鼠标按键绑定表(规范化的 =button + modifiers + target -> wm_command_t= 模板)
- 全局统一边框宽度
- 全局边框颜色集(普通/焦点)
- 初始命令(如启动时扫描到的窗口)
2. 启动时扫描到的已有窗口,不直接塞进 =wm_state_t= ,而是翻译成一批 =MANAGE_WINDOW= 命令进入 runtime。
3. 工作区在启动时根据配置分配 N 个,运行时不再增删。
3. 若 backend 提供 =set_keybindings()= / =set_pointer_bindings()= runtime
在 init 时把 bootstrap 里的绑定表同步给 backend供平台侧建立被动抓键 /
抓按钮。
4. 输出在启动时扫描并分配对应数量的数组,运行时不再增删。
4. 工作区在启动时根据配置分配 N 个,运行时不再增删。
5. layout registry 在启动时注册完成,运行时不再增删。
5. 输出在启动时扫描并分配对应数量的数组,运行时不再增删。
6. layout registry 在启动时注册完成,运行时不再增删。
* 显示器热插拔策略
@@ -223,12 +240,17 @@ sticky 窗口是否参与方向焦点搜索。
当前最小核心只保留窗口边框,不支持标题栏和其他窗口装饰。
1. =wm_window_t= 不存储 =border_width= ;边框宽度不是窗口真状态。
1. =wm_window_t= 不存储 =border_width= 或 =border_color= ;边框样式不是窗口真状态。
2. =wm_runtime_bootstrap_t.border_width= 提供全局统一边框宽度。
3. =float_rect= 和 =frame_rect= 都表示“包含边框后的外框矩形”
4. =runtime= 在提交 =WM_EFFECT_CONFIGURE_WINDOW= 时解析有效边框宽度:
3. =wm_runtime_bootstrap_t.border_palette= 提供全局边框颜色集(普通/焦点)
4. =float_rect= 和 =frame_rect= 都表示“包含边框后的外框矩形”。
5. =runtime= 在提交 =WM_EFFECT_CONFIGURE_WINDOW= 时解析有效边框宽度:
- =WM_GEOMETRY_FULLSCREEN= : 边框宽度为 0
- 其余模式 : 使用全局边框宽度
6. =runtime= 在提交 =WM_EFFECT_CONFIGURE_WINDOW= 时解析有效边框颜色:
- 当前 workspace 的 focused window : =border_palette.focused_rgba=
- 其余窗口 : =border_palette.normal_rgba=
- 若有效边框宽度为 0则 backend 可忽略颜色字段
* 交互态
@@ -253,11 +275,19 @@ move/resize 这类拖拽交互不属于 =wm_state_t= 真状态,但也不能继
=config= 仍然在核心外,但它的输出必须改成 core-native 数据,而不是旧的 =client_t/tag_t= 钩子:
1. 配置加载结果应产出 workspace 描述、policy 配置、layout 注册、规则表、快捷键到 command 的映射和服务设置。
以及全局统一边框宽度
1. 配置加载结果应产出 workspace 描述、policy 配置、layout 注册、规则表、
键盘/鼠标绑定到 command 的映射和服务设置
以及全局统一边框宽度和边框颜色集。
这里的输入绑定应直接收敛为 =wm_key_binding_table_t= /
=wm_pointer_binding_table_t= 。
2. 配置层不得直接持有 =client_t= 、=tag_t= 或直接修改 =wm_state_t= 。
3. 配置重载的边界是“重建 bootstrap 和服务配置,再显式应用到 runtime”而不是任意时刻从外部写内存。
补充约定:
1. 鼠标绑定在最小核心里只区分 =root= / =window= 两类目标。
2. 状态栏、标签栏、任务栏等点击区域属于核心外服务,不进入 core 的 pointer binding 表。
* 对象关系
这套草案里的关系固定为:
@@ -300,7 +330,8 @@ workspace 的 =layout_id= 字段指向当前活动的布局(列表中的一个
2. 每个工作区必须且只能固定归属一个输出,运行时不再改变。
3. 每个输出必须且只能显示一个当前工作区(从归属的工作区中选择)。
4. 可见性由窗口归属、工作区归属、输出当前工作区和最小化状态共同推导。
5. =geometry_mode= 只负责几何模式,不负责 z-order
5. =geometry_mode= 只负责几何模式,不负责 z-order;其中
=WM_GEOMETRY_MINIMIZED= 统一覆盖 X11 =IconicState= 这类“最小化/图标化”状态。
6. =stack_order[]= 只负责 z-order不负责几何模式。
7. =floating= 是二值状态:窗口要么由 layout 决定基础几何,要么由 =float_rect= 决定基础几何。
8. =sticky => floating= =sticky= 只放宽 workspace 可见性,不改变窗口归属工作区。
@@ -311,7 +342,8 @@ workspace 的 =layout_id= 字段指向当前活动的布局(列表中的一个
13. 工作区名称存储在 =wm_workspace_t= 中,核心算法不应依赖此字段。
14. 每个 workspace 的可用布局列表在启动时分配,之后固定不变;运行时只允许通过 =CYCLE_LAYOUT= 或 =SET_LAYOUT= 命令切换当前活动布局。
15. =frame_rect= 和 =float_rect= 都表示包含边框后的外框矩形。
16. =border_width= 不是 =wm_window_t= 真状态;它由 runtime 从全局配置和几何模式推导。
16. =border_width= / =border_color= 都不是 =wm_window_t= 真状态;它由 runtime
从全局配置、几何模式和焦点关系推导。
* Generation 字段
@@ -332,7 +364,8 @@ workspace 的 =layout_id= 字段指向当前活动的布局(列表中的一个
2. runtime 先处理窗口元数据更新这类辅助事件,并直接更新 =wm_window_t= 中的元数据字段
3. =policy.route_event()=
4. =policy.apply_command()=
5. 根据 =dirty_flags= 决定是否重新运行 =layout= 、解析最终外框矩形,并补全 =configure= effect 的边框宽度
5. 根据 =dirty_flags= 决定是否重新运行 =layout= 、解析最终外框矩形,并补全
=configure= effect 的边框宽度和边框颜色
6. 将平台 effect 发送给 =backend=
7. 将 render 失效通知发送给服务层
8. =backend.flush()=
@@ -350,50 +383,57 @@ workspace 的 =layout_id= 字段指向当前活动的布局(列表中的一个
3. =wm_event.h=
统一后的 runtime 输入事件;不承载 stop/restart 这类控制信号。
像 =_NET_ACTIVE_WINDOW= 这类平台协议请求,也应在这一层归一化为独立的
request event。
客户端 property / hint 变化(如 =urgent= / =fixed_size= / =skip_taskbar=)也应在
这一层归一化为辅助事件,而不是伪装成 configure request。
4. =wm_command.h=
4. =wm_binding.h=
=WM_EVENT_KEY_PRESS= / =WM_EVENT_POINTER_BUTTON_PRESS= 使用的输入绑定表和匹配规则。
5. =wm_command.h=
强类型语义命令。
命令语义补充见 [[file:WM_COMMAND_RULES.org][WM_COMMAND_RULES.org]]
=sticky= 多显示器与跨屏行为补充见 [[file:WM_STICKY_WINDOW_RULES.org][WM_STICKY_WINDOW_RULES.org]] 。
5. =wm_plan.h=
6. =wm_plan.h=
脏标记和后端副作用。
6. =wm_layout.h=
7. =wm_layout.h=
布局注册表、布局输入和布局输出。
7. =wm_backend.h=
8. =wm_backend.h=
后端子系统接口。
8. =wm_policy_config.h=
9. =wm_policy_config.h=
显式策略开关,替代散落在文档正文里的"可选策略"。
9. =wm_policy.h=
10. =wm_policy.h=
事件路由和命令应用接口。
10. =wm_runtime.h=
11. =wm_runtime.h=
运行时上下文和生命周期接口。
11. =wm_service.h=
12. =wm_service.h=
核心外服务的注册和事件订阅接口。
12. [[file:WM_COMMAND_RULES.org][WM_COMMAND_RULES.org]]
13. [[file:WM_COMMAND_RULES.org][WM_COMMAND_RULES.org]]
关键命令的前置条件、状态转移、可见性和副作用规则。
13. [[file:WM_STICKY_WINDOW_RULES.org][WM_STICKY_WINDOW_RULES.org]]
14. [[file:WM_STICKY_WINDOW_RULES.org][WM_STICKY_WINDOW_RULES.org]]
=sticky= 窗口在 workspace、taskbar、多显示器和跨 output 拖动下的补充规则。
14. [[file:WM_POLICY_APPLY_COMMAND_SKELETON.org][WM_POLICY_APPLY_COMMAND_SKELETON.org]]
15. [[file:WM_POLICY_APPLY_COMMAND_SKELETON.org][WM_POLICY_APPLY_COMMAND_SKELETON.org]]
=wm_policy_apply_command()= 的伪代码骨架和实现顺序建议。
15. [[file:WORKSPACE_CONFIG_EXAMPLES.org][WORKSPACE_CONFIG_EXAMPLES.org]]
16. [[file:WORKSPACE_CONFIG_EXAMPLES.org][WORKSPACE_CONFIG_EXAMPLES.org]]
Workspace-Output 配置示例和最佳实践。
* 下一步实施顺序
当前阶段不建议继续扩展抽象,应该开始把最小核心草案落成可运行骨架。推荐顺序如下:
1. 在 =src/core/= 下建立最小骨架,至少放 =wm_types/state/plan/layout/policy/policy_config/runtime/service= 的头文件和空实现,先不要替换现有 =src/= 逻辑。
1. 在 =src/core/= 下建立最小骨架,至少放 =wm_types/binding/state/plan/layout/policy/policy_config/runtime/service= 的头文件和空实现,先不要替换现有 =src/= 逻辑。
2. 优先实现 =state + query + plan= 这一层,把动态数组管理、=find_*= 查询函数和 =stack_order[]= 操作补齐。
@@ -404,11 +444,15 @@ Workspace-Output 配置示例和最佳实践。
=SET_LAYOUT=
=CYCLE_LAYOUT=
=TOGGLE_FLOATING=
=TOGGLE_MAXIMIZE=
=SET_MAXIMIZED=
=MOVE_FLOATING_WINDOW=
=BEGIN_MOVE_FLOATING_INTERACTION=
=BEGIN_RESIZE_FLOATING_INTERACTION=
4. 给上述命令建立一个无 X11 依赖的测试入口,直接构造 =wm_command_t= ,检查 =wm_state_t==wm_plan_t= 的变化是否符合文档规则。
5.=event -> command -> state -> plan/effect= 这条链路跑通后,再编写一层很薄的 adapter把现有事件翻译为 =wm_event_t= ,并把 effect 翻译回当前 X11 操作。
其中 fullscreen / maximize / minimize 这类平台请求,应先归一化为
=WM_EVENT_WINDOW_STATE_REQUEST= ,再由路由层翻译成 =SET_*= 命令。
6. =status/render/bar= 暂时不要先动。它们属于核心外服务,应在最小核心链路稳定后再迁移。

View File

@@ -21,8 +21,8 @@
都是 bootstrap 后固定集合;运行时不允许增删。
10. 文档里出现的可选行为,应优先固化到 =wm_policy_config_t= ,而不是继续留在 prose 里。
11. =frame_rect==float_rect= 都表示包含边框后的外框矩形。
12. =border_width= 不是 =wm_window_t= 真状态runtime 在提交
=WM_EFFECT_CONFIGURE_WINDOW= 时按全局配置解析它。
12. =border_width= / =border_color=不是 =wm_window_t= 真状态runtime 在提交
=WM_EFFECT_CONFIGURE_WINDOW= 时按全局配置和焦点关系解析它
* 管理类命令
@@ -82,6 +82,11 @@
1.=policy.focus_raises == true= ,则在实现中额外执行 =raise_window= 逻辑。
副作用:
1. =WM_DIRTY_DECORATION=
2. =WM_DIRTY_RENDER=
** =WM_COMMAND_FOCUS_DIRECTION=
状态变化:
@@ -90,6 +95,23 @@
2. 候选窗口应排除 =geometry_mode == WM_GEOMETRY_MINIMIZED= 的窗口。
3. =sticky= 窗口是否参与方向焦点切换,由 =policy.sticky_windows_participate_in_direction_focus= 决定。
副作用:
1. =WM_DIRTY_DECORATION=
2. =WM_DIRTY_RENDER=
** =WM_COMMAND_SET_WINDOW_URGENT=
状态变化:
1. 将目标窗口的 =urgent= 设为命令给定值。
2. 不修改 =focused_window_id=
3. 不修改 =stack_order[]=
副作用:
1. =WM_DIRTY_RENDER=
** =WM_COMMAND_RAISE_WINDOW=
状态变化:
@@ -259,57 +281,60 @@
1. =WM_DIRTY_LAYOUT=
2. =WM_DIRTY_RENDER=
** =WM_COMMAND_TOGGLE_MAXIMIZE=
** =WM_COMMAND_SET_MAXIMIZED=
状态变化:
1. 若当前 =geometry_mode == WM_GEOMETRY_MAXIMIZED=
1. =enabled == true=当前 =geometry_mode != WM_GEOMETRY_MAXIMIZED=
=geometry_mode= 设为 =WM_GEOMETRY_MAXIMIZED=
这会覆盖此前的 =FULLSCREEN= / =MINIMIZED= 状态。
2. 若 =enabled == false= 且当前 =geometry_mode == WM_GEOMETRY_MAXIMIZED=
设回 =WM_GEOMETRY_NORMAL= 。
2. 若当前 =geometry_mode == WM_GEOMETRY_FULLSCREEN=
直接切换为 =WM_GEOMETRY_MAXIMIZED=
3. 若当前 =geometry_mode == WM_GEOMETRY_MINIMIZED=
直接切换为 =WM_GEOMETRY_MAXIMIZED=
4. 其余情况:
设为 =WM_GEOMETRY_MAXIMIZED=
5. 不改变 =floating=
6. 若窗口原本是 floating退出 maximize 后回到 =float_rect=
7. 若窗口原本不是 floating退出 maximize 后重新参与 layout。
8. maximize 不改变全局边框策略;有效边框宽度仍由 runtime 统一解析。
3. 其余情况不产生状态变化。
4. 不改变 =floating=
5. 若窗口原本是 floating退出 maximize 后回到 =float_rect= 。
6. 若窗口原本不是 floating退出 maximize 后重新参与 layout
7. maximize 不改变全局边框策略;有效边框宽度仍由 runtime 统一解析。
副作用:
1. =WM_DIRTY_LAYOUT=
2. =WM_DIRTY_RENDER=
** =WM_COMMAND_TOGGLE_FULLSCREEN=
** =WM_COMMAND_SET_FULLSCREEN=
状态变化:
1. 若当前 =geometry_mode == WM_GEOMETRY_FULLSCREEN=
1. 若 =enabled == true= 且当前 =geometry_mode != WM_GEOMETRY_FULLSCREEN=
将 =geometry_mode= 设为 =WM_GEOMETRY_FULLSCREEN= 。
这会覆盖此前的 =MAXIMIZED= / =MINIMIZED= 状态。
2.=enabled == false= 且当前 =geometry_mode == WM_GEOMETRY_FULLSCREEN=
设回 =WM_GEOMETRY_NORMAL=
2. 否则:
设为 =WM_GEOMETRY_FULLSCREEN=
3. 不改变 =floating=
4. fullscreen 只决定几何占用 =output.geometry= ,不自动改变 =stack_order[]=
5. fullscreen 的有效边框宽度由 runtime 解析为 0 。
3. 其余情况不产生状态变化。
4. 不改变 =floating=
5. fullscreen 只决定几何占用 =output.geometry= ,不自动改变 =stack_order[]=
6. fullscreen 的有效边框宽度由 runtime 解析为 0
副作用:
1. =WM_DIRTY_LAYOUT=
2. =WM_DIRTY_RENDER=
** =WM_COMMAND_TOGGLE_MINIMIZE=
** =WM_COMMAND_SET_MINIMIZED=
状态变化:
1. 若当前 =geometry_mode == WM_GEOMETRY_MINIMIZED=
1. =enabled == true=当前 =geometry_mode != WM_GEOMETRY_MINIMIZED=
=geometry_mode= 设为 =WM_GEOMETRY_MINIMIZED=
在 X11 后端上,这同时覆盖 ICCCM =IconicState= / =WM_CHANGE_STATE(Iconic)=
语义core 不单独建模 =iconic=
2.=enabled == false= 且当前 =geometry_mode == WM_GEOMETRY_MINIMIZED=
设回 =WM_GEOMETRY_NORMAL=
2. 否则:
设为 =WM_GEOMETRY_MINIMIZED=
3. 不改变 =floating=
4. 不改变 =sticky=
5. minimized 后窗口不再参与显示和焦点候选
6. 若目标窗口是当前焦点,是否清空或重选焦点由 =policy.minimize_clears_focus= 决定。
3. 其余情况不产生状态变化。
4. 不改变 =floating=
5. 不改变 =sticky=
6. minimized 后窗口不再参与显示和焦点候选
7. 若本次将目标窗口切入 minimized且它正是当前焦点是否清空或重选焦点由 =policy.minimize_clears_focus= 决定
副作用:
@@ -363,15 +388,19 @@
* 边框约定
1. 当前最小核心只支持全局统一边框,不支持每窗口边框宽度
1. 当前最小核心只支持全局统一边框宽度和全局边框颜色集,不支持每窗口独立配置
2. 全局边框宽度由 =wm_runtime_bootstrap_t.border_width= 提供。
3. =layout==floating= 都产出包含边框后的外框矩形
4. =WM_EFFECT_CONFIGURE_WINDOW.rect= 也是外框矩形。
5. runtime 在把最终矩形转成 =configure= effect 时解析有效边框宽度:
3. 全局边框颜色由 =wm_runtime_bootstrap_t.border_palette= 提供
4. =layout==floating= 都产出包含边框后的外框矩形。
5. =WM_EFFECT_CONFIGURE_WINDOW.rect= 也是外框矩形。
6. runtime 在把最终矩形转成 =configure= effect 时解析有效边框宽度:
=WM_GEOMETRY_FULLSCREEN= -> 0
其余模式 -> 全局边框宽度
7. runtime 在把最终矩形转成 =configure= effect 时解析有效边框颜色:
focused window -> =border_palette.focused_rgba=
其余窗口 -> =border_palette.normal_rgba=
* redraw 和 quit
* redraw 和 runtime 控制命令
** =WM_COMMAND_REDRAW=
@@ -389,6 +418,43 @@
1. 不再接收新事件,交由 =runtime= 退出主循环。
副作用:
1. 不产生 =wm_plan_t= 平台 effect。
** =WM_COMMAND_BEGIN_MOVE_FLOATING_INTERACTION=
状态变化:
1. 不修改 =wm_state_t=
2.=runtime= 在命令应用外层拦截,并把 =interaction.mode= 设为
=WM_INTERACTION_MOVE_FLOATING=
3. 只有当当前事件是 =WM_EVENT_POINTER_BUTTON_PRESS= 、目标窗口存在,且窗口
满足 =floating == true= 时,交互才真正开始。
副作用:
1. 不产生 =wm_plan_t= 平台 effect。
** =WM_COMMAND_BEGIN_RESIZE_FLOATING_INTERACTION=
状态变化:
1. 不修改 =wm_state_t=
2.=runtime= 在命令应用外层拦截,并把 =interaction.mode= 设为
=WM_INTERACTION_RESIZE_FLOATING=
3. 只有当当前事件是 =WM_EVENT_POINTER_BUTTON_PRESS= 、目标窗口存在,且窗口
满足 =floating == true= 时,交互才真正开始。
副作用:
1. 不产生 =wm_plan_t= 平台 effect。
补充约定:
1. =WM_COMMAND_QUIT= / =WM_COMMAND_BEGIN_*_INTERACTION= 属于 runtime control commands。
2. 它们应在 =wm_policy_apply_command()= 外层被 =runtime= 拦截,而不是走状态写入分支。
* 可见性推导建议
建议实现顺序如下:

View File

@@ -24,15 +24,21 @@ void wm_runtime_process_event(wm_runtime_t *runtime, const wm_event_t *event) {
// 3. 常规事件路由
if (!consumed) {
wm_policy_route_event(&runtime->state, event, &runtime->command_buffer);
wm_policy_route_event(
&runtime->state,
&runtime->keybindings,
&runtime->pointer_bindings,
event,
&runtime->command_buffer
);
}
// 4. 应用命令
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;
if (runtime_handle_control_command(runtime, event, command,
&quit_requested)) {
continue;
}
@@ -52,6 +58,16 @@ void wm_runtime_process_event(wm_runtime_t *runtime, const wm_event_t *event) {
}
#+END_SRC
这里的 runtime control command 至少包括:
1. =WM_COMMAND_QUIT=
2. =WM_COMMAND_BEGIN_MOVE_FLOATING_INTERACTION=
3. =WM_COMMAND_BEGIN_RESIZE_FLOATING_INTERACTION=
=QUIT= 只修改 runtime 主循环状态;
=BEGIN_*_INTERACTION= 只在当前事件是 =WM_EVENT_POINTER_BUTTON_PRESS= 且目标窗口
有效时更新 =runtime->interaction= ,不进入 =wm_policy_apply_command()=
* 辅助事件处理规则
辅助事件直接更新 runtime 或广播给服务层,不经过命令系统:
@@ -59,7 +75,7 @@ void wm_runtime_process_event(wm_runtime_t *runtime, const wm_event_t *event) {
#+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;
event->type == WM_EVENT_WINDOW_HINTS_CHANGED;
}
static void runtime_handle_auxiliary_event(wm_runtime_t *runtime,
@@ -69,8 +85,8 @@ static void runtime_handle_auxiliary_event(wm_runtime_t *runtime,
runtime_handle_metadata_event(runtime, event);
return;
case WM_EVENT_STATUS_TICK:
runtime_handle_status_tick(runtime, event);
case WM_EVENT_WINDOW_HINTS_CHANGED:
runtime_handle_hints_event(runtime, event);
return;
default:
@@ -120,25 +136,46 @@ void runtime_handle_metadata_event(wm_runtime_t *runtime, const wm_event_t *even
}
#+END_SRC
**=WM_EVENT_STATUS_TICK=**
**=WM_EVENT_WINDOW_HINTS_CHANGED=**
状态服务更新事件,也不经过命令系统:
客户端属性 / hint 更新事件,也不经过命令系统:
#+BEGIN_SRC c
void runtime_handle_status_tick(wm_runtime_t *runtime, const wm_event_t *event) {
(void)event;
void runtime_handle_hints_event(wm_runtime_t *runtime, const wm_event_t *event) {
const wm_window_hints_changed_event_t *e = &event->data.window_hints_changed;
const wm_window_t *win = wm_state_window_get(&runtime->state, e->window);
// 通知状态服务更新
wm_service_event_t service_event = {
.type = WM_SERVICE_EVENT_STATUS_TICK,
};
wm_service_registry_emit(&runtime->services, &service_event, &runtime->state);
if (!win) {
return;
}
if (e->changed_fields & WM_WINDOW_HINT_CHANGED_URGENT) {
wm_state_window_set_urgent(&runtime->state, e->window, e->urgent);
}
if (e->changed_fields & WM_WINDOW_HINT_CHANGED_FIXED_SIZE) {
wm_state_window_set_fixed_size(&runtime->state, e->window, e->fixed_size);
runtime->plan.dirty_flags |= WM_DIRTY_LAYOUT;
}
if (e->changed_fields & WM_WINDOW_HINT_CHANGED_SKIP_TASKBAR) {
wm_state_window_set_skip_taskbar(&runtime->state, e->window,
e->skip_taskbar);
}
runtime->plan.dirty_flags |= WM_DIRTY_RENDER;
}
#+END_SRC
* 快捷键绑定系统
这里的边界是:
**配置格式**
1. `window hints changed` 不是 control request不生成 command。
2. `fixed_size` 属于 capability hint不等同于 `floating`
但当前最小实现允许 state setter 在 `fixed_size == true` 时派生出
`floating == true` 这种约束。
3. `skip_taskbar` 主要影响核心外 taskbar/pager 服务的可见性过滤。
* 输入绑定系统
**键盘绑定配置格式**
#+BEGIN_SRC c
// keybindings.conf
@@ -180,46 +217,141 @@ keybindings = [
]
#+END_SRC
**快捷键匹配优先级**
**鼠标按键绑定配置格式**
#+BEGIN_SRC c
// pointer_bindings.conf
pointer_bindings = [
{
// 点击窗口聚焦。window_id == INVALID 表示“使用当前点击的窗口”
button: "BUTTON1",
target: "window",
command: {
type: WM_COMMAND_FOCUS_WINDOW,
window_id: WM_WINDOW_ID_INVALID
}
},
{
// SUPER + 左键拖动 floating 窗口
button: "SUPER+BUTTON1",
target: "window",
command: {
type: WM_COMMAND_BEGIN_MOVE_FLOATING_INTERACTION,
window_id: WM_WINDOW_ID_INVALID
}
},
{
// SUPER + 右键调整 floating 窗口大小
button: "SUPER+BUTTON3",
target: "window",
command: {
type: WM_COMMAND_BEGIN_RESIZE_FLOATING_INTERACTION,
window_id: WM_WINDOW_ID_INVALID
}
}
]
#+END_SRC
**输入绑定匹配优先级**
1. 完全匹配keysym + 所有 modifiers
2. 通配符匹配keysym + 部分修饰符)
3. 默认绑定(如 SUPER+1 切换到预设工作区)
配置加载阶段应先把 =SUPER+m= 这类字符串解析成规范化按键组合。
这里的 =keysym= 指规范化后的逻辑主键编码,不是平台原始扫描码:
事件里的 =keycode= 只是保留 backend 的物理键位信息;
常规绑定仍以 =keysym + modifiers= 为准。
=SUPER+1= 这类行为也必须显式写成一条绑定,而不是由 core 内建生成。
鼠标按键绑定只区分 =root= / =window= 两类命中目标,不覆盖 bar/status 点击区域。
#+BEGIN_SRC c
typedef struct keybinding_t {
typedef enum wm_binding_match_mode_t {
WM_BINDING_MATCH_EXACT,
WM_BINDING_MATCH_ALLOW_EXTRA_MODIFIERS,
} wm_binding_match_mode_t;
typedef struct wm_key_binding_t {
wm_keysym_t keysym;
wm_modifier_mask_t modifiers;
wm_command_type_t command_type;
} keybinding_t;
wm_binding_match_mode_t match_mode;
wm_command_t command;
} wm_key_binding_t;
typedef struct wm_key_binding_table_t {
const wm_key_binding_t *items;
size_t count;
} wm_key_binding_table_t;
typedef enum wm_pointer_binding_target_t {
WM_POINTER_BINDING_TARGET_ANY,
WM_POINTER_BINDING_TARGET_ROOT,
WM_POINTER_BINDING_TARGET_WINDOW,
} wm_pointer_binding_target_t;
typedef struct wm_pointer_binding_t {
wm_button_t button;
wm_modifier_mask_t modifiers;
wm_binding_match_mode_t match_mode;
wm_pointer_binding_target_t target;
wm_command_t command;
} wm_pointer_binding_t;
typedef struct wm_pointer_binding_table_t {
const wm_pointer_binding_t *items;
size_t count;
} wm_pointer_binding_table_t;
#+END_SRC
绑定表属于 bootstrap 固定输入:
#+BEGIN_SRC c
typedef struct wm_runtime_bootstrap_t {
...
wm_key_binding_table_t keybindings;
wm_pointer_binding_table_t pointer_bindings;
...
} wm_runtime_bootstrap_t;
#+END_SRC
若 backend 需要平台侧被动抓键 / 抓按钮,则通过
=wm_backend_api_t.set_keybindings()= / =set_pointer_bindings()= 接收同一组表。
**=wm_policy_route_event()= 实现**
#+BEGIN_SRC c
bool wm_policy_route_event(const wm_state_t *state,
const wm_key_binding_table_t *keybindings,
const wm_pointer_binding_table_t *pointer_bindings,
const wm_event_t *event,
wm_command_buffer_t *out) {
switch (event->type) {
case WM_EVENT_KEY_PRESS:
return route_key_press(state, &event->data.key_press, out);
return route_key_press(state, keybindings,
&event->data.key_press, out);
case WM_EVENT_POINTER_BUTTON_PRESS:
return route_pointer_press(state, &event->data.pointer_button_press, out);
return route_pointer_press(state, pointer_bindings,
&event->data.pointer_button_press, out);
case WM_EVENT_WINDOW_MAP_REQUEST:
return route_map_request(state, &event->data.window_map_request, out);
case WM_EVENT_WINDOW_UNMAP:
return route_unmap_notify(state, &event->data.window_unmap, out);
case WM_EVENT_WINDOW_REMOVE:
return route_window_remove(state, &event->data.window_remove, out);
case WM_EVENT_WINDOW_DESTROY:
return route_destroy_notify(state, &event->data.window_destroy, out);
case WM_EVENT_WINDOW_ACTIVATE_REQUEST:
return route_activate_request(
state,
&event->data.window_activate_request,
out
);
case WM_EVENT_WINDOW_STATE_REQUEST:
return route_window_state_request(
state,
&event->data.window_state_request,
out
);
case WM_EVENT_CONFIGURE_REQUEST:
return route_configure_request(state, &event->data.configure_request, out);
@@ -231,10 +363,12 @@ bool wm_policy_route_event(const wm_state_t *state,
// 键盘事件路由
static bool route_key_press(const wm_state_t *state,
const wm_key_binding_table_t *keybindings,
const wm_key_press_event_t *e,
wm_command_buffer_t *out) {
// 1. 查找匹配的快捷键绑定
const keybinding_t *binding = keybinding_find(
// 1. 查找匹配的键绑定
const wm_key_binding_t *binding = wm_key_binding_find(
keybindings,
e->keysym,
e->modifiers
);
@@ -243,13 +377,140 @@ static bool route_key_press(const wm_state_t *state,
return false; // 没有匹配的绑定
}
// 2. 将绑定转换为命令
wm_command_t cmd = {
.type = binding->command_type,
// ... 填充命令参数
};
// 2. 直接复制绑定里的命令模板
return wm_command_buffer_push(out, binding->command);
}
static bool resolve_pointer_window_target(wm_command_t *cmd,
wm_window_id_t event_window) {
switch (cmd->type) {
case WM_COMMAND_UNMANAGE_WINDOW:
if (cmd->as.unmanage_window.window_id == WM_WINDOW_ID_INVALID) {
cmd->as.unmanage_window.window_id = event_window;
}
return cmd->as.unmanage_window.window_id != WM_WINDOW_ID_INVALID;
case WM_COMMAND_FOCUS_WINDOW:
if (cmd->as.focus_window.window_id == WM_WINDOW_ID_INVALID) {
cmd->as.focus_window.window_id = event_window;
}
return cmd->as.focus_window.window_id != WM_WINDOW_ID_INVALID;
case WM_COMMAND_RAISE_WINDOW:
if (cmd->as.raise_window.window_id == WM_WINDOW_ID_INVALID) {
cmd->as.raise_window.window_id = event_window;
}
return cmd->as.raise_window.window_id != WM_WINDOW_ID_INVALID;
case WM_COMMAND_LOWER_WINDOW:
if (cmd->as.lower_window.window_id == WM_WINDOW_ID_INVALID) {
cmd->as.lower_window.window_id = event_window;
}
return cmd->as.lower_window.window_id != WM_WINDOW_ID_INVALID;
case WM_COMMAND_SEND_WINDOW_TO_WORKSPACE:
if (cmd->as.send_window_to_workspace.window_id == WM_WINDOW_ID_INVALID) {
cmd->as.send_window_to_workspace.window_id = event_window;
}
return cmd->as.send_window_to_workspace.window_id !=
WM_WINDOW_ID_INVALID;
case WM_COMMAND_SEND_WINDOW_TO_OUTPUT:
if (cmd->as.send_window_to_output.window_id == WM_WINDOW_ID_INVALID) {
cmd->as.send_window_to_output.window_id = event_window;
}
return cmd->as.send_window_to_output.window_id !=
WM_WINDOW_ID_INVALID;
case WM_COMMAND_TOGGLE_FLOATING:
if (cmd->as.toggle_floating.window_id == WM_WINDOW_ID_INVALID) {
cmd->as.toggle_floating.window_id = event_window;
}
return cmd->as.toggle_floating.window_id != WM_WINDOW_ID_INVALID;
case WM_COMMAND_TOGGLE_STICKY:
if (cmd->as.toggle_sticky.window_id == WM_WINDOW_ID_INVALID) {
cmd->as.toggle_sticky.window_id = event_window;
}
return cmd->as.toggle_sticky.window_id != WM_WINDOW_ID_INVALID;
case WM_COMMAND_SET_MAXIMIZED:
if (cmd->as.set_maximized.window_id == WM_WINDOW_ID_INVALID) {
cmd->as.set_maximized.window_id = event_window;
}
return cmd->as.set_maximized.window_id != WM_WINDOW_ID_INVALID;
case WM_COMMAND_SET_FULLSCREEN:
if (cmd->as.set_fullscreen.window_id == WM_WINDOW_ID_INVALID) {
cmd->as.set_fullscreen.window_id = event_window;
}
return cmd->as.set_fullscreen.window_id != WM_WINDOW_ID_INVALID;
case WM_COMMAND_SET_MINIMIZED:
if (cmd->as.set_minimized.window_id == WM_WINDOW_ID_INVALID) {
cmd->as.set_minimized.window_id = event_window;
}
return cmd->as.set_minimized.window_id != WM_WINDOW_ID_INVALID;
case WM_COMMAND_MOVE_FLOATING_WINDOW:
if (cmd->as.move_floating_window.window_id == WM_WINDOW_ID_INVALID) {
cmd->as.move_floating_window.window_id = event_window;
}
return cmd->as.move_floating_window.window_id !=
WM_WINDOW_ID_INVALID;
case WM_COMMAND_RESIZE_FLOATING_WINDOW:
if (cmd->as.resize_floating_window.window_id == WM_WINDOW_ID_INVALID) {
cmd->as.resize_floating_window.window_id = event_window;
}
return cmd->as.resize_floating_window.window_id !=
WM_WINDOW_ID_INVALID;
case WM_COMMAND_BEGIN_MOVE_FLOATING_INTERACTION:
if (cmd->as.begin_move_floating_interaction.window_id ==
WM_WINDOW_ID_INVALID) {
cmd->as.begin_move_floating_interaction.window_id = event_window;
}
return cmd->as.begin_move_floating_interaction.window_id !=
WM_WINDOW_ID_INVALID;
case WM_COMMAND_BEGIN_RESIZE_FLOATING_INTERACTION:
if (cmd->as.begin_resize_floating_interaction.window_id ==
WM_WINDOW_ID_INVALID) {
cmd->as.begin_resize_floating_interaction.window_id = event_window;
}
return cmd->as.begin_resize_floating_interaction.window_id !=
WM_WINDOW_ID_INVALID;
default:
return true;
}
}
static bool route_pointer_press(const wm_state_t *state,
const wm_pointer_binding_table_t *pointer_bindings,
const wm_pointer_button_event_t *e,
wm_command_buffer_t *out) {
const wm_pointer_binding_t *binding = wm_pointer_binding_find(
pointer_bindings,
e->button,
e->modifiers,
e->window != WM_WINDOW_ID_INVALID
);
wm_command_t cmd;
(void)state;
if (!binding) {
return false;
}
cmd = binding->command;
if (!resolve_pointer_window_target(&cmd, e->window)) {
return false;
}
// 3. 添加到命令缓冲
return wm_command_buffer_push(out, cmd);
}
@@ -258,7 +519,7 @@ static bool route_map_request(const wm_state_t *state,
const wm_window_map_request_event_t *e,
wm_command_buffer_t *out) {
// 1. 检查窗口是否已存在
if (wm_state_window_get(state, e->window)) {
if (wm_state_window_get(state, e->info.id)) {
return false; // 窗口已管理,忽略
}
@@ -266,15 +527,175 @@ static bool route_map_request(const wm_state_t *state,
wm_command_t cmd = {
.type = WM_COMMAND_MANAGE_WINDOW,
.as.manage_window = {
.window_id = e->window,
// 应用规则确定初始状态
.window_id = e->info.id,
// route 层结合 e->info / transient_for / is_dialog
// 推导 initial_state、workspace_id 和初始 float_rect
}
};
return wm_command_buffer_push(out, cmd);
}
static bool route_window_remove(const wm_state_t *state,
const wm_window_remove_event_t *e,
wm_command_buffer_t *out) {
if (!wm_state_window_get(state, e->window)) {
return false;
}
// 当前最小核心不区分 withdrawn / destroy统一转成 unmanage。
(void)e->reason;
return wm_command_buffer_push(out, (wm_command_t){
.type = WM_COMMAND_UNMANAGE_WINDOW,
.as.unmanage_window = {
.window_id = e->window,
},
});
}
static bool route_activate_request(const wm_state_t *state,
const wm_window_activate_request_event_t *e,
wm_command_buffer_t *out) {
const wm_window_t *win = wm_state_window_get(state, e->window);
const wm_workspace_t *ws;
const wm_output_t *output;
wm_command_t cmd;
if (!win) {
return false;
}
if (e->source == WM_WINDOW_ACTIVATION_SOURCE_PAGER) {
ws = &state->workspaces[win->workspace_id];
output = &state->outputs[ws->output_id];
if (output->current_workspace_id != ws->id) {
cmd = (wm_command_t){
.type = WM_COMMAND_SWITCH_WORKSPACE,
.as.switch_workspace = {
.output_id = output->id,
.workspace_id = ws->id,
},
};
if (!wm_command_buffer_push(out, cmd)) {
return false;
}
}
cmd = (wm_command_t){
.type = WM_COMMAND_FOCUS_WINDOW,
.as.focus_window = {
.window_id = win->id,
},
};
return wm_command_buffer_push(out, cmd);
}
cmd = (wm_command_t){
.type = WM_COMMAND_SET_WINDOW_URGENT,
.as.set_window_urgent = {
.window_id = win->id,
.urgent = true,
},
};
return wm_command_buffer_push(out, cmd);
}
static bool route_window_state_request(const wm_state_t *state,
const wm_window_state_request_event_t *e,
wm_command_buffer_t *out) {
const wm_window_t *win = wm_state_window_get(state, e->window);
bool current_enabled;
bool enabled;
wm_command_t cmd;
if (!win) {
return false;
}
switch (e->kind) {
case WM_WINDOW_STATE_REQUEST_FULLSCREEN:
current_enabled = win->geometry_mode == WM_GEOMETRY_FULLSCREEN;
break;
case WM_WINDOW_STATE_REQUEST_MAXIMIZED:
current_enabled = win->geometry_mode == WM_GEOMETRY_MAXIMIZED;
break;
case WM_WINDOW_STATE_REQUEST_MINIMIZED:
current_enabled = win->geometry_mode == WM_GEOMETRY_MINIMIZED;
break;
default:
return false;
}
switch (e->action) {
case WM_WINDOW_STATE_REQUEST_ACTION_ADD:
enabled = true;
break;
case WM_WINDOW_STATE_REQUEST_ACTION_REMOVE:
enabled = false;
break;
case WM_WINDOW_STATE_REQUEST_ACTION_TOGGLE:
enabled = !current_enabled;
break;
default:
return false;
}
switch (e->kind) {
case WM_WINDOW_STATE_REQUEST_FULLSCREEN:
cmd = (wm_command_t){
.type = WM_COMMAND_SET_FULLSCREEN,
.as.set_fullscreen = {
.window_id = win->id,
.enabled = enabled,
},
};
break;
case WM_WINDOW_STATE_REQUEST_MAXIMIZED:
cmd = (wm_command_t){
.type = WM_COMMAND_SET_MAXIMIZED,
.as.set_maximized = {
.window_id = win->id,
.enabled = enabled,
},
};
break;
case WM_WINDOW_STATE_REQUEST_MINIMIZED:
cmd = (wm_command_t){
.type = WM_COMMAND_SET_MINIMIZED,
.as.set_minimized = {
.window_id = win->id,
.enabled = enabled,
},
};
break;
default:
return false;
}
return wm_command_buffer_push(out, cmd);
}
#+END_SRC
这里的最小策略是:
1. `pager` 来源请求可以切到目标窗口所属 workspace并把焦点交给该窗口。
2. `application` / `legacy` 来源不直接抢焦点,而是转成 =SET_WINDOW_URGENT(true)=
3. `WM_EVENT_WINDOW_STATE_REQUEST` 保留 add/remove/toggle 语义,不在 backend 侧提前求最终布尔值。
4. route 层结合当前 =geometry_mode= ,把它翻译成 =SET_FULLSCREEN= / =SET_MAXIMIZED= / =SET_MINIMIZED=
5. 这层抽象可以同时承接 X11 的 =_NET_WM_STATE= 与 Wayland 的窗口状态请求。
6. 若以后需要更细的 focus-stealing prevention可以再下沉到 =wm_policy_config_t=
* 交互态事件路由
交互态会覆盖正常的事件路由:
@@ -334,21 +755,31 @@ static bool route_move_floating_event(wm_runtime_t *runtime,
| 事件类型 | 可能生成的命令 | 条件 |
|---------|---------------|------|
| KEY_PRESS | 任意命令(通过快捷键绑定) | 绑定存在 |
| POINTER_BUTTON_PRESS | FOCUS_WINDOW, RAISE_WINDOW | 点击窗口 |
| KEY_PRESS | 任意命令(通过键绑定) | 绑定存在 |
| POINTER_BUTTON_PRESS | 任意窗口命令 / BEGIN_*_INTERACTION通过鼠标绑定 | 绑定存在 |
| WINDOW_MAP_REQUEST | MANAGE_WINDOW | 窗口未管理 |
| WINDOW_UNMAP | (可能不生成命令) | - |
| WINDOW_DESTROY | UNMANAGE_WINDOW | 窗口已管理 |
| WINDOW_REMOVE | UNMANAGE_WINDOW | 窗口已管理 |
| WINDOW_ACTIVATE_REQUEST | SWITCH_WORKSPACE + FOCUS_WINDOW / SET_WINDOW_URGENT | source == pager / source != pager |
| WINDOW_STATE_REQUEST | SET_FULLSCREEN / SET_MAXIMIZED / SET_MINIMIZED | 窗口已管理 |
| CONFIGURE_REQUEST | (可能不生成命令) | floating 窗口 |
| POINTER_MOTION | MOVE/RESIZE 命令 | 交互态下 |
补充约定:
1. `WM_EVENT_CONFIGURE_REQUEST` 只携带稀疏几何字段,不直接镜像 X11 全量协议载荷。
2. 最小核心当前只会把这些几何请求映射到 floating 窗口的 move/resize 语义。
3. `border_width` / `sibling` / `stack_mode` 这类协议细节留在 backend / adapter 层。
4. fullscreen / maximize / minimize 这类窗口模式切换不属于 configure request。
5. `WM_EVENT_WINDOW_STATE_REQUEST` 表达后端看到的原始 add/remove/toggle 请求;
core command 仍保持 =SET_*= 这种最终状态语义。
* 事件处理的边界条件
**1. 辅助事件边界**
辅助事件满足以下条件时直接处理,不经过命令系统:
- 事件类型是 `WM_EVENT_WINDOW_METADATA_CHANGED`
- 事件类型是 `WM_EVENT_STATUS_TICK`
- 事件类型是 `WM_EVENT_WINDOW_HINTS_CHANGED`
**2. 交互态边界**
@@ -366,16 +797,18 @@ static bool route_move_floating_event(wm_runtime_t *runtime,
窗口管理的边界条件:
- `WM_EVENT_WINDOW_MAP_REQUEST`:窗口已存在则忽略
- `WM_EVENT_WINDOW_DESTROY`:窗口不存在则忽略
- `WM_EVENT_CONFIGURE_REQUEST`:只有 floating 窗口才响应
- `WM_EVENT_WINDOW_REMOVE`:窗口不存在则忽略
- `WM_EVENT_WINDOW_ACTIVATE_REQUEST`:窗口不存在则忽略
- `WM_EVENT_WINDOW_STATE_REQUEST`:窗口不存在则忽略
- `WM_EVENT_CONFIGURE_REQUEST`:只有 floating 窗口的几何请求才进入 core 命令链
* 事件处理顺序保证
为了保证确定性行为,事件处理必须按以下顺序进行:
1. **辅助事件优先**:元数据和状态服务更新不走命令系统
2. **交互态优先**:拖拽操作优先于快捷键
3. **快捷键匹配优先级**:完全匹配 → 通配符 → 默认
2. **交互态优先**:拖拽操作优先于常规输入绑定
3. **输入绑定匹配优先级**:完全匹配 → 通配符
4. **命令执行顺序**:命令缓冲中的命令按顺序执行
* 主执行流程实现
@@ -408,6 +841,13 @@ void wm_runtime_run(wm_runtime_t *runtime) {
wm_runtime_relayout(runtime);
}
// 3.1.5 若布局或装饰发生变化,重建 configure effects。
// 这里需要同时补全 rect / border_width / border_rgba。
if (runtime->plan.dirty_flags &
(WM_DIRTY_LAYOUT | WM_DIRTY_DECORATION)) {
wm_runtime_rebuild_configure_effects(runtime);
}
// 3.2 依次提交 effect。RENDER_OUTPUT 留给服务层处理。
for (size_t i = 0; i < runtime->plan.effect_count; i++) {
const wm_effect_t *effect = &runtime->plan.effects[i];
@@ -447,7 +887,7 @@ void wm_runtime_run(wm_runtime_t *runtime) {
这个事件路由设计完全兼容现有 ZDWM 功能:
1. **快捷键系统**:通过 keybindings.conf 配置
1. **输入绑定系统**:通过 keybindings.conf / pointer_bindings.conf 配置
2. **规则系统**:在 MANAGE_WINDOW 命令中应用
3. **布局切换**:通过 SET_LAYOUT / CYCLE_LAYOUT 命令
4. **拖拽交互**:通过交互态管理
@@ -462,6 +902,6 @@ void wm_runtime_run(wm_runtime_t *runtime) {
3. ✅ 路由接口wm_policy.h
4. ✅ 命令应用WM_POLICY_APPLY_COMMAND_SKELETON.org
5. ✅ **路由规则**(本文档补充)
6. ✅ **快捷键系统**(本文档补充)
6. ✅ **输入绑定系统**(本文档补充)
7. ✅ **交互态处理**(本文档补充)
8. ✅ **主执行流程**(本文档补充)

View File

@@ -16,10 +16,11 @@
补充约定:
1. =border_width= 不是 =wm_window_t= 真状态。
1. =border_width= / =border_color= 不是 =wm_window_t= 真状态。
2. =apply_command()= 只负责更新 =frame_rect= / =float_rect= 和脏标记。
3. runtime 在布局求值和 effect 提交阶段,根据全局边框配置补全
=WM_EFFECT_CONFIGURE_WINDOW.border_width=
=WM_EFFECT_CONFIGURE_WINDOW.border_width= /
=WM_EFFECT_CONFIGURE_WINDOW.border_rgba=
它不负责:
@@ -43,8 +44,11 @@ bool wm_policy_apply_command(wm_state_t *state,
if (!state || !policy || !command || !plan) return false;
generation_relevant = command->type != WM_COMMAND_REDRAW &&
command->type != WM_COMMAND_QUIT;
generation_relevant =
command->type != WM_COMMAND_REDRAW &&
command->type != WM_COMMAND_QUIT &&
command->type != WM_COMMAND_BEGIN_MOVE_FLOATING_INTERACTION &&
command->type != WM_COMMAND_BEGIN_RESIZE_FLOATING_INTERACTION;
switch (command->type) {
case WM_COMMAND_NONE:
@@ -66,6 +70,10 @@ bool wm_policy_apply_command(wm_state_t *state,
changed = apply_focus_direction(state, policy, command, plan);
break;
case WM_COMMAND_SET_WINDOW_URGENT:
changed = apply_set_window_urgent(state, command, plan);
break;
case WM_COMMAND_RAISE_WINDOW:
changed = apply_raise_window(state, command, plan);
break;
@@ -94,16 +102,16 @@ bool wm_policy_apply_command(wm_state_t *state,
changed = apply_toggle_sticky(state, command, plan);
break;
case WM_COMMAND_TOGGLE_MAXIMIZE:
changed = apply_toggle_maximize(state, command, plan);
case WM_COMMAND_SET_MAXIMIZED:
changed = apply_set_maximized(state, command, plan);
break;
case WM_COMMAND_TOGGLE_FULLSCREEN:
changed = apply_toggle_fullscreen(state, command, plan);
case WM_COMMAND_SET_FULLSCREEN:
changed = apply_set_fullscreen(state, command, plan);
break;
case WM_COMMAND_TOGGLE_MINIMIZE:
changed = apply_toggle_minimize(state, policy, command, plan);
case WM_COMMAND_SET_MINIMIZED:
changed = apply_set_minimized(state, policy, command, plan);
break;
case WM_COMMAND_MOVE_FLOATING_WINDOW:
@@ -114,6 +122,11 @@ bool wm_policy_apply_command(wm_state_t *state,
changed = apply_resize_floating_window(state, command, plan);
break;
case WM_COMMAND_BEGIN_MOVE_FLOATING_INTERACTION:
case WM_COMMAND_BEGIN_RESIZE_FLOATING_INTERACTION:
case WM_COMMAND_QUIT:
return false;
case WM_COMMAND_SET_LAYOUT:
changed = apply_set_layout(state, layouts, command, plan);
break;
@@ -126,9 +139,6 @@ bool wm_policy_apply_command(wm_state_t *state,
changed = apply_redraw(state, command, plan);
break;
case WM_COMMAND_QUIT:
changed = apply_quit(state, command, plan);
break;
}
if (changed && generation_relevant) state->generation++;
@@ -264,7 +274,7 @@ static bool apply_focus_window(wm_state_t *state,
push_restack_effect(state, plan);
}
plan->dirty_flags |= WM_DIRTY_RENDER;
plan->dirty_flags |= WM_DIRTY_DECORATION | WM_DIRTY_RENDER;
return true;
}
#+END_SRC
@@ -320,7 +330,27 @@ static bool apply_focus_direction(wm_state_t *state,
if (!next || ws->focused_window_id == next) return false;
ws->focused_window_id = next;
plan->dirty_flags |= WM_DIRTY_DECORATION | WM_DIRTY_RENDER;
return true;
}
#+END_SRC
** =apply_set_window_urgent()=
#+BEGIN_SRC c
static bool apply_set_window_urgent(wm_state_t *state,
const wm_command_t *command,
wm_plan_t *plan) {
wm_window_t *win =
require_window(state, command->as.set_window_urgent.window_id);
bool urgent = command->as.set_window_urgent.urgent;
if (!win) return false;
if (win->urgent == urgent) return false;
win->urgent = urgent;
plan->dirty_flags |= WM_DIRTY_RENDER;
push_render_all_outputs(state, plan);
return true;
}
#+END_SRC
@@ -452,74 +482,91 @@ static bool apply_toggle_floating(wm_state_t *state,
}
#+END_SRC
** =apply_toggle_maximize()=
** =apply_set_maximized()=
#+BEGIN_SRC c
static bool apply_toggle_maximize(wm_state_t *state,
const wm_command_t *command,
wm_plan_t *plan) {
wm_window_t *win = require_window(state, command->as.toggle_maximize.window_id);
static bool apply_set_maximized(wm_state_t *state,
const wm_command_t *command,
wm_plan_t *plan) {
wm_window_t *win = require_window(state, command->as.set_maximized.window_id);
if (!win) return false;
if (win->geometry_mode == WM_GEOMETRY_MAXIMIZED) {
win->geometry_mode = WM_GEOMETRY_NORMAL;
} else {
win->geometry_mode = WM_GEOMETRY_MAXIMIZED;
wm_window_geometry_mode_t next_mode = win->geometry_mode;
if (command->as.set_maximized.enabled) {
next_mode = WM_GEOMETRY_MAXIMIZED;
} else if (win->geometry_mode == WM_GEOMETRY_MAXIMIZED) {
next_mode = WM_GEOMETRY_NORMAL;
}
if (next_mode == win->geometry_mode) return false;
win->geometry_mode = next_mode;
plan->dirty_flags |= WM_DIRTY_LAYOUT | WM_DIRTY_RENDER;
push_render_all_outputs(state, plan);
return true;
}
#+END_SRC
实际实现中应按 [[file:WM_COMMAND_RULES.org][WM_COMMAND_RULES.org]] 处理 =FULLSCREEN==MINIMIZED= 的切换优先级。
实际实现中应按 [[file:WM_COMMAND_RULES.org][WM_COMMAND_RULES.org]] 处理 =enabled=
的幂等性,以及与 =FULLSCREEN= / =MINIMIZED= 的互斥切换关系。
** =apply_toggle_fullscreen()=
** =apply_set_fullscreen()=
#+BEGIN_SRC c
static bool apply_toggle_fullscreen(wm_state_t *state,
const wm_command_t *command,
wm_plan_t *plan) {
static bool apply_set_fullscreen(wm_state_t *state,
const wm_command_t *command,
wm_plan_t *plan) {
wm_window_t *win =
require_window(state, command->as.toggle_fullscreen.window_id);
require_window(state, command->as.set_fullscreen.window_id);
if (!win) return false;
if (win->geometry_mode == WM_GEOMETRY_FULLSCREEN) {
win->geometry_mode = WM_GEOMETRY_NORMAL;
} else {
win->geometry_mode = WM_GEOMETRY_FULLSCREEN;
wm_window_geometry_mode_t next_mode = win->geometry_mode;
if (command->as.set_fullscreen.enabled) {
next_mode = WM_GEOMETRY_FULLSCREEN;
} else if (win->geometry_mode == WM_GEOMETRY_FULLSCREEN) {
next_mode = WM_GEOMETRY_NORMAL;
}
if (next_mode == win->geometry_mode) return false;
win->geometry_mode = next_mode;
plan->dirty_flags |= WM_DIRTY_LAYOUT | WM_DIRTY_RENDER;
push_render_all_outputs(state, plan);
return true;
}
#+END_SRC
** =apply_toggle_minimize()=
** =apply_set_minimized()=
#+BEGIN_SRC c
static bool apply_toggle_minimize(wm_state_t *state,
const wm_policy_config_t *policy,
const wm_command_t *command,
wm_plan_t *plan) {
static bool apply_set_minimized(wm_state_t *state,
const wm_policy_config_t *policy,
const wm_command_t *command,
wm_plan_t *plan) {
wm_window_t *win =
require_window(state, command->as.toggle_minimize.window_id);
require_window(state, command->as.set_minimized.window_id);
if (!win) return false;
wm_workspace_t *ws = require_workspace(state, win->workspace_id);
wm_window_geometry_mode_t next_mode = win->geometry_mode;
if (win->geometry_mode == WM_GEOMETRY_MINIMIZED) {
win->geometry_mode = WM_GEOMETRY_NORMAL;
} else {
win->geometry_mode = WM_GEOMETRY_MINIMIZED;
if (command->as.set_minimized.enabled) {
next_mode = WM_GEOMETRY_MINIMIZED;
} else if (win->geometry_mode == WM_GEOMETRY_MINIMIZED) {
next_mode = WM_GEOMETRY_NORMAL;
}
if (policy->minimize_clears_focus &&
ws &&
ws->focused_window_id == win->id) {
maybe_refocus_workspace(state, ws);
}
if (next_mode == win->geometry_mode) return false;
win->geometry_mode = next_mode;
if (command->as.set_minimized.enabled &&
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;
@@ -669,29 +716,18 @@ static bool apply_redraw(wm_state_t *state,
}
#+END_SRC
** =apply_quit()=
#+BEGIN_SRC c
static bool apply_quit(wm_state_t *state,
const wm_command_t *command,
wm_plan_t *plan) {
(void)state;
(void)command;
(void)plan;
return true;
}
#+END_SRC
通常情况下 =WM_COMMAND_QUIT= 会由 runtime 在命令应用外层直接拦截并调用
=wm_runtime_stop()= 。保留 =apply_quit()= 这个空实现,只是为了让 switch
结构保持完整。
通常情况下 =WM_COMMAND_QUIT= / =WM_COMMAND_BEGIN_*_INTERACTION= 都会由
=runtime= 在命令应用外层直接拦截,因此 =wm_policy_apply_command()= 不应真正看见
这些 runtime control commands。
* 推荐的后处理步骤
=apply_*()= 返回后,建议由上层统一做这些事,而不是在每个分支里直接跑布局:
1. 如果有 =WM_DIRTY_LAYOUT= ,则对每个 output 的当前 workspace 重新计算 layout。
2. 根据窗口的 =geometry_mode==floating= 和 layout 结果解析最终矩形。
2. 若有 =WM_DIRTY_LAYOUT==WM_DIRTY_DECORATION= ,则根据窗口的
=geometry_mode==floating= 、焦点关系和全局边框配置解析最终矩形、
有效边框宽度与边框颜色。
=sticky= 窗口由于满足 =sticky => floating= ,因此同样走 =float_rect= 路径。
3. 生成 =WM_EFFECT_CONFIGURE_WINDOW=
4. 若有 =WM_DIRTY_STACK= ,生成 =WM_EFFECT_RESTACK_WINDOWS=

View File

@@ -1,5 +1,6 @@
#pragma once
#include "wm_binding.h"
#include "wm_event.h"
#include "wm_plan.h"
@@ -18,6 +19,22 @@ typedef enum wm_backend_next_result_t {
typedef struct wm_backend_api_t {
bool (*init)(wm_backend_t *backend);
/*
* 安装 bootstrap 固定下来的键盘绑定表。
* X11 这类需要被动抓键的平台可以在这里建立平台侧 grab
* 总能收到所有键盘事件的 backend 可以把它留空。
*/
bool (*set_keybindings)(wm_backend_t *backend,
const wm_key_binding_table_t *keybindings);
/*
* 安装 bootstrap 固定下来的鼠标按键绑定表。
* X11 这类需要被动抓按钮的平台可以把它缓存下来,并在 root 或新受管窗口
* 上按 target 规则建立 grab总能收到所有 pointer button 事件的 backend
* 可以把它留空。
*/
bool (*set_pointer_bindings)(
wm_backend_t *backend,
const wm_pointer_binding_table_t *pointer_bindings);
/*
* 返回一个 backend 轮询结果。
* 只有返回 WM_BACKEND_NEXT_EVENT 时out 才包含一个完整的 runtime 输入事件。

View File

@@ -0,0 +1,86 @@
#pragma once
#include "wm_command.h"
#include "wm_types.h"
#include <stdbool.h>
#include <stddef.h>
/*
* 输入绑定只匹配 backend 已归一化后的输入事件。
* backend 负责把平台原始输入翻译为 wm_event_t并决定是否折叠 auto-repeat。
*/
typedef enum wm_binding_match_mode_t {
// modifiers 必须与事件完全一致
WM_BINDING_MATCH_EXACT,
// 事件允许带有更多修饰键,但必须至少包含 binding.modifiers
WM_BINDING_MATCH_ALLOW_EXTRA_MODIFIERS,
} wm_binding_match_mode_t;
typedef struct wm_key_binding_t {
wm_keysym_t keysym;
wm_modifier_mask_t modifiers;
wm_binding_match_mode_t match_mode;
// 命中的绑定会直接复制这条命令模板进入 command buffer
wm_command_t command;
} wm_key_binding_t;
typedef struct wm_key_binding_table_t {
const wm_key_binding_t *items;
size_t count;
} wm_key_binding_table_t;
/*
* 核心只关心 root / window 两类命中目标:
* - bar / status / widget 等点击区域属于核心外服务,不进入最小核心绑定系统
* - 当事件未命中受管窗口时wm_event_t.window == WM_WINDOW_ID_INVALID
*/
typedef enum wm_pointer_binding_target_t {
WM_POINTER_BINDING_TARGET_ANY,
WM_POINTER_BINDING_TARGET_ROOT,
WM_POINTER_BINDING_TARGET_WINDOW,
} wm_pointer_binding_target_t;
/*
* 最小核心当前只定义“按下触发”的鼠标绑定;
* button release 主要保留给 move/resize 交互结束逻辑。
*
* 若 command 是窗口目标命令,且模板中的 window_id 为
* WM_WINDOW_ID_INVALID则 route_pointer_press() 应把它替换为当前点击的
* event->window。
*/
typedef struct wm_pointer_binding_t {
wm_button_t button;
wm_modifier_mask_t modifiers;
wm_binding_match_mode_t match_mode;
wm_pointer_binding_target_t target;
wm_command_t command;
} wm_pointer_binding_t;
typedef struct wm_pointer_binding_table_t {
const wm_pointer_binding_t *items;
size_t count;
} wm_pointer_binding_table_t;
/*
* 查找与当前按键事件匹配的绑定。
* 匹配顺序固定为:
* 1. 所有 exact 绑定,按数组顺序扫描
* 2. 所有 allow-extra-modifiers 绑定,按数组顺序扫描
*
* 当前最小核心不内建“默认绑定生成规则”;像 SUPER+1 这类行为也必须显式
* 出现在 key binding table 里。
*/
const wm_key_binding_t *wm_key_binding_find(
const wm_key_binding_table_t *table, wm_keysym_t keysym,
wm_modifier_mask_t modifiers);
/*
* 查找与当前按钮按下事件匹配的鼠标绑定。
* hits_window == true 表示事件命中了某个受管窗口;
* hits_window == false 表示事件落在 root / 空白区域。
*
* 匹配顺序与键盘绑定一致:先 exact再 allow-extra-modifiers。
*/
const wm_pointer_binding_t *wm_pointer_binding_find(
const wm_pointer_binding_table_t *table, wm_button_t button,
wm_modifier_mask_t modifiers, bool hits_window);

View File

@@ -16,6 +16,7 @@ typedef enum wm_command_type_t {
WM_COMMAND_UNMANAGE_WINDOW,
WM_COMMAND_FOCUS_WINDOW,
WM_COMMAND_FOCUS_DIRECTION,
WM_COMMAND_SET_WINDOW_URGENT,
WM_COMMAND_RAISE_WINDOW,
WM_COMMAND_LOWER_WINDOW,
WM_COMMAND_SWITCH_WORKSPACE,
@@ -23,11 +24,13 @@ typedef enum wm_command_type_t {
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,
WM_COMMAND_SET_MAXIMIZED,
WM_COMMAND_SET_FULLSCREEN,
WM_COMMAND_SET_MINIMIZED,
WM_COMMAND_MOVE_FLOATING_WINDOW,
WM_COMMAND_RESIZE_FLOATING_WINDOW,
WM_COMMAND_BEGIN_MOVE_FLOATING_INTERACTION,
WM_COMMAND_BEGIN_RESIZE_FLOATING_INTERACTION,
WM_COMMAND_SET_LAYOUT,
WM_COMMAND_CYCLE_LAYOUT,
WM_COMMAND_REDRAW,
@@ -58,6 +61,11 @@ typedef struct wm_command_t {
wm_focus_direction_t direction;
} focus_direction;
struct {
wm_window_id_t window_id;
bool urgent;
} set_window_urgent;
struct {
wm_window_id_t window_id;
} raise_window;
@@ -92,15 +100,18 @@ typedef struct wm_command_t {
struct {
wm_window_id_t window_id;
} toggle_maximize;
bool enabled;
} set_maximized;
struct {
wm_window_id_t window_id;
} toggle_fullscreen;
bool enabled;
} set_fullscreen;
struct {
wm_window_id_t window_id;
} toggle_minimize;
bool enabled;
} set_minimized;
struct {
wm_window_id_t window_id;
@@ -117,6 +128,14 @@ typedef struct wm_command_t {
int16_t dh;
} resize_floating_window;
struct {
wm_window_id_t window_id;
} begin_move_floating_interaction;
struct {
wm_window_id_t window_id;
} begin_resize_floating_interaction;
struct {
wm_workspace_id_t workspace_id;
wm_layout_id_t layout_id;

View File

@@ -0,0 +1,36 @@
#pragma once
#include "wm_types.h"
typedef struct wm_output_info_t {
char *name;
wm_rect_t geometry;
} wm_output_info_t;
/*
* 由 backend 提供的窗口基础信息。
*
* 这些字符串由调用方提供state 如需长期持有,应自行复制。
*/
typedef struct wm_window_info_t {
wm_window_id_t id;
wm_rect_t frame_rect;
const char *title;
const char *app_id;
const char *class_name;
const char *instance_name;
wm_window_geometry_mode_t geometry_mode;
bool urgent;
bool fixed_size;
bool skip_taskbar;
} wm_window_info_t;
typedef struct wm_workspace_desc_t {
size_t output_index;
const char *name;
const wm_layout_id_t *layout_ids;
size_t layout_count;
wm_layout_id_t initial_layout_id;
} wm_workspace_desc_t;

View File

@@ -1,6 +1,6 @@
#pragma once
#include "wm_types.h"
#include "wm_desc.h"
/*
* wm_event_t 表示“runtime 真正关心的输入事实”:
@@ -10,17 +10,19 @@
* 的返回结果表达,而不是伪装成一个事件类型
*/
typedef enum wm_event_type_t {
// 最小核心里的快捷键只由 key press 触发,不单独暴露 key release 事件
WM_EVENT_KEY_PRESS,
// 鼠标绑定只匹配 button pressbutton release 主要供交互态结束拖拽使用
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_REMOVE,
WM_EVENT_WINDOW_METADATA_CHANGED,
WM_EVENT_WINDOW_HINTS_CHANGED,
WM_EVENT_WINDOW_ACTIVATE_REQUEST,
WM_EVENT_WINDOW_STATE_REQUEST,
WM_EVENT_CONFIGURE_REQUEST,
WM_EVENT_TIMER_TICK,
WM_EVENT_STATUS_TICK,
} wm_event_type_t;
typedef struct wm_key_press_event_t {
@@ -63,16 +65,27 @@ typedef struct wm_pointer_motion_event_t {
} wm_pointer_motion_event_t;
typedef struct wm_window_map_request_event_t {
wm_window_id_t window;
wm_window_info_t info;
// 仅用于 manage 路由判断,不进入持久 state
wm_window_id_t transient_for;
// 仅用于 manage 路由判断,不进入持久 state
bool is_dialog;
} wm_window_map_request_event_t;
typedef struct wm_window_unmap_event_t {
wm_window_id_t window;
} wm_window_unmap_event_t;
/*
* 归一化后的窗口移除事件。
* backend 把 withdraw / destroy 这类平台差异折叠成单一 remove 事件,
* core 只按 reason 区分“为何离开 managed 集合”。
*/
typedef enum wm_window_remove_reason_t {
WM_WINDOW_REMOVE_WITHDRAWN,
WM_WINDOW_REMOVE_DESTROY,
} wm_window_remove_reason_t;
typedef struct wm_window_destroy_event_t {
typedef struct wm_window_remove_event_t {
wm_window_id_t window;
} wm_window_destroy_event_t;
wm_window_remove_reason_t reason;
} wm_window_remove_event_t;
typedef enum wm_window_meta_changed_flags_t {
WM_WINDOW_META_CHANGED_NONE = 0,
@@ -97,19 +110,91 @@ typedef struct wm_window_metadata_changed_event_t {
const char *instance_name;
} wm_window_metadata_changed_event_t;
typedef enum wm_window_hint_changed_flags_t {
WM_WINDOW_HINT_CHANGED_NONE = 0,
WM_WINDOW_HINT_CHANGED_URGENT = 1u << 0,
WM_WINDOW_HINT_CHANGED_FIXED_SIZE = 1u << 1,
WM_WINDOW_HINT_CHANGED_SKIP_TASKBAR = 1u << 2,
} wm_window_hint_changed_flags_t;
/*
* 客户端属性 / hint 变化的归一化结果。
* 这类事件不是控制请求,不经过 command 路由;
* runtime 直接把结果同步到 wm_window_t 中的 hint/capability 字段。
*/
typedef struct wm_window_hints_changed_event_t {
wm_window_id_t window;
uint32_t changed_fields;
bool urgent;
bool fixed_size;
bool skip_taskbar;
} wm_window_hints_changed_event_t;
/*
* 归一化后的窗口激活请求来源。
* 取值对齐 EWMH _NET_ACTIVE_WINDOW 的 source indication
* - 0: 旧客户端 / 未指明来源
* - 1: 应用自身发起
* - 2: pager / taskbar / window switcher 发起
*/
typedef enum wm_window_activation_source_t {
WM_WINDOW_ACTIVATION_SOURCE_LEGACY = 0,
WM_WINDOW_ACTIVATION_SOURCE_APPLICATION = 1,
WM_WINDOW_ACTIVATION_SOURCE_PAGER = 2,
} wm_window_activation_source_t;
typedef struct wm_window_activate_request_event_t {
wm_window_id_t window;
wm_window_activation_source_t source;
} wm_window_activate_request_event_t;
/*
* 归一化后的窗口模式切换请求。
* backend 保留 add/remove/toggle 这类原始协议语义;
* route_event() 再结合当前 wm_state_t 翻译成最终的 SET_* 命令。
*/
typedef enum wm_window_state_request_kind_t {
WM_WINDOW_STATE_REQUEST_FULLSCREEN,
WM_WINDOW_STATE_REQUEST_MAXIMIZED,
WM_WINDOW_STATE_REQUEST_MINIMIZED,
} wm_window_state_request_kind_t;
typedef enum wm_window_state_request_action_t {
WM_WINDOW_STATE_REQUEST_ACTION_ADD,
WM_WINDOW_STATE_REQUEST_ACTION_REMOVE,
WM_WINDOW_STATE_REQUEST_ACTION_TOGGLE,
} wm_window_state_request_action_t;
typedef struct wm_window_state_request_event_t {
wm_window_id_t window;
wm_window_state_request_kind_t kind;
wm_window_state_request_action_t action;
} wm_window_state_request_event_t;
typedef enum wm_configure_request_field_t {
WM_CONFIGURE_REQUEST_FIELD_NONE = 0,
WM_CONFIGURE_REQUEST_FIELD_X = 1u << 0,
WM_CONFIGURE_REQUEST_FIELD_Y = 1u << 1,
WM_CONFIGURE_REQUEST_FIELD_WIDTH = 1u << 2,
WM_CONFIGURE_REQUEST_FIELD_HEIGHT = 1u << 3,
} wm_configure_request_field_t;
/*
* 归一化后的窗口配置请求。
* 这类事件只表达稀疏几何请求,不承载 fullscreen/maximize/minimize
* 之类窗口模式切换;后者应进入独立的 state request event。
*
* x / y / width / height 中只有 changed_fields 指示的成员才有意义。
*/
typedef struct wm_configure_request_event_t {
wm_window_id_t window;
wm_rect_t requested_rect;
uint32_t changed_fields;
int32_t x;
int32_t y;
int32_t width;
int32_t height;
} wm_configure_request_event_t;
typedef struct wm_timer_tick_event_t {
wm_time_ms_t now_ms;
} 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 {
wm_event_type_t type;
// backend 归一化后的事件时间值(毫秒),不是 wall-clock 时间戳
@@ -120,11 +205,11 @@ typedef struct wm_event_t {
wm_pointer_button_event_t pointer_button_release;
wm_pointer_motion_event_t pointer_motion;
wm_window_map_request_event_t window_map_request;
wm_window_unmap_event_t window_unmap;
wm_window_destroy_event_t window_destroy;
wm_window_remove_event_t window_remove;
wm_window_metadata_changed_event_t window_metadata_changed;
wm_window_hints_changed_event_t window_hints_changed;
wm_window_activate_request_event_t window_activate_request;
wm_window_state_request_event_t window_state_request;
wm_configure_request_event_t configure_request;
wm_timer_tick_event_t timer_tick;
wm_status_tick_event_t status_tick;
} data;
} wm_event_t;

View File

@@ -8,7 +8,9 @@ typedef enum wm_dirty_flags_t {
WM_DIRTY_LAYOUT = 1u << 1,
WM_DIRTY_STACK = 1u << 2,
WM_DIRTY_OUTPUT = 1u << 3,
WM_DIRTY_RENDER = 1u << 4,
// 只影响窗口装饰,不改变 layout 结果;例如焦点切换导致边框颜色变化
WM_DIRTY_DECORATION = 1u << 4,
WM_DIRTY_RENDER = 1u << 5,
} wm_dirty_flags_t;
typedef enum wm_effect_type_t {
@@ -36,6 +38,7 @@ typedef struct wm_effect_t {
wm_window_id_t window_id;
wm_rect_t rect; // 最终外框矩形(包含边框)
uint16_t border_width; // runtime 解析出的有效边框宽度
wm_rgba32_t border_rgba; // runtime 解析出的有效边框颜色
} configure_window;
struct {

View File

@@ -1,5 +1,6 @@
#pragma once
#include "wm_binding.h"
#include "wm_command.h"
#include "wm_event.h"
#include "wm_layout.h"
@@ -19,7 +20,10 @@ void wm_command_buffer_shutdown(wm_command_buffer_t *buffer);
bool wm_command_buffer_push(wm_command_buffer_t *buffer, wm_command_t command);
bool wm_policy_route_event(const wm_state_t *state, const wm_event_t *event,
bool wm_policy_route_event(const wm_state_t *state,
const wm_key_binding_table_t *keybindings,
const wm_pointer_binding_table_t *pointer_bindings,
const wm_event_t *event,
wm_command_buffer_t *out);
bool wm_policy_apply_command(wm_state_t *state,

View File

@@ -1,5 +1,6 @@
#pragma once
#include "wm_binding.h"
#include "wm_backend.h"
#include "wm_layout.h"
#include "wm_plan.h"
@@ -23,6 +24,11 @@ typedef struct wm_interaction_state_t {
wm_rect_t window_origin_rect;
} wm_interaction_state_t;
typedef struct wm_border_palette_t {
wm_rgba32_t normal_rgba;
wm_rgba32_t focused_rgba;
} wm_border_palette_t;
typedef struct wm_runtime_bootstrap_t {
/*
* 输出配置(启动时扫描显示器后填充)。
@@ -55,6 +61,18 @@ typedef struct wm_runtime_bootstrap_t {
*/
wm_policy_config_t policy;
/*
* 不可变键盘绑定表。
* 当前草案把它视为 bootstrap 提供的只读视图items 的生命周期必须覆盖 runtime。
*/
wm_key_binding_table_t keybindings;
/*
* 不可变鼠标按键绑定表。
* bar / status 点击属于核心外服务,因此这里只覆盖 root / window 目标。
*/
wm_pointer_binding_table_t pointer_bindings;
/*
* 全局统一边框宽度。
* 它不是窗口真状态。runtime 在提交 configure effect 时:
@@ -63,6 +81,14 @@ typedef struct wm_runtime_bootstrap_t {
*/
uint16_t border_width;
/*
* 全局边框颜色配置。
* 它不是窗口真状态。runtime 在提交 configure effect 时根据当前焦点关系解析:
* - 当前 workspace 的 focused window -> focused_rgba
* - 其余窗口 -> normal_rgba
*/
wm_border_palette_t border_palette;
/*
* Existing windows discovered during startup should be translated into these
* commands instead of being injected into wm_state_t directly.
@@ -80,7 +106,10 @@ typedef struct wm_runtime_t {
wm_command_buffer_t command_buffer;
wm_layout_registry_t layouts;
wm_policy_config_t policy;
wm_key_binding_table_t keybindings;
wm_pointer_binding_table_t pointer_bindings;
uint16_t border_width;
wm_border_palette_t border_palette;
wm_interaction_state_t interaction;
wm_backend_t backend;
wm_service_registry_t services;

View File

@@ -9,7 +9,6 @@ typedef enum wm_service_event_type_t {
WM_SERVICE_EVENT_RUNTIME_STARTED,
WM_SERVICE_EVENT_RUNTIME_STOPPING,
WM_SERVICE_EVENT_WINDOW_METADATA_CHANGED,
WM_SERVICE_EVENT_STATUS_TICK,
WM_SERVICE_EVENT_RENDER_OUTPUT,
} wm_service_event_type_t;

View File

@@ -16,6 +16,7 @@ typedef struct wm_window_t {
bool floating;
bool sticky;
bool urgent;
bool fixed_size;
// 几何信息(均为包含边框后的外框矩形)
wm_rect_t float_rect; // floating 模式下记忆的外框矩形
@@ -164,6 +165,9 @@ 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_fixed_size(wm_state_t *state,
wm_window_id_t window_id,
bool fixed_size);
void wm_state_window_set_skip_taskbar(wm_state_t *state,
wm_window_id_t window_id,
bool skip_taskbar);

View File

@@ -23,6 +23,11 @@ typedef uint32_t wm_time_ms_t;
*/
typedef uint32_t wm_keysym_t;
typedef uint32_t wm_modifier_mask_t;
/*
* 通用 32-bit RGBA 颜色值,编码为 0xRRGGBBAA。
* backend 若不支持 alpha可忽略最低 8 bit 或在提交平台 effect 时自行量化。
*/
typedef uint32_t wm_rgba32_t;
// 无效 ID 标记
#define WM_WINDOW_ID_INVALID ((wm_window_id_t)0)
@@ -74,6 +79,14 @@ typedef struct wm_rect_t {
int32_t height;
} wm_rect_t;
/*
* 核心统一使用 geometry_mode 表示窗口的显示几何模式。
* 其中 WM_GEOMETRY_MINIMIZED 覆盖所有“被最小化/被图标化”的不可见状态:
* - 在 X11 上,它对应 ICCCM 的 IconicState
* - 来自 WM_CHANGE_STATE(Iconic) 的请求也应折叠到这个模式
*
* 最小核心不单独暴露 "iconic" 枚举,避免与 minimized 形成两套并行语义。
*/
typedef enum wm_window_geometry_mode_t {
WM_GEOMETRY_NORMAL,
WM_GEOMETRY_MAXIMIZED,