mirror of
https://github.com/hyprwm/Hyprland.git
synced 2025-08-15 20:13:49 -07:00
binds: add drag_threshold for click/drag isolation (#9839)
--------- Co-authored-by: Leeman <lstrout@enlj.com>
This commit is contained in:
@@ -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:
|
||||
|
@@ -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 {};
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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;
|
||||
|
@@ -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;
|
||||
|
||||
|
@@ -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 {
|
||||
|
@@ -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;
|
||||
|
Reference in New Issue
Block a user