diff --git a/docs/min_core_draft/README.org b/docs/min_core_draft/README.org index caba726..39999d1 100644 --- a/docs/min_core_draft/README.org +++ b/docs/min_core_draft/README.org @@ -26,7 +26,7 @@ 负责把事件路由为命令,并把命令应用到状态。 5. =layout= -负责为某个输出上当前显示的工作区中的平铺窗口计算几何。 +负责为某个输出上当前显示的工作区中的平铺窗口计算外框几何。 * ID 分配策略 @@ -91,7 +91,8 @@ wm_window_t *win = wm_state_find_window(state, window_id); // O(n) 这些服务故意不放进最小核心: 1. =render= -状态栏、边框、标题、装饰绘制。 +状态栏、标题和其他装饰绘制。窗口边框不是独立服务,而是 +=WM_EFFECT_CONFIGURE_WINDOW= 的一部分。 2. =status= 网速、内存、CPU、音量、时间等信息采集与聚合。 @@ -107,7 +108,7 @@ wm_window_t *win = wm_state_find_window(state, window_id); // O(n) 为了避免再次出现当前全局 =wm= 那样的大对象耦合,最小核心里的所有权再收紧一层: 1. =runtime= 拥有 =state= 、=plan= 、=command_buffer= 和 =layout_registry= 。 -2. =runtime= 也拥有 =policy config= 、交互态和服务注册表。 +2. =runtime= 也拥有 =policy config= 、全局边框配置、交互态和服务注册表。 3. =backend= 只负责平台事件翻译和平台副作用,不拥有控制状态,不直接拼 bar。 4. 窗口元数据(title/app_id/class/instance)直接存储在 =wm_window_t= 中,与控制状态一同管理。 5. 工作区名称存储在 =wm_workspace_t= 中,与控制状态一同管理。 @@ -120,6 +121,7 @@ wm_window_t *win = wm_state_find_window(state, window_id); // O(n) - 工作区数量和初始配置(由用户配置决定) - 布局算法注册(由代码或配置决定) - 策略配置(focus_raises 等) + - 全局统一边框宽度 - 初始命令(如启动时扫描到的窗口) 2. 启动时扫描到的已有窗口,不直接塞进 =wm_state_t= ,而是翻译成一批 =MANAGE_WINDOW= 命令进入 runtime。 @@ -214,6 +216,17 @@ sticky 窗口是否参与方向焦点搜索。 5. =minimize_clears_focus= 最小化当前焦点窗口时是否清空或重选该 workspace 焦点。 +* 边框约定 + +当前最小核心只保留窗口边框,不支持标题栏和其他窗口装饰。 + +1. =wm_window_t= 不存储 =border_width= ;边框宽度不是窗口真状态。 +2. =wm_runtime_bootstrap_t.border_width= 提供全局统一边框宽度。 +3. =float_rect= 和 =frame_rect= 都表示“包含边框后的外框矩形”。 +4. =runtime= 在提交 =WM_EFFECT_CONFIGURE_WINDOW= 时解析有效边框宽度: + - =WM_GEOMETRY_FULLSCREEN= : 边框宽度为 0 + - 其余模式 : 使用全局边框宽度 + * 交互态 move/resize 这类拖拽交互不属于 =wm_state_t= 真状态,但也不能继续散落在平台分支里。runtime 应维护一个单一交互会话: @@ -238,6 +251,7 @@ move/resize 这类拖拽交互不属于 =wm_state_t= 真状态,但也不能继 =config= 仍然在核心外,但它的输出必须改成 core-native 数据,而不是旧的 =client_t/tag_t= 钩子: 1. 配置加载结果应产出 workspace 描述、policy 配置、layout 注册、规则表、快捷键到 command 的映射和服务设置。 + 以及全局统一边框宽度。 2. 配置层不得直接持有 =client_t= 、=tag_t= 或直接修改 =wm_state_t= 。 3. 配置重载的边界是“重建 bootstrap 和服务配置,再显式应用到 runtime”,而不是任意时刻从外部写内存。 @@ -273,6 +287,10 @@ move/resize 这类拖拽交互不属于 =wm_state_t= 真状态,但也不能继 workspace 的 =layout_id= 字段指向当前活动的布局(列表中的一个)。 使用 =CYCLE_LAYOUT= 命令在可用布局间循环切换。 +9. =window rectangles= +=frame_rect= 是窗口当前最终外框矩形;=float_rect= 是 floating 模式下记忆的外框矩形。 +这两个矩形都包含边框。 + * 核心不变量 1. 每个窗口必须且只能属于一个工作区。 @@ -289,6 +307,8 @@ workspace 的 =layout_id= 字段指向当前活动的布局(列表中的一个 12. 窗口标题、类名等元数据存储在 =wm_window_t= 中,与控制状态一同管理。 13. 工作区名称存储在 =wm_workspace_t= 中,核心算法不应依赖此字段。 14. 每个 workspace 的可用布局列表在启动时分配,运行时固定,只能通过 =CYCLE_LAYOUT= 或 =SET_LAYOUT= 命令切换当前活动布局。 +15. =frame_rect= 和 =float_rect= 都表示包含边框后的外框矩形。 +16. =border_width= 不是 =wm_window_t= 真状态;它由 runtime 从全局配置和几何模式推导。 * Generation 字段 @@ -309,7 +329,7 @@ workspace 的 =layout_id= 字段指向当前活动的布局(列表中的一个 2. runtime 先处理窗口元数据更新这类辅助事件,并直接更新 =wm_window_t= 中的元数据字段 3. =policy.route_event()= 4. =policy.apply_command()= -5. 根据 =dirty_flags= 决定是否重新运行 =layout= 并解析最终矩形 +5. 根据 =dirty_flags= 决定是否重新运行 =layout= 、解析最终外框矩形,并补全 =configure= effect 的边框宽度 6. 将平台 effect 发送给 =backend= 7. 将 render 失效通知发送给服务层 8. =backend.flush()= diff --git a/docs/min_core_draft/WM_COMMAND_RULES.org b/docs/min_core_draft/WM_COMMAND_RULES.org index 7691c09..734292d 100644 --- a/docs/min_core_draft/WM_COMMAND_RULES.org +++ b/docs/min_core_draft/WM_COMMAND_RULES.org @@ -14,6 +14,9 @@ =floating = false= 时,基础几何来自 layout 结果。 =floating = true= 时,基础几何来自 =float_rect= 。 8. 文档里出现的可选行为,应优先固化到 =wm_policy_config_t= ,而不是继续留在 prose 里。 +9. =frame_rect= 和 =float_rect= 都表示包含边框后的外框矩形。 +10. =border_width= 不是 =wm_window_t= 真状态;runtime 在提交 + =WM_EFFECT_CONFIGURE_WINDOW= 时按全局配置解析它。 * 管理类命令 @@ -216,7 +219,7 @@ 1. 若原来 =floating == false= : 将 =floating= 设为 =true= 。 - 将当前 =frame_rect= 复制到 =float_rect= ,作为浮动基准矩形。 + 将当前 =frame_rect= 复制到 =float_rect= ,作为浮动基准外框矩形。 2. 若原来 =floating == true= : 将 =floating= 设为 =false= 。 保留 =float_rect= ,供未来再次进入 floating 时恢复。 @@ -257,6 +260,7 @@ 5. 不改变 =floating= 。 6. 若窗口原本是 floating,退出 maximize 后回到 =float_rect= 。 7. 若窗口原本不是 floating,退出 maximize 后重新参与 layout。 +8. maximize 不改变全局边框策略;有效边框宽度仍由 runtime 统一解析。 副作用: @@ -273,6 +277,7 @@ 设为 =WM_GEOMETRY_FULLSCREEN= 。 3. 不改变 =floating= 。 4. fullscreen 只决定几何占用 =output.geometry= ,不自动改变 =stack_order[]= 。 +5. fullscreen 的有效边框宽度由 runtime 解析为 0 。 副作用: @@ -314,6 +319,7 @@ 依据 =anchor= 找到目标输出。 再按 =policy= 决定是否修改 =workspace_id= 。 4. 不改变 =geometry_mode= 。 +5. =float_rect= 是外框矩形,因此移动的是包含边框后的最终窗口外框。 副作用: @@ -333,6 +339,17 @@ 2. 应对宽高做最小值约束,避免得到零或负值。 3. 不改变 =workspace_id= 。 4. 不改变 =geometry_mode= 。 +5. =float_rect= 是外框矩形,因此缩放的是包含边框后的最终窗口外框。 + +* 边框约定 + +1. 当前最小核心只支持全局统一边框,不支持每窗口边框宽度。 +2. 全局边框宽度由 =wm_runtime_bootstrap_t.border_width= 提供。 +3. =layout= 和 =floating= 都产出包含边框后的外框矩形。 +4. =WM_EFFECT_CONFIGURE_WINDOW.rect= 也是外框矩形。 +5. runtime 在把最终矩形转成 =configure= effect 时解析有效边框宽度: + =WM_GEOMETRY_FULLSCREEN= -> 0 + 其余模式 -> 全局边框宽度 副作用: @@ -406,6 +423,8 @@ bool wm_state_window_should_be_visible_on_output(const wm_state_t *state, 窗口最终矩形建议按下面的优先级解析: +这里的“最终矩形”统一指包含边框后的外框矩形。 + 1. =WM_GEOMETRY_MINIMIZED= 不显示。 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 e8a1c89..aa303fa 100644 --- a/docs/min_core_draft/WM_POLICY_APPLY_COMMAND_SKELETON.org +++ b/docs/min_core_draft/WM_POLICY_APPLY_COMMAND_SKELETON.org @@ -11,6 +11,13 @@ 3. 向 =wm_plan_t= 记录脏标记和后端副作用。 4. 在状态真的发生变化时递增 =state.generation= 。 +补充约定: + +1. =border_width= 不是 =wm_window_t= 真状态。 +2. =apply_command()= 只负责更新 =frame_rect= / =float_rect= 和脏标记。 +3. runtime 在布局求值和 effect 提交阶段,根据全局边框配置补全 + =WM_EFFECT_CONFIGURE_WINDOW.border_width= 。 + 它不负责: 1. 从平台读取事件。 diff --git a/docs/min_core_draft/wm_layout.h b/docs/min_core_draft/wm_layout.h index 5975716..082d30e 100644 --- a/docs/min_core_draft/wm_layout.h +++ b/docs/min_core_draft/wm_layout.h @@ -18,7 +18,7 @@ typedef struct wm_layout_ctx_t { typedef struct wm_layout_item_t { wm_window_id_t window_id; - wm_rect_t rect; + wm_rect_t rect; // 平铺窗口的目标外框矩形(包含边框) } wm_layout_item_t; typedef struct wm_layout_result_t { diff --git a/docs/min_core_draft/wm_plan.h b/docs/min_core_draft/wm_plan.h index 114c3eb..00d94d8 100644 --- a/docs/min_core_draft/wm_plan.h +++ b/docs/min_core_draft/wm_plan.h @@ -34,8 +34,8 @@ typedef struct wm_effect_t { struct { wm_window_id_t window_id; - wm_rect_t rect; - uint16_t border_width; + wm_rect_t rect; // 最终外框矩形(包含边框) + uint16_t border_width; // runtime 解析出的有效边框宽度 } configure_window; struct { diff --git a/docs/min_core_draft/wm_runtime.h b/docs/min_core_draft/wm_runtime.h index a7a6b0e..15461ef 100644 --- a/docs/min_core_draft/wm_runtime.h +++ b/docs/min_core_draft/wm_runtime.h @@ -7,6 +7,7 @@ #include "wm_policy.h" #include "wm_service.h" #include "wm_state.h" +#include typedef enum wm_interaction_mode_t { WM_INTERACTION_NONE, @@ -50,6 +51,14 @@ typedef struct wm_runtime_bootstrap_t { */ wm_policy_config_t policy; + /* + * 全局统一边框宽度。 + * 它不是窗口真状态。runtime 在提交 configure effect 时: + * - fullscreen -> 0 + * - 其余模式 -> border_width + */ + uint16_t border_width; + /* * Existing windows discovered during startup should be translated into these * commands instead of being injected into wm_state_t directly. @@ -67,6 +76,7 @@ typedef struct wm_runtime_t { wm_command_buffer_t command_buffer; wm_layout_registry_t layouts; wm_policy_config_t policy; + uint16_t border_width; wm_interaction_state_t interaction; wm_backend_t backend; wm_service_registry_t services; diff --git a/docs/min_core_draft/wm_state.h b/docs/min_core_draft/wm_state.h index 620e8fd..e09f386 100644 --- a/docs/min_core_draft/wm_state.h +++ b/docs/min_core_draft/wm_state.h @@ -17,9 +17,9 @@ typedef struct wm_window_t { bool sticky; bool urgent; - // 几何信息 - wm_rect_t float_rect; // floating 窗口的矩形 - wm_rect_t frame_rect; // 最终应用的矩形(由 layout 计算或 float_rect 决定) + // 几何信息(均为包含边框后的外框矩形) + wm_rect_t float_rect; // floating 模式下记忆的外框矩形 + wm_rect_t frame_rect; // 当前最终外框矩形(由 layout 或 float_rect 解析) // 元数据(核心算法不依赖,仅用于规则匹配和服务层展示) char *title; diff --git a/src/core/layout.h b/src/core/layout.h new file mode 100644 index 0000000..85984db --- /dev/null +++ b/src/core/layout.h @@ -0,0 +1,105 @@ +#pragma once + +#include + +#include "core/types.h" + +typedef struct wm_layout_window_ref_t { + wm_window_id_t window_id; + wm_window_geometry_mode_t geometry_mode; +} wm_layout_window_ref_t; + +typedef struct wm_layout_ctx_t { + wm_output_id_t output_id; + wm_workspace_id_t workspace_id; + wm_window_id_t focused_window_id; + wm_rect_t workarea; + const wm_layout_window_ref_t *windows; + size_t window_count; +} wm_layout_ctx_t; + +typedef struct wm_layout_item_t { + wm_window_id_t window_id; + wm_rect_t rect; /* 平铺窗口的目标外框矩形(包含边框) */ +} wm_layout_item_t; + +typedef struct wm_layout_result_t { + wm_layout_item_t *items; + size_t item_count; + size_t item_capacity; +} wm_layout_result_t; + +/* + * 返回 true 表示成功生成合法的布局结果。 + * 返回 false 表示布局计算失败,调用方应丢弃本次结果。 + * + * 该返回值不表示窗口最终几何是否发生变化; + * 是否有改动应由调用方在比较当前状态与布局结果后决定。 + */ +typedef bool (*wm_layout_fn)(const wm_layout_ctx_t *ctx, + wm_layout_result_t *out); + +typedef struct wm_layout_slot_t { + wm_layout_id_t id; + /* + * 布局名称,用于: + * - 配置引用 + * - 日志输出 + * - 调试展示 + * + * 推荐使用稳定、可读的名称,如 "tile"、"monocle"、"floating"。 + */ + const char *name; + /* + * 面向用户的布局说明。 + * + * 用于帮助信息、调试输出和交互式布局选择;可为空。 + */ + const char *description; + /* + * 布局符号,用于状态栏显示等紧凑场景。 + * + * 推荐使用 1-2 个字符的简短标识符。 + */ + const char *symbol; + /* + * 布局执行函数。 + * + * fn == NULL 表示 floating 布局,即该布局不参与平铺计算。 + */ + wm_layout_fn fn; +} wm_layout_slot_t; + +typedef struct wm_layout_registry_t { + wm_layout_slot_t *slots; + size_t slot_count; + size_t slot_capacity; +} wm_layout_registry_t; + +void wm_layout_result_init(wm_layout_result_t *result); +void wm_layout_result_reset(wm_layout_result_t *result); +void wm_layout_result_shutdown(wm_layout_result_t *result); + +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); + +wm_layout_id_t wm_layout_register(wm_layout_registry_t *registry, + const char *name, const char *symbol, + const char *description, wm_layout_fn fn); +/* + * 获取可执行的布局函数。 + * + * 返回 NULL 的情况包括: + * - id 无效 + * - id 对应的 slot 不存在 + * - slot 存在,但 fn == NULL(表示 floating 布局) + */ +wm_layout_fn wm_layout_get(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/src/core/runtime.h b/src/core/runtime.h new file mode 100644 index 0000000..c969d22 --- /dev/null +++ b/src/core/runtime.h @@ -0,0 +1,11 @@ +#include "core/state.h" + +typedef struct wm_runtime_t { + bool running; + bool will_restart; + + wm_state_t state; +} wm_runtime_t; + +bool wm_runtime_init(wm_runtime_t *runtime); +void wm_runtime_shutdown(wm_runtime_t *runtime); diff --git a/src/core/state.h b/src/core/state.h new file mode 100644 index 0000000..e22e27a --- /dev/null +++ b/src/core/state.h @@ -0,0 +1,72 @@ +#pragma once + +#include + +#include "core/types.h" + +typedef struct wm_window_t { + wm_window_id_t id; + wm_workspace_id_t workspace_id; + + wm_window_geometry_mode_t geometry_mode; + bool floating; + bool sticky; + bool urgent; + bool fixed_size; + + /* 几何信息(均为包含边框后的外框矩形) */ + wm_rect_t float_rect; /* floating 模式下记忆的外框矩形 */ + wm_rect_t frame_rect; /* 当前外框矩形(由 layout 或 float_rect 解析) */ + + /* 元数据(核心算法不依赖,仅用于规则匹配和信息展示) */ + char *title; + char *app_id; + char *class_name; + char *instance_name; + + bool skip_taskbar; +} wm_window_t; + +typedef struct wm_workspace_t { + wm_workspace_id_t id; + wm_output_id_t output_id; /* 固定归属某个输出 */ + wm_layout_id_t layout_id; /* 当前活动布局(可用布局列表中的一个) */ + wm_window_id_t focused_window_id; + + /* 可用布局列表(启动时根据配置分配,运行时固定) */ + wm_layout_id_t *available_layouts; + size_t layout_count; + + /* 名称(核心算法不依赖,仅用于状态栏显示) */ + char *name; +} wm_workspace_t; + +typedef struct wm_output_t { + wm_output_id_t id; + bool enabled; + wm_rect_t geometry; /* 输出完整几何 */ + wm_rect_t workarea; /* 可用区域(排除面板等) */ + 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; + size_t window_capacity; + + wm_window_id_t *stack_order; + size_t stack_count; + size_t stack_capacity; + + uint64_t generation; + bool initialized; +} wm_state_t; diff --git a/src/core/types.h b/src/core/types.h new file mode 100644 index 0000000..b61543e --- /dev/null +++ b/src/core/types.h @@ -0,0 +1,45 @@ +#pragma once + +#include + +typedef uint32_t wm_window_id_t; +typedef uint32_t wm_workspace_id_t; +typedef uint32_t wm_output_id_t; +typedef uint32_t wm_layout_id_t; + +/* clang-format off */ +/* 无效 ID 标记 */ +#define WM_WINDOW_ID_INVALID ((wm_window_id_t)0) +#define WM_WORKSPACE_ID_INVALID ((wm_workspace_id_t)UINT32_MAX) +#define WM_OUTPUT_ID_INVALID ((wm_output_id_t)UINT32_MAX) +#define WM_LAYOUT_ID_INVALID ((wm_layout_id_t)UINT32_MAX) +/* clang-format on */ + +typedef struct wm_point_t { + int32_t x; + int32_t y; +} wm_point_t; + +typedef struct wm_rect_t { + int32_t x; + int32_t y; + int32_t width; + int32_t height; +} wm_rect_t; + +typedef enum wm_window_geometry_mode_t { + WM_GEOMETRY_NORMAL, + WM_GEOMETRY_MAXIMIZED, + WM_GEOMETRY_FULLSCREEN, + WM_GEOMETRY_MINIMIZED, +} wm_window_geometry_mode_t; + +typedef enum wm_focus_direction_t { + WM_FOCUS_PREV, + WM_FOCUS_NEXT, +} wm_focus_direction_t; + +typedef enum wm_cross_output_policy_t { + WM_CROSS_OUTPUT_KEEP_WORKSPACE, + WM_CROSS_OUTPUT_MOVE_TO_TARGET_WORKSPACE, +} wm_cross_output_policy_t;