diff --git a/hyprtester/src/tests/main/scroll.cpp b/hyprtester/src/tests/main/scroll.cpp index bd33e345e..f0f1bbb97 100644 --- a/hyprtester/src/tests/main/scroll.cpp +++ b/hyprtester/src/tests/main/scroll.cpp @@ -176,7 +176,7 @@ TEST_CASE(scrollSwapcolWrapping) { } TEST_CASE(scrollWindowRule) { - OK(getFromSocket("r/eval hl.config({ general = { layout = 'scrolling' } })")); + OK(getFromSocket("/eval hl.config({ general = { layout = 'scrolling' } })")); NLog::log("{}Testing Scrolling Width", Colors::GREEN); @@ -195,5 +195,47 @@ TEST_CASE(scrollWindowRule) { ASSERT(Tests::windowCount(), 2); // not the greatest test, but as long as res and gaps don't change, we good. - EXPECT_CONTAINS(getFromSocket("/activewindow"), "size: 174,1036"); + EXPECT_CONTAINS(getFromSocket("/activewindow"), "size: 179,1036"); +} + +TEST_CASE(scrollFullscreen) { + OK(getFromSocket("/eval hl.config({ general = { layout = 'scrolling' } })")); + + NLog::log("{}Testing Scrolling FS", Colors::GREEN); + + ASSERT(!!Tests::spawnKitty("kitty_scroll_A"), true); + ASSERT(!!Tests::spawnKitty("kitty_scroll_B"), true); + ASSERT(!!Tests::spawnKitty("kitty_scroll_C"), true); + + OK(getFromSocket("/dispatch hl.dsp.focus({ window = \"class:kitty_scroll_B\" })")); + OK(getFromSocket("/dispatch hl.dsp.window.fullscreen()")); + + { + auto str = getFromSocket("/activewindow"); + ASSERT_CONTAINS(str, "size: 1920,1080"); + ASSERT_CONTAINS(str, "class: kitty_scroll_B"); + } + + OK(getFromSocket("/dispatch hl.dsp.focus({ direction = \"left\" })")); + + { + auto str = getFromSocket("/activewindow"); + ASSERT_CONTAINS(str, "class: kitty_scroll_A"); + } + + OK(getFromSocket("/dispatch hl.dsp.focus({ direction = \"right\" })")); + OK(getFromSocket("/dispatch hl.dsp.focus({ direction = \"right\" })")); + + { + auto str = getFromSocket("/activewindow"); + ASSERT_CONTAINS(str, "class: kitty_scroll_C"); + } + + OK(getFromSocket("/dispatch hl.dsp.focus({ direction = \"left\" })")); + + { + auto str = getFromSocket("/activewindow"); + ASSERT_CONTAINS(str, "size: 1920,1080"); + ASSERT_CONTAINS(str, "class: kitty_scroll_B"); + } } diff --git a/src/Compositor.cpp b/src/Compositor.cpp index b5dba342e..bf3cf6242 100644 --- a/src/Compositor.cpp +++ b/src/Compositor.cpp @@ -1462,10 +1462,10 @@ PHLWINDOW CCompositor::getWindowInDirection(PHLWINDOW pWindow, Math::eDirection if (!PWORKSPACE) return nullptr; // ?? - return getWindowInDirection(WINDOWIDEALBB, PWORKSPACE, dir, pWindow, pWindow->m_isFloating); + return getWindowInDirection(WINDOWIDEALBB, PWORKSPACE, dir, pWindow->m_isFloating, pWindow, pWindow->m_isFloating); } -PHLWINDOW CCompositor::getWindowInDirection(const CBox& box, PHLWORKSPACE pWorkspace, Math::eDirection dir, PHLWINDOW ignoreWindow, bool useVectorAngles) { +PHLWINDOW CCompositor::getWindowInDirection(const CBox& box, PHLWORKSPACE pWorkspace, Math::eDirection dir, bool floatingPreference, PHLWINDOW ignoreWindow, bool useVectorAngles) { if (dir == Math::DIRECTION_DEFAULT) return nullptr; @@ -1509,71 +1509,93 @@ PHLWINDOW CCompositor::getWindowInDirection(const CBox& box, PHLWORKSPACE pWorks return overlap <= std::min(sizeA, sizeB) * MAX_OVERLAP_RATIO; }; - for (auto const& w : m_windows) { - if (w == ignoreWindow || !w->m_workspace || !w->m_isMapped || !w->acceptsInput() || (!w->isFullscreen() && w->m_isFloating) || !w->m_workspace->isVisible()) - continue; + auto find = [&]() { + for (auto const& w : m_windows) { + if (w == ignoreWindow || !w->m_workspace || !w->m_isMapped || (!w->isFullscreen() && w->m_isFloating) || !w->m_workspace->isVisible()) + continue; - if (pWorkspace->m_monitor == w->m_monitor && pWorkspace != w->m_workspace) - continue; + if (w->isHidden()) + continue; - if (pWorkspace->m_hasFullscreenWindow && !w->isAllowedOverFullscreen()) - continue; + // check if the input is blocked by anything except BELOW_FULLSCREEN + if (w->isInputBlocked(INPUT_BLOCK_ALL & (~INPUT_BLOCK_BELOW_FULLSCREEN))) + continue; - if (!*PMONITORFALLBACK && pWorkspace->m_monitor != w->m_monitor) - continue; + if (pWorkspace->m_monitor == w->m_monitor && pWorkspace != w->m_workspace) + continue; - const auto BWINDOWIDEALBB = w->getWindowIdealBoundingBoxIgnoreReserved(); + if (pWorkspace->m_hasFullscreenWindow && !w->isAllowedOverFullscreen()) + continue; - const auto POSB = Vector2D(BWINDOWIDEALBB.x, BWINDOWIDEALBB.y); - const auto SIZEB = Vector2D(BWINDOWIDEALBB.width, BWINDOWIDEALBB.height); + if (!*PMONITORFALLBACK && pWorkspace->m_monitor != w->m_monitor) + continue; - double intersectLength = -1; + if (w->m_isFloating != floatingPreference) + continue; - switch (dir) { - case Math::DIRECTION_LEFT: - if (isAdjacent(POSA.x, POSA.x + SIZEA.x, POSB.x, POSB.x + SIZEB.x)) - intersectLength = std::max(0.0, std::min(POSA.y + SIZEA.y, POSB.y + SIZEB.y) - std::max(POSA.y, POSB.y)); - break; - case Math::DIRECTION_RIGHT: - if (isAdjacent(POSB.x, POSB.x + SIZEB.x, POSA.x, POSA.x + SIZEA.x)) - intersectLength = std::max(0.0, std::min(POSA.y + SIZEA.y, POSB.y + SIZEB.y) - std::max(POSA.y, POSB.y)); - break; - case Math::DIRECTION_UP: - if (isAdjacent(POSA.y, POSA.y + SIZEA.y, POSB.y, POSB.y + SIZEB.y)) - intersectLength = std::max(0.0, std::min(POSA.x + SIZEA.x, POSB.x + SIZEB.x) - std::max(POSA.x, POSB.x)); - break; - case Math::DIRECTION_DOWN: - if (isAdjacent(POSB.y, POSB.y + SIZEB.y, POSA.y, POSA.y + SIZEA.y)) - intersectLength = std::max(0.0, std::min(POSA.x + SIZEA.x, POSB.x + SIZEB.x) - std::max(POSA.x, POSB.x)); - break; - default: break; - } + const auto BWINDOWIDEALBB = w->getWindowIdealBoundingBoxIgnoreReserved(); - if (*PMETHOD == 0 /* history */) { - if (intersectLength > 0) { + const auto POSB = Vector2D(BWINDOWIDEALBB.x, BWINDOWIDEALBB.y); + const auto SIZEB = Vector2D(BWINDOWIDEALBB.width, BWINDOWIDEALBB.height); - // get idx - int windowIDX = -1; - const auto& HISTORY = Desktop::History::windowTracker()->fullHistory(); - for (int64_t i = HISTORY.size() - 1; i >= 0; --i) { - if (HISTORY[i] == w) { - windowIDX = i; - break; + double intersectLength = -1; + + switch (dir) { + case Math::DIRECTION_LEFT: + if (isAdjacent(POSA.x, POSA.x + SIZEA.x, POSB.x, POSB.x + SIZEB.x)) + intersectLength = std::max(0.0, std::min(POSA.y + SIZEA.y, POSB.y + SIZEB.y) - std::max(POSA.y, POSB.y)); + break; + case Math::DIRECTION_RIGHT: + if (isAdjacent(POSB.x, POSB.x + SIZEB.x, POSA.x, POSA.x + SIZEA.x)) + intersectLength = std::max(0.0, std::min(POSA.y + SIZEA.y, POSB.y + SIZEB.y) - std::max(POSA.y, POSB.y)); + break; + case Math::DIRECTION_UP: + if (isAdjacent(POSA.y, POSA.y + SIZEA.y, POSB.y, POSB.y + SIZEB.y)) + intersectLength = std::max(0.0, std::min(POSA.x + SIZEA.x, POSB.x + SIZEB.x) - std::max(POSA.x, POSB.x)); + break; + case Math::DIRECTION_DOWN: + if (isAdjacent(POSB.y, POSB.y + SIZEB.y, POSA.y, POSA.y + SIZEA.y)) + intersectLength = std::max(0.0, std::min(POSA.x + SIZEA.x, POSB.x + SIZEB.x) - std::max(POSA.x, POSB.x)); + break; + default: break; + } + + if (*PMETHOD == 0 /* history */) { + if (intersectLength > 0) { + + // get idx + int windowIDX = -1; + const auto& HISTORY = Desktop::History::windowTracker()->fullHistory(); + for (int64_t i = HISTORY.size() - 1; i >= 0; --i) { + if (HISTORY[i] == w) { + windowIDX = i; + break; + } + } + + if (windowIDX > leaderValue) { + leaderValue = windowIDX; + leaderWindow = w; } } - - if (windowIDX > leaderValue) { - leaderValue = windowIDX; + } else /* length */ { + if (intersectLength > leaderValue) { + leaderValue = intersectLength; leaderWindow = w; } } - } else /* length */ { - if (intersectLength > leaderValue) { - leaderValue = intersectLength; - leaderWindow = w; - } } + }; + + // Find the window, then if we don't find one with preferred + // float status, try the opposite. + find(); + + if (!leaderWindow) { + floatingPreference = !floatingPreference; + find(); } + } else { static const std::unordered_map VECTORS = { {Math::DIRECTION_RIGHT, {1, 0}}, {Math::DIRECTION_UP, {0, -1}}, {Math::DIRECTION_DOWN, {0, 1}}, {Math::DIRECTION_LEFT, {-1, 0}}}; @@ -2248,9 +2270,16 @@ void CCompositor::setWindowFullscreenState(const PHLWINDOW PWINDOW, Desktop::Vie PWORKSPACE->m_fullscreenMode = NEW_EFFECTIVE_MODE; PWORKSPACE->m_hasFullscreenWindow = NEW_EFFECTIVE_MODE != FSMODE_NONE; - g_layoutManager->fullscreenRequestForTarget(PWINDOW->layoutTarget(), OLD_EFFECTIVE_MODE, NEW_EFFECTIVE_MODE); + PWORKSPACE->setNoMembersAboveFullscreen(); - PWINDOW->m_fullscreenState.internal = state.internal; + const auto FULLSCREEN_REQUEST_RESULT = g_layoutManager->fullscreenRequestForTarget(PWINDOW->layoutTarget(), OLD_EFFECTIVE_MODE, NEW_EFFECTIVE_MODE); + const bool LAYOUT_HANDLED_FULLSCREEN = FULLSCREEN_REQUEST_RESULT == Layout::FULLSCREEN_REQUEST_HANDLED_BY_LAYOUT; + + if (LAYOUT_HANDLED_FULLSCREEN) { + PWORKSPACE->m_fullscreenMode = FSMODE_NONE; + PWORKSPACE->m_hasFullscreenWindow = false; + } else + PWINDOW->m_fullscreenState.internal = state.internal; g_pEventManager->postEvent(SHyprIPCEvent{.event = "fullscreen", .data = std::to_string(sc(NEW_EFFECTIVE_MODE) != FSMODE_NONE)}); Event::bus()->m_events.window.fullscreen.emit(PWINDOW); @@ -2275,8 +2304,9 @@ void CCompositor::setWindowFullscreenState(const PHLWINDOW PWINDOW, Desktop::Vie ls->m_aboveFullscreen = false; } - g_pDesktopAnimationManager->setFullscreenFadeAnimation( - PWORKSPACE, PWORKSPACE->m_hasFullscreenWindow ? CDesktopAnimationManager::ANIMATION_TYPE_IN : CDesktopAnimationManager::ANIMATION_TYPE_OUT); + if (!LAYOUT_HANDLED_FULLSCREEN) + g_pDesktopAnimationManager->setFullscreenFadeAnimation( + PWORKSPACE, PWORKSPACE->m_hasFullscreenWindow ? CDesktopAnimationManager::ANIMATION_TYPE_IN : CDesktopAnimationManager::ANIMATION_TYPE_OUT); PWINDOW->sendWindowSize(true); @@ -2290,7 +2320,7 @@ void CCompositor::setWindowFullscreenState(const PHLWINDOW PWINDOW, Desktop::Vie // send a scanout tranche if we are entering fullscreen, and send a regular one if we aren't. // ignore if DS is disabled. - if (*PDIRECTSCANOUT == 1 || (*PDIRECTSCANOUT == 2 && PWINDOW->getContentType() == CONTENT_TYPE_GAME)) { + if (!LAYOUT_HANDLED_FULLSCREEN && (*PDIRECTSCANOUT == 1 || (*PDIRECTSCANOUT == 2 && PWINDOW->getContentType() == CONTENT_TYPE_GAME))) { auto surf = PWINDOW->getSolitaryResource(); if (surf) g_pHyprRenderer->setSurfaceScanoutMode(surf, NEW_EFFECTIVE_MODE != FSMODE_NONE ? PMONITOR->m_self.lock() : nullptr); diff --git a/src/Compositor.hpp b/src/Compositor.hpp index 7fbc7d525..98473358e 100644 --- a/src/Compositor.hpp +++ b/src/Compositor.hpp @@ -116,7 +116,8 @@ class CCompositor { void changeWindowZOrder(PHLWINDOW, bool); void cleanupFadingOut(const MONITORID& monid); PHLWINDOW getWindowInDirection(PHLWINDOW, Math::eDirection); - PHLWINDOW getWindowInDirection(const CBox& box, PHLWORKSPACE pWorkspace, Math::eDirection dir, PHLWINDOW ignoreWindow = nullptr, bool useVectorAngles = false); + PHLWINDOW getWindowInDirection(const CBox& box, PHLWORKSPACE pWorkspace, Math::eDirection dir, bool floatingPreference, PHLWINDOW ignoreWindow = nullptr, + bool useVectorAngles = false); PHLWINDOW getWindowCycle(PHLWINDOW cur, bool focusableOnly = false, std::optional floating = std::nullopt, bool visible = false, bool prev = false, bool allowFullscreenBlocked = false); PHLWINDOW getWindowCycleHist(PHLWINDOWREF cur, bool focusableOnly = false, std::optional floating = std::nullopt, bool visible = false, bool next = false, diff --git a/src/config/shared/actions/ConfigActions.cpp b/src/config/shared/actions/ConfigActions.cpp index 56fb88e98..3f586b9f6 100644 --- a/src/config/shared/actions/ConfigActions.cpp +++ b/src/config/shared/actions/ConfigActions.cpp @@ -409,7 +409,7 @@ ActionResult Actions::moveFocus(Math::eDirection dir) { } const auto PWINDOWCANDIDATE = g_pCompositor->getWindowInDirection(box, PMONITOR->m_activeSpecialWorkspace ? PMONITOR->m_activeSpecialWorkspace : PMONITOR->m_activeWorkspace, - dir, PLASTWINDOW, PLASTWINDOW->m_isFloating); + dir, PLASTWINDOW->m_isFloating, PLASTWINDOW, PLASTWINDOW->m_isFloating); if (PWINDOWCANDIDATE) switchToWindow(PWINDOWCANDIDATE); diff --git a/src/desktop/Workspace.cpp b/src/desktop/Workspace.cpp index 23ea0dd73..6ff6d0b4a 100644 --- a/src/desktop/Workspace.cpp +++ b/src/desktop/Workspace.cpp @@ -1,5 +1,6 @@ #include "Workspace.hpp" #include "view/Group.hpp" +#include "view/LayerSurface.hpp" #include "../Compositor.hpp" #include "../config/shared/animation/AnimationTree.hpp" #include "../config/shared/workspace/WorkspaceRuleManager.hpp" @@ -8,6 +9,7 @@ #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" @@ -539,7 +541,10 @@ void CWorkspace::rename(const std::string& name) { } void CWorkspace::updateWindows() { - m_hasFullscreenWindow = std::ranges::any_of(m_space->targets(), [](const auto& t) { return t->fullscreenMode() != FSMODE_NONE; }); + 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()) @@ -562,3 +567,15 @@ void CWorkspace::setPersistent(bool persistent) { 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; + } +} diff --git a/src/desktop/Workspace.hpp b/src/desktop/Workspace.hpp index 1ccf14542..1abc80c0a 100644 --- a/src/desktop/Workspace.hpp +++ b/src/desktop/Workspace.hpp @@ -80,6 +80,7 @@ class CWorkspace { void updateWindows(); void setPersistent(bool persistent); bool isPersistent(); + void setNoMembersAboveFullscreen(); struct { CSignalT<> destroy; diff --git a/src/desktop/state/FocusState.cpp b/src/desktop/state/FocusState.cpp index 5dd22f94b..48fb6456e 100644 --- a/src/desktop/state/FocusState.cpp +++ b/src/desktop/state/FocusState.cpp @@ -95,6 +95,9 @@ void CFocusState::rawWindowFocus(PHLWINDOW pWindow, eFocusReason reason, SP("input:follow_mouse"); static auto PSPECIALFALLTHROUGH = CConfigValue("input:special_fallthrough"); + if (pWindow == m_focusWindow && surface == m_focusSurface) + return; + if (!pWindow || !pWindow->priorityFocus()) { if (g_pSessionLockManager->isSessionLocked()) { Log::logger->log(Log::DEBUG, "Refusing a keyboard focus to a window because of a sessionlock"); diff --git a/src/desktop/view/Window.cpp b/src/desktop/view/Window.cpp index b2f4821b8..19f8f88d0 100644 --- a/src/desktop/view/Window.cpp +++ b/src/desktop/view/Window.cpp @@ -48,6 +48,7 @@ #include "../../managers/input/InputManager.hpp" #include "../../managers/PointerManager.hpp" #include "../../managers/animation/DesktopAnimationManager.hpp" +#include "../../layout/algorithm/Algorithm.hpp" #include "../../layout/space/Space.hpp" #include "../../layout/LayoutManager.hpp" #include "../../layout/target/WindowTarget.hpp" @@ -267,7 +268,7 @@ CBox CWindow::getWindowIdealBoundingBoxIgnoreReserved() { auto POS = m_position; auto SIZE = m_size; - if (isFullscreen()) { + if (isFullscreen() && (!layoutTarget() || !layoutTarget()->layoutManagedFullscreen())) { POS = PMONITOR->m_position; SIZE = PMONITOR->m_size; @@ -748,12 +749,12 @@ bool CWindow::isInputBlocked() const { return m_inputBlockReasons != INPUT_BLOCK_NONE; } -bool CWindow::isInputBlocked(eWindowInputBlockReason reason) const { - return (m_inputBlockReasons & sc(reason)) != 0; +bool CWindow::isInputBlocked(std::underlying_type_t reasons) const { + return (m_inputBlockReasons & reasons) != 0; } bool CWindow::isInputBlockedOnly(eWindowInputBlockReason reason) const { - return m_inputBlockReasons == sc(reason); + return m_inputBlockReasons == reason; } bool CWindow::acceptsInput() const { @@ -772,7 +773,13 @@ bool CWindow::isAllowedOverFullscreen() const { } bool CWindow::isBlockedByFullscreen() const { - if (!m_workspace || !m_workspace->m_hasFullscreenWindow) + if (!m_workspace) + return false; + + const auto ALGORITHM = m_workspace->m_space ? m_workspace->m_space->algorithm() : nullptr; + const bool HAS_LAYOUT_FULLSCREEN = ALGORITHM && ALGORITHM->layoutFullscreenCoversMonitor(); + + if (!m_workspace->m_hasFullscreenWindow && !HAS_LAYOUT_FULLSCREEN) return false; return !isAllowedOverFullscreen(); diff --git a/src/desktop/view/Window.hpp b/src/desktop/view/Window.hpp index 32a7bcfe2..6174e6940 100644 --- a/src/desktop/view/Window.hpp +++ b/src/desktop/view/Window.hpp @@ -89,11 +89,13 @@ namespace Desktop::View { WINDOW_ALPHA_LAST, }; - enum eWindowInputBlockReason : uint32_t { + enum eWindowInputBlockReason : uint8_t { INPUT_BLOCK_NONE = 0, - INPUT_BLOCK_GROUP_INACTIVE = 1 << 0, - INPUT_BLOCK_MONOCLE_INACTIVE = 1 << 1, - INPUT_BLOCK_BELOW_FULLSCREEN = 1 << 2, + INPUT_BLOCK_GROUP_INACTIVE = (1 << 0), + INPUT_BLOCK_MONOCLE_INACTIVE = (1 << 1), + INPUT_BLOCK_BELOW_FULLSCREEN = (1 << 2), + + INPUT_BLOCK_ALL = std::numeric_limits>::max(), }; struct SWindowActiveEvent { @@ -285,6 +287,11 @@ namespace Desktop::View { // For the noclosefor windowrule Time::steady_tp m_closeableSince = Time::steadyNow(); + // layout-settable flags. These are reset when layout changes. + struct { + bool cantLockCursor = false; + } m_layoutFlags; + // For the list lookup bool operator==(const CWindow& rhs) const { return m_xdgSurface == rhs.m_xdgSurface && m_xwaylandSurface == rhs.m_xwaylandSurface && m_position == rhs.m_position && m_size == rhs.m_size && @@ -314,7 +321,7 @@ namespace Desktop::View { bool isHidden() const; void setInputBlocked(eWindowInputBlockReason reason, bool blocked); bool isInputBlocked() const; - bool isInputBlocked(eWindowInputBlockReason reason) const; + bool isInputBlocked(std::underlying_type_t reasons) const; bool isInputBlockedOnly(eWindowInputBlockReason reason) const; bool acceptsInput() const; bool isAllowedOverFullscreen() const; diff --git a/src/helpers/Monitor.cpp b/src/helpers/Monitor.cpp index c7d4168ae..958c8480f 100644 --- a/src/helpers/Monitor.cpp +++ b/src/helpers/Monitor.cpp @@ -30,6 +30,8 @@ #include "../managers/input/InputManager.hpp" #include "../errorOverlay/Overlay.hpp" #include "../layout/LayoutManager.hpp" +#include "../layout/space/Space.hpp" +#include "../layout/algorithm/Algorithm.hpp" #include "../i18n/Engine.hpp" #include "../helpers/cm/ColorManagement.hpp" #include "time/Time.hpp" @@ -1672,7 +1674,7 @@ uint32_t CMonitor::isSolitaryBlocked(bool full) { return reasons; } - if (!PWORKSPACE->m_hasFullscreenWindow) { + if (!inFullscreenMode()) { reasons |= SC_WINDOWED; if (!full) return reasons; @@ -1720,7 +1722,7 @@ uint32_t CMonitor::isSolitaryBlocked(bool full) { return reasons; } - const auto PCANDIDATE = PWORKSPACE->getFullscreenWindow(); + const auto PCANDIDATE = getFullscreenWindow(); if (!PCANDIDATE) { reasons |= SC_CANDIDATE; @@ -1791,7 +1793,7 @@ void CMonitor::recheckSolitary() { if (isSolitaryBlocked()) return; - m_solitaryClient = PWORKSPACE->getFullscreenWindow(); + m_solitaryClient = getFullscreenWindow(); } uint8_t CMonitor::isTearingBlocked(bool full) { @@ -1871,11 +1873,12 @@ uint16_t CMonitor::isDSBlocked(bool full) { } if (*PDIRECTSCANOUT == 2) { - if (!PWORKSPACE || !PWORKSPACE->m_hasFullscreenWindow || PWORKSPACE->m_fullscreenMode != FSMODE_FULLSCREEN) { + const auto FSWINDOW = getFullscreenWindow(); + if (!PWORKSPACE || !inFullscreenMode() || !FSWINDOW) { reasons |= DS_BLOCK_WINDOWED; if (!full) return reasons; - } else if (PWORKSPACE->getFullscreenWindow()->getContentType() != CONTENT_TYPE_GAME) { + } else if (FSWINDOW->getContentType() != CONTENT_TYPE_GAME) { reasons |= DS_BLOCK_CONTENT; if (!full) return reasons; @@ -2268,17 +2271,34 @@ bool CMonitor::inHDR() { bool CMonitor::inFullscreenMode() { // Check special workspace first since it renders on top of regular workspaces - if (m_activeSpecialWorkspace && m_activeSpecialWorkspace->m_hasFullscreenWindow && m_activeSpecialWorkspace->m_fullscreenMode == FSMODE_FULLSCREEN) + if (m_activeSpecialWorkspace && + ((m_activeSpecialWorkspace->m_hasFullscreenWindow && m_activeSpecialWorkspace->m_fullscreenMode == FSMODE_FULLSCREEN) || + (m_activeSpecialWorkspace->m_space && m_activeSpecialWorkspace->m_space->algorithm() && m_activeSpecialWorkspace->m_space->algorithm()->layoutFullscreenCoversMonitor()))) return true; - return m_activeWorkspace && m_activeWorkspace->m_hasFullscreenWindow && m_activeWorkspace->m_fullscreenMode == FSMODE_FULLSCREEN; + return m_activeWorkspace && + ((m_activeWorkspace->m_hasFullscreenWindow && m_activeWorkspace->m_fullscreenMode == FSMODE_FULLSCREEN) || + (m_activeWorkspace->m_space && m_activeWorkspace->m_space->algorithm() && m_activeWorkspace->m_space->algorithm()->layoutFullscreenCoversMonitor())); } PHLWINDOW CMonitor::getFullscreenWindow() { // Check special workspace first since it renders on top of regular workspaces if (m_activeSpecialWorkspace && m_activeSpecialWorkspace->m_hasFullscreenWindow && m_activeSpecialWorkspace->m_fullscreenMode == FSMODE_FULLSCREEN) return m_activeSpecialWorkspace->getFullscreenWindow(); + + if (m_activeSpecialWorkspace && m_activeSpecialWorkspace->m_space && m_activeSpecialWorkspace->m_space->algorithm() && + m_activeSpecialWorkspace->m_space->algorithm()->layoutFullscreenCoversMonitor()) { + const auto TARGET = m_activeSpecialWorkspace->m_space->algorithm()->layoutFullscreenTarget(); + return TARGET ? TARGET->window() : nullptr; + } + if (m_activeWorkspace && m_activeWorkspace->m_hasFullscreenWindow && m_activeWorkspace->m_fullscreenMode == FSMODE_FULLSCREEN) return m_activeWorkspace->getFullscreenWindow(); + + if (m_activeWorkspace && m_activeWorkspace->m_space && m_activeWorkspace->m_space->algorithm() && m_activeWorkspace->m_space->algorithm()->layoutFullscreenCoversMonitor()) { + const auto TARGET = m_activeWorkspace->m_space->algorithm()->layoutFullscreenTarget(); + return TARGET ? TARGET->window() : nullptr; + } + return nullptr; } diff --git a/src/layout/LayoutManager.cpp b/src/layout/LayoutManager.cpp index f90cedf63..875ae2ae0 100644 --- a/src/layout/LayoutManager.cpp +++ b/src/layout/LayoutManager.cpp @@ -94,9 +94,11 @@ void CLayoutManager::endDragTarget() { m_dragStateController->dragEnd(); } -void CLayoutManager::fullscreenRequestForTarget(SP target, eFullscreenMode currentEffectiveMode, eFullscreenMode effectiveMode) { - if (target->space()) - target->space()->setFullscreen(target, effectiveMode); +eFullscreenRequestResult CLayoutManager::fullscreenRequestForTarget(SP target, eFullscreenMode currentEffectiveMode, eFullscreenMode effectiveMode) { + if (target && target->space()) + return target->space()->setFullscreen(target, currentEffectiveMode, effectiveMode); + + return FULLSCREEN_REQUEST_DEFAULT; } void CLayoutManager::switchTargets(SP a, SP b, bool preserveFocus) { diff --git a/src/layout/LayoutManager.hpp b/src/layout/LayoutManager.hpp index 2660cf289..ef886daa1 100644 --- a/src/layout/LayoutManager.hpp +++ b/src/layout/LayoutManager.hpp @@ -40,43 +40,48 @@ namespace Layout { SNAP_RIGHT = (1 << 3), }; + enum eFullscreenRequestResult : uint8_t { + FULLSCREEN_REQUEST_DEFAULT = 0, + FULLSCREEN_REQUEST_HANDLED_BY_LAYOUT, + }; + class CLayoutManager { public: CLayoutManager(); ~CLayoutManager() = default; - void newTarget(SP target, SP space); - void removeTarget(SP target); + void newTarget(SP target, SP space); + void removeTarget(SP target); - void changeFloatingMode(SP target); + void changeFloatingMode(SP target); - void beginDragTarget(SP target, eMouseBindMode mode); - void moveMouse(const Vector2D& mousePos); - void resizeTarget(const Vector2D& Δ, SP target, eRectCorner corner = CORNER_NONE); - void moveTarget(const Vector2D& Δ, SP target); - void setTargetGeom(const CBox& box, SP target); // floats only - void endDragTarget(); + void beginDragTarget(SP target, eMouseBindMode mode); + void moveMouse(const Vector2D& mousePos); + void resizeTarget(const Vector2D& Δ, SP target, eRectCorner corner = CORNER_NONE); + void moveTarget(const Vector2D& Δ, SP target); + void setTargetGeom(const CBox& box, SP target); // floats only + void endDragTarget(); - Config::ErrorResult layoutMsg(const std::string_view& sv); + Config::ErrorResult layoutMsg(const std::string_view& sv); - void fullscreenRequestForTarget(SP target, eFullscreenMode currentEffectiveMode, eFullscreenMode effectiveMode); + eFullscreenRequestResult fullscreenRequestForTarget(SP target, eFullscreenMode currentEffectiveMode, eFullscreenMode effectiveMode); - void switchTargets(SP a, SP b, bool preserveFocus = true); + void switchTargets(SP a, SP b, bool preserveFocus = true); - void moveInDirection(SP target, const std::string& direction, bool silent = false); + void moveInDirection(SP target, const std::string& direction, bool silent = false); - SP getNextCandidate(SP space, SP from); + SP getNextCandidate(SP space, SP from); - bool isReachable(SP target); + bool isReachable(SP target); - void bringTargetToTop(SP target); + void bringTargetToTop(SP target); - std::optional predictSizeForNewTiledTarget(); + std::optional predictSizeForNewTiledTarget(); - void performSnap(Vector2D& sourcePos, Vector2D& sourceSize, SP target, eMouseBindMode mode, int corner, const Vector2D& beginSize); + void performSnap(Vector2D& sourcePos, Vector2D& sourceSize, SP target, eMouseBindMode mode, int corner, const Vector2D& beginSize); - void invalidateMonitorGeometries(PHLMONITOR); - void recalculateMonitor(PHLMONITOR); + void invalidateMonitorGeometries(PHLMONITOR); + void recalculateMonitor(PHLMONITOR); const UP& dragController(); diff --git a/src/layout/algorithm/Algorithm.cpp b/src/layout/algorithm/Algorithm.cpp index b22eb9bfc..ae96a7d8e 100644 --- a/src/layout/algorithm/Algorithm.cpp +++ b/src/layout/algorithm/Algorithm.cpp @@ -154,6 +154,25 @@ void CAlgorithm::moveTarget(const Vector2D& Δ, SP target) { m_floating->moveTarget(Δ, target); } +eFullscreenRequestResult CAlgorithm::requestFullscreen(SP target, eFullscreenMode currentEffectiveMode, eFullscreenMode effectiveMode) { + if (!target) + return FULLSCREEN_REQUEST_DEFAULT; + + const SFullscreenRequest request = {.target = target, .currentEffectiveMode = currentEffectiveMode, .effectiveMode = effectiveMode}; + return target->floating() ? m_floating->requestFullscreen(request) : m_tiled->requestFullscreen(request); +} + +SP CAlgorithm::layoutFullscreenTarget() const { + if (const auto TARGET = m_tiled->layoutFullscreenTarget(); TARGET) + return TARGET; + + return m_floating->layoutFullscreenTarget(); +} + +bool CAlgorithm::layoutFullscreenCoversMonitor() const { + return m_tiled->layoutFullscreenCoversMonitor() || m_floating->layoutFullscreenCoversMonitor(); +} + void CAlgorithm::swapTargets(SP a, SP b) { auto swapFirst = [&a, &b](std::vector>& targets) -> bool { auto ia = std::ranges::find(targets, a); diff --git a/src/layout/algorithm/Algorithm.hpp b/src/layout/algorithm/Algorithm.hpp index 8b9e471d2..61c40285c 100644 --- a/src/layout/algorithm/Algorithm.hpp +++ b/src/layout/algorithm/Algorithm.hpp @@ -42,6 +42,10 @@ namespace Layout { void setTargetGeom(const CBox& box, SP target); // only for float + eFullscreenRequestResult requestFullscreen(SP target, eFullscreenMode currentEffectiveMode, eFullscreenMode effectiveMode); + SP layoutFullscreenTarget() const; + bool layoutFullscreenCoversMonitor() const; + void updateFloatingAlgo(UP&& algo); void updateTiledAlgo(UP&& algo); diff --git a/src/layout/algorithm/ModeAlgorithm.cpp b/src/layout/algorithm/ModeAlgorithm.cpp index c029336f3..5927e8611 100644 --- a/src/layout/algorithm/ModeAlgorithm.cpp +++ b/src/layout/algorithm/ModeAlgorithm.cpp @@ -15,6 +15,19 @@ std::optional IModeAlgorithm::predictSizeForNewTarget() { return std::nullopt; } +eFullscreenRequestResult IModeAlgorithm::requestFullscreen(const SFullscreenRequest& request) { + (void)request; + return FULLSCREEN_REQUEST_DEFAULT; +} + +SP IModeAlgorithm::layoutFullscreenTarget() const { + return nullptr; +} + +bool IModeAlgorithm::layoutFullscreenCoversMonitor() const { + return false; +} + std::optional IModeAlgorithm::focalPointForDir(SP t, Math::eDirection dir) { Vector2D focalPoint; diff --git a/src/layout/algorithm/ModeAlgorithm.hpp b/src/layout/algorithm/ModeAlgorithm.hpp index e53d4391e..1348a6f42 100644 --- a/src/layout/algorithm/ModeAlgorithm.hpp +++ b/src/layout/algorithm/ModeAlgorithm.hpp @@ -13,6 +13,12 @@ namespace Layout { class ITarget; class CAlgorithm; + struct SFullscreenRequest { + SP target; + eFullscreenMode currentEffectiveMode = static_cast(0); + eFullscreenMode effectiveMode = static_cast(0); + }; + class IModeAlgorithm { public: virtual ~IModeAlgorithm() = default; @@ -44,6 +50,13 @@ namespace Layout { // optional: predict new window's size virtual std::optional predictSizeForNewTarget(); + // optional: allow algorithms to own fullscreen semantics for a target. + virtual eFullscreenRequestResult requestFullscreen(const SFullscreenRequest& request); + + // optional: expose an algorithm-owned fullscreen target and whether it is monitor-exclusive. + virtual SP layoutFullscreenTarget() const; + virtual bool layoutFullscreenCoversMonitor() const; + // Impl'd here: focal point for dir virtual std::optional focalPointForDir(SP t, Math::eDirection dir); diff --git a/src/layout/algorithm/tiled/scrolling/ScrollTapeController.cpp b/src/layout/algorithm/tiled/scrolling/ScrollTapeController.cpp index 657b65426..722b9bd8b 100644 --- a/src/layout/algorithm/tiled/scrolling/ScrollTapeController.cpp +++ b/src/layout/algorithm/tiled/scrolling/ScrollTapeController.cpp @@ -147,14 +147,10 @@ double CScrollTapeController::calculateStripSize(size_t stripIndex, const CBox& return usablePrimary * m_strips[stripIndex].size; } -CBox CScrollTapeController::calculateTargetBox(size_t stripIndex, size_t targetIndex, const CBox& usableArea, const Vector2D& workspaceOffset, bool fullscreenOnOne) { +CBox CScrollTapeController::calculateStripBox(size_t stripIndex, const CBox& usableArea, const Vector2D& workspaceOffset, bool fullscreenOnOne) { if (stripIndex >= m_strips.size()) return {}; - const auto& strip = m_strips[stripIndex]; - if (targetIndex >= strip.targetSizes.size()) - return {}; - const double usableSecondary = getSecondary(usableArea.size()); const double usablePrimary = getPrimary(usableArea.size()); const double cameraOffset = calculateCameraOffset(usableArea, fullscreenOnOne); @@ -163,13 +159,6 @@ CBox CScrollTapeController::calculateTargetBox(size_t stripIndex, size_t targetI double primaryPos = calculateStripStart(stripIndex, usableArea, fullscreenOnOne); double primarySize = calculateStripSize(stripIndex, usableArea, fullscreenOnOne); - // calculate position along secondary axis (within strip) - double secondaryPos = 0.0; - for (size_t i = 0; i < targetIndex; ++i) { - secondaryPos += strip.targetSizes[i] * usableSecondary; - } - double secondarySize = strip.targetSizes[targetIndex] * usableSecondary; - // apply camera offset based on direction // for RIGHT/DOWN: scroll offset moves content left/up (subtract) // for LEFT/UP: scroll offset moves content right/down (different coordinate system) @@ -185,8 +174,8 @@ CBox CScrollTapeController::calculateTargetBox(size_t stripIndex, size_t targetI } // create the box in primary/secondary coordinates - Vector2D pos = makeVector(primaryPos, secondaryPos); - Vector2D size = makeVector(primarySize, secondarySize); + Vector2D pos = makeVector(primaryPos, 0.0); + Vector2D size = makeVector(primarySize, usableSecondary); // translate to workspace position pos = pos + workspaceOffset; @@ -194,6 +183,35 @@ CBox CScrollTapeController::calculateTargetBox(size_t stripIndex, size_t targetI return CBox{pos, size}; } +CBox CScrollTapeController::calculateTargetBox(size_t stripIndex, size_t targetIndex, const CBox& usableArea, const Vector2D& workspaceOffset, bool fullscreenOnOne) { + if (stripIndex >= m_strips.size()) + return {}; + + const auto& strip = m_strips[stripIndex]; + if (targetIndex >= strip.targetSizes.size()) + return {}; + + CBox stripBox = calculateStripBox(stripIndex, usableArea, workspaceOffset, fullscreenOnOne); + const double usableSecondary = getSecondary(usableArea.size()); + + double secondaryPos = 0.0; + for (size_t i = 0; i < targetIndex; ++i) { + secondaryPos += strip.targetSizes[i] * usableSecondary; + } + + const double secondarySize = strip.targetSizes[targetIndex] * usableSecondary; + + if (isPrimaryHorizontal()) { + stripBox.y = workspaceOffset.y + secondaryPos; + stripBox.h = secondarySize; + } else { + stripBox.x = workspaceOffset.x + secondaryPos; + stripBox.w = secondarySize; + } + + return stripBox; +} + double CScrollTapeController::calculateCameraOffset(const CBox& usableArea, bool fullscreenOnOne) { const double maxExtent = calculateMaxExtent(usableArea, fullscreenOnOne); const double usablePrimary = getPrimary(usableArea.size()); @@ -275,13 +293,14 @@ size_t CScrollTapeController::getStripAtCenter(const CBox& usableArea, bool full return 0; const double usablePrimary = getPrimary(usableArea.size()); - double currentPos = m_offset; + const double viewCenter = m_offset + usablePrimary / 2.0; + double currentEnd = 0.0; for (size_t i = 0; i < m_strips.size(); ++i) { const double stripSize = calculateStripSize(i, usableArea, fullscreenOnOne); - currentPos += stripSize; + currentEnd += stripSize; - if (currentPos >= usablePrimary / 2.0 - 2.0) + if (currentEnd >= viewCenter - 2.0) return i; } diff --git a/src/layout/algorithm/tiled/scrolling/ScrollTapeController.hpp b/src/layout/algorithm/tiled/scrolling/ScrollTapeController.hpp index da2efbba3..8006c4a59 100644 --- a/src/layout/algorithm/tiled/scrolling/ScrollTapeController.hpp +++ b/src/layout/algorithm/tiled/scrolling/ScrollTapeController.hpp @@ -55,6 +55,7 @@ namespace Layout::Tiled { double calculateStripStart(size_t stripIndex, const CBox& usableArea, bool fullscreenOnOne = false) const; double calculateStripSize(size_t stripIndex, const CBox& usableArea, bool fullscreenOnOne = false) const; + CBox calculateStripBox(size_t stripIndex, const CBox& usableArea, const Vector2D& workspaceOffset, bool fullscreenOnOne = false); CBox calculateTargetBox(size_t stripIndex, size_t targetIndex, const CBox& usableArea, const Vector2D& workspaceOffset, bool fullscreenOnOne = false); double calculateCameraOffset(const CBox& usableArea, bool fullscreenOnOne = false); diff --git a/src/layout/algorithm/tiled/scrolling/ScrollingAlgorithm.cpp b/src/layout/algorithm/tiled/scrolling/ScrollingAlgorithm.cpp index 28941109d..2914ae8b6 100644 --- a/src/layout/algorithm/tiled/scrolling/ScrollingAlgorithm.cpp +++ b/src/layout/algorithm/tiled/scrolling/ScrollingAlgorithm.cpp @@ -11,6 +11,7 @@ #include "../../../../config/shared/workspace/WorkspaceRuleManager.hpp" #include "../../../../render/Renderer.hpp" #include "../../../../managers/input/InputManager.hpp" +#include "../../../../managers/animation/DesktopAnimationManager.hpp" #include "../../../../event/EventBus.hpp" #include @@ -427,23 +428,83 @@ void SScrollingData::recalculate(bool forceInstant) { algorithm->m_parent->space()->workspace()->m_hasFullscreenWindow) return; + algorithm->syncFullscreenTargets(); + static const auto PFSONONE = CConfigValue("scrolling:fullscreen_on_one_column"); const CBox USABLE = algorithm->usableArea(); const auto WORKAREA = algorithm->m_parent->space()->workArea(); + const CBox MONBOX = algorithm->m_parent->space()->workspace()->m_monitor->logicalBox(); + + const auto WORKSPACERULE = Config::workspaceRuleMgr()->getWorkspaceRuleFor(algorithm->m_parent->space()->workspace()); + static auto PGAPSINDATA = CConfigValue("general:gaps_in"); + auto* const PGAPSIN = sc((PGAPSINDATA.ptr())); + const auto GAPSIN = (WORKSPACERULE && WORKSPACERULE->m_gapsIn.has_value()) ? WORKSPACERULE->m_gapsIn.value() : *PGAPSIN; + + bool anyFullscreenCovers = false; + for (const auto& COL : columns) { + if (algorithm->fullscreenTargetDataForColumn(COL) && algorithm->fullscreenColumnCoversMonitor(COL)) { + anyFullscreenCovers = true; + break; + } + } controller->setDirection(algorithm->getDynamicDirection()); + algorithm->updateFullscreenFade(anyFullscreenCovers); + + const auto targetBoxWithGaps = [&](const CBox& logical, size_t colIdx, size_t targetIdx, bool fullscreenOrHidden) -> STargetBox { + if (fullscreenOrHidden) + return {.logicalBox = logical, .visualBox = logical}; + + CBox visual = logical; + const bool PRIMARY_HORIZ = controller->isPrimaryHorizontal(); + + const bool GAP_LEFT = PRIMARY_HORIZ ? colIdx > 0 : targetIdx > 0; + const bool GAP_RIGHT = PRIMARY_HORIZ ? colIdx + 1 < columns.size() : targetIdx + 1 < columns[colIdx]->targetDatas.size(); + const bool GAP_TOP = PRIMARY_HORIZ ? targetIdx > 0 : colIdx > 0; + const bool GAP_BOTTOM = PRIMARY_HORIZ ? targetIdx + 1 < columns[colIdx]->targetDatas.size() : colIdx + 1 < columns.size(); + + const auto GAPOFFSETTOPLEFT = Vector2D(sc(GAP_LEFT ? GAPSIN.m_left : 0), sc(GAP_TOP ? GAPSIN.m_top : 0)); + const auto GAPOFFSETBOTTOMRIGHT = Vector2D(sc(GAP_RIGHT ? GAPSIN.m_right : 0), sc(GAP_BOTTOM ? GAPSIN.m_bottom : 0)); + + visual.x += GAPOFFSETTOPLEFT.x; + visual.y += GAPOFFSETTOPLEFT.y; + visual.w = std::max(1.0, visual.w - GAPOFFSETTOPLEFT.x - GAPOFFSETBOTTOMRIGHT.x); + visual.h = std::max(1.0, visual.h - GAPOFFSETTOPLEFT.y - GAPOFFSETBOTTOMRIGHT.y); + + return {.logicalBox = logical, .visualBox = visual}; + }; for (size_t i = 0; i < columns.size(); ++i) { const auto& COL = columns[i]; + const auto FS = algorithm->fullscreenTargetDataForColumn(COL); for (size_t j = 0; j < COL->targetDatas.size(); ++j) { const auto& TARGET = COL->targetDatas[j]; - TARGET->layoutBox = controller->calculateTargetBox(i, j, USABLE, WORKAREA.pos(), *PFSONONE); + if (FS) { + if (TARGET == FS) { + if (algorithm->fullscreenColumnCoversMonitor(COL)) + TARGET->layoutBox = MONBOX; + else { + TARGET->layoutBox = controller->calculateStripBox(i, USABLE, WORKAREA.pos(), *PFSONONE); + + if (controller->isPrimaryHorizontal()) { + TARGET->layoutBox.y = MONBOX.y; + TARGET->layoutBox.h = MONBOX.h; + } else { + TARGET->layoutBox.x = MONBOX.x; + TARGET->layoutBox.w = MONBOX.w; + } + } + } else + TARGET->layoutBox = CBox{WORKAREA.pos() - Vector2D{100000.0, 100000.0}, Vector2D{1.0, 1.0}}; + } else + TARGET->layoutBox = controller->calculateTargetBox(i, j, USABLE, WORKAREA.pos(), *PFSONONE); if (TARGET->target) - TARGET->target->setPositionGlobal(TARGET->layoutBox); + TARGET->target->setPositionGlobal(targetBoxWithGaps(TARGET->layoutBox, i, j, FS)); + if (forceInstant && TARGET->target) TARGET->target->warpPositionSize(); } @@ -544,6 +605,9 @@ CScrollingAlgorithm::CScrollingAlgorithm() { } CScrollingAlgorithm::~CScrollingAlgorithm() { + clearFullscreenTarget(); + updateFullscreenFade(false); + m_configCallback.reset(); m_focusCallback.reset(); } @@ -631,6 +695,8 @@ void CScrollingAlgorithm::removeTarget(SP target) { if (!DATA) return; + clearFullscreenTarget(target); + if (!m_scrollingData->next(DATA->column.lock()) && DATA->column->targetDatas.size() <= 1) { // move the view if this is the last column const auto USABLE = usableArea(); @@ -814,6 +880,303 @@ void CScrollingAlgorithm::recalculate() { m_scrollingData->recalculate(); } +void CScrollingAlgorithm::syncFullscreenTargets() { + for (auto it = m_fullscreenTargets.begin(); it != m_fullscreenTargets.end();) { + const auto TARGET = it->target.lock(); + + if (!TARGET || !TARGET->layoutManagedFullscreen() || TARGET->fullscreenMode() != FSMODE_FULLSCREEN || TARGET->space() != m_parent->space()) { + it = m_fullscreenTargets.erase(it); + continue; + } + + const auto TDATA = dataFor(TARGET); + if (!TDATA) { + ++it; + continue; + } + + if (const auto COL = TDATA->column.lock()) + COL->setColumnWidth(fullscreenColumnWidth()); + + ++it; + } + + for (const auto& COL : m_scrollingData->columns) { + for (const auto& TDATA : COL->targetDatas) { + const auto TARGET = TDATA->target.lock(); + if (!TARGET || !TARGET->layoutManagedFullscreen() || TARGET->fullscreenMode() != FSMODE_FULLSCREEN || TARGET->space() != m_parent->space()) + continue; + + if (!fullscreenStateForTarget(TARGET)) + m_fullscreenTargets.emplace_back(SFullscreenScrollState{.target = TARGET, .restoreColumnWidth = COL ? std::optional{COL->getColumnWidth()} : std::nullopt}); + + COL->setColumnWidth(fullscreenColumnWidth()); + } + } +} + +CScrollingAlgorithm::SFullscreenScrollState* CScrollingAlgorithm::fullscreenStateForTarget(SP target) { + if (!target) + return nullptr; + + for (auto& state : m_fullscreenTargets) { + if (state.target.lock() == target) + return &state; + } + + return nullptr; +} + +CScrollingAlgorithm::SFullscreenScrollState* CScrollingAlgorithm::fullscreenStateForData(SP target) { + if (!target) + return nullptr; + + return fullscreenStateForTarget(target->target.lock()); +} + +void CScrollingAlgorithm::expelTarget(SP tdata, SP srcCol, std::optional insertIdx) { + auto col = !insertIdx ? m_scrollingData->add() : m_scrollingData->add(*insertIdx); + srcCol->remove(tdata->target.lock()); + col->add(tdata); + m_scrollingData->centerOrFitCol(col); +} + +eFullscreenRequestResult CScrollingAlgorithm::requestFullscreen(const SFullscreenRequest& request) { + if (!request.target || !m_parent || request.target->space() != m_parent->space()) + return FULLSCREEN_REQUEST_DEFAULT; + + const auto TDATA = dataFor(request.target); + if (!TDATA) + return FULLSCREEN_REQUEST_DEFAULT; + + if (request.effectiveMode == FSMODE_FULLSCREEN) { + if (!fullscreenStateForTarget(request.target)) { + const auto COL = TDATA->column.lock(); + m_fullscreenTargets.emplace_back( + SFullscreenScrollState{.target = request.target, .restoreColumnWidth = COL ? std::optional{COL->getColumnWidth()} : std::nullopt}); + } + + if (const auto COL = TDATA->column.lock()) { + COL->setColumnWidth(fullscreenColumnWidth()); + m_scrollingData->centerOrFitCol(COL); + } + + request.target->setFullscreenMode(FSMODE_FULLSCREEN); + + return FULLSCREEN_REQUEST_HANDLED_BY_LAYOUT; + } else if (request.effectiveMode == FSMODE_MAXIMIZED) { + // expel, then max width + const auto CURRENT_COL = TDATA->column.lock(); + + if (CURRENT_COL->targetDatas.size() > 1) { + const auto lastTarget = CURRENT_COL->targetDatas.back(); + const auto currentIdx = m_scrollingData->idx(CURRENT_COL); + const auto NEXT_COL = m_scrollingData->next(CURRENT_COL); + const auto insertIdx = !NEXT_COL ? std::nullopt : std::optional{currentIdx}; + + expelTarget(lastTarget, CURRENT_COL, insertIdx); + + TDATA->column->setColumnWidth(1.F); + } else + CURRENT_COL->setColumnWidth(1.F); + + request.target->setFullscreenMode(FSMODE_NONE); + + return FULLSCREEN_REQUEST_HANDLED_BY_LAYOUT; + } + + if (isFullscreenTarget(TDATA) || request.target->layoutManagedFullscreen()) { + clearFullscreenTarget(request.target); + request.target->setFullscreenMode(FSMODE_NONE); + return request.effectiveMode == FSMODE_NONE ? FULLSCREEN_REQUEST_HANDLED_BY_LAYOUT : FULLSCREEN_REQUEST_DEFAULT; + } + + return FULLSCREEN_REQUEST_DEFAULT; +} + +SP CScrollingAlgorithm::layoutFullscreenTarget() const { + SP fallback; + + for (const auto& COL : m_scrollingData->columns) { + for (const auto& TDATA : COL->targetDatas) { + if (!isFullscreenTarget(TDATA)) + continue; + + if (!fallback) + fallback = TDATA; + + if (fullscreenColumnCoversMonitor(TDATA->column.lock())) + return TDATA->target.lock(); + } + } + + return fallback ? fallback->target.lock() : nullptr; +} + +bool CScrollingAlgorithm::layoutFullscreenCoversMonitor() const { + for (const auto& COL : m_scrollingData->columns) { + for (const auto& TDATA : COL->targetDatas) { + if (!isFullscreenTarget(TDATA)) + continue; + + if (fullscreenColumnCoversMonitor(TDATA->column.lock())) + return true; + } + } + + return false; +} + +SP CScrollingAlgorithm::fullscreenTargetDataForColumn(SP col) const { + if (!col) + return nullptr; + + for (const auto& TDATA : col->targetDatas) { + if (!isFullscreenTarget(TDATA)) + continue; + + return TDATA; + } + + return nullptr; +} + +bool CScrollingAlgorithm::isFullscreenTarget(SP target) const { + if (!target) + return false; + + const auto TARGET = target->target.lock(); + if (!TARGET || !TARGET->layoutManagedFullscreen() || TARGET->fullscreenMode() == FSMODE_NONE) + return false; + + return dataFor(TARGET) == target; +} + +float CScrollingAlgorithm::fullscreenColumnWidth() const { + if (!m_parent || !m_parent->space() || !m_parent->space()->workspace() || !m_parent->space()->workspace()->m_monitor || !m_scrollingData || !m_scrollingData->controller) + return 1.F; + + const auto USABLE = usableArea(); + const auto MONBOX = m_parent->space()->workspace()->m_monitor->logicalBox(); + const bool PRIMARY_HORIZ = m_scrollingData->controller->isPrimaryHorizontal(); + const double usablePrimary = PRIMARY_HORIZ ? USABLE.w : USABLE.h; + const double monitorPrimary = PRIMARY_HORIZ ? MONBOX.w : MONBOX.h; + + if (usablePrimary <= 0.0) + return 1.F; + + return std::max(1.F, sc(monitorPrimary / usablePrimary)); +} + +bool CScrollingAlgorithm::fullscreenColumnCoversMonitor(SP col) const { + if (!col || !m_scrollingData || !m_scrollingData->controller || !m_parent || !m_parent->space() || !m_parent->space()->workspace() || + !m_parent->space()->workspace()->m_monitor) + return false; + + if (!fullscreenTargetDataForColumn(col)) + return false; + + const int64_t COL_IDX = m_scrollingData->idx(col); + if (COL_IDX < 0) + return false; + + static const auto PFSONONE = CConfigValue("scrolling:fullscreen_on_one_column"); + + const auto USABLE = usableArea(); + const bool PRIMARY_HORIZ = m_scrollingData->controller->isPrimaryHorizontal(); + const double VIEW_SIZE = PRIMARY_HORIZ ? USABLE.w : USABLE.h; + const double VIEW_START = m_scrollingData->controller->getOffset(); + const double VIEW_END = VIEW_START + VIEW_SIZE; + const double COL_START = m_scrollingData->controller->calculateStripStart(COL_IDX, USABLE, *PFSONONE); + const double COL_END = COL_START + m_scrollingData->controller->calculateStripSize(COL_IDX, USABLE, *PFSONONE); + + return COL_START <= VIEW_START + 1.0 && COL_END >= VIEW_END - 1.0; +} + +void CScrollingAlgorithm::updateFullscreenFade(bool coversMonitor) { + if (m_lastFullscreenCover == coversMonitor) + return; + + m_lastFullscreenCover = coversMonitor; + + if (!coversMonitor) { + // prevent stuck focus + g_pInputManager->unconstrainMouse(); + for (const auto& fs : m_fullscreenTargets) { + if (!fs.target || !fs.target->window()) + continue; + + auto w = fs.target->window(); + + w->m_layoutFlags.cantLockCursor = true; + } + } else { + for (const auto& fs : m_fullscreenTargets) { + if (!fs.target || !fs.target->window()) + continue; + + auto w = fs.target->window(); + + w->m_layoutFlags.cantLockCursor = false; + } + } + + if (!m_parent || !m_parent->space() || !m_parent->space()->workspace()) + return; + + // properly update things on top / bottom + m_parent->space()->workspace()->setNoMembersAboveFullscreen(); + + g_pDesktopAnimationManager->setFullscreenFadeAnimation(m_parent->space()->workspace(), + coversMonitor ? CDesktopAnimationManager::ANIMATION_TYPE_IN : CDesktopAnimationManager::ANIMATION_TYPE_OUT); +} + +void CScrollingAlgorithm::clearFullscreenTarget(SP target) { + bool cleared = false; + + auto clear = [&](SP t) { + t->setLayoutManagedFullscreen(false); + if (t->window()) + t->window()->m_layoutFlags.cantLockCursor = false; + cleared = true; + }; + + for (auto it = m_fullscreenTargets.begin(); it != m_fullscreenTargets.end();) { + const auto TARGET = it->target.lock(); + + if (!TARGET || (target && TARGET != target)) { + if (!TARGET) + it = m_fullscreenTargets.erase(it); + else + ++it; + continue; + } + + const auto TDATA = dataFor(TARGET); + + clear(TARGET); + + if (const auto COL = TDATA ? TDATA->column.lock() : nullptr; COL && it->restoreColumnWidth) + COL->setColumnWidth(*it->restoreColumnWidth); + + it = m_fullscreenTargets.erase(it); + } + + if (target && target->layoutManagedFullscreen()) + clear(target); + else if (!target) { + for (const auto& COL : m_scrollingData->columns) { + for (const auto& TDATA : COL->targetDatas) { + const auto TARGET = TDATA->target.lock(); + if (!TARGET || !TARGET->layoutManagedFullscreen()) + continue; + + clear(TARGET); + } + } + } +} + SP CScrollingAlgorithm::closestNode(const Vector2D& posGlobglobgabgalab) { SP res = nullptr; double distClosest = -1; @@ -1372,14 +1735,6 @@ Config::ErrorResult CScrollingAlgorithm::layoutMsg(const std::string_view& sv) { if (!CURRENT_COL) return stateErr("no current col"); - // expel a target from srcCol into its own new column at insertIdx - auto expelTarget = [&](SP tdata, SP srcCol, std::optional insertIdx) { - auto col = !insertIdx ? m_scrollingData->add() : m_scrollingData->add(*insertIdx); - srcCol->remove(tdata->target.lock()); - col->add(tdata); - m_scrollingData->centerOrFitCol(col); - }; - // consume the first target from adjCol into dstCol auto consumeTarget = [&](SP dstCol, SP adjCol) { const auto target = adjCol->targetDatas.front(); @@ -1546,7 +1901,7 @@ SP CScrollingAlgorithm::findBestNeighbor(SP CScrollingAlgorithm::dataFor(SP t) { +SP CScrollingAlgorithm::dataFor(SP t) const { if (!t) return nullptr; @@ -1584,7 +1939,7 @@ eScrollDirection CScrollingAlgorithm::getDynamicDirection() { return SCROLL_DIR_RIGHT; // default } -CBox CScrollingAlgorithm::usableArea() { +CBox CScrollingAlgorithm::usableArea() const { if (!m_parent || !m_parent->space()) return {}; diff --git a/src/layout/algorithm/tiled/scrolling/ScrollingAlgorithm.hpp b/src/layout/algorithm/tiled/scrolling/ScrollingAlgorithm.hpp index 288d15a62..19eb9ba6e 100644 --- a/src/layout/algorithm/tiled/scrolling/ScrollingAlgorithm.hpp +++ b/src/layout/algorithm/tiled/scrolling/ScrollingAlgorithm.hpp @@ -5,6 +5,7 @@ #include "ScrollTapeController.hpp" #include "../../../../helpers/signal/Signal.hpp" +#include #include namespace Layout::Tiled { @@ -94,23 +95,27 @@ namespace Layout::Tiled { CScrollingAlgorithm(); virtual ~CScrollingAlgorithm(); - virtual void newTarget(SP target); - virtual void movedTarget(SP target, std::optional focalPoint = std::nullopt); - virtual void removeTarget(SP target); + virtual void newTarget(SP target); + virtual void movedTarget(SP target, std::optional focalPoint = std::nullopt); + virtual void removeTarget(SP target); - virtual void resizeTarget(const Vector2D& Δ, SP target, eRectCorner corner = CORNER_NONE); - virtual void recalculate(); + virtual void resizeTarget(const Vector2D& Δ, SP target, eRectCorner corner = CORNER_NONE); + virtual void recalculate(); - virtual SP getNextCandidate(SP old); + virtual SP getNextCandidate(SP old); - virtual Config::ErrorResult layoutMsg(const std::string_view& sv); - virtual std::optional predictSizeForNewTarget(); + virtual Config::ErrorResult layoutMsg(const std::string_view& sv); + virtual std::optional predictSizeForNewTarget(); - virtual void swapTargets(SP a, SP b); - virtual void moveTargetInDirection(SP t, Math::eDirection dir, bool silent); + virtual void swapTargets(SP a, SP b); + virtual void moveTargetInDirection(SP t, Math::eDirection dir, bool silent); - CBox usableArea(); - SP dataFor(SP t); + virtual eFullscreenRequestResult requestFullscreen(const SFullscreenRequest& request); + virtual SP layoutFullscreenTarget() const; + virtual bool layoutFullscreenCoversMonitor() const; + + CBox usableArea() const; + SP dataFor(SP t) const; enum eInputMode : uint8_t { INPUT_MODE_SOFT = 0, @@ -129,16 +134,36 @@ namespace Layout::Tiled { std::vector configuredWidths; } m_config; - eScrollDirection getDynamicDirection(); + eScrollDirection getDynamicDirection(); - SP findBestNeighbor(SP pCurrent, SP pTargetCol); - SP closestNode(const Vector2D& posGlobglobgabgalab); + struct SFullscreenScrollState { + WP target; + std::optional restoreColumnWidth; + }; - void focusTargetUpdate(SP target); - void moveTargetTo(SP t, Math::eDirection dir, bool silent); - void focusOnInput(SP target, eInputMode input); + void syncFullscreenTargets(); + SFullscreenScrollState* fullscreenStateForTarget(SP target); + SFullscreenScrollState* fullscreenStateForData(SP target); + SP fullscreenTargetDataForColumn(SP col) const; + bool isFullscreenTarget(SP target) const; + float fullscreenColumnWidth() const; + bool fullscreenColumnCoversMonitor(SP col) const; + void updateFullscreenFade(bool coversMonitor); + void clearFullscreenTarget(SP target = nullptr); - float defaultColumnWidth(); + SP findBestNeighbor(SP pCurrent, SP pTargetCol); + SP closestNode(const Vector2D& posGlobglobgabgalab); + + void focusTargetUpdate(SP target); + void moveTargetTo(SP t, Math::eDirection dir, bool silent); + void focusOnInput(SP target, eInputMode input); + + void expelTarget(SP tdata, SP srcCol, std::optional insertIdx); + + float defaultColumnWidth(); + + std::vector m_fullscreenTargets; + bool m_lastFullscreenCover = false; friend struct SScrollingData; }; diff --git a/src/layout/space/Space.cpp b/src/layout/space/Space.cpp index 26c02aaec..35741a14d 100644 --- a/src/layout/space/Space.cpp +++ b/src/layout/space/Space.cpp @@ -153,13 +153,29 @@ void CSpace::recalculate() { m_algorithm->recalculate(); } -void CSpace::setFullscreen(SP t, eFullscreenMode mode) { - t->setFullscreenMode(mode); +eFullscreenRequestResult CSpace::setFullscreen(SP t, eFullscreenMode currentEffectiveMode, eFullscreenMode mode) { + if (!t) + return FULLSCREEN_REQUEST_DEFAULT; + + const auto REQUEST_RESULT = m_algorithm ? m_algorithm->requestFullscreen(t, currentEffectiveMode, mode) : FULLSCREEN_REQUEST_DEFAULT; + + t->setLayoutManagedFullscreen(REQUEST_RESULT == FULLSCREEN_REQUEST_HANDLED_BY_LAYOUT && mode == FSMODE_FULLSCREEN); + if (REQUEST_RESULT != FULLSCREEN_REQUEST_HANDLED_BY_LAYOUT) + t->setFullscreenMode(mode); + + if (REQUEST_RESULT == FULLSCREEN_REQUEST_HANDLED_BY_LAYOUT) { + if (const auto WORKSPACE = workspace()) { + WORKSPACE->m_fullscreenMode = FSMODE_NONE; + WORKSPACE->m_hasFullscreenWindow = false; + } + } if (mode == FSMODE_NONE && m_algorithm && t->floating()) m_algorithm->recenter(t); recalculate(); + + return REQUEST_RESULT; } Config::ErrorResult CSpace::layoutMsg(const std::string_view& sv) { diff --git a/src/layout/space/Space.hpp b/src/layout/space/Space.hpp index 5d7f9d014..a42dd55c1 100644 --- a/src/layout/space/Space.hpp +++ b/src/layout/space/Space.hpp @@ -30,7 +30,7 @@ namespace Layout { void setAlgorithmProvider(SP algo); void recheckWorkArea(); - void setFullscreen(SP t, eFullscreenMode mode); + eFullscreenRequestResult setFullscreen(SP t, eFullscreenMode currentEffectiveMode, eFullscreenMode mode); void moveTargetInDirection(SP t, Math::eDirection dir, bool silent); diff --git a/src/layout/target/Target.cpp b/src/layout/target/Target.cpp index e8ce48bd7..54fff7927 100644 --- a/src/layout/target/Target.cpp +++ b/src/layout/target/Target.cpp @@ -50,6 +50,14 @@ void ITarget::setSpaceGhost(const SP& space) { m_ghostSpace = true; } +bool ITarget::layoutManagedFullscreen() const { + return m_layoutManagedFullscreen; +} + +void ITarget::setLayoutManagedFullscreen(bool enabled) { + m_layoutManagedFullscreen = enabled; +} + SP ITarget::space() const { return m_space; } diff --git a/src/layout/target/Target.hpp b/src/layout/target/Target.hpp index bae5eee5e..b0f55f144 100644 --- a/src/layout/target/Target.hpp +++ b/src/layout/target/Target.hpp @@ -57,6 +57,8 @@ namespace Layout { virtual void setPseudoSize(const Vector2D& size); virtual Vector2D pseudoSize(); virtual void swap(SP b); + virtual bool layoutManagedFullscreen() const; + virtual void setLayoutManagedFullscreen(bool enabled); // virtual bool floating() = 0; @@ -77,9 +79,10 @@ namespace Layout { SP m_space; WP m_self; Vector2D m_floatingSize; - bool m_pseudo = false; - bool m_ghostSpace = false; // ghost space means a target belongs to a space, but isn't sent to the layout - Vector2D m_pseudoSize = {1280, 720}; - bool m_wasTiling = false; + bool m_pseudo = false; + bool m_ghostSpace = false; // ghost space means a target belongs to a space, but isn't sent to the layout + Vector2D m_pseudoSize = {1280, 720}; + bool m_wasTiling = false; + bool m_layoutManagedFullscreen = false; }; }; diff --git a/src/layout/target/WindowGroupTarget.cpp b/src/layout/target/WindowGroupTarget.cpp index 8f9260e20..a738362a8 100644 --- a/src/layout/target/WindowGroupTarget.cpp +++ b/src/layout/target/WindowGroupTarget.cpp @@ -68,6 +68,18 @@ void CWindowGroupTarget::setFullscreenMode(eFullscreenMode mode) { m_group->current()->m_fullscreenState.internal = mode; } +bool CWindowGroupTarget::layoutManagedFullscreen() const { + return m_group->current()->m_target->layoutManagedFullscreen(); +} + +void CWindowGroupTarget::setLayoutManagedFullscreen(bool enabled) { + ITarget::setLayoutManagedFullscreen(enabled); + + for (const auto& w : m_group->windows()) { + w->m_target->setLayoutManagedFullscreen(enabled); + } +} + std::optional CWindowGroupTarget::minSize() { return m_group->current()->minSize(); } diff --git a/src/layout/target/WindowGroupTarget.hpp b/src/layout/target/WindowGroupTarget.hpp index b3e797498..f48bf9e9e 100644 --- a/src/layout/target/WindowGroupTarget.hpp +++ b/src/layout/target/WindowGroupTarget.hpp @@ -28,6 +28,8 @@ namespace Layout { virtual void damageEntire(); virtual void warpPositionSize(); virtual void onUpdateSpace(); + virtual bool layoutManagedFullscreen() const; + virtual void setLayoutManagedFullscreen(bool enabled); private: CWindowGroupTarget(SP g); diff --git a/src/layout/target/WindowTarget.cpp b/src/layout/target/WindowTarget.cpp index 13984104b..9258b4ae2 100644 --- a/src/layout/target/WindowTarget.cpp +++ b/src/layout/target/WindowTarget.cpp @@ -43,7 +43,7 @@ void CWindowTarget::updatePos() { if (!m_space) return; - if (fullscreenMode() == FSMODE_FULLSCREEN) + if (fullscreenMode() == FSMODE_FULLSCREEN && !layoutManagedFullscreen()) return; if (floating() && fullscreenMode() != FSMODE_MAXIMIZED) { @@ -83,7 +83,24 @@ void CWindowTarget::updatePos() { return; } - if (fullscreenMode() == FSMODE_FULLSCREEN) + if (fullscreenMode() == FSMODE_FULLSCREEN && layoutManagedFullscreen()) { + CBox nodeBox = m_box.logicalBox; + CBox visualBox = m_box.visualBox.empty() ? nodeBox : m_box.visualBox; + nodeBox.round(); + visualBox.round(); + + m_window->m_size = nodeBox.size(); + m_window->m_position = nodeBox.pos(); + + *m_window->m_realSize = visualBox.size(); + *m_window->m_realPosition = visualBox.pos(); + + m_window->updateWindowDecos(); + m_window->sendWindowSize(); + return; + } + + if (fullscreenMode() == FSMODE_FULLSCREEN && !layoutManagedFullscreen()) return; g_pHyprRenderer->damageWindow(window()); @@ -206,6 +223,7 @@ void CWindowTarget::updatePos() { } m_window->updateWindowDecos(); + m_window->sendWindowSize(); } void CWindowTarget::assignToSpace(const SP& space, std::optional focalPoint) { @@ -214,6 +232,8 @@ void CWindowTarget::assignToSpace(const SP& space, std::optionalm_layoutFlags = {}; + // keep the ref here so that moveToWorkspace doesn't unref the workspace // and assignToSpace doesn't think this is a new target because space wp is dead const auto WSREF = space->workspace(); @@ -236,6 +256,8 @@ void CWindowTarget::setFloating(bool x) { if (x == m_window->m_isFloating) return; + m_window->m_layoutFlags = {}; + m_window->m_isFloating = x; m_window->m_pinned = false; diff --git a/src/managers/animation/DesktopAnimationManager.cpp b/src/managers/animation/DesktopAnimationManager.cpp index 2b7cb4590..a4b994c6f 100644 --- a/src/managers/animation/DesktopAnimationManager.cpp +++ b/src/managers/animation/DesktopAnimationManager.cpp @@ -487,7 +487,7 @@ void CDesktopAnimationManager::setFullscreenFadeAnimation(PHLWORKSPACE ws, eAnim if (ws->m_id == PMONITOR->activeWorkspaceID() || ws->m_id == PMONITOR->activeSpecialWorkspaceID()) { for (auto const& ls : PMONITOR->m_layerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_TOP]) { if (!ls->m_fadingOut && !ls->m_aboveFullscreen) - *ls->m_alpha = FULLSCREEN && ws->m_fullscreenMode == FSMODE_FULLSCREEN ? 0.f : 1.f; + *ls->m_alpha = FULLSCREEN && ws->m_fullscreenMode != FSMODE_MAXIMIZED ? 0.f : 1.f; } } } diff --git a/src/managers/input/InputManager.cpp b/src/managers/input/InputManager.cpp index 85699b14c..c1ec29789 100644 --- a/src/managers/input/InputManager.cpp +++ b/src/managers/input/InputManager.cpp @@ -453,7 +453,9 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus, bool mouse, st return pWindowIdeal; }; - if (PWORKSPACE->m_hasFullscreenWindow && PWORKSPACE->m_fullscreenMode == FSMODE_FULLSCREEN) { + const bool HAS_EXCLUSIVE_FULLSCREEN = (PWORKSPACE->m_hasFullscreenWindow && PWORKSPACE->m_fullscreenMode == FSMODE_FULLSCREEN) || PMONITOR->inFullscreenMode(); + + if (HAS_EXCLUSIVE_FULLSCREEN) { const auto IS_LS_UNFOCUSABLE = pFoundLayerSurface && (pFoundLayerSurface->m_layer < ZWLR_LAYER_SHELL_V1_LAYER_TOP || (pFoundLayerSurface->m_layer == ZWLR_LAYER_SHELL_V1_LAYER_TOP && !pFoundLayerSurface->m_aboveFullscreen)); @@ -1741,7 +1743,17 @@ void CInputManager::unconstrainMouse() { bool CInputManager::isConstrained() { return std::ranges::any_of(m_constraints, [](auto const& c) { const auto constraint = c.lock(); - return constraint && constraint->isActive() && constraint->owner()->resource() == Desktop::focusState()->surface(); + + if (!constraint || !constraint->isActive() || constraint->owner()->resource() != Desktop::focusState()->surface()) + return false; + + const auto OWNER = constraint->owner()->view(); + const auto WINDOW = Desktop::View::CWindow::fromView(OWNER); + + if (!WINDOW) + return false; + + return !WINDOW->m_layoutFlags.cantLockCursor; }); } @@ -1749,7 +1761,15 @@ bool CInputManager::isLocked() { if (!isConstrained()) return false; - const auto SURF = Desktop::View::CWLSurface::fromResource(Desktop::focusState()->surface()); + const auto SURF = Desktop::View::CWLSurface::fromResource(Desktop::focusState()->surface()); + + if (SURF) { + const auto WINDOW = Desktop::View::CWindow::fromView(SURF->view()); + + if (WINDOW && WINDOW->m_layoutFlags.cantLockCursor) + return false; + } + const auto CONSTRAINT = SURF ? SURF->constraint() : nullptr; return CONSTRAINT && CONSTRAINT->isLocked(); diff --git a/src/protocols/PointerConstraints.cpp b/src/protocols/PointerConstraints.cpp index 8bfa8b3a5..d958c0972 100644 --- a/src/protocols/PointerConstraints.cpp +++ b/src/protocols/PointerConstraints.cpp @@ -127,9 +127,11 @@ void CPointerConstraint::activate() { // TODO: hack, probably not a super duper great idea if (g_pSeatManager->m_state.pointerFocus != m_hlSurface->resource()) { - const auto SURFBOX = m_hlSurface->getSurfaceBoxGlobal(); - const auto LOCAL = SURFBOX.has_value() ? logicPositionHint() - SURFBOX->pos() : Vector2D{}; - g_pSeatManager->setPointerFocus(m_hlSurface->resource(), LOCAL); + if (const auto W = Desktop::View::CWindow::fromView(m_hlSurface->view()); !W || !W->m_layoutFlags.cantLockCursor) { + const auto SURFBOX = m_hlSurface->getSurfaceBoxGlobal(); + const auto LOCAL = SURFBOX.has_value() ? logicPositionHint() - SURFBOX->pos() : Vector2D{}; + g_pSeatManager->setPointerFocus(m_hlSurface->resource(), LOCAL); + } } if (m_locked)