core: 定义按键和鼠标事件
This commit is contained in:
@@ -18,7 +18,8 @@
|
||||
|
||||
3. =state=
|
||||
负责保存窗口、工作区、输出和全局堆叠顺序,是唯一真状态。
|
||||
采用不透明结构体设计,内部实现完全封装。
|
||||
当前头文件级草案先直接展示结构体字段,便于讨论;
|
||||
实现阶段再收敛为不透明结构体也不迟。
|
||||
窗口元数据(title/app_id/class/instance)直接存储在 =wm_window_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. 可靠性高:从干净状态启动,避免边界情况
|
||||
运行时允许变化的只是:
|
||||
|
||||
实现方式:
|
||||
- 后端检测到 RandR/Output 变化时,退出主循环
|
||||
- 由启动脚本(如 systemd user service 或 .xinitrc)自动重启 WM
|
||||
- WM 重启前保存必要状态(如当前工作区),启动后恢复
|
||||
|
||||
对用户影响:
|
||||
- 显示器拔插时窗口会短暂闪烁(~1-2 秒)
|
||||
- 但比复杂的增量逻辑更可靠,符合"简单优先"原则
|
||||
1. 某个 output 当前显示的 =current_workspace_id=
|
||||
2. 某个 workspace 当前选中的 =layout_id=
|
||||
3. 窗口集合及窗口运行态
|
||||
|
||||
* 非核心但常见的服务
|
||||
|
||||
@@ -108,6 +103,7 @@ const wm_window_t *win = wm_state_window_get(state, window_id); // O(n)
|
||||
为了避免再次出现当前全局 =wm= 那样的大对象耦合,最小核心里的所有权再收紧一层:
|
||||
|
||||
1. =runtime= 拥有 =state= 、=plan= 、=command_buffer= 和 =layout_registry= 。
|
||||
其中 workspace/output/layout registry 集合在 init 完成后保持固定。
|
||||
2. =runtime= 也拥有 =policy config= 、全局边框配置、交互态和服务注册表。
|
||||
3. =backend= 只负责平台事件翻译和平台副作用,不拥有控制状态,不直接拼 bar。
|
||||
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. 输出在启动时扫描并分配对应数量的数组,运行时不再增删。
|
||||
|
||||
5. layout registry 在启动时注册完成,运行时不再增删。
|
||||
|
||||
* 显示器热插拔策略
|
||||
|
||||
当检测到显示器配置变化(新增/删除/几何改变)时:
|
||||
|
||||
1. 后端检测到 RandR/Output 变化,发送 =WM_EVENT_QUIT= 给 runtime
|
||||
1. 后端检测到 RandR/Output 变化,使 =backend.next_event()= 返回 =WM_BACKEND_NEXT_RESTART_REQUIRED=
|
||||
2. Runtime 退出主循环,清理资源
|
||||
3. 由启动脚本(systemd/xinitrc)自动重启 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 配置
|
||||
|
||||
工作区在启动时根据输出数量动态配置,固定归属到特定输出:
|
||||
工作区配置可以在启动阶段根据输出数量生成;一旦 bootstrap 完成,归属关系保持固定:
|
||||
|
||||
**配置方式 1:手动指定**
|
||||
```lua
|
||||
@@ -171,7 +173,7 @@ workspace_distribution = {
|
||||
}
|
||||
```
|
||||
|
||||
**配置方式 3:脚本式**
|
||||
**配置方式 3:启动阶段脚本生成**
|
||||
```lua
|
||||
function configure_workspaces(outputs)
|
||||
local workspaces = {}
|
||||
@@ -193,9 +195,10 @@ end
|
||||
```
|
||||
|
||||
**可见性规则**:
|
||||
- 窗口仅在归属的 output 上可见
|
||||
- 窗口可见 iff:窗口的 workspace 是该 output 的当前显示 workspace
|
||||
- 切换 workspace 只影响归属的 output
|
||||
- 普通非 =sticky= 窗口是否进入可见集,先看它的 =workspace= 是否正被归属 output 选中
|
||||
- 多输出下,窗口最终是否出现在某个 output 上,还取决于 =frame_rect= 是否与该 output 的几何相交
|
||||
- =sticky= 窗口只放宽 workspace 可见性,不提供 output 复制语义;它同样按最终矩形与 output 的相交关系决定可见性
|
||||
- 切换 workspace 只改变归属 output 的 =current_workspace_id= ,不改变 workspace/output 的固定绑定
|
||||
|
||||
* 策略配置
|
||||
|
||||
@@ -283,9 +286,9 @@ move/resize 这类拖拽交互不属于 =wm_state_t= 真状态,但也不能继
|
||||
核心算法不应依赖此字段。
|
||||
|
||||
8. =workspace layouts=
|
||||
每个 workspace 维护一个可用布局列表(=available_layouts=),在启动时根据配置分配,运行时固定。
|
||||
workspace 的 =layout_id= 字段指向当前活动的布局(列表中的一个)。
|
||||
使用 =CYCLE_LAYOUT= 命令在可用布局间循环切换。
|
||||
每个 workspace 维护一个可用布局列表(=available_layouts=),在启动时根据配置分配,之后固定不变。
|
||||
workspace 的 =layout_id= 字段指向当前活动的布局(列表中的一个),运行时允许切换。
|
||||
使用 =CYCLE_LAYOUT= / =SET_LAYOUT= 命令切换当前活动布局。
|
||||
|
||||
9. =window rectangles=
|
||||
=frame_rect= 是窗口当前最终外框矩形;=float_rect= 是 floating 模式下记忆的外框矩形。
|
||||
@@ -306,7 +309,7 @@ workspace 的 =layout_id= 字段指向当前活动的布局(列表中的一个
|
||||
11. layout 的 =symbol= 字段用于状态栏显示,应为 1-2 个字符的简短标识符(如 "T", "M");=name= 应保持稳定、可读(如 "tile", "monocle")。
|
||||
12. 窗口标题、类名等元数据存储在 =wm_window_t= 中,与控制状态一同管理。
|
||||
13. 工作区名称存储在 =wm_workspace_t= 中,核心算法不应依赖此字段。
|
||||
14. 每个 workspace 的可用布局列表在启动时分配,运行时固定,只能通过 =CYCLE_LAYOUT= 或 =SET_LAYOUT= 命令切换当前活动布局。
|
||||
14. 每个 workspace 的可用布局列表在启动时分配,之后固定不变;运行时只允许通过 =CYCLE_LAYOUT= 或 =SET_LAYOUT= 命令切换当前活动布局。
|
||||
15. =frame_rect= 和 =float_rect= 都表示包含边框后的外框矩形。
|
||||
16. =border_width= 不是 =wm_window_t= 真状态;它由 runtime 从全局配置和几何模式推导。
|
||||
|
||||
@@ -334,16 +337,19 @@ workspace 的 =layout_id= 字段指向当前活动的布局(列表中的一个
|
||||
7. 将 render 失效通知发送给服务层
|
||||
8. =backend.flush()=
|
||||
|
||||
若后端发现显示器配置变化,不进入 output 增量更新分支,而是让 =backend.next_event()=
|
||||
返回 =WM_BACKEND_NEXT_RESTART_REQUIRED= ,由外层退出并整机重启。
|
||||
|
||||
* 文件说明
|
||||
|
||||
1. =wm_types.h=
|
||||
通用标量类型、矩形、窗口几何模式和策略枚举。
|
||||
|
||||
2. =wm_state.h=
|
||||
全局状态容器,完全封装的内部实现,提供统一的访问 API。
|
||||
全局状态容器;当前草案同时保留公开字段和统一访问 API,便于先把语义定清楚。
|
||||
|
||||
3. =wm_event.h=
|
||||
统一后的运行时事件。
|
||||
统一后的 runtime 输入事件;不承载 stop/restart 这类控制信号。
|
||||
|
||||
4. =wm_command.h=
|
||||
强类型语义命令。
|
||||
|
||||
@@ -17,9 +17,11 @@
|
||||
8. =floating= 决定基础几何来源:
|
||||
=floating = false= 时,基础几何来自 layout 结果。
|
||||
=floating = true= 时,基础几何来自 =float_rect= 。
|
||||
9. 文档里出现的可选行为,应优先固化到 =wm_policy_config_t= ,而不是继续留在 prose 里。
|
||||
10. =frame_rect= 和 =float_rect= 都表示包含边框后的外框矩形。
|
||||
11. =border_width= 不是 =wm_window_t= 真状态;runtime 在提交
|
||||
9. output/workspace/layout registry 以及每个 workspace 的 =available_layouts=
|
||||
都是 bootstrap 后固定集合;运行时不允许增删。
|
||||
10. 文档里出现的可选行为,应优先固化到 =wm_policy_config_t= ,而不是继续留在 prose 里。
|
||||
11. =frame_rect= 和 =float_rect= 都表示包含边框后的外框矩形。
|
||||
12. =border_width= 不是 =wm_window_t= 真状态;runtime 在提交
|
||||
=WM_EFFECT_CONFIGURE_WINDOW= 时按全局配置解析它。
|
||||
|
||||
* 管理类命令
|
||||
@@ -48,34 +50,6 @@
|
||||
3. =WM_DIRTY_STACK=
|
||||
4. =WM_DIRTY_RENDER=
|
||||
|
||||
** =WM_COMMAND_UPSERT_OUTPUT=
|
||||
|
||||
状态变化:
|
||||
|
||||
1. 若目标 output 已存在,只更新 =geometry= 和 =workarea= 。
|
||||
2. 若目标 output 不存在,则新建 output,并使用 =initial_workspace_id= 作为其 =current_workspace_id= 。
|
||||
3. 不修改任何窗口的 =workspace_id= 。
|
||||
|
||||
副作用:
|
||||
|
||||
1. =WM_DIRTY_OUTPUT=
|
||||
2. =WM_DIRTY_LAYOUT=
|
||||
3. =WM_DIRTY_RENDER=
|
||||
|
||||
** =WM_COMMAND_REMOVE_OUTPUT=
|
||||
|
||||
状态变化:
|
||||
|
||||
1. 从 =state.outputs[]= 中移除目标 output 。
|
||||
2. 不修改任何窗口的 =workspace_id= 。
|
||||
3. 若移除后暂时没有 output,允许 runtime 进入“无可见输出”的空闲态,等待后续热插拔恢复。
|
||||
|
||||
副作用:
|
||||
|
||||
1. =WM_DIRTY_OUTPUT=
|
||||
2. =WM_DIRTY_LAYOUT=
|
||||
3. =WM_DIRTY_RENDER=
|
||||
|
||||
** =WM_COMMAND_UNMANAGE_WINDOW=
|
||||
|
||||
前置条件:
|
||||
@@ -144,6 +118,12 @@
|
||||
|
||||
** =WM_COMMAND_SWITCH_WORKSPACE=
|
||||
|
||||
前置条件:
|
||||
|
||||
1. 目标 output 存在。
|
||||
2. 目标 workspace 存在。
|
||||
3. 目标 workspace 的 =output_id= 必须等于目标 =output_id= 。
|
||||
|
||||
状态变化:
|
||||
|
||||
1. 将目标输出的 =current_workspace_id= 改为指定工作区。
|
||||
@@ -174,22 +154,41 @@
|
||||
|
||||
** =WM_COMMAND_SEND_WINDOW_TO_OUTPUT=
|
||||
|
||||
前置条件:
|
||||
|
||||
1. 目标窗口存在。
|
||||
2. 目标 output 存在。
|
||||
|
||||
状态变化:
|
||||
|
||||
1. 这是工作区迁移命令的语法糖。
|
||||
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 当前工作区。
|
||||
|
||||
副作用:
|
||||
|
||||
1. 若最终没有修改 =workspace_id= ,则不产生状态副作用。
|
||||
2. 若最终修改了 =workspace_id= ,则副作用与 =WM_COMMAND_SEND_WINDOW_TO_WORKSPACE= 一致。
|
||||
|
||||
* 布局命令
|
||||
|
||||
** =WM_COMMAND_SET_LAYOUT=
|
||||
|
||||
前置条件:
|
||||
|
||||
1. 目标 workspace 存在。
|
||||
2. 目标 =layout_id= 已在 layout registry 中注册。
|
||||
3. 目标 =layout_id= 必须出现在该 workspace 的 =available_layouts[]= 中。
|
||||
|
||||
状态变化:
|
||||
|
||||
1. 设置目标 workspace 的 =layout_id= 。
|
||||
2. 不改变窗口的 =workspace_id= 。
|
||||
3. 不改变 =stack_order[]= 。
|
||||
4. 不影响 floating 窗口的基础几何。
|
||||
5. 影响所有 =floating == false= 且 =geometry_mode == WM_GEOMETRY_NORMAL= 的窗口。
|
||||
|
||||
副作用:
|
||||
|
||||
@@ -202,6 +201,7 @@
|
||||
|
||||
1. 目标 workspace 存在。
|
||||
2. Workspace 的可用布局列表非空(=layout_count > 0=)。
|
||||
3. 当前 =layout_id= 必须存在于 =available_layouts[]= 中。
|
||||
|
||||
状态变化:
|
||||
|
||||
@@ -356,6 +356,11 @@
|
||||
4. 不改变 =geometry_mode= 。
|
||||
5. =float_rect= 是外框矩形,因此缩放的是包含边框后的最终窗口外框。
|
||||
|
||||
副作用:
|
||||
|
||||
1. =WM_DIRTY_LAYOUT=
|
||||
2. =WM_DIRTY_RENDER=
|
||||
|
||||
* 边框约定
|
||||
|
||||
1. 当前最小核心只支持全局统一边框,不支持每窗口边框宽度。
|
||||
@@ -366,31 +371,6 @@
|
||||
=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
|
||||
|
||||
** =WM_COMMAND_REDRAW=
|
||||
|
||||
@@ -6,92 +6,133 @@
|
||||
|
||||
#+BEGIN_SRC c
|
||||
void wm_runtime_process_event(wm_runtime_t *runtime, const wm_event_t *event) {
|
||||
// 1. 元数据事件特殊处理(不经过命令系统)
|
||||
if (is_metadata_event(event)) {
|
||||
runtime_handle_metadata_event(runtime, event);
|
||||
bool consumed = false;
|
||||
bool quit_requested = false;
|
||||
|
||||
wm_command_buffer_reset(&runtime->command_buffer);
|
||||
|
||||
// 1. 辅助事件特殊处理(不经过命令系统)
|
||||
if (is_auxiliary_event(event)) {
|
||||
runtime_handle_auxiliary_event(runtime, event);
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 交互态下的特殊路由
|
||||
if (runtime->interaction.mode != WM_INTERACTION_NONE) {
|
||||
if (route_interaction_event(runtime, event, &runtime->command_buffer)) {
|
||||
return; // 事件已被交互态消费
|
||||
}
|
||||
consumed = route_interaction_event(runtime, event, &runtime->command_buffer);
|
||||
}
|
||||
|
||||
// 3. 常规事件路由
|
||||
wm_command_buffer_reset(&runtime->command_buffer);
|
||||
if (!wm_policy_route_event(&runtime->state, event, &runtime->command_buffer)) {
|
||||
return; // 没有生成命令
|
||||
if (!consumed) {
|
||||
wm_policy_route_event(&runtime->state, 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;
|
||||
continue;
|
||||
}
|
||||
|
||||
wm_policy_apply_command(
|
||||
&runtime->state,
|
||||
&runtime->policy,
|
||||
&runtime->layouts,
|
||||
&runtime->command_buffer.items[i],
|
||||
command,
|
||||
&runtime->plan
|
||||
);
|
||||
}
|
||||
|
||||
// 5. 处理副作用
|
||||
if (runtime->plan.dirty_flags) {
|
||||
runtime_apply_plan(runtime);
|
||||
// 5. runtime 控制命令在命令应用之后统一处理
|
||||
if (quit_requested) {
|
||||
wm_runtime_stop(runtime);
|
||||
}
|
||||
}
|
||||
#+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=**
|
||||
|
||||
#+BEGIN_SRC c
|
||||
void runtime_handle_metadata_event(wm_runtime_t *runtime, const wm_event_t *event) {
|
||||
const wm_window_metadata_changed_event_t *e = &event->as.window_metadata_changed;
|
||||
const wm_window_metadata_changed_event_t *e = &event->data.window_metadata_changed;
|
||||
|
||||
// 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) {
|
||||
return; // 窗口已被销毁
|
||||
}
|
||||
|
||||
// 2. 通过 state 接口更新窗口元数据字段
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
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 脏(状态栏需要更新)
|
||||
runtime->plan.dirty_flags |= WM_DIRTY_RENDER;
|
||||
|
||||
// 4. 通知订阅的服务(如状态栏组件)
|
||||
wm_service_notify_metadata_changed(&runtime->services,
|
||||
e->window_id,
|
||||
e->changed_fields);
|
||||
wm_service_event_t service_event = {
|
||||
.type = WM_SERVICE_EVENT_WINDOW_METADATA_CHANGED,
|
||||
.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
|
||||
|
||||
**=WM_EVENT_STATUS_TICK=**
|
||||
|
||||
定时器事件,也不经过命令系统:
|
||||
状态服务更新事件,也不经过命令系统:
|
||||
|
||||
#+BEGIN_SRC c
|
||||
void runtime_handle_status_tick(wm_runtime_t *runtime, const wm_event_t *event) {
|
||||
// 通知状态服务更新
|
||||
wm_service_notify_status_tick(&runtime->services);
|
||||
(void)event;
|
||||
|
||||
// 状态服务更新后,如果产生了新的渲染需求,会通过命令系统处理
|
||||
// 通知状态服务更新
|
||||
wm_service_event_t service_event = {
|
||||
.type = WM_SERVICE_EVENT_STATUS_TICK,
|
||||
};
|
||||
wm_service_registry_emit(&runtime->services, &service_event, &runtime->state);
|
||||
}
|
||||
#+END_SRC
|
||||
|
||||
@@ -103,35 +144,37 @@ void runtime_handle_status_tick(wm_runtime_t *runtime, const wm_event_t *event)
|
||||
// keybindings.conf
|
||||
keybindings = [
|
||||
{
|
||||
// 键盘绑定
|
||||
keysym: "Mod4+Return",
|
||||
modifiers: [Mod4],
|
||||
// 退出 WM
|
||||
keysym: "SUPER+SHIFT+q",
|
||||
command: {
|
||||
type: WM_COMMAND_LAUNCH_TERMINAL
|
||||
type: WM_COMMAND_QUIT
|
||||
}
|
||||
},
|
||||
{
|
||||
// 快捷键绑定(可带参数)
|
||||
keysym: "Mod4+Shift+q",
|
||||
// 焦点切到前一个窗口
|
||||
keysym: "SUPER+j",
|
||||
command: {
|
||||
type: WM_COMMAND_FOCUS_WINDOW,
|
||||
type: WM_COMMAND_FOCUS_DIRECTION,
|
||||
output_id: 0,
|
||||
direction: "prev"
|
||||
}
|
||||
},
|
||||
{
|
||||
// 布局切换
|
||||
keysym: "Mod4+space",
|
||||
// 循环切换布局
|
||||
keysym: "SUPER+space",
|
||||
command: {
|
||||
type: WM_COMMAND_SET_LAYOUT,
|
||||
layout_name: "tile"
|
||||
type: WM_COMMAND_CYCLE_LAYOUT,
|
||||
workspace_id: 0,
|
||||
direction: 1
|
||||
}
|
||||
},
|
||||
{
|
||||
// 窗口切换到工作区
|
||||
keysym: "Mod4+Shift+1",
|
||||
// 切换到 workspace 0
|
||||
keysym: "SUPER+1",
|
||||
command: {
|
||||
type: WM_COMMAND_SEND_WINDOW_TO_WORKSPACE,
|
||||
workspace_index: 0
|
||||
type: WM_COMMAND_SWITCH_WORKSPACE,
|
||||
output_id: 0,
|
||||
workspace_id: 0
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -141,7 +184,20 @@ keybindings = [
|
||||
|
||||
1. 完全匹配(keysym + 所有 modifiers)
|
||||
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()= 实现**:
|
||||
|
||||
@@ -151,28 +207,22 @@ bool wm_policy_route_event(const wm_state_t *state,
|
||||
wm_command_buffer_t *out) {
|
||||
switch (event->type) {
|
||||
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:
|
||||
return route_button_press(state, &event->as.button_press, out);
|
||||
case WM_EVENT_POINTER_BUTTON_PRESS:
|
||||
return route_pointer_press(state, &event->data.pointer_button_press, out);
|
||||
|
||||
case WM_EVENT_MAP_REQUEST:
|
||||
return route_map_request(state, &event->as.map_request, out);
|
||||
case WM_EVENT_WINDOW_MAP_REQUEST:
|
||||
return route_map_request(state, &event->data.window_map_request, out);
|
||||
|
||||
case WM_EVENT_UNMAP_NOTIFY:
|
||||
return route_unmap_notify(state, &event->as.unmap_notify, out);
|
||||
case WM_EVENT_WINDOW_UNMAP:
|
||||
return route_unmap_notify(state, &event->data.window_unmap, out);
|
||||
|
||||
case WM_EVENT_DESTROY_NOTIFY:
|
||||
return route_destroy_notify(state, &event->as.destroy_notify, out);
|
||||
case WM_EVENT_WINDOW_DESTROY:
|
||||
return route_destroy_notify(state, &event->data.window_destroy, out);
|
||||
|
||||
case WM_EVENT_CONFIGURE_REQUEST:
|
||||
return route_configure_request(state, &event->as.configure_request, out);
|
||||
|
||||
case WM_EVENT_OUTPUT_CHANGED:
|
||||
return route_output_changed(state, &event->as.output_changed, out);
|
||||
|
||||
case WM_EVENT_OUTPUT_REMOVED:
|
||||
return route_output_removed(state, &event->as.output_removed, out);
|
||||
return route_configure_request(state, &event->data.configure_request, out);
|
||||
|
||||
default:
|
||||
return false;
|
||||
@@ -185,7 +235,7 @@ static bool route_key_press(const wm_state_t *state,
|
||||
wm_command_buffer_t *out) {
|
||||
// 1. 查找匹配的快捷键绑定
|
||||
const keybinding_t *binding = keybinding_find(
|
||||
e->keycode,
|
||||
e->keysym,
|
||||
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,
|
||||
const wm_map_request_event_t *e,
|
||||
const wm_window_map_request_event_t *e,
|
||||
wm_command_buffer_t *out) {
|
||||
// 1. 检查窗口是否已存在
|
||||
if (state_find_window(state, e->window_id)) {
|
||||
if (wm_state_window_get(state, e->window)) {
|
||||
return false; // 窗口已管理,忽略
|
||||
}
|
||||
|
||||
// 2. 生成 MANAGE_WINDOW 命令
|
||||
wm_command_t cmd = {
|
||||
.type = WM_COMMAND_MANAGE_WINDOW,
|
||||
.manage_window = {
|
||||
.window_id = e->window_id,
|
||||
.as.manage_window = {
|
||||
.window_id = e->window,
|
||||
// 应用规则确定初始状态
|
||||
}
|
||||
};
|
||||
@@ -251,22 +301,20 @@ static bool route_move_floating_event(wm_runtime_t *runtime,
|
||||
const wm_event_t *event,
|
||||
wm_command_buffer_t *out) {
|
||||
switch (event->type) {
|
||||
case WM_EVENT_BUTTON_RELEASE:
|
||||
// 结束移动交互
|
||||
wm_command_t end_cmd = {
|
||||
.type = WM_COMMAND_END_INTERACTION
|
||||
};
|
||||
return wm_command_buffer_push(out, end_cmd);
|
||||
case WM_EVENT_POINTER_BUTTON_RELEASE:
|
||||
// 结束移动交互。这是 runtime 局部状态,不需要额外命令。
|
||||
runtime->interaction = (wm_interaction_state_t){0};
|
||||
return true;
|
||||
|
||||
case WM_EVENT_POINTER_MOTION:
|
||||
// 生成移动命令
|
||||
wm_command_t move_cmd = {
|
||||
.type = WM_COMMAND_MOVE_FLOATING_WINDOW,
|
||||
.move_floating_window = {
|
||||
.as.move_floating_window = {
|
||||
.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,
|
||||
.delta_y = event->as.pointer_motion.root.y -
|
||||
.dy = event->data.pointer_motion.root.y -
|
||||
runtime->interaction.pointer_origin.y
|
||||
}
|
||||
};
|
||||
@@ -287,20 +335,18 @@ static bool route_move_floating_event(wm_runtime_t *runtime,
|
||||
| 事件类型 | 可能生成的命令 | 条件 |
|
||||
|---------|---------------|------|
|
||||
| KEY_PRESS | 任意命令(通过快捷键绑定) | 绑定存在 |
|
||||
| BUTTON_PRESS | FOCUS_WINDOW, RAISE_WINDOW | 点击窗口 |
|
||||
| MAP_REQUEST | MANAGE_WINDOW | 窗口未管理 |
|
||||
| UNMAP_NOTIFY | (可能不生成命令) | - |
|
||||
| DESTROY_NOTIFY | UNMANAGE_WINDOW | 窗口已管理 |
|
||||
| POINTER_BUTTON_PRESS | FOCUS_WINDOW, RAISE_WINDOW | 点击窗口 |
|
||||
| WINDOW_MAP_REQUEST | MANAGE_WINDOW | 窗口未管理 |
|
||||
| WINDOW_UNMAP | (可能不生成命令) | - |
|
||||
| WINDOW_DESTROY | UNMANAGE_WINDOW | 窗口已管理 |
|
||||
| CONFIGURE_REQUEST | (可能不生成命令) | floating 窗口 |
|
||||
| OUTPUT_CHANGED | UPSERT_OUTPUT | - |
|
||||
| OUTPUT_REMOVED | REMOVE_OUTPUT | - |
|
||||
| POINTER_MOTION | MOVE/RESIZE 命令 | 交互态下 |
|
||||
|
||||
* 事件处理的边界条件
|
||||
|
||||
**1. 元数据事件边界**
|
||||
**1. 辅助事件边界**
|
||||
|
||||
元数据事件满足以下条件时直接处理,不经过命令系统:
|
||||
辅助事件满足以下条件时直接处理,不经过命令系统:
|
||||
- 事件类型是 `WM_EVENT_WINDOW_METADATA_CHANGED`
|
||||
- 事件类型是 `WM_EVENT_STATUS_TICK`
|
||||
|
||||
@@ -308,20 +354,26 @@ static bool route_move_floating_event(wm_runtime_t *runtime,
|
||||
|
||||
交互态满足以下条件时,优先路由到交互处理器:
|
||||
- `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_DESTROY_NOTIFY`:窗口不存在则忽略
|
||||
- `WM_EVENT_WINDOW_MAP_REQUEST`:窗口已存在则忽略
|
||||
- `WM_EVENT_WINDOW_DESTROY`:窗口不存在则忽略
|
||||
- `WM_EVENT_CONFIGURE_REQUEST`:只有 floating 窗口才响应
|
||||
|
||||
* 事件处理顺序保证
|
||||
|
||||
为了保证确定性行为,事件处理必须按以下顺序进行:
|
||||
|
||||
1. **元数据事件优先**:状态栏信息更新不触发布局计算
|
||||
1. **辅助事件优先**:元数据和状态服务更新不走命令系统
|
||||
2. **交互态优先**:拖拽操作优先于快捷键
|
||||
3. **快捷键匹配优先级**:完全匹配 → 通配符 → 默认
|
||||
4. **命令执行顺序**:命令缓冲中的命令按顺序执行
|
||||
@@ -334,12 +386,20 @@ void wm_runtime_run(wm_runtime_t *runtime) {
|
||||
|
||||
while (runtime->running) {
|
||||
// 1. 获取事件
|
||||
if (!wm_backend_next_event(&runtime->backend, &event)) {
|
||||
continue;
|
||||
}
|
||||
wm_backend_next_result_t poll_result =
|
||||
runtime->backend.api->next_event(&runtime->backend, &event);
|
||||
|
||||
if (poll_result == WM_BACKEND_NEXT_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. 如果有副作用,应用它们
|
||||
if (runtime->plan.dirty_flags) {
|
||||
@@ -348,18 +408,32 @@ void wm_runtime_run(wm_runtime_t *runtime) {
|
||||
wm_runtime_relayout(runtime);
|
||||
}
|
||||
|
||||
// 3.2 应用平台副作用
|
||||
wm_backend_apply_effects(&runtime->backend, &runtime->plan);
|
||||
// 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];
|
||||
|
||||
// 3.3 通知服务层(渲染、状态栏等)
|
||||
if (runtime->plan.dirty_flags & WM_DIRTY_RENDER) {
|
||||
wm_service_notify_render(&runtime->services, &runtime->state);
|
||||
if (effect->type == WM_EFFECT_RENDER_OUTPUT) {
|
||||
wm_service_event_t service_event = {
|
||||
.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;
|
||||
}
|
||||
|
||||
// 3.4 刷新平台
|
||||
wm_backend_flush(&runtime->backend);
|
||||
runtime->backend.api->apply_effect(&runtime->backend, effect);
|
||||
}
|
||||
|
||||
// 3.5 重置 plan
|
||||
// 3.3 刷新平台
|
||||
if (runtime->backend.api->flush) {
|
||||
runtime->backend.api->flush(&runtime->backend);
|
||||
}
|
||||
|
||||
// 3.4 重置 plan
|
||||
wm_plan_reset(&runtime->plan);
|
||||
}
|
||||
|
||||
@@ -375,9 +449,9 @@ void wm_runtime_run(wm_runtime_t *runtime) {
|
||||
|
||||
1. **快捷键系统**:通过 keybindings.conf 配置
|
||||
2. **规则系统**:在 MANAGE_WINDOW 命令中应用
|
||||
3. **布局切换**:通过 SET_LAYOUT 命令
|
||||
3. **布局切换**:通过 SET_LAYOUT / CYCLE_LAYOUT 命令
|
||||
4. **拖拽交互**:通过交互态管理
|
||||
5. **多显示器**:OUTPUT_CHANGED/REMOVED 事件处理
|
||||
5. **多显示器**:显示器配置变化时通过 `WM_BACKEND_NEXT_RESTART_REQUIRED` 触发重启
|
||||
|
||||
* 缺失的文档总结
|
||||
|
||||
|
||||
@@ -34,39 +34,36 @@
|
||||
|
||||
#+BEGIN_SRC c
|
||||
bool wm_policy_apply_command(wm_state_t *state,
|
||||
const wm_policy_config_t *policy,
|
||||
const wm_layout_registry_t *layouts,
|
||||
const wm_command_t *command,
|
||||
wm_plan_t *plan) {
|
||||
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) {
|
||||
case WM_COMMAND_NONE:
|
||||
return false;
|
||||
|
||||
case WM_COMMAND_MANAGE_WINDOW:
|
||||
changed = apply_manage_window(state, command, plan);
|
||||
changed = apply_manage_window(state, policy, command, plan);
|
||||
break;
|
||||
|
||||
case WM_COMMAND_UNMANAGE_WINDOW:
|
||||
changed = apply_unmanage_window(state, command, plan);
|
||||
break;
|
||||
|
||||
case WM_COMMAND_UPSERT_OUTPUT:
|
||||
changed = apply_upsert_output(state, command, plan);
|
||||
break;
|
||||
|
||||
case WM_COMMAND_REMOVE_OUTPUT:
|
||||
changed = apply_remove_output(state, command, plan);
|
||||
break;
|
||||
|
||||
case WM_COMMAND_FOCUS_WINDOW:
|
||||
changed = apply_focus_window(state, command, plan);
|
||||
changed = apply_focus_window(state, policy, command, plan);
|
||||
break;
|
||||
|
||||
case WM_COMMAND_FOCUS_DIRECTION:
|
||||
changed = apply_focus_direction(state, command, plan);
|
||||
changed = apply_focus_direction(state, policy, command, plan);
|
||||
break;
|
||||
|
||||
case WM_COMMAND_RAISE_WINDOW:
|
||||
@@ -106,7 +103,7 @@ bool wm_policy_apply_command(wm_state_t *state,
|
||||
break;
|
||||
|
||||
case WM_COMMAND_TOGGLE_MINIMIZE:
|
||||
changed = apply_toggle_minimize(state, command, plan);
|
||||
changed = apply_toggle_minimize(state, policy, command, plan);
|
||||
break;
|
||||
|
||||
case WM_COMMAND_MOVE_FLOATING_WINDOW:
|
||||
@@ -134,7 +131,7 @@ bool wm_policy_apply_command(wm_state_t *state,
|
||||
break;
|
||||
}
|
||||
|
||||
if (changed) state->generation++;
|
||||
if (changed && generation_relevant) state->generation++;
|
||||
return changed;
|
||||
}
|
||||
#+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_output_t *require_output(wm_state_t *state, wm_output_id_t id);
|
||||
|
||||
static bool state_upsert_output(wm_state_t *state, wm_output_t output);
|
||||
static bool state_remove_output(wm_state_t *state, wm_output_id_t id);
|
||||
static bool state_append_window(wm_state_t *state, wm_window_t window);
|
||||
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_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_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_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_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 const wm_output_t *find_output_by_point(const wm_state_t *state,
|
||||
wm_point_t point);
|
||||
#+END_SRC
|
||||
|
||||
* 命令分支骨架
|
||||
@@ -172,6 +173,7 @@ static void maybe_refocus_workspace(wm_state_t *state, wm_workspace_t *workspace
|
||||
|
||||
#+BEGIN_SRC c
|
||||
static bool apply_manage_window(wm_state_t *state,
|
||||
const wm_policy_config_t *policy,
|
||||
const wm_command_t *command,
|
||||
wm_plan_t *plan) {
|
||||
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 (!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;
|
||||
}
|
||||
|
||||
@@ -213,43 +216,6 @@ static bool apply_manage_window(wm_state_t *state,
|
||||
}
|
||||
#+END_SRC
|
||||
|
||||
** =apply_upsert_output()=
|
||||
|
||||
#+BEGIN_SRC c
|
||||
static bool apply_upsert_output(wm_state_t *state,
|
||||
const wm_command_t *command,
|
||||
wm_plan_t *plan) {
|
||||
wm_output_t output = {
|
||||
.id = command->as.upsert_output.output_id,
|
||||
.geometry = command->as.upsert_output.geometry,
|
||||
.workarea = command->as.upsert_output.workarea,
|
||||
.current_workspace_id = command->as.upsert_output.initial_workspace_id,
|
||||
};
|
||||
|
||||
if (!state_upsert_output(state, output)) return false;
|
||||
|
||||
plan->dirty_flags |= WM_DIRTY_OUTPUT | WM_DIRTY_LAYOUT | WM_DIRTY_RENDER;
|
||||
push_render_all_outputs(state, plan);
|
||||
return true;
|
||||
}
|
||||
#+END_SRC
|
||||
|
||||
** =apply_remove_output()=
|
||||
|
||||
#+BEGIN_SRC c
|
||||
static bool apply_remove_output(wm_state_t *state,
|
||||
const wm_command_t *command,
|
||||
wm_plan_t *plan) {
|
||||
if (!state_remove_output(state, command->as.remove_output.output_id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
plan->dirty_flags |= WM_DIRTY_OUTPUT | WM_DIRTY_LAYOUT | WM_DIRTY_RENDER;
|
||||
push_render_all_outputs(state, plan);
|
||||
return true;
|
||||
}
|
||||
#+END_SRC
|
||||
|
||||
** =apply_unmanage_window()=
|
||||
|
||||
#+BEGIN_SRC c
|
||||
@@ -280,6 +246,7 @@ static bool apply_unmanage_window(wm_state_t *state,
|
||||
|
||||
#+BEGIN_SRC c
|
||||
static bool apply_focus_window(wm_state_t *state,
|
||||
const wm_policy_config_t *policy,
|
||||
const wm_command_t *command,
|
||||
wm_plan_t *plan) {
|
||||
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;
|
||||
|
||||
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;
|
||||
return true;
|
||||
}
|
||||
@@ -331,6 +303,7 @@ static bool apply_toggle_sticky(wm_state_t *state,
|
||||
|
||||
#+BEGIN_SRC c
|
||||
static bool apply_focus_direction(wm_state_t *state,
|
||||
const wm_policy_config_t *policy,
|
||||
const wm_command_t *command,
|
||||
wm_plan_t *plan) {
|
||||
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 =
|
||||
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;
|
||||
|
||||
ws->focused_window_id = next;
|
||||
@@ -382,15 +356,16 @@ static bool apply_switch_workspace(wm_state_t *state,
|
||||
wm_output_t *out =
|
||||
require_output(state, command->as.switch_workspace.output_id);
|
||||
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;
|
||||
}
|
||||
|
||||
if (out->current_workspace_id == command->as.switch_workspace.workspace_id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
out->current_workspace_id = command->as.switch_workspace.workspace_id;
|
||||
out->current_workspace_id = ws->id;
|
||||
plan->dirty_flags |= WM_DIRTY_OUTPUT | WM_DIRTY_LAYOUT | WM_DIRTY_RENDER;
|
||||
push_render_all_outputs(state, plan);
|
||||
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 ==
|
||||
WM_CROSS_OUTPUT_KEEP_WORKSPACE) {
|
||||
/*
|
||||
* 保留 workspace 但跨 output 调整几何,不属于这个命令的职责;
|
||||
* 调用方应使用 MOVE_FLOATING_WINDOW 只改 float_rect。
|
||||
*/
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -522,16 +501,25 @@ static bool apply_toggle_fullscreen(wm_state_t *state,
|
||||
|
||||
#+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) {
|
||||
wm_window_t *win =
|
||||
require_window(state, command->as.toggle_minimize.window_id);
|
||||
if (!win) return false;
|
||||
|
||||
wm_workspace_t *ws = require_workspace(state, win->workspace_id);
|
||||
|
||||
if (win->geometry_mode == WM_GEOMETRY_MINIMIZED) {
|
||||
win->geometry_mode = WM_GEOMETRY_NORMAL;
|
||||
} else {
|
||||
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;
|
||||
@@ -557,7 +545,7 @@ static bool apply_move_floating_window(wm_state_t *state,
|
||||
|
||||
if (command->as.move_floating_window.commit_workspace_change) {
|
||||
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 &&
|
||||
command->as.move_floating_window.policy ==
|
||||
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)) {
|
||||
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;
|
||||
|
||||
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;
|
||||
bool found_current = false;
|
||||
for (size_t i = 0; i < ws->layout_count; i++) {
|
||||
if (ws->available_layouts[i] == ws->layout_id) {
|
||||
current_idx = i;
|
||||
found_current = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found_current) return false;
|
||||
|
||||
// 计算下一个索引(支持前后循环)
|
||||
size_t next_idx;
|
||||
@@ -683,11 +677,15 @@ static bool apply_quit(wm_state_t *state,
|
||||
wm_plan_t *plan) {
|
||||
(void)state;
|
||||
(void)command;
|
||||
plan->dirty_flags |= WM_DIRTY_NONE;
|
||||
(void)plan;
|
||||
return true;
|
||||
}
|
||||
#+END_SRC
|
||||
|
||||
通常情况下 =WM_COMMAND_QUIT= 会由 runtime 在命令应用外层直接拦截并调用
|
||||
=wm_runtime_stop()= 。保留 =apply_quit()= 这个空实现,只是为了让 switch
|
||||
结构保持完整。
|
||||
|
||||
* 推荐的后处理步骤
|
||||
|
||||
在 =apply_*()= 返回后,建议由上层统一做这些事,而不是在每个分支里直接跑布局:
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
-- zdwm.lua
|
||||
|
||||
outputs = {
|
||||
{ id = 0, name = "DP-1", enabled = true, primary = true },
|
||||
{ id = 0, name = "DP-1", primary = true },
|
||||
}
|
||||
|
||||
-- 所有 workspace 都归属 DP-1
|
||||
@@ -42,8 +42,8 @@ workspaces = auto_generate_workspaces({
|
||||
#+BEGIN_SRC lua
|
||||
-- 左侧显示器 1-5,右侧显示器 6-10
|
||||
outputs = {
|
||||
{ id = 0, name = "DP-1", enabled = true, primary = true },
|
||||
{ id = 1, name = "DP-2", enabled = true },
|
||||
{ id = 0, name = "DP-1", primary = true },
|
||||
{ id = 1, name = "DP-2" },
|
||||
}
|
||||
|
||||
workspaces = {
|
||||
@@ -112,8 +112,8 @@ workspaces = {
|
||||
|
||||
outputs = {
|
||||
{ id = 0, name = "eDP-1", primary = true },
|
||||
{ id = 1, name = "DP-1", enabled = true },
|
||||
{ id = 2, name = "HDMI-1", enabled = true },
|
||||
{ id = 1, name = "DP-1" },
|
||||
{ id = 2, name = "HDMI-1" },
|
||||
}
|
||||
|
||||
workspaces = {
|
||||
@@ -134,13 +134,12 @@ workspaces = {
|
||||
}
|
||||
#+END_SRC
|
||||
|
||||
** 动态配置(脚本式)
|
||||
** 启动阶段脚本生成
|
||||
|
||||
根据实际检测到的输出数量动态生成:
|
||||
根据启动阶段检测到的输出数量生成:
|
||||
|
||||
#+BEGIN_SRC lua
|
||||
function configure_workspaces(runtime)
|
||||
local outputs = runtime:scan_outputs()
|
||||
function configure_workspaces(outputs)
|
||||
local workspaces = {}
|
||||
local ws_id = 1
|
||||
|
||||
@@ -220,12 +219,13 @@ bool load_workspace_config(const char *path,
|
||||
|
||||
#+BEGIN_SRC lua
|
||||
-- 声明布局注册表
|
||||
-- layout_id 按注册顺序稳定分配:tile=0, monocle=1, floating=2, ...
|
||||
layouts = {
|
||||
{ id = 0, name = "T", fn = layout_tile },
|
||||
{ id = 1, name = "M", fn = layout_monocle },
|
||||
{ id = 2, name = "F", fn = layout_floating },
|
||||
{ id = 3, name = "S", fn = layout_stack },
|
||||
{ id = 4, name = "G", fn = layout_grid },
|
||||
{ name = "tile", symbol = "T", fn = layout_tile },
|
||||
{ name = "monocle", symbol = "M", fn = layout_monocle },
|
||||
{ name = "floating", symbol = "F", fn = layout_floating },
|
||||
{ name = "stack", symbol = "S", fn = layout_stack },
|
||||
{ name = "grid", symbol = "G", fn = layout_grid },
|
||||
}
|
||||
|
||||
-- 单显示器:每个 workspace 可用所有布局
|
||||
@@ -280,7 +280,7 @@ workspaces = {
|
||||
|
||||
1. *固定归属*:workspace.output_id 在创建后不应改变
|
||||
2. *输出变化*:显示器配置变化时,WM 会重启,重新扫描和配置
|
||||
3. *可见性*:窗口只在归属的 output 上可见
|
||||
3. *可见性*:普通非 =sticky= 窗口先受 workspace 可见性约束,再按最终矩形与 output 的相交关系决定具体显示在哪些 output 上;=sticky= 窗口只放宽 workspace 可见性
|
||||
4. *独立性*:切换不同 output 的 workspace 互不影响
|
||||
5. *布局列表*:workspace.layouts 在启动时分配,运行时固定
|
||||
6. *布局切换*:使用 CYCLE_LAYOUT 命令在可用布局间循环切换
|
||||
6. *布局切换*:使用 =CYCLE_LAYOUT= 或 =SET_LAYOUT= 命令在可用布局集合内切换当前布局
|
||||
|
||||
@@ -5,9 +5,24 @@
|
||||
|
||||
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 {
|
||||
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.
|
||||
* Backends only receive platform effects here.
|
||||
|
||||
@@ -14,8 +14,6 @@ typedef enum wm_command_type_t {
|
||||
WM_COMMAND_NONE,
|
||||
WM_COMMAND_MANAGE_WINDOW,
|
||||
WM_COMMAND_UNMANAGE_WINDOW,
|
||||
WM_COMMAND_UPSERT_OUTPUT,
|
||||
WM_COMMAND_REMOVE_OUTPUT,
|
||||
WM_COMMAND_FOCUS_WINDOW,
|
||||
WM_COMMAND_FOCUS_DIRECTION,
|
||||
WM_COMMAND_RAISE_WINDOW,
|
||||
@@ -51,17 +49,6 @@ typedef struct wm_command_t {
|
||||
wm_window_id_t window_id;
|
||||
} unmanage_window;
|
||||
|
||||
struct {
|
||||
wm_output_id_t output_id;
|
||||
wm_rect_t geometry;
|
||||
wm_rect_t workarea;
|
||||
wm_workspace_id_t initial_workspace_id;
|
||||
} upsert_output;
|
||||
|
||||
struct {
|
||||
wm_output_id_t output_id;
|
||||
} remove_output;
|
||||
|
||||
struct {
|
||||
wm_window_id_t window_id;
|
||||
} focus_window;
|
||||
|
||||
@@ -2,52 +2,77 @@
|
||||
|
||||
#include "wm_types.h"
|
||||
|
||||
/*
|
||||
* wm_event_t 表示“runtime 真正关心的输入事实”:
|
||||
* - 它不是平台原始事件的逐字段照抄
|
||||
* - 它允许 backend 对平台事件做最小必要的归一化 / 过滤 / 聚合
|
||||
* - 它不承载 runtime 控制语义;stop / restart / error 由 backend.next_event()
|
||||
* 的返回结果表达,而不是伪装成一个事件类型
|
||||
*/
|
||||
typedef enum wm_event_type_t {
|
||||
WM_EVENT_NONE,
|
||||
WM_EVENT_KEY_PRESS,
|
||||
WM_EVENT_BUTTON_PRESS,
|
||||
WM_EVENT_BUTTON_RELEASE,
|
||||
WM_EVENT_POINTER_BUTTON_PRESS,
|
||||
WM_EVENT_POINTER_BUTTON_RELEASE,
|
||||
WM_EVENT_POINTER_MOTION,
|
||||
WM_EVENT_MAP_REQUEST,
|
||||
WM_EVENT_UNMAP_NOTIFY,
|
||||
WM_EVENT_DESTROY_NOTIFY,
|
||||
WM_EVENT_WINDOW_MAP_REQUEST,
|
||||
WM_EVENT_WINDOW_UNMAP,
|
||||
WM_EVENT_WINDOW_DESTROY,
|
||||
WM_EVENT_WINDOW_METADATA_CHANGED,
|
||||
WM_EVENT_CONFIGURE_REQUEST,
|
||||
WM_EVENT_OUTPUT_CHANGED,
|
||||
WM_EVENT_OUTPUT_REMOVED,
|
||||
WM_EVENT_TIMER_TICK,
|
||||
WM_EVENT_STATUS_TICK,
|
||||
WM_EVENT_QUIT,
|
||||
} wm_event_type_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 modifiers;
|
||||
} wm_key_press_event_t;
|
||||
|
||||
typedef struct wm_button_event_t {
|
||||
uint32_t button;
|
||||
uint32_t modifiers;
|
||||
typedef struct wm_pointer_button_event_t {
|
||||
wm_button_t button;
|
||||
wm_modifier_mask_t modifiers;
|
||||
// backend 原始按钮码,仅供调试或未来平台特化逻辑使用
|
||||
uint32_t raw_button;
|
||||
// 全局桌面坐标
|
||||
wm_point_t root;
|
||||
wm_window_id_t target_window_id;
|
||||
} wm_button_event_t;
|
||||
// 相对 window 的坐标;若 window == WM_WINDOW_ID_INVALID,则该值未定义
|
||||
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 {
|
||||
// 全局桌面坐标
|
||||
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;
|
||||
|
||||
typedef struct wm_map_request_event_t {
|
||||
wm_window_id_t window_id;
|
||||
} wm_map_request_event_t;
|
||||
typedef struct wm_window_map_request_event_t {
|
||||
wm_window_id_t window;
|
||||
} wm_window_map_request_event_t;
|
||||
|
||||
typedef struct wm_unmap_notify_event_t {
|
||||
wm_window_id_t window_id;
|
||||
} wm_unmap_notify_event_t;
|
||||
typedef struct wm_window_unmap_event_t {
|
||||
wm_window_id_t window;
|
||||
} wm_window_unmap_event_t;
|
||||
|
||||
typedef struct wm_destroy_notify_event_t {
|
||||
wm_window_id_t window_id;
|
||||
} wm_destroy_notify_event_t;
|
||||
typedef struct wm_window_destroy_event_t {
|
||||
wm_window_id_t window;
|
||||
} wm_window_destroy_event_t;
|
||||
|
||||
typedef enum wm_window_meta_changed_flags_t {
|
||||
WM_WINDOW_META_CHANGED_NONE = 0,
|
||||
@@ -58,7 +83,7 @@ typedef enum wm_window_meta_changed_flags_t {
|
||||
} wm_window_meta_changed_flags_t;
|
||||
|
||||
typedef struct wm_window_metadata_changed_event_t {
|
||||
wm_window_id_t window_id;
|
||||
wm_window_id_t window;
|
||||
uint32_t changed_fields;
|
||||
/*
|
||||
* 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;
|
||||
|
||||
typedef struct wm_configure_request_event_t {
|
||||
wm_window_id_t window_id;
|
||||
wm_window_id_t window;
|
||||
wm_rect_t requested_rect;
|
||||
} 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 {
|
||||
uint64_t now_ms;
|
||||
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;
|
||||
uint64_t ts_ms;
|
||||
// backend 归一化后的事件时间值(毫秒),不是 wall-clock 时间戳
|
||||
wm_time_ms_t time_ms;
|
||||
union {
|
||||
wm_key_press_event_t key_press;
|
||||
wm_button_event_t button_press;
|
||||
wm_button_event_t button_release;
|
||||
wm_pointer_button_event_t pointer_button_press;
|
||||
wm_pointer_button_event_t pointer_button_release;
|
||||
wm_pointer_motion_event_t pointer_motion;
|
||||
wm_map_request_event_t map_request;
|
||||
wm_unmap_notify_event_t unmap_notify;
|
||||
wm_destroy_notify_event_t destroy_notify;
|
||||
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_metadata_changed_event_t window_metadata_changed;
|
||||
wm_configure_request_event_t configure_request;
|
||||
wm_output_changed_event_t output_changed;
|
||||
wm_output_removed_event_t output_removed;
|
||||
wm_timer_tick_event_t timer_tick;
|
||||
} as;
|
||||
wm_status_tick_event_t status_tick;
|
||||
} data;
|
||||
} wm_event_t;
|
||||
|
||||
@@ -33,14 +33,22 @@ typedef bool (*wm_layout_fn)(const wm_layout_ctx_t *ctx,
|
||||
typedef struct wm_layout_slot_t {
|
||||
wm_layout_id_t id;
|
||||
/*
|
||||
* 布局标识符,用于:
|
||||
* - 状态栏显示(应简短,如 "T", "M", "F")
|
||||
* - 日志输出
|
||||
* 布局稳定名称,用于:
|
||||
* - 配置引用
|
||||
* - 日志输出
|
||||
* - 调试输出
|
||||
*
|
||||
* 推荐使用可读名字,如 "tile"、"monocle"、"floating"。
|
||||
*/
|
||||
const char *name;
|
||||
/*
|
||||
* 布局符号,用于:
|
||||
* - 状态栏显示(应简短,如 "T", "M", "F")
|
||||
* - 紧凑场景下的快速识别
|
||||
*
|
||||
* 推荐使用 1-2 个字符的简短标识符。
|
||||
*/
|
||||
const char *name;
|
||||
const char *symbol;
|
||||
wm_layout_fn fn;
|
||||
} 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_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,
|
||||
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_id_t id);
|
||||
const wm_layout_slot_t *wm_layout_slot_get(const wm_layout_registry_t *registry,
|
||||
wm_layout_id_t id);
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "wm_event.h"
|
||||
#include "wm_layout.h"
|
||||
#include "wm_plan.h"
|
||||
#include "wm_policy_config.h"
|
||||
#include "wm_state.h"
|
||||
|
||||
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);
|
||||
|
||||
bool wm_policy_apply_command(wm_state_t *state,
|
||||
const wm_policy_config_t *policy,
|
||||
const wm_layout_registry_t *layouts,
|
||||
const wm_command_t *command, wm_plan_t *plan);
|
||||
|
||||
@@ -25,7 +25,8 @@ typedef struct wm_interaction_state_t {
|
||||
|
||||
typedef struct wm_runtime_bootstrap_t {
|
||||
/*
|
||||
* 输出配置(启动时扫描显示器后填充)
|
||||
* 输出配置(启动时扫描显示器后填充)。
|
||||
* 这是 bootstrap 后固定的 output 集合。
|
||||
*/
|
||||
const wm_output_t *outputs;
|
||||
size_t output_count;
|
||||
@@ -33,16 +34,19 @@ typedef struct wm_runtime_bootstrap_t {
|
||||
/*
|
||||
* 工作区配置(已绑定 output)
|
||||
* workspace.output_id 指向 outputs 中的某个输出
|
||||
* 这是 bootstrap 后固定的 workspace 集合。
|
||||
*/
|
||||
const wm_workspace_t *workspaces;
|
||||
size_t workspace_count;
|
||||
|
||||
/*
|
||||
* 布局算法注册
|
||||
* 布局算法注册。
|
||||
* layout registry 在 bootstrap 后固定;运行时只切换 workspace.layout_id。
|
||||
*/
|
||||
struct {
|
||||
wm_layout_fn fn;
|
||||
const char *name;
|
||||
const char *symbol;
|
||||
} *layouts;
|
||||
size_t layout_count;
|
||||
|
||||
@@ -67,7 +71,7 @@ typedef struct wm_runtime_bootstrap_t {
|
||||
size_t initial_command_count;
|
||||
} wm_runtime_bootstrap_t;
|
||||
|
||||
// 运行时上下文(实现时采用不透明结构体,此定义仅作参考)
|
||||
// 当前草案直接展示 runtime 结构,便于讨论;实现阶段可再封装
|
||||
typedef struct wm_runtime_t {
|
||||
bool running;
|
||||
|
||||
@@ -88,8 +92,8 @@ bool wm_runtime_register_service(wm_runtime_t *runtime, wm_service_t service);
|
||||
|
||||
/*
|
||||
* 处理单个事件,方便做无平台依赖的单元测试。
|
||||
* 元数据事件允许 runtime 直接更新 auxiliary store;控制状态变更仍经由
|
||||
* route_event() + apply_command()。
|
||||
* 元数据事件允许 runtime 直接更新 wm_window_t 中的元数据字段;
|
||||
* 控制状态变更仍经由 route_event() + apply_command()。
|
||||
*/
|
||||
bool wm_runtime_process_event(wm_runtime_t *runtime, const wm_event_t *event);
|
||||
|
||||
|
||||
@@ -1,17 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "wm_state.h"
|
||||
|
||||
typedef enum wm_service_event_type_t {
|
||||
WM_SERVICE_EVENT_NONE,
|
||||
WM_SERVICE_EVENT_RUNTIME_STARTED,
|
||||
WM_SERVICE_EVENT_RUNTIME_STOPPING,
|
||||
WM_SERVICE_EVENT_WINDOW_METADATA_CHANGED,
|
||||
WM_SERVICE_EVENT_STATUS_TICK,
|
||||
WM_SERVICE_EVENT_RENDER_OUTPUT,
|
||||
} wm_service_event_type_t;
|
||||
|
||||
typedef struct wm_service_event_t {
|
||||
wm_service_event_type_t type;
|
||||
union {
|
||||
struct {
|
||||
wm_window_id_t window_id;
|
||||
uint32_t changed_fields;
|
||||
} window_metadata_changed;
|
||||
|
||||
struct {
|
||||
wm_output_id_t output_id;
|
||||
} 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,
|
||||
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);
|
||||
|
||||
@@ -26,6 +26,9 @@ typedef struct wm_window_t {
|
||||
char *app_id;
|
||||
char *class_name;
|
||||
char *instance_name;
|
||||
|
||||
// 任务栏可见性提示
|
||||
bool skip_taskbar;
|
||||
} wm_window_t;
|
||||
|
||||
typedef struct wm_workspace_t {
|
||||
@@ -34,8 +37,8 @@ typedef struct wm_workspace_t {
|
||||
wm_layout_id_t layout_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;
|
||||
|
||||
// 名称(核心算法不依赖,仅用于状态栏显示)
|
||||
@@ -44,23 +47,20 @@ typedef struct wm_workspace_t {
|
||||
|
||||
typedef struct wm_output_t {
|
||||
wm_output_id_t id;
|
||||
bool enabled;
|
||||
wm_rect_t geometry; // 输出完整几何
|
||||
wm_rect_t workarea; // 可用区域(排除面板等)
|
||||
wm_rect_t geometry; // 输出完整几何(bootstrap 后固定)
|
||||
wm_rect_t workarea; // 可用区域(排除面板等,bootstrap 后固定)
|
||||
wm_workspace_id_t current_workspace_id;
|
||||
} wm_output_t;
|
||||
|
||||
// ========== 全局状态容器 ==========
|
||||
|
||||
typedef struct wm_state_t {
|
||||
// 内部实现(不透明结构体,实现文件中定义)
|
||||
// 头文件级草案当前直接公开字段,便于讨论;实现阶段可再封装
|
||||
wm_workspace_t *workspaces;
|
||||
size_t workspace_count;
|
||||
size_t workspace_capacity;
|
||||
|
||||
wm_output_t *outputs;
|
||||
size_t output_count;
|
||||
size_t output_capacity;
|
||||
|
||||
wm_window_t *windows;
|
||||
size_t window_count;
|
||||
@@ -78,6 +78,7 @@ typedef struct wm_state_t {
|
||||
// ========== API 函数 ==========
|
||||
|
||||
// 初始化/清理
|
||||
// workspace/output 集合大小在 init 时一次性确定,之后不再变化。
|
||||
void wm_state_init(wm_state_t *state,
|
||||
size_t workspace_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);
|
||||
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);
|
||||
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,
|
||||
wm_output_id_t output_id,
|
||||
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 的可用布局列表。
|
||||
* 仅允许在 bootstrap / 初始化阶段调用;运行时不应修改。
|
||||
*/
|
||||
void wm_workspace_set_layouts(wm_workspace_t *workspace,
|
||||
const wm_layout_id_t *layouts,
|
||||
size_t count);
|
||||
|
||||
/*
|
||||
* 获取 workspace 的可用布局列表
|
||||
* 获取 workspace 的可用布局列表。
|
||||
* 该列表在 bootstrap 完成后保持不变。
|
||||
*/
|
||||
const wm_layout_id_t *wm_workspace_get_layouts(const wm_workspace_t *workspace,
|
||||
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);
|
||||
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);
|
||||
/*
|
||||
* 切换某个 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 操作 ==========
|
||||
|
||||
@@ -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);
|
||||
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);
|
||||
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:
|
||||
* 1. 窗口的 workspace 固定归属某个 output
|
||||
* 2. 该 output 当前正显示这个 workspace
|
||||
* 3. 窗口未最小化
|
||||
* 1. 若窗口是 sticky,则只要求窗口未最小化
|
||||
* 2. 否则,窗口的 workspace 固定归属某个 output
|
||||
* 3. 且该 output 当前正显示这个 workspace
|
||||
*/
|
||||
bool wm_state_window_should_be_visible(const wm_state_t *state,
|
||||
const wm_window_t *window);
|
||||
|
||||
@@ -11,23 +11,67 @@ typedef uint32_t wm_window_id_t;
|
||||
typedef uint16_t wm_workspace_id_t;
|
||||
typedef uint16_t wm_output_id_t;
|
||||
typedef uint32_t wm_layout_id_t;
|
||||
/*
|
||||
* 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 标记
|
||||
#define WM_WINDOW_ID_INVALID ((wm_window_id_t)0)
|
||||
#define WM_WORKSPACE_ID_INVALID ((wm_workspace_id_t)-1)
|
||||
#define WM_OUTPUT_ID_INVALID ((wm_output_id_t)-1)
|
||||
#define WM_LAYOUT_ID_INVALID ((wm_layout_id_t)-1)
|
||||
#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 {
|
||||
int16_t x;
|
||||
int16_t y;
|
||||
int32_t x;
|
||||
int32_t y;
|
||||
} wm_point_t;
|
||||
|
||||
typedef struct wm_rect_t {
|
||||
int16_t x;
|
||||
int16_t y;
|
||||
uint16_t w;
|
||||
uint16_t h;
|
||||
int32_t x;
|
||||
int32_t y;
|
||||
int32_t width;
|
||||
int32_t height;
|
||||
} wm_rect_t;
|
||||
|
||||
typedef enum wm_window_geometry_mode_t {
|
||||
|
||||
63
src/core/event.h
Normal file
63
src/core/event.h
Normal 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;
|
||||
Reference in New Issue
Block a user