From 489c82654904bb55d90f3ef745f59883aed25083 Mon Sep 17 00:00:00 2001 From: Zedhugh Chen Date: Tue, 10 Mar 2026 07:42:58 +0800 Subject: [PATCH] =?UTF-8?q?feat(tray):=20=E5=A2=9E=E5=8A=A0=E7=8B=AC?= =?UTF-8?q?=E7=AB=8B=E7=B3=BB=E7=BB=9F=E6=89=98=E7=9B=98=E6=A8=A1=E5=9D=97?= =?UTF-8?q?=E5=B9=B6=E8=A1=A5=E9=BD=90=E5=85=BC=E5=AE=B9=E6=80=A7=E4=B8=8E?= =?UTF-8?q?=E6=89=80=E6=9C=89=E6=9D=83=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CMakeLists.txt | 1 + src/atoms-list.txt | 6 + src/event.c | 16 ++ src/monitor.c | 26 +- src/tray.c | 602 +++++++++++++++++++++++++++++++++++++++++++++ src/tray.h | 21 ++ src/wm.c | 3 + 7 files changed, 667 insertions(+), 8 deletions(-) create mode 100644 src/tray.c create mode 100644 src/tray.h diff --git a/CMakeLists.txt b/CMakeLists.txt index ed5c4e4..6197de8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -48,6 +48,7 @@ add_executable(${APP_NAME} ${SOURCE_DIR}/config.c ${SOURCE_DIR}/renderer.c ${SOURCE_DIR}/image.c + ${SOURCE_DIR}/tray.c ) find_package(PkgConfig REQUIRED) diff --git a/src/atoms-list.txt b/src/atoms-list.txt index 17d444a..757f5a7 100644 --- a/src/atoms-list.txt +++ b/src/atoms-list.txt @@ -12,9 +12,15 @@ WM_WINDOW_ROLE UTF8_STRING COMPOUND_TEXT +MANAGER +_XEMBED +_XEMBED_INFO _NET_ACTIVE_WINDOW _NET_CLIENT_LIST +_NET_SYSTEM_TRAY_COLORS +_NET_SYSTEM_TRAY_OPCODE +_NET_SYSTEM_TRAY_ORIENTATION _NET_WM_DESKTOP _NET_WM_NAME _NET_WM_STATE diff --git a/src/event.c b/src/event.c index 2692452..d9fd819 100644 --- a/src/event.c +++ b/src/event.c @@ -15,6 +15,7 @@ #include "client.h" #include "default_config.h" #include "monitor.h" +#include "tray.h" #include "types.h" #include "utils.h" #include "wm.h" @@ -73,6 +74,8 @@ static void button_press(xcb_button_press_event_t *ev) { } 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) { @@ -131,6 +134,8 @@ static void key_press(xcb_key_press_event_t *ev) { } 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); @@ -183,9 +188,13 @@ static inline bool get_bool_property(bool init_property, } 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; + log_atom(ev->type); + 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; @@ -246,6 +255,8 @@ static void client_message(xcb_client_message_event_t *ev) { } 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; @@ -265,11 +276,15 @@ static void property_notify(xcb_property_notify_event_t *ev) { } 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; @@ -337,6 +352,7 @@ static void handle_xcb_event(xcb_generic_event_t *event) { EVENT(XCB_DESTROY_NOTIFY, destroy_notify); EVENT(XCB_ENTER_NOTIFY, enter_notify); EVENT(XCB_FOCUS_IN, focus_in); + EVENT(XCB_SELECTION_CLEAR, selection_clear); #undef EVENT } diff --git a/src/monitor.c b/src/monitor.c index ad93c93..2fc2c30 100644 --- a/src/monitor.c +++ b/src/monitor.c @@ -17,6 +17,7 @@ #include "renderer.h" #include "status.h" #include "text.h" +#include "tray.h" #include "types.h" #include "utils.h" #include "wm.h" @@ -239,13 +240,14 @@ static void monitor_draw_layout_symbol(monitor_t *monitor) { } } -static void monitor_draw_status(monitor_t *monitor, status_t *status) { +static void monitor_draw_status(monitor_t *monitor, status_t *status, + int16_t right_edge, int16_t left_edge) { static renderer_status_t *rendered_status = nullptr; static size_t rendered_status_capacity = 0; - monitor->status_extent.end = monitor->geometry.width; - monitor->status_extent.start = monitor->geometry.width; - int32_t end = monitor->layout_symbol_extent.end; + monitor->status_extent.end = right_edge; + monitor->status_extent.start = right_edge; + int32_t end = left_edge; if (status == nullptr || !wm.config || !wm.config->status) return; @@ -343,7 +345,7 @@ static void monitor_draw_status(monitor_t *monitor, status_t *status) { } } -static void monitor_draw_tasks(monitor_t *monitor) { +static void monitor_draw_tasks(monitor_t *monitor, int16_t right_edge) { task_in_tag_t *task_list = monitor->selected_tag->task_list; if (!task_list) return; @@ -352,7 +354,10 @@ static void monitor_draw_tasks(monitor_t *monitor) { if (task_count == 0) return; int16_t x = monitor->layout_symbol_extent.end; - uint16_t task_width = (monitor->status_extent.start - x) / task_count; + int32_t available_width = (int32_t)right_edge - x; + if (available_width <= 0) return; + + uint16_t task_width = (uint16_t)(available_width / task_count); if (task_width < wm.font_size) return; for (task_in_tag_t *task = task_list; task; task = task->next) { @@ -376,6 +381,9 @@ static void monitor_draw_tasks(monitor_t *monitor) { void monitor_draw_bar(monitor_t *monitor) { cairo_t *cr = monitor->bar_cr; uint16_t height = wm.bar_height; + uint16_t tray_width = tray_get_width(monitor); + int16_t status_right = (int16_t)monitor->geometry.width - tray_width; + if (status_right < 0) status_right = 0; area_t bar_area = { .x = 0, .y = 0, @@ -386,8 +394,10 @@ void monitor_draw_bar(monitor_t *monitor) { monitor_draw_tags(monitor); monitor_draw_layout_symbol(monitor); - monitor_draw_status(monitor, wm.status); - monitor_draw_tasks(monitor); + monitor_draw_status(monitor, wm.status, status_right, + monitor->layout_symbol_extent.end); + monitor_draw_tasks(monitor, monitor->status_extent.start); + tray_place(monitor, monitor->geometry.width); } void monitor_arrange(monitor_t *monitor) { diff --git a/src/tray.c b/src/tray.c new file mode 100644 index 0000000..56aca99 --- /dev/null +++ b/src/tray.c @@ -0,0 +1,602 @@ +#include "tray.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "atoms-extern.h" +#include "base.h" +#include "monitor.h" +#include "utils.h" +#include "wm.h" +#include "xwindow.h" + +typedef enum tray_opcode_t { + TRAY_REQUEST_DOCK = 0, + TRAY_BEGIN_MESSAGE = 1, + TRAY_CANCEL_MESSAGE = 2, +} tray_opcode_t; + +typedef enum tray_orientation_t { + TRAY_ORIENTATION_HORIZONTAL = 0, +} tray_orientation_t; + +typedef enum xembed_message_t { + XEMBED_EMBEDDED_NOTIFY = 0, +} xembed_message_t; + +enum { + XEMBED_VERSION = 0, +}; + +typedef struct tray_icon_t { + xcb_window_t window; + uint16_t natural_width; + uint16_t natural_height; + uint8_t ignore_unmaps; + bool mapped; +} tray_icon_t; + +typedef struct tray_t { + bool initialized; + xcb_atom_t selection_atom; + xcb_window_t window; + monitor_t *host_monitor; + + tray_icon_t *icons; + size_t icon_count; + size_t icon_capacity; + + uint16_t spacing; + uint16_t side_padding; +} tray_t; + +static tray_t tray = { + .selection_atom = XCB_ATOM_NONE, + .window = XCB_WINDOW_NONE, +}; + +static xcb_atom_t tray_intern_atom(const char *name) { + if (!name || !name[0]) return XCB_ATOM_NONE; + + xcb_intern_atom_cookie_t cookie = + xcb_intern_atom(wm.xcb_conn, false, (uint16_t)strlen(name), name); + xcb_intern_atom_reply_t *reply = + xcb_intern_atom_reply(wm.xcb_conn, cookie, nullptr); + if (!reply) return XCB_ATOM_NONE; + + xcb_atom_t atom = reply->atom; + p_delete(&reply); + return atom; +} + +static tray_icon_t *tray_get_icon(xcb_window_t window) { + for (size_t i = 0; i < tray.icon_count; i++) { + tray_icon_t *icon = &tray.icons[i]; + if (icon->window == window) return icon; + } + + return nullptr; +} + +static size_t tray_find_icon_index(xcb_window_t window) { + for (size_t i = 0; i < tray.icon_count; i++) { + if (tray.icons[i].window == window) return i; + } + + return SIZE_MAX; +} + +static uint16_t tray_target_icon_height(void) { + return MAX((uint16_t)1, wm.bar_height); +} + +static uint32_t tray_color_component_u16(double channel) { + if (channel < 0.0) channel = 0.0; + if (channel > 1.0) channel = 1.0; + + uint32_t value = (uint32_t)(channel * 65535.0 + 0.5); + return MIN(value, 65535u); +} + +static void tray_get_icon_layout(const tray_icon_t *icon, uint16_t *width, + uint16_t *height) { + uint32_t target_height = tray_target_icon_height(); + uint32_t natural_width = icon->natural_width; + uint32_t natural_height = icon->natural_height; + + if (natural_width == 0) natural_width = target_height; + if (natural_height == 0) natural_height = target_height; + if (natural_height == 0) natural_height = 1; + + uint32_t scaled_width = + (natural_width * target_height + natural_height / 2) / natural_height; + if (scaled_width == 0) scaled_width = 1; + + if (width) *width = (uint16_t)MIN((uint32_t)UINT16_MAX, scaled_width); + if (height) *height = (uint16_t)MIN((uint32_t)UINT16_MAX, target_height); +} + +static uint16_t tray_compute_width(void) { + if (!tray.initialized || tray.host_monitor == nullptr || tray.icon_count == 0) { + return 0; + } + + uint32_t width = (uint32_t)tray.side_padding * 2; + for (size_t i = 0; i < tray.icon_count; i++) { + uint16_t icon_width = 0; + tray_get_icon_layout(&tray.icons[i], &icon_width, nullptr); + width += icon_width; + } + if (tray.icon_count > 1) { + width += (uint32_t)(tray.icon_count - 1) * tray.spacing; + } + + return (uint16_t)MIN((uint32_t)UINT16_MAX, width); +} + +static void tray_send_xembed_message(xcb_window_t window, uint32_t message, + uint32_t detail, uint32_t data1, + uint32_t data2) { + xcb_client_message_event_t ev; + p_clear(&ev, 1); + ev.response_type = XCB_CLIENT_MESSAGE; + ev.format = 32; + ev.window = window; + ev.type = _XEMBED; + ev.data.data32[0] = XCB_CURRENT_TIME; + ev.data.data32[1] = message; + ev.data.data32[2] = detail; + ev.data.data32[3] = data1; + ev.data.data32[4] = data2; + + xcb_send_event(wm.xcb_conn, false, window, XCB_EVENT_MASK_NO_EVENT, + (char *)&ev); +} + +static void tray_select_icon_events(xcb_window_t window) { + xcb_params_cw_t params = { + .back_pixel = wm.color_set.bar_bg.argb, + .border_pixel = 0, + .event_mask = XCB_EVENT_MASK_PROPERTY_CHANGE | + XCB_EVENT_MASK_STRUCTURE_NOTIFY, + }; + uint32_t mask = + XCB_CW_BACK_PIXEL | XCB_CW_BORDER_PIXEL | XCB_CW_EVENT_MASK; + xcb_aux_change_window_attributes(wm.xcb_conn, window, mask, ¶ms); + xcb_clear_area(wm.xcb_conn, false, window, 0, 0, 0, 0); +} + +static void tray_release_icon_window(tray_icon_t *icon) { + if (!icon || icon->window == XCB_WINDOW_NONE) return; + + xcb_change_save_set(wm.xcb_conn, XCB_SET_MODE_DELETE, icon->window); + xcb_params_cw_t params = { + .event_mask = XCB_EVENT_MASK_NO_EVENT, + .border_pixel = 0, + }; + xcb_aux_change_window_attributes( + wm.xcb_conn, icon->window, XCB_CW_EVENT_MASK | XCB_CW_BORDER_PIXEL, + ¶ms); + xcb_reparent_window(wm.xcb_conn, icon->window, wm.screen->root, 0, 0); +} + +static void tray_layout_icons(int16_t right_edge) { + if (!tray.initialized || tray.window == XCB_WINDOW_NONE || + tray.host_monitor == nullptr) { + return; + } + + uint16_t tray_width = tray_compute_width(); + if (tray_width == 0) { + xcb_unmap_window(wm.xcb_conn, tray.window); + return; + } + + int32_t tray_x = (int32_t)right_edge - tray_width; + if (tray_x < 0) tray_x = 0; + + xcb_configure_window_value_list_t tray_values = { + .x = (int16_t)tray_x, + .y = 0, + .width = tray_width, + .height = wm.bar_height, + }; + uint16_t tray_mask = XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y | + XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT; + xcb_configure_window_aux(wm.xcb_conn, tray.window, tray_mask, &tray_values); + xcb_map_window(wm.xcb_conn, tray.window); + + int32_t x = tray.side_padding; + for (size_t i = 0; i < tray.icon_count; i++) { + tray_icon_t *icon = &tray.icons[i]; + uint16_t icon_width = 0; + uint16_t icon_height = 0; + tray_get_icon_layout(icon, &icon_width, &icon_height); + + int32_t y = ((int32_t)wm.bar_height - icon_height) / 2; + if (y < 0) y = 0; + + xcb_configure_window_value_list_t icon_values = { + .x = (int16_t)x, + .y = (int16_t)y, + .width = icon_width, + .height = icon_height, + .border_width = 0, + }; + uint16_t icon_mask = XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y | + XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT | + XCB_CONFIG_WINDOW_BORDER_WIDTH; + xcb_configure_window_aux(wm.xcb_conn, icon->window, icon_mask, + &icon_values); + xcb_map_window(wm.xcb_conn, icon->window); + icon->mapped = true; + + x += icon_width + tray.spacing; + } +} + +static void tray_request_redraw(void) { + if (!tray.initialized || tray.host_monitor == nullptr) return; + + monitor_draw_bar(tray.host_monitor); + xcb_flush(wm.xcb_conn); +} + +static void tray_remove_icon_by_index(size_t index, bool release_window) { + if (index >= tray.icon_count) return; + + tray_icon_t icon = tray.icons[index]; + if (release_window) tray_release_icon_window(&icon); + + if (index + 1 < tray.icon_count) { + memmove(&tray.icons[index], &tray.icons[index + 1], + sizeof(*tray.icons) * (tray.icon_count - index - 1)); + } + tray.icon_count--; + + tray_request_redraw(); +} + +static void tray_add_icon(xcb_window_t window) { + if (!tray.initialized || window == XCB_WINDOW_NONE || + tray_get_icon(window) != nullptr) { + return; + } + + if (tray.icon_count == tray.icon_capacity) { + tray.icon_capacity = (size_t)p_alloc_nr(tray.icon_capacity); + p_realloc(&tray.icons, tray.icon_capacity); + } + + xcb_get_geometry_reply_t *geometry = xwindow_get_geometry_reply(window); + if (!geometry) return; + + tray_icon_t *icon = &tray.icons[tray.icon_count++]; + *icon = (tray_icon_t){ + .window = window, + .natural_width = geometry->width, + .natural_height = geometry->height, + .ignore_unmaps = 1, + .mapped = true, + }; + p_delete(&geometry); + + xcb_change_save_set(wm.xcb_conn, XCB_SET_MODE_INSERT, window); + xcb_reparent_window(wm.xcb_conn, window, tray.window, 0, 0); + tray_select_icon_events(window); + tray_send_xembed_message(window, XEMBED_EMBEDDED_NOTIFY, 0, tray.window, + XEMBED_VERSION); + xcb_map_window(wm.xcb_conn, window); + + tray_request_redraw(); +} + +static bool tray_create_window(void) { + static xcb_colormap_t colormap = XCB_NONE; + if (tray.host_monitor == nullptr || tray.host_monitor->bar_window == XCB_NONE) { + return false; + } + + visual_t *visual = xwindow_get_xcb_visual(false); + if (visual == nullptr) return false; + + xcb_visualid_t visual_id = visual->visual->visual_id; + if (colormap == XCB_NONE) { + colormap = xcb_generate_id(wm.xcb_conn); + xcb_create_colormap(wm.xcb_conn, XCB_COLORMAP_ALLOC_NONE, colormap, + wm.screen->root, visual_id); + } + + xcb_window_t window = xcb_generate_id(wm.xcb_conn); + uint32_t value_mask = XCB_CW_BACK_PIXEL | XCB_CW_BORDER_PIXEL | + XCB_CW_EVENT_MASK | XCB_CW_COLORMAP; + const xcb_create_window_value_list_t value_list = { + .background_pixel = wm.color_set.bar_bg.argb, + .border_pixel = 0, + .event_mask = XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT | + XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY, + .colormap = colormap, + }; + + xcb_void_cookie_t cookie = xcb_create_window_aux_checked( + wm.xcb_conn, visual->depth, window, tray.host_monitor->bar_window, 0, 0, 1, + wm.bar_height, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, visual_id, value_mask, + &value_list); + p_delete(&visual); + if (xcb_request_check(wm.xcb_conn, cookie)) { + logger("cannot create system tray window\n"); + return false; + } + + tray.window = window; + xwindow_set_class_instance(window); +#define NAME APP_NAME "_tray" + xwindow_set_name_static(window, NAME); + xcb_change_property(wm.xcb_conn, XCB_PROP_MODE_REPLACE, window, _NET_WM_NAME, + UTF8_STRING, 8, sizeof(NAME) - 1, NAME); +#undef NAME + + return true; +} + +static bool tray_take_selection_owner(void) { + xcb_get_selection_owner_cookie_t owner_cookie = + xcb_get_selection_owner(wm.xcb_conn, tray.selection_atom); + xcb_get_selection_owner_reply_t *owner_reply = + xcb_get_selection_owner_reply(wm.xcb_conn, owner_cookie, nullptr); + if (owner_reply && owner_reply->owner != XCB_WINDOW_NONE) { + logger("system tray selection is already owned by window %u\n", + owner_reply->owner); + p_delete(&owner_reply); + return false; + } + p_delete(&owner_reply); + + xcb_set_selection_owner(wm.xcb_conn, tray.window, tray.selection_atom, + XCB_CURRENT_TIME); + + owner_cookie = xcb_get_selection_owner(wm.xcb_conn, tray.selection_atom); + owner_reply = xcb_get_selection_owner_reply(wm.xcb_conn, owner_cookie, nullptr); + bool success = owner_reply && owner_reply->owner == tray.window; + p_delete(&owner_reply); + + if (!success) logger("cannot acquire system tray selection\n"); + return success; +} + +static void tray_set_properties(void) { + uint32_t orientation = TRAY_ORIENTATION_HORIZONTAL; + xcb_change_property(wm.xcb_conn, XCB_PROP_MODE_REPLACE, tray.window, + _NET_SYSTEM_TRAY_ORIENTATION, XCB_ATOM_CARDINAL, 32, 1, + &orientation); + + const color_t *fg = &wm.color_set.tag_color; + uint32_t colors[12] = { + tray_color_component_u16(fg->red), + tray_color_component_u16(fg->green), + tray_color_component_u16(fg->blue), + tray_color_component_u16(fg->red), + tray_color_component_u16(fg->green), + tray_color_component_u16(fg->blue), + tray_color_component_u16(fg->red), + tray_color_component_u16(fg->green), + tray_color_component_u16(fg->blue), + tray_color_component_u16(fg->red), + tray_color_component_u16(fg->green), + tray_color_component_u16(fg->blue), + }; + xcb_change_property(wm.xcb_conn, XCB_PROP_MODE_REPLACE, tray.window, + _NET_SYSTEM_TRAY_COLORS, XCB_ATOM_CARDINAL, 32, + countof(colors), colors); +} + +static void tray_announce_manager(void) { + xcb_client_message_event_t ev; + p_clear(&ev, 1); + ev.response_type = XCB_CLIENT_MESSAGE; + ev.window = wm.screen->root; + ev.format = 32; + ev.type = MANAGER; + ev.data.data32[0] = XCB_CURRENT_TIME; + ev.data.data32[1] = tray.selection_atom; + ev.data.data32[2] = tray.window; + + xcb_send_event(wm.xcb_conn, false, wm.screen->root, + XCB_EVENT_MASK_STRUCTURE_NOTIFY, (char *)&ev); +} + +void tray_init(void) { + if (tray.initialized) return; + + tray.host_monitor = wm.monitor_list; + if (tray.host_monitor == nullptr) return; + + tray.side_padding = 0; + tray.spacing = 0; + + char selection_name[64]; + snprintf(selection_name, sizeof(selection_name), "_NET_SYSTEM_TRAY_S%d", + wm.default_screen); + tray.selection_atom = tray_intern_atom(selection_name); + if (tray.selection_atom == XCB_ATOM_NONE) { + logger("cannot intern tray selection atom %s\n", selection_name); + return; + } + + if (!tray_create_window()) return; + if (!tray_take_selection_owner()) { + tray_cleanup(); + return; + } + + tray.initialized = true; + tray_set_properties(); + tray_announce_manager(); + tray_layout_icons((int16_t)tray.host_monitor->geometry.width); + xcb_flush(wm.xcb_conn); +} + +void tray_cleanup(void) { + for (size_t i = 0; i < tray.icon_count; i++) { + tray_release_icon_window(&tray.icons[i]); + } + p_delete(&tray.icons); + tray.icon_count = 0; + tray.icon_capacity = 0; + + if (tray.selection_atom != XCB_ATOM_NONE) { + xcb_set_selection_owner(wm.xcb_conn, XCB_WINDOW_NONE, tray.selection_atom, + XCB_CURRENT_TIME); + } + + if (tray.window != XCB_WINDOW_NONE) { + xcb_destroy_window(wm.xcb_conn, tray.window); + } + + tray = (tray_t){ + .selection_atom = XCB_ATOM_NONE, + .window = XCB_WINDOW_NONE, + }; +} + +bool tray_handle_client_message(xcb_client_message_event_t *ev) { + if (!tray.initialized) return false; + + if (ev->type != _NET_SYSTEM_TRAY_OPCODE) return false; + if (ev->window != tray.window && ev->window != wm.screen->root) return false; + + switch (ev->data.data32[1]) { + case TRAY_REQUEST_DOCK: + tray_add_icon((xcb_window_t)ev->data.data32[2]); + break; + case TRAY_BEGIN_MESSAGE: + case TRAY_CANCEL_MESSAGE: + default: + break; + } + + xcb_flush(wm.xcb_conn); + return true; +} + +bool tray_handle_configure_request(xcb_configure_request_event_t *ev) { + if (!tray.initialized) return false; + + if (ev->window == tray.window) { + tray_layout_icons((int16_t)tray.host_monitor->geometry.width); + xcb_flush(wm.xcb_conn); + return true; + } + + tray_icon_t *icon = tray_get_icon(ev->window); + if (!icon) return false; + + if ((ev->value_mask & XCB_CONFIG_WINDOW_WIDTH) && ev->width > 0) { + icon->natural_width = ev->width; + } + if ((ev->value_mask & XCB_CONFIG_WINDOW_HEIGHT) && ev->height > 0) { + icon->natural_height = ev->height; + } + + tray_request_redraw(); + return true; +} + +bool tray_handle_map_request(xcb_map_request_event_t *ev) { + if (!tray.initialized) return false; + + if (ev->window == tray.window) { + tray_layout_icons((int16_t)tray.host_monitor->geometry.width); + xcb_flush(wm.xcb_conn); + return true; + } + + tray_icon_t *icon = tray_get_icon(ev->window); + if (!icon && ev->parent != tray.window) return false; + + if (icon) icon->mapped = true; + xcb_map_window(wm.xcb_conn, ev->window); + tray_layout_icons((int16_t)tray.host_monitor->geometry.width); + xcb_flush(wm.xcb_conn); + return true; +} + +bool tray_handle_destroy_notify(xcb_destroy_notify_event_t *ev) { + if (!tray.initialized) return false; + + if (ev->window == tray.window) { + tray.window = XCB_WINDOW_NONE; + tray.initialized = false; + return true; + } + + size_t index = tray_find_icon_index(ev->window); + if (index == SIZE_MAX) return false; + + tray_remove_icon_by_index(index, false); + return true; +} + +bool tray_handle_unmap_notify(xcb_unmap_notify_event_t *ev) { + if (!tray.initialized) return false; + + tray_icon_t *icon = tray_get_icon(ev->window); + if (!icon) return ev->window == tray.window; + + if (icon->ignore_unmaps > 0) { + icon->ignore_unmaps--; + return true; + } + + size_t index = tray_find_icon_index(ev->window); + if (index != SIZE_MAX) tray_remove_icon_by_index(index, true); + + return true; +} + +bool tray_handle_property_notify(xcb_property_notify_event_t *ev) { + if (!tray.initialized) return false; + return tray_is_window(ev->window); +} + +bool tray_handle_selection_clear(xcb_selection_clear_event_t *ev) { + if (!tray.initialized) return false; + if (ev->owner != tray.window) return false; + if (ev->selection != tray.selection_atom) return false; + + monitor_t *host_monitor = tray.host_monitor; + logger("system tray selection lost\n"); + tray_cleanup(); + + if (host_monitor != nullptr) monitor_draw_bar(host_monitor); + xcb_flush(wm.xcb_conn); + return true; +} + +uint16_t tray_get_width(monitor_t *monitor) { + if (!tray.initialized || monitor == nullptr || monitor != tray.host_monitor) { + return 0; + } + + return tray_compute_width(); +} + +void tray_place(monitor_t *monitor, int16_t right_edge) { + if (!tray.initialized || monitor == nullptr || monitor != tray.host_monitor) { + return; + } + + tray_layout_icons(right_edge); +} + +bool tray_is_window(xcb_window_t window) { + if (window == XCB_WINDOW_NONE) return false; + if (tray.window == window) return true; + return tray_get_icon(window) != nullptr; +} diff --git a/src/tray.h b/src/tray.h new file mode 100644 index 0000000..546193f --- /dev/null +++ b/src/tray.h @@ -0,0 +1,21 @@ +#pragma once + +#include +#include + +#include "types.h" + +void tray_init(void); +void tray_cleanup(void); + +bool tray_handle_client_message(xcb_client_message_event_t *ev); +bool tray_handle_configure_request(xcb_configure_request_event_t *ev); +bool tray_handle_map_request(xcb_map_request_event_t *ev); +bool tray_handle_destroy_notify(xcb_destroy_notify_event_t *ev); +bool tray_handle_unmap_notify(xcb_unmap_notify_event_t *ev); +bool tray_handle_property_notify(xcb_property_notify_event_t *ev); +bool tray_handle_selection_clear(xcb_selection_clear_event_t *ev); + +uint16_t tray_get_width(monitor_t *monitor); +void tray_place(monitor_t *monitor, int16_t right_edge); +bool tray_is_window(xcb_window_t window); diff --git a/src/wm.c b/src/wm.c index d2a2ebf..89f0a64 100644 --- a/src/wm.c +++ b/src/wm.c @@ -33,6 +33,7 @@ #include "monitor.h" #include "status.h" #include "text.h" +#include "tray.h" #include "types.h" #include "utils.h" #include "wallpaper.h" @@ -387,6 +388,7 @@ void wm_clean(void) { clean_status(); text_clean_pango_layout(); image_cache_clean(); + tray_cleanup(); { client_t *c = wm.client_stack_list; @@ -457,6 +459,7 @@ static void wm_setup(void) { wm_setup_keybindings(); atoms_init(wm.xcb_conn); + tray_init(); { wm.wm_check_window = xcb_generate_id(wm.xcb_conn);