summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorMihail Costea <mihail.costea@dealfront.com>2026-04-20 18:56:30 +0300
committerGitHub <noreply@github.com>2026-04-20 16:56:30 +0100
commite12d2b7cc846485a172444c2e5288c55fa3a3c59 (patch)
tree34ee6c646d31160c686a029ae1ca634bb1e6bfad /src
parenta360c31d0434898888e710f4a5f560200f50cecf (diff)
input: implement follow_mouse_shrink (#13707)
* 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
Diffstat (limited to 'src')
-rw-r--r--src/Compositor.cpp12
-rw-r--r--src/Compositor.hpp2
-rw-r--r--src/config/legacy/ConfigManager.cpp1
-rw-r--r--src/config/supplementary/ConfigDescriptions.hpp8
-rw-r--r--src/desktop/view/Window.hpp3
-rw-r--r--src/managers/input/InputManager.cpp3
6 files changed, 25 insertions, 4 deletions
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<Hyprlang::INT>("general:extend_border_grab_area");
static auto PSPECIALFALLTHRU = CConfigValue<Hyprlang::INT>("input:special_fallthrough");
static auto PMODALPARENTBLOCKING = CConfigValue<Hyprlang::INT>("general:modal_parent_blocking");
+ static auto PFOLLOWMOUSESHRINK = CConfigValue<Hyprlang::INT>("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<CWLSurfaceResource> vectorToLayerSurface(const Vector2D&, std::vector<PHLLSREF>*, Vector2D*, PHLLS*, bool aboveLockscreen = false);
SP<CWLSurfaceResource> vectorToLayerPopupSurface(const Vector2D&, PHLMONITOR monitor, Vector2D*, PHLLS*);
SP<CWLSurfaceResource> 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
@@ -640,6 +640,14 @@ namespace Config::Supplementary {
.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.",
.type = CONFIG_OPTION_FLOAT,
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;
}