binds: add drag_threshold for click/drag isolation (#9839)

---------

Co-authored-by: Leeman <lstrout@enlj.com>
This commit is contained in:
alaricljs
2025-04-12 10:43:13 -04:00
committed by GitHub
parent 0399e64274
commit 6538970087
7 changed files with 103 additions and 41 deletions

View File

@@ -1326,6 +1326,12 @@ inline static const std::vector<SConfigOptionDescription> CONFIG_OPTIONS = {
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{false},
},
SConfigOptionDescription{
.value = "binds:drag_threshold",
.description = "Movement threshold in pixels for window dragging and c/g bind flags. 0 to disable and grab on mousedown.",
.type = CONFIG_OPTION_INT,
.data = SConfigOptionDescription::SRangeData{0, 0, INT_MAX},
},
/*
* xwayland:

View File

@@ -651,6 +651,7 @@ CConfigManager::CConfigManager() {
registerConfigVar("binds:disable_keybind_grabbing", Hyprlang::INT{0});
registerConfigVar("binds:window_direction_monitor_fallback", Hyprlang::INT{1});
registerConfigVar("binds:allow_pin_fullscreen", Hyprlang::INT{0});
registerConfigVar("binds:drag_threshold", Hyprlang::INT{0});
registerConfigVar("gestures:workspace_swipe", Hyprlang::INT{0});
registerConfigVar("gestures:workspace_swipe_fingers", Hyprlang::INT{3});
@@ -2255,6 +2256,8 @@ std::optional<std::string> CConfigManager::handleBind(const std::string& command
bool longPress = false;
bool hasDescription = false;
bool dontInhibit = false;
bool click = false;
bool drag = false;
const auto BINDARGS = command.substr(4);
for (auto const& arg : BINDARGS) {
@@ -2280,6 +2283,12 @@ std::optional<std::string> CConfigManager::handleBind(const std::string& command
hasDescription = true;
} else if (arg == 'p') {
dontInhibit = true;
} else if (arg == 'c') {
click = true;
release = true;
} else if (arg == 'g') {
drag = true;
release = true;
} else {
return "bind: invalid flag";
}
@@ -2291,6 +2300,9 @@ std::optional<std::string> CConfigManager::handleBind(const std::string& command
if (mouse && (repeat || release || locked))
return "flag m is exclusive";
if (click && drag)
return "flags c and g are mutually exclusive";
const int numbArgs = hasDescription ? 5 : 4;
const auto ARGS = CVarList(value, numbArgs);
@@ -2350,7 +2362,8 @@ std::optional<std::string> CConfigManager::handleBind(const std::string& command
g_pKeybindManager->addKeybind(SKeybind{parsedKey.key, KEYSYMS, parsedKey.keycode, parsedKey.catchAll, MOD, MODS, HANDLER,
COMMAND, locked, m_szCurrentSubmap, DESCRIPTION, release, repeat, longPress,
mouse, nonConsuming, transparent, ignoreMods, multiKey, hasDescription, dontInhibit});
mouse, nonConsuming, transparent, ignoreMods, multiKey, hasDescription, dontInhibit,
click, drag});
}
return {};

View File

@@ -228,7 +228,8 @@ bool IHyprLayout::onWindowCreatedAutoGroup(PHLWINDOW pWindow) {
}
void IHyprLayout::onBeginDragWindow() {
const auto DRAGGINGWINDOW = g_pInputManager->currentlyDraggedWindow.lock();
const auto DRAGGINGWINDOW = g_pInputManager->currentlyDraggedWindow.lock();
static auto PDRAGTHRESHOLD = CConfigValue<Hyprlang::INT>("binds:drag_threshold");
m_iMouseMoveEventCount = 1;
m_vBeginDragSizeXY = Vector2D();
@@ -240,42 +241,11 @@ void IHyprLayout::onBeginDragWindow() {
return;
}
const bool WAS_FULLSCREEN = DRAGGINGWINDOW->isFullscreen();
if (WAS_FULLSCREEN) {
Debug::log(LOG, "Dragging a fullscreen window");
g_pCompositor->setWindowFullscreenInternal(DRAGGINGWINDOW, FSMODE_NONE);
}
const auto PWORKSPACE = DRAGGINGWINDOW->m_pWorkspace;
if (PWORKSPACE->m_bHasFullscreenWindow && (!DRAGGINGWINDOW->m_bCreatedOverFullscreen || !DRAGGINGWINDOW->m_bIsFloating)) {
Debug::log(LOG, "Rejecting drag on a fullscreen workspace. (window under fullscreen)");
g_pKeybindManager->changeMouseBindMode(MBIND_INVALID);
// Try to pick up dragged window now if drag_threshold is disabled
// or at least update dragging related variables for the cursors
g_pInputManager->m_bDragThresholdReached = *PDRAGTHRESHOLD <= 0;
if (updateDragWindow())
return;
}
DRAGGINGWINDOW->m_bDraggingTiled = false;
m_vDraggingWindowOriginalFloatSize = DRAGGINGWINDOW->m_vLastFloatingSize;
if (WAS_FULLSCREEN && DRAGGINGWINDOW->m_bIsFloating) {
const auto MOUSECOORDS = g_pInputManager->getMouseCoordsInternal();
*DRAGGINGWINDOW->m_vRealPosition = MOUSECOORDS - DRAGGINGWINDOW->m_vRealSize->goal() / 2.f;
} else if (!DRAGGINGWINDOW->m_bIsFloating) {
if (g_pInputManager->dragMode == MBIND_MOVE) {
DRAGGINGWINDOW->m_vLastFloatingSize = (DRAGGINGWINDOW->m_vRealSize->goal() * 0.8489).clamp(Vector2D{5, 5}, Vector2D{}).floor();
changeWindowFloatingMode(DRAGGINGWINDOW);
DRAGGINGWINDOW->m_bIsFloating = true;
DRAGGINGWINDOW->m_bDraggingTiled = true;
*DRAGGINGWINDOW->m_vRealPosition = g_pInputManager->getMouseCoordsInternal() - DRAGGINGWINDOW->m_vRealSize->goal() / 2.f;
}
}
m_vBeginDragXY = g_pInputManager->getMouseCoordsInternal();
m_vBeginDragPositionXY = DRAGGINGWINDOW->m_vRealPosition->goal();
m_vBeginDragSizeXY = DRAGGINGWINDOW->m_vRealSize->goal();
m_vLastDragXY = m_vBeginDragXY;
// get the grab corner
static auto RESIZECORNER = CConfigValue<Hyprlang::INT>("general:resize_corner");
@@ -552,7 +522,8 @@ void IHyprLayout::onMouseMove(const Vector2D& mousePos) {
if (g_pInputManager->currentlyDraggedWindow.expired())
return;
const auto DRAGGINGWINDOW = g_pInputManager->currentlyDraggedWindow.lock();
const auto DRAGGINGWINDOW = g_pInputManager->currentlyDraggedWindow.lock();
static auto PDRAGTHRESHOLD = CConfigValue<Hyprlang::INT>("binds:drag_threshold");
// Window invalid or drag begin size 0,0 meaning we rejected it.
if ((!validMapped(DRAGGINGWINDOW) || m_vBeginDragSizeXY == Vector2D())) {
@@ -560,6 +531,15 @@ void IHyprLayout::onMouseMove(const Vector2D& mousePos) {
return;
}
// Yoink dragged window here instead if using drag_threshold and it has been reached
if (*PDRAGTHRESHOLD > 0 && !g_pInputManager->m_bDragThresholdReached) {
if ((m_vBeginDragXY.distanceSq(mousePos) <= std::pow(*PDRAGTHRESHOLD, 2) && m_vBeginDragXY == m_vLastDragXY))
return;
g_pInputManager->m_bDragThresholdReached = true;
if (updateDragWindow())
return;
}
static auto TIMER = std::chrono::high_resolution_clock::now(), MSTIMER = TIMER;
const auto SPECIAL = DRAGGINGWINDOW->onSpecialWorkspace();
@@ -962,3 +942,41 @@ Vector2D IHyprLayout::predictSizeForNewWindow(PHLWINDOW pWindow) {
return sizePredicted;
}
bool IHyprLayout::updateDragWindow() {
const auto DRAGGINGWINDOW = g_pInputManager->currentlyDraggedWindow.lock();
if (g_pInputManager->m_bDragThresholdReached) {
if (DRAGGINGWINDOW->isFullscreen()) {
Debug::log(LOG, "Dragging a fullscreen window");
g_pCompositor->setWindowFullscreenInternal(DRAGGINGWINDOW, FSMODE_NONE);
}
const auto PWORKSPACE = DRAGGINGWINDOW->m_pWorkspace;
if (PWORKSPACE->m_bHasFullscreenWindow && (!DRAGGINGWINDOW->m_bCreatedOverFullscreen || !DRAGGINGWINDOW->m_bIsFloating)) {
Debug::log(LOG, "Rejecting drag on a fullscreen workspace. (window under fullscreen)");
g_pKeybindManager->changeMouseBindMode(MBIND_INVALID);
return true;
}
}
DRAGGINGWINDOW->m_bDraggingTiled = false;
m_vDraggingWindowOriginalFloatSize = DRAGGINGWINDOW->m_vLastFloatingSize;
if (!DRAGGINGWINDOW->m_bIsFloating && g_pInputManager->dragMode == MBIND_MOVE) {
DRAGGINGWINDOW->m_vLastFloatingSize = (DRAGGINGWINDOW->m_vRealSize->goal() * 0.8489).clamp(Vector2D{5, 5}, Vector2D{}).floor();
*DRAGGINGWINDOW->m_vRealPosition = g_pInputManager->getMouseCoordsInternal() - DRAGGINGWINDOW->m_vRealSize->goal() / 2.f;
if (g_pInputManager->m_bDragThresholdReached) {
changeWindowFloatingMode(DRAGGINGWINDOW);
DRAGGINGWINDOW->m_bIsFloating = true;
DRAGGINGWINDOW->m_bDraggingTiled = true;
}
}
m_vBeginDragXY = g_pInputManager->getMouseCoordsInternal();
m_vBeginDragPositionXY = DRAGGINGWINDOW->m_vRealPosition->goal();
m_vBeginDragSizeXY = DRAGGINGWINDOW->m_vRealSize->goal();
m_vLastDragXY = m_vBeginDragXY;
return false;
}

View File

@@ -204,6 +204,13 @@ class IHyprLayout {
virtual Vector2D predictSizeForNewWindow(PHLWINDOW pWindow);
virtual Vector2D predictSizeForNewWindowFloating(PHLWINDOW pWindow);
/*
Called to try to pick up window for dragging.
Updates drag related variables and floats window if threshold reached.
Return true to reject
*/
virtual bool updateDragWindow();
private:
int m_iMouseMoveEventCount;
Vector2D m_vBeginDragXY;

View File

@@ -460,6 +460,7 @@ bool CKeybindManager::onKeyEvent(std::any event, SP<IKeyboard> pKeyboard) {
.modmaskAtPressTime = MODS,
.sent = true,
.submapAtPress = m_szCurrentSelectedSubmap,
.mousePosAtPress = g_pInputManager->getMouseCoordsInternal(),
};
m_vActiveKeybinds.clear();
@@ -551,6 +552,7 @@ bool CKeybindManager::onMouseEvent(const IPointer::SButtonEvent& e) {
const auto KEY = SPressedKeyWithMods{
.keyName = KEY_NAME,
.modmaskAtPressTime = MODS,
.mousePosAtPress = g_pInputManager->getMouseCoordsInternal(),
};
m_vActiveKeybinds.clear();
@@ -638,7 +640,9 @@ std::string CKeybindManager::getCurrentSubmap() {
SDispatchResult CKeybindManager::handleKeybinds(const uint32_t modmask, const SPressedKeyWithMods& key, bool pressed) {
static auto PDISABLEINHIBIT = CConfigValue<Hyprlang::INT>("binds:disable_keybind_grabbing");
bool found = false;
static auto PDRAGTHRESHOLD = CConfigValue<Hyprlang::INT>("binds:drag_threshold");
bool found = false;
SDispatchResult res;
if (pressed) {
@@ -736,6 +740,14 @@ SDispatchResult CKeybindManager::handleKeybinds(const uint32_t modmask, const SP
found = true; // suppress the event
continue;
}
// Require mouse to stay inside drag_threshold for clicks, outside for drags
// Check if either a mouse bind has triggered or currently over the threshold (maybe there is no mouse bind on the same key)
const auto THRESHOLDREACHED = key.mousePosAtPress.distanceSq(g_pInputManager->getMouseCoordsInternal()) > std::pow(*PDRAGTHRESHOLD, 2);
if (k->click && (g_pInputManager->m_bDragThresholdReached || THRESHOLDREACHED))
continue;
else if (k->drag && !g_pInputManager->m_bDragThresholdReached && !THRESHOLDREACHED)
continue;
}
if (k->longPress) {
@@ -788,6 +800,8 @@ SDispatchResult CKeybindManager::handleKeybinds(const uint32_t modmask, const SP
found = true;
}
g_pInputManager->m_bDragThresholdReached = false;
// if keybind wasn't found (or dispatcher said to) then pass event
res.passEvent |= !found;

View File

@@ -39,6 +39,8 @@ struct SKeybind {
bool multiKey = false;
bool hasDescription = false;
bool dontInhibit = false;
bool click = false;
bool drag = false;
// DO NOT INITIALIZE
bool shadowed = false;
@@ -62,6 +64,7 @@ struct SPressedKeyWithMods {
uint32_t modmaskAtPressTime = 0;
bool sent = false;
std::string submapAtPress = "";
Vector2D mousePosAtPress = {};
};
struct SParsedKey {

View File

@@ -147,8 +147,9 @@ class CInputManager {
// for dragging floating windows
PHLWINDOWREF currentlyDraggedWindow;
eMouseBindMode dragMode = MBIND_INVALID;
bool m_bWasDraggingWindow = false;
eMouseBindMode dragMode = MBIND_INVALID;
bool m_bWasDraggingWindow = false;
bool m_bDragThresholdReached = false;
// for refocus to be forced
PHLWINDOWREF m_pForcedFocus;