Compare commits

...

130 Commits

Author SHA1 Message Date
b8ca575458 fix(policy): 新增窗口后通知原焦点窗口状态变更 2026-08-01 16:50:11 +08:00
6b2f0e3218 chore(config): 默认 Super+f 改为切换全屏 2026-07-29 04:53:11 +08:00
d7d16f1ac8 feat(window): 无标题窗口标题按优先级回退显示 instance/class/app_id
新增 window_get_showing_title,按 title → instance_name →
class_name → app_id 优先级取显示标题,窗口快照改用该函数,
避免标题缺失时显示为空。
2026-07-29 04:50:33 +08:00
88129293a0 feat: 补发 ConfigureNotify 通知客户端实际几何
当 WM 拒绝窗口的 ConfigureRequest(因窗口需要重新布局)或映射
窗口时,按 ICCCM 规范主动向客户端发送 synthetic ConfigureNotify,
告知其当前真实几何,避免客户端状态不一致。

- core: 新增 NOTIFY_CONFIGURE 命令与 CONFIGURE_NOTIFY 效果,
  由 policy 在 window_need_layout 分支生成,plan 层下发
- backend(x11): 新增 configure 批次,map/notify 时查询实际几何
  并合成 ConfigureNotify 事件下发
2026-07-29 04:31:28 +08:00
7b7b6d807f chore(make): 简化 Makefile 中的冗余逻辑
- 移除未使用的 PWD_DIR 变量及重复设置 TARGET 的 ifneq 块
- run / run_multiple_screen 直接调用 Xephyr,
  不再经过 $(shell ... &) 包装
2026-07-28 21:14:14 +08:00
80b091a806 chore(clangd): 补全时禁止自动插入头文件 2026-07-28 21:12:01 +08:00
89f11d1e24 feat(tray): tray_init 传入背景颜色 2026-07-28 05:46:15 +08:00
ceb40b434d fix(tray): 修复 tray 图标不能 reparent 到 container 窗口的 bug 2026-07-28 05:34:48 +08:00
272139f34f fix(tray): 修正图标自然尺寸为 int32_t 类型 2026-07-28 05:32:50 +08:00
583b1fa382 feat(tray): 拦截托盘图标的 unmap/destroy 事件并移除图标 2026-07-28 03:35:08 +08:00
612c2367ca feat(tray): 拦截托盘窗口的 map/configure/property 事件 2026-07-28 03:07:13 +08:00
88e589cc9e feat(tray): 托盘图标按 icon_size 等比例缩放水平排列 2026-07-28 02:30:38 +08:00
c609d15d77 fix(tray): 修复 tray_init 中初始化状态不完全导致 bar 计算布局时走文本测量的 bug 2026-07-28 02:27:46 +08:00
d47ca9cf1a feat(tray): 完善 tray_cleanup 释放图标窗口并归还 selection owner 2026-07-25 04:07:57 +08:00
e39eb245b6 feat(tray): 处理 SYSTEM_TRAY_REQUEST_DOCK 实现图标嵌入托盘容器
- 注册 _NET_SYSTEM_TRAY_OPCODE 与 _XEMBED atom
- 在事件分发中拦截托盘 client message
- tray_add_icon 将请求 dock 的窗口 reparent 到 container,
  发送 XEMBED_EMBEDDED_NOTIFY 并 map,维护 icons 数组并
  触发图标变更回调
2026-07-25 03:59:04 +08:00
3c5cfd3ead feat(tray): 实现 tray_cleanup 并在 backend_destroy 中调用以释放托盘资源 2026-07-25 01:07:49 +08:00
e7a0945a93 feat(tray): 成为 selection owner 后广播 MANAGER 事件通知托盘客户端 2026-07-24 20:59:51 +08:00
47bd59b806 feat(tray): 实现 tray_init 完成 system tray selection 接管 2026-07-24 20:21:48 +08:00
31cd35bf84 feat(bar): 新增 tray_output_index 配置用于选择 tray 挂载的屏幕 2026-07-18 18:31:31 +08:00
3d258bdc8b feat(bar): 将 tray 作为一个 item 集成到 bar 中 2026-07-18 18:23:52 +08:00
9f8681d467 feat(backend): backend_create_bar_window 返回 tray 句柄 2026-07-18 18:21:25 +08:00
d0120566bf feat(tray): 实现 tray_api_t 定义的所有接口 2026-07-15 18:49:15 +08:00
6a227b0497 fix: 移除 glibc 内部 bits/* 头文件提升可移植性
time.c 与 runtime.c 中误引的 <bits/time.h>、<bits/types/struct_itimerspec.h>、
<bits/types/sigset_t.h> 是 glibc 实现细节头文件,非 glibc 环境(bionic、musl 等)
不提供,导致跨平台编译失败。

这些符号(CLOCK_MONOTONIC / TFD_CLOEXEC / struct itimerspec / sigset_t)均由
已包含的公开标准头文件 <time.h>、<sys/timerfd.h>、<signal.h> 统一提供,
删除冗余引用后 glibc 与非 glibc 环境均可正常编译
2026-07-13 03:32:11 +08:00
6b75cda128 feat(bar): bar item 扩展钩子功能 bar cell 添加 fixed_width 属性与操作接口
- item type 加入可为空的 after_layout 和 after_draw 钩子
- cell 加 fixed_width 属性, cell api 添加 cell_set_fixed_width 和
  cell_get_region 两个接口
- cell api 改为 const 避免外部修改
2026-06-28 02:18:58 +08:00
27aab6f0e9 feat(tray): 添加 tray 相关接口并融入现有启动流程 2026-06-27 22:52:19 +08:00
eaad539a0b docs(tray): 新增实现计划,tray 接口声明移到 interface 中 2026-06-27 22:48:02 +08:00
079f41ce8a refactor: 从 core 中抽出跨模块契约建立 interface/common 模块分层
引入 interface/(零实现声明层)与 common/(共享 ADT 操作层),把跨模块
契约从 core 实现里抽出来,依赖方向回到单向(base ← interface ← common ←
各实现层)。docs/module-layering.org 记录完整方案与判定标准。

interface/(纯类型与多态接口声明,零 .c):
- types.h / event.h / backend.h 从 core 移入
- effect.h 新建(effect_t 从 plan.h 抽出)

common/(多实现层共用的自包含操作,.h + .c):
- event.{h,c}:event_reset / event_cleanup
- listeners.{h,c}:listeners 维护(add / cleanup);notify 留 core
- window.{h,c}:window_list + layer_props/metadata cleanup + window_classify_layer
- workspace.{h,c}:workspace_desc(从 wm_desc.h 拆出,改真实函数)

window 相关整理:
- window_layer_type_t 上浮 interface/types.h(common 的 classify 需要)
- window_classify_layer 移 common/window
- window_info_t 独立成 core/window_info.h(state/command 共享,不寄生)
- 删除 wm_desc.h,base/window_list 并入 common/window

全项目 include 路径同步更新
2026-06-25 03:00:30 +08:00
e1113c0052 docs: 新增模块分层与系统托盘设计文档 2026-06-24 13:54:21 +08:00
bf3de7fb92 style: 禁掉 clang-format 的 AlignConsecutiveAssignments 规则并重新格式化整个项目 2026-06-24 13:05:56 +08:00
8f671bd755 feat: bar 点击回调添加按键和修饰键参数
on_click 新增 modifiers/button,使 bar item 可按按键与修饰键区分动作;
因参数增多,回调与 bar_click 改用结构参数封装。同时将 button_t 与
modifier 类型提升至公开头 include/zdwm/types.h 以供使用
2026-06-23 07:42:52 +08:00
195aeb5166 fix(bar): 修复点击 workspace 末尾 layout 单元无法切换布局的 bug 2026-06-23 07:01:23 +08:00
af80181897 fix(bar): 计算 left/right 侧的整体区域以修复点击命中检测 2026-06-23 06:59:42 +08:00
42bddf5ac7 feat: 让 bar 支持点击触发动作 2026-06-23 06:57:38 +08:00
29e0f1e2dc feat: 为窗口移动/尺寸调整添加可配置的刷新率节流 2026-06-23 04:38:57 +08:00
74b30a5d4b fix(runtime): 修复布局函数为空导致的窗口未被布局 bug 2026-06-23 03:25:37 +08:00
40c2c328cd fix(policy): 修复切换焦点时的边框颜色设置和新窗口可能不触发布局 bug 2026-06-23 03:22:45 +08:00
71f050c2f3 feat(window): 添加判断窗口能否调整大小的辅助函数 2026-06-23 02:43:28 +08:00
f355b88b82 feat(policy): 调整窗口大小时遵循窗口的 max/min size hints 2026-06-23 00:53:17 +08:00
f3b1466dac fix(backend): 扫描窗口时按 transient 关系排序并过滤无效窗口 2026-06-23 00:33:03 +08:00
b71b66921e refactor(window): 用更通用的尺寸提示(min/max size)替换 fixed_size 标记 2026-06-22 23:22:08 +08:00
9a1e015fc1 feat: 添加 MOD4 + 鼠标右键调整窗口大小功能 2026-06-22 00:48:14 +08:00
4e7ab45864 feat: 添加 MOD4 + 鼠标左键移动窗口功能 2026-06-21 16:30:59 +08:00
b8cd40df9f feat(backend): 支持鼠标抓取功能 2026-06-20 01:06:30 +08:00
7a47f4d44d feat(backend): 添加光标支持 2026-06-20 00:59:13 +08:00
090266baa5 feat(backend): 发送鼠标按键和移动事件 2026-06-18 01:19:41 +08:00
9f9adf7e2e feat(backend): 写入 _NET_SUPPORTED 属性声明 EWMH 支持能力 2026-06-16 23:33:05 +08:00
00df685184 fix(backend): 修复 backend_poll_event 返回值意义不明导致的事件循环 bug
原因:之前的 backend_poll_event 返回 false 有两种情况
1. 当前就绪的事件已全部处理,没有事件可读了
2. 没有可以转化为感兴趣归一化的事件

当返回值为 false 并且时第二种情况时,底层的 xcb 原始事件未处理完,但不
再处理事件。导致有一部分事件一直无法处理,直到下一批事件待处理时通过文
件描述符状态变更唤醒新一轮的事件读取。这就会导致有些事件处理不及时,造
成卡顿的情况。

通过如下措施解决这个问题:
1. backend_poll_event 返回值只表示是否还有事件未处理完
2. event_t 引入 ZDWM_EVENT_NONE 表示事件为空,无人对该事件感兴趣也不需
   要清理
2026-06-16 23:16:55 +08:00
6d06b61134 fix(bar): workspaces 移除窗口时找到后立即 break 避免不必要的遍历 2026-06-16 19:13:30 +08:00
30dfb19a7b fix(config): 补全默认全景色 2026-06-16 19:11:49 +08:00
195733e008 fix(core): 补全窗口属性变更通知 2026-06-16 19:00:19 +08:00
6f1421f5b5 fix(bar): 重绘前清空内容并填充背景以消除脏区域残留 2026-06-16 17:49:34 +08:00
9bbdb26f06 fix(bar): 绘制 cell 文本前校验其文本避免渲染空内容 2026-06-16 17:22:52 +08:00
6e3e7ee937 fix(bar): 修复窗口列表布局 bug 导致的窗口列表 cell 重叠 2026-06-16 17:19:21 +08:00
9d35d17493 fix(bar): 修复 cell text/fg/bg 字符串内存泄露和 p_delete 参数错误 2026-06-16 17:17:00 +08:00
a4bd0b53de feat(bar): 添加窗口列表显示当前 workspace 的窗口列表 2026-06-16 17:14:59 +08:00
3be83aefdf fix(bar): 修复颜色缓存造成的颜色野指针从而导致无法正确渲染 cell 的 bug 2026-06-16 09:14:34 +08:00
dee4073996 feat(bar): 新增绑定模式指示器显示当前 binding mode
- 新增 bar/binding: 监听 binding mode 变更,显示当前模式名
- 支持点击单元循环切换绑定模式(binding_mode_cycle)
- 默认模式下按 show_default 配置决定是否隐藏单元
- 补充 binding 相关默认配置(颜色、内边距、是否显示默认模式)
2026-06-16 02:09:38 +08:00
e8b59e914c feat(bar): layout 颜色未配置时回退到默认背景色和前景色 2026-06-16 01:59:36 +08:00
ac65180203 feat(bar): 按间隔节流 bar_item 更新
- 新增 base/time: time_monotonic_ms 与 time_create_monotonic_timerfd_by_fps
- bar 初始化改用该工具创建 timerfd,移除内联的 itimerspec 拼装
- bar_item_update 按 update_interval_ms 节流,记录 last_updated_time 跳过过早更新
2026-06-16 00:31:07 +08:00
30e9751119 perf(bar): 仅在 bar 需要绘制新内容时才重绘并 flush 后端 2026-06-15 23:27:28 +08:00
41f82d7039 feat(bar): 在 workspace 列表末尾显示当前 workspace 布局符号 2026-06-15 19:50:42 +08:00
d0127f66e4 perf(runtime): bar 不可见时跳过更新避免无效重绘 2026-06-15 18:12:50 +08:00
4cef809f3c feat(policy): 窗口增删改时触发对应的 listeners 通知 2026-06-15 18:08:29 +08:00
d97cfba8dd fix(bar): 修复 workspace bar state 销毁时的内存泄露 2026-06-15 17:03:28 +08:00
539563c53f feat(runtime): 通过 signalfd 将信号处理集成到 poll 事件循环中 2026-06-10 01:54:20 +08:00
dbbbbce379 feat(runtime): 用基于 poll 的事件循环代替阻塞式事件等待 2026-06-10 01:26:05 +08:00
4e713682e4 refactor(policy): 统一 output 切换逻辑并添加 workspace 切换通知 2026-06-09 23:05:54 +08:00
4535e171a0 feat(bar): 实现 bar 布局排版和渲染绘制流程 2026-06-09 20:01:11 +08:00
fa2f058032 fix(bar): 修复 bar_cell_t text 字段可能为野指针 bug 2026-06-09 10:28:35 +08:00
8c145fb1b4 feat(bar): 实现 bar_draw 渲染骨架及文字排版绘制接口 2026-06-08 22:57:34 +08:00
62f6bab611 feat(bar): 新增 text 文字排版模块并将其集成到 bar 生命周期中 2026-06-08 01:24:49 +08:00
3a8c392d90 feat(bar): 实现 bar 初始化和清理功能并将其集成到 runtime 中 2026-06-08 01:21:19 +08:00
87535835a8 fix(bar): 修复 workspaces 中 destroy_state 接口内存未正确释放 bug 2026-06-08 01:07:15 +08:00
fed04cbffc refactor(bar): 隐藏 workspaces 模块内部细节仅保留必要的对外接口 2026-06-08 00:35:59 +08:00
b510b7b473 feat(bar): 初始化 bar 窗口并将 bar 集成到 runtime 启动流程中 2026-06-06 09:23:15 +08:00
c30c7d0698 fix(backend): 修复缺少 colormap 导致的创建 bar 窗口失败 2026-06-06 08:56:09 +08:00
adf839bf21 feat(bar): 实现 bar 可见性切换 action 2026-06-06 04:44:56 +08:00
be37b17c2e refactor(runtime): 将 runtime 模块从 core 目录迁移至独立的 runtime 目录并封装内部细节 2026-06-06 02:08:35 +08:00
f82d0b7a87 refactor: 移除新架构用不到的老模块 2026-06-06 00:40:39 +08:00
e9366b64e2 feat(backend): 添加创建 bar 窗口接口 2026-06-06 00:40:33 +08:00
f540c07aad refactor: 将 APP_NAME 宏从 CMake 配置文件生成改为静态头文件 2026-06-05 18:59:38 +08:00
ebc277201a chore: 删除老旧的测试代码和构建工具脚本 2026-06-05 16:23:04 +08:00
ff40b3caa0 feat(bar): 添加状态栏 bar 模块的基础架构和 workspace 指示器 2026-06-05 02:23:01 +08:00
d07d2f64e1 refactor(policy): 提取切换 workspace 和窗口聚焦的命令构造辅助函数 2026-06-04 22:28:39 +08:00
0d43c79ad6 feat(config): 默认配置补充按键绑定 2026-05-31 18:54:23 +08:00
83ed661ba6 fix(policy): 移动窗口所在 workspace 前先检查目标 workspace 是否存在 2026-05-31 18:42:51 +08:00
59520777c3 refactor(binding): key_binding_t 不再保存按键序列字符串指针 2026-05-31 18:33:11 +08:00
09d5e4b4d0 refactor(policy): 删除 action 模块,将 action -> command 解析放在 policy 模块内部
- 删除 src/core/action.c|h,将 spawn 移至 src/base/process
- policy_resolve_action 实现完整的 action 分发逻辑
- binding_table_cycle_mode 改为纯计算,返回模式 ID 而非修改状态
- 从 state 移除 state_workspace_cycle_layout / state_cycle_current_output
- 新增 ZDWM_COMMAND_QUIT 命令,通过 plan 标志驱动 runtime 退出
- set_binding_mode 现在生成 BIND_KEY effect 以同步后端按键绑定
- 扩展 action 数据类型支持 window_send_to_workspace 和 window_cycle_output
2026-05-27 01:12:04 +08:00
ff3b51fd1d feat(policy): 扩展命令并实现窗口迁移、 output/layout/binding_mode 切换功能
- command.h 新增 SEND_TO_WORKSPACE、SET_CURRENT_OUTPUT、SET_LAYOUT、SET_BINDING_MODE 四个命令类型及载荷
- policy.c 实现对应的 apply 逻辑,包含焦点转移、映射/取消映射副作用和 listener 通知
- plan.h 增加 quit/will_restart 标志,为后续优雅退出做准备
- binding.c 对重复设置同一 mode 增加短路返回
2026-05-26 13:15:54 +08:00
989a1ce6e8 feat(listeners): 调整通知结构体字段并补充文档注释 2026-05-24 17:58:29 +08:00
13fc41f141 feat(runtime): 通过 listeners 发送初始状态 2026-05-23 23:06:53 +08:00
ca9e543cc0 feat(listeners): 实现 listener 通知分发和清理功能 2026-05-23 21:09:43 +08:00
33b0f2f54f refactor(action): 重新设计 action 实现方式 2026-05-19 02:55:08 +08:00
edde023f5a fix(listeners): 公共头添加 extern "C" 链接说明以支持 C++ 包含 2026-05-17 03:36:48 +08:00
74319e97f1 feat: 添加核心状态变更通知系统 2026-05-17 03:20:38 +08:00
74be362b1f refactor(base): 通过 typeof 为 array_push 宏添加返回类型推导 2026-05-17 03:11:15 +08:00
9663097a98 fix(action): 修复 switch_workspace_by_index_in_current_output 循环条件错误 2026-05-10 12:19:50 +08:00
601c0f3d1a refactor: 迁移 CMake 到模块化目录构建并添加新入口 2026-05-09 23:03:00 +08:00
0d4a907dd8 feat(action): 实现切换当前 output 工作区功能 2026-05-09 23:00:01 +08:00
1f4659e7a4 refactor: 使用 workspace_id_invalid() 替代直接比较 ZDWM_WORKSPACE_ID_INVALID 2026-05-09 04:09:46 +08:00
a60d9f31a7 feat(action): 实现 focus_window 窗口焦点切换及 raise 命令 2026-05-07 07:20:27 +08:00
9275d14c5f feat(runtime): 启动时扫描并接管已有窗口
从 handle_map_request 中提取 populate_window_event 供复用,
新增 backend_scan_windows 扫描 X11 根窗口子树,
runtime_scan 将扫描到的窗口走完整 policy 流程纳入管理。
route_map_request 增加 override_redirect 过滤。
2026-05-07 06:36:58 +08:00
c1a4795a85 feat(state): 初始化 float_rect 并在切换 floating 状态时双向同步 2026-05-07 04:11:44 +08:00
8fcf46979a feat(action): 绑定 toggle_fullscreen/maximize/floating 快捷键 2026-05-07 03:54:30 +08:00
56f8380028 feat(action): 实现 switch_workspace cycle_layout 和 cycle_output 操作 2026-05-07 03:21:27 +08:00
2c3c264ddd feat(action): 实现 toggle_fullscreen/maximize/floating/sticky 操作 2026-05-07 03:20:24 +08:00
3031adf3cb feat(policy): 处理窗口状态变更事件 2026-05-07 02:22:16 +08:00
1b6a4788be feat(policy): 处理窗口激活请求事件 2026-05-07 02:14:38 +08:00
474ed8dc69 fix(policy): 窗口移除前获取其是否需要自动布局结果 2026-05-07 01:59:56 +08:00
964de33b23 feat(policy): 处理窗口元数据变更事件 2026-05-07 01:58:00 +08:00
b14be7b8dd feat(policy): 在窗口管理/取消管理时推送窗口列表变更 effect 2026-05-05 02:59:16 +08:00
f11125d214 fix(core): 补充 POINTER_ENTER 事件处理缺失的 break 语句 2026-05-05 02:38:30 +08:00
70e2477649 feat(action): 重构 action 接口为函数指针表并实现 quit 和 raise_or_run 2026-05-05 00:44:04 +08:00
d6512c531f fix(base): 修复 window_list_cleanup 中 p_delete 参数类型错误 2026-05-05 00:42:06 +08:00
c7399f014b fix(backend/X11): 修复按键抓取使用错误数组索引的 bug
window_grab_keys 中误用 keys (首元素)代替 key (当前元素)获取
keysym, 导致只有第一个按键能正确注册
2026-05-04 22:34:15 +08:00
42986fb783 feat(command): 添加窗口 bool 状态的 set 命令 2026-05-04 20:02:45 +08:00
ecc9834b03 refactor(command): 从 switch_workspace 命令中移除冗余的 output 字段 2026-05-04 17:52:36 +08:00
12bfce4745 refactor(core): 使用独立 bool 字段代替 geometry_mode 字段
将 fullscreen/maximized/minimized 从互斥枚举拆分为独立 bool 字段,以便
窗口状态切换时能回到上一状态。比如窗口初始为最大化状态,如果进入全屏状
态后从全屏状态退出时还能恢复最大化状态。
2026-05-04 04:07:36 +08:00
c777b58d80 feat(backend/X11): 处理 PropertyNotify 和 ClientMessage 事件 2026-05-04 03:03:15 +08:00
3349a912cc refactor(plan): 添加往 plan 中新增 effect 相关辅助函数以消除模板代码 2026-05-02 23:22:50 +08:00
b457d65fb9 feat(policy): 完善窗口状态变更时的边框和焦点管理 2026-05-02 22:25:11 +08:00
d90cebe0c2 feat(core+backend/X11): 实现窗口边框管理和焦点跟随鼠标 2026-05-02 00:46:13 +08:00
1cfa2c4505 feat(config): 添加配置窗口边框 API 2026-05-01 19:07:21 +08:00
3a830e864a fix(test): 更新 assert_default_layouts 匹配当前默认布局注册表 2026-05-01 18:56:22 +08:00
085a071f23 feat(policy): 处理 ConfigureRequest 事件 2026-04-29 01:56:31 +08:00
1485400b4c refactor(core+backend/X11): 将修改窗口几何属性合并到 CONFIGURE_WINDOW 中
将 MOVE/RESIZE/BORDER_WIDTH 三个效果合并进 CONFIGURE_WINDOW 中,通过
changed_fields 掩码提取有效数据
2026-04-29 00:39:19 +08:00
a393308704 feat(core+backend/X11): 实现窗口状态变更(最大化/最小化/全屏) 2026-04-28 03:45:40 +08:00
d602b0111e fix(core/policy): 修复切换工作区时 map_effect 使用的错误 union 字段
map_effect 应该使用 .as.map.window ,错误的用了 .as.unmap.window
2026-04-28 03:33:36 +08:00
78200debf5 refactor(core/runtime): 调整布局计算流程 2026-04-28 03:28:03 +08:00
722d3946c6 feat(core+backend/X11): 区分窗口销毁和撤销 2026-04-27 22:28:43 +08:00
169 changed files with 8933 additions and 8987 deletions

View File

@@ -6,12 +6,6 @@ IndentWidth: 2
ConstructorInitializerIndentWidth: 2
ContinuationIndentWidth: 2
AlignConsecutiveAssignments:
Enabled: true
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: true
PadOperators: true
AlignConsecutiveBitFields:
Enabled: true

View File

@@ -2,3 +2,4 @@
Completion:
ArgumentLists: None
HeaderInsertion: Never

View File

@@ -10,8 +10,6 @@ set(SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/src")
set(INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/include")
set(BUILD_DIR "${CMAKE_CURRENT_BINARY_DIR}")
add_compile_definitions(LOG_FILE="$ENV{HOME}/${APP_NAME}-log.txt")
# generate compile_commands.json for clangd
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
@@ -25,41 +23,64 @@ project(${APP_NAME}
LANGUAGES C
)
option(BUILD_TESTING "Build tests" OFF)
add_executable(${APP_NAME}
${SOURCE_DIR}/wm.c
${SOURCE_DIR}/backend/output_utils.c
${SOURCE_DIR}/backend/x11/backend.c
${SOURCE_DIR}/backend/x11/common.c
${SOURCE_DIR}/backend/x11/cursor.c
${SOURCE_DIR}/backend/x11/event.c
${SOURCE_DIR}/backend/x11/tray.c
${SOURCE_DIR}/backend/x11/window.c
${SOURCE_DIR}/bar/bar.c
${SOURCE_DIR}/bar/binding.c
${SOURCE_DIR}/bar/cell.c
${SOURCE_DIR}/bar/text.c
${SOURCE_DIR}/bar/tray.c
${SOURCE_DIR}/bar/windows.c
${SOURCE_DIR}/bar/workspaces.c
${SOURCE_DIR}/base/color.c
${SOURCE_DIR}/base/log.c
${SOURCE_DIR}/buffer.c
${SOURCE_DIR}/backtrace.c
${SOURCE_DIR}/monitor.c
${SOURCE_DIR}/client.c
${SOURCE_DIR}/text.c
${SOURCE_DIR}/color.c
${SOURCE_DIR}/xcursor.c
${SOURCE_DIR}/xwindow.c
${SOURCE_DIR}/event.c
${SOURCE_DIR}/action.c
${SOURCE_DIR}/xkb.c
${SOURCE_DIR}/atoms.c
${SOURCE_DIR}/layout.c
${SOURCE_DIR}/xres.c
${SOURCE_DIR}/status.c
${SOURCE_DIR}/audio.c
${SOURCE_DIR}/rect.c
${SOURCE_DIR}/wallpaper.c
${SOURCE_DIR}/config.c
${SOURCE_DIR}/renderer.c
${SOURCE_DIR}/image.c
${SOURCE_DIR}/tray.c
${SOURCE_DIR}/base/process.c
${SOURCE_DIR}/base/time.c
${SOURCE_DIR}/config/defaults.c
${SOURCE_DIR}/config/loader.c
${SOURCE_DIR}/config/runtime_config.c
${SOURCE_DIR}/common/event.c
${SOURCE_DIR}/common/listeners.c
${SOURCE_DIR}/common/window.c
${SOURCE_DIR}/common/workspace.c
${SOURCE_DIR}/core/binding.c
${SOURCE_DIR}/core/command_buffer.c
${SOURCE_DIR}/core/layer.c
${SOURCE_DIR}/core/layout.c
${SOURCE_DIR}/core/listeners.c
${SOURCE_DIR}/core/plan.c
${SOURCE_DIR}/core/policy.c
${SOURCE_DIR}/core/rules.c
${SOURCE_DIR}/core/state.c
${SOURCE_DIR}/core/window.c
${SOURCE_DIR}/layouts/fair.c
${SOURCE_DIR}/layouts/fullscreen.c
${SOURCE_DIR}/layouts/maximize.c
${SOURCE_DIR}/runtime/runtime.c
${SOURCE_DIR}/main.c
)
# target_compile_definitions(${APP_NAME}
# PRIVATE LOG_FILE="$ENV{HOME}/${APP_NAME}-log.txt"
# )
find_package(PkgConfig REQUIRED)
pkg_check_modules(deps REQUIRED
glib-2.0
x11
xcb
xcb-aux
xcb-cursor
@@ -68,18 +89,12 @@ pkg_check_modules(deps REQUIRED
xcb-randr
xcb-xfixes
xcb-xinerama
xcb-xkb
xcb-xrm
xkbcommon
xkbcommon-x11
cairo
cairo-xcb
pango
pangocairo
imlib2
libpulse-mainloop-glib
)
target_include_directories(${APP_NAME} SYSTEM
@@ -95,55 +110,3 @@ target_link_libraries(${APP_NAME}
PRIVATE m
PRIVATE ${deps_LIBRARIES}
)
## 配置项
# Check for backtrace_symbols()
include(CheckSymbolExists)
check_symbol_exists(backtrace_symbols execinfo.h HAS_EXECINFO)
if(NOT HAS_EXECINFO)
find_library(LIB_EXECINFO execinfo)
if(LIB_EXECINFO)
set(HAS_EXECINFO TRUE)
target_link_libraries(${APP_NAME} ${LIB_EXECINFO})
endif()
endif()
if(HAS_EXECINFO)
message(STATUS "checking for execinfo -- found")
else()
message(STATUS "checking for execinfo -- not found")
endif()
configure_file("${SOURCE_DIR}/app.h.in"
"${BUILD_DIR}/app.h"
ESCAPE_QUOTES
@ONLY
)
set(ATOM_LIST_FILE "${SOURCE_DIR}/atoms-list.txt")
add_custom_command(
COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/build-utils/atoms-extern.sh
ARGS ${ATOM_LIST_FILE} > ${BUILD_DIR}/atoms-extern.h
OUTPUT ${BUILD_DIR}/atoms-extern.h
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
DEPENDS ${ATOM_LIST_FILE}
COMMENT "Generating atoms-extern.h"
VERBATIM
)
add_custom_command(
COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/build-utils/atoms-intern.sh
ARGS ${ATOM_LIST_FILE} > ${BUILD_DIR}/atoms-intern.h
OUTPUT ${BUILD_DIR}/atoms-intern.h
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
DEPENDS ${ATOM_LIST_FILE}
COMMENT "Generating atoms-intern.h"
VERBATIM
)
add_custom_target(generate_atoms
DEPENDS ${BUILD_DIR}/atoms-extern.h ${BUILD_DIR}/atoms-intern.h
)
add_dependencies(${APP_NAME} generate_atoms)
if(BUILD_TESTING)
enable_testing()
add_subdirectory(tests)
endif()

View File

@@ -1,16 +1,11 @@
MAKEFILE_ABS_PATH := $(abspath $(lastword $(MAKEFILE_LIST)))
MAKEFILE_DIR := $(dir $(MAKEFILE_ABS_PATH))
PWD_DIR := $(CURDIR)/
BUILD_DIR := $(addprefix $(MAKEFILE_DIR),build)
SRC_DIR := $(addprefix $(MAKEFILE_DIR),/)
TARGET_NAME := zdwm
TARGET := $(addprefix $(MAKEFILE_DIR),$(TARGET_NAME))
ifneq ($(PWD_DIR),$(MAKEFILE_DIR))
TARGET := $(addprefix $(MAKEFILE_DIR),$(TARGET_NAME))
endif
$(TARGET_NAME): build
@@ -19,10 +14,10 @@ clean:
${RM} -r $(BUILD_DIR)
run:
-$(shell Xephyr :3 -screen 1920x1080 -dpi `xrdb -get Xft.dpi` &)
Xephyr :3 -screen 1920x1080 -dpi `xrdb -get Xft.dpi`
run_multiple_screen:
-$(shell Xephyr :3 -screen 1920x1080 -screen 1920x1080 +xinerama -dpi `xrdb -get Xft.dpi` &)
Xephyr :3 -screen 1920x1080 -screen 1920x1080 +xinerama -dpi `xrdb -get Xft.dpi`
wm: $(TARGET_NAME)
DISPLAY=:3 $(TARGET)
@@ -39,18 +34,8 @@ build: prepare
@cmake --build $(BUILD_DIR)
@cp -lf $(BUILD_DIR)/$(TARGET_NAME) $(TARGET)
build-test:
cmake -S $(SRC_DIR) -B $(BUILD_DIR) -DBUILD_TESTING=ON
cmake --build $(BUILD_DIR)
test: build-test
ctest --test-dir $(BUILD_DIR)
test-detail: build-test
ctest --test-dir $(BUILD_DIR) --rerun-failed --output-on-failure
install-hooks:
chmod +x $(MAKEFILE_DIR).githooks/pre-commit
git -C $(MAKEFILE_DIR) config core.hooksPath $(MAKEFILE_DIR).githooks
.PHONY: clean run wm prepare build install uninstall reinstall test install-hooks
.PHONY: clean run wm prepare build install uninstall reinstall install-hooks

View File

@@ -1,17 +0,0 @@
#!/bin/bash
echo "/* This file is autogenerated by $0 - do not edit */"
echo
echo "#include <xcb/xproto.h>"
echo
while read atom
do
# 去掉首位空格
atom_clean=$(echo "$atom" | sed 's/^[[:space:]]*//;s/[[:space:]]$//')
# 跳过空行
if [[ -n "$atom_clean" ]]; then
echo "extern xcb_atom_t ${atom_clean};"
fi
done < "$1"

View File

@@ -1,39 +0,0 @@
#!/bin/bash
echo "/* This file is autogenerated by $0 - do not edit */"
echo
echo "#include <xcb/xproto.h>"
echo
while read atom
do
# 去掉首位空格
atom_clean=$(echo "$atom" | sed 's/^[[:space:]]*//;s/[[:space:]]$//')
# 跳过空行
if [[ -n "$atom_clean" ]]; then
echo "xcb_atom_t ${atom_clean};"
fi
done < "$1"
echo
echo "typedef struct atom_item_t {"
echo " const char *name;"
echo " size_t len;"
echo " xcb_atom_t *atom;"
echo "} atom_item_t;"
echo
echo "static atom_item_t ATOM_LIST[] = {"
while read atom
do
# 去掉首位空格
atom_clean=$(echo "$atom" | sed 's/^[[:space:]]*//;s/[[:space:]]$//')
# 跳过空行
if [[ -n "$atom_clean" ]]; then
echo " {\"${atom_clean}\", sizeof(\"${atom_clean}\") - 1, &${atom_clean}},"
fi
done < "$1"
echo "};"

136
docs/module-layering.org Normal file
View File

@@ -0,0 +1,136 @@
#+title: ZDWM 模块分层与依赖方向
#+OPTIONS: toc:2
* 动机
随着模块增加,跨模块的"契约"和"共享数据结构"开始散落:有的进了 =core/=(和窗口管理逻辑混在一起),有的不得不靠依赖倒置塞进消费方目录(如 tray 契约放 =bar/= 导致 backend 反向依赖 bar。结果是 core 职责不清、=bar ↔ backend= 出现交叉依赖、依赖方向不再单向。
本文档定义一套分层约定,把"跨模块共享的东西"从具体实现里抽出来,集中到两个目录,使依赖方向回到干净的单向。
* 核心标准:按"谁直接消费类型"归类
判断一个跨模块的东西进哪个目录,不看它"属于哪个模块",也不只看"是声明还是实现",而是看**它的类型被哪些层直接消费**
- 类型被**两个或以上对等的实现层**直接消费 → 类型放 =interface/=(跨模块声明)
- 只有**操作函数**被多个实现层共用、且这些层不该互相依赖 → 操作放 =common/=
- 类型 + 操作都只被一个实现层(及其依赖者)用 → 整个 ADT 放 =common/=,或就近放该实现层
关键推论:**只需消费"类型"的层,不该被迫背上"操作函数"的依赖**。这是把类型和操作分到两个目录的根本理由。
* 目录的定位
先明确三层关系:=base/= 是**项目无关**的通用底层(任何 C 项目都能直接用),=interface/= 和 =common/= 是**项目业务相关**的通用内容。后两者依赖前者(业务依赖基础设施)。
** =src/base/= :项目无关的通用底层,零业务依赖
放任何 C 项目都能直接用的纯工具(=memory= / =array= / =macros= / =color= / =log= / =time= / =process=)。不依赖 interface/common/任何实现层——它根本不知道 ZDWM 的业务。=interface/= 和 =common/= 都可以依赖它。
** =src/interface/= :跨模块共享的【声明】,零 .c
只放类型定义、多态接口的函数声明。绝对不放假装、零实现代码。被所有实现层依赖,自身不依赖任何实现层(可依赖 =base/= 的纯类型)。
** =src/common/= :被多实现层共用的【操作函数】,.h + .c
放自包含的 ADT 操作(构造/清理/查询)。操作只依赖 =interface/= 里的类型(和 =base/= 工具),不依赖任何实现层的内部状态。被"需要这些操作的层"依赖。
注意:=common/= 不是"什么都往里塞的杂物间"。只有当操作被**多个不该互相依赖的实现层**共用时,才进 =common/=;只被单一实现层用的操作,就近放该层。
* 依赖方向
#+begin_example
base/ ← 项目无关的通用底层(任何 C 项目可用),零业务依赖
interface/ ← 零业务依赖(纯类型声明,含 backend 抽象接口);可依赖 base
common/ ← 项目业务通用的共享 ADT 操作;依赖 interface + base
core/ runtime/ backend/ bar/ ← 实现层,都依赖 common + interface+ base
#+end_example
注意:没有实现层"只依赖 interface"——所有实现层都用 =common/= 的业务通用操作。interface 的纯净性约束的是 interface *内部*(含 backend 抽象接口不依赖 interface 之外的任何东西),不是约束某个实现层的依赖深度。
不变量:
- =base/= 不依赖任何业务层interface/common/实现层),是项目无关的纯工具。
- =interface/=(含 backend 抽象接口)不依赖 =interface/= 之外的任何业务内容;可依赖 =base/= 的纯类型(业务层依赖基础设施,合理)。
- =common/= 只依赖 =interface/==base/=,不依赖任何实现层。
- 所有实现层core/runtime/backend/bar都依赖 =common/= + =interface/=+ =base/=);它们之间不直接相互依赖跨模块契约,都经由 =interface/==common/=
- 因此 =bar ↔ backend= 这类对等层之间不再有交叉依赖:都通过 =interface/tray.h= 通信。
* 判定示例effect / plan / event / listeners
用上面的标准,逐个判定现有跨模块类型:
** =effect_t= → interface类型无 .c
消费方corepolicy/plan 产生 =effect_t=+ backend执行 =effect_t=)。两个对等层直接消费类型,所以类型进 =interface/effect.h==effect_t= 是纯数据,构造它的活(=plan_push_*=)属于 plan 的操作,所以 effect 在 interface 里只有类型、没有 .c。
#+begin_src c
/* backend 只消费展开后的 effect 数组,从不碰 plan_t */
bool backend_apply_effect(backend_t *backend, const effect_t *effects, size_t effect_count);
#+end_src
** =plan_t= → 留 core完整 ADT类型 + 操作)
消费方:只 corepolicy 产生、收集 effect和 runtime提交后 reset/cleanup。两者之间是 *runtime→core 的天然单向依赖*runtime 是桥接层,本就依赖 core 的 policy/state 等)。把 =plan_t==core/plan.h= + =core/plan.c=runtime 正向 include 即可,不破坏任何方向——所以它*不必*进 common。
这把 common 的门槛精确化common 是为"对等、无天然依赖"的消费者中立化(见下文 =listeners_t=);若消费者之间已有单向依赖(如 plan 的 core+runtime共享操作放被依赖方core即可不必上浮 common。
** =event_t= → interface类型+ common操作
=event_t= 类型进 =interface/event.h= 的关键理由interface 内的 =backend.h= 契约直接 *#include 了 event.h*——=backend_poll_event= / =backend_next_event= 的参数是 =event_t *=,且契约注释讨论 event 的所有权与 =event_cleanup= 语义。一份完整的后端契约要让读者看到 event_t 全貌(字段、联合、生命周期责任),而非甩一个前向声明。所以 event_t 必须在 interface若放 common=backend.h= 要么退化为前向声明(契约不完整),要么 interface 反向依赖 common破坏零依赖
=event_reset= / =event_cleanup= 这些操作被 backend 和 runtime 共用,两者对等、不该互相依赖 → 操作落 =common/event.h= + =common/event.c=。于是 event 是"类型在 interface、操作在 common"的典型。
** =listeners_t= → common类型 + 维护)+ corenotify
=listeners_t= 类型被 bar订阅、core发布、runtime生命周期消费但 **backend 完全不碰它**。判定关键它的消费者bar/config/runtime都*既用类型又用维护操作*=add_*= / =cleanup=),没有哪个层"只需类型、不需操作"——按核心推论(只有"只需消费类型"的层才迫使类型单独上提 interface类型不必进 interface。与 =event_t= 的对照就在这里:=event_t= 因被 interface 内的 =backend.h= 契约需要完整定义而必须留 interface=listeners_t= 则*不被 interface 内任何契约依赖*=backend.h= 完全不碰它),所以能整个进 common
- 类型 =listeners_t= + 维护操作 =add_*= / =cleanup=:自包含容器操作,被 bar/config/runtime 多个对等层共用 → 整个 ADT 进 =common/listeners.{h,c}=
- =notify_*=(触发):单消费者(只 core/runtime 发布侧调用),且依赖 core 内部(把 =state_t= 翻译成 =zdwm_window_t= 等 DTO→ 声明 + 实现都留 =core/listeners=
真正的跨层契约(回调签名 =zdwm_window_added=+ DTO已在 =include/zdwm/listeners.h=(比 interface 更公开的那层),=listeners_t= 只是这些回调的容器,所以它整个待在 common 自洽,不必单独占一个 interface 头。
* 汇总表
| 东西 | 类型在哪 | 操作在哪 | 判定理由 |
|------------------------------+-----------------------+-------------------------------------------+--------------------------------------------------------------------------|
| =effect_t= | =interface/effect.h= | (无,构造归 plan 操作) | 类型跨模块core 产 + backend 执行),纯数据 |
| =plan_t= | =core/plan.h= | =core/plan.c= | 消费者 runtime→core 单向,放 core 不破坏方向,不必 common |
| =event_t= | =interface/event.h= | =common/event.h= + =common/event.c= | 类型跨模块;操作对等共用 |
| 后端接口(=backend_t= 等) | =interface/backend.h= | =backend/x11/*.c= | 多态接口:声明在 interface实现在具体后端 |
| tray 契约(=tray_t= 等) | =interface/tray.h= | =backend/x11/tray.c= | 多态接口bar 用、backend 实现,声明在 interface 消除 =bar↔backend= 交叉 |
| 通知协议listeners | =common/listeners.h= | =common/listeners.c= + =core/listeners.c= | 消费者 bar↔core 对等,必须 common 中立化notify 依赖 core 留 core |
| 基础类型(=window_id_t= 等) | =interface/types.h= | (按需) | 所有层共用 |
* 多态接口 vs 共享 ADT.c 归属的不同
=interface/= 里有两类声明,它们的 .c 归属不同,务必区分:
- **多态接口**=backend.h==tray.h=):声明一组"由谁来实现"的函数实现依赖具体实现层xcb / 未来 wayland。这类 .c **必须在实现层**=backend/x11/==core/=),不能在 interface——否则 interface 就"知道"了具体实现。
- **共享 ADT 的操作**event 的操作):实现自包含、不依赖任何实现层。这类 .c 进 =common/=
换句话说:=interface/= 永远零 .c。多态接口的 .c 在实现层,共享操作的 .c 在 =common/=
* 和 include/zdwm/ 的区别
- =include/zdwm/= 是**对外公共 API**(给用户配置、插件使用)。
- =src/interface/= 是**内部跨模块契约**(给各子系统互相通信)。
两者职责不同,不合并。像 =backend.h==event.h==effect.h= 是内部的,不该进 =include/zdwm/= 暴露给外部。
* 迁移指引
把现有代码按本分层整理时,建议顺序:
1.=src/interface/==src/common/= 目录,加入 CMakeLists。
2. 先迁移"纯类型、零依赖"的声明进 =interface/==types.h==effect.h=(从 =core/plan.h= 抽出 =effect_t=)、=event.h= 的类型部分。
3. 多态接口声明迁移:=backend.h==interface/==.c= 留原处)。=listeners= 走共享 ADT、不进 interface=listeners_t= 类型 + =add_*= / =cleanup= 整体进 =common/listeners.{h,c}==notify_*==core/listeners=(详见上方判定示例)。
4. 共享 ADT 迁移:=event.h= 操作部分 + =event.c==common/=。(=plan= 留 core消费者 runtime→core 单向,不必上浮。)
5. 更新全项目 include 路径(="core/types.h"=="interface/types.h"= 等)。
6. 每步后编译验证,确保依赖方向单向、无循环。
注意边界:遇到"看起来该进 interface、但其类型依赖还在 core"的情况(如某个 event 子结构引用了 =core/window.h= 的类型),要么把那个被引用的类型也提到 =interface/=,要么承认它暂不进 interface——**进 interface 的前提是它的所有依赖都在 interface**,不能硬塞。
* tray 的落点
tray 的契约(=tray_t= / =tray_api_t=)是 bar 与 backend 之间的多态接口,按本分层落 =interface/tray.h=(声明),实现在 =backend/x11/tray.c=。这样 bar 和 backend 都只依赖 =interface/tray.h=,彻底消除 =bar ↔ backend= 交叉依赖——这正是引入这套分层的最初动机之一。详见 [[file:tray-design.org][tray-design.org]]。

256
docs/tray-design.org Normal file
View File

@@ -0,0 +1,256 @@
#+title: ZDWM Tray 系统托盘设计
#+OPTIONS: toc:2
* 背景与动机
ZDWM 当前没有系统托盘system tray / systray功能。本文档描述如何在 bar 中实现 X11 系统托盘,让外部应用(输入法、音量、蓝牙、网络等)的托盘图标显示在状态栏右侧并正常交互,同时保持现有前后端分离架构,为未来 Wayland 后端预留扩展空间。
* 设计目标与约束
** 前后端分离
tray 协议(=_NET_SYSTEM_TRAY= + XEMBED必须*闭环在后端内部*。core 的 =event_t= / =policy= / =plan= 完全不感知 tray——tray 不产生 =event_t=、不进 policy 路由、不进 plan/effect。
这与现有架构的三条解耦通道一致:
- =backend -> core=:后端把平台原始事件归一化为 =event_t=
- =core -> backend=core 产出 =effect_t=,后端执行
- =runtime= 作为唯一桥接点bar 模块本身拿不到 backend所有平台访问经 runtime 中转
** Wayland 预留
bar 与 runtime 层必须后端无关、零 xcb。未来加 Wayland 后端时只需新增一个后端实现——Wayland 用 StatusNotifierItemSNIover D-Busicon 是像素数据而非真实窗口,由前端 cairo 绘制。只要后端接口形状不变上层bar/runtime零改动。
** tray 是 bar 的附属组件
没有 bar 就没有 tray。tray 的生命周期随 bar 窗口,*不*作为独立公共接口(不进 =include/zdwm/=。tray 能力内嵌在 bar 窗口的创建/销毁里。
* 方案选型嵌入式容器子窗口A'
X11 系统托盘协议只要求tray 创建一个 window 成为 =_NET_SYSTEM_TRAY_S<n>= 的 selection owner客户端向 owner 发 =_NET_SYSTEM_TRAY_REQUEST_DOCK= 请求tray 用 XEMBED reparent icon。协议本身*不*规定 icon reparent 到 bar 窗口还是独立窗口。
** 为何不用独立 top-level 窗口
完全独立的 top-level tray 窗口(如 stalonetray是为*没有状态栏*的环境设计的。ZDWM 已有 bar用独立窗口会引入"两个窗口的几何/对齐同步"问题tray 窗口要贴 bar 右端、随 icon 数动态 resize、跟随 bar 移动),且无功能收益。
** 为何用专门的容器子窗口
虽然直接 reparent 到 bar 的 cairo 绘制窗口技术上可行bar 已是 ARGB子窗口叠加在 cairo 内容之上),但用一个*专门的 tray 容器子窗口*做 selection owner 更正统、更干净:
1. *职责分离*bar 窗口专注 cairo 绘制tray 容器专注 XEMBED / icon 管理。dock 请求和 =_XEMBED= 消息定向到容器,不进 bar 的 =handle_client_message=
2. *事件隔离*tray 协议事件在容器窗口闭环bar 的窗口管理事件流零污染。
3. *视觉不变*:容器是 bar 窗口的子窗口,位于 tray region 内icon 跟随 bar 移动/resizeX 子窗口语义),不占额外屏幕空间。
* 后端接口设计
tray 契约独立成头 =interface/tray.h= —— 落 interface 层:不放 coretray 与 core 无关),也不放 barbackend 也要实现它。bar 和 backend 作为两个对等实现层,都依赖 =interface/tray.h= 来解耦——这正是分层消除 bar↔backend 交叉依赖的落点(详见 [[file:module-layering.org][module-layering.org]] 的 tray 落点)。它只依赖 =interface/types.h==window_id_t=),不含 =backend_t=,所以 bar include 它而不碰 =interface/backend.h= 的内部。=interface/backend.h= 反过来 include =interface/tray.h=,让 =backend_bar_window_t= 内嵌 =tray_t=
#+begin_src c
typedef void (*tray_icons_change_cb_t)(void *user_data);
typedef struct tray_api_t {
/* 返回 tray 当前挂载的 bar window_idinvalid 表示无 tray 挂载 */
window_id_t (*host_window)(void *handle);
/* 当前 icon 数量 */
size_t (*icon_count)(void *handle);
/* icon 边长(= bar heightbar 据此算 cell 宽度 */
int32_t (*icon_size)(void *handle);
/* 把容器 + icon 摆到 region 起点 x */
void (*place)(void *handle, int32_t x);
/* 注册 icon 数量变化回调 */
void (*set_listener)(void *handle,
tray_icons_change_cb_t cb,
void *user_data);
} tray_api_t;
typedef struct tray_t {
tray_api_t api; /* 值类型,后端总填充,一直可用 */
void *handle; /* 指向 backendbar 不知其类型) */
} tray_t;
typedef struct backend_bar_window_t {
window_id_t window_id;
cairo_t *cr;
tray_t tray;
} backend_bar_window_t;
/* 扩展:加 enable_trayicon_size 直接用 geometry.height */
backend_bar_window_t backend_create_bar_window(
backend_t *backend, rect_t geometry, uint32_t bg_pixel, bool enable_tray);
#+end_src
** 关键设计点
- *api 是值类型*:后端用一个全局 =static const= vtable 填充保证一直可用bar 不需判 NULL。这与项目现有 =zdwm_bar_item_type_t=(纯 vtable+ =zdwm_bar_item_t=(实例持有 api + state的模式一致。
- *handle 总有效*:实际指向 =backend_t*=tray api 函数内用 =backend->conn==backend->tray= host对 bar 透明(=void*=)。有无 tray 统一由 =host_window()= 返回值判断invalid = 无),不靠 api 是否 NULL避免"先判 api、再判 host_window"两个判断点。
- *icon_size 来自 handle*(单一数据源):后端 configure icon 用的尺寸 = bar 查布局用的尺寸,杜绝两边各算导致 icon 错位/溢出。这比在 =tray_t= 冗余存一份快照更纯粹。
- *生命周期随 bar/backend*=backend_create_bar_window(enable_tray=true)= 时创建/迁移 host=backend_destroy= 释放。无 =tray_host_create/destroy= 独立入口,体现"tray 是 bar 附属"。
** 挂载语义
X11 的 =_NET_SYSTEM_TRAY_S0= selection owner *整个 display 只能有一个*,但 =backend_create_bar_window= 是 per-output 调用的。处理方式:
- tray host 是后端单例(内联在 =backend_t=)。
- 每次传 =enable_tray=true==create_bar_window= 把容器 reparent 到本次 bar 窗口,最终挂在*最后一个* true 的 output 上。
- =host_window()= 返回当前挂载的 bar window_idbar 据此决定哪个 output 渲染 tray。
** 为何 set_listener 不配 remove_listener
tray icon 变化的唯一消费者是 bar 的 tray item且其生命周期 = host 生命周期(=backend_destroy= 时自然消失)。与 core 的 =listeners= 模块一致listener 跟宿主容器走,靠容器销毁回收),不引入 =remove_listener=
* bar item 框架扩展
tray 作为 right-side 的普通 item复用 =zdwm_bar_item_type_t= 框架。为支持 tray 的两个本质差异cell 无文本 + 需要在 layout 后把结果同步到后端真实窗口),对框架做最小*通用*扩展——不为 tray 特例化。
** cell_api 全包 cell 相关(不引入 item_api
=set_cell_count= / =get_cell_count= 本质是 *cell 集合的管理*(类比 =std::vector==size()/resize()==operator[]= 同属 vector留在 =zdwm_bar_cell_api_t=
新增两个 cell 级接口:
#+begin_src c
/* 固定宽度:>0 = 固定宽度0 = 走文本测量(零值默认),<0 = 隐藏 */
void cell_set_fixed_width(zdwm_bar_item_t *, size_t index, int32_t width);
/* 单个 cell 的 regionlayout 后的坐标) */
bar_x_region_t cell_get_region(zdwm_bar_item_t *, size_t index);
#+end_src
=bar_cell_t==int32_t fixed_width=,默认 =0=(走文本测量)。把零值留给最常见的默认行为(测量),=p_clear= 清零后新 cell 天然正确,=set_cell_count= 无需补初始化;=-1= 当"隐藏"哨兵(负数宽度本就无意义),与 =0=(测量)明确区分,没有歧义。
=bar_output_layout= 改一处:
#+begin_src c
int32_t width;
if (cell->fixed_width > 0) width = cell->fixed_width; /* 固定 */
else if (cell->fixed_width == 0) width = ; /* 测量(默认)*/
else width = 0; /* 隐藏 */
#+end_src
** after_layout / after_draw 钩子
普通 item 是 cairo 绘制tray 是"layout 后把 region 应用到后端真实窗口"。给 =zdwm_bar_item_type_t= 加两个*可空*通用钩子,参数打包(仿现有 =zdwm_bar_click_params_t= 先例):
#+begin_src c
typedef struct zdwm_bar_item_layout_params_t {
zdwm_bar_item_t *item;
const zdwm_bar_cell_api_t *cell_api;
bar_x_region_t region;
void *state;
} zdwm_bar_item_layout_params_t;
void (*after_layout)(const zdwm_bar_item_layout_params_t *); /* 可空 */
void (*after_draw)(const zdwm_bar_item_layout_params_t *); /* 可空 */
#+end_src
设计要点:
- 钩子为 NULL 则跳过,普通 item 零影响。
- =after_layout= / =after_draw==update= 多一个 =region=(它们在 layout 之后调用),且通过 =cell_api= 可调 =cell_get_region= 取 per-cell 位置。
- =update= 不打包参数3 个参数不多,保持现有 =workspaces= / =binding= / =windows= 三个 item 零改动)。
=bar_draw= 流程:
#+begin_src c
bar_output_layout(bo, ctx);
for each item: if (after_layout) after_layout(params); /* params 含 region */
bar_output_draw(bo, ctx, bg);
for each item: if (after_draw) after_draw(params);
#+end_src
* bar tray item
新增 =src/bar/tray.c=,作为普通 right-side item参考 [[file:../src/bar/workspaces.c][workspaces.c]] 的实现模式):
#+begin_src c
zdwm_bar_item_type_t bar_tray = {
.create_state = bar_tray_create_state,
.update = bar_tray_update,
.on_click = bar_tray_on_click, /* 返回 NONEicon 真实子窗口自收点击 */
.after_layout = bar_tray_after_layout,
.destroy_state = bar_tray_destroy_state,
};
#+end_src
- =create_state=:拷贝 =tray_t=,调 =set_listener= 注册回调(回调置 =state->dirty = true=)。
- =update==dirty==set_cell_count(icon_count)= + 循环 =cell_set_fixed_width(i, icon_size)=
- =after_layout==place(handle, params->region.start)=
tray cell 不画 cairo 内容text 为空,=bar_item_draw= 只画融入 bar 的背景色块,被真实 icon 窗口盖住)。
* runtime 桥接
=runtime_init_bar==src/runtime/runtime.c=)的 create 循环:
#+begin_src c
for (size_t i = 0; i < count; ++i) {
bool enable_tray = ((int32_t)i == runtime->bar.config.tray_output_index);
auto bar_window = backend_create_bar_window(backend, bar_rect, color, enable_tray);
...
runtime->bar.tray = bar_window.tray; /* 每个返回都一样,存一份 */
}
bar_init(&runtime->bar, &runtime->listeners);
#+end_src
=bar_init= 遍历 =bar_output=,对 =host_window()= 匹配自身 =window_id= 的那个 output 调 =bar_output_add_tray=right side其余不加。tray 生命周期随 =backend_destroy=runtime 无特殊清理。
* 配置
=zdwm_bar_config_t==include/zdwm/config.h=)加:
#+begin_src c
int32_t tray_output_index; /* 默认 0 = 第一个 output */
#+end_src
配置 setup 中可通过 output 信息确定具体 index。
* 数据流(触发链)
#+begin_example
icon 增删
-> 后端 set_listener 回调 -> state->dirty = true
-> 下个 timerfd tick -> bar_update -> bar_tray_update
-> set_cell_count + cell_set_fixed_width -> item 标 dirty
-> bar_draw -> bar_output_layout 重算 regioncell 数变了)
-> tray 的 after_layout -> backend place(handle, region.start)
-> 后端 move 容器 + configure 每个 icon 到 (region.start + i*icon_size, 0)
#+end_example
bar 重绘是 timerfdfps驱动非事件驱动。tray icon 是真实窗口X server 自管绘制,不依赖 cairo 重绘。
* 实现范围(第一版)
第一版做*基础可用*
- =_NET_SYSTEM_TRAY_S0= selection owner + MANAGER 广播
- 基础 XEMBEDreparent 容器、=_XEMBED_EMBEDDED_NOTIFY=
- icon 显示与点击
- icon 尺寸 = bar 高度(统一,不缩放)
放后续迭代:
- 完整 XEMBED 焦点 / 激活转发
- icon 尺寸协商与缩放
- 客户端异常断开的清理
*透明背景*第一版基本白送——bar 窗口已是 ARGB visual=backend_create_bar_window= 调用 =window_get_visual(backend, true)=tray 容器作为子窗口继承。
* 关键文件清单
| 文件 | 改动 |
|------------------------------+------------------------------------------------------------------------------------------------------------------------|
| =interface/tray.h= | **新增** tray 契约:=tray_t= / =tray_api_t= / =tray_icons_change_cb_t=bar 与 backend 共享,落 interface 解耦) |
| =interface/backend.h= | include =interface/tray.h==backend_bar_window_t= 内嵌 =tray_t=;扩展 =backend_create_bar_window=(加 =enable_tray= |
| =src/backend/x11/tray.c= | *新增*selection / MANAGER / 基础 XEMBED / icon 管理 / =place= |
| =src/backend/x11/internal.h= | =ATOM_LIST= 追加 tray/XEMBED atoms=backend_t= 加 =tray= 字段 |
| =src/backend/x11/event.c= | 消化 tray 相关 client message不产 =event_t= |
| =src/backend/x11/backend.c= | =backend_create_bar_window= 实现 enable_tray 分支;=backend_destroy= 释放 tray |
| =src/bar/types.h= | =bar_cell_t= 加 =fixed_width==bar_t= 加 =tray= |
| =src/bar/cell.h= / =cell.c= | =cell_api= 加 =cell_set_fixed_width= / =cell_get_region= |
| =src/bar/bar.c= | layout 支持 =fixed_width==bar_draw= 调 after 钩子;=bar_init= 加 tray item新增 layout params 类型 |
| =src/bar/tray.c= | *新增*tray item 实现 |
| =src/runtime/runtime.c= | =runtime_init_bar= 传 =enable_tray=、存 =bar->tray= |
| =include/zdwm/config.h= | =zdwm_bar_config_t= 加 =tray_output_index= |
* 复用现有
- =backend_create_bar_window==src/backend/x11/backend.c=bar 窗口已是 ARGBtray 容器继承透明。
- =zdwm_bar_item_type_t= vtable 框架 + [[file:../src/bar/workspaces.c][workspaces.c]]tray item 同模式落地。
- =zdwm_bar_click_params_t==after_layout= / =after_draw= 的 params 打包仿此先例。
- =cell_api= 机制tray =update= 通过它操作 cell与 workspaces 一致)。

View File

@@ -0,0 +1,91 @@
#+title: ZDWM Tray 系统托盘实现计划
#+OPTIONS: toc:2
* 背景
bar 当前没有系统托盘。目标是让外部应用(输入法、音量、蓝牙、网络等)的 tray icon 显示在 bar 右侧并正常交互。
设计文档 [[file:tray-design.org][tray-design.org]] 已把方案定死。刚完成的模块分层重构([[file:module-layering.org][module-layering.org]])为 tray 铺了路——=interface/= 层让 bar 与 backend 经契约解耦tray 契约落 =interface/tray.h= 消除 =bar↔backend= 交叉依赖(分层的核心动机之一)。
* 关键决策
- *契约落 =interface/tray.h=*(分层后的位置,非设计文档旧版的 =src/bar/tray.h=)。
- *tray 闭环后端*core 的 =event_t= / =policy= / =plan= 完全不感知tray 消息在 =event.c= 拦截后 =return false=,不进 policy 路由。
- *tray 是 bar 附属 + 后端单例*:生命周期随 bar 窗口;=_NET_SYSTEM_TRAY_S0= selection owner 全 display 唯一,挂到最后一个 =enable_tray=true= 的 output。
- *第一版范围*selection owner + MANAGER 广播 + 基础 XEMBEDreparent + =_XEMBED_EMBEDDED_NOTIFY=+ icon 显示与点击 + icon 尺寸 = bar 高度(不缩放)。焦点/激活转发、尺寸协商、客户端异常断开清理放后续迭代。
- *tray 存 =bar_t.tray=(一份)*:单例语义,=bar_init= 时用 =host_window()= 匹配决定哪个 output 渲染。
* 实现步骤
每步可独立编译,建议每步一个提交。
** 步骤 1契约层 + 调用点占位
签名变更与唯一调用点耦合,必须一起改才能编译。
- *新增 =src/interface/tray.h=*(纯声明,零 .c=tray_icons_change_cb_t==tray_api_t==host_window= / =icon_count= / =icon_size= / =place= / =set_listener=)、=tray_t==api= + =handle=)。只依赖 =interface/types.h=
- *=src/interface/backend.h=*=#include "interface/tray.h"==backend_bar_window_t= 内嵌 =tray_t tray==backend_create_bar_window==bool enable_tray=
- *=src/runtime/runtime.c=*:临时传 =enable_tray=false=
- *=src/backend/x11/backend.c=*:签名加参数,函数体先忽略,返回 =.tray = {0}=。
- 验证编译通过bar 行为不变。
** 步骤 2bar 框架扩展(零影响现有 3 个 item
为 tray item 铺路;钩子可空、=fixed_width= 默认 0现有 item 走原路径。
- *=include/zdwm/bar.h=*=zdwm_bar_item_type_t= 加可空钩子 =after_layout= / =after_draw=;新增 =zdwm_bar_item_layout_params_t=(仿 =zdwm_bar_click_params_t==item= / =cell_api= / =region= / =state==zdwm_bar_cell_api_t==cell_set_fixed_width==cell_get_region=
- *=src/bar/types.h=*=bar_cell_t==int32_t fixed_width=(默认 0 = 走文本测量,=p_clear= 后天然正确;<0 = 隐藏哨兵)。
- *=src/bar/cell.c=*:实现 =bar_cell_set_fixed_width= / =bar_cell_get_region=,注册进 =bar_cell_api=
- *=src/bar/bar.c=*=bar_output_layout= 三处宽度计算加 =fixed_width= 分支(>0 固定 / ==0 测量 / <0 隐藏);=bar_draw= 在 layout 后遍历调 =after_layout=、draw 后调 =after_draw=NULL 跳过)。
- 验证:编译 + 运行,*workspaces/binding/windows 三个 item 视觉与点击零变化*(关键回归点)。
** 步骤 3后端协议层核心
tray 能力全部 X11 细节,"已实现但未启用"。
- *新增 =src/backend/x11/tray.c=*tray host 状态 + 成为 =_NET_SYSTEM_TRAY_S0= owner + 广播 MANAGER + 容器子窗口reparent 到当前 bar+ 基础 XEMBEDreparent icon + =_XEMBED_EMBEDDED_NOTIFY=+ dock 请求处理 + =place(handle,x)=move 容器、configure 每个 icon 到 =x + i*icon_size=+ 填充 =static const tray_api_t==handle= 实际是 =backend_t*=)。=icon_size= 用 bar 窗口高度(单一数据源)。
- *=src/backend/x11/internal.h=*=ATOM_LIST= 追加 =_NET_SYSTEM_TRAY_S0= / =_NET_SYSTEM_TRAY_OPCODE= / =_NET_SYSTEM_TRAY_MESSAGE_DATA= / =_XEMBED= / =_XEMBED_INFO= / =Manager==backend_t= 加 tray host 字段单例host_window、icon 列表、listener、enabled
- *=src/backend/x11/event.c=*=handle_client_message=):开头拦截 tray 消息(=Manager==data.data32[1]==_NET_SYSTEM_TRAY_S0==_NET_SYSTEM_TRAY_OPCODE==REQUEST_DOCK=),处理后 =return false=(不产 =event_t=)。
- *=src/backend/x11/backend.c=*=backend_create_bar_window= 的 =enable_tray=true= 分支(创建/迁移 host 容器到此 bar 窗口,返回 =tray= 填 backend 的 api + =handle=backend==backend_destroy= 释放 trayselection、容器、icon 列表)。
- *=CMakeLists.txt=*:加 =src/backend/x11/tray.c=
- 验证编译通过。runtime 仍传 false行为不变。
** 步骤 4bar tray item
消费步骤 2 的框架 + 步骤 3 的契约tray 作为 right-side item 接入。
- *新增 =src/bar/tray.c=*(参考 =workspaces.c= 模式):=bar_tray= vtable = =create_state= / =update= / =on_click= / =after_layout= / =destroy_state=。=create_state= 拷贝 =tray_t= + =set_listener= 注册回调(置 dirty=update= 在 dirty 时 =set_cell_count(icon_count)= + 循环 =cell_set_fixed_width(i, icon_size)==on_click= 返回 =ACTION_NONE=icon 真实子窗口自收点击);=after_layout= 调 =place(handle, region.start)=。cell 不画 cairotext 空,被真实 icon 窗口盖住)。
- *=src/bar/bar.h=*=bar_t= 加 =tray_t tray=(一份,单例)。
- *=src/bar/bar.c=*=bar_init=):遍历 output对 =bar->tray.host_window()= 匹配自身 =window_id= 的 output 调 =bar_output_add_tray=right side其余不加。
- *=CMakeLists.txt=*:加 =src/bar/tray.c=。
- 验证编译通过。right side 仍空runtime 未注入 tray
** 步骤 5runtime/配置串起来(启用)
- *=include/zdwm/bar.h=*=zdwm_bar_config_t=):加 =int32_t tray_output_index=(默认 0 = 第一个 output
- *=src/runtime/runtime.c=*=runtime_init_bar==bool enable_tray = ((int32_t)i == runtime->bar.config.tray_output_index);= 传给 =backend_create_bar_window==runtime->bar.tray = bar_window.tray;=(每个返回都一样,存一份)。
- *=src/config/defaults.c=*=tray_output_index= 默认 0。
- 验证:编译 + 运行tray 启用。
* 风险点
- *after 钩子对现有 item*:现有三个 item 的 vtable 不显式初始化新钩子 → NULLC 部分初始化保证);=bar_draw= 调用前判 NULL。=fixed_width= 默认 0 走原测量路径。步骤 2 后必须确认三个 item 零变化。
- *selection owner 单例 vs per-output create*host 是 backend 单例;每次 =enable_tray=true= 把容器 reparent 到本次 bar 窗口,最终挂最后一个 true 的 output=host_window()= 返回当前挂载 window_id。
- *XEMBED 第一版只做基础*reparent + =EMBEDDED_NOTIFY=;不做焦点/激活/缩放。某些高级 applet要求焦点的输入法可能受限基础音量/网络 icon 无影响。
- *core 零感知*tray 消息在 =event.c= 拦截 =return false=,不进 policy/plan。icon 增删不触发 core 重排——icon 是真实 X 子窗口X server 自管绘制bar 重绘靠 timerfd 驱动region 变化经 =after_layout==place= 同步。
- *tray cell padding*=fixed_width = icon_size=,确保 tray item 的 cell padding 为 0避免 icon 间额外间距。
- *透明背景*bar 窗口已 ARGB=backend.c==window_get_visual(true)=tray 容器子窗口继承,第一版白送。
* 端到端验证
1. *每步编译*=cmake --build build=,零 warning项目 =-Wall=)。
2. *步骤 5 后运行*=./build/zdwm=
3. *tray icon 验证*:启动一个提供 tray icon 的 app=volumeicon= / =nm-applet= / =fcitx5= 等,不要用 stalonetray——它会抢 selection owner。验证icon 显示在 bar *右侧*、尺寸 = bar 高度无溢出、多 icon 横向无重叠、点击有响应、透明背景融入 bar。
4. *动态增删*:启动/退出 tray app观察 icon 实时增删 + 布局重排(经 =set_listener= 回调 → dirty → 下个 timerfd tick → =update= 重算 → =after_layout= 重排),无残留空位。
5. *回归*(每步都做,重点步骤 2workspaces tag 切换、binding 模式、windows 标题、bar 点击命中全部不变。
* 关键文件
- *新增*=src/interface/tray.h=(契约)、=src/backend/x11/tray.c=(协议核心)、=src/bar/tray.c=bar item
- *修改*=src/interface/backend.h==src/backend/x11/{internal.h,event.c,backend.c}==include/zdwm/bar.h==src/bar/{types.h,cell.c,bar.c,bar.h}==src/runtime/runtime.c==src/config/defaults.c==CMakeLists.txt=
- *复用*=workspaces.c=item 模板)、=zdwm_bar_click_params_t=layout params 打包先例)、=ATOM_LIST=atom 注册)、=window_get_visual(true)=ARGB=bar_cell_api= vtable 机制。

View File

@@ -2,26 +2,91 @@
#define ZDWM_ACTION_H
#include <stdint.h>
#include <zdwm/types.h>
#if defined(__cplusplus)
extern "C" {
#endif
typedef union zdwm_action_arg_t {
bool b;
int32_t i;
uint32_t ui;
const char *str;
} zdwm_action_arg_t;
typedef enum zdwm_action_type_t {
ZDWM_ACTION_NONE,
ZDWM_ACTION_SPAWN,
ZDWM_ACTION_QUIT,
ZDWM_ACTION_RAISE_OR_RUN,
typedef struct zdwm_action_ctx_t zdwm_action_ctx_t;
ZDWM_ACTION_OUTPUT_CYCLE,
ZDWM_ACTION_WORKSPACE_SWITCH,
ZDWM_ACTION_WORKSPACE_SWITCH_SAME_OUTPUT_BY_INDEX,
ZDWM_ACTION_LAYOUT_CYCLE,
ZDWM_ACTION_BINDING_MODE_CYCLE,
typedef void
zdwm_action_fn(const zdwm_action_ctx_t *ctx, const zdwm_action_arg_t *arg);
ZDWM_ACTION_WINDOW_TOGGLE_FULLSCREEN,
ZDWM_ACTION_WINDOW_TOGGLE_MAXIMIZE,
ZDWM_ACTION_WINDOW_TOGGLE_MINIMIZE,
ZDWM_ACTION_WINDOW_TOGGLE_FLOATING,
ZDWM_ACTION_WINDOW_TOGGLE_STICKY,
struct zdwm_action_ctx_t {
void (*spawn)(const char *command);
};
ZDWM_ACTION_WINDOW_FOCUS_CYCLE,
ZDWM_ACTION_WINDOW_KILL,
ZDWM_ACTION_WINDOW_SEND_TO_WORKSPACE_SAME_OUTPUT_BY_INDEX,
ZDWM_ACTION_WINDOW_CYCLE_OUTPUT,
ZDWM_ACTION_BAR_TOGGLE_VISIBILITY,
} zdwm_action_type_t;
typedef struct zdwm_action_data_spawn_t {
const char *command;
} zdwm_action_data_spawn_t;
typedef struct zdwm_action_data_quit_t {
bool restart;
} zdwm_action_data_quit_t;
typedef struct zdwm_action_data_raise_or_run_t {
const char *class_name;
const char *command;
} zdwm_action_data_raise_or_run_t;
typedef struct zdwm_action_data_delta_only_t {
int32_t delta;
} zdwm_action_data_delta_only_t;
typedef struct zdwm_action_data_index_only_t {
uint32_t index;
} zdwm_action_data_index_only_t;
typedef struct zdwm_action_data_switch_workspace_t {
zdwm_workspace_id_t workspace;
} zdwm_action_data_switch_workspace_t;
typedef struct zdwm_action_data_window_send_to_workspace_t {
uint32_t index;
bool switch_workspace;
} zdwm_action_data_window_send_to_workspace_t;
typedef struct zdwm_action_data_window_cycle_output_t {
int32_t delta;
bool keep_focus;
} zdwm_action_data_window_cycle_output_t;
typedef struct zdwm_action_t {
zdwm_action_type_t type;
union {
zdwm_action_data_spawn_t spawn;
zdwm_action_data_quit_t quit;
zdwm_action_data_raise_or_run_t raise_or_run;
zdwm_action_data_delta_only_t output_cycle;
zdwm_action_data_index_only_t workspace_switch_same_output_by_index;
zdwm_action_data_switch_workspace_t switch_workspace;
zdwm_action_data_delta_only_t layout_cycle;
zdwm_action_data_delta_only_t binding_mode_cycle;
zdwm_action_data_delta_only_t window_focus_cycle;
zdwm_action_data_window_send_to_workspace_t
window_send_to_workspace_same_output_by_index;
zdwm_action_data_window_cycle_output_t window_cycle_output;
} as;
} zdwm_action_t;
#if defined(__cplusplus)
}

111
include/zdwm/bar.h Normal file
View File

@@ -0,0 +1,111 @@
#ifndef ZDWM_BAR_H
#define ZDWM_BAR_H
#include <stddef.h>
#include <stdint.h>
#include <zdwm/action.h>
#include <zdwm/types.h>
#if defined(__cplusplus)
extern "C" {
#endif
typedef struct zdwm_bar_config_t {
int32_t height;
bool show_top; /* 如果为 false 则 bar 显示在屏幕底部 */
uint8_t padding_x;
uint16_t fps;
const char *font_family;
uint32_t font_size;
uint32_t dpi;
const char *bg;
const char *fg;
uint32_t tag_cell_padding_x;
uint32_t tag_indicator_width;
const char *tag_bg;
const char *tag_fg;
const char *tag_active_bg;
const char *tag_active_fg;
const char *tag_urgent_bg;
const char *tag_urgent_fg;
const char *layout_bg;
const char *layout_fg;
bool binding_show_default;
uint32_t binding_padding_x;
const char *binding_mode_bg;
const char *binding_mode_fg;
int32_t tray_output_index;
uint32_t window_padding_x;
const char *window_bg;
const char *window_fg;
const char *window_focused_bg;
const char *window_focused_fg;
} zdwm_bar_config_t;
typedef struct zdwm_bar_item_t zdwm_bar_item_t;
typedef struct zdwm_bar_x_region_t {
int32_t start;
int32_t end;
} zdwm_bar_x_region_t;
typedef struct zdwm_bar_cell_api_t {
size_t (*get_cell_count)(zdwm_bar_item_t *item);
void (*set_cell_count)(zdwm_bar_item_t *item, size_t count);
void (*cell_set_text)(zdwm_bar_item_t *item, size_t index, const char *text);
void (*cell_set_bg)(zdwm_bar_item_t *item, size_t index, const char *color);
void (*cell_set_fg)(zdwm_bar_item_t *item, size_t index, const char *color);
void (*cell_set_icon)(zdwm_bar_item_t *item, size_t index, zdwm_icon_t icon);
void (*cell_set_indicator)(zdwm_bar_item_t *item, size_t index, bool show);
void (*cell_set_fixed_width)(
zdwm_bar_item_t *item,
size_t index,
int32_t width
);
zdwm_bar_x_region_t (*cell_get_region)(zdwm_bar_item_t *item, size_t index);
} zdwm_bar_cell_api_t;
typedef struct zdwm_bar_click_params_t {
zdwm_bar_item_t *item;
size_t cell_index;
int32_t x;
zdwm_modifier_mask_t modifiers;
zdwm_button_t button;
void *state;
} zdwm_bar_click_params_t;
typedef struct zdwm_bar_item_hook_params_t {
zdwm_bar_item_t *item;
const zdwm_bar_cell_api_t *cell_api;
zdwm_bar_x_region_t region;
void *state;
} zdwm_bar_item_hook_params_t;
typedef struct zdwm_bar_item_type_t {
void *(*create_state)(zdwm_output_id_t output_id, void *config);
void (*update)(
zdwm_bar_item_t *item,
const zdwm_bar_cell_api_t *cells,
void *state
);
void (*after_layout)(const zdwm_bar_item_hook_params_t *params);
void (*after_draw)(const zdwm_bar_item_hook_params_t *params);
zdwm_action_t (*on_click)(zdwm_bar_click_params_t *params);
void (*destroy_state)(void *state);
zdwm_bar_item_t *instance;
uint32_t update_interval_ms;
} zdwm_bar_item_type_t;
#if defined(__cplusplus)
}
#endif
#endif /* ZDWM_BAR_H */

View File

@@ -5,7 +5,9 @@
#include <stddef.h>
#include <stdint.h>
#include <zdwm/action.h>
#include <zdwm/bar.h>
#include <zdwm/layout.h>
#include <zdwm/listeners.h>
#include <zdwm/rules.h>
#include <zdwm/types.h>
@@ -111,8 +113,7 @@ typedef struct zdwm_api_t {
zdwm_config_builder_t *builder,
zdwm_binding_mode_id_t bind_mode,
const char *key_sequence,
zdwm_action_fn action_fn,
zdwm_action_arg_t action_arg
zdwm_action_t action
);
/**
@@ -140,6 +141,88 @@ typedef struct zdwm_api_t {
zdwm_config_builder_t *builder,
zdwm_binding_mode_id_t mode_id
);
/**
* @brief 设置边框配置信息
*
* @param builder 配置构建上下文
* @param width 边框宽度
* @param normal 普通窗口边框颜色
* @param focused 焦点窗口边框颜色
*/
void (*set_border_config)(
zdwm_config_builder_t *builder,
uint32_t width,
const char *normal,
const char *focused
);
/**
* @brief 设置窗口移动和调整大小时的刷新率
*
* @param builder 配置构建上下文
* @param fps 刷新帧率,最小 10 帧,若设置小于 10 帧会设置为 10 帧
*/
void (*set_interaction_fps)(zdwm_config_builder_t *builder, uint32_t fps);
/** @name Listener 注册
* @{ */
void (*subscribe_current_output)(
zdwm_config_builder_t *builder,
zdwm_current_output_id_listener *fn,
void *user_data
);
void (*subscribe_initial_workspace_list)(
zdwm_config_builder_t *builder,
zdwm_initial_workspace_list *fn,
void *user_data
);
void (*subscribe_workspace_active)(
zdwm_config_builder_t *builder,
zdwm_workspace_active_updated *fn,
void *user_data
);
void (*subscribe_layout)(
zdwm_config_builder_t *builder,
zdwm_layout_notify *fn,
void *user_data
);
void (*subscribe_binding_mode)(
zdwm_config_builder_t *builder,
zdwm_binding_mode_notify *fn,
void *user_data
);
void (*subscribe_initial_window_list)(
zdwm_config_builder_t *builder,
zdwm_initial_window_list *fn,
void *user_data
);
void (*subscribe_window_added)(
zdwm_config_builder_t *builder,
zdwm_window_added *fn,
void *user_data
);
void (*subscribe_window_updated)(
zdwm_config_builder_t *builder,
zdwm_window_updated *fn,
void *user_data
);
void (*subscribe_window_removed)(
zdwm_config_builder_t *builder,
zdwm_window_removed *fn,
void *user_data
);
/** @} */
/** @name bar 配置
* @{ */
void (*set_bar_config)(
zdwm_config_builder_t *builder,
zdwm_bar_config_t config
);
/** @} */
} zdwm_api_t;
/**

View File

@@ -56,7 +56,7 @@ zdwm_layout_result_push(zdwm_layout_result_t *result, zdwm_layout_item_t item) {
realloc(result->items, capacity * sizeof(*result->items));
if (!items) abort();
result->items = items;
result->items = items;
result->item_capacity = capacity;
}

70
include/zdwm/listeners.h Normal file
View File

@@ -0,0 +1,70 @@
#ifndef ZDWM_LISTENERS_H
#define ZDWM_LISTENERS_H
#include <stddef.h>
#include <zdwm/types.h>
#if defined(__cplusplus)
extern "C" {
#endif
typedef void
zdwm_current_output_id_listener(zdwm_output_id_t output_id, void *user_data);
typedef struct zdwm_workspace_t {
zdwm_output_id_t output_id;
zdwm_workspace_id_t id;
const char *name; /* 不持有内存 */
} zdwm_workspace_t;
typedef void zdwm_initial_workspace_list(
const zdwm_workspace_t *list,
size_t count,
void *user_data
);
typedef void zdwm_workspace_active_updated(
zdwm_output_id_t output_id,
zdwm_workspace_id_t workspace_id,
void *user_data
);
typedef struct zdwm_layout_notify_t {
zdwm_workspace_id_t workspace;
zdwm_layout_id_t id;
const char *name; /* 不持有内存 */
const char *symbol; /* 不持有内存 */
const char *description; /* 不持有内存 */
} zdwm_layout_notify_t;
typedef void zdwm_layout_notify(zdwm_layout_notify_t layout, void *user_data);
typedef struct zdwm_binding_mode_notify_t {
bool is_default_mode;
zdwm_binding_mode_id_t id;
const char *name; /* 不持有内存 */
} zdwm_binding_mode_notify_t;
typedef void zdwm_binding_mode_notify(
zdwm_binding_mode_notify_t binding_mode,
void *user_data
);
typedef struct zdwm_window_t {
zdwm_workspace_id_t workspace;
zdwm_window_id_t id;
const char *title; /* 不持有内存 */
bool focused;
bool urgent;
bool skip_taskbar;
} zdwm_window_t;
typedef void zdwm_initial_window_list(
const zdwm_window_t *list,
size_t count,
void *user_data
);
typedef void zdwm_window_added(const zdwm_window_t *window, void *user_data);
typedef void zdwm_window_updated(const zdwm_window_t *window, void *user_data);
typedef void zdwm_window_removed(zdwm_window_id_t window_id, void *user_data);
#if defined(__cplusplus)
}
#endif
#endif /* ZDWM_LISTENERS_H */

View File

@@ -7,6 +7,7 @@
extern "C" {
#endif
typedef uint32_t zdwm_output_id_t;
typedef uint32_t zdwm_layout_id_t;
typedef uint32_t zdwm_window_id_t;
typedef uint32_t zdwm_workspace_id_t;
@@ -17,6 +18,28 @@ typedef uint32_t zdwm_binding_mode_id_t;
#define ZDWM_WORKSPACE_ID_INVALID ((zdwm_workspace_id_t)UINT32_MAX)
#define ZDWM_BINDING_MODE_ID_INVALID ((zdwm_binding_mode_id_t)UINT32_MAX)
typedef enum zdwm_button_t {
ZDWM_BUTTON_NONE,
ZDWM_BUTTON_LEFT,
ZDWM_BUTTON_RIGHT,
ZDWM_BUTTON_MIDDLE,
} zdwm_button_t;
/* clang-format off */
typedef enum zdwm_modifier_bit_t {
ZDWM_MOD_NONE = 0u,
ZDWM_MOD_SHIFT = 1u << 0,
ZDWM_MOD_CONTROL = 1u << 1,
ZDWM_MOD_1 = 1u << 2,
ZDWM_MOD_2 = 1u << 3,
ZDWM_MOD_3 = 1u << 4,
ZDWM_MOD_4 = 1u << 5,
ZDWM_MOD_5 = 1u << 6,
} zdwm_modifier_bit_t;
/* clang-format on */
typedef uint32_t zdwm_modifier_mask_t;
typedef struct zdwm_rect_t {
int32_t x;
int32_t y;
@@ -29,6 +52,19 @@ typedef struct zdwm_output_info_t {
zdwm_rect_t geometry;
} zdwm_output_info_t;
typedef enum zdwm_icon_type_t {
ZDWM_ICON_TEXT,
ZDWM_ICON_IMAGE,
} zdwm_icon_type_t;
typedef struct zdwm_icon_t {
zdwm_icon_type_t type;
union {
const char *text;
const char *image_path;
} as;
} zdwm_icon_t;
#if defined(__cplusplus)
}
#endif

View File

@@ -1,16 +0,0 @@
# -*- mode: yaml; -*-
---
BasedOnStyle: Google
IndentWidth: 2
ConstructorInitializerIndentWidth: 2
ContinuationIndentWidth: 2
---
Language: C
DerivePointerAlignment: false
PointerAlignment: Right
---
Language: Cpp
DerivePointerAlignment: false
PointerAlignment: Right
---

View File

@@ -1,158 +0,0 @@
#include "action.h"
#include <glib.h>
#include <string.h>
#include "audio.h"
#include "client.h"
#include "monitor.h"
#include "types.h"
#include "wm.h"
#include "xcursor.h"
void focus_client_in_same_tag(const user_action_arg_t *arg) {
bool next = arg->b;
tag_t *tag = wm.current_monitor->selected_tag;
task_in_tag_t *task = nullptr;
if (next) {
task = client_get_next_task_in_tag(wm.client_focused, tag);
} else {
task = client_get_previous_task_in_tag(wm.client_focused, tag);
}
if (!task) return;
client_stack_raise(task->client);
client_focus(task->client);
}
void select_tag_of_current_monitor(const user_action_arg_t *arg) {
monitor_select_tag(wm.current_monitor, arg->ui);
}
void send_client_to_tag(const user_action_arg_t *arg) {
if (wm.client_focused) client_send_to_tag(wm.client_focused, arg->ui);
}
void send_client_to_next_monitor(const user_action_arg_t *arg) {
if (!wm.client_focused) return;
monitor_t *next_monitor = wm_get_next_monitor(wm.client_focused->monitor);
if (wm.client_focused->monitor == next_monitor) return;
client_send_to_monitor(wm.client_focused, next_monitor);
}
void focus_next_monitor(const user_action_arg_t *arg) {
monitor_t *next_monitor = wm_get_next_monitor(wm.current_monitor);
if (next_monitor == wm.current_monitor) return;
wm_set_current_monitor(next_monitor, true);
if (wm.current_monitor->selected_tag->task_list) {
monitor_deal_focus(wm.current_monitor);
}
}
void spawn(const user_action_arg_t *arg) {
const char *cmd = (const char *)arg->ptr;
if (cmd == nullptr || strlen(cmd) == 0) return;
g_spawn_command_line_async((const char *)arg->ptr, nullptr);
}
void quit(const user_action_arg_t *arg) {
const bool restart = arg->b;
if (restart) {
wm_restart();
} else {
wm_quit();
}
}
void raise_or_run(const user_action_arg_t *arg) {
const char *class = ((const char **)arg->ptr)[0];
client_t *client = client_get_next_by_class(wm.client_focused, class);
if (client) {
bool monitor_changed = client->monitor != wm.current_monitor;
bool tag_changed = client->tags != client->monitor->selected_tag->mask;
if (monitor_changed || tag_changed) {
point_t point = monitor_changed
? monitor_get_restore_cursor_point(client->monitor)
: xcursor_query_pointer_position();
wm_ignore_enter_notify_at_point(point);
}
wm_set_current_monitor(client->monitor, true);
monitor_select_tag(client->monitor, client->tags);
client_stack_raise(client);
client_focus(client);
return;
}
const char *command = ((const char **)arg->ptr)[1];
user_action_arg_t arguments = {.ptr = command};
spawn(&arguments);
}
void toggle_mute(const user_action_arg_t *arg) {
if (wm.status->pulse_context) toggle_pulse_mute(wm.status->pulse_context);
}
void change_volume(const user_action_arg_t *arg) {
if (wm.status->pulse_context) {
change_pulse_volume(wm.status->pulse_context, arg->i);
}
}
void toggle_client_floating(const user_action_arg_t *arg) {
if (!wm.client_focused) return;
client_set_floating(wm.client_focused, !wm.client_focused->floating);
}
void toggle_client_fullscreen(const user_action_arg_t *arg) {
if (!wm.client_focused) return;
client_set_fullscreen(wm.client_focused, !wm.client_focused->fullscreen);
}
void toggle_client_maximize(const user_action_arg_t *arg) {
if (!wm.client_focused) return;
client_set_maximize(wm.client_focused, !wm.client_focused->maximize);
}
void toggle_client_minimize(const user_action_arg_t *arg) {
client_t *client = wm.client_focused;
if (arg->ptr) client = (client_t *)arg->ptr;
if (!client) return;
client_set_minimize(client, !client->minimize);
}
void kill_client(const user_action_arg_t *arg) {
if (wm.client_focused) {
client_kill(wm.client_focused);
}
}
/**
* @brief 根据 client 的当前状态切换 client 下一状态
* @description 如果当前 client 被遮挡了或者最小化了,则显示出来,否则最小化
*/
void change_client_state_dwim(const user_action_arg_t *arg) {
client_t *client = (client_t *)arg->ptr;
if (!client) return;
if (client != wm.client_stack_list &&
client_get_obscured_state(client, wm.client_stack_list) !=
obscured_none) {
client_stack_raise(client);
client_focus(client);
return;
}
client_set_minimize(client, !client->minimize);
if (!client->minimize) client_stack_raise(client);
}

View File

@@ -1,67 +0,0 @@
#pragma once
#include <stdint.h>
#include <xcb/xproto.h>
typedef union user_action_arg_t {
bool b;
int32_t i;
uint32_t ui;
const void *ptr;
} user_action_arg_t;
typedef enum click_area_t {
click_none,
click_tag,
click_client_name,
} click_area_t;
typedef enum modifier_t {
modifier_none = 0,
modifier_shift = XCB_MOD_MASK_SHIFT,
modifier_control = XCB_MOD_MASK_CONTROL,
modifier_alt = XCB_MOD_MASK_1,
modifier_super = XCB_MOD_MASK_4,
modifier_any = XCB_MOD_MASK_ANY,
} modifier_t;
typedef uint16_t modifier_value_t;
typedef enum button_index_t {
button_any = XCB_BUTTON_INDEX_ANY,
button_left = XCB_BUTTON_INDEX_1,
button_right = XCB_BUTTON_INDEX_2,
button_middle = XCB_BUTTON_INDEX_3,
} button_index_t;
typedef struct button_t {
click_area_t click_area : 16;
modifier_value_t modifiers;
button_index_t button;
void (*func)(const user_action_arg_t *arg);
const user_action_arg_t arg;
} button_t;
typedef struct keyboard_t {
modifier_value_t modifiers;
xcb_keysym_t keysym;
void (*func)(const user_action_arg_t *arg);
const user_action_arg_t arg;
} keyboard_t;
void focus_client_in_same_tag(const user_action_arg_t *arg);
void select_tag_of_current_monitor(const user_action_arg_t *arg);
void send_client_to_tag(const user_action_arg_t *arg);
void send_client_to_next_monitor(const user_action_arg_t *arg);
void focus_next_monitor(const user_action_arg_t *arg);
void spawn(const user_action_arg_t *arg);
void quit(const user_action_arg_t *arg);
void raise_or_run(const user_action_arg_t *arg);
void toggle_mute(const user_action_arg_t *arg);
void change_volume(const user_action_arg_t *arg);
void toggle_client_floating(const user_action_arg_t *arg);
void toggle_client_fullscreen(const user_action_arg_t *arg);
void toggle_client_maximize(const user_action_arg_t *arg);
void toggle_client_minimize(const user_action_arg_t *arg);
void kill_client(const user_action_arg_t *arg);
void change_client_state_dwim(const user_action_arg_t *arg);

View File

@@ -1,5 +0,0 @@
#pragma once
#define APP_NAME "@APP_NAME@"
#cmakedefine HAS_EXECINFO

View File

@@ -1,34 +0,0 @@
_XKB_RULES_NAMES
_XROOTPMAP_ID
ESETROOT_PMAP_ID
WM_CHANGE_STATE
WM_DELETE_WINDOW
WM_PROTOCOLS
WM_TAKE_FOCUS
WM_NAME
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
_NET_WM_STATE_FULLSCREEN
_NET_WM_STATE_MAXIMIZED_HORZ
_NET_WM_STATE_MAXIMIZED_VERT
_NET_WM_STATE_SKIP_TASKBAR
_NET_WM_WINDOW_TYPE
_NET_WM_WINDOW_TYPE_DIALOG
_NET_SUPPORTED
_NET_SUPPORTING_WM_CHECK

View File

@@ -1,39 +0,0 @@
#include "atoms.h"
#include <stdint.h>
#include <string.h>
#include <xcb/xproto.h>
#include "atoms-intern.h"
#include "utils.h"
#include "wm.h"
void atoms_init(xcb_connection_t *conn) {
xcb_intern_atom_cookie_t cookies[countof(ATOM_LIST)];
for (int i = 0; i < countof(ATOM_LIST); i++) {
atom_item_t atom = ATOM_LIST[i];
cookies[i] = xcb_intern_atom_unchecked(conn, false, atom.len, atom.name);
}
atom_item_t *atom = nullptr;
uint32_t supported_length = 0;
xcb_atom_t supported_list[countof(ATOM_LIST)] = {XCB_ATOM_NONE};
xcb_intern_atom_reply_t *reply = nullptr;
for (int i = 0; i < countof(ATOM_LIST); i++) {
reply = xcb_intern_atom_reply(conn, cookies[i], nullptr);
if (reply) {
atom = &ATOM_LIST[i];
*atom->atom = reply->atom;
p_delete(&reply);
if (strstr(atom->name, "_NET_") == atom->name) {
supported_list[supported_length++] = *atom->atom;
}
}
}
xcb_change_property(wm.xcb_conn, XCB_PROP_MODE_REPLACE, wm.screen->root,
_NET_SUPPORTED, XCB_ATOM_ATOM, 32, supported_length,
supported_list);
}

View File

@@ -1,5 +0,0 @@
#pragma once
#include <xcb/xcb.h>
void atoms_init(xcb_connection_t *conn);

View File

@@ -1,182 +0,0 @@
#include "audio.h"
#include <glib.h>
#include <math.h>
#include <pulse/context.h>
#include <pulse/def.h>
#include <pulse/glib-mainloop.h>
#include <pulse/introspect.h>
#include <pulse/mainloop-api.h>
#include <pulse/proplist.h>
#include <pulse/subscribe.h>
#include <pulse/volume.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include "app.h"
#include "utils.h"
struct pulse_context_t {
GMainContext *g_context;
pulse_notify_t callback;
pa_glib_mainloop *loop;
pa_context *context;
pa_cvolume volume;
pulse_t pulse;
uint32_t index;
bool inited;
};
static void clean_pulse_context(pulse_context_t *context, bool full_cleanup);
static void init_pulse_context(pulse_context_t *context, bool create_loop);
static void state_callback(pa_context *context, void *userdata);
static void subscribe_callback(pa_context *context,
pa_subscription_event_type_t t, uint32_t index,
void *userdata);
static void server_info_callback(pa_context *context,
const pa_server_info *info, void *userdata);
static void sink_info_callback(pa_context *context, const pa_sink_info *info,
int eol, void *userdata);
static void parse_sink(const pa_sink_info *info, pulse_t *pulse);
pulse_context_t *init_pulse(GMainContext *context, pulse_notify_t callback) {
pulse_context_t *ctx = p_new(pulse_context_t, 1);
ctx->g_context = context;
ctx->callback = callback;
init_pulse_context(ctx, true);
return ctx;
}
void change_pulse_volume(pulse_context_t *context, int step) {
if (!context->inited || !step || step > 100 || step < -100) return;
pa_cvolume vol = context->volume;
if (step > 0) {
pa_cvolume_inc(&vol, PA_VOLUME_NORM * step / 100);
if (pa_cvolume_max(&vol) > PA_VOLUME_NORM) {
pa_cvolume_set(&vol, vol.channels, PA_VOLUME_NORM);
}
} else {
pa_cvolume_dec(&vol, PA_VOLUME_NORM * (-step) / 100);
if (pa_cvolume_min(&vol) < PA_VOLUME_MUTED) {
pa_cvolume_set(&vol, vol.channels, PA_VOLUME_MUTED);
}
}
pa_context *ctx = context->context;
uint32_t index = context->index;
pa_context_set_sink_volume_by_index(ctx, index, &vol, nullptr, nullptr);
}
void toggle_pulse_mute(pulse_context_t *context) {
if (!context->inited) return;
pa_context *ctx = context->context;
uint32_t index = context->index;
bool mute = !context->pulse.mute;
pa_context_set_sink_mute_by_index(ctx, index, mute, nullptr, nullptr);
}
void clean_pulse(pulse_context_t *context) {
clean_pulse_context(context, true);
p_delete(&context);
}
void clean_pulse_context(pulse_context_t *context, bool full_cleanup) {
memset(&context->pulse, 0, sizeof(context->pulse));
if (context->callback) context->callback(&context->pulse);
if (context->context) {
pa_context_set_state_callback(context->context, nullptr, nullptr);
pa_context_set_subscribe_callback(context->context, nullptr, nullptr);
pa_context_disconnect(context->context);
pa_context_unref(context->context);
context->context = nullptr;
}
context->inited = false;
if (full_cleanup && context->loop) {
pa_glib_mainloop_free(context->loop);
context->loop = nullptr;
}
}
void init_pulse_context(pulse_context_t *context, bool create_loop) {
if (create_loop) {
if (context->loop) pa_glib_mainloop_free(context->loop);
context->loop = pa_glib_mainloop_new(context->g_context);
}
pa_mainloop_api *api = pa_glib_mainloop_get_api(context->loop);
context->context = pa_context_new(api, APP_NAME "-audio-status");
pa_context_connect(context->context, nullptr, PA_CONTEXT_NOFAIL, nullptr);
pa_context_set_state_callback(context->context, state_callback, context);
}
void state_callback(pa_context *context, void *userdata) {
pulse_context_t *ctx = userdata;
pa_context_state_t state = pa_context_get_state(context);
if (PA_CONTEXT_IS_GOOD(state)) {
pa_context_set_subscribe_callback(context, subscribe_callback, ctx);
pa_context_subscribe(context, PA_SUBSCRIPTION_MASK_SINK, nullptr, nullptr);
pa_context_get_server_info(context, server_info_callback, ctx);
} else if (state == PA_CONTEXT_FAILED) {
clean_pulse_context(ctx, false);
init_pulse_context(ctx, false);
}
}
void subscribe_callback(pa_context *context, pa_subscription_event_type_t t,
uint32_t index, void *userdata) {
auto facility = t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK;
if (facility != PA_SUBSCRIPTION_EVENT_SINK) return;
pa_subscription_event_type_t event = t & PA_SUBSCRIPTION_EVENT_TYPE_MASK;
if (event == PA_SUBSCRIPTION_EVENT_CHANGE) {
pa_context_get_server_info(context, server_info_callback, userdata);
}
}
void server_info_callback(pa_context *context, const pa_server_info *info,
void *userdata) {
pa_context_get_sink_info_by_name(context, info->default_sink_name,
sink_info_callback, userdata);
}
void sink_info_callback(pa_context *context, const pa_sink_info *info, int eol,
void *userdata) {
if (eol != 0 || !info) return;
pulse_context_t *ctx = userdata;
ctx->inited = true;
ctx->volume = info->volume;
ctx->index = info->index;
parse_sink(info, &ctx->pulse);
if (ctx->callback) ctx->callback(&ctx->pulse);
}
static inline int volume_to_percent(pa_volume_t volume) {
return (int)round(((double)volume) * 100 / PA_VOLUME_NORM);
}
void parse_sink(const pa_sink_info *info, pulse_t *pulse) {
size_t size = sizeof(pulse->device_name);
pulse->mute = info->mute;
pulse->volume_percent = volume_to_percent(pa_cvolume_avg(&info->volume));
strncpy(pulse->device_name, info->description, size);
const char *key = nullptr;
void *state = nullptr;
while ((key = pa_proplist_iterate(info->proplist, &state)) != nullptr) {
if (strcmp(key, "api.alsa.path") != 0 &&
strcmp(key, "device.description") != 0) {
continue;
}
const char *value = pa_proplist_gets(info->proplist, key);
size_t len = strnlen(value, size);
if (len && (len < strnlen(pulse->device_name, size))) {
strncpy(pulse->device_name, value, size);
pulse->device_name[size - 1] = '\0';
}
}
}

View File

@@ -1,19 +0,0 @@
#pragma once
#include <glib.h>
#include <stdint.h>
typedef struct pulse_context_t pulse_context_t;
typedef struct pulse_t {
int volume_percent;
bool mute;
char device_name[255];
} pulse_t;
typedef void (*pulse_notify_t)(pulse_t *pulse);
pulse_context_t *init_pulse(GMainContext *context, pulse_notify_t callback);
void change_pulse_volume(pulse_context_t *context, int step);
void toggle_pulse_mute(pulse_context_t *context);
void clean_pulse(pulse_context_t *context);

View File

@@ -1 +0,0 @@
../../.clang-format

View File

@@ -4,8 +4,8 @@
#include <stdint.h>
#include "base/memory.h"
#include "core/backend.h"
#include "core/types.h"
#include "interface/backend.h"
#include "interface/types.h"
static inline int32_t right(rect_t a) { return a.x + a.width; }
static inline int32_t bottom(rect_t a) { return a.y + a.height; }
@@ -42,14 +42,14 @@ output_remove_duplication(const output_info_t *output, const size_t count) {
}
output_info_t *list = p_new(output_info_t, amount);
amount = 0;
amount = 0;
for (size_t i = 0; i < count; i++) {
if (remove[i]) continue;
list[amount++] = output[i];
}
p_delete(&remove);
backend_detect_t *detect = p_new(backend_detect_t, 1);
detect->outputs = list;
detect->output_count = amount;
detect->outputs = list;
detect->output_count = amount;
return detect;
}

View File

@@ -2,8 +2,7 @@
#include <stddef.h>
#include "core/backend.h"
#include "core/types.h"
#include "interface/backend.h"
backend_detect_t *
output_remove_duplication(const output_info_t *output, const size_t count);

View File

@@ -1,5 +1,7 @@
#include "core/backend.h"
#include "interface/backend.h"
#include <cairo-xcb.h>
#include <cairo.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
@@ -9,19 +11,22 @@
#include <xcb/randr.h>
#include <xcb/xcb.h>
#include <xcb/xcb_aux.h>
#include <xcb/xcb_icccm.h>
#include <xcb/xcb_keysyms.h>
#include <xcb/xfixes.h>
#include <xcb/xinerama.h>
#include <xcb/xproto.h>
#include "backend/output_utils.h"
#include "backend/x11/cursor.h"
#include "backend/x11/tray.h"
#include "backend/x11/window.h"
#include "base/app.h"
#include "base/array.h"
#include "base/log.h"
#include "base/macros.h"
#include "base/memory.h"
#include "core/plan.h"
#include "core/types.h"
#include "common/window.h"
#include "internal.h"
typedef struct atom_item_t {
@@ -32,7 +37,7 @@ typedef struct atom_item_t {
static void atoms_init(backend_t *backend) {
xcb_connection_t *conn = backend->conn;
atoms_t *atoms = &backend->atoms;
atoms_t *atoms = &backend->atoms;
#define ATOM_ITEM(name) {#name, sizeof(#name) - 1, &atoms->name},
atom_item_t atom_list[] = {ATOM_LIST(ATOM_ITEM)};
@@ -50,71 +55,85 @@ static void atoms_init(backend_t *backend) {
if (!reply) continue;
atom_item_t *atom = &atom_list[i];
*atom->atom = reply->atom;
*atom->atom = reply->atom;
p_delete(&reply);
}
#define EWMH_ITEM(name) atoms->name,
xcb_atom_t ewmh_supported_atoms[] = {EWMH_ATOMS(EWMH_ITEM)};
#undef EWMH_ITEM
auto root = backend->screen->root;
xcb_change_property(
conn,
XCB_PROP_MODE_REPLACE,
root,
atoms->_NET_SUPPORTED,
XCB_ATOM_ATOM,
32,
countof(ewmh_supported_atoms),
ewmh_supported_atoms
);
}
static void create_wm_check_window(backend_t *backend) {
xcb_connection_t *conn = backend->conn;
xcb_window_t root = backend->screen->root;
uint8_t depth = backend->screen->root_depth;
xcb_visualid_t v = backend->screen->root_visual;
uint16_t _c = XCB_WINDOW_CLASS_COPY_FROM_PARENT;
uint32_t m = XCB_NONE;
void *p = nullptr;
xcb_window_t root = backend->screen->root;
uint8_t depth = backend->screen->root_depth;
xcb_visualid_t v = backend->screen->root_visual;
uint16_t _c = XCB_WINDOW_CLASS_COPY_FROM_PARENT;
uint32_t m = XCB_NONE;
void *p = nullptr;
xcb_window_t win = xcb_generate_id(conn);
xcb_create_window(conn, depth, win, root, -1, -1, 1, 1, 0, _c, v, m, p);
window_set_class_instance(conn, win);
#define NAME "zdwm"
window_set_name_static(conn, win, NAME);
uint8_t mode = XCB_PROP_MODE_REPLACE;
window_set_name_static(conn, win, APP_NAME);
uint8_t mode = XCB_PROP_MODE_REPLACE;
xcb_atom_t prop = backend->atoms._NET_WM_NAME;
xcb_atom_t type = backend->atoms.UTF8_STRING;
xcb_change_property(conn, mode, win, prop, type, 8, sizeof(NAME) - 1, NAME);
#undef NAME
auto data_len = sizeof(APP_NAME) - 1;
xcb_change_property(conn, mode, win, prop, type, 8, data_len, APP_NAME);
prop = backend->atoms._NET_SUPPORTING_WM_CHECK;
type = XCB_ATOM_WINDOW;
xcb_change_property(conn, mode, win, prop, type, 32, 1, &win);
xcb_change_property(conn, mode, root, prop, type, 32, 1, &win);
prop = backend->atoms._NET_WM_PID;
type = XCB_ATOM_CARDINAL;
prop = backend->atoms._NET_WM_PID;
type = XCB_ATOM_CARDINAL;
pid_t pid = getpid();
xcb_change_property(conn, mode, win, prop, type, 32, 1, &pid);
}
static void create_no_focus_window(backend_t *backend) {
xcb_connection_t *conn = backend->conn;
xcb_window_t root = backend->screen->root;
uint8_t d = backend->screen->root_depth;
xcb_visualid_t v = backend->screen->root_visual;
uint16_t _c = XCB_WINDOW_CLASS_COPY_FROM_PARENT;
xcb_window_t root = backend->screen->root;
uint8_t d = backend->screen->root_depth;
xcb_visualid_t v = backend->screen->root_visual;
uint16_t _c = XCB_WINDOW_CLASS_COPY_FROM_PARENT;
xcb_window_t win = xcb_generate_id(conn);
uint32_t m = XCB_CW_BACK_PIXEL | XCB_CW_BORDER_PIXEL |
uint32_t m = XCB_CW_BACK_PIXEL | XCB_CW_BORDER_PIXEL |
XCB_CW_OVERRIDE_REDIRECT | XCB_CW_COLORMAP;
const xcb_create_window_value_list_t p = {
.override_redirect = true,
.background_pixel = backend->screen->black_pixel,
.border_pixel = backend->screen->black_pixel,
.colormap = backend->screen->default_colormap,
.background_pixel = backend->screen->black_pixel,
.border_pixel = backend->screen->black_pixel,
.colormap = backend->screen->default_colormap,
};
xcb_create_window_aux(conn, d, win, root, -1, -1, 1, 1, 0, _c, v, m, &p);
window_set_class_instance(conn, win);
window_set_name_static(conn, win, "zdwm no input window");
window_set_name_static(conn, win, APP_NAME " no input window");
xcb_map_window(conn, win);
backend->window_no_focus = win;
}
backend_t *backend_create(const char *display_name) {
int screen_num = 0;
int screen_num = 0;
xcb_connection_t *conn = xcb_connect(display_name, &screen_num);
int xcb_conn_error = xcb_connection_has_error(conn);
int xcb_conn_error = xcb_connection_has_error(conn);
if (xcb_conn_error) {
fatal("cannot open display %s, error %d", display_name, xcb_conn_error);
}
@@ -123,7 +142,7 @@ backend_t *backend_create(const char *display_name) {
if (!screen) fatal("cannot get screen info");
xcb_window_t root = screen->root;
uint32_t mask = XCB_CW_EVENT_MASK;
uint32_t mask = XCB_CW_EVENT_MASK;
const xcb_params_cw_t params = {
.event_mask = XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT,
};
@@ -137,9 +156,9 @@ backend_t *backend_create(const char *display_name) {
}
backend_t *backend = p_new(backend_t, 1);
backend->conn = conn;
backend->screen = screen;
backend->screenp = screen_num;
backend->conn = conn;
backend->screen = screen;
backend->screenp = screen_num;
xcb_prefetch_extension_data(conn, &xcb_xfixes_id);
const xcb_query_extension_reply_t *query =
@@ -163,36 +182,44 @@ backend_t *backend_create(const char *display_name) {
backend->key_symbols = xcb_key_symbols_alloc(conn);
cursor_init(backend);
atoms_init(backend);
create_wm_check_window(backend);
create_no_focus_window(backend);
window_change_cursor(backend, root, ZDWM_CURSOR_NORMAL);
return backend;
}
void backend_destroy(backend_t *backend) {
if (!backend) return;
tray_cleanup(backend);
p_delete(&backend->config_list.cfgs);
p_clear(&backend->config_list, 1);
p_delete(&backend->unmap.windows);
p_clear(&backend->unmap, 1);
p_delete(&backend->map.windows);
p_clear(&backend->map, 1);
p_delete(&backend->kill.windows);
p_clear(&backend->kill, 1);
window_list_cleanup(&backend->unmap);
window_list_cleanup(&backend->map);
window_list_cleanup(&backend->kill);
xcb_key_symbols_free(backend->key_symbols);
backend->key_symbols = nullptr;
cursor_cleanup(backend);
xcb_disconnect(backend->conn);
backend->conn = nullptr;
free(backend);
}
int backend_get_fd(backend_t *backend) {
if (!backend || !backend->conn) return -1;
return xcb_get_file_descriptor(backend->conn);
}
static bool detect_monitor_by_randr(
const backend_t *backend,
output_info_t **outputs,
@@ -240,10 +267,10 @@ static bool detect_monitor_by_randr(
xcb_randr_monitor_info_iterator_t iter =
xcb_randr_get_monitors_monitors_iterator(monitors_reply);
for (int i = 0; iter.rem; xcb_randr_monitor_info_next(&iter), i++) {
output_info_t *output = &output_list[i];
output->geometry.x = iter.data->x;
output->geometry.y = iter.data->y;
output->geometry.width = iter.data->width;
output_info_t *output = &output_list[i];
output->geometry.x = iter.data->x;
output->geometry.y = iter.data->y;
output->geometry.width = iter.data->width;
output->geometry.height = iter.data->height;
xcb_get_atom_name_cookie_t name_cookie =
@@ -252,8 +279,8 @@ static bool detect_monitor_by_randr(
xcb_get_atom_name_reply(backend->conn, name_cookie, nullptr);
if (name_reply) {
char *name = xcb_get_atom_name_name(name_reply);
int length = xcb_get_atom_name_name_length(name_reply);
char *name = xcb_get_atom_name_name(name_reply);
int length = xcb_get_atom_name_name_length(name_reply);
output->name = p_strndup(name, length);
p_delete(&name_reply);
}
@@ -322,10 +349,10 @@ static bool detect_monitor_by_xinerama(
output_info_t *output_list = p_new(output_info_t, len);
for (int i = 0; i < len; i++) {
output_info_t *output = &output_list[i];
output->geometry.x = screen_info[i].x_org;
output->geometry.y = screen_info[i].y_org;
output->geometry.width = screen_info[i].width;
output_info_t *output = &output_list[i];
output->geometry.x = screen_info[i].x_org;
output->geometry.y = screen_info[i].y_org;
output->geometry.width = screen_info[i].width;
output->geometry.height = screen_info[i].height;
}
p_delete(&screens_reply);
@@ -343,7 +370,7 @@ static bool detect_monitor_by_xinerama(
backend_detect_t *backend_detect(backend_t *backend) {
output_info_t *outputs = nullptr;
size_t count = 0;
size_t count = 0;
if (detect_monitor_by_randr(backend, &outputs, &count)) {
backend_detect_t *detect = output_remove_duplication(outputs, count);
@@ -357,15 +384,15 @@ backend_detect_t *backend_detect(backend_t *backend) {
return detect;
}
output_info_t *output = p_new(output_info_t, 1);
output->geometry.x = 0;
output->geometry.y = 0;
output->geometry.width = backend->screen->width_in_pixels;
output_info_t *output = p_new(output_info_t, 1);
output->geometry.x = 0;
output->geometry.y = 0;
output->geometry.width = backend->screen->width_in_pixels;
output->geometry.height = backend->screen->height_in_pixels;
backend_detect_t *detect = p_new(backend_detect_t, 1);
detect->outputs = output;
detect->output_count = 1;
detect->outputs = output;
detect->output_count = 1;
return detect;
}
@@ -382,16 +409,16 @@ void backend_detect_destroy(backend_detect_t *detect) {
static void backend_focus_window(backend_t *backend, xcb_window_t window) {
xcb_connection_t *conn = backend->conn;
xcb_window_t root = backend->screen->root;
const atoms_t *atoms = &backend->atoms;
xcb_atom_t property = atoms->_NET_ACTIVE_WINDOW;
xcb_timestamp_t time = XCB_TIME_CURRENT_TIME;
xcb_window_t root = backend->screen->root;
const atoms_t *atoms = &backend->atoms;
xcb_atom_t property = atoms->_NET_ACTIVE_WINDOW;
xcb_timestamp_t time = XCB_TIME_CURRENT_TIME;
if (window == XCB_WINDOW_NONE || window == root) {
window = backend->window_no_focus;
xcb_set_input_focus(conn, XCB_INPUT_FOCUS_PARENT, window, time);
xcb_delete_property(conn, root, property);
} else {
uint8_t mode = XCB_PROP_MODE_REPLACE;
uint8_t mode = XCB_PROP_MODE_REPLACE;
xcb_atom_t type = XCB_ATOM_WINDOW;
xcb_set_input_focus(conn, XCB_INPUT_FOCUS_PARENT, window, time);
xcb_change_property(conn, mode, root, property, type, 32, 1, &window);
@@ -421,12 +448,12 @@ static void
backend_restack_windows(backend_t *backend, const effect_window_list_t *list) {
if (list->count == 0) return;
xcb_connection_t *conn = backend->conn;
xcb_connection_t *conn = backend->conn;
xcb_window_t sibling_window = backend->window_no_focus;
for (size_t i = 0; i < list->count; ++i) {
uint16_t mask = XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE;
xcb_configure_window_value_list_t params = {
.sibling = sibling_window,
.sibling = sibling_window,
.stack_mode = XCB_STACK_MODE_ABOVE
};
sibling_window = list->windows[i];
@@ -440,7 +467,7 @@ static void window_configure_list_reset(window_configure_list_t *configs) {
}
static void backend_apply_window_configure_list(backend_t *backend) {
xcb_connection_t *conn = backend->conn;
xcb_connection_t *conn = backend->conn;
window_configure_list_t *configs = &backend->config_list;
for (size_t i = 0; i < configs->count; ++i) {
window_configure_t *cfg = &configs->cfgs[i];
@@ -461,28 +488,77 @@ find_or_push_configure(window_configure_list_t *configs, xcb_window_t window) {
cfg = array_push(configs->cfgs, configs->count, configs->capacity);
p_clear(cfg, 1);
cfg->window = window;
cfg->mask = 0;
cfg->mask = 0;
return cfg;
}
static void merge_window_configure_params(
backend_t *backend,
const configure_data_t *data
) {
auto cfg = find_or_push_configure(&backend->config_list, data->window);
#define FIELD_MERGE(MASK, VALUE_FIELD, DATA_FIELD) \
if (data->changed_fields & (MASK)) { \
cfg->mask |= (MASK); \
cfg->value.VALUE_FIELD = data->DATA_FIELD; \
}
FIELD_MERGE(ZDWM_CONFIGURE_FIELD_X, x, x);
FIELD_MERGE(ZDWM_CONFIGURE_FIELD_Y, y, y);
FIELD_MERGE(ZDWM_CONFIGURE_FIELD_WIDTH, width, width);
FIELD_MERGE(ZDWM_CONFIGURE_FIELD_HEIGHT, height, height);
FIELD_MERGE(ZDWM_CONFIGURE_FIELD_BORDER_WIDTH, border_width, border_width);
FIELD_MERGE(ZDWM_CONFIGURE_FIELD_SIBLING, sibling, sibling);
FIELD_MERGE(ZDWM_CONFIGURE_FIELD_STACK_MODE, stack_mode, stack_mode);
#undef FIELD_MERGE
}
static void
backend_bind_key(backend_t *backend, const effect_bind_key_t *bind_key) {
auto root = backend->screen->root;
window_grab_keys(backend, root, bind_key->keys, bind_key->count);
}
static void backend_grab_button(
backend_t *backend,
const effect_grab_button_t *grab_button
) {
auto conn = backend->conn;
auto window = grab_button->window;
xcb_ungrab_button(conn, XCB_BUTTON_INDEX_ANY, window, XCB_MOD_MASK_ANY);
auto buttons = grab_button->buttons;
for (size_t i = 0; i < grab_button->count; ++i) {
auto button = buttons[i].button;
auto modifiers = buttons[i].modifiers;
window_grab_button(backend, window, button, modifiers);
}
}
static void backend_grab_pointer(backend_t *backend, cursor_t cursor) {
auto conn = backend->conn;
auto root = backend->screen->root;
auto mask = XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE |
XCB_EVENT_MASK_POINTER_MOTION;
auto mode = XCB_GRAB_MODE_ASYNC;
auto xcursor = cursor_get_xcb_cursor(backend, cursor);
auto time = XCB_TIME_CURRENT_TIME;
xcb_grab_pointer(conn, false, root, mask, mode, mode, root, xcursor, time);
}
static void backend_merge_effects(
backend_t *backend,
const effect_t *effects,
size_t effect_count
) {
window_configure_list_t *configs = &backend->config_list;
for (size_t i = 0; i < effect_count; ++i) {
const effect_t *e = &effects[i];
switch (e->type) {
case ZDWM_EFFECT_MAP_WINDOW:
window_list_push(&backend->map, e->as.map.window);
window_list_push(&backend->configure, e->as.map.window);
break;
case ZDWM_EFFECT_UNMAP_WINDOW:
window_list_push(&backend->unmap, e->as.unmap.window);
@@ -494,20 +570,52 @@ static void backend_merge_effects(
case ZDWM_EFFECT_KILL_WINDOW:
window_list_push(&backend->kill, e->as.kill.window);
break;
case ZDWM_EFFECT_MOVE_WINDOW: {
window_configure_t *cfg =
find_or_push_configure(configs, e->as.move.window);
cfg->mask |= XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y;
cfg->value.x = e->as.move.left_top_point.x;
cfg->value.y = e->as.move.left_top_point.y;
case ZDWM_EFFECT_WITHDRAW_WINDOW:
window_set_icccm_wm_state(
backend->conn,
e->as.withdraw.window,
XCB_ICCCM_WM_STATE_WITHDRAWN
);
break;
case ZDWM_EFFECT_START_MOVE_WINDOW:
backend_grab_pointer(backend, ZDWM_CURSOR_MOVE);
break;
case ZDWM_EFFECT_START_RESIZE_WINDOW:
backend_grab_pointer(backend, ZDWM_CURSOR_RESIZE);
break;
case ZDWM_EFFECT_MINIMIZE_WINDOW: {
auto window = e->as.minimize.window;
xcb_icccm_wm_state_t state = XCB_ICCCM_WM_STATE_NORMAL;
if (e->as.minimize.value) state = XCB_ICCCM_WM_STATE_ICONIC;
window_set_icccm_wm_state(backend->conn, window, state);
} break;
case ZDWM_EFFECT_RESIZE_WINDOW: {
window_configure_t *cfg =
find_or_push_configure(configs, e->as.resize.window);
cfg->mask |= XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT;
cfg->value.width = (uint32_t)e->as.resize.width;
cfg->value.height = (uint32_t)e->as.resize.height;
case ZDWM_EFFECT_MAXIMIZE_WINDOW: {
auto window = e->as.maximize.window;
if (e->as.maximize.value) {
xcb_atom_t max_atoms[] = {
backend->atoms._NET_WM_STATE_MAXIMIZED_HORZ,
backend->atoms._NET_WM_STATE_MAXIMIZED_VERT
};
window_set_net_wm_state(backend, window, countof(max_atoms), max_atoms);
} else {
window_set_net_wm_state(backend, window, 0, nullptr);
}
} break;
case ZDWM_EFFECT_FULLSCREEN_WINDOW: {
auto window = e->as.fullscreen.window;
if (e->as.fullscreen.value) {
auto fullscreen_atom = backend->atoms._NET_WM_STATE_FULLSCREEN;
window_set_net_wm_state(backend, window, 1, &fullscreen_atom);
} else {
window_set_net_wm_state(backend, window, 0, nullptr);
}
} break;
case ZDWM_EFFECT_CONFIGURE_WINDOW:
merge_window_configure_params(backend, &e->as.configure);
break;
case ZDWM_EFFECT_CONFIGURE_NOTIFY:
window_list_push(&backend->configure, e->as.configure_notify.window);
break;
case ZDWM_EFFECT_CHANGE_BORDER_COLOR: {
xcb_change_window_attributes_value_list_t value = {
.border_pixel = e->as.change_border_color.color->argb
@@ -519,12 +627,6 @@ static void backend_merge_effects(
&value
);
} break;
case ZDWM_EFFECT_CHANGE_BORDER_WIDTH: {
window_configure_t *cfg =
find_or_push_configure(configs, e->as.change_border_width.window);
cfg->mask |= XCB_CONFIG_WINDOW_BORDER_WIDTH;
cfg->value.border_width = e->as.change_border_width.border_width;
} break;
case ZDWM_EFFECT_CHANGE_WINDOW_LIST:
backend_change_window_list(backend, false, &e->as.change_window_list);
break;
@@ -535,10 +637,56 @@ static void backend_merge_effects(
case ZDWM_EFFECT_BIND_KEY:
backend_bind_key(backend, &e->as.bind_key);
break;
case ZDWM_EFFECT_GRAB_BUTTON:
backend_grab_button(backend, &e->as.grab_button);
break;
case ZDWM_EFFECT_UNGRAB_POINTER:
xcb_ungrab_pointer(backend->conn, XCB_CURRENT_TIME);
break;
}
}
}
static void batch_send_configure_notify(
xcb_connection_t *conn,
xcb_window_t *windows,
size_t count
) {
if (count == 0) return;
auto cookie_list = p_new(xcb_get_geometry_cookie_t, count);
for (size_t i = 0; i < count; ++i) {
auto window = windows[i];
cookie_list[i] = xcb_get_geometry(conn, window);
}
uint32_t event_mask = XCB_EVENT_MASK_STRUCTURE_NOTIFY;
for (size_t i = 0; i < count; ++i) {
auto window = windows[i];
auto cookie = cookie_list[i];
auto reply = xcb_get_geometry_reply(conn, cookie, nullptr);
if (!reply) continue;
xcb_configure_notify_event_t ev = {
.response_type = XCB_CONFIGURE_NOTIFY,
.event = window,
.window = window,
.above_sibling = XCB_WINDOW_NONE,
.x = reply->x,
.y = reply->y,
.width = reply->width,
.height = reply->height,
.border_width = reply->border_width,
.override_redirect = false,
};
xcb_send_event(conn, false, window, event_mask, (char *)&ev);
p_delete(&reply);
}
p_delete(&cookie_list);
}
static void backend_batch_apply_effects(backend_t *backend) {
xcb_connection_t *conn = backend->conn;
if (backend->unmap.count) {
@@ -568,6 +716,12 @@ static void backend_batch_apply_effects(backend_t *backend) {
backend_apply_window_configure_list(backend);
{
auto windows = backend->configure.windows;
auto count = backend->configure.count;
batch_send_configure_notify(conn, windows, count);
}
if (backend->update_focus) {
backend_focus_window(backend, backend->focus_window);
}
@@ -583,6 +737,7 @@ bool backend_apply_effect(
window_list_reset(&backend->unmap);
window_list_reset(&backend->map);
window_list_reset(&backend->kill);
window_list_reset(&backend->configure);
backend_merge_effects(backend, effects, effect_count);
backend_batch_apply_effects(backend);
@@ -590,3 +745,178 @@ bool backend_apply_effect(
xcb_flush(backend->conn);
return true;
}
backend_scan_result_t *backend_scan_windows(backend_t *backend) {
auto conn = backend->conn;
auto root = backend->screen->root;
auto cookie = xcb_query_tree_unchecked(conn, root);
auto reply = xcb_query_tree_reply(conn, cookie, nullptr);
if (!reply) return nullptr;
auto length = xcb_query_tree_children_length(reply);
if (!length) {
p_delete(&reply);
return nullptr;
}
auto list = xcb_query_tree_children(reply);
auto result = p_new(backend_scan_result_t, 1);
auto sub_result = p_new(backend_scan_result_t, 1);
for (typeof(length) i = 0; i < length; ++i) {
auto window = list[i];
auto wa = window_get_attributes(backend, window);
if (!wa) continue;
auto override_redirect = wa->override_redirect;
auto map_state = wa->map_state;
p_delete(&wa);
if (override_redirect) continue;
auto hints = (xcb_icccm_wm_hints_t){0};
auto hints_getted = window_get_wm_hints(backend, window, &hints);
if (!(map_state == XCB_MAP_STATE_VIEWABLE ||
(hints_getted && hints.initial_state == XCB_ICCCM_WM_STATE_ICONIC))) {
continue;
}
auto transient_for = window_get_transient_for(backend, window);
auto r = transient_for == XCB_WINDOW_NONE ? result : sub_result;
auto slot = array_push(r->windows, r->count, r->capacity);
if (
!populate_window_event(backend, list[i], slot) || slot->override_redirect
) {
window_layer_props_cleanup(&slot->props);
window_metadata_cleanup(&slot->metadata);
r->count--;
}
}
p_delete(&reply);
for (size_t i = 0; i < sub_result->count; ++i) {
auto slot = array_push(result->windows, result->count, result->capacity);
*slot = sub_result->windows[i];
}
p_clear(sub_result->windows, sub_result->count);
backend_scan_result_destroy(sub_result);
if (result->count == 0) {
backend_scan_result_destroy(result);
result = nullptr;
return nullptr;
}
return result;
}
void backend_scan_result_destroy(backend_scan_result_t *result) {
if (!result) return;
for (size_t i = 0; i < result->count; i++) {
window_map_request_event_t *ev = &result->windows[i];
window_layer_props_cleanup(&ev->props);
window_metadata_cleanup(&ev->metadata);
}
p_delete(&result->windows);
p_delete(&result);
}
backend_bar_window_t backend_create_bar_window(
backend_t *backend,
rect_t geometry,
uint32_t bg_pixel,
bool enable_tray
) {
auto conn = backend->conn;
auto root = backend->screen->root;
auto visual = window_get_visual(backend, true);
static xcb_colormap_t colormap = XCB_NONE;
if (colormap == XCB_NONE) {
colormap = xcb_generate_id(conn);
uint8_t alloc = XCB_COLORMAP_ALLOC_NONE;
xcb_create_colormap(conn, alloc, colormap, root, visual->visual->visual_id);
}
window_clean_event_mask(conn, root);
xcb_grab_server(conn);
auto window_id = xcb_generate_id(conn);
uint16_t _class = XCB_WINDOW_CLASS_INPUT_OUTPUT;
uint32_t value_mask = XCB_CW_OVERRIDE_REDIRECT | XCB_CW_BACK_PIXEL |
XCB_CW_BORDER_PIXEL | XCB_CW_EVENT_MASK |
XCB_CW_COLORMAP;
const xcb_create_window_value_list_t value_list = {
.override_redirect = true,
.background_pixel = bg_pixel,
.border_pixel = 0,
.event_mask = XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_EXPOSURE,
.colormap = colormap,
};
auto cookie = xcb_create_window_aux_checked(
conn,
visual->depth,
window_id,
root,
geometry.x,
geometry.y,
geometry.width,
geometry.height,
0,
_class,
visual->visual->visual_id,
value_mask,
&value_list
);
if (xcb_request_check(conn, cookie)) fatal("cannot create bar window");
cookie = xcb_map_window_checked(conn, window_id);
if (xcb_request_check(conn, cookie)) fatal("cannot map bar window");
window_set_class_instance(conn, window_id);
window_set_name_static(conn, window_id, APP_NAME "_bar");
xcb_ungrab_server(conn);
root_set_event_mask(backend);
xcb_aux_sync(conn);
auto width = geometry.width;
auto height = geometry.height;
auto vid = visual->visual;
auto surface = cairo_xcb_surface_create(conn, window_id, vid, width, height);
auto cr = cairo_create(surface);
cairo_surface_destroy(surface);
p_delete(&visual);
auto statue = cairo_status(cr);
if (statue != CAIRO_STATUS_SUCCESS) {
fatal("cannot create cairo context: %s", cairo_status_to_string(statue));
}
if (enable_tray) {
tray_host_window_t host_window = {
.window = window_id,
.width = width,
.height = height,
.bg_pixel = bg_pixel,
};
tray_init(backend, host_window);
}
return (backend_bar_window_t){
.window_id = window_id,
.cr = cr,
.tray = {
.api = tray_api,
.handle = backend,
},
};
}
void backend_flush(backend_t *backend) { xcb_flush(backend->conn); }

71
src/backend/x11/common.c Normal file
View File

@@ -0,0 +1,71 @@
#include "common.h"
#include <stddef.h>
#include <stdint.h>
#include <xcb/xproto.h>
#include "base/macros.h"
#include "interface/types.h"
typedef struct modifier_map_t {
modifier_bit_t modifier;
xcb_mod_mask_t mask;
} modifier_map_t;
static constexpr modifier_map_t modifier_map[] = {
{ZDWM_MOD_SHIFT, XCB_MOD_MASK_SHIFT},
{ZDWM_MOD_CONTROL, XCB_MOD_MASK_CONTROL},
{ZDWM_MOD_1, XCB_MOD_MASK_1},
{ZDWM_MOD_2, XCB_MOD_MASK_2},
{ZDWM_MOD_3, XCB_MOD_MASK_3},
{ZDWM_MOD_4, XCB_MOD_MASK_4},
{ZDWM_MOD_5, XCB_MOD_MASK_5},
};
modifier_mask_t modifiers_xcb_to_zdwm(uint16_t mask) {
modifier_mask_t mods = ZDWM_MOD_NONE;
for (size_t i = 0; i < countof(modifier_map); ++i) {
auto item = &modifier_map[i];
if (mask & item->mask) mods |= item->modifier;
}
return mods;
}
uint16_t modifiers_zdwm_to_xcb(modifier_mask_t mask) {
uint16_t mods = 0U;
for (size_t i = 0; i < countof(modifier_map); ++i) {
auto item = &modifier_map[i];
if (mask & item->modifier) mods |= item->mask;
}
return mods;
}
typedef struct button_map_t {
button_t zdwm_button;
xcb_button_index_t xcb_button;
} button_map_t;
static constexpr button_map_t button_map[] = {
{ZDWM_BUTTON_LEFT, XCB_BUTTON_INDEX_1},
{ZDWM_BUTTON_RIGHT, XCB_BUTTON_INDEX_3},
{ZDWM_BUTTON_MIDDLE, XCB_BUTTON_INDEX_2},
/* TODO: 后续可以考虑加上鼠标滚轮事件 */
};
button_t button_xcb_to_zdwm(xcb_button_t button) {
for (size_t i = 0; i < countof(button_map); ++i) {
auto item = &button_map[i];
if (button == item->xcb_button) return item->zdwm_button;
}
return ZDWM_BUTTON_NONE;
}
xcb_button_t button_zdwm_to_xcb(button_t button) {
for (size_t i = 0; i < countof(button_map); ++i) {
auto item = &button_map[i];
if (button == item->zdwm_button) return item->xcb_button;
}
return XCB_BUTTON_INDEX_ANY;
}

12
src/backend/x11/common.h Normal file
View File

@@ -0,0 +1,12 @@
#pragma once
#include <stdint.h>
#include <xcb/xproto.h>
#include "interface/types.h"
modifier_mask_t modifiers_xcb_to_zdwm(uint16_t mask);
uint16_t modifiers_zdwm_to_xcb(modifier_mask_t mask);
button_t button_xcb_to_zdwm(xcb_button_t button);
xcb_button_t button_zdwm_to_xcb(button_t button);

51
src/backend/x11/cursor.c Normal file
View File

@@ -0,0 +1,51 @@
#include <stddef.h>
#include <xcb/xcb_cursor.h>
#include <xcb/xproto.h>
#include "base/macros.h"
#include "base/memory.h"
#include "internal.h"
static const char *const cursor_name_map[] = {
[ZDWM_CURSOR_NORMAL] = "left_ptr",
[ZDWM_CURSOR_MOVE] = "fleur",
[ZDWM_CURSOR_RESIZE] = "bottom_right_corner",
};
static xcb_cursor_context_t *get_xcb_cursor_context(backend_t *backend) {
if (backend->cursor_context) return backend->cursor_context;
auto conn = backend->conn;
auto screen = backend->screen;
if (xcb_cursor_context_new(conn, screen, &backend->cursor_context) == 0) {
return backend->cursor_context;
}
return nullptr;
}
void cursor_init(backend_t *backend) {
auto ctx = get_xcb_cursor_context(backend);
if (!ctx) return;
for (size_t i = 0; i < countof(backend->cursors); ++i) {
auto cursor_name = cursor_name_map[i];
backend->cursors[i] = xcb_cursor_load_cursor(ctx, cursor_name);
}
}
void cursor_cleanup(backend_t *backend) {
constexpr size_t count = countof(backend->cursors);
for (size_t i = 0; i < count; ++i) {
xcb_free_cursor(backend->conn, backend->cursors[i]);
}
p_clear(backend->cursors, count);
xcb_cursor_context_free(backend->cursor_context);
backend->cursor_context = nullptr;
}
xcb_cursor_t cursor_get_xcb_cursor(backend_t *backend, cursor_t cursor) {
if (backend->cursor_context == nullptr) return XCB_CURSOR_NONE;
return backend->cursors[cursor];
}

9
src/backend/x11/cursor.h Normal file
View File

@@ -0,0 +1,9 @@
#pragma once
#include <xcb/xproto.h>
#include "internal.h"
void cursor_init(backend_t *backend);
void cursor_cleanup(backend_t *backend);
xcb_cursor_t cursor_get_xcb_cursor(backend_t *backend, cursor_t cursor);

View File

@@ -1,5 +1,6 @@
#include "core/event.h"
#include "common/event.h"
#include <assert.h>
#include <stdint.h>
#include <stdio.h>
#include <xcb/xcb.h>
@@ -8,12 +9,12 @@
#include <xcb/xcb_keysyms.h>
#include <xcb/xproto.h>
#include "backend/x11/common.h"
#include "backend/x11/tray.h"
#include "backend/x11/window.h"
#include "base/macros.h"
#include "base/memory.h"
#include "core/backend.h"
#include "core/types.h"
#include "core/window.h"
#include "interface/backend.h"
#include "interface/event.h"
#include "internal.h"
static window_state_t *derive_window_states(
@@ -27,7 +28,7 @@ static window_state_t *derive_window_states(
return nullptr;
}
window_state_t *buf = p_new(window_state_t, count);
size_t valid = 0;
size_t valid = 0;
for (uint32_t i = 0; i < count; i++) {
window_state_t s = atom_to_window_state(atoms, raw[i]);
if (s != (window_state_t)-1) buf[valid++] = s;
@@ -51,27 +52,30 @@ static bool derive_skip_taskbar(
return false;
}
static window_geometry_mode_t geometry_mode_from_hints(
const xcb_icccm_wm_hints_t *hints
) {
if (hints->initial_state == XCB_ICCCM_WM_STATE_ICONIC)
return ZDWM_GEOMETRY_MINIMIZED;
return ZDWM_GEOMETRY_NORMAL;
}
static window_geometry_mode_t geometry_mode_from_states(
static bool maximized_from_states(
const atoms_t *atoms,
const xcb_atom_t *state_atoms,
uint32_t state_count
) {
for (uint32_t i = 0; i < state_count; i++) {
if (state_atoms[i] == atoms->_NET_WM_STATE_FULLSCREEN)
return ZDWM_GEOMETRY_FULLSCREEN;
if (state_atoms[i] == atoms->_NET_WM_STATE_MAXIMIZED_VERT ||
state_atoms[i] == atoms->_NET_WM_STATE_MAXIMIZED_HORZ)
return ZDWM_GEOMETRY_MAXIMIZED;
if (
state_atoms[i] == atoms->_NET_WM_STATE_MAXIMIZED_VERT ||
state_atoms[i] == atoms->_NET_WM_STATE_MAXIMIZED_HORZ
)
return true;
}
return ZDWM_GEOMETRY_NORMAL;
return false;
}
static bool fullscreen_from_states(
const atoms_t *atoms,
const xcb_atom_t *state_atoms,
uint32_t state_count
) {
for (uint32_t i = 0; i < state_count; i++) {
if (state_atoms[i] == atoms->_NET_WM_STATE_FULLSCREEN) return true;
}
return false;
}
static bool urgent_from_states(
@@ -86,38 +90,56 @@ static bool urgent_from_states(
return false;
}
static bool handle_map_request(
backend_t *backend,
event_t *event,
const xcb_map_request_event_t *xcb_event
static void parse_size_hints(
const xcb_size_hints_t *hints,
zdwm_size_t *min,
zdwm_size_t *max
) {
xcb_window_t window = xcb_event->window;
assert(hints != nullptr);
assert(min != nullptr);
assert(max != nullptr);
event->type = ZDWM_EVENT_WINDOW_MAP_REQUEST;
*min = (zdwm_size_t){.width = 0, .height = 0};
*max = (zdwm_size_t){.width = INT32_MAX, .height = INT32_MAX};
window_map_request_event_t *ev = &event->as.window_map_request;
ev->window = (window_id_t)window;
ev->transient_for = window_get_transient_for(backend, window);
ev->geometry_mode = ZDWM_GEOMETRY_NORMAL;
if (hints->flags & XCB_ICCCM_SIZE_HINT_P_MIN_SIZE) {
min->width = hints->min_width;
min->height = hints->min_height;
}
if (hints->flags & XCB_ICCCM_SIZE_HINT_P_MAX_SIZE) {
max->width = hints->max_width;
max->height = hints->max_height;
}
}
xcb_get_window_attributes_reply_t *wa =
window_get_attributes(backend, window);
bool populate_window_event(
backend_t *backend,
xcb_window_t window,
window_map_request_event_t *ev
) {
ev->window = (window_id_t)window;
ev->transient_for = window_get_transient_for(backend, window);
auto wa = window_get_attributes(backend, window);
if (!wa) return false;
ev->override_redirect = (bool)wa->override_redirect;
p_delete(&wa);
xcb_icccm_wm_hints_t wm_hints = {0};
if (window_get_wm_hints(backend, window, &wm_hints)) {
ev->urgent = (bool)xcb_icccm_wm_hints_get_urgency(&wm_hints);
ev->geometry_mode = geometry_mode_from_hints(&wm_hints);
ev->urgent = (bool)xcb_icccm_wm_hints_get_urgency(&wm_hints);
ev->minimized = wm_hints.initial_state == XCB_ICCCM_WM_STATE_ICONIC;
}
if (!window_get_fixed_size(backend, window, &ev->fixed_size)) return false;
xcb_size_hints_t size_hints = {0};
if (!window_get_size_hints(backend, window, &size_hints)) return false;
parse_size_hints(&size_hints, &ev->min_size, &ev->max_size);
if (!window_get_geometry(backend, window, &ev->rect)) return false;
const atoms_t *atoms = &backend->atoms;
const atoms_t *atoms = &backend->atoms;
xcb_atom_t *state_atoms = nullptr;
uint32_t state_count = 0;
uint32_t state_count = 0;
if (!window_get_atom_array(
backend,
window,
@@ -128,12 +150,9 @@ static bool handle_map_request(
return false;
}
if (ev->geometry_mode == ZDWM_GEOMETRY_NORMAL && state_atoms) {
ev->geometry_mode =
geometry_mode_from_states(atoms, state_atoms, state_count);
}
if (state_atoms) {
ev->maximized = maximized_from_states(atoms, state_atoms, state_count);
ev->fullscreen = fullscreen_from_states(atoms, state_atoms, state_count);
ev->skip_taskbar = derive_skip_taskbar(atoms, state_atoms, state_count);
ev->props.states = derive_window_states(
atoms,
@@ -154,7 +173,7 @@ static bool handle_map_request(
return false;
}
ev->metadata.role = window_get_role(backend, window);
ev->metadata.role = window_get_role(backend, window);
ev->metadata.title = window_get_title(backend, window);
window_get_class(
backend,
@@ -166,14 +185,35 @@ static bool handle_map_request(
return true;
}
static bool handle_map_request(
backend_t *backend,
event_t *event,
const xcb_map_request_event_t *xcb_event
) {
if (tray_handle_map_request(backend, xcb_event)) return false;
event->type = ZDWM_EVENT_WINDOW_MAP_REQUEST;
return populate_window_event(
backend,
xcb_event->window,
&event->as.window_map_request
);
}
static bool handle_unmap_notify(
backend_t *backend,
event_t *event,
const xcb_unmap_notify_event_t *xcb_event
) {
event->type = ZDWM_EVENT_WINDOW_REMOVE;
if (tray_handle_unmap_notify(backend, xcb_event)) return false;
event->type = ZDWM_EVENT_WINDOW_REMOVE;
event->as.window_remove.window = xcb_event->window;
event->as.window_remove.reason = ZDWM_WINDOW_REMOVE_WITHDRAWN;
if (XCB_EVENT_SENT(xcb_event)) {
event->as.window_remove.reason = ZDWM_WINDOW_REMOVE_WITHDRAWN;
} else {
event->as.window_remove.reason = ZDWM_WINDOW_REMOVE_DESTROY;
}
return true;
}
@@ -183,7 +223,9 @@ static bool handle_destroy_notify(
event_t *event,
const xcb_destroy_notify_event_t *xcb_event
) {
event->type = ZDWM_EVENT_WINDOW_REMOVE;
if (tray_handle_destroy_notify(backend, xcb_event)) return false;
event->type = ZDWM_EVENT_WINDOW_REMOVE;
event->as.window_remove.window = xcb_event->window;
event->as.window_remove.reason = ZDWM_WINDOW_REMOVE_DESTROY;
@@ -195,32 +237,30 @@ static bool handle_configure_request(
event_t *event,
const xcb_configure_request_event_t *xcb_event
) {
if (tray_handle_configure_request(backend, xcb_event)) return false;
event->type = ZDWM_EVENT_CONFIGURE_REQUEST;
/* TODO: */
return true;
}
auto data = &event->as.configure_request;
data->window = xcb_event->window;
data->changed_fields = 0u;
static modifier_mask_t get_modifiers(uint16_t mask) {
typedef struct modifier_map_t {
modifier_bit_t modifier;
xcb_mod_mask_t mask;
} modifier_map_t;
static constexpr modifier_map_t modifier_map[] = {
{ZDWM_MOD_SHIFT, XCB_MOD_MASK_SHIFT},
{ZDWM_MOD_CONTROL, XCB_MOD_MASK_CONTROL},
{ZDWM_MOD_1, XCB_MOD_MASK_1},
{ZDWM_MOD_2, XCB_MOD_MASK_2},
{ZDWM_MOD_3, XCB_MOD_MASK_3},
{ZDWM_MOD_4, XCB_MOD_MASK_4},
{ZDWM_MOD_5, XCB_MOD_MASK_5},
};
auto mods = ZDWM_MOD_NONE;
for (size_t i = 0; i < countof(modifier_map); ++i) {
auto item = &modifier_map[i];
if (mask & item->mask) mods |= item->modifier;
#define EXTRACT_FIELD(XCB_MASK, FIELD_MASK, DATA_FIELD, EVENT_FIELD) \
if (xcb_event->value_mask & XCB_CONFIG_WINDOW_##XCB_MASK) { \
data->DATA_FIELD = xcb_event->EVENT_FIELD; \
data->changed_fields |= ZDWM_CONFIGURE_FIELD_##FIELD_MASK; \
}
return mods;
EXTRACT_FIELD(X, X, x, x);
EXTRACT_FIELD(Y, Y, y, y);
EXTRACT_FIELD(WIDTH, WIDTH, width, width);
EXTRACT_FIELD(HEIGHT, HEIGHT, height, height);
EXTRACT_FIELD(BORDER_WIDTH, BORDER_WIDTH, border_width, border_width);
EXTRACT_FIELD(SIBLING, SIBLING, sibling, sibling);
EXTRACT_FIELD(STACK_MODE, STACK_MODE, stack_mode, stack_mode);
#undef EXTRACT_FIELD
return true;
}
static bool handle_key_press(
@@ -233,14 +273,300 @@ static bool handle_key_press(
/* keysym without any modifiers */
auto keysym = xcb_key_symbols_get_keysym(backend->key_symbols, keycode, 0);
event->type = ZDWM_EVENT_KEY_PRESS;
event->as.key_press.keycode = keycode;
event->as.key_press.keysym = keysym;
event->as.key_press.modifiers = get_modifiers(xcb_event->state);
event->type = ZDWM_EVENT_KEY_PRESS;
event->as.key_press.keycode = keycode;
event->as.key_press.keysym = keysym;
event->as.key_press.modifiers = modifiers_xcb_to_zdwm(xcb_event->state);
return true;
}
static bool fill_button_event(
const xcb_button_press_event_t *xcb_event,
pointer_button_event_t *data
) {
auto button = button_xcb_to_zdwm(xcb_event->detail);
data->modifiers = modifiers_xcb_to_zdwm(xcb_event->state);
data->button = button;
data->window = xcb_event->event;
data->root = (point_t){.x = xcb_event->root_x, .y = xcb_event->root_y};
data->local = (point_t){.x = xcb_event->event_x, .y = xcb_event->event_y};
return true;
}
static bool handle_button_press(
backend_t *backend,
event_t *event,
const xcb_button_press_event_t *xcb_event
) {
auto press = &event->as.pointer_button_press;
auto handled = fill_button_event(xcb_event, press);
if (handled) event->type = ZDWM_EVENT_POINTER_BUTTON_PRESS;
return handled;
}
static bool handle_button_release(
backend_t *backend,
event_t *event,
const xcb_button_release_event_t *xcb_event
) {
auto release = &event->as.pointer_button_release;
auto handled = fill_button_event(xcb_event, release);
if (handled) event->type = ZDWM_EVENT_POINTER_BUTTON_RELEASE;
return handled;
}
static bool handle_motion_notify(
backend_t *backend,
event_t *event,
const xcb_motion_notify_event_t *xcb_event
) {
event->type = ZDWM_EVENT_POINTER_MOTION;
auto data = &event->as.pointer_motion;
data->window = xcb_event->event;
data->root = (point_t){.x = xcb_event->root_x, .y = xcb_event->root_y};
data->local = (point_t){.x = xcb_event->event_x, .y = xcb_event->event_y};
return true;
}
static bool handle_enter_notify(
backend_t *backend,
event_t *event,
const xcb_enter_notify_event_t *xcb_event
) {
event->type = ZDWM_EVENT_POINTER_ENTER;
event->as.pointer_enter.window = xcb_event->event;
return true;
}
static bool handle_property_notify(
backend_t *backend,
event_t *event,
const xcb_property_notify_event_t *xcb_event
) {
if (tray_handle_property_notify(backend, xcb_event)) return false;
auto window_id = xcb_event->window;
if (
xcb_event->atom == backend->atoms.WM_NAME ||
xcb_event->atom == backend->atoms._NET_WM_NAME
) {
event->type = ZDWM_EVENT_WINDOW_METADATA_CHANGED;
auto data = &event->as.window_metadata_change;
data->window = window_id;
data->metadata.title = window_get_title(backend, window_id);
data->changed_fields = ZDWM_WINDOW_METADATA_CHANGE_TITLE;
return true;
}
if (xcb_event->atom == backend->atoms.WM_WINDOW_ROLE) {
event->type = ZDWM_EVENT_WINDOW_METADATA_CHANGED;
auto data = &event->as.window_metadata_change;
data->window = window_id;
data->metadata.role = window_get_role(backend, window_id);
data->changed_fields = ZDWM_WINDOW_METADATA_CHANGE_ROLE;
return true;
}
if (xcb_event->atom == XCB_ATOM_WM_CLASS) {
event->type = ZDWM_EVENT_WINDOW_METADATA_CHANGED;
auto data = &event->as.window_metadata_change;
data->window = window_id;
window_get_class(
backend,
window_id,
&data->metadata.class_name,
&data->metadata.instance_name
);
data->changed_fields =
ZDWM_WINDOW_METADATA_CHANGE_CLASS | ZDWM_WINDOW_METADATA_CHANGE_INSTANCE;
return true;
}
if (xcb_event->atom == XCB_ATOM_WM_HINTS) {
xcb_icccm_wm_hints_t hints = {0};
if (!window_get_wm_hints(backend, window_id, &hints)) return false;
event->type = ZDWM_EVENT_WINDOW_HINTS_CHANGED;
auto data = &event->as.hints;
data->window = window_id;
if (hints.flags & XCB_ICCCM_WM_HINT_X_URGENCY) {
data->changed_fields |= ZDWM_HINT_FIELD_URGENT;
data->urgent = xcb_icccm_wm_hints_get_urgency(&hints);
return true;
}
return false;
}
if (xcb_event->atom == XCB_ATOM_WM_NORMAL_HINTS) {
xcb_size_hints_t hints = {0};
if (!window_get_size_hints(backend, window_id, &hints)) return false;
event->type = ZDWM_EVENT_WINDOW_HINTS_CHANGED;
auto data = &event->as.hints;
data->window = window_id;
data->changed_fields |= ZDWM_HINT_FIELD_SIZE;
parse_size_hints(&hints, &data->min_size, &data->max_size);
return true;
}
return false;
}
static bool handle_client_message(
backend_t *backend,
event_t *event,
const xcb_client_message_event_t *xcb_event
) {
if (tray_handle_client_message(backend, xcb_event)) return false;
auto atoms = &backend->atoms;
if (xcb_event->type == atoms->_NET_ACTIVE_WINDOW) {
event->type = ZDWM_EVENT_WINDOW_ACTIVATE_REQUEST;
event->as.window_activate_request.window = xcb_event->window;
event->as.window_activate_request.source = xcb_event->data.data32[0];
return true;
}
if (xcb_event->type == atoms->WM_CHANGE_STATE) {
if (xcb_event->data.data32[0] == XCB_ICCCM_WM_STATE_ICONIC) {
event->type = ZDWM_EVENT_WINDOW_STATE_REQUEST;
event->as.window_state_request.window = xcb_event->window;
event->as.window_state_request.type = ZDWM_WINDOW_STATE_REQUEST_MINIMIZED;
event->as.window_state_request.action = ZDWM_WINDOW_STATE_ACTION_ADD;
return true;
}
return false;
}
if (xcb_event->type == atoms->_NET_WM_STATE) {
event->type = ZDWM_EVENT_WINDOW_STATE_REQUEST;
auto data = &event->as.window_state_request;
data->window = xcb_event->window;
/**
* _NET_WM_STATE 协议数据格式为
* data.data32 = {
* action, // 操作类型(添加/移除/切换)
* property1, // 第一个状态原子如_NET_WM_STATE_FULLSCREEN
* property2, // 第二个状态原子可选通常为0
* 0, // 预留字段固定为0
* 0 // 预留字段固定为0
* };
* action 值可能为
* _NET_WM_STATE_ADD1添加全屏状态窗口进入全屏模式
* _NET_WM_STATE_REMOVE0移除全屏状态窗口退出全屏模式
* _NET_WM_STATE_TOGGLE2切换全屏状态若当前全屏则退出反之进入
*/
auto action = xcb_event->data.data32[0];
switch (action) {
case 0:
data->action = ZDWM_WINDOW_STATE_ACTION_REMOVE;
break;
case 1:
data->action = ZDWM_WINDOW_STATE_ACTION_ADD;
break;
case 2:
data->action = ZDWM_WINDOW_STATE_ACTION_TOGGLE;
break;
}
auto properties = &xcb_event->data.data32[1];
uint32_t count = 2;
if (maximized_from_states(atoms, properties, count)) {
data->type = ZDWM_WINDOW_STATE_REQUEST_MAXIMIZED;
return true;
}
if (fullscreen_from_states(atoms, properties, count)) {
data->type = ZDWM_WINDOW_STATE_REQUEST_FULLSCREEN;
return true;
}
if (derive_skip_taskbar(atoms, properties, count)) {
data->type = ZDWM_WINDOW_STATE_REQUEST_SKIP_TASKBAR;
return true;
}
if (urgent_from_states(atoms, properties, count)) {
event->type = ZDWM_EVENT_WINDOW_HINTS_CHANGED;
event->as.hints = (typeof(event->as.hints)){
.urgent = true,
.changed_fields = ZDWM_HINT_FIELD_URGENT,
};
return true;
}
return false;
}
return false;
}
static bool handle_event(
backend_t *backend,
xcb_generic_event_t *raw_event,
event_t *event
) {
uint8_t response_type = XCB_EVENT_RESPONSE_TYPE(raw_event);
bool handled = false;
auto label = xcb_event_get_label(response_type);
printf("xcb event type: %s[%u]\n", label, response_type);
switch (response_type) {
#define EVENT(type, handler) \
case type: \
event_reset(event); \
handled = handler(backend, event, (void *)raw_event); \
break
EVENT(XCB_MAP_REQUEST, handle_map_request);
EVENT(XCB_UNMAP_NOTIFY, handle_unmap_notify);
EVENT(XCB_DESTROY_NOTIFY, handle_destroy_notify);
EVENT(XCB_CONFIGURE_REQUEST, handle_configure_request);
EVENT(XCB_KEY_PRESS, handle_key_press);
EVENT(XCB_BUTTON_PRESS, handle_button_press);
EVENT(XCB_BUTTON_RELEASE, handle_button_release);
EVENT(XCB_MOTION_NOTIFY, handle_motion_notify);
EVENT(XCB_ENTER_NOTIFY, handle_enter_notify);
EVENT(XCB_PROPERTY_NOTIFY, handle_property_notify);
EVENT(XCB_CLIENT_MESSAGE, handle_client_message);
#undef EVENT
default:
break;
}
p_delete(&raw_event);
if (handled) return true;
event_reset(event);
return false;
}
bool backend_poll_event(backend_t *backend, event_t *event) {
if (!backend || !backend->conn || !event) return false;
auto raw_event = xcb_poll_for_event(backend->conn);
if (!raw_event) return false;
handle_event(backend, raw_event, event);
return true;
}
bool backend_next_event(backend_t *backend, event_t *event) {
if (!backend || !backend->conn || !event) return false;
@@ -248,32 +574,6 @@ bool backend_next_event(backend_t *backend, event_t *event) {
xcb_generic_event_t *raw_event = xcb_wait_for_event(backend->conn);
if (!raw_event) return false;
uint8_t response_type = XCB_EVENT_RESPONSE_TYPE(raw_event);
bool handled = false;
printf("xcb event type: %u\n", response_type);
switch (response_type) {
#define EVENT(type, handler) \
case type: \
event_reset(event); \
handled = handler(backend, event, (void *)raw_event); \
break
EVENT(XCB_MAP_REQUEST, handle_map_request);
EVENT(XCB_UNMAP_NOTIFY, handle_unmap_notify);
EVENT(XCB_DESTROY_NOTIFY, handle_destroy_notify);
EVENT(XCB_CONFIGURE_REQUEST, handle_configure_request);
EVENT(XCB_KEY_PRESS, handle_key_press);
#undef EVENT
default:
break;
}
p_delete(&raw_event);
if (handled) return true;
event_reset(event);
if (handle_event(backend, raw_event, event)) return true;
}
}

View File

@@ -3,23 +3,16 @@
#include <stddef.h>
#include <stdint.h>
#include <xcb/xcb.h>
#include <xcb/xcb_cursor.h>
#include <xcb/xcb_keysyms.h>
#include <xcb/xproto.h>
#define ATOM_LIST(X) \
X(COMPOUND_TEXT) \
X(UTF8_STRING) \
\
X(WM_WINDOW_ROLE) \
X(WM_NAME) \
#include "common/window.h"
#include "interface/event.h"
#define EWMH_ATOMS(X) \
X(_NET_WM_NAME) \
\
X(WM_PROTOCOLS) \
X(WM_TAKE_FOCUS) \
X(WM_DELETE_WINDOW) \
X(_NET_ACTIVE_WINDOW) \
\
X(_NET_SUPPORTING_WM_CHECK) \
X(_NET_WM_PID) \
\
X(_NET_CLIENT_LIST) \
@@ -52,6 +45,26 @@
X(_NET_WM_WINDOW_TYPE_DND) \
X(_NET_WM_WINDOW_TYPE_NOTIFICATION)
#define ATOM_LIST(X) \
X(MANAGER) \
X(_NET_SYSTEM_TRAY_OPCODE) \
X(_XEMBED) \
\
X(COMPOUND_TEXT) \
X(UTF8_STRING) \
\
X(WM_WINDOW_ROLE) \
X(WM_NAME) \
X(WM_CHANGE_STATE) \
\
X(WM_PROTOCOLS) \
X(WM_TAKE_FOCUS) \
X(WM_DELETE_WINDOW) \
\
X(_NET_SUPPORTING_WM_CHECK) \
X(_NET_SUPPORTED) \
EWMH_ATOMS(X)
typedef struct atoms_t {
#define DECLARATION_ATOM(name) xcb_atom_t name;
ATOM_LIST(DECLARATION_ATOM);
@@ -60,7 +73,7 @@ typedef struct atoms_t {
typedef struct window_configure_t {
xcb_window_t window;
uint16_t mask;
uint32_t mask;
xcb_configure_window_value_list_t value;
} window_configure_t;
@@ -70,13 +83,16 @@ typedef struct window_configure_list_t {
size_t capacity;
} window_configure_list_t;
typedef struct window_list_t {
xcb_window_t *windows;
size_t count;
size_t capacity;
} window_list_t;
typedef enum cursor_t {
ZDWM_CURSOR_NORMAL,
ZDWM_CURSOR_MOVE,
ZDWM_CURSOR_RESIZE,
ZDWM_CURSOR_COUNT,
} cursor_t;
struct backend_t {
typedef struct tray_host_t tray_host_t;
typedef struct backend_t {
xcb_key_symbols_t *key_symbols;
xcb_connection_t *conn;
xcb_screen_t *screen;
@@ -92,8 +108,20 @@ struct backend_t {
xcb_window_t focus_window;
bool update_focus;
xcb_cursor_t cursors[ZDWM_CURSOR_COUNT];
xcb_cursor_context_t *cursor_context;
window_configure_list_t config_list;
window_list_t unmap;
window_list_t map;
window_list_t kill;
};
window_list_t configure;
tray_host_t *tray;
} backend_t;
bool populate_window_event(
struct backend_t *backend,
xcb_window_t window,
window_map_request_event_t *ev
);

606
src/backend/x11/tray.c Normal file
View File

@@ -0,0 +1,606 @@
#include "backend/x11/tray.h"
#include <assert.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <xcb/xcb.h>
#include <xcb/xcb_aux.h>
#include <xcb/xproto.h>
#include <zdwm/types.h>
#include "backend/x11/window.h"
#include "base/array.h"
#include "base/log.h"
#include "base/macros.h"
#include "base/memory.h"
#include "interface/tray.h"
#include "interface/types.h"
#include "internal.h"
typedef enum tray_opcode_t {
SYSTEM_TRAY_REQUEST_DOCK = 0,
SYSTEM_TRAY_BEGIN_MESSAGE = 1,
SYSTEM_TRAY_CANCEL_MESSAGE = 2,
} tray_opcode_t;
typedef enum xembed_message_t {
XEMBED_EMBEDDED_NOTIFY = 0,
} xembed_message_t;
static constexpr auto XEMBED_VERSION = 0;
typedef struct tray_icon_t {
xcb_window_t window;
int32_t natural_width;
int32_t natural_height;
} tray_icon_t;
typedef struct tray_host_t {
bool enabled;
bool initialized;
xcb_atom_t selection_atom;
xcb_window_t host_window;
xcb_window_t container;
int32_t icon_size;
tray_icon_t *icons;
size_t icon_count;
size_t icon_capacity;
tray_icons_change_cb_t icon_change_cb;
void *cb_user_data;
} tray_host_t;
static tray_host_t *get_tray_host(void *handle) {
backend_t *backend = handle;
if (!backend) return nullptr;
auto tray = backend->tray;
if (!tray || !tray->enabled) return nullptr;
return tray;
}
static size_t tray_find_icon(tray_host_t *tray, 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 tray_icon_t *tray_get_icon(tray_host_t *tray, xcb_window_t window) {
for (size_t i = 0; i < tray->icon_count; ++i) {
auto icon = &tray->icons[i];
if (icon->window == window) return icon;
}
return nullptr;
}
static inline void tray_dispatch_callback(tray_host_t *tray) {
assert(tray);
if (tray->icon_change_cb) tray->icon_change_cb(tray->cb_user_data);
}
static void tray_release_icon_window(backend_t *backend, tray_icon_t *icon) {
if (!icon || icon->window == XCB_WINDOW_NONE) return;
auto tray = get_tray_host(backend);
if (!tray || !tray->initialized) return;
auto conn = backend->conn;
auto root = backend->screen->root;
auto window = icon->window;
auto mask = XCB_CW_EVENT_MASK;
xcb_params_cw_t params = {.event_mask = XCB_EVENT_MASK_NO_EVENT};
xcb_change_save_set(conn, XCB_SET_MODE_DELETE, window);
xcb_aux_change_window_attributes(conn, window, mask, &params);
xcb_reparent_window(conn, window, root, 0, 0);
}
static void tray_remove_icon_by_index(
backend_t *backend,
size_t index,
bool release_window
) {
auto tray = backend->tray;
if (index >= tray->icon_count) return;
auto icon = &tray->icons[index];
if (release_window) tray_release_icon_window(backend, icon);
array_erase(tray->icons, tray->icon_count, index);
tray_dispatch_callback(tray);
}
static zdwm_size_t tray_get_icon_size(tray_host_t *tray, size_t index) {
auto target_size = MAX(tray->icon_size, (int32_t)1);
auto natural_width = tray->icons[index].natural_width;
auto natural_height = tray->icons[index].natural_height;
if (natural_width == 0) natural_width = target_size;
if (natural_height == 0) natural_height = target_size;
double width_scale = (double)target_size / (double)natural_width;
double height_scale = (double)target_size / (double)natural_height;
auto scale = MIN(width_scale, height_scale);
zdwm_size_t size = {
.width = (int32_t)(natural_width * scale),
.height = (int32_t)(natural_height * scale),
};
return size;
}
static void tray_layout_icons(backend_t *backend, int32_t start_x) {
auto tray = get_tray_host(backend);
if (!tray || !tray->initialized) return;
if (tray->container == XCB_WINDOW_NONE) return;
auto conn = backend->conn;
if (tray->icon_count == 0) {
xcb_unmap_window(conn, tray->container);
return;
}
auto height = tray->icon_size;
auto width = height * (int32_t)tray->icon_count;
xcb_configure_window_value_list_t value_list = {
.x = start_x,
.y = 0,
.width = (uint32_t)width,
.height = (uint32_t)height,
};
uint16_t value_mask = XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y |
XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT;
xcb_configure_window_aux(conn, tray->container, value_mask, &value_list);
xcb_map_window(conn, tray->container);
for (size_t i = 0; i < tray->icon_count; ++i) {
auto window = tray->icons[i].window;
auto icon_size = tray_get_icon_size(tray, i);
value_mask |= XCB_CONFIG_WINDOW_BORDER_WIDTH;
auto offset_x = height * (int32_t)i;
value_list = (xcb_configure_window_value_list_t){
.x = offset_x + (icon_size.width - height) / 2,
.y = (icon_size.height - height) / 2,
.width = icon_size.width,
.height = icon_size.height,
.border_width = 0,
};
xcb_configure_window_aux(conn, window, value_mask, &value_list);
xcb_map_window(conn, window);
}
}
static window_id_t tray_host_window(void *handle) {
auto tray = get_tray_host(handle);
if (!tray) return ZDWM_WINDOW_ID_INVALID;
return tray->host_window;
}
static size_t tray_icon_count(void *handle) {
auto tray = get_tray_host(handle);
return tray ? tray->icon_count : 0;
}
static int32_t tray_icon_size(void *handle) {
auto tray = get_tray_host(handle);
return tray ? tray->icon_size : 0;
}
static void tray_place(void *handle, int32_t x) {
auto tray = get_tray_host(handle);
if (!tray) return;
tray_layout_icons((backend_t *)handle, x);
}
static void
tray_set_listeners(void *handle, tray_icons_change_cb_t cb, void *user_data) {
auto tray = get_tray_host(handle);
if (!tray) return;
tray->icon_change_cb = cb;
tray->cb_user_data = user_data;
}
const tray_api_t tray_api = {
.host_window = tray_host_window,
.icon_count = tray_icon_count,
.icon_size = tray_icon_size,
.place = tray_place,
.set_listener = tray_set_listeners,
};
static bool tray_intern_selection_atom(tray_host_t *tray, backend_t *backend) {
auto conn = backend->conn;
auto screenp = backend->screenp;
char selection_name[64] = {0};
auto atom_name_format = "_NET_SYSTEM_TRAY_S%d";
snprintf(selection_name, sizeof(selection_name), atom_name_format, screenp);
uint16_t name_length = strlen(selection_name);
auto cookie = xcb_intern_atom(conn, false, name_length, selection_name);
auto reply = xcb_intern_atom_reply(conn, cookie, nullptr);
if (!reply) {
warn("cannot intern tray selection atom: %s", selection_name);
return false;
}
tray->selection_atom = reply->atom;
p_delete(&reply);
return true;
}
static bool tray_create_container_window(
tray_host_t *tray,
backend_t *backend,
tray_host_window_t host_window
) {
auto conn = backend->conn;
auto window_id = xcb_generate_id(conn);
auto depth = backend->screen->root_depth;
auto visual_id = backend->screen->root_visual;
static xcb_colormap_t colormap = XCB_NONE;
if (colormap == XCB_NONE) {
colormap = xcb_generate_id(conn);
auto root = backend->screen->root;
uint8_t alloc = XCB_COLORMAP_ALLOC_NONE;
xcb_create_colormap(conn, alloc, colormap, root, visual_id);
}
uint32_t value_mask = XCB_CW_BACK_PIXEL | XCB_CW_BORDER_PIXEL |
XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK |
XCB_CW_COLORMAP;
xcb_create_window_value_list_t value_list = {
.background_pixel = host_window.bg_pixel,
.border_pixel = 0,
.override_redirect = true,
.event_mask =
XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_STRUCTURE_NOTIFY,
.colormap = colormap,
};
auto cookie = xcb_create_window_aux_checked(
conn,
depth,
window_id,
host_window.window,
(int16_t)host_window.width,
0,
1,
(uint16_t)MAX(1, host_window.height),
0,
XCB_WINDOW_CLASS_INPUT_OUTPUT,
visual_id,
value_mask,
&value_list
);
if (xcb_request_check(conn, cookie)) {
warn("cannot create system tray container window: 0x%x", window_id);
return false;
}
tray->container = window_id;
window_set_class_instance(conn, window_id);
#define TRAY_CONTAINER_WINDOW_NAME APP_NAME "_tray"
window_set_name_static(conn, window_id, TRAY_CONTAINER_WINDOW_NAME);
auto name = TRAY_CONTAINER_WINDOW_NAME;
auto name_len = sizeof(TRAY_CONTAINER_WINDOW_NAME) - 1;
#undef TRAY_CONTAINER_WINDOW_NAME
uint8_t mode = XCB_PROP_MODE_REPLACE;
auto atoms = &backend->atoms;
auto prop = atoms->_NET_WM_NAME;
auto type = atoms->UTF8_STRING;
xcb_change_property(conn, mode, window_id, prop, type, 8, name_len, name);
return true;
}
static bool tray_take_selection_owner(tray_host_t *tray, backend_t *backend) {
auto conn = backend->conn;
auto atom = tray->selection_atom;
auto cookie = xcb_get_selection_owner(conn, atom);
auto reply = xcb_get_selection_owner_reply(conn, cookie, nullptr);
if (reply && reply->owner != XCB_WINDOW_NONE) {
warn(
"system tray selection is already owned by window: 0x%x",
reply->owner
);
p_delete(&reply);
return false;
}
p_delete(&reply);
auto owner = tray->container;
xcb_set_selection_owner(conn, owner, atom, XCB_CURRENT_TIME);
cookie = xcb_get_selection_owner(conn, atom);
reply = xcb_get_selection_owner_reply(conn, cookie, nullptr);
bool success = reply && reply->owner == owner;
p_delete(&reply);
if (!success) warn("cannot acquire system tray selection");
return success;
}
static void tray_broadcast_manager(tray_host_t *tray, backend_t *backend) {
auto conn = backend->conn;
auto root = backend->screen->root;
xcb_client_message_event_t event = {
.response_type = XCB_CLIENT_MESSAGE,
.window = root,
.format = 32,
.type = backend->atoms.MANAGER,
.data.data32 = {
[0] = XCB_CURRENT_TIME,
[1] = tray->selection_atom,
[2] = tray->container,
},
};
uint32_t event_mask = XCB_EVENT_MASK_STRUCTURE_NOTIFY;
xcb_send_event(conn, false, root, event_mask, (char *)&event);
}
void tray_init(backend_t *backend, tray_host_window_t host_window) {
auto tray = get_tray_host(backend);
if (!tray) {
tray = p_new(tray_host_t, 1);
backend->tray = tray;
}
if (tray->initialized) return;
if (!tray_intern_selection_atom(tray, backend)) return;
if (!tray_create_container_window(tray, backend, host_window)) return;
if (!tray_take_selection_owner(tray, backend)) {
tray_cleanup(backend);
return;
}
tray->enabled = true;
tray->initialized = true;
tray->host_window = host_window.window;
tray->icon_size = host_window.height;
tray_broadcast_manager(tray, backend);
tray_layout_icons(backend, host_window.width);
xcb_flush(backend->conn);
}
void tray_cleanup(backend_t *backend) {
auto tray = backend->tray;
if (!tray) return;
for (size_t i = 0; i < tray->icon_count; ++i) {
tray_release_icon_window(backend, &tray->icons[i]);
}
p_delete(&tray->icons);
tray->icon_count = 0;
tray->icon_capacity = 0;
auto conn = backend->conn;
auto atom = tray->selection_atom;
if (atom != XCB_ATOM_NONE) {
xcb_set_selection_owner(conn, XCB_WINDOW_NONE, atom, XCB_CURRENT_TIME);
}
if (tray->container != XCB_WINDOW_NONE) {
xcb_destroy_window(conn, tray->container);
}
p_clear(backend->tray, 1);
p_delete(&backend->tray);
}
static void
tray_select_icon_events(xcb_connection_t *conn, xcb_window_t window) {
xcb_params_cw_t params = {
.event_mask =
XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_PROPERTY_CHANGE,
};
uint32_t mask = XCB_CW_EVENT_MASK;
xcb_aux_change_window_attributes(conn, window, mask, &params);
xcb_clear_area(conn, false, window, 0, 0, 0, 0);
}
/* reference:
* https://specifications.freedesktop.org/xembed/latest-single/#id-1.7.4 */
typedef struct xembed_message_data_t {
uint32_t message; /* message opcode */
uint32_t detail; /* message detail */
uint32_t data1; /* message data 1 */
uint32_t data2; /* message data 2 */
} xembed_message_data_t;
static void tray_send_xembed_message(
backend_t *backend,
xcb_window_t window,
xembed_message_data_t message
) {
auto conn = backend->conn;
auto message_type = backend->atoms._XEMBED;
xcb_client_message_event_t event = {
.response_type = XCB_CLIENT_MESSAGE,
.format = 32,
.window = window,
.type = message_type,
.data.data32 = {
[0] = XCB_CURRENT_TIME,
[1] = message.message,
[2] = message.detail,
[3] = message.data1,
[4] = message.data2,
},
};
uint32_t event_mask = XCB_EVENT_MASK_NO_EVENT;
xcb_send_event(conn, false, window, event_mask, (char *)&event);
}
static void tray_add_icon(backend_t *backend, xcb_window_t window) {
if (window == XCB_WINDOW_NONE) return;
auto tray = get_tray_host(backend);
if (!tray || !tray->initialized || tray_get_icon(tray, window)) return;
rect_t geometry = {0};
if (!window_get_geometry(backend, window, &geometry)) return;
auto icon = array_push(tray->icons, tray->icon_count, tray->icon_capacity);
*icon = (tray_icon_t){
.window = window,
.natural_width = geometry.width,
.natural_height = geometry.height,
};
auto conn = backend->conn;
auto container = tray->container;
xembed_message_data_t message = {
.message = XEMBED_EMBEDDED_NOTIFY,
.detail = 0,
.data1 = container,
.data2 = XEMBED_VERSION,
};
xcb_change_save_set(conn, XCB_SET_MODE_INSERT, window);
xcb_reparent_window(conn, window, tray->container, 0, 0);
tray_select_icon_events(conn, window);
tray_send_xembed_message(backend, window, message);
xcb_map_window(conn, window);
tray_dispatch_callback(tray);
}
bool tray_handle_client_message(
backend_t *backend,
const xcb_client_message_event_t *ev
) {
auto tray = get_tray_host(backend);
if (!tray || !tray->enabled || !tray->initialized) return false;
if (ev->window != tray->container && ev->window != backend->screen->root) {
return false;
}
auto opcode = ev->data.data32[1];
switch (opcode) {
case SYSTEM_TRAY_REQUEST_DOCK:
auto window = ev->data.data32[2];
tray_add_icon(backend, window);
break;
case SYSTEM_TRAY_BEGIN_MESSAGE:
case SYSTEM_TRAY_CANCEL_MESSAGE:
default:
break;
}
xcb_flush(backend->conn);
return true;
}
bool tray_handle_configure_request(
backend_t *backend,
const xcb_configure_request_event_t *ev
) {
auto tray = get_tray_host(backend);
if (!tray || !tray->initialized) return false;
if (ev->window == tray->container) {
tray_dispatch_callback(tray);
return true;
}
auto icon = tray_get_icon(tray, ev->window);
if (!icon) return false;
if ((ev->value_mask & XCB_CONFIG_WINDOW_WIDTH) && ev->width > 0) {
icon->natural_width = (int32_t)ev->width;
}
if ((ev->value_mask & XCB_CONFIG_WINDOW_HEIGHT) && ev->height > 0) {
icon->natural_height = (int32_t)ev->height;
}
tray_dispatch_callback(tray);
return true;
}
bool tray_handle_map_request(
backend_t *backend,
const xcb_map_request_event_t *ev
) {
auto tray = get_tray_host(backend);
if (!tray || !tray->initialized) return false;
auto conn = backend->conn;
if (ev->window == tray->container) {
tray_dispatch_callback(tray);
return true;
}
auto icon = tray_get_icon(tray, ev->window);
if (!icon && ev->parent != tray->container) return false;
xcb_map_window(conn, ev->window);
xcb_flush(conn);
tray_dispatch_callback(tray);
return true;
}
bool tray_handle_property_notify(
backend_t *backend,
const xcb_property_notify_event_t *ev
) {
auto tray = get_tray_host(backend);
if (!tray || !tray->initialized) return false;
if (ev->window == XCB_WINDOW_NONE) return false;
if (ev->window == tray->container) return true;
return tray_get_icon(tray, ev->window) != nullptr;
}
bool tray_handle_unmap_notify(
backend_t *backend,
const xcb_unmap_notify_event_t *ev
) {
auto tray = get_tray_host(backend);
if (!tray || !tray->initialized) return false;
auto index = tray_find_icon(tray, ev->window);
if (index == SIZE_MAX) return ev->window == tray->container;
tray_remove_icon_by_index(backend, index, true);
return true;
}
bool tray_handle_destroy_notify(
backend_t *backend,
const xcb_destroy_notify_event_t *ev
) {
auto tray = get_tray_host(backend);
if (!tray || !tray->initialized) return false;
if (ev->window == tray->container) {
tray_cleanup(backend);
return true;
}
auto index = tray_find_icon(tray, ev->window);
if (index == SIZE_MAX) return false;
tray_remove_icon_by_index(backend, index, false);
return true;
}

45
src/backend/x11/tray.h Normal file
View File

@@ -0,0 +1,45 @@
#pragma once
#include <stdint.h>
#include <xcb/xproto.h>
#include "interface/tray.h"
extern const tray_api_t tray_api;
typedef struct backend_t backend_t;
typedef struct tray_host_window_t {
xcb_window_t window;
int32_t width;
int32_t height;
uint32_t bg_pixel;
} tray_host_window_t;
void tray_init(backend_t *backend, tray_host_window_t host_window);
void tray_cleanup(backend_t *backend);
bool tray_handle_client_message(
backend_t *backend,
const xcb_client_message_event_t *ev
);
bool tray_handle_configure_request(
backend_t *backend,
const xcb_configure_request_event_t *ev
);
bool tray_handle_map_request(
backend_t *backend,
const xcb_map_request_event_t *ev
);
bool tray_handle_property_notify(
backend_t *backend,
const xcb_property_notify_event_t *ev
);
bool tray_handle_unmap_notify(
backend_t *backend,
const xcb_unmap_notify_event_t *ev
);
bool tray_handle_destroy_notify(
backend_t *backend,
const xcb_destroy_notify_event_t *ev
);

View File

@@ -3,15 +3,15 @@
#include <stddef.h>
#include <stdint.h>
#include <xcb/xcb.h>
#include <xcb/xcb_aux.h>
#include <xcb/xcb_icccm.h>
#include <xcb/xcb_keysyms.h>
#include <xcb/xproto.h>
#include "base/array.h"
#include "backend/x11/common.h"
#include "backend/x11/cursor.h"
#include "base/macros.h"
#include "base/memory.h"
#include "core/backend.h"
#include "core/types.h"
#include "internal.h"
static char *window_get_text_property(
@@ -32,14 +32,16 @@ static char *window_get_text_property(
xcb_get_property_reply(backend->conn, cookie, nullptr);
if (!reply) return nullptr;
int length = xcb_get_property_value_length(reply);
int length = xcb_get_property_value_length(reply);
char *value = (char *)xcb_get_property_value(reply);
char *text = nullptr;
if (length && reply->format == 8 &&
(reply->type == XCB_ATOM_STRING ||
reply->type == backend->atoms.UTF8_STRING ||
reply->type == backend->atoms.COMPOUND_TEXT)) {
if (
length && reply->format == 8 &&
(reply->type == XCB_ATOM_STRING ||
reply->type == backend->atoms.UTF8_STRING ||
reply->type == backend->atoms.COMPOUND_TEXT)
) {
text = p_strndup(value, (size_t)length);
}
@@ -54,10 +56,10 @@ char *window_get_role(backend_t *backend, xcb_window_t window) {
char *window_get_title(backend_t *backend, xcb_window_t window) {
xcb_atom_t property = backend->atoms._NET_WM_NAME;
char *name = window_get_text_property(backend, window, property);
char *name = window_get_text_property(backend, window, property);
if (!name) {
property = backend->atoms.WM_NAME;
name = window_get_text_property(backend, window, property);
name = window_get_text_property(backend, window, property);
}
return name;
}
@@ -132,7 +134,7 @@ bool window_get_types(
size_t *count
) {
uint32_t atom_count = 0;
xcb_atom_t *atoms = nullptr;
xcb_atom_t *atoms = nullptr;
if (!window_get_atom_array(
backend,
window,
@@ -158,22 +160,18 @@ bool window_get_types(
return true;
}
bool window_get_fixed_size(backend_t *backend, xcb_window_t window, bool *out) {
xcb_size_hints_t hints = {0};
bool window_get_size_hints(
backend_t *backend,
xcb_window_t window,
xcb_size_hints_t *out
) {
xcb_connection_t *conn = backend->conn;
xcb_get_property_cookie_t cookie =
xcb_icccm_get_wm_normal_hints_unchecked(conn, window);
if (!xcb_icccm_get_wm_normal_hints_reply(conn, cookie, &hints, nullptr)) {
if (!xcb_icccm_get_wm_normal_hints_reply(conn, cookie, out, nullptr)) {
return false;
}
if ((hints.flags & XCB_ICCCM_SIZE_HINT_P_MIN_SIZE) &&
(hints.flags & XCB_ICCCM_SIZE_HINT_P_MAX_SIZE)) {
*out = hints.min_width == hints.max_width &&
hints.min_height == hints.max_height;
} else {
*out = false;
}
return true;
}
@@ -183,9 +181,9 @@ bool window_get_geometry(backend_t *backend, xcb_window_t window, rect_t *out) {
xcb_get_geometry_reply(backend->conn, cookie, nullptr);
if (!reply) return false;
out->x = reply->x;
out->y = reply->y;
out->width = reply->width;
out->x = reply->x;
out->y = reply->y;
out->width = reply->width;
out->height = reply->height;
p_delete(&reply);
@@ -217,7 +215,7 @@ bool window_get_atom_array(
*out_atoms =
p_copy((xcb_atom_t *)xcb_get_property_value(reply), reply->value_len);
} else {
*out_len = 0;
*out_len = 0;
*out_atoms = nullptr;
}
@@ -253,7 +251,7 @@ window_send_event(backend_t *backend, xcb_window_t window, xcb_atom_t atom) {
bool exist = false;
xcb_connection_t *conn = backend->conn;
auto WM_PROTOCOLS = backend->atoms.WM_PROTOCOLS;
auto WM_PROTOCOLS = backend->atoms.WM_PROTOCOLS;
xcb_get_property_cookie_t cookie =
xcb_icccm_get_wm_protocols_unchecked(conn, window, WM_PROTOCOLS);
@@ -268,10 +266,10 @@ window_send_event(backend_t *backend, xcb_window_t window, xcb_atom_t atom) {
if (exist) {
xcb_client_message_event_t ev = {
.response_type = XCB_CLIENT_MESSAGE,
.format = 32,
.window = window,
.type = WM_PROTOCOLS,
.data.data32 = {atom, XCB_CURRENT_TIME},
.format = 32,
.window = window,
.type = WM_PROTOCOLS,
.data.data32 = {atom, XCB_CURRENT_TIME},
};
xcb_send_event(conn, false, window, XCB_EVENT_MASK_NO_EVENT, (char *)&ev);
}
@@ -290,9 +288,9 @@ void window_kill(backend_t *backend, xcb_window_t window) {
}
void root_set_event_mask(backend_t *backend) {
xcb_connection_t *conn = backend->conn;
xcb_window_t root = backend->screen->root;
xcb_cw_t change_mask = XCB_CW_EVENT_MASK;
xcb_connection_t *conn = backend->conn;
xcb_window_t root = backend->screen->root;
xcb_cw_t change_mask = XCB_CW_EVENT_MASK;
xcb_change_window_attributes_value_list_t value_list = {
.event_mask =
XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT | XCB_EVENT_MASK_KEY_PRESS,
@@ -301,7 +299,7 @@ void root_set_event_mask(backend_t *backend) {
}
void window_set_event_mask(xcb_connection_t *conn, xcb_window_t window) {
xcb_cw_t change_mask = XCB_CW_EVENT_MASK;
xcb_cw_t change_mask = XCB_CW_EVENT_MASK;
xcb_change_window_attributes_value_list_t value_list = {
.event_mask = XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_FOCUS_CHANGE |
XCB_EVENT_MASK_PROPERTY_CHANGE |
@@ -312,22 +310,50 @@ void window_set_event_mask(xcb_connection_t *conn, xcb_window_t window) {
}
void window_clean_event_mask(xcb_connection_t *conn, xcb_window_t window) {
xcb_cw_t change_mask = XCB_CW_EVENT_MASK;
xcb_cw_t change_mask = XCB_CW_EVENT_MASK;
xcb_change_window_attributes_value_list_t value_list = {
.event_mask = XCB_EVENT_MASK_NO_EVENT,
};
xcb_change_window_attributes_aux(conn, window, change_mask, &value_list);
}
void window_list_push(window_list_t *window_list, xcb_window_t window) {
xcb_window_t *win =
array_push(window_list->windows, window_list->count, window_list->capacity);
*win = window;
void window_set_icccm_wm_state(
xcb_connection_t *conn,
xcb_window_t window,
xcb_icccm_wm_state_t state
) {
xcb_icccm_wm_hints_t hints;
xcb_icccm_wm_hints_set_none(&hints);
switch (state) {
case XCB_ICCCM_WM_STATE_WITHDRAWN:
xcb_icccm_wm_hints_set_withdrawn(&hints);
break;
case XCB_ICCCM_WM_STATE_NORMAL:
xcb_icccm_wm_hints_set_normal(&hints);
break;
case XCB_ICCCM_WM_STATE_ICONIC:
xcb_icccm_wm_hints_set_iconic(&hints);
break;
}
xcb_icccm_set_wm_hints(conn, window, &hints);
}
void window_list_reset(window_list_t *window_list) {
p_clear(window_list->windows, window_list->count);
window_list->count = 0;
void window_set_net_wm_state(
backend_t *backend,
xcb_window_t window,
size_t count,
xcb_atom_t *atoms
) {
xcb_change_property(
backend->conn,
XCB_PROP_MODE_REPLACE,
window,
backend->atoms._NET_WM_STATE,
XCB_ATOM_ATOM,
32,
count,
atoms
);
}
static uint16_t get_xcb_modifier(modifier_mask_t modifiers) {
@@ -359,7 +385,7 @@ void window_grab_keys(
const key_bind_t *keys,
size_t count
) {
auto conn = backend->conn;
auto conn = backend->conn;
auto key_symbols = backend->key_symbols;
xcb_ungrab_key(conn, XCB_GRAB_ANY, window, XCB_MOD_MASK_ANY);
@@ -367,8 +393,8 @@ void window_grab_keys(
xcb_grab_mode_t mode = XCB_GRAB_MODE_ASYNC;
for (size_t i = 0; i < count; ++i) {
auto key = &keys[i];
auto keycodes = xcb_key_symbols_get_keycode(key_symbols, keys->keysym);
auto key = &keys[i];
auto keycodes = xcb_key_symbols_get_keycode(key_symbols, key->keysym);
if (!keycodes) continue;
auto modifiers = get_xcb_modifier(key->modifiers);
@@ -378,3 +404,82 @@ void window_grab_keys(
p_delete(&keycodes);
}
}
static visual_t *get_alpha_visual(backend_t *backend) {
auto depth_iter = xcb_screen_allowed_depths_iterator(backend->screen);
for (; depth_iter.rem; xcb_depth_next(&depth_iter)) {
if (depth_iter.data->depth != 32) continue;
auto visual_iter = xcb_depth_visuals_iterator(depth_iter.data);
for (; visual_iter.rem; xcb_visualtype_next(&visual_iter)) {
if (visual_iter.data->_class == XCB_VISUAL_CLASS_TRUE_COLOR) {
auto visual = p_new(visual_t, 1);
visual->visual = visual_iter.data;
visual->depth = depth_iter.data->depth;
return visual;
}
}
}
return nullptr;
}
static visual_t *get_root_visual(backend_t *backend) {
auto depth_iter = xcb_screen_allowed_depths_iterator(backend->screen);
auto visual_id = backend->screen->root_visual;
for (; depth_iter.rem; xcb_depth_next(&depth_iter)) {
auto visual_iter = xcb_depth_visuals_iterator(depth_iter.data);
for (; visual_iter.rem; xcb_visualtype_next(&visual_iter)) {
if (visual_iter.data->visual_id == visual_id) {
auto visual = p_new(visual_t, 1);
visual->visual = visual_iter.data;
visual->depth = depth_iter.data->depth;
return visual;
}
}
}
return nullptr;
}
visual_t *window_get_visual(backend_t *backend, bool prefer_alpha) {
if (!prefer_alpha) return get_root_visual(backend);
auto visual = get_alpha_visual(backend);
if (!visual) visual = get_root_visual(backend);
return visual;
}
void window_change_cursor(
backend_t *backend,
xcb_window_t window,
cursor_t cursor
) {
auto conn = backend->conn;
xcb_params_cw_t params = {.cursor = cursor_get_xcb_cursor(backend, cursor)};
xcb_aux_change_window_attributes(conn, window, XCB_CW_CURSOR, &params);
}
void window_grab_button(
backend_t *backend,
xcb_window_t window,
button_t button,
modifier_mask_t modifiers
) {
auto xcb_button = button_zdwm_to_xcb(button);
if (xcb_button == XCB_BUTTON_INDEX_ANY) return;
xcb_grab_button(
backend->conn,
false, /* 事件仅发送给抓取者,不发送给其他窗口 */
window,
XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE,
XCB_GRAB_MODE_ASYNC,
XCB_GRAB_MODE_SYNC,
XCB_WINDOW_NONE,
XCB_CURSOR_NONE,
xcb_button,
modifiers_zdwm_to_xcb(modifiers)
);
}

View File

@@ -3,10 +3,12 @@
#include <stdbool.h>
#include <stddef.h>
#include <string.h>
#include <xcb/xcb.h>
#include <xcb/xcb_icccm.h>
#include <xcb/xproto.h>
#include "core/backend.h"
#include "base/app.h"
#include "interface/backend.h"
#include "internal.h"
typedef struct atoms_t atoms_t;
@@ -52,7 +54,11 @@ bool window_get_types(
window_type_t **types,
size_t *count
);
bool window_get_fixed_size(backend_t *backend, xcb_window_t window, bool *out);
bool window_get_size_hints(
backend_t *backend,
xcb_window_t window,
xcb_size_hints_t *out
);
bool window_get_geometry(backend_t *backend, xcb_window_t window, rect_t *out);
bool window_get_atom_array(
backend_t *backend,
@@ -72,7 +78,7 @@ window_state_t atom_to_window_state(const atoms_t *atoms, xcb_atom_t atom);
xcb_icccm_set_wm_name(conn, win, XCB_ATOM_STRING, 8, sizeof(name) - 1, name)
#define window_set_class_instance(conn, win) \
window_set_class_instance_static(conn, win, "zdwm", "zdwm")
window_set_class_instance_static(conn, win, APP_NAME, APP_NAME)
#define window_set_class_instance_static(conn, win, instance, class) \
_window_set_class_instance_static(conn, win, instance "\0" class)
#define _window_set_class_instance_static(conn, win, instance_class) \
@@ -84,9 +90,17 @@ void window_kill(backend_t *backend, xcb_window_t window);
void root_set_event_mask(backend_t *backend);
void window_set_event_mask(xcb_connection_t *conn, xcb_window_t window);
void window_clean_event_mask(xcb_connection_t *conn, xcb_window_t window);
void window_list_push(window_list_t *window_list, xcb_window_t window);
void window_list_reset(window_list_t *window_list);
void window_set_icccm_wm_state(
xcb_connection_t *conn,
xcb_window_t window,
xcb_icccm_wm_state_t state
);
void window_set_net_wm_state(
backend_t *backend,
xcb_window_t window,
size_t count,
xcb_atom_t *atoms
);
void window_grab_keys(
backend_t *backend,
@@ -94,3 +108,30 @@ void window_grab_keys(
const key_bind_t *keys,
size_t count
);
typedef struct visual_t {
uint8_t depth;
xcb_visualtype_t *visual;
} visual_t;
/**
* @brief 获取 xcb 的 visual 及其对应的 depth
*
* @param prefer_alpha 支持 alpha 的 visual 优先
*
* @returns 返回的信息使用后记得使用 free 释放
*/
visual_t *window_get_visual(backend_t *backend, bool prefer_alpha);
void window_change_cursor(
backend_t *backend,
xcb_window_t window,
cursor_t cursor
);
void window_grab_button(
backend_t *backend,
xcb_window_t window,
button_t button,
modifier_mask_t modifiers
);

View File

@@ -1,37 +0,0 @@
#include "backtrace.h"
#include "app.h"
#include "buffer.h"
#ifdef HAS_EXECINFO
#include <execinfo.h>
#endif
#define MAX_STACK_SIZE 32
/**
* Get a backtrace.
* @param buffer The buffer to fill with backtrace.
*/
void backtrace_get(buffer_t *buffer) {
buffer_init(buffer);
#ifdef HAS_EXECINFO
void *stack[MAX_STACK_SIZE];
char **bt;
int stack_size;
stack_size = backtrace(stack, countof(stack));
bt = backtrace_symbols(stack, stack_size);
if (bt) {
for (int i = 0; i < stack_size; i++) {
if (i > 0) buffer_addsl(buffer, "\n");
buffer_adds(buffer, bt[i]);
}
p_delete(&bt);
} else
#endif
buffer_addsl(buffer, "Cannot get backtrace symbols.");
}

View File

@@ -1,5 +0,0 @@
#pragma once
#include "buffer.h"
void backtrace_get(buffer_t *buffer);

527
src/bar/bar.c Normal file
View File

@@ -0,0 +1,527 @@
#include "bar/bar.h"
#include <assert.h>
#include <cairo.h>
#include <stddef.h>
#include <stdint.h>
#include <unistd.h>
#include <zdwm/action.h>
#include <zdwm/bar.h>
#include <zdwm/types.h>
#include "bar/binding.h"
#include "bar/cell.h"
#include "bar/text.h"
#include "bar/tray.h"
#include "bar/types.h"
#include "bar/windows.h"
#include "bar/workspaces.h"
#include "base/array.h"
#include "base/color.h"
#include "base/macros.h"
#include "base/memory.h"
#include "base/time.h"
static zdwm_bar_item_t *bar_add_item(
zdwm_output_id_t output_id,
bar_side_t *side,
zdwm_bar_item_type_t item_type,
void *config
) {
auto item = array_push(side->items, side->count, side->capacity);
item->api = item_type;
auto create_state = item_type.create_state;
if (create_state) item->state = create_state(output_id, config);
return item;
}
static void bar_item_cleanup(zdwm_bar_item_t *item) {
auto destroy_state = item->api.destroy_state;
if (destroy_state) destroy_state(item->state);
item->state = nullptr;
for (size_t i = 0; i < item->count; ++i) {
auto cell = &item->cells[i];
p_delete(&cell->text);
p_delete(&cell->fg_text);
p_delete(&cell->bg_text);
}
p_delete(&item->cells);
p_clear(item, 1);
}
#define VALUE(value, fallback) ((value) ? (value) : (fallback))
void bar_output_add_workspace(
bar_output_t *bar_output,
zdwm_bar_config_t *config,
listeners_t *listeners
) {
bar_workspace_config_t workspace_config = {
.cell_padding = VALUE(config->tag_cell_padding_x, config->padding_x),
.indicator_width = VALUE(config->tag_indicator_width, 4),
.bg = VALUE(config->tag_bg, config->bg),
.fg = VALUE(config->tag_fg, config->fg),
.active_bg = config->tag_active_bg,
.active_fg = config->tag_active_fg,
.urgent_bg = config->tag_urgent_bg,
.urgent_fg = config->tag_urgent_fg,
.layout_bg = VALUE(config->layout_bg, config->bg),
.layout_fg = VALUE(config->layout_fg, config->fg),
};
auto item = bar_add_item(
bar_output->output_id,
&bar_output->left,
bar_workspace,
&workspace_config
);
item->cell_padding = workspace_config.cell_padding;
item->indicator_width = workspace_config.indicator_width;
bar_workspace_add_listeners(listeners, item->state);
}
static void bar_output_add_binding(
bar_output_t *bar_output,
zdwm_bar_config_t *config,
listeners_t *listeners
) {
bar_binding_config_t binding_config = {
.show_default = config->binding_show_default,
.cell_padding = VALUE(config->binding_padding_x, config->padding_x),
.bg = VALUE(config->binding_mode_bg, config->bg),
.fg = VALUE(config->binding_mode_fg, config->fg),
};
auto item = bar_add_item(
bar_output->output_id,
&bar_output->left,
bar_binding,
&binding_config
);
item->cell_padding = binding_config.cell_padding;
bar_binding_add_listeners(listeners, item->state);
}
static void bar_output_add_windows(
bar_output_t *bar_output,
zdwm_bar_config_t *config,
listeners_t *listeners
) {
auto item = &bar_output->center;
bar_windows_config_t window_config = {
.cell_padding = VALUE(config->window_padding_x, config->padding_x),
.bg = VALUE(config->window_bg, config->bg),
.fg = VALUE(config->window_fg, config->fg),
.focused_bg = VALUE(config->window_focused_bg, config->bg),
.focused_fg = VALUE(config->window_focused_fg, config->fg),
};
item->cell_padding = window_config.cell_padding;
item->api = bar_windows;
auto create_state = item->api.create_state;
auto output_id = bar_output->output_id;
if (create_state) item->state = create_state(output_id, &window_config);
bar_windows_add_listeners(listeners, item->state);
}
static inline void bar_output_add_tray(bar_output_t *bar_output, tray_t *tray) {
bar_add_item(bar_output->output_id, &bar_output->right, bar_tray, tray);
}
void bar_init(bar_t *bar, listeners_t *listeners) {
auto c = &bar->config;
bar->ctx = text_context_create(c->font_family, c->font_size, c->dpi);
auto tray = &bar->tray;
for (size_t i = 0; i < bar->count; ++i) {
auto bar_output = &bar->bars[i];
bar_output->height = bar->config.height;
bar_output_add_workspace(bar_output, &bar->config, listeners);
bar_output_add_binding(bar_output, &bar->config, listeners);
bar_output_add_windows(bar_output, &bar->config, listeners);
if (tray->api.host_window(tray->handle) == bar_output->window_id) {
bar_output_add_tray(bar_output, &bar->tray);
}
}
bar->timerfd = time_create_monotonic_timerfd_by_fps(bar->config.fps);
}
static void bar_side_cleanup(bar_side_t *side) {
for (size_t i = 0; i < side->count; ++i) bar_item_cleanup(&side->items[i]);
p_delete(&side->items);
p_clear(side, 1);
}
void bar_cleanup(bar_t *bar) {
close(bar->timerfd);
bar->timerfd = -1;
text_context_destory(bar->ctx);
bar->ctx = nullptr;
for (size_t i = 0; i < bar->count; ++i) {
auto bar_output = &bar->bars[i];
bar_side_cleanup(&bar_output->left);
bar_side_cleanup(&bar_output->right);
bar_item_cleanup(&bar_output->center);
cairo_destroy(bar_output->cr);
bar_output->cr = nullptr;
}
p_delete(&bar->bars);
bar->count = 0;
}
static inline void bar_item_update(zdwm_bar_item_t *item) {
auto update = item->api.update;
if (!update) return;
auto update_interval_ms = item->api.update_interval_ms;
auto last_updated_time = item->last_updated_time;
auto now = time_monotonic_ms();
if (now < last_updated_time + update_interval_ms) return;
item->last_updated_time = now;
update(item, &bar_cell_api, item->state);
}
static inline void bar_side_update(bar_side_t *side) {
for (size_t i = 0; i < side->count; ++i) {
bar_item_update(&side->items[i]);
}
}
void bar_update(bar_t *bar) {
for (size_t i = 0; i < bar->count; ++i) {
auto bar_output = &bar->bars[i];
bar_side_update(&bar_output->left);
bar_side_update(&bar_output->right);
bar_item_update(&bar_output->center);
}
}
static void bar_output_layout(bar_output_t *bar_output, text_context_t *ctx) {
int32_t start = 0;
auto end = bar_output->width;
auto left = &bar_output->left;
for (size_t i = 0; i < left->count; ++i) {
auto item = &left->items[i];
item->region.start = start;
for (size_t j = 0; j < item->count; ++j) {
auto cell = &item->cells[j];
auto fixed_width = cell->fixed_width;
bar_x_region_t region = {.start = start, .end = start};
if (fixed_width == 0) {
int32_t width = 0;
text_context_get_text_size(ctx, cell->text, &width, nullptr);
region.end += width + 2 * item->cell_padding;
} else if (fixed_width > 0) {
region.end += fixed_width;
}
bar_cell_set_region(item, j, region);
start = region.end;
}
item->region.end = start;
}
if (left->count) {
left->region.start = left->items[0].region.start;
left->region.end = left->items[left->count - 1].region.end;
}
auto right = &bar_output->right;
for (size_t i = right->count; i > 0; --i) {
auto item_index = i - 1;
auto item = &right->items[item_index];
item->region.end = end;
for (size_t j = item->count; j > 0; --j) {
auto cell_index = j - 1;
auto cell = &item->cells[cell_index];
auto fixed_width = cell->fixed_width;
bar_x_region_t region = {.start = end, .end = end};
if (fixed_width == 0) {
int32_t width = 0;
text_context_get_text_size(ctx, cell->text, &width, nullptr);
region.start -= width + item->cell_padding * 2;
} else if (fixed_width > 0) {
region.start -= fixed_width;
}
bar_cell_set_region(item, cell_index, region);
end = region.start;
}
item->region.start = end;
}
if (right->count) {
right->region.start = right->items[0].region.start;
right->region.end = right->items[right->count - 1].region.end;
}
auto center = &bar_output->center;
if (center->count == 0) return;
center->region = (bar_x_region_t){.start = start, .end = end};
auto center_width = end - start;
auto center_count = center->count;
for (size_t i = 0; i < center->count; ++i) {
auto cell = &center->cells[i];
auto fixed_width = cell->fixed_width;
if (fixed_width > 0) {
center_width -= fixed_width;
} else if (fixed_width < 0) {
/* 隐藏的 cell 不占任何空间,平分宽度时不参与 */
--center_count;
}
}
auto width = center_width / (int32_t)center_count;
for (size_t i = 0; i < center->count; ++i) {
auto cell = &center->cells[i];
auto fixed_width = cell->fixed_width;
bar_x_region_t region = {.start = start, .end = start};
if (fixed_width == 0) {
region.end += width + center->cell_padding * 2;
} else if (fixed_width > 0) {
region.end += fixed_width;
}
start = region.end;
bar_cell_set_region(center, i, region);
}
}
static void bar_item_draw(
cairo_t *cr,
zdwm_bar_item_t *item,
text_context_t *ctx,
int32_t height
) {
for (size_t i = 0; i < item->count; ++i) {
auto cell = &item->cells[i];
auto region = &cell->region;
zdwm_rect_t cell_area = {
.x = region->start,
.y = 0,
.width = region->end - region->start,
.height = height,
};
if (cell_area.width <= 0) {
cell->dirty = false;
continue;
}
draw_background(cr, &cell->bg, cell_area);
if (cell->show_indicator) {
auto size = MIN(item->indicator_width, cell_area.width);
size = MIN(size, height);
if (size > 0) {
zdwm_rect_t indicator_area = {
.x = region->start,
.y = 0,
.width = size,
.height = size,
};
draw_background(cr, &cell->fg, indicator_area);
}
}
zdwm_rect_t text_area = {
.x = cell_area.x + item->cell_padding,
.y = 0,
.width = cell_area.width - item->cell_padding * 2,
.height = height,
};
if (text_area.width > 0 && cell->text && cell->text[0] != '\0') {
draw_text(cr, ctx, cell->text, &cell->fg, text_area);
}
cell->dirty = false;
}
item->dirty = false;
}
static bool bar_item_is_dirty(zdwm_bar_item_t *item) {
if (item->dirty) return true;
for (size_t i = 0; i < item->count; ++i) {
auto cell = &item->cells[i];
if (cell->dirty) return true;
}
return false;
}
static bool bar_side_is_dirty(bar_side_t *side) {
for (size_t j = 0; j < side->count; ++j) {
auto item = &side->items[j];
if (bar_item_is_dirty(item)) return true;
}
return false;
}
static inline bool bar_output_is_dirty(bar_output_t *bar_output) {
if (bar_side_is_dirty(&bar_output->left)) return true;
if (bar_side_is_dirty(&bar_output->right)) return true;
if (bar_item_is_dirty(&bar_output->center)) return true;
return false;
}
static inline void clean_cairo_context(cairo_t *cr) {
cairo_save(cr);
cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR);
cairo_paint(cr);
cairo_restore(cr);
}
static void
bar_output_draw(bar_output_t *bar_output, text_context_t *ctx, color_t *bg) {
auto cr = bar_output->cr;
clean_cairo_context(cr);
zdwm_rect_t output_area = {
.x = 0,
.y = 0,
.width = bar_output->width,
.height = bar_output->height,
};
draw_background(cr, bg, output_area);
auto left = &bar_output->left;
for (size_t i = 0; i < left->count; ++i) {
bar_item_draw(cr, &left->items[i], ctx, bar_output->height);
}
auto right = &bar_output->right;
for (size_t i = 0; i < right->count; ++i) {
bar_item_draw(cr, &right->items[i], ctx, bar_output->height);
}
bar_item_draw(cr, &bar_output->center, ctx, bar_output->height);
}
static inline void bar_item_run_hook(
zdwm_bar_item_t *item,
void (*hook)(const zdwm_bar_item_hook_params_t *params)
) {
if (!hook) return;
zdwm_bar_item_hook_params_t params = {
.item = item,
.cell_api = &bar_cell_api,
.region = item->region,
.state = item->state
};
hook(&params);
}
static void visitor_after_layout(zdwm_bar_item_t *item) {
bar_item_run_hook(item, item->api.after_layout);
}
static void visitor_after_draw(zdwm_bar_item_t *item) {
bar_item_run_hook(item, item->api.after_draw);
}
typedef void (*bar_item_visitor_t)(zdwm_bar_item_t *item);
static void
bar_output_foreach_item(bar_output_t *bar_output, bar_item_visitor_t visit) {
for (size_t i = 0; i < bar_output->left.count; ++i) {
visit(&bar_output->left.items[i]);
}
visit(&bar_output->center);
for (size_t i = 0; i < bar_output->right.count; ++i) {
visit(&bar_output->right.items[i]);
}
}
bool bar_draw(bar_t *bar) {
auto ctx = bar->ctx;
bool changed = false;
for (size_t i = 0; i < bar->count; ++i) {
auto bar_output = &bar->bars[i];
if (!bar_output_is_dirty(bar_output)) continue;
bar_output_layout(bar_output, ctx);
bar_output_foreach_item(bar_output, visitor_after_layout);
bar_output_draw(bar_output, ctx, &bar->palette.bg);
bar_output_foreach_item(bar_output, visitor_after_draw);
changed = true;
}
return changed;
}
static inline bool bar_item_click(
zdwm_bar_item_t *item,
bar_click_info_t info,
zdwm_action_t *action
) {
auto x = info.x;
if (!(item->region.start <= x && item->region.end >= x)) return false;
if (!item->api.on_click) return true;
for (size_t i = 0; i < item->count; ++i) {
auto cell = &item->cells[i];
if (cell->region.start <= x && cell->region.end >= x) {
zdwm_bar_click_params_t params = {
.item = item,
.cell_index = i,
.x = x,
.modifiers = info.modifiers,
.button = info.button,
.state = item->state,
};
*action = item->api.on_click(&params);
return true;
}
}
return true;
}
static inline bool
bar_side_click(bar_side_t *side, bar_click_info_t info, zdwm_action_t *action) {
if (side->region.start > info.x || side->region.end < info.x) return false;
for (size_t i = 0; i < side->count; ++i) {
if (bar_item_click(&side->items[i], info, action)) return true;
}
return true;
}
bool bar_click(bar_t *bar, bar_click_info_t info, zdwm_action_t *action) {
for (size_t i = 0; i < bar->count; ++i) {
auto bar_output = &bar->bars[i];
if (bar_output->window_id != info.window) continue;
if (bar_side_click(&bar_output->left, info, action)) return true;
if (bar_item_click(&bar_output->center, info, action)) return true;
if (bar_side_click(&bar_output->right, info, action)) return true;
}
return false;
}

55
src/bar/bar.h Normal file
View File

@@ -0,0 +1,55 @@
#pragma once
#include <cairo.h>
#include <stddef.h>
#include <stdint.h>
#include <zdwm/action.h>
#include <zdwm/bar.h>
#include <zdwm/types.h>
#include "bar/text.h"
#include "bar/types.h"
#include "base/color.h"
#include "common/listeners.h"
#include "interface/tray.h"
typedef struct bar_output_t {
cairo_t *cr;
zdwm_window_id_t window_id;
zdwm_output_id_t output_id;
int32_t width;
int32_t height;
bar_side_t left;
bar_side_t right;
zdwm_bar_item_t center;
} bar_output_t;
typedef struct bar_palette_t {
color_t bg;
} bar_palette_t;
typedef struct bar_t {
bar_output_t *bars;
size_t count;
bool visible;
int timerfd;
zdwm_bar_config_t config;
bar_palette_t palette;
text_context_t *ctx;
tray_t tray;
} bar_t;
void bar_init(bar_t *bar, listeners_t *listeners);
void bar_cleanup(bar_t *bar);
void bar_update(bar_t *bar);
bool bar_draw(bar_t *bar);
typedef struct bar_click_info_t {
zdwm_window_id_t window;
int32_t x;
zdwm_modifier_mask_t modifiers;
zdwm_button_t button;
} bar_click_info_t;
bool bar_click(bar_t *bar, bar_click_info_t info, zdwm_action_t *action);

99
src/bar/binding.c Normal file
View File

@@ -0,0 +1,99 @@
#include "bar/binding.h"
#include <assert.h>
#include <stddef.h>
#include <stdint.h>
#include <zdwm/action.h>
#include <zdwm/bar.h>
#include <zdwm/listeners.h>
#include <zdwm/types.h>
#include "base/memory.h"
#include "common/listeners.h"
typedef struct bar_binding_state_t {
zdwm_binding_mode_notify_t mode;
bar_binding_config_t config;
bool inited;
bool dirty;
} bar_binding_state_t;
static void *
bar_binding_create_state(zdwm_output_id_t output_id, void *config) {
const bar_binding_config_t *bar_config = config;
assert(bar_config);
auto state = p_new(bar_binding_state_t, 1);
state->config = *bar_config;
return state;
}
static void bar_binding_update(
zdwm_bar_item_t *item,
const zdwm_bar_cell_api_t *cells,
void *state
) {
auto data = (bar_binding_state_t *)state;
if (!data->inited || !data->dirty) return;
auto config = &data->config;
auto mode = &data->mode;
if (!config->show_default && mode->is_default_mode) {
cells->set_cell_count(item, 0);
} else {
constexpr auto index = 0;
cells->set_cell_count(item, 1);
cells->cell_set_text(item, index, mode->name);
cells->cell_set_bg(item, index, config->bg);
cells->cell_set_fg(item, index, config->fg);
}
data->dirty = false;
}
static zdwm_action_t bar_binding_on_click(zdwm_bar_click_params_t *params) {
return (zdwm_action_t){
.type = ZDWM_ACTION_BINDING_MODE_CYCLE,
.as.binding_mode_cycle = {.delta = 1},
};
}
static void bar_binding_destroy_state(void *state) {
if (!state) return;
auto data = (bar_binding_state_t *)state;
p_delete(&data);
}
zdwm_bar_item_type_t bar_binding = {
.create_state = bar_binding_create_state,
.update = bar_binding_update,
.on_click = bar_binding_on_click,
.destroy_state = bar_binding_destroy_state,
.update_interval_ms = 0,
};
static void
bar_binding_mode_notify(zdwm_binding_mode_notify_t mode, void *user_data) {
auto state = (bar_binding_state_t *)user_data;
if (!state->inited) {
state->mode = mode;
state->inited = true;
state->dirty = true;
return;
}
if (state->mode.id == mode.id) return;
state->mode = mode;
state->dirty = true;
}
void bar_binding_add_listeners(listeners_t *listeners, void *state) {
listeners_add_binding_mode_notify(listeners, bar_binding_mode_notify, state);
}

17
src/bar/binding.h Normal file
View File

@@ -0,0 +1,17 @@
#pragma once
#include <stdint.h>
#include <zdwm/bar.h>
#include "common/listeners.h"
typedef struct bar_binding_config_t {
bool show_default;
uint32_t cell_padding;
const char *bg;
const char *fg;
} bar_binding_config_t;
extern zdwm_bar_item_type_t bar_binding;
void bar_binding_add_listeners(listeners_t *listeners, void *state);

173
src/bar/cell.c Normal file
View File

@@ -0,0 +1,173 @@
#include "bar/cell.h"
#include <assert.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include <strings.h>
#include <zdwm/types.h>
#include "bar/types.h"
#include "base/color.h"
#include "base/memory.h"
static size_t bar_cell_get_count(zdwm_bar_item_t *item) { return item->count; }
static void bar_cell_set_count(zdwm_bar_item_t *item, size_t count) {
if (item->count == count) return;
for (size_t i = 0; i < item->count; ++i) {
auto cell = &item->cells[i];
p_delete(&cell->text);
p_delete(&cell->fg_text);
p_delete(&cell->bg_text);
}
item->count = count;
p_realloc(&item->cells, count);
p_clear(item->cells, count);
item->dirty = true;
}
static void
bar_cell_set_text(zdwm_bar_item_t *item, size_t index, const char *text) {
if (index >= item->count) return;
auto cell = &item->cells[index];
if (cell->text && text && strcmp(cell->text, text) == 0) return;
p_delete(&cell->text);
cell->text = p_strdup_nullable(text);
cell->dirty = true;
item->dirty = true;
}
static void
bar_cell_set_bg(zdwm_bar_item_t *item, size_t index, const char *color) {
if (index >= item->count) return;
auto cell = &item->cells[index];
if (cell->bg_text && strcmp(color, cell->bg_text) == 0) return;
p_delete(&cell->bg_text);
cell->bg_text = p_strdup(color);
color_parse(color, &cell->bg);
cell->dirty = true;
item->dirty = true;
}
static void
bar_cell_set_fg(zdwm_bar_item_t *item, size_t index, const char *color) {
if (index >= item->count) return;
auto cell = &item->cells[index];
if (cell->fg_text && strcmp(color, cell->fg_text) == 0) return;
p_delete(&cell->fg_text);
cell->fg_text = p_strdup(color);
color_parse(color, &cell->fg);
cell->dirty = true;
item->dirty = true;
}
static void
bar_cell_set_icon(zdwm_bar_item_t *item, size_t index, zdwm_icon_t icon) {
if (index >= item->count) return;
auto cell = &item->cells[index];
auto icon_ptr = &cell->icon;
if (icon_ptr->type == icon.type) {
switch (icon.type) {
case ZDWM_ICON_TEXT:
if (
icon_ptr->as.text == icon.as.text ||
strcmp(icon_ptr->as.text, icon.as.text) == 0
) {
return;
}
case ZDWM_ICON_IMAGE:
if (
icon_ptr->as.image_path == icon.as.image_path ||
strcmp(icon_ptr->as.image_path, icon.as.image_path) == 0
) {
return;
}
}
}
*icon_ptr = icon;
cell->dirty = true;
item->dirty = true;
}
static void
bar_cell_set_indicator(zdwm_bar_item_t *item, size_t index, bool show) {
if (index >= item->count) return;
auto cell = &item->cells[index];
if (cell->show_indicator == show) return;
cell->show_indicator = show;
cell->dirty = true;
item->dirty = true;
}
static void
bar_cell_set_fixed_width(zdwm_bar_item_t *item, size_t index, int32_t width) {
if (index >= item->count) return;
auto cell = &item->cells[index];
if (cell->fixed_width == width) return;
cell->fixed_width = width;
cell->dirty = true;
item->dirty = true;
}
static bar_x_region_t bar_cell_get_region(zdwm_bar_item_t *item, size_t index) {
if (index >= item->count) return (bar_x_region_t){0};
auto cell = &item->cells[index];
return cell->region;
}
/* clang-format off */
const zdwm_bar_cell_api_t bar_cell_api = {
.get_cell_count = bar_cell_get_count,
.set_cell_count = bar_cell_set_count,
.cell_set_text = bar_cell_set_text,
.cell_set_bg = bar_cell_set_bg,
.cell_set_fg = bar_cell_set_fg,
.cell_set_icon = bar_cell_set_icon,
.cell_set_indicator = bar_cell_set_indicator,
.cell_set_fixed_width = bar_cell_set_fixed_width,
.cell_get_region = bar_cell_get_region,
};
/* clang-format on */
void bar_cell_set_region(
zdwm_bar_item_t *item,
size_t index,
bar_x_region_t region
) {
if (index >= item->count) return;
auto cell = &item->cells[index];
auto r = &cell->region;
if (r->start == region.start && r->end == region.end) return;
*r = region;
cell->dirty = true;
item->dirty = true;
}

15
src/bar/cell.h Normal file
View File

@@ -0,0 +1,15 @@
#pragma once
#include <stddef.h>
#include <zdwm/bar.h>
#include <zdwm/types.h>
#include "bar/types.h"
void bar_cell_set_region(
zdwm_bar_item_t *item,
size_t index,
bar_x_region_t region
);
extern const zdwm_bar_cell_api_t bar_cell_api;

141
src/bar/text.c Normal file
View File

@@ -0,0 +1,141 @@
#include "bar/text.h"
#include <assert.h>
#include <cairo.h>
#include <glib-object.h>
#include <pango/pango-attributes.h>
#include <pango/pango-context.h>
#include <pango/pango-font.h>
#include <pango/pango-layout.h>
#include <pango/pango-types.h>
#include <pango/pangocairo.h>
#include <stdint.h>
#include "base/memory.h"
struct text_context_t {
PangoContext *context;
PangoLayout *layout;
PangoAttrList *attr_list;
};
text_context_t *
text_context_create(const char *family, uint32_t size, uint32_t dpi) {
auto fontmap = pango_cairo_font_map_new();
auto context = pango_font_map_create_context(fontmap);
pango_cairo_context_set_resolution(context, (double)dpi);
auto desc = pango_font_description_from_string(family);
pango_font_description_set_size(desc, size * PANGO_SCALE);
auto attr = pango_attr_font_desc_new(desc);
auto attr_list = pango_attr_list_new();
pango_attr_list_insert(attr_list, attr);
auto layout = pango_layout_new(context);
pango_layout_set_attributes(layout, attr_list);
pango_layout_set_wrap(layout, PANGO_WRAP_NONE);
pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_END);
pango_font_description_free(desc);
g_object_unref(fontmap);
auto ctx = p_new(text_context_t, 1);
*ctx = (text_context_t){
.context = context,
.layout = layout,
.attr_list = attr_list,
};
return ctx;
}
void text_context_destory(text_context_t *context) {
if (!context) return;
if (context->layout) {
g_object_unref(context->layout);
context->layout = nullptr;
}
if (context->attr_list) {
pango_attr_list_unref(context->attr_list);
context->attr_list = nullptr;
}
if (context->context) {
g_object_unref(context->context);
context->context = nullptr;
}
p_delete(&context);
}
static inline void
get_layout_size(PangoLayout *layout, int32_t *width, int32_t *height) {
pango_layout_get_pixel_size(layout, width, height);
}
void text_context_get_text_size(
text_context_t *context,
const char *text,
int32_t *width,
int32_t *height
) {
assert(context && text);
if (!width && !height) return;
auto layout = context->layout;
pango_layout_set_width(layout, -1);
pango_layout_set_height(layout, 0);
pango_layout_set_text(layout, text, -1);
get_layout_size(layout, width, height);
}
void draw_text(
cairo_t *cr,
text_context_t *context,
const char *text,
const color_t *color,
zdwm_rect_t area
) {
assert(cr);
assert(context);
assert(text);
assert(color);
auto layout = context->layout;
pango_layout_set_width(layout, area.width * PANGO_SCALE);
pango_layout_set_text(layout, text, -1);
cairo_set_source_rgba(
cr,
color->red,
color->green,
color->blue,
color->alpha
);
int32_t height = 0;
get_layout_size(layout, nullptr, &height);
double offset_y = (area.height - height) / 2.0;
pango_cairo_update_layout(cr, layout);
cairo_move_to(cr, area.x, offset_y + area.y);
pango_cairo_show_layout(cr, layout);
}
void draw_background(cairo_t *cr, const color_t *color, zdwm_rect_t area) {
cairo_move_to(cr, area.x, area.y);
cairo_set_source_rgba(
cr,
color->red,
color->green,
color->blue,
color->alpha
);
cairo_rectangle(cr, area.x, area.y, area.width, area.height);
cairo_fill(cr);
}

27
src/bar/text.h Normal file
View File

@@ -0,0 +1,27 @@
#pragma once
#include <cairo.h>
#include <stdint.h>
#include <zdwm/types.h>
#include "base/color.h"
typedef struct text_context_t text_context_t;
text_context_t *
text_context_create(const char *family, uint32_t size, uint32_t dpi);
void text_context_destory(text_context_t *context);
void text_context_get_text_size(
text_context_t *context,
const char *text,
int32_t *width,
int32_t *height
);
void draw_text(
cairo_t *cr,
text_context_t *context,
const char *text,
const color_t *color,
zdwm_rect_t area
);
void draw_background(cairo_t *cr, const color_t *color, zdwm_rect_t area);

78
src/bar/tray.c Normal file
View File

@@ -0,0 +1,78 @@
#include "bar/tray.h"
#include <assert.h>
#include <stddef.h>
#include <zdwm/bar.h>
#include <zdwm/types.h>
#include "base/memory.h"
#include "interface/tray.h"
typedef struct bar_tray_state_t {
tray_t tray;
bool dirty;
} bar_tray_state_t;
static void bar_tray_icons_changed(void *user_data) {
bar_tray_state_t *state = user_data;
state->dirty = true;
}
static void *bar_tray_create_state(zdwm_output_id_t output_id, void *config) {
tray_t *tray = config;
assert(tray != nullptr);
auto state = p_new(bar_tray_state_t, 1);
state->tray = *tray;
tray->api.set_listener(tray->handle, bar_tray_icons_changed, state);
return state;
}
static void bar_tray_update(
zdwm_bar_item_t *item,
const zdwm_bar_cell_api_t *cell_api,
void *state
) {
bar_tray_state_t *data = state;
if (!data->dirty) return;
auto tray_handle = data->tray.handle;
auto tray_api = &data->tray.api;
auto icon_count = tray_api->icon_count(tray_handle);
auto cell_count = cell_api->get_cell_count(item);
if (cell_count == icon_count) {
data->dirty = false;
return;
}
cell_api->set_cell_count(item, icon_count);
auto cell_width = tray_api->icon_size(tray_handle);
for (size_t i = 0; i < icon_count; ++i) {
cell_api->cell_set_fixed_width(item, i, cell_width);
}
data->dirty = false;
}
static void bar_tray_after_layout(const zdwm_bar_item_hook_params_t *params) {
bar_tray_state_t *state = params->state;
auto tray_handle = state->tray.handle;
auto tray_api = &state->tray.api;
tray_api->place(tray_handle, params->region.start);
}
static void bar_tray_destroy_state(void *state) {
if (!state) return;
bar_tray_state_t *data = state;
p_delete(&data);
}
zdwm_bar_item_type_t bar_tray = {
.create_state = bar_tray_create_state,
.update = bar_tray_update,
.after_layout = bar_tray_after_layout,
.destroy_state = bar_tray_destroy_state,
.update_interval_ms = 0,
};

5
src/bar/tray.h Normal file
View File

@@ -0,0 +1,5 @@
#pragma once
#include <zdwm/bar.h>
extern zdwm_bar_item_type_t bar_tray;

43
src/bar/types.h Normal file
View File

@@ -0,0 +1,43 @@
#pragma once
#include <stddef.h>
#include <stdint.h>
#include <zdwm/bar.h>
#include <zdwm/types.h>
#include "base/color.h"
typedef struct zdwm_bar_x_region_t bar_x_region_t;
typedef struct bar_cell_t {
zdwm_icon_t icon;
char *text; /* 持有内存,避免野指针问题 */
char *fg_text;
char *bg_text;
color_t fg;
color_t bg;
bar_x_region_t region;
int32_t fixed_width;
bool show_indicator;
bool dirty;
} bar_cell_t;
struct zdwm_bar_item_t {
bar_cell_t *cells;
size_t count;
bar_x_region_t region;
int32_t cell_padding;
int32_t indicator_width;
void *state;
zdwm_bar_item_type_t api;
bool dirty;
uint64_t last_updated_time;
};
typedef struct bar_side_t {
zdwm_bar_item_t *items;
size_t count;
size_t capacity;
bar_x_region_t region;
} bar_side_t;

175
src/bar/windows.c Normal file
View File

@@ -0,0 +1,175 @@
#include "bar/windows.h"
#include <assert.h>
#include <stddef.h>
#include <zdwm/bar.h>
#include <zdwm/listeners.h>
#include <zdwm/types.h>
#include "base/array.h"
#include "base/memory.h"
#include "common/listeners.h"
typedef struct bar_windows_state_t {
zdwm_output_id_t output_id;
zdwm_workspace_id_t workspace_id;
zdwm_window_t *windows;
size_t count;
size_t capacity;
bar_windows_config_t config;
bool workspace_inited;
bool dirty;
} bar_windows_state_t;
static zdwm_window_t *
state_get_window(bar_windows_state_t *state, zdwm_window_id_t window_id) {
for (size_t i = 0; i < state->count; ++i) {
auto window = &state->windows[i];
if (window->id == window_id) return window;
}
return nullptr;
}
static void *
bar_windows_create_state(zdwm_output_id_t output_id, void *config) {
const bar_windows_config_t *windows_config = config;
assert(windows_config);
auto state = p_new(bar_windows_state_t, 1);
state->output_id = output_id;
state->workspace_id = ZDWM_WORKSPACE_ID_INVALID;
state->config = *windows_config;
return state;
}
static void bar_windows_update(
zdwm_bar_item_t *item,
const zdwm_bar_cell_api_t *cells,
void *state
) {
bar_windows_state_t *data = state;
if (!data->workspace_inited || !data->dirty) return;
zdwm_window_t *windows = nullptr;
size_t count = 0, capacity = 0;
for (size_t i = 0; i < data->count; ++i) {
auto win = &data->windows[i];
if (win->workspace != data->workspace_id || win->skip_taskbar) continue;
auto window = array_push(windows, count, capacity);
*window = *win;
}
cells->set_cell_count(item, count);
for (size_t i = 0; i < count; ++i) {
auto window = &windows[i];
auto bg = window->focused ? data->config.focused_bg : data->config.bg;
auto fg = window->focused ? data->config.focused_fg : data->config.fg;
cells->cell_set_text(item, i, window->title);
cells->cell_set_bg(item, i, bg);
cells->cell_set_fg(item, i, fg);
}
p_delete(&windows);
data->dirty = false;
}
static void bar_windows_destroy_state(void *state) {
bar_windows_state_t *data = state;
p_delete(&data->windows);
p_delete(&data);
}
zdwm_bar_item_type_t bar_windows = {
.create_state = bar_windows_create_state,
.update = bar_windows_update,
.destroy_state = bar_windows_destroy_state,
.update_interval_ms = 0,
};
static void bar_windows_workspace_active(
zdwm_output_id_t output_id,
zdwm_workspace_id_t workspace_id,
void *user_data
) {
bar_windows_state_t *state = user_data;
if (state->output_id != output_id) return;
if (state->workspace_id == workspace_id) return;
state->workspace_id = workspace_id;
state->workspace_inited = true;
state->dirty = true;
}
static void
bar_windows_add_window(const zdwm_window_t *window, void *user_data) {
bar_windows_state_t *state = user_data;
auto win = array_push(state->windows, state->count, state->capacity);
*win = *window;
state->dirty = true;
}
static void
bar_windows_initial(const zdwm_window_t *list, size_t count, void *user_data) {
bar_windows_state_t *state = user_data;
for (size_t i = 0; i < count; ++i) {
auto window = &list[i];
bar_windows_add_window(window, state);
}
state->dirty = true;
}
static void
bar_windows_update_window(const zdwm_window_t *window, void *user_data) {
bar_windows_state_t *state = user_data;
auto old_window = state_get_window(state, window->id);
*old_window = *window;
state->dirty = true;
}
static void
bar_windows_remove_window(zdwm_window_id_t window_id, void *user_data) {
bar_windows_state_t *state = user_data;
bool found = false;
size_t index = 0;
for (size_t i = 0; i < state->count; ++i) {
auto window = &state->windows[i];
if (window->id == window_id) {
found = true;
index = i;
break;
}
}
if (!found) return;
if (array_erase(state->windows, state->count, index)) {
state->dirty = true;
}
}
void bar_windows_add_listeners(listeners_t *listeners, void *state) {
#define ADD(ADD_FN, LISTENER) ADD_FN(listeners, LISTENER, state)
ADD(listeners_add_active_workspace_listener, bar_windows_workspace_active);
ADD(listeners_add_initial_window_listener, bar_windows_initial);
ADD(listeners_add_window_added_listener, bar_windows_add_window);
ADD(listeners_add_window_updated_listener, bar_windows_update_window);
ADD(listeners_add_window_removed_listener, bar_windows_remove_window);
#undef ADD
}

18
src/bar/windows.h Normal file
View File

@@ -0,0 +1,18 @@
#pragma once
#include <stdint.h>
#include <zdwm/bar.h>
#include "common/listeners.h"
typedef struct bar_windows_config_t {
uint32_t cell_padding;
const char *bg;
const char *fg;
const char *focused_bg;
const char *focused_fg;
} bar_windows_config_t;
extern zdwm_bar_item_type_t bar_windows;
void bar_windows_add_listeners(listeners_t *listeners, void *state);

305
src/bar/workspaces.c Normal file
View File

@@ -0,0 +1,305 @@
#include "bar/workspaces.h"
#include <assert.h>
#include <stddef.h>
#include <stdint.h>
#include <zdwm/action.h>
#include <zdwm/bar.h>
#include <zdwm/listeners.h>
#include <zdwm/types.h>
#include "base/array.h"
#include "base/memory.h"
#include "common/listeners.h"
typedef struct bar_workspace_t {
zdwm_workspace_t info;
zdwm_layout_notify_t layout;
size_t window_count;
size_t urgent_window_count;
} bar_workspace_t;
typedef struct bar_workspace_state_t {
zdwm_output_id_t output_id;
zdwm_workspace_id_t workspace_id;
bar_workspace_t *workspaces;
size_t count;
size_t capacity;
zdwm_window_t *windows;
size_t window_count;
size_t window_capacity;
bar_workspace_config_t config;
bool list_inited;
bool active_inited;
bool dirty;
} bar_workspace_state_t;
static size_t state_workspace_get_urgent_window_count(
const bar_workspace_state_t *state,
zdwm_workspace_id_t workspace_id
) {
size_t count = 0;
for (size_t i = 0; i < state->window_count; ++i) {
auto window = &state->windows[i];
if (window->workspace == workspace_id && window->urgent) {
count++;
}
}
return count;
}
static size_t state_workspace_get_window_count(
const bar_workspace_state_t *state,
zdwm_workspace_id_t workspace_id
) {
size_t count = 0;
for (size_t i = 0; i < state->window_count; ++i) {
auto window = &state->windows[i];
if (window->workspace == workspace_id && !window->skip_taskbar) {
count++;
}
}
return count;
}
static zdwm_window_t *state_get_window(
const bar_workspace_state_t *state,
zdwm_window_id_t window_id
) {
for (size_t i = 0; i < state->window_count; ++i) {
auto window = &state->windows[i];
if (window->id == window_id) return window;
}
return nullptr;
}
static void *
bar_workspaces_create_state(zdwm_output_id_t output_id, void *config) {
const bar_workspace_config_t *workspace_config = config;
assert(workspace_config);
auto state = p_new(bar_workspace_state_t, 1);
state->output_id = output_id;
state->workspace_id = ZDWM_WORKSPACE_ID_INVALID;
state->config = *workspace_config;
return state;
}
static void bar_workspaces_update(
zdwm_bar_item_t *item,
const zdwm_bar_cell_api_t *cell_api,
void *state
) {
auto data = (bar_workspace_state_t *)state;
if (!data->list_inited || !data->active_inited || !data->dirty) return;
/* 当前 workspace 的 layout 指示器放在最后 */
cell_api->set_cell_count(item, data->count + 1);
auto layout_index = data->count;
auto config = &data->config;
for (size_t i = 0; i < data->count; ++i) {
auto workspace = &data->workspaces[i];
auto info = &workspace->info;
workspace->window_count = state_workspace_get_window_count(state, info->id);
workspace->urgent_window_count =
state_workspace_get_urgent_window_count(state, info->id);
bool show_indicator = workspace->window_count;
cell_api->cell_set_indicator(item, i, show_indicator);
cell_api->cell_set_text(item, i, info->name);
if (info->id == data->workspace_id) {
cell_api->cell_set_bg(item, i, config->active_bg);
cell_api->cell_set_fg(item, i, config->active_fg);
cell_api->cell_set_bg(item, layout_index, config->layout_bg);
cell_api->cell_set_fg(item, layout_index, config->layout_fg);
cell_api->cell_set_text(item, layout_index, workspace->layout.symbol);
continue;
}
if (workspace->urgent_window_count > 0) {
cell_api->cell_set_bg(item, i, config->urgent_bg);
cell_api->cell_set_fg(item, i, config->urgent_fg);
continue;
}
cell_api->cell_set_bg(item, i, config->bg);
cell_api->cell_set_fg(item, i, config->fg);
}
data->dirty = false;
}
static zdwm_action_t bar_workspace_on_click(zdwm_bar_click_params_t *params) {
auto cell_index = params->cell_index;
auto data = (bar_workspace_state_t *)params->state;
if (cell_index == data->count) {
return (zdwm_action_t){
.type = ZDWM_ACTION_LAYOUT_CYCLE,
.as.layout_cycle = {.delta = 1},
};
}
auto workspace = &data->workspaces[cell_index];
if (workspace->info.id == data->workspace_id) {
return (zdwm_action_t){.type = ZDWM_ACTION_NONE};
}
return (zdwm_action_t){
.type = ZDWM_ACTION_WORKSPACE_SWITCH,
.as.switch_workspace.workspace = workspace->info.id,
};
}
static void bar_workspace_destroy_state(void *state) {
if (!state) return;
auto data = (bar_workspace_state_t *)state;
p_delete(&data->workspaces);
p_delete(&data->windows);
p_delete(&data);
}
zdwm_bar_item_type_t bar_workspace = {
.create_state = bar_workspaces_create_state,
.update = bar_workspaces_update,
.on_click = bar_workspace_on_click,
.destroy_state = bar_workspace_destroy_state,
.update_interval_ms = 0,
};
typedef struct bar_workspace_listener_user_data_t {
zdwm_bar_item_t *bar;
bar_workspace_state_t *state;
} bar_workspace_listener_user_data_t;
static void bar_workspace_list_filter(
const zdwm_workspace_t *list,
size_t count,
void *user_data
) {
auto state = (bar_workspace_state_t *)user_data;
for (size_t i = 0; i < count; ++i) {
auto workspace = &list[i];
if (workspace->output_id != state->output_id) continue;
auto item = array_push(state->workspaces, state->count, state->capacity);
p_clear(item, 1);
item->info = *workspace;
}
state->list_inited = true;
state->dirty = true;
}
static void bar_workspace_active_updated(
zdwm_output_id_t output_id,
zdwm_workspace_id_t workspace_id,
void *user_data
) {
auto state = (bar_workspace_state_t *)user_data;
if (state->output_id != output_id) return;
if (state->workspace_id == workspace_id) return;
state->workspace_id = workspace_id;
state->active_inited = true;
state->dirty = true;
}
static void
bar_workspace_layout_notify(zdwm_layout_notify_t layout, void *user_data) {
auto state = (bar_workspace_state_t *)user_data;
for (size_t i = 0; i < state->count; ++i) {
auto workspace = &state->workspaces[i];
if (workspace->info.id == layout.workspace) {
workspace->layout = layout;
state->dirty = true;
return;
}
}
}
void bar_workspace_window_added(const zdwm_window_t *window, void *user_data) {
auto state = (bar_workspace_state_t *)user_data;
auto win =
array_push(state->windows, state->window_count, state->window_capacity);
*win = *window;
state->dirty = true;
}
static void bar_workspace_initial_windows(
const zdwm_window_t *list,
size_t count,
void *user_data
) {
for (size_t i = 0; i < count; ++i) {
auto window = &list[i];
bar_workspace_window_added(window, user_data);
}
}
void bar_workspace_window_updated(
const zdwm_window_t *window,
void *user_data
) {
auto state = (bar_workspace_state_t *)user_data;
auto old_window = state_get_window(state, window->id);
*old_window = *window;
state->dirty = true;
}
void bar_workspace_window_removed(zdwm_window_id_t window_id, void *user_data) {
auto state = (bar_workspace_state_t *)user_data;
bool found = false;
size_t index = 0;
for (size_t i = 0; i < state->window_count; ++i) {
auto window = &state->windows[i];
if (window->id == window_id) {
found = true;
index = i;
break;
}
}
if (!found) return;
if (array_erase(state->windows, state->window_count, index)) {
state->dirty = true;
}
}
void bar_workspace_add_listeners(listeners_t *listeners, void *state) {
#define ADD(ADD_FN, LISTENER) ADD_FN(listeners, LISTENER, state)
ADD(listeners_add_initial_workspace_listener, bar_workspace_list_filter);
ADD(listeners_add_active_workspace_listener, bar_workspace_active_updated);
ADD(listeners_add_layout_notify, bar_workspace_layout_notify);
ADD(listeners_add_initial_window_listener, bar_workspace_initial_windows);
ADD(listeners_add_window_added_listener, bar_workspace_window_added);
ADD(listeners_add_window_updated_listener, bar_workspace_window_updated);
ADD(listeners_add_window_removed_listener, bar_workspace_window_removed);
#undef ADD
}

23
src/bar/workspaces.h Normal file
View File

@@ -0,0 +1,23 @@
#pragma once
#include <stdint.h>
#include <zdwm/bar.h>
#include "common/listeners.h"
typedef struct bar_workspace_config_t {
uint32_t cell_padding;
uint32_t indicator_width;
const char *bg;
const char *fg;
const char *active_bg;
const char *active_fg;
const char *urgent_bg;
const char *urgent_fg;
const char *layout_bg;
const char *layout_fg;
} bar_workspace_config_t;
extern zdwm_bar_item_type_t bar_workspace;
void bar_workspace_add_listeners(listeners_t *listeners, void *state);

View File

@@ -1,17 +0,0 @@
#pragma once
#include <stdint.h>
typedef struct area_t {
int16_t x, y;
uint16_t width, height;
} area_t;
typedef struct extent_in_bar_t {
int16_t start;
int16_t end;
} extent_in_bar_t;
typedef struct point_t {
int16_t x, y;
} point_t;

View File

@@ -1 +0,0 @@
../../.clang-format

3
src/base/app.h Normal file
View File

@@ -0,0 +1,3 @@
#pragma once
#define APP_NAME "zdwm"

View File

@@ -86,7 +86,7 @@ array_erase_impl(void *items, size_t item_size, size_t *count, size_t index) {
size_t tail_count = *count - index - 1;
if (tail_count > 0) {
char *dest = (char *)items + index * item_size;
char *src = dest + item_size;
char *src = dest + item_size;
memmove(dest, src, tail_count * item_size);
}
@@ -98,9 +98,11 @@ array_erase_impl(void *items, size_t item_size, size_t *count, size_t index) {
#define array_reserve(items, capacity, need) \
array_reserve_impl((void **)&(items), sizeof(*(items)), &(capacity), (need))
/* clang-format off */
/** @brief 为具体类型数组追加一个槽位。 */
#define array_push(items, count, capacity) \
array_push_impl((void **)&(items), sizeof(*(items)), &(count), &(capacity))
#define array_push(items, count, capacity)\
(typeof(items))array_push_impl((void **)&(items), sizeof(*(items)), &(count), &(capacity))
/* clang-format on */
/** @brief 删除具体类型数组中指定下标的元素。 */
#define array_erase(items, count, index) \

View File

@@ -1,6 +1,11 @@
#include "color.h"
#include "base/color.h"
#include "utils.h"
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include "base/log.h"
/**
* @brief 16 uint32 rgba数据
@@ -24,16 +29,26 @@ static uint32_t rgba_to_argb(uint32_t rgba);
* @param blue blue channel, range 0~1
* @param alpha alpha channel, range 0~1
*/
static void extract_color_channel(const uint32_t rgba, double *red,
double *green, double *blue, double *alpha);
static void extract_color_channel(
const uint32_t rgba,
double *red,
double *green,
double *blue,
double *alpha
);
void color_parse(const char *hex, color_t *const color) {
bool success = hex_color_to_rgba(hex, &color->rgba);
if (!success) fatal("invalid hex color: %s", hex);
color->argb = rgba_to_argb(color->rgba);
extract_color_channel(color->rgba, &color->red, &color->green, &color->blue,
&color->alpha);
extract_color_channel(
color->rgba,
&color->red,
&color->green,
&color->blue,
&color->alpha
);
}
bool hex_color_to_rgba(const char *hex, uint32_t *rgba) {
@@ -41,7 +56,7 @@ bool hex_color_to_rgba(const char *hex, uint32_t *rgba) {
size_t len = strlen(hex);
char extended[9] = {0};
uint32_t color = 0x00000000;
uint32_t color = 0x00'00'00'00;
if (len == 3 || len == 4) {
extended[0] = extended[1] = hex[0];
@@ -61,7 +76,7 @@ bool hex_color_to_rgba(const char *hex, uint32_t *rgba) {
return false;
}
color = strtoul(extended, nullptr, 16) & 0xffffffff;
color = strtoul(extended, nullptr, 16) & 0xff'ff'ff'ff;
*rgba = color;
return true;
}
@@ -78,8 +93,13 @@ uint32_t rgba_to_argb(uint32_t rgba) {
}
#define COLOR_SPLIT(C, R) (EXTRACE_COLOR_BIT((C), (R)) / (double)0xff)
void extract_color_channel(const uint32_t rgba, double *red, double *green,
double *blue, double *alpha) {
void extract_color_channel(
const uint32_t rgba,
double *red,
double *green,
double *blue,
double *alpha
) {
*red = COLOR_SPLIT(rgba, 24);
*green = COLOR_SPLIT(rgba, 16);
*blue = COLOR_SPLIT(rgba, 8);

20
src/base/color.h Normal file
View File

@@ -0,0 +1,20 @@
#pragma once
#include <stdint.h>
typedef struct color_t {
uint32_t rgba;
uint32_t argb;
/* RGBA color channels for Cairo, each channel in the range [0, 1] */
double red, green, blue, alpha;
} color_t;
/**
* @brief 解析 16 进制表示的颜色
*
* @param hex 颜色的 16 进制表示,支持的格式有
* RGB/RGBA/RRGGBB/RRGGBBAA/#RGB/#RGBA/#RRGGBB/#RRGGBBAA
* @param color 存储颜色值的结构体指针,不能为 nullptr
*/
void color_parse(const char *hex, color_t *const color);

View File

@@ -5,7 +5,7 @@
#include <stdlib.h>
#include <time.h>
#include "app.h"
#include "base/app.h"
const char *current_time_str(void) {
static char buffer[25];

View File

@@ -1,4 +1,4 @@
#include "core/action.h"
#include "base/process.h"
#include <paths.h>
#include <stdlib.h>

54
src/base/time.c Normal file
View File

@@ -0,0 +1,54 @@
#include "base/time.h"
#include <errno.h>
#include <stdint.h>
#include <string.h>
#include <sys/timerfd.h>
#include <time.h>
#include "base/log.h"
static int time_create_monotonic_timerfd(const struct itimerspec *itimerspec) {
auto timerfd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC);
if (timerfd == -1) {
auto err = errno;
warn("time_create_timerfd failed: %s", strerror(err));
return timerfd;
}
timerfd_settime(timerfd, 0, itimerspec, nullptr);
return timerfd;
}
static constexpr auto NANOSECONDS_OF_ONE_SECOND = 1'000'000'000ULL;
int time_create_monotonic_timerfd_by_fps(uint32_t fps) {
if (fps == 0) fps = 30;
auto interval_ns = NANOSECONDS_OF_ONE_SECOND / fps;
/* clang-format off */
struct itimerspec timer_spec = {
.it_value = {
.tv_sec = 0,
.tv_nsec = interval_ns % NANOSECONDS_OF_ONE_SECOND,
},
.it_interval = {
.tv_sec = interval_ns / NANOSECONDS_OF_ONE_SECOND,
.tv_nsec = interval_ns % NANOSECONDS_OF_ONE_SECOND,
},
};
/* clang-format on */
return time_create_monotonic_timerfd(&timer_spec);
}
static uint64_t time_monotonic_ns(void) {
struct timespec timestamp = {0};
if (clock_gettime(CLOCK_MONOTONIC, &timestamp) == -1) {
warn("clock_gettime failed: %s", strerror(errno));
}
return timestamp.tv_sec * NANOSECONDS_OF_ONE_SECOND + timestamp.tv_nsec;
}
uint64_t time_monotonic_ms(void) { return time_monotonic_ns() / 1'000'000U; }

6
src/base/time.h Normal file
View File

@@ -0,0 +1,6 @@
#pragma once
#include <stdint.h>
int time_create_monotonic_timerfd_by_fps(uint32_t fps);
uint64_t time_monotonic_ms(void);

View File

@@ -1,37 +0,0 @@
#include "buffer.h"
#include <stdlib.h>
#include <string.h>
#include <sysexits.h>
#include "utils.h"
char buffer_slop[1];
void buffer_ensure(buffer_t *buf, int newlen) {
if (newlen < 0) exit(EX_SOFTWARE);
if (newlen < buf->size) return;
if (newlen < buf->offs + buf->size && buf->offs > buf->size / 4) {
/* Data fits in the current area, shift it left */
memmove(buf->s - buf->offs, buf->s, buf->len + 1);
buf->s -= buf->offs;
buf->size += buf->offs;
buf->offs = 0;
return;
}
buf->size = p_alloc_nr(buf->size + buf->offs);
if (buf->size < newlen + 1) buf->size = newlen + 1;
if (buf->alloced && !buf->offs)
p_realloc(&buf->s, buf->size);
else {
char *new_area = xmalloc(buf->size);
memcpy(new_area, buf->s, buf->len + 1);
if (buf->alloced) free(buf->s - buf->offs);
buf->alloced = true;
buf->s = new_area;
buf->offs = 0;
}
}

View File

@@ -1,78 +0,0 @@
#pragma once
#include <assert.h>
#include <string.h>
#include "utils.h"
typedef struct buffer_t {
char *s;
int len, size;
unsigned alloced : 1;
unsigned offs : 31;
} buffer_t;
extern char buffer_slop[1];
#define BUFFER_INIT (buffer_t){.s = buffer_slop, .size = 1}
/**
* Initialize a buffer.
* @param buf A buffer pointer.
* @return The same buffer pointer.
*/
static inline buffer_t *buffer_init(buffer_t *buf) {
*buf = BUFFER_INIT;
return buf;
}
void buffer_ensure(buffer_t *buf, int len);
/**
* Add data in the buffer.
* @param buf Buffer where to add.
* @param pos Position where to add.
* @param len Length.
* @param data Data to add.
* @param dlen Data length.
*/
static inline void buffer_splice(buffer_t *buf, int pos, int len,
const void *data, int dlen) {
assert(pos >= 0 && len >= 0 && dlen >= 0);
if (unlikely(pos > buf->len)) pos = buf->len;
if (unlikely(len > buf->len - pos)) len = buf->len - pos;
if (pos == 0 && len + buf->offs >= dlen) {
buf->offs += len - dlen;
buf->s += len - dlen;
buf->size -= len - dlen;
buf->len -= len - dlen;
} else if (len != dlen) {
buffer_ensure(buf, buf->len + dlen - len);
memmove(buf->s + pos + dlen, buf->s + pos + len, buf->len - pos - len);
buf->len += dlen - len;
buf->s[buf->len] = '\0';
}
memcpy(buf->s + pos, data, dlen);
}
/**
* Add data at the end of buffer.
* @param buf Buffer where to add.
* @param data Data to add.
* @param len Data length.
*/
static inline void buffer_add(buffer_t *buf, const void *data, int len) {
buffer_splice(buf, buf->len, 0, data, len);
}
#define buffer_addsl(buf, data) buffer_add(buf, data, sizeof(data) - 1);
/**
* Add a string to the and of a buffer.
* @param buf The buffer where to add.
* @param s The string to add.
*/
static inline void buffer_adds(buffer_t *buf, const char *s) {
buffer_splice(buf, buf->len, 0, s, a_strlen(s));
}

View File

@@ -1,706 +0,0 @@
#include "client.h"
#include <stdint.h>
#include <string.h>
#include <xcb/xcb.h>
#include <xcb/xcb_aux.h>
#include <xcb/xcb_icccm.h>
#include <xcb/xproto.h>
#include "atoms-extern.h"
#include "base.h"
#include "monitor.h"
#include "rect.h"
#include "types.h"
#include "utils.h"
#include "wm.h"
#include "xwindow.h"
client_t *client_get_by_window(xcb_window_t window) {
for (client_t *c = wm.client_list; c; c = c->next) {
if (c->window == window) return c;
}
return nullptr;
}
client_t *client_get_next_by_class(client_t *current, const char *class) {
client_t *start = current ? current->next : wm.client_list;
for (client_t *c = start; c; c = c->next) {
if (strcmp(class, c->class) == 0) return c;
}
for (client_t *c = wm.client_list; c && c != start; c = c->next) {
if (strcmp(class, c->class) == 0) return c;
}
return nullptr;
}
static void client_attach_list(client_t *client) {
client->next = wm.client_list;
wm.client_list = client;
}
static void client_detach_list(client_t *client) {
client_t **tc = nullptr;
for (tc = &wm.client_list; *tc && *tc != client; tc = &(*tc)->next);
*tc = client->next;
}
static void client_attach_stack(client_t *client) {
client->stack_next = wm.client_stack_list;
wm.client_stack_list = client;
}
static void client_detach_stack(client_t *client) {
client_t **tc = &wm.client_stack_list;
for (; *tc && *tc != client; tc = &(*tc)->stack_next);
*tc = client->stack_next;
if (client == wm.client_focused) {
client_t *t = wm.client_stack_list;
for (; t && !client_is_visible(t); t = t->stack_next);
wm.client_focused = t;
}
}
task_in_tag_t *client_get_task_in_tag(client_t *client, tag_t *tag) {
for (task_in_tag_t *task = tag->task_list; task; task = task->next) {
if (task->client == client) return task;
}
return nullptr;
}
task_in_tag_t *client_get_next_task_in_tag(client_t *client, tag_t *tag) {
if (!tag->task_list) return nullptr;
task_in_tag_t *task = tag->task_list;
for (; task && task->client != client; task = task->next);
return task && task->next ? task->next : tag->task_list;
}
task_in_tag_t *client_get_previous_task_in_tag(client_t *client, tag_t *tag) {
if (!tag->task_list) return nullptr;
task_in_tag_t *task = tag->task_list;
for (; task && task->next && task->next->client != client; task = task->next);
return task ? task : tag->task_list;
}
static void client_add_to_tag(client_t *client, tag_t *tag) {
if (client_get_task_in_tag(client, tag)) return;
xwindow_set_wm_desktop(client->window, tag->index);
task_in_tag_t *task = p_new(task_in_tag_t, 1);
task->client = client;
task->next = tag->task_list;
tag->task_list = task;
task->geometry = client->geometry;
}
static void client_remove_from_tag(client_t *client, tag_t *tag) {
task_in_tag_t **task = &tag->task_list;
while (*task) {
if ((*task)->client == client) {
task_in_tag_t *t = *task;
*task = (*task)->next;
p_delete(&t);
continue;
}
task = &(*task)->next;
}
}
static inline void client_wipe(client_t *client) {
p_delete(&client->class);
p_delete(&client->instance);
p_delete(&client->name);
p_delete(&client->net_name);
p_delete(&client->role);
p_delete(&client);
}
static inline void client_tags_apply(client_t *client) {
for (tag_t *tag = client->monitor->tag_list; tag; tag = tag->next) {
if (client->tags & tag->mask) {
client_add_to_tag(client, tag);
} else {
client_remove_from_tag(client, tag);
}
}
if (client->tags == 0) client_wipe(client);
}
static inline void client_update_names(client_t *c) {
xwindow_get_text_property(c->window, WM_NAME, &c->name);
xwindow_get_text_property(c->window, _NET_WM_NAME, &c->net_name);
}
static inline void client_init_geometry(client_t *c) {
if (c->maximize || c->fullscreen || c->minimize) return;
area_t workarea = c->monitor->workarea;
if (c->geometry.x + client_width(c) > workarea.x + workarea.width) {
c->geometry.x = workarea.x + workarea.width - client_width(c);
}
if (c->geometry.y + client_height(c) > workarea.y + workarea.height) {
c->geometry.y = workarea.y + workarea.height - client_height(c);
}
c->geometry.x = MAX(c->geometry.x, workarea.x);
c->geometry.y = MAX(c->geometry.y, workarea.y);
client_move_to(c, c->geometry.x, c->geometry.y);
client_resize(c, c->geometry.width, c->geometry.height);
client_change_border_color(c, &wm.color_set.active_border_color);
client_change_border_width(c, wm.border_width);
}
static void client_init_tag_by_wm_desktop(client_t *client, uint32_t tag_mask) {
uint32_t tag_index = 0;
if (!xwindow_get_wm_desktop(client->window, &tag_index)) {
goto tag_fallback;
}
for (monitor_t *m = wm.monitor_list; m; m = m->next) {
for (tag_t *tag = m->tag_list; tag; tag = tag->next) {
if (tag->index == tag_index) {
client->monitor = m;
client->tags = tag->mask;
return;
}
}
}
tag_fallback:
client->tags = tag_mask;
}
static void set_window_event_mask(xcb_window_t window, bool clean) {
xcb_cw_t change_mask = XCB_CW_EVENT_MASK;
uint32_t init_event_mask =
XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_FOCUS_CHANGE |
XCB_EVENT_MASK_PROPERTY_CHANGE | XCB_EVENT_MASK_STRUCTURE_NOTIFY |
XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY;
uint32_t event_mask = clean ? XCB_EVENT_MASK_NO_EVENT : init_event_mask;
xcb_params_cw_t params = {.event_mask = event_mask};
xcb_aux_change_window_attributes(wm.xcb_conn, window, change_mask, &params);
}
void client_manage(xcb_window_t window,
xcb_get_geometry_reply_t *geometry_reply) {
client_t *c = p_new(client_t, 1);
c->window = window;
c->old_geometry.x = geometry_reply->x;
c->old_geometry.y = geometry_reply->y;
c->old_geometry.width = geometry_reply->width;
c->old_geometry.height = geometry_reply->height;
c->geometry = c->old_geometry;
c->old_border_width = geometry_reply->border_width;
c->minimize = xwindow_get_state(window) == XCB_ICCCM_WM_STATE_ICONIC;
set_window_event_mask(window, false);
client_update_names(c);
xwindow_get_text_property(c->window, WM_WINDOW_ROLE, &c->role);
client_update_wm_hints(c);
{
xcb_icccm_get_wm_class_reply_t prop;
xcb_get_property_cookie_t cookie =
xcb_icccm_get_wm_class_unchecked(wm.xcb_conn, c->window);
if (xcb_icccm_get_wm_class_reply(wm.xcb_conn, cookie, &prop, nullptr)) {
c->class = strdup(prop.class_name);
c->instance = strdup(prop.instance_name);
xcb_icccm_get_wm_class_reply_wipe(&prop);
}
}
{
xcb_get_property_cookie_t cookie =
xcb_icccm_get_wm_transient_for_unchecked(wm.xcb_conn, window);
xcb_icccm_get_wm_transient_for_reply(wm.xcb_conn, cookie,
&c->transient_for_window, nullptr);
client_t *transient_for_client = nullptr;
if (c->transient_for_window && (transient_for_client = client_get_by_window(
c->transient_for_window))) {
c->monitor = transient_for_client->monitor;
client_init_tag_by_wm_desktop(c, transient_for_client->tags);
} else {
c->monitor = wm.current_monitor;
client_init_tag_by_wm_desktop(c, c->monitor->selected_tag->mask);
client_apply_rules(c, wm.rules, wm.rules_count);
}
client_init_geometry(c);
client_tags_apply(c);
}
client_attach_list(c);
client_attach_stack(c);
client_update_window_type(c);
client_update_size_hints(c);
xcb_change_property(wm.xcb_conn, XCB_PROP_MODE_APPEND, wm.screen->root,
_NET_CLIENT_LIST, XCB_ATOM_WINDOW, 32, 1, &window);
if (c->minimize) {
xwindow_set_state(window, XCB_ICCCM_WM_STATE_ICONIC);
} else {
xwindow_set_state(window, XCB_ICCCM_WM_STATE_NORMAL);
xcb_map_window(wm.xcb_conn, window);
}
monitor_arrange(c->monitor);
monitor_draw_bar(c->monitor);
wm_restack_clients();
if (c->tags & c->monitor->selected_tag->mask) client_focus(c);
xcb_flush(wm.xcb_conn);
}
char **client_get_task_title(client_t *client) {
if (client->net_name) return &client->net_name;
if (client->name) return &client->name;
if (client->instance) return &client->instance;
if (client->class) return &client->class;
return nullptr;
}
bool client_need_layout(client_t *client) {
if (client->floating || client->fullscreen || client->maximize ||
client->minimize || client->size_freeze) {
return false;
}
return true;
}
void client_send_to_tag(client_t *client, uint32_t tag_mask) {
if (client->tags == tag_mask) return;
client->tags = tag_mask;
client_tags_apply(client);
monitor_arrange(client->monitor);
monitor_draw_bar(client->monitor);
monitor_deal_focus(client->monitor);
xcb_flush(wm.xcb_conn);
}
void client_send_to_monitor(client_t *client, monitor_t *monitor) {
if (client->monitor == monitor) return;
for (tag_t *tag = client->monitor->tag_list; tag; tag = tag->next) {
client_remove_from_tag(client, tag);
}
monitor_t *m = client->monitor;
client->monitor = monitor;
client->tags = monitor->selected_tag->mask;
client_add_to_tag(client, monitor->selected_tag);
wm_set_current_monitor(monitor, true);
monitor_arrange(m);
monitor_arrange(monitor);
monitor_draw_bar(m);
monitor_draw_bar(monitor);
xcb_flush(wm.xcb_conn);
}
void client_move_to(client_t *client, int16_t x, int16_t y) {
client->geometry.x = x;
client->geometry.y = y;
uint16_t mask = XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y;
const xcb_params_configure_window_t params = {.x = x, .y = y};
xcb_aux_configure_window(wm.xcb_conn, client->window, mask, &params);
logger("== client 0x%x move to x: %d, y: %d\n", client->window, x, y);
}
void client_resize(client_t *client, uint16_t width, uint16_t height) {
client->geometry.width = width;
client->geometry.height = height;
uint16_t mask = XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT;
const xcb_params_configure_window_t params = {
.width = width,
.height = height,
};
xcb_aux_configure_window(wm.xcb_conn, client->window, mask, &params);
logger("== client 0x%x resize to width: %d, height: %d\n", client->window,
width, height);
}
void client_change_border_color(client_t *client, color_t *color) {
uint16_t mask = XCB_CW_BORDER_PIXEL;
const xcb_params_cw_t params = {.border_pixel = color->argb};
xcb_aux_change_window_attributes(wm.xcb_conn, client->window, mask, &params);
client->border_color = color;
logger("== client 0x%x border color: RGBA(#%08x), ARGB(#%08x)\n",
client->window, color->rgba, color->argb);
}
void client_change_border_width(client_t *client, uint16_t border_width) {
if (client->border_width == border_width) return;
uint16_t mask = XCB_CONFIG_WINDOW_BORDER_WIDTH;
const xcb_params_configure_window_t params = {.border_width = border_width};
xcb_aux_configure_window(wm.xcb_conn, client->window, mask, &params);
client->border_width = border_width;
logger("== client 0x%x border width: %u\n", client->window, border_width);
}
void client_update_window_type(client_t *client) {
xcb_window_t window = client->window;
xcb_get_property_cookie_t state_cookie = xcb_get_property_unchecked(
wm.xcb_conn, false, window, _NET_WM_STATE, XCB_ATOM_ATOM, 0, 1);
xcb_get_property_cookie_t window_type_cookie = xcb_get_property_unchecked(
wm.xcb_conn, false, window, _NET_WM_WINDOW_TYPE, XCB_ATOM_ATOM, 0, 1);
xcb_get_property_reply_t *state_reply =
xcb_get_property_reply(wm.xcb_conn, state_cookie, nullptr);
xcb_get_property_reply_t *window_type_reply =
xcb_get_property_reply(wm.xcb_conn, window_type_cookie, nullptr);
if (state_reply) {
xcb_atom_t state = *(xcb_atom_t *)xcb_get_property_value(state_reply);
client->skip_taskbar = state == _NET_WM_STATE_SKIP_TASKBAR;
if (state == _NET_WM_STATE_FULLSCREEN) {
client_set_fullscreen(client, true);
}
p_delete(&state_reply);
}
if (window_type_reply) {
xcb_atom_t window_type =
*(xcb_atom_t *)xcb_get_property_value(window_type_reply);
if (window_type == _NET_WM_WINDOW_TYPE_DIALOG) {
client_set_floating(client, true);
}
p_delete(&window_type_reply);
}
}
void client_update_wm_hints(client_t *client) {
xcb_get_property_cookie_t cookie =
xcb_icccm_get_wm_hints(wm.xcb_conn, client->window);
xcb_icccm_wm_hints_t hints;
if (xcb_icccm_get_wm_hints_reply(wm.xcb_conn, cookie, &hints, nullptr)) {
if (client == wm.client_focused &&
(hints.flags & XCB_ICCCM_WM_HINT_X_URGENCY)) {
hints.flags &= ~XCB_ICCCM_WM_HINT_X_URGENCY;
xcb_icccm_set_wm_hints(wm.xcb_conn, client->window, &hints);
} else {
client->urgent = hints.flags & XCB_ICCCM_WM_HINT_X_URGENCY;
}
}
}
void client_update_size_hints(client_t *client) {
xcb_connection_t *conn = wm.xcb_conn;
xcb_size_hints_t hints;
xcb_get_property_cookie_t cookie =
xcb_icccm_get_wm_normal_hints_unchecked(conn, client->window);
if (!xcb_icccm_get_wm_normal_hints_reply(conn, cookie, &hints, nullptr)) {
return;
}
int32_t min_width = 0, min_height = 0, max_width = 0, max_height = 0;
if (hints.flags & XCB_ICCCM_SIZE_HINT_P_MIN_SIZE) {
min_width = hints.min_width;
min_height = hints.min_height;
}
if (hints.flags & XCB_ICCCM_SIZE_HINT_P_MAX_SIZE) {
max_width = hints.max_width;
max_height = hints.max_height;
}
if (min_width == max_width && min_height == max_height) {
client->size_freeze = true;
client_set_floating(client, true);
}
}
void client_set_floating(client_t *client, bool floating) {
if (client->floating == floating || client->fullscreen) return;
logger("== client set floating: %s\n", floating ? "true" : "false");
logger("== old geometry: x -> %d, y -> %d, width -> %u, height -> %u\n",
client->old_geometry.x, client->old_geometry.y,
client->old_geometry.width, client->old_geometry.height);
client->floating = floating;
if (floating) {
client_apply_workarea_geometry(client, client->old_geometry);
client->old_geometry = client->geometry;
}
monitor_arrange(client->monitor);
}
void client_set_fullscreen(client_t *client, bool fullscreen) {
if (client->fullscreen == fullscreen) return;
client->fullscreen = fullscreen;
if (fullscreen) {
xcb_change_property(wm.xcb_conn, XCB_PROP_MODE_REPLACE, client->window,
_NET_WM_STATE, XCB_ATOM_ATOM, 32, 1,
&_NET_WM_STATE_FULLSCREEN);
client->old_border_width = client->border_width;
if (client->floating) client->old_geometry = client->geometry;
client_change_border_width(client, 0);
client_apply_geometry(client, client->monitor->geometry);
client_stack_raise(client);
} else {
xcb_change_property(wm.xcb_conn, XCB_PROP_MODE_REPLACE, client->window,
_NET_WM_STATE, XCB_ATOM_ATOM, 32, 0, 0);
client_change_border_width(client, client->old_border_width);
client_apply_geometry(client, client->old_geometry);
}
monitor_arrange(client->monitor);
client_focus(client);
}
void client_set_maximize(client_t *client, bool maximize) {
if (client->maximize == maximize) return;
client->maximize = maximize;
if (maximize) {
xcb_atom_t max_atoms[] = {
_NET_WM_STATE_MAXIMIZED_HORZ,
_NET_WM_STATE_MAXIMIZED_VERT,
};
xcb_change_property(wm.xcb_conn, XCB_PROP_MODE_REPLACE, client->window,
_NET_WM_STATE, XCB_ATOM_ATOM, 32, countof(max_atoms),
max_atoms);
client->old_border_width = client->border_width;
if (client->floating) client->old_geometry = client->geometry;
client_change_border_width(client, 0);
client_apply_geometry(client, client->monitor->workarea);
client_stack_raise(client);
} else {
xcb_change_property(wm.xcb_conn, XCB_PROP_MODE_REPLACE, client->window,
_NET_WM_STATE, XCB_ATOM_ATOM, 32, 0, 0);
client_change_border_width(client, client->old_border_width);
client_apply_geometry(client, client->old_geometry);
}
monitor_arrange(client->monitor);
client_focus(client);
}
void client_set_minimize(client_t *client, bool minimize) {
if (client->minimize == minimize) return;
client->minimize = minimize;
if (minimize) {
xwindow_set_state(client->window, XCB_ICCCM_WM_STATE_ICONIC);
xcb_grab_server(wm.xcb_conn);
set_window_event_mask(client->window, true);
xcb_unmap_window(wm.xcb_conn, client->window);
set_window_event_mask(client->window, false);
xcb_ungrab_server(wm.xcb_conn);
} else {
xwindow_set_state(client->window, XCB_ICCCM_WM_STATE_NORMAL);
xcb_map_window(wm.xcb_conn, client->window);
}
monitor_arrange(client->monitor);
client_focus(client);
}
static void update_client_list(void) {
xcb_connection_t *conn = wm.xcb_conn;
xcb_window_t root = wm.screen->root;
xcb_atom_t atom = _NET_CLIENT_LIST;
uint8_t mode = XCB_PROP_MODE_PREPEND;
xcb_atom_t type = XCB_ATOM_WINDOW;
xcb_delete_property(conn, root, atom);
for (client_t *c = wm.client_list; c; c = c->next) {
xcb_change_property(conn, mode, root, atom, type, 32, 1, &c->window);
}
}
void client_kill(client_t *client) {
if (!xwindow_send_event(client->window, WM_DELETE_WINDOW)) {
xwindow_kill_window(client->window);
}
}
void client_unmanage(client_t *client, bool destroyed) {
monitor_t *m = client->monitor;
client_detach_list(client);
client_detach_stack(client);
for (tag_t *tag = m->tag_list; tag; tag = tag->next) {
if (tag->mask & client->tags) client_remove_from_tag(client, tag);
}
if (!destroyed) {
set_window_event_mask(client->window, true);
client_change_border_width(client, client->old_border_width);
xwindow_set_state(client->window, XCB_ICCCM_WM_STATE_WITHDRAWN);
xcb_flush(wm.xcb_conn);
}
client_wipe(client);
update_client_list();
wm_restack_clients();
monitor_deal_focus(m);
monitor_arrange(m);
monitor_draw_bar(m);
xcb_flush(wm.xcb_conn);
}
void client_apply_geometry(client_t *client, area_t geometry) {
uint16_t mask = 0;
const xcb_params_configure_window_t params = {
.x = geometry.x,
.y = geometry.y,
.width = geometry.width,
.height = geometry.height,
};
if (client->geometry.x != geometry.x) mask |= XCB_CONFIG_WINDOW_X;
if (client->geometry.y != geometry.y) mask |= XCB_CONFIG_WINDOW_Y;
if (client->geometry.width != geometry.width) mask |= XCB_CONFIG_WINDOW_WIDTH;
if (client->geometry.height != geometry.height) {
mask |= XCB_CONFIG_WINDOW_HEIGHT;
}
client->geometry = geometry;
xcb_aux_configure_window(wm.xcb_conn, client->window, mask, &params);
}
/**
* @brief 调整 client 的几何信息但需让 client 任在其 monitor 的 workarea 中
* @param client 要调整几何信息的 client
* @param geometry 目标几何信息
*/
void client_apply_workarea_geometry(client_t *client, area_t geometry) {
area_t workarea = client->monitor->workarea;
uint16_t width = MIN(geometry.width, workarea.width);
uint16_t height = MIN(geometry.height, workarea.height);
int16_t x = MAX(geometry.x, workarea.x);
int16_t y = MAX(geometry.y, workarea.y);
if (x + width + client->border_width * 2 > workarea.x + workarea.width) {
x = workarea.x + workarea.width - width - client->border_width * 2;
}
if (y + height + client->border_width * 2 > workarea.y + workarea.height) {
y = workarea.y + workarea.height - height - client->border_width * 2;
}
area_t rect = {.x = x, .y = y, .width = width, .height = height};
client_apply_geometry(client, rect);
}
void client_apply_rules(client_t *client, const rule_t rules[],
size_t rules_count) {
for (size_t i = 0; i < rules_count; i++) {
const rule_t *r = &rules[i];
if (!((!r->role || (client->role && strstr(client->role, r->role))) &&
(!r->class || (client->class && strstr(client->class, r->class))))) {
continue;
}
client->fullscreen = r->fullscreen;
client->maximize = r->maximize;
client->floating = r->floating;
for (monitor_t *m = wm.monitor_list; m; m = m->next) {
for (tag_t *t = m->tag_list; t; t = t->next) {
if (t->index != r->tag_index) continue;
client->monitor = m;
client->tags = t->mask;
if (r->switch_to_tag) {
wm_set_current_monitor(m, true);
m->selected_tag = t;
logger("++ switch to tag: %u\n", t->index);
}
}
}
}
if (client->fullscreen) {
xcb_change_property(wm.xcb_conn, XCB_PROP_MODE_REPLACE, client->window,
_NET_WM_STATE, XCB_ATOM_ATOM, 32, 1,
&_NET_WM_STATE_FULLSCREEN);
} else if (client->maximize) {
xcb_atom_t max_atoms[] = {
_NET_WM_STATE_MAXIMIZED_HORZ,
_NET_WM_STATE_MAXIMIZED_VERT,
};
xcb_change_property(wm.xcb_conn, XCB_PROP_MODE_REPLACE, client->window,
_NET_WM_STATE, XCB_ATOM_ATOM, 32, countof(max_atoms),
max_atoms);
}
}
void client_focus(client_t *client) {
wm.client_focused = client;
xwindow_focus(client ? client->window : XCB_WINDOW_NONE);
}
void client_stack_raise(client_t *client) {
if (wm.client_stack_list == client) return;
client_detach_stack(client);
client_attach_stack(client);
wm_restack_clients();
}
bool client_is_visible(client_t *client) {
return client->tags & client->monitor->selected_tag->mask;
}
static inline rect_t client_to_rect(client_t *client) {
return (rect_t){
.x1 = client->geometry.x,
.y1 = client->geometry.y,
.x2 = client->geometry.x + (int32_t)client_width(client),
.y2 = client->geometry.y + (int32_t)client_height(client),
};
}
obscured_t client_get_obscured_state(client_t *client, client_t *client_stack) {
if (!client || !client_stack) return obscured_none;
rect_t source = client_to_rect(client);
size_t clip_count = 0;
size_t clip_capacity = 0;
rect_t *clips = nullptr;
bool has_overlap = false;
for (client_t *c = client_stack; c; c = c->stack_next) {
if (c == client) break;
if (c->minimize || !client_is_visible(c)) continue;
rect_t clip = client_to_rect(c);
if (!rect_intersection(source, clip, nullptr)) continue;
has_overlap = true;
if (clip_count == clip_capacity) {
clip_capacity = clip_capacity ? clip_capacity * 2 : 4;
p_realloc(&clips, clip_capacity);
}
clips[clip_count++] = clip;
}
if (!has_overlap) {
p_delete(&clips);
return obscured_none;
}
rect_t *remaining = nullptr;
size_t remaining_count =
rect_subtract_many(source, clips, clip_count, &remaining);
p_delete(&clips);
p_delete(&remaining);
return remaining_count == 0 ? obscured_fully : obscured_partially;
}

View File

@@ -1,76 +0,0 @@
#pragma once
#include <stddef.h>
#include <stdint.h>
#include "color.h"
#include "types.h"
client_t *client_get_by_window(xcb_window_t window);
client_t *client_get_next_by_class(client_t *current, const char *class);
task_in_tag_t *client_get_task_in_tag(client_t *client, tag_t *tag);
task_in_tag_t *client_get_next_task_in_tag(client_t *client, tag_t *tag);
task_in_tag_t *client_get_previous_task_in_tag(client_t *client, tag_t *tag);
void client_manage(xcb_window_t window,
xcb_get_geometry_reply_t *geometry_reply);
char **client_get_task_title(client_t *client);
bool client_need_layout(client_t *client);
void client_send_to_tag(client_t *client, uint32_t tag_mask);
void client_send_to_monitor(client_t *client, monitor_t *monitor);
void client_move_to(client_t *client, int16_t x, int16_t y);
void client_resize(client_t *client, uint16_t width, uint16_t height);
void client_change_border_color(client_t *client, color_t *color);
void client_change_border_width(client_t *client, uint16_t border_width);
void client_update_window_type(client_t *client);
void client_update_wm_hints(client_t *client);
void client_update_size_hints(client_t *client);
void client_set_floating(client_t *client, bool floating);
void client_set_fullscreen(client_t *client, bool fullscreen);
void client_set_maximize(client_t *client, bool maximize);
void client_set_minimize(client_t *client, bool minimize);
void client_set_sticky(client_t *client, bool sticky);
void client_set_urgent(client_t *client, bool urgent);
void client_set_class_instance(client_t *client, const char *class,
const char *instance);
void client_set_name(client_t *client, char *name);
void client_set_icon_name(client_t *client, char *icon_name);
void client_set_net_name(client_t *client, char *name);
void client_set_net_icon_name(client_t *client, char *icon_name);
void client_set_role(client_t *client, char *role);
void client_set_transient_for_window(client_t *client,
xcb_window_t transient_for_window);
void client_set_leader_window(client_t *client, xcb_window_t leader_window);
void client_kill(client_t *client);
void client_unmanage(client_t *client, bool destroyed);
void client_apply_geometry(client_t *client, area_t geometry);
void client_apply_workarea_geometry(client_t *client, area_t geometry);
void client_apply_rules(client_t *client, const rule_t rules[],
size_t rules_count);
void client_focus(client_t *client);
void client_stack_raise(client_t *client);
bool client_is_visible(client_t *client);
typedef enum obscured_t {
obscured_none,
obscured_partially,
obscured_fully,
} obscured_t;
obscured_t client_get_obscured_state(client_t *client, client_t *client_stack);
static inline uint16_t client_width(client_t *c) {
return c->geometry.width + c->border_width * 2;
}
static inline uint16_t client_height(client_t *c) {
return c->geometry.height + c->border_width * 2;
}

View File

@@ -1,22 +0,0 @@
#pragma once
#include <stdint.h>
typedef struct color_t {
uint32_t rgba; /* use for human reading */
uint32_t argb; /* use for xcb */
/* channels of color, use for cairo */
double red; /* red channel */
double green; /* green channel */
double blue; /* blue channel */
double alpha; /* alpha channel */
} color_t;
/**
* @brief parsing color represented in hexadecimal format
* @param hex color represented in hexadecimal format, the supported format
have: RGB/RGBA/RRGGBB/RRGGBBAA/#RGB/#RGBA/#RRGGBB/#RRGGBBAA
* @param color return the parsed color
*/
void color_parse(const char *hex, color_t *const color);

View File

@@ -1,10 +1,10 @@
#include "core/event.h"
#include "interface/event.h"
#include "base/memory.h"
#include "core/window.h"
#include "common/window.h"
void event_cleanup(event_t *event) {
if (!event) return;
if (!event || event->type == ZDWM_EVENT_NONE) return;
switch (event->type) {
case ZDWM_EVENT_WINDOW_MAP_REQUEST:

6
src/common/event.h Normal file
View File

@@ -0,0 +1,6 @@
#pragma once
#include "interface/event.h"
void event_cleanup(event_t *event);
void event_reset(event_t *event);

107
src/common/listeners.c Normal file
View File

@@ -0,0 +1,107 @@
#include "common/listeners.h"
#include <zdwm/listeners.h>
#include "base/array.h"
#include "base/memory.h"
#define ADD_LISTENER(FIELD) \
auto list = &listeners->FIELD; \
auto item = array_push(list->items, list->count, list->capacity); \
item->fn = fn; \
item->user_data = user_data
void listeners_add_output_listener(
listeners_t *listeners,
zdwm_current_output_id_listener fn,
void *user_data
) {
ADD_LISTENER(output_listeners);
}
void listeners_add_initial_workspace_listener(
listeners_t *listeners,
zdwm_initial_workspace_list fn,
void *user_data
) {
ADD_LISTENER(initial_workspace_listeners);
}
void listeners_add_active_workspace_listener(
listeners_t *listeners,
zdwm_workspace_active_updated fn,
void *user_data
) {
ADD_LISTENER(active_workspace_listeners);
}
void listeners_add_layout_notify(
listeners_t *listeners,
zdwm_layout_notify fn,
void *user_data
) {
ADD_LISTENER(layout_listeners);
}
void listeners_add_binding_mode_notify(
listeners_t *listeners,
zdwm_binding_mode_notify fn,
void *user_data
) {
ADD_LISTENER(binding_mode_listeners);
}
void listeners_add_initial_window_listener(
listeners_t *listeners,
zdwm_initial_window_list fn,
void *user_data
) {
ADD_LISTENER(initial_window_listeners);
}
void listeners_add_window_added_listener(
listeners_t *listeners,
zdwm_window_added fn,
void *user_data
) {
ADD_LISTENER(window_added_listeners);
}
void listeners_add_window_updated_listener(
listeners_t *listeners,
zdwm_window_updated fn,
void *user_data
) {
ADD_LISTENER(window_updated_listeners);
}
void listeners_add_window_removed_listener(
listeners_t *listeners,
zdwm_window_removed fn,
void *user_data
) {
ADD_LISTENER(window_removed_listeners);
}
#undef ADD_LISTENER
#define CLEANUP_LIST(FIELD) \
p_delete(&listeners->FIELD.items); \
listeners->FIELD.count = 0; \
listeners->FIELD.capacity = 0
void listeners_cleanup(listeners_t *listeners) {
if (!listeners) return;
CLEANUP_LIST(output_listeners);
CLEANUP_LIST(initial_workspace_listeners);
CLEANUP_LIST(active_workspace_listeners);
CLEANUP_LIST(layout_listeners);
CLEANUP_LIST(binding_mode_listeners);
CLEANUP_LIST(initial_window_listeners);
CLEANUP_LIST(window_added_listeners);
CLEANUP_LIST(window_updated_listeners);
CLEANUP_LIST(window_removed_listeners);
}
#undef CLEANUP_LIST

94
src/common/listeners.h Normal file
View File

@@ -0,0 +1,94 @@
#pragma once
#include <zdwm/listeners.h>
typedef struct binding_table_t binding_table_t;
typedef struct layout_registry_t layout_registry_t;
typedef struct state_t state_t;
#define ITEM(NAME, FN_TYPE) \
typedef struct NAME##_listener_item_t { \
FN_TYPE *fn; \
void *user_data; \
} NAME##_listener_item_t
#define LISTENERS(NAME, FN_TYPE) \
ITEM(NAME, FN_TYPE); \
typedef struct NAME##_listeners_t { \
NAME##_listener_item_t *items; \
size_t count; \
size_t capacity; \
} NAME##_listeners_t
LISTENERS(output, zdwm_current_output_id_listener);
LISTENERS(initial_workspace_list, zdwm_initial_workspace_list);
LISTENERS(workspace_active, zdwm_workspace_active_updated);
LISTENERS(layout, zdwm_layout_notify);
LISTENERS(binding_mode, zdwm_binding_mode_notify);
LISTENERS(initial_window_list, zdwm_initial_window_list);
LISTENERS(window_added, zdwm_window_added);
LISTENERS(window_updated, zdwm_window_updated);
LISTENERS(window_removed, zdwm_window_removed);
#undef ITEM
#undef LISTENERS
typedef struct listeners_t {
output_listeners_t output_listeners;
initial_workspace_list_listeners_t initial_workspace_listeners;
workspace_active_listeners_t active_workspace_listeners;
layout_listeners_t layout_listeners;
binding_mode_listeners_t binding_mode_listeners;
initial_window_list_listeners_t initial_window_listeners;
window_added_listeners_t window_added_listeners;
window_updated_listeners_t window_updated_listeners;
window_removed_listeners_t window_removed_listeners;
} listeners_t;
void listeners_add_output_listener(
listeners_t *listeners,
zdwm_current_output_id_listener fn,
void *user_data
);
void listeners_add_initial_workspace_listener(
listeners_t *listeners,
zdwm_initial_workspace_list fn,
void *user_data
);
void listeners_add_active_workspace_listener(
listeners_t *listeners,
zdwm_workspace_active_updated fn,
void *user_data
);
void listeners_add_layout_notify(
listeners_t *listeners,
zdwm_layout_notify fn,
void *user_data
);
void listeners_add_binding_mode_notify(
listeners_t *listeners,
zdwm_binding_mode_notify fn,
void *user_data
);
void listeners_add_initial_window_listener(
listeners_t *listeners,
zdwm_initial_window_list fn,
void *user_data
);
void listeners_add_window_added_listener(
listeners_t *listeners,
zdwm_window_added fn,
void *user_data
);
void listeners_add_window_updated_listener(
listeners_t *listeners,
zdwm_window_updated fn,
void *user_data
);
void listeners_add_window_removed_listener(
listeners_t *listeners,
zdwm_window_removed fn,
void *user_data
);
void listeners_cleanup(listeners_t *listeners);

55
src/common/window.c Normal file
View File

@@ -0,0 +1,55 @@
#include "common/window.h"
#include "base/array.h"
#include "base/memory.h"
#include "interface/types.h"
window_layer_type_t window_classify_layer(const window_layer_props_t *props) {
for (size_t i = 0; i < props->type_count; ++i) {
window_type_t type = props->types[i];
if (type == ZDWM_WINDOW_TYPE_NOTIFICATION) return ZDWM_WINDOW_LAYER_OVERLAY;
}
for (size_t i = 0; i < props->state_count; ++i) {
window_state_t state = props->states[i];
if (state == ZDWM_WINDOW_STATE_ABOVE) return ZDWM_WINDOW_LAYER_TOP;
}
return ZDWM_WINDOW_LAYER_NORMAL;
}
void window_layer_props_cleanup(window_layer_props_t *props) {
if (!props) return;
p_delete(&props->types);
props->type_count = 0;
p_delete(&props->states);
props->state_count = 0;
}
void window_metadata_cleanup(window_metadata_t *metadata) {
if (!metadata) return;
p_delete(&metadata->title);
p_delete(&metadata->app_id);
p_delete(&metadata->role);
p_delete(&metadata->class_name);
p_delete(&metadata->instance_name);
}
void window_list_push(window_list_t *window_list, zdwm_window_id_t window_id) {
zdwm_window_id_t *window =
array_push(window_list->windows, window_list->count, window_list->capacity);
*window = window_id;
}
void window_list_reset(window_list_t *window_list) {
p_clear(window_list->windows, window_list->capacity);
window_list->count = 0;
}
void window_list_cleanup(window_list_t *window_list) {
p_delete(&window_list->windows);
window_list->count = 0;
window_list->capacity = 0;
}

17
src/common/window.h Normal file
View File

@@ -0,0 +1,17 @@
#pragma once
#include "interface/types.h"
window_layer_type_t window_classify_layer(const window_layer_props_t *props);
void window_layer_props_cleanup(window_layer_props_t *props);
void window_metadata_cleanup(window_metadata_t *metadata);
typedef struct window_list_t {
zdwm_window_id_t *windows;
size_t count;
size_t capacity;
} window_list_t;
void window_list_push(window_list_t *window_list, zdwm_window_id_t window_id);
void window_list_reset(window_list_t *window_list);
void window_list_cleanup(window_list_t *window_list);

46
src/common/workspace.c Normal file
View File

@@ -0,0 +1,46 @@
#include "common/workspace.h"
#include "base/memory.h"
void workspace_desc_cleanup(workspace_desc_t *workspace) {
if (!workspace) return;
p_delete(&workspace->name);
p_delete(&workspace->layout_ids);
workspace->output_index = 0;
workspace->layout_count = 0;
workspace->initial_layout_id = ZDWM_LAYOUT_ID_INVALID;
}
void workspace_desc_list_cleanup(workspace_desc_t **list, size_t *count) {
if (!list || !count) return;
for (size_t i = 0; i < *count; i++) {
workspace_desc_cleanup(&(*list)[i]);
}
p_delete(list);
*count = 0;
}
bool workspace_desc_layouts_valid(const 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;
}
bool workspace_desc_valid(
const workspace_desc_t *workspace,
size_t output_count
) {
if (!workspace || !workspace->name) return false;
if (workspace->output_index >= output_count) return false;
return workspace_desc_layouts_valid(workspace);
}

21
src/common/workspace.h Normal file
View File

@@ -0,0 +1,21 @@
#pragma once
#include <stddef.h>
#include "interface/types.h"
typedef struct workspace_desc_t {
size_t output_index; /* 对应 state_init() 中 outputs[] 的索引 */
const char *name;
const layout_id_t *layout_ids;
size_t layout_count;
layout_id_t initial_layout_id;
} workspace_desc_t;
void workspace_desc_cleanup(workspace_desc_t *workspace);
void workspace_desc_list_cleanup(workspace_desc_t **list, size_t *count);
bool workspace_desc_layouts_valid(const workspace_desc_t *workspace);
bool workspace_desc_valid(
const workspace_desc_t *workspace,
size_t output_count
);

View File

@@ -1,81 +0,0 @@
#include "config.h"
#include <string.h>
#include "default_config.h"
#include "utils.h"
void config_status_set_gap(config_status_t *status_config, uint32_t gap,
uint32_t item_gap) {
if (!status_config) fatal("no valid status config");
status_config->status_gap = gap;
status_config->status_item_gap = item_gap;
}
void config_status_add_item(config_status_t **status_config,
config_status_item_t status_item) {
const auto item_count = (*status_config)->status_count + 1;
const auto new_size =
sizeof(config_status_t) + item_count * sizeof(config_status_item_t);
xrealloc((void **)status_config, (ssize_t)new_size);
(*status_config)->list[item_count - 1] = status_item;
(*status_config)->status_count = item_count;
}
void config_status_filter_item(config_status_t **status_config,
status_item_filter filter) {
if (!status_config || !*status_config) fatal("no valid status config");
if (!filter) return;
config_status_t *config = *status_config;
size_t write_idx = 0;
size_t item_count = config->status_count;
for (size_t read_idx = 0; read_idx < item_count; read_idx++) {
config_status_item_t *item = &config->list[read_idx];
if (!filter(item)) continue;
if (write_idx != read_idx) {
config->list[write_idx] = *item;
}
write_idx++;
}
if (write_idx == item_count) return;
config->status_count = write_idx;
size_t new_size =
sizeof(config_status_t) + write_idx * sizeof(config_status_item_t);
xrealloc((void **)status_config, (ssize_t)new_size);
}
static config_status_t *config_create_default_status(void) {
size_t item_count = countof(status_list);
size_t size = sizeof(config_status_t) + item_count * sizeof(status_list[0]);
config_status_t *status = xmalloc((ssize_t)size);
status->status_gap = status_config.status_gap;
status->status_item_gap = status_config.status_item_gap;
status->status_count = item_count;
memcpy(status->list, status_list, item_count * sizeof(status_list[0]));
return status;
}
static config_t runtime_config = {};
const config_t *init_config(void) {
if (!runtime_config.status) {
runtime_config.status = config_create_default_status();
}
if (!runtime_config.status_renderer) {
runtime_config.status_renderer = renderer_render_status;
}
return &runtime_config;
}
void config_set_custom_status_renderer(config_t *config,
status_renderer_t status_renderer) {
if (!config) fatal("no valid config");
config->status_renderer =
status_renderer ? status_renderer : renderer_render_status;
}

View File

@@ -1,57 +0,0 @@
#pragma once
#include <stddef.h>
#include <stdint.h>
#include "renderer.h"
#include "status.h"
typedef struct config_status_item_t {
status_type_t type;
icon_type_t icon_type;
const char *icon;
const char *color;
} config_status_item_t;
typedef struct config_status_t {
uint32_t status_gap;
uint32_t status_item_gap;
size_t status_count;
config_status_item_t list[];
} config_status_t;
void config_status_set_gap(config_status_t *status_config, uint32_t gap,
uint32_t item_gap);
void config_status_add_item(config_status_t **status_config,
config_status_item_t status_item);
typedef bool (*status_item_filter)(config_status_item_t *item);
/**
* @brief 按过滤条件原地筛选 status 项并保持原有顺序
*
* 当 filter(item) 返回 true 时保留该项,返回 false 时删除该项。
* 筛选后会压缩 list、更新 status_count并收缩配置对象内存。
*
* @param status_config 配置指针地址(函数内部会重分配内存)
* @param filter 过滤函数
*/
void config_status_filter_item(config_status_t **status_config,
status_item_filter filter);
typedef void (*status_renderer_t)(config_status_t *config, size_t config_count,
status_t *status, renderer_status_t *output);
typedef struct config_t {
config_status_t *status;
status_renderer_t status_renderer;
} config_t;
/**
* @brief 初始化配置对象
* @return 返回静态变量指针,不需要释放
*/
const config_t *init_config(void);
void config_set_custom_status_renderer(config_t *config,
status_renderer_t status_renderer);

View File

@@ -1 +0,0 @@
../../.clang-format

View File

@@ -1,16 +1,25 @@
#include "config/defaults.h"
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <zdwm/action.h>
#include <zdwm/bar.h>
#include <zdwm/types.h>
#include "base/log.h"
#include "base/macros.h"
#include "interface/types.h"
static constexpr char launcher[] =
"rofi -show combi -modes combi -combi-modes window,drun,run,ssh,windowcd";
static void spawn(const zdwm_action_ctx_t *ctx, const zdwm_action_arg_t *arg) {
ctx->spawn(arg->str);
}
typedef struct zdwm_action_data_raise_or_run_t raise_or_run_data_t;
static raise_or_run_data_t terminal = {"XTerm", "xterm"};
static raise_or_run_data_t editor = {"Emacs", "emacsclient -a '' -r -n"};
static raise_or_run_data_t browser = {"firefox", "firefox-bin"};
static raise_or_run_data_t chrome = {"Google-chrome", "google-chrome-stable"};
bool config_defaults_build(
const zdwm_api_t *api,
@@ -43,10 +52,12 @@ bool config_defaults_build(
);
zdwm_layout_id_t floating_id =
api->register_layout(builder, "floating", "><>", "floating", nullptr);
if (fair_id == ZDWM_LAYOUT_ID_INVALID ||
maximize_id == ZDWM_LAYOUT_ID_INVALID ||
fullscreen_id == ZDWM_LAYOUT_ID_INVALID ||
floating_id == ZDWM_LAYOUT_ID_INVALID) {
if (
fair_id == ZDWM_LAYOUT_ID_INVALID ||
maximize_id == ZDWM_LAYOUT_ID_INVALID ||
fullscreen_id == ZDWM_LAYOUT_ID_INVALID ||
floating_id == ZDWM_LAYOUT_ID_INVALID
) {
return false;
}
@@ -56,30 +67,142 @@ bool config_defaults_build(
fullscreen_id,
floating_id,
};
for (size_t i = 0; i < output_count; i++) {
if (api->define_workspace(
builder,
i,
"main",
layout_ids,
countof(layout_ids),
fair_id
) == ZDWM_WORKSPACE_ID_INVALID) {
return false;
#define DEF_WORKSPACE(NAME) \
api->define_workspace( \
builder, \
i, \
(NAME), \
layout_ids, \
countof(layout_ids), \
fair_id \
)
char *workspace_names[] = {"1 terminal", "2 Editor", "3 Browser"};
for (size_t j = 0; j < countof(workspace_names); ++j) {
auto workspace = DEF_WORKSPACE(workspace_names[j]);
if (workspace_id_invalid(workspace)) {
logger("== define workspace \"%s\" failed\n", workspace_names[j]);
return false;
}
}
#undef DEF_WORKSPACE
}
auto default_mode = api->add_mode(builder, "default");
#define BIND(mode, key, fn, arg) \
api->bind(builder, mode, key, fn, (zdwm_action_arg_t)arg)
#define Alt_Key "Mod1"
#define Super_key "Mod4"
#define Alt(K) Alt_Key "+" K
#define Super(K) Super_key "+" K
BIND(default_mode, "Mod4+r", spawn, {.str = launcher});
#define BIND(MODE, KEY, ...) \
api->bind(builder, MODE, KEY, (zdwm_action_t)__VA_ARGS__)
#define SPAWN(MODE, KEY, COMMAND) \
BIND(MODE, KEY, {.type = ZDWM_ACTION_SPAWN, .as.spawn.command = (COMMAND)})
#define QUIT(MODE, KEY, RESTART) \
BIND(MODE, KEY, {.type = ZDWM_ACTION_QUIT, .as.quit.restart = (RESTART)})
#define RAISE_OR_RUN(MODE, KEY, ...) \
BIND( \
MODE, \
KEY, \
{ \
.type = ZDWM_ACTION_RAISE_OR_RUN, \
.as.raise_or_run = (zdwm_action_data_raise_or_run_t)__VA_ARGS__, \
} \
)
#define BIND_DEFAULT(KEY, ...) BIND(default_mode, KEY, __VA_ARGS__)
SPAWN(default_mode, Super("r"), launcher);
QUIT(default_mode, Super("Shift+q"), false);
QUIT(default_mode, Super("Control+r"), true);
SPAWN(default_mode, Super("Return"), terminal.command);
RAISE_OR_RUN(default_mode, Alt("Control+r"), terminal);
RAISE_OR_RUN(default_mode, Super("e"), editor);
RAISE_OR_RUN(default_mode, Super("q"), browser);
RAISE_OR_RUN(default_mode, Super("a"), chrome);
BIND_DEFAULT(Super("f"), {.type = ZDWM_ACTION_WINDOW_TOGGLE_FULLSCREEN});
BIND_DEFAULT(Super("m"), {.type = ZDWM_ACTION_WINDOW_TOGGLE_MAXIMIZE});
BIND_DEFAULT(
Super("Control+space"),
{.type = ZDWM_ACTION_WINDOW_TOGGLE_FLOATING}
);
BIND_DEFAULT(
Alt("j"),
{.type = ZDWM_ACTION_WINDOW_FOCUS_CYCLE, .as.window_focus_cycle.delta = 1}
);
BIND_DEFAULT(
Alt("k"),
{.type = ZDWM_ACTION_WINDOW_FOCUS_CYCLE, .as.window_focus_cycle.delta = -1}
);
zdwm_action_t window_cycle_output_action = {
.type = ZDWM_ACTION_WINDOW_CYCLE_OUTPUT,
.as.window_cycle_output = {.delta = 1, .keep_focus = true},
};
BIND_DEFAULT(Super("o"), window_cycle_output_action);
char buffer[64] = {0};
for (uint32_t i = 0; i < 9; ++i) {
snprintf(buffer, sizeof(buffer), Super("%d"), i + 1);
zdwm_action_t switch_workspace_action = {
.type = ZDWM_ACTION_WORKSPACE_SWITCH_SAME_OUTPUT_BY_INDEX,
.as.workspace_switch_same_output_by_index.index = i,
};
BIND_DEFAULT(buffer, switch_workspace_action);
snprintf(buffer, sizeof(buffer), Super("Shift+%d"), i + 1);
zdwm_action_t send_window_to_workspace_action = {
.type = ZDWM_ACTION_WINDOW_SEND_TO_WORKSPACE_SAME_OUTPUT_BY_INDEX,
.as.window_send_to_workspace_same_output_by_index = {
.index = i,
.switch_workspace = false,
},
};
BIND_DEFAULT(buffer, send_window_to_workspace_action);
}
#undef BIND_DEFAULT
#undef BIND
api->set_default_mode(builder, default_mode);
api->set_initial_mode(builder, default_mode);
api->set_border_config(builder, 2, "#1c2022", "#606060");
api->set_interaction_fps(builder, 30);
zdwm_bar_config_t bar_config = {
.height = 28,
.show_top = true,
.padding_x = 10,
.fps = 30,
.font_family = "Terminus, Sarasa Term SC",
.font_size = 10,
.dpi = 144,
.bg = "#222222",
.fg = "#bbbbbb",
.tag_cell_padding_x = 10,
.tag_indicator_width = 4,
.tag_bg = "#222222",
.tag_fg = "#bbbbbb",
.tag_active_bg = "#005577",
.tag_active_fg = "#eeeeee",
.tag_urgent_bg = "#222222",
.tag_urgent_fg = "#ff0000",
.layout_bg = "#222222",
.layout_fg = "#bbbbbb",
.binding_show_default = true,
.binding_padding_x = 2,
.binding_mode_bg = "#900000",
.binding_mode_fg = "#ffffff",
.window_focused_bg = "#005577",
.window_focused_fg = "#eeeeee",
};
api->set_bar_config(builder, bar_config);
return true;
}

View File

@@ -10,9 +10,9 @@
static char *config_loader_join_path(const char *base, const char *suffix) {
if (!base || !suffix) return nullptr;
size_t base_len = strlen(base);
size_t base_len = strlen(base);
size_t suffix_len = strlen(suffix);
char *path = p_new(char, base_len + suffix_len + 1);
char *path = p_new(char, base_len + suffix_len + 1);
memcpy(path, base, base_len);
memcpy(path + base_len, suffix, suffix_len + 1);
return path;
@@ -25,28 +25,28 @@ static bool config_loader_resolve_path(
) {
const char *path = override_path;
if (path && *path) {
*out_path = p_strdup(path);
*out_path = p_strdup(path);
*out_is_explicit = true;
return true;
}
path = getenv("ZDWM_CONFIG_LIB");
if (path && *path) {
*out_path = p_strdup(path);
*out_path = p_strdup(path);
*out_is_explicit = true;
return true;
}
path = getenv("XDG_CONFIG_HOME");
if (path && *path) {
*out_path = config_loader_join_path(path, "/zdwm/config.so");
*out_path = config_loader_join_path(path, "/zdwm/config.so");
*out_is_explicit = false;
return *out_path != nullptr;
}
path = getenv("HOME");
if (path && *path) {
*out_path = config_loader_join_path(path, "/.config/zdwm/config.so");
*out_path = config_loader_join_path(path, "/.config/zdwm/config.so");
*out_is_explicit = false;
return *out_path != nullptr;
}
@@ -79,7 +79,7 @@ bool config_loader_load(config_loader_t *loader, const char *override_path) {
}
dlerror();
loader->setup = (zdwm_config_setup_fn *)dlsym(loader->handle, "setup");
loader->setup = (zdwm_config_setup_fn *)dlsym(loader->handle, "setup");
const char *error = dlerror();
if (!error) return true;

View File

@@ -2,18 +2,22 @@
#include <dlfcn.h>
#include <stddef.h>
#include <stdint.h>
#include <zdwm/bar.h>
#include <zdwm/config.h>
#include <zdwm/listeners.h>
#include "base/array.h"
#include "base/color.h"
#include "base/memory.h"
#include "common/listeners.h"
#include "common/workspace.h"
#include "config/defaults.h"
#include "config/loader.h"
#include "core/binding.h"
#include "core/layout.h"
#include "core/rules.h"
#include "core/runtime.h"
#include "core/types.h"
#include "core/wm_desc.h"
#include "interface/types.h"
#include "layouts/fair.h"
#include "layouts/fullscreen.h"
#include "layouts/maximize.h"
@@ -25,18 +29,25 @@ struct zdwm_config_builder_t {
size_t workspace_count;
size_t workspace_capacity;
size_t output_count;
border_config_t border;
uint32_t fps;
binding_table_t *binding_table;
listeners_t listeners;
zdwm_bar_config_t bar;
};
void runtime_config_cleanup(runtime_init_desc_t *desc) {
if (!desc) return;
if (desc->layouts.slots || desc->layouts.slot_count ||
desc->layouts.slot_capacity) {
if (
desc->layouts.slots || desc->layouts.slot_count ||
desc->layouts.slot_capacity
) {
layout_registry_cleanup(&desc->layouts);
}
binding_table_destroy(desc->binding_table);
desc->binding_table = nullptr;
listeners_cleanup(&desc->listeners);
workspace_desc_list_cleanup(&desc->workspaces, &desc->workspace_count);
if (desc->config_module_handle) dlclose(desc->config_module_handle);
desc->config_module_handle = nullptr;
@@ -45,16 +56,19 @@ void runtime_config_cleanup(runtime_init_desc_t *desc) {
static void config_builder_cleanup(zdwm_config_builder_t *builder) {
if (!builder) return;
if (builder->layouts.slots || builder->layouts.slot_count ||
builder->layouts.slot_capacity) {
if (
builder->layouts.slots || builder->layouts.slot_count ||
builder->layouts.slot_capacity
) {
layout_registry_cleanup(&builder->layouts);
}
workspace_desc_list_cleanup(&builder->workspaces, &builder->workspace_count);
builder->workspace_capacity = 0;
builder->output_count = 0;
builder->output_count = 0;
binding_table_destroy(builder->binding_table);
builder->binding_table = nullptr;
listeners_cleanup(&builder->listeners);
}
static layout_id_t runtime_config_register_layout(
@@ -84,10 +98,10 @@ static workspace_id_t runtime_config_define_workspace(
}
workspace_desc_t desc = {
.output_index = output_index,
.name = name,
.layout_ids = layout_ids,
.layout_count = layout_count,
.output_index = output_index,
.name = name,
.layout_ids = layout_ids,
.layout_count = layout_count,
.initial_layout_id = initial_layout_id,
};
if (!workspace_desc_valid(&desc, builder->output_count)) {
@@ -101,15 +115,15 @@ static workspace_id_t runtime_config_define_workspace(
}
workspace_id_t workspace_id = (workspace_id_t)builder->workspace_count;
workspace_desc_t *slot = array_push(
workspace_desc_t *slot = array_push(
builder->workspaces,
builder->workspace_count,
builder->workspace_capacity
);
slot->output_index = output_index;
slot->name = p_strdup(name);
slot->layout_ids = p_copy(layout_ids, layout_count);
slot->layout_count = layout_count;
slot->output_index = output_index;
slot->name = p_strdup(name);
slot->layout_ids = p_copy(layout_ids, layout_count);
slot->layout_count = layout_count;
slot->initial_layout_id = initial_layout_id;
return workspace_id;
}
@@ -125,7 +139,7 @@ static bool
rule_action_valid(const rule_action_t *action, size_t workspace_count) {
if (!action) return false;
if (action->workspace == ZDWM_WORKSPACE_ID_INVALID) {
if (workspace_id_invalid(action->workspace)) {
return action->switch_to_workspace || action->fullscreen ||
action->maximize || action->floating;
}
@@ -142,14 +156,14 @@ static bool runtime_config_add_rule(
if (!rule_match_valid(match)) return false;
if (!rule_action_valid(action, builder->workspace_count)) return false;
rules_t *rules = &builder->rules;
rules_t *rules = &builder->rules;
rule_item_t *rule = array_push(rules->items, rules->count, rules->capacity);
rule->match.app_id = p_strdup_nullable(match->app_id);
rule->match.role = p_strdup_nullable(match->role);
rule->match.class_name = p_strdup_nullable(match->class_name);
rule->match.app_id = p_strdup_nullable(match->app_id);
rule->match.role = p_strdup_nullable(match->role);
rule->match.class_name = p_strdup_nullable(match->class_name);
rule->match.instance_name = p_strdup_nullable(match->instance_name);
rule->action = *action;
rule->action = *action;
return true;
}
@@ -163,10 +177,9 @@ static bool runtime_config_bind(
zdwm_config_builder_t *builder,
zdwm_binding_mode_id_t mode,
const char *key,
zdwm_action_fn fn,
zdwm_action_arg_t arg
zdwm_action_t action
) {
return binding_table_add_bind(builder->binding_table, mode, key, fn, arg);
return binding_table_add_bind(builder->binding_table, mode, key, action);
}
static bool runtime_config_set_default_mode(
@@ -183,6 +196,113 @@ static bool runtime_config_set_initial_mode(
return binding_table_set_current_mode(builder->binding_table, mode_id);
}
static void runtime_config_set_border_config(
zdwm_config_builder_t *builder,
uint32_t width,
const char *normal,
const char *focused
) {
builder->border.width = width;
color_parse(normal, &builder->border.normal_color);
color_parse(focused, &builder->border.focused_color);
}
static void runtime_config_set_interaction_fps(
zdwm_config_builder_t *builder,
uint32_t fps
) {
builder->fps = fps;
}
static void runtime_config_subscribe_current_output(
zdwm_config_builder_t *builder,
zdwm_current_output_id_listener *fn,
void *user_data
) {
if (!builder || !fn) return;
listeners_add_output_listener(&builder->listeners, fn, user_data);
}
static void runtime_config_subscribe_initial_workspace_list(
zdwm_config_builder_t *builder,
zdwm_initial_workspace_list *fn,
void *user_data
) {
if (!builder || !fn) return;
listeners_add_initial_workspace_listener(&builder->listeners, fn, user_data);
}
static void runtime_config_subscribe_workspace_active(
zdwm_config_builder_t *builder,
zdwm_workspace_active_updated *fn,
void *user_data
) {
if (!builder || !fn) return;
listeners_add_active_workspace_listener(&builder->listeners, fn, user_data);
}
static void runtime_config_subscribe_layout(
zdwm_config_builder_t *builder,
zdwm_layout_notify *fn,
void *user_data
) {
if (!builder || !fn) return;
listeners_add_layout_notify(&builder->listeners, fn, user_data);
}
static void runtime_config_subscribe_binding_mode(
zdwm_config_builder_t *builder,
zdwm_binding_mode_notify *fn,
void *user_data
) {
if (!builder || !fn) return;
listeners_add_binding_mode_notify(&builder->listeners, fn, user_data);
}
static void runtime_config_subscribe_initial_window_list(
zdwm_config_builder_t *builder,
zdwm_initial_window_list *fn,
void *user_data
) {
if (!builder || !fn) return;
listeners_add_initial_window_listener(&builder->listeners, fn, user_data);
}
static void runtime_config_subscribe_window_added(
zdwm_config_builder_t *builder,
zdwm_window_added *fn,
void *user_data
) {
if (!builder || !fn) return;
listeners_add_window_added_listener(&builder->listeners, fn, user_data);
}
static void runtime_config_subscribe_window_updated(
zdwm_config_builder_t *builder,
zdwm_window_updated *fn,
void *user_data
) {
if (!builder || !fn) return;
listeners_add_window_updated_listener(&builder->listeners, fn, user_data);
}
static void runtime_config_subscribe_window_removed(
zdwm_config_builder_t *builder,
zdwm_window_removed *fn,
void *user_data
) {
if (!builder || !fn) return;
listeners_add_window_removed_listener(&builder->listeners, fn, user_data);
}
static void runtime_config_set_bar_config(
zdwm_config_builder_t *builder,
zdwm_bar_config_t config
) {
if (!builder) return;
builder->bar = config;
}
static bool config_builder_finish(
zdwm_config_builder_t *builder,
runtime_init_desc_t *out
@@ -193,14 +313,20 @@ static bool config_builder_finish(
if (!layout_registry_move(&builder->layouts, &out->layouts)) return false;
if (!rules_move(&builder->rules, &out->rules)) return false;
out->binding_table = builder->binding_table;
out->workspaces = builder->workspaces;
out->workspace_count = builder->workspace_count;
builder->binding_table = nullptr;
builder->workspaces = nullptr;
builder->workspace_count = 0;
out->border = builder->border;
out->fps = builder->fps;
out->binding_table = builder->binding_table;
out->workspaces = builder->workspaces;
out->workspace_count = builder->workspace_count;
out->listeners = builder->listeners;
out->bar = builder->bar;
builder->binding_table = nullptr;
builder->workspaces = nullptr;
builder->workspace_count = 0;
builder->workspace_capacity = 0;
builder->output_count = 0;
builder->output_count = 0;
p_clear(&builder->listeners, 1);
p_clear(&builder->bar, 1);
return true;
}
@@ -212,25 +338,41 @@ static bool runtime_config_build(
) {
if (!setup || !out) return false;
zdwm_config_builder_t builder = {0};
builder.output_count = output_count;
builder.binding_table = binding_table_create();
builder.output_count = output_count;
builder.binding_table = binding_table_create();
/* clang-format off */
zdwm_api_t api = {
.abi_version = ZDWM_CONFIG_ABI_VERSION,
.builtin_layouts =
{
.fair = fair,
.fullscreen = fullscreen,
.maximize = maximize,
},
.register_layout = runtime_config_register_layout,
.define_workspace = runtime_config_define_workspace,
.add_rule = runtime_config_add_rule,
.add_mode = runtime_config_add_mode,
.bind = runtime_config_bind,
.set_default_mode = runtime_config_set_default_mode,
.set_initial_mode = runtime_config_set_initial_mode,
.builtin_layouts = {
.fair = fair,
.fullscreen = fullscreen,
.maximize = maximize,
},
.register_layout = runtime_config_register_layout,
.define_workspace = runtime_config_define_workspace,
.add_rule = runtime_config_add_rule,
.add_mode = runtime_config_add_mode,
.bind = runtime_config_bind,
.set_default_mode = runtime_config_set_default_mode,
.set_initial_mode = runtime_config_set_initial_mode,
.set_border_config = runtime_config_set_border_config,
.set_interaction_fps = runtime_config_set_interaction_fps,
.subscribe_current_output = runtime_config_subscribe_current_output,
.subscribe_initial_workspace_list = runtime_config_subscribe_initial_workspace_list,
.subscribe_workspace_active = runtime_config_subscribe_workspace_active,
.subscribe_layout = runtime_config_subscribe_layout,
.subscribe_binding_mode = runtime_config_subscribe_binding_mode,
.subscribe_initial_window_list = runtime_config_subscribe_initial_window_list,
.subscribe_window_added = runtime_config_subscribe_window_added,
.subscribe_window_updated = runtime_config_subscribe_window_updated,
.subscribe_window_removed = runtime_config_subscribe_window_removed,
.set_bar_config = runtime_config_set_bar_config,
};
/* clang-format on */
bool ok = setup(&api, &builder, outputs, output_count) &&
config_builder_finish(&builder, out);
config_builder_cleanup(&builder);
@@ -252,7 +394,7 @@ bool runtime_config_load(const char *override_path, runtime_init_desc_t *out) {
bool ok = runtime_config_build(setup, out->outputs, out->output_count, out);
if (ok) {
out->config_module_handle = loader.handle;
loader.handle = nullptr;
loader.handle = nullptr;
}
config_loader_cleanup(&loader);

View File

@@ -1,6 +1,33 @@
#pragma once
typedef struct runtime_init_desc_t runtime_init_desc_t;
#include <stddef.h>
#include <stdint.h>
#include <zdwm/bar.h>
#include <zdwm/types.h>
#include "common/listeners.h"
#include "common/workspace.h"
#include "core/binding.h"
#include "core/layout.h"
#include "core/rules.h"
#include "interface/backend.h"
typedef struct runtime_init_desc_t {
backend_t *backend;
zdwm_output_info_t *outputs;
size_t output_count;
layout_registry_t layouts;
rules_t rules;
border_config_t border;
uint32_t fps;
workspace_desc_t *workspaces;
size_t workspace_count;
void *config_module_handle;
binding_table_t *binding_table;
listeners_t listeners;
zdwm_bar_config_t bar;
} runtime_init_desc_t;
bool runtime_config_load(const char *override_path, runtime_init_desc_t *out);
void runtime_config_cleanup(runtime_init_desc_t *desc);

View File

@@ -1 +0,0 @@
../../.clang-format

View File

@@ -1,39 +0,0 @@
#pragma once
#include <stddef.h>
#include "core/event.h"
#include "core/plan.h"
#include "core/types.h"
typedef struct backend_t backend_t;
typedef struct backend_detect_t {
output_info_t *outputs;
size_t output_count;
} backend_detect_t;
backend_t *backend_create(const char *display_name);
void backend_destroy(backend_t *backend);
backend_detect_t *backend_detect(backend_t *backend);
void backend_detect_destroy(backend_detect_t *detect);
/**
* @brief 阻塞等待 backend 产出下一个可交给 runtime 的归一化事件
* @details
* backend 可以在内部丢弃无关的原始平台事件,直到拿到一个可路由事件或遇到
* stop/error 条件。
*
* 返回 true 时,`event` 已被完整填充;若其中包含堆内存,所有权转交给调用方,
* 调用方必须在本轮处理结束后调用 event_cleanup() 或 event_reset()。
*
* 返回 false 时,表示 backend 请求停止或遇到错误;此时 `event` 必须保持为
* 无需 cleanup 的状态,调用方可以直接退出循环。
*/
bool backend_next_event(backend_t *backend, event_t *event);
bool backend_apply_effect(
backend_t *backend,
const effect_t *effects,
size_t effect_count
);

View File

@@ -12,7 +12,7 @@
#include "base/array.h"
#include "base/macros.h"
#include "base/memory.h"
#include "core/types.h"
#include "interface/types.h"
typedef struct binding_mode_t {
zdwm_binding_mode_id_t id;
@@ -43,10 +43,9 @@ binding_table_add_mode(binding_table_t *table, const char *mode_name) {
}
zdwm_binding_mode_id_t id = (zdwm_binding_mode_id_t)table->count;
binding_mode_t *new_mode =
array_push(table->modes, table->count, table->capacity);
auto new_mode = array_push(table->modes, table->count, table->capacity);
new_mode->name = mode_name;
new_mode->id = id;
new_mode->id = id;
return id;
}
@@ -83,8 +82,10 @@ static const mod_entry_t mod_table[] = {
static modifier_mask_t lookup_modifier(const char *name, size_t len) {
for (size_t i = 0; i < countof(mod_table); ++i) {
if (strlen(mod_table[i].name) == len &&
strncmp(name, mod_table[i].name, len) == 0) {
if (
strlen(mod_table[i].name) == len &&
strncmp(name, mod_table[i].name, len) == 0
) {
return mod_table[i].mask;
}
}
@@ -119,13 +120,13 @@ static bool parse_key_sequence(
if (!key_sequence || !modifiers || !keysym) return false;
modifier_mask_t mods = ZDWM_MOD_NONE;
xkb_keysym_t key = XKB_KEY_NoSymbol;
xkb_keysym_t key = XKB_KEY_NoSymbol;
const char *p = key_sequence;
const char *p = key_sequence;
const char *end = key_sequence + strlen(key_sequence);
while (p < end) {
const char *plus = strchr(p, '+');
const char *plus = strchr(p, '+');
const char *seg_end = plus ? plus : end;
const char *s = p;
@@ -140,7 +141,7 @@ static bool parse_key_sequence(
modifier_mask_t mod = lookup_modifier(s, (size_t)(e - s));
if (mod == ZDWM_MOD_NONE) return false;
mods |= mod;
p = plus + 1;
p = plus + 1;
} else {
char key_buf[64];
size_t key_len = (size_t)(e - s);
@@ -153,7 +154,7 @@ static bool parse_key_sequence(
return false;
} else {
*modifiers = mods;
*keysym = key;
*keysym = key;
return true;
}
}
@@ -166,22 +167,19 @@ bool binding_table_add_bind(
binding_table_t *table,
zdwm_binding_mode_id_t mode_id,
const char *key_sequence,
zdwm_action_fn fn,
zdwm_action_arg_t arg
zdwm_action_t action
) {
binding_mode_t *mode = binding_table_get_mode(table, mode_id);
if (!mode) return false;
modifier_mask_t modifiers = 0;
xkb_keysym_t keysym = XKB_KEY_NoSymbol;
xkb_keysym_t keysym = XKB_KEY_NoSymbol;
if (!parse_key_sequence(key_sequence, &modifiers, &keysym)) return false;
key_binding_t *item = array_push(mode->items, mode->count, mode->capacity);
item->key_str = key_sequence;
item->modifiers = modifiers;
item->keysym = keysym;
item->fn = fn;
item->arg = arg;
auto item = array_push(mode->items, mode->count, mode->capacity);
item->modifiers = modifiers;
item->keysym = keysym;
item->action = action;
return true;
}
@@ -192,13 +190,13 @@ void binding_table_destroy(binding_table_t *table) {
for (size_t i = 0; i < table->count; ++i) {
binding_mode_t *mode = &table->modes[i];
p_delete(&mode->items);
mode->count = 0;
mode->count = 0;
mode->capacity = 0;
}
p_delete(&table->modes);
table->count = 0;
table->capacity = 0;
table->count = 0;
table->capacity = 0;
table->default_mode = ZDWM_BINDING_MODE_ID_INVALID;
p_delete(&table);
@@ -228,14 +226,17 @@ bool binding_table_set_current_mode(
binding_table_t *table,
zdwm_binding_mode_id_t mode_id
) {
if (mode_id >= table->count) return false;
if (mode_id >= table->count || mode_id == table->current_mode) return false;
table->current_mode = mode_id;
return true;
}
bool binding_table_cycle_mode(binding_table_t *table, int delta) {
if (!table || !table->modes || table->count < 1) return false;
zdwm_binding_mode_id_t
binding_table_cycle_mode(const binding_table_t *table, int32_t delta) {
if (!table || !table->modes || table->count <= 1) {
return ZDWM_BINDING_MODE_ID_INVALID;
}
bool matched = false;
size_t index = (size_t)table->current_mode;
@@ -244,24 +245,20 @@ bool binding_table_cycle_mode(binding_table_t *table, int delta) {
} else {
for (size_t i = 0; i < table->count; ++i) {
if (table->modes[i].id == table->current_mode) {
index = i;
index = i;
matched = true;
break;
}
}
}
if (!matched) return false;
if (!matched) return ZDWM_BINDING_MODE_ID_INVALID;
/* 只有一个模式没必要切换 */
if (table->count == 1) return true;
int64_t count = (int64_t)table->count;
int64_t count = (int64_t)table->count;
int64_t next_index = ((int64_t)index + (int64_t)delta) % count;
if (next_index < 0) next_index += count;
table->current_mode = table->modes[next_index].id;
return true;
return table->modes[next_index].id;
}
const key_binding_t *
@@ -272,3 +269,48 @@ binding_table_get_current_bindings(binding_table_t *table, size_t *count) {
*count = mode->count;
return mode->items;
}
size_t binding_table_mode_count(const binding_table_t *table) {
if (!table) return 0;
return table->count;
}
zdwm_binding_mode_id_t binding_table_get_default_mode(
const binding_table_t *table
) {
if (!table) return ZDWM_BINDING_MODE_ID_INVALID;
return table->default_mode;
}
zdwm_binding_mode_id_t binding_table_get_current_mode(
const binding_table_t *table
) {
if (!table) return ZDWM_BINDING_MODE_ID_INVALID;
return table->current_mode;
}
static const binding_mode_t *binding_table_get_mode_const(
const binding_table_t *table,
zdwm_binding_mode_id_t mode_id
) {
if (!table || mode_id >= table->count) return nullptr;
const binding_mode_t *mode = &table->modes[mode_id];
if (mode->id == mode_id) return mode;
for (size_t i = 0; i < table->count; ++i) {
mode = &table->modes[i];
if (mode->id == mode_id) return mode;
}
return nullptr;
}
const char *binding_table_get_mode_name(
const binding_table_t *table,
zdwm_binding_mode_id_t mode_id
) {
auto mode = binding_table_get_mode_const(table, mode_id);
if (!mode) return nullptr;
return mode->name;
}

View File

@@ -1,19 +1,18 @@
#pragma once
#include <stddef.h>
#include <stdint.h>
#include <zdwm/action.h>
#include <zdwm/types.h>
#include "core/types.h"
#include "interface/types.h"
typedef struct binding_table_t binding_table_t;
typedef struct key_binding_t {
const char *key_str;
modifier_mask_t modifiers;
keysym_t keysym;
zdwm_action_fn *fn;
zdwm_action_arg_t arg;
zdwm_action_t action;
} key_binding_t;
/**
@@ -37,8 +36,7 @@ binding_table_add_mode(binding_table_t *table, const char *mode_name);
* @param table 按键绑定表
* @param mode_id 模式 ID
* @param key_sequence 按键序列的字符串表达
* @param fn 用户行为函数
* @param arg 用户行为函数所需的参数
* @param action 用户行为描述
*
* @return 添加成功返回 true 否则返回 false
*/
@@ -46,8 +44,7 @@ bool binding_table_add_bind(
binding_table_t *table,
zdwm_binding_mode_id_t mode_id,
const char *key_sequence,
zdwm_action_fn fn,
zdwm_action_arg_t arg
zdwm_action_t action
);
/**
@@ -89,12 +86,15 @@ bool binding_table_set_current_mode(
/**
* @brief 切换按键绑定模式
*
* @details 纯计算不修改任何内容
*
* @param table 按键绑定表
* @param delta 切换变化量
*
* @return 无法切换返回 false 否则返回 true
* @return 切换后的模式 ID
*/
bool binding_table_cycle_mode(binding_table_t *table, int delta);
zdwm_binding_mode_id_t
binding_table_cycle_mode(const binding_table_t *table, int32_t delta);
/**
* @brief 获取当前按键绑定列表
@@ -110,3 +110,15 @@ bool binding_table_cycle_mode(binding_table_t *table, int delta);
*/
const key_binding_t *
binding_table_get_current_bindings(binding_table_t *table, size_t *count);
size_t binding_table_mode_count(const binding_table_t *table);
zdwm_binding_mode_id_t binding_table_get_default_mode(
const binding_table_t *table
);
zdwm_binding_mode_id_t binding_table_get_current_mode(
const binding_table_t *table
);
const char *binding_table_get_mode_name(
const binding_table_t *table,
zdwm_binding_mode_id_t mode_id
);

View File

@@ -1,14 +1,35 @@
#pragma once
#include "core/types.h"
#include "core/wm_desc.h"
#include "core/window_info.h"
#include "interface/types.h"
typedef enum command_type_t {
ZDWM_COMMAND_MANAGE_WINDOW,
ZDWM_COMMAND_UNMANAGE_WINDOW,
ZDWM_COMMAND_FOCUS_WINDOW,
ZDWM_COMMAND_KILL_WINDOW,
ZDWM_COMMAND_RAISE_WINDOW,
ZDWM_COMMAND_WITHDRAW_WINDOW,
ZDWM_COMMAND_CONFIGURE_WINDOW,
ZDWM_COMMAND_NOTIFY_CONFIGURE,
ZDWM_COMMAND_CHANGE_WINDOW_STATE,
ZDWM_COMMAND_START_MOVE_WINDOW,
ZDWM_COMMAND_STOP_MOVE_WINDOW,
ZDWM_COMMAND_START_RESIZE_WINDOW,
ZDWM_COMMAND_STOP_RESIZE_WINDOW,
ZDWM_COMMAND_WINDOW_SEND_TO_WORKSPACE,
ZDWM_COMMAND_WINDOW_SET_FLOATING,
ZDWM_COMMAND_WINDOW_SET_STICKY,
ZDWM_COMMAND_WINDOW_SET_MINIMIZED,
ZDWM_COMMAND_WINDOW_SET_MAXIMIZED,
ZDWM_COMMAND_WINDOW_SET_FULLSCREEN,
ZDWM_COMMAND_CHANGE_HINTS,
ZDWM_COMMAND_SWITCH_WORKSPACE,
ZDWM_COMMAND_SET_CURRENT_OUTPUT,
ZDWM_COMMAND_SET_LAYOUT,
ZDWM_COMMAND_SET_BINDING_MODE,
ZDWM_COMMAND_SET_BAR_VISIBILITY,
ZDWM_COMMAND_QUIT,
} command_type_t;
typedef struct manage_window_command_t {
@@ -18,13 +39,50 @@ typedef struct manage_window_command_t {
} manage_window_command_t;
typedef struct switch_workspace_command_t {
output_id_t output;
workspace_id_t workspace;
} switch_workspace_command_t;
typedef struct only_window_command_t {
typedef struct window_state_change_command_t {
window_id_t window;
} only_window_command_t;
window_state_request_type_t type;
window_state_request_action_t action;
} window_state_change_command_t;
typedef struct window_start_move_command_t {
window_id_t window;
point_t pointer_coordinate;
} start_interaction_command_t;
typedef struct window_send_to_workspace_command_t {
window_id_t window;
workspace_id_t workspace;
} window_send_to_workspace_command_t;
typedef struct window_bool_state_t {
window_id_t window;
bool state;
} window_bool_state_t;
typedef struct set_current_output_command_t {
output_id_t output;
} set_current_output_command_t;
typedef struct set_layout_command_t {
workspace_id_t workspace;
layout_id_t layout;
} set_layout_command_t;
typedef struct set_binding_mode_command_t {
zdwm_binding_mode_id_t mode;
} set_binding_mode_command_t;
typedef struct visibility_command_t {
bool visible;
} visibility_command_t;
typedef struct quit_command_t {
bool will_restart;
} quit_command_t;
/**
* @brief 非 owning 的命令值对象
@@ -39,9 +97,28 @@ typedef struct command_t {
command_type_t type;
union {
manage_window_command_t manage_window;
only_window_command_t unmanage;
only_window_command_t focus;
only_window_command_t kill;
only_window_data_t unmanage;
only_window_data_t focus;
only_window_data_t kill;
only_window_data_t raise;
only_window_data_t withdraw;
configure_data_t configure;
only_window_data_t notify_configure;
window_state_change_command_t state_change;
start_interaction_command_t move;
start_interaction_command_t resize;
window_send_to_workspace_command_t send_to_workspace;
window_bool_state_t floating;
window_bool_state_t sticky;
window_bool_state_t minimized;
window_bool_state_t maximized;
window_bool_state_t fullscreen;
hints_data_t hints;
switch_workspace_command_t switch_workspace;
set_current_output_command_t current_output;
set_layout_command_t layout;
set_binding_mode_command_t binding_mode;
visibility_command_t bar_visibility;
quit_command_t quit;
} as;
} command_t;

View File

@@ -13,11 +13,11 @@ void command_buffer_reset(command_buffer_t *buffer) {
void command_buffer_cleanup(command_buffer_t *buffer) {
p_delete(&buffer->items);
buffer->count = 0;
buffer->count = 0;
buffer->capacity = 0;
}
void command_buffer_push(command_buffer_t *buffer, const command_t *command) {
command_t *cmd = array_push(buffer->items, buffer->count, buffer->capacity);
*cmd = *command;
*cmd = *command;
}

View File

@@ -4,11 +4,11 @@
#include "base/array.h"
#include "base/memory.h"
#include "core/types.h"
#include "interface/types.h"
void layer_stack_cleanup(layer_stack_t *layer) {
p_delete(&layer->order);
layer->count = 0;
layer->count = 0;
layer->capacity = 0;
}
@@ -19,7 +19,7 @@ void layer_stack_append(layer_stack_t *layer, window_id_t window) {
}
bool layer_stack_remove(layer_stack_t *layer, window_id_t window) {
bool found = false;
bool found = false;
size_t index = 0;
for (size_t i = 0; i < layer->count; ++i) {
if (layer->order[i] != window) continue;
@@ -40,7 +40,7 @@ bool layer_stack_remove(layer_stack_t *layer, window_id_t window) {
}
bool layer_stack_raise(layer_stack_t *layer, window_id_t window) {
bool found = false;
bool found = false;
size_t index = 0;
for (size_t i = 0; i < layer->count; ++i) {
if (layer->order[i] != window) continue;
@@ -64,7 +64,7 @@ bool layer_stack_raise(layer_stack_t *layer, window_id_t window) {
}
bool layer_stack_lower(layer_stack_t *layer, window_id_t window) {
bool found = false;
bool found = false;
size_t index = 0;
for (size_t i = 0; i < layer->count; ++i) {
if (layer->order[i] != window) continue;

View File

@@ -2,7 +2,7 @@
#include <stddef.h>
#include "core/types.h"
#include "interface/types.h"
typedef struct layer_stack_t {
window_id_t *order;

View File

@@ -6,11 +6,11 @@
#include "base/array.h"
#include "base/memory.h"
#include "core/types.h"
#include "interface/types.h"
void layout_result_cleanup(layout_result_t *result) {
p_delete(&result->items);
result->item_count = 0;
result->item_count = 0;
result->item_capacity = 0;
}
@@ -31,19 +31,19 @@ void layout_registry_cleanup(layout_registry_t *registry) {
}
p_delete(&registry->slots);
registry->slot_count = 0;
registry->slot_count = 0;
registry->slot_capacity = 0;
}
bool layout_registry_move(layout_registry_t *src, layout_registry_t *dest) {
if (!src || !dest) return false;
dest->slots = src->slots;
dest->slot_count = src->slot_count;
dest->slots = src->slots;
dest->slot_count = src->slot_count;
dest->slot_capacity = src->slot_capacity;
src->slots = nullptr;
src->slot_count = 0;
src->slots = nullptr;
src->slot_count = 0;
src->slot_capacity = 0;
return true;
@@ -71,11 +71,11 @@ layout_id_t layout_register(
layout_id_t id = (layout_id_t)registry->slot_count;
layout_slot_t *r =
array_push(registry->slots, registry->slot_count, registry->slot_capacity);
r->id = id;
r->name = p_strdup(name);
r->symbol = p_strdup(symbol);
r->id = id;
r->name = p_strdup(name);
r->symbol = p_strdup(symbol);
r->description = p_strdup_nullable(description);
r->fn = fn;
r->fn = fn;
return r->id;
}

View File

@@ -3,7 +3,7 @@
#include <stddef.h>
#include <zdwm/layout.h>
#include "core/types.h"
#include "interface/types.h"
typedef zdwm_layout_ctx_t layout_ctx_t;
typedef zdwm_layout_item_t layout_item_t;

193
src/core/listeners.c Normal file
View File

@@ -0,0 +1,193 @@
#include "core/listeners.h"
#include "base/memory.h"
#include "common/listeners.h"
#include "core/binding.h"
#include "core/layout.h"
#include "core/state.h"
#include "core/window.h"
#define FOR_EACH_LISTENER(FIELD, BODY) \
do { \
if (!(listeners)) return; \
auto list = &(listeners)->FIELD; \
for (size_t i = 0; i < list->count; ++i) { \
auto item = &list->items[i]; \
BODY; \
} \
} while (false)
void listeners_notify_current_output(
const listeners_t *listeners,
output_id_t output_id
) {
FOR_EACH_LISTENER(output_listeners, item->fn(output_id, item->user_data));
}
void listeners_notify_initial_workspaces(
const listeners_t *listeners,
const state_t *state
) {
if (!listeners || !state) return;
size_t count = state_workspace_count(state);
auto items = count ? p_new(zdwm_workspace_t, count) : nullptr;
for (size_t i = 0; i < count; ++i) {
auto workspace = state_workspace_at(state, i);
items[i] = (zdwm_workspace_t){
.output_id = workspace->output_id,
.id = workspace->id,
.name = workspace->name,
};
}
FOR_EACH_LISTENER(
initial_workspace_listeners,
item->fn(items, count, item->user_data)
);
p_delete(&items);
}
void listeners_notify_workspace_active(
const listeners_t *listeners,
output_id_t output_id,
workspace_id_t workspace_id
) {
FOR_EACH_LISTENER(
active_workspace_listeners,
item->fn(output_id, workspace_id, item->user_data)
);
}
void listeners_notify_layout(
const listeners_t *listeners,
const layout_registry_t *layouts,
workspace_id_t workspace_id,
layout_id_t layout_id
) {
if (!listeners || !layouts) return;
auto slot = layout_slot_get(layouts, layout_id);
if (!slot) return;
zdwm_layout_notify_t notify = {
.workspace = workspace_id,
.id = layout_id,
.name = slot->name,
.symbol = slot->symbol,
.description = slot->description,
};
FOR_EACH_LISTENER(layout_listeners, item->fn(notify, item->user_data));
}
void listeners_notify_binding_mode(
const listeners_t *listeners,
const binding_table_t *binding_table
) {
if (!listeners || !binding_table) return;
auto default_mode = binding_table_get_default_mode(binding_table);
auto current_mode = binding_table_get_current_mode(binding_table);
auto name = binding_table_get_mode_name(binding_table, current_mode);
zdwm_binding_mode_notify_t notify = {
.is_default_mode = default_mode == current_mode,
.id = current_mode,
.name = name,
};
FOR_EACH_LISTENER(binding_mode_listeners, item->fn(notify, item->user_data));
}
static bool listeners_window_snapshot(
const state_t *state,
window_id_t window_id,
zdwm_window_t *out
) {
if (!state || !out || window_id_invalid(window_id)) return false;
auto window = state_window_get(state, window_id);
if (!window) return false;
auto workspace = state_workspace_get(state, window->workspace_id);
if (!workspace) return false;
*out = (zdwm_window_t){
.workspace = window->workspace_id,
.id = window->id,
.title = window_get_showing_title(window),
.focused = workspace->focused_window_id == window->id,
.urgent = window->urgent,
.skip_taskbar = window->skip_taskbar,
};
return true;
}
void listeners_notify_initial_windows(
const listeners_t *listeners,
const state_t *state
) {
if (!listeners || !state) return;
size_t count = state_window_count(state);
zdwm_window_t *items = count ? p_new(zdwm_window_t, count) : nullptr;
for (size_t i = 0; i < count; ++i) {
auto window = state_window_at(state, i);
if (!listeners_window_snapshot(state, window->id, &items[i])) {
p_clear(&items[i], 1);
}
}
FOR_EACH_LISTENER(
initial_window_listeners,
item->fn(items, count, item->user_data)
);
p_delete(&items);
}
void listeners_notify_window_added(
const listeners_t *listeners,
const state_t *state,
window_id_t window_id
) {
if (!listeners || !state) return;
zdwm_window_t window = {0};
if (!listeners_window_snapshot(state, window_id, &window)) return;
FOR_EACH_LISTENER(window_added_listeners, item->fn(&window, item->user_data));
}
void listeners_notify_window_updated(
const listeners_t *listeners,
const state_t *state,
window_id_t window_id
) {
if (!listeners || !state) return;
zdwm_window_t window = {0};
if (!listeners_window_snapshot(state, window_id, &window)) return;
FOR_EACH_LISTENER(
window_updated_listeners,
item->fn(&window, item->user_data)
);
}
void listeners_notify_window_removed(
const listeners_t *listeners,
window_id_t window_id
) {
if (!listeners || window_id_invalid(window_id)) return;
FOR_EACH_LISTENER(
window_removed_listeners,
item->fn(window_id, item->user_data)
);
}
#undef FOR_EACH_LISTENER

92
src/core/listeners.h Normal file
View File

@@ -0,0 +1,92 @@
#pragma once
#include <stddef.h>
#include <zdwm/listeners.h>
#include "common/listeners.h"
#include "interface/types.h"
/**
* @brief 通知当前 output 信息
*
* @param listeners 通知函数集合
* @param output_id 当前 output id
*/
void listeners_notify_current_output(
const listeners_t *listeners,
output_id_t output_id
);
/**
* @brief 通知初始化 workspace 列表
*/
void listeners_notify_initial_workspaces(
const listeners_t *listeners,
const state_t *state
);
/**
* @brief 通知当前激活的 workspace 信息
*/
void listeners_notify_workspace_active(
const listeners_t *listeners,
output_id_t output_id,
workspace_id_t workspace_id
);
/**
* @brief 通知当前布局信息
*
* @param listeners 通知函数集合
* @param layouts 布局注册系统
* @param workspace_id 当前 workspace id
* @param layout_id 当前 workspace 的当前布局 id
*/
void listeners_notify_layout(
const listeners_t *listeners,
const layout_registry_t *layouts,
workspace_id_t workspace_id,
layout_id_t layout_id
);
/**
* @brief 通知当前绑定模式信息
*/
void listeners_notify_binding_mode(
const listeners_t *listeners,
const binding_table_t *binding_table
);
/**
* @brief 通知初始窗口列表
*/
void listeners_notify_initial_windows(
const listeners_t *listeners,
const state_t *state
);
/**
* @brief 通知新增窗口
*/
void listeners_notify_window_added(
const listeners_t *listeners,
const state_t *state,
window_id_t window_id
);
/**
* @brief 通知窗口信息更新
*/
void listeners_notify_window_updated(
const listeners_t *listeners,
const state_t *state,
window_id_t window_id
);
/**
* @brief 通知移除窗口
*/
void listeners_notify_window_removed(
const listeners_t *listeners,
window_id_t window_id
);

View File

@@ -4,6 +4,8 @@
#include "base/array.h"
#include "base/memory.h"
#include "interface/effect.h"
#include "interface/types.h"
static void free_memory_hold_by_effects(effect_t *effects, size_t count) {
for (size_t i = 0; i < count; ++i) {
@@ -19,6 +21,8 @@ static void free_memory_hold_by_effects(effect_t *effects, size_t count) {
case ZDWM_EFFECT_BIND_KEY:
p_delete(&effect->as.bind_key.keys);
break;
case ZDWM_EFFECT_GRAB_BUTTON:
p_delete(&effect->as.grab_button.buttons);
default:
}
}
@@ -35,11 +39,167 @@ void plan_reset(plan_t *plan) {
void plan_cleanup(plan_t *plan) {
free_memory_hold_by_effects(plan->effects, plan->count);
p_delete(&plan->effects);
plan->count = 0;
plan->count = 0;
plan->capacity = 0;
plan->need_relayout = false;
plan->quit = false;
plan->will_restart = false;
}
void plan_push_effect(plan_t *plan, const effect_t *effect) {
effect_t *eft = array_push(plan->effects, plan->count, plan->capacity);
*eft = *effect;
*eft = *effect;
}
void plan_push_map_effect(plan_t *plan, window_id_t window_id) {
if (window_id_invalid(window_id)) return;
effect_t effect = {
.type = ZDWM_EFFECT_MAP_WINDOW,
.as.map.window = window_id,
};
plan_push_effect(plan, &effect);
}
void plan_push_unmap_effect(plan_t *plan, window_id_t window_id) {
if (window_id_invalid(window_id)) return;
effect_t effect = {
.type = ZDWM_EFFECT_UNMAP_WINDOW,
.as.unmap.window = window_id,
};
plan_push_effect(plan, &effect);
}
void plan_push_focus_effect(plan_t *plan, window_id_t window_id) {
if (window_id_invalid(window_id)) return;
effect_t effect = {
.type = ZDWM_EFFECT_FOCUS_WINDOW,
.as.focus.window = window_id,
};
plan_push_effect(plan, &effect);
}
void plan_push_kill_effect(plan_t *plan, window_id_t window_id) {
if (window_id_invalid(window_id)) return;
effect_t effect = {
.type = ZDWM_EFFECT_KILL_WINDOW,
.as.kill.window = window_id,
};
plan_push_effect(plan, &effect);
}
void plan_push_withdraw_effect(plan_t *plan, window_id_t window_id) {
if (window_id_invalid(window_id)) return;
effect_t effect = {
.type = ZDWM_EFFECT_WITHDRAW_WINDOW,
.as.withdraw.window = window_id,
};
plan_push_effect(plan, &effect);
}
void plan_push_move_effect(plan_t *plan, window_id_t window_id) {
if (window_id_invalid(window_id)) return;
effect_t effect = {
.type = ZDWM_EFFECT_START_MOVE_WINDOW,
.as.move.window = window_id,
};
plan_push_effect(plan, &effect);
}
void plan_push_resize_effect(plan_t *plan, window_id_t window_id) {
if (window_id_invalid(window_id)) return;
effect_t effect = {
.type = ZDWM_EFFECT_START_RESIZE_WINDOW,
.as.resize.window = window_id,
};
plan_push_effect(plan, &effect);
}
void plan_push_configure_notify_effect(plan_t *plan, window_id_t window_id) {
if (window_id_invalid(window_id)) return;
effect_t effect = {
.type = ZDWM_EFFECT_CONFIGURE_NOTIFY,
.as.configure_notify.window = window_id,
};
plan_push_effect(plan, &effect);
}
void plan_push_fullscreen_effect(
plan_t *plan,
window_id_t window_id,
bool value
) {
if (window_id_invalid(window_id)) return;
effect_t effect = {
.type = ZDWM_EFFECT_FULLSCREEN_WINDOW,
.as.fullscreen = {
.window = window_id,
.value = value,
},
};
plan_push_effect(plan, &effect);
}
void plan_push_maximize_effect(
plan_t *plan,
window_id_t window_id,
bool value
) {
if (window_id_invalid(window_id)) return;
effect_t effect = {
.type = ZDWM_EFFECT_MAXIMIZE_WINDOW,
.as.maximize = {
.window = window_id,
.value = value,
},
};
plan_push_effect(plan, &effect);
}
void plan_push_minimize_effect(
plan_t *plan,
window_id_t window_id,
bool value
) {
if (window_id_invalid(window_id)) return;
effect_t effect = {
.type = ZDWM_EFFECT_MINIMIZE_WINDOW,
.as.minimize = {
.window = window_id,
.value = value,
},
};
plan_push_effect(plan, &effect);
}
void plan_push_change_border_color_effect(
plan_t *plan,
window_id_t window_id,
const color_t *color
) {
if (window_id_invalid(window_id) || color == nullptr) return;
effect_t effect = {
.type = ZDWM_EFFECT_CHANGE_BORDER_COLOR,
.as.change_border_color = {
.window = window_id,
.color = color,
},
};
plan_push_effect(plan, &effect);
}
void plan_push_ungrab_pointer(plan_t *plan) {
effect_t effect = {.type = ZDWM_EFFECT_UNGRAB_POINTER};
plan_push_effect(plan, &effect);
}

View File

@@ -1,83 +1,42 @@
#pragma once
#include <stddef.h>
#include <stdint.h>
#include "core/types.h"
typedef enum effect_type_t {
ZDWM_EFFECT_MAP_WINDOW = 1,
ZDWM_EFFECT_UNMAP_WINDOW,
ZDWM_EFFECT_FOCUS_WINDOW,
ZDWM_EFFECT_KILL_WINDOW,
ZDWM_EFFECT_MOVE_WINDOW,
ZDWM_EFFECT_RESIZE_WINDOW,
ZDWM_EFFECT_CHANGE_BORDER_COLOR,
ZDWM_EFFECT_CHANGE_BORDER_WIDTH,
ZDWM_EFFECT_CHANGE_WINDOW_LIST,
ZDWM_EFFECT_RESTACK_WINDOWS,
ZDWM_EFFECT_BIND_KEY,
} effect_type_t;
typedef struct effect_only_window_id_t {
window_id_t window;
} effect_only_window_id_t;
typedef struct effect_move_window_t {
window_id_t window;
point_t left_top_point;
} effect_move_window_t;
typedef struct effect_resize_window_t {
window_id_t window;
int32_t width;
int32_t height;
} effect_resize_window_t;
typedef struct effect_change_border_color_t {
window_id_t window;
const color_t *color;
} effect_change_border_color_t;
typedef struct effect_change_border_width_t {
window_id_t window;
uint32_t border_width;
} effect_change_border_width_t;
typedef struct effect_window_list_t {
const window_id_t *windows;
size_t count;
} effect_window_list_t;
typedef struct effect_bind_key_t {
const key_bind_t *keys;
size_t count;
} effect_bind_key_t;
typedef struct effect_t {
effect_type_t type;
union {
effect_only_window_id_t map;
effect_only_window_id_t unmap;
effect_only_window_id_t focus;
effect_only_window_id_t kill;
effect_move_window_t move;
effect_resize_window_t resize;
effect_change_border_color_t change_border_color;
effect_change_border_width_t change_border_width;
effect_window_list_t change_window_list;
effect_window_list_t restack_windows;
effect_bind_key_t bind_key;
} as;
} effect_t;
#include "base/color.h"
#include "interface/effect.h"
#include "interface/types.h"
typedef struct plan_t {
effect_t *effects;
size_t count;
size_t capacity;
bool need_relayout;
bool quit;
bool will_restart;
} plan_t;
void plan_reset(plan_t *plan);
void plan_cleanup(plan_t *plan);
void plan_push_effect(plan_t *plan, const effect_t *effect);
void plan_push_map_effect(plan_t *plan, window_id_t window_id);
void plan_push_unmap_effect(plan_t *plan, window_id_t window_id);
void plan_push_focus_effect(plan_t *plan, window_id_t window_id);
void plan_push_kill_effect(plan_t *plan, window_id_t window_id);
void plan_push_withdraw_effect(plan_t *plan, window_id_t window_id);
void plan_push_move_effect(plan_t *plan, window_id_t window_id);
void plan_push_resize_effect(plan_t *plan, window_id_t window_id);
void plan_push_configure_notify_effect(plan_t *plan, window_id_t window_id);
void plan_push_fullscreen_effect(
plan_t *plan,
window_id_t window_id,
bool value
);
void plan_push_maximize_effect(plan_t *plan, window_id_t window_id, bool value);
void plan_push_minimize_effect(plan_t *plan, window_id_t window_id, bool value);
void plan_push_change_border_color_effect(
plan_t *plan,
window_id_t window_id,
const color_t *color
);
void plan_push_ungrab_pointer(plan_t *plan);

Some files were not shown because too many files have changed in this diff Show More