From db92d6ed01b9def2241e54f83f62313a15bd7d5c Mon Sep 17 00:00:00 2001 From: Guanran Wang Date: Sat, 9 Mar 2024 10:52:22 +0800 Subject: [PATCH] overlays/sway/1.9: use patches rebased by NickHu --- .../1.9/0001-Tray-Implement-dbusmenu.patch | 1754 ----------------- overlays/sway/1.9/default.nix | 13 +- 2 files changed, 11 insertions(+), 1756 deletions(-) delete mode 100644 overlays/sway/1.9/0001-Tray-Implement-dbusmenu.patch diff --git a/overlays/sway/1.9/0001-Tray-Implement-dbusmenu.patch b/overlays/sway/1.9/0001-Tray-Implement-dbusmenu.patch deleted file mode 100644 index a1b5ee6..0000000 --- a/overlays/sway/1.9/0001-Tray-Implement-dbusmenu.patch +++ /dev/null @@ -1,1754 +0,0 @@ -From 44ebf74ca6e02855b5974149b65c65362336f16f Mon Sep 17 00:00:00 2001 -From: Felix Weilbach -Date: Sun, 30 May 2021 20:45:01 +0200 -Subject: [PATCH] Tray: Implement dbusmenu - -Co-authored-by: Ian Fan -Co-authored-by: Nathan Schulte - -Signed-off-by: Felix Weilbach ---- - 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 197d2190..d0b3dc81 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 8ea88a69..81ccaa98 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 00000000..dc90f6e5 ---- /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 73937a0c..9a4a00ff 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 d2e80a6d..853f17cd 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 021fc3bd..9c83db3e 100644 ---- a/swaybar/bar.c -+++ b/swaybar/bar.c -@@ -29,6 +29,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; -@@ -365,6 +366,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); - } - } - -@@ -539,6 +542,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 ada4bc86..dfac5480 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 e5f1811e..fef1ee77 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 1113ca44..7769063d 100644 ---- a/swaybar/render.c -+++ b/swaybar/render.c -@@ -160,6 +160,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; -@@ -599,6 +600,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 00000000..423abc42 ---- /dev/null -+++ b/swaybar/tray/dbusmenu.c -@@ -0,0 +1,1367 @@ -+#define _POSIX_C_SOURCE 200809L -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#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 ? "" : ""; -+ } 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 = ""; -+ } -+ 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 1f18b8bb..d159640f 100644 ---- a/swaybar/tray/item.c -+++ b/swaybar/tray/item.c -@@ -8,6 +8,7 @@ - #include "swaybar/bar.h" - #include "swaybar/config.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" -@@ -333,8 +334,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) { -@@ -365,7 +367,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; -@@ -385,6 +391,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); - -@@ -406,7 +413,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"); --- -2.43.1 - diff --git a/overlays/sway/1.9/default.nix b/overlays/sway/1.9/default.nix index 2ce35d4..8c3ee77 100644 --- a/overlays/sway/1.9/default.nix +++ b/overlays/sway/1.9/default.nix @@ -18,8 +18,17 @@ hash = "sha256-PzhQBRpyB1WhErn05UBtBfaDW5bxnQLRKWu8jy7dEiM="; }) - # (rebased) Tray D-Bus Menu + # Tray D-Bus Menu # https://github.com/swaywm/sway/pull/6249 - ./0001-Tray-Implement-dbusmenu.patch + (prev.fetchpatch { + name = "0001-Tray-Implement-dbusmenu.patch"; + url = "https://github.com/NickHu/sway/commit/0fc5d7aed84415a77b718ca9dc3c0b3ad2c05b02.patch"; + hash = "sha256-1KuGZGwyGJK8KO4OngS+tWKV/3Yu++bCNnp+xTrlGoY="; + }) + (prev.fetchpatch { + name = "0002-Tray-dont-invoke-dbus-menu-when-tray-is-disabled.patch"; + url = "https://github.com/NickHu/sway/commit/03c14421354e54332e12f78d029dcaa9919fd161.patch"; + hash = "sha256-GhBlCnk7aB6s57wV1FNOPAt6s0oJxLgf2bMw+8ktn8A"; + }) ]; }