diff --git a/src/lib/platform/OSXScreen.h b/src/lib/platform/OSXScreen.h index 66de16c3d5..9efd66a016 100644 --- a/src/lib/platform/OSXScreen.h +++ b/src/lib/platform/OSXScreen.h @@ -112,7 +112,7 @@ private: void sendClipboardEvent(EventTypes type, ClipboardID id) const; // message handlers - bool onMouseMove(); + bool onMouseMove(CGEventRef event); // mouse button handler. pressed is true if this is a mousedown // event, false if it is a mouseup event. macButton is the index // of the button pressed using the mac button mapping. diff --git a/src/lib/platform/OSXScreen.mm b/src/lib/platform/OSXScreen.mm index a138200014..8cbc795319 100644 --- a/src/lib/platform/OSXScreen.mm +++ b/src/lib/platform/OSXScreen.mm @@ -769,6 +769,8 @@ void OSXScreen::enter() showCursor(); if (m_isPrimary) { + // re-couple the mouse to the cursor, undoing the capture from leave() + CGAssociateMouseAndMouseCursorPosition(true); setZeroSuppressionInterval(); } else { // reset buttons @@ -798,8 +800,11 @@ void OSXScreen::leave() if (m_isPrimary) { avoidHesitatingCursor(); - LOG_VERBOSE("centering cursor on leave: %+d, %+d", m_xCenter, m_yCenter); - warpCursor(m_xCenter, m_yCenter); + + // capture the mouse: freeze the cursor so no local app sees motion while on + // a client (onMouseMove reads raw deltas instead). must follow hideCursor(), + // which re-associates. re-coupled in enter()/disable(). + CGAssociateMouseAndMouseCursorPosition(false); } // now off screen @@ -958,64 +963,40 @@ void OSXScreen::handleSystemEvent(const Event &event) } } -bool OSXScreen::onMouseMove() +bool OSXScreen::onMouseMove(CGEventRef event) { - // when we receive a mouse-move event, it is possible it was queued for a period - // and that the mouse has already moved again since then. to handle this, we need - // to query the current mouse position rather than using the position in the event. - CGEventRef event = CGEventCreate(NULL); - CGPoint pos = CGEventGetLocation(event); - CFRelease(event); - CGFloat mx = pos.x; - CGFloat my = pos.y; - - LOG_VERBOSE("mouse move %+f,%+f", mx, my); - - CGFloat x = mx - m_xCursor; - CGFloat y = my - m_yCursor; - - if ((x == 0 && y == 0) || (mx == m_xCenter && mx == m_yCenter)) { - return true; - } - - // save position to compute delta of next motion - m_xCursor = (int32_t)mx; - m_yCursor = (int32_t)my; - if (m_isOnScreen) { - // motion on primary screen + // motion on primary screen. the event may have been queued a while, so + // query the live cursor position rather than the stale event position. + CGEventRef posEvent = CGEventCreate(NULL); + CGPoint pos = CGEventGetLocation(posEvent); + CFRelease(posEvent); + CGFloat mx = pos.x; + CGFloat my = pos.y; + + LOG_VERBOSE("mouse move %+f,%+f", mx, my); + + CGFloat x = mx - m_xCursor; + CGFloat y = my - m_yCursor; + + if ((x == 0 && y == 0) || (mx == m_xCenter && mx == m_yCenter)) { + return true; + } + + m_xCursor = (int32_t)mx; + m_yCursor = (int32_t)my; + sendEvent(EventTypes::PrimaryScreenMotionOnPrimary, MotionInfo::alloc(m_xCursor, m_yCursor)); } else { - // motion on secondary screen. warp mouse back to - // center. - warpCursor(m_xCenter, m_yCenter); + // motion on secondary screen. the cursor is frozen (see leave()), so read + // raw deltas from the event instead of diffing position. + int32_t dx = (int32_t)CGEventGetIntegerValueField(event, kCGMouseEventDeltaX); + int32_t dy = (int32_t)CGEventGetIntegerValueField(event, kCGMouseEventDeltaY); - // examine the motion. if it's about the distance - // from the center of the screen to an edge then - // it's probably a bogus motion that we want to - // ignore (see warpCursorNoFlush() for a further - // description). - static int32_t bogusZoneSize = 10; - if (-x + bogusZoneSize > m_xCenter - m_x || x + bogusZoneSize > m_x + m_w - m_xCenter || - -y + bogusZoneSize > m_yCenter - m_y || y + bogusZoneSize > m_y + m_h - m_yCenter) { - LOG_DEBUG("dropped bogus motion %+d,%+d", x, y); - } else { - // send motion - // Accumulate together the move into the running total - static CGFloat m_xFractionalMove = 0; - static CGFloat m_yFractionalMove = 0; + LOG_VERBOSE("mouse delta %+d,%+d", dx, dy); - m_xFractionalMove += x; - m_yFractionalMove += y; - - // Return the integer part - int32_t intX = (int32_t)m_xFractionalMove; - int32_t intY = (int32_t)m_yFractionalMove; - - // And keep only the fractional part - m_xFractionalMove -= intX; - m_yFractionalMove -= intY; - sendEvent(EventTypes::PrimaryScreenMotionOnSecondary, MotionInfo::alloc(intX, intY)); + if (dx != 0 || dy != 0) { + sendEvent(EventTypes::PrimaryScreenMotionOnSecondary, MotionInfo::alloc(dx, dy)); } } @@ -1690,15 +1671,9 @@ CGEventRef OSXScreen::handleCGInputEvent(CGEventTapProxy proxy, CGEventType type case kCGEventRightMouseDragged: case kCGEventOtherMouseDragged: case kCGEventMouseMoved: - // we intentionally ignore the position in the event here as the events are - // queued and will no longer be accurate when we process them. - screen->onMouseMove(); - - // The system ignores our cursor-centering calls if - // we don't return the event. This should be harmless, - // but might register as slight movement to other apps - // on the system. It hasn't been a problem before, though. - return event; + // off-screen the cursor is frozen (see leave()), so fall through to consume + // the move below instead of returning (leaking) it to local apps. + screen->onMouseMove(event); break; case kCGEventScrollWheel: screen->onMouseWheel(