[PATCH 1/5] winemac.drv: Factor out cursor clipping code to its own class.
Tim Clem
tclem at codeweavers.com
Wed Jan 19 13:40:24 CST 2022
Signed-off-by: Tim Clem <tclem at codeweavers.com>
---
dlls/winemac.drv/Makefile.in | 1 +
dlls/winemac.drv/cocoa_app.h | 11 +-
dlls/winemac.drv/cocoa_app.m | 326 ++--------------------
dlls/winemac.drv/cocoa_cursorclipping.h | 46 +++
dlls/winemac.drv/cocoa_cursorclipping.m | 353 ++++++++++++++++++++++++
5 files changed, 430 insertions(+), 307 deletions(-)
create mode 100644 dlls/winemac.drv/cocoa_cursorclipping.h
create mode 100644 dlls/winemac.drv/cocoa_cursorclipping.m
diff --git a/dlls/winemac.drv/Makefile.in b/dlls/winemac.drv/Makefile.in
index fc3dddbdae71..c43fcd46ed3d 100644
--- a/dlls/winemac.drv/Makefile.in
+++ b/dlls/winemac.drv/Makefile.in
@@ -25,6 +25,7 @@ C_SRCS = \
OBJC_SRCS = \
cocoa_app.m \
cocoa_clipboard.m \
+ cocoa_cursorclipping.m \
cocoa_display.m \
cocoa_event.m \
cocoa_main.m \
diff --git a/dlls/winemac.drv/cocoa_app.h b/dlls/winemac.drv/cocoa_app.h
index 0b70a2fd55b5..4ddf2fb3babb 100644
--- a/dlls/winemac.drv/cocoa_app.h
+++ b/dlls/winemac.drv/cocoa_app.h
@@ -67,6 +67,7 @@
@class WineEventQueue;
+ at class WineEventTapClipCursorHandler;
@class WineWindow;
@@ -118,13 +119,9 @@ @interface WineApplicationController : NSObject <NSApplicationDelegate>
BOOL cursorHidden;
BOOL clientWantsCursorHidden;
- BOOL clippingCursor;
- CGRect cursorClipRect;
- CFMachPortRef cursorClippingEventTap;
- NSMutableArray* warpRecords;
- CGPoint synthesizedLocation;
NSTimeInterval lastSetCursorPositionTime;
- NSTimeInterval lastEventTapEventTime;
+
+ WineEventTapClipCursorHandler* clipCursorHandler;
NSImage* applicationIcon;
@@ -139,6 +136,7 @@ @interface WineApplicationController : NSObject <NSApplicationDelegate>
@property (readonly, nonatomic) BOOL areDisplaysCaptured;
@property (readonly) BOOL clippingCursor;
+ at property (nonatomic) NSTimeInterval lastSetCursorPositionTime;
+ (WineApplicationController*) sharedController;
@@ -160,6 +158,7 @@ - (void) window:(WineWindow*)window isBeingDragged:(BOOL)dragged;
- (void) windowWillOrderOut:(WineWindow*)window;
- (void) flipRect:(NSRect*)rect;
+ - (NSPoint) flippedMouseLocation:(NSPoint)point;
- (WineWindow*) frontWineWindow;
- (void) adjustWindowLevels;
diff --git a/dlls/winemac.drv/cocoa_app.m b/dlls/winemac.drv/cocoa_app.m
index 1bb752d20b78..b2d54bce9087 100644
--- a/dlls/winemac.drv/cocoa_app.m
+++ b/dlls/winemac.drv/cocoa_app.m
@@ -21,6 +21,7 @@
#import <Carbon/Carbon.h>
#import "cocoa_app.h"
+#import "cocoa_cursorclipping.h"
#import "cocoa_event.h"
#import "cocoa_window.h"
@@ -80,27 +81,6 @@ - (void) setWineController:(WineApplicationController*)newController
@end
- at interface WarpRecord : NSObject
-{
- CGEventTimestamp timeBefore, timeAfter;
- CGPoint from, to;
-}
-
- at property (nonatomic) CGEventTimestamp timeBefore;
- at property (nonatomic) CGEventTimestamp timeAfter;
- at property (nonatomic) CGPoint from;
- at property (nonatomic) CGPoint to;
-
- at end
-
-
- at implementation WarpRecord
-
- at synthesize timeBefore, timeAfter, from, to;
-
- at end;
-
-
@interface WineApplicationController ()
@property (readwrite, copy, nonatomic) NSEvent* lastFlagsChanged;
@@ -125,8 +105,7 @@ @implementation WineApplicationController
@synthesize applicationIcon;
@synthesize cursorFrames, cursorTimer, cursor;
@synthesize mouseCaptureWindow;
-
- @synthesize clippingCursor;
+ @synthesize lastSetCursorPositionTime;
+ (void) initialize
{
@@ -183,8 +162,6 @@ - (id) init
originalDisplayModes = [[NSMutableDictionary alloc] init];
latentDisplayModes = [[NSMutableDictionary alloc] init];
- warpRecords = [[NSMutableArray alloc] init];
-
windowsBeingDragged = [[NSMutableSet alloc] init];
// On macOS 10.12+, use notifications to more reliably detect when windows are being dragged.
@@ -197,7 +174,7 @@ - (id) init
useDragNotifications = NO;
if (!requests || !requestsManipQueue || !eventQueues || !eventQueuesLock ||
- !keyWindows || !originalDisplayModes || !latentDisplayModes || !warpRecords)
+ !keyWindows || !originalDisplayModes || !latentDisplayModes)
{
[self release];
return nil;
@@ -219,7 +196,7 @@ - (void) dealloc
[cursor release];
[screenFrameCGRects release];
[applicationIcon release];
- [warpRecords release];
+ [clipCursorHandler release];
[cursorTimer release];
[cursorFrames release];
[latentDisplayModes release];
@@ -1162,251 +1139,14 @@ - (void) handleCommandTab
}
}
- /*
- * ---------- Cursor clipping methods ----------
- *
- * Neither Quartz nor Cocoa has an exact analog for Win32 cursor clipping.
- * For one simple case, clipping to a 1x1 rectangle, Quartz does have an
- * equivalent: CGAssociateMouseAndMouseCursorPosition(false). For the
- * general case, we leverage that. We disassociate mouse movements from
- * the cursor position and then move the cursor manually, keeping it within
- * the clipping rectangle.
- *
- * Moving the cursor manually isn't enough. We need to modify the event
- * stream so that the events have the new location, too. We need to do
- * this at a point before the events enter Cocoa, so that Cocoa will assign
- * the correct window to the event. So, we install a Quartz event tap to
- * do that.
- *
- * Also, there's a complication when we move the cursor. We use
- * CGWarpMouseCursorPosition(). That doesn't generate mouse movement
- * events, but the change of cursor position is incorporated into the
- * deltas of the next mouse move event. When the mouse is disassociated
- * from the cursor position, we need the deltas to only reflect actual
- * device movement, not programmatic changes. So, the event tap cancels
- * out the change caused by our calls to CGWarpMouseCursorPosition().
- */
- - (void) clipCursorLocation:(CGPoint*)location
- {
- if (location->x < CGRectGetMinX(cursorClipRect))
- location->x = CGRectGetMinX(cursorClipRect);
- if (location->y < CGRectGetMinY(cursorClipRect))
- location->y = CGRectGetMinY(cursorClipRect);
- if (location->x > CGRectGetMaxX(cursorClipRect) - 1)
- location->x = CGRectGetMaxX(cursorClipRect) - 1;
- if (location->y > CGRectGetMaxY(cursorClipRect) - 1)
- location->y = CGRectGetMaxY(cursorClipRect) - 1;
- }
-
- - (BOOL) warpCursorTo:(CGPoint*)newLocation from:(const CGPoint*)currentLocation
- {
- CGPoint oldLocation;
-
- if (currentLocation)
- oldLocation = *currentLocation;
- else
- oldLocation = NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]]);
-
- if (!CGPointEqualToPoint(oldLocation, *newLocation))
- {
- WarpRecord* warpRecord = [[[WarpRecord alloc] init] autorelease];
- CGError err;
-
- warpRecord.from = oldLocation;
- warpRecord.timeBefore = [[NSProcessInfo processInfo] systemUptime] * NSEC_PER_SEC;
-
- /* Actually move the cursor. */
- err = CGWarpMouseCursorPosition(*newLocation);
- if (err != kCGErrorSuccess)
- return FALSE;
-
- warpRecord.timeAfter = [[NSProcessInfo processInfo] systemUptime] * NSEC_PER_SEC;
- *newLocation = NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]]);
-
- if (!CGPointEqualToPoint(oldLocation, *newLocation))
- {
- warpRecord.to = *newLocation;
- [warpRecords addObject:warpRecord];
- }
- }
-
- return TRUE;
- }
-
- - (BOOL) isMouseMoveEventType:(CGEventType)type
- {
- switch(type)
- {
- case kCGEventMouseMoved:
- case kCGEventLeftMouseDragged:
- case kCGEventRightMouseDragged:
- case kCGEventOtherMouseDragged:
- return TRUE;
- default:
- return FALSE;
- }
- }
-
- - (int) warpsFinishedByEventTime:(CGEventTimestamp)eventTime location:(CGPoint)eventLocation
- {
- int warpsFinished = 0;
- for (WarpRecord* warpRecord in warpRecords)
- {
- if (warpRecord.timeAfter < eventTime ||
- (warpRecord.timeBefore <= eventTime && CGPointEqualToPoint(eventLocation, warpRecord.to)))
- warpsFinished++;
- else
- break;
- }
-
- return warpsFinished;
- }
-
- - (CGEventRef) eventTapWithProxy:(CGEventTapProxy)proxy
- type:(CGEventType)type
- event:(CGEventRef)event
- {
- CGEventTimestamp eventTime;
- CGPoint eventLocation, cursorLocation;
-
- if (type == kCGEventTapDisabledByUserInput)
- return event;
- if (type == kCGEventTapDisabledByTimeout)
- {
- CGEventTapEnable(cursorClippingEventTap, TRUE);
- return event;
- }
-
- if (!clippingCursor)
- return event;
-
- eventTime = CGEventGetTimestamp(event);
- lastEventTapEventTime = eventTime / (double)NSEC_PER_SEC;
-
- eventLocation = CGEventGetLocation(event);
-
- cursorLocation = NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]]);
-
- if ([self isMouseMoveEventType:type])
- {
- double deltaX, deltaY;
- int warpsFinished = [self warpsFinishedByEventTime:eventTime location:eventLocation];
- int i;
-
- deltaX = CGEventGetDoubleValueField(event, kCGMouseEventDeltaX);
- deltaY = CGEventGetDoubleValueField(event, kCGMouseEventDeltaY);
-
- for (i = 0; i < warpsFinished; i++)
- {
- WarpRecord* warpRecord = [warpRecords objectAtIndex:0];
- deltaX -= warpRecord.to.x - warpRecord.from.x;
- deltaY -= warpRecord.to.y - warpRecord.from.y;
- [warpRecords removeObjectAtIndex:0];
- }
-
- if (warpsFinished)
- {
- CGEventSetDoubleValueField(event, kCGMouseEventDeltaX, deltaX);
- CGEventSetDoubleValueField(event, kCGMouseEventDeltaY, deltaY);
- }
-
- synthesizedLocation.x += deltaX;
- synthesizedLocation.y += deltaY;
- }
-
- // If the event is destined for another process, don't clip it. This may
- // happen if the user activates Exposé or Mission Control. In that case,
- // our app does not resign active status, so clipping is still in effect,
- // but the cursor should not actually be clipped.
- //
- // In addition, the fact that mouse moves may have been delivered to a
- // different process means we have to treat the next one we receive as
- // absolute rather than relative.
- if (CGEventGetIntegerValueField(event, kCGEventTargetUnixProcessID) == getpid())
- [self clipCursorLocation:&synthesizedLocation];
- else
- lastSetCursorPositionTime = lastEventTapEventTime;
-
- [self warpCursorTo:&synthesizedLocation from:&cursorLocation];
- if (!CGPointEqualToPoint(eventLocation, synthesizedLocation))
- CGEventSetLocation(event, synthesizedLocation);
-
- return event;
- }
-
- CGEventRef WineAppEventTapCallBack(CGEventTapProxy proxy, CGEventType type,
- CGEventRef event, void *refcon)
- {
- WineApplicationController* controller = refcon;
- return [controller eventTapWithProxy:proxy type:type event:event];
- }
-
- - (BOOL) installEventTap
- {
- CGEventMask mask = CGEventMaskBit(kCGEventLeftMouseDown) |
- CGEventMaskBit(kCGEventLeftMouseUp) |
- CGEventMaskBit(kCGEventRightMouseDown) |
- CGEventMaskBit(kCGEventRightMouseUp) |
- CGEventMaskBit(kCGEventMouseMoved) |
- CGEventMaskBit(kCGEventLeftMouseDragged) |
- CGEventMaskBit(kCGEventRightMouseDragged) |
- CGEventMaskBit(kCGEventOtherMouseDown) |
- CGEventMaskBit(kCGEventOtherMouseUp) |
- CGEventMaskBit(kCGEventOtherMouseDragged) |
- CGEventMaskBit(kCGEventScrollWheel);
- CFRunLoopSourceRef source;
-
- if (cursorClippingEventTap)
- return TRUE;
-
- // We create an annotated session event tap rather than a process-specific
- // event tap because we need to programmatically move the cursor even when
- // mouse moves are directed to other processes. We disable our tap when
- // other processes are active, but things like Exposé are handled by other
- // processes even when we remain active.
- cursorClippingEventTap = CGEventTapCreate(kCGAnnotatedSessionEventTap, kCGHeadInsertEventTap,
- kCGEventTapOptionDefault, mask, WineAppEventTapCallBack, self);
- if (!cursorClippingEventTap)
- return FALSE;
-
- CGEventTapEnable(cursorClippingEventTap, FALSE);
-
- source = CFMachPortCreateRunLoopSource(NULL, cursorClippingEventTap, 0);
- if (!source)
- {
- CFRelease(cursorClippingEventTap);
- cursorClippingEventTap = NULL;
- return FALSE;
- }
-
- CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes);
- CFRelease(source);
- return TRUE;
- }
-
- (BOOL) setCursorPosition:(CGPoint)pos
{
BOOL ret;
if ([windowsBeingDragged count])
ret = FALSE;
- else if (clippingCursor)
- {
- [self clipCursorLocation:&pos];
-
- ret = [self warpCursorTo:&pos from:NULL];
- synthesizedLocation = pos;
- if (ret)
- {
- // We want to discard mouse-move events that have already been
- // through the event tap, because it's too late to account for
- // the setting of the cursor position with them. However, the
- // events that may be queued with times after that but before
- // the above warp can still be used. So, use the last event
- // tap event time so that -sendEvent: doesn't discard them.
- lastSetCursorPositionTime = lastEventTapEventTime;
- }
- }
+ else if (self.clippingCursor)
+ ret = [clipCursorHandler setCursorPosition:pos];
else
{
// Annoyingly, CGWarpMouseCursorPosition() effectively disassociates
@@ -1465,22 +1205,15 @@ - (void) updateWindowsForCursorClipping
- (BOOL) startClippingCursor:(CGRect)rect
{
- CGError err;
+ if (!clipCursorHandler)
+ clipCursorHandler = [[WineEventTapClipCursorHandler alloc] init];
- if (!cursorClippingEventTap && ![self installEventTap])
- return FALSE;
-
- if (clippingCursor && CGRectEqualToRect(rect, cursorClipRect))
+ if (self.clippingCursor && CGRectEqualToRect(rect, clipCursorHandler.cursorClipRect))
return TRUE;
- err = CGAssociateMouseAndMouseCursorPosition(false);
- if (err != kCGErrorSuccess)
+ if (![clipCursorHandler startClippingCursor:rect])
return FALSE;
- clippingCursor = TRUE;
- cursorClipRect = rect;
-
- CGEventTapEnable(cursorClippingEventTap, TRUE);
[self setCursorPosition:NSPointToCGPoint([self flippedMouseLocation:[NSEvent mouseLocation]])];
[self updateWindowsForCursorClipping];
@@ -1490,19 +1223,12 @@ - (BOOL) startClippingCursor:(CGRect)rect
- (BOOL) stopClippingCursor
{
- CGError err;
-
- if (!clippingCursor)
+ if (!self.clippingCursor)
return TRUE;
- err = CGAssociateMouseAndMouseCursorPosition(true);
- if (err != kCGErrorSuccess)
+ if (![clipCursorHandler stopClippingCursor])
return FALSE;
- clippingCursor = FALSE;
-
- CGEventTapEnable(cursorClippingEventTap, FALSE);
- [warpRecords removeAllObjects];
lastSetCursorPositionTime = [[NSProcessInfo processInfo] systemUptime];
[self updateWindowsForCursorClipping];
@@ -1510,6 +1236,11 @@ - (BOOL) stopClippingCursor
return TRUE;
}
+ - (BOOL) clippingCursor
+ {
+ return clipCursorHandler.clippingCursor;
+ }
+
- (BOOL) isKeyPressed:(uint16_t)keyCode
{
int bits = sizeof(pressedKeyCodes[0]) * 8;
@@ -1659,7 +1390,7 @@ - (void) handleMouseMove:(NSEvent*)anEvent
// Assume cursor is pinned for now
absolute = FALSE;
- if (!clippingCursor || CGRectContainsPoint(cursorClipRect, computedPoint))
+ if (!self.clippingCursor || CGRectContainsPoint(clipCursorHandler.cursorClipRect, computedPoint))
{
const CGRect* rects;
NSUInteger count, i;
@@ -1683,8 +1414,8 @@ - (void) handleMouseMove:(NSEvent*)anEvent
if (absolute)
{
- if (clippingCursor)
- [self clipCursorLocation:&point];
+ if (self.clippingCursor)
+ [clipCursorHandler clipCursorLocation:&point];
point = cgpoint_win_from_mac(point);
event = macdrv_create_event(MOUSE_MOVED_ABSOLUTE, targetWindow);
@@ -1776,8 +1507,8 @@ - (void) handleMouseButton:(NSEvent*)theEvent
type == NSEventTypeOtherMouseDown);
CGPoint pt = CGEventGetLocation([theEvent CGEvent]);
- if (clippingCursor)
- [self clipCursorLocation:&pt];
+ if (self.clippingCursor)
+ [clipCursorHandler clipCursorLocation:&pt];
if (pressed)
{
@@ -1894,8 +1625,8 @@ - (void) handleScrollWheel:(NSEvent*)theEvent
CGPoint pt = CGEventGetLocation(cgevent);
BOOL process;
- if (clippingCursor)
- [self clipCursorLocation:&pt];
+ if (self.clippingCursor)
+ [clipCursorHandler clipCursorLocation:&pt];
if (mouseCaptureWindow)
process = TRUE;
@@ -2262,14 +1993,7 @@ - (void) setRetinaMode:(int)mode
{
retina_on = mode;
- if (clippingCursor)
- {
- double scale = mode ? 0.5 : 2.0;
- cursorClipRect.origin.x *= scale;
- cursorClipRect.origin.y *= scale;
- cursorClipRect.size.width *= scale;
- cursorClipRect.size.height *= scale;
- }
+ [clipCursorHandler setRetinaMode:mode];
for (WineWindow* window in [NSApp windows])
{
diff --git a/dlls/winemac.drv/cocoa_cursorclipping.h b/dlls/winemac.drv/cocoa_cursorclipping.h
new file mode 100644
index 000000000000..132527e10396
--- /dev/null
+++ b/dlls/winemac.drv/cocoa_cursorclipping.h
@@ -0,0 +1,46 @@
+/*
+ * MACDRV CGEventTap-based cursor clipping class declaration
+ *
+ * Copyright 2011, 2012, 2013 Ken Thomases for CodeWeavers Inc.
+ * Copyright 2021 Tim Clem for CodeWeavers Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+#import <AppKit/AppKit.h>
+
+ at interface WineEventTapClipCursorHandler : NSObject
+{
+ BOOL clippingCursor;
+ CGRect cursorClipRect;
+ CFMachPortRef cursorClippingEventTap;
+ NSMutableArray* warpRecords;
+ CGPoint synthesizedLocation;
+ NSTimeInterval lastEventTapEventTime;
+}
+
+ at property (readonly, nonatomic) BOOL clippingCursor;
+ at property (readonly, nonatomic) CGRect cursorClipRect;
+
+ - (BOOL) startClippingCursor:(CGRect)rect;
+ - (BOOL) stopClippingCursor;
+
+ - (void) clipCursorLocation:(CGPoint*)location;
+
+ - (void) setRetinaMode:(int)mode;
+
+ - (BOOL) setCursorPosition:(CGPoint)pos;
+
+ at end
diff --git a/dlls/winemac.drv/cocoa_cursorclipping.m b/dlls/winemac.drv/cocoa_cursorclipping.m
new file mode 100644
index 000000000000..7c0b53e47d93
--- /dev/null
+++ b/dlls/winemac.drv/cocoa_cursorclipping.m
@@ -0,0 +1,353 @@
+/*
+ * MACDRV CGEventTap-based cursor clipping class
+ *
+ * Copyright 2011, 2012, 2013 Ken Thomases for CodeWeavers Inc.
+ * Copyright 2021 Tim Clem for CodeWeavers Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+#import "cocoa_app.h"
+#import "cocoa_cursorclipping.h"
+
+
+/* Neither Quartz nor Cocoa has an exact analog for Win32 cursor clipping.
+ * For one simple case, clipping to a 1x1 rectangle, Quartz does have an
+ * equivalent: CGAssociateMouseAndMouseCursorPosition(false). For the
+ * general case, we leverage that. We disassociate mouse movements from
+ * the cursor position and then move the cursor manually, keeping it within
+ * the clipping rectangle.
+ *
+ * Moving the cursor manually isn't enough. We need to modify the event
+ * stream so that the events have the new location, too. We need to do
+ * this at a point before the events enter Cocoa, so that Cocoa will assign
+ * the correct window to the event. So, we install a Quartz event tap to
+ * do that.
+ *
+ * Also, there's a complication when we move the cursor. We use
+ * CGWarpMouseCursorPosition(). That doesn't generate mouse movement
+ * events, but the change of cursor position is incorporated into the
+ * deltas of the next mouse move event. When the mouse is disassociated
+ * from the cursor position, we need the deltas to only reflect actual
+ * device movement, not programmatic changes. So, the event tap cancels
+ * out the change caused by our calls to CGWarpMouseCursorPosition().
+ */
+
+
+ at interface WarpRecord : NSObject
+{
+ CGEventTimestamp timeBefore, timeAfter;
+ CGPoint from, to;
+}
+
+ at property (nonatomic) CGEventTimestamp timeBefore;
+ at property (nonatomic) CGEventTimestamp timeAfter;
+ at property (nonatomic) CGPoint from;
+ at property (nonatomic) CGPoint to;
+
+ at end
+
+
+ at implementation WarpRecord
+
+ at synthesize timeBefore, timeAfter, from, to;
+
+ at end;
+
+
+ at implementation WineEventTapClipCursorHandler
+
+ at synthesize clippingCursor, cursorClipRect;
+
+ - (id) init
+ {
+ self = [super init];
+ if (self)
+ {
+ warpRecords = [[NSMutableArray alloc] init];
+ }
+
+ return self;
+ }
+
+ - (void) dealloc
+ {
+ [warpRecords release];
+ [super dealloc];
+ }
+
+ - (void) clipCursorLocation:(CGPoint*)location
+ {
+ if (location->x < CGRectGetMinX(cursorClipRect))
+ location->x = CGRectGetMinX(cursorClipRect);
+ if (location->y < CGRectGetMinY(cursorClipRect))
+ location->y = CGRectGetMinY(cursorClipRect);
+ if (location->x > CGRectGetMaxX(cursorClipRect) - 1)
+ location->x = CGRectGetMaxX(cursorClipRect) - 1;
+ if (location->y > CGRectGetMaxY(cursorClipRect) - 1)
+ location->y = CGRectGetMaxY(cursorClipRect) - 1;
+ }
+
+ - (BOOL) warpCursorTo:(CGPoint*)newLocation from:(const CGPoint*)currentLocation
+ {
+ CGPoint oldLocation;
+
+ if (currentLocation)
+ oldLocation = *currentLocation;
+ else
+ oldLocation = NSPointToCGPoint([[WineApplicationController sharedController] flippedMouseLocation:[NSEvent mouseLocation]]);
+
+ if (!CGPointEqualToPoint(oldLocation, *newLocation))
+ {
+ WarpRecord* warpRecord = [[[WarpRecord alloc] init] autorelease];
+ CGError err;
+
+ warpRecord.from = oldLocation;
+ warpRecord.timeBefore = [[NSProcessInfo processInfo] systemUptime] * NSEC_PER_SEC;
+
+ /* Actually move the cursor. */
+ err = CGWarpMouseCursorPosition(*newLocation);
+ if (err != kCGErrorSuccess)
+ return FALSE;
+
+ warpRecord.timeAfter = [[NSProcessInfo processInfo] systemUptime] * NSEC_PER_SEC;
+ *newLocation = NSPointToCGPoint([[WineApplicationController sharedController] flippedMouseLocation:[NSEvent mouseLocation]]);
+
+ if (!CGPointEqualToPoint(oldLocation, *newLocation))
+ {
+ warpRecord.to = *newLocation;
+ [warpRecords addObject:warpRecord];
+ }
+ }
+
+ return TRUE;
+ }
+
+ - (BOOL) isMouseMoveEventType:(CGEventType)type
+ {
+ switch(type)
+ {
+ case kCGEventMouseMoved:
+ case kCGEventLeftMouseDragged:
+ case kCGEventRightMouseDragged:
+ case kCGEventOtherMouseDragged:
+ return TRUE;
+ default:
+ return FALSE;
+ }
+ }
+
+ - (int) warpsFinishedByEventTime:(CGEventTimestamp)eventTime location:(CGPoint)eventLocation
+ {
+ int warpsFinished = 0;
+ for (WarpRecord* warpRecord in warpRecords)
+ {
+ if (warpRecord.timeAfter < eventTime ||
+ (warpRecord.timeBefore <= eventTime && CGPointEqualToPoint(eventLocation, warpRecord.to)))
+ warpsFinished++;
+ else
+ break;
+ }
+
+ return warpsFinished;
+ }
+
+ - (CGEventRef) eventTapWithProxy:(CGEventTapProxy)proxy
+ type:(CGEventType)type
+ event:(CGEventRef)event
+ {
+ CGEventTimestamp eventTime;
+ CGPoint eventLocation, cursorLocation;
+
+ if (type == kCGEventTapDisabledByUserInput)
+ return event;
+ if (type == kCGEventTapDisabledByTimeout)
+ {
+ CGEventTapEnable(cursorClippingEventTap, TRUE);
+ return event;
+ }
+
+ if (!clippingCursor)
+ return event;
+
+ eventTime = CGEventGetTimestamp(event);
+ lastEventTapEventTime = eventTime / (double)NSEC_PER_SEC;
+
+ eventLocation = CGEventGetLocation(event);
+
+ cursorLocation = NSPointToCGPoint([[WineApplicationController sharedController] flippedMouseLocation:[NSEvent mouseLocation]]);
+
+ if ([self isMouseMoveEventType:type])
+ {
+ double deltaX, deltaY;
+ int warpsFinished = [self warpsFinishedByEventTime:eventTime location:eventLocation];
+ int i;
+
+ deltaX = CGEventGetDoubleValueField(event, kCGMouseEventDeltaX);
+ deltaY = CGEventGetDoubleValueField(event, kCGMouseEventDeltaY);
+
+ for (i = 0; i < warpsFinished; i++)
+ {
+ WarpRecord* warpRecord = [warpRecords objectAtIndex:0];
+ deltaX -= warpRecord.to.x - warpRecord.from.x;
+ deltaY -= warpRecord.to.y - warpRecord.from.y;
+ [warpRecords removeObjectAtIndex:0];
+ }
+
+ if (warpsFinished)
+ {
+ CGEventSetDoubleValueField(event, kCGMouseEventDeltaX, deltaX);
+ CGEventSetDoubleValueField(event, kCGMouseEventDeltaY, deltaY);
+ }
+
+ synthesizedLocation.x += deltaX;
+ synthesizedLocation.y += deltaY;
+ }
+
+ // If the event is destined for another process, don't clip it. This may
+ // happen if the user activates Exposé or Mission Control. In that case,
+ // our app does not resign active status, so clipping is still in effect,
+ // but the cursor should not actually be clipped.
+ //
+ // In addition, the fact that mouse moves may have been delivered to a
+ // different process means we have to treat the next one we receive as
+ // absolute rather than relative.
+ if (CGEventGetIntegerValueField(event, kCGEventTargetUnixProcessID) == getpid())
+ [self clipCursorLocation:&synthesizedLocation];
+ else
+ [WineApplicationController sharedController].lastSetCursorPositionTime = lastEventTapEventTime;
+
+ [self warpCursorTo:&synthesizedLocation from:&cursorLocation];
+ if (!CGPointEqualToPoint(eventLocation, synthesizedLocation))
+ CGEventSetLocation(event, synthesizedLocation);
+
+ return event;
+ }
+
+ CGEventRef WineAppEventTapCallBack(CGEventTapProxy proxy, CGEventType type,
+ CGEventRef event, void *refcon)
+ {
+ WineEventTapClipCursorHandler* handler = refcon;
+ return [handler eventTapWithProxy:proxy type:type event:event];
+ }
+
+ - (BOOL) installEventTap
+ {
+ CGEventMask mask = CGEventMaskBit(kCGEventLeftMouseDown) |
+ CGEventMaskBit(kCGEventLeftMouseUp) |
+ CGEventMaskBit(kCGEventRightMouseDown) |
+ CGEventMaskBit(kCGEventRightMouseUp) |
+ CGEventMaskBit(kCGEventMouseMoved) |
+ CGEventMaskBit(kCGEventLeftMouseDragged) |
+ CGEventMaskBit(kCGEventRightMouseDragged) |
+ CGEventMaskBit(kCGEventOtherMouseDown) |
+ CGEventMaskBit(kCGEventOtherMouseUp) |
+ CGEventMaskBit(kCGEventOtherMouseDragged) |
+ CGEventMaskBit(kCGEventScrollWheel);
+ CFRunLoopSourceRef source;
+
+ if (cursorClippingEventTap)
+ return TRUE;
+
+ // We create an annotated session event tap rather than a process-specific
+ // event tap because we need to programmatically move the cursor even when
+ // mouse moves are directed to other processes. We disable our tap when
+ // other processes are active, but things like Exposé are handled by other
+ // processes even when we remain active.
+ cursorClippingEventTap = CGEventTapCreate(kCGAnnotatedSessionEventTap, kCGHeadInsertEventTap,
+ kCGEventTapOptionDefault, mask, WineAppEventTapCallBack, self);
+ if (!cursorClippingEventTap)
+ return FALSE;
+
+ CGEventTapEnable(cursorClippingEventTap, FALSE);
+
+ source = CFMachPortCreateRunLoopSource(NULL, cursorClippingEventTap, 0);
+ if (!source)
+ {
+ CFRelease(cursorClippingEventTap);
+ cursorClippingEventTap = NULL;
+ return FALSE;
+ }
+
+ CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes);
+ CFRelease(source);
+ return TRUE;
+ }
+
+ - (BOOL) setCursorPosition:(CGPoint)pos
+ {
+ BOOL ret;
+
+ [self clipCursorLocation:&pos];
+
+ ret = [self warpCursorTo:&pos from:NULL];
+ synthesizedLocation = pos;
+ if (ret)
+ {
+ // We want to discard mouse-move events that have already been
+ // through the event tap, because it's too late to account for
+ // the setting of the cursor position with them. However, the
+ // events that may be queued with times after that but before
+ // the above warp can still be used. So, use the last event
+ // tap event time so that -sendEvent: doesn't discard them.
+ [WineApplicationController sharedController].lastSetCursorPositionTime = lastEventTapEventTime;
+ }
+
+ return ret;
+ }
+
+ - (BOOL) startClippingCursor:(CGRect)rect
+ {
+ CGError err;
+
+ if (!cursorClippingEventTap && ![self installEventTap])
+ return FALSE;
+
+ err = CGAssociateMouseAndMouseCursorPosition(false);
+ if (err != kCGErrorSuccess)
+ return FALSE;
+
+ clippingCursor = TRUE;
+ cursorClipRect = rect;
+
+ CGEventTapEnable(cursorClippingEventTap, TRUE);
+
+ return TRUE;
+ }
+
+ - (BOOL) stopClippingCursor
+ {
+ CGError err = CGAssociateMouseAndMouseCursorPosition(true);
+ if (err != kCGErrorSuccess)
+ return FALSE;
+
+ clippingCursor = FALSE;
+
+ CGEventTapEnable(cursorClippingEventTap, FALSE);
+ [warpRecords removeAllObjects];
+
+ return TRUE;
+ }
+
+ - (void) setRetinaMode:(int)mode
+ {
+ double scale = mode ? 0.5 : 2.0;
+ cursorClipRect.origin.x *= scale;
+ cursorClipRect.origin.y *= scale;
+ cursorClipRect.size.width *= scale;
+ cursorClipRect.size.height *= scale;
+ }
+
+ at end
--
2.34.1
More information about the wine-devel
mailing list