summaryrefslogtreecommitdiff
path: root/sway/6249.patch
blob: 71fa1f6fa54695edaaff9c734ef59f5dc34f09d3 (plain)
    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");

Generated by cgit