Files
zdwm/src/backend/x11/event.c
Zedhugh Chen 9275d14c5f feat(runtime): 启动时扫描并接管已有窗口
从 handle_map_request 中提取 populate_window_event 供复用,
新增 backend_scan_windows 扫描 X11 根窗口子树,
runtime_scan 将扫描到的窗口走完整 policy 流程纳入管理。
route_map_request 增加 override_redirect 过滤。
2026-05-07 06:36:58 +08:00

489 lines
14 KiB
C
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include "core/event.h"
#include <stdint.h>
#include <stdio.h>
#include <xcb/xcb.h>
#include <xcb/xcb_event.h>
#include <xcb/xcb_icccm.h>
#include <xcb/xcb_keysyms.h>
#include <xcb/xproto.h>
#include "backend/x11/window.h"
#include "base/macros.h"
#include "base/memory.h"
#include "core/backend.h"
#include "core/types.h"
#include "core/window.h"
#include "internal.h"
static window_state_t *derive_window_states(
const atoms_t *atoms,
const xcb_atom_t *raw,
uint32_t count,
size_t *out_count
) {
if (!raw || count == 0) {
*out_count = 0;
return nullptr;
}
window_state_t *buf = p_new(window_state_t, count);
size_t valid = 0;
for (uint32_t i = 0; i < count; i++) {
window_state_t s = atom_to_window_state(atoms, raw[i]);
if (s != (window_state_t)-1) buf[valid++] = s;
}
if (valid == 0) {
p_delete(&buf);
*out_count = 0;
return nullptr;
}
*out_count = valid;
return buf;
}
static bool derive_skip_taskbar(
const atoms_t *atoms,
const xcb_atom_t *raw,
uint32_t count
) {
for (uint32_t i = 0; i < count; i++)
if (raw[i] == atoms->_NET_WM_STATE_SKIP_TASKBAR) return true;
return false;
}
static bool minimized_from_hints(const xcb_icccm_wm_hints_t *hints) {
return hints->initial_state == XCB_ICCCM_WM_STATE_ICONIC;
}
static bool maximized_from_states(
const atoms_t *atoms,
const xcb_atom_t *state_atoms,
uint32_t state_count
) {
for (uint32_t i = 0; i < state_count; i++) {
if (state_atoms[i] == atoms->_NET_WM_STATE_MAXIMIZED_VERT ||
state_atoms[i] == atoms->_NET_WM_STATE_MAXIMIZED_HORZ)
return true;
}
return false;
}
static bool fullscreen_from_states(
const atoms_t *atoms,
const xcb_atom_t *state_atoms,
uint32_t state_count
) {
for (uint32_t i = 0; i < state_count; i++) {
if (state_atoms[i] == atoms->_NET_WM_STATE_FULLSCREEN) return true;
}
return false;
}
static bool urgent_from_states(
const atoms_t *atoms,
const xcb_atom_t *state_atoms,
uint32_t state_count
) {
for (uint32_t i = 0; i < state_count; ++i) {
if (state_atoms[i] == atoms->_NET_WM_STATE_DEMANDS_ATTENTION) return true;
}
return false;
}
bool populate_window_event(
backend_t *backend,
xcb_window_t window,
window_map_request_event_t *ev
) {
ev->window = (window_id_t)window;
ev->transient_for = window_get_transient_for(backend, window);
xcb_get_window_attributes_reply_t *wa =
window_get_attributes(backend, window);
if (!wa) return false;
ev->override_redirect = (bool)wa->override_redirect;
p_delete(&wa);
xcb_icccm_wm_hints_t wm_hints = {0};
if (window_get_wm_hints(backend, window, &wm_hints)) {
ev->urgent = (bool)xcb_icccm_wm_hints_get_urgency(&wm_hints);
ev->minimized = minimized_from_hints(&wm_hints);
}
if (!window_get_fixed_size(backend, window, &ev->fixed_size)) return false;
if (!window_get_geometry(backend, window, &ev->rect)) return false;
const atoms_t *atoms = &backend->atoms;
xcb_atom_t *state_atoms = nullptr;
uint32_t state_count = 0;
if (!window_get_atom_array(
backend,
window,
atoms->_NET_WM_STATE,
&state_atoms,
&state_count
)) {
return false;
}
if (state_atoms) {
ev->maximized = maximized_from_states(atoms, state_atoms, state_count);
ev->fullscreen = fullscreen_from_states(atoms, state_atoms, state_count);
ev->skip_taskbar = derive_skip_taskbar(atoms, state_atoms, state_count);
ev->props.states = derive_window_states(
atoms,
state_atoms,
state_count,
&ev->props.state_count
);
if (!ev->urgent) {
ev->urgent = urgent_from_states(atoms, state_atoms, state_count);
}
}
p_delete(&state_atoms);
window_layer_props_t *props = &ev->props;
if (!window_get_types(backend, window, &props->types, &props->type_count)) {
p_delete(&props->states);
return false;
}
ev->metadata.role = window_get_role(backend, window);
ev->metadata.title = window_get_title(backend, window);
window_get_class(
backend,
window,
&ev->metadata.class_name,
&ev->metadata.instance_name
);
return true;
}
static bool handle_map_request(
backend_t *backend,
event_t *event,
const xcb_map_request_event_t *xcb_event
) {
event->type = ZDWM_EVENT_WINDOW_MAP_REQUEST;
return populate_window_event(
backend,
xcb_event->window,
&event->as.window_map_request
);
}
static bool handle_unmap_notify(
backend_t *backend,
event_t *event,
const xcb_unmap_notify_event_t *xcb_event
) {
event->type = ZDWM_EVENT_WINDOW_REMOVE;
event->as.window_remove.window = xcb_event->window;
if (XCB_EVENT_SENT(xcb_event)) {
event->as.window_remove.reason = ZDWM_WINDOW_REMOVE_WITHDRAWN;
} else {
event->as.window_remove.reason = ZDWM_WINDOW_REMOVE_DESTROY;
}
return true;
}
static bool handle_destroy_notify(
backend_t *backend,
event_t *event,
const xcb_destroy_notify_event_t *xcb_event
) {
event->type = ZDWM_EVENT_WINDOW_REMOVE;
event->as.window_remove.window = xcb_event->window;
event->as.window_remove.reason = ZDWM_WINDOW_REMOVE_DESTROY;
return true;
}
static bool handle_configure_request(
backend_t *backend,
event_t *event,
const xcb_configure_request_event_t *xcb_event
) {
event->type = ZDWM_EVENT_CONFIGURE_REQUEST;
auto data = &event->as.configure_request;
data->window = xcb_event->window;
data->changed_fields = 0u;
#define EXTRACT_FIELD(XCB_MASK, FIELD_MASK, DATA_FIELD, EVENT_FIELD) \
if (xcb_event->value_mask & XCB_CONFIG_WINDOW_##XCB_MASK) { \
data->DATA_FIELD = xcb_event->EVENT_FIELD; \
data->changed_fields |= ZDWM_CONFIGURE_FIELD_##FIELD_MASK; \
}
EXTRACT_FIELD(X, X, x, x);
EXTRACT_FIELD(Y, Y, y, y);
EXTRACT_FIELD(WIDTH, WIDTH, width, width);
EXTRACT_FIELD(HEIGHT, HEIGHT, height, height);
EXTRACT_FIELD(BORDER_WIDTH, BORDER_WIDTH, border_width, border_width);
EXTRACT_FIELD(SIBLING, SIBLING, sibling, sibling);
EXTRACT_FIELD(STACK_MODE, STACK_MODE, stack_mode, stack_mode);
#undef EXTRACT_FIELD
return true;
}
static modifier_mask_t get_modifiers(uint16_t mask) {
typedef struct modifier_map_t {
modifier_bit_t modifier;
xcb_mod_mask_t mask;
} modifier_map_t;
static constexpr modifier_map_t modifier_map[] = {
{ZDWM_MOD_SHIFT, XCB_MOD_MASK_SHIFT},
{ZDWM_MOD_CONTROL, XCB_MOD_MASK_CONTROL},
{ZDWM_MOD_1, XCB_MOD_MASK_1},
{ZDWM_MOD_2, XCB_MOD_MASK_2},
{ZDWM_MOD_3, XCB_MOD_MASK_3},
{ZDWM_MOD_4, XCB_MOD_MASK_4},
{ZDWM_MOD_5, XCB_MOD_MASK_5},
};
auto mods = ZDWM_MOD_NONE;
for (size_t i = 0; i < countof(modifier_map); ++i) {
auto item = &modifier_map[i];
if (mask & item->mask) mods |= item->modifier;
}
return mods;
}
static bool handle_key_press(
backend_t *backend,
event_t *event,
const xcb_key_press_event_t *xcb_event
) {
auto keycode = xcb_event->detail;
/* keysym without any modifiers */
auto keysym = xcb_key_symbols_get_keysym(backend->key_symbols, keycode, 0);
event->type = ZDWM_EVENT_KEY_PRESS;
event->as.key_press.keycode = keycode;
event->as.key_press.keysym = keysym;
event->as.key_press.modifiers = get_modifiers(xcb_event->state);
return true;
}
static bool handle_enter_notify(
backend_t *backend,
event_t *event,
const xcb_enter_notify_event_t *xcb_event
) {
event->type = ZDWM_EVENT_POINTER_ENTER;
event->as.pointer_enter.window = xcb_event->event;
return true;
}
static bool handle_property_notify(
backend_t *backend,
event_t *event,
const xcb_property_notify_event_t *xcb_event
) {
auto window_id = xcb_event->window;
if (xcb_event->atom == backend->atoms.WM_NAME ||
xcb_event->atom == backend->atoms._NET_WM_NAME) {
event->type = ZDWM_EVENT_WINDOW_METADATA_CHANGED;
auto data = &event->as.window_metadata_change;
data->window = window_id;
data->metadata.title = window_get_title(backend, window_id);
data->changed_fields = ZDWM_WINDOW_METADATA_CHANGE_TITLE;
return true;
}
if (xcb_event->atom == backend->atoms.WM_WINDOW_ROLE) {
event->type = ZDWM_EVENT_WINDOW_METADATA_CHANGED;
auto data = &event->as.window_metadata_change;
data->window = window_id;
data->metadata.role = window_get_role(backend, window_id);
data->changed_fields = ZDWM_WINDOW_METADATA_CHANGE_ROLE;
return true;
}
if (xcb_event->atom == XCB_ATOM_WM_CLASS) {
event->type = ZDWM_EVENT_WINDOW_METADATA_CHANGED;
auto data = &event->as.window_metadata_change;
data->window = window_id;
window_get_class(
backend,
window_id,
&data->metadata.class_name,
&data->metadata.instance_name
);
data->changed_fields =
ZDWM_WINDOW_METADATA_CHANGE_CLASS | ZDWM_WINDOW_METADATA_CHANGE_INSTANCE;
return true;
}
if (xcb_event->atom == XCB_ATOM_WM_HINTS) {
xcb_icccm_wm_hints_t hints = {0};
auto handled = window_get_wm_hints(backend, window_id, &hints);
if (!handled) return false;
event->type = ZDWM_EVENT_WINDOW_STATE_REQUEST;
auto data = &event->as.window_state_request;
data->window = window_id;
if (hints.flags & XCB_ICCCM_WM_HINT_X_URGENCY) {
data->type = ZDWM_WINDOW_STATE_REQUEST_URGENT;
data->action = xcb_icccm_wm_hints_get_urgency(&hints)
? ZDWM_WINDOW_STATE_ACTION_ADD
: ZDWM_WINDOW_STATE_ACTION_REMOVE;
return true;
}
return false;
}
if (xcb_event->atom == XCB_ATOM_WM_NORMAL_HINTS) {
bool fixed_size = false;
if (!window_get_fixed_size(backend, window_id, &fixed_size)) return false;
event->type = ZDWM_EVENT_WINDOW_STATE_REQUEST;
auto data = &event->as.window_state_request;
data->window = window_id;
data->type = ZDWM_WINDOW_STATE_REQUEST_FIXED_SIZE;
data->action = fixed_size ? ZDWM_WINDOW_STATE_ACTION_ADD
: ZDWM_WINDOW_STATE_ACTION_REMOVE;
return true;
}
return false;
}
static bool handle_client_message(
backend_t *backend,
event_t *event,
const xcb_client_message_event_t *xcb_event
) {
auto atoms = &backend->atoms;
if (xcb_event->type == atoms->_NET_ACTIVE_WINDOW) {
event->type = ZDWM_EVENT_WINDOW_ACTIVATE_REQUEST;
event->as.window_activate_request.window = xcb_event->window;
event->as.window_activate_request.source = xcb_event->data.data32[0];
return true;
}
if (xcb_event->type == atoms->WM_CHANGE_STATE) {
if (xcb_event->data.data32[0] == XCB_ICCCM_WM_STATE_ICONIC) {
event->type = ZDWM_EVENT_WINDOW_STATE_REQUEST;
event->as.window_state_request.window = xcb_event->window;
event->as.window_state_request.type = ZDWM_WINDOW_STATE_REQUEST_MINIMIZED;
event->as.window_state_request.action = ZDWM_WINDOW_STATE_ACTION_ADD;
return true;
}
return false;
}
if (xcb_event->type == atoms->_NET_WM_STATE) {
event->type = ZDWM_EVENT_WINDOW_STATE_REQUEST;
auto data = &event->as.window_state_request;
data->window = xcb_event->window;
/**
* _NET_WM_STATE 协议数据格式为
* data.data32 = {
* action, // 操作类型(添加/移除/切换)
* property1, // 第一个状态原子如_NET_WM_STATE_FULLSCREEN
* property2, // 第二个状态原子可选通常为0
* 0, // 预留字段固定为0
* 0 // 预留字段固定为0
* };
* action 值可能为
* _NET_WM_STATE_ADD1添加全屏状态窗口进入全屏模式
* _NET_WM_STATE_REMOVE0移除全屏状态窗口退出全屏模式
* _NET_WM_STATE_TOGGLE2切换全屏状态若当前全屏则退出反之进入
*/
auto action = xcb_event->data.data32[0];
switch (action) {
case 0:
data->action = ZDWM_WINDOW_STATE_ACTION_REMOVE;
break;
case 1:
data->action = ZDWM_WINDOW_STATE_ACTION_ADD;
break;
case 2:
data->action = ZDWM_WINDOW_STATE_ACTION_TOGGLE;
break;
}
auto properties = &xcb_event->data.data32[1];
uint32_t count = 2;
if (maximized_from_states(atoms, properties, count)) {
data->type = ZDWM_WINDOW_STATE_REQUEST_MAXIMIZED;
return true;
}
if (fullscreen_from_states(atoms, properties, count)) {
data->type = ZDWM_WINDOW_STATE_REQUEST_FULLSCREEN;
return true;
}
if (derive_skip_taskbar(atoms, properties, count)) {
data->type = ZDWM_WINDOW_STATE_REQUEST_SKIP_TASKBAR;
return true;
}
if (urgent_from_states(atoms, properties, count)) {
data->type = ZDWM_WINDOW_STATE_REQUEST_URGENT;
return true;
}
return false;
}
return false;
}
bool backend_next_event(backend_t *backend, event_t *event) {
if (!backend || !backend->conn || !event) return false;
for (;;) {
xcb_generic_event_t *raw_event = xcb_wait_for_event(backend->conn);
if (!raw_event) return false;
uint8_t response_type = XCB_EVENT_RESPONSE_TYPE(raw_event);
bool handled = false;
auto label = xcb_event_get_label(response_type);
printf("xcb event type: %s[%u]\n", label, response_type);
switch (response_type) {
#define EVENT(type, handler) \
case type: \
event_reset(event); \
handled = handler(backend, event, (void *)raw_event); \
break
EVENT(XCB_MAP_REQUEST, handle_map_request);
EVENT(XCB_UNMAP_NOTIFY, handle_unmap_notify);
EVENT(XCB_DESTROY_NOTIFY, handle_destroy_notify);
EVENT(XCB_CONFIGURE_REQUEST, handle_configure_request);
EVENT(XCB_KEY_PRESS, handle_key_press);
EVENT(XCB_ENTER_NOTIFY, handle_enter_notify);
EVENT(XCB_PROPERTY_NOTIFY, handle_property_notify);
EVENT(XCB_CLIENT_MESSAGE, handle_client_message);
#undef EVENT
default:
break;
}
p_delete(&raw_event);
if (handled) return true;
event_reset(event);
}
}