1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "webkit/glue/webcursor.h"
7 #import <AppKit/AppKit.h>
8 #include <Carbon/Carbon.h>
10 #include "base/logging.h"
11 #include "base/mac/mac_util.h"
12 #include "base/mac/scoped_cftyperef.h"
13 #include "base/memory/scoped_nsobject.h"
14 #include "grit/webkit_chromium_resources.h"
15 #include "skia/ext/skia_utils_mac.h"
16 #include "third_party/WebKit/Source/WebKit/chromium/public/WebCursorInfo.h"
17 #include "third_party/WebKit/Source/WebKit/chromium/public/platform/WebImage.h"
18 #include "third_party/WebKit/Source/WebKit/chromium/public/platform/WebSize.h"
19 #include "ui/base/resource/resource_bundle.h"
22 #if defined(MAC_OS_X_VERSION_10_7) && \
23 MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7
24 // The 10.7 SDK no longer has QuickDraw headers.
25 // http://developer.apple.com/legacy/mac/library/documentation/Carbon/reference/QuickDraw_Ref/QuickDraw_Ref.pdf
26 typedef short Bits16[16];
34 using WebKit::WebCursorInfo;
35 using WebKit::WebImage;
36 using WebKit::WebSize;
38 // Declare symbols that are part of the 10.7 SDK.
39 #if !defined(MAC_OS_X_VERSION_10_7) || \
40 MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7
42 @interface NSCursor (LionSDKDeclarations)
43 + (NSCursor*)IBeamCursorForVerticalLayout;
46 #endif // MAC_OS_X_VERSION_10_7
48 // Private interface to CoreCursor, as of Mac OS X 10.7. This is essentially the
49 // implementation of WKCursor in WebKitSystemInterface.
55 kOperationNotAllowedCursor = 3,
56 kBusyButClickableCursor = 4,
58 kClosedHandCursor = 11,
60 kPointingHandCursor = 13,
61 kCountingUpHandCursor = 14,
62 kCountingDownHandCursor = 15,
63 kCountingUpAndDownHandCursor = 16,
64 kResizeLeftCursor = 17,
65 kResizeRightCursor = 18,
66 kResizeLeftRightCursor = 19,
67 kCrosshairCursor = 20,
69 kResizeDownCursor = 22,
70 kResizeUpDownCursor = 23,
71 kContextualMenuCursor = 24,
72 kDisappearingItemCursor = 25,
73 kVerticalIBeamCursor = 26,
74 kResizeEastCursor = 27,
75 kResizeEastWestCursor = 28,
76 kResizeNortheastCursor = 29,
77 kResizeNortheastSouthwestCursor = 30,
78 kResizeNorthCursor = 31,
79 kResizeNorthSouthCursor = 32,
80 kResizeNorthwestCursor = 33,
81 kResizeNorthwestSoutheastCursor = 34,
82 kResizeSoutheastCursor = 35,
83 kResizeSouthCursor = 36,
84 kResizeSouthwestCursor = 37,
85 kResizeWestCursor = 38,
87 kHelpCursor = 40, // Present on >= 10.7.3.
88 kCellCursor = 41, // Present on >= 10.7.3.
89 kZoomInCursor = 42, // Present on >= 10.7.3.
90 kZoomOutCursor = 43 // Present on >= 10.7.3.
92 typedef long long CrCoreCursorType;
94 @interface CrCoreCursor : NSCursor {
96 CrCoreCursorType type_;
99 + (id)cursorWithType:(CrCoreCursorType)type;
100 - (id)initWithType:(CrCoreCursorType)type;
101 - (CrCoreCursorType)_coreCursorType;
105 @implementation CrCoreCursor
107 + (id)cursorWithType:(CrCoreCursorType)type {
108 NSCursor* cursor = [[CrCoreCursor alloc] initWithType:type];
110 return [cursor autorelease];
116 - (id)initWithType:(CrCoreCursorType)type {
117 if ((self = [super init])) {
123 - (CrCoreCursorType)_coreCursorType {
131 NSCursor* LoadCursor(int resource_id, int hotspot_x, int hotspot_y) {
132 const gfx::Image& cursor_image =
133 ResourceBundle::GetSharedInstance().GetNativeImageNamed(resource_id);
134 DCHECK(!cursor_image.IsEmpty());
135 return [[[NSCursor alloc] initWithImage:cursor_image.ToNSImage()
136 hotSpot:NSMakePoint(hotspot_x,
137 hotspot_y)] autorelease];
140 // Gets a specified cursor from CoreCursor, falling back to loading it from the
141 // image cache if CoreCursor cannot provide it.
142 NSCursor* GetCoreCursorWithFallback(CrCoreCursorType type,
146 if (base::mac::IsOSLionOrLater()) {
147 NSCursor* cursor = [CrCoreCursor cursorWithType:type];
152 return LoadCursor(resource_id, hotspot_x, hotspot_y);
155 // TODO(avi): When Skia becomes default, fold this function into the remaining
156 // caller, InitFromCursor().
157 CGImageRef CreateCGImageFromCustomData(const std::vector<char>& custom_data,
158 const gfx::Size& custom_size) {
159 // If the data is missing, leave the backing transparent.
161 if (!custom_data.empty()) {
162 // This is safe since we're not going to draw into the context we're
164 data = const_cast<char*>(&custom_data[0]);
167 // If the size is empty, use a 1x1 transparent image.
168 gfx::Size size = custom_size;
169 if (size.IsEmpty()) {
174 base::mac::ScopedCFTypeRef<CGColorSpaceRef> cg_color(
175 CGColorSpaceCreateDeviceRGB());
176 // The settings here match SetCustomData() below; keep in sync.
177 base::mac::ScopedCFTypeRef<CGContextRef> context(
178 CGBitmapContextCreate(data,
184 kCGImageAlphaPremultipliedLast |
185 kCGBitmapByteOrder32Big));
186 return CGBitmapContextCreateImage(context.get());
189 NSCursor* CreateCustomCursor(const std::vector<char>& custom_data,
190 const gfx::Size& custom_size,
191 const gfx::Point& hotspot) {
192 // If the data is missing, leave the backing transparent.
194 size_t data_size = 0;
195 if (!custom_data.empty()) {
196 // This is safe since we're not going to draw into the context we're
198 data = const_cast<char*>(&custom_data[0]);
199 data_size = custom_data.size();
202 // If the size is empty, use a 1x1 transparent image.
203 gfx::Size size = custom_size;
204 if (size.IsEmpty()) {
210 bitmap.setConfig(SkBitmap::kARGB_8888_Config, size.width(), size.height());
211 bitmap.allocPixels();
213 memcpy(bitmap.getAddr32(0, 0), data, data_size);
215 bitmap.eraseARGB(0, 0, 0, 0);
216 NSImage* cursor_image = gfx::SkBitmapToNSImage(bitmap);
218 NSCursor* cursor = [[NSCursor alloc] initWithImage:cursor_image
219 hotSpot:NSMakePoint(hotspot.x(),
222 return [cursor autorelease];
227 // Match Safari's cursor choices; see platform/mac/CursorMac.mm .
228 gfx::NativeCursor WebCursor::GetNativeCursor() {
230 case WebCursorInfo::TypePointer:
231 return [NSCursor arrowCursor];
232 case WebCursorInfo::TypeCross:
233 return [NSCursor crosshairCursor];
234 case WebCursorInfo::TypeHand:
235 // If >= 10.7, the pointingHandCursor has a shadow so use it. Otherwise
236 // use the custom one.
237 if (base::mac::IsOSLionOrLater())
238 return [NSCursor pointingHandCursor];
240 return LoadCursor(IDR_LINK_CURSOR, 6, 1);
241 case WebCursorInfo::TypeIBeam:
242 return [NSCursor IBeamCursor];
243 case WebCursorInfo::TypeWait:
244 return GetCoreCursorWithFallback(kBusyButClickableCursor,
245 IDR_WAIT_CURSOR, 7, 7);
246 case WebCursorInfo::TypeHelp:
247 return GetCoreCursorWithFallback(kHelpCursor,
248 IDR_HELP_CURSOR, 8, 8);
249 case WebCursorInfo::TypeEastResize:
250 case WebCursorInfo::TypeEastPanning:
251 return GetCoreCursorWithFallback(kResizeEastCursor,
252 IDR_EAST_RESIZE_CURSOR, 14, 7);
253 case WebCursorInfo::TypeNorthResize:
254 case WebCursorInfo::TypeNorthPanning:
255 return GetCoreCursorWithFallback(kResizeNorthCursor,
256 IDR_NORTH_RESIZE_CURSOR, 7, 1);
257 case WebCursorInfo::TypeNorthEastResize:
258 case WebCursorInfo::TypeNorthEastPanning:
259 return GetCoreCursorWithFallback(kResizeNortheastCursor,
260 IDR_NORTHEAST_RESIZE_CURSOR, 14, 1);
261 case WebCursorInfo::TypeNorthWestResize:
262 case WebCursorInfo::TypeNorthWestPanning:
263 return GetCoreCursorWithFallback(kResizeNorthwestCursor,
264 IDR_NORTHWEST_RESIZE_CURSOR, 0, 0);
265 case WebCursorInfo::TypeSouthResize:
266 case WebCursorInfo::TypeSouthPanning:
267 return GetCoreCursorWithFallback(kResizeSouthCursor,
268 IDR_SOUTH_RESIZE_CURSOR, 7, 14);
269 case WebCursorInfo::TypeSouthEastResize:
270 case WebCursorInfo::TypeSouthEastPanning:
271 return GetCoreCursorWithFallback(kResizeSoutheastCursor,
272 IDR_SOUTHEAST_RESIZE_CURSOR, 14, 14);
273 case WebCursorInfo::TypeSouthWestResize:
274 case WebCursorInfo::TypeSouthWestPanning:
275 return GetCoreCursorWithFallback(kResizeSouthwestCursor,
276 IDR_SOUTHWEST_RESIZE_CURSOR, 1, 14);
277 case WebCursorInfo::TypeWestResize:
278 case WebCursorInfo::TypeWestPanning:
279 return GetCoreCursorWithFallback(kResizeWestCursor,
280 IDR_WEST_RESIZE_CURSOR, 1, 7);
281 case WebCursorInfo::TypeNorthSouthResize:
282 return GetCoreCursorWithFallback(kResizeNorthSouthCursor,
283 IDR_NORTHSOUTH_RESIZE_CURSOR, 7, 7);
284 case WebCursorInfo::TypeEastWestResize:
285 return GetCoreCursorWithFallback(kResizeEastWestCursor,
286 IDR_EASTWEST_RESIZE_CURSOR, 7, 7);
287 case WebCursorInfo::TypeNorthEastSouthWestResize:
288 return GetCoreCursorWithFallback(kResizeNortheastSouthwestCursor,
289 IDR_NORTHEASTSOUTHWEST_RESIZE_CURSOR,
291 case WebCursorInfo::TypeNorthWestSouthEastResize:
292 return GetCoreCursorWithFallback(kResizeNorthwestSoutheastCursor,
293 IDR_NORTHWESTSOUTHEAST_RESIZE_CURSOR,
295 case WebCursorInfo::TypeColumnResize:
296 return [NSCursor resizeLeftRightCursor];
297 case WebCursorInfo::TypeRowResize:
298 return [NSCursor resizeUpDownCursor];
299 case WebCursorInfo::TypeMiddlePanning:
300 case WebCursorInfo::TypeMove:
301 return GetCoreCursorWithFallback(kMoveCursor,
302 IDR_MOVE_CURSOR, 7, 7);
303 case WebCursorInfo::TypeVerticalText:
304 // IBeamCursorForVerticalLayout is >= 10.7.
305 if ([NSCursor respondsToSelector:@selector(IBeamCursorForVerticalLayout)])
306 return [NSCursor IBeamCursorForVerticalLayout];
308 return LoadCursor(IDR_VERTICALTEXT_CURSOR, 7, 7);
309 case WebCursorInfo::TypeCell:
310 return GetCoreCursorWithFallback(kCellCursor,
311 IDR_CELL_CURSOR, 7, 7);
312 case WebCursorInfo::TypeContextMenu:
313 // contextualMenuCursor is >= 10.6.
314 if ([NSCursor respondsToSelector:@selector(contextualMenuCursor)])
315 return [NSCursor contextualMenuCursor];
317 return LoadCursor(IDR_CONTEXTMENU_CURSOR, 3, 2);
318 case WebCursorInfo::TypeAlias:
319 return GetCoreCursorWithFallback(kMakeAliasCursor,
320 IDR_ALIAS_CURSOR, 11, 3);
321 case WebCursorInfo::TypeProgress:
322 return GetCoreCursorWithFallback(kBusyButClickableCursor,
323 IDR_PROGRESS_CURSOR, 3, 2);
324 case WebCursorInfo::TypeNoDrop:
325 case WebCursorInfo::TypeNotAllowed:
326 // Docs say that operationNotAllowedCursor is >= 10.6, and it's not in the
327 // 10.5 SDK, but later SDKs note that it really is available on 10.5.
328 return [NSCursor operationNotAllowedCursor];
329 case WebCursorInfo::TypeCopy:
330 // dragCopyCursor is >= 10.6.
331 if ([NSCursor respondsToSelector:@selector(dragCopyCursor)])
332 return [NSCursor dragCopyCursor];
334 return LoadCursor(IDR_COPY_CURSOR, 3, 2);
335 case WebCursorInfo::TypeNone:
336 return LoadCursor(IDR_NONE_CURSOR, 7, 7);
337 case WebCursorInfo::TypeZoomIn:
338 return GetCoreCursorWithFallback(kZoomInCursor,
339 IDR_ZOOMIN_CURSOR, 7, 7);
340 case WebCursorInfo::TypeZoomOut:
341 return GetCoreCursorWithFallback(kZoomOutCursor,
342 IDR_ZOOMOUT_CURSOR, 7, 7);
343 case WebCursorInfo::TypeGrab:
344 return [NSCursor openHandCursor];
345 case WebCursorInfo::TypeGrabbing:
346 return [NSCursor closedHandCursor];
347 case WebCursorInfo::TypeCustom:
348 return CreateCustomCursor(custom_data_, custom_size_, hotspot_);
354 void WebCursor::InitFromThemeCursor(ThemeCursor cursor) {
355 WebKit::WebCursorInfo cursor_info;
358 case kThemeArrowCursor:
359 cursor_info.type = WebCursorInfo::TypePointer;
361 case kThemeCopyArrowCursor:
362 cursor_info.type = WebCursorInfo::TypeCopy;
364 case kThemeAliasArrowCursor:
365 cursor_info.type = WebCursorInfo::TypeAlias;
367 case kThemeContextualMenuArrowCursor:
368 cursor_info.type = WebCursorInfo::TypeContextMenu;
370 case kThemeIBeamCursor:
371 cursor_info.type = WebCursorInfo::TypeIBeam;
373 case kThemeCrossCursor:
374 case kThemePlusCursor:
375 cursor_info.type = WebCursorInfo::TypeCross;
377 case kThemeWatchCursor:
378 case kThemeSpinningCursor:
379 cursor_info.type = WebCursorInfo::TypeWait;
381 case kThemeClosedHandCursor:
382 cursor_info.type = WebCursorInfo::TypeGrabbing;
384 case kThemeOpenHandCursor:
385 cursor_info.type = WebCursorInfo::TypeGrab;
387 case kThemePointingHandCursor:
388 case kThemeCountingUpHandCursor:
389 case kThemeCountingDownHandCursor:
390 case kThemeCountingUpAndDownHandCursor:
391 cursor_info.type = WebCursorInfo::TypeHand;
393 case kThemeResizeLeftCursor:
394 cursor_info.type = WebCursorInfo::TypeWestResize;
396 case kThemeResizeRightCursor:
397 cursor_info.type = WebCursorInfo::TypeEastResize;
399 case kThemeResizeLeftRightCursor:
400 cursor_info.type = WebCursorInfo::TypeEastWestResize;
402 case kThemeNotAllowedCursor:
403 cursor_info.type = WebCursorInfo::TypeNotAllowed;
405 case kThemeResizeUpCursor:
406 cursor_info.type = WebCursorInfo::TypeNorthResize;
408 case kThemeResizeDownCursor:
409 cursor_info.type = WebCursorInfo::TypeSouthResize;
411 case kThemeResizeUpDownCursor:
412 cursor_info.type = WebCursorInfo::TypeNorthSouthResize;
414 case kThemePoofCursor: // *shrug*
416 cursor_info.type = WebCursorInfo::TypePointer;
420 InitFromCursorInfo(cursor_info);
423 void WebCursor::InitFromCursor(const Cursor* cursor) {
424 // This conversion isn't perfect (in particular, the inversion effect of
425 // data==1, mask==0 won't work). Not planning on fixing it.
427 gfx::Size custom_size(16, 16);
428 std::vector<char> raw_data;
429 for (int row = 0; row < 16; ++row) {
430 unsigned short data = cursor->data[row];
431 unsigned short mask = cursor->mask[row];
433 // The Core Endian flipper callback for 'CURS' doesn't flip Bits16 as if it
434 // were a short (which it is), so we flip it here.
435 data = ((data << 8) & 0xFF00) | ((data >> 8) & 0x00FF);
436 mask = ((mask << 8) & 0xFF00) | ((mask >> 8) & 0x00FF);
438 for (int bit = 0; bit < 16; ++bit) {
440 raw_data.push_back(0x00);
441 raw_data.push_back(0x00);
442 raw_data.push_back(0x00);
444 raw_data.push_back(0xFF);
445 raw_data.push_back(0xFF);
446 raw_data.push_back(0xFF);
449 raw_data.push_back(0xFF);
451 raw_data.push_back(0x00);
457 base::mac::ScopedCFTypeRef<CGImageRef> cg_image(
458 CreateCGImageFromCustomData(raw_data, custom_size));
460 WebKit::WebCursorInfo cursor_info;
461 cursor_info.type = WebCursorInfo::TypeCustom;
462 cursor_info.hotSpot = WebKit::WebPoint(cursor->hotSpot.h, cursor->hotSpot.v);
463 // TODO(avi): build the cursor image in Skia directly rather than going via
464 // this roundabout path.
465 cursor_info.customImage = gfx::CGImageToSkBitmap(cg_image.get());
467 InitFromCursorInfo(cursor_info);
470 void WebCursor::InitFromNSCursor(NSCursor* cursor) {
471 WebKit::WebCursorInfo cursor_info;
473 if ([cursor isEqual:[NSCursor arrowCursor]]) {
474 cursor_info.type = WebCursorInfo::TypePointer;
475 } else if ([cursor isEqual:[NSCursor IBeamCursor]]) {
476 cursor_info.type = WebCursorInfo::TypeIBeam;
477 } else if ([cursor isEqual:[NSCursor crosshairCursor]]) {
478 cursor_info.type = WebCursorInfo::TypeCross;
479 } else if ([cursor isEqual:[NSCursor pointingHandCursor]]) {
480 cursor_info.type = WebCursorInfo::TypeHand;
481 } else if ([cursor isEqual:[NSCursor resizeLeftCursor]]) {
482 cursor_info.type = WebCursorInfo::TypeWestResize;
483 } else if ([cursor isEqual:[NSCursor resizeRightCursor]]) {
484 cursor_info.type = WebCursorInfo::TypeEastResize;
485 } else if ([cursor isEqual:[NSCursor resizeLeftRightCursor]]) {
486 cursor_info.type = WebCursorInfo::TypeEastWestResize;
487 } else if ([cursor isEqual:[NSCursor resizeUpCursor]]) {
488 cursor_info.type = WebCursorInfo::TypeNorthResize;
489 } else if ([cursor isEqual:[NSCursor resizeDownCursor]]) {
490 cursor_info.type = WebCursorInfo::TypeSouthResize;
491 } else if ([cursor isEqual:[NSCursor resizeUpDownCursor]]) {
492 cursor_info.type = WebCursorInfo::TypeNorthSouthResize;
493 } else if ([cursor isEqual:[NSCursor openHandCursor]]) {
494 cursor_info.type = WebCursorInfo::TypeGrab;
495 } else if ([cursor isEqual:[NSCursor closedHandCursor]]) {
496 cursor_info.type = WebCursorInfo::TypeGrabbing;
497 } else if ([cursor isEqual:[NSCursor operationNotAllowedCursor]]) {
498 cursor_info.type = WebCursorInfo::TypeNotAllowed;
499 } else if ([NSCursor respondsToSelector:@selector(dragCopyCursor)] &&
500 [cursor isEqual:[NSCursor dragCopyCursor]]) {
501 cursor_info.type = WebCursorInfo::TypeCopy;
502 } else if ([NSCursor respondsToSelector:@selector(contextualMenuCursor)] &&
503 [cursor isEqual:[NSCursor contextualMenuCursor]]) {
504 cursor_info.type = WebCursorInfo::TypeContextMenu;
506 [NSCursor respondsToSelector:@selector(IBeamCursorForVerticalLayout)] &&
507 [cursor isEqual:[NSCursor IBeamCursorForVerticalLayout]]) {
508 cursor_info.type = WebCursorInfo::TypeVerticalText;
510 // Also handles the [NSCursor disappearingItemCursor] case. Quick-and-dirty
511 // image conversion; TODO(avi): do better.
512 CGImageRef cg_image = nil;
513 NSImage* image = [cursor image];
514 for (id rep in [image representations]) {
515 if ([rep isKindOfClass:[NSBitmapImageRep class]]) {
516 cg_image = [rep CGImage];
522 cursor_info.type = WebCursorInfo::TypeCustom;
523 NSPoint hot_spot = [cursor hotSpot];
524 cursor_info.hotSpot = WebKit::WebPoint(hot_spot.x, hot_spot.y);
525 cursor_info.customImage = gfx::CGImageToSkBitmap(cg_image);
527 cursor_info.type = WebCursorInfo::TypePointer;
531 InitFromCursorInfo(cursor_info);
534 void WebCursor::InitPlatformData() {
538 bool WebCursor::SerializePlatformData(Pickle* pickle) const {
542 bool WebCursor::DeserializePlatformData(PickleIterator* iter) {
546 bool WebCursor::IsPlatformDataEqual(const WebCursor& other) const {
550 void WebCursor::CleanupPlatformData() {
554 void WebCursor::CopyPlatformData(const WebCursor& other) {