diff --git a/meson.build b/meson.build index 102bb878..1af8e2b2 100644 --- a/meson.build +++ b/meson.build @@ -305,7 +305,9 @@ if true 'src/modules/hyprland/language.cpp', 'src/modules/hyprland/submap.cpp', 'src/modules/hyprland/window.cpp', + 'src/modules/hyprland/workspace.cpp', 'src/modules/hyprland/workspaces.cpp', + 'src/modules/hyprland/windowcreationpayload.cpp', ) man_files += files( 'man/waybar-hyprland-language.5.scd', diff --git a/src/modules/hyprland/windowcreationpayload.cpp b/src/modules/hyprland/windowcreationpayload.cpp new file mode 100644 index 00000000..261edcbc --- /dev/null +++ b/src/modules/hyprland/windowcreationpayload.cpp @@ -0,0 +1,110 @@ +#include "modules/hyprland/windowcreationpayload.hpp" + +#include +#include + +#include +#include +#include +#include +#include + +#include "modules/hyprland/workspaces.hpp" +#include "util/regex_collection.hpp" + +namespace waybar::modules::hyprland { +WindowCreationPayload::WindowCreationPayload(std::string workspace_name, + WindowAddress window_address, std::string window_repr) + : m_window(std::move(window_repr)), + m_windowAddress(std::move(window_address)), + m_workspaceName(std::move(workspace_name)) { + clearAddr(); + clearWorkspaceName(); +} + +WindowCreationPayload::WindowCreationPayload(std::string workspace_name, + WindowAddress window_address, std::string window_class, + std::string window_title) + : m_window(std::make_pair(std::move(window_class), std::move(window_title))), + m_windowAddress(std::move(window_address)), + m_workspaceName(std::move(workspace_name)) { + clearAddr(); + clearWorkspaceName(); +} + +WindowCreationPayload::WindowCreationPayload(Json::Value const &client_data) + : m_window(std::make_pair(client_data["class"].asString(), client_data["title"].asString())), + m_windowAddress(client_data["address"].asString()), + m_workspaceName(client_data["workspace"]["name"].asString()) { + clearAddr(); + clearWorkspaceName(); +} + +std::string WindowCreationPayload::repr(Workspaces &workspace_manager) { + if (std::holds_alternative(m_window)) { + return std::get(m_window); + } + if (std::holds_alternative(m_window)) { + auto [window_class, window_title] = std::get(m_window); + return workspace_manager.getRewrite(window_class, window_title); + } + // Unreachable + spdlog::error("WorkspaceWindow::repr: Unreachable"); + throw std::runtime_error("WorkspaceWindow::repr: Unreachable"); +} + +bool WindowCreationPayload::isEmpty(Workspaces &workspace_manager) { + if (std::holds_alternative(m_window)) { + return std::get(m_window).empty(); + } + if (std::holds_alternative(m_window)) { + auto [window_class, window_title] = std::get(m_window); + return (window_class.empty() && + (!workspace_manager.windowRewriteConfigUsesTitle() || window_title.empty())); + } + // Unreachable + spdlog::error("WorkspaceWindow::isEmpty: Unreachable"); + throw std::runtime_error("WorkspaceWindow::isEmpty: Unreachable"); +} + +int WindowCreationPayload::incrementTimeSpentUncreated() { return m_timeSpentUncreated++; } + +void WindowCreationPayload::clearAddr() { + // substr(2, ...) is necessary because Hyprland's JSON follows this format: + // 0x{ADDR} + // While Hyprland's IPC follows this format: + // {ADDR} + static const std::string ADDR_PREFIX = "0x"; + static const int ADDR_PREFIX_LEN = ADDR_PREFIX.length(); + + if (m_windowAddress.starts_with(ADDR_PREFIX)) { + m_windowAddress = + m_windowAddress.substr(ADDR_PREFIX_LEN, m_windowAddress.length() - ADDR_PREFIX_LEN); + } +} + +void WindowCreationPayload::clearWorkspaceName() { + // The workspace name may optionally feature "special:" at the beginning. + // If so, we need to remove it because the workspace is saved WITHOUT the + // special qualifier. The reasoning is that not all of Hyprland's IPC events + // use this qualifier, so it's better to be consistent about our uses. + + static const std::string SPECIAL_QUALIFIER_PREFIX = "special:"; + static const int SPECIAL_QUALIFIER_PREFIX_LEN = SPECIAL_QUALIFIER_PREFIX.length(); + + if (m_workspaceName.starts_with(SPECIAL_QUALIFIER_PREFIX)) { + m_workspaceName = m_workspaceName.substr( + SPECIAL_QUALIFIER_PREFIX_LEN, m_workspaceName.length() - SPECIAL_QUALIFIER_PREFIX_LEN); + } + + std::size_t spaceFound = m_workspaceName.find(' '); + if (spaceFound != std::string::npos) { + m_workspaceName.erase(m_workspaceName.begin() + spaceFound, m_workspaceName.end()); + } +} + +void WindowCreationPayload::moveToWorksace(std::string &new_workspace_name) { + m_workspaceName = new_workspace_name; +} + +} // namespace waybar::modules::hyprland diff --git a/src/modules/hyprland/workspace.cpp b/src/modules/hyprland/workspace.cpp new file mode 100644 index 00000000..1e3c05f3 --- /dev/null +++ b/src/modules/hyprland/workspace.cpp @@ -0,0 +1,213 @@ +#include +#include + +#include +#include +#include +#include +#include + +#include "modules/hyprland/workspaces.hpp" +#include "util/regex_collection.hpp" + +namespace waybar::modules::hyprland { + +void Workspace::initializeWindowMap(const Json::Value &clients_data) { + m_windowMap.clear(); + for (auto client : clients_data) { + if (client["workspace"]["id"].asInt() == id()) { + insertWindow({client}); + } + } +} + +void Workspace::insertWindow(WindowCreationPayload create_window_paylod) { + if (!create_window_paylod.isEmpty(m_workspaceManager)) { + m_windowMap[create_window_paylod.getAddress()] = create_window_paylod.repr(m_workspaceManager); + } +}; + +std::string Workspace::removeWindow(WindowAddress const &addr) { + std::string windowRepr = m_windowMap[addr]; + m_windowMap.erase(addr); + return windowRepr; +} + +bool Workspace::onWindowOpened(WindowCreationPayload const &create_window_paylod) { + if (create_window_paylod.getWorkspaceName() == name()) { + insertWindow(create_window_paylod); + return true; + } + return false; +} + +std::optional Workspace::closeWindow(WindowAddress const &addr) { + if (m_windowMap.contains(addr)) { + return removeWindow(addr); + } + return std::nullopt; +} + +Workspace::Workspace(const Json::Value &workspace_data, Workspaces &workspace_manager, + const Json::Value &clients_data) + : m_workspaceManager(workspace_manager), + m_id(workspace_data["id"].asInt()), + m_name(workspace_data["name"].asString()), + m_output(workspace_data["monitor"].asString()), // TODO:allow using monitor desc + m_windows(workspace_data["windows"].asInt()), + m_isActive(true), + m_isPersistentRule(workspace_data["persistent-rule"].asBool()), + m_isPersistentConfig(workspace_data["persistent-config"].asBool()) { + if (m_name.starts_with("name:")) { + m_name = m_name.substr(5); + } else if (m_name.starts_with("special")) { + m_name = m_id == -99 ? m_name : m_name.substr(8); + m_isSpecial = true; + } + + m_button.add_events(Gdk::BUTTON_PRESS_MASK); + m_button.signal_button_press_event().connect(sigc::mem_fun(*this, &Workspace::handleClicked), + false); + + m_button.set_relief(Gtk::RELIEF_NONE); + m_content.set_center_widget(m_label); + m_button.add(m_content); + + initializeWindowMap(clients_data); +} + +void addOrRemoveClass(const Glib::RefPtr &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) { + // clang-format off + if (this->m_workspaceManager.activeOnly() && \ + !this->isActive() && \ + !this->isPersistent() && \ + !this->isVisible() && \ + !this->isSpecial()) { + // clang-format on + // if activeOnly is true, hide if not active, persistent, visible or special + m_button.hide(); + return; + } + m_button.show(); + + auto styleContext = m_button.get_style_context(); + addOrRemoveClass(styleContext, isActive(), "active"); + addOrRemoveClass(styleContext, isSpecial(), "special"); + addOrRemoveClass(styleContext, isEmpty(), "empty"); + addOrRemoveClass(styleContext, isPersistent(), "persistent"); + addOrRemoveClass(styleContext, isUrgent(), "urgent"); + addOrRemoveClass(styleContext, isVisible(), "visible"); + addOrRemoveClass(styleContext, m_workspaceManager.getBarOutput() == output(), "hosting-monitor"); + + std::string windows; + auto windowSeparator = m_workspaceManager.getWindowSeparator(); + + bool isNotFirst = false; + + for (auto &[_pid, window_repr] : m_windowMap) { + if (isNotFirst) { + windows.append(windowSeparator); + } + isNotFirst = true; + windows.append(window_repr); + } + + m_label.set_markup(fmt::format(fmt::runtime(format), fmt::arg("id", id()), + fmt::arg("name", name()), fmt::arg("icon", icon), + fmt::arg("windows", windows))); +} + +std::string &Workspace::selectIcon(std::map &icons_map) { + spdlog::trace("Selecting icon for workspace {}", name()); + if (isUrgent()) { + auto urgentIconIt = icons_map.find("urgent"); + if (urgentIconIt != icons_map.end()) { + return urgentIconIt->second; + } + } + + if (isActive()) { + auto activeIconIt = icons_map.find("active"); + if (activeIconIt != icons_map.end()) { + return activeIconIt->second; + } + } + + if (isSpecial()) { + auto specialIconIt = icons_map.find("special"); + if (specialIconIt != icons_map.end()) { + return specialIconIt->second; + } + } + + auto namedIconIt = icons_map.find(name()); + if (namedIconIt != icons_map.end()) { + return namedIconIt->second; + } + + if (isVisible()) { + auto visibleIconIt = icons_map.find("visible"); + if (visibleIconIt != icons_map.end()) { + return visibleIconIt->second; + } + } + + if (isEmpty()) { + auto emptyIconIt = icons_map.find("empty"); + if (emptyIconIt != icons_map.end()) { + return emptyIconIt->second; + } + } + + if (isPersistent()) { + auto persistentIconIt = icons_map.find("persistent"); + if (persistentIconIt != icons_map.end()) { + return persistentIconIt->second; + } + } + + auto defaultIconIt = icons_map.find("default"); + if (defaultIconIt != icons_map.end()) { + return defaultIconIt->second; + } + + return m_name; +} + +bool Workspace::handleClicked(GdkEventButton *bt) const { + if (bt->type == GDK_BUTTON_PRESS) { + try { + if (id() > 0) { // normal + if (m_workspaceManager.moveToMonitor()) { + gIPC->getSocket1Reply("dispatch focusworkspaceoncurrentmonitor " + std::to_string(id())); + } else { + gIPC->getSocket1Reply("dispatch workspace " + std::to_string(id())); + } + } else if (!isSpecial()) { // named (this includes persistent) + if (m_workspaceManager.moveToMonitor()) { + gIPC->getSocket1Reply("dispatch focusworkspaceoncurrentmonitor name:" + name()); + } else { + gIPC->getSocket1Reply("dispatch workspace name:" + name()); + } + } else if (id() != -99) { // named special + gIPC->getSocket1Reply("dispatch togglespecialworkspace " + name()); + } else { // special + gIPC->getSocket1Reply("dispatch togglespecialworkspace"); + } + return true; + } catch (const std::exception &e) { + spdlog::error("Failed to dispatch workspace: {}", e.what()); + } + } + return false; +} +} // namespace waybar::modules::hyprland diff --git a/src/modules/hyprland/workspaces.cpp b/src/modules/hyprland/workspaces.cpp index b535794f..2074ad54 100644 --- a/src/modules/hyprland/workspaces.cpp +++ b/src/modules/hyprland/workspaces.cpp @@ -589,42 +589,6 @@ void Workspaces::updateWindowCount() { } } -void Workspace::initializeWindowMap(const Json::Value &clients_data) { - m_windowMap.clear(); - for (auto client : clients_data) { - if (client["workspace"]["id"].asInt() == id()) { - insertWindow({client}); - } - } -} - -void Workspace::insertWindow(WindowCreationPayload create_window_paylod) { - if (!create_window_paylod.isEmpty(m_workspaceManager)) { - m_windowMap[create_window_paylod.getAddress()] = create_window_paylod.repr(m_workspaceManager); - } -}; - -std::string Workspace::removeWindow(WindowAddress const &addr) { - std::string windowRepr = m_windowMap[addr]; - m_windowMap.erase(addr); - return windowRepr; -} - -bool Workspace::onWindowOpened(WindowCreationPayload const &create_window_paylod) { - if (create_window_paylod.getWorkspaceName() == name()) { - insertWindow(create_window_paylod); - return true; - } - return false; -} - -std::optional Workspace::closeWindow(WindowAddress const &addr) { - if (m_windowMap.contains(addr)) { - return removeWindow(addr); - } - return std::nullopt; -} - void Workspaces::createWorkspace(Json::Value const &workspace_data, Json::Value const &clients_data) { auto workspaceName = workspace_data["name"].asString(); @@ -857,84 +821,6 @@ Workspaces::~Workspaces() { std::lock_guard lg(m_mutex); } -Workspace::Workspace(const Json::Value &workspace_data, Workspaces &workspace_manager, - const Json::Value &clients_data) - : m_workspaceManager(workspace_manager), - m_id(workspace_data["id"].asInt()), - m_name(workspace_data["name"].asString()), - m_output(workspace_data["monitor"].asString()), // TODO:allow using monitor desc - m_windows(workspace_data["windows"].asInt()), - m_isActive(true), - m_isPersistentRule(workspace_data["persistent-rule"].asBool()), - m_isPersistentConfig(workspace_data["persistent-config"].asBool()) { - if (m_name.starts_with("name:")) { - m_name = m_name.substr(5); - } else if (m_name.starts_with("special")) { - m_name = m_id == -99 ? m_name : m_name.substr(8); - m_isSpecial = true; - } - - m_button.add_events(Gdk::BUTTON_PRESS_MASK); - m_button.signal_button_press_event().connect(sigc::mem_fun(*this, &Workspace::handleClicked), - false); - - m_button.set_relief(Gtk::RELIEF_NONE); - m_content.set_center_widget(m_label); - m_button.add(m_content); - - initializeWindowMap(clients_data); -} - -void addOrRemoveClass(const Glib::RefPtr &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) { - // clang-format off - if (this->m_workspaceManager.activeOnly() && \ - !this->isActive() && \ - !this->isPersistent() && \ - !this->isVisible() && \ - !this->isSpecial()) { - // clang-format on - // if activeOnly is true, hide if not active, persistent, visible or special - m_button.hide(); - return; - } - m_button.show(); - - auto styleContext = m_button.get_style_context(); - addOrRemoveClass(styleContext, isActive(), "active"); - addOrRemoveClass(styleContext, isSpecial(), "special"); - addOrRemoveClass(styleContext, isEmpty(), "empty"); - addOrRemoveClass(styleContext, isPersistent(), "persistent"); - addOrRemoveClass(styleContext, isUrgent(), "urgent"); - addOrRemoveClass(styleContext, isVisible(), "visible"); - addOrRemoveClass(styleContext, m_workspaceManager.getBarOutput() == output(), "hosting-monitor"); - - std::string windows; - auto windowSeparator = m_workspaceManager.getWindowSeparator(); - - bool isNotFirst = false; - - for (auto &[_pid, window_repr] : m_windowMap) { - if (isNotFirst) { - windows.append(windowSeparator); - } - isNotFirst = true; - windows.append(window_repr); - } - - m_label.set_markup(fmt::format(fmt::runtime(format), fmt::arg("id", id()), - fmt::arg("name", name()), fmt::arg("icon", icon), - fmt::arg("windows", windows))); -} - void Workspaces::sortWorkspaces() { std::sort(m_workspaces.begin(), m_workspaces.end(), [&](std::unique_ptr &a, std::unique_ptr &b) { @@ -998,91 +884,6 @@ void Workspaces::sortWorkspaces() { } } -std::string &Workspace::selectIcon(std::map &icons_map) { - spdlog::trace("Selecting icon for workspace {}", name()); - if (isUrgent()) { - auto urgentIconIt = icons_map.find("urgent"); - if (urgentIconIt != icons_map.end()) { - return urgentIconIt->second; - } - } - - if (isActive()) { - auto activeIconIt = icons_map.find("active"); - if (activeIconIt != icons_map.end()) { - return activeIconIt->second; - } - } - - if (isSpecial()) { - auto specialIconIt = icons_map.find("special"); - if (specialIconIt != icons_map.end()) { - return specialIconIt->second; - } - } - - auto namedIconIt = icons_map.find(name()); - if (namedIconIt != icons_map.end()) { - return namedIconIt->second; - } - - if (isVisible()) { - auto visibleIconIt = icons_map.find("visible"); - if (visibleIconIt != icons_map.end()) { - return visibleIconIt->second; - } - } - - if (isEmpty()) { - auto emptyIconIt = icons_map.find("empty"); - if (emptyIconIt != icons_map.end()) { - return emptyIconIt->second; - } - } - - if (isPersistent()) { - auto persistentIconIt = icons_map.find("persistent"); - if (persistentIconIt != icons_map.end()) { - return persistentIconIt->second; - } - } - - auto defaultIconIt = icons_map.find("default"); - if (defaultIconIt != icons_map.end()) { - return defaultIconIt->second; - } - - return m_name; -} - -bool Workspace::handleClicked(GdkEventButton *bt) const { - if (bt->type == GDK_BUTTON_PRESS) { - try { - if (id() > 0) { // normal - if (m_workspaceManager.moveToMonitor()) { - gIPC->getSocket1Reply("dispatch focusworkspaceoncurrentmonitor " + std::to_string(id())); - } else { - gIPC->getSocket1Reply("dispatch workspace " + std::to_string(id())); - } - } else if (!isSpecial()) { // named (this includes persistent) - if (m_workspaceManager.moveToMonitor()) { - gIPC->getSocket1Reply("dispatch focusworkspaceoncurrentmonitor name:" + name()); - } else { - gIPC->getSocket1Reply("dispatch workspace name:" + name()); - } - } else if (id() != -99) { // named special - gIPC->getSocket1Reply("dispatch togglespecialworkspace " + name()); - } else { // special - gIPC->getSocket1Reply("dispatch togglespecialworkspace"); - } - return true; - } catch (const std::exception &e) { - spdlog::error("Failed to dispatch workspace: {}", e.what()); - } - } - return false; -} - void Workspaces::setUrgentWorkspace(std::string const &windowaddress) { const Json::Value clientsJson = gIPC->getSocket1JsonReply("clients"); int workspaceId = -1; @@ -1113,99 +914,4 @@ std::string Workspaces::getRewrite(std::string window_class, std::string window_ return fmt::format(fmt::runtime(rewriteRule), fmt::arg("class", window_class), fmt::arg("title", window_title)); } - -WindowCreationPayload::WindowCreationPayload(std::string workspace_name, - WindowAddress window_address, std::string window_repr) - : m_window(std::move(window_repr)), - m_windowAddress(std::move(window_address)), - m_workspaceName(std::move(workspace_name)) { - clearAddr(); - clearWorkspaceName(); -} - -WindowCreationPayload::WindowCreationPayload(std::string workspace_name, - WindowAddress window_address, std::string window_class, - std::string window_title) - : m_window(std::make_pair(std::move(window_class), std::move(window_title))), - m_windowAddress(std::move(window_address)), - m_workspaceName(std::move(workspace_name)) { - clearAddr(); - clearWorkspaceName(); -} - -WindowCreationPayload::WindowCreationPayload(Json::Value const &client_data) - : m_window(std::make_pair(client_data["class"].asString(), client_data["title"].asString())), - m_windowAddress(client_data["address"].asString()), - m_workspaceName(client_data["workspace"]["name"].asString()) { - clearAddr(); - clearWorkspaceName(); -} - -std::string WindowCreationPayload::repr(Workspaces &workspace_manager) { - if (std::holds_alternative(m_window)) { - return std::get(m_window); - } - if (std::holds_alternative(m_window)) { - auto [window_class, window_title] = std::get(m_window); - return workspace_manager.getRewrite(window_class, window_title); - } - // Unreachable - spdlog::error("WorkspaceWindow::repr: Unreachable"); - throw std::runtime_error("WorkspaceWindow::repr: Unreachable"); -} - -bool WindowCreationPayload::isEmpty(Workspaces &workspace_manager) { - if (std::holds_alternative(m_window)) { - return std::get(m_window).empty(); - } - if (std::holds_alternative(m_window)) { - auto [window_class, window_title] = std::get(m_window); - return (window_class.empty() && - (!workspace_manager.windowRewriteConfigUsesTitle() || window_title.empty())); - } - // Unreachable - spdlog::error("WorkspaceWindow::isEmpty: Unreachable"); - throw std::runtime_error("WorkspaceWindow::isEmpty: Unreachable"); -} - -int WindowCreationPayload::incrementTimeSpentUncreated() { return m_timeSpentUncreated++; } - -void WindowCreationPayload::clearAddr() { - // substr(2, ...) is necessary because Hyprland's JSON follows this format: - // 0x{ADDR} - // While Hyprland's IPC follows this format: - // {ADDR} - static const std::string ADDR_PREFIX = "0x"; - static const int ADDR_PREFIX_LEN = ADDR_PREFIX.length(); - - if (m_windowAddress.starts_with(ADDR_PREFIX)) { - m_windowAddress = - m_windowAddress.substr(ADDR_PREFIX_LEN, m_windowAddress.length() - ADDR_PREFIX_LEN); - } -} - -void WindowCreationPayload::clearWorkspaceName() { - // The workspace name may optionally feature "special:" at the beginning. - // If so, we need to remove it because the workspace is saved WITHOUT the - // special qualifier. The reasoning is that not all of Hyprland's IPC events - // use this qualifier, so it's better to be consistent about our uses. - - static const std::string SPECIAL_QUALIFIER_PREFIX = "special:"; - static const int SPECIAL_QUALIFIER_PREFIX_LEN = SPECIAL_QUALIFIER_PREFIX.length(); - - if (m_workspaceName.starts_with(SPECIAL_QUALIFIER_PREFIX)) { - m_workspaceName = m_workspaceName.substr( - SPECIAL_QUALIFIER_PREFIX_LEN, m_workspaceName.length() - SPECIAL_QUALIFIER_PREFIX_LEN); - } - - std::size_t spaceFound = m_workspaceName.find(' '); - if (spaceFound != std::string::npos) { - m_workspaceName.erase(m_workspaceName.begin() + spaceFound, m_workspaceName.end()); - } -} - -void WindowCreationPayload::moveToWorksace(std::string &new_workspace_name) { - m_workspaceName = new_workspace_name; -} - } // namespace waybar::modules::hyprland