config: Add cursor:warp_on_monitor_change (#15844)

Allow to control cursor warping when switching to a workspace
on a different monitor.
This commit is contained in:
Nikolai Nechaev
2026-08-15 17:17:59 +02:00
committed by GitHub
parent fea3b3367b
commit a1b9dc0e8c
4 changed files with 86 additions and 11 deletions
+7 -5
View File
@@ -173,11 +173,13 @@ inline std::map<std::string, std::shared_ptr<CTestCase>> testCases;
} while (0)
#define EXPECT_NOT(expr, val) \
if (const auto RESULT = expr; RESULT == (val)) { \
MARK_TEST_FAILED("{}, expected not {}, got {}", #expr, val, RESULT); \
} else { \
LOG_OK("{}. Got {}", #expr, val); \
}
do { \
if (const auto RESULT = expr; RESULT == (val)) { \
MARK_TEST_FAILED("{}, expected not {}, got {}", #expr, val, RESULT); \
} else { \
LOG_OK("{}. Got {}", #expr, val); \
} \
} while (0)
#define EXPECT_VECTOR2D(expr, val) \
do { \
+70 -4
View File
@@ -225,10 +225,7 @@ TEST_CASE(globalWindowMoveState) {
}
TEST_CASE(pointerNoWarpsFocusesMonitorOnly) {
CScopeGuard guard = {[&]() {
OK(getFromSocket("/eval hl.config({ cursor = { no_warps = false } })"));
cleanupRefactorStateMonitors();
}};
CScopeGuard guard = {[&]() { cleanupRefactorStateMonitors(); }};
ASSERT(prepareRefactorStateMonitors(), true);
Tests::killAllWindows();
@@ -253,3 +250,72 @@ TEST_CASE(pointerNoWarpsFocusesMonitorOnly) {
OK(getFromSocket("/dispatch hl.dsp.cursor.move({ x = 2000, y = 10 })"));
EXPECT_NOT(getFromSocket("/cursorpos"), originalCursor);
}
SUBTEST(checkCursorWarpWhenSwitchingMonitor, bool noWarps, int warpOnChangeWorkspace, int warpOnMonitorChange, bool expectMove) {
auto conf = std::format("/eval hl.config({{ cursor = {{ no_warps = {}, warp_on_change_workspace = {}, warp_on_monitor_change = {} }} }})", noWarps, warpOnChangeWorkspace,
warpOnMonitorChange);
NLog::yellow("{}", conf);
OK(getFromSocket(conf));
OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = 300 })"));
OK(getFromSocket("/dispatch hl.dsp.cursor.move({ x = 5, y = 5 })"));
ASSERT(getFromSocket("/cursorpos"), "5, 5");
OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = 301 })"));
if (expectMove)
EXPECT_NOT(getFromSocket("/cursorpos"), "5, 5");
else
EXPECT(getFromSocket("/cursorpos"), "5, 5");
// TODO: uncomment after #15649 is resolved. Might need an additional fix in ConfigActions to pass:
// focusing with `hl.dsp.focus({ monitor = ... })` should trigger proper workspace switching.
/*
OK(getFromSocket(std::format("/dispatch hl.dsp.focus({{ monitor = '{}' }})", TEST_MONITOR_LEFT)));
OK(getFromSocket("/dispatch hl.dsp.cursor.move({ x = 5, y = 5 })"));
ASSERT(getFromSocket("/cursorpos"), "5, 5");
OK(getFromSocket(std::format("/dispatch hl.dsp.focus({{ monitor = '{}' }})", TEST_MONITOR_RIGHT)));
if (expectMove)
EXPECT_NOT(getFromSocket("/cursorpos"), "5, 5");
else
EXPECT(getFromSocket("/cursorpos"), "5, 5");
*/
}
TEST_CASE(cursorWarpFocusOnMonitorChange) {
CScopeGuard guard = {[&]() { cleanupRefactorStateMonitors(); }};
ASSERT(prepareRefactorStateMonitors(), true);
OK(getFromSocket(std::format("/dispatch hl.dsp.focus({{ monitor = '{}' }})", TEST_MONITOR_RIGHT)));
OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = '301' })"));
SPAWN_KITTY("kitty301"); // A window must exist on the workspace (bug #15649)
OK(getFromSocket(std::format("/dispatch hl.dsp.focus({{ monitor = '{}' }})", TEST_MONITOR_LEFT)));
SPAWN_KITTY("kitty300"); // A window must exist on the workspace (bug #15649)
OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = '300' })"));
// no_warps, W, M, expect_move
//CALL_SUBTEST(checkCursorWarpWhenSwitchingMonitor, false, 0, -1, false); // BUG: fails!
//CALL_SUBTEST(checkCursorWarpWhenSwitchingMonitor, false, 0, 0, false); // BUG: fails!
CALL_SUBTEST(checkCursorWarpWhenSwitchingMonitor, false, 0, 1, true);
CALL_SUBTEST(checkCursorWarpWhenSwitchingMonitor, false, 0, 2, true);
CALL_SUBTEST(checkCursorWarpWhenSwitchingMonitor, false, 1, -1, true);
//CALL_SUBTEST(checkCursorWarpWhenSwitchingMonitor, false, 1, 0, false); // BUG: fails!
CALL_SUBTEST(checkCursorWarpWhenSwitchingMonitor, false, 1, 1, true);
CALL_SUBTEST(checkCursorWarpWhenSwitchingMonitor, false, 1, 2, true);
CALL_SUBTEST(checkCursorWarpWhenSwitchingMonitor, false, 2, -1, true);
//CALL_SUBTEST(checkCursorWarpWhenSwitchingMonitor, false, 2, 0, false); // BUG: fails!
CALL_SUBTEST(checkCursorWarpWhenSwitchingMonitor, false, 2, 1, true);
CALL_SUBTEST(checkCursorWarpWhenSwitchingMonitor, false, 2, 2, true);
CALL_SUBTEST(checkCursorWarpWhenSwitchingMonitor, true, 0, -1, false);
CALL_SUBTEST(checkCursorWarpWhenSwitchingMonitor, true, 0, 0, false);
CALL_SUBTEST(checkCursorWarpWhenSwitchingMonitor, true, 0, 1, false);
CALL_SUBTEST(checkCursorWarpWhenSwitchingMonitor, true, 0, 2, true);
CALL_SUBTEST(checkCursorWarpWhenSwitchingMonitor, true, 1, -1, false);
CALL_SUBTEST(checkCursorWarpWhenSwitchingMonitor, true, 1, 0, false);
CALL_SUBTEST(checkCursorWarpWhenSwitchingMonitor, true, 1, 1, false);
CALL_SUBTEST(checkCursorWarpWhenSwitchingMonitor, true, 1, 2, true);
CALL_SUBTEST(checkCursorWarpWhenSwitchingMonitor, true, 2, -1, true);
CALL_SUBTEST(checkCursorWarpWhenSwitchingMonitor, true, 2, 0, false);
CALL_SUBTEST(checkCursorWarpWhenSwitchingMonitor, true, 2, 1, false);
CALL_SUBTEST(checkCursorWarpWhenSwitchingMonitor, true, 2, 2, true);
}
+7 -2
View File
@@ -90,6 +90,8 @@ static void switchToWindow(PHLWINDOW PWINDOWTOCHANGETO, bool forceFSCycle = fals
}
}
// TODO: This fucntion should probably just find a workspace and call `Actions::changeWorkspace`.
// Can be refactored after #15649 is resolved.
static bool tryMoveFocusToMonitor(PHLMONITOR monitor) {
if (!monitor)
return false;
@@ -985,13 +987,16 @@ ActionResult Actions::changeWorkspace(PHLWORKSPACE ws) {
}
const static auto PWARPONWORKSPACECHANGE = CConfigValue<Config::INTEGER>("cursor:warp_on_change_workspace");
const static auto PWARPONMONITORCHANGE = CConfigValue<Config::INTEGER>("cursor:warp_on_monitor_change");
if (*PWARPONWORKSPACECHANGE > 0) {
const auto WARP = ws->monitorID() == PMONITOR->id() || *PWARPONMONITORCHANGE == -1 ? *PWARPONWORKSPACECHANGE : *PWARPONMONITORCHANGE;
if (WARP > 0) {
auto PLAST = ws->getLastFocusedWindow();
auto HLSurface = Desktop::View::CWLSurface::fromResource(g_pSeatManager->m_state.pointerFocus.lock());
if (PLAST && (!HLSurface || HLSurface->view()->type() == Desktop::View::VIEW_TYPE_WINDOW))
PLAST->warpCursor(*PWARPONWORKSPACECHANGE == 2);
PLAST->warpCursor(WARP == 2);
}
return {};
+2
View File
@@ -613,6 +613,8 @@ std::vector<SP<IValue>> Values::getConfigValues() {
MS<Bool>("cursor:persistent_warps", "When a window is refocused, the cursor returns to its last position relative to that window.", false),
MS<Int>("cursor:warp_on_change_workspace", "Move the cursor to the last focused window after changing the workspace.", 0,
{.min = 0, .max = 2, .map = OptionMap{{"disable", 0}, {"enable", 1}, {"force", 2}}}),
MS<Int>("cursor:warp_on_monitor_change", "Move the cursor to the last focused window when focusing a different monitor.", -1,
{.min = -1, .max = 2, .map = OptionMap{{"same_as_warp_on_change_workspace", -1}, {"disable", 0}, {"enable", 1}, {"force", 2}}}),
MS<Int>("cursor:warp_on_toggle_special", "Move the cursor to the last focused window when toggling a special workspace.", 0,
{.min = 0, .max = 2, .map = OptionMap{{"disable", 0}, {"enable", 1}, {"force", 2}}}),
MS<String>("cursor:default_monitor", "the name of a default monitor for the cursor to be set to on startup", STRVAL_EMPTY),