commit 00cdf3a94603b81c9d18fee7028bc3eb34d265bb
parent aeb36f1750c026dc00bb86c1218080f06173ef93
Author: Markus Stange <mstange.moz@gmail.com>
Date: Thu, 23 Oct 2025 04:49:51 +0000
Bug 1820168 - Let the system re-set the current cursor when needed. r=mac-reviewers,bradwerth
This renames nsMacCursorManager to MOZDynamicCursor and makes it implement NSCursor.
We also install a cursor rect which covers the entire ChildView and which refers
to the MOZDynamicCursor singleton.
With the help of the cursor rect, the -[NSCursor set] implementation is now called
at the right times.
Here's a profile which contains the stacks under which -[MOZDynamicCursor set]
is now called: https://share.firefox.dev/3yEXLsC
Differential Revision: https://phabricator.services.mozilla.com/D172484
Diffstat:
6 files changed, 354 insertions(+), 344 deletions(-)
diff --git a/widget/cocoa/MOZDynamicCursor.h b/widget/cocoa/MOZDynamicCursor.h
@@ -0,0 +1,37 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef MOZDynamicCursor_h_
+#define MOZDynamicCursor_h_
+
+#import <Cocoa/Cocoa.h>
+#include "nsIWidget.h"
+
+// MOZDynamicCursor.sharedInstance is a singleton NSCursor object whose
+// underlying cursor can be changed at runtime.
+// It can be used in an NSView cursorRect so that the system will call
+// -[NSCursor set] on it at the right moments, for example when the
+// mouse moves into a window or when the cursor needs to be set after
+// a drag operation or when a context menu closes.
+@interface MOZDynamicCursor : NSCursor {
+ @private
+ NSMutableDictionary* mCursors;
+ NSCursor* mCurrentCursor;
+ nsCursor mCurrentCursorType;
+}
+
+// Sets non-custom cursors and can be used as a fallback if setting
+// a custom cursor did not succeed.
+- (void)setNonCustomCursor:(const nsIWidget::Cursor&)aCursor;
+
+// As above, but returns an error if the cursor isn't custom or we couldn't set
+// it for some reason.
+- (nsresult)setCustomCursor:(const nsIWidget::Cursor&)aCursor
+ widgetScaleFactor:(CGFloat)aWidgetScaleFactor
+ forceUpdate:(bool)aForceUpdate;
+
++ (MOZDynamicCursor*)sharedInstance;
+@end
+
+#endif // MOZDynamicCursor_h_
diff --git a/widget/cocoa/MOZDynamicCursor.mm b/widget/cocoa/MOZDynamicCursor.mm
@@ -0,0 +1,308 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "imgIContainer.h"
+#include "nsCocoaUtils.h"
+#include "MOZDynamicCursor.h"
+#include "nsObjCExceptions.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsIFile.h"
+#include <math.h>
+
+static MOZDynamicCursor* gInstance;
+static CGFloat sCurrentCursorScaleFactor = 0.0f;
+MOZ_RUNINIT static nsIWidget::Cursor sCurrentCursor;
+static constexpr nsCursor kCustomCursor = eCursorCount;
+
+@interface MOZDynamicCursor (PrivateMethods)
++ (NSCursor*)freshCursorWithType:(nsCursor)aCursor;
+- (NSCursor*)cursorWithType:(nsCursor)aCursor;
+
+// Set the cursor.
+- (void)setCursor:(NSCursor*)aMacCursor;
+
+@end
+
+@interface NSCursor (CreateWithImageNamed)
++ (NSCursor*)cursorWithImageNamed:(NSString*)imageName hotSpot:(NSPoint)aPoint;
+@end
+
+@interface NSCursor (Undocumented)
+// busyButClickableCursor is an undocumented NSCursor API, but has been in use
+// since at least OS X 10.4 and through 10.9.
++ (NSCursor*)busyButClickableCursor;
+@end
+
+@implementation MOZDynamicCursor
+
++ (MOZDynamicCursor*)sharedInstance {
+ if (!gInstance) {
+ gInstance = [[MOZDynamicCursor alloc] init];
+ }
+ return gInstance;
+}
+
++ (NSCursor*)freshCursorWithType:(enum nsCursor)aCursor {
+ switch (aCursor) {
+ case eCursor_standard:
+ return [NSCursor arrowCursor];
+ case eCursor_wait:
+ case eCursor_spinning: {
+ return [NSCursor busyButClickableCursor];
+ }
+ case eCursor_select:
+ return [NSCursor IBeamCursor];
+ case eCursor_hyperlink:
+ return [NSCursor pointingHandCursor];
+ case eCursor_crosshair:
+ return [NSCursor crosshairCursor];
+ case eCursor_move:
+ return [NSCursor cursorWithImageNamed:@"move"
+ hotSpot:NSMakePoint(12, 12)];
+ case eCursor_help:
+ return [NSCursor cursorWithImageNamed:@"help"
+ hotSpot:NSMakePoint(12, 12)];
+ case eCursor_copy: {
+ return [NSCursor dragCopyCursor];
+ }
+ case eCursor_alias: {
+ return [NSCursor dragLinkCursor];
+ }
+ case eCursor_context_menu: {
+ return [NSCursor contextualMenuCursor];
+ }
+ case eCursor_cell:
+ return [NSCursor cursorWithImageNamed:@"cell"
+ hotSpot:NSMakePoint(12, 12)];
+ case eCursor_grab:
+ return [NSCursor openHandCursor];
+ case eCursor_grabbing:
+ return [NSCursor closedHandCursor];
+ case eCursor_zoom_in:
+ return [NSCursor cursorWithImageNamed:@"zoomIn"
+ hotSpot:NSMakePoint(10, 10)];
+ case eCursor_zoom_out:
+ return [NSCursor cursorWithImageNamed:@"zoomOut"
+ hotSpot:NSMakePoint(10, 10)];
+ case eCursor_vertical_text:
+ return [NSCursor cursorWithImageNamed:@"vtIBeam"
+ hotSpot:NSMakePoint(12, 11)];
+ case eCursor_all_scroll:
+ return [NSCursor openHandCursor];
+ case eCursor_not_allowed:
+ case eCursor_no_drop: {
+ return [NSCursor operationNotAllowedCursor];
+ }
+ // Resize Cursors:
+ // North
+ case eCursor_n_resize:
+ return [NSCursor resizeUpCursor];
+ // North East
+ case eCursor_ne_resize:
+ return [NSCursor cursorWithImageNamed:@"sizeNE"
+ hotSpot:NSMakePoint(12, 11)];
+ // East
+ case eCursor_e_resize:
+ return [NSCursor resizeRightCursor];
+ // South East
+ case eCursor_se_resize:
+ return [NSCursor cursorWithImageNamed:@"sizeSE"
+ hotSpot:NSMakePoint(12, 12)];
+ // South
+ case eCursor_s_resize:
+ return [NSCursor resizeDownCursor];
+ // South West
+ case eCursor_sw_resize:
+ return [NSCursor cursorWithImageNamed:@"sizeSW"
+ hotSpot:NSMakePoint(10, 12)];
+ // West
+ case eCursor_w_resize:
+ return [NSCursor resizeLeftCursor];
+ // North West
+ case eCursor_nw_resize:
+ return [NSCursor cursorWithImageNamed:@"sizeNW"
+ hotSpot:NSMakePoint(11, 11)];
+ // North & South
+ case eCursor_ns_resize:
+ return [NSCursor resizeUpDownCursor];
+ // East & West
+ case eCursor_ew_resize:
+ return [NSCursor resizeLeftRightCursor];
+ // North East & South West
+ case eCursor_nesw_resize:
+ return [NSCursor cursorWithImageNamed:@"sizeNESW"
+ hotSpot:NSMakePoint(12, 12)];
+ // North West & South East
+ case eCursor_nwse_resize:
+ return [NSCursor cursorWithImageNamed:@"sizeNWSE"
+ hotSpot:NSMakePoint(12, 12)];
+ // Column Resize
+ case eCursor_col_resize:
+ return [NSCursor cursorWithImageNamed:@"colResize"
+ hotSpot:NSMakePoint(12, 12)];
+ // Row Resize
+ case eCursor_row_resize:
+ return [NSCursor cursorWithImageNamed:@"rowResize"
+ hotSpot:NSMakePoint(12, 12)];
+ default:
+ return [NSCursor arrowCursor];
+ }
+}
+
+- (id)init {
+ if ((self = [super init])) {
+ mCursors = [[NSMutableDictionary alloc] initWithCapacity:25];
+ mCurrentCursor = [[NSCursor arrowCursor] retain];
+ mCurrentCursorType = eCursor_standard;
+ }
+ return self;
+}
+
+- (void)setNonCustomCursor:(const nsIWidget::Cursor&)aCursor {
+ [self setCursor:[self cursorWithType:aCursor.mDefaultCursor]
+ type:aCursor.mDefaultCursor];
+ sCurrentCursor = aCursor;
+}
+
+- (void)setCursor:(NSCursor*)aMacCursor type:(nsCursor)aType {
+ if (mCurrentCursorType != aType) {
+ if (aType == eCursor_none) {
+ [NSCursor hide];
+ } else if (mCurrentCursorType == eCursor_none) {
+ [NSCursor unhide];
+ }
+ mCurrentCursorType = aType;
+ }
+
+ if (mCurrentCursor != aMacCursor) {
+ [mCurrentCursor release];
+ mCurrentCursor = [aMacCursor retain];
+ [mCurrentCursor set];
+ }
+}
+
+- (nsresult)setCustomCursor:(const nsIWidget::Cursor&)aCursor
+ widgetScaleFactor:(CGFloat)scaleFactor
+ forceUpdate:(bool)aForceUpdate {
+ // As the user moves the mouse, this gets called repeatedly with the same
+ // aCursorImage
+ if (!aForceUpdate && sCurrentCursor == aCursor &&
+ sCurrentCursorScaleFactor == scaleFactor && mCurrentCursor) {
+ return NS_OK;
+ }
+
+ sCurrentCursor = aCursor;
+ sCurrentCursorScaleFactor = scaleFactor;
+
+ if (!aCursor.IsCustom()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsIntSize size = nsIWidget::CustomCursorSize(aCursor);
+ // prevent DoS attacks
+ if (size.width > 128 || size.height > 128) {
+ return NS_ERROR_FAILURE;
+ }
+
+ const NSSize cocoaSize = NSMakeSize(size.width, size.height);
+ NSImage* cursorImage;
+ nsresult rv = nsCocoaUtils::CreateNSImageFromImageContainer(
+ aCursor.mContainer, imgIContainer::FRAME_FIRST, nullptr, cocoaSize,
+ &cursorImage, scaleFactor);
+ if (NS_FAILED(rv) || !cursorImage) {
+ return NS_ERROR_FAILURE;
+ }
+
+ [cursorImage setSize:cocoaSize];
+ [[[cursorImage representations] objectAtIndex:0] setSize:cocoaSize];
+
+ // if the hotspot is nonsensical, make it 0,0
+ uint32_t hotspotX =
+ aCursor.mHotspotX > (uint32_t(size.width) - 1) ? 0 : aCursor.mHotspotX;
+ uint32_t hotspotY =
+ aCursor.mHotspotY > (uint32_t(size.height) - 1) ? 0 : aCursor.mHotspotY;
+ NSPoint hotSpot = ::NSMakePoint(hotspotX, hotspotY);
+ [self setCursor:[[NSCursor alloc] initWithImage:cursorImage hotSpot:hotSpot]
+ type:kCustomCursor];
+ [cursorImage release];
+ return NS_OK;
+}
+
+- (NSCursor*)cursorWithType:(enum nsCursor)aCursor {
+ NSCursor* result = [mCursors objectForKey:[NSNumber numberWithInt:aCursor]];
+ if (!result) {
+ result = [MOZDynamicCursor freshCursorWithType:aCursor];
+ [mCursors setObject:result forKey:[NSNumber numberWithInt:aCursor]];
+ }
+ return result;
+}
+
+// This method gets called by ChildView's cursor rect (or rather its underlying
+// NSTrackingArea) whenever the mouse enters it, for example after a dragging
+// operation, after a menu closes, or when the mouse enters a window.
+- (void)set {
+ [mCurrentCursor set];
+}
+
+- (void)dealloc {
+ [mCurrentCursor release];
+ [mCursors release];
+ sCurrentCursor = {};
+ [super dealloc];
+}
+
+@end
+
+@implementation NSCursor (CreateWithImageName)
+
++ (NSCursor*)cursorWithImageNamed:(NSString*)imageName hotSpot:(NSPoint)aPoint {
+ nsCOMPtr<nsIFile> resDir;
+ nsAutoCString resPath;
+ NSString *pathToImage, *pathToHiDpiImage;
+ NSImage *cursorImage, *hiDpiCursorImage;
+
+ nsresult rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(resDir));
+ if (NS_FAILED(rv)) goto INIT_FAILURE;
+ resDir->AppendNative("res"_ns);
+ resDir->AppendNative("cursors"_ns);
+
+ rv = resDir->GetNativePath(resPath);
+ if (NS_FAILED(rv)) goto INIT_FAILURE;
+
+ pathToImage = [NSString stringWithUTF8String:(const char*)resPath.get()];
+ if (!pathToImage) goto INIT_FAILURE;
+ pathToImage = [pathToImage stringByAppendingPathComponent:imageName];
+ pathToHiDpiImage = [pathToImage stringByAppendingString:@"@2x"];
+ // Add same extension to both image paths.
+ pathToImage = [pathToImage stringByAppendingPathExtension:@"png"];
+ pathToHiDpiImage = [pathToHiDpiImage stringByAppendingPathExtension:@"png"];
+
+ cursorImage =
+ [[[NSImage alloc] initWithContentsOfFile:pathToImage] autorelease];
+ if (!cursorImage) goto INIT_FAILURE;
+
+ // Note 1: There are a few different ways to get a hidpi image via
+ // initWithContentsOfFile. We let the OS handle this here: when the
+ // file basename ends in "@2x", it will be displayed at native resolution
+ // instead of being pixel-doubled. See bug 784909 comment 7 for alternate
+ // ways.
+ //
+ // Note 2: The OS is picky, and will ignore the hidpi representation
+ // unless it is exactly twice the size of the lowdpi image.
+ hiDpiCursorImage =
+ [[[NSImage alloc] initWithContentsOfFile:pathToHiDpiImage] autorelease];
+ if (hiDpiCursorImage) {
+ NSImageRep* imageRep = [[hiDpiCursorImage representations] objectAtIndex:0];
+ [cursorImage addRepresentation:imageRep];
+ }
+ return [[[NSCursor alloc] initWithImage:cursorImage
+ hotSpot:aPoint] autorelease];
+
+INIT_FAILURE:
+ NS_WARNING("Problem getting path to cursor image file!");
+ [self release];
+ return nil;
+}
+
+@end
diff --git a/widget/cocoa/moz.build b/widget/cocoa/moz.build
@@ -33,6 +33,7 @@ UNIFIED_SOURCES += [
"CompositorWidgetChild.cpp",
"CompositorWidgetParent.cpp",
"GfxInfo.mm",
+ "MOZDynamicCursor.mm",
"MOZIconHelper.mm",
"MOZMenuOpeningCoordinator.mm",
"NativeKeyBindings.mm",
@@ -44,7 +45,6 @@ UNIFIED_SOURCES += [
"nsCocoaUtils.mm",
"nsCocoaWindow.mm",
"nsColorPicker.mm",
- "nsCursorManager.mm",
"nsDeviceContextSpecX.mm",
"nsFilePicker.mm",
"nsLookAndFeel.mm",
diff --git a/widget/cocoa/nsCocoaWindow.mm b/widget/cocoa/nsCocoaWindow.mm
@@ -7,7 +7,7 @@
#include "nsCocoaWindow.h"
#include "nsArrayUtils.h"
-#include "nsCursorManager.h"
+#include "MOZDynamicCursor.h"
#include "nsIAppStartup.h"
#include "nsIDOMWindowUtils.h"
#include "nsILocalFileMac.h"
@@ -315,14 +315,14 @@ void nsCocoaWindow::SetCursor(const Cursor& aCursor) {
bool forceUpdate = mUpdateCursor;
mUpdateCursor = false;
- if (mCustomCursorAllowed && NS_SUCCEEDED([[nsCursorManager sharedInstance]
+ if (mCustomCursorAllowed && NS_SUCCEEDED([MOZDynamicCursor.sharedInstance
setCustomCursor:aCursor
widgetScaleFactor:BackingScaleFactor()
forceUpdate:forceUpdate])) {
return;
}
- [[nsCursorManager sharedInstance] setNonCustomCursor:aCursor];
+ [MOZDynamicCursor.sharedInstance setNonCustomCursor:aCursor];
NS_OBJC_END_TRY_IGNORE_BLOCK;
}
@@ -3710,6 +3710,10 @@ static gfx::IntPoint GetIntegerDeltaForEvent(NSEvent* aEvent) {
NS_OBJC_END_TRY_BLOCK_RETURN(NSDragOperationNone);
}
+- (void)resetCursorRects {
+ [self addCursorRect:self.bounds cursor:MOZDynamicCursor.sharedInstance];
+}
+
// NSDraggingDestination
- (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender {
NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
@@ -4535,7 +4539,7 @@ void ChildViewMouseTracker::ReEvaluateMouseEnterState(NSEvent* aEvent,
// After the cursor exits the window set it to a visible regular arrow
// cursor.
if (exitFrom == WidgetMouseEvent::ePlatformTopLevel) {
- [[nsCursorManager sharedInstance]
+ [MOZDynamicCursor.sharedInstance
setNonCustomCursor:nsIWidget::Cursor{eCursor_standard}];
}
[sLastMouseEventView sendMouseEnterOrExitEvent:aEvent
@@ -4997,7 +5001,6 @@ nsresult nsCocoaWindow::CreateNativeWindow(const NSRect& aRect,
mWindow.collectionBehavior | NSWindowCollectionBehaviorCanJoinAllSpaces;
}
mWindow.contentMinSize = NSMakeSize(60, 60);
- [mWindow disableCursorRects];
// Make the window use CoreAnimation from the start, so that we don't
// switch from a non-CA window to a CA-window in the middle.
diff --git a/widget/cocoa/nsCursorManager.h b/widget/cocoa/nsCursorManager.h
@@ -1,37 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#ifndef nsCursorManager_h_
-#define nsCursorManager_h_
-
-#import <Cocoa/Cocoa.h>
-#include "nsIWidget.h"
-
-@interface nsCursorManager : NSObject {
- @private
- NSMutableDictionary* mCursors;
- NSCursor* mCurrentCursor;
- nsCursor mCurrentCursorType;
-}
-
-// Sets non-custom cursors and can be used as a fallback if setting
-// a custom cursor did not succeed.
-- (void)setNonCustomCursor:(const nsIWidget::Cursor&)aCursor;
-
-// As above, but returns an error if the cursor isn't custom or we couldn't set
-// it for some reason.
-- (nsresult)setCustomCursor:(const nsIWidget::Cursor&)aCursor
- widgetScaleFactor:(CGFloat)aWidgetScaleFactor
- forceUpdate:(bool)aForceUpdate;
-
-+ (nsCursorManager*)sharedInstance;
-@end
-
-@interface NSCursor (Undocumented)
-// busyButClickableCursor is an undocumented NSCursor API, but has been in use
-// since at least OS X 10.4 and through 10.9.
-+ (NSCursor*)busyButClickableCursor;
-@end
-
-#endif // nsCursorManager_h_
diff --git a/widget/cocoa/nsCursorManager.mm b/widget/cocoa/nsCursorManager.mm
@@ -1,301 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#include "imgIContainer.h"
-#include "nsCocoaUtils.h"
-#include "nsCursorManager.h"
-#include "nsObjCExceptions.h"
-#include "nsDirectoryServiceDefs.h"
-#include "nsIFile.h"
-#include <math.h>
-
-static nsCursorManager* gInstance;
-static CGFloat sCurrentCursorScaleFactor = 0.0f;
-MOZ_RUNINIT static nsIWidget::Cursor sCurrentCursor;
-static constexpr nsCursor kCustomCursor = eCursorCount;
-
-@interface nsCursorManager (PrivateMethods)
-+ (NSCursor*)freshCursorWithType:(nsCursor)aCursor;
-- (NSCursor*)cursorWithType:(nsCursor)aCursor;
-
-// Set the cursor.
-- (void)setCursor:(NSCursor*)aMacCursor;
-
-@end
-
-@interface NSCursor (CreateWithImageNamed)
-+ (NSCursor*)cursorWithImageNamed:(NSString*)imageName hotSpot:(NSPoint)aPoint;
-@end
-
-@implementation nsCursorManager
-
-+ (nsCursorManager*)sharedInstance {
- if (!gInstance) {
- gInstance = [[nsCursorManager alloc] init];
- }
- return gInstance;
-}
-
-+ (NSCursor*)freshCursorWithType:(enum nsCursor)aCursor {
- switch (aCursor) {
- case eCursor_standard:
- return [NSCursor arrowCursor];
- case eCursor_wait:
- case eCursor_spinning: {
- return [NSCursor busyButClickableCursor];
- }
- case eCursor_select:
- return [NSCursor IBeamCursor];
- case eCursor_hyperlink:
- return [NSCursor pointingHandCursor];
- case eCursor_crosshair:
- return [NSCursor crosshairCursor];
- case eCursor_move:
- return [NSCursor cursorWithImageNamed:@"move"
- hotSpot:NSMakePoint(12, 12)];
- case eCursor_help:
- return [NSCursor cursorWithImageNamed:@"help"
- hotSpot:NSMakePoint(12, 12)];
- case eCursor_copy: {
- return [NSCursor dragCopyCursor];
- }
- case eCursor_alias: {
- return [NSCursor dragLinkCursor];
- }
- case eCursor_context_menu: {
- return [NSCursor contextualMenuCursor];
- }
- case eCursor_cell:
- return [NSCursor cursorWithImageNamed:@"cell"
- hotSpot:NSMakePoint(12, 12)];
- case eCursor_grab:
- return [NSCursor openHandCursor];
- case eCursor_grabbing:
- return [NSCursor closedHandCursor];
- case eCursor_zoom_in:
- return [NSCursor cursorWithImageNamed:@"zoomIn"
- hotSpot:NSMakePoint(10, 10)];
- case eCursor_zoom_out:
- return [NSCursor cursorWithImageNamed:@"zoomOut"
- hotSpot:NSMakePoint(10, 10)];
- case eCursor_vertical_text:
- return [NSCursor cursorWithImageNamed:@"vtIBeam"
- hotSpot:NSMakePoint(12, 11)];
- case eCursor_all_scroll:
- return [NSCursor openHandCursor];
- case eCursor_not_allowed:
- case eCursor_no_drop: {
- return [NSCursor operationNotAllowedCursor];
- }
- // Resize Cursors:
- // North
- case eCursor_n_resize:
- return [NSCursor resizeUpCursor];
- // North East
- case eCursor_ne_resize:
- return [NSCursor cursorWithImageNamed:@"sizeNE"
- hotSpot:NSMakePoint(12, 11)];
- // East
- case eCursor_e_resize:
- return [NSCursor resizeRightCursor];
- // South East
- case eCursor_se_resize:
- return [NSCursor cursorWithImageNamed:@"sizeSE"
- hotSpot:NSMakePoint(12, 12)];
- // South
- case eCursor_s_resize:
- return [NSCursor resizeDownCursor];
- // South West
- case eCursor_sw_resize:
- return [NSCursor cursorWithImageNamed:@"sizeSW"
- hotSpot:NSMakePoint(10, 12)];
- // West
- case eCursor_w_resize:
- return [NSCursor resizeLeftCursor];
- // North West
- case eCursor_nw_resize:
- return [NSCursor cursorWithImageNamed:@"sizeNW"
- hotSpot:NSMakePoint(11, 11)];
- // North & South
- case eCursor_ns_resize:
- return [NSCursor resizeUpDownCursor];
- // East & West
- case eCursor_ew_resize:
- return [NSCursor resizeLeftRightCursor];
- // North East & South West
- case eCursor_nesw_resize:
- return [NSCursor cursorWithImageNamed:@"sizeNESW"
- hotSpot:NSMakePoint(12, 12)];
- // North West & South East
- case eCursor_nwse_resize:
- return [NSCursor cursorWithImageNamed:@"sizeNWSE"
- hotSpot:NSMakePoint(12, 12)];
- // Column Resize
- case eCursor_col_resize:
- return [NSCursor cursorWithImageNamed:@"colResize"
- hotSpot:NSMakePoint(12, 12)];
- // Row Resize
- case eCursor_row_resize:
- return [NSCursor cursorWithImageNamed:@"rowResize"
- hotSpot:NSMakePoint(12, 12)];
- default:
- return [NSCursor arrowCursor];
- }
-}
-
-- (id)init {
- if ((self = [super init])) {
- mCursors = [[NSMutableDictionary alloc] initWithCapacity:25];
- mCurrentCursor = [[NSCursor arrowCursor] retain];
- mCurrentCursorType = eCursor_standard;
- }
- return self;
-}
-
-- (void)setNonCustomCursor:(const nsIWidget::Cursor&)aCursor {
- [self setCursor:[self cursorWithType:aCursor.mDefaultCursor]
- type:aCursor.mDefaultCursor];
- sCurrentCursor = aCursor;
-}
-
-- (void)setCursor:(NSCursor*)aMacCursor type:(nsCursor)aType {
- if (mCurrentCursorType != aType) {
- if (aType == eCursor_none) {
- [NSCursor hide];
- } else if (mCurrentCursorType == eCursor_none) {
- [NSCursor unhide];
- }
- mCurrentCursorType = aType;
- }
-
- if (mCurrentCursor != aMacCursor ||
- [NSCursor currentCursor] != mCurrentCursor) {
- [aMacCursor retain];
- [aMacCursor set];
- [mCurrentCursor release];
- mCurrentCursor = aMacCursor;
- }
-}
-
-- (nsresult)setCustomCursor:(const nsIWidget::Cursor&)aCursor
- widgetScaleFactor:(CGFloat)scaleFactor
- forceUpdate:(bool)aForceUpdate {
- // As the user moves the mouse, this gets called repeatedly with the same
- // aCursorImage
- if (!aForceUpdate && sCurrentCursor == aCursor &&
- sCurrentCursorScaleFactor == scaleFactor && mCurrentCursor) {
- // Native dragging can unset our cursor apparently (see bug 1739352).
- if (MOZ_UNLIKELY([NSCursor currentCursor] != mCurrentCursor)) {
- [mCurrentCursor set];
- }
- return NS_OK;
- }
-
- sCurrentCursor = aCursor;
- sCurrentCursorScaleFactor = scaleFactor;
-
- if (!aCursor.IsCustom()) {
- return NS_ERROR_FAILURE;
- }
-
- nsIntSize size = nsIWidget::CustomCursorSize(aCursor);
- // prevent DoS attacks
- if (size.width > 128 || size.height > 128) {
- return NS_ERROR_FAILURE;
- }
-
- const NSSize cocoaSize = NSMakeSize(size.width, size.height);
- NSImage* cursorImage;
- nsresult rv = nsCocoaUtils::CreateNSImageFromImageContainer(
- aCursor.mContainer, imgIContainer::FRAME_FIRST, nullptr, cocoaSize,
- &cursorImage, scaleFactor);
- if (NS_FAILED(rv) || !cursorImage) {
- return NS_ERROR_FAILURE;
- }
-
- [cursorImage setSize:cocoaSize];
- [[[cursorImage representations] objectAtIndex:0] setSize:cocoaSize];
-
- // if the hotspot is nonsensical, make it 0,0
- uint32_t hotspotX =
- aCursor.mHotspotX > (uint32_t(size.width) - 1) ? 0 : aCursor.mHotspotX;
- uint32_t hotspotY =
- aCursor.mHotspotY > (uint32_t(size.height) - 1) ? 0 : aCursor.mHotspotY;
- NSPoint hotSpot = ::NSMakePoint(hotspotX, hotspotY);
- [self setCursor:[[NSCursor alloc] initWithImage:cursorImage hotSpot:hotSpot]
- type:kCustomCursor];
- [cursorImage release];
- return NS_OK;
-}
-
-- (NSCursor*)cursorWithType:(enum nsCursor)aCursor {
- NSCursor* result = [mCursors objectForKey:[NSNumber numberWithInt:aCursor]];
- if (!result) {
- result = [nsCursorManager freshCursorWithType:aCursor];
- [mCursors setObject:result forKey:[NSNumber numberWithInt:aCursor]];
- }
- return result;
-}
-
-- (void)dealloc {
- [mCurrentCursor release];
- [mCursors release];
- sCurrentCursor = {};
- [super dealloc];
-}
-
-@end
-
-@implementation NSCursor (CreateWithImageName)
-
-+ (NSCursor*)cursorWithImageNamed:(NSString*)imageName hotSpot:(NSPoint)aPoint {
- nsCOMPtr<nsIFile> resDir;
- nsAutoCString resPath;
- NSString *pathToImage, *pathToHiDpiImage;
- NSImage *cursorImage, *hiDpiCursorImage;
-
- nsresult rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(resDir));
- if (NS_FAILED(rv)) goto INIT_FAILURE;
- resDir->AppendNative("res"_ns);
- resDir->AppendNative("cursors"_ns);
-
- rv = resDir->GetNativePath(resPath);
- if (NS_FAILED(rv)) goto INIT_FAILURE;
-
- pathToImage = [NSString stringWithUTF8String:(const char*)resPath.get()];
- if (!pathToImage) goto INIT_FAILURE;
- pathToImage = [pathToImage stringByAppendingPathComponent:imageName];
- pathToHiDpiImage = [pathToImage stringByAppendingString:@"@2x"];
- // Add same extension to both image paths.
- pathToImage = [pathToImage stringByAppendingPathExtension:@"png"];
- pathToHiDpiImage = [pathToHiDpiImage stringByAppendingPathExtension:@"png"];
-
- cursorImage =
- [[[NSImage alloc] initWithContentsOfFile:pathToImage] autorelease];
- if (!cursorImage) goto INIT_FAILURE;
-
- // Note 1: There are a few different ways to get a hidpi image via
- // initWithContentsOfFile. We let the OS handle this here: when the
- // file basename ends in "@2x", it will be displayed at native resolution
- // instead of being pixel-doubled. See bug 784909 comment 7 for alternate
- // ways.
- //
- // Note 2: The OS is picky, and will ignore the hidpi representation
- // unless it is exactly twice the size of the lowdpi image.
- hiDpiCursorImage =
- [[[NSImage alloc] initWithContentsOfFile:pathToHiDpiImage] autorelease];
- if (hiDpiCursorImage) {
- NSImageRep* imageRep = [[hiDpiCursorImage representations] objectAtIndex:0];
- [cursorImage addRepresentation:imageRep];
- }
- return [[[NSCursor alloc] initWithImage:cursorImage
- hotSpot:aPoint] autorelease];
-
-INIT_FAILURE:
- NS_WARNING("Problem getting path to cursor image file!");
- [self release];
- return nil;
-}
-
-@end