diff --git a/src/lib/platform/MSWindowsDesks.cpp b/src/lib/platform/MSWindowsDesks.cpp index 73d2c4695..f62948767 100644 --- a/src/lib/platform/MSWindowsDesks.cpp +++ b/src/lib/platform/MSWindowsDesks.cpp @@ -332,6 +332,15 @@ void MSWindowsDesks::fakeTouchClick(SInt32 x, SInt32 y) const sendMessage(DESKFLOW_MSG_FAKE_TOUCH, static_cast(x), static_cast(y)); } +void MSWindowsDesks::setPendingTouchClick(SInt32 x, SInt32 y) +{ + m_pendingTouchX = x; + m_pendingTouchY = y; + m_pendingTouchUp = true; + m_touchLifted = true; + LOG((CLOG_DEBUG "touch: LL hook path, pending click at %d,%d for deskEnter replay", x, y)); +} + void MSWindowsDesks::fakeMouseMove(SInt32 x, SInt32 y) const { sendMessage(DESKFLOW_MSG_FAKE_MOVE, static_cast(x), static_cast(y)); @@ -883,12 +892,21 @@ MSWindowsDesks::HidTouchDevice MSWindowsDesks::initHidTouchDevice(HANDLE hDevice USAGE usage = vc.IsRange ? vc.Range.UsageMin : vc.NotRange.Usage; auto &ci = collections[vc.LinkCollection]; + + // Windows sign-extends LogicalMax into a LONG using BitSize bits. + // A 16-bit descriptor with max 0xFFFF becomes -1 as a signed LONG. + // Reconstruct the unsigned value from the bit width. + LONG logMax = vc.LogicalMax; + if (logMax < 0 && vc.BitSize > 0 && vc.BitSize < 32) { + logMax = static_cast((1UL << vc.BitSize) - 1); + } + if (usage == HID_USAGE_GENERIC_X) { ci.hasX = true; - ci.maxX = vc.LogicalMax > 0 ? vc.LogicalMax : vc.PhysicalMax; + ci.maxX = logMax > 0 ? logMax : vc.PhysicalMax; } else if (usage == HID_USAGE_GENERIC_Y) { ci.hasY = true; - ci.maxY = vc.LogicalMax > 0 ? vc.LogicalMax : vc.PhysicalMax; + ci.maxY = logMax > 0 ? logMax : vc.PhysicalMax; } } @@ -954,6 +972,9 @@ bool MSWindowsDesks::parseHidTouch(const RAWINPUT *raw, const HidTouchDevice &de // Touch digitizer maps to the primary monitor, not the virtual desktop SInt32 pw = GetSystemMetrics(SM_CXSCREEN); SInt32 ph = GetSystemMetrics(SM_CYSCREEN); + if (dev.logicalMaxX == 0 || dev.logicalMaxY == 0) { + return false; + } outX = static_cast(rawX * pw / dev.logicalMaxX); outY = static_cast(rawY * ph / dev.logicalMaxY); LOG((CLOG_DEBUG1 "touch HID: raw=%lu,%lu logMax=%lu,%lu primary=%dx%d -> %d,%d", diff --git a/src/lib/platform/MSWindowsDesks.h b/src/lib/platform/MSWindowsDesks.h index b042c0f21..fc55cbe45 100644 --- a/src/lib/platform/MSWindowsDesks.h +++ b/src/lib/platform/MSWindowsDesks.h @@ -172,6 +172,8 @@ class MSWindowsDesks */ void fakeMouseButton(ButtonID id, bool press); + void setPendingTouchClick(SInt32 x, SInt32 y); + //! Fake mouse move /*! Synthesize a mouse move to the absolute coordinates \c x,y. diff --git a/src/lib/platform/MSWindowsHook.cpp b/src/lib/platform/MSWindowsHook.cpp index da2a23ea3..4e03063f1 100644 --- a/src/lib/platform/MSWindowsHook.cpp +++ b/src/lib/platform/MSWindowsHook.cpp @@ -46,6 +46,8 @@ static bool g_fakeServerInput = false; static BOOL g_isPrimary = TRUE; static BOOL g_isOnScreen = TRUE; static bool g_touchActivateScreen = false; +static SInt32 g_xCenter = 0; +static SInt32 g_yCenter = 0; MSWindowsHook::MSWindowsHook() { @@ -166,6 +168,12 @@ void MSWindowsHook::setIsOnScreen(bool onScreen) g_isOnScreen = onScreen ? TRUE : FALSE; } +void MSWindowsHook::setCursorCenter(SInt32 x, SInt32 y) +{ + g_xCenter = x; + g_yCenter = y; +} + static void keyboardGetState(BYTE keys[256], DWORD vkCode, bool kf_up) { // we have to use GetAsyncKeyState() rather than GetKeyState() because @@ -615,12 +623,20 @@ static LRESULT CALLBACK mouseLLHook(int code, WPARAM wParam, LPARAM lParam) if (isTouchEvent && (wParam == WM_LBUTTONDOWN || wParam == WM_MOUSEMOVE)) { SInt32 x = static_cast(info->pt.x); SInt32 y = static_cast(info->pt.y); - LOG((CLOG_DEBUG "hook: touch at %d,%d posting DESKFLOW_MSG_TOUCH", x, y)); - PostThreadMessage(g_threadID, DESKFLOW_MSG_TOUCH, x, y); - if (g_isPrimary) { - LOG((CLOG_DEBUG "hook: eating touch event (relay mode)")); + + // Filter out warp-echo: the server warps the cursor to center after + // a switch, which generates a synthetic mouse event at that position. + // Without this filter, the echo triggers an immediate switch-back. + SInt32 dxc = x - g_xCenter; + SInt32 dyc = y - g_yCenter; + if (dxc >= -1 && dxc <= 1 && dyc >= -1 && dyc <= 1) { + LOG((CLOG_DEBUG2 "hook: ignoring touch at cursor center %d,%d", x, y)); return 1; } + + LOG((CLOG_DEBUG "hook: touch at %d,%d posting DESKFLOW_MSG_TOUCH", x, y)); + PostThreadMessage(g_threadID, DESKFLOW_MSG_TOUCH, x, y); + // let the touch event pass through to the app so the click registers } } diff --git a/src/lib/platform/MSWindowsHook.h b/src/lib/platform/MSWindowsHook.h index ff1bc5c51..7e19bb6bd 100644 --- a/src/lib/platform/MSWindowsHook.h +++ b/src/lib/platform/MSWindowsHook.h @@ -55,4 +55,6 @@ class MSWindowsHook void setIsPrimary(bool primary); void setIsOnScreen(bool onScreen); + + void setCursorCenter(SInt32 x, SInt32 y); }; diff --git a/src/lib/platform/MSWindowsScreen.cpp b/src/lib/platform/MSWindowsScreen.cpp index 84fd41ea7..aaf15280c 100644 --- a/src/lib/platform/MSWindowsScreen.cpp +++ b/src/lib/platform/MSWindowsScreen.cpp @@ -996,6 +996,7 @@ bool MSWindowsScreen::onPreDispatch(HWND hwnd, UINT message, WPARAM wParam, LPAR MotionInfo::alloc(x, y)); } else { LOG((CLOG_INFO "hook: touch requesting grab input at %d,%d", x, y)); + m_desks->setPendingTouchClick(x, y); sendEvent(m_events->forIScreen().grabInput(), MotionInfo::alloc(x, y)); } @@ -1640,6 +1641,8 @@ void MSWindowsScreen::updateScreenShape() // tell the desks m_desks->setShape(m_x, m_y, m_w, m_h, m_xCenter, m_yCenter, m_multimon); + + m_hook.setCursorCenter(m_xCenter, m_yCenter); } void MSWindowsScreen::handleFixes(const Event &, void *) diff --git a/src/lib/server/Server.cpp b/src/lib/server/Server.cpp index a7d51189f..03faa7453 100644 --- a/src/lib/server/Server.cpp +++ b/src/lib/server/Server.cpp @@ -726,6 +726,17 @@ BaseClientProxy *Server::mapToNeighbor(BaseClientProxy *src, EDirection srcSide, // move inwards because that side can't provoke a jump. avoidJumpZone(dst, srcSide, x, y); + // Clamp entry coordinates 1px inside the destination screen. + // avoidJumpZone only adjusts the crossing axis; the parallel axis can + // land exactly on the edge, which the cursor-warp logic treats as still + // being on the source side, bouncing the cursor back immediately. + SInt32 cx, cy, cw, ch; + dst->getShape(cx, cy, cw, ch); + if (x < cx + 1) x = cx + 1; + if (x > cx + cw - 2) x = cx + cw - 2; + if (y < cy + 1) y = cy + 1; + if (y > cy + ch - 2) y = cy + ch - 2; + return dst; }