Merge branch 'master' of github.com:Alexays/Waybar
This commit is contained in:
		
						commit
						d22d6a4522
					
				|  | @ -4,12 +4,16 @@ on: [ push, pull_request ] | ||||||
| 
 | 
 | ||||||
| jobs: | jobs: | ||||||
|   clang: |   clang: | ||||||
|     runs-on: macos-latest # until https://github.com/actions/runner/issues/385 |     # Run actions in a FreeBSD vm on the macos-10.15 runner | ||||||
|  |     # https://github.com/actions/runner/issues/385 - for FreeBSD runner support | ||||||
|  |     # https://github.com/actions/virtual-environments/issues/4060 - for lack of VirtualBox on MacOS 11 runners | ||||||
|  |     runs-on: macos-10.15 | ||||||
|     steps: |     steps: | ||||||
|     - uses: actions/checkout@v2 |     - uses: actions/checkout@v2 | ||||||
|     - name: Test in FreeBSD VM |     - name: Test in FreeBSD VM | ||||||
|       uses: vmactions/freebsd-vm@v0.1.4 # aka FreeBSD 12.2 |       uses: vmactions/freebsd-vm@v0.1.5 # aka FreeBSD 13.0 | ||||||
|       with: |       with: | ||||||
|  |         mem: 2048 | ||||||
|         usesh: true |         usesh: true | ||||||
|         prepare: | |         prepare: | | ||||||
|           export CPPFLAGS=-isystem/usr/local/include LDFLAGS=-L/usr/local/lib # sndio |           export CPPFLAGS=-isystem/usr/local/include LDFLAGS=-L/usr/local/lib # sndio | ||||||
|  |  | ||||||
|  | @ -2,7 +2,7 @@ | ||||||
| 
 | 
 | ||||||
| > Highly customizable Wayland bar for Sway and Wlroots based compositors.<br> | > Highly customizable Wayland bar for Sway and Wlroots based compositors.<br> | ||||||
| > Available in Arch [community](https://www.archlinux.org/packages/community/x86_64/waybar/) or | > Available in Arch [community](https://www.archlinux.org/packages/community/x86_64/waybar/) or | ||||||
| [AUR](https://aur.archlinux.org/packages/waybar-git/), [openSUSE](https://build.opensuse.org/package/show/X11:Wayland/waybar), and [Alpine Linux](https://pkgs.alpinelinux.org/packages?name=waybar)<br> | [AUR](https://aur.archlinux.org/packages/waybar-git/), [Gentoo](https://packages.gentoo.org/packages/gui-apps/waybar), [openSUSE](https://build.opensuse.org/package/show/X11:Wayland/waybar), and [Alpine Linux](https://pkgs.alpinelinux.org/packages?name=waybar)<br> | ||||||
| > *Waybar [examples](https://github.com/Alexays/Waybar/wiki/Examples)* | > *Waybar [examples](https://github.com/Alexays/Waybar/wiki/Examples)* | ||||||
| 
 | 
 | ||||||
| #### Current features | #### Current features | ||||||
|  | @ -69,6 +69,7 @@ libdbusmenu-gtk3 [Tray module] | ||||||
| libmpdclient [MPD module] | libmpdclient [MPD module] | ||||||
| libsndio [sndio module] | libsndio [sndio module] | ||||||
| libevdev [KeyboardState module] | libevdev [KeyboardState module] | ||||||
|  | xkbregistry | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| **Build dependencies** | **Build dependencies** | ||||||
|  | @ -101,7 +102,8 @@ sudo apt install \ | ||||||
|   libsigc++-2.0-dev \ |   libsigc++-2.0-dev \ | ||||||
|   libspdlog-dev \ |   libspdlog-dev \ | ||||||
|   libwayland-dev \ |   libwayland-dev \ | ||||||
|   scdoc |   scdoc \ | ||||||
|  |   libxkbregistry-dev | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -8,6 +8,9 @@ | ||||||
| #include <gtkmm/window.h> | #include <gtkmm/window.h> | ||||||
| #include <json/json.h> | #include <json/json.h> | ||||||
| 
 | 
 | ||||||
|  | #include <memory> | ||||||
|  | #include <vector> | ||||||
|  | 
 | ||||||
| #include "AModule.hpp" | #include "AModule.hpp" | ||||||
| #include "xdg-output-unstable-v1-client-protocol.h" | #include "xdg-output-unstable-v1-client-protocol.h" | ||||||
| 
 | 
 | ||||||
|  | @ -36,6 +39,19 @@ struct bar_margins { | ||||||
|   int left = 0; |   int left = 0; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | struct bar_mode { | ||||||
|  |   bar_layer layer; | ||||||
|  |   bool      exclusive; | ||||||
|  |   bool      passthrough; | ||||||
|  |   bool      visible; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | #ifdef HAVE_SWAY | ||||||
|  | namespace modules::sway { | ||||||
|  | class BarIpcClient; | ||||||
|  | } | ||||||
|  | #endif  // HAVE_SWAY
 | ||||||
|  | 
 | ||||||
| class BarSurface { | class BarSurface { | ||||||
|  protected: |  protected: | ||||||
|   BarSurface() = default; |   BarSurface() = default; | ||||||
|  | @ -54,38 +70,56 @@ class BarSurface { | ||||||
| 
 | 
 | ||||||
| class Bar { | class Bar { | ||||||
|  public: |  public: | ||||||
|  |   using bar_mode_map = std::map<std::string_view, struct bar_mode>; | ||||||
|  |   static const bar_mode_map     PRESET_MODES; | ||||||
|  |   static const std::string_view MODE_DEFAULT; | ||||||
|  |   static const std::string_view MODE_INVISIBLE; | ||||||
|  | 
 | ||||||
|   Bar(struct waybar_output *w_output, const Json::Value &); |   Bar(struct waybar_output *w_output, const Json::Value &); | ||||||
|   Bar(const Bar &) = delete; |   Bar(const Bar &) = delete; | ||||||
|   ~Bar() = default; |   ~Bar(); | ||||||
| 
 | 
 | ||||||
|  |   void setMode(const std::string_view &); | ||||||
|   void setVisible(bool visible); |   void setVisible(bool visible); | ||||||
|   void toggle(); |   void toggle(); | ||||||
|   void handleSignal(int); |   void handleSignal(int); | ||||||
| 
 | 
 | ||||||
|   struct waybar_output *output; |   struct waybar_output *output; | ||||||
|   Json::Value           config; |   Json::Value           config; | ||||||
|   struct wl_surface *   surface; |   struct wl_surface    *surface; | ||||||
|   bool                  exclusive = true; |  | ||||||
|   bool                  visible = true; |   bool                  visible = true; | ||||||
|   bool                  vertical = false; |   bool                  vertical = false; | ||||||
|   Gtk::Window           window; |   Gtk::Window           window; | ||||||
| 
 | 
 | ||||||
|  | #ifdef HAVE_SWAY | ||||||
|  |   std::string bar_id; | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
|  private: |  private: | ||||||
|   void onMap(GdkEventAny *); |   void onMap(GdkEventAny *); | ||||||
|   auto setupWidgets() -> void; |   auto setupWidgets() -> void; | ||||||
|   void getModules(const Factory &, const std::string &); |   void getModules(const Factory &, const std::string &, Gtk::Box*); | ||||||
|   void setupAltFormatKeyForModule(const std::string &module_name); |   void setupAltFormatKeyForModule(const std::string &module_name); | ||||||
|   void setupAltFormatKeyForModuleList(const char *module_list_name); |   void setupAltFormatKeyForModuleList(const char *module_list_name); | ||||||
|  |   void setMode(const bar_mode &); | ||||||
|  | 
 | ||||||
|  |   /* Copy initial set of modes to allow customization */ | ||||||
|  |   bar_mode_map configured_modes = PRESET_MODES; | ||||||
|  |   std::string  last_mode_{MODE_DEFAULT}; | ||||||
| 
 | 
 | ||||||
|   std::unique_ptr<BarSurface>                   surface_impl_; |   std::unique_ptr<BarSurface>                   surface_impl_; | ||||||
|   bar_layer                                     layer_; |  | ||||||
|   Gtk::Box                                      left_; |   Gtk::Box                                      left_; | ||||||
|   Gtk::Box                                      center_; |   Gtk::Box                                      center_; | ||||||
|   Gtk::Box                                      right_; |   Gtk::Box                                      right_; | ||||||
|   Gtk::Box                                      box_; |   Gtk::Box                                      box_; | ||||||
|   std::vector<std::unique_ptr<waybar::AModule>> modules_left_; |   std::vector<std::shared_ptr<waybar::AModule>> modules_left_; | ||||||
|   std::vector<std::unique_ptr<waybar::AModule>> modules_center_; |   std::vector<std::shared_ptr<waybar::AModule>> modules_center_; | ||||||
|   std::vector<std::unique_ptr<waybar::AModule>> modules_right_; |   std::vector<std::shared_ptr<waybar::AModule>> modules_right_; | ||||||
|  | #ifdef HAVE_SWAY | ||||||
|  |   using BarIpcClient = modules::sway::BarIpcClient; | ||||||
|  |   std::unique_ptr<BarIpcClient> _ipc_client; | ||||||
|  | #endif | ||||||
|  |   std::vector<std::shared_ptr<waybar::AModule>> modules_all_; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| }  // namespace waybar
 | }  // namespace waybar
 | ||||||
|  |  | ||||||
|  | @ -29,6 +29,7 @@ class Client { | ||||||
|   struct zwp_idle_inhibit_manager_v1 *idle_inhibit_manager = nullptr; |   struct zwp_idle_inhibit_manager_v1 *idle_inhibit_manager = nullptr; | ||||||
|   std::vector<std::unique_ptr<Bar>>   bars; |   std::vector<std::unique_ptr<Bar>>   bars; | ||||||
|   Config                              config; |   Config                              config; | ||||||
|  |   std::string                         bar_id; | ||||||
| 
 | 
 | ||||||
|  private: |  private: | ||||||
|   Client() = default; |   Client() = default; | ||||||
|  |  | ||||||
|  | @ -14,6 +14,7 @@ | ||||||
| #endif | #endif | ||||||
| #ifdef HAVE_WLR | #ifdef HAVE_WLR | ||||||
| #include "modules/wlr/taskbar.hpp" | #include "modules/wlr/taskbar.hpp" | ||||||
|  | #include "modules/wlr/workspace_manager.hpp" | ||||||
| #endif | #endif | ||||||
| #ifdef HAVE_RIVER | #ifdef HAVE_RIVER | ||||||
| #include "modules/river/tags.hpp" | #include "modules/river/tags.hpp" | ||||||
|  | @ -50,6 +51,9 @@ | ||||||
| #ifdef HAVE_LIBSNDIO | #ifdef HAVE_LIBSNDIO | ||||||
| #include "modules/sndio.hpp" | #include "modules/sndio.hpp" | ||||||
| #endif | #endif | ||||||
|  | #ifdef HAVE_GIO_UNIX | ||||||
|  | #include "modules/inhibitor.hpp" | ||||||
|  | #endif | ||||||
| #include "bar.hpp" | #include "bar.hpp" | ||||||
| #include "modules/custom.hpp" | #include "modules/custom.hpp" | ||||||
| #include "modules/temperature.hpp" | #include "modules/temperature.hpp" | ||||||
|  |  | ||||||
|  | @ -0,0 +1,21 @@ | ||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <gtkmm/widget.h> | ||||||
|  | #include <gtkmm/box.h> | ||||||
|  | #include <json/json.h> | ||||||
|  | #include "AModule.hpp" | ||||||
|  | #include "bar.hpp" | ||||||
|  | #include "factory.hpp" | ||||||
|  | 
 | ||||||
|  | namespace waybar { | ||||||
|  | 
 | ||||||
|  | class Group : public AModule { | ||||||
|  |  public: | ||||||
|  |   Group(const std::string&, const Bar&, const Json::Value&); | ||||||
|  |   ~Group() = default; | ||||||
|  |   auto update() -> void; | ||||||
|  |   operator Gtk::Widget &(); | ||||||
|  |   Gtk::Box box; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | }  // namespace waybar
 | ||||||
|  | @ -17,6 +17,8 @@ struct waybar_time { | ||||||
|   date::zoned_seconds ztime; |   date::zoned_seconds ztime; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | const std::string kCalendarPlaceholder = "calendar"; | ||||||
|  | 
 | ||||||
| class Clock : public ALabel { | class Clock : public ALabel { | ||||||
|  public: |  public: | ||||||
|   Clock(const std::string&, const Json::Value&); |   Clock(const std::string&, const Json::Value&); | ||||||
|  | @ -26,18 +28,19 @@ class Clock : public ALabel { | ||||||
|  private: |  private: | ||||||
|   util::SleeperThread thread_; |   util::SleeperThread thread_; | ||||||
|   std::locale locale_; |   std::locale locale_; | ||||||
|   const date::time_zone* time_zone_; |   std::vector<const date::time_zone*> time_zones_; | ||||||
|   bool fixed_time_zone_; |   int current_time_zone_idx_; | ||||||
|   int time_zone_idx_; |  | ||||||
|   date::year_month_day cached_calendar_ymd_ = date::January/1/0; |   date::year_month_day cached_calendar_ymd_ = date::January/1/0; | ||||||
|   std::string cached_calendar_text_; |   std::string cached_calendar_text_; | ||||||
|  |   bool is_calendar_in_tooltip_; | ||||||
| 
 | 
 | ||||||
|   bool handleScroll(GdkEventScroll* e); |   bool handleScroll(GdkEventScroll* e); | ||||||
| 
 | 
 | ||||||
|   auto calendar_text(const waybar_time& wtime) -> std::string; |   auto calendar_text(const waybar_time& wtime) -> std::string; | ||||||
|   auto weekdays_header(const date::weekday& first_dow, std::ostream& os) -> void; |   auto weekdays_header(const date::weekday& first_dow, std::ostream& os) -> void; | ||||||
|   auto first_day_of_week() -> date::weekday; |   auto first_day_of_week() -> date::weekday; | ||||||
|   bool setTimeZone(Json::Value zone_name); |   const date::time_zone* current_timezone(); | ||||||
|  |   bool is_timezone_fixed(); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| }  // namespace waybar::modules
 | }  // namespace waybar::modules
 | ||||||
|  |  | ||||||
|  | @ -0,0 +1,27 @@ | ||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <memory> | ||||||
|  | 
 | ||||||
|  | #include <gio/gio.h> | ||||||
|  | 
 | ||||||
|  | #include "ALabel.hpp" | ||||||
|  | #include "bar.hpp" | ||||||
|  | 
 | ||||||
|  | namespace waybar::modules { | ||||||
|  | 
 | ||||||
|  | class Inhibitor : public ALabel { | ||||||
|  |  public: | ||||||
|  |   Inhibitor(const std::string&, const waybar::Bar&, const Json::Value&); | ||||||
|  |   ~Inhibitor() override; | ||||||
|  |   auto update() -> void; | ||||||
|  |   auto activated() -> bool; | ||||||
|  | 
 | ||||||
|  |  private: | ||||||
|  |   auto handleToggle(::GdkEventButton* const& e) -> bool; | ||||||
|  | 
 | ||||||
|  |   const std::unique_ptr<::GDBusConnection, void(*)(::GDBusConnection*)> dbus_; | ||||||
|  |   const std::string inhibitors_; | ||||||
|  |   int handle_ = -1; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | } // namespace waybar::modules
 | ||||||
|  | @ -43,6 +43,7 @@ class Network : public ALabel { | ||||||
|   const std::string getNetworkState() const; |   const std::string getNetworkState() const; | ||||||
|   void              clearIface(); |   void              clearIface(); | ||||||
|   bool              wildcardMatch(const std::string& pattern, const std::string& text) const; |   bool              wildcardMatch(const std::string& pattern, const std::string& text) const; | ||||||
|  |   std::optional<std::pair<unsigned long long, unsigned long long>> readBandwidthUsage(); | ||||||
| 
 | 
 | ||||||
|   int                ifid_; |   int                ifid_; | ||||||
|   sa_family_t        family_; |   sa_family_t        family_; | ||||||
|  | @ -72,7 +73,8 @@ class Network : public ALabel { | ||||||
|   int         cidr_; |   int         cidr_; | ||||||
|   int32_t     signal_strength_dbm_; |   int32_t     signal_strength_dbm_; | ||||||
|   uint8_t     signal_strength_; |   uint8_t     signal_strength_; | ||||||
|   uint32_t    frequency_; |   std::string signal_strength_app_; | ||||||
|  |   float       frequency_; | ||||||
|   uint32_t    route_priority; |   uint32_t    route_priority; | ||||||
| 
 | 
 | ||||||
|   util::SleeperThread thread_; |   util::SleeperThread thread_; | ||||||
|  |  | ||||||
|  | @ -6,6 +6,7 @@ | ||||||
| #include "AModule.hpp" | #include "AModule.hpp" | ||||||
| #include "bar.hpp" | #include "bar.hpp" | ||||||
| #include "river-status-unstable-v1-client-protocol.h" | #include "river-status-unstable-v1-client-protocol.h" | ||||||
|  | #include "river-control-unstable-v1-client-protocol.h" | ||||||
| #include "xdg-output-unstable-v1-client-protocol.h" | #include "xdg-output-unstable-v1-client-protocol.h" | ||||||
| 
 | 
 | ||||||
| namespace waybar::modules::river { | namespace waybar::modules::river { | ||||||
|  | @ -20,7 +21,12 @@ class Tags : public waybar::AModule { | ||||||
|   void handle_view_tags(struct wl_array *tags); |   void handle_view_tags(struct wl_array *tags); | ||||||
|   void handle_urgent_tags(uint32_t tags); |   void handle_urgent_tags(uint32_t tags); | ||||||
| 
 | 
 | ||||||
|  |   void handle_primary_clicked(uint32_t tag); | ||||||
|  |   bool handle_button_press(GdkEventButton *event_button, uint32_t tag); | ||||||
|  | 
 | ||||||
|   struct zriver_status_manager_v1 *status_manager_; |   struct zriver_status_manager_v1 *status_manager_; | ||||||
|  |   struct zriver_control_v1 *control_; | ||||||
|  |   struct wl_seat *seat_; | ||||||
| 
 | 
 | ||||||
|  private: |  private: | ||||||
|   const waybar::Bar &      bar_; |   const waybar::Bar &      bar_; | ||||||
|  |  | ||||||
|  | @ -0,0 +1,49 @@ | ||||||
|  | #pragma once | ||||||
|  | #include <string> | ||||||
|  | 
 | ||||||
|  | #include "modules/sway/ipc/client.hpp" | ||||||
|  | #include "util/SafeSignal.hpp" | ||||||
|  | #include "util/json.hpp" | ||||||
|  | 
 | ||||||
|  | namespace waybar { | ||||||
|  | 
 | ||||||
|  | class Bar; | ||||||
|  | 
 | ||||||
|  | namespace modules::sway { | ||||||
|  | 
 | ||||||
|  | /*
 | ||||||
|  |  * Supported subset of i3/sway IPC barconfig object | ||||||
|  |  */ | ||||||
|  | struct swaybar_config { | ||||||
|  |   std::string id; | ||||||
|  |   std::string mode; | ||||||
|  |   std::string hidden_state; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * swaybar IPC client | ||||||
|  |  */ | ||||||
|  | class BarIpcClient { | ||||||
|  |  public: | ||||||
|  |   BarIpcClient(waybar::Bar& bar); | ||||||
|  | 
 | ||||||
|  |  private: | ||||||
|  |   void onInitialConfig(const struct Ipc::ipc_response& res); | ||||||
|  |   void onIpcEvent(const struct Ipc::ipc_response&); | ||||||
|  |   void onConfigUpdate(const swaybar_config& config); | ||||||
|  |   void onVisibilityUpdate(bool visible_by_modifier); | ||||||
|  |   void update(); | ||||||
|  | 
 | ||||||
|  |   Bar&             bar_; | ||||||
|  |   util::JsonParser parser_; | ||||||
|  |   Ipc              ipc_; | ||||||
|  | 
 | ||||||
|  |   swaybar_config bar_config_; | ||||||
|  |   bool           visible_by_modifier_ = false; | ||||||
|  | 
 | ||||||
|  |   SafeSignal<bool>           signal_visible_; | ||||||
|  |   SafeSignal<swaybar_config> signal_config_; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | }  // namespace modules::sway
 | ||||||
|  | }  // namespace waybar
 | ||||||
|  | @ -32,6 +32,7 @@ class Language : public ALabel, public sigc::trackable { | ||||||
|     std::string short_name; |     std::string short_name; | ||||||
|     std::string variant; |     std::string variant; | ||||||
|     std::string short_description; |     std::string short_description; | ||||||
|  |     std::string country_flag() const; | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   class XKBContext { |   class XKBContext { | ||||||
|  | @ -54,7 +55,7 @@ class Language : public ALabel, public sigc::trackable { | ||||||
| 
 | 
 | ||||||
|   const static std::string XKB_LAYOUT_NAMES_KEY; |   const static std::string XKB_LAYOUT_NAMES_KEY; | ||||||
|   const static std::string XKB_ACTIVE_LAYOUT_NAME_KEY; |   const static std::string XKB_ACTIVE_LAYOUT_NAME_KEY; | ||||||
|    | 
 | ||||||
|   Layout                        layout_; |   Layout                        layout_; | ||||||
|   std::string tooltip_format_ = ""; |   std::string tooltip_format_ = ""; | ||||||
|   std::map<std::string, Layout> layouts_map_; |   std::map<std::string, Layout> layouts_map_; | ||||||
|  |  | ||||||
|  | @ -3,11 +3,13 @@ | ||||||
| #include "AModule.hpp" | #include "AModule.hpp" | ||||||
| #include "bar.hpp" | #include "bar.hpp" | ||||||
| #include "client.hpp" | #include "client.hpp" | ||||||
|  | #include "giomm/desktopappinfo.h" | ||||||
| #include "util/json.hpp" | #include "util/json.hpp" | ||||||
| 
 | 
 | ||||||
| #include <memory> | #include <memory> | ||||||
| #include <string> | #include <string> | ||||||
| #include <vector> | #include <vector> | ||||||
|  | #include <map> | ||||||
| #include <unordered_set> | #include <unordered_set> | ||||||
| 
 | 
 | ||||||
| #include <gdk/gdk.h> | #include <gdk/gdk.h> | ||||||
|  | @ -61,15 +63,18 @@ class Task | ||||||
|     Gtk::Image icon_; |     Gtk::Image icon_; | ||||||
|     Gtk::Label text_before_; |     Gtk::Label text_before_; | ||||||
|     Gtk::Label text_after_; |     Gtk::Label text_after_; | ||||||
|     bool button_visible_; |     Glib::RefPtr<Gio::DesktopAppInfo> app_info_; | ||||||
|     bool ignored_; |     bool button_visible_ = false; | ||||||
|  |     bool ignored_ = false; | ||||||
| 
 | 
 | ||||||
|     bool with_icon_; |     bool with_icon_ = false; | ||||||
|  |     bool with_name_ = false; | ||||||
|     std::string format_before_; |     std::string format_before_; | ||||||
|     std::string format_after_; |     std::string format_after_; | ||||||
| 
 | 
 | ||||||
|     std::string format_tooltip_; |     std::string format_tooltip_; | ||||||
| 
 | 
 | ||||||
|  |     std::string name_; | ||||||
|     std::string title_; |     std::string title_; | ||||||
|     std::string app_id_; |     std::string app_id_; | ||||||
|     uint32_t state_ = 0; |     uint32_t state_ = 0; | ||||||
|  | @ -77,6 +82,9 @@ class Task | ||||||
|    private: |    private: | ||||||
|     std::string repr() const; |     std::string repr() const; | ||||||
|     std::string state_string(bool = false) const; |     std::string state_string(bool = false) const; | ||||||
|  |     void set_app_info_from_app_id_list(const std::string& app_id_list); | ||||||
|  |     bool image_load_icon(Gtk::Image& image, const Glib::RefPtr<Gtk::IconTheme>& icon_theme, Glib::RefPtr<Gio::DesktopAppInfo> app_info, int size); | ||||||
|  |     void hide_if_ignored(); | ||||||
| 
 | 
 | ||||||
|    public: |    public: | ||||||
|     /* Getter functions */ |     /* Getter functions */ | ||||||
|  | @ -135,6 +143,7 @@ class Taskbar : public waybar::AModule | ||||||
| 
 | 
 | ||||||
|     std::vector<Glib::RefPtr<Gtk::IconTheme>> icon_themes_; |     std::vector<Glib::RefPtr<Gtk::IconTheme>> icon_themes_; | ||||||
|     std::unordered_set<std::string> ignore_list_; |     std::unordered_set<std::string> ignore_list_; | ||||||
|  |     std::map<std::string, std::string> app_ids_replace_map_; | ||||||
| 
 | 
 | ||||||
|     struct zwlr_foreign_toplevel_manager_v1 *manager_; |     struct zwlr_foreign_toplevel_manager_v1 *manager_; | ||||||
|     struct wl_seat *seat_; |     struct wl_seat *seat_; | ||||||
|  | @ -157,8 +166,9 @@ class Taskbar : public waybar::AModule | ||||||
|     bool show_output(struct wl_output *) const; |     bool show_output(struct wl_output *) const; | ||||||
|     bool all_outputs() const; |     bool all_outputs() const; | ||||||
| 
 | 
 | ||||||
|     std::vector<Glib::RefPtr<Gtk::IconTheme>> icon_themes() const; |     const std::vector<Glib::RefPtr<Gtk::IconTheme>>& icon_themes() const; | ||||||
|     const std::unordered_set<std::string>& ignore_list() const; |     const std::unordered_set<std::string>& ignore_list() const; | ||||||
|  |     const std::map<std::string, std::string>& app_ids_replace_map() const; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| } /* namespace waybar::modules::wlr */ | } /* namespace waybar::modules::wlr */ | ||||||
|  |  | ||||||
|  | @ -0,0 +1,160 @@ | ||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <fmt/format.h> | ||||||
|  | #include <gtkmm/button.h> | ||||||
|  | #include <gtkmm/image.h> | ||||||
|  | #include <gtkmm/label.h> | ||||||
|  | 
 | ||||||
|  | #include <functional> | ||||||
|  | #include <map> | ||||||
|  | #include <memory> | ||||||
|  | #include <vector> | ||||||
|  | 
 | ||||||
|  | #include "AModule.hpp" | ||||||
|  | #include "bar.hpp" | ||||||
|  | #include "ext-workspace-unstable-v1-client-protocol.h" | ||||||
|  | 
 | ||||||
|  | namespace waybar::modules::wlr { | ||||||
|  | 
 | ||||||
|  | class WorkspaceManager; | ||||||
|  | class WorkspaceGroup; | ||||||
|  | 
 | ||||||
|  | class Workspace { | ||||||
|  |  public: | ||||||
|  |   Workspace(const waybar::Bar &bar, const Json::Value &config, WorkspaceGroup &workspace_group, | ||||||
|  |             zext_workspace_handle_v1 *workspace, uint32_t id); | ||||||
|  |   ~Workspace(); | ||||||
|  |   auto update() -> void; | ||||||
|  | 
 | ||||||
|  |   auto id() const -> uint32_t { return id_; } | ||||||
|  |   auto is_active() const -> bool { return state_ & static_cast<uint32_t>(State::ACTIVE); } | ||||||
|  |   auto is_urgent() const -> bool { return state_ & static_cast<uint32_t>(State::URGENT); } | ||||||
|  |   auto is_hidden() const -> bool { return state_ & static_cast<uint32_t>(State::HIDDEN); } | ||||||
|  |   // wlr stuff
 | ||||||
|  |   auto handle_name(const std::string &name) -> void; | ||||||
|  |   auto handle_coordinates(const std::vector<uint32_t> &coordinates) -> void; | ||||||
|  |   auto handle_state(const std::vector<uint32_t> &state) -> void; | ||||||
|  |   auto handle_remove() -> void; | ||||||
|  | 
 | ||||||
|  |   auto handle_done() -> void; | ||||||
|  |   auto handle_clicked(GdkEventButton *bt) -> bool; | ||||||
|  |   auto show() -> void; | ||||||
|  |   auto hide() -> void; | ||||||
|  |   auto get_button_ref() -> Gtk::Button & { return button_; } | ||||||
|  |   auto get_name() -> std::string & { return name_; } | ||||||
|  |   auto get_coords() -> std::vector<uint32_t> & { return coordinates_; } | ||||||
|  | 
 | ||||||
|  |   enum class State { | ||||||
|  |     ACTIVE = (1 << 0), | ||||||
|  |     URGENT = (1 << 1), | ||||||
|  |     HIDDEN = (1 << 2), | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |  private: | ||||||
|  |   auto get_icon() -> std::string; | ||||||
|  | 
 | ||||||
|  |   const Bar         &bar_; | ||||||
|  |   const Json::Value &config_; | ||||||
|  |   WorkspaceGroup    &workspace_group_; | ||||||
|  | 
 | ||||||
|  |   // wlr stuff
 | ||||||
|  |   zext_workspace_handle_v1 *workspace_handle_; | ||||||
|  |   uint32_t                  state_ = 0; | ||||||
|  | 
 | ||||||
|  |   uint32_t                                  id_; | ||||||
|  |   std::string                               name_; | ||||||
|  |   std::vector<uint32_t>                     coordinates_; | ||||||
|  |   static std::map<std::string, std::string> icons_map_; | ||||||
|  |   std::string                               format_; | ||||||
|  |   bool                                      with_icon_ = false; | ||||||
|  | 
 | ||||||
|  |   Gtk::Button button_; | ||||||
|  |   Gtk::Box    content_; | ||||||
|  |   Gtk::Label  label_; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | class WorkspaceGroup { | ||||||
|  |  public: | ||||||
|  |   WorkspaceGroup(const waybar::Bar &bar, Gtk::Box &box, const Json::Value &config, | ||||||
|  |                  WorkspaceManager &manager, zext_workspace_group_handle_v1 *workspace_group_handle, | ||||||
|  |                  uint32_t id); | ||||||
|  |   ~WorkspaceGroup(); | ||||||
|  |   auto update() -> void; | ||||||
|  | 
 | ||||||
|  |   auto id() const -> uint32_t { return id_; } | ||||||
|  |   auto is_visible() const -> bool; | ||||||
|  |   auto remove_workspace(uint32_t id_) -> void; | ||||||
|  |   auto active_only() const -> bool; | ||||||
|  |   auto creation_delayed() const -> bool; | ||||||
|  |   auto workspaces() -> std::vector<std::unique_ptr<Workspace>> & { return workspaces_; } | ||||||
|  | 
 | ||||||
|  |   auto sort_workspaces() -> void; | ||||||
|  |   auto set_need_to_sort() -> void { need_to_sort = true; } | ||||||
|  |   auto add_button(Gtk::Button &button) -> void; | ||||||
|  |   auto remove_button(Gtk::Button &button) -> void; | ||||||
|  | 
 | ||||||
|  |   // wlr stuff
 | ||||||
|  |   auto handle_workspace_create(zext_workspace_handle_v1 *workspace_handle) -> void; | ||||||
|  |   auto handle_remove() -> void; | ||||||
|  |   auto handle_output_enter(wl_output *output) -> void; | ||||||
|  |   auto handle_output_leave() -> void; | ||||||
|  |   auto handle_done() -> void; | ||||||
|  |   auto commit() -> void; | ||||||
|  | 
 | ||||||
|  |  private: | ||||||
|  |   static uint32_t    workspace_global_id; | ||||||
|  |   const waybar::Bar &bar_; | ||||||
|  |   Gtk::Box          &box_; | ||||||
|  |   const Json::Value &config_; | ||||||
|  |   WorkspaceManager  &workspace_manager_; | ||||||
|  | 
 | ||||||
|  |   // wlr stuff
 | ||||||
|  |   zext_workspace_group_handle_v1 *workspace_group_handle_; | ||||||
|  |   wl_output                      *output_ = nullptr; | ||||||
|  | 
 | ||||||
|  |   uint32_t                                id_; | ||||||
|  |   std::vector<std::unique_ptr<Workspace>> workspaces_; | ||||||
|  |   bool                                    need_to_sort = false; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | class WorkspaceManager : public AModule { | ||||||
|  |  public: | ||||||
|  |   WorkspaceManager(const std::string &id, const waybar::Bar &bar, const Json::Value &config); | ||||||
|  |   ~WorkspaceManager() override; | ||||||
|  |   auto update() -> void override; | ||||||
|  | 
 | ||||||
|  |   auto all_outputs() const -> bool { return all_outputs_; } | ||||||
|  |   auto active_only() const -> bool { return active_only_; } | ||||||
|  |   auto workspace_comparator() const | ||||||
|  |       -> std::function<bool(std::unique_ptr<Workspace> &, std::unique_ptr<Workspace> &)>; | ||||||
|  |   auto creation_delayed() const -> bool { return creation_delayed_; } | ||||||
|  | 
 | ||||||
|  |   auto sort_workspaces() -> void; | ||||||
|  |   auto remove_workspace_group(uint32_t id_) -> void; | ||||||
|  | 
 | ||||||
|  |   // wlr stuff
 | ||||||
|  |   auto register_manager(wl_registry *registry, uint32_t name, uint32_t version) -> void; | ||||||
|  |   auto handle_workspace_group_create(zext_workspace_group_handle_v1 *workspace_group_handle) | ||||||
|  |       -> void; | ||||||
|  |   auto handle_done() -> void; | ||||||
|  |   auto handle_finished() -> void; | ||||||
|  |   auto commit() -> void; | ||||||
|  | 
 | ||||||
|  |  private: | ||||||
|  |   const waybar::Bar                           &bar_; | ||||||
|  |   Gtk::Box                                     box_; | ||||||
|  |   std::vector<std::unique_ptr<WorkspaceGroup>> groups_; | ||||||
|  | 
 | ||||||
|  |   // wlr stuff
 | ||||||
|  |   zext_workspace_manager_v1 *workspace_manager_ = nullptr; | ||||||
|  | 
 | ||||||
|  |   static uint32_t group_global_id; | ||||||
|  | 
 | ||||||
|  |   bool sort_by_name_ = true; | ||||||
|  |   bool sort_by_coordinates_ = true; | ||||||
|  |   bool all_outputs_ = false; | ||||||
|  |   bool active_only_ = false; | ||||||
|  |   bool creation_delayed_ = false; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | }  // namespace waybar::modules::wlr
 | ||||||
|  | @ -0,0 +1,8 @@ | ||||||
|  | #include "ext-workspace-unstable-v1-client-protocol.h" | ||||||
|  | 
 | ||||||
|  | namespace waybar::modules::wlr { | ||||||
|  |   void add_registry_listener(void *data); | ||||||
|  |   void add_workspace_listener(zext_workspace_handle_v1 *workspace_handle, void *data); | ||||||
|  |   void add_workspace_group_listener(zext_workspace_group_handle_v1 *workspace_group_handle, void *data); | ||||||
|  |   zext_workspace_manager_v1* workspace_manager_bind(wl_registry *registry, uint32_t name, uint32_t version, void *data); | ||||||
|  | } | ||||||
|  | @ -0,0 +1,75 @@ | ||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <glibmm/dispatcher.h> | ||||||
|  | #include <sigc++/signal.h> | ||||||
|  | 
 | ||||||
|  | #include <functional> | ||||||
|  | #include <mutex> | ||||||
|  | #include <queue> | ||||||
|  | #include <thread> | ||||||
|  | #include <tuple> | ||||||
|  | #include <type_traits> | ||||||
|  | #include <utility> | ||||||
|  | 
 | ||||||
|  | namespace waybar { | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * Thread-safe signal wrapper. | ||||||
|  |  * Uses Glib::Dispatcher to pass events to another thread and locked queue to pass the arguments. | ||||||
|  |  */ | ||||||
|  | template <typename... Args> | ||||||
|  | struct SafeSignal : sigc::signal<void(std::decay_t<Args>...)> { | ||||||
|  |  public: | ||||||
|  |   SafeSignal() { dp_.connect(sigc::mem_fun(*this, &SafeSignal::handle_event)); } | ||||||
|  | 
 | ||||||
|  |   template <typename... EmitArgs> | ||||||
|  |   void emit(EmitArgs&&... args) { | ||||||
|  |     if (main_tid_ == std::this_thread::get_id()) { | ||||||
|  |       /*
 | ||||||
|  |        * Bypass the queue if the method is called the main thread. | ||||||
|  |        * Ensures that events emitted from the main thread are processed synchronously and saves a | ||||||
|  |        * few CPU cycles on locking/queuing. | ||||||
|  |        * As a downside, this makes main thread events prioritized over the other threads and | ||||||
|  |        * disrupts chronological order. | ||||||
|  |        */ | ||||||
|  |       signal_t::emit(std::forward<EmitArgs>(args)...); | ||||||
|  |     } else { | ||||||
|  |       { | ||||||
|  |         std::unique_lock lock(mutex_); | ||||||
|  |         queue_.emplace(std::forward<EmitArgs>(args)...); | ||||||
|  |       } | ||||||
|  |       dp_.emit(); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   template <typename... EmitArgs> | ||||||
|  |   inline void operator()(EmitArgs&&... args) { | ||||||
|  |     emit(std::forward<EmitArgs>(args)...); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |  protected: | ||||||
|  |   using signal_t = sigc::signal<void(std::decay_t<Args>...)>; | ||||||
|  |   using slot_t = decltype(std::declval<signal_t>().make_slot()); | ||||||
|  |   using arg_tuple_t = std::tuple<std::decay_t<Args>...>; | ||||||
|  |   // ensure that unwrapped methods are not accessible
 | ||||||
|  |   using signal_t::emit_reverse; | ||||||
|  |   using signal_t::make_slot; | ||||||
|  | 
 | ||||||
|  |   void handle_event() { | ||||||
|  |     for (std::unique_lock lock(mutex_); !queue_.empty(); lock.lock()) { | ||||||
|  |       auto args = queue_.front(); | ||||||
|  |       queue_.pop(); | ||||||
|  |       lock.unlock(); | ||||||
|  |       std::apply(cached_fn_, args); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   Glib::Dispatcher        dp_; | ||||||
|  |   std::mutex              mutex_; | ||||||
|  |   std::queue<arg_tuple_t> queue_; | ||||||
|  |   const std::thread::id   main_tid_ = std::this_thread::get_id(); | ||||||
|  |   // cache functor for signal emission to avoid recreating it on each event
 | ||||||
|  |   const slot_t cached_fn_ = make_slot(); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | }  // namespace waybar
 | ||||||
|  | @ -120,7 +120,7 @@ The two arguments are: | ||||||
| 
 | 
 | ||||||
| # CUSTOM FORMATS | # CUSTOM FORMATS | ||||||
| 
 | 
 | ||||||
| The *battery* module allows to define custom formats based on up to two factors. The best fitting format will be selected. | The *battery* module allows one to define custom formats based on up to two factors. The best fitting format will be selected. | ||||||
| 
 | 
 | ||||||
| *format-<state>*: With *states*, a custom format can be set depending on the capacity of your battery. | *format-<state>*: With *states*, a custom format can be set depending on the capacity of your battery. | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -0,0 +1,92 @@ | ||||||
|  | waybar-inhibitor(5) | ||||||
|  | 
 | ||||||
|  | # NAME | ||||||
|  | 
 | ||||||
|  | waybar - inhibitor module | ||||||
|  | 
 | ||||||
|  | # DESCRIPTION | ||||||
|  | 
 | ||||||
|  | The *inhibitor* module allows to take an inhibitor lock that logind provides. | ||||||
|  | See *systemd-inhibit*(1) for more information. | ||||||
|  | 
 | ||||||
|  | # CONFIGURATION | ||||||
|  | 
 | ||||||
|  | *what*: ++ | ||||||
|  | 	typeof: string or array ++ | ||||||
|  | 	The inhibitor lock or locks that should be taken when active. The available inhibitor locks are *idle*, *shutdown*, *sleep*, *handle-power-key*, *handle-suspend-key*, *handle-hibernate-key* and *handle-lid-switch*. | ||||||
|  | 
 | ||||||
|  | *format*: ++ | ||||||
|  | 	typeof: string ++ | ||||||
|  | 	The format, how the state should be displayed. | ||||||
|  | 
 | ||||||
|  | *format-icons*: ++ | ||||||
|  | 	typeof: array ++ | ||||||
|  | 	Based on the current state, the corresponding icon gets selected. | ||||||
|  | 
 | ||||||
|  | *rotate*: ++ | ||||||
|  | 	typeof: integer ++ | ||||||
|  | 	Positive value to rotate the text label. | ||||||
|  | 
 | ||||||
|  | *max-length*: ++ | ||||||
|  | 	typeof: integer ++ | ||||||
|  | 	The maximum length in character the module should display. | ||||||
|  | 
 | ||||||
|  | *min-length*: ++ | ||||||
|  | 	typeof: integer ++ | ||||||
|  | 	The minimum length in characters the module should take up. | ||||||
|  | 
 | ||||||
|  | *align*: ++ | ||||||
|  | 	typeof: float ++ | ||||||
|  | 	The alignment of the text, where 0 is left-aligned and 1 is right-aligned. If the module is rotated, it will follow the flow of the text. | ||||||
|  | 
 | ||||||
|  | *on-click*: ++ | ||||||
|  | 	typeof: string ++ | ||||||
|  | 	Command to execute when clicked on the module. A click also toggles the state | ||||||
|  | 
 | ||||||
|  | *on-click-middle*: ++ | ||||||
|  | 	typeof: string ++ | ||||||
|  | 	Command to execute when middle-clicked on the module using mousewheel. | ||||||
|  | 
 | ||||||
|  | *on-click-right*: ++ | ||||||
|  | 	typeof: string ++ | ||||||
|  | 	Command to execute when you right clicked on the module. | ||||||
|  | 
 | ||||||
|  | *on-update*: ++ | ||||||
|  | 	typeof: string ++ | ||||||
|  | 	Command to execute when the module is updated. | ||||||
|  | 
 | ||||||
|  | *on-scroll-up*: ++ | ||||||
|  | 	typeof: string ++ | ||||||
|  | 	Command to execute when scrolling up on the module. | ||||||
|  | 
 | ||||||
|  | *on-scroll-down*: ++ | ||||||
|  | 	typeof: string ++ | ||||||
|  | 	Command to execute when scrolling down on the module. | ||||||
|  | 
 | ||||||
|  | *smooth-scrolling-threshold*: ++ | ||||||
|  | 	typeof: double ++ | ||||||
|  | 	Threshold to be used when scrolling. | ||||||
|  | 
 | ||||||
|  | *tooltip*: ++ | ||||||
|  | 	typeof: bool ++ | ||||||
|  | 	default: true ++ | ||||||
|  | 	Option to disable tooltip on hover. | ||||||
|  | 
 | ||||||
|  | # FORMAT REPLACEMENTS | ||||||
|  | 
 | ||||||
|  | *{status}*: status (*activated* or *deactivated*) | ||||||
|  | 
 | ||||||
|  | *{icon}*: Icon, as defined in *format-icons* | ||||||
|  | 
 | ||||||
|  | # EXAMPLES | ||||||
|  | 
 | ||||||
|  | ``` | ||||||
|  | "inhibitor": { | ||||||
|  | 	"what": "handle-lid-switch", | ||||||
|  | 	"format": "{icon}", | ||||||
|  | 	"format-icons": { | ||||||
|  | 	    "activated": "", | ||||||
|  | 	    "deactivated": "" | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | @ -21,6 +21,11 @@ Addressed by *river/tags* | ||||||
|     typeof: array ++ |     typeof: array ++ | ||||||
|     The label to display for each tag. |     The label to display for each tag. | ||||||
| 
 | 
 | ||||||
|  | *disable-click*: ++ | ||||||
|  |    typeof: bool ++ | ||||||
|  |    default: false ++ | ||||||
|  |    If set to false, you can left click to set focused tag. Right click to toggle tag focus. If set to true this behaviour is disabled. | ||||||
|  | 
 | ||||||
| # EXAMPLE | # EXAMPLE | ||||||
| 
 | 
 | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
|  | @ -37,6 +37,8 @@ Addressed by *sway/language* | ||||||
| 
 | 
 | ||||||
| *{variant}*: Variant of layout (e.g. "dvorak"). | *{variant}*: Variant of layout (e.g. "dvorak"). | ||||||
| 
 | 
 | ||||||
|  | *{flag}*: Country flag of layout. | ||||||
|  | 
 | ||||||
| # EXAMPLES | # EXAMPLES | ||||||
| 
 | 
 | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
|  | @ -69,10 +69,6 @@ Addressed by *sway/workspaces* | ||||||
|     typeof: string ++ |     typeof: string ++ | ||||||
|     Command to execute when the module is updated. |     Command to execute when the module is updated. | ||||||
| 
 | 
 | ||||||
| *numeric-first*: ++ |  | ||||||
|     typeof: bool ++ |  | ||||||
|     Whether to put workspaces starting with numbers before workspaces that do not start with a number. |  | ||||||
| 
 |  | ||||||
| *disable-auto-back-and-forth*: ++ | *disable-auto-back-and-forth*: ++ | ||||||
|     typeof: bool ++ |     typeof: bool ++ | ||||||
|     Whether to disable *workspace_auto_back_and_forth* when clicking on workspaces. If this is set to *true*, clicking on a workspace you are already on won't do anything, even if *workspace_auto_back_and_forth* is enabled in the Sway configuration. |     Whether to disable *workspace_auto_back_and_forth* when clicking on workspaces. If this is set to *true*, clicking on a workspace you are already on won't do anything, even if *workspace_auto_back_and_forth* is enabled in the Sway configuration. | ||||||
|  | @ -120,7 +116,6 @@ n.b.: the list of outputs can be obtained from command line using *swaymsg -t ge | ||||||
| "sway/workspaces": { | "sway/workspaces": { | ||||||
|     "disable-scroll": true, |     "disable-scroll": true, | ||||||
|     "all-outputs": true, |     "all-outputs": true, | ||||||
|     "numeric-first": false, |  | ||||||
|     "format": "{name}: {icon}", |     "format": "{name}: {icon}", | ||||||
|     "format-icons": { |     "format-icons": { | ||||||
|         "1": "", |         "1": "", | ||||||
|  |  | ||||||
|  | @ -70,12 +70,18 @@ Addressed by *wlr/taskbar* | ||||||
| 
 | 
 | ||||||
| *ignore-list*: ++ | *ignore-list*: ++ | ||||||
| 	typeof: array ++ | 	typeof: array ++ | ||||||
| 	List of app_id to be invisible. | 	List of app_id/titles to be invisible. | ||||||
|  | 
 | ||||||
|  | *app_ids-mapping*: ++ | ||||||
|  | 	typeof: object ++ | ||||||
|  | 	Dictionary of app_id to be replaced with | ||||||
| 
 | 
 | ||||||
| # FORMAT REPLACEMENTS | # FORMAT REPLACEMENTS | ||||||
| 
 | 
 | ||||||
| *{icon}*: The icon of the application. | *{icon}*: The icon of the application. | ||||||
| 
 | 
 | ||||||
|  | *{title}*: The application name as in desktop file if appropriate desktop fils found, otherwise same as {app_id} | ||||||
|  | 
 | ||||||
| *{title}*: The title of the application. | *{title}*: The title of the application. | ||||||
| 
 | 
 | ||||||
| *{app_id}*: The app_id (== application name) of the application. | *{app_id}*: The app_id (== application name) of the application. | ||||||
|  | @ -105,7 +111,10 @@ Addressed by *wlr/taskbar* | ||||||
| 	"on-click-middle": "close", | 	"on-click-middle": "close", | ||||||
| 	"ignore-list": [ | 	"ignore-list": [ | ||||||
| 	    "Alacritty" | 	    "Alacritty" | ||||||
| 	] | 	], | ||||||
|  | 	"app_ids-mapping": { | ||||||
|  | 		"firefoxdeveloperedition": "firefox-developer-edition" | ||||||
|  | 	} | ||||||
| } | } | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -0,0 +1,87 @@ | ||||||
|  | waybar-wlr-workspaces(5) | ||||||
|  | 
 | ||||||
|  | # NAME | ||||||
|  | 
 | ||||||
|  | waybar - wlr workspaces module | ||||||
|  | 
 | ||||||
|  | # DESCRIPTION | ||||||
|  | 
 | ||||||
|  | The *workspaces* module displays the currently used workspaces in wayland compositor. | ||||||
|  | 
 | ||||||
|  | # CONFIGURATION | ||||||
|  | 
 | ||||||
|  | Addressed by *wlr/workspaces* | ||||||
|  | 
 | ||||||
|  | *format*: ++ | ||||||
|  | 	typeof: string ++ | ||||||
|  | 	default: {name} ++ | ||||||
|  | 	The format, how information should be displayed. | ||||||
|  | 
 | ||||||
|  | *format-icons*: ++ | ||||||
|  | 	typeof: array ++ | ||||||
|  | 	Based on the workspace name and state, the corresponding icon gets selected. See *icons*. | ||||||
|  | 
 | ||||||
|  | *sort-by-name*: ++ | ||||||
|  | 	typeof: bool ++ | ||||||
|  | 	default: true ++ | ||||||
|  | 	Should workspaces be sorted by name. | ||||||
|  | 
 | ||||||
|  | *sort-by-coordinates*: ++ | ||||||
|  | 	typeof: bool ++ | ||||||
|  | 	default: true ++ | ||||||
|  | 	Should workspaces be sorted by coordinates. | ||||||
|  | 	Note that if both  *sort-by-name* and *sort-by-coordinates* are true sort by name will be first. | ||||||
|  | 	If both are false - sort by id will be performed. | ||||||
|  | 
 | ||||||
|  | *all-outputs*: ++ | ||||||
|  | 	typeof: bool ++ | ||||||
|  | 	default: false ++ | ||||||
|  | 	If set to false workspaces group will be shown only in assigned output. Otherwise all workspace groups are shown. | ||||||
|  | 
 | ||||||
|  | *active-only*: ++ | ||||||
|  | 	typeof: bool ++ | ||||||
|  | 	default: false ++ | ||||||
|  | 	If set to true only active or urgent workspaces will be shown. | ||||||
|  | 
 | ||||||
|  | # FORMAT REPLACEMENTS | ||||||
|  | 
 | ||||||
|  | *{name}*: Name of workspace assigned by compositor | ||||||
|  | 
 | ||||||
|  | *{icon}*: Icon, as defined in *format-icons*. | ||||||
|  | 
 | ||||||
|  | # CLICK ACTIONS | ||||||
|  | 
 | ||||||
|  | *activate*: Switch to workspace. | ||||||
|  | *close*: Close the workspace. | ||||||
|  | 
 | ||||||
|  | # ICONS | ||||||
|  | 
 | ||||||
|  | Additional to workspace name matching, the following *format-icons* can be set. | ||||||
|  | 
 | ||||||
|  | - *default*: Will be shown, when no string match is found. | ||||||
|  | - *focused*: Will be shown, when workspace is focused | ||||||
|  | 
 | ||||||
|  | # EXAMPLES | ||||||
|  | 
 | ||||||
|  | ``` | ||||||
|  | "wlr/workspaces": { | ||||||
|  | 	"format": "{name}: {icon}", | ||||||
|  | 	"format-icons": { | ||||||
|  | 		"1": "", | ||||||
|  | 		"2": "", | ||||||
|  | 		"3": "", | ||||||
|  | 		"4": "", | ||||||
|  | 		"5": "", | ||||||
|  | 		"focused": "", | ||||||
|  | 		"default": "" | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | # Style | ||||||
|  | 
 | ||||||
|  | - *#workspaces* | ||||||
|  | - *#workspaces button* | ||||||
|  | - *#workspaces button.active* | ||||||
|  | - *#workspaces button.urgent* | ||||||
|  | - *#workspaces button.hidden* | ||||||
|  | @ -72,14 +72,25 @@ Also a minimal example configuration can be found on the at the bottom of this m | ||||||
| 	typeof: string ++ | 	typeof: string ++ | ||||||
| 	Optional name added as a CSS class, for styling multiple waybars. | 	Optional name added as a CSS class, for styling multiple waybars. | ||||||
| 
 | 
 | ||||||
|  | *mode* ++ | ||||||
|  | 	typeof: string ++ | ||||||
|  | 	Selects one of the preconfigured display modes. This is an equivalent of the sway-bar(5) *mode* command and supports the same values: *dock*, *hide*, *invisible*, *overlay*. ++ | ||||||
|  | 	Note: *hide* and *invisible* modes may be not as useful without Sway IPC. | ||||||
|  | 
 | ||||||
| *exclusive* ++ | *exclusive* ++ | ||||||
| 	typeof: bool ++ | 	typeof: bool ++ | ||||||
| 	default: *true* unless the layer is set to *overlay* ++ | 	default: *true* ++ | ||||||
| 	Option to request an exclusive zone from the compositor. Disable this to allow drawing application windows underneath or on top of the bar. | 	Option to request an exclusive zone from the compositor. Disable this to allow drawing application windows underneath or on top of the bar. | ||||||
| 
 | 
 | ||||||
|  | *fixed-center* ++ | ||||||
|  | 	typeof: bool ++ | ||||||
|  | 	default: *true* | ||||||
|  | 	Prefer fixed center position for the `modules-center` block. The center block will stay in the middle of the bar whenever possible. It can still be pushed around if other blocks need more space. | ||||||
|  | 	When false, the center block is centered in the space between the left and right block. | ||||||
|  | 
 | ||||||
| *passthrough* ++ | *passthrough* ++ | ||||||
| 	typeof: bool ++ | 	typeof: bool ++ | ||||||
| 	default: *false* unless the layer is set to *overlay* ++ | 	default: *false* ++ | ||||||
| 	Option to pass any pointer events to the window under the bar. | 	Option to pass any pointer events to the window under the bar. | ||||||
| 	Intended to be used with either *top* or *overlay* layers and without exclusive zone. | 	Intended to be used with either *top* or *overlay* layers and without exclusive zone. | ||||||
| 
 | 
 | ||||||
|  | @ -89,6 +100,16 @@ Also a minimal example configuration can be found on the at the bottom of this m | ||||||
| 	Option to disable the use of gtk-layer-shell for popups. | 	Option to disable the use of gtk-layer-shell for popups. | ||||||
| 	Only functional if compiled with gtk-layer-shell support. | 	Only functional if compiled with gtk-layer-shell support. | ||||||
| 
 | 
 | ||||||
|  | *ipc* ++ | ||||||
|  | 	typeof: bool ++ | ||||||
|  | 	default: false ++ | ||||||
|  | 	Option to subscribe to the Sway IPC bar configuration and visibility events and control waybar with *swaymsg bar* commands. ++ | ||||||
|  | 	Requires *bar_id* value from sway configuration to be either passed with the *-b* commandline argument or specified with the *id* option. | ||||||
|  | 
 | ||||||
|  | *id* ++ | ||||||
|  | 	typeof: string ++ | ||||||
|  | 	*bar_id* for the Sway IPC. Use this if you need to override the value passed with the *-b bar_id* commandline argument for the specific bar instance. | ||||||
|  | 
 | ||||||
| *include* ++ | *include* ++ | ||||||
| 	typeof: string|array ++ | 	typeof: string|array ++ | ||||||
| 	Paths to additional configuration files. | 	Paths to additional configuration files. | ||||||
|  | @ -203,6 +224,28 @@ When positioning Waybar on the left or right side of the screen, sometimes it's | ||||||
| 
 | 
 | ||||||
| Valid options for the "rotate" property are: 0, 90, 180 and 270. | Valid options for the "rotate" property are: 0, 90, 180 and 270. | ||||||
| 
 | 
 | ||||||
|  | ## Grouping modules | ||||||
|  | 
 | ||||||
|  | Module groups allow stacking modules in the direction orthogonal to the bar direction. When the bar is positioned on the top or bottom of the screen, modules in a group are stacked vertically. Likewise, when positioned on the left or right, modules in a group are stacked horizontally. | ||||||
|  | 
 | ||||||
|  | A module group is defined by specifying a module named "group/some-group-name". The group must also be configured with a list of contained modules. Example: | ||||||
|  | 
 | ||||||
|  | ``` | ||||||
|  | { | ||||||
|  | 	"modules-right": ["group/hardware", "clock"], | ||||||
|  | 
 | ||||||
|  | 	"group/hardware": { | ||||||
|  | 		"modules": [ | ||||||
|  | 			"cpu", | ||||||
|  | 			"memory", | ||||||
|  | 			"battery" | ||||||
|  | 		] | ||||||
|  | 	}, | ||||||
|  | 
 | ||||||
|  | 	... | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
| # SUPPORTED MODULES | # SUPPORTED MODULES | ||||||
| 
 | 
 | ||||||
| - *waybar-backlight(5)* | - *waybar-backlight(5)* | ||||||
|  | @ -224,5 +267,6 @@ Valid options for the "rotate" property are: 0, 90, 180 and 270. | ||||||
| - *waybar-sway-window(5)* | - *waybar-sway-window(5)* | ||||||
| - *waybar-sway-workspaces(5)* | - *waybar-sway-workspaces(5)* | ||||||
| - *waybar-wlr-taskbar(5)* | - *waybar-wlr-taskbar(5)* | ||||||
|  | - *waybar-wlr-workspaces(5)* | ||||||
| - *waybar-temperature(5)* | - *waybar-temperature(5)* | ||||||
| - *waybar-tray(5)* | - *waybar-tray(5)* | ||||||
|  |  | ||||||
							
								
								
									
										23
									
								
								meson.build
								
								
								
								
							
							
						
						
									
										23
									
								
								meson.build
								
								
								
								
							|  | @ -86,7 +86,7 @@ wayland_cursor = dependency('wayland-cursor') | ||||||
| wayland_protos = dependency('wayland-protocols') | wayland_protos = dependency('wayland-protocols') | ||||||
| gtkmm = dependency('gtkmm-3.0', version : ['>=3.22.0']) | gtkmm = dependency('gtkmm-3.0', version : ['>=3.22.0']) | ||||||
| dbusmenu_gtk = dependency('dbusmenu-gtk3-0.4', required: get_option('dbusmenu-gtk')) | dbusmenu_gtk = dependency('dbusmenu-gtk3-0.4', required: get_option('dbusmenu-gtk')) | ||||||
| giounix = dependency('gio-unix-2.0', required: get_option('dbusmenu-gtk')) | giounix = dependency('gio-unix-2.0', required: (get_option('dbusmenu-gtk').enabled() or get_option('logind').enabled())) | ||||||
| jsoncpp = dependency('jsoncpp') | jsoncpp = dependency('jsoncpp') | ||||||
| sigcpp = dependency('sigc++-2.0') | sigcpp = dependency('sigc++-2.0') | ||||||
| libepoll = dependency('epoll-shim', required: false) | libepoll = dependency('epoll-shim', required: false) | ||||||
|  | @ -150,6 +150,7 @@ src_files = files( | ||||||
|     'src/bar.cpp', |     'src/bar.cpp', | ||||||
|     'src/client.cpp', |     'src/client.cpp', | ||||||
|     'src/config.cpp', |     'src/config.cpp', | ||||||
|  |     'src/group.cpp', | ||||||
|     'src/util/ustring_clen.cpp' |     'src/util/ustring_clen.cpp' | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | @ -177,6 +178,7 @@ endif | ||||||
| add_project_arguments('-DHAVE_SWAY', language: 'cpp') | add_project_arguments('-DHAVE_SWAY', language: 'cpp') | ||||||
| src_files += [ | src_files += [ | ||||||
|     'src/modules/sway/ipc/client.cpp', |     'src/modules/sway/ipc/client.cpp', | ||||||
|  |     'src/modules/sway/bar.cpp', | ||||||
|     'src/modules/sway/mode.cpp', |     'src/modules/sway/mode.cpp', | ||||||
|     'src/modules/sway/language.cpp', |     'src/modules/sway/language.cpp', | ||||||
|     'src/modules/sway/window.cpp', |     'src/modules/sway/window.cpp', | ||||||
|  | @ -186,6 +188,8 @@ src_files += [ | ||||||
| if true | if true | ||||||
|     add_project_arguments('-DHAVE_WLR', language: 'cpp') |     add_project_arguments('-DHAVE_WLR', language: 'cpp') | ||||||
|     src_files += 'src/modules/wlr/taskbar.cpp' |     src_files += 'src/modules/wlr/taskbar.cpp' | ||||||
|  |     src_files += 'src/modules/wlr/workspace_manager.cpp' | ||||||
|  |     src_files += 'src/modules/wlr/workspace_manager_binding.cpp' | ||||||
| endif | endif | ||||||
| 
 | 
 | ||||||
| if true | if true | ||||||
|  | @ -238,6 +242,11 @@ if libsndio.found() | ||||||
|     src_files += 'src/modules/sndio.cpp' |     src_files += 'src/modules/sndio.cpp' | ||||||
| endif | endif | ||||||
| 
 | 
 | ||||||
|  | if (giounix.found() and not get_option('logind').disabled()) | ||||||
|  |     add_project_arguments('-DHAVE_GIO_UNIX', language: 'cpp') | ||||||
|  |     src_files += 'src/modules/inhibitor.cpp' | ||||||
|  | endif | ||||||
|  | 
 | ||||||
| if get_option('rfkill').enabled() | if get_option('rfkill').enabled() | ||||||
|     if is_linux |     if is_linux | ||||||
|         add_project_arguments('-DWANT_RFKILL', language: 'cpp') |         add_project_arguments('-DWANT_RFKILL', language: 'cpp') | ||||||
|  | @ -255,6 +264,10 @@ else | ||||||
|     src_files += 'src/modules/simpleclock.cpp' |     src_files += 'src/modules/simpleclock.cpp' | ||||||
| endif | endif | ||||||
| 
 | 
 | ||||||
|  | if get_option('experimental') | ||||||
|  |     add_project_arguments('-DUSE_EXPERIMENTAL', language: 'cpp') | ||||||
|  | endif | ||||||
|  | 
 | ||||||
| subdir('protocol') | subdir('protocol') | ||||||
| 
 | 
 | ||||||
| executable( | executable( | ||||||
|  | @ -282,7 +295,7 @@ executable( | ||||||
|         gtk_layer_shell, |         gtk_layer_shell, | ||||||
|         libsndio, |         libsndio, | ||||||
|         tz_dep, |         tz_dep, | ||||||
| 				xkbregistry  | 		xkbregistry  | ||||||
|     ], |     ], | ||||||
|     include_directories: [include_directories('include')], |     include_directories: [include_directories('include')], | ||||||
|     install: true, |     install: true, | ||||||
|  | @ -334,10 +347,15 @@ if scdoc.found() | ||||||
|         'waybar-tray.5.scd', |         'waybar-tray.5.scd', | ||||||
|         'waybar-states.5.scd', |         'waybar-states.5.scd', | ||||||
|         'waybar-wlr-taskbar.5.scd', |         'waybar-wlr-taskbar.5.scd', | ||||||
|  |         'waybar-wlr-workspaces.5.scd', | ||||||
|         'waybar-bluetooth.5.scd', |         'waybar-bluetooth.5.scd', | ||||||
|         'waybar-sndio.5.scd', |         'waybar-sndio.5.scd', | ||||||
|     ] |     ] | ||||||
| 
 | 
 | ||||||
|  |     if (giounix.found() and not get_option('logind').disabled()) | ||||||
|  |         man_files += 'waybar-inhibitor.5.scd' | ||||||
|  |     endif | ||||||
|  | 
 | ||||||
|     foreach file : man_files |     foreach file : man_files | ||||||
|         path = '@0@'.format(file) |         path = '@0@'.format(file) | ||||||
|         basename = path.split('/')[-1] |         basename = path.split('/')[-1] | ||||||
|  | @ -380,3 +398,4 @@ if clangtidy.found() | ||||||
|             '-p', meson.build_root() |             '-p', meson.build_root() | ||||||
|         ] + src_files) |         ] + src_files) | ||||||
| endif | endif | ||||||
|  | 
 | ||||||
|  |  | ||||||
|  | @ -10,4 +10,6 @@ option('mpd', type: 'feature', value: 'auto', description: 'Enable support for t | ||||||
| option('gtk-layer-shell', type: 'feature', value: 'auto', description: 'Use gtk-layer-shell library for popups support') | option('gtk-layer-shell', type: 'feature', value: 'auto', description: 'Use gtk-layer-shell library for popups support') | ||||||
| option('rfkill', type: 'feature', value: 'auto', description: 'Enable support for RFKILL') | option('rfkill', type: 'feature', value: 'auto', description: 'Enable support for RFKILL') | ||||||
| option('sndio', type: 'feature', value: 'auto', description: 'Enable support for sndio') | option('sndio', type: 'feature', value: 'auto', description: 'Enable support for sndio') | ||||||
|  | option('logind', type: 'feature', value: 'auto', description: 'Enable support for logind') | ||||||
| option('tests', type: 'feature', value: 'auto', description: 'Enable tests') | option('tests', type: 'feature', value: 'auto', description: 'Enable tests') | ||||||
|  | option('experimental', type : 'boolean', value : false, description: 'Enable experimental features') | ||||||
|  |  | ||||||
|  | @ -0,0 +1,306 @@ | ||||||
|  | <?xml version="1.0" encoding="UTF-8"?> | ||||||
|  | <protocol name="ext_workspace_unstable_v1"> | ||||||
|  |   <copyright> | ||||||
|  |     Copyright © 2019 Christopher Billington | ||||||
|  |     Copyright © 2020 Ilia Bozhinov | ||||||
|  | 
 | ||||||
|  |     Permission to use, copy, modify, distribute, and sell this | ||||||
|  |     software and its documentation for any purpose is hereby granted | ||||||
|  |     without fee, provided that the above copyright notice appear in | ||||||
|  |     all copies and that both that copyright notice and this permission | ||||||
|  |     notice appear in supporting documentation, and that the name of | ||||||
|  |     the copyright holders not be used in advertising or publicity | ||||||
|  |     pertaining to distribution of the software without specific, | ||||||
|  |     written prior permission.  The copyright holders make no | ||||||
|  |     representations about the suitability of this software for any | ||||||
|  |     purpose.  It is provided "as is" without express or implied | ||||||
|  |     warranty. | ||||||
|  | 
 | ||||||
|  |     THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS | ||||||
|  |     SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND | ||||||
|  |     FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY | ||||||
|  |     SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | ||||||
|  |     WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN | ||||||
|  |     AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, | ||||||
|  |     ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF | ||||||
|  |     THIS SOFTWARE. | ||||||
|  |   </copyright> | ||||||
|  | 
 | ||||||
|  |   <interface name="zext_workspace_manager_v1" version="1"> | ||||||
|  |     <description summary="list and control workspaces"> | ||||||
|  |       Workspaces, also called virtual desktops, are groups of surfaces. A | ||||||
|  |       compositor with a concept of workspaces may only show some such groups of | ||||||
|  |       surfaces (those of 'active' workspaces) at a time. 'Activating' a | ||||||
|  |       workspace is a request for the compositor to display that workspace's | ||||||
|  |       surfaces as normal, whereas the compositor may hide or otherwise | ||||||
|  |       de-emphasise surfaces that are associated only with 'inactive' workspaces. | ||||||
|  |       Workspaces are grouped by which sets of outputs they correspond to, and | ||||||
|  |       may contain surfaces only from those outputs. In this way, it is possible | ||||||
|  |       for each output to have its own set of workspaces, or for all outputs (or | ||||||
|  |       any other arbitrary grouping) to share workspaces. Compositors may | ||||||
|  |       optionally conceptually arrange each group of workspaces in an | ||||||
|  |       N-dimensional grid. | ||||||
|  | 
 | ||||||
|  |       The purpose of this protocol is to enable the creation of taskbars and | ||||||
|  |       docks by providing them with a list of workspaces and their properties, | ||||||
|  |       and allowing them to activate and deactivate workspaces. | ||||||
|  | 
 | ||||||
|  |       After a client binds the zext_workspace_manager_v1, each workspace will be | ||||||
|  |       sent via the workspace event. | ||||||
|  |     </description> | ||||||
|  | 
 | ||||||
|  |     <event name="workspace_group"> | ||||||
|  |       <description summary="a workspace group has been created"> | ||||||
|  |         This event is emitted whenever a new workspace group has been created. | ||||||
|  | 
 | ||||||
|  |         All initial details of the workspace group (workspaces, outputs) will be | ||||||
|  |         sent immediately after this event via the corresponding events in | ||||||
|  |         zext_workspace_group_handle_v1. | ||||||
|  |       </description> | ||||||
|  |       <arg name="workspace_group" type="new_id" interface="zext_workspace_group_handle_v1"/> | ||||||
|  |     </event> | ||||||
|  | 
 | ||||||
|  |     <request name="commit"> | ||||||
|  |       <description summary="all requests about the workspaces have been sent"> | ||||||
|  |         The client must send this request after it has finished sending other | ||||||
|  |         requests. The compositor must process a series of requests preceding a | ||||||
|  |         commit request atomically. | ||||||
|  | 
 | ||||||
|  |         This allows changes to the workspace properties to be seen as atomic, | ||||||
|  |         even if they happen via multiple events, and even if they involve | ||||||
|  |         multiple zext_workspace_handle_v1 objects, for example, deactivating one | ||||||
|  |         workspace and activating another. | ||||||
|  |       </description> | ||||||
|  |     </request> | ||||||
|  | 
 | ||||||
|  |     <event name="done"> | ||||||
|  |       <description summary="all information about the workspace groups has been sent"> | ||||||
|  |         This event is sent after all changes in all workspace groups have been | ||||||
|  |         sent. | ||||||
|  | 
 | ||||||
|  |         This allows changes to one or more zext_workspace_group_handle_v1 | ||||||
|  |         properties to be seen as atomic, even if they happen via multiple | ||||||
|  |         events. In particular, an output moving from one workspace group to | ||||||
|  |         another sends an output_enter event and an output_leave event to the two | ||||||
|  |         zext_workspace_group_handle_v1 objects in question. The compositor sends | ||||||
|  |         the done event only after updating the output information in both | ||||||
|  |         workspace groups. | ||||||
|  |       </description> | ||||||
|  |     </event> | ||||||
|  | 
 | ||||||
|  |     <event name="finished"> | ||||||
|  |       <description summary="the compositor has finished with the workspace_manager"> | ||||||
|  |         This event indicates that the compositor is done sending events to the | ||||||
|  |         zext_workspace_manager_v1. The server will destroy the object | ||||||
|  |         immediately after sending this request, so it will become invalid and | ||||||
|  |         the client should free any resources associated with it. | ||||||
|  |       </description> | ||||||
|  |     </event> | ||||||
|  | 
 | ||||||
|  |     <request name="stop"> | ||||||
|  |       <description summary="stop sending events"> | ||||||
|  |         Indicates the client no longer wishes to receive events for new | ||||||
|  |         workspace groups. However the compositor may emit further workspace | ||||||
|  |         events, until the finished event is emitted. | ||||||
|  | 
 | ||||||
|  |         The client must not send any more requests after this one. | ||||||
|  |       </description> | ||||||
|  |     </request> | ||||||
|  |   </interface> | ||||||
|  | 
 | ||||||
|  |   <interface name="zext_workspace_group_handle_v1" version="1"> | ||||||
|  |     <description summary="a workspace group assigned to a set of outputs"> | ||||||
|  |       A zext_workspace_group_handle_v1 object represents a a workspace group | ||||||
|  |       that is assigned a set of outputs and contains a number of workspaces. | ||||||
|  | 
 | ||||||
|  |       The set of outputs assigned to the workspace group is conveyed to the client via | ||||||
|  |       output_enter and output_leave events, and its workspaces are conveyed with | ||||||
|  |       workspace events. | ||||||
|  | 
 | ||||||
|  |       For example, a compositor which has a set of workspaces for each output may | ||||||
|  |       advertise a workspace group (and its workspaces) per output, whereas a compositor | ||||||
|  |       where a workspace spans all outputs may advertise a single workspace group for all | ||||||
|  |       outputs. | ||||||
|  |     </description> | ||||||
|  | 
 | ||||||
|  |     <event name="output_enter"> | ||||||
|  |       <description summary="output assigned to workspace group"> | ||||||
|  |         This event is emitted whenever an output is assigned to the workspace | ||||||
|  |         group. | ||||||
|  |       </description> | ||||||
|  |       <arg name="output" type="object" interface="wl_output"/> | ||||||
|  |     </event> | ||||||
|  | 
 | ||||||
|  |     <event name="output_leave"> | ||||||
|  |       <description summary="output removed from workspace group"> | ||||||
|  |         This event is emitted whenever an output is removed from the workspace | ||||||
|  |         group. | ||||||
|  |       </description> | ||||||
|  |       <arg name="output" type="object" interface="wl_output"/> | ||||||
|  |     </event> | ||||||
|  | 
 | ||||||
|  |     <event name="workspace"> | ||||||
|  |       <description summary="workspace added to workspace group"> | ||||||
|  |         This event is emitted whenever a new workspace has been created. | ||||||
|  | 
 | ||||||
|  |         All initial details of the workspace (name, coordinates, state) will | ||||||
|  |         be sent immediately after this event via the corresponding events in | ||||||
|  |         zext_workspace_handle_v1. | ||||||
|  |       </description> | ||||||
|  |       <arg name="workspace" type="new_id" interface="zext_workspace_handle_v1"/> | ||||||
|  |     </event> | ||||||
|  | 
 | ||||||
|  |     <event name="remove"> | ||||||
|  |       <description summary="this workspace group has been destroyed"> | ||||||
|  |         This event means the zext_workspace_group_handle_v1 has been destroyed. | ||||||
|  |         It is guaranteed there won't be any more events for this | ||||||
|  |         zext_workspace_group_handle_v1. The zext_workspace_group_handle_v1 becomes | ||||||
|  |         inert so any requests will be ignored except the destroy request. | ||||||
|  | 
 | ||||||
|  |         The compositor must remove all workspaces belonging to a workspace group | ||||||
|  |         before removing the workspace group. | ||||||
|  |       </description> | ||||||
|  |     </event> | ||||||
|  | 
 | ||||||
|  |     <request name="create_workspace"> | ||||||
|  |       <description summary="create a new workspace"> | ||||||
|  |         Request that the compositor create a new workspace with the given name. | ||||||
|  | 
 | ||||||
|  |         There is no guarantee that the compositor will create a new workspace, | ||||||
|  |         or that the created workspace will have the provided name. | ||||||
|  |       </description> | ||||||
|  |       <arg name="workspace" type="string"/> | ||||||
|  |     </request> | ||||||
|  | 
 | ||||||
|  |     <request name="destroy" type="destructor"> | ||||||
|  |       <description summary="destroy the zext_workspace_handle_v1 object"> | ||||||
|  |         Destroys the zext_workspace_handle_v1 object. | ||||||
|  | 
 | ||||||
|  |         This request should be called either when the client does not want to | ||||||
|  |         use the workspace object any more or after the remove event to finalize | ||||||
|  |         the destruction of the object. | ||||||
|  |       </description> | ||||||
|  |     </request> | ||||||
|  |   </interface> | ||||||
|  | 
 | ||||||
|  |   <interface name="zext_workspace_handle_v1" version="1"> | ||||||
|  |     <description summary="a workspace handing a group of surfaces"> | ||||||
|  |       A zext_workspace_handle_v1 object represents a a workspace that handles a | ||||||
|  |       group of surfaces. | ||||||
|  | 
 | ||||||
|  |       Each workspace has a name, conveyed to the client with the name event; a | ||||||
|  |       list of states, conveyed to the client with the state event; and | ||||||
|  |       optionally a set of coordinates, conveyed to the client with the | ||||||
|  |       coordinates event. The client may request that the compositor activate or | ||||||
|  |       deactivate the workspace. | ||||||
|  | 
 | ||||||
|  |       Each workspace can belong to only a single workspace group. | ||||||
|  |       Depepending on the compositor policy, there might be workspaces with | ||||||
|  |       the same name in different workspace groups, but these workspaces are still | ||||||
|  |       separate (e.g. one of them might be active while the other is not). | ||||||
|  |     </description> | ||||||
|  | 
 | ||||||
|  |     <event name="name"> | ||||||
|  |       <description summary="workspace name changed"> | ||||||
|  |         This event is emitted immediately after the zext_workspace_handle_v1 is | ||||||
|  |         created and whenever the name of the workspace changes. | ||||||
|  |       </description> | ||||||
|  |       <arg name="name" type="string"/> | ||||||
|  |     </event> | ||||||
|  | 
 | ||||||
|  |     <event name="coordinates"> | ||||||
|  |       <description summary="workspace coordinates changed"> | ||||||
|  |         This event is used to organize workspaces into an N-dimensional grid | ||||||
|  |         within a workspace group, and if supported, is emitted immediately after | ||||||
|  |         the zext_workspace_handle_v1 is created and whenever the coordinates of | ||||||
|  |         the workspace change. Compositors may not send this event if they do not | ||||||
|  |         conceptually arrange workspaces in this way. If compositors simply | ||||||
|  |         number workspaces, without any geometric interpretation, they may send | ||||||
|  |         1D coordinates, which clients should not interpret as implying any | ||||||
|  |         geometry. Sending an empty array means that the compositor no longer | ||||||
|  |         orders the workspace geometrically. | ||||||
|  | 
 | ||||||
|  |         Coordinates have an arbitrary number of dimensions N with an uint32 | ||||||
|  |         position along each dimension. By convention if N > 1, the first | ||||||
|  |         dimension is X, the second Y, the third Z, and so on. The compositor may | ||||||
|  |         chose to utilize these events for a more novel workspace layout | ||||||
|  |         convention, however. No guarantee is made about the grid being filled or | ||||||
|  |         bounded; there may be a workspace at coordinate 1 and another at | ||||||
|  |         coordinate 1000 and none in between. Within a workspace group, however, | ||||||
|  |         workspaces must have unique coordinates of equal dimensionality. | ||||||
|  |       </description> | ||||||
|  |       <arg name="coordinates" type="array"/> | ||||||
|  |     </event> | ||||||
|  | 
 | ||||||
|  |     <event name="state"> | ||||||
|  |       <description summary="the state of the workspace changed"> | ||||||
|  |         This event is emitted immediately after the zext_workspace_handle_v1 is | ||||||
|  |         created and each time the workspace state changes, either because of a | ||||||
|  |         compositor action or because of a request in this protocol. | ||||||
|  |       </description> | ||||||
|  |       <arg name="state" type="array"/> | ||||||
|  |     </event> | ||||||
|  | 
 | ||||||
|  |     <enum name="state"> | ||||||
|  |       <description summary="types of states on the workspace"> | ||||||
|  |         The different states that a workspace can have. | ||||||
|  |       </description> | ||||||
|  | 
 | ||||||
|  |       <entry name="active" value="0" summary="the workspace is active"/> | ||||||
|  |       <entry name="urgent" value="1" summary="the workspace requests attention"/> | ||||||
|  |       <entry name="hidden" value="2"> | ||||||
|  |         <description summary="the workspace is not visible"> | ||||||
|  |           The workspace is not visible in its workspace group, and clients | ||||||
|  |           attempting to visualize the compositor workspace state should not | ||||||
|  |           display such workspaces. | ||||||
|  |         </description> | ||||||
|  |       </entry> | ||||||
|  |     </enum> | ||||||
|  | 
 | ||||||
|  |     <event name="remove"> | ||||||
|  |       <description summary="this workspace has been destroyed"> | ||||||
|  |         This event means the zext_workspace_handle_v1 has been destroyed. It is | ||||||
|  |         guaranteed there won't be any more events for this | ||||||
|  |         zext_workspace_handle_v1. The zext_workspace_handle_v1 becomes inert so | ||||||
|  |         any requests will be ignored except the destroy request. | ||||||
|  |       </description> | ||||||
|  |     </event> | ||||||
|  | 
 | ||||||
|  |     <request name="destroy" type="destructor"> | ||||||
|  |       <description summary="destroy the zext_workspace_handle_v1 object"> | ||||||
|  |         Destroys the zext_workspace_handle_v1 object. | ||||||
|  | 
 | ||||||
|  |         This request should be called either when the client does not want to | ||||||
|  |         use the workspace object any more or after the remove event to finalize | ||||||
|  |         the destruction of the object. | ||||||
|  |       </description> | ||||||
|  |     </request> | ||||||
|  | 
 | ||||||
|  |     <request name="activate"> | ||||||
|  |       <description summary="activate the workspace"> | ||||||
|  |         Request that this workspace be activated. | ||||||
|  | 
 | ||||||
|  |         There is no guarantee the workspace will be actually activated, and | ||||||
|  |         behaviour may be compositor-dependent. For example, activating a | ||||||
|  |         workspace may or may not deactivate all other workspaces in the same | ||||||
|  |         group. | ||||||
|  |       </description> | ||||||
|  |     </request> | ||||||
|  | 
 | ||||||
|  |     <request name="deactivate"> | ||||||
|  |       <description summary="activate the workspace"> | ||||||
|  |         Request that this workspace be deactivated. | ||||||
|  | 
 | ||||||
|  |         There is no guarantee the workspace will be actually deactivated. | ||||||
|  |       </description> | ||||||
|  |     </request> | ||||||
|  | 
 | ||||||
|  |     <request name="remove"> | ||||||
|  |       <description summary="remove the workspace"> | ||||||
|  |         Request that this workspace be removed. | ||||||
|  | 
 | ||||||
|  |         There is no guarantee the workspace will be actually removed. | ||||||
|  |       </description> | ||||||
|  |     </request> | ||||||
|  |   </interface> | ||||||
|  | </protocol> | ||||||
|  | @ -27,7 +27,9 @@ client_protocols = [ | ||||||
| 	[wl_protocol_dir, 'unstable/idle-inhibit/idle-inhibit-unstable-v1.xml'], | 	[wl_protocol_dir, 'unstable/idle-inhibit/idle-inhibit-unstable-v1.xml'], | ||||||
| 	['wlr-layer-shell-unstable-v1.xml'], | 	['wlr-layer-shell-unstable-v1.xml'], | ||||||
| 	['wlr-foreign-toplevel-management-unstable-v1.xml'], | 	['wlr-foreign-toplevel-management-unstable-v1.xml'], | ||||||
|  | 	['ext-workspace-unstable-v1.xml'], | ||||||
| 	['river-status-unstable-v1.xml'], | 	['river-status-unstable-v1.xml'], | ||||||
|  | 	['river-control-unstable-v1.xml'], | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
| client_protos_src = [] | client_protos_src = [] | ||||||
|  |  | ||||||
|  | @ -0,0 +1,85 @@ | ||||||
|  | <?xml version="1.0" encoding="UTF-8"?> | ||||||
|  | <protocol name="river_control_unstable_v1"> | ||||||
|  |   <copyright> | ||||||
|  |     Copyright 2020 The River Developers | ||||||
|  | 
 | ||||||
|  |     Permission to use, copy, modify, and/or distribute this software for any | ||||||
|  |     purpose with or without fee is hereby granted, provided that the above | ||||||
|  |     copyright notice and this permission notice appear in all copies. | ||||||
|  | 
 | ||||||
|  |     THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | ||||||
|  |     WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | ||||||
|  |     MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | ||||||
|  |     ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | ||||||
|  |     WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | ||||||
|  |     ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | ||||||
|  |     OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | ||||||
|  |   </copyright> | ||||||
|  | 
 | ||||||
|  |   <interface name="zriver_control_v1" version="1"> | ||||||
|  |     <description summary="run compositor commands"> | ||||||
|  |       This interface allows clients to run compositor commands and receive a | ||||||
|  |       success/failure response with output or a failure message respectively. | ||||||
|  | 
 | ||||||
|  |       Each command is built up in a series of add_argument requests and | ||||||
|  |       executed with a run_command request. The first argument is the command | ||||||
|  |       to be run. | ||||||
|  | 
 | ||||||
|  |       A complete list of commands should be made available in the man page of | ||||||
|  |       the compositor. | ||||||
|  |     </description> | ||||||
|  | 
 | ||||||
|  |     <request name="destroy" type="destructor"> | ||||||
|  |       <description summary="destroy the river_control object"> | ||||||
|  |         This request indicates that the client will not use the | ||||||
|  |         river_control object any more. Objects that have been created | ||||||
|  |         through this instance are not affected. | ||||||
|  |       </description> | ||||||
|  |     </request> | ||||||
|  | 
 | ||||||
|  |     <request name="add_argument"> | ||||||
|  |       <description summary="add an argument to the current command"> | ||||||
|  |         Arguments are stored by the server in the order they were sent until | ||||||
|  |         the run_command request is made. | ||||||
|  |       </description> | ||||||
|  |       <arg name="argument" type="string" summary="the argument to add"/> | ||||||
|  |     </request> | ||||||
|  | 
 | ||||||
|  |     <request name="run_command"> | ||||||
|  |       <description summary="run the current command"> | ||||||
|  |         Execute the command built up using the add_argument request for the | ||||||
|  |         given seat. | ||||||
|  |       </description> | ||||||
|  |       <arg name="seat" type="object" interface="wl_seat"/> | ||||||
|  |       <arg name="callback" type="new_id" interface="zriver_command_callback_v1" | ||||||
|  |         summary="callback object"/> | ||||||
|  |     </request> | ||||||
|  |   </interface> | ||||||
|  | 
 | ||||||
|  |   <interface name="zriver_command_callback_v1" version="1"> | ||||||
|  |     <description summary="callback object"> | ||||||
|  |       This object is created by the run_command request. Exactly one of the | ||||||
|  |       success or failure events will be sent. This object will be destroyed | ||||||
|  |       by the compositor after one of the events is sent. | ||||||
|  |     </description> | ||||||
|  | 
 | ||||||
|  |     <event name="success"> | ||||||
|  |       <description summary="command successful"> | ||||||
|  |         Sent when the command has been successfully received and executed by | ||||||
|  |         the compositor. Some commands may produce output, in which case the | ||||||
|  |         output argument will be a non-empty string. | ||||||
|  |       </description> | ||||||
|  |       <arg name="output" type="string" summary="the output of the command"/> | ||||||
|  |     </event> | ||||||
|  | 
 | ||||||
|  |     <event name="failure"> | ||||||
|  |       <description summary="command failed"> | ||||||
|  |         Sent when the command could not be carried out. This could be due to | ||||||
|  |         sending a non-existent command, no command, not enough arguments, too | ||||||
|  |         many arguments, invalid arguments, etc. | ||||||
|  |       </description> | ||||||
|  |       <arg name="failure_message" type="string" | ||||||
|  |         summary="a message explaining why failure occurred"/> | ||||||
|  |     </event> | ||||||
|  |   </interface> | ||||||
|  | </protocol> | ||||||
|  | @ -110,6 +110,7 @@ def main(): | ||||||
| 
 | 
 | ||||||
|     signal.signal(signal.SIGINT, signal_handler) |     signal.signal(signal.SIGINT, signal_handler) | ||||||
|     signal.signal(signal.SIGTERM, signal_handler) |     signal.signal(signal.SIGTERM, signal_handler) | ||||||
|  |     signal.signal(signal.SIGPIPE, signal.SIG_DFL) | ||||||
| 
 | 
 | ||||||
|     for player in manager.props.player_names: |     for player in manager.props.player_names: | ||||||
|         if arguments.player is not None and arguments.player != player.name: |         if arguments.player is not None and arguments.player != player.name: | ||||||
|  |  | ||||||
							
								
								
									
										248
									
								
								src/bar.cpp
								
								
								
								
							
							
						
						
									
										248
									
								
								src/bar.cpp
								
								
								
								
							|  | @ -9,8 +9,13 @@ | ||||||
| #include "bar.hpp" | #include "bar.hpp" | ||||||
| #include "client.hpp" | #include "client.hpp" | ||||||
| #include "factory.hpp" | #include "factory.hpp" | ||||||
|  | #include "group.hpp" | ||||||
| #include "wlr-layer-shell-unstable-v1-client-protocol.h" | #include "wlr-layer-shell-unstable-v1-client-protocol.h" | ||||||
| 
 | 
 | ||||||
|  | #ifdef HAVE_SWAY | ||||||
|  | #include "modules/sway/bar.hpp" | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
| namespace waybar { | namespace waybar { | ||||||
| static constexpr const char* MIN_HEIGHT_MSG = | static constexpr const char* MIN_HEIGHT_MSG = | ||||||
|     "Requested height: {} is less than the minimum height: {} required by the modules"; |     "Requested height: {} is less than the minimum height: {} required by the modules"; | ||||||
|  | @ -23,6 +28,84 @@ static constexpr const char* BAR_SIZE_MSG = "Bar configured (width: {}, height: | ||||||
| static constexpr const char* SIZE_DEFINED = | static constexpr const char* SIZE_DEFINED = | ||||||
|     "{} size is defined in the config file so it will stay like that"; |     "{} size is defined in the config file so it will stay like that"; | ||||||
| 
 | 
 | ||||||
|  | const Bar::bar_mode_map Bar::PRESET_MODES = {  //
 | ||||||
|  |     {"default", | ||||||
|  |      {// Special mode to hold the global bar configuration
 | ||||||
|  |       .layer = bar_layer::BOTTOM, | ||||||
|  |       .exclusive = true, | ||||||
|  |       .passthrough = false, | ||||||
|  |       .visible = true}}, | ||||||
|  |     {"dock", | ||||||
|  |      {// Modes supported by the sway config; see man sway-bar(5)
 | ||||||
|  |       .layer = bar_layer::BOTTOM, | ||||||
|  |       .exclusive = true, | ||||||
|  |       .passthrough = false, | ||||||
|  |       .visible = true}}, | ||||||
|  |     {"hide", | ||||||
|  |      {//
 | ||||||
|  |       .layer = bar_layer::TOP, | ||||||
|  |       .exclusive = false, | ||||||
|  |       .passthrough = false, | ||||||
|  |       .visible = true}}, | ||||||
|  |     {"invisible", | ||||||
|  |      {//
 | ||||||
|  |       .layer = bar_layer::BOTTOM, | ||||||
|  |       .exclusive = false, | ||||||
|  |       .passthrough = true, | ||||||
|  |       .visible = false}}, | ||||||
|  |     {"overlay", | ||||||
|  |      {//
 | ||||||
|  |       .layer = bar_layer::TOP, | ||||||
|  |       .exclusive = false, | ||||||
|  |       .passthrough = true, | ||||||
|  |       .visible = true}}}; | ||||||
|  | 
 | ||||||
|  | const std::string_view Bar::MODE_DEFAULT = "default"; | ||||||
|  | const std::string_view Bar::MODE_INVISIBLE = "invisible"; | ||||||
|  | const std::string_view DEFAULT_BAR_ID = "bar-0"; | ||||||
|  | 
 | ||||||
|  | /* Deserializer for enum bar_layer */ | ||||||
|  | void from_json(const Json::Value& j, bar_layer& l) { | ||||||
|  |   if (j == "bottom") { | ||||||
|  |     l = bar_layer::BOTTOM; | ||||||
|  |   } else if (j == "top") { | ||||||
|  |     l = bar_layer::TOP; | ||||||
|  |   } else if (j == "overlay") { | ||||||
|  |     l = bar_layer::OVERLAY; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* Deserializer for struct bar_mode */ | ||||||
|  | void from_json(const Json::Value& j, bar_mode& m) { | ||||||
|  |   if (j.isObject()) { | ||||||
|  |     if (auto v = j["layer"]; v.isString()) { | ||||||
|  |       from_json(v, m.layer); | ||||||
|  |     } | ||||||
|  |     if (auto v = j["exclusive"]; v.isBool()) { | ||||||
|  |       m.exclusive = v.asBool(); | ||||||
|  |     } | ||||||
|  |     if (auto v = j["passthrough"]; v.isBool()) { | ||||||
|  |       m.passthrough = v.asBool(); | ||||||
|  |     } | ||||||
|  |     if (auto v = j["visible"]; v.isBool()) { | ||||||
|  |       m.visible = v.asBool(); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* Deserializer for JSON Object -> map<string compatible type, Value>
 | ||||||
|  |  * Assumes that all the values in the object are deserializable to the same type. | ||||||
|  |  */ | ||||||
|  | template <typename Key, typename Value, | ||||||
|  |           typename = std::enable_if_t<std::is_convertible<std::string_view, Key>::value>> | ||||||
|  | void from_json(const Json::Value& j, std::map<Key, Value>& m) { | ||||||
|  |   if (j.isObject()) { | ||||||
|  |     for (auto it = j.begin(); it != j.end(); ++it) { | ||||||
|  |       from_json(*it, m[it.key().asString()]); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
| #ifdef HAVE_GTK_LAYER_SHELL | #ifdef HAVE_GTK_LAYER_SHELL | ||||||
| struct GLSSurfaceImpl : public BarSurface, public sigc::trackable { | struct GLSSurfaceImpl : public BarSurface, public sigc::trackable { | ||||||
|   GLSSurfaceImpl(Gtk::Window& window, struct waybar_output& output) : window_{window} { |   GLSSurfaceImpl(Gtk::Window& window, struct waybar_output& output) : window_{window} { | ||||||
|  | @ -391,7 +474,6 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config) | ||||||
|     : output(w_output), |     : output(w_output), | ||||||
|       config(w_config), |       config(w_config), | ||||||
|       window{Gtk::WindowType::WINDOW_TOPLEVEL}, |       window{Gtk::WindowType::WINDOW_TOPLEVEL}, | ||||||
|       layer_{bar_layer::BOTTOM}, |  | ||||||
|       left_(Gtk::ORIENTATION_HORIZONTAL, 0), |       left_(Gtk::ORIENTATION_HORIZONTAL, 0), | ||||||
|       center_(Gtk::ORIENTATION_HORIZONTAL, 0), |       center_(Gtk::ORIENTATION_HORIZONTAL, 0), | ||||||
|       right_(Gtk::ORIENTATION_HORIZONTAL, 0), |       right_(Gtk::ORIENTATION_HORIZONTAL, 0), | ||||||
|  | @ -403,27 +485,6 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config) | ||||||
|   window.get_style_context()->add_class(config["name"].asString()); |   window.get_style_context()->add_class(config["name"].asString()); | ||||||
|   window.get_style_context()->add_class(config["position"].asString()); |   window.get_style_context()->add_class(config["position"].asString()); | ||||||
| 
 | 
 | ||||||
|   if (config["layer"] == "top") { |  | ||||||
|     layer_ = bar_layer::TOP; |  | ||||||
|   } else if (config["layer"] == "overlay") { |  | ||||||
|     layer_ = bar_layer::OVERLAY; |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   if (config["exclusive"].isBool()) { |  | ||||||
|     exclusive = config["exclusive"].asBool(); |  | ||||||
|   } else if (layer_ == bar_layer::OVERLAY) { |  | ||||||
|     // swaybar defaults: overlay mode does not reserve an exclusive zone
 |  | ||||||
|     exclusive = false; |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   bool passthrough = false; |  | ||||||
|   if (config["passthrough"].isBool()) { |  | ||||||
|     passthrough = config["passthrough"].asBool(); |  | ||||||
|   } else if (layer_ == bar_layer::OVERLAY) { |  | ||||||
|     // swaybar defaults: overlay mode does not accept pointer events.
 |  | ||||||
|     passthrough = true; |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   auto position = config["position"].asString(); |   auto position = config["position"].asString(); | ||||||
| 
 | 
 | ||||||
|   if (position == "right" || position == "left") { |   if (position == "right" || position == "left") { | ||||||
|  | @ -505,15 +566,43 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config) | ||||||
|     surface_impl_ = std::make_unique<RawSurfaceImpl>(window, *output); |     surface_impl_ = std::make_unique<RawSurfaceImpl>(window, *output); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   surface_impl_->setLayer(layer_); |  | ||||||
|   surface_impl_->setExclusiveZone(exclusive); |  | ||||||
|   surface_impl_->setMargins(margins_); |   surface_impl_->setMargins(margins_); | ||||||
|   surface_impl_->setPassThrough(passthrough); |  | ||||||
|   surface_impl_->setPosition(position); |   surface_impl_->setPosition(position); | ||||||
|   surface_impl_->setSize(width, height); |   surface_impl_->setSize(width, height); | ||||||
| 
 | 
 | ||||||
|  |   /* Read custom modes if available */ | ||||||
|  |   if (auto modes = config.get("modes", {}); modes.isObject()) { | ||||||
|  |     from_json(modes, configured_modes); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /* Update "default" mode with the global bar options */ | ||||||
|  |   from_json(config, configured_modes[MODE_DEFAULT]); | ||||||
|  | 
 | ||||||
|  |   if (auto mode = config.get("mode", {}); mode.isString()) { | ||||||
|  |     setMode(config["mode"].asString()); | ||||||
|  |   } else { | ||||||
|  |     setMode(MODE_DEFAULT); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   window.signal_map_event().connect_notify(sigc::mem_fun(*this, &Bar::onMap)); |   window.signal_map_event().connect_notify(sigc::mem_fun(*this, &Bar::onMap)); | ||||||
| 
 | 
 | ||||||
|  | #if HAVE_SWAY | ||||||
|  |   if (auto ipc = config["ipc"]; ipc.isBool() && ipc.asBool()) { | ||||||
|  |     bar_id = Client::inst()->bar_id; | ||||||
|  |     if (auto id = config["id"]; id.isString()) { | ||||||
|  |       bar_id = id.asString(); | ||||||
|  |     } | ||||||
|  |     if (bar_id.empty()) { | ||||||
|  |       bar_id = DEFAULT_BAR_ID; | ||||||
|  |     } | ||||||
|  |     try { | ||||||
|  |       _ipc_client = std::make_unique<BarIpcClient>(*this); | ||||||
|  |     } catch (const std::exception& exc) { | ||||||
|  |       spdlog::warn("Failed to open bar ipc connection: {}", exc.what()); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
|   setupWidgets(); |   setupWidgets(); | ||||||
|   window.show_all(); |   window.show_all(); | ||||||
| 
 | 
 | ||||||
|  | @ -528,6 +617,44 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config) | ||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | /* Need to define it here because of forward declared members */ | ||||||
|  | waybar::Bar::~Bar() = default; | ||||||
|  | 
 | ||||||
|  | void waybar::Bar::setMode(const std::string_view& mode) { | ||||||
|  |   using namespace std::literals::string_literals; | ||||||
|  | 
 | ||||||
|  |   auto style = window.get_style_context(); | ||||||
|  |   /* remove styles added by previous setMode calls */ | ||||||
|  |   style->remove_class("mode-"s + last_mode_); | ||||||
|  | 
 | ||||||
|  |   auto it = configured_modes.find(mode); | ||||||
|  |   if (it != configured_modes.end()) { | ||||||
|  |     last_mode_ = mode; | ||||||
|  |     style->add_class("mode-"s + last_mode_); | ||||||
|  |     setMode(it->second); | ||||||
|  |   } else { | ||||||
|  |     spdlog::warn("Unknown mode \"{}\" requested", mode); | ||||||
|  |     last_mode_ = MODE_DEFAULT; | ||||||
|  |     style->add_class("mode-"s + last_mode_); | ||||||
|  |     setMode(configured_modes.at(MODE_DEFAULT)); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void waybar::Bar::setMode(const struct bar_mode& mode) { | ||||||
|  |   surface_impl_->setLayer(mode.layer); | ||||||
|  |   surface_impl_->setExclusiveZone(mode.exclusive); | ||||||
|  |   surface_impl_->setPassThrough(mode.passthrough); | ||||||
|  | 
 | ||||||
|  |   if (mode.visible) { | ||||||
|  |     window.get_style_context()->remove_class("hidden"); | ||||||
|  |     window.set_opacity(1); | ||||||
|  |   } else { | ||||||
|  |     window.get_style_context()->add_class("hidden"); | ||||||
|  |     window.set_opacity(0); | ||||||
|  |   } | ||||||
|  |   surface_impl_->commit(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| void waybar::Bar::onMap(GdkEventAny*) { | void waybar::Bar::onMap(GdkEventAny*) { | ||||||
|   /*
 |   /*
 | ||||||
|    * Obtain a pointer to the custom layer surface for modules that require it (idle_inhibitor). |    * Obtain a pointer to the custom layer surface for modules that require it (idle_inhibitor). | ||||||
|  | @ -538,17 +665,7 @@ void waybar::Bar::onMap(GdkEventAny*) { | ||||||
| 
 | 
 | ||||||
| void waybar::Bar::setVisible(bool value) { | void waybar::Bar::setVisible(bool value) { | ||||||
|   visible = value; |   visible = value; | ||||||
|   if (!visible) { |   setMode(visible ? MODE_DEFAULT : MODE_INVISIBLE); | ||||||
|     window.get_style_context()->add_class("hidden"); |  | ||||||
|     window.set_opacity(0); |  | ||||||
|     surface_impl_->setLayer(bar_layer::BOTTOM); |  | ||||||
|   } else { |  | ||||||
|     window.get_style_context()->remove_class("hidden"); |  | ||||||
|     window.set_opacity(1); |  | ||||||
|     surface_impl_->setLayer(layer_); |  | ||||||
|   } |  | ||||||
|   surface_impl_->setExclusiveZone(exclusive && visible); |  | ||||||
|   surface_impl_->commit(); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void waybar::Bar::toggle() { setVisible(!visible); } | void waybar::Bar::toggle() { setVisible(!visible); } | ||||||
|  | @ -594,19 +711,7 @@ void waybar::Bar::setupAltFormatKeyForModuleList(const char* module_list_name) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void waybar::Bar::handleSignal(int signal) { | void waybar::Bar::handleSignal(int signal) { | ||||||
|   for (auto& module : modules_left_) { |   for (auto& module : modules_all_) { | ||||||
|     auto* custom = dynamic_cast<waybar::modules::Custom*>(module.get()); |  | ||||||
|     if (custom != nullptr) { |  | ||||||
|       custom->refresh(signal); |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|   for (auto& module : modules_center_) { |  | ||||||
|     auto* custom = dynamic_cast<waybar::modules::Custom*>(module.get()); |  | ||||||
|     if (custom != nullptr) { |  | ||||||
|       custom->refresh(signal); |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|   for (auto& module : modules_right_) { |  | ||||||
|     auto* custom = dynamic_cast<waybar::modules::Custom*>(module.get()); |     auto* custom = dynamic_cast<waybar::modules::Custom*>(module.get()); | ||||||
|     if (custom != nullptr) { |     if (custom != nullptr) { | ||||||
|       custom->refresh(signal); |       custom->refresh(signal); | ||||||
|  | @ -614,19 +719,36 @@ void waybar::Bar::handleSignal(int signal) { | ||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void waybar::Bar::getModules(const Factory& factory, const std::string& pos) { | void waybar::Bar::getModules(const Factory& factory, const std::string& pos, Gtk::Box* group = nullptr) { | ||||||
|   if (config[pos].isArray()) { |   auto module_list = group ? config[pos]["modules"] : config[pos]; | ||||||
|     for (const auto& name : config[pos]) { |   if (module_list.isArray()) { | ||||||
|  |     for (const auto& name : module_list) { | ||||||
|       try { |       try { | ||||||
|         auto module = factory.makeModule(name.asString()); |         auto ref = name.asString(); | ||||||
|         if (pos == "modules-left") { |         AModule* module; | ||||||
|           modules_left_.emplace_back(module); | 
 | ||||||
|  |         if (ref.compare(0, 6, "group/") == 0 && ref.size() > 6) { | ||||||
|  |           auto group_module = new waybar::Group(ref, *this, config[ref]); | ||||||
|  |           getModules(factory, ref, &group_module->box); | ||||||
|  |           module = group_module; | ||||||
|  |         } else { | ||||||
|  |           module = factory.makeModule(ref); | ||||||
|         } |         } | ||||||
|         if (pos == "modules-center") { | 
 | ||||||
|           modules_center_.emplace_back(module); |         std::shared_ptr<AModule> module_sp(module); | ||||||
|         } |         modules_all_.emplace_back(module_sp); | ||||||
|         if (pos == "modules-right") { |         if (group) { | ||||||
|           modules_right_.emplace_back(module); |           group->pack_start(*module, false, false); | ||||||
|  |         } else { | ||||||
|  |           if (pos == "modules-left") { | ||||||
|  |             modules_left_.emplace_back(module_sp); | ||||||
|  |           } | ||||||
|  |           if (pos == "modules-center") { | ||||||
|  |             modules_center_.emplace_back(module_sp); | ||||||
|  |           } | ||||||
|  |           if (pos == "modules-right") { | ||||||
|  |             modules_right_.emplace_back(module_sp); | ||||||
|  |           } | ||||||
|         } |         } | ||||||
|         module->dp.connect([module, &name] { |         module->dp.connect([module, &name] { | ||||||
|           try { |           try { | ||||||
|  | @ -645,7 +767,11 @@ void waybar::Bar::getModules(const Factory& factory, const std::string& pos) { | ||||||
| auto waybar::Bar::setupWidgets() -> void { | auto waybar::Bar::setupWidgets() -> void { | ||||||
|   window.add(box_); |   window.add(box_); | ||||||
|   box_.pack_start(left_, false, false); |   box_.pack_start(left_, false, false); | ||||||
|   box_.set_center_widget(center_); |   if (config["fixed-center"].isBool() ? config["fixed-center"].asBool() : true) { | ||||||
|  |     box_.set_center_widget(center_); | ||||||
|  |   } else { | ||||||
|  |     box_.pack_start(center_, true, false); | ||||||
|  |   } | ||||||
|   box_.pack_end(right_, false, false); |   box_.pack_end(right_, false, false); | ||||||
| 
 | 
 | ||||||
|   // Convert to button code for every module that is used.
 |   // Convert to button code for every module that is used.
 | ||||||
|  |  | ||||||
|  | @ -199,7 +199,6 @@ int waybar::Client::main(int argc, char *argv[]) { | ||||||
|   bool        show_version = false; |   bool        show_version = false; | ||||||
|   std::string config_opt; |   std::string config_opt; | ||||||
|   std::string style_opt; |   std::string style_opt; | ||||||
|   std::string bar_id; |  | ||||||
|   std::string log_level; |   std::string log_level; | ||||||
|   auto        cli = clara::detail::Help(show_help) | |   auto        cli = clara::detail::Help(show_help) | | ||||||
|              clara::detail::Opt(show_version)["-v"]["--version"]("Show version") | |              clara::detail::Opt(show_version)["-v"]["--version"]("Show version") | | ||||||
|  |  | ||||||
|  | @ -30,6 +30,11 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const { | ||||||
|     if (ref == "wlr/taskbar") { |     if (ref == "wlr/taskbar") { | ||||||
|       return new waybar::modules::wlr::Taskbar(id, bar_, config_[name]); |       return new waybar::modules::wlr::Taskbar(id, bar_, config_[name]); | ||||||
|     } |     } | ||||||
|  | #ifdef USE_EXPERIMENTAL | ||||||
|  |     if (ref == "wlr/workspaces") { | ||||||
|  |       return new waybar::modules::wlr::WorkspaceManager(id, bar_, config_[name]); | ||||||
|  |     } | ||||||
|  | #endif | ||||||
| #endif | #endif | ||||||
| #ifdef HAVE_RIVER | #ifdef HAVE_RIVER | ||||||
|     if (ref == "river/tags") { |     if (ref == "river/tags") { | ||||||
|  | @ -89,6 +94,11 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const { | ||||||
|     if (ref == "sndio") { |     if (ref == "sndio") { | ||||||
|       return new waybar::modules::Sndio(id, config_[name]); |       return new waybar::modules::Sndio(id, config_[name]); | ||||||
|     } |     } | ||||||
|  | #endif | ||||||
|  | #ifdef HAVE_GIO_UNIX | ||||||
|  |     if (ref == "inhibitor") { | ||||||
|  |       return new waybar::modules::Inhibitor(id, bar_, config_[name]); | ||||||
|  |     } | ||||||
| #endif | #endif | ||||||
|     if (ref == "temperature") { |     if (ref == "temperature") { | ||||||
|       return new waybar::modules::Temperature(id, config_[name]); |       return new waybar::modules::Temperature(id, config_[name]); | ||||||
|  |  | ||||||
|  | @ -0,0 +1,19 @@ | ||||||
|  | #include "group.hpp" | ||||||
|  | #include <fmt/format.h> | ||||||
|  | #include <util/command.hpp> | ||||||
|  | 
 | ||||||
|  | namespace waybar { | ||||||
|  | 
 | ||||||
|  | Group::Group(const std::string& name, const Bar& bar, const Json::Value& config) | ||||||
|  |     : AModule(config, name, "", false, false), | ||||||
|  |       box{bar.vertical ? Gtk::ORIENTATION_HORIZONTAL : Gtk::ORIENTATION_VERTICAL, 0} | ||||||
|  |     { | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | auto Group::update() -> void { | ||||||
|  |   // noop
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | Group::operator Gtk::Widget&() { return box; } | ||||||
|  | 
 | ||||||
|  | }  // namespace waybar
 | ||||||
|  | @ -14,17 +14,51 @@ | ||||||
| using waybar::modules::waybar_time; | using waybar::modules::waybar_time; | ||||||
| 
 | 
 | ||||||
| waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config) | waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config) | ||||||
|     : ALabel(config, "clock", id, "{:%H:%M}", 60, false, false, true), fixed_time_zone_(false) { |     : ALabel(config, "clock", id, "{:%H:%M}", 60, false, false, true), | ||||||
|  |       current_time_zone_idx_(0), | ||||||
|  |       is_calendar_in_tooltip_(false) | ||||||
|  | { | ||||||
|   if (config_["timezones"].isArray() && !config_["timezones"].empty()) { |   if (config_["timezones"].isArray() && !config_["timezones"].empty()) { | ||||||
|     time_zone_idx_ = 0; |     for (const auto& zone_name: config_["timezones"]) { | ||||||
|     setTimeZone(config_["timezones"][time_zone_idx_]); |       if (!zone_name.isString() || zone_name.asString().empty()) { | ||||||
|   } else { |         time_zones_.push_back(nullptr); | ||||||
|     setTimeZone(config_["timezone"]); |         continue; | ||||||
|  |       } | ||||||
|  |       time_zones_.push_back( | ||||||
|  |         date::locate_zone( | ||||||
|  |           zone_name.asString() | ||||||
|  |         ) | ||||||
|  |       ); | ||||||
|  |     } | ||||||
|  |   } else if (config_["timezone"].isString() && !config_["timezone"].asString().empty()) { | ||||||
|  |     time_zones_.push_back( | ||||||
|  |         date::locate_zone( | ||||||
|  |           config_["timezone"].asString() | ||||||
|  |         ) | ||||||
|  |       ); | ||||||
|   } |   } | ||||||
|   if (fixed_time_zone_) { | 
 | ||||||
|  |   // If all timezones are parsed and no one is good, add nullptr to the timezones vector, to mark that local time should be shown.
 | ||||||
|  |   if (!time_zones_.size()) { | ||||||
|  |     time_zones_.push_back(nullptr); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   if (!is_timezone_fixed()) { | ||||||
|     spdlog::warn("As using a timezone, some format args may be missing as the date library haven't got a release since 2018."); |     spdlog::warn("As using a timezone, some format args may be missing as the date library haven't got a release since 2018."); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   // Check if a particular placeholder is present in the tooltip format, to know what to calculate on update.
 | ||||||
|  |   if (config_["tooltip-format"].isString()) { | ||||||
|  |     std::string trimmed_format = config_["tooltip-format"].asString(); | ||||||
|  |     trimmed_format.erase(std::remove_if(trimmed_format.begin(), | ||||||
|  |                               trimmed_format.end(), | ||||||
|  |                               [](unsigned char x){return std::isspace(x);}), | ||||||
|  |                trimmed_format.end()); | ||||||
|  |     if (trimmed_format.find("{" + kCalendarPlaceholder + "}") != std::string::npos) { | ||||||
|  |       is_calendar_in_tooltip_ = true; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   if (config_["locale"].isString()) { |   if (config_["locale"].isString()) { | ||||||
|     locale_ = std::locale(config_["locale"].asString()); |     locale_ = std::locale(config_["locale"].asString()); | ||||||
|   } else { |   } else { | ||||||
|  | @ -40,53 +74,46 @@ waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config) | ||||||
|   }; |   }; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | const date::time_zone* waybar::modules::Clock::current_timezone() { | ||||||
|  |   return time_zones_[current_time_zone_idx_] ? time_zones_[current_time_zone_idx_] : date::current_zone(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool waybar::modules::Clock::is_timezone_fixed() { | ||||||
|  |   return time_zones_[current_time_zone_idx_] != nullptr; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| auto waybar::modules::Clock::update() -> void { | auto waybar::modules::Clock::update() -> void { | ||||||
|   if (!fixed_time_zone_) { |   auto time_zone = current_timezone(); | ||||||
|     // Time zone can change. Be sure to pick that.
 |   auto now = std::chrono::system_clock::now(); | ||||||
|     time_zone_ = date::current_zone(); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   auto        now = std::chrono::system_clock::now(); |  | ||||||
|   waybar_time wtime = {locale_, |   waybar_time wtime = {locale_, | ||||||
|                        date::make_zoned(time_zone_, date::floor<std::chrono::seconds>(now))}; |                        date::make_zoned(time_zone, date::floor<std::chrono::seconds>(now))}; | ||||||
| 
 |   std::string text = ""; | ||||||
|   std::string text; |   if (!is_timezone_fixed()) { | ||||||
|   if (!fixed_time_zone_) { |  | ||||||
|     // As date dep is not fully compatible, prefer fmt
 |     // As date dep is not fully compatible, prefer fmt
 | ||||||
|     tzset(); |     tzset(); | ||||||
|     auto localtime = fmt::localtime(std::chrono::system_clock::to_time_t(now)); |     auto localtime = fmt::localtime(std::chrono::system_clock::to_time_t(now)); | ||||||
|     text = fmt::format(format_, localtime); |     text = fmt::format(format_, localtime); | ||||||
|     label_.set_markup(text); |  | ||||||
|   } else { |   } else { | ||||||
|     text = fmt::format(format_, wtime); |     text = fmt::format(format_, wtime); | ||||||
|     label_.set_markup(text); |  | ||||||
|   } |   } | ||||||
|  |   label_.set_markup(text); | ||||||
| 
 | 
 | ||||||
|   if (tooltipEnabled()) { |   if (tooltipEnabled()) { | ||||||
|     if (config_["tooltip-format"].isString()) { |     if (config_["tooltip-format"].isString()) { | ||||||
|       const auto calendar = calendar_text(wtime); |       std::string calendar_lines = ""; | ||||||
|       auto       tooltip_format = config_["tooltip-format"].asString(); |       if (is_calendar_in_tooltip_) { | ||||||
|       auto       tooltip_text = fmt::format(tooltip_format, wtime, fmt::arg("calendar", calendar)); |         calendar_lines = calendar_text(wtime); | ||||||
|       label_.set_tooltip_markup(tooltip_text); |       } | ||||||
|     } else { |       auto tooltip_format = config_["tooltip-format"].asString(); | ||||||
|       label_.set_tooltip_markup(text); |       text = fmt::format(tooltip_format, wtime, fmt::arg(kCalendarPlaceholder.c_str(), calendar_lines)); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  | 
 | ||||||
|  |   label_.set_tooltip_markup(text); | ||||||
|   // Call parent update
 |   // Call parent update
 | ||||||
|   ALabel::update(); |   ALabel::update(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool waybar::modules::Clock::setTimeZone(Json::Value zone_name) { |  | ||||||
|   if (!zone_name.isString() || zone_name.asString().empty()) { |  | ||||||
|       fixed_time_zone_ = false; |  | ||||||
|       return false; |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   time_zone_ = date::locate_zone(zone_name.asString()); |  | ||||||
|   fixed_time_zone_ = true; |  | ||||||
|   return true; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| bool waybar::modules::Clock::handleScroll(GdkEventScroll *e) { | bool waybar::modules::Clock::handleScroll(GdkEventScroll *e) { | ||||||
|   // defer to user commands if set
 |   // defer to user commands if set
 | ||||||
|   if (config_["on-scroll-up"].isString() || config_["on-scroll-down"].isString()) { |   if (config_["on-scroll-up"].isString() || config_["on-scroll-down"].isString()) { | ||||||
|  | @ -97,17 +124,18 @@ bool waybar::modules::Clock::handleScroll(GdkEventScroll *e) { | ||||||
|   if (dir != SCROLL_DIR::UP && dir != SCROLL_DIR::DOWN) { |   if (dir != SCROLL_DIR::UP && dir != SCROLL_DIR::DOWN) { | ||||||
|     return true; |     return true; | ||||||
|   } |   } | ||||||
|   if (!config_["timezones"].isArray() || config_["timezones"].empty()) { |   if (time_zones_.size() == 1) { | ||||||
|     return true; |     return true; | ||||||
|   } |   } | ||||||
|   auto nr_zones = config_["timezones"].size(); | 
 | ||||||
|  |   auto nr_zones = time_zones_.size(); | ||||||
|   if (dir == SCROLL_DIR::UP) { |   if (dir == SCROLL_DIR::UP) { | ||||||
|     size_t new_idx = time_zone_idx_ + 1; |     size_t new_idx = current_time_zone_idx_ + 1; | ||||||
|     time_zone_idx_ = new_idx == nr_zones ? 0 : new_idx; |     current_time_zone_idx_ = new_idx == nr_zones ? 0 : new_idx; | ||||||
|   } else { |   } else { | ||||||
|     time_zone_idx_ = time_zone_idx_ == 0 ? nr_zones - 1 : time_zone_idx_ - 1; |     current_time_zone_idx_ = current_time_zone_idx_ == 0 ? nr_zones - 1 : current_time_zone_idx_ - 1; | ||||||
|   } |   } | ||||||
|   setTimeZone(config_["timezones"][time_zone_idx_]); | 
 | ||||||
|   update(); |   update(); | ||||||
|   return true; |   return true; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -45,9 +45,9 @@ auto waybar::modules::Disk::update() -> void { | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   auto free = pow_format(stats.f_bavail * stats.f_frsize, "B", true); |   auto free = pow_format(stats.f_bavail * stats.f_frsize, "B", true); | ||||||
|   auto used = pow_format((stats.f_blocks - stats.f_bavail) * stats.f_frsize, "B", true); |   auto used = pow_format((stats.f_blocks - stats.f_bfree) * stats.f_frsize, "B", true); | ||||||
|   auto total = pow_format(stats.f_blocks * stats.f_frsize, "B", true); |   auto total = pow_format(stats.f_blocks * stats.f_frsize, "B", true); | ||||||
|   auto percentage_used = (stats.f_blocks - stats.f_bavail) * 100 / stats.f_blocks; |   auto percentage_used = (stats.f_blocks - stats.f_bfree) * 100 / stats.f_blocks; | ||||||
| 
 | 
 | ||||||
|   auto format = format_; |   auto format = format_; | ||||||
|   auto state = getState(percentage_used); |   auto state = getState(percentage_used); | ||||||
|  |  | ||||||
|  | @ -0,0 +1,175 @@ | ||||||
|  | #include "modules/inhibitor.hpp" | ||||||
|  | 
 | ||||||
|  | #include <gio/gio.h> | ||||||
|  | #include <gio/gunixfdlist.h> | ||||||
|  | #include <spdlog/spdlog.h> | ||||||
|  | 
 | ||||||
|  | namespace { | ||||||
|  | 
 | ||||||
|  | using DBus = std::unique_ptr<GDBusConnection, void(*)(GDBusConnection*)>; | ||||||
|  | 
 | ||||||
|  | auto dbus() -> DBus { | ||||||
|  |   GError *error = nullptr; | ||||||
|  |   GDBusConnection* connection = | ||||||
|  |       g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, &error); | ||||||
|  | 
 | ||||||
|  |   if (error) { | ||||||
|  |     spdlog::error("g_bus_get_sync() failed: {}", error->message); | ||||||
|  |     g_error_free(error); | ||||||
|  |     connection = nullptr; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   auto destructor = [](GDBusConnection* connection) { | ||||||
|  |       GError *error = nullptr; | ||||||
|  |       g_dbus_connection_close_sync(connection, nullptr, &error); | ||||||
|  |       if (error) { | ||||||
|  |         spdlog::error( | ||||||
|  |             "g_bus_connection_close_sync failed(): {}", | ||||||
|  |             error->message); | ||||||
|  |         g_error_free(error); | ||||||
|  |       } | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   return DBus{connection, destructor}; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | auto getLocks(const DBus& bus, const std::string& inhibitors) -> int { | ||||||
|  |   GError *error = nullptr; | ||||||
|  |   GUnixFDList* fd_list; | ||||||
|  |   int handle; | ||||||
|  | 
 | ||||||
|  |   auto reply = g_dbus_connection_call_with_unix_fd_list_sync(bus.get(), | ||||||
|  |       "org.freedesktop.login1", | ||||||
|  |       "/org/freedesktop/login1", | ||||||
|  |       "org.freedesktop.login1.Manager", | ||||||
|  |       "Inhibit", | ||||||
|  |       g_variant_new( | ||||||
|  |           "(ssss)", | ||||||
|  |           inhibitors.c_str(), | ||||||
|  |           "waybar", | ||||||
|  |           "Asked by user", | ||||||
|  |           "block"), | ||||||
|  |       G_VARIANT_TYPE("(h)"), | ||||||
|  |       G_DBUS_CALL_FLAGS_NONE, | ||||||
|  |       -1, | ||||||
|  |       nullptr, | ||||||
|  |       &fd_list, | ||||||
|  |       nullptr, | ||||||
|  |       &error); | ||||||
|  |   if (error) { | ||||||
|  |     spdlog::error( | ||||||
|  |         "g_dbus_connection_call_with_unix_fd_list_sync() failed: {}", | ||||||
|  |         error->message); | ||||||
|  |     g_error_free(error); | ||||||
|  |     handle = -1; | ||||||
|  |   } else { | ||||||
|  |     gint index; | ||||||
|  |     g_variant_get(reply, "(h)", &index); | ||||||
|  |     g_variant_unref(reply); | ||||||
|  |     handle = g_unix_fd_list_get(fd_list, index, nullptr); | ||||||
|  |     g_object_unref(fd_list); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   return handle; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | auto checkInhibitor(const std::string& inhibitor) -> const std::string& { | ||||||
|  |   static const auto inhibitors = std::array{ | ||||||
|  |       "idle", | ||||||
|  |       "shutdown", | ||||||
|  |       "sleep", | ||||||
|  |       "handle-power-key", | ||||||
|  |       "handle-suspend-key", | ||||||
|  |       "handle-hibernate-key", | ||||||
|  |       "handle-lid-switch" | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   if (std::find(inhibitors.begin(), inhibitors.end(), inhibitor) | ||||||
|  |           == inhibitors.end()) { | ||||||
|  |     throw std::runtime_error("invalid logind inhibitor " + inhibitor); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   return inhibitor; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | auto getInhibitors(const Json::Value& config) -> std::string { | ||||||
|  |   std::string inhibitors = "idle"; | ||||||
|  | 
 | ||||||
|  |   if (config["what"].empty()) { | ||||||
|  |     return inhibitors; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   if (config["what"].isString()) { | ||||||
|  |     return checkInhibitor(config["what"].asString()); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   if (config["what"].isArray()) { | ||||||
|  |     inhibitors = checkInhibitor(config["what"][0].asString()); | ||||||
|  |     for (decltype(config["what"].size()) i = 1; i < config["what"].size(); ++i) { | ||||||
|  |       inhibitors += ":" + checkInhibitor(config["what"][i].asString()); | ||||||
|  |     } | ||||||
|  |     return inhibitors; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   return inhibitors; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | namespace waybar::modules { | ||||||
|  | 
 | ||||||
|  | Inhibitor::Inhibitor(const std::string& id, const Bar& bar, | ||||||
|  |                      const Json::Value& config) | ||||||
|  |     : ALabel(config, "inhibitor", id, "{status}", true), | ||||||
|  |       dbus_(::dbus()), | ||||||
|  |       inhibitors_(::getInhibitors(config)) { | ||||||
|  |   event_box_.add_events(Gdk::BUTTON_PRESS_MASK); | ||||||
|  |   event_box_.signal_button_press_event().connect( | ||||||
|  |       sigc::mem_fun(*this, &Inhibitor::handleToggle)); | ||||||
|  |   dp.emit(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | Inhibitor::~Inhibitor() { | ||||||
|  |   if (handle_ != -1) { | ||||||
|  |     ::close(handle_); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | auto Inhibitor::activated() -> bool { | ||||||
|  |   return handle_ != -1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | auto Inhibitor::update() -> void { | ||||||
|  |   std::string status_text = activated() ? "activated" : "deactivated"; | ||||||
|  | 
 | ||||||
|  |   label_.get_style_context()->remove_class( | ||||||
|  |       activated() ? "deactivated" : "activated"); | ||||||
|  |   label_.set_markup( | ||||||
|  |       fmt::format(format_, fmt::arg("status", status_text), | ||||||
|  |       fmt::arg("icon", getIcon(0, status_text)))); | ||||||
|  |   label_.get_style_context()->add_class(status_text); | ||||||
|  | 
 | ||||||
|  |   if (tooltipEnabled()) { | ||||||
|  |     label_.set_tooltip_text(status_text); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   return ALabel::update(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | auto Inhibitor::handleToggle(GdkEventButton* const& e) -> bool { | ||||||
|  |   if (e->button == 1) { | ||||||
|  |     if (activated()) { | ||||||
|  |       ::close(handle_); | ||||||
|  |       handle_ = -1; | ||||||
|  |     } else { | ||||||
|  |       handle_ = ::getLocks(dbus_, inhibitors_); | ||||||
|  |       if (handle_ == -1) { | ||||||
|  |         spdlog::error("cannot get inhibitor locks"); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   return ALabel::handleToggle(e); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | } // waybar::modules
 | ||||||
|  | @ -131,6 +131,9 @@ void waybar::modules::MPD::setLabel() { | ||||||
|     date = getTag(MPD_TAG_DATE); |     date = getTag(MPD_TAG_DATE); | ||||||
|     song_pos = mpd_status_get_song_pos(status_.get()); |     song_pos = mpd_status_get_song_pos(status_.get()); | ||||||
|     volume = mpd_status_get_volume(status_.get()); |     volume = mpd_status_get_volume(status_.get()); | ||||||
|  |     if (volume < 0) { | ||||||
|  |       volume = 0; | ||||||
|  |     } | ||||||
|     queue_length = mpd_status_get_queue_length(status_.get()); |     queue_length = mpd_status_get_queue_length(status_.get()); | ||||||
|     elapsedTime = std::chrono::seconds(mpd_status_get_elapsed_time(status_.get())); |     elapsedTime = std::chrono::seconds(mpd_status_get_elapsed_time(status_.get())); | ||||||
|     totalTime = std::chrono::seconds(mpd_status_get_total_time(status_.get())); |     totalTime = std::chrono::seconds(mpd_status_get_total_time(status_.get())); | ||||||
|  |  | ||||||
|  | @ -1,87 +1,77 @@ | ||||||
| #include "modules/network.hpp" | #include <linux/if.h> | ||||||
| #include <spdlog/spdlog.h> | #include <spdlog/spdlog.h> | ||||||
| #include <sys/eventfd.h> | #include <sys/eventfd.h> | ||||||
| #include <linux/if.h> | 
 | ||||||
| #include <fstream> |  | ||||||
| #include <cassert> | #include <cassert> | ||||||
|  | #include <fstream> | ||||||
|  | #include <sstream> | ||||||
| #include <optional> | #include <optional> | ||||||
|  | 
 | ||||||
|  | #include "modules/network.hpp" | ||||||
| #include "util/format.hpp" | #include "util/format.hpp" | ||||||
| #ifdef WANT_RFKILL | #ifdef WANT_RFKILL | ||||||
| #include "util/rfkill.hpp" | #include "util/rfkill.hpp" | ||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
| namespace { | namespace { | ||||||
| 
 |  | ||||||
| using namespace waybar::util; | using namespace waybar::util; | ||||||
| 
 |  | ||||||
| constexpr const char *NETSTAT_FILE = |  | ||||||
|     "/proc/net/netstat";  // std::ifstream does not take std::string_view as param
 |  | ||||||
| constexpr std::string_view BANDWIDTH_CATEGORY = "IpExt"; |  | ||||||
| constexpr std::string_view BANDWIDTH_DOWN_TOTAL_KEY = "InOctets"; |  | ||||||
| constexpr std::string_view BANDWIDTH_UP_TOTAL_KEY = "OutOctets"; |  | ||||||
| constexpr const char *DEFAULT_FORMAT = "{ifname}"; | constexpr const char *DEFAULT_FORMAT = "{ifname}"; | ||||||
| 
 |  | ||||||
| std::ifstream                     netstat(NETSTAT_FILE); |  | ||||||
| std::optional<unsigned long long> read_netstat(std::string_view category, std::string_view key) { |  | ||||||
|   if (!netstat) { |  | ||||||
|     spdlog::warn("Failed to open netstat file {}", NETSTAT_FILE); |  | ||||||
|     return {}; |  | ||||||
|   } |  | ||||||
|   netstat.seekg(std::ios_base::beg); |  | ||||||
| 
 |  | ||||||
|   // finding corresponding line (category)
 |  | ||||||
|   // looks into the file for the first line starting by the 'category' string
 |  | ||||||
|   auto starts_with = [](const std::string &str, std::string_view start) { |  | ||||||
|     return start == std::string_view{str.data(), std::min(str.size(), start.size())}; |  | ||||||
|   }; |  | ||||||
| 
 |  | ||||||
|   std::string read; |  | ||||||
|   while (std::getline(netstat, read) && !starts_with(read, category)) |  | ||||||
|     ; |  | ||||||
|   if (!starts_with(read, category)) { |  | ||||||
|     spdlog::warn("Category '{}' not found in netstat file {}", category, NETSTAT_FILE); |  | ||||||
|     return {}; |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   // finding corresponding column (key)
 |  | ||||||
|   // looks into the fetched line for the first word (space separated) equal to 'key'
 |  | ||||||
|   int  index = 0; |  | ||||||
|   auto r_it = read.begin(); |  | ||||||
|   auto k_it = key.begin(); |  | ||||||
|   while (k_it != key.end() && r_it != read.end()) { |  | ||||||
|     if (*r_it != *k_it) { |  | ||||||
|       r_it = std::find(r_it, read.end(), ' '); |  | ||||||
|       if (r_it != read.end()) { |  | ||||||
|         ++r_it; |  | ||||||
|       } |  | ||||||
|       k_it = key.begin(); |  | ||||||
|       ++index; |  | ||||||
|     } else { |  | ||||||
|       ++r_it; |  | ||||||
|       ++k_it; |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   if (r_it == read.end() && k_it != key.end()) { |  | ||||||
|     spdlog::warn( |  | ||||||
|         "Key '{}' not found in category '{}' of netstat file {}", key, category, NETSTAT_FILE); |  | ||||||
|     return {}; |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   // finally accessing value
 |  | ||||||
|   // accesses the line right under the fetched one
 |  | ||||||
|   std::getline(netstat, read); |  | ||||||
|   assert(starts_with(read, category)); |  | ||||||
|   std::istringstream iss(read); |  | ||||||
|   while (index--) { |  | ||||||
|     std::getline(iss, read, ' '); |  | ||||||
|   } |  | ||||||
|   unsigned long long value; |  | ||||||
|   iss >> value; |  | ||||||
|   return value; |  | ||||||
| } |  | ||||||
| }  // namespace
 | }  // namespace
 | ||||||
| 
 | 
 | ||||||
|  | constexpr const char *NETDEV_FILE = | ||||||
|  |     "/proc/net/dev";  // std::ifstream does not take std::string_view as param
 | ||||||
|  | std::optional<std::pair<unsigned long long, unsigned long long>> | ||||||
|  | waybar::modules::Network::readBandwidthUsage() { | ||||||
|  |   std::ifstream netdev(NETDEV_FILE); | ||||||
|  |   if (!netdev) { | ||||||
|  |     spdlog::warn("Failed to open netdev file {}", NETDEV_FILE); | ||||||
|  |     return {}; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   std::string line; | ||||||
|  |   // skip the headers (first two lines)
 | ||||||
|  |   std::getline(netdev, line); | ||||||
|  |   std::getline(netdev, line); | ||||||
|  | 
 | ||||||
|  |   unsigned long long receivedBytes = 0ull; | ||||||
|  |   unsigned long long transmittedBytes = 0ull; | ||||||
|  |   while (std::getline(netdev, line)) { | ||||||
|  |     std::istringstream iss(line); | ||||||
|  | 
 | ||||||
|  |     std::string ifacename; | ||||||
|  |     iss >> ifacename;  // ifacename contains "eth0:"
 | ||||||
|  |     ifacename.pop_back();  // remove trailing ':'
 | ||||||
|  |     if (!checkInterface(ifacename)) { | ||||||
|  |       continue; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // The rest of the line consists of whitespace separated counts divided
 | ||||||
|  |     // into two groups (receive and transmit). Each group has the following
 | ||||||
|  |     // columns: bytes, packets, errs, drop, fifo, frame, compressed, multicast
 | ||||||
|  |     //
 | ||||||
|  |     // We only care about the bytes count, so we'll just ignore the 7 other
 | ||||||
|  |     // columns.
 | ||||||
|  |     unsigned long long r = 0ull; | ||||||
|  |     unsigned long long t = 0ull; | ||||||
|  |     // Read received bytes
 | ||||||
|  |     iss >> r; | ||||||
|  |     // Skip all the other columns in the received group
 | ||||||
|  |     for (int colsToSkip = 7; colsToSkip > 0; colsToSkip--) { | ||||||
|  |       // skip whitespace between columns
 | ||||||
|  |       while (iss.peek() == ' ') { iss.ignore(); } | ||||||
|  |       // skip the irrelevant column
 | ||||||
|  |       while (iss.peek() != ' ') { iss.ignore(); } | ||||||
|  |     } | ||||||
|  |     // Read transmit bytes
 | ||||||
|  |     iss >> t; | ||||||
|  | 
 | ||||||
|  |     receivedBytes += r; | ||||||
|  |     transmittedBytes += t; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   return {{receivedBytes, transmittedBytes}}; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| waybar::modules::Network::Network(const std::string &id, const Json::Value &config) | waybar::modules::Network::Network(const std::string &id, const Json::Value &config) | ||||||
|     : ALabel(config, "network", id, DEFAULT_FORMAT, 60), |     : ALabel(config, "network", id, DEFAULT_FORMAT, 60), | ||||||
|       ifid_(-1), |       ifid_(-1), | ||||||
|  | @ -98,7 +88,7 @@ waybar::modules::Network::Network(const std::string &id, const Json::Value &conf | ||||||
| #ifdef WANT_RFKILL | #ifdef WANT_RFKILL | ||||||
|       rfkill_{RFKILL_TYPE_WLAN}, |       rfkill_{RFKILL_TYPE_WLAN}, | ||||||
| #endif | #endif | ||||||
|       frequency_(0) { |       frequency_(0.0) { | ||||||
| 
 | 
 | ||||||
|   // Start with some "text" in the module's label_, update() will then
 |   // Start with some "text" in the module's label_, update() will then
 | ||||||
|   // update it. Since the text should be different, update() will be able
 |   // update it. Since the text should be different, update() will be able
 | ||||||
|  | @ -106,17 +96,12 @@ waybar::modules::Network::Network(const std::string &id, const Json::Value &conf | ||||||
|   // the module start with no text, but the the event_box_ is shown.
 |   // the module start with no text, but the the event_box_ is shown.
 | ||||||
|   label_.set_markup("<s></s>"); |   label_.set_markup("<s></s>"); | ||||||
| 
 | 
 | ||||||
|   auto down_octets = read_netstat(BANDWIDTH_CATEGORY, BANDWIDTH_DOWN_TOTAL_KEY); |   auto bandwidth = readBandwidthUsage(); | ||||||
|   auto up_octets = read_netstat(BANDWIDTH_CATEGORY, BANDWIDTH_UP_TOTAL_KEY); |   if (bandwidth.has_value()) { | ||||||
|   if (down_octets) { |     bandwidth_down_total_ = (*bandwidth).first; | ||||||
|     bandwidth_down_total_ = *down_octets; |     bandwidth_up_total_ = (*bandwidth).second; | ||||||
|   } else { |   } else { | ||||||
|     bandwidth_down_total_ = 0; |     bandwidth_down_total_ = 0; | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   if (up_octets) { |  | ||||||
|     bandwidth_up_total_ = *up_octets; |  | ||||||
|   } else { |  | ||||||
|     bandwidth_up_total_ = 0; |     bandwidth_up_total_ = 0; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  | @ -303,20 +288,21 @@ const std::string waybar::modules::Network::getNetworkState() const { | ||||||
| auto waybar::modules::Network::update() -> void { | auto waybar::modules::Network::update() -> void { | ||||||
|   std::lock_guard<std::mutex> lock(mutex_); |   std::lock_guard<std::mutex> lock(mutex_); | ||||||
|   std::string                 tooltip_format; |   std::string                 tooltip_format; | ||||||
|   auto down_octets = read_netstat(BANDWIDTH_CATEGORY, BANDWIDTH_DOWN_TOTAL_KEY); |  | ||||||
|   auto up_octets = read_netstat(BANDWIDTH_CATEGORY, BANDWIDTH_UP_TOTAL_KEY); |  | ||||||
| 
 | 
 | ||||||
|   unsigned long long bandwidth_down = 0; |   auto bandwidth = readBandwidthUsage(); | ||||||
|   if (down_octets) { |   auto bandwidth_down = 0ull; | ||||||
|     bandwidth_down = *down_octets - bandwidth_down_total_; |   auto bandwidth_up = 0ull; | ||||||
|     bandwidth_down_total_ = *down_octets; |   if (bandwidth.has_value()) { | ||||||
|  |     auto down_octets = (*bandwidth).first; | ||||||
|  |     auto up_octets = (*bandwidth).second; | ||||||
|  | 
 | ||||||
|  |     bandwidth_down = down_octets - bandwidth_down_total_; | ||||||
|  |     bandwidth_down_total_ = down_octets; | ||||||
|  | 
 | ||||||
|  |     bandwidth_up = up_octets - bandwidth_up_total_; | ||||||
|  |     bandwidth_up_total_ = up_octets; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   unsigned long long bandwidth_up = 0; |  | ||||||
|   if (up_octets) { |  | ||||||
|     bandwidth_up = *up_octets - bandwidth_up_total_; |  | ||||||
|     bandwidth_up_total_ = *up_octets; |  | ||||||
|   } |  | ||||||
|   if (!alt_) { |   if (!alt_) { | ||||||
|     auto state = getNetworkState(); |     auto state = getNetworkState(); | ||||||
|     if (!state_.empty() && label_.get_style_context()->has_class(state_)) { |     if (!state_.empty() && label_.get_style_context()->has_class(state_)) { | ||||||
|  | @ -345,12 +331,13 @@ auto waybar::modules::Network::update() -> void { | ||||||
|       fmt::arg("essid", essid_), |       fmt::arg("essid", essid_), | ||||||
|       fmt::arg("signaldBm", signal_strength_dbm_), |       fmt::arg("signaldBm", signal_strength_dbm_), | ||||||
|       fmt::arg("signalStrength", signal_strength_), |       fmt::arg("signalStrength", signal_strength_), | ||||||
|  |       fmt::arg("signalStrengthApp", signal_strength_app_), | ||||||
|       fmt::arg("ifname", ifname_), |       fmt::arg("ifname", ifname_), | ||||||
|       fmt::arg("netmask", netmask_), |       fmt::arg("netmask", netmask_), | ||||||
|       fmt::arg("ipaddr", ipaddr_), |       fmt::arg("ipaddr", ipaddr_), | ||||||
|       fmt::arg("gwaddr", gwaddr_), |       fmt::arg("gwaddr", gwaddr_), | ||||||
|       fmt::arg("cidr", cidr_), |       fmt::arg("cidr", cidr_), | ||||||
|       fmt::arg("frequency", frequency_), |       fmt::arg("frequency", fmt::format("{:.1f}", frequency_)), | ||||||
|       fmt::arg("icon", getIcon(signal_strength_, state_)), |       fmt::arg("icon", getIcon(signal_strength_, state_)), | ||||||
|       fmt::arg("bandwidthDownBits", pow_format(bandwidth_down * 8ull / interval_.count(), "b/s")), |       fmt::arg("bandwidthDownBits", pow_format(bandwidth_down * 8ull / interval_.count(), "b/s")), | ||||||
|       fmt::arg("bandwidthUpBits", pow_format(bandwidth_up * 8ull / interval_.count(), "b/s")), |       fmt::arg("bandwidthUpBits", pow_format(bandwidth_up * 8ull / interval_.count(), "b/s")), | ||||||
|  | @ -374,12 +361,13 @@ auto waybar::modules::Network::update() -> void { | ||||||
|           fmt::arg("essid", essid_), |           fmt::arg("essid", essid_), | ||||||
|           fmt::arg("signaldBm", signal_strength_dbm_), |           fmt::arg("signaldBm", signal_strength_dbm_), | ||||||
|           fmt::arg("signalStrength", signal_strength_), |           fmt::arg("signalStrength", signal_strength_), | ||||||
|  |           fmt::arg("signalStrengthApp", signal_strength_app_), | ||||||
|           fmt::arg("ifname", ifname_), |           fmt::arg("ifname", ifname_), | ||||||
|           fmt::arg("netmask", netmask_), |           fmt::arg("netmask", netmask_), | ||||||
|           fmt::arg("ipaddr", ipaddr_), |           fmt::arg("ipaddr", ipaddr_), | ||||||
|           fmt::arg("gwaddr", gwaddr_), |           fmt::arg("gwaddr", gwaddr_), | ||||||
|           fmt::arg("cidr", cidr_), |           fmt::arg("cidr", cidr_), | ||||||
|           fmt::arg("frequency", frequency_), |           fmt::arg("frequency", fmt::format("{:.1f}", frequency_)), | ||||||
|           fmt::arg("icon", getIcon(signal_strength_, state_)), |           fmt::arg("icon", getIcon(signal_strength_, state_)), | ||||||
|           fmt::arg("bandwidthDownBits", |           fmt::arg("bandwidthDownBits", | ||||||
|                    pow_format(bandwidth_down * 8ull / interval_.count(), "b/s")), |                    pow_format(bandwidth_down * 8ull / interval_.count(), "b/s")), | ||||||
|  | @ -417,7 +405,8 @@ void waybar::modules::Network::clearIface() { | ||||||
|   cidr_ = 0; |   cidr_ = 0; | ||||||
|   signal_strength_dbm_ = 0; |   signal_strength_dbm_ = 0; | ||||||
|   signal_strength_ = 0; |   signal_strength_ = 0; | ||||||
|   frequency_ = 0; |   signal_strength_app_.clear(); | ||||||
|  |   frequency_ = 0.0; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) { | int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) { | ||||||
|  | @ -484,7 +473,8 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) { | ||||||
|             net->essid_.clear(); |             net->essid_.clear(); | ||||||
|             net->signal_strength_dbm_ = 0; |             net->signal_strength_dbm_ = 0; | ||||||
|             net->signal_strength_ = 0; |             net->signal_strength_ = 0; | ||||||
|             net->frequency_ = 0; |             net->signal_strength_app_.clear(); | ||||||
|  |             net->frequency_ = 0.0; | ||||||
|           } |           } | ||||||
|         } |         } | ||||||
|         net->carrier_ = carrier.value(); |         net->carrier_ = carrier.value(); | ||||||
|  | @ -802,13 +792,30 @@ void waybar::modules::Network::parseSignal(struct nlattr **bss) { | ||||||
|   if (bss[NL80211_BSS_SIGNAL_MBM] != nullptr) { |   if (bss[NL80211_BSS_SIGNAL_MBM] != nullptr) { | ||||||
|     // signalstrength in dBm from mBm
 |     // signalstrength in dBm from mBm
 | ||||||
|     signal_strength_dbm_ = nla_get_s32(bss[NL80211_BSS_SIGNAL_MBM]) / 100; |     signal_strength_dbm_ = nla_get_s32(bss[NL80211_BSS_SIGNAL_MBM]) / 100; | ||||||
|  |     // WiFi-hardware usually operates in the range -90 to -30dBm.
 | ||||||
| 
 | 
 | ||||||
|     // WiFi-hardware usually operates in the range -90 to -20dBm.
 |     // If a signal is too strong, it can overwhelm receiving circuity that is designed
 | ||||||
|     const int hardwareMax = -20; |     // to pick up and process a certain signal level. The following percentage is scaled to
 | ||||||
|  |     // punish signals that are too strong (>= -45dBm) or too weak (<= -45 dBm).
 | ||||||
|  |     const int hardwareOptimum = -45; | ||||||
|     const int hardwareMin = -90; |     const int hardwareMin = -90; | ||||||
|     const int strength = |     const int strength = | ||||||
|       ((signal_strength_dbm_ - hardwareMin) / double{hardwareMax - hardwareMin}) * 100; |         100 - ((abs(signal_strength_dbm_ - hardwareOptimum) / double{hardwareOptimum - hardwareMin}) * 100); | ||||||
|     signal_strength_ = std::clamp(strength, 0, 100); |     signal_strength_ = std::clamp(strength, 0, 100);   | ||||||
|  | 
 | ||||||
|  |     if (signal_strength_dbm_ >= -50) { | ||||||
|  |       signal_strength_app_ = "Great Connectivity"; | ||||||
|  |     } else if (signal_strength_dbm_ >= -60) { | ||||||
|  |       signal_strength_app_ = "Good Connectivity"; | ||||||
|  |     } else if (signal_strength_dbm_ >= -67) { | ||||||
|  |       signal_strength_app_ = "Streaming"; | ||||||
|  |     } else if (signal_strength_dbm_ >= -70) { | ||||||
|  |       signal_strength_app_ = "Web Surfing"; | ||||||
|  |     } else if (signal_strength_dbm_ >= -80) { | ||||||
|  |       signal_strength_app_ = "Basic Connectivity"; | ||||||
|  |     } else { | ||||||
|  |       signal_strength_app_ = "Poor Connectivity"; | ||||||
|  |     } | ||||||
|   } |   } | ||||||
|   if (bss[NL80211_BSS_SIGNAL_UNSPEC] != nullptr) { |   if (bss[NL80211_BSS_SIGNAL_UNSPEC] != nullptr) { | ||||||
|     signal_strength_ = nla_get_u8(bss[NL80211_BSS_SIGNAL_UNSPEC]); |     signal_strength_ = nla_get_u8(bss[NL80211_BSS_SIGNAL_UNSPEC]); | ||||||
|  | @ -817,8 +824,8 @@ void waybar::modules::Network::parseSignal(struct nlattr **bss) { | ||||||
| 
 | 
 | ||||||
| void waybar::modules::Network::parseFreq(struct nlattr **bss) { | void waybar::modules::Network::parseFreq(struct nlattr **bss) { | ||||||
|   if (bss[NL80211_BSS_FREQUENCY] != nullptr) { |   if (bss[NL80211_BSS_FREQUENCY] != nullptr) { | ||||||
|     // in MHz
 |     // in GHz
 | ||||||
|     frequency_ = nla_get_u32(bss[NL80211_BSS_FREQUENCY]); |     frequency_ = (double) nla_get_u32(bss[NL80211_BSS_FREQUENCY]) / 1000; | ||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -79,6 +79,13 @@ bool waybar::modules::Pulseaudio::handleScroll(GdkEventScroll *e) { | ||||||
|   if (dir == SCROLL_DIR::NONE) { |   if (dir == SCROLL_DIR::NONE) { | ||||||
|     return true; |     return true; | ||||||
|   } |   } | ||||||
|  |   if (config_["reverse-scrolling"].asInt() == 1){ | ||||||
|  |     if (dir == SCROLL_DIR::UP) { | ||||||
|  |       dir = SCROLL_DIR::DOWN; | ||||||
|  |     } else if (dir == SCROLL_DIR::DOWN) { | ||||||
|  |       dir = SCROLL_DIR::UP; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|   double      volume_tick = static_cast<double>(PA_VOLUME_NORM) / 100; |   double      volume_tick = static_cast<double>(PA_VOLUME_NORM) / 100; | ||||||
|   pa_volume_t change = volume_tick; |   pa_volume_t change = volume_tick; | ||||||
|   pa_cvolume  pa_volume = pa_volume_; |   pa_cvolume  pa_volume = pa_volume_; | ||||||
|  | @ -211,7 +218,7 @@ static const std::array<std::string, 9> ports = { | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| const std::vector<std::string> waybar::modules::Pulseaudio::getPulseIcon() const { | const std::vector<std::string> waybar::modules::Pulseaudio::getPulseIcon() const { | ||||||
|   std::vector<std::string> res = {default_source_name_}; |   std::vector<std::string> res = {current_sink_name_, default_source_name_}; | ||||||
|   std::string nameLC = port_name_ + form_factor_; |   std::string nameLC = port_name_ + form_factor_; | ||||||
|   std::transform(nameLC.begin(), nameLC.end(), nameLC.begin(), ::tolower); |   std::transform(nameLC.begin(), nameLC.end(), nameLC.begin(), ::tolower); | ||||||
|   for (auto const &port : ports) { |   for (auto const &port : ports) { | ||||||
|  |  | ||||||
|  | @ -7,7 +7,6 @@ | ||||||
| 
 | 
 | ||||||
| #include "client.hpp" | #include "client.hpp" | ||||||
| #include "modules/river/tags.hpp" | #include "modules/river/tags.hpp" | ||||||
| #include "river-status-unstable-v1-client-protocol.h" |  | ||||||
| #include "xdg-output-unstable-v1-client-protocol.h" | #include "xdg-output-unstable-v1-client-protocol.h" | ||||||
| 
 | 
 | ||||||
| namespace waybar::modules::river { | namespace waybar::modules::river { | ||||||
|  | @ -33,6 +32,23 @@ static const zriver_output_status_v1_listener output_status_listener_impl{ | ||||||
|     .urgent_tags = listen_urgent_tags, |     .urgent_tags = listen_urgent_tags, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | static void listen_command_success(void *data, | ||||||
|  |                                    struct zriver_command_callback_v1 *zriver_command_callback_v1, | ||||||
|  |                                    const char *output) { | ||||||
|  |   // Do nothing but keep listener to avoid crashing when command was successful
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void listen_command_failure(void *data, | ||||||
|  |                                    struct zriver_command_callback_v1 *zriver_command_callback_v1, | ||||||
|  |                                    const char *output) { | ||||||
|  |   spdlog::error("failure when selecting/toggling tags {}", output); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static const zriver_command_callback_v1_listener command_callback_listener_impl { | ||||||
|  |     .success = listen_command_success, | ||||||
|  |     .failure = listen_command_failure, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
| static void handle_global(void *data, struct wl_registry *registry, uint32_t name, | static void handle_global(void *data, struct wl_registry *registry, uint32_t name, | ||||||
|                           const char *interface, uint32_t version) { |                           const char *interface, uint32_t version) { | ||||||
|   if (std::strcmp(interface, zriver_status_manager_v1_interface.name) == 0) { |   if (std::strcmp(interface, zriver_status_manager_v1_interface.name) == 0) { | ||||||
|  | @ -43,18 +59,33 @@ static void handle_global(void *data, struct wl_registry *registry, uint32_t nam | ||||||
|     static_cast<Tags *>(data)->status_manager_ = static_cast<struct zriver_status_manager_v1 *>( |     static_cast<Tags *>(data)->status_manager_ = static_cast<struct zriver_status_manager_v1 *>( | ||||||
|         wl_registry_bind(registry, name, &zriver_status_manager_v1_interface, version)); |         wl_registry_bind(registry, name, &zriver_status_manager_v1_interface, version)); | ||||||
|   } |   } | ||||||
|  | 
 | ||||||
|  |   if (std::strcmp(interface, zriver_control_v1_interface.name) == 0) { | ||||||
|  |     version = std::min<uint32_t>(version, 1); | ||||||
|  |     static_cast<Tags *>(data)->control_ = static_cast<struct zriver_control_v1 *>( | ||||||
|  |         wl_registry_bind(registry, name, &zriver_control_v1_interface, version)); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   if (std::strcmp(interface, wl_seat_interface.name) == 0) { | ||||||
|  |     version = std::min<uint32_t>(version, 1); | ||||||
|  |     static_cast<Tags *>(data)->seat_ = static_cast<struct wl_seat *>( | ||||||
|  |         wl_registry_bind(registry, name, &wl_seat_interface, version)); | ||||||
|  |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) { | static void handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) { | ||||||
|   /* Ignore event */ |   /* Ignore event */ | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| static const wl_registry_listener registry_listener_impl = {.global = handle_global, | static const wl_registry_listener registry_listener_impl = {.global = handle_global, | ||||||
|                                                             .global_remove = handle_global_remove}; |                                                             .global_remove = handle_global_remove}; | ||||||
| 
 | 
 | ||||||
| Tags::Tags(const std::string &id, const waybar::Bar &bar, const Json::Value &config) | Tags::Tags(const std::string &id, const waybar::Bar &bar, const Json::Value &config) | ||||||
|     : waybar::AModule(config, "tags", id, false, false), |     : waybar::AModule(config, "tags", id, false, false), | ||||||
|       status_manager_{nullptr}, |       status_manager_{nullptr}, | ||||||
|  |       control_{nullptr}, | ||||||
|  |       seat_{nullptr}, | ||||||
|       bar_(bar), |       bar_(bar), | ||||||
|       box_{bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0}, |       box_{bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0}, | ||||||
|       output_status_{nullptr} { |       output_status_{nullptr} { | ||||||
|  | @ -68,6 +99,14 @@ Tags::Tags(const std::string &id, const waybar::Bar &bar, const Json::Value &con | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   if (!control_) { | ||||||
|  |     spdlog::error("river_control_v1 not advertised"); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   if (!seat_) { | ||||||
|  |     spdlog::error("wl_seat not advertised"); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   box_.set_name("tags"); |   box_.set_name("tags"); | ||||||
|   if (!id.empty()) { |   if (!id.empty()) { | ||||||
|     box_.get_style_context()->add_class(id); |     box_.get_style_context()->add_class(id); | ||||||
|  | @ -89,11 +128,17 @@ Tags::Tags(const std::string &id, const waybar::Bar &bar, const Json::Value &con | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   uint32_t i = 1; | ||||||
|   for (const auto &tag_label : tag_labels) { |   for (const auto &tag_label : tag_labels) { | ||||||
|     Gtk::Button &button = buttons_.emplace_back(tag_label); |     Gtk::Button &button = buttons_.emplace_back(tag_label); | ||||||
|     button.set_relief(Gtk::RELIEF_NONE); |     button.set_relief(Gtk::RELIEF_NONE); | ||||||
|     box_.pack_start(button, false, false, 0); |     box_.pack_start(button, false, false, 0); | ||||||
|  |     if (!config_["disable-click"].asBool()) { | ||||||
|  |       button.signal_clicked().connect(sigc::bind(sigc::mem_fun(*this, &Tags::handle_primary_clicked), i)); | ||||||
|  |       button.signal_button_press_event().connect(sigc::bind(sigc::mem_fun(*this, &Tags::handle_button_press), i)); | ||||||
|  |     } | ||||||
|     button.show(); |     button.show(); | ||||||
|  |     i <<= 1; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   struct wl_output *output = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj()); |   struct wl_output *output = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj()); | ||||||
|  | @ -107,6 +152,31 @@ Tags::~Tags() { | ||||||
|   if (output_status_) { |   if (output_status_) { | ||||||
|     zriver_output_status_v1_destroy(output_status_); |     zriver_output_status_v1_destroy(output_status_); | ||||||
|   } |   } | ||||||
|  | 
 | ||||||
|  |   if (control_) { | ||||||
|  |     zriver_control_v1_destroy(control_); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void Tags::handle_primary_clicked(uint32_t tag) { | ||||||
|  |   // Send river command to select tag on left mouse click
 | ||||||
|  |   zriver_command_callback_v1 *callback; | ||||||
|  |   zriver_control_v1_add_argument(control_, "set-focused-tags"); | ||||||
|  |   zriver_control_v1_add_argument(control_, std::to_string(tag).c_str()); | ||||||
|  |   callback = zriver_control_v1_run_command(control_, seat_); | ||||||
|  |   zriver_command_callback_v1_add_listener(callback, &command_callback_listener_impl, nullptr); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool Tags::handle_button_press(GdkEventButton *event_button, uint32_t tag) { | ||||||
|  |   if (event_button->type == GDK_BUTTON_PRESS && event_button->button == 3) { | ||||||
|  |     // Send river command to toggle tag on right mouse click
 | ||||||
|  |     zriver_command_callback_v1 *callback; | ||||||
|  |     zriver_control_v1_add_argument(control_, "toggle-focused-tags"); | ||||||
|  |     zriver_control_v1_add_argument(control_, std::to_string(tag).c_str()); | ||||||
|  |     callback = zriver_control_v1_run_command(control_, seat_); | ||||||
|  |     zriver_command_callback_v1_add_listener(callback, &command_callback_listener_impl, nullptr); | ||||||
|  |   } | ||||||
|  |   return true; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void Tags::handle_focused_tags(uint32_t tags) { | void Tags::handle_focused_tags(uint32_t tags) { | ||||||
|  |  | ||||||
|  | @ -0,0 +1,107 @@ | ||||||
|  | #include "modules/sway/bar.hpp" | ||||||
|  | 
 | ||||||
|  | #include <fmt/ostream.h> | ||||||
|  | #include <spdlog/spdlog.h> | ||||||
|  | 
 | ||||||
|  | #include <stdexcept> | ||||||
|  | 
 | ||||||
|  | #include "bar.hpp" | ||||||
|  | #include "modules/sway/ipc/ipc.hpp" | ||||||
|  | 
 | ||||||
|  | namespace waybar::modules::sway { | ||||||
|  | 
 | ||||||
|  | BarIpcClient::BarIpcClient(waybar::Bar& bar) : bar_{bar} { | ||||||
|  |   { | ||||||
|  |     sigc::connection handle = | ||||||
|  |         ipc_.signal_cmd.connect(sigc::mem_fun(*this, &BarIpcClient::onInitialConfig)); | ||||||
|  |     ipc_.sendCmd(IPC_GET_BAR_CONFIG, bar_.bar_id); | ||||||
|  | 
 | ||||||
|  |     handle.disconnect(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   signal_config_.connect(sigc::mem_fun(*this, &BarIpcClient::onConfigUpdate)); | ||||||
|  |   signal_visible_.connect(sigc::mem_fun(*this, &BarIpcClient::onVisibilityUpdate)); | ||||||
|  | 
 | ||||||
|  |   ipc_.subscribe(R"(["bar_state_update", "barconfig_update"])"); | ||||||
|  |   ipc_.signal_event.connect(sigc::mem_fun(*this, &BarIpcClient::onIpcEvent)); | ||||||
|  |   // Launch worker
 | ||||||
|  |   ipc_.setWorker([this] { | ||||||
|  |     try { | ||||||
|  |       ipc_.handleEvent(); | ||||||
|  |     } catch (const std::exception& e) { | ||||||
|  |       spdlog::error("BarIpcClient::handleEvent {}", e.what()); | ||||||
|  |     } | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | struct swaybar_config parseConfig(const Json::Value& payload) { | ||||||
|  |   swaybar_config conf; | ||||||
|  |   if (auto id = payload["id"]; id.isString()) { | ||||||
|  |     conf.id = id.asString(); | ||||||
|  |   } | ||||||
|  |   if (auto mode = payload["mode"]; mode.isString()) { | ||||||
|  |     conf.mode = mode.asString(); | ||||||
|  |   } | ||||||
|  |   if (auto hs = payload["hidden_state"]; hs.isString()) { | ||||||
|  |     conf.hidden_state = hs.asString(); | ||||||
|  |   } | ||||||
|  |   return conf; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void BarIpcClient::onInitialConfig(const struct Ipc::ipc_response& res) { | ||||||
|  |   auto payload = parser_.parse(res.payload); | ||||||
|  |   if (auto success = payload.get("success", true); !success.asBool()) { | ||||||
|  |     auto err = payload.get("error", "Unknown error"); | ||||||
|  |     throw std::runtime_error(err.asString()); | ||||||
|  |   } | ||||||
|  |   auto config = parseConfig(payload); | ||||||
|  |   onConfigUpdate(config); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void BarIpcClient::onIpcEvent(const struct Ipc::ipc_response& res) { | ||||||
|  |   try { | ||||||
|  |     auto payload = parser_.parse(res.payload); | ||||||
|  |     if (auto id = payload["id"]; id.isString() && id.asString() != bar_.bar_id) { | ||||||
|  |       spdlog::trace("swaybar ipc: ignore event for {}", id.asString()); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     if (payload.isMember("visible_by_modifier")) { | ||||||
|  |       // visibility change for hidden bar
 | ||||||
|  |       signal_visible_(payload["visible_by_modifier"].asBool()); | ||||||
|  |     } else { | ||||||
|  |       // configuration update
 | ||||||
|  |       auto config = parseConfig(payload); | ||||||
|  |       signal_config_(std::move(config)); | ||||||
|  |     } | ||||||
|  |   } catch (const std::exception& e) { | ||||||
|  |     spdlog::error("BarIpcClient::onEvent {}", e.what()); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void BarIpcClient::onConfigUpdate(const swaybar_config& config) { | ||||||
|  |   spdlog::info("config update for {}: id {}, mode {}, hidden_state {}", | ||||||
|  |                bar_.bar_id, | ||||||
|  |                config.id, | ||||||
|  |                config.mode, | ||||||
|  |                config.hidden_state); | ||||||
|  |   bar_config_ = config; | ||||||
|  |   update(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void BarIpcClient::onVisibilityUpdate(bool visible_by_modifier) { | ||||||
|  |   spdlog::debug("visiblity update for {}: {}", bar_.bar_id, visible_by_modifier); | ||||||
|  |   visible_by_modifier_ = visible_by_modifier; | ||||||
|  |   update(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void BarIpcClient::update() { | ||||||
|  |   bool visible = visible_by_modifier_; | ||||||
|  |   if (bar_config_.mode == "invisible") { | ||||||
|  |     visible = false; | ||||||
|  |   } else if (bar_config_.mode != "hide" || bar_config_.hidden_state != "hide") { | ||||||
|  |     visible = true; | ||||||
|  |   } | ||||||
|  |   bar_.setMode(visible ? bar_config_.mode : Bar::MODE_INVISIBLE); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | }  // namespace waybar::modules::sway
 | ||||||
|  | @ -99,7 +99,8 @@ auto Language::update() -> void { | ||||||
|                                          fmt::arg("short", layout_.short_name), |                                          fmt::arg("short", layout_.short_name), | ||||||
|                                          fmt::arg("shortDescription", layout_.short_description), |                                          fmt::arg("shortDescription", layout_.short_description), | ||||||
|                                          fmt::arg("long", layout_.full_name), |                                          fmt::arg("long", layout_.full_name), | ||||||
|                                          fmt::arg("variant", layout_.variant))); |                                          fmt::arg("variant", layout_.variant), | ||||||
|  |                                          fmt::arg("flag", layout_.country_flag()))); | ||||||
|   label_.set_markup(display_layout); |   label_.set_markup(display_layout); | ||||||
|   if (tooltipEnabled()) { |   if (tooltipEnabled()) { | ||||||
|     if (tooltip_format_ != "") { |     if (tooltip_format_ != "") { | ||||||
|  | @ -107,7 +108,8 @@ auto Language::update() -> void { | ||||||
|                                                      fmt::arg("short", layout_.short_name), |                                                      fmt::arg("short", layout_.short_name), | ||||||
|                                                      fmt::arg("shortDescription", layout_.short_description), |                                                      fmt::arg("shortDescription", layout_.short_description), | ||||||
|                                                      fmt::arg("long", layout_.full_name), |                                                      fmt::arg("long", layout_.full_name), | ||||||
|                                                      fmt::arg("variant", layout_.variant))); |                                                      fmt::arg("variant", layout_.variant), | ||||||
|  |                                                      fmt::arg("flag", layout_.country_flag()))); | ||||||
|       label_.set_tooltip_markup(tooltip_display_layout); |       label_.set_tooltip_markup(tooltip_display_layout); | ||||||
|     } else { |     } else { | ||||||
|       label_.set_tooltip_markup(display_layout); |       label_.set_tooltip_markup(display_layout); | ||||||
|  | @ -212,4 +214,15 @@ Language::XKBContext::~XKBContext() { | ||||||
|   rxkb_context_unref(context_); |   rxkb_context_unref(context_); | ||||||
|   delete layout_; |   delete layout_; | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | std::string Language::Layout::country_flag() const { | ||||||
|  |   if (short_name.size() != 2) return ""; | ||||||
|  |   unsigned char result[] = "\xf0\x9f\x87\x00\xf0\x9f\x87\x00"; | ||||||
|  |   result[3] = short_name[0] + 0x45; | ||||||
|  |   result[7] = short_name[1] + 0x45; | ||||||
|  |   // Check if both emojis are in A-Z symbol bounds
 | ||||||
|  |   if (result[3] < 0xa6 || result[3] > 0xbf) return ""; | ||||||
|  |   if (result[7] < 0xa6 || result[7] > 0xbf) return ""; | ||||||
|  |   return std::string{reinterpret_cast<char*>(result)}; | ||||||
|  | } | ||||||
| }  // namespace waybar::modules::sway
 | }  // namespace waybar::modules::sway
 | ||||||
|  |  | ||||||
|  | @ -2,6 +2,7 @@ | ||||||
| 
 | 
 | ||||||
| #include <spdlog/spdlog.h> | #include <spdlog/spdlog.h> | ||||||
| 
 | 
 | ||||||
|  | #include <algorithm> | ||||||
| #include <cctype> | #include <cctype> | ||||||
| #include <string> | #include <string> | ||||||
| 
 | 
 | ||||||
|  | @ -98,6 +99,7 @@ void Workspaces::onCmd(const struct Ipc::ipc_response &res) { | ||||||
|                   Json::Value v; |                   Json::Value v; | ||||||
|                   v["name"] = p_w_name; |                   v["name"] = p_w_name; | ||||||
|                   v["target_output"] = bar_.output->name; |                   v["target_output"] = bar_.output->name; | ||||||
|  |                   v["num"] = convertWorkspaceNameToNum(p_w_name); | ||||||
|                   workspaces_.emplace_back(std::move(v)); |                   workspaces_.emplace_back(std::move(v)); | ||||||
|                   break; |                   break; | ||||||
|                 } |                 } | ||||||
|  | @ -107,57 +109,59 @@ void Workspaces::onCmd(const struct Ipc::ipc_response &res) { | ||||||
|               Json::Value v; |               Json::Value v; | ||||||
|               v["name"] = p_w_name; |               v["name"] = p_w_name; | ||||||
|               v["target_output"] = ""; |               v["target_output"] = ""; | ||||||
|  |               v["num"] = convertWorkspaceNameToNum(p_w_name); | ||||||
|               workspaces_.emplace_back(std::move(v)); |               workspaces_.emplace_back(std::move(v)); | ||||||
|             } |             } | ||||||
|           } |           } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         // config option to sort numeric workspace names before others
 |         // sway has a defined ordering of workspaces that should be preserved in
 | ||||||
|         bool config_numeric_first = config_["numeric-first"].asBool(); |         // the representation displayed by waybar to ensure that commands such
 | ||||||
| 
 |         // as "workspace prev" or "workspace next" make sense when looking at
 | ||||||
|  |         // the workspace representation in the bar.
 | ||||||
|  |         // Due to waybar's own feature of persistent workspaces unknown to sway,
 | ||||||
|  |         // custom sorting logic is necessary to make these workspaces appear
 | ||||||
|  |         // naturally in the list of workspaces without messing up sway's
 | ||||||
|  |         // sorting. For this purpose, a custom numbering property is created
 | ||||||
|  |         // that preserves the order provided by sway while inserting numbered
 | ||||||
|  |         // persistent workspaces at their natural positions.
 | ||||||
|  |         //
 | ||||||
|  |         // All of this code assumes that sway provides numbered workspaces first
 | ||||||
|  |         // and other workspaces are sorted by their creation time.
 | ||||||
|  |         //
 | ||||||
|  |         // In a first pass, the maximum "num" value is computed to enqueue
 | ||||||
|  |         // unnumbered workspaces behind numbered ones when computing the sort
 | ||||||
|  |         // attribute.
 | ||||||
|  |         int max_num = -1; | ||||||
|  |         for (auto & workspace : workspaces_) { | ||||||
|  |           max_num = std::max(workspace["num"].asInt(), max_num); | ||||||
|  |         } | ||||||
|  |         for (auto & workspace : workspaces_) { | ||||||
|  |           auto workspace_num = workspace["num"].asInt(); | ||||||
|  |           if (workspace_num > -1) { | ||||||
|  |             workspace["sort"] = workspace_num; | ||||||
|  |           } else { | ||||||
|  |             workspace["sort"] = ++max_num; | ||||||
|  |           } | ||||||
|  |         } | ||||||
|         std::sort(workspaces_.begin(), |         std::sort(workspaces_.begin(), | ||||||
|                   workspaces_.end(), |                   workspaces_.end(), | ||||||
|                   [config_numeric_first](const Json::Value &lhs, const Json::Value &rhs) { |                   [](const Json::Value &lhs, const Json::Value &rhs) { | ||||||
|                     // the "num" property (integer type):
 |  | ||||||
|                     // The workspace number or -1 for workspaces that do
 |  | ||||||
|                     // not start with a number.
 |  | ||||||
|                     // We could rely on sway providing this property:
 |  | ||||||
|                     //
 |  | ||||||
|                     //     auto l = lhs["num"].asInt();
 |  | ||||||
|                     //     auto r = rhs["num"].asInt();
 |  | ||||||
|                     //
 |  | ||||||
|                     // We cannot rely on the "num" property as provided by sway
 |  | ||||||
|                     // via IPC, because persistent workspace might not exist in
 |  | ||||||
|                     // sway's view. However, we need this property also for
 |  | ||||||
|                     // not-yet created persistent workspace. As such, we simply
 |  | ||||||
|                     // duplicate sway's logic of assigning the "num" property
 |  | ||||||
|                     // into waybar (see convertWorkspaceNameToNum). This way the
 |  | ||||||
|                     // sorting should work out even when we include workspaces
 |  | ||||||
|                     // that do not currently exist.
 |  | ||||||
|                     auto lname = lhs["name"].asString(); |                     auto lname = lhs["name"].asString(); | ||||||
|                     auto rname = rhs["name"].asString(); |                     auto rname = rhs["name"].asString(); | ||||||
|                     int  l = convertWorkspaceNameToNum(lname); |                     int  l = lhs["sort"].asInt(); | ||||||
|                     int  r = convertWorkspaceNameToNum(rname); |                     int  r = rhs["sort"].asInt(); | ||||||
| 
 | 
 | ||||||
|                     if (l == r) { |                     if (l == r) { | ||||||
|                       // in case both integers are the same, lexicographical
 |                       // In case both integers are the same, lexicographical
 | ||||||
|                       // sort. This also covers the case when both don't have a
 |                       // sort. The code above already ensure that this will only
 | ||||||
|                       // number (i.e., l == r == -1).
 |                       // happend in case of explicitly numbered workspaces.
 | ||||||
|                       return lname < rname; |                       return lname < rname; | ||||||
|                     } |                     } | ||||||
| 
 | 
 | ||||||
|                     // one of the workspaces doesn't begin with a number, so
 |  | ||||||
|                     // num is -1.
 |  | ||||||
|                     if (l < 0 || r < 0) { |  | ||||||
|                       if (config_numeric_first) { |  | ||||||
|                         return r < 0; |  | ||||||
|                       } |  | ||||||
|                       return l < 0; |  | ||||||
|                     } |  | ||||||
| 
 |  | ||||||
|                     // both workspaces have a "num" so let's just compare those
 |  | ||||||
|                     return l < r; |                     return l < r; | ||||||
|                   }); |                   }); | ||||||
|  | 
 | ||||||
|       } |       } | ||||||
|       dp.emit(); |       dp.emit(); | ||||||
|     } catch (const std::exception &e) { |     } catch (const std::exception &e) { | ||||||
|  |  | ||||||
|  | @ -11,7 +11,9 @@ | ||||||
| #include <cstring> | #include <cstring> | ||||||
| #include <memory> | #include <memory> | ||||||
| #include <sstream> | #include <sstream> | ||||||
|  | #include <utility> | ||||||
| 
 | 
 | ||||||
|  | #include <fmt/core.h> | ||||||
| #include <gdkmm/monitor.h> | #include <gdkmm/monitor.h> | ||||||
| 
 | 
 | ||||||
| #include <gtkmm/icontheme.h> | #include <gtkmm/icontheme.h> | ||||||
|  | @ -86,8 +88,7 @@ static Glib::RefPtr<Gdk::Pixbuf> load_icon_from_file(std::string icon_path, int | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /* Method 1 - get the correct icon name from the desktop file */ | static Glib::RefPtr<Gio::DesktopAppInfo> get_app_info_by_name(const std::string& app_id) | ||||||
| static std::string get_from_desktop_app_info(const std::string &app_id) |  | ||||||
| { | { | ||||||
|     static std::vector<std::string> prefixes = search_prefix(); |     static std::vector<std::string> prefixes = search_prefix(); | ||||||
| 
 | 
 | ||||||
|  | @ -103,33 +104,29 @@ static std::string get_from_desktop_app_info(const std::string &app_id) | ||||||
|         ".desktop" |         ".desktop" | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     Glib::RefPtr<Gio::DesktopAppInfo> app_info; | 	for (auto& prefix : prefixes) { | ||||||
|  | 		for (auto& folder : app_folders) { | ||||||
|  | 			for (auto& suffix : suffixes) { | ||||||
|  |                     auto app_info_ = Gio::DesktopAppInfo::create_from_filename(prefix + folder + app_id + suffix); | ||||||
|  | 					if (!app_info_) { | ||||||
|  | 						continue; | ||||||
|  | 					} | ||||||
| 
 | 
 | ||||||
|     for (auto& prefix : prefixes) | 					return app_info_; | ||||||
|         for (auto& folder : app_folders) | 			} | ||||||
|             for (auto& suffix : suffixes) | 		} | ||||||
|                 if (!app_info) | 	} | ||||||
|                     app_info = Gio::DesktopAppInfo::create_from_filename(prefix + folder + app_id + suffix); |  | ||||||
| 
 | 
 | ||||||
|     if (app_info && app_info->get_icon()) | 	return {}; | ||||||
|         return app_info->get_icon()->to_string(); |  | ||||||
| 
 |  | ||||||
|     return ""; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /* Method 2 - use the app_id and check whether there is an icon with this name in the icon theme */ | Glib::RefPtr<Gio::DesktopAppInfo> get_desktop_app_info(const std::string &app_id) | ||||||
| static std::string get_from_icon_theme(const Glib::RefPtr<Gtk::IconTheme>& icon_theme, |  | ||||||
|         const std::string &app_id) |  | ||||||
| { | { | ||||||
|     if (icon_theme->lookup_icon(app_id, 24)) | 	auto app_info = get_app_info_by_name(app_id); | ||||||
|         return app_id; | 	if (app_info) { | ||||||
|  | 		return app_info; | ||||||
|  | 	} | ||||||
| 
 | 
 | ||||||
|     return ""; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /* Method 3 - as last resort perform a search for most appropriate desktop info file */ |  | ||||||
| static std::string get_from_desktop_app_info_search(const std::string &app_id) |  | ||||||
| { |  | ||||||
|     std::string desktop_file = ""; |     std::string desktop_file = ""; | ||||||
| 
 | 
 | ||||||
|     gchar*** desktop_list = g_desktop_app_info_search(app_id.c_str()); |     gchar*** desktop_list = g_desktop_app_info_search(app_id.c_str()); | ||||||
|  | @ -151,65 +148,84 @@ static std::string get_from_desktop_app_info_search(const std::string &app_id) | ||||||
|     } |     } | ||||||
|     g_free(desktop_list); |     g_free(desktop_list); | ||||||
| 
 | 
 | ||||||
|     return get_from_desktop_app_info(desktop_file); |     return get_app_info_by_name(desktop_file); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static bool image_load_icon(Gtk::Image& image, const Glib::RefPtr<Gtk::IconTheme>& icon_theme, | void Task::set_app_info_from_app_id_list(const std::string& app_id_list) { | ||||||
|         const std::string &app_id_list, int size) |  | ||||||
| { |  | ||||||
|     std::string app_id; |     std::string app_id; | ||||||
|     std::istringstream stream(app_id_list); |     std::istringstream stream(app_id_list); | ||||||
|     bool found = false; |  | ||||||
| 
 | 
 | ||||||
|     /* Wayfire sends a list of app-id's in space separated format, other compositors
 |     /* Wayfire sends a list of app-id's in space separated format, other compositors
 | ||||||
|      * send a single app-id, but in any case this works fine */ |      * send a single app-id, but in any case this works fine */ | ||||||
|     while (stream >> app_id) |     while (stream >> app_id) | ||||||
|     { |     { | ||||||
|  |         app_info_ = get_desktop_app_info(app_id); | ||||||
|  | 		if (app_info_) { | ||||||
|  | 			return; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		auto lower_app_id = app_id; | ||||||
|  | 		std::transform(lower_app_id.begin(), lower_app_id.end(), lower_app_id.begin(), | ||||||
|  | 				[](char c){ return std::tolower(c); }); | ||||||
|  | 		app_info_ = get_desktop_app_info(lower_app_id); | ||||||
|  | 		if (app_info_) { | ||||||
|  | 			return; | ||||||
|  | 		} | ||||||
|  | 				 | ||||||
|         size_t start = 0, end = app_id.size(); |         size_t start = 0, end = app_id.size(); | ||||||
|         start = app_id.rfind(".", end); |         start = app_id.rfind(".", end); | ||||||
|         std::string app_name = app_id.substr(start+1, app_id.size()); |         std::string app_name = app_id.substr(start+1, app_id.size()); | ||||||
|  |         app_info_ = get_desktop_app_info(app_name); | ||||||
|  | 		if (app_info_) { | ||||||
|  | 			return; | ||||||
|  | 		} | ||||||
| 
 | 
 | ||||||
|         auto lower_app_id = app_id; |         start = app_id.find("-"); | ||||||
|         std::transform(lower_app_id.begin(), lower_app_id.end(), lower_app_id.begin(), |         app_name = app_id.substr(0, start); | ||||||
|                 [](char c){ return std::tolower(c); }); |         app_info_ = get_desktop_app_info(app_name); | ||||||
|  | 	} | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
|         std::string icon_name = get_from_icon_theme(icon_theme, app_id); | static std::string get_icon_name_from_icon_theme(const Glib::RefPtr<Gtk::IconTheme>& icon_theme, | ||||||
|  |         const std::string &app_id) | ||||||
|  | { | ||||||
|  |     if (icon_theme->lookup_icon(app_id, 24)) | ||||||
|  |         return app_id; | ||||||
| 
 | 
 | ||||||
|         if (icon_name.empty()) |     return ""; | ||||||
|             icon_name = get_from_icon_theme(icon_theme, lower_app_id); | } | ||||||
|         if (icon_name.empty()) |  | ||||||
|             icon_name = get_from_icon_theme(icon_theme, app_name); |  | ||||||
|         if (icon_name.empty()) |  | ||||||
|             icon_name = get_from_desktop_app_info(app_id); |  | ||||||
|         if (icon_name.empty()) |  | ||||||
|             icon_name = get_from_desktop_app_info(lower_app_id); |  | ||||||
|         if (icon_name.empty()) |  | ||||||
|             icon_name = get_from_desktop_app_info(app_name); |  | ||||||
|         if (icon_name.empty()) |  | ||||||
|             icon_name = get_from_desktop_app_info_search(app_id); |  | ||||||
| 
 | 
 | ||||||
|         if (icon_name.empty()) | bool Task::image_load_icon(Gtk::Image& image, const Glib::RefPtr<Gtk::IconTheme>& icon_theme, Glib::RefPtr<Gio::DesktopAppInfo> app_info, int size) | ||||||
|             icon_name = "unknown"; | { | ||||||
|  | 	std::string ret_icon_name = "unknown"; | ||||||
|  | 	if (app_info) { | ||||||
|  | 		std::string icon_name = get_icon_name_from_icon_theme(icon_theme, app_info->get_startup_wm_class()); | ||||||
|  | 		if (!icon_name.empty()) { | ||||||
|  | 			ret_icon_name = icon_name; | ||||||
|  | 		} else { | ||||||
|  | 			if (app_info->get_icon()) { | ||||||
|  | 				ret_icon_name = app_info->get_icon()->to_string(); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
| 
 | 
 | ||||||
|         Glib::RefPtr<Gdk::Pixbuf> pixbuf; | 	Glib::RefPtr<Gdk::Pixbuf> pixbuf; | ||||||
| 
 | 
 | ||||||
|         try { | 	try { | ||||||
|             pixbuf = icon_theme->load_icon(icon_name, size, Gtk::ICON_LOOKUP_FORCE_SIZE); | 		pixbuf = icon_theme->load_icon(ret_icon_name, size, Gtk::ICON_LOOKUP_FORCE_SIZE); | ||||||
|         } catch(...) { | 	} catch(...) { | ||||||
|             if (Glib::file_test(icon_name, Glib::FILE_TEST_EXISTS)) | 		if (Glib::file_test(ret_icon_name, Glib::FILE_TEST_EXISTS)) | ||||||
|                 pixbuf = load_icon_from_file(icon_name, size); | 			pixbuf = load_icon_from_file(ret_icon_name, size); | ||||||
|             else | 		else | ||||||
|                 pixbuf = {}; | 			pixbuf = {}; | ||||||
|         } | 	} | ||||||
| 
 | 
 | ||||||
|         if (pixbuf) { | 	if (pixbuf) { | ||||||
|             image.set(pixbuf); | 		image.set(pixbuf); | ||||||
|             found = true; | 		return true; | ||||||
|             break; | 	} | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     return found; |     return false; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /* Task class implementation */ | /* Task class implementation */ | ||||||
|  | @ -276,8 +292,7 @@ Task::Task(const waybar::Bar &bar, const Json::Value &config, Taskbar *tbar, | ||||||
|         struct zwlr_foreign_toplevel_handle_v1 *tl_handle, struct wl_seat *seat) : |         struct zwlr_foreign_toplevel_handle_v1 *tl_handle, struct wl_seat *seat) : | ||||||
|     bar_{bar}, config_{config}, tbar_{tbar}, handle_{tl_handle}, seat_{seat}, |     bar_{bar}, config_{config}, tbar_{tbar}, handle_{tl_handle}, seat_{seat}, | ||||||
|     id_{global_id++}, |     id_{global_id++}, | ||||||
|     content_{bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0}, |     content_{bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0} | ||||||
|     button_visible_{false}, ignored_{false} |  | ||||||
| { | { | ||||||
|     zwlr_foreign_toplevel_handle_v1_add_listener(handle_, &toplevel_handle_impl, this); |     zwlr_foreign_toplevel_handle_v1_add_listener(handle_, &toplevel_handle_impl, this); | ||||||
| 
 | 
 | ||||||
|  | @ -290,13 +305,15 @@ Task::Task(const waybar::Bar &bar, const Json::Value &config, Taskbar *tbar, | ||||||
|     content_.show(); |     content_.show(); | ||||||
|     button_.add(content_); |     button_.add(content_); | ||||||
| 
 | 
 | ||||||
|     with_icon_ = false; |  | ||||||
|     format_before_.clear(); |     format_before_.clear(); | ||||||
|     format_after_.clear(); |     format_after_.clear(); | ||||||
| 
 | 
 | ||||||
|     if (config_["format"].isString()) { |     if (config_["format"].isString()) { | ||||||
|         /* The user defined a format string, use it */ |         /* The user defined a format string, use it */ | ||||||
|         auto format = config_["format"].asString(); |         auto format = config_["format"].asString(); | ||||||
|  | 		if (format.find("{name}") != std::string::npos) { | ||||||
|  | 			with_name_ = true; | ||||||
|  | 		} | ||||||
| 
 | 
 | ||||||
|         auto icon_pos = format.find("{icon}"); |         auto icon_pos = format.find("{icon}"); | ||||||
|         if (icon_pos == 0) { |         if (icon_pos == 0) { | ||||||
|  | @ -377,13 +394,12 @@ std::string Task::state_string(bool shortened) const | ||||||
| void Task::handle_title(const char *title) | void Task::handle_title(const char *title) | ||||||
| { | { | ||||||
|     title_ = title; |     title_ = title; | ||||||
|  |     hide_if_ignored(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void Task::handle_app_id(const char *app_id) | void Task::hide_if_ignored() | ||||||
| { | { | ||||||
|     app_id_ = app_id; |     if (tbar_->ignore_list().count(app_id_) || tbar_->ignore_list().count(title_)) { | ||||||
| 
 |  | ||||||
|     if (tbar_->ignore_list().count(app_id)) { |  | ||||||
|         ignored_ = true; |         ignored_ = true; | ||||||
|         if (button_visible_) { |         if (button_visible_) { | ||||||
|           auto output = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj()); |           auto output = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj()); | ||||||
|  | @ -397,14 +413,35 @@ void Task::handle_app_id(const char *app_id) | ||||||
|           handle_output_enter(output); |           handle_output_enter(output); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
|     if (!with_icon_) | void Task::handle_app_id(const char *app_id) | ||||||
|  | { | ||||||
|  |     app_id_ = app_id; | ||||||
|  |     hide_if_ignored(); | ||||||
|  | 
 | ||||||
|  | 	auto ids_replace_map = tbar_->app_ids_replace_map(); | ||||||
|  | 	if (ids_replace_map.count(app_id_)) { | ||||||
|  | 		auto replaced_id = ids_replace_map[app_id_]; | ||||||
|  | 		spdlog::debug(fmt::format("Task ({}) [{}] app_id was replaced with {}", id_, app_id_, replaced_id)); | ||||||
|  | 		app_id_ = replaced_id; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if (!with_icon_ && !with_name_) { | ||||||
|         return; |         return; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	set_app_info_from_app_id_list(app_id_); | ||||||
|  | 	name_ = app_info_ ? app_info_->get_display_name() : app_id; | ||||||
|  | 
 | ||||||
|  | 	if (!with_icon_) { | ||||||
|  | 		return; | ||||||
|  | 	} | ||||||
| 
 | 
 | ||||||
|     int icon_size = config_["icon-size"].isInt() ? config_["icon-size"].asInt() : 16; |     int icon_size = config_["icon-size"].isInt() ? config_["icon-size"].asInt() : 16; | ||||||
|     bool found = false; |     bool found = false; | ||||||
|     for (auto& icon_theme : tbar_->icon_themes()) { |     for (auto& icon_theme : tbar_->icon_themes()) { | ||||||
|         if (image_load_icon(icon_, icon_theme, app_id_, icon_size)) { |         if (image_load_icon(icon_, icon_theme, app_info_, icon_size)) { | ||||||
|             found = true; |             found = true; | ||||||
|             break; |             break; | ||||||
|         } |         } | ||||||
|  | @ -418,13 +455,13 @@ void Task::handle_app_id(const char *app_id) | ||||||
| 
 | 
 | ||||||
| void Task::handle_output_enter(struct wl_output *output) | void Task::handle_output_enter(struct wl_output *output) | ||||||
| { | { | ||||||
|     spdlog::debug("{} entered output {}", repr(), (void*)output); |  | ||||||
| 
 |  | ||||||
|     if (ignored_) { |     if (ignored_) { | ||||||
|       spdlog::debug("{} is ignored", repr()); |       spdlog::debug("{} is ignored", repr()); | ||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     spdlog::debug("{} entered output {}", repr(), (void*)output); | ||||||
|  | 
 | ||||||
|     if (!button_visible_ && (tbar_->all_outputs() || tbar_->show_output(output))) { |     if (!button_visible_ && (tbar_->all_outputs() || tbar_->show_output(output))) { | ||||||
|         /* The task entered the output of the current bar make the button visible */ |         /* The task entered the output of the current bar make the button visible */ | ||||||
|         tbar_->add_button(button_); |         tbar_->add_button(button_); | ||||||
|  | @ -560,14 +597,17 @@ void Task::update() | ||||||
| { | { | ||||||
|     bool markup = config_["markup"].isBool() ? config_["markup"].asBool() : false; |     bool markup = config_["markup"].isBool() ? config_["markup"].asBool() : false; | ||||||
|     std::string title = title_; |     std::string title = title_; | ||||||
|  |     std::string name = name_; | ||||||
|     std::string app_id = app_id_; |     std::string app_id = app_id_; | ||||||
|     if (markup) { |     if (markup) { | ||||||
|         title = Glib::Markup::escape_text(title); |         title = Glib::Markup::escape_text(title); | ||||||
|  |         name = Glib::Markup::escape_text(name); | ||||||
|         app_id = Glib::Markup::escape_text(app_id); |         app_id = Glib::Markup::escape_text(app_id); | ||||||
|     } |     } | ||||||
|     if (!format_before_.empty()) { |     if (!format_before_.empty()) { | ||||||
|         auto txt = fmt::format(format_before_, |         auto txt = fmt::format(format_before_, | ||||||
|                     fmt::arg("title", title), |                     fmt::arg("title", title), | ||||||
|  |                     fmt::arg("name", name), | ||||||
|                     fmt::arg("app_id", app_id), |                     fmt::arg("app_id", app_id), | ||||||
|                     fmt::arg("state", state_string()), |                     fmt::arg("state", state_string()), | ||||||
|                     fmt::arg("short_state", state_string(true)) |                     fmt::arg("short_state", state_string(true)) | ||||||
|  | @ -581,6 +621,7 @@ void Task::update() | ||||||
|     if (!format_after_.empty()) { |     if (!format_after_.empty()) { | ||||||
|         auto txt = fmt::format(format_after_, |         auto txt = fmt::format(format_after_, | ||||||
|                     fmt::arg("title", title), |                     fmt::arg("title", title), | ||||||
|  |                     fmt::arg("name", name), | ||||||
|                     fmt::arg("app_id", app_id), |                     fmt::arg("app_id", app_id), | ||||||
|                     fmt::arg("state", state_string()), |                     fmt::arg("state", state_string()), | ||||||
|                     fmt::arg("short_state", state_string(true)) |                     fmt::arg("short_state", state_string(true)) | ||||||
|  | @ -595,6 +636,7 @@ void Task::update() | ||||||
|     if (!format_tooltip_.empty()) { |     if (!format_tooltip_.empty()) { | ||||||
|         auto txt = fmt::format(format_tooltip_, |         auto txt = fmt::format(format_tooltip_, | ||||||
|                     fmt::arg("title", title), |                     fmt::arg("title", title), | ||||||
|  |                     fmt::arg("name", name), | ||||||
|                     fmt::arg("app_id", app_id), |                     fmt::arg("app_id", app_id), | ||||||
|                     fmt::arg("state", state_string()), |                     fmt::arg("state", state_string()), | ||||||
|                     fmt::arg("short_state", state_string(true)) |                     fmt::arg("short_state", state_string(true)) | ||||||
|  | @ -722,6 +764,15 @@ Taskbar::Taskbar(const std::string &id, const waybar::Bar &bar, const Json::Valu | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     // Load app_id remappings
 | ||||||
|  |     if (config_["app_ids-mapping"].isObject()) { | ||||||
|  | 		const Json::Value& mapping = config_["app_ids-mapping"]; | ||||||
|  | 		const std::vector<std::string> app_ids = config_["app_ids-mapping"].getMemberNames(); | ||||||
|  |         for (auto& app_id : app_ids) { | ||||||
|  |           app_ids_replace_map_.emplace(app_id, mapping[app_id].asString()); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     icon_themes_.push_back(Gtk::IconTheme::get_default()); |     icon_themes_.push_back(Gtk::IconTheme::get_default()); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -853,10 +904,10 @@ bool Taskbar::all_outputs() const | ||||||
|     return config_["all-outputs"].isBool() && config_["all-outputs"].asBool(); |     return config_["all-outputs"].isBool() && config_["all-outputs"].asBool(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| std::vector<Glib::RefPtr<Gtk::IconTheme>> Taskbar::icon_themes() const | const std::vector<Glib::RefPtr<Gtk::IconTheme>>& Taskbar::icon_themes() const { return icon_themes_; } | ||||||
| { | 
 | ||||||
|     return icon_themes_; | const std::unordered_set<std::string>& Taskbar::ignore_list() const { return ignore_list_; } | ||||||
| } | 
 | ||||||
| const std::unordered_set<std::string> &Taskbar::ignore_list() const { return ignore_list_; } | const std::map<std::string, std::string>& Taskbar::app_ids_replace_map() const { return app_ids_replace_map_; } | ||||||
| 
 | 
 | ||||||
| } /* namespace waybar::modules::wlr */ | } /* namespace waybar::modules::wlr */ | ||||||
|  |  | ||||||
|  | @ -0,0 +1,472 @@ | ||||||
|  | #include "modules/wlr/workspace_manager.hpp" | ||||||
|  | 
 | ||||||
|  | #include <gdk/gdkwayland.h> | ||||||
|  | #include <gtkmm.h> | ||||||
|  | #include <spdlog/spdlog.h> | ||||||
|  | 
 | ||||||
|  | #include <algorithm> | ||||||
|  | #include <iterator> | ||||||
|  | #include <vector> | ||||||
|  | 
 | ||||||
|  | #include "gtkmm/widget.h" | ||||||
|  | #include "modules/wlr/workspace_manager_binding.hpp" | ||||||
|  | 
 | ||||||
|  | namespace waybar::modules::wlr { | ||||||
|  | 
 | ||||||
|  | uint32_t                           WorkspaceGroup::workspace_global_id = 0; | ||||||
|  | uint32_t                           WorkspaceManager::group_global_id = 0; | ||||||
|  | std::map<std::string, std::string> Workspace::icons_map_; | ||||||
|  | 
 | ||||||
|  | WorkspaceManager::WorkspaceManager(const std::string &id, const waybar::Bar &bar, | ||||||
|  |                                    const Json::Value &config) | ||||||
|  |     : waybar::AModule(config, "workspaces", id, false, false), | ||||||
|  |       bar_(bar), | ||||||
|  |       box_(bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0) { | ||||||
|  |   auto config_sort_by_name = config_["sort-by-name"]; | ||||||
|  |   if (config_sort_by_name.isBool()) { | ||||||
|  |     sort_by_name_ = config_sort_by_name.asBool(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   auto config_sort_by_coordinates = config_["sort-by-coordinates"]; | ||||||
|  |   if (config_sort_by_coordinates.isBool()) { | ||||||
|  |     sort_by_coordinates_ = config_sort_by_coordinates.asBool(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   auto config_all_outputs = config_["all-outputs"]; | ||||||
|  |   if (config_all_outputs.isBool()) { | ||||||
|  |     all_outputs_ = config_all_outputs.asBool(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   auto config_active_only = config_["active-only"]; | ||||||
|  |   if (config_active_only.isBool()) { | ||||||
|  |     active_only_ = config_active_only.asBool(); | ||||||
|  |     creation_delayed_ = active_only_; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   box_.set_name("workspaces"); | ||||||
|  |   if (!id.empty()) { | ||||||
|  |     box_.get_style_context()->add_class(id); | ||||||
|  |   } | ||||||
|  |   event_box_.add(box_); | ||||||
|  | 
 | ||||||
|  |   add_registry_listener(this); | ||||||
|  |   if (!workspace_manager_) { | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | auto WorkspaceManager::workspace_comparator() const | ||||||
|  |     -> std::function<bool(std::unique_ptr<Workspace> &, std::unique_ptr<Workspace> &)> { | ||||||
|  |   return [=](std::unique_ptr<Workspace> &lhs, std::unique_ptr<Workspace> &rhs) { | ||||||
|  |     auto is_name_less = lhs->get_name() < rhs->get_name(); | ||||||
|  |     auto is_name_eq = lhs->get_name() == rhs->get_name(); | ||||||
|  |     auto is_coords_less = lhs->get_coords() < rhs->get_coords(); | ||||||
|  |     if (sort_by_name_) { | ||||||
|  |       if (sort_by_coordinates_) { | ||||||
|  |         return is_name_eq ? is_coords_less : is_name_less; | ||||||
|  |       } else { | ||||||
|  |         return is_name_less; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (sort_by_coordinates_) { | ||||||
|  |       return is_coords_less; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return lhs->id() < rhs->id(); | ||||||
|  |   }; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | auto WorkspaceManager::sort_workspaces() -> void { | ||||||
|  |   std::vector<std::reference_wrapper<std::unique_ptr<Workspace>>> all_workspaces; | ||||||
|  |   for (auto &group : groups_) { | ||||||
|  |     auto &group_workspaces = group->workspaces(); | ||||||
|  |     all_workspaces.reserve(all_workspaces.size() + | ||||||
|  |                            std::distance(group_workspaces.begin(), group_workspaces.end())); | ||||||
|  |     if (!active_only()) { | ||||||
|  |       all_workspaces.insert(all_workspaces.end(), group_workspaces.begin(), group_workspaces.end()); | ||||||
|  |       continue; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     for (auto &workspace : group_workspaces) { | ||||||
|  |       if (!workspace->is_active()) { | ||||||
|  |         continue; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       all_workspaces.push_back(workspace); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   std::sort(all_workspaces.begin(), all_workspaces.end(), workspace_comparator()); | ||||||
|  |   for (size_t i = 0; i < all_workspaces.size(); ++i) { | ||||||
|  |     box_.reorder_child(all_workspaces[i].get()->get_button_ref(), i); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | auto WorkspaceManager::register_manager(wl_registry *registry, uint32_t name, uint32_t version) | ||||||
|  |     -> void { | ||||||
|  |   if (workspace_manager_) { | ||||||
|  |     spdlog::warn("Register workspace manager again although already registered!"); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   if (version != 1) { | ||||||
|  |     spdlog::warn("Using different workspace manager protocol version: {}", version); | ||||||
|  |   } | ||||||
|  |   workspace_manager_ = workspace_manager_bind(registry, name, version, this); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | auto WorkspaceManager::handle_workspace_group_create( | ||||||
|  |     zext_workspace_group_handle_v1 *workspace_group_handle) -> void { | ||||||
|  |   auto new_id = ++group_global_id; | ||||||
|  |   groups_.push_back( | ||||||
|  |       std::make_unique<WorkspaceGroup>(bar_, box_, config_, *this, workspace_group_handle, new_id)); | ||||||
|  |   spdlog::debug("Workspace group {} created", new_id); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | auto WorkspaceManager::handle_finished() -> void { | ||||||
|  |   zext_workspace_manager_v1_destroy(workspace_manager_); | ||||||
|  |   workspace_manager_ = nullptr; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | auto WorkspaceManager::handle_done() -> void { | ||||||
|  |   for (auto &group : groups_) { | ||||||
|  |     group->handle_done(); | ||||||
|  |   } | ||||||
|  |   dp.emit(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | auto WorkspaceManager::update() -> void { | ||||||
|  |   for (auto &group : groups_) { | ||||||
|  |     group->update(); | ||||||
|  |   } | ||||||
|  |   if (creation_delayed()) { | ||||||
|  |     creation_delayed_ = false; | ||||||
|  |     sort_workspaces(); | ||||||
|  |   } | ||||||
|  |   AModule::update(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | WorkspaceManager::~WorkspaceManager() { | ||||||
|  |   if (!workspace_manager_) { | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   zext_workspace_manager_v1_destroy(workspace_manager_); | ||||||
|  |   workspace_manager_ = nullptr; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | auto WorkspaceManager::remove_workspace_group(uint32_t id) -> void { | ||||||
|  |   auto it = std::find_if(groups_.begin(), | ||||||
|  |                          groups_.end(), | ||||||
|  |                          [id](const std::unique_ptr<WorkspaceGroup> &g) { return g->id() == id; }); | ||||||
|  | 
 | ||||||
|  |   if (it == groups_.end()) { | ||||||
|  |     spdlog::warn("Can't find group with id {}", id); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   groups_.erase(it); | ||||||
|  | } | ||||||
|  | auto WorkspaceManager::commit() -> void { zext_workspace_manager_v1_commit(workspace_manager_); } | ||||||
|  | 
 | ||||||
|  | WorkspaceGroup::WorkspaceGroup(const Bar &bar, Gtk::Box &box, const Json::Value &config, | ||||||
|  |                                WorkspaceManager               &manager, | ||||||
|  |                                zext_workspace_group_handle_v1 *workspace_group_handle, uint32_t id) | ||||||
|  |     : bar_(bar), | ||||||
|  |       box_(box), | ||||||
|  |       config_(config), | ||||||
|  |       workspace_manager_(manager), | ||||||
|  |       workspace_group_handle_(workspace_group_handle), | ||||||
|  |       id_(id) { | ||||||
|  |   add_workspace_group_listener(workspace_group_handle, this); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | auto WorkspaceGroup::active_only() const -> bool { return workspace_manager_.active_only(); } | ||||||
|  | auto WorkspaceGroup::creation_delayed() const -> bool { | ||||||
|  |   return workspace_manager_.creation_delayed(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | auto WorkspaceGroup::add_button(Gtk::Button &button) -> void { | ||||||
|  |   box_.pack_start(button, false, false); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | WorkspaceGroup::~WorkspaceGroup() { | ||||||
|  |   if (!workspace_group_handle_) { | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   zext_workspace_group_handle_v1_destroy(workspace_group_handle_); | ||||||
|  |   workspace_group_handle_ = nullptr; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | auto WorkspaceGroup::handle_workspace_create(zext_workspace_handle_v1 *workspace) -> void { | ||||||
|  |   auto new_id = ++workspace_global_id; | ||||||
|  |   workspaces_.push_back(std::make_unique<Workspace>(bar_, config_, *this, workspace, new_id)); | ||||||
|  |   spdlog::debug("Workspace {} created", new_id); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | auto WorkspaceGroup::handle_remove() -> void { | ||||||
|  |   zext_workspace_group_handle_v1_destroy(workspace_group_handle_); | ||||||
|  |   workspace_group_handle_ = nullptr; | ||||||
|  |   workspace_manager_.remove_workspace_group(id_); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | auto WorkspaceGroup::handle_output_enter(wl_output *output) -> void { | ||||||
|  |   spdlog::debug("Output {} assigned to {} group", (void *)output, id_); | ||||||
|  |   output_ = output; | ||||||
|  | 
 | ||||||
|  |   if (!is_visible() || workspace_manager_.creation_delayed()) { | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   for (auto &workspace : workspaces_) { | ||||||
|  |     add_button(workspace->get_button_ref()); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | auto WorkspaceGroup::is_visible() const -> bool { | ||||||
|  |   return output_ != nullptr && | ||||||
|  |          (workspace_manager_.all_outputs() || | ||||||
|  |           output_ == gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj())); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | auto WorkspaceGroup::handle_output_leave() -> void { | ||||||
|  |   spdlog::debug("Output {} remove from {} group", (void *)output_, id_); | ||||||
|  |   output_ = nullptr; | ||||||
|  | 
 | ||||||
|  |   if (output_ != gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj())) { | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   for (auto &workspace : workspaces_) { | ||||||
|  |     remove_button(workspace->get_button_ref()); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | auto WorkspaceGroup::update() -> void { | ||||||
|  |   for (auto &workspace : workspaces_) { | ||||||
|  |     if (workspace_manager_.creation_delayed()) { | ||||||
|  |       add_button(workspace->get_button_ref()); | ||||||
|  |       if (is_visible() && (workspace->is_active() || workspace->is_urgent())) { | ||||||
|  |         workspace->show(); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     workspace->update(); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | auto WorkspaceGroup::remove_workspace(uint32_t id) -> void { | ||||||
|  |   auto it = std::find_if(workspaces_.begin(), | ||||||
|  |                          workspaces_.end(), | ||||||
|  |                          [id](const std::unique_ptr<Workspace> &w) { return w->id() == id; }); | ||||||
|  | 
 | ||||||
|  |   if (it == workspaces_.end()) { | ||||||
|  |     spdlog::warn("Can't find workspace with id {}", id); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   workspaces_.erase(it); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | auto WorkspaceGroup::handle_done() -> void { | ||||||
|  |   need_to_sort = false; | ||||||
|  |   if (!is_visible()) { | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   for (auto &workspace : workspaces_) { | ||||||
|  |     workspace->handle_done(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   if (creation_delayed()) { | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   if (!workspace_manager_.all_outputs()) { | ||||||
|  |     sort_workspaces(); | ||||||
|  |   } else { | ||||||
|  |     workspace_manager_.sort_workspaces(); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | auto WorkspaceGroup::commit() -> void { workspace_manager_.commit(); } | ||||||
|  | 
 | ||||||
|  | auto WorkspaceGroup::sort_workspaces() -> void { | ||||||
|  |   std::sort(workspaces_.begin(), workspaces_.end(), workspace_manager_.workspace_comparator()); | ||||||
|  |   for (size_t i = 0; i < workspaces_.size(); ++i) { | ||||||
|  |     box_.reorder_child(workspaces_[i]->get_button_ref(), i); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | auto WorkspaceGroup::remove_button(Gtk::Button &button) -> void { box_.remove(button); } | ||||||
|  | 
 | ||||||
|  | Workspace::Workspace(const Bar &bar, const Json::Value &config, WorkspaceGroup &workspace_group, | ||||||
|  |                      zext_workspace_handle_v1 *workspace, uint32_t id) | ||||||
|  |     : bar_(bar), | ||||||
|  |       config_(config), | ||||||
|  |       workspace_group_(workspace_group), | ||||||
|  |       workspace_handle_(workspace), | ||||||
|  |       id_(id) { | ||||||
|  |   add_workspace_listener(workspace, this); | ||||||
|  | 
 | ||||||
|  |   auto config_format = config["format"]; | ||||||
|  | 
 | ||||||
|  |   format_ = config_format.isString() ? config_format.asString() : "{name}"; | ||||||
|  |   with_icon_ = format_.find("{icon}") != std::string::npos; | ||||||
|  | 
 | ||||||
|  |   if (with_icon_ && icons_map_.empty()) { | ||||||
|  |     auto format_icons = config["format-icons"]; | ||||||
|  |     for (auto &name : format_icons.getMemberNames()) { | ||||||
|  |       icons_map_.emplace(name, format_icons[name].asString()); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /* Handle click events if configured */ | ||||||
|  |   if (config_["on-click"].isString() || config_["on-click-middle"].isString() || | ||||||
|  |       config_["on-click-right"].isString()) { | ||||||
|  |     button_.add_events(Gdk::BUTTON_PRESS_MASK); | ||||||
|  |     button_.signal_button_press_event().connect(sigc::mem_fun(*this, &Workspace::handle_clicked), | ||||||
|  |                                                 false); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   button_.set_relief(Gtk::RELIEF_NONE); | ||||||
|  |   content_.set_center_widget(label_); | ||||||
|  |   button_.add(content_); | ||||||
|  | 
 | ||||||
|  |   if (!workspace_group.is_visible()) { | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   workspace_group.add_button(button_); | ||||||
|  |   button_.show_all(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | Workspace::~Workspace() { | ||||||
|  |   workspace_group_.remove_button(button_); | ||||||
|  |   if (!workspace_handle_) { | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   zext_workspace_handle_v1_destroy(workspace_handle_); | ||||||
|  |   workspace_handle_ = nullptr; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | auto Workspace::update() -> void { | ||||||
|  |   label_.set_markup(fmt::format( | ||||||
|  |       format_, fmt::arg("name", name_), fmt::arg("icon", with_icon_ ? get_icon() : ""))); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | auto Workspace::handle_state(const std::vector<uint32_t> &state) -> void { | ||||||
|  |   state_ = 0; | ||||||
|  |   for (auto state_entry : state) { | ||||||
|  |     switch (state_entry) { | ||||||
|  |       case ZEXT_WORKSPACE_HANDLE_V1_STATE_ACTIVE: | ||||||
|  |         state_ |= (uint32_t)State::ACTIVE; | ||||||
|  |         break; | ||||||
|  |       case ZEXT_WORKSPACE_HANDLE_V1_STATE_URGENT: | ||||||
|  |         state_ |= (uint32_t)State::URGENT; | ||||||
|  |         break; | ||||||
|  |       case ZEXT_WORKSPACE_HANDLE_V1_STATE_HIDDEN: | ||||||
|  |         state_ |= (uint32_t)State::HIDDEN; | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | auto Workspace::handle_remove() -> void { | ||||||
|  |   zext_workspace_handle_v1_destroy(workspace_handle_); | ||||||
|  |   workspace_handle_ = nullptr; | ||||||
|  |   workspace_group_.remove_workspace(id_); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | auto add_or_remove_class(Glib::RefPtr<Gtk::StyleContext> context, bool condition, | ||||||
|  |                          const std::string &class_name) { | ||||||
|  |   if (condition) { | ||||||
|  |     context->add_class(class_name); | ||||||
|  |   } else { | ||||||
|  |     context->remove_class(class_name); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | auto Workspace::handle_done() -> void { | ||||||
|  |   spdlog::debug("Workspace {} changed to state {}", id_, state_); | ||||||
|  |   auto style_context = button_.get_style_context(); | ||||||
|  |   add_or_remove_class(style_context, is_active(), "active"); | ||||||
|  |   add_or_remove_class(style_context, is_urgent(), "urgent"); | ||||||
|  |   add_or_remove_class(style_context, is_hidden(), "hidden"); | ||||||
|  | 
 | ||||||
|  |   if (workspace_group_.creation_delayed()) { | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   if (workspace_group_.active_only() && (is_active() || is_urgent())) { | ||||||
|  |     button_.show_all(); | ||||||
|  |   } else if (workspace_group_.active_only() && !(is_active() || is_urgent())) { | ||||||
|  |     button_.hide(); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | auto Workspace::get_icon() -> std::string { | ||||||
|  |   if (is_active()) { | ||||||
|  |     auto active_icon_it = icons_map_.find("active"); | ||||||
|  |     if (active_icon_it != icons_map_.end()) { | ||||||
|  |       return active_icon_it->second; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   auto named_icon_it = icons_map_.find(name_); | ||||||
|  |   if (named_icon_it != icons_map_.end()) { | ||||||
|  |     return named_icon_it->second; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   auto default_icon_it = icons_map_.find("default"); | ||||||
|  |   if (default_icon_it != icons_map_.end()) { | ||||||
|  |     return default_icon_it->second; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   return name_; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | auto Workspace::handle_clicked(GdkEventButton *bt) -> bool { | ||||||
|  |   std::string action; | ||||||
|  |   if (config_["on-click"].isString() && bt->button == 1) { | ||||||
|  |     action = config_["on-click"].asString(); | ||||||
|  |   } else if (config_["on-click-middle"].isString() && bt->button == 2) { | ||||||
|  |     action = config_["on-click-middle"].asString(); | ||||||
|  |   } else if (config_["on-click-right"].isString() && bt->button == 3) { | ||||||
|  |     action = config_["on-click-right"].asString(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   if (action.empty()) | ||||||
|  |     return true; | ||||||
|  |   else if (action == "activate") { | ||||||
|  |     zext_workspace_handle_v1_activate(workspace_handle_); | ||||||
|  |   } else if (action == "close") { | ||||||
|  |     zext_workspace_handle_v1_remove(workspace_handle_); | ||||||
|  |   } else { | ||||||
|  |     spdlog::warn("Unknown action {}", action); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   workspace_group_.commit(); | ||||||
|  | 
 | ||||||
|  |   return true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | auto Workspace::show() -> void { button_.show_all(); } | ||||||
|  | auto Workspace::hide() -> void { button_.hide(); } | ||||||
|  | 
 | ||||||
|  | auto Workspace::handle_name(const std::string &name) -> void { | ||||||
|  |   if (name_ != name) { | ||||||
|  |     workspace_group_.set_need_to_sort(); | ||||||
|  |   } | ||||||
|  |   name_ = name; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | auto Workspace::handle_coordinates(const std::vector<uint32_t> &coordinates) -> void { | ||||||
|  |   if (coordinates_ != coordinates) { | ||||||
|  |     workspace_group_.set_need_to_sort(); | ||||||
|  |   } | ||||||
|  |   coordinates_ = coordinates; | ||||||
|  | } | ||||||
|  | }  // namespace waybar::modules::wlr
 | ||||||
|  | @ -0,0 +1,135 @@ | ||||||
|  | #include "modules/wlr/workspace_manager_binding.hpp" | ||||||
|  | 
 | ||||||
|  | #include <spdlog/spdlog.h> | ||||||
|  | #include <cstdint> | ||||||
|  | 
 | ||||||
|  | #include "client.hpp" | ||||||
|  | #include "modules/wlr/workspace_manager.hpp" | ||||||
|  | 
 | ||||||
|  | namespace waybar::modules::wlr { | ||||||
|  | 
 | ||||||
|  | static void handle_global(void *data, wl_registry *registry, uint32_t name, const char *interface, | ||||||
|  |                           uint32_t version) { | ||||||
|  |   if (std::strcmp(interface, zext_workspace_manager_v1_interface.name) == 0) { | ||||||
|  |     static_cast<WorkspaceManager *>(data)->register_manager(registry, name, version); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void handle_global_remove(void *data, wl_registry *registry, uint32_t name) { | ||||||
|  |   /* Nothing to do here */ | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static const wl_registry_listener registry_listener_impl = {.global = handle_global, | ||||||
|  |                                                             .global_remove = handle_global_remove}; | ||||||
|  | 
 | ||||||
|  | void add_registry_listener(void *data) { | ||||||
|  |   wl_display * display = Client::inst()->wl_display; | ||||||
|  |   wl_registry *registry = wl_display_get_registry(display); | ||||||
|  | 
 | ||||||
|  |   wl_registry_add_listener(registry, ®istry_listener_impl, data); | ||||||
|  |   wl_display_roundtrip(display); | ||||||
|  |   wl_display_roundtrip(display); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void workspace_manager_handle_workspace_group( | ||||||
|  |     void *data, zext_workspace_manager_v1 *_, zext_workspace_group_handle_v1 *workspace_group) { | ||||||
|  |   static_cast<WorkspaceManager *>(data)->handle_workspace_group_create(workspace_group); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void workspace_manager_handle_done(void *data, zext_workspace_manager_v1 *_) { | ||||||
|  |   static_cast<WorkspaceManager *>(data)->handle_done(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void workspace_manager_handle_finished(void *data, zext_workspace_manager_v1 *_) { | ||||||
|  |   static_cast<WorkspaceManager *>(data)->handle_finished(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static const zext_workspace_manager_v1_listener workspace_manager_impl = { | ||||||
|  |     .workspace_group = workspace_manager_handle_workspace_group, | ||||||
|  |     .done = workspace_manager_handle_done, | ||||||
|  |     .finished = workspace_manager_handle_finished, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | zext_workspace_manager_v1 *workspace_manager_bind(wl_registry *registry, uint32_t name, | ||||||
|  |                                                   uint32_t version, void *data) { | ||||||
|  |   auto *workspace_manager = static_cast<zext_workspace_manager_v1 *>( | ||||||
|  |       wl_registry_bind(registry, name, &zext_workspace_manager_v1_interface, version)); | ||||||
|  | 
 | ||||||
|  |   if (workspace_manager) | ||||||
|  |     zext_workspace_manager_v1_add_listener(workspace_manager, &workspace_manager_impl, data); | ||||||
|  |   else | ||||||
|  |     spdlog::error("Failed to register manager"); | ||||||
|  | 
 | ||||||
|  |   return workspace_manager; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void workspace_group_handle_output_enter(void *data, zext_workspace_group_handle_v1 *_, | ||||||
|  |                                                 wl_output *output) { | ||||||
|  |   static_cast<WorkspaceGroup *>(data)->handle_output_enter(output); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void workspace_group_handle_output_leave(void *data, zext_workspace_group_handle_v1 *_, | ||||||
|  |                                                 wl_output *output) { | ||||||
|  |   static_cast<WorkspaceGroup *>(data)->handle_output_leave(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void workspace_group_handle_workspace(void *data, zext_workspace_group_handle_v1 *_, | ||||||
|  |                                              zext_workspace_handle_v1 *workspace) { | ||||||
|  |   static_cast<WorkspaceGroup *>(data)->handle_workspace_create(workspace); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void workspace_group_handle_remove(void *data, zext_workspace_group_handle_v1 *_) { | ||||||
|  |   static_cast<WorkspaceGroup *>(data)->handle_remove(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static const zext_workspace_group_handle_v1_listener workspace_group_impl = { | ||||||
|  |     .output_enter = workspace_group_handle_output_enter, | ||||||
|  |     .output_leave = workspace_group_handle_output_leave, | ||||||
|  |     .workspace = workspace_group_handle_workspace, | ||||||
|  |     .remove = workspace_group_handle_remove}; | ||||||
|  | 
 | ||||||
|  | void add_workspace_group_listener(zext_workspace_group_handle_v1 *workspace_group_handle, | ||||||
|  |                                   void *                          data) { | ||||||
|  |   zext_workspace_group_handle_v1_add_listener(workspace_group_handle, &workspace_group_impl, data); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void workspace_handle_name(void *data, struct zext_workspace_handle_v1 *_, const char *name) { | ||||||
|  |   static_cast<Workspace *>(data)->handle_name(name); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void workspace_handle_coordinates(void *data, struct zext_workspace_handle_v1 *_, | ||||||
|  |                                   struct wl_array *coordinates) { | ||||||
|  |   std::vector<uint32_t> coords_vec; | ||||||
|  |   auto                  coords = static_cast<uint32_t *>(coordinates->data); | ||||||
|  |   for (size_t i = 0; i < coordinates->size / sizeof(uint32_t); ++i) { | ||||||
|  |     coords_vec.push_back(coords[i]); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static_cast<Workspace *>(data)->handle_coordinates(coords_vec); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void workspace_handle_state(void *data, struct zext_workspace_handle_v1 *workspace_handle, | ||||||
|  |                             struct wl_array *state) { | ||||||
|  |   std::vector<uint32_t> state_vec; | ||||||
|  |   auto                  states = static_cast<uint32_t *>(state->data); | ||||||
|  |   for (size_t i = 0; i < state->size / sizeof(uint32_t); ++i) { | ||||||
|  |     state_vec.push_back(states[i]); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static_cast<Workspace *>(data)->handle_state(state_vec); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void workspace_handle_remove(void *data, struct zext_workspace_handle_v1 *_) { | ||||||
|  |   static_cast<Workspace *>(data)->handle_remove(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static const zext_workspace_handle_v1_listener workspace_impl = { | ||||||
|  |     .name = workspace_handle_name, | ||||||
|  |     .coordinates = workspace_handle_coordinates, | ||||||
|  |     .state = workspace_handle_state, | ||||||
|  |     .remove = workspace_handle_remove}; | ||||||
|  | 
 | ||||||
|  | void add_workspace_listener(zext_workspace_handle_v1 *workspace_handle, void *data) { | ||||||
|  |   zext_workspace_handle_v1_add_listener(workspace_handle, &workspace_impl, data); | ||||||
|  | } | ||||||
|  | }  // namespace waybar::modules::wlr
 | ||||||
|  | @ -0,0 +1,24 @@ | ||||||
|  | #pragma once | ||||||
|  | #include <glibmm/main.h> | ||||||
|  | /**
 | ||||||
|  |  * Minimal Glib application to be used for tests that require Glib main loop | ||||||
|  |  */ | ||||||
|  | class GlibTestsFixture : public sigc::trackable { | ||||||
|  |  public: | ||||||
|  |   GlibTestsFixture() : main_loop_{Glib::MainLoop::create()} {} | ||||||
|  | 
 | ||||||
|  |   void setTimeout(int timeout) { | ||||||
|  |     Glib::signal_timeout().connect_once([]() { throw std::runtime_error("Test timed out"); }, | ||||||
|  |                                         timeout); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   void run(std::function<void()> fn) { | ||||||
|  |     Glib::signal_idle().connect_once(fn); | ||||||
|  |     main_loop_->run(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   void quit() { main_loop_->quit(); } | ||||||
|  | 
 | ||||||
|  |  protected: | ||||||
|  |   Glib::RefPtr<Glib::MainLoop> main_loop_; | ||||||
|  | }; | ||||||
|  | @ -0,0 +1,145 @@ | ||||||
|  | #define CATCH_CONFIG_RUNNER | ||||||
|  | #include "util/SafeSignal.hpp" | ||||||
|  | 
 | ||||||
|  | #include <glibmm.h> | ||||||
|  | 
 | ||||||
|  | #include <catch2/catch.hpp> | ||||||
|  | #include <thread> | ||||||
|  | #include <type_traits> | ||||||
|  | 
 | ||||||
|  | #include "GlibTestsFixture.hpp" | ||||||
|  | 
 | ||||||
|  | using namespace waybar; | ||||||
|  | 
 | ||||||
|  | template <typename T> | ||||||
|  | using remove_cvref_t = typename std::remove_cv<typename std::remove_reference<T>::type>::type; | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * Basic sanity test for SafeSignal: | ||||||
|  |  * check that type deduction works, events are delivered and the order is right | ||||||
|  |  * Running this with -fsanitize=thread should not fail | ||||||
|  |  */ | ||||||
|  | TEST_CASE_METHOD(GlibTestsFixture, "SafeSignal basic functionality", "[signal][thread][util]") { | ||||||
|  |   const int NUM_EVENTS = 100; | ||||||
|  |   int       count = 0; | ||||||
|  |   int       last_value = 0; | ||||||
|  | 
 | ||||||
|  |   SafeSignal<int, std::string> test_signal; | ||||||
|  | 
 | ||||||
|  |   const auto  main_tid = std::this_thread::get_id(); | ||||||
|  |   std::thread producer; | ||||||
|  | 
 | ||||||
|  |   // timeout the test in 500ms
 | ||||||
|  |   setTimeout(500); | ||||||
|  | 
 | ||||||
|  |   test_signal.connect([&](auto val, auto str) { | ||||||
|  |     static_assert(std::is_same<int, decltype(val)>::value); | ||||||
|  |     static_assert(std::is_same<std::string, decltype(str)>::value); | ||||||
|  |     // check that we're in the same thread as the main loop
 | ||||||
|  |     REQUIRE(std::this_thread::get_id() == main_tid); | ||||||
|  |     // check event order
 | ||||||
|  |     REQUIRE(val == last_value + 1); | ||||||
|  | 
 | ||||||
|  |     last_value = val; | ||||||
|  |     if (++count >= NUM_EVENTS) { | ||||||
|  |       this->quit(); | ||||||
|  |     }; | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   run([&]() { | ||||||
|  |     // check that events from the same thread are delivered and processed synchronously
 | ||||||
|  |     test_signal.emit(1, "test"); | ||||||
|  |     REQUIRE(count == 1); | ||||||
|  | 
 | ||||||
|  |     // start another thread and generate events
 | ||||||
|  |     producer = std::thread([&]() { | ||||||
|  |       for (auto i = 2; i <= NUM_EVENTS; ++i) { | ||||||
|  |         test_signal.emit(i, "test"); | ||||||
|  |       } | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
|  |   producer.join(); | ||||||
|  |   REQUIRE(count == NUM_EVENTS); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | template <typename T> | ||||||
|  | struct TestObject { | ||||||
|  |   T        value; | ||||||
|  |   unsigned copied = 0; | ||||||
|  |   unsigned moved = 0; | ||||||
|  | 
 | ||||||
|  |   TestObject(const T& v) : value(v){}; | ||||||
|  |   ~TestObject() = default; | ||||||
|  | 
 | ||||||
|  |   TestObject(const TestObject& other) | ||||||
|  |       : value(other.value), copied(other.copied + 1), moved(other.moved) {} | ||||||
|  | 
 | ||||||
|  |   TestObject(TestObject&& other) noexcept | ||||||
|  |       : value(std::move(other.value)), | ||||||
|  |         copied(std::exchange(other.copied, 0)), | ||||||
|  |         moved(std::exchange(other.moved, 0) + 1) {} | ||||||
|  | 
 | ||||||
|  |   TestObject& operator=(const TestObject& other) { | ||||||
|  |     value = other.value; | ||||||
|  |     copied = other.copied + 1; | ||||||
|  |     moved = other.moved; | ||||||
|  |     return *this; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   TestObject& operator=(TestObject&& other) noexcept { | ||||||
|  |     value = std::move(other.value); | ||||||
|  |     copied = std::exchange(other.copied, 0); | ||||||
|  |     moved = std::exchange(other.moved, 0) + 1; | ||||||
|  |     return *this; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   bool operator==(T other) const { return value == other; } | ||||||
|  |        operator T() const { return value; } | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /*
 | ||||||
|  |  * Check the number of copies/moves performed on the object passed through SafeSignal | ||||||
|  |  */ | ||||||
|  | TEST_CASE_METHOD(GlibTestsFixture, "SafeSignal copy/move counter", "[signal][thread][util]") { | ||||||
|  |   const int NUM_EVENTS = 3; | ||||||
|  |   int       count = 0; | ||||||
|  | 
 | ||||||
|  |   SafeSignal<TestObject<int>> test_signal; | ||||||
|  | 
 | ||||||
|  |   std::thread producer; | ||||||
|  | 
 | ||||||
|  |   // timeout the test in 500ms
 | ||||||
|  |   setTimeout(500); | ||||||
|  | 
 | ||||||
|  |   test_signal.connect([&](auto& val) { | ||||||
|  |     static_assert(std::is_same<TestObject<int>, remove_cvref_t<decltype(val)>>::value); | ||||||
|  | 
 | ||||||
|  |     /* explicit move in the producer thread */ | ||||||
|  |     REQUIRE(val.moved <= 1); | ||||||
|  |     /* copy within the SafeSignal queuing code */ | ||||||
|  |     REQUIRE(val.copied <= 1); | ||||||
|  | 
 | ||||||
|  |     if (++count >= NUM_EVENTS) { | ||||||
|  |       this->quit(); | ||||||
|  |     }; | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   run([&]() { | ||||||
|  |     test_signal.emit(1); | ||||||
|  |     REQUIRE(count == 1); | ||||||
|  |     producer = std::thread([&]() { | ||||||
|  |       for (auto i = 2; i <= NUM_EVENTS; ++i) { | ||||||
|  |         TestObject<int> t{i}; | ||||||
|  |         // check that signal.emit accepts moved objects
 | ||||||
|  |         test_signal.emit(std::move(t)); | ||||||
|  |       } | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
|  |   producer.join(); | ||||||
|  |   REQUIRE(count == NUM_EVENTS); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | int main(int argc, char* argv[]) { | ||||||
|  |   Glib::init(); | ||||||
|  |   return Catch::Session().run(argc, argv); | ||||||
|  | } | ||||||
|  | @ -2,6 +2,7 @@ test_inc = include_directories('../include') | ||||||
| test_dep = [ | test_dep = [ | ||||||
|     catch2, |     catch2, | ||||||
|     fmt, |     fmt, | ||||||
|  |     gtkmm, | ||||||
|     jsoncpp, |     jsoncpp, | ||||||
|     spdlog, |     spdlog, | ||||||
| ] | ] | ||||||
|  | @ -14,8 +15,21 @@ config_test = executable( | ||||||
|     include_directories: test_inc, |     include_directories: test_inc, | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | safesignal_test = executable( | ||||||
|  |     'safesignal_test', | ||||||
|  |     'SafeSignal.cpp', | ||||||
|  |     dependencies: test_dep, | ||||||
|  |     include_directories: test_inc, | ||||||
|  | ) | ||||||
|  | 
 | ||||||
| test( | test( | ||||||
|     'Configuration test', |     'Configuration test', | ||||||
|     config_test, |     config_test, | ||||||
|     workdir: meson.source_root(), |     workdir: meson.source_root(), | ||||||
| ) | ) | ||||||
|  | 
 | ||||||
|  | test( | ||||||
|  |     'SafeSignal test', | ||||||
|  |     safesignal_test, | ||||||
|  |     workdir: meson.source_root(), | ||||||
|  | ) | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue