Compare commits

...

79 Commits

Author SHA1 Message Date
c6ade0144c 添加 TODO.org 记录待开发功能及 bug 2025-09-30 21:41:39 +08:00
6d9ff81f78 事件适配器适配 XCB_MAPPING_NOTIFY 和 XCB_BUTTON_PRESS 事件 2025-09-30 21:40:54 +08:00
0626a4c512 调整 set_window_event_mask 函数中窗口默认事件掩码组织方式 2025-09-30 20:58:20 +08:00
4500a5adfd 调整 Makefile 和 CMake 配置 2025-09-30 20:51:57 +08:00
9315505085 manage_window 函数中释放窗口位置信息对应的内存 2025-09-30 16:23:16 +08:00
bec2162890 记录 property_notify 和 client_message 事件中的 xcb atom 名称 2025-09-30 16:22:44 +08:00
b40f499e7b 修复 get_window_tag 函数内存未释放 bug 2025-09-05 23:07:31 +08:00
9dfcf62c3c 修复 get_window_class_instance 函数中的内存释放错误 2025-09-03 00:44:48 +08:00
55c3fa1156 添加鼠标事件拦截 2025-09-02 22:42:47 +08:00
ac91ebbed3 记录光标在每个屏幕中最后停留的位置 2025-09-02 16:27:45 +08:00
6719ccca2b 添加自动运行功能 2025-09-02 01:04:50 +08:00
8cccf23a32 修复 cpu 占用率始终为 0 bug 2025-09-01 20:35:36 +08:00
0c361a6593 处理 EWMH 扩展的 _NET_ACTIVE_WINDOW 协议 2025-09-01 17:55:20 +08:00
645083d3a7 调整音量监听及清除逻辑 2025-09-01 17:26:43 +08:00
f31b18316b 退出时清理 xcb 按键资源避免内存泄露 2025-09-01 17:16:43 +08:00
88d9dfd714 保存图标像素数据指针以便退出时释放对应的内存 2025-09-01 15:31:45 +08:00
e58a43386d 修改 imlib2 重置图片数据方法 2025-09-01 15:30:50 +08:00
91950dc2f4 状态栏绘制添加图标和色彩设置 2025-09-01 00:56:45 +08:00
1b0ed3b9b8 分离修改窗口宽高/边框宽度和移动窗口操作 2025-08-31 00:12:41 +08:00
91c417a0e3 调整有多个 class 相同的 client 时 raise_or_run 的行为 2025-08-30 20:39:33 +08:00
e347dabc21 调整窗口管理器重启后恢复 client tag 信息时机让 rule 尽可能多的生效 2025-08-30 19:03:57 +08:00
fc543e6f9e 添加启动 st 的快捷键 2025-08-30 18:51:16 +08:00
ad1d413c62 添加快捷键将窗口移动到其他 tag 和 monitor 2025-08-30 18:47:44 +08:00
c5e2b4c081 添加状态栏和调整音量的快捷键 2025-08-27 06:57:37 +08:00
96629c3045 添加 raise or run 功能并添加对应快捷键 2025-08-27 04:27:12 +08:00
8d9becbf0f 添加切换当前屏幕快捷键 2025-08-27 03:45:53 +08:00
a0c8baa863 添加调整焦点快捷键 2025-08-26 16:45:47 +08:00
84b07ebcf4 绘制当前显示窗口的名称 2025-08-26 05:06:27 +08:00
2c49c9f179 绘制 layout 标志 2025-08-26 03:29:15 +08:00
9b77469ed2 调整未设置壁纸的背景 2025-08-26 02:33:27 +08:00
8321175915 修复堆叠顺序导致的全屏规则不生效 bug 2025-08-26 02:02:47 +08:00
850b63bf82 显示窗口函数添加是否显示子窗口选项 2025-08-26 01:47:27 +08:00
f225dc7011 处理 clientmessage 事件和全屏窗口 2025-08-26 01:13:44 +08:00
b28c9cd58b 添加关闭窗口快捷键 2025-08-25 22:09:35 +08:00
b645c4cf39 调整窗口堆叠顺序 2025-08-25 22:08:42 +08:00
9397e9e572 在 log 信息中 window 统一显示为 16 进制格式 2025-08-25 16:43:12 +08:00
a46522da3b 不记录鼠标移动事件信息(触发太频繁) 2025-08-25 12:03:43 +08:00
3dd2f80128 调整壁纸绘制方案 2025-08-25 11:57:23 +08:00
ebfed8245c 修改退出和重启的快捷键 2025-08-24 21:49:01 +08:00
a4f39d49f9 添加单独的函数来初始化 xcb 2025-08-24 21:46:02 +08:00
52cadbda34 简化检查是否有其他窗口管理器正在运行的实现 2025-08-24 21:45:24 +08:00
54fcb27457 退出程序时清理主循环 2025-08-24 21:23:47 +08:00
8f892633cc 处理 property_notify 事件 2025-08-24 12:44:15 +08:00
165dd0733c 处理 expose 事件 2025-08-24 11:44:56 +08:00
0039cd2c43 处理窗口销毁事件 2025-08-24 11:35:52 +08:00
bac73368e3 启动时扫描已有窗口并管理 2025-08-24 10:01:46 +08:00
7eea0ee2d3 处理焦点相关事件 2025-08-24 08:46:17 +08:00
25ef44e5f9 处理移动鼠标事件 2025-08-24 04:04:39 +08:00
8ffa546152 调整设置壁纸函数参数 2025-08-24 02:19:21 +08:00
90b4513fb5 调整最大化和全屏窗口大小 2025-08-24 02:18:09 +08:00
505e2a5bcb 修复窗口默认 tag 错误 bug 2025-08-24 02:18:05 +08:00
dfdc90e6c1 绘制 tag 的 client 标志 2025-08-23 16:01:03 +08:00
256b88ef91 添加切换 tag 的快捷键 2025-08-23 04:33:14 +08:00
bd052edffe 修改规则格式 2025-08-23 02:28:02 +08:00
2e75dbf75e 窗口消失时清理对应的 client 2025-08-23 00:28:35 +08:00
69c69200d8 添加布局算法并在新建窗口时应用规则 2025-08-22 23:01:25 +08:00
c3bcb7ddd2 显示窗口 2025-08-22 15:47:58 +08:00
2a7ab324be 添加快捷键设置 2025-08-22 15:14:37 +08:00
bb61bfcd2e 添加获取时间戳函数用于测试性能 2025-08-19 00:48:10 +08:00
36941aef25 事件适配器 2025-08-19 00:42:42 +08:00
4252604c52 清理 xcb 光标 2025-08-16 06:43:44 +08:00
8ac231fb4b 修复检测其他窗口管理器失效 bug 2025-08-16 05:34:45 +08:00
e87607f817 绘制 tag 标识 2025-08-16 05:32:19 +08:00
8472fda46a 创建每个屏幕对应的信息条窗口 2025-08-16 02:06:36 +08:00
be94f90c94 添加颜色配置解析 2025-08-15 11:12:19 +08:00
14b0366c65 添加解析 .Xresources 文件配置功能 2025-08-15 11:12:16 +08:00
482c707f2b 初始化配色方案 2025-08-15 10:21:06 +08:00
e78c847125 调整配置结构并添加 client 规则配置 2025-08-13 21:26:40 +08:00
72151c1458 根据配置为每个屏幕初始化 tags 属性 2025-08-13 15:53:55 +08:00
43ca95d2f4 开启 xcb 事件循环 2025-08-12 21:12:16 +08:00
943d566cef 释放 xcb 库应该释放的内存 2025-08-11 07:52:49 +08:00
366e4a8f91 添加动态设置壁纸条件 2025-08-11 07:25:00 +08:00
b2041333c1 修复壁纸数据错误 bug 2025-08-11 06:18:18 +08:00
714a69f258 添加壁纸动态切换功能 2025-08-11 05:00:07 +08:00
0b605e195c 去掉对 xcb/xinput.h 的依赖,因为未使用到 xinput 扩展 2025-08-10 11:08:51 +08:00
fc58edca2d 添加壁纸功能 2025-08-10 02:07:57 +08:00
751fce23c1 多屏幕环境模拟 2025-08-09 17:14:37 +08:00
4db9c4c078 调整构建配置 2025-08-09 01:16:31 +08:00
16204bd638 解析壁纸配置 2025-08-08 22:53:33 +08:00
35 changed files with 4649 additions and 123 deletions

View File

@@ -21,14 +21,21 @@ clean:
run:
-$(shell Xephyr :3 -screen 1920x1080 &)
run_multiple_screen:
-$(shell Xephyr :3 -screen 1920x1080 -screen 1920x1080 +xinerama &)
wm: $(TARGET_NAME)
DISPLAY=:3 $(TARGET)
debug: $(TARGET_NAME)
DISPLAY=:3 valgrind --leak-check=full $(TARGET)
prepare:
@cmake -S $(SRC_DIR) -B $(BUILD_DIR)
@cp $(BUILD_DIR)/compile_commands.json $(MAKEFILE_DIR)
build: prepare
${RM} -f $(TARGET)
@cmake --build $(BUILD_DIR)
@cp $(BUILD_DIR)/$(TARGET_NAME) $(TARGET)

21
TODO.org Normal file
View File

@@ -0,0 +1,21 @@
#+title: zswm 代办事项
* TODO 调整光标在每个 monitor 中最后停留位置的逻辑
因为在有些程序(如 Emacs 和 Firefox中移动鼠标不会触发 motion_notify 事件,
因此需调整记录每个 monitor 中光标最后停留位置的逻辑,不能将 motion_notify 事件作为唯一依赖。
可在切换屏幕时主动查询当前光标,将其位置记录到切换前的 monitor 中。
* TODO 处理鼠标点击 bar_window 事件
* TODO 处理鼠标移动窗口事件
* TODO 处理鼠标调整窗口大小事件
* TODO 调整 stack 顺序和焦点逻辑,总感觉现在的情况有 bug
* TODO 调整 stack 逻辑,让 bar_window 在最底部
* TODO 添加切换布局算法功能
* TODO 添加系统托盘显示

View File

@@ -3,46 +3,79 @@ cmake_minimum_required(VERSION 3.24)
set(APP_NAME "zswm")
set(SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR})
add_compile_definitions(LOG_FILE="$ENV{HOME}/zswm-log.txt")
# add_compile_definitions(RELEASE)
# generate compile_commands.json for clangd
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
# compile flags
add_compile_options(-g)
add_compile_options(-Wall)
# add_link_options(-fsanitize=address)
project(${APP_NAME}
VERSION 0.0.1
LANGUAGES C
)
add_executable(${APP_NAME} main.c utils.c)
add_executable(${APP_NAME} main.c utils.c layout.c status.c)
# use C17
set_property(TARGET ${APP_NAME} PROPERTY C_STANDARD 17)
target_include_directories(${APP_NAME} PRIVATE "${SOURCE_DIR}/include")
find_package(PkgConfig REQUIRED)
pkg_check_modules(GLIB REQUIRED glib-2.0)
pkg_check_modules(XCB REQUIRED
xcb
xcb-cursor
xcb-ewmh
xcb-icccm
xcb-image
xcb-keysyms
xcb-util
xcb-xfixes
xcb-xinerama
xcb-xrm
)
target_include_directories(${APP_NAME} SYSTEM PUBLIC ${XCB_INCLUDE_DIRS})
pkg_check_modules(RENDERER REQUIRED cairo pango pangocairo imlib2)
pkg_check_modules(CAIRO_XCB REQUIRED cairo-xcb)
pkg_check_modules(PULSE REQUIRED libpulse-mainloop-glib)
add_subdirectory(xcb)
target_link_libraries(${APP_NAME} ${LIB_XCB_NAME})
add_subdirectory(renderer)
# status icons(all from https://remixicon.com/)
set(NET_UP_ICON "${SOURCE_DIR}/resources/icons/corner-right-up-line.png")
set(NET_DOWN_ICON "${SOURCE_DIR}/resources/icons/corner-left-down-line.png")
set(SPEAKER_ICON "${SOURCE_DIR}/resources/icons/volume-up-fill.png")
set(MEM_ICON "${SOURCE_DIR}/resources/icons/ram-line.png")
set(CPU_ICON "${SOURCE_DIR}/resources/icons/cpu-line.png")
set(CLOCK_ICON "${SOURCE_DIR}/resources/icons/time-line.png")
configure_file(
"${CMAKE_CURRENT_SOURCE_DIR}/app.h.in"
"${CMAKE_BINARY_DIR}/app.h"
@ONLY
)
target_include_directories(${APP_NAME} PRIVATE ${CMAKE_BINARY_DIR})
target_include_directories(${APP_NAME} SYSTEM
PRIVATE ${GLIB_INCLUDE_DIRS}
PRIVATE ${CAIRO_XCB_INCLUDE_DIRS}
PRIVATE ${PULSE_INCLUDE_DIRS}
)
target_include_directories(${APP_NAME}
PRIVATE "${SOURCE_DIR}/include"
PRIVATE "${SOURCE_DIR}"
PRIVATE ${CMAKE_BINARY_DIR}
)
target_link_libraries(${APP_NAME}
PRIVATE m
PRIVATE ${GLIB_LIBRARIES}
PRIVATE ${CAIRO_XCB_LIBRARIES}
PRIVATE ${PULSE_LIBRARIES}
PRIVATE ${LIB_XCB_NAME}
PRIVATE ${LIB_RENDERER_NAME}
)

15
src/action.h Normal file
View File

@@ -0,0 +1,15 @@
#pragma once
#include "types.h"
void spawn(const user_action_arg_t *arg);
void quit(const user_action_arg_t *arg);
void select_tag(const user_action_arg_t *arg);
void kill_client(const user_action_arg_t *arg);
void focus_stack(const user_action_arg_t *arg);
void focus_monitor(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 move_to_monitor(const user_action_arg_t *arg);
void move_to_tag(const user_action_arg_t *arg);

View File

@@ -1,3 +1,10 @@
#pragma once
#define APP_NAME "@APP_NAME@"
#define NET_UP_ICON "@NET_UP_ICON@"
#define NET_DOWN_ICON "@NET_DOWN_ICON@"
#define SPEAKER_ICON "@SPEAKER_ICON@"
#define MEM_ICON "@MEM_ICON@"
#define CPU_ICON "@CPU_ICON@"
#define CLOCK_ICON "@CLOCK_ICON@"

191
src/config.h Normal file
View File

@@ -0,0 +1,191 @@
#pragma once
#include <X11/XF86keysym.h>
#include <X11/keysym.h>
#include <stdbool.h>
#include <stdint.h>
#include <xcb/xproto.h>
#include "action.h"
#include "layout.h"
#include "types.h"
/* 壁纸列表,支持通配符和 shell 环境变量拼接 */
const char *wallpapers[] = {};
/* 壁纸切换间隔单位0 表示不切换 */
const uint32_t wallpaper_interval = 0;
/**
* @brief 屏幕 tags 设置
* @description 每个屏幕的 tags 配置为字符串数组,以 NULL 结束,如
* {"1", "2", "3", "4", "5", "6", NULL}
*
* 每一项配置里面的数组个数表示对应的屏幕个数,并最后以 NULL 结束,
*
* 如一个屏幕应该配置为 {{"1", "2", "3", "4", NULL}, NULL}
* 两个屏幕应该配置为 {{"1", "2", "3", NULL}, {"1", "2", NULL}, NULL},以此类推
*/
const char *const *const *const monitor_tags[] = {
(const char *const *const[]){
(const char *const[]){"1", "2", "3", "4", "5", "6", NULL},
NULL,
},
(const char *const *const[]){
(const char *const[]){"1", "2", "3", NULL},
(const char *const[]){"1", "2", "3", "4", "5", NULL},
NULL,
},
};
/**
* @brief 默认屏幕 tags 设置
* @description 若屏幕个数与 monitor_tags 中设置的所有配置项都不符,
* 则所有屏幕的 tags 都使用这个默认配置
*
* 格式为字符串数组,以 NULL 标志结束,如 {"1", "2", "3", NULL}
*/
const char *const default_monitor_tags[] = {
"1", "2", "3", "4", "5", "6", "7", "8", "9", NULL,
};
const layout_t layouts[] = {
{"[]=", tile},
{"[M]", monocle},
{"><>", NULL},
};
/* 默认 tags 规则 */
const rule_tag_t browser_tag[] = {
{.monitor_index = 0, .tags = 1 << 1},
{0},
};
const rule_tag_t mpv_tag[] = {
{.monitor_amount = 1, .monitor_index = 0, .tags = 1 << 3},
{.monitor_amount = 2, .monitor_index = 1, .tags = 1 << 2},
{0},
};
const rule_tag_t emacs_tag[] = {
{.monitor_amount = 1, .monitor_index = 0, .tags = 1 << 2},
{.monitor_amount = 2, .monitor_index = 1, .tags = 1 << 1},
{0},
};
/* 新建窗口规则 */
const rule_t rules[] = {
{.class = "firefox", .action = {.maximize = true}, .tag = browser_tag},
{.class = "Google-chrome", .action = {.maximize = true}, .tag = browser_tag},
{.class = "mpv", .action = {.fullscreen = true}, .tag = mpv_tag},
{
.class = "Emacs",
.action = {.switch_to_tag = true, .maximize = true},
.tag = emacs_tag,
},
};
#define MODIFIER_ANY XCB_MOD_MASK_ANY
#define SUPER XCB_MOD_MASK_4
#define ALT XCB_MOD_MASK_1
#define CTRL XCB_MOD_MASK_CONTROL
#define SHIFT XCB_MOD_MASK_SHIFT
#define BUTTON_ANY XCB_BUTTON_INDEX_ANY
#define BUTTON_LEFT XCB_BUTTON_INDEX_1
#define BUTTON_RIGHT XCB_BUTTON_INDEX_2
#define BUTTON_MIDDLE XCB_BUTTON_INDEX_3
const char launcher[] =
"rofi -show combi -modes window,drun,run,ssh,combi,windowcd";
const keybinding_t keys[] = {
{SUPER, XK_p, spawn, {.ptr = launcher}},
{SUPER, XK_Return, spawn, {.ptr = "st"}},
{SUPER | SHIFT, XK_q, quit, {.b = false}},
{SUPER | CTRL, XK_r, quit, {.b = true}},
{SUPER | SHIFT, XK_c, kill_client, {0}},
{ALT, XK_j, focus_stack, {.b = true}},
{ALT, XK_k, focus_stack, {.b = false}},
{SUPER | SHIFT, XK_j, focus_monitor, {.b = true}},
{SUPER | SHIFT, XK_k, focus_monitor, {.b = false}},
{SUPER, XK_o, move_to_monitor, {.b = true}},
{
SUPER,
XK_a,
raise_or_run,
{.ptr = (char *[]){"Google-chrome", "google-chrome-stable"}},
},
{
SUPER,
XK_e,
raise_or_run,
{.ptr = (char *[]){"Emacs", "emacsclient -a '' -c -n"}},
},
{ALT | CTRL, XK_r, raise_or_run, {.ptr = (char *[]){"St", "st"}}},
{SUPER, XK_q, raise_or_run, {.ptr = (char *[]){"firefox", "firefox-bin"}}},
{SUPER, XK_Up, change_volume, {.i = 1}},
{SUPER, XK_Down, change_volume, {.i = -1}},
{SUPER | SHIFT, XK_m, toggle_mute, {0}},
{0, XF86XK_AudioRaiseVolume, change_volume, {.i = 1}},
{0, XF86XK_AudioLowerVolume, change_volume, {.i = -1}},
{0, XF86XK_AudioMute, toggle_mute, {0}},
{SUPER, XK_1, select_tag, {.ui = 1 << 0}},
{SUPER, XK_2, select_tag, {.ui = 1 << 1}},
{SUPER, XK_3, select_tag, {.ui = 1 << 2}},
{SUPER, XK_4, select_tag, {.ui = 1 << 3}},
{SUPER, XK_5, select_tag, {.ui = 1 << 4}},
{SUPER, XK_6, select_tag, {.ui = 1 << 5}},
{SUPER, XK_7, select_tag, {.ui = 1 << 6}},
{SUPER, XK_8, select_tag, {.ui = 1 << 7}},
{SUPER, XK_9, select_tag, {.ui = 1 << 8}},
{SUPER | SHIFT, XK_1, move_to_tag, {.ui = 1 << 0}},
{SUPER | SHIFT, XK_2, move_to_tag, {.ui = 1 << 1}},
{SUPER | SHIFT, XK_3, move_to_tag, {.ui = 1 << 2}},
{SUPER | SHIFT, XK_4, move_to_tag, {.ui = 1 << 3}},
{SUPER | SHIFT, XK_5, move_to_tag, {.ui = 1 << 4}},
{SUPER | SHIFT, XK_6, move_to_tag, {.ui = 1 << 5}},
{SUPER | SHIFT, XK_7, move_to_tag, {.ui = 1 << 6}},
{SUPER | SHIFT, XK_8, move_to_tag, {.ui = 1 << 7}},
{SUPER | SHIFT, XK_9, move_to_tag, {.ui = 1 << 8}},
};
const button_t buttons[] = {
{click_tag, 0, BUTTON_LEFT, select_tag, {0}},
{click_tag, SUPER, BUTTON_LEFT, move_to_tag, {0}},
};
const char *const bar_bg = "#222222";
const char *const tag_bg = "#222222";
const char *const active_tag_bg = "#005577";
const char *const tag_color = "#bbbbbb";
const char *const active_tag_color = "#eeeeee";
const char *const layout_color = "#bbbbbb";
const char *const client_name_bg = "#222222";
const char *const client_name_color = "#bbbbbb";
const char *const active_client_name_bg = "#005577";
const char *const active_client_name_color = "#eeeeee";
const char *const status_net_recv_color = "#87af5f";
const char *const status_net_send_color = "#e54c62";
const char *const status_volume_color = "#7493d2";
const char *const status_memory_color = "#e0da37";
const char *const status_cpu_color = "#e33a6e";
const char *const status_datetime_color = "#7788af";
const char *const border_color = "#444444";
const char *const active_border_color = "#005577";
const char *const default_font_family = "sans-serif, monospace";
const uint32_t default_font_size = 10;
const uint32_t dpi_fallback = 96;
const uint32_t refresh_rate = 60;
const uint32_t bar_y_padding = 1;
const uint32_t tag_x_padding = 10;
const uint32_t layout_symbol_x_padding = 10;
const uint32_t client_name_x_padding = 10;
const uint32_t status_x_padding = 4;
const uint32_t status_icon_padding = 2;
const uint32_t border_width = 1;
const char *autostart_list[] = {
"gentoo-pipewire-launcher restart",
"picom -b --backend xrender",
};

178
src/include/event-adapter.h Normal file
View File

@@ -0,0 +1,178 @@
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include "types.h"
typedef enum event_type_t {
EVENT_TYPE_MAPPING_NOTIFY,
EVENT_TYPE_KEY_PRESS,
EVENT_TYPE_BUTTON_PRESS,
EVENT_TYPE_CONFIGURE_REQUEST,
EVENT_TYPE_CONFIGURE_NOTIFY,
EVENT_TYPE_MAP_REQUEST,
EVENT_TYPE_UNMAP_NOTIFY,
EVENT_TYPE_DESTROY_NOTIFY,
EVENT_TYPE_EXPOSE,
EVENT_TYPE_MOTION_NOTIFY,
EVENT_TYPE_ENTER_NOTIFY,
EVENT_TYPE_FOCUS_IN,
EVENT_TYPE_PROPERTY_NOTIFY,
EVENT_TYPE_CLIENT_MESSAGE,
} event_type_t;
typedef enum notify_mode_t {
notify_mode_normal = 0,
notify_mode_grab = 1,
notify_mode_ungrab = 2,
notify_mode_while_grabbed = 3
} notify_mode_t;
typedef enum notify_detail_t {
notify_detail_ancestor = 0,
notify_detail_virtual = 1,
notify_detail_inferior = 2,
notify_detail_nonlinear = 3,
notify_detail_nonlinear_virtual = 4,
notify_detail_pointer = 5,
notify_detail_pointer_root = 6,
notify_detail_none = 7,
} notify_detail_t;
typedef enum mapping_t {
MAPPING_MODIFIER = 0,
MAPPING_KEYBOARD = 1,
MAPPING_POINTER = 2
} mapping_t;
typedef struct mapping_notify_event_t {
event_type_t type;
mapping_t request;
} mapping_notify_event_t;
typedef struct key_press_event_t {
event_type_t type;
uint32_t key_symbol;
uint32_t modifiers;
} key_press_event_t;
typedef struct button_press_event_t {
event_type_t type;
xcb_window_t window;
xcb_window_t root;
int32_t x, y, root_x, root_y;
uint32_t time; /* 时间戳单位ms */
} button_press_event_t;
typedef struct configure_request_event_t {
event_type_t type;
xcb_window_t window;
xcb_window_t parent;
xcb_window_t sibling;
int32_t x, y;
uint32_t width, height, border_width;
uint32_t value_mask;
uint8_t stack_mode;
} configure_request_event_t;
typedef struct configure_notify_event_t {
event_type_t type;
} configure_notify_event_t;
typedef struct map_request_event_t {
event_type_t type;
xcb_window_t window;
xcb_window_t parent;
} map_request_event_t;
typedef struct unmap_notify_event_t {
event_type_t type;
xcb_window_t window;
bool send_event;
} unmap_notify_event_t;
typedef struct destroy_notify_event_t {
event_type_t type;
xcb_window_t window;
} destroy_notify_event_t;
typedef struct expose_event_t {
event_type_t type;
xcb_window_t window;
uint32_t count;
} expose_event_t;
typedef struct motion_notify_event_t {
event_type_t type;
xcb_window_t root;
int32_t root_x;
int32_t root_y;
xcb_window_t window;
int32_t x;
int32_t y;
uint32_t time; /* 时间戳单位ms */
} motion_notify_event_t;
typedef struct enter_notify_event_t {
event_type_t type;
xcb_window_t window;
notify_mode_t mode;
notify_detail_t detail;
} enter_notify_event_t;
typedef struct focus_in_event_t {
event_type_t type;
xcb_window_t window;
} focus_in_event_t;
typedef enum property_state_t {
property_new_value = 0,
property_delete = 1
} property_state_t;
typedef struct property_notify_event_t {
event_type_t type;
xcb_window_t window;
uint32_t xcb_atom;
property_state_t state;
} property_notify_event_t;
typedef union client_message_data_t {
uint8_t data8[20];
uint16_t data16[10];
uint32_t data32[5];
} client_message_data_t;
typedef struct client_message_event_t {
event_type_t type;
xcb_window_t window;
uint32_t message_type;
client_message_data_t data;
uint8_t format;
} client_message_event_t;
typedef union generic_event_t {
event_type_t type;
mapping_notify_event_t mapping_notify;
key_press_event_t key_press;
button_press_event_t button_press;
configure_request_event_t configure_request;
configure_notify_event_t configure_notify;
map_request_event_t map_request;
unmap_notify_event_t unmap_notify;
destroy_notify_event_t destroy_notify;
expose_event_t expose;
motion_notify_event_t motiion_notify;
enter_notify_event_t enter_notify;
focus_in_event_t focus_in;
property_notify_event_t property_notify;
client_message_event_t client_message;
} generic_event_t;
typedef void (*event_handler)(generic_event_t *event, void *user_data);
typedef struct event_adapter_t event_adapter_t;
event_adapter_t *create_event_adapter(void);
void destroy_event_adapter(event_adapter_t *adapter);
void event_adapter_set_handler(event_adapter_t *adapter, event_handler handler,
void *user_data);
void event_adapter_poll(event_adapter_t *adapter);

21
src/include/renderer.h Normal file
View File

@@ -0,0 +1,21 @@
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include "types.h"
uint32_t *generate_wallpaper(const wallpaper_config_t *wallpaper_config,
const monitor_t *monitors, const uint32_t index,
const uint32_t screen_width,
const uint32_t screen_height);
uint32_t *generate_icon_pixels(const char *image_path, uint32_t width,
uint32_t height, uint32_t padding);
uint32_t get_font_height(const char *font_family, const uint32_t font_size,
const uint32_t dpi);
void draw_bar(monitor_t *monitor, bool is_current_monitor, status_t *status,
icons_t *icons, color_set_t *color_set, uint32_t height,
uint32_t tag_x_padding, uint32_t layout_x_padding,
uint32_t client_name_x_padding, uint32_t status_x_padding);
void clean_pango_context(void);

202
src/include/types.h Normal file
View File

@@ -0,0 +1,202 @@
#pragma once
#include <stdbool.h>
#include <stdint.h>
typedef struct monitor_t monitor_t;
typedef struct client_t client_t;
typedef struct layout_t {
const char *symbol;
void (*arrange)(monitor_t *monitor);
} layout_t;
/* 避免引入 xcb 头文件 */
typedef uint32_t xcb_window_t;
struct monitor_t {
uint32_t index;
int32_t monitor_x, monitor_y, window_x, window_y;
uint32_t monitor_width, monitor_height, window_width, window_height;
uint32_t selected_tags;
char **tags;
/* 为了避免引入 cairo 相关头文件,这里用 void *
* 指针代替实际的指针类型,使用时强转为相应指针类型使用即可 */
void *cr; /* cairo_t */
const layout_t *layout;
monitor_t *next;
client_t *clients;
client_t *clients_stack;
client_t *selected_client;
int32_t pointer_x; /* 记录光标 x 坐标位置 */
int32_t pointer_y; /* 记录光标 y 坐标位置 */
xcb_window_t bar_window;
};
struct client_t {
char name[256];
int32_t x, y, old_x, old_y;
uint32_t width, height, border_width, old_width, old_height, old_border_width;
uint32_t tags;
bool fullscreen;
bool floating;
bool maximize;
monitor_t *monitor;
client_t *next;
client_t *stack_next;
xcb_window_t window;
};
#define CLIENT_VISIBLE(C) ((C)->tags & (C)->monitor->selected_tags)
typedef struct wallpaper_config_t {
uint32_t count;
char **path_list;
} wallpaper_config_t;
/* 窗口默认屏幕和 tags */
typedef struct rule_tag_t {
const uint32_t monitor_amount;
uint32_t monitor_index;
uint32_t tags;
} rule_tag_t;
typedef struct rule_action_t {
bool switch_to_tag;
bool fullscreen;
bool maximize;
bool floating;
} rule_action_t;
/* 新建窗口规则 */
typedef struct rule_t {
/* 规则匹配字段组 */
const char *class;
const char *instance;
const char *title;
const rule_tag_t *tag; /* 默认所在屏幕和 tags 设置,以 {0} 结尾 */
const rule_action_t action;
} rule_t;
typedef union user_action_arg_t {
bool b;
int32_t i;
uint32_t ui;
float f;
const void *ptr;
} user_action_arg_t;
typedef struct keybinding_t {
uint32_t modifiers;
uint32_t key_symbol;
void (*func)(const user_action_arg_t *);
const user_action_arg_t arg;
} keybinding_t;
typedef struct color_t {
uint32_t rgba;
uint32_t argb;
double red;
double green;
double blue;
double alpha;
} color_t;
typedef struct color_set_t {
color_t bar_bg;
color_t tag_bg;
color_t active_tag_bg;
color_t tag_color;
color_t active_tag_color;
color_t layout_color;
color_t client_name_bg;
color_t client_name_color;
color_t active_client_name_bg;
color_t active_client_name_color;
color_t status_net_recv_color;
color_t status_net_send_color;
color_t status_volume_color;
color_t status_memory_color;
color_t status_cpu_color;
color_t status_datetime_color;
color_t border_color;
color_t active_border_color;
} color_set_t;
typedef struct {
char mem_used_text[32];
char swap_used_text[32];
float mem_percent;
float swap_percent;
uint64_t mem_used;
uint64_t swap_used;
uint64_t mem_total;
uint64_t mem_free;
uint64_t buffers;
uint64_t cached;
uint64_t swap_total;
uint64_t swap_free;
uint64_t s_reclaimable;
} memory_usage_t;
typedef struct {
uint64_t rx_bytes;
uint64_t tx_bytes;
char up[128];
char down[128];
} net_speed_t;
typedef struct {
uint32_t index;
int mute;
int avg_volume; /* 各通道平均音量(百分比) */
int volumes[5]; /* 各通道音量(百分比) */
char device[512]; /* 音频设备名称 */
} pulse_t;
typedef struct {
pulse_t pulse;
net_speed_t net_speed;
memory_usage_t mem_usage;
double cpu_usage_percent;
char time[256];
} status_t;
typedef struct icon_res_t {
/* 图像像素数据RGBA 顺序) */
uint32_t *pixels;
/* cairo_surface_t * 类型,为避免引入 cairo 头文件使用 void * 类型代替 */
void *surface;
} icon_res_t;
typedef struct icons_t {
icon_res_t net_down;
icon_res_t net_up;
icon_res_t volume;
icon_res_t memory;
icon_res_t cpu;
icon_res_t clock;
} icons_t;
typedef enum click_area_t {
click_tag,
click_layout_symbol,
click_window_title,
click_status,
click_client_window,
click_root_win,
} click_area_t;
typedef struct button_t {
click_area_t click_area;
uint16_t modifiers;
uint8_t button;
void (*func)(const user_action_arg_t *);
const user_action_arg_t arg;
} button_t;

View File

@@ -1,8 +1,61 @@
#pragma once
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <sys/time.h>
#include "types.h"
#define LENGTH(X) (sizeof(X) / sizeof(X)[0])
#ifndef MIN
#define MIN(a, b) (((a) < (b)) ? (a) : (b))
#endif
#ifndef MAX
#define MAX(a, b) (((a) > (b)) ? (a) : (b))
#endif
double get_time();
void die(const char *format, ...);
void *ecalloc(size_t number_of_member, size_t member_size);
bool file_exist(const char *path);
#if defined(RELEASE)
#define logger(fmt, ...)
#elif defined(LOG_FILE)
void logger(const char *fmt, ...);
#else
#define logger(fmt, ...) printf(fmt, ##__VA_ARGS__)
#endif
/**
* @brief 将 16 进制的颜色配置转换成 uint32 格式的 rgba数据
*
* @param hex 16 进制的颜色表示,支持的格式有
* RGB/RGBA/RRGGBB/RRGGBBAA/#RGB/#RGBA/#RRGGBB/#RRGGBBAA
* @param rgba 返回数据的指针,解析结果会放到这个指针指向的内存中
* @return 如果 hex 是一个能正常解析的颜色,返回 true ,否则返回 false
*/
bool hex_color_to_rgba(const char *hex, uint32_t *rgba);
/**
* @brief 将 rgba 通道顺序的颜色转换成 argb 顺序
* @description xcb 库用到的颜色需要是 argb 通道顺序
*/
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
*/
void extract_color_channel(const uint32_t rgba, double *red, double *green,
double *blue, double *alpha);
monitor_t *rect_to_monitor(monitor_t *monitors, int32_t x, int32_t y,
uint32_t width, uint32_t height);

View File

@@ -1,10 +1,91 @@
#pragma once
#include <X11/cursorfont.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <xcb/xproto.h>
#include "types.h"
#define WINDOW_NONE XCB_WINDOW_NONE
bool has_other_wm_running(void);
void setup_xcb(void);
int get_xcb_connection_fd(void);
void flush_xcb_connection(void);
typedef enum cursor_t {
cursor_normal = XC_left_ptr,
cursor_resize = XC_bottom_right_corner,
cursor_move = XC_fleur,
} cursor_t;
void change_window_cursor(xcb_window_t window, cursor_t cursor);
xcb_window_t get_root_window(void);
bool get_window_visible(xcb_window_t window);
xcb_window_t get_transient_window_for(xcb_window_t window);
typedef struct window_class_instance_t {
char class[128];
char instance[128];
} window_class_instance_t;
void get_window_class_instance(xcb_window_t window,
window_class_instance_t *class_instance);
typedef struct window_geometry_t {
int32_t x, y;
uint32_t width, height, border_width;
} window_geometry_t;
/**
* @brief 获取窗口的几何信息
* @returns 返回几何信息指针,使用完后记得使用 free 释放内存
*/
window_geometry_t *get_window_geometry(xcb_window_t window);
void map_window(xcb_window_t window, bool map_sub_window);
/**
* @brief 获取窗口名
* @returns 返回窗口名字符串指针,使用完后记得使用 free 释放内存
*/
char *get_window_name(xcb_window_t window);
void set_window_event_mask(xcb_window_t window, bool clear);
void change_window_border_color(xcb_window_t window, uint32_t argb);
void change_window_border_width(xcb_window_t window, uint32_t border_width);
void focus_window(xcb_window_t window);
/**
* @brief 将 window 置于堆叠顺序的顶部
*/
void raise_window(xcb_window_t window);
/**
* @brief 在堆叠顺序中将 window 置于 sibling 之上
*/
void place_window_above_sibling(xcb_window_t window, xcb_window_t sibling);
/**
* @brief 在堆叠顺序中将 window 置于 sibling 之下
*/
void place_window_below_sibling(xcb_window_t window, xcb_window_t sibling);
/**
* @brief 关闭窗口并清理资源
*/
void kill_window(xcb_window_t window);
void set_window_fullscreen_state(xcb_window_t window, bool fullscreen);
typedef struct bar_cairo_params_t {
xcb_connection_t *conn;
xcb_window_t window;
xcb_visualtype_t *visual;
} bar_cairo_params_t;
/**
* @brief 创建每个屏幕的 bar 窗口
* @returns 返回创建 cairo surface 需要的参数,使用完记得使用 free 释放内存
*/
bar_cairo_params_t *create_bar_window(int16_t x, int16_t y, uint16_t width,
uint16_t height, uint32_t background_argb,
cursor_t cursor);
void destroy_window(xcb_window_t window);
void configure_window(xcb_window_t window, int32_t x, int32_t y, uint32_t width,
uint32_t height, uint32_t border_width);
void move_window(xcb_window_t window, int32_t x, int32_t y);
void resize_window(xcb_window_t window, uint32_t width, uint32_t height);
typedef struct monitor_rect_t monitor_rect_t;
struct monitor_rect_t {
@@ -16,10 +97,69 @@ struct monitor_rect_t {
};
monitor_rect_t *detect_monitor_rect(void);
typedef struct rect_size_t {
uint32_t width;
uint32_t height;
} rect_size_t;
rect_size_t *get_screen_size(void);
bar_cairo_params_t *prepare_wallpaper_surface_params(void);
void refresh_root_window(void);
void set_wallpaper(uint32_t *wallpaper_data);
typedef struct window_list_t {
uint32_t count;
xcb_window_t *list;
xcb_window_t *normal_list;
xcb_window_t *transient_list;
uint32_t normal_count;
uint32_t transient_count;
} window_list_t;
window_list_t *scan_window_list(void);
void free_window_list(window_list_t *window_list);
void clean_xcb(void);
void grab_keybindings(const keybinding_t *keys, const size_t length);
bool get_xresource_uint32(const char *name, uint32_t *value);
void get_xresource_string(const char *name, char **value, const char *fallback);
void clean_xresource(void);
typedef enum atom_t {
atom_wm_protocols,
atom_wm_delete,
atom_wm_take_focus,
atom_net_active_window,
atom_net_wm_name,
atom_net_wm_state,
atom_net_wm_fullscreen,
atom__xrootpmap_id,
atom_esetroot_pmap_id,
atom_zswm_client_info, /* 自定义属性,用于存储所在 monitor 和 tags */
} atom_t;
xcb_atom_t get_xcb_atom(atom_t atom);
char *get_xcb_atom_name(xcb_atom_t atom);
bool send_event(xcb_window_t window, atom_t atom);
typedef enum wm_state_t {
wm_state_withdrawn = 0,
wm_state_normal = 1,
wm_state_iconic = 3
} wm_state_t;
int32_t get_window_state(xcb_window_t window);
void set_window_state(xcb_window_t window, wm_state_t state);
void set_window_list(xcb_window_t *list, uint32_t length);
typedef struct client_info_t {
uint32_t tags;
uint32_t monitor_index;
} client_tag_info_t;
bool get_window_tag(xcb_window_t window, client_tag_info_t *client_info);
void set_window_tag(xcb_window_t window, client_tag_info_t *client_info);
void remove_window_tag(xcb_window_t window);
bool get_pointer_position(int32_t *x, int32_t *y);
void set_pointer_position(int32_t x, int32_t y);
/**
* @brief 非阻塞检查特定事件掩码(模拟 XCheckMaskEvent
*/
bool xcb_check_mask_event(xcb_event_mask_t event_mask);
void ungrab_any_button(xcb_window_t window);
void grab_button(xcb_window_t window, uint8_t button, uint16_t modifiers);

109
src/layout.c Normal file
View File

@@ -0,0 +1,109 @@
#include "layout.h"
#include <math.h>
#include <stdbool.h>
#include <unistd.h>
#include "types.h"
client_t *next_visible_client(client_t *c) {
while (c &&
(c->floating || c->fullscreen || c->maximize || !CLIENT_VISIBLE(c))) {
c = c->next;
}
return c;
}
void maximize_client(client_t *c) {
monitor_t *m = c->monitor;
c->x = m->window_x;
c->y = m->window_y;
c->width = m->window_width - 2 * c->border_width;
c->height = m->window_height - 2 * c->border_width;
}
/**
* @brief 设置全屏窗口的几何属性
*/
void fullscreen_client(client_t *c) {
if (c->floating) {
c->old_x = c->x;
c->old_y = c->y;
c->old_width = c->width;
c->old_height = c->height;
}
monitor_t *m = c->monitor;
c->x = m->monitor_x;
c->y = m->monitor_y;
c->width = m->monitor_width;
c->height = m->monitor_height;
c->old_border_width = c->border_width;
c->border_width = 0;
}
void monocle(monitor_t *monitor) {
client_t *c = next_visible_client(monitor->clients);
for (; c; c = next_visible_client(c->next)) maximize_client(c);
}
void tile(monitor_t *monitor) {
uint32_t n;
client_t *c = NULL;
for (n = 0, c = next_visible_client(monitor->clients); c;
c = next_visible_client(c->next), ++n);
if (n == 0) {
return;
}
uint32_t columns = (uint32_t)floor(sqrt(n));
if (columns * (columns + 1) <= n) {
columns++;
}
uint32_t rows_in_other_cols = n / columns;
uint32_t rows_in_main_col;
while ((rows_in_main_col = n - (columns - 1) * rows_in_other_cols) >
rows_in_other_cols) {
rows_in_other_cols++;
}
uint32_t width_avg = monitor->window_width / columns;
uint32_t width_for_main_col =
monitor->window_width - (columns - 1) * width_avg;
uint32_t width_for_other_cols = width_avg;
uint32_t i = 0, row = 0, col = 0, row_count, width, height;
int32_t y = monitor->window_y, x = monitor->window_x;
for (c = next_visible_client(monitor->clients); c;
c = next_visible_client(c->next), i++) {
if (i < rows_in_main_col) {
row = i;
width = width_for_main_col;
row_count = rows_in_main_col;
} else {
width = width_for_other_cols;
row_count = rows_in_other_cols;
if (((i - rows_in_main_col) % row_count) == 0) {
col++;
row = 0;
x += col == 1 ? width_for_main_col : width_for_other_cols;
y = monitor->window_y;
}
}
uint32_t h_avg = monitor->window_height / row_count;
uint32_t h_main = monitor->window_height - h_avg * (row_count - 1);
height = row == 0 ? h_main : h_avg;
c->x = x;
c->y = y;
c->width = width - 2 * c->border_width;
c->height = height - 2 * c->border_width;
y += height;
}
}
void calc_clients_geometry_of_monitor(monitor_t *monitor) {
if (monitor && monitor->layout && monitor->layout->arrange) {
monitor->layout->arrange(monitor);
}
}

12
src/layout.h Normal file
View File

@@ -0,0 +1,12 @@
#pragma once
#include "types.h"
/**
* 获取下一个需要被布局算法自动管理的可见 client
*/
client_t *next_visible_client(client_t *client);
void maximize_client(client_t *client);
void fullscreen_client(client_t *client);
void monocle(monitor_t *monitor);
void tile(monitor_t *monitor);

1568
src/main.c

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,14 @@
set(LIB_RENDERER "zswm-renderer")
set(LIB_RENDERER_NAME ${LIB_RENDERER} PARENT_SCOPE)
add_library(${LIB_RENDERER} STATIC image.c text.c)
target_include_directories(${LIB_RENDERER} SYSTEM
PRIVATE ${RENDERER_INCLUDE_DIRS}
)
target_include_directories(${LIB_RENDERER}
PRIVATE "${CMAKE_SOURCE_DIR}/include"
)
target_link_libraries(${LIB_RENDERER}
PRIVATE ${RENDERER_LIBRARIES}
)

135
src/renderer/image.c Normal file
View File

@@ -0,0 +1,135 @@
#include <Imlib2.h>
#include <cairo.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "renderer.h"
#include "types.h"
#include "utils.h"
/**
* @brief 生成需要由 xcb 绘制的壁纸数据
* @returns 壁纸像素数据( RGBA 顺序),使用后记得使用 free 释放
*/
uint32_t *generate_wallpaper(const wallpaper_config_t *wallpaper_config,
const monitor_t *monitors, const uint32_t index,
const uint32_t screen_width,
const uint32_t screen_height) {
if (!wallpaper_config || !monitors) return NULL;
Imlib_Image final_image = imlib_create_image(screen_width, screen_height);
if (!final_image) return NULL;
imlib_context_set_image(final_image);
imlib_image_set_has_alpha(true);
/* 重置像素数据 */
imlib_image_clear();
const monitor_t *monitor = monitors;
uint32_t monitor_index = 0;
while (monitor) {
uint32_t img_index = (index + monitor_index) % wallpaper_config->count;
const char *path = wallpaper_config->path_list[img_index];
Imlib_Image wallpaper_img = imlib_load_image(path);
if (!wallpaper_img) {
monitor = monitor->next;
monitor_index++;
continue;
}
uint32_t dst_width = monitor->monitor_width;
uint32_t dst_height = monitor->monitor_height;
/* 获取图片原始尺寸 */
imlib_context_set_image(wallpaper_img);
uint32_t img_width = imlib_image_get_width();
uint32_t img_height = imlib_image_get_height();
/* 计算缩放比例,保持原始宽高比 */
float width_ratio = (float)dst_width / img_width;
float height_ratio = (float)dst_height / img_height;
float scale = MAX(width_ratio, height_ratio);
/* 图片居中显示,计算原图绘制区域 */
int src_width = dst_width / scale;
int src_height = dst_height / scale;
int src_x = (img_width - src_width) / 2;
int src_y = (img_height - src_height) / 2;
/* 绘制图片到最终图像上 */
imlib_context_set_image(final_image);
imlib_blend_image_onto_image(wallpaper_img, 1, src_x, src_y, src_width,
src_height, monitor->monitor_x,
monitor->monitor_y, dst_width, dst_height);
/* 释放图片资源 */
imlib_context_set_image(wallpaper_img);
imlib_free_image();
monitor = monitor->next;
monitor_index++;
}
/* 获取最终图像数据 */
imlib_context_set_image(final_image);
uint32_t *data = imlib_image_get_data_for_reading_only();
/* 复制数据到自己分配的内存中(原数据会在图像释放后无效) */
size_t data_size = screen_width * screen_height * sizeof(uint32_t);
uint32_t *result = ecalloc(1, data_size);
if (result) memcpy(result, data, data_size);
/* 释放图像 */
imlib_context_set_image(final_image);
imlib_free_image();
return result;
}
/**
* 生成图标像素数据
* @returns 图标像素数据( RGBA 顺序),使用后记得使用 free 释放
*/
uint32_t *generate_icon_pixels(const char *image_path, uint32_t width,
uint32_t height, uint32_t padding) {
uint32_t dst_width = width - padding * 2;
uint32_t dst_height = height - padding * 2;
if (dst_width <= 0 || dst_height <= 0) return NULL;
Imlib_Image img = imlib_load_image(image_path);
imlib_context_set_image(img);
int w = imlib_image_get_width();
int h = imlib_image_get_height();
float width_ratio = (float)dst_width / w;
float height_ratio = (float)dst_height / h;
float scale = MAX(width_ratio, height_ratio);
int src_width = dst_width / scale;
int src_height = dst_height / scale;
int src_x = (w - src_width) / 2;
int src_y = (h - src_height) / 2;
Imlib_Image target_img = imlib_create_image(width, height);
imlib_context_set_image(target_img);
imlib_image_set_has_alpha(true);
imlib_image_clear();
imlib_blend_image_onto_image(img, 1, src_x, src_y, src_width, src_height,
padding, padding, dst_width, dst_height);
uint32_t *data = imlib_image_get_data_for_reading_only();
uint32_t data_size = width * height * sizeof(uint32_t);
uint32_t *pixels = ecalloc(1, data_size);
if (pixels) memcpy(pixels, data, data_size);
imlib_free_image_and_decache();
imlib_context_set_image(img);
imlib_free_image_and_decache();
return pixels;
}

283
src/renderer/text.c Normal file
View File

@@ -0,0 +1,283 @@
#include <cairo.h>
#include <glib-object.h>
#include <glib.h>
#include <pango/pango-attributes.h>
#include <pango/pango-context.h>
#include <pango/pango-font.h>
#include <pango/pango-fontmap.h>
#include <pango/pango-layout.h>
#include <pango/pango-types.h>
#include <pango/pangocairo.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include "renderer.h"
#include "types.h"
#include "utils.h"
static PangoContext *context = NULL;
static PangoAttrList *attr_list = NULL;
static PangoLayout *layout = NULL;
uint32_t get_font_height(const char *font_family, const uint32_t font_size,
const uint32_t dpi) {
clean_pango_context();
PangoFontMap *fontmap = pango_cairo_font_map_new();
context = pango_font_map_create_context(fontmap);
pango_cairo_context_set_resolution(context, dpi);
PangoLanguage *lang = pango_context_get_language(context);
attr_list = pango_attr_list_new();
PangoFontDescription *desc = pango_font_description_from_string(font_family);
pango_font_description_set_size(desc, font_size * PANGO_SCALE);
PangoAttribute *attr = pango_attr_font_desc_new(desc);
pango_attr_list_insert(attr_list, attr);
PangoFontMetrics *metrics = pango_context_get_metrics(context, desc, lang);
int ascent = pango_font_metrics_get_ascent(metrics);
int descent = pango_font_metrics_get_descent(metrics);
uint32_t font_height = PANGO_PIXELS(ascent + descent);
pango_font_metrics_unref(metrics);
pango_font_description_free(desc);
g_object_unref(fontmap);
return font_height;
}
static void draw_background(cairo_t *cr, color_t *color, int32_t x, int32_t y,
uint32_t width, uint32_t height) {
cairo_move_to(cr, x, y);
cairo_set_source_rgba(cr, color->red, color->green, color->blue,
color->alpha);
cairo_rectangle(cr, x, y, width, height);
cairo_fill(cr);
}
static void prepare_layout(bool align_center) {
if (!layout) {
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);
}
PangoAlignment align = align_center ? PANGO_ALIGN_CENTER : PANGO_ALIGN_LEFT;
pango_layout_set_alignment(layout, align);
}
static void get_layout_size(PangoLayout *layout, int *width, int *height) {
PangoRectangle ink_rect, logical_rect;
pango_layout_get_pixel_extents(layout, &ink_rect, &logical_rect);
int w = MAX(ink_rect.width, logical_rect.width);
int h = MAX(ink_rect.height, logical_rect.height);
if (width) *width = w;
if (height) *height = h;
}
static uint32_t get_text_width(const char *text) {
prepare_layout(false);
pango_layout_set_width(layout, -1);
pango_layout_set_text(layout, text, -1);
int w;
get_layout_size(layout, &w, NULL);
return (uint32_t)w;
}
static void draw_text(cairo_t *cr, const char *text, color_t *color, int32_t x,
uint32_t y, uint32_t width, uint32_t height,
bool align_center) {
prepare_layout(align_center);
pango_layout_set_width(layout, width * PANGO_SCALE);
pango_layout_set_text(layout, text, -1);
cairo_set_source_rgba(cr, color->red, color->green, color->blue,
color->alpha);
int h = 0;
get_layout_size(layout, NULL, &h);
double ty = ((double)height - h) / 2;
pango_cairo_update_layout(cr, layout);
cairo_move_to(cr, x, y + ty);
pango_cairo_show_layout(cr, layout);
}
static 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 draw_rect(cairo_t *cr, int32_t x, int32_t y, uint32_t width,
uint32_t height, bool fill, color_t *color,
uint32_t line_width) {
cairo_set_line_width(cr, line_width);
if (fill) {
cairo_rectangle(cr, x, y, width, height);
cairo_set_source_rgba(cr, color->red, color->green, color->blue,
color->alpha);
cairo_fill(cr);
} else {
double half_lw = (double)line_width / 2;
double sx = x, sy = y, w = width, h = height;
sx += half_lw;
sy += half_lw;
w -= line_width;
h -= line_width;
cairo_rectangle(cr, sx, sy, w, h);
cairo_set_source_rgba(cr, color->red, color->green, color->blue,
color->alpha);
cairo_stroke(cr);
}
}
static int32_t draw_status(monitor_t *monitor, status_t *status, icons_t *icons,
color_set_t *color_set, uint32_t height,
uint32_t x_padding, int32_t end) {
typedef struct {
const char *const text;
color_t *color;
icon_res_t *icon_res;
} status_item_t;
char cpu[16], mem[64], volume[600];
sprintf(cpu, "%.1f", status->cpu_usage_percent);
sprintf(mem, "%s(%.1f%%)", status->mem_usage.mem_used_text,
status->mem_usage.mem_percent);
sprintf(volume, "%d%%%s%s%s", status->pulse.avg_volume,
status->pulse.mute ? "M" : "",
strlen(status->pulse.device) ? "|" : "", status->pulse.device);
status_item_t items[] = {
{status->net_speed.down, &color_set->status_net_recv_color,
&icons->net_down},
{status->net_speed.up, &color_set->status_net_send_color, &icons->net_up},
{volume, &color_set->status_volume_color, &icons->volume},
{mem, &color_set->status_memory_color, &icons->memory},
{cpu, &color_set->status_cpu_color, &icons->cpu},
{status->time, &color_set->status_datetime_color, &icons->clock},
};
int32_t x = monitor->monitor_width;
for (int i = LENGTH(items) - 1; i >= 0; i--) {
const char *text = items[i].text;
if (!strlen(text)) continue;
color_t *color = items[i].color;
uint32_t width = get_text_width(text);
x -= width + x_padding;
if (x < end) return x + (width + x_padding);
draw_text(monitor->cr, text, color, x, 0, width, height, false);
cairo_surface_t *surface = items[i].icon_res->surface;
if (surface) {
if (x - height < end) return x;
x -= height;
cairo_set_source_surface(monitor->cr, surface, x, 0);
cairo_paint(monitor->cr);
}
x -= x_padding;
if (x < end) return x + x_padding;
}
return x;
};
void draw_bar(monitor_t *monitor, bool is_current_monitor, status_t *status,
icons_t *icons, color_set_t *color_set, uint32_t height,
uint32_t tag_x_padding, uint32_t layout_x_padding,
uint32_t client_name_x_padding, uint32_t status_x_padding) {
cairo_t *cr = monitor->cr;
int32_t x = 0;
uint32_t width = 0;
const char *text;
color_t *color, *bg;
color_t *bar_bg = &color_set->bar_bg;
uint32_t tag_mask_has_client = 0;
uint32_t visible_client_count = 0;
for (client_t *c = monitor->clients; c; c = c->next) {
tag_mask_has_client |= c->tags;
if (CLIENT_VISIBLE(c)) visible_client_count++;
}
clean_cairo_context(cr);
draw_background(cr, bar_bg, 0, 0, monitor->monitor_width, height);
/* 绘制 tags */
for (uint32_t i = 0; i < g_strv_length(monitor->tags); i++) {
bool selected = (monitor->selected_tags >> i) & 0x1;
bool has_client = (1 << i) & tag_mask_has_client;
text = monitor->tags[i];
width = get_text_width(text) + tag_x_padding * 2;
bg = selected ? &color_set->active_tag_bg : &color_set->tag_bg;
color = selected ? &color_set->active_tag_color : &color_set->tag_color;
if (bg->rgba != bar_bg->rgba) {
draw_background(cr, bg, x, 0, width, height);
}
if (has_client) draw_rect(cr, x, 0, 4, 4, selected, color, 1);
draw_text(cr, text, color, x, 0, width, height, true);
x += width;
}
/* 绘制 layout 标识 */
{
text = monitor->layout->symbol;
width = get_text_width(text) + layout_x_padding * 2;
color = &color_set->layout_color;
draw_text(cr, text, color, x, 0, width, height, true);
x += width;
}
/* 绘制状态栏信息 */
int32_t status_start =
draw_status(monitor, status, icons, color_set, height, status_x_padding, x);
/* 绘制 client name 列表 */
if (visible_client_count) {
uint32_t box_width = (status_start - x) / visible_client_count;
width = box_width - 2 * client_name_x_padding;
for (client_t *c = monitor->clients; c; c = c->next) {
if (!CLIENT_VISIBLE(c)) continue;
if (is_current_monitor && c == monitor->selected_client) {
bg = &color_set->active_client_name_bg;
color = &color_set->active_client_name_color;
} else {
bg = &color_set->client_name_bg;
color = &color_set->client_name_color;
}
text = c->name;
if (bg->rgba != bar_bg->rgba) {
draw_background(cr, bg, x, 0, box_width, height);
}
x += client_name_x_padding;
draw_text(cr, text, color, x, 0, width, height, false);
x += width + client_name_x_padding;
}
}
}
void clean_pango_context(void) {
if (layout) {
g_object_unref(layout);
layout = NULL;
}
if (attr_list) {
pango_attr_list_unref(attr_list);
attr_list = NULL;
}
if (context) {
g_object_unref(context);
context = NULL;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 766 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 786 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 769 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 564 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

407
src/status.c Normal file
View File

@@ -0,0 +1,407 @@
#include "status.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/subscribe.h>
#include <pulse/volume.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include "types.h"
#include "utils.h"
#define CPU_FILE "/proc/stat"
#define MEM_FILE "/proc/meminfo"
#define NET_FILE "/proc/net/dev"
#define INTERVAL 1
#define TIME_FORMAT "%A %m-%d %H:%M:%S"
typedef struct cpu_stat_t {
uint64_t user;
uint64_t nice;
uint64_t system;
uint64_t idle;
uint64_t iowait;
uint64_t irq;
uint64_t softirq;
uint64_t steal;
uint64_t guest;
uint64_t guest_nice;
} cpu_stat_t;
typedef struct net_stat_t {
uint64_t rx_bytes;
uint64_t tx_bytes;
} net_stat_t;
typedef void (*pulse_notify)(pulse_t pulse);
static status_t status;
static guint timer = 0;
static status_changed_notify listener = NULL;
static bool pulse_inited = false;
static pa_cvolume current_volume;
static pa_context *context = NULL;
static pa_glib_mainloop *loop = NULL;
static double calc_cpu_usage(cpu_stat_t curr, cpu_stat_t prev) {
uint64_t curr_idle = curr.idle + curr.iowait;
uint64_t curr_total = curr.user + curr.nice + curr.system + curr.idle +
curr.iowait + curr.irq + curr.softirq + curr.steal +
curr.guest + curr.guest_nice;
uint64_t prev_idle = prev.idle + prev.iowait;
uint64_t prev_total = prev.user + prev.nice + prev.system + prev.idle +
prev.iowait + prev.irq + prev.softirq + prev.steal +
prev.guest + prev.guest_nice;
uint64_t total = curr_total - prev_total;
uint64_t idle = curr_idle - prev_idle;
uint64_t active = total - idle;
if (total == 0) {
return 0.0;
}
double usage = ((double)active) / total * 100;
return usage;
}
static bool get_cpu_usage(double *usage, GError *error) {
static cpu_stat_t prev_cpu_stat = {0};
static gboolean inited = false;
gchar *contents = NULL;
gsize length = 0;
if (!g_file_get_contents(CPU_FILE, &contents, &length, &error)) {
return false;
}
gchar **lines = g_strsplit(contents, "\n", 0);
g_free(contents);
char cpu_line[256];
for (gchar **line = lines; *line != NULL; ++line) {
if (strncmp(*line, "cpu ", 4) == 0) {
strncpy(cpu_line, *line, sizeof(cpu_line));
break;
}
}
cpu_stat_t stat = {0};
sscanf(cpu_line, "cpu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu", &stat.user,
&stat.nice, &stat.system, &stat.idle, &stat.iowait, &stat.irq,
&stat.softirq, &stat.steal, &stat.guest, &stat.guest_nice);
if (!inited) {
prev_cpu_stat = stat;
inited = true;
}
double percent = calc_cpu_usage(stat, prev_cpu_stat);
prev_cpu_stat = stat;
*usage = percent;
g_strfreev(lines);
return true;
}
static void bytes_to_readable_size(uint64_t bytes, char *str) {
static char *units[] = {"B", "K", "M", "G"};
double size = (double)bytes;
int i = 0;
while (size > 1024) {
size /= 1024;
++i;
}
sprintf(str, "%.1lf%s", size, units[i]);
}
static void calc_mem_usage(memory_usage_t *usage) {
usage->mem_used = usage->mem_total - usage->mem_free - usage->buffers -
usage->cached - usage->s_reclaimable;
usage->swap_used = usage->swap_total - usage->swap_free;
usage->mem_percent =
((float)usage->mem_used) / ((float)usage->mem_total) * 100;
if (usage->swap_total == 0) {
usage->swap_percent = 0;
}
bytes_to_readable_size(usage->mem_used * 1024, usage->mem_used_text);
bytes_to_readable_size(usage->swap_used * 1024, usage->swap_used_text);
}
static bool get_mem_usage(memory_usage_t *usage, GError *error) {
gchar *contents = NULL;
gsize length = 0;
if (!g_file_get_contents(MEM_FILE, &contents, &length, &error)) {
return false;
}
gchar **lines = g_strsplit(contents, "\n", 0);
g_free(contents);
for (gchar **line = lines; *line != NULL; ++line) {
char name[32];
uint64_t kb = 0;
sscanf(*line, "%[^:]: %lu", name, &kb);
if (g_str_equal(name, "MemTotal")) {
usage->mem_total = kb;
} else if (g_str_equal(name, "MemFree")) {
usage->mem_free = kb;
} else if (g_str_equal(name, "Buffers")) {
usage->buffers = kb;
} else if (g_str_equal(name, "Cached")) {
usage->cached = kb;
} else if (g_str_equal(name, "SwapTotal")) {
usage->swap_total = kb;
} else if (g_str_equal(name, "SwapFree")) {
usage->swap_free = kb;
} else if (g_str_equal(name, "SReclaimable")) {
usage->s_reclaimable = kb;
}
}
calc_mem_usage(usage);
g_strfreev(lines);
return true;
}
static void calc_speed(const uint32_t interval, const net_stat_t *current,
const net_stat_t *previous, net_speed_t *speed) {
speed->rx_bytes = current->rx_bytes - previous->rx_bytes;
speed->tx_bytes = current->tx_bytes - previous->tx_bytes;
bytes_to_readable_size(speed->rx_bytes / interval, speed->down);
bytes_to_readable_size(speed->tx_bytes / interval, speed->up);
}
static bool get_net_speed(const uint32_t interval, net_speed_t *speed,
GError *error) {
static net_stat_t prev = {0};
static bool inited = false;
gchar *contents = NULL;
gsize length = 0;
if (!g_file_get_contents(NET_FILE, &contents, &length, &error)) {
return false;
}
gchar **lines = g_strsplit(contents, "\n", 0);
g_free(contents);
net_stat_t stat = {.rx_bytes = 0, .tx_bytes = 0};
for (gchar **line = lines; *line != NULL; ++line) {
if (strchr(*line, ':') == NULL) {
continue;
}
char name[16];
uint64_t rx_bytes, tx_bytes;
sscanf(*line, " %[^:]: %lu %*u %*u %*u %*u %*u %*u %*u %lu", name,
&rx_bytes, &tx_bytes);
if (g_str_equal(name, "lo")) {
continue;
}
stat.rx_bytes += rx_bytes;
stat.tx_bytes += tx_bytes;
}
if (!inited) {
inited = true;
prev = stat;
}
calc_speed(interval, &stat, &prev, speed);
prev = stat;
g_strfreev(lines);
return false;
}
static void get_date_time(char *time_text, size_t length) {
time_t current_time = time(NULL);
struct tm *time_info = localtime(&current_time);
strftime(time_text, length, TIME_FORMAT, time_info);
}
#define volume_to_percent(v) ((int)round((double)(v) * 100 / PA_VOLUME_NORM))
static void parse_sink(const pa_sink_info *i, pulse_t *pulse) {
pulse->index = i->index;
pulse->mute = i->mute;
memset(pulse->volumes, -1, sizeof(pulse->volumes));
pulse->avg_volume = volume_to_percent(pa_cvolume_avg(&i->volume));
strncpy(pulse->device, i->description, sizeof(pulse->device));
int len = 0;
for (uint8_t ch = 0; ch < LENGTH(pulse->volumes); ++ch) {
if (!pa_channel_map_has_position(&i->channel_map, ch)) {
continue;
}
pa_volume_t v = pa_cvolume_get_position(&i->volume, &i->channel_map, ch);
int volume = volume_to_percent(v);
pulse->volumes[len++] = volume;
}
for (uint8_t i = len; i < LENGTH(pulse->volumes); ++i) {
pulse->volumes[i] = -1;
}
const char *key = NULL;
void *state = NULL;
while ((key = pa_proplist_iterate(i->proplist, &state)) != NULL) {
if (strcmp(key, "api.alsa.path") != 0 &&
strcmp(key, "device.description") != 0) {
continue;
}
const char *value = pa_proplist_gets(i->proplist, key);
unsigned long len = strlen(value);
if (len && (len < strlen(pulse->device))) {
strncpy(pulse->device, value, sizeof(pulse->device));
}
}
}
static void notify_status_change() {
if (listener != NULL) listener(&status);
}
static void sink_info_callback(pa_context *c, const pa_sink_info *info, int eol,
void *userdata) {
if (eol < 0) {
return;
}
if (eol > 0) {
return;
}
if (info) {
parse_sink(info, &status.pulse);
current_volume = info->volume;
pulse_inited = true;
notify_status_change();
}
}
static void server_info_callback(pa_context *c, const pa_server_info *info,
void *userdata) {
pa_context_get_sink_info_by_name(c, info->default_sink_name,
sink_info_callback, NULL);
}
static void subscribe_callback(pa_context *c, pa_subscription_event_type_t t,
uint32_t idx, void *userdata) {
uint32_t facility = t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK;
if (facility == PA_SUBSCRIPTION_EVENT_SINK) {
pa_context_get_server_info(c, server_info_callback, NULL);
return;
}
}
static void clean_pulse() {
memset(&status.pulse, 0, sizeof(status.pulse));
notify_status_change();
if (context) {
pa_context_disconnect(context);
pa_context_unref(context);
context = NULL;
}
if (loop) pa_glib_mainloop_free(loop);
pulse_inited = false;
loop = NULL;
}
static void init_pulse(GMainContext *g_context);
static void state_callback(pa_context *c, void *userdata) {
pa_context_state_t state = pa_context_get_state(c);
if (PA_CONTEXT_IS_GOOD(state)) {
pa_context_set_subscribe_callback(c, subscribe_callback, NULL);
pa_context_subscribe(c, PA_SUBSCRIPTION_MASK_SINK, NULL, NULL);
pa_context_get_server_info(c, server_info_callback, NULL);
} else if (state == PA_CONTEXT_FAILED) {
clean_pulse();
init_pulse((GMainContext *)userdata);
}
}
static void init_pulse(GMainContext *g_context) {
if (loop) pa_glib_mainloop_free(loop);
pa_glib_mainloop *loop = pa_glib_mainloop_new(g_context);
pa_mainloop_api *api = pa_glib_mainloop_get_api(loop);
context = pa_context_new(api, "ZSWM-Status-Volume");
pa_context_connect(context, NULL, PA_CONTEXT_NOFAIL, NULL);
pa_context_set_state_callback(context, state_callback, g_context);
}
static gboolean update_status(gpointer data) {
GError *error = NULL;
get_net_speed(INTERVAL, &status.net_speed, error);
get_mem_usage(&status.mem_usage, error);
get_cpu_usage(&status.cpu_usage_percent, error);
get_date_time(status.time, sizeof(status.time));
notify_status_change();
return true;
}
void init_status(GMainContext *context, status_changed_notify callback) {
listener = callback;
init_pulse(context);
update_status(NULL);
timer = g_timeout_add_seconds(INTERVAL, update_status, NULL);
}
void clean_status() {
clean_pulse();
g_source_remove(timer);
listener = NULL;
}
void change_pulse_volume(int step) {
if (!pulse_inited || !step || step > 100 || step < -100) {
return;
}
pa_cvolume vol = current_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);
}
}
uint32_t index = status.pulse.index;
pa_context_set_sink_volume_by_index(context, index, &vol, NULL, NULL);
}
void toggle_pulse_mute() {
if (!pulse_inited) {
return;
}
uint32_t index = status.pulse.index;
int mute = !status.pulse.mute;
pa_context_set_sink_mute_by_index(context, index, mute, NULL, NULL);
}

12
src/status.h Normal file
View File

@@ -0,0 +1,12 @@
#pragma once
#include <glib.h>
#include "types.h"
typedef void (*status_changed_notify)(status_t *status);
void init_status(GMainContext *context, status_changed_notify callback);
void clean_status(void);
void change_pulse_volume(int step);
void toggle_pulse_mute(void);

View File

@@ -1,9 +1,20 @@
#include "utils.h"
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "types.h"
double get_time() {
struct timeval tv;
gettimeofday(&tv, NULL);
return tv.tv_sec + tv.tv_usec / 1000000.0;
}
void die(const char *fmt, ...) {
va_list ap;
@@ -30,3 +41,88 @@ void *ecalloc(size_t number_of_member, size_t member_size) {
return p;
}
bool file_exist(const char *path) {
if (!path || !*path) return false;
return access(path, R_OK) == 0;
}
#if (!defined(RELEASE)) && defined(LOG_FILE)
void logger(const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
FILE *log_file = fopen(LOG_FILE, "a+t");
vfprintf(log_file, fmt, ap);
va_end(ap);
fclose(log_file);
}
#endif
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, NULL, 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);
}
#define INTERSECT(x, y, w, h, m) \
(MAX(0, MIN((x) + (w), (m)->monitor_x + (int)(m)->monitor_width) - \
MAX((x), (m)->monitor_x)) * \
MAX(0, MIN((y) + (h), (m)->monitor_y + (int)(m)->monitor_height) - \
MAX((y), (m)->monitor_y)))
monitor_t *rect_to_monitor(monitor_t *monitors, int32_t x, int32_t y,
uint32_t width, uint32_t height) {
monitor_t *r = monitors;
int32_t a, area = 0;
for (monitor_t *m = monitors; m; m = m->next) {
if ((a = INTERSECT(x, y, width, height, m)) > area) {
area = a;
r = m;
}
}
return r;
}

View File

@@ -1,12 +1,17 @@
set(LIB_XCB "zswm-xcb")
set(LIB_XCB_NAME ${LIB_XCB} PARENT_SCOPE)
add_library(${LIB_XCB} STATIC xcb.c window.c)
add_library(${LIB_XCB} STATIC xcb.c window.c res.c cursor.c event.c wm-extension.c)
target_include_directories(${LIB_XCB} SYSTEM PUBLIC ${XCB_INCLUDE_DIRS})
target_include_directories(${LIB_XCB} PUBLIC
"${CMAKE_SOURCE_DIR}/include"
"${CMAKE_BINARY_DIR}"
target_include_directories(${LIB_XCB} SYSTEM
PRIVATE ${XCB_INCLUDE_DIRS}
)
target_include_directories(${LIB_XCB}
PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}"
PRIVATE "${CMAKE_SOURCE_DIR}/include"
PRIVATE "${CMAKE_BINARY_DIR}"
)
target_link_libraries(${LIB_XCB} ${XCB_LIBRARIES})
target_link_libraries(${LIB_XCB}
PRIVATE ${XCB_LIBRARIES}
)

126
src/xcb/cursor.c Normal file
View File

@@ -0,0 +1,126 @@
#include <X11/cursorfont.h>
#include <xcb/xcb_cursor.h>
#include "utils.h"
#include "xcb-private.h"
#include "xcb.h"
static const char *const xcursor_font[] = {
[XC_X_cursor] = "X_cursor",
[XC_arrow] = "arrow",
[XC_based_arrow_down] = "based_arrow_down",
[XC_based_arrow_up] = "based_arrow_up",
[XC_boat] = "boat",
[XC_bogosity] = "bogosity",
[XC_bottom_left_corner] = "bottom_left_corner",
[XC_bottom_right_corner] = "bottom_right_corner",
[XC_bottom_side] = "bottom_side",
[XC_bottom_tee] = "bottom_tee",
[XC_box_spiral] = "box_spiral",
[XC_center_ptr] = "center_ptr",
[XC_circle] = "circle",
[XC_clock] = "clock",
[XC_coffee_mug] = "coffee_mug",
[XC_cross] = "cross",
[XC_cross_reverse] = "cross_reverse",
[XC_crosshair] = "crosshair",
[XC_diamond_cross] = "diamond_cross",
[XC_dot] = "dot",
[XC_dotbox] = "dotbox",
[XC_double_arrow] = "double_arrow",
[XC_draft_large] = "draft_large",
[XC_draft_small] = "draft_small",
[XC_draped_box] = "draped_box",
[XC_exchange] = "exchange",
[XC_fleur] = "fleur",
[XC_gobbler] = "gobbler",
[XC_gumby] = "gumby",
[XC_hand1] = "hand1",
[XC_hand2] = "hand2",
[XC_heart] = "heart",
[XC_icon] = "icon",
[XC_iron_cross] = "iron_cross",
[XC_left_ptr] = "left_ptr",
[XC_left_side] = "left_side",
[XC_left_tee] = "left_tee",
[XC_leftbutton] = "leftbutton",
[XC_ll_angle] = "ll_angle",
[XC_lr_angle] = "lr_angle",
[XC_man] = "man",
[XC_middlebutton] = "middlebutton",
[XC_mouse] = "mouse",
[XC_pencil] = "pencil",
[XC_pirate] = "pirate",
[XC_plus] = "plus",
[XC_question_arrow] = "question_arrow",
[XC_right_ptr] = "right_ptr",
[XC_right_side] = "right_side",
[XC_right_tee] = "right_tee",
[XC_rightbutton] = "rightbutton",
[XC_rtl_logo] = "rtl_logo",
[XC_sailboat] = "sailboat",
[XC_sb_down_arrow] = "sb_down_arrow",
[XC_sb_h_double_arrow] = "sb_h_double_arrow",
[XC_sb_left_arrow] = "sb_left_arrow",
[XC_sb_right_arrow] = "sb_right_arrow",
[XC_sb_up_arrow] = "sb_up_arrow",
[XC_sb_v_double_arrow] = "sb_v_double_arrow",
[XC_shuttle] = "shuttle",
[XC_sizing] = "sizing",
[XC_spider] = "spider",
[XC_spraycan] = "spraycan",
[XC_star] = "star",
[XC_target] = "target",
[XC_tcross] = "tcross",
[XC_top_left_arrow] = "top_left_arrow",
[XC_top_left_corner] = "top_left_corner",
[XC_top_right_corner] = "top_right_corner",
[XC_top_side] = "top_side",
[XC_top_tee] = "top_tee",
[XC_trek] = "trek",
[XC_ul_angle] = "ul_angle",
[XC_umbrella] = "umbrella",
[XC_ur_angle] = "ur_angle",
[XC_watch] = "watch",
[XC_xterm] = "xterm",
};
static xcb_cursor_context_t *get_xcb_cursor_context(void) {
static xcb_cursor_context_t *ctx = NULL;
if (ctx) return ctx;
if (xcb_cursor_context_new(conn, screen, &ctx) < 0) {
return NULL;
}
return ctx;
}
static const char *xcursor_font_tostr(uint16_t c) {
if (c < LENGTH(xcursor_font)) {
return xcursor_font[c];
}
return NULL;
}
static xcb_cursor_t cursor_list[LENGTH(xcursor_font)];
xcb_cursor_context_t *cursor_ctx = NULL;
xcb_cursor_t get_xcb_cursor(cursor_t cursor) {
if (!cursor_ctx) cursor_ctx = get_xcb_cursor_context();
if (!cursor_ctx) die("cannot get cursor:");
if (!cursor_list[cursor]) {
const char *name = xcursor_font_tostr(cursor);
cursor_list[cursor] = xcb_cursor_load_cursor(cursor_ctx, name);
}
return cursor_list[cursor];
}
void clean_xcb_cursor(void) {
for (int i = 0; i < LENGTH(xcursor_font); i++) {
xcb_cursor_t cursor = cursor_list[i];
if (cursor) xcb_free_cursor(conn, cursor);
}
xcb_cursor_context_free(cursor_ctx);
}

171
src/xcb/event.c Normal file
View File

@@ -0,0 +1,171 @@
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <xcb/xcb.h>
#include <xcb/xcb_event.h>
#include <xcb/xproto.h>
#include "event-adapter.h"
#include "utils.h"
#include "xcb-private.h"
struct event_adapter_t {
xcb_connection_t *conn;
event_handler handler;
void *user_data;
};
static void convert_event(xcb_generic_event_t *xcb_event,
generic_event_t *event) {
uint8_t event_type = XCB_EVENT_RESPONSE_TYPE(xcb_event);
switch (event_type) {
case XCB_MAPPING_NOTIFY: {
xcb_mapping_notify_event_t *ev = (xcb_mapping_notify_event_t *)xcb_event;
event->mapping_notify.type = EVENT_TYPE_MAPPING_NOTIFY;
event->mapping_notify.request = ev->request;
break;
}
case XCB_KEY_PRESS: {
xcb_key_press_event_t *ev = (xcb_key_press_event_t *)xcb_event;
int col = ev->state & XCB_MOD_MASK_2 ? 1 : 0;
xcb_keysym_t keysym = xcb_key_press_lookup_keysym(key_symbols, ev, col);
event->key_press.type = EVENT_TYPE_KEY_PRESS;
event->key_press.key_symbol = keysym;
event->key_press.modifiers = ev->state;
break;
}
case XCB_BUTTON_PRESS: {
xcb_button_press_event_t *ev = (xcb_button_press_event_t *)xcb_event;
event->button_press.type = EVENT_TYPE_BUTTON_PRESS;
event->button_press.window = ev->event;
event->button_press.root = ev->root;
event->button_press.x = ev->event_x;
event->button_press.y = ev->event_y;
event->button_press.root_x = ev->root_x;
event->button_press.root_y = ev->root_y;
event->button_press.time = ev->time;
break;
}
case XCB_CONFIGURE_REQUEST: {
xcb_configure_request_event_t *ev =
(xcb_configure_request_event_t *)xcb_event;
event->configure_request.type = EVENT_TYPE_CONFIGURE_REQUEST;
event->configure_request.window = ev->window;
event->configure_request.parent = ev->parent;
event->configure_request.sibling = ev->sibling;
event->configure_request.x = ev->x;
event->configure_request.y = ev->y;
event->configure_request.width = ev->width;
event->configure_request.height = ev->height;
event->configure_request.border_width = ev->border_width;
event->configure_request.value_mask = ev->value_mask;
event->configure_request.stack_mode = ev->stack_mode;
break;
}
case XCB_CONFIGURE_NOTIFY:
event->type = EVENT_TYPE_CONFIGURE_NOTIFY;
break;
case XCB_MAP_REQUEST: {
xcb_map_request_event_t *ev = (xcb_map_request_event_t *)xcb_event;
event->map_request.type = EVENT_TYPE_MAP_REQUEST;
event->map_request.window = ev->window;
event->map_request.parent = ev->parent;
break;
}
case XCB_UNMAP_NOTIFY: {
xcb_unmap_notify_event_t *ev = (xcb_unmap_notify_event_t *)xcb_event;
event->unmap_notify.type = EVENT_TYPE_UNMAP_NOTIFY;
event->unmap_notify.window = ev->window;
event->unmap_notify.send_event = XCB_EVENT_SENT(ev);
break;
}
case XCB_DESTROY_NOTIFY: {
xcb_destroy_notify_event_t *ev = (xcb_destroy_notify_event_t *)xcb_event;
event->destroy_notify.type = EVENT_TYPE_DESTROY_NOTIFY;
event->destroy_notify.window = ev->window;
break;
}
case XCB_EXPOSE: {
xcb_expose_event_t *ev = (xcb_expose_event_t *)xcb_event;
event->expose.type = EVENT_TYPE_EXPOSE;
event->expose.window = ev->window;
event->expose.count = ev->count;
break;
}
case XCB_MOTION_NOTIFY: {
xcb_motion_notify_event_t *ev = (xcb_motion_notify_event_t *)xcb_event;
event->motiion_notify.type = EVENT_TYPE_MOTION_NOTIFY;
event->motiion_notify.root = ev->root;
event->motiion_notify.root_x = ev->root_x;
event->motiion_notify.root_y = ev->root_y;
event->motiion_notify.window = ev->event;
event->motiion_notify.x = ev->event_x;
event->motiion_notify.y = ev->event_y;
event->motiion_notify.time = ev->time;
break;
}
case XCB_ENTER_NOTIFY: {
xcb_enter_notify_event_t *ev = (xcb_enter_notify_event_t *)xcb_event;
event->enter_notify.type = EVENT_TYPE_ENTER_NOTIFY;
event->enter_notify.window = ev->event;
event->enter_notify.mode = ev->mode;
event->enter_notify.detail = ev->detail;
break;
}
case XCB_FOCUS_IN: {
xcb_focus_in_event_t *ev = (xcb_focus_in_event_t *)xcb_event;
event->focus_in.type = EVENT_TYPE_FOCUS_IN;
event->focus_in.window = ev->event;
} break;
case XCB_PROPERTY_NOTIFY: {
xcb_property_notify_event_t *ev =
(xcb_property_notify_event_t *)xcb_event;
event->property_notify.type = EVENT_TYPE_PROPERTY_NOTIFY;
event->property_notify.window = ev->window;
event->property_notify.xcb_atom = ev->atom;
event->property_notify.state = ev->state;
break;
}
case XCB_CLIENT_MESSAGE: {
xcb_client_message_event_t *ev = (xcb_client_message_event_t *)xcb_event;
event->client_message.type = EVENT_TYPE_CLIENT_MESSAGE;
event->client_message.window = ev->window;
event->client_message.message_type = ev->type;
event->client_message.format = ev->format;
memcpy(&event->client_message.data, &ev->data, sizeof(ev->data));
break;
}
default:
event->type = -1;
return;
}
if (event->type) {
const char *label = xcb_event_get_label(event_type);
logger("========================== %s ==========================\n", label);
}
}
event_adapter_t *create_event_adapter(void) {
event_adapter_t *adapter = ecalloc(1, sizeof(event_adapter_t));
adapter->conn = conn;
return adapter;
}
void destroy_event_adapter(event_adapter_t *adapter) { free(adapter); }
void event_adapter_set_handler(event_adapter_t *adapter, event_handler handler,
void *user_data) {
adapter->handler = handler;
adapter->user_data = user_data;
}
void event_adapter_poll(event_adapter_t *adapter) {
xcb_generic_event_t *xcb_event;
generic_event_t event;
while ((xcb_event = xcb_poll_for_event(adapter->conn))) {
convert_event(xcb_event, &event);
if (adapter->handler) adapter->handler(&event, adapter->user_data);
free(xcb_event);
xcb_event = NULL;
}
}

54
src/xcb/res.c Normal file
View File

@@ -0,0 +1,54 @@
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include <xcb/xcb_xrm.h>
#include "utils.h"
#include "xcb-private.h"
#include "xcb.h"
static xcb_xrm_database_t *db = NULL;
static xcb_xrm_database_t *get_xrm_db(void) {
if (!db) {
db = xcb_xrm_database_from_default(conn);
}
return db;
}
bool get_xresource_uint32(const char *name, uint32_t *value) {
xcb_xrm_database_t *db = get_xrm_db();
if (!db) return false;
long temp_value = *value;
bool success = xcb_xrm_resource_get_long(db, name, NULL, &temp_value) == 0;
if (success) *value = (uint32_t)temp_value;
return success;
}
/**
* @brief 读取 Xresources 文件中的字符串配置
* @param name Xresources 文件中的配置名
* @param value 存放读取到的字符串存储的指针的指针,使用完后记得使用
* free(*value) 释放字符串对应的内存
* @param fallback 若 Xresources 解析失败,则使用这个默认值
*/
void get_xresource_string(const char *name, char **value,
const char *fallback) {
xcb_xrm_database_t *db = get_xrm_db();
if (!db) return;
if (!xcb_xrm_resource_get_string(db, name, NULL, value)) return;
size_t len = strlen(fallback) + 1;
*value = ecalloc(len, sizeof(char));
strncpy(*value, fallback, len);
}
void clean_xresource(void) {
if (!db) return;
xcb_xrm_database_free(db);
db = NULL;
}

View File

@@ -1,10 +1,20 @@
#include "window.h"
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <xcb/xcb.h>
#include <xcb/xcb_aux.h>
#include <xcb/xcb_ewmh.h>
#include <xcb/xcb_icccm.h>
#include <xcb/xproto.h>
#include "app.h"
#include "utils.h"
#include "xcb-private.h"
#include "xcb.h"
void set_window_class_instance(xcb_window_t window, const char *class,
const char *instance) {
@@ -24,3 +34,342 @@ void set_window_class_instance(xcb_window_t window, const char *class,
xcb_icccm_set_wm_class(conn, window, length, str);
free(str);
}
static char broken[] = "broken";
void get_window_class_instance(xcb_window_t window,
window_class_instance_t *class_instance) {
xcb_get_property_cookie_t cookie = xcb_icccm_get_wm_class(conn, window);
xcb_icccm_get_wm_class_reply_t prop;
const char *class = NULL;
const char *instance = NULL;
bool has_reply = xcb_icccm_get_wm_class_reply(conn, cookie, &prop, NULL);
if (has_reply) {
class = prop.class_name;
instance = prop.instance_name;
} else {
class = broken;
instance = broken;
}
strncpy(class_instance->class, class, LENGTH(class_instance->class));
strncpy(class_instance->instance, instance, LENGTH(class_instance->instance));
if (has_reply) xcb_icccm_get_wm_class_reply_wipe(&prop);
}
static visual_t *find_alpha_visual() {
xcb_depth_iterator_t depth_iter;
depth_iter = xcb_screen_allowed_depths_iterator(screen);
for (; depth_iter.rem; xcb_depth_next(&depth_iter)) {
if (depth_iter.data->depth != 32) continue;
xcb_visualtype_iterator_t visual_iter;
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) {
visual_t *r = ecalloc(1, sizeof(visual_t));
r->visual = visual_iter.data;
r->depth = depth_iter.data->depth;
return r;
}
}
}
return NULL;
}
static visual_t *find_root_visual() {
xcb_depth_iterator_t depth_iter;
depth_iter = xcb_screen_allowed_depths_iterator(screen);
xcb_visualid_t vid = screen->root_visual;
for (; depth_iter.rem; xcb_depth_next(&depth_iter)) {
xcb_visualtype_iterator_t visual_iter;
visual_iter = xcb_depth_visuals_iterator(depth_iter.data);
for (; visual_iter.rem; xcb_visualtype_next(&visual_iter)) {
if (visual_iter.data->visual_id == vid) {
visual_t *r = ecalloc(1, sizeof(visual_t));
r->visual = visual_iter.data;
r->depth = depth_iter.data->depth;
return r;
}
}
}
return NULL;
}
/**
* @brief 获取 xcb 的 visual 及其对应的 depth
* @param prefer_alpha 支持 alpha 的 visual 优先
* @returns 返回的信息使用后记得使用 free 释放
*/
visual_t *get_xcb_visual(bool prefer_alpha) {
if (!prefer_alpha) return find_root_visual();
visual_t *visual = find_alpha_visual();
if (visual == NULL) visual = find_root_visual();
return visual;
}
typedef struct bar_window_param_t {
uint8_t depth;
xcb_colormap_t colormap;
xcb_visualid_t visual_id;
xcb_visualtype_t *visual;
} bar_window_param_t;
static bar_window_param_t *prepare_bar_window_params(void) {
static xcb_colormap_t colormap = XCB_NONE;
visual_t *visual = get_xcb_visual(true);
xcb_visualid_t visual_id = visual->visual->visual_id;
if (colormap == XCB_NONE) {
colormap = xcb_generate_id(conn);
uint8_t alloc = XCB_COLORMAP_ALLOC_NONE;
xcb_create_colormap(conn, alloc, colormap, screen->root, visual_id);
}
bar_window_param_t *params = ecalloc(1, sizeof(bar_window_param_t));
params->depth = visual->depth;
params->visual = visual->visual;
params->visual_id = visual_id;
params->colormap = colormap;
free(visual);
return params;
}
bar_cairo_params_t *create_bar_window(int16_t x, int16_t y, uint16_t width,
uint16_t height, uint32_t background_argb,
cursor_t cursor) {
bar_window_param_t *params = prepare_bar_window_params();
xcb_visualtype_t *visual = params->visual;
xcb_window_t window = xcb_generate_id(conn);
uint32_t value_mask = XCB_CW_OVERRIDE_REDIRECT | XCB_CW_BACK_PIXEL |
XCB_CW_BORDER_PIXEL | XCB_CW_EVENT_MASK |
XCB_CW_CURSOR | XCB_CW_COLORMAP;
const xcb_create_window_value_list_t value_list = {
.override_redirect = true,
.background_pixel = background_argb,
.border_pixel = 0,
.event_mask = XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_EXPOSURE,
.cursor = get_xcb_cursor(cursor),
.colormap = params->colormap,
};
xcb_void_cookie_t cookie;
cookie = xcb_create_window_aux_checked(
conn, params->depth, window, screen->root, x, y, width, height, 0,
XCB_WINDOW_CLASS_INPUT_OUTPUT, params->visual_id, value_mask, &value_list);
free(params);
if (xcb_request_check(conn, cookie)) die("create bar window:");
cookie = xcb_map_window(conn, window);
if (xcb_request_check(conn, cookie)) die("map bar window:");
set_window_class_instance(window, APP_NAME, APP_NAME);
xcb_flush(conn);
bar_cairo_params_t *p = ecalloc(1, sizeof(bar_cairo_params_t));
p->conn = conn;
p->visual = visual;
p->window = window;
return p;
}
void destroy_window(xcb_window_t window) { xcb_destroy_window(conn, window); }
void configure_window(xcb_window_t window, int32_t x, int32_t y, uint32_t width,
uint32_t height, uint32_t border_width) {
xcb_config_window_t config_mask =
XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y | XCB_CONFIG_WINDOW_WIDTH |
XCB_CONFIG_WINDOW_HEIGHT | XCB_CONFIG_WINDOW_BORDER_WIDTH;
xcb_params_configure_window_t window_config = {
.x = x,
.y = y,
.width = width,
.height = height,
.border_width = border_width,
};
xcb_aux_configure_window(conn, window, config_mask, &window_config);
}
void move_window(xcb_window_t window, int32_t x, int32_t y) {
xcb_config_window_t mask = XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y;
xcb_params_configure_window_t position = {.x = x, .y = y};
xcb_aux_configure_window(conn, window, mask, &position);
}
void resize_window(xcb_window_t window, uint32_t width, uint32_t height) {
xcb_config_window_t config_mask =
XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT;
xcb_params_configure_window_t window_config = {
.width = width,
.height = height,
};
xcb_aux_configure_window(conn, window, config_mask, &window_config);
}
void change_window_cursor(xcb_window_t window, cursor_t cursor) {
xcb_change_window_attributes_value_list_t value_list = {
.cursor = get_xcb_cursor(cursor),
};
xcb_change_window_attributes_aux(conn, window, XCB_CW_CURSOR, &value_list);
xcb_flush(conn);
}
xcb_window_t get_root_window(void) { return screen->root; }
bool get_window_visible(xcb_window_t window) {
xcb_get_window_attributes_cookie_t cookie =
xcb_get_window_attributes(conn, window);
xcb_get_window_attributes_reply_t *reply =
xcb_get_window_attributes_reply(conn, cookie, NULL);
bool visible =
!reply->override_redirect && reply->map_state != XCB_MAP_STATE_UNVIEWABLE;
free(reply);
return visible;
}
xcb_window_t get_transient_window_for(xcb_window_t window) {
xcb_window_t transient_for = WINDOW_NONE;
xcb_get_property_cookie_t cookie =
xcb_icccm_get_wm_transient_for(conn, window);
if (xcb_icccm_get_wm_transient_for_reply(conn, cookie, &transient_for,
NULL)) {
return transient_for;
}
return WINDOW_NONE;
}
window_geometry_t *get_window_geometry(xcb_window_t window) {
xcb_get_geometry_cookie_t cookie = xcb_get_geometry(conn, window);
xcb_get_geometry_reply_t *reply = xcb_get_geometry_reply(conn, cookie, NULL);
window_geometry_t *geometry = ecalloc(1, sizeof(window_geometry_t));
geometry->x = reply->x;
geometry->y = reply->y;
geometry->width = reply->width;
geometry->height = reply->height;
geometry->border_width = reply->border_width;
free(reply);
return geometry;
}
void map_window(xcb_window_t window, bool map_sub_window) {
xcb_map_window(conn, window);
if (map_sub_window) xcb_map_subwindows(conn, window);
}
char *get_window_name(xcb_window_t window) {
xcb_get_property_cookie_t cookie = xcb_ewmh_get_wm_name(ewmh, window);
xcb_ewmh_get_utf8_strings_reply_t reply;
if (xcb_ewmh_get_wm_name_reply(ewmh, cookie, &reply, NULL)) {
char *name = ecalloc(reply.strings_len + 1, sizeof(char));
strncpy(name, reply.strings, reply.strings_len);
xcb_ewmh_get_utf8_strings_reply_wipe(&reply);
return name;
}
cookie = xcb_icccm_get_wm_name(conn, window);
xcb_icccm_get_text_property_reply_t prop;
if (xcb_icccm_get_wm_name_reply(conn, cookie, &prop, NULL)) {
char *name = ecalloc(prop.name_len + 1, sizeof(char));
strncpy(name, prop.name, prop.name_len);
xcb_icccm_get_text_property_reply_wipe(&prop);
return name;
}
{
char *name = ecalloc(LENGTH(broken), sizeof(char));
strncpy(name, broken, LENGTH(broken));
return name;
}
}
void set_window_event_mask(xcb_window_t window, bool clear) {
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_SUBSTRUCTURE_NOTIFY;
uint32_t event_mask = clear ? XCB_EVENT_MASK_NO_EVENT : init_event_mask;
xcb_params_cw_t params = {.event_mask = event_mask};
xcb_aux_change_window_attributes(conn, window, change_mask, &params);
}
void change_window_border_color(xcb_window_t window, uint32_t argb) {
xcb_params_cw_t params = {.border_pixel = argb};
xcb_aux_change_window_attributes(conn, window, XCB_CW_BORDER_PIXEL, &params);
}
void change_window_border_width(xcb_window_t window, uint32_t border_width) {
xcb_config_window_t config_mask = XCB_CONFIG_WINDOW_BORDER_WIDTH;
xcb_params_configure_window_t window_config = {.border_width = border_width};
xcb_aux_configure_window(conn, window, config_mask, &window_config);
}
void focus_window(xcb_window_t window) {
xcb_window_t root = screen->root;
if (window == WINDOW_NONE) {
xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, root,
XCB_CURRENT_TIME);
xcb_delete_property(conn, root, ewmh->_NET_ACTIVE_WINDOW);
} else {
xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, window,
XCB_CURRENT_TIME);
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root,
ewmh->_NET_ACTIVE_WINDOW, XCB_ATOM_WINDOW, 32, 1,
&window);
send_event(window, atom_wm_take_focus);
}
}
void raise_window(xcb_window_t window) {
xcb_params_configure_window_t params = {.stack_mode = XCB_STACK_MODE_TOP_IF};
xcb_aux_configure_window(conn, window, XCB_CONFIG_WINDOW_STACK_MODE, &params);
xcb_flush(conn);
}
void place_window_above_sibling(xcb_window_t window, xcb_window_t sibling) {
uint16_t mask = XCB_CONFIG_WINDOW_STACK_MODE | XCB_CONFIG_WINDOW_SIBLING;
xcb_params_configure_window_t params = {
.stack_mode = XCB_STACK_MODE_ABOVE,
.sibling = sibling,
};
xcb_aux_configure_window(conn, window, mask, &params);
}
void place_window_below_sibling(xcb_window_t window, xcb_window_t sibling) {
uint16_t mask = XCB_CONFIG_WINDOW_STACK_MODE | XCB_CONFIG_WINDOW_SIBLING;
xcb_params_configure_window_t params = {
.stack_mode = XCB_STACK_MODE_BELOW,
.sibling = sibling,
};
xcb_aux_configure_window(conn, window, mask, &params);
}
void kill_window(xcb_window_t window) {
xcb_grab_server(conn);
xcb_set_close_down_mode(conn, XCB_CLOSE_DOWN_DESTROY_ALL);
xcb_kill_client(conn, window);
xcb_flush(conn);
xcb_ungrab_server(conn);
}
void set_window_fullscreen_state(xcb_window_t window, bool fullscreen) {
xcb_atom_t atom = fullscreen ? ewmh->_NET_WM_STATE_FULLSCREEN : 0;
uint32_t data_len = fullscreen ? 1 : 0;
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, window, atom,
XCB_ATOM_WINDOW, 32, data_len, &atom);
}
void ungrab_any_button(xcb_window_t window) {
xcb_ungrab_button(conn, XCB_BUTTON_INDEX_ANY, window, XCB_MOD_MASK_ANY);
}
void grab_button(xcb_window_t window, uint8_t button, uint16_t modifiers) {
uint8_t owner_events = false; /* 事件仅发送给抓取者,不发送给其他窗口 */
uint16_t event_mask =
XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE;
xcb_grab_button(conn, owner_events, window, event_mask, XCB_GRAB_MODE_SYNC,
XCB_GRAB_MODE_ASYNC, XCB_NONE, XCB_NONE, button, modifiers);
}

View File

@@ -1,6 +1,13 @@
#pragma once
#include <stdbool.h>
#include <xcb/xproto.h>
void set_window_class_instance(xcb_window_t window, const char *class,
const char *instance);
typedef struct visual_t {
uint8_t depth;
xcb_visualtype_t *visual;
} visual_t;
visual_t *get_xcb_visual(bool prefer_alpha);

222
src/xcb/wm-extension.c Normal file
View File

@@ -0,0 +1,222 @@
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <xcb/xcb.h>
#include <xcb/xcb_ewmh.h>
#include <xcb/xcb_icccm.h>
#include <xcb/xproto.h>
#include "app.h"
#include "utils.h"
#include "window.h"
#include "xcb-private.h"
#include "xcb.h"
xcb_ewmh_connection_t *ewmh = NULL;
static bool ewmh_init_failed = false;
static xcb_window_t wm_check_window = XCB_NONE;
typedef struct atom_name_map_t {
const atom_t atom;
const char *name;
xcb_atom_t xcb_atom;
} atom_map_t;
static atom_map_t atom_map[] = {
{.atom = atom_wm_protocols, .name = "WM_PROTOCOLS"},
{.atom = atom_wm_delete, .name = "WM_DELETE_WINDOW"},
{.atom = atom_wm_take_focus, .name = "WM_TAKE_FOCUS"},
{.atom = atom_net_active_window, .name = "_NET_ACTIVE_WINDOW"},
{.atom = atom_net_wm_name, .name = "_NET_WM_NAME"},
{.atom = atom_net_wm_state, .name = "_NET_WM_STATE"},
{.atom = atom_net_wm_fullscreen, .name = "_NET_WM_STATE_FULLSCREEN"},
{.atom = atom__xrootpmap_id, .name = "_XROOTPMAP_ID"},
{.atom = atom_esetroot_pmap_id, .name = "ESETROOT_PMAP_ID"},
{.atom = atom_zswm_client_info, .name = "ZSWM_CLIENT_INFO"},
};
void init_icccm_extension(void) {
xcb_intern_atom_cookie_t cookie;
for (int i = 0; i < LENGTH(atom_map); i++) {
const char *name = atom_map[i].name;
cookie = xcb_intern_atom(conn, false, strlen(name), name);
xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(conn, cookie, NULL);
atom_map[i].xcb_atom = reply->atom;
free(reply);
}
}
void init_ewmh_extension(void) {
if (ewmh || ewmh_init_failed) return;
ewmh = ecalloc(1, sizeof(xcb_ewmh_connection_t));
xcb_intern_atom_cookie_t *cookie = xcb_ewmh_init_atoms(conn, ewmh);
if (!xcb_ewmh_init_atoms_replies(ewmh, cookie, NULL)) {
free(ewmh);
ewmh = NULL;
ewmh_init_failed = true;
return;
}
wm_check_window = xcb_generate_id(conn);
xcb_create_window(conn, XCB_COPY_FROM_PARENT, wm_check_window, screen->root,
0, 0, 1, 1, 0, XCB_WINDOW_CLASS_INPUT_ONLY,
screen->root_visual, XCB_NONE, NULL);
const char *class = APP_NAME "_wm_check";
set_window_class_instance(wm_check_window, class, class);
xcb_ewmh_set_wm_name(ewmh, wm_check_window, strlen(APP_NAME), APP_NAME);
xcb_ewmh_set_supporting_wm_check(ewmh, screen->root, wm_check_window);
xcb_ewmh_set_supporting_wm_check(ewmh, wm_check_window, wm_check_window);
uint8_t mode = XCB_PROP_MODE_REPLACE;
xcb_atom_t atom = XCB_ATOM_ATOM;
const xcb_atom_t state[] = {ewmh->_NET_WM_STATE_STICKY};
const xcb_atom_t supported[] = {
ewmh->_NET_ACTIVE_WINDOW,
ewmh->_NET_SUPPORTED,
ewmh->_NET_WM_NAME,
ewmh->_NET_WM_STATE,
ewmh->_NET_SUPPORTING_WM_CHECK,
ewmh->_NET_WM_STATE_FULLSCREEN,
ewmh->_NET_WM_WINDOW_TYPE,
ewmh->_NET_WM_WINDOW_TYPE_DIALOG,
ewmh->_NET_CLIENT_LIST,
};
xcb_change_property(conn, mode, wm_check_window, ewmh->_NET_WM_STATE, atom,
32, LENGTH(state), state);
xcb_change_property(conn, mode, screen->root, ewmh->_NET_SUPPORTED, atom, 32,
LENGTH(supported), supported);
xcb_delete_property(conn, screen->root, ewmh->_NET_CLIENT_LIST);
xcb_delete_property(conn, screen->root, get_xcb_atom(atom_zswm_client_info));
}
void clean_ewmh_extension(void) {
if (wm_check_window != XCB_NONE) {
xcb_destroy_window(conn, wm_check_window);
wm_check_window = XCB_NONE;
}
if (ewmh) {
if (!ewmh_init_failed) {
xcb_ewmh_connection_wipe(ewmh);
}
free(ewmh);
ewmh_init_failed = false;
ewmh = NULL;
}
}
xcb_atom_t get_xcb_atom(atom_t atom) {
if (atom < 0 || atom >= LENGTH(atom_map)) return XCB_ATOM_NONE;
return atom_map[atom].xcb_atom;
}
/**
* @brief 获取 xcb atom 名称
* @returns xcb atom 对应的名称,使用后记得用 free 释放
*/
char *get_xcb_atom_name(xcb_atom_t atom) {
xcb_get_atom_name_cookie_t cookie = xcb_get_atom_name(conn, atom);
xcb_get_atom_name_reply_t *reply =
xcb_get_atom_name_reply(conn, cookie, NULL);
int length = xcb_get_atom_name_name_length(reply);
char *name_buffer = ecalloc(1, length + 1);
const char *name = xcb_get_atom_name_name(reply);
strncpy(name_buffer, name, length);
free(reply);
return name_buffer;
}
bool send_event(xcb_window_t window, atom_t atom) {
if (atom < 0 || atom >= LENGTH(atom_map)) return false;
bool exist = false;
xcb_atom_t xcb_atom = atom_map[atom].xcb_atom;
xcb_get_property_cookie_t cookie =
xcb_icccm_get_wm_protocols(conn, window, xcb_atom);
xcb_icccm_get_wm_protocols_reply_t reply;
if (xcb_icccm_get_wm_protocols_reply(conn, cookie, &reply, NULL)) {
for (uint32_t i = 0; !exist && i < reply.atoms_len; i++) {
exist = reply.atoms[i] == xcb_atom;
}
xcb_icccm_get_wm_protocols_reply_wipe(&reply);
}
if (exist) {
xcb_client_message_event_t ev = {
.response_type = XCB_CLIENT_MESSAGE,
.format = 32,
.window = window,
.type = atom_map[atom_wm_protocols].xcb_atom,
.data = {.data32 = {xcb_atom, XCB_CURRENT_TIME, 0, 0, 0}},
};
xcb_send_event(conn, false, window, XCB_EVENT_MASK_NO_EVENT, (char *)&ev);
}
return exist;
}
int32_t get_window_state(xcb_window_t window) {
xcb_icccm_wm_hints_t hints;
xcb_get_property_cookie_t cookie = xcb_icccm_get_wm_hints(conn, window);
if (!xcb_icccm_get_wm_hints_reply(conn, cookie, &hints, NULL)) return -1;
return hints.initial_state;
}
void set_window_state(xcb_window_t window, wm_state_t state) {
xcb_icccm_wm_hints_t hints;
xcb_icccm_wm_hints_set_none(&hints);
switch (state) {
case wm_state_withdrawn:
xcb_icccm_wm_hints_set_withdrawn(&hints);
break;
case wm_state_normal:
xcb_icccm_wm_hints_set_normal(&hints);
break;
case wm_state_iconic:
xcb_icccm_wm_hints_set_iconic(&hints);
break;
}
xcb_icccm_set_wm_hints(conn, window, &hints);
}
void set_window_list(xcb_window_t *list, uint32_t length) {
int screen_nbr = 0;
for (int i = 0; i < ewmh->nb_screens; i++) {
if (screen == ewmh->screens[i]) {
screen_nbr = i;
break;
}
}
xcb_ewmh_set_client_list(ewmh, screen_nbr, length, list);
}
bool get_window_tag(xcb_window_t window, client_tag_info_t *client_info) {
xcb_get_property_cookie_t cookie =
xcb_get_property(conn, 0, window, get_xcb_atom(atom_zswm_client_info),
XCB_ATOM_CARDINAL, 0, 2);
xcb_get_property_reply_t *reply = xcb_get_property_reply(conn, cookie, NULL);
int length = xcb_get_property_value_length(reply);
bool result = length >= 2;
if (result) {
uint32_t *data = xcb_get_property_value(reply);
client_info->tags = data[0];
client_info->monitor_index = data[1];
}
free(reply);
return result;
}
void set_window_tag(xcb_window_t window, client_tag_info_t *client_info) {
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, window,
get_xcb_atom(atom_zswm_client_info), XCB_ATOM_CARDINAL,
32, 2, client_info);
}
void remove_window_tag(xcb_window_t window) {
xcb_delete_property(conn, window, get_xcb_atom(atom_zswm_client_info));
}

View File

@@ -1,6 +1,18 @@
#pragma once
#include <xcb/xcb.h>
#include <xcb/xcb_ewmh.h>
#include <xcb/xcb_keysyms.h>
#include "xcb.h"
extern xcb_ewmh_connection_t *ewmh;
extern xcb_connection_t *conn;
extern xcb_screen_t *screen;
extern xcb_key_symbols_t *key_symbols;
xcb_cursor_t get_xcb_cursor(cursor_t cursor);
void clean_xcb_cursor(void);
void init_icccm_extension(void);
void init_ewmh_extension(void);
void clean_ewmh_extension(void);

View File

@@ -2,15 +2,16 @@
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <xcb/xcb.h>
#include <xcb/xcb_aux.h>
#include <xcb/xcb_event.h>
#include <xcb/xcb_ewmh.h>
#include <xcb/xcb_icccm.h>
#include <xcb/xcb_image.h>
#include <xcb/xcb_keysyms.h>
#include <xcb/xinerama.h>
#include <xcb/xinput.h>
#include <xcb/xproto.h>
#include "app.h"
@@ -20,6 +21,7 @@
xcb_connection_t *conn = NULL;
xcb_screen_t *screen = NULL;
xcb_key_symbols_t *key_symbols = NULL;
bool has_other_wm_running(void) {
conn = xcb_connect(NULL, NULL);
@@ -30,18 +32,11 @@ bool has_other_wm_running(void) {
const xcb_setup_t *setup = xcb_get_setup(conn);
xcb_screen_iterator_t iter = xcb_setup_roots_iterator(setup);
screen = iter.data;
printf("screen number: %d\n", iter.rem);
uint32_t mask = XCB_CW_EVENT_MASK;
xcb_params_cw_t params = {
.event_mask = XCB_EVENT_MASK_LEAVE_WINDOW | XCB_EVENT_MASK_ENTER_WINDOW |
XCB_EVENT_MASK_POINTER_MOTION |
XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT |
XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY,
};
xcb_params_cw_t params = {.event_mask = XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT};
xcb_void_cookie_t cookie =
xcb_aux_change_window_attributes(conn, screen->root, mask, &params);
xcb_generic_error_t *error = xcb_request_check(conn, cookie);
if (error) {
xcb_aux_change_window_attributes_checked(conn, screen->root, mask, &params);
if (xcb_request_check(conn, cookie)) {
xcb_disconnect(conn);
return true;
}
@@ -49,6 +44,27 @@ bool has_other_wm_running(void) {
return false;
}
void setup_xcb(void) {
uint32_t event_mask =
XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY |
XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_POINTER_MOTION |
XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW |
XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_PROPERTY_CHANGE;
uint32_t mask = XCB_CW_EVENT_MASK;
xcb_params_cw_t params = {.event_mask = event_mask};
xcb_aux_change_window_attributes(conn, screen->root, mask, &params);
init_icccm_extension();
init_ewmh_extension();
}
/**
* @brief 获取 xcb 链接对应的文件描述符
*/
int get_xcb_connection_fd(void) { return xcb_get_file_descriptor(conn); }
void flush_xcb_connection(void) { xcb_flush(conn); }
/* xinerama 扩展是否激活 */
static bool xinerama_active(void) {
const char *name = "XINERAMA";
@@ -101,6 +117,7 @@ static monitor_rect_t *detect_monitor_rect_by_xinerama(void) {
}
prev = m;
}
free(reply);
return list;
}
@@ -124,51 +141,88 @@ monitor_rect_t *detect_monitor_rect(void) {
return rect;
}
static xcb_ewmh_connection_t *ewmh = NULL;
static bool ewmh_init_failed = false;
static xcb_window_t wm_check_window = XCB_NONE;
static void init_ewmh_extension(void) {
if (ewmh || ewmh_init_failed) return;
/**
* @brief 获取整个屏幕的宽高
* @returns 屏幕大小数据的指针,使用完后记得释放对应内存
*/
rect_size_t *get_screen_size(void) {
rect_size_t *size = ecalloc(1, sizeof(rect_size_t));
size->width = screen->width_in_pixels;
size->height = screen->height_in_pixels;
return size;
}
ewmh = ecalloc(1, sizeof(xcb_ewmh_connection_t));
xcb_intern_atom_cookie_t *cookie = xcb_ewmh_init_atoms(conn, ewmh);
if (!xcb_ewmh_init_atoms_replies(ewmh, cookie, NULL)) {
free(ewmh);
ewmh = NULL;
ewmh_init_failed = true;
return;
bar_cairo_params_t *prepare_wallpaper_surface_params(void) {
uint32_t width = screen->width_in_pixels;
uint32_t height = screen->height_in_pixels;
xcb_window_t root = screen->root;
visual_t *v = get_xcb_visual(false);
static xcb_pixmap_t pixmap = XCB_PIXMAP_NONE;
if (pixmap != XCB_PIXMAP_NONE) {
xcb_kill_client(conn, pixmap);
xcb_free_pixmap(conn, pixmap);
}
wm_check_window = xcb_generate_id(conn);
xcb_create_window(conn, XCB_COPY_FROM_PARENT, wm_check_window, screen->root,
0, 0, 1, 1, 0, XCB_WINDOW_CLASS_INPUT_ONLY,
screen->root_visual, XCB_NONE, NULL);
const char *class = APP_NAME "_wm_check";
set_window_class_instance(wm_check_window, class, class);
xcb_ewmh_set_wm_name(ewmh, wm_check_window, strlen(APP_NAME), APP_NAME);
xcb_ewmh_set_supporting_wm_check(ewmh, screen->root, wm_check_window);
xcb_ewmh_set_supporting_wm_check(ewmh, wm_check_window, wm_check_window);
uint8_t mode = XCB_PROP_MODE_REPLACE;
uint8_t format = XCB_INPUT_PROPERTY_FORMAT_32_BITS;
xcb_atom_t atom = XCB_ATOM_ATOM;
const xcb_atom_t state[] = {ewmh->_NET_WM_STATE_STICKY};
const xcb_atom_t supported[] = {
ewmh->_NET_ACTIVE_WINDOW,
ewmh->_NET_SUPPORTED,
ewmh->_NET_WM_NAME,
ewmh->_NET_WM_STATE,
ewmh->_NET_SUPPORTING_WM_CHECK,
ewmh->_NET_WM_STATE_FULLSCREEN,
ewmh->_NET_WM_WINDOW_TYPE,
ewmh->_NET_WM_WINDOW_TYPE_DIALOG,
ewmh->_NET_CLIENT_LIST,
pixmap = xcb_generate_id(conn);
xcb_create_pixmap(conn, v->depth, pixmap, root, width, height);
xcb_gcontext_t gc = xcb_generate_id(conn);
xcb_create_gc(conn, gc, pixmap, 0, NULL);
xcb_change_window_attributes_value_list_t value_list = {
.background_pixmap = pixmap,
};
xcb_change_property(conn, mode, wm_check_window, ewmh->_NET_WM_STATE, atom,
format, LENGTH(state), state);
xcb_change_property(conn, mode, screen->root, ewmh->_NET_SUPPORTED, atom,
format, LENGTH(supported), supported);
xcb_delete_property(conn, screen->root, ewmh->_NET_CLIENT_LIST);
xcb_change_window_attributes_aux(conn, root, XCB_CW_BACK_PIXMAP, &value_list);
/* 兼容合成器,不设置若开启合成器壁纸会不显示 */
uint8_t mode = XCB_PROP_MODE_REPLACE;
xcb_atom_t atom = get_xcb_atom(atom__xrootpmap_id);
xcb_change_property(conn, mode, root, atom, XCB_ATOM_PIXMAP, 32, 1, &pixmap);
atom = get_xcb_atom(atom_esetroot_pmap_id);
xcb_change_property(conn, mode, root, atom, XCB_ATOM_PIXMAP, 32, 1, &pixmap);
xcb_clear_area(conn, 0, root, 0, 0, width, height);
xcb_free_gc(conn, gc);
xcb_flush(conn);
bar_cairo_params_t *p = ecalloc(1, sizeof(bar_cairo_params_t));
p->conn = conn;
p->visual = v->visual;
p->window = pixmap;
free(v);
return p;
}
void refresh_root_window(void) {
xcb_clear_area(conn, 0, screen->root, 0, 0, 0, 0);
xcb_flush(conn);
}
void set_wallpaper(uint32_t *wallpaper_data) {
uint32_t width = screen->width_in_pixels;
uint32_t height = screen->height_in_pixels;
uint8_t depth = screen->root_depth;
xcb_window_t root = screen->root;
uint32_t data_size = width * height * sizeof(uint32_t);
uint8_t *data = (uint8_t *)wallpaper_data;
xcb_image_t *xcb_img =
xcb_image_create_native(conn, width, height, XCB_IMAGE_FORMAT_Z_PIXMAP,
depth, NULL, data_size, data);
xcb_pixmap_t pixmap = xcb_generate_id(conn);
xcb_create_pixmap(conn, depth, pixmap, root, width, height);
xcb_gcontext_t gc = xcb_generate_id(conn);
xcb_create_gc(conn, gc, pixmap, 0, NULL);
xcb_image_put(conn, pixmap, gc, xcb_img, 0, 0, 0);
xcb_change_window_attributes_value_list_t value_list = {
.background_pixmap = pixmap,
};
xcb_change_window_attributes_aux(conn, root, XCB_CW_BACK_PIXMAP, &value_list);
xcb_clear_area(conn, 0, root, 0, 0, width, height);
xcb_image_destroy(xcb_img);
xcb_free_gc(conn, gc);
xcb_flush(conn);
}
/**
@@ -177,70 +231,122 @@ static void init_ewmh_extension(void) {
* @note: 返回的窗口列表数据使用完后使用 free_window_list 释放
*/
window_list_t *scan_window_list(void) {
init_ewmh_extension();
xcb_query_tree_cookie_t cookie = xcb_query_tree(conn, screen->root);
xcb_query_tree_reply_t *reply = xcb_query_tree_reply(conn, cookie, NULL);
xcb_window_t *list = xcb_query_tree_children(reply);
int len = xcb_query_tree_children_length(reply);
xcb_query_tree_reply_t *tree_reply = xcb_query_tree_reply(conn, cookie, NULL);
if (!tree_reply) return NULL;
uint32_t count = 0;
xcb_window_t temp_windoow_array[len];
xcb_window_t *list = xcb_query_tree_children(tree_reply);
int len = xcb_query_tree_children_length(tree_reply);
if (!len) return NULL;
uint32_t normal_count = 0, transient_count = 0;
xcb_window_t *normal_window_array = ecalloc(len, sizeof(xcb_window_t));
xcb_window_t *transient_window_array = ecalloc(len, sizeof(xcb_window_t));
for (int i = 0; i < len; i++) {
xcb_window_t window = list[i];
xcb_get_window_attributes_cookie_t wa_cookie =
xcb_get_window_attributes(conn, window);
xcb_get_window_attributes_reply_t *reply =
xcb_get_window_attributes_reply_t *attribute_reply =
xcb_get_window_attributes_reply(conn, wa_cookie, NULL);
uint8_t override_redirect = reply->override_redirect;
uint8_t map_state = reply->map_state;
free(reply);
if (override_redirect || map_state != XCB_MAP_STATE_VIEWABLE) continue;
if (!attribute_reply) continue;
xcb_window_t transient_for = XCB_NONE;
xcb_get_property_cookie_t p_cookie =
xcb_icccm_get_wm_transient_for(conn, window);
xcb_icccm_get_wm_transient_for_reply(conn, p_cookie, &transient_for, NULL);
if (transient_for != XCB_NONE) continue;
temp_windoow_array[count] = window;
count++;
uint8_t override_redirect = attribute_reply->override_redirect;
uint8_t map_state = attribute_reply->map_state;
free(attribute_reply);
if (!(map_state == XCB_MAP_STATE_VIEWABLE ||
get_window_state(window) == wm_state_iconic)) {
continue;
}
if (!count) return NULL;
xcb_window_t *window_list = ecalloc(count, sizeof(xcb_window_t));
for (uint32_t i = 0; i < count; i++) window_list[i] = temp_windoow_array[i];
xcb_window_t transient_for = get_transient_window_for(window);
if (transient_for) {
transient_window_array[transient_count++] = window;
} else if (!override_redirect) {
normal_window_array[normal_count++] = window;
}
}
free(tree_reply);
window_list_t *r = ecalloc(1, sizeof(window_list_t));
r->count = count;
r->list = window_list;
r->normal_list = normal_window_array;
r->transient_list = transient_window_array;
r->normal_count = normal_count;
r->transient_count = transient_count;
return r;
}
void free_window_list(window_list_t *window_list) {
if (window_list == NULL) return;
free(window_list->list);
if (window_list->normal_list) free(window_list->normal_list);
if (window_list->transient_list) free(window_list->transient_list);
free(window_list);
}
void clean_xcb(void) {
if (wm_check_window != XCB_NONE) {
xcb_destroy_window(conn, wm_check_window);
wm_check_window = XCB_NONE;
}
if (ewmh) {
if (!ewmh_init_failed) {
xcb_ewmh_connection_wipe(ewmh);
}
ewmh_init_failed = false;
ewmh = NULL;
}
focus_window(XCB_WINDOW_NONE);
clean_xcb_cursor();
clean_ewmh_extension();
if (key_symbols) xcb_key_symbols_free(key_symbols);
xcb_disconnect(conn);
conn = NULL;
screen = NULL;
key_symbols = NULL;
}
void grab_keybindings(const keybinding_t *keys, const size_t length) {
xcb_window_t root = screen->root;
xcb_keycode_t key = XCB_GRAB_ANY;
xcb_ungrab_key(conn, key, root, XCB_MOD_MASK_ANY);
key_symbols = xcb_key_symbols_alloc(conn);
xcb_grab_mode_t mode = XCB_GRAB_MODE_ASYNC;
xcb_keycode_t *keycode = NULL;
xcb_mod_mask_t modifiers;
xcb_void_cookie_t cookie;
xcb_generic_error_t *error = NULL;
for (int i = 0; i < length; i++) {
xcb_keysym_t keysym = keys[i].key_symbol;
keycode = xcb_key_symbols_get_keycode(key_symbols, keysym);
modifiers = keys[i].modifiers;
cookie = xcb_grab_key(conn, key, root, modifiers, *keycode, mode, mode);
free(keycode);
error = xcb_request_check(conn, cookie);
if (error) {
clean_xcb();
die("%scannot grap key", APP_NAME);
}
}
}
bool get_pointer_position(int32_t *x, int32_t *y) {
xcb_query_pointer_cookie_t cookie = xcb_query_pointer(conn, screen->root);
xcb_query_pointer_reply_t *reply =
xcb_query_pointer_reply(conn, cookie, NULL);
if (reply) {
*x = reply->root_x;
*y = reply->root_y;
free(reply);
return true;
}
return false;
}
void set_pointer_position(int32_t x, int32_t y) {
xcb_warp_pointer(conn, XCB_WINDOW_NONE, screen->root, 0, 0, 1, 1, x, y);
xcb_flush(conn);
}
bool xcb_check_mask_event(xcb_event_mask_t event_mask) {
xcb_generic_event_t *event = xcb_poll_for_event(conn);
if (!event) return false;
bool result = XCB_EVENT_RESPONSE_TYPE(event) == event_mask;
free(event);
return result;
}