Compare commits
46 Commits
7eea0ee2d3
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
c6ade0144c
|
|||
|
6d9ff81f78
|
|||
|
0626a4c512
|
|||
|
4500a5adfd
|
|||
|
9315505085
|
|||
|
bec2162890
|
|||
|
b40f499e7b
|
|||
|
9dfcf62c3c
|
|||
|
55c3fa1156
|
|||
|
ac91ebbed3
|
|||
|
6719ccca2b
|
|||
|
8cccf23a32
|
|||
|
0c361a6593
|
|||
|
645083d3a7
|
|||
|
f31b18316b
|
|||
|
88d9dfd714
|
|||
|
e58a43386d
|
|||
|
91950dc2f4
|
|||
|
1b0ed3b9b8
|
|||
|
91c417a0e3
|
|||
|
e347dabc21
|
|||
|
fc543e6f9e
|
|||
|
ad1d413c62
|
|||
|
c5e2b4c081
|
|||
|
96629c3045
|
|||
|
8d9becbf0f
|
|||
|
a0c8baa863
|
|||
|
84b07ebcf4
|
|||
|
2c49c9f179
|
|||
|
9b77469ed2
|
|||
|
8321175915
|
|||
|
850b63bf82
|
|||
|
f225dc7011
|
|||
|
b28c9cd58b
|
|||
|
b645c4cf39
|
|||
|
9397e9e572
|
|||
|
a46522da3b
|
|||
|
3dd2f80128
|
|||
|
ebfed8245c
|
|||
|
a4f39d49f9
|
|||
|
52cadbda34
|
|||
|
54fcb27457
|
|||
|
8f892633cc
|
|||
|
165dd0733c
|
|||
|
0039cd2c43
|
|||
|
bac73368e3
|
3
Makefile
3
Makefile
@@ -28,13 +28,14 @@ wm: $(TARGET_NAME)
|
||||
DISPLAY=:3 $(TARGET)
|
||||
|
||||
debug: $(TARGET_NAME)
|
||||
DISPLAY=:3 valgrind $(TARGET)
|
||||
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
21
TODO.org
Normal 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 添加系统托盘显示
|
||||
@@ -12,13 +12,14 @@ 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 layout.c)
|
||||
add_executable(${APP_NAME} main.c utils.c layout.c status.c)
|
||||
|
||||
# use C17
|
||||
set_property(TARGET ${APP_NAME} PROPERTY C_STANDARD 17)
|
||||
@@ -40,10 +41,19 @@ pkg_check_modules(XCB REQUIRED
|
||||
)
|
||||
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)
|
||||
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"
|
||||
@@ -53,6 +63,7 @@ configure_file(
|
||||
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"
|
||||
@@ -64,6 +75,7 @@ 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}
|
||||
)
|
||||
|
||||
@@ -5,3 +5,11 @@
|
||||
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);
|
||||
|
||||
@@ -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@"
|
||||
|
||||
95
src/config.h
95
src/config.h
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <X11/XF86keysym.h>
|
||||
#include <X11/keysym.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
@@ -79,26 +80,77 @@ const rule_t rules[] = {
|
||||
},
|
||||
};
|
||||
|
||||
#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_q, quit, {.b = false}},
|
||||
{SUPER, XK_r, quit, {.b = true}},
|
||||
{SUPER, XK_1, select_tag, {.ui = 0}},
|
||||
{SUPER, XK_2, select_tag, {.ui = 1}},
|
||||
{SUPER, XK_3, select_tag, {.ui = 2}},
|
||||
{SUPER, XK_4, select_tag, {.ui = 3}},
|
||||
{SUPER, XK_5, select_tag, {.ui = 4}},
|
||||
{SUPER, XK_6, select_tag, {.ui = 5}},
|
||||
{SUPER, XK_7, select_tag, {.ui = 6}},
|
||||
{SUPER, XK_8, select_tag, {.ui = 7}},
|
||||
{SUPER, XK_9, select_tag, { .ui = 8 }},
|
||||
{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";
|
||||
@@ -107,16 +159,33 @@ 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_color = "#005577";
|
||||
const char *const status_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",
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <xcb/xproto.h>
|
||||
|
||||
#include "types.h"
|
||||
|
||||
@@ -40,8 +40,15 @@ typedef enum notify_detail_t {
|
||||
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 {
|
||||
@@ -52,6 +59,10 @@ typedef struct 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 {
|
||||
@@ -78,14 +89,18 @@ typedef struct 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 {
|
||||
@@ -96,6 +111,7 @@ typedef struct motion_notify_event_t {
|
||||
xcb_window_t window;
|
||||
int32_t x;
|
||||
int32_t y;
|
||||
uint32_t time; /* 时间戳(单位:ms) */
|
||||
} motion_notify_event_t;
|
||||
|
||||
typedef struct enter_notify_event_t {
|
||||
@@ -110,12 +126,28 @@ typedef struct focus_in_event_t {
|
||||
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 {
|
||||
|
||||
@@ -9,9 +9,13 @@ 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,
|
||||
color_set_t *color_set, uint32_t height, uint32_t tag_x_padding);
|
||||
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);
|
||||
|
||||
@@ -33,6 +33,9 @@ struct monitor_t {
|
||||
client_t *clients_stack;
|
||||
client_t *selected_client;
|
||||
|
||||
int32_t pointer_x; /* 记录光标 x 坐标位置 */
|
||||
int32_t pointer_y; /* 记录光标 y 坐标位置 */
|
||||
|
||||
xcb_window_t bar_window;
|
||||
};
|
||||
|
||||
@@ -52,6 +55,8 @@ struct client_t {
|
||||
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;
|
||||
@@ -109,9 +114,89 @@ typedef struct color_set_t {
|
||||
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_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;
|
||||
|
||||
@@ -10,8 +10,13 @@
|
||||
|
||||
#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, ...);
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#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);
|
||||
|
||||
@@ -40,15 +41,33 @@ typedef struct window_geometry_t {
|
||||
* @returns 返回几何信息指针,使用完后记得使用 free 释放内存
|
||||
*/
|
||||
window_geometry_t *get_window_geometry(xcb_window_t window);
|
||||
void map_window(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 init_window_event_mask(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;
|
||||
@@ -65,6 +84,8 @@ bar_cairo_params_t *create_bar_window(int16_t x, int16_t y, uint16_t width,
|
||||
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 {
|
||||
@@ -81,11 +102,15 @@ typedef struct rect_size_t {
|
||||
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);
|
||||
@@ -101,7 +126,40 @@ 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);
|
||||
|
||||
15
src/layout.c
15
src/layout.c
@@ -22,12 +22,23 @@ void maximize_client(client_t *c) {
|
||||
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 - 2 * c->border_width;
|
||||
c->height = m->monitor_height - 2 * c->border_width;
|
||||
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) {
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
#include "types.h"
|
||||
|
||||
#define CLIENT_VISIBLE(C) ((C)->tags & (C)->monitor->selected_tags)
|
||||
|
||||
/**
|
||||
* 获取下一个需要被布局算法自动管理的可见 client
|
||||
*/
|
||||
|
||||
820
src/main.c
820
src/main.c
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,9 @@
|
||||
#include <Imlib2.h>
|
||||
#include <cairo.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
@@ -11,7 +13,7 @@
|
||||
|
||||
/**
|
||||
* @brief 生成需要由 xcb 绘制的壁纸数据
|
||||
* @returns 壁纸数据,使用后记得释放
|
||||
* @returns 壁纸像素数据( RGBA 顺序),使用后记得使用 free 释放
|
||||
*/
|
||||
uint32_t *generate_wallpaper(const wallpaper_config_t *wallpaper_config,
|
||||
const monitor_t *monitors, const uint32_t index,
|
||||
@@ -23,11 +25,10 @@ uint32_t *generate_wallpaper(const wallpaper_config_t *wallpaper_config,
|
||||
if (!final_image) return NULL;
|
||||
|
||||
imlib_context_set_image(final_image);
|
||||
imlib_image_set_has_alpha(false);
|
||||
imlib_image_set_has_alpha(true);
|
||||
|
||||
/* 填充背景色 */
|
||||
imlib_context_set_color(0, 0, 0, 255);
|
||||
imlib_image_fill_rectangle(0, 0, screen_width, screen_height);
|
||||
/* 重置像素数据 */
|
||||
imlib_image_clear();
|
||||
|
||||
const monitor_t *monitor = monitors;
|
||||
uint32_t monitor_index = 0;
|
||||
@@ -89,3 +90,46 @@ uint32_t *generate_wallpaper(const wallpaper_config_t *wallpaper_config,
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -11,9 +11,11 @@
|
||||
#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;
|
||||
@@ -135,20 +137,80 @@ static void draw_rect(cairo_t *cr, int32_t x, int32_t y, uint32_t width,
|
||||
}
|
||||
}
|
||||
|
||||
void draw_bar(monitor_t *monitor, bool is_current_monitor,
|
||||
color_set_t *color_set, uint32_t height, uint32_t tag_x_padding) {
|
||||
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;
|
||||
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, &color_set->bar_bg, 0, 0, monitor->monitor_width, height);
|
||||
draw_background(cr, bar_bg, 0, 0, monitor->monitor_width, height);
|
||||
|
||||
/* 绘制 tags */
|
||||
for (uint32_t i = 0; i < g_strv_length(monitor->tags); i++) {
|
||||
@@ -157,15 +219,52 @@ void draw_bar(monitor_t *monitor, bool is_current_monitor,
|
||||
text = monitor->tags[i];
|
||||
width = get_text_width(text) + tag_x_padding * 2;
|
||||
|
||||
color = selected ? &color_set->active_tag_bg : &color_set->tag_bg;
|
||||
if (color->rgba != color_set->bar_bg.rgba) {
|
||||
draw_background(cr, color, x, 0, width, height);
|
||||
}
|
||||
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) {
|
||||
|
||||
BIN
src/resources/icons/corner-left-down-line.png
Normal file
BIN
src/resources/icons/corner-left-down-line.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 766 B |
BIN
src/resources/icons/corner-right-up-line.png
Normal file
BIN
src/resources/icons/corner-right-up-line.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 786 B |
BIN
src/resources/icons/cpu-line.png
Normal file
BIN
src/resources/icons/cpu-line.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 769 B |
BIN
src/resources/icons/ram-line.png
Normal file
BIN
src/resources/icons/ram-line.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 564 B |
BIN
src/resources/icons/time-line.png
Normal file
BIN
src/resources/icons/time-line.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.4 KiB |
BIN
src/resources/icons/volume-up-fill.png
Normal file
BIN
src/resources/icons/volume-up-fill.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.9 KiB |
407
src/status.c
Normal file
407
src/status.c
Normal 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(¤t_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
12
src/status.h
Normal 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);
|
||||
13
src/utils.c
13
src/utils.c
@@ -2,11 +2,14 @@
|
||||
|
||||
#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);
|
||||
@@ -105,11 +108,11 @@ void extract_color_channel(const uint32_t rgba, double *red, double *green,
|
||||
*alpha = COLOR_SPLIT(rgba, 0);
|
||||
}
|
||||
|
||||
#define INTERSECT(x, y, w, h, m) \
|
||||
(MAX(0, MIN((x) + (w), (m)->window_x + (m)->window_width) - \
|
||||
MAX((x), (m)->window_x)) * \
|
||||
MAX(0, MIN((y) + (h), (m)->window_y + (m)->window_height) - \
|
||||
MAX((y), (m)->window_y)))
|
||||
#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;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <xcb/xcb.h>
|
||||
#include <xcb/xcb_event.h>
|
||||
#include <xcb/xproto.h>
|
||||
@@ -17,12 +18,13 @@ struct event_adapter_t {
|
||||
static void convert_event(xcb_generic_event_t *xcb_event,
|
||||
generic_event_t *event) {
|
||||
uint8_t event_type = XCB_EVENT_RESPONSE_TYPE(xcb_event);
|
||||
const char *label = xcb_event_get_label(event_type);
|
||||
logger("=========================== %s ===========================\n", label);
|
||||
switch (event_type) {
|
||||
case XCB_MAPPING_NOTIFY:
|
||||
event->type = EVENT_TYPE_MAPPING_NOTIFY;
|
||||
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;
|
||||
@@ -32,9 +34,18 @@ static void convert_event(xcb_generic_event_t *xcb_event,
|
||||
event->key_press.modifiers = ev->state;
|
||||
break;
|
||||
}
|
||||
case XCB_BUTTON_PRESS:
|
||||
event->type = EVENT_TYPE_BUTTON_PRESS;
|
||||
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;
|
||||
@@ -65,13 +76,22 @@ static void convert_event(xcb_generic_event_t *xcb_event,
|
||||
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;
|
||||
} break;
|
||||
case XCB_DESTROY_NOTIFY:
|
||||
event->type = EVENT_TYPE_DESTROY_NOTIFY;
|
||||
event->unmap_notify.send_event = XCB_EVENT_SENT(ev);
|
||||
break;
|
||||
case XCB_EXPOSE:
|
||||
event->type = EVENT_TYPE_EXPOSE;
|
||||
}
|
||||
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;
|
||||
@@ -81,6 +101,7 @@ static void convert_event(xcb_generic_event_t *xcb_event,
|
||||
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: {
|
||||
@@ -96,16 +117,32 @@ static void convert_event(xcb_generic_event_t *xcb_event,
|
||||
event->focus_in.type = EVENT_TYPE_FOCUS_IN;
|
||||
event->focus_in.window = ev->event;
|
||||
} break;
|
||||
case XCB_PROPERTY_NOTIFY:
|
||||
event->type = EVENT_TYPE_PROPERTY_NOTIFY;
|
||||
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:
|
||||
event->type = EVENT_TYPE_CLIENT_MESSAGE;
|
||||
}
|
||||
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) {
|
||||
|
||||
108
src/xcb/window.c
108
src/xcb/window.c
@@ -43,7 +43,8 @@ void get_window_class_instance(xcb_window_t window,
|
||||
xcb_icccm_get_wm_class_reply_t prop;
|
||||
const char *class = NULL;
|
||||
const char *instance = NULL;
|
||||
if (xcb_icccm_get_wm_class_reply(conn, cookie, &prop, 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 {
|
||||
@@ -54,14 +55,9 @@ void get_window_class_instance(xcb_window_t window,
|
||||
strncpy(class_instance->class, class, LENGTH(class_instance->class));
|
||||
strncpy(class_instance->instance, instance, LENGTH(class_instance->instance));
|
||||
|
||||
xcb_icccm_get_wm_class_reply_wipe(&prop);
|
||||
if (has_reply) xcb_icccm_get_wm_class_reply_wipe(&prop);
|
||||
}
|
||||
|
||||
typedef struct visual_t {
|
||||
uint8_t depth;
|
||||
xcb_visualtype_t *visual;
|
||||
} visual_t;
|
||||
|
||||
static visual_t *find_alpha_visual() {
|
||||
xcb_depth_iterator_t depth_iter;
|
||||
depth_iter = xcb_screen_allowed_depths_iterator(screen);
|
||||
@@ -195,6 +191,22 @@ void configure_window(xcb_window_t window, int32_t x, int32_t y, uint32_t 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),
|
||||
@@ -218,11 +230,14 @@ bool get_window_visible(xcb_window_t window) {
|
||||
}
|
||||
|
||||
xcb_window_t get_transient_window_for(xcb_window_t window) {
|
||||
xcb_window_t transient_for = XCB_NONE;
|
||||
xcb_window_t transient_for = WINDOW_NONE;
|
||||
xcb_get_property_cookie_t cookie =
|
||||
xcb_icccm_get_wm_transient_for(conn, window);
|
||||
xcb_icccm_get_wm_transient_for_reply(conn, cookie, &transient_for, NULL);
|
||||
return transient_for;
|
||||
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) {
|
||||
@@ -240,9 +255,9 @@ window_geometry_t *get_window_geometry(xcb_window_t window) {
|
||||
return geometry;
|
||||
}
|
||||
|
||||
void map_window(xcb_window_t window) {
|
||||
void map_window(xcb_window_t window, bool map_sub_window) {
|
||||
xcb_map_window(conn, window);
|
||||
xcb_map_subwindows(conn, window);
|
||||
if (map_sub_window) xcb_map_subwindows(conn, window);
|
||||
}
|
||||
|
||||
char *get_window_name(xcb_window_t window) {
|
||||
@@ -271,13 +286,13 @@ char *get_window_name(xcb_window_t window) {
|
||||
}
|
||||
}
|
||||
|
||||
void init_window_event_mask(xcb_window_t window) {
|
||||
void set_window_event_mask(xcb_window_t window, bool clear) {
|
||||
xcb_cw_t change_mask = XCB_CW_EVENT_MASK;
|
||||
xcb_params_cw_t params = {
|
||||
.event_mask = XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_FOCUS_CHANGE |
|
||||
XCB_EVENT_MASK_PROPERTY_CHANGE |
|
||||
XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY,
|
||||
};
|
||||
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, ¶ms);
|
||||
}
|
||||
|
||||
@@ -286,6 +301,12 @@ void change_window_border_color(xcb_window_t window, uint32_t argb) {
|
||||
xcb_aux_change_window_attributes(conn, window, XCB_CW_BORDER_PIXEL, ¶ms);
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -301,3 +322,54 @@ void focus_window(xcb_window_t 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, ¶ms);
|
||||
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, ¶ms);
|
||||
}
|
||||
|
||||
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, ¶ms);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
#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>
|
||||
|
||||
@@ -24,6 +26,13 @@ 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) {
|
||||
@@ -78,6 +87,7 @@ void init_ewmh_extension(void) {
|
||||
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) {
|
||||
@@ -96,6 +106,28 @@ void clean_ewmh_extension(void) {
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
@@ -125,3 +157,66 @@ bool send_event(xcb_window_t window, atom_t atom) {
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
133
src/xcb/xcb.c
133
src/xcb/xcb.c
@@ -10,11 +10,13 @@
|
||||
#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/xproto.h>
|
||||
|
||||
#include "app.h"
|
||||
#include "utils.h"
|
||||
#include "window.h"
|
||||
#include "xcb-private.h"
|
||||
|
||||
xcb_connection_t *conn = NULL;
|
||||
@@ -31,16 +33,10 @@ bool has_other_wm_running(void) {
|
||||
xcb_screen_iterator_t iter = xcb_setup_roots_iterator(setup);
|
||||
screen = iter.data;
|
||||
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_checked(conn, screen->root, mask, ¶ms);
|
||||
xcb_generic_error_t *error = xcb_request_check(conn, cookie);
|
||||
if (error) {
|
||||
if (xcb_request_check(conn, cookie)) {
|
||||
xcb_disconnect(conn);
|
||||
return true;
|
||||
}
|
||||
@@ -48,6 +44,20 @@ 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, ¶ms);
|
||||
|
||||
init_icccm_extension();
|
||||
init_ewmh_extension();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取 xcb 链接对应的文件描述符
|
||||
*/
|
||||
@@ -142,6 +152,52 @@ rect_size_t *get_screen_size(void) {
|
||||
return size;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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_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;
|
||||
@@ -175,18 +231,17 @@ void set_wallpaper(uint32_t *wallpaper_data) {
|
||||
* @note: 返回的窗口列表数据使用完后使用 free_window_list 释放
|
||||
*/
|
||||
window_list_t *scan_window_list(void) {
|
||||
init_icccm_extension();
|
||||
init_ewmh_extension();
|
||||
|
||||
xcb_query_tree_cookie_t cookie = xcb_query_tree(conn, screen->root);
|
||||
xcb_query_tree_reply_t *tree_reply = xcb_query_tree_reply(conn, cookie, NULL);
|
||||
if (!tree_reply) return NULL;
|
||||
|
||||
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 count = 0;
|
||||
xcb_window_t temp_window_array[len];
|
||||
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];
|
||||
|
||||
@@ -199,43 +254,46 @@ window_list_t *scan_window_list(void) {
|
||||
uint8_t override_redirect = attribute_reply->override_redirect;
|
||||
uint8_t map_state = attribute_reply->map_state;
|
||||
free(attribute_reply);
|
||||
if (override_redirect || map_state != XCB_MAP_STATE_VIEWABLE) continue;
|
||||
if (!(map_state == XCB_MAP_STATE_VIEWABLE ||
|
||||
get_window_state(window) == wm_state_iconic)) {
|
||||
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_window_array[count] = window;
|
||||
count++;
|
||||
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);
|
||||
|
||||
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_window_array[i];
|
||||
|
||||
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) {
|
||||
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) {
|
||||
@@ -255,6 +313,7 @@ void grab_keybindings(const keybinding_t *keys, const size_t length) {
|
||||
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();
|
||||
@@ -277,3 +336,17 @@ bool get_pointer_position(int32_t *x, int32_t *y) {
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user