mirror of
				https://github.com/rad4day/Waybar.git
				synced 2025-10-31 07:52:42 +01:00 
			
		
		
		
	Merge pull request #2264 from Anakael/pr/anakael/hyprland-workspaces
feat: hyprland/workspaces
This commit is contained in:
		| @@ -31,6 +31,7 @@ | |||||||
| #include "modules/hyprland/language.hpp" | #include "modules/hyprland/language.hpp" | ||||||
| #include "modules/hyprland/submap.hpp" | #include "modules/hyprland/submap.hpp" | ||||||
| #include "modules/hyprland/window.hpp" | #include "modules/hyprland/window.hpp" | ||||||
|  | #include "modules/hyprland/workspaces.hpp" | ||||||
| #endif | #endif | ||||||
| #if defined(__FreeBSD__) || (defined(__linux__) && !defined(NO_FILESYSTEM)) | #if defined(__FreeBSD__) || (defined(__linux__) && !defined(NO_FILESYSTEM)) | ||||||
| #include "modules/battery.hpp" | #include "modules/battery.hpp" | ||||||
|   | |||||||
| @@ -5,6 +5,7 @@ | |||||||
| #include <mutex> | #include <mutex> | ||||||
| #include <string> | #include <string> | ||||||
| #include <thread> | #include <thread> | ||||||
|  | #include "util/json.hpp" | ||||||
|  |  | ||||||
| namespace waybar::modules::hyprland { | namespace waybar::modules::hyprland { | ||||||
|  |  | ||||||
| @@ -22,12 +23,14 @@ class IPC { | |||||||
|   void unregisterForIPC(EventHandler*); |   void unregisterForIPC(EventHandler*); | ||||||
|  |  | ||||||
|   std::string getSocket1Reply(const std::string& rq); |   std::string getSocket1Reply(const std::string& rq); | ||||||
|  |   Json::Value getSocket1JsonReply(const std::string& rq); | ||||||
|  |  | ||||||
|  private: |  private: | ||||||
|   void startIPC(); |   void startIPC(); | ||||||
|   void parseIPC(const std::string&); |   void parseIPC(const std::string&); | ||||||
|  |  | ||||||
|   std::mutex callbackMutex; |   std::mutex callbackMutex; | ||||||
|  |   util::JsonParser parser_; | ||||||
|   std::list<std::pair<std::string, EventHandler*>> callbacks; |   std::list<std::pair<std::string, EventHandler*>> callbacks; | ||||||
| }; | }; | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										58
									
								
								include/modules/hyprland/workspaces.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								include/modules/hyprland/workspaces.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,58 @@ | |||||||
|  | #include <gtkmm/button.h> | ||||||
|  | #include <gtkmm/label.h> | ||||||
|  |  | ||||||
|  | #include "AModule.hpp" | ||||||
|  | #include "bar.hpp" | ||||||
|  | #include "modules/hyprland/backend.hpp" | ||||||
|  |  | ||||||
|  | namespace waybar::modules::hyprland { | ||||||
|  |  | ||||||
|  | struct WorkspaceDto { | ||||||
|  |   int id; | ||||||
|  |  | ||||||
|  |   static WorkspaceDto parse(const Json::Value& value); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | class Workspace { | ||||||
|  |  public: | ||||||
|  |   Workspace(int id); | ||||||
|  |   Workspace(WorkspaceDto dto); | ||||||
|  |   int id() const { return id_; }; | ||||||
|  |   int active() const { return active_; }; | ||||||
|  |   std::string& select_icon(std::map<std::string, std::string>& icons_map); | ||||||
|  |   void set_active(bool value = true) { active_ = value; }; | ||||||
|  |   Gtk::Button& button() { return button_; }; | ||||||
|  |  | ||||||
|  |   void update(const std::string& format, const std::string& icon); | ||||||
|  |  | ||||||
|  |  private: | ||||||
|  |   int id_; | ||||||
|  |   bool active_; | ||||||
|  |  | ||||||
|  |   Gtk::Button button_; | ||||||
|  |   Gtk::Box content_; | ||||||
|  |   Gtk::Label label_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | class Workspaces : public AModule, public EventHandler { | ||||||
|  |  public: | ||||||
|  |   Workspaces(const std::string&, const waybar::Bar&, const Json::Value&); | ||||||
|  |   virtual ~Workspaces(); | ||||||
|  |   void update() override; | ||||||
|  |   void init(); | ||||||
|  |  | ||||||
|  |  private: | ||||||
|  |   void onEvent(const std::string&) override; | ||||||
|  |   void sort_workspaces(); | ||||||
|  |  | ||||||
|  |   std::string format_; | ||||||
|  |   std::map<std::string, std::string> icons_map_; | ||||||
|  |   bool with_icon_; | ||||||
|  |   int active_workspace_id; | ||||||
|  |   std::vector<Workspace> workspaces_; | ||||||
|  |   std::mutex mutex_; | ||||||
|  |   const Bar& bar_; | ||||||
|  |   Gtk::Box box_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace waybar::modules::hyprland | ||||||
							
								
								
									
										59
									
								
								man/waybar-hyprland-workspaces.5.scd
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								man/waybar-hyprland-workspaces.5.scd
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,59 @@ | |||||||
|  | waybar-wlr-workspaces(5) | ||||||
|  |  | ||||||
|  | # NAME | ||||||
|  |  | ||||||
|  | waybar - hyprland workspaces module | ||||||
|  |  | ||||||
|  | # DESCRIPTION | ||||||
|  |  | ||||||
|  | The *workspaces* module displays the currently used workspaces in hyprland compositor. | ||||||
|  |  | ||||||
|  | # CONFIGURATION | ||||||
|  |  | ||||||
|  | Addressed by *hyprland/workspaces* | ||||||
|  |  | ||||||
|  | *format*: ++ | ||||||
|  | 	typeof: string ++ | ||||||
|  | 	default: {id} ++ | ||||||
|  | 	The format, how information should be displayed. | ||||||
|  |  | ||||||
|  | *format-icons*: ++ | ||||||
|  | 	typeof: array ++ | ||||||
|  | 	Based on the workspace id and state, the corresponding icon gets selected. See *icons*. | ||||||
|  |  | ||||||
|  | # FORMAT REPLACEMENTS | ||||||
|  |  | ||||||
|  | *{id}*: id of workspace assigned by compositor | ||||||
|  |  | ||||||
|  | *{icon}*: Icon, as defined in *format-icons*. | ||||||
|  |  | ||||||
|  | # ICONS | ||||||
|  |  | ||||||
|  | Additional to workspace name matching, the following *format-icons* can be set. | ||||||
|  |  | ||||||
|  | - *default*: Will be shown, when no string match is found. | ||||||
|  | - *active*: Will be shown, when workspace is active | ||||||
|  |  | ||||||
|  | # EXAMPLES | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | "wlr/workspaces": { | ||||||
|  | 	"format": "{name}: {icon}", | ||||||
|  | 	"format-icons": { | ||||||
|  | 		"1": "", | ||||||
|  | 		"2": "", | ||||||
|  | 		"3": "", | ||||||
|  | 		"4": "", | ||||||
|  | 		"5": "", | ||||||
|  | 		"active": "", | ||||||
|  | 		"default": "" | ||||||
|  | 	}, | ||||||
|  | 	"sort-by-number": true | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | # Style | ||||||
|  |  | ||||||
|  | - *#workspaces* | ||||||
|  | - *#workspaces button* | ||||||
|  | - *#workspaces button.active* | ||||||
| @@ -65,7 +65,7 @@ Addressed by *wlr/workspaces* | |||||||
| Additional to workspace name matching, the following *format-icons* can be set. | Additional to workspace name matching, the following *format-icons* can be set. | ||||||
|  |  | ||||||
| - *default*: Will be shown, when no string match is found. | - *default*: Will be shown, when no string match is found. | ||||||
| - *focused*: Will be shown, when workspace is focused | - *active*: Will be shown, when workspace is active | ||||||
|  |  | ||||||
| # EXAMPLES | # EXAMPLES | ||||||
|  |  | ||||||
| @@ -78,7 +78,7 @@ Additional to workspace name matching, the following *format-icons* can be set. | |||||||
| 		"3": "", | 		"3": "", | ||||||
| 		"4": "", | 		"4": "", | ||||||
| 		"5": "", | 		"5": "", | ||||||
| 		"focused": "", | 		"active": "", | ||||||
| 		"default": "" | 		"default": "" | ||||||
| 	}, | 	}, | ||||||
| 	"sort-by-number": true | 	"sort-by-number": true | ||||||
|   | |||||||
| @@ -240,6 +240,7 @@ if true | |||||||
|     src_files += 'src/modules/hyprland/window.cpp' |     src_files += 'src/modules/hyprland/window.cpp' | ||||||
|     src_files += 'src/modules/hyprland/language.cpp' |     src_files += 'src/modules/hyprland/language.cpp' | ||||||
|     src_files += 'src/modules/hyprland/submap.cpp' |     src_files += 'src/modules/hyprland/submap.cpp' | ||||||
|  |     src_files += 'src/modules/hyprland/workspaces.cpp' | ||||||
| endif | endif | ||||||
|  |  | ||||||
| if libnl.found() and libnlgen.found() | if libnl.found() and libnlgen.found() | ||||||
|   | |||||||
| @@ -83,6 +83,9 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const { | |||||||
|     if (ref == "hyprland/submap") { |     if (ref == "hyprland/submap") { | ||||||
|       return new waybar::modules::hyprland::Submap(id, bar_, config_[name]); |       return new waybar::modules::hyprland::Submap(id, bar_, config_[name]); | ||||||
|     } |     } | ||||||
|  |     if (ref == "hyprland/workspaces") { | ||||||
|  |       return new waybar::modules::hyprland::Workspaces(id, bar_, config_[name]); | ||||||
|  |     } | ||||||
| #endif | #endif | ||||||
|     if (ref == "idle_inhibitor") { |     if (ref == "idle_inhibitor") { | ||||||
|       return new waybar::modules::IdleInhibitor(id, bar_, config_[name]); |       return new waybar::modules::IdleInhibitor(id, bar_, config_[name]); | ||||||
|   | |||||||
| @@ -198,4 +198,8 @@ std::string IPC::getSocket1Reply(const std::string& rq) { | |||||||
|   return response; |   return response; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | Json::Value IPC::getSocket1JsonReply(const std::string& rq) { | ||||||
|  |   return parser_.parse(getSocket1Reply("j/" + rq)); | ||||||
|  | } | ||||||
|  |  | ||||||
| }  // namespace waybar::modules::hyprland | }  // namespace waybar::modules::hyprland | ||||||
|   | |||||||
							
								
								
									
										167
									
								
								src/modules/hyprland/workspaces.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										167
									
								
								src/modules/hyprland/workspaces.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,167 @@ | |||||||
|  | #include "modules/hyprland/workspaces.hpp" | ||||||
|  |  | ||||||
|  | #include <json/value.h> | ||||||
|  | #include <spdlog/spdlog.h> | ||||||
|  |  | ||||||
|  | #include <algorithm> | ||||||
|  | #include <charconv> | ||||||
|  | #include <string> | ||||||
|  |  | ||||||
|  | namespace waybar::modules::hyprland { | ||||||
|  |  | ||||||
|  | Workspaces::Workspaces(const std::string &id, const Bar &bar, const Json::Value &config) | ||||||
|  |     : AModule(config, "workspaces", id, false, false), | ||||||
|  |       bar_(bar), | ||||||
|  |       box_(bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0) { | ||||||
|  |   Json::Value config_format = config["format"]; | ||||||
|  |  | ||||||
|  |   format_ = config_format.isString() ? config_format.asString() : "{id}"; | ||||||
|  |   with_icon_ = format_.find("{icon}") != std::string::npos; | ||||||
|  |  | ||||||
|  |   if (with_icon_ && icons_map_.empty()) { | ||||||
|  |     Json::Value format_icons = config["format-icons"]; | ||||||
|  |     for (std::string &name : format_icons.getMemberNames()) { | ||||||
|  |       icons_map_.emplace(name, format_icons[name].asString()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     icons_map_.emplace("", ""); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   box_.set_name("workspaces"); | ||||||
|  |   if (!id.empty()) { | ||||||
|  |     box_.get_style_context()->add_class(id); | ||||||
|  |   } | ||||||
|  |   event_box_.add(box_); | ||||||
|  |   modulesReady = true; | ||||||
|  |   if (!gIPC.get()) { | ||||||
|  |     gIPC = std::make_unique<IPC>(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   init(); | ||||||
|  |  | ||||||
|  |   gIPC->registerForIPC("workspace", this); | ||||||
|  |   gIPC->registerForIPC("createworkspace", this); | ||||||
|  |   gIPC->registerForIPC("destroyworkspace", this); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | auto Workspaces::update() -> void { | ||||||
|  |   std::lock_guard<std::mutex> lock(mutex_); | ||||||
|  |   for (Workspace &workspace : workspaces_) { | ||||||
|  |     workspace.set_active(workspace.id() == active_workspace_id); | ||||||
|  |  | ||||||
|  |     std::string &workspace_icon = icons_map_[""]; | ||||||
|  |     if (with_icon_) { | ||||||
|  |       workspace_icon = workspace.select_icon(icons_map_); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     workspace.update(format_, workspace_icon); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   AModule::update(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void Workspaces::onEvent(const std::string &ev) { | ||||||
|  |   std::string eventName(begin(ev), begin(ev) + ev.find_first_of('>')); | ||||||
|  |   std::string payload = ev.substr(eventName.size() + 2); | ||||||
|  |   if (eventName == "workspace") { | ||||||
|  |     std::from_chars(payload.data(), payload.data() + payload.size(), active_workspace_id); | ||||||
|  |   } else if (eventName == "destroyworkspace") { | ||||||
|  |     int deleted_workspace_id; | ||||||
|  |     std::from_chars(payload.data(), payload.data() + payload.size(), deleted_workspace_id); | ||||||
|  |     workspaces_.erase(std::remove_if(workspaces_.begin(), workspaces_.end(), | ||||||
|  |                                      [&](Workspace &x) { return x.id() == deleted_workspace_id; })); | ||||||
|  |   } else if (eventName == "createworkspace") { | ||||||
|  |     int new_workspace_id; | ||||||
|  |     std::from_chars(payload.data(), payload.data() + payload.size(), new_workspace_id); | ||||||
|  |     workspaces_.push_back(new_workspace_id); | ||||||
|  |     Gtk::Button &new_workspace_button = workspaces_.back().button(); | ||||||
|  |     box_.pack_end(new_workspace_button, false, false); | ||||||
|  |     sort_workspaces(); | ||||||
|  |     new_workspace_button.show_all(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   dp.emit(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void Workspaces::init() { | ||||||
|  |   const auto activeWorkspace = WorkspaceDto::parse(gIPC->getSocket1JsonReply("activeworkspace")); | ||||||
|  |   active_workspace_id = activeWorkspace.id; | ||||||
|  |   const Json::Value workspaces_json = gIPC->getSocket1JsonReply("workspaces"); | ||||||
|  |   for (const Json::Value &workspace_json : workspaces_json) { | ||||||
|  |     workspaces_.push_back(Workspace(WorkspaceDto::parse(workspace_json))); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   for (auto &workspace : workspaces_) { | ||||||
|  |     box_.pack_start(workspace.button(), false, false); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   sort_workspaces(); | ||||||
|  |  | ||||||
|  |   dp.emit(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | Workspaces::~Workspaces() { | ||||||
|  |   gIPC->unregisterForIPC(this); | ||||||
|  |   // wait for possible event handler to finish | ||||||
|  |   std::lock_guard<std::mutex> lg(mutex_); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | WorkspaceDto WorkspaceDto::parse(const Json::Value &value) { | ||||||
|  |   return WorkspaceDto{value["id"].asInt()}; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | Workspace::Workspace(WorkspaceDto dto) : Workspace(dto.id){}; | ||||||
|  |  | ||||||
|  | Workspace::Workspace(int id) : id_(id) { | ||||||
|  |   button_.set_relief(Gtk::RELIEF_NONE); | ||||||
|  |   content_.set_center_widget(label_); | ||||||
|  |   button_.add(content_); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | void 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); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void Workspace::update(const std::string &format, const std::string &icon) { | ||||||
|  |   Glib::RefPtr<Gtk::StyleContext> style_context = button_.get_style_context(); | ||||||
|  |   add_or_remove_class(style_context, active(), "active"); | ||||||
|  |  | ||||||
|  |   label_.set_markup( | ||||||
|  |       fmt::format(fmt::runtime(format), fmt::arg("id", id()), fmt::arg("icon", icon))); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void Workspaces::sort_workspaces() { | ||||||
|  |   std::sort(workspaces_.begin(), workspaces_.end(), | ||||||
|  |             [](Workspace &lhs, Workspace &rhs) { return lhs.id() < rhs.id(); }); | ||||||
|  |  | ||||||
|  |   for (size_t i = 0; i < workspaces_.size(); ++i) { | ||||||
|  |     box_.reorder_child(workspaces_[i].button(), i); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | std::string &Workspace::select_icon(std::map<std::string, std::string> &icons_map) { | ||||||
|  |   if (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(std::to_string(id())); | ||||||
|  |   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 icons_map[""]; | ||||||
|  | } | ||||||
|  | }  // namespace waybar::modules::hyprland | ||||||
		Reference in New Issue
	
	Block a user
	 Alexis Rouillard
					Alexis Rouillard