From fc68e75f8285893182042d120787e265cc905ac0 Mon Sep 17 00:00:00 2001 From: Zedhugh Chen Date: Thu, 23 Apr 2026 04:37:34 +0800 Subject: [PATCH] =?UTF-8?q?refactor(backend/X11):=20=E9=87=8D=E6=9E=84=20e?= =?UTF-8?q?ffect=20=E5=BA=94=E7=94=A8=E4=B8=BA=E5=BB=B6=E8=BF=9F=E6=89=B9?= =?UTF-8?q?=E9=87=8F=E6=A8=A1=E5=BC=8F=E5=B9=B6=E6=B7=BB=E5=8A=A0=20KILL?= =?UTF-8?q?=5FWINDOW=20=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加 ZDWM_EFFECT_KILL_WINDOW 副作用和 WM_DELETE_WINDOW 协议支持 - 将 backend_apply_effect 从即时调用 X11 拆分为 merge + batch apply 两阶段: - 先收集 effect 到后端持有的列表 - 再按 unmap(grab server) → map → kill → configure → focus 顺序统一提交 --- src/backend/x11/backend.c | 137 +++++++++++++++++++++++++------------ src/backend/x11/internal.h | 29 ++++++++ src/backend/x11/window.c | 89 +++++++++++++++++++++--- src/backend/x11/window.h | 11 ++- src/core/plan.h | 2 + 5 files changed, 213 insertions(+), 55 deletions(-) diff --git a/src/backend/x11/backend.c b/src/backend/x11/backend.c index 063625d..5e9a4f8 100644 --- a/src/backend/x11/backend.c +++ b/src/backend/x11/backend.c @@ -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->window = window; - cfg->mask = 0; + + 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); + } + + root_set_event_mask(backend); + xcb_ungrab_server(conn); } - p_delete(&cfgs); - if (focus_window_changed) backend_focus_window(backend, focus_window); + 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); + } - xcb_flush(conn); + 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; } diff --git a/src/backend/x11/internal.h b/src/backend/x11/internal.h index 52067aa..fed2702 100644 --- a/src/backend/x11/internal.h +++ b/src/backend/x11/internal.h @@ -1,5 +1,7 @@ #pragma once +#include +#include #include #include @@ -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; }; diff --git a/src/backend/x11/window.c b/src/backend/x11/window.c index 2369692..a122d37 100644 --- a/src/backend/x11/window.c +++ b/src/backend/x11/window.c @@ -6,6 +6,7 @@ #include #include +#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; - xcb_send_event(conn, false, window, XCB_EVENT_MASK_NO_EVENT, (char *)&ev); + 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; } diff --git a/src/backend/x11/window.h b/src/backend/x11/window.h index 0ca9d01..79bc61d 100644 --- a/src/backend/x11/window.h +++ b/src/backend/x11/window.h @@ -7,9 +7,10 @@ #include #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); diff --git a/src/core/plan.h b/src/core/plan.h index 152b25c..cdabaef 100644 --- a/src/core/plan.h +++ b/src/core/plan.h @@ -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;