From c3df5486471df00aff1411524282574936fada6c Mon Sep 17 00:00:00 2001 From: Felix Weilbach Date: Sun, 30 May 2021 20:45:01 +0200 Subject: [PATCH 01/15] 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 197d21901..d0b3dc815 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 8ea88a69a..81ccaa989 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 000000000..dc90f6e57 --- /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 73937a0cc..9a4a00ff6 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 d2e80a6d4..853f17cdc 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 4d20f20f0..ee9f843e3 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 ada4bc862..dfac5480f 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 34bbdeea9..baeb74c46 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 c51321f18..7c6d2192d 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; @@ -612,6 +613,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 000000000..423abc425 --- /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 12929743b..9fb5eb064 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" @@ -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 (has_prefix(method, "Scroll")) { + if (has_prefix(method, "ContextMenu")) { + if (sni->menu && !sni->tray->menu) { + swaybar_dbusmenu_open(sni, output, seat, serial, x, y); + } + } else if (has_prefix(method, "Scroll")) { 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"); From 42817f2a8e5411bd89e619b1102b27a06aec4587 Mon Sep 17 00:00:00 2001 From: Florian Franzen Date: Sat, 2 Mar 2024 11:19:37 +0000 Subject: [PATCH 02/15] 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 dfac5480f..54e1d5cde 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 a4f382bfb..906c27010 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 f6420647177b3b95911f4c9a55913b8f6fee426b Mon Sep 17 00:00:00 2001 From: Giancarlo Razzolini Date: Fri, 25 Oct 2024 15:25:49 -0300 Subject: [PATCH 03/15] Apply suggestions from code review Co-authored-by: Demi Marie Obenour --- 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 423abc425..64caf2d32 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; } From 56c4660ebc60d18f87039794d33994d7a4a8e84b Mon Sep 17 00:00:00 2001 From: blinxen Date: Mon, 13 Oct 2025 01:30:03 +0200 Subject: [PATCH 04/15] Remove trailing whitespace --- swaybar/tray/dbusmenu.c | 42 ++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/swaybar/tray/dbusmenu.c b/swaybar/tray/dbusmenu.c index 64caf2d32..63d94d9cc 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 6523a2379b650a61952cb00c0271ccbc71f719af Mon Sep 17 00:00:00 2001 From: blinxen Date: Mon, 13 Oct 2025 01:33:36 +0200 Subject: [PATCH 05/15] Replace old usage of background-image.h See https://github.com/swaywm/sway/pull/8405#discussion_r1817770755 and https://github.com/swaywm/sway/pull/8405#discussion_r1817770817 --- 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 63d94d9cc..fc7911424 100644 --- a/swaybar/tray/dbusmenu.c +++ b/swaybar/tray/dbusmenu.c @@ -9,7 +9,6 @@ #include #include -#include "background-image.h" #include "cairo.h" #include "cairo_util.h" #include "list.h" @@ -17,6 +16,7 @@ #include "pango.h" #include "swaybar/bar.h" #include "swaybar/config.h" +#include "swaybar/image.h" #include "swaybar/input.h" #include "swaybar/tray/icon.h" #include "swaybar/tray/item.h" @@ -416,7 +416,7 @@ static void draw_menu_items(cairo_t *cairo, struct swaybar_dbusmenu_menu *menu, list_free(icon_search_paths); if (icon_path) { - cairo_surface_t *icon = load_background_image(icon_path); + cairo_surface_t *icon = load_image(icon_path); free(icon_path); cairo_surface_t *icon_scaled = cairo_image_surface_scale(icon, size, size); From 8241bde588085277d4cb74bbecafbfb8524689d7 Mon Sep 17 00:00:00 2001 From: blinxen Date: Mon, 13 Oct 2025 01:34:46 +0200 Subject: [PATCH 06/15] Move variable creation outside of conditional code block See https://github.com/swaywm/sway/pull/8405#discussion_r1818100461 --- swaybar/input.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/swaybar/input.c b/swaybar/input.c index 54e1d5cde..f484618bf 100644 --- a/swaybar/input.c +++ b/swaybar/input.c @@ -147,8 +147,8 @@ 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 struct swaybar_seat *seat = data; +#if HAVE_TRAY struct swaybar_config *config = seat->bar->config; if (!config->tray_hidden && dbusmenu_pointer_leave(data, wl_pointer, serial, surface)) { From 023e68a6738c2c447596ad7f0e4f259ea3d51d48 Mon Sep 17 00:00:00 2001 From: blinxen Date: Mon, 13 Oct 2025 21:26:39 +0200 Subject: [PATCH 07/15] Initialize surface height with 0 before drawing --- swaybar/tray/dbusmenu.c | 1 + 1 file changed, 1 insertion(+) diff --git a/swaybar/tray/dbusmenu.c b/swaybar/tray/dbusmenu.c index fc7911424..6ed0d5ed5 100644 --- a/swaybar/tray/dbusmenu.c +++ b/swaybar/tray/dbusmenu.c @@ -359,6 +359,7 @@ static void draw_menu_items(cairo_t *cairo, struct swaybar_dbusmenu_menu *menu, *surface_y = 0; *surface_x = 0; *surface_width = 0; + *surface_height = 0; bool is_icon_drawn = false; int icon_size = 0; From 9c1da3a631fbf0a771bcff2f071cc5ab7bdf309c Mon Sep 17 00:00:00 2001 From: blinxen Date: Mon, 13 Oct 2025 21:28:33 +0200 Subject: [PATCH 08/15] Close dbus menu when unregistering sni --- include/swaybar/tray/dbusmenu.h | 2 ++ swaybar/tray/host.c | 5 ++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/include/swaybar/tray/dbusmenu.h b/include/swaybar/tray/dbusmenu.h index dc90f6e57..701fdb823 100644 --- a/include/swaybar/tray/dbusmenu.h +++ b/include/swaybar/tray/dbusmenu.h @@ -8,6 +8,8 @@ void swaybar_dbusmenu_open(struct swaybar_sni *sni, struct swaybar_output *output, struct swaybar_seat *seat, uint32_t serial, int x, int y); +void swaybar_dbusmenu_destroy(struct swaybar_dbusmenu *menu); + bool dbusmenu_pointer_button(void *data, struct wl_pointer *wl_pointer, uint32_t serial, uint32_t time_, uint32_t button, uint32_t state); diff --git a/swaybar/tray/host.c b/swaybar/tray/host.c index 79b54606f..aa00199f4 100644 --- a/swaybar/tray/host.c +++ b/swaybar/tray/host.c @@ -7,6 +7,7 @@ #include "swaybar/tray/host.h" #include "swaybar/tray/item.h" #include "swaybar/tray/tray.h" +#include "swaybar/tray/dbusmenu.h" #include "list.h" #include "log.h" #include "stringop.h" @@ -55,7 +56,9 @@ static int handle_sni_unregistered(sd_bus_message *msg, void *data, int idx = list_seq_find(tray->items, cmp_sni_id, id); if (idx != -1) { sway_log(SWAY_INFO, "Unregistering Status Notifier Item '%s'", id); - destroy_sni(tray->items->items[idx]); + struct swaybar_sni *sni = tray->items->items[idx]; + swaybar_dbusmenu_destroy(sni->tray->menu); + destroy_sni(sni); list_del(tray->items, idx); set_bar_dirty(tray->bar); } From 0289272bd0e722522eaec7e7e5a1404bd332e4d6 Mon Sep 17 00:00:00 2001 From: blinxen Date: Mon, 13 Oct 2025 21:28:56 +0200 Subject: [PATCH 09/15] Remove comment that should be now obsolete --- swaybar/tray/item.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/swaybar/tray/item.c b/swaybar/tray/item.c index 9fb5eb064..262b00dd5 100644 --- a/swaybar/tray/item.c +++ b/swaybar/tray/item.c @@ -19,8 +19,6 @@ #include "stringop.h" #include "wlr-layer-shell-unstable-v1-client-protocol.h" -// TODO menu - static bool sni_ready(struct swaybar_sni *sni) { return sni->status && (sni->status[0] == 'N' ? // NeedsAttention sni->attention_icon_name || sni->attention_icon_pixmap : From 47234b37da5f7ae539ee138d91ab7ec1241948f5 Mon Sep 17 00:00:00 2001 From: blinxen Date: Mon, 13 Oct 2025 21:47:17 +0200 Subject: [PATCH 10/15] Limit recursion depth when trying to find items under dbus menu --- swaybar/tray/dbusmenu.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/swaybar/tray/dbusmenu.c b/swaybar/tray/dbusmenu.c index 6ed0d5ed5..70e18e133 100644 --- a/swaybar/tray/dbusmenu.c +++ b/swaybar/tray/dbusmenu.c @@ -267,7 +267,8 @@ static const struct xdg_popup_listener xdg_popup_listener = { static struct swaybar_dbusmenu_menu_item * find_item_under_menu(struct swaybar_dbusmenu_menu *menu, int item_id) { - if (!menu->items) { + static int recursion_depth = 0; + if (!menu->items || recursion_depth > 10) { return NULL; } @@ -277,6 +278,7 @@ find_item_under_menu(struct swaybar_dbusmenu_menu *menu, int item_id) { return item; } if (item->submenu && item->submenu->item_id != 0) { + recursion_depth++; struct swaybar_dbusmenu_menu_item *found_item = find_item_under_menu(item->submenu, item_id); if (found_item) { From 0f7853e07494e2dd91758d11b431f2fe2479904c Mon Sep 17 00:00:00 2001 From: blinxen Date: Mon, 13 Oct 2025 21:55:52 +0200 Subject: [PATCH 11/15] Rename dbusmenu functions to adhere to the style guide --- include/swaybar/tray/dbusmenu.h | 12 ++++++------ swaybar/input.c | 12 ++++++------ swaybar/tray/dbusmenu.c | 12 ++++++------ 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/include/swaybar/tray/dbusmenu.h b/include/swaybar/tray/dbusmenu.h index 701fdb823..e27abf8d0 100644 --- a/include/swaybar/tray/dbusmenu.h +++ b/include/swaybar/tray/dbusmenu.h @@ -10,20 +10,20 @@ void swaybar_dbusmenu_open(struct swaybar_sni *sni, void swaybar_dbusmenu_destroy(struct swaybar_dbusmenu *menu); -bool dbusmenu_pointer_button(void *data, struct wl_pointer *wl_pointer, +bool sway_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, +bool sway_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, +bool sway_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, +bool sway_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 sway_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); +bool sway_dbusmenu_pointer_axis(struct swaybar_seat *data, struct wl_pointer *wl_pointer); #endif diff --git a/swaybar/input.c b/swaybar/input.c index f484618bf..d1b4c5877 100644 --- a/swaybar/input.c +++ b/swaybar/input.c @@ -138,7 +138,7 @@ static void wl_pointer_enter(void *data, struct wl_pointer *wl_pointer, #if HAVE_TRAY struct swaybar_config *config = seat->bar->config; - if (!config->tray_hidden && dbusmenu_pointer_enter(data, wl_pointer, serial, + if (!config->tray_hidden && sway_dbusmenu_pointer_enter(data, wl_pointer, serial, surface, surface_x, surface_y)) { return; } @@ -150,7 +150,7 @@ static void wl_pointer_leave(void *data, struct wl_pointer *wl_pointer, struct swaybar_seat *seat = data; #if HAVE_TRAY struct swaybar_config *config = seat->bar->config; - if (!config->tray_hidden && dbusmenu_pointer_leave(data, wl_pointer, serial, + if (!config->tray_hidden && sway_dbusmenu_pointer_leave(data, wl_pointer, serial, surface)) { return; } @@ -166,7 +166,7 @@ static void wl_pointer_motion(void *data, struct wl_pointer *wl_pointer, seat->pointer.y = wl_fixed_to_double(surface_y); #if HAVE_TRAY struct swaybar_config *config = seat->bar->config; - if (!config->tray_hidden && dbusmenu_pointer_motion(data, wl_pointer, time, + if (!config->tray_hidden && sway_dbusmenu_pointer_motion(data, wl_pointer, time, surface_x, surface_y)) { return; } @@ -210,7 +210,7 @@ static void wl_pointer_button(void *data, struct wl_pointer *wl_pointer, struct swaybar_seat *seat = data; #if HAVE_TRAY struct swaybar_config *config = seat->bar->config; - if (!config->tray_hidden && dbusmenu_pointer_button(seat, wl_pointer, serial, + if (!config->tray_hidden && sway_dbusmenu_pointer_button(seat, wl_pointer, serial, time, button, state)) { return; } @@ -334,7 +334,7 @@ static void wl_pointer_axis(void *data, struct wl_pointer *wl_pointer, #if HAVE_TRAY struct swaybar_config *config = seat->bar->config; - if (!config->tray_hidden && dbusmenu_pointer_axis(data, wl_pointer)) { + if (!config->tray_hidden && sway_dbusmenu_pointer_axis(data, wl_pointer)) { return; } #endif @@ -357,7 +357,7 @@ static void wl_pointer_frame(void *data, struct wl_pointer *wl_pointer) { #if HAVE_TRAY struct swaybar_config *config = seat->bar->config; - if (!config->tray_hidden && dbusmenu_pointer_frame(data, wl_pointer)) { + if (!config->tray_hidden && sway_dbusmenu_pointer_frame(data, wl_pointer)) { return; } #endif diff --git a/swaybar/tray/dbusmenu.c b/swaybar/tray/dbusmenu.c index 70e18e133..6c8fd672f 100644 --- a/swaybar/tray/dbusmenu.c +++ b/swaybar/tray/dbusmenu.c @@ -1158,7 +1158,7 @@ pointer_motion_process_item(struct swaybar_dbusmenu_menu *focused_menu, } } -bool dbusmenu_pointer_motion(struct swaybar_seat *seat, +bool sway_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; @@ -1234,7 +1234,7 @@ static void close_unfocused_child_menus(struct swaybar_dbusmenu_menu *menu, } } -bool dbusmenu_pointer_frame(struct swaybar_seat *data, +bool sway_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)) { @@ -1243,7 +1243,7 @@ bool dbusmenu_pointer_frame(struct swaybar_seat *data, return true; } -bool dbusmenu_pointer_axis(struct swaybar_seat *data, +bool sway_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)) { @@ -1252,7 +1252,7 @@ bool dbusmenu_pointer_axis(struct swaybar_seat *data, return true; } -bool dbusmenu_pointer_enter(void *data, struct wl_pointer *wl_pointer, +bool sway_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; @@ -1284,7 +1284,7 @@ bool dbusmenu_pointer_enter(void *data, struct wl_pointer *wl_pointer, return true; } -bool dbusmenu_pointer_leave(void *data, struct wl_pointer *wl_pointer, +bool sway_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; @@ -1348,7 +1348,7 @@ static bool dbusmenu_pointer_button_left(struct swaybar_dbusmenu *dbusmenu, return true; } -bool dbusmenu_pointer_button(void *data, struct wl_pointer *wl_pointer, +bool sway_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; From 9e035e027729bdfffa86d8032f81418e6c032ea3 Mon Sep 17 00:00:00 2001 From: blinxen Date: Mon, 13 Oct 2025 22:11:39 +0200 Subject: [PATCH 12/15] Remove unused includes --- swaybar/bar.c | 2 -- swaybar/config.c | 1 - swaybar/i3bar.c | 1 - swaybar/status_line.c | 1 - swaybar/tray/host.c | 1 - swaybar/tray/watcher.c | 1 - 6 files changed, 7 deletions(-) diff --git a/swaybar/bar.c b/swaybar/bar.c index ee9f843e3..5763bfa5c 100644 --- a/swaybar/bar.c +++ b/swaybar/bar.c @@ -13,7 +13,6 @@ #include "config.h" #include "swaybar/bar.h" #include "swaybar/config.h" -#include "swaybar/i3bar.h" #include "swaybar/input.h" #include "swaybar/ipc.h" #include "swaybar/status_line.h" @@ -22,7 +21,6 @@ #include "swaybar/tray/tray.h" #endif #include "ipc-client.h" -#include "list.h" #include "log.h" #include "loop.h" #include "pool-buffer.h" diff --git a/swaybar/config.c b/swaybar/config.c index 55bfcb722..887051af7 100644 --- a/swaybar/config.c +++ b/swaybar/config.c @@ -3,7 +3,6 @@ #include "swaybar/config.h" #include "wlr-layer-shell-unstable-v1-client-protocol.h" #include "config.h" -#include "stringop.h" #include "list.h" #include "log.h" diff --git a/swaybar/i3bar.c b/swaybar/i3bar.c index 62c22d43c..420aea94d 100644 --- a/swaybar/i3bar.c +++ b/swaybar/i3bar.c @@ -6,7 +6,6 @@ #include #include "log.h" #include "swaybar/bar.h" -#include "swaybar/config.h" #include "swaybar/i3bar.h" #include "swaybar/input.h" #include "swaybar/status_line.h" diff --git a/swaybar/status_line.c b/swaybar/status_line.c index e542e606b..5094931e7 100644 --- a/swaybar/status_line.c +++ b/swaybar/status_line.c @@ -11,7 +11,6 @@ #include "log.h" #include "loop.h" #include "swaybar/bar.h" -#include "swaybar/config.h" #include "swaybar/i3bar.h" #include "swaybar/status_line.h" diff --git a/swaybar/tray/host.c b/swaybar/tray/host.c index aa00199f4..172d649f5 100644 --- a/swaybar/tray/host.c +++ b/swaybar/tray/host.c @@ -1,5 +1,4 @@ #include -#include #include #include #include diff --git a/swaybar/tray/watcher.c b/swaybar/tray/watcher.c index 284964030..1b8cd52b1 100644 --- a/swaybar/tray/watcher.c +++ b/swaybar/tray/watcher.c @@ -1,6 +1,5 @@ #include #include -#include #include #include #include "list.h" From 2508a1d019919a610ed6b385459b68c285af8d8e Mon Sep 17 00:00:00 2001 From: Vladimir Panteleev Date: Sat, 10 Jan 2026 13:02:57 +0000 Subject: [PATCH 13/15] fix(swaybar/dbusmenu): fix null pointer dereference when loading icons This change adds a null check when loading icons in the dbusmenu code to avoid a segmentation fault when the icon surface fails to load. --- swaybar/tray/dbusmenu.c | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/swaybar/tray/dbusmenu.c b/swaybar/tray/dbusmenu.c index 6c8fd672f..65ec8d7d8 100644 --- a/swaybar/tray/dbusmenu.c +++ b/swaybar/tray/dbusmenu.c @@ -421,15 +421,17 @@ static void draw_menu_items(cairo_t *cairo, struct swaybar_dbusmenu_menu *menu, if (icon_path) { cairo_surface_t *icon = load_image(icon_path); free(icon_path); - cairo_surface_t *icon_scaled = - cairo_image_surface_scale(icon, size, size); - cairo_surface_destroy(icon); + if (icon) { + 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; + 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); From d0242fb6b4a85a03ff1b0495abed9b6b2ecb0108 Mon Sep 17 00:00:00 2001 From: Vladimir Panteleev Date: Sat, 10 Jan 2026 13:05:45 +0000 Subject: [PATCH 14/15] fix(swaybar/dbusmenu): properly destroy cairo surface for icon data Properly destroy cairo surface for icon data when freeing dbusmenu items. --- swaybar/tray/dbusmenu.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/swaybar/tray/dbusmenu.c b/swaybar/tray/dbusmenu.c index 65ec8d7d8..7665f83f2 100644 --- a/swaybar/tray/dbusmenu.c +++ b/swaybar/tray/dbusmenu.c @@ -236,7 +236,9 @@ static void swaybar_dbusmenu_menu_destroy(struct swaybar_dbusmenu_menu *menu) { } free(item->label); free(item->icon_name); - free(item->icon_data); + if (item->icon_data) { + cairo_surface_destroy(item->icon_data); + } free(item); } } From a7a7660a49f964d0c76872e839a70db10ba8bcc0 Mon Sep 17 00:00:00 2001 From: Vladimir Panteleev Date: Sat, 10 Jan 2026 13:13:57 +0000 Subject: [PATCH 15/15] fix(swaybar/dbusmenu): cache icon lookups to avoid repeated filesystem access Add caching for icon lookups in menu items. Two new fields are added to the menu item struct: - icon_surface: cached icon surface loaded from icon_name - icon_lookup_done: tracks whether icon lookup has been attempted The icon is now looked up once and cached, rather than performing filesystem access every time the menu is drawn. The cached surface is properly cleaned up when the menu is destroyed. --- swaybar/tray/dbusmenu.c | 61 +++++++++++++++++++++++------------------ 1 file changed, 35 insertions(+), 26 deletions(-) diff --git a/swaybar/tray/dbusmenu.c b/swaybar/tray/dbusmenu.c index 7665f83f2..360838817 100644 --- a/swaybar/tray/dbusmenu.c +++ b/swaybar/tray/dbusmenu.c @@ -60,6 +60,8 @@ struct swaybar_dbusmenu_menu_item { char *label; char *icon_name; cairo_surface_t *icon_data; + cairo_surface_t *icon_surface; // cached icon loaded from icon_name + bool icon_lookup_done; // true if we've already tried to look up icon_name enum menu_toggle_type toggle_type; bool enabled; bool visible; @@ -239,6 +241,9 @@ static void swaybar_dbusmenu_menu_destroy(struct swaybar_dbusmenu_menu *menu) { if (item->icon_data) { cairo_surface_destroy(item->icon_data); } + if (item->icon_surface) { + cairo_surface_destroy(item->icon_surface); + } free(item); } } @@ -404,36 +409,40 @@ static void draw_menu_items(cairo_t *cairo, struct swaybar_dbusmenu_menu *menu, 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); + // Cache icon lookup to avoid repeated filesystem access + if (!item->icon_lookup_done) { + item->icon_lookup_done = true; + 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) { + item->icon_surface = load_image(icon_path); + free(icon_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_image(icon_path); - free(icon_path); - if (icon) { - cairo_surface_t *icon_scaled = - cairo_image_surface_scale(icon, size, size); - cairo_surface_destroy(icon); + if (item->icon_surface) { + cairo_surface_t *icon_scaled = + cairo_image_surface_scale(item->icon_surface, size, size); - 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; - } + 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);