mirror of
				https://github.com/rad4day/Waybar.git
				synced 2025-10-30 23:42:42 +01:00 
			
		
		
		
	Merge pull request #2245 from gardenappl/main
hyprland/window: Add CSS customization similar to sway/window
This commit is contained in:
		| @@ -1,7 +1,5 @@ | |||||||
| #include <fmt/format.h> | #include <fmt/format.h> | ||||||
|  |  | ||||||
| #include <tuple> |  | ||||||
|  |  | ||||||
| #include "ALabel.hpp" | #include "ALabel.hpp" | ||||||
| #include "bar.hpp" | #include "bar.hpp" | ||||||
| #include "modules/hyprland/backend.hpp" | #include "modules/hyprland/backend.hpp" | ||||||
| @@ -17,15 +15,32 @@ class Window : public waybar::ALabel, public EventHandler { | |||||||
|   auto update() -> void override; |   auto update() -> void override; | ||||||
|  |  | ||||||
|  private: |  private: | ||||||
|   int getActiveWorkspaceID(std::string); |   struct Workspace { | ||||||
|   std::string getLastWindowTitle(int); |     int id; | ||||||
|  |     int windows; | ||||||
|  |     std::string last_window; | ||||||
|  |     std::string last_window_title; | ||||||
|  |  | ||||||
|  |     static auto parse(const Json::Value&) -> Workspace; | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   auto getActiveWorkspace(const std::string&) -> Workspace; | ||||||
|  |   auto getActiveWorkspace() -> Workspace; | ||||||
|   void onEvent(const std::string&) override; |   void onEvent(const std::string&) override; | ||||||
|  |   void queryActiveWorkspace(); | ||||||
|  |   void setClass(const std::string&, bool enable); | ||||||
|  |  | ||||||
|   bool separate_outputs; |   bool separate_outputs; | ||||||
|   std::mutex mutex_; |   std::mutex mutex_; | ||||||
|   const Bar& bar_; |   const Bar& bar_; | ||||||
|   util::JsonParser parser_; |   util::JsonParser parser_; | ||||||
|   std::string lastView; |   std::string last_title_; | ||||||
|  |   Workspace workspace_; | ||||||
|  |   std::string solo_class_; | ||||||
|  |   std::string last_solo_class_; | ||||||
|  |   bool solo_; | ||||||
|  |   bool all_floating_; | ||||||
|  |   bool fullscreen_; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| }  // namespace waybar::modules::hyprland | }  // namespace waybar::modules::hyprland | ||||||
|   | |||||||
| @@ -22,7 +22,7 @@ Language::Language(const std::string& id, const Bar& bar, const Json::Value& con | |||||||
|   initLanguage(); |   initLanguage(); | ||||||
|  |  | ||||||
|   label_.hide(); |   label_.hide(); | ||||||
|   ALabel::update(); |   update(); | ||||||
|  |  | ||||||
|   // register for hyprland ipc |   // register for hyprland ipc | ||||||
|   gIPC->registerForIPC("activelayout", this); |   gIPC->registerForIPC("activelayout", this); | ||||||
|   | |||||||
| @@ -2,30 +2,35 @@ | |||||||
|  |  | ||||||
| #include <spdlog/spdlog.h> | #include <spdlog/spdlog.h> | ||||||
|  |  | ||||||
|  | #include <algorithm> | ||||||
| #include <regex> | #include <regex> | ||||||
| #include <util/sanitize_str.hpp> | #include <vector> | ||||||
|  |  | ||||||
| #include "modules/hyprland/backend.hpp" | #include "modules/hyprland/backend.hpp" | ||||||
| #include "util/command.hpp" |  | ||||||
| #include "util/json.hpp" | #include "util/json.hpp" | ||||||
| #include "util/rewrite_string.hpp" | #include "util/rewrite_string.hpp" | ||||||
|  | #include <util/sanitize_str.hpp> | ||||||
|  |  | ||||||
| namespace waybar::modules::hyprland { | namespace waybar::modules::hyprland { | ||||||
|  |  | ||||||
| Window::Window(const std::string& id, const Bar& bar, const Json::Value& config) | Window::Window(const std::string& id, const Bar& bar, const Json::Value& config) | ||||||
|     : ALabel(config, "window", id, "{}", 0, true), bar_(bar) { |     : ALabel(config, "window", id, "{}", 0, true), bar_(bar) { | ||||||
|   modulesReady = true; |   modulesReady = true; | ||||||
|   separate_outputs = config["separate-outputs"].as<bool>(); |   separate_outputs = config["separate-outputs"].asBool(); | ||||||
|  |  | ||||||
|   if (!gIPC.get()) { |   if (!gIPC.get()) { | ||||||
|     gIPC = std::make_unique<IPC>(); |     gIPC = std::make_unique<IPC>(); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   label_.hide(); |   queryActiveWorkspace(); | ||||||
|   ALabel::update(); |   update(); | ||||||
|  |  | ||||||
|   // register for hyprland ipc |   // register for hyprland ipc | ||||||
|   gIPC->registerForIPC("activewindow", this); |   gIPC->registerForIPC("activewindow", this); | ||||||
|  |   gIPC->registerForIPC("closewindow", this); | ||||||
|  |   gIPC->registerForIPC("movewindow", this); | ||||||
|  |   gIPC->registerForIPC("changefloatingmode", this); | ||||||
|  |   gIPC->registerForIPC("fullscreen", this); | ||||||
| } | } | ||||||
|  |  | ||||||
| Window::~Window() { | Window::~Window() { | ||||||
| @@ -38,69 +43,145 @@ auto Window::update() -> void { | |||||||
|   // fix ampersands |   // fix ampersands | ||||||
|   std::lock_guard<std::mutex> lg(mutex_); |   std::lock_guard<std::mutex> lg(mutex_); | ||||||
|  |  | ||||||
|  |   std::string window_name = waybar::util::sanitize_string(workspace_.last_window_title); | ||||||
|  |  | ||||||
|  |   if (window_name != last_title_) { | ||||||
|  |     if (window_name.empty()) { | ||||||
|  |       label_.get_style_context()->add_class("empty"); | ||||||
|  |     } else { | ||||||
|  |       label_.get_style_context()->remove_class("empty"); | ||||||
|  |     } | ||||||
|  |     last_title_ = window_name; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |  | ||||||
|   if (!format_.empty()) { |   if (!format_.empty()) { | ||||||
|     label_.show(); |     label_.show(); | ||||||
|     label_.set_markup(fmt::format(fmt::runtime(format_), |     label_.set_markup(fmt::format(fmt::runtime(format_), | ||||||
|                                   waybar::util::rewriteString(lastView, config_["rewrite"]))); |                                   waybar::util::rewriteString(window_name, config_["rewrite"]))); | ||||||
|   } else { |   } else { | ||||||
|     label_.hide(); |     label_.hide(); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   setClass("empty", workspace_.windows == 0); | ||||||
|  |   setClass("solo", solo_); | ||||||
|  |   setClass("fullscreen", fullscreen_); | ||||||
|  |   setClass("floating", all_floating_); | ||||||
|  |  | ||||||
|  |   if (!last_solo_class_.empty() && solo_class_ != last_solo_class_) { | ||||||
|  |     if (bar_.window.get_style_context()->has_class(last_solo_class_)) { | ||||||
|  |       bar_.window.get_style_context()->remove_class(last_solo_class_); | ||||||
|  |       spdlog::trace("Removing solo class: {}", last_solo_class_); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (!solo_class_.empty() && solo_class_ != last_solo_class_) { | ||||||
|  |     bar_.window.get_style_context()->add_class(solo_class_); | ||||||
|  |     spdlog::trace("Adding solo class: {}", solo_class_); | ||||||
|  |   } | ||||||
|  |   last_solo_class_ = solo_class_; | ||||||
|  |  | ||||||
|   ALabel::update(); |   ALabel::update(); | ||||||
| } | } | ||||||
|  |  | ||||||
| int Window::getActiveWorkspaceID(std::string monitorName) { | auto Window::getActiveWorkspace() -> Workspace { | ||||||
|   auto cmd = waybar::util::command::exec("hyprctl monitors -j"); |   const auto workspace = gIPC->getSocket1Reply("j/activeworkspace"); | ||||||
|   assert(cmd.exit_code == 0); |   Json::Value json = parser_.parse(workspace); | ||||||
|   Json::Value json = parser_.parse(cmd.out); |   assert(json.isObject()); | ||||||
|  |   return Workspace::parse(json); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | auto Window::getActiveWorkspace(const std::string& monitorName) -> Workspace { | ||||||
|  |   const auto monitors = gIPC->getSocket1Reply("j/monitors"); | ||||||
|  |   Json::Value json = parser_.parse(monitors); | ||||||
|   assert(json.isArray()); |   assert(json.isArray()); | ||||||
|   auto monitor = std::find_if(json.begin(), json.end(), |   auto monitor = std::find_if(json.begin(), json.end(), | ||||||
|                               [&](Json::Value monitor) { return monitor["name"] == monitorName; }); |                               [&](Json::Value monitor) { return monitor["name"] == monitorName; }); | ||||||
|   if (monitor == std::end(json)) { |   if (monitor == std::end(json)) { | ||||||
|     return 0; |     spdlog::warn("Monitor not found: {}", monitorName); | ||||||
|  |     return Workspace{-1, 0, "", ""}; | ||||||
|   } |   } | ||||||
|   return (*monitor)["activeWorkspace"]["id"].as<int>(); |   const int id = (*monitor)["activeWorkspace"]["id"].asInt(); | ||||||
|  |  | ||||||
|  |   const auto workspaces = gIPC->getSocket1Reply("j/workspaces"); | ||||||
|  |   json = parser_.parse(workspaces); | ||||||
|  |   assert(json.isArray()); | ||||||
|  |   auto workspace = std::find_if(json.begin(), json.end(), | ||||||
|  |                                 [&](Json::Value workspace) { return workspace["id"] == id; }); | ||||||
|  |   if (workspace == std::end(json)) { | ||||||
|  |     spdlog::warn("No workspace with id {}", id); | ||||||
|  |     return Workspace{-1, 0, "", ""}; | ||||||
|  |   } | ||||||
|  |   return Workspace::parse(*workspace); | ||||||
| } | } | ||||||
|  |  | ||||||
| std::string Window::getLastWindowTitle(int workspaceID) { | auto Window::Workspace::parse(const Json::Value& value) -> Window::Workspace { | ||||||
|   auto cmd = waybar::util::command::exec("hyprctl workspaces -j"); |   return Workspace{ | ||||||
|   assert(cmd.exit_code == 0); |     value["id"].asInt(), | ||||||
|   Json::Value json = parser_.parse(cmd.out); |     value["windows"].asInt(), | ||||||
|   assert(json.isArray()); |     value["lastwindow"].asString(), | ||||||
|   auto workspace = std::find_if(json.begin(), json.end(), [&](Json::Value workspace) { |     value["lastwindowtitle"].asString() | ||||||
|     return workspace["id"].as<int>() == workspaceID; |   }; | ||||||
|   }); | } | ||||||
|  |  | ||||||
|   if (workspace == std::end(json)) { | void Window::queryActiveWorkspace() { | ||||||
|     return ""; |   std::lock_guard<std::mutex> lg(mutex_); | ||||||
|  |  | ||||||
|  |   if (separate_outputs) { | ||||||
|  |     workspace_ = getActiveWorkspace(this->bar_.output->name); | ||||||
|  |   } else { | ||||||
|  |     workspace_ = getActiveWorkspace(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   if (workspace_.windows > 0) { | ||||||
|  |     const auto clients = gIPC->getSocket1Reply("j/clients"); | ||||||
|  |     Json::Value json = parser_.parse(clients); | ||||||
|  |     assert(json.isArray()); | ||||||
|  |     auto active_window = std::find_if(json.begin(), json.end(), | ||||||
|  |                                       [&](Json::Value window) { return window["address"] == workspace_.last_window; }); | ||||||
|  |     if (active_window == std::end(json)) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (workspace_.windows == 1 && !(*active_window)["floating"].asBool()) { | ||||||
|  |       solo_class_ = (*active_window)["class"].asString(); | ||||||
|  |     } else { | ||||||
|  |       solo_class_ = ""; | ||||||
|  |     } | ||||||
|  |     std::vector<Json::Value> workspace_windows; | ||||||
|  |     std::copy_if(json.begin(), json.end(), std::back_inserter(workspace_windows), | ||||||
|  |                  [&](Json::Value window) { return window["workspace"]["id"] == workspace_.id && | ||||||
|  |                                                   window["mapped"].asBool(); }); | ||||||
|  |     solo_ = 1 == std::count_if(workspace_windows.begin(), workspace_windows.end(), | ||||||
|  |                                [&](Json::Value window) { return !window["floating"].asBool(); }); | ||||||
|  |     all_floating_ = std::all_of(workspace_windows.begin(), workspace_windows.end(), | ||||||
|  |                                 [&](Json::Value window) { return window["floating"].asBool(); }); | ||||||
|  |     fullscreen_ = (*active_window)["fullscreen"].asBool(); | ||||||
|  |   } else { | ||||||
|  |     solo_class_ = ""; | ||||||
|  |     solo_ = false; | ||||||
|  |     all_floating_ = false; | ||||||
|  |     fullscreen_ = false; | ||||||
|   } |   } | ||||||
|   return (*workspace)["lastwindowtitle"].as<std::string>(); |  | ||||||
| } | } | ||||||
|  |  | ||||||
| void Window::onEvent(const std::string& ev) { | void Window::onEvent(const std::string& ev) { | ||||||
|   std::lock_guard<std::mutex> lg(mutex_); |   queryActiveWorkspace(); | ||||||
|  |  | ||||||
|   std::string windowName; |  | ||||||
|   if (separate_outputs) { |  | ||||||
|     windowName = getLastWindowTitle(getActiveWorkspaceID(this->bar_.output->name)); |  | ||||||
|   } else { |  | ||||||
|     windowName = ev.substr(ev.find_first_of(',') + 1).substr(0, 256); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   windowName = waybar::util::sanitize_string(windowName); |  | ||||||
|  |  | ||||||
|   if (windowName == lastView) return; |  | ||||||
|  |  | ||||||
|   lastView = windowName; |  | ||||||
|  |  | ||||||
|   if (windowName.empty()) { |  | ||||||
|     label_.get_style_context()->add_class("empty"); |  | ||||||
|   } else { |  | ||||||
|     label_.get_style_context()->remove_class("empty"); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   spdlog::debug("hyprland window onevent with {}", windowName); |  | ||||||
|  |  | ||||||
|   dp.emit(); |   dp.emit(); | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | void Window::setClass(const std::string& classname, bool enable) { | ||||||
|  |   if (enable) { | ||||||
|  |     if (!bar_.window.get_style_context()->has_class(classname)) { | ||||||
|  |       bar_.window.get_style_context()->add_class(classname); | ||||||
|  |     } | ||||||
|  |   } else { | ||||||
|  |     bar_.window.get_style_context()->remove_class(classname); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
| }  // namespace waybar::modules::hyprland | }  // namespace waybar::modules::hyprland | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Alexis Rouillard
					Alexis Rouillard