widget/cocoa/nsCocoaUtils.mm

changeset 2
7e26c7da4463
equal deleted inserted replaced
-1:000000000000 0:7416781ae729
1 /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5
6 #include "gfxImageSurface.h"
7 #include "gfxPlatform.h"
8 #include "gfxUtils.h"
9 #include "nsCocoaUtils.h"
10 #include "nsChildView.h"
11 #include "nsMenuBarX.h"
12 #include "nsCocoaWindow.h"
13 #include "nsCOMPtr.h"
14 #include "nsIInterfaceRequestorUtils.h"
15 #include "nsIAppShellService.h"
16 #include "nsIXULWindow.h"
17 #include "nsIBaseWindow.h"
18 #include "nsIServiceManager.h"
19 #include "nsMenuUtilsX.h"
20 #include "nsToolkit.h"
21 #include "nsCRT.h"
22 #include "mozilla/gfx/2D.h"
23 #include "mozilla/MiscEvents.h"
24 #include "mozilla/Preferences.h"
25 #include "mozilla/TextEvents.h"
26
27 using namespace mozilla;
28 using namespace mozilla::widget;
29
30 using mozilla::gfx::BackendType;
31 using mozilla::gfx::DataSourceSurface;
32 using mozilla::gfx::DrawTarget;
33 using mozilla::gfx::Factory;
34 using mozilla::gfx::IntPoint;
35 using mozilla::gfx::IntRect;
36 using mozilla::gfx::IntSize;
37 using mozilla::gfx::SurfaceFormat;
38 using mozilla::gfx::SourceSurface;
39
40 static float
41 MenuBarScreenHeight()
42 {
43 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
44
45 NSArray* allScreens = [NSScreen screens];
46 if ([allScreens count]) {
47 return [[allScreens objectAtIndex:0] frame].size.height;
48 }
49
50 return 0.0;
51
52 NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(0.0);
53 }
54
55 float
56 nsCocoaUtils::FlippedScreenY(float y)
57 {
58 return MenuBarScreenHeight() - y;
59 }
60
61 NSRect nsCocoaUtils::GeckoRectToCocoaRect(const nsIntRect &geckoRect)
62 {
63 // We only need to change the Y coordinate by starting with the primary screen
64 // height and subtracting the gecko Y coordinate of the bottom of the rect.
65 return NSMakeRect(geckoRect.x,
66 MenuBarScreenHeight() - geckoRect.YMost(),
67 geckoRect.width,
68 geckoRect.height);
69 }
70
71 NSRect nsCocoaUtils::GeckoRectToCocoaRectDevPix(const nsIntRect &aGeckoRect,
72 CGFloat aBackingScale)
73 {
74 return NSMakeRect(aGeckoRect.x / aBackingScale,
75 MenuBarScreenHeight() - aGeckoRect.YMost() / aBackingScale,
76 aGeckoRect.width / aBackingScale,
77 aGeckoRect.height / aBackingScale);
78 }
79
80 nsIntRect nsCocoaUtils::CocoaRectToGeckoRect(const NSRect &cocoaRect)
81 {
82 // We only need to change the Y coordinate by starting with the primary screen
83 // height and subtracting both the cocoa y origin and the height of the
84 // cocoa rect.
85 nsIntRect rect;
86 rect.x = NSToIntRound(cocoaRect.origin.x);
87 rect.y = NSToIntRound(FlippedScreenY(cocoaRect.origin.y + cocoaRect.size.height));
88 rect.width = NSToIntRound(cocoaRect.origin.x + cocoaRect.size.width) - rect.x;
89 rect.height = NSToIntRound(FlippedScreenY(cocoaRect.origin.y)) - rect.y;
90 return rect;
91 }
92
93 nsIntRect nsCocoaUtils::CocoaRectToGeckoRectDevPix(const NSRect &aCocoaRect,
94 CGFloat aBackingScale)
95 {
96 nsIntRect rect;
97 rect.x = NSToIntRound(aCocoaRect.origin.x * aBackingScale);
98 rect.y = NSToIntRound(FlippedScreenY(aCocoaRect.origin.y + aCocoaRect.size.height) * aBackingScale);
99 rect.width = NSToIntRound((aCocoaRect.origin.x + aCocoaRect.size.width) * aBackingScale) - rect.x;
100 rect.height = NSToIntRound(FlippedScreenY(aCocoaRect.origin.y) * aBackingScale) - rect.y;
101 return rect;
102 }
103
104 NSPoint nsCocoaUtils::ScreenLocationForEvent(NSEvent* anEvent)
105 {
106 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
107
108 // Don't trust mouse locations of mouse move events, see bug 443178.
109 if (!anEvent || [anEvent type] == NSMouseMoved)
110 return [NSEvent mouseLocation];
111
112 // Pin momentum scroll events to the location of the last user-controlled
113 // scroll event.
114 if (IsMomentumScrollEvent(anEvent))
115 return ChildViewMouseTracker::sLastScrollEventScreenLocation;
116
117 return [[anEvent window] convertBaseToScreen:[anEvent locationInWindow]];
118
119 NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NSMakePoint(0.0, 0.0));
120 }
121
122 BOOL nsCocoaUtils::IsEventOverWindow(NSEvent* anEvent, NSWindow* aWindow)
123 {
124 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
125
126 return NSPointInRect(ScreenLocationForEvent(anEvent), [aWindow frame]);
127
128 NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NO);
129 }
130
131 NSPoint nsCocoaUtils::EventLocationForWindow(NSEvent* anEvent, NSWindow* aWindow)
132 {
133 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
134
135 return [aWindow convertScreenToBase:ScreenLocationForEvent(anEvent)];
136
137 NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NSMakePoint(0.0, 0.0));
138 }
139
140 BOOL nsCocoaUtils::IsMomentumScrollEvent(NSEvent* aEvent)
141 {
142 if ([aEvent type] != NSScrollWheel)
143 return NO;
144
145 if ([aEvent respondsToSelector:@selector(momentumPhase)])
146 return ([aEvent momentumPhase] & NSEventPhaseChanged) != 0;
147
148 if ([aEvent respondsToSelector:@selector(_scrollPhase)])
149 return [aEvent _scrollPhase] != 0;
150
151 return NO;
152 }
153
154 void nsCocoaUtils::HideOSChromeOnScreen(bool aShouldHide, NSScreen* aScreen)
155 {
156 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
157
158 // Keep track of how many hiding requests have been made, so that they can
159 // be nested.
160 static int sMenuBarHiddenCount = 0, sDockHiddenCount = 0;
161
162 // Always hide the Dock, since it's not necessarily on the primary screen.
163 sDockHiddenCount += aShouldHide ? 1 : -1;
164 NS_ASSERTION(sMenuBarHiddenCount >= 0, "Unbalanced HideMenuAndDockForWindow calls");
165
166 // Only hide the menu bar if the window is on the same screen.
167 // The menu bar is always on the first screen in the screen list.
168 if (aScreen == [[NSScreen screens] objectAtIndex:0]) {
169 sMenuBarHiddenCount += aShouldHide ? 1 : -1;
170 NS_ASSERTION(sDockHiddenCount >= 0, "Unbalanced HideMenuAndDockForWindow calls");
171 }
172
173 // TODO This should be upgraded to use [NSApplication setPresentationOptions:]
174 // when support for 10.5 is dropped.
175 if (sMenuBarHiddenCount > 0) {
176 ::SetSystemUIMode(kUIModeAllHidden, 0);
177 } else if (sDockHiddenCount > 0) {
178 ::SetSystemUIMode(kUIModeContentHidden, 0);
179 } else {
180 ::SetSystemUIMode(kUIModeNormal, 0);
181 }
182
183 NS_OBJC_END_TRY_ABORT_BLOCK;
184 }
185
186
187 #define NS_APPSHELLSERVICE_CONTRACTID "@mozilla.org/appshell/appShellService;1"
188 nsIWidget* nsCocoaUtils::GetHiddenWindowWidget()
189 {
190 nsCOMPtr<nsIAppShellService> appShell(do_GetService(NS_APPSHELLSERVICE_CONTRACTID));
191 if (!appShell) {
192 NS_WARNING("Couldn't get AppShellService in order to get hidden window ref");
193 return nullptr;
194 }
195
196 nsCOMPtr<nsIXULWindow> hiddenWindow;
197 appShell->GetHiddenWindow(getter_AddRefs(hiddenWindow));
198 if (!hiddenWindow) {
199 // Don't warn, this happens during shutdown, bug 358607.
200 return nullptr;
201 }
202
203 nsCOMPtr<nsIBaseWindow> baseHiddenWindow;
204 baseHiddenWindow = do_GetInterface(hiddenWindow);
205 if (!baseHiddenWindow) {
206 NS_WARNING("Couldn't get nsIBaseWindow from hidden window (nsIXULWindow)");
207 return nullptr;
208 }
209
210 nsCOMPtr<nsIWidget> hiddenWindowWidget;
211 if (NS_FAILED(baseHiddenWindow->GetMainWidget(getter_AddRefs(hiddenWindowWidget)))) {
212 NS_WARNING("Couldn't get nsIWidget from hidden window (nsIBaseWindow)");
213 return nullptr;
214 }
215
216 return hiddenWindowWidget;
217 }
218
219 void nsCocoaUtils::PrepareForNativeAppModalDialog()
220 {
221 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
222
223 // Don't do anything if this is embedding. We'll assume that if there is no hidden
224 // window we shouldn't do anything, and that should cover the embedding case.
225 nsMenuBarX* hiddenWindowMenuBar = nsMenuUtilsX::GetHiddenWindowMenuBar();
226 if (!hiddenWindowMenuBar)
227 return;
228
229 // First put up the hidden window menu bar so that app menu event handling is correct.
230 hiddenWindowMenuBar->Paint();
231
232 NSMenu* mainMenu = [NSApp mainMenu];
233 NS_ASSERTION([mainMenu numberOfItems] > 0, "Main menu does not have any items, something is terribly wrong!");
234
235 // Create new menu bar for use with modal dialog
236 NSMenu* newMenuBar = [[NSMenu alloc] initWithTitle:@""];
237
238 // Swap in our app menu. Note that the event target is whatever window is up when
239 // the app modal dialog goes up.
240 NSMenuItem* firstMenuItem = [[mainMenu itemAtIndex:0] retain];
241 [mainMenu removeItemAtIndex:0];
242 [newMenuBar insertItem:firstMenuItem atIndex:0];
243 [firstMenuItem release];
244
245 // Add standard edit menu
246 [newMenuBar addItem:nsMenuUtilsX::GetStandardEditMenuItem()];
247
248 // Show the new menu bar
249 [NSApp setMainMenu:newMenuBar];
250 [newMenuBar release];
251
252 NS_OBJC_END_TRY_ABORT_BLOCK;
253 }
254
255 void nsCocoaUtils::CleanUpAfterNativeAppModalDialog()
256 {
257 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
258
259 // Don't do anything if this is embedding. We'll assume that if there is no hidden
260 // window we shouldn't do anything, and that should cover the embedding case.
261 nsMenuBarX* hiddenWindowMenuBar = nsMenuUtilsX::GetHiddenWindowMenuBar();
262 if (!hiddenWindowMenuBar)
263 return;
264
265 NSWindow* mainWindow = [NSApp mainWindow];
266 if (!mainWindow)
267 hiddenWindowMenuBar->Paint();
268 else
269 [WindowDelegate paintMenubarForWindow:mainWindow];
270
271 NS_OBJC_END_TRY_ABORT_BLOCK;
272 }
273
274 void data_ss_release_callback(void *aDataSourceSurface,
275 const void *data,
276 size_t size)
277 {
278 if (aDataSourceSurface) {
279 static_cast<DataSourceSurface*>(aDataSourceSurface)->Unmap();
280 static_cast<DataSourceSurface*>(aDataSourceSurface)->Release();
281 }
282 }
283
284 nsresult nsCocoaUtils::CreateCGImageFromSurface(SourceSurface* aSurface,
285 CGImageRef* aResult)
286 {
287 RefPtr<DataSourceSurface> dataSurface;
288
289 if (aSurface->GetFormat() == SurfaceFormat::B8G8R8A8) {
290 dataSurface = aSurface->GetDataSurface();
291 } else {
292 // CGImageCreate only supports 16- and 32-bit bit-depth
293 // Convert format to SurfaceFormat::B8G8R8A8
294 dataSurface = gfxUtils::
295 CopySurfaceToDataSourceSurfaceWithFormat(aSurface,
296 SurfaceFormat::B8G8R8A8);
297 }
298
299 NS_ENSURE_TRUE(dataSurface, NS_ERROR_FAILURE);
300
301 int32_t width = dataSurface->GetSize().width;
302 int32_t height = dataSurface->GetSize().height;
303 if (height < 1 || width < 1) {
304 return NS_ERROR_FAILURE;
305 }
306
307 DataSourceSurface::MappedSurface map;
308 if (!dataSurface->Map(DataSourceSurface::MapType::READ, &map)) {
309 return NS_ERROR_FAILURE;
310 }
311 // The Unmap() call happens in data_ss_release_callback
312
313 // Create a CGImageRef with the bits from the image, taking into account
314 // the alpha ordering and endianness of the machine so we don't have to
315 // touch the bits ourselves.
316 CGDataProviderRef dataProvider = ::CGDataProviderCreateWithData(dataSurface.forget().drop(),
317 map.mData,
318 map.mStride * height,
319 data_ss_release_callback);
320 CGColorSpaceRef colorSpace = ::CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);
321 *aResult = ::CGImageCreate(width,
322 height,
323 8,
324 32,
325 map.mStride,
326 colorSpace,
327 kCGBitmapByteOrder32Host | kCGImageAlphaPremultipliedFirst,
328 dataProvider,
329 NULL,
330 0,
331 kCGRenderingIntentDefault);
332 ::CGColorSpaceRelease(colorSpace);
333 ::CGDataProviderRelease(dataProvider);
334 return *aResult ? NS_OK : NS_ERROR_FAILURE;
335 }
336
337 nsresult nsCocoaUtils::CreateNSImageFromCGImage(CGImageRef aInputImage, NSImage **aResult)
338 {
339 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
340
341 // Be very careful when creating the NSImage that the backing NSImageRep is
342 // exactly 1:1 with the input image. On a retina display, both [NSImage
343 // lockFocus] and [NSImage initWithCGImage:size:] will create an image with a
344 // 2x backing NSImageRep. This prevents NSCursor from recognizing a retina
345 // cursor, which only occurs if pixelsWide and pixelsHigh are exactly 2x the
346 // size of the NSImage.
347 //
348 // For example, if a 32x32 SVG cursor is rendered on a retina display, then
349 // aInputImage will be 64x64. The resulting NSImage will be scaled back down
350 // to 32x32 so it stays the correct size on the screen by changing its size
351 // (resizing a NSImage only scales the image and doesn't resample the data).
352 // If aInputImage is converted using [NSImage initWithCGImage:size:] then the
353 // bitmap will be 128x128 and NSCursor won't recognize a retina cursor, since
354 // it will expect a 64x64 bitmap.
355
356 int32_t width = ::CGImageGetWidth(aInputImage);
357 int32_t height = ::CGImageGetHeight(aInputImage);
358 NSRect imageRect = ::NSMakeRect(0.0, 0.0, width, height);
359
360 NSBitmapImageRep *offscreenRep = [[NSBitmapImageRep alloc]
361 initWithBitmapDataPlanes:NULL
362 pixelsWide:width
363 pixelsHigh:height
364 bitsPerSample:8
365 samplesPerPixel:4
366 hasAlpha:YES
367 isPlanar:NO
368 colorSpaceName:NSDeviceRGBColorSpace
369 bitmapFormat:NSAlphaFirstBitmapFormat
370 bytesPerRow:0
371 bitsPerPixel:0];
372
373 NSGraphicsContext *context = [NSGraphicsContext graphicsContextWithBitmapImageRep:offscreenRep];
374 [NSGraphicsContext saveGraphicsState];
375 [NSGraphicsContext setCurrentContext:context];
376
377 // Get the Quartz context and draw.
378 CGContextRef imageContext = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
379 ::CGContextDrawImage(imageContext, *(CGRect*)&imageRect, aInputImage);
380
381 [NSGraphicsContext restoreGraphicsState];
382
383 *aResult = [[NSImage alloc] initWithSize:NSMakeSize(width, height)];
384 [*aResult addRepresentation:offscreenRep];
385 [offscreenRep release];
386 return NS_OK;
387
388 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
389 }
390
391 nsresult nsCocoaUtils::CreateNSImageFromImageContainer(imgIContainer *aImage, uint32_t aWhichFrame, NSImage **aResult, CGFloat scaleFactor)
392 {
393 RefPtr<SourceSurface> surface;
394 int32_t width = 0, height = 0;
395 aImage->GetWidth(&width);
396 aImage->GetHeight(&height);
397
398 // Render a vector image at the correct resolution on a retina display
399 if (aImage->GetType() == imgIContainer::TYPE_VECTOR && scaleFactor != 1.0f) {
400 int scaledWidth = (int)ceilf(width * scaleFactor);
401 int scaledHeight = (int)ceilf(height * scaleFactor);
402
403 RefPtr<DrawTarget> drawTarget = gfxPlatform::GetPlatform()->
404 CreateOffscreenContentDrawTarget(IntSize(scaledWidth, scaledHeight),
405 SurfaceFormat::B8G8R8A8);
406 if (!drawTarget) {
407 NS_ERROR("Failed to create DrawTarget");
408 return NS_ERROR_FAILURE;
409 }
410
411 nsRefPtr<gfxContext> context = new gfxContext(drawTarget);
412 if (!context) {
413 NS_ERROR("Failed to create gfxContext");
414 return NS_ERROR_FAILURE;
415 }
416
417 aImage->Draw(context, GraphicsFilter::FILTER_NEAREST, gfxMatrix(),
418 gfxRect(0.0f, 0.0f, scaledWidth, scaledHeight),
419 nsIntRect(0, 0, width, height),
420 nsIntSize(scaledWidth, scaledHeight),
421 nullptr, aWhichFrame, imgIContainer::FLAG_SYNC_DECODE);
422
423 surface = drawTarget->Snapshot();
424 } else {
425 surface = aImage->GetFrame(aWhichFrame, imgIContainer::FLAG_SYNC_DECODE);
426 }
427
428 NS_ENSURE_TRUE(surface, NS_ERROR_FAILURE);
429
430 CGImageRef imageRef = NULL;
431 nsresult rv = nsCocoaUtils::CreateCGImageFromSurface(surface, &imageRef);
432 if (NS_FAILED(rv) || !imageRef) {
433 return NS_ERROR_FAILURE;
434 }
435
436 rv = nsCocoaUtils::CreateNSImageFromCGImage(imageRef, aResult);
437 if (NS_FAILED(rv) || !aResult) {
438 return NS_ERROR_FAILURE;
439 }
440 ::CGImageRelease(imageRef);
441
442 // Ensure the image will be rendered the correct size on a retina display
443 NSSize size = NSMakeSize(width, height);
444 [*aResult setSize:size];
445 [[[*aResult representations] objectAtIndex:0] setSize:size];
446 return NS_OK;
447 }
448
449 // static
450 void
451 nsCocoaUtils::GetStringForNSString(const NSString *aSrc, nsAString& aDist)
452 {
453 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
454
455 if (!aSrc) {
456 aDist.Truncate();
457 return;
458 }
459
460 aDist.SetLength([aSrc length]);
461 [aSrc getCharacters: reinterpret_cast<unichar*>(aDist.BeginWriting())];
462
463 NS_OBJC_END_TRY_ABORT_BLOCK;
464 }
465
466 // static
467 NSString*
468 nsCocoaUtils::ToNSString(const nsAString& aString)
469 {
470 if (aString.IsEmpty()) {
471 return [NSString string];
472 }
473 return [NSString stringWithCharacters:reinterpret_cast<const unichar*>(aString.BeginReading())
474 length:aString.Length()];
475 }
476
477 // static
478 void
479 nsCocoaUtils::GeckoRectToNSRect(const nsIntRect& aGeckoRect,
480 NSRect& aOutCocoaRect)
481 {
482 aOutCocoaRect.origin.x = aGeckoRect.x;
483 aOutCocoaRect.origin.y = aGeckoRect.y;
484 aOutCocoaRect.size.width = aGeckoRect.width;
485 aOutCocoaRect.size.height = aGeckoRect.height;
486 }
487
488 // static
489 void
490 nsCocoaUtils::NSRectToGeckoRect(const NSRect& aCocoaRect,
491 nsIntRect& aOutGeckoRect)
492 {
493 aOutGeckoRect.x = NSToIntRound(aCocoaRect.origin.x);
494 aOutGeckoRect.y = NSToIntRound(aCocoaRect.origin.y);
495 aOutGeckoRect.width = NSToIntRound(aCocoaRect.origin.x + aCocoaRect.size.width) - aOutGeckoRect.x;
496 aOutGeckoRect.height = NSToIntRound(aCocoaRect.origin.y + aCocoaRect.size.height) - aOutGeckoRect.y;
497 }
498
499 // static
500 NSEvent*
501 nsCocoaUtils::MakeNewCocoaEventWithType(NSEventType aEventType, NSEvent *aEvent)
502 {
503 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
504
505 NSEvent* newEvent =
506 [NSEvent keyEventWithType:aEventType
507 location:[aEvent locationInWindow]
508 modifierFlags:[aEvent modifierFlags]
509 timestamp:[aEvent timestamp]
510 windowNumber:[aEvent windowNumber]
511 context:[aEvent context]
512 characters:[aEvent characters]
513 charactersIgnoringModifiers:[aEvent charactersIgnoringModifiers]
514 isARepeat:[aEvent isARepeat]
515 keyCode:[aEvent keyCode]];
516 return newEvent;
517
518 NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
519 }
520
521 // static
522 void
523 nsCocoaUtils::InitNPCocoaEvent(NPCocoaEvent* aNPCocoaEvent)
524 {
525 memset(aNPCocoaEvent, 0, sizeof(NPCocoaEvent));
526 }
527
528 // static
529 void
530 nsCocoaUtils::InitPluginEvent(WidgetPluginEvent &aPluginEvent,
531 NPCocoaEvent &aCocoaEvent)
532 {
533 aPluginEvent.time = PR_IntervalNow();
534 aPluginEvent.pluginEvent = (void*)&aCocoaEvent;
535 aPluginEvent.retargetToFocusedDocument = false;
536 }
537
538 // static
539 void
540 nsCocoaUtils::InitInputEvent(WidgetInputEvent& aInputEvent,
541 NSEvent* aNativeEvent)
542 {
543 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
544
545 NSUInteger modifiers =
546 aNativeEvent ? [aNativeEvent modifierFlags] : [NSEvent modifierFlags];
547 InitInputEvent(aInputEvent, modifiers);
548
549 aInputEvent.time = PR_IntervalNow();
550
551 NS_OBJC_END_TRY_ABORT_BLOCK;
552 }
553
554 // static
555 void
556 nsCocoaUtils::InitInputEvent(WidgetInputEvent& aInputEvent,
557 NSUInteger aModifiers)
558 {
559 aInputEvent.modifiers = 0;
560 if (aModifiers & NSShiftKeyMask) {
561 aInputEvent.modifiers |= MODIFIER_SHIFT;
562 }
563 if (aModifiers & NSControlKeyMask) {
564 aInputEvent.modifiers |= MODIFIER_CONTROL;
565 }
566 if (aModifiers & NSAlternateKeyMask) {
567 aInputEvent.modifiers |= MODIFIER_ALT;
568 // Mac's option key is similar to other platforms' AltGr key.
569 // Let's set AltGr flag when option key is pressed for consistency with
570 // other platforms.
571 aInputEvent.modifiers |= MODIFIER_ALTGRAPH;
572 }
573 if (aModifiers & NSCommandKeyMask) {
574 aInputEvent.modifiers |= MODIFIER_META;
575 }
576
577 if (aModifiers & NSAlphaShiftKeyMask) {
578 aInputEvent.modifiers |= MODIFIER_CAPSLOCK;
579 }
580 // Mac doesn't have NumLock key. We can assume that NumLock is always locked
581 // if user is using a keyboard which has numpad. Otherwise, if user is using
582 // a keyboard which doesn't have numpad, e.g., MacBook's keyboard, we can
583 // assume that NumLock is always unlocked.
584 // Unfortunately, we cannot know whether current keyboard has numpad or not.
585 // We should notify locked state only when keys in numpad are pressed.
586 // By this, web applications may not be confused by unexpected numpad key's
587 // key event with unlocked state.
588 if (aModifiers & NSNumericPadKeyMask) {
589 aInputEvent.modifiers |= MODIFIER_NUMLOCK;
590 }
591
592 // Be aware, NSFunctionKeyMask is included when arrow keys, home key or some
593 // other keys are pressed. We cannot check whether 'fn' key is pressed or
594 // not by the flag.
595
596 }
597
598 // static
599 UInt32
600 nsCocoaUtils::ConvertToCarbonModifier(NSUInteger aCocoaModifier)
601 {
602 UInt32 carbonModifier = 0;
603 if (aCocoaModifier & NSAlphaShiftKeyMask) {
604 carbonModifier |= alphaLock;
605 }
606 if (aCocoaModifier & NSControlKeyMask) {
607 carbonModifier |= controlKey;
608 }
609 if (aCocoaModifier & NSAlternateKeyMask) {
610 carbonModifier |= optionKey;
611 }
612 if (aCocoaModifier & NSShiftKeyMask) {
613 carbonModifier |= shiftKey;
614 }
615 if (aCocoaModifier & NSCommandKeyMask) {
616 carbonModifier |= cmdKey;
617 }
618 if (aCocoaModifier & NSNumericPadKeyMask) {
619 carbonModifier |= kEventKeyModifierNumLockMask;
620 }
621 if (aCocoaModifier & NSFunctionKeyMask) {
622 carbonModifier |= kEventKeyModifierFnMask;
623 }
624 return carbonModifier;
625 }
626
627 // While HiDPI support is not 100% complete and tested, we'll have a pref
628 // to allow it to be turned off in case of problems (or for testing purposes).
629
630 // gfx.hidpi.enabled is an integer with the meaning:
631 // <= 0 : HiDPI support is disabled
632 // 1 : HiDPI enabled provided all screens have the same backing resolution
633 // > 1 : HiDPI enabled even if there are a mixture of screen modes
634
635 // All the following code is to be removed once HiDPI work is more complete.
636
637 static bool sHiDPIEnabled = false;
638 static bool sHiDPIPrefInitialized = false;
639
640 // static
641 bool
642 nsCocoaUtils::HiDPIEnabled()
643 {
644 if (!sHiDPIPrefInitialized) {
645 sHiDPIPrefInitialized = true;
646
647 int prefSetting = Preferences::GetInt("gfx.hidpi.enabled", 1);
648 if (prefSetting <= 0) {
649 return false;
650 }
651
652 // prefSetting is at least 1, need to check attached screens...
653
654 int scaleFactors = 0; // used as a bitset to track the screen types found
655 NSEnumerator *screenEnum = [[NSScreen screens] objectEnumerator];
656 while (NSScreen *screen = [screenEnum nextObject]) {
657 NSDictionary *desc = [screen deviceDescription];
658 if ([desc objectForKey:NSDeviceIsScreen] == nil) {
659 continue;
660 }
661 CGFloat scale =
662 [screen respondsToSelector:@selector(backingScaleFactor)] ?
663 [screen backingScaleFactor] : 1.0;
664 // Currently, we only care about differentiating "1.0" and "2.0",
665 // so we set one of the two low bits to record which.
666 if (scale > 1.0) {
667 scaleFactors |= 2;
668 } else {
669 scaleFactors |= 1;
670 }
671 }
672
673 // Now scaleFactors will be:
674 // 0 if no screens (supporting backingScaleFactor) found
675 // 1 if only lo-DPI screens
676 // 2 if only hi-DPI screens
677 // 3 if both lo- and hi-DPI screens
678 // We'll enable HiDPI support if there's only a single screen type,
679 // OR if the pref setting is explicitly greater than 1.
680 sHiDPIEnabled = (scaleFactors <= 2) || (prefSetting > 1);
681 }
682
683 return sHiDPIEnabled;
684 }
685
686 void
687 nsCocoaUtils::GetCommandsFromKeyEvent(NSEvent* aEvent,
688 nsTArray<KeyBindingsCommand>& aCommands)
689 {
690 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
691
692 MOZ_ASSERT(aEvent);
693
694 static NativeKeyBindingsRecorder* sNativeKeyBindingsRecorder;
695 if (!sNativeKeyBindingsRecorder) {
696 sNativeKeyBindingsRecorder = [NativeKeyBindingsRecorder new];
697 }
698
699 [sNativeKeyBindingsRecorder startRecording:aCommands];
700
701 // This will trigger 0 - N calls to doCommandBySelector: and insertText:
702 [sNativeKeyBindingsRecorder
703 interpretKeyEvents:[NSArray arrayWithObject:aEvent]];
704
705 NS_OBJC_END_TRY_ABORT_BLOCK;
706 }
707
708 @implementation NativeKeyBindingsRecorder
709
710 - (void)startRecording:(nsTArray<KeyBindingsCommand>&)aCommands
711 {
712 mCommands = &aCommands;
713 mCommands->Clear();
714 }
715
716 - (void)doCommandBySelector:(SEL)aSelector
717 {
718 KeyBindingsCommand command = {
719 aSelector,
720 nil
721 };
722
723 mCommands->AppendElement(command);
724 }
725
726 - (void)insertText:(id)aString
727 {
728 KeyBindingsCommand command = {
729 @selector(insertText:),
730 aString
731 };
732
733 mCommands->AppendElement(command);
734 }
735
736 @end // NativeKeyBindingsRecorder
737
738 struct KeyConversionData
739 {
740 const char* str;
741 size_t strLength;
742 uint32_t geckoKeyCode;
743 uint32_t charCode;
744 };
745
746 static const KeyConversionData gKeyConversions[] = {
747
748 #define KEYCODE_ENTRY(aStr, aCode) \
749 {#aStr, sizeof(#aStr) - 1, NS_##aStr, aCode}
750
751 // Some keycodes may have different name in nsIDOMKeyEvent from its key name.
752 #define KEYCODE_ENTRY2(aStr, aNSName, aCode) \
753 {#aStr, sizeof(#aStr) - 1, NS_##aNSName, aCode}
754
755 KEYCODE_ENTRY(VK_CANCEL, 0x001B),
756 KEYCODE_ENTRY(VK_DELETE, NSDeleteFunctionKey),
757 KEYCODE_ENTRY(VK_BACK, NSBackspaceCharacter),
758 KEYCODE_ENTRY2(VK_BACK_SPACE, VK_BACK, NSBackspaceCharacter),
759 KEYCODE_ENTRY(VK_TAB, NSTabCharacter),
760 KEYCODE_ENTRY(VK_CLEAR, NSClearLineFunctionKey),
761 KEYCODE_ENTRY(VK_RETURN, NSEnterCharacter),
762 KEYCODE_ENTRY(VK_SHIFT, 0),
763 KEYCODE_ENTRY(VK_CONTROL, 0),
764 KEYCODE_ENTRY(VK_ALT, 0),
765 KEYCODE_ENTRY(VK_PAUSE, NSPauseFunctionKey),
766 KEYCODE_ENTRY(VK_CAPS_LOCK, 0),
767 KEYCODE_ENTRY(VK_ESCAPE, 0),
768 KEYCODE_ENTRY(VK_SPACE, ' '),
769 KEYCODE_ENTRY(VK_PAGE_UP, NSPageUpFunctionKey),
770 KEYCODE_ENTRY(VK_PAGE_DOWN, NSPageDownFunctionKey),
771 KEYCODE_ENTRY(VK_END, NSEndFunctionKey),
772 KEYCODE_ENTRY(VK_HOME, NSHomeFunctionKey),
773 KEYCODE_ENTRY(VK_LEFT, NSLeftArrowFunctionKey),
774 KEYCODE_ENTRY(VK_UP, NSUpArrowFunctionKey),
775 KEYCODE_ENTRY(VK_RIGHT, NSRightArrowFunctionKey),
776 KEYCODE_ENTRY(VK_DOWN, NSDownArrowFunctionKey),
777 KEYCODE_ENTRY(VK_PRINTSCREEN, NSPrintScreenFunctionKey),
778 KEYCODE_ENTRY(VK_INSERT, NSInsertFunctionKey),
779 KEYCODE_ENTRY(VK_HELP, NSHelpFunctionKey),
780 KEYCODE_ENTRY(VK_0, '0'),
781 KEYCODE_ENTRY(VK_1, '1'),
782 KEYCODE_ENTRY(VK_2, '2'),
783 KEYCODE_ENTRY(VK_3, '3'),
784 KEYCODE_ENTRY(VK_4, '4'),
785 KEYCODE_ENTRY(VK_5, '5'),
786 KEYCODE_ENTRY(VK_6, '6'),
787 KEYCODE_ENTRY(VK_7, '7'),
788 KEYCODE_ENTRY(VK_8, '8'),
789 KEYCODE_ENTRY(VK_9, '9'),
790 KEYCODE_ENTRY(VK_SEMICOLON, ':'),
791 KEYCODE_ENTRY(VK_EQUALS, '='),
792 KEYCODE_ENTRY(VK_A, 'A'),
793 KEYCODE_ENTRY(VK_B, 'B'),
794 KEYCODE_ENTRY(VK_C, 'C'),
795 KEYCODE_ENTRY(VK_D, 'D'),
796 KEYCODE_ENTRY(VK_E, 'E'),
797 KEYCODE_ENTRY(VK_F, 'F'),
798 KEYCODE_ENTRY(VK_G, 'G'),
799 KEYCODE_ENTRY(VK_H, 'H'),
800 KEYCODE_ENTRY(VK_I, 'I'),
801 KEYCODE_ENTRY(VK_J, 'J'),
802 KEYCODE_ENTRY(VK_K, 'K'),
803 KEYCODE_ENTRY(VK_L, 'L'),
804 KEYCODE_ENTRY(VK_M, 'M'),
805 KEYCODE_ENTRY(VK_N, 'N'),
806 KEYCODE_ENTRY(VK_O, 'O'),
807 KEYCODE_ENTRY(VK_P, 'P'),
808 KEYCODE_ENTRY(VK_Q, 'Q'),
809 KEYCODE_ENTRY(VK_R, 'R'),
810 KEYCODE_ENTRY(VK_S, 'S'),
811 KEYCODE_ENTRY(VK_T, 'T'),
812 KEYCODE_ENTRY(VK_U, 'U'),
813 KEYCODE_ENTRY(VK_V, 'V'),
814 KEYCODE_ENTRY(VK_W, 'W'),
815 KEYCODE_ENTRY(VK_X, 'X'),
816 KEYCODE_ENTRY(VK_Y, 'Y'),
817 KEYCODE_ENTRY(VK_Z, 'Z'),
818 KEYCODE_ENTRY(VK_CONTEXT_MENU, NSMenuFunctionKey),
819 KEYCODE_ENTRY(VK_NUMPAD0, '0'),
820 KEYCODE_ENTRY(VK_NUMPAD1, '1'),
821 KEYCODE_ENTRY(VK_NUMPAD2, '2'),
822 KEYCODE_ENTRY(VK_NUMPAD3, '3'),
823 KEYCODE_ENTRY(VK_NUMPAD4, '4'),
824 KEYCODE_ENTRY(VK_NUMPAD5, '5'),
825 KEYCODE_ENTRY(VK_NUMPAD6, '6'),
826 KEYCODE_ENTRY(VK_NUMPAD7, '7'),
827 KEYCODE_ENTRY(VK_NUMPAD8, '8'),
828 KEYCODE_ENTRY(VK_NUMPAD9, '9'),
829 KEYCODE_ENTRY(VK_MULTIPLY, '*'),
830 KEYCODE_ENTRY(VK_ADD, '+'),
831 KEYCODE_ENTRY(VK_SEPARATOR, 0),
832 KEYCODE_ENTRY(VK_SUBTRACT, '-'),
833 KEYCODE_ENTRY(VK_DECIMAL, '.'),
834 KEYCODE_ENTRY(VK_DIVIDE, '/'),
835 KEYCODE_ENTRY(VK_F1, NSF1FunctionKey),
836 KEYCODE_ENTRY(VK_F2, NSF2FunctionKey),
837 KEYCODE_ENTRY(VK_F3, NSF3FunctionKey),
838 KEYCODE_ENTRY(VK_F4, NSF4FunctionKey),
839 KEYCODE_ENTRY(VK_F5, NSF5FunctionKey),
840 KEYCODE_ENTRY(VK_F6, NSF6FunctionKey),
841 KEYCODE_ENTRY(VK_F7, NSF7FunctionKey),
842 KEYCODE_ENTRY(VK_F8, NSF8FunctionKey),
843 KEYCODE_ENTRY(VK_F9, NSF9FunctionKey),
844 KEYCODE_ENTRY(VK_F10, NSF10FunctionKey),
845 KEYCODE_ENTRY(VK_F11, NSF11FunctionKey),
846 KEYCODE_ENTRY(VK_F12, NSF12FunctionKey),
847 KEYCODE_ENTRY(VK_F13, NSF13FunctionKey),
848 KEYCODE_ENTRY(VK_F14, NSF14FunctionKey),
849 KEYCODE_ENTRY(VK_F15, NSF15FunctionKey),
850 KEYCODE_ENTRY(VK_F16, NSF16FunctionKey),
851 KEYCODE_ENTRY(VK_F17, NSF17FunctionKey),
852 KEYCODE_ENTRY(VK_F18, NSF18FunctionKey),
853 KEYCODE_ENTRY(VK_F19, NSF19FunctionKey),
854 KEYCODE_ENTRY(VK_F20, NSF20FunctionKey),
855 KEYCODE_ENTRY(VK_F21, NSF21FunctionKey),
856 KEYCODE_ENTRY(VK_F22, NSF22FunctionKey),
857 KEYCODE_ENTRY(VK_F23, NSF23FunctionKey),
858 KEYCODE_ENTRY(VK_F24, NSF24FunctionKey),
859 KEYCODE_ENTRY(VK_NUM_LOCK, NSClearLineFunctionKey),
860 KEYCODE_ENTRY(VK_SCROLL_LOCK, NSScrollLockFunctionKey),
861 KEYCODE_ENTRY(VK_COMMA, ','),
862 KEYCODE_ENTRY(VK_PERIOD, '.'),
863 KEYCODE_ENTRY(VK_SLASH, '/'),
864 KEYCODE_ENTRY(VK_BACK_QUOTE, '`'),
865 KEYCODE_ENTRY(VK_OPEN_BRACKET, '['),
866 KEYCODE_ENTRY(VK_BACK_SLASH, '\\'),
867 KEYCODE_ENTRY(VK_CLOSE_BRACKET, ']'),
868 KEYCODE_ENTRY(VK_QUOTE, '\'')
869
870 #undef KEYCODE_ENTRY
871
872 };
873
874 uint32_t
875 nsCocoaUtils::ConvertGeckoNameToMacCharCode(const nsAString& aKeyCodeName)
876 {
877 if (aKeyCodeName.IsEmpty()) {
878 return 0;
879 }
880
881 nsAutoCString keyCodeName;
882 keyCodeName.AssignWithConversion(aKeyCodeName);
883 // We want case-insensitive comparison with data stored as uppercase.
884 ToUpperCase(keyCodeName);
885
886 uint32_t keyCodeNameLength = keyCodeName.Length();
887 const char* keyCodeNameStr = keyCodeName.get();
888 for (uint16_t i = 0; i < ArrayLength(gKeyConversions); ++i) {
889 if (keyCodeNameLength == gKeyConversions[i].strLength &&
890 nsCRT::strcmp(gKeyConversions[i].str, keyCodeNameStr) == 0) {
891 return gKeyConversions[i].charCode;
892 }
893 }
894
895 return 0;
896 }
897
898 uint32_t
899 nsCocoaUtils::ConvertGeckoKeyCodeToMacCharCode(uint32_t aKeyCode)
900 {
901 if (!aKeyCode) {
902 return 0;
903 }
904
905 for (uint16_t i = 0; i < ArrayLength(gKeyConversions); ++i) {
906 if (gKeyConversions[i].geckoKeyCode == aKeyCode) {
907 return gKeyConversions[i].charCode;
908 }
909 }
910
911 return 0;
912 }

mercurial