From 9728fad871ddbc3d4a69cff8e216544376726448 Mon Sep 17 00:00:00 2001 From: Zedhugh Chen Date: Tue, 7 Apr 2026 03:16:51 +0800 Subject: [PATCH] =?UTF-8?q?backend:=20X11=20=E5=90=8E=E7=AB=AF=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E8=8E=B7=E5=8F=96=E7=AA=97=E5=8F=A3=20metadata=20?= =?UTF-8?q?=E4=BF=A1=E6=81=AF=E6=96=B9=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/backend/x11/backend.c | 39 +++++++++++++++++++++++ src/backend/x11/event.c | 26 +++++++++++----- src/backend/x11/internal.h | 11 +++++++ src/backend/x11/window.c | 63 ++++++++++++++++++++++++++++++++++++++ src/backend/x11/window.h | 33 ++++++++++++++++++++ src/base/memory.h | 6 ++++ src/core/window.h | 8 ++--- tests/core/CMakeLists.txt | 1 + 8 files changed, 176 insertions(+), 11 deletions(-) create mode 100644 src/backend/x11/window.c create mode 100644 src/backend/x11/window.h diff --git a/src/backend/x11/backend.c b/src/backend/x11/backend.c index 9f1f4bc..452be2f 100644 --- a/src/backend/x11/backend.c +++ b/src/backend/x11/backend.c @@ -13,10 +13,47 @@ #include "backend/output_utils.h" #include "base/log.h" +#include "base/macros.h" #include "base/memory.h" #include "core/types.h" #include "internal.h" /* IWYU pragma: keep */ +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; + + atom_item_t atom_list[] = { + {"COMPOUND_TEXT", sizeof("COMPOUND_TEXT") - 1, &atoms->COMPOUND_TEXT}, + {"UTF8_STRING", sizeof("UTF8_STRING") - 1, &atoms->UTF8_STRING}, + + {"WM_WINDOW_ROLE", sizeof("WM_WINDOW_ROLE") - 1, &atoms->WM_WINDOW_ROLE}, + {"WM_NAME", sizeof("WM_NAME") - 1, &atoms->WM_NAME}, + {"_NET_WM_NAME", sizeof("_NET_WM_NAME") - 1, &atoms->_NET_WM_NAME}, + }; + + 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); + } +} + backend_t *backend_create(const char *display_name) { int screen_num = 0; xcb_connection_t *conn = xcb_connect(display_name, &screen_num); @@ -63,6 +100,8 @@ backend_t *backend_create(const char *display_name) { backend->have_xfixes = false; } + atoms_init(backend); + return backend; } diff --git a/src/backend/x11/event.c b/src/backend/x11/event.c index db9cb4c..806c40e 100644 --- a/src/backend/x11/event.c +++ b/src/backend/x11/event.c @@ -1,21 +1,32 @@ +#include "core/event.h" + #include #include #include #include +#include "backend/x11/window.h" #include "base/memory.h" #include "core/backend.h" +#include "core/window.h" #include "internal.h" /* IWYU pragma: keep */ -static bool handle_map_request(event_t *event, +static bool handle_map_request(backend_t *backend, event_t *event, const xcb_map_request_event_t *xcb_event) { p_clear(event, 1); + xcb_window_t window = xcb_event->window; event->type = ZDWM_EVENT_WINDOW_MAP_REQUEST; + event->data.window_map_request.window = window; + window_metadata_t *metadata = &event->data.window_map_request.metadata; + metadata->app_id = window_get_role(backend, window); + metadata->title = window_get_title(backend, window); + window_get_class(backend, window, &metadata->class_name, + &metadata->instance_name); /* TODO: */ return true; } -static bool handle_unmap_notify(event_t *event, +static bool handle_unmap_notify(backend_t *backend, event_t *event, const xcb_unmap_notify_event_t *xcb_event) { p_clear(event, 1); event->type = ZDWM_EVENT_WINDOW_REMOVE; @@ -23,7 +34,7 @@ static bool handle_unmap_notify(event_t *event, return true; } -static bool handle_destroy_notify(event_t *event, +static bool handle_destroy_notify(backend_t *backend, event_t *event, const xcb_destroy_notify_event_t *xcb_event) { p_clear(event, 1); event->type = ZDWM_EVENT_WINDOW_REMOVE; @@ -32,7 +43,8 @@ static bool handle_destroy_notify(event_t *event, } static bool handle_configure_request( - event_t *event, const xcb_configure_request_event_t *xcb_event) { + backend_t *backend, event_t *event, + const xcb_configure_request_event_t *xcb_event) { p_clear(event, 1); event->type = ZDWM_EVENT_CONFIGURE_REQUEST; /* TODO: */ @@ -52,9 +64,9 @@ bool backend_next_event(backend_t *backend, event_t *event) { printf("xcb event type: %u\n", response_type); switch (response_type) { -#define EVENT(type, handler) \ - case type: \ - handled = handler(event, (void *)raw_event); \ +#define EVENT(type, handler) \ + case type: \ + handled = handler(backend, event, (void *)raw_event); \ break EVENT(XCB_MAP_REQUEST, handle_map_request); diff --git a/src/backend/x11/internal.h b/src/backend/x11/internal.h index 460bc5e..bf4adf7 100644 --- a/src/backend/x11/internal.h +++ b/src/backend/x11/internal.h @@ -1,12 +1,23 @@ #pragma once #include +#include #include "core/backend.h" /* IWYU pragma: keep */ +typedef struct atoms_t { + xcb_atom_t COMPOUND_TEXT; + xcb_atom_t UTF8_STRING; + xcb_atom_t WM_WINDOW_ROLE; + xcb_atom_t WM_NAME; + xcb_atom_t _NET_WM_NAME; +} atoms_t; + struct backend_t { xcb_connection_t *conn; xcb_screen_t *screen; int screenp; bool have_xfixes; + + atoms_t atoms; }; diff --git a/src/backend/x11/window.c b/src/backend/x11/window.c new file mode 100644 index 0000000..894410f --- /dev/null +++ b/src/backend/x11/window.c @@ -0,0 +1,63 @@ +#include "window.h" + +#include +#include +#include +#include +#include + +#include "base/memory.h" +#include "internal.h" + +static char *window_get_text_property(backend_t *backend, xcb_window_t window, + xcb_atom_t property) { + xcb_get_property_cookie_t cookie = xcb_get_property_unchecked( + backend->conn, false, window, property, XCB_ATOM_ANY, 0, UINT32_MAX); + xcb_get_property_reply_t *reply = + xcb_get_property_reply(backend->conn, cookie, nullptr); + if (!reply) return nullptr; + + int length = xcb_get_property_value_length(reply); + char *value = (char *)xcb_get_property_value(reply); + + char *text = nullptr; + if (length && reply->format == 8 && + (reply->type == XCB_ATOM_STRING || + reply->type == backend->atoms.UTF8_STRING || + reply->type == backend->atoms.COMPOUND_TEXT)) { + text = p_strndup(value, (size_t)length); + } + + p_delete(&reply); + return text; +} + +char *window_get_role(backend_t *backend, xcb_window_t window) { + xcb_atom_t property = backend->atoms.WM_WINDOW_ROLE; + return window_get_text_property(backend, window, property); +} + +char *window_get_title(backend_t *backend, xcb_window_t window) { + xcb_atom_t property = backend->atoms._NET_WM_NAME; + char *name = window_get_text_property(backend, window, property); + if (!name) { + property = backend->atoms.WM_NAME; + name = window_get_text_property(backend, window, property); + } + return name; +} + +void window_get_class(backend_t *backend, xcb_window_t window, char **class_out, + char **instance_out) { + xcb_icccm_get_wm_class_reply_t prop; + xcb_get_property_cookie_t cookie = + xcb_icccm_get_wm_class_unchecked(backend->conn, window); + if (!xcb_icccm_get_wm_class_reply(backend->conn, cookie, &prop, nullptr)) { + return; + } + + if (class_out) *class_out = p_strdup_nullable(prop.class_name); + if (instance_out) *instance_out = p_strdup_nullable(prop.instance_name); + + xcb_icccm_get_wm_class_reply_wipe(&prop); +} diff --git a/src/backend/x11/window.h b/src/backend/x11/window.h new file mode 100644 index 0000000..728590c --- /dev/null +++ b/src/backend/x11/window.h @@ -0,0 +1,33 @@ +#pragma once + +#include + +#include "core/backend.h" + +/** + * @brief 获取窗口的 role 属性 + * @param backend 后端实例指针 + * @param window 目标窗口 + * @return 返回窗口的 role 属性字符串,调用者持有其内存并负责调用 free 释放 + */ +char *window_get_role(backend_t *backend, xcb_window_t window); + +/** + * @brief 获取窗口标题 + * @details 获取窗口的 _NET_WM_NAME/WM_NAME 属性作为其 title + * @param backend 后端实例指针 + * @param window 目标窗口 + * @return 返回窗口标题字符串,由调用者持有其内存并负责调用 free 释放 + */ +char *window_get_title(backend_t *backend, xcb_window_t window); + +/** + * @brief 获取窗口的 class 和 instance 信息 + * @param backend 后端实例指针 + * @param window 目标窗口 + * @param class_out class 信息返回指针,调用者持有其内存并负责调用 free 释放 + * @param instance_out instance 信息返回指针,调用者持有其内存并负责调用 free + * 释放 + */ +void window_get_class(backend_t *backend, xcb_window_t window, char **class_out, + char **instance_out); diff --git a/src/base/memory.h b/src/base/memory.h index 39e093f..c9a47bf 100644 --- a/src/base/memory.h +++ b/src/base/memory.h @@ -24,6 +24,12 @@ static inline char *p_strdup(const char *text) { return r; } +static inline char *p_strndup(const char *text, size_t n) { + char *r = strndup(text, n); + if (!r) abort(); + return r; +} + static inline char *p_strdup_nullable(const char *text) { if (!text) return nullptr; return p_strdup(text); diff --git a/src/core/window.h b/src/core/window.h index e1d0259..2ee9c91 100644 --- a/src/core/window.h +++ b/src/core/window.h @@ -45,10 +45,10 @@ typedef struct window_props_t { } window_props_t; typedef struct window_metadata_t { - const char *title; - const char *app_id; - const char *class_name; - const char *instance_name; + char *title; + char *app_id; /* X11 中将其当 role 用 */ + char *class_name; + char *instance_name; } window_metadata_t; typedef struct window_geometry_t { diff --git a/tests/core/CMakeLists.txt b/tests/core/CMakeLists.txt index 4c46dcd..98c5ecf 100644 --- a/tests/core/CMakeLists.txt +++ b/tests/core/CMakeLists.txt @@ -9,6 +9,7 @@ add_executable(${TEST_APP_NAME} ${SOURCE_DIR}/config/runtime_config.c ${SOURCE_DIR}/backend/x11/backend.c ${SOURCE_DIR}/backend/x11/event.c + ${SOURCE_DIR}/backend/x11/window.c ${SOURCE_DIR}/core/layer.c ${SOURCE_DIR}/core/layout.c ${SOURCE_DIR}/core/runtime.c