diff --git a/src/config/ConfigDescriptions.hpp b/src/config/ConfigDescriptions.hpp index 7037f537..78b9f559 100644 --- a/src/config/ConfigDescriptions.hpp +++ b/src/config/ConfigDescriptions.hpp @@ -1326,6 +1326,12 @@ inline static const std::vector 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: diff --git a/src/config/ConfigManager.cpp b/src/config/ConfigManager.cpp index 56ff3f30..70f6701e 100644 --- a/src/config/ConfigManager.cpp +++ b/src/config/ConfigManager.cpp @@ -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 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 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 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 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 {}; diff --git a/src/layout/IHyprLayout.cpp b/src/layout/IHyprLayout.cpp index a0f15f5c..0c1c3788 100644 --- a/src/layout/IHyprLayout.cpp +++ b/src/layout/IHyprLayout.cpp @@ -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("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("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("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; +} diff --git a/src/layout/IHyprLayout.hpp b/src/layout/IHyprLayout.hpp index e31bb63e..f85fc52a 100644 --- a/src/layout/IHyprLayout.hpp +++ b/src/layout/IHyprLayout.hpp @@ -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; diff --git a/src/managers/KeybindManager.cpp b/src/managers/KeybindManager.cpp index 948ef11e..2644cf53 100644 --- a/src/managers/KeybindManager.cpp +++ b/src/managers/KeybindManager.cpp @@ -460,6 +460,7 @@ bool CKeybindManager::onKeyEvent(std::any event, SP 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("binds:disable_keybind_grabbing"); - bool found = false; + static auto PDRAGTHRESHOLD = CConfigValue("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; diff --git a/src/managers/KeybindManager.hpp b/src/managers/KeybindManager.hpp index 8b13bac2..0040d761 100644 --- a/src/managers/KeybindManager.hpp +++ b/src/managers/KeybindManager.hpp @@ -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 { diff --git a/src/managers/input/InputManager.hpp b/src/managers/input/InputManager.hpp index fa06454f..406e1845 100644 --- a/src/managers/input/InputManager.hpp +++ b/src/managers/input/InputManager.hpp @@ -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;