#include "Workspace.hpp" #include "view/Group.hpp" #include "view/LayerSurface.hpp" #include "../Compositor.hpp" #include "../config/shared/parserUtils/ParserUtils.hpp" #include "../config/shared/animation/AnimationTree.hpp" #include "../config/shared/workspace/WorkspaceRuleManager.hpp" #include "../config/supplementary/executor/Executor.hpp" #include "managers/animation/AnimationManager.hpp" #include "../managers/EventManager.hpp" #include "../helpers/Monitor.hpp" #include "../layout/space/Space.hpp" #include "../layout/target/Target.hpp" #include "../layout/supplementary/WorkspaceAlgoMatcher.hpp" #include "../event/EventBus.hpp" #include #include using namespace Hyprutils::String; using namespace Desktop::View; PHLWORKSPACE CWorkspace::create(WORKSPACEID id, PHLMONITOR monitor, std::string name, bool special, bool isEmpty) { PHLWORKSPACE workspace = makeShared(id, monitor, name, special, isEmpty); workspace->init(workspace); g_pCompositor->registerWorkspace(workspace); return workspace; } CWorkspace::CWorkspace(WORKSPACEID id, PHLMONITOR monitor, std::string name, bool special, bool isEmpty) : m_id(id), m_name(name), m_monitor(monitor), m_isSpecialWorkspace(special), m_wasCreatedEmpty(isEmpty) { ; } void CWorkspace::init(PHLWORKSPACE self) { m_self = self; g_pAnimationManager->createAnimation( Vector2D(0, 0), m_renderOffset, Config::animationTree()->getAnimationPropertyConfig(m_isSpecialWorkspace ? "specialWorkspaceIn" : "workspacesIn"), self, AVARDAMAGE_ENTIRE); g_pAnimationManager->createAnimation(1.f, m_alpha, Config::animationTree()->getAnimationPropertyConfig(m_isSpecialWorkspace ? "specialWorkspaceIn" : "workspacesIn"), self, AVARDAMAGE_ENTIRE); const auto RULEFORTHIS = Config::workspaceRuleMgr()->getWorkspaceRuleFor(self).value_or(Config::CWorkspaceRule{}); if (RULEFORTHIS.m_defaultName.has_value()) m_name = RULEFORTHIS.m_defaultName.value(); if (RULEFORTHIS.m_animationStyle.has_value()) m_animationStyle = RULEFORTHIS.m_animationStyle.value(); m_focusedWindowHook = Event::bus()->m_events.window.close.listen([this](PHLWINDOW pWindow) { if (pWindow == m_lastFocusedWindow.lock()) m_lastFocusedWindow.reset(); }); m_space = Layout::CSpace::create(m_self.lock()); m_space->setAlgorithmProvider(Layout::Supplementary::algoMatcher()->createAlgorithmForWorkspace(m_self.lock())); m_inert = false; const auto WORKSPACERULE = Config::workspaceRuleMgr()->getWorkspaceRuleFor(self).value_or(Config::CWorkspaceRule{}); setPersistent(WORKSPACERULE.m_isPersistent.value_or(false)); if (self->m_wasCreatedEmpty) if (auto cmd = WORKSPACERULE.m_onCreatedEmptyRunCmd) Config::Supplementary::executor()->spawnWithRules(*cmd, self); g_pEventManager->postEvent({.event = "createworkspace", .data = m_name}); g_pEventManager->postEvent({.event = "createworkspacev2", .data = std::format("{},{}", m_id, m_name)}); Event::bus()->m_events.workspace.created.emit(self); } CWorkspace::~CWorkspace() { Log::logger->log(Log::DEBUG, "Destroying workspace ID {}", m_id); if (g_pEventManager) { g_pEventManager->postEvent({.event = "destroyworkspace", .data = m_name}); g_pEventManager->postEvent({.event = "destroyworkspacev2", .data = std::format("{},{}", m_id, m_name)}); } Event::bus()->m_events.workspace.removed.emit(m_self); m_events.destroy.emit(); } PHLWINDOW CWorkspace::getLastFocusedWindow() { if (!validMapped(m_lastFocusedWindow) || m_lastFocusedWindow->workspaceID() != m_id) return nullptr; return m_lastFocusedWindow.lock(); } PHLWINDOW CWorkspace::getFocusCandidate() { auto pWindow = getLastFocusedWindow(); if (!pWindow) pWindow = getTopLeftWindow(); if (!pWindow) pWindow = getFirstWindow(); return pWindow; } std::string CWorkspace::getConfigName() { if (m_isSpecialWorkspace) { return m_name; } if (m_id > 0) return std::to_string(m_id); return "name:" + m_name; } bool CWorkspace::matchesStaticSelector(const std::string& selector_) { auto selector = trim(selector_); if (selector.empty()) return true; if (isNumber(selector)) { const auto& [wsid, wsname, isAutoID] = getWorkspaceIDNameFromString(selector); if (wsid == WORKSPACE_INVALID) return false; return wsid == m_id; } else if (selector.starts_with("name:")) { return m_name == selector.substr(5); } else if (selector.starts_with("special")) { return m_name == selector; } else { // parse selector for (size_t i = 0; i < selector.length(); ++i) { const char& cur = selector[i]; if (std::isspace(cur)) continue; // Allowed selectors: // r - range: r[1-5] // s - special: s[true] // n - named: n[true] or n[s:string] or n[e:string] // m - monitor: m[monitor_selector] // w - windowCount: w[1-4] or w[1], optional flag t or f for tiled or floating and // flag p to count only pinned windows, e.g. w[p1-2], w[pg4] // flag g to count groups instead of windows, e.g. w[t1-2], w[fg4] // flag v will count only visible windows // f - fullscreen state : f[-1], f[0], f[1], or f[2] for different fullscreen states // -1: no fullscreen, 0: fullscreen, 1: maximized, 2: fullscreen without sending fs state to window const auto CLOSING_BRACKET = selector.find_first_of(']', i); std::string prop = selector.substr(i, CLOSING_BRACKET == std::string::npos ? std::string::npos : CLOSING_BRACKET + 1 - i); i = std::min(CLOSING_BRACKET, std::string::npos - 1); if (cur == 'r') { WORKSPACEID from = 0, to = 0; if (!prop.starts_with("r[") || !prop.ends_with("]")) { Log::logger->log(Log::DEBUG, "Invalid selector {}", selector); return false; } prop = prop.substr(2, prop.length() - 3); if (!prop.contains("-")) { Log::logger->log(Log::DEBUG, "Invalid selector {}", selector); return false; } const auto DASHPOS = prop.find('-'); const auto LHS = prop.substr(0, DASHPOS), RHS = prop.substr(DASHPOS + 1); if (!isNumber(LHS) || !isNumber(RHS)) { Log::logger->log(Log::DEBUG, "Invalid selector {}", selector); return false; } try { from = std::stoll(LHS); to = std::stoll(RHS); } catch (std::exception& e) { Log::logger->log(Log::DEBUG, "Invalid selector {}", selector); return false; } if (to < from || to < 1 || from < 1) { Log::logger->log(Log::DEBUG, "Invalid selector {}", selector); return false; } if (std::clamp(m_id, from, to) != m_id) return false; continue; } if (cur == 's') { if (!prop.starts_with("s[") || !prop.ends_with("]")) { Log::logger->log(Log::DEBUG, "Invalid selector {}", selector); return false; } prop = prop.substr(2, prop.length() - 3); const auto SHOULDBESPECIAL = Config::ParserUtils::parseInt(prop); if (SHOULDBESPECIAL && sc(*SHOULDBESPECIAL) != m_isSpecialWorkspace) return false; continue; } if (cur == 'm') { if (!prop.starts_with("m[") || !prop.ends_with("]")) { Log::logger->log(Log::DEBUG, "Invalid selector {}", selector); return false; } prop = prop.substr(2, prop.length() - 3); const auto PMONITOR = g_pCompositor->getMonitorFromString(prop); if (!(PMONITOR ? PMONITOR == m_monitor : false)) return false; continue; } if (cur == 'n') { if (!prop.starts_with("n[") || !prop.ends_with("]")) { Log::logger->log(Log::DEBUG, "Invalid selector {}", selector); return false; } prop = prop.substr(2, prop.length() - 3); if (prop.starts_with("s:") && !m_name.starts_with(prop.substr(2))) return false; if (prop.starts_with("e:") && !m_name.ends_with(prop.substr(2))) return false; const auto WANTSNAMED = Config::ParserUtils::parseInt(prop); if (WANTSNAMED && *WANTSNAMED != (m_id <= -1337)) return false; continue; } if (cur == 'w') { WORKSPACEID from = 0, to = 0; if (!prop.starts_with("w[") || !prop.ends_with("]")) { Log::logger->log(Log::DEBUG, "Invalid selector {}", selector); return false; } prop = prop.substr(2, prop.length() - 3); int wantsOnlyTiled = -1; int wantsOnlyPinned = false; bool wantsCountGroup = false; bool wantsCountVisible = false; int flagCount = 0; for (auto const& flag : prop) { if (flag == 't' && wantsOnlyTiled == -1) { wantsOnlyTiled = 1; flagCount++; } else if (flag == 'f' && wantsOnlyTiled == -1) { wantsOnlyTiled = 0; flagCount++; } else if (flag == 'p' && !wantsOnlyPinned) { wantsOnlyPinned = true; flagCount++; } else if (flag == 'g' && !wantsCountGroup) { wantsCountGroup = true; flagCount++; } else if (flag == 'v' && !wantsCountVisible) { wantsCountVisible = true; flagCount++; } else { break; } } prop = prop.substr(flagCount); if (!prop.contains("-")) { // try single if (!isNumber(prop)) { Log::logger->log(Log::DEBUG, "Invalid selector {}", selector); return false; } try { from = std::stoll(prop); } catch (std::exception& e) { Log::logger->log(Log::DEBUG, "Invalid selector {}", selector); return false; } int count; if (wantsCountGroup) count = getGroups(wantsOnlyTiled == -1 ? std::nullopt : std::optional(sc(wantsOnlyTiled)), wantsOnlyPinned ? std::optional(wantsOnlyPinned) : std::nullopt, wantsCountVisible ? std::optional(wantsCountVisible) : std::nullopt); else count = getWindows(wantsOnlyTiled == -1 ? std::nullopt : std::optional(sc(wantsOnlyTiled)), wantsOnlyPinned ? std::optional(wantsOnlyPinned) : std::nullopt, wantsCountVisible ? std::optional(wantsCountVisible) : std::nullopt); if (count != from) return false; continue; } const auto DASHPOS = prop.find('-'); const auto LHS = prop.substr(0, DASHPOS), RHS = prop.substr(DASHPOS + 1); if (!isNumber(LHS) || !isNumber(RHS)) { Log::logger->log(Log::DEBUG, "Invalid selector {}", selector); return false; } try { from = std::stoll(LHS); to = std::stoll(RHS); } catch (std::exception& e) { Log::logger->log(Log::DEBUG, "Invalid selector {}", selector); return false; } if (to < from || to < 1 || from < 1) { Log::logger->log(Log::DEBUG, "Invalid selector {}", selector); return false; } WORKSPACEID count; if (wantsCountGroup) count = getGroups(wantsOnlyTiled == -1 ? std::nullopt : std::optional(sc(wantsOnlyTiled)), wantsOnlyPinned ? std::optional(wantsOnlyPinned) : std::nullopt, wantsCountVisible ? std::optional(wantsCountVisible) : std::nullopt); else count = getWindows(wantsOnlyTiled == -1 ? std::nullopt : std::optional(sc(wantsOnlyTiled)), wantsOnlyPinned ? std::optional(wantsOnlyPinned) : std::nullopt, wantsCountVisible ? std::optional(wantsCountVisible) : std::nullopt); if (std::clamp(count, from, to) != count) return false; continue; } if (cur == 'f') { if (!prop.starts_with("f[") || !prop.ends_with("]")) { Log::logger->log(Log::DEBUG, "Invalid selector {}", selector); return false; } prop = prop.substr(2, prop.length() - 3); int FSSTATE = -1; try { FSSTATE = std::stoi(prop); } catch (std::exception& e) { Log::logger->log(Log::DEBUG, "Invalid selector {}", selector); return false; } switch (FSSTATE) { case -1: // no fullscreen if (m_hasFullscreenWindow) return false; break; case 0: // fullscreen full if (!m_hasFullscreenWindow || m_fullscreenMode != FSMODE_FULLSCREEN) return false; break; case 1: // maximized if (!m_hasFullscreenWindow || m_fullscreenMode != FSMODE_MAXIMIZED) return false; break; default: break; } continue; } Log::logger->log(Log::DEBUG, "Invalid selector {}", selector); return false; } return true; } UNREACHABLE(); return false; } void CWorkspace::markInert() { m_inert = true; m_id = WORKSPACE_INVALID; m_visible = false; m_monitor.reset(); } bool CWorkspace::inert() { return m_inert; } MONITORID CWorkspace::monitorID() { return m_monitor ? m_monitor->m_id : MONITOR_INVALID; } PHLWINDOW CWorkspace::getFullscreenWindow() { for (auto const& w : g_pCompositor->m_windows) { if (w->m_workspace == m_self && w->isFullscreen()) return w; } return nullptr; } bool CWorkspace::isVisible() { return m_visible; } bool CWorkspace::isVisibleNotCovered() { const auto PMONITOR = m_monitor.lock(); if (!PMONITOR) return false; if (PMONITOR->m_activeSpecialWorkspace) return PMONITOR->m_activeSpecialWorkspace->m_id == m_id; return PMONITOR->m_activeWorkspace->m_id == m_id; } int CWorkspace::getWindows(std::optional onlyTiled, std::optional onlyPinned, std::optional onlyVisible) { int no = 0; if (!m_space) return 0; for (auto const& t : m_space->targets()) { if (!t) continue; const auto visibilityFulfilled = t->window() && !t->window()->isHidden() && !t->window()->isInputBlocked(INPUT_BLOCK_GROUP_INACTIVE | INPUT_BLOCK_MONOCLE_INACTIVE | INPUT_BLOCK_BELOW_FULLSCREEN); if (onlyTiled.has_value() && t->floating() == onlyTiled.value()) continue; if (onlyPinned.has_value() && (!t->window() || t->window()->m_pinned != onlyPinned.value())) continue; if (onlyVisible.has_value() && (!t->window() || visibilityFulfilled != onlyVisible.value())) continue; no++; } return no; } int CWorkspace::getGroups(std::optional onlyTiled, std::optional onlyPinned, std::optional onlyVisible) { int no = 0; for (auto const& g : Desktop::View::groups()) { const auto HEAD = g->head(); const auto visibilityFulfilled = g->current() && !g->current()->isHidden() && !g->current()->isInputBlocked(INPUT_BLOCK_GROUP_INACTIVE | INPUT_BLOCK_MONOCLE_INACTIVE | INPUT_BLOCK_BELOW_FULLSCREEN); if (HEAD->workspaceID() != m_id || !HEAD->m_isMapped) continue; if (onlyTiled.has_value() && HEAD->m_isFloating == onlyTiled.value()) continue; if (onlyPinned.has_value() && HEAD->m_pinned != onlyPinned.value()) continue; if (onlyVisible.has_value() && visibilityFulfilled != onlyVisible.value()) continue; no++; } return no; } PHLWINDOW CWorkspace::getFirstWindow() { for (auto const& w : g_pCompositor->m_windows) { if (w->m_workspace == m_self && w->m_isMapped && w->acceptsInput()) return w; } return nullptr; } PHLWINDOW CWorkspace::getTopLeftWindow() { const auto PMONITOR = m_monitor.lock(); for (auto const& w : g_pCompositor->m_windows) { if (w->m_workspace != m_self || !w->m_isMapped || !w->acceptsInput()) continue; const auto WINDOWIDEALBB = w->getWindowIdealBoundingBoxIgnoreReserved(); if (WINDOWIDEALBB.x <= PMONITOR->m_position.x + 1 && WINDOWIDEALBB.y <= PMONITOR->m_position.y + 1) return w; } return nullptr; } bool CWorkspace::hasUrgentWindow() { return std::ranges::any_of(g_pCompositor->m_windows, [this](const auto& w) { return w->m_workspace == m_self && w->m_isMapped && w->m_isUrgent; }); } void CWorkspace::updateWindowDecos() { for (auto const& w : g_pCompositor->m_windows) { if (w->m_workspace != m_self) continue; w->updateWindowDecos(); } } void CWorkspace::updateWindowData() { const auto WORKSPACERULE = Config::workspaceRuleMgr()->getWorkspaceRuleFor(m_self.lock()); for (auto const& w : g_pCompositor->m_windows) { if (w->m_workspace != m_self) continue; w->updateWindowData(WORKSPACERULE.value_or(Config::CWorkspaceRule{})); } } void CWorkspace::forceReportSizesToWindows() { for (auto const& w : g_pCompositor->m_windows) { if (w->m_workspace != m_self || !w->m_isMapped || w->isHidden()) continue; w->sendWindowSize(true); } } void CWorkspace::rename(const std::string& name) { if (g_pCompositor->isWorkspaceSpecial(m_id)) return; Log::logger->log(Log::DEBUG, "CWorkspace::rename: Renaming workspace {} to '{}'", m_id, name); m_name = name; const auto WORKSPACERULE = Config::workspaceRuleMgr()->getWorkspaceRuleFor(m_self.lock()).value_or(Config::CWorkspaceRule{}); setPersistent(WORKSPACERULE.m_isPersistent.value_or(false)); if (WORKSPACERULE.m_isPersistent.value_or(false)) g_pCompositor->ensurePersistentWorkspacesPresent(std::vector{WORKSPACERULE}, m_self.lock()); g_pEventManager->postEvent({.event = "renameworkspace", .data = std::to_string(m_id) + "," + m_name}); m_events.renamed.emit(); } void CWorkspace::updateWindows() { m_hasFullscreenWindow = std::ranges::any_of(m_space->targets(), [](const auto& t) { return t && t->fullscreenMode() != FSMODE_NONE && !t->layoutManagedFullscreen(); }); if (!m_hasFullscreenWindow) m_fullscreenMode = FSMODE_NONE; for (auto const& t : m_space->targets()) { if (t->window()) t->window()->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_ON_WORKSPACE); } } void CWorkspace::setPersistent(bool persistent) { if (m_persistent == persistent) return; m_persistent = persistent; if (persistent) m_selfPersistent = m_self.lock(); else m_selfPersistent.reset(); } bool CWorkspace::isPersistent() { return m_persistent; } void CWorkspace::setNoMembersAboveFullscreen() { // make all windows and layers on the same workspace under the fullscreen window for (auto const& w : g_pCompositor->m_windows) { if (w->m_workspace == m_self && !w->isFullscreen() && !w->m_fadingOut && !w->m_pinned) w->m_createdOverFullscreen = false; } for (auto const& ls : g_pCompositor->m_layers) { if (ls->m_monitor == m_monitor) ls->m_aboveFullscreen = false; } }