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;
|
void sendClipboardEvent(EventTypes type, ClipboardID id) const;
|
||||||
|
|
||||||
// message handlers
|
// message handlers
|
||||||
bool onMouseMove();
|
bool onMouseMove(CGEventRef event);
|
||||||
// mouse button handler. pressed is true if this is a mousedown
|
// mouse button handler. pressed is true if this is a mousedown
|
||||||
// event, false if it is a mouseup event. macButton is the index
|
// event, false if it is a mouseup event. macButton is the index
|
||||||
// of the button pressed using the mac button mapping.
|
// of the button pressed using the mac button mapping.
|
||||||
|
|
|
||||||
|
|
@ -769,6 +769,8 @@ void OSXScreen::enter()
|
||||||
showCursor();
|
showCursor();
|
||||||
|
|
||||||
if (m_isPrimary) {
|
if (m_isPrimary) {
|
||||||
|
// re-couple the mouse to the cursor, undoing the capture from leave()
|
||||||
|
CGAssociateMouseAndMouseCursorPosition(true);
|
||||||
setZeroSuppressionInterval();
|
setZeroSuppressionInterval();
|
||||||
} else {
|
} else {
|
||||||
// reset buttons
|
// reset buttons
|
||||||
|
|
@ -798,8 +800,11 @@ void OSXScreen::leave()
|
||||||
|
|
||||||
if (m_isPrimary) {
|
if (m_isPrimary) {
|
||||||
avoidHesitatingCursor();
|
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
|
// 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
|
if (m_isOnScreen) {
|
||||||
// and that the mouse has already moved again since then. to handle this, we need
|
// motion on primary screen. the event may have been queued a while, so
|
||||||
// to query the current mouse position rather than using the position in the event.
|
// query the live cursor position rather than the stale event position.
|
||||||
CGEventRef event = CGEventCreate(NULL);
|
CGEventRef posEvent = CGEventCreate(NULL);
|
||||||
CGPoint pos = CGEventGetLocation(event);
|
CGPoint pos = CGEventGetLocation(posEvent);
|
||||||
CFRelease(event);
|
CFRelease(posEvent);
|
||||||
CGFloat mx = pos.x;
|
CGFloat mx = pos.x;
|
||||||
CGFloat my = pos.y;
|
CGFloat my = pos.y;
|
||||||
|
|
||||||
|
|
@ -978,44 +983,20 @@ bool OSXScreen::onMouseMove()
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// save position to compute delta of next motion
|
|
||||||
m_xCursor = (int32_t)mx;
|
m_xCursor = (int32_t)mx;
|
||||||
m_yCursor = (int32_t)my;
|
m_yCursor = (int32_t)my;
|
||||||
|
|
||||||
if (m_isOnScreen) {
|
|
||||||
// motion on primary screen
|
|
||||||
sendEvent(EventTypes::PrimaryScreenMotionOnPrimary, MotionInfo::alloc(m_xCursor, m_yCursor));
|
sendEvent(EventTypes::PrimaryScreenMotionOnPrimary, MotionInfo::alloc(m_xCursor, m_yCursor));
|
||||||
} else {
|
} else {
|
||||||
// motion on secondary screen. warp mouse back to
|
// motion on secondary screen. the cursor is frozen (see leave()), so read
|
||||||
// center.
|
// raw deltas from the event instead of diffing position.
|
||||||
warpCursor(m_xCenter, m_yCenter);
|
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
|
LOG_VERBOSE("mouse delta %+d,%+d", dx, dy);
|
||||||
// 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;
|
|
||||||
|
|
||||||
m_xFractionalMove += x;
|
if (dx != 0 || dy != 0) {
|
||||||
m_yFractionalMove += y;
|
sendEvent(EventTypes::PrimaryScreenMotionOnSecondary, MotionInfo::alloc(dx, dy));
|
||||||
|
|
||||||
// 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));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1690,15 +1671,9 @@ CGEventRef OSXScreen::handleCGInputEvent(CGEventTapProxy proxy, CGEventType type
|
||||||
case kCGEventRightMouseDragged:
|
case kCGEventRightMouseDragged:
|
||||||
case kCGEventOtherMouseDragged:
|
case kCGEventOtherMouseDragged:
|
||||||
case kCGEventMouseMoved:
|
case kCGEventMouseMoved:
|
||||||
// we intentionally ignore the position in the event here as the events are
|
// off-screen the cursor is frozen (see leave()), so fall through to consume
|
||||||
// queued and will no longer be accurate when we process them.
|
// the move below instead of returning (leaking) it to local apps.
|
||||||
screen->onMouseMove();
|
screen->onMouseMove(event);
|
||||||
|
|
||||||
// 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;
|
|
||||||
break;
|
break;
|
||||||
case kCGEventScrollWheel:
|
case kCGEventScrollWheel:
|
||||||
screen->onMouseWheel(
|
screen->onMouseWheel(
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue