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.c443
1 files changed, 443 insertions, 0 deletions
diff --git a/swaybar/tray/item.c b/swaybar/tray/item.c
new file mode 100644
index 00000000..41cacd16
--- /dev/null
+++ b/swaybar/tray/item.c
@@ -0,0 +1,443 @@
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' ?
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_DEBUG, "Failed to read property %s: %s", prop, strerror(-ret));
39 return ret;
40 }
41
42 if (sd_bus_message_at_end(msg, 0)) {
43 return ret;
44 }
45
46 list_t *pixmaps = create_list();
47 if (!pixmaps) {
48 return -12; // -ENOMEM
49 }
50
51 while (!sd_bus_message_at_end(msg, 0)) {
52 ret = sd_bus_message_enter_container(msg, 'r', "iiay");
53 if (ret < 0) {
54 wlr_log(WLR_DEBUG, "Failed to read property %s: %s", prop, strerror(-ret));
55 goto error;
56 }
57
58 int size;
59 ret = sd_bus_message_read(msg, "ii", NULL, &size);
60 if (ret < 0) {
61 wlr_log(WLR_DEBUG, "Failed to read property %s: %s", prop, strerror(-ret));
62 goto error;
63 }
64
65 const void *pixels;
66 size_t npixels;
67 ret = sd_bus_message_read_array(msg, 'y', &pixels, &npixels);
68 if (ret < 0) {
69 wlr_log(WLR_DEBUG, "Failed to read property %s: %s", prop, strerror(-ret));
70 goto error;
71 }
72
73 struct swaybar_pixmap *pixmap =
74 malloc(sizeof(struct swaybar_pixmap) + npixels);
75 pixmap->size = size;
76 memcpy(pixmap->pixels, pixels, npixels);
77 list_add(pixmaps, pixmap);
78
79 sd_bus_message_exit_container(msg);
80 }
81 *dest = pixmaps;
82
83 return ret;
84error:
85 list_free_items_and_destroy(pixmaps);
86 return ret;
87}
88
89struct get_property_data {
90 struct swaybar_sni *sni;
91 const char *prop;
92 const char *type;
93 void *dest;
94};
95
96static int get_property_callback(sd_bus_message *msg, void *data,
97 sd_bus_error *error) {
98 struct get_property_data *d = data;
99 struct swaybar_sni *sni = d->sni;
100 const char *prop = d->prop;
101 const char *type = d->type;
102 void *dest = d->dest;
103
104 int ret;
105 if (sd_bus_message_is_method_error(msg, NULL)) {
106 sd_bus_error err = *sd_bus_message_get_error(msg);
107 wlr_log(WLR_DEBUG, "Failed to get property %s: %s", prop, err.message);
108 ret = -sd_bus_error_get_errno(&err);
109 goto cleanup;
110 }
111
112 ret = sd_bus_message_enter_container(msg, 'v', type);
113 if (ret < 0) {
114 wlr_log(WLR_DEBUG, "Failed to read property %s: %s", prop, strerror(-ret));
115 goto cleanup;
116 }
117
118 if (!type) {
119 ret = read_pixmap(msg, sni, prop, dest);
120 if (ret < 0) {
121 goto cleanup;
122 }
123 } else {
124 ret = sd_bus_message_read(msg, type, dest);
125 if (ret < 0) {
126 wlr_log(WLR_DEBUG, "Failed to read property %s: %s", prop,
127 strerror(-ret));
128 goto cleanup;
129 } else if (*type == 's' || *type == 'o') {
130 char **str = dest;
131 *str = strdup(*str);
132 }
133 }
134
135 if (strcmp(prop, "Status") == 0 || (sni->status && (sni->status[0] == 'N' ?
136 prop[0] == 'A' : strncmp(prop, "Icon", 4) == 0))) {
137 set_sni_dirty(sni);
138 }
139cleanup:
140 free(data);
141 return ret;
142}
143
144static void sni_get_property_async(struct swaybar_sni *sni, const char *prop,
145 const char *type, void *dest) {
146 struct get_property_data *data = malloc(sizeof(struct get_property_data));
147 data->sni = sni;
148 data->prop = prop;
149 data->type = type;
150 data->dest = dest;
151 int ret = sd_bus_call_method_async(sni->tray->bus, NULL, sni->service,
152 sni->path, "org.freedesktop.DBus.Properties", "Get",
153 get_property_callback, data, "ss", sni->interface, prop);
154 if (ret < 0) {
155 wlr_log(WLR_DEBUG, "Failed to get property %s: %s", prop, strerror(-ret));
156 }
157}
158
159static int handle_new_icon(sd_bus_message *msg, void *data, sd_bus_error *error) {
160 struct swaybar_sni *sni = data;
161 wlr_log(WLR_DEBUG, "%s has new IconName", sni->watcher_id);
162
163 free(sni->icon_name);
164 sni->icon_name = NULL;
165 sni_get_property_async(sni, "IconName", "s", &sni->icon_name);
166
167 list_free_items_and_destroy(sni->icon_pixmap);
168 sni->icon_pixmap = NULL;
169 sni_get_property_async(sni, "IconPixmap", NULL, &sni->icon_pixmap);
170
171 return 0;
172}
173
174static int handle_new_attention_icon(sd_bus_message *msg, void *data,
175 sd_bus_error *error) {
176 struct swaybar_sni *sni = data;
177 wlr_log(WLR_DEBUG, "%s has new AttentionIconName", sni->watcher_id);
178
179 free(sni->attention_icon_name);
180 sni->attention_icon_name = NULL;
181 sni_get_property_async(sni, "AttentionIconName", "s", &sni->attention_icon_name);
182
183 list_free_items_and_destroy(sni->attention_icon_pixmap);
184 sni->attention_icon_pixmap = NULL;
185 sni_get_property_async(sni, "AttentionIconPixmap", NULL, &sni->attention_icon_pixmap);
186
187 return 0;
188}
189
190static int handle_new_status(sd_bus_message *msg, void *data, sd_bus_error *error) {
191 char *status;
192 int ret = sd_bus_message_read(msg, "s", &status);
193 if (ret < 0) {
194 wlr_log(WLR_DEBUG, "Failed to read new status message: %s", strerror(-ret));
195 } else {
196 struct swaybar_sni *sni = data;
197 free(sni->status);
198 sni->status = strdup(status);
199 wlr_log(WLR_DEBUG, "%s has new Status '%s'", sni->watcher_id, status);
200 set_sni_dirty(sni);
201 }
202 return ret;
203}
204
205static void sni_match_signal(struct swaybar_sni *sni, char *signal,
206 sd_bus_message_handler_t callback) {
207 int ret = sd_bus_match_signal(sni->tray->bus, NULL, sni->service, sni->path,
208 sni->interface, signal, callback, sni);
209 if (ret < 0) {
210 wlr_log(WLR_DEBUG, "Failed to subscribe to signal %s: %s", signal,
211 strerror(-ret));
212 }
213}
214
215struct swaybar_sni *create_sni(char *id, struct swaybar_tray *tray) {
216 struct swaybar_sni *sni = calloc(1, sizeof(struct swaybar_sni));
217 if (!sni) {
218 return NULL;
219 }
220 sni->tray = tray;
221 sni->watcher_id = strdup(id);
222 char *path_ptr = strchr(id, '/');
223 if (!path_ptr) {
224 sni->service = strdup(id);
225 sni->path = strdup("/StatusNotifierItem");
226 sni->interface = "org.freedesktop.StatusNotifierItem";
227 } else {
228 sni->service = strndup(id, path_ptr - id);
229 sni->path = strdup(path_ptr);
230 sni->interface = "org.kde.StatusNotifierItem";
231 sni_get_property_async(sni, "IconThemePath", "s", &sni->icon_theme_path);
232 }
233
234 // Ignored: Category, Id, Title, WindowId, OverlayIconName,
235 // OverlayIconPixmap, AttentionMovieName, ToolTip
236 sni_get_property_async(sni, "Status", "s", &sni->status);
237 sni_get_property_async(sni, "IconName", "s", &sni->icon_name);
238 sni_get_property_async(sni, "IconPixmap", NULL, &sni->icon_pixmap);
239 sni_get_property_async(sni, "AttentionIconName", "s", &sni->attention_icon_name);
240 sni_get_property_async(sni, "AttentionIconPixmap", NULL, &sni->attention_icon_pixmap);
241 sni_get_property_async(sni, "ItemIsMenu", "b", &sni->item_is_menu);
242 sni_get_property_async(sni, "Menu", "o", &sni->menu);
243
244 sni_match_signal(sni, "NewIcon", handle_new_icon);
245 sni_match_signal(sni, "NewAttentionIcon", handle_new_attention_icon);
246 sni_match_signal(sni, "NewStatus", handle_new_status);
247
248 return sni;
249}
250
251void destroy_sni(struct swaybar_sni *sni) {
252 if (!sni) {
253 return;
254 }
255
256 free(sni->watcher_id);
257 free(sni->service);
258 free(sni->path);
259 free(sni->status);
260 free(sni->icon_name);
261 free(sni->icon_pixmap);
262 free(sni->attention_icon_name);
263 free(sni->menu);
264 free(sni);
265}
266
267static void handle_click(struct swaybar_sni *sni, int x, int y,
268 enum x11_button button, int delta) {
269 const char *method = sni->tray->bar->config->tray_bindings[button];
270 if (!method) {
271 static const char *default_bindings[10] = {
272 "nop",
273 "Activate",
274 "SecondaryActivate",
275 "ContextMenu",
276 "ScrollUp",
277 "ScrollDown",
278 "ScrollLeft",
279 "ScrollRight",
280 "nop",
281 "nop"
282 };
283 method = default_bindings[button];
284 }
285 if (strcmp(method, "nop") == 0) {
286 return;
287 }
288 if (sni->item_is_menu && strcmp(method, "Activate") == 0) {
289 method = "ContextMenu";
290 }
291
292 if (strncmp(method, "Scroll", strlen("Scroll")) == 0) {
293 char dir = method[strlen("Scroll")];
294 char *orientation = (dir = 'U' || dir == 'D') ? "vertical" : "horizontal";
295 int sign = (dir == 'U' || dir == 'L') ? -1 : 1;
296
297 int ret = sd_bus_call_method_async(sni->tray->bus, NULL, sni->service,
298 sni->path, sni->interface, "Scroll", NULL, NULL, "is",
299 delta*sign, orientation);
300 if (ret < 0) {
301 wlr_log(WLR_DEBUG, "Failed to scroll on SNI: %s", strerror(-ret));
302 }
303 } else {
304 int ret = sd_bus_call_method_async(sni->tray->bus, NULL, sni->service,
305 sni->path, sni->interface, method, NULL, NULL, "ii", x, y);
306 if (ret < 0) {
307 wlr_log(WLR_DEBUG, "Failed to click on SNI: %s", strerror(-ret));
308 }
309 }
310}
311
312static int cmp_sni_id(const void *item, const void *cmp_to) {
313 const struct swaybar_sni *sni = item;
314 return strcmp(sni->watcher_id, cmp_to);
315}
316
317static enum hotspot_event_handling icon_hotspot_callback(
318 struct swaybar_output *output, struct swaybar_hotspot *hotspot,
319 int x, int y, enum x11_button button, void *data) {
320 wlr_log(WLR_DEBUG, "Clicked on Status Notifier Item '%s'", (char *)data);
321
322 struct swaybar_tray *tray = output->bar->tray;
323 int idx = list_seq_find(tray->items, cmp_sni_id, data);
324
325 if (idx != -1) {
326 struct swaybar_sni *sni = tray->items->items[idx];
327 // guess global position since wayland doesn't expose it
328 struct swaybar_config *config = tray->bar->config;
329 int global_x = output->output_x + config->gaps.left + x;
330 bool top_bar = config->position & ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP;
331 int global_y = output->output_y + (top_bar ? config->gaps.top + y:
332 (int) output->output_height - config->gaps.bottom - y);
333
334 wlr_log(WLR_DEBUG, "Guessing click at (%d, %d)", global_x, global_y);
335 handle_click(sni, global_x, global_y, button, 1); // TODO get delta from event
336 return HOTSPOT_IGNORE;
337 } else {
338 wlr_log(WLR_DEBUG, "but it doesn't exist");
339 }
340
341 return HOTSPOT_PROCESS;
342}
343
344uint32_t render_sni(cairo_t *cairo, struct swaybar_output *output, double *x,
345 struct swaybar_sni *sni) {
346 uint32_t height = output->height * output->scale;
347 int padding = output->bar->config->tray_padding;
348 int ideal_size = height - 2*padding;
349 if ((ideal_size < sni->min_size || ideal_size > sni->max_size) && sni_ready(sni)) {
350 bool icon_found = false;
351 char *icon_name = sni->status[0] == 'N' ?
352 sni->attention_icon_name : sni->icon_name;
353 if (icon_name) {
354 char *icon_path = find_icon(sni->tray->themes, sni->tray->basedirs,
355 icon_name, ideal_size, output->bar->config->icon_theme,
356 &sni->min_size, &sni->max_size);
357 if (!icon_path && sni->icon_theme_path) {
358 icon_path = find_icon_in_dir(icon_name, sni->icon_theme_path,
359 &sni->min_size, &sni->max_size);
360 }
361 if (icon_path) {
362 cairo_surface_destroy(sni->icon);
363 sni->icon = load_background_image(icon_path);
364 free(icon_path);
365 icon_found = true;
366 }
367 }
368 if (!icon_found) {
369 list_t *pixmaps = sni->status[0] == 'N' ?
370 sni->attention_icon_pixmap : sni->icon_pixmap;
371 if (pixmaps) {
372 int idx = -1;
373 unsigned smallest_error = -1; // UINT_MAX
374 for (int i = 0; i < pixmaps->length; ++i) {
375 struct swaybar_pixmap *pixmap = pixmaps->items[i];
376 unsigned error = (ideal_size - pixmap->size) *
377 (ideal_size < pixmap->size ? -1 : 1);
378 if (error < smallest_error) {
379 smallest_error = error;
380 idx = i;
381 }
382 }
383 struct swaybar_pixmap *pixmap = pixmaps->items[idx];
384 cairo_surface_destroy(sni->icon);
385 sni->icon = cairo_image_surface_create_for_data(pixmap->pixels,
386 CAIRO_FORMAT_ARGB32, pixmap->size, pixmap->size,
387 cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, pixmap->size));
388 }
389 }
390 }
391
392 int icon_size;
393 cairo_surface_t *icon;
394 if (sni->icon) {
395 int actual_size = cairo_image_surface_get_height(sni->icon);
396 icon_size = actual_size < ideal_size ?
397 actual_size*(ideal_size/actual_size) : ideal_size;
398 icon = cairo_image_surface_scale(sni->icon, icon_size, icon_size);
399 } else { // draw a sad face
400 icon_size = ideal_size*0.8;
401 icon = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, icon_size, icon_size);
402 cairo_t *cairo_icon = cairo_create(icon);
403 cairo_set_source_u32(cairo_icon, 0xFF0000FF);
404 cairo_translate(cairo_icon, icon_size/2, icon_size/2);
405 cairo_scale(cairo_icon, icon_size/2, icon_size/2);
406 cairo_arc(cairo_icon, 0, 0, 1, 0, 7);
407 cairo_fill(cairo_icon);
408 cairo_set_operator(cairo_icon, CAIRO_OPERATOR_CLEAR);
409 cairo_arc(cairo_icon, 0.35, -0.3, 0.1, 0, 7);
410 cairo_fill(cairo_icon);
411 cairo_arc(cairo_icon, -0.35, -0.3, 0.1, 0, 7);
412 cairo_fill(cairo_icon);
413 cairo_arc(cairo_icon, 0, 0.75, 0.5, 3.71238898038469, 5.71238898038469);
414 cairo_set_line_width(cairo_icon, 0.1);
415 cairo_stroke(cairo_icon);
416 cairo_destroy(cairo_icon);
417 }
418
419 int padded_size = icon_size + 2*padding;
420 *x -= padded_size;
421 int y = floor((height - padded_size) / 2.0);
422
423 cairo_operator_t op = cairo_get_operator(cairo);
424 cairo_set_operator(cairo, CAIRO_OPERATOR_OVER);
425 cairo_set_source_surface(cairo, icon, *x + padding, y + padding);
426 cairo_rectangle(cairo, *x, y, padded_size, padded_size);
427 cairo_fill(cairo);
428 cairo_set_operator(cairo, op);
429
430 cairo_surface_destroy(icon);
431
432 struct swaybar_hotspot *hotspot = calloc(1, sizeof(struct swaybar_hotspot));
433 hotspot->x = *x;
434 hotspot->y = 0;
435 hotspot->width = height;
436 hotspot->height = height;
437 hotspot->callback = icon_hotspot_callback;
438 hotspot->destroy = free;
439 hotspot->data = strdup(sni->watcher_id);
440 wl_list_insert(&output->hotspots, &hotspot->link);
441
442 return output->height;
443}