Module: wine
Branch: master
Commit: 599ecd97a8fded8c00aa261535880a4d8b5d2693
URL: https://source.winehq.org/git/wine.git/?a=commit;h=599ecd97a8fded8c00aa2615…
Author: Tim Clem <tclem(a)codeweavers.com>
Date: Wed Jan 19 11:40:28 2022 -0800
winemac.drv: Use -setMouseConfinementRect: for cursor clipping by default.
On macOS 10.13+, use this private NSWindow method for ClipCursor
calls. The old behavior can be restored by setting the per-app Mac
Driver registry key UseConfinementCursorClipping to N.
Signed-off-by: Tim Clem <tclem(a)codeweavers.com>
Signed-off-by: Alexandre Julliard <julliard(a)winehq.org>
---
dlls/winemac.drv/cocoa_app.m | 8 ++++++--
dlls/winemac.drv/cocoa_cursorclipping.m | 4 ++++
dlls/winemac.drv/macdrv_cocoa.h | 1 +
dlls/winemac.drv/macdrv_main.c | 4 ++++
4 files changed, 15 insertions(+), 2 deletions(-)
diff --git a/dlls/winemac.drv/cocoa_app.m b/dlls/winemac.drv/cocoa_app.m
index b0dd36f4abc..b5a3059382e 100644
--- a/dlls/winemac.drv/cocoa_app.m
+++ b/dlls/winemac.drv/cocoa_app.m
@@ -1211,8 +1211,12 @@ static NSString* WineLocalizedString(unsigned int stringID)
- (BOOL) startClippingCursor:(CGRect)rect
{
- if (!clipCursorHandler)
- clipCursorHandler = [[WineEventTapClipCursorHandler alloc] init];
+ if (!clipCursorHandler) {
+ if (use_confinement_cursor_clipping && [WineConfinementClipCursorHandler isAvailable])
+ clipCursorHandler = [[WineConfinementClipCursorHandler alloc] init];
+ else
+ clipCursorHandler = [[WineEventTapClipCursorHandler alloc] init];
+ }
if (self.clippingCursor && CGRectEqualToRect(rect, clipCursorHandler.cursorClipRect))
return TRUE;
diff --git a/dlls/winemac.drv/cocoa_cursorclipping.m b/dlls/winemac.drv/cocoa_cursorclipping.m
index bbaa896099b..81b53c2703c 100644
--- a/dlls/winemac.drv/cocoa_cursorclipping.m
+++ b/dlls/winemac.drv/cocoa_cursorclipping.m
@@ -34,6 +34,10 @@
* -[NSWindow setMouseConfinementRect:]. It comes with its own drawbacks,
* but is generally far simpler. It is described and implemented in
* the WineConfinementClipCursorHandler class.
+ *
+ * On macOS 10.13+, WineConfinementClipCursorHandler is the default.
+ * The Mac driver registry key UseConfinementCursorClipping can be set
+ * to "n" to use the event tap implementation.
*/
diff --git a/dlls/winemac.drv/macdrv_cocoa.h b/dlls/winemac.drv/macdrv_cocoa.h
index 5c19b4f4e81..94f9fbcfa17 100644
--- a/dlls/winemac.drv/macdrv_cocoa.h
+++ b/dlls/winemac.drv/macdrv_cocoa.h
@@ -162,6 +162,7 @@ extern int right_option_is_alt DECLSPEC_HIDDEN;
extern int left_command_is_ctrl DECLSPEC_HIDDEN;
extern int right_command_is_ctrl DECLSPEC_HIDDEN;
extern int allow_immovable_windows DECLSPEC_HIDDEN;
+extern int use_confinement_cursor_clipping DECLSPEC_HIDDEN;
extern int cursor_clipping_locks_windows DECLSPEC_HIDDEN;
extern int use_precise_scrolling DECLSPEC_HIDDEN;
extern int gl_surface_mode DECLSPEC_HIDDEN;
diff --git a/dlls/winemac.drv/macdrv_main.c b/dlls/winemac.drv/macdrv_main.c
index 63c6a8199e0..a6a7f73e040 100644
--- a/dlls/winemac.drv/macdrv_main.c
+++ b/dlls/winemac.drv/macdrv_main.c
@@ -57,6 +57,7 @@ int right_command_is_ctrl = 0;
BOOL allow_software_rendering = FALSE;
BOOL disable_window_decorations = FALSE;
int allow_immovable_windows = TRUE;
+int use_confinement_cursor_clipping = TRUE;
int cursor_clipping_locks_windows = TRUE;
int use_precise_scrolling = TRUE;
int gl_surface_mode = GL_SURFACE_IN_FRONT_OPAQUE;
@@ -194,6 +195,9 @@ static void setup_options(void)
if (!get_config_key(hkey, appkey, "AllowImmovableWindows", buffer, sizeof(buffer)))
allow_immovable_windows = IS_OPTION_TRUE(buffer[0]);
+ if (!get_config_key(hkey, appkey, "UseConfinementCursorClipping", buffer, sizeof(buffer)))
+ use_confinement_cursor_clipping = IS_OPTION_TRUE(buffer[0]);
+
if (!get_config_key(hkey, appkey, "CursorClippingLocksWindows", buffer, sizeof(buffer)))
cursor_clipping_locks_windows = IS_OPTION_TRUE(buffer[0]);
Module: wine
Branch: master
Commit: 648fcd1882831a8c2e009916b25588b2cb2c5017
URL: https://source.winehq.org/git/wine.git/?a=commit;h=648fcd1882831a8c2e009916…
Author: Tim Clem <tclem(a)codeweavers.com>
Date: Wed Jan 19 11:40:27 2022 -0800
winemac.drv: Add a cursor clipping implementation using -setMouseConfinementRect:.
This 10.13+ API is far simpler than the CGEventTap approach, and does
not require Accessibility permissions. It is not currently not enabled.
Signed-off-by: Tim Clem <tclem(a)codeweavers.com>
Signed-off-by: Alexandre Julliard <julliard(a)winehq.org>
---
dlls/winemac.drv/cocoa_cursorclipping.h | 16 ++++
dlls/winemac.drv/cocoa_cursorclipping.m | 141 ++++++++++++++++++++++++++++++++
2 files changed, 157 insertions(+)
diff --git a/dlls/winemac.drv/cocoa_cursorclipping.h b/dlls/winemac.drv/cocoa_cursorclipping.h
index 7ce0529a157..bec7b384d87 100644
--- a/dlls/winemac.drv/cocoa_cursorclipping.h
+++ b/dlls/winemac.drv/cocoa_cursorclipping.h
@@ -55,3 +55,19 @@
}
@end
+
+
+@interface WineConfinementClipCursorHandler : NSObject <WineClipCursorHandler>
+{
+ BOOL clippingCursor;
+ CGRect cursorClipRect;
+ /* The number of the window that "owns" the clipping (i.e., the one with a
+ * mouseConfinementRect set). Using this rather than a WineWindow* to avoid
+ * tricky retain situations. */
+ NSInteger clippingWindowNumber;
+}
+
+ /* Returns true if the API in use by this handler is available. */
+ + (BOOL) isAvailable;
+
+@end
diff --git a/dlls/winemac.drv/cocoa_cursorclipping.m b/dlls/winemac.drv/cocoa_cursorclipping.m
index eaa243ae1a5..bbaa896099b 100644
--- a/dlls/winemac.drv/cocoa_cursorclipping.m
+++ b/dlls/winemac.drv/cocoa_cursorclipping.m
@@ -21,9 +21,24 @@
#import "cocoa_app.h"
#import "cocoa_cursorclipping.h"
+#import "cocoa_window.h"
/* Neither Quartz nor Cocoa has an exact analog for Win32 cursor clipping.
+ *
+ * Historically, we've used a CGEventTap and the
+ * CGAssociateMouseAndMouseCursorPosition function, as implemented in
+ * the WineEventTapClipCursorHandler class.
+ *
+ * As of macOS 10.13, there is an undocumented alternative,
+ * -[NSWindow setMouseConfinementRect:]. It comes with its own drawbacks,
+ * but is generally far simpler. It is described and implemented in
+ * the WineConfinementClipCursorHandler class.
+ */
+
+
+/* Clipping via CGEventTap and CGAssociateMouseAndMouseCursorPosition:
+ *
* 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
@@ -363,3 +378,129 @@ static void scale_rect_for_retina_mode(int mode, CGRect *cursorClipRect)
}
@end
+
+
+/* Clipping via mouse confinement rects:
+ *
+ * The undocumented -[NSWindow setMouseConfinementRect:] method is almost
+ * perfect for our needs. It has two main drawbacks compared to the CGEventTap
+ * approach:
+ * 1. It requires macOS 10.13+
+ * 2. A mouse confinement rect is tied to a region of a particular window. If
+ * an app calls ClipCursor with a rect that is outside the bounds of a
+ * window, the best we can do is intersect that rect with the window's bounds
+ * and clip to the result. If no windows are visible in the app, we can't do
+ * any clipping. Switching between windows in the same app while clipping is
+ * active is likewise impossible.
+ *
+ * But it has two major benefits:
+ * 1. The code is far simpler.
+ * 2. CGEventTap started requiring Accessibility permissions from macOS in
+ * Catalina. It's a hassle to enable, and if it's triggered while an app is
+ * fullscreen (which is often the case with clipping), it's easy to miss.
+ */
+
+
+@interface NSWindow (UndocumentedMouseConfinement)
+ /* Confines the system's mouse location to the provided window-relative rect
+ * while the app is frontmost and the window is key or a child of the key
+ * window. Confinement rects will be unioned among the key window and its
+ * children. The app should invoke this any time internal window geometry
+ * changes to keep the region up to date. Set NSZeroRect to remove mouse
+ * location confinement.
+ *
+ * These have been available since 10.13.
+ */
+ - (NSRect) mouseConfinementRect;
+ - (void) setMouseConfinementRect:(NSRect)mouseConfinementRect;
+@end
+
+
+@implementation WineConfinementClipCursorHandler
+
+@synthesize clippingCursor, cursorClipRect;
+
+ + (BOOL) isAvailable
+ {
+ if ([NSProcessInfo instancesRespondToSelector:@selector(isOperatingSystemAtLeastVersion:)])
+ {
+ NSOperatingSystemVersion requiredVersion = { 10, 13, 0 };
+ return [[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:requiredVersion] &&
+ [NSWindow instancesRespondToSelector:@selector(setMouseConfinementRect:)];
+ }
+
+ return FALSE;
+ }
+
+ /* Returns the region of the given rect that intersects with the given
+ * window. The rect should be in screen coordinates. The result will be in
+ * window-relative coordinates.
+ *
+ * Returns NSZeroRect if the rect lies entirely outside the window.
+ */
+ + (NSRect) rectForScreenRect:(CGRect)rect inWindow:(NSWindow*)window
+ {
+ NSRect flippedRect = NSRectFromCGRect(rect);
+ [[WineApplicationController sharedController] flipRect:&flippedRect];
+
+ NSRect intersection = NSIntersectionRect([window frame], flippedRect);
+
+ if (NSIsEmptyRect(intersection))
+ return NSZeroRect;
+
+ return [window convertRectFromScreen:intersection];
+ }
+
+ - (BOOL) startClippingCursor:(CGRect)rect
+ {
+ if (clippingCursor && ![self stopClippingCursor])
+ return FALSE;
+
+ WineWindow *ownerWindow = [[WineApplicationController sharedController] frontWineWindow];
+ if (!ownerWindow)
+ {
+ /* There's nothing we can do here in this case, since confinement
+ * rects must be tied to a window. */
+ return FALSE;
+ }
+
+ NSRect clipRectInWindowCoords = [WineConfinementClipCursorHandler rectForScreenRect:rect
+ inWindow:ownerWindow];
+
+ if (NSIsEmptyRect(clipRectInWindowCoords))
+ {
+ /* If the clip region is entirely outside of the bounds of the
+ * window, there's again nothing we can do. */
+ return FALSE;
+ }
+
+ [ownerWindow setMouseConfinementRect:clipRectInWindowCoords];
+
+ clippingWindowNumber = ownerWindow.windowNumber;
+ cursorClipRect = rect;
+ clippingCursor = TRUE;
+
+ return TRUE;
+ }
+
+ - (BOOL) stopClippingCursor
+ {
+ NSWindow *ownerWindow = [NSApp windowWithWindowNumber:clippingWindowNumber];
+ [ownerWindow setMouseConfinementRect:NSZeroRect];
+
+ clippingCursor = FALSE;
+
+ return TRUE;
+ }
+
+ - (void) clipCursorLocation:(CGPoint*)location
+ {
+ clip_cursor_location(cursorClipRect, location);
+ }
+
+ - (void) setRetinaMode:(int)mode
+ {
+ scale_rect_for_retina_mode(mode, &cursorClipRect);
+ }
+
+@end