diff --git a/src/backend/x11/backend.c b/src/backend/x11/backend.c index d1629e5..a56c3e5 100644 --- a/src/backend/x11/backend.c +++ b/src/backend/x11/backend.c @@ -19,6 +19,7 @@ #include "backend/output_utils.h" #include "backend/x11/cursor.h" +#include "backend/x11/tray.h" #include "backend/x11/window.h" #include "base/app.h" #include "base/array.h" @@ -845,6 +846,8 @@ backend_bar_window_t backend_create_bar_window( fatal("cannot create cairo context: %s", cairo_status_to_string(statue)); } + if (enable_tray) tray_init(backend, window_id, width, height); + return (backend_bar_window_t){ .window_id = window_id, .cr = cr, diff --git a/src/backend/x11/internal.h b/src/backend/x11/internal.h index db04315..ae5cad1 100644 --- a/src/backend/x11/internal.h +++ b/src/backend/x11/internal.h @@ -9,7 +9,6 @@ #include "common/window.h" #include "interface/event.h" -#include "interface/tray.h" #define EWMH_ATOMS(X) \ X(_NET_WM_NAME) \ @@ -87,17 +86,7 @@ typedef enum cursor_t { ZDWM_CURSOR_COUNT, } cursor_t; -typedef struct tray_host_t { - bool enabled; - xcb_window_t host_window; - xcb_window_t container; - int32_t icon_size; - size_t icon_count; - tray_icons_change_cb_t icon_change_cb; - void *cb_user_data; -} tray_host_t; - -extern const tray_api_t tray_api; +typedef struct tray_host_t tray_host_t; typedef struct backend_t { xcb_key_symbols_t *key_symbols; @@ -123,7 +112,7 @@ typedef struct backend_t { window_list_t map; window_list_t kill; - tray_host_t tray; + tray_host_t *tray; } backend_t; bool populate_window_event( diff --git a/src/backend/x11/tray.c b/src/backend/x11/tray.c index c1633e0..1748675 100644 --- a/src/backend/x11/tray.c +++ b/src/backend/x11/tray.c @@ -1,41 +1,66 @@ -#include "interface/tray.h" +#include "backend/x11/tray.h" #include #include +#include +#include +#include #include +#include "backend/x11/window.h" +#include "base/log.h" +#include "base/macros.h" +#include "base/memory.h" +#include "interface/tray.h" #include "interface/types.h" #include "internal.h" -static window_id_t tray_host_window(void *handle) { - backend_t *backend = handle; - if (backend == nullptr || !backend->tray.enabled) { - return ZDWM_WINDOW_ID_INVALID; - } +typedef struct tray_host_t { + bool enabled; + bool initialized; + xcb_atom_t selection_atom; + xcb_window_t host_window; + xcb_window_t container; + int32_t icon_size; + size_t icon_count; + tray_icons_change_cb_t icon_change_cb; + void *cb_user_data; +} tray_host_t; - return backend->tray.host_window; +static tray_host_t *get_tray_host(void *handle) { + backend_t *backend = handle; + if (!backend) return nullptr; + + auto tray = backend->tray; + if (!tray || !tray->enabled) return nullptr; + + return tray; +} + +static window_id_t tray_host_window(void *handle) { + auto tray = get_tray_host(handle); + if (!tray) return ZDWM_WINDOW_ID_INVALID; + + return tray->host_window; } static size_t tray_icon_count(void *handle) { - backend_t *backend = handle; - if (backend == nullptr || !backend->tray.enabled) return 0; + auto tray = get_tray_host(handle); - return backend->tray.icon_count; + return tray ? tray->icon_count : 0; } static int32_t tray_icon_size(void *handle) { - backend_t *backend = handle; - if (backend == nullptr || !backend->tray.enabled) return 0; + auto tray = get_tray_host(handle); - return backend->tray.icon_size; + return tray ? tray->icon_size : 0; } static void tray_place(void *handle, int32_t x) { - backend_t *backend = handle; - if (backend == nullptr || !backend->tray.enabled) return; + auto tray = get_tray_host(handle); + if (!tray) return; - auto conn = backend->conn; - auto tray = &backend->tray; + auto conn = ((backend_t *)handle)->conn; uint16_t value_mask = XCB_CONFIG_WINDOW_X; xcb_configure_window_value_list_t value_list = {.x = x}; xcb_configure_window_aux(conn, tray->container, value_mask, &value_list); @@ -43,11 +68,11 @@ static void tray_place(void *handle, int32_t x) { static void tray_set_listeners(void *handle, tray_icons_change_cb_t cb, void *user_data) { - backend_t *backend = handle; - if (backend == nullptr) return; + auto tray = get_tray_host(handle); + if (!tray) return; - backend->tray.icon_change_cb = cb; - backend->tray.cb_user_data = user_data; + tray->icon_change_cb = cb; + tray->cb_user_data = user_data; } const tray_api_t tray_api = { @@ -57,3 +82,142 @@ const tray_api_t tray_api = { .place = tray_place, .set_listener = tray_set_listeners, }; + +static bool tray_intern_selection_atom(tray_host_t *tray, backend_t *backend) { + auto conn = backend->conn; + auto screenp = backend->screenp; + + char selection_name[64] = {0}; + auto atom_name_format = "_NET_SYSTEM_TRAY_S%d"; + snprintf(selection_name, sizeof(selection_name), atom_name_format, screenp); + + uint16_t name_length = strlen(selection_name); + auto cookie = xcb_intern_atom(conn, false, name_length, selection_name); + auto reply = xcb_intern_atom_reply(conn, cookie, nullptr); + if (!reply) { + warn("cannot intern tray selection atom: %s", selection_name); + return false; + } + + tray->selection_atom = reply->atom; + p_delete(&reply); + return true; +} + +typedef struct tray_host_window_t { + xcb_window_t window; + int32_t width; + int32_t height; +} tray_host_window_t; + +static bool tray_create_container_window( + tray_host_t *tray, + backend_t *backend, + tray_host_window_t host_window +) { + auto conn = backend->conn; + auto window_id = xcb_generate_id(conn); + + xcb_create_window_value_list_t value_list = { + .override_redirect = true, + .event_mask = + XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_STRUCTURE_NOTIFY + }; + auto cookie = xcb_create_window_aux_checked( + conn, + XCB_COPY_FROM_PARENT, + window_id, + host_window.window, + (int16_t)host_window.width, + 0, + 1, + (uint16_t)MAX(1, host_window.height), + 0, + XCB_WINDOW_CLASS_INPUT_OUTPUT, + XCB_COPY_FROM_PARENT, + XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK, + &value_list + ); + + if (xcb_request_check(conn, cookie)) { + warn("cannot create system tray container window: 0x%x", window_id); + return false; + } + + tray->container = window_id; + + window_set_class_instance(conn, window_id); + +#define TRAY_CONTAINER_WINDOW_NAME APP_NAME "_tray" + window_set_name_static(conn, window_id, TRAY_CONTAINER_WINDOW_NAME); + auto name = TRAY_CONTAINER_WINDOW_NAME; + auto name_len = sizeof(TRAY_CONTAINER_WINDOW_NAME) - 1; +#undef TRAY_CONTAINER_WINDOW_NAME + + uint8_t mode = XCB_PROP_MODE_REPLACE; + auto atoms = &backend->atoms; + auto prop = atoms->_NET_WM_NAME; + auto type = atoms->UTF8_STRING; + xcb_change_property(conn, mode, window_id, prop, type, 8, name_len, name); + + return true; +} + +static bool tray_take_selection_owner(tray_host_t *tray, backend_t *backend) { + auto conn = backend->conn; + auto atom = tray->selection_atom; + auto cookie = xcb_get_selection_owner(conn, atom); + auto reply = xcb_get_selection_owner_reply(conn, cookie, nullptr); + if (reply && reply->owner != XCB_WINDOW_NONE) { + warn( + "system tray selection is already owned by window: 0x%x", + reply->owner + ); + p_delete(&reply); + return false; + } + + p_delete(&reply); + + auto owner = tray->container; + xcb_set_selection_owner(conn, owner, atom, XCB_CURRENT_TIME); + + cookie = xcb_get_selection_owner(conn, atom); + reply = xcb_get_selection_owner_reply(conn, cookie, nullptr); + bool success = reply && reply->owner == owner; + p_delete(&reply); + + if (!success) warn("cannot acquire system tray selection"); + return success; +} + +void tray_init( + backend_t *backend, + xcb_window_t host_window, + int32_t host_width, + int32_t host_height +) { + auto tray = get_tray_host(backend); + if (!tray) { + tray = p_new(tray_host_t, 1); + backend->tray = tray; + } + + if (tray->initialized) return; + + if (!tray_intern_selection_atom(tray, backend)) return; + + tray_host_window_t host_window_params = { + .window = host_window, + .width = host_width, + .height = host_height + }; + if (!tray_create_container_window(tray, backend, host_window_params)) return; + if (!tray_take_selection_owner(tray, backend)) { + /* TODO: cleanup tray */ + return; + } + + tray->enabled = true; + tray->initialized = true; +} diff --git a/src/backend/x11/tray.h b/src/backend/x11/tray.h new file mode 100644 index 0000000..4df0025 --- /dev/null +++ b/src/backend/x11/tray.h @@ -0,0 +1,17 @@ +#pragma once + +#include +#include + +#include "interface/tray.h" + +extern const tray_api_t tray_api; + +typedef struct backend_t backend_t; + +void tray_init( + backend_t *backend, + xcb_window_t host_window, + int32_t host_width, + int32_t host_height +);