diff --git a/flake.lock b/flake.lock index fe5d0bb..febb002 100755 --- a/flake.lock +++ b/flake.lock @@ -10,11 +10,11 @@ ] }, "locked": { - "lastModified": 1708539149, - "narHash": "sha256-LgkOe+y8Lk7LjB6MzKZ6enM8NPahdM7Gdg1es4Zi3IU=", + "lastModified": 1709561402, + "narHash": "sha256-rPGuqkPmaeLz+pHxoPkUw/nANLB9uX3OgYoP60aRJxM=", "owner": "berberman", "repo": "flakes", - "rev": "7220e84f72ac182cf71cb9f4f66c927c48fc4cf6", + "rev": "a56b8e19a8a1b8487aa0a52f42ab4dfbb2b78ca6", "type": "github" }, "original": { @@ -30,11 +30,11 @@ ] }, "locked": { - "lastModified": 1708794349, - "narHash": "sha256-jX+B1VGHT0ruHHL5RwS8L21R6miBn4B6s9iVyUJsJJY=", + "lastModified": 1709610799, + "narHash": "sha256-5jfLQx0U9hXbi2skYMGodDJkIgffrjIOgMRjZqms2QE=", "owner": "ipetkov", "repo": "crane", - "rev": "2c94ff9a6fbeb9f3ea0107f28688edbe9c81deaa", + "rev": "81c393c776d5379c030607866afef6406ca1be57", "type": "github" }, "original": { @@ -50,11 +50,11 @@ ] }, "locked": { - "lastModified": 1708910350, - "narHash": "sha256-cTuJVlOm05aQFIgGuYikgkrI61P2vTO2OfXwIRWEzUg=", + "lastModified": 1709682352, + "narHash": "sha256-71S/64RbyADT6FUVJq4WLiNbmcxFvgMsSihf/C2Hgno=", "owner": "nix-community", "repo": "disko", - "rev": "a13f36255cf4ce99cc4236a34251c2e7106e101d", + "rev": "ad5e8bd14df2e6bdb836582577dc163318617738", "type": "github" }, "original": { @@ -85,11 +85,11 @@ ] }, "locked": { - "lastModified": 1706830856, - "narHash": "sha256-a0NYyp+h9hlb7ddVz4LUn1vT/PLwqfrWYcHMvFB1xYg=", + "lastModified": 1709336216, + "narHash": "sha256-Dt/wOWeW6Sqm11Yh+2+t0dfEWxoMxGBvv3JpIocFl9E=", "owner": "hercules-ci", "repo": "flake-parts", - "rev": "b253292d9c0a5ead9bc98c4e9a26c6312e27d69f", + "rev": "f7b3c975cf067e56e7cda6cb098ebe3fb4d74ca2", "type": "github" }, "original": { @@ -145,11 +145,11 @@ ] }, "locked": { - "lastModified": 1709204054, - "narHash": "sha256-U1idK0JHs1XOfSI1APYuXi4AEADf+B+ZU4Wifc0pBHk=", + "lastModified": 1709710941, + "narHash": "sha256-4FjuX5kGQhvrxkwuB3tAcA7cqNgNW10eZ7qzePfl+kM=", "owner": "nix-community", "repo": "home-manager", - "rev": "2f3367769a93b226c467551315e9e270c3f78b15", + "rev": "3c7bacf1d42e533299c5e3baf74556a0e0ac3d0e", "type": "github" }, "original": { @@ -198,11 +198,11 @@ ] }, "locked": { - "lastModified": 1709051793, - "narHash": "sha256-4FXFBq5mN1IwN18Fd2OEF1iCZV5PC40gP2L64oyq3xQ=", + "lastModified": 1709641033, + "narHash": "sha256-Eegyvq56NiXgrDrZ6FgE1lNCD2Mi6aLY/HFOeVG8ZtI=", "owner": "nix-community", "repo": "lanzaboote", - "rev": "e761c7ee47b64debc687d8bff7599d702c893dcc", + "rev": "3881267e84cc966ded48601390e88fc13d960319", "type": "github" }, "original": { @@ -221,11 +221,11 @@ ] }, "locked": { - "lastModified": 1709015655, - "narHash": "sha256-7HWzg9JzheM03akV9Bj1hhsXv6v1YRtSkPa16XuZ11w=", + "lastModified": 1709718332, + "narHash": "sha256-xBfSV65VVEHMjthvEij1Bjka5OFR1/YhYE8kcnDyJUo=", "owner": "Guanran928", "repo": "nvim", - "rev": "ce9dffb2865405740026bb26259e78863083acd7", + "rev": "79fa35d0009a10a871914131a9cc7c8ee8300c34", "type": "github" }, "original": { @@ -241,11 +241,11 @@ ] }, "locked": { - "lastModified": 1709112925, - "narHash": "sha256-5y8Dhw1HYdc+BWv+qQjJUIwc+ByoudtoGaHEcrXYlXw=", + "lastModified": 1709554374, + "narHash": "sha256-1yYgwxBzia+QrOaQaZ6YKqGFfiQcSBwYLzd9XRsRLQY=", "owner": "LnL7", "repo": "nix-darwin", - "rev": "283d59778e6b8c41cac4bdeac5b2512d6de51150", + "rev": "daa03606dfb5296a22e842acb02b46c1c4e9f5e7", "type": "github" }, "original": { @@ -264,11 +264,11 @@ ] }, "locked": { - "lastModified": 1709168939, - "narHash": "sha256-btWt7NRsA8Hysgin5su77nIdmZZbxsKW5qqyj/bBlF0=", + "lastModified": 1709428394, + "narHash": "sha256-WIDKofwDWFLHtk14MdBiRiM8Lxn+OByHYtybjSU/18Q=", "owner": "fufexan", "repo": "nix-gaming", - "rev": "22586cc4910284c9c7306f19dcd63ac0adbcbcc9", + "rev": "1f3fa3b398051c815e8500d4c4fe794ecfdee328", "type": "github" }, "original": { @@ -284,11 +284,11 @@ ] }, "locked": { - "lastModified": 1709156363, - "narHash": "sha256-syrzpHREwzhdlt88mL/gjNcbbHuYD470e0fjFyhZ0ho=", + "lastModified": 1709674747, + "narHash": "sha256-itIAFkMPQ6o7SBc5WGp71DABLkgSSHcaObG8Zg1SppA=", "owner": "jacekszymanski", "repo": "nixcasks", - "rev": "012549dd04474182308b18991538840455ad62aa", + "rev": "954a0d92b13435a186693671b569ac1fff3ba0e6", "type": "github" }, "original": { @@ -299,11 +299,11 @@ }, "nixos-hardware": { "locked": { - "lastModified": 1709147990, - "narHash": "sha256-vpXMWoaCtMYJ7lisJedCRhQG9BSsInEyZnnG5GfY9tQ=", + "lastModified": 1709410583, + "narHash": "sha256-esOSUoQ7mblwcsSea0K17McZuwAIjoS6dq/4b83+lvw=", "owner": "NixOS", "repo": "nixos-hardware", - "rev": "33a97b5814d36ddd65ad678ad07ce43b1a67f159", + "rev": "59e37017b9ed31dee303dbbd4531c594df95cfbc", "type": "github" }, "original": { @@ -314,11 +314,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1709200309, - "narHash": "sha256-lKdtMbhnBNU1lr978T+wEYet3sfIXXgyiDZNEgx8CV8=", + "lastModified": 1709641832, + "narHash": "sha256-bzzRc3DiV8Cm/67HDa39pyBymqF45ISgUbXqjrMk2UE=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "ebe6e807793e7c9cc59cf81225fdee1a03413811", + "rev": "bfa8b30043892dc2b660d403faa159bab7b65898", "type": "github" }, "original": { @@ -330,11 +330,11 @@ }, "nixpkgs-stable": { "locked": { - "lastModified": 1709128929, - "narHash": "sha256-GWrv9a+AgGhG4/eI/CyVVIIygia7cEy68Huv3P8oyaw=", + "lastModified": 1709569716, + "narHash": "sha256-iOR44RU4jQ+YPGrn+uQeYAp7Xo7Z/+gT+wXJoGxxLTY=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "c8e74c2f83fe12b4e5a8bd1abbc090575b0f7611", + "rev": "617579a787259b9a6419492eaac670a5f7663917", "type": "github" }, "original": { @@ -346,11 +346,11 @@ }, "nur": { "locked": { - "lastModified": 1709225079, - "narHash": "sha256-2bmOKg/aIgmguPZz3OwjG5lCAGaLwM9ubBkXjcp7edU=", + "lastModified": 1709714153, + "narHash": "sha256-2QrzVuItf+IXwVwYNfOA/XB37c6IgPg5cZLLnUJE28I=", "owner": "nix-community", "repo": "NUR", - "rev": "c52cf9478c981de340151638d76c3c2371eb9ebe", + "rev": "466d739cfde240577a1e201889e489f55bbda6c7", "type": "github" }, "original": { @@ -455,11 +455,11 @@ ] }, "locked": { - "lastModified": 1709172595, - "narHash": "sha256-0oYeE5VkhnPA7YBl+0Utq2cYoHcfsEhSGwraCa27Vs8=", + "lastModified": 1709691047, + "narHash": "sha256-2Vwx1FLufoMEcOS8KAwP8H83IP3Hw6ZPrIDHkSXrFCY=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "72fa0217f76020ad3aeb2dd9dd72490905b23b6f", + "rev": "d55139f3061cdf2c8f5f7bc8d49e884826e6a4ea", "type": "github" }, "original": { @@ -478,11 +478,11 @@ ] }, "locked": { - "lastModified": 1708987867, - "narHash": "sha256-k2lDaDWNTU5sBVHanYzjDKVDmk29RHIgdbbXu5sdzBA=", + "lastModified": 1709711091, + "narHash": "sha256-L0rSIU9IguTG4YqSj4B/02SyTEz55ACq5t8gXpzteYc=", "owner": "Mic92", "repo": "sops-nix", - "rev": "a1c8de14f60924fafe13aea66b46157f0150f4cf", + "rev": "25dd60fdd08fcacee2567a26ba6b91fe098941dc", "type": "github" }, "original": { diff --git a/overlays/default.nix b/overlays/default.nix index 7d783f8..a546ab9 100644 --- a/overlays/default.nix +++ b/overlays/default.nix @@ -6,5 +6,5 @@ let in { nautilus = import ./nautilus.nix {inherit addPatches;}; prismlauncher = import ./prismlauncher.nix {inherit addPatches;}; - sway = import ./sway; + sway = import ./sway/git; } diff --git a/overlays/sway/_1.8.nix b/overlays/sway/_1.8.nix deleted file mode 100644 index 48465a6..0000000 --- a/overlays/sway/_1.8.nix +++ /dev/null @@ -1,24 +0,0 @@ -{addPatches, ...}: _final: prev: { - sway-unwrapped = addPatches prev.sway-unwrapped [ - # text_input: Implement input-method popups - # https://github.com/swaywm/sway/pull/7226 - (prev.fetchpatch { - url = "https://github.com/swaywm/sway/commit/d1c6e44886d1047b3aa6ff6aaac383eadd72f36a.patch"; - hash = "sha256-LsCoK60FKp3d8qopGtrbCFXofxHT+kOv1e1PiLSyvsA="; - }) - - # Tray D-Bus Menu - # https://github.com/swaywm/sway/pull/6249 - (prev.substitute { - src = prev.fetchpatch { - url = "https://github.com/swaywm/sway/commit/2f304ef0532a45d00b2ec2c7fc63adef0aec7607.patch"; - hash = "sha256-nd+Z6A7GE5Go7QxXOI+hiLWQiXegsQatcNfxEsXgamI="; - }; - substitutions = [ - "--replace-warn" - "int surface_x, surface_y, surface_width, surface_height;" - "int surface_x, surface_y, surface_width, surface_height = 0;" - ]; - }) - ]; -} diff --git a/overlays/sway/_1.9.nix b/overlays/sway/_1.9.nix deleted file mode 100644 index 6363c24..0000000 --- a/overlays/sway/_1.9.nix +++ /dev/null @@ -1,32 +0,0 @@ -_final: prev: { - sway-unwrapped = prev.sway-unwrapped.overrideAttrs rec { - version = "1.9"; - - src = prev.fetchFromGitHub { - owner = "swaywm"; - repo = "sway"; - rev = version; - hash = "sha256-/6+iDkQfdLcL/pTJaqNc6QdP4SRVOYLjfOItEu/bZtg"; - }; - - buildInputs = with prev; [ - cairo - gdk-pixbuf - json_c - libGL - libdrm - libevdev - libinput - librsvg - libxkbcommon - pango - pcre2 - wayland - wayland-protocols - wlroots_0_17 - xorg.xcbutilwm - ]; - - patches = []; - }; -} diff --git a/overlays/sway/_1.9/0001-Tray-Implement-dbusmenu.patch b/overlays/sway/_1.9/0001-Tray-Implement-dbusmenu.patch new file mode 100644 index 0000000..a1b5ee6 --- /dev/null +++ b/overlays/sway/_1.9/0001-Tray-Implement-dbusmenu.patch @@ -0,0 +1,1754 @@ +From 44ebf74ca6e02855b5974149b65c65362336f16f Mon Sep 17 00:00:00 2001 +From: Felix Weilbach +Date: Sun, 30 May 2021 20:45:01 +0200 +Subject: [PATCH] Tray: Implement dbusmenu + +Co-authored-by: Ian Fan +Co-authored-by: Nathan Schulte + +Signed-off-by: Felix Weilbach +--- + include/swaybar/bar.h | 1 + + include/swaybar/input.h | 5 +- + include/swaybar/tray/dbusmenu.h | 27 + + include/swaybar/tray/item.h | 2 + + include/swaybar/tray/tray.h | 3 + + swaybar/bar.c | 4 + + swaybar/input.c | 49 +- + swaybar/meson.build | 3 +- + swaybar/render.c | 2 + + swaybar/tray/dbusmenu.c | 1367 +++++++++++++++++++++++++++++++ + swaybar/tray/item.c | 16 +- + 11 files changed, 1468 insertions(+), 11 deletions(-) + create mode 100644 include/swaybar/tray/dbusmenu.h + create mode 100644 swaybar/tray/dbusmenu.c + +diff --git a/include/swaybar/bar.h b/include/swaybar/bar.h +index 197d2190..d0b3dc81 100644 +--- a/include/swaybar/bar.h ++++ b/include/swaybar/bar.h +@@ -33,6 +33,7 @@ struct swaybar { + struct zxdg_output_manager_v1 *xdg_output_manager; + struct wp_cursor_shape_manager_v1 *cursor_shape_manager; + struct wl_shm *shm; ++ struct xdg_wm_base *wm_base; + + struct swaybar_config *config; + struct status_line *status; +diff --git a/include/swaybar/input.h b/include/swaybar/input.h +index 8ea88a69..81ccaa98 100644 +--- a/include/swaybar/input.h ++++ b/include/swaybar/input.h +@@ -15,6 +15,7 @@ + + struct swaybar; + struct swaybar_output; ++struct swaybar_seat; + + struct swaybar_pointer { + struct wl_pointer *pointer; +@@ -48,8 +49,8 @@ struct swaybar_hotspot { + struct wl_list link; // swaybar_output::hotspots + int x, y, width, height; + enum hotspot_event_handling (*callback)(struct swaybar_output *output, +- struct swaybar_hotspot *hotspot, double x, double y, uint32_t button, +- bool released, void *data); ++ struct swaybar_hotspot *hotspot, struct swaybar_seat *seat, uint32_t serial, ++ double x, double y, uint32_t button, bool released, void *data); + void (*destroy)(void *data); + void *data; + }; +diff --git a/include/swaybar/tray/dbusmenu.h b/include/swaybar/tray/dbusmenu.h +new file mode 100644 +index 00000000..dc90f6e5 +--- /dev/null ++++ b/include/swaybar/tray/dbusmenu.h +@@ -0,0 +1,27 @@ ++#ifndef _SWAYBAR_TRAY_DBUSMENU_H ++#define _SWAYBAR_TRAY_DBUSMENU_H ++ ++#include "swaybar/bar.h" ++#include "swaybar/tray/item.h" ++ ++void swaybar_dbusmenu_open(struct swaybar_sni *sni, ++ struct swaybar_output *output, struct swaybar_seat *seat, uint32_t serial, ++ int x, int y); ++ ++bool dbusmenu_pointer_button(void *data, struct wl_pointer *wl_pointer, ++ uint32_t serial, uint32_t time_, uint32_t button, uint32_t state); ++ ++bool dbusmenu_pointer_motion(struct swaybar_seat *seat, struct wl_pointer *wl_pointer, ++ uint32_t time_, wl_fixed_t surface_x, wl_fixed_t surface_y); ++ ++bool dbusmenu_pointer_enter(void *data, struct wl_pointer *wl_pointer, uint32_t serial, ++ struct wl_surface *surface, wl_fixed_t surface_x, wl_fixed_t surface_y); ++ ++bool dbusmenu_pointer_leave(void *data, struct wl_pointer *wl_pointer, uint32_t serial, ++ struct wl_surface *surface); ++ ++bool dbusmenu_pointer_frame(struct swaybar_seat *data, struct wl_pointer *wl_pointer); ++ ++bool dbusmenu_pointer_axis(struct swaybar_seat *data, struct wl_pointer *wl_pointer); ++ ++#endif +diff --git a/include/swaybar/tray/item.h b/include/swaybar/tray/item.h +index 73937a0c..9a4a00ff 100644 +--- a/include/swaybar/tray/item.h ++++ b/include/swaybar/tray/item.h +@@ -18,6 +18,7 @@ struct swaybar_pixmap { + struct swaybar_sni_slot { + struct wl_list link; // swaybar_sni::slots + struct swaybar_sni *sni; ++ int menu_id; + const char *prop; + const char *type; + void *dest; +@@ -48,6 +49,7 @@ struct swaybar_sni { + char *icon_theme_path; // non-standard KDE property + + struct wl_list slots; // swaybar_sni_slot::link ++ char **menu_icon_theme_paths; + }; + + struct swaybar_sni *create_sni(char *id, struct swaybar_tray *tray); +diff --git a/include/swaybar/tray/tray.h b/include/swaybar/tray/tray.h +index d2e80a6d..853f17cd 100644 +--- a/include/swaybar/tray/tray.h ++++ b/include/swaybar/tray/tray.h +@@ -32,6 +32,9 @@ struct swaybar_tray { + + list_t *basedirs; // char * + list_t *themes; // struct swaybar_theme * ++ ++ struct swaybar_dbusmenu *menu; ++ struct swaybar_dbusmenu_menu *menu_pointer_focus; + }; + + struct swaybar_tray *create_tray(struct swaybar *bar); +diff --git a/swaybar/bar.c b/swaybar/bar.c +index 021fc3bd..9c83db3e 100644 +--- a/swaybar/bar.c ++++ b/swaybar/bar.c +@@ -29,6 +29,7 @@ + #include "pool-buffer.h" + #include "wlr-layer-shell-unstable-v1-client-protocol.h" + #include "xdg-output-unstable-v1-client-protocol.h" ++#include "xdg-shell-client-protocol.h" + + void free_workspaces(struct wl_list *list) { + struct swaybar_workspace *ws, *tmp; +@@ -365,6 +366,8 @@ static void handle_global(void *data, struct wl_registry *registry, + } else if (strcmp(interface, wp_cursor_shape_manager_v1_interface.name) == 0) { + bar->cursor_shape_manager = wl_registry_bind(registry, name, + &wp_cursor_shape_manager_v1_interface, 1); ++ } else if (strcmp(interface, xdg_wm_base_interface.name) == 0) { ++ bar->wm_base = wl_registry_bind(registry, name, &xdg_wm_base_interface, 1); + } + } + +@@ -539,6 +542,7 @@ void bar_teardown(struct swaybar *bar) { + #if HAVE_TRAY + destroy_tray(bar->tray); + #endif ++ xdg_wm_base_destroy(bar->wm_base); + free_outputs(&bar->outputs); + free_outputs(&bar->unused_outputs); + free_seats(&bar->seats); +diff --git a/swaybar/input.c b/swaybar/input.c +index ada4bc86..dfac5480 100644 +--- a/swaybar/input.c ++++ b/swaybar/input.c +@@ -10,6 +10,10 @@ + #include "swaybar/input.h" + #include "swaybar/ipc.h" + ++#if HAVE_TRAY ++#include "swaybar/tray/dbusmenu.h" ++#endif ++ + void free_hotspots(struct wl_list *list) { + struct swaybar_hotspot *hotspot, *tmp; + wl_list_for_each_safe(hotspot, tmp, list, link) { +@@ -131,10 +135,23 @@ static void wl_pointer_enter(void *data, struct wl_pointer *wl_pointer, + pointer->serial = serial; + update_cursor(seat); + } ++ ++#if HAVE_TRAY ++ if (dbusmenu_pointer_enter(data, wl_pointer, serial, surface, surface_x, ++ surface_y)) { ++ return; ++ } ++#endif + } + + static void wl_pointer_leave(void *data, struct wl_pointer *wl_pointer, + uint32_t serial, struct wl_surface *surface) { ++#if HAVE_TRAY ++ if (dbusmenu_pointer_leave(data, wl_pointer, serial, surface)) { ++ return; ++ } ++#endif ++ + struct swaybar_seat *seat = data; + seat->pointer.current = NULL; + } +@@ -144,6 +161,11 @@ static void wl_pointer_motion(void *data, struct wl_pointer *wl_pointer, + struct swaybar_seat *seat = data; + seat->pointer.x = wl_fixed_to_double(surface_x); + seat->pointer.y = wl_fixed_to_double(surface_y); ++#if HAVE_TRAY ++ if (dbusmenu_pointer_motion(data, wl_pointer, time, surface_x, surface_y)) { ++ return; ++ } ++#endif + } + + static bool check_bindings(struct swaybar *bar, uint32_t button, +@@ -160,6 +182,7 @@ static bool check_bindings(struct swaybar *bar, uint32_t button, + } + + static bool process_hotspots(struct swaybar_output *output, ++ struct swaybar_seat *seat, uint32_t serial, + double x, double y, uint32_t button, uint32_t state) { + bool released = state == WL_POINTER_BUTTON_STATE_RELEASED; + struct swaybar_hotspot *hotspot; +@@ -167,7 +190,7 @@ static bool process_hotspots(struct swaybar_output *output, + if (x >= hotspot->x && y >= hotspot->y + && x < hotspot->x + hotspot->width + && y < hotspot->y + hotspot->height) { +- if (HOTSPOT_IGNORE == hotspot->callback(output, hotspot, x, y, ++ if (HOTSPOT_IGNORE == hotspot->callback(output, hotspot, seat, serial, x, y, + button, released, hotspot->data)) { + return true; + } +@@ -180,13 +203,19 @@ static bool process_hotspots(struct swaybar_output *output, + static void wl_pointer_button(void *data, struct wl_pointer *wl_pointer, + uint32_t serial, uint32_t time, uint32_t button, uint32_t state) { + struct swaybar_seat *seat = data; ++#if HAVE_TRAY ++ if (dbusmenu_pointer_button(seat, wl_pointer, serial, time, button, ++ state)) { ++ return; ++ } ++#endif + struct swaybar_pointer *pointer = &seat->pointer; + struct swaybar_output *output = pointer->current; + if (!sway_assert(output, "button with no active output")) { + return; + } + +- if (process_hotspots(output, pointer->x, pointer->y, button, state)) { ++ if (process_hotspots(output, seat, serial, pointer->x, pointer->y, button, state)) { + return; + } + +@@ -240,7 +269,7 @@ static void process_discrete_scroll(struct swaybar_seat *seat, + struct swaybar_output *output, struct swaybar_pointer *pointer, + uint32_t axis, wl_fixed_t value) { + uint32_t button = wl_axis_to_button(axis, value); +- if (process_hotspots(output, pointer->x, pointer->y, button, WL_POINTER_BUTTON_STATE_PRESSED)) { ++ if (process_hotspots(output, seat, 0, pointer->x, pointer->y, button, WL_POINTER_BUTTON_STATE_PRESSED)) { + // (Currently hotspots don't do anything on release events, so no need to emit one) + return; + } +@@ -297,6 +326,12 @@ static void wl_pointer_axis(void *data, struct wl_pointer *wl_pointer, + return; + } + ++#if HAVE_TRAY ++ if (dbusmenu_pointer_axis(data, wl_pointer)) { ++ return; ++ } ++#endif ++ + // If there's a while since the last scroll event, + // set 'value' to zero as if to reset the "virtual scroll wheel" + if (seat->axis[axis].discrete_steps == 0 && +@@ -313,6 +348,12 @@ static void wl_pointer_frame(void *data, struct wl_pointer *wl_pointer) { + struct swaybar_pointer *pointer = &seat->pointer; + struct swaybar_output *output = pointer->current; + ++#if HAVE_TRAY ++ if (dbusmenu_pointer_frame(data, wl_pointer)) { ++ return; ++ } ++#endif ++ + if (output == NULL) { + return; + } +@@ -420,7 +461,7 @@ static void wl_touch_up(void *data, struct wl_touch *wl_touch, + } + if (time - slot->time < 500) { + // Tap, treat it like a pointer click +- process_hotspots(slot->output, slot->x, slot->y, BTN_LEFT, WL_POINTER_BUTTON_STATE_PRESSED); ++ process_hotspots(slot->output, seat, serial, slot->x, slot->y, BTN_LEFT, WL_POINTER_BUTTON_STATE_PRESSED); + // (Currently hotspots don't do anything on release events, so no need to emit one) + } + slot->output = NULL; +diff --git a/swaybar/meson.build b/swaybar/meson.build +index e5f1811e..fef1ee77 100644 +--- a/swaybar/meson.build ++++ b/swaybar/meson.build +@@ -3,7 +3,8 @@ tray_files = have_tray ? [ + 'tray/icon.c', + 'tray/item.c', + 'tray/tray.c', +- 'tray/watcher.c' ++ 'tray/watcher.c', ++ 'tray/dbusmenu.c' + ] : [] + + swaybar_deps = [ +diff --git a/swaybar/render.c b/swaybar/render.c +index 1113ca44..7769063d 100644 +--- a/swaybar/render.c ++++ b/swaybar/render.c +@@ -160,6 +160,7 @@ static void render_sharp_line(cairo_t *cairo, uint32_t color, + + static enum hotspot_event_handling block_hotspot_callback( + struct swaybar_output *output, struct swaybar_hotspot *hotspot, ++ struct swaybar_seat *seat, uint32_t serial, + double x, double y, uint32_t button, bool released, void *data) { + struct i3bar_block *block = data; + struct status_line *status = output->bar->status; +@@ -599,6 +600,7 @@ static uint32_t render_binding_mode_indicator(struct render_context *ctx, + + static enum hotspot_event_handling workspace_hotspot_callback( + struct swaybar_output *output, struct swaybar_hotspot *hotspot, ++ struct swaybar_seat *seat, uint32_t serial, + double x, double y, uint32_t button, bool released, void *data) { + if (button != BTN_LEFT) { + return HOTSPOT_PROCESS; +diff --git a/swaybar/tray/dbusmenu.c b/swaybar/tray/dbusmenu.c +new file mode 100644 +index 00000000..423abc42 +--- /dev/null ++++ b/swaybar/tray/dbusmenu.c +@@ -0,0 +1,1367 @@ ++#define _POSIX_C_SOURCE 200809L ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "background-image.h" ++#include "cairo.h" ++#include "cairo_util.h" ++#include "list.h" ++#include "log.h" ++#include "pango.h" ++#include "swaybar/bar.h" ++#include "swaybar/config.h" ++#include "swaybar/input.h" ++#include "swaybar/tray/icon.h" ++#include "swaybar/tray/item.h" ++#include "swaybar/tray/tray.h" ++ ++static const char *menu_interface = "com.canonical.dbusmenu"; ++ ++static void swaybar_dbusmenu_get_layout_root(struct swaybar_dbusmenu *menu); ++static void swaybar_dbusmenu_get_layout(struct swaybar_dbusmenu *menu, int id); ++static void swaybar_dbusmenu_draw(struct swaybar_dbusmenu *dbusmenu, int id); ++ ++struct swaybar_dbusmenu_hotspot { ++ int x, y, width, height; ++}; ++ ++struct swaybar_dbusmenu_surface { ++ struct xdg_popup *xdg_popup; ++ struct xdg_surface *xdg_surface; ++ struct wl_surface *surface; ++ struct pool_buffer *current_buffer; ++ struct pool_buffer buffers[2]; ++ int width, height; ++ bool configured; ++}; ++ ++enum menu_toggle_type { ++ MENU_NONE, ++ MENU_CHECKMARK, ++ MENU_RADIO ++}; ++ ++struct swaybar_dbusmenu_menu_item { ++ struct swaybar_dbusmenu_hotspot hotspot; ++ // Set if the item has a submenu ++ struct swaybar_dbusmenu_menu *submenu; ++ // The menu in which the item is displayed ++ struct swaybar_dbusmenu_menu *menu; ++ struct swaybar_dbusmenu_menu_item *parent_item; ++ int id; ++ int toggle_state; ++ char *label; ++ char *icon_name; ++ cairo_surface_t *icon_data; ++ enum menu_toggle_type toggle_type; ++ bool enabled; ++ bool visible; ++ bool is_separator; ++}; ++ ++struct swaybar_dbusmenu_menu { ++ struct swaybar_dbusmenu *dbusmenu; ++ struct swaybar_dbusmenu_surface *surface; ++ struct swaybar_dbusmenu_menu_item *last_hovered_item; ++ list_t *items; // struct swaybar_dbusmenu_menu_item ++ int item_id; ++ int x, y; ++}; ++ ++struct swaybar_dbusmenu { ++ struct swaybar_sni *sni; ++ struct swaybar_output *output; ++ struct swaybar_seat *seat; ++ struct swaybar_dbusmenu_menu *menu; ++ struct swaybar *bar; ++ int serial; ++ int x, y; ++}; ++ ++struct get_layout_callback_data { ++ struct swaybar_dbusmenu *menu; ++ int id; ++}; ++ ++struct png_stream { ++ const void *data; ++ size_t left; ++}; ++ ++static void commit_menu_surface(struct swaybar_dbusmenu_menu *menu) ++{ ++ struct swaybar_dbusmenu_surface * dbusmenu_surface = menu->surface; ++ if (!dbusmenu_surface->configured || dbusmenu_surface->current_buffer == NULL) { ++ return; ++ } ++ ++ struct wl_surface *surface = dbusmenu_surface->surface; ++ wl_surface_set_buffer_scale(surface, menu->dbusmenu->output->scale); ++ wl_surface_attach(surface, dbusmenu_surface->current_buffer->buffer, 0, 0); ++ wl_surface_damage(surface, 0, 0, dbusmenu_surface->width, dbusmenu_surface->height); ++ wl_surface_commit(surface); ++} ++ ++static int handle_items_properties_updated(sd_bus_message *msg, void *data, ++ sd_bus_error *error) { ++ struct swaybar_sni *sni = data; ++ sway_log(SWAY_DEBUG, "%s%s item properties updated", sni->service, sni->menu); ++ ++ // TODO: Optimize. Update only needed properties ++ if (sni->tray->menu) { ++ swaybar_dbusmenu_get_layout_root(sni->tray->menu); ++ } ++ return 0; ++} ++ ++static int handle_layout_updated(sd_bus_message *msg, void *data, ++ sd_bus_error *error) { ++ struct swaybar_sni *sni = data; ++ sway_log(SWAY_DEBUG, "%s%s layout updated", sni->service, sni->menu); ++ ++ int id; ++ sd_bus_message_read(msg, "ui", NULL, &id); ++ if (sni->tray->menu) { ++ swaybar_dbusmenu_get_layout(sni->tray->menu, id); ++ } ++ return 0; ++} ++ ++static int handle_item_activation_requested(sd_bus_message *msg, void *data, ++ sd_bus_error *error) { ++ return 0; // TODO: Implement handling of hotkeys for opening the menu ++} ++ ++static struct swaybar_dbusmenu_surface *swaybar_dbusmenu_surface_create() { ++ struct swaybar_dbusmenu_surface *dbusmenu = calloc(1, ++ sizeof(struct swaybar_dbusmenu_surface)); ++ if (!dbusmenu) { ++ sway_log(SWAY_DEBUG, "Could not allocate dbusmenu"); ++ } ++ return dbusmenu; ++} ++ ++static void xdg_surface_handle_configure(void *data, ++ struct xdg_surface *xdg_surface, uint32_t serial) { ++ xdg_surface_ack_configure(xdg_surface, serial); ++ ++ struct swaybar_dbusmenu_menu *menu = data; ++ menu->surface->configured = true; ++ commit_menu_surface(menu); ++} ++ ++static const struct xdg_surface_listener xdg_surface_listener = { ++ .configure = xdg_surface_handle_configure, ++}; ++ ++static void xdg_popup_configure(void *data, struct xdg_popup *xdg_popup, ++ int32_t x, int32_t y, int32_t width, int32_t height) { ++ // intentionally left blank ++} ++ ++static void destroy_dbusmenu_surface( ++ struct swaybar_dbusmenu_surface *dbusmenu_surface) { ++ if (!dbusmenu_surface) { ++ return; ++ } ++ ++ if (dbusmenu_surface->xdg_popup) { ++ xdg_popup_destroy(dbusmenu_surface->xdg_popup); ++ dbusmenu_surface->xdg_popup = NULL; ++ } ++ if (dbusmenu_surface->surface) { ++ xdg_surface_destroy(dbusmenu_surface->xdg_surface); ++ wl_surface_destroy(dbusmenu_surface->surface); ++ dbusmenu_surface->surface = NULL; ++ } ++ destroy_buffer(&dbusmenu_surface->buffers[0]); ++ destroy_buffer(&dbusmenu_surface->buffers[1]); ++ ++ free(dbusmenu_surface); ++} ++ ++static void close_menu(struct swaybar_dbusmenu_menu *menu) { ++ if (!menu) { ++ return; ++ } ++ ++ if (menu->surface) { ++ destroy_dbusmenu_surface(menu->surface); ++ menu->surface = NULL; ++ ++ int id = menu->item_id; ++ struct swaybar_sni *sni = menu->dbusmenu->sni; ++ sd_bus_call_method_async(sni->tray->bus, NULL, sni->service, sni->menu, ++ menu_interface, "Event", NULL, NULL, "isvu", id, "closed", "y", ++ 0, time(NULL)); ++ sway_log(SWAY_DEBUG, "%s%s closed id %d", sni->service, sni->menu, id); ++ } ++} ++ ++static void close_menus(struct swaybar_dbusmenu_menu *menu) { ++ if (!menu) { ++ return; ++ } ++ ++ if (menu->items) { ++ for (int i = 0; i < menu->items->length; ++i) { ++ struct swaybar_dbusmenu_menu_item *item = menu->items->items[i]; ++ if (item->submenu && item->submenu->item_id != 0) { ++ close_menus(item->submenu); ++ } ++ } ++ } ++ ++ close_menu(menu); ++} ++ ++static void swaybar_dbusmenu_menu_destroy(struct swaybar_dbusmenu_menu *menu) { ++ if (!menu) { ++ return; ++ } ++ ++ if (menu->items) { ++ for (int i = 0; i < menu->items->length; ++i) { ++ struct swaybar_dbusmenu_menu_item *item = menu->items->items[i]; ++ struct swaybar_dbusmenu_menu *child_menu = item->submenu; ++ if (child_menu && child_menu->item_id != 0) { ++ swaybar_dbusmenu_menu_destroy(item->submenu); ++ } ++ free(item->label); ++ free(item->icon_name); ++ free(item->icon_data); ++ free(item); ++ } ++ } ++ list_free(menu->items); ++ free(menu); ++} ++ ++void swaybar_dbusmenu_destroy(struct swaybar_dbusmenu *menu) { ++ if (!menu) { ++ return; ++ } ++ ++ menu->sni->tray->menu = NULL; ++ menu->sni->tray->menu_pointer_focus = NULL; ++ ++ close_menus(menu->menu); ++ swaybar_dbusmenu_menu_destroy(menu->menu); ++ free(menu); ++} ++ ++static void xdg_popup_done(void *data, struct xdg_popup *xdg_popup) { ++ struct swaybar_dbusmenu_menu *menu = data; ++ swaybar_dbusmenu_destroy(menu->dbusmenu); ++} ++ ++static const struct xdg_popup_listener xdg_popup_listener = { ++ .configure = xdg_popup_configure, .popup_done = xdg_popup_done}; ++ ++static struct swaybar_dbusmenu_menu_item * ++find_item_under_menu(struct swaybar_dbusmenu_menu *menu, int item_id) { ++ if (!menu->items) { ++ return NULL; ++ } ++ ++ for (int i = 0; i < menu->items->length; ++i) { ++ struct swaybar_dbusmenu_menu_item *item = menu->items->items[i]; ++ if (item->id == item_id) { ++ return item; ++ } ++ if (item->submenu && item->submenu->item_id != 0) { ++ struct swaybar_dbusmenu_menu_item *found_item = ++ find_item_under_menu(item->submenu, item_id); ++ if (found_item) { ++ return found_item; ++ } ++ } ++ } ++ ++ return NULL; ++} ++ ++static struct swaybar_dbusmenu_menu_item * ++find_item(struct swaybar_dbusmenu *dbusmenu, int item_id) { ++ if (!dbusmenu->menu) { ++ return NULL; ++ } ++ ++ return find_item_under_menu(dbusmenu->menu, item_id); ++} ++ ++static struct swaybar_dbusmenu_menu * ++find_parent_menu_under_menu(struct swaybar_dbusmenu_menu *menu, ++ struct swaybar_dbusmenu_menu *child_menu) { ++ if (!menu->items) { ++ return NULL; ++ } ++ ++ for (int i = 0; i < menu->items->length; ++i) { ++ struct swaybar_dbusmenu_menu_item *item = menu->items->items[i]; ++ struct swaybar_dbusmenu_menu *maybe_child_menu = item->submenu; ++ if (maybe_child_menu && maybe_child_menu->item_id != 0) { ++ if (maybe_child_menu == child_menu) { ++ return menu; ++ } ++ maybe_child_menu = find_parent_menu_under_menu(maybe_child_menu, child_menu); ++ if (maybe_child_menu) { ++ return maybe_child_menu; ++ } ++ } ++ } ++ ++ return NULL; ++} ++ ++static struct swaybar_dbusmenu_menu * ++find_parent_menu(struct swaybar_dbusmenu_menu *menu) { ++ if (menu && menu->item_id == 0) { ++ return NULL; ++ } ++ struct swaybar_dbusmenu_menu *root_menu = menu->dbusmenu->menu; ++ return find_parent_menu_under_menu(root_menu, menu); ++} ++ ++static bool is_in_hotspot(struct swaybar_dbusmenu_hotspot *hotspot, int x, int y) { ++ if (!hotspot) { ++ return false; ++ } ++ ++ if (hotspot->x <= x && x < hotspot->x + hotspot->width && hotspot->y <= y && ++ y < hotspot->y + hotspot->height) { ++ return true; ++ } ++ ++ return false; ++} ++ ++static void draw_menu_items(cairo_t *cairo, struct swaybar_dbusmenu_menu *menu, ++ int *surface_x, int *surface_y, int *surface_width, int *surface_height, ++ bool open) { ++ struct swaybar_sni *sni = menu->dbusmenu->sni; ++ struct swaybar_tray *tray = sni->tray; ++ struct swaybar_output *output = menu->dbusmenu->output; ++ struct swaybar_config *config = menu->dbusmenu->output->bar->config; ++ ++ int padding = config->tray_padding * output->scale; ++ ++ list_t *items = menu->items; ++ int height = 0; ++ ++ *surface_y = 0; ++ *surface_x = 0; ++ *surface_width = 0; ++ bool is_icon_drawn = false; ++ int icon_size = 0; ++ ++ for (int i = 0; i < items->length; ++i) { ++ struct swaybar_dbusmenu_menu_item *item = items->items[i]; ++ ++ if (!item->visible) { ++ continue; ++ } ++ ++ int new_height = height; ++ if (item->is_separator) { ++ // drawn later, after the width is known ++ new_height = height + output->scale; ++ } else if (item->label) { ++ cairo_move_to(cairo, padding, height + padding); ++ ++ // draw label ++ if (item->enabled) { ++ cairo_set_source_u32(cairo, config->colors.focused_statusline); ++ } else { ++ uint32_t c = config->colors.focused_statusline; ++ uint32_t disabled_color = c - ((c & 0xFF) >> 1); ++ cairo_set_source_u32(cairo, disabled_color); ++ } ++ render_text(cairo, config->font_description, output->scale, false, "%s", ++ item->label); ++ ++ // draw icon or menu indicator if needed ++ int text_height; ++ int text_width; ++ get_text_size(cairo, config->font_description, &text_width, &text_height, ++ NULL, output->scale, false, "%s", item->label); ++ text_width += padding; ++ int size = text_height; ++ int x = -2 * padding - size; ++ int y = height + padding; ++ icon_size = 2 * padding + size; ++ cairo_set_source_u32(cairo, config->colors.focused_statusline); ++ if (item->icon_name) { ++ list_t *icon_search_paths = create_list(); ++ list_cat(icon_search_paths, tray->basedirs); ++ if (sni->menu_icon_theme_paths) { ++ for (char **path = sni->menu_icon_theme_paths; *path; ++path) { ++ list_add(icon_search_paths, *path); ++ } ++ } ++ if (sni->icon_theme_path) { ++ list_add(icon_search_paths, sni->icon_theme_path); ++ } ++ int min_size, max_size; ++ char *icon_path = ++ find_icon(tray->themes, icon_search_paths, item->icon_name, size, ++ config->icon_theme, &min_size, &max_size); ++ list_free(icon_search_paths); ++ ++ if (icon_path) { ++ cairo_surface_t *icon = load_background_image(icon_path); ++ free(icon_path); ++ cairo_surface_t *icon_scaled = ++ cairo_image_surface_scale(icon, size, size); ++ cairo_surface_destroy(icon); ++ ++ cairo_set_source_surface(cairo, icon_scaled, x, y); ++ cairo_rectangle(cairo, x, y, size, size); ++ cairo_fill(cairo); ++ cairo_surface_destroy(icon_scaled); ++ is_icon_drawn = true; ++ } ++ } else if (item->icon_data) { ++ cairo_surface_t *icon = cairo_image_surface_scale(item->icon_data, size, size); ++ cairo_set_source_surface(cairo, icon, x, y); ++ cairo_rectangle(cairo, x, y, size, size); ++ cairo_fill(cairo); ++ cairo_surface_destroy(icon); ++ is_icon_drawn = true; ++ } else if (item->toggle_type == MENU_CHECKMARK) { ++ cairo_rectangle(cairo, x, y, size, size); ++ cairo_fill(cairo); ++ cairo_set_operator(cairo, CAIRO_OPERATOR_CLEAR); ++ if (item->toggle_state == 1) { // tick ++ cairo_move_to(cairo, x + size * 3.0 / 4, y + size * 5.0 / 16.0); ++ cairo_line_to(cairo, x + size * 3.0 / 8, y + size * 11.0 / 16.0); ++ cairo_line_to(cairo, x + size / 4.0, y + size * 9.0 / 16.0); ++ cairo_stroke(cairo); ++ } else if (item->toggle_state != 0) { // horizontal line ++ cairo_rectangle(cairo, x + size / 4.0, y + size / 2.0 - 1, ++ size / 2.0, 2); ++ cairo_fill(cairo); ++ } ++ cairo_set_operator(cairo, CAIRO_OPERATOR_OVER); ++ is_icon_drawn = true; ++ } else if (item->toggle_type == MENU_RADIO) { ++ cairo_arc(cairo, x + size / 2.0, y + size / 2.0, size / 2.0, 0, 7); ++ cairo_fill(cairo); ++ if (item->toggle_state == 1) { ++ cairo_set_operator(cairo, CAIRO_OPERATOR_CLEAR); ++ cairo_arc(cairo, x + size / 2.0, y + size / 2.0, size / 4.0, 0, 7); ++ cairo_fill(cairo); ++ cairo_set_operator(cairo, CAIRO_OPERATOR_OVER); ++ } ++ is_icon_drawn = true; ++ } else if (item->submenu) { // arrowhead ++ cairo_move_to(cairo, x + size / 4.0, y + size / 2.0); ++ cairo_line_to(cairo, x + size * 3.0 / 4, y + size / 4.0); ++ cairo_line_to(cairo, x + size * 3.0 / 4, y + size * 3.0 / 4); ++ cairo_fill(cairo); ++ is_icon_drawn = true; ++ } ++ ++ *surface_width = *surface_width < text_width ? text_width : *surface_width; ++ new_height = height + text_height + 2 * padding; ++ } else { ++ continue; ++ } ++ ++ struct swaybar_dbusmenu_hotspot *hotspot = &item->hotspot; ++ hotspot->y = height; ++ ++ hotspot->y = height; ++ hotspot->height = new_height - height; ++ // x and width is not known at the moment ++ ++ height = new_height; ++ } ++ ++ if (height == 0) { ++ return; ++ } ++ ++ if (is_icon_drawn) { ++ *surface_x = -icon_size - padding; ++ *surface_width += icon_size + padding; ++ } ++ ++ *surface_width += padding; ++ *surface_height = height; ++ ++ // Make sure height and width are divideable by scale ++ // otherwise the menu will not showup ++ if (*surface_width % output->scale != 0) { ++ *surface_width -= *surface_width % output->scale; ++ } ++ if (*surface_height % output->scale != 0) { ++ *surface_height -= *surface_height % output->scale; ++ } ++ ++ cairo_set_line_width(cairo, output->scale); ++ cairo_set_source_u32(cairo, config->colors.focused_separator); ++ for (int i = 0; i < items->length; ++i) { ++ struct swaybar_dbusmenu_menu_item *item = items->items[i]; ++ struct swaybar_dbusmenu_hotspot *hotspot = &item->hotspot; ++ hotspot->x = 0; ++ hotspot->width = *surface_width; ++ if (item->is_separator) { ++ int y = hotspot->y + hotspot->height / 2.0; ++ cairo_move_to(cairo, *surface_x, y); ++ cairo_line_to(cairo, *surface_x + *surface_width, y); ++ cairo_stroke(cairo); ++ } else if (!open && item->enabled && ++ is_in_hotspot(hotspot, ++ tray->menu->seat->pointer.x * output->scale, ++ tray->menu->seat->pointer.y * output->scale)) { ++ cairo_save(cairo); ++ cairo_set_operator(cairo, CAIRO_OPERATOR_DEST_OVER); ++ cairo_rectangle(cairo, *surface_x, hotspot->y, *surface_width, ++ hotspot->height); ++ cairo_set_source_u32(cairo, ++ sni->tray->bar->config->colors.focused_separator); ++ cairo_fill(cairo); ++ cairo_restore(cairo); ++ } ++ } ++} ++ ++struct swaybar_dbusmenu_menu *find_menu_id(struct swaybar_dbusmenu_menu *menu, ++ int id) { ++ if (!menu) { ++ return NULL; ++ } ++ if (menu->item_id == id) { ++ return menu; ++ } ++ ++ if (menu->items) { ++ for (int i = 0; i < menu->items->length; ++i) { ++ struct swaybar_dbusmenu_menu_item *item = menu->items->items[i]; ++ struct swaybar_dbusmenu_menu *child_menu = item->submenu; ++ if (child_menu) { ++ if (child_menu->item_id == id) { ++ return child_menu; ++ } ++ if (child_menu->item_id == 0) { ++ continue; ++ } ++ struct swaybar_dbusmenu_menu *child_child_menu = find_menu_id(child_menu, id); ++ if (child_child_menu) { ++ return child_child_menu; ++ } ++ } ++ } ++ } ++ ++ return NULL; ++} ++ ++static void swaybar_dbusmenu_draw_menu(struct swaybar_dbusmenu_menu *menu, ++ int id, bool open) { ++ // For now just search for menu with id ++ struct swaybar_tray *tray = menu->dbusmenu->sni->tray; ++ menu = find_menu_id(menu->dbusmenu->menu, id); ++ if (!menu) { ++ return; ++ } ++ ++ if (!menu->surface) { ++ menu->surface = swaybar_dbusmenu_surface_create(); ++ if (!menu->surface) { ++ sway_log(SWAY_ERROR, "Could not create surface for menu %d", menu->item_id); ++ return; ++ } ++ } ++ ++ cairo_surface_t *recorder = ++ cairo_recording_surface_create(CAIRO_CONTENT_COLOR_ALPHA, NULL); ++ if (!recorder) { ++ return; ++ } ++ cairo_t *cairo = cairo_create(recorder); ++ if (!cairo) { ++ cairo_surface_destroy(recorder); ++ return; ++ } ++ int surface_x, surface_y, surface_width, surface_height = 0; ++ draw_menu_items(cairo, menu, &surface_x, &surface_y, &surface_width, ++ &surface_height, open); ++ ++ struct swaybar *bar = menu->dbusmenu->sni->tray->bar; ++ struct swaybar_dbusmenu_surface *dbusmenu_surface = menu->surface; ++ dbusmenu_surface->current_buffer = get_next_buffer( ++ bar->shm, dbusmenu_surface->buffers, surface_width, surface_height); ++ ++ if (!dbusmenu_surface->current_buffer) { ++ cairo_surface_destroy(recorder); ++ cairo_destroy(cairo); ++ return; ++ } ++ ++ cairo_t *shm = dbusmenu_surface->current_buffer->cairo; ++ cairo_set_operator(shm, CAIRO_OPERATOR_SOURCE); ++ cairo_set_source_u32( ++ shm, menu->dbusmenu->sni->tray->bar->config->colors.focused_background); ++ cairo_paint(shm); ++ ++ cairo_set_operator(shm, CAIRO_OPERATOR_OVER); ++ cairo_set_source_surface(shm, recorder, -surface_x, -surface_y); ++ cairo_paint(shm); ++ ++ cairo_surface_destroy(recorder); ++ cairo_destroy(cairo); ++ ++ if (dbusmenu_surface->width != surface_width || ++ dbusmenu_surface->height != surface_height) { ++ if (dbusmenu_surface->surface) { ++ xdg_surface_destroy(dbusmenu_surface->xdg_surface); ++ dbusmenu_surface->xdg_surface = NULL; ++ wl_surface_destroy(dbusmenu_surface->surface); ++ dbusmenu_surface->surface = NULL; ++ sway_log(SWAY_DEBUG, "Destroy xdg popup"); ++ xdg_popup_destroy(dbusmenu_surface->xdg_popup); ++ dbusmenu_surface->xdg_popup = NULL; ++ } ++ ++ // configure & position popup surface ++ struct wl_surface *surface = wl_compositor_create_surface(bar->compositor); ++ struct xdg_surface *xdg_surface = ++ xdg_wm_base_get_xdg_surface(menu->dbusmenu->bar->wm_base, surface); ++ struct xdg_positioner *positioner = ++ xdg_wm_base_create_positioner(menu->dbusmenu->bar->wm_base); ++ ++ // find the menu item (if any) which requested to open this submenu ++ // to find out on which x and y coordinate the submenu should be drawn ++ struct swaybar_dbusmenu_menu_item *item = ++ find_item(menu->dbusmenu, menu->item_id); ++ struct swaybar_output *output = menu->dbusmenu->output; ++ int x = menu->item_id == 0 ? menu->dbusmenu->x ++ : item->hotspot.x / output->scale; ++ int y = menu->item_id == 0 ? menu->dbusmenu->y ++ : item->hotspot.y / output->scale; ++ ++ xdg_positioner_set_offset(positioner, 0, 0); ++ // Need to divide through scale because surface width/height is scaled ++ xdg_positioner_set_size(positioner, surface_width / output->scale, ++ surface_height / output->scale); ++ ++ int padding = (tray->bar->config->tray_padding * output->scale) / 2; ++ if (bar->config->position & ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP) { // top bar ++ xdg_positioner_set_anchor(positioner, XDG_POSITIONER_ANCHOR_BOTTOM_LEFT); ++ xdg_positioner_set_gravity(positioner, XDG_POSITIONER_GRAVITY_BOTTOM_LEFT); ++ xdg_positioner_set_anchor_rect(positioner, x, y - padding, 1, 1); ++ } else { ++ xdg_positioner_set_anchor(positioner, XDG_POSITIONER_ANCHOR_TOP_LEFT); ++ xdg_positioner_set_gravity(positioner, XDG_POSITIONER_GRAVITY_TOP_LEFT); ++ xdg_positioner_set_anchor_rect( ++ positioner, x, item->hotspot.height / output->scale, 1, 1); ++ } ++ ++ struct xdg_popup *xdg_popup; ++ struct swaybar_dbusmenu_menu *parent_menu = find_parent_menu(menu); ++ if (!parent_menu) { ++ // Top level menu ++ xdg_popup = xdg_surface_get_popup(xdg_surface, NULL, positioner); ++ zwlr_layer_surface_v1_get_popup(output->layer_surface, xdg_popup); ++ } else { ++ // Nested menu ++ xdg_popup = xdg_surface_get_popup( ++ xdg_surface, parent_menu->surface->xdg_surface, positioner); ++ } ++ xdg_positioner_destroy(positioner); ++ ++ xdg_popup_grab(xdg_popup, menu->dbusmenu->seat->wl_seat, ++ menu->dbusmenu->serial); ++ xdg_popup_add_listener(xdg_popup, &xdg_popup_listener, menu); ++ xdg_surface_add_listener(xdg_surface, &xdg_surface_listener, menu); ++ wl_surface_commit(surface); ++ ++ dbusmenu_surface->xdg_popup = xdg_popup; ++ dbusmenu_surface->xdg_surface = xdg_surface; ++ dbusmenu_surface->surface = surface; ++ dbusmenu_surface->width = surface_width; ++ dbusmenu_surface->height = surface_height; ++ dbusmenu_surface->configured = false; ++ } ++ ++ commit_menu_surface(menu); ++} ++ ++static void swaybar_dbusmenu_draw(struct swaybar_dbusmenu *dbusmenu, int id) { ++ if (!dbusmenu || !dbusmenu->menu) { ++ sway_log(SWAY_ERROR, "Can not draw dbusmenu, menu structure not initialized yet!"); ++ return; ++ } ++ swaybar_dbusmenu_draw_menu(dbusmenu->menu, id, true); ++} ++ ++static cairo_status_t read_png_stream(void *closure, unsigned char *data, ++ unsigned int length) { ++ struct png_stream *png_stream = closure; ++ if (length > png_stream->left) { ++ return CAIRO_STATUS_READ_ERROR; ++ } ++ memcpy(data, png_stream->data, length); ++ png_stream->data += length; ++ png_stream->left -= length; ++ return CAIRO_STATUS_SUCCESS; ++} ++ ++static cairo_surface_t *read_png(const void *data, size_t data_size) { ++ struct png_stream png_stream = {0}; ++ png_stream.data = data; ++ png_stream.left = data_size; ++ cairo_surface_t *surface = ++ cairo_image_surface_create_from_png_stream(read_png_stream, &png_stream); ++ ++ if (cairo_surface_status(surface) == CAIRO_STATUS_SUCCESS) { ++ return surface; ++ } ++ ++ cairo_surface_destroy(surface); ++ return NULL; ++} ++ ++static int about_to_show_callback(sd_bus_message *msg, void *data, ++ sd_bus_error *error) { ++ struct swaybar_sni_slot *slot = data; ++ struct swaybar_sni *sni = slot->sni; ++ int menu_id = slot->menu_id; ++ wl_list_remove(&slot->link); ++ free(slot); ++ ++ int need_update; ++ sd_bus_message_read_basic(msg, 'b', &need_update); ++ if (need_update) { ++ swaybar_dbusmenu_get_layout(sni->tray->menu, menu_id); ++ } ++ ++ swaybar_dbusmenu_draw(sni->tray->menu, menu_id); ++ ++ sd_bus_call_method_async(sni->tray->bus, NULL, sni->service, sni->menu, ++ menu_interface, "Event", NULL, NULL, "isvu", menu_id, "opened", "y", 0, ++ time(NULL)); ++ ++ sway_log(SWAY_DEBUG, "%s%s opened id %d", sni->service, sni->menu, menu_id); ++ ++ return 0; ++} ++ ++static void open_menu_id(struct swaybar_dbusmenu *dbusmenu, int menu_id) { ++ struct swaybar_dbusmenu_menu *menu = find_menu_id(dbusmenu->menu, menu_id); ++ if (!menu || menu->surface) { ++ // menu could not be found or is already shown ++ return; ++ } ++ ++ struct swaybar_sni *sni = dbusmenu->sni; ++ struct swaybar_sni_slot *slot = calloc(1, sizeof(struct swaybar_sni_slot)); ++ slot->sni = sni; ++ slot->menu_id = menu_id; ++ ++ int ret = sd_bus_call_method_async(sni->tray->bus, &slot->slot, sni->service, ++ sni->menu, menu_interface, "AboutToShow", about_to_show_callback, slot, "i", ++ menu_id); ++ ++ if (ret >= 0) { ++ wl_list_insert(&sni->slots, &slot->link); ++ } else { ++ sway_log(SWAY_ERROR, "%s%s failed to send AboutToShow signal: %s", ++ sni->service, sni->menu, strerror(-ret)); ++ free(slot); ++ } ++} ++ ++static int update_item_properties(struct swaybar_dbusmenu_menu_item *item, ++ sd_bus_message *msg) { ++ sd_bus_message_enter_container(msg, 'a', "{sv}"); ++ while (!sd_bus_message_at_end(msg, 0)) { ++ sd_bus_message_enter_container(msg, 'e', "sv"); ++ char *key, *log_value; ++ sd_bus_message_read_basic(msg, 's', &key); ++ if (strcmp(key, "type") == 0) { ++ char *type; ++ sd_bus_message_read(msg, "v", "s", &type); ++ item->is_separator = strcmp(type, "separator") == 0; ++ log_value = type; ++ } else if (strcmp(key, "label") == 0) { ++ char *label; ++ sd_bus_message_read(msg, "v", "s", &label); ++ item->label = realloc(item->label, strlen(label) + 1); ++ if (!item->label) { ++ return -ENOMEM; ++ } ++ int i = 0; ++ for (char *c = label; *c; ++c) { ++ if (*c == '_' && !*++c) { ++ break; ++ } ++ item->label[i++] = *c; ++ } ++ item->label[i] = '\0'; ++ log_value = label; ++ } else if (strcmp(key, "enabled") == 0) { ++ int enabled; ++ sd_bus_message_read(msg, "v", "b", &enabled); ++ item->enabled = enabled; ++ log_value = item->enabled ? "true" : "false"; ++ } else if (strcmp(key, "visible") == 0) { ++ int visible; ++ sd_bus_message_read(msg, "v", "b", &visible); ++ item->visible = visible; ++ log_value = item->visible ? "true" : "false"; ++ } else if (strcmp(key, "icon-name") == 0) { ++ sd_bus_message_read(msg, "v", "s", &item->icon_name); ++ item->icon_name = strdup(item->icon_name); ++ log_value = item->icon_name; ++ } else if (strcmp(key, "icon-data") == 0) { ++ const void *data; ++ size_t data_size; ++ sd_bus_message_enter_container(msg, 'v', "ay"); ++ sd_bus_message_read_array(msg, 'y', &data, &data_size); ++ sd_bus_message_exit_container(msg); ++ item->icon_data = read_png(data, data_size); ++ log_value = item->icon_data ? "" : ""; ++ } else if (strcmp(key, "toggle-type") == 0) { ++ char *toggle_type; ++ sd_bus_message_read(msg, "v", "s", &toggle_type); ++ if (strcmp(toggle_type, "checkmark") == 0) { ++ item->toggle_type = MENU_CHECKMARK; ++ } else if (strcmp(toggle_type, "radio") == 0) { ++ item->toggle_type = MENU_RADIO; ++ } ++ log_value = toggle_type; ++ } else if (strcmp(key, "toggle-state") == 0) { ++ sd_bus_message_read(msg, "v", "i", &item->toggle_state); ++ log_value = item->toggle_state == 0 ? ++ "off" : item->toggle_state == 1 ? "on" : "indeterminate"; ++ } else if (strcmp(key, "children-display") == 0) { ++ char *children_display; ++ sd_bus_message_read(msg, "v", "s", &children_display); ++ if (strcmp(children_display, "submenu") == 0) { ++ struct swaybar_dbusmenu_menu *submenu; ++ if (item->id != 0) { ++ submenu = calloc(1, sizeof(struct swaybar_dbusmenu_menu)); ++ if (!submenu) { ++ sway_log(SWAY_ERROR, "Could not allocate submenu"); ++ return -ENOMEM; ++ } ++ } else { ++ submenu = item->menu; ++ } ++ submenu->item_id = item->id; ++ submenu->dbusmenu = item->menu->dbusmenu; ++ item->submenu = submenu; ++ } ++ log_value = children_display; ++ } else { ++ // Ignored: shortcut, disposition ++ sd_bus_message_skip(msg, "v"); ++ log_value = ""; ++ } ++ sd_bus_message_exit_container(msg); ++ sway_log(SWAY_DEBUG, "%s%s %s = '%s'", item->menu->dbusmenu->sni->service, ++ item->menu->dbusmenu->sni->menu, key, log_value); ++ } ++ return sd_bus_message_exit_container(msg); ++} ++ ++static int get_layout_callback(sd_bus_message *msg, void *data, ++ sd_bus_error *error) { ++ struct swaybar_sni_slot *slot = data; ++ struct swaybar_sni *sni = slot->sni; ++ int menu_id = slot->menu_id; ++ wl_list_remove(&slot->link); ++ free(slot); ++ ++ struct swaybar_dbusmenu *dbusmenu = sni->tray->menu; ++ if (dbusmenu == NULL) { ++ return 0; ++ } ++ ++ if (sd_bus_message_is_method_error(msg, NULL)) { ++ sway_log(SWAY_ERROR, "%s%s failed to get layout: %s", ++ dbusmenu->sni->service, dbusmenu->sni->menu, ++ sd_bus_message_get_error(msg)->message); ++ return sd_bus_message_get_errno(msg); ++ } ++ ++ // Parse the layout. The layout comes as a recursive structure as ++ // dbus message in the following form (ia{sv}av) ++ ++ // Skip the menu revision ++ sd_bus_message_skip(msg, "u"); ++ ++ sni->tray->menu_pointer_focus = NULL; ++ ++ bool already_open = false; ++ struct swaybar_dbusmenu_menu *menu_to_update = ++ find_menu_id(dbusmenu->menu, menu_id); ++ if (menu_to_update && menu_to_update->surface) { ++ already_open = true; ++ } ++ ++ if (dbusmenu->menu) { ++ close_menus(dbusmenu->menu); ++ swaybar_dbusmenu_menu_destroy(dbusmenu->menu); ++ dbusmenu->menu = NULL; ++ } ++ ++ struct swaybar_dbusmenu_menu_item *parent_item = NULL; ++ struct swaybar_dbusmenu_menu *menu = calloc(1, ++ sizeof(struct swaybar_dbusmenu_menu)); ++ if (!menu) { ++ sway_log(SWAY_ERROR, "Could not allocate menu"); ++ return -ENOMEM; ++ } ++ dbusmenu->menu = menu; ++ menu->dbusmenu = dbusmenu; ++ int ret = 0; ++ while (!sd_bus_message_at_end(msg, 1)) { ++ sd_bus_message_enter_container(msg, 'r', "ia{sv}av"); ++ ++ struct swaybar_dbusmenu_menu_item *item ++ = calloc(1, sizeof(struct swaybar_dbusmenu_menu_item)); ++ if (!item) { ++ ret = -ENOMEM; ++ break; ++ } ++ ++ // default properties ++ item->parent_item = parent_item; ++ item->menu = menu; ++ item->enabled = true; ++ item->visible = true; ++ item->toggle_state = -1; ++ ++ // Read the id ++ sd_bus_message_read_basic(msg, 'i', &item->id); ++ ++ // Process a{sv}. a{sv} contains key-value pairs ++ ret = update_item_properties(item, msg); ++ if (!menu->items) { ++ menu->items = create_list(); ++ } ++ list_add(menu->items, item); ++ if (ret < 0) { ++ break; ++ } ++ if (item->id != 0 && item->submenu) { ++ menu = item->submenu; ++ } ++ ++ sd_bus_message_enter_container(msg, 'a', "v"); ++ ++ parent_item = item; ++ while (parent_item && sd_bus_message_at_end(msg, 0)) { ++ if (parent_item->submenu) { ++ menu = find_parent_menu(menu); ++ } ++ parent_item = parent_item->parent_item; ++ ++ sd_bus_message_exit_container(msg); ++ sd_bus_message_exit_container(msg); ++ sd_bus_message_exit_container(msg); ++ } ++ ++ if (parent_item) { ++ sd_bus_message_enter_container(msg, 'v', "(ia{sv}av)"); ++ } ++ } ++ ++ if (already_open) { ++ swaybar_dbusmenu_draw(sni->tray->menu, menu_id); ++ } else { ++ open_menu_id(dbusmenu, 0); ++ } ++ ++ return 0; ++} ++ ++static void swaybar_dbusmenu_subscribe_signal(struct swaybar_dbusmenu *menu, ++ const char *signal_name, sd_bus_message_handler_t callback) { ++ int ret = sd_bus_match_signal_async( menu->sni->tray->bus, NULL, ++ menu->sni->service, menu->sni->menu, menu_interface, signal_name, callback, ++ NULL, menu->sni); ++ ++ if (ret < 0) { ++ sway_log(SWAY_ERROR, "%s%s failed to subscribe to signal %s: %s", ++ menu->sni->service, menu->sni->menu, signal_name, strerror(-ret)); ++ } ++} ++ ++static void swaybar_dbusmenu_setup_signals(struct swaybar_dbusmenu *menu) { ++ swaybar_dbusmenu_subscribe_signal(menu, "ItemsPropertiesUpdated", ++ handle_items_properties_updated); ++ swaybar_dbusmenu_subscribe_signal(menu, "LayoutUpdated", ++ handle_layout_updated); ++ swaybar_dbusmenu_subscribe_signal(menu, "ItemActivationRequested", ++ handle_item_activation_requested); ++} ++ ++static void swaybar_dbusmenu_get_layout(struct swaybar_dbusmenu *menu, int id) { ++ if (menu == NULL) { ++ return; ++ } ++ ++ struct swaybar_sni_slot *slot = calloc(1, sizeof(struct swaybar_sni_slot)); ++ if (slot == NULL) { ++ sway_log(SWAY_ERROR, "Could not allocate swaybar_sni_slot"); ++ return; ++ } ++ slot->sni = menu->sni; ++ slot->menu_id = id; ++ ++ int ret = ++ sd_bus_call_method_async(menu->sni->tray->bus, NULL, menu->sni->service, ++ menu->sni->menu, menu_interface, "GetLayout", ++ get_layout_callback, slot, "iias", id, -1, NULL); ++ ++ if (ret >= 0) { ++ wl_list_insert(&menu->sni->slots, &slot->link); ++ } else { ++ sway_log(SWAY_ERROR, "%s%s failed to call method GetLayout: %s", ++ menu->sni->service, menu->sni->menu, strerror(-ret)); ++ free(slot); ++ } ++} ++ ++static void swaybar_dbusmenu_get_layout_root(struct swaybar_dbusmenu *menu) { ++ swaybar_dbusmenu_get_layout(menu, 0); ++} ++ ++static int get_icon_theme_path_callback(sd_bus_message *msg, void *data, ++ sd_bus_error *error) { ++ struct swaybar_sni_slot *slot = data; ++ struct swaybar_sni *sni = slot->sni; ++ wl_list_remove(&slot->link); ++ free(slot); ++ ++ int ret; ++ if (!sd_bus_message_is_method_error(msg, NULL)) { ++ ret = sd_bus_message_enter_container(msg, 'v', NULL); ++ if (ret >= 0) { ++ ret = sd_bus_message_read_strv(msg, &sni->menu_icon_theme_paths); ++ } ++ } else { ++ ret = -sd_bus_message_get_errno(msg); ++ } ++ ++ if (ret < 0) { ++ sway_log(SWAY_ERROR, "%s%s failed to read IconThemePath: %s", sni->service, ++ sni->menu, strerror(-ret)); ++ } ++ return ret; ++} ++ ++static void swaybar_dbusmenu_setup(struct swaybar_dbusmenu *menu) { ++ struct swaybar_sni_slot *slot = calloc(1, sizeof(struct swaybar_sni_slot)); ++ slot->sni = menu->sni; ++ int ret = sd_bus_call_method_async( menu->sni->tray->bus, &slot->slot, ++ menu->sni->service, menu->sni->path, "org.freedesktop.DBus.Properties", ++ "Get", get_icon_theme_path_callback, slot, "ss", menu->sni->interface, ++ "IconThemePath"); ++ if (ret >= 0) { ++ wl_list_insert(&menu->sni->slots, &slot->link); ++ } else { ++ sway_log(SWAY_ERROR, "%s%s failed to get IconThemePath: %s", ++ menu->sni->service, menu->sni->menu, strerror(-ret)); ++ free(slot); ++ } ++ ++ swaybar_dbusmenu_setup_signals(menu); ++ swaybar_dbusmenu_get_layout_root(menu); ++} ++ ++void swaybar_dbusmenu_open(struct swaybar_sni *sni, ++ struct swaybar_output *output, struct swaybar_seat *seat, uint32_t serial, ++ int x, int y) { ++ struct swaybar_dbusmenu *dbusmenu = sni->tray->menu; ++ if (!dbusmenu) { ++ dbusmenu = calloc(1, sizeof(struct swaybar_dbusmenu)); ++ if (!dbusmenu) { ++ sway_log(SWAY_DEBUG, "Could not allocate dbusmenu"); ++ return; ++ } ++ sni->tray->menu = dbusmenu; ++ } ++ ++ dbusmenu->sni = sni; ++ dbusmenu->output = output; ++ dbusmenu->seat = seat; ++ dbusmenu->serial = serial; ++ dbusmenu->x = seat->pointer.x; ++ dbusmenu->y = seat->pointer.y; ++ dbusmenu->bar = output->bar; ++ ++ swaybar_dbusmenu_setup(dbusmenu); ++} ++ ++static void close_child_menus_except(struct swaybar_dbusmenu_menu *menu, ++ int id) { ++ if (!menu || !menu->items) { ++ return; ++ } ++ // close all child menus of menu, except the child menu with the given id ++ for (int i = 0; i < menu->items->length; ++i) { ++ struct swaybar_dbusmenu_menu_item *item = menu->items->items[i]; ++ if (item->id == id) { ++ continue; ++ } ++ struct swaybar_dbusmenu_menu *menu = item->submenu; ++ if (menu && menu->item_id != 0) { ++ close_menus(menu); ++ } ++ } ++} ++ ++static void ++pointer_motion_process_item(struct swaybar_dbusmenu_menu *focused_menu, ++ struct swaybar_dbusmenu_menu_item *item, struct swaybar_seat *seat) { ++ int scale = focused_menu->dbusmenu->output->scale; ++ double x = seat->pointer.x * scale; ++ double y = seat->pointer.y * scale; ++ if (is_in_hotspot(&item->hotspot, x, y) && item->enabled && ++ !item->is_separator) { ++ struct swaybar_tray *tray = focused_menu->dbusmenu->sni->tray; ++ struct swaybar_sni *sni = tray->menu->sni; ++ if (focused_menu->last_hovered_item != item) { ++ sd_bus_call_method_async(tray->bus, NULL, sni->service, sni->menu, ++ menu_interface, "Event", NULL, NULL, "isvu", item->id, "hovered", ++ "y", 0, time(NULL)); ++ ++ sway_log(SWAY_DEBUG, "%s%s hovered id %d", sni->service, sni->menu, ++ item->id); ++ ++ // open child menu if current item has a child menu and close other ++ // potential open child menus. Only one child menu can be open at a time ++ close_child_menus_except(focused_menu, item->id); ++ open_menu_id(focused_menu->dbusmenu, item->id); ++ focused_menu->last_hovered_item = item; ++ ++ // a different item needs to be highlighted ++ swaybar_dbusmenu_draw_menu(focused_menu, focused_menu->item_id, false); ++ } ++ ++ } ++} ++ ++bool dbusmenu_pointer_motion(struct swaybar_seat *seat, ++ struct wl_pointer *wl_pointer, uint32_t time_, wl_fixed_t surface_x, ++ wl_fixed_t surface_y) { ++ struct swaybar_tray *tray = seat->bar->tray; ++ struct swaybar_dbusmenu_menu *focused_menu = tray->menu_pointer_focus; ++ if (!(tray && tray->menu && focused_menu)) { ++ return false; ++ } ++ ++ for (int i = 0; i < focused_menu->items->length; ++i) { ++ struct swaybar_dbusmenu_menu_item *item = focused_menu->items->items[i]; ++ pointer_motion_process_item(focused_menu, item, seat); ++ } ++ ++ return true; ++} ++ ++static struct swaybar_dbusmenu_menu * ++dbusmenu_menu_find_menu_surface(struct swaybar_dbusmenu_menu *menu, ++ struct wl_surface *surface) { ++ if (menu->surface && menu->surface->surface == surface) { ++ return menu; ++ } ++ if (!menu->items) { ++ return NULL; ++ } ++ for (int i = 0; i < menu->items->length; ++i) { ++ struct swaybar_dbusmenu_menu_item *item = menu->items->items[i]; ++ struct swaybar_dbusmenu_menu *child_menu = item->submenu; ++ if (child_menu && child_menu->surface ++ && child_menu->surface->surface == surface) { ++ return child_menu; ++ } ++ if (child_menu) { ++ if (child_menu->item_id == 0) { ++ continue; ++ } ++ struct swaybar_dbusmenu_menu *child_child_menu = ++ dbusmenu_menu_find_menu_surface(child_menu, surface); ++ if (child_child_menu != NULL) { ++ return child_child_menu; ++ } ++ } ++ } ++ ++ return NULL; ++} ++ ++static void close_menus_by_id(struct swaybar_dbusmenu_menu *menu, int item_id) { ++ if (menu->items == NULL) { ++ return; ++ } ++ for (int i = 0; i < menu->items->length; ++i) { ++ struct swaybar_dbusmenu_menu_item *item = menu->items->items[i]; ++ struct swaybar_dbusmenu_menu *child_menu = item->submenu; ++ if (child_menu && child_menu->item_id == item_id && child_menu->item_id != 0) { ++ close_menus(child_menu); ++ } ++ } ++} ++ ++static void close_unfocused_child_menus(struct swaybar_dbusmenu_menu *menu, ++ struct swaybar_seat *seat) { ++ for (int i = 0; i < menu->items->length; ++i) { ++ struct swaybar_dbusmenu_menu_item *item = menu->items->items[i]; ++ ++ int scale = menu->dbusmenu->output->scale; ++ int x = seat->pointer.x * scale; ++ int y = seat->pointer.y * scale; ++ if (item->submenu && item->submenu->item_id != 0 ++ && !is_in_hotspot(&item->hotspot, x, y)) { ++ close_menus_by_id(menu, item->id); ++ } ++ } ++} ++ ++bool dbusmenu_pointer_frame(struct swaybar_seat *data, ++ struct wl_pointer *wl_pointer) { ++ struct swaybar_tray *tray = data->bar->tray; ++ if (!(tray && tray->menu && tray->menu_pointer_focus)) { ++ return false; ++ } ++ return true; ++} ++ ++bool dbusmenu_pointer_axis(struct swaybar_seat *data, ++ struct wl_pointer *wl_pointer) { ++ struct swaybar_tray *tray = data->bar->tray; ++ if (!(tray && tray->menu && tray->menu_pointer_focus)) { ++ return false; ++ } ++ return true; ++} ++ ++bool dbusmenu_pointer_enter(void *data, struct wl_pointer *wl_pointer, ++ uint32_t serial, struct wl_surface *surface, wl_fixed_t surface_x, ++ wl_fixed_t surface_y) { ++ struct swaybar_seat *seat = data; ++ struct swaybar_tray *tray = seat->bar->tray; ++ if (!(tray && tray->menu)) { ++ return false; ++ } ++ ++ struct swaybar_dbusmenu_menu *new_focused_menu = ++ dbusmenu_menu_find_menu_surface(tray->menu->menu, surface); ++ ++ // Check if there are any child menus ++ bool has_child_menus = false; ++ if (new_focused_menu && new_focused_menu->items) { ++ for (int i = 0; i < new_focused_menu->items->length; ++i) { ++ struct swaybar_dbusmenu_menu_item *item = new_focused_menu->items->items[i]; ++ if (item->submenu && item->submenu->item_id != 0) { ++ has_child_menus = true; ++ } ++ } ++ } ++ ++ if (has_child_menus) { ++ close_unfocused_child_menus(new_focused_menu, seat); ++ } ++ ++ tray->menu_pointer_focus = new_focused_menu; ++ ++ return true; ++} ++ ++bool dbusmenu_pointer_leave(void *data, struct wl_pointer *wl_pointer, ++ uint32_t serial, struct wl_surface *surface) { ++ struct swaybar_seat *seat = data; ++ struct swaybar_tray *tray = seat->bar->tray; ++ if (!(tray && tray->menu)) { ++ return false; ++ } ++ ++ tray->menu_pointer_focus = NULL; ++ ++ return true; ++} ++ ++static bool dbusmenu_pointer_button_left_process_item(struct swaybar_dbusmenu *dbusmenu, ++ struct swaybar_dbusmenu_menu_item *item, struct swaybar_seat *seat) { ++ struct swaybar_sni *sni = dbusmenu->sni; ++ struct swaybar_tray *tray = sni->tray; ++ int scale = dbusmenu->output->scale; ++ ++ if (is_in_hotspot(&item->hotspot, seat->pointer.x * scale, ++ seat->pointer.y * scale)) { ++ if (!item->enabled || item->is_separator) { ++ return false; ++ } ++ ++ sway_log(SWAY_DEBUG, "%s%s menu clicked id %d", sni->service, sni->menu, ++ item->id); ++ ++ sd_bus_call_method_async(tray->bus, NULL, sni->service, sni->menu, ++ menu_interface, "Event", NULL, NULL, "isvu", item->id, "clicked", "y", 0, ++ time(NULL)); ++ ++ if (item->submenu) { ++ open_menu_id(dbusmenu, item->id); ++ } else { ++ // The user clicked an menu item other than a submenu. That means ++ // the user made it's choise. Close the tray menu. ++ swaybar_dbusmenu_destroy(tray->menu); ++ } ++ return true; ++ } ++ ++ return false; ++} ++ ++static bool dbusmenu_pointer_button_left(struct swaybar_dbusmenu *dbusmenu, ++ struct swaybar_seat *seat) { ++ struct swaybar_dbusmenu_menu *focused_menu ++ = dbusmenu->sni->tray->menu_pointer_focus; ++ ++ if (!focused_menu) { ++ return true; ++ } ++ ++ for (int i = 0; i < focused_menu->items->length; ++i) { ++ struct swaybar_dbusmenu_menu_item *item = focused_menu->items->items[i]; ++ if (dbusmenu_pointer_button_left_process_item(dbusmenu, item, seat)) { ++ return true; ++ } ++ } ++ ++ return true; ++} ++ ++bool dbusmenu_pointer_button(void *data, struct wl_pointer *wl_pointer, ++ uint32_t serial, uint32_t time_, uint32_t button, uint32_t state) { ++ struct swaybar_seat *seat = data; ++ struct swaybar_tray *tray = seat->bar->tray; ++ if (!(tray && tray->menu)) { ++ return false; ++ } ++ ++ if (state != WL_POINTER_BUTTON_STATE_PRESSED) { ++ // intentionally left blank ++ return true; ++ } else if (!tray->menu_pointer_focus) { ++ swaybar_dbusmenu_destroy(tray->menu); ++ return true; ++ } else if (button == BTN_LEFT) { ++ return dbusmenu_pointer_button_left(tray->menu, seat); ++ } ++ ++ return false; ++} +diff --git a/swaybar/tray/item.c b/swaybar/tray/item.c +index 1f18b8bb..d159640f 100644 +--- a/swaybar/tray/item.c ++++ b/swaybar/tray/item.c +@@ -8,6 +8,7 @@ + #include "swaybar/bar.h" + #include "swaybar/config.h" + #include "swaybar/input.h" ++#include "swaybar/tray/dbusmenu.h" + #include "swaybar/tray/host.h" + #include "swaybar/tray/icon.h" + #include "swaybar/tray/item.h" +@@ -333,8 +334,9 @@ void destroy_sni(struct swaybar_sni *sni) { + free(sni); + } + +-static void handle_click(struct swaybar_sni *sni, int x, int y, +- uint32_t button, int delta) { ++static void handle_click(struct swaybar_sni *sni, struct swaybar_output *output, ++ struct swaybar_seat *seat, uint32_t serial, int x, int y, uint32_t button, ++ int delta) { + const char *method = NULL; + struct tray_binding *binding = NULL; + wl_list_for_each(binding, &sni->tray->bar->config->tray_bindings, link) { +@@ -365,7 +367,11 @@ static void handle_click(struct swaybar_sni *sni, int x, int y, + method = "ContextMenu"; + } + +- if (strncmp(method, "Scroll", strlen("Scroll")) == 0) { ++ if (strcmp(method, "ContextMenu") == 0) { ++ if (sni->menu && !sni->tray->menu) { ++ swaybar_dbusmenu_open(sni, output, seat, serial, x, y); ++ } ++ } else if (strncmp(method, "Scroll", strlen("Scroll")) == 0) { + char dir = method[strlen("Scroll")]; + char *orientation = (dir == 'U' || dir == 'D') ? "vertical" : "horizontal"; + int sign = (dir == 'U' || dir == 'L') ? -1 : 1; +@@ -385,6 +391,7 @@ static int cmp_sni_id(const void *item, const void *cmp_to) { + + static enum hotspot_event_handling icon_hotspot_callback( + struct swaybar_output *output, struct swaybar_hotspot *hotspot, ++ struct swaybar_seat *seat, uint32_t serial, + double x, double y, uint32_t button, bool released, void *data) { + sway_log(SWAY_DEBUG, "Clicked on %s", (char *)data); + +@@ -406,7 +413,8 @@ static enum hotspot_event_handling icon_hotspot_callback( + (int) output->output_height - config->gaps.bottom - y); + + sway_log(SWAY_DEBUG, "Guessing click position at (%d, %d)", global_x, global_y); +- handle_click(sni, global_x, global_y, button, 1); // TODO get delta from event ++ // TODO get delta from event ++ handle_click(sni, output, seat, serial, global_x, global_y, button, 1); + return HOTSPOT_IGNORE; + } else { + sway_log(SWAY_DEBUG, "but it doesn't exist"); +-- +2.43.1 + diff --git a/overlays/sway/_1.9/default.nix b/overlays/sway/_1.9/default.nix new file mode 100644 index 0000000..2ce35d4 --- /dev/null +++ b/overlays/sway/_1.9/default.nix @@ -0,0 +1,25 @@ +{addPatches, ...}: _final: prev: { + sway-unwrapped = addPatches prev.sway-unwrapped [ + # text_input: Implement input-method popups + # https://github.com/swaywm/sway/pull/7226 + (prev.fetchpatch rec { + name = "0001-text_input-Implement-input-method-popups.patch"; + url = "https://aur.archlinux.org/cgit/aur.git/plain/${name}?h=sway-im&id=b8434b3ad9e8c6946dbf7b14b0f7ef5679452b94"; + hash = "sha256-A+rBaWMWs616WllVoo21AJaf9lxg/oCG0b9tHLfuJII="; + }) + (prev.fetchpatch rec { + name = "0002-chore-fractal-scale-handle.patch"; + url = "https://aur.archlinux.org/cgit/aur.git/plain/${name}?h=sway-im&id=b8434b3ad9e8c6946dbf7b14b0f7ef5679452b94"; + hash = "sha256-YOFm0A4uuRSuiwnvF9xbp8Wl7oGicFGnq61vLegqJ0E="; + }) + (prev.fetchpatch rec { + name = "0003-chore-left_pt-on-method-popup.patch"; + url = "https://aur.archlinux.org/cgit/aur.git/plain/${name}?h=sway-im&id=b8434b3ad9e8c6946dbf7b14b0f7ef5679452b94"; + hash = "sha256-PzhQBRpyB1WhErn05UBtBfaDW5bxnQLRKWu8jy7dEiM="; + }) + + # (rebased) Tray D-Bus Menu + # https://github.com/swaywm/sway/pull/6249 + ./0001-Tray-Implement-dbusmenu.patch + ]; +} diff --git a/overlays/sway/default.nix b/overlays/sway/default.nix deleted file mode 100644 index c35e946..0000000 --- a/overlays/sway/default.nix +++ /dev/null @@ -1,49 +0,0 @@ -_final: prev: { - sway-unwrapped = prev.sway-unwrapped.overrideAttrs (old: { - version = "1.10-unstable-2024-02-23"; - src = prev.fetchFromGitHub { - owner = "swaywm"; - repo = "sway"; - rev = "fc640d5f6c82883c35e90a64f0098486e6091293"; - hash = "sha256-n0U1RoSv3fuOkey2gXPX+O4mRA2PCASpkRrIE5069BI"; - }; - - buildInputs = with prev; [ - (wlroots.overrideAttrs { - version = "1.18.0-unstable-2024-02-23"; - src = prev.fetchFromGitLab { - domain = "gitlab.freedesktop.org"; - owner = "wlroots"; - repo = "wlroots"; - rev = "54e1fefd2e29cb00dff7c02801913d793ceab7d6"; - hash = "sha256-VX2AAzcYl255yF43+uetcOS+uYzAVfk7WccWONpjmYU="; - }; - }) - - cairo - gdk-pixbuf - json_c - libGL - libdrm - libevdev - libinput - librsvg - libxkbcommon - pango - pcre2 - wayland - wayland-protocols - xorg.xcbutilwm - ]; - - patches = let - removePatches = ["LIBINPUT_CONFIG_ACCEL_PROFILE_CUSTOM.patch"]; - in - builtins.filter (patch: !builtins.elem (patch.name or null) removePatches) (old.patches or []) - ++ [ - # rebased version of https://github.com/swaywm/sway/pull/6249 - # using sed at this point is just stupid (see ./_1.8.nix) - ./0001-Tray-Implement-dbusmenu.patch - ]; - }); -} diff --git a/overlays/sway/0001-Tray-Implement-dbusmenu.patch b/overlays/sway/git/0001-Tray-Implement-dbusmenu.patch similarity index 98% rename from overlays/sway/0001-Tray-Implement-dbusmenu.patch rename to overlays/sway/git/0001-Tray-Implement-dbusmenu.patch index ca730d9..cbf8d49 100644 --- a/overlays/sway/0001-Tray-Implement-dbusmenu.patch +++ b/overlays/sway/git/0001-Tray-Implement-dbusmenu.patch @@ -1,4 +1,4 @@ -From c0389b28a6ea0c1917212fb60d19f1d401bdc943 Mon Sep 17 00:00:00 2001 +From 9f051dc1e77a516abebf1422d41deba6403f2850 Mon Sep 17 00:00:00 2001 From: Felix Weilbach Date: Sun, 30 May 2021 20:45:01 +0200 Subject: [PATCH] Tray: Implement dbusmenu @@ -323,7 +323,7 @@ index 879a4e42..eea6b35d 100644 return HOTSPOT_PROCESS; diff --git a/swaybar/tray/dbusmenu.c b/swaybar/tray/dbusmenu.c new file mode 100644 -index 00000000..77b5d3f5 +index 00000000..45531af5 --- /dev/null +++ b/swaybar/tray/dbusmenu.c @@ -0,0 +1,1367 @@ @@ -469,7 +469,7 @@ index 00000000..77b5d3f5 +} + +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"); @@ -606,7 +606,7 @@ index 00000000..77b5d3f5 + 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; @@ -627,7 +627,7 @@ index 00000000..77b5d3f5 +} + +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; @@ -911,7 +911,7 @@ index 00000000..77b5d3f5 + } + } + -+ cairo_surface_t *recorder = ++ cairo_surface_t *recorder = + cairo_recording_surface_create(CAIRO_CONTENT_COLOR_ALPHA, NULL); + if (!recorder) { + return; @@ -1077,7 +1077,7 @@ index 00000000..77b5d3f5 + 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); @@ -1098,7 +1098,7 @@ index 00000000..77b5d3f5 + 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) { @@ -1171,7 +1171,7 @@ index 00000000..77b5d3f5 + 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; @@ -1204,7 +1204,7 @@ index 00000000..77b5d3f5 + 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; @@ -1258,7 +1258,7 @@ index 00000000..77b5d3f5 + 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; @@ -1318,7 +1318,7 @@ index 00000000..77b5d3f5 + +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); + @@ -1395,7 +1395,7 @@ index 00000000..77b5d3f5 +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"); @@ -1412,7 +1412,7 @@ index 00000000..77b5d3f5 +} + +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) { @@ -1464,14 +1464,14 @@ index 00000000..77b5d3f5 + 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); @@ -1484,7 +1484,7 @@ index 00000000..77b5d3f5 + } +} + -+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; @@ -1513,7 +1513,7 @@ index 00000000..77b5d3f5 + 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; + } @@ -1553,14 +1553,14 @@ index 00000000..77b5d3f5 + 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)) { @@ -1639,13 +1639,13 @@ index 00000000..77b5d3f5 + 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); + } @@ -1657,7 +1657,7 @@ index 00000000..77b5d3f5 + +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) { @@ -1750,5 +1750,5 @@ index ca6c03ad..29bc8436 100644 } else { sway_log(SWAY_DEBUG, "but it doesn't exist"); -- -2.43.0 +2.43.1 diff --git a/overlays/sway/git/default.nix b/overlays/sway/git/default.nix new file mode 100644 index 0000000..057a997 --- /dev/null +++ b/overlays/sway/git/default.nix @@ -0,0 +1,32 @@ +_final: prev: { + sway-unwrapped = + (prev.sway-unwrapped.overrideAttrs (old: { + version = "1.10-unstable-2024-03-01"; + src = prev.fetchFromGitHub { + owner = "swaywm"; + repo = "sway"; + rev = "5e18ed3cf03eee9e83909fede46dd98dff652647"; + hash = "sha256-O0Kh+pnB1Hxc0TBZuAMVLkCYY6BBsSesvvYD923zDvo="; + }; + + patches = + old.patches + ++ [ + # (rebased) Tray D-Bus Menu + # https://github.com/swaywm/sway/pull/6249 + ./0001-Tray-Implement-dbusmenu.patch + ]; + })) + .override { + wlroots = prev.wlroots.overrideAttrs { + version = "1.18.0-unstable-2024-03-05"; + src = prev.fetchFromGitLab { + domain = "gitlab.freedesktop.org"; + owner = "wlroots"; + repo = "wlroots"; + rev = "31c842e5ece93145604c65be1b14c2f8cee24832"; + hash = "sha256-otjJDgWBgn1Wk1e46Y3wr/eEvOsQPTP9jjaX9dlFcjA="; + }; + }; + }; +}