mirror of
				https://github.com/rad4day/Waybar.git
				synced 2025-10-31 07:52:42 +01:00 
			
		
		
		
	Merge branch 'master' of https://github.com/Alexays/Waybar into pr/anakael/add-name-to-taskbar
This commit is contained in:
		
							
								
								
									
										14
									
								
								.github/workflows/freebsd.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										14
									
								
								.github/workflows/freebsd.yml
									
									
									
									
										vendored
									
									
								
							| @@ -4,20 +4,24 @@ on: [ push, pull_request ] | |||||||
|  |  | ||||||
| jobs: | jobs: | ||||||
|   clang: |   clang: | ||||||
|     runs-on: macos-latest # until https://github.com/actions/runner/issues/385 |     # Run actions in a FreeBSD vm on the macos-10.15 runner | ||||||
|  |     # https://github.com/actions/runner/issues/385 - for FreeBSD runner support | ||||||
|  |     # https://github.com/actions/virtual-environments/issues/4060 - for lack of VirtualBox on MacOS 11 runners | ||||||
|  |     runs-on: macos-10.15 | ||||||
|     steps: |     steps: | ||||||
|     - uses: actions/checkout@v2 |     - uses: actions/checkout@v2 | ||||||
|     - name: Test in FreeBSD VM |     - name: Test in FreeBSD VM | ||||||
|       uses: vmactions/freebsd-vm@v0.1.4 # aka FreeBSD 12.2 |       uses: vmactions/freebsd-vm@v0.1.5 # aka FreeBSD 13.0 | ||||||
|       with: |       with: | ||||||
|         usesh: true |         usesh: true | ||||||
|         prepare: | |         prepare: | | ||||||
|           export CPPFLAGS=-isystem/usr/local/include LDFLAGS=-L/usr/local/lib # sndio |           export CPPFLAGS=-isystem/usr/local/include LDFLAGS=-L/usr/local/lib # sndio | ||||||
|           sed -i '' 's/quarterly/latest/' /etc/pkg/FreeBSD.conf |           sed -i '' 's/quarterly/latest/' /etc/pkg/FreeBSD.conf | ||||||
|           pkg install -y git #  subprojects/date |           pkg install -y git #  subprojects/date | ||||||
|           pkg install -y evdev-proto gtk-layer-shell gtkmm30 jsoncpp libdbusmenu \ |           pkg install -y catch evdev-proto gtk-layer-shell gtkmm30 jsoncpp \ | ||||||
|             libevdev libfmt libmpdclient libudev-devd meson pkgconf pulseaudio \ |             libdbusmenu libevdev libfmt libmpdclient libudev-devd meson \ | ||||||
|             scdoc sndio spdlog |             pkgconf pulseaudio scdoc sndio spdlog | ||||||
|         run: | |         run: | | ||||||
|           meson build -Dman-pages=enabled |           meson build -Dman-pages=enabled | ||||||
|           ninja -C build |           ninja -C build | ||||||
|  |           meson test -C build --no-rebuild --print-errorlogs --suite waybar | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								.github/workflows/linux.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/linux.yml
									
									
									
									
										vendored
									
									
								
							| @@ -23,3 +23,5 @@ jobs: | |||||||
|         run: meson -Dman-pages=enabled build |         run: meson -Dman-pages=enabled build | ||||||
|       - name: build |       - name: build | ||||||
|         run: ninja -C build |         run: ninja -C build | ||||||
|  |       - name: test | ||||||
|  |         run: meson test -C build --no-rebuild --print-errorlogs --suite waybar | ||||||
|   | |||||||
							
								
								
									
										3
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										3
									
								
								Makefile
									
									
									
									
									
								
							| @@ -16,5 +16,8 @@ install: build | |||||||
| run: build | run: build | ||||||
| 	./build/waybar | 	./build/waybar | ||||||
|  |  | ||||||
|  | debug-run: build-debug | ||||||
|  | 	./build/waybar --log-level debug | ||||||
|  |  | ||||||
| clean: | clean: | ||||||
| 	rm -rf build | 	rm -rf build | ||||||
|   | |||||||
| @@ -3,11 +3,10 @@ | |||||||
| #include <fmt/format.h> | #include <fmt/format.h> | ||||||
| #include <gdk/gdk.h> | #include <gdk/gdk.h> | ||||||
| #include <gdk/gdkwayland.h> | #include <gdk/gdkwayland.h> | ||||||
| #include <unistd.h> |  | ||||||
| #include <wayland-client.h> | #include <wayland-client.h> | ||||||
| #include <wordexp.h> |  | ||||||
|  |  | ||||||
| #include "bar.hpp" | #include "bar.hpp" | ||||||
|  | #include "config.hpp" | ||||||
|  |  | ||||||
| struct zwlr_layer_shell_v1; | struct zwlr_layer_shell_v1; | ||||||
| struct zwp_idle_inhibitor_v1; | struct zwp_idle_inhibitor_v1; | ||||||
| @@ -29,18 +28,13 @@ class Client { | |||||||
|   struct zxdg_output_manager_v1 *     xdg_output_manager = nullptr; |   struct zxdg_output_manager_v1 *     xdg_output_manager = nullptr; | ||||||
|   struct zwp_idle_inhibit_manager_v1 *idle_inhibit_manager = nullptr; |   struct zwp_idle_inhibit_manager_v1 *idle_inhibit_manager = nullptr; | ||||||
|   std::vector<std::unique_ptr<Bar>>   bars; |   std::vector<std::unique_ptr<Bar>>   bars; | ||||||
|  |   Config                              config; | ||||||
|  |  | ||||||
|  private: |  private: | ||||||
|   Client() = default; |   Client() = default; | ||||||
|   std::tuple<const std::string, const std::string> getConfigs(const std::string &config, |   const std::string        getStyle(const std::string &style); | ||||||
|                                                               const std::string &style) const; |  | ||||||
|   void                     bindInterfaces(); |   void                     bindInterfaces(); | ||||||
|   const std::string        getValidPath(const std::vector<std::string> &paths) const; |  | ||||||
|   void                     handleOutput(struct waybar_output &output); |   void                     handleOutput(struct waybar_output &output); | ||||||
|   bool                     isValidOutput(const Json::Value &config, struct waybar_output &output); |  | ||||||
|   auto                     setupConfig(const std::string &config_file, int depth) -> void; |  | ||||||
|   auto                     resolveConfigIncludes(Json::Value &config, int depth) -> void; |  | ||||||
|   auto                     mergeConfig(Json::Value &a_config_, Json::Value &b_config_) -> void; |  | ||||||
|   auto                     setupCss(const std::string &css_file) -> void; |   auto                     setupCss(const std::string &css_file) -> void; | ||||||
|   struct waybar_output &   getOutput(void *); |   struct waybar_output &   getOutput(void *); | ||||||
|   std::vector<Json::Value> getOutputConfigs(struct waybar_output &output); |   std::vector<Json::Value> getOutputConfigs(struct waybar_output &output); | ||||||
| @@ -55,7 +49,6 @@ class Client { | |||||||
|   void        handleMonitorRemoved(Glib::RefPtr<Gdk::Monitor> monitor); |   void        handleMonitorRemoved(Glib::RefPtr<Gdk::Monitor> monitor); | ||||||
|   void        handleDeferredMonitorRemoval(Glib::RefPtr<Gdk::Monitor> monitor); |   void        handleDeferredMonitorRemoval(Glib::RefPtr<Gdk::Monitor> monitor); | ||||||
|  |  | ||||||
|   Json::Value                     config_; |  | ||||||
|   Glib::RefPtr<Gtk::StyleContext> style_context_; |   Glib::RefPtr<Gtk::StyleContext> style_context_; | ||||||
|   Glib::RefPtr<Gtk::CssProvider>  css_provider_; |   Glib::RefPtr<Gtk::CssProvider>  css_provider_; | ||||||
|   std::list<struct waybar_output> outputs_; |   std::list<struct waybar_output> outputs_; | ||||||
|   | |||||||
							
								
								
									
										39
									
								
								include/config.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								include/config.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,39 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include <json/json.h> | ||||||
|  |  | ||||||
|  | #include <optional> | ||||||
|  | #include <string> | ||||||
|  |  | ||||||
|  | #ifndef SYSCONFDIR | ||||||
|  | #define SYSCONFDIR "/etc" | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  | namespace waybar { | ||||||
|  |  | ||||||
|  | class Config { | ||||||
|  |  public: | ||||||
|  |   static const std::vector<std::string> CONFIG_DIRS; | ||||||
|  |  | ||||||
|  |   /* Try to find any of provided names in the supported set of config directories */ | ||||||
|  |   static std::optional<std::string> findConfigPath( | ||||||
|  |       const std::vector<std::string> &names, const std::vector<std::string> &dirs = CONFIG_DIRS); | ||||||
|  |  | ||||||
|  |   Config() = default; | ||||||
|  |  | ||||||
|  |   void load(const std::string &config); | ||||||
|  |  | ||||||
|  |   Json::Value &getConfig() { return config_; } | ||||||
|  |  | ||||||
|  |   std::vector<Json::Value> getOutputConfigs(const std::string &name, const std::string &identifier); | ||||||
|  |  | ||||||
|  |  private: | ||||||
|  |   void setupConfig(Json::Value &dst, const std::string &config_file, int depth); | ||||||
|  |   void resolveConfigIncludes(Json::Value &config, int depth); | ||||||
|  |   void mergeConfig(Json::Value &a_config_, Json::Value &b_config_); | ||||||
|  |  | ||||||
|  |   std::string config_file_; | ||||||
|  |  | ||||||
|  |   Json::Value config_; | ||||||
|  | }; | ||||||
|  | }  // namespace waybar | ||||||
| @@ -14,6 +14,7 @@ | |||||||
| #endif | #endif | ||||||
| #ifdef HAVE_WLR | #ifdef HAVE_WLR | ||||||
| #include "modules/wlr/taskbar.hpp" | #include "modules/wlr/taskbar.hpp" | ||||||
|  | #include "modules/wlr/workspace_manager.hpp" | ||||||
| #endif | #endif | ||||||
| #ifdef HAVE_RIVER | #ifdef HAVE_RIVER | ||||||
| #include "modules/river/tags.hpp" | #include "modules/river/tags.hpp" | ||||||
|   | |||||||
| @@ -37,6 +37,7 @@ class Clock : public ALabel { | |||||||
|   auto calendar_text(const waybar_time& wtime) -> std::string; |   auto calendar_text(const waybar_time& wtime) -> std::string; | ||||||
|   auto weekdays_header(const date::weekday& first_dow, std::ostream& os) -> void; |   auto weekdays_header(const date::weekday& first_dow, std::ostream& os) -> void; | ||||||
|   auto first_day_of_week() -> date::weekday; |   auto first_day_of_week() -> date::weekday; | ||||||
|  |   bool setTimeZone(Json::Value zone_name); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| }  // namespace waybar::modules | }  // namespace waybar::modules | ||||||
|   | |||||||
| @@ -20,7 +20,7 @@ class Cpu : public ALabel { | |||||||
|  |  | ||||||
|  private: |  private: | ||||||
|   double                                         getCpuLoad(); |   double                                         getCpuLoad(); | ||||||
|   std::tuple<uint16_t, std::string>       getCpuUsage(); |   std::tuple<std::vector<uint16_t>, std::string> getCpuUsage(); | ||||||
|   std::tuple<float, float, float>                getCpuFrequency(); |   std::tuple<float, float, float>                getCpuFrequency(); | ||||||
|   std::vector<std::tuple<size_t, size_t>>        parseCpuinfo(); |   std::vector<std::tuple<size_t, size_t>>        parseCpuinfo(); | ||||||
|   std::vector<float>                             parseCpuFrequencies(); |   std::vector<float>                             parseCpuFrequencies(); | ||||||
|   | |||||||
| @@ -43,6 +43,7 @@ class Network : public ALabel { | |||||||
|   const std::string getNetworkState() const; |   const std::string getNetworkState() const; | ||||||
|   void              clearIface(); |   void              clearIface(); | ||||||
|   bool              wildcardMatch(const std::string& pattern, const std::string& text) const; |   bool              wildcardMatch(const std::string& pattern, const std::string& text) const; | ||||||
|  |   std::optional<std::pair<unsigned long long, unsigned long long>> readBandwidthUsage(); | ||||||
|  |  | ||||||
|   int                ifid_; |   int                ifid_; | ||||||
|   sa_family_t        family_; |   sa_family_t        family_; | ||||||
| @@ -67,6 +68,7 @@ class Network : public ALabel { | |||||||
|   bool        carrier_; |   bool        carrier_; | ||||||
|   std::string ifname_; |   std::string ifname_; | ||||||
|   std::string ipaddr_; |   std::string ipaddr_; | ||||||
|  |   std::string gwaddr_; | ||||||
|   std::string netmask_; |   std::string netmask_; | ||||||
|   int         cidr_; |   int         cidr_; | ||||||
|   int32_t     signal_strength_dbm_; |   int32_t     signal_strength_dbm_; | ||||||
|   | |||||||
| @@ -6,6 +6,7 @@ | |||||||
| #include "AModule.hpp" | #include "AModule.hpp" | ||||||
| #include "bar.hpp" | #include "bar.hpp" | ||||||
| #include "river-status-unstable-v1-client-protocol.h" | #include "river-status-unstable-v1-client-protocol.h" | ||||||
|  | #include "river-control-unstable-v1-client-protocol.h" | ||||||
| #include "xdg-output-unstable-v1-client-protocol.h" | #include "xdg-output-unstable-v1-client-protocol.h" | ||||||
|  |  | ||||||
| namespace waybar::modules::river { | namespace waybar::modules::river { | ||||||
| @@ -20,7 +21,12 @@ class Tags : public waybar::AModule { | |||||||
|   void handle_view_tags(struct wl_array *tags); |   void handle_view_tags(struct wl_array *tags); | ||||||
|   void handle_urgent_tags(uint32_t tags); |   void handle_urgent_tags(uint32_t tags); | ||||||
|  |  | ||||||
|  |   void handle_primary_clicked(uint32_t tag); | ||||||
|  |   bool handle_button_press(GdkEventButton *event_button, uint32_t tag); | ||||||
|  |  | ||||||
|   struct zriver_status_manager_v1 *status_manager_; |   struct zriver_status_manager_v1 *status_manager_; | ||||||
|  |   struct zriver_control_v1 *control_; | ||||||
|  |   struct wl_seat *seat_; | ||||||
|  |  | ||||||
|  private: |  private: | ||||||
|   const waybar::Bar &      bar_; |   const waybar::Bar &      bar_; | ||||||
|   | |||||||
| @@ -84,6 +84,7 @@ class Task | |||||||
|     std::string state_string(bool = false) const; |     std::string state_string(bool = false) const; | ||||||
|     void set_app_info_from_app_id_list(const std::string& app_id_list); |     void set_app_info_from_app_id_list(const std::string& app_id_list); | ||||||
|     bool image_load_icon(Gtk::Image& image, const Glib::RefPtr<Gtk::IconTheme>& icon_theme, Glib::RefPtr<Gio::DesktopAppInfo> app_info, int size); |     bool image_load_icon(Gtk::Image& image, const Glib::RefPtr<Gtk::IconTheme>& icon_theme, Glib::RefPtr<Gio::DesktopAppInfo> app_info, int size); | ||||||
|  |     void hide_if_ignored(); | ||||||
|  |  | ||||||
|    public: |    public: | ||||||
|     /* Getter functions */ |     /* Getter functions */ | ||||||
|   | |||||||
							
								
								
									
										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); | ||||||
|  | } | ||||||
| @@ -120,7 +120,7 @@ The two arguments are: | |||||||
|  |  | ||||||
| # CUSTOM FORMATS | # CUSTOM FORMATS | ||||||
|  |  | ||||||
| The *battery* module allows to define custom formats based on up to two factors. The best fitting format will be selected. | The *battery* module allows one to define custom formats based on up to two factors. The best fitting format will be selected. | ||||||
|  |  | ||||||
| *format-<state>*: With *states*, a custom format can be set depending on the capacity of your battery. | *format-<state>*: With *states*, a custom format can be set depending on the capacity of your battery. | ||||||
|  |  | ||||||
|   | |||||||
| @@ -24,7 +24,8 @@ The *clock* module displays the current date and time. | |||||||
| *timezone*: ++ | *timezone*: ++ | ||||||
| 	typeof: string ++ | 	typeof: string ++ | ||||||
| 	default: inferred local timezone ++ | 	default: inferred local timezone ++ | ||||||
| 	The timezone to display the time in, e.g. America/New_York. | 	The timezone to display the time in, e.g. America/New_York. ++ | ||||||
|  | 	This field will be ignored if *timezones* field is set and have at least one value. | ||||||
|  |  | ||||||
| *timezones*: ++ | *timezones*: ++ | ||||||
| 	typeof: list of strings ++ | 	typeof: list of strings ++ | ||||||
|   | |||||||
| @@ -82,7 +82,9 @@ The *cpu* module displays the current cpu utilization. | |||||||
|  |  | ||||||
| *{load}*: Current cpu load. | *{load}*: Current cpu load. | ||||||
|  |  | ||||||
| *{usage}*: Current cpu usage. | *{usage}*: Current overall cpu usage. | ||||||
|  |  | ||||||
|  | *{usage*{n}*}*: Current cpu core n usage. Cores are numbered from zero, so first core will be {usage0} and 4th will be {usage3}. | ||||||
|  |  | ||||||
| *{avg_frequency}*: Current cpu average frequency (based on all cores) in GHz. | *{avg_frequency}*: Current cpu average frequency (based on all cores) in GHz. | ||||||
|  |  | ||||||
| @@ -90,7 +92,13 @@ The *cpu* module displays the current cpu utilization. | |||||||
|  |  | ||||||
| *{min_frequency}*: Current cpu min frequency (based on the core with the lowest frequency) in GHz. | *{min_frequency}*: Current cpu min frequency (based on the core with the lowest frequency) in GHz. | ||||||
|  |  | ||||||
| # EXAMPLE | *{icon}*: Icon for overall cpu usage. | ||||||
|  |  | ||||||
|  | *{icon*{n}*}*: Icon for cpu core n usage. Use like {icon0}. | ||||||
|  |  | ||||||
|  | # EXAMPLES | ||||||
|  |  | ||||||
|  | Basic configuration: | ||||||
|  |  | ||||||
| ``` | ``` | ||||||
| "cpu": { | "cpu": { | ||||||
| @@ -100,6 +108,16 @@ The *cpu* module displays the current cpu utilization. | |||||||
| } | } | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
|  | Cpu usage per core rendered as icons: | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | "cpu": { | ||||||
|  | 	"interval": 1, | ||||||
|  | 	"format": "{icon0}{icon1}{icon2}{icon3} {usage:>2}% ", | ||||||
|  | 	"format-icons": ["▁", "▂", "▃", "▄", "▅", "▆", "▇", "█"], | ||||||
|  | }, | ||||||
|  | ``` | ||||||
|  |  | ||||||
| # STYLE | # STYLE | ||||||
|  |  | ||||||
| - *#cpu* | - *#cpu* | ||||||
|   | |||||||
| @@ -131,6 +131,8 @@ Addressed by *network* | |||||||
|  |  | ||||||
| *{ipaddr}*: The first IP of the interface. | *{ipaddr}*: The first IP of the interface. | ||||||
|  |  | ||||||
|  | *{gwaddr}*: The default gateway for the interface | ||||||
|  |  | ||||||
| *{netmask}*: The subnetmask corresponding to the IP. | *{netmask}*: The subnetmask corresponding to the IP. | ||||||
|  |  | ||||||
| *{cidr}*: The subnetmask corresponding to the IP in CIDR notation. | *{cidr}*: The subnetmask corresponding to the IP in CIDR notation. | ||||||
|   | |||||||
| @@ -21,6 +21,11 @@ Addressed by *river/tags* | |||||||
|     typeof: array ++ |     typeof: array ++ | ||||||
|     The label to display for each tag. |     The label to display for each tag. | ||||||
|  |  | ||||||
|  | *disable-click*: ++ | ||||||
|  |    typeof: bool ++ | ||||||
|  |    default: false ++ | ||||||
|  |    If set to false, you can left click to set focused tag. Right click to toggle tag focus. If set to true this behaviour is disabled. | ||||||
|  |  | ||||||
| # EXAMPLE | # EXAMPLE | ||||||
|  |  | ||||||
| ``` | ``` | ||||||
|   | |||||||
| @@ -70,7 +70,7 @@ Addressed by *wlr/taskbar* | |||||||
|  |  | ||||||
| *ignore-list*: ++ | *ignore-list*: ++ | ||||||
| 	typeof: array ++ | 	typeof: array ++ | ||||||
| 	List of app_id to be invisible. | 	List of app_id/titles to be invisible. | ||||||
|  |  | ||||||
| *app_ids-mapping*: ++ | *app_ids-mapping*: ++ | ||||||
| 	typeof: object ++ | 	typeof: object ++ | ||||||
|   | |||||||
							
								
								
									
										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,6 +64,10 @@ Also a minimal example configuration can be found on the at the bottom of this m | |||||||
| 	typeof: integer ++ | 	typeof: integer ++ | ||||||
| 	Margins value without units. | 	Margins value without units. | ||||||
|  |  | ||||||
|  | *spacing* ++ | ||||||
|  | 	typeof: integer ++ | ||||||
|  | 	Size of gaps in between of the different modules. | ||||||
|  |  | ||||||
| *name* ++ | *name* ++ | ||||||
| 	typeof: string ++ | 	typeof: string ++ | ||||||
| 	Optional name added as a CSS class, for styling multiple waybars. | 	Optional name added as a CSS class, for styling multiple waybars. | ||||||
| @@ -87,8 +91,9 @@ Also a minimal example configuration can be found on the at the bottom of this m | |||||||
|  |  | ||||||
| *include* ++ | *include* ++ | ||||||
| 	typeof: string|array ++ | 	typeof: string|array ++ | ||||||
| 	Paths to additional configuration files. In case of duplicate options, the including file's value takes precedence. Make sure to avoid circular imports. | 	Paths to additional configuration files. | ||||||
| 	For a multi-bar config, specify at least an empty object for each bar also in every file being included. | 	Each file can contain a single object with any of the bar configuration options. In case of duplicate options, the first defined value takes precedence, i.e. including file -> first included file -> etc. Nested includes are permitted, but make sure to avoid circular imports. | ||||||
|  | 	For a multi-bar config, the include directive affects only current bar configuration object. | ||||||
|  |  | ||||||
| # MODULE FORMAT | # MODULE FORMAT | ||||||
|  |  | ||||||
| @@ -219,5 +224,6 @@ Valid options for the "rotate" property are: 0, 90, 180 and 270. | |||||||
| - *waybar-sway-window(5)* | - *waybar-sway-window(5)* | ||||||
| - *waybar-sway-workspaces(5)* | - *waybar-sway-workspaces(5)* | ||||||
| - *waybar-wlr-taskbar(5)* | - *waybar-wlr-taskbar(5)* | ||||||
|  | - *waybar-wlr-workspaces(5)* | ||||||
| - *waybar-temperature(5)* | - *waybar-temperature(5)* | ||||||
| - *waybar-tray(5)* | - *waybar-tray(5)* | ||||||
|   | |||||||
							
								
								
									
										13
									
								
								meson.build
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								meson.build
									
									
									
									
									
								
							| @@ -149,6 +149,7 @@ src_files = files( | |||||||
|     'src/main.cpp', |     'src/main.cpp', | ||||||
|     'src/bar.cpp', |     'src/bar.cpp', | ||||||
|     'src/client.cpp', |     'src/client.cpp', | ||||||
|  |     'src/config.cpp', | ||||||
|     'src/util/ustring_clen.cpp' |     'src/util/ustring_clen.cpp' | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @@ -185,6 +186,8 @@ src_files += [ | |||||||
| if true | if true | ||||||
|     add_project_arguments('-DHAVE_WLR', language: 'cpp') |     add_project_arguments('-DHAVE_WLR', language: 'cpp') | ||||||
|     src_files += 'src/modules/wlr/taskbar.cpp' |     src_files += 'src/modules/wlr/taskbar.cpp' | ||||||
|  |     src_files += 'src/modules/wlr/workspace_manager.cpp' | ||||||
|  |     src_files += 'src/modules/wlr/workspace_manager_binding.cpp' | ||||||
| endif | endif | ||||||
|  |  | ||||||
| if true | if true | ||||||
| @@ -333,6 +336,7 @@ if scdoc.found() | |||||||
|         'waybar-tray.5.scd', |         'waybar-tray.5.scd', | ||||||
|         'waybar-states.5.scd', |         'waybar-states.5.scd', | ||||||
|         'waybar-wlr-taskbar.5.scd', |         'waybar-wlr-taskbar.5.scd', | ||||||
|  |         'waybar-wlr-workspaces.5.scd', | ||||||
|         'waybar-bluetooth.5.scd', |         'waybar-bluetooth.5.scd', | ||||||
|         'waybar-sndio.5.scd', |         'waybar-sndio.5.scd', | ||||||
|     ] |     ] | ||||||
| @@ -359,6 +363,15 @@ if scdoc.found() | |||||||
|     endforeach |     endforeach | ||||||
| endif | endif | ||||||
|  |  | ||||||
|  | catch2 = dependency( | ||||||
|  |     'catch2', | ||||||
|  |     fallback: ['catch2', 'catch2_dep'], | ||||||
|  |     required: get_option('tests'), | ||||||
|  | ) | ||||||
|  | if catch2.found() | ||||||
|  |     subdir('test') | ||||||
|  | endif | ||||||
|  |  | ||||||
| clangtidy = find_program('clang-tidy', required: false) | clangtidy = find_program('clang-tidy', required: false) | ||||||
|  |  | ||||||
| if clangtidy.found() | if clangtidy.found() | ||||||
|   | |||||||
| @@ -10,3 +10,4 @@ option('mpd', type: 'feature', value: 'auto', description: 'Enable support for t | |||||||
| option('gtk-layer-shell', type: 'feature', value: 'auto', description: 'Use gtk-layer-shell library for popups support') | option('gtk-layer-shell', type: 'feature', value: 'auto', description: 'Use gtk-layer-shell library for popups support') | ||||||
| option('rfkill', type: 'feature', value: 'auto', description: 'Enable support for RFKILL') | option('rfkill', type: 'feature', value: 'auto', description: 'Enable support for RFKILL') | ||||||
| option('sndio', type: 'feature', value: 'auto', description: 'Enable support for sndio') | option('sndio', type: 'feature', value: 'auto', description: 'Enable support for sndio') | ||||||
|  | option('tests', type: 'feature', value: 'auto', description: 'Enable tests') | ||||||
|   | |||||||
							
								
								
									
										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'], | 	[wl_protocol_dir, 'unstable/idle-inhibit/idle-inhibit-unstable-v1.xml'], | ||||||
| 	['wlr-layer-shell-unstable-v1.xml'], | 	['wlr-layer-shell-unstable-v1.xml'], | ||||||
| 	['wlr-foreign-toplevel-management-unstable-v1.xml'], | 	['wlr-foreign-toplevel-management-unstable-v1.xml'], | ||||||
|  | 	['ext-workspace-unstable-v1.xml'], | ||||||
| 	['river-status-unstable-v1.xml'], | 	['river-status-unstable-v1.xml'], | ||||||
|  | 	['river-control-unstable-v1.xml'], | ||||||
| ] | ] | ||||||
|  |  | ||||||
| client_protos_src = [] | client_protos_src = [] | ||||||
|   | |||||||
							
								
								
									
										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) |     // "position": "bottom", // Waybar position (top|bottom|left|right) | ||||||
|     "height": 30, // Waybar height (to be removed for auto height) |     "height": 30, // Waybar height (to be removed for auto height) | ||||||
|     // "width": 1280, // Waybar width |     // "width": 1280, // Waybar width | ||||||
|  |     "spacing": 4, // Gaps between modules (4px) | ||||||
|     // Choose the order of the modules |     // Choose the order of the modules | ||||||
|     "modules-left": ["sway/workspaces", "sway/mode", "custom/media"], |     "modules-left": ["sway/workspaces", "sway/mode", "custom/media"], | ||||||
|     "modules-center": ["sway/window"], |     "modules-center": ["sway/window"], | ||||||
| @@ -117,7 +118,8 @@ | |||||||
|     "network": { |     "network": { | ||||||
|         // "interface": "wlp2*", // (Optional) To force the use of this interface |         // "interface": "wlp2*", // (Optional) To force the use of this interface | ||||||
|         "format-wifi": "{essid} ({signalStrength}%) ", |         "format-wifi": "{essid} ({signalStrength}%) ", | ||||||
|         "format-ethernet": "{ifname}: {ipaddr}/{cidr} ", |         "format-ethernet": "{ipaddr}/{cidr} ", | ||||||
|  |         "tooltip-format": "{ifname} via {gwaddr} ", | ||||||
|         "format-linked": "{ifname} (No IP) ", |         "format-linked": "{ifname} (No IP) ", | ||||||
|         "format-disconnected": "Disconnected ⚠", |         "format-disconnected": "Disconnected ⚠", | ||||||
|         "format-alt": "{ifname}: {ipaddr}/{cidr}" |         "format-alt": "{ifname}: {ipaddr}/{cidr}" | ||||||
|   | |||||||
| @@ -79,7 +79,7 @@ def signal_handler(sig, frame): | |||||||
| def parse_arguments(): | def parse_arguments(): | ||||||
|     parser = argparse.ArgumentParser() |     parser = argparse.ArgumentParser() | ||||||
|  |  | ||||||
|     # Increase verbosity with every occurence of -v |     # Increase verbosity with every occurrence of -v | ||||||
|     parser.add_argument('-v', '--verbose', action='count', default=0) |     parser.add_argument('-v', '--verbose', action='count', default=0) | ||||||
|  |  | ||||||
|     # Define for which player we're listening |     # Define for which player we're listening | ||||||
|   | |||||||
| @@ -80,7 +80,6 @@ window#waybar.chromium { | |||||||
| #idle_inhibitor, | #idle_inhibitor, | ||||||
| #mpd { | #mpd { | ||||||
|     padding: 0 10px; |     padding: 0 10px; | ||||||
|     margin: 0 4px; |  | ||||||
|     color: #ffffff; |     color: #ffffff; | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -67,9 +67,11 @@ std::string ALabel::getIcon(uint16_t percentage, const std::string& alt, uint16_ | |||||||
|   } |   } | ||||||
|   if (format_icons.isArray()) { |   if (format_icons.isArray()) { | ||||||
|     auto size = format_icons.size(); |     auto size = format_icons.size(); | ||||||
|  |     if (size) { | ||||||
|       auto idx = std::clamp(percentage / ((max == 0 ? 100 : max) / size), 0U, size - 1); |       auto idx = std::clamp(percentage / ((max == 0 ? 100 : max) / size), 0U, size - 1); | ||||||
|       format_icons = format_icons[idx]; |       format_icons = format_icons[idx]; | ||||||
|     } |     } | ||||||
|  |   } | ||||||
|   if (format_icons.isString()) { |   if (format_icons.isString()) { | ||||||
|     return format_icons.asString(); |     return format_icons.asString(); | ||||||
|   } |   } | ||||||
| @@ -90,9 +92,11 @@ std::string ALabel::getIcon(uint16_t percentage, const std::vector<std::string>& | |||||||
|   } |   } | ||||||
|   if (format_icons.isArray()) { |   if (format_icons.isArray()) { | ||||||
|     auto size = format_icons.size(); |     auto size = format_icons.size(); | ||||||
|  |     if (size) { | ||||||
|       auto idx = std::clamp(percentage / ((max == 0 ? 100 : max) / size), 0U, size - 1); |       auto idx = std::clamp(percentage / ((max == 0 ? 100 : max) / size), 0U, size - 1); | ||||||
|       format_icons = format_icons[idx]; |       format_icons = format_icons[idx]; | ||||||
|     } |     } | ||||||
|  |   } | ||||||
|   if (format_icons.isString()) { |   if (format_icons.isString()) { | ||||||
|     return format_icons.asString(); |     return format_icons.asString(); | ||||||
|   } |   } | ||||||
|   | |||||||
							
								
								
									
										11
									
								
								src/bar.cpp
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								src/bar.cpp
									
									
									
									
									
								
							| @@ -13,10 +13,10 @@ | |||||||
|  |  | ||||||
| namespace waybar { | namespace waybar { | ||||||
| static constexpr const char* MIN_HEIGHT_MSG = | 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 = | 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* BAR_SIZE_MSG = "Bar configured (width: {}, height: {}) for output: {}"; | ||||||
|  |  | ||||||
| @@ -438,6 +438,13 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config) | |||||||
|   center_.get_style_context()->add_class("modules-center"); |   center_.get_style_context()->add_class("modules-center"); | ||||||
|   right_.get_style_context()->add_class("modules-right"); |   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 height = config["height"].isUInt() ? config["height"].asUInt() : 0; | ||||||
|   uint32_t width = config["width"].isUInt() ? config["width"].asUInt() : 0; |   uint32_t width = config["width"].isUInt() ? config["width"].asUInt() : 0; | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										160
									
								
								src/client.cpp
									
									
									
									
									
								
							
							
						
						
									
										160
									
								
								src/client.cpp
									
									
									
									
									
								
							| @@ -3,12 +3,10 @@ | |||||||
| #include <fmt/ostream.h> | #include <fmt/ostream.h> | ||||||
| #include <spdlog/spdlog.h> | #include <spdlog/spdlog.h> | ||||||
|  |  | ||||||
| #include <fstream> |  | ||||||
| #include <iostream> | #include <iostream> | ||||||
|  |  | ||||||
| #include "idle-inhibit-unstable-v1-client-protocol.h" | #include "idle-inhibit-unstable-v1-client-protocol.h" | ||||||
| #include "util/clara.hpp" | #include "util/clara.hpp" | ||||||
| #include "util/json.hpp" |  | ||||||
| #include "wlr-layer-shell-unstable-v1-client-protocol.h" | #include "wlr-layer-shell-unstable-v1-client-protocol.h" | ||||||
|  |  | ||||||
| waybar::Client *waybar::Client::inst() { | waybar::Client *waybar::Client::inst() { | ||||||
| @@ -16,23 +14,6 @@ waybar::Client *waybar::Client::inst() { | |||||||
|   return c; |   return c; | ||||||
| } | } | ||||||
|  |  | ||||||
| const std::string waybar::Client::getValidPath(const std::vector<std::string> &paths) const { |  | ||||||
|   wordexp_t p; |  | ||||||
|  |  | ||||||
|   for (const std::string &path : paths) { |  | ||||||
|     if (wordexp(path.c_str(), &p, 0) == 0) { |  | ||||||
|       if (access(*p.we_wordv, F_OK) == 0) { |  | ||||||
|         std::string result = *p.we_wordv; |  | ||||||
|         wordfree(&p); |  | ||||||
|         return result; |  | ||||||
|       } |  | ||||||
|       wordfree(&p); |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   return std::string(); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void waybar::Client::handleGlobal(void *data, struct wl_registry *registry, uint32_t name, | void waybar::Client::handleGlobal(void *data, struct wl_registry *registry, uint32_t name, | ||||||
|                                   const char *interface, uint32_t version) { |                                   const char *interface, uint32_t version) { | ||||||
|   auto client = static_cast<Client *>(data); |   auto client = static_cast<Client *>(data); | ||||||
| @@ -70,29 +51,6 @@ void waybar::Client::handleOutput(struct waybar_output &output) { | |||||||
|   zxdg_output_v1_add_listener(output.xdg_output.get(), &xdgOutputListener, &output); |   zxdg_output_v1_add_listener(output.xdg_output.get(), &xdgOutputListener, &output); | ||||||
| } | } | ||||||
|  |  | ||||||
| bool waybar::Client::isValidOutput(const Json::Value &config, struct waybar_output &output) { |  | ||||||
|   if (config["output"].isArray()) { |  | ||||||
|     for (auto const &output_conf : config["output"]) { |  | ||||||
|       if (output_conf.isString() && |  | ||||||
|           (output_conf.asString() == output.name || output_conf.asString() == output.identifier)) { |  | ||||||
|         return true; |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|     return false; |  | ||||||
|   } else if (config["output"].isString()) { |  | ||||||
|     auto config_output = config["output"].asString(); |  | ||||||
|     if (!config_output.empty()) { |  | ||||||
|       if (config_output.substr(0, 1) == "!") { |  | ||||||
|         return config_output.substr(1) != output.name && |  | ||||||
|                config_output.substr(1) != output.identifier; |  | ||||||
|       } |  | ||||||
|       return config_output == output.name || config_output == output.identifier; |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   return true; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| struct waybar::waybar_output &waybar::Client::getOutput(void *addr) { | struct waybar::waybar_output &waybar::Client::getOutput(void *addr) { | ||||||
|   auto it = std::find_if( |   auto it = std::find_if( | ||||||
|       outputs_.begin(), outputs_.end(), [&addr](const auto &output) { return &output == addr; }); |       outputs_.begin(), outputs_.end(), [&addr](const auto &output) { return &output == addr; }); | ||||||
| @@ -103,17 +61,7 @@ struct waybar::waybar_output &waybar::Client::getOutput(void *addr) { | |||||||
| } | } | ||||||
|  |  | ||||||
| std::vector<Json::Value> waybar::Client::getOutputConfigs(struct waybar_output &output) { | std::vector<Json::Value> waybar::Client::getOutputConfigs(struct waybar_output &output) { | ||||||
|   std::vector<Json::Value> configs; |   return config.getOutputConfigs(output.name, output.identifier); | ||||||
|   if (config_.isArray()) { |  | ||||||
|     for (auto const &config : config_) { |  | ||||||
|       if (config.isObject() && isValidOutput(config, output)) { |  | ||||||
|         configs.push_back(config); |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|   } else if (isValidOutput(config_, output)) { |  | ||||||
|     configs.push_back(config_); |  | ||||||
|   } |  | ||||||
|   return configs; |  | ||||||
| } | } | ||||||
|  |  | ||||||
| void waybar::Client::handleOutputDone(void *data, struct zxdg_output_v1 * /*xdg_output*/) { | void waybar::Client::handleOutputDone(void *data, struct zxdg_output_v1 * /*xdg_output*/) { | ||||||
| @@ -203,94 +151,14 @@ void waybar::Client::handleDeferredMonitorRemoval(Glib::RefPtr<Gdk::Monitor> mon | |||||||
|   outputs_.remove_if([&monitor](const auto &output) { return output.monitor == monitor; }); |   outputs_.remove_if([&monitor](const auto &output) { return output.monitor == monitor; }); | ||||||
| } | } | ||||||
|  |  | ||||||
| std::tuple<const std::string, const std::string> waybar::Client::getConfigs( | const std::string waybar::Client::getStyle(const std::string &style) { | ||||||
|     const std::string &config, const std::string &style) const { |   auto css_file = style.empty() ? Config::findConfigPath({"style.css"}) : style; | ||||||
|   auto config_file = config.empty() ? getValidPath({ |   if (!css_file) { | ||||||
|                                           "$XDG_CONFIG_HOME/waybar/config", |     throw std::runtime_error("Missing required resource files"); | ||||||
|                                           "$XDG_CONFIG_HOME/waybar/config.jsonc", |  | ||||||
|                                           "$HOME/.config/waybar/config", |  | ||||||
|                                           "$HOME/.config/waybar/config.jsonc", |  | ||||||
|                                           "$HOME/waybar/config", |  | ||||||
|                                           "$HOME/waybar/config.jsonc", |  | ||||||
|                                           "/etc/xdg/waybar/config", |  | ||||||
|                                           "/etc/xdg/waybar/config.jsonc", |  | ||||||
|                                           SYSCONFDIR "/xdg/waybar/config", |  | ||||||
|                                           "./resources/config", |  | ||||||
|                                       }) |  | ||||||
|                                     : config; |  | ||||||
|   auto css_file = style.empty() ? getValidPath({ |  | ||||||
|                                       "$XDG_CONFIG_HOME/waybar/style.css", |  | ||||||
|                                       "$HOME/.config/waybar/style.css", |  | ||||||
|                                       "$HOME/waybar/style.css", |  | ||||||
|                                       "/etc/xdg/waybar/style.css", |  | ||||||
|                                       SYSCONFDIR "/xdg/waybar/style.css", |  | ||||||
|                                       "./resources/style.css", |  | ||||||
|                                   }) |  | ||||||
|                                 : style; |  | ||||||
|   if (css_file.empty() || config_file.empty()) { |  | ||||||
|     throw std::runtime_error("Missing required resources files"); |  | ||||||
|   } |  | ||||||
|   spdlog::info("Resources files: {}, {}", config_file, css_file); |  | ||||||
|   return {config_file, css_file}; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| auto waybar::Client::setupConfig(const std::string &config_file, int depth) -> void { |  | ||||||
|   if (depth > 100) { |  | ||||||
|     throw std::runtime_error("Aborting due to likely recursive include in config files"); |  | ||||||
|   } |  | ||||||
|   std::ifstream file(config_file); |  | ||||||
|   if (!file.is_open()) { |  | ||||||
|     throw std::runtime_error("Can't open config file"); |  | ||||||
|   } |  | ||||||
|   std::string      str((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>()); |  | ||||||
|   util::JsonParser parser; |  | ||||||
|   Json::Value      tmp_config_ = parser.parse(str); |  | ||||||
|   if (tmp_config_.isArray()) { |  | ||||||
|     for (auto &config_part : tmp_config_) { |  | ||||||
|       resolveConfigIncludes(config_part, depth); |  | ||||||
|     } |  | ||||||
|   } else { |  | ||||||
|     resolveConfigIncludes(tmp_config_, depth); |  | ||||||
|   } |  | ||||||
|   mergeConfig(config_, tmp_config_); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| auto waybar::Client::resolveConfigIncludes(Json::Value &config, int depth) -> void { |  | ||||||
|   Json::Value includes = config["include"]; |  | ||||||
|   if (includes.isArray()) { |  | ||||||
|     for (const auto &include : includes) { |  | ||||||
|       spdlog::info("Including resource file: {}", include.asString()); |  | ||||||
|       setupConfig(getValidPath({include.asString()}), ++depth); |  | ||||||
|     } |  | ||||||
|   } else if (includes.isString()) { |  | ||||||
|     spdlog::info("Including resource file: {}", includes.asString()); |  | ||||||
|     setupConfig(getValidPath({includes.asString()}), ++depth); |  | ||||||
|   } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| auto waybar::Client::mergeConfig(Json::Value &a_config_, Json::Value &b_config_) -> void { |  | ||||||
|   if (!a_config_) { |  | ||||||
|     // For the first config |  | ||||||
|     a_config_ = b_config_; |  | ||||||
|   } else if (a_config_.isObject() && b_config_.isObject()) { |  | ||||||
|     for (const auto &key : b_config_.getMemberNames()) { |  | ||||||
|       if (a_config_[key].isObject() && b_config_[key].isObject()) { |  | ||||||
|         mergeConfig(a_config_[key], b_config_[key]); |  | ||||||
|       } else { |  | ||||||
|         a_config_[key] = b_config_[key]; |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|   } else if (a_config_.isArray() && b_config_.isArray()) { |  | ||||||
|     // This can happen only on the top-level array of a multi-bar config |  | ||||||
|     for (Json::Value::ArrayIndex i = 0; i < b_config_.size(); i++) { |  | ||||||
|       if (a_config_[i].isObject() && b_config_[i].isObject()) { |  | ||||||
|         mergeConfig(a_config_[i], b_config_[i]); |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|   } else { |  | ||||||
|     spdlog::error("Cannot merge config, conflicting or invalid JSON types"); |  | ||||||
|   } |  | ||||||
|   } |   } | ||||||
|  |   spdlog::info("Using CSS file {}", css_file.value()); | ||||||
|  |   return css_file.value(); | ||||||
|  | }; | ||||||
|  |  | ||||||
| auto waybar::Client::setupCss(const std::string &css_file) -> void { | auto waybar::Client::setupCss(const std::string &css_file) -> void { | ||||||
|   css_provider_ = Gtk::CssProvider::create(); |   css_provider_ = Gtk::CssProvider::create(); | ||||||
| @@ -329,14 +197,14 @@ void waybar::Client::bindInterfaces() { | |||||||
| int waybar::Client::main(int argc, char *argv[]) { | int waybar::Client::main(int argc, char *argv[]) { | ||||||
|   bool        show_help = false; |   bool        show_help = false; | ||||||
|   bool        show_version = false; |   bool        show_version = false; | ||||||
|   std::string config; |   std::string config_opt; | ||||||
|   std::string style; |   std::string style_opt; | ||||||
|   std::string bar_id; |   std::string bar_id; | ||||||
|   std::string log_level; |   std::string log_level; | ||||||
|   auto        cli = clara::detail::Help(show_help) | |   auto        cli = clara::detail::Help(show_help) | | ||||||
|              clara::detail::Opt(show_version)["-v"]["--version"]("Show version") | |              clara::detail::Opt(show_version)["-v"]["--version"]("Show version") | | ||||||
|              clara::detail::Opt(config, "config")["-c"]["--config"]("Config path") | |              clara::detail::Opt(config_opt, "config")["-c"]["--config"]("Config path") | | ||||||
|              clara::detail::Opt(style, "style")["-s"]["--style"]("Style path") | |              clara::detail::Opt(style_opt, "style")["-s"]["--style"]("Style path") | | ||||||
|              clara::detail::Opt( |              clara::detail::Opt( | ||||||
|                  log_level, |                  log_level, | ||||||
|                  "trace|debug|info|warning|error|critical|off")["-l"]["--log-level"]("Log level") | |                  "trace|debug|info|warning|error|critical|off")["-l"]["--log-level"]("Log level") | | ||||||
| @@ -367,8 +235,8 @@ int waybar::Client::main(int argc, char *argv[]) { | |||||||
|     throw std::runtime_error("Bar need to run under Wayland"); |     throw std::runtime_error("Bar need to run under Wayland"); | ||||||
|   } |   } | ||||||
|   wl_display = gdk_wayland_display_get_wl_display(gdk_display->gobj()); |   wl_display = gdk_wayland_display_get_wl_display(gdk_display->gobj()); | ||||||
|   auto [config_file, css_file] = getConfigs(config, style); |   config.load(config_opt); | ||||||
|   setupConfig(config_file, 0); |   auto css_file = getStyle(style_opt); | ||||||
|   setupCss(css_file); |   setupCss(css_file); | ||||||
|   bindInterfaces(); |   bindInterfaces(); | ||||||
|   gtk_app->hold(); |   gtk_app->hold(); | ||||||
|   | |||||||
							
								
								
									
										153
									
								
								src/config.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										153
									
								
								src/config.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,153 @@ | |||||||
|  | #include "config.hpp" | ||||||
|  |  | ||||||
|  | #include <fmt/ostream.h> | ||||||
|  | #include <spdlog/spdlog.h> | ||||||
|  | #include <unistd.h> | ||||||
|  | #include <wordexp.h> | ||||||
|  |  | ||||||
|  | #include <fstream> | ||||||
|  | #include <stdexcept> | ||||||
|  |  | ||||||
|  | #include "util/json.hpp" | ||||||
|  |  | ||||||
|  | namespace waybar { | ||||||
|  |  | ||||||
|  | const std::vector<std::string> Config::CONFIG_DIRS = { | ||||||
|  |     "$XDG_CONFIG_HOME/waybar/", | ||||||
|  |     "$HOME/.config/waybar/", | ||||||
|  |     "$HOME/waybar/", | ||||||
|  |     "/etc/xdg/waybar/", | ||||||
|  |     SYSCONFDIR "/xdg/waybar/", | ||||||
|  |     "./resources/", | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | std::optional<std::string> tryExpandPath(const std::string &path) { | ||||||
|  |   wordexp_t p; | ||||||
|  |   if (wordexp(path.c_str(), &p, 0) == 0) { | ||||||
|  |     if (access(*p.we_wordv, F_OK) == 0) { | ||||||
|  |       std::string result = *p.we_wordv; | ||||||
|  |       wordfree(&p); | ||||||
|  |       return result; | ||||||
|  |     } | ||||||
|  |     wordfree(&p); | ||||||
|  |   } | ||||||
|  |   return std::nullopt; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | std::optional<std::string> Config::findConfigPath(const std::vector<std::string> &names, | ||||||
|  |                                                   const std::vector<std::string> &dirs) { | ||||||
|  |   std::vector<std::string> paths; | ||||||
|  |   for (const auto &dir : dirs) { | ||||||
|  |     for (const auto &name : names) { | ||||||
|  |       if (auto res = tryExpandPath(dir + name); res) { | ||||||
|  |         return res; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   return std::nullopt; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void Config::setupConfig(Json::Value &dst, const std::string &config_file, int depth) { | ||||||
|  |   if (depth > 100) { | ||||||
|  |     throw std::runtime_error("Aborting due to likely recursive include in config files"); | ||||||
|  |   } | ||||||
|  |   std::ifstream file(config_file); | ||||||
|  |   if (!file.is_open()) { | ||||||
|  |     throw std::runtime_error("Can't open config file"); | ||||||
|  |   } | ||||||
|  |   std::string      str((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>()); | ||||||
|  |   util::JsonParser parser; | ||||||
|  |   Json::Value      tmp_config = parser.parse(str); | ||||||
|  |   if (tmp_config.isArray()) { | ||||||
|  |     for (auto &config_part : tmp_config) { | ||||||
|  |       resolveConfigIncludes(config_part, depth); | ||||||
|  |     } | ||||||
|  |   } else { | ||||||
|  |     resolveConfigIncludes(tmp_config, depth); | ||||||
|  |   } | ||||||
|  |   mergeConfig(dst, tmp_config); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void Config::resolveConfigIncludes(Json::Value &config, int depth) { | ||||||
|  |   Json::Value includes = config["include"]; | ||||||
|  |   if (includes.isArray()) { | ||||||
|  |     for (const auto &include : includes) { | ||||||
|  |       spdlog::info("Including resource file: {}", include.asString()); | ||||||
|  |       setupConfig(config, tryExpandPath(include.asString()).value_or(""), ++depth); | ||||||
|  |     } | ||||||
|  |   } else if (includes.isString()) { | ||||||
|  |     spdlog::info("Including resource file: {}", includes.asString()); | ||||||
|  |     setupConfig(config, tryExpandPath(includes.asString()).value_or(""), ++depth); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void Config::mergeConfig(Json::Value &a_config_, Json::Value &b_config_) { | ||||||
|  |   if (!a_config_) { | ||||||
|  |     // For the first config | ||||||
|  |     a_config_ = b_config_; | ||||||
|  |   } else if (a_config_.isObject() && b_config_.isObject()) { | ||||||
|  |     for (const auto &key : b_config_.getMemberNames()) { | ||||||
|  |       // [] creates key with default value. Use `get` to avoid that. | ||||||
|  |       if (a_config_.get(key, Json::Value::nullSingleton()).isObject() && | ||||||
|  |           b_config_[key].isObject()) { | ||||||
|  |         mergeConfig(a_config_[key], b_config_[key]); | ||||||
|  |       } else if (!a_config_.isMember(key)) { | ||||||
|  |         // do not allow overriding value set by top or previously included config | ||||||
|  |         a_config_[key] = b_config_[key]; | ||||||
|  |       } else { | ||||||
|  |         spdlog::trace("Option {} is already set; ignoring value {}", key, b_config_[key]); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } else { | ||||||
|  |     spdlog::error("Cannot merge config, conflicting or invalid JSON types"); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | bool isValidOutput(const Json::Value &config, const std::string &name, | ||||||
|  |                    const std::string &identifier) { | ||||||
|  |   if (config["output"].isArray()) { | ||||||
|  |     for (auto const &output_conf : config["output"]) { | ||||||
|  |       if (output_conf.isString() && | ||||||
|  |           (output_conf.asString() == name || output_conf.asString() == identifier)) { | ||||||
|  |         return true; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     return false; | ||||||
|  |   } else if (config["output"].isString()) { | ||||||
|  |     auto config_output = config["output"].asString(); | ||||||
|  |     if (!config_output.empty()) { | ||||||
|  |       if (config_output.substr(0, 1) == "!") { | ||||||
|  |         return config_output.substr(1) != name && config_output.substr(1) != identifier; | ||||||
|  |       } | ||||||
|  |       return config_output == name || config_output == identifier; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return true; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void Config::load(const std::string &config) { | ||||||
|  |   auto file = config.empty() ? findConfigPath({"config", "config.jsonc"}) : config; | ||||||
|  |   if (!file) { | ||||||
|  |     throw std::runtime_error("Missing required resource files"); | ||||||
|  |   } | ||||||
|  |   config_file_ = file.value(); | ||||||
|  |   spdlog::info("Using configuration file {}", config_file_); | ||||||
|  |   setupConfig(config_, config_file_, 0); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | std::vector<Json::Value> Config::getOutputConfigs(const std::string &name, | ||||||
|  |                                                   const std::string &identifier) { | ||||||
|  |   std::vector<Json::Value> configs; | ||||||
|  |   if (config_.isArray()) { | ||||||
|  |     for (auto const &config : config_) { | ||||||
|  |       if (config.isObject() && isValidOutput(config, name, identifier)) { | ||||||
|  |         configs.push_back(config); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } else if (isValidOutput(config_, name, identifier)) { | ||||||
|  |     configs.push_back(config_); | ||||||
|  |   } | ||||||
|  |   return configs; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | }  // namespace waybar | ||||||
| @@ -30,6 +30,9 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const { | |||||||
|     if (ref == "wlr/taskbar") { |     if (ref == "wlr/taskbar") { | ||||||
|       return new waybar::modules::wlr::Taskbar(id, bar_, config_[name]); |       return new waybar::modules::wlr::Taskbar(id, bar_, config_[name]); | ||||||
|     } |     } | ||||||
|  |     if (ref == "wlr/workspaces") { | ||||||
|  |       return new waybar::modules::wlr::WorkspaceManager(id, bar_, config_[name]); | ||||||
|  |     } | ||||||
| #endif | #endif | ||||||
| #ifdef HAVE_RIVER | #ifdef HAVE_RIVER | ||||||
|     if (ref == "river/tags") { |     if (ref == "river/tags") { | ||||||
|   | |||||||
| @@ -173,7 +173,7 @@ auto waybar::modules::Backlight::update() -> void { | |||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     const auto percent = best->get_max() == 0 ? 100 : best->get_actual() * 100 / best->get_max(); |     const uint8_t percent = best->get_max() == 0 ? 100 : round(best->get_actual() * 100.0f / best->get_max()); | ||||||
|     label_.set_markup(fmt::format( |     label_.set_markup(fmt::format( | ||||||
|         format_, fmt::arg("percent", std::to_string(percent)), fmt::arg("icon", getIcon(percent)))); |         format_, fmt::arg("percent", std::to_string(percent)), fmt::arg("icon", getIcon(percent)))); | ||||||
|     getState(percent); |     getState(percent); | ||||||
|   | |||||||
| @@ -15,10 +15,14 @@ using waybar::modules::waybar_time; | |||||||
|  |  | ||||||
| waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config) | waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config) | ||||||
|     : ALabel(config, "clock", id, "{:%H:%M}", 60, false, false, true), fixed_time_zone_(false) { |     : ALabel(config, "clock", id, "{:%H:%M}", 60, false, false, true), fixed_time_zone_(false) { | ||||||
|   if (config_["timezone"].isString()) { |   if (config_["timezones"].isArray() && !config_["timezones"].empty()) { | ||||||
|     spdlog::warn("As using a timezone, some format args may be missing as the date library havn't got a release since 2018."); |     time_zone_idx_ = 0; | ||||||
|     time_zone_ = date::locate_zone(config_["timezone"].asString()); |     setTimeZone(config_["timezones"][time_zone_idx_]); | ||||||
|     fixed_time_zone_ = true; |   } else { | ||||||
|  |     setTimeZone(config_["timezone"]); | ||||||
|  |   } | ||||||
|  |   if (fixed_time_zone_) { | ||||||
|  |     spdlog::warn("As using a timezone, some format args may be missing as the date library haven't got a release since 2018."); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   if (config_["locale"].isString()) { |   if (config_["locale"].isString()) { | ||||||
| @@ -72,6 +76,17 @@ auto waybar::modules::Clock::update() -> void { | |||||||
|   ALabel::update(); |   ALabel::update(); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | bool waybar::modules::Clock::setTimeZone(Json::Value zone_name) { | ||||||
|  |   if (!zone_name.isString() || zone_name.asString().empty()) { | ||||||
|  |       fixed_time_zone_ = false; | ||||||
|  |       return false; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   time_zone_ = date::locate_zone(zone_name.asString()); | ||||||
|  |   fixed_time_zone_ = true; | ||||||
|  |   return true; | ||||||
|  | } | ||||||
|  |  | ||||||
| bool waybar::modules::Clock::handleScroll(GdkEventScroll *e) { | bool waybar::modules::Clock::handleScroll(GdkEventScroll *e) { | ||||||
|   // defer to user commands if set |   // defer to user commands if set | ||||||
|   if (config_["on-scroll-up"].isString() || config_["on-scroll-down"].isString()) { |   if (config_["on-scroll-up"].isString() || config_["on-scroll-down"].isString()) { | ||||||
| @@ -92,14 +107,7 @@ bool waybar::modules::Clock::handleScroll(GdkEventScroll *e) { | |||||||
|   } else { |   } else { | ||||||
|     time_zone_idx_ = time_zone_idx_ == 0 ? nr_zones - 1 : time_zone_idx_ - 1; |     time_zone_idx_ = time_zone_idx_ == 0 ? nr_zones - 1 : time_zone_idx_ - 1; | ||||||
|   } |   } | ||||||
|   auto zone_name = config_["timezones"][time_zone_idx_]; |   setTimeZone(config_["timezones"][time_zone_idx_]); | ||||||
|   if (!zone_name.isString() || zone_name.empty()) { |  | ||||||
|     fixed_time_zone_ = false; |  | ||||||
|   } else { |  | ||||||
|     time_zone_ = date::locate_zone(zone_name.asString()); |  | ||||||
|     fixed_time_zone_ = true; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   update(); |   update(); | ||||||
|   return true; |   return true; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,5 +1,14 @@ | |||||||
| #include "modules/cpu.hpp" | #include "modules/cpu.hpp" | ||||||
|  |  | ||||||
|  | // In the 80000 version of fmt library authors decided to optimize imports | ||||||
|  | // and moved declarations required for fmt::dynamic_format_arg_store in new | ||||||
|  | // header fmt/args.h | ||||||
|  | #if (FMT_VERSION >= 80000) | ||||||
|  | #include <fmt/args.h> | ||||||
|  | #else | ||||||
|  | #include <fmt/core.h> | ||||||
|  | #endif | ||||||
|  |  | ||||||
| waybar::modules::Cpu::Cpu(const std::string& id, const Json::Value& config) | waybar::modules::Cpu::Cpu(const std::string& id, const Json::Value& config) | ||||||
|     : ALabel(config, "cpu", id, "{usage}%", 10) { |     : ALabel(config, "cpu", id, "{usage}%", 10) { | ||||||
|   thread_ = [this] { |   thread_ = [this] { | ||||||
| @@ -17,7 +26,8 @@ auto waybar::modules::Cpu::update() -> void { | |||||||
|     label_.set_tooltip_text(tooltip); |     label_.set_tooltip_text(tooltip); | ||||||
|   } |   } | ||||||
|   auto format = format_; |   auto format = format_; | ||||||
|   auto state = getState(cpu_usage); |   auto total_usage = cpu_usage.empty() ? 0 : cpu_usage[0]; | ||||||
|  |   auto state = getState(total_usage); | ||||||
|   if (!state.empty() && config_["format-" + state].isString()) { |   if (!state.empty() && config_["format-" + state].isString()) { | ||||||
|     format = config_["format-" + state].asString(); |     format = config_["format-" + state].asString(); | ||||||
|   } |   } | ||||||
| @@ -27,13 +37,22 @@ auto waybar::modules::Cpu::update() -> void { | |||||||
|   } else { |   } else { | ||||||
|     event_box_.show(); |     event_box_.show(); | ||||||
|     auto icons = std::vector<std::string>{state}; |     auto icons = std::vector<std::string>{state}; | ||||||
|     label_.set_markup(fmt::format(format, |     fmt::dynamic_format_arg_store<fmt::format_context> store; | ||||||
|                                   fmt::arg("load", cpu_load), |     store.push_back(fmt::arg("load", cpu_load)); | ||||||
|                                   fmt::arg("usage", cpu_usage), |     store.push_back(fmt::arg("load", cpu_load)); | ||||||
|                                   fmt::arg("icon", getIcon(cpu_usage, icons)), |     store.push_back(fmt::arg("usage", total_usage)); | ||||||
|                                   fmt::arg("max_frequency", max_frequency), |     store.push_back(fmt::arg("icon", getIcon(total_usage, icons))); | ||||||
|                                   fmt::arg("min_frequency", min_frequency), |     store.push_back(fmt::arg("max_frequency", max_frequency)); | ||||||
|                                   fmt::arg("avg_frequency", avg_frequency))); |     store.push_back(fmt::arg("min_frequency", min_frequency)); | ||||||
|  |     store.push_back(fmt::arg("avg_frequency", avg_frequency)); | ||||||
|  |     for (size_t i = 1; i < cpu_usage.size(); ++i) { | ||||||
|  | 	    auto core_i = i - 1; | ||||||
|  | 	    auto core_format = fmt::format("usage{}", core_i); | ||||||
|  | 	    store.push_back(fmt::arg(core_format.c_str(), cpu_usage[i])); | ||||||
|  | 	    auto icon_format = fmt::format("icon{}", core_i); | ||||||
|  | 	    store.push_back(fmt::arg(icon_format.c_str(), getIcon(cpu_usage[i], icons))); | ||||||
|  |     } | ||||||
|  |     label_.set_markup(fmt::vformat(format, store)); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   // Call parent update |   // Call parent update | ||||||
| @@ -48,14 +67,14 @@ double waybar::modules::Cpu::getCpuLoad() { | |||||||
|   throw std::runtime_error("Can't get Cpu load"); |   throw std::runtime_error("Can't get Cpu load"); | ||||||
| } | } | ||||||
|  |  | ||||||
| std::tuple<uint16_t, std::string> waybar::modules::Cpu::getCpuUsage() { | std::tuple<std::vector<uint16_t>, std::string> waybar::modules::Cpu::getCpuUsage() { | ||||||
|   if (prev_times_.empty()) { |   if (prev_times_.empty()) { | ||||||
|     prev_times_ = parseCpuinfo(); |     prev_times_ = parseCpuinfo(); | ||||||
|     std::this_thread::sleep_for(std::chrono::milliseconds(100)); |     std::this_thread::sleep_for(std::chrono::milliseconds(100)); | ||||||
|   } |   } | ||||||
|   std::vector<std::tuple<size_t, size_t>> curr_times = parseCpuinfo(); |   std::vector<std::tuple<size_t, size_t>> curr_times = parseCpuinfo(); | ||||||
|   std::string                             tooltip; |   std::string                             tooltip; | ||||||
|   uint16_t                                usage = 0; |   std::vector<uint16_t>                   usage; | ||||||
|   for (size_t i = 0; i < curr_times.size(); ++i) { |   for (size_t i = 0; i < curr_times.size(); ++i) { | ||||||
|     auto [curr_idle, curr_total] = curr_times[i]; |     auto [curr_idle, curr_total] = curr_times[i]; | ||||||
|     auto [prev_idle, prev_total] = prev_times_[i]; |     auto [prev_idle, prev_total] = prev_times_[i]; | ||||||
| @@ -63,11 +82,11 @@ std::tuple<uint16_t, std::string> waybar::modules::Cpu::getCpuUsage() { | |||||||
|     const float delta_total = curr_total - prev_total; |     const float delta_total = curr_total - prev_total; | ||||||
|     uint16_t    tmp = 100 * (1 - delta_idle / delta_total); |     uint16_t    tmp = 100 * (1 - delta_idle / delta_total); | ||||||
|     if (i == 0) { |     if (i == 0) { | ||||||
|       usage = tmp; |  | ||||||
|       tooltip = fmt::format("Total: {}%", tmp); |       tooltip = fmt::format("Total: {}%", tmp); | ||||||
|     } else { |     } else { | ||||||
|       tooltip = tooltip + fmt::format("\nCore{}: {}%", i - 1, tmp); |       tooltip = tooltip + fmt::format("\nCore{}: {}%", i - 1, tmp); | ||||||
|     } |     } | ||||||
|  |     usage.push_back(tmp); | ||||||
|   } |   } | ||||||
|   prev_times_ = curr_times; |   prev_times_ = curr_times; | ||||||
|   return {usage, tooltip}; |   return {usage, tooltip}; | ||||||
|   | |||||||
| @@ -45,9 +45,9 @@ auto waybar::modules::Disk::update() -> void { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   auto free = pow_format(stats.f_bavail * stats.f_frsize, "B", true); |   auto free = pow_format(stats.f_bavail * stats.f_frsize, "B", true); | ||||||
|   auto used = pow_format((stats.f_blocks - stats.f_bavail) * stats.f_frsize, "B", true); |   auto used = pow_format((stats.f_blocks - stats.f_bfree) * stats.f_frsize, "B", true); | ||||||
|   auto total = pow_format(stats.f_blocks * stats.f_frsize, "B", true); |   auto total = pow_format(stats.f_blocks * stats.f_frsize, "B", true); | ||||||
|   auto percentage_used = (stats.f_blocks - stats.f_bavail) * 100 / stats.f_blocks; |   auto percentage_used = (stats.f_blocks - stats.f_bfree) * 100 / stats.f_blocks; | ||||||
|  |  | ||||||
|   auto format = format_; |   auto format = format_; | ||||||
|   auto state = getState(percentage_used); |   auto state = getState(percentage_used); | ||||||
|   | |||||||
| @@ -15,11 +15,11 @@ auto waybar::modules::Memory::update() -> void { | |||||||
|   unsigned long memfree; |   unsigned long memfree; | ||||||
|   if (meminfo_.count("MemAvailable")) { |   if (meminfo_.count("MemAvailable")) { | ||||||
|     // New kernels (3.4+) have an accurate available memory field. |     // New kernels (3.4+) have an accurate available memory field. | ||||||
|     memfree = meminfo_["MemAvailable"]; |     memfree = meminfo_["MemAvailable"] + meminfo_["zfs_size"]; | ||||||
|   } else { |   } else { | ||||||
|     // Old kernel; give a best-effort approximation of available memory. |     // Old kernel; give a best-effort approximation of available memory. | ||||||
|     memfree = meminfo_["MemFree"] + meminfo_["Buffers"] + meminfo_["Cached"] + |     memfree = meminfo_["MemFree"] + meminfo_["Buffers"] + meminfo_["Cached"] + | ||||||
|               meminfo_["SReclaimable"] - meminfo_["Shmem"]; |               meminfo_["SReclaimable"] - meminfo_["Shmem"] + meminfo_["zfs_size"]; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   if (memtotal > 0 && memfree >= 0) { |   if (memtotal > 0 && memfree >= 0) { | ||||||
|   | |||||||
| @@ -1,5 +1,26 @@ | |||||||
| #include "modules/memory.hpp" | #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() { | void waybar::modules::Memory::parseMeminfo() { | ||||||
|   const std::string data_dir_ = "/proc/meminfo"; |   const std::string data_dir_ = "/proc/meminfo"; | ||||||
|   std::ifstream     info(data_dir_); |   std::ifstream     info(data_dir_); | ||||||
| @@ -17,4 +38,6 @@ void waybar::modules::Memory::parseMeminfo() { | |||||||
|     int64_t     value = std::stol(line.substr(posDelim + 1)); |     int64_t     value = std::stol(line.substr(posDelim + 1)); | ||||||
|     meminfo_[name] = value; |     meminfo_[name] = value; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   meminfo_["zfs_size"] = zfsArcSize(); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -131,6 +131,9 @@ void waybar::modules::MPD::setLabel() { | |||||||
|     date = getTag(MPD_TAG_DATE); |     date = getTag(MPD_TAG_DATE); | ||||||
|     song_pos = mpd_status_get_song_pos(status_.get()); |     song_pos = mpd_status_get_song_pos(status_.get()); | ||||||
|     volume = mpd_status_get_volume(status_.get()); |     volume = mpd_status_get_volume(status_.get()); | ||||||
|  |     if (volume < 0) { | ||||||
|  |       volume = 0; | ||||||
|  |     } | ||||||
|     queue_length = mpd_status_get_queue_length(status_.get()); |     queue_length = mpd_status_get_queue_length(status_.get()); | ||||||
|     elapsedTime = std::chrono::seconds(mpd_status_get_elapsed_time(status_.get())); |     elapsedTime = std::chrono::seconds(mpd_status_get_elapsed_time(status_.get())); | ||||||
|     totalTime = std::chrono::seconds(mpd_status_get_total_time(status_.get())); |     totalTime = std::chrono::seconds(mpd_status_get_total_time(status_.get())); | ||||||
|   | |||||||
| @@ -1,87 +1,77 @@ | |||||||
| #include "modules/network.hpp" | #include <linux/if.h> | ||||||
| #include <spdlog/spdlog.h> | #include <spdlog/spdlog.h> | ||||||
| #include <sys/eventfd.h> | #include <sys/eventfd.h> | ||||||
| #include <linux/if.h> |  | ||||||
| #include <fstream> |  | ||||||
| #include <cassert> | #include <cassert> | ||||||
|  | #include <fstream> | ||||||
|  | #include <sstream> | ||||||
| #include <optional> | #include <optional> | ||||||
|  |  | ||||||
|  | #include "modules/network.hpp" | ||||||
| #include "util/format.hpp" | #include "util/format.hpp" | ||||||
| #ifdef WANT_RFKILL | #ifdef WANT_RFKILL | ||||||
| #include "util/rfkill.hpp" | #include "util/rfkill.hpp" | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
| namespace { | namespace { | ||||||
|  |  | ||||||
| using namespace waybar::util; | using namespace waybar::util; | ||||||
|  |  | ||||||
| constexpr const char *NETSTAT_FILE = |  | ||||||
|     "/proc/net/netstat";  // std::ifstream does not take std::string_view as param |  | ||||||
| constexpr std::string_view BANDWIDTH_CATEGORY = "IpExt"; |  | ||||||
| constexpr std::string_view BANDWIDTH_DOWN_TOTAL_KEY = "InOctets"; |  | ||||||
| constexpr std::string_view BANDWIDTH_UP_TOTAL_KEY = "OutOctets"; |  | ||||||
| constexpr const char *DEFAULT_FORMAT = "{ifname}"; | constexpr const char *DEFAULT_FORMAT = "{ifname}"; | ||||||
|  |  | ||||||
| std::ifstream                     netstat(NETSTAT_FILE); |  | ||||||
| std::optional<unsigned long long> read_netstat(std::string_view category, std::string_view key) { |  | ||||||
|   if (!netstat) { |  | ||||||
|     spdlog::warn("Failed to open netstat file {}", NETSTAT_FILE); |  | ||||||
|     return {}; |  | ||||||
|   } |  | ||||||
|   netstat.seekg(std::ios_base::beg); |  | ||||||
|  |  | ||||||
|   // finding corresponding line (category) |  | ||||||
|   // looks into the file for the first line starting by the 'category' string |  | ||||||
|   auto starts_with = [](const std::string &str, std::string_view start) { |  | ||||||
|     return start == std::string_view{str.data(), std::min(str.size(), start.size())}; |  | ||||||
|   }; |  | ||||||
|  |  | ||||||
|   std::string read; |  | ||||||
|   while (std::getline(netstat, read) && !starts_with(read, category)) |  | ||||||
|     ; |  | ||||||
|   if (!starts_with(read, category)) { |  | ||||||
|     spdlog::warn("Category '{}' not found in netstat file {}", category, NETSTAT_FILE); |  | ||||||
|     return {}; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   // finding corresponding column (key) |  | ||||||
|   // looks into the fetched line for the first word (space separated) equal to 'key' |  | ||||||
|   int  index = 0; |  | ||||||
|   auto r_it = read.begin(); |  | ||||||
|   auto k_it = key.begin(); |  | ||||||
|   while (k_it != key.end() && r_it != read.end()) { |  | ||||||
|     if (*r_it != *k_it) { |  | ||||||
|       r_it = std::find(r_it, read.end(), ' '); |  | ||||||
|       if (r_it != read.end()) { |  | ||||||
|         ++r_it; |  | ||||||
|       } |  | ||||||
|       k_it = key.begin(); |  | ||||||
|       ++index; |  | ||||||
|     } else { |  | ||||||
|       ++r_it; |  | ||||||
|       ++k_it; |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   if (r_it == read.end() && k_it != key.end()) { |  | ||||||
|     spdlog::warn( |  | ||||||
|         "Key '{}' not found in category '{}' of netstat file {}", key, category, NETSTAT_FILE); |  | ||||||
|     return {}; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   // finally accessing value |  | ||||||
|   // accesses the line right under the fetched one |  | ||||||
|   std::getline(netstat, read); |  | ||||||
|   assert(starts_with(read, category)); |  | ||||||
|   std::istringstream iss(read); |  | ||||||
|   while (index--) { |  | ||||||
|     std::getline(iss, read, ' '); |  | ||||||
|   } |  | ||||||
|   unsigned long long value; |  | ||||||
|   iss >> value; |  | ||||||
|   return value; |  | ||||||
| } |  | ||||||
| }  // namespace | }  // namespace | ||||||
|  |  | ||||||
|  | constexpr const char *NETDEV_FILE = | ||||||
|  |     "/proc/net/dev";  // std::ifstream does not take std::string_view as param | ||||||
|  | std::optional<std::pair<unsigned long long, unsigned long long>> | ||||||
|  | waybar::modules::Network::readBandwidthUsage() { | ||||||
|  |   std::ifstream netdev(NETDEV_FILE); | ||||||
|  |   if (!netdev) { | ||||||
|  |     spdlog::warn("Failed to open netdev file {}", NETDEV_FILE); | ||||||
|  |     return {}; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   std::string line; | ||||||
|  |   // skip the headers (first two lines) | ||||||
|  |   std::getline(netdev, line); | ||||||
|  |   std::getline(netdev, line); | ||||||
|  |  | ||||||
|  |   unsigned long long receivedBytes = 0ull; | ||||||
|  |   unsigned long long transmittedBytes = 0ull; | ||||||
|  |   while (std::getline(netdev, line)) { | ||||||
|  |     std::istringstream iss(line); | ||||||
|  |  | ||||||
|  |     std::string ifacename; | ||||||
|  |     iss >> ifacename;  // ifacename contains "eth0:" | ||||||
|  |     ifacename.pop_back();  // remove trailing ':' | ||||||
|  |     if (!checkInterface(ifacename)) { | ||||||
|  |       continue; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // The rest of the line consists of whitespace separated counts divided | ||||||
|  |     // into two groups (receive and transmit). Each group has the following | ||||||
|  |     // columns: bytes, packets, errs, drop, fifo, frame, compressed, multicast | ||||||
|  |     // | ||||||
|  |     // We only care about the bytes count, so we'll just ignore the 7 other | ||||||
|  |     // columns. | ||||||
|  |     unsigned long long r = 0ull; | ||||||
|  |     unsigned long long t = 0ull; | ||||||
|  |     // Read received bytes | ||||||
|  |     iss >> r; | ||||||
|  |     // Skip all the other columns in the received group | ||||||
|  |     for (int colsToSkip = 7; colsToSkip > 0; colsToSkip--) { | ||||||
|  |       // skip whitespace between columns | ||||||
|  |       while (iss.peek() == ' ') { iss.ignore(); } | ||||||
|  |       // skip the irrelevant column | ||||||
|  |       while (iss.peek() != ' ') { iss.ignore(); } | ||||||
|  |     } | ||||||
|  |     // Read transmit bytes | ||||||
|  |     iss >> t; | ||||||
|  |  | ||||||
|  |     receivedBytes += r; | ||||||
|  |     transmittedBytes += t; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return {{receivedBytes, transmittedBytes}}; | ||||||
|  | } | ||||||
|  |  | ||||||
| waybar::modules::Network::Network(const std::string &id, const Json::Value &config) | waybar::modules::Network::Network(const std::string &id, const Json::Value &config) | ||||||
|     : ALabel(config, "network", id, DEFAULT_FORMAT, 60), |     : ALabel(config, "network", id, DEFAULT_FORMAT, 60), | ||||||
|       ifid_(-1), |       ifid_(-1), | ||||||
| @@ -106,17 +96,12 @@ waybar::modules::Network::Network(const std::string &id, const Json::Value &conf | |||||||
|   // the module start with no text, but the the event_box_ is shown. |   // the module start with no text, but the the event_box_ is shown. | ||||||
|   label_.set_markup("<s></s>"); |   label_.set_markup("<s></s>"); | ||||||
|  |  | ||||||
|   auto down_octets = read_netstat(BANDWIDTH_CATEGORY, BANDWIDTH_DOWN_TOTAL_KEY); |   auto bandwidth = readBandwidthUsage(); | ||||||
|   auto up_octets = read_netstat(BANDWIDTH_CATEGORY, BANDWIDTH_UP_TOTAL_KEY); |   if (bandwidth.has_value()) { | ||||||
|   if (down_octets) { |     bandwidth_down_total_ = (*bandwidth).first; | ||||||
|     bandwidth_down_total_ = *down_octets; |     bandwidth_up_total_ = (*bandwidth).second; | ||||||
|   } else { |   } else { | ||||||
|     bandwidth_down_total_ = 0; |     bandwidth_down_total_ = 0; | ||||||
|   } |  | ||||||
|  |  | ||||||
|   if (up_octets) { |  | ||||||
|     bandwidth_up_total_ = *up_octets; |  | ||||||
|   } else { |  | ||||||
|     bandwidth_up_total_ = 0; |     bandwidth_up_total_ = 0; | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -303,20 +288,21 @@ const std::string waybar::modules::Network::getNetworkState() const { | |||||||
| auto waybar::modules::Network::update() -> void { | auto waybar::modules::Network::update() -> void { | ||||||
|   std::lock_guard<std::mutex> lock(mutex_); |   std::lock_guard<std::mutex> lock(mutex_); | ||||||
|   std::string                 tooltip_format; |   std::string                 tooltip_format; | ||||||
|   auto down_octets = read_netstat(BANDWIDTH_CATEGORY, BANDWIDTH_DOWN_TOTAL_KEY); |  | ||||||
|   auto up_octets = read_netstat(BANDWIDTH_CATEGORY, BANDWIDTH_UP_TOTAL_KEY); |  | ||||||
|  |  | ||||||
|   unsigned long long bandwidth_down = 0; |   auto bandwidth = readBandwidthUsage(); | ||||||
|   if (down_octets) { |   auto bandwidth_down = 0ull; | ||||||
|     bandwidth_down = *down_octets - bandwidth_down_total_; |   auto bandwidth_up = 0ull; | ||||||
|     bandwidth_down_total_ = *down_octets; |   if (bandwidth.has_value()) { | ||||||
|  |     auto down_octets = (*bandwidth).first; | ||||||
|  |     auto up_octets = (*bandwidth).second; | ||||||
|  |  | ||||||
|  |     bandwidth_down = down_octets - bandwidth_down_total_; | ||||||
|  |     bandwidth_down_total_ = down_octets; | ||||||
|  |  | ||||||
|  |     bandwidth_up = up_octets - bandwidth_up_total_; | ||||||
|  |     bandwidth_up_total_ = up_octets; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   unsigned long long bandwidth_up = 0; |  | ||||||
|   if (up_octets) { |  | ||||||
|     bandwidth_up = *up_octets - bandwidth_up_total_; |  | ||||||
|     bandwidth_up_total_ = *up_octets; |  | ||||||
|   } |  | ||||||
|   if (!alt_) { |   if (!alt_) { | ||||||
|     auto state = getNetworkState(); |     auto state = getNetworkState(); | ||||||
|     if (!state_.empty() && label_.get_style_context()->has_class(state_)) { |     if (!state_.empty() && label_.get_style_context()->has_class(state_)) { | ||||||
| @@ -348,6 +334,7 @@ auto waybar::modules::Network::update() -> void { | |||||||
|       fmt::arg("ifname", ifname_), |       fmt::arg("ifname", ifname_), | ||||||
|       fmt::arg("netmask", netmask_), |       fmt::arg("netmask", netmask_), | ||||||
|       fmt::arg("ipaddr", ipaddr_), |       fmt::arg("ipaddr", ipaddr_), | ||||||
|  |       fmt::arg("gwaddr", gwaddr_), | ||||||
|       fmt::arg("cidr", cidr_), |       fmt::arg("cidr", cidr_), | ||||||
|       fmt::arg("frequency", frequency_), |       fmt::arg("frequency", frequency_), | ||||||
|       fmt::arg("icon", getIcon(signal_strength_, state_)), |       fmt::arg("icon", getIcon(signal_strength_, state_)), | ||||||
| @@ -376,6 +363,7 @@ auto waybar::modules::Network::update() -> void { | |||||||
|           fmt::arg("ifname", ifname_), |           fmt::arg("ifname", ifname_), | ||||||
|           fmt::arg("netmask", netmask_), |           fmt::arg("netmask", netmask_), | ||||||
|           fmt::arg("ipaddr", ipaddr_), |           fmt::arg("ipaddr", ipaddr_), | ||||||
|  |           fmt::arg("gwaddr", gwaddr_), | ||||||
|           fmt::arg("cidr", cidr_), |           fmt::arg("cidr", cidr_), | ||||||
|           fmt::arg("frequency", frequency_), |           fmt::arg("frequency", frequency_), | ||||||
|           fmt::arg("icon", getIcon(signal_strength_, state_)), |           fmt::arg("icon", getIcon(signal_strength_, state_)), | ||||||
| @@ -409,6 +397,7 @@ void waybar::modules::Network::clearIface() { | |||||||
|   ifname_.clear(); |   ifname_.clear(); | ||||||
|   essid_.clear(); |   essid_.clear(); | ||||||
|   ipaddr_.clear(); |   ipaddr_.clear(); | ||||||
|  |   gwaddr_.clear(); | ||||||
|   netmask_.clear(); |   netmask_.clear(); | ||||||
|   carrier_ = false; |   carrier_ = false; | ||||||
|   cidr_ = 0; |   cidr_ = 0; | ||||||
| @@ -466,7 +455,7 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     if (!is_del_event && ifi->ifi_index == net->ifid_) { |     if (!is_del_event && ifi->ifi_index == net->ifid_) { | ||||||
|       // Update inferface information |       // Update interface information | ||||||
|       if (net->ifname_.empty() && ifname != NULL) { |       if (net->ifname_.empty() && ifname != NULL) { | ||||||
|         std::string new_ifname (ifname, ifname_len); |         std::string new_ifname (ifname, ifname_len); | ||||||
|         net->ifname_ = new_ifname; |         net->ifname_ = new_ifname; | ||||||
| @@ -581,6 +570,7 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) { | |||||||
|     break; |     break; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |     char	temp_gw_addr[INET6_ADDRSTRLEN]; | ||||||
|   case RTM_DELROUTE: |   case RTM_DELROUTE: | ||||||
|     is_del_event = true; |     is_del_event = true; | ||||||
|   case RTM_NEWROUTE: { |   case RTM_NEWROUTE: { | ||||||
| @@ -595,6 +585,7 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) { | |||||||
|     int            temp_idx = -1; |     int            temp_idx = -1; | ||||||
|     uint32_t       priority = 0; |     uint32_t       priority = 0; | ||||||
|  |  | ||||||
|  |  | ||||||
|     /* Find the message(s) concerting the main routing table, each message |     /* Find the message(s) concerting the main routing table, each message | ||||||
|      * corresponds to a single routing table entry. |      * corresponds to a single routing table entry. | ||||||
|      */ |      */ | ||||||
| @@ -612,9 +603,10 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) { | |||||||
|       case RTA_GATEWAY: |       case RTA_GATEWAY: | ||||||
|         /* The gateway of the route. |         /* The gateway of the route. | ||||||
|          * |          * | ||||||
|          * If someone every needs to figure out the gateway address as well, |          * If someone ever needs to figure out the gateway address as well, | ||||||
|          * it's here as the attribute payload. |          * it's here as the attribute payload. | ||||||
|          */ |          */ | ||||||
|  | 	inet_ntop(net->family_, RTA_DATA(attr), temp_gw_addr, sizeof(temp_gw_addr)); | ||||||
|         has_gateway = true; |         has_gateway = true; | ||||||
|         break; |         break; | ||||||
|       case RTA_DST: { |       case RTA_DST: { | ||||||
| @@ -655,8 +647,8 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) { | |||||||
|         net->clearIface(); |         net->clearIface(); | ||||||
|         net->ifid_ = temp_idx; |         net->ifid_ = temp_idx; | ||||||
|         net->route_priority = priority; |         net->route_priority = priority; | ||||||
|  |         net->gwaddr_ = temp_gw_addr; | ||||||
|         spdlog::debug("network: new default route via if{} metric {}", temp_idx, priority); |         spdlog::debug("network: new default route via {} on if{} metric {}", temp_gw_addr, temp_idx, priority); | ||||||
|  |  | ||||||
|         /* Ask ifname associated with temp_idx as well as carrier status */ |         /* Ask ifname associated with temp_idx as well as carrier status */ | ||||||
|         struct ifinfomsg ifinfo_hdr = { |         struct ifinfomsg ifinfo_hdr = { | ||||||
|   | |||||||
| @@ -7,7 +7,6 @@ | |||||||
|  |  | ||||||
| #include "client.hpp" | #include "client.hpp" | ||||||
| #include "modules/river/tags.hpp" | #include "modules/river/tags.hpp" | ||||||
| #include "river-status-unstable-v1-client-protocol.h" |  | ||||||
| #include "xdg-output-unstable-v1-client-protocol.h" | #include "xdg-output-unstable-v1-client-protocol.h" | ||||||
|  |  | ||||||
| namespace waybar::modules::river { | namespace waybar::modules::river { | ||||||
| @@ -33,6 +32,23 @@ static const zriver_output_status_v1_listener output_status_listener_impl{ | |||||||
|     .urgent_tags = listen_urgent_tags, |     .urgent_tags = listen_urgent_tags, | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | static void listen_command_success(void *data, | ||||||
|  |                                    struct zriver_command_callback_v1 *zriver_command_callback_v1, | ||||||
|  |                                    const char *output) { | ||||||
|  |   // Do nothing but keep listener to avoid crashing when command was successful | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static void listen_command_failure(void *data, | ||||||
|  |                                    struct zriver_command_callback_v1 *zriver_command_callback_v1, | ||||||
|  |                                    const char *output) { | ||||||
|  |   spdlog::error("failure when selecting/toggling tags {}", output); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static const zriver_command_callback_v1_listener command_callback_listener_impl { | ||||||
|  |     .success = listen_command_success, | ||||||
|  |     .failure = listen_command_failure, | ||||||
|  | }; | ||||||
|  |  | ||||||
| static void handle_global(void *data, struct wl_registry *registry, uint32_t name, | static void handle_global(void *data, struct wl_registry *registry, uint32_t name, | ||||||
|                           const char *interface, uint32_t version) { |                           const char *interface, uint32_t version) { | ||||||
|   if (std::strcmp(interface, zriver_status_manager_v1_interface.name) == 0) { |   if (std::strcmp(interface, zriver_status_manager_v1_interface.name) == 0) { | ||||||
| @@ -43,18 +59,33 @@ static void handle_global(void *data, struct wl_registry *registry, uint32_t nam | |||||||
|     static_cast<Tags *>(data)->status_manager_ = static_cast<struct zriver_status_manager_v1 *>( |     static_cast<Tags *>(data)->status_manager_ = static_cast<struct zriver_status_manager_v1 *>( | ||||||
|         wl_registry_bind(registry, name, &zriver_status_manager_v1_interface, version)); |         wl_registry_bind(registry, name, &zriver_status_manager_v1_interface, version)); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   if (std::strcmp(interface, zriver_control_v1_interface.name) == 0) { | ||||||
|  |     version = std::min<uint32_t>(version, 1); | ||||||
|  |     static_cast<Tags *>(data)->control_ = static_cast<struct zriver_control_v1 *>( | ||||||
|  |         wl_registry_bind(registry, name, &zriver_control_v1_interface, version)); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (std::strcmp(interface, wl_seat_interface.name) == 0) { | ||||||
|  |     version = std::min<uint32_t>(version, 1); | ||||||
|  |     static_cast<Tags *>(data)->seat_ = static_cast<struct wl_seat *>( | ||||||
|  |         wl_registry_bind(registry, name, &wl_seat_interface, version)); | ||||||
|  |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| static void handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) { | static void handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) { | ||||||
|   /* Ignore event */ |   /* Ignore event */ | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| static const wl_registry_listener registry_listener_impl = {.global = handle_global, | static const wl_registry_listener registry_listener_impl = {.global = handle_global, | ||||||
|                                                             .global_remove = handle_global_remove}; |                                                             .global_remove = handle_global_remove}; | ||||||
|  |  | ||||||
| Tags::Tags(const std::string &id, const waybar::Bar &bar, const Json::Value &config) | Tags::Tags(const std::string &id, const waybar::Bar &bar, const Json::Value &config) | ||||||
|     : waybar::AModule(config, "tags", id, false, false), |     : waybar::AModule(config, "tags", id, false, false), | ||||||
|       status_manager_{nullptr}, |       status_manager_{nullptr}, | ||||||
|  |       control_{nullptr}, | ||||||
|  |       seat_{nullptr}, | ||||||
|       bar_(bar), |       bar_(bar), | ||||||
|       box_{bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0}, |       box_{bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0}, | ||||||
|       output_status_{nullptr} { |       output_status_{nullptr} { | ||||||
| @@ -68,6 +99,14 @@ Tags::Tags(const std::string &id, const waybar::Bar &bar, const Json::Value &con | |||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   if (!control_) { | ||||||
|  |     spdlog::error("river_control_v1 not advertised"); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (!seat_) { | ||||||
|  |     spdlog::error("wl_seat not advertised"); | ||||||
|  |   } | ||||||
|  |  | ||||||
|   box_.set_name("tags"); |   box_.set_name("tags"); | ||||||
|   if (!id.empty()) { |   if (!id.empty()) { | ||||||
|     box_.get_style_context()->add_class(id); |     box_.get_style_context()->add_class(id); | ||||||
| @@ -89,11 +128,17 @@ Tags::Tags(const std::string &id, const waybar::Bar &bar, const Json::Value &con | |||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   uint32_t i = 1; | ||||||
|   for (const auto &tag_label : tag_labels) { |   for (const auto &tag_label : tag_labels) { | ||||||
|     Gtk::Button &button = buttons_.emplace_back(tag_label); |     Gtk::Button &button = buttons_.emplace_back(tag_label); | ||||||
|     button.set_relief(Gtk::RELIEF_NONE); |     button.set_relief(Gtk::RELIEF_NONE); | ||||||
|     box_.pack_start(button, false, false, 0); |     box_.pack_start(button, false, false, 0); | ||||||
|  |     if (!config_["disable-click"].asBool()) { | ||||||
|  |       button.signal_clicked().connect(sigc::bind(sigc::mem_fun(*this, &Tags::handle_primary_clicked), i)); | ||||||
|  |       button.signal_button_press_event().connect(sigc::bind(sigc::mem_fun(*this, &Tags::handle_button_press), i)); | ||||||
|  |     } | ||||||
|     button.show(); |     button.show(); | ||||||
|  |     i <<= 1; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   struct wl_output *output = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj()); |   struct wl_output *output = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj()); | ||||||
| @@ -107,6 +152,31 @@ Tags::~Tags() { | |||||||
|   if (output_status_) { |   if (output_status_) { | ||||||
|     zriver_output_status_v1_destroy(output_status_); |     zriver_output_status_v1_destroy(output_status_); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   if (control_) { | ||||||
|  |     zriver_control_v1_destroy(control_); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void Tags::handle_primary_clicked(uint32_t tag) { | ||||||
|  |   // Send river command to select tag on left mouse click | ||||||
|  |   zriver_command_callback_v1 *callback; | ||||||
|  |   zriver_control_v1_add_argument(control_, "set-focused-tags"); | ||||||
|  |   zriver_control_v1_add_argument(control_, std::to_string(tag).c_str()); | ||||||
|  |   callback = zriver_control_v1_run_command(control_, seat_); | ||||||
|  |   zriver_command_callback_v1_add_listener(callback, &command_callback_listener_impl, nullptr); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool Tags::handle_button_press(GdkEventButton *event_button, uint32_t tag) { | ||||||
|  |   if (event_button->type == GDK_BUTTON_PRESS && event_button->button == 3) { | ||||||
|  |     // Send river command to toggle tag on right mouse click | ||||||
|  |     zriver_command_callback_v1 *callback; | ||||||
|  |     zriver_control_v1_add_argument(control_, "toggle-focused-tags"); | ||||||
|  |     zriver_control_v1_add_argument(control_, std::to_string(tag).c_str()); | ||||||
|  |     callback = zriver_control_v1_run_command(control_, seat_); | ||||||
|  |     zriver_command_callback_v1_add_listener(callback, &command_callback_listener_impl, nullptr); | ||||||
|  |   } | ||||||
|  |   return true; | ||||||
| } | } | ||||||
|  |  | ||||||
| void Tags::handle_focused_tags(uint32_t tags) { | void Tags::handle_focused_tags(uint32_t tags) { | ||||||
|   | |||||||
| @@ -62,6 +62,7 @@ Item::Item(const std::string& bn, const std::string& op, const Json::Value& conf | |||||||
|   event_box.signal_button_press_event().connect(sigc::mem_fun(*this, &Item::handleClick)); |   event_box.signal_button_press_event().connect(sigc::mem_fun(*this, &Item::handleClick)); | ||||||
|   event_box.signal_scroll_event().connect(sigc::mem_fun(*this, &Item::handleScroll)); |   event_box.signal_scroll_event().connect(sigc::mem_fun(*this, &Item::handleScroll)); | ||||||
|   // initial visibility |   // initial visibility | ||||||
|  |   event_box.show_all(); | ||||||
|   event_box.set_visible(show_passive_); |   event_box.set_visible(show_passive_); | ||||||
|  |  | ||||||
|   cancellable_ = Gio::Cancellable::create(); |   cancellable_ = Gio::Cancellable::create(); | ||||||
| @@ -287,7 +288,11 @@ Glib::RefPtr<Gdk::Pixbuf> Item::extractPixBuf(GVariant* variant) { | |||||||
|           if (array != nullptr) { |           if (array != nullptr) { | ||||||
|             g_free(array); |             g_free(array); | ||||||
|           } |           } | ||||||
|  | #if GLIB_MAJOR_VERSION >= 2 && GLIB_MINOR_VERSION >= 68 | ||||||
|  |           array = static_cast<guchar*>(g_memdup2(data, size)); | ||||||
|  | #else | ||||||
|           array = static_cast<guchar*>(g_memdup(data, size)); |           array = static_cast<guchar*>(g_memdup(data, size)); | ||||||
|  | #endif | ||||||
|           lwidth = width; |           lwidth = width; | ||||||
|           lheight = height; |           lheight = height; | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -35,11 +35,8 @@ void Tray::onRemove(std::unique_ptr<Item>& item) { | |||||||
| } | } | ||||||
|  |  | ||||||
| auto Tray::update() -> void { | auto Tray::update() -> void { | ||||||
|   if (box_.get_children().empty()) { |   // Show tray only when items are available | ||||||
|     box_.hide(); |   box_.set_visible(!box_.get_children().empty()); | ||||||
|   } else { |  | ||||||
|     box_.show_all(); |  | ||||||
|   } |  | ||||||
|   // Call parent update |   // Call parent update | ||||||
|   AModule::update(); |   AModule::update(); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -68,15 +68,22 @@ auto Window::update() -> void { | |||||||
|  |  | ||||||
| int leafNodesInWorkspace(const Json::Value& node) { | int leafNodesInWorkspace(const Json::Value& node) { | ||||||
|   auto const& nodes = node["nodes"]; |   auto const& nodes = node["nodes"]; | ||||||
|   if(nodes.empty()) { |   auto const& floating_nodes = node["floating_nodes"]; | ||||||
|  |   if(nodes.empty() && floating_nodes.empty()) { | ||||||
|     if(node["type"] == "workspace") |     if(node["type"] == "workspace") | ||||||
|       return 0; |       return 0; | ||||||
|     else |     else | ||||||
|       return 1; |       return 1; | ||||||
|   } |   } | ||||||
|   int sum = 0; |   int sum = 0; | ||||||
|  |   if (!nodes.empty()) { | ||||||
|     for(auto const& node : nodes) |     for(auto const& node : nodes) | ||||||
|       sum += leafNodesInWorkspace(node); |       sum += leafNodesInWorkspace(node); | ||||||
|  |   } | ||||||
|  |   if (!floating_nodes.empty()) { | ||||||
|  |     for(auto const& node : floating_nodes) | ||||||
|  |       sum += leafNodesInWorkspace(node); | ||||||
|  |   } | ||||||
|   return sum; |   return sum; | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -292,8 +292,7 @@ Task::Task(const waybar::Bar &bar, const Json::Value &config, Taskbar *tbar, | |||||||
|         struct zwlr_foreign_toplevel_handle_v1 *tl_handle, struct wl_seat *seat) : |         struct zwlr_foreign_toplevel_handle_v1 *tl_handle, struct wl_seat *seat) : | ||||||
|     bar_{bar}, config_{config}, tbar_{tbar}, handle_{tl_handle}, seat_{seat}, |     bar_{bar}, config_{config}, tbar_{tbar}, handle_{tl_handle}, seat_{seat}, | ||||||
|     id_{global_id++}, |     id_{global_id++}, | ||||||
|     content_{bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0}, |     content_{bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0} | ||||||
|     button_visible_{false}, ignored_{false} |  | ||||||
| { | { | ||||||
|     zwlr_foreign_toplevel_handle_v1_add_listener(handle_, &toplevel_handle_impl, this); |     zwlr_foreign_toplevel_handle_v1_add_listener(handle_, &toplevel_handle_impl, this); | ||||||
|  |  | ||||||
| @@ -395,13 +394,12 @@ std::string Task::state_string(bool shortened) const | |||||||
| void Task::handle_title(const char *title) | void Task::handle_title(const char *title) | ||||||
| { | { | ||||||
|     title_ = title; |     title_ = title; | ||||||
|  |     hide_if_ignored(); | ||||||
| } | } | ||||||
|  |  | ||||||
| void Task::handle_app_id(const char *app_id) | void Task::hide_if_ignored() | ||||||
| { | { | ||||||
|     app_id_ = app_id; |     if (tbar_->ignore_list().count(app_id_) || tbar_->ignore_list().count(title_)) { | ||||||
|  |  | ||||||
|     if (tbar_->ignore_list().count(app_id)) { |  | ||||||
|         ignored_ = true; |         ignored_ = true; | ||||||
|         if (button_visible_) { |         if (button_visible_) { | ||||||
|           auto output = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj()); |           auto output = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj()); | ||||||
| @@ -415,6 +413,12 @@ void Task::handle_app_id(const char *app_id) | |||||||
|           handle_output_enter(output); |           handle_output_enter(output); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void Task::handle_app_id(const char *app_id) | ||||||
|  | { | ||||||
|  |     app_id_ = app_id; | ||||||
|  |     hide_if_ignored(); | ||||||
|  |  | ||||||
| 	auto ids_replace_map = tbar_->app_ids_replace_map(); | 	auto ids_replace_map = tbar_->app_ids_replace_map(); | ||||||
| 	if (ids_replace_map.count(app_id_)) { | 	if (ids_replace_map.count(app_id_)) { | ||||||
| @@ -451,13 +455,13 @@ void Task::handle_app_id(const char *app_id) | |||||||
|  |  | ||||||
| void Task::handle_output_enter(struct wl_output *output) | void Task::handle_output_enter(struct wl_output *output) | ||||||
| { | { | ||||||
|     spdlog::debug("{} entered output {}", repr(), (void*)output); |  | ||||||
|  |  | ||||||
|     if (ignored_) { |     if (ignored_) { | ||||||
|       spdlog::debug("{} is ignored", repr()); |       spdlog::debug("{} is ignored", repr()); | ||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     spdlog::debug("{} entered output {}", repr(), (void*)output); | ||||||
|  |  | ||||||
|     if (!button_visible_ && (tbar_->all_outputs() || tbar_->show_output(output))) { |     if (!button_visible_ && (tbar_->all_outputs() || tbar_->show_output(output))) { | ||||||
|         /* The task entered the output of the current bar make the button visible */ |         /* The task entered the output of the current bar make the button visible */ | ||||||
|         tbar_->add_button(button_); |         tbar_->add_button(button_); | ||||||
|   | |||||||
							
								
								
									
										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 | ||||||
							
								
								
									
										12
									
								
								subprojects/catch2.wrap
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								subprojects/catch2.wrap
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | |||||||
|  | [wrap-file] | ||||||
|  | directory = Catch2-2.13.7 | ||||||
|  | source_url = https://github.com/catchorg/Catch2/archive/v2.13.7.zip | ||||||
|  | source_filename = Catch2-2.13.7.zip | ||||||
|  | source_hash = 3f3ccd90ad3a8fbb1beeb15e6db440ccdcbebe378dfd125d07a1f9a587a927e9 | ||||||
|  | patch_filename = catch2_2.13.7-1_patch.zip | ||||||
|  | patch_url = https://wrapdb.mesonbuild.com/v2/catch2_2.13.7-1/get_patch | ||||||
|  | patch_hash = 2f7369645d747e5bd866317ac1dd4c3d04dc97d3aad4fc6b864bdf75d3b57158 | ||||||
|  |  | ||||||
|  | [provide] | ||||||
|  | catch2 = catch2_dep | ||||||
|  |  | ||||||
							
								
								
									
										115
									
								
								test/config.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										115
									
								
								test/config.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,115 @@ | |||||||
|  | #define CATCH_CONFIG_MAIN | ||||||
|  | #include "config.hpp" | ||||||
|  |  | ||||||
|  | #include <catch2/catch.hpp> | ||||||
|  |  | ||||||
|  | TEST_CASE("Load simple config", "[config]") { | ||||||
|  |   waybar::Config conf; | ||||||
|  |   conf.load("test/config/simple.json"); | ||||||
|  |  | ||||||
|  |   SECTION("validate the config data") { | ||||||
|  |     auto& data = conf.getConfig(); | ||||||
|  |     REQUIRE(data["layer"].asString() == "top"); | ||||||
|  |     REQUIRE(data["height"].asInt() == 30); | ||||||
|  |   } | ||||||
|  |   SECTION("select configs for configured output") { | ||||||
|  |     auto configs = conf.getOutputConfigs("HDMI-0", "Fake HDMI output #0"); | ||||||
|  |     REQUIRE(configs.size() == 1); | ||||||
|  |   } | ||||||
|  |   SECTION("select configs for missing output") { | ||||||
|  |     auto configs = conf.getOutputConfigs("HDMI-1", "Fake HDMI output #1"); | ||||||
|  |     REQUIRE(configs.empty()); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | TEST_CASE("Load config with multiple bars", "[config]") { | ||||||
|  |   waybar::Config conf; | ||||||
|  |   conf.load("test/config/multi.json"); | ||||||
|  |  | ||||||
|  |   SECTION("select multiple configs #1") { | ||||||
|  |     auto data = conf.getOutputConfigs("DP-0", "Fake DisplayPort output #0"); | ||||||
|  |     REQUIRE(data.size() == 3); | ||||||
|  |     REQUIRE(data[0]["layer"].asString() == "bottom"); | ||||||
|  |     REQUIRE(data[0]["height"].asInt() == 20); | ||||||
|  |     REQUIRE(data[1]["layer"].asString() == "top"); | ||||||
|  |     REQUIRE(data[1]["position"].asString() == "bottom"); | ||||||
|  |     REQUIRE(data[1]["height"].asInt() == 21); | ||||||
|  |     REQUIRE(data[2]["layer"].asString() == "overlay"); | ||||||
|  |     REQUIRE(data[2]["position"].asString() == "right"); | ||||||
|  |     REQUIRE(data[2]["height"].asInt() == 23); | ||||||
|  |   } | ||||||
|  |   SECTION("select multiple configs #2") { | ||||||
|  |     auto data = conf.getOutputConfigs("HDMI-0", "Fake HDMI output #0"); | ||||||
|  |     REQUIRE(data.size() == 2); | ||||||
|  |     REQUIRE(data[0]["layer"].asString() == "bottom"); | ||||||
|  |     REQUIRE(data[0]["height"].asInt() == 20); | ||||||
|  |     REQUIRE(data[1]["layer"].asString() == "overlay"); | ||||||
|  |     REQUIRE(data[1]["position"].asString() == "right"); | ||||||
|  |     REQUIRE(data[1]["height"].asInt() == 23); | ||||||
|  |   } | ||||||
|  |   SECTION("select single config by output description") { | ||||||
|  |     auto data = conf.getOutputConfigs("HDMI-1", "Fake HDMI output #1"); | ||||||
|  |     REQUIRE(data.size() == 1); | ||||||
|  |     REQUIRE(data[0]["layer"].asString() == "overlay"); | ||||||
|  |     REQUIRE(data[0]["position"].asString() == "left"); | ||||||
|  |     REQUIRE(data[0]["height"].asInt() == 22); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | TEST_CASE("Load simple config with include", "[config]") { | ||||||
|  |   waybar::Config conf; | ||||||
|  |   conf.load("test/config/include.json"); | ||||||
|  |  | ||||||
|  |   SECTION("validate the config data") { | ||||||
|  |     auto& data = conf.getConfig(); | ||||||
|  |     // config override behavior: preserve first included value | ||||||
|  |     REQUIRE(data["layer"].asString() == "top"); | ||||||
|  |     REQUIRE(data["height"].asInt() == 30); | ||||||
|  |     // config override behavior: preserve value from the top config | ||||||
|  |     REQUIRE(data["position"].asString() == "top"); | ||||||
|  |     // config override behavior: explicit null is still a value and should be preserved | ||||||
|  |     REQUIRE((data.isMember("nullOption") && data["nullOption"].isNull())); | ||||||
|  |   } | ||||||
|  |   SECTION("select configs for configured output") { | ||||||
|  |     auto configs = conf.getOutputConfigs("HDMI-0", "Fake HDMI output #0"); | ||||||
|  |     REQUIRE(configs.size() == 1); | ||||||
|  |   } | ||||||
|  |   SECTION("select configs for missing output") { | ||||||
|  |     auto configs = conf.getOutputConfigs("HDMI-1", "Fake HDMI output #1"); | ||||||
|  |     REQUIRE(configs.empty()); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | TEST_CASE("Load multiple bar config with include", "[config]") { | ||||||
|  |   waybar::Config conf; | ||||||
|  |   conf.load("test/config/include-multi.json"); | ||||||
|  |  | ||||||
|  |   SECTION("bar config with sole include") { | ||||||
|  |     auto data = conf.getOutputConfigs("OUT-0", "Fake output #0"); | ||||||
|  |     REQUIRE(data.size() == 1); | ||||||
|  |     REQUIRE(data[0]["height"].asInt() == 20); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   SECTION("bar config with output and include") { | ||||||
|  |     auto data = conf.getOutputConfigs("OUT-1", "Fake output #1"); | ||||||
|  |     REQUIRE(data.size() == 1); | ||||||
|  |     REQUIRE(data[0]["height"].asInt() == 21); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   SECTION("bar config with output override") { | ||||||
|  |     auto data = conf.getOutputConfigs("OUT-2", "Fake output #2"); | ||||||
|  |     REQUIRE(data.size() == 1); | ||||||
|  |     REQUIRE(data[0]["height"].asInt() == 22); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   SECTION("multiple levels of include") { | ||||||
|  |     auto data = conf.getOutputConfigs("OUT-3", "Fake output #3"); | ||||||
|  |     REQUIRE(data.size() == 1); | ||||||
|  |     REQUIRE(data[0]["height"].asInt() == 23); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   auto& data = conf.getConfig(); | ||||||
|  |   REQUIRE(data.isArray()); | ||||||
|  |   REQUIRE(data.size() == 4); | ||||||
|  |   REQUIRE(data[0]["output"].asString() == "OUT-0"); | ||||||
|  | } | ||||||
							
								
								
									
										7
									
								
								test/config/include-1.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								test/config/include-1.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | |||||||
|  | { | ||||||
|  |   "layer": "top", | ||||||
|  |   "position": "bottom", | ||||||
|  |   "height": 30, | ||||||
|  |   "output": ["HDMI-0", "DP-0"], | ||||||
|  |   "nullOption": "not null" | ||||||
|  | } | ||||||
							
								
								
									
										3
									
								
								test/config/include-2.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								test/config/include-2.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | |||||||
|  | { | ||||||
|  |   "layer": "bottom" | ||||||
|  | } | ||||||
							
								
								
									
										4
									
								
								test/config/include-multi-0.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								test/config/include-multi-0.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | |||||||
|  | { | ||||||
|  |   "output": "OUT-0", | ||||||
|  |   "height": 20 | ||||||
|  | } | ||||||
							
								
								
									
										3
									
								
								test/config/include-multi-1.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								test/config/include-multi-1.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | |||||||
|  | { | ||||||
|  |   "height": 21 | ||||||
|  | } | ||||||
							
								
								
									
										4
									
								
								test/config/include-multi-2.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								test/config/include-multi-2.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | |||||||
|  | { | ||||||
|  |   "output": "OUT-1", | ||||||
|  |   "height": 22 | ||||||
|  | } | ||||||
							
								
								
									
										3
									
								
								test/config/include-multi-3-0.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								test/config/include-multi-3-0.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | |||||||
|  | { | ||||||
|  |   "height": 23 | ||||||
|  | } | ||||||
							
								
								
									
										4
									
								
								test/config/include-multi-3.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								test/config/include-multi-3.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | |||||||
|  | { | ||||||
|  |   "output": "OUT-3", | ||||||
|  |   "include": "test/config/include-multi-3-0.json" | ||||||
|  | } | ||||||
							
								
								
									
										16
									
								
								test/config/include-multi.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								test/config/include-multi.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | |||||||
|  | [ | ||||||
|  |   { | ||||||
|  |     "include": "test/config/include-multi-0.json" | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     "output": "OUT-1", | ||||||
|  |     "include": "test/config/include-multi-1.json" | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     "output": "OUT-2", | ||||||
|  |     "include": "test/config/include-multi-2.json" | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     "include": "test/config/include-multi-3.json" | ||||||
|  |   } | ||||||
|  | ] | ||||||
							
								
								
									
										5
									
								
								test/config/include.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								test/config/include.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | |||||||
|  | { | ||||||
|  |   "include": ["test/config/include-1.json", "test/config/include-2.json"], | ||||||
|  |   "position": "top", | ||||||
|  |   "nullOption": null | ||||||
|  | } | ||||||
							
								
								
									
										25
									
								
								test/config/multi.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								test/config/multi.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | |||||||
|  | [ | ||||||
|  |   { | ||||||
|  |     "layer": "bottom", | ||||||
|  |     "height": 20, | ||||||
|  |     "output": ["HDMI-0", "DP-0"] | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     "position": "bottom", | ||||||
|  |     "layer": "top", | ||||||
|  |     "height": 21, | ||||||
|  |     "output": ["DP-0"] | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     "position": "left", | ||||||
|  |     "layer": "overlay", | ||||||
|  |     "height": 22, | ||||||
|  |     "output": "Fake HDMI output #1" | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     "position": "right", | ||||||
|  |     "layer": "overlay", | ||||||
|  |     "height": 23, | ||||||
|  |     "output": "!HDMI-1" | ||||||
|  |   } | ||||||
|  | ] | ||||||
							
								
								
									
										5
									
								
								test/config/simple.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								test/config/simple.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | |||||||
|  | { | ||||||
|  |   "layer": "top", | ||||||
|  |   "height": 30, | ||||||
|  |   "output": ["HDMI-0", "DP-0"] | ||||||
|  | } | ||||||
							
								
								
									
										21
									
								
								test/meson.build
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								test/meson.build
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | |||||||
|  | test_inc = include_directories('../include') | ||||||
|  | test_dep = [ | ||||||
|  |     catch2, | ||||||
|  |     fmt, | ||||||
|  |     jsoncpp, | ||||||
|  |     spdlog, | ||||||
|  | ] | ||||||
|  |  | ||||||
|  | config_test = executable( | ||||||
|  |     'config_test', | ||||||
|  |     'config.cpp', | ||||||
|  |     '../src/config.cpp', | ||||||
|  |     dependencies: test_dep, | ||||||
|  |     include_directories: test_inc, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | test( | ||||||
|  |     'Configuration test', | ||||||
|  |     config_test, | ||||||
|  |     workdir: meson.source_root(), | ||||||
|  | ) | ||||||
		Reference in New Issue
	
	Block a user
	 dmitry
					dmitry