Compare commits
26 Commits
079f41ce8a
...
arch_refac
| Author | SHA1 | Date | |
|---|---|---|---|
|
b8ca575458
|
|||
|
6b2f0e3218
|
|||
|
d7d16f1ac8
|
|||
|
88129293a0
|
|||
|
7b7b6d807f
|
|||
|
80b091a806
|
|||
|
89f11d1e24
|
|||
|
ceb40b434d
|
|||
|
272139f34f
|
|||
|
583b1fa382
|
|||
|
612c2367ca
|
|||
|
88e589cc9e
|
|||
|
c609d15d77
|
|||
|
d47ca9cf1a
|
|||
|
e39eb245b6
|
|||
|
3c5cfd3ead
|
|||
|
e7a0945a93
|
|||
|
47bd59b806
|
|||
|
31cd35bf84
|
|||
|
3d258bdc8b
|
|||
|
9f8681d467
|
|||
|
d0120566bf
|
|||
|
6a227b0497
|
|||
|
6b75cda128
|
|||
|
27aab6f0e9
|
|||
|
eaad539a0b
|
@@ -29,12 +29,14 @@ add_executable(${APP_NAME}
|
||||
${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
|
||||
|
||||
|
||||
9
Makefile
9
Makefile
@@ -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)
|
||||
|
||||
@@ -42,7 +42,7 @@ X11 系统托盘协议只要求:tray 创建一个 window 成为 =_NET_SYSTEM_T
|
||||
|
||||
* 后端接口设计
|
||||
|
||||
tray 契约独立成头 =src/bar/tray.h= —— *不放 core*,因为 tray 与 core 无关(core 不感知 tray)。由 bar 定义、后端实现(消费方定义接口、提供方实现的依赖倒置;契约是纯抽象头)。它只依赖 =core/types.h=(=window_id_t=),不含 =backend_t=,所以 bar include 它而不碰 =core/backend.h=,保持"bar 不知道后端内部"。=core/backend.h= 反过来 include =bar/tray.h=,让 =backend_bar_window_t= 内嵌 =tray_t=。
|
||||
tray 契约独立成头 =interface/tray.h= —— 落 interface 层:不放 core(tray 与 core 无关),也不放 bar(backend 也要实现它)。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);
|
||||
@@ -233,20 +233,20 @@ bar 重绘是 timerfd(fps)驱动,非事件驱动。tray icon 是真实窗
|
||||
|
||||
* 关键文件清单
|
||||
|
||||
| 文件 | 改动 |
|
||||
|------------------------------+----------------------------------------------------------------------------------------------------------|
|
||||
| =src/bar/tray.h= | **新增** tray 契约:=tray_t= / =tray_api_t= / =tray_icons_change_cb_t=(bar 定义、后端实现;不进 core) |
|
||||
| =src/core/backend.h= | include =bar/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= |
|
||||
| 文件 | 改动 |
|
||||
|------------------------------+------------------------------------------------------------------------------------------------------------------------|
|
||||
| =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= |
|
||||
|
||||
* 复用现有
|
||||
|
||||
|
||||
91
docs/tray-implementation-plan.org
Normal file
91
docs/tray-implementation-plan.org
Normal 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 广播 + 基础 XEMBED(reparent + =_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 行为不变。
|
||||
|
||||
** 步骤 2:bar 框架扩展(零影响现有 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)+ 基础 XEMBED(reparent 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= 释放 tray(selection、容器、icon 列表)。
|
||||
- *=CMakeLists.txt=*:加 =src/backend/x11/tray.c=。
|
||||
- 验证:编译通过。runtime 仍传 false,行为不变。
|
||||
|
||||
** 步骤 4:bar 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 不画 cairo(text 空,被真实 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)。
|
||||
|
||||
** 步骤 5:runtime/配置串起来(启用)
|
||||
|
||||
- *=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 不显式初始化新钩子 → NULL(C 部分初始化保证);=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. *回归*(每步都做,重点步骤 2):workspaces 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 机制。
|
||||
@@ -39,6 +39,8 @@ typedef struct zdwm_bar_config_t {
|
||||
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;
|
||||
@@ -48,6 +50,11 @@ typedef struct 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);
|
||||
@@ -57,6 +64,13 @@ typedef struct zdwm_bar_cell_api_t {
|
||||
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 {
|
||||
@@ -68,6 +82,13 @@ typedef struct zdwm_bar_click_params_t {
|
||||
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)(
|
||||
@@ -75,6 +96,8 @@ typedef struct zdwm_bar_item_type_t {
|
||||
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;
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
#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"
|
||||
@@ -194,6 +195,8 @@ backend_t *backend_create(const char *display_name) {
|
||||
void backend_destroy(backend_t *backend) {
|
||||
if (!backend) return;
|
||||
|
||||
tray_cleanup(backend);
|
||||
|
||||
p_delete(&backend->config_list.cfgs);
|
||||
p_clear(&backend->config_list, 1);
|
||||
|
||||
@@ -555,6 +558,7 @@ static void backend_merge_effects(
|
||||
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);
|
||||
@@ -609,6 +613,9 @@ static void backend_merge_effects(
|
||||
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
|
||||
@@ -640,6 +647,46 @@ static void backend_merge_effects(
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -669,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);
|
||||
}
|
||||
@@ -684,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);
|
||||
@@ -774,7 +828,8 @@ void backend_scan_result_destroy(backend_scan_result_t *result) {
|
||||
backend_bar_window_t backend_create_bar_window(
|
||||
backend_t *backend,
|
||||
rect_t geometry,
|
||||
uint32_t bg_pixel
|
||||
uint32_t bg_pixel,
|
||||
bool enable_tray
|
||||
) {
|
||||
auto conn = backend->conn;
|
||||
auto root = backend->screen->root;
|
||||
@@ -844,7 +899,24 @@ backend_bar_window_t backend_create_bar_window(
|
||||
fatal("cannot create cairo context: %s", cairo_status_to_string(statue));
|
||||
}
|
||||
|
||||
return (backend_bar_window_t){.window_id = window_id, .cr = cr};
|
||||
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); }
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <xcb/xproto.h>
|
||||
|
||||
#include "backend/x11/common.h"
|
||||
#include "backend/x11/tray.h"
|
||||
#include "backend/x11/window.h"
|
||||
#include "base/memory.h"
|
||||
#include "interface/backend.h"
|
||||
@@ -189,6 +190,8 @@ static bool handle_map_request(
|
||||
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,
|
||||
@@ -202,6 +205,8 @@ static bool handle_unmap_notify(
|
||||
event_t *event,
|
||||
const xcb_unmap_notify_event_t *xcb_event
|
||||
) {
|
||||
if (tray_handle_unmap_notify(backend, xcb_event)) return false;
|
||||
|
||||
event->type = ZDWM_EVENT_WINDOW_REMOVE;
|
||||
event->as.window_remove.window = xcb_event->window;
|
||||
if (XCB_EVENT_SENT(xcb_event)) {
|
||||
@@ -218,6 +223,8 @@ static bool handle_destroy_notify(
|
||||
event_t *event,
|
||||
const xcb_destroy_notify_event_t *xcb_event
|
||||
) {
|
||||
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;
|
||||
@@ -230,6 +237,8 @@ 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;
|
||||
auto data = &event->as.configure_request;
|
||||
data->window = xcb_event->window;
|
||||
@@ -341,6 +350,8 @@ static bool handle_property_notify(
|
||||
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 ||
|
||||
@@ -419,6 +430,8 @@ static bool handle_client_message(
|
||||
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) {
|
||||
|
||||
@@ -46,6 +46,10 @@
|
||||
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) \
|
||||
\
|
||||
@@ -86,6 +90,8 @@ typedef enum cursor_t {
|
||||
ZDWM_CURSOR_COUNT,
|
||||
} cursor_t;
|
||||
|
||||
typedef struct tray_host_t tray_host_t;
|
||||
|
||||
typedef struct backend_t {
|
||||
xcb_key_symbols_t *key_symbols;
|
||||
xcb_connection_t *conn;
|
||||
@@ -109,6 +115,9 @@ typedef struct backend_t {
|
||||
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(
|
||||
|
||||
606
src/backend/x11/tray.c
Normal file
606
src/backend/x11/tray.c
Normal 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, ¶ms);
|
||||
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, ¶ms);
|
||||
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
45
src/backend/x11/tray.h
Normal 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
|
||||
);
|
||||
108
src/bar/bar.c
108
src/bar/bar.c
@@ -12,6 +12,7 @@
|
||||
#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"
|
||||
@@ -130,9 +131,14 @@ static void bar_output_add_windows(
|
||||
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];
|
||||
@@ -141,6 +147,10 @@ void bar_init(bar_t *bar, listeners_t *listeners) {
|
||||
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);
|
||||
@@ -212,14 +222,16 @@ static void bar_output_layout(bar_output_t *bar_output, text_context_t *ctx) {
|
||||
item->region.start = start;
|
||||
for (size_t j = 0; j < item->count; ++j) {
|
||||
auto cell = &item->cells[j];
|
||||
auto fixed_width = cell->fixed_width;
|
||||
|
||||
int32_t width = 0;
|
||||
text_context_get_text_size(ctx, cell->text, &width, nullptr);
|
||||
|
||||
bar_x_region_t region = {
|
||||
.start = start,
|
||||
.end = start + width + item->cell_padding * 2,
|
||||
};
|
||||
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;
|
||||
@@ -240,14 +252,16 @@ static void bar_output_layout(bar_output_t *bar_output, text_context_t *ctx) {
|
||||
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;
|
||||
|
||||
int32_t width = 0;
|
||||
text_context_get_text_size(ctx, cell->text, &width, nullptr);
|
||||
|
||||
bar_x_region_t region = {
|
||||
.start = end - width - item->cell_padding * 2,
|
||||
.end = end,
|
||||
};
|
||||
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;
|
||||
@@ -263,12 +277,30 @@ static void bar_output_layout(bar_output_t *bar_output, text_context_t *ctx) {
|
||||
if (center->count == 0) return;
|
||||
center->region = (bar_x_region_t){.start = start, .end = end};
|
||||
|
||||
auto width = (end - start) / (int32_t)center->count;
|
||||
auto center_width = end - start;
|
||||
auto center_count = center->count;
|
||||
for (size_t i = 0; i < center->count; ++i) {
|
||||
bar_x_region_t region = {
|
||||
.start = start,
|
||||
.end = start + width + center->cell_padding * 2,
|
||||
};
|
||||
auto cell = ¢er->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 = ¢er->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);
|
||||
}
|
||||
@@ -386,6 +418,42 @@ bar_output_draw(bar_output_t *bar_output, text_context_t *ctx, color_t *bg) {
|
||||
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(¶ms);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
@@ -396,7 +464,9 @@ bool bar_draw(bar_t *bar) {
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "bar/types.h"
|
||||
#include "base/color.h"
|
||||
#include "common/listeners.h"
|
||||
#include "interface/tray.h"
|
||||
|
||||
typedef struct bar_output_t {
|
||||
cairo_t *cr;
|
||||
@@ -37,6 +38,7 @@ typedef struct bar_t {
|
||||
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);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <assert.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
#include <zdwm/types.h>
|
||||
@@ -119,15 +120,37 @@ bar_cell_set_indicator(zdwm_bar_item_t *item, size_t index, bool show) {
|
||||
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 */
|
||||
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,
|
||||
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 */
|
||||
|
||||
|
||||
@@ -12,4 +12,4 @@ void bar_cell_set_region(
|
||||
bar_x_region_t region
|
||||
);
|
||||
|
||||
extern zdwm_bar_cell_api_t bar_cell_api;
|
||||
extern const zdwm_bar_cell_api_t bar_cell_api;
|
||||
|
||||
78
src/bar/tray.c
Normal file
78
src/bar/tray.c
Normal 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
5
src/bar/tray.h
Normal file
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include <zdwm/bar.h>
|
||||
|
||||
extern zdwm_bar_item_type_t bar_tray;
|
||||
@@ -7,10 +7,7 @@
|
||||
|
||||
#include "base/color.h"
|
||||
|
||||
typedef struct bar_x_region_t {
|
||||
int32_t start;
|
||||
int32_t end;
|
||||
} bar_x_region_t;
|
||||
typedef struct zdwm_bar_x_region_t bar_x_region_t;
|
||||
|
||||
typedef struct bar_cell_t {
|
||||
zdwm_icon_t icon;
|
||||
@@ -20,6 +17,7 @@ typedef struct bar_cell_t {
|
||||
color_t fg;
|
||||
color_t bg;
|
||||
bar_x_region_t region;
|
||||
int32_t fixed_width;
|
||||
bool show_indicator;
|
||||
bool dirty;
|
||||
} bar_cell_t;
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
#include "base/time.h"
|
||||
|
||||
#include <bits/time.h>
|
||||
#include <bits/types/struct_itimerspec.h>
|
||||
#include <errno.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
@@ -122,7 +122,7 @@ bool config_defaults_build(
|
||||
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_FLOATING});
|
||||
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"),
|
||||
|
||||
@@ -11,6 +11,7 @@ typedef enum command_type_t {
|
||||
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,
|
||||
@@ -102,6 +103,7 @@ typedef struct command_t {
|
||||
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;
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "core/binding.h"
|
||||
#include "core/layout.h"
|
||||
#include "core/state.h"
|
||||
#include "core/window.h"
|
||||
|
||||
#define FOR_EACH_LISTENER(FIELD, BODY) \
|
||||
do { \
|
||||
@@ -117,7 +118,7 @@ static bool listeners_window_snapshot(
|
||||
*out = (zdwm_window_t){
|
||||
.workspace = window->workspace_id,
|
||||
.id = window->id,
|
||||
.title = window->title,
|
||||
.title = window_get_showing_title(window),
|
||||
.focused = workspace->focused_window_id == window->id,
|
||||
.urgent = window->urgent,
|
||||
.skip_taskbar = window->skip_taskbar,
|
||||
|
||||
@@ -121,6 +121,16 @@ void plan_push_resize_effect(plan_t *plan, window_id_t 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,
|
||||
|
||||
@@ -26,6 +26,7 @@ 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,
|
||||
|
||||
@@ -771,6 +771,15 @@ static void route_window_state_request(
|
||||
command_buffer_push(out, &change_window_state_cmd);
|
||||
}
|
||||
|
||||
static void
|
||||
add_notify_configure_command(command_buffer_t *out, window_id_t window) {
|
||||
command_t notify_cmd = {
|
||||
.type = ZDWM_COMMAND_NOTIFY_CONFIGURE,
|
||||
.as.notify_configure = {.window = window},
|
||||
};
|
||||
command_buffer_push(out, ¬ify_cmd);
|
||||
}
|
||||
|
||||
static void route_configure_request(
|
||||
state_t *state,
|
||||
const configure_data_t *data,
|
||||
@@ -788,6 +797,7 @@ static void route_configure_request(
|
||||
}
|
||||
|
||||
if (!state_workspace_show(state, window->workspace_id)) return;
|
||||
add_notify_configure_command(out, data->window);
|
||||
if (window_need_layout(window)) return;
|
||||
auto workspace = state_workspace_get(state, window->workspace_id);
|
||||
if (layout_get(layouts, workspace->layout_id)) return;
|
||||
@@ -938,10 +948,13 @@ static void manage_window(
|
||||
auto window = state_window_add(state, &command->info);
|
||||
auto window_id = window->id;
|
||||
auto workspace_id = command->workspace;
|
||||
auto workspace = state_workspace_get(state, workspace_id);
|
||||
auto old_focused_window = workspace->focused_window_id;
|
||||
state_window_set_workspace(state, window_id, workspace_id);
|
||||
set_foucs_window(ctx, workspace_id, window_id, plan);
|
||||
plan_push_grab_button_effect(plan, window_id);
|
||||
push_window_list_effect(state, plan);
|
||||
listeners_notify_window_updated(ctx->listeners, state, old_focused_window);
|
||||
listeners_notify_window_added(ctx->listeners, state, window_id);
|
||||
|
||||
if (window_should_fix_size(window)) {
|
||||
@@ -950,7 +963,6 @@ static void manage_window(
|
||||
state_window_set_floating(state, window_id, command->floating);
|
||||
}
|
||||
|
||||
auto workspace = state_workspace_get(state, workspace_id);
|
||||
auto need_layout = window_need_layout(window);
|
||||
auto layout_func = layout_get(ctx->layouts, workspace->layout_id);
|
||||
|
||||
@@ -1690,6 +1702,9 @@ void policy_apply_command(
|
||||
case ZDWM_COMMAND_CONFIGURE_WINDOW:
|
||||
configure_window(state, &cmd->as.configure, plan);
|
||||
break;
|
||||
case ZDWM_COMMAND_NOTIFY_CONFIGURE:
|
||||
plan_push_configure_notify_effect(plan, cmd->as.notify_configure.window);
|
||||
break;
|
||||
case ZDWM_COMMAND_CHANGE_WINDOW_STATE:
|
||||
change_window_state(ctx, &cmd->as.state_change, plan);
|
||||
break;
|
||||
|
||||
@@ -145,3 +145,11 @@ bool window_can_resize(const window_t *window) {
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const char *window_get_showing_title(const window_t *window) {
|
||||
if (window->title) return window->title;
|
||||
if (window->instance_name) return window->instance_name;
|
||||
if (window->class_name) return window->class_name;
|
||||
if (window->app_id) return window->app_id;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -67,3 +67,4 @@ bool window_need_resize(const window_t *window, int32_t width, int32_t height);
|
||||
bool window_should_has_border(const window_t *window);
|
||||
bool window_should_fix_size(const window_t *window);
|
||||
bool window_can_resize(const window_t *window);
|
||||
const char *window_get_showing_title(const window_t *window);
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
#include "interface/effect.h"
|
||||
#include "interface/event.h"
|
||||
#include "interface/tray.h"
|
||||
#include "interface/types.h"
|
||||
|
||||
typedef struct backend_t backend_t;
|
||||
@@ -69,11 +70,13 @@ void backend_scan_result_destroy(backend_scan_result_t *result);
|
||||
typedef struct backend_bar_window_t {
|
||||
window_id_t window_id;
|
||||
cairo_t *cr;
|
||||
tray_t tray;
|
||||
} backend_bar_window_t;
|
||||
|
||||
backend_bar_window_t backend_create_bar_window(
|
||||
backend_t *backend,
|
||||
rect_t geometry,
|
||||
uint32_t bg_pixel
|
||||
uint32_t bg_pixel,
|
||||
bool enable_tray
|
||||
);
|
||||
void backend_flush(backend_t *backend);
|
||||
|
||||
@@ -17,6 +17,7 @@ typedef enum effect_type_t {
|
||||
ZDWM_EFFECT_MAXIMIZE_WINDOW,
|
||||
ZDWM_EFFECT_FULLSCREEN_WINDOW,
|
||||
ZDWM_EFFECT_CONFIGURE_WINDOW,
|
||||
ZDWM_EFFECT_CONFIGURE_NOTIFY,
|
||||
ZDWM_EFFECT_CHANGE_BORDER_COLOR,
|
||||
ZDWM_EFFECT_CHANGE_WINDOW_LIST,
|
||||
ZDWM_EFFECT_RESTACK_WINDOWS,
|
||||
@@ -86,6 +87,7 @@ typedef struct effect_t {
|
||||
effect_bool_window_t maximize;
|
||||
effect_bool_window_t fullscreen;
|
||||
configure_data_t configure;
|
||||
only_window_data_t configure_notify;
|
||||
effect_change_border_color_t change_border_color;
|
||||
effect_window_list_t change_window_list;
|
||||
effect_window_list_t restack_windows;
|
||||
|
||||
35
src/interface/tray.h
Normal file
35
src/interface/tray.h
Normal file
@@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "interface/types.h"
|
||||
|
||||
typedef void (*tray_icons_change_cb_t)(void *user_data);
|
||||
|
||||
typedef struct tray_api_t {
|
||||
/**
|
||||
* @brief 获取 tray 当前挂载的 bar window id
|
||||
*
|
||||
* @detail 如果返回的 window_id 是 ZDWM_WINDOW_ID_INVALID 则表示 tray
|
||||
* 功能未开启,无 tray 需要挂载
|
||||
*/
|
||||
window_id_t (*host_window)(void *handle);
|
||||
/* 获取当前 icon 数量 */
|
||||
size_t (*icon_count)(void *handle);
|
||||
/* 获取每个 icon 的边长, icon 长宽一样,都等于 bar_height */
|
||||
int32_t (*icon_size)(void *handle);
|
||||
/* 将 tray 的 icon 容器窗口起点放到 bar 中 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;
|
||||
} tray_t;
|
||||
@@ -1,6 +1,5 @@
|
||||
#include "runtime/runtime.h"
|
||||
|
||||
#include <bits/types/sigset_t.h>
|
||||
#include <dlfcn.h>
|
||||
#include <signal.h>
|
||||
#include <stddef.h>
|
||||
@@ -121,7 +120,10 @@ static void runtime_init_bar(runtime_t *runtime) {
|
||||
.height = bar_height,
|
||||
};
|
||||
auto color = bar->palette.bg.argb;
|
||||
auto bar_window = backend_create_bar_window(backend, bar_rect, color);
|
||||
bool enable_tray = (int32_t)i == bar->config.tray_output_index;
|
||||
auto bar_window =
|
||||
backend_create_bar_window(backend, bar_rect, color, enable_tray);
|
||||
bar->tray = bar_window.tray;
|
||||
auto bar_output = &bar->bars[i];
|
||||
bar_output->cr = bar_window.cr;
|
||||
bar_output->window_id = bar_window.window_id;
|
||||
|
||||
Reference in New Issue
Block a user