#include "MonocleAlgorithm.hpp" #include "../../Algorithm.hpp" #include "../../../space/Space.hpp" #include "../../../target/WindowTarget.hpp" #include "../../../LayoutManager.hpp" #include "../../../../config/ConfigValue.hpp" #include "../../../../desktop/state/FocusState.hpp" #include "../../../../desktop/history/WindowHistoryTracker.hpp" #include "../../../../helpers/Monitor.hpp" #include "../../../../Compositor.hpp" #include "../../../../event/EventBus.hpp" #include #include #include using namespace Hyprutils::String; using namespace Hyprutils::Utils; using namespace Layout; using namespace Layout::Tiled; CMonocleAlgorithm::CMonocleAlgorithm() { // hook into focus changes to bring focused window to front m_focusCallback = Event::bus()->m_events.window.active.listen([this](PHLWINDOW pWindow, Desktop::eFocusReason reason) { if (!pWindow) return; if (!pWindow->m_workspace->isVisible()) return; const auto TARGET = pWindow->layoutTarget(); if (!TARGET) return; focusTargetUpdate(TARGET); }); } CMonocleAlgorithm::~CMonocleAlgorithm() { // unhide all windows before destruction for (const auto& data : m_targetDatas) { const auto TARGET = data->target.lock(); if (!TARGET) continue; const auto WINDOW = TARGET->window(); if (WINDOW) WINDOW->setHidden(false); } m_focusCallback.reset(); } SP CMonocleAlgorithm::dataFor(SP t) { for (auto& data : m_targetDatas) { if (data->target.lock() == t) return data; } return nullptr; } void CMonocleAlgorithm::newTarget(SP target) { const auto DATA = m_targetDatas.emplace_back(makeShared(target)); m_currentVisibleIndex = m_targetDatas.size() - 1; recalculate(); } void CMonocleAlgorithm::movedTarget(SP target, std::optional focalPoint) { newTarget(target); } void CMonocleAlgorithm::removeTarget(SP target) { auto it = std::ranges::find_if(m_targetDatas, [target](const auto& data) { return data->target.lock() == target; }); if (it == m_targetDatas.end()) return; // unhide window when removing from monocle layout const auto WINDOW = target->window(); if (WINDOW) WINDOW->setHidden(false); const auto INDEX = std::distance(m_targetDatas.begin(), it); m_targetDatas.erase(it); if (m_targetDatas.empty()) { m_currentVisibleIndex = 0; return; } // try to use the last window in history if we can for (const auto& historyWindow : Desktop::History::windowTracker()->historyForWorkspace(m_parent->space()->workspace()) | std::views::reverse) { auto it = std::ranges::find_if(m_targetDatas, [&historyWindow](const auto& d) { return d->target == historyWindow->layoutTarget(); }); if (it == m_targetDatas.end()) continue; // we found a historical target, use that first m_currentVisibleIndex = std::distance(m_targetDatas.begin(), it); recalculate(); return; } // if we didn't find history, fall back to last if (m_currentVisibleIndex >= (int)m_targetDatas.size()) m_currentVisibleIndex = m_targetDatas.size() - 1; else if (INDEX <= m_currentVisibleIndex && m_currentVisibleIndex > 0) m_currentVisibleIndex--; recalculate(); } void CMonocleAlgorithm::resizeTarget(const Vector2D& Δ, SP target, eRectCorner corner) { // monocle layout doesn't support manual resizing, all windows are fullscreen } void CMonocleAlgorithm::recalculate() { if (m_targetDatas.empty()) return; const auto WORK_AREA = m_parent->space()->workArea(); for (size_t i = 0; i < m_targetDatas.size(); ++i) { const auto& DATA = m_targetDatas[i]; const auto TARGET = DATA->target.lock(); if (!TARGET) continue; const auto WINDOW = TARGET->window(); if (!WINDOW) continue; DATA->layoutBox = WORK_AREA; TARGET->setPositionGlobal(WORK_AREA); const bool SHOULD_BE_VISIBLE = ((int)i == m_currentVisibleIndex); WINDOW->setHidden(!SHOULD_BE_VISIBLE); } } SP CMonocleAlgorithm::getNextCandidate(SP old) { if (m_targetDatas.empty()) return nullptr; auto it = std::ranges::find_if(m_targetDatas, [old](const auto& data) { return data->target.lock() == old; }); if (it == m_targetDatas.end()) { if (m_currentVisibleIndex >= 0 && m_currentVisibleIndex < (int)m_targetDatas.size()) return m_targetDatas[m_currentVisibleIndex]->target.lock(); return nullptr; } auto next = std::next(it); if (next == m_targetDatas.end()) next = m_targetDatas.begin(); return next->get()->target.lock(); } std::expected CMonocleAlgorithm::layoutMsg(const std::string_view& sv) { CVarList2 vars(std::string{sv}, 0, 's'); if (vars.size() < 1) return std::unexpected("layoutmsg requires at least 1 argument"); const auto COMMAND = vars[0]; if (COMMAND == "cyclenext") { cycleNext(); return {}; } else if (COMMAND == "cycleprev") { cyclePrev(); return {}; } return std::unexpected(std::format("Unknown monocle layoutmsg: {}", COMMAND)); } std::optional CMonocleAlgorithm::predictSizeForNewTarget() { const auto WORK_AREA = m_parent->space()->workArea(); return WORK_AREA.size(); } void CMonocleAlgorithm::swapTargets(SP a, SP b) { auto nodeA = dataFor(a); auto nodeB = dataFor(b); if (nodeA) nodeA->target = b; if (nodeB) nodeB->target = a; recalculate(); } void CMonocleAlgorithm::moveTargetInDirection(SP t, Math::eDirection dir, bool silent) { static auto PMONITORFALLBACK = CConfigValue("binds:window_direction_monitor_fallback"); if (!*PMONITORFALLBACK) return; // noop // try to find a monitor in the specified direction, thats the logical thing if (!t || !t->space() || !t->space()->workspace()) return; const auto PMONITOR = t->space()->workspace()->m_monitor.lock(); const auto PMONINDIR = g_pCompositor->getMonitorInDirection(PMONITOR, dir); // if we found a monitor, move the window there if (PMONINDIR && PMONINDIR != PMONITOR) { const auto TARGETWS = PMONINDIR->m_activeWorkspace; if (t->window()) t->window()->setAnimationsToMove(); t->assignToSpace(TARGETWS->m_space, focalPointForDir(t, dir)); } } void CMonocleAlgorithm::cycleNext() { if (m_targetDatas.empty()) return; m_currentVisibleIndex = (m_currentVisibleIndex + 1) % m_targetDatas.size(); updateVisible(); } void CMonocleAlgorithm::cyclePrev() { if (m_targetDatas.empty()) return; m_currentVisibleIndex--; if (m_currentVisibleIndex < 0) m_currentVisibleIndex = m_targetDatas.size() - 1; updateVisible(); } void CMonocleAlgorithm::focusTargetUpdate(SP target) { auto it = std::ranges::find_if(m_targetDatas, [target](const auto& data) { return data->target.lock() == target; }); if (it == m_targetDatas.end()) return; const auto NEW_INDEX = std::distance(m_targetDatas.begin(), it); if (m_currentVisibleIndex != NEW_INDEX) { m_currentVisibleIndex = NEW_INDEX; updateVisible(); } } void CMonocleAlgorithm::updateVisible() { recalculate(); const auto VISIBLE_TARGET = getVisibleTarget(); if (!VISIBLE_TARGET) return; const auto WINDOW = VISIBLE_TARGET->window(); if (!WINDOW) return; Desktop::focusState()->fullWindowFocus(WINDOW, Desktop::FOCUS_REASON_DESKTOP_STATE_CHANGE); } SP CMonocleAlgorithm::getVisibleTarget() { if (m_currentVisibleIndex < 0 || m_currentVisibleIndex >= (int)m_targetDatas.size()) return nullptr; return m_targetDatas[m_currentVisibleIndex]->target.lock(); }