diff --git a/docs/min_core_draft/README.org b/docs/min_core_draft/README.org index 8b00f0e..f6142b6 100644 --- a/docs/min_core_draft/README.org +++ b/docs/min_core_draft/README.org @@ -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= 强类型语义命令。 diff --git a/docs/min_core_draft/WM_COMMAND_RULES.org b/docs/min_core_draft/WM_COMMAND_RULES.org index 483ac8a..3a24f8f 100644 --- a/docs/min_core_draft/WM_COMMAND_RULES.org +++ b/docs/min_core_draft/WM_COMMAND_RULES.org @@ -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= diff --git a/docs/min_core_draft/WM_EVENT_ROUTING.org b/docs/min_core_draft/WM_EVENT_ROUTING.org index 99f2128..7f31f6e 100644 --- a/docs/min_core_draft/WM_EVENT_ROUTING.org +++ b/docs/min_core_draft/WM_EVENT_ROUTING.org @@ -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,23 +301,21 @@ 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 - - runtime->interaction.pointer_origin.x, - .delta_y = event->as.pointer_motion.root.y - - runtime->interaction.pointer_origin.y + .dx = event->data.pointer_motion.root.x - + runtime->interaction.pointer_origin.x, + .dy = event->data.pointer_motion.root.y - + runtime->interaction.pointer_origin.y } }; return wm_command_buffer_push(out, move_cmd); @@ -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); - // 2. 处理事件 - wm_runtime_process_event(runtime, &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; + } + + runtime->backend.api->apply_effect(&runtime->backend, effect); } - // 3.4 刷新平台 - wm_backend_flush(&runtime->backend); + // 3.3 刷新平台 + if (runtime->backend.api->flush) { + runtime->backend.api->flush(&runtime->backend); + } - // 3.5 重置 plan + // 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` 触发重启 * 缺失的文档总结 diff --git a/docs/min_core_draft/WM_POLICY_APPLY_COMMAND_SKELETON.org b/docs/min_core_draft/WM_POLICY_APPLY_COMMAND_SKELETON.org index 9dd92e3..729812e 100644 --- a/docs/min_core_draft/WM_POLICY_APPLY_COMMAND_SKELETON.org +++ b/docs/min_core_draft/WM_POLICY_APPLY_COMMAND_SKELETON.org @@ -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_*()= 返回后,建议由上层统一做这些事,而不是在每个分支里直接跑布局: diff --git a/docs/min_core_draft/WORKSPACE_CONFIG_EXAMPLES.org b/docs/min_core_draft/WORKSPACE_CONFIG_EXAMPLES.org index 5819fef..af2b01e 100644 --- a/docs/min_core_draft/WORKSPACE_CONFIG_EXAMPLES.org +++ b/docs/min_core_draft/WORKSPACE_CONFIG_EXAMPLES.org @@ -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= 命令在可用布局集合内切换当前布局 diff --git a/docs/min_core_draft/wm_backend.h b/docs/min_core_draft/wm_backend.h index 29c0c4c..95e6f27 100644 --- a/docs/min_core_draft/wm_backend.h +++ b/docs/min_core_draft/wm_backend.h @@ -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. diff --git a/docs/min_core_draft/wm_command.h b/docs/min_core_draft/wm_command.h index 3d83b92..cdac935 100644 --- a/docs/min_core_draft/wm_command.h +++ b/docs/min_core_draft/wm_command.h @@ -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; diff --git a/docs/min_core_draft/wm_event.h b/docs/min_core_draft/wm_event.h index b7d710b..dbb0804 100644 --- a/docs/min_core_draft/wm_event.h +++ b/docs/min_core_draft/wm_event.h @@ -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; diff --git a/docs/min_core_draft/wm_layout.h b/docs/min_core_draft/wm_layout.h index 082d30e..6e6f468 100644 --- a/docs/min_core_draft/wm_layout.h +++ b/docs/min_core_draft/wm_layout.h @@ -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_fn fn); +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); diff --git a/docs/min_core_draft/wm_policy.h b/docs/min_core_draft/wm_policy.h index b8d5e6a..d68f3c7 100644 --- a/docs/min_core_draft/wm_policy.h +++ b/docs/min_core_draft/wm_policy.h @@ -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); diff --git a/docs/min_core_draft/wm_runtime.h b/docs/min_core_draft/wm_runtime.h index 15461ef..a900296 100644 --- a/docs/min_core_draft/wm_runtime.h +++ b/docs/min_core_draft/wm_runtime.h @@ -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); diff --git a/docs/min_core_draft/wm_service.h b/docs/min_core_draft/wm_service.h index 73979d7..8aa3256 100644 --- a/docs/min_core_draft/wm_service.h +++ b/docs/min_core_draft/wm_service.h @@ -1,17 +1,26 @@ #pragma once +#include + #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); diff --git a/docs/min_core_draft/wm_state.h b/docs/min_core_draft/wm_state.h index e09f386..e22683f 100644 --- a/docs/min_core_draft/wm_state.h +++ b/docs/min_core_draft/wm_state.h @@ -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); diff --git a/docs/min_core_draft/wm_types.h b/docs/min_core_draft/wm_types.h index 0ea3c49..de41cdc 100644 --- a/docs/min_core_draft/wm_types.h +++ b/docs/min_core_draft/wm_types.h @@ -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 { diff --git a/src/core/event.h b/src/core/event.h new file mode 100644 index 0000000..9db237d --- /dev/null +++ b/src/core/event.h @@ -0,0 +1,63 @@ +#pragma once + +#include +#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;