1 From ce789fe3f752e94d7f73f39106bfac5339b6bf7e Mon Sep 17 00:00:00 2001
2 From: Felix Weilbach <felix.weilbach@t-online.de>
3 Date: Sun, 30 May 2021 20:45:01 +0200
4 Subject: [PATCH] Tray: Implement dbusmenu
5
6 Co-authored-by: Ian Fan <ianfan0@gmail.com>
7 Co-authored-by: Nathan Schulte <nmschulte@gmail.com>
8
9 Signed-off-by: Felix Weilbach <felix.weilbach@t-online.de>
10 ---
11 include/swaybar/bar.h | 1 +
12 include/swaybar/input.h | 5 +-
13 include/swaybar/tray/dbusmenu.h | 27 +
14 include/swaybar/tray/item.h | 2 +
15 include/swaybar/tray/tray.h | 3 +
16 swaybar/bar.c | 4 +
17 swaybar/input.c | 49 +-
18 swaybar/meson.build | 3 +-
19 swaybar/render.c | 2 +
20 swaybar/tray/dbusmenu.c | 1367 +++++++++++++++++++++++++++++++
21 swaybar/tray/item.c | 16 +-
22 11 files changed, 1468 insertions(+), 11 deletions(-)
23 create mode 100644 include/swaybar/tray/dbusmenu.h
24 create mode 100644 swaybar/tray/dbusmenu.c
25
26 diff --git a/include/swaybar/bar.h b/include/swaybar/bar.h
27 index 3ad0bdf3ce..3c0b49265e 100644
28 --- a/include/swaybar/bar.h
29 +++ b/include/swaybar/bar.h
30 @@ -31,6 +31,7 @@ struct swaybar {
31 struct zwlr_layer_shell_v1 *layer_shell;
32 struct zxdg_output_manager_v1 *xdg_output_manager;
33 struct wl_shm *shm;
34 + struct xdg_wm_base *wm_base;
35
36 struct swaybar_config *config;
37 struct status_line *status;
38 diff --git a/include/swaybar/input.h b/include/swaybar/input.h
39 index 8ea88a69a0..81ccaa989a 100644
40 --- a/include/swaybar/input.h
41 +++ b/include/swaybar/input.h
42 @@ -15,6 +15,7 @@
43
44 struct swaybar;
45 struct swaybar_output;
46 +struct swaybar_seat;
47
48 struct swaybar_pointer {
49 struct wl_pointer *pointer;
50 @@ -48,8 +49,8 @@ struct swaybar_hotspot {
51 struct wl_list link; // swaybar_output::hotspots
52 int x, y, width, height;
53 enum hotspot_event_handling (*callback)(struct swaybar_output *output,
54 - struct swaybar_hotspot *hotspot, double x, double y, uint32_t button,
55 - bool released, void *data);
56 + struct swaybar_hotspot *hotspot, struct swaybar_seat *seat, uint32_t serial,
57 + double x, double y, uint32_t button, bool released, void *data);
58 void (*destroy)(void *data);
59 void *data;
60 };
61 diff --git a/include/swaybar/tray/dbusmenu.h b/include/swaybar/tray/dbusmenu.h
62 new file mode 100644
63 index 0000000000..dc90f6e571
64 --- /dev/null
65 +++ b/include/swaybar/tray/dbusmenu.h
66 @@ -0,0 +1,27 @@
67 +#ifndef _SWAYBAR_TRAY_DBUSMENU_H
68 +#define _SWAYBAR_TRAY_DBUSMENU_H
69 +
70 +#include "swaybar/bar.h"
71 +#include "swaybar/tray/item.h"
72 +
73 +void swaybar_dbusmenu_open(struct swaybar_sni *sni,
74 + struct swaybar_output *output, struct swaybar_seat *seat, uint32_t serial,
75 + int x, int y);
76 +
77 +bool dbusmenu_pointer_button(void *data, struct wl_pointer *wl_pointer,
78 + uint32_t serial, uint32_t time_, uint32_t button, uint32_t state);
79 +
80 +bool dbusmenu_pointer_motion(struct swaybar_seat *seat, struct wl_pointer *wl_pointer,
81 + uint32_t time_, wl_fixed_t surface_x, wl_fixed_t surface_y);
82 +
83 +bool dbusmenu_pointer_enter(void *data, struct wl_pointer *wl_pointer, uint32_t serial,
84 + struct wl_surface *surface, wl_fixed_t surface_x, wl_fixed_t surface_y);
85 +
86 +bool dbusmenu_pointer_leave(void *data, struct wl_pointer *wl_pointer, uint32_t serial,
87 + struct wl_surface *surface);
88 +
89 +bool dbusmenu_pointer_frame(struct swaybar_seat *data, struct wl_pointer *wl_pointer);
90 +
91 +bool dbusmenu_pointer_axis(struct swaybar_seat *data, struct wl_pointer *wl_pointer);
92 +
93 +#endif
94 diff --git a/include/swaybar/tray/item.h b/include/swaybar/tray/item.h
95 index c02a558237..0e25cae318 100644
96 --- a/include/swaybar/tray/item.h
97 +++ b/include/swaybar/tray/item.h
98 @@ -17,6 +17,7 @@ struct swaybar_pixmap {
99 struct swaybar_sni_slot {
100 struct wl_list link; // swaybar_sni::slots
101 struct swaybar_sni *sni;
102 + int menu_id;
103 const char *prop;
104 const char *type;
105 void *dest;
106 @@ -47,6 +48,7 @@ struct swaybar_sni {
107 char *icon_theme_path; // non-standard KDE property
108
109 struct wl_list slots; // swaybar_sni_slot::link
110 + char **menu_icon_theme_paths;
111 };
112
113 struct swaybar_sni *create_sni(char *id, struct swaybar_tray *tray);
114 diff --git a/include/swaybar/tray/tray.h b/include/swaybar/tray/tray.h
115 index d2e80a6d47..853f17cdc1 100644
116 --- a/include/swaybar/tray/tray.h
117 +++ b/include/swaybar/tray/tray.h
118 @@ -32,6 +32,9 @@ struct swaybar_tray {
119
120 list_t *basedirs; // char *
121 list_t *themes; // struct swaybar_theme *
122 +
123 + struct swaybar_dbusmenu *menu;
124 + struct swaybar_dbusmenu_menu *menu_pointer_focus;
125 };
126
127 struct swaybar_tray *create_tray(struct swaybar *bar);
128 diff --git a/swaybar/bar.c b/swaybar/bar.c
129 index 5e4ebd97c9..177b870b48 100644
130 --- a/swaybar/bar.c
131 +++ b/swaybar/bar.c
132 @@ -29,6 +29,7 @@
133 #include "pool-buffer.h"
134 #include "wlr-layer-shell-unstable-v1-client-protocol.h"
135 #include "xdg-output-unstable-v1-client-protocol.h"
136 +#include "xdg-shell-client-protocol.h"
137
138 void free_workspaces(struct wl_list *list) {
139 struct swaybar_workspace *ws, *tmp;
140 @@ -362,6 +363,8 @@ static void handle_global(void *data, struct wl_registry *registry,
141 } else if (strcmp(interface, zxdg_output_manager_v1_interface.name) == 0) {
142 bar->xdg_output_manager = wl_registry_bind(registry, name,
143 &zxdg_output_manager_v1_interface, 2);
144 + } else if (strcmp(interface, xdg_wm_base_interface.name) == 0) {
145 + bar->wm_base = wl_registry_bind(registry, name, &xdg_wm_base_interface, 1);
146 }
147 }
148
149 @@ -534,6 +537,7 @@ void bar_teardown(struct swaybar *bar) {
150 #if HAVE_TRAY
151 destroy_tray(bar->tray);
152 #endif
153 + xdg_wm_base_destroy(bar->wm_base);
154 free_outputs(&bar->outputs);
155 free_outputs(&bar->unused_outputs);
156 free_seats(&bar->seats);
157 diff --git a/swaybar/input.c b/swaybar/input.c
158 index 8eccf5420b..4ee4915991 100644
159 --- a/swaybar/input.c
160 +++ b/swaybar/input.c
161 @@ -10,6 +10,10 @@
162 #include "swaybar/input.h"
163 #include "swaybar/ipc.h"
164
165 +#if HAVE_TRAY
166 +#include "swaybar/tray/dbusmenu.h"
167 +#endif
168 +
169 void free_hotspots(struct wl_list *list) {
170 struct swaybar_hotspot *hotspot, *tmp;
171 wl_list_for_each_safe(hotspot, tmp, list, link) {
172 @@ -112,10 +116,23 @@ static void wl_pointer_enter(void *data, struct wl_pointer *wl_pointer,
173 }
174 }
175 update_cursor(seat);
176 +
177 +#if HAVE_TRAY
178 + if (dbusmenu_pointer_enter(data, wl_pointer, serial, surface, surface_x,
179 + surface_y)) {
180 + return;
181 + }
182 +#endif
183 }
184
185 static void wl_pointer_leave(void *data, struct wl_pointer *wl_pointer,
186 uint32_t serial, struct wl_surface *surface) {
187 +#if HAVE_TRAY
188 + if (dbusmenu_pointer_leave(data, wl_pointer, serial, surface)) {
189 + return;
190 + }
191 +#endif
192 +
193 struct swaybar_seat *seat = data;
194 seat->pointer.current = NULL;
195 }
196 @@ -125,6 +142,11 @@ static void wl_pointer_motion(void *data, struct wl_pointer *wl_pointer,
197 struct swaybar_seat *seat = data;
198 seat->pointer.x = wl_fixed_to_double(surface_x);
199 seat->pointer.y = wl_fixed_to_double(surface_y);
200 +#if HAVE_TRAY
201 + if (dbusmenu_pointer_motion(data, wl_pointer, time, surface_x, surface_y)) {
202 + return;
203 + }
204 +#endif
205 }
206
207 static bool check_bindings(struct swaybar *bar, uint32_t button,
208 @@ -141,6 +163,7 @@ static bool check_bindings(struct swaybar *bar, uint32_t button,
209 }
210
211 static bool process_hotspots(struct swaybar_output *output,
212 + struct swaybar_seat *seat, uint32_t serial,
213 double x, double y, uint32_t button, uint32_t state) {
214 bool released = state == WL_POINTER_BUTTON_STATE_RELEASED;
215 struct swaybar_hotspot *hotspot;
216 @@ -148,7 +171,7 @@ static bool process_hotspots(struct swaybar_output *output,
217 if (x >= hotspot->x && y >= hotspot->y
218 && x < hotspot->x + hotspot->width
219 && y < hotspot->y + hotspot->height) {
220 - if (HOTSPOT_IGNORE == hotspot->callback(output, hotspot, x, y,
221 + if (HOTSPOT_IGNORE == hotspot->callback(output, hotspot, seat, serial, x, y,
222 button, released, hotspot->data)) {
223 return true;
224 }
225 @@ -161,13 +184,19 @@ static bool process_hotspots(struct swaybar_output *output,
226 static void wl_pointer_button(void *data, struct wl_pointer *wl_pointer,
227 uint32_t serial, uint32_t time, uint32_t button, uint32_t state) {
228 struct swaybar_seat *seat = data;
229 +#if HAVE_TRAY
230 + if (dbusmenu_pointer_button(seat, wl_pointer, serial, time, button,
231 + state)) {
232 + return;
233 + }
234 +#endif
235 struct swaybar_pointer *pointer = &seat->pointer;
236 struct swaybar_output *output = pointer->current;
237 if (!sway_assert(output, "button with no active output")) {
238 return;
239 }
240
241 - if (process_hotspots(output, pointer->x, pointer->y, button, state)) {
242 + if (process_hotspots(output, seat, serial, pointer->x, pointer->y, button, state)) {
243 return;
244 }
245
246 @@ -221,7 +250,7 @@ static void process_discrete_scroll(struct swaybar_seat *seat,
247 struct swaybar_output *output, struct swaybar_pointer *pointer,
248 uint32_t axis, wl_fixed_t value) {
249 uint32_t button = wl_axis_to_button(axis, value);
250 - if (process_hotspots(output, pointer->x, pointer->y, button, WL_POINTER_BUTTON_STATE_PRESSED)) {
251 + if (process_hotspots(output, seat, 0, pointer->x, pointer->y, button, WL_POINTER_BUTTON_STATE_PRESSED)) {
252 // (Currently hotspots don't do anything on release events, so no need to emit one)
253 return;
254 }
255 @@ -278,6 +307,12 @@ static void wl_pointer_axis(void *data, struct wl_pointer *wl_pointer,
256 return;
257 }
258
259 +#if HAVE_TRAY
260 + if (dbusmenu_pointer_axis(data, wl_pointer)) {
261 + return;
262 + }
263 +#endif
264 +
265 // If there's a while since the last scroll event,
266 // set 'value' to zero as if to reset the "virtual scroll wheel"
267 if (seat->axis[axis].discrete_steps == 0 &&
268 @@ -294,6 +329,12 @@ static void wl_pointer_frame(void *data, struct wl_pointer *wl_pointer) {
269 struct swaybar_pointer *pointer = &seat->pointer;
270 struct swaybar_output *output = pointer->current;
271
272 +#if HAVE_TRAY
273 + if (dbusmenu_pointer_frame(data, wl_pointer)) {
274 + return;
275 + }
276 +#endif
277 +
278 if (output == NULL) {
279 return;
280 }
281 @@ -401,7 +442,7 @@ static void wl_touch_up(void *data, struct wl_touch *wl_touch,
282 }
283 if (time - slot->time < 500) {
284 // Tap, treat it like a pointer click
285 - process_hotspots(slot->output, slot->x, slot->y, BTN_LEFT, WL_POINTER_BUTTON_STATE_PRESSED);
286 + process_hotspots(slot->output, seat, serial, slot->x, slot->y, BTN_LEFT, WL_POINTER_BUTTON_STATE_PRESSED);
287 // (Currently hotspots don't do anything on release events, so no need to emit one)
288 }
289 slot->output = NULL;
290 diff --git a/swaybar/meson.build b/swaybar/meson.build
291 index e5f1811eb0..fef1ee778f 100644
292 --- a/swaybar/meson.build
293 +++ b/swaybar/meson.build
294 @@ -3,7 +3,8 @@ tray_files = have_tray ? [
295 'tray/icon.c',
296 'tray/item.c',
297 'tray/tray.c',
298 - 'tray/watcher.c'
299 + 'tray/watcher.c',
300 + 'tray/dbusmenu.c'
301 ] : []
302
303 swaybar_deps = [
304 diff --git a/swaybar/render.c b/swaybar/render.c
305 index 95f6e5be4d..da3e3bd0fd 100644
306 --- a/swaybar/render.c
307 +++ b/swaybar/render.c
308 @@ -160,6 +160,7 @@ static void render_sharp_line(cairo_t *cairo, uint32_t color,
309
310 static enum hotspot_event_handling block_hotspot_callback(
311 struct swaybar_output *output, struct swaybar_hotspot *hotspot,
312 + struct swaybar_seat *seat, uint32_t serial,
313 double x, double y, uint32_t button, bool released, void *data) {
314 struct i3bar_block *block = data;
315 struct status_line *status = output->bar->status;
316 @@ -599,6 +600,7 @@ static uint32_t render_binding_mode_indicator(struct render_context *ctx,
317
318 static enum hotspot_event_handling workspace_hotspot_callback(
319 struct swaybar_output *output, struct swaybar_hotspot *hotspot,
320 + struct swaybar_seat *seat, uint32_t serial,
321 double x, double y, uint32_t button, bool released, void *data) {
322 if (button != BTN_LEFT) {
323 return HOTSPOT_PROCESS;
324 diff --git a/swaybar/tray/dbusmenu.c b/swaybar/tray/dbusmenu.c
325 new file mode 100644
326 index 0000000000..ed74e04029
327 --- /dev/null
328 +++ b/swaybar/tray/dbusmenu.c
329 @@ -0,0 +1,1367 @@
330 +#define _POSIX_C_SOURCE 200809L
331 +#include <linux/input-event-codes.h>
332 +#include <pool-buffer.h>
333 +#include <stdlib.h>
334 +#include <wayland-client-core.h>
335 +#include <wayland-client-protocol.h>
336 +#include <wayland-util.h>
337 +#include <wlr-layer-shell-unstable-v1-client-protocol.h>
338 +#include <xdg-shell-client-protocol.h>
339 +#include <xdg-shell-protocol.h>
340 +
341 +#include "background-image.h"
342 +#include "cairo.h"
343 +#include "cairo_util.h"
344 +#include "list.h"
345 +#include "log.h"
346 +#include "pango.h"
347 +#include "swaybar/bar.h"
348 +#include "swaybar/config.h"
349 +#include "swaybar/input.h"
350 +#include "swaybar/tray/icon.h"
351 +#include "swaybar/tray/item.h"
352 +#include "swaybar/tray/tray.h"
353 +
354 +static const char *menu_interface = "com.canonical.dbusmenu";
355 +
356 +static void swaybar_dbusmenu_get_layout_root(struct swaybar_dbusmenu *menu);
357 +static void swaybar_dbusmenu_get_layout(struct swaybar_dbusmenu *menu, int id);
358 +static void swaybar_dbusmenu_draw(struct swaybar_dbusmenu *dbusmenu, int id);
359 +
360 +struct swaybar_dbusmenu_hotspot {
361 + int x, y, width, height;
362 +};
363 +
364 +struct swaybar_dbusmenu_surface {
365 + struct xdg_popup *xdg_popup;
366 + struct xdg_surface *xdg_surface;
367 + struct wl_surface *surface;
368 + struct pool_buffer *current_buffer;
369 + struct pool_buffer buffers[2];
370 + int width, height;
371 + bool configured;
372 +};
373 +
374 +enum menu_toggle_type {
375 + MENU_NONE,
376 + MENU_CHECKMARK,
377 + MENU_RADIO
378 +};
379 +
380 +struct swaybar_dbusmenu_menu_item {
381 + struct swaybar_dbusmenu_hotspot hotspot;
382 + // Set if the item has a submenu
383 + struct swaybar_dbusmenu_menu *submenu;
384 + // The menu in which the item is displayed
385 + struct swaybar_dbusmenu_menu *menu;
386 + struct swaybar_dbusmenu_menu_item *parent_item;
387 + int id;
388 + int toggle_state;
389 + char *label;
390 + char *icon_name;
391 + cairo_surface_t *icon_data;
392 + enum menu_toggle_type toggle_type;
393 + bool enabled;
394 + bool visible;
395 + bool is_separator;
396 +};
397 +
398 +struct swaybar_dbusmenu_menu {
399 + struct swaybar_dbusmenu *dbusmenu;
400 + struct swaybar_dbusmenu_surface *surface;
401 + struct swaybar_dbusmenu_menu_item *last_hovered_item;
402 + list_t *items; // struct swaybar_dbusmenu_menu_item
403 + int item_id;
404 + int x, y;
405 +};
406 +
407 +struct swaybar_dbusmenu {
408 + struct swaybar_sni *sni;
409 + struct swaybar_output *output;
410 + struct swaybar_seat *seat;
411 + struct swaybar_dbusmenu_menu *menu;
412 + struct swaybar *bar;
413 + int serial;
414 + int x, y;
415 +};
416 +
417 +struct get_layout_callback_data {
418 + struct swaybar_dbusmenu *menu;
419 + int id;
420 +};
421 +
422 +struct png_stream {
423 + const void *data;
424 + size_t left;
425 +};
426 +
427 +static void commit_menu_surface(struct swaybar_dbusmenu_menu *menu)
428 +{
429 + struct swaybar_dbusmenu_surface * dbusmenu_surface = menu->surface;
430 + if (!dbusmenu_surface->configured || dbusmenu_surface->current_buffer == NULL) {
431 + return;
432 + }
433 +
434 + struct wl_surface *surface = dbusmenu_surface->surface;
435 + wl_surface_set_buffer_scale(surface, menu->dbusmenu->output->scale);
436 + wl_surface_attach(surface, dbusmenu_surface->current_buffer->buffer, 0, 0);
437 + wl_surface_damage(surface, 0, 0, dbusmenu_surface->width, dbusmenu_surface->height);
438 + wl_surface_commit(surface);
439 +}
440 +
441 +static int handle_items_properties_updated(sd_bus_message *msg, void *data,
442 + sd_bus_error *error) {
443 + struct swaybar_sni *sni = data;
444 + sway_log(SWAY_DEBUG, "%s%s item properties updated", sni->service, sni->menu);
445 +
446 + // TODO: Optimize. Update only needed properties
447 + if (sni->tray->menu) {
448 + swaybar_dbusmenu_get_layout_root(sni->tray->menu);
449 + }
450 + return 0;
451 +}
452 +
453 +static int handle_layout_updated(sd_bus_message *msg, void *data,
454 + sd_bus_error *error) {
455 + struct swaybar_sni *sni = data;
456 + sway_log(SWAY_DEBUG, "%s%s layout updated", sni->service, sni->menu);
457 +
458 + int id;
459 + sd_bus_message_read(msg, "ui", NULL, &id);
460 + if (sni->tray->menu) {
461 + swaybar_dbusmenu_get_layout(sni->tray->menu, id);
462 + }
463 + return 0;
464 +}
465 +
466 +static int handle_item_activation_requested(sd_bus_message *msg, void *data,
467 + sd_bus_error *error) {
468 + return 0; // TODO: Implement handling of hotkeys for opening the menu
469 +}
470 +
471 +static struct swaybar_dbusmenu_surface *swaybar_dbusmenu_surface_create() {
472 + struct swaybar_dbusmenu_surface *dbusmenu = calloc(1,
473 + sizeof(struct swaybar_dbusmenu_surface));
474 + if (!dbusmenu) {
475 + sway_log(SWAY_DEBUG, "Could not allocate dbusmenu");
476 + }
477 + return dbusmenu;
478 +}
479 +
480 +static void xdg_surface_handle_configure(void *data,
481 + struct xdg_surface *xdg_surface, uint32_t serial) {
482 + xdg_surface_ack_configure(xdg_surface, serial);
483 +
484 + struct swaybar_dbusmenu_menu *menu = data;
485 + menu->surface->configured = true;
486 + commit_menu_surface(menu);
487 +}
488 +
489 +static const struct xdg_surface_listener xdg_surface_listener = {
490 + .configure = xdg_surface_handle_configure,
491 +};
492 +
493 +static void xdg_popup_configure(void *data, struct xdg_popup *xdg_popup,
494 + int32_t x, int32_t y, int32_t width, int32_t height) {
495 + // intentionally left blank
496 +}
497 +
498 +static void destroy_dbusmenu_surface(
499 + struct swaybar_dbusmenu_surface *dbusmenu_surface) {
500 + if (!dbusmenu_surface) {
501 + return;
502 + }
503 +
504 + if (dbusmenu_surface->xdg_popup) {
505 + xdg_popup_destroy(dbusmenu_surface->xdg_popup);
506 + dbusmenu_surface->xdg_popup = NULL;
507 + }
508 + if (dbusmenu_surface->surface) {
509 + xdg_surface_destroy(dbusmenu_surface->xdg_surface);
510 + wl_surface_destroy(dbusmenu_surface->surface);
511 + dbusmenu_surface->surface = NULL;
512 + }
513 + destroy_buffer(&dbusmenu_surface->buffers[0]);
514 + destroy_buffer(&dbusmenu_surface->buffers[1]);
515 +
516 + free(dbusmenu_surface);
517 +}
518 +
519 +static void close_menu(struct swaybar_dbusmenu_menu *menu) {
520 + if (!menu) {
521 + return;
522 + }
523 +
524 + if (menu->surface) {
525 + destroy_dbusmenu_surface(menu->surface);
526 + menu->surface = NULL;
527 +
528 + int id = menu->item_id;
529 + struct swaybar_sni *sni = menu->dbusmenu->sni;
530 + sd_bus_call_method_async(sni->tray->bus, NULL, sni->service, sni->menu,
531 + menu_interface, "Event", NULL, NULL, "isvu", id, "closed", "y",
532 + 0, time(NULL));
533 + sway_log(SWAY_DEBUG, "%s%s closed id %d", sni->service, sni->menu, id);
534 + }
535 +}
536 +
537 +static void close_menus(struct swaybar_dbusmenu_menu *menu) {
538 + if (!menu) {
539 + return;
540 + }
541 +
542 + if (menu->items) {
543 + for (int i = 0; i < menu->items->length; ++i) {
544 + struct swaybar_dbusmenu_menu_item *item = menu->items->items[i];
545 + if (item->submenu && item->submenu->item_id != 0) {
546 + close_menus(item->submenu);
547 + }
548 + }
549 + }
550 +
551 + close_menu(menu);
552 +}
553 +
554 +static void swaybar_dbusmenu_menu_destroy(struct swaybar_dbusmenu_menu *menu) {
555 + if (!menu) {
556 + return;
557 + }
558 +
559 + if (menu->items) {
560 + for (int i = 0; i < menu->items->length; ++i) {
561 + struct swaybar_dbusmenu_menu_item *item = menu->items->items[i];
562 + struct swaybar_dbusmenu_menu *child_menu = item->submenu;
563 + if (child_menu && child_menu->item_id != 0) {
564 + swaybar_dbusmenu_menu_destroy(item->submenu);
565 + }
566 + free(item->label);
567 + free(item->icon_name);
568 + free(item->icon_data);
569 + free(item);
570 + }
571 + }
572 + list_free(menu->items);
573 + free(menu);
574 +}
575 +
576 +void swaybar_dbusmenu_destroy(struct swaybar_dbusmenu *menu) {
577 + if (!menu) {
578 + return;
579 + }
580 +
581 + menu->sni->tray->menu = NULL;
582 + menu->sni->tray->menu_pointer_focus = NULL;
583 +
584 + close_menus(menu->menu);
585 + swaybar_dbusmenu_menu_destroy(menu->menu);
586 + free(menu);
587 +}
588 +
589 +static void xdg_popup_done(void *data, struct xdg_popup *xdg_popup) {
590 + struct swaybar_dbusmenu_menu *menu = data;
591 + swaybar_dbusmenu_destroy(menu->dbusmenu);
592 +}
593 +
594 +static const struct xdg_popup_listener xdg_popup_listener = {
595 + .configure = xdg_popup_configure, .popup_done = xdg_popup_done};
596 +
597 +static struct swaybar_dbusmenu_menu_item *
598 +find_item_under_menu(struct swaybar_dbusmenu_menu *menu, int item_id) {
599 + if (!menu->items) {
600 + return NULL;
601 + }
602 +
603 + for (int i = 0; i < menu->items->length; ++i) {
604 + struct swaybar_dbusmenu_menu_item *item = menu->items->items[i];
605 + if (item->id == item_id) {
606 + return item;
607 + }
608 + if (item->submenu && item->submenu->item_id != 0) {
609 + struct swaybar_dbusmenu_menu_item *found_item =
610 + find_item_under_menu(item->submenu, item_id);
611 + if (found_item) {
612 + return found_item;
613 + }
614 + }
615 + }
616 +
617 + return NULL;
618 +}
619 +
620 +static struct swaybar_dbusmenu_menu_item *
621 +find_item(struct swaybar_dbusmenu *dbusmenu, int item_id) {
622 + if (!dbusmenu->menu) {
623 + return NULL;
624 + }
625 +
626 + return find_item_under_menu(dbusmenu->menu, item_id);
627 +}
628 +
629 +static struct swaybar_dbusmenu_menu *
630 +find_parent_menu_under_menu(struct swaybar_dbusmenu_menu *menu,
631 + struct swaybar_dbusmenu_menu *child_menu) {
632 + if (!menu->items) {
633 + return NULL;
634 + }
635 +
636 + for (int i = 0; i < menu->items->length; ++i) {
637 + struct swaybar_dbusmenu_menu_item *item = menu->items->items[i];
638 + struct swaybar_dbusmenu_menu *maybe_child_menu = item->submenu;
639 + if (maybe_child_menu && maybe_child_menu->item_id != 0) {
640 + if (maybe_child_menu == child_menu) {
641 + return menu;
642 + }
643 + maybe_child_menu = find_parent_menu_under_menu(maybe_child_menu, child_menu);
644 + if (maybe_child_menu) {
645 + return maybe_child_menu;
646 + }
647 + }
648 + }
649 +
650 + return NULL;
651 +}
652 +
653 +static struct swaybar_dbusmenu_menu *
654 +find_parent_menu(struct swaybar_dbusmenu_menu *menu) {
655 + if (menu && menu->item_id == 0) {
656 + return NULL;
657 + }
658 + struct swaybar_dbusmenu_menu *root_menu = menu->dbusmenu->menu;
659 + return find_parent_menu_under_menu(root_menu, menu);
660 +}
661 +
662 +static bool is_in_hotspot(struct swaybar_dbusmenu_hotspot *hotspot, int x, int y) {
663 + if (!hotspot) {
664 + return false;
665 + }
666 +
667 + if (hotspot->x <= x && x < hotspot->x + hotspot->width && hotspot->y <= y &&
668 + y < hotspot->y + hotspot->height) {
669 + return true;
670 + }
671 +
672 + return false;
673 +}
674 +
675 +static void draw_menu_items(cairo_t *cairo, struct swaybar_dbusmenu_menu *menu,
676 + int *surface_x, int *surface_y, int *surface_width, int *surface_height,
677 + bool open) {
678 + struct swaybar_sni *sni = menu->dbusmenu->sni;
679 + struct swaybar_tray *tray = sni->tray;
680 + struct swaybar_output *output = menu->dbusmenu->output;
681 + struct swaybar_config *config = menu->dbusmenu->output->bar->config;
682 +
683 + int padding = config->tray_padding * output->scale;
684 +
685 + list_t *items = menu->items;
686 + int height = 0;
687 +
688 + *surface_y = 0;
689 + *surface_x = 0;
690 + *surface_width = 0;
691 + bool is_icon_drawn = false;
692 + int icon_size = 0;
693 +
694 + for (int i = 0; i < items->length; ++i) {
695 + struct swaybar_dbusmenu_menu_item *item = items->items[i];
696 +
697 + if (!item->visible) {
698 + continue;
699 + }
700 +
701 + int new_height = height;
702 + if (item->is_separator) {
703 + // drawn later, after the width is known
704 + new_height = height + output->scale;
705 + } else if (item->label) {
706 + cairo_move_to(cairo, padding, height + padding);
707 +
708 + // draw label
709 + if (item->enabled) {
710 + cairo_set_source_u32(cairo, config->colors.focused_statusline);
711 + } else {
712 + uint32_t c = config->colors.focused_statusline;
713 + uint32_t disabled_color = c - ((c & 0xFF) >> 1);
714 + cairo_set_source_u32(cairo, disabled_color);
715 + }
716 + render_text(cairo, config->font_description, output->scale, false, "%s",
717 + item->label);
718 +
719 + // draw icon or menu indicator if needed
720 + int text_height;
721 + int text_width;
722 + get_text_size(cairo, config->font_description, &text_width, &text_height,
723 + NULL, output->scale, false, "%s", item->label);
724 + text_width += padding;
725 + int size = text_height;
726 + int x = -2 * padding - size;
727 + int y = height + padding;
728 + icon_size = 2 * padding + size;
729 + cairo_set_source_u32(cairo, config->colors.focused_statusline);
730 + if (item->icon_name) {
731 + list_t *icon_search_paths = create_list();
732 + list_cat(icon_search_paths, tray->basedirs);
733 + if (sni->menu_icon_theme_paths) {
734 + for (char **path = sni->menu_icon_theme_paths; *path; ++path) {
735 + list_add(icon_search_paths, *path);
736 + }
737 + }
738 + if (sni->icon_theme_path) {
739 + list_add(icon_search_paths, sni->icon_theme_path);
740 + }
741 + int min_size, max_size;
742 + char *icon_path =
743 + find_icon(tray->themes, icon_search_paths, item->icon_name, size,
744 + config->icon_theme, &min_size, &max_size);
745 + list_free(icon_search_paths);
746 +
747 + if (icon_path) {
748 + cairo_surface_t *icon = load_background_image(icon_path);
749 + free(icon_path);
750 + cairo_surface_t *icon_scaled =
751 + cairo_image_surface_scale(icon, size, size);
752 + cairo_surface_destroy(icon);
753 +
754 + cairo_set_source_surface(cairo, icon_scaled, x, y);
755 + cairo_rectangle(cairo, x, y, size, size);
756 + cairo_fill(cairo);
757 + cairo_surface_destroy(icon_scaled);
758 + is_icon_drawn = true;
759 + }
760 + } else if (item->icon_data) {
761 + cairo_surface_t *icon = cairo_image_surface_scale(item->icon_data, size, size);
762 + cairo_set_source_surface(cairo, icon, x, y);
763 + cairo_rectangle(cairo, x, y, size, size);
764 + cairo_fill(cairo);
765 + cairo_surface_destroy(icon);
766 + is_icon_drawn = true;
767 + } else if (item->toggle_type == MENU_CHECKMARK) {
768 + cairo_rectangle(cairo, x, y, size, size);
769 + cairo_fill(cairo);
770 + cairo_set_operator(cairo, CAIRO_OPERATOR_CLEAR);
771 + if (item->toggle_state == 1) { // tick
772 + cairo_move_to(cairo, x + size * 3.0 / 4, y + size * 5.0 / 16.0);
773 + cairo_line_to(cairo, x + size * 3.0 / 8, y + size * 11.0 / 16.0);
774 + cairo_line_to(cairo, x + size / 4.0, y + size * 9.0 / 16.0);
775 + cairo_stroke(cairo);
776 + } else if (item->toggle_state != 0) { // horizontal line
777 + cairo_rectangle(cairo, x + size / 4.0, y + size / 2.0 - 1,
778 + size / 2.0, 2);
779 + cairo_fill(cairo);
780 + }
781 + cairo_set_operator(cairo, CAIRO_OPERATOR_OVER);
782 + is_icon_drawn = true;
783 + } else if (item->toggle_type == MENU_RADIO) {
784 + cairo_arc(cairo, x + size / 2.0, y + size / 2.0, size / 2.0, 0, 7);
785 + cairo_fill(cairo);
786 + if (item->toggle_state == 1) {
787 + cairo_set_operator(cairo, CAIRO_OPERATOR_CLEAR);
788 + cairo_arc(cairo, x + size / 2.0, y + size / 2.0, size / 4.0, 0, 7);
789 + cairo_fill(cairo);
790 + cairo_set_operator(cairo, CAIRO_OPERATOR_OVER);
791 + }
792 + is_icon_drawn = true;
793 + } else if (item->submenu) { // arrowhead
794 + cairo_move_to(cairo, x + size / 4.0, y + size / 2.0);
795 + cairo_line_to(cairo, x + size * 3.0 / 4, y + size / 4.0);
796 + cairo_line_to(cairo, x + size * 3.0 / 4, y + size * 3.0 / 4);
797 + cairo_fill(cairo);
798 + is_icon_drawn = true;
799 + }
800 +
801 + *surface_width = *surface_width < text_width ? text_width : *surface_width;
802 + new_height = height + text_height + 2 * padding;
803 + } else {
804 + continue;
805 + }
806 +
807 + struct swaybar_dbusmenu_hotspot *hotspot = &item->hotspot;
808 + hotspot->y = height;
809 +
810 + hotspot->y = height;
811 + hotspot->height = new_height - height;
812 + // x and width is not known at the moment
813 +
814 + height = new_height;
815 + }
816 +
817 + if (height == 0) {
818 + return;
819 + }
820 +
821 + if (is_icon_drawn) {
822 + *surface_x = -icon_size - padding;
823 + *surface_width += icon_size + padding;
824 + }
825 +
826 + *surface_width += padding;
827 + *surface_height = height;
828 +
829 + // Make sure height and width are divideable by scale
830 + // otherwise the menu will not showup
831 + if (*surface_width % output->scale != 0) {
832 + *surface_width -= *surface_width % output->scale;
833 + }
834 + if (*surface_height % output->scale != 0) {
835 + *surface_height -= *surface_height % output->scale;
836 + }
837 +
838 + cairo_set_line_width(cairo, output->scale);
839 + cairo_set_source_u32(cairo, config->colors.focused_separator);
840 + for (int i = 0; i < items->length; ++i) {
841 + struct swaybar_dbusmenu_menu_item *item = items->items[i];
842 + struct swaybar_dbusmenu_hotspot *hotspot = &item->hotspot;
843 + hotspot->x = 0;
844 + hotspot->width = *surface_width;
845 + if (item->is_separator) {
846 + int y = hotspot->y + hotspot->height / 2.0;
847 + cairo_move_to(cairo, *surface_x, y);
848 + cairo_line_to(cairo, *surface_x + *surface_width, y);
849 + cairo_stroke(cairo);
850 + } else if (!open && item->enabled &&
851 + is_in_hotspot(hotspot,
852 + tray->menu->seat->pointer.x * output->scale,
853 + tray->menu->seat->pointer.y * output->scale)) {
854 + cairo_save(cairo);
855 + cairo_set_operator(cairo, CAIRO_OPERATOR_DEST_OVER);
856 + cairo_rectangle(cairo, *surface_x, hotspot->y, *surface_width,
857 + hotspot->height);
858 + cairo_set_source_u32(cairo,
859 + sni->tray->bar->config->colors.focused_separator);
860 + cairo_fill(cairo);
861 + cairo_restore(cairo);
862 + }
863 + }
864 +}
865 +
866 +struct swaybar_dbusmenu_menu *find_menu_id(struct swaybar_dbusmenu_menu *menu,
867 + int id) {
868 + if (!menu) {
869 + return NULL;
870 + }
871 + if (menu->item_id == id) {
872 + return menu;
873 + }
874 +
875 + if (menu->items) {
876 + for (int i = 0; i < menu->items->length; ++i) {
877 + struct swaybar_dbusmenu_menu_item *item = menu->items->items[i];
878 + struct swaybar_dbusmenu_menu *child_menu = item->submenu;
879 + if (child_menu) {
880 + if (child_menu->item_id == id) {
881 + return child_menu;
882 + }
883 + if (child_menu->item_id == 0) {
884 + continue;
885 + }
886 + struct swaybar_dbusmenu_menu *child_child_menu = find_menu_id(child_menu, id);
887 + if (child_child_menu) {
888 + return child_child_menu;
889 + }
890 + }
891 + }
892 + }
893 +
894 + return NULL;
895 +}
896 +
897 +static void swaybar_dbusmenu_draw_menu(struct swaybar_dbusmenu_menu *menu,
898 + int id, bool open) {
899 + // For now just search for menu with id
900 + struct swaybar_tray *tray = menu->dbusmenu->sni->tray;
901 + menu = find_menu_id(menu->dbusmenu->menu, id);
902 + if (!menu) {
903 + return;
904 + }
905 +
906 + if (!menu->surface) {
907 + menu->surface = swaybar_dbusmenu_surface_create();
908 + if (!menu->surface) {
909 + sway_log(SWAY_ERROR, "Could not create surface for menu %d", menu->item_id);
910 + return;
911 + }
912 + }
913 +
914 + cairo_surface_t *recorder =
915 + cairo_recording_surface_create(CAIRO_CONTENT_COLOR_ALPHA, NULL);
916 + if (!recorder) {
917 + return;
918 + }
919 + cairo_t *cairo = cairo_create(recorder);
920 + if (!cairo) {
921 + cairo_surface_destroy(recorder);
922 + return;
923 + }
924 + int surface_x, surface_y, surface_width, surface_height;
925 + draw_menu_items(cairo, menu, &surface_x, &surface_y, &surface_width,
926 + &surface_height, open);
927 +
928 + struct swaybar *bar = menu->dbusmenu->sni->tray->bar;
929 + struct swaybar_dbusmenu_surface *dbusmenu_surface = menu->surface;
930 + dbusmenu_surface->current_buffer = get_next_buffer(
931 + bar->shm, dbusmenu_surface->buffers, surface_width, surface_height);
932 +
933 + if (!dbusmenu_surface->current_buffer) {
934 + cairo_surface_destroy(recorder);
935 + cairo_destroy(cairo);
936 + return;
937 + }
938 +
939 + cairo_t *shm = dbusmenu_surface->current_buffer->cairo;
940 + cairo_set_operator(shm, CAIRO_OPERATOR_SOURCE);
941 + cairo_set_source_u32(
942 + shm, menu->dbusmenu->sni->tray->bar->config->colors.focused_background);
943 + cairo_paint(shm);
944 +
945 + cairo_set_operator(shm, CAIRO_OPERATOR_OVER);
946 + cairo_set_source_surface(shm, recorder, -surface_x, -surface_y);
947 + cairo_paint(shm);
948 +
949 + cairo_surface_destroy(recorder);
950 + cairo_destroy(cairo);
951 +
952 + if (dbusmenu_surface->width != surface_width ||
953 + dbusmenu_surface->height != surface_height) {
954 + if (dbusmenu_surface->surface) {
955 + xdg_surface_destroy(dbusmenu_surface->xdg_surface);
956 + dbusmenu_surface->xdg_surface = NULL;
957 + wl_surface_destroy(dbusmenu_surface->surface);
958 + dbusmenu_surface->surface = NULL;
959 + sway_log(SWAY_DEBUG, "Destroy xdg popup");
960 + xdg_popup_destroy(dbusmenu_surface->xdg_popup);
961 + dbusmenu_surface->xdg_popup = NULL;
962 + }
963 +
964 + // configure & position popup surface
965 + struct wl_surface *surface = wl_compositor_create_surface(bar->compositor);
966 + struct xdg_surface *xdg_surface =
967 + xdg_wm_base_get_xdg_surface(menu->dbusmenu->bar->wm_base, surface);
968 + struct xdg_positioner *positioner =
969 + xdg_wm_base_create_positioner(menu->dbusmenu->bar->wm_base);
970 +
971 + // find the menu item (if any) which requested to open this submenu
972 + // to find out on which x and y coordinate the submenu should be drawn
973 + struct swaybar_dbusmenu_menu_item *item =
974 + find_item(menu->dbusmenu, menu->item_id);
975 + struct swaybar_output *output = menu->dbusmenu->output;
976 + int x = menu->item_id == 0 ? menu->dbusmenu->x
977 + : item->hotspot.x / output->scale;
978 + int y = menu->item_id == 0 ? menu->dbusmenu->y
979 + : item->hotspot.y / output->scale;
980 +
981 + xdg_positioner_set_offset(positioner, 0, 0);
982 + // Need to divide through scale because surface width/height is scaled
983 + xdg_positioner_set_size(positioner, surface_width / output->scale,
984 + surface_height / output->scale);
985 +
986 + int padding = (tray->bar->config->tray_padding * output->scale) / 2;
987 + if (bar->config->position & ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP) { // top bar
988 + xdg_positioner_set_anchor(positioner, XDG_POSITIONER_ANCHOR_BOTTOM_LEFT);
989 + xdg_positioner_set_gravity(positioner, XDG_POSITIONER_GRAVITY_BOTTOM_LEFT);
990 + xdg_positioner_set_anchor_rect(positioner, x, y - padding, 1, 1);
991 + } else {
992 + xdg_positioner_set_anchor(positioner, XDG_POSITIONER_ANCHOR_TOP_LEFT);
993 + xdg_positioner_set_gravity(positioner, XDG_POSITIONER_GRAVITY_TOP_LEFT);
994 + xdg_positioner_set_anchor_rect(
995 + positioner, x, item->hotspot.height / output->scale, 1, 1);
996 + }
997 +
998 + struct xdg_popup *xdg_popup;
999 + struct swaybar_dbusmenu_menu *parent_menu = find_parent_menu(menu);
1000 + if (!parent_menu) {
1001 + // Top level menu
1002 + xdg_popup = xdg_surface_get_popup(xdg_surface, NULL, positioner);
1003 + zwlr_layer_surface_v1_get_popup(output->layer_surface, xdg_popup);
1004 + } else {
1005 + // Nested menu
1006 + xdg_popup = xdg_surface_get_popup(
1007 + xdg_surface, parent_menu->surface->xdg_surface, positioner);
1008 + }
1009 + xdg_positioner_destroy(positioner);
1010 +
1011 + xdg_popup_grab(xdg_popup, menu->dbusmenu->seat->wl_seat,
1012 + menu->dbusmenu->serial);
1013 + xdg_popup_add_listener(xdg_popup, &xdg_popup_listener, menu);
1014 + xdg_surface_add_listener(xdg_surface, &xdg_surface_listener, menu);
1015 + wl_surface_commit(surface);
1016 +
1017 + dbusmenu_surface->xdg_popup = xdg_popup;
1018 + dbusmenu_surface->xdg_surface = xdg_surface;
1019 + dbusmenu_surface->surface = surface;
1020 + dbusmenu_surface->width = surface_width;
1021 + dbusmenu_surface->height = surface_height;
1022 + dbusmenu_surface->configured = false;
1023 + }
1024 +
1025 + commit_menu_surface(menu);
1026 +}
1027 +
1028 +static void swaybar_dbusmenu_draw(struct swaybar_dbusmenu *dbusmenu, int id) {
1029 + if (!dbusmenu || !dbusmenu->menu) {
1030 + sway_log(SWAY_ERROR, "Can not draw dbusmenu, menu structure not initialized yet!");
1031 + return;
1032 + }
1033 + swaybar_dbusmenu_draw_menu(dbusmenu->menu, id, true);
1034 +}
1035 +
1036 +static cairo_status_t read_png_stream(void *closure, unsigned char *data,
1037 + unsigned int length) {
1038 + struct png_stream *png_stream = closure;
1039 + if (length > png_stream->left) {
1040 + return CAIRO_STATUS_READ_ERROR;
1041 + }
1042 + memcpy(data, png_stream->data, length);
1043 + png_stream->data += length;
1044 + png_stream->left -= length;
1045 + return CAIRO_STATUS_SUCCESS;
1046 +}
1047 +
1048 +static cairo_surface_t *read_png(const void *data, size_t data_size) {
1049 + struct png_stream png_stream = {0};
1050 + png_stream.data = data;
1051 + png_stream.left = data_size;
1052 + cairo_surface_t *surface =
1053 + cairo_image_surface_create_from_png_stream(read_png_stream, &png_stream);
1054 +
1055 + if (cairo_surface_status(surface) == CAIRO_STATUS_SUCCESS) {
1056 + return surface;
1057 + }
1058 +
1059 + cairo_surface_destroy(surface);
1060 + return NULL;
1061 +}
1062 +
1063 +static int about_to_show_callback(sd_bus_message *msg, void *data,
1064 + sd_bus_error *error) {
1065 + struct swaybar_sni_slot *slot = data;
1066 + struct swaybar_sni *sni = slot->sni;
1067 + int menu_id = slot->menu_id;
1068 + wl_list_remove(&slot->link);
1069 + free(slot);
1070 +
1071 + int need_update;
1072 + sd_bus_message_read_basic(msg, 'b', &need_update);
1073 + if (need_update) {
1074 + swaybar_dbusmenu_get_layout(sni->tray->menu, menu_id);
1075 + }
1076 +
1077 + swaybar_dbusmenu_draw(sni->tray->menu, menu_id);
1078 +
1079 + sd_bus_call_method_async(sni->tray->bus, NULL, sni->service, sni->menu,
1080 + menu_interface, "Event", NULL, NULL, "isvu", menu_id, "opened", "y", 0,
1081 + time(NULL));
1082 +
1083 + sway_log(SWAY_DEBUG, "%s%s opened id %d", sni->service, sni->menu, menu_id);
1084 +
1085 + return 0;
1086 +}
1087 +
1088 +static void open_menu_id(struct swaybar_dbusmenu *dbusmenu, int menu_id) {
1089 + struct swaybar_dbusmenu_menu *menu = find_menu_id(dbusmenu->menu, menu_id);
1090 + if (!menu || menu->surface) {
1091 + // menu could not be found or is already shown
1092 + return;
1093 + }
1094 +
1095 + struct swaybar_sni *sni = dbusmenu->sni;
1096 + struct swaybar_sni_slot *slot = calloc(1, sizeof(struct swaybar_sni_slot));
1097 + slot->sni = sni;
1098 + slot->menu_id = menu_id;
1099 +
1100 + int ret = sd_bus_call_method_async(sni->tray->bus, &slot->slot, sni->service,
1101 + sni->menu, menu_interface, "AboutToShow", about_to_show_callback, slot, "i",
1102 + menu_id);
1103 +
1104 + if (ret >= 0) {
1105 + wl_list_insert(&sni->slots, &slot->link);
1106 + } else {
1107 + sway_log(SWAY_ERROR, "%s%s failed to send AboutToShow signal: %s",
1108 + sni->service, sni->menu, strerror(-ret));
1109 + free(slot);
1110 + }
1111 +}
1112 +
1113 +static int update_item_properties(struct swaybar_dbusmenu_menu_item *item,
1114 + sd_bus_message *msg) {
1115 + sd_bus_message_enter_container(msg, 'a', "{sv}");
1116 + while (!sd_bus_message_at_end(msg, 0)) {
1117 + sd_bus_message_enter_container(msg, 'e', "sv");
1118 + char *key, *log_value;
1119 + sd_bus_message_read_basic(msg, 's', &key);
1120 + if (strcmp(key, "type") == 0) {
1121 + char *type;
1122 + sd_bus_message_read(msg, "v", "s", &type);
1123 + item->is_separator = strcmp(type, "separator") == 0;
1124 + log_value = type;
1125 + } else if (strcmp(key, "label") == 0) {
1126 + char *label;
1127 + sd_bus_message_read(msg, "v", "s", &label);
1128 + item->label = realloc(item->label, strlen(label) + 1);
1129 + if (!item->label) {
1130 + return -ENOMEM;
1131 + }
1132 + int i = 0;
1133 + for (char *c = label; *c; ++c) {
1134 + if (*c == '_' && !*++c) {
1135 + break;
1136 + }
1137 + item->label[i++] = *c;
1138 + }
1139 + item->label[i] = '\0';
1140 + log_value = label;
1141 + } else if (strcmp(key, "enabled") == 0) {
1142 + int enabled;
1143 + sd_bus_message_read(msg, "v", "b", &enabled);
1144 + item->enabled = enabled;
1145 + log_value = item->enabled ? "true" : "false";
1146 + } else if (strcmp(key, "visible") == 0) {
1147 + int visible;
1148 + sd_bus_message_read(msg, "v", "b", &visible);
1149 + item->visible = visible;
1150 + log_value = item->visible ? "true" : "false";
1151 + } else if (strcmp(key, "icon-name") == 0) {
1152 + sd_bus_message_read(msg, "v", "s", &item->icon_name);
1153 + item->icon_name = strdup(item->icon_name);
1154 + log_value = item->icon_name;
1155 + } else if (strcmp(key, "icon-data") == 0) {
1156 + const void *data;
1157 + size_t data_size;
1158 + sd_bus_message_enter_container(msg, 'v', "ay");
1159 + sd_bus_message_read_array(msg, 'y', &data, &data_size);
1160 + sd_bus_message_exit_container(msg);
1161 + item->icon_data = read_png(data, data_size);
1162 + log_value = item->icon_data ? "<success>" : "<failure>";
1163 + } else if (strcmp(key, "toggle-type") == 0) {
1164 + char *toggle_type;
1165 + sd_bus_message_read(msg, "v", "s", &toggle_type);
1166 + if (strcmp(toggle_type, "checkmark") == 0) {
1167 + item->toggle_type = MENU_CHECKMARK;
1168 + } else if (strcmp(toggle_type, "radio") == 0) {
1169 + item->toggle_type = MENU_RADIO;
1170 + }
1171 + log_value = toggle_type;
1172 + } else if (strcmp(key, "toggle-state") == 0) {
1173 + sd_bus_message_read(msg, "v", "i", &item->toggle_state);
1174 + log_value = item->toggle_state == 0 ?
1175 + "off" : item->toggle_state == 1 ? "on" : "indeterminate";
1176 + } else if (strcmp(key, "children-display") == 0) {
1177 + char *children_display;
1178 + sd_bus_message_read(msg, "v", "s", &children_display);
1179 + if (strcmp(children_display, "submenu") == 0) {
1180 + struct swaybar_dbusmenu_menu *submenu;
1181 + if (item->id != 0) {
1182 + submenu = calloc(1, sizeof(struct swaybar_dbusmenu_menu));
1183 + if (!submenu) {
1184 + sway_log(SWAY_ERROR, "Could not allocate submenu");
1185 + return -ENOMEM;
1186 + }
1187 + } else {
1188 + submenu = item->menu;
1189 + }
1190 + submenu->item_id = item->id;
1191 + submenu->dbusmenu = item->menu->dbusmenu;
1192 + item->submenu = submenu;
1193 + }
1194 + log_value = children_display;
1195 + } else {
1196 + // Ignored: shortcut, disposition
1197 + sd_bus_message_skip(msg, "v");
1198 + log_value = "<ignored>";
1199 + }
1200 + sd_bus_message_exit_container(msg);
1201 + sway_log(SWAY_DEBUG, "%s%s %s = '%s'", item->menu->dbusmenu->sni->service,
1202 + item->menu->dbusmenu->sni->menu, key, log_value);
1203 + }
1204 + return sd_bus_message_exit_container(msg);
1205 +}
1206 +
1207 +static int get_layout_callback(sd_bus_message *msg, void *data,
1208 + sd_bus_error *error) {
1209 + struct swaybar_sni_slot *slot = data;
1210 + struct swaybar_sni *sni = slot->sni;
1211 + int menu_id = slot->menu_id;
1212 + wl_list_remove(&slot->link);
1213 + free(slot);
1214 +
1215 + struct swaybar_dbusmenu *dbusmenu = sni->tray->menu;
1216 + if (dbusmenu == NULL) {
1217 + return 0;
1218 + }
1219 +
1220 + if (sd_bus_message_is_method_error(msg, NULL)) {
1221 + sway_log(SWAY_ERROR, "%s%s failed to get layout: %s",
1222 + dbusmenu->sni->service, dbusmenu->sni->menu,
1223 + sd_bus_message_get_error(msg)->message);
1224 + return sd_bus_message_get_errno(msg);
1225 + }
1226 +
1227 + // Parse the layout. The layout comes as a recursive structure as
1228 + // dbus message in the following form (ia{sv}av)
1229 +
1230 + // Skip the menu revision
1231 + sd_bus_message_skip(msg, "u");
1232 +
1233 + sni->tray->menu_pointer_focus = NULL;
1234 +
1235 + bool already_open = false;
1236 + struct swaybar_dbusmenu_menu *menu_to_update =
1237 + find_menu_id(dbusmenu->menu, menu_id);
1238 + if (menu_to_update && menu_to_update->surface) {
1239 + already_open = true;
1240 + }
1241 +
1242 + if (dbusmenu->menu) {
1243 + close_menus(dbusmenu->menu);
1244 + swaybar_dbusmenu_menu_destroy(dbusmenu->menu);
1245 + dbusmenu->menu = NULL;
1246 + }
1247 +
1248 + struct swaybar_dbusmenu_menu_item *parent_item = NULL;
1249 + struct swaybar_dbusmenu_menu *menu = calloc(1,
1250 + sizeof(struct swaybar_dbusmenu_menu));
1251 + if (!menu) {
1252 + sway_log(SWAY_ERROR, "Could not allocate menu");
1253 + return -ENOMEM;
1254 + }
1255 + dbusmenu->menu = menu;
1256 + menu->dbusmenu = dbusmenu;
1257 + int ret = 0;
1258 + while (!sd_bus_message_at_end(msg, 1)) {
1259 + sd_bus_message_enter_container(msg, 'r', "ia{sv}av");
1260 +
1261 + struct swaybar_dbusmenu_menu_item *item
1262 + = calloc(1, sizeof(struct swaybar_dbusmenu_menu_item));
1263 + if (!item) {
1264 + ret = -ENOMEM;
1265 + break;
1266 + }
1267 +
1268 + // default properties
1269 + item->parent_item = parent_item;
1270 + item->menu = menu;
1271 + item->enabled = true;
1272 + item->visible = true;
1273 + item->toggle_state = -1;
1274 +
1275 + // Read the id
1276 + sd_bus_message_read_basic(msg, 'i', &item->id);
1277 +
1278 + // Process a{sv}. a{sv} contains key-value pairs
1279 + ret = update_item_properties(item, msg);
1280 + if (!menu->items) {
1281 + menu->items = create_list();
1282 + }
1283 + list_add(menu->items, item);
1284 + if (ret < 0) {
1285 + break;
1286 + }
1287 + if (item->id != 0 && item->submenu) {
1288 + menu = item->submenu;
1289 + }
1290 +
1291 + sd_bus_message_enter_container(msg, 'a', "v");
1292 +
1293 + parent_item = item;
1294 + while (parent_item && sd_bus_message_at_end(msg, 0)) {
1295 + if (parent_item->submenu) {
1296 + menu = find_parent_menu(menu);
1297 + }
1298 + parent_item = parent_item->parent_item;
1299 +
1300 + sd_bus_message_exit_container(msg);
1301 + sd_bus_message_exit_container(msg);
1302 + sd_bus_message_exit_container(msg);
1303 + }
1304 +
1305 + if (parent_item) {
1306 + sd_bus_message_enter_container(msg, 'v', "(ia{sv}av)");
1307 + }
1308 + }
1309 +
1310 + if (already_open) {
1311 + swaybar_dbusmenu_draw(sni->tray->menu, menu_id);
1312 + } else {
1313 + open_menu_id(dbusmenu, 0);
1314 + }
1315 +
1316 + return 0;
1317 +}
1318 +
1319 +static void swaybar_dbusmenu_subscribe_signal(struct swaybar_dbusmenu *menu,
1320 + const char *signal_name, sd_bus_message_handler_t callback) {
1321 + int ret = sd_bus_match_signal_async( menu->sni->tray->bus, NULL,
1322 + menu->sni->service, menu->sni->menu, menu_interface, signal_name, callback,
1323 + NULL, menu->sni);
1324 +
1325 + if (ret < 0) {
1326 + sway_log(SWAY_ERROR, "%s%s failed to subscribe to signal %s: %s",
1327 + menu->sni->service, menu->sni->menu, signal_name, strerror(-ret));
1328 + }
1329 +}
1330 +
1331 +static void swaybar_dbusmenu_setup_signals(struct swaybar_dbusmenu *menu) {
1332 + swaybar_dbusmenu_subscribe_signal(menu, "ItemsPropertiesUpdated",
1333 + handle_items_properties_updated);
1334 + swaybar_dbusmenu_subscribe_signal(menu, "LayoutUpdated",
1335 + handle_layout_updated);
1336 + swaybar_dbusmenu_subscribe_signal(menu, "ItemActivationRequested",
1337 + handle_item_activation_requested);
1338 +}
1339 +
1340 +static void swaybar_dbusmenu_get_layout(struct swaybar_dbusmenu *menu, int id) {
1341 + if (menu == NULL) {
1342 + return;
1343 + }
1344 +
1345 + struct swaybar_sni_slot *slot = calloc(1, sizeof(struct swaybar_sni_slot));
1346 + if (slot == NULL) {
1347 + sway_log(SWAY_ERROR, "Could not allocate swaybar_sni_slot");
1348 + return;
1349 + }
1350 + slot->sni = menu->sni;
1351 + slot->menu_id = id;
1352 +
1353 + int ret =
1354 + sd_bus_call_method_async(menu->sni->tray->bus, NULL, menu->sni->service,
1355 + menu->sni->menu, menu_interface, "GetLayout",
1356 + get_layout_callback, slot, "iias", id, -1, NULL);
1357 +
1358 + if (ret >= 0) {
1359 + wl_list_insert(&menu->sni->slots, &slot->link);
1360 + } else {
1361 + sway_log(SWAY_ERROR, "%s%s failed to call method GetLayout: %s",
1362 + menu->sni->service, menu->sni->menu, strerror(-ret));
1363 + free(slot);
1364 + }
1365 +}
1366 +
1367 +static void swaybar_dbusmenu_get_layout_root(struct swaybar_dbusmenu *menu) {
1368 + swaybar_dbusmenu_get_layout(menu, 0);
1369 +}
1370 +
1371 +static int get_icon_theme_path_callback(sd_bus_message *msg, void *data,
1372 + sd_bus_error *error) {
1373 + struct swaybar_sni_slot *slot = data;
1374 + struct swaybar_sni *sni = slot->sni;
1375 + wl_list_remove(&slot->link);
1376 + free(slot);
1377 +
1378 + int ret;
1379 + if (!sd_bus_message_is_method_error(msg, NULL)) {
1380 + ret = sd_bus_message_enter_container(msg, 'v', NULL);
1381 + if (ret >= 0) {
1382 + ret = sd_bus_message_read_strv(msg, &sni->menu_icon_theme_paths);
1383 + }
1384 + } else {
1385 + ret = -sd_bus_message_get_errno(msg);
1386 + }
1387 +
1388 + if (ret < 0) {
1389 + sway_log(SWAY_ERROR, "%s%s failed to read IconThemePath: %s", sni->service,
1390 + sni->menu, strerror(-ret));
1391 + }
1392 + return ret;
1393 +}
1394 +
1395 +static void swaybar_dbusmenu_setup(struct swaybar_dbusmenu *menu) {
1396 + struct swaybar_sni_slot *slot = calloc(1, sizeof(struct swaybar_sni_slot));
1397 + slot->sni = menu->sni;
1398 + int ret = sd_bus_call_method_async( menu->sni->tray->bus, &slot->slot,
1399 + menu->sni->service, menu->sni->path, "org.freedesktop.DBus.Properties",
1400 + "Get", get_icon_theme_path_callback, slot, "ss", menu->sni->interface,
1401 + "IconThemePath");
1402 + if (ret >= 0) {
1403 + wl_list_insert(&menu->sni->slots, &slot->link);
1404 + } else {
1405 + sway_log(SWAY_ERROR, "%s%s failed to get IconThemePath: %s",
1406 + menu->sni->service, menu->sni->menu, strerror(-ret));
1407 + free(slot);
1408 + }
1409 +
1410 + swaybar_dbusmenu_setup_signals(menu);
1411 + swaybar_dbusmenu_get_layout_root(menu);
1412 +}
1413 +
1414 +void swaybar_dbusmenu_open(struct swaybar_sni *sni,
1415 + struct swaybar_output *output, struct swaybar_seat *seat, uint32_t serial,
1416 + int x, int y) {
1417 + struct swaybar_dbusmenu *dbusmenu = sni->tray->menu;
1418 + if (!dbusmenu) {
1419 + dbusmenu = calloc(1, sizeof(struct swaybar_dbusmenu));
1420 + if (!dbusmenu) {
1421 + sway_log(SWAY_DEBUG, "Could not allocate dbusmenu");
1422 + return;
1423 + }
1424 + sni->tray->menu = dbusmenu;
1425 + }
1426 +
1427 + dbusmenu->sni = sni;
1428 + dbusmenu->output = output;
1429 + dbusmenu->seat = seat;
1430 + dbusmenu->serial = serial;
1431 + dbusmenu->x = x;
1432 + dbusmenu->y = y;
1433 + dbusmenu->bar = output->bar;
1434 +
1435 + swaybar_dbusmenu_setup(dbusmenu);
1436 +}
1437 +
1438 +static void close_child_menus_except(struct swaybar_dbusmenu_menu *menu,
1439 + int id) {
1440 + if (!menu || !menu->items) {
1441 + return;
1442 + }
1443 + // close all child menus of menu, except the child menu with the given id
1444 + for (int i = 0; i < menu->items->length; ++i) {
1445 + struct swaybar_dbusmenu_menu_item *item = menu->items->items[i];
1446 + if (item->id == id) {
1447 + continue;
1448 + }
1449 + struct swaybar_dbusmenu_menu *menu = item->submenu;
1450 + if (menu && menu->item_id != 0) {
1451 + close_menus(menu);
1452 + }
1453 + }
1454 +}
1455 +
1456 +static void
1457 +pointer_motion_process_item(struct swaybar_dbusmenu_menu *focused_menu,
1458 + struct swaybar_dbusmenu_menu_item *item, struct swaybar_seat *seat) {
1459 + int scale = focused_menu->dbusmenu->output->scale;
1460 + double x = seat->pointer.x * scale;
1461 + double y = seat->pointer.y * scale;
1462 + if (is_in_hotspot(&item->hotspot, x, y) && item->enabled &&
1463 + !item->is_separator) {
1464 + struct swaybar_tray *tray = focused_menu->dbusmenu->sni->tray;
1465 + struct swaybar_sni *sni = tray->menu->sni;
1466 + if (focused_menu->last_hovered_item != item) {
1467 + sd_bus_call_method_async(tray->bus, NULL, sni->service, sni->menu,
1468 + menu_interface, "Event", NULL, NULL, "isvu", item->id, "hovered",
1469 + "y", 0, time(NULL));
1470 +
1471 + sway_log(SWAY_DEBUG, "%s%s hovered id %d", sni->service, sni->menu,
1472 + item->id);
1473 +
1474 + // open child menu if current item has a child menu and close other
1475 + // potential open child menus. Only one child menu can be open at a time
1476 + close_child_menus_except(focused_menu, item->id);
1477 + open_menu_id(focused_menu->dbusmenu, item->id);
1478 + focused_menu->last_hovered_item = item;
1479 +
1480 + // a different item needs to be highlighted
1481 + swaybar_dbusmenu_draw_menu(focused_menu, focused_menu->item_id, false);
1482 + }
1483 +
1484 + }
1485 +}
1486 +
1487 +bool dbusmenu_pointer_motion(struct swaybar_seat *seat,
1488 + struct wl_pointer *wl_pointer, uint32_t time_, wl_fixed_t surface_x,
1489 + wl_fixed_t surface_y) {
1490 + struct swaybar_tray *tray = seat->bar->tray;
1491 + struct swaybar_dbusmenu_menu *focused_menu = tray->menu_pointer_focus;
1492 + if (!(tray && tray->menu && focused_menu)) {
1493 + return false;
1494 + }
1495 +
1496 + for (int i = 0; i < focused_menu->items->length; ++i) {
1497 + struct swaybar_dbusmenu_menu_item *item = focused_menu->items->items[i];
1498 + pointer_motion_process_item(focused_menu, item, seat);
1499 + }
1500 +
1501 + return true;
1502 +}
1503 +
1504 +static struct swaybar_dbusmenu_menu *
1505 +dbusmenu_menu_find_menu_surface(struct swaybar_dbusmenu_menu *menu,
1506 + struct wl_surface *surface) {
1507 + if (menu->surface && menu->surface->surface == surface) {
1508 + return menu;
1509 + }
1510 + if (!menu->items) {
1511 + return NULL;
1512 + }
1513 + for (int i = 0; i < menu->items->length; ++i) {
1514 + struct swaybar_dbusmenu_menu_item *item = menu->items->items[i];
1515 + struct swaybar_dbusmenu_menu *child_menu = item->submenu;
1516 + if (child_menu && child_menu->surface
1517 + && child_menu->surface->surface == surface) {
1518 + return child_menu;
1519 + }
1520 + if (child_menu) {
1521 + if (child_menu->item_id == 0) {
1522 + continue;
1523 + }
1524 + struct swaybar_dbusmenu_menu *child_child_menu =
1525 + dbusmenu_menu_find_menu_surface(child_menu, surface);
1526 + if (child_child_menu != NULL) {
1527 + return child_child_menu;
1528 + }
1529 + }
1530 + }
1531 +
1532 + return NULL;
1533 +}
1534 +
1535 +static void close_menus_by_id(struct swaybar_dbusmenu_menu *menu, int item_id) {
1536 + if (menu->items == NULL) {
1537 + return;
1538 + }
1539 + for (int i = 0; i < menu->items->length; ++i) {
1540 + struct swaybar_dbusmenu_menu_item *item = menu->items->items[i];
1541 + struct swaybar_dbusmenu_menu *child_menu = item->submenu;
1542 + if (child_menu && child_menu->item_id == item_id && child_menu->item_id != 0) {
1543 + close_menus(child_menu);
1544 + }
1545 + }
1546 +}
1547 +
1548 +static void close_unfocused_child_menus(struct swaybar_dbusmenu_menu *menu,
1549 + struct swaybar_seat *seat) {
1550 + for (int i = 0; i < menu->items->length; ++i) {
1551 + struct swaybar_dbusmenu_menu_item *item = menu->items->items[i];
1552 +
1553 + int scale = menu->dbusmenu->output->scale;
1554 + int x = seat->pointer.x * scale;
1555 + int y = seat->pointer.y * scale;
1556 + if (item->submenu && item->submenu->item_id != 0
1557 + && !is_in_hotspot(&item->hotspot, x, y)) {
1558 + close_menus_by_id(menu, item->id);
1559 + }
1560 + }
1561 +}
1562 +
1563 +bool dbusmenu_pointer_frame(struct swaybar_seat *data,
1564 + struct wl_pointer *wl_pointer) {
1565 + struct swaybar_tray *tray = data->bar->tray;
1566 + if (!(tray && tray->menu && tray->menu_pointer_focus)) {
1567 + return false;
1568 + }
1569 + return true;
1570 +}
1571 +
1572 +bool dbusmenu_pointer_axis(struct swaybar_seat *data,
1573 + struct wl_pointer *wl_pointer) {
1574 + struct swaybar_tray *tray = data->bar->tray;
1575 + if (!(tray && tray->menu && tray->menu_pointer_focus)) {
1576 + return false;
1577 + }
1578 + return true;
1579 +}
1580 +
1581 +bool dbusmenu_pointer_enter(void *data, struct wl_pointer *wl_pointer,
1582 + uint32_t serial, struct wl_surface *surface, wl_fixed_t surface_x,
1583 + wl_fixed_t surface_y) {
1584 + struct swaybar_seat *seat = data;
1585 + struct swaybar_tray *tray = seat->bar->tray;
1586 + if (!(tray && tray->menu)) {
1587 + return false;
1588 + }
1589 +
1590 + struct swaybar_dbusmenu_menu *new_focused_menu =
1591 + dbusmenu_menu_find_menu_surface(tray->menu->menu, surface);
1592 +
1593 + // Check if there are any child menus
1594 + bool has_child_menus = false;
1595 + if (new_focused_menu && new_focused_menu->items) {
1596 + for (int i = 0; i < new_focused_menu->items->length; ++i) {
1597 + struct swaybar_dbusmenu_menu_item *item = new_focused_menu->items->items[i];
1598 + if (item->submenu && item->submenu->item_id != 0) {
1599 + has_child_menus = true;
1600 + }
1601 + }
1602 + }
1603 +
1604 + if (has_child_menus) {
1605 + close_unfocused_child_menus(new_focused_menu, seat);
1606 + }
1607 +
1608 + tray->menu_pointer_focus = new_focused_menu;
1609 +
1610 + return true;
1611 +}
1612 +
1613 +bool dbusmenu_pointer_leave(void *data, struct wl_pointer *wl_pointer,
1614 + uint32_t serial, struct wl_surface *surface) {
1615 + struct swaybar_seat *seat = data;
1616 + struct swaybar_tray *tray = seat->bar->tray;
1617 + if (!(tray && tray->menu)) {
1618 + return false;
1619 + }
1620 +
1621 + tray->menu_pointer_focus = NULL;
1622 +
1623 + return true;
1624 +}
1625 +
1626 +static bool dbusmenu_pointer_button_left_process_item(struct swaybar_dbusmenu *dbusmenu,
1627 + struct swaybar_dbusmenu_menu_item *item, struct swaybar_seat *seat) {
1628 + struct swaybar_sni *sni = dbusmenu->sni;
1629 + struct swaybar_tray *tray = sni->tray;
1630 + int scale = dbusmenu->output->scale;
1631 +
1632 + if (is_in_hotspot(&item->hotspot, seat->pointer.x * scale,
1633 + seat->pointer.y * scale)) {
1634 + if (!item->enabled || item->is_separator) {
1635 + return false;
1636 + }
1637 +
1638 + sway_log(SWAY_DEBUG, "%s%s menu clicked id %d", sni->service, sni->menu,
1639 + item->id);
1640 +
1641 + sd_bus_call_method_async(tray->bus, NULL, sni->service, sni->menu,
1642 + menu_interface, "Event", NULL, NULL, "isvu", item->id, "clicked", "y", 0,
1643 + time(NULL));
1644 +
1645 + if (item->submenu) {
1646 + open_menu_id(dbusmenu, item->id);
1647 + } else {
1648 + // The user clicked an menu item other than a submenu. That means
1649 + // the user made it's choise. Close the tray menu.
1650 + swaybar_dbusmenu_destroy(tray->menu);
1651 + }
1652 + return true;
1653 + }
1654 +
1655 + return false;
1656 +}
1657 +
1658 +static bool dbusmenu_pointer_button_left(struct swaybar_dbusmenu *dbusmenu,
1659 + struct swaybar_seat *seat) {
1660 + struct swaybar_dbusmenu_menu *focused_menu
1661 + = dbusmenu->sni->tray->menu_pointer_focus;
1662 +
1663 + if (!focused_menu) {
1664 + return true;
1665 + }
1666 +
1667 + for (int i = 0; i < focused_menu->items->length; ++i) {
1668 + struct swaybar_dbusmenu_menu_item *item = focused_menu->items->items[i];
1669 + if (dbusmenu_pointer_button_left_process_item(dbusmenu, item, seat)) {
1670 + return true;
1671 + }
1672 + }
1673 +
1674 + return true;
1675 +}
1676 +
1677 +bool dbusmenu_pointer_button(void *data, struct wl_pointer *wl_pointer,
1678 + uint32_t serial, uint32_t time_, uint32_t button, uint32_t state) {
1679 + struct swaybar_seat *seat = data;
1680 + struct swaybar_tray *tray = seat->bar->tray;
1681 + if (!(tray && tray->menu)) {
1682 + return false;
1683 + }
1684 +
1685 + if (state != WL_POINTER_BUTTON_STATE_PRESSED) {
1686 + // intentionally left blank
1687 + return true;
1688 + } else if (!tray->menu_pointer_focus) {
1689 + swaybar_dbusmenu_destroy(tray->menu);
1690 + return true;
1691 + } else if (button == BTN_LEFT) {
1692 + return dbusmenu_pointer_button_left(tray->menu, seat);
1693 + }
1694 +
1695 + return false;
1696 +}
1697 diff --git a/swaybar/tray/item.c b/swaybar/tray/item.c
1698 index 1f18b8bb32..d159640f67 100644
1699 --- a/swaybar/tray/item.c
1700 +++ b/swaybar/tray/item.c
1701 @@ -8,6 +8,7 @@
1702 #include "swaybar/bar.h"
1703 #include "swaybar/config.h"
1704 #include "swaybar/input.h"
1705 +#include "swaybar/tray/dbusmenu.h"
1706 #include "swaybar/tray/host.h"
1707 #include "swaybar/tray/icon.h"
1708 #include "swaybar/tray/item.h"
1709 @@ -333,8 +334,9 @@ void destroy_sni(struct swaybar_sni *sni) {
1710 free(sni);
1711 }
1712
1713 -static void handle_click(struct swaybar_sni *sni, int x, int y,
1714 - uint32_t button, int delta) {
1715 +static void handle_click(struct swaybar_sni *sni, struct swaybar_output *output,
1716 + struct swaybar_seat *seat, uint32_t serial, int x, int y, uint32_t button,
1717 + int delta) {
1718 const char *method = NULL;
1719 struct tray_binding *binding = NULL;
1720 wl_list_for_each(binding, &sni->tray->bar->config->tray_bindings, link) {
1721 @@ -365,7 +367,11 @@ static void handle_click(struct swaybar_sni *sni, int x, int y,
1722 method = "ContextMenu";
1723 }
1724
1725 - if (strncmp(method, "Scroll", strlen("Scroll")) == 0) {
1726 + if (strcmp(method, "ContextMenu") == 0) {
1727 + if (sni->menu && !sni->tray->menu) {
1728 + swaybar_dbusmenu_open(sni, output, seat, serial, x, y);
1729 + }
1730 + } else if (strncmp(method, "Scroll", strlen("Scroll")) == 0) {
1731 char dir = method[strlen("Scroll")];
1732 char *orientation = (dir == 'U' || dir == 'D') ? "vertical" : "horizontal";
1733 int sign = (dir == 'U' || dir == 'L') ? -1 : 1;
1734 @@ -385,6 +391,7 @@ static int cmp_sni_id(const void *item, const void *cmp_to) {
1735
1736 static enum hotspot_event_handling icon_hotspot_callback(
1737 struct swaybar_output *output, struct swaybar_hotspot *hotspot,
1738 + struct swaybar_seat *seat, uint32_t serial,
1739 double x, double y, uint32_t button, bool released, void *data) {
1740 sway_log(SWAY_DEBUG, "Clicked on %s", (char *)data);
1741
1742 @@ -406,7 +413,8 @@ static enum hotspot_event_handling icon_hotspot_callback(
1743 (int) output->output_height - config->gaps.bottom - y);
1744
1745 sway_log(SWAY_DEBUG, "Guessing click position at (%d, %d)", global_x, global_y);
1746 - handle_click(sni, global_x, global_y, button, 1); // TODO get delta from event
1747 + // TODO get delta from event
1748 + handle_click(sni, output, seat, serial, global_x, global_y, button, 1);
1749 return HOTSPOT_IGNORE;
1750 } else {
1751 sway_log(SWAY_DEBUG, "but it doesn't exist");
|