summaryrefslogtreecommitdiffstats
path: root/swaybar/tray/item.c
diff options
context:
space:
mode:
Diffstat (limited to 'swaybar/tray/item.c')
-rw-r--r--swaybar/tray/item.c472
1 files changed, 472 insertions, 0 deletions
diff --git a/swaybar/tray/item.c b/swaybar/tray/item.c
new file mode 100644
index 00000000..0833dcb9
--- /dev/null
+++ b/swaybar/tray/item.c
@@ -0,0 +1,472 @@
1#define _POSIX_C_SOURCE 200809L
2#include <cairo.h>
3#include <stdbool.h>
4#include <stdlib.h>
5#include <string.h>
6#include "swaybar/bar.h"
7#include "swaybar/config.h"
8#include "swaybar/input.h"
9#include "swaybar/tray/host.h"
10#include "swaybar/tray/icon.h"
11#include "swaybar/tray/item.h"
12#include "swaybar/tray/tray.h"
13#include "background-image.h"
14#include "cairo.h"
15#include "list.h"
16#include "log.h"
17#include "wlr-layer-shell-unstable-v1-client-protocol.h"
18
19// TODO menu
20
21static bool sni_ready(struct swaybar_sni *sni) {
22 return sni->status && (sni->status[0] == 'N' ? // NeedsAttention
23 sni->attention_icon_name || sni->attention_icon_pixmap :
24 sni->icon_name || sni->icon_pixmap);
25}
26
27static void set_sni_dirty(struct swaybar_sni *sni) {
28 if (sni_ready(sni)) {
29 sni->min_size = sni->max_size = 0; // invalidate previous icon
30 set_bar_dirty(sni->tray->bar);
31 }
32}
33
34static int read_pixmap(sd_bus_message *msg, struct swaybar_sni *sni,
35 const char *prop, list_t **dest) {
36 int ret = sd_bus_message_enter_container(msg, 'a', "(iiay)");
37 if (ret < 0) {
38 wlr_log(WLR_ERROR, "%s %s: %s", sni->watcher_id, prop, strerror(-ret));
39 return ret;
40 }
41
42 if (sd_bus_message_at_end(msg, 0)) {
43 wlr_log(WLR_DEBUG, "%s %s no. of icons = 0", sni->watcher_id, prop);
44 return ret;
45 }
46
47 list_t *pixmaps = create_list();
48 if (!pixmaps) {
49 return -12; // -ENOMEM
50 }
51
52 while (!sd_bus_message_at_end(msg, 0)) {
53 ret = sd_bus_message_enter_container(msg, 'r', "iiay");
54 if (ret < 0) {
55 wlr_log(WLR_ERROR, "%s %s: %s", sni->watcher_id, prop, strerror(-ret));
56 goto error;
57 }
58
59 int size;
60 ret = sd_bus_message_read(msg, "ii", NULL, &size);
61 if (ret < 0) {
62 wlr_log(WLR_ERROR, "%s %s: %s", sni->watcher_id, prop, strerror(-ret));
63 goto error;
64 }
65
66 const void *pixels;
67 size_t npixels;
68 ret = sd_bus_message_read_array(msg, 'y', &pixels, &npixels);
69 if (ret < 0) {
70 wlr_log(WLR_ERROR, "%s %s: %s", sni->watcher_id, prop, strerror(-ret));
71 goto error;
72 }
73
74 struct swaybar_pixmap *pixmap =
75 malloc(sizeof(struct swaybar_pixmap) + npixels);
76 pixmap->size = size;
77 memcpy(pixmap->pixels, pixels, npixels);
78 list_add(pixmaps, pixmap);
79
80 sd_bus_message_exit_container(msg);
81 }
82 list_free_items_and_destroy(*dest);
83 *dest = pixmaps;
84 wlr_log(WLR_DEBUG, "%s %s no. of icons = %d", sni->watcher_id, prop,
85 pixmaps->length);
86
87 return ret;
88error:
89 list_free_items_and_destroy(pixmaps);
90 return ret;
91}
92
93struct get_property_data {
94 struct swaybar_sni *sni;
95 const char *prop;
96 const char *type;
97 void *dest;
98};
99
100static int get_property_callback(sd_bus_message *msg, void *data,
101 sd_bus_error *error) {
102 struct get_property_data *d = data;
103 struct swaybar_sni *sni = d->sni;
104 const char *prop = d->prop;
105 const char *type = d->type;
106 void *dest = d->dest;
107
108 int ret;
109 if (sd_bus_message_is_method_error(msg, NULL)) {
110 wlr_log(WLR_ERROR, "%s %s: %s", sni->watcher_id, prop,
111 sd_bus_message_get_error(msg)->message);
112 ret = sd_bus_message_get_errno(msg);
113 goto cleanup;
114 }
115
116 ret = sd_bus_message_enter_container(msg, 'v', type);
117 if (ret < 0) {
118 wlr_log(WLR_ERROR, "%s %s: %s", sni->watcher_id, prop, strerror(-ret));
119 goto cleanup;
120 }
121
122 if (!type) {
123 ret = read_pixmap(msg, sni, prop, dest);
124 if (ret < 0) {
125 goto cleanup;
126 }
127 } else {
128 if (*type == 's' || *type == 'o') {
129 free(*(char **)dest);
130 }
131
132 ret = sd_bus_message_read(msg, type, dest);
133 if (ret < 0) {
134 wlr_log(WLR_ERROR, "%s %s: %s", sni->watcher_id, prop, strerror(-ret));
135 goto cleanup;
136 }
137
138 if (*type == 's' || *type == 'o') {
139 char **str = dest;
140 *str = strdup(*str);
141 wlr_log(WLR_DEBUG, "%s %s = '%s'", sni->watcher_id, prop, *str);
142 } else if (*type == 'b') {
143 wlr_log(WLR_DEBUG, "%s %s = %s", sni->watcher_id, prop,
144 *(bool *)dest ? "true" : "false");
145 }
146 }
147
148 if (strcmp(prop, "Status") == 0 || (sni->status && (sni->status[0] == 'N' ?
149 prop[0] == 'A' : strncmp(prop, "Icon", 4) == 0))) {
150 set_sni_dirty(sni);
151 }
152cleanup:
153 free(data);
154 return ret;
155}
156
157static void sni_get_property_async(struct swaybar_sni *sni, const char *prop,
158 const char *type, void *dest) {
159 struct get_property_data *data = malloc(sizeof(struct get_property_data));
160 data->sni = sni;
161 data->prop = prop;
162 data->type = type;
163 data->dest = dest;
164 int ret = sd_bus_call_method_async(sni->tray->bus, NULL, sni->service,
165 sni->path, "org.freedesktop.DBus.Properties", "Get",
166 get_property_callback, data, "ss", sni->interface, prop);
167 if (ret < 0) {
168 wlr_log(WLR_ERROR, "%s %s: %s", sni->watcher_id, prop, strerror(-ret));
169 }
170}
171
172/*
173 * There is a quirk in sd-bus that in some systems, it is unable to get the
174 * well-known names on the bus, so it cannot identify if an incoming signal,
175 * which uses the sender's unique name, actually matches the callback's matching
176 * sender if the callback uses a well-known name, in which case it just calls
177 * the callback and hopes for the best, resulting in false positives. In the
178 * case of NewIcon & NewAttentionIcon, this doesn't affect anything, but it
179 * means that for NewStatus, if the SNI does not definitely match the sender,
180 * then the safe thing to do is to query the status independently.
181 * This function returns 1 if the SNI definitely matches the signal sender,
182 * which is returned by the calling function to indicate that signal matching
183 * can stop since it has already found the required callback, otherwise, it
184 * returns 0, which allows matching to continue.
185 */
186static int sni_check_msg_sender(struct swaybar_sni *sni, sd_bus_message *msg,
187 const char *signal) {
188 bool has_well_known_names =
189 sd_bus_creds_get_mask(sd_bus_message_get_creds(msg)) & SD_BUS_CREDS_WELL_KNOWN_NAMES;
190 if (sni->service[0] == ':' || has_well_known_names) {
191 wlr_log(WLR_DEBUG, "%s has new %s", sni->watcher_id, signal);
192 return 1;
193 } else {
194 wlr_log(WLR_DEBUG, "%s may have new %s", sni->watcher_id, signal);
195 return 0;
196 }
197}
198
199static int handle_new_icon(sd_bus_message *msg, void *data, sd_bus_error *error) {
200 struct swaybar_sni *sni = data;
201 sni_get_property_async(sni, "IconName", "s", &sni->icon_name);
202 sni_get_property_async(sni, "IconPixmap", NULL, &sni->icon_pixmap);
203 return sni_check_msg_sender(sni, msg, "icon");
204}
205
206static int handle_new_attention_icon(sd_bus_message *msg, void *data,
207 sd_bus_error *error) {
208 struct swaybar_sni *sni = data;
209 sni_get_property_async(sni, "AttentionIconName", "s", &sni->attention_icon_name);
210 sni_get_property_async(sni, "AttentionIconPixmap", NULL, &sni->attention_icon_pixmap);
211 return sni_check_msg_sender(sni, msg, "attention icon");
212}
213
214static int handle_new_status(sd_bus_message *msg, void *data, sd_bus_error *error) {
215 struct swaybar_sni *sni = data;
216 int ret = sni_check_msg_sender(sni, msg, "status");
217 if (ret == 1) {
218 char *status;
219 int r = sd_bus_message_read(msg, "s", &status);
220 if (r < 0) {
221 wlr_log(WLR_ERROR, "%s new status error: %s", sni->watcher_id, strerror(-ret));
222 ret = r;
223 } else {
224 free(sni->status);
225 sni->status = strdup(status);
226 wlr_log(WLR_DEBUG, "%s has new status = '%s'", sni->watcher_id, status);
227 set_sni_dirty(sni);
228 }
229 } else {
230 sni_get_property_async(sni, "Status", "s", &sni->status);
231 }
232
233 return ret;
234}
235
236static void sni_match_signal(struct swaybar_sni *sni, sd_bus_slot **slot,
237 char *signal, sd_bus_message_handler_t callback) {
238 int ret = sd_bus_match_signal(sni->tray->bus, slot, sni->service, sni->path,
239 sni->interface, signal, callback, sni);
240 if (ret < 0) {
241 wlr_log(WLR_ERROR, "Failed to subscribe to signal %s: %s", signal,
242 strerror(-ret));
243 }
244}
245
246struct swaybar_sni *create_sni(char *id, struct swaybar_tray *tray) {
247 struct swaybar_sni *sni = calloc(1, sizeof(struct swaybar_sni));
248 if (!sni) {
249 return NULL;
250 }
251 sni->tray = tray;
252 sni->watcher_id = strdup(id);
253 char *path_ptr = strchr(id, '/');
254 if (!path_ptr) {
255 sni->service = strdup(id);
256 sni->path = strdup("/StatusNotifierItem");
257 sni->interface = "org.freedesktop.StatusNotifierItem";
258 } else {
259 sni->service = strndup(id, path_ptr - id);
260 sni->path = strdup(path_ptr);
261 sni->interface = "org.kde.StatusNotifierItem";
262 sni_get_property_async(sni, "IconThemePath", "s", &sni->icon_theme_path);
263 }
264
265 // Ignored: Category, Id, Title, WindowId, OverlayIconName,
266 // OverlayIconPixmap, AttentionMovieName, ToolTip
267 sni_get_property_async(sni, "Status", "s", &sni->status);
268 sni_get_property_async(sni, "IconName", "s", &sni->icon_name);
269 sni_get_property_async(sni, "IconPixmap", NULL, &sni->icon_pixmap);
270 sni_get_property_async(sni, "AttentionIconName", "s", &sni->attention_icon_name);
271 sni_get_property_async(sni, "AttentionIconPixmap", NULL, &sni->attention_icon_pixmap);
272 sni_get_property_async(sni, "ItemIsMenu", "b", &sni->item_is_menu);
273 sni_get_property_async(sni, "Menu", "o", &sni->menu);
274
275 sni_match_signal(sni, &sni->new_icon_slot, "NewIcon", handle_new_icon);
276 sni_match_signal(sni, &sni->new_attention_icon_slot, "NewAttentionIcon",
277 handle_new_attention_icon);
278 sni_match_signal(sni, &sni->new_status_slot, "NewStatus", handle_new_status);
279
280 return sni;
281}
282
283void destroy_sni(struct swaybar_sni *sni) {
284 if (!sni) {
285 return;
286 }
287
288 sd_bus_slot_unref(sni->new_icon_slot);
289 sd_bus_slot_unref(sni->new_attention_icon_slot);
290 sd_bus_slot_unref(sni->new_status_slot);
291
292 free(sni->watcher_id);
293 free(sni->service);
294 free(sni->path);
295 free(sni->status);
296 free(sni->icon_name);
297 free(sni->icon_pixmap);
298 free(sni->attention_icon_name);
299 free(sni->menu);
300 free(sni);
301}
302
303static void handle_click(struct swaybar_sni *sni, int x, int y,
304 enum x11_button button, int delta) {
305 const char *method = sni->tray->bar->config->tray_bindings[button];
306 if (!method) {
307 static const char *default_bindings[10] = {
308 "nop",
309 "Activate",
310 "SecondaryActivate",
311 "ContextMenu",
312 "ScrollUp",
313 "ScrollDown",
314 "ScrollLeft",
315 "ScrollRight",
316 "nop",
317 "nop"
318 };
319 method = default_bindings[button];
320 }
321 if (strcmp(method, "nop") == 0) {
322 return;
323 }
324 if (sni->item_is_menu && strcmp(method, "Activate") == 0) {
325 method = "ContextMenu";
326 }
327
328 if (strncmp(method, "Scroll", strlen("Scroll")) == 0) {
329 char dir = method[strlen("Scroll")];
330 char *orientation = (dir = 'U' || dir == 'D') ? "vertical" : "horizontal";
331 int sign = (dir == 'U' || dir == 'L') ? -1 : 1;
332
333 sd_bus_call_method_async(sni->tray->bus, NULL, sni->service, sni->path,
334 sni->interface, "Scroll", NULL, NULL, "is", delta*sign, orientation);
335 } else {
336 sd_bus_call_method_async(sni->tray->bus, NULL, sni->service, sni->path,
337 sni->interface, method, NULL, NULL, "ii", x, y);
338 }
339}
340
341static int cmp_sni_id(const void *item, const void *cmp_to) {
342 const struct swaybar_sni *sni = item;
343 return strcmp(sni->watcher_id, cmp_to);
344}
345
346static enum hotspot_event_handling icon_hotspot_callback(
347 struct swaybar_output *output, struct swaybar_hotspot *hotspot,
348 int x, int y, enum x11_button button, void *data) {
349 wlr_log(WLR_DEBUG, "Clicked on %s", (char *)data);
350
351 struct swaybar_tray *tray = output->bar->tray;
352 int idx = list_seq_find(tray->items, cmp_sni_id, data);
353
354 if (idx != -1) {
355 struct swaybar_sni *sni = tray->items->items[idx];
356 // guess global position since wayland doesn't expose it
357 struct swaybar_config *config = tray->bar->config;
358 int global_x = output->output_x + config->gaps.left + x;
359 bool top_bar = config->position & ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP;
360 int global_y = output->output_y + (top_bar ? config->gaps.top + y:
361 (int) output->output_height - config->gaps.bottom - y);
362
363 wlr_log(WLR_DEBUG, "Guessing click position at (%d, %d)", global_x, global_y);
364 handle_click(sni, global_x, global_y, button, 1); // TODO get delta from event
365 return HOTSPOT_IGNORE;
366 } else {
367 wlr_log(WLR_DEBUG, "but it doesn't exist");
368 }
369
370 return HOTSPOT_PROCESS;
371}
372
373uint32_t render_sni(cairo_t *cairo, struct swaybar_output *output, double *x,
374 struct swaybar_sni *sni) {
375 uint32_t height = output->height * output->scale;
376 int padding = output->bar->config->tray_padding;
377 int ideal_size = height - 2*padding;
378 if ((ideal_size < sni->min_size || ideal_size > sni->max_size) && sni_ready(sni)) {
379 bool icon_found = false;
380 char *icon_name = sni->status[0] == 'N' ?
381 sni->attention_icon_name : sni->icon_name;
382 if (icon_name) {
383 char *icon_path = find_icon(sni->tray->themes, sni->tray->basedirs,
384 icon_name, ideal_size, output->bar->config->icon_theme,
385 &sni->min_size, &sni->max_size);
386 if (!icon_path && sni->icon_theme_path) {
387 icon_path = find_icon_in_dir(icon_name, sni->icon_theme_path,
388 &sni->min_size, &sni->max_size);
389 }
390 if (icon_path) {
391 cairo_surface_destroy(sni->icon);
392 sni->icon = load_background_image(icon_path);
393 free(icon_path);
394 icon_found = true;
395 }
396 }
397 if (!icon_found) {
398 list_t *pixmaps = sni->status[0] == 'N' ?
399 sni->attention_icon_pixmap : sni->icon_pixmap;
400 if (pixmaps) {
401 int idx = -1;
402 unsigned smallest_error = -1; // UINT_MAX
403 for (int i = 0; i < pixmaps->length; ++i) {
404 struct swaybar_pixmap *pixmap = pixmaps->items[i];
405 unsigned error = (ideal_size - pixmap->size) *
406 (ideal_size < pixmap->size ? -1 : 1);
407 if (error < smallest_error) {
408 smallest_error = error;
409 idx = i;
410 }
411 }
412 struct swaybar_pixmap *pixmap = pixmaps->items[idx];
413 cairo_surface_destroy(sni->icon);
414 sni->icon = cairo_image_surface_create_for_data(pixmap->pixels,
415 CAIRO_FORMAT_ARGB32, pixmap->size, pixmap->size,
416 cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, pixmap->size));
417 }
418 }
419 }
420
421 int icon_size;
422 cairo_surface_t *icon;
423 if (sni->icon) {
424 int actual_size = cairo_image_surface_get_height(sni->icon);
425 icon_size = actual_size < ideal_size ?
426 actual_size*(ideal_size/actual_size) : ideal_size;
427 icon = cairo_image_surface_scale(sni->icon, icon_size, icon_size);
428 } else { // draw a :(
429 icon_size = ideal_size*0.8;
430 icon = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, icon_size, icon_size);
431 cairo_t *cairo_icon = cairo_create(icon);
432 cairo_set_source_u32(cairo_icon, 0xFF0000FF);
433 cairo_translate(cairo_icon, icon_size/2, icon_size/2);
434 cairo_scale(cairo_icon, icon_size/2, icon_size/2);
435 cairo_arc(cairo_icon, 0, 0, 1, 0, 7);
436 cairo_fill(cairo_icon);
437 cairo_set_operator(cairo_icon, CAIRO_OPERATOR_CLEAR);
438 cairo_arc(cairo_icon, 0.35, -0.3, 0.1, 0, 7);
439 cairo_fill(cairo_icon);
440 cairo_arc(cairo_icon, -0.35, -0.3, 0.1, 0, 7);
441 cairo_fill(cairo_icon);
442 cairo_arc(cairo_icon, 0, 0.75, 0.5, 3.71238898038469, 5.71238898038469);
443 cairo_set_line_width(cairo_icon, 0.1);
444 cairo_stroke(cairo_icon);
445 cairo_destroy(cairo_icon);
446 }
447
448 int padded_size = icon_size + 2*padding;
449 *x -= padded_size;
450 int y = floor((height - padded_size) / 2.0);
451
452 cairo_operator_t op = cairo_get_operator(cairo);
453 cairo_set_operator(cairo, CAIRO_OPERATOR_OVER);
454 cairo_set_source_surface(cairo, icon, *x + padding, y + padding);
455 cairo_rectangle(cairo, *x, y, padded_size, padded_size);
456 cairo_fill(cairo);
457 cairo_set_operator(cairo, op);
458
459 cairo_surface_destroy(icon);
460
461 struct swaybar_hotspot *hotspot = calloc(1, sizeof(struct swaybar_hotspot));
462 hotspot->x = *x;
463 hotspot->y = 0;
464 hotspot->width = height;
465 hotspot->height = height;
466 hotspot->callback = icon_hotspot_callback;
467 hotspot->destroy = free;
468 hotspot->data = strdup(sni->watcher_id);
469 wl_list_insert(&output->hotspots, &hotspot->link);
470
471 return output->height;
472}