Compare commits

...

3 Commits

Author SHA1 Message Date
d47ca9cf1a feat(tray): 完善 tray_cleanup 释放图标窗口并归还 selection owner 2026-07-25 04:07:57 +08:00
e39eb245b6 feat(tray): 处理 SYSTEM_TRAY_REQUEST_DOCK 实现图标嵌入托盘容器
- 注册 _NET_SYSTEM_TRAY_OPCODE 与 _XEMBED atom
- 在事件分发中拦截托盘 client message
- tray_add_icon 将请求 dock 的窗口 reparent 到 container,
  发送 XEMBED_EMBEDDED_NOTIFY 并 map,维护 icons 数组并
  触发图标变更回调
2026-07-25 03:59:04 +08:00
3c5cfd3ead feat(tray): 实现 tray_cleanup 并在 backend_destroy 中调用以释放托盘资源 2026-07-25 01:07:49 +08:00
5 changed files with 200 additions and 1 deletions

View File

@@ -195,6 +195,8 @@ backend_t *backend_create(const char *display_name) {
void backend_destroy(backend_t *backend) {
if (!backend) return;
tray_cleanup(backend);
p_delete(&backend->config_list.cfgs);
p_clear(&backend->config_list, 1);

View File

@@ -10,6 +10,7 @@
#include <xcb/xproto.h>
#include "backend/x11/common.h"
#include "backend/x11/tray.h"
#include "backend/x11/window.h"
#include "base/memory.h"
#include "interface/backend.h"
@@ -419,6 +420,8 @@ static bool handle_client_message(
event_t *event,
const xcb_client_message_event_t *xcb_event
) {
if (tray_handle_client_message(backend, xcb_event)) return false;
auto atoms = &backend->atoms;
if (xcb_event->type == atoms->_NET_ACTIVE_WINDOW) {

View File

@@ -47,6 +47,8 @@
#define ATOM_LIST(X) \
X(MANAGER) \
X(_NET_SYSTEM_TRAY_OPCODE) \
X(_XEMBED) \
\
X(COMPOUND_TEXT) \
X(UTF8_STRING) \

View File

@@ -1,13 +1,16 @@
#include "backend/x11/tray.h"
#include <assert.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <xcb/xcb.h>
#include <xcb/xcb_aux.h>
#include <xcb/xproto.h>
#include <zdwm/types.h>
#include "backend/x11/window.h"
#include "base/array.h"
#include "base/log.h"
#include "base/macros.h"
#include "base/memory.h"
@@ -15,6 +18,24 @@
#include "interface/types.h"
#include "internal.h"
typedef enum tray_opcode_t {
SYSTEM_TRAY_REQUEST_DOCK = 0,
SYSTEM_TRAY_BEGIN_MESSAGE = 1,
SYSTEM_TRAY_CANCEL_MESSAGE = 2,
} tray_opcode_t;
typedef enum xembed_message_t {
XEMBED_EMBEDDED_NOTIFY = 0,
} xembed_message_t;
static constexpr auto XEMBED_VERSION = 0;
typedef struct tray_icon_t {
xcb_window_t window;
uint16_t natural_width;
uint16_t natural_height;
} tray_icon_t;
typedef struct tray_host_t {
bool enabled;
bool initialized;
@@ -22,7 +43,11 @@ typedef struct tray_host_t {
xcb_window_t host_window;
xcb_window_t container;
int32_t icon_size;
tray_icon_t *icons;
size_t icon_count;
size_t icon_capacity;
tray_icons_change_cb_t icon_change_cb;
void *cb_user_data;
} tray_host_t;
@@ -37,6 +62,36 @@ static tray_host_t *get_tray_host(void *handle) {
return tray;
}
static tray_icon_t *tray_get_icon(tray_host_t *tray, xcb_window_t window) {
for (size_t i = 0; i < tray->icon_count; ++i) {
auto icon = &tray->icons[i];
if (icon->window == window) return icon;
}
return nullptr;
}
static inline void tray_dispatch_callback(tray_host_t *tray) {
assert(tray);
if (tray->icon_change_cb) tray->icon_change_cb(tray->cb_user_data);
}
static void tray_release_icon_window(backend_t *backend, tray_icon_t *icon) {
if (!icon || icon->window == XCB_WINDOW_NONE) return;
auto tray = get_tray_host(backend);
if (!tray || !tray->initialized) return;
auto conn = backend->conn;
auto root = backend->screen->root;
auto window = icon->window;
auto mask = XCB_CW_EVENT_MASK;
xcb_params_cw_t params = {.event_mask = XCB_EVENT_MASK_NO_EVENT};
xcb_change_save_set(conn, XCB_SET_MODE_DELETE, window);
xcb_aux_change_window_attributes(conn, window, mask, &params);
xcb_reparent_window(conn, window, root, 0, 0);
}
static window_id_t tray_host_window(void *handle) {
auto tray = get_tray_host(handle);
if (!tray) return ZDWM_WINDOW_ID_INVALID;
@@ -233,7 +288,7 @@ void tray_init(
};
if (!tray_create_container_window(tray, backend, host_window_params)) return;
if (!tray_take_selection_owner(tray, backend)) {
/* TODO: cleanup tray */
tray_cleanup(backend);
return;
}
@@ -243,3 +298,133 @@ void tray_init(
tray_broadcast_manager(tray, backend);
xcb_flush(backend->conn);
}
void tray_cleanup(backend_t *backend) {
auto tray = backend->tray;
if (!tray) return;
for (size_t i = 0; i < tray->icon_count; ++i) {
tray_release_icon_window(backend, &tray->icons[i]);
}
p_delete(&tray->icons);
tray->icon_count = 0;
tray->icon_capacity = 0;
auto conn = backend->conn;
auto atom = tray->selection_atom;
if (atom != XCB_ATOM_NONE) {
xcb_set_selection_owner(conn, XCB_WINDOW_NONE, atom, XCB_CURRENT_TIME);
}
if (tray->container != XCB_WINDOW_NONE) {
xcb_destroy_window(conn, tray->container);
}
p_clear(backend->tray, 1);
p_delete(&backend->tray);
}
static void
tray_select_icon_events(xcb_connection_t *conn, xcb_window_t window) {
xcb_params_cw_t params = {
.event_mask =
XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_PROPERTY_CHANGE,
};
uint32_t mask = XCB_CW_EVENT_MASK;
xcb_aux_change_window_attributes(conn, window, mask, &params);
xcb_clear_area(conn, false, window, 0, 0, 0, 0);
}
/* reference:
* https://specifications.freedesktop.org/xembed/latest-single/#id-1.7.4 */
typedef struct xembed_message_data_t {
uint32_t message; /* message opcode */
uint32_t detail; /* message detail */
uint32_t data1; /* message data 1 */
uint32_t data2; /* message data 2 */
} xembed_message_data_t;
static void tray_send_xembed_message(
backend_t *backend,
xcb_window_t window,
xembed_message_data_t message
) {
auto conn = backend->conn;
auto message_type = backend->atoms._XEMBED;
xcb_client_message_event_t event = {
.response_type = XCB_CLIENT_MESSAGE,
.format = 32,
.window = window,
.type = message_type,
.data.data32 = {
[0] = XCB_CURRENT_TIME,
[1] = message.message,
[2] = message.detail,
[3] = message.data1,
[4] = message.data2,
},
};
uint32_t event_mask = XCB_EVENT_MASK_NO_EVENT;
xcb_send_event(conn, false, window, event_mask, (char *)&event);
}
static void tray_add_icon(backend_t *backend, xcb_window_t window) {
if (window == XCB_WINDOW_NONE) return;
auto tray = get_tray_host(backend);
if (!tray || !tray->initialized || tray_get_icon(tray, window)) return;
rect_t geometry = {0};
if (!window_get_geometry(backend, window, &geometry)) return;
auto icon = array_push(tray->icons, tray->icon_count, tray->icon_capacity);
*icon = (tray_icon_t){
.window = window,
.natural_width = geometry.width,
.natural_height = geometry.height
};
auto conn = backend->conn;
auto container = tray->container;
xembed_message_data_t message = {
.message = XEMBED_EMBEDDED_NOTIFY,
.detail = 0,
.data1 = container,
.data2 = XEMBED_VERSION,
};
xcb_change_save_set(conn, XCB_SET_MODE_INSERT, window);
xcb_reparent_window(conn, window, tray->container, 0, 0);
tray_select_icon_events(conn, window);
tray_send_xembed_message(backend, window, message);
xcb_map_window(conn, window);
tray_dispatch_callback(tray);
}
bool tray_handle_client_message(
backend_t *backend,
const xcb_client_message_event_t *ev
) {
auto tray = get_tray_host(backend);
if (!tray || !tray->enabled || !tray->initialized) return false;
if (ev->window != tray->container && ev->window != backend->screen->root) {
return false;
}
auto opcode = ev->data.data32[1];
switch (opcode) {
case SYSTEM_TRAY_REQUEST_DOCK:
auto window = ev->data.data32[2];
tray_add_icon(backend, window);
break;
case SYSTEM_TRAY_BEGIN_MESSAGE:
case SYSTEM_TRAY_CANCEL_MESSAGE:
default:
break;
}
xcb_flush(backend->conn);
return true;
}

View File

@@ -9,9 +9,16 @@ extern const tray_api_t tray_api;
typedef struct backend_t backend_t;
/* TODO: 添加窗口背景色 */
void tray_init(
backend_t *backend,
xcb_window_t host_window,
int32_t host_width,
int32_t host_height
);
void tray_cleanup(backend_t *backend);
bool tray_handle_client_message(
backend_t *backend,
const xcb_client_message_event_t *ev
);