344 lines
10 KiB
C
344 lines
10 KiB
C
#include "xcb.h"
|
||
|
||
#include <stdbool.h>
|
||
#include <stdint.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/xinerama.h>
|
||
#include <xcb/xproto.h>
|
||
|
||
#include "app.h"
|
||
#include "utils.h"
|
||
#include "window.h"
|
||
#include "xcb-private.h"
|
||
|
||
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);
|
||
if (!conn || xcb_connection_has_error(conn)) {
|
||
die("connection:");
|
||
}
|
||
|
||
const xcb_setup_t *setup = xcb_get_setup(conn);
|
||
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_SUBSTRUCTURE_REDIRECT};
|
||
xcb_void_cookie_t cookie =
|
||
xcb_aux_change_window_attributes_checked(conn, screen->root, mask, ¶ms);
|
||
if (xcb_request_check(conn, cookie)) {
|
||
xcb_disconnect(conn);
|
||
return true;
|
||
}
|
||
|
||
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 链接对应的文件描述符
|
||
*/
|
||
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";
|
||
xcb_query_extension_cookie_t xinerama_cookie =
|
||
xcb_query_extension(conn, strlen(name), name);
|
||
xcb_query_extension_reply_t *xinerama_reply =
|
||
xcb_query_extension_reply(conn, xinerama_cookie, NULL);
|
||
|
||
/* 扩展不存在 */
|
||
if (!xinerama_reply || !xinerama_reply->present) {
|
||
free(xinerama_reply);
|
||
return false;
|
||
}
|
||
|
||
xcb_xinerama_is_active_cookie_t active_cookie = xcb_xinerama_is_active(conn);
|
||
xcb_xinerama_is_active_reply_t *active_reply =
|
||
xcb_xinerama_is_active_reply(conn, active_cookie, NULL);
|
||
bool active = active_reply && active_reply->state;
|
||
free(xinerama_reply);
|
||
free(active_reply);
|
||
return active;
|
||
}
|
||
|
||
static monitor_rect_t *detect_monitor_rect_by_xinerama(void) {
|
||
monitor_rect_t *list, *prev;
|
||
xcb_xinerama_query_screens_cookie_t cookie = xcb_xinerama_query_screens(conn);
|
||
xcb_xinerama_query_screens_reply_t *reply =
|
||
xcb_xinerama_query_screens_reply(conn, cookie, NULL);
|
||
xcb_xinerama_screen_info_t *screen_info =
|
||
xcb_xinerama_query_screens_screen_info(reply);
|
||
|
||
if (screen_info == NULL) {
|
||
die("no monitor finded");
|
||
}
|
||
|
||
int count = xcb_xinerama_query_screens_screen_info_length(reply);
|
||
for (int i = 0; i < count; i++) {
|
||
monitor_rect_t *m = ecalloc(1, sizeof(monitor_rect_t));
|
||
xcb_xinerama_screen_info_t info = screen_info[i];
|
||
m->x = info.x_org;
|
||
m->y = info.y_org;
|
||
m->width = info.width;
|
||
m->height = info.height;
|
||
m->next = NULL;
|
||
|
||
if (i == 0) {
|
||
list = m;
|
||
} else {
|
||
prev->next = m;
|
||
}
|
||
prev = m;
|
||
}
|
||
free(reply);
|
||
|
||
return list;
|
||
}
|
||
|
||
/**
|
||
* 探测屏幕信息
|
||
* @note: 返回值使用完后记得释放其内存
|
||
*/
|
||
monitor_rect_t *detect_monitor_rect(void) {
|
||
if (xinerama_active()) {
|
||
return detect_monitor_rect_by_xinerama();
|
||
}
|
||
|
||
if (!screen) return NULL;
|
||
|
||
monitor_rect_t *rect = ecalloc(1, sizeof(monitor_rect_t));
|
||
rect->x = 0;
|
||
rect->y = 0;
|
||
rect->width = screen->width_in_pixels;
|
||
rect->height = screen->height_in_pixels;
|
||
return rect;
|
||
}
|
||
|
||
/**
|
||
* @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;
|
||
}
|
||
|
||
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;
|
||
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);
|
||
}
|
||
|
||
/**
|
||
* 扫描窗口列表
|
||
* @returns 若无窗口,返回 NULL,否则返回窗口列表
|
||
* @note: 返回的窗口列表数据使用完后使用 free_window_list 释放
|
||
*/
|
||
window_list_t *scan_window_list(void) {
|
||
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 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 *attribute_reply =
|
||
xcb_get_window_attributes_reply(conn, wa_cookie, NULL);
|
||
if (!attribute_reply) continue;
|
||
|
||
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;
|
||
}
|
||
|
||
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->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;
|
||
|
||
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();
|
||
xcb_disconnect(conn);
|
||
conn = NULL;
|
||
screen = 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);
|
||
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;
|
||
}
|
||
|
||
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;
|
||
}
|