1/* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5#include "imgIContainer.h" 6#include "nsCocoaUtils.h" 7#include "nsCursorManager.h" 8#include "nsObjCExceptions.h" 9#include <math.h> 10 11static nsCursorManager* gInstance; 12static CGFloat sCurrentCursorScaleFactor = 0.0f; 13static nsIWidget::Cursor sCurrentCursor; 14static constexpr nsCursor kCustomCursor = eCursorCount; 15 16/*! @category nsCursorManager(PrivateMethods) 17 Private methods for the cursor manager class. 18*/ 19@interface nsCursorManager (PrivateMethods) 20/*! @method getCursor: 21 @abstract Get a reference to the native Mac representation of a cursor. 22 @discussion Gets a reference to the Mac native implementation of a cursor. 23 If the cursor has been requested before, it is retreived from the cursor cache, 24 otherwise it is created and cached. 25 @param aCursor the cursor to get 26 @result the Mac native implementation of the cursor 27*/ 28- (nsMacCursor*)getCursor:(nsCursor)aCursor; 29 30/*! @method setMacCursor: 31 @abstract Set the current Mac native cursor 32 @discussion Sets the current cursor - this routine is what actually causes the cursor to change. 33 The argument is retained and the old cursor is released. 34 @param aMacCursor the cursor to set 35 @result NS_OK 36 */ 37- (nsresult)setMacCursor:(nsMacCursor*)aMacCursor; 38 39/*! @method createCursor: 40 @abstract Create a Mac native representation of a cursor. 41 @discussion Creates a version of the Mac native representation of this cursor 42 @param aCursor the cursor to create 43 @result the Mac native implementation of the cursor 44*/ 45+ (nsMacCursor*)createCursor:(enum nsCursor)aCursor; 46 47@end 48 49@implementation nsCursorManager 50 51+ (nsCursorManager*)sharedInstance { 52 NS_OBJC_BEGIN_TRY_BLOCK_RETURN; 53 54 if (!gInstance) { 55 gInstance = [[nsCursorManager alloc] init]; 56 } 57 return gInstance; 58 59 NS_OBJC_END_TRY_BLOCK_RETURN(nil); 60} 61 62+ (void)dispose { 63 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK; 64 65 [gInstance release]; 66 gInstance = nil; 67 68 NS_OBJC_END_TRY_IGNORE_BLOCK; 69} 70 71+ (nsMacCursor*)createCursor:(enum nsCursor)aCursor { 72 NS_OBJC_BEGIN_TRY_BLOCK_RETURN; 73 74 switch (aCursor) { 75 SEL cursorSelector; 76 case eCursor_standard: 77 return [nsMacCursor cursorWithCursor:[NSCursor arrowCursor] type:aCursor]; 78 case eCursor_wait: 79 case eCursor_spinning: { 80 return [nsMacCursor cursorWithCursor:[NSCursor busyButClickableCursor] type:aCursor]; 81 } 82 case eCursor_select: 83 return [nsMacCursor cursorWithCursor:[NSCursor IBeamCursor] type:aCursor]; 84 case eCursor_hyperlink: 85 return [nsMacCursor cursorWithCursor:[NSCursor pointingHandCursor] type:aCursor]; 86 case eCursor_crosshair: 87 return [nsMacCursor cursorWithCursor:[NSCursor crosshairCursor] type:aCursor]; 88 case eCursor_move: 89 return [nsMacCursor cursorWithImageNamed:@"move" hotSpot:NSMakePoint(12, 12) type:aCursor]; 90 case eCursor_help: 91 return [nsMacCursor cursorWithImageNamed:@"help" hotSpot:NSMakePoint(12, 12) type:aCursor]; 92 case eCursor_copy: 93 cursorSelector = @selector(dragCopyCursor); 94 return [nsMacCursor cursorWithCursor:[NSCursor respondsToSelector:cursorSelector] 95 ? [NSCursor performSelector:cursorSelector] 96 : [NSCursor arrowCursor] 97 type:aCursor]; 98 case eCursor_alias: 99 cursorSelector = @selector(dragLinkCursor); 100 return [nsMacCursor cursorWithCursor:[NSCursor respondsToSelector:cursorSelector] 101 ? [NSCursor performSelector:cursorSelector] 102 : [NSCursor arrowCursor] 103 type:aCursor]; 104 case eCursor_context_menu: 105 cursorSelector = @selector(contextualMenuCursor); 106 return [nsMacCursor cursorWithCursor:[NSCursor respondsToSelector:cursorSelector] 107 ? [NSCursor performSelector:cursorSelector] 108 : [NSCursor arrowCursor] 109 type:aCursor]; 110 case eCursor_cell: 111 return [nsMacCursor cursorWithImageNamed:@"cell" hotSpot:NSMakePoint(12, 12) type:aCursor]; 112 case eCursor_grab: 113 return [nsMacCursor cursorWithCursor:[NSCursor openHandCursor] type:aCursor]; 114 case eCursor_grabbing: 115 return [nsMacCursor cursorWithCursor:[NSCursor closedHandCursor] type:aCursor]; 116 case eCursor_zoom_in: 117 return [nsMacCursor cursorWithImageNamed:@"zoomIn" hotSpot:NSMakePoint(10, 10) type:aCursor]; 118 case eCursor_zoom_out: 119 return [nsMacCursor cursorWithImageNamed:@"zoomOut" hotSpot:NSMakePoint(10, 10) type:aCursor]; 120 case eCursor_vertical_text: 121 return [nsMacCursor cursorWithImageNamed:@"vtIBeam" hotSpot:NSMakePoint(12, 11) type:aCursor]; 122 case eCursor_all_scroll: 123 return [nsMacCursor cursorWithCursor:[NSCursor openHandCursor] type:aCursor]; 124 case eCursor_not_allowed: 125 case eCursor_no_drop: 126 cursorSelector = @selector(operationNotAllowedCursor); 127 return [nsMacCursor cursorWithCursor:[NSCursor respondsToSelector:cursorSelector] 128 ? [NSCursor performSelector:cursorSelector] 129 : [NSCursor arrowCursor] 130 type:aCursor]; 131 // Resize Cursors: 132 // North 133 case eCursor_n_resize: 134 return [nsMacCursor cursorWithCursor:[NSCursor resizeUpCursor] type:aCursor]; 135 // North East 136 case eCursor_ne_resize: 137 return [nsMacCursor cursorWithImageNamed:@"sizeNE" hotSpot:NSMakePoint(12, 11) type:aCursor]; 138 // East 139 case eCursor_e_resize: 140 return [nsMacCursor cursorWithCursor:[NSCursor resizeRightCursor] type:aCursor]; 141 // South East 142 case eCursor_se_resize: 143 return [nsMacCursor cursorWithImageNamed:@"sizeSE" hotSpot:NSMakePoint(12, 12) type:aCursor]; 144 // South 145 case eCursor_s_resize: 146 return [nsMacCursor cursorWithCursor:[NSCursor resizeDownCursor] type:aCursor]; 147 // South West 148 case eCursor_sw_resize: 149 return [nsMacCursor cursorWithImageNamed:@"sizeSW" hotSpot:NSMakePoint(10, 12) type:aCursor]; 150 // West 151 case eCursor_w_resize: 152 return [nsMacCursor cursorWithCursor:[NSCursor resizeLeftCursor] type:aCursor]; 153 // North West 154 case eCursor_nw_resize: 155 return [nsMacCursor cursorWithImageNamed:@"sizeNW" hotSpot:NSMakePoint(11, 11) type:aCursor]; 156 // North & South 157 case eCursor_ns_resize: 158 return [nsMacCursor cursorWithCursor:[NSCursor resizeUpDownCursor] type:aCursor]; 159 // East & West 160 case eCursor_ew_resize: 161 return [nsMacCursor cursorWithCursor:[NSCursor resizeLeftRightCursor] type:aCursor]; 162 // North East & South West 163 case eCursor_nesw_resize: 164 return [nsMacCursor cursorWithImageNamed:@"sizeNESW" 165 hotSpot:NSMakePoint(12, 12) 166 type:aCursor]; 167 // North West & South East 168 case eCursor_nwse_resize: 169 return [nsMacCursor cursorWithImageNamed:@"sizeNWSE" 170 hotSpot:NSMakePoint(12, 12) 171 type:aCursor]; 172 // Column Resize 173 case eCursor_col_resize: 174 return [nsMacCursor cursorWithImageNamed:@"colResize" 175 hotSpot:NSMakePoint(12, 12) 176 type:aCursor]; 177 // Row Resize 178 case eCursor_row_resize: 179 return [nsMacCursor cursorWithImageNamed:@"rowResize" 180 hotSpot:NSMakePoint(12, 12) 181 type:aCursor]; 182 default: 183 return [nsMacCursor cursorWithCursor:[NSCursor arrowCursor] type:aCursor]; 184 } 185 186 NS_OBJC_END_TRY_BLOCK_RETURN(nil); 187} 188 189- (id)init { 190 NS_OBJC_BEGIN_TRY_BLOCK_RETURN; 191 192 if ((self = [super init])) { 193 mCursors = [[NSMutableDictionary alloc] initWithCapacity:25]; 194 } 195 return self; 196 197 NS_OBJC_END_TRY_BLOCK_RETURN(nil); 198} 199 200- (nsresult)setNonCustomCursor:(const nsIWidget::Cursor&)aCursor { 201 NS_OBJC_BEGIN_TRY_BLOCK_RETURN; 202 203 [self setMacCursor:[self getCursor:aCursor.mDefaultCursor]]; 204 205 sCurrentCursor = aCursor; 206 return NS_OK; 207 208 NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE); 209} 210 211- (nsresult)setMacCursor:(nsMacCursor*)aMacCursor { 212 NS_OBJC_BEGIN_TRY_BLOCK_RETURN; 213 214 nsCursor oldType = [mCurrentMacCursor type]; 215 nsCursor newType = [aMacCursor type]; 216 if (oldType != newType) { 217 if (newType == eCursor_none) { 218 [NSCursor hide]; 219 } else if (oldType == eCursor_none) { 220 [NSCursor unhide]; 221 } 222 } 223 224 if (mCurrentMacCursor != aMacCursor || ![mCurrentMacCursor isSet]) { 225 [aMacCursor retain]; 226 [mCurrentMacCursor unset]; 227 [aMacCursor set]; 228 [mCurrentMacCursor release]; 229 mCurrentMacCursor = aMacCursor; 230 } 231 232 return NS_OK; 233 234 NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE); 235} 236 237- (nsresult)setCustomCursor:(const nsIWidget::Cursor&)aCursor 238 widgetScaleFactor:(CGFloat)scaleFactor { 239 NS_OBJC_BEGIN_TRY_BLOCK_RETURN; 240 241 // As the user moves the mouse, this gets called repeatedly with the same aCursorImage 242 if (sCurrentCursor == aCursor && sCurrentCursorScaleFactor == scaleFactor && mCurrentMacCursor) { 243 // Native dragging can unset our cursor apparently (see bug 1739352). 244 if (MOZ_UNLIKELY(![mCurrentMacCursor isSet])) { 245 [mCurrentMacCursor set]; 246 } 247 return NS_OK; 248 } 249 250 sCurrentCursor = aCursor; 251 sCurrentCursorScaleFactor = scaleFactor; 252 253 if (!aCursor.IsCustom()) { 254 return NS_ERROR_FAILURE; 255 } 256 257 nsIntSize size = nsIWidget::CustomCursorSize(aCursor); 258 // prevent DoS attacks 259 if (size.width > 128 || size.height > 128) { 260 return NS_ERROR_FAILURE; 261 } 262 263 NSImage* cursorImage; 264 nsresult rv = nsCocoaUtils::CreateNSImageFromImageContainer( 265 aCursor.mContainer, imgIContainer::FRAME_FIRST, nullptr, &cursorImage, scaleFactor); 266 if (NS_FAILED(rv) || !cursorImage) { 267 return NS_ERROR_FAILURE; 268 } 269 270 { 271 NSSize cocoaSize = NSMakeSize(size.width, size.height); 272 [cursorImage setSize:cocoaSize]; 273 [[[cursorImage representations] objectAtIndex:0] setSize:cocoaSize]; 274 } 275 276 // if the hotspot is nonsensical, make it 0,0 277 uint32_t hotspotX = aCursor.mHotspotX > (uint32_t(size.width) - 1) ? 0 : aCursor.mHotspotX; 278 uint32_t hotspotY = aCursor.mHotspotY > (uint32_t(size.height) - 1) ? 0 : aCursor.mHotspotY; 279 NSPoint hotSpot = ::NSMakePoint(hotspotX, hotspotY); 280 [self setMacCursor:[nsMacCursor cursorWithCursor:[[NSCursor alloc] initWithImage:cursorImage 281 hotSpot:hotSpot] 282 type:kCustomCursor]]; 283 [cursorImage release]; 284 return NS_OK; 285 286 NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE); 287} 288 289- (nsMacCursor*)getCursor:(enum nsCursor)aCursor { 290 NS_OBJC_BEGIN_TRY_BLOCK_RETURN; 291 292 nsMacCursor* result = [mCursors objectForKey:[NSNumber numberWithInt:aCursor]]; 293 if (!result) { 294 result = [nsCursorManager createCursor:aCursor]; 295 [mCursors setObject:result forKey:[NSNumber numberWithInt:aCursor]]; 296 } 297 return result; 298 299 NS_OBJC_END_TRY_BLOCK_RETURN(nil); 300} 301 302- (void)dealloc { 303 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK; 304 305 [mCurrentMacCursor unset]; 306 [mCurrentMacCursor release]; 307 [mCursors release]; 308 sCurrentCursor = {}; 309 [super dealloc]; 310 311 NS_OBJC_END_TRY_IGNORE_BLOCK; 312} 313 314@end 315