aboutsummaryrefslogtreecommitdiffstats
path: root/swaybar/tray/sni.c
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/sni.c
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/sni.c')
-rw-r--r--swaybar/tray/sni.c463
1 files changed, 463 insertions, 0 deletions
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}