From f106857cd80e379aef6acf54ceb18c0eb4b181e6 Mon Sep 17 00:00:00 2001 From: Zedhugh Chen Date: Tue, 10 Mar 2026 06:37:23 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E7=9A=84=E6=9E=B6=E6=9E=84=E8=AE=BE?= =?UTF-8?q?=E8=AE=A1=E6=96=87=E6=A1=A3=E5=92=8C=E6=A0=B8=E5=BF=83=E7=B3=BB?= =?UTF-8?q?=E7=BB=9F=E6=95=B0=E6=8D=AE=E7=BB=93=E6=9E=84=E5=8F=8A=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3=E5=A3=B0=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/ARCHITECTURE_REFACTORING_PLAN.org | 1230 +++++++++++++++++ docs/config_system.md | 407 ++++++ docs/min_core_draft/README.org | 171 +++ docs/min_core_draft/WM_COMMAND_RULES.org | 342 +++++ .../WM_POLICY_APPLY_COMMAND_SKELETON.org | 550 ++++++++ docs/min_core_draft/wm_backend.h | 19 + docs/min_core_draft/wm_command.h | 108 ++ docs/min_core_draft/wm_event.h | 95 ++ docs/min_core_draft/wm_layout.h | 58 + docs/min_core_draft/wm_plan.h | 67 + docs/min_core_draft/wm_policy.h | 26 + docs/min_core_draft/wm_runtime.h | 28 + docs/min_core_draft/wm_state.h | 103 ++ docs/min_core_draft/wm_types.h | 39 + docs/min_core_draft/wm_window_meta.h | 29 + docs/min_core_draft/wm_workspace_desc.h | 37 + 16 files changed, 3309 insertions(+) create mode 100644 docs/ARCHITECTURE_REFACTORING_PLAN.org create mode 100644 docs/config_system.md create mode 100644 docs/min_core_draft/README.org create mode 100644 docs/min_core_draft/WM_COMMAND_RULES.org create mode 100644 docs/min_core_draft/WM_POLICY_APPLY_COMMAND_SKELETON.org create mode 100644 docs/min_core_draft/wm_backend.h create mode 100644 docs/min_core_draft/wm_command.h create mode 100644 docs/min_core_draft/wm_event.h create mode 100644 docs/min_core_draft/wm_layout.h create mode 100644 docs/min_core_draft/wm_plan.h create mode 100644 docs/min_core_draft/wm_policy.h create mode 100644 docs/min_core_draft/wm_runtime.h create mode 100644 docs/min_core_draft/wm_state.h create mode 100644 docs/min_core_draft/wm_types.h create mode 100644 docs/min_core_draft/wm_window_meta.h create mode 100644 docs/min_core_draft/wm_workspace_desc.h diff --git a/docs/ARCHITECTURE_REFACTORING_PLAN.org b/docs/ARCHITECTURE_REFACTORING_PLAN.org new file mode 100644 index 0000000..ce3a7b4 --- /dev/null +++ b/docs/ARCHITECTURE_REFACTORING_PLAN.org @@ -0,0 +1,1230 @@ +* ZDWM 窗口管理器完整抽象层设计方案 + +** 设计目标 + +创建一个清晰、实用、可落地的抽象层体系,解决当前项目的架构问题,提升可维护性和可扩展性,同时保持项目的简洁性和高性能。 + +** 当前架构优势保留 + +ZDWM 现有的优秀设计基础: +- ✅ 清晰的四层架构(UI/业务/数据/基础设施) +- ✅ 优秀的观察者模式(状态栏更新) +- ✅ 灵活的策略模式(布局系统) +- ✅ 高效的事件驱动模型 +- ✅ 良好的资源管理 + +** 核心抽象层设计 + +*** 第一层:基础设施抽象层 (Infrastructure Layer) + +**** 1.1 上下文管理器 +*问题*: 当前全局 =wm= 对象导致所有模块紧密耦合 + +*解决方案*: 引入上下文对象,实现依赖注入 + +#+BEGIN_SRC c +// src/core/context.h +// 前向声明 +typedef struct display_backend_t display_backend_t; + +typedef struct wm_context_t { + // 显示后端(X11 或 Wayland) + display_backend_t *display_backend; + + // 核心状态 + monitor_t *monitor_list; + client_t *client_list; + tag_t *tag_list; + + // 配置和状态 + config_t *config; + status_t *status; + + // 事件循环 + GMainContext *main_context; + + // 引用计数 + atomic_int ref_count; +} wm_context_t; + +// 上下文生命周期管理 +wm_context_t *wm_context_create(display_backend_type_t backend_type); +wm_context_t *wm_context_ref(wm_context_t *ctx); +void wm_context_unref(wm_context_t *ctx); + +// 获取全局上下文(向后兼容) +wm_context_t *wm_context_get_default(void); + +// 显示后端访问(向后兼容) +static inline xcb_connection_t* context_get_xcb_conn(wm_context_t *ctx) { + return ctx->display_backend ? + (xcb_connection_t*)ctx->display_backend->get_platform_handle() : NULL; +} +#+END_SRC + +*优势*: +- 支持多实例(测试环境) +- 清晰的生命周期管理 +- 便于依赖注入 +- 保持向后兼容 + +**** 1.2 日志系统 +*问题*: 当前 =logger()= 输出简单,=fatal()= 直接退出 + +*解决方案*: 分级日志系统 + +#+BEGIN_SRC c +// src/core/log.h +typedef enum log_level_t { + LOG_LEVEL_DEBUG = 0, + LOG_LEVEL_INFO, + LOG_LEVEL_WARN, + LOG_LEVEL_ERROR, + LOG_LEVEL_FATAL +} log_level_t; + +typedef struct log_handler_t { + void (*write)(log_level_t level, const char *file, int line, + const char *fmt, va_list args); + log_level_t min_level; + log_handler_t *next; +} log_handler_t; + +// 日志API +void log_init(log_level_t default_level); +void log_cleanup(void); +void log_add_handler(log_handler_t *handler); +void log_remove_handler(log_handler_t *handler); + +// 便捷宏 +#define LOG_DEBUG(fmt, ...) log_write(LOG_LEVEL_DEBUG, __FILE__, __LINE__, fmt, ##__VA_ARGS__) +#define LOG_INFO(fmt, ...) log_write(LOG_LEVEL_INFO, __FILE__, __LINE__, fmt, ##__VA_ARGS__) +#define LOG_WARN(fmt, ...) log_write(LOG_LEVEL_WARN, __FILE__, __LINE__, fmt, ##__VA_ARGS__) +#define LOG_ERROR(fmt, ...) log_write(LOG_LEVEL_ERROR, __FILE__, __LINE__, fmt, ##__VA_ARGS__) +#define LOG_FATAL(fmt, ...) log_write(LOG_LEVEL_FATAL, __FILE__, __LINE__, fmt, ##__VA_ARGS__) + +// 替代现有的 logger/fatal/warn +#define logger LOG_INFO +#define warn LOG_WARN +#define fatal LOG_FATAL +#+END_SRC + +**** 1.3 错误处理机制 +*问题*: GError 使用不当,错误信息丢失 + +*解决方案*: 统一错误处理接口 + +#+BEGIN_SRC c +// src/core/error.h +typedef enum error_code_t { + ERROR_NONE = 0, + ERROR_NOMEM, + ERROR_INVALID_ARG, + ERROR_XCB_FAILED, + ERROR_FILE_NOT_FOUND, + ERROR_PARSE_FAILED, + // ... 更多错误码 +} error_code_t; + +typedef struct error_t { + error_code_t code; + char message[256]; + const char *file; + int line; +} error_t; + +// 错误处理API +void error_set(error_t **err, error_code_t code, const char *fmt, ...); +void error_clear(error_t **err); +const char *error_get_message(const error_t *err); + +// 错误传播宏 +#define CHECK_ERROR(cond, err, code, fmt, ...) \ + if (!(cond)) { \ + error_set(err, code, fmt, ##__VA_ARGS__); \ + return false; \ + } +#+END_SRC + +*** 第二层:数据抽象层 (Data Layer) + +**** 2.1 状态管理器 +*问题*: 状态散落在多个模块,难以同步 + +*解决方案*: 统一状态管理接口 + +#+BEGIN_SRC c +// src/core/state.h +typedef enum state_type_t { + STATE_MONITOR, + STATE_CLIENT, + STATE_TAG, + STATUS_LAYOUT, + STATE_CONFIG +} state_type_t; + +typedef struct state_change_t { + state_type_t type; + void *old_state; + void *new_state; +} state_change_t; + +typedef void (*state_change_callback)(const state_change_t *change, void *user_data); + +typedef struct state_manager_t { + // 状态变更监听器 + state_change_callback *listeners; + void **listener_data; + size_t listener_count; + + // 状态存储 + hash_table_t *states; +} state_manager_t; + +// 状态管理API +state_manager_t *state_manager_create(void); +void state_manager_destroy(state_manager_t *mgr); + +void state_manager_notify(state_manager_t *mgr, const state_change_t *change); +void state_manager_subscribe(state_manager_t *mgr, + state_change_callback callback, + void *user_data); + +// 状态访问 +void *state_get(state_manager_t *mgr, state_type_t type); +void state_set(state_manager_t *mgr, state_type_t type, void *data); +#+END_SRC + +**** 2.2 配置抽象层 +*问题*: 配置系统不统一,缺少热重载 + +*解决方案*: 分层配置系统 + +#+BEGIN_SRC c +// src/config/config_layer.h +typedef enum config_source_t { + CONFIG_SOURCE_DEFAULT, // 编译时默认值 + CONFIG_SOURCE_FILE, // 配置文件 + CONFIG_SOURCE_XRESOURCE, // X资源数据库 + CONFIG_SOURCE_RUNTIME, // 运行时API(最高优先级) +} config_source_t; + +typedef struct config_value_t { + config_source_t source; + union { + bool boolean; + int integer; + char *string; + void *pointer; + } value; +} config_value_t; + +typedef struct config_layer_t { + hash_table_t *values; // key -> config_value_t + config_source_t source; + bool readonly; +} config_layer_t; + +typedef struct config_stack_t { + config_layer_t **layers; + size_t layer_count; +} config_stack_t; + +// 配置API +config_stack_t *config_stack_create(void); +void config_stack_destroy(config_stack_t *stack); + +void config_stack_push_layer(config_stack_t *stack, config_source_t source); +void config_stack_pop_layer(config_stack_t *stack); + +// 配置访问(自动从最高优先级层查找) +bool config_get_bool(config_stack_t *stack, const char *key, bool *value); +bool config_get_int(config_stack_t *stack, const char *key, int *value); +bool config_get_string(config_stack_t *stack, const char *key, char **value); + +// 配置设置(总是设置到运行时层) +bool config_set_bool(config_stack_t *stack, const char *key, bool value); +bool config_set_int(config_stack_t *stack, const char *key, int value); +bool config_set_string(config_stack_t *stack, const char *key, const char *value); + +// 配置热重载 +bool config_reload_from_file(config_stack_t *stack, const char *filepath); +#+END_SRC + +*** 第三层:服务抽象层 (Service Layer) + +**** 3.1 显示后端抽象 +*问题*: XCB调用散落各处,难以测试,且无法支持 Wayland + +*解决方案*: 显示服务器无关的统一接口,支持 X11 和 Wayland + +#+BEGIN_SRC c +// src/services/display_backend.h + +// 显示后端类型 +typedef enum display_backend_type_t { + DISPLAY_BACKEND_AUTO, // 自动检测 + DISPLAY_BACKEND_X11, + DISPLAY_BACKEND_WAYLAND +} display_backend_type_t; + +// 统一的窗口表示(跨平台) +typedef struct window_t { + uint64_t id; // 平台无关的窗口ID + void *platform_handle; // XCB window or wl_surface +} window_t; + +// 几何信息 +typedef struct rect_t { + int32_t x, y; + uint32_t width, height; +} rect_t; + +// 输入事件 +typedef struct input_event_t { + enum { INPUT_KEY, INPUT_BUTTON, INPUT_MOTION, INPUT_AXIS } type; + uint32_t time; + uint32_t detail; // keycode or button number + uint32_t state; // modifier state + int32_t x, y; // cursor position + int32_t axis; // scroll axis delta +} input_event_t; + +// 渲染表面 +typedef struct render_surface_t { + void *handle; // cairo_surface_t* for X11, EGLSurface for Wayland + uint32_t width, height; +} render_surface_t; + +// 显示后端接口 +typedef struct display_backend_t { + const char *name; + const char *version; + + // 生命周期 + bool (*init)(wm_context_t *ctx); + void (*cleanup)(void); + + // 事件处理 + int (*get_fd)(void); // 获取事件文件描述符 + bool (*dispatch_event)(void); // 分发单个事件 + void (*flush)(void); // 刷新请求 + + // 屏幕信息 + bool (*get_screen_geometry)(size_t screen_index, rect_t *geometry); + size_t (*get_screen_count)(void); + + // 窗口操作 + window_t (*create_window)(const rect_t *geometry); + void (*destroy_window)(window_t window); + void (*map_window)(window_t window); + void (*unmap_window)(window_t window); + + // 窗口属性 + typedef enum { + WINDOW_PROPERTY_TITLE, + WINDOW_PROPERTY_CLASS, + WINDOW_PROPERTY_ROLE, + WINDOW_PROPERTY_PID, + WINDOW_PROPERTY_TYPE, + WINDOW_PROPERTY_TRANSIENT_FOR, + WINDOW_PROPERTY_SIZE_HINTS, + WINDOW_PROPERTY_MOTIF_HINTS, + WINDOW_PROPERTY_FULLSCREEN, + WINDOW_PROPERTY_OPACITY, + } window_property_t; + + bool (*get_window_property)(window_t window, window_property_t prop, + void *data, size_t *size); + bool (*set_window_property)(window_t window, window_property_t prop, + const void *data, size_t size); + + // 窗口几何 + bool (*configure_window)(window_t window, const rect_t *geometry); + bool (*get_window_geometry)(window_t window, rect_t *geometry); + void (*raise_window)(window_t window); + void (*focus_window)(window_t window); + + // 输入处理 + bool (*grab_keyboard)(window_t window); + bool (*grab_pointer)(window_t window); + void (*ungrab_keyboard)(void); + void (*ungrab_pointer)(void); + + // 渲染集成 + render_surface_t (*create_render_surface)(window_t window, + uint32_t width, uint32_t height); + void (*destroy_render_surface)(render_surface_t surface); + cairo_t* (*create_cairo_context)(render_surface_t surface); + + // Wayland 特定:窗口状态(用于 xdg-shell) + typedef enum { + STATE_FLAG_FULLSCREEN = (1 << 0), + STATE_FLAG_MAXIMIZED = (1 << 1), + STATE_FLAG_ RESIZING = (1 << 2), + STATE_FLAG_ACTIVATING = (1 << 3), + } state_flag_t; + + bool (*set_window_state)(window_t window, uint32_t state_flags); + bool (*set_window_bounds)(window_t window, const rect_t *bounds); + + // 平台特定扩展 + void* (*get_platform_handle)(void); // xcb_connection_t* or wl_display* +} display_backend_t; + +// 显示后端管理 +display_backend_t *display_backend_create(display_backend_type_t type); +bool display_backend_init(wm_context_t *ctx, display_backend_type_t type); +void display_backend_cleanup(display_backend_t *backend); + +// X11 后端声明 +extern display_backend_t x11_backend; + +// Wayland 后端声明(未来实现) +extern display_backend_t wayland_backend; +#+END_SRC + +*优势*: +- 为 Wayland 移植奠定基础 +- 显示逻辑与核心逻辑分离 +- 便于使用 mock 后端进行测试 +- 运行时可切换显示后端 + +*X11 后端实现*: + +#+BEGIN_SRC c +// src/services/x11_backend.c +#include +#include +#include + +typedef struct x11_backend_data_t { + xcb_connection_t *conn; + xcb_screen_t *screen; + xcb_window_t root_window; + xcb_atom_t wm_protocols; + xcb_atom_t wm_delete_window; + GHashTable *window_map; // uint64_t -> xcb_window_t +} x11_backend_data_t; + +static bool x11_init(wm_context_t *ctx) { + x11_backend_data_t *data = calloc(1, sizeof(x11_backend_data_t)); + + data->conn = xcb_connect(NULL, NULL); + if (xcb_connection_has_error(data->conn)) { + free(data); + return false; + } + + data->screen = xcb_setup_roots_iterator(xcb_get_setup(data->conn)).data; + data->root_window = data->screen->root; + + // 初始化 atoms 和其他资源 + // ... + + return true; +} + +static window_t x11_create_window(const rect_t *geometry) { + x11_backend_data_t *data = display_backend_get_data(&x11_backend); + + xcb_window_t xcb_win = xcb_generate_id(data->conn); + // 创建窗口... + + return (window_t){ + .id = (uint64_t)xcb_win, + .platform_handle = (void *)(uintptr_t)xcb_win + }; +} + +// ... 其他 X11 实现 + +display_backend_t x11_backend = { + .name = "x11", + .version = "1.0", + .init = x11_init, + .cleanup = x11_cleanup, + .create_window = x11_create_window, + // ... 其他函数指针 +}; +#+END_SRC + +*Wayland 后端框架*: + +#+BEGIN_SRC c +// src/services/wayland_backend.c +#include + +typedef struct wayland_backend_data_t { + struct wl_display *display; + struct wl_compositor *compositor; + struct wl_seat *seat; + struct xdg_wm_base *xdg_wm_base; + GHashTable *window_map; +} wayland_backend_data_t; + +static bool wayland_init(wm_context_t *ctx) { + wayland_backend_data_t *data = calloc(1, sizeof(wayland_backend_data_t)); + + data->display = wl_display_connect(NULL); + if (!data->display) { + free(data); + return false; + } + + // 注册全局对象监听器 + wl_display_roundtrip(data->display); + + return true; +} + +// ... 其他 Wayland 实现(未来工作) + +display_backend_t wayland_backend = { + .name = "wayland", + .version = "1.0", + .init = wayland_init, + // ... 其他函数指针 +}; +#+END_SRC + +**** 3.2 事件总线 +*问题*: 事件处理直接耦合业务逻辑 + +*解决方案*: 发布-订阅事件系统 + +#+BEGIN_SRC c +// src/services/event_bus.h +typedef enum event_type_t { + // 窗口事件 + EVENT_WINDOW_CREATE, + EVENT_WINDOW_DESTROY, + EVENT_WINDOW_CONFIGURE, + EVENT_WINDOW_FOCUS, + + // 监视器事件 + EVENT_MONITOR_ADD, + EVENT_MONITOR_REMOVE, + EVENT_MONITOR_CHANGE, + + // 标签事件 + EVENT_TAG_ADD, + EVENT_TAG_REMOVE, + EVENT_TAG_CHANGE, + + // 用户事件 + EVENT_KEY_PRESS, + EVENT_MOUSE_PRESS, + EVENT_COMMAND, + + // 系统事件 + EVENT_CONFIG_RELOAD, + EVENT_RESTART, + EVENT_EXIT +} event_type_t; + +typedef struct event_t { + event_type_t type; + void *data; // 事件特定数据 + void *source; // 事件源对象 +} event_t; + +typedef void (*event_callback)(const event_t *event, void *user_data); + +typedef struct event_subscription_t { + event_type_t type; + event_callback callback; + void *user_data; + bool once; // 是否只触发一次 +} event_subscription_t; + +typedef struct event_bus_t { + event_subscription_t **subscriptions; + size_t subscription_count; + size_t capacity; +} event_bus_t; + +// 事件总线API +event_bus_t *event_bus_create(void); +void event_bus_destroy(event_bus_t *bus); + +// 订阅事件 +event_subscription_t *event_bus_subscribe(event_bus_t *bus, + event_type_t type, + event_callback callback, + void *user_data); +void event_bus_unsubscribe(event_bus_t *bus, event_subscription_t *sub); + +// 发布事件 +void event_bus_publish(event_bus_t *bus, const event_t *event); + +// 便捷宏 +#define EVENT_SUBSCRIBE(bus, type, callback, data) \ + event_bus_subscribe(bus, type, callback, data) + +#define EVENT_PUBLISH(bus, type, data) \ + event_bus_publish(bus, &(event_t){ .type = type, .data = data }) +#+END_SRC + +**** 3.3 插件系统 +*问题*: 扩展功能需要修改核心代码 + +*解决方案*: 轻量级插件接口 + +#+BEGIN_SRC c +// src/services/plugin.h +typedef struct plugin_t { + char *name; + char *version; + void *handle; // dlopen句柄 + + // 生命周期 + bool (*init)(wm_context_t *ctx); + void (*cleanup)(void); + + // 事件处理 + void (*on_event)(const event_t *event); + + // 配置 + bool (*set_config)(const char *key, const char *value); +} plugin_t; + +typedef struct plugin_manager_t { + plugin_t **plugins; + size_t plugin_count; + event_bus_t *event_bus; +} plugin_manager_t; + +// 插件管理API +plugin_manager_t *plugin_manager_create(event_bus_t *bus); +void plugin_manager_destroy(plugin_manager_t *mgr); + +// 插件加载 +bool plugin_manager_load(plugin_manager_t *mgr, const char *path); +bool plugin_manager_unload(plugin_manager_t *mgr, const char *name); + +// 插件API(供插件实现者使用) +void plugin_register(plugin_t *plugin); +void plugin_unregister(const char *name); + +// 插件宏(简化插件开发) +#define PLUGIN_EXPORT(name, version, init_fn, cleanup_fn) \ + __attribute__((used)) plugin_t *_plugin_get_info(void) { \ + static plugin_t plugin = { \ + .name = name, \ + .version = version, \ + .init = init_fn, \ + .cleanup = cleanup_fn \ + }; \ + return &plugin; \ + } +#+END_SRC + +*** 第四层:业务抽象层 (Business Layer) + +**** 4.1 客户端抽象 +*问题*: client.c 职责过多,难以维护 + +*解决方案*: 客户端接口抽象 + +#+BEGIN_SRC c +// src/business/client_interface.h +// 客户端状态机 +typedef enum client_state_t { + CLIENT_STATE_NEW, + CLIENT_STATE_NORMAL, + CLIENT_STATE_FLOATING, + CLIENT_STATE_FULLSCREEN, + CLIENT_STATE_MAXIMIZED, + CLIENT_STATE_MINIMIZED, + CLIENT_STATE_DOCK, + CLIENT_STATE_DYING +} client_state_t; + +// 客户端接口 +typedef struct client_interface_t { + // 生命周期 + client_t* (*create)(xcb_window_t window); + void (*destroy)(client_t *client); + + // 状态管理 + client_state_t (*get_state)(const client_t *client); + bool (*set_state)(client_t *client, client_state_t new_state); + + // 几何管理 + bool (*set_geometry)(client_t *client, const rect_t *geometry); + rect_t (*get_geometry)(const client_t *client); + + // 焦点管理 + bool (*focus)(client_t *client); + bool (*is_focused)(const client_t *client); + + // 属性管理 + bool (*set_property)(client_t *client, const char *key, void *value); + void* (*get_property)(const client_t *client, const char *key); + + // 渲染 + bool (*needs_redraw)(const client_t *client); + void (*draw)(client_t *client, cairo_t *cr); +} client_interface_t; + +// 客户端管理器 +typedef struct client_manager_t { + client_interface_t *iface; + list_t *clients; // 所有客户端 + hash_table_t *window_map; // window -> client +} client_manager_t; + +// 客户端管理API +client_manager_t *client_manager_create(client_interface_t *iface); +void client_manager_destroy(client_manager_t *mgr); + +client_t *client_manager_add(client_manager_t *mgr, xcb_window_t window); +bool client_manager_remove(client_manager_t *mgr, client_t *client); +client_t *client_manager_find(client_manager_t *mgr, xcb_window_t window); +#+END_SRC + +**** 4.2 布局引擎抽象 +*问题*: 布局系统已经设计良好,但缺少插件化 + +*解决方案*: 布局插件接口 + +#+BEGIN_SRC c +// src/business/layout_engine.h +typedef struct layout_context_t { + tag_t *tag; + rect_t workarea; + client_t **clients; + size_t client_count; +} layout_context_t; + +typedef struct layout_algorithm_t { + const char *name; + const char *symbol; + + // 布局计算 + void (*arrange)(const layout_context_t *ctx, rect_t *geometries); + + // 可选:预览布局(不实际应用) + void (*preview)(const layout_context_t *ctx, rect_t *geometries); + + // 可选:布局参数 + bool (*set_param)(const char *key, void *value); +} layout_algorithm_t; + +typedef struct layout_engine_t { + layout_algorithm_t **algorithms; + size_t algorithm_count; + layout_algorithm_t *default_algorithm; +} layout_engine_t; + +// 布局引擎API +layout_engine_t *layout_engine_create(void); +void layout_engine_destroy(layout_engine_t *engine); + +bool layout_engine_register(layout_engine_t *engine, layout_algorithm_t *algo); +bool layout_engine_unregister(layout_engine_t *engine, const char *name); + +layout_algorithm_t *layout_engine_find(layout_engine_t *engine, const char *name); +void layout_engine_arrange(layout_engine_t *engine, const layout_context_t *ctx); + +// 便捷宏:定义布局算法 +#define LAYOUT_ALGORITHM(name, symbol, arrange_fn) \ + static layout_algorithm_t _layout_##name = { \ + .name = #name, \ + .symbol = symbol, \ + .arrange = arrange_fn \ + }; \ + __attribute__((constructor)) \ + static void _layout_register_##name(void) { \ + layout_engine_register(layout_engine_get_default(), &_layout_##name); \ + } +#+END_SRC + +**** 4.3 操作抽象 +*问题*: 操作系统已经设计良好,可以增强 + +*解决方案*: 命令模式增强 + +#+BEGIN_SRC c +// src/business/command.h +typedef enum command_arg_type_t { + COMMAND_ARG_NONE, + COMMAND_ARG_INT, + COMMAND_ARG_STRING, + COMMAND_ARG_BOOLEAN +} command_arg_type_t; + +typedef struct command_arg_t { + command_arg_type_t type; + union { + int integer; + char *string; + bool boolean; + } value; +} command_arg_t; + +typedef struct command_result_t { + bool success; + char *message; + void *data; // 可选的返回数据 + void (*data_cleanup)(void *data); +} command_result_t; + +typedef struct command_t { + const char *name; + const char *description; + + // 参数定义 + command_arg_type_t *arg_types; + size_t arg_count; + + // 执行函数 + command_result_t (*execute)(wm_context_t *ctx, + const command_arg_t *args, + size_t arg_count); + + // 可选:自动完成 + char* (*complete)(const char *prefix, size_t arg_index); +} command_t; + +typedef struct command_registry_t { + hash_table_t *commands; // name -> command_t +} command_registry_t; + +// 命令注册API +command_registry_t *command_registry_create(void); +void command_registry_destroy(command_registry_t *registry); + +bool command_registry_register(command_registry_t *registry, command_t *cmd); +bool command_registry_unregister(command_registry_t *registry, const char *name); + +command_result_t command_execute(command_registry_t *registry, + wm_context_t *ctx, + const char *name, + const command_arg_t *args, + size_t arg_count); + +// 便捷宏 +#define COMMAND_REGISTER(registry, name, fn) \ + command_registry_register(registry, &(command_t){ \ + .name = name, \ + .execute = fn \ + }) +#+END_SRC + +*** 第五层:UI抽象层 (Presentation Layer) + +**** 5.1 渲染器抽象 +*问题*: 渲染逻辑散落各处,难以扩展 + +*解决方案*: 统一渲染接口 + +#+BEGIN_SRC c +// src/ui/renderer.h +typedef struct render_context_t { + cairo_t *cr; + rect_t viewport; + color_scheme_t *colors; +} render_context_t; + +typedef struct renderer_t { + const char *name; + + // 初始化 + bool (*init)(wm_context_t *ctx); + + // 清理 + void (*cleanup)(void); + + // 渲染 + void (*render)(const render_context_t *ctx, void *data); + + // 尺寸计算 + void (*measure)(const render_context_t *ctx, void *data, size_t *width, size_t *height); +} renderer_t; + +typedef struct renderer_manager_t { + renderer_t **renderers; + size_t renderer_count; +} renderer_manager_t; + +// 渲染器管理API +renderer_manager_t *renderer_manager_create(void); +void renderer_manager_destroy(renderer_manager_t *mgr); + +bool renderer_manager_register(renderer_manager_t *mgr, renderer_t *r); +bool renderer_manager_unregister(renderer_manager_t *mgr, const char *name); + +renderer_t *renderer_manager_find(renderer_manager_t *mgr, const char *name); +void renderer_manager_render(renderer_manager_t *mgr, const char *name, + const render_context_t *ctx, void *data); +#+END_SRC + +**** 5.2 状态栏组件系统 +*问题*: 状态栏已经设计良好,可以组件化 + +*解决方案*: 组件化状态栏 + +#+BEGIN_SRC c +// src/ui/statusbar_component.h +typedef struct statusbar_component_t { + const char *name; + size_t preferred_width; + size_t preferred_height; + + // 渲染 + void (*render)(const render_context_t *ctx, void *data); + + // 尺寸计算 + void (*measure)(const render_context_t *ctx, void *data, + size_t *width, size_t *height); + + // 事件处理 + void (*on_click)(void *data, int x, int y); + void (*on_scroll)(void *data, int delta); + + // 生命周期 + void (*init)(wm_context_t *ctx, void **data); + void (*cleanup)(void *data); +} statusbar_component_t; + +typedef struct statusbar_t { + statusbar_component_t **components; + void **component_data; + size_t component_count; + + // 布局管理 + enum { + STATUSBAR_LAYOUT_LEFT_TO_RIGHT, + STATUSBAR_LAYOUT_RIGHT_TO_LEFT + } layout_direction; +} statusbar_t; + +// 状态栏组件API +statusbar_t *statusbar_create(void); +void statusbar_destroy(statusbar_t *bar); + +bool statusbar_add_component(statusbar_t *bar, statusbar_component_t *comp); +bool statusbar_remove_component(statusbar_t *bar, const char *name); + +void statusbar_render(statusbar_t *bar, const render_context_t *ctx); +void statusbar_handle_event(statusbar_t *bar, const event_t *event); + +// 预定义组件 +extern statusbar_component_t statusbar_clock_component; +extern statusbar_component_t statusbar_cpu_component; +extern statusbar_component_t statusbar_memory_component; +extern statusbar_component_t statusbar_network_component; +#+END_SRC + +** 架构分层图 + +#+BEGIN_EXAMPLE +┌─────────────────────────────────────────────────────────────┐ +│ UI 抽象层 │ +│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ +│ │ 渲染器管理 │ │ 状态栏组件 │ │ 通知系统 │ │ +│ └──────────────┘ └──────────────┘ └──────────────┘ │ +└─────────────────────────────────────────────────────────────┘ + ▲ + │ +┌─────────────────────────────────────────────────────────────┐ +│ 业务抽象层 │ +│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ +│ │ 客户端接口 │ │ 布局引擎 │ │ 命令系统 │ │ +│ └──────────────┘ └──────────────┘ └──────────────┘ │ +└─────────────────────────────────────────────────────────────┘ + ▲ + │ +┌─────────────────────────────────────────────────────────────┐ +│ 服务抽象层 │ +│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ +│ │ 显示后端 │ │ 事件总线 │ │ 插件系统 │ │ +│ │ (X11/Wayland)│ │ │ │ │ │ +│ └──────────────┘ └──────────────┘ └──────────────┘ │ +└─────────────────────────────────────────────────────────────┘ + ▲ + │ +┌─────────────────────────────────────────────────────────────┐ +│ 数据抽象层 │ +│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ +│ │ 状态管理器 │ │ 配置系统 │ │ 资源缓存 │ │ +│ └──────────────┘ └──────────────┘ └──────────────┘ │ +└─────────────────────────────────────────────────────────────┘ + ▲ + │ +┌─────────────────────────────────────────────────────────────┐ +│ 基础设施抽象层 │ +│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ +│ │ 上下文管理 │ │ 日志系统 │ │ 错误处理 │ │ +│ └──────────────┘ └──────────────┘ └──────────────┘ │ +└─────────────────────────────────────────────────────────────┘ +#+END_EXAMPLE + +** 实施路径 + +*** 阶段1:基础设施层 (2-3周) +1. *上下文管理器* + - 创建 =wm_context_t= + - 修改现有代码使用上下文 + - 保持向后兼容宏 + +2. *日志系统* + - 实现 =log.h/c= + - 替换所有 =logger/fatal/warn= + - 添加文件输出 handler + +3. *错误处理* + - 实现 =error.h/c= + - 修复 GError 使用 + - 添加错误恢复机制 + +*** 阶段2:数据抽象层 (3-4周) +1. *状态管理器* + - 实现 =state_manager_t= + - 迁移关键状态到统一管理 + - 添加状态变更监听 + +2. *配置系统* + - 实现配置栈 + - 支持配置文件热重载 + - 添加配置验证 + +*** 阶段3:服务抽象层 (4-6周) +1. *显示后端抽象* + - 定义显示后端统一接口 + - 实现 X11 后端(迁移现有 XCB 调用) + - 创建 Wayland 后端框架(仅接口定义) + - 实现 Mock 后端用于单元测试 + - 修改核心逻辑使用显示后端接口 + - 添加后端自动检测和运行时切换 + +2. *事件总线* + - 实现发布-订阅系统 + - 迁移现有事件处理 + - 添加事件日志 + +3. *插件系统* + - 实现插件加载器 + - 定义插件接口 + - 创建示例插件 + +*** 阶段4:业务抽象层 (6-8周) +1. *客户端重构* + - 定义客户端接口 + - 实现状态机 + - 拆分 client.c + +2. *布局引擎* + - 提取布局算法 + - 支持动态注册 + - 添加布局预览 + +3. *命令系统* + - 统一命令接口 + - 支持参数验证 + - 添加自动完成 + +*** 阶段5:UI抽象层 (4-6周) +1. *渲染器管理* + - 统一渲染接口 + - 支持多种渲染器 + - 添加渲染缓存 + +2. *状态栏组件化* + - 提取组件接口 + - 实现预定义组件 + - 支持自定义组件 + +** 向后兼容策略 + +1. *渐进式迁移* + - 新旧API并存 + - 使用宏提供兼容层 + - 标记旧API为deprecated + +2. *编译时选项* + #+BEGIN_SRC c + #ifdef USE_NEW_ARCH + // 使用新API + #else + // 使用旧API + #endif + #+END_SRC + +3. *运行时切换* + - 支持命令行参数选择架构 + - 便于性能对比和问题定位 + +** 测试策略 + +1. *单元测试* + - 每个抽象层独立测试 + - 使用mock对象隔离依赖 + +2. *集成测试* + - 测试层间交互 + - 端到端场景测试 + +3. *性能测试* + - 对比新旧架构性能 + - 内存使用分析 + - 响应时间测试 + +** 预期收益 + +*** 可维护性提升 +- 清晰的职责划分 +- 松耦合的模块设计 +- 易于定位和修复问题 + +*** 可扩展性提升 +- 插件化架构 +- 组件化UI +- 动态配置系统 + +*** 可测试性提升 +- 依赖注入支持 +- Mock对象友好 +- 隔离测试环境 + +*** 用户体验提升 +- 配置热重载 +- 插件生态 +- 更好的错误提示 + +这个抽象层设计既保持了ZDWM的简洁高效,又大幅提升了可维护性和可扩展性,为项目的长期发展奠定坚实基础。 + +** 显示后端抽象与 Wayland 支持 + +*** 设计目标 + +通过引入显示后端抽象层,实现以下目标: +- 为 Wayland 移植奠定基础,适应显示服务器技术演进 +- 显示逻辑与核心业务逻辑完全分离 +- 支持运行时后端切换,用户可选择 X11 或 Wayland +- 便于使用 Mock 后端进行单元测试 + +*** 架构优势 + +1. *显示服务器无关性*: 核心窗口管理逻辑不依赖特定显示服务器 +2. *渐进式迁移*: 先完善 X11 后端,再逐步实现 Wayland 后端 +3. *性能无损*: 通过编译时优化和内联函数保持高性能 +4. *测试友好*: Mock 后端使单元测试更简单 + +*** X11 后端实现重点 + +#+BEGIN_SRC c +// X11 后端的核心职责 +- 管理 xcb_connection_t 和 xcb_screen_t +- 窗口生命周期管理(创建、销毁、映射、取消映射) +- ICCCM/EWMH 协议实现 +- 事件转换(XCB 事件 → 通用 display_event_t) +- Cairo 集成(cairo_xcb_surface_t) +- 输入设备抓取(键盘、鼠标) +#+END_SRC + +*** Wayland 后端实现路线图 + +*阶段1:基础框架(未来 3-6 个月)* +- 实现 Wayland 客户端协议绑定 +- 支持 wl_display, wl_compositor, wl_shm +- 实现 xdg-shell 基础功能 +- 窗口创建和基本管理 + +*阶段2:核心功能(未来 6-12 个月)* +- 完整的 xdg-shell 支持 +- 输入事件处理(wl_seat, wl_pointer, wl_keyboard) +- 表面和帧回调(wl_surface, wl_callback) +- EGL/渲染集成 + +*阶段3:高级特性(未来 12-18 个月)* +- xdg-activation 协议(焦点管理) +- xdg-decoration 协议(服务器端装饰) +- wl_output 多显示器支持 +- Layer-shell 支持(面板、覆盖层) + +*** 后端切换策略 + +1. *编译时选择*(零成本抽象) + #+BEGIN_SRC c + #if defined(DISPLAY_BACKEND_X11) + #define DISPLAY_CALL(func, ...) x11_##func(__VA_ARGS__) + #elif defined(DISPLAY_BACKEND_WAYLAND) + #define DISPLAY_CALL(func, ...) wayland_##func(__VA_ARGS__) + #endif + #+END_SRC + +2. *运行时切换*(灵活性) + #+BEGIN_SRC c + // 通过环境变量选择 + export ZDWM_BACKEND=x11 // 或 wayland + + // 通过命令行参数选择 + zdwm --backend=x11 + zdwm --backend=wayland + + // 自动检测 + if (getenv("WAYLAND_DISPLAY")) { + backend = &wayland_backend; + } else if (getenv("DISPLAY")) { + backend = &x11_backend; + } + #+END_SRC + +*** 配置示例 + +#+BEGIN_SRC c +// 配置文件支持 +typedef struct display_config_t { + display_backend_type_t backend; + struct { + bool enable_ssd; // 服务器端装饰(X11) + bool enable_csd; // 客户端装饰(Wayland) + } decoration; + struct { + bool enable_vsync; + bool use_egl; // EGL vs SHM(Wayland) + } rendering; +} display_config_t; +#+END_SRC + +*** 兼容性考虑 + +1. *向后兼容*: 保持现有 XCB 调用接口,通过宏迁移到新接口 +2. *功能对等*: 确保 X11 和 Wayland 后端功能一致性 +3. *性能保持*: Wayland 后端性能应不低于 X11 后端 + +*** 测试策略 + +1. *Mock 后端测试* + #+BEGIN_SRC c + // 用于单元测试的模拟后端 + static display_backend_t mock_backend = { + .name = "mock", + .init = mock_init, + .create_window = mock_create_window, + // ... 其他实现 + }; + + // 测试核心逻辑 + void test_client_management_with_mock_backend() { + wm_context_t *ctx = wm_context_create(DISPLAY_BACKEND_MOCK); + // 测试逻辑... + } + #+END_SRC + +2. *双后端集成测试* + - 使用相同测试用例验证 X11 和 Wayland 后端 + - 确保行为一致性 + +*** 预期收益 + +1. *未来兼容性*: 为 Wayland 移植奠定基础 +2. *架构清晰*: 显示逻辑与核心逻辑分离 +3. *测试友好*: Mock 后端简化单元测试 +4. *性能无损*: 通过编译时优化保持高性能 +5. *用户选择*: 用户可根据需求选择 X11 或 Wayland + +这个显示后端抽象设计确保了 ZDWM 能够适应显示服务器技术的演进,同时保持当前的优秀性能和稳定性。 diff --git a/docs/config_system.md b/docs/config_system.md new file mode 100644 index 0000000..9e2ab41 --- /dev/null +++ b/docs/config_system.md @@ -0,0 +1,407 @@ +# Zdwm 配置系统设计文档 + +## 概述 + +三层配置系统,按优先级从低到高加载: + +``` +内置默认配置 → XResources → 动态库配置 +(后者覆盖前者) +``` + +--- + +## 配置层次 + +### 第一层:内置默认配置 +- **位置**: 编译到二进制中 +- **作用**: 提供完整的默认配置,确保无外部配置时也能正常运行 +- **范围**: 所有配置项 + +### 第二层:XResources 配置 +- **位置**: `~/.Xresources` 或 `~/.config/zdwm/Xresources` +- **作用**: 覆盖外观配置(颜色、字体、边框等) +- **特点**: 无需重新编译,`xrdb` 后重启生效 +- **范围**: 仅外观配置 + +### 第三层:动态库配置 +- **位置**: `~/.config/zdwm/config.c` → `config.so` +- **作用**: 高级配置(快捷键、规则、自定义布局等) +- **特点**: 最大灵活性,需编译 C 代码 +- **范围**: 所有配置项 + +--- + +## 配置加载流程 + +``` +1. 加载内置默认配置 + ↓ +2. 尝试加载 XResources + ├─ 成功 → 覆盖默认配置中的外观项 + └─ 失败 → 跳过,保持默认值 + ↓ +3. 尝试加载动态库 + ├─ 存在 → 调用 zdwm_config_init(),增量修改配置 + └─ 不存在 → 跳过 + ↓ +4. 应用最终配置 +``` + +--- + +## 数据结构 + +### 配置优先级标记 + +```c +typedef struct config_source_t { + bool has_xres; // 是否加载了 XResources + bool has_user_lib; // 是否加载了用户动态库 +} config_source_t; +``` + +### 完整配置结构 + +```c +typedef struct config_t { + // 外观 + char *font_family; + uint32_t font_size; + uint32_t dpi; + uint16_t border_width; + uint16_t bar_y_padding; + uint16_t tag_x_padding; + color_set_t colors; + + // 快捷键 + const keyboard_t *key_list; + size_t key_count; + + // 布局 + const layout_t *layout_list; + size_t layout_count; + + // 标签 + const char *const *tags; + + // 规则 + const rule_t *rules; + size_t rules_count; + + // 自动启动 + const char *const *autostart_list; + + // 钩子 + void (*hook_manage_new)(client_t *client); + void (*hook_manage_unmanage)(client_t *client); + + // 元数据 + config_source_t source; +} config_t; +``` + +--- + +## API 接口(动态库) + +### zdwm_api_t + +```c +typedef struct zdwm_api_t { + uint32_t version; + + // 外观(覆盖 XResources) + void (*set_font)(const char *family, uint32_t size); + void (*set_dpi)(uint32_t dpi); + void (*set_color)(const char *name, const char *hex); + void (*set_border_width)(uint16_t width); + void (*set_padding)(uint16_t bar_y, uint16_t tag_x); + + // 快捷键(增量式) + void (*add_keybinding)(uint32_t modifiers, xcb_keysym_t keysym, + void (*action)(const void *), + const void *arg); + void (*remove_keybinding)(uint32_t modifiers, xcb_keysym_t keysym); + void (*clear_keybindings)(void); + + // 规则 + void (*add_rule)(const char *role, const char *class, + int32_t tag_index, bool floating, + bool fullscreen, bool maximize, + bool switch_to_tag); + + // 自动启动 + void (*add_autostart)(const char *command); + + // 布局 + void (*add_layout)(const char *symbol, void (*arrange)(tag_t *tag)); + + // 钩子 + void (*hook_manage_new)(void (*hook)(client_t *client)); + void (*hook_manage_unmanage)(void (*hook)(client_t *client)); +} zdwm_api_t; +``` + +### 用户配置初始化函数 + +```c +typedef struct zdwm_user_config_t { + void (*cleanup)(void); +} zdwm_user_config_t; + +// 用户在 config.c 中实现此函数(可选) +zdwm_user_config_t *zdwm_config_init(zdwm_api_t *api); +``` + +--- + +## XResources 支持的配置项 + +### 配置项命名规范 + +``` +Zdwm.font.family # 字体族 +Zdwm.font.size # 字体大小 +Zdwm.border.width # 边框宽度 +Zdwm.padding.bar_y # Bar 上下内边距 +Zdwm.padding.tag_x # Tag 左右内边距 +Zdwm.color.bar # Bar 背景色 +Zdwm.color.tag # Tag 背景色 +Zdwm.color.tagActive # 活动 Tag 背景色 +Zdwm.color.tagText # Tag 文字颜色 +Zdwm.color.tagActiveText # 活动 Tag 文字颜色 +Zdwm.color.border # 边框颜色 +Zdwm.color.borderActive # 活动窗口边框颜色 +``` + +### 数据类型映射 + +``` +XResources 类型 → C 类型 +--------------------------→------------------- +字符串 → char * +整数 → uint32_t +颜色 (#RRGGBB) → color_t (uint32_t argb) +``` + +--- + +## 实现要点 + +### 1. 配置加载函数 + +```c +// src/config.h +config_t *config_load_full(void); +void config_free(config_t *config); +config_t *config_reload(void); +``` + +### 2. 加载顺序 + +```c +// src/config.c +config_t *config_load_full(void) { + config_t *config = calloc(1, sizeof(config_t)); + + // 第一步:默认配置 + config_apply_defaults(config); + + // 第二步:XResources(覆盖外观) + config_load_xres(config); + + // 第三步:动态库(覆盖所有) + config_load_user_lib(config); + + return config; +} +``` + +### 3. XResources 解析 + +```c +// src/config_xres.c +bool config_load_xres(config_t *config) { + if (!xres_init_xrm_db()) return false; + + // 覆盖外观配置 + xres_get_string("Zdwm.font.family", &config->font_family, default_font); + xres_get_uint32("Zdwm.font.size", &config->font_size); + // ... + + xres_clean(); + return true; +} +``` + +### 4. 动态库加载 + +```c +// src/config_lib.c +bool config_load_user_lib(config_t *config) { + const char *path = "~/.config/zdwm/config.so"; + + void *handle = dlopen(path, RTLD_LAZY); + if (!handle) return false; + + zdwm_user_config_t *(*init)(zdwm_api_t *) = dlsym(handle, "zdwm_config_init"); + if (!init) { + dlclose(handle); + return false; // 没有配置函数是正常的 + } + + zdwm_api_t api = create_api(config); + init(&api); + + return true; +} +``` + +--- + +## 配置热重载 + +### XResources 热重载 + +编辑 XResources 后的操作流程: + +```bash +# 1. 编辑配置文件 +vim ~/.Xresources + +# 2. 加载到 X Server +xrdb -merge ~/.Xresources + +# 3. 重载 zdwm 配置 +killall -HUP zdwm +``` + +### 实现要点 + +```c +// src/wm.c + +// 重新应用配置 +static void wm_reapply_config(void) { + // 重新初始化字体 + text_reinit_pango_layout(wm.font_family, wm.font_size, wm.dpi); + + // 重新计算 bar 高度 + wm.bar_height = text_get_height() + 2 * wm.padding.bar_y; + + // 刷新所有 monitor 的 bar + for (monitor_t *m = wm.monitor_list; m; m = m->next) { + monitor_draw_bar(m); + } + + xcb_flush(wm.xcb_conn); +} + +// SIGHUP 信号处理器 +static void reload_config_handler(int sig) { + log("Received SIGHUP, reloading configuration..."); + + // 清理旧的 XResources 数据库 + xres_clean(); + + // 重新加载 XResources + wm_get_xres_config(); + + // 重新应用配置 + wm_reapply_config(); + + log("Configuration reloaded successfully"); +} + +// 在 wm_setup_signal() 中注册 +void wm_setup_signal(void) { + // ... 现有信号处理 ... + + signal(SIGHUP, reload_config_handler); + signal(SIGUSR1, reload_config_handler); // 备用信号 +} +``` + +### xres 模块需要添加 + +```c +// src/xres.h +void xres_reload(void); + +// src/xres.c +void xres_reload(void) { + if (wm.xrm) { + xcb_xrm_database_free(wm.xrm); + wm.xrm = nullptr; + } + xres_init_xrm_db(); +} +``` + +### 完整重载流程 + +``` +用户编辑 XResources + ↓ +xrdb -merge ~/.Xresources (加载到 X Server) + ↓ +killall -HUP zdwm (发送重载信号) + ↓ +zdwm 收到 SIGHUP + ↓ +xres_clean() (清理旧数据库) + ↓ +wm_get_xres_config() (重新读取 XResources) + ↓ +wm_reapply_config() (应用新配置) + ↓ +monitor_draw_bar() (刷新显示) +``` + +--- + +## 文件结构 + +``` +src/ +├── config.h # 配置系统头文件 +├── config.c # 配置加载主逻辑 +├── config_default.c # 内置默认配置 +├── config_xres.c # XResources 解析 +├── config_lib.c # 动态库加载 +└── config_api.c # API 实现(供动态库调用) + +docs/ +└── config_system.md # 本文档 +``` + +--- + +## 依赖关系 + +``` +config.c +├── config_default.c (内置默认值) +├── config_xres.c (XResources 解析) +│ └── xres.c (已有) +└── config_lib.c (动态库加载) + └── dlopen() +``` + +--- + +## 实现优先级 + +| 优先级 | 模块 | 说明 | +|-------|------|------| +| P0 | config_default.c | 定义默认配置值 | +| P0 | config.c | 主加载逻辑 | +| P1 | config_xres.c | XResources 支持(已有基础) | +| P1 | 热重载 | SIGHUP 支持(配合 XResources) | +| P1 | config_lib.c | 动态库加载 | +| P2 | config_api.c | API 实现 | + +**P0**: 核心功能,必须实现 +**P1**: 重要功能,提升用户体验 +**P2**: 增强功能,可后续添加 diff --git a/docs/min_core_draft/README.org b/docs/min_core_draft/README.org new file mode 100644 index 0000000..931ab98 --- /dev/null +++ b/docs/min_core_draft/README.org @@ -0,0 +1,171 @@ +* ZDWM 最小核心草案 + +这个目录放的是一套头文件级别的最小核心 API 草案,目标是先把窗口管理器最核心的对象关系、子系统边界和主执行流程固定下来,再决定具体实现。 + +* 目标 + +1. 保持核心技术栈简洁,不引入脚本运行时、序列化协议和多进程插件宿主。 +2. 先把最小核心收敛到可以管理窗口、计算布局、处理输入、提交后端副作用。 +3. 将渲染、状态栏、配置、插件都放在核心之外,作为后续服务挂接。 + +* 最小核心子系统 + +1. =runtime= +负责主循环、模块装配和固定执行顺序。 + +2. =backend= +负责平台交互。它把平台原始事件转成 =wm_event_t= ,并执行 =wm_effect_t= 。 + +3. =state= +负责保存窗口、工作区、输出和全局堆叠顺序,是唯一真状态。 + +4. =policy= +负责把事件路由为命令,并把命令应用到状态。 + +5. =layout= +负责为某个输出上当前显示的工作区中的平铺窗口计算几何。 + +6. =window metadata= +负责窗口标题、类名、实例名、app id 这类展示和匹配信息。 + +7. =workspace descriptor= +负责 workspace 在 bar 中显示时使用的名字和文本或图标符号。 + +* 非核心但常见的服务 + +这些服务故意不放进最小核心: + +1. =render= +状态栏、边框、标题、装饰绘制。 + +2. =status= +网速、内存、CPU、音量、时间等信息采集与聚合。 + +3. =config= +默认配置和用户覆盖。 + +4. =plugin= +动态扩展加载。 + +* 对象关系 + +这套草案里的关系固定为: + +1. =window -> workspace= +每个窗口只属于一个工作区。 + +2. =output -> current_workspace= +每个输出同一时刻只显示一个当前工作区。 + +3. =window visible on output= +这是派生结果,不是独立存储的真相。 + +4. =stack_order[]= +这是全局 z-order 的唯一真相,从下到上排列。 + +5. =window metadata= +窗口标题等字符串信息不放进最小控制状态,而是作为独立元数据表存在。 + +6. =workspace descriptor= +workspace 在 bar 中显示时使用的名字和符号不放进运行时控制状态,而是作为独立描述表存在。 + +* 核心不变量 + +1. 每个窗口必须且只能属于一个工作区。 +2. 每个输出必须且只能显示一个当前工作区。 +3. 可见性由窗口归属、输出当前工作区和最小化状态共同推导。 +4. =geometry_mode= 只负责几何模式,不负责 z-order。 +5. =stack_order[]= 只负责 z-order,不负责几何模式。 +6. =floating= 是二值状态:窗口要么由 layout 决定基础几何,要么由 =float_rect= 决定基础几何。 +7. =sticky= 只影响可见性,不改变窗口归属工作区。 +8. floating 窗口可以跨多个输出显示,但归属工作区仍然只有一个。 +9. 多输出下 floating 窗口是否迁移工作区,由锚点和跨输出策略决定,不由覆盖面积决定。 +10. layout 的名字和 bar 符号属于布局注册表描述信息,不属于工作区运行状态。 +11. 窗口标题和类名属于窗口元数据,不属于窗口控制状态。 +12. workspace 的显示名称和 bar 符号属于 workspace 描述信息,不属于工作区运行状态。 + +* Generation 字段 + +=generation= 是版本号,不是业务关系字段。 + +当前草案只建议保留: + +1. =wm_state_t.generation= +每次成功提交状态变更后递增,用于缓存失效、增量重算和调试。 + +以后如果有必要,再给渲染缓存或状态服务增加更细粒度的版本号。 + +* 主执行流程 + +=runtime= 负责固定主循环顺序: + +1. =backend.next_event()= +2. =policy.route_event()= +3. =policy.apply_command()= +4. 根据 =dirty_flags= 决定是否重新运行 =layout= +5. 逐条执行 =wm_effect_t= +6. 可选的渲染刷新 + +* 文件说明 + +1. =wm_types.h= +通用标量类型、矩形、窗口几何模式和策略枚举。 + +2. =wm_state.h= +窗口、工作区、输出和全局状态结构,以及查询接口。 + +3. =wm_event.h= +统一后的运行时事件。 + +4. =wm_command.h= +强类型语义命令。 +命令语义补充见 [[file:WM_COMMAND_RULES.org][WM_COMMAND_RULES.org]] 。 + +5. =wm_plan.h= +脏标记和后端副作用。 + +6. =wm_layout.h= +布局注册表、布局输入和布局输出。 + +7. =wm_window_meta.h= +窗口标题、类名、实例名和 app id 的元数据表。 + +8. =wm_workspace_desc.h= +workspace 的显示名称、文本符号和图标符号描述表。 + +9. =wm_backend.h= +后端子系统接口。 + +10. =wm_policy.h= +事件路由和命令应用接口。 + +11. =wm_runtime.h= +运行时上下文和生命周期接口。 + +12. [[file:WM_COMMAND_RULES.org][WM_COMMAND_RULES.org]] +关键命令的前置条件、状态转移、可见性和副作用规则。 + +13. [[file:WM_POLICY_APPLY_COMMAND_SKELETON.org][WM_POLICY_APPLY_COMMAND_SKELETON.org]] +=wm_policy_apply_command()= 的伪代码骨架和实现顺序建议。 + +* 下一步实施顺序 + +当前阶段不建议继续扩展抽象,应该开始把最小核心草案落成可运行骨架。推荐顺序如下: + +1. 在 =src/core/= 下建立最小骨架,只放 =wm_types/state/plan/layout/policy/runtime= 的头文件和空实现,先不要替换现有 =src/= 逻辑。 + +2. 优先实现 =state + query + plan= 这一层,把动态数组管理、=find_*= 查询函数和 =stack_order[]= 操作补齐。 + +3. 再实现 =wm_policy_apply_command()= 的一小批核心命令: + =MANAGE_WINDOW= + =UNMANAGE_WINDOW= + =SWITCH_WORKSPACE= + =TOGGLE_FLOATING= + =TOGGLE_MAXIMIZE= + =MOVE_FLOATING_WINDOW= + +4. 给上述命令建立一个无 X11 依赖的测试入口,直接构造 =wm_command_t= ,检查 =wm_state_t= 和 =wm_plan_t= 的变化是否符合文档规则。 + +5. 等 =event -> command -> state -> plan/effect= 这条链路跑通后,再编写一层很薄的 adapter,把现有事件翻译为 =wm_event_t= ,并把 effect 翻译回当前 X11 操作。 + +6. =status/render/bar= 暂时不要先动。它们属于核心外服务,应在最小核心链路稳定后再迁移。 diff --git a/docs/min_core_draft/WM_COMMAND_RULES.org b/docs/min_core_draft/WM_COMMAND_RULES.org new file mode 100644 index 0000000..5ecdee9 --- /dev/null +++ b/docs/min_core_draft/WM_COMMAND_RULES.org @@ -0,0 +1,342 @@ +* ZDWM 最小核心命令规则 + +这份文档定义 =wm_policy_apply_command()= 应遵守的最小语义。目标不是覆盖所有未来扩展,而是先把最关键的状态转移规则固定下来,避免后续实现时在 =floating=、=sticky=、=geometry_mode= 和 =stack_order[]= 上出现分叉语义。 + +* 通用规则 + +1. 找不到目标 =window_id=、=workspace_id= 或 =output_id= 时,命令失败,不修改状态。 +2. 命令失败时,不递增 =state.generation= 。 +3. 命令成功且造成状态变化时,递增 =state.generation= ,并设置相应 =dirty_flags= 。 +4. =stack_order[]= 是全局 z-order 真相,=raise/lower/focus= 只能通过修改这个数组影响堆叠。 +5. =geometry_mode= 只表示几何显示模式,不表示窗口是否在最上层。 +6. =sticky= 只影响可见性,不改变 =workspace_id= 。 +7. =floating= 决定基础几何来源: + =floating = false= 时,基础几何来自 layout 结果。 + =floating = true= 时,基础几何来自 =float_rect= 。 + +* 管理类命令 + +** =WM_COMMAND_MANAGE_WINDOW= + +前置条件: + +1. 目标窗口尚未出现在 =state.windows[]= 中。 +2. 目标工作区存在。 + +状态变化: + +1. 向 =state.windows[]= 添加一个新窗口。 +2. 设定其 =workspace_id= 。 +3. 初始 =floating= 、=sticky= 、=urgent= 、=geometry_mode= 使用调用方或配置给出的默认值。 +4. 将窗口追加到 =stack_order[]= 顶部。 +5. 可选地将该窗口设为所属工作区的 =focused_window_id= 。 + +副作用: + +1. =WM_DIRTY_STATE= +2. =WM_DIRTY_LAYOUT= +3. =WM_DIRTY_STACK= +4. =WM_DIRTY_RENDER= + +** =WM_COMMAND_UNMANAGE_WINDOW= + +前置条件: + +1. 目标窗口存在。 + +状态变化: + +1. 从 =state.windows[]= 中删除该窗口。 +2. 从 =stack_order[]= 中移除该窗口。 +3. 如果它是某个 workspace 的 =focused_window_id= ,清空或重新选择新的 focused window。 + +副作用: + +1. =WM_DIRTY_STATE= +2. =WM_DIRTY_LAYOUT= +3. =WM_DIRTY_STACK= +4. =WM_DIRTY_RENDER= + +* 焦点和堆叠命令 + +** =WM_COMMAND_FOCUS_WINDOW= + +状态变化: + +1. 将目标窗口所属 workspace 的 =focused_window_id= 设置为该窗口。 +2. 不强制改变 =stack_order[]= 。 + +可选策略: + +1. 若 WM 采用 =focus raises= ,则在实现中额外执行 =raise_window= 逻辑。 + +** =WM_COMMAND_FOCUS_DIRECTION= + +状态变化: + +1. 只在目标 =output_id= 当前显示的 workspace 内选择焦点。 +2. 候选窗口应排除 =geometry_mode == WM_GEOMETRY_MINIMIZED= 的窗口。 +3. =sticky= 窗口是否参与方向焦点切换,建议作为单独策略开关,不在最小核心中写死。 + +** =WM_COMMAND_RAISE_WINDOW= + +状态变化: + +1. 在 =stack_order[]= 中找到目标窗口。 +2. 将它移动到数组末尾,也就是最顶层。 + +副作用: + +1. =WM_DIRTY_STACK= +2. =WM_DIRTY_RENDER= + +** =WM_COMMAND_LOWER_WINDOW= + +状态变化: + +1. 在 =stack_order[]= 中找到目标窗口。 +2. 将它移动到数组开头,也就是最底层。 + +副作用: + +1. =WM_DIRTY_STACK= +2. =WM_DIRTY_RENDER= + +* 工作区命令 + +** =WM_COMMAND_SWITCH_WORKSPACE= + +状态变化: + +1. 将目标输出的 =current_workspace_id= 改为指定工作区。 +2. 不修改任何窗口的 =workspace_id= 。 +3. 不修改 =stack_order[]= 。 + +副作用: + +1. =WM_DIRTY_OUTPUT= +2. =WM_DIRTY_LAYOUT= +3. =WM_DIRTY_RENDER= + +** =WM_COMMAND_SEND_WINDOW_TO_WORKSPACE= + +状态变化: + +1. 修改目标窗口的 =workspace_id= 。 +2. 不改变 =sticky= 。 +3. 不改变 =floating= 。 +4. 不改变 =float_rect= 。 +5. 如果窗口是原 workspace 的 =focused_window_id= ,需要重新选择原 workspace 焦点。 + +副作用: + +1. =WM_DIRTY_STATE= +2. =WM_DIRTY_LAYOUT= +3. =WM_DIRTY_RENDER= + +** =WM_COMMAND_SEND_WINDOW_TO_OUTPUT= + +状态变化: + +1. 这是工作区迁移命令的语法糖。 +2. 取目标 output 当前的 =current_workspace_id= 。 +3. 若 =policy == WM_CROSS_OUTPUT_KEEP_WORKSPACE= ,则不改 =workspace_id= ,只允许调用方随后自行调整几何。 +4. 若 =policy == WM_CROSS_OUTPUT_MOVE_TO_TARGET_WORKSPACE= ,则把窗口 =workspace_id= 改成目标 output 当前工作区。 + +* 模式切换命令 + +** =WM_COMMAND_TOGGLE_FLOATING= + +状态变化: + +1. 若原来 =floating == false= : + 将 =floating= 设为 =true= 。 + 将当前 =frame_rect= 复制到 =float_rect= ,作为浮动基准矩形。 +2. 若原来 =floating == true= : + 将 =floating= 设为 =false= 。 + 保留 =float_rect= ,供未来再次进入 floating 时恢复。 +3. 不改变 =geometry_mode= 。 +4. 若窗口处于 =WM_GEOMETRY_MINIMIZED= ,允许切换 =floating= ,但不会立刻显示出来。 + +副作用: + +1. =WM_DIRTY_LAYOUT= +2. =WM_DIRTY_RENDER= + +** =WM_COMMAND_TOGGLE_MAXIMIZE= + +状态变化: + +1. 若当前 =geometry_mode == WM_GEOMETRY_MAXIMIZED= : + 设回 =WM_GEOMETRY_NORMAL= 。 +2. 若当前 =geometry_mode == WM_GEOMETRY_FULLSCREEN= : + 直接切换为 =WM_GEOMETRY_MAXIMIZED= 。 +3. 若当前 =geometry_mode == WM_GEOMETRY_MINIMIZED= : + 直接切换为 =WM_GEOMETRY_MAXIMIZED= 。 +4. 其余情况: + 设为 =WM_GEOMETRY_MAXIMIZED= 。 +5. 不改变 =floating= 。 +6. 若窗口原本是 floating,退出 maximize 后回到 =float_rect= 。 +7. 若窗口原本不是 floating,退出 maximize 后重新参与 layout。 + +副作用: + +1. =WM_DIRTY_LAYOUT= +2. =WM_DIRTY_RENDER= + +** =WM_COMMAND_TOGGLE_FULLSCREEN= + +状态变化: + +1. 若当前 =geometry_mode == WM_GEOMETRY_FULLSCREEN= : + 设回 =WM_GEOMETRY_NORMAL= 。 +2. 否则: + 设为 =WM_GEOMETRY_FULLSCREEN= 。 +3. 不改变 =floating= 。 +4. fullscreen 只决定几何占用 =output.geometry= ,不自动改变 =stack_order[]= 。 + +副作用: + +1. =WM_DIRTY_LAYOUT= +2. =WM_DIRTY_RENDER= + +** =WM_COMMAND_TOGGLE_MINIMIZE= + +状态变化: + +1. 若当前 =geometry_mode == WM_GEOMETRY_MINIMIZED= : + 设回 =WM_GEOMETRY_NORMAL= 。 +2. 否则: + 设为 =WM_GEOMETRY_MINIMIZED= 。 +3. 不改变 =floating= 。 +4. 不改变 =sticky= 。 +5. minimized 后窗口不再参与显示和焦点候选。 + +副作用: + +1. =WM_DIRTY_LAYOUT= +2. =WM_DIRTY_RENDER= + +* 浮动几何命令 + +** =WM_COMMAND_MOVE_FLOATING_WINDOW= + +前置条件: + +1. 目标窗口存在。 +2. 目标窗口应为 =floating == true= 。 + +状态变化: + +1. 按 =dx/dy= 修改 =float_rect.x/y= 。 +2. 若 =commit_workspace_change == false= ,只修改几何,不修改 =workspace_id= 。 +3. 若 =commit_workspace_change == true= : + 依据 =anchor= 找到目标输出。 + 再按 =policy= 决定是否修改 =workspace_id= 。 +4. 不改变 =geometry_mode= 。 + +副作用: + +1. =WM_DIRTY_LAYOUT= +2. =WM_DIRTY_RENDER= + +** =WM_COMMAND_RESIZE_FLOATING_WINDOW= + +前置条件: + +1. 目标窗口存在。 +2. 目标窗口应为 =floating == true= 。 + +状态变化: + +1. 按 =dw/dh= 修改 =float_rect.w/h= 。 +2. 应对宽高做最小值约束,避免得到零或负值。 +3. 不改变 =workspace_id= 。 +4. 不改变 =geometry_mode= 。 + +副作用: + +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= + +状态变化: + +1. 不修改 =state= 。 + +副作用: + +1. =WM_DIRTY_RENDER= + +** =WM_COMMAND_QUIT= + +状态变化: + +1. 不再接收新事件,交由 =runtime= 退出主循环。 + +* 可见性推导建议 + +建议实现顺序如下: + +#+BEGIN_SRC c +bool wm_state_window_should_be_visible(const wm_state_t *state, + const wm_window_t *window) { + if (window->geometry_mode == WM_GEOMETRY_MINIMIZED) return false; + if (window->sticky) return true; + return wm_state_workspace_is_visible(state, window->workspace_id); +} +#+END_SRC + +然后再按输出判断: + +#+BEGIN_SRC c +bool wm_state_window_should_be_visible_on_output(const wm_state_t *state, + const wm_window_t *window, + const wm_output_t *output) { + if (!wm_state_window_should_be_visible(state, window)) return false; + if (window->sticky) return true; + return window->workspace_id == output->current_workspace_id; +} +#+END_SRC + +* 几何解析优先级 + +窗口最终矩形建议按下面的优先级解析: + +1. =WM_GEOMETRY_MINIMIZED= +不显示。 + +2. =WM_GEOMETRY_FULLSCREEN= +使用 =output.geometry= 。 + +3. =WM_GEOMETRY_MAXIMIZED= +使用 =output.workarea= 。 + +4. =floating == true= +使用 =float_rect= 。 + +5. 其余情况 +使用 layout 输出。 diff --git a/docs/min_core_draft/WM_POLICY_APPLY_COMMAND_SKELETON.org b/docs/min_core_draft/WM_POLICY_APPLY_COMMAND_SKELETON.org new file mode 100644 index 0000000..ea02af0 --- /dev/null +++ b/docs/min_core_draft/WM_POLICY_APPLY_COMMAND_SKELETON.org @@ -0,0 +1,550 @@ +* ZDWM =wm_policy_apply_command()= 伪代码骨架 + +这份文档给出 =wm_policy_apply_command()= 的最小实现骨架。目标是把实际代码的控制流固定住,使其和 [[file:WM_COMMAND_RULES.org][WM_COMMAND_RULES.org]] 中定义的命令规则一一对应。 + +* 函数职责 + +=wm_policy_apply_command()= 只做四件事: + +1. 校验命令输入是否合法。 +2. 修改 =wm_state_t= 中的运行状态。 +3. 向 =wm_plan_t= 记录脏标记和后端副作用。 +4. 在状态真的发生变化时递增 =state.generation= 。 + +它不负责: + +1. 从平台读取事件。 +2. 直接调用 X11/Wayland API。 +3. 直接执行渲染。 +4. 直接格式化状态栏文本。 + +* 顶层控制流 + +建议骨架如下: + +#+BEGIN_SRC c +bool wm_policy_apply_command(wm_state_t *state, + const wm_layout_registry_t *layouts, + const wm_command_t *command, + wm_plan_t *plan) { + bool changed = false; + + if (!state || !command || !plan) return false; + + switch (command->type) { + case WM_COMMAND_NONE: + return false; + + case WM_COMMAND_MANAGE_WINDOW: + changed = apply_manage_window(state, command, plan); + break; + + case WM_COMMAND_UNMANAGE_WINDOW: + changed = apply_unmanage_window(state, command, plan); + break; + + case WM_COMMAND_FOCUS_WINDOW: + changed = apply_focus_window(state, command, plan); + break; + + case WM_COMMAND_FOCUS_DIRECTION: + changed = apply_focus_direction(state, command, plan); + break; + + case WM_COMMAND_RAISE_WINDOW: + changed = apply_raise_window(state, command, plan); + break; + + case WM_COMMAND_LOWER_WINDOW: + changed = apply_lower_window(state, command, plan); + break; + + case WM_COMMAND_SWITCH_WORKSPACE: + changed = apply_switch_workspace(state, command, plan); + break; + + case WM_COMMAND_SEND_WINDOW_TO_WORKSPACE: + changed = apply_send_window_to_workspace(state, command, plan); + break; + + case WM_COMMAND_SEND_WINDOW_TO_OUTPUT: + changed = apply_send_window_to_output(state, command, plan); + break; + + case WM_COMMAND_TOGGLE_FLOATING: + changed = apply_toggle_floating(state, command, plan); + break; + + case WM_COMMAND_TOGGLE_MAXIMIZE: + changed = apply_toggle_maximize(state, command, plan); + break; + + case WM_COMMAND_TOGGLE_FULLSCREEN: + changed = apply_toggle_fullscreen(state, command, plan); + break; + + case WM_COMMAND_TOGGLE_MINIMIZE: + changed = apply_toggle_minimize(state, command, plan); + break; + + case WM_COMMAND_MOVE_FLOATING_WINDOW: + changed = apply_move_floating_window(state, command, plan); + break; + + case WM_COMMAND_RESIZE_FLOATING_WINDOW: + changed = apply_resize_floating_window(state, command, plan); + break; + + case WM_COMMAND_SET_LAYOUT: + changed = apply_set_layout(state, layouts, command, plan); + break; + + case WM_COMMAND_REDRAW: + changed = apply_redraw(state, command, plan); + break; + + case WM_COMMAND_QUIT: + changed = apply_quit(state, command, plan); + break; + } + + if (changed) state->generation++; + return changed; +} +#+END_SRC + +* 辅助函数建议 + +建议先把这些低层辅助函数写出来,避免在每个命令分支中重复拼逻辑: + +#+BEGIN_SRC c +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 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 void mark_layout_dirty(wm_plan_t *plan); +static void mark_stack_dirty(wm_plan_t *plan); +static void mark_render_dirty(wm_plan_t *plan); +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); +#+END_SRC + +* 命令分支骨架 + +** =apply_manage_window()= + +#+BEGIN_SRC c +static bool apply_manage_window(wm_state_t *state, + const wm_command_t *command, + wm_plan_t *plan) { + wm_window_id_t id = command->as.manage_window.window_id; + wm_workspace_id_t ws_id = command->as.manage_window.workspace_id; + + if (wm_state_find_window_const(state, id)) return false; + wm_workspace_t *ws = require_workspace(state, ws_id); + if (!ws) return false; + + wm_window_t win = { + .id = id, + .workspace_id = ws_id, + .geometry_mode = WM_GEOMETRY_NORMAL, + .floating = false, + .sticky = false, + .urgent = false, + }; + + if (!state_append_window(state, win)) return false; + if (!stack_append(state, id)) return false; + + ws->focused_window_id = id; + + plan->dirty_flags |= WM_DIRTY_STATE | WM_DIRTY_LAYOUT | + WM_DIRTY_STACK | WM_DIRTY_RENDER; + push_restack_effect(state, plan); + push_render_all_outputs(state, plan); + return true; +} +#+END_SRC + +** =apply_unmanage_window()= + +#+BEGIN_SRC c +static bool apply_unmanage_window(wm_state_t *state, + const wm_command_t *command, + wm_plan_t *plan) { + wm_window_id_t id = command->as.unmanage_window.window_id; + wm_window_t *win = require_window(state, id); + if (!win) return false; + + wm_workspace_t *ws = require_workspace(state, win->workspace_id); + if (!ws) return false; + + if (!stack_remove(state, id)) return false; + if (!state_remove_window(state, id)) return false; + + if (ws->focused_window_id == id) maybe_refocus_workspace(state, ws); + + plan->dirty_flags |= WM_DIRTY_STATE | WM_DIRTY_LAYOUT | + WM_DIRTY_STACK | WM_DIRTY_RENDER; + push_restack_effect(state, plan); + push_render_all_outputs(state, plan); + return true; +} +#+END_SRC + +** =apply_focus_window()= + +#+BEGIN_SRC c +static bool apply_focus_window(wm_state_t *state, + const wm_command_t *command, + wm_plan_t *plan) { + wm_window_id_t id = command->as.focus_window.window_id; + wm_window_t *win = require_window(state, id); + if (!win) return false; + + wm_workspace_t *ws = require_workspace(state, win->workspace_id); + if (!ws) return false; + if (ws->focused_window_id == id) return false; + + ws->focused_window_id = id; + + plan->dirty_flags |= WM_DIRTY_RENDER; + return true; +} +#+END_SRC + +** =apply_focus_direction()= + +#+BEGIN_SRC c +static bool apply_focus_direction(wm_state_t *state, + const wm_command_t *command, + wm_plan_t *plan) { + wm_output_id_t output_id = command->as.focus_direction.output_id; + wm_output_t *out = require_output(state, output_id); + if (!out) return false; + + wm_workspace_t *ws = require_workspace(state, out->current_workspace_id); + if (!ws) return false; + + wm_window_id_t next = + find_focus_candidate_in_workspace(state, ws, + command->as.focus_direction.direction); + if (!next || ws->focused_window_id == next) return false; + + ws->focused_window_id = next; + plan->dirty_flags |= WM_DIRTY_RENDER; + return true; +} +#+END_SRC + +** =apply_raise_window()= 和 =apply_lower_window()= + +#+BEGIN_SRC c +static bool apply_raise_window(wm_state_t *state, + const wm_command_t *command, + wm_plan_t *plan) { + if (!stack_raise(state, command->as.raise_window.window_id)) return false; + plan->dirty_flags |= WM_DIRTY_STACK | WM_DIRTY_RENDER; + push_restack_effect(state, plan); + return true; +} + +static bool apply_lower_window(wm_state_t *state, + const wm_command_t *command, + wm_plan_t *plan) { + if (!stack_lower(state, command->as.lower_window.window_id)) return false; + plan->dirty_flags |= WM_DIRTY_STACK | WM_DIRTY_RENDER; + push_restack_effect(state, plan); + return true; +} +#+END_SRC + +** =apply_switch_workspace()= + +#+BEGIN_SRC c +static bool apply_switch_workspace(wm_state_t *state, + const wm_command_t *command, + wm_plan_t *plan) { + 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)) { + 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; + plan->dirty_flags |= WM_DIRTY_OUTPUT | WM_DIRTY_LAYOUT | WM_DIRTY_RENDER; + push_render_all_outputs(state, plan); + return true; +} +#+END_SRC + +** =apply_send_window_to_workspace()= + +#+BEGIN_SRC c +static bool apply_send_window_to_workspace(wm_state_t *state, + const wm_command_t *command, + wm_plan_t *plan) { + wm_window_t *win = + require_window(state, command->as.send_window_to_workspace.window_id); + if (!win) return false; + + wm_workspace_id_t dst_id = command->as.send_window_to_workspace.workspace_id; + if (!require_workspace(state, dst_id)) return false; + if (win->workspace_id == dst_id) return false; + + wm_workspace_t *src = require_workspace(state, win->workspace_id); + win->workspace_id = dst_id; + + if (src && src->focused_window_id == win->id) maybe_refocus_workspace(state, src); + + plan->dirty_flags |= WM_DIRTY_STATE | WM_DIRTY_LAYOUT | WM_DIRTY_RENDER; + push_render_all_outputs(state, plan); + return true; +} +#+END_SRC + +** =apply_send_window_to_output()= + +#+BEGIN_SRC c +static bool apply_send_window_to_output(wm_state_t *state, + const wm_command_t *command, + wm_plan_t *plan) { + wm_window_t *win = + require_window(state, command->as.send_window_to_output.window_id); + wm_output_t *out = + require_output(state, command->as.send_window_to_output.output_id); + if (!win || !out) return false; + + if (command->as.send_window_to_output.policy == + WM_CROSS_OUTPUT_KEEP_WORKSPACE) { + return false; + } + + wm_command_t forwarded = { + .type = WM_COMMAND_SEND_WINDOW_TO_WORKSPACE, + .as.send_window_to_workspace = { + .window_id = win->id, + .workspace_id = out->current_workspace_id, + }, + }; + return apply_send_window_to_workspace(state, &forwarded, plan); +} +#+END_SRC + +** =apply_toggle_floating()= + +#+BEGIN_SRC c +static bool apply_toggle_floating(wm_state_t *state, + const wm_command_t *command, + wm_plan_t *plan) { + wm_window_t *win = require_window(state, command->as.toggle_floating.window_id); + if (!win) return false; + + if (!win->floating) { + win->floating = true; + win->float_rect = win->frame_rect; + } else { + win->floating = false; + } + + plan->dirty_flags |= WM_DIRTY_LAYOUT | WM_DIRTY_RENDER; + push_render_all_outputs(state, plan); + return true; +} +#+END_SRC + +** =apply_toggle_maximize()= + +#+BEGIN_SRC c +static bool apply_toggle_maximize(wm_state_t *state, + const wm_command_t *command, + wm_plan_t *plan) { + wm_window_t *win = require_window(state, command->as.toggle_maximize.window_id); + if (!win) return false; + + if (win->geometry_mode == WM_GEOMETRY_MAXIMIZED) { + win->geometry_mode = WM_GEOMETRY_NORMAL; + } else { + win->geometry_mode = WM_GEOMETRY_MAXIMIZED; + } + + plan->dirty_flags |= WM_DIRTY_LAYOUT | WM_DIRTY_RENDER; + push_render_all_outputs(state, plan); + return true; +} +#+END_SRC + +实际实现中应按 [[file:WM_COMMAND_RULES.org][WM_COMMAND_RULES.org]] 处理与 =FULLSCREEN= 和 =MINIMIZED= 的切换优先级。 + +** =apply_toggle_fullscreen()= + +#+BEGIN_SRC c +static bool apply_toggle_fullscreen(wm_state_t *state, + const wm_command_t *command, + wm_plan_t *plan) { + wm_window_t *win = + require_window(state, command->as.toggle_fullscreen.window_id); + if (!win) return false; + + if (win->geometry_mode == WM_GEOMETRY_FULLSCREEN) { + win->geometry_mode = WM_GEOMETRY_NORMAL; + } else { + win->geometry_mode = WM_GEOMETRY_FULLSCREEN; + } + + plan->dirty_flags |= WM_DIRTY_LAYOUT | WM_DIRTY_RENDER; + push_render_all_outputs(state, plan); + return true; +} +#+END_SRC + +** =apply_toggle_minimize()= + +#+BEGIN_SRC c +static bool apply_toggle_minimize(wm_state_t *state, + 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; + + if (win->geometry_mode == WM_GEOMETRY_MINIMIZED) { + win->geometry_mode = WM_GEOMETRY_NORMAL; + } else { + win->geometry_mode = WM_GEOMETRY_MINIMIZED; + } + + plan->dirty_flags |= WM_DIRTY_LAYOUT | WM_DIRTY_RENDER; + push_render_all_outputs(state, plan); + return true; +} +#+END_SRC + +** =apply_move_floating_window()= + +#+BEGIN_SRC c +static bool apply_move_floating_window(wm_state_t *state, + const wm_command_t *command, + wm_plan_t *plan) { + wm_window_t *win = + require_window(state, command->as.move_floating_window.window_id); + if (!win || !win->floating) return false; + + win->float_rect.x += command->as.move_floating_window.dx; + win->float_rect.y += command->as.move_floating_window.dy; + + 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); + if (out && + command->as.move_floating_window.policy == + WM_CROSS_OUTPUT_MOVE_TO_TARGET_WORKSPACE) { + win->workspace_id = out->current_workspace_id; + } + } + + plan->dirty_flags |= WM_DIRTY_LAYOUT | WM_DIRTY_RENDER; + push_render_all_outputs(state, plan); + return true; +} +#+END_SRC + +** =apply_resize_floating_window()= + +#+BEGIN_SRC c +static bool apply_resize_floating_window(wm_state_t *state, + const wm_command_t *command, + wm_plan_t *plan) { + wm_window_t *win = + require_window(state, command->as.resize_floating_window.window_id); + if (!win || !win->floating) return false; + + win->float_rect.w = + clamp_u16((int)win->float_rect.w + command->as.resize_floating_window.dw, + WM_MIN_WINDOW_W, UINT16_MAX); + win->float_rect.h = + clamp_u16((int)win->float_rect.h + command->as.resize_floating_window.dh, + WM_MIN_WINDOW_H, UINT16_MAX); + + plan->dirty_flags |= WM_DIRTY_LAYOUT | WM_DIRTY_RENDER; + push_render_all_outputs(state, plan); + return true; +} +#+END_SRC + +** =apply_set_layout()= + +#+BEGIN_SRC c +static bool apply_set_layout(wm_state_t *state, + const wm_layout_registry_t *layouts, + const wm_command_t *command, + wm_plan_t *plan) { + wm_workspace_t *ws = + require_workspace(state, command->as.set_layout.workspace_id); + if (!ws) return false; + if (!wm_layout_lookup(layouts, 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; + plan->dirty_flags |= WM_DIRTY_LAYOUT | WM_DIRTY_RENDER; + push_render_all_outputs(state, plan); + return true; +} +#+END_SRC + +** =apply_redraw()= + +#+BEGIN_SRC c +static bool apply_redraw(wm_state_t *state, + const wm_command_t *command, + wm_plan_t *plan) { + (void)state; + (void)command; + plan->dirty_flags |= WM_DIRTY_RENDER; + push_render_all_outputs(state, plan); + return true; +} +#+END_SRC + +** =apply_quit()= + +#+BEGIN_SRC c +static bool apply_quit(wm_state_t *state, + const wm_command_t *command, + wm_plan_t *plan) { + (void)state; + (void)command; + plan->dirty_flags |= WM_DIRTY_NONE; + return true; +} +#+END_SRC + +* 推荐的后处理步骤 + +在 =apply_*()= 返回后,建议由上层统一做这些事,而不是在每个分支里直接跑布局: + +1. 如果有 =WM_DIRTY_LAYOUT= ,则对每个 output 的当前 workspace 重新计算 layout。 +2. 根据窗口的 =geometry_mode= 、=floating= 和 layout 结果解析最终矩形。 +3. 生成 =WM_EFFECT_CONFIGURE_WINDOW= 。 +4. 若有 =WM_DIRTY_STACK= ,生成 =WM_EFFECT_RESTACK_WINDOWS= 。 +5. 若有 =WM_DIRTY_RENDER= ,生成 =WM_EFFECT_RENDER_OUTPUT= 。 + +* 为什么要拆成多个 =apply_*()= + +1. 便于单元测试,每个命令可以单独测试。 +2. 便于和 =WM_COMMAND_RULES.org= 对照审查。 +3. 便于以后替换部分策略,例如 =focus raises= 或 sticky 参与焦点候选的规则。 +4. 便于将复杂逻辑下沉到更小的辅助函数,而不是把 =wm_policy_apply_command()= 写成一个超长 switch。 diff --git a/docs/min_core_draft/wm_backend.h b/docs/min_core_draft/wm_backend.h new file mode 100644 index 0000000..257599a --- /dev/null +++ b/docs/min_core_draft/wm_backend.h @@ -0,0 +1,19 @@ +#pragma once + +#include "wm_event.h" +#include "wm_plan.h" + +typedef struct wm_backend_t wm_backend_t; + +typedef struct wm_backend_api_t { + bool (*init)(wm_backend_t *backend); + bool (*next_event)(wm_backend_t *backend, wm_event_t *out); + bool (*apply_effect)(wm_backend_t *backend, const wm_effect_t *effect); + void (*flush)(wm_backend_t *backend); + void (*shutdown)(wm_backend_t *backend); +} wm_backend_api_t; + +struct wm_backend_t { + const wm_backend_api_t *api; + void *impl; +}; diff --git a/docs/min_core_draft/wm_command.h b/docs/min_core_draft/wm_command.h new file mode 100644 index 0000000..99203de --- /dev/null +++ b/docs/min_core_draft/wm_command.h @@ -0,0 +1,108 @@ +#pragma once + +#include "wm_types.h" + +typedef enum wm_command_type_t { + WM_COMMAND_NONE, + WM_COMMAND_MANAGE_WINDOW, + WM_COMMAND_UNMANAGE_WINDOW, + WM_COMMAND_FOCUS_WINDOW, + WM_COMMAND_FOCUS_DIRECTION, + WM_COMMAND_RAISE_WINDOW, + WM_COMMAND_LOWER_WINDOW, + WM_COMMAND_SWITCH_WORKSPACE, + WM_COMMAND_SEND_WINDOW_TO_WORKSPACE, + WM_COMMAND_SEND_WINDOW_TO_OUTPUT, + WM_COMMAND_TOGGLE_FLOATING, + WM_COMMAND_TOGGLE_MAXIMIZE, + WM_COMMAND_TOGGLE_FULLSCREEN, + WM_COMMAND_TOGGLE_MINIMIZE, + WM_COMMAND_MOVE_FLOATING_WINDOW, + WM_COMMAND_RESIZE_FLOATING_WINDOW, + WM_COMMAND_SET_LAYOUT, + WM_COMMAND_REDRAW, + WM_COMMAND_QUIT, +} wm_command_type_t; + +typedef struct wm_command_t { + wm_command_type_t type; + union { + struct { + wm_window_id_t window_id; + wm_workspace_id_t workspace_id; + } manage_window; + + struct { + wm_window_id_t window_id; + } unmanage_window; + + struct { + wm_window_id_t window_id; + } focus_window; + + struct { + wm_output_id_t output_id; + wm_focus_direction_t direction; + } focus_direction; + + struct { + wm_window_id_t window_id; + } raise_window; + + struct { + wm_window_id_t window_id; + } lower_window; + + struct { + wm_output_id_t output_id; + wm_workspace_id_t workspace_id; + } switch_workspace; + + struct { + wm_window_id_t window_id; + wm_workspace_id_t workspace_id; + } send_window_to_workspace; + + struct { + wm_window_id_t window_id; + wm_output_id_t output_id; + wm_cross_output_policy_t policy; + } send_window_to_output; + + struct { + wm_window_id_t window_id; + } toggle_floating; + + struct { + wm_window_id_t window_id; + } toggle_maximize; + + struct { + wm_window_id_t window_id; + } toggle_fullscreen; + + struct { + wm_window_id_t window_id; + } toggle_minimize; + + struct { + wm_window_id_t window_id; + int16_t dx; + int16_t dy; + bool commit_workspace_change; + wm_point_t anchor; + wm_cross_output_policy_t policy; + } move_floating_window; + + struct { + wm_window_id_t window_id; + int16_t dw; + int16_t dh; + } resize_floating_window; + + struct { + wm_workspace_id_t workspace_id; + wm_layout_id_t layout_id; + } set_layout; + } as; +} wm_command_t; diff --git a/docs/min_core_draft/wm_event.h b/docs/min_core_draft/wm_event.h new file mode 100644 index 0000000..76a9ac9 --- /dev/null +++ b/docs/min_core_draft/wm_event.h @@ -0,0 +1,95 @@ +#pragma once + +#include "wm_types.h" + +typedef enum wm_event_type_t { + WM_EVENT_NONE, + WM_EVENT_KEY_PRESS, + WM_EVENT_BUTTON_PRESS, + WM_EVENT_BUTTON_RELEASE, + WM_EVENT_POINTER_MOTION, + WM_EVENT_MAP_REQUEST, + WM_EVENT_UNMAP_NOTIFY, + WM_EVENT_DESTROY_NOTIFY, + WM_EVENT_WINDOW_METADATA_CHANGED, + WM_EVENT_CONFIGURE_REQUEST, + WM_EVENT_OUTPUT_CHANGED, + WM_EVENT_TIMER_TICK, + WM_EVENT_STATUS_TICK, + WM_EVENT_QUIT, +} wm_event_type_t; + +typedef struct wm_key_press_event_t { + uint32_t keycode; + uint32_t modifiers; +} wm_key_press_event_t; + +typedef struct wm_button_event_t { + uint32_t button; + uint32_t modifiers; + wm_point_t root; + wm_window_id_t target_window_id; +} wm_button_event_t; + +typedef struct wm_pointer_motion_event_t { + wm_point_t root; + wm_window_id_t target_window_id; +} 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_unmap_notify_event_t { + wm_window_id_t window_id; +} wm_unmap_notify_event_t; + +typedef struct wm_destroy_notify_event_t { + wm_window_id_t window_id; +} wm_destroy_notify_event_t; + +typedef enum wm_window_meta_changed_flags_t { + WM_WINDOW_META_CHANGED_NONE = 0, + WM_WINDOW_META_CHANGED_TITLE = 1u << 0, + WM_WINDOW_META_CHANGED_APP_ID = 1u << 1, + WM_WINDOW_META_CHANGED_CLASS = 1u << 2, + WM_WINDOW_META_CHANGED_INSTANCE = 1u << 3, +} wm_window_meta_changed_flags_t; + +typedef struct wm_window_metadata_changed_event_t { + wm_window_id_t window_id; + uint32_t changed_fields; +} wm_window_metadata_changed_event_t; + +typedef struct wm_configure_request_event_t { + wm_window_id_t window_id; + 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_timer_tick_event_t { + uint64_t now_ms; +} wm_timer_tick_event_t; + +typedef struct wm_event_t { + wm_event_type_t type; + uint64_t ts_ms; + union { + wm_key_press_event_t key_press; + wm_button_event_t button_press; + wm_button_event_t 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_metadata_changed_event_t window_metadata_changed; + wm_configure_request_event_t configure_request; + wm_output_changed_event_t output_changed; + wm_timer_tick_event_t timer_tick; + } as; +} wm_event_t; diff --git a/docs/min_core_draft/wm_layout.h b/docs/min_core_draft/wm_layout.h new file mode 100644 index 0000000..1946075 --- /dev/null +++ b/docs/min_core_draft/wm_layout.h @@ -0,0 +1,58 @@ +#pragma once + +#include "wm_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_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; + +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; + const char *name; + const char *symbol_text; + const char *symbol_icon_path; + 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); + +bool wm_layout_register(wm_layout_registry_t *registry, wm_layout_id_t id, + wm_layout_fn fn); +wm_layout_fn wm_layout_lookup(const wm_layout_registry_t *registry, + wm_layout_id_t id); diff --git a/docs/min_core_draft/wm_plan.h b/docs/min_core_draft/wm_plan.h new file mode 100644 index 0000000..114c3eb --- /dev/null +++ b/docs/min_core_draft/wm_plan.h @@ -0,0 +1,67 @@ +#pragma once + +#include "wm_types.h" + +typedef enum wm_dirty_flags_t { + WM_DIRTY_NONE = 0, + WM_DIRTY_STATE = 1u << 0, + WM_DIRTY_LAYOUT = 1u << 1, + WM_DIRTY_STACK = 1u << 2, + WM_DIRTY_OUTPUT = 1u << 3, + WM_DIRTY_RENDER = 1u << 4, +} wm_dirty_flags_t; + +typedef enum wm_effect_type_t { + WM_EFFECT_NONE, + WM_EFFECT_MAP_WINDOW, + WM_EFFECT_UNMAP_WINDOW, + WM_EFFECT_CONFIGURE_WINDOW, + WM_EFFECT_FOCUS_WINDOW, + WM_EFFECT_RESTACK_WINDOWS, + WM_EFFECT_RENDER_OUTPUT, +} wm_effect_type_t; + +typedef struct wm_effect_t { + wm_effect_type_t type; + union { + struct { + wm_window_id_t window_id; + } map_window; + + struct { + wm_window_id_t window_id; + } unmap_window; + + struct { + wm_window_id_t window_id; + wm_rect_t rect; + uint16_t border_width; + } configure_window; + + struct { + wm_window_id_t window_id; + } focus_window; + + struct { + const wm_window_id_t *stack_order; + size_t stack_count; + } restack_windows; + + struct { + wm_output_id_t output_id; + } render_output; + } as; +} wm_effect_t; + +typedef struct wm_plan_t { + wm_effect_t *effects; + size_t effect_count; + size_t effect_capacity; + uint32_t dirty_flags; +} wm_plan_t; + +void wm_plan_init(wm_plan_t *plan); +void wm_plan_reset(wm_plan_t *plan); +void wm_plan_shutdown(wm_plan_t *plan); + +bool wm_plan_push_effect(wm_plan_t *plan, wm_effect_t effect); diff --git a/docs/min_core_draft/wm_policy.h b/docs/min_core_draft/wm_policy.h new file mode 100644 index 0000000..b8d5e6a --- /dev/null +++ b/docs/min_core_draft/wm_policy.h @@ -0,0 +1,26 @@ +#pragma once + +#include "wm_command.h" +#include "wm_event.h" +#include "wm_layout.h" +#include "wm_plan.h" +#include "wm_state.h" + +typedef struct wm_command_buffer_t { + wm_command_t *items; + size_t count; + size_t capacity; +} wm_command_buffer_t; + +void wm_command_buffer_init(wm_command_buffer_t *buffer); +void wm_command_buffer_reset(wm_command_buffer_t *buffer); +void wm_command_buffer_shutdown(wm_command_buffer_t *buffer); + +bool wm_command_buffer_push(wm_command_buffer_t *buffer, wm_command_t command); + +bool wm_policy_route_event(const wm_state_t *state, const wm_event_t *event, + wm_command_buffer_t *out); + +bool wm_policy_apply_command(wm_state_t *state, + 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 new file mode 100644 index 0000000..8c42c62 --- /dev/null +++ b/docs/min_core_draft/wm_runtime.h @@ -0,0 +1,28 @@ +#pragma once + +#include "wm_backend.h" +#include "wm_layout.h" +#include "wm_plan.h" +#include "wm_policy.h" +#include "wm_state.h" + +typedef struct wm_runtime_t { + bool running; + + wm_state_t state; + wm_plan_t plan; + wm_command_buffer_t command_buffer; + wm_layout_registry_t layouts; + wm_backend_t backend; +} wm_runtime_t; + +bool wm_runtime_init(wm_runtime_t *runtime, wm_backend_t backend); + +/* + * 处理单个事件,方便做无平台依赖的单元测试。 + */ +bool wm_runtime_process_event(wm_runtime_t *runtime, const wm_event_t *event); + +void wm_runtime_run(wm_runtime_t *runtime); +void wm_runtime_stop(wm_runtime_t *runtime); +void wm_runtime_shutdown(wm_runtime_t *runtime); diff --git a/docs/min_core_draft/wm_state.h b/docs/min_core_draft/wm_state.h new file mode 100644 index 0000000..aa1d418 --- /dev/null +++ b/docs/min_core_draft/wm_state.h @@ -0,0 +1,103 @@ +#pragma once + +#include "wm_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; + + /* + * false: 由当前 workspace 的 layout 决定基础几何 + * true : 使用 float_rect 作为基础几何 + */ + bool floating; + + /* + * sticky 只影响可见性,不改变 workspace 归属。 + */ + bool sticky; + bool urgent; + + /* + * floating 模式的基准矩形。退出 maximize/fullscreen 后可以回到这里。 + */ + wm_rect_t float_rect; + + /* + * 最近一次实际提交给后端的矩形。 + */ + wm_rect_t frame_rect; +} wm_window_t; + +typedef struct wm_workspace_t { + wm_workspace_id_t id; + + /* + * 这里只保留运行状态,不存名称、bar 符号等描述信息。 + */ + wm_layout_id_t layout_id; + wm_window_id_t focused_window_id; +} wm_workspace_t; + +typedef struct wm_output_t { + wm_output_id_t id; + wm_rect_t geometry; + wm_rect_t workarea; + wm_workspace_id_t current_workspace_id; +} wm_output_t; + +typedef struct wm_state_t { + wm_window_t *windows; + size_t window_count; + + /* + * 全局 z-order,从下到上。每个受管窗口最多出现一次。 + */ + wm_window_id_t *stack_order; + size_t stack_count; + + wm_workspace_t *workspaces; + size_t workspace_count; + + wm_output_t *outputs; + size_t output_count; + + /* + * 成功提交一次状态变更后递增。 + */ + uint64_t generation; +} wm_state_t; + +void wm_state_init(wm_state_t *state); +void wm_state_shutdown(wm_state_t *state); + +wm_window_t *wm_state_find_window(wm_state_t *state, wm_window_id_t id); +const wm_window_t *wm_state_find_window_const(const wm_state_t *state, + wm_window_id_t id); + +wm_workspace_t *wm_state_find_workspace(wm_state_t *state, + wm_workspace_id_t id); +const wm_workspace_t *wm_state_find_workspace_const(const wm_state_t *state, + wm_workspace_id_t id); + +wm_output_t *wm_state_find_output(wm_state_t *state, wm_output_id_t id); +const wm_output_t *wm_state_find_output_const(const wm_state_t *state, + wm_output_id_t id); + +const wm_output_t *wm_state_find_output_by_point(const wm_state_t *state, + wm_point_t point); + +bool wm_state_workspace_is_visible(const wm_state_t *state, + wm_workspace_id_t workspace_id); + +bool wm_state_window_should_be_visible(const wm_state_t *state, + const wm_window_t *window); + +bool wm_state_window_should_be_visible_on_output(const wm_state_t *state, + const wm_window_t *window, + const wm_output_t *output); diff --git a/docs/min_core_draft/wm_types.h b/docs/min_core_draft/wm_types.h new file mode 100644 index 0000000..d883ae2 --- /dev/null +++ b/docs/min_core_draft/wm_types.h @@ -0,0 +1,39 @@ +#pragma once + +#include +#include +#include + +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; + +typedef struct wm_point_t { + int16_t x; + int16_t y; +} wm_point_t; + +typedef struct wm_rect_t { + int16_t x; + int16_t y; + uint16_t w; + uint16_t h; +} 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; diff --git a/docs/min_core_draft/wm_window_meta.h b/docs/min_core_draft/wm_window_meta.h new file mode 100644 index 0000000..cdd2aa0 --- /dev/null +++ b/docs/min_core_draft/wm_window_meta.h @@ -0,0 +1,29 @@ +#pragma once + +#include "wm_types.h" + +typedef struct wm_window_meta_t { + wm_window_id_t id; + char *title; + char *app_id; + char *class_name; + char *instance_name; +} wm_window_meta_t; + +typedef struct wm_window_meta_store_t { + wm_window_meta_t *items; + size_t count; + size_t capacity; +} wm_window_meta_store_t; + +void wm_window_meta_store_init(wm_window_meta_store_t *store); +void wm_window_meta_store_shutdown(wm_window_meta_store_t *store); + +wm_window_meta_t *wm_window_meta_find(wm_window_meta_store_t *store, + wm_window_id_t id); +const wm_window_meta_t *wm_window_meta_find_const( + const wm_window_meta_store_t *store, wm_window_id_t id); + +bool wm_window_meta_upsert(wm_window_meta_store_t *store, + const wm_window_meta_t *meta); +bool wm_window_meta_remove(wm_window_meta_store_t *store, wm_window_id_t id); diff --git a/docs/min_core_draft/wm_workspace_desc.h b/docs/min_core_draft/wm_workspace_desc.h new file mode 100644 index 0000000..510d9ef --- /dev/null +++ b/docs/min_core_draft/wm_workspace_desc.h @@ -0,0 +1,37 @@ +#pragma once + +#include "wm_types.h" + +typedef struct wm_workspace_desc_t { + wm_workspace_id_t id; + + /* + * 人类可读名称,例如 "web"、"code"、"chat"。 + */ + const char *name; + + /* + * bar 中展示的文本符号,例如 "1"、"2"、"M"。 + */ + const char *symbol_text; + + /* + * 可选图标路径。若为空,则使用 symbol_text。 + */ + const char *symbol_icon_path; +} wm_workspace_desc_t; + +typedef struct wm_workspace_desc_table_t { + wm_workspace_desc_t *items; + size_t count; + size_t capacity; +} wm_workspace_desc_table_t; + +void wm_workspace_desc_table_init(wm_workspace_desc_table_t *table); +void wm_workspace_desc_table_shutdown(wm_workspace_desc_table_t *table); + +const wm_workspace_desc_t *wm_workspace_desc_lookup( + const wm_workspace_desc_table_t *table, wm_workspace_id_t id); + +bool wm_workspace_desc_register(wm_workspace_desc_table_t *table, + wm_workspace_desc_t desc);