Compare commits
94 Commits
74319e97f1
...
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
|
|||
|
079f41ce8a
|
|||
|
e1113c0052
|
|||
|
bf3de7fb92
|
|||
|
8f671bd755
|
|||
|
195aeb5166
|
|||
|
af80181897
|
|||
|
42bddf5ac7
|
|||
|
29e0f1e2dc
|
|||
|
74b30a5d4b
|
|||
|
40c2c328cd
|
|||
|
71f050c2f3
|
|||
|
f355b88b82
|
|||
|
f3b1466dac
|
|||
|
b71b66921e
|
|||
|
9a1e015fc1
|
|||
|
4e7ab45864
|
|||
|
b8cd40df9f
|
|||
|
7a47f4d44d
|
|||
|
090266baa5
|
|||
|
9f9adf7e2e
|
|||
|
00df685184
|
|||
|
6d06b61134
|
|||
|
30dfb19a7b
|
|||
|
195733e008
|
|||
|
6f1421f5b5
|
|||
|
9bbdb26f06
|
|||
|
6e3e7ee937
|
|||
|
9d35d17493
|
|||
|
a4bd0b53de
|
|||
|
3be83aefdf
|
|||
|
dee4073996
|
|||
|
e8b59e914c
|
|||
|
ac65180203
|
|||
|
30e9751119
|
|||
|
41f82d7039
|
|||
|
d0127f66e4
|
|||
|
4cef809f3c
|
|||
|
d97cfba8dd
|
|||
|
539563c53f
|
|||
|
dbbbbce379
|
|||
|
4e713682e4
|
|||
|
4535e171a0
|
|||
|
fa2f058032
|
|||
|
8c145fb1b4
|
|||
|
62f6bab611
|
|||
|
3a8c392d90
|
|||
|
87535835a8
|
|||
|
fed04cbffc
|
|||
|
b510b7b473
|
|||
|
c30c7d0698
|
|||
|
adf839bf21
|
|||
|
be37b17c2e
|
|||
|
f82d0b7a87
|
|||
|
e9366b64e2
|
|||
|
f540c07aad
|
|||
|
ebc277201a
|
|||
|
ff40b3caa0
|
|||
|
d07d2f64e1
|
|||
|
0d43c79ad6
|
|||
|
83ed661ba6
|
|||
|
59520777c3
|
|||
|
09d5e4b4d0
|
|||
|
ff3b51fd1d
|
|||
|
989a1ce6e8
|
|||
|
13fc41f141
|
|||
|
ca9e543cc0
|
|||
|
33b0f2f54f
|
|||
|
edde023f5a
|
@@ -6,12 +6,6 @@ IndentWidth: 2
|
||||
ConstructorInitializerIndentWidth: 2
|
||||
ContinuationIndentWidth: 2
|
||||
|
||||
AlignConsecutiveAssignments:
|
||||
Enabled: true
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: false
|
||||
AlignCompound: true
|
||||
PadOperators: true
|
||||
AlignConsecutiveBitFields:
|
||||
Enabled: true
|
||||
|
||||
|
||||
@@ -23,33 +23,45 @@ project(${APP_NAME}
|
||||
LANGUAGES C
|
||||
)
|
||||
|
||||
option(BUILD_TESTING "Build tests" OFF)
|
||||
|
||||
add_executable(${APP_NAME}
|
||||
${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}/base/window_list.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}/core/action.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/event.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/runtime.c
|
||||
${SOURCE_DIR}/core/state.c
|
||||
${SOURCE_DIR}/core/window.c
|
||||
|
||||
@@ -57,6 +69,8 @@ add_executable(${APP_NAME}
|
||||
${SOURCE_DIR}/layouts/fullscreen.c
|
||||
${SOURCE_DIR}/layouts/maximize.c
|
||||
|
||||
${SOURCE_DIR}/runtime/runtime.c
|
||||
|
||||
${SOURCE_DIR}/main.c
|
||||
)
|
||||
|
||||
@@ -69,12 +83,18 @@ find_package(PkgConfig REQUIRED)
|
||||
pkg_check_modules(deps REQUIRED
|
||||
xcb
|
||||
xcb-aux
|
||||
xcb-cursor
|
||||
xcb-icccm
|
||||
xcb-keysyms
|
||||
xcb-randr
|
||||
xcb-xfixes
|
||||
xcb-xinerama
|
||||
xkbcommon
|
||||
|
||||
cairo
|
||||
cairo-xcb
|
||||
pango
|
||||
pangocairo
|
||||
)
|
||||
|
||||
target_include_directories(${APP_NAME} SYSTEM
|
||||
@@ -90,14 +110,3 @@ target_link_libraries(${APP_NAME}
|
||||
PRIVATE m
|
||||
PRIVATE ${deps_LIBRARIES}
|
||||
)
|
||||
|
||||
configure_file("${SOURCE_DIR}/app.h.in"
|
||||
"${BUILD_DIR}/app.h"
|
||||
ESCAPE_QUOTES
|
||||
@ONLY
|
||||
)
|
||||
|
||||
if(BUILD_TESTING)
|
||||
enable_testing()
|
||||
add_subdirectory(tests)
|
||||
endif()
|
||||
|
||||
21
Makefile
21
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)
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
@@ -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
136
docs/module-layering.org
Normal 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
|
||||
|
||||
消费方:core(policy/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:类型 + 操作)
|
||||
|
||||
消费方:只 core(policy 产生、收集 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(类型 + 维护)+ core(notify)
|
||||
|
||||
=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
256
docs/tray-design.org
Normal 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 用 StatusNotifierItem(SNI)over D-Bus,icon 是像素数据而非真实窗口,由前端 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 移动/resize(X 子窗口语义),不占额外屏幕空间。
|
||||
|
||||
* 后端接口设计
|
||||
|
||||
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);
|
||||
|
||||
typedef struct tray_api_t {
|
||||
/* 返回 tray 当前挂载的 bar window_id;invalid 表示无 tray 挂载 */
|
||||
window_id_t (*host_window)(void *handle);
|
||||
/* 当前 icon 数量 */
|
||||
size_t (*icon_count)(void *handle);
|
||||
/* icon 边长(= bar height),bar 据此算 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; /* 指向 backend(bar 不知其类型) */
|
||||
} tray_t;
|
||||
|
||||
typedef struct backend_bar_window_t {
|
||||
window_id_t window_id;
|
||||
cairo_t *cr;
|
||||
tray_t tray;
|
||||
} backend_bar_window_t;
|
||||
|
||||
/* 扩展:加 enable_tray;icon_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_id,bar 据此决定哪个 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 的 region(layout 后的坐标) */
|
||||
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, /* 返回 NONE:icon 真实子窗口自收点击 */
|
||||
.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 重算 region(cell 数变了)
|
||||
-> tray 的 after_layout -> backend place(handle, region.start)
|
||||
-> 后端 move 容器 + configure 每个 icon 到 (region.start + i*icon_size, 0)
|
||||
#+end_example
|
||||
|
||||
bar 重绘是 timerfd(fps)驱动,非事件驱动。tray icon 是真实窗口,X server 自管绘制,不依赖 cairo 重绘。
|
||||
|
||||
* 实现范围(第一版)
|
||||
|
||||
第一版做*基础可用*:
|
||||
|
||||
- =_NET_SYSTEM_TRAY_S0= selection owner + MANAGER 广播
|
||||
- 基础 XEMBED(reparent 容器、=_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 窗口已是 ARGB,tray 容器继承透明。
|
||||
- =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 一致)。
|
||||
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 机制。
|
||||
@@ -8,50 +8,85 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef union zdwm_action_arg_t {
|
||||
bool b;
|
||||
int32_t i;
|
||||
uint32_t ui;
|
||||
const char *str;
|
||||
const void *ptr;
|
||||
} 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_runtime_t zdwm_runtime_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 struct zdwm_action_api_t {
|
||||
void (*spawn)(const char *command);
|
||||
void (*quit)(zdwm_runtime_t *runtime, bool restart);
|
||||
void (*raise_or_run)(
|
||||
zdwm_runtime_t *runtime,
|
||||
const char *class_name,
|
||||
const char *command
|
||||
);
|
||||
void (*toggle_fullscreen)(zdwm_runtime_t *runtime);
|
||||
void (*toggle_maximize)(zdwm_runtime_t *runtime);
|
||||
void (*toggle_floating)(zdwm_runtime_t *runtime);
|
||||
void (*toggle_sticky)(zdwm_runtime_t *runtime);
|
||||
void (*switch_workspace)(
|
||||
zdwm_runtime_t *runtime,
|
||||
zdwm_workspace_id_t workspace_id
|
||||
);
|
||||
void (*switch_workspace_in_current_output)(
|
||||
zdwm_runtime_t *runtime,
|
||||
zdwm_workspace_id_t workspace_id
|
||||
);
|
||||
void (*switch_workspace_by_index_in_current_output)(
|
||||
zdwm_runtime_t *runtime,
|
||||
uint32_t index
|
||||
);
|
||||
void (*cycle_layout)(zdwm_runtime_t *runtime, int32_t delta);
|
||||
void (*cycle_current_output)(zdwm_runtime_t *runtime, int32_t delta);
|
||||
void (*focus_window)(zdwm_runtime_t *runtime, int32_t delta);
|
||||
} zdwm_action_api_t;
|
||||
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,
|
||||
|
||||
typedef void zdwm_action_fn(
|
||||
zdwm_runtime_t *runtime,
|
||||
const zdwm_action_api_t *ctx,
|
||||
const zdwm_action_arg_t *arg
|
||||
);
|
||||
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
111
include/zdwm/bar.h
Normal 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 */
|
||||
@@ -5,6 +5,7 @@
|
||||
#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>
|
||||
@@ -112,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
|
||||
);
|
||||
|
||||
/**
|
||||
@@ -157,6 +157,14 @@ typedef struct zdwm_api_t {
|
||||
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 注册
|
||||
* @{ */
|
||||
|
||||
@@ -207,6 +215,14 @@ typedef struct zdwm_api_t {
|
||||
);
|
||||
|
||||
/** @} */
|
||||
|
||||
/** @name bar 配置
|
||||
* @{ */
|
||||
void (*set_bar_config)(
|
||||
zdwm_config_builder_t *builder,
|
||||
zdwm_bar_config_t config
|
||||
);
|
||||
/** @} */
|
||||
} zdwm_api_t;
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,6 +4,10 @@
|
||||
#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);
|
||||
|
||||
@@ -26,13 +30,14 @@ typedef void zdwm_workspace_active_updated(
|
||||
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;
|
||||
bool is_current_mode;
|
||||
zdwm_binding_mode_id_t id;
|
||||
const char *name; /* 不持有内存 */
|
||||
} zdwm_binding_mode_notify_t;
|
||||
@@ -58,6 +63,8 @@ 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);
|
||||
|
||||
typedef struct zdwm_listeners_t zdwm_listeners_t;
|
||||
#if defined(__cplusplus)
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* ZDWM_LISTENERS_H */
|
||||
|
||||
@@ -18,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;
|
||||
@@ -30,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
|
||||
|
||||
@@ -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
|
||||
---
|
||||
158
src/action.c
158
src/action.c
@@ -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);
|
||||
}
|
||||
67
src/action.h
67
src/action.h
@@ -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);
|
||||
@@ -1,5 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#define APP_NAME "@APP_NAME@"
|
||||
|
||||
#cmakedefine HAS_EXECINFO
|
||||
@@ -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
|
||||
39
src/atoms.c
39
src/atoms.c
@@ -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);
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <xcb/xcb.h>
|
||||
|
||||
void atoms_init(xcb_connection_t *conn);
|
||||
182
src/audio.c
182
src/audio.c
@@ -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';
|
||||
}
|
||||
}
|
||||
}
|
||||
19
src/audio.h
19
src/audio.h
@@ -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);
|
||||
@@ -1 +0,0 @@
|
||||
../../.clang-format
|
||||
@@ -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; }
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
@@ -16,16 +18,15 @@
|
||||
#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 "base/window_list.h"
|
||||
#include "core/event.h"
|
||||
#include "core/plan.h"
|
||||
#include "core/types.h"
|
||||
#include "core/window.h"
|
||||
#include "common/window.h"
|
||||
#include "internal.h"
|
||||
|
||||
typedef struct atom_item_t {
|
||||
@@ -57,6 +58,21 @@ static void atoms_init(backend_t *backend) {
|
||||
*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) {
|
||||
@@ -72,13 +88,12 @@ static void create_wm_check_window(backend_t *backend) {
|
||||
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);
|
||||
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;
|
||||
@@ -109,7 +124,7 @@ static void create_no_focus_window(backend_t *backend) {
|
||||
};
|
||||
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;
|
||||
@@ -167,16 +182,21 @@ 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);
|
||||
|
||||
@@ -187,11 +207,19 @@ void backend_destroy(backend_t *backend) {
|
||||
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,
|
||||
@@ -493,6 +521,33 @@ backend_bind_key(backend_t *backend, const effect_bind_key_t *bind_key) {
|
||||
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,
|
||||
@@ -503,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);
|
||||
@@ -521,6 +577,12 @@ static void backend_merge_effects(
|
||||
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;
|
||||
@@ -551,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
|
||||
@@ -572,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) {
|
||||
@@ -605,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);
|
||||
}
|
||||
@@ -620,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);
|
||||
@@ -643,20 +761,48 @@ backend_scan_result_t *backend_scan_windows(backend_t *backend) {
|
||||
|
||||
auto list = xcb_query_tree_children(reply);
|
||||
|
||||
backend_scan_result_t *result = p_new(backend_scan_result_t, 1);
|
||||
auto result = p_new(backend_scan_result_t, 1);
|
||||
auto sub_result = p_new(backend_scan_result_t, 1);
|
||||
|
||||
for (int i = 0; i < length; i++) {
|
||||
auto slot = (window_map_request_event_t *)
|
||||
array_push(result->windows, result->count, result->capacity);
|
||||
if (!populate_window_event(backend, list[i], slot)) {
|
||||
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);
|
||||
result->count--;
|
||||
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;
|
||||
@@ -678,3 +824,99 @@ void backend_scan_result_destroy(backend_scan_result_t *result) {
|
||||
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
71
src/backend/x11/common.c
Normal 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
12
src/backend/x11/common.h
Normal 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
51
src/backend/x11/cursor.c
Normal 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
9
src/backend/x11/cursor.h
Normal 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);
|
||||
@@ -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(
|
||||
@@ -51,18 +52,16 @@ static bool derive_skip_taskbar(
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool minimized_from_hints(const xcb_icccm_wm_hints_t *hints) {
|
||||
return hints->initial_state == XCB_ICCCM_WM_STATE_ICONIC;
|
||||
}
|
||||
|
||||
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_MAXIMIZED_VERT ||
|
||||
state_atoms[i] == atoms->_NET_WM_STATE_MAXIMIZED_HORZ)
|
||||
if (
|
||||
state_atoms[i] == atoms->_NET_WM_STATE_MAXIMIZED_VERT ||
|
||||
state_atoms[i] == atoms->_NET_WM_STATE_MAXIMIZED_HORZ
|
||||
)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -91,6 +90,28 @@ static bool urgent_from_states(
|
||||
return false;
|
||||
}
|
||||
|
||||
static void parse_size_hints(
|
||||
const xcb_size_hints_t *hints,
|
||||
zdwm_size_t *min,
|
||||
zdwm_size_t *max
|
||||
) {
|
||||
assert(hints != nullptr);
|
||||
assert(min != nullptr);
|
||||
assert(max != nullptr);
|
||||
|
||||
*min = (zdwm_size_t){.width = 0, .height = 0};
|
||||
*max = (zdwm_size_t){.width = INT32_MAX, .height = INT32_MAX};
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
bool populate_window_event(
|
||||
backend_t *backend,
|
||||
xcb_window_t window,
|
||||
@@ -99,8 +120,7 @@ bool populate_window_event(
|
||||
ev->window = (window_id_t)window;
|
||||
ev->transient_for = window_get_transient_for(backend, window);
|
||||
|
||||
xcb_get_window_attributes_reply_t *wa =
|
||||
window_get_attributes(backend, window);
|
||||
auto wa = window_get_attributes(backend, window);
|
||||
if (!wa) return false;
|
||||
ev->override_redirect = (bool)wa->override_redirect;
|
||||
p_delete(&wa);
|
||||
@@ -108,10 +128,13 @@ bool populate_window_event(
|
||||
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->minimized = minimized_from_hints(&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;
|
||||
@@ -167,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,
|
||||
@@ -180,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)) {
|
||||
@@ -196,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;
|
||||
@@ -208,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;
|
||||
@@ -232,29 +263,6 @@ static bool handle_configure_request(
|
||||
return true;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
return mods;
|
||||
}
|
||||
|
||||
static bool handle_key_press(
|
||||
backend_t *backend,
|
||||
event_t *event,
|
||||
@@ -268,11 +276,64 @@ static bool handle_key_press(
|
||||
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->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,
|
||||
@@ -289,9 +350,13 @@ 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 ||
|
||||
xcb_event->atom == backend->atoms._NET_WM_NAME) {
|
||||
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;
|
||||
@@ -329,34 +394,31 @@ static bool handle_property_notify(
|
||||
|
||||
if (xcb_event->atom == XCB_ATOM_WM_HINTS) {
|
||||
xcb_icccm_wm_hints_t hints = {0};
|
||||
auto handled = window_get_wm_hints(backend, window_id, &hints);
|
||||
if (!handled) return false;
|
||||
if (!window_get_wm_hints(backend, window_id, &hints)) return false;
|
||||
|
||||
event->type = ZDWM_EVENT_WINDOW_STATE_REQUEST;
|
||||
event->type = ZDWM_EVENT_WINDOW_HINTS_CHANGED;
|
||||
|
||||
auto data = &event->as.window_state_request;
|
||||
auto data = &event->as.hints;
|
||||
data->window = window_id;
|
||||
if (hints.flags & XCB_ICCCM_WM_HINT_X_URGENCY) {
|
||||
data->type = ZDWM_WINDOW_STATE_REQUEST_URGENT;
|
||||
data->action = xcb_icccm_wm_hints_get_urgency(&hints)
|
||||
? ZDWM_WINDOW_STATE_ACTION_ADD
|
||||
: ZDWM_WINDOW_STATE_ACTION_REMOVE;
|
||||
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) {
|
||||
bool fixed_size = false;
|
||||
if (!window_get_fixed_size(backend, window_id, &fixed_size)) return false;
|
||||
xcb_size_hints_t hints = {0};
|
||||
if (!window_get_size_hints(backend, window_id, &hints)) return false;
|
||||
|
||||
event->type = ZDWM_EVENT_WINDOW_STATE_REQUEST;
|
||||
event->type = ZDWM_EVENT_WINDOW_HINTS_CHANGED;
|
||||
|
||||
auto data = &event->as.window_state_request;
|
||||
auto data = &event->as.hints;
|
||||
data->window = window_id;
|
||||
data->type = ZDWM_WINDOW_STATE_REQUEST_FIXED_SIZE;
|
||||
data->action = fixed_size ? ZDWM_WINDOW_STATE_ACTION_ADD
|
||||
: ZDWM_WINDOW_STATE_ACTION_REMOVE;
|
||||
|
||||
data->changed_fields |= ZDWM_HINT_FIELD_SIZE;
|
||||
parse_size_hints(&hints, &data->min_size, &data->max_size);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -368,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) {
|
||||
@@ -436,7 +500,11 @@ static bool handle_client_message(
|
||||
}
|
||||
|
||||
if (urgent_from_states(atoms, properties, count)) {
|
||||
data->type = ZDWM_WINDOW_STATE_REQUEST_URGENT;
|
||||
event->type = ZDWM_EVENT_WINDOW_HINTS_CHANGED;
|
||||
event->as.hints = (typeof(event->as.hints)){
|
||||
.urgent = true,
|
||||
.changed_fields = ZDWM_HINT_FIELD_URGENT,
|
||||
};
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -446,13 +514,11 @@ static bool handle_client_message(
|
||||
return false;
|
||||
}
|
||||
|
||||
bool backend_next_event(backend_t *backend, event_t *event) {
|
||||
if (!backend || !backend->conn || !event) return false;
|
||||
|
||||
for (;;) {
|
||||
xcb_generic_event_t *raw_event = xcb_wait_for_event(backend->conn);
|
||||
if (!raw_event) 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;
|
||||
|
||||
@@ -471,6 +537,9 @@ bool backend_next_event(backend_t *backend, event_t *event) {
|
||||
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);
|
||||
@@ -484,5 +553,27 @@ bool backend_next_event(backend_t *backend, event_t *event) {
|
||||
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;
|
||||
|
||||
for (;;) {
|
||||
xcb_generic_event_t *raw_event = xcb_wait_for_event(backend->conn);
|
||||
if (!raw_event) return false;
|
||||
|
||||
if (handle_event(backend, raw_event, event)) return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,27 +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>
|
||||
|
||||
#include "base/window_list.h"
|
||||
#include "core/event.h"
|
||||
#include "common/window.h"
|
||||
#include "interface/event.h"
|
||||
|
||||
#define ATOM_LIST(X) \
|
||||
X(COMPOUND_TEXT) \
|
||||
X(UTF8_STRING) \
|
||||
\
|
||||
X(WM_WINDOW_ROLE) \
|
||||
X(WM_NAME) \
|
||||
X(WM_CHANGE_STATE) \
|
||||
#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) \
|
||||
@@ -56,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);
|
||||
@@ -74,7 +83,16 @@ typedef struct window_configure_list_t {
|
||||
size_t capacity;
|
||||
} window_configure_list_t;
|
||||
|
||||
struct backend_t {
|
||||
typedef enum cursor_t {
|
||||
ZDWM_CURSOR_NORMAL,
|
||||
ZDWM_CURSOR_MOVE,
|
||||
ZDWM_CURSOR_RESIZE,
|
||||
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;
|
||||
xcb_screen_t *screen;
|
||||
@@ -90,11 +108,17 @@ 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,
|
||||
|
||||
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
|
||||
);
|
||||
@@ -3,14 +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 "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(
|
||||
@@ -35,10 +36,12 @@ static char *window_get_text_property(
|
||||
char *value = (char *)xcb_get_property_value(reply);
|
||||
|
||||
char *text = nullptr;
|
||||
if (length && reply->format == 8 &&
|
||||
if (
|
||||
length && reply->format == 8 &&
|
||||
(reply->type == XCB_ATOM_STRING ||
|
||||
reply->type == backend->atoms.UTF8_STRING ||
|
||||
reply->type == backend->atoms.COMPOUND_TEXT)) {
|
||||
reply->type == backend->atoms.COMPOUND_TEXT)
|
||||
) {
|
||||
text = p_strndup(value, (size_t)length);
|
||||
}
|
||||
|
||||
@@ -157,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;
|
||||
}
|
||||
|
||||
@@ -405,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, ¶ms);
|
||||
}
|
||||
|
||||
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)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
#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;
|
||||
@@ -53,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,
|
||||
@@ -73,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) \
|
||||
@@ -103,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
|
||||
);
|
||||
|
||||
@@ -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.");
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "buffer.h"
|
||||
|
||||
void backtrace_get(buffer_t *buffer);
|
||||
527
src/bar/bar.c
Normal file
527
src/bar/bar.c
Normal 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 = ¢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);
|
||||
}
|
||||
}
|
||||
|
||||
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(¶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;
|
||||
|
||||
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(¶ms);
|
||||
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
55
src/bar/bar.h
Normal 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
99
src/bar/binding.c
Normal 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
17
src/bar/binding.h
Normal 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
173
src/bar/cell.c
Normal 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
15
src/bar/cell.h
Normal 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
141
src/bar/text.c
Normal 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
27
src/bar/text.h
Normal 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
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;
|
||||
43
src/bar/types.h
Normal file
43
src/bar/types.h
Normal 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
175
src/bar/windows.c
Normal 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
18
src/bar/windows.h
Normal 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
305
src/bar/workspaces.c
Normal 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
23
src/bar/workspaces.h
Normal 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);
|
||||
17
src/base.h
17
src/base.h
@@ -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;
|
||||
@@ -1 +0,0 @@
|
||||
../../.clang-format
|
||||
3
src/base/app.h
Normal file
3
src/base/app.h
Normal file
@@ -0,0 +1,3 @@
|
||||
#pragma once
|
||||
|
||||
#define APP_NAME "zdwm"
|
||||
@@ -98,11 +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) \
|
||||
( \
|
||||
typeof(items) \
|
||||
)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) \
|
||||
|
||||
@@ -56,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];
|
||||
@@ -76,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;
|
||||
}
|
||||
|
||||
@@ -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];
|
||||
|
||||
23
src/base/process.c
Normal file
23
src/base/process.c
Normal file
@@ -0,0 +1,23 @@
|
||||
#include "base/process.h"
|
||||
|
||||
#include <paths.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "base/log.h"
|
||||
|
||||
void spawn(const char *command) {
|
||||
if (fork() == 0) {
|
||||
setsid();
|
||||
|
||||
if (fork() == 0) {
|
||||
execl(_PATH_BSHELL, _PATH_BSHELL, "-c", command, nullptr);
|
||||
fatal("execl fail: %s", command);
|
||||
}
|
||||
|
||||
exit(0);
|
||||
}
|
||||
|
||||
wait(0);
|
||||
}
|
||||
3
src/base/process.h
Normal file
3
src/base/process.h
Normal file
@@ -0,0 +1,3 @@
|
||||
#pragma once
|
||||
|
||||
void spawn(const char *command);
|
||||
54
src/base/time.c
Normal file
54
src/base/time.c
Normal 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, ×tamp) == -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
6
src/base/time.h
Normal 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);
|
||||
@@ -1,26 +0,0 @@
|
||||
#include "base/window_list.h"
|
||||
|
||||
#include <zdwm/types.h>
|
||||
|
||||
#include "base/array.h"
|
||||
#include "base/memory.h"
|
||||
|
||||
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;
|
||||
}
|
||||
37
src/buffer.c
37
src/buffer.c
@@ -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;
|
||||
}
|
||||
}
|
||||
78
src/buffer.h
78
src/buffer.h
@@ -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));
|
||||
}
|
||||
706
src/client.c
706
src/client.c
@@ -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, ¶ms);
|
||||
}
|
||||
|
||||
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, ¶ms);
|
||||
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, ¶ms);
|
||||
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, ¶ms);
|
||||
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, ¶ms);
|
||||
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, ¶ms);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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;
|
||||
}
|
||||
76
src/client.h
76
src/client.h
@@ -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;
|
||||
}
|
||||
87
src/color.c
87
src/color.c
@@ -1,87 +0,0 @@
|
||||
#include "color.h"
|
||||
|
||||
#include "utils.h"
|
||||
|
||||
/**
|
||||
* @brief 将 16 进制的颜色配置转换成 uint32 格式的 rgba数据
|
||||
*
|
||||
* @param hex 16 进制的颜色表示,支持的格式有
|
||||
* RGB/RGBA/RRGGBB/RRGGBBAA/#RGB/#RGBA/#RRGGBB/#RRGGBBAA
|
||||
* @param rgba 返回数据的指针,解析结果会放到这个指针指向的内存中
|
||||
* @return 如果 hex 是一个能正常解析的颜色,返回 true ,否则返回 false
|
||||
*/
|
||||
static bool hex_color_to_rgba(const char *hex, uint32_t *rgba);
|
||||
/**
|
||||
* @brief 将 rgba 通道顺序的颜色转换成 argb 顺序
|
||||
* @description xcb 库用到的颜色需要是 argb 通道顺序
|
||||
*/
|
||||
static uint32_t rgba_to_argb(uint32_t rgba);
|
||||
/**
|
||||
* @brief 提取颜色中的各个颜色通道数值
|
||||
* @param rgba uint32 表示的颜色
|
||||
* @param red red channel, range 0~1
|
||||
* @param green green channel, range 0~1
|
||||
* @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);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
bool hex_color_to_rgba(const char *hex, uint32_t *rgba) {
|
||||
if (hex[0] == '#') hex++;
|
||||
|
||||
size_t len = strlen(hex);
|
||||
char extended[9] = {0};
|
||||
uint32_t color = 0x00000000;
|
||||
|
||||
if (len == 3 || len == 4) {
|
||||
extended[0] = extended[1] = hex[0];
|
||||
extended[2] = extended[3] = hex[1];
|
||||
extended[4] = extended[5] = hex[2];
|
||||
if (len == 3) {
|
||||
extended[6] = extended[7] = 'F';
|
||||
} else {
|
||||
extended[6] = extended[7] = hex[3];
|
||||
}
|
||||
} else if (len == 6) {
|
||||
strncpy(extended, hex, len);
|
||||
extended[6] = extended[7] = 'F';
|
||||
} else if (len == 8) {
|
||||
strncpy(extended, hex, len);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
color = strtoul(extended, nullptr, 16) & 0xffffffff;
|
||||
*rgba = color;
|
||||
return true;
|
||||
}
|
||||
|
||||
#define EXTRACE_COLOR_BIT(C, R) (((C) >> (R)) & 0xff)
|
||||
uint32_t rgba_to_argb(uint32_t rgba) {
|
||||
uint32_t r = EXTRACE_COLOR_BIT(rgba, 24);
|
||||
uint32_t g = EXTRACE_COLOR_BIT(rgba, 16);
|
||||
uint32_t b = EXTRACE_COLOR_BIT(rgba, 8);
|
||||
uint32_t a = EXTRACE_COLOR_BIT(rgba, 0);
|
||||
|
||||
uint32_t argb = (a << 24) | (r << 16) | (g << 8) | b;
|
||||
return argb;
|
||||
}
|
||||
|
||||
#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) {
|
||||
*red = COLOR_SPLIT(rgba, 24);
|
||||
*green = COLOR_SPLIT(rgba, 16);
|
||||
*blue = COLOR_SPLIT(rgba, 8);
|
||||
*alpha = COLOR_SPLIT(rgba, 0);
|
||||
}
|
||||
22
src/color.h
22
src/color.h
@@ -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);
|
||||
@@ -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
6
src/common/event.h
Normal 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
107
src/common/listeners.c
Normal 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
94
src/common/listeners.h
Normal 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
55
src/common/window.c
Normal 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;
|
||||
}
|
||||
@@ -1,7 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
#include <zdwm/types.h>
|
||||
#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;
|
||||
46
src/common/workspace.c
Normal file
46
src/common/workspace.c
Normal 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
21
src/common/workspace.h
Normal 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
|
||||
);
|
||||
81
src/config.c
81
src/config.c
@@ -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;
|
||||
}
|
||||
57
src/config.h
57
src/config.h
@@ -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);
|
||||
@@ -1 +0,0 @@
|
||||
../../.clang-format
|
||||
@@ -4,89 +4,22 @@
|
||||
#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 "core/types.h"
|
||||
#include "interface/types.h"
|
||||
|
||||
static constexpr char launcher[] =
|
||||
"rofi -show combi -modes combi -combi-modes window,drun,run,ssh,windowcd";
|
||||
|
||||
typedef struct raise_or_run_arg_t {
|
||||
const char *class_name;
|
||||
const char *command;
|
||||
} raise_or_run_arg_t;
|
||||
typedef struct zdwm_action_data_raise_or_run_t raise_or_run_data_t;
|
||||
|
||||
static raise_or_run_arg_t terminal = {"XTerm", "xterm"};
|
||||
static raise_or_run_arg_t editor = {"Emacs", "emacsclient -a '' -r -n"};
|
||||
static raise_or_run_arg_t browser = {"firefox", "firefox-bin"};
|
||||
static raise_or_run_arg_t chrome = {"Google-chrome", "google-chrome-stable"};
|
||||
|
||||
static void spawn(
|
||||
zdwm_runtime_t *runtime,
|
||||
const zdwm_action_api_t *api,
|
||||
const zdwm_action_arg_t *arg
|
||||
) {
|
||||
api->spawn(arg->str);
|
||||
}
|
||||
|
||||
static void quit(
|
||||
zdwm_runtime_t *runtime,
|
||||
const zdwm_action_api_t *api,
|
||||
const zdwm_action_arg_t *arg
|
||||
) {
|
||||
api->quit(runtime, arg->b);
|
||||
}
|
||||
|
||||
static void raise_or_run(
|
||||
zdwm_runtime_t *runtime,
|
||||
const zdwm_action_api_t *api,
|
||||
const zdwm_action_arg_t *arg
|
||||
) {
|
||||
auto args = (raise_or_run_arg_t *)arg->ptr;
|
||||
api->raise_or_run(runtime, args->class_name, args->command);
|
||||
}
|
||||
|
||||
static void toggle_fullscreen(
|
||||
zdwm_runtime_t *runtime,
|
||||
const zdwm_action_api_t *api,
|
||||
const zdwm_action_arg_t *arg
|
||||
) {
|
||||
api->toggle_fullscreen(runtime);
|
||||
}
|
||||
|
||||
static void toggle_maximize(
|
||||
zdwm_runtime_t *runtime,
|
||||
const zdwm_action_api_t *api,
|
||||
const zdwm_action_arg_t *arg
|
||||
) {
|
||||
api->toggle_maximize(runtime);
|
||||
}
|
||||
|
||||
static void toggle_floating(
|
||||
zdwm_runtime_t *runtime,
|
||||
const zdwm_action_api_t *api,
|
||||
const zdwm_action_arg_t *arg
|
||||
) {
|
||||
api->toggle_floating(runtime);
|
||||
}
|
||||
|
||||
static void switch_workspace(
|
||||
zdwm_runtime_t *runtime,
|
||||
const zdwm_action_api_t *api,
|
||||
const zdwm_action_arg_t *arg
|
||||
) {
|
||||
api->switch_workspace_by_index_in_current_output(runtime, arg->ui);
|
||||
}
|
||||
|
||||
static void focus_window(
|
||||
zdwm_runtime_t *runtime,
|
||||
const zdwm_action_api_t *api,
|
||||
const zdwm_action_arg_t *arg
|
||||
) {
|
||||
api->focus_window(runtime, arg->i);
|
||||
}
|
||||
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,
|
||||
@@ -119,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 ||
|
||||
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) {
|
||||
floating_id == ZDWM_LAYOUT_ID_INVALID
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -156,37 +91,118 @@ bool config_defaults_build(
|
||||
|
||||
auto default_mode = api->add_mode(builder, "default");
|
||||
|
||||
#define Alt "Mod1"
|
||||
#define Super "Mod4"
|
||||
#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, Super "+r", spawn, {.str = launcher});
|
||||
BIND(default_mode, Super "+Shift+q", quit, {.b = false});
|
||||
BIND(default_mode, Super "+Control+r", quit, {.b = true});
|
||||
#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__)
|
||||
|
||||
BIND(default_mode, Super "+Return", spawn, {.str = terminal.command});
|
||||
BIND(default_mode, Alt "+Control+r", raise_or_run, {.ptr = &terminal});
|
||||
BIND(default_mode, Super "+e", raise_or_run, {.ptr = &editor});
|
||||
BIND(default_mode, Super "+q", raise_or_run, {.ptr = &browser});
|
||||
BIND(default_mode, Super "+a", raise_or_run, {.ptr = &chrome});
|
||||
BIND(default_mode, Super "+f", toggle_fullscreen, {0});
|
||||
BIND(default_mode, Super "+m", toggle_maximize, {0});
|
||||
BIND(default_mode, Super "+Control+space", toggle_floating, {0});
|
||||
BIND(default_mode, Alt "+j", focus_window, {.i = 1});
|
||||
BIND(default_mode, Alt "+k", focus_window, {.i = -1});
|
||||
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), "%s+%d", Super, i + 1);
|
||||
BIND(default_mode, buffer, switch_workspace, {.ui = 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;
|
||||
}
|
||||
|
||||
@@ -2,20 +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/listeners.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"
|
||||
@@ -28,19 +30,24 @@ struct zdwm_config_builder_t {
|
||||
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;
|
||||
@@ -49,8 +56,10 @@ 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);
|
||||
@@ -59,6 +68,7 @@ static void config_builder_cleanup(zdwm_config_builder_t *builder) {
|
||||
|
||||
binding_table_destroy(builder->binding_table);
|
||||
builder->binding_table = nullptr;
|
||||
listeners_cleanup(&builder->listeners);
|
||||
}
|
||||
|
||||
static layout_id_t runtime_config_register_layout(
|
||||
@@ -167,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(
|
||||
@@ -198,6 +207,13 @@ static void runtime_config_set_border_config(
|
||||
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,
|
||||
@@ -279,6 +295,14 @@ static void runtime_config_subscribe_window_removed(
|
||||
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
|
||||
@@ -290,16 +314,19 @@ 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->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;
|
||||
p_clear(&builder->listeners, 1);
|
||||
p_clear(&builder->bar, 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -314,10 +341,10 @@ static bool runtime_config_build(
|
||||
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 =
|
||||
{
|
||||
.builtin_layouts = {
|
||||
.fair = fair,
|
||||
.fullscreen = fullscreen,
|
||||
.maximize = maximize,
|
||||
@@ -331,18 +358,21 @@ static bool runtime_config_build(
|
||||
.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_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_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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
../../.clang-format
|
||||
@@ -1,265 +0,0 @@
|
||||
#include "core/action.h"
|
||||
|
||||
#include <paths.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "base/log.h"
|
||||
#include "base/macros.h"
|
||||
#include "core/command.h"
|
||||
#include "core/command_buffer.h"
|
||||
#include "core/runtime.h"
|
||||
#include "core/state.h"
|
||||
#include "core/types.h"
|
||||
#include "core/window.h"
|
||||
|
||||
void spawn(const char *command) {
|
||||
if (fork() == 0) {
|
||||
setsid();
|
||||
|
||||
if (fork() == 0) {
|
||||
execl(_PATH_BSHELL, _PATH_BSHELL, "-c", command, nullptr);
|
||||
fatal("execl fail: %s", command);
|
||||
}
|
||||
|
||||
exit(0);
|
||||
}
|
||||
|
||||
wait(0);
|
||||
}
|
||||
|
||||
void quit(runtime_t *runtime, bool restart) {
|
||||
runtime->will_restart = restart;
|
||||
runtime->running = false;
|
||||
}
|
||||
|
||||
static const window_t *
|
||||
get_next_window_by_class(state_t *state, const char *class_name) {
|
||||
auto output = state_output_at(state, state->current_output_index);
|
||||
auto workspace = state_workspace_get(state, output->current_workspace_id);
|
||||
|
||||
size_t start_index = 0;
|
||||
if (!window_id_invalid(workspace->focused_window_id)) {
|
||||
for (size_t i = 0; i < state_window_count(state); ++i) {
|
||||
if (state_window_at(state, i)->id == workspace->focused_window_id) {
|
||||
start_index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = start_index + 1; i < state_window_count(state); ++i) {
|
||||
auto window = state_window_at(state, i);
|
||||
if (strcmp(window->class_name, class_name) == 0) {
|
||||
return window;
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < MIN(start_index + 1, state_window_count(state)); ++i) {
|
||||
auto window = state_window_at(state, i);
|
||||
if (strcmp(window->class_name, class_name) == 0) {
|
||||
return window;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void raise_or_run(
|
||||
runtime_t *runtime,
|
||||
const char *class_name,
|
||||
const char *command
|
||||
) {
|
||||
auto window = get_next_window_by_class(&runtime->state, class_name);
|
||||
if (!window) {
|
||||
spawn(command);
|
||||
return;
|
||||
}
|
||||
|
||||
command_t switch_workspace_cmd = {
|
||||
.type = ZDWM_COMMAND_SWITCH_WORKSPACE,
|
||||
.as.switch_workspace.workspace = window->workspace_id,
|
||||
};
|
||||
command_buffer_push(&runtime->command_buffer, &switch_workspace_cmd);
|
||||
|
||||
command_t focus_cmd = {
|
||||
.type = ZDWM_COMMAND_FOCUS_WINDOW,
|
||||
.as.focus.window = window->id,
|
||||
};
|
||||
command_buffer_push(&runtime->command_buffer, &focus_cmd);
|
||||
}
|
||||
|
||||
static const window_t *get_current_focused_window(const state_t *state) {
|
||||
auto output = state_output_at(state, state->current_output_index);
|
||||
auto workspace = state_workspace_get(state, output->current_workspace_id);
|
||||
return state_window_get(state, workspace->focused_window_id);
|
||||
}
|
||||
|
||||
void toggle_fullscreen(runtime_t *runtime) {
|
||||
auto window = get_current_focused_window(&runtime->state);
|
||||
if (!window) return;
|
||||
|
||||
command_t window_set_fullscreen_cmd = {
|
||||
.type = ZDWM_COMMAND_WINDOW_SET_FULLSCREEN,
|
||||
.as.fullscreen = {
|
||||
.window = window->id,
|
||||
.state = !window->fullscreen,
|
||||
}
|
||||
};
|
||||
command_buffer_push(&runtime->command_buffer, &window_set_fullscreen_cmd);
|
||||
}
|
||||
|
||||
void toggle_maximize(runtime_t *runtime) {
|
||||
auto window = get_current_focused_window(&runtime->state);
|
||||
if (!window) return;
|
||||
|
||||
command_t window_set_maximized_cmd = {
|
||||
.type = ZDWM_COMMAND_WINDOW_SET_MAXIMIZED,
|
||||
.as.maximized = {
|
||||
.window = window->id,
|
||||
.state = !window->maximized,
|
||||
}
|
||||
};
|
||||
command_buffer_push(&runtime->command_buffer, &window_set_maximized_cmd);
|
||||
}
|
||||
|
||||
void toggle_floating(runtime_t *runtime) {
|
||||
auto window = get_current_focused_window(&runtime->state);
|
||||
if (!window) return;
|
||||
|
||||
command_t window_set_floating_cmd = {
|
||||
.type = ZDWM_COMMAND_WINDOW_SET_FLOATING,
|
||||
.as.floating = {
|
||||
.window = window->id,
|
||||
.state = !window->floating,
|
||||
}
|
||||
};
|
||||
command_buffer_push(&runtime->command_buffer, &window_set_floating_cmd);
|
||||
}
|
||||
|
||||
void toggle_sticky(runtime_t *runtime) {
|
||||
auto window = get_current_focused_window(&runtime->state);
|
||||
if (!window) return;
|
||||
|
||||
command_t window_set_sticky_cmd = {
|
||||
.type = ZDWM_COMMAND_WINDOW_SET_STICKY,
|
||||
.as.sticky = {
|
||||
.window = window->id,
|
||||
.state = !window->sticky,
|
||||
}
|
||||
};
|
||||
command_buffer_push(&runtime->command_buffer, &window_set_sticky_cmd);
|
||||
}
|
||||
|
||||
void switch_workspace(runtime_t *runtime, workspace_id_t workspace_id) {
|
||||
auto workspace = state_workspace_get(&runtime->state, workspace_id);
|
||||
if (!workspace) return;
|
||||
|
||||
command_t switch_workspace_cmd = {
|
||||
.type = ZDWM_COMMAND_SWITCH_WORKSPACE,
|
||||
.as.switch_workspace.workspace = workspace_id
|
||||
};
|
||||
command_buffer_push(&runtime->command_buffer, &switch_workspace_cmd);
|
||||
}
|
||||
|
||||
void switch_workspace_in_current_output(
|
||||
runtime_t *runtime,
|
||||
workspace_id_t workspace_id
|
||||
) {
|
||||
auto state = &runtime->state;
|
||||
auto output = state_output_at(state, state->current_output_index);
|
||||
auto workspace = state_workspace_get(state, workspace_id);
|
||||
if (workspace->output_id != output->id) return;
|
||||
|
||||
command_t switch_workspace_cmd = {
|
||||
.type = ZDWM_COMMAND_SWITCH_WORKSPACE,
|
||||
.as.switch_workspace.workspace = workspace_id
|
||||
};
|
||||
command_buffer_push(&runtime->command_buffer, &switch_workspace_cmd);
|
||||
}
|
||||
|
||||
void switch_workspace_by_index_in_current_output(
|
||||
runtime_t *runtime,
|
||||
uint32_t index
|
||||
) {
|
||||
auto state = &runtime->state;
|
||||
auto output = state_output_at(state, state->current_output_index);
|
||||
uint32_t count = 0;
|
||||
for (size_t i = 0; i < state_workspace_count(state); ++i) {
|
||||
auto workspace = state_workspace_at(state, i);
|
||||
if (workspace->output_id != output->id) continue;
|
||||
|
||||
if (count == index) {
|
||||
command_t switch_workspace_cmd = {
|
||||
.type = ZDWM_COMMAND_SWITCH_WORKSPACE,
|
||||
.as.switch_workspace.workspace = workspace->id,
|
||||
};
|
||||
command_buffer_push(&runtime->command_buffer, &switch_workspace_cmd);
|
||||
return;
|
||||
}
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
void cycle_layout(runtime_t *runtime, int32_t delta) {
|
||||
auto state = &runtime->state;
|
||||
auto output = state_output_at(state, state->current_output_index);
|
||||
auto workspace = state_workspace_get(state, output->current_workspace_id);
|
||||
if (!state_workspace_cycle_layout(state, workspace->id, delta)) return;
|
||||
|
||||
runtime->plan.need_relayout = true;
|
||||
}
|
||||
|
||||
void cycle_current_output(runtime_t *runtime, int32_t delta) {
|
||||
auto state = &runtime->state;
|
||||
state_cycle_current_output(state, delta);
|
||||
}
|
||||
|
||||
void focus_window(runtime_t *runtime, int32_t delta) {
|
||||
auto state = &runtime->state;
|
||||
auto output = state_output_at(state, state->current_output_index);
|
||||
auto workspace = state_workspace_get(state, output->current_workspace_id);
|
||||
|
||||
size_t count = state_window_count(state);
|
||||
if (!count) return;
|
||||
|
||||
size_t indices[count];
|
||||
size_t num = 0;
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
auto window = state_window_at(state, i);
|
||||
if (window->workspace_id == workspace->id) {
|
||||
indices[num++] = i;
|
||||
}
|
||||
}
|
||||
if (!num) return;
|
||||
|
||||
size_t current = 0;
|
||||
if (!window_id_invalid(workspace->focused_window_id)) {
|
||||
for (size_t i = 0; i < num; ++i) {
|
||||
auto window = state_window_at(state, indices[i]);
|
||||
if (window->id == workspace->focused_window_id) {
|
||||
current = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int64_t next = ((int64_t)current + (int64_t)delta) % (int64_t)num;
|
||||
if (next < 0) next += (int64_t)num;
|
||||
|
||||
auto target = state_window_at(state, indices[next]);
|
||||
|
||||
command_t focus_cmd = {
|
||||
.type = ZDWM_COMMAND_FOCUS_WINDOW,
|
||||
.as.focus.window = target->id,
|
||||
};
|
||||
command_buffer_push(&runtime->command_buffer, &focus_cmd);
|
||||
|
||||
command_t raise_cmd = {
|
||||
.type = ZDWM_COMMAND_RAISE_WINDOW,
|
||||
.as.raise.window = target->id,
|
||||
};
|
||||
command_buffer_push(&runtime->command_buffer, &raise_cmd);
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <zdwm/action.h>
|
||||
|
||||
void spawn(const char *command);
|
||||
void quit(zdwm_runtime_t *runtime, bool restart);
|
||||
void raise_or_run(
|
||||
zdwm_runtime_t *runtime,
|
||||
const char *class_name,
|
||||
const char *command
|
||||
);
|
||||
|
||||
void toggle_fullscreen(zdwm_runtime_t *runtime);
|
||||
void toggle_maximize(zdwm_runtime_t *runtime);
|
||||
void toggle_floating(zdwm_runtime_t *runtime);
|
||||
void toggle_sticky(zdwm_runtime_t *runtime);
|
||||
void switch_workspace(
|
||||
zdwm_runtime_t *runtime,
|
||||
zdwm_workspace_id_t workspace_id
|
||||
);
|
||||
void switch_workspace_in_current_output(
|
||||
zdwm_runtime_t *runtime,
|
||||
zdwm_workspace_id_t workspace_id
|
||||
);
|
||||
void switch_workspace_by_index_in_current_output(
|
||||
zdwm_runtime_t *runtime,
|
||||
uint32_t index
|
||||
);
|
||||
void cycle_layout(zdwm_runtime_t *runtime, int32_t delta);
|
||||
void cycle_current_output(zdwm_runtime_t *runtime, int32_t delta);
|
||||
void focus_window(zdwm_runtime_t *runtime, int32_t delta);
|
||||
@@ -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,8 +43,7 @@ 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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -166,8 +167,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
|
||||
) {
|
||||
binding_mode_t *mode = binding_table_get_mode(table, mode_id);
|
||||
if (!mode) return false;
|
||||
@@ -176,12 +176,10 @@ bool binding_table_add_bind(
|
||||
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;
|
||||
auto item = array_push(mode->items, mode->count, mode->capacity);
|
||||
item->modifiers = modifiers;
|
||||
item->keysym = keysym;
|
||||
item->fn = fn;
|
||||
item->arg = arg;
|
||||
item->action = action;
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -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;
|
||||
@@ -251,17 +252,13 @@ bool binding_table_cycle_mode(binding_table_t *table, int delta) {
|
||||
}
|
||||
}
|
||||
|
||||
if (!matched) return false;
|
||||
|
||||
/* 只有一个模式没必要切换 */
|
||||
if (table->count == 1) return true;
|
||||
if (!matched) return ZDWM_BINDING_MODE_ID_INVALID;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#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,
|
||||
@@ -11,13 +11,25 @@ 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,
|
||||
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 {
|
||||
@@ -36,11 +48,42 @@ typedef struct window_state_change_command_t {
|
||||
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 的命令值对象
|
||||
* @details
|
||||
@@ -60,12 +103,22 @@ 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;
|
||||
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;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
#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);
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include "core/types.h"
|
||||
#include "interface/types.h"
|
||||
|
||||
typedef struct layer_stack_t {
|
||||
window_id_t *order;
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
#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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,83 +1,193 @@
|
||||
#include "core/listeners.h"
|
||||
|
||||
#include "base/array.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 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
|
||||
#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_add_output_listener(
|
||||
listeners_t *listeners,
|
||||
zdwm_current_output_id_listener fn,
|
||||
void *user_data
|
||||
void listeners_notify_current_output(
|
||||
const listeners_t *listeners,
|
||||
output_id_t output_id
|
||||
) {
|
||||
ADD_LISTENER(output_listeners);
|
||||
FOR_EACH_LISTENER(output_listeners, item->fn(output_id, item->user_data));
|
||||
}
|
||||
|
||||
void listeners_add_initial_workspace_listener(
|
||||
listeners_t *listeners,
|
||||
zdwm_initial_workspace_list fn,
|
||||
void *user_data
|
||||
void listeners_notify_initial_workspaces(
|
||||
const listeners_t *listeners,
|
||||
const state_t *state
|
||||
) {
|
||||
ADD_LISTENER(initial_workspace_listeners);
|
||||
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_add_active_workspace_listener(
|
||||
listeners_t *listeners,
|
||||
zdwm_workspace_active_updated fn,
|
||||
void *user_data
|
||||
void listeners_notify_workspace_active(
|
||||
const listeners_t *listeners,
|
||||
output_id_t output_id,
|
||||
workspace_id_t workspace_id
|
||||
) {
|
||||
ADD_LISTENER(active_workspace_listeners);
|
||||
FOR_EACH_LISTENER(
|
||||
active_workspace_listeners,
|
||||
item->fn(output_id, workspace_id, item->user_data)
|
||||
);
|
||||
}
|
||||
|
||||
void listeners_add_layout_notify(
|
||||
listeners_t *listeners,
|
||||
zdwm_layout_notify fn,
|
||||
void *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
|
||||
) {
|
||||
ADD_LISTENER(layout_listeners);
|
||||
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_add_binding_mode_notify(
|
||||
listeners_t *listeners,
|
||||
zdwm_binding_mode_notify fn,
|
||||
void *user_data
|
||||
void listeners_notify_binding_mode(
|
||||
const listeners_t *listeners,
|
||||
const binding_table_t *binding_table
|
||||
) {
|
||||
ADD_LISTENER(binding_mode_listeners);
|
||||
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));
|
||||
}
|
||||
|
||||
void listeners_add_initial_window_listener(
|
||||
listeners_t *listeners,
|
||||
zdwm_initial_window_list fn,
|
||||
void *user_data
|
||||
static bool listeners_window_snapshot(
|
||||
const state_t *state,
|
||||
window_id_t window_id,
|
||||
zdwm_window_t *out
|
||||
) {
|
||||
ADD_LISTENER(initial_window_listeners);
|
||||
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_add_window_added_listener(
|
||||
listeners_t *listeners,
|
||||
zdwm_window_added fn,
|
||||
void *user_data
|
||||
void listeners_notify_initial_windows(
|
||||
const listeners_t *listeners,
|
||||
const state_t *state
|
||||
) {
|
||||
ADD_LISTENER(window_added_listeners);
|
||||
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_add_window_updated_listener(
|
||||
listeners_t *listeners,
|
||||
zdwm_window_updated fn,
|
||||
void *user_data
|
||||
void listeners_notify_window_added(
|
||||
const listeners_t *listeners,
|
||||
const state_t *state,
|
||||
window_id_t window_id
|
||||
) {
|
||||
ADD_LISTENER(window_updated_listeners);
|
||||
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_add_window_removed_listener(
|
||||
listeners_t *listeners,
|
||||
zdwm_window_removed fn,
|
||||
void *user_data
|
||||
void listeners_notify_window_updated(
|
||||
const listeners_t *listeners,
|
||||
const state_t *state,
|
||||
window_id_t window_id
|
||||
) {
|
||||
ADD_LISTENER(window_removed_listeners);
|
||||
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)
|
||||
);
|
||||
}
|
||||
|
||||
#undef ADD_LISTENER
|
||||
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
|
||||
|
||||
@@ -3,87 +3,90 @@
|
||||
#include <stddef.h>
|
||||
#include <zdwm/listeners.h>
|
||||
|
||||
#define ITEM(NAME, FN_TYPE) \
|
||||
typedef struct NAME##_listener_item_t { \
|
||||
FN_TYPE *fn; \
|
||||
void *user_data; \
|
||||
} NAME##_listener_item_t
|
||||
#include "common/listeners.h"
|
||||
#include "interface/types.h"
|
||||
|
||||
#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
|
||||
/**
|
||||
* @brief 通知当前 output 信息
|
||||
*
|
||||
* @param listeners 通知函数集合
|
||||
* @param output_id 当前 output id
|
||||
*/
|
||||
void listeners_notify_current_output(
|
||||
const listeners_t *listeners,
|
||||
output_id_t output_id
|
||||
);
|
||||
|
||||
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);
|
||||
/**
|
||||
* @brief 通知初始化 workspace 列表
|
||||
*/
|
||||
void listeners_notify_initial_workspaces(
|
||||
const listeners_t *listeners,
|
||||
const state_t *state
|
||||
);
|
||||
|
||||
#undef ITEM
|
||||
#undef LISTENERS
|
||||
/**
|
||||
* @brief 通知当前激活的 workspace 信息
|
||||
*/
|
||||
void listeners_notify_workspace_active(
|
||||
const listeners_t *listeners,
|
||||
output_id_t output_id,
|
||||
workspace_id_t workspace_id
|
||||
);
|
||||
|
||||
typedef struct zdwm_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;
|
||||
/**
|
||||
* @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
|
||||
);
|
||||
|
||||
void listeners_add_output_listener(
|
||||
listeners_t *listeners,
|
||||
zdwm_current_output_id_listener fn,
|
||||
void *user_data
|
||||
/**
|
||||
* @brief 通知当前绑定模式信息
|
||||
*/
|
||||
void listeners_notify_binding_mode(
|
||||
const listeners_t *listeners,
|
||||
const binding_table_t *binding_table
|
||||
);
|
||||
void listeners_add_initial_workspace_listener(
|
||||
listeners_t *listeners,
|
||||
zdwm_initial_workspace_list fn,
|
||||
void *user_data
|
||||
|
||||
/**
|
||||
* @brief 通知初始窗口列表
|
||||
*/
|
||||
void listeners_notify_initial_windows(
|
||||
const listeners_t *listeners,
|
||||
const state_t *state
|
||||
);
|
||||
void listeners_add_active_workspace_listener(
|
||||
listeners_t *listeners,
|
||||
zdwm_workspace_active_updated fn,
|
||||
void *user_data
|
||||
|
||||
/**
|
||||
* @brief 通知新增窗口
|
||||
*/
|
||||
void listeners_notify_window_added(
|
||||
const listeners_t *listeners,
|
||||
const state_t *state,
|
||||
window_id_t window_id
|
||||
);
|
||||
void listeners_add_layout_notify(
|
||||
listeners_t *listeners,
|
||||
zdwm_layout_notify fn,
|
||||
void *user_data
|
||||
|
||||
/**
|
||||
* @brief 通知窗口信息更新
|
||||
*/
|
||||
void listeners_notify_window_updated(
|
||||
const listeners_t *listeners,
|
||||
const state_t *state,
|
||||
window_id_t window_id
|
||||
);
|
||||
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
|
||||
|
||||
/**
|
||||
* @brief 通知移除窗口
|
||||
*/
|
||||
void listeners_notify_window_removed(
|
||||
const listeners_t *listeners,
|
||||
window_id_t window_id
|
||||
);
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
|
||||
#include "base/array.h"
|
||||
#include "base/memory.h"
|
||||
#include "core/types.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) {
|
||||
@@ -20,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:
|
||||
}
|
||||
}
|
||||
@@ -38,6 +41,9 @@ void plan_cleanup(plan_t *plan) {
|
||||
p_delete(&plan->effects);
|
||||
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) {
|
||||
@@ -95,6 +101,36 @@ void plan_push_withdraw_effect(plan_t *plan, window_id_t 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,
|
||||
@@ -162,3 +198,8 @@ void plan_push_change_border_color_effect(
|
||||
};
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -1,82 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "base/color.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_WITHDRAW_WINDOW,
|
||||
ZDWM_EFFECT_MINIMIZE_WINDOW,
|
||||
ZDWM_EFFECT_MAXIMIZE_WINDOW,
|
||||
ZDWM_EFFECT_FULLSCREEN_WINDOW,
|
||||
ZDWM_EFFECT_CONFIGURE_WINDOW,
|
||||
ZDWM_EFFECT_CHANGE_BORDER_COLOR,
|
||||
ZDWM_EFFECT_CHANGE_WINDOW_LIST,
|
||||
ZDWM_EFFECT_RESTACK_WINDOWS,
|
||||
ZDWM_EFFECT_BIND_KEY,
|
||||
} effect_type_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_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_bool_window_t {
|
||||
window_id_t window;
|
||||
bool value;
|
||||
} effect_bool_window_t;
|
||||
|
||||
typedef struct effect_t {
|
||||
effect_type_t type;
|
||||
union {
|
||||
only_window_data_t map;
|
||||
only_window_data_t unmap;
|
||||
only_window_data_t focus;
|
||||
only_window_data_t kill;
|
||||
only_window_data_t withdraw;
|
||||
effect_bool_window_t minimize;
|
||||
effect_bool_window_t maximize;
|
||||
effect_bool_window_t fullscreen;
|
||||
configure_data_t configure;
|
||||
effect_change_border_color_t change_border_color;
|
||||
effect_window_list_t change_window_list;
|
||||
effect_window_list_t restack_windows;
|
||||
effect_bind_key_t bind_key;
|
||||
} as;
|
||||
} effect_t;
|
||||
#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);
|
||||
@@ -88,6 +24,9 @@ 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,
|
||||
@@ -100,3 +39,4 @@ void plan_push_change_border_color_effect(
|
||||
window_id_t window_id,
|
||||
const color_t *color
|
||||
);
|
||||
void plan_push_ungrab_pointer(plan_t *plan);
|
||||
|
||||
1041
src/core/policy.c
1041
src/core/policy.c
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user