aboutsummaryrefslogtreecommitdiffstats
path: root/swaybar/tray
diff options
context:
space:
mode:
authorLibravatar Calvin Lee <cyrus296@gmail.com>2017-06-07 16:45:28 -0700
committerLibravatar Calvin Lee <cyrus296@gmail.com>2017-06-07 17:49:16 -0700
commit843ad38b3c427adb0bf319e9613d9813c8d9246c (patch)
treee02a5b06e2b6923371fd53724791c147c18a1fa4 /swaybar/tray
parentMerge pull request #1232 from johalun/master-freebsd (diff)
downloadsway-843ad38b3c427adb0bf319e9613d9813c8d9246c.tar.gz
sway-843ad38b3c427adb0bf319e9613d9813c8d9246c.tar.zst
sway-843ad38b3c427adb0bf319e9613d9813c8d9246c.zip
Implement Tray Icons
This commit implements the StatusNotifierItem protocol, and enables swaybar to show tray icons. It also uses `xembedsniproxy` in order to communicate with xembed applications. The tray is completely optional, and can be disabled on compile time with the `enable-tray` option. Or on runtime with the bar config option `tray_output none`. Overview of changes: In swaybar very little is changed outside the tray subfolder except that all events are now polled in `event_loop.c`, this creates no functional difference. Six bar configuration options were added, these are detailed in sway-bar(5) The tray subfolder is where all protocol implementation takes place and is organised as follows: tray/sni_watcher.c: This file contains the StatusNotifierWatcher. It keeps track of items and hosts and reports when they come or go. tray/tray.c This file contains the StatusNotifierHost. It keeps track of sway's version of the items and represents the tray itself. tray/sni.c This file contains the StatusNotifierItem struct and all communication with individual items. tray/icon.c This file implements the icon theme protocol. It allows for finding icons by name, rather than by pixmap. tray/dbus.c This file allows for asynchronous DBus communication. See #986 #343
Diffstat (limited to 'swaybar/tray')
-rw-r--r--swaybar/tray/dbus.c189
-rw-r--r--swaybar/tray/icon.c404
-rw-r--r--swaybar/tray/sni.c463
-rw-r--r--swaybar/tray/sni_watcher.c487
-rw-r--r--swaybar/tray/tray.c279
5 files changed, 1822 insertions, 0 deletions
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..f0638dca
--- /dev/null
+++ b/swaybar/tray/sni.c
@@ -0,0 +1,463 @@
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 if (!dbus_message_get_args(reply, NULL,
401 DBUS_TYPE_STRING, &item->unique_name,
402 DBUS_TYPE_INVALID)) {
403 item->unique_name = NULL;
404 sway_log(L_ERROR, "Error parsing method args");
405 }
406
407 dbus_message_unref(reply);
408}
409
410struct StatusNotifierItem *sni_create(const char *name) {
411 struct StatusNotifierItem *item = malloc(sizeof(struct StatusNotifierItem));
412 item->name = strdup(name);
413 item->unique_name = NULL;
414 item->image = NULL;
415 item->dirty = false;
416
417 // If it doesn't use this name then assume that it uses the KDE spec
418 // This is because xembed-sni-proxy uses neither "org.freedesktop" nor
419 // "org.kde" and just gives us the items "unique name"
420 //
421 // We could use this to our advantage and fill out the "unique name"
422 // field with the given name if it is neither freedesktop or kde, but
423 // that's makes us rely on KDE hackyness which is bad practice
424 const char freedesktop_name[] = "org.freedesktop";
425 if (strncmp(name, freedesktop_name, sizeof(freedesktop_name) - 1) != 0) {
426 item->kde_special_snowflake = true;
427 } else {
428 item->kde_special_snowflake = false;
429 }
430
431 get_icon(item);
432
433 get_unique_name(item);
434
435 return item;
436}
437/* Return true if `item` has a name of `str` */
438int sni_str_cmp(const void *_item, const void *_str) {
439 const struct StatusNotifierItem *item = _item;
440 const char *str = _str;
441
442 return strcmp(item->name, str);
443}
444/* Returns true if `item` has a unique name of `str` */
445int sni_uniq_cmp(const void *_item, const void *_str) {
446 const struct StatusNotifierItem *item = _item;
447 const char *str = _str;
448
449 if (!item->unique_name) {
450 return false;
451 }
452 return strcmp(item->unique_name, str);
453}
454void sni_free(struct StatusNotifierItem *item) {
455 if (!item) {
456 return;
457 }
458 free(item->name);
459 if (item->image) {
460 cairo_surface_destroy(item->image);
461 }
462 free(item);
463}
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..9a709fe4
--- /dev/null
+++ b/swaybar/tray/tray.c
@@ -0,0 +1,279 @@
1#define _XOPEN_SOURCE 500
2#include <unistd.h>
3#include <stdlib.h>
4#include <string.h>
5#include <dbus/dbus.h>
6#include "swaybar/bar.h"
7#include "swaybar/tray/tray.h"
8#include "swaybar/tray/dbus.h"
9#include "swaybar/tray/sni.h"
10#include "swaybar/bar.h"
11#include "list.h"
12#include "log.h"
13
14struct tray *tray;
15
16static void register_host(char *name) {
17 DBusMessage *message;
18
19 message = dbus_message_new_method_call(
20 "org.freedesktop.StatusNotifierWatcher",
21 "/StatusNotifierWatcher",
22 "org.freedesktop.StatusNotifierWatcher",
23 "RegisterStatusNotifierHost");
24 if (!message) {
25 sway_log(L_ERROR, "Cannot allocate dbus method call");
26 return;
27 }
28
29 dbus_message_append_args(message,
30 DBUS_TYPE_STRING, &name,
31 DBUS_TYPE_INVALID);
32
33 dbus_connection_send(conn, message, NULL);
34
35 dbus_message_unref(message);
36}
37
38static void get_items_reply(DBusPendingCall *pending, void *_data) {
39 DBusMessage *reply = dbus_pending_call_steal_reply(pending);
40
41 if (!reply) {
42 sway_log(L_ERROR, "Got no items reply from sni watcher");
43 goto bail;
44 }
45
46 int message_type = dbus_message_get_type(reply);
47
48 if (message_type == DBUS_MESSAGE_TYPE_ERROR) {
49 char *msg;
50
51 dbus_message_get_args(reply, NULL,
52 DBUS_TYPE_STRING, &msg,
53 DBUS_TYPE_INVALID);
54
55 sway_log(L_ERROR, "Message is error: %s", msg);
56 goto bail;
57 }
58
59 DBusMessageIter iter;
60 DBusMessageIter variant;
61 DBusMessageIter array;
62
63 dbus_message_iter_init(reply, &iter);
64 if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) {
65 sway_log(L_ERROR, "Replyed with wrong type, not v(as)");
66 goto bail;
67 }
68 dbus_message_iter_recurse(&iter, &variant);
69 if (dbus_message_iter_get_arg_type(&variant) != DBUS_TYPE_ARRAY ||
70 dbus_message_iter_get_element_type(&variant) != DBUS_TYPE_STRING) {
71 sway_log(L_ERROR, "Replyed with wrong type, not v(as)");
72 goto bail;
73 }
74
75 // Clear list
76 list_foreach(tray->items, (void (*)(void *))sni_free);
77 list_free(tray->items);
78 tray->items = create_list();
79
80 // O(n) function, could be faster dynamically reading values
81 int len = dbus_message_iter_get_element_count(&variant);
82
83 dbus_message_iter_recurse(&variant, &array);
84 for (int i = 0; i < len; i++) {
85 const char *name;
86 dbus_message_iter_get_basic(&array, &name);
87
88 struct StatusNotifierItem *item = sni_create(name);
89
90 sway_log(L_DEBUG, "Item registered with host: %s", name);
91 list_add(tray->items, item);
92 dirty = true;
93 }
94
95bail:
96 dbus_message_unref(reply);
97 return;
98}
99static void get_items() {
100 DBusPendingCall *pending;
101 DBusMessage *message = dbus_message_new_method_call(
102 "org.freedesktop.StatusNotifierWatcher",
103 "/StatusNotifierWatcher",
104 "org.freedesktop.DBus.Properties",
105 "Get");
106
107 const char *iface = "org.freedesktop.StatusNotifierWatcher";
108 const char *prop = "RegisteredStatusNotifierItems";
109 dbus_message_append_args(message,
110 DBUS_TYPE_STRING, &iface,
111 DBUS_TYPE_STRING, &prop,
112 DBUS_TYPE_INVALID);
113
114 bool status =
115 dbus_connection_send_with_reply(conn, message, &pending, -1);
116 dbus_message_unref(message);
117
118 if (!(pending || status)) {
119 sway_log(L_ERROR, "Could not get items");
120 return;
121 }
122
123 dbus_pending_call_set_notify(pending, get_items_reply, NULL, NULL);
124}
125
126static DBusHandlerResult signal_handler(DBusConnection *connection,
127 DBusMessage *message, void *_data) {
128 if (dbus_message_is_signal(message, "org.freedesktop.StatusNotifierWatcher",
129 "StatusNotifierItemRegistered")) {
130 const char *name;
131 if (!dbus_message_get_args(message, NULL,
132 DBUS_TYPE_STRING, &name,
133 DBUS_TYPE_INVALID)) {
134 sway_log(L_ERROR, "Error getting StatusNotifierItemRegistered args");
135 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
136 }
137
138 if (list_seq_find(tray->items, sni_str_cmp, name) == -1) {
139 struct StatusNotifierItem *item = sni_create(name);
140
141 list_add(tray->items, item);
142 dirty = true;
143 }
144
145 return DBUS_HANDLER_RESULT_HANDLED;
146 } else if (dbus_message_is_signal(message, "org.freedesktop.StatusNotifierWatcher",
147 "StatusNotifierItemUnregistered")) {
148 const char *name;
149 if (!dbus_message_get_args(message, NULL,
150 DBUS_TYPE_STRING, &name,
151 DBUS_TYPE_INVALID)) {
152 sway_log(L_ERROR, "Error getting StatusNotifierItemUnregistered args");
153 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
154 }
155
156 int index;
157 if ((index = list_seq_find(tray->items, sni_str_cmp, name)) != -1) {
158 sni_free(tray->items->items[index]);
159 list_del(tray->items, index);
160 dirty = true;
161 } else {
162 // If it's not in our list, then our list is incorrect.
163 // Fetch all items again
164 sway_log(L_INFO, "Host item list incorrect, refreshing");
165 get_items();
166 }
167
168 return DBUS_HANDLER_RESULT_HANDLED;
169 } else if (dbus_message_is_signal(message, "org.freedesktop.StatusNotifierItem",
170 "NewIcon") || dbus_message_is_signal(message,
171 "org.kde.StatusNotifierItem", "NewIcon")) {
172 const char *name;
173 int index;
174 struct StatusNotifierItem *item;
175
176 name = dbus_message_get_sender(message);
177 if ((index = list_seq_find(tray->items, sni_uniq_cmp, name)) != -1) {
178 item = tray->items->items[index];
179 get_icon(item);
180 }
181
182 return DBUS_HANDLER_RESULT_HANDLED;
183 }
184 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
185}
186
187int init_tray() {
188 tray = (struct tray *)malloc(sizeof(tray));
189
190 tray->items = create_list();
191
192 DBusError error;
193 dbus_error_init(&error);
194 char *name = NULL;
195 if (!conn) {
196 sway_log(L_ERROR, "Connection is null, cannot init SNI host");
197 goto err;
198 }
199 name = calloc(sizeof(char), 256);
200
201 if (!name) {
202 sway_log(L_ERROR, "Cannot allocate name");
203 goto err;
204 }
205
206 pid_t pid = getpid();
207 if (snprintf(name, 256, "org.freedesktop.StatusNotifierHost-%d", pid)
208 >= 256) {
209 sway_log(L_ERROR, "Cannot get host name because string is too short."
210 "This should not happen");
211 goto err;
212 }
213
214 // We want to be the sole owner of this name
215 if (dbus_bus_request_name(conn, name, DBUS_NAME_FLAG_DO_NOT_QUEUE,
216 &error) != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
217 sway_log(L_ERROR, "Cannot get host name and start the tray");
218 goto err;
219 }
220 if (dbus_error_is_set(&error)) {
221 sway_log(L_ERROR, "Dbus err getting host name: %s\n", error.message);
222 goto err;
223 }
224 sway_log(L_DEBUG, "Got host name");
225
226 register_host(name);
227
228 get_items();
229
230 // Perhaps use addmatch helper functions like wlc does?
231 dbus_bus_add_match(conn,
232 "type='signal',\
233 sender='org.freedesktop.StatusNotifierWatcher',\
234 member='StatusNotifierItemRegistered'",
235 &error);
236 if (dbus_error_is_set(&error)) {
237 sway_log(L_ERROR, "dbus_err: %s", error.message);
238 goto err;
239 }
240 dbus_bus_add_match(conn,
241 "type='signal',\
242 sender='org.freedesktop.StatusNotifierWatcher',\
243 member='StatusNotifierItemUnregistered'",
244 &error);
245 if (dbus_error_is_set(&error)) {
246 sway_log(L_ERROR, "dbus_err: %s", error.message);
247 return -1;
248 }
249
250 // SNI matches
251 dbus_bus_add_match(conn,
252 "type='signal',\
253 interface='org.freedesktop.StatusNotifierItem',\
254 member='NewIcon'",
255 &error);
256 if (dbus_error_is_set(&error)) {
257 sway_log(L_ERROR, "dbus_err %s", error.message);
258 goto err;
259 }
260 dbus_bus_add_match(conn,
261 "type='signal',\
262 interface='org.kde.StatusNotifierItem',\
263 member='NewIcon'",
264 &error);
265 if (dbus_error_is_set(&error)) {
266 sway_log(L_ERROR, "dbus_err %s", error.message);
267 goto err;
268 }
269
270 dbus_connection_add_filter(conn, signal_handler, NULL, NULL);
271
272 free(name);
273 return 0;
274
275err:
276 // TODO better handle errors
277 free(name);
278 return -1;
279}