From 22cd36272416b0b95723384b1d1961f091b0d408 Mon Sep 17 00:00:00 2001 From: Zedhugh Chen Date: Sun, 19 Apr 2026 04:49:02 +0800 Subject: [PATCH] =?UTF-8?q?feat(backend/X11):=20=E5=AE=9E=E7=8E=B0=20backe?= =?UTF-8?q?nd=5Fapply=5Feffect=20=E6=8E=A5=E5=8F=A3=E4=BB=A5=E6=8F=90?= =?UTF-8?q?=E4=BA=A4=20X11=20=E5=89=AF=E4=BD=9C=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/backend/x11/backend.c | 187 ++++++++++++++++++++++++++++++++++++- src/backend/x11/internal.h | 10 ++ src/backend/x11/window.c | 14 +++ src/backend/x11/window.h | 13 +++ src/core/plan.h | 3 +- 5 files changed, 224 insertions(+), 3 deletions(-) diff --git a/src/backend/x11/backend.c b/src/backend/x11/backend.c index a1851cf..ac188ab 100644 --- a/src/backend/x11/backend.c +++ b/src/backend/x11/backend.c @@ -12,9 +12,11 @@ #include #include "backend/output_utils.h" +#include "backend/x11/window.h" #include "base/log.h" #include "base/macros.h" #include "base/memory.h" +#include "core/plan.h" #include "core/types.h" #include "internal.h" @@ -49,6 +51,40 @@ static void atoms_init(backend_t *backend) { } } +static void create_no_focus_window(backend_t *backend) { + xcb_connection_t *conn = backend->conn; + + xcb_window_t win = xcb_generate_id(conn); + uint32_t value_mask = XCB_CW_BACK_PIXEL | XCB_CW_BORDER_PIXEL | + XCB_CW_OVERRIDE_REDIRECT | XCB_CW_COLORMAP; + const xcb_create_window_value_list_t value_list = { + .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, + backend->screen->root_depth, + win, + backend->screen->root, + -1, + -1, + 1, + 1, + 0, + XCB_WINDOW_CLASS_COPY_FROM_PARENT, + backend->screen->root_visual, + value_mask, + &value_list + ); + window_set_class_instance(conn, win); + window_set_name_static(conn, win, "zdwm 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); @@ -100,6 +136,7 @@ backend_t *backend_create(const char *display_name) { } atoms_init(backend); + create_no_focus_window(backend); return backend; } @@ -299,11 +336,159 @@ void backend_detect_destroy(backend_detect_t *detect) { 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, ¶ms); + } +} + +typedef struct window_configure_t { + xcb_window_t window; + uint16_t mask; + xcb_configure_window_value_list_t value; +} window_configure_t; + +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]; + } + window_configure_t *cfg = &cfgs[(*cfg_count)++]; + cfg->window = window; + cfg->mask = 0; + return cfg; +} + bool backend_apply_effect( backend_t *backend, const effect_t *effects, size_t effect_count ) { - /* TODO: */ + 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; + + 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); + break; + case ZDWM_EFFECT_UNMAP_WINDOW: + xcb_unmap_window(conn, e->as.unmap.window); + break; + case ZDWM_EFFECT_FOCUS_WINDOW: + backend_focus_window(backend, e->as.focus.window); + break; + case ZDWM_EFFECT_MOVE_WINDOW: { + window_configure_t *cfg = + find_or_push_configure(cfgs, &cfg_count, 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); + 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; + } 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( + 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 + ); + cfg->mask |= XCB_CONFIG_WINDOW_BORDER_WIDTH; + cfg->value.border_width = e->as.change_border_width.border_width; + } 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; + } + } + + for (size_t i = 0; i < cfg_count; ++i) { + flush_configure(conn, &cfgs[i]); + } + p_delete(&cfgs); + + xcb_flush(conn); return true; } diff --git a/src/backend/x11/internal.h b/src/backend/x11/internal.h index ff7dc37..ef574a9 100644 --- a/src/backend/x11/internal.h +++ b/src/backend/x11/internal.h @@ -11,6 +11,13 @@ X(WM_NAME) \ X(_NET_WM_NAME) \ \ + X(WM_PROTOCOLS) \ + X(WM_TAKE_FOCUS) \ + X(_NET_ACTIVE_WINDOW) \ + \ + X(_NET_CLIENT_LIST) \ + X(_NET_CLIENT_LIST_STACKING) \ + \ X(_NET_WM_STATE) \ X(_NET_WM_STATE_FULLSCREEN) \ X(_NET_WM_STATE_ABOVE) \ @@ -49,4 +56,7 @@ struct backend_t { bool have_xfixes; atoms_t atoms; + + /* When no window should take focus, then focus this window */ + xcb_window_t window_no_focus; }; diff --git a/src/backend/x11/window.c b/src/backend/x11/window.c index 13c15ed..2369692 100644 --- a/src/backend/x11/window.c +++ b/src/backend/x11/window.c @@ -243,3 +243,17 @@ window_state_t atom_to_window_state(const atoms_t *atoms, xcb_atom_t atom) { if (atom == atoms->_NET_WM_STATE_MODAL) return ZDWM_WINDOW_STATE_MODAL; 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; + + xcb_connection_t *conn = backend->conn; + xcb_send_event(conn, false, window, XCB_EVENT_MASK_NO_EVENT, (char *)&ev); +} diff --git a/src/backend/x11/window.h b/src/backend/x11/window.h index 4187188..0ca9d01 100644 --- a/src/backend/x11/window.h +++ b/src/backend/x11/window.h @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -65,3 +66,15 @@ bool window_get_wm_hints( xcb_icccm_wm_hints_t *out ); window_state_t atom_to_window_state(const atoms_t *atoms, xcb_atom_t atom); + +#define window_set_name_static(conn, win, name) \ + xcb_icccm_set_wm_name(conn, win, XCB_ATOM_STRING, 8, sizeof(name) - 1, name) + +#define window_set_class_instance(conn, win) \ + window_set_class_instance_static(conn, win, "zdwm", "zdwm") +#define window_set_class_instance_static(conn, win, instance, class) \ + _window_set_class_instance_static(conn, win, instance "\0" class) +#define _window_set_class_instance_static(conn, win, instance_class) \ + xcb_icccm_set_wm_class(conn, win, sizeof(instance_class), instance_class) + +void window_takefocus(backend_t *backend, xcb_window_t window); diff --git a/src/core/plan.h b/src/core/plan.h index 6780b07..929b02d 100644 --- a/src/core/plan.h +++ b/src/core/plan.h @@ -13,8 +13,7 @@ typedef enum dirty_flags_t : dirty_mask_t { } dirty_flags_t; typedef enum effect_type_t { - ZDWM_EFFECT_NONE, - ZDWM_EFFECT_MAP_WINDOW, + ZDWM_EFFECT_MAP_WINDOW = 1, ZDWM_EFFECT_UNMAP_WINDOW, ZDWM_EFFECT_FOCUS_WINDOW, ZDWM_EFFECT_MOVE_WINDOW,