From bf2f2510a4a193bb9cf31669ab500f7380dbdd2a Mon Sep 17 00:00:00 2001 From: Tim Biermann Date: Sun, 19 Sep 2021 19:44:47 +0200 Subject: [PATCH] sway: initial commit, version 1.6.1 --- sway/.footprint | 22 + sway/.signature | 6 + sway/6249.patch | 1774 +++++++++++++++++++++++++++++++++++++++++++++++ sway/Pkgfile | 32 + 4 files changed, 1834 insertions(+) create mode 100644 sway/.footprint create mode 100644 sway/.signature create mode 100644 sway/6249.patch create mode 100644 sway/Pkgfile diff --git a/sway/.footprint b/sway/.footprint new file mode 100644 index 000000000..670215003 --- /dev/null +++ b/sway/.footprint @@ -0,0 +1,22 @@ +drwxr-xr-x root/root etc/ +drwxr-xr-x root/root etc/sway/ +-rw-r--r-- root/root etc/sway/config +drwxr-xr-x root/root usr/ +drwxr-xr-x root/root usr/bin/ +-rwxr-xr-x root/root usr/bin/sway +-rwxr-xr-x root/root usr/bin/swaybar +-rwxr-xr-x root/root usr/bin/swaymsg +-rwxr-xr-x root/root usr/bin/swaynag +drwxr-xr-x root/root usr/share/ +drwxr-xr-x root/root usr/share/backgrounds/ +drwxr-xr-x root/root usr/share/backgrounds/sway/ +-rw-r--r-- root/root usr/share/backgrounds/sway/Sway_Wallpaper_Blue_1136x640.png +-rw-r--r-- root/root usr/share/backgrounds/sway/Sway_Wallpaper_Blue_1136x640_Portrait.png +-rw-r--r-- root/root usr/share/backgrounds/sway/Sway_Wallpaper_Blue_1366x768.png +-rw-r--r-- root/root usr/share/backgrounds/sway/Sway_Wallpaper_Blue_1920x1080.png +-rw-r--r-- root/root usr/share/backgrounds/sway/Sway_Wallpaper_Blue_2048x1536.png +-rw-r--r-- root/root usr/share/backgrounds/sway/Sway_Wallpaper_Blue_2048x1536_Portrait.png +-rw-r--r-- root/root usr/share/backgrounds/sway/Sway_Wallpaper_Blue_768x1024.png +-rw-r--r-- root/root usr/share/backgrounds/sway/Sway_Wallpaper_Blue_768x1024_Portrait.png +drwxr-xr-x root/root usr/share/wayland-sessions/ +-rw-r--r-- root/root usr/share/wayland-sessions/sway.desktop diff --git a/sway/.signature b/sway/.signature new file mode 100644 index 000000000..cb8348c89 --- /dev/null +++ b/sway/.signature @@ -0,0 +1,6 @@ +untrusted comment: verify with /etc/ports/contrib.pub +RWSagIOpLGJF33u1z3MjCG1z7DS1jJCg1FIt1Qu8sLlGMI5hiyODZi1h9qy3HhnbjRKqP1/WKPDuR9dYw2RvoRXcimzRQ0wY5QM= +SHA256 (Pkgfile) = ecf73aa92fd842cf49f3db8a0ccc9a21ce99cb8920000d1b811ed2f5461fc916 +SHA256 (.footprint) = 76cf242dac27363a8a964536eda5e54f00953e79380ee493090ca249bd688920 +SHA256 (sway-1.6.1.tar.gz) = 8b138867cd83217de07138144b1279fe58e0421eb10c91122aa33c8770fe7391 +SHA256 (6249.patch) = b12f7ba4663ac296d1bd435bd2d514fad0639391c6639bac695eb6f590917dd2 diff --git a/sway/6249.patch b/sway/6249.patch new file mode 100644 index 000000000..512c0fbbd --- /dev/null +++ b/sway/6249.patch @@ -0,0 +1,1774 @@ +From f641e581a5301eeaefcb0da51904946d83240a88 Mon Sep 17 00:00:00 2001 +From: Felix Weilbach +Date: Sun, 30 May 2021 20:45:01 +0200 +Subject: [PATCH] Tray: Implement dbusmenu + +Signed-off-by: Felix Weilbach +--- + include/swaybar/input.h | 10 +- + include/swaybar/tray/dbusmenu.h | 25 + + include/swaybar/tray/item.h | 2 + + include/swaybar/tray/tray.h | 3 + + swaybar/input.c | 56 +- + swaybar/meson.build | 3 +- + swaybar/render.c | 29 +- + swaybar/tray/dbusmenu.c | 1346 +++++++++++++++++++++++++++++++ + swaybar/tray/item.c | 38 +- + 9 files changed, 1487 insertions(+), 25 deletions(-) + create mode 100644 include/swaybar/tray/dbusmenu.h + create mode 100644 swaybar/tray/dbusmenu.c + +diff --git a/include/swaybar/input.h b/include/swaybar/input.h +index e8735d883a..3dcee659cc 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,13 @@ 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, +- void *data); ++ struct swaybar_hotspot *hotspot, ++ struct swaybar_seat *seat, ++ uint32_t serial, ++ double x, ++ double y, ++ uint32_t button, ++ void *data); + void (*destroy)(void *data); + void *data; + }; +diff --git a/include/swaybar/tray/dbusmenu.h b/include/swaybar/tray/dbusmenu.h +new file mode 100644 +index 0000000000..55db61ddd9 +--- /dev/null ++++ b/include/swaybar/tray/dbusmenu.h +@@ -0,0 +1,25 @@ ++#ifndef _SWAYBAR_TRAY_DBUSMENU_H ++#define _SWAYBAR_TRAY_DBUSMENU_H ++ ++#include "swaybar/bar.h" ++#include "swaybar/tray/item.h" ++ ++struct swaybar_dbusmenu *swaybar_dbusmenu_create(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); ++ ++#endif +diff --git a/include/swaybar/tray/item.h b/include/swaybar/tray/item.h +index c02a558237..0e25cae318 100644 +--- a/include/swaybar/tray/item.h ++++ b/include/swaybar/tray/item.h +@@ -17,6 +17,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; +@@ -47,6 +48,7 @@ struct swaybar_sni { + char *icon_theme_path; // non-standard KDE property + + struct wl_list slots; // swaybar_sni_slot::link ++ char **menu_icon_theme_paths; + }; + + struct swaybar_sni *create_sni(char *id, struct swaybar_tray *tray); +diff --git a/include/swaybar/tray/tray.h b/include/swaybar/tray/tray.h +index d2e80a6d47..853f17cdc1 100644 +--- a/include/swaybar/tray/tray.h ++++ b/include/swaybar/tray/tray.h +@@ -32,6 +32,9 @@ struct swaybar_tray { + + list_t *basedirs; // char * + list_t *themes; // struct swaybar_theme * ++ ++ struct swaybar_dbusmenu *menu; ++ struct swaybar_dbusmenu_menu *menu_pointer_focus; + }; + + struct swaybar_tray *create_tray(struct swaybar *bar); +diff --git a/swaybar/input.c b/swaybar/input.c +index 6e13f17732..65c33db864 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) { +@@ -97,8 +101,8 @@ void update_cursor(struct swaybar_seat *seat) { + } + + static void wl_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) { ++ uint32_t serial, struct wl_surface *surface, wl_fixed_t surface_x, ++ wl_fixed_t surface_y) { + struct swaybar_seat *seat = data; + struct swaybar_pointer *pointer = &seat->pointer; + seat->pointer.x = wl_fixed_to_double(surface_x); +@@ -112,19 +116,38 @@ static void wl_pointer_enter(void *data, struct wl_pointer *wl_pointer, + } + } + 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; + } + + static void wl_pointer_motion(void *data, struct wl_pointer *wl_pointer, + uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y) { ++#if HAVE_TRAY + struct swaybar_seat *seat = data; + seat->pointer.x = wl_fixed_to_double(surface_x); + seat->pointer.y = wl_fixed_to_double(surface_y); ++ ++ if (dbusmenu_pointer_motion(data, wl_pointer, time, surface_x, surface_y)) { ++ return; ++ } ++#endif + } + + static bool check_bindings(struct swaybar *bar, uint32_t button, +@@ -141,7 +164,11 @@ static bool check_bindings(struct swaybar *bar, uint32_t button, + } + + static bool process_hotspots(struct swaybar_output *output, +- double x, double y, uint32_t button) { ++ struct swaybar_seat *seat, ++ uint32_t serial, ++ double x, ++ double y, ++ uint32_t button) { + double px = x * output->scale; + double py = y * output->scale; + struct swaybar_hotspot *hotspot; +@@ -149,8 +176,14 @@ static bool process_hotspots(struct swaybar_output *output, + if (px >= hotspot->x && py >= hotspot->y + && px < hotspot->x + hotspot->width + && py < hotspot->y + hotspot->height) { +- if (HOTSPOT_IGNORE == hotspot->callback(output, hotspot, x, y, +- button, hotspot->data)) { ++ if (HOTSPOT_IGNORE == hotspot->callback(output, ++ hotspot, ++ seat, ++ serial, ++ x, ++ y, ++ button, ++ hotspot->data)) { + return true; + } + } +@@ -162,6 +195,12 @@ 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")) { +@@ -175,7 +214,7 @@ static void wl_pointer_button(void *data, struct wl_pointer *wl_pointer, + if (state != WL_POINTER_BUTTON_STATE_PRESSED) { + return; + } +- process_hotspots(output, pointer->x, pointer->y, button); ++ process_hotspots(output, seat, serial, pointer->x, pointer->y, button); + } + + static void workspace_next(struct swaybar *bar, struct swaybar_output *output, +@@ -232,7 +271,7 @@ static void process_discrete_scroll(struct swaybar_seat *seat, + return; + } + +- if (process_hotspots(output, pointer->x, pointer->y, button)) { ++ if (process_hotspots(output, seat, 0, pointer->x, pointer->y, button)) { + return; + } + +@@ -405,7 +444,8 @@ 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); ++ process_hotspots( ++ slot->output, seat, serial, slot->x, slot->y, BTN_LEFT); + } + slot->output = NULL; + } +diff --git a/swaybar/meson.build b/swaybar/meson.build +index 9feb3cd2d0..86abe93235 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 fcc8be1d6f..68cbe853a7 100644 +--- a/swaybar/render.c ++++ b/swaybar/render.c +@@ -160,8 +160,14 @@ 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, +- double x, double y, uint32_t button, void *data) { ++ struct swaybar_output *output, ++ struct swaybar_hotspot *hotspot, ++ struct swaybar_seat *seat, ++ uint32_t serial, ++ double x, ++ double y, ++ uint32_t button, ++ void *data) { + struct i3bar_block *block = data; + struct status_line *status = output->bar->status; + return i3bar_block_send_click(status, block, x, y, +@@ -296,7 +302,7 @@ static uint32_t render_status_block(struct render_context *ctx, + } else if (strncmp(block->align, "right", 5) == 0) { + offset = x_pos + width - text_width; + } else if (strncmp(block->align, "center", 6) == 0) { +- offset = x_pos + (width - text_width) / 2; ++ offset = x_pos + (width - text_width) / 2.0; + } + double text_y = height / 2.0 - text_height / 2.0; + cairo_move_to(cairo, offset, (int)floor(text_y)); +@@ -325,7 +331,7 @@ static uint32_t render_status_block(struct render_context *ctx, + } + cairo_set_source_u32(cairo, color); + if (config->sep_symbol) { +- offset = x_pos + (sep_block_width - sep_width) / 2; ++ offset = x_pos + (sep_block_width - sep_width) / 2.0; + double sep_y = height / 2.0 - sep_height / 2.0; + cairo_move_to(cairo, offset, (int)floor(sep_y)); + choose_text_aa_mode(ctx, color); +@@ -334,7 +340,7 @@ static uint32_t render_status_block(struct render_context *ctx, + } else { + cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE); + cairo_set_line_width(cairo, 1); +- cairo_move_to(cairo, x_pos + sep_block_width / 2, margin); ++ cairo_move_to(cairo, x_pos + sep_block_width / 2.0, margin); + cairo_line_to(cairo, x_pos + sep_block_width / 2, height - margin); + cairo_stroke(cairo); + } +@@ -596,8 +602,14 @@ 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, +- double x, double y, uint32_t button, void *data) { ++ struct swaybar_output *output, ++ struct swaybar_hotspot *hotspot, ++ struct swaybar_seat *seat, ++ uint32_t serial, ++ double x, ++ double y, ++ uint32_t button, ++ void *data) { + if (button != BTN_LEFT) { + return HOTSPOT_PROCESS; + } +@@ -662,7 +674,8 @@ static uint32_t render_workspace_button(struct render_context *ctx, + + double text_y = height / 2.0 - text_height / 2.0; + cairo_set_source_u32(cairo, box_colors.text); +- cairo_move_to(cairo, *x + width / 2 - text_width / 2, (int)floor(text_y)); ++ cairo_move_to( ++ cairo, *x + width / 2.0 - text_width / 2.0, (int)floor(text_y)); + choose_text_aa_mode(ctx, box_colors.text); + pango_printf(cairo, config->font, output->scale, config->pango_markup, + "%s", ws->label); +diff --git a/swaybar/tray/dbusmenu.c b/swaybar/tray/dbusmenu.c +new file mode 100644 +index 0000000000..e39699f50f +--- /dev/null ++++ b/swaybar/tray/dbusmenu.c +@@ -0,0 +1,1346 @@ ++#define _POSIX_C_SOURCE 200809L ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "list.h" ++#include "log.h" ++#include "cairo.h" ++#include "swaybar/bar.h" ++#include "swaybar/tray/tray.h" ++#include "swaybar/tray/item.h" ++#include "swaybar/config.h" ++#include "swaybar/tray/icon.h" ++#include "swaybar/input.h" ++#include "cairo_util.h" ++#include "pango.h" ++#include "background-image.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); ++ ++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 buffers[2]; ++ struct pool_buffer *current_buffer; ++ int width, height; ++}; ++ ++struct swaybar_dbusmenu_menu_item { ++ int id; ++ ++ 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; ++ ++ bool enabled; ++ bool visible; ++ bool is_separator; ++ int toggle_state; ++ char *label; ++ char *icon_name; ++ cairo_surface_t *icon_data; ++ ++ enum { MENU_NONE, MENU_CHECKMARK, MENU_RADIO } toggle_type; ++}; ++ ++struct swaybar_dbusmenu_menu { ++ int item_id; ++ // struct swaybar_dbusmenu_menu_item *parent_item; ++ struct swaybar_dbusmenu_menu *parent_menu; ++ int x, y; ++ struct swaybar_dbusmenu *dbusmenu; ++ struct swaybar_dbusmenu_surface *surface; ++ list_t *items; // struct swaybar_dbusmenu_menu_item ++ list_t *child_menus; // struct swaybar_dbusmenu_menu ++}; ++ ++struct swaybar_dbusmenu { ++ struct swaybar_sni *sni; ++ struct xdg_wm_base *wm_base; ++ struct swaybar_output *output; ++ struct swaybar_seat *seat; ++ int serial; ++ int x, y; ++ struct swaybar_dbusmenu_menu *menu; ++ struct swaybar *bar; ++ ++ bool drawing; ++}; ++ ++struct get_layout_callback_data { ++ int id; ++ struct swaybar_dbusmenu *menu; ++}; ++ ++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 ++} ++ ++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); ++} ++ ++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) { ++ 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->child_menus) { ++ for (int i = 0; i < menu->child_menus->length; ++i) { ++ close_menus(menu->child_menus->items[i]); ++ } ++ } ++ ++ close_menu(menu); ++} ++ ++static void free_items(struct swaybar_dbusmenu_menu *menu) { ++ if (!menu) { ++ return; ++ } ++ ++ if (menu->child_menus) { ++ for (int i = 0; i < menu->child_menus->length; ++i) { ++ free_items(menu->child_menus->items[i]); ++ } ++ } ++ ++ if (menu->items) { ++ for (int i = 0; i < menu->items->length; ++i) { ++ struct swaybar_dbusmenu_menu_item *item = menu->items->items[i]; ++ if (item->label) { ++ free(item->label); ++ } ++ free(item); ++ } ++ } ++} ++ ++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); ++ free_items(menu->menu); ++ xdg_wm_base_destroy(menu->wm_base); ++ 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( ++ struct swaybar_dbusmenu_menu *menu, int item_id) { ++ ++ 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; ++ } ++ } ++ return NULL; ++} ++ ++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); ++ } ++ pango_printf(cairo, config->font, 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, &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->child_menus && menu->child_menus->length > 0) { ++ ++ for (int i = 0; i < menu->child_menus->length; ++i) { ++ struct swaybar_dbusmenu_menu *child_menu = ++ menu->child_menus->items[i]; ++ if (child_menu->item_id == id) { ++ return child_menu; ++ } ++ ++ 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) { ++ ++ if (menu->dbusmenu->drawing) { ++ return; ++ } ++ menu->dbusmenu->drawing = true; ++ ++ if (menu->item_id != 0 && !menu->parent_menu) { ++ sway_log(SWAY_ERROR, ++ "Can not draw menu %d because parent menu was not drawn", ++ menu->item_id); ++ menu->dbusmenu->drawing = false; ++ return; ++ } ++ ++ // 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) { ++ menu->dbusmenu->drawing = false; ++ 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); ++ menu->dbusmenu->drawing = false; ++ return; ++ } ++ } ++ ++ cairo_surface_t *recorder = ++ cairo_recording_surface_create(CAIRO_CONTENT_COLOR_ALPHA, NULL); ++ if (!recorder) { ++ menu->dbusmenu->drawing = false; ++ return; ++ } ++ cairo_t *cairo = cairo_create(recorder); ++ if (!cairo) { ++ cairo_surface_destroy(recorder); ++ menu->dbusmenu->drawing = false; ++ return; ++ } ++ int surface_x, surface_y, surface_width, surface_height; ++ 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); ++ menu->dbusmenu->drawing = false; ++ 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) { ++ 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->wm_base, surface); ++ struct xdg_positioner *positioner = ++ xdg_wm_base_create_positioner(menu->dbusmenu->wm_base); ++ ++ struct swaybar_dbusmenu_menu_item *item = find_item( ++ !menu->parent_menu ? menu : menu->parent_menu, 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, ++ y + item->hotspot.height / output->scale, 1, 1); ++ } ++ ++ struct xdg_popup *xdg_popup; ++ if (!menu->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, ++ menu->parent_menu->surface->xdg_surface, 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, NULL); ++ wl_surface_commit(surface); ++ ++ wl_display_roundtrip(bar->display); ++ xdg_positioner_destroy(positioner); ++ ++ 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 = menu->surface; ++ 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, surface_width, surface_height); ++ wl_surface_commit(surface); ++ ++ menu->dbusmenu->drawing = false; ++} ++ ++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); ++ ++ if (!dbusmenu->sni->tray->menu_pointer_focus) { ++ // First shown ++ dbusmenu->sni->tray->menu_pointer_focus = ++ find_menu_id(dbusmenu->menu, id); ++ } ++} ++ ++struct png_stream { ++ const void *data; ++ size_t left; ++}; ++ ++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 = malloc(sizeof(struct png_stream)); ++ 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); ++ free(png_stream); ++ if (cairo_surface_status(surface) == CAIRO_STATUS_SUCCESS) { ++ return surface; ++ } else { ++ 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); ++ } else { ++ 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) { ++ return; ++ } ++ ++ if (menu->surface) { ++ // Menu 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, 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 (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); ++ free_items(dbusmenu->menu); ++ dbusmenu->menu = NULL; ++ } ++ ++ struct swaybar_dbusmenu_menu_item *parent_item = NULL; ++ dbusmenu->menu = calloc(1, sizeof(struct swaybar_dbusmenu_menu)); ++ if (!dbusmenu->menu) { ++ sway_log(SWAY_ERROR, "Could not allocate menu"); ++ return -ENOMEM; ++ } ++ struct swaybar_dbusmenu_menu *menu = dbusmenu->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) { ++ item->submenu->parent_menu = menu; ++ if (!menu->child_menus) { ++ menu->child_menus = create_list(); ++ } ++ list_add(menu->child_menus, item->submenu); ++ menu = item->submenu; ++ } ++ ++ sd_bus_message_enter_container(msg, 'a', "v"); ++ ++ parent_item = item; ++ bool pop_menu = false; ++ while (parent_item && sd_bus_message_at_end(msg, 0)) { ++ 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 (pop_menu) { ++ menu = menu->parent_menu; ++ } ++ pop_menu = true; ++ } ++ ++ 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) { ++ struct swaybar_sni_slot *slot = calloc(1, sizeof(struct swaybar_sni_slot)); ++ 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); ++} ++ ++static void handle_global(void *data, struct wl_registry *registry, ++ uint32_t name, const char *interface, uint32_t version) { ++ struct swaybar_dbusmenu *menu = data; ++ if (strcmp(interface, xdg_wm_base_interface.name) == 0) { ++ menu->wm_base = ++ wl_registry_bind(registry, name, &xdg_wm_base_interface, 1); ++ } ++} ++ ++static void handle_global_remove(void *data, struct wl_registry *registry, ++ uint32_t name) { ++ // intentionally left blank ++} ++ ++static const struct wl_registry_listener registry_listener = { ++ .global = handle_global, ++ .global_remove = handle_global_remove, ++}; ++ ++struct swaybar_dbusmenu *swaybar_dbusmenu_create(struct swaybar_sni *sni, ++ struct swaybar_output *output, struct swaybar_seat *seat, ++ uint32_t serial, int x, int y) { ++ struct swaybar_dbusmenu *dbusmenu = ++ calloc(1, sizeof(struct swaybar_dbusmenu)); ++ ++ if (!dbusmenu) { ++ sway_log(SWAY_DEBUG, "Could not allocate dbusmenu"); ++ return NULL; ++ } ++ ++ 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; ++ struct wl_registry *registry = ++ wl_display_get_registry(sni->tray->bar->display); ++ wl_registry_add_listener(registry, ®istry_listener, dbusmenu); ++ wl_display_roundtrip(sni->tray->bar->display); ++ ++ swaybar_dbusmenu_setup(dbusmenu); ++ ++ return dbusmenu; ++} ++ ++static void close_child_menus(struct swaybar_dbusmenu_menu *menu) { ++ if (!menu || !menu->child_menus) { ++ return; ++ } ++ ++ for (int i = 0; i < menu->child_menus->length; ++i) { ++ close_menus(menu->child_menus->items[i]); ++ } ++} ++ ++static void open_close_child_menu(struct swaybar_dbusmenu_menu *menu, ++ struct swaybar_dbusmenu_menu_item *item, int x, int y) { ++ ++ bool in_hotspot = is_in_hotspot(&item->hotspot, x, y); ++ ++ if (item->submenu && in_hotspot) { ++ if (item->id == 0) { ++ // No need to open the root menu ++ return; ++ } ++ open_menu_id(menu->dbusmenu, item->id); ++ } else if (in_hotspot && !item->submenu) { ++ close_child_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; ++ 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); ++ } ++ ++ if (!focused_menu->dbusmenu->drawing) { ++ open_close_child_menu(focused_menu, item, x, y); ++ } ++} ++ ++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); ++ } ++ ++ swaybar_dbusmenu_draw_menu(focused_menu, focused_menu->item_id, false); ++ ++ 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->child_menus) { ++ return NULL; ++ } ++ ++ for (int i = 0; i < menu->child_menus->length; ++i) { ++ struct swaybar_dbusmenu_menu *child_menu = menu->child_menus->items[i]; ++ if (child_menu->surface && child_menu->surface->surface == surface) { ++ return child_menu; ++ } ++ ++ 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_id(struct swaybar_dbusmenu_menu *menu, int item_id) { ++ for (int j = 0; j < menu->child_menus->length; ++j) { ++ struct swaybar_dbusmenu_menu *child_menu = menu->child_menus->items[j]; ++ if (child_menu->item_id == item_id) { ++ close_menus(child_menu); ++ } ++ } ++} ++ ++static void close_child_menus_outside_pointer( ++ 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 && !is_in_hotspot(&item->hotspot, x, y)) { ++ close_menus_id(menu, item->id); ++ } ++ } ++} ++ ++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); ++ ++ if (new_focused_menu && new_focused_menu->child_menus) { ++ close_child_menus_outside_pointer(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; ++ struct swaybar_dbusmenu_menu *focused_menu = tray->menu_pointer_focus; ++ if (!(tray && tray->menu && focused_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 (!tray->menu->drawing) { ++ 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) { ++ if (!tray->menu->drawing) { ++ 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 19f4beacaa..247eab33cc 100644 +--- a/swaybar/tray/item.c ++++ b/swaybar/tray/item.c +@@ -16,6 +16,7 @@ + #include "cairo_util.h" + #include "list.h" + #include "log.h" ++#include "swaybar/tray/dbusmenu.h" + #include "wlr-layer-shell-unstable-v1-client-protocol.h" + + // TODO menu +@@ -312,6 +313,8 @@ void destroy_sni(struct swaybar_sni *sni) { + return; + } + ++ sway_log(SWAY_DEBUG, "Destroy sni"); ++ + cairo_surface_destroy(sni->icon); + free(sni->watcher_id); + free(sni->service); +@@ -333,8 +336,14 @@ 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 +374,11 @@ static void handle_click(struct swaybar_sni *sni, int x, int y, + method = "ContextMenu"; + } + +- if (strncmp(method, "Scroll", strlen("Scroll")) == 0) { ++ if (sni->menu && strcmp(method, "ContextMenu") == 0) { ++ if (!sni->tray->menu) { ++ swaybar_dbusmenu_create(sni, output, seat, serial, x, y); ++ } ++ } else if (strncmp(method, "Scroll", strlen("Scroll")) == 0) { + char dir = method[strlen("Scroll")]; + char *orientation = (dir == 'U' || dir == 'D') ? "vertical" : "horizontal"; + int sign = (dir == 'U' || dir == 'L') ? -1 : 1; +@@ -384,8 +397,14 @@ 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, +- double x, double y, uint32_t button, void *data) { ++ struct swaybar_output *output, ++ struct swaybar_hotspot *hotspot, ++ struct swaybar_seat *seat, ++ uint32_t serial, ++ double x, ++ double y, ++ uint32_t button, ++ void *data) { + sway_log(SWAY_DEBUG, "Clicked on %s", (char *)data); + + struct swaybar_tray *tray = output->bar->tray; +@@ -401,7 +420,14 @@ 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 ++ handle_click(sni, ++ output, ++ seat, ++ serial, ++ global_x, ++ global_y, ++ button, ++ 1); // TODO get delta from event + return HOTSPOT_IGNORE; + } else { + sway_log(SWAY_DEBUG, "but it doesn't exist"); diff --git a/sway/Pkgfile b/sway/Pkgfile new file mode 100644 index 000000000..52c2caf0f --- /dev/null +++ b/sway/Pkgfile @@ -0,0 +1,32 @@ +# Description: An i3-compatible wayland compositor. +# URL: https://swaywm.org/ +# Maintainer: Tim Biermann, tbier at posteo dot de +# Depends on: graphite2 json-c pango wlroots +# Optional: basu gdk-pixbuf scdoc seatd + +name=sway +version=1.6.1 +release=5 +source=(https://github.com/swaywm/sway/archive/$version/$name-$version.tar.gz + 6249.patch) + +build() { + patch -Np1 -d $name-$version -i $SRC/6249.patch + + prt-get isinst bash-completion || PKGMK_SWAY+=' -D bash-completions=false' + prt-get isinst zsh || PKGMK_SWAY+=' -D zsh-completions=false' + [[ -e /usr/bin/Xwayland ]] && PKGMK_SWAY+=' -D xwayland=enabled' || PKGMK_SWAY+=' -D xwayland=disabled' + + meson setup $name-$version build $PKGMK_SWAY \ + --prefix=/usr \ + --buildtype=plain \ + --wrap-mode nodownload \ + -D b_lto=true \ + -D b_pie=true \ + -D tray=enabled \ + -D sd-bus-provider=basu \ + -D fish-completions=false \ + -D werror=false + meson compile -C build + DESTDIR=$PKG meson install -C build +}