Files
zdwm/src/backend/x11/backend.c
Zedhugh Chen 88129293a0 feat: 补发 ConfigureNotify 通知客户端实际几何
当 WM 拒绝窗口的 ConfigureRequest(因窗口需要重新布局)或映射
窗口时,按 ICCCM 规范主动向客户端发送 synthetic ConfigureNotify,
告知其当前真实几何,避免客户端状态不一致。

- core: 新增 NOTIFY_CONFIGURE 命令与 CONFIGURE_NOTIFY 效果,
  由 policy 在 window_need_layout 分支生成,plan 层下发
- backend(x11): 新增 configure 批次,map/notify 时查询实际几何
  并合成 ConfigureNotify 事件下发
2026-07-29 04:31:28 +08:00

923 lines
27 KiB
C

#include "interface/backend.h"
#include <cairo-xcb.h>
#include <cairo.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <xcb/randr.h>
#include <xcb/xcb.h>
#include <xcb/xcb_aux.h>
#include <xcb/xcb_icccm.h>
#include <xcb/xcb_keysyms.h>
#include <xcb/xfixes.h>
#include <xcb/xinerama.h>
#include <xcb/xproto.h>
#include "backend/output_utils.h"
#include "backend/x11/cursor.h"
#include "backend/x11/tray.h"
#include "backend/x11/window.h"
#include "base/app.h"
#include "base/array.h"
#include "base/log.h"
#include "base/macros.h"
#include "base/memory.h"
#include "common/window.h"
#include "internal.h"
typedef struct atom_item_t {
const char *name;
size_t len;
xcb_atom_t *atom;
} atom_item_t;
static void atoms_init(backend_t *backend) {
xcb_connection_t *conn = backend->conn;
atoms_t *atoms = &backend->atoms;
#define ATOM_ITEM(name) {#name, sizeof(#name) - 1, &atoms->name},
atom_item_t atom_list[] = {ATOM_LIST(ATOM_ITEM)};
#undef ATOM_ITEM
xcb_intern_atom_cookie_t cookies[countof(atom_list)];
for (size_t i = 0; i < countof(atom_list); ++i) {
const atom_item_t *atom = &atom_list[i];
cookies[i] = xcb_intern_atom_unchecked(conn, false, atom->len, atom->name);
}
xcb_intern_atom_reply_t *reply = nullptr;
for (size_t i = 0; i < countof(atom_list); ++i) {
reply = xcb_intern_atom_reply(conn, cookies[i], nullptr);
if (!reply) continue;
atom_item_t *atom = &atom_list[i];
*atom->atom = reply->atom;
p_delete(&reply);
}
#define EWMH_ITEM(name) atoms->name,
xcb_atom_t ewmh_supported_atoms[] = {EWMH_ATOMS(EWMH_ITEM)};
#undef EWMH_ITEM
auto root = backend->screen->root;
xcb_change_property(
conn,
XCB_PROP_MODE_REPLACE,
root,
atoms->_NET_SUPPORTED,
XCB_ATOM_ATOM,
32,
countof(ewmh_supported_atoms),
ewmh_supported_atoms
);
}
static void create_wm_check_window(backend_t *backend) {
xcb_connection_t *conn = backend->conn;
xcb_window_t root = backend->screen->root;
uint8_t depth = backend->screen->root_depth;
xcb_visualid_t v = backend->screen->root_visual;
uint16_t _c = XCB_WINDOW_CLASS_COPY_FROM_PARENT;
uint32_t m = XCB_NONE;
void *p = nullptr;
xcb_window_t win = xcb_generate_id(conn);
xcb_create_window(conn, depth, win, root, -1, -1, 1, 1, 0, _c, v, m, p);
window_set_class_instance(conn, win);
window_set_name_static(conn, win, APP_NAME);
uint8_t mode = XCB_PROP_MODE_REPLACE;
xcb_atom_t prop = backend->atoms._NET_WM_NAME;
xcb_atom_t type = backend->atoms.UTF8_STRING;
auto data_len = sizeof(APP_NAME) - 1;
xcb_change_property(conn, mode, win, prop, type, 8, data_len, APP_NAME);
prop = backend->atoms._NET_SUPPORTING_WM_CHECK;
type = XCB_ATOM_WINDOW;
xcb_change_property(conn, mode, win, prop, type, 32, 1, &win);
xcb_change_property(conn, mode, root, prop, type, 32, 1, &win);
prop = backend->atoms._NET_WM_PID;
type = XCB_ATOM_CARDINAL;
pid_t pid = getpid();
xcb_change_property(conn, mode, win, prop, type, 32, 1, &pid);
}
static void create_no_focus_window(backend_t *backend) {
xcb_connection_t *conn = backend->conn;
xcb_window_t root = backend->screen->root;
uint8_t d = backend->screen->root_depth;
xcb_visualid_t v = backend->screen->root_visual;
uint16_t _c = XCB_WINDOW_CLASS_COPY_FROM_PARENT;
xcb_window_t win = xcb_generate_id(conn);
uint32_t m = XCB_CW_BACK_PIXEL | XCB_CW_BORDER_PIXEL |
XCB_CW_OVERRIDE_REDIRECT | XCB_CW_COLORMAP;
const xcb_create_window_value_list_t p = {
.override_redirect = true,
.background_pixel = backend->screen->black_pixel,
.border_pixel = backend->screen->black_pixel,
.colormap = backend->screen->default_colormap,
};
xcb_create_window_aux(conn, d, win, root, -1, -1, 1, 1, 0, _c, v, m, &p);
window_set_class_instance(conn, win);
window_set_name_static(conn, win, APP_NAME " no input window");
xcb_map_window(conn, win);
backend->window_no_focus = win;
}
backend_t *backend_create(const char *display_name) {
int screen_num = 0;
xcb_connection_t *conn = xcb_connect(display_name, &screen_num);
int xcb_conn_error = xcb_connection_has_error(conn);
if (xcb_conn_error) {
fatal("cannot open display %s, error %d", display_name, xcb_conn_error);
}
xcb_screen_t *screen = xcb_aux_get_screen(conn, screen_num);
if (!screen) fatal("cannot get screen info");
xcb_window_t root = screen->root;
uint32_t mask = XCB_CW_EVENT_MASK;
const xcb_params_cw_t params = {
.event_mask = XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT,
};
xcb_void_cookie_t cookie =
xcb_aux_change_window_attributes_checked(conn, root, mask, &params);
if (xcb_request_check(conn, cookie)) {
fatal(
"another window manager is already running (cannot select "
"SubstructureRedirect)"
);
}
backend_t *backend = p_new(backend_t, 1);
backend->conn = conn;
backend->screen = screen;
backend->screenp = screen_num;
xcb_prefetch_extension_data(conn, &xcb_xfixes_id);
const xcb_query_extension_reply_t *query =
xcb_get_extension_data(conn, &xcb_xfixes_id);
if (query && query->present) {
xcb_xfixes_query_version_cookie_t cookie = xcb_xfixes_query_version(
conn,
XCB_XFIXES_MAJOR_VERSION,
XCB_XFIXES_MINOR_VERSION
);
xcb_xfixes_query_version_reply_t *reply =
xcb_xfixes_query_version_reply(conn, cookie, nullptr);
if (reply) {
backend->have_xfixes = true;
p_delete(&reply);
}
} else {
backend->have_xfixes = false;
}
backend->key_symbols = xcb_key_symbols_alloc(conn);
cursor_init(backend);
atoms_init(backend);
create_wm_check_window(backend);
create_no_focus_window(backend);
window_change_cursor(backend, root, ZDWM_CURSOR_NORMAL);
return backend;
}
void backend_destroy(backend_t *backend) {
if (!backend) return;
tray_cleanup(backend);
p_delete(&backend->config_list.cfgs);
p_clear(&backend->config_list, 1);
window_list_cleanup(&backend->unmap);
window_list_cleanup(&backend->map);
window_list_cleanup(&backend->kill);
xcb_key_symbols_free(backend->key_symbols);
backend->key_symbols = nullptr;
cursor_cleanup(backend);
xcb_disconnect(backend->conn);
backend->conn = nullptr;
free(backend);
}
int backend_get_fd(backend_t *backend) {
if (!backend || !backend->conn) return -1;
return xcb_get_file_descriptor(backend->conn);
}
static bool detect_monitor_by_randr(
const backend_t *backend,
output_info_t **outputs,
size_t *count
) {
xcb_prefetch_extension_data(backend->conn, &xcb_randr_id);
const xcb_query_extension_reply_t *query =
xcb_get_extension_data(backend->conn, &xcb_randr_id);
if (!query || !query->present) return false;
xcb_randr_query_version_cookie_t cookie = xcb_randr_query_version(
backend->conn,
XCB_RANDR_MAJOR_VERSION,
XCB_RANDR_MINOR_VERSION
);
xcb_randr_query_version_reply_t *reply =
xcb_randr_query_version_reply(backend->conn, cookie, nullptr);
if (!reply) return false;
/* xcb_randr_get_monitors 接口是 1.5 引入的 */
uint32_t major_version = reply->major_version;
uint32_t minor_version = reply->minor_version;
if (major_version < 1 || (major_version == 1 && minor_version < 5)) {
p_delete(&reply);
return false;
}
p_delete(&reply);
xcb_randr_get_monitors_cookie_t monitors_cookie =
xcb_randr_get_monitors(backend->conn, backend->screen->root, 1);
xcb_randr_get_monitors_reply_t *monitors_reply =
xcb_randr_get_monitors_reply(backend->conn, monitors_cookie, nullptr);
if (!monitors_reply) {
warn("RandR get monitor failed");
return false;
}
int len = xcb_randr_get_monitors_monitors_length(monitors_reply);
if (len <= 0) {
p_delete(&monitors_reply);
return false;
}
output_info_t *output_list = p_new(output_info_t, len);
xcb_randr_monitor_info_iterator_t iter =
xcb_randr_get_monitors_monitors_iterator(monitors_reply);
for (int i = 0; iter.rem; xcb_randr_monitor_info_next(&iter), i++) {
output_info_t *output = &output_list[i];
output->geometry.x = iter.data->x;
output->geometry.y = iter.data->y;
output->geometry.width = iter.data->width;
output->geometry.height = iter.data->height;
xcb_get_atom_name_cookie_t name_cookie =
xcb_get_atom_name_unchecked(backend->conn, iter.data->name);
xcb_get_atom_name_reply_t *name_reply =
xcb_get_atom_name_reply(backend->conn, name_cookie, nullptr);
if (name_reply) {
char *name = xcb_get_atom_name_name(name_reply);
int length = xcb_get_atom_name_name_length(name_reply);
output->name = p_strndup(name, length);
p_delete(&name_reply);
}
}
p_delete(&monitors_reply);
if (count) *count = (size_t)len;
if (outputs) {
*outputs = output_list;
} else {
for (size_t i = 0; i < (size_t)len; i++) p_delete(&output_list[i].name);
p_delete(&output_list);
}
return true;
}
static bool detect_monitor_by_xinerama(
const backend_t *backend,
output_info_t **outputs,
size_t *count
) {
xcb_prefetch_extension_data(backend->conn, &xcb_xinerama_id);
const xcb_query_extension_reply_t *query =
xcb_get_extension_data(backend->conn, &xcb_xinerama_id);
if (!query || !query->present) return false;
xcb_xinerama_query_version_cookie_t cookie = xcb_xinerama_query_version(
backend->conn,
XCB_XINERAMA_MAJOR_VERSION,
XCB_XINERAMA_MINOR_VERSION
);
xcb_xinerama_query_version_reply_t *version_reply =
xcb_xinerama_query_version_reply(backend->conn, cookie, nullptr);
if (!version_reply) return false;
p_delete(&version_reply);
xcb_xinerama_is_active_cookie_t active_cookie =
xcb_xinerama_is_active(backend->conn);
xcb_xinerama_is_active_reply_t *active_reply =
xcb_xinerama_is_active_reply(backend->conn, active_cookie, nullptr);
bool active = active_reply && active_reply->state;
if (active_reply) p_delete(&active_reply);
if (!active) return false;
xcb_xinerama_query_screens_cookie_t screens_cookie =
xcb_xinerama_query_screens(backend->conn);
xcb_xinerama_query_screens_reply_t *screens_reply =
xcb_xinerama_query_screens_reply(backend->conn, screens_cookie, nullptr);
if (!screens_reply) return false;
int len = xcb_xinerama_query_screens_screen_info_length(screens_reply);
if (len <= 0) {
p_delete(&screens_reply);
return false;
}
xcb_xinerama_screen_info_t *screen_info =
xcb_xinerama_query_screens_screen_info(screens_reply);
if (!screen_info) {
p_delete(&screens_reply);
return false;
}
output_info_t *output_list = p_new(output_info_t, len);
for (int i = 0; i < len; i++) {
output_info_t *output = &output_list[i];
output->geometry.x = screen_info[i].x_org;
output->geometry.y = screen_info[i].y_org;
output->geometry.width = screen_info[i].width;
output->geometry.height = screen_info[i].height;
}
p_delete(&screens_reply);
if (count) *count = (size_t)len;
if (outputs) {
*outputs = output_list;
} else {
for (size_t i = 0; i < (size_t)len; i++) p_delete(&output_list[i].name);
p_delete(&output_list);
}
return true;
}
backend_detect_t *backend_detect(backend_t *backend) {
output_info_t *outputs = nullptr;
size_t count = 0;
if (detect_monitor_by_randr(backend, &outputs, &count)) {
backend_detect_t *detect = output_remove_duplication(outputs, count);
p_delete(&outputs);
return detect;
}
if (detect_monitor_by_xinerama(backend, &outputs, &count)) {
backend_detect_t *detect = output_remove_duplication(outputs, count);
p_delete(&outputs);
return detect;
}
output_info_t *output = p_new(output_info_t, 1);
output->geometry.x = 0;
output->geometry.y = 0;
output->geometry.width = backend->screen->width_in_pixels;
output->geometry.height = backend->screen->height_in_pixels;
backend_detect_t *detect = p_new(backend_detect_t, 1);
detect->outputs = output;
detect->output_count = 1;
return detect;
}
void backend_detect_destroy(backend_detect_t *detect) {
if (!detect) return;
for (size_t i = 0; i < detect->output_count; i++) {
p_delete(&detect->outputs[i].name);
}
p_delete(&detect->outputs);
free(detect);
}
static void backend_focus_window(backend_t *backend, xcb_window_t window) {
xcb_connection_t *conn = backend->conn;
xcb_window_t root = backend->screen->root;
const atoms_t *atoms = &backend->atoms;
xcb_atom_t property = atoms->_NET_ACTIVE_WINDOW;
xcb_timestamp_t time = XCB_TIME_CURRENT_TIME;
if (window == XCB_WINDOW_NONE || window == root) {
window = backend->window_no_focus;
xcb_set_input_focus(conn, XCB_INPUT_FOCUS_PARENT, window, time);
xcb_delete_property(conn, root, property);
} else {
uint8_t mode = XCB_PROP_MODE_REPLACE;
xcb_atom_t type = XCB_ATOM_WINDOW;
xcb_set_input_focus(conn, XCB_INPUT_FOCUS_PARENT, window, time);
xcb_change_property(conn, mode, root, property, type, 32, 1, &window);
window_takefocus(backend, window);
}
}
static inline void backend_change_window_list(
backend_t *backend,
bool stack_list,
const effect_window_list_t *list
) {
xcb_change_property(
backend->conn,
XCB_PROP_MODE_REPLACE,
backend->screen->root,
stack_list ? backend->atoms._NET_CLIENT_LIST_STACKING
: backend->atoms._NET_CLIENT_LIST,
XCB_ATOM_WINDOW,
32,
list->count,
list->windows
);
}
static void
backend_restack_windows(backend_t *backend, const effect_window_list_t *list) {
if (list->count == 0) return;
xcb_connection_t *conn = backend->conn;
xcb_window_t sibling_window = backend->window_no_focus;
for (size_t i = 0; i < list->count; ++i) {
uint16_t mask = XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE;
xcb_configure_window_value_list_t params = {
.sibling = sibling_window,
.stack_mode = XCB_STACK_MODE_ABOVE
};
sibling_window = list->windows[i];
xcb_configure_window_aux(conn, sibling_window, mask, &params);
}
}
static void window_configure_list_reset(window_configure_list_t *configs) {
p_clear(configs->cfgs, configs->count);
configs->count = 0;
}
static void backend_apply_window_configure_list(backend_t *backend) {
xcb_connection_t *conn = backend->conn;
window_configure_list_t *configs = &backend->config_list;
for (size_t i = 0; i < configs->count; ++i) {
window_configure_t *cfg = &configs->cfgs[i];
if (!cfg->mask) continue;
xcb_configure_window_aux(conn, cfg->window, cfg->mask, &cfg->value);
}
}
static window_configure_t *
find_or_push_configure(window_configure_list_t *configs, xcb_window_t window) {
window_configure_t *cfg = nullptr;
for (size_t i = 0; i < configs->count; ++i) {
cfg = &configs->cfgs[i];
if (cfg->window == window) return cfg;
}
cfg = array_push(configs->cfgs, configs->count, configs->capacity);
p_clear(cfg, 1);
cfg->window = window;
cfg->mask = 0;
return cfg;
}
static void merge_window_configure_params(
backend_t *backend,
const configure_data_t *data
) {
auto cfg = find_or_push_configure(&backend->config_list, data->window);
#define FIELD_MERGE(MASK, VALUE_FIELD, DATA_FIELD) \
if (data->changed_fields & (MASK)) { \
cfg->mask |= (MASK); \
cfg->value.VALUE_FIELD = data->DATA_FIELD; \
}
FIELD_MERGE(ZDWM_CONFIGURE_FIELD_X, x, x);
FIELD_MERGE(ZDWM_CONFIGURE_FIELD_Y, y, y);
FIELD_MERGE(ZDWM_CONFIGURE_FIELD_WIDTH, width, width);
FIELD_MERGE(ZDWM_CONFIGURE_FIELD_HEIGHT, height, height);
FIELD_MERGE(ZDWM_CONFIGURE_FIELD_BORDER_WIDTH, border_width, border_width);
FIELD_MERGE(ZDWM_CONFIGURE_FIELD_SIBLING, sibling, sibling);
FIELD_MERGE(ZDWM_CONFIGURE_FIELD_STACK_MODE, stack_mode, stack_mode);
#undef FIELD_MERGE
}
static void
backend_bind_key(backend_t *backend, const effect_bind_key_t *bind_key) {
auto root = backend->screen->root;
window_grab_keys(backend, root, bind_key->keys, bind_key->count);
}
static void backend_grab_button(
backend_t *backend,
const effect_grab_button_t *grab_button
) {
auto conn = backend->conn;
auto window = grab_button->window;
xcb_ungrab_button(conn, XCB_BUTTON_INDEX_ANY, window, XCB_MOD_MASK_ANY);
auto buttons = grab_button->buttons;
for (size_t i = 0; i < grab_button->count; ++i) {
auto button = buttons[i].button;
auto modifiers = buttons[i].modifiers;
window_grab_button(backend, window, button, modifiers);
}
}
static void backend_grab_pointer(backend_t *backend, cursor_t cursor) {
auto conn = backend->conn;
auto root = backend->screen->root;
auto mask = XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE |
XCB_EVENT_MASK_POINTER_MOTION;
auto mode = XCB_GRAB_MODE_ASYNC;
auto xcursor = cursor_get_xcb_cursor(backend, cursor);
auto time = XCB_TIME_CURRENT_TIME;
xcb_grab_pointer(conn, false, root, mask, mode, mode, root, xcursor, time);
}
static void backend_merge_effects(
backend_t *backend,
const effect_t *effects,
size_t effect_count
) {
for (size_t i = 0; i < effect_count; ++i) {
const effect_t *e = &effects[i];
switch (e->type) {
case ZDWM_EFFECT_MAP_WINDOW:
window_list_push(&backend->map, e->as.map.window);
window_list_push(&backend->configure, e->as.map.window);
break;
case ZDWM_EFFECT_UNMAP_WINDOW:
window_list_push(&backend->unmap, e->as.unmap.window);
break;
case ZDWM_EFFECT_FOCUS_WINDOW:
backend->focus_window = e->as.focus.window;
backend->update_focus = true;
break;
case ZDWM_EFFECT_KILL_WINDOW:
window_list_push(&backend->kill, e->as.kill.window);
break;
case ZDWM_EFFECT_WITHDRAW_WINDOW:
window_set_icccm_wm_state(
backend->conn,
e->as.withdraw.window,
XCB_ICCCM_WM_STATE_WITHDRAWN
);
break;
case ZDWM_EFFECT_START_MOVE_WINDOW:
backend_grab_pointer(backend, ZDWM_CURSOR_MOVE);
break;
case ZDWM_EFFECT_START_RESIZE_WINDOW:
backend_grab_pointer(backend, ZDWM_CURSOR_RESIZE);
break;
case ZDWM_EFFECT_MINIMIZE_WINDOW: {
auto window = e->as.minimize.window;
xcb_icccm_wm_state_t state = XCB_ICCCM_WM_STATE_NORMAL;
if (e->as.minimize.value) state = XCB_ICCCM_WM_STATE_ICONIC;
window_set_icccm_wm_state(backend->conn, window, state);
} break;
case ZDWM_EFFECT_MAXIMIZE_WINDOW: {
auto window = e->as.maximize.window;
if (e->as.maximize.value) {
xcb_atom_t max_atoms[] = {
backend->atoms._NET_WM_STATE_MAXIMIZED_HORZ,
backend->atoms._NET_WM_STATE_MAXIMIZED_VERT
};
window_set_net_wm_state(backend, window, countof(max_atoms), max_atoms);
} else {
window_set_net_wm_state(backend, window, 0, nullptr);
}
} break;
case ZDWM_EFFECT_FULLSCREEN_WINDOW: {
auto window = e->as.fullscreen.window;
if (e->as.fullscreen.value) {
auto fullscreen_atom = backend->atoms._NET_WM_STATE_FULLSCREEN;
window_set_net_wm_state(backend, window, 1, &fullscreen_atom);
} else {
window_set_net_wm_state(backend, window, 0, nullptr);
}
} break;
case ZDWM_EFFECT_CONFIGURE_WINDOW:
merge_window_configure_params(backend, &e->as.configure);
break;
case ZDWM_EFFECT_CONFIGURE_NOTIFY:
window_list_push(&backend->configure, e->as.configure_notify.window);
break;
case ZDWM_EFFECT_CHANGE_BORDER_COLOR: {
xcb_change_window_attributes_value_list_t value = {
.border_pixel = e->as.change_border_color.color->argb
};
xcb_change_window_attributes_aux(
backend->conn,
e->as.change_border_color.window,
XCB_CW_BORDER_PIXEL,
&value
);
} break;
case ZDWM_EFFECT_CHANGE_WINDOW_LIST:
backend_change_window_list(backend, false, &e->as.change_window_list);
break;
case ZDWM_EFFECT_RESTACK_WINDOWS:
backend_restack_windows(backend, &e->as.restack_windows);
backend_change_window_list(backend, true, &e->as.restack_windows);
break;
case ZDWM_EFFECT_BIND_KEY:
backend_bind_key(backend, &e->as.bind_key);
break;
case ZDWM_EFFECT_GRAB_BUTTON:
backend_grab_button(backend, &e->as.grab_button);
break;
case ZDWM_EFFECT_UNGRAB_POINTER:
xcb_ungrab_pointer(backend->conn, XCB_CURRENT_TIME);
break;
}
}
}
static void batch_send_configure_notify(
xcb_connection_t *conn,
xcb_window_t *windows,
size_t count
) {
if (count == 0) return;
auto cookie_list = p_new(xcb_get_geometry_cookie_t, count);
for (size_t i = 0; i < count; ++i) {
auto window = windows[i];
cookie_list[i] = xcb_get_geometry(conn, window);
}
uint32_t event_mask = XCB_EVENT_MASK_STRUCTURE_NOTIFY;
for (size_t i = 0; i < count; ++i) {
auto window = windows[i];
auto cookie = cookie_list[i];
auto reply = xcb_get_geometry_reply(conn, cookie, nullptr);
if (!reply) continue;
xcb_configure_notify_event_t ev = {
.response_type = XCB_CONFIGURE_NOTIFY,
.event = window,
.window = window,
.above_sibling = XCB_WINDOW_NONE,
.x = reply->x,
.y = reply->y,
.width = reply->width,
.height = reply->height,
.border_width = reply->border_width,
.override_redirect = false,
};
xcb_send_event(conn, false, window, event_mask, (char *)&ev);
p_delete(&reply);
}
p_delete(&cookie_list);
}
static void backend_batch_apply_effects(backend_t *backend) {
xcb_connection_t *conn = backend->conn;
if (backend->unmap.count) {
xcb_grab_server(conn);
window_clean_event_mask(conn, backend->screen->root);
window_list_t *unmap = &backend->unmap;
for (size_t i = 0; i < unmap->count; ++i) {
xcb_window_t window = unmap->windows[i];
window_clean_event_mask(conn, window);
xcb_unmap_window(conn, window);
}
root_set_event_mask(backend);
xcb_ungrab_server(conn);
}
for (size_t i = 0; i < backend->map.count; ++i) {
xcb_window_t window = backend->map.windows[i];
window_set_event_mask(conn, window);
xcb_map_window(conn, window);
}
for (size_t i = 0; i < backend->kill.count; ++i) {
window_kill(backend, backend->kill.windows[i]);
}
backend_apply_window_configure_list(backend);
{
auto windows = backend->configure.windows;
auto count = backend->configure.count;
batch_send_configure_notify(conn, windows, count);
}
if (backend->update_focus) {
backend_focus_window(backend, backend->focus_window);
}
}
bool backend_apply_effect(
backend_t *backend,
const effect_t *effects,
size_t effect_count
) {
backend->update_focus = false;
window_configure_list_reset(&backend->config_list);
window_list_reset(&backend->unmap);
window_list_reset(&backend->map);
window_list_reset(&backend->kill);
window_list_reset(&backend->configure);
backend_merge_effects(backend, effects, effect_count);
backend_batch_apply_effects(backend);
xcb_flush(backend->conn);
return true;
}
backend_scan_result_t *backend_scan_windows(backend_t *backend) {
auto conn = backend->conn;
auto root = backend->screen->root;
auto cookie = xcb_query_tree_unchecked(conn, root);
auto reply = xcb_query_tree_reply(conn, cookie, nullptr);
if (!reply) return nullptr;
auto length = xcb_query_tree_children_length(reply);
if (!length) {
p_delete(&reply);
return nullptr;
}
auto list = xcb_query_tree_children(reply);
auto result = p_new(backend_scan_result_t, 1);
auto sub_result = p_new(backend_scan_result_t, 1);
for (typeof(length) i = 0; i < length; ++i) {
auto window = list[i];
auto wa = window_get_attributes(backend, window);
if (!wa) continue;
auto override_redirect = wa->override_redirect;
auto map_state = wa->map_state;
p_delete(&wa);
if (override_redirect) continue;
auto hints = (xcb_icccm_wm_hints_t){0};
auto hints_getted = window_get_wm_hints(backend, window, &hints);
if (!(map_state == XCB_MAP_STATE_VIEWABLE ||
(hints_getted && hints.initial_state == XCB_ICCCM_WM_STATE_ICONIC))) {
continue;
}
auto transient_for = window_get_transient_for(backend, window);
auto r = transient_for == XCB_WINDOW_NONE ? result : sub_result;
auto slot = array_push(r->windows, r->count, r->capacity);
if (
!populate_window_event(backend, list[i], slot) || slot->override_redirect
) {
window_layer_props_cleanup(&slot->props);
window_metadata_cleanup(&slot->metadata);
r->count--;
}
}
p_delete(&reply);
for (size_t i = 0; i < sub_result->count; ++i) {
auto slot = array_push(result->windows, result->count, result->capacity);
*slot = sub_result->windows[i];
}
p_clear(sub_result->windows, sub_result->count);
backend_scan_result_destroy(sub_result);
if (result->count == 0) {
backend_scan_result_destroy(result);
result = nullptr;
return nullptr;
}
return result;
}
void backend_scan_result_destroy(backend_scan_result_t *result) {
if (!result) return;
for (size_t i = 0; i < result->count; i++) {
window_map_request_event_t *ev = &result->windows[i];
window_layer_props_cleanup(&ev->props);
window_metadata_cleanup(&ev->metadata);
}
p_delete(&result->windows);
p_delete(&result);
}
backend_bar_window_t backend_create_bar_window(
backend_t *backend,
rect_t geometry,
uint32_t bg_pixel,
bool enable_tray
) {
auto conn = backend->conn;
auto root = backend->screen->root;
auto visual = window_get_visual(backend, true);
static xcb_colormap_t colormap = XCB_NONE;
if (colormap == XCB_NONE) {
colormap = xcb_generate_id(conn);
uint8_t alloc = XCB_COLORMAP_ALLOC_NONE;
xcb_create_colormap(conn, alloc, colormap, root, visual->visual->visual_id);
}
window_clean_event_mask(conn, root);
xcb_grab_server(conn);
auto window_id = xcb_generate_id(conn);
uint16_t _class = XCB_WINDOW_CLASS_INPUT_OUTPUT;
uint32_t value_mask = XCB_CW_OVERRIDE_REDIRECT | XCB_CW_BACK_PIXEL |
XCB_CW_BORDER_PIXEL | XCB_CW_EVENT_MASK |
XCB_CW_COLORMAP;
const xcb_create_window_value_list_t value_list = {
.override_redirect = true,
.background_pixel = bg_pixel,
.border_pixel = 0,
.event_mask = XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_EXPOSURE,
.colormap = colormap,
};
auto cookie = xcb_create_window_aux_checked(
conn,
visual->depth,
window_id,
root,
geometry.x,
geometry.y,
geometry.width,
geometry.height,
0,
_class,
visual->visual->visual_id,
value_mask,
&value_list
);
if (xcb_request_check(conn, cookie)) fatal("cannot create bar window");
cookie = xcb_map_window_checked(conn, window_id);
if (xcb_request_check(conn, cookie)) fatal("cannot map bar window");
window_set_class_instance(conn, window_id);
window_set_name_static(conn, window_id, APP_NAME "_bar");
xcb_ungrab_server(conn);
root_set_event_mask(backend);
xcb_aux_sync(conn);
auto width = geometry.width;
auto height = geometry.height;
auto vid = visual->visual;
auto surface = cairo_xcb_surface_create(conn, window_id, vid, width, height);
auto cr = cairo_create(surface);
cairo_surface_destroy(surface);
p_delete(&visual);
auto statue = cairo_status(cr);
if (statue != CAIRO_STATUS_SUCCESS) {
fatal("cannot create cairo context: %s", cairo_status_to_string(statue));
}
if (enable_tray) {
tray_host_window_t host_window = {
.window = window_id,
.width = width,
.height = height,
.bg_pixel = bg_pixel,
};
tray_init(backend, host_window);
}
return (backend_bar_window_t){
.window_id = window_id,
.cr = cr,
.tray = {
.api = tray_api,
.handle = backend,
},
};
}
void backend_flush(backend_t *backend) { xcb_flush(backend->conn); }