From e12d2b7cc846485a172444c2e5288c55fa3a3c59 Mon Sep 17 00:00:00 2001 From: Mihail Costea Date: Mon, 20 Apr 2026 18:56:30 +0300 Subject: input: implement follow_mouse_shrink (#13707) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * input: implement follow_mouse_shrink Add a new config option `input:follow_mouse_shrink` (INT, 0-300, default 0) that shrinks inactive window hitboxes by the specified number of pixels for focus detection purposes. This creates a dead zone in gaps between windows where moving the cursor will not trigger a focus change. The shrink only applies to inactive (non-focused) windows and only during mouse-move focus checks (follow_mouse = 1). Click and scroll interactions are unaffected — clicking on a window in the shrunk area still works normally once the window is focused. Closes #9973 * tests: add integration test for follow_mouse_shrink --- src/Compositor.cpp | 12 +++++++++++- src/Compositor.hpp | 2 +- src/config/legacy/ConfigManager.cpp | 1 + src/config/supplementary/ConfigDescriptions.hpp | 8 ++++++++ src/desktop/view/Window.hpp | 3 ++- src/managers/input/InputManager.cpp | 3 ++- 6 files changed, 25 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/Compositor.cpp b/src/Compositor.cpp index 92f81fc6..f823b62c 100644 --- a/src/Compositor.cpp +++ b/src/Compositor.cpp @@ -895,7 +895,7 @@ bool CCompositor::monitorExists(PHLMONITOR pMonitor) { return std::ranges::any_of(m_realMonitors, [&](const PHLMONITOR& m) { return m == pMonitor; }); } -PHLWINDOW CCompositor::vectorToWindowUnified(const Vector2D& pos, uint8_t properties, PHLWINDOW pIgnoreWindow) { +PHLWINDOW CCompositor::vectorToWindowUnified(const Vector2D& pos, uint16_t properties, PHLWINDOW pIgnoreWindow) { const auto PMONITOR = getMonitorFromVector(pos); if (!PMONITOR) return nullptr; @@ -905,8 +905,12 @@ PHLWINDOW CCompositor::vectorToWindowUnified(const Vector2D& pos, uint8_t proper static auto PBORDERGRABEXTEND = CConfigValue("general:extend_border_grab_area"); static auto PSPECIALFALLTHRU = CConfigValue("input:special_fallthrough"); static auto PMODALPARENTBLOCKING = CConfigValue("general:modal_parent_blocking"); + static auto PFOLLOWMOUSESHRINK = CConfigValue("input:follow_mouse_shrink"); const auto BORDER_GRAB_AREA = *PRESIZEONBORDER ? *PBORDERSIZE + *PBORDERGRABEXTEND : 0; const bool ONLY_PRIORITY = properties & Desktop::View::FOCUS_PRIORITY; + const bool FOLLOW_MOUSE_CHECK = properties & Desktop::View::FOLLOW_MOUSE_CHECK; + const auto HITBOX_SHRINK = FOLLOW_MOUSE_CHECK ? *PFOLLOWMOUSESHRINK : 0; + const auto LASTFOCUSED = Desktop::focusState()->window(); const auto isShadowedByModal = [](PHLWINDOW w) -> bool { return *PMODALPARENTBLOCKING && w->m_xdgSurface && w->m_xdgSurface->m_toplevel && w->m_xdgSurface->m_toplevel->anyChildModal(); @@ -922,6 +926,8 @@ PHLWINDOW CCompositor::vectorToWindowUnified(const Vector2D& pos, uint8_t proper w != pIgnoreWindow && !isShadowedByModal(w)) { const auto BB = w->getWindowBoxUnified(properties); CBox box = BB.copy().expand(!w->isX11OverrideRedirect() ? BORDER_GRAB_AREA : 0); + if (HITBOX_SHRINK > 0 && w != LASTFOCUSED) + box = box.copy().expand(-HITBOX_SHRINK); if (box.containsPoint(pos)) return w; @@ -964,6 +970,8 @@ PHLWINDOW CCompositor::vectorToWindowUnified(const Vector2D& pos, uint8_t proper const auto BB = w->getWindowBoxUnified(properties); CBox box = BB.copy().expand(!w->isX11OverrideRedirect() ? BORDER_GRAB_AREA : 0); + if (HITBOX_SHRINK > 0 && w != LASTFOCUSED) + box = box.copy().expand(-HITBOX_SHRINK); if (box.containsPoint(pos)) { if (w->m_isX11 && w->isX11OverrideRedirect() && !w->m_xwaylandSurface->wantsFocus()) { @@ -1095,6 +1103,8 @@ PHLWINDOW CCompositor::vectorToWindowUnified(const Vector2D& pos, uint8_t proper if (isWindowCloseToWorkAreaEdge(Math::eDirection::DIRECTION_DOWN)) box.height += BORDER_GRAB_AREA; } + if (HITBOX_SHRINK > 0 && w != LASTFOCUSED) + box = box.copy().expand(-HITBOX_SHRINK); if (box.containsPoint(pos)) return w; } diff --git a/src/Compositor.hpp b/src/Compositor.hpp index 96850407..fe9ed2e9 100644 --- a/src/Compositor.hpp +++ b/src/Compositor.hpp @@ -99,7 +99,7 @@ class CCompositor { PHLMONITOR getMonitorFromVector(const Vector2D&); void removeWindowFromVectorSafe(PHLWINDOW); bool monitorExists(PHLMONITOR); - PHLWINDOW vectorToWindowUnified(const Vector2D&, uint8_t properties, PHLWINDOW pIgnoreWindow = nullptr); + PHLWINDOW vectorToWindowUnified(const Vector2D&, uint16_t properties, PHLWINDOW pIgnoreWindow = nullptr); SP vectorToLayerSurface(const Vector2D&, std::vector*, Vector2D*, PHLLS*, bool aboveLockscreen = false); SP vectorToLayerPopupSurface(const Vector2D&, PHLMONITOR monitor, Vector2D*, PHLLS*); SP vectorWindowToSurface(const Vector2D&, PHLWINDOW, Vector2D& sl); diff --git a/src/config/legacy/ConfigManager.cpp b/src/config/legacy/ConfigManager.cpp index bfe8fef3..310d2710 100644 --- a/src/config/legacy/ConfigManager.cpp +++ b/src/config/legacy/ConfigManager.cpp @@ -684,6 +684,7 @@ CConfigManager::CConfigManager() { registerConfigVar("animations:workspace_wraparound", Hyprlang::INT{0}); registerConfigVar("input:follow_mouse", Hyprlang::INT{1}); + registerConfigVar("input:follow_mouse_shrink", Hyprlang::INT{0}); registerConfigVar("input:follow_mouse_threshold", Hyprlang::FLOAT{0}); registerConfigVar("input:focus_on_close", Hyprlang::INT{0}); registerConfigVar("input:mouse_refocus", Hyprlang::INT{1}); diff --git a/src/config/supplementary/ConfigDescriptions.hpp b/src/config/supplementary/ConfigDescriptions.hpp index de679b52..862cc1a9 100644 --- a/src/config/supplementary/ConfigDescriptions.hpp +++ b/src/config/supplementary/ConfigDescriptions.hpp @@ -639,6 +639,14 @@ namespace Config::Supplementary { .type = CONFIG_OPTION_INT, .data = SConfigOptionDescription::SRangeData{1, 0, 3}, }, + SConfigOptionDescription{ + .value = "input:follow_mouse_shrink", + .description = + "Shrinks the inactive window hitboxes used for focus detection by the specified number of pixels. This creates a dead zone in gaps between windows where " + "moving the cursor will not change focus. Works only with follow_mouse = 1.", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{0, 0, 300}, + }, SConfigOptionDescription{ .value = "input:follow_mouse_threshold", .description = "The smallest distance in logical pixels the mouse needs to travel for the window under it to get focused. Works only with follow_mouse = 1.", diff --git a/src/desktop/view/Window.hpp b/src/desktop/view/Window.hpp index f69585e3..a72c626b 100644 --- a/src/desktop/view/Window.hpp +++ b/src/desktop/view/Window.hpp @@ -55,7 +55,7 @@ namespace Desktop::View { GROUP_DENY = 1 << 7, // deny }; - enum eGetWindowProperties : uint8_t { + enum eGetWindowProperties : uint16_t { WINDOW_ONLY = 0, RESERVED_EXTENTS = 1 << 0, INPUT_EXTENTS = 1 << 1, @@ -65,6 +65,7 @@ namespace Desktop::View { USE_PROP_TILED = 1 << 5, SKIP_FULLSCREEN_PRIORITY = 1 << 6, FOCUS_PRIORITY = 1 << 7, + FOLLOW_MOUSE_CHECK = 1 << 8, }; enum eSuppressEvents : uint8_t { diff --git a/src/managers/input/InputManager.cpp b/src/managers/input/InputManager.cpp index 95e7704a..c4df7c53 100644 --- a/src/managers/input/InputManager.cpp +++ b/src/managers/input/InputManager.cpp @@ -419,7 +419,8 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus, bool mouse, st PHLWINDOW pWindowIdeal; const auto getWindowIdeal = [&]() -> const PHLWINDOW& { if (!windowIdealQueried) { - pWindowIdeal = g_pCompositor->vectorToWindowUnified(mouseCoords, Desktop::View::RESERVED_EXTENTS | Desktop::View::INPUT_EXTENTS | Desktop::View::ALLOW_FLOATING); + pWindowIdeal = g_pCompositor->vectorToWindowUnified( + mouseCoords, Desktop::View::RESERVED_EXTENTS | Desktop::View::INPUT_EXTENTS | Desktop::View::ALLOW_FLOATING | Desktop::View::FOLLOW_MOUSE_CHECK); windowIdealQueried = true; } -- cgit v1.2.3