diff options
| author | Nikolai Nechaev <nikolay_nechaev@mail.ru> | 2025-11-26 07:44:26 +0900 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-11-25 22:44:26 +0000 |
| commit | 40d8fa84919c6d0e9fcb1536f4bc09710d4c031c (patch) | |
| tree | e45044c92e5d8f716f2aa15668c61fed5aaf5895 /src/desktop | |
| parent | 1c1746de61a90dbbae072f3fc618c45af33fdd59 (diff) | |
compositor: Configurable behavior when window to be focused conflicts with fullscreen (#12033)
Renames `misc:new_window_takes_over_fullscreen` into
`misc:on_focus_under_fullscreen` and implements the following behavior:
- By default, when a tiling window is being focused on a workspace where
a fullscreen/maximized window exists, respect
the `misc:on_focus_under_fullscreen` config variable.
Diffstat (limited to 'src/desktop')
| -rw-r--r-- | src/desktop/LayerSurface.cpp | 16 | ||||
| -rw-r--r-- | src/desktop/Subsurface.cpp | 5 | ||||
| -rw-r--r-- | src/desktop/Window.cpp | 41 | ||||
| -rw-r--r-- | src/desktop/rule/windowRule/WindowRule.cpp | 3 | ||||
| -rw-r--r-- | src/desktop/state/FocusState.cpp | 330 | ||||
| -rw-r--r-- | src/desktop/state/FocusState.hpp | 43 |
6 files changed, 406 insertions, 32 deletions
diff --git a/src/desktop/LayerSurface.cpp b/src/desktop/LayerSurface.cpp index aab0b15a..4f08bff6 100644 --- a/src/desktop/LayerSurface.cpp +++ b/src/desktop/LayerSurface.cpp @@ -1,4 +1,5 @@ #include "LayerSurface.hpp" +#include "state/FocusState.hpp" #include "../Compositor.hpp" #include "../events/Events.hpp" #include "../protocols/LayerShell.hpp" @@ -16,7 +17,7 @@ PHLLS CLayerSurface::create(SP<CLayerShellResource> resource) { PHLLS pLS = SP<CLayerSurface>(new CLayerSurface(resource)); - auto pMonitor = resource->m_monitor.empty() ? g_pCompositor->m_lastMonitor.lock() : g_pCompositor->getMonitorFromName(resource->m_monitor); + auto pMonitor = resource->m_monitor.empty() ? Desktop::focusState()->monitor() : g_pCompositor->getMonitorFromName(resource->m_monitor); pLS->m_surface->assign(resource->m_surface.lock(), pLS); @@ -173,7 +174,7 @@ void CLayerSurface::onMap() { g_pSeatManager->setGrab(nullptr); g_pInputManager->releaseAllMouseButtons(); - g_pCompositor->focusSurface(m_surface->resource()); + Desktop::focusState()->rawSurfaceFocus(m_surface->resource()); const auto LOCAL = g_pInputManager->getMouseCoordsInternal() - Vector2D(m_geometry.x + PMONITOR->m_position.x, m_geometry.y + PMONITOR->m_position.y); g_pSeatManager->setPointerFocus(m_surface->resource(), LOCAL); @@ -244,11 +245,12 @@ void CLayerSurface::onUnmap() { // refocus if needed // vvvvvvvvvvvvv if there is a last focus and the last focus is not keyboard focusable, fallback to window - if (WASLASTFOCUS || (g_pCompositor->m_lastFocus && g_pCompositor->m_lastFocus->m_hlSurface && !g_pCompositor->m_lastFocus->m_hlSurface->keyboardFocusable())) { + if (WASLASTFOCUS || + (Desktop::focusState()->surface() && Desktop::focusState()->surface()->m_hlSurface && !Desktop::focusState()->surface()->m_hlSurface->keyboardFocusable())) { if (!g_pInputManager->refocusLastWindow(PMONITOR)) g_pInputManager->refocus(); - } else if (g_pCompositor->m_lastFocus && g_pCompositor->m_lastFocus != m_surface->resource()) - g_pSeatManager->setKeyboardFocus(g_pCompositor->m_lastFocus.lock()); + } else if (Desktop::focusState()->surface() && Desktop::focusState()->surface() != m_surface->resource()) + g_pSeatManager->setKeyboardFocus(Desktop::focusState()->surface()); CBox geomFixed = {m_geometry.x + PMONITOR->m_position.x, m_geometry.y + PMONITOR->m_position.y, m_geometry.width, m_geometry.height}; g_pHyprRenderer->damageBox(geomFixed); @@ -374,7 +376,7 @@ void CLayerSurface::onCommit() { if (WASLASTFOCUS && m_layerSurface->m_current.interactivity == ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_NONE) { // moveMouseUnified won't focus non interactive layers but it won't unfocus them either, // so unfocus the surface here. - g_pCompositor->focusSurface(nullptr); + Desktop::focusState()->rawSurfaceFocus(nullptr); g_pInputManager->refocusLastWindow(m_monitor.lock()); } else if (WASLASTFOCUS && WASEXCLUSIVE && m_layerSurface->m_current.interactivity == ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_ON_DEMAND) { g_pInputManager->simulateMouseMovement(); @@ -382,7 +384,7 @@ void CLayerSurface::onCommit() { // if now exclusive and not previously g_pSeatManager->setGrab(nullptr); g_pInputManager->releaseAllMouseButtons(); - g_pCompositor->focusSurface(m_surface->resource()); + Desktop::focusState()->rawSurfaceFocus(m_surface->resource()); const auto LOCAL = g_pInputManager->getMouseCoordsInternal() - Vector2D(m_geometry.x + PMONITOR->m_position.x, m_geometry.y + PMONITOR->m_position.y); g_pSeatManager->setPointerFocus(m_surface->resource(), LOCAL); diff --git a/src/desktop/Subsurface.cpp b/src/desktop/Subsurface.cpp index ba7340f1..cea6977a 100644 --- a/src/desktop/Subsurface.cpp +++ b/src/desktop/Subsurface.cpp @@ -1,6 +1,7 @@ #include "Subsurface.hpp" #include "../events/Events.hpp" -#include "../Compositor.hpp" +#include "../desktop/state/FocusState.hpp" +#include "../desktop/Window.hpp" #include "../config/ConfigValue.hpp" #include "../protocols/core/Compositor.hpp" #include "../protocols/core/Subcompositor.hpp" @@ -163,7 +164,7 @@ void CSubsurface::onMap() { void CSubsurface::onUnmap() { damageLastArea(); - if (m_wlSurface->resource() == g_pCompositor->m_lastFocus) + if (m_wlSurface->resource() == Desktop::focusState()->surface()) g_pInputManager->releaseAllMouseButtons(); g_pInputManager->simulateMouseMovement(); diff --git a/src/desktop/Window.cpp b/src/desktop/Window.cpp index 6a602748..706d21cc 100644 --- a/src/desktop/Window.cpp +++ b/src/desktop/Window.cpp @@ -7,6 +7,7 @@ #include <bit> #include <string_view> #include "Window.hpp" +#include "state/FocusState.hpp" #include "../Compositor.hpp" #include "../render/decorations/CHyprDropShadowDecoration.hpp" #include "../render/decorations/CHyprGroupBarDecoration.hpp" @@ -121,9 +122,9 @@ CWindow::CWindow(SP<CXWaylandSurface> surface) : m_xwaylandSurface(surface) { } CWindow::~CWindow() { - if (g_pCompositor->m_lastWindow == m_self) { - g_pCompositor->m_lastFocus.reset(); - g_pCompositor->m_lastWindow.reset(); + if (Desktop::focusState()->window() == m_self) { + Desktop::focusState()->surface().reset(); + Desktop::focusState()->window().reset(); } m_events.destroy.emit(); @@ -528,8 +529,6 @@ void CWindow::onUnmap() { if (m_workspace->m_isSpecialWorkspace && m_workspace->getWindows() == 0) m_lastWorkspace = m_monitor->activeWorkspaceID(); - std::erase_if(g_pCompositor->m_windowFocusHistory, [this](const auto& other) { return other.expired() || other == m_self; }); - if (*PCLOSEONLASTSPECIAL && m_workspace && m_workspace->getWindows() == 0 && onSpecialWorkspace()) { const auto PMONITOR = m_monitor.lock(); if (PMONITOR && PMONITOR->m_activeSpecialWorkspace && PMONITOR->m_activeSpecialWorkspace == m_workspace) @@ -594,8 +593,6 @@ void CWindow::onMap() { m_movingFromWorkspaceAlpha->setValueAndWarp(1.F); - g_pCompositor->m_windowFocusHistory.push_back(m_self); - m_reportedSize = m_pendingReportedSize; m_animatingIn = true; @@ -629,8 +626,8 @@ void CWindow::onBorderAngleAnimEnd(WP<CBaseAnimatedVariable> pav) { void CWindow::setHidden(bool hidden) { m_hidden = hidden; - if (hidden && g_pCompositor->m_lastWindow == m_self) - g_pCompositor->m_lastWindow.reset(); + if (hidden && Desktop::focusState()->window() == m_self) + Desktop::focusState()->window().reset(); setSuspended(hidden); } @@ -862,7 +859,7 @@ void CWindow::setGroupCurrent(PHLWINDOW pWindow) { const auto WORKSPACE = PCURRENT->m_workspace; const auto MODE = PCURRENT->m_fullscreenState.internal; - const auto CURRENTISFOCUS = PCURRENT == g_pCompositor->m_lastWindow.lock(); + const auto CURRENTISFOCUS = PCURRENT == Desktop::focusState()->window(); const auto PWINDOWSIZE = PCURRENT->m_realSize->value(); const auto PWINDOWPOS = PCURRENT->m_realPosition->value(); @@ -897,7 +894,7 @@ void CWindow::setGroupCurrent(PHLWINDOW pWindow) { g_pCompositor->updateAllWindowsAnimatedDecorationValues(); if (CURRENTISFOCUS) - g_pCompositor->focusWindow(pWindow); + Desktop::focusState()->rawWindowFocus(pWindow); g_pHyprRenderer->damageWindow(pWindow); @@ -1240,7 +1237,7 @@ std::unordered_map<std::string, std::string> CWindow::getEnv() { } void CWindow::activate(bool force) { - if (g_pCompositor->m_lastWindow == m_self) + if (Desktop::focusState()->window() == m_self) return; static auto PFOCUSONACTIVATE = CConfigValue<Hyprlang::INT>("misc:focus_on_activate"); @@ -1262,7 +1259,7 @@ void CWindow::activate(bool force) { if (m_isFloating) g_pCompositor->changeWindowZOrder(m_self.lock(), true); - g_pCompositor->focusWindow(m_self.lock()); + Desktop::focusState()->fullWindowFocus(m_self.lock()); warpCursor(); } @@ -1276,7 +1273,7 @@ void CWindow::onUpdateState() { if (m_isMapped) { const auto monitor = g_pCompositor->getMonitorFromID(requestsID.value()); g_pCompositor->moveWindowToWorkspaceSafe(m_self.lock(), monitor->m_activeWorkspace); - g_pCompositor->setActiveMonitor(monitor); + Desktop::focusState()->rawMonitorFocus(monitor); } if (!m_isMapped) @@ -1311,7 +1308,7 @@ void CWindow::onUpdateMeta() { g_pEventManager->postEvent(SHyprIPCEvent{.event = "windowtitlev2", .data = std::format("{:x},{}", rc<uintptr_t>(this), m_title)}); EMIT_HOOK_EVENT("windowTitle", m_self.lock()); - if (m_self == g_pCompositor->m_lastWindow) { // if it's the active, let's post an event to update others + if (m_self == Desktop::focusState()->window()) { // if it's the active, let's post an event to update others g_pEventManager->postEvent(SHyprIPCEvent{.event = "activewindow", .data = m_class + "," + m_title}); g_pEventManager->postEvent(SHyprIPCEvent{.event = "activewindowv2", .data = std::format("{:x}", rc<uintptr_t>(this))}); EMIT_HOOK_EVENT("activeWindow", m_self.lock()); @@ -1325,7 +1322,7 @@ void CWindow::onUpdateMeta() { if (m_class != NEWCLASS) { m_class = NEWCLASS; - if (m_self == g_pCompositor->m_lastWindow) { // if it's the active, let's post an event to update others + if (m_self == Desktop::focusState()->window()) { // if it's the active, let's post an event to update others g_pEventManager->postEvent(SHyprIPCEvent{.event = "activewindow", .data = m_class + "," + m_title}); g_pEventManager->postEvent(SHyprIPCEvent{.event = "activewindowv2", .data = std::format("{:x}", rc<uintptr_t>(this))}); EMIT_HOOK_EVENT("activeWindow", m_self.lock()); @@ -1502,7 +1499,7 @@ PHLWINDOW CWindow::getSwallower() { return candidates[0]; // walk up the focus history and find the last focused - for (auto const& w : g_pCompositor->m_windowFocusHistory) { + for (auto const& w : Desktop::focusState()->windowHistory()) { if (!w) continue; @@ -1781,7 +1778,7 @@ void CWindow::updateDecorationValues() { setBorderColor(*RENDERDATA.borderGradient); else { const bool GROUPLOCKED = m_groupData.pNextWindow.lock() ? getGroupHead()->m_groupData.locked : false; - if (m_self == g_pCompositor->m_lastWindow) { + if (m_self == Desktop::focusState()->window()) { const auto* const ACTIVECOLOR = !m_groupData.pNextWindow.lock() ? (!m_groupData.deny ? ACTIVECOL : NOGROUPACTIVECOL) : (GROUPLOCKED ? GROUPACTIVELOCKEDCOL : GROUPACTIVECOL); setBorderColor(m_ruleApplicator->activeBorderColor().valueOr(*ACTIVECOLOR)); @@ -1797,7 +1794,7 @@ void CWindow::updateDecorationValues() { if (isEffectiveInternalFSMode(FSMODE_FULLSCREEN)) { *m_activeInactiveAlpha = m_ruleApplicator->alphaFullscreen().valueOrDefault().applyAlpha(*PFULLSCREENALPHA); } else { - if (m_self == g_pCompositor->m_lastWindow) + if (m_self == Desktop::focusState()->window()) *m_activeInactiveAlpha = m_ruleApplicator->alpha().valueOrDefault().applyAlpha(*PACTIVEALPHA); else *m_activeInactiveAlpha = m_ruleApplicator->alphaInactive().valueOrDefault().applyAlpha(*PINACTIVEALPHA); @@ -1805,7 +1802,7 @@ void CWindow::updateDecorationValues() { // dim float goalDim = 1.F; - if (m_self == g_pCompositor->m_lastWindow.lock() || m_ruleApplicator->noDim().valueOrDefault() || !*PDIMENABLED) + if (m_self == Desktop::focusState()->window() || m_ruleApplicator->noDim().valueOrDefault() || !*PDIMENABLED) goalDim = 0; else goalDim = *PDIMSTRENGTH; @@ -1817,7 +1814,7 @@ void CWindow::updateDecorationValues() { // shadow if (!isX11OverrideRedirect() && !m_X11DoesntWantBorders) { - if (m_self == g_pCompositor->m_lastWindow) + if (m_self == Desktop::focusState()->window()) *m_realShadowColor = CHyprColor(*PSHADOWCOL); else *m_realShadowColor = CHyprColor(*PSHADOWCOLINACTIVE != -1 ? *PSHADOWCOLINACTIVE : *PSHADOWCOL); @@ -1828,7 +1825,7 @@ void CWindow::updateDecorationValues() { } std::optional<double> CWindow::calculateSingleExpr(const std::string& s) { - const auto PMONITOR = m_monitor ? m_monitor : g_pCompositor->m_lastMonitor; + const auto PMONITOR = m_monitor ? m_monitor : Desktop::focusState()->monitor(); const auto CURSOR_LOCAL = g_pInputManager->getMouseCoordsInternal() - (PMONITOR ? PMONITOR->m_position : Vector2D{}); Math::CExpression expr; diff --git a/src/desktop/rule/windowRule/WindowRule.cpp b/src/desktop/rule/windowRule/WindowRule.cpp index 6a6878d2..893243b0 100644 --- a/src/desktop/rule/windowRule/WindowRule.cpp +++ b/src/desktop/rule/windowRule/WindowRule.cpp @@ -3,6 +3,7 @@ #include "../../../helpers/Monitor.hpp" #include "../../../Compositor.hpp" #include "../../../managers/TokenManager.hpp" +#include "../../../desktop/state/FocusState.hpp" using namespace Desktop; using namespace Desktop::Rule; @@ -72,7 +73,7 @@ bool CWindowRule::matches(PHLWINDOW w, bool allowEnvLookup) { return false; break; case RULE_PROP_FOCUS: - if (!engine->match(g_pCompositor->m_lastWindow == w)) + if (!engine->match(Desktop::focusState()->window() == w)) return false; break; case RULE_PROP_GROUP: diff --git a/src/desktop/state/FocusState.cpp b/src/desktop/state/FocusState.cpp new file mode 100644 index 00000000..8908d3de --- /dev/null +++ b/src/desktop/state/FocusState.cpp @@ -0,0 +1,330 @@ +#include "FocusState.hpp" +#include "../Window.hpp" +#include "../../Compositor.hpp" +#include "../../protocols/XDGShell.hpp" +#include "../../render/Renderer.hpp" +#include "../../managers/LayoutManager.hpp" +#include "../../managers/EventManager.hpp" +#include "../../managers/HookSystemManager.hpp" +#include "../../xwayland/XSurface.hpp" +#include "../../protocols/PointerConstraints.hpp" + +using namespace Desktop; + +SP<CFocusState> Desktop::focusState() { + static SP<CFocusState> state = makeShared<CFocusState>(); + return state; +} + +Desktop::CFocusState::CFocusState() { + m_windowOpen = g_pHookSystem->hookDynamic("openWindowEarly", [this](void* self, SCallbackInfo& info, std::any data) { + auto window = std::any_cast<PHLWINDOW>(data); + + addWindowToHistory(window); + }); + + m_windowClose = g_pHookSystem->hookDynamic("closeWindow", [this](void* self, SCallbackInfo& info, std::any data) { + auto window = std::any_cast<PHLWINDOW>(data); + + removeWindowFromHistory(window); + }); +} + +struct SFullscreenWorkspaceFocusResult { + PHLWINDOW overrideFocusWindow = nullptr; +}; + +static SFullscreenWorkspaceFocusResult onFullscreenWorkspaceFocusWindow(PHLWINDOW pWindow, bool forceFSCycle) { + const auto FSWINDOW = pWindow->m_workspace->getFullscreenWindow(); + const auto FSMODE = pWindow->m_workspace->m_fullscreenMode; + + if (pWindow == FSWINDOW) + return {}; // no conflict + + if (pWindow->m_isFloating) { + // if the window is floating, just bring it to the top + pWindow->m_createdOverFullscreen = true; + g_pHyprRenderer->damageWindow(pWindow); + return {}; + } + + static auto PONFOCUSUNDERFS = CConfigValue<Hyprlang::INT>("misc:on_focus_under_fullscreen"); + + switch (*PONFOCUSUNDERFS) { + case 0: + // focus the fullscreen window instead + return {.overrideFocusWindow = FSWINDOW}; + case 2: + // undo fs, unless we force a cycle + if (!forceFSCycle) { + g_pCompositor->setWindowFullscreenInternal(FSWINDOW, FSMODE_NONE); + break; + } + [[fallthrough]]; + case 1: + // replace fullscreen + g_pCompositor->setWindowFullscreenInternal(FSWINDOW, FSMODE_NONE); + g_pCompositor->setWindowFullscreenInternal(pWindow, FSMODE); + break; + + default: Debug::log(ERR, "Invalid misc:on_focus_under_fullscreen mode: {}", *PONFOCUSUNDERFS); break; + } + + return {}; +} + +void CFocusState::fullWindowFocus(PHLWINDOW pWindow, SP<CWLSurfaceResource> surface, bool preserveFocusHistory, bool forceFSCycle) { + if (pWindow) { + if (!pWindow->m_workspace) + return; + + const auto CURRENT_FS_MODE = pWindow->m_workspace->m_hasFullscreenWindow ? pWindow->m_workspace->m_fullscreenMode : FSMODE_NONE; + if (CURRENT_FS_MODE != FSMODE_NONE) { + const auto RESULT = onFullscreenWorkspaceFocusWindow(pWindow, forceFSCycle); + if (RESULT.overrideFocusWindow) + pWindow = RESULT.overrideFocusWindow; + } + } + + static auto PMODALPARENTBLOCKING = CConfigValue<Hyprlang::INT>("general:modal_parent_blocking"); + + if (*PMODALPARENTBLOCKING && pWindow && pWindow->m_xdgSurface && pWindow->m_xdgSurface->m_toplevel && pWindow->m_xdgSurface->m_toplevel->anyChildModal()) { + Debug::log(LOG, "Refusing focus to window shadowed by modal dialog"); + return; + } + + rawWindowFocus(pWindow, surface, preserveFocusHistory); +} + +void CFocusState::rawWindowFocus(PHLWINDOW pWindow, SP<CWLSurfaceResource> surface, bool preserveFocusHistory) { + static auto PFOLLOWMOUSE = CConfigValue<Hyprlang::INT>("input:follow_mouse"); + static auto PSPECIALFALLTHROUGH = CConfigValue<Hyprlang::INT>("input:special_fallthrough"); + + if (!pWindow || !pWindow->priorityFocus()) { + if (g_pSessionLockManager->isSessionLocked()) { + Debug::log(LOG, "Refusing a keyboard focus to a window because of a sessionlock"); + return; + } + + if (!g_pInputManager->m_exclusiveLSes.empty()) { + Debug::log(LOG, "Refusing a keyboard focus to a window because of an exclusive ls"); + return; + } + } + + if (pWindow && pWindow->m_isX11 && pWindow->isX11OverrideRedirect() && !pWindow->m_xwaylandSurface->wantsFocus()) + return; + + g_pLayoutManager->getCurrentLayout()->bringWindowToTop(pWindow); + + if (!pWindow || !validMapped(pWindow)) { + + if (m_focusWindow.expired() && !pWindow) + return; + + const auto PLASTWINDOW = m_focusWindow.lock(); + m_focusWindow.reset(); + + if (PLASTWINDOW && PLASTWINDOW->m_isMapped) { + PLASTWINDOW->m_ruleApplicator->propertiesChanged(Rule::RULE_PROP_FOCUS); + PLASTWINDOW->updateDecorationValues(); + + g_pXWaylandManager->activateWindow(PLASTWINDOW, false); + } + + g_pSeatManager->setKeyboardFocus(nullptr); + + g_pEventManager->postEvent(SHyprIPCEvent{"activewindow", ","}); + g_pEventManager->postEvent(SHyprIPCEvent{"activewindowv2", ""}); + + EMIT_HOOK_EVENT("activeWindow", PHLWINDOW{nullptr}); + + g_pLayoutManager->getCurrentLayout()->onWindowFocusChange(nullptr); + + m_focusSurface.reset(); + + g_pInputManager->recheckIdleInhibitorStatus(); + return; + } + + if (pWindow->m_ruleApplicator->noFocus().valueOrDefault()) { + Debug::log(LOG, "Ignoring focus to nofocus window!"); + return; + } + + if (m_focusWindow.lock() == pWindow && g_pSeatManager->m_state.keyboardFocus == surface && g_pSeatManager->m_state.keyboardFocus) + return; + + if (pWindow->m_pinned) + pWindow->m_workspace = m_focusMonitor->m_activeWorkspace; + + const auto PMONITOR = pWindow->m_monitor.lock(); + + if (!pWindow->m_workspace || !pWindow->m_workspace->isVisible()) { + const auto PWORKSPACE = pWindow->m_workspace; + // This is to fix incorrect feedback on the focus history. + PWORKSPACE->m_lastFocusedWindow = pWindow; + if (m_focusMonitor->m_activeWorkspace) + PWORKSPACE->rememberPrevWorkspace(m_focusMonitor->m_activeWorkspace); + if (PWORKSPACE->m_isSpecialWorkspace) + m_focusMonitor->changeWorkspace(PWORKSPACE, false, true); // if special ws, open on current monitor + else if (PMONITOR) + PMONITOR->changeWorkspace(PWORKSPACE, false, true); + // changeworkspace already calls focusWindow + return; + } + + const auto PLASTWINDOW = m_focusWindow.lock(); + m_focusWindow = pWindow; + + /* If special fallthrough is enabled, this behavior will be disabled, as I have no better idea of nicely tracking which + window focuses are "via keybinds" and which ones aren't. */ + if (PMONITOR && PMONITOR->m_activeSpecialWorkspace && PMONITOR->m_activeSpecialWorkspace != pWindow->m_workspace && !pWindow->m_pinned && !*PSPECIALFALLTHROUGH) + PMONITOR->setSpecialWorkspace(nullptr); + + // we need to make the PLASTWINDOW not equal to m_pLastWindow so that RENDERDATA is correct for an unfocused window + if (PLASTWINDOW && PLASTWINDOW->m_isMapped) { + PLASTWINDOW->m_ruleApplicator->propertiesChanged(Rule::RULE_PROP_FOCUS); + PLASTWINDOW->updateDecorationValues(); + + if (!pWindow->m_isX11 || !pWindow->isX11OverrideRedirect()) + g_pXWaylandManager->activateWindow(PLASTWINDOW, false); + } + + const auto PWINDOWSURFACE = surface ? surface : pWindow->m_wlSurface->resource(); + + rawSurfaceFocus(PWINDOWSURFACE, pWindow); + + g_pXWaylandManager->activateWindow(pWindow, true); // sets the m_pLastWindow + + pWindow->m_ruleApplicator->propertiesChanged(Rule::RULE_PROP_FOCUS); + pWindow->onFocusAnimUpdate(); + pWindow->updateDecorationValues(); + + if (pWindow->m_isUrgent) + pWindow->m_isUrgent = false; + + // Send an event + g_pEventManager->postEvent(SHyprIPCEvent{.event = "activewindow", .data = pWindow->m_class + "," + pWindow->m_title}); + g_pEventManager->postEvent(SHyprIPCEvent{.event = "activewindowv2", .data = std::format("{:x}", rc<uintptr_t>(pWindow.get()))}); + + EMIT_HOOK_EVENT("activeWindow", pWindow); + + g_pLayoutManager->getCurrentLayout()->onWindowFocusChange(pWindow); + + g_pInputManager->recheckIdleInhibitorStatus(); + + if (!preserveFocusHistory) { + // move to front of the window history + moveWindowToLatestInHistory(pWindow); + } + + if (*PFOLLOWMOUSE == 0) + g_pInputManager->sendMotionEventsToFocused(); + + if (pWindow->m_groupData.pNextWindow) + pWindow->deactivateGroupMembers(); +} + +void CFocusState::rawSurfaceFocus(SP<CWLSurfaceResource> pSurface, PHLWINDOW pWindowOwner) { + if (g_pSeatManager->m_state.keyboardFocus == pSurface || (pWindowOwner && g_pSeatManager->m_state.keyboardFocus == pWindowOwner->m_wlSurface->resource())) + return; // Don't focus when already focused on this. + + if (g_pSessionLockManager->isSessionLocked() && pSurface && !g_pSessionLockManager->isSurfaceSessionLock(pSurface)) + return; + + if (g_pSeatManager->m_seatGrab && !g_pSeatManager->m_seatGrab->accepts(pSurface)) { + Debug::log(LOG, "surface {:x} won't receive kb focus because grab rejected it", rc<uintptr_t>(pSurface.get())); + return; + } + + const auto PLASTSURF = m_focusSurface.lock(); + + // Unfocus last surface if should + if (m_focusSurface && !pWindowOwner) + g_pXWaylandManager->activateSurface(m_focusSurface.lock(), false); + + if (!pSurface) { + g_pSeatManager->setKeyboardFocus(nullptr); + g_pEventManager->postEvent(SHyprIPCEvent{.event = "activewindow", .data = ","}); + g_pEventManager->postEvent(SHyprIPCEvent{.event = "activewindowv2", .data = ""}); + EMIT_HOOK_EVENT("keyboardFocus", SP<CWLSurfaceResource>{nullptr}); + m_focusSurface.reset(); + return; + } + + if (g_pSeatManager->m_keyboard) + g_pSeatManager->setKeyboardFocus(pSurface); + + if (pWindowOwner) + Debug::log(LOG, "Set keyboard focus to surface {:x}, with {}", rc<uintptr_t>(pSurface.get()), pWindowOwner); + else + Debug::log(LOG, "Set keyboard focus to surface {:x}", rc<uintptr_t>(pSurface.get())); + + g_pXWaylandManager->activateSurface(pSurface, true); + m_focusSurface = pSurface; + + EMIT_HOOK_EVENT("keyboardFocus", pSurface); + + const auto SURF = CWLSurface::fromResource(pSurface); + const auto OLDSURF = CWLSurface::fromResource(PLASTSURF); + + if (OLDSURF && OLDSURF->constraint()) + OLDSURF->constraint()->deactivate(); + + if (SURF && SURF->constraint()) + SURF->constraint()->activate(); +} + +void CFocusState::rawMonitorFocus(PHLMONITOR pMonitor) { + if (m_focusMonitor == pMonitor) + return; + + if (!pMonitor) { + m_focusMonitor.reset(); + return; + } + + const auto PWORKSPACE = pMonitor->m_activeWorkspace; + + const auto WORKSPACE_ID = PWORKSPACE ? std::to_string(PWORKSPACE->m_id) : std::to_string(WORKSPACE_INVALID); + const auto WORKSPACE_NAME = PWORKSPACE ? PWORKSPACE->m_name : "?"; + + g_pEventManager->postEvent(SHyprIPCEvent{.event = "focusedmon", .data = pMonitor->m_name + "," + WORKSPACE_NAME}); + g_pEventManager->postEvent(SHyprIPCEvent{.event = "focusedmonv2", .data = pMonitor->m_name + "," + WORKSPACE_ID}); + + EMIT_HOOK_EVENT("focusedMon", pMonitor); + m_focusMonitor = pMonitor; +} + +SP<CWLSurfaceResource> CFocusState::surface() { + return m_focusSurface.lock(); +} + +PHLWINDOW CFocusState::window() { + return m_focusWindow.lock(); +} + +PHLMONITOR CFocusState::monitor() { + return m_focusMonitor.lock(); +} + +const std::vector<PHLWINDOWREF>& CFocusState::windowHistory() { + return m_windowFocusHistory; +} + +void CFocusState::removeWindowFromHistory(PHLWINDOW w) { + std::erase_if(m_windowFocusHistory, [&w](const auto& e) { return !e || e == w; }); +} + +void CFocusState::addWindowToHistory(PHLWINDOW w) { + m_windowFocusHistory.emplace_back(w); +} + +void CFocusState::moveWindowToLatestInHistory(PHLWINDOW w) { + const auto HISTORYPIVOT = std::ranges::find_if(m_windowFocusHistory, [&w](const auto& other) { return other.lock() == w; }); + if (HISTORYPIVOT == m_windowFocusHistory.end()) + Debug::log(TRACE, "CFocusState: {} has no pivot in history, ignoring request to move to latest", w); + else + std::rotate(m_windowFocusHistory.begin(), HISTORYPIVOT, HISTORYPIVOT + 1); +} diff --git a/src/desktop/state/FocusState.hpp b/src/desktop/state/FocusState.hpp new file mode 100644 index 00000000..2bf0953d --- /dev/null +++ b/src/desktop/state/FocusState.hpp @@ -0,0 +1,43 @@ +#pragma once + +#include "../DesktopTypes.hpp" +#include "../../SharedDefs.hpp" + +class CWLSurfaceResource; + +namespace Desktop { + class CFocusState { + public: + CFocusState(); + ~CFocusState() = default; + + CFocusState(CFocusState&&) = delete; + CFocusState(CFocusState&) = delete; + CFocusState(const CFocusState&) = delete; + + void fullWindowFocus(PHLWINDOW w, SP<CWLSurfaceResource> surface = nullptr, bool preserveFocusHistory = false, bool forceFSCycle = false); + void rawWindowFocus(PHLWINDOW w, SP<CWLSurfaceResource> surface = nullptr, bool preserveFocusHistory = false); + void rawSurfaceFocus(SP<CWLSurfaceResource> s, PHLWINDOW pWindowOwner = nullptr); + void rawMonitorFocus(PHLMONITOR m); + + SP<CWLSurfaceResource> surface(); + PHLWINDOW window(); + PHLMONITOR monitor(); + const std::vector<PHLWINDOWREF>& windowHistory(); + + void addWindowToHistory(PHLWINDOW w); + + private: + void removeWindowFromHistory(PHLWINDOW w); + void moveWindowToLatestInHistory(PHLWINDOW w); + + WP<CWLSurfaceResource> m_focusSurface; + PHLWINDOWREF m_focusWindow; + PHLMONITORREF m_focusMonitor; + std::vector<PHLWINDOWREF> m_windowFocusHistory; // first element is the most recently focused + + SP<HOOK_CALLBACK_FN> m_windowOpen, m_windowClose; + }; + + SP<CFocusState> focusState(); +}; |
