fix: capture the mouse on macOS while the cursor is on a client
On the server, while the cursor is on a client, every physical mouse move warped the local cursor back to screen center and the tap callback returned the mouse-move event instead of consuming it. That leaked continuous motion to whatever local app sat under the centered cursor (e.g. a fullscreen game or a centered video reacting to the phantom movement). Capture the mouse the way macOS games do instead: dissociate the hardware mouse from the cursor in leave() so the cursor freezes and hides, read raw deltas from the event in onMouseMove(), consume off-screen moves in the tap callback, and re-associate in enter() (disable() already does via showCursor()). This retires the center-warp and the "bogus motion" edge heuristic that only existed to paper over the warp. fixes: #5871
This commit is contained in:
parent
89a6c7f2ed
commit
96f544de6d
2 changed files with 39 additions and 64 deletions
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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,14 +963,14 @@ 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);
|
||||
if (m_isOnScreen) {
|
||||
// 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;
|
||||
|
||||
|
|
@ -978,44 +983,20 @@ bool OSXScreen::onMouseMove()
|
|||
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
|
||||
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(
|
||||
|
|
|
|||
Loading…
Reference in a new issue