aboutsummaryrefslogtreecommitdiff
path: root/swaybar
diff options
context:
space:
mode:
Diffstat (limited to 'swaybar')
-rw-r--r--swaybar/bar.c83
-rw-r--r--swaybar/config.c23
-rw-r--r--swaybar/i3bar.c36
-rw-r--r--swaybar/input.c84
-rw-r--r--swaybar/ipc.c115
-rw-r--r--swaybar/main.c4
-rw-r--r--swaybar/meson.build44
-rw-r--r--swaybar/render.c190
-rw-r--r--swaybar/status_line.c2
-rw-r--r--swaybar/tray/host.c210
-rw-r--r--swaybar/tray/icon.c462
-rw-r--r--swaybar/tray/item.c472
-rw-r--r--swaybar/tray/tray.c131
-rw-r--r--swaybar/tray/watcher.c212
14 files changed, 1885 insertions, 183 deletions
diff --git a/swaybar/bar.c b/swaybar/bar.c
index 08c386a7..d36367fc 100644
--- a/swaybar/bar.c
+++ b/swaybar/bar.c
@@ -1,4 +1,4 @@
-#define _XOPEN_SOURCE 500
+#define _POSIX_C_SOURCE 200809L
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
@@ -11,6 +11,7 @@
#include <wayland-client.h>
#include <wayland-cursor.h>
#include <wlr/util/log.h>
+#include "config.h"
#include "swaybar/bar.h"
#include "swaybar/config.h"
#include "swaybar/i3bar.h"
@@ -18,6 +19,9 @@
#include "swaybar/ipc.h"
#include "swaybar/status_line.h"
#include "swaybar/render.h"
+#if HAVE_TRAY
+#include "swaybar/tray/tray.h"
+#endif
#include "ipc-client.h"
#include "list.h"
#include "log.h"
@@ -31,6 +35,7 @@ void free_workspaces(struct wl_list *list) {
wl_list_for_each_safe(ws, tmp, list, link) {
wl_list_remove(&ws->link);
free(ws->name);
+ free(ws->label);
free(ws);
}
}
@@ -54,6 +59,7 @@ static void swaybar_output_free(struct swaybar_output *output) {
free_workspaces(&output->workspaces);
wl_list_remove(&output->link);
free(output->name);
+ free(output->identifier);
free(output);
}
@@ -119,7 +125,7 @@ static void destroy_layer_surface(struct swaybar_output *output) {
output->frame_scheduled = false;
}
-static void set_bar_dirty(struct swaybar *bar) {
+void set_bar_dirty(struct swaybar *bar) {
struct swaybar_output *output;
wl_list_for_each(output, &bar->outputs, link) {
set_output_dirty(output);
@@ -161,13 +167,15 @@ bool determine_bar_visibility(struct swaybar *bar, bool moving_layer) {
return visible;
}
-static bool bar_uses_output(struct swaybar *bar, const char *name) {
- if (bar->config->all_outputs) {
+static bool bar_uses_output(struct swaybar_output *output) {
+ if (output->bar->config->all_outputs) {
return true;
}
+ char *identifier = output->identifier;
struct config_output *coutput;
- wl_list_for_each(coutput, &bar->config->outputs, link) {
- if (strcmp(coutput->name, name) == 0) {
+ wl_list_for_each(coutput, &output->bar->config->outputs, link) {
+ if (strcmp(coutput->name, output->name) == 0 ||
+ (identifier && strcmp(coutput->name, identifier) == 0)) {
return true;
}
}
@@ -195,6 +203,10 @@ static void output_scale(void *data, struct wl_output *wl_output,
int32_t factor) {
struct swaybar_output *output = data;
output->scale = factor;
+ if (output == output->bar->pointer.current) {
+ update_cursor(output->bar);
+ render_frame(output);
+ }
}
struct wl_output_listener output_listener = {
@@ -206,12 +218,16 @@ struct wl_output_listener output_listener = {
static void xdg_output_handle_logical_position(void *data,
struct zxdg_output_v1 *xdg_output, int32_t x, int32_t y) {
- // Who cares
+ struct swaybar_output *output = data;
+ output->output_x = x;
+ output->output_y = y;
}
static void xdg_output_handle_logical_size(void *data,
struct zxdg_output_v1 *xdg_output, int32_t width, int32_t height) {
- // Who cares
+ struct swaybar_output *output = data;
+ output->output_height = height;
+ output->output_width = width;
}
static void xdg_output_handle_done(void *data,
@@ -220,7 +236,7 @@ static void xdg_output_handle_done(void *data,
struct swaybar *bar = output->bar;
assert(output->name != NULL);
- if (!bar_uses_output(bar, output->name)) {
+ if (!bar_uses_output(output)) {
swaybar_output_free(output);
return;
}
@@ -245,7 +261,22 @@ static void xdg_output_handle_name(void *data,
static void xdg_output_handle_description(void *data,
struct zxdg_output_v1 *xdg_output, const char *description) {
- // Who cares
+ // wlroots currently sets the description to `make model serial (name)`
+ // If this changes in the future, this will need to be modified.
+ struct swaybar_output *output = data;
+ free(output->identifier);
+ output->identifier = NULL;
+ char *paren = strrchr(description, '(');
+ if (paren) {
+ size_t length = paren - description;
+ output->identifier = malloc(length);
+ if (!output->identifier) {
+ wlr_log(WLR_ERROR, "Failed to allocate output identifier");
+ return;
+ }
+ strncpy(output->identifier, description, length);
+ output->identifier[length - 1] = '\0';
+ }
}
struct zxdg_output_v1_listener xdg_output_listener = {
@@ -272,7 +303,7 @@ static void handle_global(void *data, struct wl_registry *registry,
struct swaybar *bar = data;
if (strcmp(interface, wl_compositor_interface.name) == 0) {
bar->compositor = wl_registry_bind(registry, name,
- &wl_compositor_interface, 3);
+ &wl_compositor_interface, 4);
} else if (strcmp(interface, wl_seat_interface.name) == 0) {
bar->seat = wl_registry_bind(registry, name,
&wl_seat_interface, 3);
@@ -354,25 +385,15 @@ bool bar_setup(struct swaybar *bar, const char *socket_path) {
wl_display_roundtrip(bar->display);
struct swaybar_pointer *pointer = &bar->pointer;
-
- int max_scale = 1;
- struct swaybar_output *output;
- wl_list_for_each(output, &bar->outputs, link) {
- if (output->scale > max_scale) {
- max_scale = output->scale;
- }
- }
-
- pointer->cursor_theme =
- wl_cursor_theme_load(NULL, 24 * max_scale, bar->shm);
- assert(pointer->cursor_theme);
- struct wl_cursor *cursor;
- cursor = wl_cursor_theme_get_cursor(pointer->cursor_theme, "left_ptr");
- assert(cursor);
- pointer->cursor_image = cursor->images[0];
pointer->cursor_surface = wl_compositor_create_surface(bar->compositor);
assert(pointer->cursor_surface);
+#if HAVE_TRAY
+ if (!bar->config->tray_hidden) {
+ bar->tray = create_tray(bar);
+ }
+#endif
+
if (bar->config->workspace_buttons) {
ipc_get_workspaces(bar);
}
@@ -414,6 +435,11 @@ void bar_run(struct swaybar *bar) {
loop_add_fd(bar->eventloop, bar->status->read_fd, POLLIN,
status_in, bar);
}
+#if HAVE_TRAY
+ if (bar->tray) {
+ loop_add_fd(bar->eventloop, bar->tray->fd, POLLIN, tray_in, bar->tray->bus);
+ }
+#endif
while (1) {
errno = 0;
if (wl_display_flush(bar->display) == -1 && errno != EAGAIN) {
@@ -431,6 +457,9 @@ static void free_outputs(struct wl_list *list) {
}
void bar_teardown(struct swaybar *bar) {
+#if HAVE_TRAY
+ destroy_tray(bar->tray);
+#endif
free_outputs(&bar->outputs);
if (bar->config) {
free_config(bar->config);
diff --git a/swaybar/config.c b/swaybar/config.c
index 0fd1f02e..d4cc9b1a 100644
--- a/swaybar/config.c
+++ b/swaybar/config.c
@@ -1,9 +1,10 @@
-#define _XOPEN_SOURCE 500
+#define _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <string.h>
#include <wlr/util/log.h>
#include "swaybar/config.h"
#include "wlr-layer-shell-unstable-v1-client-protocol.h"
+#include "config.h"
#include "stringop.h"
#include "list.h"
@@ -30,15 +31,24 @@ struct swaybar_config *init_config(void) {
config->hidden_state = strdup("hide");
config->sep_symbol = NULL;
config->strip_workspace_numbers = false;
+ config->strip_workspace_name = false;
config->binding_mode_indicator = true;
config->wrap_scroll = false;
config->workspace_buttons = true;
config->bindings = create_list();
wl_list_init(&config->outputs);
+ config->status_padding = 1;
+ config->status_edge_padding = 3;
/* height */
config->height = 0;
+ /* gaps */
+ config->gaps.top = 0;
+ config->gaps.right = 0;
+ config->gaps.bottom = 0;
+ config->gaps.left = 0;
+
/* colors */
config->colors.background = 0x000000FF;
config->colors.focused_background = 0x000000FF;
@@ -66,6 +76,10 @@ struct swaybar_config *init_config(void) {
config->colors.binding_mode.background = 0x900000FF;
config->colors.binding_mode.text = 0xFFFFFFFF;
+#if HAVE_TRAY
+ config->tray_padding = 2;
+#endif
+
return config;
}
@@ -95,5 +109,12 @@ void free_config(struct swaybar_config *config) {
free(coutput->name);
free(coutput);
}
+#if HAVE_TRAY
+ list_free_items_and_destroy(config->tray_outputs);
+ for (int i = 0; i < 10; ++i) {
+ free(config->tray_bindings[i]);
+ }
+ free(config->icon_theme);
+#endif
free(config);
}
diff --git a/swaybar/i3bar.c b/swaybar/i3bar.c
index 3ea74e13..116c8f6e 100644
--- a/swaybar/i3bar.c
+++ b/swaybar/i3bar.c
@@ -259,8 +259,34 @@ bool i3bar_handle_readable(struct status_line *status) {
}
}
+static uint32_t event_to_x11_button(uint32_t event) {
+ switch (event) {
+ case BTN_LEFT:
+ return 1;
+ case BTN_MIDDLE:
+ return 2;
+ case BTN_RIGHT:
+ return 3;
+ case SWAY_SCROLL_UP:
+ return 4;
+ case SWAY_SCROLL_DOWN:
+ return 5;
+ case SWAY_SCROLL_LEFT:
+ return 6;
+ case SWAY_SCROLL_RIGHT:
+ return 7;
+ case BTN_SIDE:
+ return 8;
+ case BTN_EXTRA:
+ return 9;
+ default:
+ return 0;
+ }
+}
+
enum hotspot_event_handling i3bar_block_send_click(struct status_line *status,
- struct i3bar_block *block, int x, int y, enum x11_button button) {
+ struct i3bar_block *block, int x, int y, int rx, int ry, int w, int h,
+ uint32_t button) {
wlr_log(WLR_DEBUG, "block %s clicked", block->name);
if (!block->name || !status->click_events) {
return HOTSPOT_PROCESS;
@@ -274,9 +300,15 @@ enum hotspot_event_handling i3bar_block_send_click(struct status_line *status,
json_object_new_string(block->instance));
}
- json_object_object_add(event_json, "button", json_object_new_int(button));
+ json_object_object_add(event_json, "button",
+ json_object_new_int(event_to_x11_button(button)));
+ json_object_object_add(event_json, "event", json_object_new_int(button));
json_object_object_add(event_json, "x", json_object_new_int(x));
json_object_object_add(event_json, "y", json_object_new_int(y));
+ json_object_object_add(event_json, "relative_x", json_object_new_int(rx));
+ json_object_object_add(event_json, "relative_y", json_object_new_int(ry));
+ json_object_object_add(event_json, "width", json_object_new_int(w));
+ json_object_object_add(event_json, "height", json_object_new_int(h));
if (dprintf(status->write_fd, "%s%s\n", status->clicked ? "," : "",
json_object_to_json_string(event_json)) < 0) {
status_error(status, "[failed to write click event]");
diff --git a/swaybar/input.c b/swaybar/input.c
index 263d0253..bdd55e58 100644
--- a/swaybar/input.c
+++ b/swaybar/input.c
@@ -1,9 +1,5 @@
#include <assert.h>
-#ifdef __FreeBSD__
-#include <dev/evdev/input-event-codes.h>
-#else
#include <linux/input-event-codes.h>
-#endif
#include <stdlib.h>
#include <wayland-client.h>
#include <wayland-cursor.h>
@@ -26,33 +22,39 @@ void free_hotspots(struct wl_list *list) {
}
}
-static enum x11_button wl_button_to_x11_button(uint32_t button) {
- switch (button) {
- case BTN_LEFT:
- return LEFT;
- case BTN_MIDDLE:
- return MIDDLE;
- case BTN_RIGHT:
- return RIGHT;
- case BTN_SIDE:
- return BACK;
- case BTN_EXTRA:
- return FORWARD;
- default:
- return NONE;
- }
-}
-
-static enum x11_button wl_axis_to_x11_button(uint32_t axis, wl_fixed_t value) {
+static uint32_t wl_axis_to_button(uint32_t axis, wl_fixed_t value) {
+ bool negative = wl_fixed_to_double(value) < 0;
switch (axis) {
case WL_POINTER_AXIS_VERTICAL_SCROLL:
- return wl_fixed_to_double(value) < 0 ? SCROLL_UP : SCROLL_DOWN;
+ return negative ? SWAY_SCROLL_UP : SWAY_SCROLL_DOWN;
case WL_POINTER_AXIS_HORIZONTAL_SCROLL:
- return wl_fixed_to_double(value) < 0 ? SCROLL_LEFT : SCROLL_RIGHT;
+ return negative ? SWAY_SCROLL_LEFT : SWAY_SCROLL_RIGHT;
default:
wlr_log(WLR_DEBUG, "Unexpected axis value on mouse scroll");
- return NONE;
+ return 0;
+ }
+}
+
+void update_cursor(struct swaybar *bar) {
+ struct swaybar_pointer *pointer = &bar->pointer;
+ if (pointer->cursor_theme) {
+ wl_cursor_theme_destroy(pointer->cursor_theme);
}
+ int scale = pointer->current ? pointer->current->scale : 1;
+ pointer->cursor_theme = wl_cursor_theme_load(NULL, 24 * scale, bar->shm);
+ struct wl_cursor *cursor;
+ cursor = wl_cursor_theme_get_cursor(pointer->cursor_theme, "left_ptr");
+ pointer->cursor_image = cursor->images[0];
+ wl_surface_set_buffer_scale(pointer->cursor_surface, scale);
+ wl_surface_attach(pointer->cursor_surface,
+ wl_cursor_image_get_buffer(pointer->cursor_image), 0, 0);
+ wl_pointer_set_cursor(pointer->pointer, pointer->serial,
+ pointer->cursor_surface,
+ pointer->cursor_image->hotspot_x / scale,
+ pointer->cursor_image->hotspot_y / scale);
+ wl_surface_damage_buffer(pointer->cursor_surface, 0, 0,
+ INT32_MAX, INT32_MAX);
+ wl_surface_commit(pointer->cursor_surface);
}
static void wl_pointer_enter(void *data, struct wl_pointer *wl_pointer,
@@ -60,6 +62,7 @@ static void wl_pointer_enter(void *data, struct wl_pointer *wl_pointer,
wl_fixed_t surface_x, wl_fixed_t surface_y) {
struct swaybar *bar = data;
struct swaybar_pointer *pointer = &bar->pointer;
+ pointer->serial = serial;
struct swaybar_output *output;
wl_list_for_each(output, &bar->outputs, link) {
if (output->surface == surface) {
@@ -67,20 +70,7 @@ static void wl_pointer_enter(void *data, struct wl_pointer *wl_pointer,
break;
}
}
- int max_scale = 1;
- struct swaybar_output *_output;
- wl_list_for_each(_output, &bar->outputs, link) {
- if (_output->scale > max_scale) {
- max_scale = _output->scale;
- }
- }
- wl_surface_set_buffer_scale(pointer->cursor_surface, max_scale);
- wl_surface_attach(pointer->cursor_surface,
- wl_cursor_image_get_buffer(pointer->cursor_image), 0, 0);
- wl_pointer_set_cursor(wl_pointer, serial, pointer->cursor_surface,
- pointer->cursor_image->hotspot_x / max_scale,
- pointer->cursor_image->hotspot_y / max_scale);
- wl_surface_commit(pointer->cursor_surface);
+ update_cursor(bar);
}
static void wl_pointer_leave(void *data, struct wl_pointer *wl_pointer,
@@ -96,12 +86,12 @@ static void wl_pointer_motion(void *data, struct wl_pointer *wl_pointer,
bar->pointer.y = wl_fixed_to_int(surface_y);
}
-static bool check_bindings(struct swaybar *bar, uint32_t x11_button,
+static bool check_bindings(struct swaybar *bar, uint32_t button,
uint32_t state) {
bool released = state == WL_POINTER_BUTTON_STATE_RELEASED;
for (int i = 0; i < bar->config->bindings->length; i++) {
struct swaybar_binding *binding = bar->config->bindings->items[i];
- if (binding->button == x11_button && binding->release == released) {
+ if (binding->button == button && binding->release == released) {
ipc_execute_binding(bar, binding);
return true;
}
@@ -118,7 +108,7 @@ static void wl_pointer_button(void *data, struct wl_pointer *wl_pointer,
return;
}
- if (check_bindings(bar, wl_button_to_x11_button(button), state)) {
+ if (check_bindings(bar, button, state)) {
return;
}
@@ -133,8 +123,8 @@ static void wl_pointer_button(void *data, struct wl_pointer *wl_pointer,
&& y >= hotspot->y
&& x < hotspot->x + hotspot->width
&& y < hotspot->y + hotspot->height) {
- if (HOTSPOT_IGNORE == hotspot->callback(output, pointer->x, pointer->y,
- wl_button_to_x11_button(button), hotspot->data)) {
+ if (HOTSPOT_IGNORE == hotspot->callback(output, hotspot,
+ pointer->x, pointer->y, button, hotspot->data)) {
return;
}
}
@@ -152,7 +142,7 @@ static void wl_pointer_axis(void *data, struct wl_pointer *wl_pointer,
// If there is a button press binding, execute it, skip default behavior,
// and check button release bindings
- enum x11_button button = wl_axis_to_x11_button(axis, value);
+ uint32_t button = wl_axis_to_button(axis, value);
if (check_bindings(bar, button, WL_POINTER_BUTTON_STATE_PRESSED)) {
check_bindings(bar, button, WL_POINTER_BUTTON_STATE_RELEASED);
return;
@@ -166,8 +156,8 @@ static void wl_pointer_axis(void *data, struct wl_pointer *wl_pointer,
&& y >= hotspot->y
&& x < hotspot->x + hotspot->width
&& y < hotspot->y + hotspot->height) {
- if (HOTSPOT_IGNORE == hotspot->callback(
- output, pointer->x, pointer->y, button, hotspot->data)) {
+ if (HOTSPOT_IGNORE == hotspot->callback(output, hotspot,
+ pointer->x, pointer->y, button, hotspot->data)) {
return;
}
}
diff --git a/swaybar/ipc.c b/swaybar/ipc.c
index 706f968d..097f9161 100644
--- a/swaybar/ipc.c
+++ b/swaybar/ipc.c
@@ -6,6 +6,7 @@
#include <wlr/util/log.h>
#include "swaybar/config.h"
#include "swaybar/ipc.h"
+#include "config.h"
#include "ipc-client.h"
#include "list.h"
@@ -153,18 +154,21 @@ static bool ipc_parse_config(
return false;
}
json_object *markup, *mode, *hidden_state, *position, *status_command;
- json_object *font, *bar_height, *wrap_scroll, *workspace_buttons, *strip_workspace_numbers;
- json_object *binding_mode_indicator, *verbose, *colors, *sep_symbol, *outputs;
- json_object *bindings;
+ json_object *font, *gaps, *bar_height, *wrap_scroll, *workspace_buttons;
+ json_object *strip_workspace_numbers, *strip_workspace_name;
+ json_object *binding_mode_indicator, *verbose, *colors, *sep_symbol;
+ json_object *outputs, *bindings, *status_padding, *status_edge_padding;
json_object_object_get_ex(bar_config, "mode", &mode);
json_object_object_get_ex(bar_config, "hidden_state", &hidden_state);
json_object_object_get_ex(bar_config, "position", &position);
json_object_object_get_ex(bar_config, "status_command", &status_command);
json_object_object_get_ex(bar_config, "font", &font);
+ json_object_object_get_ex(bar_config, "gaps", &gaps);
json_object_object_get_ex(bar_config, "bar_height", &bar_height);
json_object_object_get_ex(bar_config, "wrap_scroll", &wrap_scroll);
json_object_object_get_ex(bar_config, "workspace_buttons", &workspace_buttons);
json_object_object_get_ex(bar_config, "strip_workspace_numbers", &strip_workspace_numbers);
+ json_object_object_get_ex(bar_config, "strip_workspace_name", &strip_workspace_name);
json_object_object_get_ex(bar_config, "binding_mode_indicator", &binding_mode_indicator);
json_object_object_get_ex(bar_config, "verbose", &verbose);
json_object_object_get_ex(bar_config, "separator_symbol", &sep_symbol);
@@ -172,6 +176,9 @@ static bool ipc_parse_config(
json_object_object_get_ex(bar_config, "outputs", &outputs);
json_object_object_get_ex(bar_config, "pango_markup", &markup);
json_object_object_get_ex(bar_config, "bindings", &bindings);
+ json_object_object_get_ex(bar_config, "status_padding", &status_padding);
+ json_object_object_get_ex(bar_config, "status_edge_padding",
+ &status_edge_padding);
if (status_command) {
free(config->status_command);
config->status_command = strdup(json_object_get_string(status_command));
@@ -190,6 +197,9 @@ static bool ipc_parse_config(
if (strip_workspace_numbers) {
config->strip_workspace_numbers = json_object_get_boolean(strip_workspace_numbers);
}
+ if (strip_workspace_name) {
+ config->strip_workspace_name = json_object_get_boolean(strip_workspace_name);
+ }
if (binding_mode_indicator) {
config->binding_mode_indicator = json_object_get_boolean(binding_mode_indicator);
}
@@ -202,6 +212,30 @@ static bool ipc_parse_config(
if (bar_height) {
config->height = json_object_get_int(bar_height);
}
+ if (status_padding) {
+ config->status_padding = json_object_get_int(status_padding);
+ }
+ if (status_edge_padding) {
+ config->status_edge_padding = json_object_get_int(status_edge_padding);
+ }
+ if (gaps) {
+ json_object *top = json_object_object_get(gaps, "top");
+ if (top) {
+ config->gaps.top = json_object_get_int(top);
+ }
+ json_object *right = json_object_object_get(gaps, "right");
+ if (right) {
+ config->gaps.right = json_object_get_int(right);
+ }
+ json_object *bottom = json_object_object_get(gaps, "bottom");
+ if (bottom) {
+ config->gaps.bottom = json_object_get_int(bottom);
+ }
+ json_object *left = json_object_object_get(gaps, "left");
+ if (left) {
+ config->gaps.left = json_object_get_int(left);
+ }
+ }
if (markup) {
config->pango_markup = json_object_get_boolean(markup);
}
@@ -212,7 +246,7 @@ static bool ipc_parse_config(
struct swaybar_binding *binding =
calloc(1, sizeof(struct swaybar_binding));
binding->button = json_object_get_int(
- json_object_object_get(bindobj, "input_code"));
+ json_object_object_get(bindobj, "event_code"));
binding->command = strdup(json_object_get_string(
json_object_object_get(bindobj, "command")));
binding->release = json_object_get_boolean(
@@ -258,6 +292,40 @@ static bool ipc_parse_config(
ipc_parse_colors(config, colors);
}
+#if HAVE_TRAY
+ json_object *tray_outputs, *tray_padding, *tray_bindings, *icon_theme;
+
+ if ((json_object_object_get_ex(bar_config, "tray_outputs", &tray_outputs))) {
+ config->tray_outputs = create_list();
+ int length = json_object_array_length(tray_outputs);
+ for (int i = 0; i < length; ++i) {
+ json_object *o = json_object_array_get_idx(tray_outputs, i);
+ list_add(config->tray_outputs, strdup(json_object_get_string(o)));
+ }
+ config->tray_hidden = strcmp(config->tray_outputs->items[0], "none") == 0;
+ }
+
+ if ((json_object_object_get_ex(bar_config, "tray_padding", &tray_padding))) {
+ config->tray_padding = json_object_get_int(tray_padding);
+ }
+
+ if ((json_object_object_get_ex(bar_config, "tray_bindings", &tray_bindings))) {
+ int length = json_object_array_length(tray_bindings);
+ for (int i = 0; i < length; ++i) {
+ json_object *bind = json_object_array_get_idx(tray_bindings, i);
+ json_object *button, *command;
+ json_object_object_get_ex(bind, "input_code", &button);
+ json_object_object_get_ex(bind, "command", &command);
+ config->tray_bindings[json_object_get_int(button)] =
+ strdup(json_object_get_string(command));
+ }
+ }
+
+ if ((json_object_object_get_ex(bar_config, "icon_theme", &icon_theme))) {
+ config->icon_theme = strdup(json_object_get_string(icon_theme));
+ }
+#endif
+
json_object_put(bar_config);
return true;
}
@@ -298,6 +366,24 @@ bool ipc_get_workspaces(struct swaybar *bar) {
calloc(1, sizeof(struct swaybar_workspace));
ws->num = json_object_get_int(num);
ws->name = strdup(json_object_get_string(name));
+ ws->label = strdup(ws->name);
+ // ws->num will be -1 if workspace name doesn't begin with int.
+ if (ws->num != -1) {
+ size_t len_offset = numlen(ws->num);
+ if (bar->config->strip_workspace_name) {
+ free(ws->label);
+ ws->label = malloc(len_offset + 1 * sizeof(char));
+ ws->label[len_offset] = '\0';
+ strncpy(ws->label, ws->name, len_offset);
+ } else if (bar->config->strip_workspace_numbers) {
+ len_offset += ws->label[len_offset] == ':';
+ if (strlen(ws->name) > len_offset) {
+ free(ws->label);
+ // Strip number prefix [1-?:] using len_offset.
+ ws->label = strdup(ws->name + len_offset);
+ }
+ }
+ }
ws->visible = json_object_get_boolean(visible);
ws->focused = json_object_get_boolean(focused);
if (ws->focused) {
@@ -423,6 +509,27 @@ static bool handle_barconfig_update(struct swaybar *bar,
config->mode = strdup(json_object_get_string(json_mode));
wlr_log(WLR_DEBUG, "Changing bar mode to %s", config->mode);
+ json_object *gaps;
+ json_object_object_get_ex(json_config, "gaps", &gaps);
+ if (gaps) {
+ json_object *top = json_object_object_get(gaps, "top");
+ if (top) {
+ config->gaps.top = json_object_get_int(top);
+ }
+ json_object *right = json_object_object_get(gaps, "right");
+ if (right) {
+ config->gaps.right = json_object_get_int(right);
+ }
+ json_object *bottom = json_object_object_get(gaps, "bottom");
+ if (bottom) {
+ config->gaps.bottom = json_object_get_int(bottom);
+ }
+ json_object *left = json_object_object_get(gaps, "left");
+ if (left) {
+ config->gaps.left = json_object_get_int(left);
+ }
+ }
+
return determine_bar_visibility(bar, true);
}
diff --git a/swaybar/main.c b/swaybar/main.c
index 2672abef..fa99b1ba 100644
--- a/swaybar/main.c
+++ b/swaybar/main.c
@@ -1,4 +1,4 @@
-#define _XOPEN_SOURCE 500
+#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -76,7 +76,7 @@ int main(int argc, char **argv) {
if (debug) {
wlr_log_init(WLR_DEBUG, NULL);
} else {
- wlr_log_init(WLR_ERROR, NULL);
+ wlr_log_init(WLR_INFO, NULL);
}
if (!swaybar.id) {
diff --git a/swaybar/meson.build b/swaybar/meson.build
index c27cf2c2..312ca97b 100644
--- a/swaybar/meson.build
+++ b/swaybar/meson.build
@@ -1,3 +1,32 @@
+tray_files = get_option('enable-tray') ? [
+ 'tray/host.c',
+ 'tray/icon.c',
+ 'tray/item.c',
+ 'tray/tray.c',
+ 'tray/watcher.c'
+] : []
+
+swaybar_deps = [
+ cairo,
+ client_protos,
+ gdk_pixbuf,
+ jsonc,
+ math,
+ pango,
+ pangocairo,
+ rt,
+ wayland_client,
+ wayland_cursor,
+ wlroots,
+]
+if get_option('enable-tray')
+ if systemd.found()
+ swaybar_deps += systemd
+ elif elogind.found()
+ swaybar_deps += elogind
+ endif
+endif
+
executable(
'swaybar', [
'bar.c',
@@ -8,21 +37,10 @@ executable(
'main.c',
'render.c',
'status_line.c',
+ tray_files
],
include_directories: [sway_inc],
- dependencies: [
- cairo,
- client_protos,
- gdk_pixbuf,
- jsonc,
- math,
- pango,
- pangocairo,
- rt,
- wayland_client,
- wayland_cursor,
- wlroots,
- ],
+ dependencies: swaybar_deps,
link_with: [lib_sway_common, lib_sway_client],
install_rpath : rpathdir,
install: true
diff --git a/swaybar/render.c b/swaybar/render.c
index 4ebf922e..55f680ed 100644
--- a/swaybar/render.c
+++ b/swaybar/render.c
@@ -1,5 +1,6 @@
#define _POSIX_C_SOURCE 200809L
#include <assert.h>
+#include <linux/input-event-codes.h>
#include <limits.h>
#include <stdlib.h>
#include <stdint.h>
@@ -14,6 +15,9 @@
#include "swaybar/ipc.h"
#include "swaybar/render.h"
#include "swaybar/status_line.h"
+#if HAVE_TRAY
+#include "swaybar/tray/tray.h"
+#endif
#include "wlr-layer-shell-unstable-v1-client-protocol.h"
static const int WS_HORIZONTAL_PADDING = 5;
@@ -32,7 +36,8 @@ static uint32_t render_status_line_error(cairo_t *cairo,
cairo_set_source_u32(cairo, 0xFF0000FF);
int margin = 3 * output->scale;
- int ws_vertical_padding = WS_VERTICAL_PADDING * output->scale;
+ double ws_vertical_padding =
+ output->bar->config->status_padding * output->scale;
char *font = output->bar->config->font;
int text_width, text_height;
@@ -41,7 +46,8 @@ static uint32_t render_status_line_error(cairo_t *cairo,
uint32_t ideal_height = text_height + ws_vertical_padding * 2;
uint32_t ideal_surface_height = ideal_height / output->scale;
- if (output->height < ideal_surface_height) {
+ if (!output->bar->config->height &&
+ output->height < ideal_surface_height) {
return ideal_surface_height;
}
*x -= text_width + margin;
@@ -68,12 +74,13 @@ static uint32_t render_status_line_text(cairo_t *cairo,
get_text_size(cairo, config->font, &text_width, &text_height, NULL,
output->scale, config->pango_markup, "%s", text);
- int ws_vertical_padding = WS_VERTICAL_PADDING * output->scale;
+ double ws_vertical_padding = config->status_padding * output->scale;
int margin = 3 * output->scale;
uint32_t ideal_height = text_height + ws_vertical_padding * 2;
uint32_t ideal_surface_height = ideal_height / output->scale;
- if (output->height < ideal_surface_height) {
+ if (!output->bar->config->height &&
+ output->height < ideal_surface_height) {
return ideal_surface_height;
}
@@ -87,13 +94,24 @@ static uint32_t render_status_line_text(cairo_t *cairo,
return output->height;
}
-static void render_sharp_line(cairo_t *cairo, uint32_t color,
+static void render_sharp_rectangle(cairo_t *cairo, uint32_t color,
double x, double y, double width, double height) {
+ cairo_save(cairo);
cairo_set_source_u32(cairo, color);
+ cairo_set_antialias(cairo, CAIRO_ANTIALIAS_NONE);
+ cairo_rectangle(cairo, x, y, width, height);
+ cairo_fill(cairo);
+ cairo_restore(cairo);
+}
+
+static void render_sharp_line(cairo_t *cairo, uint32_t color,
+ double x, double y, double width, double height) {
if (width > 1 && height > 1) {
- cairo_rectangle(cairo, x, y, width, height);
- cairo_fill(cairo);
+ render_sharp_rectangle(cairo, color, x, y, width, height);
} else {
+ cairo_save(cairo);
+ cairo_set_source_u32(cairo, color);
+ cairo_set_antialias(cairo, CAIRO_ANTIALIAS_NONE);
if (width == 1) {
x += 0.5;
height += y;
@@ -108,14 +126,17 @@ static void render_sharp_line(cairo_t *cairo, uint32_t color,
cairo_set_line_width(cairo, 1.0);
cairo_line_to(cairo, width, height);
cairo_stroke(cairo);
+ cairo_restore(cairo);
}
}
-static enum hotspot_event_handling block_hotspot_callback(struct swaybar_output *output,
- int x, int y, enum x11_button button, void *data) {
+static enum hotspot_event_handling block_hotspot_callback(
+ struct swaybar_output *output, struct swaybar_hotspot *hotspot,
+ int x, int y, uint32_t button, void *data) {
struct i3bar_block *block = data;
struct status_line *status = output->bar->status;
- return i3bar_block_send_click(status, block, x, y, button);
+ return i3bar_block_send_click(status, block, x, y, x - hotspot->x,
+ y - hotspot->y, hotspot->width, hotspot->height, button);
}
static void i3bar_block_unref_callback(void *data) {
@@ -136,7 +157,7 @@ static uint32_t render_status_block(cairo_t *cairo,
output->scale, block->markup, "%s", block->full_text);
int margin = 3 * output->scale;
- int ws_vertical_padding = WS_VERTICAL_PADDING * 2;
+ double ws_vertical_padding = config->status_padding * output->scale;
int width = text_width;
if (width < block->min_width) {
@@ -146,37 +167,40 @@ static uint32_t render_status_block(cairo_t *cairo,
double block_width = width;
uint32_t ideal_height = text_height + ws_vertical_padding * 2;
uint32_t ideal_surface_height = ideal_height / output->scale;
- if (output->height < ideal_surface_height) {
+ if (!output->bar->config->height &&
+ output->height < ideal_surface_height) {
return ideal_surface_height;
}
*x -= width;
- if (block->border && block->border_left > 0) {
- *x -= (block->border_left + margin);
- block_width += block->border_left + margin;
+ if ((block->border || block->urgent) && block->border_left > 0) {
+ *x -= (block->border_left * output->scale + margin);
+ block_width += block->border_left * output->scale + margin;
}
- if (block->border && block->border_right > 0) {
- *x -= (block->border_right + margin);
- block_width += block->border_right + margin;
+ if ((block->border || block->urgent) && block->border_right > 0) {
+ *x -= (block->border_right * output->scale + margin);
+ block_width += block->border_right * output->scale + margin;
}
int sep_width, sep_height;
+ int sep_block_width = block->separator_block_width;
if (!edge) {
if (config->sep_symbol) {
get_text_size(cairo, config->font, &sep_width, &sep_height, NULL,
output->scale, false, "%s", config->sep_symbol);
uint32_t _ideal_height = sep_height + ws_vertical_padding * 2;
uint32_t _ideal_surface_height = _ideal_height / output->scale;
- if (output->height < _ideal_surface_height) {
+ if (!output->bar->config->height &&
+ output->height < _ideal_surface_height) {
return _ideal_surface_height;
}
- if (sep_width > block->separator_block_width) {
- block->separator_block_width = sep_width + margin * 2;
+ if (sep_width > sep_block_width) {
+ sep_block_width = sep_width + margin * 2;
}
}
- *x -= block->separator_block_width;
- } else {
- *x -= margin;
+ *x -= sep_block_width;
+ } else if (config->status_edge_padding) {
+ *x -= config->status_edge_padding * output->scale;
}
uint32_t height = output->height * output->scale;
@@ -193,53 +217,55 @@ static uint32_t render_status_block(cairo_t *cairo,
wl_list_insert(&output->hotspots, &hotspot->link);
}
- double pos = *x;
- if (block->background) {
- cairo_set_source_u32(cairo, block->background);
- cairo_rectangle(cairo, pos - 0.5 * output->scale,
- output->scale, width, height);
- cairo_fill(cairo);
+ double x_pos = *x;
+ double y_pos = ws_vertical_padding;
+ double render_height = height - ws_vertical_padding * 2;
+
+ uint32_t bg_color = block->urgent
+ ? config->colors.urgent_workspace.background : block->background;
+ if (bg_color) {
+ render_sharp_rectangle(cairo, bg_color, x_pos, y_pos,
+ block_width, render_height);
}
- if (block->border && block->border_top > 0) {
- render_sharp_line(cairo, block->border,
- pos - 0.5 * output->scale, output->scale,
- block_width, block->border_top);
+ uint32_t border_color = block->urgent
+ ? config->colors.urgent_workspace.border : block->border;
+ if (border_color && block->border_top > 0) {
+ render_sharp_line(cairo, border_color, x_pos, y_pos,
+ block_width, block->border_top * output->scale);
}
- if (block->border && block->border_bottom > 0) {
- render_sharp_line(cairo, block->border,
- pos - 0.5 * output->scale,
- height - output->scale - block->border_bottom,
- block_width, block->border_bottom);
+ if (border_color && block->border_bottom > 0) {
+ render_sharp_line(cairo, border_color, x_pos,
+ y_pos + render_height - block->border_bottom * output->scale,
+ block_width, block->border_bottom * output->scale);
}
- if (block->border != 0 && block->border_left > 0) {
- render_sharp_line(cairo, block->border,
- pos - 0.5 * output->scale, output->scale,
- block->border_left, height);
- pos += block->border_left + margin;
+ if (border_color && block->border_left > 0) {
+ render_sharp_line(cairo, border_color, x_pos, y_pos,
+ block->border_left * output->scale, render_height);
+ x_pos += block->border_left * output->scale + margin;
}
double offset = 0;
if (strncmp(block->align, "left", 5) == 0) {
- offset = pos;
+ offset = x_pos;
} else if (strncmp(block->align, "right", 5) == 0) {
- offset = pos + width - text_width;
+ offset = x_pos + width - text_width;
} else if (strncmp(block->align, "center", 6) == 0) {
- offset = pos + (width - text_width) / 2;
+ offset = x_pos + (width - text_width) / 2;
}
cairo_move_to(cairo, offset, height / 2.0 - text_height / 2.0);
uint32_t color = block->color ? *block->color : config->colors.statusline;
+ color = block->urgent ? config->colors.urgent_workspace.text : color;
cairo_set_source_u32(cairo, color);
pango_printf(cairo, config->font, output->scale,
block->markup, "%s", block->full_text);
- pos += width;
+ x_pos += width;
if (block->border && block->border_right > 0) {
- pos += margin;
- render_sharp_line(cairo, block->border,
- pos - 0.5 * output->scale, output->scale,
- block->border_right, height);
- pos += block->border_right;
+ x_pos += margin;
+ render_sharp_line(cairo, border_color, x_pos, y_pos,
+ block->border_right * output->scale, render_height);
+ x_pos += block->border_right * output->scale;
}
if (!edge && block->separator) {
@@ -249,16 +275,14 @@ static uint32_t render_status_block(cairo_t *cairo,
cairo_set_source_u32(cairo, config->colors.separator);
}
if (config->sep_symbol) {
- offset = pos + (block->separator_block_width - sep_width) / 2;
+ offset = x_pos + (sep_block_width - sep_width) / 2;
cairo_move_to(cairo, offset, height / 2.0 - sep_height / 2.0);
pango_printf(cairo, config->font, output->scale, false,
"%s", config->sep_symbol);
} else {
cairo_set_line_width(cairo, 1);
- cairo_move_to(cairo,
- pos + block->separator_block_width / 2, margin);
- cairo_line_to(cairo,
- pos + block->separator_block_width / 2, height - margin);
+ cairo_move_to(cairo, x_pos + sep_block_width / 2, margin);
+ cairo_line_to(cairo, x_pos + sep_block_width / 2, height - margin);
cairo_stroke(cairo);
}
}
@@ -268,7 +292,7 @@ static uint32_t render_status_block(cairo_t *cairo,
static uint32_t render_status_line_i3bar(cairo_t *cairo,
struct swaybar_output *output, double *x) {
uint32_t max_height = 0;
- bool edge = true;
+ bool edge = *x == output->width * output->scale;
struct i3bar_block *block;
wl_list_for_each(block, &output->bar->status->blocks, link) {
uint32_t h = render_status_block(cairo, output, block, x, edge);
@@ -314,7 +338,8 @@ static uint32_t render_binding_mode_indicator(cairo_t *cairo,
uint32_t ideal_height = text_height + ws_vertical_padding * 2
+ border_width * 2;
uint32_t ideal_surface_height = ideal_height / output->scale;
- if (output->height < ideal_surface_height) {
+ if (!output->bar->config->height &&
+ output->height < ideal_surface_height) {
return ideal_surface_height;
}
uint32_t width = text_width + ws_horizontal_padding * 2 + border_width * 2;
@@ -342,22 +367,10 @@ static uint32_t render_binding_mode_indicator(cairo_t *cairo,
return output->height;
}
-static const char *strip_workspace_number(const char *ws_name) {
- size_t len = strlen(ws_name);
- for (size_t i = 0; i < len; ++i) {
- if (ws_name[i] < '0' || ws_name[i] > '9') {
- if (':' == ws_name[i] && i < len - 1 && i > 0) {
- return ws_name + i + 1;
- }
- return ws_name;
- }
- }
- return ws_name;
-}
-
-static enum hotspot_event_handling workspace_hotspot_callback(struct swaybar_output *output,
- int x, int y, enum x11_button button, void *data) {
- if (button != LEFT) {
+static enum hotspot_event_handling workspace_hotspot_callback(
+ struct swaybar_output *output, struct swaybar_hotspot *hotspot,
+ int x, int y, uint32_t button, void *data) {
+ if (button != BTN_LEFT) {
return HOTSPOT_PROCESS;
}
ipc_send_workspace_command(output->bar, (const char *)data);
@@ -368,11 +381,6 @@ static uint32_t render_workspace_button(cairo_t *cairo,
struct swaybar_output *output,
struct swaybar_workspace *ws, double *x) {
struct swaybar_config *config = output->bar->config;
- const char *name = ws->name;
- if (config->strip_workspace_numbers) {
- name = strip_workspace_number(ws->name);
- }
-
struct box_colors box_colors;
if (ws->urgent) {
box_colors = config->colors.urgent_workspace;
@@ -388,7 +396,7 @@ static uint32_t render_workspace_button(cairo_t *cairo,
int text_width, text_height;
get_text_size(cairo, config->font, &text_width, &text_height, NULL,
- output->scale, config->pango_markup, "%s", name);
+ output->scale, config->pango_markup, "%s", ws->label);
int ws_vertical_padding = WS_VERTICAL_PADDING * output->scale;
int ws_horizontal_padding = WS_HORIZONTAL_PADDING * output->scale;
@@ -397,7 +405,8 @@ static uint32_t render_workspace_button(cairo_t *cairo,
uint32_t ideal_height = ws_vertical_padding * 2 + text_height
+ border_width * 2;
uint32_t ideal_surface_height = ideal_height / output->scale;
- if (output->height < ideal_surface_height) {
+ if (!output->bar->config->height &&
+ output->height < ideal_surface_height) {
return ideal_surface_height;
}
@@ -421,7 +430,7 @@ static uint32_t render_workspace_button(cairo_t *cairo,
cairo_set_source_u32(cairo, box_colors.text);
cairo_move_to(cairo, *x + width / 2 - text_width / 2, (int)floor(text_y));
pango_printf(cairo, config->font, output->scale, config->pango_markup,
- "%s", name);
+ "%s", ws->label);
struct swaybar_hotspot *hotspot = calloc(1, sizeof(struct swaybar_hotspot));
hotspot->x = *x;
@@ -459,6 +468,12 @@ static uint32_t render_to_cairo(cairo_t *cairo, struct swaybar_output *output) {
* utilize the available space.
*/
double x = output->width * output->scale;
+#if HAVE_TRAY
+ if (bar->tray) {
+ uint32_t h = render_tray(cairo, output, &x);
+ max_height = h > max_height ? h : max_height;
+ }
+#endif
if (bar->status) {
uint32_t h = render_status_line(cairo, output, &x);
max_height = h > max_height ? h : max_height;
@@ -518,12 +533,17 @@ void render_frame(struct swaybar_output *output) {
cairo_restore(cairo);
uint32_t height = render_to_cairo(cairo, output);
int config_height = output->bar->config->height;
- if (config_height >= 0 && height < (uint32_t)config_height) {
+ if (config_height > 0) {
height = config_height;
}
if (height != output->height || output->width == 0) {
// Reconfigure surface
zwlr_layer_surface_v1_set_size(output->layer_surface, 0, height);
+ zwlr_layer_surface_v1_set_margin(output->layer_surface,
+ output->bar->config->gaps.top,
+ output->bar->config->gaps.right,
+ output->bar->config->gaps.bottom,
+ output->bar->config->gaps.left);
if (strcmp(output->bar->config->mode, "dock") == 0) {
zwlr_layer_surface_v1_set_exclusive_zone(output->layer_surface, height);
}
diff --git a/swaybar/status_line.c b/swaybar/status_line.c
index 744d2785..f0e2c300 100644
--- a/swaybar/status_line.c
+++ b/swaybar/status_line.c
@@ -12,7 +12,6 @@
#include "swaybar/config.h"
#include "swaybar/i3bar.h"
#include "swaybar/status_line.h"
-#include "readline.h"
static void status_line_close_fds(struct status_line *status) {
if (status->read_fd != -1) {
@@ -185,7 +184,6 @@ void status_line_free(struct status_line *status) {
}
free(status->read);
free(status->write);
- free((char*) status->text);
free(status->buffer);
free(status);
}
diff --git a/swaybar/tray/host.c b/swaybar/tray/host.c
new file mode 100644
index 00000000..cc8dd188
--- /dev/null
+++ b/swaybar/tray/host.c
@@ -0,0 +1,210 @@
+#define _POSIX_C_SOURCE 200809L
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include "swaybar/bar.h"
+#include "swaybar/tray/host.h"
+#include "swaybar/tray/item.h"
+#include "swaybar/tray/tray.h"
+#include "list.h"
+#include "log.h"
+
+static const char *watcher_path = "/StatusNotifierWatcher";
+
+static int cmp_sni_id(const void *item, const void *cmp_to) {
+ const struct swaybar_sni *sni = item;
+ return strcmp(sni->watcher_id, cmp_to);
+}
+
+static void add_sni(struct swaybar_tray *tray, char *id) {
+ int idx = list_seq_find(tray->items, cmp_sni_id, id);
+ if (idx == -1) {
+ wlr_log(WLR_INFO, "Registering Status Notifier Item '%s'", id);
+ struct swaybar_sni *sni = create_sni(id, tray);
+ if (sni) {
+ list_add(tray->items, sni);
+ }
+ }
+}
+
+static int handle_sni_registered(sd_bus_message *msg, void *data,
+ sd_bus_error *error) {
+ char *id;
+ int ret = sd_bus_message_read(msg, "s", &id);
+ if (ret < 0) {
+ wlr_log(WLR_ERROR, "Failed to parse register SNI message: %s", strerror(-ret));
+ }
+
+ struct swaybar_tray *tray = data;
+ add_sni(tray, id);
+
+ return ret;
+}
+
+static int handle_sni_unregistered(sd_bus_message *msg, void *data,
+ sd_bus_error *error) {
+ char *id;
+ int ret = sd_bus_message_read(msg, "s", &id);
+ if (ret < 0) {
+ wlr_log(WLR_ERROR, "Failed to parse unregister SNI message: %s", strerror(-ret));
+ }
+
+ struct swaybar_tray *tray = data;
+ int idx = list_seq_find(tray->items, cmp_sni_id, id);
+ if (idx != -1) {
+ wlr_log(WLR_INFO, "Unregistering Status Notifier Item '%s'", id);
+ destroy_sni(tray->items->items[idx]);
+ list_del(tray->items, idx);
+ set_bar_dirty(tray->bar);
+ }
+ return ret;
+}
+
+static int get_registered_snis_callback(sd_bus_message *msg, void *data,
+ sd_bus_error *error) {
+ if (sd_bus_message_is_method_error(msg, NULL)) {
+ sd_bus_error err = *sd_bus_message_get_error(msg);
+ wlr_log(WLR_ERROR, "Failed to get registered SNIs: %s", err.message);
+ return -sd_bus_error_get_errno(&err);
+ }
+
+ int ret = sd_bus_message_enter_container(msg, 'v', NULL);
+ if (ret < 0) {
+ wlr_log(WLR_ERROR, "Failed to read registered SNIs: %s", strerror(-ret));
+ return ret;
+ }
+
+ char **ids;
+ ret = sd_bus_message_read_strv(msg, &ids);
+ if (ret < 0) {
+ wlr_log(WLR_ERROR, "Failed to read registered SNIs: %s", strerror(-ret));
+ return ret;
+ }
+
+ if (ids) {
+ struct swaybar_tray *tray = data;
+ for (char **id = ids; *id; ++id) {
+ add_sni(tray, *id);
+ }
+ }
+
+ return ret;
+}
+
+static bool register_to_watcher(struct swaybar_host *host) {
+ // this is called asynchronously in case the watcher is owned by this process
+ int ret = sd_bus_call_method_async(host->tray->bus, NULL,
+ host->watcher_interface, watcher_path, host->watcher_interface,
+ "RegisterStatusNotifierHost", NULL, NULL, "s", host->service);
+ if (ret < 0) {
+ wlr_log(WLR_ERROR, "Failed to send register call: %s", strerror(-ret));
+ return false;
+ }
+
+ ret = sd_bus_call_method_async(host->tray->bus, NULL,
+ host->watcher_interface, watcher_path,
+ "org.freedesktop.DBus.Properties", "Get",
+ get_registered_snis_callback, host->tray, "ss",
+ host->watcher_interface, "RegisteredStatusNotifierItems");
+ if (ret < 0) {
+ wlr_log(WLR_ERROR, "Failed to get registered SNIs: %s", strerror(-ret));
+ }
+
+ return ret >= 0;
+}
+
+static int handle_new_watcher(sd_bus_message *msg,
+ void *data, sd_bus_error *error) {
+ char *service, *old_owner, *new_owner;
+ int ret = sd_bus_message_read(msg, "sss", &service, &old_owner, &new_owner);
+ if (ret < 0) {
+ wlr_log(WLR_ERROR, "Failed to parse owner change message: %s", strerror(-ret));
+ return ret;
+ }
+
+ if (!*old_owner) {
+ struct swaybar_host *host = data;
+ if (strcmp(service, host->watcher_interface) == 0) {
+ register_to_watcher(host);
+ }
+ }
+
+ return 0;
+}
+
+bool init_host(struct swaybar_host *host, char *protocol,
+ struct swaybar_tray *tray) {
+ size_t len = snprintf(NULL, 0, "org.%s.StatusNotifierWatcher", protocol) + 1;
+ host->watcher_interface = malloc(len);
+ if (!host->watcher_interface) {
+ return false;
+ }
+ snprintf(host->watcher_interface, len, "org.%s.StatusNotifierWatcher", protocol);
+
+ sd_bus_slot *reg_slot = NULL, *unreg_slot = NULL, *watcher_slot = NULL;
+ int ret = sd_bus_match_signal(tray->bus, &reg_slot, host->watcher_interface,
+ watcher_path, host->watcher_interface,
+ "StatusNotifierItemRegistered", handle_sni_registered, tray);
+ if (ret < 0) {
+ wlr_log(WLR_ERROR, "Failed to subscribe to registering events: %s",
+ strerror(-ret));
+ goto error;
+ }
+ ret = sd_bus_match_signal(tray->bus, &unreg_slot, host->watcher_interface,
+ watcher_path, host->watcher_interface,
+ "StatusNotifierItemUnregistered", handle_sni_unregistered, tray);
+ if (ret < 0) {
+ wlr_log(WLR_ERROR, "Failed to subscribe to unregistering events: %s",
+ strerror(-ret));
+ goto error;
+ }
+
+ ret = sd_bus_match_signal(tray->bus, &watcher_slot, "org.freedesktop.DBus",
+ "/org/freedesktop/DBus", "org.freedesktop.DBus", "NameOwnerChanged",
+ handle_new_watcher, host);
+ if (ret < 0) {
+ wlr_log(WLR_ERROR, "Failed to subscribe to unregistering events: %s",
+ strerror(-ret));
+ goto error;
+ }
+
+ pid_t pid = getpid();
+ size_t service_len = snprintf(NULL, 0, "org.%s.StatusNotifierHost-%d",
+ protocol, pid) + 1;
+ host->service = malloc(service_len);
+ if (!host->service) {
+ goto error;
+ }
+ snprintf(host->service, service_len, "org.%s.StatusNotifierHost-%d", protocol, pid);
+ ret = sd_bus_request_name(tray->bus, host->service, 0);
+ if (ret < 0) {
+ wlr_log(WLR_DEBUG, "Failed to acquire service name: %s", strerror(-ret));
+ goto error;
+ }
+
+ host->tray = tray;
+ if (!register_to_watcher(host)) {
+ goto error;
+ }
+
+ sd_bus_slot_set_floating(reg_slot, 1);
+ sd_bus_slot_set_floating(unreg_slot, 1);
+ sd_bus_slot_set_floating(watcher_slot, 1);
+
+ wlr_log(WLR_DEBUG, "Registered %s", host->service);
+ return true;
+error:
+ sd_bus_slot_unref(reg_slot);
+ sd_bus_slot_unref(unreg_slot);
+ sd_bus_slot_unref(watcher_slot);
+ finish_host(host);
+ return false;
+}
+
+void finish_host(struct swaybar_host *host) {
+ sd_bus_release_name(host->tray->bus, host->service);
+ free(host->service);
+ free(host->watcher_interface);
+}
diff --git a/swaybar/tray/icon.c b/swaybar/tray/icon.c
new file mode 100644
index 00000000..67805858
--- /dev/null
+++ b/swaybar/tray/icon.c
@@ -0,0 +1,462 @@
+#define _POSIX_C_SOURCE 200809L
+#include <ctype.h>
+#include <dirent.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <wordexp.h>
+#include "swaybar/tray/icon.h"
+#include "config.h"
+#include "list.h"
+#include "log.h"
+#include "stringop.h"
+
+static bool dir_exists(char *path) {
+ struct stat sb;
+ return stat(path, &sb) == 0 && S_ISDIR(sb.st_mode);
+}
+
+static list_t *get_basedirs(void) {
+ list_t *basedirs = create_list();
+ list_add(basedirs, strdup("$HOME/.icons")); // deprecated
+
+ char *data_home = getenv("XDG_DATA_HOME");
+ list_add(basedirs, strdup(data_home && *data_home ?
+ "$XDG_DATA_HOME/icons" : "$HOME/.local/share/icons"));
+
+ list_add(basedirs, strdup("/usr/share/pixmaps"));
+
+ char *data_dirs = getenv("XDG_DATA_DIRS");
+ if (!(data_dirs && *data_dirs)) {
+ data_dirs = "/usr/local/share:/usr/share";
+ }
+ data_dirs = strdup(data_dirs);
+ char *dir = strtok(data_dirs, ":");
+ do {
+ size_t path_len = snprintf(NULL, 0, "%s/icons", dir) + 1;
+ char *path = malloc(path_len);
+ snprintf(path, path_len, "%s/icons", dir);
+ list_add(basedirs, path);
+ } while ((dir = strtok(NULL, ":")));
+ free(data_dirs);
+
+ list_t *basedirs_expanded = create_list();
+ for (int i = 0; i < basedirs->length; ++i) {
+ wordexp_t p;
+ if (wordexp(basedirs->items[i], &p, WRDE_UNDEF) == 0) {
+ if (dir_exists(p.we_wordv[0])) {
+ list_add(basedirs_expanded, strdup(p.we_wordv[0]));
+ }
+ wordfree(&p);
+ }
+ }
+
+ list_free_items_and_destroy(basedirs);
+
+ return basedirs_expanded;
+}
+
+static void destroy_theme(struct icon_theme *theme) {
+ if (!theme) {
+ return;
+ }
+ free(theme->name);
+ free(theme->comment);
+ free(theme->inherits);
+ list_free_items_and_destroy(theme->directories);
+ free(theme->dir);
+
+ for (int i = 0; i < theme->subdirs->length; ++i) {
+ struct icon_theme_subdir *subdir = theme->subdirs->items[i];
+ free(subdir->name);
+ free(subdir);
+ }
+ list_free(theme->subdirs);
+ free(theme);
+}
+
+static int cmp_group(const void *item, const void *cmp_to) {
+ return strcmp(item, cmp_to);
+}
+
+static bool group_handler(char *old_group, char *new_group,
+ struct icon_theme *theme) {
+ if (!old_group) { // first group must be "Icon Theme"
+ return strcmp(new_group, "Icon Theme");
+ }
+
+ if (strcmp(old_group, "Icon Theme") == 0) {
+ if (!(theme->name && theme->comment && theme->directories)) {
+ return true;
+ }
+ } else {
+ if (theme->subdirs->length == 0) { // skip
+ return false;
+ }
+
+ struct icon_theme_subdir *subdir =
+ theme->subdirs->items[theme->subdirs->length - 1];
+ if (!subdir->size) return true;
+
+ switch (subdir->type) {
+ case FIXED: subdir->max_size = subdir->min_size = subdir->size;
+ break;
+ case SCALABLE: {
+ if (!subdir->max_size) subdir->max_size = subdir->size;
+ if (!subdir->min_size) subdir->min_size = subdir->size;
+ break;
+ }
+ case THRESHOLD:
+ subdir->max_size = subdir->size + subdir->threshold;
+ subdir->min_size = subdir->size - subdir->threshold;
+ }
+ }
+
+ if (new_group && list_seq_find(theme->directories, cmp_group, new_group) != -1) {
+ struct icon_theme_subdir *subdir = calloc(1, sizeof(struct icon_theme_subdir));
+ if (!subdir) {
+ return true;
+ }
+ subdir->name = strdup(new_group);
+ subdir->threshold = 2;
+ list_add(theme->subdirs, subdir);
+ }
+
+ return false;
+}
+
+static int entry_handler(char *group, char *key, char *value,
+ struct icon_theme *theme) {
+ if (strcmp(group, "Icon Theme") == 0) {
+ if (strcmp(key, "Name") == 0) {
+ theme->name = strdup(value);
+ } else if (strcmp(key, "Comment") == 0) {
+ theme->comment = strdup(value);
+ } else if (strcmp(key, "Inherists") == 0) {
+ theme->inherits = strdup(value);
+ } else if (strcmp(key, "Directories") == 0) {
+ theme->directories = split_string(value, ",");
+ } // Ignored: ScaledDirectories, Hidden, Example
+ } else {
+ if (theme->subdirs->length == 0) { // skip
+ return false;
+ }
+
+ struct icon_theme_subdir *subdir =
+ theme->subdirs->items[theme->subdirs->length - 1];
+ if (strcmp(subdir->name, group) != 0) { // skip
+ return false;
+ }
+
+ char *end;
+ int n = strtol(value, &end, 10);
+ if (strcmp(key, "Size") == 0) {
+ subdir->size = n;
+ return *end != '\0';
+ } else if (strcmp(key, "Type") == 0) {
+ if (strcmp(value, "Fixed") == 0) {
+ subdir->type = FIXED;
+ } else if (strcmp(value, "Scalable") == 0) {
+ subdir->type = SCALABLE;
+ } else if (strcmp(value, "Threshold") == 0) {
+ subdir->type = THRESHOLD;
+ } else {
+ return true;
+ }
+ } else if (strcmp(key, "MaxSize") == 0) {
+ subdir->max_size = n;
+ return *end != '\0';
+ } else if (strcmp(key, "MinSize") == 0) {
+ subdir->min_size = n;
+ return *end != '\0';
+ } else if (strcmp(key, "Threshold") == 0) {
+ subdir->threshold = n;
+ return *end != '\0';
+ } // Ignored: Scale, Applications
+ }
+ return false;
+}
+
+/*
+ * This is a Freedesktop Desktop Entry parser (essentially INI)
+ * It calls entry_handler for every entry
+ * and group_handler between every group (as well as at both ends)
+ * Handlers return whether an error occured, which stops parsing
+ */
+static struct icon_theme *read_theme_file(char *basedir, char *theme_name) {
+ // look for index.theme file
+ size_t path_len = snprintf(NULL, 0, "%s/%s/index.theme", basedir,
+ theme_name) + 1;
+ char *path = malloc(path_len);
+ snprintf(path, path_len, "%s/%s/index.theme", basedir, theme_name);
+ FILE *theme_file = fopen(path, "r");
+ if (!theme_file) {
+ return NULL;
+ }
+
+ struct icon_theme *theme = calloc(1, sizeof(struct icon_theme));
+ if (!theme) {
+ return NULL;
+ }
+ theme->subdirs = create_list();
+
+ bool error = false;
+ char *group = NULL;
+ char *full_line = NULL;
+ size_t full_len = 0;
+ ssize_t nread;
+ while ((nread = getline(&full_line, &full_len, theme_file)) != -1) {
+ char *line = full_line - 1;
+ while (isspace(*++line)) {} // remove leading whitespace
+ if (!*line || line[0] == '#') continue; // ignore blank lines & comments
+
+ int len = nread - (line - full_line);
+ while (isspace(line[--len])) {}
+ line[++len] = '\0'; // remove trailing whitespace
+
+ if (line[0] == '[') { // group header
+ // check well-formed
+ if (line[--len] != ']') {
+ error = true;
+ break;
+ }
+ int i = 1;
+ for (; !iscntrl(line[i]) && line[i] != '[' && line[i] != ']'; ++i) {}
+ if (i < len) {
+ error = true;
+ break;
+ }
+
+ // call handler
+ line[len] = '\0';
+ error = group_handler(group, &line[1], theme);
+ if (error) {
+ break;
+ }
+ free(group);
+ group = strdup(&line[1]);
+ } else { // key-value pair
+ // check well-formed
+ int eok = 0;
+ for (; isalnum(line[eok]) || line[eok] == '-'; ++eok) {} // TODO locale?
+ int i = eok - 1;
+ while (isspace(line[++i])) {}
+ if (line[i] != '=') {
+ error = true;
+ break;
+ }
+
+ line[eok] = '\0'; // split into key-value pair
+ char *value = &line[i];
+ while (isspace(*++value)) {}
+ // TODO unescape value
+ error = entry_handler(group, line, value, theme);
+ if (error) {
+ break;
+ }
+ }
+ }
+
+ if (!error && group) {
+ error = group_handler(group, NULL, theme);
+ }
+
+ free(group);
+ free(full_line);
+ fclose(theme_file);
+
+ if (!error) {
+ theme->dir = strdup(theme_name);
+ return theme;
+ } else {
+ destroy_theme(theme);
+ return NULL;
+ }
+}
+
+static list_t *load_themes_in_dir(char *basedir) {
+ DIR *dir;
+ if (!(dir = opendir(basedir))) {
+ return NULL;
+ }
+
+ list_t *themes = create_list();
+ struct dirent *entry;
+ while ((entry = readdir(dir))) {
+ if (entry->d_name[0] == '.') continue;
+
+ struct icon_theme *theme = read_theme_file(basedir, entry->d_name);
+ if (theme) {
+ list_add(themes, theme);
+ }
+ }
+ return themes;
+}
+
+void init_themes(list_t **themes, list_t **basedirs) {
+ *basedirs = get_basedirs();
+
+ *themes = create_list();
+ for (int i = 0; i < (*basedirs)->length; ++i) {
+ list_t *dir_themes = load_themes_in_dir((*basedirs)->items[i]);
+ list_cat(*themes, dir_themes);
+ list_free(dir_themes);
+ }
+
+ list_t *theme_names = create_list();
+ for (int i = 0; i < (*themes)->length; ++i) {
+ struct icon_theme *theme = (*themes)->items[i];
+ list_add(theme_names, theme->name);
+ }
+ wlr_log(WLR_DEBUG, "Loaded themes: %s", join_list(theme_names, ", "));
+ list_free(theme_names);
+}
+
+void finish_themes(list_t *themes, list_t *basedirs) {
+ for (int i = 0; i < themes->length; ++i) {
+ destroy_theme(themes->items[i]);
+ }
+ list_free(themes);
+ list_free_items_and_destroy(basedirs);
+}
+
+static char *find_icon_in_subdir(char *name, char *basedir, char *theme,
+ char *subdir) {
+ static const char *extensions[] = {
+#if HAVE_GDK_PIXBUF
+ "svg",
+#endif
+ "png",
+#if HAVE_GDK_PIXBUF
+ "xpm"
+#endif
+ };
+
+ size_t path_len = snprintf(NULL, 0, "%s/%s/%s/%s.EXT", basedir, theme,
+ subdir, name) + 1;
+ char *path = malloc(path_len);
+
+ for (size_t i = 0; i < sizeof(extensions) / sizeof(*extensions); ++i) {
+ snprintf(path, path_len, "%s/%s/%s/%s.%s", basedir, theme, subdir,
+ name, extensions[i]);
+ if (access(path, R_OK) == 0) {
+ return path;
+ }
+ }
+
+ free(path);
+ return NULL;
+}
+
+static bool theme_exists_in_basedir(char *theme, char *basedir) {
+ size_t path_len = snprintf(NULL, 0, "%s/%s", basedir, theme) + 1;
+ char *path = malloc(path_len);
+ snprintf(path, path_len, "%s/%s", basedir, theme);
+ bool ret = dir_exists(path);
+ free(path);
+ return ret;
+}
+
+static char *find_icon_with_theme(list_t *basedirs, list_t *themes, char *name,
+ int size, char *theme_name, int *min_size, int *max_size) {
+ struct icon_theme *theme = NULL;
+ for (int i = 0; i < themes->length; ++i) {
+ theme = themes->items[i];
+ if (strcmp(theme->name, theme_name) == 0) {
+ break;
+ }
+ theme = NULL;
+ }
+ if (!theme) return NULL;
+
+ char *icon = NULL;
+ for (int i = 0; i < basedirs->length; ++i) {
+ if (!theme_exists_in_basedir(theme->dir, basedirs->items[i])) {
+ continue;
+ }
+ // search backwards to hopefully hit scalable/larger icons first
+ for (int j = theme->subdirs->length - 1; j >= 0; --j) {
+ struct icon_theme_subdir *subdir = theme->subdirs->items[j];
+ if (size >= subdir->min_size && size <= subdir->max_size) {
+ if ((icon = find_icon_in_subdir(name, basedirs->items[i],
+ theme->dir, subdir->name))) {
+ *min_size = subdir->min_size;
+ *max_size = subdir->max_size;
+ return icon;
+ }
+ }
+ }
+ }
+
+ // inexact match
+ unsigned smallest_error = -1; // UINT_MAX
+ for (int i = 0; i < basedirs->length; ++i) {
+ if (!theme_exists_in_basedir(theme->dir, basedirs->items[i])) {
+ continue;
+ }
+ for (int j = theme->subdirs->length - 1; j >= 0; --j) {
+ struct icon_theme_subdir *subdir = theme->subdirs->items[j];
+ unsigned error = (size > subdir->max_size ? size - subdir->max_size : 0)
+ + (size < subdir->min_size ? subdir->min_size - size : 0);
+ if (error < smallest_error) {
+ char *test_icon = find_icon_in_subdir(name, basedirs->items[i],
+ theme->dir, subdir->name);
+ if (test_icon) {
+ icon = test_icon;
+ smallest_error = error;
+ *min_size = subdir->min_size;
+ *max_size = subdir->max_size;
+ }
+ }
+ }
+ }
+
+ if (!icon && theme->inherits) {
+ icon = find_icon_with_theme(basedirs, themes, name, size,
+ theme->inherits, min_size, max_size);
+ }
+
+ return icon;
+}
+
+char *find_icon_in_dir(char *name, char *dir, int *min_size, int *max_size) {
+ char *icon = find_icon_in_subdir(name, dir, "", "");
+ if (icon) {
+ *min_size = 1;
+ *max_size = 512;
+ }
+ return icon;
+
+}
+
+static char *find_fallback_icon(list_t *basedirs, char *name, int *min_size,
+ int *max_size) {
+ for (int i = 0; i < basedirs->length; ++i) {
+ char *icon = find_icon_in_dir(name, basedirs->items[i], min_size, max_size);
+ if (icon) {
+ return icon;
+ }
+ }
+ return NULL;
+}
+
+char *find_icon(list_t *themes, list_t *basedirs, char *name, int size,
+ char *theme, int *min_size, int *max_size) {
+ // TODO https://specifications.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html#implementation_notes
+ char *icon = NULL;
+ if (theme) {
+ icon = find_icon_with_theme(basedirs, themes, name, size, theme,
+ min_size, max_size);
+ }
+ if (!icon) {
+ icon = find_icon_with_theme(basedirs, themes, name, size, "Hicolor",
+ min_size, max_size);
+ }
+ if (!icon) {
+ icon = find_fallback_icon(basedirs, name, min_size, max_size);
+ }
+ return icon;
+}
diff --git a/swaybar/tray/item.c b/swaybar/tray/item.c
new file mode 100644
index 00000000..0833dcb9
--- /dev/null
+++ b/swaybar/tray/item.c
@@ -0,0 +1,472 @@
+#define _POSIX_C_SOURCE 200809L
+#include <cairo.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include "swaybar/bar.h"
+#include "swaybar/config.h"
+#include "swaybar/input.h"
+#include "swaybar/tray/host.h"
+#include "swaybar/tray/icon.h"
+#include "swaybar/tray/item.h"
+#include "swaybar/tray/tray.h"
+#include "background-image.h"
+#include "cairo.h"
+#include "list.h"
+#include "log.h"
+#include "wlr-layer-shell-unstable-v1-client-protocol.h"
+
+// TODO menu
+
+static bool sni_ready(struct swaybar_sni *sni) {
+ return sni->status && (sni->status[0] == 'N' ? // NeedsAttention
+ sni->attention_icon_name || sni->attention_icon_pixmap :
+ sni->icon_name || sni->icon_pixmap);
+}
+
+static void set_sni_dirty(struct swaybar_sni *sni) {
+ if (sni_ready(sni)) {
+ sni->min_size = sni->max_size = 0; // invalidate previous icon
+ set_bar_dirty(sni->tray->bar);
+ }
+}
+
+static int read_pixmap(sd_bus_message *msg, struct swaybar_sni *sni,
+ const char *prop, list_t **dest) {
+ int ret = sd_bus_message_enter_container(msg, 'a', "(iiay)");
+ if (ret < 0) {
+ wlr_log(WLR_ERROR, "%s %s: %s", sni->watcher_id, prop, strerror(-ret));
+ return ret;
+ }
+
+ if (sd_bus_message_at_end(msg, 0)) {
+ wlr_log(WLR_DEBUG, "%s %s no. of icons = 0", sni->watcher_id, prop);
+ return ret;
+ }
+
+ list_t *pixmaps = create_list();
+ if (!pixmaps) {
+ return -12; // -ENOMEM
+ }
+
+ while (!sd_bus_message_at_end(msg, 0)) {
+ ret = sd_bus_message_enter_container(msg, 'r', "iiay");
+ if (ret < 0) {
+ wlr_log(WLR_ERROR, "%s %s: %s", sni->watcher_id, prop, strerror(-ret));
+ goto error;
+ }
+
+ int size;
+ ret = sd_bus_message_read(msg, "ii", NULL, &size);
+ if (ret < 0) {
+ wlr_log(WLR_ERROR, "%s %s: %s", sni->watcher_id, prop, strerror(-ret));
+ goto error;
+ }
+
+ const void *pixels;
+ size_t npixels;
+ ret = sd_bus_message_read_array(msg, 'y', &pixels, &npixels);
+ if (ret < 0) {
+ wlr_log(WLR_ERROR, "%s %s: %s", sni->watcher_id, prop, strerror(-ret));
+ goto error;
+ }
+
+ struct swaybar_pixmap *pixmap =
+ malloc(sizeof(struct swaybar_pixmap) + npixels);
+ pixmap->size = size;
+ memcpy(pixmap->pixels, pixels, npixels);
+ list_add(pixmaps, pixmap);
+
+ sd_bus_message_exit_container(msg);
+ }
+ list_free_items_and_destroy(*dest);
+ *dest = pixmaps;
+ wlr_log(WLR_DEBUG, "%s %s no. of icons = %d", sni->watcher_id, prop,
+ pixmaps->length);
+
+ return ret;
+error:
+ list_free_items_and_destroy(pixmaps);
+ return ret;
+}
+
+struct get_property_data {
+ struct swaybar_sni *sni;
+ const char *prop;
+ const char *type;
+ void *dest;
+};
+
+static int get_property_callback(sd_bus_message *msg, void *data,
+ sd_bus_error *error) {
+ struct get_property_data *d = data;
+ struct swaybar_sni *sni = d->sni;
+ const char *prop = d->prop;
+ const char *type = d->type;
+ void *dest = d->dest;
+
+ int ret;
+ if (sd_bus_message_is_method_error(msg, NULL)) {
+ wlr_log(WLR_ERROR, "%s %s: %s", sni->watcher_id, prop,
+ sd_bus_message_get_error(msg)->message);
+ ret = sd_bus_message_get_errno(msg);
+ goto cleanup;
+ }
+
+ ret = sd_bus_message_enter_container(msg, 'v', type);
+ if (ret < 0) {
+ wlr_log(WLR_ERROR, "%s %s: %s", sni->watcher_id, prop, strerror(-ret));
+ goto cleanup;
+ }
+
+ if (!type) {
+ ret = read_pixmap(msg, sni, prop, dest);
+ if (ret < 0) {
+ goto cleanup;
+ }
+ } else {
+ if (*type == 's' || *type == 'o') {
+ free(*(char **)dest);
+ }
+
+ ret = sd_bus_message_read(msg, type, dest);
+ if (ret < 0) {
+ wlr_log(WLR_ERROR, "%s %s: %s", sni->watcher_id, prop, strerror(-ret));
+ goto cleanup;
+ }
+
+ if (*type == 's' || *type == 'o') {
+ char **str = dest;
+ *str = strdup(*str);
+ wlr_log(WLR_DEBUG, "%s %s = '%s'", sni->watcher_id, prop, *str);
+ } else if (*type == 'b') {
+ wlr_log(WLR_DEBUG, "%s %s = %s", sni->watcher_id, prop,
+ *(bool *)dest ? "true" : "false");
+ }
+ }
+
+ if (strcmp(prop, "Status") == 0 || (sni->status && (sni->status[0] == 'N' ?
+ prop[0] == 'A' : strncmp(prop, "Icon", 4) == 0))) {
+ set_sni_dirty(sni);
+ }
+cleanup:
+ free(data);
+ return ret;
+}
+
+static void sni_get_property_async(struct swaybar_sni *sni, const char *prop,
+ const char *type, void *dest) {
+ struct get_property_data *data = malloc(sizeof(struct get_property_data));
+ data->sni = sni;
+ data->prop = prop;
+ data->type = type;
+ data->dest = dest;
+ int ret = sd_bus_call_method_async(sni->tray->bus, NULL, sni->service,
+ sni->path, "org.freedesktop.DBus.Properties", "Get",
+ get_property_callback, data, "ss", sni->interface, prop);
+ if (ret < 0) {
+ wlr_log(WLR_ERROR, "%s %s: %s", sni->watcher_id, prop, strerror(-ret));
+ }
+}
+
+/*
+ * There is a quirk in sd-bus that in some systems, it is unable to get the
+ * well-known names on the bus, so it cannot identify if an incoming signal,
+ * which uses the sender's unique name, actually matches the callback's matching
+ * sender if the callback uses a well-known name, in which case it just calls
+ * the callback and hopes for the best, resulting in false positives. In the
+ * case of NewIcon & NewAttentionIcon, this doesn't affect anything, but it
+ * means that for NewStatus, if the SNI does not definitely match the sender,
+ * then the safe thing to do is to query the status independently.
+ * This function returns 1 if the SNI definitely matches the signal sender,
+ * which is returned by the calling function to indicate that signal matching
+ * can stop since it has already found the required callback, otherwise, it
+ * returns 0, which allows matching to continue.
+ */
+static int sni_check_msg_sender(struct swaybar_sni *sni, sd_bus_message *msg,
+ const char *signal) {
+ bool has_well_known_names =
+ sd_bus_creds_get_mask(sd_bus_message_get_creds(msg)) & SD_BUS_CREDS_WELL_KNOWN_NAMES;
+ if (sni->service[0] == ':' || has_well_known_names) {
+ wlr_log(WLR_DEBUG, "%s has new %s", sni->watcher_id, signal);
+ return 1;
+ } else {
+ wlr_log(WLR_DEBUG, "%s may have new %s", sni->watcher_id, signal);
+ return 0;
+ }
+}
+
+static int handle_new_icon(sd_bus_message *msg, void *data, sd_bus_error *error) {
+ struct swaybar_sni *sni = data;
+ sni_get_property_async(sni, "IconName", "s", &sni->icon_name);
+ sni_get_property_async(sni, "IconPixmap", NULL, &sni->icon_pixmap);
+ return sni_check_msg_sender(sni, msg, "icon");
+}
+
+static int handle_new_attention_icon(sd_bus_message *msg, void *data,
+ sd_bus_error *error) {
+ struct swaybar_sni *sni = data;
+ sni_get_property_async(sni, "AttentionIconName", "s", &sni->attention_icon_name);
+ sni_get_property_async(sni, "AttentionIconPixmap", NULL, &sni->attention_icon_pixmap);
+ return sni_check_msg_sender(sni, msg, "attention icon");
+}
+
+static int handle_new_status(sd_bus_message *msg, void *data, sd_bus_error *error) {
+ struct swaybar_sni *sni = data;
+ int ret = sni_check_msg_sender(sni, msg, "status");
+ if (ret == 1) {
+ char *status;
+ int r = sd_bus_message_read(msg, "s", &status);
+ if (r < 0) {
+ wlr_log(WLR_ERROR, "%s new status error: %s", sni->watcher_id, strerror(-ret));
+ ret = r;
+ } else {
+ free(sni->status);
+ sni->status = strdup(status);
+ wlr_log(WLR_DEBUG, "%s has new status = '%s'", sni->watcher_id, status);
+ set_sni_dirty(sni);
+ }
+ } else {
+ sni_get_property_async(sni, "Status", "s", &sni->status);
+ }
+
+ return ret;
+}
+
+static void sni_match_signal(struct swaybar_sni *sni, sd_bus_slot **slot,
+ char *signal, sd_bus_message_handler_t callback) {
+ int ret = sd_bus_match_signal(sni->tray->bus, slot, sni->service, sni->path,
+ sni->interface, signal, callback, sni);
+ if (ret < 0) {
+ wlr_log(WLR_ERROR, "Failed to subscribe to signal %s: %s", signal,
+ strerror(-ret));
+ }
+}
+
+struct swaybar_sni *create_sni(char *id, struct swaybar_tray *tray) {
+ struct swaybar_sni *sni = calloc(1, sizeof(struct swaybar_sni));
+ if (!sni) {
+ return NULL;
+ }
+ sni->tray = tray;
+ sni->watcher_id = strdup(id);
+ char *path_ptr = strchr(id, '/');
+ if (!path_ptr) {
+ sni->service = strdup(id);
+ sni->path = strdup("/StatusNotifierItem");
+ sni->interface = "org.freedesktop.StatusNotifierItem";
+ } else {
+ sni->service = strndup(id, path_ptr - id);
+ sni->path = strdup(path_ptr);
+ sni->interface = "org.kde.StatusNotifierItem";
+ sni_get_property_async(sni, "IconThemePath", "s", &sni->icon_theme_path);
+ }
+
+ // Ignored: Category, Id, Title, WindowId, OverlayIconName,
+ // OverlayIconPixmap, AttentionMovieName, ToolTip
+ sni_get_property_async(sni, "Status", "s", &sni->status);
+ sni_get_property_async(sni, "IconName", "s", &sni->icon_name);
+ sni_get_property_async(sni, "IconPixmap", NULL, &sni->icon_pixmap);
+ sni_get_property_async(sni, "AttentionIconName", "s", &sni->attention_icon_name);
+ sni_get_property_async(sni, "AttentionIconPixmap", NULL, &sni->attention_icon_pixmap);
+ sni_get_property_async(sni, "ItemIsMenu", "b", &sni->item_is_menu);
+ sni_get_property_async(sni, "Menu", "o", &sni->menu);
+
+ sni_match_signal(sni, &sni->new_icon_slot, "NewIcon", handle_new_icon);
+ sni_match_signal(sni, &sni->new_attention_icon_slot, "NewAttentionIcon",
+ handle_new_attention_icon);
+ sni_match_signal(sni, &sni->new_status_slot, "NewStatus", handle_new_status);
+
+ return sni;
+}
+
+void destroy_sni(struct swaybar_sni *sni) {
+ if (!sni) {
+ return;
+ }
+
+ sd_bus_slot_unref(sni->new_icon_slot);
+ sd_bus_slot_unref(sni->new_attention_icon_slot);
+ sd_bus_slot_unref(sni->new_status_slot);
+
+ free(sni->watcher_id);
+ free(sni->service);
+ free(sni->path);
+ free(sni->status);
+ free(sni->icon_name);
+ free(sni->icon_pixmap);
+ free(sni->attention_icon_name);
+ free(sni->menu);
+ free(sni);
+}
+
+static void handle_click(struct swaybar_sni *sni, int x, int y,
+ enum x11_button button, int delta) {
+ const char *method = sni->tray->bar->config->tray_bindings[button];
+ if (!method) {
+ static const char *default_bindings[10] = {
+ "nop",
+ "Activate",
+ "SecondaryActivate",
+ "ContextMenu",
+ "ScrollUp",
+ "ScrollDown",
+ "ScrollLeft",
+ "ScrollRight",
+ "nop",
+ "nop"
+ };
+ method = default_bindings[button];
+ }
+ if (strcmp(method, "nop") == 0) {
+ return;
+ }
+ if (sni->item_is_menu && strcmp(method, "Activate") == 0) {
+ method = "ContextMenu";
+ }
+
+ if (strncmp(method, "Scroll", strlen("Scroll")) == 0) {
+ char dir = method[strlen("Scroll")];
+ char *orientation = (dir = 'U' || dir == 'D') ? "vertical" : "horizontal";
+ int sign = (dir == 'U' || dir == 'L') ? -1 : 1;
+
+ sd_bus_call_method_async(sni->tray->bus, NULL, sni->service, sni->path,
+ sni->interface, "Scroll", NULL, NULL, "is", delta*sign, orientation);
+ } else {
+ sd_bus_call_method_async(sni->tray->bus, NULL, sni->service, sni->path,
+ sni->interface, method, NULL, NULL, "ii", x, y);
+ }
+}
+
+static int cmp_sni_id(const void *item, const void *cmp_to) {
+ const struct swaybar_sni *sni = item;
+ return strcmp(sni->watcher_id, cmp_to);
+}
+
+static enum hotspot_event_handling icon_hotspot_callback(
+ struct swaybar_output *output, struct swaybar_hotspot *hotspot,
+ int x, int y, enum x11_button button, void *data) {
+ wlr_log(WLR_DEBUG, "Clicked on %s", (char *)data);
+
+ struct swaybar_tray *tray = output->bar->tray;
+ int idx = list_seq_find(tray->items, cmp_sni_id, data);
+
+ if (idx != -1) {
+ struct swaybar_sni *sni = tray->items->items[idx];
+ // guess global position since wayland doesn't expose it
+ struct swaybar_config *config = tray->bar->config;
+ int global_x = output->output_x + config->gaps.left + x;
+ bool top_bar = config->position & ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP;
+ int global_y = output->output_y + (top_bar ? config->gaps.top + y:
+ (int) output->output_height - config->gaps.bottom - y);
+
+ wlr_log(WLR_DEBUG, "Guessing click position at (%d, %d)", global_x, global_y);
+ handle_click(sni, global_x, global_y, button, 1); // TODO get delta from event
+ return HOTSPOT_IGNORE;
+ } else {
+ wlr_log(WLR_DEBUG, "but it doesn't exist");
+ }
+
+ return HOTSPOT_PROCESS;
+}
+
+uint32_t render_sni(cairo_t *cairo, struct swaybar_output *output, double *x,
+ struct swaybar_sni *sni) {
+ uint32_t height = output->height * output->scale;
+ int padding = output->bar->config->tray_padding;
+ int ideal_size = height - 2*padding;
+ if ((ideal_size < sni->min_size || ideal_size > sni->max_size) && sni_ready(sni)) {
+ bool icon_found = false;
+ char *icon_name = sni->status[0] == 'N' ?
+ sni->attention_icon_name : sni->icon_name;
+ if (icon_name) {
+ char *icon_path = find_icon(sni->tray->themes, sni->tray->basedirs,
+ icon_name, ideal_size, output->bar->config->icon_theme,
+ &sni->min_size, &sni->max_size);
+ if (!icon_path && sni->icon_theme_path) {
+ icon_path = find_icon_in_dir(icon_name, sni->icon_theme_path,
+ &sni->min_size, &sni->max_size);
+ }
+ if (icon_path) {
+ cairo_surface_destroy(sni->icon);
+ sni->icon = load_background_image(icon_path);
+ free(icon_path);
+ icon_found = true;
+ }
+ }
+ if (!icon_found) {
+ list_t *pixmaps = sni->status[0] == 'N' ?
+ sni->attention_icon_pixmap : sni->icon_pixmap;
+ if (pixmaps) {
+ int idx = -1;
+ unsigned smallest_error = -1; // UINT_MAX
+ for (int i = 0; i < pixmaps->length; ++i) {
+ struct swaybar_pixmap *pixmap = pixmaps->items[i];
+ unsigned error = (ideal_size - pixmap->size) *
+ (ideal_size < pixmap->size ? -1 : 1);
+ if (error < smallest_error) {
+ smallest_error = error;
+ idx = i;
+ }
+ }
+ struct swaybar_pixmap *pixmap = pixmaps->items[idx];
+ cairo_surface_destroy(sni->icon);
+ sni->icon = cairo_image_surface_create_for_data(pixmap->pixels,
+ CAIRO_FORMAT_ARGB32, pixmap->size, pixmap->size,
+ cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, pixmap->size));
+ }
+ }
+ }
+
+ int icon_size;
+ cairo_surface_t *icon;
+ if (sni->icon) {
+ int actual_size = cairo_image_surface_get_height(sni->icon);
+ icon_size = actual_size < ideal_size ?
+ actual_size*(ideal_size/actual_size) : ideal_size;
+ icon = cairo_image_surface_scale(sni->icon, icon_size, icon_size);
+ } else { // draw a :(
+ icon_size = ideal_size*0.8;
+ icon = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, icon_size, icon_size);
+ cairo_t *cairo_icon = cairo_create(icon);
+ cairo_set_source_u32(cairo_icon, 0xFF0000FF);
+ cairo_translate(cairo_icon, icon_size/2, icon_size/2);
+ cairo_scale(cairo_icon, icon_size/2, icon_size/2);
+ cairo_arc(cairo_icon, 0, 0, 1, 0, 7);
+ cairo_fill(cairo_icon);
+ cairo_set_operator(cairo_icon, CAIRO_OPERATOR_CLEAR);
+ cairo_arc(cairo_icon, 0.35, -0.3, 0.1, 0, 7);
+ cairo_fill(cairo_icon);
+ cairo_arc(cairo_icon, -0.35, -0.3, 0.1, 0, 7);
+ cairo_fill(cairo_icon);
+ cairo_arc(cairo_icon, 0, 0.75, 0.5, 3.71238898038469, 5.71238898038469);
+ cairo_set_line_width(cairo_icon, 0.1);
+ cairo_stroke(cairo_icon);
+ cairo_destroy(cairo_icon);
+ }
+
+ int padded_size = icon_size + 2*padding;
+ *x -= padded_size;
+ int y = floor((height - padded_size) / 2.0);
+
+ cairo_operator_t op = cairo_get_operator(cairo);
+ cairo_set_operator(cairo, CAIRO_OPERATOR_OVER);
+ cairo_set_source_surface(cairo, icon, *x + padding, y + padding);
+ cairo_rectangle(cairo, *x, y, padded_size, padded_size);
+ cairo_fill(cairo);
+ cairo_set_operator(cairo, op);
+
+ cairo_surface_destroy(icon);
+
+ struct swaybar_hotspot *hotspot = calloc(1, sizeof(struct swaybar_hotspot));
+ hotspot->x = *x;
+ hotspot->y = 0;
+ hotspot->width = height;
+ hotspot->height = height;
+ hotspot->callback = icon_hotspot_callback;
+ hotspot->destroy = free;
+ hotspot->data = strdup(sni->watcher_id);
+ wl_list_insert(&output->hotspots, &hotspot->link);
+
+ return output->height;
+}
diff --git a/swaybar/tray/tray.c b/swaybar/tray/tray.c
new file mode 100644
index 00000000..d5d0c84e
--- /dev/null
+++ b/swaybar/tray/tray.c
@@ -0,0 +1,131 @@
+#include <cairo.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include "swaybar/config.h"
+#include "swaybar/bar.h"
+#include "swaybar/tray/icon.h"
+#include "swaybar/tray/host.h"
+#include "swaybar/tray/item.h"
+#include "swaybar/tray/tray.h"
+#include "swaybar/tray/watcher.h"
+#include "list.h"
+#include "log.h"
+
+static int handle_lost_watcher(sd_bus_message *msg,
+ void *data, sd_bus_error *error) {
+ char *service, *old_owner, *new_owner;
+ int ret = sd_bus_message_read(msg, "sss", &service, &old_owner, &new_owner);
+ if (ret < 0) {
+ wlr_log(WLR_ERROR, "Failed to parse owner change message: %s", strerror(-ret));
+ return ret;
+ }
+
+ if (!*new_owner) {
+ struct swaybar_tray *tray = data;
+ if (strcmp(service, "org.freedesktop.StatusNotifierWatcher") == 0) {
+ tray->watcher_xdg = create_watcher("freedesktop", tray->bus);
+ } else if (strcmp(service, "org.kde.StatusNotifierWatcher") == 0) {
+ tray->watcher_kde = create_watcher("kde", tray->bus);
+ }
+ }
+
+ return 0;
+}
+
+struct swaybar_tray *create_tray(struct swaybar *bar) {
+ wlr_log(WLR_DEBUG, "Initializing tray");
+
+ sd_bus *bus;
+ int ret = sd_bus_open_user(&bus);
+ if (ret < 0) {
+ wlr_log(WLR_ERROR, "Failed to connect to user bus: %s", strerror(-ret));
+ return NULL;
+ }
+
+ struct swaybar_tray *tray = calloc(1, sizeof(struct swaybar_tray));
+ if (!tray) {
+ return NULL;
+ }
+ tray->bar = bar;
+ tray->bus = bus;
+ tray->fd = sd_bus_get_fd(tray->bus);
+
+ tray->watcher_xdg = create_watcher("freedesktop", tray->bus);
+ tray->watcher_kde = create_watcher("kde", tray->bus);
+
+ ret = sd_bus_match_signal(bus, NULL, "org.freedesktop.DBus",
+ "/org/freedesktop/DBus", "org.freedesktop.DBus",
+ "NameOwnerChanged", handle_lost_watcher, tray);
+ if (ret < 0) {
+ wlr_log(WLR_ERROR, "Failed to subscribe to unregistering events: %s",
+ strerror(-ret));
+ }
+
+ tray->items = create_list();
+
+ init_host(&tray->host_xdg, "freedesktop", tray);
+ init_host(&tray->host_kde, "kde", tray);
+
+ init_themes(&tray->themes, &tray->basedirs);
+
+ return tray;
+}
+
+void destroy_tray(struct swaybar_tray *tray) {
+ if (!tray) {
+ return;
+ }
+ finish_host(&tray->host_xdg);
+ finish_host(&tray->host_kde);
+ for (int i = 0; i < tray->items->length; ++i) {
+ destroy_sni(tray->items->items[i]);
+ }
+ list_free(tray->items);
+ destroy_watcher(tray->watcher_xdg);
+ destroy_watcher(tray->watcher_kde);
+ sd_bus_flush_close_unref(tray->bus);
+ finish_themes(tray->themes, tray->basedirs);
+ free(tray);
+}
+
+void tray_in(int fd, short mask, void *data) {
+ sd_bus *bus = data;
+ int ret;
+ while ((ret = sd_bus_process(bus, NULL)) > 0) {
+ // This space intentionally left blank
+ }
+ if (ret < 0) {
+ wlr_log(WLR_ERROR, "Failed to process bus: %s", strerror(-ret));
+ }
+}
+
+static int cmp_output(const void *item, const void *cmp_to) {
+ const struct swaybar_output *output = cmp_to;
+ if (output->identifier && strcmp(item, output->identifier) == 0) {
+ return 0;
+ }
+ return strcmp(item, output->name);
+}
+
+uint32_t render_tray(cairo_t *cairo, struct swaybar_output *output, double *x) {
+ struct swaybar_config *config = output->bar->config;
+ if (config->tray_outputs) {
+ if (list_seq_find(config->tray_outputs, cmp_output, output) == -1) {
+ return 0;
+ }
+ } // else display on all
+
+ if ((int) output->height*output->scale <= 2*config->tray_padding) {
+ return 2*config->tray_padding + 1;
+ }
+
+ uint32_t max_height = 0;
+ struct swaybar_tray *tray = output->bar->tray;
+ for (int i = 0; i < tray->items->length; ++i) {
+ uint32_t h = render_sni(cairo, output, x, tray->items->items[i]);
+ max_height = h > max_height ? h : max_height;
+ }
+
+ return max_height;
+}
diff --git a/swaybar/tray/watcher.c b/swaybar/tray/watcher.c
new file mode 100644
index 00000000..198c6c85
--- /dev/null
+++ b/swaybar/tray/watcher.c
@@ -0,0 +1,212 @@
+#define _POSIX_C_SOURCE 200809L
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "list.h"
+#include "log.h"
+#include "swaybar/tray/watcher.h"
+
+static const char *obj_path = "/StatusNotifierWatcher";
+
+static bool using_standard_protocol(struct swaybar_watcher *watcher) {
+ return watcher->interface[strlen("org.")] == 'f'; // freedesktop
+}
+
+static int cmp_id(const void *item, const void *cmp_to) {
+ return strcmp(item, cmp_to);
+}
+
+static int cmp_service(const void *item, const void *cmp_to) {
+ return strncmp(item, cmp_to, strlen(cmp_to));
+}
+
+static int handle_lost_service(sd_bus_message *msg,
+ void *data, sd_bus_error *error) {
+ char *service, *old_owner, *new_owner;
+ int ret = sd_bus_message_read(msg, "sss", &service, &old_owner, &new_owner);
+ if (ret < 0) {
+ wlr_log(WLR_ERROR, "Failed to parse owner change message: %s", strerror(-ret));
+ return ret;
+ }
+
+ if (!*new_owner) {
+ struct swaybar_watcher *watcher = data;
+ int idx = list_seq_find(watcher->items,
+ using_standard_protocol(watcher) ? cmp_id : cmp_service, service);
+ if (idx != -1) {
+ char *id = watcher->items->items[idx];
+ wlr_log(WLR_DEBUG, "Unregistering Status Notifier Item '%s'", id);
+ list_del(watcher->items, idx);
+ sd_bus_emit_signal(watcher->bus, obj_path, watcher->interface,
+ "StatusNotifierItemUnregistered", "s", id);
+ free(id);
+ }
+
+ idx = list_seq_find(watcher->hosts, cmp_id, service);
+ if (idx != -1) {
+ wlr_log(WLR_DEBUG, "Unregistering Status Notifier Host '%s'", service);
+ free(watcher->hosts->items[idx]);
+ list_del(watcher->hosts, idx);
+ }
+ }
+
+ return 0;
+}
+
+static int register_sni(sd_bus_message *msg, void *data, sd_bus_error *error) {
+ char *service_or_path, *id;
+ int ret = sd_bus_message_read(msg, "s", &service_or_path);
+ if (ret < 0) {
+ wlr_log(WLR_ERROR, "Failed to parse register SNI message: %s", strerror(-ret));
+ return ret;
+ }
+
+ struct swaybar_watcher *watcher = data;
+ if (using_standard_protocol(watcher)) {
+ id = strdup(service_or_path);
+ } else {
+ const char *service, *path;
+ if (service_or_path[0] == '/') {
+ service = sd_bus_message_get_sender(msg);
+ path = service_or_path;
+ } else {
+ service = service_or_path;
+ path = "/StatusNotifierItem";
+ }
+ size_t id_len = snprintf(NULL, 0, "%s%s", service, path) + 1;
+ id = malloc(id_len);
+ snprintf(id, id_len, "%s%s", service, path);
+ }
+
+ if (list_seq_find(watcher->items, cmp_id, id) == -1) {
+ wlr_log(WLR_DEBUG, "Registering Status Notifier Item '%s'", id);
+ list_add(watcher->items, id);
+ sd_bus_emit_signal(watcher->bus, obj_path, watcher->interface,
+ "StatusNotifierItemRegistered", "s", id);
+ } else {
+ wlr_log(WLR_DEBUG, "Status Notifier Item '%s' already registered", id);
+ free(id);
+ }
+
+ return sd_bus_reply_method_return(msg, "");
+}
+
+static int register_host(sd_bus_message *msg, void *data, sd_bus_error *error) {
+ char *service;
+ int ret = sd_bus_message_read(msg, "s", &service);
+ if (ret < 0) {
+ wlr_log(WLR_ERROR, "Failed to parse register host message: %s", strerror(-ret));
+ return ret;
+ }
+
+ struct swaybar_watcher *watcher = data;
+ if (list_seq_find(watcher->hosts, cmp_id, service) == -1) {
+ wlr_log(WLR_DEBUG, "Registering Status Notifier Host '%s'", service);
+ list_add(watcher->hosts, strdup(service));
+ sd_bus_emit_signal(watcher->bus, obj_path, watcher->interface,
+ "StatusNotifierHostRegistered", "s", service);
+ } else {
+ wlr_log(WLR_DEBUG, "Status Notifier Host '%s' already registered", service);
+ }
+
+ return sd_bus_reply_method_return(msg, "");
+}
+
+static int get_registered_snis(sd_bus *bus, const char *obj_path,
+ const char *interface, const char *property, sd_bus_message *reply,
+ void *data, sd_bus_error *error) {
+ struct swaybar_watcher *watcher = data;
+ list_add(watcher->items, NULL); // strv expects NULL-terminated string array
+ int ret = sd_bus_message_append_strv(reply, (char **)watcher->items->items);
+ list_del(watcher->items, watcher->items->length - 1);
+ return ret;
+}
+
+static int is_host_registered(sd_bus *bus, const char *obj_path,
+ const char *interface, const char *property, sd_bus_message *reply,
+ void *data, sd_bus_error *error) {
+ struct swaybar_watcher *watcher = data;
+ int val = watcher->hosts->length > 0; // dbus expects int rather than bool
+ return sd_bus_message_append_basic(reply, 'b', &val);
+}
+
+static const sd_bus_vtable watcher_vtable[] = {
+ SD_BUS_VTABLE_START(0),
+ SD_BUS_METHOD("RegisterStatusNotifierItem", "s", "", register_sni,
+ SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD("RegisterStatusNotifierHost", "s", "", register_host,
+ SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_PROPERTY("RegisteredStatusNotifierItems", "as", get_registered_snis,
+ 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
+ SD_BUS_PROPERTY("IsStatusNotifierHostRegistered", "b", is_host_registered,
+ 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
+ SD_BUS_PROPERTY("ProtocolVersion", "i", NULL,
+ offsetof(struct swaybar_watcher, version),
+ SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_SIGNAL("StatusNotifierItemRegistered", "s", 0),
+ SD_BUS_SIGNAL("StatusNotifierItemUnregistered", "s", 0),
+ SD_BUS_SIGNAL("StatusNotifierHostRegistered", NULL, 0),
+ SD_BUS_VTABLE_END
+};
+
+struct swaybar_watcher *create_watcher(char *protocol, sd_bus *bus) {
+ struct swaybar_watcher *watcher =
+ calloc(1, sizeof(struct swaybar_watcher));
+ if (!watcher) {
+ return NULL;
+ }
+
+ size_t len = snprintf(NULL, 0, "org.%s.StatusNotifierWatcher", protocol) + 1;
+ watcher->interface = malloc(len);
+ snprintf(watcher->interface, len, "org.%s.StatusNotifierWatcher", protocol);
+
+ sd_bus_slot *signal_slot = NULL, *vtable_slot = NULL;
+ int ret = sd_bus_add_object_vtable(bus, &vtable_slot, obj_path,
+ watcher->interface, watcher_vtable, watcher);
+ if (ret < 0) {
+ wlr_log(WLR_ERROR, "Failed to add object vtable: %s", strerror(-ret));
+ goto error;
+ }
+
+ ret = sd_bus_match_signal(bus, &signal_slot, "org.freedesktop.DBus",
+ "/org/freedesktop/DBus", "org.freedesktop.DBus",
+ "NameOwnerChanged", handle_lost_service, watcher);
+ if (ret < 0) {
+ wlr_log(WLR_ERROR, "Failed to subscribe to unregistering events: %s",
+ strerror(-ret));
+ goto error;
+ }
+
+ ret = sd_bus_request_name(bus, watcher->interface, 0);
+ if (ret < 0) {
+ wlr_log(WLR_ERROR, "Failed to acquire service name: %s", strerror(-ret));
+ goto error;
+ }
+
+ sd_bus_slot_set_floating(signal_slot, 0);
+ sd_bus_slot_set_floating(vtable_slot, 0);
+
+ watcher->bus = bus;
+ watcher->hosts = create_list();
+ watcher->items = create_list();
+ watcher->version = 0;
+ wlr_log(WLR_DEBUG, "Registered %s", watcher->interface);
+ return watcher;
+error:
+ sd_bus_slot_unref(signal_slot);
+ sd_bus_slot_unref(vtable_slot);
+ destroy_watcher(watcher);
+ return NULL;
+}
+
+void destroy_watcher(struct swaybar_watcher *watcher) {
+ if (!watcher) {
+ return;
+ }
+ list_free_items_and_destroy(watcher->hosts);
+ list_free_items_and_destroy(watcher->items);
+ free(watcher->interface);
+ free(watcher);
+}