summaryrefslogtreecommitdiffstats
path: root/swaybar
diff options
context:
space:
mode:
Diffstat (limited to 'swaybar')
-rw-r--r--swaybar/CMakeLists.txt11
-rw-r--r--swaybar/bar.c84
-rw-r--r--swaybar/config.c12
-rw-r--r--swaybar/event_loop.c143
-rw-r--r--swaybar/ipc.c40
-rw-r--r--swaybar/render.c15
-rw-r--r--swaybar/tray/dbus.c189
-rw-r--r--swaybar/tray/icon.c404
-rw-r--r--swaybar/tray/sni.c471
-rw-r--r--swaybar/tray/sni_watcher.c487
-rw-r--r--swaybar/tray/tray.c393
11 files changed, 2210 insertions, 39 deletions
diff --git a/swaybar/CMakeLists.txt b/swaybar/CMakeLists.txt
index f59a48fd..373719de 100644
--- a/swaybar/CMakeLists.txt
+++ b/swaybar/CMakeLists.txt
@@ -5,7 +5,13 @@ include_directories(
5 ${PANGO_INCLUDE_DIRS} 5 ${PANGO_INCLUDE_DIRS}
6 ${JSONC_INCLUDE_DIRS} 6 ${JSONC_INCLUDE_DIRS}
7 ${XKBCOMMON_INCLUDE_DIRS} 7 ${XKBCOMMON_INCLUDE_DIRS}
8 ${DBUS_INCLUDE_DIRS}
8) 9)
10if (enable-tray)
11 file(GLOB tray
12 tray/*.c
13 )
14endif()
9 15
10add_executable(swaybar 16add_executable(swaybar
11 main.c 17 main.c
@@ -14,6 +20,8 @@ add_executable(swaybar
14 bar.c 20 bar.c
15 status_line.c 21 status_line.c
16 ipc.c 22 ipc.c
23 event_loop.c
24 ${tray}
17) 25)
18 26
19target_link_libraries(swaybar 27target_link_libraries(swaybar
@@ -24,6 +32,7 @@ target_link_libraries(swaybar
24 ${CAIRO_LIBRARIES} 32 ${CAIRO_LIBRARIES}
25 ${PANGO_LIBRARIES} 33 ${PANGO_LIBRARIES}
26 ${JSONC_LIBRARIES} 34 ${JSONC_LIBRARIES}
35 ${DBUS_LIBRARIES}
27) 36)
28 37
29if (WITH_GDK_PIXBUF) 38if (WITH_GDK_PIXBUF)
@@ -32,6 +41,8 @@ if (WITH_GDK_PIXBUF)
32 ) 41 )
33endif() 42endif()
34 43
44target_link_libraries(swaybar rt)
45
35install( 46install(
36 TARGETS swaybar 47 TARGETS swaybar
37 RUNTIME 48 RUNTIME
diff --git a/swaybar/bar.c b/swaybar/bar.c
index 5ed0d266..3412ff29 100644
--- a/swaybar/bar.c
+++ b/swaybar/bar.c
@@ -7,10 +7,17 @@
7#include <sys/wait.h> 7#include <sys/wait.h>
8#include <signal.h> 8#include <signal.h>
9#include <poll.h> 9#include <poll.h>
10#ifdef ENABLE_TRAY
11#include <dbus/dbus.h>
12#include "swaybar/tray/sni_watcher.h"
13#include "swaybar/tray/tray.h"
14#include "swaybar/tray/sni.h"
15#endif
10#include "swaybar/ipc.h" 16#include "swaybar/ipc.h"
11#include "swaybar/render.h" 17#include "swaybar/render.h"
12#include "swaybar/config.h" 18#include "swaybar/config.h"
13#include "swaybar/status_line.h" 19#include "swaybar/status_line.h"
20#include "swaybar/event_loop.h"
14#include "swaybar/bar.h" 21#include "swaybar/bar.h"
15#include "ipc-client.h" 22#include "ipc-client.h"
16#include "list.h" 23#include "list.h"
@@ -57,12 +64,15 @@ struct output *new_output(const char *name) {
57 output->window = NULL; 64 output->window = NULL;
58 output->registry = NULL; 65 output->registry = NULL;
59 output->workspaces = create_list(); 66 output->workspaces = create_list();
67#ifdef ENABLE_TRAY
68 output->items = create_list();
69#endif
60 return output; 70 return output;
61} 71}
62 72
63static void mouse_button_notify(struct window *window, int x, int y, 73static void mouse_button_notify(struct window *window, int x, int y,
64 uint32_t button, uint32_t state_w) { 74 uint32_t button, uint32_t state_w) {
65 sway_log(L_DEBUG, "Mouse button %d clicked at %d %d %d\n", button, x, y, state_w); 75 sway_log(L_DEBUG, "Mouse button %d clicked at %d %d %d", button, x, y, state_w);
66 if (!state_w) { 76 if (!state_w) {
67 return; 77 return;
68 } 78 }
@@ -93,6 +103,10 @@ static void mouse_button_notify(struct window *window, int x, int y,
93 break; 103 break;
94 } 104 }
95 } 105 }
106
107#ifdef ENABLE_TRAY
108 tray_mouse_event(clicked_output, x, y, button, state_w);
109#endif
96} 110}
97 111
98static void mouse_scroll_notify(struct window *window, enum scroll_direction direction) { 112static void mouse_scroll_notify(struct window *window, enum scroll_direction direction) {
@@ -137,6 +151,9 @@ void bar_setup(struct bar *bar, const char *socket_path, const char *bar_id) {
137 /* initialize bar with default values */ 151 /* initialize bar with default values */
138 bar_init(bar); 152 bar_init(bar);
139 153
154 /* Initialize event loop lists */
155 init_event_loop();
156
140 /* connect to sway ipc */ 157 /* connect to sway ipc */
141 bar->ipc_socketfd = ipc_open_socket(socket_path); 158 bar->ipc_socketfd = ipc_open_socket(socket_path);
142 bar->ipc_event_socketfd = ipc_open_socket(socket_path); 159 bar->ipc_event_socketfd = ipc_open_socket(socket_path);
@@ -179,23 +196,41 @@ void bar_setup(struct bar *bar, const char *socket_path, const char *bar_id) {
179 } 196 }
180 /* spawn status command */ 197 /* spawn status command */
181 spawn_status_cmd_proc(bar); 198 spawn_status_cmd_proc(bar);
199
200#ifdef ENABLE_TRAY
201 init_tray(bar);
202#endif
182} 203}
183 204
184void bar_run(struct bar *bar) { 205bool dirty = true;
185 int pfds = bar->outputs->length + 2; 206
186 struct pollfd *pfd = malloc(pfds * sizeof(struct pollfd)); 207static void respond_ipc(int fd, short mask, void *_bar) {
187 bool dirty = true; 208 struct bar *bar = (struct bar *)_bar;
209 sway_log(L_DEBUG, "Got IPC event.");
210 dirty = handle_ipc_event(bar);
211}
212
213static void respond_command(int fd, short mask, void *_bar) {
214 struct bar *bar = (struct bar *)_bar;
215 dirty = handle_status_line(bar);
216}
217
218static void respond_output(int fd, short mask, void *_output) {
219 struct output *output = (struct output *)_output;
220 if (wl_display_dispatch(output->registry->display) == -1) {
221 sway_log(L_ERROR, "failed to dispatch wl: %d", errno);
222 }
223}
188 224
189 pfd[0].fd = bar->ipc_event_socketfd; 225void bar_run(struct bar *bar) {
190 pfd[0].events = POLLIN; 226 add_event(bar->ipc_event_socketfd, POLLIN, respond_ipc, bar);
191 pfd[1].fd = bar->status_read_fd; 227 add_event(bar->status_read_fd, POLLIN, respond_command, bar);
192 pfd[1].events = POLLIN;
193 228
194 int i; 229 int i;
195 for (i = 0; i < bar->outputs->length; ++i) { 230 for (i = 0; i < bar->outputs->length; ++i) {
196 struct output *output = bar->outputs->items[i]; 231 struct output *output = bar->outputs->items[i];
197 pfd[i+2].fd = wl_display_get_fd(output->registry->display); 232 add_event(wl_display_get_fd(output->registry->display),
198 pfd[i+2].events = POLLIN; 233 POLLIN, respond_output, output);
199 } 234 }
200 235
201 while (1) { 236 while (1) {
@@ -213,29 +248,10 @@ void bar_run(struct bar *bar) {
213 248
214 dirty = false; 249 dirty = false;
215 250
216 poll(pfd, pfds, -1); 251 event_loop_poll();
217 252#ifdef ENABLE_TRAY
218 if (pfd[0].revents & POLLIN) { 253 dispatch_dbus();
219 sway_log(L_DEBUG, "Got IPC event."); 254#endif
220 dirty = handle_ipc_event(bar);
221 }
222
223 if (bar->config->status_command && pfd[1].revents & POLLIN) {
224 sway_log(L_DEBUG, "Got update from status command.");
225 dirty = handle_status_line(bar);
226 }
227
228 // dispatch wl_display events
229 for (i = 0; i < bar->outputs->length; ++i) {
230 struct output *output = bar->outputs->items[i];
231 if (pfd[i+2].revents & POLLIN) {
232 if (wl_display_dispatch(output->registry->display) == -1) {
233 sway_log(L_ERROR, "failed to dispatch wl: %d", errno);
234 }
235 } else {
236 wl_display_dispatch_pending(output->registry->display);
237 }
238 }
239 } 255 }
240} 256}
241 257
diff --git a/swaybar/config.c b/swaybar/config.c
index 1d802022..8fe552f2 100644
--- a/swaybar/config.c
+++ b/swaybar/config.c
@@ -48,6 +48,18 @@ struct config *init_config() {
48 /* height */ 48 /* height */
49 config->height = 0; 49 config->height = 0;
50 50
51#ifdef ENABLE_TRAY
52 config->tray_output = NULL;
53 config->icon_theme = NULL;
54 config->tray_padding = 2;
55 /**
56 * These constants are used by wayland and are defined in
57 * linux/input-event-codes.h
58 */
59 config->activate_button = 0x110; /* BTN_LEFT */
60 config->context_button = 0x111; /* BTN_RIGHT */
61#endif
62
51 /* colors */ 63 /* colors */
52 config->colors.background = 0x000000FF; 64 config->colors.background = 0x000000FF;
53 config->colors.statusline = 0xFFFFFFFF; 65 config->colors.statusline = 0xFFFFFFFF;
diff --git a/swaybar/event_loop.c b/swaybar/event_loop.c
new file mode 100644
index 00000000..80655d8b
--- /dev/null
+++ b/swaybar/event_loop.c
@@ -0,0 +1,143 @@
1#define _XOPEN_SOURCE 500
2#include <stdlib.h>
3#include <stdbool.h>
4#include <string.h>
5#include <strings.h>
6#include <poll.h>
7#include "swaybar/bar.h"
8#include "swaybar/event_loop.h"
9#include "list.h"
10#include "log.h"
11
12struct event_item {
13 void(*cb)(int fd, short mask, void *data);
14 void *data;
15};
16
17struct timer_item {
18 timer_t timer;
19 void(*cb)(timer_t timer, void *data);
20 void *data;
21};
22
23static struct {
24 // The order of each must be kept consistent
25 struct { /* pollfd array */
26 struct pollfd *items;
27 int capacity;
28 int length;
29 } fds;
30 list_t *items; /* event_item list */
31
32 // Timer list
33 list_t *timers;
34} event_loop;
35
36void add_timer(timer_t timer,
37 void(*cb)(timer_t timer, void *data),
38 void *data) {
39
40 struct timer_item *item = malloc(sizeof(struct timer_item));
41 item->timer = timer;
42 item->cb = cb;
43 item->data = data;
44
45 list_add(event_loop.timers, item);
46}
47
48void add_event(int fd, short mask,
49 void(*cb)(int fd, short mask, void *data), void *data) {
50
51 struct pollfd pollfd = {
52 fd,
53 mask,
54 0,
55 };
56
57 // Resize
58 if (event_loop.fds.length == event_loop.fds.capacity) {
59 event_loop.fds.capacity += 10;
60 event_loop.fds.items = realloc(event_loop.fds.items,
61 sizeof(struct pollfd) * event_loop.fds.capacity);
62 }
63
64 event_loop.fds.items[event_loop.fds.length++] = pollfd;
65
66 struct event_item *item = malloc(sizeof(struct event_item));
67 item->cb = cb;
68 item->data = data;
69
70 list_add(event_loop.items, item);
71
72 return;
73}
74
75bool remove_event(int fd) {
76 int index = -1;
77 for (int i = 0; i < event_loop.fds.length; ++i) {
78 if (event_loop.fds.items[i].fd == fd) {
79 index = i;
80 }
81 }
82 if (index != -1) {
83 free(event_loop.items->items[index]);
84
85 --event_loop.fds.length;
86 memmove(&event_loop.fds.items[index], &event_loop.fds.items[index + 1],
87 sizeof(struct pollfd) * event_loop.fds.length - index);
88
89 list_del(event_loop.items, index);
90 return true;
91 } else {
92 return false;
93 }
94}
95
96static int timer_item_timer_cmp(const void *_timer_item, const void *_timer) {
97 const struct timer_item *timer_item = _timer_item;
98 const timer_t *timer = _timer;
99 if (timer_item->timer == timer) {
100 return 0;
101 } else {
102 return -1;
103 }
104}
105bool remove_timer(timer_t timer) {
106 int index = list_seq_find(event_loop.timers, timer_item_timer_cmp, &timer);
107 if (index != -1) {
108 list_del(event_loop.timers, index);
109 return true;
110 }
111 return false;
112}
113
114void event_loop_poll() {
115 poll(event_loop.fds.items, event_loop.fds.length, -1);
116
117 for (int i = 0; i < event_loop.fds.length; ++i) {
118 struct pollfd pfd = event_loop.fds.items[i];
119 struct event_item *item = (struct event_item *)event_loop.items->items[i];
120
121 if (pfd.revents & pfd.events) {
122 item->cb(pfd.fd, pfd.revents, item->data);
123 }
124 }
125
126 // check timers
127 // not tested, but seems to work
128 for (int i = 0; i < event_loop.timers->length; ++i) {
129 struct timer_item *item = event_loop.timers->items[i];
130 int overrun = timer_getoverrun(item->timer);
131 if (overrun && overrun != -1) {
132 item->cb(item->timer, item->data);
133 }
134 }
135}
136
137void init_event_loop() {
138 event_loop.fds.length = 0;
139 event_loop.fds.capacity = 10;
140 event_loop.fds.items = malloc(event_loop.fds.capacity * sizeof(struct pollfd));
141 event_loop.items = create_list();
142 event_loop.timers = create_list();
143}
diff --git a/swaybar/ipc.c b/swaybar/ipc.c
index b08eeea8..93d1219c 100644
--- a/swaybar/ipc.c
+++ b/swaybar/ipc.c
@@ -19,11 +19,19 @@ void ipc_send_workspace_command(const char *workspace_name) {
19 19
20static void ipc_parse_config(struct config *config, const char *payload) { 20static void ipc_parse_config(struct config *config, const char *payload) {
21 json_object *bar_config = json_tokener_parse(payload); 21 json_object *bar_config = json_tokener_parse(payload);
22 json_object *tray_output, *mode, *hidden_bar, *position, *status_command; 22 json_object *markup, *mode, *hidden_bar, *position, *status_command;
23 json_object *font, *bar_height, *wrap_scroll, *workspace_buttons, *strip_workspace_numbers; 23 json_object *font, *bar_height, *wrap_scroll, *workspace_buttons, *strip_workspace_numbers;
24 json_object *binding_mode_indicator, *verbose, *colors, *sep_symbol, *outputs; 24 json_object *binding_mode_indicator, *verbose, *colors, *sep_symbol, *outputs;
25 json_object *markup; 25#ifdef ENABLE_TRAY
26 json_object *tray_output, *icon_theme, *tray_padding, *activate_button, *context_button;
27 json_object *secondary_button;
26 json_object_object_get_ex(bar_config, "tray_output", &tray_output); 28 json_object_object_get_ex(bar_config, "tray_output", &tray_output);
29 json_object_object_get_ex(bar_config, "icon_theme", &icon_theme);
30 json_object_object_get_ex(bar_config, "tray_padding", &tray_padding);
31 json_object_object_get_ex(bar_config, "activate_button", &activate_button);
32 json_object_object_get_ex(bar_config, "context_button", &context_button);
33 json_object_object_get_ex(bar_config, "secondary_button", &secondary_button);
34#endif
27 json_object_object_get_ex(bar_config, "mode", &mode); 35 json_object_object_get_ex(bar_config, "mode", &mode);
28 json_object_object_get_ex(bar_config, "hidden_bar", &hidden_bar); 36 json_object_object_get_ex(bar_config, "hidden_bar", &hidden_bar);
29 json_object_object_get_ex(bar_config, "position", &position); 37 json_object_object_get_ex(bar_config, "position", &position);
@@ -83,6 +91,34 @@ static void ipc_parse_config(struct config *config, const char *payload) {
83 config->pango_markup = json_object_get_boolean(markup); 91 config->pango_markup = json_object_get_boolean(markup);
84 } 92 }
85 93
94#ifdef ENABLE_TRAY
95 if (tray_output) {
96 free(config->tray_output);
97 config->tray_output = strdup(json_object_get_string(tray_output));
98 }
99
100 if (icon_theme) {
101 free(config->icon_theme);
102 config->icon_theme = strdup(json_object_get_string(icon_theme));
103 }
104
105 if (tray_padding) {
106 config->tray_padding = json_object_get_int(tray_padding);
107 }
108
109 if (activate_button) {
110 config->activate_button = json_object_get_int(activate_button);
111 }
112
113 if (context_button) {
114 config->context_button = json_object_get_int(context_button);
115 }
116
117 if (secondary_button) {
118 config->secondary_button = json_object_get_int(secondary_button);
119 }
120#endif
121
86 // free previous outputs list 122 // free previous outputs list
87 int i; 123 int i;
88 for (i = 0; i < config->outputs->length; ++i) { 124 for (i = 0; i < config->outputs->length; ++i) {
diff --git a/swaybar/render.c b/swaybar/render.c
index 2eae997f..6ec47e79 100644
--- a/swaybar/render.c
+++ b/swaybar/render.c
@@ -8,6 +8,10 @@
8#include "swaybar/config.h" 8#include "swaybar/config.h"
9#include "swaybar/status_line.h" 9#include "swaybar/status_line.h"
10#include "swaybar/render.h" 10#include "swaybar/render.h"
11#ifdef ENABLE_TRAY
12#include "swaybar/tray/tray.h"
13#include "swaybar/tray/sni.h"
14#endif
11#include "log.h" 15#include "log.h"
12 16
13 17
@@ -297,6 +301,12 @@ void render(struct output *output, struct config *config, struct status_line *li
297 } 301 }
298 cairo_paint(cairo); 302 cairo_paint(cairo);
299 303
304#ifdef ENABLE_TRAY
305 uint32_t tray_width = tray_render(output, config);
306#else
307 const uint32_t tray_width = window->width * window->scale;
308#endif
309
300 // Command output 310 // Command output
301 if (is_focused) { 311 if (is_focused) {
302 cairo_set_source_u32(cairo, config->colors.focused_statusline); 312 cairo_set_source_u32(cairo, config->colors.focused_statusline);
@@ -309,12 +319,11 @@ void render(struct output *output, struct config *config, struct status_line *li
309 if (line->protocol == TEXT) { 319 if (line->protocol == TEXT) {
310 get_text_size(window->cairo, window->font, &width, &height, 320 get_text_size(window->cairo, window->font, &width, &height,
311 window->scale, config->pango_markup, "%s", line->text_line); 321 window->scale, config->pango_markup, "%s", line->text_line);
312 cairo_move_to(cairo, (window->width * window->scale) 322 cairo_move_to(cairo, tray_width - margin - width, margin);
313 - margin - width, margin);
314 pango_printf(window->cairo, window->font, window->scale, 323 pango_printf(window->cairo, window->font, window->scale,
315 config->pango_markup, "%s", line->text_line); 324 config->pango_markup, "%s", line->text_line);
316 } else if (line->protocol == I3BAR && line->block_line) { 325 } else if (line->protocol == I3BAR && line->block_line) {
317 double pos = (window->width * window->scale) - 0.5; 326 double pos = tray_width - 0.5;
318 bool edge = true; 327 bool edge = true;
319 for (i = line->block_line->length - 1; i >= 0; --i) { 328 for (i = line->block_line->length - 1; i >= 0; --i) {
320 struct status_block *block = line->block_line->items[i]; 329 struct status_block *block = line->block_line->items[i];
diff --git a/swaybar/tray/dbus.c b/swaybar/tray/dbus.c
new file mode 100644
index 00000000..333d398e
--- /dev/null
+++ b/swaybar/tray/dbus.c
@@ -0,0 +1,189 @@
1#define _XOPEN_SOURCE 500
2#include <stdio.h>
3#include <stdlib.h>
4#include <stdint.h>
5#include <stdbool.h>
6#include <poll.h>
7#include <signal.h>
8#include <time.h>
9#include <dbus/dbus.h>
10#include "swaybar/tray/dbus.h"
11#include "swaybar/event_loop.h"
12#include "log.h"
13
14DBusConnection *conn = NULL;
15
16static void dispatch_watch(int fd, short mask, void *data) {
17 sway_log(L_DEBUG, "Dispatching watch");
18 DBusWatch *watch = data;
19
20 if (!dbus_watch_get_enabled(watch)) {
21 return;
22 }
23
24 uint32_t flags = 0;
25
26 if (mask & POLLIN) {
27 flags |= DBUS_WATCH_READABLE;
28 } if (mask & POLLOUT) {
29 flags |= DBUS_WATCH_WRITABLE;
30 } if (mask & POLLHUP) {
31 flags |= DBUS_WATCH_HANGUP;
32 } if (mask & POLLERR) {
33 flags |= DBUS_WATCH_ERROR;
34 }
35
36 dbus_watch_handle(watch, flags);
37}
38
39static dbus_bool_t add_watch(DBusWatch *watch, void *_data) {
40 if (!dbus_watch_get_enabled(watch)) {
41 // Watch should not be polled
42 return TRUE;
43 }
44
45 short mask = 0;
46 uint32_t flags = dbus_watch_get_flags(watch);
47
48 if (flags & DBUS_WATCH_READABLE) {
49 mask |= POLLIN;
50 } if (flags & DBUS_WATCH_WRITABLE) {
51 mask |= POLLOUT;
52 }
53
54 int fd = dbus_watch_get_unix_fd(watch);
55
56 sway_log(L_DEBUG, "Adding DBus watch fd: %d", fd);
57 add_event(fd, mask, dispatch_watch, watch);
58
59 return TRUE;
60}
61
62static void remove_watch(DBusWatch *watch, void *_data) {
63 int fd = dbus_watch_get_unix_fd(watch);
64
65 remove_event(fd);
66}
67
68static void dispatch_timeout(timer_t timer, void *data) {
69 sway_log(L_DEBUG, "Dispatching DBus timeout");
70 DBusTimeout *timeout = data;
71
72 if (dbus_timeout_get_enabled(timeout)) {
73 dbus_timeout_handle(timeout);
74 }
75}
76
77static dbus_bool_t add_timeout(DBusTimeout *timeout, void *_data) {
78 if (!dbus_timeout_get_enabled(timeout)) {
79 return TRUE;
80 }
81
82 timer_t *timer = malloc(sizeof(timer_t));
83 if (!timer) {
84 sway_log(L_ERROR, "Cannot allocate memory");
85 return FALSE;
86 }
87 struct sigevent ev = {
88 .sigev_notify = SIGEV_NONE,
89 };
90
91 if (timer_create(CLOCK_MONOTONIC, &ev, timer)) {
92 sway_log(L_ERROR, "Could not create DBus timer");
93 return FALSE;
94 }
95
96 int interval = dbus_timeout_get_interval(timeout);
97 int interval_sec = interval / 1000;
98 int interval_msec = (interval_sec * 1000) - interval;
99
100 struct timespec period = {
101 (time_t) interval_sec,
102 ((long) interval_msec) * 1000 * 1000,
103 };
104 struct itimerspec time = {
105 period,
106 period,
107 };
108
109 timer_settime(*timer, 0, &time, NULL);
110
111 dbus_timeout_set_data(timeout, timer, free);
112
113 sway_log(L_DEBUG, "Adding DBus timeout. Interval: %ds %dms", interval_sec, interval_msec);
114 add_timer(*timer, dispatch_timeout, timeout);
115
116 return TRUE;
117}
118static void remove_timeout(DBusTimeout *timeout, void *_data) {
119 timer_t *timer = (timer_t *) dbus_timeout_get_data(timeout);
120 sway_log(L_DEBUG, "Removing DBus timeout.");
121
122 if (timer) {
123 remove_timer(*timer);
124 }
125}
126
127static bool should_dispatch = true;
128
129static void dispatch_status(DBusConnection *connection, DBusDispatchStatus new_status,
130 void *_data) {
131 if (new_status == DBUS_DISPATCH_DATA_REMAINS) {
132 should_dispatch = true;
133 }
134}
135
136/* Public functions below */
137
138void dispatch_dbus() {
139 if (!should_dispatch) {
140 return;
141 }
142
143 DBusDispatchStatus status;
144
145 do {
146 status = dbus_connection_dispatch(conn);
147 } while (status == DBUS_DISPATCH_DATA_REMAINS);
148
149 if (status != DBUS_DISPATCH_COMPLETE) {
150 sway_log(L_ERROR, "Cannot dispatch dbus events: %d", status);
151 }
152
153 should_dispatch = false;
154}
155
156int dbus_init() {
157 DBusError error;
158 dbus_error_init(&error);
159
160 conn = dbus_bus_get(DBUS_BUS_SESSION, &error);
161 dbus_connection_set_exit_on_disconnect(conn, FALSE);
162 if (dbus_error_is_set(&error)) {
163 sway_log(L_ERROR, "Cannot get bus connection: %s\n", error.message);
164 conn = NULL;
165 return -1;
166 }
167
168 sway_log(L_INFO, "Unique name: %s\n", dbus_bus_get_unique_name(conn));
169
170 // Will be called if dispatch status changes
171 dbus_connection_set_dispatch_status_function(conn, dispatch_status, NULL, NULL);
172
173 if (!dbus_connection_set_watch_functions(conn, add_watch, remove_watch,
174 NULL, NULL, NULL)) {
175 dbus_connection_set_watch_functions(conn, NULL, NULL, NULL, NULL, NULL);
176 sway_log(L_ERROR, "Failed to activate DBUS watch functions");
177 return -1;
178 }
179
180 if (!dbus_connection_set_timeout_functions(conn, add_timeout, remove_timeout,
181 NULL, NULL, NULL)) {
182 dbus_connection_set_watch_functions(conn, NULL, NULL, NULL, NULL, NULL);
183 dbus_connection_set_timeout_functions(conn, NULL, NULL, NULL, NULL, NULL);
184 sway_log(L_ERROR, "Failed to activate DBUS timeout functions");
185 return -1;
186 }
187
188 return 0;
189}
diff --git a/swaybar/tray/icon.c b/swaybar/tray/icon.c
new file mode 100644
index 00000000..29151a74
--- /dev/null
+++ b/swaybar/tray/icon.c
@@ -0,0 +1,404 @@
1#define _XOPEN_SOURCE 500
2#define _POSIX_C_SOURCE 200809L
3#include <stdio.h>
4#include <stdlib.h>
5#include <unistd.h>
6#include <string.h>
7#include <dirent.h>
8#include <sys/stat.h>
9#include <stdbool.h>
10#include <stdint.h>
11#include <limits.h>
12#include "swaybar/tray/icon.h"
13#include "swaybar/bar.h"
14#include "swaybar/config.h"
15#include "stringop.h"
16#include "log.h"
17
18/**
19 * REVIEW:
20 * This file repeats lots of "costly" operations that are the same for every
21 * icon. It's possible to create a dictionary or some other structure to cache
22 * these, though it may complicate things somewhat.
23 *
24 * Also parsing (index.theme) is currently pretty messy, so that could be made
25 * much better as well. Over all, things work, but are not optimal.
26 */
27
28/* Finds all themes that the given theme inherits */
29static list_t *find_inherits(const char *theme_dir) {
30 const char inherits[] = "Inherits";
31 const char index_name[] = "index.theme";
32 list_t *themes = create_list();
33 FILE *index = NULL;
34 char *path = malloc(strlen(theme_dir) + sizeof(index_name));
35 if (!path) {
36 goto fail;
37 }
38 if (!themes) {
39 goto fail;
40 }
41
42 strcpy(path, theme_dir);
43 strcat(path, index_name);
44
45 index = fopen(path, "r");
46 if (!index) {
47 goto fail;
48 }
49
50 char *buf = NULL;
51 size_t n = 0;
52 while (!feof(index)) {
53 getline(&buf, &n, index);
54 if (n <= sizeof(inherits) + 1) {
55 continue;
56 }
57 if (strncmp(inherits, buf, sizeof(inherits) - 1) == 0) {
58 char *themestr = buf + sizeof(inherits);
59 themes = split_string(themestr, ",");
60 break;
61 }
62 }
63 free(buf);
64
65fail:
66 free(path);
67 if (index) {
68 fclose(index);
69 }
70 return themes;
71}
72
73static bool isdir(const char *path) {
74 struct stat statbuf;
75 if (stat(path, &statbuf) != -1) {
76 if (S_ISDIR(statbuf.st_mode)) {
77 return true;
78 }
79 }
80 return false;
81
82}
83
84/**
85 * Returns the directory of a given theme if it exists.
86 * The returned pointer must be freed.
87 */
88static char *find_theme_dir(const char *theme) {
89 char *basedir;
90 char *icon_dir;
91
92 if (!theme) {
93 return NULL;
94 }
95
96 if (!(icon_dir = malloc(1024))) {
97 sway_log(L_ERROR, "Out of memory!");
98 goto fail;
99 }
100
101 if ((basedir = getenv("HOME"))) {
102 if (snprintf(icon_dir, 1024, "%s/.icons/%s", basedir, theme) >= 1024) {
103 sway_log(L_ERROR, "Path too long to render");
104 // XXX perhaps just goto trying in /usr/share? This
105 // shouldn't happen anyway, but might with a long global
106 goto fail;
107 }
108
109 if (isdir(icon_dir)) {
110 return icon_dir;
111 }
112 }
113
114 if ((basedir = getenv("XDG_DATA_DIRS"))) {
115 if (snprintf(icon_dir, 1024, "%s/icons/%s", basedir, theme) >= 1024) {
116 sway_log(L_ERROR, "Path too long to render");
117 // ditto
118 goto fail;
119 }
120
121 if (isdir(icon_dir)) {
122 return icon_dir;
123 }
124 }
125
126 // Spec says use "/usr/share/pixmaps/", but I see everything in
127 // "/usr/share/icons/" look it both, I suppose.
128 if (snprintf(icon_dir, 1024, "/usr/share/pixmaps/%s", theme) >= 1024) {
129 sway_log(L_ERROR, "Path too long to render");
130 goto fail;
131 }
132 if (isdir(icon_dir)) {
133 return icon_dir;
134 }
135
136 if (snprintf(icon_dir, 1024, "/usr/share/icons/%s", theme) >= 1024) {
137 sway_log(L_ERROR, "Path too long to render");
138 goto fail;
139 }
140 if (isdir(icon_dir)) {
141 return icon_dir;
142 }
143
144fail:
145 free(icon_dir);
146 sway_log(L_ERROR, "Could not find dir for theme: %s", theme);
147 return NULL;
148}
149
150/**
151 * Returns all theme dirs needed to be looked in for an icon.
152 * Does not check for duplicates
153 */
154static list_t *find_all_theme_dirs(const char *theme) {
155 list_t *dirs = create_list();
156 if (!dirs) {
157 return NULL;
158 }
159 char *dir = find_theme_dir(theme);
160 if (dir) {
161 list_add(dirs, dir);
162 list_t *inherits = find_inherits(dir);
163 list_cat(dirs, inherits);
164 list_free(inherits);
165 }
166 dir = find_theme_dir("hicolor");
167 if (dir) {
168 list_add(dirs, dir);
169 }
170
171 return dirs;
172}
173
174struct subdir {
175 int size;
176 char name[];
177};
178
179static int subdir_str_cmp(const void *_subdir, const void *_str) {
180 const struct subdir *subdir = _subdir;
181 const char *str = _str;
182 return strcmp(subdir->name, str);
183}
184/**
185 * Helper to find_subdirs. Acts similar to `split_string(subdirs, ",")` but
186 * generates a list of struct subdirs
187 */
188static list_t *split_subdirs(char *subdir_str) {
189 list_t *subdir_list = create_list();
190 char *copy = strdup(subdir_str);
191 if (!subdir_list || !copy) {
192 list_free(subdir_list);
193 free(copy);
194 return NULL;
195 }
196
197 char *token;
198 token = strtok(copy, ",");
199 while(token) {
200 int len = strlen(token) + 1;
201 struct subdir *subdir =
202 malloc(sizeof(struct subdir) + sizeof(char [len]));
203 if (!subdir) {
204 // Return what we have
205 return subdir_list;
206 }
207 subdir->size = 0;
208 strcpy(subdir->name, token);
209
210 list_add(subdir_list, subdir);
211
212 token = strtok(NULL, ",");
213 }
214 free(copy);
215
216 return subdir_list;
217}
218/**
219 * Returns a list of all subdirectories of a theme.
220 * Take note: the subdir names are all relative to `theme_dir` and must be
221 * combined with it to form a valid directory.
222 *
223 * Each member of the list is of type (struct subdir *) this struct contains
224 * the name of the subdir, along with size information. These must be freed
225 * bye the caller.
226 *
227 * This currently ignores min and max sizes of icons.
228 */
229static list_t* find_theme_subdirs(const char *theme_dir) {
230 const char index_name[] = "/index.theme";
231 list_t *dirs = NULL;
232 char *path = malloc(strlen(theme_dir) + sizeof(index_name));
233 FILE *index = NULL;
234 if (!path) {
235 sway_log(L_ERROR, "Failed to allocate memory");
236 goto fail;
237 }
238
239 strcpy(path, theme_dir);
240 strcat(path, index_name);
241
242 index = fopen(path, "r");
243 if (!index) {
244 sway_log(L_ERROR, "Could not open file: %s", path);
245 goto fail;
246 }
247
248 char *buf = NULL;
249 size_t n = 0;
250 while (!feof(index)) {
251 const char directories[] = "Directories";
252 getline(&buf, &n, index);
253 if (n <= sizeof(directories) + 1) {
254 continue;
255 }
256 if (strncmp(directories, buf, sizeof(directories) - 1) == 0) {
257 char *dirstr = buf + sizeof(directories);
258 dirs = split_subdirs(dirstr);
259 break;
260 }
261 }
262 // Now, find the size of each dir
263 struct subdir *current_subdir = NULL;
264 while (!feof(index)) {
265 const char size[] = "Size";
266 getline(&buf, &n, index);
267
268 if (buf[0] == '[') {
269 int len = strlen(buf);
270 if (buf[len-1] == '\n') {
271 len--;
272 }
273 // replace ']'
274 buf[len-1] = '\0';
275
276 int index;
277 if ((index = list_seq_find(dirs, subdir_str_cmp, buf+1)) != -1) {
278 current_subdir = (dirs->items[index]);
279 }
280 }
281
282 if (strncmp(size, buf, sizeof(size) - 1) == 0) {
283 if (current_subdir) {
284 current_subdir->size = atoi(buf + sizeof(size));
285 }
286 }
287 }
288 free(buf);
289fail:
290 free(path);
291 if (index) {
292 fclose(index);
293 }
294 return dirs;
295}
296
297/* Returns the file of an icon given its name and size */
298static char *find_icon_file(const char *name, int size) {
299 int namelen = strlen(name);
300 list_t *dirs = find_all_theme_dirs(swaybar.config->icon_theme);
301 if (!dirs) {
302 return NULL;
303 }
304 int min_size_diff = INT_MAX;
305 char *current_file = NULL;
306
307 for (int i = 0; i < dirs->length; ++i) {
308 char *dir = dirs->items[i];
309 list_t *subdirs = find_theme_subdirs(dir);
310
311 if (!subdirs) {
312 continue;
313 }
314
315 for (int i = 0; i < subdirs->length; ++i) {
316 struct subdir *subdir = subdirs->items[i];
317
318 // Only use an unsized if we don't already have a
319 // canidate this should probably change to allow svgs
320 if (!subdir->size && current_file) {
321 continue;
322 }
323
324 int size_diff = abs(size - subdir->size);
325
326 if (size_diff >= min_size_diff) {
327 continue;
328 }
329
330 char *path = malloc(strlen(subdir->name) + strlen(dir) + 2);
331
332 strcpy(path, dir);
333 path[strlen(dir)] = '/';
334 strcpy(path + strlen(dir) + 1, subdir->name);
335
336 DIR *icons = opendir(path);
337 if (!icons) {
338 free(path);
339 continue;
340 }
341
342 struct dirent *direntry;
343 while ((direntry = readdir(icons)) != NULL) {
344 int len = strlen(direntry->d_name);
345 if (len <= namelen + 2) { //must have some ext
346 continue;
347 }
348 if (strncmp(direntry->d_name, name, namelen) == 0) {
349 char *ext = direntry->d_name + namelen + 1;
350#ifdef WITH_GDK_PIXBUF
351 if (strcmp(ext, "png") == 0 ||
352 strcmp(ext, "xpm") == 0 ||
353 strcmp(ext, "svg") == 0) {
354#else
355 if (strcmp(ext, "png") == 0) {
356#endif
357 free(current_file);
358 char *icon_path = malloc(strlen(path) + len + 2);
359
360 strcpy(icon_path, path);
361 icon_path[strlen(path)] = '/';
362 strcpy(icon_path + strlen(path) + 1, direntry->d_name);
363 current_file = icon_path;
364 min_size_diff = size_diff;
365 }
366 }
367 }
368 free(path);
369 closedir(icons);
370 }
371 free_flat_list(subdirs);
372 }
373 free_flat_list(dirs);
374
375 return current_file;
376}
377
378cairo_surface_t *find_icon(const char *name, int size) {
379 char *image_path = find_icon_file(name, size);
380 if (image_path == NULL) {
381 return NULL;
382 }
383
384 cairo_surface_t *image = NULL;
385#ifdef WITH_GDK_PIXBUF
386 GError *err = NULL;
387 GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file(image_path, &err);
388 if (!pixbuf) {
389 sway_log(L_ERROR, "Failed to load icon image: %s", err->message);
390 }
391 image = gdk_cairo_image_surface_create_from_pixbuf(pixbuf);
392 g_object_unref(pixbuf);
393#else
394 // TODO make svg work? cairo supports it. maybe remove gdk alltogether
395 image = cairo_image_surface_create_from_png(image_path);
396#endif //WITH_GDK_PIXBUF
397 if (!image) {
398 sway_log(L_ERROR, "Could not read icon image");
399 return NULL;
400 }
401
402 free(image_path);
403 return image;
404}
diff --git a/swaybar/tray/sni.c b/swaybar/tray/sni.c
new file mode 100644
index 00000000..0c46d5c0
--- /dev/null
+++ b/swaybar/tray/sni.c
@@ -0,0 +1,471 @@
1#define _XOPEN_SOURCE 500
2#include <stdlib.h>
3#include <stdio.h>
4#include <string.h>
5#include <stdint.h>
6#include <stdbool.h>
7#include <dbus/dbus.h>
8#include <arpa/inet.h>
9#include <netinet/in.h>
10#include "swaybar/tray/dbus.h"
11#include "swaybar/tray/sni.h"
12#include "swaybar/tray/icon.h"
13#include "swaybar/bar.h"
14#include "client/cairo.h"
15#include "log.h"
16
17// Not sure what this is but cairo needs it.
18static const cairo_user_data_key_t cairo_user_data_key;
19
20struct sni_icon_ref *sni_icon_ref_create(struct StatusNotifierItem *item,
21 int height) {
22 struct sni_icon_ref *sni_ref = malloc(sizeof(struct sni_icon_ref));
23 if (!sni_ref) {
24 return NULL;
25 }
26 sni_ref->icon = cairo_image_surface_scale(item->image, height, height);
27 sni_ref->ref = item;
28
29 return sni_ref;
30}
31
32void sni_icon_ref_free(struct sni_icon_ref *sni_ref) {
33 if (!sni_ref) {
34 return;
35 }
36 cairo_surface_destroy(sni_ref->icon);
37 free(sni_ref);
38}
39
40/* Gets the pixmap of an icon */
41static void reply_icon(DBusPendingCall *pending, void *_data) {
42 struct StatusNotifierItem *item = _data;
43
44 DBusMessage *reply = dbus_pending_call_steal_reply(pending);
45
46 if (!reply) {
47 sway_log(L_ERROR, "Did not get reply");
48 goto bail;
49 }
50
51 int message_type = dbus_message_get_type(reply);
52
53 if (message_type == DBUS_MESSAGE_TYPE_ERROR) {
54 char *msg;
55
56 dbus_message_get_args(reply, NULL,
57 DBUS_TYPE_STRING, &msg,
58 DBUS_TYPE_INVALID);
59
60 sway_log(L_ERROR, "Message is error: %s", msg);
61 goto bail;
62 }
63
64 DBusMessageIter iter;
65 DBusMessageIter variant; /* v[a(iiay)] */
66 DBusMessageIter array; /* a(iiay) */
67 DBusMessageIter d_struct; /* (iiay) */
68 DBusMessageIter icon; /* ay */
69
70 dbus_message_iter_init(reply, &iter);
71
72 // Each if here checks the types above before recursing
73 if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) {
74 sway_log(L_ERROR, "Relpy type incorrect");
75 sway_log(L_ERROR, "Should be \"v\", is \"%s\"",
76 dbus_message_iter_get_signature(&iter));
77 goto bail;
78 }
79 dbus_message_iter_recurse(&iter, &variant);
80
81 if (strcmp("a(iiay)", dbus_message_iter_get_signature(&variant)) != 0) {
82 sway_log(L_ERROR, "Relpy type incorrect");
83 sway_log(L_ERROR, "Should be \"a(iiay)\", is \"%s\"",
84 dbus_message_iter_get_signature(&variant));
85 goto bail;
86 }
87
88 if (dbus_message_iter_get_element_count(&variant) == 0) {
89 // Can't recurse if there are no items
90 sway_log(L_INFO, "Item has no icon");
91 goto bail;
92 }
93 dbus_message_iter_recurse(&variant, &array);
94
95 dbus_message_iter_recurse(&array, &d_struct);
96
97 int width;
98 dbus_message_iter_get_basic(&d_struct, &width);
99 dbus_message_iter_next(&d_struct);
100
101 int height;
102 dbus_message_iter_get_basic(&d_struct, &height);
103 dbus_message_iter_next(&d_struct);
104
105 int len = dbus_message_iter_get_element_count(&d_struct);
106
107 if (!len) {
108 sway_log(L_ERROR, "No icon data");
109 goto bail;
110 }
111
112 // Also implies len % 4 == 0, useful below
113 if (len != width * height * 4) {
114 sway_log(L_ERROR, "Incorrect array size passed");
115 goto bail;
116 }
117
118 dbus_message_iter_recurse(&d_struct, &icon);
119
120 int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width);
121 // FIXME support a variable stride
122 // (works on my machine though for all tested widths)
123 if (!sway_assert(stride == width * 4, "Stride must be equal to byte length")) {
124 goto bail;
125 }
126
127 // Data is by reference, no need to free
128 uint8_t *message_data;
129 dbus_message_iter_get_fixed_array(&icon, &message_data, &len);
130
131 uint8_t *image_data = malloc(stride * height);
132 if (!image_data) {
133 sway_log(L_ERROR, "Could not allocate memory for icon");
134 goto bail;
135 }
136
137 // Transform from network byte order to host byte order
138 // Assumptions are safe because the equality above
139 uint32_t *network = (uint32_t *) message_data;
140 uint32_t *host = (uint32_t *)image_data;
141 for (int i = 0; i < width * height; ++i) {
142 host[i] = ntohl(network[i]);
143 }
144
145 cairo_surface_t *image = cairo_image_surface_create_for_data(
146 image_data, CAIRO_FORMAT_ARGB32,
147 width, height, stride);
148
149 if (image) {
150 if (item->image) {
151 cairo_surface_destroy(item->image);
152 }
153 item->image = image;
154 // Free the image data on surface destruction
155 cairo_surface_set_user_data(image,
156 &cairo_user_data_key,
157 image_data,
158 free);
159 item->dirty = true;
160 dirty = true;
161
162 dbus_message_unref(reply);
163 return;
164 } else {
165 sway_log(L_ERROR, "Could not create image surface");
166 free(image_data);
167 }
168
169bail:
170 if (reply) {
171 dbus_message_unref(reply);
172 }
173 sway_log(L_ERROR, "Could not get icon from item");
174 return;
175}
176static void send_icon_msg(struct StatusNotifierItem *item) {
177 DBusPendingCall *pending;
178 DBusMessage *message = dbus_message_new_method_call(
179 item->name,
180 "/StatusNotifierItem",
181 "org.freedesktop.DBus.Properties",
182 "Get");
183 const char *iface;
184 if (item->kde_special_snowflake) {
185 iface = "org.kde.StatusNotifierItem";
186 } else {
187 iface = "org.freedesktop.StatusNotifierItem";
188 }
189 const char *prop = "IconPixmap";
190
191 dbus_message_append_args(message,
192 DBUS_TYPE_STRING, &iface,
193 DBUS_TYPE_STRING, &prop,
194 DBUS_TYPE_INVALID);
195
196 bool status =
197 dbus_connection_send_with_reply(conn, message, &pending, -1);
198
199 dbus_message_unref(message);
200
201 if (!(pending || status)) {
202 sway_log(L_ERROR, "Could not get item icon");
203 return;
204 }
205
206 dbus_pending_call_set_notify(pending, reply_icon, item, NULL);
207}
208
209/* Get an icon by its name */
210static void reply_icon_name(DBusPendingCall *pending, void *_data) {
211 struct StatusNotifierItem *item = _data;
212
213 DBusMessage *reply = dbus_pending_call_steal_reply(pending);
214
215 if (!reply) {
216 sway_log(L_INFO, "Got no icon name reply from item");
217 goto bail;
218 }
219
220 int message_type = dbus_message_get_type(reply);
221
222 if (message_type == DBUS_MESSAGE_TYPE_ERROR) {
223 char *msg;
224
225 dbus_message_get_args(reply, NULL,
226 DBUS_TYPE_STRING, &msg,
227 DBUS_TYPE_INVALID);
228
229 sway_log(L_INFO, "Could not get icon name: %s", msg);
230 goto bail;
231 }
232
233 DBusMessageIter iter; /* v[s] */
234 DBusMessageIter variant; /* s */
235
236 dbus_message_iter_init(reply, &iter);
237 if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) {
238 sway_log(L_ERROR, "Relpy type incorrect");
239 sway_log(L_ERROR, "Should be \"v\", is \"%s\"",
240 dbus_message_iter_get_signature(&iter));
241 goto bail;
242 }
243 dbus_message_iter_recurse(&iter, &variant);
244
245
246 if (dbus_message_iter_get_arg_type(&variant) != DBUS_TYPE_STRING) {
247 sway_log(L_ERROR, "Relpy type incorrect");
248 sway_log(L_ERROR, "Should be \"s\", is \"%s\"",
249 dbus_message_iter_get_signature(&iter));
250 goto bail;
251 }
252
253 char *icon_name;
254 dbus_message_iter_get_basic(&variant, &icon_name);
255
256 cairo_surface_t *image = find_icon(icon_name, 256);
257
258 if (image) {
259 sway_log(L_DEBUG, "Icon for %s found with size %d", icon_name,
260 cairo_image_surface_get_width(image));
261 if (item->image) {
262 cairo_surface_destroy(item->image);
263 }
264 item->image = image;
265 item->dirty = true;
266 dirty = true;
267
268 dbus_message_unref(reply);
269 return;
270 }
271
272bail:
273 if (reply) {
274 dbus_message_unref(reply);
275 }
276 // Now try the pixmap
277 send_icon_msg(item);
278 return;
279}
280static void send_icon_name_msg(struct StatusNotifierItem *item) {
281 DBusPendingCall *pending;
282 DBusMessage *message = dbus_message_new_method_call(
283 item->name,
284 "/StatusNotifierItem",
285 "org.freedesktop.DBus.Properties",
286 "Get");
287 const char *iface;
288 if (item->kde_special_snowflake) {
289 iface = "org.kde.StatusNotifierItem";
290 } else {
291 iface = "org.freedesktop.StatusNotifierItem";
292 }
293 const char *prop = "IconName";
294
295 dbus_message_append_args(message,
296 DBUS_TYPE_STRING, &iface,
297 DBUS_TYPE_STRING, &prop,
298 DBUS_TYPE_INVALID);
299
300 bool status =
301 dbus_connection_send_with_reply(conn, message, &pending, -1);
302
303 dbus_message_unref(message);
304
305 if (!(pending || status)) {
306 sway_log(L_ERROR, "Could not get item icon name");
307 return;
308 }
309
310 dbus_pending_call_set_notify(pending, reply_icon_name, item, NULL);
311}
312
313void get_icon(struct StatusNotifierItem *item) {
314 send_icon_name_msg(item);
315}
316
317void sni_activate(struct StatusNotifierItem *item, uint32_t x, uint32_t y) {
318 const char *iface =
319 (item->kde_special_snowflake ? "org.kde.StatusNotifierItem"
320 : "org.freedesktop.StatusNotifierItem");
321 DBusMessage *message = dbus_message_new_method_call(
322 item->name,
323 "/StatusNotifierItem",
324 iface,
325 "Activate");
326
327 dbus_message_append_args(message,
328 DBUS_TYPE_INT32, &x,
329 DBUS_TYPE_INT32, &y,
330 DBUS_TYPE_INVALID);
331
332 dbus_connection_send(conn, message, NULL);
333
334 dbus_message_unref(message);
335}
336
337void sni_context_menu(struct StatusNotifierItem *item, uint32_t x, uint32_t y) {
338 const char *iface =
339 (item->kde_special_snowflake ? "org.kde.StatusNotifierItem"
340 : "org.freedesktop.StatusNotifierItem");
341 DBusMessage *message = dbus_message_new_method_call(
342 item->name,
343 "/StatusNotifierItem",
344 iface,
345 "ContextMenu");
346
347 dbus_message_append_args(message,
348 DBUS_TYPE_INT32, &x,
349 DBUS_TYPE_INT32, &y,
350 DBUS_TYPE_INVALID);
351
352 dbus_connection_send(conn, message, NULL);
353
354 dbus_message_unref(message);
355}
356void sni_secondary(struct StatusNotifierItem *item, uint32_t x, uint32_t y) {
357 const char *iface =
358 (item->kde_special_snowflake ? "org.kde.StatusNotifierItem"
359 : "org.freedesktop.StatusNotifierItem");
360 DBusMessage *message = dbus_message_new_method_call(
361 item->name,
362 "/StatusNotifierItem",
363 iface,
364 "SecondaryActivate");
365
366 dbus_message_append_args(message,
367 DBUS_TYPE_INT32, &x,
368 DBUS_TYPE_INT32, &y,
369 DBUS_TYPE_INVALID);
370
371 dbus_connection_send(conn, message, NULL);
372
373 dbus_message_unref(message);
374}
375
376static void get_unique_name(struct StatusNotifierItem *item) {
377 // I think that we're fine being sync here becaues the message is
378 // directly to the message bus. Could be async though.
379 DBusMessage *message = dbus_message_new_method_call(
380 "org.freedesktop.DBus",
381 "/org/freedesktop/DBus",
382 "org.freedesktop.DBus",
383 "GetNameOwner");
384
385 dbus_message_append_args(message,
386 DBUS_TYPE_STRING, &item->name,
387 DBUS_TYPE_INVALID);
388
389 DBusMessage *reply = dbus_connection_send_with_reply_and_block(
390 conn, message, -1, NULL);
391
392 dbus_message_unref(message);
393
394 if (!reply) {
395 sway_log(L_ERROR, "Could not get unique name for item: %s",
396 item->name);
397 return;
398 }
399
400 char *unique_name;
401 if (!dbus_message_get_args(reply, NULL,
402 DBUS_TYPE_STRING, &unique_name,
403 DBUS_TYPE_INVALID)) {
404 sway_log(L_ERROR, "Error parsing method args");
405 } else {
406 if (item->unique_name) {
407 free(item->unique_name);
408 }
409 item->unique_name = strdup(unique_name);
410 }
411
412 dbus_message_unref(reply);
413}
414
415struct StatusNotifierItem *sni_create(const char *name) {
416 struct StatusNotifierItem *item = malloc(sizeof(struct StatusNotifierItem));
417 item->name = strdup(name);
418 item->unique_name = NULL;
419 item->image = NULL;
420 item->dirty = false;
421
422 // If it doesn't use this name then assume that it uses the KDE spec
423 // This is because xembed-sni-proxy uses neither "org.freedesktop" nor
424 // "org.kde" and just gives us the items "unique name"
425 //
426 // We could use this to our advantage and fill out the "unique name"
427 // field with the given name if it is neither freedesktop or kde, but
428 // that's makes us rely on KDE hackyness which is bad practice
429 const char freedesktop_name[] = "org.freedesktop";
430 if (strncmp(name, freedesktop_name, sizeof(freedesktop_name) - 1) != 0) {
431 item->kde_special_snowflake = true;
432 } else {
433 item->kde_special_snowflake = false;
434 }
435
436 get_icon(item);
437
438 get_unique_name(item);
439
440 return item;
441}
442/* Return 0 if `item` has a name of `str` */
443int sni_str_cmp(const void *_item, const void *_str) {
444 const struct StatusNotifierItem *item = _item;
445 const char *str = _str;
446
447 return strcmp(item->name, str);
448}
449/* Returns 0 if `item` has a unique name of `str` */
450int sni_uniq_cmp(const void *_item, const void *_str) {
451 const struct StatusNotifierItem *item = _item;
452 const char *str = _str;
453
454 if (!item->unique_name) {
455 return false;
456 }
457 return strcmp(item->unique_name, str);
458}
459void sni_free(struct StatusNotifierItem *item) {
460 if (!item) {
461 return;
462 }
463 free(item->name);
464 if (item->unique_name) {
465 free(item->unique_name);
466 }
467 if (item->image) {
468 cairo_surface_destroy(item->image);
469 }
470 free(item);
471}
diff --git a/swaybar/tray/sni_watcher.c b/swaybar/tray/sni_watcher.c
new file mode 100644
index 00000000..388e181d
--- /dev/null
+++ b/swaybar/tray/sni_watcher.c
@@ -0,0 +1,487 @@
1#define _XOPEN_SOURCE 500
2#include <unistd.h>
3#include <stdio.h>
4#include <stdlib.h>
5#include <string.h>
6#include <stdbool.h>
7#include <dbus/dbus.h>
8#include "swaybar/tray/dbus.h"
9#include "list.h"
10#include "log.h"
11
12static list_t *items = NULL;
13static list_t *hosts = NULL;
14
15/**
16 * Describes the function of the StatusNotifierWatcher
17 * See https://freedesktop.org/wiki/Specifications/StatusNotifierItem/StatusNotifierWatcher/
18 *
19 * We also implement KDE's special snowflake protocol, it's like this but with
20 * all occurrences 'freedesktop' replaced with 'kde'. There is no KDE introspect.
21 */
22static const char *interface_xml =
23 "<!DOCTYPE node PUBLIC '-//freedesktop//DTD D-BUS Object Introspection 1.0//EN'"
24 "'http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd'>"
25 "<node>"
26 " <interface name='org.freedesktop.DBus.Introspectable'>"
27 " <method name='Introspect'>"
28 " <arg name='xml_data' direction='out' type='s'/>"
29 " </method>"
30 " </interface>"
31 " <interface name='org.freedesktop.DBus.Properties'>"
32 " <method name='Get'>"
33 " <arg name='interface' direction='in' type='s'/>"
34 " <arg name='propname' direction='in' type='s'/>"
35 " <arg name='value' direction='out' type='v'/>"
36 " </method>"
37 " <method name='Set'>"
38 " <arg name='interface' direction='in' type='s'/>"
39 " <arg name='propname' direction='in' type='s'/>"
40 " <arg name='value' direction='in' type='v'/>"
41 " </method>"
42 " <method name='GetAll'>"
43 " <arg name='interface' direction='in' type='s'/>"
44 " <arg name='props' direction='out' type='a{sv}'/>"
45 " </method>"
46 " </interface>"
47 " <interface name='org.freedesktop.StatusNotifierWatcher'>"
48 " <method name='RegisterStatusNotifierItem'>"
49 " <arg type='s' name='service' direction='in'/>"
50 " </method>"
51 " <method name='RegisterStatusNotifierHost'>"
52 " <arg type='s' name='service' direction='in'/>"
53 " </method>"
54 " <property name='RegisteredStatusNotifierItems' type='as' access='read'/>"
55 " <property name='IsStatusNotifierHostRegistered' type='b' access='read'/>"
56 " <property name='ProtocolVersion' type='i' access='read'/>"
57 " <signal name='StatusNotifierItemRegistered'>"
58 " <arg type='s' name='service' direction='out'/>"
59 " </signal>"
60 " <signal name='StatusNotifierItemUnregistered'>"
61 " <arg type='s' name='service' direction='out'/>"
62 " </signal>"
63 " <signal name='StatusNotifierHostRegistered'>"
64 " <arg type='' name='service' direction='out'/>"
65 " </signal>"
66 " </interface>"
67 "</node>";
68
69static void host_registered_signal(DBusConnection *connection) {
70 // Send one signal for each protocol
71 DBusMessage *signal = dbus_message_new_signal(
72 "/StatusNotifierWatcher",
73 "org.freedesktop.StatusNotifierWatcher",
74 "StatusNotifierHostRegistered");
75
76 dbus_connection_send(connection, signal, NULL);
77 dbus_message_unref(signal);
78
79
80 signal = dbus_message_new_signal(
81 "/StatusNotifierWatcher",
82 "org.kde.StatusNotifierWatcher",
83 "StatusNotifierHostRegistered");
84
85 dbus_connection_send(connection, signal, NULL);
86 dbus_message_unref(signal);
87}
88static void item_registered_signal(DBusConnection *connection, const char *name) {
89 DBusMessage *signal = dbus_message_new_signal(
90 "/StatusNotifierWatcher",
91 "org.freedesktop.StatusNotifierWatcher",
92 "StatusNotifierItemRegistered");
93 dbus_message_append_args(signal,
94 DBUS_TYPE_STRING, &name,
95 DBUS_TYPE_INVALID);
96 dbus_connection_send(connection, signal, NULL);
97 dbus_message_unref(signal);
98
99 signal = dbus_message_new_signal(
100 "/StatusNotifierWatcher",
101 "org.kde.StatusNotifierWatcher",
102 "StatusNotifierItemRegistered");
103 dbus_message_append_args(signal,
104 DBUS_TYPE_STRING, &name,
105 DBUS_TYPE_INVALID);
106 dbus_connection_send(connection, signal, NULL);
107 dbus_message_unref(signal);
108}
109static void item_unregistered_signal(DBusConnection *connection, const char *name) {
110 DBusMessage *signal = dbus_message_new_signal(
111 "/StatusNotifierWatcher",
112 "org.freedesktop.StatusNotifierWatcher",
113 "StatusNotifierItemUnregistered");
114 dbus_message_append_args(signal,
115 DBUS_TYPE_STRING, &name,
116 DBUS_TYPE_INVALID);
117 dbus_connection_send(connection, signal, NULL);
118 dbus_message_unref(signal);
119
120 signal = dbus_message_new_signal(
121 "/StatusNotifierWatcher",
122 "org.kde.StatusNotifierWatcher",
123 "StatusNotifierItemUnregistered");
124 dbus_message_append_args(signal,
125 DBUS_TYPE_STRING, &name,
126 DBUS_TYPE_INVALID);
127 dbus_connection_send(connection, signal, NULL);
128 dbus_message_unref(signal);
129}
130
131static void respond_to_introspect(DBusConnection *connection, DBusMessage *request) {
132 DBusMessage *reply;
133
134 reply = dbus_message_new_method_return(request);
135 dbus_message_append_args(reply,
136 DBUS_TYPE_STRING, &interface_xml,
137 DBUS_TYPE_INVALID);
138 dbus_connection_send(connection, reply, NULL);
139 dbus_message_unref(reply);
140}
141
142static void register_item(DBusConnection *connection, DBusMessage *message) {
143 DBusError error;
144 char *name;
145
146 dbus_error_init(&error);
147 if (!dbus_message_get_args(message, &error,
148 DBUS_TYPE_STRING, &name,
149 DBUS_TYPE_INVALID)) {
150 sway_log(L_ERROR, "Error parsing method args: %s\n", error.message);
151 }
152
153 name = strdup(name);
154 sway_log(L_INFO, "RegisterStatusNotifierItem called with \"%s\"\n", name);
155
156 // Don't add duplicate or not real item
157 if (list_seq_find(items, (int (*)(const void *, const void *))strcmp, name) != -1) {
158 return;
159 }
160 if (!dbus_bus_name_has_owner(connection, name, &error)) {
161 return;
162 }
163
164 list_add(items, name);
165 item_registered_signal(connection, name);
166
167 // It's silly, but xembedsniproxy wants a reply for this function
168 DBusMessage *reply = dbus_message_new_method_return(message);
169 dbus_connection_send(connection, reply, NULL);
170 dbus_message_unref(reply);
171}
172
173static void register_host(DBusConnection *connection, DBusMessage *message) {
174 DBusError error;
175 char *name;
176
177 dbus_error_init(&error);
178 if (!dbus_message_get_args(message, &error,
179 DBUS_TYPE_STRING, &name,
180 DBUS_TYPE_INVALID)) {
181 sway_log(L_ERROR, "Error parsing method args: %s\n", error.message);
182 }
183
184 sway_log(L_INFO, "RegisterStatusNotifierHost called with \"%s\"\n", name);
185
186 // Don't add duplicate or not real host
187 if (list_seq_find(hosts, (int (*)(const void *, const void *))strcmp, name) != -1) {
188 return;
189 }
190 if (!dbus_bus_name_has_owner(connection, name, &error)) {
191 return;
192 }
193
194 list_add(hosts, strdup(name));
195 host_registered_signal(connection);
196}
197
198static void get_property(DBusConnection *connection, DBusMessage *message) {
199 DBusError error;
200 char *interface;
201 char *property;
202
203 dbus_error_init(&error);
204 if (!dbus_message_get_args(message, &error,
205 DBUS_TYPE_STRING, &interface,
206 DBUS_TYPE_STRING, &property,
207 DBUS_TYPE_INVALID)) {
208 sway_log(L_ERROR, "Error parsing prop args: %s\n", error.message);
209 return;
210 }
211
212 if (strcmp(property, "RegisteredStatusNotifierItems") == 0) {
213 sway_log(L_INFO, "Replying with items\n");
214 DBusMessage *reply;
215 reply = dbus_message_new_method_return(message);
216 DBusMessageIter iter;
217 DBusMessageIter sub;
218 DBusMessageIter subsub;
219
220 dbus_message_iter_init_append(reply, &iter);
221
222 dbus_message_iter_open_container(&iter, DBUS_TYPE_VARIANT,
223 "as", &sub);
224 dbus_message_iter_open_container(&sub, DBUS_TYPE_ARRAY,
225 "s", &subsub);
226
227 for (int i = 0; i < items->length; ++i) {
228 dbus_message_iter_append_basic(&subsub,
229 DBUS_TYPE_STRING, &items->items[i]);
230 }
231
232 dbus_message_iter_close_container(&sub, &subsub);
233 dbus_message_iter_close_container(&iter, &sub);
234
235 dbus_connection_send(connection, reply, NULL);
236 dbus_message_unref(reply);
237 } else if (strcmp(property, "IsStatusNotifierHostRegistered") == 0) {
238 DBusMessage *reply;
239 DBusMessageIter iter;
240 DBusMessageIter sub;
241 int registered = (hosts == NULL || hosts->length == 0) ? 0 : 1;
242
243 reply = dbus_message_new_method_return(message);
244
245 dbus_message_iter_init_append(reply, &iter);
246
247 dbus_message_iter_open_container(&iter, DBUS_TYPE_VARIANT,
248 "b", &sub);
249 dbus_message_iter_append_basic(&sub,
250 DBUS_TYPE_BOOLEAN, &registered);
251
252 dbus_message_iter_close_container(&iter, &sub);
253
254 dbus_connection_send(connection, reply, NULL);
255 dbus_message_unref(reply);
256 } else if (strcmp(property, "ProtocolVersion") == 0) {
257 DBusMessage *reply;
258 DBusMessageIter iter;
259 DBusMessageIter sub;
260 const int version = 0;
261
262 reply = dbus_message_new_method_return(message);
263
264 dbus_message_iter_init_append(reply, &iter);
265
266 dbus_message_iter_open_container(&iter, DBUS_TYPE_VARIANT,
267 "i", &sub);
268 dbus_message_iter_append_basic(&sub,
269 DBUS_TYPE_INT32, &version);
270
271 dbus_message_iter_close_container(&iter, &sub);
272 dbus_connection_send(connection, reply, NULL);
273 dbus_message_unref(reply);
274 }
275}
276
277static void set_property(DBusConnection *connection, DBusMessage *message) {
278 // All properties are read only and we don't allow new properties
279 return;
280}
281
282static void get_all(DBusConnection *connection, DBusMessage *message) {
283 DBusMessage *reply;
284 reply = dbus_message_new_method_return(message);
285 DBusMessageIter iter; /* a{v} */
286 DBusMessageIter arr;
287 DBusMessageIter dict;
288 DBusMessageIter sub;
289 DBusMessageIter subsub;
290 int registered = (hosts == NULL || hosts->length == 0) ? 0 : 1;
291 const int version = 0;
292 const char *prop;
293
294 // Could clean this up with a function for each prop
295 dbus_message_iter_init_append(reply, &iter);
296 dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
297 "{sv}", &arr);
298
299 prop = "RegisteredStatusNotifierItems";
300 dbus_message_iter_open_container(&arr, DBUS_TYPE_DICT_ENTRY,
301 NULL, &dict);
302 dbus_message_iter_append_basic(&dict,
303 DBUS_TYPE_STRING, &prop);
304 dbus_message_iter_open_container(&dict, DBUS_TYPE_VARIANT,
305 "as", &sub);
306 dbus_message_iter_open_container(&sub, DBUS_TYPE_ARRAY,
307 "s", &subsub);
308 for (int i = 0; i < items->length; ++i) {
309 dbus_message_iter_append_basic(&subsub,
310 DBUS_TYPE_STRING, &items->items[i]);
311 }
312 dbus_message_iter_close_container(&sub, &subsub);
313 dbus_message_iter_close_container(&dict, &sub);
314 dbus_message_iter_close_container(&arr, &dict);
315
316 prop = "IsStatusNotifierHostRegistered";
317 dbus_message_iter_open_container(&arr, DBUS_TYPE_DICT_ENTRY,
318 NULL, &dict);
319 dbus_message_iter_append_basic(&dict,
320 DBUS_TYPE_STRING, &prop);
321 dbus_message_iter_open_container(&dict, DBUS_TYPE_VARIANT,
322 "b", &sub);
323 dbus_message_iter_append_basic(&sub,
324 DBUS_TYPE_BOOLEAN, &registered);
325 dbus_message_iter_close_container(&dict, &sub);
326 dbus_message_iter_close_container(&arr, &dict);
327
328 prop = "ProtocolVersion";
329 dbus_message_iter_open_container(&arr, DBUS_TYPE_DICT_ENTRY,
330 NULL, &dict);
331 dbus_message_iter_append_basic(&dict,
332 DBUS_TYPE_STRING, &prop);
333 dbus_message_iter_open_container(&dict, DBUS_TYPE_VARIANT,
334 "i", &sub);
335 dbus_message_iter_append_basic(&sub,
336 DBUS_TYPE_INT32, &version);
337 dbus_message_iter_close_container(&dict, &sub);
338 dbus_message_iter_close_container(&arr, &dict);
339
340 dbus_message_iter_close_container(&iter, &arr);
341
342 dbus_connection_send(connection, reply, NULL);
343 dbus_message_unref(reply);
344}
345
346static DBusHandlerResult message_handler(DBusConnection *connection,
347 DBusMessage *message, void *data) {
348 const char *interface_name = dbus_message_get_interface(message);
349 const char *member_name = dbus_message_get_member(message);
350
351 // In order of the xml above
352 if (strcmp(interface_name, "org.freedesktop.DBus.Introspectable") == 0 &&
353 strcmp(member_name, "Introspect") == 0) {
354 // We don't have an introspect for KDE
355 respond_to_introspect(connection, message);
356 return DBUS_HANDLER_RESULT_HANDLED;
357 } else if (strcmp(interface_name, "org.freedesktop.DBus.Properties") == 0) {
358 if (strcmp(member_name, "Get") == 0) {
359 get_property(connection, message);
360 return DBUS_HANDLER_RESULT_HANDLED;
361 } else if (strcmp(member_name, "Set") == 0) {
362 set_property(connection, message);
363 return DBUS_HANDLER_RESULT_HANDLED;
364 } else if (strcmp(member_name, "GetAll") == 0) {
365 get_all(connection, message);
366 return DBUS_HANDLER_RESULT_HANDLED;
367 } else {
368 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
369 }
370 } else if (strcmp(interface_name, "org.freedesktop.StatusNotifierWatcher") == 0 ||
371 strcmp(interface_name, "org.kde.StatusNotifierWatcher") == 0) {
372 if (strcmp(member_name, "RegisterStatusNotifierItem") == 0) {
373 register_item(connection, message);
374 return DBUS_HANDLER_RESULT_HANDLED;
375 } else if (strcmp(member_name, "RegisterStatusNotifierHost") == 0) {
376 register_host(connection, message);
377 return DBUS_HANDLER_RESULT_HANDLED;
378 } else {
379 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
380 }
381 }
382 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
383}
384
385static DBusHandlerResult signal_handler(DBusConnection *connection,
386 DBusMessage *message, void *_data) {
387 if (dbus_message_is_signal(message, "org.freedesktop.DBus", "NameOwnerChanged")) {
388 // Only eat the message if it is name that we are watching
389 const char *name;
390 const char *old_owner;
391 const char *new_owner;
392 int index;
393 if (!dbus_message_get_args(message, NULL,
394 DBUS_TYPE_STRING, &name,
395 DBUS_TYPE_STRING, &old_owner,
396 DBUS_TYPE_STRING, &new_owner,
397 DBUS_TYPE_INVALID)) {
398 sway_log(L_ERROR, "Error getting LostName args");
399 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
400 }
401 if (strcmp(new_owner, "") != 0) {
402 // Name is not lost
403 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
404 }
405 if ((index = list_seq_find(items, (int (*)(const void *, const void *))strcmp, name)) != -1) {
406 sway_log(L_INFO, "Status Notifier Item lost %s", name);
407 free(items->items[index]);
408 list_del(items, index);
409 item_unregistered_signal(connection, name);
410
411 return DBUS_HANDLER_RESULT_HANDLED;
412 }
413 if ((index = list_seq_find(hosts, (int (*)(const void *, const void *))strcmp, name)) != -1) {
414 sway_log(L_INFO, "Status Notifier Host lost %s", name);
415 free(hosts->items[index]);
416 list_del(hosts, index);
417
418 return DBUS_HANDLER_RESULT_HANDLED;
419 }
420 }
421 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
422}
423
424static const DBusObjectPathVTable vtable = {
425 .message_function = message_handler,
426 .unregister_function = NULL,
427};
428
429int init_sni_watcher() {
430 DBusError error;
431 dbus_error_init(&error);
432 if (!conn) {
433 sway_log(L_ERROR, "Connection is null, cannot initiate StatusNotifierWatcher");
434 return -1;
435 }
436
437 items = create_list();
438 hosts = create_list();
439
440 int status = dbus_bus_request_name(conn, "org.freedesktop.StatusNotifierWatcher",
441 DBUS_NAME_FLAG_REPLACE_EXISTING,
442 &error);
443 if (status == DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
444 sway_log(L_DEBUG, "Got watcher name");
445 } else if (status == DBUS_REQUEST_NAME_REPLY_IN_QUEUE) {
446 sway_log(L_INFO, "Could not get watcher name, it may start later");
447 }
448 if (dbus_error_is_set(&error)) {
449 sway_log(L_ERROR, "dbus err getting watcher name: %s\n", error.message);
450 return -1;
451 }
452
453 status = dbus_bus_request_name(conn, "org.kde.StatusNotifierWatcher",
454 DBUS_NAME_FLAG_REPLACE_EXISTING,
455 &error);
456 if (status == DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
457 sway_log(L_DEBUG, "Got kde watcher name");
458 } else if (status == DBUS_REQUEST_NAME_REPLY_IN_QUEUE) {
459 sway_log(L_INFO, "Could not get kde watcher name, it may start later");
460 }
461 if (dbus_error_is_set(&error)) {
462 sway_log(L_ERROR, "dbus err getting kde watcher name: %s\n", error.message);
463 return -1;
464 }
465
466 dbus_connection_try_register_object_path(conn,
467 "/StatusNotifierWatcher",
468 &vtable, NULL, &error);
469 if (dbus_error_is_set(&error)) {
470 sway_log(L_ERROR, "dbus_err: %s\n", error.message);
471 return -1;
472 }
473
474 dbus_bus_add_match(conn,
475 "type='signal',\
476 sender='org.freedesktop.DBus',\
477 interface='org.freedesktop.DBus',\
478 member='NameOwnerChanged'",
479 &error);
480
481 if (dbus_error_is_set(&error)) {
482 sway_log(L_ERROR, "DBus error getting match args: %s", error.message);
483 }
484
485 dbus_connection_add_filter(conn, signal_handler, NULL, NULL);
486 return 0;
487}
diff --git a/swaybar/tray/tray.c b/swaybar/tray/tray.c
new file mode 100644
index 00000000..00f1a44f
--- /dev/null
+++ b/swaybar/tray/tray.c
@@ -0,0 +1,393 @@
1#define _XOPEN_SOURCE 500
2#include <unistd.h>
3#include <stdlib.h>
4#include <string.h>
5#include <sys/wait.h>
6#include <dbus/dbus.h>
7#include "swaybar/bar.h"
8#include "swaybar/tray/tray.h"
9#include "swaybar/tray/dbus.h"
10#include "swaybar/tray/sni.h"
11#include "swaybar/tray/sni_watcher.h"
12#include "swaybar/bar.h"
13#include "swaybar/config.h"
14#include "list.h"
15#include "log.h"
16
17struct tray *tray;
18
19static void register_host(char *name) {
20 DBusMessage *message;
21
22 message = dbus_message_new_method_call(
23 "org.freedesktop.StatusNotifierWatcher",
24 "/StatusNotifierWatcher",
25 "org.freedesktop.StatusNotifierWatcher",
26 "RegisterStatusNotifierHost");
27 if (!message) {
28 sway_log(L_ERROR, "Cannot allocate dbus method call");
29 return;
30 }
31
32 dbus_message_append_args(message,
33 DBUS_TYPE_STRING, &name,
34 DBUS_TYPE_INVALID);
35
36 dbus_connection_send(conn, message, NULL);
37
38 dbus_message_unref(message);
39}
40
41static void get_items_reply(DBusPendingCall *pending, void *_data) {
42 DBusMessage *reply = dbus_pending_call_steal_reply(pending);
43
44 if (!reply) {
45 sway_log(L_ERROR, "Got no items reply from sni watcher");
46 goto bail;
47 }
48
49 int message_type = dbus_message_get_type(reply);
50
51 if (message_type == DBUS_MESSAGE_TYPE_ERROR) {
52 char *msg;
53
54 dbus_message_get_args(reply, NULL,
55 DBUS_TYPE_STRING, &msg,
56 DBUS_TYPE_INVALID);
57
58 sway_log(L_ERROR, "Message is error: %s", msg);
59 goto bail;
60 }
61
62 DBusMessageIter iter;
63 DBusMessageIter variant;
64 DBusMessageIter array;
65
66 dbus_message_iter_init(reply, &iter);
67 if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) {
68 sway_log(L_ERROR, "Replyed with wrong type, not v(as)");
69 goto bail;
70 }
71 dbus_message_iter_recurse(&iter, &variant);
72 if (dbus_message_iter_get_arg_type(&variant) != DBUS_TYPE_ARRAY ||
73 dbus_message_iter_get_element_type(&variant) != DBUS_TYPE_STRING) {
74 sway_log(L_ERROR, "Replyed with wrong type, not v(as)");
75 goto bail;
76 }
77
78 // Clear list
79 list_foreach(tray->items, (void (*)(void *))sni_free);
80 list_free(tray->items);
81 tray->items = create_list();
82
83 // O(n) function, could be faster dynamically reading values
84 int len = dbus_message_iter_get_element_count(&variant);
85
86 dbus_message_iter_recurse(&variant, &array);
87 for (int i = 0; i < len; i++) {
88 const char *name;
89 dbus_message_iter_get_basic(&array, &name);
90
91 struct StatusNotifierItem *item = sni_create(name);
92
93 sway_log(L_DEBUG, "Item registered with host: %s", name);
94 list_add(tray->items, item);
95 dirty = true;
96 }
97
98bail:
99 dbus_message_unref(reply);
100 return;
101}
102static void get_items() {
103 DBusPendingCall *pending;
104 DBusMessage *message = dbus_message_new_method_call(
105 "org.freedesktop.StatusNotifierWatcher",
106 "/StatusNotifierWatcher",
107 "org.freedesktop.DBus.Properties",
108 "Get");
109
110 const char *iface = "org.freedesktop.StatusNotifierWatcher";
111 const char *prop = "RegisteredStatusNotifierItems";
112 dbus_message_append_args(message,
113 DBUS_TYPE_STRING, &iface,
114 DBUS_TYPE_STRING, &prop,
115 DBUS_TYPE_INVALID);
116
117 bool status =
118 dbus_connection_send_with_reply(conn, message, &pending, -1);
119 dbus_message_unref(message);
120
121 if (!(pending || status)) {
122 sway_log(L_ERROR, "Could not get items");
123 return;
124 }
125
126 dbus_pending_call_set_notify(pending, get_items_reply, NULL, NULL);
127}
128
129static DBusHandlerResult signal_handler(DBusConnection *connection,
130 DBusMessage *message, void *_data) {
131 if (dbus_message_is_signal(message, "org.freedesktop.StatusNotifierWatcher",
132 "StatusNotifierItemRegistered")) {
133 const char *name;
134 if (!dbus_message_get_args(message, NULL,
135 DBUS_TYPE_STRING, &name,
136 DBUS_TYPE_INVALID)) {
137 sway_log(L_ERROR, "Error getting StatusNotifierItemRegistered args");
138 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
139 }
140
141 if (list_seq_find(tray->items, sni_str_cmp, name) == -1) {
142 struct StatusNotifierItem *item = sni_create(name);
143
144 list_add(tray->items, item);
145 dirty = true;
146 }
147
148 return DBUS_HANDLER_RESULT_HANDLED;
149 } else if (dbus_message_is_signal(message, "org.freedesktop.StatusNotifierWatcher",
150 "StatusNotifierItemUnregistered")) {
151 const char *name;
152 if (!dbus_message_get_args(message, NULL,
153 DBUS_TYPE_STRING, &name,
154 DBUS_TYPE_INVALID)) {
155 sway_log(L_ERROR, "Error getting StatusNotifierItemUnregistered args");
156 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
157 }
158
159 int index;
160 if ((index = list_seq_find(tray->items, sni_str_cmp, name)) != -1) {
161 sni_free(tray->items->items[index]);
162 list_del(tray->items, index);
163 dirty = true;
164 } else {
165 // If it's not in our list, then our list is incorrect.
166 // Fetch all items again
167 sway_log(L_INFO, "Host item list incorrect, refreshing");
168 get_items();
169 }
170
171 return DBUS_HANDLER_RESULT_HANDLED;
172 } else if (dbus_message_is_signal(message, "org.freedesktop.StatusNotifierItem",
173 "NewIcon") || dbus_message_is_signal(message,
174 "org.kde.StatusNotifierItem", "NewIcon")) {
175 const char *name;
176 int index;
177 struct StatusNotifierItem *item;
178
179 name = dbus_message_get_sender(message);
180 if ((index = list_seq_find(tray->items, sni_uniq_cmp, name)) != -1) {
181 item = tray->items->items[index];
182 sway_log(L_INFO, "NewIcon signal from item %s", item->name);
183 get_icon(item);
184 }
185
186 return DBUS_HANDLER_RESULT_HANDLED;
187 }
188 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
189}
190
191static int init_host() {
192 tray = (struct tray *)malloc(sizeof(tray));
193
194 tray->items = create_list();
195
196 DBusError error;
197 dbus_error_init(&error);
198 char *name = NULL;
199 if (!conn) {
200 sway_log(L_ERROR, "Connection is null, cannot init SNI host");
201 goto err;
202 }
203 name = calloc(sizeof(char), 256);
204
205 if (!name) {
206 sway_log(L_ERROR, "Cannot allocate name");
207 goto err;
208 }
209
210 pid_t pid = getpid();
211 if (snprintf(name, 256, "org.freedesktop.StatusNotifierHost-%d", pid)
212 >= 256) {
213 sway_log(L_ERROR, "Cannot get host name because string is too short."
214 "This should not happen");
215 goto err;
216 }
217
218 // We want to be the sole owner of this name
219 if (dbus_bus_request_name(conn, name, DBUS_NAME_FLAG_DO_NOT_QUEUE,
220 &error) != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
221 sway_log(L_ERROR, "Cannot get host name and start the tray");
222 goto err;
223 }
224 if (dbus_error_is_set(&error)) {
225 sway_log(L_ERROR, "Dbus err getting host name: %s\n", error.message);
226 goto err;
227 }
228 sway_log(L_DEBUG, "Got host name");
229
230 register_host(name);
231
232 get_items();
233
234 // Perhaps use addmatch helper functions like wlc does?
235 dbus_bus_add_match(conn,
236 "type='signal',\
237 sender='org.freedesktop.StatusNotifierWatcher',\
238 member='StatusNotifierItemRegistered'",
239 &error);
240 if (dbus_error_is_set(&error)) {
241 sway_log(L_ERROR, "dbus_err: %s", error.message);
242 goto err;
243 }
244 dbus_bus_add_match(conn,
245 "type='signal',\
246 sender='org.freedesktop.StatusNotifierWatcher',\
247 member='StatusNotifierItemUnregistered'",
248 &error);
249 if (dbus_error_is_set(&error)) {
250 sway_log(L_ERROR, "dbus_err: %s", error.message);
251 return -1;
252 }
253
254 // SNI matches
255 dbus_bus_add_match(conn,
256 "type='signal',\
257 interface='org.freedesktop.StatusNotifierItem',\
258 member='NewIcon'",
259 &error);
260 if (dbus_error_is_set(&error)) {
261 sway_log(L_ERROR, "dbus_err %s", error.message);
262 goto err;
263 }
264 dbus_bus_add_match(conn,
265 "type='signal',\
266 interface='org.kde.StatusNotifierItem',\
267 member='NewIcon'",
268 &error);
269 if (dbus_error_is_set(&error)) {
270 sway_log(L_ERROR, "dbus_err %s", error.message);
271 goto err;
272 }
273
274 dbus_connection_add_filter(conn, signal_handler, NULL, NULL);
275
276 free(name);
277 return 0;
278
279err:
280 // TODO better handle errors
281 free(name);
282 return -1;
283}
284
285void tray_mouse_event(struct output *output, int x, int y,
286 uint32_t button, uint32_t state) {
287
288 struct window *window = output->window;
289 uint32_t tray_padding = swaybar.config->tray_padding;
290 int tray_width = window->width * window->scale;
291
292 for (int i = 0; i < output->items->length; ++i) {
293 struct sni_icon_ref *item =
294 output->items->items[i];
295 int icon_width = cairo_image_surface_get_width(item->icon);
296
297 tray_width -= tray_padding;
298 if (x <= tray_width && x >= tray_width - icon_width) {
299 if (button == swaybar.config->activate_button) {
300 sni_activate(item->ref, x, y);
301 } else if (button == swaybar.config->context_button) {
302 sni_context_menu(item->ref, x, y);
303 } else if (button == swaybar.config->secondary_button) {
304 sni_secondary(item->ref, x, y);
305 }
306 break;
307 }
308 tray_width -= icon_width;
309 }
310}
311
312uint32_t tray_render(struct output *output, struct config *config) {
313 struct window *window = output->window;
314 cairo_t *cairo = window->cairo;
315
316 // Tray icons
317 uint32_t tray_padding = config->tray_padding;
318 uint32_t tray_width = window->width * window->scale;
319 const int item_size = (window->height * window->scale) - (2 * tray_padding);
320
321 if (item_size < 0) {
322 // Can't render items if the padding is too large
323 return tray_width;
324 }
325
326 if (config->tray_output && strcmp(config->tray_output, output->name) != 0) {
327 return tray_width;
328 }
329
330 for (int i = 0; i < tray->items->length; ++i) {
331 struct StatusNotifierItem *item =
332 tray->items->items[i];
333 if (!item->image) {
334 continue;
335 }
336
337 struct sni_icon_ref *render_item = NULL;
338 int j;
339 for (j = i; j < output->items->length; ++j) {
340 struct sni_icon_ref *ref =
341 output->items->items[j];
342 if (ref->ref == item) {
343 render_item = ref;
344 break;
345 } else {
346 sni_icon_ref_free(ref);
347 list_del(output->items, j);
348 }
349 }
350
351 if (!render_item) {
352 render_item = sni_icon_ref_create(item, item_size);
353 list_add(output->items, render_item);
354 } else if (item->dirty) {
355 // item needs re-render
356 sni_icon_ref_free(render_item);
357 output->items->items[j] = render_item =
358 sni_icon_ref_create(item, item_size);
359 }
360
361 tray_width -= tray_padding;
362 tray_width -= item_size;
363
364 cairo_operator_t op = cairo_get_operator(cairo);
365 cairo_set_operator(cairo, CAIRO_OPERATOR_OVER);
366 cairo_set_source_surface(cairo, render_item->icon, tray_width, tray_padding);
367 cairo_rectangle(cairo, tray_width, tray_padding, item_size, item_size);
368 cairo_fill(cairo);
369 cairo_set_operator(cairo, op);
370
371 item->dirty = false;
372 }
373
374
375 if (tray_width != window->width * window->scale) {
376 tray_width -= tray_padding;
377 }
378
379 return tray_width;
380}
381
382void init_tray(struct bar *bar) {
383 if (!bar->config->tray_output || strcmp(bar->config->tray_output, "none") != 0) {
384 /* Connect to the D-Bus */
385 dbus_init();
386
387 /* Start the SNI watcher */
388 init_sni_watcher();
389
390 /* Start the SNI host */
391 init_host();
392 }
393}