Compare commits
2 Commits
988f1ca17a
...
f106857cd8
| Author | SHA1 | Date | |
|---|---|---|---|
|
f106857cd8
|
|||
|
cdcafa1216
|
@@ -48,6 +48,7 @@ add_executable(${APP_NAME}
|
||||
${SOURCE_DIR}/config.c
|
||||
${SOURCE_DIR}/renderer.c
|
||||
${SOURCE_DIR}/image.c
|
||||
${SOURCE_DIR}/tray.c
|
||||
)
|
||||
|
||||
find_package(PkgConfig REQUIRED)
|
||||
|
||||
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
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**: 增强功能,可后续添加
|
||||
171
docs/min_core_draft/README.org
Normal file
171
docs/min_core_draft/README.org
Normal file
@@ -0,0 +1,171 @@
|
||||
* ZDWM 最小核心草案
|
||||
|
||||
这个目录放的是一套头文件级别的最小核心 API 草案,目标是先把窗口管理器最核心的对象关系、子系统边界和主执行流程固定下来,再决定具体实现。
|
||||
|
||||
* 目标
|
||||
|
||||
1. 保持核心技术栈简洁,不引入脚本运行时、序列化协议和多进程插件宿主。
|
||||
2. 先把最小核心收敛到可以管理窗口、计算布局、处理输入、提交后端副作用。
|
||||
3. 将渲染、状态栏、配置、插件都放在核心之外,作为后续服务挂接。
|
||||
|
||||
* 最小核心子系统
|
||||
|
||||
1. =runtime=
|
||||
负责主循环、模块装配和固定执行顺序。
|
||||
|
||||
2. =backend=
|
||||
负责平台交互。它把平台原始事件转成 =wm_event_t= ,并执行 =wm_effect_t= 。
|
||||
|
||||
3. =state=
|
||||
负责保存窗口、工作区、输出和全局堆叠顺序,是唯一真状态。
|
||||
|
||||
4. =policy=
|
||||
负责把事件路由为命令,并把命令应用到状态。
|
||||
|
||||
5. =layout=
|
||||
负责为某个输出上当前显示的工作区中的平铺窗口计算几何。
|
||||
|
||||
6. =window metadata=
|
||||
负责窗口标题、类名、实例名、app id 这类展示和匹配信息。
|
||||
|
||||
7. =workspace descriptor=
|
||||
负责 workspace 在 bar 中显示时使用的名字和文本或图标符号。
|
||||
|
||||
* 非核心但常见的服务
|
||||
|
||||
这些服务故意不放进最小核心:
|
||||
|
||||
1. =render=
|
||||
状态栏、边框、标题、装饰绘制。
|
||||
|
||||
2. =status=
|
||||
网速、内存、CPU、音量、时间等信息采集与聚合。
|
||||
|
||||
3. =config=
|
||||
默认配置和用户覆盖。
|
||||
|
||||
4. =plugin=
|
||||
动态扩展加载。
|
||||
|
||||
* 对象关系
|
||||
|
||||
这套草案里的关系固定为:
|
||||
|
||||
1. =window -> workspace=
|
||||
每个窗口只属于一个工作区。
|
||||
|
||||
2. =output -> current_workspace=
|
||||
每个输出同一时刻只显示一个当前工作区。
|
||||
|
||||
3. =window visible on output=
|
||||
这是派生结果,不是独立存储的真相。
|
||||
|
||||
4. =stack_order[]=
|
||||
这是全局 z-order 的唯一真相,从下到上排列。
|
||||
|
||||
5. =window metadata=
|
||||
窗口标题等字符串信息不放进最小控制状态,而是作为独立元数据表存在。
|
||||
|
||||
6. =workspace descriptor=
|
||||
workspace 在 bar 中显示时使用的名字和符号不放进运行时控制状态,而是作为独立描述表存在。
|
||||
|
||||
* 核心不变量
|
||||
|
||||
1. 每个窗口必须且只能属于一个工作区。
|
||||
2. 每个输出必须且只能显示一个当前工作区。
|
||||
3. 可见性由窗口归属、输出当前工作区和最小化状态共同推导。
|
||||
4. =geometry_mode= 只负责几何模式,不负责 z-order。
|
||||
5. =stack_order[]= 只负责 z-order,不负责几何模式。
|
||||
6. =floating= 是二值状态:窗口要么由 layout 决定基础几何,要么由 =float_rect= 决定基础几何。
|
||||
7. =sticky= 只影响可见性,不改变窗口归属工作区。
|
||||
8. floating 窗口可以跨多个输出显示,但归属工作区仍然只有一个。
|
||||
9. 多输出下 floating 窗口是否迁移工作区,由锚点和跨输出策略决定,不由覆盖面积决定。
|
||||
10. layout 的名字和 bar 符号属于布局注册表描述信息,不属于工作区运行状态。
|
||||
11. 窗口标题和类名属于窗口元数据,不属于窗口控制状态。
|
||||
12. workspace 的显示名称和 bar 符号属于 workspace 描述信息,不属于工作区运行状态。
|
||||
|
||||
* Generation 字段
|
||||
|
||||
=generation= 是版本号,不是业务关系字段。
|
||||
|
||||
当前草案只建议保留:
|
||||
|
||||
1. =wm_state_t.generation=
|
||||
每次成功提交状态变更后递增,用于缓存失效、增量重算和调试。
|
||||
|
||||
以后如果有必要,再给渲染缓存或状态服务增加更细粒度的版本号。
|
||||
|
||||
* 主执行流程
|
||||
|
||||
=runtime= 负责固定主循环顺序:
|
||||
|
||||
1. =backend.next_event()=
|
||||
2. =policy.route_event()=
|
||||
3. =policy.apply_command()=
|
||||
4. 根据 =dirty_flags= 决定是否重新运行 =layout=
|
||||
5. 逐条执行 =wm_effect_t=
|
||||
6. 可选的渲染刷新
|
||||
|
||||
* 文件说明
|
||||
|
||||
1. =wm_types.h=
|
||||
通用标量类型、矩形、窗口几何模式和策略枚举。
|
||||
|
||||
2. =wm_state.h=
|
||||
窗口、工作区、输出和全局状态结构,以及查询接口。
|
||||
|
||||
3. =wm_event.h=
|
||||
统一后的运行时事件。
|
||||
|
||||
4. =wm_command.h=
|
||||
强类型语义命令。
|
||||
命令语义补充见 [[file:WM_COMMAND_RULES.org][WM_COMMAND_RULES.org]] 。
|
||||
|
||||
5. =wm_plan.h=
|
||||
脏标记和后端副作用。
|
||||
|
||||
6. =wm_layout.h=
|
||||
布局注册表、布局输入和布局输出。
|
||||
|
||||
7. =wm_window_meta.h=
|
||||
窗口标题、类名、实例名和 app id 的元数据表。
|
||||
|
||||
8. =wm_workspace_desc.h=
|
||||
workspace 的显示名称、文本符号和图标符号描述表。
|
||||
|
||||
9. =wm_backend.h=
|
||||
后端子系统接口。
|
||||
|
||||
10. =wm_policy.h=
|
||||
事件路由和命令应用接口。
|
||||
|
||||
11. =wm_runtime.h=
|
||||
运行时上下文和生命周期接口。
|
||||
|
||||
12. [[file:WM_COMMAND_RULES.org][WM_COMMAND_RULES.org]]
|
||||
关键命令的前置条件、状态转移、可见性和副作用规则。
|
||||
|
||||
13. [[file:WM_POLICY_APPLY_COMMAND_SKELETON.org][WM_POLICY_APPLY_COMMAND_SKELETON.org]]
|
||||
=wm_policy_apply_command()= 的伪代码骨架和实现顺序建议。
|
||||
|
||||
* 下一步实施顺序
|
||||
|
||||
当前阶段不建议继续扩展抽象,应该开始把最小核心草案落成可运行骨架。推荐顺序如下:
|
||||
|
||||
1. 在 =src/core/= 下建立最小骨架,只放 =wm_types/state/plan/layout/policy/runtime= 的头文件和空实现,先不要替换现有 =src/= 逻辑。
|
||||
|
||||
2. 优先实现 =state + query + plan= 这一层,把动态数组管理、=find_*= 查询函数和 =stack_order[]= 操作补齐。
|
||||
|
||||
3. 再实现 =wm_policy_apply_command()= 的一小批核心命令:
|
||||
=MANAGE_WINDOW=
|
||||
=UNMANAGE_WINDOW=
|
||||
=SWITCH_WORKSPACE=
|
||||
=TOGGLE_FLOATING=
|
||||
=TOGGLE_MAXIMIZE=
|
||||
=MOVE_FLOATING_WINDOW=
|
||||
|
||||
4. 给上述命令建立一个无 X11 依赖的测试入口,直接构造 =wm_command_t= ,检查 =wm_state_t= 和 =wm_plan_t= 的变化是否符合文档规则。
|
||||
|
||||
5. 等 =event -> command -> state -> plan/effect= 这条链路跑通后,再编写一层很薄的 adapter,把现有事件翻译为 =wm_event_t= ,并把 effect 翻译回当前 X11 操作。
|
||||
|
||||
6. =status/render/bar= 暂时不要先动。它们属于核心外服务,应在最小核心链路稳定后再迁移。
|
||||
342
docs/min_core_draft/WM_COMMAND_RULES.org
Normal file
342
docs/min_core_draft/WM_COMMAND_RULES.org
Normal file
@@ -0,0 +1,342 @@
|
||||
* ZDWM 最小核心命令规则
|
||||
|
||||
这份文档定义 =wm_policy_apply_command()= 应遵守的最小语义。目标不是覆盖所有未来扩展,而是先把最关键的状态转移规则固定下来,避免后续实现时在 =floating=、=sticky=、=geometry_mode= 和 =stack_order[]= 上出现分叉语义。
|
||||
|
||||
* 通用规则
|
||||
|
||||
1. 找不到目标 =window_id=、=workspace_id= 或 =output_id= 时,命令失败,不修改状态。
|
||||
2. 命令失败时,不递增 =state.generation= 。
|
||||
3. 命令成功且造成状态变化时,递增 =state.generation= ,并设置相应 =dirty_flags= 。
|
||||
4. =stack_order[]= 是全局 z-order 真相,=raise/lower/focus= 只能通过修改这个数组影响堆叠。
|
||||
5. =geometry_mode= 只表示几何显示模式,不表示窗口是否在最上层。
|
||||
6. =sticky= 只影响可见性,不改变 =workspace_id= 。
|
||||
7. =floating= 决定基础几何来源:
|
||||
=floating = false= 时,基础几何来自 layout 结果。
|
||||
=floating = true= 时,基础几何来自 =float_rect= 。
|
||||
|
||||
* 管理类命令
|
||||
|
||||
** =WM_COMMAND_MANAGE_WINDOW=
|
||||
|
||||
前置条件:
|
||||
|
||||
1. 目标窗口尚未出现在 =state.windows[]= 中。
|
||||
2. 目标工作区存在。
|
||||
|
||||
状态变化:
|
||||
|
||||
1. 向 =state.windows[]= 添加一个新窗口。
|
||||
2. 设定其 =workspace_id= 。
|
||||
3. 初始 =floating= 、=sticky= 、=urgent= 、=geometry_mode= 使用调用方或配置给出的默认值。
|
||||
4. 将窗口追加到 =stack_order[]= 顶部。
|
||||
5. 可选地将该窗口设为所属工作区的 =focused_window_id= 。
|
||||
|
||||
副作用:
|
||||
|
||||
1. =WM_DIRTY_STATE=
|
||||
2. =WM_DIRTY_LAYOUT=
|
||||
3. =WM_DIRTY_STACK=
|
||||
4. =WM_DIRTY_RENDER=
|
||||
|
||||
** =WM_COMMAND_UNMANAGE_WINDOW=
|
||||
|
||||
前置条件:
|
||||
|
||||
1. 目标窗口存在。
|
||||
|
||||
状态变化:
|
||||
|
||||
1. 从 =state.windows[]= 中删除该窗口。
|
||||
2. 从 =stack_order[]= 中移除该窗口。
|
||||
3. 如果它是某个 workspace 的 =focused_window_id= ,清空或重新选择新的 focused window。
|
||||
|
||||
副作用:
|
||||
|
||||
1. =WM_DIRTY_STATE=
|
||||
2. =WM_DIRTY_LAYOUT=
|
||||
3. =WM_DIRTY_STACK=
|
||||
4. =WM_DIRTY_RENDER=
|
||||
|
||||
* 焦点和堆叠命令
|
||||
|
||||
** =WM_COMMAND_FOCUS_WINDOW=
|
||||
|
||||
状态变化:
|
||||
|
||||
1. 将目标窗口所属 workspace 的 =focused_window_id= 设置为该窗口。
|
||||
2. 不强制改变 =stack_order[]= 。
|
||||
|
||||
可选策略:
|
||||
|
||||
1. 若 WM 采用 =focus raises= ,则在实现中额外执行 =raise_window= 逻辑。
|
||||
|
||||
** =WM_COMMAND_FOCUS_DIRECTION=
|
||||
|
||||
状态变化:
|
||||
|
||||
1. 只在目标 =output_id= 当前显示的 workspace 内选择焦点。
|
||||
2. 候选窗口应排除 =geometry_mode == WM_GEOMETRY_MINIMIZED= 的窗口。
|
||||
3. =sticky= 窗口是否参与方向焦点切换,建议作为单独策略开关,不在最小核心中写死。
|
||||
|
||||
** =WM_COMMAND_RAISE_WINDOW=
|
||||
|
||||
状态变化:
|
||||
|
||||
1. 在 =stack_order[]= 中找到目标窗口。
|
||||
2. 将它移动到数组末尾,也就是最顶层。
|
||||
|
||||
副作用:
|
||||
|
||||
1. =WM_DIRTY_STACK=
|
||||
2. =WM_DIRTY_RENDER=
|
||||
|
||||
** =WM_COMMAND_LOWER_WINDOW=
|
||||
|
||||
状态变化:
|
||||
|
||||
1. 在 =stack_order[]= 中找到目标窗口。
|
||||
2. 将它移动到数组开头,也就是最底层。
|
||||
|
||||
副作用:
|
||||
|
||||
1. =WM_DIRTY_STACK=
|
||||
2. =WM_DIRTY_RENDER=
|
||||
|
||||
* 工作区命令
|
||||
|
||||
** =WM_COMMAND_SWITCH_WORKSPACE=
|
||||
|
||||
状态变化:
|
||||
|
||||
1. 将目标输出的 =current_workspace_id= 改为指定工作区。
|
||||
2. 不修改任何窗口的 =workspace_id= 。
|
||||
3. 不修改 =stack_order[]= 。
|
||||
|
||||
副作用:
|
||||
|
||||
1. =WM_DIRTY_OUTPUT=
|
||||
2. =WM_DIRTY_LAYOUT=
|
||||
3. =WM_DIRTY_RENDER=
|
||||
|
||||
** =WM_COMMAND_SEND_WINDOW_TO_WORKSPACE=
|
||||
|
||||
状态变化:
|
||||
|
||||
1. 修改目标窗口的 =workspace_id= 。
|
||||
2. 不改变 =sticky= 。
|
||||
3. 不改变 =floating= 。
|
||||
4. 不改变 =float_rect= 。
|
||||
5. 如果窗口是原 workspace 的 =focused_window_id= ,需要重新选择原 workspace 焦点。
|
||||
|
||||
副作用:
|
||||
|
||||
1. =WM_DIRTY_STATE=
|
||||
2. =WM_DIRTY_LAYOUT=
|
||||
3. =WM_DIRTY_RENDER=
|
||||
|
||||
** =WM_COMMAND_SEND_WINDOW_TO_OUTPUT=
|
||||
|
||||
状态变化:
|
||||
|
||||
1. 这是工作区迁移命令的语法糖。
|
||||
2. 取目标 output 当前的 =current_workspace_id= 。
|
||||
3. 若 =policy == WM_CROSS_OUTPUT_KEEP_WORKSPACE= ,则不改 =workspace_id= ,只允许调用方随后自行调整几何。
|
||||
4. 若 =policy == WM_CROSS_OUTPUT_MOVE_TO_TARGET_WORKSPACE= ,则把窗口 =workspace_id= 改成目标 output 当前工作区。
|
||||
|
||||
* 模式切换命令
|
||||
|
||||
** =WM_COMMAND_TOGGLE_FLOATING=
|
||||
|
||||
状态变化:
|
||||
|
||||
1. 若原来 =floating == false= :
|
||||
将 =floating= 设为 =true= 。
|
||||
将当前 =frame_rect= 复制到 =float_rect= ,作为浮动基准矩形。
|
||||
2. 若原来 =floating == true= :
|
||||
将 =floating= 设为 =false= 。
|
||||
保留 =float_rect= ,供未来再次进入 floating 时恢复。
|
||||
3. 不改变 =geometry_mode= 。
|
||||
4. 若窗口处于 =WM_GEOMETRY_MINIMIZED= ,允许切换 =floating= ,但不会立刻显示出来。
|
||||
|
||||
副作用:
|
||||
|
||||
1. =WM_DIRTY_LAYOUT=
|
||||
2. =WM_DIRTY_RENDER=
|
||||
|
||||
** =WM_COMMAND_TOGGLE_MAXIMIZE=
|
||||
|
||||
状态变化:
|
||||
|
||||
1. 若当前 =geometry_mode == WM_GEOMETRY_MAXIMIZED= :
|
||||
设回 =WM_GEOMETRY_NORMAL= 。
|
||||
2. 若当前 =geometry_mode == WM_GEOMETRY_FULLSCREEN= :
|
||||
直接切换为 =WM_GEOMETRY_MAXIMIZED= 。
|
||||
3. 若当前 =geometry_mode == WM_GEOMETRY_MINIMIZED= :
|
||||
直接切换为 =WM_GEOMETRY_MAXIMIZED= 。
|
||||
4. 其余情况:
|
||||
设为 =WM_GEOMETRY_MAXIMIZED= 。
|
||||
5. 不改变 =floating= 。
|
||||
6. 若窗口原本是 floating,退出 maximize 后回到 =float_rect= 。
|
||||
7. 若窗口原本不是 floating,退出 maximize 后重新参与 layout。
|
||||
|
||||
副作用:
|
||||
|
||||
1. =WM_DIRTY_LAYOUT=
|
||||
2. =WM_DIRTY_RENDER=
|
||||
|
||||
** =WM_COMMAND_TOGGLE_FULLSCREEN=
|
||||
|
||||
状态变化:
|
||||
|
||||
1. 若当前 =geometry_mode == WM_GEOMETRY_FULLSCREEN= :
|
||||
设回 =WM_GEOMETRY_NORMAL= 。
|
||||
2. 否则:
|
||||
设为 =WM_GEOMETRY_FULLSCREEN= 。
|
||||
3. 不改变 =floating= 。
|
||||
4. fullscreen 只决定几何占用 =output.geometry= ,不自动改变 =stack_order[]= 。
|
||||
|
||||
副作用:
|
||||
|
||||
1. =WM_DIRTY_LAYOUT=
|
||||
2. =WM_DIRTY_RENDER=
|
||||
|
||||
** =WM_COMMAND_TOGGLE_MINIMIZE=
|
||||
|
||||
状态变化:
|
||||
|
||||
1. 若当前 =geometry_mode == WM_GEOMETRY_MINIMIZED= :
|
||||
设回 =WM_GEOMETRY_NORMAL= 。
|
||||
2. 否则:
|
||||
设为 =WM_GEOMETRY_MINIMIZED= 。
|
||||
3. 不改变 =floating= 。
|
||||
4. 不改变 =sticky= 。
|
||||
5. minimized 后窗口不再参与显示和焦点候选。
|
||||
|
||||
副作用:
|
||||
|
||||
1. =WM_DIRTY_LAYOUT=
|
||||
2. =WM_DIRTY_RENDER=
|
||||
|
||||
* 浮动几何命令
|
||||
|
||||
** =WM_COMMAND_MOVE_FLOATING_WINDOW=
|
||||
|
||||
前置条件:
|
||||
|
||||
1. 目标窗口存在。
|
||||
2. 目标窗口应为 =floating == true= 。
|
||||
|
||||
状态变化:
|
||||
|
||||
1. 按 =dx/dy= 修改 =float_rect.x/y= 。
|
||||
2. 若 =commit_workspace_change == false= ,只修改几何,不修改 =workspace_id= 。
|
||||
3. 若 =commit_workspace_change == true= :
|
||||
依据 =anchor= 找到目标输出。
|
||||
再按 =policy= 决定是否修改 =workspace_id= 。
|
||||
4. 不改变 =geometry_mode= 。
|
||||
|
||||
副作用:
|
||||
|
||||
1. =WM_DIRTY_LAYOUT=
|
||||
2. =WM_DIRTY_RENDER=
|
||||
|
||||
** =WM_COMMAND_RESIZE_FLOATING_WINDOW=
|
||||
|
||||
前置条件:
|
||||
|
||||
1. 目标窗口存在。
|
||||
2. 目标窗口应为 =floating == true= 。
|
||||
|
||||
状态变化:
|
||||
|
||||
1. 按 =dw/dh= 修改 =float_rect.w/h= 。
|
||||
2. 应对宽高做最小值约束,避免得到零或负值。
|
||||
3. 不改变 =workspace_id= 。
|
||||
4. 不改变 =geometry_mode= 。
|
||||
|
||||
副作用:
|
||||
|
||||
1. =WM_DIRTY_LAYOUT=
|
||||
2. =WM_DIRTY_RENDER=
|
||||
|
||||
* 布局命令
|
||||
|
||||
** =WM_COMMAND_SET_LAYOUT=
|
||||
|
||||
前置条件:
|
||||
|
||||
1. 目标 workspace 存在。
|
||||
2. 目标 =layout_id= 已在 layout registry 中注册。
|
||||
|
||||
状态变化:
|
||||
|
||||
1. 修改 workspace 的 =layout_id= 。
|
||||
2. 不影响 floating 窗口的基础几何。
|
||||
3. 影响所有 =floating == false= 且 =geometry_mode == WM_GEOMETRY_NORMAL= 的窗口。
|
||||
|
||||
副作用:
|
||||
|
||||
1. =WM_DIRTY_LAYOUT=
|
||||
2. =WM_DIRTY_RENDER=
|
||||
|
||||
* redraw 和 quit
|
||||
|
||||
** =WM_COMMAND_REDRAW=
|
||||
|
||||
状态变化:
|
||||
|
||||
1. 不修改 =state= 。
|
||||
|
||||
副作用:
|
||||
|
||||
1. =WM_DIRTY_RENDER=
|
||||
|
||||
** =WM_COMMAND_QUIT=
|
||||
|
||||
状态变化:
|
||||
|
||||
1. 不再接收新事件,交由 =runtime= 退出主循环。
|
||||
|
||||
* 可见性推导建议
|
||||
|
||||
建议实现顺序如下:
|
||||
|
||||
#+BEGIN_SRC c
|
||||
bool wm_state_window_should_be_visible(const wm_state_t *state,
|
||||
const wm_window_t *window) {
|
||||
if (window->geometry_mode == WM_GEOMETRY_MINIMIZED) return false;
|
||||
if (window->sticky) return true;
|
||||
return wm_state_workspace_is_visible(state, window->workspace_id);
|
||||
}
|
||||
#+END_SRC
|
||||
|
||||
然后再按输出判断:
|
||||
|
||||
#+BEGIN_SRC c
|
||||
bool wm_state_window_should_be_visible_on_output(const wm_state_t *state,
|
||||
const wm_window_t *window,
|
||||
const wm_output_t *output) {
|
||||
if (!wm_state_window_should_be_visible(state, window)) return false;
|
||||
if (window->sticky) return true;
|
||||
return window->workspace_id == output->current_workspace_id;
|
||||
}
|
||||
#+END_SRC
|
||||
|
||||
* 几何解析优先级
|
||||
|
||||
窗口最终矩形建议按下面的优先级解析:
|
||||
|
||||
1. =WM_GEOMETRY_MINIMIZED=
|
||||
不显示。
|
||||
|
||||
2. =WM_GEOMETRY_FULLSCREEN=
|
||||
使用 =output.geometry= 。
|
||||
|
||||
3. =WM_GEOMETRY_MAXIMIZED=
|
||||
使用 =output.workarea= 。
|
||||
|
||||
4. =floating == true=
|
||||
使用 =float_rect= 。
|
||||
|
||||
5. 其余情况
|
||||
使用 layout 输出。
|
||||
550
docs/min_core_draft/WM_POLICY_APPLY_COMMAND_SKELETON.org
Normal file
550
docs/min_core_draft/WM_POLICY_APPLY_COMMAND_SKELETON.org
Normal file
@@ -0,0 +1,550 @@
|
||||
* ZDWM =wm_policy_apply_command()= 伪代码骨架
|
||||
|
||||
这份文档给出 =wm_policy_apply_command()= 的最小实现骨架。目标是把实际代码的控制流固定住,使其和 [[file:WM_COMMAND_RULES.org][WM_COMMAND_RULES.org]] 中定义的命令规则一一对应。
|
||||
|
||||
* 函数职责
|
||||
|
||||
=wm_policy_apply_command()= 只做四件事:
|
||||
|
||||
1. 校验命令输入是否合法。
|
||||
2. 修改 =wm_state_t= 中的运行状态。
|
||||
3. 向 =wm_plan_t= 记录脏标记和后端副作用。
|
||||
4. 在状态真的发生变化时递增 =state.generation= 。
|
||||
|
||||
它不负责:
|
||||
|
||||
1. 从平台读取事件。
|
||||
2. 直接调用 X11/Wayland API。
|
||||
3. 直接执行渲染。
|
||||
4. 直接格式化状态栏文本。
|
||||
|
||||
* 顶层控制流
|
||||
|
||||
建议骨架如下:
|
||||
|
||||
#+BEGIN_SRC c
|
||||
bool wm_policy_apply_command(wm_state_t *state,
|
||||
const wm_layout_registry_t *layouts,
|
||||
const wm_command_t *command,
|
||||
wm_plan_t *plan) {
|
||||
bool changed = false;
|
||||
|
||||
if (!state || !command || !plan) return false;
|
||||
|
||||
switch (command->type) {
|
||||
case WM_COMMAND_NONE:
|
||||
return false;
|
||||
|
||||
case WM_COMMAND_MANAGE_WINDOW:
|
||||
changed = apply_manage_window(state, command, plan);
|
||||
break;
|
||||
|
||||
case WM_COMMAND_UNMANAGE_WINDOW:
|
||||
changed = apply_unmanage_window(state, command, plan);
|
||||
break;
|
||||
|
||||
case WM_COMMAND_FOCUS_WINDOW:
|
||||
changed = apply_focus_window(state, command, plan);
|
||||
break;
|
||||
|
||||
case WM_COMMAND_FOCUS_DIRECTION:
|
||||
changed = apply_focus_direction(state, command, plan);
|
||||
break;
|
||||
|
||||
case WM_COMMAND_RAISE_WINDOW:
|
||||
changed = apply_raise_window(state, command, plan);
|
||||
break;
|
||||
|
||||
case WM_COMMAND_LOWER_WINDOW:
|
||||
changed = apply_lower_window(state, command, plan);
|
||||
break;
|
||||
|
||||
case WM_COMMAND_SWITCH_WORKSPACE:
|
||||
changed = apply_switch_workspace(state, command, plan);
|
||||
break;
|
||||
|
||||
case WM_COMMAND_SEND_WINDOW_TO_WORKSPACE:
|
||||
changed = apply_send_window_to_workspace(state, command, plan);
|
||||
break;
|
||||
|
||||
case WM_COMMAND_SEND_WINDOW_TO_OUTPUT:
|
||||
changed = apply_send_window_to_output(state, command, plan);
|
||||
break;
|
||||
|
||||
case WM_COMMAND_TOGGLE_FLOATING:
|
||||
changed = apply_toggle_floating(state, command, plan);
|
||||
break;
|
||||
|
||||
case WM_COMMAND_TOGGLE_MAXIMIZE:
|
||||
changed = apply_toggle_maximize(state, command, plan);
|
||||
break;
|
||||
|
||||
case WM_COMMAND_TOGGLE_FULLSCREEN:
|
||||
changed = apply_toggle_fullscreen(state, command, plan);
|
||||
break;
|
||||
|
||||
case WM_COMMAND_TOGGLE_MINIMIZE:
|
||||
changed = apply_toggle_minimize(state, command, plan);
|
||||
break;
|
||||
|
||||
case WM_COMMAND_MOVE_FLOATING_WINDOW:
|
||||
changed = apply_move_floating_window(state, command, plan);
|
||||
break;
|
||||
|
||||
case WM_COMMAND_RESIZE_FLOATING_WINDOW:
|
||||
changed = apply_resize_floating_window(state, command, plan);
|
||||
break;
|
||||
|
||||
case WM_COMMAND_SET_LAYOUT:
|
||||
changed = apply_set_layout(state, layouts, command, plan);
|
||||
break;
|
||||
|
||||
case WM_COMMAND_REDRAW:
|
||||
changed = apply_redraw(state, command, plan);
|
||||
break;
|
||||
|
||||
case WM_COMMAND_QUIT:
|
||||
changed = apply_quit(state, command, plan);
|
||||
break;
|
||||
}
|
||||
|
||||
if (changed) state->generation++;
|
||||
return changed;
|
||||
}
|
||||
#+END_SRC
|
||||
|
||||
* 辅助函数建议
|
||||
|
||||
建议先把这些低层辅助函数写出来,避免在每个命令分支中重复拼逻辑:
|
||||
|
||||
#+BEGIN_SRC c
|
||||
static wm_window_t *require_window(wm_state_t *state, wm_window_id_t id);
|
||||
static wm_workspace_t *require_workspace(wm_state_t *state, wm_workspace_id_t id);
|
||||
static wm_output_t *require_output(wm_state_t *state, wm_output_id_t id);
|
||||
|
||||
static bool stack_raise(wm_state_t *state, wm_window_id_t id);
|
||||
static bool stack_lower(wm_state_t *state, wm_window_id_t id);
|
||||
static bool stack_remove(wm_state_t *state, wm_window_id_t id);
|
||||
static bool stack_append(wm_state_t *state, wm_window_id_t id);
|
||||
|
||||
static void mark_layout_dirty(wm_plan_t *plan);
|
||||
static void mark_stack_dirty(wm_plan_t *plan);
|
||||
static void mark_render_dirty(wm_plan_t *plan);
|
||||
static void mark_output_dirty(wm_plan_t *plan);
|
||||
|
||||
static void push_render_all_outputs(const wm_state_t *state, wm_plan_t *plan);
|
||||
static void push_restack_effect(const wm_state_t *state, wm_plan_t *plan);
|
||||
static void maybe_refocus_workspace(wm_state_t *state, wm_workspace_t *workspace);
|
||||
#+END_SRC
|
||||
|
||||
* 命令分支骨架
|
||||
|
||||
** =apply_manage_window()=
|
||||
|
||||
#+BEGIN_SRC c
|
||||
static bool apply_manage_window(wm_state_t *state,
|
||||
const wm_command_t *command,
|
||||
wm_plan_t *plan) {
|
||||
wm_window_id_t id = command->as.manage_window.window_id;
|
||||
wm_workspace_id_t ws_id = command->as.manage_window.workspace_id;
|
||||
|
||||
if (wm_state_find_window_const(state, id)) return false;
|
||||
wm_workspace_t *ws = require_workspace(state, ws_id);
|
||||
if (!ws) return false;
|
||||
|
||||
wm_window_t win = {
|
||||
.id = id,
|
||||
.workspace_id = ws_id,
|
||||
.geometry_mode = WM_GEOMETRY_NORMAL,
|
||||
.floating = false,
|
||||
.sticky = false,
|
||||
.urgent = false,
|
||||
};
|
||||
|
||||
if (!state_append_window(state, win)) return false;
|
||||
if (!stack_append(state, id)) return false;
|
||||
|
||||
ws->focused_window_id = id;
|
||||
|
||||
plan->dirty_flags |= WM_DIRTY_STATE | WM_DIRTY_LAYOUT |
|
||||
WM_DIRTY_STACK | WM_DIRTY_RENDER;
|
||||
push_restack_effect(state, plan);
|
||||
push_render_all_outputs(state, plan);
|
||||
return true;
|
||||
}
|
||||
#+END_SRC
|
||||
|
||||
** =apply_unmanage_window()=
|
||||
|
||||
#+BEGIN_SRC c
|
||||
static bool apply_unmanage_window(wm_state_t *state,
|
||||
const wm_command_t *command,
|
||||
wm_plan_t *plan) {
|
||||
wm_window_id_t id = command->as.unmanage_window.window_id;
|
||||
wm_window_t *win = require_window(state, id);
|
||||
if (!win) return false;
|
||||
|
||||
wm_workspace_t *ws = require_workspace(state, win->workspace_id);
|
||||
if (!ws) return false;
|
||||
|
||||
if (!stack_remove(state, id)) return false;
|
||||
if (!state_remove_window(state, id)) return false;
|
||||
|
||||
if (ws->focused_window_id == id) maybe_refocus_workspace(state, ws);
|
||||
|
||||
plan->dirty_flags |= WM_DIRTY_STATE | WM_DIRTY_LAYOUT |
|
||||
WM_DIRTY_STACK | WM_DIRTY_RENDER;
|
||||
push_restack_effect(state, plan);
|
||||
push_render_all_outputs(state, plan);
|
||||
return true;
|
||||
}
|
||||
#+END_SRC
|
||||
|
||||
** =apply_focus_window()=
|
||||
|
||||
#+BEGIN_SRC c
|
||||
static bool apply_focus_window(wm_state_t *state,
|
||||
const wm_command_t *command,
|
||||
wm_plan_t *plan) {
|
||||
wm_window_id_t id = command->as.focus_window.window_id;
|
||||
wm_window_t *win = require_window(state, id);
|
||||
if (!win) return false;
|
||||
|
||||
wm_workspace_t *ws = require_workspace(state, win->workspace_id);
|
||||
if (!ws) return false;
|
||||
if (ws->focused_window_id == id) return false;
|
||||
|
||||
ws->focused_window_id = id;
|
||||
|
||||
plan->dirty_flags |= WM_DIRTY_RENDER;
|
||||
return true;
|
||||
}
|
||||
#+END_SRC
|
||||
|
||||
** =apply_focus_direction()=
|
||||
|
||||
#+BEGIN_SRC c
|
||||
static bool apply_focus_direction(wm_state_t *state,
|
||||
const wm_command_t *command,
|
||||
wm_plan_t *plan) {
|
||||
wm_output_id_t output_id = command->as.focus_direction.output_id;
|
||||
wm_output_t *out = require_output(state, output_id);
|
||||
if (!out) return false;
|
||||
|
||||
wm_workspace_t *ws = require_workspace(state, out->current_workspace_id);
|
||||
if (!ws) return false;
|
||||
|
||||
wm_window_id_t next =
|
||||
find_focus_candidate_in_workspace(state, ws,
|
||||
command->as.focus_direction.direction);
|
||||
if (!next || ws->focused_window_id == next) return false;
|
||||
|
||||
ws->focused_window_id = next;
|
||||
plan->dirty_flags |= WM_DIRTY_RENDER;
|
||||
return true;
|
||||
}
|
||||
#+END_SRC
|
||||
|
||||
** =apply_raise_window()= 和 =apply_lower_window()=
|
||||
|
||||
#+BEGIN_SRC c
|
||||
static bool apply_raise_window(wm_state_t *state,
|
||||
const wm_command_t *command,
|
||||
wm_plan_t *plan) {
|
||||
if (!stack_raise(state, command->as.raise_window.window_id)) return false;
|
||||
plan->dirty_flags |= WM_DIRTY_STACK | WM_DIRTY_RENDER;
|
||||
push_restack_effect(state, plan);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool apply_lower_window(wm_state_t *state,
|
||||
const wm_command_t *command,
|
||||
wm_plan_t *plan) {
|
||||
if (!stack_lower(state, command->as.lower_window.window_id)) return false;
|
||||
plan->dirty_flags |= WM_DIRTY_STACK | WM_DIRTY_RENDER;
|
||||
push_restack_effect(state, plan);
|
||||
return true;
|
||||
}
|
||||
#+END_SRC
|
||||
|
||||
** =apply_switch_workspace()=
|
||||
|
||||
#+BEGIN_SRC c
|
||||
static bool apply_switch_workspace(wm_state_t *state,
|
||||
const wm_command_t *command,
|
||||
wm_plan_t *plan) {
|
||||
wm_output_t *out =
|
||||
require_output(state, command->as.switch_workspace.output_id);
|
||||
if (!out) return false;
|
||||
if (!require_workspace(state, command->as.switch_workspace.workspace_id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (out->current_workspace_id == command->as.switch_workspace.workspace_id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
out->current_workspace_id = command->as.switch_workspace.workspace_id;
|
||||
plan->dirty_flags |= WM_DIRTY_OUTPUT | WM_DIRTY_LAYOUT | WM_DIRTY_RENDER;
|
||||
push_render_all_outputs(state, plan);
|
||||
return true;
|
||||
}
|
||||
#+END_SRC
|
||||
|
||||
** =apply_send_window_to_workspace()=
|
||||
|
||||
#+BEGIN_SRC c
|
||||
static bool apply_send_window_to_workspace(wm_state_t *state,
|
||||
const wm_command_t *command,
|
||||
wm_plan_t *plan) {
|
||||
wm_window_t *win =
|
||||
require_window(state, command->as.send_window_to_workspace.window_id);
|
||||
if (!win) return false;
|
||||
|
||||
wm_workspace_id_t dst_id = command->as.send_window_to_workspace.workspace_id;
|
||||
if (!require_workspace(state, dst_id)) return false;
|
||||
if (win->workspace_id == dst_id) return false;
|
||||
|
||||
wm_workspace_t *src = require_workspace(state, win->workspace_id);
|
||||
win->workspace_id = dst_id;
|
||||
|
||||
if (src && src->focused_window_id == win->id) maybe_refocus_workspace(state, src);
|
||||
|
||||
plan->dirty_flags |= WM_DIRTY_STATE | WM_DIRTY_LAYOUT | WM_DIRTY_RENDER;
|
||||
push_render_all_outputs(state, plan);
|
||||
return true;
|
||||
}
|
||||
#+END_SRC
|
||||
|
||||
** =apply_send_window_to_output()=
|
||||
|
||||
#+BEGIN_SRC c
|
||||
static bool apply_send_window_to_output(wm_state_t *state,
|
||||
const wm_command_t *command,
|
||||
wm_plan_t *plan) {
|
||||
wm_window_t *win =
|
||||
require_window(state, command->as.send_window_to_output.window_id);
|
||||
wm_output_t *out =
|
||||
require_output(state, command->as.send_window_to_output.output_id);
|
||||
if (!win || !out) return false;
|
||||
|
||||
if (command->as.send_window_to_output.policy ==
|
||||
WM_CROSS_OUTPUT_KEEP_WORKSPACE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
wm_command_t forwarded = {
|
||||
.type = WM_COMMAND_SEND_WINDOW_TO_WORKSPACE,
|
||||
.as.send_window_to_workspace = {
|
||||
.window_id = win->id,
|
||||
.workspace_id = out->current_workspace_id,
|
||||
},
|
||||
};
|
||||
return apply_send_window_to_workspace(state, &forwarded, plan);
|
||||
}
|
||||
#+END_SRC
|
||||
|
||||
** =apply_toggle_floating()=
|
||||
|
||||
#+BEGIN_SRC c
|
||||
static bool apply_toggle_floating(wm_state_t *state,
|
||||
const wm_command_t *command,
|
||||
wm_plan_t *plan) {
|
||||
wm_window_t *win = require_window(state, command->as.toggle_floating.window_id);
|
||||
if (!win) return false;
|
||||
|
||||
if (!win->floating) {
|
||||
win->floating = true;
|
||||
win->float_rect = win->frame_rect;
|
||||
} else {
|
||||
win->floating = false;
|
||||
}
|
||||
|
||||
plan->dirty_flags |= WM_DIRTY_LAYOUT | WM_DIRTY_RENDER;
|
||||
push_render_all_outputs(state, plan);
|
||||
return true;
|
||||
}
|
||||
#+END_SRC
|
||||
|
||||
** =apply_toggle_maximize()=
|
||||
|
||||
#+BEGIN_SRC c
|
||||
static bool apply_toggle_maximize(wm_state_t *state,
|
||||
const wm_command_t *command,
|
||||
wm_plan_t *plan) {
|
||||
wm_window_t *win = require_window(state, command->as.toggle_maximize.window_id);
|
||||
if (!win) return false;
|
||||
|
||||
if (win->geometry_mode == WM_GEOMETRY_MAXIMIZED) {
|
||||
win->geometry_mode = WM_GEOMETRY_NORMAL;
|
||||
} else {
|
||||
win->geometry_mode = WM_GEOMETRY_MAXIMIZED;
|
||||
}
|
||||
|
||||
plan->dirty_flags |= WM_DIRTY_LAYOUT | WM_DIRTY_RENDER;
|
||||
push_render_all_outputs(state, plan);
|
||||
return true;
|
||||
}
|
||||
#+END_SRC
|
||||
|
||||
实际实现中应按 [[file:WM_COMMAND_RULES.org][WM_COMMAND_RULES.org]] 处理与 =FULLSCREEN= 和 =MINIMIZED= 的切换优先级。
|
||||
|
||||
** =apply_toggle_fullscreen()=
|
||||
|
||||
#+BEGIN_SRC c
|
||||
static bool apply_toggle_fullscreen(wm_state_t *state,
|
||||
const wm_command_t *command,
|
||||
wm_plan_t *plan) {
|
||||
wm_window_t *win =
|
||||
require_window(state, command->as.toggle_fullscreen.window_id);
|
||||
if (!win) return false;
|
||||
|
||||
if (win->geometry_mode == WM_GEOMETRY_FULLSCREEN) {
|
||||
win->geometry_mode = WM_GEOMETRY_NORMAL;
|
||||
} else {
|
||||
win->geometry_mode = WM_GEOMETRY_FULLSCREEN;
|
||||
}
|
||||
|
||||
plan->dirty_flags |= WM_DIRTY_LAYOUT | WM_DIRTY_RENDER;
|
||||
push_render_all_outputs(state, plan);
|
||||
return true;
|
||||
}
|
||||
#+END_SRC
|
||||
|
||||
** =apply_toggle_minimize()=
|
||||
|
||||
#+BEGIN_SRC c
|
||||
static bool apply_toggle_minimize(wm_state_t *state,
|
||||
const wm_command_t *command,
|
||||
wm_plan_t *plan) {
|
||||
wm_window_t *win =
|
||||
require_window(state, command->as.toggle_minimize.window_id);
|
||||
if (!win) return false;
|
||||
|
||||
if (win->geometry_mode == WM_GEOMETRY_MINIMIZED) {
|
||||
win->geometry_mode = WM_GEOMETRY_NORMAL;
|
||||
} else {
|
||||
win->geometry_mode = WM_GEOMETRY_MINIMIZED;
|
||||
}
|
||||
|
||||
plan->dirty_flags |= WM_DIRTY_LAYOUT | WM_DIRTY_RENDER;
|
||||
push_render_all_outputs(state, plan);
|
||||
return true;
|
||||
}
|
||||
#+END_SRC
|
||||
|
||||
** =apply_move_floating_window()=
|
||||
|
||||
#+BEGIN_SRC c
|
||||
static bool apply_move_floating_window(wm_state_t *state,
|
||||
const wm_command_t *command,
|
||||
wm_plan_t *plan) {
|
||||
wm_window_t *win =
|
||||
require_window(state, command->as.move_floating_window.window_id);
|
||||
if (!win || !win->floating) return false;
|
||||
|
||||
win->float_rect.x += command->as.move_floating_window.dx;
|
||||
win->float_rect.y += command->as.move_floating_window.dy;
|
||||
|
||||
if (command->as.move_floating_window.commit_workspace_change) {
|
||||
const wm_output_t *out =
|
||||
wm_state_find_output_by_point(state, command->as.move_floating_window.anchor);
|
||||
if (out &&
|
||||
command->as.move_floating_window.policy ==
|
||||
WM_CROSS_OUTPUT_MOVE_TO_TARGET_WORKSPACE) {
|
||||
win->workspace_id = out->current_workspace_id;
|
||||
}
|
||||
}
|
||||
|
||||
plan->dirty_flags |= WM_DIRTY_LAYOUT | WM_DIRTY_RENDER;
|
||||
push_render_all_outputs(state, plan);
|
||||
return true;
|
||||
}
|
||||
#+END_SRC
|
||||
|
||||
** =apply_resize_floating_window()=
|
||||
|
||||
#+BEGIN_SRC c
|
||||
static bool apply_resize_floating_window(wm_state_t *state,
|
||||
const wm_command_t *command,
|
||||
wm_plan_t *plan) {
|
||||
wm_window_t *win =
|
||||
require_window(state, command->as.resize_floating_window.window_id);
|
||||
if (!win || !win->floating) return false;
|
||||
|
||||
win->float_rect.w =
|
||||
clamp_u16((int)win->float_rect.w + command->as.resize_floating_window.dw,
|
||||
WM_MIN_WINDOW_W, UINT16_MAX);
|
||||
win->float_rect.h =
|
||||
clamp_u16((int)win->float_rect.h + command->as.resize_floating_window.dh,
|
||||
WM_MIN_WINDOW_H, UINT16_MAX);
|
||||
|
||||
plan->dirty_flags |= WM_DIRTY_LAYOUT | WM_DIRTY_RENDER;
|
||||
push_render_all_outputs(state, plan);
|
||||
return true;
|
||||
}
|
||||
#+END_SRC
|
||||
|
||||
** =apply_set_layout()=
|
||||
|
||||
#+BEGIN_SRC c
|
||||
static bool apply_set_layout(wm_state_t *state,
|
||||
const wm_layout_registry_t *layouts,
|
||||
const wm_command_t *command,
|
||||
wm_plan_t *plan) {
|
||||
wm_workspace_t *ws =
|
||||
require_workspace(state, command->as.set_layout.workspace_id);
|
||||
if (!ws) return false;
|
||||
if (!wm_layout_lookup(layouts, command->as.set_layout.layout_id)) return false;
|
||||
if (ws->layout_id == command->as.set_layout.layout_id) return false;
|
||||
|
||||
ws->layout_id = command->as.set_layout.layout_id;
|
||||
plan->dirty_flags |= WM_DIRTY_LAYOUT | WM_DIRTY_RENDER;
|
||||
push_render_all_outputs(state, plan);
|
||||
return true;
|
||||
}
|
||||
#+END_SRC
|
||||
|
||||
** =apply_redraw()=
|
||||
|
||||
#+BEGIN_SRC c
|
||||
static bool apply_redraw(wm_state_t *state,
|
||||
const wm_command_t *command,
|
||||
wm_plan_t *plan) {
|
||||
(void)state;
|
||||
(void)command;
|
||||
plan->dirty_flags |= WM_DIRTY_RENDER;
|
||||
push_render_all_outputs(state, plan);
|
||||
return true;
|
||||
}
|
||||
#+END_SRC
|
||||
|
||||
** =apply_quit()=
|
||||
|
||||
#+BEGIN_SRC c
|
||||
static bool apply_quit(wm_state_t *state,
|
||||
const wm_command_t *command,
|
||||
wm_plan_t *plan) {
|
||||
(void)state;
|
||||
(void)command;
|
||||
plan->dirty_flags |= WM_DIRTY_NONE;
|
||||
return true;
|
||||
}
|
||||
#+END_SRC
|
||||
|
||||
* 推荐的后处理步骤
|
||||
|
||||
在 =apply_*()= 返回后,建议由上层统一做这些事,而不是在每个分支里直接跑布局:
|
||||
|
||||
1. 如果有 =WM_DIRTY_LAYOUT= ,则对每个 output 的当前 workspace 重新计算 layout。
|
||||
2. 根据窗口的 =geometry_mode= 、=floating= 和 layout 结果解析最终矩形。
|
||||
3. 生成 =WM_EFFECT_CONFIGURE_WINDOW= 。
|
||||
4. 若有 =WM_DIRTY_STACK= ,生成 =WM_EFFECT_RESTACK_WINDOWS= 。
|
||||
5. 若有 =WM_DIRTY_RENDER= ,生成 =WM_EFFECT_RENDER_OUTPUT= 。
|
||||
|
||||
* 为什么要拆成多个 =apply_*()=
|
||||
|
||||
1. 便于单元测试,每个命令可以单独测试。
|
||||
2. 便于和 =WM_COMMAND_RULES.org= 对照审查。
|
||||
3. 便于以后替换部分策略,例如 =focus raises= 或 sticky 参与焦点候选的规则。
|
||||
4. 便于将复杂逻辑下沉到更小的辅助函数,而不是把 =wm_policy_apply_command()= 写成一个超长 switch。
|
||||
19
docs/min_core_draft/wm_backend.h
Normal file
19
docs/min_core_draft/wm_backend.h
Normal file
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include "wm_event.h"
|
||||
#include "wm_plan.h"
|
||||
|
||||
typedef struct wm_backend_t wm_backend_t;
|
||||
|
||||
typedef struct wm_backend_api_t {
|
||||
bool (*init)(wm_backend_t *backend);
|
||||
bool (*next_event)(wm_backend_t *backend, wm_event_t *out);
|
||||
bool (*apply_effect)(wm_backend_t *backend, const wm_effect_t *effect);
|
||||
void (*flush)(wm_backend_t *backend);
|
||||
void (*shutdown)(wm_backend_t *backend);
|
||||
} wm_backend_api_t;
|
||||
|
||||
struct wm_backend_t {
|
||||
const wm_backend_api_t *api;
|
||||
void *impl;
|
||||
};
|
||||
108
docs/min_core_draft/wm_command.h
Normal file
108
docs/min_core_draft/wm_command.h
Normal file
@@ -0,0 +1,108 @@
|
||||
#pragma once
|
||||
|
||||
#include "wm_types.h"
|
||||
|
||||
typedef enum wm_command_type_t {
|
||||
WM_COMMAND_NONE,
|
||||
WM_COMMAND_MANAGE_WINDOW,
|
||||
WM_COMMAND_UNMANAGE_WINDOW,
|
||||
WM_COMMAND_FOCUS_WINDOW,
|
||||
WM_COMMAND_FOCUS_DIRECTION,
|
||||
WM_COMMAND_RAISE_WINDOW,
|
||||
WM_COMMAND_LOWER_WINDOW,
|
||||
WM_COMMAND_SWITCH_WORKSPACE,
|
||||
WM_COMMAND_SEND_WINDOW_TO_WORKSPACE,
|
||||
WM_COMMAND_SEND_WINDOW_TO_OUTPUT,
|
||||
WM_COMMAND_TOGGLE_FLOATING,
|
||||
WM_COMMAND_TOGGLE_MAXIMIZE,
|
||||
WM_COMMAND_TOGGLE_FULLSCREEN,
|
||||
WM_COMMAND_TOGGLE_MINIMIZE,
|
||||
WM_COMMAND_MOVE_FLOATING_WINDOW,
|
||||
WM_COMMAND_RESIZE_FLOATING_WINDOW,
|
||||
WM_COMMAND_SET_LAYOUT,
|
||||
WM_COMMAND_REDRAW,
|
||||
WM_COMMAND_QUIT,
|
||||
} wm_command_type_t;
|
||||
|
||||
typedef struct wm_command_t {
|
||||
wm_command_type_t type;
|
||||
union {
|
||||
struct {
|
||||
wm_window_id_t window_id;
|
||||
wm_workspace_id_t workspace_id;
|
||||
} manage_window;
|
||||
|
||||
struct {
|
||||
wm_window_id_t window_id;
|
||||
} unmanage_window;
|
||||
|
||||
struct {
|
||||
wm_window_id_t window_id;
|
||||
} focus_window;
|
||||
|
||||
struct {
|
||||
wm_output_id_t output_id;
|
||||
wm_focus_direction_t direction;
|
||||
} focus_direction;
|
||||
|
||||
struct {
|
||||
wm_window_id_t window_id;
|
||||
} raise_window;
|
||||
|
||||
struct {
|
||||
wm_window_id_t window_id;
|
||||
} lower_window;
|
||||
|
||||
struct {
|
||||
wm_output_id_t output_id;
|
||||
wm_workspace_id_t workspace_id;
|
||||
} switch_workspace;
|
||||
|
||||
struct {
|
||||
wm_window_id_t window_id;
|
||||
wm_workspace_id_t workspace_id;
|
||||
} send_window_to_workspace;
|
||||
|
||||
struct {
|
||||
wm_window_id_t window_id;
|
||||
wm_output_id_t output_id;
|
||||
wm_cross_output_policy_t policy;
|
||||
} send_window_to_output;
|
||||
|
||||
struct {
|
||||
wm_window_id_t window_id;
|
||||
} toggle_floating;
|
||||
|
||||
struct {
|
||||
wm_window_id_t window_id;
|
||||
} toggle_maximize;
|
||||
|
||||
struct {
|
||||
wm_window_id_t window_id;
|
||||
} toggle_fullscreen;
|
||||
|
||||
struct {
|
||||
wm_window_id_t window_id;
|
||||
} toggle_minimize;
|
||||
|
||||
struct {
|
||||
wm_window_id_t window_id;
|
||||
int16_t dx;
|
||||
int16_t dy;
|
||||
bool commit_workspace_change;
|
||||
wm_point_t anchor;
|
||||
wm_cross_output_policy_t policy;
|
||||
} move_floating_window;
|
||||
|
||||
struct {
|
||||
wm_window_id_t window_id;
|
||||
int16_t dw;
|
||||
int16_t dh;
|
||||
} resize_floating_window;
|
||||
|
||||
struct {
|
||||
wm_workspace_id_t workspace_id;
|
||||
wm_layout_id_t layout_id;
|
||||
} set_layout;
|
||||
} as;
|
||||
} wm_command_t;
|
||||
95
docs/min_core_draft/wm_event.h
Normal file
95
docs/min_core_draft/wm_event.h
Normal file
@@ -0,0 +1,95 @@
|
||||
#pragma once
|
||||
|
||||
#include "wm_types.h"
|
||||
|
||||
typedef enum wm_event_type_t {
|
||||
WM_EVENT_NONE,
|
||||
WM_EVENT_KEY_PRESS,
|
||||
WM_EVENT_BUTTON_PRESS,
|
||||
WM_EVENT_BUTTON_RELEASE,
|
||||
WM_EVENT_POINTER_MOTION,
|
||||
WM_EVENT_MAP_REQUEST,
|
||||
WM_EVENT_UNMAP_NOTIFY,
|
||||
WM_EVENT_DESTROY_NOTIFY,
|
||||
WM_EVENT_WINDOW_METADATA_CHANGED,
|
||||
WM_EVENT_CONFIGURE_REQUEST,
|
||||
WM_EVENT_OUTPUT_CHANGED,
|
||||
WM_EVENT_TIMER_TICK,
|
||||
WM_EVENT_STATUS_TICK,
|
||||
WM_EVENT_QUIT,
|
||||
} wm_event_type_t;
|
||||
|
||||
typedef struct wm_key_press_event_t {
|
||||
uint32_t keycode;
|
||||
uint32_t modifiers;
|
||||
} wm_key_press_event_t;
|
||||
|
||||
typedef struct wm_button_event_t {
|
||||
uint32_t button;
|
||||
uint32_t modifiers;
|
||||
wm_point_t root;
|
||||
wm_window_id_t target_window_id;
|
||||
} wm_button_event_t;
|
||||
|
||||
typedef struct wm_pointer_motion_event_t {
|
||||
wm_point_t root;
|
||||
wm_window_id_t target_window_id;
|
||||
} wm_pointer_motion_event_t;
|
||||
|
||||
typedef struct wm_map_request_event_t {
|
||||
wm_window_id_t window_id;
|
||||
} wm_map_request_event_t;
|
||||
|
||||
typedef struct wm_unmap_notify_event_t {
|
||||
wm_window_id_t window_id;
|
||||
} wm_unmap_notify_event_t;
|
||||
|
||||
typedef struct wm_destroy_notify_event_t {
|
||||
wm_window_id_t window_id;
|
||||
} wm_destroy_notify_event_t;
|
||||
|
||||
typedef enum wm_window_meta_changed_flags_t {
|
||||
WM_WINDOW_META_CHANGED_NONE = 0,
|
||||
WM_WINDOW_META_CHANGED_TITLE = 1u << 0,
|
||||
WM_WINDOW_META_CHANGED_APP_ID = 1u << 1,
|
||||
WM_WINDOW_META_CHANGED_CLASS = 1u << 2,
|
||||
WM_WINDOW_META_CHANGED_INSTANCE = 1u << 3,
|
||||
} wm_window_meta_changed_flags_t;
|
||||
|
||||
typedef struct wm_window_metadata_changed_event_t {
|
||||
wm_window_id_t window_id;
|
||||
uint32_t changed_fields;
|
||||
} wm_window_metadata_changed_event_t;
|
||||
|
||||
typedef struct wm_configure_request_event_t {
|
||||
wm_window_id_t window_id;
|
||||
wm_rect_t requested_rect;
|
||||
} wm_configure_request_event_t;
|
||||
|
||||
typedef struct wm_output_changed_event_t {
|
||||
wm_output_id_t output_id;
|
||||
wm_rect_t geometry;
|
||||
wm_rect_t workarea;
|
||||
} wm_output_changed_event_t;
|
||||
|
||||
typedef struct wm_timer_tick_event_t {
|
||||
uint64_t now_ms;
|
||||
} wm_timer_tick_event_t;
|
||||
|
||||
typedef struct wm_event_t {
|
||||
wm_event_type_t type;
|
||||
uint64_t ts_ms;
|
||||
union {
|
||||
wm_key_press_event_t key_press;
|
||||
wm_button_event_t button_press;
|
||||
wm_button_event_t button_release;
|
||||
wm_pointer_motion_event_t pointer_motion;
|
||||
wm_map_request_event_t map_request;
|
||||
wm_unmap_notify_event_t unmap_notify;
|
||||
wm_destroy_notify_event_t destroy_notify;
|
||||
wm_window_metadata_changed_event_t window_metadata_changed;
|
||||
wm_configure_request_event_t configure_request;
|
||||
wm_output_changed_event_t output_changed;
|
||||
wm_timer_tick_event_t timer_tick;
|
||||
} as;
|
||||
} wm_event_t;
|
||||
58
docs/min_core_draft/wm_layout.h
Normal file
58
docs/min_core_draft/wm_layout.h
Normal file
@@ -0,0 +1,58 @@
|
||||
#pragma once
|
||||
|
||||
#include "wm_types.h"
|
||||
|
||||
typedef struct wm_layout_window_ref_t {
|
||||
wm_window_id_t window_id;
|
||||
wm_window_geometry_mode_t geometry_mode;
|
||||
} wm_layout_window_ref_t;
|
||||
|
||||
typedef struct wm_layout_ctx_t {
|
||||
wm_output_id_t output_id;
|
||||
wm_workspace_id_t workspace_id;
|
||||
wm_rect_t workarea;
|
||||
const wm_layout_window_ref_t *windows;
|
||||
size_t window_count;
|
||||
} wm_layout_ctx_t;
|
||||
|
||||
typedef struct wm_layout_item_t {
|
||||
wm_window_id_t window_id;
|
||||
wm_rect_t rect;
|
||||
} wm_layout_item_t;
|
||||
|
||||
typedef struct wm_layout_result_t {
|
||||
wm_layout_item_t *items;
|
||||
size_t item_count;
|
||||
size_t item_capacity;
|
||||
} wm_layout_result_t;
|
||||
|
||||
typedef bool (*wm_layout_fn)(const wm_layout_ctx_t *ctx,
|
||||
wm_layout_result_t *out);
|
||||
|
||||
typedef struct wm_layout_slot_t {
|
||||
wm_layout_id_t id;
|
||||
const char *name;
|
||||
const char *symbol_text;
|
||||
const char *symbol_icon_path;
|
||||
wm_layout_fn fn;
|
||||
} wm_layout_slot_t;
|
||||
|
||||
typedef struct wm_layout_registry_t {
|
||||
wm_layout_slot_t *slots;
|
||||
size_t slot_count;
|
||||
size_t slot_capacity;
|
||||
} wm_layout_registry_t;
|
||||
|
||||
void wm_layout_result_init(wm_layout_result_t *result);
|
||||
void wm_layout_result_reset(wm_layout_result_t *result);
|
||||
void wm_layout_result_shutdown(wm_layout_result_t *result);
|
||||
|
||||
bool wm_layout_result_push(wm_layout_result_t *result, wm_layout_item_t item);
|
||||
|
||||
void wm_layout_registry_init(wm_layout_registry_t *registry);
|
||||
void wm_layout_registry_shutdown(wm_layout_registry_t *registry);
|
||||
|
||||
bool wm_layout_register(wm_layout_registry_t *registry, wm_layout_id_t id,
|
||||
wm_layout_fn fn);
|
||||
wm_layout_fn wm_layout_lookup(const wm_layout_registry_t *registry,
|
||||
wm_layout_id_t id);
|
||||
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;
|
||||
} 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);
|
||||
28
docs/min_core_draft/wm_runtime.h
Normal file
28
docs/min_core_draft/wm_runtime.h
Normal file
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include "wm_backend.h"
|
||||
#include "wm_layout.h"
|
||||
#include "wm_plan.h"
|
||||
#include "wm_policy.h"
|
||||
#include "wm_state.h"
|
||||
|
||||
typedef struct wm_runtime_t {
|
||||
bool running;
|
||||
|
||||
wm_state_t state;
|
||||
wm_plan_t plan;
|
||||
wm_command_buffer_t command_buffer;
|
||||
wm_layout_registry_t layouts;
|
||||
wm_backend_t backend;
|
||||
} wm_runtime_t;
|
||||
|
||||
bool wm_runtime_init(wm_runtime_t *runtime, wm_backend_t backend);
|
||||
|
||||
/*
|
||||
* 处理单个事件,方便做无平台依赖的单元测试。
|
||||
*/
|
||||
bool wm_runtime_process_event(wm_runtime_t *runtime, const wm_event_t *event);
|
||||
|
||||
void wm_runtime_run(wm_runtime_t *runtime);
|
||||
void wm_runtime_stop(wm_runtime_t *runtime);
|
||||
void wm_runtime_shutdown(wm_runtime_t *runtime);
|
||||
103
docs/min_core_draft/wm_state.h
Normal file
103
docs/min_core_draft/wm_state.h
Normal file
@@ -0,0 +1,103 @@
|
||||
#pragma once
|
||||
|
||||
#include "wm_types.h"
|
||||
|
||||
typedef struct wm_window_t {
|
||||
wm_window_id_t id;
|
||||
wm_workspace_id_t workspace_id;
|
||||
|
||||
/*
|
||||
* 这里只保留控制状态,不存标题、类名、实例名等展示元数据。
|
||||
*/
|
||||
wm_window_geometry_mode_t geometry_mode;
|
||||
|
||||
/*
|
||||
* false: 由当前 workspace 的 layout 决定基础几何
|
||||
* true : 使用 float_rect 作为基础几何
|
||||
*/
|
||||
bool floating;
|
||||
|
||||
/*
|
||||
* sticky 只影响可见性,不改变 workspace 归属。
|
||||
*/
|
||||
bool sticky;
|
||||
bool urgent;
|
||||
|
||||
/*
|
||||
* floating 模式的基准矩形。退出 maximize/fullscreen 后可以回到这里。
|
||||
*/
|
||||
wm_rect_t float_rect;
|
||||
|
||||
/*
|
||||
* 最近一次实际提交给后端的矩形。
|
||||
*/
|
||||
wm_rect_t frame_rect;
|
||||
} wm_window_t;
|
||||
|
||||
typedef struct wm_workspace_t {
|
||||
wm_workspace_id_t id;
|
||||
|
||||
/*
|
||||
* 这里只保留运行状态,不存名称、bar 符号等描述信息。
|
||||
*/
|
||||
wm_layout_id_t layout_id;
|
||||
wm_window_id_t focused_window_id;
|
||||
} wm_workspace_t;
|
||||
|
||||
typedef struct wm_output_t {
|
||||
wm_output_id_t id;
|
||||
wm_rect_t geometry;
|
||||
wm_rect_t workarea;
|
||||
wm_workspace_id_t current_workspace_id;
|
||||
} wm_output_t;
|
||||
|
||||
typedef struct wm_state_t {
|
||||
wm_window_t *windows;
|
||||
size_t window_count;
|
||||
|
||||
/*
|
||||
* 全局 z-order,从下到上。每个受管窗口最多出现一次。
|
||||
*/
|
||||
wm_window_id_t *stack_order;
|
||||
size_t stack_count;
|
||||
|
||||
wm_workspace_t *workspaces;
|
||||
size_t workspace_count;
|
||||
|
||||
wm_output_t *outputs;
|
||||
size_t output_count;
|
||||
|
||||
/*
|
||||
* 成功提交一次状态变更后递增。
|
||||
*/
|
||||
uint64_t generation;
|
||||
} wm_state_t;
|
||||
|
||||
void wm_state_init(wm_state_t *state);
|
||||
void wm_state_shutdown(wm_state_t *state);
|
||||
|
||||
wm_window_t *wm_state_find_window(wm_state_t *state, wm_window_id_t id);
|
||||
const wm_window_t *wm_state_find_window_const(const wm_state_t *state,
|
||||
wm_window_id_t id);
|
||||
|
||||
wm_workspace_t *wm_state_find_workspace(wm_state_t *state,
|
||||
wm_workspace_id_t id);
|
||||
const wm_workspace_t *wm_state_find_workspace_const(const wm_state_t *state,
|
||||
wm_workspace_id_t id);
|
||||
|
||||
wm_output_t *wm_state_find_output(wm_state_t *state, wm_output_id_t id);
|
||||
const wm_output_t *wm_state_find_output_const(const wm_state_t *state,
|
||||
wm_output_id_t id);
|
||||
|
||||
const wm_output_t *wm_state_find_output_by_point(const wm_state_t *state,
|
||||
wm_point_t point);
|
||||
|
||||
bool wm_state_workspace_is_visible(const wm_state_t *state,
|
||||
wm_workspace_id_t workspace_id);
|
||||
|
||||
bool wm_state_window_should_be_visible(const wm_state_t *state,
|
||||
const wm_window_t *window);
|
||||
|
||||
bool wm_state_window_should_be_visible_on_output(const wm_state_t *state,
|
||||
const wm_window_t *window,
|
||||
const wm_output_t *output);
|
||||
39
docs/min_core_draft/wm_types.h
Normal file
39
docs/min_core_draft/wm_types.h
Normal file
@@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
typedef uint32_t wm_window_id_t;
|
||||
typedef uint16_t wm_workspace_id_t;
|
||||
typedef uint16_t wm_output_id_t;
|
||||
typedef uint32_t wm_layout_id_t;
|
||||
|
||||
typedef struct wm_point_t {
|
||||
int16_t x;
|
||||
int16_t y;
|
||||
} wm_point_t;
|
||||
|
||||
typedef struct wm_rect_t {
|
||||
int16_t x;
|
||||
int16_t y;
|
||||
uint16_t w;
|
||||
uint16_t h;
|
||||
} wm_rect_t;
|
||||
|
||||
typedef enum wm_window_geometry_mode_t {
|
||||
WM_GEOMETRY_NORMAL,
|
||||
WM_GEOMETRY_MAXIMIZED,
|
||||
WM_GEOMETRY_FULLSCREEN,
|
||||
WM_GEOMETRY_MINIMIZED,
|
||||
} wm_window_geometry_mode_t;
|
||||
|
||||
typedef enum wm_focus_direction_t {
|
||||
WM_FOCUS_PREV,
|
||||
WM_FOCUS_NEXT,
|
||||
} wm_focus_direction_t;
|
||||
|
||||
typedef enum wm_cross_output_policy_t {
|
||||
WM_CROSS_OUTPUT_KEEP_WORKSPACE,
|
||||
WM_CROSS_OUTPUT_MOVE_TO_TARGET_WORKSPACE,
|
||||
} wm_cross_output_policy_t;
|
||||
29
docs/min_core_draft/wm_window_meta.h
Normal file
29
docs/min_core_draft/wm_window_meta.h
Normal file
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include "wm_types.h"
|
||||
|
||||
typedef struct wm_window_meta_t {
|
||||
wm_window_id_t id;
|
||||
char *title;
|
||||
char *app_id;
|
||||
char *class_name;
|
||||
char *instance_name;
|
||||
} wm_window_meta_t;
|
||||
|
||||
typedef struct wm_window_meta_store_t {
|
||||
wm_window_meta_t *items;
|
||||
size_t count;
|
||||
size_t capacity;
|
||||
} wm_window_meta_store_t;
|
||||
|
||||
void wm_window_meta_store_init(wm_window_meta_store_t *store);
|
||||
void wm_window_meta_store_shutdown(wm_window_meta_store_t *store);
|
||||
|
||||
wm_window_meta_t *wm_window_meta_find(wm_window_meta_store_t *store,
|
||||
wm_window_id_t id);
|
||||
const wm_window_meta_t *wm_window_meta_find_const(
|
||||
const wm_window_meta_store_t *store, wm_window_id_t id);
|
||||
|
||||
bool wm_window_meta_upsert(wm_window_meta_store_t *store,
|
||||
const wm_window_meta_t *meta);
|
||||
bool wm_window_meta_remove(wm_window_meta_store_t *store, wm_window_id_t id);
|
||||
37
docs/min_core_draft/wm_workspace_desc.h
Normal file
37
docs/min_core_draft/wm_workspace_desc.h
Normal file
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include "wm_types.h"
|
||||
|
||||
typedef struct wm_workspace_desc_t {
|
||||
wm_workspace_id_t id;
|
||||
|
||||
/*
|
||||
* 人类可读名称,例如 "web"、"code"、"chat"。
|
||||
*/
|
||||
const char *name;
|
||||
|
||||
/*
|
||||
* bar 中展示的文本符号,例如 "1"、"2"、"M"。
|
||||
*/
|
||||
const char *symbol_text;
|
||||
|
||||
/*
|
||||
* 可选图标路径。若为空,则使用 symbol_text。
|
||||
*/
|
||||
const char *symbol_icon_path;
|
||||
} wm_workspace_desc_t;
|
||||
|
||||
typedef struct wm_workspace_desc_table_t {
|
||||
wm_workspace_desc_t *items;
|
||||
size_t count;
|
||||
size_t capacity;
|
||||
} wm_workspace_desc_table_t;
|
||||
|
||||
void wm_workspace_desc_table_init(wm_workspace_desc_table_t *table);
|
||||
void wm_workspace_desc_table_shutdown(wm_workspace_desc_table_t *table);
|
||||
|
||||
const wm_workspace_desc_t *wm_workspace_desc_lookup(
|
||||
const wm_workspace_desc_table_t *table, wm_workspace_id_t id);
|
||||
|
||||
bool wm_workspace_desc_register(wm_workspace_desc_table_t *table,
|
||||
wm_workspace_desc_t desc);
|
||||
@@ -12,9 +12,15 @@ WM_WINDOW_ROLE
|
||||
|
||||
UTF8_STRING
|
||||
COMPOUND_TEXT
|
||||
MANAGER
|
||||
_XEMBED
|
||||
_XEMBED_INFO
|
||||
|
||||
_NET_ACTIVE_WINDOW
|
||||
_NET_CLIENT_LIST
|
||||
_NET_SYSTEM_TRAY_COLORS
|
||||
_NET_SYSTEM_TRAY_OPCODE
|
||||
_NET_SYSTEM_TRAY_ORIENTATION
|
||||
_NET_WM_DESKTOP
|
||||
_NET_WM_NAME
|
||||
_NET_WM_STATE
|
||||
|
||||
18
src/event.c
18
src/event.c
@@ -15,6 +15,7 @@
|
||||
#include "client.h"
|
||||
#include "default_config.h"
|
||||
#include "monitor.h"
|
||||
#include "tray.h"
|
||||
#include "types.h"
|
||||
#include "utils.h"
|
||||
#include "wm.h"
|
||||
@@ -73,6 +74,8 @@ static void button_press(xcb_button_press_event_t *ev) {
|
||||
}
|
||||
|
||||
static void configure_request(xcb_configure_request_event_t *ev) {
|
||||
if (tray_handle_configure_request(ev)) return;
|
||||
|
||||
client_t *client = client_get_by_window(ev->window);
|
||||
if (client) {
|
||||
if (ev->value_mask & XCB_CONFIG_WINDOW_BORDER_WIDTH) {
|
||||
@@ -131,6 +134,8 @@ static void key_press(xcb_key_press_event_t *ev) {
|
||||
}
|
||||
|
||||
static void map_request(xcb_map_request_event_t *ev) {
|
||||
if (tray_handle_map_request(ev)) return;
|
||||
|
||||
xcb_get_window_attributes_reply_t *wa_reply =
|
||||
xwindow_get_attributes_reply(ev->window);
|
||||
|
||||
@@ -183,6 +188,8 @@ static inline bool get_bool_property(bool init_property,
|
||||
}
|
||||
|
||||
static void client_message(xcb_client_message_event_t *ev) {
|
||||
if (tray_handle_client_message(ev)) return;
|
||||
|
||||
client_t *c = client_get_by_window(ev->window);
|
||||
if (c == nullptr) return;
|
||||
|
||||
@@ -246,6 +253,8 @@ static void client_message(xcb_client_message_event_t *ev) {
|
||||
}
|
||||
|
||||
static void property_notify(xcb_property_notify_event_t *ev) {
|
||||
if (tray_handle_property_notify(ev)) return;
|
||||
|
||||
client_t *client = client_get_by_window(ev->window);
|
||||
if (client == nullptr) return;
|
||||
|
||||
@@ -265,11 +274,15 @@ static void property_notify(xcb_property_notify_event_t *ev) {
|
||||
}
|
||||
|
||||
static void destroy_notify(xcb_destroy_notify_event_t *ev) {
|
||||
if (tray_handle_destroy_notify(ev)) return;
|
||||
|
||||
client_t *client = client_get_by_window(ev->window);
|
||||
if (client) client_unmanage(client, true);
|
||||
}
|
||||
|
||||
static void unmap_notify(xcb_unmap_notify_event_t *ev) {
|
||||
if (tray_handle_unmap_notify(ev)) return;
|
||||
|
||||
client_t *client = client_get_by_window(ev->window);
|
||||
if (!client || client->minimize) return;
|
||||
|
||||
@@ -312,6 +325,10 @@ static void focus_in(xcb_focus_in_event_t *ev) {
|
||||
wm_set_current_monitor(client->monitor, ev->mode != XCB_NOTIFY_MODE_NORMAL);
|
||||
}
|
||||
|
||||
static void selection_clear(xcb_selection_clear_event_t *ev) {
|
||||
tray_handle_selection_clear(ev);
|
||||
}
|
||||
|
||||
static void handle_xcb_event(xcb_generic_event_t *event) {
|
||||
uint8_t event_type = XCB_EVENT_RESPONSE_TYPE(event);
|
||||
const char *label = xcb_event_get_label(event_type);
|
||||
@@ -337,6 +354,7 @@ static void handle_xcb_event(xcb_generic_event_t *event) {
|
||||
EVENT(XCB_DESTROY_NOTIFY, destroy_notify);
|
||||
EVENT(XCB_ENTER_NOTIFY, enter_notify);
|
||||
EVENT(XCB_FOCUS_IN, focus_in);
|
||||
EVENT(XCB_SELECTION_CLEAR, selection_clear);
|
||||
|
||||
#undef EVENT
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include "renderer.h"
|
||||
#include "status.h"
|
||||
#include "text.h"
|
||||
#include "tray.h"
|
||||
#include "types.h"
|
||||
#include "utils.h"
|
||||
#include "wm.h"
|
||||
@@ -239,13 +240,14 @@ static void monitor_draw_layout_symbol(monitor_t *monitor) {
|
||||
}
|
||||
}
|
||||
|
||||
static void monitor_draw_status(monitor_t *monitor, status_t *status) {
|
||||
static void monitor_draw_status(monitor_t *monitor, status_t *status,
|
||||
int16_t right_edge, int16_t left_edge) {
|
||||
static renderer_status_t *rendered_status = nullptr;
|
||||
static size_t rendered_status_capacity = 0;
|
||||
|
||||
monitor->status_extent.end = monitor->geometry.width;
|
||||
monitor->status_extent.start = monitor->geometry.width;
|
||||
int32_t end = monitor->layout_symbol_extent.end;
|
||||
monitor->status_extent.end = right_edge;
|
||||
monitor->status_extent.start = right_edge;
|
||||
int32_t end = left_edge;
|
||||
|
||||
if (status == nullptr || !wm.config || !wm.config->status) return;
|
||||
|
||||
@@ -343,7 +345,7 @@ static void monitor_draw_status(monitor_t *monitor, status_t *status) {
|
||||
}
|
||||
}
|
||||
|
||||
static void monitor_draw_tasks(monitor_t *monitor) {
|
||||
static void monitor_draw_tasks(monitor_t *monitor, int16_t right_edge) {
|
||||
task_in_tag_t *task_list = monitor->selected_tag->task_list;
|
||||
if (!task_list) return;
|
||||
|
||||
@@ -352,7 +354,10 @@ static void monitor_draw_tasks(monitor_t *monitor) {
|
||||
if (task_count == 0) return;
|
||||
|
||||
int16_t x = monitor->layout_symbol_extent.end;
|
||||
uint16_t task_width = (monitor->status_extent.start - x) / task_count;
|
||||
int32_t available_width = (int32_t)right_edge - x;
|
||||
if (available_width <= 0) return;
|
||||
|
||||
uint16_t task_width = (uint16_t)(available_width / task_count);
|
||||
if (task_width < wm.font_size) return;
|
||||
|
||||
for (task_in_tag_t *task = task_list; task; task = task->next) {
|
||||
@@ -376,6 +381,9 @@ static void monitor_draw_tasks(monitor_t *monitor) {
|
||||
void monitor_draw_bar(monitor_t *monitor) {
|
||||
cairo_t *cr = monitor->bar_cr;
|
||||
uint16_t height = wm.bar_height;
|
||||
uint16_t tray_width = tray_get_width(monitor);
|
||||
int16_t status_right = (int16_t)monitor->geometry.width - tray_width;
|
||||
if (status_right < 0) status_right = 0;
|
||||
area_t bar_area = {
|
||||
.x = 0,
|
||||
.y = 0,
|
||||
@@ -386,8 +394,10 @@ void monitor_draw_bar(monitor_t *monitor) {
|
||||
|
||||
monitor_draw_tags(monitor);
|
||||
monitor_draw_layout_symbol(monitor);
|
||||
monitor_draw_status(monitor, wm.status);
|
||||
monitor_draw_tasks(monitor);
|
||||
monitor_draw_status(monitor, wm.status, status_right,
|
||||
monitor->layout_symbol_extent.end);
|
||||
monitor_draw_tasks(monitor, monitor->status_extent.start);
|
||||
tray_place(monitor, monitor->geometry.width);
|
||||
}
|
||||
|
||||
void monitor_arrange(monitor_t *monitor) {
|
||||
|
||||
602
src/tray.c
Normal file
602
src/tray.c
Normal file
@@ -0,0 +1,602 @@
|
||||
#include "tray.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <xcb/xcb.h>
|
||||
#include <xcb/xcb_aux.h>
|
||||
#include <xcb/xproto.h>
|
||||
|
||||
#include "atoms-extern.h"
|
||||
#include "base.h"
|
||||
#include "monitor.h"
|
||||
#include "utils.h"
|
||||
#include "wm.h"
|
||||
#include "xwindow.h"
|
||||
|
||||
typedef enum tray_opcode_t {
|
||||
TRAY_REQUEST_DOCK = 0,
|
||||
TRAY_BEGIN_MESSAGE = 1,
|
||||
TRAY_CANCEL_MESSAGE = 2,
|
||||
} tray_opcode_t;
|
||||
|
||||
typedef enum tray_orientation_t {
|
||||
TRAY_ORIENTATION_HORIZONTAL = 0,
|
||||
} tray_orientation_t;
|
||||
|
||||
typedef enum xembed_message_t {
|
||||
XEMBED_EMBEDDED_NOTIFY = 0,
|
||||
} xembed_message_t;
|
||||
|
||||
enum {
|
||||
XEMBED_VERSION = 0,
|
||||
};
|
||||
|
||||
typedef struct tray_icon_t {
|
||||
xcb_window_t window;
|
||||
uint16_t natural_width;
|
||||
uint16_t natural_height;
|
||||
uint8_t ignore_unmaps;
|
||||
bool mapped;
|
||||
} tray_icon_t;
|
||||
|
||||
typedef struct tray_t {
|
||||
bool initialized;
|
||||
xcb_atom_t selection_atom;
|
||||
xcb_window_t window;
|
||||
monitor_t *host_monitor;
|
||||
|
||||
tray_icon_t *icons;
|
||||
size_t icon_count;
|
||||
size_t icon_capacity;
|
||||
|
||||
uint16_t spacing;
|
||||
uint16_t side_padding;
|
||||
} tray_t;
|
||||
|
||||
static tray_t tray = {
|
||||
.selection_atom = XCB_ATOM_NONE,
|
||||
.window = XCB_WINDOW_NONE,
|
||||
};
|
||||
|
||||
static xcb_atom_t tray_intern_atom(const char *name) {
|
||||
if (!name || !name[0]) return XCB_ATOM_NONE;
|
||||
|
||||
xcb_intern_atom_cookie_t cookie =
|
||||
xcb_intern_atom(wm.xcb_conn, false, (uint16_t)strlen(name), name);
|
||||
xcb_intern_atom_reply_t *reply =
|
||||
xcb_intern_atom_reply(wm.xcb_conn, cookie, nullptr);
|
||||
if (!reply) return XCB_ATOM_NONE;
|
||||
|
||||
xcb_atom_t atom = reply->atom;
|
||||
p_delete(&reply);
|
||||
return atom;
|
||||
}
|
||||
|
||||
static tray_icon_t *tray_get_icon(xcb_window_t window) {
|
||||
for (size_t i = 0; i < tray.icon_count; i++) {
|
||||
tray_icon_t *icon = &tray.icons[i];
|
||||
if (icon->window == window) return icon;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static size_t tray_find_icon_index(xcb_window_t window) {
|
||||
for (size_t i = 0; i < tray.icon_count; i++) {
|
||||
if (tray.icons[i].window == window) return i;
|
||||
}
|
||||
|
||||
return SIZE_MAX;
|
||||
}
|
||||
|
||||
static uint16_t tray_target_icon_height(void) {
|
||||
return MAX((uint16_t)1, wm.bar_height);
|
||||
}
|
||||
|
||||
static uint32_t tray_color_component_u16(double channel) {
|
||||
if (channel < 0.0) channel = 0.0;
|
||||
if (channel > 1.0) channel = 1.0;
|
||||
|
||||
uint32_t value = (uint32_t)(channel * 65535.0 + 0.5);
|
||||
return MIN(value, 65535u);
|
||||
}
|
||||
|
||||
static void tray_get_icon_layout(const tray_icon_t *icon, uint16_t *width,
|
||||
uint16_t *height) {
|
||||
uint32_t target_height = tray_target_icon_height();
|
||||
uint32_t natural_width = icon->natural_width;
|
||||
uint32_t natural_height = icon->natural_height;
|
||||
|
||||
if (natural_width == 0) natural_width = target_height;
|
||||
if (natural_height == 0) natural_height = target_height;
|
||||
if (natural_height == 0) natural_height = 1;
|
||||
|
||||
uint32_t scaled_width =
|
||||
(natural_width * target_height + natural_height / 2) / natural_height;
|
||||
if (scaled_width == 0) scaled_width = 1;
|
||||
|
||||
if (width) *width = (uint16_t)MIN((uint32_t)UINT16_MAX, scaled_width);
|
||||
if (height) *height = (uint16_t)MIN((uint32_t)UINT16_MAX, target_height);
|
||||
}
|
||||
|
||||
static uint16_t tray_compute_width(void) {
|
||||
if (!tray.initialized || tray.host_monitor == nullptr || tray.icon_count == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t width = (uint32_t)tray.side_padding * 2;
|
||||
for (size_t i = 0; i < tray.icon_count; i++) {
|
||||
uint16_t icon_width = 0;
|
||||
tray_get_icon_layout(&tray.icons[i], &icon_width, nullptr);
|
||||
width += icon_width;
|
||||
}
|
||||
if (tray.icon_count > 1) {
|
||||
width += (uint32_t)(tray.icon_count - 1) * tray.spacing;
|
||||
}
|
||||
|
||||
return (uint16_t)MIN((uint32_t)UINT16_MAX, width);
|
||||
}
|
||||
|
||||
static void tray_send_xembed_message(xcb_window_t window, uint32_t message,
|
||||
uint32_t detail, uint32_t data1,
|
||||
uint32_t data2) {
|
||||
xcb_client_message_event_t ev;
|
||||
p_clear(&ev, 1);
|
||||
ev.response_type = XCB_CLIENT_MESSAGE;
|
||||
ev.format = 32;
|
||||
ev.window = window;
|
||||
ev.type = _XEMBED;
|
||||
ev.data.data32[0] = XCB_CURRENT_TIME;
|
||||
ev.data.data32[1] = message;
|
||||
ev.data.data32[2] = detail;
|
||||
ev.data.data32[3] = data1;
|
||||
ev.data.data32[4] = data2;
|
||||
|
||||
xcb_send_event(wm.xcb_conn, false, window, XCB_EVENT_MASK_NO_EVENT,
|
||||
(char *)&ev);
|
||||
}
|
||||
|
||||
static void tray_select_icon_events(xcb_window_t window) {
|
||||
xcb_params_cw_t params = {
|
||||
.back_pixel = wm.color_set.bar_bg.argb,
|
||||
.border_pixel = 0,
|
||||
.event_mask = XCB_EVENT_MASK_PROPERTY_CHANGE |
|
||||
XCB_EVENT_MASK_STRUCTURE_NOTIFY,
|
||||
};
|
||||
uint32_t mask =
|
||||
XCB_CW_BACK_PIXEL | XCB_CW_BORDER_PIXEL | XCB_CW_EVENT_MASK;
|
||||
xcb_aux_change_window_attributes(wm.xcb_conn, window, mask, ¶ms);
|
||||
xcb_clear_area(wm.xcb_conn, false, window, 0, 0, 0, 0);
|
||||
}
|
||||
|
||||
static void tray_release_icon_window(tray_icon_t *icon) {
|
||||
if (!icon || icon->window == XCB_WINDOW_NONE) return;
|
||||
|
||||
xcb_change_save_set(wm.xcb_conn, XCB_SET_MODE_DELETE, icon->window);
|
||||
xcb_params_cw_t params = {
|
||||
.event_mask = XCB_EVENT_MASK_NO_EVENT,
|
||||
.border_pixel = 0,
|
||||
};
|
||||
xcb_aux_change_window_attributes(
|
||||
wm.xcb_conn, icon->window, XCB_CW_EVENT_MASK | XCB_CW_BORDER_PIXEL,
|
||||
¶ms);
|
||||
xcb_reparent_window(wm.xcb_conn, icon->window, wm.screen->root, 0, 0);
|
||||
}
|
||||
|
||||
static void tray_layout_icons(int16_t right_edge) {
|
||||
if (!tray.initialized || tray.window == XCB_WINDOW_NONE ||
|
||||
tray.host_monitor == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint16_t tray_width = tray_compute_width();
|
||||
if (tray_width == 0) {
|
||||
xcb_unmap_window(wm.xcb_conn, tray.window);
|
||||
return;
|
||||
}
|
||||
|
||||
int32_t tray_x = (int32_t)right_edge - tray_width;
|
||||
if (tray_x < 0) tray_x = 0;
|
||||
|
||||
xcb_configure_window_value_list_t tray_values = {
|
||||
.x = (int16_t)tray_x,
|
||||
.y = 0,
|
||||
.width = tray_width,
|
||||
.height = wm.bar_height,
|
||||
};
|
||||
uint16_t tray_mask = XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y |
|
||||
XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT;
|
||||
xcb_configure_window_aux(wm.xcb_conn, tray.window, tray_mask, &tray_values);
|
||||
xcb_map_window(wm.xcb_conn, tray.window);
|
||||
|
||||
int32_t x = tray.side_padding;
|
||||
for (size_t i = 0; i < tray.icon_count; i++) {
|
||||
tray_icon_t *icon = &tray.icons[i];
|
||||
uint16_t icon_width = 0;
|
||||
uint16_t icon_height = 0;
|
||||
tray_get_icon_layout(icon, &icon_width, &icon_height);
|
||||
|
||||
int32_t y = ((int32_t)wm.bar_height - icon_height) / 2;
|
||||
if (y < 0) y = 0;
|
||||
|
||||
xcb_configure_window_value_list_t icon_values = {
|
||||
.x = (int16_t)x,
|
||||
.y = (int16_t)y,
|
||||
.width = icon_width,
|
||||
.height = icon_height,
|
||||
.border_width = 0,
|
||||
};
|
||||
uint16_t icon_mask = XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y |
|
||||
XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT |
|
||||
XCB_CONFIG_WINDOW_BORDER_WIDTH;
|
||||
xcb_configure_window_aux(wm.xcb_conn, icon->window, icon_mask,
|
||||
&icon_values);
|
||||
xcb_map_window(wm.xcb_conn, icon->window);
|
||||
icon->mapped = true;
|
||||
|
||||
x += icon_width + tray.spacing;
|
||||
}
|
||||
}
|
||||
|
||||
static void tray_request_redraw(void) {
|
||||
if (!tray.initialized || tray.host_monitor == nullptr) return;
|
||||
|
||||
monitor_draw_bar(tray.host_monitor);
|
||||
xcb_flush(wm.xcb_conn);
|
||||
}
|
||||
|
||||
static void tray_remove_icon_by_index(size_t index, bool release_window) {
|
||||
if (index >= tray.icon_count) return;
|
||||
|
||||
tray_icon_t icon = tray.icons[index];
|
||||
if (release_window) tray_release_icon_window(&icon);
|
||||
|
||||
if (index + 1 < tray.icon_count) {
|
||||
memmove(&tray.icons[index], &tray.icons[index + 1],
|
||||
sizeof(*tray.icons) * (tray.icon_count - index - 1));
|
||||
}
|
||||
tray.icon_count--;
|
||||
|
||||
tray_request_redraw();
|
||||
}
|
||||
|
||||
static void tray_add_icon(xcb_window_t window) {
|
||||
if (!tray.initialized || window == XCB_WINDOW_NONE ||
|
||||
tray_get_icon(window) != nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (tray.icon_count == tray.icon_capacity) {
|
||||
tray.icon_capacity = (size_t)p_alloc_nr(tray.icon_capacity);
|
||||
p_realloc(&tray.icons, tray.icon_capacity);
|
||||
}
|
||||
|
||||
xcb_get_geometry_reply_t *geometry = xwindow_get_geometry_reply(window);
|
||||
if (!geometry) return;
|
||||
|
||||
tray_icon_t *icon = &tray.icons[tray.icon_count++];
|
||||
*icon = (tray_icon_t){
|
||||
.window = window,
|
||||
.natural_width = geometry->width,
|
||||
.natural_height = geometry->height,
|
||||
.ignore_unmaps = 1,
|
||||
.mapped = true,
|
||||
};
|
||||
p_delete(&geometry);
|
||||
|
||||
xcb_change_save_set(wm.xcb_conn, XCB_SET_MODE_INSERT, window);
|
||||
xcb_reparent_window(wm.xcb_conn, window, tray.window, 0, 0);
|
||||
tray_select_icon_events(window);
|
||||
tray_send_xembed_message(window, XEMBED_EMBEDDED_NOTIFY, 0, tray.window,
|
||||
XEMBED_VERSION);
|
||||
xcb_map_window(wm.xcb_conn, window);
|
||||
|
||||
tray_request_redraw();
|
||||
}
|
||||
|
||||
static bool tray_create_window(void) {
|
||||
static xcb_colormap_t colormap = XCB_NONE;
|
||||
if (tray.host_monitor == nullptr || tray.host_monitor->bar_window == XCB_NONE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
visual_t *visual = xwindow_get_xcb_visual(false);
|
||||
if (visual == nullptr) return false;
|
||||
|
||||
xcb_visualid_t visual_id = visual->visual->visual_id;
|
||||
if (colormap == XCB_NONE) {
|
||||
colormap = xcb_generate_id(wm.xcb_conn);
|
||||
xcb_create_colormap(wm.xcb_conn, XCB_COLORMAP_ALLOC_NONE, colormap,
|
||||
wm.screen->root, visual_id);
|
||||
}
|
||||
|
||||
xcb_window_t window = xcb_generate_id(wm.xcb_conn);
|
||||
uint32_t value_mask = XCB_CW_BACK_PIXEL | XCB_CW_BORDER_PIXEL |
|
||||
XCB_CW_EVENT_MASK | XCB_CW_COLORMAP;
|
||||
const xcb_create_window_value_list_t value_list = {
|
||||
.background_pixel = wm.color_set.bar_bg.argb,
|
||||
.border_pixel = 0,
|
||||
.event_mask = XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT |
|
||||
XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY,
|
||||
.colormap = colormap,
|
||||
};
|
||||
|
||||
xcb_void_cookie_t cookie = xcb_create_window_aux_checked(
|
||||
wm.xcb_conn, visual->depth, window, tray.host_monitor->bar_window, 0, 0, 1,
|
||||
wm.bar_height, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, visual_id, value_mask,
|
||||
&value_list);
|
||||
p_delete(&visual);
|
||||
if (xcb_request_check(wm.xcb_conn, cookie)) {
|
||||
logger("cannot create system tray window\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
tray.window = window;
|
||||
xwindow_set_class_instance(window);
|
||||
#define NAME APP_NAME "_tray"
|
||||
xwindow_set_name_static(window, NAME);
|
||||
xcb_change_property(wm.xcb_conn, XCB_PROP_MODE_REPLACE, window, _NET_WM_NAME,
|
||||
UTF8_STRING, 8, sizeof(NAME) - 1, NAME);
|
||||
#undef NAME
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool tray_take_selection_owner(void) {
|
||||
xcb_get_selection_owner_cookie_t owner_cookie =
|
||||
xcb_get_selection_owner(wm.xcb_conn, tray.selection_atom);
|
||||
xcb_get_selection_owner_reply_t *owner_reply =
|
||||
xcb_get_selection_owner_reply(wm.xcb_conn, owner_cookie, nullptr);
|
||||
if (owner_reply && owner_reply->owner != XCB_WINDOW_NONE) {
|
||||
logger("system tray selection is already owned by window %u\n",
|
||||
owner_reply->owner);
|
||||
p_delete(&owner_reply);
|
||||
return false;
|
||||
}
|
||||
p_delete(&owner_reply);
|
||||
|
||||
xcb_set_selection_owner(wm.xcb_conn, tray.window, tray.selection_atom,
|
||||
XCB_CURRENT_TIME);
|
||||
|
||||
owner_cookie = xcb_get_selection_owner(wm.xcb_conn, tray.selection_atom);
|
||||
owner_reply = xcb_get_selection_owner_reply(wm.xcb_conn, owner_cookie, nullptr);
|
||||
bool success = owner_reply && owner_reply->owner == tray.window;
|
||||
p_delete(&owner_reply);
|
||||
|
||||
if (!success) logger("cannot acquire system tray selection\n");
|
||||
return success;
|
||||
}
|
||||
|
||||
static void tray_set_properties(void) {
|
||||
uint32_t orientation = TRAY_ORIENTATION_HORIZONTAL;
|
||||
xcb_change_property(wm.xcb_conn, XCB_PROP_MODE_REPLACE, tray.window,
|
||||
_NET_SYSTEM_TRAY_ORIENTATION, XCB_ATOM_CARDINAL, 32, 1,
|
||||
&orientation);
|
||||
|
||||
const color_t *fg = &wm.color_set.tag_color;
|
||||
uint32_t colors[12] = {
|
||||
tray_color_component_u16(fg->red),
|
||||
tray_color_component_u16(fg->green),
|
||||
tray_color_component_u16(fg->blue),
|
||||
tray_color_component_u16(fg->red),
|
||||
tray_color_component_u16(fg->green),
|
||||
tray_color_component_u16(fg->blue),
|
||||
tray_color_component_u16(fg->red),
|
||||
tray_color_component_u16(fg->green),
|
||||
tray_color_component_u16(fg->blue),
|
||||
tray_color_component_u16(fg->red),
|
||||
tray_color_component_u16(fg->green),
|
||||
tray_color_component_u16(fg->blue),
|
||||
};
|
||||
xcb_change_property(wm.xcb_conn, XCB_PROP_MODE_REPLACE, tray.window,
|
||||
_NET_SYSTEM_TRAY_COLORS, XCB_ATOM_CARDINAL, 32,
|
||||
countof(colors), colors);
|
||||
}
|
||||
|
||||
static void tray_announce_manager(void) {
|
||||
xcb_client_message_event_t ev;
|
||||
p_clear(&ev, 1);
|
||||
ev.response_type = XCB_CLIENT_MESSAGE;
|
||||
ev.window = wm.screen->root;
|
||||
ev.format = 32;
|
||||
ev.type = MANAGER;
|
||||
ev.data.data32[0] = XCB_CURRENT_TIME;
|
||||
ev.data.data32[1] = tray.selection_atom;
|
||||
ev.data.data32[2] = tray.window;
|
||||
|
||||
xcb_send_event(wm.xcb_conn, false, wm.screen->root,
|
||||
XCB_EVENT_MASK_STRUCTURE_NOTIFY, (char *)&ev);
|
||||
}
|
||||
|
||||
void tray_init(void) {
|
||||
if (tray.initialized) return;
|
||||
|
||||
tray.host_monitor = wm.monitor_list;
|
||||
if (tray.host_monitor == nullptr) return;
|
||||
|
||||
tray.side_padding = 0;
|
||||
tray.spacing = 0;
|
||||
|
||||
char selection_name[64];
|
||||
snprintf(selection_name, sizeof(selection_name), "_NET_SYSTEM_TRAY_S%d",
|
||||
wm.default_screen);
|
||||
tray.selection_atom = tray_intern_atom(selection_name);
|
||||
if (tray.selection_atom == XCB_ATOM_NONE) {
|
||||
logger("cannot intern tray selection atom %s\n", selection_name);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!tray_create_window()) return;
|
||||
if (!tray_take_selection_owner()) {
|
||||
tray_cleanup();
|
||||
return;
|
||||
}
|
||||
|
||||
tray.initialized = true;
|
||||
tray_set_properties();
|
||||
tray_announce_manager();
|
||||
tray_layout_icons((int16_t)tray.host_monitor->geometry.width);
|
||||
xcb_flush(wm.xcb_conn);
|
||||
}
|
||||
|
||||
void tray_cleanup(void) {
|
||||
for (size_t i = 0; i < tray.icon_count; i++) {
|
||||
tray_release_icon_window(&tray.icons[i]);
|
||||
}
|
||||
p_delete(&tray.icons);
|
||||
tray.icon_count = 0;
|
||||
tray.icon_capacity = 0;
|
||||
|
||||
if (tray.selection_atom != XCB_ATOM_NONE) {
|
||||
xcb_set_selection_owner(wm.xcb_conn, XCB_WINDOW_NONE, tray.selection_atom,
|
||||
XCB_CURRENT_TIME);
|
||||
}
|
||||
|
||||
if (tray.window != XCB_WINDOW_NONE) {
|
||||
xcb_destroy_window(wm.xcb_conn, tray.window);
|
||||
}
|
||||
|
||||
tray = (tray_t){
|
||||
.selection_atom = XCB_ATOM_NONE,
|
||||
.window = XCB_WINDOW_NONE,
|
||||
};
|
||||
}
|
||||
|
||||
bool tray_handle_client_message(xcb_client_message_event_t *ev) {
|
||||
if (!tray.initialized) return false;
|
||||
|
||||
if (ev->type != _NET_SYSTEM_TRAY_OPCODE) return false;
|
||||
if (ev->window != tray.window && ev->window != wm.screen->root) return false;
|
||||
|
||||
switch (ev->data.data32[1]) {
|
||||
case TRAY_REQUEST_DOCK:
|
||||
tray_add_icon((xcb_window_t)ev->data.data32[2]);
|
||||
break;
|
||||
case TRAY_BEGIN_MESSAGE:
|
||||
case TRAY_CANCEL_MESSAGE:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
xcb_flush(wm.xcb_conn);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool tray_handle_configure_request(xcb_configure_request_event_t *ev) {
|
||||
if (!tray.initialized) return false;
|
||||
|
||||
if (ev->window == tray.window) {
|
||||
tray_layout_icons((int16_t)tray.host_monitor->geometry.width);
|
||||
xcb_flush(wm.xcb_conn);
|
||||
return true;
|
||||
}
|
||||
|
||||
tray_icon_t *icon = tray_get_icon(ev->window);
|
||||
if (!icon) return false;
|
||||
|
||||
if ((ev->value_mask & XCB_CONFIG_WINDOW_WIDTH) && ev->width > 0) {
|
||||
icon->natural_width = ev->width;
|
||||
}
|
||||
if ((ev->value_mask & XCB_CONFIG_WINDOW_HEIGHT) && ev->height > 0) {
|
||||
icon->natural_height = ev->height;
|
||||
}
|
||||
|
||||
tray_request_redraw();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool tray_handle_map_request(xcb_map_request_event_t *ev) {
|
||||
if (!tray.initialized) return false;
|
||||
|
||||
if (ev->window == tray.window) {
|
||||
tray_layout_icons((int16_t)tray.host_monitor->geometry.width);
|
||||
xcb_flush(wm.xcb_conn);
|
||||
return true;
|
||||
}
|
||||
|
||||
tray_icon_t *icon = tray_get_icon(ev->window);
|
||||
if (!icon && ev->parent != tray.window) return false;
|
||||
|
||||
if (icon) icon->mapped = true;
|
||||
xcb_map_window(wm.xcb_conn, ev->window);
|
||||
tray_layout_icons((int16_t)tray.host_monitor->geometry.width);
|
||||
xcb_flush(wm.xcb_conn);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool tray_handle_destroy_notify(xcb_destroy_notify_event_t *ev) {
|
||||
if (!tray.initialized) return false;
|
||||
|
||||
if (ev->window == tray.window) {
|
||||
tray.window = XCB_WINDOW_NONE;
|
||||
tray.initialized = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t index = tray_find_icon_index(ev->window);
|
||||
if (index == SIZE_MAX) return false;
|
||||
|
||||
tray_remove_icon_by_index(index, false);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool tray_handle_unmap_notify(xcb_unmap_notify_event_t *ev) {
|
||||
if (!tray.initialized) return false;
|
||||
|
||||
tray_icon_t *icon = tray_get_icon(ev->window);
|
||||
if (!icon) return ev->window == tray.window;
|
||||
|
||||
if (icon->ignore_unmaps > 0) {
|
||||
icon->ignore_unmaps--;
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t index = tray_find_icon_index(ev->window);
|
||||
if (index != SIZE_MAX) tray_remove_icon_by_index(index, true);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool tray_handle_property_notify(xcb_property_notify_event_t *ev) {
|
||||
if (!tray.initialized) return false;
|
||||
return tray_is_window(ev->window);
|
||||
}
|
||||
|
||||
bool tray_handle_selection_clear(xcb_selection_clear_event_t *ev) {
|
||||
if (!tray.initialized) return false;
|
||||
if (ev->owner != tray.window) return false;
|
||||
if (ev->selection != tray.selection_atom) return false;
|
||||
|
||||
monitor_t *host_monitor = tray.host_monitor;
|
||||
logger("system tray selection lost\n");
|
||||
tray_cleanup();
|
||||
|
||||
if (host_monitor != nullptr) monitor_draw_bar(host_monitor);
|
||||
xcb_flush(wm.xcb_conn);
|
||||
return true;
|
||||
}
|
||||
|
||||
uint16_t tray_get_width(monitor_t *monitor) {
|
||||
if (!tray.initialized || monitor == nullptr || monitor != tray.host_monitor) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return tray_compute_width();
|
||||
}
|
||||
|
||||
void tray_place(monitor_t *monitor, int16_t right_edge) {
|
||||
if (!tray.initialized || monitor == nullptr || monitor != tray.host_monitor) {
|
||||
return;
|
||||
}
|
||||
|
||||
tray_layout_icons(right_edge);
|
||||
}
|
||||
|
||||
bool tray_is_window(xcb_window_t window) {
|
||||
if (window == XCB_WINDOW_NONE) return false;
|
||||
if (tray.window == window) return true;
|
||||
return tray_get_icon(window) != nullptr;
|
||||
}
|
||||
21
src/tray.h
Normal file
21
src/tray.h
Normal file
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <xcb/xproto.h>
|
||||
|
||||
#include "types.h"
|
||||
|
||||
void tray_init(void);
|
||||
void tray_cleanup(void);
|
||||
|
||||
bool tray_handle_client_message(xcb_client_message_event_t *ev);
|
||||
bool tray_handle_configure_request(xcb_configure_request_event_t *ev);
|
||||
bool tray_handle_map_request(xcb_map_request_event_t *ev);
|
||||
bool tray_handle_destroy_notify(xcb_destroy_notify_event_t *ev);
|
||||
bool tray_handle_unmap_notify(xcb_unmap_notify_event_t *ev);
|
||||
bool tray_handle_property_notify(xcb_property_notify_event_t *ev);
|
||||
bool tray_handle_selection_clear(xcb_selection_clear_event_t *ev);
|
||||
|
||||
uint16_t tray_get_width(monitor_t *monitor);
|
||||
void tray_place(monitor_t *monitor, int16_t right_edge);
|
||||
bool tray_is_window(xcb_window_t window);
|
||||
3
src/wm.c
3
src/wm.c
@@ -33,6 +33,7 @@
|
||||
#include "monitor.h"
|
||||
#include "status.h"
|
||||
#include "text.h"
|
||||
#include "tray.h"
|
||||
#include "types.h"
|
||||
#include "utils.h"
|
||||
#include "wallpaper.h"
|
||||
@@ -387,6 +388,7 @@ void wm_clean(void) {
|
||||
clean_status();
|
||||
text_clean_pango_layout();
|
||||
image_cache_clean();
|
||||
tray_cleanup();
|
||||
|
||||
{
|
||||
client_t *c = wm.client_stack_list;
|
||||
@@ -457,6 +459,7 @@ static void wm_setup(void) {
|
||||
|
||||
wm_setup_keybindings();
|
||||
atoms_init(wm.xcb_conn);
|
||||
tray_init();
|
||||
|
||||
{
|
||||
wm.wm_check_window = xcb_generate_id(wm.xcb_conn);
|
||||
|
||||
Reference in New Issue
Block a user