hyprland/workspaces: sort methods
This commit is contained in:
		
							parent
							
								
									9ba9d57c8c
								
							
						
					
					
						commit
						f5bb086460
					
				| 
						 | 
					@ -54,6 +54,8 @@ class Workspaces : public AModule, public EventHandler {
 | 
				
			||||||
  void sortWorkspaces();
 | 
					  void sortWorkspaces();
 | 
				
			||||||
  void createWorkspace(Json::Value const& workspaceData,
 | 
					  void createWorkspace(Json::Value const& workspaceData,
 | 
				
			||||||
                       Json::Value const& clientsData = Json::Value::nullRef);
 | 
					                       Json::Value const& clientsData = Json::Value::nullRef);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Json::Value createMonitorWorkspaceData(std::string const& name, std::string const& monitor);
 | 
				
			||||||
  void removeWorkspace(std::string const& name);
 | 
					  void removeWorkspace(std::string const& name);
 | 
				
			||||||
  void setUrgentWorkspace(std::string const& windowaddress);
 | 
					  void setUrgentWorkspace(std::string const& windowaddress);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -13,26 +13,6 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace waybar::modules::hyprland {
 | 
					namespace waybar::modules::hyprland {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
int Workspaces::windowRewritePriorityFunction(std::string const &window_rule) {
 | 
					 | 
				
			||||||
  // Rules that match against title are prioritized
 | 
					 | 
				
			||||||
  // Rules that don't specify if they're matching against either title or class are deprioritized
 | 
					 | 
				
			||||||
  bool const hasTitle = window_rule.find("title") != std::string::npos;
 | 
					 | 
				
			||||||
  bool const hasClass = window_rule.find("class") != std::string::npos;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (hasTitle && hasClass) {
 | 
					 | 
				
			||||||
    m_anyWindowRewriteRuleUsesTitle = true;
 | 
					 | 
				
			||||||
    return 3;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  if (hasTitle) {
 | 
					 | 
				
			||||||
    m_anyWindowRewriteRuleUsesTitle = true;
 | 
					 | 
				
			||||||
    return 2;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  if (hasClass) {
 | 
					 | 
				
			||||||
    return 1;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  return 0;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Workspaces::Workspaces(const std::string &id, const Bar &bar, const Json::Value &config)
 | 
					Workspaces::Workspaces(const std::string &id, const Bar &bar, const Json::Value &config)
 | 
				
			||||||
    : AModule(config, "workspaces", id, false, false), m_bar(bar), m_box(bar.orientation, 0) {
 | 
					    : AModule(config, "workspaces", id, false, false), m_bar(bar), m_box(bar.orientation, 0) {
 | 
				
			||||||
  modulesReady = true;
 | 
					  modulesReady = true;
 | 
				
			||||||
| 
						 | 
					@ -54,132 +34,87 @@ Workspaces::Workspaces(const std::string &id, const Bar &bar, const Json::Value
 | 
				
			||||||
  registerIpc();
 | 
					  registerIpc();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
auto Workspaces::parseConfig(const Json::Value &config) -> void {
 | 
					Workspaces::~Workspaces() {
 | 
				
			||||||
  const auto &configFormat = config["format"];
 | 
					  gIPC->unregisterForIPC(this);
 | 
				
			||||||
  m_format = configFormat.isString() ? configFormat.asString() : "{name}";
 | 
					  // wait for possible event handler to finish
 | 
				
			||||||
  m_withIcon = m_format.find("{icon}") != std::string::npos;
 | 
					  std::lock_guard<std::mutex> lg(m_mutex);
 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (m_withIcon && m_iconsMap.empty()) {
 | 
					 | 
				
			||||||
    populateIconsMap(config["format-icons"]);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  populateBoolConfig(config, "all-outputs", m_allOutputs);
 | 
					 | 
				
			||||||
  populateBoolConfig(config, "show-special", m_showSpecial);
 | 
					 | 
				
			||||||
  populateBoolConfig(config, "active-only", m_activeOnly);
 | 
					 | 
				
			||||||
  populateBoolConfig(config, "move-to-monitor", m_moveToMonitor);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  populateSortByConfig(config);
 | 
					 | 
				
			||||||
  populateIgnoreWorkspacesConfig(config);
 | 
					 | 
				
			||||||
  populatePersistentWorkspacesConfig(config);
 | 
					 | 
				
			||||||
  populateFormatWindowSeparatorConfig(config);
 | 
					 | 
				
			||||||
  populateWindowRewriteConfig(config);
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
auto Workspaces::populateIconsMap(const Json::Value &formatIcons) -> void {
 | 
					void Workspaces::init() {
 | 
				
			||||||
  for (const auto &name : formatIcons.getMemberNames()) {
 | 
					  m_activeWorkspaceName = (gIPC->getSocket1JsonReply("activeworkspace"))["name"].asString();
 | 
				
			||||||
    m_iconsMap.emplace(name, formatIcons[name].asString());
 | 
					
 | 
				
			||||||
  }
 | 
					  initializeWorkspaces();
 | 
				
			||||||
  m_iconsMap.emplace("", "");
 | 
					  dp.emit();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
auto Workspaces::populateBoolConfig(const Json::Value &config, const std::string &key, bool &member)
 | 
					Json::Value Workspaces::createMonitorWorkspaceData(std::string const &name,
 | 
				
			||||||
    -> void {
 | 
					                                                   std::string const &monitor) {
 | 
				
			||||||
  auto configValue = config[key];
 | 
					  spdlog::trace("Creating persistent workspace: {} on monitor {}", name, monitor);
 | 
				
			||||||
  if (configValue.isBool()) {
 | 
					  Json::Value workspaceData;
 | 
				
			||||||
    member = configValue.asBool();
 | 
					  try {
 | 
				
			||||||
 | 
					    // numbered persistent workspaces get the name as ID
 | 
				
			||||||
 | 
					    workspaceData["id"] = name == "special" ? -99 : std::stoi(name);
 | 
				
			||||||
 | 
					  } catch (const std::exception &e) {
 | 
				
			||||||
 | 
					    // named persistent workspaces start with ID=0
 | 
				
			||||||
 | 
					    workspaceData["id"] = 0;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  workspaceData["name"] = name;
 | 
				
			||||||
 | 
					  workspaceData["monitor"] = monitor;
 | 
				
			||||||
 | 
					  workspaceData["windows"] = 0;
 | 
				
			||||||
 | 
					  return workspaceData;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
auto Workspaces::populateSortByConfig(const Json::Value &config) -> void {
 | 
					void Workspaces::createWorkspace(Json::Value const &workspace_data,
 | 
				
			||||||
  auto configSortBy = config["sort-by"];
 | 
					                                 Json::Value const &clients_data) {
 | 
				
			||||||
  if (configSortBy.isString()) {
 | 
					  auto workspaceName = workspace_data["name"].asString();
 | 
				
			||||||
    auto sortByStr = configSortBy.asString();
 | 
					  spdlog::debug("Creating workspace {}", workspaceName);
 | 
				
			||||||
    try {
 | 
					
 | 
				
			||||||
      m_sortBy = m_enumParser.parseStringToEnum(sortByStr, m_sortMap);
 | 
					  // avoid recreating existing workspaces
 | 
				
			||||||
    } catch (const std::invalid_argument &e) {
 | 
					  auto workspace = std::find_if(
 | 
				
			||||||
      m_sortBy = SortMethod::DEFAULT;
 | 
					      m_workspaces.begin(), m_workspaces.end(),
 | 
				
			||||||
      spdlog::warn(
 | 
					      [workspaceName](std::unique_ptr<Workspace> const &w) {
 | 
				
			||||||
          "Invalid string representation for sort-by. Falling back to default sort method.");
 | 
					        return (workspaceName.starts_with("special:") && workspaceName.substr(8) == w->name()) ||
 | 
				
			||||||
 | 
					               workspaceName == w->name();
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (workspace != m_workspaces.end()) {
 | 
				
			||||||
 | 
					    // don't recreate workspace, but update persistency if necessary
 | 
				
			||||||
 | 
					    const auto keys = workspace_data.getMemberNames();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const auto *k = "persistent-rule";
 | 
				
			||||||
 | 
					    if (std::find(keys.begin(), keys.end(), k) != keys.end()) {
 | 
				
			||||||
 | 
					      spdlog::debug("Set dynamic persistency of workspace {} to: {}", workspaceName,
 | 
				
			||||||
 | 
					                    workspace_data[k].asBool() ? "true" : "false");
 | 
				
			||||||
 | 
					      (*workspace)->setPersistentRule(workspace_data[k].asBool());
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
auto Workspaces::populateIgnoreWorkspacesConfig(const Json::Value &config) -> void {
 | 
					    k = "persistent-config";
 | 
				
			||||||
  auto ignoreWorkspaces = config["ignore-workspaces"];
 | 
					    if (std::find(keys.begin(), keys.end(), k) != keys.end()) {
 | 
				
			||||||
  if (ignoreWorkspaces.isArray()) {
 | 
					      spdlog::debug("Set config persistency of workspace {} to: {}", workspaceName,
 | 
				
			||||||
    for (const auto &workspaceRegex : ignoreWorkspaces) {
 | 
					                    workspace_data[k].asBool() ? "true" : "false");
 | 
				
			||||||
      if (workspaceRegex.isString()) {
 | 
					      (*workspace)->setPersistentConfig(workspace_data[k].asBool());
 | 
				
			||||||
        std::string ruleString = workspaceRegex.asString();
 | 
					 | 
				
			||||||
        try {
 | 
					 | 
				
			||||||
          const std::regex rule{ruleString, std::regex_constants::icase};
 | 
					 | 
				
			||||||
          m_ignoreWorkspaces.emplace_back(rule);
 | 
					 | 
				
			||||||
        } catch (const std::regex_error &e) {
 | 
					 | 
				
			||||||
          spdlog::error("Invalid rule {}: {}", ruleString, e.what());
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      } else {
 | 
					 | 
				
			||||||
        spdlog::error("Not a string: '{}'", workspaceRegex);
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
auto Workspaces::populatePersistentWorkspacesConfig(const Json::Value &config) -> void {
 | 
					 | 
				
			||||||
  if (config.isMember("persistent-workspaces") || config.isMember("persistent_workspaces")) {
 | 
					 | 
				
			||||||
    spdlog::warn(
 | 
					 | 
				
			||||||
        "persistent_workspaces is deprecated. Please change config to use persistent-workspaces.");
 | 
					 | 
				
			||||||
    m_persistentWorkspaceConfig =
 | 
					 | 
				
			||||||
        config.get("persistent-workspaces", config.get("persistent_workspaces", Json::Value()));
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
auto Workspaces::populateFormatWindowSeparatorConfig(const Json::Value &config) -> void {
 | 
					 | 
				
			||||||
  auto formatWindowSeparator = config["format-window-separator"];
 | 
					 | 
				
			||||||
  m_formatWindowSeparator =
 | 
					 | 
				
			||||||
      formatWindowSeparator.isString() ? formatWindowSeparator.asString() : " ";
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
auto Workspaces::populateWindowRewriteConfig(const Json::Value &config) -> void {
 | 
					 | 
				
			||||||
  const auto &windowRewrite = config["window-rewrite"];
 | 
					 | 
				
			||||||
  if (!windowRewrite.isObject()) {
 | 
					 | 
				
			||||||
    spdlog::debug("window-rewrite is not defined or is not an object, using default rules.");
 | 
					 | 
				
			||||||
    return;
 | 
					    return;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const auto &windowRewriteDefaultConfig = config["window-rewrite-default"];
 | 
					  // create new workspace
 | 
				
			||||||
  std::string windowRewriteDefault =
 | 
					  m_workspaces.emplace_back(std::make_unique<Workspace>(workspace_data, *this, clients_data));
 | 
				
			||||||
      windowRewriteDefaultConfig.isString() ? windowRewriteDefaultConfig.asString() : "?";
 | 
					  Gtk::Button &newWorkspaceButton = m_workspaces.back()->button();
 | 
				
			||||||
 | 
					  m_box.pack_start(newWorkspaceButton, false, false);
 | 
				
			||||||
  m_windowRewriteRules = util::RegexCollection(
 | 
					  sortWorkspaces();
 | 
				
			||||||
      windowRewrite, windowRewriteDefault,
 | 
					  newWorkspaceButton.show_all();
 | 
				
			||||||
      [this](std::string &window_rule) { return windowRewritePriorityFunction(window_rule); });
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void Workspaces::registerOrphanWindow(WindowCreationPayload create_window_payload) {
 | 
					void Workspaces::createWorkspacesToCreate() {
 | 
				
			||||||
  if (!create_window_payload.isEmpty(*this)) {
 | 
					  for (const auto &[workspaceData, clientsData] : m_workspacesToCreate) {
 | 
				
			||||||
    m_orphanWindowMap[create_window_payload.getAddress()] = create_window_payload.repr(*this);
 | 
					    createWorkspace(workspaceData, clientsData);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					  if (!m_workspacesToCreate.empty()) {
 | 
				
			||||||
 | 
					    updateWindowCount();
 | 
				
			||||||
auto Workspaces::registerIpc() -> void {
 | 
					    sortWorkspaces();
 | 
				
			||||||
  gIPC->registerForIPC("workspace", this);
 | 
					 | 
				
			||||||
  gIPC->registerForIPC("activespecial", this);
 | 
					 | 
				
			||||||
  gIPC->registerForIPC("createworkspace", this);
 | 
					 | 
				
			||||||
  gIPC->registerForIPC("destroyworkspace", this);
 | 
					 | 
				
			||||||
  gIPC->registerForIPC("focusedmon", this);
 | 
					 | 
				
			||||||
  gIPC->registerForIPC("moveworkspace", this);
 | 
					 | 
				
			||||||
  gIPC->registerForIPC("renameworkspace", this);
 | 
					 | 
				
			||||||
  gIPC->registerForIPC("openwindow", this);
 | 
					 | 
				
			||||||
  gIPC->registerForIPC("closewindow", this);
 | 
					 | 
				
			||||||
  gIPC->registerForIPC("movewindow", this);
 | 
					 | 
				
			||||||
  gIPC->registerForIPC("urgent", this);
 | 
					 | 
				
			||||||
  gIPC->registerForIPC("configreloaded", this);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (windowRewriteConfigUsesTitle()) {
 | 
					 | 
				
			||||||
    spdlog::info(
 | 
					 | 
				
			||||||
        "Registering for Hyprland's 'windowtitle' events because a user-defined window "
 | 
					 | 
				
			||||||
        "rewrite rule uses the 'title' field.");
 | 
					 | 
				
			||||||
    gIPC->registerForIPC("windowtitle", this);
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  m_workspacesToCreate.clear();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
| 
						 | 
					@ -207,22 +142,25 @@ void Workspaces::doUpdate() {
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void Workspaces::removeWorkspacesToRemove() {
 | 
					void Workspaces::extendOrphans(int workspaceId, Json::Value const &clientsJson) {
 | 
				
			||||||
  for (const auto &workspaceName : m_workspacesToRemove) {
 | 
					  spdlog::trace("Extending orphans with workspace {}", workspaceId);
 | 
				
			||||||
    removeWorkspace(workspaceName);
 | 
					  for (const auto &client : clientsJson) {
 | 
				
			||||||
 | 
					    if (client["workspace"]["id"].asInt() == workspaceId) {
 | 
				
			||||||
 | 
					      registerOrphanWindow({client});
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  m_workspacesToRemove.clear();
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void Workspaces::createWorkspacesToCreate() {
 | 
					std::string Workspaces::getRewrite(std::string window_class, std::string window_title) {
 | 
				
			||||||
  for (const auto &[workspaceData, clientsData] : m_workspacesToCreate) {
 | 
					  std::string windowReprKey;
 | 
				
			||||||
    createWorkspace(workspaceData, clientsData);
 | 
					  if (windowRewriteConfigUsesTitle()) {
 | 
				
			||||||
 | 
					    windowReprKey = fmt::format("class<{}> title<{}>", window_class, window_title);
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    windowReprKey = fmt::format("class<{}>", window_class);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  if (!m_workspacesToCreate.empty()) {
 | 
					  auto const rewriteRule = m_windowRewriteRules.get(windowReprKey);
 | 
				
			||||||
    updateWindowCount();
 | 
					  return fmt::format(fmt::runtime(rewriteRule), fmt::arg("class", window_class),
 | 
				
			||||||
    sortWorkspaces();
 | 
					                     fmt::arg("title", window_title));
 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  m_workspacesToCreate.clear();
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
std::vector<std::string> Workspaces::getVisibleWorkspaces() {
 | 
					std::vector<std::string> Workspaces::getVisibleWorkspaces() {
 | 
				
			||||||
| 
						 | 
					@ -242,62 +180,36 @@ std::vector<std::string> Workspaces::getVisibleWorkspaces() {
 | 
				
			||||||
  return visibleWorkspaces;
 | 
					  return visibleWorkspaces;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void Workspaces::updateWorkspaceStates(const std::vector<std::string> &visibleWorkspaces) {
 | 
					void Workspaces::initializeWorkspaces() {
 | 
				
			||||||
  auto updatedWorkspaces = gIPC->getSocket1JsonReply("workspaces");
 | 
					  spdlog::debug("Initializing workspaces");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // if the workspace rules changed since last initialization, make sure we reset everything:
 | 
				
			||||||
  for (auto &workspace : m_workspaces) {
 | 
					  for (auto &workspace : m_workspaces) {
 | 
				
			||||||
    workspace->setActive(workspace->name() == m_activeWorkspaceName ||
 | 
					    m_workspacesToRemove.push_back(workspace->name());
 | 
				
			||||||
                         workspace->name() == m_activeSpecialWorkspaceName);
 | 
					 | 
				
			||||||
    if (workspace->name() == m_activeWorkspaceName && workspace->isUrgent()) {
 | 
					 | 
				
			||||||
      workspace->setUrgent(false);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    workspace->setVisible(std::find(visibleWorkspaces.begin(), visibleWorkspaces.end(),
 | 
					 | 
				
			||||||
                                    workspace->name()) != visibleWorkspaces.end());
 | 
					 | 
				
			||||||
    std::string &workspaceIcon = m_iconsMap[""];
 | 
					 | 
				
			||||||
    if (m_withIcon) {
 | 
					 | 
				
			||||||
      workspaceIcon = workspace->selectIcon(m_iconsMap);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    auto updatedWorkspace = std::find_if(
 | 
					 | 
				
			||||||
        updatedWorkspaces.begin(), updatedWorkspaces.end(), [&workspace](const auto &w) {
 | 
					 | 
				
			||||||
          auto wNameRaw = w["name"].asString();
 | 
					 | 
				
			||||||
          auto wName = wNameRaw.starts_with("special:") ? wNameRaw.substr(8) : wNameRaw;
 | 
					 | 
				
			||||||
          return wName == workspace->name();
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
    if (updatedWorkspace != updatedWorkspaces.end()) {
 | 
					 | 
				
			||||||
      workspace->setOutput((*updatedWorkspace)["monitor"].asString());
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    workspace->update(m_format, workspaceIcon);
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
bool Workspaces::updateWindowsToCreate() {
 | 
					  // get all current workspaces
 | 
				
			||||||
  bool anyWindowCreated = false;
 | 
					  auto const workspacesJson = gIPC->getSocket1JsonReply("workspaces");
 | 
				
			||||||
  std::vector<WindowCreationPayload> notCreated;
 | 
					  auto const clientsJson = gIPC->getSocket1JsonReply("clients");
 | 
				
			||||||
  for (auto &windowPayload : m_windowsToCreate) {
 | 
					
 | 
				
			||||||
    bool created = false;
 | 
					  for (Json::Value workspaceJson : workspacesJson) {
 | 
				
			||||||
    for (auto &workspace : m_workspaces) {
 | 
					    std::string workspaceName = workspaceJson["name"].asString();
 | 
				
			||||||
      if (workspace->onWindowOpened(windowPayload)) {
 | 
					    if ((allOutputs() || m_bar.output->name == workspaceJson["monitor"].asString()) &&
 | 
				
			||||||
        created = true;
 | 
					        (!workspaceName.starts_with("special") || showSpecial()) &&
 | 
				
			||||||
        anyWindowCreated = true;
 | 
					        !isWorkspaceIgnored(workspaceName)) {
 | 
				
			||||||
        break;
 | 
					      m_workspacesToCreate.emplace_back(workspaceJson, clientsJson);
 | 
				
			||||||
      }
 | 
					    } else {
 | 
				
			||||||
    }
 | 
					      extendOrphans(workspaceJson["id"].asInt(), clientsJson);
 | 
				
			||||||
    if (!created) {
 | 
					 | 
				
			||||||
      static auto const WINDOW_CREATION_TIMEOUT = 2;
 | 
					 | 
				
			||||||
      if (windowPayload.incrementTimeSpentUncreated() < WINDOW_CREATION_TIMEOUT) {
 | 
					 | 
				
			||||||
        notCreated.push_back(windowPayload);
 | 
					 | 
				
			||||||
      } else {
 | 
					 | 
				
			||||||
        registerOrphanWindow(windowPayload);
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  m_windowsToCreate.clear();
 | 
					 | 
				
			||||||
  m_windowsToCreate = notCreated;
 | 
					 | 
				
			||||||
  return anyWindowCreated;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
auto Workspaces::update() -> void {
 | 
					  spdlog::debug("Initializing persistent workspaces");
 | 
				
			||||||
  doUpdate();
 | 
					  if (m_persistentWorkspaceConfig.isObject()) {
 | 
				
			||||||
  AModule::update();
 | 
					    // a persistent workspace config is defined, so use that instead of workspace rules
 | 
				
			||||||
 | 
					    loadPersistentWorkspacesFromConfig(clientsJson);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  // load Hyprland's workspace rules
 | 
				
			||||||
 | 
					  loadPersistentWorkspacesFromWorkspaceRules(clientsJson);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bool isDoubleSpecial(std::string const &workspace_name) {
 | 
					bool isDoubleSpecial(std::string const &workspace_name) {
 | 
				
			||||||
| 
						 | 
					@ -319,6 +231,90 @@ bool Workspaces::isWorkspaceIgnored(std::string const &name) {
 | 
				
			||||||
  return false;
 | 
					  return false;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Workspaces::loadPersistentWorkspacesFromConfig(Json::Value const &clientsJson) {
 | 
				
			||||||
 | 
					  spdlog::info("Loading persistent workspaces from Waybar config");
 | 
				
			||||||
 | 
					  const std::vector<std::string> keys = m_persistentWorkspaceConfig.getMemberNames();
 | 
				
			||||||
 | 
					  std::vector<std::string> persistentWorkspacesToCreate;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const std::string currentMonitor = m_bar.output->name;
 | 
				
			||||||
 | 
					  const bool monitorInConfig = std::find(keys.begin(), keys.end(), currentMonitor) != keys.end();
 | 
				
			||||||
 | 
					  for (const std::string &key : keys) {
 | 
				
			||||||
 | 
					    // only add if either:
 | 
				
			||||||
 | 
					    // 1. key is the current monitor name
 | 
				
			||||||
 | 
					    // 2. key is "*" and this monitor is not already defined in the config
 | 
				
			||||||
 | 
					    bool canCreate = key == currentMonitor || (key == "*" && !monitorInConfig);
 | 
				
			||||||
 | 
					    const Json::Value &value = m_persistentWorkspaceConfig[key];
 | 
				
			||||||
 | 
					    spdlog::trace("Parsing persistent workspace config: {} => {}", key, value.toStyledString());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (value.isInt()) {
 | 
				
			||||||
 | 
					      // value is a number => create that many workspaces for this monitor
 | 
				
			||||||
 | 
					      if (canCreate) {
 | 
				
			||||||
 | 
					        int amount = value.asInt();
 | 
				
			||||||
 | 
					        spdlog::debug("Creating {} persistent workspaces for monitor {}", amount, currentMonitor);
 | 
				
			||||||
 | 
					        for (int i = 0; i < amount; i++) {
 | 
				
			||||||
 | 
					          persistentWorkspacesToCreate.emplace_back(std::to_string(m_monitorId * amount + i + 1));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    } else if (value.isArray() && !value.empty()) {
 | 
				
			||||||
 | 
					      // value is an array => create defined workspaces for this monitor
 | 
				
			||||||
 | 
					      if (canCreate) {
 | 
				
			||||||
 | 
					        for (const Json::Value &workspace : value) {
 | 
				
			||||||
 | 
					          if (workspace.isInt()) {
 | 
				
			||||||
 | 
					            spdlog::debug("Creating workspace {} on monitor {}", workspace, currentMonitor);
 | 
				
			||||||
 | 
					            persistentWorkspacesToCreate.emplace_back(std::to_string(workspace.asInt()));
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        // key is the workspace and value is array of monitors to create on
 | 
				
			||||||
 | 
					        for (const Json::Value &monitor : value) {
 | 
				
			||||||
 | 
					          if (monitor.isString() && monitor.asString() == currentMonitor) {
 | 
				
			||||||
 | 
					            persistentWorkspacesToCreate.emplace_back(currentMonitor);
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      // this workspace should be displayed on all monitors
 | 
				
			||||||
 | 
					      persistentWorkspacesToCreate.emplace_back(key);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  for (auto const &workspace : persistentWorkspacesToCreate) {
 | 
				
			||||||
 | 
					    auto workspaceData = createMonitorWorkspaceData(workspace, m_bar.output->name);
 | 
				
			||||||
 | 
					    workspaceData["persistent-config"] = true;
 | 
				
			||||||
 | 
					    m_workspacesToCreate.emplace_back(workspaceData, clientsJson);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Workspaces::loadPersistentWorkspacesFromWorkspaceRules(const Json::Value &clientsJson) {
 | 
				
			||||||
 | 
					  spdlog::info("Loading persistent workspaces from Hyprland workspace rules");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  auto const workspaceRules = gIPC->getSocket1JsonReply("workspacerules");
 | 
				
			||||||
 | 
					  for (Json::Value const &rule : workspaceRules) {
 | 
				
			||||||
 | 
					    if (!rule["workspaceString"].isString()) {
 | 
				
			||||||
 | 
					      spdlog::warn("Workspace rules: invalid workspaceString, skipping: {}", rule);
 | 
				
			||||||
 | 
					      continue;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (!rule["persistent"].asBool()) {
 | 
				
			||||||
 | 
					      continue;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    auto const &workspace = rule["workspaceString"].asString();
 | 
				
			||||||
 | 
					    auto const &monitor = rule["monitor"].asString();
 | 
				
			||||||
 | 
					    // create this workspace persistently if:
 | 
				
			||||||
 | 
					    // 1. the allOutputs config option is enabled
 | 
				
			||||||
 | 
					    // 2. the rule's monitor is the current monitor
 | 
				
			||||||
 | 
					    // 3. no monitor is specified in the rule => assume it needs to be persistent on every monitor
 | 
				
			||||||
 | 
					    if (allOutputs() || m_bar.output->name == monitor || monitor.empty()) {
 | 
				
			||||||
 | 
					      // => persistent workspace should be shown on this monitor
 | 
				
			||||||
 | 
					      auto workspaceData = createMonitorWorkspaceData(workspace, m_bar.output->name);
 | 
				
			||||||
 | 
					      workspaceData["persistent-rule"] = true;
 | 
				
			||||||
 | 
					      m_workspacesToCreate.emplace_back(workspaceData, clientsJson);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      m_workspacesToRemove.emplace_back(workspace);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void Workspaces::onEvent(const std::string &ev) {
 | 
					void Workspaces::onEvent(const std::string &ev) {
 | 
				
			||||||
  std::lock_guard<std::mutex> lock(m_mutex);
 | 
					  std::lock_guard<std::mutex> lock(m_mutex);
 | 
				
			||||||
  std::string eventName(begin(ev), begin(ev) + ev.find_first_of('>'));
 | 
					  std::string eventName(begin(ev), begin(ev) + ev.find_first_of('>'));
 | 
				
			||||||
| 
						 | 
					@ -569,66 +565,139 @@ void Workspaces::onConfigReloaded() {
 | 
				
			||||||
  init();
 | 
					  init();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void Workspaces::updateWindowCount() {
 | 
					auto Workspaces::parseConfig(const Json::Value &config) -> void {
 | 
				
			||||||
  const Json::Value workspacesJson = gIPC->getSocket1JsonReply("workspaces");
 | 
					  const auto &configFormat = config["format"];
 | 
				
			||||||
  for (auto &workspace : m_workspaces) {
 | 
					  m_format = configFormat.isString() ? configFormat.asString() : "{name}";
 | 
				
			||||||
    auto workspaceJson =
 | 
					  m_withIcon = m_format.find("{icon}") != std::string::npos;
 | 
				
			||||||
        std::find_if(workspacesJson.begin(), workspacesJson.end(), [&](Json::Value const &x) {
 | 
					
 | 
				
			||||||
          return x["name"].asString() == workspace->name() ||
 | 
					  if (m_withIcon && m_iconsMap.empty()) {
 | 
				
			||||||
                 (workspace->isSpecial() && x["name"].asString() == "special:" + workspace->name());
 | 
					    populateIconsMap(config["format-icons"]);
 | 
				
			||||||
        });
 | 
					  }
 | 
				
			||||||
    uint32_t count = 0;
 | 
					
 | 
				
			||||||
    if (workspaceJson != workspacesJson.end()) {
 | 
					  populateBoolConfig(config, "all-outputs", m_allOutputs);
 | 
				
			||||||
      try {
 | 
					  populateBoolConfig(config, "show-special", m_showSpecial);
 | 
				
			||||||
        count = (*workspaceJson)["windows"].asUInt();
 | 
					  populateBoolConfig(config, "active-only", m_activeOnly);
 | 
				
			||||||
      } catch (const std::exception &e) {
 | 
					  populateBoolConfig(config, "move-to-monitor", m_moveToMonitor);
 | 
				
			||||||
        spdlog::error("Failed to update window count: {}", e.what());
 | 
					
 | 
				
			||||||
      }
 | 
					  populateSortByConfig(config);
 | 
				
			||||||
    }
 | 
					  populateIgnoreWorkspacesConfig(config);
 | 
				
			||||||
    workspace->setWindows(count);
 | 
					  populatePersistentWorkspacesConfig(config);
 | 
				
			||||||
 | 
					  populateFormatWindowSeparatorConfig(config);
 | 
				
			||||||
 | 
					  populateWindowRewriteConfig(config);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					auto Workspaces::populateIconsMap(const Json::Value &formatIcons) -> void {
 | 
				
			||||||
 | 
					  for (const auto &name : formatIcons.getMemberNames()) {
 | 
				
			||||||
 | 
					    m_iconsMap.emplace(name, formatIcons[name].asString());
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  m_iconsMap.emplace("", "");
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					auto Workspaces::populateBoolConfig(const Json::Value &config, const std::string &key, bool &member)
 | 
				
			||||||
 | 
					    -> void {
 | 
				
			||||||
 | 
					  auto configValue = config[key];
 | 
				
			||||||
 | 
					  if (configValue.isBool()) {
 | 
				
			||||||
 | 
					    member = configValue.asBool();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void Workspaces::createWorkspace(Json::Value const &workspace_data,
 | 
					auto Workspaces::populateSortByConfig(const Json::Value &config) -> void {
 | 
				
			||||||
                                 Json::Value const &clients_data) {
 | 
					  auto configSortBy = config["sort-by"];
 | 
				
			||||||
  auto workspaceName = workspace_data["name"].asString();
 | 
					  if (configSortBy.isString()) {
 | 
				
			||||||
  spdlog::debug("Creating workspace {}", workspaceName);
 | 
					    auto sortByStr = configSortBy.asString();
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
  // avoid recreating existing workspaces
 | 
					      m_sortBy = m_enumParser.parseStringToEnum(sortByStr, m_sortMap);
 | 
				
			||||||
  auto workspace = std::find_if(
 | 
					    } catch (const std::invalid_argument &e) {
 | 
				
			||||||
      m_workspaces.begin(), m_workspaces.end(),
 | 
					      m_sortBy = SortMethod::DEFAULT;
 | 
				
			||||||
      [workspaceName](std::unique_ptr<Workspace> const &w) {
 | 
					      spdlog::warn(
 | 
				
			||||||
        return (workspaceName.starts_with("special:") && workspaceName.substr(8) == w->name()) ||
 | 
					          "Invalid string representation for sort-by. Falling back to default sort method.");
 | 
				
			||||||
               workspaceName == w->name();
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (workspace != m_workspaces.end()) {
 | 
					 | 
				
			||||||
    // don't recreate workspace, but update persistency if necessary
 | 
					 | 
				
			||||||
    const auto keys = workspace_data.getMemberNames();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const auto *k = "persistent-rule";
 | 
					 | 
				
			||||||
    if (std::find(keys.begin(), keys.end(), k) != keys.end()) {
 | 
					 | 
				
			||||||
      spdlog::debug("Set dynamic persistency of workspace {} to: {}", workspaceName,
 | 
					 | 
				
			||||||
                    workspace_data[k].asBool() ? "true" : "false");
 | 
					 | 
				
			||||||
      (*workspace)->setPersistentRule(workspace_data[k].asBool());
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    k = "persistent-config";
 | 
					auto Workspaces::populateIgnoreWorkspacesConfig(const Json::Value &config) -> void {
 | 
				
			||||||
    if (std::find(keys.begin(), keys.end(), k) != keys.end()) {
 | 
					  auto ignoreWorkspaces = config["ignore-workspaces"];
 | 
				
			||||||
      spdlog::debug("Set config persistency of workspace {} to: {}", workspaceName,
 | 
					  if (ignoreWorkspaces.isArray()) {
 | 
				
			||||||
                    workspace_data[k].asBool() ? "true" : "false");
 | 
					    for (const auto &workspaceRegex : ignoreWorkspaces) {
 | 
				
			||||||
      (*workspace)->setPersistentConfig(workspace_data[k].asBool());
 | 
					      if (workspaceRegex.isString()) {
 | 
				
			||||||
 | 
					        std::string ruleString = workspaceRegex.asString();
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					          const std::regex rule{ruleString, std::regex_constants::icase};
 | 
				
			||||||
 | 
					          m_ignoreWorkspaces.emplace_back(rule);
 | 
				
			||||||
 | 
					        } catch (const std::regex_error &e) {
 | 
				
			||||||
 | 
					          spdlog::error("Invalid rule {}: {}", ruleString, e.what());
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        spdlog::error("Not a string: '{}'", workspaceRegex);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					auto Workspaces::populatePersistentWorkspacesConfig(const Json::Value &config) -> void {
 | 
				
			||||||
 | 
					  if (config.isMember("persistent-workspaces") || config.isMember("persistent_workspaces")) {
 | 
				
			||||||
 | 
					    spdlog::warn(
 | 
				
			||||||
 | 
					        "persistent_workspaces is deprecated. Please change config to use persistent-workspaces.");
 | 
				
			||||||
 | 
					    m_persistentWorkspaceConfig =
 | 
				
			||||||
 | 
					        config.get("persistent-workspaces", config.get("persistent_workspaces", Json::Value()));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					auto Workspaces::populateFormatWindowSeparatorConfig(const Json::Value &config) -> void {
 | 
				
			||||||
 | 
					  auto formatWindowSeparator = config["format-window-separator"];
 | 
				
			||||||
 | 
					  m_formatWindowSeparator =
 | 
				
			||||||
 | 
					      formatWindowSeparator.isString() ? formatWindowSeparator.asString() : " ";
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					auto Workspaces::populateWindowRewriteConfig(const Json::Value &config) -> void {
 | 
				
			||||||
 | 
					  const auto &windowRewrite = config["window-rewrite"];
 | 
				
			||||||
 | 
					  if (!windowRewrite.isObject()) {
 | 
				
			||||||
 | 
					    spdlog::debug("window-rewrite is not defined or is not an object, using default rules.");
 | 
				
			||||||
    return;
 | 
					    return;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // create new workspace
 | 
					  const auto &windowRewriteDefaultConfig = config["window-rewrite-default"];
 | 
				
			||||||
  m_workspaces.emplace_back(std::make_unique<Workspace>(workspace_data, *this, clients_data));
 | 
					  std::string windowRewriteDefault =
 | 
				
			||||||
  Gtk::Button &newWorkspaceButton = m_workspaces.back()->button();
 | 
					      windowRewriteDefaultConfig.isString() ? windowRewriteDefaultConfig.asString() : "?";
 | 
				
			||||||
  m_box.pack_start(newWorkspaceButton, false, false);
 | 
					
 | 
				
			||||||
  sortWorkspaces();
 | 
					  m_windowRewriteRules = util::RegexCollection(
 | 
				
			||||||
  newWorkspaceButton.show_all();
 | 
					      windowRewrite, windowRewriteDefault,
 | 
				
			||||||
 | 
					      [this](std::string &window_rule) { return windowRewritePriorityFunction(window_rule); });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Workspaces::registerOrphanWindow(WindowCreationPayload create_window_payload) {
 | 
				
			||||||
 | 
					  if (!create_window_payload.isEmpty(*this)) {
 | 
				
			||||||
 | 
					    m_orphanWindowMap[create_window_payload.getAddress()] = create_window_payload.repr(*this);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					auto Workspaces::registerIpc() -> void {
 | 
				
			||||||
 | 
					  gIPC->registerForIPC("workspace", this);
 | 
				
			||||||
 | 
					  gIPC->registerForIPC("activespecial", this);
 | 
				
			||||||
 | 
					  gIPC->registerForIPC("createworkspace", this);
 | 
				
			||||||
 | 
					  gIPC->registerForIPC("destroyworkspace", this);
 | 
				
			||||||
 | 
					  gIPC->registerForIPC("focusedmon", this);
 | 
				
			||||||
 | 
					  gIPC->registerForIPC("moveworkspace", this);
 | 
				
			||||||
 | 
					  gIPC->registerForIPC("renameworkspace", this);
 | 
				
			||||||
 | 
					  gIPC->registerForIPC("openwindow", this);
 | 
				
			||||||
 | 
					  gIPC->registerForIPC("closewindow", this);
 | 
				
			||||||
 | 
					  gIPC->registerForIPC("movewindow", this);
 | 
				
			||||||
 | 
					  gIPC->registerForIPC("urgent", this);
 | 
				
			||||||
 | 
					  gIPC->registerForIPC("configreloaded", this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (windowRewriteConfigUsesTitle()) {
 | 
				
			||||||
 | 
					    spdlog::info(
 | 
				
			||||||
 | 
					        "Registering for Hyprland's 'windowtitle' events because a user-defined window "
 | 
				
			||||||
 | 
					        "rewrite rule uses the 'title' field.");
 | 
				
			||||||
 | 
					    gIPC->registerForIPC("windowtitle", this);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Workspaces::removeWorkspacesToRemove() {
 | 
				
			||||||
 | 
					  for (const auto &workspaceName : m_workspacesToRemove) {
 | 
				
			||||||
 | 
					    removeWorkspace(workspaceName);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  m_workspacesToRemove.clear();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void Workspaces::removeWorkspace(std::string const &name) {
 | 
					void Workspaces::removeWorkspace(std::string const &name) {
 | 
				
			||||||
| 
						 | 
					@ -652,106 +721,6 @@ void Workspaces::removeWorkspace(std::string const &name) {
 | 
				
			||||||
  m_workspaces.erase(workspace);
 | 
					  m_workspaces.erase(workspace);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Json::Value createMonitorWorkspaceData(std::string const &name, std::string const &monitor) {
 | 
					 | 
				
			||||||
  spdlog::trace("Creating persistent workspace: {} on monitor {}", name, monitor);
 | 
					 | 
				
			||||||
  Json::Value workspaceData;
 | 
					 | 
				
			||||||
  try {
 | 
					 | 
				
			||||||
    // numbered persistent workspaces get the name as ID
 | 
					 | 
				
			||||||
    workspaceData["id"] = name == "special" ? -99 : std::stoi(name);
 | 
					 | 
				
			||||||
  } catch (const std::exception &e) {
 | 
					 | 
				
			||||||
    // named persistent workspaces start with ID=0
 | 
					 | 
				
			||||||
    workspaceData["id"] = 0;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  workspaceData["name"] = name;
 | 
					 | 
				
			||||||
  workspaceData["monitor"] = monitor;
 | 
					 | 
				
			||||||
  workspaceData["windows"] = 0;
 | 
					 | 
				
			||||||
  return workspaceData;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void Workspaces::loadPersistentWorkspacesFromConfig(Json::Value const &clientsJson) {
 | 
					 | 
				
			||||||
  spdlog::info("Loading persistent workspaces from Waybar config");
 | 
					 | 
				
			||||||
  const std::vector<std::string> keys = m_persistentWorkspaceConfig.getMemberNames();
 | 
					 | 
				
			||||||
  std::vector<std::string> persistentWorkspacesToCreate;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const std::string currentMonitor = m_bar.output->name;
 | 
					 | 
				
			||||||
  const bool monitorInConfig = std::find(keys.begin(), keys.end(), currentMonitor) != keys.end();
 | 
					 | 
				
			||||||
  for (const std::string &key : keys) {
 | 
					 | 
				
			||||||
    // only add if either:
 | 
					 | 
				
			||||||
    // 1. key is the current monitor name
 | 
					 | 
				
			||||||
    // 2. key is "*" and this monitor is not already defined in the config
 | 
					 | 
				
			||||||
    bool canCreate = key == currentMonitor || (key == "*" && !monitorInConfig);
 | 
					 | 
				
			||||||
    const Json::Value &value = m_persistentWorkspaceConfig[key];
 | 
					 | 
				
			||||||
    spdlog::trace("Parsing persistent workspace config: {} => {}", key, value.toStyledString());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (value.isInt()) {
 | 
					 | 
				
			||||||
      // value is a number => create that many workspaces for this monitor
 | 
					 | 
				
			||||||
      if (canCreate) {
 | 
					 | 
				
			||||||
        int amount = value.asInt();
 | 
					 | 
				
			||||||
        spdlog::debug("Creating {} persistent workspaces for monitor {}", amount, currentMonitor);
 | 
					 | 
				
			||||||
        for (int i = 0; i < amount; i++) {
 | 
					 | 
				
			||||||
          persistentWorkspacesToCreate.emplace_back(std::to_string(m_monitorId * amount + i + 1));
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    } else if (value.isArray() && !value.empty()) {
 | 
					 | 
				
			||||||
      // value is an array => create defined workspaces for this monitor
 | 
					 | 
				
			||||||
      if (canCreate) {
 | 
					 | 
				
			||||||
        for (const Json::Value &workspace : value) {
 | 
					 | 
				
			||||||
          if (workspace.isInt()) {
 | 
					 | 
				
			||||||
            spdlog::debug("Creating workspace {} on monitor {}", workspace, currentMonitor);
 | 
					 | 
				
			||||||
            persistentWorkspacesToCreate.emplace_back(std::to_string(workspace.asInt()));
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      } else {
 | 
					 | 
				
			||||||
        // key is the workspace and value is array of monitors to create on
 | 
					 | 
				
			||||||
        for (const Json::Value &monitor : value) {
 | 
					 | 
				
			||||||
          if (monitor.isString() && monitor.asString() == currentMonitor) {
 | 
					 | 
				
			||||||
            persistentWorkspacesToCreate.emplace_back(currentMonitor);
 | 
					 | 
				
			||||||
            break;
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      // this workspace should be displayed on all monitors
 | 
					 | 
				
			||||||
      persistentWorkspacesToCreate.emplace_back(key);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  for (auto const &workspace : persistentWorkspacesToCreate) {
 | 
					 | 
				
			||||||
    auto workspaceData = createMonitorWorkspaceData(workspace, m_bar.output->name);
 | 
					 | 
				
			||||||
    workspaceData["persistent-config"] = true;
 | 
					 | 
				
			||||||
    m_workspacesToCreate.emplace_back(workspaceData, clientsJson);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void Workspaces::loadPersistentWorkspacesFromWorkspaceRules(const Json::Value &clientsJson) {
 | 
					 | 
				
			||||||
  spdlog::info("Loading persistent workspaces from Hyprland workspace rules");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  auto const workspaceRules = gIPC->getSocket1JsonReply("workspacerules");
 | 
					 | 
				
			||||||
  for (Json::Value const &rule : workspaceRules) {
 | 
					 | 
				
			||||||
    if (!rule["workspaceString"].isString()) {
 | 
					 | 
				
			||||||
      spdlog::warn("Workspace rules: invalid workspaceString, skipping: {}", rule);
 | 
					 | 
				
			||||||
      continue;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if (!rule["persistent"].asBool()) {
 | 
					 | 
				
			||||||
      continue;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    auto const &workspace = rule["workspaceString"].asString();
 | 
					 | 
				
			||||||
    auto const &monitor = rule["monitor"].asString();
 | 
					 | 
				
			||||||
    // create this workspace persistently if:
 | 
					 | 
				
			||||||
    // 1. the allOutputs config option is enabled
 | 
					 | 
				
			||||||
    // 2. the rule's monitor is the current monitor
 | 
					 | 
				
			||||||
    // 3. no monitor is specified in the rule => assume it needs to be persistent on every monitor
 | 
					 | 
				
			||||||
    if (allOutputs() || m_bar.output->name == monitor || monitor.empty()) {
 | 
					 | 
				
			||||||
      // => persistent workspace should be shown on this monitor
 | 
					 | 
				
			||||||
      auto workspaceData = createMonitorWorkspaceData(workspace, m_bar.output->name);
 | 
					 | 
				
			||||||
      workspaceData["persistent-rule"] = true;
 | 
					 | 
				
			||||||
      m_workspacesToCreate.emplace_back(workspaceData, clientsJson);
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      m_workspacesToRemove.emplace_back(workspace);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void Workspaces::setCurrentMonitorId() {
 | 
					void Workspaces::setCurrentMonitorId() {
 | 
				
			||||||
  // get monitor ID from name (used by persistent workspaces)
 | 
					  // get monitor ID from name (used by persistent workspaces)
 | 
				
			||||||
  m_monitorId = 0;
 | 
					  m_monitorId = 0;
 | 
				
			||||||
| 
						 | 
					@ -767,60 +736,6 @@ void Workspaces::setCurrentMonitorId() {
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void Workspaces::initializeWorkspaces() {
 | 
					 | 
				
			||||||
  spdlog::debug("Initializing workspaces");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // if the workspace rules changed since last initialization, make sure we reset everything:
 | 
					 | 
				
			||||||
  for (auto &workspace : m_workspaces) {
 | 
					 | 
				
			||||||
    m_workspacesToRemove.push_back(workspace->name());
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // get all current workspaces
 | 
					 | 
				
			||||||
  auto const workspacesJson = gIPC->getSocket1JsonReply("workspaces");
 | 
					 | 
				
			||||||
  auto const clientsJson = gIPC->getSocket1JsonReply("clients");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  for (Json::Value workspaceJson : workspacesJson) {
 | 
					 | 
				
			||||||
    std::string workspaceName = workspaceJson["name"].asString();
 | 
					 | 
				
			||||||
    if ((allOutputs() || m_bar.output->name == workspaceJson["monitor"].asString()) &&
 | 
					 | 
				
			||||||
        (!workspaceName.starts_with("special") || showSpecial()) &&
 | 
					 | 
				
			||||||
        !isWorkspaceIgnored(workspaceName)) {
 | 
					 | 
				
			||||||
      m_workspacesToCreate.emplace_back(workspaceJson, clientsJson);
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      extendOrphans(workspaceJson["id"].asInt(), clientsJson);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  spdlog::debug("Initializing persistent workspaces");
 | 
					 | 
				
			||||||
  if (m_persistentWorkspaceConfig.isObject()) {
 | 
					 | 
				
			||||||
    // a persistent workspace config is defined, so use that instead of workspace rules
 | 
					 | 
				
			||||||
    loadPersistentWorkspacesFromConfig(clientsJson);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  // load Hyprland's workspace rules
 | 
					 | 
				
			||||||
  loadPersistentWorkspacesFromWorkspaceRules(clientsJson);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void Workspaces::extendOrphans(int workspaceId, Json::Value const &clientsJson) {
 | 
					 | 
				
			||||||
  spdlog::trace("Extending orphans with workspace {}", workspaceId);
 | 
					 | 
				
			||||||
  for (const auto &client : clientsJson) {
 | 
					 | 
				
			||||||
    if (client["workspace"]["id"].asInt() == workspaceId) {
 | 
					 | 
				
			||||||
      registerOrphanWindow({client});
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void Workspaces::init() {
 | 
					 | 
				
			||||||
  m_activeWorkspaceName = (gIPC->getSocket1JsonReply("activeworkspace"))["name"].asString();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  initializeWorkspaces();
 | 
					 | 
				
			||||||
  dp.emit();
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Workspaces::~Workspaces() {
 | 
					 | 
				
			||||||
  gIPC->unregisterForIPC(this);
 | 
					 | 
				
			||||||
  // wait for possible event handler to finish
 | 
					 | 
				
			||||||
  std::lock_guard<std::mutex> lg(m_mutex);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void Workspaces::sortWorkspaces() {
 | 
					void Workspaces::sortWorkspaces() {
 | 
				
			||||||
  std::sort(m_workspaces.begin(), m_workspaces.end(),
 | 
					  std::sort(m_workspaces.begin(), m_workspaces.end(),
 | 
				
			||||||
            [&](std::unique_ptr<Workspace> &a, std::unique_ptr<Workspace> &b) {
 | 
					            [&](std::unique_ptr<Workspace> &a, std::unique_ptr<Workspace> &b) {
 | 
				
			||||||
| 
						 | 
					@ -903,15 +818,102 @@ void Workspaces::setUrgentWorkspace(std::string const &windowaddress) {
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
std::string Workspaces::getRewrite(std::string window_class, std::string window_title) {
 | 
					auto Workspaces::update() -> void {
 | 
				
			||||||
  std::string windowReprKey;
 | 
					  doUpdate();
 | 
				
			||||||
  if (windowRewriteConfigUsesTitle()) {
 | 
					  AModule::update();
 | 
				
			||||||
    windowReprKey = fmt::format("class<{}> title<{}>", window_class, window_title);
 | 
					 | 
				
			||||||
  } else {
 | 
					 | 
				
			||||||
    windowReprKey = fmt::format("class<{}>", window_class);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  auto const rewriteRule = m_windowRewriteRules.get(windowReprKey);
 | 
					 | 
				
			||||||
  return fmt::format(fmt::runtime(rewriteRule), fmt::arg("class", window_class),
 | 
					 | 
				
			||||||
                     fmt::arg("title", window_title));
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Workspaces::updateWindowCount() {
 | 
				
			||||||
 | 
					  const Json::Value workspacesJson = gIPC->getSocket1JsonReply("workspaces");
 | 
				
			||||||
 | 
					  for (auto &workspace : m_workspaces) {
 | 
				
			||||||
 | 
					    auto workspaceJson =
 | 
				
			||||||
 | 
					        std::find_if(workspacesJson.begin(), workspacesJson.end(), [&](Json::Value const &x) {
 | 
				
			||||||
 | 
					          return x["name"].asString() == workspace->name() ||
 | 
				
			||||||
 | 
					                 (workspace->isSpecial() && x["name"].asString() == "special:" + workspace->name());
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    uint32_t count = 0;
 | 
				
			||||||
 | 
					    if (workspaceJson != workspacesJson.end()) {
 | 
				
			||||||
 | 
					      try {
 | 
				
			||||||
 | 
					        count = (*workspaceJson)["windows"].asUInt();
 | 
				
			||||||
 | 
					      } catch (const std::exception &e) {
 | 
				
			||||||
 | 
					        spdlog::error("Failed to update window count: {}", e.what());
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    workspace->setWindows(count);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool Workspaces::updateWindowsToCreate() {
 | 
				
			||||||
 | 
					  bool anyWindowCreated = false;
 | 
				
			||||||
 | 
					  std::vector<WindowCreationPayload> notCreated;
 | 
				
			||||||
 | 
					  for (auto &windowPayload : m_windowsToCreate) {
 | 
				
			||||||
 | 
					    bool created = false;
 | 
				
			||||||
 | 
					    for (auto &workspace : m_workspaces) {
 | 
				
			||||||
 | 
					      if (workspace->onWindowOpened(windowPayload)) {
 | 
				
			||||||
 | 
					        created = true;
 | 
				
			||||||
 | 
					        anyWindowCreated = true;
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (!created) {
 | 
				
			||||||
 | 
					      static auto const WINDOW_CREATION_TIMEOUT = 2;
 | 
				
			||||||
 | 
					      if (windowPayload.incrementTimeSpentUncreated() < WINDOW_CREATION_TIMEOUT) {
 | 
				
			||||||
 | 
					        notCreated.push_back(windowPayload);
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        registerOrphanWindow(windowPayload);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  m_windowsToCreate.clear();
 | 
				
			||||||
 | 
					  m_windowsToCreate = notCreated;
 | 
				
			||||||
 | 
					  return anyWindowCreated;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Workspaces::updateWorkspaceStates(const std::vector<std::string> &visibleWorkspaces) {
 | 
				
			||||||
 | 
					  auto updatedWorkspaces = gIPC->getSocket1JsonReply("workspaces");
 | 
				
			||||||
 | 
					  for (auto &workspace : m_workspaces) {
 | 
				
			||||||
 | 
					    workspace->setActive(workspace->name() == m_activeWorkspaceName ||
 | 
				
			||||||
 | 
					                         workspace->name() == m_activeSpecialWorkspaceName);
 | 
				
			||||||
 | 
					    if (workspace->name() == m_activeWorkspaceName && workspace->isUrgent()) {
 | 
				
			||||||
 | 
					      workspace->setUrgent(false);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    workspace->setVisible(std::find(visibleWorkspaces.begin(), visibleWorkspaces.end(),
 | 
				
			||||||
 | 
					                                    workspace->name()) != visibleWorkspaces.end());
 | 
				
			||||||
 | 
					    std::string &workspaceIcon = m_iconsMap[""];
 | 
				
			||||||
 | 
					    if (m_withIcon) {
 | 
				
			||||||
 | 
					      workspaceIcon = workspace->selectIcon(m_iconsMap);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    auto updatedWorkspace = std::find_if(
 | 
				
			||||||
 | 
					        updatedWorkspaces.begin(), updatedWorkspaces.end(), [&workspace](const auto &w) {
 | 
				
			||||||
 | 
					          auto wNameRaw = w["name"].asString();
 | 
				
			||||||
 | 
					          auto wName = wNameRaw.starts_with("special:") ? wNameRaw.substr(8) : wNameRaw;
 | 
				
			||||||
 | 
					          return wName == workspace->name();
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    if (updatedWorkspace != updatedWorkspaces.end()) {
 | 
				
			||||||
 | 
					      workspace->setOutput((*updatedWorkspace)["monitor"].asString());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    workspace->update(m_format, workspaceIcon);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					int Workspaces::windowRewritePriorityFunction(std::string const &window_rule) {
 | 
				
			||||||
 | 
					  // Rules that match against title are prioritized
 | 
				
			||||||
 | 
					  // Rules that don't specify if they're matching against either title or class are deprioritized
 | 
				
			||||||
 | 
					  bool const hasTitle = window_rule.find("title") != std::string::npos;
 | 
				
			||||||
 | 
					  bool const hasClass = window_rule.find("class") != std::string::npos;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (hasTitle && hasClass) {
 | 
				
			||||||
 | 
					    m_anyWindowRewriteRuleUsesTitle = true;
 | 
				
			||||||
 | 
					    return 3;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  if (hasTitle) {
 | 
				
			||||||
 | 
					    m_anyWindowRewriteRuleUsesTitle = true;
 | 
				
			||||||
 | 
					    return 2;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  if (hasClass) {
 | 
				
			||||||
 | 
					    return 1;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  return 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}  // namespace waybar::modules::hyprland
 | 
					}  // namespace waybar::modules::hyprland
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue