486 lines
14 KiB
C
486 lines
14 KiB
C
#include "window.h"
|
|
|
|
#include <stddef.h>
|
|
#include <stdint.h>
|
|
#include <xcb/xcb.h>
|
|
#include <xcb/xcb_aux.h>
|
|
#include <xcb/xcb_icccm.h>
|
|
#include <xcb/xcb_keysyms.h>
|
|
#include <xcb/xproto.h>
|
|
|
|
#include "backend/x11/common.h"
|
|
#include "backend/x11/cursor.h"
|
|
#include "base/macros.h"
|
|
#include "base/memory.h"
|
|
#include "core/backend.h"
|
|
#include "core/types.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);
|
|
}
|
|
|
|
xcb_window_t window_get_transient_for(backend_t *backend, xcb_window_t window) {
|
|
xcb_connection_t *conn = backend->conn;
|
|
xcb_get_property_cookie_t cookie =
|
|
xcb_icccm_get_wm_transient_for_unchecked(conn, window);
|
|
xcb_window_t trans_for = XCB_WINDOW_NONE;
|
|
if (xcb_icccm_get_wm_transient_for_reply(conn, cookie, &trans_for, nullptr)) {
|
|
return trans_for;
|
|
}
|
|
|
|
return XCB_WINDOW_NONE;
|
|
}
|
|
|
|
xcb_get_window_attributes_reply_t *
|
|
window_get_attributes(backend_t *backend, xcb_window_t window) {
|
|
xcb_get_window_attributes_cookie_t cookie =
|
|
xcb_get_window_attributes(backend->conn, window);
|
|
return xcb_get_window_attributes_reply(backend->conn, cookie, nullptr);
|
|
}
|
|
|
|
static window_type_t
|
|
atom_to_window_type(const atoms_t *atoms, xcb_atom_t atom) {
|
|
if (atom == atoms->_NET_WM_WINDOW_TYPE_DESKTOP)
|
|
return ZDWM_WINDOW_TYPE_DESKTOP;
|
|
if (atom == atoms->_NET_WM_WINDOW_TYPE_DOCK) return ZDWM_WINDOW_TYPE_DOCK;
|
|
if (atom == atoms->_NET_WM_WINDOW_TYPE_TOOLBAR)
|
|
return ZDWM_WINDOW_TYPE_TOOLBAR;
|
|
if (atom == atoms->_NET_WM_WINDOW_TYPE_DIALOG) return ZDWM_WINDOW_TYPE_DIALOG;
|
|
if (atom == atoms->_NET_WM_WINDOW_TYPE_UTILITY)
|
|
return ZDWM_WINDOW_TYPE_UTILITY;
|
|
if (atom == atoms->_NET_WM_WINDOW_TYPE_SPLASH) return ZDWM_WINDOW_TYPE_SPLASH;
|
|
if (atom == atoms->_NET_WM_WINDOW_TYPE_MENU) return ZDWM_WINDOW_TYPE_MENU;
|
|
if (atom == atoms->_NET_WM_WINDOW_TYPE_DROPDOWN_MENU)
|
|
return ZDWM_WINDOW_TYPE_DROPDOWN_MENU;
|
|
if (atom == atoms->_NET_WM_WINDOW_TYPE_POPUP_MENU)
|
|
return ZDWM_WINDOW_TYPE_POPUP_MENU;
|
|
if (atom == atoms->_NET_WM_WINDOW_TYPE_TOOLTIP)
|
|
return ZDWM_WINDOW_TYPE_TOOLTIP;
|
|
if (atom == atoms->_NET_WM_WINDOW_TYPE_COMBO) return ZDWM_WINDOW_TYPE_COMBO;
|
|
if (atom == atoms->_NET_WM_WINDOW_TYPE_DND) return ZDWM_WINDOW_TYPE_DND;
|
|
if (atom == atoms->_NET_WM_WINDOW_TYPE_NOTIFICATION)
|
|
return ZDWM_WINDOW_TYPE_NOTIFICATION;
|
|
return ZDWM_WINDOW_TYPE_NORMAL;
|
|
}
|
|
|
|
bool window_get_types(
|
|
backend_t *backend,
|
|
xcb_window_t window,
|
|
window_type_t **types,
|
|
size_t *count
|
|
) {
|
|
uint32_t atom_count = 0;
|
|
xcb_atom_t *atoms = nullptr;
|
|
if (!window_get_atom_array(
|
|
backend,
|
|
window,
|
|
backend->atoms._NET_WM_WINDOW_TYPE,
|
|
&atoms,
|
|
&atom_count
|
|
)) {
|
|
return false;
|
|
}
|
|
if (!atoms) {
|
|
*types = nullptr;
|
|
*count = 0;
|
|
return true;
|
|
}
|
|
|
|
*count = atom_count;
|
|
*types = p_new(window_type_t, atom_count);
|
|
for (uint32_t i = 0; i < atom_count; i++) {
|
|
(*types)[i] = atom_to_window_type(&backend->atoms, atoms[i]);
|
|
}
|
|
|
|
p_delete(&atoms);
|
|
return true;
|
|
}
|
|
|
|
bool window_get_size_hints(
|
|
backend_t *backend,
|
|
xcb_window_t window,
|
|
xcb_size_hints_t *out
|
|
) {
|
|
xcb_connection_t *conn = backend->conn;
|
|
xcb_get_property_cookie_t cookie =
|
|
xcb_icccm_get_wm_normal_hints_unchecked(conn, window);
|
|
if (!xcb_icccm_get_wm_normal_hints_reply(conn, cookie, out, nullptr)) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool window_get_geometry(backend_t *backend, xcb_window_t window, rect_t *out) {
|
|
xcb_get_geometry_cookie_t cookie = xcb_get_geometry(backend->conn, window);
|
|
xcb_get_geometry_reply_t *reply =
|
|
xcb_get_geometry_reply(backend->conn, cookie, nullptr);
|
|
if (!reply) return false;
|
|
|
|
out->x = reply->x;
|
|
out->y = reply->y;
|
|
out->width = reply->width;
|
|
out->height = reply->height;
|
|
|
|
p_delete(&reply);
|
|
return true;
|
|
}
|
|
|
|
bool window_get_atom_array(
|
|
backend_t *backend,
|
|
xcb_window_t window,
|
|
xcb_atom_t property,
|
|
xcb_atom_t **out_atoms,
|
|
uint32_t *out_len
|
|
) {
|
|
xcb_get_property_cookie_t cookie = xcb_get_property_unchecked(
|
|
backend->conn,
|
|
false,
|
|
window,
|
|
property,
|
|
XCB_ATOM_ATOM,
|
|
0,
|
|
UINT32_MAX
|
|
);
|
|
xcb_get_property_reply_t *reply =
|
|
xcb_get_property_reply(backend->conn, cookie, nullptr);
|
|
if (!reply) return false;
|
|
|
|
if (reply->type == XCB_ATOM_ATOM && reply->format == 32 && reply->value_len) {
|
|
*out_len = reply->value_len;
|
|
*out_atoms =
|
|
p_copy((xcb_atom_t *)xcb_get_property_value(reply), reply->value_len);
|
|
} else {
|
|
*out_len = 0;
|
|
*out_atoms = nullptr;
|
|
}
|
|
|
|
p_delete(&reply);
|
|
return true;
|
|
}
|
|
|
|
bool window_get_wm_hints(
|
|
backend_t *backend,
|
|
xcb_window_t window,
|
|
xcb_icccm_wm_hints_t *out
|
|
) {
|
|
xcb_get_property_cookie_t cookie =
|
|
xcb_icccm_get_wm_hints_unchecked(backend->conn, window);
|
|
if (!xcb_icccm_get_wm_hints_reply(backend->conn, cookie, out, nullptr)) {
|
|
p_clear(out, 1);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
window_state_t atom_to_window_state(const atoms_t *atoms, xcb_atom_t atom) {
|
|
if (atom == atoms->_NET_WM_STATE_FULLSCREEN)
|
|
return ZDWM_WINDOW_STATE_FULLSCREEN;
|
|
if (atom == atoms->_NET_WM_STATE_ABOVE) return ZDWM_WINDOW_STATE_ABOVE;
|
|
if (atom == atoms->_NET_WM_STATE_STICKY) return ZDWM_WINDOW_STATE_STICKY;
|
|
if (atom == atoms->_NET_WM_STATE_MODAL) return ZDWM_WINDOW_STATE_MODAL;
|
|
return (window_state_t)-1;
|
|
}
|
|
|
|
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;
|
|
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_set_icccm_wm_state(
|
|
xcb_connection_t *conn,
|
|
xcb_window_t window,
|
|
xcb_icccm_wm_state_t state
|
|
) {
|
|
xcb_icccm_wm_hints_t hints;
|
|
xcb_icccm_wm_hints_set_none(&hints);
|
|
switch (state) {
|
|
case XCB_ICCCM_WM_STATE_WITHDRAWN:
|
|
xcb_icccm_wm_hints_set_withdrawn(&hints);
|
|
break;
|
|
case XCB_ICCCM_WM_STATE_NORMAL:
|
|
xcb_icccm_wm_hints_set_normal(&hints);
|
|
break;
|
|
case XCB_ICCCM_WM_STATE_ICONIC:
|
|
xcb_icccm_wm_hints_set_iconic(&hints);
|
|
break;
|
|
}
|
|
xcb_icccm_set_wm_hints(conn, window, &hints);
|
|
}
|
|
|
|
void window_set_net_wm_state(
|
|
backend_t *backend,
|
|
xcb_window_t window,
|
|
size_t count,
|
|
xcb_atom_t *atoms
|
|
) {
|
|
xcb_change_property(
|
|
backend->conn,
|
|
XCB_PROP_MODE_REPLACE,
|
|
window,
|
|
backend->atoms._NET_WM_STATE,
|
|
XCB_ATOM_ATOM,
|
|
32,
|
|
count,
|
|
atoms
|
|
);
|
|
}
|
|
|
|
static uint16_t get_xcb_modifier(modifier_mask_t modifiers) {
|
|
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},
|
|
};
|
|
|
|
uint16_t mods = ZDWM_MOD_NONE;
|
|
for (size_t i = 0; i < countof(modifier_map); ++i) {
|
|
auto item = &modifier_map[i];
|
|
if (modifiers & item->modifier) mods |= item->mask;
|
|
}
|
|
return mods;
|
|
}
|
|
|
|
void window_grab_keys(
|
|
backend_t *backend,
|
|
xcb_window_t window,
|
|
const key_bind_t *keys,
|
|
size_t count
|
|
) {
|
|
auto conn = backend->conn;
|
|
auto key_symbols = backend->key_symbols;
|
|
|
|
xcb_ungrab_key(conn, XCB_GRAB_ANY, window, XCB_MOD_MASK_ANY);
|
|
|
|
xcb_grab_mode_t mode = XCB_GRAB_MODE_ASYNC;
|
|
|
|
for (size_t i = 0; i < count; ++i) {
|
|
auto key = &keys[i];
|
|
auto keycodes = xcb_key_symbols_get_keycode(key_symbols, key->keysym);
|
|
if (!keycodes) continue;
|
|
|
|
auto modifiers = get_xcb_modifier(key->modifiers);
|
|
for (auto keycode = keycodes; *keycode != XCB_NO_SYMBOL; ++keycode) {
|
|
xcb_grab_key(conn, true, window, modifiers, *keycode, mode, mode);
|
|
}
|
|
p_delete(&keycodes);
|
|
}
|
|
}
|
|
|
|
static visual_t *get_alpha_visual(backend_t *backend) {
|
|
auto depth_iter = xcb_screen_allowed_depths_iterator(backend->screen);
|
|
for (; depth_iter.rem; xcb_depth_next(&depth_iter)) {
|
|
if (depth_iter.data->depth != 32) continue;
|
|
|
|
auto visual_iter = xcb_depth_visuals_iterator(depth_iter.data);
|
|
for (; visual_iter.rem; xcb_visualtype_next(&visual_iter)) {
|
|
if (visual_iter.data->_class == XCB_VISUAL_CLASS_TRUE_COLOR) {
|
|
auto visual = p_new(visual_t, 1);
|
|
visual->visual = visual_iter.data;
|
|
visual->depth = depth_iter.data->depth;
|
|
return visual;
|
|
}
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
static visual_t *get_root_visual(backend_t *backend) {
|
|
auto depth_iter = xcb_screen_allowed_depths_iterator(backend->screen);
|
|
auto visual_id = backend->screen->root_visual;
|
|
for (; depth_iter.rem; xcb_depth_next(&depth_iter)) {
|
|
auto visual_iter = xcb_depth_visuals_iterator(depth_iter.data);
|
|
for (; visual_iter.rem; xcb_visualtype_next(&visual_iter)) {
|
|
if (visual_iter.data->visual_id == visual_id) {
|
|
auto visual = p_new(visual_t, 1);
|
|
visual->visual = visual_iter.data;
|
|
visual->depth = depth_iter.data->depth;
|
|
return visual;
|
|
}
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
visual_t *window_get_visual(backend_t *backend, bool prefer_alpha) {
|
|
if (!prefer_alpha) return get_root_visual(backend);
|
|
|
|
auto visual = get_alpha_visual(backend);
|
|
if (!visual) visual = get_root_visual(backend);
|
|
|
|
return visual;
|
|
}
|
|
|
|
void window_change_cursor(
|
|
backend_t *backend,
|
|
xcb_window_t window,
|
|
cursor_t cursor
|
|
) {
|
|
auto conn = backend->conn;
|
|
xcb_params_cw_t params = {.cursor = cursor_get_xcb_cursor(backend, cursor)};
|
|
xcb_aux_change_window_attributes(conn, window, XCB_CW_CURSOR, ¶ms);
|
|
}
|
|
|
|
void window_grab_button(
|
|
backend_t *backend,
|
|
xcb_window_t window,
|
|
button_t button,
|
|
modifier_mask_t modifiers
|
|
) {
|
|
auto xcb_button = button_zdwm_to_xcb(button);
|
|
if (xcb_button == XCB_BUTTON_INDEX_ANY) return;
|
|
|
|
xcb_grab_button(
|
|
backend->conn,
|
|
false, /* 事件仅发送给抓取者,不发送给其他窗口 */
|
|
window,
|
|
XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE,
|
|
XCB_GRAB_MODE_ASYNC,
|
|
XCB_GRAB_MODE_SYNC,
|
|
XCB_WINDOW_NONE,
|
|
XCB_CURSOR_NONE,
|
|
xcb_button,
|
|
modifiers_zdwm_to_xcb(modifiers)
|
|
);
|
|
}
|