From 1868bfd07f1bd12838a4a214e90bb9b24185df9d Mon Sep 17 00:00:00 2001
From: Simon Ser <contact@emersion.fr>
Date: Wed, 22 Nov 2023 00:38:05 +0100
Subject: [PATCH 1/6] ci: checkout wlroots 0.17

---
 .builds/alpine.yml    | 2 +-
 .builds/archlinux.yml | 2 +-
 .builds/freebsd.yml   | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/.builds/alpine.yml b/.builds/alpine.yml
index 7a1fa58e0c..6e6fc5f48d 100644
--- a/.builds/alpine.yml
+++ b/.builds/alpine.yml
@@ -25,7 +25,7 @@ packages:
   - hwdata-dev
 sources:
   - https://github.com/swaywm/sway
-  - https://gitlab.freedesktop.org/wlroots/wlroots.git
+  - https://gitlab.freedesktop.org/wlroots/wlroots.git#0.17
 tasks:
   - wlroots: |
       cd wlroots
diff --git a/.builds/archlinux.yml b/.builds/archlinux.yml
index e249571ee7..a445e222cd 100644
--- a/.builds/archlinux.yml
+++ b/.builds/archlinux.yml
@@ -22,7 +22,7 @@ packages:
   - hwdata
 sources:
   - https://github.com/swaywm/sway
-  - https://gitlab.freedesktop.org/wlroots/wlroots.git
+  - https://gitlab.freedesktop.org/wlroots/wlroots.git#0.17
 tasks:
   - wlroots: |
       cd wlroots
diff --git a/.builds/freebsd.yml b/.builds/freebsd.yml
index 977fe467e8..ddcac4f8ce 100644
--- a/.builds/freebsd.yml
+++ b/.builds/freebsd.yml
@@ -31,7 +31,7 @@ packages:
 - misc/hwdata
 sources:
 - https://github.com/swaywm/sway
-- https://gitlab.freedesktop.org/wlroots/wlroots.git
+- https://gitlab.freedesktop.org/wlroots/wlroots.git#0.17
 tasks:
 - setup: |
     cd sway

From 3fc5ba6febe3919071a3afb2498ff52b1643e5ca Mon Sep 17 00:00:00 2001
From: Simon Ser <contact@emersion.fr>
Date: Thu, 15 Feb 2024 11:52:38 +0100
Subject: [PATCH 2/6] Fix build with wlroots DRM backend disabled

The header is not installed by wlroots when the DRM backend is
disabled. We don't need it here, so don't include it.

Closes: https://github.com/swaywm/sway/issues/7943
(cherry picked from commit ca40663d4277ade2a7dfec0484db5b7881799ee4)
---
 include/sway/server.h | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)

diff --git a/include/sway/server.h b/include/sway/server.h
index ccf4a9cc26..433a298565 100644
--- a/include/sway/server.h
+++ b/include/sway/server.h
@@ -2,6 +2,22 @@
 #define _SWAY_SERVER_H
 #include <stdbool.h>
 #include <wayland-server-core.h>
+#include <wlr/backend.h>
+#include <wlr/render/allocator.h>
+#include <wlr/render/wlr_renderer.h>
+#include <wlr/types/wlr_compositor.h>
+#include <wlr/types/wlr_data_device.h>
+#include <wlr/types/wlr_input_method_v2.h>
+#include <wlr/types/wlr_foreign_toplevel_management_v1.h>
+#include <wlr/types/wlr_layer_shell_v1.h>
+#include <wlr/types/wlr_output_management_v1.h>
+#include <wlr/types/wlr_output_power_management_v1.h>
+#include <wlr/types/wlr_presentation_time.h>
+#include <wlr/types/wlr_relative_pointer_v1.h>
+#include <wlr/types/wlr_session_lock_v1.h>
+#include <wlr/types/wlr_server_decoration.h>
+#include <wlr/types/wlr_text_input_v3.h>
+#include <wlr/types/wlr_xdg_shell.h>
 #include "config.h"
 #include "list.h"
 #include "sway/desktop/idle_inhibit_v1.h"

From fffe886a14c79bf0d6815cb01efa76afb62f2372 Mon Sep 17 00:00:00 2001
From: Felix Weilbach <felix.weilbach@t-online.de>
Date: Sun, 30 May 2021 20:45:01 +0200
Subject: [PATCH 3/6] Tray: Implement dbusmenu

Co-authored-by: Ian Fan <ianfan0@gmail.com>
Co-authored-by: Nathan Schulte <nmschulte@gmail.com>

Signed-off-by: Felix Weilbach <felix.weilbach@t-online.de>
---
 include/swaybar/bar.h           |    1 +
 include/swaybar/input.h         |    5 +-
 include/swaybar/tray/dbusmenu.h |   27 +
 include/swaybar/tray/item.h     |    2 +
 include/swaybar/tray/tray.h     |    3 +
 swaybar/bar.c                   |    4 +
 swaybar/input.c                 |   49 +-
 swaybar/meson.build             |    3 +-
 swaybar/render.c                |    2 +
 swaybar/tray/dbusmenu.c         | 1367 +++++++++++++++++++++++++++++++
 swaybar/tray/item.c             |   16 +-
 11 files changed, 1468 insertions(+), 11 deletions(-)
 create mode 100644 include/swaybar/tray/dbusmenu.h
 create mode 100644 swaybar/tray/dbusmenu.c

diff --git a/include/swaybar/bar.h b/include/swaybar/bar.h
index 197d219011..d0b3dc8153 100644
--- a/include/swaybar/bar.h
+++ b/include/swaybar/bar.h
@@ -33,6 +33,7 @@ struct swaybar {
 	struct zxdg_output_manager_v1 *xdg_output_manager;
 	struct wp_cursor_shape_manager_v1 *cursor_shape_manager;
 	struct wl_shm *shm;
+	struct xdg_wm_base *wm_base;
 
 	struct swaybar_config *config;
 	struct status_line *status;
diff --git a/include/swaybar/input.h b/include/swaybar/input.h
index 8ea88a69a0..81ccaa989a 100644
--- a/include/swaybar/input.h
+++ b/include/swaybar/input.h
@@ -15,6 +15,7 @@
 
 struct swaybar;
 struct swaybar_output;
+struct swaybar_seat;
 
 struct swaybar_pointer {
 	struct wl_pointer *pointer;
@@ -48,8 +49,8 @@ struct swaybar_hotspot {
 	struct wl_list link; // swaybar_output::hotspots
 	int x, y, width, height;
 	enum hotspot_event_handling (*callback)(struct swaybar_output *output,
-		struct swaybar_hotspot *hotspot, double x, double y, uint32_t button,
-		bool released, void *data);
+			struct swaybar_hotspot *hotspot, struct swaybar_seat *seat, uint32_t serial,
+			double x, double y, uint32_t button, bool released, void *data);
 	void (*destroy)(void *data);
 	void *data;
 };
diff --git a/include/swaybar/tray/dbusmenu.h b/include/swaybar/tray/dbusmenu.h
new file mode 100644
index 0000000000..dc90f6e571
--- /dev/null
+++ b/include/swaybar/tray/dbusmenu.h
@@ -0,0 +1,27 @@
+#ifndef _SWAYBAR_TRAY_DBUSMENU_H
+#define _SWAYBAR_TRAY_DBUSMENU_H
+
+#include "swaybar/bar.h"
+#include "swaybar/tray/item.h"
+
+void swaybar_dbusmenu_open(struct swaybar_sni *sni,
+		struct swaybar_output *output, struct swaybar_seat *seat, uint32_t serial,
+		int x, int y);
+
+bool dbusmenu_pointer_button(void *data, struct wl_pointer *wl_pointer,
+		uint32_t serial, uint32_t time_, uint32_t button, uint32_t state);
+
+bool dbusmenu_pointer_motion(struct swaybar_seat *seat, struct wl_pointer *wl_pointer,
+		uint32_t time_, wl_fixed_t surface_x, wl_fixed_t surface_y);
+
+bool dbusmenu_pointer_enter(void *data, struct wl_pointer *wl_pointer, uint32_t serial,
+		struct wl_surface *surface, wl_fixed_t surface_x, wl_fixed_t surface_y);
+
+bool dbusmenu_pointer_leave(void *data, struct wl_pointer *wl_pointer, uint32_t serial,
+		struct wl_surface *surface);
+
+bool dbusmenu_pointer_frame(struct swaybar_seat *data, struct wl_pointer *wl_pointer);
+
+bool dbusmenu_pointer_axis(struct swaybar_seat *data, struct wl_pointer *wl_pointer);
+
+#endif
diff --git a/include/swaybar/tray/item.h b/include/swaybar/tray/item.h
index 73937a0cc2..9a4a00ff66 100644
--- a/include/swaybar/tray/item.h
+++ b/include/swaybar/tray/item.h
@@ -18,6 +18,7 @@ struct swaybar_pixmap {
 struct swaybar_sni_slot {
 	struct wl_list link; // swaybar_sni::slots
 	struct swaybar_sni *sni;
+	int menu_id;
 	const char *prop;
 	const char *type;
 	void *dest;
@@ -48,6 +49,7 @@ struct swaybar_sni {
 	char *icon_theme_path; // non-standard KDE property
 
 	struct wl_list slots; // swaybar_sni_slot::link
+	char **menu_icon_theme_paths;
 };
 
 struct swaybar_sni *create_sni(char *id, struct swaybar_tray *tray);
diff --git a/include/swaybar/tray/tray.h b/include/swaybar/tray/tray.h
index d2e80a6d47..853f17cdc1 100644
--- a/include/swaybar/tray/tray.h
+++ b/include/swaybar/tray/tray.h
@@ -32,6 +32,9 @@ struct swaybar_tray {
 
 	list_t *basedirs; // char *
 	list_t *themes; // struct swaybar_theme *
+
+	struct swaybar_dbusmenu *menu;
+	struct swaybar_dbusmenu_menu *menu_pointer_focus;
 };
 
 struct swaybar_tray *create_tray(struct swaybar *bar);
diff --git a/swaybar/bar.c b/swaybar/bar.c
index 4d20f20f0a..ee9f843e32 100644
--- a/swaybar/bar.c
+++ b/swaybar/bar.c
@@ -28,6 +28,7 @@
 #include "pool-buffer.h"
 #include "wlr-layer-shell-unstable-v1-client-protocol.h"
 #include "xdg-output-unstable-v1-client-protocol.h"
+#include "xdg-shell-client-protocol.h"
 
 void free_workspaces(struct wl_list *list) {
 	struct swaybar_workspace *ws, *tmp;
@@ -364,6 +365,8 @@ static void handle_global(void *data, struct wl_registry *registry,
 	} else if (strcmp(interface, wp_cursor_shape_manager_v1_interface.name) == 0) {
 		bar->cursor_shape_manager = wl_registry_bind(registry, name,
 			&wp_cursor_shape_manager_v1_interface, 1);
+	} else if (strcmp(interface, xdg_wm_base_interface.name) == 0) {
+		bar->wm_base = wl_registry_bind(registry, name, &xdg_wm_base_interface, 1);
 	}
 }
 
@@ -538,6 +541,7 @@ void bar_teardown(struct swaybar *bar) {
 #if HAVE_TRAY
 	destroy_tray(bar->tray);
 #endif
+	xdg_wm_base_destroy(bar->wm_base);
 	free_outputs(&bar->outputs);
 	free_outputs(&bar->unused_outputs);
 	free_seats(&bar->seats);
diff --git a/swaybar/input.c b/swaybar/input.c
index ada4bc8624..dfac5480f4 100644
--- a/swaybar/input.c
+++ b/swaybar/input.c
@@ -10,6 +10,10 @@
 #include "swaybar/input.h"
 #include "swaybar/ipc.h"
 
+#if HAVE_TRAY
+#include "swaybar/tray/dbusmenu.h"
+#endif
+
 void free_hotspots(struct wl_list *list) {
 	struct swaybar_hotspot *hotspot, *tmp;
 	wl_list_for_each_safe(hotspot, tmp, list, link) {
@@ -131,10 +135,23 @@ static void wl_pointer_enter(void *data, struct wl_pointer *wl_pointer,
 		pointer->serial = serial;
 		update_cursor(seat);
 	}
+
+#if HAVE_TRAY
+	if (dbusmenu_pointer_enter(data, wl_pointer, serial, surface, surface_x,
+		surface_y)) {
+		return;
+	}
+#endif
 }
 
 static void wl_pointer_leave(void *data, struct wl_pointer *wl_pointer,
 		uint32_t serial, struct wl_surface *surface) {
+#if HAVE_TRAY
+	if (dbusmenu_pointer_leave(data, wl_pointer, serial, surface)) {
+		return;
+	}
+#endif
+
 	struct swaybar_seat *seat = data;
 	seat->pointer.current = NULL;
 }
@@ -144,6 +161,11 @@ static void wl_pointer_motion(void *data, struct wl_pointer *wl_pointer,
 	struct swaybar_seat *seat = data;
 	seat->pointer.x = wl_fixed_to_double(surface_x);
 	seat->pointer.y = wl_fixed_to_double(surface_y);
+#if HAVE_TRAY
+	if (dbusmenu_pointer_motion(data, wl_pointer, time, surface_x, surface_y)) {
+		return;
+	}
+#endif
 }
 
 static bool check_bindings(struct swaybar *bar, uint32_t button,
@@ -160,6 +182,7 @@ static bool check_bindings(struct swaybar *bar, uint32_t button,
 }
 
 static bool process_hotspots(struct swaybar_output *output,
+		struct swaybar_seat *seat, uint32_t serial,
 		double x, double y, uint32_t button, uint32_t state) {
 	bool released = state == WL_POINTER_BUTTON_STATE_RELEASED;
 	struct swaybar_hotspot *hotspot;
@@ -167,7 +190,7 @@ static bool process_hotspots(struct swaybar_output *output,
 		if (x >= hotspot->x && y >= hotspot->y
 				&& x < hotspot->x + hotspot->width
 				&& y < hotspot->y + hotspot->height) {
-			if (HOTSPOT_IGNORE == hotspot->callback(output, hotspot, x, y,
+			if (HOTSPOT_IGNORE == hotspot->callback(output, hotspot, seat, serial, x, y,
 					button, released, hotspot->data)) {
 				return true;
 			}
@@ -180,13 +203,19 @@ static bool process_hotspots(struct swaybar_output *output,
 static void wl_pointer_button(void *data, struct wl_pointer *wl_pointer,
 		uint32_t serial, uint32_t time, uint32_t button, uint32_t state) {
 	struct swaybar_seat *seat = data;
+#if HAVE_TRAY
+	if (dbusmenu_pointer_button(seat, wl_pointer, serial, time, button,
+				state)) {
+		return;
+	}
+#endif
 	struct swaybar_pointer *pointer = &seat->pointer;
 	struct swaybar_output *output = pointer->current;
 	if (!sway_assert(output, "button with no active output")) {
 		return;
 	}
 
-	if (process_hotspots(output, pointer->x, pointer->y, button, state)) {
+	if (process_hotspots(output, seat, serial, pointer->x, pointer->y, button, state)) {
 		return;
 	}
 
@@ -240,7 +269,7 @@ static void process_discrete_scroll(struct swaybar_seat *seat,
 		struct swaybar_output *output, struct swaybar_pointer *pointer,
 		uint32_t axis, wl_fixed_t value) {
 	uint32_t button = wl_axis_to_button(axis, value);
-	if (process_hotspots(output, pointer->x, pointer->y, button, WL_POINTER_BUTTON_STATE_PRESSED)) {
+	if (process_hotspots(output, seat, 0, pointer->x, pointer->y, button, WL_POINTER_BUTTON_STATE_PRESSED)) {
 		// (Currently hotspots don't do anything on release events, so no need to emit one)
 		return;
 	}
@@ -297,6 +326,12 @@ static void wl_pointer_axis(void *data, struct wl_pointer *wl_pointer,
 		return;
 	}
 
+#if HAVE_TRAY
+	if (dbusmenu_pointer_axis(data, wl_pointer)) {
+		return;
+	}
+#endif
+
 	// If there's a while since the last scroll event,
 	// set 'value' to zero as if to reset the "virtual scroll wheel"
 	if (seat->axis[axis].discrete_steps == 0 &&
@@ -313,6 +348,12 @@ static void wl_pointer_frame(void *data, struct wl_pointer *wl_pointer) {
 	struct swaybar_pointer *pointer = &seat->pointer;
 	struct swaybar_output *output = pointer->current;
 
+#if HAVE_TRAY
+	if (dbusmenu_pointer_frame(data, wl_pointer)) {
+		return;
+	}
+#endif
+
 	if (output == NULL) {
 		return;
 	}
@@ -420,7 +461,7 @@ static void wl_touch_up(void *data, struct wl_touch *wl_touch,
 	}
 	if (time - slot->time < 500) {
 		// Tap, treat it like a pointer click
-		process_hotspots(slot->output, slot->x, slot->y, BTN_LEFT, WL_POINTER_BUTTON_STATE_PRESSED);
+		process_hotspots(slot->output, seat, serial, slot->x, slot->y, BTN_LEFT, WL_POINTER_BUTTON_STATE_PRESSED);
 		// (Currently hotspots don't do anything on release events, so no need to emit one)
 	}
 	slot->output = NULL;
diff --git a/swaybar/meson.build b/swaybar/meson.build
index 34bbdeea99..baeb74c460 100644
--- a/swaybar/meson.build
+++ b/swaybar/meson.build
@@ -3,7 +3,8 @@ tray_files = have_tray ? [
 	'tray/icon.c',
 	'tray/item.c',
 	'tray/tray.c',
-	'tray/watcher.c'
+	'tray/watcher.c',
+	'tray/dbusmenu.c'
 ] : []
 
 swaybar_deps = [
diff --git a/swaybar/render.c b/swaybar/render.c
index 879a4e42a3..eea6b35d00 100644
--- a/swaybar/render.c
+++ b/swaybar/render.c
@@ -159,6 +159,7 @@ static void render_sharp_line(cairo_t *cairo, uint32_t color,
 
 static enum hotspot_event_handling block_hotspot_callback(
 		struct swaybar_output *output, struct swaybar_hotspot *hotspot,
+		struct swaybar_seat *seat, uint32_t serial,
 		double x, double y, uint32_t button, bool released, void *data) {
 	struct i3bar_block *block = data;
 	struct status_line *status = output->bar->status;
@@ -598,6 +599,7 @@ static uint32_t render_binding_mode_indicator(struct render_context *ctx,
 
 static enum hotspot_event_handling workspace_hotspot_callback(
 		struct swaybar_output *output, struct swaybar_hotspot *hotspot,
+		struct swaybar_seat *seat, uint32_t serial,
 		double x, double y, uint32_t button, bool released, void *data) {
 	if (button != BTN_LEFT) {
 		return HOTSPOT_PROCESS;
diff --git a/swaybar/tray/dbusmenu.c b/swaybar/tray/dbusmenu.c
new file mode 100644
index 0000000000..423abc425e
--- /dev/null
+++ b/swaybar/tray/dbusmenu.c
@@ -0,0 +1,1367 @@
+#define _POSIX_C_SOURCE 200809L
+#include <linux/input-event-codes.h>
+#include <pool-buffer.h>
+#include <stdlib.h>
+#include <wayland-client-core.h>
+#include <wayland-client-protocol.h>
+#include <wayland-util.h>
+#include <wlr-layer-shell-unstable-v1-client-protocol.h>
+#include <xdg-shell-client-protocol.h>
+#include <xdg-shell-protocol.h>
+
+#include "background-image.h"
+#include "cairo.h"
+#include "cairo_util.h"
+#include "list.h"
+#include "log.h"
+#include "pango.h"
+#include "swaybar/bar.h"
+#include "swaybar/config.h"
+#include "swaybar/input.h"
+#include "swaybar/tray/icon.h"
+#include "swaybar/tray/item.h"
+#include "swaybar/tray/tray.h"
+
+static const char *menu_interface = "com.canonical.dbusmenu";
+
+static void swaybar_dbusmenu_get_layout_root(struct swaybar_dbusmenu *menu);
+static void swaybar_dbusmenu_get_layout(struct swaybar_dbusmenu *menu, int id);
+static void swaybar_dbusmenu_draw(struct swaybar_dbusmenu *dbusmenu, int id);
+
+struct swaybar_dbusmenu_hotspot {
+	int x, y, width, height;
+};
+
+struct swaybar_dbusmenu_surface {
+	struct xdg_popup *xdg_popup;
+	struct xdg_surface *xdg_surface;
+	struct wl_surface *surface;
+	struct pool_buffer *current_buffer;
+	struct pool_buffer buffers[2];
+	int width, height;
+	bool configured;
+};
+
+enum menu_toggle_type {
+	MENU_NONE,
+	MENU_CHECKMARK,
+	MENU_RADIO
+};
+
+struct swaybar_dbusmenu_menu_item {
+	struct swaybar_dbusmenu_hotspot hotspot;
+	// Set if the item has a submenu
+	struct swaybar_dbusmenu_menu *submenu;
+	// The menu in which the item is displayed
+	struct swaybar_dbusmenu_menu *menu;
+	struct swaybar_dbusmenu_menu_item *parent_item;
+	int id;
+	int toggle_state;
+	char *label;
+	char *icon_name;
+	cairo_surface_t *icon_data;
+	enum menu_toggle_type toggle_type;
+	bool enabled;
+	bool visible;
+	bool is_separator;
+};
+
+struct swaybar_dbusmenu_menu {
+	struct swaybar_dbusmenu *dbusmenu;
+	struct swaybar_dbusmenu_surface *surface;
+	struct swaybar_dbusmenu_menu_item *last_hovered_item;
+	list_t *items; // struct swaybar_dbusmenu_menu_item
+	int item_id;
+	int x, y;
+};
+
+struct swaybar_dbusmenu {
+	struct swaybar_sni *sni;
+	struct swaybar_output *output;
+	struct swaybar_seat *seat;
+	struct swaybar_dbusmenu_menu *menu;
+	struct swaybar *bar;
+	int serial;
+	int x, y;
+};
+
+struct get_layout_callback_data {
+	struct swaybar_dbusmenu *menu;
+	int id;
+};
+
+struct png_stream {
+	const void *data;
+	size_t left;
+};
+
+static void commit_menu_surface(struct swaybar_dbusmenu_menu *menu)
+{
+	struct swaybar_dbusmenu_surface * dbusmenu_surface = menu->surface;
+	if (!dbusmenu_surface->configured || dbusmenu_surface->current_buffer == NULL) {
+		return;
+	}
+
+	struct wl_surface *surface = dbusmenu_surface->surface;
+	wl_surface_set_buffer_scale(surface, menu->dbusmenu->output->scale);
+	wl_surface_attach(surface, dbusmenu_surface->current_buffer->buffer, 0, 0);
+	wl_surface_damage(surface, 0, 0, dbusmenu_surface->width, dbusmenu_surface->height);
+	wl_surface_commit(surface);
+}
+
+static int handle_items_properties_updated(sd_bus_message *msg, void *data,
+		sd_bus_error *error) {
+	struct swaybar_sni *sni = data;
+	sway_log(SWAY_DEBUG, "%s%s item properties updated", sni->service, sni->menu);
+
+	// TODO: Optimize. Update only needed properties
+	if (sni->tray->menu) {
+		swaybar_dbusmenu_get_layout_root(sni->tray->menu);
+	}
+	return 0;
+}
+
+static int handle_layout_updated(sd_bus_message *msg, void *data,
+		sd_bus_error *error) {
+	struct swaybar_sni *sni = data;
+	sway_log(SWAY_DEBUG, "%s%s layout updated", sni->service, sni->menu);
+
+	int id;
+	sd_bus_message_read(msg, "ui", NULL, &id);
+	if (sni->tray->menu) {
+		swaybar_dbusmenu_get_layout(sni->tray->menu, id);
+	}
+	return 0;
+}
+
+static int handle_item_activation_requested(sd_bus_message *msg, void *data,
+		sd_bus_error *error) {
+	return 0; // TODO: Implement handling of hotkeys for opening the menu
+}
+
+static struct swaybar_dbusmenu_surface *swaybar_dbusmenu_surface_create() {
+	struct swaybar_dbusmenu_surface *dbusmenu = calloc(1, 
+			sizeof(struct swaybar_dbusmenu_surface));
+	if (!dbusmenu) {
+		sway_log(SWAY_DEBUG, "Could not allocate dbusmenu");
+	}
+	return dbusmenu;
+}
+
+static void xdg_surface_handle_configure(void *data,
+		struct xdg_surface *xdg_surface, uint32_t serial) {
+	xdg_surface_ack_configure(xdg_surface, serial);
+
+	struct swaybar_dbusmenu_menu *menu = data;
+	menu->surface->configured = true;
+	commit_menu_surface(menu);
+}
+
+static const struct xdg_surface_listener xdg_surface_listener = {
+	.configure = xdg_surface_handle_configure,
+};
+
+static void xdg_popup_configure(void *data, struct xdg_popup *xdg_popup,
+		int32_t x, int32_t y, int32_t width, int32_t height) {
+	// intentionally left blank
+}
+
+static void destroy_dbusmenu_surface(
+		struct swaybar_dbusmenu_surface *dbusmenu_surface) {
+	if (!dbusmenu_surface) {
+		return;
+	}
+
+	if (dbusmenu_surface->xdg_popup) {
+		xdg_popup_destroy(dbusmenu_surface->xdg_popup);
+		dbusmenu_surface->xdg_popup = NULL;
+	}
+	if (dbusmenu_surface->surface) {
+		xdg_surface_destroy(dbusmenu_surface->xdg_surface);
+		wl_surface_destroy(dbusmenu_surface->surface);
+		dbusmenu_surface->surface = NULL;
+	}
+	destroy_buffer(&dbusmenu_surface->buffers[0]);
+	destroy_buffer(&dbusmenu_surface->buffers[1]);
+
+	free(dbusmenu_surface);
+}
+
+static void close_menu(struct swaybar_dbusmenu_menu *menu) {
+	if (!menu) {
+		return;
+	}
+
+	if (menu->surface) {
+		destroy_dbusmenu_surface(menu->surface);
+		menu->surface = NULL;
+
+		int id = menu->item_id;
+		struct swaybar_sni *sni = menu->dbusmenu->sni;
+		sd_bus_call_method_async(sni->tray->bus, NULL, sni->service, sni->menu,
+				menu_interface, "Event", NULL, NULL, "isvu", id, "closed", "y",
+				0, time(NULL));
+		sway_log(SWAY_DEBUG, "%s%s closed id %d", sni->service, sni->menu, id);
+	}
+}
+
+static void close_menus(struct swaybar_dbusmenu_menu *menu) {
+	if (!menu) {
+		return;
+	}
+
+	if (menu->items) {
+		for (int i = 0; i < menu->items->length; ++i) {
+			struct swaybar_dbusmenu_menu_item *item = menu->items->items[i];
+			if (item->submenu && item->submenu->item_id != 0) {
+				close_menus(item->submenu);
+			}
+		}
+	}
+
+	close_menu(menu);
+}
+
+static void swaybar_dbusmenu_menu_destroy(struct swaybar_dbusmenu_menu *menu) {
+	if (!menu) {
+		return;
+	}
+
+	if (menu->items) {
+		for (int i = 0; i < menu->items->length; ++i) {
+			struct swaybar_dbusmenu_menu_item *item = menu->items->items[i];
+			struct swaybar_dbusmenu_menu *child_menu = item->submenu;
+			if (child_menu && child_menu->item_id != 0) {
+				swaybar_dbusmenu_menu_destroy(item->submenu);
+			}
+			free(item->label);
+			free(item->icon_name);
+			free(item->icon_data);
+			free(item);
+		}
+	}
+	list_free(menu->items);
+	free(menu);
+}
+
+void swaybar_dbusmenu_destroy(struct swaybar_dbusmenu *menu) {
+	if (!menu) {
+		return;
+	}
+
+	menu->sni->tray->menu = NULL;
+	menu->sni->tray->menu_pointer_focus = NULL;
+
+	close_menus(menu->menu);
+	swaybar_dbusmenu_menu_destroy(menu->menu);
+	free(menu);
+}
+
+static void xdg_popup_done(void *data, struct xdg_popup *xdg_popup) {
+	struct swaybar_dbusmenu_menu *menu = data;
+	swaybar_dbusmenu_destroy(menu->dbusmenu);
+}
+
+static const struct xdg_popup_listener xdg_popup_listener = {
+	.configure = xdg_popup_configure, .popup_done = xdg_popup_done};
+
+static struct swaybar_dbusmenu_menu_item *
+find_item_under_menu(struct swaybar_dbusmenu_menu *menu, int item_id) {
+	if (!menu->items) {
+		return NULL;
+	}
+
+	for (int i = 0; i < menu->items->length; ++i) {
+		struct swaybar_dbusmenu_menu_item *item = menu->items->items[i];
+		if (item->id == item_id) {
+			return item;
+		}
+		if (item->submenu && item->submenu->item_id != 0) {
+			struct swaybar_dbusmenu_menu_item *found_item = 
+				find_item_under_menu(item->submenu, item_id);
+			if (found_item) {
+				return found_item;
+			}
+		}
+	}
+
+	return NULL;
+}
+
+static struct swaybar_dbusmenu_menu_item *
+find_item(struct swaybar_dbusmenu *dbusmenu, int item_id) {
+	if (!dbusmenu->menu) {
+		return NULL;
+	}
+
+	return find_item_under_menu(dbusmenu->menu, item_id);
+}
+
+static struct swaybar_dbusmenu_menu *
+find_parent_menu_under_menu(struct swaybar_dbusmenu_menu *menu, 
+		struct swaybar_dbusmenu_menu *child_menu) {
+	if (!menu->items) {
+		return NULL;
+	}
+
+	for (int i = 0; i < menu->items->length; ++i) {
+		struct swaybar_dbusmenu_menu_item *item = menu->items->items[i];
+		struct swaybar_dbusmenu_menu *maybe_child_menu = item->submenu;
+		if (maybe_child_menu && maybe_child_menu->item_id != 0) {
+			if (maybe_child_menu == child_menu) {
+				return menu;
+			}
+			maybe_child_menu = find_parent_menu_under_menu(maybe_child_menu, child_menu);
+			if (maybe_child_menu) {
+				return maybe_child_menu;
+			}
+		}
+	}
+
+	return NULL;
+}
+
+static struct swaybar_dbusmenu_menu *
+find_parent_menu(struct swaybar_dbusmenu_menu *menu) {
+	if (menu && menu->item_id == 0) {
+		return NULL;
+	}
+	struct swaybar_dbusmenu_menu *root_menu = menu->dbusmenu->menu;
+	return find_parent_menu_under_menu(root_menu, menu);
+}
+
+static bool is_in_hotspot(struct swaybar_dbusmenu_hotspot *hotspot, int x, int y) {
+	if (!hotspot) {
+		return false;
+	}
+
+	if (hotspot->x <= x && x < hotspot->x + hotspot->width && hotspot->y <= y &&
+			y < hotspot->y + hotspot->height) {
+		return true;
+	}
+
+	return false;
+}
+
+static void draw_menu_items(cairo_t *cairo, struct swaybar_dbusmenu_menu *menu,
+		int *surface_x, int *surface_y, int *surface_width, int *surface_height,
+		bool open) {
+	struct swaybar_sni *sni = menu->dbusmenu->sni;
+	struct swaybar_tray *tray = sni->tray;
+	struct swaybar_output *output = menu->dbusmenu->output;
+	struct swaybar_config *config = menu->dbusmenu->output->bar->config;
+
+	int padding = config->tray_padding * output->scale;
+
+	list_t *items = menu->items;
+	int height = 0;
+
+	*surface_y = 0;
+	*surface_x = 0;
+	*surface_width = 0;
+	bool is_icon_drawn = false;
+	int icon_size = 0;
+
+	for (int i = 0; i < items->length; ++i) {
+		struct swaybar_dbusmenu_menu_item *item = items->items[i];
+
+		if (!item->visible) {
+			continue;
+		}
+
+		int new_height = height;
+		if (item->is_separator) {
+			// drawn later, after the width is known
+			new_height = height + output->scale;
+		} else if (item->label) {
+			cairo_move_to(cairo, padding, height + padding);
+
+			// draw label
+			if (item->enabled) {
+				cairo_set_source_u32(cairo, config->colors.focused_statusline);
+			} else {
+				uint32_t c = config->colors.focused_statusline;
+				uint32_t disabled_color = c - ((c & 0xFF) >> 1);
+				cairo_set_source_u32(cairo, disabled_color);
+			}
+			render_text(cairo, config->font_description, output->scale, false, "%s",
+					item->label);
+
+			// draw icon or menu indicator if needed
+			int text_height;
+			int text_width;
+			get_text_size(cairo, config->font_description, &text_width, &text_height,
+					NULL, output->scale, false, "%s", item->label);
+			text_width += padding;
+			int size = text_height;
+			int x = -2 * padding - size;
+			int y = height + padding;
+			icon_size = 2 * padding + size;
+			cairo_set_source_u32(cairo, config->colors.focused_statusline);
+			if (item->icon_name) {
+				list_t *icon_search_paths = create_list();
+				list_cat(icon_search_paths, tray->basedirs);
+				if (sni->menu_icon_theme_paths) {
+					for (char **path = sni->menu_icon_theme_paths; *path; ++path) {
+						list_add(icon_search_paths, *path);
+					}
+				}
+				if (sni->icon_theme_path) {
+					list_add(icon_search_paths, sni->icon_theme_path);
+				}
+				int min_size, max_size;
+				char *icon_path =
+				find_icon(tray->themes, icon_search_paths, item->icon_name, size,
+						config->icon_theme, &min_size, &max_size);
+				list_free(icon_search_paths);
+
+				if (icon_path) {
+					cairo_surface_t *icon = load_background_image(icon_path);
+					free(icon_path);
+					cairo_surface_t *icon_scaled =
+					cairo_image_surface_scale(icon, size, size);
+					cairo_surface_destroy(icon);
+
+					cairo_set_source_surface(cairo, icon_scaled, x, y);
+					cairo_rectangle(cairo, x, y, size, size);
+					cairo_fill(cairo);
+					cairo_surface_destroy(icon_scaled);
+					is_icon_drawn = true;
+				}
+			} else if (item->icon_data) {
+				cairo_surface_t *icon = cairo_image_surface_scale(item->icon_data, size, size);
+				cairo_set_source_surface(cairo, icon, x, y);
+				cairo_rectangle(cairo, x, y, size, size);
+				cairo_fill(cairo);
+				cairo_surface_destroy(icon);
+				is_icon_drawn = true;
+			} else if (item->toggle_type == MENU_CHECKMARK) {
+				cairo_rectangle(cairo, x, y, size, size);
+				cairo_fill(cairo);
+				cairo_set_operator(cairo, CAIRO_OPERATOR_CLEAR);
+				if (item->toggle_state == 1) { // tick
+					cairo_move_to(cairo, x + size * 3.0 / 4, y + size * 5.0 / 16.0);
+					cairo_line_to(cairo, x + size * 3.0 / 8, y + size * 11.0 / 16.0);
+					cairo_line_to(cairo, x + size / 4.0, y + size * 9.0 / 16.0);
+					cairo_stroke(cairo);
+				} else if (item->toggle_state != 0) { // horizontal line
+					cairo_rectangle(cairo, x + size / 4.0, y + size / 2.0 - 1,
+							size / 2.0, 2);
+					cairo_fill(cairo);
+				}
+				cairo_set_operator(cairo, CAIRO_OPERATOR_OVER);
+				is_icon_drawn = true;
+			} else if (item->toggle_type == MENU_RADIO) {
+				cairo_arc(cairo, x + size / 2.0, y + size / 2.0, size / 2.0, 0, 7);
+				cairo_fill(cairo);
+				if (item->toggle_state == 1) {
+					cairo_set_operator(cairo, CAIRO_OPERATOR_CLEAR);
+					cairo_arc(cairo, x + size / 2.0, y + size / 2.0, size / 4.0, 0, 7);
+					cairo_fill(cairo);
+					cairo_set_operator(cairo, CAIRO_OPERATOR_OVER);
+				}
+				is_icon_drawn = true;
+			} else if (item->submenu) { // arrowhead
+				cairo_move_to(cairo, x + size / 4.0, y + size / 2.0);
+				cairo_line_to(cairo, x + size * 3.0 / 4, y + size / 4.0);
+				cairo_line_to(cairo, x + size * 3.0 / 4, y + size * 3.0 / 4);
+				cairo_fill(cairo);
+				is_icon_drawn = true;
+			}
+
+			*surface_width = *surface_width < text_width ? text_width : *surface_width;
+			new_height = height + text_height + 2 * padding;
+		} else {
+			continue;
+		}
+
+		struct swaybar_dbusmenu_hotspot *hotspot = &item->hotspot;
+		hotspot->y = height;
+
+		hotspot->y = height;
+		hotspot->height = new_height - height;
+		// x and width is not known at the moment
+
+		height = new_height;
+	}
+
+	if (height == 0) {
+		return;
+	}
+
+	if (is_icon_drawn) {
+		*surface_x = -icon_size - padding;
+		*surface_width += icon_size + padding;
+	}
+
+	*surface_width += padding;
+	*surface_height = height;
+
+	// Make sure height and width are divideable by scale
+	// otherwise the menu will not showup
+	if (*surface_width % output->scale != 0) {
+		*surface_width -= *surface_width % output->scale;
+	}
+	if (*surface_height % output->scale != 0) {
+		*surface_height -= *surface_height % output->scale;
+	}
+
+	cairo_set_line_width(cairo, output->scale);
+	cairo_set_source_u32(cairo, config->colors.focused_separator);
+	for (int i = 0; i < items->length; ++i) {
+		struct swaybar_dbusmenu_menu_item *item = items->items[i];
+		struct swaybar_dbusmenu_hotspot *hotspot = &item->hotspot;
+		hotspot->x = 0;
+		hotspot->width = *surface_width;
+		if (item->is_separator) {
+			int y = hotspot->y + hotspot->height / 2.0;
+			cairo_move_to(cairo, *surface_x, y);
+			cairo_line_to(cairo, *surface_x + *surface_width, y);
+			cairo_stroke(cairo);
+		} else if (!open && item->enabled &&
+					is_in_hotspot(hotspot,
+						tray->menu->seat->pointer.x * output->scale,
+						tray->menu->seat->pointer.y * output->scale)) {
+			cairo_save(cairo);
+			cairo_set_operator(cairo, CAIRO_OPERATOR_DEST_OVER);
+			cairo_rectangle(cairo, *surface_x, hotspot->y, *surface_width,
+					hotspot->height);
+			cairo_set_source_u32(cairo,
+					sni->tray->bar->config->colors.focused_separator);
+			cairo_fill(cairo);
+			cairo_restore(cairo);
+		}
+	}
+}
+
+struct swaybar_dbusmenu_menu *find_menu_id(struct swaybar_dbusmenu_menu *menu,
+		int id) {
+	if (!menu) {
+		return NULL;
+	}
+	if (menu->item_id == id) {
+		return menu;
+	}
+
+	if (menu->items) {
+		for (int i = 0; i < menu->items->length; ++i) {
+			struct swaybar_dbusmenu_menu_item *item = menu->items->items[i];
+			struct swaybar_dbusmenu_menu *child_menu = item->submenu;
+			if (child_menu) {
+				if (child_menu->item_id == id) {
+					return child_menu;
+				}
+				if (child_menu->item_id == 0) {
+					continue;
+				}
+				struct swaybar_dbusmenu_menu *child_child_menu = find_menu_id(child_menu, id);
+				if (child_child_menu) {
+					return child_child_menu;
+				}
+			}
+		}
+	}
+
+	return NULL;
+}
+
+static void swaybar_dbusmenu_draw_menu(struct swaybar_dbusmenu_menu *menu,
+		int id, bool open) {
+	// For now just search for menu with id
+	struct swaybar_tray *tray = menu->dbusmenu->sni->tray;
+	menu = find_menu_id(menu->dbusmenu->menu, id);
+	if (!menu) {
+		return;
+	}
+
+	if (!menu->surface) {
+		menu->surface = swaybar_dbusmenu_surface_create();
+		if (!menu->surface) {
+			sway_log(SWAY_ERROR, "Could not create surface for menu %d", menu->item_id);
+			return;
+		}
+	}
+
+	cairo_surface_t *recorder = 
+		cairo_recording_surface_create(CAIRO_CONTENT_COLOR_ALPHA, NULL);
+	if (!recorder) {
+		return;
+	}
+	cairo_t *cairo = cairo_create(recorder);
+	if (!cairo) {
+		cairo_surface_destroy(recorder);
+		return;
+	}
+	int surface_x, surface_y, surface_width, surface_height = 0;
+	draw_menu_items(cairo, menu, &surface_x, &surface_y, &surface_width,
+					&surface_height, open);
+
+	struct swaybar *bar = menu->dbusmenu->sni->tray->bar;
+	struct swaybar_dbusmenu_surface *dbusmenu_surface = menu->surface;
+	dbusmenu_surface->current_buffer = get_next_buffer(
+		bar->shm, dbusmenu_surface->buffers, surface_width, surface_height);
+
+	if (!dbusmenu_surface->current_buffer) {
+		cairo_surface_destroy(recorder);
+		cairo_destroy(cairo);
+		return;
+	}
+
+	cairo_t *shm = dbusmenu_surface->current_buffer->cairo;
+	cairo_set_operator(shm, CAIRO_OPERATOR_SOURCE);
+	cairo_set_source_u32(
+		shm, menu->dbusmenu->sni->tray->bar->config->colors.focused_background);
+	cairo_paint(shm);
+
+	cairo_set_operator(shm, CAIRO_OPERATOR_OVER);
+	cairo_set_source_surface(shm, recorder, -surface_x, -surface_y);
+	cairo_paint(shm);
+
+	cairo_surface_destroy(recorder);
+	cairo_destroy(cairo);
+
+	if (dbusmenu_surface->width != surface_width ||
+			dbusmenu_surface->height != surface_height) {
+		if (dbusmenu_surface->surface) {
+			xdg_surface_destroy(dbusmenu_surface->xdg_surface);
+			dbusmenu_surface->xdg_surface = NULL;
+			wl_surface_destroy(dbusmenu_surface->surface);
+			dbusmenu_surface->surface = NULL;
+			sway_log(SWAY_DEBUG, "Destroy xdg popup");
+			xdg_popup_destroy(dbusmenu_surface->xdg_popup);
+			dbusmenu_surface->xdg_popup = NULL;
+		}
+
+		// configure & position popup surface
+		struct wl_surface *surface = wl_compositor_create_surface(bar->compositor);
+		struct xdg_surface *xdg_surface =
+			xdg_wm_base_get_xdg_surface(menu->dbusmenu->bar->wm_base, surface);
+		struct xdg_positioner *positioner =
+			xdg_wm_base_create_positioner(menu->dbusmenu->bar->wm_base);
+
+		// find the menu item (if any) which requested to open this submenu
+		// to find out on which x and y coordinate the submenu should be drawn
+		struct swaybar_dbusmenu_menu_item *item =
+			find_item(menu->dbusmenu, menu->item_id);
+		struct swaybar_output *output = menu->dbusmenu->output;
+		int x = menu->item_id == 0 ? menu->dbusmenu->x
+				: item->hotspot.x / output->scale;
+		int y = menu->item_id == 0 ? menu->dbusmenu->y
+				: item->hotspot.y / output->scale;
+
+		xdg_positioner_set_offset(positioner, 0, 0);
+		// Need to divide through scale because surface width/height is scaled
+		xdg_positioner_set_size(positioner, surface_width / output->scale,
+								surface_height / output->scale);
+
+		int padding = (tray->bar->config->tray_padding * output->scale) / 2;
+		if (bar->config->position & ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP) { // top bar
+			xdg_positioner_set_anchor(positioner, XDG_POSITIONER_ANCHOR_BOTTOM_LEFT);
+			xdg_positioner_set_gravity(positioner, XDG_POSITIONER_GRAVITY_BOTTOM_LEFT);
+			xdg_positioner_set_anchor_rect(positioner, x, y - padding, 1, 1);
+		} else {
+			xdg_positioner_set_anchor(positioner, XDG_POSITIONER_ANCHOR_TOP_LEFT);
+			xdg_positioner_set_gravity(positioner, XDG_POSITIONER_GRAVITY_TOP_LEFT);
+			xdg_positioner_set_anchor_rect(
+			positioner, x, item->hotspot.height / output->scale, 1, 1);
+		}
+
+		struct xdg_popup *xdg_popup;
+		struct swaybar_dbusmenu_menu *parent_menu = find_parent_menu(menu);
+		if (!parent_menu) {
+			// Top level menu
+			xdg_popup = xdg_surface_get_popup(xdg_surface, NULL, positioner);
+			zwlr_layer_surface_v1_get_popup(output->layer_surface, xdg_popup);
+		} else {
+			// Nested menu
+			xdg_popup = xdg_surface_get_popup(
+					xdg_surface, parent_menu->surface->xdg_surface, positioner);
+		}
+		xdg_positioner_destroy(positioner);
+
+		xdg_popup_grab(xdg_popup, menu->dbusmenu->seat->wl_seat,
+				menu->dbusmenu->serial);
+		xdg_popup_add_listener(xdg_popup, &xdg_popup_listener, menu);
+		xdg_surface_add_listener(xdg_surface, &xdg_surface_listener, menu);
+		wl_surface_commit(surface);
+
+		dbusmenu_surface->xdg_popup = xdg_popup;
+		dbusmenu_surface->xdg_surface = xdg_surface;
+		dbusmenu_surface->surface = surface;
+		dbusmenu_surface->width = surface_width;
+		dbusmenu_surface->height = surface_height;
+		dbusmenu_surface->configured = false;
+	}
+
+	commit_menu_surface(menu);
+}
+
+static void swaybar_dbusmenu_draw(struct swaybar_dbusmenu *dbusmenu, int id) {
+	if (!dbusmenu || !dbusmenu->menu) {
+		sway_log(SWAY_ERROR, "Can not draw dbusmenu, menu structure not initialized yet!");
+		return;
+	}
+	swaybar_dbusmenu_draw_menu(dbusmenu->menu, id, true);
+}
+
+static cairo_status_t read_png_stream(void *closure, unsigned char *data,
+		unsigned int length) {
+	struct png_stream *png_stream = closure;
+	if (length > png_stream->left) {
+		return CAIRO_STATUS_READ_ERROR;
+	}
+	memcpy(data, png_stream->data, length);
+	png_stream->data += length;
+	png_stream->left -= length;
+	return CAIRO_STATUS_SUCCESS;
+}
+
+static cairo_surface_t *read_png(const void *data, size_t data_size) {
+	struct png_stream png_stream = {0};
+	png_stream.data = data;
+	png_stream.left = data_size;
+	cairo_surface_t *surface =
+		cairo_image_surface_create_from_png_stream(read_png_stream, &png_stream);
+
+	if (cairo_surface_status(surface) == CAIRO_STATUS_SUCCESS) {
+		return surface;
+	}
+
+	cairo_surface_destroy(surface);
+	return NULL;
+}
+
+static int about_to_show_callback(sd_bus_message *msg, void *data,
+		sd_bus_error *error) {
+	struct swaybar_sni_slot *slot = data;
+	struct swaybar_sni *sni = slot->sni;
+	int menu_id = slot->menu_id;
+	wl_list_remove(&slot->link);
+	free(slot);
+
+	int need_update;
+	sd_bus_message_read_basic(msg, 'b', &need_update);
+	if (need_update) {
+		swaybar_dbusmenu_get_layout(sni->tray->menu, menu_id);
+	}
+
+	swaybar_dbusmenu_draw(sni->tray->menu, menu_id);
+
+	sd_bus_call_method_async(sni->tray->bus, NULL, sni->service, sni->menu,
+		menu_interface, "Event", NULL, NULL, "isvu", menu_id, "opened", "y", 0, 
+		time(NULL));
+
+	sway_log(SWAY_DEBUG, "%s%s opened id %d", sni->service, sni->menu, menu_id);
+
+	return 0;
+}
+
+static void open_menu_id(struct swaybar_dbusmenu *dbusmenu, int menu_id) {
+	struct swaybar_dbusmenu_menu *menu = find_menu_id(dbusmenu->menu, menu_id);
+	if (!menu || menu->surface) {
+		// menu could not be found or is already shown
+		return;
+	}
+
+	struct swaybar_sni *sni = dbusmenu->sni;
+	struct swaybar_sni_slot *slot = calloc(1, sizeof(struct swaybar_sni_slot));
+	slot->sni = sni;
+	slot->menu_id = menu_id;
+
+	int ret = sd_bus_call_method_async(sni->tray->bus, &slot->slot, sni->service,
+		sni->menu, menu_interface, "AboutToShow", about_to_show_callback, slot, "i", 
+		menu_id);
+
+	if (ret >= 0) {
+		wl_list_insert(&sni->slots, &slot->link);
+	} else {
+		sway_log(SWAY_ERROR, "%s%s failed to send AboutToShow signal: %s",
+			sni->service, sni->menu, strerror(-ret));
+		free(slot);
+	}
+}
+
+static int update_item_properties(struct swaybar_dbusmenu_menu_item *item,
+		sd_bus_message *msg) {
+	sd_bus_message_enter_container(msg, 'a', "{sv}");
+	while (!sd_bus_message_at_end(msg, 0)) {
+		sd_bus_message_enter_container(msg, 'e', "sv");
+		char *key, *log_value;
+		sd_bus_message_read_basic(msg, 's', &key);
+		if (strcmp(key, "type") == 0) {
+			char *type;
+			sd_bus_message_read(msg, "v", "s", &type);
+			item->is_separator = strcmp(type, "separator") == 0;
+			log_value = type;
+		} else if (strcmp(key, "label") == 0) {
+			char *label;
+			sd_bus_message_read(msg, "v", "s", &label);
+			item->label = realloc(item->label, strlen(label) + 1);
+			if (!item->label) {
+				return -ENOMEM;
+			}
+			int i = 0;
+			for (char *c = label; *c; ++c) {
+				if (*c == '_' && !*++c) {
+					break;
+				}
+				item->label[i++] = *c;
+			}
+			item->label[i] = '\0';
+			log_value = label;
+		} else if (strcmp(key, "enabled") == 0) {
+			int enabled;
+			sd_bus_message_read(msg, "v", "b", &enabled);
+			item->enabled = enabled;
+			log_value = item->enabled ? "true" : "false";
+		} else if (strcmp(key, "visible") == 0) {
+			int visible;
+			sd_bus_message_read(msg, "v", "b", &visible);
+			item->visible = visible;
+			log_value = item->visible ? "true" : "false";
+		} else if (strcmp(key, "icon-name") == 0) {
+			sd_bus_message_read(msg, "v", "s", &item->icon_name);
+			item->icon_name = strdup(item->icon_name);
+			log_value = item->icon_name;
+		} else if (strcmp(key, "icon-data") == 0) {
+			const void *data;
+			size_t data_size;
+			sd_bus_message_enter_container(msg, 'v', "ay");
+			sd_bus_message_read_array(msg, 'y', &data, &data_size);
+			sd_bus_message_exit_container(msg);
+			item->icon_data = read_png(data, data_size);
+			log_value = item->icon_data ? "<success>" : "<failure>";
+		} else if (strcmp(key, "toggle-type") == 0) {
+			char *toggle_type;
+			sd_bus_message_read(msg, "v", "s", &toggle_type);
+			if (strcmp(toggle_type, "checkmark") == 0) {
+				item->toggle_type = MENU_CHECKMARK;
+			} else if (strcmp(toggle_type, "radio") == 0) {
+				item->toggle_type = MENU_RADIO;
+			}
+			log_value = toggle_type;
+		} else if (strcmp(key, "toggle-state") == 0) {
+			sd_bus_message_read(msg, "v", "i", &item->toggle_state);
+			log_value = item->toggle_state == 0 ? 
+				"off" : item->toggle_state == 1 ? "on" : "indeterminate";
+		} else if (strcmp(key, "children-display") == 0) {
+			char *children_display;
+			sd_bus_message_read(msg, "v", "s", &children_display);
+			if (strcmp(children_display, "submenu") == 0) {
+				struct swaybar_dbusmenu_menu *submenu;
+				if (item->id != 0) {
+					submenu = calloc(1, sizeof(struct swaybar_dbusmenu_menu));
+					if (!submenu) {
+						sway_log(SWAY_ERROR, "Could not allocate submenu");
+						return -ENOMEM;
+					}
+				} else {
+					submenu = item->menu;
+				}
+				submenu->item_id = item->id;
+				submenu->dbusmenu = item->menu->dbusmenu;
+				item->submenu = submenu;
+			}
+			log_value = children_display;
+		} else {
+			// Ignored: shortcut, disposition
+			sd_bus_message_skip(msg, "v");
+			log_value = "<ignored>";
+		}
+		sd_bus_message_exit_container(msg);
+		sway_log(SWAY_DEBUG, "%s%s %s = '%s'", item->menu->dbusmenu->sni->service,
+		item->menu->dbusmenu->sni->menu, key, log_value);
+	}
+	return sd_bus_message_exit_container(msg);
+}
+
+static int get_layout_callback(sd_bus_message *msg, void *data, 
+		sd_bus_error *error) {
+	struct swaybar_sni_slot *slot = data;
+	struct swaybar_sni *sni = slot->sni;
+	int menu_id = slot->menu_id;
+	wl_list_remove(&slot->link);
+	free(slot);
+
+	struct swaybar_dbusmenu *dbusmenu = sni->tray->menu;
+	if (dbusmenu == NULL) {
+		return 0;
+	}
+
+	if (sd_bus_message_is_method_error(msg, NULL)) {
+		sway_log(SWAY_ERROR, "%s%s failed to get layout: %s",
+			dbusmenu->sni->service, dbusmenu->sni->menu,
+		sd_bus_message_get_error(msg)->message);
+		return sd_bus_message_get_errno(msg);
+	}
+
+	// Parse the layout. The layout comes as a recursive structure as
+	// dbus message in the following form (ia{sv}av)
+
+	// Skip the menu revision
+	sd_bus_message_skip(msg, "u");
+
+	sni->tray->menu_pointer_focus = NULL;
+
+	bool already_open = false;
+	struct swaybar_dbusmenu_menu *menu_to_update =
+	find_menu_id(dbusmenu->menu, menu_id);
+	if (menu_to_update && menu_to_update->surface) {
+		already_open = true;
+	}
+
+	if (dbusmenu->menu) {
+		close_menus(dbusmenu->menu);
+		swaybar_dbusmenu_menu_destroy(dbusmenu->menu);
+		dbusmenu->menu = NULL;
+	}
+
+	struct swaybar_dbusmenu_menu_item *parent_item = NULL;
+	struct swaybar_dbusmenu_menu *menu = calloc(1,
+			sizeof(struct swaybar_dbusmenu_menu));
+	if (!menu) {
+		sway_log(SWAY_ERROR, "Could not allocate menu");
+		return -ENOMEM;
+	}
+	dbusmenu->menu = menu;
+	menu->dbusmenu = dbusmenu;
+	int ret = 0;
+	while (!sd_bus_message_at_end(msg, 1)) {
+		sd_bus_message_enter_container(msg, 'r', "ia{sv}av");
+
+		struct swaybar_dbusmenu_menu_item *item 
+			= calloc(1, sizeof(struct swaybar_dbusmenu_menu_item));
+		if (!item) {
+			ret = -ENOMEM;
+			break;
+		}
+
+		// default properties
+		item->parent_item = parent_item;
+		item->menu = menu;
+		item->enabled = true;
+		item->visible = true;
+		item->toggle_state = -1;
+
+		// Read the id
+		sd_bus_message_read_basic(msg, 'i', &item->id);
+
+		// Process a{sv}. a{sv} contains key-value pairs
+		ret = update_item_properties(item, msg);
+		if (!menu->items) {
+			menu->items = create_list();
+		}
+		list_add(menu->items, item);
+		if (ret < 0) {
+			break;
+		}
+		if (item->id != 0 && item->submenu) {
+			menu = item->submenu;
+		}
+
+		sd_bus_message_enter_container(msg, 'a', "v");
+
+		parent_item = item;
+		while (parent_item && sd_bus_message_at_end(msg, 0)) {
+			if (parent_item->submenu) {
+				menu = find_parent_menu(menu);
+			}
+			parent_item = parent_item->parent_item;
+
+			sd_bus_message_exit_container(msg);
+			sd_bus_message_exit_container(msg);
+			sd_bus_message_exit_container(msg);
+		}
+
+		if (parent_item) {
+			sd_bus_message_enter_container(msg, 'v', "(ia{sv}av)");
+		}
+	}
+
+	if (already_open) {
+		swaybar_dbusmenu_draw(sni->tray->menu, menu_id);
+	} else {
+		open_menu_id(dbusmenu, 0);
+	}
+
+	return 0;
+}
+
+static void swaybar_dbusmenu_subscribe_signal(struct swaybar_dbusmenu *menu,
+		const char *signal_name, sd_bus_message_handler_t callback) {
+	int ret = sd_bus_match_signal_async( menu->sni->tray->bus, NULL, 
+			menu->sni->service, menu->sni->menu, menu_interface, signal_name, callback,
+			NULL, menu->sni);
+
+	if (ret < 0) {
+		sway_log(SWAY_ERROR, "%s%s failed to subscribe to signal %s: %s",
+			menu->sni->service, menu->sni->menu, signal_name, strerror(-ret));
+	}
+}
+
+static void swaybar_dbusmenu_setup_signals(struct swaybar_dbusmenu *menu) {
+	swaybar_dbusmenu_subscribe_signal(menu, "ItemsPropertiesUpdated",
+		handle_items_properties_updated);
+	swaybar_dbusmenu_subscribe_signal(menu, "LayoutUpdated",
+		handle_layout_updated);
+	swaybar_dbusmenu_subscribe_signal(menu, "ItemActivationRequested",
+		handle_item_activation_requested);
+}
+
+static void swaybar_dbusmenu_get_layout(struct swaybar_dbusmenu *menu, int id) {
+	if (menu == NULL) {
+		return;
+	}
+
+	struct swaybar_sni_slot *slot = calloc(1, sizeof(struct swaybar_sni_slot));
+	if (slot == NULL) {
+		sway_log(SWAY_ERROR, "Could not allocate swaybar_sni_slot");
+		return;
+	}
+	slot->sni = menu->sni;
+	slot->menu_id = id;
+
+	int ret =
+		sd_bus_call_method_async(menu->sni->tray->bus, NULL, menu->sni->service,
+				menu->sni->menu, menu_interface, "GetLayout",
+				get_layout_callback, slot, "iias", id, -1, NULL);
+
+	if (ret >= 0) {
+		wl_list_insert(&menu->sni->slots, &slot->link);
+	} else {
+		sway_log(SWAY_ERROR, "%s%s failed to call method GetLayout: %s",
+				menu->sni->service, menu->sni->menu, strerror(-ret));
+		free(slot);
+	}
+}
+
+static void swaybar_dbusmenu_get_layout_root(struct swaybar_dbusmenu *menu) {
+	swaybar_dbusmenu_get_layout(menu, 0);
+}
+
+static int get_icon_theme_path_callback(sd_bus_message *msg, void *data,
+		sd_bus_error *error) {
+	struct swaybar_sni_slot *slot = data;
+	struct swaybar_sni *sni = slot->sni;
+	wl_list_remove(&slot->link);
+	free(slot);
+
+	int ret;
+	if (!sd_bus_message_is_method_error(msg, NULL)) {
+		ret = sd_bus_message_enter_container(msg, 'v', NULL);
+		if (ret >= 0) {
+			ret = sd_bus_message_read_strv(msg, &sni->menu_icon_theme_paths);
+		}
+	} else {
+		ret = -sd_bus_message_get_errno(msg);
+	}
+
+	if (ret < 0) {
+		sway_log(SWAY_ERROR, "%s%s failed to read IconThemePath: %s", sni->service,
+			sni->menu, strerror(-ret));
+	}
+	return ret;
+}
+
+static void swaybar_dbusmenu_setup(struct swaybar_dbusmenu *menu) {
+		struct swaybar_sni_slot *slot = calloc(1, sizeof(struct swaybar_sni_slot));
+	slot->sni = menu->sni;
+	int ret = sd_bus_call_method_async( menu->sni->tray->bus, &slot->slot, 
+			menu->sni->service, menu->sni->path, "org.freedesktop.DBus.Properties",
+			"Get", get_icon_theme_path_callback, slot, "ss", menu->sni->interface,
+			"IconThemePath");
+	if (ret >= 0) {
+		wl_list_insert(&menu->sni->slots, &slot->link);
+	} else {
+		sway_log(SWAY_ERROR, "%s%s failed to get IconThemePath: %s",
+			menu->sni->service, menu->sni->menu, strerror(-ret));
+		free(slot);
+	}
+
+	swaybar_dbusmenu_setup_signals(menu);
+	swaybar_dbusmenu_get_layout_root(menu);
+}
+
+void swaybar_dbusmenu_open(struct swaybar_sni *sni,
+		struct swaybar_output *output, struct swaybar_seat *seat, uint32_t serial, 
+		int x, int y) {
+	struct swaybar_dbusmenu *dbusmenu = sni->tray->menu;
+	if (!dbusmenu) {
+		dbusmenu = calloc(1, sizeof(struct swaybar_dbusmenu));
+		if (!dbusmenu) {
+			sway_log(SWAY_DEBUG, "Could not allocate dbusmenu");
+			return;
+		}
+		sni->tray->menu = dbusmenu;
+	}
+
+	dbusmenu->sni = sni;
+	dbusmenu->output = output;
+	dbusmenu->seat = seat;
+	dbusmenu->serial = serial;
+	dbusmenu->x = seat->pointer.x;
+	dbusmenu->y = seat->pointer.y;
+	dbusmenu->bar = output->bar;
+
+	swaybar_dbusmenu_setup(dbusmenu);
+}
+
+static void close_child_menus_except(struct swaybar_dbusmenu_menu *menu,
+		int id) {
+	if (!menu || !menu->items) {
+		return;
+	}
+	// close all child menus of menu, except the child menu with the given id
+	for (int i = 0; i < menu->items->length; ++i) {
+		struct swaybar_dbusmenu_menu_item *item = menu->items->items[i];
+		if (item->id == id) {
+			continue;
+		}
+		struct swaybar_dbusmenu_menu *menu = item->submenu;
+		if (menu && menu->item_id != 0) {
+			close_menus(menu);
+		}
+	}
+}
+
+static void
+pointer_motion_process_item(struct swaybar_dbusmenu_menu *focused_menu,
+		struct swaybar_dbusmenu_menu_item *item, struct swaybar_seat *seat) {
+	int scale = focused_menu->dbusmenu->output->scale;
+	double x = seat->pointer.x * scale;
+	double y = seat->pointer.y * scale;
+	if (is_in_hotspot(&item->hotspot, x, y) && item->enabled &&
+			!item->is_separator) {
+		struct swaybar_tray *tray = focused_menu->dbusmenu->sni->tray;
+		struct swaybar_sni *sni = tray->menu->sni;
+		if (focused_menu->last_hovered_item != item) {
+			sd_bus_call_method_async(tray->bus, NULL, sni->service, sni->menu, 
+				menu_interface, "Event", NULL, NULL, "isvu", item->id, "hovered",
+				"y", 0, time(NULL));
+
+			sway_log(SWAY_DEBUG, "%s%s hovered id %d", sni->service, sni->menu,
+				item->id);
+
+			// open child menu if current item has a child menu and close other 
+			// potential open child menus. Only one child menu can be open at a time
+			close_child_menus_except(focused_menu, item->id);
+			open_menu_id(focused_menu->dbusmenu, item->id);
+			focused_menu->last_hovered_item = item;
+
+			// a different item needs to be highlighted
+			swaybar_dbusmenu_draw_menu(focused_menu, focused_menu->item_id, false);
+		}
+
+	}
+}
+
+bool dbusmenu_pointer_motion(struct swaybar_seat *seat, 
+		struct wl_pointer *wl_pointer, uint32_t time_, wl_fixed_t surface_x,
+		wl_fixed_t surface_y) {
+	struct swaybar_tray *tray = seat->bar->tray;
+	struct swaybar_dbusmenu_menu *focused_menu = tray->menu_pointer_focus;
+	if (!(tray && tray->menu && focused_menu)) {
+		return false;
+	}
+
+	for (int i = 0; i < focused_menu->items->length; ++i) {
+		struct swaybar_dbusmenu_menu_item *item = focused_menu->items->items[i];
+		pointer_motion_process_item(focused_menu, item, seat);
+	}
+
+	return true;
+}
+
+static struct swaybar_dbusmenu_menu *
+dbusmenu_menu_find_menu_surface(struct swaybar_dbusmenu_menu *menu,
+		struct wl_surface *surface) {
+	if (menu->surface && menu->surface->surface == surface) {
+		return menu;
+	}
+	if (!menu->items) {
+		return NULL;
+	}
+	for (int i = 0; i < menu->items->length; ++i) {
+		struct swaybar_dbusmenu_menu_item *item = menu->items->items[i];
+		struct swaybar_dbusmenu_menu *child_menu = item->submenu;
+		if (child_menu && child_menu->surface 
+				&& child_menu->surface->surface == surface) {
+			return child_menu;
+		}
+		if (child_menu) {
+			if (child_menu->item_id == 0) {
+				continue;
+			}
+			struct swaybar_dbusmenu_menu *child_child_menu =
+					dbusmenu_menu_find_menu_surface(child_menu, surface);
+			if (child_child_menu != NULL) {
+				return child_child_menu;
+			}
+		}
+	}
+
+	return NULL;
+}
+
+static void close_menus_by_id(struct swaybar_dbusmenu_menu *menu, int item_id) {
+	if (menu->items == NULL) {
+		return;
+	}
+	for (int i = 0; i < menu->items->length; ++i) {
+		struct swaybar_dbusmenu_menu_item *item = menu->items->items[i];
+		struct swaybar_dbusmenu_menu *child_menu = item->submenu;
+		if (child_menu && child_menu->item_id == item_id && child_menu->item_id != 0) {
+			close_menus(child_menu);
+		}
+	}
+}
+
+static void close_unfocused_child_menus(struct swaybar_dbusmenu_menu *menu,
+		struct swaybar_seat *seat) {
+	for (int i = 0; i < menu->items->length; ++i) {
+		struct swaybar_dbusmenu_menu_item *item = menu->items->items[i];
+
+		int scale = menu->dbusmenu->output->scale;
+		int x = seat->pointer.x * scale;
+		int y = seat->pointer.y * scale;
+		if (item->submenu && item->submenu->item_id != 0 
+				&& !is_in_hotspot(&item->hotspot, x, y)) {
+			close_menus_by_id(menu, item->id);
+		}
+	}
+}
+
+bool dbusmenu_pointer_frame(struct swaybar_seat *data, 
+		struct wl_pointer *wl_pointer) {
+	struct swaybar_tray *tray = data->bar->tray;
+	if (!(tray && tray->menu && tray->menu_pointer_focus)) {
+		return false;
+	}
+	return true;
+}
+
+bool dbusmenu_pointer_axis(struct swaybar_seat *data,
+		struct wl_pointer *wl_pointer) {
+	struct swaybar_tray *tray = data->bar->tray;
+	if (!(tray && tray->menu && tray->menu_pointer_focus)) {
+		return false;
+	}
+	return true;
+}
+
+bool dbusmenu_pointer_enter(void *data, struct wl_pointer *wl_pointer,
+		uint32_t serial, struct wl_surface *surface, wl_fixed_t surface_x,
+		wl_fixed_t surface_y) {
+	struct swaybar_seat *seat = data;
+	struct swaybar_tray *tray = seat->bar->tray;
+	if (!(tray && tray->menu)) {
+		return false;
+	}
+
+	struct swaybar_dbusmenu_menu *new_focused_menu =
+	dbusmenu_menu_find_menu_surface(tray->menu->menu, surface);
+
+	// Check if there are any child menus
+	bool has_child_menus = false;
+	if (new_focused_menu && new_focused_menu->items) {
+		for (int i = 0; i < new_focused_menu->items->length; ++i) {
+			struct swaybar_dbusmenu_menu_item *item = new_focused_menu->items->items[i];
+			if (item->submenu && item->submenu->item_id != 0) {
+				has_child_menus = true;
+			}
+		}
+	}
+
+	if (has_child_menus) {
+		close_unfocused_child_menus(new_focused_menu, seat);
+	}
+
+	tray->menu_pointer_focus = new_focused_menu;
+
+	return true;
+}
+
+bool dbusmenu_pointer_leave(void *data, struct wl_pointer *wl_pointer,
+		uint32_t serial, struct wl_surface *surface) {
+	struct swaybar_seat *seat = data;
+	struct swaybar_tray *tray = seat->bar->tray;
+	if (!(tray && tray->menu)) {
+		return false;
+	}
+
+	tray->menu_pointer_focus = NULL;
+
+	return true;
+}
+
+static bool dbusmenu_pointer_button_left_process_item(struct swaybar_dbusmenu *dbusmenu,
+		struct swaybar_dbusmenu_menu_item *item, struct swaybar_seat *seat) {
+	struct swaybar_sni *sni = dbusmenu->sni;
+	struct swaybar_tray *tray = sni->tray;
+	int scale = dbusmenu->output->scale;
+
+	if (is_in_hotspot(&item->hotspot, seat->pointer.x * scale,
+			seat->pointer.y * scale)) {
+		if (!item->enabled || item->is_separator) {
+			return false;
+		}
+
+		sway_log(SWAY_DEBUG, "%s%s menu clicked id %d", sni->service, sni->menu,
+				item->id);
+
+		sd_bus_call_method_async(tray->bus, NULL, sni->service, sni->menu,
+				menu_interface, "Event", NULL, NULL, "isvu", item->id, "clicked", "y", 0, 
+				time(NULL));
+
+		if (item->submenu) {
+			open_menu_id(dbusmenu, item->id);
+		} else {
+			// The user clicked an menu item other than a submenu. That means 
+			// the user made it's choise. Close the tray menu.
+			swaybar_dbusmenu_destroy(tray->menu);
+		}
+		return true;
+	}
+
+	return false;
+}
+
+static bool dbusmenu_pointer_button_left(struct swaybar_dbusmenu *dbusmenu,
+		struct swaybar_seat *seat) {
+	struct swaybar_dbusmenu_menu *focused_menu 
+		= dbusmenu->sni->tray->menu_pointer_focus;
+
+	if (!focused_menu) {
+		return true;
+	}
+
+	for (int i = 0; i < focused_menu->items->length; ++i) {
+		struct swaybar_dbusmenu_menu_item *item = focused_menu->items->items[i];
+		if (dbusmenu_pointer_button_left_process_item(dbusmenu, item, seat)) {
+			return true;
+		}
+	}
+
+	return true;
+}
+
+bool dbusmenu_pointer_button(void *data, struct wl_pointer *wl_pointer,
+		uint32_t serial, uint32_t time_, uint32_t button, uint32_t state) {
+	struct swaybar_seat *seat = data;
+	struct swaybar_tray *tray = seat->bar->tray;
+	if (!(tray && tray->menu)) {
+		return false;
+	}
+
+	if (state != WL_POINTER_BUTTON_STATE_PRESSED) {
+		// intentionally left blank
+		return true;
+	} else if (!tray->menu_pointer_focus) {
+		swaybar_dbusmenu_destroy(tray->menu);
+		return true;
+	} else if (button == BTN_LEFT) {
+		return dbusmenu_pointer_button_left(tray->menu, seat);
+	}
+
+	return false;
+}
diff --git a/swaybar/tray/item.c b/swaybar/tray/item.c
index ca6c03ad53..29bc843642 100644
--- a/swaybar/tray/item.c
+++ b/swaybar/tray/item.c
@@ -8,6 +8,7 @@
 #include "swaybar/config.h"
 #include "swaybar/image.h"
 #include "swaybar/input.h"
+#include "swaybar/tray/dbusmenu.h"
 #include "swaybar/tray/host.h"
 #include "swaybar/tray/icon.h"
 #include "swaybar/tray/item.h"
@@ -332,8 +333,9 @@ void destroy_sni(struct swaybar_sni *sni) {
 	free(sni);
 }
 
-static void handle_click(struct swaybar_sni *sni, int x, int y,
-		uint32_t button, int delta) {
+static void handle_click(struct swaybar_sni *sni, struct swaybar_output *output,
+		struct swaybar_seat *seat, uint32_t serial, int x, int y, uint32_t button,
+		int delta) {
 	const char *method = NULL;
 	struct tray_binding *binding = NULL;
 	wl_list_for_each(binding, &sni->tray->bar->config->tray_bindings, link) {
@@ -364,7 +366,11 @@ static void handle_click(struct swaybar_sni *sni, int x, int y,
 		method = "ContextMenu";
 	}
 
-	if (strncmp(method, "Scroll", strlen("Scroll")) == 0) {
+	if (strcmp(method, "ContextMenu") == 0) {
+		if (sni->menu && !sni->tray->menu) {
+			swaybar_dbusmenu_open(sni, output, seat, serial, x, y);
+		}
+	} else if (strncmp(method, "Scroll", strlen("Scroll")) == 0) {
 		char dir = method[strlen("Scroll")];
 		char *orientation = (dir == 'U' || dir == 'D') ? "vertical" : "horizontal";
 		int sign = (dir == 'U' || dir == 'L') ? -1 : 1;
@@ -384,6 +390,7 @@ static int cmp_sni_id(const void *item, const void *cmp_to) {
 
 static enum hotspot_event_handling icon_hotspot_callback(
 		struct swaybar_output *output, struct swaybar_hotspot *hotspot,
+		struct swaybar_seat *seat, uint32_t serial,
 		double x, double y, uint32_t button, bool released, void *data) {
 	sway_log(SWAY_DEBUG, "Clicked on %s", (char *)data);
 
@@ -405,7 +412,8 @@ static enum hotspot_event_handling icon_hotspot_callback(
 				(int) output->output_height - config->gaps.bottom - y);
 
 		sway_log(SWAY_DEBUG, "Guessing click position at (%d, %d)", global_x, global_y);
-		handle_click(sni, global_x, global_y, button, 1); // TODO get delta from event
+		// TODO get delta from event
+		handle_click(sni, output, seat, serial, global_x, global_y, button, 1);
 		return HOTSPOT_IGNORE;
 	} else {
 		sway_log(SWAY_DEBUG, "but it doesn't exist");

From 56ade4f0495d5782e7b592f84a73e2ce7af39b1e Mon Sep 17 00:00:00 2001
From: Florian Franzen <Florian.Franzen@gmail.com>
Date: Sat, 2 Mar 2024 11:19:37 +0000
Subject: [PATCH 4/6] Tray: don't invoke dbus menu when tray is disabled

---
 swaybar/input.c     | 26 +++++++++++++++++---------
 swaybar/tray/tray.c |  2 +-
 2 files changed, 18 insertions(+), 10 deletions(-)

diff --git a/swaybar/input.c b/swaybar/input.c
index dfac5480f4..54e1d5cde5 100644
--- a/swaybar/input.c
+++ b/swaybar/input.c
@@ -137,8 +137,9 @@ static void wl_pointer_enter(void *data, struct wl_pointer *wl_pointer,
 	}
 
 #if HAVE_TRAY
-	if (dbusmenu_pointer_enter(data, wl_pointer, serial, surface, surface_x,
-		surface_y)) {
+	struct swaybar_config *config = seat->bar->config;
+	if (!config->tray_hidden && dbusmenu_pointer_enter(data, wl_pointer, serial,
+		surface, surface_x, surface_y)) {
 		return;
 	}
 #endif
@@ -147,12 +148,14 @@ static void wl_pointer_enter(void *data, struct wl_pointer *wl_pointer,
 static void wl_pointer_leave(void *data, struct wl_pointer *wl_pointer,
 		uint32_t serial, struct wl_surface *surface) {
 #if HAVE_TRAY
-	if (dbusmenu_pointer_leave(data, wl_pointer, serial, surface)) {
+	struct swaybar_seat *seat = data;
+	struct swaybar_config *config = seat->bar->config;
+	if (!config->tray_hidden && dbusmenu_pointer_leave(data, wl_pointer, serial,
+		surface)) {
 		return;
 	}
 #endif
 
-	struct swaybar_seat *seat = data;
 	seat->pointer.current = NULL;
 }
 
@@ -162,7 +165,9 @@ static void wl_pointer_motion(void *data, struct wl_pointer *wl_pointer,
 	seat->pointer.x = wl_fixed_to_double(surface_x);
 	seat->pointer.y = wl_fixed_to_double(surface_y);
 #if HAVE_TRAY
-	if (dbusmenu_pointer_motion(data, wl_pointer, time, surface_x, surface_y)) {
+	struct swaybar_config *config = seat->bar->config;
+	if (!config->tray_hidden && dbusmenu_pointer_motion(data, wl_pointer, time,
+		surface_x, surface_y)) {
 		return;
 	}
 #endif
@@ -204,8 +209,9 @@ static void wl_pointer_button(void *data, struct wl_pointer *wl_pointer,
 		uint32_t serial, uint32_t time, uint32_t button, uint32_t state) {
 	struct swaybar_seat *seat = data;
 #if HAVE_TRAY
-	if (dbusmenu_pointer_button(seat, wl_pointer, serial, time, button,
-				state)) {
+	struct swaybar_config *config = seat->bar->config;
+	if (!config->tray_hidden && dbusmenu_pointer_button(seat, wl_pointer, serial,
+		time, button, state)) {
 		return;
 	}
 #endif
@@ -327,7 +333,8 @@ static void wl_pointer_axis(void *data, struct wl_pointer *wl_pointer,
 	}
 
 #if HAVE_TRAY
-	if (dbusmenu_pointer_axis(data, wl_pointer)) {
+	struct swaybar_config *config = seat->bar->config;
+	if (!config->tray_hidden && dbusmenu_pointer_axis(data, wl_pointer)) {
 		return;
 	}
 #endif
@@ -349,7 +356,8 @@ static void wl_pointer_frame(void *data, struct wl_pointer *wl_pointer) {
 	struct swaybar_output *output = pointer->current;
 
 #if HAVE_TRAY
-	if (dbusmenu_pointer_frame(data, wl_pointer)) {
+	struct swaybar_config *config = seat->bar->config;
+	if (!config->tray_hidden && dbusmenu_pointer_frame(data, wl_pointer)) {
 		return;
 	}
 #endif
diff --git a/swaybar/tray/tray.c b/swaybar/tray/tray.c
index a4f382bfbb..906c27010a 100644
--- a/swaybar/tray/tray.c
+++ b/swaybar/tray/tray.c
@@ -118,7 +118,7 @@ static int cmp_output(const void *item, const void *cmp_to) {
 
 uint32_t render_tray(cairo_t *cairo, struct swaybar_output *output, double *x) {
 	struct swaybar_config *config = output->bar->config;
-	if (config->tray_outputs) {
+	if (config->tray_outputs && !config->tray_hidden) {
 		if (list_seq_find(config->tray_outputs, cmp_output, output) == -1) {
 			return 0;
 		}

From 0eb4afc898dadc3ae8b6ca79fe1dc2a06b97414b Mon Sep 17 00:00:00 2001
From: Giancarlo Razzolini <grazzolini@gmail.com>
Date: Thu, 24 Oct 2024 09:19:03 -0300
Subject: [PATCH 5/6] * Tray dbus menu patches - Rebased against master - Made
 sure only the tray dbus menu patches are in

---
 .builds/alpine.yml      |  2 +-
 .builds/archlinux.yml   |  2 +-
 .builds/freebsd.yml     |  2 +-
 swaybar/tray/dbusmenu.c | 42 ++++++++++++++++++++---------------------
 4 files changed, 24 insertions(+), 24 deletions(-)

diff --git a/.builds/alpine.yml b/.builds/alpine.yml
index 6e6fc5f48d..7a1fa58e0c 100644
--- a/.builds/alpine.yml
+++ b/.builds/alpine.yml
@@ -25,7 +25,7 @@ packages:
   - hwdata-dev
 sources:
   - https://github.com/swaywm/sway
-  - https://gitlab.freedesktop.org/wlroots/wlroots.git#0.17
+  - https://gitlab.freedesktop.org/wlroots/wlroots.git
 tasks:
   - wlroots: |
       cd wlroots
diff --git a/.builds/archlinux.yml b/.builds/archlinux.yml
index a445e222cd..e249571ee7 100644
--- a/.builds/archlinux.yml
+++ b/.builds/archlinux.yml
@@ -22,7 +22,7 @@ packages:
   - hwdata
 sources:
   - https://github.com/swaywm/sway
-  - https://gitlab.freedesktop.org/wlroots/wlroots.git#0.17
+  - https://gitlab.freedesktop.org/wlroots/wlroots.git
 tasks:
   - wlroots: |
       cd wlroots
diff --git a/.builds/freebsd.yml b/.builds/freebsd.yml
index ddcac4f8ce..977fe467e8 100644
--- a/.builds/freebsd.yml
+++ b/.builds/freebsd.yml
@@ -31,7 +31,7 @@ packages:
 - misc/hwdata
 sources:
 - https://github.com/swaywm/sway
-- https://gitlab.freedesktop.org/wlroots/wlroots.git#0.17
+- https://gitlab.freedesktop.org/wlroots/wlroots.git
 tasks:
 - setup: |
     cd sway
diff --git a/swaybar/tray/dbusmenu.c b/swaybar/tray/dbusmenu.c
index 423abc425e..6f8f6b6168 100644
--- a/swaybar/tray/dbusmenu.c
+++ b/swaybar/tray/dbusmenu.c
@@ -140,7 +140,7 @@ static int handle_item_activation_requested(sd_bus_message *msg, void *data,
 }
 
 static struct swaybar_dbusmenu_surface *swaybar_dbusmenu_surface_create() {
-	struct swaybar_dbusmenu_surface *dbusmenu = calloc(1, 
+	struct swaybar_dbusmenu_surface *dbusmenu = calloc(1,
 			sizeof(struct swaybar_dbusmenu_surface));
 	if (!dbusmenu) {
 		sway_log(SWAY_DEBUG, "Could not allocate dbusmenu");
@@ -277,7 +277,7 @@ find_item_under_menu(struct swaybar_dbusmenu_menu *menu, int item_id) {
 			return item;
 		}
 		if (item->submenu && item->submenu->item_id != 0) {
-			struct swaybar_dbusmenu_menu_item *found_item = 
+			struct swaybar_dbusmenu_menu_item *found_item =
 				find_item_under_menu(item->submenu, item_id);
 			if (found_item) {
 				return found_item;
@@ -298,7 +298,7 @@ find_item(struct swaybar_dbusmenu *dbusmenu, int item_id) {
 }
 
 static struct swaybar_dbusmenu_menu *
-find_parent_menu_under_menu(struct swaybar_dbusmenu_menu *menu, 
+find_parent_menu_under_menu(struct swaybar_dbusmenu_menu *menu,
 		struct swaybar_dbusmenu_menu *child_menu) {
 	if (!menu->items) {
 		return NULL;
@@ -582,7 +582,7 @@ static void swaybar_dbusmenu_draw_menu(struct swaybar_dbusmenu_menu *menu,
 		}
 	}
 
-	cairo_surface_t *recorder = 
+	cairo_surface_t *recorder =
 		cairo_recording_surface_create(CAIRO_CONTENT_COLOR_ALPHA, NULL);
 	if (!recorder) {
 		return;
@@ -748,7 +748,7 @@ static int about_to_show_callback(sd_bus_message *msg, void *data,
 	swaybar_dbusmenu_draw(sni->tray->menu, menu_id);
 
 	sd_bus_call_method_async(sni->tray->bus, NULL, sni->service, sni->menu,
-		menu_interface, "Event", NULL, NULL, "isvu", menu_id, "opened", "y", 0, 
+		menu_interface, "Event", NULL, NULL, "isvu", menu_id, "opened", "y", 0,
 		time(NULL));
 
 	sway_log(SWAY_DEBUG, "%s%s opened id %d", sni->service, sni->menu, menu_id);
@@ -769,7 +769,7 @@ static void open_menu_id(struct swaybar_dbusmenu *dbusmenu, int menu_id) {
 	slot->menu_id = menu_id;
 
 	int ret = sd_bus_call_method_async(sni->tray->bus, &slot->slot, sni->service,
-		sni->menu, menu_interface, "AboutToShow", about_to_show_callback, slot, "i", 
+		sni->menu, menu_interface, "AboutToShow", about_to_show_callback, slot, "i",
 		menu_id);
 
 	if (ret >= 0) {
@@ -842,7 +842,7 @@ static int update_item_properties(struct swaybar_dbusmenu_menu_item *item,
 			log_value = toggle_type;
 		} else if (strcmp(key, "toggle-state") == 0) {
 			sd_bus_message_read(msg, "v", "i", &item->toggle_state);
-			log_value = item->toggle_state == 0 ? 
+			log_value = item->toggle_state == 0 ?
 				"off" : item->toggle_state == 1 ? "on" : "indeterminate";
 		} else if (strcmp(key, "children-display") == 0) {
 			char *children_display;
@@ -875,7 +875,7 @@ static int update_item_properties(struct swaybar_dbusmenu_menu_item *item,
 	return sd_bus_message_exit_container(msg);
 }
 
-static int get_layout_callback(sd_bus_message *msg, void *data, 
+static int get_layout_callback(sd_bus_message *msg, void *data,
 		sd_bus_error *error) {
 	struct swaybar_sni_slot *slot = data;
 	struct swaybar_sni *sni = slot->sni;
@@ -929,7 +929,7 @@ static int get_layout_callback(sd_bus_message *msg, void *data,
 	while (!sd_bus_message_at_end(msg, 1)) {
 		sd_bus_message_enter_container(msg, 'r', "ia{sv}av");
 
-		struct swaybar_dbusmenu_menu_item *item 
+		struct swaybar_dbusmenu_menu_item *item
 			= calloc(1, sizeof(struct swaybar_dbusmenu_menu_item));
 		if (!item) {
 			ret = -ENOMEM;
@@ -989,7 +989,7 @@ static int get_layout_callback(sd_bus_message *msg, void *data,
 
 static void swaybar_dbusmenu_subscribe_signal(struct swaybar_dbusmenu *menu,
 		const char *signal_name, sd_bus_message_handler_t callback) {
-	int ret = sd_bus_match_signal_async( menu->sni->tray->bus, NULL, 
+	int ret = sd_bus_match_signal_async( menu->sni->tray->bus, NULL,
 			menu->sni->service, menu->sni->menu, menu_interface, signal_name, callback,
 			NULL, menu->sni);
 
@@ -1066,7 +1066,7 @@ static int get_icon_theme_path_callback(sd_bus_message *msg, void *data,
 static void swaybar_dbusmenu_setup(struct swaybar_dbusmenu *menu) {
 		struct swaybar_sni_slot *slot = calloc(1, sizeof(struct swaybar_sni_slot));
 	slot->sni = menu->sni;
-	int ret = sd_bus_call_method_async( menu->sni->tray->bus, &slot->slot, 
+	int ret = sd_bus_call_method_async( menu->sni->tray->bus, &slot->slot,
 			menu->sni->service, menu->sni->path, "org.freedesktop.DBus.Properties",
 			"Get", get_icon_theme_path_callback, slot, "ss", menu->sni->interface,
 			"IconThemePath");
@@ -1083,7 +1083,7 @@ static void swaybar_dbusmenu_setup(struct swaybar_dbusmenu *menu) {
 }
 
 void swaybar_dbusmenu_open(struct swaybar_sni *sni,
-		struct swaybar_output *output, struct swaybar_seat *seat, uint32_t serial, 
+		struct swaybar_output *output, struct swaybar_seat *seat, uint32_t serial,
 		int x, int y) {
 	struct swaybar_dbusmenu *dbusmenu = sni->tray->menu;
 	if (!dbusmenu) {
@@ -1135,14 +1135,14 @@ pointer_motion_process_item(struct swaybar_dbusmenu_menu *focused_menu,
 		struct swaybar_tray *tray = focused_menu->dbusmenu->sni->tray;
 		struct swaybar_sni *sni = tray->menu->sni;
 		if (focused_menu->last_hovered_item != item) {
-			sd_bus_call_method_async(tray->bus, NULL, sni->service, sni->menu, 
+			sd_bus_call_method_async(tray->bus, NULL, sni->service, sni->menu,
 				menu_interface, "Event", NULL, NULL, "isvu", item->id, "hovered",
 				"y", 0, time(NULL));
 
 			sway_log(SWAY_DEBUG, "%s%s hovered id %d", sni->service, sni->menu,
 				item->id);
 
-			// open child menu if current item has a child menu and close other 
+			// open child menu if current item has a child menu and close other
 			// potential open child menus. Only one child menu can be open at a time
 			close_child_menus_except(focused_menu, item->id);
 			open_menu_id(focused_menu->dbusmenu, item->id);
@@ -1155,7 +1155,7 @@ pointer_motion_process_item(struct swaybar_dbusmenu_menu *focused_menu,
 	}
 }
 
-bool dbusmenu_pointer_motion(struct swaybar_seat *seat, 
+bool dbusmenu_pointer_motion(struct swaybar_seat *seat,
 		struct wl_pointer *wl_pointer, uint32_t time_, wl_fixed_t surface_x,
 		wl_fixed_t surface_y) {
 	struct swaybar_tray *tray = seat->bar->tray;
@@ -1184,7 +1184,7 @@ dbusmenu_menu_find_menu_surface(struct swaybar_dbusmenu_menu *menu,
 	for (int i = 0; i < menu->items->length; ++i) {
 		struct swaybar_dbusmenu_menu_item *item = menu->items->items[i];
 		struct swaybar_dbusmenu_menu *child_menu = item->submenu;
-		if (child_menu && child_menu->surface 
+		if (child_menu && child_menu->surface
 				&& child_menu->surface->surface == surface) {
 			return child_menu;
 		}
@@ -1224,14 +1224,14 @@ static void close_unfocused_child_menus(struct swaybar_dbusmenu_menu *menu,
 		int scale = menu->dbusmenu->output->scale;
 		int x = seat->pointer.x * scale;
 		int y = seat->pointer.y * scale;
-		if (item->submenu && item->submenu->item_id != 0 
+		if (item->submenu && item->submenu->item_id != 0
 				&& !is_in_hotspot(&item->hotspot, x, y)) {
 			close_menus_by_id(menu, item->id);
 		}
 	}
 }
 
-bool dbusmenu_pointer_frame(struct swaybar_seat *data, 
+bool dbusmenu_pointer_frame(struct swaybar_seat *data,
 		struct wl_pointer *wl_pointer) {
 	struct swaybar_tray *tray = data->bar->tray;
 	if (!(tray && tray->menu && tray->menu_pointer_focus)) {
@@ -1310,13 +1310,13 @@ static bool dbusmenu_pointer_button_left_process_item(struct swaybar_dbusmenu *d
 				item->id);
 
 		sd_bus_call_method_async(tray->bus, NULL, sni->service, sni->menu,
-				menu_interface, "Event", NULL, NULL, "isvu", item->id, "clicked", "y", 0, 
+				menu_interface, "Event", NULL, NULL, "isvu", item->id, "clicked", "y", 0,
 				time(NULL));
 
 		if (item->submenu) {
 			open_menu_id(dbusmenu, item->id);
 		} else {
-			// The user clicked an menu item other than a submenu. That means 
+			// The user clicked an menu item other than a submenu. That means
 			// the user made it's choise. Close the tray menu.
 			swaybar_dbusmenu_destroy(tray->menu);
 		}
@@ -1328,7 +1328,7 @@ static bool dbusmenu_pointer_button_left_process_item(struct swaybar_dbusmenu *d
 
 static bool dbusmenu_pointer_button_left(struct swaybar_dbusmenu *dbusmenu,
 		struct swaybar_seat *seat) {
-	struct swaybar_dbusmenu_menu *focused_menu 
+	struct swaybar_dbusmenu_menu *focused_menu
 		= dbusmenu->sni->tray->menu_pointer_focus;
 
 	if (!focused_menu) {

From 19661d1853e766f28ac44284383ff2ee49bf53ae Mon Sep 17 00:00:00 2001
From: Giancarlo Razzolini <grazzolini@users.noreply.github.com>
Date: Fri, 25 Oct 2024 15:25:49 -0300
Subject: [PATCH 6/6] Apply suggestions from code review

Co-authored-by: Demi Marie Obenour <demiobenour@gmail.com>
---
 swaybar/tray/dbusmenu.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/swaybar/tray/dbusmenu.c b/swaybar/tray/dbusmenu.c
index 6f8f6b6168..63d94d9cc3 100644
--- a/swaybar/tray/dbusmenu.c
+++ b/swaybar/tray/dbusmenu.c
@@ -335,8 +335,8 @@ static bool is_in_hotspot(struct swaybar_dbusmenu_hotspot *hotspot, int x, int y
 		return false;
 	}
 
-	if (hotspot->x <= x && x < hotspot->x + hotspot->width && hotspot->y <= y &&
-			y < hotspot->y + hotspot->height) {
+	if (hotspot->x <= x && x - hotspot->x < hotspot->width && hotspot->y <= y &&
+			y - hotspot->y < hotspot->height) {
 		return true;
 	}
 
