Merge pull request #3878 from matt-fff/mw/hyprland-events-workspaces-v2

Migrate Hyprland workspace events to v2
This commit is contained in:
Alexis Rouillard
2025-03-28 09:45:44 +01:00
committed by GitHub
2 changed files with 248 additions and 158 deletions

View File

@ -7,6 +7,7 @@
#include <cstdint> #include <cstdint>
#include <map> #include <map>
#include <memory> #include <memory>
#include <optional>
#include <regex> #include <regex>
#include <string> #include <string>
#include <vector> #include <vector>
@ -55,7 +56,7 @@ class Workspaces : public AModule, public EventHandler {
static Json::Value createMonitorWorkspaceData(std::string const& name, static Json::Value createMonitorWorkspaceData(std::string const& name,
std::string const& monitor); std::string const& monitor);
void removeWorkspace(std::string const& name); void removeWorkspace(std::string const& workspaceString);
void setUrgentWorkspace(std::string const& windowaddress); void setUrgentWorkspace(std::string const& windowaddress);
// Config // Config
@ -74,10 +75,11 @@ class Workspaces : public AModule, public EventHandler {
void onWorkspaceActivated(std::string const& payload); void onWorkspaceActivated(std::string const& payload);
void onSpecialWorkspaceActivated(std::string const& payload); void onSpecialWorkspaceActivated(std::string const& payload);
void onWorkspaceDestroyed(std::string const& payload); void onWorkspaceDestroyed(std::string const& payload);
void onWorkspaceCreated(std::string const& workspaceName, void onWorkspaceCreated(std::string const& payload,
Json::Value const& clientsData = Json::Value::nullRef); Json::Value const& clientsData = Json::Value::nullRef);
void onWorkspaceMoved(std::string const& payload); void onWorkspaceMoved(std::string const& payload);
void onWorkspaceRenamed(std::string const& payload); void onWorkspaceRenamed(std::string const& payload);
static std::optional<int> parseWorkspaceId(std::string const& workspaceIdStr);
// monitor events // monitor events
void onMonitorFocused(std::string const& payload); void onMonitorFocused(std::string const& payload);
@ -93,11 +95,18 @@ class Workspaces : public AModule, public EventHandler {
int windowRewritePriorityFunction(std::string const& window_rule); int windowRewritePriorityFunction(std::string const& window_rule);
// event payload management
template <typename... Args>
static std::string makePayload(Args const&... args);
static std::pair<std::string, std::string> splitDoublePayload(std::string const& payload);
static std::tuple<std::string, std::string, std::string> splitTriplePayload(
std::string const& payload);
// Update methods // Update methods
void doUpdate(); void doUpdate();
void removeWorkspacesToRemove(); void removeWorkspacesToRemove();
void createWorkspacesToCreate(); void createWorkspacesToCreate();
static std::vector<std::string> getVisibleWorkspaces(); static std::vector<int> getVisibleWorkspaces();
void updateWorkspaceStates(); void updateWorkspaceStates();
bool updateWindowsToCreate(); bool updateWindowsToCreate();
@ -138,7 +147,7 @@ class Workspaces : public AModule, public EventHandler {
bool m_withIcon; bool m_withIcon;
uint64_t m_monitorId; uint64_t m_monitorId;
std::string m_activeWorkspaceName; int m_activeWorkspaceId;
std::string m_activeSpecialWorkspaceName; std::string m_activeSpecialWorkspaceName;
std::vector<std::unique_ptr<Workspace>> m_workspaces; std::vector<std::unique_ptr<Workspace>> m_workspaces;
std::vector<std::pair<Json::Value, Json::Value>> m_workspacesToCreate; std::vector<std::pair<Json::Value, Json::Value>> m_workspacesToCreate;

View File

@ -5,6 +5,7 @@
#include <algorithm> #include <algorithm>
#include <memory> #include <memory>
#include <sstream>
#include <string> #include <string>
#include <utility> #include <utility>
@ -39,7 +40,7 @@ Workspaces::~Workspaces() {
} }
void Workspaces::init() { void Workspaces::init() {
m_activeWorkspaceName = (m_ipc.getSocket1JsonReply("activeworkspace"))["name"].asString(); m_activeWorkspaceId = m_ipc.getSocket1JsonReply("activeworkspace")["id"].asInt();
initializeWorkspaces(); initializeWorkspaces();
dp.emit(); dp.emit();
@ -49,13 +50,12 @@ Json::Value Workspaces::createMonitorWorkspaceData(std::string const &name,
std::string const &monitor) { std::string const &monitor) {
spdlog::trace("Creating persistent workspace: {} on monitor {}", name, monitor); spdlog::trace("Creating persistent workspace: {} on monitor {}", name, monitor);
Json::Value workspaceData; Json::Value workspaceData;
try {
// numbered persistent workspaces get the name as ID auto workspaceId = parseWorkspaceId(name);
workspaceData["id"] = name == "special" ? -99 : std::stoi(name); if (!workspaceId.has_value()) {
} catch (const std::exception &e) { workspaceId = 0;
// named persistent workspaces start with ID=0
workspaceData["id"] = 0;
} }
workspaceData["id"] = *workspaceId;
workspaceData["name"] = name; workspaceData["name"] = name;
workspaceData["monitor"] = monitor; workspaceData["monitor"] = monitor;
workspaceData["windows"] = 0; workspaceData["windows"] = 0;
@ -68,9 +68,8 @@ void Workspaces::createWorkspace(Json::Value const &workspace_data,
spdlog::debug("Creating workspace {}", workspaceName); spdlog::debug("Creating workspace {}", workspaceName);
// avoid recreating existing workspaces // avoid recreating existing workspaces
auto workspace = std::find_if( auto workspace =
m_workspaces.begin(), m_workspaces.end(), std::ranges::find_if(m_workspaces, [workspaceName](std::unique_ptr<Workspace> const &w) {
[workspaceName](std::unique_ptr<Workspace> const &w) {
return (workspaceName.starts_with("special:") && workspaceName.substr(8) == w->name()) || return (workspaceName.starts_with("special:") && workspaceName.substr(8) == w->name()) ||
workspaceName == w->name(); workspaceName == w->name();
}); });
@ -158,18 +157,18 @@ std::string Workspaces::getRewrite(std::string window_class, std::string window_
fmt::arg("title", window_title)); fmt::arg("title", window_title));
} }
std::vector<std::string> Workspaces::getVisibleWorkspaces() { std::vector<int> Workspaces::getVisibleWorkspaces() {
std::vector<std::string> visibleWorkspaces; std::vector<int> visibleWorkspaces;
auto monitors = IPC::inst().getSocket1JsonReply("monitors"); auto monitors = IPC::inst().getSocket1JsonReply("monitors");
for (const auto &monitor : monitors) { for (const auto &monitor : monitors) {
auto ws = monitor["activeWorkspace"]; auto ws = monitor["activeWorkspace"];
if (ws.isObject() && ws["name"].isString()) { if (ws.isObject() && ws["id"].isInt()) {
visibleWorkspaces.push_back(ws["name"].asString()); visibleWorkspaces.push_back(ws["id"].asInt());
} }
auto sws = monitor["specialWorkspace"]; auto sws = monitor["specialWorkspace"];
auto name = sws["name"].asString(); auto name = sws["name"].asString();
if (sws.isObject() && sws["name"].isString() && !name.empty()) { if (sws.isObject() && sws["id"].isInt() && !name.empty()) {
visibleWorkspaces.push_back(!name.starts_with("special:") ? name : name.substr(8)); visibleWorkspaces.push_back(sws["id"].asInt());
} }
} }
return visibleWorkspaces; return visibleWorkspaces;
@ -180,7 +179,7 @@ void Workspaces::initializeWorkspaces() {
// if the workspace rules changed since last initialization, make sure we reset everything: // if the workspace rules changed since last initialization, make sure we reset everything:
for (auto &workspace : m_workspaces) { for (auto &workspace : m_workspaces) {
m_workspacesToRemove.push_back(workspace->name()); m_workspacesToRemove.push_back(std::to_string(workspace->id()));
} }
// get all current workspaces // get all current workspaces
@ -247,7 +246,7 @@ void Workspaces::loadPersistentWorkspacesFromConfig(Json::Value const &clientsJs
int amount = value.asInt(); int amount = value.asInt();
spdlog::debug("Creating {} persistent workspaces for monitor {}", amount, currentMonitor); spdlog::debug("Creating {} persistent workspaces for monitor {}", amount, currentMonitor);
for (int i = 0; i < amount; i++) { for (int i = 0; i < amount; i++) {
persistentWorkspacesToCreate.emplace_back(std::to_string(m_monitorId * amount + i + 1)); persistentWorkspacesToCreate.emplace_back(std::to_string((m_monitorId * amount) + i + 1));
} }
} }
} else if (value.isArray() && !value.empty()) { } else if (value.isArray() && !value.empty()) {
@ -306,6 +305,7 @@ void Workspaces::loadPersistentWorkspacesFromWorkspaceRules(const Json::Value &c
workspaceData["persistent-rule"] = true; workspaceData["persistent-rule"] = true;
m_workspacesToCreate.emplace_back(workspaceData, clientsJson); m_workspacesToCreate.emplace_back(workspaceData, clientsJson);
} else { } else {
// This can be any workspace selector.
m_workspacesToRemove.emplace_back(workspace); m_workspacesToRemove.emplace_back(workspace);
} }
} }
@ -316,29 +316,29 @@ void Workspaces::onEvent(const std::string &ev) {
std::string eventName(begin(ev), begin(ev) + ev.find_first_of('>')); std::string eventName(begin(ev), begin(ev) + ev.find_first_of('>'));
std::string payload = ev.substr(eventName.size() + 2); std::string payload = ev.substr(eventName.size() + 2);
if (eventName == "workspace") { if (eventName == "workspacev2") {
onWorkspaceActivated(payload); onWorkspaceActivated(payload);
} else if (eventName == "activespecial") { } else if (eventName == "activespecial") {
onSpecialWorkspaceActivated(payload); onSpecialWorkspaceActivated(payload);
} else if (eventName == "destroyworkspace") { } else if (eventName == "destroyworkspacev2") {
onWorkspaceDestroyed(payload); onWorkspaceDestroyed(payload);
} else if (eventName == "createworkspace") { } else if (eventName == "createworkspacev2") {
onWorkspaceCreated(payload); onWorkspaceCreated(payload);
} else if (eventName == "focusedmon") { } else if (eventName == "focusedmonv2") {
onMonitorFocused(payload); onMonitorFocused(payload);
} else if (eventName == "moveworkspace") { } else if (eventName == "moveworkspacev2") {
onWorkspaceMoved(payload); onWorkspaceMoved(payload);
} else if (eventName == "openwindow") { } else if (eventName == "openwindow") {
onWindowOpened(payload); onWindowOpened(payload);
} else if (eventName == "closewindow") { } else if (eventName == "closewindow") {
onWindowClosed(payload); onWindowClosed(payload);
} else if (eventName == "movewindow") { } else if (eventName == "movewindowv2") {
onWindowMoved(payload); onWindowMoved(payload);
} else if (eventName == "urgent") { } else if (eventName == "urgent") {
setUrgentWorkspace(payload); setUrgentWorkspace(payload);
} else if (eventName == "renameworkspace") { } else if (eventName == "renameworkspace") {
onWorkspaceRenamed(payload); onWorkspaceRenamed(payload);
} else if (eventName == "windowtitle") { } else if (eventName == "windowtitlev2") {
onWindowTitleEvent(payload); onWindowTitleEvent(payload);
} else if (eventName == "configreloaded") { } else if (eventName == "configreloaded") {
onConfigReloaded(); onConfigReloaded();
@ -348,7 +348,11 @@ void Workspaces::onEvent(const std::string &ev) {
} }
void Workspaces::onWorkspaceActivated(std::string const &payload) { void Workspaces::onWorkspaceActivated(std::string const &payload) {
m_activeWorkspaceName = payload; const auto [workspaceIdStr, workspaceName] = splitDoublePayload(payload);
const auto workspaceId = parseWorkspaceId(workspaceIdStr);
if (workspaceId.has_value()) {
m_activeWorkspaceId = *workspaceId;
}
} }
void Workspaces::onSpecialWorkspaceActivated(std::string const &payload) { void Workspaces::onSpecialWorkspaceActivated(std::string const &payload) {
@ -357,42 +361,55 @@ void Workspaces::onSpecialWorkspaceActivated(std::string const &payload) {
} }
void Workspaces::onWorkspaceDestroyed(std::string const &payload) { void Workspaces::onWorkspaceDestroyed(std::string const &payload) {
if (!isDoubleSpecial(payload)) { const auto [workspaceId, workspaceName] = splitDoublePayload(payload);
m_workspacesToRemove.push_back(payload); if (!isDoubleSpecial(workspaceName)) {
m_workspacesToRemove.push_back(workspaceId);
} }
} }
void Workspaces::onWorkspaceCreated(std::string const &workspaceName, void Workspaces::onWorkspaceCreated(std::string const &payload, Json::Value const &clientsData) {
Json::Value const &clientsData) { spdlog::debug("Workspace created: {}", payload);
spdlog::debug("Workspace created: {}", workspaceName);
const auto [workspaceIdStr, _] = splitDoublePayload(payload);
const auto workspaceId = parseWorkspaceId(workspaceIdStr);
if (!workspaceId.has_value()) {
return;
}
auto const workspaceRules = m_ipc.getSocket1JsonReply("workspacerules");
auto const workspacesJson = m_ipc.getSocket1JsonReply("workspaces"); auto const workspacesJson = m_ipc.getSocket1JsonReply("workspaces");
if (!isWorkspaceIgnored(workspaceName)) { for (Json::Value workspaceJson : workspacesJson) {
auto const workspaceRules = m_ipc.getSocket1JsonReply("workspacerules"); const auto currentId = workspaceJson["id"].asInt();
for (Json::Value workspaceJson : workspacesJson) { if (currentId == *workspaceId) {
std::string name = workspaceJson["name"].asString(); std::string workspaceName = workspaceJson["name"].asString();
if (name == workspaceName) { // This workspace name is more up-to-date than the one in the event payload.
if ((allOutputs() || m_bar.output->name == workspaceJson["monitor"].asString()) && if (isWorkspaceIgnored(workspaceName)) {
(showSpecial() || !name.starts_with("special")) && !isDoubleSpecial(workspaceName)) { spdlog::trace("Not creating workspace because it is ignored: id={} name={}", *workspaceId,
for (Json::Value const &rule : workspaceRules) { workspaceName);
auto ruleWorkspaceName = rule.isMember("defaultName") break;
? rule["defaultName"].asString()
: rule["workspaceString"].asString();
if (ruleWorkspaceName == workspaceName) {
workspaceJson["persistent-rule"] = rule["persistent"].asBool();
break;
}
}
m_workspacesToCreate.emplace_back(workspaceJson, clientsData);
break;
}
} else {
extendOrphans(workspaceJson["id"].asInt(), clientsData);
} }
if ((allOutputs() || m_bar.output->name == workspaceJson["monitor"].asString()) &&
(showSpecial() || !workspaceName.starts_with("special")) &&
!isDoubleSpecial(workspaceName)) {
for (Json::Value const &rule : workspaceRules) {
auto ruleWorkspaceName = rule.isMember("defaultName")
? rule["defaultName"].asString()
: rule["workspaceString"].asString();
if (ruleWorkspaceName == workspaceName) {
workspaceJson["persistent-rule"] = rule["persistent"].asBool();
break;
}
}
m_workspacesToCreate.emplace_back(workspaceJson, clientsData);
break;
}
} else {
extendOrphans(*workspaceId, clientsData);
} }
} else {
spdlog::trace("Not creating workspace because it is ignored: {}", workspaceName);
} }
} }
@ -400,32 +417,34 @@ void Workspaces::onWorkspaceMoved(std::string const &payload) {
spdlog::debug("Workspace moved: {}", payload); spdlog::debug("Workspace moved: {}", payload);
// Update active workspace // Update active workspace
m_activeWorkspaceName = (m_ipc.getSocket1JsonReply("activeworkspace"))["name"].asString(); m_activeWorkspaceId = (m_ipc.getSocket1JsonReply("activeworkspace"))["id"].asInt();
if (allOutputs()) return; if (allOutputs()) return;
std::string workspaceName = payload.substr(0, payload.find(',')); const auto [workspaceIdStr, workspaceName, monitorName] = splitTriplePayload(payload);
std::string monitorName = payload.substr(payload.find(',') + 1);
const auto subPayload = makePayload(workspaceIdStr, workspaceName);
if (m_bar.output->name == monitorName) { if (m_bar.output->name == monitorName) {
Json::Value clientsData = m_ipc.getSocket1JsonReply("clients"); Json::Value clientsData = m_ipc.getSocket1JsonReply("clients");
onWorkspaceCreated(workspaceName, clientsData); onWorkspaceCreated(subPayload, clientsData);
} else { } else {
spdlog::debug("Removing workspace because it was moved to another monitor: {}"); spdlog::debug("Removing workspace because it was moved to another monitor: {}", subPayload);
onWorkspaceDestroyed(workspaceName); onWorkspaceDestroyed(subPayload);
} }
} }
void Workspaces::onWorkspaceRenamed(std::string const &payload) { void Workspaces::onWorkspaceRenamed(std::string const &payload) {
spdlog::debug("Workspace renamed: {}", payload); spdlog::debug("Workspace renamed: {}", payload);
std::string workspaceIdStr = payload.substr(0, payload.find(',')); const auto [workspaceIdStr, newName] = splitDoublePayload(payload);
int workspaceId = workspaceIdStr == "special" ? -99 : std::stoi(workspaceIdStr);
std::string newName = payload.substr(payload.find(',') + 1); const auto workspaceId = parseWorkspaceId(workspaceIdStr);
if (!workspaceId.has_value()) {
return;
}
for (auto &workspace : m_workspaces) { for (auto &workspace : m_workspaces) {
if (workspace->id() == workspaceId) { if (workspace->id() == *workspaceId) {
if (workspace->name() == m_activeWorkspaceName) {
m_activeWorkspaceName = newName;
}
workspace->setName(newName); workspace->setName(newName);
break; break;
} }
@ -435,11 +454,19 @@ void Workspaces::onWorkspaceRenamed(std::string const &payload) {
void Workspaces::onMonitorFocused(std::string const &payload) { void Workspaces::onMonitorFocused(std::string const &payload) {
spdlog::trace("Monitor focused: {}", payload); spdlog::trace("Monitor focused: {}", payload);
m_activeWorkspaceName = payload.substr(payload.find(',') + 1);
const auto [monitorName, workspaceIdStr] = splitDoublePayload(payload);
const auto workspaceId = parseWorkspaceId(workspaceIdStr);
if (!workspaceId.has_value()) {
return;
}
m_activeWorkspaceId = *workspaceId;
for (Json::Value &monitor : m_ipc.getSocket1JsonReply("monitors")) { for (Json::Value &monitor : m_ipc.getSocket1JsonReply("monitors")) {
if (monitor["name"].asString() == payload.substr(0, payload.find(','))) { if (monitor["name"].asString() == monitorName) {
auto name = monitor["specialWorkspace"]["name"].asString(); const auto name = monitor["specialWorkspace"]["name"].asString();
m_activeSpecialWorkspaceName = !name.starts_with("special:") ? name : name.substr(8); m_activeSpecialWorkspaceName = !name.starts_with("special:") ? name : name.substr(8);
} }
} }
@ -478,11 +505,7 @@ void Workspaces::onWindowClosed(std::string const &addr) {
void Workspaces::onWindowMoved(std::string const &payload) { void Workspaces::onWindowMoved(std::string const &payload) {
spdlog::trace("Window moved: {}", payload); spdlog::trace("Window moved: {}", payload);
updateWindowCount(); updateWindowCount();
size_t lastCommaIdx = 0; auto [windowAddress, _, workspaceName] = splitTriplePayload(payload);
size_t nextCommaIdx = payload.find(',');
std::string windowAddress = payload.substr(lastCommaIdx, nextCommaIdx - lastCommaIdx);
std::string workspaceName = payload.substr(nextCommaIdx + 1, payload.length() - nextCommaIdx);
std::string windowRepr; std::string windowRepr;
@ -518,13 +541,15 @@ void Workspaces::onWindowTitleEvent(std::string const &payload) {
spdlog::trace("Window title changed: {}", payload); spdlog::trace("Window title changed: {}", payload);
std::optional<std::function<void(WindowCreationPayload)>> inserter; std::optional<std::function<void(WindowCreationPayload)>> inserter;
const auto [windowAddress, _] = splitDoublePayload(payload);
// If the window was an orphan, rename it at the orphan's vector // If the window was an orphan, rename it at the orphan's vector
if (m_orphanWindowMap.contains(payload)) { if (m_orphanWindowMap.contains(windowAddress)) {
inserter = [this](WindowCreationPayload wcp) { this->registerOrphanWindow(std::move(wcp)); }; inserter = [this](WindowCreationPayload wcp) { this->registerOrphanWindow(std::move(wcp)); };
} else { } else {
auto windowWorkspace = auto windowWorkspace = std::ranges::find_if(m_workspaces, [windowAddress](auto &workspace) {
std::find_if(m_workspaces.begin(), m_workspaces.end(), return workspace->containsWindow(windowAddress);
[payload](auto &workspace) { return workspace->containsWindow(payload); }); });
// If the window exists on a workspace, rename it at the workspace's window // If the window exists on a workspace, rename it at the workspace's window
// map // map
@ -533,9 +558,9 @@ void Workspaces::onWindowTitleEvent(std::string const &payload) {
(*windowWorkspace)->insertWindow(std::move(wcp)); (*windowWorkspace)->insertWindow(std::move(wcp));
}; };
} else { } else {
auto queuedWindow = std::find_if( auto queuedWindow = std::ranges::find_if(m_windowsToCreate, [payload](auto &windowPayload) {
m_windowsToCreate.begin(), m_windowsToCreate.end(), return windowPayload.getAddress() == payload;
[payload](auto &windowPayload) { return windowPayload.getAddress() == payload; }); });
// If the window was queued, rename it in the queue // If the window was queued, rename it in the queue
if (queuedWindow != m_windowsToCreate.end()) { if (queuedWindow != m_windowsToCreate.end()) {
@ -663,40 +688,58 @@ void Workspaces::registerOrphanWindow(WindowCreationPayload create_window_payloa
} }
auto Workspaces::registerIpc() -> void { auto Workspaces::registerIpc() -> void {
m_ipc.registerForIPC("workspace", this); m_ipc.registerForIPC("workspacev2", this);
m_ipc.registerForIPC("activespecial", this); m_ipc.registerForIPC("activespecial", this);
m_ipc.registerForIPC("createworkspace", this); m_ipc.registerForIPC("createworkspacev2", this);
m_ipc.registerForIPC("destroyworkspace", this); m_ipc.registerForIPC("destroyworkspacev2", this);
m_ipc.registerForIPC("focusedmon", this); m_ipc.registerForIPC("focusedmonv2", this);
m_ipc.registerForIPC("moveworkspace", this); m_ipc.registerForIPC("moveworkspacev2", this);
m_ipc.registerForIPC("renameworkspace", this); m_ipc.registerForIPC("renameworkspace", this);
m_ipc.registerForIPC("openwindow", this); m_ipc.registerForIPC("openwindow", this);
m_ipc.registerForIPC("closewindow", this); m_ipc.registerForIPC("closewindow", this);
m_ipc.registerForIPC("movewindow", this); m_ipc.registerForIPC("movewindowv2", this);
m_ipc.registerForIPC("urgent", this); m_ipc.registerForIPC("urgent", this);
m_ipc.registerForIPC("configreloaded", this); m_ipc.registerForIPC("configreloaded", this);
if (windowRewriteConfigUsesTitle()) { if (windowRewriteConfigUsesTitle()) {
spdlog::info( spdlog::info(
"Registering for Hyprland's 'windowtitle' events because a user-defined window " "Registering for Hyprland's 'windowtitlev2' events because a user-defined window "
"rewrite rule uses the 'title' field."); "rewrite rule uses the 'title' field.");
m_ipc.registerForIPC("windowtitle", this); m_ipc.registerForIPC("windowtitlev2", this);
} }
} }
void Workspaces::removeWorkspacesToRemove() { void Workspaces::removeWorkspacesToRemove() {
for (const auto &workspaceName : m_workspacesToRemove) { for (const auto &workspaceString : m_workspacesToRemove) {
removeWorkspace(workspaceName); removeWorkspace(workspaceString);
} }
m_workspacesToRemove.clear(); m_workspacesToRemove.clear();
} }
void Workspaces::removeWorkspace(std::string const &name) { void Workspaces::removeWorkspace(std::string const &workspaceString) {
spdlog::debug("Removing workspace {}", name); spdlog::debug("Removing workspace {}", workspaceString);
auto workspace =
std::find_if(m_workspaces.begin(), m_workspaces.end(), [&](std::unique_ptr<Workspace> &x) { // If this succeeds, we have a workspace ID.
return (name.starts_with("special:") && name.substr(8) == x->name()) || name == x->name(); const auto workspaceId = parseWorkspaceId(workspaceString);
});
std::string name;
// TODO: At some point we want to support all workspace selectors
// This is just a subset.
// https://wiki.hyprland.org/Configuring/Workspace-Rules/#workspace-selectors
if (workspaceString.starts_with("special:")) {
name = workspaceString.substr(8);
} else if (workspaceString.starts_with("name:")) {
name = workspaceString.substr(5);
} else {
name = workspaceString;
}
const auto workspace = std::ranges::find_if(m_workspaces, [&](std::unique_ptr<Workspace> &x) {
if (workspaceId.has_value()) {
return *workspaceId == x->id();
}
return name == x->name();
});
if (workspace == m_workspaces.end()) { if (workspace == m_workspaces.end()) {
// happens when a workspace on another monitor is destroyed // happens when a workspace on another monitor is destroyed
@ -704,7 +747,8 @@ void Workspaces::removeWorkspace(std::string const &name) {
} }
if ((*workspace)->isPersistentConfig()) { if ((*workspace)->isPersistentConfig()) {
spdlog::trace("Not removing config persistent workspace {}", name); spdlog::trace("Not removing config persistent workspace id={} name={}", (*workspace)->id(),
(*workspace)->name());
return; return;
} }
@ -728,62 +772,63 @@ void Workspaces::setCurrentMonitorId() {
} }
void Workspaces::sortWorkspaces() { void Workspaces::sortWorkspaces() {
std::sort(m_workspaces.begin(), m_workspaces.end(), std::ranges::sort( //
[&](std::unique_ptr<Workspace> &a, std::unique_ptr<Workspace> &b) { m_workspaces, [&](std::unique_ptr<Workspace> &a, std::unique_ptr<Workspace> &b) {
// Helper comparisons // Helper comparisons
auto isIdLess = a->id() < b->id(); auto isIdLess = a->id() < b->id();
auto isNameLess = a->name() < b->name(); auto isNameLess = a->name() < b->name();
switch (m_sortBy) { switch (m_sortBy) {
case SortMethod::ID: case SortMethod::ID:
return isIdLess; return isIdLess;
case SortMethod::NAME: case SortMethod::NAME:
return isNameLess; return isNameLess;
case SortMethod::NUMBER: case SortMethod::NUMBER:
try { try {
return std::stoi(a->name()) < std::stoi(b->name()); return std::stoi(a->name()) < std::stoi(b->name());
} catch (const std::invalid_argument &) { } catch (const std::invalid_argument &) {
// Handle the exception if necessary. // Handle the exception if necessary.
break; break;
} }
case SortMethod::DEFAULT: case SortMethod::DEFAULT:
default: default:
// Handle the default case here. // Handle the default case here.
// normal -> named persistent -> named -> special -> named special // normal -> named persistent -> named -> special -> named special
// both normal (includes numbered persistent) => sort by ID // both normal (includes numbered persistent) => sort by ID
if (a->id() > 0 && b->id() > 0) { if (a->id() > 0 && b->id() > 0) {
return isIdLess; return isIdLess;
} }
// one normal, one special => normal first // one normal, one special => normal first
if ((a->isSpecial()) ^ (b->isSpecial())) { if ((a->isSpecial()) ^ (b->isSpecial())) {
return b->isSpecial(); return b->isSpecial();
} }
// only one normal, one named // only one normal, one named
if ((a->id() > 0) ^ (b->id() > 0)) { if ((a->id() > 0) ^ (b->id() > 0)) {
return a->id() > 0; return a->id() > 0;
} }
// both special // both special
if (a->isSpecial() && b->isSpecial()) { if (a->isSpecial() && b->isSpecial()) {
// if one is -99 => put it last // if one is -99 => put it last
if (a->id() == -99 || b->id() == -99) { if (a->id() == -99 || b->id() == -99) {
return b->id() == -99; return b->id() == -99;
}
// both are 0 (not yet named persistents) / named specials (-98 <= ID <= -1)
return isNameLess;
}
// sort non-special named workspaces by name (ID <= -1377)
return isNameLess;
break;
} }
// both are 0 (not yet named persistents) / named specials
// (-98 <= ID <= -1)
return isNameLess;
}
// Return a default value if none of the cases match. // sort non-special named workspaces by name (ID <= -1377)
return isNameLess; // You can adjust this to your specific needs. return isNameLess;
}); break;
}
// Return a default value if none of the cases match.
return isNameLess; // You can adjust this to your specific needs.
});
for (size_t i = 0; i < m_workspaces.size(); ++i) { for (size_t i = 0; i < m_workspaces.size(); ++i) {
m_box.reorder_child(m_workspaces[i]->button(), i); m_box.reorder_child(m_workspaces[i]->button(), i);
@ -861,16 +906,17 @@ bool Workspaces::updateWindowsToCreate() {
} }
void Workspaces::updateWorkspaceStates() { void Workspaces::updateWorkspaceStates() {
const std::vector<std::string> visibleWorkspaces = getVisibleWorkspaces(); const std::vector<int> visibleWorkspaces = getVisibleWorkspaces();
auto updatedWorkspaces = m_ipc.getSocket1JsonReply("workspaces"); auto updatedWorkspaces = m_ipc.getSocket1JsonReply("workspaces");
for (auto &workspace : m_workspaces) { for (auto &workspace : m_workspaces) {
workspace->setActive(workspace->name() == m_activeWorkspaceName || workspace->setActive(
workspace->name() == m_activeSpecialWorkspaceName); workspace->id() == m_activeWorkspaceId ||
(workspace->isSpecial() && workspace->name() == m_activeSpecialWorkspaceName));
if (workspace->isActive() && workspace->isUrgent()) { if (workspace->isActive() && workspace->isUrgent()) {
workspace->setUrgent(false); workspace->setUrgent(false);
} }
workspace->setVisible(std::find(visibleWorkspaces.begin(), visibleWorkspaces.end(), workspace->setVisible(std::ranges::find(visibleWorkspaces, workspace->id()) !=
workspace->name()) != visibleWorkspaces.end()); visibleWorkspaces.end());
std::string &workspaceIcon = m_iconsMap[""]; std::string &workspaceIcon = m_iconsMap[""];
if (m_withIcon) { if (m_withIcon) {
workspaceIcon = workspace->selectIcon(m_iconsMap); workspaceIcon = workspace->selectIcon(m_iconsMap);
@ -908,4 +954,39 @@ int Workspaces::windowRewritePriorityFunction(std::string const &window_rule) {
return 0; return 0;
} }
template <typename... Args>
std::string Workspaces::makePayload(Args const &...args) {
std::ostringstream result;
bool first = true;
((result << (first ? "" : ",") << args, first = false), ...);
return result.str();
}
std::pair<std::string, std::string> Workspaces::splitDoublePayload(std::string const &payload) {
const std::string part1 = payload.substr(0, payload.find(','));
const std::string part2 = payload.substr(part1.size() + 1);
return {part1, part2};
}
std::tuple<std::string, std::string, std::string> Workspaces::splitTriplePayload(
std::string const &payload) {
const size_t firstComma = payload.find(',');
const size_t secondComma = payload.find(',', firstComma + 1);
const std::string part1 = payload.substr(0, firstComma);
const std::string part2 = payload.substr(firstComma + 1, secondComma - (firstComma + 1));
const std::string part3 = payload.substr(secondComma + 1);
return {part1, part2, part3};
}
std::optional<int> Workspaces::parseWorkspaceId(std::string const &workspaceIdStr) {
try {
return workspaceIdStr == "special" ? -99 : std::stoi(workspaceIdStr);
} catch (std::exception const &e) {
spdlog::error("Failed to parse workspace ID: {}", e.what());
return std::nullopt;
}
}
} // namespace waybar::modules::hyprland } // namespace waybar::modules::hyprland