summaryrefslogtreecommitdiffstats
path: root/swaybar/tray/tray.c
diff options
context:
space:
mode:
Diffstat (limited to 'swaybar/tray/tray.c')
-rw-r--r--swaybar/tray/tray.c393
1 files changed, 393 insertions, 0 deletions
diff --git a/swaybar/tray/tray.c b/swaybar/tray/tray.c
new file mode 100644
index 00000000..00f1a44f
--- /dev/null
+++ b/swaybar/tray/tray.c
@@ -0,0 +1,393 @@
1#define _XOPEN_SOURCE 500
2#include <unistd.h>
3#include <stdlib.h>
4#include <string.h>
5#include <sys/wait.h>
6#include <dbus/dbus.h>
7#include "swaybar/bar.h"
8#include "swaybar/tray/tray.h"
9#include "swaybar/tray/dbus.h"
10#include "swaybar/tray/sni.h"
11#include "swaybar/tray/sni_watcher.h"
12#include "swaybar/bar.h"
13#include "swaybar/config.h"
14#include "list.h"
15#include "log.h"
16
17struct tray *tray;
18
19static void register_host(char *name) {
20 DBusMessage *message;
21
22 message = dbus_message_new_method_call(
23 "org.freedesktop.StatusNotifierWatcher",
24 "/StatusNotifierWatcher",
25 "org.freedesktop.StatusNotifierWatcher",
26 "RegisterStatusNotifierHost");
27 if (!message) {
28 sway_log(L_ERROR, "Cannot allocate dbus method call");
29 return;
30 }
31
32 dbus_message_append_args(message,
33 DBUS_TYPE_STRING, &name,
34 DBUS_TYPE_INVALID);
35
36 dbus_connection_send(conn, message, NULL);
37
38 dbus_message_unref(message);
39}
40
41static void get_items_reply(DBusPendingCall *pending, void *_data) {
42 DBusMessage *reply = dbus_pending_call_steal_reply(pending);
43
44 if (!reply) {
45 sway_log(L_ERROR, "Got no items reply from sni watcher");
46 goto bail;
47 }
48
49 int message_type = dbus_message_get_type(reply);
50
51 if (message_type == DBUS_MESSAGE_TYPE_ERROR) {
52 char *msg;
53
54 dbus_message_get_args(reply, NULL,
55 DBUS_TYPE_STRING, &msg,
56 DBUS_TYPE_INVALID);
57
58 sway_log(L_ERROR, "Message is error: %s", msg);
59 goto bail;
60 }
61
62 DBusMessageIter iter;
63 DBusMessageIter variant;
64 DBusMessageIter array;
65
66 dbus_message_iter_init(reply, &iter);
67 if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) {
68 sway_log(L_ERROR, "Replyed with wrong type, not v(as)");
69 goto bail;
70 }
71 dbus_message_iter_recurse(&iter, &variant);
72 if (dbus_message_iter_get_arg_type(&variant) != DBUS_TYPE_ARRAY ||
73 dbus_message_iter_get_element_type(&variant) != DBUS_TYPE_STRING) {
74 sway_log(L_ERROR, "Replyed with wrong type, not v(as)");
75 goto bail;
76 }
77
78 // Clear list
79 list_foreach(tray->items, (void (*)(void *))sni_free);
80 list_free(tray->items);
81 tray->items = create_list();
82
83 // O(n) function, could be faster dynamically reading values
84 int len = dbus_message_iter_get_element_count(&variant);
85
86 dbus_message_iter_recurse(&variant, &array);
87 for (int i = 0; i < len; i++) {
88 const char *name;
89 dbus_message_iter_get_basic(&array, &name);
90
91 struct StatusNotifierItem *item = sni_create(name);
92
93 sway_log(L_DEBUG, "Item registered with host: %s", name);
94 list_add(tray->items, item);
95 dirty = true;
96 }
97
98bail:
99 dbus_message_unref(reply);
100 return;
101}
102static void get_items() {
103 DBusPendingCall *pending;
104 DBusMessage *message = dbus_message_new_method_call(
105 "org.freedesktop.StatusNotifierWatcher",
106 "/StatusNotifierWatcher",
107 "org.freedesktop.DBus.Properties",
108 "Get");
109
110 const char *iface = "org.freedesktop.StatusNotifierWatcher";
111 const char *prop = "RegisteredStatusNotifierItems";
112 dbus_message_append_args(message,
113 DBUS_TYPE_STRING, &iface,
114 DBUS_TYPE_STRING, &prop,
115 DBUS_TYPE_INVALID);
116
117 bool status =
118 dbus_connection_send_with_reply(conn, message, &pending, -1);
119 dbus_message_unref(message);
120
121 if (!(pending || status)) {
122 sway_log(L_ERROR, "Could not get items");
123 return;
124 }
125
126 dbus_pending_call_set_notify(pending, get_items_reply, NULL, NULL);
127}
128
129static DBusHandlerResult signal_handler(DBusConnection *connection,
130 DBusMessage *message, void *_data) {
131 if (dbus_message_is_signal(message, "org.freedesktop.StatusNotifierWatcher",
132 "StatusNotifierItemRegistered")) {
133 const char *name;
134 if (!dbus_message_get_args(message, NULL,
135 DBUS_TYPE_STRING, &name,
136 DBUS_TYPE_INVALID)) {
137 sway_log(L_ERROR, "Error getting StatusNotifierItemRegistered args");
138 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
139 }
140
141 if (list_seq_find(tray->items, sni_str_cmp, name) == -1) {
142 struct StatusNotifierItem *item = sni_create(name);
143
144 list_add(tray->items, item);
145 dirty = true;
146 }
147
148 return DBUS_HANDLER_RESULT_HANDLED;
149 } else if (dbus_message_is_signal(message, "org.freedesktop.StatusNotifierWatcher",
150 "StatusNotifierItemUnregistered")) {
151 const char *name;
152 if (!dbus_message_get_args(message, NULL,
153 DBUS_TYPE_STRING, &name,
154 DBUS_TYPE_INVALID)) {
155 sway_log(L_ERROR, "Error getting StatusNotifierItemUnregistered args");
156 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
157 }
158
159 int index;
160 if ((index = list_seq_find(tray->items, sni_str_cmp, name)) != -1) {
161 sni_free(tray->items->items[index]);
162 list_del(tray->items, index);
163 dirty = true;
164 } else {
165 // If it's not in our list, then our list is incorrect.
166 // Fetch all items again
167 sway_log(L_INFO, "Host item list incorrect, refreshing");
168 get_items();
169 }
170
171 return DBUS_HANDLER_RESULT_HANDLED;
172 } else if (dbus_message_is_signal(message, "org.freedesktop.StatusNotifierItem",
173 "NewIcon") || dbus_message_is_signal(message,
174 "org.kde.StatusNotifierItem", "NewIcon")) {
175 const char *name;
176 int index;
177 struct StatusNotifierItem *item;
178
179 name = dbus_message_get_sender(message);
180 if ((index = list_seq_find(tray->items, sni_uniq_cmp, name)) != -1) {
181 item = tray->items->items[index];
182 sway_log(L_INFO, "NewIcon signal from item %s", item->name);
183 get_icon(item);
184 }
185
186 return DBUS_HANDLER_RESULT_HANDLED;
187 }
188 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
189}
190
191static int init_host() {
192 tray = (struct tray *)malloc(sizeof(tray));
193
194 tray->items = create_list();
195
196 DBusError error;
197 dbus_error_init(&error);
198 char *name = NULL;
199 if (!conn) {
200 sway_log(L_ERROR, "Connection is null, cannot init SNI host");
201 goto err;
202 }
203 name = calloc(sizeof(char), 256);
204
205 if (!name) {
206 sway_log(L_ERROR, "Cannot allocate name");
207 goto err;
208 }
209
210 pid_t pid = getpid();
211 if (snprintf(name, 256, "org.freedesktop.StatusNotifierHost-%d", pid)
212 >= 256) {
213 sway_log(L_ERROR, "Cannot get host name because string is too short."
214 "This should not happen");
215 goto err;
216 }
217
218 // We want to be the sole owner of this name
219 if (dbus_bus_request_name(conn, name, DBUS_NAME_FLAG_DO_NOT_QUEUE,
220 &error) != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
221 sway_log(L_ERROR, "Cannot get host name and start the tray");
222 goto err;
223 }
224 if (dbus_error_is_set(&error)) {
225 sway_log(L_ERROR, "Dbus err getting host name: %s\n", error.message);
226 goto err;
227 }
228 sway_log(L_DEBUG, "Got host name");
229
230 register_host(name);
231
232 get_items();
233
234 // Perhaps use addmatch helper functions like wlc does?
235 dbus_bus_add_match(conn,
236 "type='signal',\
237 sender='org.freedesktop.StatusNotifierWatcher',\
238 member='StatusNotifierItemRegistered'",
239 &error);
240 if (dbus_error_is_set(&error)) {
241 sway_log(L_ERROR, "dbus_err: %s", error.message);
242 goto err;
243 }
244 dbus_bus_add_match(conn,
245 "type='signal',\
246 sender='org.freedesktop.StatusNotifierWatcher',\
247 member='StatusNotifierItemUnregistered'",
248 &error);
249 if (dbus_error_is_set(&error)) {
250 sway_log(L_ERROR, "dbus_err: %s", error.message);
251 return -1;
252 }
253
254 // SNI matches
255 dbus_bus_add_match(conn,
256 "type='signal',\
257 interface='org.freedesktop.StatusNotifierItem',\
258 member='NewIcon'",
259 &error);
260 if (dbus_error_is_set(&error)) {
261 sway_log(L_ERROR, "dbus_err %s", error.message);
262 goto err;
263 }
264 dbus_bus_add_match(conn,
265 "type='signal',\
266 interface='org.kde.StatusNotifierItem',\
267 member='NewIcon'",
268 &error);
269 if (dbus_error_is_set(&error)) {
270 sway_log(L_ERROR, "dbus_err %s", error.message);
271 goto err;
272 }
273
274 dbus_connection_add_filter(conn, signal_handler, NULL, NULL);
275
276 free(name);
277 return 0;
278
279err:
280 // TODO better handle errors
281 free(name);
282 return -1;
283}
284
285void tray_mouse_event(struct output *output, int x, int y,
286 uint32_t button, uint32_t state) {
287
288 struct window *window = output->window;
289 uint32_t tray_padding = swaybar.config->tray_padding;
290 int tray_width = window->width * window->scale;
291
292 for (int i = 0; i < output->items->length; ++i) {
293 struct sni_icon_ref *item =
294 output->items->items[i];
295 int icon_width = cairo_image_surface_get_width(item->icon);
296
297 tray_width -= tray_padding;
298 if (x <= tray_width && x >= tray_width - icon_width) {
299 if (button == swaybar.config->activate_button) {
300 sni_activate(item->ref, x, y);
301 } else if (button == swaybar.config->context_button) {
302 sni_context_menu(item->ref, x, y);
303 } else if (button == swaybar.config->secondary_button) {
304 sni_secondary(item->ref, x, y);
305 }
306 break;
307 }
308 tray_width -= icon_width;
309 }
310}
311
312uint32_t tray_render(struct output *output, struct config *config) {
313 struct window *window = output->window;
314 cairo_t *cairo = window->cairo;
315
316 // Tray icons
317 uint32_t tray_padding = config->tray_padding;
318 uint32_t tray_width = window->width * window->scale;
319 const int item_size = (window->height * window->scale) - (2 * tray_padding);
320
321 if (item_size < 0) {
322 // Can't render items if the padding is too large
323 return tray_width;
324 }
325
326 if (config->tray_output && strcmp(config->tray_output, output->name) != 0) {
327 return tray_width;
328 }
329
330 for (int i = 0; i < tray->items->length; ++i) {
331 struct StatusNotifierItem *item =
332 tray->items->items[i];
333 if (!item->image) {
334 continue;
335 }
336
337 struct sni_icon_ref *render_item = NULL;
338 int j;
339 for (j = i; j < output->items->length; ++j) {
340 struct sni_icon_ref *ref =
341 output->items->items[j];
342 if (ref->ref == item) {
343 render_item = ref;
344 break;
345 } else {
346 sni_icon_ref_free(ref);
347 list_del(output->items, j);
348 }
349 }
350
351 if (!render_item) {
352 render_item = sni_icon_ref_create(item, item_size);
353 list_add(output->items, render_item);
354 } else if (item->dirty) {
355 // item needs re-render
356 sni_icon_ref_free(render_item);
357 output->items->items[j] = render_item =
358 sni_icon_ref_create(item, item_size);
359 }
360
361 tray_width -= tray_padding;
362 tray_width -= item_size;
363
364 cairo_operator_t op = cairo_get_operator(cairo);
365 cairo_set_operator(cairo, CAIRO_OPERATOR_OVER);
366 cairo_set_source_surface(cairo, render_item->icon, tray_width, tray_padding);
367 cairo_rectangle(cairo, tray_width, tray_padding, item_size, item_size);
368 cairo_fill(cairo);
369 cairo_set_operator(cairo, op);
370
371 item->dirty = false;
372 }
373
374
375 if (tray_width != window->width * window->scale) {
376 tray_width -= tray_padding;
377 }
378
379 return tray_width;
380}
381
382void init_tray(struct bar *bar) {
383 if (!bar->config->tray_output || strcmp(bar->config->tray_output, "none") != 0) {
384 /* Connect to the D-Bus */
385 dbus_init();
386
387 /* Start the SNI watcher */
388 init_sni_watcher();
389
390 /* Start the SNI host */
391 init_host();
392 }
393}