refactor(backend/X11): 重构 effect 应用为延迟批量模式并添加 KILL_WINDOW 支持

- 添加 ZDWM_EFFECT_KILL_WINDOW 副作用和 WM_DELETE_WINDOW 协议支持
- 将 backend_apply_effect 从即时调用 X11 拆分为 merge + batch apply 两阶段:
  - 先收集 effect 到后端持有的列表
  - 再按 unmap(grab server) → map → kill → configure → focus 顺序统一提交
This commit is contained in:
2026-04-23 04:37:34 +08:00
parent 28ebb086b1
commit fc68e75f82
5 changed files with 213 additions and 55 deletions

View File

@@ -15,6 +15,7 @@
#include "backend/output_utils.h"
#include "backend/x11/window.h"
#include "base/array.h"
#include "base/log.h"
#include "base/macros.h"
#include "base/memory.h"
@@ -169,6 +170,15 @@ backend_t *backend_create(const char *display_name) {
void backend_destroy(backend_t *backend) {
if (!backend) return;
p_delete(&backend->config_list.cfgs);
p_clear(&backend->config_list, 1);
p_delete(&backend->unmap.windows);
p_clear(&backend->unmap, 1);
p_delete(&backend->kill.windows);
p_clear(&backend->kill, 1);
xcb_disconnect(backend->conn);
backend->conn = nullptr;
free(backend);
@@ -415,70 +425,70 @@ backend_restack_windows(backend_t *backend, const effect_window_list_t *list) {
}
}
typedef struct window_configure_t {
xcb_window_t window;
uint16_t mask;
xcb_configure_window_value_list_t value;
} window_configure_t;
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;
static void
flush_configure(xcb_connection_t *conn, const window_configure_t *cfg) {
if (cfg->mask) {
xcb_configure_window_aux(conn, cfg->window, cfg->mask, &cfg->value);
}
}
static window_configure_t *find_or_push_configure(
window_configure_t *cfgs,
size_t *cfg_count,
xcb_window_t window
) {
for (size_t j = 0; j < *cfg_count; ++j) {
if (cfgs[j].window == window) return &cfgs[j];
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;
}
window_configure_t *cfg = &cfgs[(*cfg_count)++];
cfg = array_push(configs->cfgs, configs->count, configs->capacity);
p_clear(cfg, 1);
cfg->window = window;
cfg->mask = 0;
return cfg;
}
bool backend_apply_effect(
static void backend_merge_effects(
backend_t *backend,
const effect_t *effects,
size_t effect_count
) {
xcb_connection_t *conn = backend->conn;
/* collect configure-like effects per window */
window_configure_t *cfgs = p_new(window_configure_t, effect_count);
size_t cfg_count = 0;
bool focus_window_changed = false;
xcb_window_t focus_window = XCB_WINDOW_NONE;
window_configure_list_t *configs = &backend->config_list;
for (size_t i = 0; i < effect_count; ++i) {
const effect_t *e = &effects[i];
switch (e->type) {
case ZDWM_EFFECT_MAP_WINDOW:
xcb_map_window(conn, e->as.map.window);
window_list_push(&backend->map, e->as.map.window);
break;
case ZDWM_EFFECT_UNMAP_WINDOW:
xcb_unmap_window(conn, e->as.unmap.window);
window_list_push(&backend->unmap, e->as.unmap.window);
break;
case ZDWM_EFFECT_FOCUS_WINDOW:
focus_window = e->as.focus.window;
focus_window_changed = true;
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_MOVE_WINDOW: {
window_configure_t *cfg =
find_or_push_configure(cfgs, &cfg_count, e->as.move.window);
find_or_push_configure(configs, e->as.move.window);
cfg->mask |= XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y;
cfg->value.x = e->as.move.left_top_point.x;
cfg->value.y = e->as.move.left_top_point.y;
} break;
case ZDWM_EFFECT_RESIZE_WINDOW: {
window_configure_t *cfg =
find_or_push_configure(cfgs, &cfg_count, e->as.resize.window);
find_or_push_configure(configs, e->as.resize.window);
cfg->mask |= XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT;
cfg->value.width = (uint32_t)e->as.resize.width;
cfg->value.height = (uint32_t)e->as.resize.height;
@@ -488,18 +498,15 @@ bool backend_apply_effect(
.border_pixel = e->as.change_border_color.color->argb
};
xcb_change_window_attributes_aux(
conn,
backend->conn,
e->as.change_border_color.window,
XCB_CW_BORDER_PIXEL,
&value
);
} break;
case ZDWM_EFFECT_CHANGE_BORDER_WIDTH: {
window_configure_t *cfg = find_or_push_configure(
cfgs,
&cfg_count,
e->as.change_border_width.window
);
window_configure_t *cfg =
find_or_push_configure(configs, e->as.change_border_width.window);
cfg->mask |= XCB_CONFIG_WINDOW_BORDER_WIDTH;
cfg->value.border_width = e->as.change_border_width.border_width;
} break;
@@ -512,14 +519,56 @@ bool backend_apply_effect(
break;
}
}
}
for (size_t i = 0; i < cfg_count; ++i) {
flush_configure(conn, &cfgs[i]);
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);
}
p_delete(&cfgs);
if (focus_window_changed) backend_focus_window(backend, focus_window);
root_set_event_mask(backend);
xcb_ungrab_server(conn);
}
xcb_flush(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);
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);
backend_merge_effects(backend, effects, effect_count);
backend_batch_apply_effects(backend);
xcb_flush(backend->conn);
return true;
}

View File

@@ -1,5 +1,7 @@
#pragma once
#include <stddef.h>
#include <stdint.h>
#include <xcb/xcb.h>
#include <xcb/xproto.h>
@@ -13,6 +15,7 @@
\
X(WM_PROTOCOLS) \
X(WM_TAKE_FOCUS) \
X(WM_DELETE_WINDOW) \
X(_NET_ACTIVE_WINDOW) \
\
X(_NET_SUPPORTING_WM_CHECK) \
@@ -54,6 +57,24 @@ typedef struct atoms_t {
#undef DECLARATION_ATOM
} atoms_t;
typedef struct window_configure_t {
xcb_window_t window;
uint16_t mask;
xcb_configure_window_value_list_t value;
} window_configure_t;
typedef struct window_configure_list_t {
window_configure_t *cfgs;
size_t count;
size_t capacity;
} window_configure_list_t;
typedef struct window_list_t {
xcb_window_t *windows;
size_t count;
size_t capacity;
} window_list_t;
struct backend_t {
xcb_connection_t *conn;
xcb_screen_t *screen;
@@ -65,4 +86,12 @@ struct backend_t {
/* When no window should take focus, then focus this window */
xcb_window_t window_no_focus;
xcb_window_t wm_check_window;
xcb_window_t focus_window;
bool update_focus;
window_configure_list_t config_list;
window_list_t unmap;
window_list_t map;
window_list_t kill;
};

View File

@@ -6,6 +6,7 @@
#include <xcb/xcb_icccm.h>
#include <xcb/xproto.h>
#include "base/array.h"
#include "base/memory.h"
#include "core/backend.h"
#include "internal.h"
@@ -244,16 +245,84 @@ window_state_t atom_to_window_state(const atoms_t *atoms, xcb_atom_t atom) {
return (window_state_t)-1;
}
void window_takefocus(backend_t *backend, xcb_window_t window) {
xcb_client_message_event_t ev = {0};
ev.response_type = XCB_CLIENT_MESSAGE;
ev.window = window;
ev.format = 32;
ev.type = backend->atoms.WM_PROTOCOLS,
ev.data.data32[0] = backend->atoms.WM_TAKE_FOCUS;
ev.data.data32[1] = XCB_CURRENT_TIME;
static bool
window_send_event(backend_t *backend, xcb_window_t window, xcb_atom_t atom) {
bool exist = false;
xcb_connection_t *conn = backend->conn;
auto WM_PROTOCOLS = backend->atoms.WM_PROTOCOLS;
xcb_get_property_cookie_t cookie =
xcb_icccm_get_wm_protocols_unchecked(conn, window, WM_PROTOCOLS);
xcb_icccm_get_wm_protocols_reply_t reply = {0};
if (xcb_icccm_get_wm_protocols_reply(conn, cookie, &reply, nullptr)) {
for (uint32_t i = 0; !exist && i < reply.atoms_len; ++i) {
exist = reply.atoms[i] == atom;
}
xcb_icccm_get_wm_protocols_reply_wipe(&reply);
}
if (exist) {
xcb_client_message_event_t ev = {
.response_type = XCB_CLIENT_MESSAGE,
.format = 32,
.window = window,
.type = WM_PROTOCOLS,
.data.data32 = {atom, XCB_CURRENT_TIME},
};
xcb_send_event(conn, false, window, XCB_EVENT_MASK_NO_EVENT, (char *)&ev);
}
return exist;
}
void window_takefocus(backend_t *backend, xcb_window_t window) {
window_send_event(backend, window, backend->atoms.WM_TAKE_FOCUS);
}
void window_kill(backend_t *backend, xcb_window_t window) {
if (window_send_event(backend, window, backend->atoms.WM_DELETE_WINDOW)) {
xcb_kill_client(backend->conn, window);
}
}
void root_set_event_mask(backend_t *backend) {
xcb_connection_t *conn = backend->conn;
xcb_window_t root = backend->screen->root;
xcb_cw_t change_mask = XCB_CW_EVENT_MASK;
xcb_change_window_attributes_value_list_t value_list = {
.event_mask =
XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT | XCB_EVENT_MASK_KEY_PRESS,
};
xcb_change_window_attributes_aux(conn, root, change_mask, &value_list);
}
void window_set_event_mask(xcb_connection_t *conn, xcb_window_t window) {
xcb_cw_t change_mask = XCB_CW_EVENT_MASK;
xcb_change_window_attributes_value_list_t value_list = {
.event_mask = XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_FOCUS_CHANGE |
XCB_EVENT_MASK_PROPERTY_CHANGE |
XCB_EVENT_MASK_STRUCTURE_NOTIFY |
XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY,
};
xcb_change_window_attributes_aux(conn, window, change_mask, &value_list);
}
void window_clean_event_mask(xcb_connection_t *conn, xcb_window_t window) {
xcb_cw_t change_mask = XCB_CW_EVENT_MASK;
xcb_change_window_attributes_value_list_t value_list = {
.event_mask = XCB_EVENT_MASK_NO_EVENT,
};
xcb_change_window_attributes_aux(conn, window, change_mask, &value_list);
}
void window_list_push(window_list_t *window_list, xcb_window_t window) {
xcb_window_t *win =
array_push(window_list->windows, window_list->count, window_list->capacity);
*win = window;
}
void window_list_reset(window_list_t *window_list) {
p_clear(window_list->windows, window_list->count);
window_list->count = 0;
}

View File

@@ -7,9 +7,10 @@
#include <xcb/xproto.h>
#include "core/backend.h"
#include "core/window.h"
#include "internal.h"
typedef struct atoms_t atoms_t;
typedef struct window_list_t window_list_t;
/**
* @brief 获取窗口的 role 属性
@@ -78,3 +79,11 @@ window_state_t atom_to_window_state(const atoms_t *atoms, xcb_atom_t atom);
xcb_icccm_set_wm_class(conn, win, sizeof(instance_class), instance_class)
void window_takefocus(backend_t *backend, xcb_window_t window);
void window_kill(backend_t *backend, xcb_window_t window);
void root_set_event_mask(backend_t *backend);
void window_set_event_mask(xcb_connection_t *conn, xcb_window_t window);
void window_clean_event_mask(xcb_connection_t *conn, xcb_window_t window);
void window_list_push(window_list_t *window_list, xcb_window_t window);
void window_list_reset(window_list_t *window_list);

View File

@@ -9,6 +9,7 @@ typedef enum effect_type_t {
ZDWM_EFFECT_MAP_WINDOW = 1,
ZDWM_EFFECT_UNMAP_WINDOW,
ZDWM_EFFECT_FOCUS_WINDOW,
ZDWM_EFFECT_KILL_WINDOW,
ZDWM_EFFECT_MOVE_WINDOW,
ZDWM_EFFECT_RESIZE_WINDOW,
ZDWM_EFFECT_CHANGE_BORDER_COLOR,
@@ -53,6 +54,7 @@ typedef struct effect_t {
effect_only_window_id_t map;
effect_only_window_id_t unmap;
effect_only_window_id_t focus;
effect_only_window_id_t kill;
effect_move_window_t move;
effect_resize_window_t resize;
effect_change_border_color_t change_border_color;