mirror of
				https://github.com/rad4day/Waybar.git
				synced 2025-10-31 07:52:42 +01:00 
			
		
		
		
	Merge branch 'master' into master
This commit is contained in:
		
							
								
								
									
										8
									
								
								.github/workflows/freebsd.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								.github/workflows/freebsd.yml
									
									
									
									
										vendored
									
									
								
							| @@ -4,12 +4,16 @@ on: [ push, pull_request ] | ||||
|  | ||||
| jobs: | ||||
|   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: | ||||
|     - uses: actions/checkout@v2 | ||||
|     - 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: | ||||
|         mem: 2048 | ||||
|         usesh: true | ||||
|         prepare: | | ||||
|           export CPPFLAGS=-isystem/usr/local/include LDFLAGS=-L/usr/local/lib # sndio | ||||
|   | ||||
| @@ -8,6 +8,9 @@ | ||||
| #include <gtkmm/window.h> | ||||
| #include <json/json.h> | ||||
|  | ||||
| #include <memory> | ||||
| #include <vector> | ||||
|  | ||||
| #include "AModule.hpp" | ||||
| #include "xdg-output-unstable-v1-client-protocol.h" | ||||
|  | ||||
| @@ -36,6 +39,19 @@ struct bar_margins { | ||||
|   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 { | ||||
|  protected: | ||||
|   BarSurface() = default; | ||||
| @@ -54,38 +70,56 @@ class BarSurface { | ||||
|  | ||||
| class Bar { | ||||
|  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(const Bar &) = delete; | ||||
|   ~Bar() = default; | ||||
|   ~Bar(); | ||||
|  | ||||
|   void setMode(const std::string_view &); | ||||
|   void setVisible(bool visible); | ||||
|   void toggle(); | ||||
|   void handleSignal(int); | ||||
|  | ||||
|   struct waybar_output *output; | ||||
|   Json::Value           config; | ||||
|   struct wl_surface *   surface; | ||||
|   bool                  exclusive = true; | ||||
|   struct wl_surface    *surface; | ||||
|   bool                  visible = true; | ||||
|   bool                  vertical = false; | ||||
|   Gtk::Window           window; | ||||
|  | ||||
| #ifdef HAVE_SWAY | ||||
|   std::string bar_id; | ||||
| #endif | ||||
|  | ||||
|  private: | ||||
|   void onMap(GdkEventAny *); | ||||
|   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 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_; | ||||
|   bar_layer                                     layer_; | ||||
|   Gtk::Box                                      left_; | ||||
|   Gtk::Box                                      center_; | ||||
|   Gtk::Box                                      right_; | ||||
|   Gtk::Box                                      box_; | ||||
|   std::vector<std::unique_ptr<waybar::AModule>> modules_left_; | ||||
|   std::vector<std::unique_ptr<waybar::AModule>> modules_center_; | ||||
|   std::vector<std::unique_ptr<waybar::AModule>> modules_right_; | ||||
|   std::vector<std::shared_ptr<waybar::AModule>> modules_left_; | ||||
|   std::vector<std::shared_ptr<waybar::AModule>> modules_center_; | ||||
|   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 | ||||
|   | ||||
| @@ -29,6 +29,7 @@ class Client { | ||||
|   struct zwp_idle_inhibit_manager_v1 *idle_inhibit_manager = nullptr; | ||||
|   std::vector<std::unique_ptr<Bar>>   bars; | ||||
|   Config                              config; | ||||
|   std::string                         bar_id; | ||||
|  | ||||
|  private: | ||||
|   Client() = default; | ||||
|   | ||||
| @@ -14,6 +14,7 @@ | ||||
| #endif | ||||
| #ifdef HAVE_WLR | ||||
| #include "modules/wlr/taskbar.hpp" | ||||
| #include "modules/wlr/workspace_manager.hpp" | ||||
| #endif | ||||
| #ifdef HAVE_RIVER | ||||
| #include "modules/river/tags.hpp" | ||||
|   | ||||
							
								
								
									
										21
									
								
								include/group.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								include/group.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -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; | ||||
| }; | ||||
|  | ||||
| const std::string kCalendarPlaceholder = "calendar"; | ||||
|  | ||||
| class Clock : public ALabel { | ||||
|  public: | ||||
|   Clock(const std::string&, const Json::Value&); | ||||
| @@ -26,18 +28,19 @@ class Clock : public ALabel { | ||||
|  private: | ||||
|   util::SleeperThread thread_; | ||||
|   std::locale locale_; | ||||
|   const date::time_zone* time_zone_; | ||||
|   bool fixed_time_zone_; | ||||
|   int time_zone_idx_; | ||||
|   std::vector<const date::time_zone*> time_zones_; | ||||
|   int current_time_zone_idx_; | ||||
|   date::year_month_day cached_calendar_ymd_ = date::January/1/0; | ||||
|   std::string cached_calendar_text_; | ||||
|   bool is_calendar_in_tooltip_; | ||||
|  | ||||
|   bool handleScroll(GdkEventScroll* e); | ||||
|  | ||||
|   auto calendar_text(const waybar_time& wtime) -> std::string; | ||||
|   auto weekdays_header(const date::weekday& first_dow, std::ostream& os) -> void; | ||||
|   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 | ||||
|   | ||||
| @@ -43,6 +43,7 @@ class Network : public ALabel { | ||||
|   const std::string getNetworkState() const; | ||||
|   void              clearIface(); | ||||
|   bool              wildcardMatch(const std::string& pattern, const std::string& text) const; | ||||
|   std::optional<std::pair<unsigned long long, unsigned long long>> readBandwidthUsage(); | ||||
|  | ||||
|   int                ifid_; | ||||
|   sa_family_t        family_; | ||||
|   | ||||
| @@ -6,6 +6,7 @@ | ||||
| #include "AModule.hpp" | ||||
| #include "bar.hpp" | ||||
| #include "river-status-unstable-v1-client-protocol.h" | ||||
| #include "river-control-unstable-v1-client-protocol.h" | ||||
| #include "xdg-output-unstable-v1-client-protocol.h" | ||||
|  | ||||
| namespace waybar::modules::river { | ||||
| @@ -20,7 +21,12 @@ class Tags : public waybar::AModule { | ||||
|   void handle_view_tags(struct wl_array *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_control_v1 *control_; | ||||
|   struct wl_seat *seat_; | ||||
|  | ||||
|  private: | ||||
|   const waybar::Bar &      bar_; | ||||
|   | ||||
							
								
								
									
										49
									
								
								include/modules/sway/bar.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								include/modules/sway/bar.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -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 variant; | ||||
|     std::string short_description; | ||||
|     std::string country_flag() const; | ||||
|   }; | ||||
|  | ||||
|   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_ACTIVE_LAYOUT_NAME_KEY; | ||||
|    | ||||
|  | ||||
|   Layout                        layout_; | ||||
|   std::string tooltip_format_ = ""; | ||||
|   std::map<std::string, Layout> layouts_map_; | ||||
|   | ||||
| @@ -61,8 +61,8 @@ class Task | ||||
|     Gtk::Image icon_; | ||||
|     Gtk::Label text_before_; | ||||
|     Gtk::Label text_after_; | ||||
|     bool button_visible_; | ||||
|     bool ignored_; | ||||
|     bool button_visible_ = false; | ||||
|     bool ignored_ = false; | ||||
|  | ||||
|     bool with_icon_; | ||||
|     std::string format_before_; | ||||
| @@ -77,6 +77,7 @@ class Task | ||||
|    private: | ||||
|     std::string repr() const; | ||||
|     std::string state_string(bool = false) const; | ||||
|     void hide_if_ignored(); | ||||
|  | ||||
|    public: | ||||
|     /* Getter functions */ | ||||
|   | ||||
							
								
								
									
										160
									
								
								include/modules/wlr/workspace_manager.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										160
									
								
								include/modules/wlr/workspace_manager.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
							
								
								
									
										8
									
								
								include/modules/wlr/workspace_manager_binding.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								include/modules/wlr/workspace_manager_binding.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -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); | ||||
| } | ||||
							
								
								
									
										75
									
								
								include/util/SafeSignal.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								include/util/SafeSignal.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
|  | ||||
| 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. | ||||
|  | ||||
|   | ||||
| @@ -21,6 +21,11 @@ Addressed by *river/tags* | ||||
|     typeof: array ++ | ||||
|     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 | ||||
|  | ||||
| ``` | ||||
|   | ||||
| @@ -37,6 +37,8 @@ Addressed by *sway/language* | ||||
|  | ||||
| *{variant}*: Variant of layout (e.g. "dvorak"). | ||||
|  | ||||
| *{flag}*: Country flag of layout. | ||||
|  | ||||
| # EXAMPLES | ||||
|  | ||||
| ``` | ||||
|   | ||||
| @@ -70,7 +70,7 @@ Addressed by *wlr/taskbar* | ||||
|  | ||||
| *ignore-list*: ++ | ||||
| 	typeof: array ++ | ||||
| 	List of app_id to be invisible. | ||||
| 	List of app_id/titles to be invisible. | ||||
|  | ||||
| # FORMAT REPLACEMENTS | ||||
|  | ||||
|   | ||||
							
								
								
									
										87
									
								
								man/waybar-wlr-workspaces.5.scd
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								man/waybar-wlr-workspaces.5.scd
									
									
									
									
									
										Normal file
									
								
							| @@ -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* | ||||
| @@ -64,18 +64,27 @@ Also a minimal example configuration can be found on the at the bottom of this m | ||||
| 	typeof: integer ++ | ||||
| 	Margins value without units. | ||||
|  | ||||
| *spacing* ++ | ||||
| 	typeof: integer ++ | ||||
| 	Size of gaps in between of the different modules. | ||||
|  | ||||
| *name* ++ | ||||
| 	typeof: string ++ | ||||
| 	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* ++ | ||||
| 	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. | ||||
|  | ||||
| *passthrough* ++ | ||||
| 	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. | ||||
| 	Intended to be used with either *top* or *overlay* layers and without exclusive zone. | ||||
|  | ||||
| @@ -85,6 +94,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. | ||||
| 	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* ++ | ||||
| 	typeof: string|array ++ | ||||
| 	Paths to additional configuration files. | ||||
| @@ -199,6 +218,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. | ||||
|  | ||||
| ## 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 | ||||
|  | ||||
| - *waybar-backlight(5)* | ||||
| @@ -220,5 +261,6 @@ Valid options for the "rotate" property are: 0, 90, 180 and 270. | ||||
| - *waybar-sway-window(5)* | ||||
| - *waybar-sway-workspaces(5)* | ||||
| - *waybar-wlr-taskbar(5)* | ||||
| - *waybar-wlr-workspaces(5)* | ||||
| - *waybar-temperature(5)* | ||||
| - *waybar-tray(5)* | ||||
|   | ||||
							
								
								
									
										10
									
								
								meson.build
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								meson.build
									
									
									
									
									
								
							| @@ -150,6 +150,7 @@ src_files = files( | ||||
|     'src/bar.cpp', | ||||
|     'src/client.cpp', | ||||
|     'src/config.cpp', | ||||
|     'src/group.cpp', | ||||
|     'src/util/ustring_clen.cpp' | ||||
| ) | ||||
|  | ||||
| @@ -177,6 +178,7 @@ endif | ||||
| add_project_arguments('-DHAVE_SWAY', language: 'cpp') | ||||
| src_files += [ | ||||
|     'src/modules/sway/ipc/client.cpp', | ||||
|     'src/modules/sway/bar.cpp', | ||||
|     'src/modules/sway/mode.cpp', | ||||
|     'src/modules/sway/language.cpp', | ||||
|     'src/modules/sway/window.cpp', | ||||
| @@ -186,6 +188,8 @@ src_files += [ | ||||
| if true | ||||
|     add_project_arguments('-DHAVE_WLR', language: '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 | ||||
|  | ||||
| if true | ||||
| @@ -255,6 +259,10 @@ else | ||||
|     src_files += 'src/modules/simpleclock.cpp' | ||||
| endif | ||||
|  | ||||
| if get_option('experimental') | ||||
|     add_project_arguments('-DUSE_EXPERIMENTAL', language: 'cpp') | ||||
| endif | ||||
|  | ||||
| subdir('protocol') | ||||
|  | ||||
| executable( | ||||
| @@ -334,6 +342,7 @@ if scdoc.found() | ||||
|         'waybar-tray.5.scd', | ||||
|         'waybar-states.5.scd', | ||||
|         'waybar-wlr-taskbar.5.scd', | ||||
|         'waybar-wlr-workspaces.5.scd', | ||||
|         'waybar-bluetooth.5.scd', | ||||
|         'waybar-sndio.5.scd', | ||||
|     ] | ||||
| @@ -380,3 +389,4 @@ if clangtidy.found() | ||||
|             '-p', meson.build_root() | ||||
|         ] + src_files) | ||||
| endif | ||||
|  | ||||
|   | ||||
| @@ -11,3 +11,4 @@ option('gtk-layer-shell', type: 'feature', value: 'auto', description: 'Use gtk- | ||||
| option('rfkill', type: 'feature', value: 'auto', description: 'Enable support for RFKILL') | ||||
| option('sndio', type: 'feature', value: 'auto', description: 'Enable support for sndio') | ||||
| option('tests', type: 'feature', value: 'auto', description: 'Enable tests') | ||||
| option('experimental', type : 'boolean', value : false, description: 'Enable experimental features') | ||||
|   | ||||
							
								
								
									
										306
									
								
								protocol/ext-workspace-unstable-v1.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										306
									
								
								protocol/ext-workspace-unstable-v1.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -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'], | ||||
| 	['wlr-layer-shell-unstable-v1.xml'], | ||||
| 	['wlr-foreign-toplevel-management-unstable-v1.xml'], | ||||
| 	['ext-workspace-unstable-v1.xml'], | ||||
| 	['river-status-unstable-v1.xml'], | ||||
| 	['river-control-unstable-v1.xml'], | ||||
| ] | ||||
|  | ||||
| client_protos_src = [] | ||||
|   | ||||
							
								
								
									
										85
									
								
								protocol/river-control-unstable-v1.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								protocol/river-control-unstable-v1.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -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> | ||||
| @@ -3,6 +3,7 @@ | ||||
|     // "position": "bottom", // Waybar position (top|bottom|left|right) | ||||
|     "height": 30, // Waybar height (to be removed for auto height) | ||||
|     // "width": 1280, // Waybar width | ||||
|     "spacing": 4, // Gaps between modules (4px) | ||||
|     // Choose the order of the modules | ||||
|     "modules-left": ["sway/workspaces", "sway/mode", "custom/media"], | ||||
|     "modules-center": ["sway/window"], | ||||
|   | ||||
| @@ -110,6 +110,7 @@ def main(): | ||||
|  | ||||
|     signal.signal(signal.SIGINT, signal_handler) | ||||
|     signal.signal(signal.SIGTERM, signal_handler) | ||||
|     signal.signal(signal.SIGPIPE, signal.SIG_DFL) | ||||
|  | ||||
|     for player in manager.props.player_names: | ||||
|         if arguments.player is not None and arguments.player != player.name: | ||||
|   | ||||
| @@ -80,7 +80,6 @@ window#waybar.chromium { | ||||
| #idle_inhibitor, | ||||
| #mpd { | ||||
|     padding: 0 10px; | ||||
|     margin: 0 4px; | ||||
|     color: #ffffff; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -67,8 +67,10 @@ std::string ALabel::getIcon(uint16_t percentage, const std::string& alt, uint16_ | ||||
|   } | ||||
|   if (format_icons.isArray()) { | ||||
|     auto size = format_icons.size(); | ||||
|     auto idx = std::clamp(percentage / ((max == 0 ? 100 : max) / size), 0U, size - 1); | ||||
|     format_icons = format_icons[idx]; | ||||
|     if (size) { | ||||
|       auto idx = std::clamp(percentage / ((max == 0 ? 100 : max) / size), 0U, size - 1); | ||||
|       format_icons = format_icons[idx]; | ||||
|     } | ||||
|   } | ||||
|   if (format_icons.isString()) { | ||||
|     return format_icons.asString(); | ||||
| @@ -90,8 +92,10 @@ std::string ALabel::getIcon(uint16_t percentage, const std::vector<std::string>& | ||||
|   } | ||||
|   if (format_icons.isArray()) { | ||||
|     auto size = format_icons.size(); | ||||
|     auto idx = std::clamp(percentage / ((max == 0 ? 100 : max) / size), 0U, size - 1); | ||||
|     format_icons = format_icons[idx]; | ||||
|     if (size) { | ||||
|       auto idx = std::clamp(percentage / ((max == 0 ? 100 : max) / size), 0U, size - 1); | ||||
|       format_icons = format_icons[idx]; | ||||
|     } | ||||
|   } | ||||
|   if (format_icons.isString()) { | ||||
|     return format_icons.asString(); | ||||
|   | ||||
							
								
								
									
										253
									
								
								src/bar.cpp
									
									
									
									
									
								
							
							
						
						
									
										253
									
								
								src/bar.cpp
									
									
									
									
									
								
							| @@ -9,20 +9,103 @@ | ||||
| #include "bar.hpp" | ||||
| #include "client.hpp" | ||||
| #include "factory.hpp" | ||||
| #include "group.hpp" | ||||
| #include "wlr-layer-shell-unstable-v1-client-protocol.h" | ||||
|  | ||||
| #ifdef HAVE_SWAY | ||||
| #include "modules/sway/bar.hpp" | ||||
| #endif | ||||
|  | ||||
| namespace waybar { | ||||
| static constexpr const char* MIN_HEIGHT_MSG = | ||||
|     "Requested height: {} exceeds the minimum height: {} required by the modules"; | ||||
|     "Requested height: {} is less than the minimum height: {} required by the modules"; | ||||
|  | ||||
| static constexpr const char* MIN_WIDTH_MSG = | ||||
|     "Requested width: {} exceeds the minimum width: {} required by the modules"; | ||||
|     "Requested width: {} is less than the minimum width: {} required by the modules"; | ||||
|  | ||||
| static constexpr const char* BAR_SIZE_MSG = "Bar configured (width: {}, height: {}) for output: {}"; | ||||
|  | ||||
| static constexpr const char* SIZE_DEFINED = | ||||
|     "{} 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 | ||||
| struct GLSSurfaceImpl : public BarSurface, public sigc::trackable { | ||||
|   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), | ||||
|       config(w_config), | ||||
|       window{Gtk::WindowType::WINDOW_TOPLEVEL}, | ||||
|       layer_{bar_layer::BOTTOM}, | ||||
|       left_(Gtk::ORIENTATION_HORIZONTAL, 0), | ||||
|       center_(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["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(); | ||||
|  | ||||
|   if (position == "right" || position == "left") { | ||||
| @@ -438,6 +499,13 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config) | ||||
|   center_.get_style_context()->add_class("modules-center"); | ||||
|   right_.get_style_context()->add_class("modules-right"); | ||||
|  | ||||
|   if (config["spacing"].isInt()) { | ||||
|     int spacing = config["spacing"].asInt(); | ||||
|     left_.set_spacing(spacing); | ||||
|     center_.set_spacing(spacing); | ||||
|     right_.set_spacing(spacing); | ||||
|   } | ||||
|  | ||||
|   uint32_t height = config["height"].isUInt() ? config["height"].asUInt() : 0; | ||||
|   uint32_t width = config["width"].isUInt() ? config["width"].asUInt() : 0; | ||||
|  | ||||
| @@ -498,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_->setLayer(layer_); | ||||
|   surface_impl_->setExclusiveZone(exclusive); | ||||
|   surface_impl_->setMargins(margins_); | ||||
|   surface_impl_->setPassThrough(passthrough); | ||||
|   surface_impl_->setPosition(position); | ||||
|   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)); | ||||
|  | ||||
| #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(); | ||||
|   window.show_all(); | ||||
|  | ||||
| @@ -521,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*) { | ||||
|   /* | ||||
|    * Obtain a pointer to the custom layer surface for modules that require it (idle_inhibitor). | ||||
| @@ -531,17 +665,7 @@ void waybar::Bar::onMap(GdkEventAny*) { | ||||
|  | ||||
| void waybar::Bar::setVisible(bool value) { | ||||
|   visible = value; | ||||
|   if (!visible) { | ||||
|     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(); | ||||
|   setMode(visible ? MODE_DEFAULT : MODE_INVISIBLE); | ||||
| } | ||||
|  | ||||
| void waybar::Bar::toggle() { setVisible(!visible); } | ||||
| @@ -587,19 +711,7 @@ void waybar::Bar::setupAltFormatKeyForModuleList(const char* module_list_name) { | ||||
| } | ||||
|  | ||||
| void waybar::Bar::handleSignal(int signal) { | ||||
|   for (auto& module : modules_left_) { | ||||
|     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_) { | ||||
|   for (auto& module : modules_all_) { | ||||
|     auto* custom = dynamic_cast<waybar::modules::Custom*>(module.get()); | ||||
|     if (custom != nullptr) { | ||||
|       custom->refresh(signal); | ||||
| @@ -607,19 +719,36 @@ void waybar::Bar::handleSignal(int signal) { | ||||
|   } | ||||
| } | ||||
|  | ||||
| void waybar::Bar::getModules(const Factory& factory, const std::string& pos) { | ||||
|   if (config[pos].isArray()) { | ||||
|     for (const auto& name : config[pos]) { | ||||
| void waybar::Bar::getModules(const Factory& factory, const std::string& pos, Gtk::Box* group = nullptr) { | ||||
|   auto module_list = group ? config[pos]["modules"] : config[pos]; | ||||
|   if (module_list.isArray()) { | ||||
|     for (const auto& name : module_list) { | ||||
|       try { | ||||
|         auto module = factory.makeModule(name.asString()); | ||||
|         if (pos == "modules-left") { | ||||
|           modules_left_.emplace_back(module); | ||||
|         auto ref = name.asString(); | ||||
|         AModule* 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); | ||||
|         } | ||||
|         if (pos == "modules-right") { | ||||
|           modules_right_.emplace_back(module); | ||||
|  | ||||
|         std::shared_ptr<AModule> module_sp(module); | ||||
|         modules_all_.emplace_back(module_sp); | ||||
|         if (group) { | ||||
|           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] { | ||||
|           try { | ||||
|   | ||||
| @@ -199,7 +199,6 @@ int waybar::Client::main(int argc, char *argv[]) { | ||||
|   bool        show_version = false; | ||||
|   std::string config_opt; | ||||
|   std::string style_opt; | ||||
|   std::string bar_id; | ||||
|   std::string log_level; | ||||
|   auto        cli = clara::detail::Help(show_help) | | ||||
|              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") { | ||||
|       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 | ||||
| #ifdef HAVE_RIVER | ||||
|     if (ref == "river/tags") { | ||||
|   | ||||
							
								
								
									
										19
									
								
								src/group.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								src/group.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -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; | ||||
|  | ||||
| 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()) { | ||||
|     time_zone_idx_ = 0; | ||||
|     setTimeZone(config_["timezones"][time_zone_idx_]); | ||||
|   } else { | ||||
|     setTimeZone(config_["timezone"]); | ||||
|     for (const auto& zone_name: config_["timezones"]) { | ||||
|       if (!zone_name.isString() || zone_name.asString().empty()) { | ||||
|         time_zones_.push_back(nullptr); | ||||
|         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."); | ||||
|   } | ||||
|  | ||||
|   // 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()) { | ||||
|     locale_ = std::locale(config_["locale"].asString()); | ||||
|   } 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 { | ||||
|   if (!fixed_time_zone_) { | ||||
|     // Time zone can change. Be sure to pick that. | ||||
|     time_zone_ = date::current_zone(); | ||||
|   } | ||||
|  | ||||
|   auto        now = std::chrono::system_clock::now(); | ||||
|   auto time_zone = current_timezone(); | ||||
|   auto now = std::chrono::system_clock::now(); | ||||
|   waybar_time wtime = {locale_, | ||||
|                        date::make_zoned(time_zone_, date::floor<std::chrono::seconds>(now))}; | ||||
|  | ||||
|   std::string text; | ||||
|   if (!fixed_time_zone_) { | ||||
|                        date::make_zoned(time_zone, date::floor<std::chrono::seconds>(now))}; | ||||
|   std::string text = ""; | ||||
|   if (!is_timezone_fixed()) { | ||||
|     // As date dep is not fully compatible, prefer fmt | ||||
|     tzset(); | ||||
|     auto localtime = fmt::localtime(std::chrono::system_clock::to_time_t(now)); | ||||
|     text = fmt::format(format_, localtime); | ||||
|     label_.set_markup(text); | ||||
|   } else { | ||||
|     text = fmt::format(format_, wtime); | ||||
|     label_.set_markup(text); | ||||
|   } | ||||
|   label_.set_markup(text); | ||||
|  | ||||
|   if (tooltipEnabled()) { | ||||
|     if (config_["tooltip-format"].isString()) { | ||||
|       const auto calendar = calendar_text(wtime); | ||||
|       auto       tooltip_format = config_["tooltip-format"].asString(); | ||||
|       auto       tooltip_text = fmt::format(tooltip_format, wtime, fmt::arg("calendar", calendar)); | ||||
|       label_.set_tooltip_markup(tooltip_text); | ||||
|     } else { | ||||
|       label_.set_tooltip_markup(text); | ||||
|       std::string calendar_lines = ""; | ||||
|       if (is_calendar_in_tooltip_) { | ||||
|         calendar_lines = calendar_text(wtime); | ||||
|       } | ||||
|       auto tooltip_format = config_["tooltip-format"].asString(); | ||||
|       text = fmt::format(tooltip_format, wtime, fmt::arg(kCalendarPlaceholder.c_str(), calendar_lines)); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   label_.set_tooltip_markup(text); | ||||
|   // Call parent 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) { | ||||
|   // defer to user commands if set | ||||
|   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) { | ||||
|     return true; | ||||
|   } | ||||
|   if (!config_["timezones"].isArray() || config_["timezones"].empty()) { | ||||
|   if (time_zones_.size() == 1) { | ||||
|     return true; | ||||
|   } | ||||
|   auto nr_zones = config_["timezones"].size(); | ||||
|  | ||||
|   auto nr_zones = time_zones_.size(); | ||||
|   if (dir == SCROLL_DIR::UP) { | ||||
|     size_t new_idx = time_zone_idx_ + 1; | ||||
|     time_zone_idx_ = new_idx == nr_zones ? 0 : new_idx; | ||||
|     size_t new_idx = current_time_zone_idx_ + 1; | ||||
|     current_time_zone_idx_ = new_idx == nr_zones ? 0 : new_idx; | ||||
|   } 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(); | ||||
|   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 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 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 state = getState(percentage_used); | ||||
|   | ||||
| @@ -15,11 +15,11 @@ auto waybar::modules::Memory::update() -> void { | ||||
|   unsigned long memfree; | ||||
|   if (meminfo_.count("MemAvailable")) { | ||||
|     // New kernels (3.4+) have an accurate available memory field. | ||||
|     memfree = meminfo_["MemAvailable"]; | ||||
|     memfree = meminfo_["MemAvailable"] + meminfo_["zfs_size"]; | ||||
|   } else { | ||||
|     // Old kernel; give a best-effort approximation of available memory. | ||||
|     memfree = meminfo_["MemFree"] + meminfo_["Buffers"] + meminfo_["Cached"] + | ||||
|               meminfo_["SReclaimable"] - meminfo_["Shmem"]; | ||||
|               meminfo_["SReclaimable"] - meminfo_["Shmem"] + meminfo_["zfs_size"]; | ||||
|   } | ||||
|  | ||||
|   if (memtotal > 0 && memfree >= 0) { | ||||
|   | ||||
| @@ -1,8 +1,29 @@ | ||||
| #include "modules/memory.hpp" | ||||
|  | ||||
| static unsigned zfsArcSize() { | ||||
|   std::ifstream zfs_arc_stats{"/proc/spl/kstat/zfs/arcstats"}; | ||||
|  | ||||
|   if (zfs_arc_stats.is_open()) { | ||||
|     std::string   name; | ||||
|     std::string   type; | ||||
|     unsigned long data{0}; | ||||
|  | ||||
|     std::string line; | ||||
|     while (std::getline(zfs_arc_stats, line)) { | ||||
|       std::stringstream(line) >> name >> type >> data; | ||||
|  | ||||
|       if (name == "size") { | ||||
|         return data / 1024;  // convert to kB | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   return 0; | ||||
| } | ||||
|  | ||||
| void waybar::modules::Memory::parseMeminfo() { | ||||
|   const std::string data_dir_ = "/proc/meminfo"; | ||||
|   std::ifstream info(data_dir_); | ||||
|   std::ifstream     info(data_dir_); | ||||
|   if (!info.is_open()) { | ||||
|     throw std::runtime_error("Can't open " + data_dir_); | ||||
|   } | ||||
| @@ -17,4 +38,6 @@ void waybar::modules::Memory::parseMeminfo() { | ||||
|     int64_t     value = std::stol(line.substr(posDelim + 1)); | ||||
|     meminfo_[name] = value; | ||||
|   } | ||||
|  | ||||
|   meminfo_["zfs_size"] = zfsArcSize(); | ||||
| } | ||||
|   | ||||
| @@ -131,6 +131,9 @@ void waybar::modules::MPD::setLabel() { | ||||
|     date = getTag(MPD_TAG_DATE); | ||||
|     song_pos = mpd_status_get_song_pos(status_.get()); | ||||
|     volume = mpd_status_get_volume(status_.get()); | ||||
|     if (volume < 0) { | ||||
|       volume = 0; | ||||
|     } | ||||
|     queue_length = mpd_status_get_queue_length(status_.get()); | ||||
|     elapsedTime = std::chrono::seconds(mpd_status_get_elapsed_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 <sys/eventfd.h> | ||||
| #include <linux/if.h> | ||||
| #include <fstream> | ||||
|  | ||||
| #include <cassert> | ||||
| #include <fstream> | ||||
| #include <sstream> | ||||
| #include <optional> | ||||
|  | ||||
| #include "modules/network.hpp" | ||||
| #include "util/format.hpp" | ||||
| #ifdef WANT_RFKILL | ||||
| #include "util/rfkill.hpp" | ||||
| #endif | ||||
|  | ||||
| namespace { | ||||
|  | ||||
| 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}"; | ||||
|  | ||||
| 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 | ||||
|  | ||||
| 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) | ||||
|     : ALabel(config, "network", id, DEFAULT_FORMAT, 60), | ||||
|       ifid_(-1), | ||||
| @@ -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. | ||||
|   label_.set_markup("<s></s>"); | ||||
|  | ||||
|   auto down_octets = read_netstat(BANDWIDTH_CATEGORY, BANDWIDTH_DOWN_TOTAL_KEY); | ||||
|   auto up_octets = read_netstat(BANDWIDTH_CATEGORY, BANDWIDTH_UP_TOTAL_KEY); | ||||
|   if (down_octets) { | ||||
|     bandwidth_down_total_ = *down_octets; | ||||
|   auto bandwidth = readBandwidthUsage(); | ||||
|   if (bandwidth.has_value()) { | ||||
|     bandwidth_down_total_ = (*bandwidth).first; | ||||
|     bandwidth_up_total_ = (*bandwidth).second; | ||||
|   } else { | ||||
|     bandwidth_down_total_ = 0; | ||||
|   } | ||||
|  | ||||
|   if (up_octets) { | ||||
|     bandwidth_up_total_ = *up_octets; | ||||
|   } else { | ||||
|     bandwidth_up_total_ = 0; | ||||
|   } | ||||
|  | ||||
| @@ -303,20 +288,21 @@ const std::string waybar::modules::Network::getNetworkState() const { | ||||
| auto waybar::modules::Network::update() -> void { | ||||
|   std::lock_guard<std::mutex> lock(mutex_); | ||||
|   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; | ||||
|   if (down_octets) { | ||||
|     bandwidth_down = *down_octets - bandwidth_down_total_; | ||||
|     bandwidth_down_total_ = *down_octets; | ||||
|   auto bandwidth = readBandwidthUsage(); | ||||
|   auto bandwidth_down = 0ull; | ||||
|   auto bandwidth_up = 0ull; | ||||
|   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_) { | ||||
|     auto state = getNetworkState(); | ||||
|     if (!state_.empty() && label_.get_style_context()->has_class(state_)) { | ||||
|   | ||||
| @@ -218,7 +218,7 @@ static const std::array<std::string, 9> ports = { | ||||
| }; | ||||
|  | ||||
| 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::transform(nameLC.begin(), nameLC.end(), nameLC.begin(), ::tolower); | ||||
|   for (auto const &port : ports) { | ||||
|   | ||||
| @@ -7,7 +7,6 @@ | ||||
|  | ||||
| #include "client.hpp" | ||||
| #include "modules/river/tags.hpp" | ||||
| #include "river-status-unstable-v1-client-protocol.h" | ||||
| #include "xdg-output-unstable-v1-client-protocol.h" | ||||
|  | ||||
| namespace waybar::modules::river { | ||||
| @@ -33,6 +32,23 @@ static const zriver_output_status_v1_listener output_status_listener_impl{ | ||||
|     .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, | ||||
|                           const char *interface, uint32_t version) { | ||||
|   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 *>( | ||||
|         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) { | ||||
|   /* Ignore event */ | ||||
| } | ||||
|  | ||||
|  | ||||
| static const wl_registry_listener registry_listener_impl = {.global = handle_global, | ||||
|                                                             .global_remove = handle_global_remove}; | ||||
|  | ||||
| Tags::Tags(const std::string &id, const waybar::Bar &bar, const Json::Value &config) | ||||
|     : waybar::AModule(config, "tags", id, false, false), | ||||
|       status_manager_{nullptr}, | ||||
|       control_{nullptr}, | ||||
|       seat_{nullptr}, | ||||
|       bar_(bar), | ||||
|       box_{bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0}, | ||||
|       output_status_{nullptr} { | ||||
| @@ -68,6 +99,14 @@ Tags::Tags(const std::string &id, const waybar::Bar &bar, const Json::Value &con | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   if (!control_) { | ||||
|     spdlog::error("river_control_v1 not advertised"); | ||||
|   } | ||||
|  | ||||
|   if (!seat_) { | ||||
|     spdlog::error("wl_seat not advertised"); | ||||
|   } | ||||
|  | ||||
|   box_.set_name("tags"); | ||||
|   if (!id.empty()) { | ||||
|     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) { | ||||
|     Gtk::Button &button = buttons_.emplace_back(tag_label); | ||||
|     button.set_relief(Gtk::RELIEF_NONE); | ||||
|     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(); | ||||
|     i <<= 1; | ||||
|   } | ||||
|  | ||||
|   struct wl_output *output = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj()); | ||||
| @@ -107,6 +152,31 @@ Tags::~Tags() { | ||||
|   if (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) { | ||||
|   | ||||
							
								
								
									
										107
									
								
								src/modules/sway/bar.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								src/modules/sway/bar.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -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("shortDescription", layout_.short_description), | ||||
|                                          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); | ||||
|   if (tooltipEnabled()) { | ||||
|     if (tooltip_format_ != "") { | ||||
| @@ -107,7 +108,8 @@ auto Language::update() -> void { | ||||
|                                                      fmt::arg("short", layout_.short_name), | ||||
|                                                      fmt::arg("shortDescription", layout_.short_description), | ||||
|                                                      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); | ||||
|     } else { | ||||
|       label_.set_tooltip_markup(display_layout); | ||||
| @@ -212,4 +214,15 @@ Language::XKBContext::~XKBContext() { | ||||
|   rxkb_context_unref(context_); | ||||
|   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 | ||||
|   | ||||
| @@ -276,8 +276,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) : | ||||
|     bar_{bar}, config_{config}, tbar_{tbar}, handle_{tl_handle}, seat_{seat}, | ||||
|     id_{global_id++}, | ||||
|     content_{bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0}, | ||||
|     button_visible_{false}, ignored_{false} | ||||
|     content_{bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0} | ||||
| { | ||||
|     zwlr_foreign_toplevel_handle_v1_add_listener(handle_, &toplevel_handle_impl, this); | ||||
|  | ||||
| @@ -377,13 +376,12 @@ std::string Task::state_string(bool shortened) const | ||||
| void Task::handle_title(const char *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)) { | ||||
|     if (tbar_->ignore_list().count(app_id_) || tbar_->ignore_list().count(title_)) { | ||||
|         ignored_ = true; | ||||
|         if (button_visible_) { | ||||
|           auto output = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj()); | ||||
| @@ -397,6 +395,12 @@ void Task::handle_app_id(const char *app_id) | ||||
|           handle_output_enter(output); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| void Task::handle_app_id(const char *app_id) | ||||
| { | ||||
|     app_id_ = app_id; | ||||
|     hide_if_ignored(); | ||||
|  | ||||
|     if (!with_icon_) | ||||
|         return; | ||||
| @@ -418,13 +422,13 @@ void Task::handle_app_id(const char *app_id) | ||||
|  | ||||
| void Task::handle_output_enter(struct wl_output *output) | ||||
| { | ||||
|     spdlog::debug("{} entered output {}", repr(), (void*)output); | ||||
|  | ||||
|     if (ignored_) { | ||||
|       spdlog::debug("{} is ignored", repr()); | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     spdlog::debug("{} entered output {}", repr(), (void*)output); | ||||
|  | ||||
|     if (!button_visible_ && (tbar_->all_outputs() || tbar_->show_output(output))) { | ||||
|         /* The task entered the output of the current bar make the button visible */ | ||||
|         tbar_->add_button(button_); | ||||
|   | ||||
							
								
								
									
										472
									
								
								src/modules/wlr/workspace_manager.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										472
									
								
								src/modules/wlr/workspace_manager.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
							
								
								
									
										135
									
								
								src/modules/wlr/workspace_manager_binding.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										135
									
								
								src/modules/wlr/workspace_manager_binding.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
							
								
								
									
										24
									
								
								test/GlibTestsFixture.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								test/GlibTestsFixture.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -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_; | ||||
| }; | ||||
							
								
								
									
										145
									
								
								test/SafeSignal.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										145
									
								
								test/SafeSignal.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -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 = [ | ||||
|     catch2, | ||||
|     fmt, | ||||
|     gtkmm, | ||||
|     jsoncpp, | ||||
|     spdlog, | ||||
| ] | ||||
| @@ -14,8 +15,21 @@ config_test = executable( | ||||
|     include_directories: test_inc, | ||||
| ) | ||||
|  | ||||
| safesignal_test = executable( | ||||
|     'safesignal_test', | ||||
|     'SafeSignal.cpp', | ||||
|     dependencies: test_dep, | ||||
|     include_directories: test_inc, | ||||
| ) | ||||
|  | ||||
| test( | ||||
|     'Configuration test', | ||||
|     config_test, | ||||
|     workdir: meson.source_root(), | ||||
| ) | ||||
|  | ||||
| test( | ||||
|     'SafeSignal test', | ||||
|     safesignal_test, | ||||
|     workdir: meson.source_root(), | ||||
| ) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 aashu
					aashu