386 lines
12 KiB
C
386 lines
12 KiB
C
#include "event.h"
|
||
|
||
#include <glib.h>
|
||
#include <stdint.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 <xkbcommon/xkbcommon.h>
|
||
|
||
#include "action.h"
|
||
#include "atoms-extern.h"
|
||
#include "base.h"
|
||
#include "client.h"
|
||
#include "default_config.h"
|
||
#include "monitor.h"
|
||
#include "tray.h"
|
||
#include "types.h"
|
||
#include "utils.h"
|
||
#include "wm.h"
|
||
#include "xkb.h"
|
||
#include "xwindow.h"
|
||
|
||
static void button_press(xcb_button_press_event_t *ev) {
|
||
click_area_t click_area = click_none;
|
||
user_action_arg_t arg = {0};
|
||
|
||
monitor_t *monitor = nullptr;
|
||
for (monitor_t *m = wm.monitor_list; m; m = m->next) {
|
||
if (ev->event == m->bar_window) {
|
||
monitor = m;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (!monitor) return;
|
||
|
||
wm_set_current_monitor(monitor, false);
|
||
if (ev->event_x >= monitor->tag_extent.start &&
|
||
ev->event_x <= monitor->tag_extent.end) {
|
||
click_area = click_tag;
|
||
|
||
for (tag_t *tag = monitor->tag_list; tag; tag = tag->next) {
|
||
if (ev->event_x >= tag->bar_extent.start &&
|
||
ev->event_x <= tag->bar_extent.end) {
|
||
arg.ui = tag->mask;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (click_area == click_none) {
|
||
for (task_in_tag_t *task = monitor->selected_tag->task_list; task;
|
||
task = task->next) {
|
||
if (ev->event_x >= task->bar_extent.start &&
|
||
ev->event_x <= task->bar_extent.end) {
|
||
click_area = click_client_name;
|
||
arg.ptr = task->client;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (click_area == click_none) return;
|
||
|
||
for (int i = 0; i < countof(button_list); i++) {
|
||
if (click_area == button_list[i].click_area &&
|
||
button_list[i].button == ev->detail &&
|
||
button_list[i].modifiers == ev->state) {
|
||
button_list[i].func(&arg);
|
||
}
|
||
}
|
||
}
|
||
|
||
static void configure_request(xcb_configure_request_event_t *ev) {
|
||
if (tray_handle_configure_request(ev)) return;
|
||
|
||
client_t *client = client_get_by_window(ev->window);
|
||
if (client) {
|
||
if (ev->value_mask & XCB_CONFIG_WINDOW_BORDER_WIDTH) {
|
||
client_change_border_width(client, ev->border_width);
|
||
} else {
|
||
tag_t *tag = client->monitor->selected_tag;
|
||
task_in_tag_t *task = client_get_task_in_tag(client, tag);
|
||
if (!task || !(client->floating || !tag->layout->arrange)) return;
|
||
|
||
if (ev->value_mask & XCB_CONFIG_WINDOW_X) task->geometry.x = ev->x;
|
||
if (ev->value_mask & XCB_CONFIG_WINDOW_Y) task->geometry.y = ev->y;
|
||
if (ev->value_mask & XCB_CONFIG_WINDOW_WIDTH)
|
||
task->geometry.width = ev->width;
|
||
if (ev->value_mask & XCB_CONFIG_WINDOW_HEIGHT)
|
||
task->geometry.height = ev->height;
|
||
client_apply_geometry(client, task->geometry);
|
||
}
|
||
} else {
|
||
uint16_t value_mask = ev->value_mask;
|
||
xcb_configure_window_value_list_t value_list = {
|
||
.x = ev->x,
|
||
.y = ev->y,
|
||
.width = ev->width,
|
||
.height = ev->height,
|
||
.border_width = ev->border_width,
|
||
.sibling = ev->sibling,
|
||
.stack_mode = ev->stack_mode,
|
||
};
|
||
xcb_configure_window_aux(wm.xcb_conn, ev->window, value_mask, &value_list);
|
||
xcb_flush(wm.xcb_conn);
|
||
}
|
||
}
|
||
|
||
static void key_press(xcb_key_press_event_t *ev) {
|
||
bool is_press = XCB_EVENT_RESPONSE_TYPE(ev) == XCB_KEY_PRESS;
|
||
xcb_keysym_t keysym = xcb_key_press_lookup_keysym(wm.key_symbols, ev, 0);
|
||
char key_name[16];
|
||
if (xkb_keysym_get_name(keysym, key_name, sizeof(key_name)) != -1) {
|
||
logger("key %s: %s\n", is_press ? "press" : "release", key_name);
|
||
}
|
||
|
||
if (!is_press) return;
|
||
|
||
for (int i = 0; i < countof(key_list); i++) {
|
||
keyboard_t key = key_list[i];
|
||
if (keysym == key.keysym && ev->state == key.modifiers && key.func) {
|
||
/* 查询光标所在屏幕并将其设置为当前屏幕 */
|
||
point_t point = {.x = ev->root_x, .y = ev->root_y};
|
||
monitor_t *monitor = wm_get_monitor_by_point(point);
|
||
wm_set_current_monitor(monitor, false);
|
||
|
||
key.func(&key.arg);
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
|
||
static void map_request(xcb_map_request_event_t *ev) {
|
||
if (tray_handle_map_request(ev)) return;
|
||
|
||
xcb_get_window_attributes_reply_t *wa_reply =
|
||
xwindow_get_attributes_reply(ev->window);
|
||
|
||
if (!wa_reply) return;
|
||
if (wa_reply->override_redirect) {
|
||
p_delete(&wa_reply);
|
||
return;
|
||
}
|
||
|
||
client_t *c = client_get_by_window(ev->window);
|
||
if (!c) {
|
||
xcb_get_geometry_reply_t *geo_reply =
|
||
xwindow_get_geometry_reply(ev->window);
|
||
if (!geo_reply) {
|
||
p_delete(&wa_reply);
|
||
return;
|
||
}
|
||
|
||
client_manage(ev->window, geo_reply);
|
||
p_delete(&geo_reply);
|
||
}
|
||
|
||
p_delete(&wa_reply);
|
||
}
|
||
|
||
typedef enum _NET_WM_STATE_ACTION : uint32_t {
|
||
_NET_WM_STATE_ADD = 1,
|
||
_NET_WM_STATE_REMOVE = 0,
|
||
_NET_WM_STATE_TOGGLE = 2,
|
||
} _NET_WM_STATE_ACTION;
|
||
|
||
static inline bool get_bool_property(bool init_property,
|
||
_NET_WM_STATE_ACTION action) {
|
||
switch (action) {
|
||
case _NET_WM_STATE_ADD:
|
||
return true;
|
||
case _NET_WM_STATE_REMOVE:
|
||
return false;
|
||
case _NET_WM_STATE_TOGGLE:
|
||
return !init_property;
|
||
default:
|
||
fatal(
|
||
"Invalid _NET_WM_STATE_ACTION: %u\n"
|
||
"Only supported:"
|
||
"_NET_WM_STATE_ADD: %u"
|
||
"_NET_WM_STATE_REMOVE: %u"
|
||
"_NET_WM_STATE_TOGGLE: %u",
|
||
action, _NET_WM_STATE_ADD, _NET_WM_STATE_REMOVE, _NET_WM_STATE_TOGGLE);
|
||
}
|
||
}
|
||
|
||
static void client_message(xcb_client_message_event_t *ev) {
|
||
if (tray_handle_client_message(ev)) return;
|
||
|
||
client_t *c = client_get_by_window(ev->window);
|
||
if (c == nullptr) return;
|
||
|
||
if (ev->type == _NET_ACTIVE_WINDOW) {
|
||
for (tag_t *t = c->monitor->tag_list; t; t = t->next) {
|
||
if ((t->mask & c->tags) == 0) continue;
|
||
|
||
logger("== _NET_ACTIVE_WINDOW: %s[%u]\n", c->class, ev->data.data32[0]);
|
||
|
||
switch (ev->data.data32[0]) {
|
||
case 2: /* 来自 pager */
|
||
wm_set_current_monitor(c->monitor, true);
|
||
monitor_select_tag(c->monitor, t->mask);
|
||
client_focus(c);
|
||
break;
|
||
case 1: /* 来自应用程序 */
|
||
c->urgent = true;
|
||
monitor_draw_bar(c->monitor);
|
||
break;
|
||
case 0: /* 来自老版本标志 */
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
} else if (ev->type == _NET_WM_STATE) {
|
||
/**
|
||
* _NET_WM_STATE 协议数据格式为
|
||
* data.data32 = {
|
||
* action, // 操作类型(添加/移除/切换)
|
||
* property1, // 第一个状态原子(如_NET_WM_STATE_FULLSCREEN)
|
||
* property2, // 第二个状态原子(可选,通常为0)
|
||
* 0, // 预留字段(固定为0)
|
||
* 0 // 预留字段(固定为0)
|
||
* };
|
||
* action 值可能为
|
||
* _NET_WM_STATE_ADD(1):添加全屏状态,窗口进入全屏模式
|
||
* _NET_WM_STATE_REMOVE(0):移除全屏状态,窗口退出全屏模式
|
||
* _NET_WM_STATE_TOGGLE(2):切换全屏状态(若当前全屏则退出,反之进入)
|
||
*/
|
||
_NET_WM_STATE_ACTION action = ev->data.data32[0];
|
||
|
||
if (ev->data.data32[1] == _NET_WM_STATE_FULLSCREEN ||
|
||
ev->data.data32[2] == _NET_WM_STATE_FULLSCREEN) {
|
||
client_set_fullscreen(c, get_bool_property(c->fullscreen, action));
|
||
} else if (ev->data.data32[1] == _NET_WM_STATE_MAXIMIZED_HORZ ||
|
||
ev->data.data32[1] == _NET_WM_STATE_MAXIMIZED_VERT ||
|
||
ev->data.data32[2] == _NET_WM_STATE_MAXIMIZED_HORZ ||
|
||
ev->data.data32[2] == _NET_WM_STATE_MAXIMIZED_VERT) {
|
||
client_set_maximize(c, get_bool_property(c->maximize, action));
|
||
}
|
||
} else if (ev->type == WM_CHANGE_STATE) {
|
||
switch (ev->data.data32[0]) {
|
||
case XCB_ICCCM_WM_STATE_ICONIC:
|
||
client_set_minimize(c, true);
|
||
break;
|
||
case XCB_ICCCM_WM_STATE_NORMAL:
|
||
client_set_minimize(c, false);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
static void property_notify(xcb_property_notify_event_t *ev) {
|
||
if (tray_handle_property_notify(ev)) return;
|
||
|
||
client_t *client = client_get_by_window(ev->window);
|
||
if (client == nullptr) return;
|
||
|
||
if (ev->atom == WM_NAME) {
|
||
xwindow_get_text_property(ev->window, ev->atom, &client->name);
|
||
monitor_draw_bar(client->monitor);
|
||
xcb_flush(wm.xcb_conn);
|
||
} else if (ev->atom == _NET_WM_NAME) {
|
||
xwindow_get_text_property(ev->window, ev->atom, &client->net_name);
|
||
monitor_draw_bar(client->monitor);
|
||
xcb_flush(wm.xcb_conn);
|
||
} else if (ev->atom == XCB_ATOM_WM_HINTS) {
|
||
client_update_wm_hints(client);
|
||
monitor_draw_bar(client->monitor);
|
||
xcb_flush(wm.xcb_conn);
|
||
}
|
||
}
|
||
|
||
static void destroy_notify(xcb_destroy_notify_event_t *ev) {
|
||
if (tray_handle_destroy_notify(ev)) return;
|
||
|
||
client_t *client = client_get_by_window(ev->window);
|
||
if (client) client_unmanage(client, true);
|
||
}
|
||
|
||
static void unmap_notify(xcb_unmap_notify_event_t *ev) {
|
||
if (tray_handle_unmap_notify(ev)) return;
|
||
|
||
client_t *client = client_get_by_window(ev->window);
|
||
if (!client || client->minimize) return;
|
||
|
||
if (XCB_EVENT_SENT(ev)) {
|
||
xwindow_set_state(ev->window, XCB_ICCCM_WM_STATE_WITHDRAWN);
|
||
} else {
|
||
client_unmanage(client, false);
|
||
}
|
||
}
|
||
|
||
static void map_notify(xcb_map_notify_event_t *ev) {
|
||
/* 锁屏重新进入桌面后绘制 bar 内容以免 bar 不显示内容 */
|
||
for (monitor_t *m = wm.monitor_list; m; m = m->next) {
|
||
if (ev->window == m->bar_window) {
|
||
monitor_draw_bar(m);
|
||
xcb_flush(wm.xcb_conn);
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
|
||
static void expose(xcb_expose_event_t *ev) {
|
||
if (ev->count > 0) return;
|
||
|
||
monitor_t *monitor = wm_get_monitor_by_window(ev->window);
|
||
monitor_draw_bar(monitor);
|
||
xcb_flush(wm.xcb_conn);
|
||
}
|
||
|
||
static void enter_notify(xcb_enter_notify_event_t *ev) {
|
||
client_t *client = client_get_by_window(ev->event);
|
||
if (!client || wm.client_focused == client) return;
|
||
|
||
client_focus(client);
|
||
wm_set_current_monitor(client->monitor, false);
|
||
}
|
||
|
||
static void selection_clear(xcb_selection_clear_event_t *ev) {
|
||
tray_handle_selection_clear(ev);
|
||
}
|
||
|
||
static void handle_xcb_event(xcb_generic_event_t *event) {
|
||
uint8_t event_type = XCB_EVENT_RESPONSE_TYPE(event);
|
||
const char *label = xcb_event_get_label(event_type);
|
||
logger("event type: %u[%s], xkb_event: %u\n", event_type, label,
|
||
wm.event_base_xkb);
|
||
|
||
switch (event_type) {
|
||
#define EVENT(type, callback) \
|
||
case type: \
|
||
callback((void *)event); \
|
||
return
|
||
|
||
EVENT(XCB_BUTTON_PRESS, button_press);
|
||
EVENT(XCB_KEY_PRESS, key_press);
|
||
EVENT(XCB_KEY_RELEASE, key_press);
|
||
EVENT(XCB_CONFIGURE_REQUEST, configure_request);
|
||
EVENT(XCB_MAP_REQUEST, map_request);
|
||
EVENT(XCB_MAP_NOTIFY, map_notify);
|
||
EVENT(XCB_EXPOSE, expose);
|
||
EVENT(XCB_CLIENT_MESSAGE, client_message);
|
||
EVENT(XCB_PROPERTY_NOTIFY, property_notify);
|
||
EVENT(XCB_UNMAP_NOTIFY, unmap_notify);
|
||
EVENT(XCB_DESTROY_NOTIFY, destroy_notify);
|
||
EVENT(XCB_ENTER_NOTIFY, enter_notify);
|
||
EVENT(XCB_SELECTION_CLEAR, selection_clear);
|
||
|
||
#undef EVENT
|
||
}
|
||
|
||
/* 处理 XKB 事件 */
|
||
if (wm.event_base_xkb != 0 && event_type == wm.event_base_xkb) {
|
||
xkb_handle_event(event);
|
||
}
|
||
}
|
||
|
||
static gboolean xcb_event_loop(GIOChannel *channel, GIOCondition condition,
|
||
gpointer user_data) {
|
||
if (condition & (G_IO_HUP | G_IO_ERR)) {
|
||
wm_quit();
|
||
return false;
|
||
}
|
||
|
||
xcb_generic_event_t *event = nullptr;
|
||
while ((event = xcb_poll_for_event(wm.xcb_conn))) {
|
||
handle_xcb_event(event);
|
||
p_delete(&event);
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
void setup_event_loop(void) {
|
||
int fd = xcb_get_file_descriptor(wm.xcb_conn);
|
||
GIOChannel *channel = g_io_channel_unix_new(fd);
|
||
g_io_channel_set_encoding(channel, nullptr, nullptr);
|
||
GIOCondition cond = G_IO_IN | G_IO_HUP | G_IO_ERR;
|
||
g_io_add_watch(channel, cond, xcb_event_loop, nullptr);
|
||
g_io_channel_unref(channel);
|
||
}
|