#include "event.h" #include #include #include #include #include #include #include #include #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) { if (wm_should_ignore_enter_notify(ev)) return; 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); }