feat(tray): 增加独立系统托盘模块并补齐兼容性与所有权处理
This commit is contained in:
@@ -48,6 +48,7 @@ add_executable(${APP_NAME}
|
|||||||
${SOURCE_DIR}/config.c
|
${SOURCE_DIR}/config.c
|
||||||
${SOURCE_DIR}/renderer.c
|
${SOURCE_DIR}/renderer.c
|
||||||
${SOURCE_DIR}/image.c
|
${SOURCE_DIR}/image.c
|
||||||
|
${SOURCE_DIR}/tray.c
|
||||||
)
|
)
|
||||||
|
|
||||||
find_package(PkgConfig REQUIRED)
|
find_package(PkgConfig REQUIRED)
|
||||||
|
|||||||
@@ -12,9 +12,15 @@ WM_WINDOW_ROLE
|
|||||||
|
|
||||||
UTF8_STRING
|
UTF8_STRING
|
||||||
COMPOUND_TEXT
|
COMPOUND_TEXT
|
||||||
|
MANAGER
|
||||||
|
_XEMBED
|
||||||
|
_XEMBED_INFO
|
||||||
|
|
||||||
_NET_ACTIVE_WINDOW
|
_NET_ACTIVE_WINDOW
|
||||||
_NET_CLIENT_LIST
|
_NET_CLIENT_LIST
|
||||||
|
_NET_SYSTEM_TRAY_COLORS
|
||||||
|
_NET_SYSTEM_TRAY_OPCODE
|
||||||
|
_NET_SYSTEM_TRAY_ORIENTATION
|
||||||
_NET_WM_DESKTOP
|
_NET_WM_DESKTOP
|
||||||
_NET_WM_NAME
|
_NET_WM_NAME
|
||||||
_NET_WM_STATE
|
_NET_WM_STATE
|
||||||
|
|||||||
18
src/event.c
18
src/event.c
@@ -15,6 +15,7 @@
|
|||||||
#include "client.h"
|
#include "client.h"
|
||||||
#include "default_config.h"
|
#include "default_config.h"
|
||||||
#include "monitor.h"
|
#include "monitor.h"
|
||||||
|
#include "tray.h"
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
#include "wm.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) {
|
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);
|
client_t *client = client_get_by_window(ev->window);
|
||||||
if (client) {
|
if (client) {
|
||||||
if (ev->value_mask & XCB_CONFIG_WINDOW_BORDER_WIDTH) {
|
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) {
|
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 =
|
xcb_get_window_attributes_reply_t *wa_reply =
|
||||||
xwindow_get_attributes_reply(ev->window);
|
xwindow_get_attributes_reply(ev->window);
|
||||||
|
|
||||||
@@ -183,6 +188,8 @@ static inline bool get_bool_property(bool init_property,
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void client_message(xcb_client_message_event_t *ev) {
|
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);
|
client_t *c = client_get_by_window(ev->window);
|
||||||
if (c == nullptr) return;
|
if (c == nullptr) return;
|
||||||
|
|
||||||
@@ -246,6 +253,8 @@ static void client_message(xcb_client_message_event_t *ev) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void property_notify(xcb_property_notify_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);
|
client_t *client = client_get_by_window(ev->window);
|
||||||
if (client == nullptr) return;
|
if (client == nullptr) return;
|
||||||
|
|
||||||
@@ -265,11 +274,15 @@ static void property_notify(xcb_property_notify_event_t *ev) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void destroy_notify(xcb_destroy_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);
|
client_t *client = client_get_by_window(ev->window);
|
||||||
if (client) client_unmanage(client, true);
|
if (client) client_unmanage(client, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void unmap_notify(xcb_unmap_notify_event_t *ev) {
|
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);
|
client_t *client = client_get_by_window(ev->window);
|
||||||
if (!client || client->minimize) return;
|
if (!client || client->minimize) return;
|
||||||
|
|
||||||
@@ -312,6 +325,10 @@ static void focus_in(xcb_focus_in_event_t *ev) {
|
|||||||
wm_set_current_monitor(client->monitor, ev->mode != XCB_NOTIFY_MODE_NORMAL);
|
wm_set_current_monitor(client->monitor, ev->mode != XCB_NOTIFY_MODE_NORMAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
static void handle_xcb_event(xcb_generic_event_t *event) {
|
||||||
uint8_t event_type = XCB_EVENT_RESPONSE_TYPE(event);
|
uint8_t event_type = XCB_EVENT_RESPONSE_TYPE(event);
|
||||||
const char *label = xcb_event_get_label(event_type);
|
const char *label = xcb_event_get_label(event_type);
|
||||||
@@ -337,6 +354,7 @@ static void handle_xcb_event(xcb_generic_event_t *event) {
|
|||||||
EVENT(XCB_DESTROY_NOTIFY, destroy_notify);
|
EVENT(XCB_DESTROY_NOTIFY, destroy_notify);
|
||||||
EVENT(XCB_ENTER_NOTIFY, enter_notify);
|
EVENT(XCB_ENTER_NOTIFY, enter_notify);
|
||||||
EVENT(XCB_FOCUS_IN, focus_in);
|
EVENT(XCB_FOCUS_IN, focus_in);
|
||||||
|
EVENT(XCB_SELECTION_CLEAR, selection_clear);
|
||||||
|
|
||||||
#undef EVENT
|
#undef EVENT
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
#include "renderer.h"
|
#include "renderer.h"
|
||||||
#include "status.h"
|
#include "status.h"
|
||||||
#include "text.h"
|
#include "text.h"
|
||||||
|
#include "tray.h"
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
#include "wm.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 renderer_status_t *rendered_status = nullptr;
|
||||||
static size_t rendered_status_capacity = 0;
|
static size_t rendered_status_capacity = 0;
|
||||||
|
|
||||||
monitor->status_extent.end = monitor->geometry.width;
|
monitor->status_extent.end = right_edge;
|
||||||
monitor->status_extent.start = monitor->geometry.width;
|
monitor->status_extent.start = right_edge;
|
||||||
int32_t end = monitor->layout_symbol_extent.end;
|
int32_t end = left_edge;
|
||||||
|
|
||||||
if (status == nullptr || !wm.config || !wm.config->status) return;
|
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;
|
task_in_tag_t *task_list = monitor->selected_tag->task_list;
|
||||||
if (!task_list) return;
|
if (!task_list) return;
|
||||||
|
|
||||||
@@ -352,7 +354,10 @@ static void monitor_draw_tasks(monitor_t *monitor) {
|
|||||||
if (task_count == 0) return;
|
if (task_count == 0) return;
|
||||||
|
|
||||||
int16_t x = monitor->layout_symbol_extent.end;
|
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;
|
if (task_width < wm.font_size) return;
|
||||||
|
|
||||||
for (task_in_tag_t *task = task_list; task; task = task->next) {
|
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) {
|
void monitor_draw_bar(monitor_t *monitor) {
|
||||||
cairo_t *cr = monitor->bar_cr;
|
cairo_t *cr = monitor->bar_cr;
|
||||||
uint16_t height = wm.bar_height;
|
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 = {
|
area_t bar_area = {
|
||||||
.x = 0,
|
.x = 0,
|
||||||
.y = 0,
|
.y = 0,
|
||||||
@@ -386,8 +394,10 @@ void monitor_draw_bar(monitor_t *monitor) {
|
|||||||
|
|
||||||
monitor_draw_tags(monitor);
|
monitor_draw_tags(monitor);
|
||||||
monitor_draw_layout_symbol(monitor);
|
monitor_draw_layout_symbol(monitor);
|
||||||
monitor_draw_status(monitor, wm.status);
|
monitor_draw_status(monitor, wm.status, status_right,
|
||||||
monitor_draw_tasks(monitor);
|
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) {
|
void monitor_arrange(monitor_t *monitor) {
|
||||||
|
|||||||
602
src/tray.c
Normal file
602
src/tray.c
Normal file
@@ -0,0 +1,602 @@
|
|||||||
|
#include "tray.h"
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <xcb/xcb.h>
|
||||||
|
#include <xcb/xcb_aux.h>
|
||||||
|
#include <xcb/xproto.h>
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
||||||
21
src/tray.h
Normal file
21
src/tray.h
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <xcb/xproto.h>
|
||||||
|
|
||||||
|
#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);
|
||||||
3
src/wm.c
3
src/wm.c
@@ -33,6 +33,7 @@
|
|||||||
#include "monitor.h"
|
#include "monitor.h"
|
||||||
#include "status.h"
|
#include "status.h"
|
||||||
#include "text.h"
|
#include "text.h"
|
||||||
|
#include "tray.h"
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
#include "wallpaper.h"
|
#include "wallpaper.h"
|
||||||
@@ -387,6 +388,7 @@ void wm_clean(void) {
|
|||||||
clean_status();
|
clean_status();
|
||||||
text_clean_pango_layout();
|
text_clean_pango_layout();
|
||||||
image_cache_clean();
|
image_cache_clean();
|
||||||
|
tray_cleanup();
|
||||||
|
|
||||||
{
|
{
|
||||||
client_t *c = wm.client_stack_list;
|
client_t *c = wm.client_stack_list;
|
||||||
@@ -457,6 +459,7 @@ static void wm_setup(void) {
|
|||||||
|
|
||||||
wm_setup_keybindings();
|
wm_setup_keybindings();
|
||||||
atoms_init(wm.xcb_conn);
|
atoms_init(wm.xcb_conn);
|
||||||
|
tray_init();
|
||||||
|
|
||||||
{
|
{
|
||||||
wm.wm_check_window = xcb_generate_id(wm.xcb_conn);
|
wm.wm_check_window = xcb_generate_id(wm.xcb_conn);
|
||||||
|
|||||||
Reference in New Issue
Block a user