aboutsummaryrefslogtreecommitdiffstats
path: root/swaybar/tray
diff options
context:
space:
mode:
authorLibravatar Drew DeVault <sir@cmpwn.com>2018-03-28 23:04:20 -0400
committerLibravatar Drew DeVault <sir@cmpwn.com>2018-03-29 22:11:08 -0400
commitcab1352801b62d1b8a12ca1c995cb24445ce4bc9 (patch)
treebc67373916c06d48700c4f69b8c2470a2f86887f /swaybar/tray
parentAllow sway IPC clients to fall back to i3 socket (diff)
downloadsway-cab1352801b62d1b8a12ca1c995cb24445ce4bc9.tar.gz
sway-cab1352801b62d1b8a12ca1c995cb24445ce4bc9.tar.zst
sway-cab1352801b62d1b8a12ca1c995cb24445ce4bc9.zip
Start port of swaybar to layer shell
This starts up the event loop and wayland display and shims out the basic top level rendering concepts. Also includes some changes to incorporate pango into the 1.x codebase properly.
Diffstat (limited to 'swaybar/tray')
-rw-r--r--swaybar/tray/dbus.c197
-rw-r--r--swaybar/tray/icon.c400
-rw-r--r--swaybar/tray/sni.c481
-rw-r--r--swaybar/tray/sni_watcher.c497
-rw-r--r--swaybar/tray/tray.c398
5 files changed, 0 insertions, 1973 deletions
diff --git a/swaybar/tray/dbus.c b/swaybar/tray/dbus.c
deleted file mode 100644
index 8e719fd9..00000000
--- a/swaybar/tray/dbus.c
+++ /dev/null
@@ -1,197 +0,0 @@
1#define _XOPEN_SOURCE 700
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, NULL);
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 timer_delete(*timer);
125 free(timer);
126 }
127}
128
129static bool should_dispatch = true;
130
131static void dispatch_status(DBusConnection *connection, DBusDispatchStatus new_status,
132 void *_data) {
133 if (new_status == DBUS_DISPATCH_DATA_REMAINS) {
134 should_dispatch = true;
135 }
136}
137
138/* Public functions below */
139
140void dispatch_dbus() {
141 if (!should_dispatch || !conn) {
142 return;
143 }
144
145 DBusDispatchStatus status;
146
147 do {
148 status = dbus_connection_dispatch(conn);
149 } while (status == DBUS_DISPATCH_DATA_REMAINS);
150
151 if (status != DBUS_DISPATCH_COMPLETE) {
152 sway_log(L_ERROR, "Cannot dispatch dbus events: %d", status);
153 }
154
155 should_dispatch = false;
156}
157
158int dbus_init() {
159 DBusError error;
160 dbus_error_init(&error);
161
162 conn = dbus_bus_get(DBUS_BUS_SESSION, &error);
163 if (conn == NULL) {
164 sway_log(L_INFO, "Compiled with dbus support, but unable to connect to dbus");
165 sway_log(L_INFO, "swaybar will be unable to display tray icons.");
166 return -1;
167 }
168
169 dbus_connection_set_exit_on_disconnect(conn, FALSE);
170 if (dbus_error_is_set(&error)) {
171 sway_log(L_ERROR, "Cannot get bus connection: %s\n", error.message);
172 conn = NULL;
173 return -1;
174 }
175
176 sway_log(L_INFO, "Unique name: %s\n", dbus_bus_get_unique_name(conn));
177
178 // Will be called if dispatch status changes
179 dbus_connection_set_dispatch_status_function(conn, dispatch_status, NULL, NULL);
180
181 if (!dbus_connection_set_watch_functions(conn, add_watch, remove_watch,
182 NULL, NULL, NULL)) {
183 dbus_connection_set_watch_functions(conn, NULL, NULL, NULL, NULL, NULL);
184 sway_log(L_ERROR, "Failed to activate DBUS watch functions");
185 return -1;
186 }
187
188 if (!dbus_connection_set_timeout_functions(conn, add_timeout, remove_timeout,
189 NULL, NULL, NULL)) {
190 dbus_connection_set_watch_functions(conn, NULL, NULL, NULL, NULL, NULL);
191 dbus_connection_set_timeout_functions(conn, NULL, NULL, NULL, NULL, NULL);
192 sway_log(L_ERROR, "Failed to activate DBUS timeout functions");
193 return -1;
194 }
195
196 return 0;
197}
diff --git a/swaybar/tray/icon.c b/swaybar/tray/icon.c
deleted file mode 100644
index c146bf32..00000000
--- a/swaybar/tray/icon.c
+++ /dev/null
@@ -1,400 +0,0 @@
1#define _XOPEN_SOURCE 700
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) && getline(&buf, &n, index) != -1) {
53 if (n <= sizeof(inherits) + 1) {
54 continue;
55 }
56 if (strncmp(inherits, buf, sizeof(inherits) - 1) == 0) {
57 char *themestr = buf + sizeof(inherits);
58 themes = split_string(themestr, ",");
59 break;
60 }
61 }
62 free(buf);
63
64fail:
65 free(path);
66 if (index) {
67 fclose(index);
68 }
69 return themes;
70}
71
72static bool isdir(const char *path) {
73 struct stat statbuf;
74 if (stat(path, &statbuf) != -1) {
75 if (S_ISDIR(statbuf.st_mode)) {
76 return true;
77 }
78 }
79 return false;
80
81}
82
83/**
84 * Returns the directory of a given theme if it exists.
85 * The returned pointer must be freed.
86 */
87static char *find_theme_dir(const char *theme) {
88 char *basedir;
89 char *icon_dir;
90
91 if (!theme) {
92 return NULL;
93 }
94
95 if (!(icon_dir = malloc(1024))) {
96 sway_log(L_ERROR, "Out of memory!");
97 goto fail;
98 }
99
100 if ((basedir = getenv("HOME"))) {
101 if (snprintf(icon_dir, 1024, "%s/.icons/%s", basedir, theme) >= 1024) {
102 sway_log(L_ERROR, "Path too long to render");
103 // XXX perhaps just goto trying in /usr/share? This
104 // shouldn't happen anyway, but might with a long global
105 goto fail;
106 }
107
108 if (isdir(icon_dir)) {
109 return icon_dir;
110 }
111 }
112
113 if ((basedir = getenv("XDG_DATA_DIRS"))) {
114 if (snprintf(icon_dir, 1024, "%s/icons/%s", basedir, theme) >= 1024) {
115 sway_log(L_ERROR, "Path too long to render");
116 // ditto
117 goto fail;
118 }
119
120 if (isdir(icon_dir)) {
121 return icon_dir;
122 }
123 }
124
125 // Spec says use "/usr/share/pixmaps/", but I see everything in
126 // "/usr/share/icons/" look it both, I suppose.
127 if (snprintf(icon_dir, 1024, "/usr/share/pixmaps/%s", theme) >= 1024) {
128 sway_log(L_ERROR, "Path too long to render");
129 goto fail;
130 }
131 if (isdir(icon_dir)) {
132 return icon_dir;
133 }
134
135 if (snprintf(icon_dir, 1024, "/usr/share/icons/%s", theme) >= 1024) {
136 sway_log(L_ERROR, "Path too long to render");
137 goto fail;
138 }
139 if (isdir(icon_dir)) {
140 return icon_dir;
141 }
142
143fail:
144 free(icon_dir);
145 sway_log(L_ERROR, "Could not find dir for theme: %s", theme);
146 return NULL;
147}
148
149/**
150 * Returns all theme dirs needed to be looked in for an icon.
151 * Does not check for duplicates
152 */
153static list_t *find_all_theme_dirs(const char *theme) {
154 list_t *dirs = create_list();
155 if (!dirs) {
156 return NULL;
157 }
158 char *dir = find_theme_dir(theme);
159 if (dir) {
160 list_add(dirs, dir);
161 list_t *inherits = find_inherits(dir);
162 list_cat(dirs, inherits);
163 list_free(inherits);
164 }
165 dir = find_theme_dir("hicolor");
166 if (dir) {
167 list_add(dirs, dir);
168 }
169
170 return dirs;
171}
172
173struct subdir {
174 int size;
175 char name[];
176};
177
178static int subdir_str_cmp(const void *_subdir, const void *_str) {
179 const struct subdir *subdir = _subdir;
180 const char *str = _str;
181 return strcmp(subdir->name, str);
182}
183/**
184 * Helper to find_subdirs. Acts similar to `split_string(subdirs, ",")` but
185 * generates a list of struct subdirs
186 */
187static list_t *split_subdirs(char *subdir_str) {
188 list_t *subdir_list = create_list();
189 char *copy = strdup(subdir_str);
190 if (!subdir_list || !copy) {
191 list_free(subdir_list);
192 free(copy);
193 return NULL;
194 }
195
196 char *token;
197 token = strtok(copy, ",");
198 while(token) {
199 int len = strlen(token) + 1;
200 struct subdir *subdir =
201 malloc(sizeof(struct subdir) + sizeof(char [len]));
202 if (!subdir) {
203 // Return what we have
204 return subdir_list;
205 }
206 subdir->size = 0;
207 strcpy(subdir->name, token);
208
209 list_add(subdir_list, subdir);
210
211 token = strtok(NULL, ",");
212 }
213 free(copy);
214
215 return subdir_list;
216}
217/**
218 * Returns a list of all subdirectories of a theme.
219 * Take note: the subdir names are all relative to `theme_dir` and must be
220 * combined with it to form a valid directory.
221 *
222 * Each member of the list is of type (struct subdir *) this struct contains
223 * the name of the subdir, along with size information. These must be freed
224 * bye the caller.
225 *
226 * This currently ignores min and max sizes of icons.
227 */
228static list_t* find_theme_subdirs(const char *theme_dir) {
229 const char index_name[] = "/index.theme";
230 list_t *dirs = NULL;
231 char *path = malloc(strlen(theme_dir) + sizeof(index_name));
232 FILE *index = NULL;
233 if (!path) {
234 sway_log(L_ERROR, "Failed to allocate memory");
235 goto fail;
236 }
237
238 strcpy(path, theme_dir);
239 strcat(path, index_name);
240
241 index = fopen(path, "r");
242 if (!index) {
243 sway_log(L_ERROR, "Could not open file: %s", path);
244 goto fail;
245 }
246
247 char *buf = NULL;
248 size_t n = 0;
249 const char directories[] = "Directories";
250 while (!feof(index) && getline(&buf, &n, index) != -1) {
251 if (n <= sizeof(directories) + 1) {
252 continue;
253 }
254 if (strncmp(directories, buf, sizeof(directories) - 1) == 0) {
255 char *dirstr = buf + sizeof(directories);
256 dirs = split_subdirs(dirstr);
257 break;
258 }
259 }
260 // Now, find the size of each dir
261 struct subdir *current_subdir = NULL;
262 const char size[] = "Size";
263 while (!feof(index) && getline(&buf, &n, index) != -1) {
264 if (buf[0] == '[') {
265 int len = strlen(buf);
266 if (buf[len-1] == '\n') {
267 len--;
268 }
269 // replace ']'
270 buf[len-1] = '\0';
271
272 int index;
273 if ((index = list_seq_find(dirs, subdir_str_cmp, buf+1)) != -1) {
274 current_subdir = (dirs->items[index]);
275 }
276 }
277
278 if (strncmp(size, buf, sizeof(size) - 1) == 0) {
279 if (current_subdir) {
280 current_subdir->size = atoi(buf + sizeof(size));
281 }
282 }
283 }
284 free(buf);
285fail:
286 free(path);
287 if (index) {
288 fclose(index);
289 }
290 return dirs;
291}
292
293/* Returns the file of an icon given its name and size */
294static char *find_icon_file(const char *name, int size) {
295 int namelen = strlen(name);
296 list_t *dirs = find_all_theme_dirs(swaybar.config->icon_theme);
297 if (!dirs) {
298 return NULL;
299 }
300 int min_size_diff = INT_MAX;
301 char *current_file = NULL;
302
303 for (int i = 0; i < dirs->length; ++i) {
304 char *dir = dirs->items[i];
305 list_t *subdirs = find_theme_subdirs(dir);
306
307 if (!subdirs) {
308 continue;
309 }
310
311 for (int i = 0; i < subdirs->length; ++i) {
312 struct subdir *subdir = subdirs->items[i];
313
314 // Only use an unsized if we don't already have a
315 // canidate this should probably change to allow svgs
316 if (!subdir->size && current_file) {
317 continue;
318 }
319
320 int size_diff = abs(size - subdir->size);
321
322 if (size_diff >= min_size_diff) {
323 continue;
324 }
325
326 char *path = malloc(strlen(subdir->name) + strlen(dir) + 2);
327
328 strcpy(path, dir);
329 path[strlen(dir)] = '/';
330 strcpy(path + strlen(dir) + 1, subdir->name);
331
332 DIR *icons = opendir(path);
333 if (!icons) {
334 free(path);
335 continue;
336 }
337
338 struct dirent *direntry;
339 while ((direntry = readdir(icons)) != NULL) {
340 int len = strlen(direntry->d_name);
341 if (len <= namelen + 2) { //must have some ext
342 continue;
343 }
344 if (strncmp(direntry->d_name, name, namelen) == 0) {
345 char *ext = direntry->d_name + namelen + 1;
346#ifdef WITH_GDK_PIXBUF
347 if (strcmp(ext, "png") == 0 ||
348 strcmp(ext, "xpm") == 0 ||
349 strcmp(ext, "svg") == 0) {
350#else
351 if (strcmp(ext, "png") == 0) {
352#endif
353 free(current_file);
354 char *icon_path = malloc(strlen(path) + len + 2);
355
356 strcpy(icon_path, path);
357 icon_path[strlen(path)] = '/';
358 strcpy(icon_path + strlen(path) + 1, direntry->d_name);
359 current_file = icon_path;
360 min_size_diff = size_diff;
361 }
362 }
363 }
364 free(path);
365 closedir(icons);
366 }
367 free_flat_list(subdirs);
368 }
369 free_flat_list(dirs);
370
371 return current_file;
372}
373
374cairo_surface_t *find_icon(const char *name, int size) {
375 char *image_path = find_icon_file(name, size);
376 if (image_path == NULL) {
377 return NULL;
378 }
379
380 cairo_surface_t *image = NULL;
381#ifdef WITH_GDK_PIXBUF
382 GError *err = NULL;
383 GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file(image_path, &err);
384 if (!pixbuf) {
385 sway_log(L_ERROR, "Failed to load icon image: %s", err->message);
386 }
387 image = gdk_cairo_image_surface_create_from_pixbuf(pixbuf);
388 g_object_unref(pixbuf);
389#else
390 // TODO make svg work? cairo supports it. maybe remove gdk alltogether
391 image = cairo_image_surface_create_from_png(image_path);
392#endif //WITH_GDK_PIXBUF
393 if (!image) {
394 sway_log(L_ERROR, "Could not read icon image");
395 return NULL;
396 }
397
398 free(image_path);
399 return image;
400}
diff --git a/swaybar/tray/sni.c b/swaybar/tray/sni.c
deleted file mode 100644
index c9d00657..00000000
--- a/swaybar/tray/sni.c
+++ /dev/null
@@ -1,481 +0,0 @@
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 dbus_pending_call_unref(pending);
164 return;
165 } else {
166 sway_log(L_ERROR, "Could not create image surface");
167 free(image_data);
168 }
169
170bail:
171 if (reply) {
172 dbus_message_unref(reply);
173 }
174 dbus_pending_call_unref(pending);
175 sway_log(L_ERROR, "Could not get icon from item");
176 return;
177}
178static void send_icon_msg(struct StatusNotifierItem *item) {
179 DBusPendingCall *pending;
180 DBusMessage *message = dbus_message_new_method_call(
181 item->name,
182 "/StatusNotifierItem",
183 "org.freedesktop.DBus.Properties",
184 "Get");
185 const char *iface;
186 if (item->kde_special_snowflake) {
187 iface = "org.kde.StatusNotifierItem";
188 } else {
189 iface = "org.freedesktop.StatusNotifierItem";
190 }
191 const char *prop = "IconPixmap";
192
193 dbus_message_append_args(message,
194 DBUS_TYPE_STRING, &iface,
195 DBUS_TYPE_STRING, &prop,
196 DBUS_TYPE_INVALID);
197
198 bool status =
199 dbus_connection_send_with_reply(conn, message, &pending, -1);
200
201 dbus_message_unref(message);
202
203 if (!(pending || status)) {
204 sway_log(L_ERROR, "Could not get item icon");
205 return;
206 }
207
208 dbus_pending_call_set_notify(pending, reply_icon, item, NULL);
209}
210
211/* Get an icon by its name */
212static void reply_icon_name(DBusPendingCall *pending, void *_data) {
213 struct StatusNotifierItem *item = _data;
214
215 DBusMessage *reply = dbus_pending_call_steal_reply(pending);
216
217 if (!reply) {
218 sway_log(L_INFO, "Got no icon name reply from item");
219 goto bail;
220 }
221
222 int message_type = dbus_message_get_type(reply);
223
224 if (message_type == DBUS_MESSAGE_TYPE_ERROR) {
225 char *msg;
226
227 dbus_message_get_args(reply, NULL,
228 DBUS_TYPE_STRING, &msg,
229 DBUS_TYPE_INVALID);
230
231 sway_log(L_INFO, "Could not get icon name: %s", msg);
232 goto bail;
233 }
234
235 DBusMessageIter iter; /* v[s] */
236 DBusMessageIter variant; /* s */
237
238 dbus_message_iter_init(reply, &iter);
239 if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) {
240 sway_log(L_ERROR, "Relpy type incorrect");
241 sway_log(L_ERROR, "Should be \"v\", is \"%s\"",
242 dbus_message_iter_get_signature(&iter));
243 goto bail;
244 }
245 dbus_message_iter_recurse(&iter, &variant);
246
247
248 if (dbus_message_iter_get_arg_type(&variant) != DBUS_TYPE_STRING) {
249 sway_log(L_ERROR, "Relpy type incorrect");
250 sway_log(L_ERROR, "Should be \"s\", is \"%s\"",
251 dbus_message_iter_get_signature(&iter));
252 goto bail;
253 }
254
255 char *icon_name;
256 dbus_message_iter_get_basic(&variant, &icon_name);
257
258 cairo_surface_t *image = find_icon(icon_name, 256);
259
260 if (image) {
261 sway_log(L_DEBUG, "Icon for %s found with size %d", icon_name,
262 cairo_image_surface_get_width(image));
263 if (item->image) {
264 cairo_surface_destroy(item->image);
265 }
266 item->image = image;
267 item->dirty = true;
268 dirty = true;
269
270 dbus_message_unref(reply);
271 dbus_pending_call_unref(pending);
272 return;
273 }
274
275bail:
276 if (reply) {
277 dbus_message_unref(reply);
278 }
279 dbus_pending_call_unref(pending);
280 // Now try the pixmap
281 send_icon_msg(item);
282 return;
283}
284static void send_icon_name_msg(struct StatusNotifierItem *item) {
285 DBusPendingCall *pending;
286 DBusMessage *message = dbus_message_new_method_call(
287 item->name,
288 "/StatusNotifierItem",
289 "org.freedesktop.DBus.Properties",
290 "Get");
291 const char *iface;
292 if (item->kde_special_snowflake) {
293 iface = "org.kde.StatusNotifierItem";
294 } else {
295 iface = "org.freedesktop.StatusNotifierItem";
296 }
297 const char *prop = "IconName";
298
299 dbus_message_append_args(message,
300 DBUS_TYPE_STRING, &iface,
301 DBUS_TYPE_STRING, &prop,
302 DBUS_TYPE_INVALID);
303
304 bool status =
305 dbus_connection_send_with_reply(conn, message, &pending, -1);
306
307 dbus_message_unref(message);
308
309 if (!(pending || status)) {
310 sway_log(L_ERROR, "Could not get item icon name");
311 return;
312 }
313
314 dbus_pending_call_set_notify(pending, reply_icon_name, item, NULL);
315}
316
317void get_icon(struct StatusNotifierItem *item) {
318 send_icon_name_msg(item);
319}
320
321void sni_activate(struct StatusNotifierItem *item, uint32_t x, uint32_t y) {
322 const char *iface =
323 (item->kde_special_snowflake ? "org.kde.StatusNotifierItem"
324 : "org.freedesktop.StatusNotifierItem");
325 DBusMessage *message = dbus_message_new_method_call(
326 item->name,
327 "/StatusNotifierItem",
328 iface,
329 "Activate");
330
331 dbus_message_append_args(message,
332 DBUS_TYPE_INT32, &x,
333 DBUS_TYPE_INT32, &y,
334 DBUS_TYPE_INVALID);
335
336 dbus_connection_send(conn, message, NULL);
337
338 dbus_message_unref(message);
339}
340
341void sni_context_menu(struct StatusNotifierItem *item, uint32_t x, uint32_t y) {
342 const char *iface =
343 (item->kde_special_snowflake ? "org.kde.StatusNotifierItem"
344 : "org.freedesktop.StatusNotifierItem");
345 DBusMessage *message = dbus_message_new_method_call(
346 item->name,
347 "/StatusNotifierItem",
348 iface,
349 "ContextMenu");
350
351 dbus_message_append_args(message,
352 DBUS_TYPE_INT32, &x,
353 DBUS_TYPE_INT32, &y,
354 DBUS_TYPE_INVALID);
355
356 dbus_connection_send(conn, message, NULL);
357
358 dbus_message_unref(message);
359}
360void sni_secondary(struct StatusNotifierItem *item, uint32_t x, uint32_t y) {
361 const char *iface =
362 (item->kde_special_snowflake ? "org.kde.StatusNotifierItem"
363 : "org.freedesktop.StatusNotifierItem");
364 DBusMessage *message = dbus_message_new_method_call(
365 item->name,
366 "/StatusNotifierItem",
367 iface,
368 "SecondaryActivate");
369
370 dbus_message_append_args(message,
371 DBUS_TYPE_INT32, &x,
372 DBUS_TYPE_INT32, &y,
373 DBUS_TYPE_INVALID);
374
375 dbus_connection_send(conn, message, NULL);
376
377 dbus_message_unref(message);
378}
379
380static void get_unique_name(struct StatusNotifierItem *item) {
381 // I think that we're fine being sync here becaues the message is
382 // directly to the message bus. Could be async though.
383 DBusMessage *message = dbus_message_new_method_call(
384 "org.freedesktop.DBus",
385 "/org/freedesktop/DBus",
386 "org.freedesktop.DBus",
387 "GetNameOwner");
388
389 dbus_message_append_args(message,
390 DBUS_TYPE_STRING, &item->name,
391 DBUS_TYPE_INVALID);
392
393 DBusMessage *reply = dbus_connection_send_with_reply_and_block(
394 conn, message, -1, NULL);
395
396 dbus_message_unref(message);
397
398 if (!reply) {
399 sway_log(L_ERROR, "Could not get unique name for item: %s",
400 item->name);
401 return;
402 }
403
404 char *unique_name;
405 if (!dbus_message_get_args(reply, NULL,
406 DBUS_TYPE_STRING, &unique_name,
407 DBUS_TYPE_INVALID)) {
408 sway_log(L_ERROR, "Error parsing method args");
409 } else {
410 if (item->unique_name) {
411 free(item->unique_name);
412 }
413 item->unique_name = strdup(unique_name);
414 }
415
416 dbus_message_unref(reply);
417}
418
419struct StatusNotifierItem *sni_create(const char *name) {
420 // Make sure `name` is well formed
421 if (!dbus_validate_bus_name(name, NULL)) {
422 sway_log(L_INFO, "Name (%s) is not a bus name. We cannot create an item.", name);
423 return NULL;
424 }
425
426 struct StatusNotifierItem *item = malloc(sizeof(struct StatusNotifierItem));
427 item->name = strdup(name);
428 item->unique_name = NULL;
429 item->image = NULL;
430 item->dirty = false;
431
432 // If it doesn't use this name then assume that it uses the KDE spec
433 // This is because xembed-sni-proxy uses neither "org.freedesktop" nor
434 // "org.kde" and just gives us the items "unique name"
435 //
436 // We could use this to our advantage and fill out the "unique name"
437 // field with the given name if it is neither freedesktop or kde, but
438 // that's makes us rely on KDE hackyness which is bad practice
439 const char freedesktop_name[] = "org.freedesktop";
440 if (strncmp(name, freedesktop_name, sizeof(freedesktop_name) - 1) != 0) {
441 item->kde_special_snowflake = true;
442 } else {
443 item->kde_special_snowflake = false;
444 }
445
446 get_icon(item);
447
448 get_unique_name(item);
449
450 return item;
451}
452/* Return 0 if `item` has a name of `str` */
453int sni_str_cmp(const void *_item, const void *_str) {
454 const struct StatusNotifierItem *item = _item;
455 const char *str = _str;
456
457 return strcmp(item->name, str);
458}
459/* Returns 0 if `item` has a unique name of `str` */
460int sni_uniq_cmp(const void *_item, const void *_str) {
461 const struct StatusNotifierItem *item = _item;
462 const char *str = _str;
463
464 if (!item->unique_name) {
465 return false;
466 }
467 return strcmp(item->unique_name, str);
468}
469void sni_free(struct StatusNotifierItem *item) {
470 if (!item) {
471 return;
472 }
473 free(item->name);
474 if (item->unique_name) {
475 free(item->unique_name);
476 }
477 if (item->image) {
478 cairo_surface_destroy(item->image);
479 }
480 free(item);
481}
diff --git a/swaybar/tray/sni_watcher.c b/swaybar/tray/sni_watcher.c
deleted file mode 100644
index 86453e70..00000000
--- a/swaybar/tray/sni_watcher.c
+++ /dev/null
@@ -1,497 +0,0 @@
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 sway_log(L_INFO, "RegisterStatusNotifierItem called with \"%s\"\n", name);
154
155 // Don't add duplicate or not real item
156 if (!dbus_validate_bus_name(name, NULL)) {
157 sway_log(L_INFO, "This item is not valid, we cannot keep track of it.");
158 return;
159 }
160
161 if (list_seq_find(items, (int (*)(const void *, const void *))strcmp, name) != -1) {
162 return;
163 }
164 if (!dbus_bus_name_has_owner(connection, name, &error)) {
165 return;
166 }
167
168 list_add(items, strdup(name));
169 item_registered_signal(connection, name);
170
171 // It's silly, but xembedsniproxy wants a reply for this function
172 DBusMessage *reply = dbus_message_new_method_return(message);
173 dbus_connection_send(connection, reply, NULL);
174 dbus_message_unref(reply);
175}
176
177static void register_host(DBusConnection *connection, DBusMessage *message) {
178 DBusError error;
179 char *name;
180
181 dbus_error_init(&error);
182 if (!dbus_message_get_args(message, &error,
183 DBUS_TYPE_STRING, &name,
184 DBUS_TYPE_INVALID)) {
185 sway_log(L_ERROR, "Error parsing method args: %s\n", error.message);
186 }
187
188 sway_log(L_INFO, "RegisterStatusNotifierHost called with \"%s\"\n", name);
189
190 // Don't add duplicate or not real host
191 if (!dbus_validate_bus_name(name, NULL)) {
192 sway_log(L_INFO, "This item is not valid, we cannot keep track of it.");
193 return;
194 }
195
196
197 if (list_seq_find(hosts, (int (*)(const void *, const void *))strcmp, name) != -1) {
198 return;
199 }
200 if (!dbus_bus_name_has_owner(connection, name, &error)) {
201 return;
202 }
203
204 list_add(hosts, strdup(name));
205 host_registered_signal(connection);
206}
207
208static void get_property(DBusConnection *connection, DBusMessage *message) {
209 DBusError error;
210 char *interface;
211 char *property;
212
213 dbus_error_init(&error);
214 if (!dbus_message_get_args(message, &error,
215 DBUS_TYPE_STRING, &interface,
216 DBUS_TYPE_STRING, &property,
217 DBUS_TYPE_INVALID)) {
218 sway_log(L_ERROR, "Error parsing prop args: %s\n", error.message);
219 return;
220 }
221
222 if (strcmp(property, "RegisteredStatusNotifierItems") == 0) {
223 sway_log(L_INFO, "Replying with items\n");
224 DBusMessage *reply;
225 reply = dbus_message_new_method_return(message);
226 DBusMessageIter iter;
227 DBusMessageIter sub;
228 DBusMessageIter subsub;
229
230 dbus_message_iter_init_append(reply, &iter);
231
232 dbus_message_iter_open_container(&iter, DBUS_TYPE_VARIANT,
233 "as", &sub);
234 dbus_message_iter_open_container(&sub, DBUS_TYPE_ARRAY,
235 "s", &subsub);
236
237 for (int i = 0; i < items->length; ++i) {
238 dbus_message_iter_append_basic(&subsub,
239 DBUS_TYPE_STRING, &items->items[i]);
240 }
241
242 dbus_message_iter_close_container(&sub, &subsub);
243 dbus_message_iter_close_container(&iter, &sub);
244
245 dbus_connection_send(connection, reply, NULL);
246 dbus_message_unref(reply);
247 } else if (strcmp(property, "IsStatusNotifierHostRegistered") == 0) {
248 DBusMessage *reply;
249 DBusMessageIter iter;
250 DBusMessageIter sub;
251 int registered = (hosts == NULL || hosts->length == 0) ? 0 : 1;
252
253 reply = dbus_message_new_method_return(message);
254
255 dbus_message_iter_init_append(reply, &iter);
256
257 dbus_message_iter_open_container(&iter, DBUS_TYPE_VARIANT,
258 "b", &sub);
259 dbus_message_iter_append_basic(&sub,
260 DBUS_TYPE_BOOLEAN, &registered);
261
262 dbus_message_iter_close_container(&iter, &sub);
263
264 dbus_connection_send(connection, reply, NULL);
265 dbus_message_unref(reply);
266 } else if (strcmp(property, "ProtocolVersion") == 0) {
267 DBusMessage *reply;
268 DBusMessageIter iter;
269 DBusMessageIter sub;
270 const int version = 0;
271
272 reply = dbus_message_new_method_return(message);
273
274 dbus_message_iter_init_append(reply, &iter);
275
276 dbus_message_iter_open_container(&iter, DBUS_TYPE_VARIANT,
277 "i", &sub);
278 dbus_message_iter_append_basic(&sub,
279 DBUS_TYPE_INT32, &version);
280
281 dbus_message_iter_close_container(&iter, &sub);
282 dbus_connection_send(connection, reply, NULL);
283 dbus_message_unref(reply);
284 }
285}
286
287static void set_property(DBusConnection *connection, DBusMessage *message) {
288 // All properties are read only and we don't allow new properties
289 return;
290}
291
292static void get_all(DBusConnection *connection, DBusMessage *message) {
293 DBusMessage *reply;
294 reply = dbus_message_new_method_return(message);
295 DBusMessageIter iter; /* a{v} */
296 DBusMessageIter arr;
297 DBusMessageIter dict;
298 DBusMessageIter sub;
299 DBusMessageIter subsub;
300 int registered = (hosts == NULL || hosts->length == 0) ? 0 : 1;
301 const int version = 0;
302 const char *prop;
303
304 // Could clean this up with a function for each prop
305 dbus_message_iter_init_append(reply, &iter);
306 dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
307 "{sv}", &arr);
308
309 prop = "RegisteredStatusNotifierItems";
310 dbus_message_iter_open_container(&arr, DBUS_TYPE_DICT_ENTRY,
311 NULL, &dict);
312 dbus_message_iter_append_basic(&dict,
313 DBUS_TYPE_STRING, &prop);
314 dbus_message_iter_open_container(&dict, DBUS_TYPE_VARIANT,
315 "as", &sub);
316 dbus_message_iter_open_container(&sub, DBUS_TYPE_ARRAY,
317 "s", &subsub);
318 for (int i = 0; i < items->length; ++i) {
319 dbus_message_iter_append_basic(&subsub,
320 DBUS_TYPE_STRING, &items->items[i]);
321 }
322 dbus_message_iter_close_container(&sub, &subsub);
323 dbus_message_iter_close_container(&dict, &sub);
324 dbus_message_iter_close_container(&arr, &dict);
325
326 prop = "IsStatusNotifierHostRegistered";
327 dbus_message_iter_open_container(&arr, DBUS_TYPE_DICT_ENTRY,
328 NULL, &dict);
329 dbus_message_iter_append_basic(&dict,
330 DBUS_TYPE_STRING, &prop);
331 dbus_message_iter_open_container(&dict, DBUS_TYPE_VARIANT,
332 "b", &sub);
333 dbus_message_iter_append_basic(&sub,
334 DBUS_TYPE_BOOLEAN, &registered);
335 dbus_message_iter_close_container(&dict, &sub);
336 dbus_message_iter_close_container(&arr, &dict);
337
338 prop = "ProtocolVersion";
339 dbus_message_iter_open_container(&arr, DBUS_TYPE_DICT_ENTRY,
340 NULL, &dict);
341 dbus_message_iter_append_basic(&dict,
342 DBUS_TYPE_STRING, &prop);
343 dbus_message_iter_open_container(&dict, DBUS_TYPE_VARIANT,
344 "i", &sub);
345 dbus_message_iter_append_basic(&sub,
346 DBUS_TYPE_INT32, &version);
347 dbus_message_iter_close_container(&dict, &sub);
348 dbus_message_iter_close_container(&arr, &dict);
349
350 dbus_message_iter_close_container(&iter, &arr);
351
352 dbus_connection_send(connection, reply, NULL);
353 dbus_message_unref(reply);
354}
355
356static DBusHandlerResult message_handler(DBusConnection *connection,
357 DBusMessage *message, void *data) {
358 const char *interface_name = dbus_message_get_interface(message);
359 const char *member_name = dbus_message_get_member(message);
360
361 // In order of the xml above
362 if (strcmp(interface_name, "org.freedesktop.DBus.Introspectable") == 0 &&
363 strcmp(member_name, "Introspect") == 0) {
364 // We don't have an introspect for KDE
365 respond_to_introspect(connection, message);
366 return DBUS_HANDLER_RESULT_HANDLED;
367 } else if (strcmp(interface_name, "org.freedesktop.DBus.Properties") == 0) {
368 if (strcmp(member_name, "Get") == 0) {
369 get_property(connection, message);
370 return DBUS_HANDLER_RESULT_HANDLED;
371 } else if (strcmp(member_name, "Set") == 0) {
372 set_property(connection, message);
373 return DBUS_HANDLER_RESULT_HANDLED;
374 } else if (strcmp(member_name, "GetAll") == 0) {
375 get_all(connection, message);
376 return DBUS_HANDLER_RESULT_HANDLED;
377 } else {
378 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
379 }
380 } else if (strcmp(interface_name, "org.freedesktop.StatusNotifierWatcher") == 0 ||
381 strcmp(interface_name, "org.kde.StatusNotifierWatcher") == 0) {
382 if (strcmp(member_name, "RegisterStatusNotifierItem") == 0) {
383 register_item(connection, message);
384 return DBUS_HANDLER_RESULT_HANDLED;
385 } else if (strcmp(member_name, "RegisterStatusNotifierHost") == 0) {
386 register_host(connection, message);
387 return DBUS_HANDLER_RESULT_HANDLED;
388 } else {
389 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
390 }
391 }
392 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
393}
394
395static DBusHandlerResult signal_handler(DBusConnection *connection,
396 DBusMessage *message, void *_data) {
397 if (dbus_message_is_signal(message, "org.freedesktop.DBus", "NameOwnerChanged")) {
398 // Only eat the message if it is name that we are watching
399 const char *name;
400 const char *old_owner;
401 const char *new_owner;
402 int index;
403 if (!dbus_message_get_args(message, NULL,
404 DBUS_TYPE_STRING, &name,
405 DBUS_TYPE_STRING, &old_owner,
406 DBUS_TYPE_STRING, &new_owner,
407 DBUS_TYPE_INVALID)) {
408 sway_log(L_ERROR, "Error getting LostName args");
409 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
410 }
411 if (strcmp(new_owner, "") != 0) {
412 // Name is not lost
413 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
414 }
415 if ((index = list_seq_find(items, (int (*)(const void *, const void *))strcmp, name)) != -1) {
416 sway_log(L_INFO, "Status Notifier Item lost %s", name);
417 free(items->items[index]);
418 list_del(items, index);
419 item_unregistered_signal(connection, name);
420
421 return DBUS_HANDLER_RESULT_HANDLED;
422 }
423 if ((index = list_seq_find(hosts, (int (*)(const void *, const void *))strcmp, name)) != -1) {
424 sway_log(L_INFO, "Status Notifier Host lost %s", name);
425 free(hosts->items[index]);
426 list_del(hosts, index);
427
428 return DBUS_HANDLER_RESULT_HANDLED;
429 }
430 }
431 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
432}
433
434static const DBusObjectPathVTable vtable = {
435 .message_function = message_handler,
436 .unregister_function = NULL,
437};
438
439int init_sni_watcher() {
440 DBusError error;
441 dbus_error_init(&error);
442 if (!conn) {
443 sway_log(L_ERROR, "Connection is null, cannot initiate StatusNotifierWatcher");
444 return -1;
445 }
446
447 items = create_list();
448 hosts = create_list();
449
450 int status = dbus_bus_request_name(conn, "org.freedesktop.StatusNotifierWatcher",
451 DBUS_NAME_FLAG_REPLACE_EXISTING,
452 &error);
453 if (status == DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
454 sway_log(L_DEBUG, "Got watcher name");
455 } else if (status == DBUS_REQUEST_NAME_REPLY_IN_QUEUE) {
456 sway_log(L_INFO, "Could not get watcher name, it may start later");
457 }
458 if (dbus_error_is_set(&error)) {
459 sway_log(L_ERROR, "dbus err getting watcher name: %s\n", error.message);
460 return -1;
461 }
462
463 status = dbus_bus_request_name(conn, "org.kde.StatusNotifierWatcher",
464 DBUS_NAME_FLAG_REPLACE_EXISTING,
465 &error);
466 if (status == DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
467 sway_log(L_DEBUG, "Got kde watcher name");
468 } else if (status == DBUS_REQUEST_NAME_REPLY_IN_QUEUE) {
469 sway_log(L_INFO, "Could not get kde watcher name, it may start later");
470 }
471 if (dbus_error_is_set(&error)) {
472 sway_log(L_ERROR, "dbus err getting kde watcher name: %s\n", error.message);
473 return -1;
474 }
475
476 dbus_connection_try_register_object_path(conn,
477 "/StatusNotifierWatcher",
478 &vtable, NULL, &error);
479 if (dbus_error_is_set(&error)) {
480 sway_log(L_ERROR, "dbus_err: %s\n", error.message);
481 return -1;
482 }
483
484 dbus_bus_add_match(conn,
485 "type='signal',\
486 sender='org.freedesktop.DBus',\
487 interface='org.freedesktop.DBus',\
488 member='NameOwnerChanged'",
489 &error);
490
491 if (dbus_error_is_set(&error)) {
492 sway_log(L_ERROR, "DBus error getting match args: %s", error.message);
493 }
494
495 dbus_connection_add_filter(conn, signal_handler, NULL, NULL);
496 return 0;
497}
diff --git a/swaybar/tray/tray.c b/swaybar/tray/tray.c
deleted file mode 100644
index 91c3af06..00000000
--- a/swaybar/tray/tray.c
+++ /dev/null
@@ -1,398 +0,0 @@
1#define _XOPEN_SOURCE 700
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 if (item) {
94 sway_log(L_DEBUG, "Item registered with host: %s", name);
95 list_add(tray->items, item);
96 dirty = true;
97 }
98 }
99
100bail:
101 dbus_message_unref(reply);
102 dbus_pending_call_unref(pending);
103 return;
104}
105static void get_items() {
106 DBusPendingCall *pending;
107 DBusMessage *message = dbus_message_new_method_call(
108 "org.freedesktop.StatusNotifierWatcher",
109 "/StatusNotifierWatcher",
110 "org.freedesktop.DBus.Properties",
111 "Get");
112
113 const char *iface = "org.freedesktop.StatusNotifierWatcher";
114 const char *prop = "RegisteredStatusNotifierItems";
115 dbus_message_append_args(message,
116 DBUS_TYPE_STRING, &iface,
117 DBUS_TYPE_STRING, &prop,
118 DBUS_TYPE_INVALID);
119
120 bool status =
121 dbus_connection_send_with_reply(conn, message, &pending, -1);
122 dbus_message_unref(message);
123
124 if (!(pending || status)) {
125 sway_log(L_ERROR, "Could not get items");
126 return;
127 }
128
129 dbus_pending_call_set_notify(pending, get_items_reply, NULL, NULL);
130}
131
132static DBusHandlerResult signal_handler(DBusConnection *connection,
133 DBusMessage *message, void *_data) {
134 if (dbus_message_is_signal(message, "org.freedesktop.StatusNotifierWatcher",
135 "StatusNotifierItemRegistered")) {
136 const char *name;
137 if (!dbus_message_get_args(message, NULL,
138 DBUS_TYPE_STRING, &name,
139 DBUS_TYPE_INVALID)) {
140 sway_log(L_ERROR, "Error getting StatusNotifierItemRegistered args");
141 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
142 }
143
144 if (list_seq_find(tray->items, sni_str_cmp, name) == -1) {
145 struct StatusNotifierItem *item = sni_create(name);
146
147 if (item) {
148 list_add(tray->items, item);
149 dirty = true;
150 }
151 }
152
153 return DBUS_HANDLER_RESULT_HANDLED;
154 } else if (dbus_message_is_signal(message, "org.freedesktop.StatusNotifierWatcher",
155 "StatusNotifierItemUnregistered")) {
156 const char *name;
157 if (!dbus_message_get_args(message, NULL,
158 DBUS_TYPE_STRING, &name,
159 DBUS_TYPE_INVALID)) {
160 sway_log(L_ERROR, "Error getting StatusNotifierItemUnregistered args");
161 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
162 }
163
164 int index;
165 if ((index = list_seq_find(tray->items, sni_str_cmp, name)) != -1) {
166 sni_free(tray->items->items[index]);
167 list_del(tray->items, index);
168 dirty = true;
169 } else {
170 // If it's not in our list, then our list is incorrect.
171 // Fetch all items again
172 sway_log(L_INFO, "Host item list incorrect, refreshing");
173 get_items();
174 }
175
176 return DBUS_HANDLER_RESULT_HANDLED;
177 } else if (dbus_message_is_signal(message, "org.freedesktop.StatusNotifierItem",
178 "NewIcon") || dbus_message_is_signal(message,
179 "org.kde.StatusNotifierItem", "NewIcon")) {
180 const char *name;
181 int index;
182 struct StatusNotifierItem *item;
183
184 name = dbus_message_get_sender(message);
185 if ((index = list_seq_find(tray->items, sni_uniq_cmp, name)) != -1) {
186 item = tray->items->items[index];
187 sway_log(L_INFO, "NewIcon signal from item %s", item->name);
188 get_icon(item);
189 }
190
191 return DBUS_HANDLER_RESULT_HANDLED;
192 }
193 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
194}
195
196static int init_host() {
197 tray = (struct tray *)malloc(sizeof(tray));
198
199 tray->items = create_list();
200
201 DBusError error;
202 dbus_error_init(&error);
203 char *name = NULL;
204 if (!conn) {
205 sway_log(L_ERROR, "Connection is null, cannot init SNI host");
206 goto err;
207 }
208 name = calloc(sizeof(char), 256);
209
210 if (!name) {
211 sway_log(L_ERROR, "Cannot allocate name");
212 goto err;
213 }
214
215 pid_t pid = getpid();
216 if (snprintf(name, 256, "org.freedesktop.StatusNotifierHost-%d", pid)
217 >= 256) {
218 sway_log(L_ERROR, "Cannot get host name because string is too short."
219 "This should not happen");
220 goto err;
221 }
222
223 // We want to be the sole owner of this name
224 if (dbus_bus_request_name(conn, name, DBUS_NAME_FLAG_DO_NOT_QUEUE,
225 &error) != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
226 sway_log(L_ERROR, "Cannot get host name and start the tray");
227 goto err;
228 }
229 if (dbus_error_is_set(&error)) {
230 sway_log(L_ERROR, "Dbus err getting host name: %s\n", error.message);
231 goto err;
232 }
233 sway_log(L_DEBUG, "Got host name");
234
235 register_host(name);
236
237 get_items();
238
239 // Perhaps use addmatch helper functions like wlc does?
240 dbus_bus_add_match(conn,
241 "type='signal',\
242 sender='org.freedesktop.StatusNotifierWatcher',\
243 member='StatusNotifierItemRegistered'",
244 &error);
245 if (dbus_error_is_set(&error)) {
246 sway_log(L_ERROR, "dbus_err: %s", error.message);
247 goto err;
248 }
249 dbus_bus_add_match(conn,
250 "type='signal',\
251 sender='org.freedesktop.StatusNotifierWatcher',\
252 member='StatusNotifierItemUnregistered'",
253 &error);
254 if (dbus_error_is_set(&error)) {
255 sway_log(L_ERROR, "dbus_err: %s", error.message);
256 return -1;
257 }
258
259 // SNI matches
260 dbus_bus_add_match(conn,
261 "type='signal',\
262 interface='org.freedesktop.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 dbus_bus_add_match(conn,
270 "type='signal',\
271 interface='org.kde.StatusNotifierItem',\
272 member='NewIcon'",
273 &error);
274 if (dbus_error_is_set(&error)) {
275 sway_log(L_ERROR, "dbus_err %s", error.message);
276 goto err;
277 }
278
279 dbus_connection_add_filter(conn, signal_handler, NULL, NULL);
280
281 free(name);
282 return 0;
283
284err:
285 // TODO better handle errors
286 free(name);
287 return -1;
288}
289
290void tray_mouse_event(struct output *output, int x, int y,
291 uint32_t button, uint32_t state) {
292
293 struct window *window = output->window;
294 uint32_t tray_padding = swaybar.config->tray_padding;
295 int tray_width = window->width * window->scale;
296
297 for (int i = 0; i < output->items->length; ++i) {
298 struct sni_icon_ref *item =
299 output->items->items[i];
300 int icon_width = cairo_image_surface_get_width(item->icon);
301
302 tray_width -= tray_padding;
303 if (x <= tray_width && x >= tray_width - icon_width) {
304 if (button == swaybar.config->activate_button) {
305 sni_activate(item->ref, x, y);
306 } else if (button == swaybar.config->context_button) {
307 sni_context_menu(item->ref, x, y);
308 } else if (button == swaybar.config->secondary_button) {
309 sni_secondary(item->ref, x, y);
310 }
311 break;
312 }
313 tray_width -= icon_width;
314 }
315}
316
317uint32_t tray_render(struct output *output, struct config *config) {
318 struct window *window = output->window;
319 cairo_t *cairo = window->cairo;
320
321 // Tray icons
322 uint32_t tray_padding = config->tray_padding;
323 uint32_t tray_width = window->width * window->scale;
324 const int item_size = (window->height * window->scale) - (2 * tray_padding);
325
326 if (item_size < 0) {
327 // Can't render items if the padding is too large
328 return tray_width;
329 }
330
331 if (config->tray_output && strcmp(config->tray_output, output->name) != 0) {
332 return tray_width;
333 }
334
335 for (int i = 0; i < tray->items->length; ++i) {
336 struct StatusNotifierItem *item =
337 tray->items->items[i];
338 if (!item->image) {
339 continue;
340 }
341
342 struct sni_icon_ref *render_item = NULL;
343 int j;
344 for (j = i; j < output->items->length; ++j) {
345 struct sni_icon_ref *ref =
346 output->items->items[j];
347 if (ref->ref == item) {
348 render_item = ref;
349 break;
350 } else {
351 sni_icon_ref_free(ref);
352 list_del(output->items, j);
353 }
354 }
355
356 if (!render_item) {
357 render_item = sni_icon_ref_create(item, item_size);
358 list_add(output->items, render_item);
359 } else if (item->dirty) {
360 // item needs re-render
361 sni_icon_ref_free(render_item);
362 output->items->items[j] = render_item =
363 sni_icon_ref_create(item, item_size);
364 }
365
366 tray_width -= tray_padding;
367 tray_width -= item_size;
368
369 cairo_operator_t op = cairo_get_operator(cairo);
370 cairo_set_operator(cairo, CAIRO_OPERATOR_OVER);
371 cairo_set_source_surface(cairo, render_item->icon, tray_width, tray_padding);
372 cairo_rectangle(cairo, tray_width, tray_padding, item_size, item_size);
373 cairo_fill(cairo);
374 cairo_set_operator(cairo, op);
375
376 item->dirty = false;
377 }
378
379
380 if (tray_width != window->width * window->scale) {
381 tray_width -= tray_padding;
382 }
383
384 return tray_width;
385}
386
387void init_tray(struct bar *bar) {
388 if (!bar->config->tray_output || strcmp(bar->config->tray_output, "none") != 0) {
389 /* Connect to the D-Bus */
390 dbus_init();
391
392 /* Start the SNI watcher */
393 init_sni_watcher();
394
395 /* Start the SNI host */
396 init_host();
397 }
398}