Files
zswm/src/xcb/xcb.c
2025-08-16 05:30:05 +08:00

304 lines
9.1 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include "xcb.h"
#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/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;
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_LEAVE_WINDOW | XCB_EVENT_MASK_ENTER_WINDOW |
XCB_EVENT_MASK_POINTER_MOTION |
XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT |
XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY,
};
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_disconnect(conn);
return true;
}
return false;
}
/**
* @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;
}
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);
}
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;
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);
}
/**
* 扫描窗口列表
* @returns 若无窗口,返回 NULL否则返回窗口列表
* @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 *tree_reply = xcb_query_tree_reply(conn, cookie, NULL);
xcb_window_t *list = xcb_query_tree_children(tree_reply);
int len = xcb_query_tree_children_length(tree_reply);
uint32_t count = 0;
xcb_window_t temp_windoow_array[len];
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);
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;
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++;
}
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_windoow_array[i];
window_list_t *r = ecalloc(1, sizeof(window_list_t));
r->count = count;
r->list = window_list;
return r;
}
void free_window_list(window_list_t *window_list) {
if (window_list == NULL) return;
free(window_list->list);
free(window_list);
}
void start_xcb_event_loop(void) {
xcb_generic_event_t *event = NULL;
while ((event = xcb_poll_for_event(conn)) != NULL) {
uint8_t event_type = XCB_EVENT_RESPONSE_TYPE(event);
const char *label = xcb_event_get_label(event_type);
printf("================== %s ==================\n", label);
free(event);
}
}
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);
}
free(ewmh);
ewmh_init_failed = false;
ewmh = NULL;
}
xcb_disconnect(conn);
conn = NULL;
screen = NULL;
}