Compare commits
9 Commits
main
...
59a6177770
| Author | SHA1 | Date | |
|---|---|---|---|
|
59a6177770
|
|||
|
6335013172
|
|||
|
cfbc66cf49
|
|||
|
57c6a831d4
|
|||
|
499030bd04
|
|||
|
1bee0de9f9
|
|||
|
7de9cd500b
|
|||
|
329943ab3e
|
|||
|
f106857cd8
|
1230
docs/ARCHITECTURE_REFACTORING_PLAN.org
Normal file
1230
docs/ARCHITECTURE_REFACTORING_PLAN.org
Normal file
File diff suppressed because it is too large
Load Diff
650
docs/MINIMAL_CORE_WITH_EXTENSIONS.org
Normal file
650
docs/MINIMAL_CORE_WITH_EXTENSIONS.org
Normal file
@@ -0,0 +1,650 @@
|
||||
# ZDWM 最小核心 + 外围扩展设计
|
||||
|
||||
* 设计原则
|
||||
|
||||
1. *核心只做一件事*:管理窗口、显示器、工作区
|
||||
2. *所有扩展都通过插件*:状态栏、配置、快捷键、布局算法
|
||||
3. *核心提供稳定的钩子*:插件可以在关键点介入
|
||||
4. *零运行时开销*:扩展不增加核心复杂度
|
||||
|
||||
* 最小核心定义
|
||||
|
||||
#+BEGIN_SRC c
|
||||
// src/core/zdwm.h
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
// ========== 基础类型 ==========
|
||||
typedef struct {
|
||||
int x, y;
|
||||
unsigned int width, height;
|
||||
} rect_t;
|
||||
|
||||
typedef struct {
|
||||
const char *name;
|
||||
const char *instance;
|
||||
} wm_class_t;
|
||||
|
||||
// ========== 窗口 ==========
|
||||
typedef uint64_t wm_window_id_t;
|
||||
|
||||
typedef struct {
|
||||
wm_window_id_t id;
|
||||
rect_t geometry;
|
||||
wm_class_t class;
|
||||
bool mapped;
|
||||
bool floating;
|
||||
bool fullscreen;
|
||||
bool focused;
|
||||
// ... 其他基本状态
|
||||
} wm_window_t;
|
||||
|
||||
// ========== 工作区 ==========
|
||||
typedef uint32_t wm_workspace_id_t;
|
||||
|
||||
typedef struct {
|
||||
wm_workspace_id_t id;
|
||||
char *name;
|
||||
wm_window_id_t *windows;
|
||||
size_t window_count;
|
||||
wm_window_id_t focused_window;
|
||||
} wm_workspace_t;
|
||||
|
||||
// ========== 显示器 ==========
|
||||
typedef uint32_t wm_output_id_t;
|
||||
|
||||
typedef struct {
|
||||
wm_output_id_t id;
|
||||
rect_t geometry;
|
||||
rect_t workarea;
|
||||
wm_workspace_id_t current_workspace;
|
||||
} wm_output_t;
|
||||
|
||||
// ========== 核心状态 ==========
|
||||
typedef struct {
|
||||
wm_window_t *windows;
|
||||
size_t window_count;
|
||||
|
||||
wm_workspace_t *workspaces;
|
||||
size_t workspace_count;
|
||||
wm_workspace_id_t current_workspace;
|
||||
|
||||
wm_output_t *outputs;
|
||||
size_t output_count;
|
||||
} wm_state_t;
|
||||
|
||||
// ========== 核心 API ==========
|
||||
// 初始化和清理
|
||||
bool wm_init(void);
|
||||
void wm_shutdown(void);
|
||||
|
||||
// 主循环
|
||||
void wm_run(void);
|
||||
|
||||
// 查询接口
|
||||
wm_window_t* wm_find_window(wm_window_id_t id);
|
||||
wm_workspace_t* wm_find_workspace(wm_workspace_id_t id);
|
||||
wm_output_t* wm_find_output(wm_output_id_t id);
|
||||
|
||||
// 窗口操作
|
||||
void wm_manage_window(wm_window_id_t id);
|
||||
void wm_unmanage_window(wm_window_id_t id);
|
||||
void wm_focus_window(wm_window_id_t id);
|
||||
void wm_set_window_floating(wm_window_id_t id, bool floating);
|
||||
|
||||
// 工作区操作
|
||||
void wm_switch_workspace(wm_workspace_id_t id);
|
||||
void wm_send_window_to_workspace(wm_window_id_t window, wm_workspace_id_t workspace);
|
||||
|
||||
// 显示器操作
|
||||
void wm_switch_output(wm_output_id_t id);
|
||||
#+END_SRC
|
||||
|
||||
* 扩展接口设计
|
||||
|
||||
**1. 事件钩子系统**
|
||||
|
||||
#+BEGIN_SRC c
|
||||
// src/core/hooks.h
|
||||
|
||||
// 钩子类型(关键事件点)
|
||||
typedef enum wm_hook_type_t {
|
||||
HOOK_WINDOW_MANAGE, // 窗口被管理时
|
||||
HOOK_WINDOW_UNMANAGE, // 窗口取消管理时
|
||||
HOOK_WINDOW_FOCUS, // 窗口焦点变化时
|
||||
HOOK_WORKSPACE_SWITCH, // 工作区切换时
|
||||
HOOK_LAYOUT_CALCULATE, // 布局计算时
|
||||
HOOK_CONFIG_RELOAD, // 配置重载时
|
||||
HOOK_KEY_PRESS, // 键盘按下时
|
||||
HOOK_COUNT
|
||||
} wm_hook_type_t;
|
||||
|
||||
// 钩子回调函数类型
|
||||
typedef void (*wm_hook_window_manage_t)(wm_window_t *window, void *user_data);
|
||||
typedef void (*wm_hook_window_focus_t)(wm_window_t *old_focus, wm_window_t *new_focus, void *user_data);
|
||||
typedef void (*wm_hook_layout_calculate_t)(wm_workspace_t *workspace, rect_t *geometries, size_t count, void *user_data);
|
||||
typedef bool (*wm_hook_key_press_t)(uint32_t keycode, uint32_t modifiers, void *user_data);
|
||||
|
||||
// 钩子注册
|
||||
typedef struct {
|
||||
wm_hook_type_t type;
|
||||
void *callback;
|
||||
void *user_data;
|
||||
int priority; // 优先级:数字越小越先执行
|
||||
} wm_hook_t;
|
||||
|
||||
// 核心 API:注册钩子
|
||||
void wm_hook_register(wm_hook_type_t type, void *callback, void *user_data, int priority);
|
||||
void wm_hook_unregister(wm_hook_type_t type, void *callback);
|
||||
|
||||
// 核心 API:触发钩子
|
||||
void wm_hook_trigger_window_manage(wm_window_t *window);
|
||||
void wm_hook_trigger_window_focus(wm_window_t *old_focus, wm_window_t *new_focus);
|
||||
void wm_hook_trigger_layout_calculate(wm_workspace_t *workspace, rect_t *geometries, size_t count);
|
||||
bool wm_hook_trigger_key_press(uint32_t keycode, uint32_t modifiers);
|
||||
#+END_SRC
|
||||
|
||||
**2. 插件系统**
|
||||
|
||||
#+BEGIN_SRC c
|
||||
// src/core/plugin.h
|
||||
|
||||
// 插件接口
|
||||
typedef struct {
|
||||
const char *name;
|
||||
const char *version;
|
||||
|
||||
// 必须实现
|
||||
bool (*init)(void);
|
||||
void (*shutdown)(void);
|
||||
|
||||
// 可选实现
|
||||
void (*on_event)(wm_hook_type_t event, void *event_data);
|
||||
} wm_plugin_t;
|
||||
|
||||
// 核心 API:插件加载
|
||||
bool wm_plugin_load(wm_plugin_t *plugin);
|
||||
bool wm_plugin_unload(const char *name);
|
||||
#+END_SRC
|
||||
|
||||
* 扩展编写指南
|
||||
|
||||
**扩展1:布局算法(插件)**
|
||||
|
||||
#+BEGIN_SRC c
|
||||
// plugins/layout_tile.c
|
||||
|
||||
#include "plugins/layout.h"
|
||||
#include "core/hooks.h"
|
||||
|
||||
// 布局算法实现
|
||||
static void tile_calculate_layout(wm_workspace_t *workspace,
|
||||
rect_t *geometries,
|
||||
size_t count,
|
||||
void *user_data) {
|
||||
// 布局参数(可以通过配置获取)
|
||||
int gap = 10;
|
||||
int master_count = 1;
|
||||
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
if (i < master_count) {
|
||||
// 主窗口:左侧,占 60%
|
||||
geometries[i].x = workspace->outputs[0].x;
|
||||
geometries[i].y = workspace->outputs[0].y;
|
||||
geometries[i].width = workspace->outputs[0].width * 0.6 - gap;
|
||||
geometries[i].height = workspace->outputs[0].height;
|
||||
} else {
|
||||
// 堆叠窗口:右侧,占 40%
|
||||
geometries[i].x = workspace->outputs[0].x + workspace->outputs[0].width * 0.6 + gap;
|
||||
geometries[i].y = workspace->outputs[0].y + (i - master_count) * 20;
|
||||
geometries[i].width = workspace->outputs[0].width * 0.4 - gap;
|
||||
geometries[i].height = 20;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 插件初始化
|
||||
static bool layout_tile_init(void) {
|
||||
// 注册布局钩子
|
||||
wm_hook_register(HOOK_LAYOUT_CALCULATE, tile_calculate_layout, NULL, 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void layout_tile_shutdown(void) {
|
||||
// 自动取消注册
|
||||
}
|
||||
|
||||
// 插件定义
|
||||
wm_plugin_t plugin_tile = {
|
||||
.name = "tile",
|
||||
.version = "1.0",
|
||||
.init = layout_tile_init,
|
||||
.shutdown = layout_tile_shutdown
|
||||
};
|
||||
#+END_SRC
|
||||
|
||||
**扩展2:状态栏(插件)**
|
||||
|
||||
#+BEGIN_SRC c
|
||||
// plugins/statusbar.c
|
||||
|
||||
#include "plugins/statusbar.h"
|
||||
#include "core/hooks.h"
|
||||
|
||||
static cairo_surface_t *g_bar_surface = NULL;
|
||||
|
||||
// 在工作区切换时更新状态栏
|
||||
static void on_workspace_switch(void *event_data) {
|
||||
wm_workspace_t *workspace = event_data;
|
||||
|
||||
// 绘制状态栏
|
||||
draw_statusbar(workspace);
|
||||
}
|
||||
|
||||
// 在布局计算后绘制到状态栏
|
||||
static void on_layout_calculate(wm_workspace_t *workspace,
|
||||
rect_t *geometries,
|
||||
size_t count,
|
||||
void *user_data) {
|
||||
// 可以在这里显示布局信息
|
||||
draw_layout_info(workspace);
|
||||
}
|
||||
|
||||
// 绘制状态栏
|
||||
static void draw_statusbar(wm_workspace_t *workspace) {
|
||||
// 使用 cairo 绘制
|
||||
// ...
|
||||
}
|
||||
|
||||
static bool statusbar_init(void) {
|
||||
// 注册钩子
|
||||
wm_hook_register(HOOK_WORKSPACE_SWITCH, on_workspace_switch, NULL, 0);
|
||||
wm_hook_register(HOOK_LAYOUT_CALCULATE, on_layout_calculate, NULL, 0);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void statusbar_shutdown(void) {
|
||||
if (g_bar_surface) {
|
||||
cairo_surface_destroy(g_bar_surface);
|
||||
}
|
||||
}
|
||||
|
||||
wm_plugin_t plugin_statusbar = {
|
||||
.name = "statusbar",
|
||||
.version = "1.0",
|
||||
.init = statusbar_init,
|
||||
.shutdown = statusbar_shutdown
|
||||
};
|
||||
#+END_SRC
|
||||
|
||||
**扩展3:快捷键绑定(插件)**
|
||||
|
||||
#+BEGIN_SRC c
|
||||
// plugins/keybindings.c
|
||||
|
||||
#include "plugins/keybindings.h"
|
||||
#include "core/hooks.h"
|
||||
|
||||
// 快捷键配置
|
||||
typedef struct {
|
||||
uint32_t keycode;
|
||||
uint32_t modifiers;
|
||||
void (*action)(void);
|
||||
} keybinding_t;
|
||||
|
||||
static keybinding_t g_bindings[] = {
|
||||
{ .keycode = XK_Return, .modifiers = Mod4Mask, .action = action_launch_terminal },
|
||||
{ .keycode = XK_q, .modifiers = Mod4Mask | ShiftMask, .action = action_close_window },
|
||||
{ .keycode = XK_space, .modifiers = Mod4Mask, .action = action_next_layout },
|
||||
};
|
||||
|
||||
static bool handle_key_press(uint32_t keycode, uint32_t modifiers, void *user_data) {
|
||||
for (size_t i = 0; i < sizeof(g_bindings)/sizeof(g_bindings[0]); i++) {
|
||||
if (g_bindings[i].keycode == keycode &&
|
||||
g_bindings[i].modifiers == modifiers) {
|
||||
g_bindings[i].action();
|
||||
return true; // 事件被消费
|
||||
}
|
||||
}
|
||||
return false; // 继续传递
|
||||
}
|
||||
|
||||
static bool keybindings_init(void) {
|
||||
wm_hook_register(HOOK_KEY_PRESS, handle_key_press, NULL, 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void keybindings_shutdown(void) {
|
||||
// 自动取消注册
|
||||
}
|
||||
|
||||
wm_plugin_t plugin_keybindings = {
|
||||
.name = "keybindings",
|
||||
.version = "1.0",
|
||||
.init = keybindings_init,
|
||||
.shutdown = keybindings_shutdown
|
||||
};
|
||||
#+END_SRC
|
||||
|
||||
**扩展4:窗口规则(插件)**
|
||||
|
||||
#+BEGIN_SRC c
|
||||
// plugins/rules.c
|
||||
|
||||
#include "plugins/rules.h"
|
||||
#include "core/hooks.h"
|
||||
|
||||
typedef struct {
|
||||
char *class_name;
|
||||
bool floating;
|
||||
bool sticky;
|
||||
wm_workspace_id_t target_workspace;
|
||||
} rule_t;
|
||||
|
||||
static rule_t g_rules[] = {
|
||||
{ .class_name = "floating_window", .floating = true },
|
||||
{ .class_name = "sticky_terminal", .sticky = true },
|
||||
};
|
||||
|
||||
static void on_window_manage(wm_window_t *window, void *user_data) {
|
||||
// 应用规则
|
||||
for (size_t i = 0; i < sizeof(g_rules)/sizeof(g_rules[0]); i++) {
|
||||
if (strcmp(window->class.name, g_rules[i].class_name) == 0) {
|
||||
if (g_rules[i].floating) {
|
||||
wm_set_window_floating(window->id, true);
|
||||
}
|
||||
// ... 应用其他规则
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool rules_init(void) {
|
||||
wm_hook_register(HOOK_WINDOW_MANAGE, on_window_manage, NULL, 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void rules_shutdown(void) {
|
||||
// 自动取消注册
|
||||
}
|
||||
|
||||
wm_plugin_t plugin_rules = {
|
||||
.name = "rules",
|
||||
.version = "1.0",
|
||||
.init = rules_init,
|
||||
.shutdown = rules_shutdown
|
||||
};
|
||||
#+END_SRC
|
||||
|
||||
**扩展5:配置系统(插件)**
|
||||
|
||||
#+BEGIN_SRC c
|
||||
// plugins/config.c
|
||||
|
||||
#include "plugins/config.h"
|
||||
#include "core/hooks.h"
|
||||
|
||||
typedef struct {
|
||||
char *key;
|
||||
char *value;
|
||||
} config_item_t;
|
||||
|
||||
static config_item_t g_config[] = {
|
||||
{ .key = "terminal", .value = "st" },
|
||||
{ .key = "browser", .value = "firefox" },
|
||||
};
|
||||
|
||||
static const char* config_get(const char *key) {
|
||||
for (size_t i = 0; i < sizeof(g_config)/sizeof(g_config[0]); i++) {
|
||||
if (strcmp(g_config[i].key, key) == 0) {
|
||||
return g_config[i].value;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void on_config_reload(void *event_data) {
|
||||
// 重新加载配置文件
|
||||
load_config_file("~/.config/zdwm/config");
|
||||
}
|
||||
|
||||
static bool config_init(void) {
|
||||
// 加载初始配置
|
||||
load_config_file("~/.config/zdwm/config");
|
||||
|
||||
// 监听配置重载信号
|
||||
wm_hook_register(HOOK_CONFIG_RELOAD, on_config_reload, NULL, 0);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void config_shutdown(void) {
|
||||
// 清理配置资源
|
||||
}
|
||||
|
||||
wm_plugin_t plugin_config = {
|
||||
.name = "config",
|
||||
.version = "1.0",
|
||||
.init = config_init,
|
||||
.shutdown = config_shutdown
|
||||
};
|
||||
#+END_SRC
|
||||
|
||||
* 核心实现(简化版)
|
||||
|
||||
#+BEGIN_SRC c
|
||||
// src/core/zdwm.c
|
||||
|
||||
// 核心 API:触发钩子
|
||||
void wm_hook_trigger_layout_calculate(wm_workspace_t *workspace, rect_t *geometries, size_t count) {
|
||||
wm_hook_t *hook = g_hooks[HOOK_LAYOUT_CALCULATE];
|
||||
while (hook) {
|
||||
wm_hook_layout_calculate_t callback = (wm_hook_layout_calculate_t)hook->callback;
|
||||
callback(workspace, geometries, count, hook->user_data);
|
||||
hook = hook->next;
|
||||
}
|
||||
}
|
||||
|
||||
bool wm_hook_trigger_key_press(uint32_t keycode, uint32_t modifiers) {
|
||||
wm_hook_t *hook = g_hooks[HOOK_KEY_PRESS];
|
||||
while (hook) {
|
||||
wm_hook_key_press_t callback = (wm_hook_key_press_t)hook->callback;
|
||||
if (callback(keycode, modifiers, hook->user_data)) {
|
||||
return true; // 事件被消费
|
||||
}
|
||||
hook = hook->next;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// 主循环(简化版)
|
||||
void wm_run(void) {
|
||||
xcb_generic_event_t *event;
|
||||
|
||||
while (g_running) {
|
||||
// 1. 获取事件
|
||||
event = xcb_wait_for_event(g_conn);
|
||||
|
||||
// 2. 处理事件
|
||||
wm_handle_event(event);
|
||||
|
||||
// 3. 更新布局(如果需要)
|
||||
if (g_dirty_layout) {
|
||||
wm_update_layout();
|
||||
}
|
||||
|
||||
// 4. 渲染(如果有状态栏插件)
|
||||
// (通过钩子让状态栏插件自己绘制)
|
||||
}
|
||||
}
|
||||
|
||||
// 布局更新
|
||||
void wm_update_layout(void) {
|
||||
for (size_t i = 0; i < g_state.workspace_count; i++) {
|
||||
wm_workspace_t *ws = &g_state.workspaces[i];
|
||||
|
||||
// 分配几何数组
|
||||
rect_t geometries[ws->window_count];
|
||||
|
||||
// 触发布局钩子(布局插件会计算几何)
|
||||
wm_hook_trigger_layout_calculate(ws, geometries, ws->window_count);
|
||||
|
||||
// 应用几何到窗口
|
||||
for (size_t j = 0; j < ws->window_count; j++) {
|
||||
wm_window_t *win = wm_find_window(ws->windows[j]);
|
||||
win->geometry = geometries[j];
|
||||
}
|
||||
}
|
||||
}
|
||||
#+END_SRC
|
||||
|
||||
* 扩展交互示例
|
||||
|
||||
**场景1:状态栏需要显示布局信息**
|
||||
|
||||
#+BEGIN_SRC c
|
||||
// 状态栏插件订阅布局钩子
|
||||
static void on_layout_calculate(wm_workspace_t *workspace,
|
||||
rect_t *geometries,
|
||||
size_t count,
|
||||
void *user_data) {
|
||||
// 更新状态栏显示的布局信息
|
||||
update_layout_indicator(workspace, geometries, count);
|
||||
}
|
||||
#+END_SRC
|
||||
|
||||
**场景2:多个布局算法共存**
|
||||
|
||||
#+BEGIN_SRC c
|
||||
// 插件1:tile 布局
|
||||
wm_plugin_t plugin_tile = { .name = "tile", ... };
|
||||
|
||||
// 插件2:monocle 布局
|
||||
wm_plugin_t plugin_monocle = { .name = "monocle", ... };
|
||||
|
||||
// 运行时切换
|
||||
void switch_layout(const char *layout_name) {
|
||||
// 卸载当前布局插件
|
||||
wm_plugin_unload("tile");
|
||||
|
||||
// 加载新布局插件
|
||||
wm_plugin_load(find_plugin(layout_name));
|
||||
}
|
||||
#+END_SRC
|
||||
|
||||
**场景3:插件间通信**
|
||||
|
||||
#+BEGIN_SRC c
|
||||
// 场景:网络速度插件需要通知状态栏插件更新
|
||||
|
||||
// 方案1:通过核心状态
|
||||
// network 插件更新核心状态中的某个字段
|
||||
// statusbar 插件在 HOOK_LAYOUT_CALCULATE 中读取该字段
|
||||
|
||||
// 方案2:直接函数调用(如果两个插件在同一进程中)
|
||||
extern void statusbar_update_network_speed(uint64_t speed);
|
||||
|
||||
void network_on_speed_update(uint64_t speed) {
|
||||
statusbar_update_network_speed(speed);
|
||||
}
|
||||
|
||||
// 方案3:自定义事件(如果有事件总线插件)
|
||||
event_bus_publish("network.speed_changed", &speed);
|
||||
#+END_SRC
|
||||
|
||||
* 扩展最佳实践
|
||||
|
||||
**✅ 推荐做法**
|
||||
|
||||
1. *插件职责单一*
|
||||
- 一个插件做一件事
|
||||
- 例如:tile 布局插件只负责布局计算
|
||||
|
||||
2. *使用钩子优先级*
|
||||
- 核心功能(如窗口管理)优先级 = 0
|
||||
- 扩展功能(如状态栏)优先级 = 10
|
||||
- 调试功能优先级 = 100
|
||||
|
||||
3. *插件间依赖最小化*
|
||||
- 尽量避免插件间直接调用
|
||||
- 通过核心状态中转
|
||||
|
||||
4. *错误处理*
|
||||
- 插件 init 失败应返回 false
|
||||
- 核心会跳过该插件,继续运行
|
||||
|
||||
**❌ 避免的做法**
|
||||
|
||||
1. *插件直接修改核心结构*
|
||||
#+BEGIN_SRC c
|
||||
// ❌ 不好:插件直接修改核心
|
||||
extern wm_state_t *g_state;
|
||||
g_state->current_workspace = 1;
|
||||
|
||||
// ✅ 好:通过核心 API
|
||||
wm_switch_workspace(1);
|
||||
#+END_SRC
|
||||
|
||||
2. *插件阻塞执行*
|
||||
#+BEGIN_SRC c
|
||||
// ❌ 不好:插件中有阻塞操作
|
||||
void statusbar_init(void) {
|
||||
sleep(1); // 阻塞主循环!
|
||||
}
|
||||
|
||||
// ✅ 好:异步操作
|
||||
void statusbar_init(void) {
|
||||
pthread_create(&thread, NULL, async_init, NULL);
|
||||
}
|
||||
#+END_SRC
|
||||
|
||||
3. *插件循环依赖*
|
||||
- 插件A依赖插件B
|
||||
- 插件B依赖插件A
|
||||
- 解决方案:通过核心状态解耦
|
||||
|
||||
* 与 mini_core_draft 对比
|
||||
|
||||
| 特性 | mini_core_draft | 最小核心 + 插件 |
|
||||
|------|-----------------|----------------|
|
||||
| 核心代码行数 | ~3000行 | ~1500行 |
|
||||
| 抽象层 | 3-4层 | 1层(钩子) |
|
||||
| 扩展方式 | 服务注册 | 插件+钩子 |
|
||||
| 学习曲线 | 陡峭 | 平缓 |
|
||||
| 性能 | 中等 | 最好 |
|
||||
| 可扩展性 | 很高 | 高 |
|
||||
| 复杂度 | 高 | 低 |
|
||||
|
||||
* 核心代码量估算
|
||||
|
||||
#+BEGIN_EXAMPLE
|
||||
核心:
|
||||
- zdwm.h (核心 API): 200 行
|
||||
- zdwm.c (核心实现): 500 行
|
||||
- hooks.c (钩子系统): 200 行
|
||||
- plugin.c (插件加载): 150 行
|
||||
------------------------------------
|
||||
核心总计: ~1050 行
|
||||
|
||||
插件示例:
|
||||
- layout_tile.c: 100 行
|
||||
- statusbar.c: 300 行
|
||||
- keybindings.c: 150 行
|
||||
- rules.c: 200 行
|
||||
------------------------------------
|
||||
插件总计: ~750 行
|
||||
|
||||
总计: ~1800 行完整功能的窗口管理器
|
||||
#+END_EXAMPLE
|
||||
|
||||
* 总结
|
||||
|
||||
*最小核心 + 插件*方案的优势:
|
||||
|
||||
1. *核心真正最小* - 只做窗口管理,1500行代码
|
||||
2. *扩展性有保证* - 钩子系统允许任何扩展
|
||||
3. *性能最优* - 一层间接,零开销抽象
|
||||
4. *易于理解* - 新手也能看懂核心代码
|
||||
5. *渐进式开发* - 先核心,后扩展
|
||||
|
||||
这个设计比 ~mini_core_draft~ 简单很多,但扩展性并不差。关键是*钩子系统*设计得当,可以实现大部分扩展需求。
|
||||
407
docs/config_system.md
Normal file
407
docs/config_system.md
Normal file
@@ -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**: 增强功能,可后续添加
|
||||
412
docs/config_system.org
Normal file
412
docs/config_system.org
Normal file
@@ -0,0 +1,412 @@
|
||||
* ZDWM 配置系统设计
|
||||
|
||||
** 概述
|
||||
|
||||
配置系统现在只保留两类正式输入:
|
||||
|
||||
1. 编译进二进制的默认配置
|
||||
2. 用户动态库配置(=~/.config/zdwm/config.c= 编译得到 =config.so=)
|
||||
|
||||
不再支持 XResources,也不再保留 =Xft.dpi= 作为兼容输入。字体、DPI、颜色、边框、布局、规则、快捷键和服务设置都只来自默认配置和动态库配置。
|
||||
|
||||
配置系统的职责也收紧为:
|
||||
|
||||
1. 生成 core-native 配置快照。
|
||||
2. 为 runtime adapter、service 层和输入绑定层提供稳定输入。
|
||||
3. 不直接操作 =wm_state_t= 。
|
||||
4. 不再暴露 =client_t= 、=tag_t= 、=monitor_t= 之类的旧对象。
|
||||
|
||||
** 设计目标
|
||||
|
||||
1. 配置层继续放在核心之外。
|
||||
2. 配置结果必须能无损对接新的 =runtime/policy/layout/service= 边界。
|
||||
3. 配置重载应以“重建快照并显式应用”为单位,而不是任意时刻写全局内存。
|
||||
4. 彻底移除 XResources 路径,避免维护两套并行语义。
|
||||
|
||||
** 配置来源与优先级
|
||||
|
||||
最终有效配置按下面顺序构建:
|
||||
|
||||
#+BEGIN_EXAMPLE
|
||||
默认配置
|
||||
↓
|
||||
动态库配置 config.so
|
||||
↓
|
||||
冻结为 zdwm_config_t
|
||||
#+END_EXAMPLE
|
||||
|
||||
动态库配置优先级高于默认配置。
|
||||
|
||||
** 非目标
|
||||
|
||||
下面这些能力不再属于配置系统设计范围:
|
||||
|
||||
1. =Zdwm.*= 风格的 XResources 覆盖。
|
||||
2. =Xft.dpi= 的额外兼容读取。
|
||||
3. =hook_manage_new(client_t *)= 和 =hook_manage_unmanage(client_t *)= 这类直接对象钩子。
|
||||
4. =arrange(tag_t *)= 这类依赖旧 tag 模型的布局接口。
|
||||
5. 从配置层直接访问或修改 =wm= 全局对象。
|
||||
|
||||
** 配置输出模型
|
||||
|
||||
配置系统输出一份冻结后的配置快照,而不是一组可随时调用的副作用回调。
|
||||
|
||||
*** 外观配置
|
||||
|
||||
#+BEGIN_SRC c
|
||||
typedef struct zdwm_appearance_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;
|
||||
} zdwm_appearance_config_t;
|
||||
#+END_SRC
|
||||
|
||||
这部分不进入最小核心状态,而是供 render/bar/text 这类核心外服务使用。
|
||||
|
||||
*** workspace 配置
|
||||
|
||||
=wm_workspace_t= 是运行时状态结构,里面包含 =focused_window_id= 这类不属于静态配置的数据。因此配置层应单独定义 workspace 配置描述:
|
||||
|
||||
#+BEGIN_SRC c
|
||||
typedef struct zdwm_workspace_config_t {
|
||||
wm_workspace_id_t id;
|
||||
wm_layout_id_t initial_layout_id;
|
||||
|
||||
const char *name;
|
||||
const char *symbol_text;
|
||||
const char *symbol_icon_path;
|
||||
} zdwm_workspace_config_t;
|
||||
#+END_SRC
|
||||
|
||||
runtime adapter 会把它拆成:
|
||||
|
||||
1. =wm_workspace_t= 的初始数组
|
||||
2. =wm_workspace_desc_t= 描述表
|
||||
|
||||
*** 管理规则
|
||||
|
||||
#+BEGIN_SRC c
|
||||
typedef struct zdwm_manage_rule_t {
|
||||
const char *title;
|
||||
const char *app_id;
|
||||
const char *class_name;
|
||||
const char *instance_name;
|
||||
|
||||
wm_workspace_id_t workspace_id;
|
||||
wm_manage_window_init_t initial_state;
|
||||
bool switch_to_workspace;
|
||||
} zdwm_manage_rule_t;
|
||||
#+END_SRC
|
||||
|
||||
规则层的职责仅限于:
|
||||
|
||||
1. 匹配窗口元数据。
|
||||
2. 决定 =MANAGE_WINDOW= 的 =workspace_id= 。
|
||||
3. 决定 =MANAGE_WINDOW= 的 =initial_state= 。
|
||||
4. 可选地决定是否切到目标 workspace。
|
||||
|
||||
规则层不直接管理窗口对象,也不直接写 =wm_state_t= 。
|
||||
|
||||
*** 快捷键绑定
|
||||
|
||||
快捷键也需要从旧的“直接 action 回调”收敛到新边界。
|
||||
|
||||
#+BEGIN_SRC c
|
||||
typedef enum zdwm_binding_target_t {
|
||||
ZDWM_BINDING_CORE_COMMAND,
|
||||
ZDWM_BINDING_SERVICE_ACTION,
|
||||
} zdwm_binding_target_t;
|
||||
|
||||
typedef struct zdwm_service_action_t {
|
||||
const char *service_name;
|
||||
const char *action_name;
|
||||
const char *string_arg;
|
||||
int32_t int_arg;
|
||||
} zdwm_service_action_t;
|
||||
|
||||
typedef struct zdwm_keybinding_t {
|
||||
uint32_t modifiers;
|
||||
xkb_keysym_t keysym;
|
||||
zdwm_binding_target_t target;
|
||||
union {
|
||||
wm_command_t command;
|
||||
zdwm_service_action_t service_action;
|
||||
} as;
|
||||
} zdwm_keybinding_t;
|
||||
#+END_SRC
|
||||
|
||||
=core command= 用于工作区切换、布局切换、浮动切换、焦点切换等窗口管理行为。
|
||||
|
||||
=service action= 用于启动外部程序、刷新状态服务、触发核心外功能。
|
||||
|
||||
*** 服务设置
|
||||
|
||||
#+BEGIN_SRC c
|
||||
typedef struct zdwm_service_setting_t {
|
||||
const char *service_name;
|
||||
const char *key;
|
||||
const char *value;
|
||||
} zdwm_service_setting_t;
|
||||
#+END_SRC
|
||||
|
||||
服务配置由配置层生成,但仍由 service 层解释和持有。
|
||||
|
||||
*** 总配置快照
|
||||
|
||||
#+BEGIN_SRC c
|
||||
typedef struct zdwm_config_source_t {
|
||||
bool has_user_lib;
|
||||
} zdwm_config_source_t;
|
||||
|
||||
typedef struct zdwm_runtime_config_t {
|
||||
wm_policy_config_t policy;
|
||||
|
||||
zdwm_workspace_config_t *workspaces;
|
||||
size_t workspace_count;
|
||||
|
||||
wm_layout_slot_t *layouts;
|
||||
size_t layout_count;
|
||||
} zdwm_runtime_config_t;
|
||||
|
||||
typedef struct zdwm_config_t {
|
||||
zdwm_runtime_config_t runtime;
|
||||
zdwm_appearance_config_t appearance;
|
||||
|
||||
zdwm_manage_rule_t *rules;
|
||||
size_t rule_count;
|
||||
|
||||
zdwm_keybinding_t *keybindings;
|
||||
size_t keybinding_count;
|
||||
|
||||
const char *const *autostart_list;
|
||||
|
||||
zdwm_service_setting_t *service_settings;
|
||||
size_t service_setting_count;
|
||||
|
||||
zdwm_config_source_t source;
|
||||
} zdwm_config_t;
|
||||
#+END_SRC
|
||||
|
||||
注意这里故意不把 =outputs= 和启动时已有窗口塞进配置结果里。它们属于 backend 发现结果,不属于静态配置。
|
||||
|
||||
** 与最小核心的集成方式
|
||||
|
||||
配置层与 runtime 的拼装分为两步:
|
||||
|
||||
*** 第一步:构建配置快照
|
||||
|
||||
#+BEGIN_EXAMPLE
|
||||
默认配置
|
||||
↓
|
||||
config.so 覆盖与增量注册
|
||||
↓
|
||||
zdwm_config_t
|
||||
#+END_EXAMPLE
|
||||
|
||||
*** 第二步:由 adapter 组装 runtime bootstrap
|
||||
|
||||
#+BEGIN_EXAMPLE
|
||||
zdwm_config_t
|
||||
+
|
||||
backend 发现的 outputs
|
||||
+
|
||||
backend 扫描得到的初始 MANAGE_WINDOW 命令
|
||||
↓
|
||||
wm_runtime_bootstrap_t
|
||||
#+END_EXAMPLE
|
||||
|
||||
组装规则应为:
|
||||
|
||||
1. =policy= 来自 =config.runtime.policy= 。
|
||||
2. =workspaces= 来自 =config.runtime.workspaces[]= 的静态定义。
|
||||
3. =workspace_descs= 来自 =config.runtime.workspaces[]= 中的展示字段。
|
||||
4. =outputs= 来自 backend。
|
||||
5. =initial_commands= 来自 backend 对已有窗口的扫描和规则匹配。
|
||||
|
||||
这样配置层不需要知道当前有哪些物理输出,也不需要自己扫描窗口。
|
||||
|
||||
** 动态库 API
|
||||
|
||||
动态库 API 由“builder 风格”的配置写入接口组成:
|
||||
|
||||
#+BEGIN_SRC c
|
||||
typedef struct zdwm_api_t {
|
||||
uint32_t version;
|
||||
|
||||
// appearance
|
||||
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);
|
||||
|
||||
// runtime config
|
||||
void (*set_policy)(wm_policy_config_t policy);
|
||||
void (*define_workspace)(zdwm_workspace_config_t workspace);
|
||||
void (*register_layout)(wm_layout_slot_t layout);
|
||||
|
||||
// rules
|
||||
void (*add_rule)(zdwm_manage_rule_t rule);
|
||||
|
||||
// bindings
|
||||
void (*add_keybinding)(zdwm_keybinding_t binding);
|
||||
void (*clear_keybindings)(void);
|
||||
|
||||
// autostart and services
|
||||
void (*add_autostart)(const char *command);
|
||||
void (*set_service_option)(const char *service_name,
|
||||
const char *key,
|
||||
const char *value);
|
||||
} zdwm_api_t;
|
||||
#+END_SRC
|
||||
|
||||
用户动态库入口保持简单:
|
||||
|
||||
#+BEGIN_SRC c
|
||||
typedef struct zdwm_user_config_t {
|
||||
void (*cleanup)(void);
|
||||
} zdwm_user_config_t;
|
||||
|
||||
zdwm_user_config_t *zdwm_config_init(zdwm_api_t *api);
|
||||
#+END_SRC
|
||||
|
||||
=cleanup()= 只负责动态库自身资源,不负责 runtime 状态回收。
|
||||
|
||||
** 加载流程
|
||||
|
||||
#+BEGIN_SRC c
|
||||
zdwm_config_t *config_load(void) {
|
||||
zdwm_config_t *config = calloc(1, sizeof(*config));
|
||||
|
||||
config_apply_defaults(config);
|
||||
config_load_user_lib(config);
|
||||
config_finalize(config);
|
||||
|
||||
return config;
|
||||
}
|
||||
#+END_SRC
|
||||
|
||||
这里的 =config_finalize()= 建议至少做这些事情:
|
||||
|
||||
1. 检查 workspace id 是否唯一。
|
||||
2. 检查 layout id 是否唯一。
|
||||
3. 检查规则和快捷键数组是否合法。
|
||||
4. 补齐未设置的默认值。
|
||||
5. 冻结动态数组,避免运行时再被随意写入。
|
||||
|
||||
** 管理规则接入链路
|
||||
|
||||
管理规则不直接操作窗口,推荐链路如下:
|
||||
|
||||
#+BEGIN_EXAMPLE
|
||||
backend 发现新窗口
|
||||
↓
|
||||
同步窗口 metadata
|
||||
↓
|
||||
在 rules 中匹配
|
||||
↓
|
||||
生成 WM_COMMAND_MANAGE_WINDOW
|
||||
- workspace_id
|
||||
- initial_state
|
||||
↓
|
||||
runtime / policy apply_command()
|
||||
#+END_EXAMPLE
|
||||
|
||||
这样规则层只负责“把 metadata 翻译成命令参数”。
|
||||
|
||||
** 快捷键接入链路
|
||||
|
||||
#+BEGIN_EXAMPLE
|
||||
backend 输入事件
|
||||
↓
|
||||
keybinding table 匹配
|
||||
↓
|
||||
若 target == ZDWM_BINDING_CORE_COMMAND:
|
||||
生成 wm_command_t
|
||||
若 target == ZDWM_BINDING_SERVICE_ACTION:
|
||||
发送给 service 层
|
||||
#+END_EXAMPLE
|
||||
|
||||
配置层不再保存 =void (*action)(const void *)= 这类旧式回调。
|
||||
|
||||
** 热重载
|
||||
|
||||
配置热重载仍然可以保留,但边界必须清晰。
|
||||
|
||||
推荐流程:
|
||||
|
||||
#+BEGIN_EXAMPLE
|
||||
收到 SIGHUP
|
||||
↓
|
||||
重建 zdwm_config_t
|
||||
↓
|
||||
原子替换:
|
||||
- appearance
|
||||
- policy
|
||||
- keybindings
|
||||
- workspace descriptors
|
||||
- layout registry
|
||||
- service settings
|
||||
↓
|
||||
通知相关 service 刷新
|
||||
#+END_EXAMPLE
|
||||
|
||||
热重载约束:
|
||||
|
||||
1. 不直接改 =wm_state_t= 。
|
||||
2. 规则变更只影响后续新管理的窗口,不回溯重算现有窗口。
|
||||
3. workspace 描述、颜色、字体、DPI、bindings、policy 适合热重载。
|
||||
4. 大规模布局或 service 拓扑变化,必要时可以走受控重启。
|
||||
|
||||
** 文件结构
|
||||
|
||||
#+BEGIN_EXAMPLE
|
||||
src/
|
||||
├── config.h # 配置系统头文件
|
||||
├── config.c # 配置加载主逻辑
|
||||
├── config_defaults.c # 默认配置
|
||||
├── config_lib.c # 动态库加载
|
||||
├── config_api.c # builder API
|
||||
└── config_validate.c # 配置收尾校验(可选)
|
||||
|
||||
docs/
|
||||
└── config_system.org # 本文档
|
||||
#+END_EXAMPLE
|
||||
|
||||
这里不再有 =config_xres.c= 或任何通用 XResources 解析模块。
|
||||
|
||||
** 迁移说明
|
||||
|
||||
下面这些旧设计应视为废弃:
|
||||
|
||||
1. =默认配置 -> XResources -> 动态库配置= 三层覆盖模型。
|
||||
2. =Xft.dpi= 的兼容读取。
|
||||
3. =hook_manage_new(client_t *)= 和 =hook_manage_unmanage(client_t *)= 。
|
||||
4. =add_layout(... arrange(tag_t *tag))= 。
|
||||
5. 任何直接依赖 =client_t= 、=tag_t= 、=monitor_t= 的配置接口。
|
||||
|
||||
迁移后的对应关系:
|
||||
|
||||
1. tag 名称与符号 → =zdwm_workspace_config_t=
|
||||
2. 布局注册 → =wm_layout_slot_t=
|
||||
3. 窗口规则 → =zdwm_manage_rule_t=
|
||||
4. 快捷键动作 → =wm_command_t= 或 =zdwm_service_action_t=
|
||||
5. 外观设置 → =zdwm_appearance_config_t=
|
||||
|
||||
** 实施优先级
|
||||
|
||||
1. =config_defaults.c=
|
||||
先保证“无用户配置”也能跑。
|
||||
|
||||
2. =config.c + config_lib.c=
|
||||
打通默认配置和动态库配置装配。
|
||||
|
||||
3. =config_api.c=
|
||||
落地新的 builder API,替代旧 action/hook 模型。
|
||||
|
||||
4. runtime adapter 对接
|
||||
把 =zdwm_config_t= 装配成 =wm_runtime_bootstrap_t= 、规则表、service 设置和 keybinding table。
|
||||
408
docs/min_core_draft/README.org
Normal file
408
docs/min_core_draft/README.org
Normal file
@@ -0,0 +1,408 @@
|
||||
* ZDWM 最小核心草案
|
||||
|
||||
这个目录放的是一套头文件级别的最小核心 API 草案,目标是先把窗口管理器最核心的对象关系、子系统边界和主执行流程固定下来,再决定具体实现。
|
||||
|
||||
* 目标
|
||||
|
||||
1. 保持核心技术栈简洁,不引入脚本运行时、序列化协议和多进程插件宿主。
|
||||
2. 先把最小核心收敛到可以管理窗口、计算布局、处理输入、提交后端副作用。
|
||||
3. 将渲染、状态栏、配置、插件都放在核心之外,作为后续服务挂接。
|
||||
|
||||
* 最小核心子系统
|
||||
|
||||
1. =runtime=
|
||||
负责主循环、模块装配和固定执行顺序。
|
||||
|
||||
2. =backend=
|
||||
负责平台交互。它把平台原始事件转成 =wm_event_t= ,并执行 =wm_effect_t= 。
|
||||
|
||||
3. =state=
|
||||
负责保存窗口、工作区、输出和全局堆叠顺序,是唯一真状态。
|
||||
采用不透明结构体设计,内部实现完全封装。
|
||||
窗口元数据(title/app_id/class/instance)直接存储在 =wm_window_t= 中。
|
||||
工作区名称存储在 =wm_workspace_t= 中。
|
||||
|
||||
4. =policy=
|
||||
负责把事件路由为命令,并把命令应用到状态。
|
||||
|
||||
5. =layout=
|
||||
负责为某个输出上当前显示的工作区中的平铺窗口计算外框几何。
|
||||
|
||||
* ID 分配策略
|
||||
|
||||
最小核心采用"运行时固定,启动时配置"的 ID 策略:
|
||||
|
||||
**半静态实体(启动时分配,运行时固定)**:
|
||||
- =workspace= : 启动时根据配置分配 N 个(如 10 个),ID = 数组索引(0-N-1)
|
||||
- =output= : 启动时根据扫描到的显示器数量分配,ID = 数组索引
|
||||
- =layout= : 启动时根据注册的布局算法分配,ID = 数组索引
|
||||
|
||||
这些实体在启动时一次性分配,运行时不再增删。
|
||||
访问时直接用索引,零开销查找,无需映射表。
|
||||
|
||||
```c
|
||||
// 半静态实体:直接访问(零开销)
|
||||
wm_workspace_t *ws = &state->workspaces[workspace_id]; // O(1)
|
||||
wm_output_t *output = &state->outputs[output_id]; // O(1)
|
||||
```
|
||||
|
||||
**动态实体(运行时增删)**:
|
||||
- =window= : 动态数组,ID 由后端生成(如 X11 的 xcb_window_t)
|
||||
查找时线性搜索 O(n),对于几十到几百个窗口性能足够。
|
||||
(后续如成为性能热点,可改用 hashmap 优化到 O(1))
|
||||
|
||||
```c
|
||||
// 半静态实体:直接访问(零开销)
|
||||
wm_workspace_t *ws = &state->workspaces[workspace_id]; // O(1)
|
||||
wm_output_t *output = &state->outputs[output_id]; // O(1)
|
||||
|
||||
// 动态实体:线性查找(简单且足够)
|
||||
const wm_window_t *win = wm_state_window_get(state, window_id); // O(n)
|
||||
```
|
||||
|
||||
**设计优势**:
|
||||
- 灵活性:workspace 数量由配置决定,不硬编码
|
||||
- 性能:半静态实体零开销访问,动态实体简单线性查找
|
||||
- 简单性:无需复杂的映射表,代码更清晰
|
||||
|
||||
* 热插拔策略
|
||||
|
||||
**显示器变化时自动重启 WM**:
|
||||
|
||||
当检测到显示器配置变化(新增/删除/几何改变)时,最简单且可靠的方案是重启整个窗口管理器。
|
||||
|
||||
理由:
|
||||
1. 逻辑简单:无需处理复杂的运行时重配置
|
||||
2. 状态一致:避免重配置后的不一致状态
|
||||
3. 实现成本低:不需要增量更新逻辑
|
||||
4. 可靠性高:从干净状态启动,避免边界情况
|
||||
|
||||
实现方式:
|
||||
- 后端检测到 RandR/Output 变化时,退出主循环
|
||||
- 由启动脚本(如 systemd user service 或 .xinitrc)自动重启 WM
|
||||
- WM 重启前保存必要状态(如当前工作区),启动后恢复
|
||||
|
||||
对用户影响:
|
||||
- 显示器拔插时窗口会短暂闪烁(~1-2 秒)
|
||||
- 但比复杂的增量逻辑更可靠,符合"简单优先"原则
|
||||
|
||||
* 非核心但常见的服务
|
||||
|
||||
这些服务故意不放进最小核心:
|
||||
|
||||
1. =render=
|
||||
状态栏、标题和其他装饰绘制。窗口边框不是独立服务,而是
|
||||
=WM_EFFECT_CONFIGURE_WINDOW= 的一部分。
|
||||
|
||||
2. =status=
|
||||
网速、内存、CPU、音量、时间等信息采集与聚合。
|
||||
|
||||
3. =config=
|
||||
默认配置和用户覆盖。
|
||||
|
||||
4. =plugin=
|
||||
动态扩展加载。
|
||||
|
||||
* 运行时装配与所有权
|
||||
|
||||
为了避免再次出现当前全局 =wm= 那样的大对象耦合,最小核心里的所有权再收紧一层:
|
||||
|
||||
1. =runtime= 拥有 =state= 、=plan= 、=command_buffer= 和 =layout_registry= 。
|
||||
2. =runtime= 也拥有 =policy config= 、全局边框配置、交互态和服务注册表。
|
||||
3. =backend= 只负责平台事件翻译和平台副作用,不拥有控制状态,不直接拼 bar。
|
||||
4. 窗口元数据(title/app_id/class/instance)直接存储在 =wm_window_t= 中,与控制状态一同管理。
|
||||
5. 工作区名称存储在 =wm_workspace_t= 中,与控制状态一同管理。
|
||||
|
||||
* 启动与初始化约定
|
||||
|
||||
最小核心的初始化流程:
|
||||
|
||||
1. =wm_runtime_init()= 接收一份 bootstrap 描述,包含:
|
||||
- 工作区数量和初始配置(由用户配置决定)
|
||||
- 布局算法注册(由代码或配置决定)
|
||||
- 策略配置(focus_raises 等)
|
||||
- 全局统一边框宽度
|
||||
- 初始命令(如启动时扫描到的窗口)
|
||||
|
||||
2. 启动时扫描到的已有窗口,不直接塞进 =wm_state_t= ,而是翻译成一批 =MANAGE_WINDOW= 命令进入 runtime。
|
||||
|
||||
3. 工作区在启动时根据配置分配 N 个,运行时不再增删。
|
||||
|
||||
4. 输出在启动时扫描并分配对应数量的数组,运行时不再增删。
|
||||
|
||||
* 显示器热插拔策略
|
||||
|
||||
当检测到显示器配置变化(新增/删除/几何改变)时:
|
||||
|
||||
1. 后端检测到 RandR/Output 变化,发送 =WM_EVENT_QUIT= 给 runtime
|
||||
2. Runtime 退出主循环,清理资源
|
||||
3. 由启动脚本(systemd/xinitrc)自动重启 WM
|
||||
4. WM 重启后重新扫描显示器配置,从干净状态启动
|
||||
|
||||
**优势**:
|
||||
- ✅ 逻辑简单,无需复杂的运行时重配置
|
||||
- ✅ 状态一致,避免增量更新的边界情况
|
||||
- ✅ 实现可靠,从干净状态启动
|
||||
|
||||
**代价**:
|
||||
- 窗口会短暂闪烁(~1-2 秒)
|
||||
- 但相比复杂的热插拔逻辑,这是合理的工程权衡
|
||||
|
||||
* Workspace-Output 配置
|
||||
|
||||
工作区在启动时根据输出数量动态配置,固定归属到特定输出:
|
||||
|
||||
**配置方式 1:手动指定**
|
||||
```lua
|
||||
workspaces = {
|
||||
{ id = 1, name = "web", output_id = 0 }, -- DP-1 的 workspace
|
||||
{ id = 2, name = "code", output_id = 0 },
|
||||
{ id = 3, name = "chat", output_id = 1 }, -- DP-2 的 workspace
|
||||
{ id = 4, name = "media", output_id = 1 },
|
||||
}
|
||||
```
|
||||
|
||||
**配置方式 2:规则式**
|
||||
```lua
|
||||
-- 每个 output 自动分配 N 个 workspace
|
||||
workspace_distribution = {
|
||||
{ output_id = 0, count = 5 }, -- DP-1: workspace 1-5
|
||||
{ output_id = 1, count = 5 }, -- DP-2: workspace 6-10
|
||||
}
|
||||
```
|
||||
|
||||
**配置方式 3:脚本式**
|
||||
```lua
|
||||
function configure_workspaces(outputs)
|
||||
local workspaces = {}
|
||||
local ws_id = 1
|
||||
|
||||
for _, output in ipairs(outputs) do
|
||||
for i = 1, 3 do -- 每个 output 3 个 workspace
|
||||
table.insert(workspaces, {
|
||||
id = ws_id,
|
||||
name = tostring(ws_id),
|
||||
output_id = output.id
|
||||
})
|
||||
ws_id = ws_id + 1
|
||||
end
|
||||
end
|
||||
|
||||
return workspaces
|
||||
end
|
||||
```
|
||||
|
||||
**可见性规则**:
|
||||
- 窗口仅在归属的 output 上可见
|
||||
- 窗口可见 iff:窗口的 workspace 是该 output 的当前显示 workspace
|
||||
- 切换 workspace 只影响归属的 output
|
||||
|
||||
* 策略配置
|
||||
|
||||
前一版草案里有几处“建议作为可选策略”的描述,现在收敛为显式 =wm_policy_config_t= :
|
||||
|
||||
1. =focus_raises=
|
||||
焦点切换时是否隐式执行 raise。
|
||||
|
||||
2. =sticky_windows_participate_in_direction_focus=
|
||||
sticky 窗口是否参与方向焦点搜索。
|
||||
|
||||
3. =manage_sets_focus=
|
||||
新受管窗口是否默认夺取所属 workspace 的焦点。
|
||||
|
||||
4. =switch_workspace_restores_last_focus=
|
||||
切换工作区时是否恢复该 workspace 的最近焦点。
|
||||
|
||||
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 应维护一个单一交互会话:
|
||||
|
||||
1. 空闲
|
||||
2. 正在移动 floating 窗口
|
||||
3. 正在调整 floating 窗口大小
|
||||
|
||||
交互会话至少记录目标窗口、起始指针位置、起始窗口矩形和起始 output,供 =policy.route_event()= 生成连续的 move/resize 命令。
|
||||
|
||||
* 核心外服务接入约定
|
||||
|
||||
既然 =render/status/bar= 被明确放在核心外,就需要一条稳定的接入边界:
|
||||
|
||||
1. =WM_EFFECT_RENDER_OUTPUT= 不是平台副作用,而是 runtime 发给服务层的失效通知。
|
||||
2. =backend.apply_effect()= 只接收 map/unmap/configure/focus/restack 这类平台 effect。
|
||||
3. =runtime= 在处理完 plan 后,把 render 和 metadata 相关事件广播给已注册服务。
|
||||
4. 服务只读 =state/meta/descriptor= 快照,不直接改 =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”,而不是任意时刻从外部写内存。
|
||||
|
||||
* 对象关系
|
||||
|
||||
这套草案里的关系固定为:
|
||||
|
||||
1. =window -> workspace=
|
||||
每个窗口只属于一个工作区。
|
||||
|
||||
2. =workspace -> output=
|
||||
每个工作区固定归属某个输出,运行时不再改变。
|
||||
|
||||
3. =output -> current_workspace=
|
||||
每个输出同一时刻只显示一个当前工作区(从归属的工作区中选择)。
|
||||
|
||||
4. =window visible on output=
|
||||
这是派生结果,不是独立存储的真相。
|
||||
|
||||
5. =stack_order[]=
|
||||
这是全局 z-order 的唯一真相,从下到上排列。
|
||||
|
||||
6. =window metadata=
|
||||
窗口标题、类名等字符串信息直接存储在 =wm_window_t= 中。
|
||||
核心算法(layout/policy)不应依赖这些字段,仅用于匹配规则和服务层展示。
|
||||
|
||||
7. =workspace name=
|
||||
工作区名称直接存储在 =wm_workspace_t= 中,用于状态栏显示。
|
||||
核心算法不应依赖此字段。
|
||||
|
||||
8. =workspace layouts=
|
||||
每个 workspace 维护一个可用布局列表(=available_layouts=),在启动时根据配置分配,运行时固定。
|
||||
workspace 的 =layout_id= 字段指向当前活动的布局(列表中的一个)。
|
||||
使用 =CYCLE_LAYOUT= 命令在可用布局间循环切换。
|
||||
|
||||
9. =window rectangles=
|
||||
=frame_rect= 是窗口当前最终外框矩形;=float_rect= 是 floating 模式下记忆的外框矩形。
|
||||
这两个矩形都包含边框。
|
||||
|
||||
* 核心不变量
|
||||
|
||||
1. 每个窗口必须且只能属于一个工作区。
|
||||
2. 每个工作区必须且只能固定归属一个输出,运行时不再改变。
|
||||
3. 每个输出必须且只能显示一个当前工作区(从归属的工作区中选择)。
|
||||
4. 可见性由窗口归属、工作区归属、输出当前工作区和最小化状态共同推导。
|
||||
5. =geometry_mode= 只负责几何模式,不负责 z-order。
|
||||
6. =stack_order[]= 只负责 z-order,不负责几何模式。
|
||||
7. =floating= 是二值状态:窗口要么由 layout 决定基础几何,要么由 =float_rect= 决定基础几何。
|
||||
8. =sticky => floating= ;=sticky= 只放宽 workspace 可见性,不改变窗口归属工作区。
|
||||
9. floating / sticky 窗口都可以跨多个输出显示,但归属工作区仍然只有一个。
|
||||
10. 多输出下 floating / sticky 窗口是否迁移工作区,由锚点和跨输出策略决定,不由覆盖面积决定。
|
||||
11. layout 的 =symbol= 字段用于状态栏显示,应为 1-2 个字符的简短标识符(如 "T", "M");=name= 应保持稳定、可读(如 "tile", "monocle")。
|
||||
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 字段
|
||||
|
||||
=generation= 是版本号,不是业务关系字段。
|
||||
|
||||
当前草案只建议保留:
|
||||
|
||||
1. =wm_state_t.generation=
|
||||
每次成功提交状态变更后递增,用于缓存失效、增量重算和调试。
|
||||
|
||||
以后如果有必要,再给渲染缓存或状态服务增加更细粒度的版本号。
|
||||
|
||||
* 主执行流程
|
||||
|
||||
=runtime= 负责固定主循环顺序:
|
||||
|
||||
1. =backend.next_event()=
|
||||
2. runtime 先处理窗口元数据更新这类辅助事件,并直接更新 =wm_window_t= 中的元数据字段
|
||||
3. =policy.route_event()=
|
||||
4. =policy.apply_command()=
|
||||
5. 根据 =dirty_flags= 决定是否重新运行 =layout= 、解析最终外框矩形,并补全 =configure= effect 的边框宽度
|
||||
6. 将平台 effect 发送给 =backend=
|
||||
7. 将 render 失效通知发送给服务层
|
||||
8. =backend.flush()=
|
||||
|
||||
* 文件说明
|
||||
|
||||
1. =wm_types.h=
|
||||
通用标量类型、矩形、窗口几何模式和策略枚举。
|
||||
|
||||
2. =wm_state.h=
|
||||
全局状态容器,完全封装的内部实现,提供统一的访问 API。
|
||||
|
||||
3. =wm_event.h=
|
||||
统一后的运行时事件。
|
||||
|
||||
4. =wm_command.h=
|
||||
强类型语义命令。
|
||||
命令语义补充见 [[file:WM_COMMAND_RULES.org][WM_COMMAND_RULES.org]] ;
|
||||
=sticky= 多显示器与跨屏行为补充见 [[file:WM_STICKY_WINDOW_RULES.org][WM_STICKY_WINDOW_RULES.org]] 。
|
||||
|
||||
5. =wm_plan.h=
|
||||
脏标记和后端副作用。
|
||||
|
||||
6. =wm_layout.h=
|
||||
布局注册表、布局输入和布局输出。
|
||||
|
||||
7. =wm_backend.h=
|
||||
后端子系统接口。
|
||||
|
||||
8. =wm_policy_config.h=
|
||||
显式策略开关,替代散落在文档正文里的"可选策略"。
|
||||
|
||||
9. =wm_policy.h=
|
||||
事件路由和命令应用接口。
|
||||
|
||||
10. =wm_runtime.h=
|
||||
运行时上下文和生命周期接口。
|
||||
|
||||
11. =wm_service.h=
|
||||
核心外服务的注册和事件订阅接口。
|
||||
|
||||
12. [[file:WM_COMMAND_RULES.org][WM_COMMAND_RULES.org]]
|
||||
关键命令的前置条件、状态转移、可见性和副作用规则。
|
||||
|
||||
13. [[file:WM_STICKY_WINDOW_RULES.org][WM_STICKY_WINDOW_RULES.org]]
|
||||
=sticky= 窗口在 workspace、taskbar、多显示器和跨 output 拖动下的补充规则。
|
||||
|
||||
14. [[file:WM_POLICY_APPLY_COMMAND_SKELETON.org][WM_POLICY_APPLY_COMMAND_SKELETON.org]]
|
||||
=wm_policy_apply_command()= 的伪代码骨架和实现顺序建议。
|
||||
|
||||
15. [[file:WORKSPACE_CONFIG_EXAMPLES.org][WORKSPACE_CONFIG_EXAMPLES.org]]
|
||||
Workspace-Output 配置示例和最佳实践。
|
||||
|
||||
* 下一步实施顺序
|
||||
|
||||
当前阶段不建议继续扩展抽象,应该开始把最小核心草案落成可运行骨架。推荐顺序如下:
|
||||
|
||||
1. 在 =src/core/= 下建立最小骨架,至少放 =wm_types/state/plan/layout/policy/policy_config/runtime/service= 的头文件和空实现,先不要替换现有 =src/= 逻辑。
|
||||
|
||||
2. 优先实现 =state + query + plan= 这一层,把动态数组管理、=find_*= 查询函数和 =stack_order[]= 操作补齐。
|
||||
|
||||
3. 再实现 =wm_policy_apply_command()= 的一小批核心命令:
|
||||
=MANAGE_WINDOW=
|
||||
=UNMANAGE_WINDOW=
|
||||
=SWITCH_WORKSPACE=
|
||||
=SET_LAYOUT=
|
||||
=CYCLE_LAYOUT=
|
||||
=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= 暂时不要先动。它们属于核心外服务,应在最小核心链路稳定后再迁移。
|
||||
460
docs/min_core_draft/WM_COMMAND_RULES.org
Normal file
460
docs/min_core_draft/WM_COMMAND_RULES.org
Normal file
@@ -0,0 +1,460 @@
|
||||
* ZDWM 最小核心命令规则
|
||||
|
||||
这份文档定义 =wm_policy_apply_command()= 应遵守的最小语义。目标不是覆盖所有未来扩展,而是先把最关键的状态转移规则固定下来,避免后续实现时在 =floating=、=sticky=、=geometry_mode= 和 =stack_order[]= 上出现分叉语义。
|
||||
|
||||
=sticky= 在多显示器、taskbar 和跨 output 拖动下的补充语义见
|
||||
[[file:WM_STICKY_WINDOW_RULES.org][WM_STICKY_WINDOW_RULES.org]] 。
|
||||
|
||||
* 通用规则
|
||||
|
||||
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 => floating= 。
|
||||
7. =sticky= 只放宽 workspace 可见性,不改变 =workspace_id= 。
|
||||
8. =floating= 决定基础几何来源:
|
||||
=floating = false= 时,基础几何来自 layout 结果。
|
||||
=floating = true= 时,基础几何来自 =float_rect= 。
|
||||
9. 文档里出现的可选行为,应优先固化到 =wm_policy_config_t= ,而不是继续留在 prose 里。
|
||||
10. =frame_rect= 和 =float_rect= 都表示包含边框后的外框矩形。
|
||||
11. =border_width= 不是 =wm_window_t= 真状态;runtime 在提交
|
||||
=WM_EFFECT_CONFIGURE_WINDOW= 时按全局配置解析它。
|
||||
|
||||
* 管理类命令
|
||||
|
||||
** =WM_COMMAND_MANAGE_WINDOW=
|
||||
|
||||
前置条件:
|
||||
|
||||
1. 目标窗口尚未出现在 =state.windows[]= 中。
|
||||
2. 目标工作区存在。
|
||||
|
||||
状态变化:
|
||||
|
||||
1. 向 =state.windows[]= 添加一个新窗口。
|
||||
2. 设定其 =workspace_id= 。
|
||||
3. 初始 =floating= 、=sticky= 、=urgent= 、=geometry_mode= 由 =command.as.manage_window.initial_state= 显式给出。
|
||||
4. 若 =initial_state.sticky == true= ,则结果状态中必须满足 =floating == true= 。
|
||||
5. 若 =has_initial_float_rect == true= ,将其复制到 =float_rect= 。
|
||||
6. 将窗口追加到 =stack_order[]= 顶部。
|
||||
7. 是否将该窗口设为所属 workspace 的 =focused_window_id= ,由 =initial_state.set_focus= 或 =policy.manage_sets_focus= 决定。
|
||||
|
||||
副作用:
|
||||
|
||||
1. =WM_DIRTY_STATE=
|
||||
2. =WM_DIRTY_LAYOUT=
|
||||
3. =WM_DIRTY_STACK=
|
||||
4. =WM_DIRTY_RENDER=
|
||||
|
||||
** =WM_COMMAND_UPSERT_OUTPUT=
|
||||
|
||||
状态变化:
|
||||
|
||||
1. 若目标 output 已存在,只更新 =geometry= 和 =workarea= 。
|
||||
2. 若目标 output 不存在,则新建 output,并使用 =initial_workspace_id= 作为其 =current_workspace_id= 。
|
||||
3. 不修改任何窗口的 =workspace_id= 。
|
||||
|
||||
副作用:
|
||||
|
||||
1. =WM_DIRTY_OUTPUT=
|
||||
2. =WM_DIRTY_LAYOUT=
|
||||
3. =WM_DIRTY_RENDER=
|
||||
|
||||
** =WM_COMMAND_REMOVE_OUTPUT=
|
||||
|
||||
状态变化:
|
||||
|
||||
1. 从 =state.outputs[]= 中移除目标 output 。
|
||||
2. 不修改任何窗口的 =workspace_id= 。
|
||||
3. 若移除后暂时没有 output,允许 runtime 进入“无可见输出”的空闲态,等待后续热插拔恢复。
|
||||
|
||||
副作用:
|
||||
|
||||
1. =WM_DIRTY_OUTPUT=
|
||||
2. =WM_DIRTY_LAYOUT=
|
||||
3. =WM_DIRTY_RENDER=
|
||||
|
||||
** =WM_COMMAND_UNMANAGE_WINDOW=
|
||||
|
||||
前置条件:
|
||||
|
||||
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. 若 =policy.focus_raises == true= ,则在实现中额外执行 =raise_window= 逻辑。
|
||||
|
||||
** =WM_COMMAND_FOCUS_DIRECTION=
|
||||
|
||||
状态变化:
|
||||
|
||||
1. 只在目标 =output_id= 当前显示的 workspace 内选择焦点。
|
||||
2. 候选窗口应排除 =geometry_mode == WM_GEOMETRY_MINIMIZED= 的窗口。
|
||||
3. =sticky= 窗口是否参与方向焦点切换,由 =policy.sticky_windows_participate_in_direction_focus= 决定。
|
||||
|
||||
** =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_SET_LAYOUT=
|
||||
|
||||
状态变化:
|
||||
|
||||
1. 设置目标 workspace 的 =layout_id= 。
|
||||
2. 不改变窗口的 =workspace_id= 。
|
||||
3. 不改变 =stack_order[]= 。
|
||||
|
||||
副作用:
|
||||
|
||||
1. =WM_DIRTY_LAYOUT=
|
||||
2. =WM_DIRTY_RENDER=
|
||||
|
||||
** =WM_COMMAND_CYCLE_LAYOUT=
|
||||
|
||||
前置条件:
|
||||
|
||||
1. 目标 workspace 存在。
|
||||
2. Workspace 的可用布局列表非空(=layout_count > 0=)。
|
||||
|
||||
状态变化:
|
||||
|
||||
1. 若 =direction == 1= :切换到下一个布局(循环)。
|
||||
2. 若 =direction == -1= :切换到上一个布局(循环)。
|
||||
3. 在 workspace 的 =available_layouts[]= 数组中找到当前 =layout_id= 的索引。
|
||||
4. 按方向移动到相邻索引,循环回到数组开头或末尾。
|
||||
5. 将新的布局 ID 设置为 workspace 的 =layout_id= 。
|
||||
|
||||
副作用:
|
||||
|
||||
1. =WM_DIRTY_LAYOUT=
|
||||
2. =WM_DIRTY_RENDER=
|
||||
|
||||
* 模式切换命令
|
||||
|
||||
** =WM_COMMAND_TOGGLE_FLOATING=
|
||||
|
||||
前置条件:
|
||||
|
||||
1. 目标窗口存在。
|
||||
2. 若目标窗口当前 =sticky == true= ,命令失败。
|
||||
|
||||
状态变化:
|
||||
|
||||
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_STICKY=
|
||||
|
||||
状态变化:
|
||||
|
||||
1. 若原来 =sticky == false= :
|
||||
若当前 =floating == false= ,先将当前 =frame_rect= 复制到 =float_rect= ,再设 =floating = true= 。
|
||||
然后将 =sticky= 设为 =true= 。
|
||||
2. 若原来 =sticky == true= :
|
||||
将 =sticky= 设为 =false= 。
|
||||
3. 不改变 =workspace_id= 。
|
||||
4. 关闭 =sticky= 时不自动恢复平铺;=floating= 保持原值不变。
|
||||
5. 不改变 =geometry_mode= 。
|
||||
|
||||
副作用:
|
||||
|
||||
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。
|
||||
8. maximize 不改变全局边框策略;有效边框宽度仍由 runtime 统一解析。
|
||||
|
||||
副作用:
|
||||
|
||||
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[]= 。
|
||||
5. fullscreen 的有效边框宽度由 runtime 解析为 0 。
|
||||
|
||||
副作用:
|
||||
|
||||
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 后窗口不再参与显示和焦点候选。
|
||||
6. 若目标窗口是当前焦点,是否清空或重选焦点由 =policy.minimize_clears_focus= 决定。
|
||||
|
||||
副作用:
|
||||
|
||||
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= 。
|
||||
该规则对普通 =floating= 窗口和 =sticky= 窗口一致适用。
|
||||
4. 不改变 =geometry_mode= 。
|
||||
5. =float_rect= 是外框矩形,因此移动的是包含边框后的最终窗口外框。
|
||||
|
||||
副作用:
|
||||
|
||||
1. =WM_DIRTY_LAYOUT=
|
||||
2. =WM_DIRTY_RENDER=
|
||||
|
||||
** =WM_COMMAND_RESIZE_FLOATING_WINDOW=
|
||||
|
||||
前置条件:
|
||||
|
||||
1. 目标窗口存在。
|
||||
2. 目标窗口应为 =floating == true= 。
|
||||
|
||||
状态变化:
|
||||
|
||||
1. 按 =dw/dh= 修改 =float_rect.width/height= 。
|
||||
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
|
||||
其余模式 -> 全局边框宽度
|
||||
|
||||
副作用:
|
||||
|
||||
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;
|
||||
return wm_rect_intersects(window->frame_rect, output->geometry);
|
||||
}
|
||||
#+END_SRC
|
||||
|
||||
若后续存在按 output 渲染的 taskbar ,建议在下列条件同时满足时显示窗口条目:
|
||||
|
||||
1. =window->skip_taskbar == false=
|
||||
2. =wm_state_window_should_be_visible_on_output(...) == true=
|
||||
|
||||
* 几何解析优先级
|
||||
|
||||
窗口最终矩形建议按下面的优先级解析:
|
||||
|
||||
这里的“最终矩形”统一指包含边框后的外框矩形。
|
||||
|
||||
1. =WM_GEOMETRY_MINIMIZED=
|
||||
不显示。
|
||||
|
||||
2. =WM_GEOMETRY_FULLSCREEN=
|
||||
使用 =output.geometry= 。
|
||||
|
||||
3. =WM_GEOMETRY_MAXIMIZED=
|
||||
使用 =output.workarea= 。
|
||||
|
||||
4. =floating == true=
|
||||
使用 =float_rect= 。
|
||||
|
||||
5. 其余情况
|
||||
使用 layout 输出。
|
||||
393
docs/min_core_draft/WM_EVENT_ROUTING.org
Normal file
393
docs/min_core_draft/WM_EVENT_ROUTING.org
Normal file
@@ -0,0 +1,393 @@
|
||||
* ZDWM 最小核心事件路由设计
|
||||
|
||||
这份文档定义 =wm_event_t= 到 =wm_command_t= 的完整路由规则,确保事件处理链路的每个环节都有明确的行为定义。
|
||||
|
||||
* 事件路由总体流程
|
||||
|
||||
#+BEGIN_SRC c
|
||||
void wm_runtime_process_event(wm_runtime_t *runtime, const wm_event_t *event) {
|
||||
// 1. 元数据事件特殊处理(不经过命令系统)
|
||||
if (is_metadata_event(event)) {
|
||||
runtime_handle_metadata_event(runtime, event);
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 交互态下的特殊路由
|
||||
if (runtime->interaction.mode != WM_INTERACTION_NONE) {
|
||||
if (route_interaction_event(runtime, event, &runtime->command_buffer)) {
|
||||
return; // 事件已被交互态消费
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 常规事件路由
|
||||
wm_command_buffer_reset(&runtime->command_buffer);
|
||||
if (!wm_policy_route_event(&runtime->state, event, &runtime->command_buffer)) {
|
||||
return; // 没有生成命令
|
||||
}
|
||||
|
||||
// 4. 应用命令
|
||||
for (size_t i = 0; i < runtime->command_buffer.count; i++) {
|
||||
wm_policy_apply_command(
|
||||
&runtime->state,
|
||||
&runtime->layouts,
|
||||
&runtime->command_buffer.items[i],
|
||||
&runtime->plan
|
||||
);
|
||||
}
|
||||
|
||||
// 5. 处理副作用
|
||||
if (runtime->plan.dirty_flags) {
|
||||
runtime_apply_plan(runtime);
|
||||
}
|
||||
}
|
||||
#+END_SRC
|
||||
|
||||
* 元数据事件处理规则
|
||||
|
||||
元数据事件直接更新 =wm_window_t= 中的元数据字段,不经过命令系统:
|
||||
|
||||
**=WM_EVENT_WINDOW_METADATA_CHANGED=**
|
||||
|
||||
#+BEGIN_SRC c
|
||||
void runtime_handle_metadata_event(wm_runtime_t *runtime, const wm_event_t *event) {
|
||||
const wm_window_metadata_changed_event_t *e = &event->as.window_metadata_changed;
|
||||
|
||||
// 1. 查找窗口
|
||||
const wm_window_t *win = wm_state_window_get(&runtime->state, e->window_id);
|
||||
if (!win) {
|
||||
return; // 窗口已被销毁
|
||||
}
|
||||
|
||||
// 2. 通过 state 接口更新窗口元数据字段
|
||||
if (e->changed_fields & WM_WINDOW_META_CHANGED_TITLE) {
|
||||
wm_state_window_set_title(&runtime->state, e->window_id, e->title);
|
||||
}
|
||||
if (e->changed_fields & WM_WINDOW_META_CHANGED_APP_ID) {
|
||||
wm_state_window_set_app_id(&runtime->state, e->window_id, e->app_id);
|
||||
}
|
||||
if (e->changed_fields & WM_WINDOW_META_CHANGED_CLASS) {
|
||||
wm_state_window_set_class(&runtime->state, e->window_id, e->class_name);
|
||||
}
|
||||
if (e->changed_fields & WM_WINDOW_META_CHANGED_INSTANCE) {
|
||||
wm_state_window_set_instance(&runtime->state, e->window_id, e->instance_name);
|
||||
}
|
||||
|
||||
// 3. 标记 render 脏(状态栏需要更新)
|
||||
runtime->plan.dirty_flags |= WM_DIRTY_RENDER;
|
||||
|
||||
// 4. 通知订阅的服务(如状态栏组件)
|
||||
wm_service_notify_metadata_changed(&runtime->services,
|
||||
e->window_id,
|
||||
e->changed_fields);
|
||||
}
|
||||
#+END_SRC
|
||||
|
||||
**=WM_EVENT_STATUS_TICK=**
|
||||
|
||||
定时器事件,也不经过命令系统:
|
||||
|
||||
#+BEGIN_SRC c
|
||||
void runtime_handle_status_tick(wm_runtime_t *runtime, const wm_event_t *event) {
|
||||
// 通知状态服务更新
|
||||
wm_service_notify_status_tick(&runtime->services);
|
||||
|
||||
// 状态服务更新后,如果产生了新的渲染需求,会通过命令系统处理
|
||||
}
|
||||
#+END_SRC
|
||||
|
||||
* 快捷键绑定系统
|
||||
|
||||
**配置格式**:
|
||||
|
||||
#+BEGIN_SRC c
|
||||
// keybindings.conf
|
||||
keybindings = [
|
||||
{
|
||||
// 键盘绑定
|
||||
keysym: "Mod4+Return",
|
||||
modifiers: [Mod4],
|
||||
command: {
|
||||
type: WM_COMMAND_LAUNCH_TERMINAL
|
||||
}
|
||||
},
|
||||
{
|
||||
// 快捷键绑定(可带参数)
|
||||
keysym: "Mod4+Shift+q",
|
||||
command: {
|
||||
type: WM_COMMAND_FOCUS_WINDOW,
|
||||
direction: "prev"
|
||||
}
|
||||
},
|
||||
{
|
||||
// 布局切换
|
||||
keysym: "Mod4+space",
|
||||
command: {
|
||||
type: WM_COMMAND_SET_LAYOUT,
|
||||
layout_name: "tile"
|
||||
}
|
||||
},
|
||||
{
|
||||
// 窗口切换到工作区
|
||||
keysym: "Mod4+Shift+1",
|
||||
command: {
|
||||
type: WM_COMMAND_SEND_WINDOW_TO_WORKSPACE,
|
||||
workspace_index: 0
|
||||
}
|
||||
}
|
||||
]
|
||||
#+END_SRC
|
||||
|
||||
**快捷键匹配优先级**:
|
||||
|
||||
1. 完全匹配(keysym + 所有 modifiers)
|
||||
2. 通配符匹配(keysym + 部分修饰符)
|
||||
3. 默认绑定(如 Mod4+Enter 启动终端)
|
||||
|
||||
**=wm_policy_route_event()= 实现**:
|
||||
|
||||
#+BEGIN_SRC c
|
||||
bool wm_policy_route_event(const wm_state_t *state,
|
||||
const wm_event_t *event,
|
||||
wm_command_buffer_t *out) {
|
||||
switch (event->type) {
|
||||
case WM_EVENT_KEY_PRESS:
|
||||
return route_key_press(state, &event->as.key_press, out);
|
||||
|
||||
case WM_EVENT_BUTTON_PRESS:
|
||||
return route_button_press(state, &event->as.button_press, out);
|
||||
|
||||
case WM_EVENT_MAP_REQUEST:
|
||||
return route_map_request(state, &event->as.map_request, out);
|
||||
|
||||
case WM_EVENT_UNMAP_NOTIFY:
|
||||
return route_unmap_notify(state, &event->as.unmap_notify, out);
|
||||
|
||||
case WM_EVENT_DESTROY_NOTIFY:
|
||||
return route_destroy_notify(state, &event->as.destroy_notify, out);
|
||||
|
||||
case WM_EVENT_CONFIGURE_REQUEST:
|
||||
return route_configure_request(state, &event->as.configure_request, out);
|
||||
|
||||
case WM_EVENT_OUTPUT_CHANGED:
|
||||
return route_output_changed(state, &event->as.output_changed, out);
|
||||
|
||||
case WM_EVENT_OUTPUT_REMOVED:
|
||||
return route_output_removed(state, &event->as.output_removed, out);
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 键盘事件路由
|
||||
static bool route_key_press(const wm_state_t *state,
|
||||
const wm_key_press_event_t *e,
|
||||
wm_command_buffer_t *out) {
|
||||
// 1. 查找匹配的快捷键绑定
|
||||
const keybinding_t *binding = keybinding_find(
|
||||
e->keycode,
|
||||
e->modifiers
|
||||
);
|
||||
|
||||
if (!binding) {
|
||||
return false; // 没有匹配的绑定
|
||||
}
|
||||
|
||||
// 2. 将绑定转换为命令
|
||||
wm_command_t cmd = {
|
||||
.type = binding->command_type,
|
||||
// ... 填充命令参数
|
||||
};
|
||||
|
||||
// 3. 添加到命令缓冲
|
||||
return wm_command_buffer_push(out, cmd);
|
||||
}
|
||||
|
||||
// 窗口映射请求路由
|
||||
static bool route_map_request(const wm_state_t *state,
|
||||
const wm_map_request_event_t *e,
|
||||
wm_command_buffer_t *out) {
|
||||
// 1. 检查窗口是否已存在
|
||||
if (state_find_window(state, e->window_id)) {
|
||||
return false; // 窗口已管理,忽略
|
||||
}
|
||||
|
||||
// 2. 生成 MANAGE_WINDOW 命令
|
||||
wm_command_t cmd = {
|
||||
.type = WM_COMMAND_MANAGE_WINDOW,
|
||||
.manage_window = {
|
||||
.window_id = e->window_id,
|
||||
// 应用规则确定初始状态
|
||||
}
|
||||
};
|
||||
|
||||
return wm_command_buffer_push(out, cmd);
|
||||
}
|
||||
#+END_SRC
|
||||
|
||||
* 交互态事件路由
|
||||
|
||||
交互态会覆盖正常的事件路由:
|
||||
|
||||
**=WM_INTERACTION_MOVE_FLOATING=**
|
||||
|
||||
#+BEGIN_SRC c
|
||||
static bool route_interaction_event(wm_runtime_t *runtime,
|
||||
const wm_event_t *event,
|
||||
wm_command_buffer_t *out) {
|
||||
switch (runtime->interaction.mode) {
|
||||
case WM_INTERACTION_MOVE_FLOATING:
|
||||
return route_move_floating_event(runtime, event, out);
|
||||
|
||||
case WM_INTERACTION_RESIZE_FLOATING:
|
||||
return route_resize_floating_event(runtime, event, out);
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static bool route_move_floating_event(wm_runtime_t *runtime,
|
||||
const wm_event_t *event,
|
||||
wm_command_buffer_t *out) {
|
||||
switch (event->type) {
|
||||
case WM_EVENT_BUTTON_RELEASE:
|
||||
// 结束移动交互
|
||||
wm_command_t end_cmd = {
|
||||
.type = WM_COMMAND_END_INTERACTION
|
||||
};
|
||||
return wm_command_buffer_push(out, end_cmd);
|
||||
|
||||
case WM_EVENT_POINTER_MOTION:
|
||||
// 生成移动命令
|
||||
wm_command_t move_cmd = {
|
||||
.type = WM_COMMAND_MOVE_FLOATING_WINDOW,
|
||||
.move_floating_window = {
|
||||
.window_id = runtime->interaction.window_id,
|
||||
.delta_x = event->as.pointer_motion.root.x -
|
||||
runtime->interaction.pointer_origin.x,
|
||||
.delta_y = event->as.pointer_motion.root.y -
|
||||
runtime->interaction.pointer_origin.y
|
||||
}
|
||||
};
|
||||
return wm_command_buffer_push(out, move_cmd);
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#+END_SRC
|
||||
|
||||
* 命令生成规则
|
||||
|
||||
每种事件类型对应一个或多个命令的生成规则:
|
||||
|
||||
**输入事件 → 命令映射**
|
||||
|
||||
| 事件类型 | 可能生成的命令 | 条件 |
|
||||
|---------|---------------|------|
|
||||
| KEY_PRESS | 任意命令(通过快捷键绑定) | 绑定存在 |
|
||||
| BUTTON_PRESS | FOCUS_WINDOW, RAISE_WINDOW | 点击窗口 |
|
||||
| MAP_REQUEST | MANAGE_WINDOW | 窗口未管理 |
|
||||
| UNMAP_NOTIFY | (可能不生成命令) | - |
|
||||
| DESTROY_NOTIFY | UNMANAGE_WINDOW | 窗口已管理 |
|
||||
| CONFIGURE_REQUEST | (可能不生成命令) | floating 窗口 |
|
||||
| OUTPUT_CHANGED | UPSERT_OUTPUT | - |
|
||||
| OUTPUT_REMOVED | REMOVE_OUTPUT | - |
|
||||
| POINTER_MOTION | MOVE/RESIZE 命令 | 交互态下 |
|
||||
|
||||
* 事件处理的边界条件
|
||||
|
||||
**1. 元数据事件边界**
|
||||
|
||||
元数据事件满足以下条件时直接处理,不经过命令系统:
|
||||
- 事件类型是 `WM_EVENT_WINDOW_METADATA_CHANGED`
|
||||
- 事件类型是 `WM_EVENT_STATUS_TICK`
|
||||
|
||||
**2. 交互态边界**
|
||||
|
||||
交互态满足以下条件时,优先路由到交互处理器:
|
||||
- `runtime->interaction.mode != WM_INTERACTION_NONE`
|
||||
- 事件类型与交互相关(POINTER_MOTION, BUTTON_RELEASE)
|
||||
|
||||
**3. 窗口管理边界**
|
||||
|
||||
窗口管理的边界条件:
|
||||
- `WM_EVENT_MAP_REQUEST`:窗口已存在则忽略
|
||||
- `WM_EVENT_DESTROY_NOTIFY`:窗口不存在则忽略
|
||||
- `WM_EVENT_CONFIGURE_REQUEST`:只有 floating 窗口才响应
|
||||
|
||||
* 事件处理顺序保证
|
||||
|
||||
为了保证确定性行为,事件处理必须按以下顺序进行:
|
||||
|
||||
1. **元数据事件优先**:状态栏信息更新不触发布局计算
|
||||
2. **交互态优先**:拖拽操作优先于快捷键
|
||||
3. **快捷键匹配优先级**:完全匹配 → 通配符 → 默认
|
||||
4. **命令执行顺序**:命令缓冲中的命令按顺序执行
|
||||
|
||||
* 主执行流程实现
|
||||
|
||||
#+BEGIN_SRC c
|
||||
void wm_runtime_run(wm_runtime_t *runtime) {
|
||||
wm_event_t event;
|
||||
|
||||
while (runtime->running) {
|
||||
// 1. 获取事件
|
||||
if (!wm_backend_next_event(&runtime->backend, &event)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 2. 处理事件
|
||||
wm_runtime_process_event(runtime, &event);
|
||||
|
||||
// 3. 如果有副作用,应用它们
|
||||
if (runtime->plan.dirty_flags) {
|
||||
// 3.1 重新计算布局(如果需要)
|
||||
if (runtime->plan.dirty_flags & WM_DIRTY_LAYOUT) {
|
||||
wm_runtime_relayout(runtime);
|
||||
}
|
||||
|
||||
// 3.2 应用平台副作用
|
||||
wm_backend_apply_effects(&runtime->backend, &runtime->plan);
|
||||
|
||||
// 3.3 通知服务层(渲染、状态栏等)
|
||||
if (runtime->plan.dirty_flags & WM_DIRTY_RENDER) {
|
||||
wm_service_notify_render(&runtime->services, &runtime->state);
|
||||
}
|
||||
|
||||
// 3.4 刷新平台
|
||||
wm_backend_flush(&runtime->backend);
|
||||
|
||||
// 3.5 重置 plan
|
||||
wm_plan_reset(&runtime->plan);
|
||||
}
|
||||
|
||||
// 4. 处理定时器(可选,降低 CPU 使用)
|
||||
usleep(1000); // 1ms
|
||||
}
|
||||
}
|
||||
#+END_SRC
|
||||
|
||||
* 与现有功能的兼容性
|
||||
|
||||
这个事件路由设计完全兼容现有 ZDWM 功能:
|
||||
|
||||
1. **快捷键系统**:通过 keybindings.conf 配置
|
||||
2. **规则系统**:在 MANAGE_WINDOW 命令中应用
|
||||
3. **布局切换**:通过 SET_LAYOUT 命令
|
||||
4. **拖拽交互**:通过交互态管理
|
||||
5. **多显示器**:OUTPUT_CHANGED/REMOVED 事件处理
|
||||
|
||||
* 缺失的文档总结
|
||||
|
||||
通过补充本文档,mini_core_draft 的事件处理链路就完整了:
|
||||
|
||||
1. ✅ 事件定义(wm_event.h)
|
||||
2. ✅ 命令定义(wm_command.h + WM_COMMAND_RULES.org)
|
||||
3. ✅ 路由接口(wm_policy.h)
|
||||
4. ✅ 命令应用(WM_POLICY_APPLY_COMMAND_SKELETON.org)
|
||||
5. ✅ **路由规则**(本文档补充)
|
||||
6. ✅ **快捷键系统**(本文档补充)
|
||||
7. ✅ **交互态处理**(本文档补充)
|
||||
8. ✅ **主执行流程**(本文档补充)
|
||||
708
docs/min_core_draft/WM_POLICY_APPLY_COMMAND_SKELETON.org
Normal file
708
docs/min_core_draft/WM_POLICY_APPLY_COMMAND_SKELETON.org
Normal file
@@ -0,0 +1,708 @@
|
||||
* ZDWM =wm_policy_apply_command()= 伪代码骨架
|
||||
|
||||
这份文档给出 =wm_policy_apply_command()= 的最小实现骨架。目标是把实际代码的控制流固定住,使其和 [[file:WM_COMMAND_RULES.org][WM_COMMAND_RULES.org]] 中定义的命令规则一一对应。
|
||||
|
||||
=sticky= 在多显示器、taskbar 和跨 output 拖动下的补充语义见
|
||||
[[file:WM_STICKY_WINDOW_RULES.org][WM_STICKY_WINDOW_RULES.org]] 。
|
||||
|
||||
* 函数职责
|
||||
|
||||
=wm_policy_apply_command()= 只做四件事:
|
||||
|
||||
1. 校验命令输入是否合法。
|
||||
2. 修改 =wm_state_t= 中的运行状态。
|
||||
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. 从平台读取事件。
|
||||
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_UPSERT_OUTPUT:
|
||||
changed = apply_upsert_output(state, command, plan);
|
||||
break;
|
||||
|
||||
case WM_COMMAND_REMOVE_OUTPUT:
|
||||
changed = apply_remove_output(state, command, plan);
|
||||
break;
|
||||
|
||||
case WM_COMMAND_FOCUS_WINDOW:
|
||||
changed = apply_focus_window(state, command, plan);
|
||||
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_STICKY:
|
||||
changed = apply_toggle_sticky(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_CYCLE_LAYOUT:
|
||||
changed = apply_cycle_layout(state, 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 state_upsert_output(wm_state_t *state, wm_output_t output);
|
||||
static bool state_remove_output(wm_state_t *state, wm_output_id_t id);
|
||||
|
||||
static bool 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_window_get(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 = command->as.manage_window.initial_state.geometry_mode,
|
||||
.floating = command->as.manage_window.initial_state.floating,
|
||||
.sticky = command->as.manage_window.initial_state.sticky,
|
||||
.urgent = command->as.manage_window.initial_state.urgent,
|
||||
};
|
||||
|
||||
if (win.sticky) {
|
||||
win.floating = true;
|
||||
}
|
||||
|
||||
if (command->as.manage_window.has_initial_float_rect) {
|
||||
win.float_rect = command->as.manage_window.initial_float_rect;
|
||||
}
|
||||
|
||||
if (!state_append_window(state, win)) return false;
|
||||
if (!stack_append(state, id)) return false;
|
||||
|
||||
if (command->as.manage_window.initial_state.set_focus) {
|
||||
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_upsert_output()=
|
||||
|
||||
#+BEGIN_SRC c
|
||||
static bool apply_upsert_output(wm_state_t *state,
|
||||
const wm_command_t *command,
|
||||
wm_plan_t *plan) {
|
||||
wm_output_t output = {
|
||||
.id = command->as.upsert_output.output_id,
|
||||
.geometry = command->as.upsert_output.geometry,
|
||||
.workarea = command->as.upsert_output.workarea,
|
||||
.current_workspace_id = command->as.upsert_output.initial_workspace_id,
|
||||
};
|
||||
|
||||
if (!state_upsert_output(state, output)) return false;
|
||||
|
||||
plan->dirty_flags |= WM_DIRTY_OUTPUT | WM_DIRTY_LAYOUT | WM_DIRTY_RENDER;
|
||||
push_render_all_outputs(state, plan);
|
||||
return true;
|
||||
}
|
||||
#+END_SRC
|
||||
|
||||
** =apply_remove_output()=
|
||||
|
||||
#+BEGIN_SRC c
|
||||
static bool apply_remove_output(wm_state_t *state,
|
||||
const wm_command_t *command,
|
||||
wm_plan_t *plan) {
|
||||
if (!state_remove_output(state, command->as.remove_output.output_id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
plan->dirty_flags |= WM_DIRTY_OUTPUT | WM_DIRTY_LAYOUT | WM_DIRTY_RENDER;
|
||||
push_render_all_outputs(state, plan);
|
||||
return true;
|
||||
}
|
||||
#+END_SRC
|
||||
|
||||
** =apply_unmanage_window()=
|
||||
|
||||
#+BEGIN_SRC c
|
||||
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_toggle_sticky()=
|
||||
|
||||
#+BEGIN_SRC c
|
||||
static bool apply_toggle_sticky(wm_state_t *state,
|
||||
const wm_command_t *command,
|
||||
wm_plan_t *plan) {
|
||||
wm_window_t *win = require_window(state, command->as.toggle_sticky.window_id);
|
||||
if (!win) return false;
|
||||
|
||||
if (!win->sticky) {
|
||||
if (!win->floating) {
|
||||
win->float_rect = win->frame_rect;
|
||||
win->floating = true;
|
||||
}
|
||||
win->sticky = true;
|
||||
} else {
|
||||
win->sticky = false;
|
||||
/* 关闭 sticky 时保持 floating 不变,不自动恢复平铺。 */
|
||||
}
|
||||
|
||||
plan->dirty_flags |= WM_DIRTY_LAYOUT | WM_DIRTY_RENDER;
|
||||
push_render_all_outputs(state, plan);
|
||||
return true;
|
||||
}
|
||||
#+END_SRC
|
||||
|
||||
实际实现中,若关闭 =sticky= 后窗口对当前正在查看的 workspace 不再可见,
|
||||
且它曾是相关 workspace 的焦点窗口,则应重选该 workspace 的焦点。
|
||||
|
||||
** =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->sticky) 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;
|
||||
|
||||
wm_workspace_id_t old_ws_id = win->workspace_id;
|
||||
|
||||
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;
|
||||
if (win->workspace_id != old_ws_id) {
|
||||
wm_workspace_t *src = require_workspace(state, old_ws_id);
|
||||
if (src && src->focused_window_id == win->id) {
|
||||
maybe_refocus_workspace(state, src);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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.width =
|
||||
clamp_i32(win->float_rect.width + command->as.resize_floating_window.dw,
|
||||
WM_MIN_WINDOW_WIDTH, INT32_MAX);
|
||||
win->float_rect.height =
|
||||
clamp_i32(win->float_rect.height + command->as.resize_floating_window.dh,
|
||||
WM_MIN_WINDOW_HEIGHT, INT32_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_slot_get(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_cycle_layout()=
|
||||
|
||||
#+BEGIN_SRC c
|
||||
static bool apply_cycle_layout(wm_state_t *state,
|
||||
const wm_command_t *command,
|
||||
wm_plan_t *plan) {
|
||||
wm_workspace_t *ws =
|
||||
require_workspace(state, command->as.cycle_layout.workspace_id);
|
||||
if (!ws) return false;
|
||||
if (ws->layout_count == 0) return false;
|
||||
|
||||
// 找到当前布局在列表中的索引
|
||||
size_t current_idx = 0;
|
||||
for (size_t i = 0; i < ws->layout_count; i++) {
|
||||
if (ws->available_layouts[i] == ws->layout_id) {
|
||||
current_idx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 计算下一个索引(支持前后循环)
|
||||
size_t next_idx;
|
||||
if (command->as.cycle_layout.direction > 0) {
|
||||
next_idx = (current_idx + 1) % ws->layout_count;
|
||||
} else {
|
||||
next_idx = (current_idx == 0) ? (ws->layout_count - 1) : (current_idx - 1);
|
||||
}
|
||||
|
||||
// 如果新旧布局相同,直接返回
|
||||
wm_layout_id_t next_layout = ws->available_layouts[next_idx];
|
||||
if (next_layout == ws->layout_id) return false;
|
||||
|
||||
ws->layout_id = next_layout;
|
||||
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 结果解析最终矩形。
|
||||
=sticky= 窗口由于满足 =sticky => floating= ,因此同样走 =float_rect= 路径。
|
||||
3. 生成 =WM_EFFECT_CONFIGURE_WINDOW= 。
|
||||
4. 若有 =WM_DIRTY_STACK= ,生成 =WM_EFFECT_RESTACK_WINDOWS= 。
|
||||
5. 若有 =WM_DIRTY_RENDER= ,生成 =WM_EFFECT_RENDER_OUTPUT= 。
|
||||
6. =WM_EFFECT_RENDER_OUTPUT= 由 runtime 分发给服务层,而不是直接交给 backend 。
|
||||
|
||||
* 为什么要拆成多个 =apply_*()=
|
||||
|
||||
1. 便于单元测试,每个命令可以单独测试。
|
||||
2. 便于和 =WM_COMMAND_RULES.org= 对照审查。
|
||||
3. 便于以后替换部分策略,例如 =focus raises= 或 sticky 参与焦点候选的规则。
|
||||
4. 便于将复杂逻辑下沉到更小的辅助函数,而不是把 =wm_policy_apply_command()= 写成一个超长 switch。
|
||||
200
docs/min_core_draft/WM_STICKY_WINDOW_RULES.org
Normal file
200
docs/min_core_draft/WM_STICKY_WINDOW_RULES.org
Normal file
@@ -0,0 +1,200 @@
|
||||
* Sticky 窗口规则
|
||||
|
||||
这份文档补充 =sticky= 窗口在最小核心中的约束,重点覆盖以下问题:
|
||||
|
||||
1. =sticky= 与 =floating= 的关系
|
||||
2. =sticky= 窗口是否参与自动布局
|
||||
3. =sticky= 窗口在 taskbar 中的显示规则
|
||||
4. 多显示器下 =sticky= 窗口的可见性与跨屏移动语义
|
||||
|
||||
* 核心结论
|
||||
|
||||
1. =sticky => floating=
|
||||
2. =sticky= 窗口不参与任何自动布局
|
||||
3. =sticky= 只放宽 workspace 可见性,不改变窗口的唯一归属 =workspace_id=
|
||||
4. =sticky= 窗口不复制到每个 output;是否出现在某个 output 上,只由其最终矩形是否覆盖该 output 决定
|
||||
5. 拖动 =sticky= 或普通 =floating= 窗口跨 output 时,默认只修改 =float_rect= ,不隐式修改 =workspace_id=
|
||||
6. 只有显式提交跨 output 迁移时,才允许把窗口归属 workspace 改到目标 output 当前显示的 workspace
|
||||
|
||||
* 状态约束
|
||||
|
||||
** =sticky= 与 =floating=
|
||||
|
||||
最小核心中,=sticky= 不是独立于 =floating= 的另一套几何模式,而是附加在 floating 语义之上的可见性扩展。
|
||||
|
||||
因此规定:
|
||||
|
||||
1. 若窗口进入 =sticky= 状态,则必须满足 =floating == true=
|
||||
2. 若窗口原本是平铺窗口,开启 =sticky= 时,应先把当前最终外框矩形写入 =float_rect= ,再设 =floating = true= ,最后设 =sticky = true=
|
||||
3. 关闭 =sticky= 时,只清除 =sticky= 标记,不自动恢复到平铺模式
|
||||
|
||||
不自动恢复平铺的原因是:否则核心必须额外保存“进入 sticky 前是否为 floating”的历史状态,复杂度不值得。
|
||||
|
||||
** 唯一归属 workspace
|
||||
|
||||
每个窗口始终只有一个归属 =workspace_id= 。
|
||||
|
||||
=sticky= 的作用只是:
|
||||
|
||||
- 让窗口在非归属 workspace 上也可见
|
||||
|
||||
而不是:
|
||||
|
||||
- 把窗口复制到多个 workspace
|
||||
- 让窗口同时归属于多个 workspace
|
||||
|
||||
因此:
|
||||
|
||||
1. =workspace_id= 仍然是窗口的唯一归属
|
||||
2. layout、focus 历史、命令目标和后续迁移规则都仍以该归属 workspace 为准
|
||||
|
||||
* 布局规则
|
||||
|
||||
** 不参与自动布局
|
||||
|
||||
=sticky= 窗口不参与任何自动布局。
|
||||
|
||||
这条规则在单显示器和多显示器下都成立:
|
||||
|
||||
1. 不进入任何 workspace 的平铺输入
|
||||
2. 不进入任何 output 的布局输入
|
||||
3. 几何始终来自 =float_rect=
|
||||
|
||||
原因:
|
||||
|
||||
1. 自动布局应是 workspace-local / output-local 的
|
||||
2. =sticky= 如果参与布局,会让同一个窗口对多个 workspace 或多个 output 的布局结果产生耦合
|
||||
3. 同一个窗口不能同时接受多份独立 layout 结果
|
||||
|
||||
** =fn == NULL= 的 floating 布局槽位
|
||||
|
||||
若当前 workspace 的活动布局槽位满足 =fn == NULL= ,则该布局表示 =floating= 布局。
|
||||
|
||||
这只表示当前 workspace 上的普通窗口不进行平铺计算,不改变 =sticky= 窗口的特殊规则:
|
||||
|
||||
1. 普通 floating 窗口:几何来自各自的 =float_rect=
|
||||
2. =sticky= 窗口:同样几何来自 =float_rect=
|
||||
3. 二者都不参与 layout 函数计算
|
||||
|
||||
* Taskbar 规则
|
||||
|
||||
** 基本规则
|
||||
|
||||
如果 taskbar 表达的是“当前 workspace 上用户可见的窗口列表”,则 =sticky= 窗口应被视为可见窗口,因此应出现在 taskbar 中。
|
||||
|
||||
推荐的基础判断是:
|
||||
|
||||
1. =skip_taskbar == false=
|
||||
2. 窗口在当前上下文中实际可见
|
||||
|
||||
** 单显示器
|
||||
|
||||
单显示器下,若当前 workspace 与窗口归属 workspace 不同,只要窗口是 =sticky= 且当前几何可见,仍应出现在 taskbar 中。
|
||||
|
||||
** 多显示器 / 每 output 一条 taskbar
|
||||
|
||||
若系统为每个 output 提供独立 taskbar ,推荐规则是:
|
||||
|
||||
1. 只在窗口实际可见的 output 上显示该窗口
|
||||
2. 不在所有 output 的 taskbar 中无条件复制显示
|
||||
|
||||
也就是说:
|
||||
|
||||
1. 窗口只落在左屏,则只显示在左屏 taskbar
|
||||
2. 窗口只落在右屏,则只显示在右屏 taskbar
|
||||
3. 窗口跨越两个 output ,则可同时出现在两个 output 的 taskbar
|
||||
|
||||
这样 taskbar 与屏幕上的真实可见内容保持一致。
|
||||
|
||||
* 多显示器规则
|
||||
|
||||
** 坐标空间
|
||||
|
||||
=float_rect= 和 =frame_rect= 使用全局桌面坐标,而不是某个 output 的局部坐标。
|
||||
|
||||
因此:
|
||||
|
||||
1. 一个 floating/sticky 窗口可以自然跨越多个 output
|
||||
2. output 可见性可以通过“窗口最终矩形是否与 output 矩形相交”来判断
|
||||
|
||||
** 可见性
|
||||
|
||||
=sticky= 只放宽 workspace 可见性,不提供 output 复制语义。
|
||||
|
||||
因此:
|
||||
|
||||
1. 窗口不会因为 =sticky= 自动在所有 output 上都出现
|
||||
2. 它只会出现在最终矩形实际覆盖到的 output 上
|
||||
3. 若窗口完全移到另一块屏幕,则旧屏幕上不再可见
|
||||
|
||||
* 跨 output 移动规则
|
||||
|
||||
** 拖动过程中
|
||||
|
||||
无论窗口是普通 =floating= 还是 =sticky= ,拖动过程中都只更新 =float_rect= 。
|
||||
|
||||
不应在拖动过程中频繁修改 =workspace_id= ,否则归属语义会抖动,也不利于测试。
|
||||
|
||||
** 拖动结束后的归属处理
|
||||
|
||||
拖动结束后,是否迁移窗口归属 workspace,应由显式策略控制,而不是隐式发生。
|
||||
|
||||
推荐沿用以下策略:
|
||||
|
||||
1. =WM_CROSS_OUTPUT_KEEP_WORKSPACE=
|
||||
- 只更新 =float_rect=
|
||||
- 不修改 =workspace_id=
|
||||
2. =WM_CROSS_OUTPUT_MOVE_TO_TARGET_WORKSPACE=
|
||||
- 使用稳定锚点确定目标 output
|
||||
- 若锚点落在另一个 output 上,则把 =workspace_id= 改为该 output 当前显示的 workspace
|
||||
|
||||
推荐锚点:
|
||||
|
||||
1. 窗口中心点
|
||||
2. 或命令中显式提供的 =anchor=
|
||||
|
||||
不建议按覆盖面积决定归属,因为该规则在窗口跨屏时不稳定,也不直观。
|
||||
|
||||
** =sticky= 下的跨 output 移动
|
||||
|
||||
=sticky= 窗口跨屏拖动时,默认也不自动修改 =workspace_id= 。
|
||||
|
||||
原因:
|
||||
|
||||
1. =sticky= 本来就可以跨 workspace 可见
|
||||
2. 若移动几何时顺便修改归属,会让“移动窗口”和“迁移窗口”两个动作发生耦合
|
||||
|
||||
因此推荐:
|
||||
|
||||
1. 默认只更新 =float_rect=
|
||||
2. 若用户明确要求迁移,则再执行归属 workspace 切换
|
||||
|
||||
* 关闭 Sticky 的语义
|
||||
|
||||
关闭 =sticky= 时:
|
||||
|
||||
1. 只清除 =sticky= 标记
|
||||
2. 不自动修改 =workspace_id=
|
||||
3. 不自动修改 =floating=
|
||||
4. 不自动恢复平铺
|
||||
|
||||
若当前查看的 workspace 不是窗口归属的 =workspace_id= ,则关闭 =sticky= 后窗口应立即从当前视图消失。
|
||||
|
||||
如果该窗口在关闭 =sticky= 前是当前焦点窗口,则需要重新选择当前 workspace 的焦点窗口。
|
||||
|
||||
* 设计原则总结
|
||||
|
||||
这套规则的目标是把几个概念彻底拆开:
|
||||
|
||||
1. =floating= 管几何来源
|
||||
2. =sticky= 管跨 workspace 可见性
|
||||
3. =workspace_id= 管唯一归属
|
||||
4. 跨 output 拖动只改几何
|
||||
5. workspace 迁移必须显式提交
|
||||
|
||||
这样可以避免以下几类混乱语义:
|
||||
|
||||
1. 拖动窗口时归属 workspace 频繁跳变
|
||||
2. =sticky= 窗口同时参与多个布局
|
||||
3. taskbar 显示内容与实际可见窗口不一致
|
||||
4. 关闭 =sticky= 时隐式触发额外状态回滚
|
||||
286
docs/min_core_draft/WORKSPACE_CONFIG_EXAMPLES.org
Normal file
286
docs/min_core_draft/WORKSPACE_CONFIG_EXAMPLES.org
Normal file
@@ -0,0 +1,286 @@
|
||||
* Workspace-Output 配置示例
|
||||
|
||||
本文档展示在不同场景下如何配置 workspace 和 output 的归属关系。
|
||||
|
||||
** 单显示器配置
|
||||
|
||||
最简单的配置:所有 workspace 归属到唯一输出。
|
||||
|
||||
#+BEGIN_SRC lua
|
||||
-- zdwm.lua
|
||||
|
||||
outputs = {
|
||||
{ id = 0, name = "DP-1", enabled = true, primary = true },
|
||||
}
|
||||
|
||||
-- 所有 workspace 都归属 DP-1
|
||||
workspaces = {
|
||||
{ id = 1, name = "1", output_id = 0 },
|
||||
{ id = 2, name = "2", output_id = 0 },
|
||||
{ id = 3, name = "3", output_id = 0 },
|
||||
{ id = 4, name = "4", output_id = 0 },
|
||||
{ id = 5, name = "5", output_id = 0 },
|
||||
{ id = 6, name = "6", output_id = 0 },
|
||||
{ id = 7, name = "7", output_id = 0 },
|
||||
{ id = 8, name = "8", output_id = 0 },
|
||||
{ id = 9, name = "9", output_id = 0 },
|
||||
{ id = 10, name = "10", output_id = 0 },
|
||||
}
|
||||
|
||||
-- 或使用自动生成
|
||||
workspaces = auto_generate_workspaces({
|
||||
output_id = 0,
|
||||
count = 10,
|
||||
name_pattern = "number" -- 1, 2, 3, ...
|
||||
})
|
||||
#+END_SRC
|
||||
|
||||
** 双显示器配置
|
||||
|
||||
*** 场景 1:平分 workspace
|
||||
|
||||
#+BEGIN_SRC lua
|
||||
-- 左侧显示器 1-5,右侧显示器 6-10
|
||||
outputs = {
|
||||
{ id = 0, name = "DP-1", enabled = true, primary = true },
|
||||
{ id = 1, name = "DP-2", enabled = true },
|
||||
}
|
||||
|
||||
workspaces = {
|
||||
-- DP-1 的 workspace
|
||||
{ id = 1, name = "1", output_id = 0 },
|
||||
{ id = 2, name = "2", output_id = 0 },
|
||||
{ id = 3, name = "3", output_id = 0 },
|
||||
{ id = 4, name = "4", output_id = 0 },
|
||||
{ id = 5, name = "5", output_id = 0 },
|
||||
|
||||
-- DP-2 的 workspace
|
||||
{ id = 6, name = "6", output_id = 1 },
|
||||
{ id = 7, name = "7", output_id = 1 },
|
||||
{ id = 8, name = "8", output_id = 1 },
|
||||
{ id = 9, name = "9", output_id = 1 },
|
||||
{ id = 10, name = "10", output_id = 1 },
|
||||
}
|
||||
#+END_SRC
|
||||
|
||||
*** 场景 2:偶数/奇数分配
|
||||
|
||||
#+BEGIN_SRC lua
|
||||
-- 奇数在左,偶数在右
|
||||
workspaces = {}
|
||||
for i = 1, 10 do
|
||||
table.insert(workspaces, {
|
||||
id = i,
|
||||
name = tostring(i),
|
||||
output_id = (i % 2 == 1) and 0 or 1 -- 奇数->0,偶数->1
|
||||
})
|
||||
end
|
||||
#+END_SRC
|
||||
|
||||
*** 场景 3:主题化 workspace
|
||||
|
||||
#+BEGIN_SRC lua
|
||||
-- 左侧开发相关,右侧娱乐相关
|
||||
outputs = {
|
||||
{ id = 0, name = "DP-1", primary = true },
|
||||
{ id = 1, name = "HDMI-1" },
|
||||
}
|
||||
|
||||
workspaces = {
|
||||
-- DP-1 (开发)
|
||||
{ id = 1, name = "code", output_id = 0 },
|
||||
{ id = 2, name = "term", output_id = 0 },
|
||||
{ id = 3, name = "docs", output_id = 0 },
|
||||
{ id = 4, name = "debug", output_id = 0 },
|
||||
|
||||
-- HDMI-1 (娱乐)
|
||||
{ id = 5, name = "chat", output_id = 1 },
|
||||
{ id = 6, name = "web", output_id = 1 },
|
||||
{ id = 7, name = "media", output_id = 1 },
|
||||
{ id = 8, name = "game", output_id = 1 },
|
||||
}
|
||||
#+END_SRC
|
||||
|
||||
** 三显示器配置
|
||||
|
||||
笔记本 + 外接显示器的常见配置:
|
||||
|
||||
#+BEGIN_SRC lua
|
||||
-- eDP-1: 日常工作 (1-3)
|
||||
-- DP-1: 代码 (4-6)
|
||||
-- HDMI-1: 娱乐 (7-9)
|
||||
|
||||
outputs = {
|
||||
{ id = 0, name = "eDP-1", primary = true },
|
||||
{ id = 1, name = "DP-1", enabled = true },
|
||||
{ id = 2, name = "HDMI-1", enabled = true },
|
||||
}
|
||||
|
||||
workspaces = {
|
||||
-- eDP-1 (笔记本)
|
||||
{ id = 1, name = "mail", output_id = 0 },
|
||||
{ id = 2, name = "chat", output_id = 0 },
|
||||
{ id = 3, name = "docs", output_id = 0 },
|
||||
|
||||
-- DP-1 (外接显示器 - 左)
|
||||
{ id = 4, name = "code", output_id = 1 },
|
||||
{ id = 5, name = "term", output_id = 1 },
|
||||
{ id = 6, name = "debug", output_id = 1 },
|
||||
|
||||
-- HDMI-1 (外接显示器 - 右)
|
||||
{ id = 7, name = "web", output_id = 2 },
|
||||
{ id = 8, name = "media", output_id = 2 },
|
||||
{ id = 9, name = "game", output_id = 2 },
|
||||
}
|
||||
#+END_SRC
|
||||
|
||||
** 动态配置(脚本式)
|
||||
|
||||
根据实际检测到的输出数量动态生成:
|
||||
|
||||
#+BEGIN_SRC lua
|
||||
function configure_workspaces(runtime)
|
||||
local outputs = runtime:scan_outputs()
|
||||
local workspaces = {}
|
||||
local ws_id = 1
|
||||
|
||||
-- 每个 output 分配 3 个 workspace
|
||||
for _, output in ipairs(outputs) do
|
||||
for i = 1, 3 do
|
||||
table.insert(workspaces, {
|
||||
id = ws_id,
|
||||
name = string.format("%d:%s", ws_id, output.name),
|
||||
output_id = output.id,
|
||||
layout_id = 0, -- 默认布局
|
||||
})
|
||||
ws_id = ws_id + 1
|
||||
end
|
||||
end
|
||||
|
||||
return workspaces
|
||||
end
|
||||
#+END_SRC
|
||||
|
||||
** 配置加载流程
|
||||
|
||||
#+BEGIN_SRC c
|
||||
// config_loader.c
|
||||
|
||||
typedef struct workspace_config_t {
|
||||
wm_workspace_id_t id;
|
||||
const char *name;
|
||||
wm_output_id_t output_id;
|
||||
wm_layout_id_t layout_id;
|
||||
} workspace_config_t;
|
||||
|
||||
bool load_workspace_config(const char *path,
|
||||
workspace_config_t **out_workspaces,
|
||||
size_t *out_count) {
|
||||
// 1. 扫描输出
|
||||
output_config_t *outputs;
|
||||
size_t output_count;
|
||||
scan_outputs(&outputs, &output_count);
|
||||
|
||||
// 2. 加载 workspace 配置
|
||||
workspace_config_t *workspaces;
|
||||
size_t workspace_count;
|
||||
parse_config_file(path, &workspaces, &workspace_count);
|
||||
|
||||
// 3. 验证:每个 workspace 的 output_id 必须有效
|
||||
for (size_t i = 0; i < workspace_count; i++) {
|
||||
if (workspaces[i].output_id >= output_count) {
|
||||
fprintf(stderr, "Workspace %d: invalid output_id %d\n",
|
||||
workspaces[i].id, workspaces[i].output_id);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
*out_workspaces = workspaces;
|
||||
*out_count = workspace_count;
|
||||
return true;
|
||||
}
|
||||
#+END_SRC
|
||||
|
||||
** 切换行为示例
|
||||
|
||||
#+BEGIN_SRC lua
|
||||
-- 切换到 workspace 4(在 DP-1 上)
|
||||
-- 效果:只有 DP-1 切换,DP-2 保持当前 workspace
|
||||
|
||||
-- 将窗口从 workspace 1 移动到 workspace 4
|
||||
-- 效果:窗口从 DP-1 移动到 DP-1(但在不同的 workspace)
|
||||
-- 如果 workspace 4 当前在 DP-2 显示,窗口会在 DP-2 出现
|
||||
#+END_SRC
|
||||
|
||||
** 布局列表配置
|
||||
|
||||
每个 workspace 可以配置自己的可用布局列表。
|
||||
|
||||
*** 基础配置
|
||||
|
||||
#+BEGIN_SRC lua
|
||||
-- 声明布局注册表
|
||||
layouts = {
|
||||
{ id = 0, name = "T", fn = layout_tile },
|
||||
{ id = 1, name = "M", fn = layout_monocle },
|
||||
{ id = 2, name = "F", fn = layout_floating },
|
||||
{ id = 3, name = "S", fn = layout_stack },
|
||||
{ id = 4, name = "G", fn = layout_grid },
|
||||
}
|
||||
|
||||
-- 单显示器:每个 workspace 可用所有布局
|
||||
workspaces = {
|
||||
{ id = 1, name = "1", output_id = 0, layouts = {0, 1, 2, 3, 4}, default_layout = 0 },
|
||||
{ id = 2, name = "2", output_id = 0, layouts = {0, 1, 2, 3, 4}, default_layout = 0 },
|
||||
{ id = 3, name = "3", output_id = 0, layouts = {0, 1, 2, 3, 4}, default_layout = 0 },
|
||||
}
|
||||
#+END_SRC
|
||||
|
||||
*** 主题化布局配置
|
||||
|
||||
不同用途的 workspace 配置不同的布局集合:
|
||||
|
||||
#+BEGIN_SRC lua
|
||||
workspaces = {
|
||||
-- 代码 workspace:平铺、单列、堆叠(适合代码+终端)
|
||||
{ id = 1, name = "code", output_id = 0,
|
||||
layouts = {0, 3, 4}, default_layout = 0 }, -- T, S, G
|
||||
|
||||
-- 浏览器 workspace:单列、浮动、全屏(适合阅读)
|
||||
{ id = 2, name = "web", output_id = 0,
|
||||
layouts = {1, 2}, default_layout = 1 }, -- M, F
|
||||
|
||||
-- 游戏 workspace:仅浮动(游戏窗口通常需要浮动)
|
||||
{ id = 3, name = "game", output_id = 1,
|
||||
layouts = {2}, default_layout = 2 }, -- F only
|
||||
|
||||
-- 聊天 workspace:堆叠、单列
|
||||
{ id = 4, name = "chat", output_id = 1,
|
||||
layouts = {3, 1}, default_layout = 3 }, -- S, M
|
||||
}
|
||||
#+END_SRC
|
||||
|
||||
*** 默认布局配置
|
||||
|
||||
如果不指定 layouts 字段,使用全局默认布局列表:
|
||||
|
||||
#+BEGIN_SRC lua
|
||||
-- 全局默认
|
||||
default_layouts = {0, 1, 2, 3, 4} -- 所有布局可用
|
||||
|
||||
-- workspace 省略 layouts 时使用全局默认
|
||||
workspaces = {
|
||||
{ id = 1, name = "1", output_id = 0 }, -- 使用全局默认
|
||||
{ id = 2, name = "2", output_id = 0,
|
||||
layouts = {0, 1}, default_layout = 0 }, -- 自定义
|
||||
}
|
||||
#+END_SRC
|
||||
|
||||
** 注意事项
|
||||
|
||||
1. *固定归属*:workspace.output_id 在创建后不应改变
|
||||
2. *输出变化*:显示器配置变化时,WM 会重启,重新扫描和配置
|
||||
3. *可见性*:窗口只在归属的 output 上可见
|
||||
4. *独立性*:切换不同 output 的 workspace 互不影响
|
||||
5. *布局列表*:workspace.layouts 在启动时分配,运行时固定
|
||||
6. *布局切换*:使用 CYCLE_LAYOUT 命令在可用布局间循环切换
|
||||
23
docs/min_core_draft/wm_backend.h
Normal file
23
docs/min_core_draft/wm_backend.h
Normal file
@@ -0,0 +1,23 @@
|
||||
#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);
|
||||
/*
|
||||
* Runtime filters out service-side effects such as WM_EFFECT_RENDER_OUTPUT.
|
||||
* Backends only receive platform effects here.
|
||||
*/
|
||||
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;
|
||||
};
|
||||
143
docs/min_core_draft/wm_command.h
Normal file
143
docs/min_core_draft/wm_command.h
Normal file
@@ -0,0 +1,143 @@
|
||||
#pragma once
|
||||
|
||||
#include "wm_types.h"
|
||||
|
||||
typedef struct wm_manage_window_init_t {
|
||||
wm_window_geometry_mode_t geometry_mode;
|
||||
bool floating;
|
||||
bool sticky;
|
||||
bool urgent;
|
||||
bool set_focus;
|
||||
} wm_manage_window_init_t;
|
||||
|
||||
typedef enum wm_command_type_t {
|
||||
WM_COMMAND_NONE,
|
||||
WM_COMMAND_MANAGE_WINDOW,
|
||||
WM_COMMAND_UNMANAGE_WINDOW,
|
||||
WM_COMMAND_UPSERT_OUTPUT,
|
||||
WM_COMMAND_REMOVE_OUTPUT,
|
||||
WM_COMMAND_FOCUS_WINDOW,
|
||||
WM_COMMAND_FOCUS_DIRECTION,
|
||||
WM_COMMAND_RAISE_WINDOW,
|
||||
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_STICKY,
|
||||
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_CYCLE_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;
|
||||
wm_manage_window_init_t initial_state;
|
||||
bool has_initial_float_rect;
|
||||
wm_rect_t initial_float_rect;
|
||||
} manage_window;
|
||||
|
||||
struct {
|
||||
wm_window_id_t window_id;
|
||||
} unmanage_window;
|
||||
|
||||
struct {
|
||||
wm_output_id_t output_id;
|
||||
wm_rect_t geometry;
|
||||
wm_rect_t workarea;
|
||||
wm_workspace_id_t initial_workspace_id;
|
||||
} upsert_output;
|
||||
|
||||
struct {
|
||||
wm_output_id_t output_id;
|
||||
} remove_output;
|
||||
|
||||
struct {
|
||||
wm_window_id_t window_id;
|
||||
} focus_window;
|
||||
|
||||
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_sticky;
|
||||
|
||||
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;
|
||||
|
||||
struct {
|
||||
wm_workspace_id_t workspace_id;
|
||||
int direction; // 1: 下一个布局, -1: 上一个布局
|
||||
} cycle_layout;
|
||||
} as;
|
||||
} wm_command_t;
|
||||
111
docs/min_core_draft/wm_event.h
Normal file
111
docs/min_core_draft/wm_event.h
Normal file
@@ -0,0 +1,111 @@
|
||||
#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_OUTPUT_REMOVED,
|
||||
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;
|
||||
/*
|
||||
* These strings are borrowed from the backend adapter and are valid until
|
||||
* wm_runtime_process_event() returns. changed_fields decides which ones are
|
||||
* meaningful for the current event.
|
||||
* runtime 会复制这些字符串到 wm_window_t 中的对应字段。
|
||||
*/
|
||||
const char *title;
|
||||
const char *app_id;
|
||||
const char *class_name;
|
||||
const char *instance_name;
|
||||
} 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_output_removed_event_t {
|
||||
wm_output_id_t output_id;
|
||||
} wm_output_removed_event_t;
|
||||
|
||||
typedef struct wm_timer_tick_event_t {
|
||||
uint64_t now_ms;
|
||||
} wm_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_output_removed_event_t output_removed;
|
||||
wm_timer_tick_event_t timer_tick;
|
||||
} as;
|
||||
} wm_event_t;
|
||||
65
docs/min_core_draft/wm_layout.h
Normal file
65
docs/min_core_draft/wm_layout.h
Normal file
@@ -0,0 +1,65 @@
|
||||
#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_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;
|
||||
|
||||
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;
|
||||
/*
|
||||
* 布局标识符,用于:
|
||||
* - 状态栏显示(应简短,如 "T", "M", "F")
|
||||
* - 日志输出
|
||||
* - 配置引用
|
||||
*
|
||||
* 推荐使用 1-2 个字符的简短标识符。
|
||||
*/
|
||||
const char *name;
|
||||
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);
|
||||
67
docs/min_core_draft/wm_plan.h
Normal file
67
docs/min_core_draft/wm_plan.h
Normal file
@@ -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; // runtime 解析出的有效边框宽度
|
||||
} 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);
|
||||
26
docs/min_core_draft/wm_policy.h
Normal file
26
docs/min_core_draft/wm_policy.h
Normal file
@@ -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);
|
||||
13
docs/min_core_draft/wm_policy_config.h
Normal file
13
docs/min_core_draft/wm_policy_config.h
Normal file
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
typedef struct wm_policy_config_t {
|
||||
bool focus_raises;
|
||||
bool sticky_windows_participate_in_direction_focus;
|
||||
bool manage_sets_focus;
|
||||
bool switch_workspace_restores_last_focus;
|
||||
bool minimize_clears_focus;
|
||||
} wm_policy_config_t;
|
||||
|
||||
void wm_policy_config_init_default(wm_policy_config_t *config);
|
||||
98
docs/min_core_draft/wm_runtime.h
Normal file
98
docs/min_core_draft/wm_runtime.h
Normal file
@@ -0,0 +1,98 @@
|
||||
#pragma once
|
||||
|
||||
#include "wm_backend.h"
|
||||
#include "wm_layout.h"
|
||||
#include "wm_plan.h"
|
||||
#include "wm_policy_config.h"
|
||||
#include "wm_policy.h"
|
||||
#include "wm_service.h"
|
||||
#include "wm_state.h"
|
||||
#include <stdint.h>
|
||||
|
||||
typedef enum wm_interaction_mode_t {
|
||||
WM_INTERACTION_NONE,
|
||||
WM_INTERACTION_MOVE_FLOATING,
|
||||
WM_INTERACTION_RESIZE_FLOATING,
|
||||
} wm_interaction_mode_t;
|
||||
|
||||
typedef struct wm_interaction_state_t {
|
||||
wm_interaction_mode_t mode;
|
||||
wm_window_id_t window_id;
|
||||
wm_output_id_t origin_output_id;
|
||||
wm_point_t pointer_origin;
|
||||
wm_rect_t window_origin_rect;
|
||||
} wm_interaction_state_t;
|
||||
|
||||
typedef struct wm_runtime_bootstrap_t {
|
||||
/*
|
||||
* 输出配置(启动时扫描显示器后填充)
|
||||
*/
|
||||
const wm_output_t *outputs;
|
||||
size_t output_count;
|
||||
|
||||
/*
|
||||
* 工作区配置(已绑定 output)
|
||||
* workspace.output_id 指向 outputs 中的某个输出
|
||||
*/
|
||||
const wm_workspace_t *workspaces;
|
||||
size_t workspace_count;
|
||||
|
||||
/*
|
||||
* 布局算法注册
|
||||
*/
|
||||
struct {
|
||||
wm_layout_fn fn;
|
||||
const char *name;
|
||||
} *layouts;
|
||||
size_t layout_count;
|
||||
|
||||
/*
|
||||
* 策略配置
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
const wm_command_t *initial_commands;
|
||||
size_t initial_command_count;
|
||||
} wm_runtime_bootstrap_t;
|
||||
|
||||
// 运行时上下文(实现时采用不透明结构体,此定义仅作参考)
|
||||
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_policy_config_t policy;
|
||||
uint16_t border_width;
|
||||
wm_interaction_state_t interaction;
|
||||
wm_backend_t backend;
|
||||
wm_service_registry_t services;
|
||||
} wm_runtime_t;
|
||||
|
||||
bool wm_runtime_init(wm_runtime_t *runtime, wm_backend_t backend,
|
||||
const wm_runtime_bootstrap_t *bootstrap);
|
||||
bool wm_runtime_register_service(wm_runtime_t *runtime, wm_service_t service);
|
||||
|
||||
/*
|
||||
* 处理单个事件,方便做无平台依赖的单元测试。
|
||||
* 元数据事件允许 runtime 直接更新 auxiliary store;控制状态变更仍经由
|
||||
* route_event() + apply_command()。
|
||||
*/
|
||||
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);
|
||||
45
docs/min_core_draft/wm_service.h
Normal file
45
docs/min_core_draft/wm_service.h
Normal file
@@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
|
||||
#include "wm_state.h"
|
||||
|
||||
typedef enum wm_service_event_type_t {
|
||||
WM_SERVICE_EVENT_NONE,
|
||||
WM_SERVICE_EVENT_RUNTIME_STARTED,
|
||||
WM_SERVICE_EVENT_RUNTIME_STOPPING,
|
||||
WM_SERVICE_EVENT_RENDER_OUTPUT,
|
||||
} wm_service_event_type_t;
|
||||
|
||||
typedef struct wm_service_event_t {
|
||||
wm_service_event_type_t type;
|
||||
union {
|
||||
struct {
|
||||
wm_output_id_t output_id;
|
||||
} render_output;
|
||||
} as;
|
||||
} wm_service_event_t;
|
||||
|
||||
typedef struct wm_service_t wm_service_t;
|
||||
|
||||
typedef struct wm_service_api_t {
|
||||
bool (*init)(wm_service_t *service);
|
||||
void (*handle_event)(wm_service_t *service, const wm_service_event_t *event,
|
||||
const wm_state_t *state);
|
||||
void (*shutdown)(wm_service_t *service);
|
||||
} wm_service_api_t;
|
||||
|
||||
struct wm_service_t {
|
||||
const wm_service_api_t *api;
|
||||
void *impl;
|
||||
};
|
||||
|
||||
typedef struct wm_service_registry_t {
|
||||
wm_service_t *items;
|
||||
size_t count;
|
||||
size_t capacity;
|
||||
} wm_service_registry_t;
|
||||
|
||||
void wm_service_registry_init(wm_service_registry_t *registry);
|
||||
void wm_service_registry_shutdown(wm_service_registry_t *registry);
|
||||
|
||||
bool wm_service_registry_register(wm_service_registry_t *registry,
|
||||
wm_service_t service);
|
||||
167
docs/min_core_draft/wm_state.h
Normal file
167
docs/min_core_draft/wm_state.h
Normal file
@@ -0,0 +1,167 @@
|
||||
#pragma once
|
||||
|
||||
#include "wm_types.h"
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.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;
|
||||
|
||||
// 几何信息(均为包含边框后的外框矩形)
|
||||
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;
|
||||
} 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;
|
||||
|
||||
// ========== API 函数 ==========
|
||||
|
||||
// 初始化/清理
|
||||
void wm_state_init(wm_state_t *state,
|
||||
size_t workspace_count,
|
||||
size_t output_count);
|
||||
void wm_state_shutdown(wm_state_t *state);
|
||||
|
||||
// ========== Workspace 访问 ==========
|
||||
|
||||
wm_workspace_t *wm_state_workspace(wm_state_t *state, wm_workspace_id_t id);
|
||||
wm_workspace_t *wm_state_workspace_at(wm_state_t *state, size_t index);
|
||||
size_t wm_state_workspace_count(const wm_state_t *state);
|
||||
bool wm_state_workspace_valid(wm_state_t *state, wm_workspace_id_t id);
|
||||
|
||||
// 查询某个 output 的所有 workspace
|
||||
size_t wm_state_workspace_get_by_output(const wm_state_t *state,
|
||||
wm_output_id_t output_id,
|
||||
wm_workspace_t **out_workspaces);
|
||||
|
||||
// ========== Workspace 布局操作 ==========
|
||||
|
||||
/*
|
||||
* 设置 workspace 的可用布局列表
|
||||
* 注意:应在 workspace 初始化时调用,运行时不应修改
|
||||
*/
|
||||
void wm_workspace_set_layouts(wm_workspace_t *workspace,
|
||||
const wm_layout_id_t *layouts,
|
||||
size_t count);
|
||||
|
||||
/*
|
||||
* 获取 workspace 的可用布局列表
|
||||
*/
|
||||
const wm_layout_id_t *wm_workspace_get_layouts(const wm_workspace_t *workspace,
|
||||
size_t *out_count);
|
||||
|
||||
/*
|
||||
* 切换到下一个布局(循环)
|
||||
*/
|
||||
bool wm_workspace_cycle_layout(wm_workspace_t *workspace);
|
||||
|
||||
/*
|
||||
* 切换到指定索引的布局
|
||||
*/
|
||||
bool wm_workspace_set_layout_by_index(wm_workspace_t *workspace, size_t index);
|
||||
|
||||
/*
|
||||
* 切换到指定 ID 的布局(如果该布局在可用列表中)
|
||||
*/
|
||||
bool wm_workspace_set_layout_by_id(wm_workspace_t *workspace, wm_layout_id_t id);
|
||||
|
||||
// ========== Output 访问 ==========
|
||||
|
||||
wm_output_t *wm_state_output(wm_state_t *state, wm_output_id_t id);
|
||||
wm_output_t *wm_state_output_at(wm_state_t *state, size_t index);
|
||||
size_t wm_state_output_count(const wm_state_t *state);
|
||||
bool wm_state_output_valid(wm_state_t *state, wm_output_id_t id);
|
||||
|
||||
// ========== Window 操作 ==========
|
||||
|
||||
wm_window_t *wm_state_window_add(wm_state_t *state, wm_window_id_t id);
|
||||
wm_window_t *wm_state_window_get(wm_state_t *state, wm_window_id_t id);
|
||||
wm_window_t *wm_state_window_at(wm_state_t *state, size_t index);
|
||||
void wm_state_window_remove(wm_state_t *state, wm_window_id_t id);
|
||||
size_t wm_state_window_count(const wm_state_t *state);
|
||||
|
||||
// ========== 查询辅助函数 ==========
|
||||
|
||||
/*
|
||||
* 判断工作区是否可见(某个 output 正在显示它)
|
||||
*/
|
||||
bool wm_state_workspace_is_visible(const wm_state_t *state,
|
||||
wm_workspace_id_t workspace_id);
|
||||
|
||||
/*
|
||||
* 判断窗口是否可见
|
||||
* 窗口可见 iff:
|
||||
* 1. 窗口的 workspace 固定归属某个 output
|
||||
* 2. 该 output 当前正显示这个 workspace
|
||||
* 3. 窗口未最小化
|
||||
*/
|
||||
bool wm_state_window_should_be_visible(const wm_state_t *state,
|
||||
const wm_window_t *window);
|
||||
|
||||
/*
|
||||
* 判断窗口是否在特定 output 上可见
|
||||
*/
|
||||
bool wm_state_window_should_be_visible_on_output(const wm_state_t *state,
|
||||
const wm_window_t *window,
|
||||
const wm_output_t *output);
|
||||
48
docs/min_core_draft/wm_types.h
Normal file
48
docs/min_core_draft/wm_types.h
Normal file
@@ -0,0 +1,48 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
// ID 类型定义
|
||||
// workspace/output/layout 在启动时分配固定数量,运行时不再增删
|
||||
// window 由后端管理,使用后端原生 ID
|
||||
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;
|
||||
|
||||
// 无效 ID 标记
|
||||
#define WM_WINDOW_ID_INVALID ((wm_window_id_t)0)
|
||||
#define WM_WORKSPACE_ID_INVALID ((wm_workspace_id_t)-1)
|
||||
#define WM_OUTPUT_ID_INVALID ((wm_output_id_t)-1)
|
||||
#define WM_LAYOUT_ID_INVALID ((wm_layout_id_t)-1)
|
||||
|
||||
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;
|
||||
99
src/core/layout.c
Normal file
99
src/core/layout.c
Normal file
@@ -0,0 +1,99 @@
|
||||
#include "core/layout.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "utils.h"
|
||||
|
||||
void wm_layout_result_init(wm_layout_result_t *result) {
|
||||
result->item_count = 0;
|
||||
result->item_capacity = INIT_CAPACITY;
|
||||
result->items = p_new(wm_layout_item_t, result->item_capacity);
|
||||
}
|
||||
|
||||
void wm_layout_result_reset(wm_layout_result_t *result) {
|
||||
result->item_count = 0;
|
||||
}
|
||||
|
||||
void wm_layout_result_cleanup(wm_layout_result_t *result) {
|
||||
p_delete(&result->items);
|
||||
result->item_count = 0;
|
||||
result->item_capacity = 0;
|
||||
}
|
||||
|
||||
void wm_layout_result_push(wm_layout_result_t *result, wm_layout_item_t item) {
|
||||
if (result->item_count == result->item_capacity) {
|
||||
size_t new_capacity = next_capacity(result->item_capacity);
|
||||
p_realloc(&result->items, new_capacity);
|
||||
result->item_capacity = new_capacity;
|
||||
}
|
||||
|
||||
result->items[result->item_count++] = item;
|
||||
}
|
||||
|
||||
void wm_layout_registry_init(wm_layout_registry_t *registry) {
|
||||
registry->slot_count = 0;
|
||||
registry->slot_capacity = INIT_CAPACITY;
|
||||
registry->slots = p_new(wm_layout_slot_t, registry->slot_capacity);
|
||||
}
|
||||
|
||||
void wm_layout_registry_cleanup(wm_layout_registry_t *registry) {
|
||||
while (registry->slot_count > 0) {
|
||||
wm_layout_slot_t *r = ®istry->slots[--registry->slot_count];
|
||||
p_delete(&r->name);
|
||||
p_delete(&r->symbol);
|
||||
p_delete(&r->description);
|
||||
r->fn = nullptr;
|
||||
r->id = WM_LAYOUT_ID_INVALID;
|
||||
}
|
||||
p_delete(®istry->slots);
|
||||
registry->slot_count = 0;
|
||||
registry->slot_capacity = 0;
|
||||
}
|
||||
|
||||
size_t wm_layout_registry_count(const wm_layout_registry_t *registry) {
|
||||
return registry->slot_count;
|
||||
}
|
||||
|
||||
const wm_layout_slot_t *wm_layout_registry_at(
|
||||
const wm_layout_registry_t *registry, size_t index) {
|
||||
if (index >= registry->slot_count) return nullptr;
|
||||
return ®istry->slots[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) {
|
||||
if (!name || !symbol) return WM_LAYOUT_ID_INVALID;
|
||||
|
||||
if (registry->slot_count == registry->slot_capacity) {
|
||||
size_t new_capacity = next_capacity(registry->slot_capacity);
|
||||
p_realloc(®istry->slots, new_capacity);
|
||||
registry->slot_capacity = new_capacity;
|
||||
}
|
||||
wm_layout_id_t id = (wm_layout_id_t)registry->slot_count;
|
||||
|
||||
wm_layout_slot_t *r = ®istry->slots[registry->slot_count];
|
||||
r->id = id;
|
||||
r->name = p_strdup(name);
|
||||
r->symbol = p_strdup(symbol);
|
||||
r->description = description ? p_strdup(description) : nullptr;
|
||||
r->fn = fn;
|
||||
|
||||
registry->slot_count++;
|
||||
return id;
|
||||
}
|
||||
|
||||
wm_layout_fn wm_layout_get(const wm_layout_registry_t *registry,
|
||||
wm_layout_id_t id) {
|
||||
const wm_layout_slot_t *layout_slot = wm_layout_slot_get(registry, id);
|
||||
if (layout_slot) return layout_slot->fn;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const wm_layout_slot_t *wm_layout_slot_get(const wm_layout_registry_t *registry,
|
||||
wm_layout_id_t id) {
|
||||
if (id >= registry->slot_count) return nullptr;
|
||||
return ®istry->slots[id];
|
||||
}
|
||||
138
src/core/layout.h
Normal file
138
src/core/layout.h
Normal file
@@ -0,0 +1,138 @@
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include "core/types.h"
|
||||
|
||||
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;
|
||||
/*
|
||||
* 参与本次布局计算的窗口 ID 列表。
|
||||
*
|
||||
* 调用方负责在进入 layout 前完成筛选;这里不包含 floating、sticky、
|
||||
* fullscreen、minimized 等不参与平铺计算的窗口。
|
||||
*
|
||||
* 数组顺序具有语义,layout 应按该顺序解释窗口排列优先级。
|
||||
*/
|
||||
const wm_window_id_t *window_ids;
|
||||
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;
|
||||
|
||||
/*
|
||||
* 调用约束:
|
||||
* - result 必须是有效的非空指针
|
||||
* - 除 init 之外,其余 result 相关接口都要求 result 已初始化
|
||||
* - 传入空指针或未初始化对象属于调用方错误
|
||||
*/
|
||||
void wm_layout_result_init(wm_layout_result_t *result);
|
||||
void wm_layout_result_reset(wm_layout_result_t *result);
|
||||
void wm_layout_result_cleanup(wm_layout_result_t *result);
|
||||
|
||||
void wm_layout_result_push(wm_layout_result_t *result, wm_layout_item_t item);
|
||||
|
||||
/*
|
||||
* 调用约束:
|
||||
* - registry 必须是有效的非空指针
|
||||
* - 除 init 之外,其余 registry 相关接口都要求 registry 已初始化
|
||||
* - 传入空指针或未初始化对象属于调用方错误
|
||||
*/
|
||||
void wm_layout_registry_init(wm_layout_registry_t *registry);
|
||||
void wm_layout_registry_cleanup(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);
|
||||
|
||||
/*
|
||||
* 注册一个布局并返回其稳定 ID。
|
||||
*
|
||||
* 前置条件:
|
||||
* - registry 必须已初始化
|
||||
*
|
||||
* 参数约束:
|
||||
* - name 不可为空,用于稳定配置名、日志和调试展示
|
||||
* - symbol 不可为空,用于状态栏等紧凑展示
|
||||
* - description 可为空
|
||||
* - fn 可为空;fn == NULL 表示 floating 布局,不参与平铺计算
|
||||
*
|
||||
* 返回值:
|
||||
* - 成功时返回新注册布局的 ID
|
||||
* - name 或 symbol 非法时返回 WM_LAYOUT_ID_INVALID
|
||||
*
|
||||
* 注册成功后,布局 ID 与其在 registry 中的槽位索引保持一致。
|
||||
*/
|
||||
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);
|
||||
13
src/core/runtime.h
Normal file
13
src/core/runtime.h
Normal file
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#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);
|
||||
539
src/core/state.c
Normal file
539
src/core/state.c
Normal file
@@ -0,0 +1,539 @@
|
||||
#include "core/state.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "core/types.h"
|
||||
#include "utils.h"
|
||||
#include "wm_desc.h"
|
||||
|
||||
void wm_state_init(wm_state_t *state, const wm_output_info_t *outputs,
|
||||
size_t output_count, const wm_workspace_desc_t *workspaces,
|
||||
size_t workspace_count) {
|
||||
p_clear(state, 1);
|
||||
|
||||
state->outputs = p_new(wm_output_t, output_count);
|
||||
for (size_t i = 0; i < output_count; i++) {
|
||||
const wm_output_info_t *output_info = &outputs[i];
|
||||
wm_output_t *output = &state->outputs[i];
|
||||
|
||||
output->id = (wm_output_id_t)i;
|
||||
output->current_workspace_id = WM_WORKSPACE_ID_INVALID;
|
||||
output->name = p_strdup(output_info->name);
|
||||
output->geometry = output_info->geometry;
|
||||
output->workarea = output_info->geometry;
|
||||
}
|
||||
state->output_count = output_count;
|
||||
|
||||
state->workspaces = p_new(wm_workspace_t, workspace_count);
|
||||
for (size_t i = 0; i < workspace_count; i++) {
|
||||
const wm_workspace_desc_t *workspace_desc = &workspaces[i];
|
||||
if (!wm_workspace_desc_valid(workspace_desc, output_count)) {
|
||||
fatal("workspace desc at index %zu invalid", i);
|
||||
}
|
||||
|
||||
wm_output_t *output = &state->outputs[workspace_desc->output_index];
|
||||
wm_workspace_t *workspace = &state->workspaces[i];
|
||||
workspace->id = (wm_workspace_id_t)i;
|
||||
workspace->output_id = output->id;
|
||||
workspace->focused_window_id = WM_WINDOW_ID_INVALID;
|
||||
workspace->available_layouts =
|
||||
p_copy(workspace_desc->layout_ids, workspace_desc->layout_count);
|
||||
workspace->layout_id = workspace_desc->initial_layout_id;
|
||||
workspace->layout_count = workspace_desc->layout_count;
|
||||
workspace->name = p_strdup(workspace_desc->name);
|
||||
|
||||
if (output->current_workspace_id == WM_WORKSPACE_ID_INVALID) {
|
||||
output->current_workspace_id = workspace->id;
|
||||
}
|
||||
}
|
||||
state->workspace_count = workspace_count;
|
||||
|
||||
for (size_t i = 0; i < state->output_count; i++) {
|
||||
const wm_output_t *output = &state->outputs[i];
|
||||
if (output->current_workspace_id == WM_WORKSPACE_ID_INVALID) {
|
||||
fatal("output at index %zu has no workspace", i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void wm_state_cleanup(wm_state_t *state) {
|
||||
for (size_t i = 0; i < state->window_count; i++) {
|
||||
wm_window_t *window = &state->windows[i];
|
||||
p_delete(&window->title);
|
||||
p_delete(&window->app_id);
|
||||
p_delete(&window->class_name);
|
||||
p_delete(&window->instance_name);
|
||||
}
|
||||
state->window_count = 0;
|
||||
p_delete(&state->windows);
|
||||
p_delete(&state->stack_order);
|
||||
state->window_capacity = 0;
|
||||
|
||||
for (size_t i = 0; i < state->workspace_count; i++) {
|
||||
wm_workspace_t *workspace = &state->workspaces[i];
|
||||
p_delete(&workspace->name);
|
||||
p_delete(&workspace->available_layouts);
|
||||
workspace->layout_count = 0;
|
||||
}
|
||||
p_delete(&state->workspaces);
|
||||
state->workspace_count = 0;
|
||||
|
||||
for (size_t i = 0; i < state->output_count; i++) {
|
||||
wm_output_t *output = &state->outputs[i];
|
||||
p_delete(&output->name);
|
||||
}
|
||||
p_delete(&state->outputs);
|
||||
state->output_count = 0;
|
||||
|
||||
p_clear(state, 1);
|
||||
}
|
||||
|
||||
const wm_workspace_t *wm_state_workspace_get(const wm_state_t *state,
|
||||
wm_workspace_id_t id) {
|
||||
if (id < state->workspace_count) return &state->workspaces[id];
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const wm_workspace_t *wm_state_workspace_at(const wm_state_t *state,
|
||||
size_t index) {
|
||||
if (index < state->workspace_count) return &state->workspaces[index];
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static void wm_state_workspace_adjust_focused_window(
|
||||
const wm_state_t *state, wm_workspace_id_t workspace_id) {
|
||||
wm_workspace_t *workspace =
|
||||
(wm_workspace_t *)wm_state_workspace_get(state, workspace_id);
|
||||
if (!workspace) return;
|
||||
|
||||
const wm_window_t *window =
|
||||
wm_state_window_get(state, workspace->focused_window_id);
|
||||
if (window && window->workspace_id == workspace_id) return;
|
||||
|
||||
for (size_t i = state->window_count; i > 0; i--) {
|
||||
wm_window_id_t window_id = state->stack_order[i - 1];
|
||||
const wm_window_t *window = wm_state_window_get(state, window_id);
|
||||
if (window && window->workspace_id == workspace_id) {
|
||||
workspace->focused_window_id = window_id;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
workspace->focused_window_id = WM_WINDOW_ID_INVALID;
|
||||
}
|
||||
|
||||
bool wm_state_workspace_cycle_layout(wm_state_t *state,
|
||||
wm_workspace_id_t workspace_id) {
|
||||
wm_workspace_t *workspace =
|
||||
(wm_workspace_t *)wm_state_workspace_get(state, workspace_id);
|
||||
if (!workspace) return false;
|
||||
|
||||
size_t next_index = 0;
|
||||
bool matched = false;
|
||||
for (size_t i = 0; i < workspace->layout_count; i++) {
|
||||
if (workspace->layout_id == workspace->available_layouts[i]) {
|
||||
next_index = (i + 1) % workspace->layout_count;
|
||||
matched = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!matched) return false;
|
||||
|
||||
auto next_layout_id = workspace->available_layouts[next_index];
|
||||
workspace->layout_id = next_layout_id;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool wm_state_workspace_set_layout_by_index(wm_state_t *state,
|
||||
wm_workspace_id_t workspace_id,
|
||||
size_t index) {
|
||||
wm_workspace_t *workspace =
|
||||
(wm_workspace_t *)wm_state_workspace_get(state, workspace_id);
|
||||
if (!workspace) return false;
|
||||
if (index >= workspace->layout_count) return false;
|
||||
|
||||
workspace->layout_id = workspace->available_layouts[index];
|
||||
return true;
|
||||
}
|
||||
|
||||
bool wm_state_workspace_set_layout_by_id(wm_state_t *state,
|
||||
wm_workspace_id_t workspace_id,
|
||||
wm_layout_id_t layout_id) {
|
||||
wm_workspace_t *workspace =
|
||||
(wm_workspace_t *)wm_state_workspace_get(state, workspace_id);
|
||||
if (!workspace) return false;
|
||||
for (size_t i = 0; i < workspace->layout_count; i++) {
|
||||
if (workspace->available_layouts[i] == layout_id) {
|
||||
workspace->layout_id = layout_id;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void wm_state_workspace_set_focused_window(wm_state_t *state,
|
||||
wm_workspace_id_t workspace_id,
|
||||
wm_window_id_t window_id) {
|
||||
wm_workspace_t *workspace =
|
||||
(wm_workspace_t *)wm_state_workspace_get(state, workspace_id);
|
||||
if (!workspace) return;
|
||||
|
||||
for (size_t i = 0; i < state->window_count; i++) {
|
||||
wm_window_t *window = &state->windows[i];
|
||||
if (window->id == window_id && window->workspace_id == workspace_id) {
|
||||
workspace->focused_window_id = window_id;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
wm_state_workspace_adjust_focused_window(state, workspace_id);
|
||||
}
|
||||
|
||||
size_t wm_state_workspace_count(const wm_state_t *state) {
|
||||
return state->workspace_count;
|
||||
}
|
||||
|
||||
bool wm_state_workspace_valid(const wm_state_t *state, wm_workspace_id_t id) {
|
||||
return id < state->workspace_count;
|
||||
|
||||
/* 线性扫描作为备用 */
|
||||
#if 0
|
||||
if (id >= state->workspace_count) return false;
|
||||
|
||||
for (size_t i = 0; i < state->workspace_count; i++) {
|
||||
if (state->workspaces[i].id == id) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
const wm_output_t *wm_state_output_get(const wm_state_t *state,
|
||||
wm_output_id_t id) {
|
||||
if (id < state->output_count) return &state->outputs[id];
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const wm_output_t *wm_state_output_at(const wm_state_t *state, size_t index) {
|
||||
if (index < state->output_count) return &state->outputs[index];
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void wm_state_output_set_workarea(wm_state_t *state, wm_output_id_t output_id,
|
||||
wm_rect_t workarea) {
|
||||
wm_output_t *output = (wm_output_t *)wm_state_output_get(state, output_id);
|
||||
if (output) output->workarea = workarea;
|
||||
}
|
||||
|
||||
void wm_state_output_set_current_workspace(wm_state_t *state,
|
||||
wm_output_id_t output_id,
|
||||
wm_workspace_id_t workspace_id) {
|
||||
wm_output_t *output = (wm_output_t *)wm_state_output_get(state, output_id);
|
||||
if (!output) return;
|
||||
const wm_workspace_t *workspace = wm_state_workspace_get(state, workspace_id);
|
||||
if (!workspace || workspace->output_id != output_id) return;
|
||||
|
||||
output->current_workspace_id = workspace_id;
|
||||
}
|
||||
|
||||
size_t wm_state_output_count(const wm_state_t *state) {
|
||||
return state->output_count;
|
||||
}
|
||||
|
||||
bool wm_state_output_valid(const wm_state_t *state, wm_output_id_t id) {
|
||||
return id < state->output_count;
|
||||
|
||||
/* 线性扫描作为备用 */
|
||||
#if 0
|
||||
if (id >= state->output_count) return false;
|
||||
|
||||
for (size_t i = 0; i < state->output_count; i++) {
|
||||
if (state->outputs[i].id == id) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
const wm_window_t *wm_state_window_add(wm_state_t *state,
|
||||
const wm_window_info_t *info) {
|
||||
wm_window_id_t id = info->id;
|
||||
wm_window_t *window = (wm_window_t *)wm_state_window_get(state, id);
|
||||
if (!window) {
|
||||
if (state->window_count == state->window_capacity) {
|
||||
size_t capacity = next_capacity(state->window_capacity);
|
||||
p_realloc(&state->windows, capacity);
|
||||
p_realloc(&state->stack_order, capacity);
|
||||
state->window_capacity = capacity;
|
||||
}
|
||||
window = &state->windows[state->window_count];
|
||||
p_clear(window, 1);
|
||||
window->id = id;
|
||||
window->workspace_id = WM_WORKSPACE_ID_INVALID;
|
||||
|
||||
state->stack_order[state->window_count] = id;
|
||||
state->window_count++;
|
||||
}
|
||||
|
||||
wm_state_window_set_geometry_mode(state, id, info->geometry_mode);
|
||||
wm_state_window_set_urgent(state, id, info->urgent);
|
||||
wm_state_window_set_fixed_size(state, id, info->fixed_size);
|
||||
wm_state_window_set_frame_rect(state, id, info->frame_rect);
|
||||
wm_state_window_set_title(state, id, info->title);
|
||||
wm_state_window_set_app_id(state, id, info->app_id);
|
||||
wm_state_window_set_class(state, id, info->class_name);
|
||||
wm_state_window_set_instance(state, id, info->instance_name);
|
||||
wm_state_window_set_skip_taskbar(state, id, info->skip_taskbar);
|
||||
|
||||
return window;
|
||||
}
|
||||
|
||||
const wm_window_t *wm_state_window_get(const wm_state_t *state,
|
||||
wm_window_id_t id) {
|
||||
for (size_t i = 0; i < state->window_count; i++) {
|
||||
if (state->windows[i].id == id) return &state->windows[i];
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const wm_window_t *wm_state_window_at(const wm_state_t *state, size_t index) {
|
||||
if (index < state->window_count) return &state->windows[index];
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void wm_state_window_remove(wm_state_t *state, wm_window_id_t id) {
|
||||
bool matched = false;
|
||||
size_t index = 0;
|
||||
for (size_t i = 0; i < state->window_count; i++) {
|
||||
if (state->windows[i].id == id) {
|
||||
matched = true;
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!matched) return;
|
||||
|
||||
wm_window_t *window = &state->windows[index];
|
||||
wm_workspace_id_t workspace_id = window->workspace_id;
|
||||
p_delete(&window->title);
|
||||
p_delete(&window->app_id);
|
||||
p_delete(&window->class_name);
|
||||
p_delete(&window->instance_name);
|
||||
|
||||
for (size_t i = index + 1; i < state->window_count; i++) {
|
||||
state->windows[i - 1] = state->windows[i];
|
||||
}
|
||||
window = &state->windows[state->window_count - 1];
|
||||
p_clear(window, 1);
|
||||
window->id = WM_WINDOW_ID_INVALID;
|
||||
window->workspace_id = WM_WORKSPACE_ID_INVALID;
|
||||
|
||||
matched = false;
|
||||
for (size_t i = 0; i < state->window_count; i++) {
|
||||
if (state->stack_order[i] == id) {
|
||||
matched = true;
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (matched) {
|
||||
for (size_t i = index + 1; i < state->window_count; i++) {
|
||||
state->stack_order[i - 1] = state->stack_order[i];
|
||||
}
|
||||
state->stack_order[state->window_count - 1] = WM_WINDOW_ID_INVALID;
|
||||
}
|
||||
|
||||
state->window_count--;
|
||||
|
||||
wm_workspace_t *workspace =
|
||||
(wm_workspace_t *)wm_state_workspace_get(state, workspace_id);
|
||||
if (workspace && workspace->focused_window_id == id) {
|
||||
wm_state_workspace_adjust_focused_window(state, workspace_id);
|
||||
}
|
||||
}
|
||||
|
||||
size_t wm_state_window_count(const wm_state_t *state) {
|
||||
return state->window_count;
|
||||
}
|
||||
|
||||
void wm_state_window_set_workspace(wm_state_t *state, wm_window_id_t window_id,
|
||||
wm_workspace_id_t workspace_id) {
|
||||
const wm_workspace_t *workspace = wm_state_workspace_get(state, workspace_id);
|
||||
wm_window_t *window = (wm_window_t *)wm_state_window_get(state, window_id);
|
||||
if (!workspace || !window || window->workspace_id == workspace_id) return;
|
||||
|
||||
workspace = wm_state_workspace_get(state, window->workspace_id);
|
||||
window->workspace_id = workspace_id;
|
||||
|
||||
if (workspace && workspace->focused_window_id == window_id) {
|
||||
wm_state_workspace_adjust_focused_window(state, workspace->id);
|
||||
}
|
||||
|
||||
wm_state_workspace_adjust_focused_window(state, workspace_id);
|
||||
}
|
||||
|
||||
void wm_state_window_set_geometry_mode(
|
||||
wm_state_t *state, wm_window_id_t window_id,
|
||||
wm_window_geometry_mode_t geometry_mode) {
|
||||
wm_window_t *window = (wm_window_t *)wm_state_window_get(state, window_id);
|
||||
if (!window) return;
|
||||
|
||||
window->geometry_mode = geometry_mode;
|
||||
}
|
||||
|
||||
void wm_state_window_set_floating(wm_state_t *state, wm_window_id_t window_id,
|
||||
bool floating) {
|
||||
wm_window_t *window = (wm_window_t *)wm_state_window_get(state, window_id);
|
||||
if (!window) return;
|
||||
|
||||
window->floating = floating;
|
||||
}
|
||||
|
||||
void wm_state_window_set_sticky(wm_state_t *state, wm_window_id_t window_id,
|
||||
bool sticky) {
|
||||
wm_window_t *window = (wm_window_t *)wm_state_window_get(state, window_id);
|
||||
if (!window) return;
|
||||
|
||||
window->sticky = sticky;
|
||||
if (window->sticky) wm_state_window_set_floating(state, window_id, true);
|
||||
}
|
||||
|
||||
void wm_state_window_set_urgent(wm_state_t *state, wm_window_id_t window_id,
|
||||
bool urgent) {
|
||||
wm_window_t *window = (wm_window_t *)wm_state_window_get(state, window_id);
|
||||
if (!window) return;
|
||||
|
||||
window->urgent = urgent;
|
||||
}
|
||||
|
||||
void wm_state_window_set_fixed_size(wm_state_t *state, wm_window_id_t window_id,
|
||||
bool fixed_size) {
|
||||
wm_window_t *window = (wm_window_t *)wm_state_window_get(state, window_id);
|
||||
if (!window) return;
|
||||
|
||||
window->fixed_size = fixed_size;
|
||||
if (fixed_size) wm_state_window_set_floating(state, window_id, true);
|
||||
}
|
||||
|
||||
void wm_state_window_set_skip_taskbar(wm_state_t *state,
|
||||
wm_window_id_t window_id,
|
||||
bool skip_taskbar) {
|
||||
wm_window_t *window = (wm_window_t *)wm_state_window_get(state, window_id);
|
||||
if (!window) return;
|
||||
|
||||
window->skip_taskbar = skip_taskbar;
|
||||
}
|
||||
|
||||
void wm_state_window_set_float_rect(wm_state_t *state, wm_window_id_t window_id,
|
||||
wm_rect_t float_rect) {
|
||||
wm_window_t *window = (wm_window_t *)wm_state_window_get(state, window_id);
|
||||
if (!window) return;
|
||||
|
||||
window->float_rect = float_rect;
|
||||
}
|
||||
|
||||
void wm_state_window_set_frame_rect(wm_state_t *state, wm_window_id_t window_id,
|
||||
wm_rect_t frame_rect) {
|
||||
wm_window_t *window = (wm_window_t *)wm_state_window_get(state, window_id);
|
||||
if (!window) return;
|
||||
|
||||
window->frame_rect = frame_rect;
|
||||
}
|
||||
|
||||
bool wm_state_window_set_title(wm_state_t *state, wm_window_id_t window_id,
|
||||
const char *title) {
|
||||
wm_window_t *window = (wm_window_t *)wm_state_window_get(state, window_id);
|
||||
if (!window) return false;
|
||||
|
||||
p_delete(&window->title);
|
||||
if (title) window->title = p_strdup(title);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool wm_state_window_set_app_id(wm_state_t *state, wm_window_id_t window_id,
|
||||
const char *app_id) {
|
||||
wm_window_t *window = (wm_window_t *)wm_state_window_get(state, window_id);
|
||||
if (!window) return false;
|
||||
|
||||
p_delete(&window->app_id);
|
||||
if (app_id) window->app_id = p_strdup(app_id);
|
||||
|
||||
return true;
|
||||
}
|
||||
bool wm_state_window_set_class(wm_state_t *state, wm_window_id_t window_id,
|
||||
const char *class_name) {
|
||||
wm_window_t *window = (wm_window_t *)wm_state_window_get(state, window_id);
|
||||
if (!window) return false;
|
||||
|
||||
p_delete(&window->class_name);
|
||||
if (class_name) window->class_name = p_strdup(class_name);
|
||||
|
||||
return true;
|
||||
}
|
||||
bool wm_state_window_set_instance(wm_state_t *state, wm_window_id_t window_id,
|
||||
const char *instance_name) {
|
||||
wm_window_t *window = (wm_window_t *)wm_state_window_get(state, window_id);
|
||||
if (!window) return false;
|
||||
|
||||
p_delete(&window->instance_name);
|
||||
if (instance_name) window->instance_name = p_strdup(instance_name);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const wm_window_id_t *wm_state_stack_order(const wm_state_t *state) {
|
||||
return state->stack_order;
|
||||
}
|
||||
|
||||
wm_window_id_t wm_state_stack_at(const wm_state_t *state, size_t index) {
|
||||
if (index >= state->window_count) return WM_WINDOW_ID_INVALID;
|
||||
return state->stack_order[index];
|
||||
}
|
||||
|
||||
bool wm_state_stack_raise(wm_state_t *state, wm_window_id_t window_id) {
|
||||
bool matched = false;
|
||||
size_t index = 0;
|
||||
for (size_t i = 0; i < state->window_count; i++) {
|
||||
if (state->stack_order[i] == window_id) {
|
||||
matched = true;
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!matched) return false;
|
||||
|
||||
for (size_t i = index + 1; i < state->window_count; i++) {
|
||||
state->stack_order[i - 1] = state->stack_order[i];
|
||||
}
|
||||
state->stack_order[state->window_count - 1] = window_id;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool wm_state_stack_lower(wm_state_t *state, wm_window_id_t window_id) {
|
||||
bool matched = false;
|
||||
size_t index = 0;
|
||||
for (size_t i = 0; i < state->window_count; i++) {
|
||||
if (state->stack_order[i] == window_id) {
|
||||
matched = true;
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!matched) return false;
|
||||
|
||||
for (size_t i = index; i > 0; i--) {
|
||||
state->stack_order[i] = state->stack_order[i - 1];
|
||||
}
|
||||
state->stack_order[0] = window_id;
|
||||
|
||||
return true;
|
||||
}
|
||||
195
src/core/state.h
Normal file
195
src/core/state.h
Normal file
@@ -0,0 +1,195 @@
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include "core/types.h"
|
||||
#include "wm_desc.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 解析) */
|
||||
|
||||
/* 元数据(核心算法不依赖,仅用于规则匹配和信息展示) */
|
||||
const char *title;
|
||||
const char *app_id;
|
||||
const char *class_name;
|
||||
const 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;
|
||||
|
||||
/* 当前 workspace 的可用布局列表 */
|
||||
const wm_layout_id_t *available_layouts;
|
||||
size_t layout_count;
|
||||
|
||||
/* 名称(核心算法不依赖) */
|
||||
const char *name;
|
||||
} wm_workspace_t;
|
||||
|
||||
typedef struct wm_output_t {
|
||||
wm_output_id_t id;
|
||||
wm_workspace_id_t current_workspace_id;
|
||||
const char *name;
|
||||
wm_rect_t geometry; /* 输出完整几何 */
|
||||
wm_rect_t workarea; /* 可用区域(排除面板等) */
|
||||
} wm_output_t;
|
||||
|
||||
/* 全局状态容器 */
|
||||
typedef struct wm_state_t {
|
||||
/* 按 id 稳定引用的 workspace / output 集合 */
|
||||
wm_workspace_t *workspaces;
|
||||
size_t workspace_count;
|
||||
|
||||
wm_output_t *outputs;
|
||||
size_t output_count;
|
||||
|
||||
wm_window_t *windows;
|
||||
size_t window_count;
|
||||
size_t window_capacity;
|
||||
|
||||
/* 与 windows[] 一一对应的堆叠顺序视图,长度等于 window_count */
|
||||
wm_window_id_t *stack_order;
|
||||
|
||||
} wm_state_t;
|
||||
|
||||
/*
|
||||
* 调用约束:
|
||||
* - state 必须是有效的非空指针
|
||||
* - wm_state_init() / wm_state_cleanup() 必须成对调用
|
||||
* - 同一个 state 生命周期内,wm_state_init() 不允许重复调用
|
||||
* - 除 wm_state_init() 外,其余 state 相关接口都要求 state 已初始化
|
||||
* - workspaces[i] 必须满足 wm_workspace_desc_valid(&workspaces[i], output_count)
|
||||
* - 传入空指针、未初始化对象或违反前置条件属于调用方错误
|
||||
*
|
||||
* 初始化时根据描述表构建 outputs[] 与 workspaces[]。
|
||||
* workspace_id 由 state 按描述表顺序分配并保持稳定,不从 desc 中读取。
|
||||
* 每个 output 的 current_workspace_id 会自动设置为首个归属到该 output 的
|
||||
* workspace;若某个 output 没有任何归属 workspace,wm_state_init() 会失败。
|
||||
*/
|
||||
void wm_state_init(wm_state_t *state, const wm_output_info_t *outputs,
|
||||
size_t output_count,
|
||||
const wm_workspace_desc_t *workspaces,
|
||||
size_t workspace_count);
|
||||
void wm_state_cleanup(wm_state_t *state);
|
||||
|
||||
/*
|
||||
* state 持有的 workspace 集合接口
|
||||
*
|
||||
* 这组接口对外只提供只读访问。
|
||||
* workspace 的运行期可变状态必须通过专门的 state 级更新接口修改。
|
||||
*/
|
||||
const wm_workspace_t *wm_state_workspace_get(const wm_state_t *state,
|
||||
wm_workspace_id_t id);
|
||||
const wm_workspace_t *wm_state_workspace_at(const wm_state_t *state,
|
||||
size_t index);
|
||||
bool wm_state_workspace_cycle_layout(wm_state_t *state,
|
||||
wm_workspace_id_t workspace_id);
|
||||
bool wm_state_workspace_set_layout_by_index(wm_state_t *state,
|
||||
wm_workspace_id_t workspace_id,
|
||||
size_t index);
|
||||
bool wm_state_workspace_set_layout_by_id(wm_state_t *state,
|
||||
wm_workspace_id_t workspace_id,
|
||||
wm_layout_id_t layout_id);
|
||||
void wm_state_workspace_set_focused_window(wm_state_t *state,
|
||||
wm_workspace_id_t workspace_id,
|
||||
wm_window_id_t window_id);
|
||||
size_t wm_state_workspace_count(const wm_state_t *state);
|
||||
bool wm_state_workspace_valid(const wm_state_t *state, wm_workspace_id_t id);
|
||||
|
||||
/*
|
||||
* state 持有的 output 集合接口
|
||||
*
|
||||
* 这组接口对外只提供只读访问。
|
||||
* output 的运行期可变状态必须通过专门的 state 级更新接口修改。
|
||||
*/
|
||||
const wm_output_t *wm_state_output_get(const wm_state_t *state,
|
||||
wm_output_id_t id);
|
||||
const wm_output_t *wm_state_output_at(const wm_state_t *state, size_t index);
|
||||
void wm_state_output_set_workarea(wm_state_t *state, wm_output_id_t output_id,
|
||||
wm_rect_t workarea);
|
||||
void wm_state_output_set_current_workspace(wm_state_t *state,
|
||||
wm_output_id_t output_id,
|
||||
wm_workspace_id_t workspace_id);
|
||||
size_t wm_state_output_count(const wm_state_t *state);
|
||||
bool wm_state_output_valid(const wm_state_t *state, wm_output_id_t id);
|
||||
|
||||
/*
|
||||
* state 持有的 window 集合接口
|
||||
*
|
||||
* 这组接口负责管理 wm_state_t.windows[] 容器本身,并保持
|
||||
* stack_order[] 与 windows[] 同步。
|
||||
* 对外只提供只读访问。
|
||||
* window 的运行期可变状态必须通过专门的 state 级更新接口修改。
|
||||
*/
|
||||
/*
|
||||
* 添加窗口,并将其追加到 stack_order[] 顶部。
|
||||
*
|
||||
* info 提供 backend 已探测到的窗口基础信息。
|
||||
* workspace 归属与其他策略相关字段由后续 wm_state_window_* 接口设置。
|
||||
*/
|
||||
const wm_window_t *wm_state_window_add(wm_state_t *state,
|
||||
const wm_window_info_t *info);
|
||||
const wm_window_t *wm_state_window_get(const wm_state_t *state,
|
||||
wm_window_id_t id);
|
||||
const wm_window_t *wm_state_window_at(const wm_state_t *state, size_t index);
|
||||
/* 删除窗口,并同步从 stack_order[] 中移除。 */
|
||||
void wm_state_window_remove(wm_state_t *state, wm_window_id_t id);
|
||||
size_t wm_state_window_count(const wm_state_t *state);
|
||||
|
||||
/* state 持有的单个 window 状态更新接口 */
|
||||
void wm_state_window_set_workspace(wm_state_t *state, wm_window_id_t window_id,
|
||||
wm_workspace_id_t workspace_id);
|
||||
void wm_state_window_set_geometry_mode(wm_state_t *state,
|
||||
wm_window_id_t window_id,
|
||||
wm_window_geometry_mode_t geometry_mode);
|
||||
void wm_state_window_set_floating(wm_state_t *state, wm_window_id_t window_id,
|
||||
bool floating);
|
||||
void wm_state_window_set_sticky(wm_state_t *state, wm_window_id_t window_id,
|
||||
bool sticky);
|
||||
void wm_state_window_set_urgent(wm_state_t *state, wm_window_id_t window_id,
|
||||
bool urgent);
|
||||
void wm_state_window_set_fixed_size(wm_state_t *state, wm_window_id_t window_id,
|
||||
bool fixed_size);
|
||||
void wm_state_window_set_skip_taskbar(wm_state_t *state,
|
||||
wm_window_id_t window_id,
|
||||
bool skip_taskbar);
|
||||
void wm_state_window_set_float_rect(wm_state_t *state, wm_window_id_t window_id,
|
||||
wm_rect_t float_rect);
|
||||
void wm_state_window_set_frame_rect(wm_state_t *state, wm_window_id_t window_id,
|
||||
wm_rect_t frame_rect);
|
||||
bool wm_state_window_set_title(wm_state_t *state, wm_window_id_t window_id,
|
||||
const char *title);
|
||||
bool wm_state_window_set_app_id(wm_state_t *state, wm_window_id_t window_id,
|
||||
const char *app_id);
|
||||
bool wm_state_window_set_class(wm_state_t *state, wm_window_id_t window_id,
|
||||
const char *class_name);
|
||||
bool wm_state_window_set_instance(wm_state_t *state, wm_window_id_t window_id,
|
||||
const char *instance_name);
|
||||
|
||||
/*
|
||||
* state 持有的堆叠顺序接口
|
||||
*
|
||||
* stack_order[] 是全局 z-order 的唯一真相,从下到上排列。
|
||||
* 其长度恒等于 wm_state_window_count(state),因此不提供单独的 count 接口。
|
||||
*/
|
||||
const wm_window_id_t *wm_state_stack_order(const wm_state_t *state);
|
||||
wm_window_id_t wm_state_stack_at(const wm_state_t *state, size_t index);
|
||||
bool wm_state_stack_raise(wm_state_t *state, wm_window_id_t window_id);
|
||||
bool wm_state_stack_lower(wm_state_t *state, wm_window_id_t window_id);
|
||||
45
src/core/types.h
Normal file
45
src/core/types.h
Normal file
@@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
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;
|
||||
23
src/utils.h
23
src/utils.h
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
@@ -12,6 +13,7 @@
|
||||
#define p_new(type, count) ((type *)xmalloc(sizeof(type) * (count)))
|
||||
#define p_clear(p, count) ((void)memset((p), 0, sizeof(*(p)) * (count)))
|
||||
#define p_realloc(pp, count) xrealloc((void *)(pp), sizeof(**(pp)) * (count))
|
||||
#define p_copy(src, count) xmemcopy((src), sizeof(*(src)) * (count))
|
||||
|
||||
#define p_delete(mem_p) \
|
||||
do { \
|
||||
@@ -28,6 +30,12 @@
|
||||
#define unlikely(expr) expr
|
||||
#endif
|
||||
|
||||
static inline char *p_strdup(const char *text) {
|
||||
char *r = strdup(text);
|
||||
if (!r) abort();
|
||||
return r;
|
||||
}
|
||||
|
||||
static inline void *__attribute__((malloc)) xmalloc(ssize_t size) {
|
||||
void *ptr;
|
||||
|
||||
@@ -40,6 +48,15 @@ static inline void *__attribute__((malloc)) xmalloc(ssize_t size) {
|
||||
return ptr;
|
||||
}
|
||||
|
||||
static inline void *xmemcopy(const void *src, size_t n) {
|
||||
if (!n) return nullptr;
|
||||
|
||||
void *mem = xmalloc(n);
|
||||
void *ret = memcpy(mem, src, n);
|
||||
if (ret != mem) abort();
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline void xrealloc(void **ptr, ssize_t newsize) {
|
||||
if (newsize <= 0)
|
||||
p_delete(ptr);
|
||||
@@ -101,3 +118,9 @@ static inline void logger(const char *format, ...) {
|
||||
#ifndef MAX
|
||||
#define MAX(a, b) (((a) > (b)) ? (a) : (b))
|
||||
#endif
|
||||
|
||||
static constexpr size_t INIT_CAPACITY = 4;
|
||||
static inline size_t next_capacity(size_t capacity) {
|
||||
if (capacity) return capacity * 2;
|
||||
return INIT_CAPACITY;
|
||||
}
|
||||
|
||||
64
src/wm_desc.h
Normal file
64
src/wm_desc.h
Normal file
@@ -0,0 +1,64 @@
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include "core/types.h"
|
||||
|
||||
typedef struct wm_output_info_t {
|
||||
const char *name;
|
||||
wm_rect_t geometry;
|
||||
} wm_output_info_t;
|
||||
|
||||
/*
|
||||
* 由 backend 提供的窗口基础信息。
|
||||
*
|
||||
* 这些字符串由调用方提供;state 如需长期持有,应自行复制。
|
||||
*/
|
||||
typedef struct wm_window_info_t {
|
||||
wm_window_id_t id;
|
||||
wm_rect_t frame_rect;
|
||||
|
||||
const char *title;
|
||||
const char *app_id;
|
||||
const char *class_name;
|
||||
const char *instance_name;
|
||||
|
||||
wm_window_geometry_mode_t geometry_mode;
|
||||
bool urgent;
|
||||
bool fixed_size;
|
||||
bool skip_taskbar;
|
||||
} wm_window_info_t;
|
||||
|
||||
typedef struct wm_workspace_desc_t {
|
||||
size_t output_index; /* 对应 wm_state_init() 中 outputs[] 的索引 */
|
||||
const char *name;
|
||||
const wm_layout_id_t *layout_ids;
|
||||
size_t layout_count;
|
||||
wm_layout_id_t initial_layout_id;
|
||||
} wm_workspace_desc_t;
|
||||
|
||||
/*
|
||||
* workspace 描述校验接口。
|
||||
*
|
||||
* 这组接口只校验描述表自身的一致性,不访问 state。
|
||||
*/
|
||||
static inline bool
|
||||
wm_workspace_desc_layouts_valid(const wm_workspace_desc_t *workspace) {
|
||||
if (!workspace || !workspace->layout_count || !workspace->layout_ids) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < workspace->layout_count; i++) {
|
||||
if (workspace->layout_ids[i] == workspace->initial_layout_id) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline bool wm_workspace_desc_valid(const wm_workspace_desc_t *workspace,
|
||||
size_t output_count) {
|
||||
if (!workspace || !workspace->name) return false;
|
||||
if (workspace->output_index >= output_count) return false;
|
||||
|
||||
return wm_workspace_desc_layouts_valid(workspace);
|
||||
}
|
||||
Reference in New Issue
Block a user