michael@0: /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "gfxImageSurface.h" michael@0: #include "gfxPlatform.h" michael@0: #include "gfxUtils.h" michael@0: #include "nsCocoaUtils.h" michael@0: #include "nsChildView.h" michael@0: #include "nsMenuBarX.h" michael@0: #include "nsCocoaWindow.h" michael@0: #include "nsCOMPtr.h" michael@0: #include "nsIInterfaceRequestorUtils.h" michael@0: #include "nsIAppShellService.h" michael@0: #include "nsIXULWindow.h" michael@0: #include "nsIBaseWindow.h" michael@0: #include "nsIServiceManager.h" michael@0: #include "nsMenuUtilsX.h" michael@0: #include "nsToolkit.h" michael@0: #include "nsCRT.h" michael@0: #include "mozilla/gfx/2D.h" michael@0: #include "mozilla/MiscEvents.h" michael@0: #include "mozilla/Preferences.h" michael@0: #include "mozilla/TextEvents.h" michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::widget; michael@0: michael@0: using mozilla::gfx::BackendType; michael@0: using mozilla::gfx::DataSourceSurface; michael@0: using mozilla::gfx::DrawTarget; michael@0: using mozilla::gfx::Factory; michael@0: using mozilla::gfx::IntPoint; michael@0: using mozilla::gfx::IntRect; michael@0: using mozilla::gfx::IntSize; michael@0: using mozilla::gfx::SurfaceFormat; michael@0: using mozilla::gfx::SourceSurface; michael@0: michael@0: static float michael@0: MenuBarScreenHeight() michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN; michael@0: michael@0: NSArray* allScreens = [NSScreen screens]; michael@0: if ([allScreens count]) { michael@0: return [[allScreens objectAtIndex:0] frame].size.height; michael@0: } michael@0: michael@0: return 0.0; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(0.0); michael@0: } michael@0: michael@0: float michael@0: nsCocoaUtils::FlippedScreenY(float y) michael@0: { michael@0: return MenuBarScreenHeight() - y; michael@0: } michael@0: michael@0: NSRect nsCocoaUtils::GeckoRectToCocoaRect(const nsIntRect &geckoRect) michael@0: { michael@0: // We only need to change the Y coordinate by starting with the primary screen michael@0: // height and subtracting the gecko Y coordinate of the bottom of the rect. michael@0: return NSMakeRect(geckoRect.x, michael@0: MenuBarScreenHeight() - geckoRect.YMost(), michael@0: geckoRect.width, michael@0: geckoRect.height); michael@0: } michael@0: michael@0: NSRect nsCocoaUtils::GeckoRectToCocoaRectDevPix(const nsIntRect &aGeckoRect, michael@0: CGFloat aBackingScale) michael@0: { michael@0: return NSMakeRect(aGeckoRect.x / aBackingScale, michael@0: MenuBarScreenHeight() - aGeckoRect.YMost() / aBackingScale, michael@0: aGeckoRect.width / aBackingScale, michael@0: aGeckoRect.height / aBackingScale); michael@0: } michael@0: michael@0: nsIntRect nsCocoaUtils::CocoaRectToGeckoRect(const NSRect &cocoaRect) michael@0: { michael@0: // We only need to change the Y coordinate by starting with the primary screen michael@0: // height and subtracting both the cocoa y origin and the height of the michael@0: // cocoa rect. michael@0: nsIntRect rect; michael@0: rect.x = NSToIntRound(cocoaRect.origin.x); michael@0: rect.y = NSToIntRound(FlippedScreenY(cocoaRect.origin.y + cocoaRect.size.height)); michael@0: rect.width = NSToIntRound(cocoaRect.origin.x + cocoaRect.size.width) - rect.x; michael@0: rect.height = NSToIntRound(FlippedScreenY(cocoaRect.origin.y)) - rect.y; michael@0: return rect; michael@0: } michael@0: michael@0: nsIntRect nsCocoaUtils::CocoaRectToGeckoRectDevPix(const NSRect &aCocoaRect, michael@0: CGFloat aBackingScale) michael@0: { michael@0: nsIntRect rect; michael@0: rect.x = NSToIntRound(aCocoaRect.origin.x * aBackingScale); michael@0: rect.y = NSToIntRound(FlippedScreenY(aCocoaRect.origin.y + aCocoaRect.size.height) * aBackingScale); michael@0: rect.width = NSToIntRound((aCocoaRect.origin.x + aCocoaRect.size.width) * aBackingScale) - rect.x; michael@0: rect.height = NSToIntRound(FlippedScreenY(aCocoaRect.origin.y) * aBackingScale) - rect.y; michael@0: return rect; michael@0: } michael@0: michael@0: NSPoint nsCocoaUtils::ScreenLocationForEvent(NSEvent* anEvent) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN; michael@0: michael@0: // Don't trust mouse locations of mouse move events, see bug 443178. michael@0: if (!anEvent || [anEvent type] == NSMouseMoved) michael@0: return [NSEvent mouseLocation]; michael@0: michael@0: // Pin momentum scroll events to the location of the last user-controlled michael@0: // scroll event. michael@0: if (IsMomentumScrollEvent(anEvent)) michael@0: return ChildViewMouseTracker::sLastScrollEventScreenLocation; michael@0: michael@0: return [[anEvent window] convertBaseToScreen:[anEvent locationInWindow]]; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NSMakePoint(0.0, 0.0)); michael@0: } michael@0: michael@0: BOOL nsCocoaUtils::IsEventOverWindow(NSEvent* anEvent, NSWindow* aWindow) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN; michael@0: michael@0: return NSPointInRect(ScreenLocationForEvent(anEvent), [aWindow frame]); michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NO); michael@0: } michael@0: michael@0: NSPoint nsCocoaUtils::EventLocationForWindow(NSEvent* anEvent, NSWindow* aWindow) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN; michael@0: michael@0: return [aWindow convertScreenToBase:ScreenLocationForEvent(anEvent)]; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NSMakePoint(0.0, 0.0)); michael@0: } michael@0: michael@0: BOOL nsCocoaUtils::IsMomentumScrollEvent(NSEvent* aEvent) michael@0: { michael@0: if ([aEvent type] != NSScrollWheel) michael@0: return NO; michael@0: michael@0: if ([aEvent respondsToSelector:@selector(momentumPhase)]) michael@0: return ([aEvent momentumPhase] & NSEventPhaseChanged) != 0; michael@0: michael@0: if ([aEvent respondsToSelector:@selector(_scrollPhase)]) michael@0: return [aEvent _scrollPhase] != 0; michael@0: michael@0: return NO; michael@0: } michael@0: michael@0: void nsCocoaUtils::HideOSChromeOnScreen(bool aShouldHide, NSScreen* aScreen) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: // Keep track of how many hiding requests have been made, so that they can michael@0: // be nested. michael@0: static int sMenuBarHiddenCount = 0, sDockHiddenCount = 0; michael@0: michael@0: // Always hide the Dock, since it's not necessarily on the primary screen. michael@0: sDockHiddenCount += aShouldHide ? 1 : -1; michael@0: NS_ASSERTION(sMenuBarHiddenCount >= 0, "Unbalanced HideMenuAndDockForWindow calls"); michael@0: michael@0: // Only hide the menu bar if the window is on the same screen. michael@0: // The menu bar is always on the first screen in the screen list. michael@0: if (aScreen == [[NSScreen screens] objectAtIndex:0]) { michael@0: sMenuBarHiddenCount += aShouldHide ? 1 : -1; michael@0: NS_ASSERTION(sDockHiddenCount >= 0, "Unbalanced HideMenuAndDockForWindow calls"); michael@0: } michael@0: michael@0: // TODO This should be upgraded to use [NSApplication setPresentationOptions:] michael@0: // when support for 10.5 is dropped. michael@0: if (sMenuBarHiddenCount > 0) { michael@0: ::SetSystemUIMode(kUIModeAllHidden, 0); michael@0: } else if (sDockHiddenCount > 0) { michael@0: ::SetSystemUIMode(kUIModeContentHidden, 0); michael@0: } else { michael@0: ::SetSystemUIMode(kUIModeNormal, 0); michael@0: } michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: michael@0: #define NS_APPSHELLSERVICE_CONTRACTID "@mozilla.org/appshell/appShellService;1" michael@0: nsIWidget* nsCocoaUtils::GetHiddenWindowWidget() michael@0: { michael@0: nsCOMPtr appShell(do_GetService(NS_APPSHELLSERVICE_CONTRACTID)); michael@0: if (!appShell) { michael@0: NS_WARNING("Couldn't get AppShellService in order to get hidden window ref"); michael@0: return nullptr; michael@0: } michael@0: michael@0: nsCOMPtr hiddenWindow; michael@0: appShell->GetHiddenWindow(getter_AddRefs(hiddenWindow)); michael@0: if (!hiddenWindow) { michael@0: // Don't warn, this happens during shutdown, bug 358607. michael@0: return nullptr; michael@0: } michael@0: michael@0: nsCOMPtr baseHiddenWindow; michael@0: baseHiddenWindow = do_GetInterface(hiddenWindow); michael@0: if (!baseHiddenWindow) { michael@0: NS_WARNING("Couldn't get nsIBaseWindow from hidden window (nsIXULWindow)"); michael@0: return nullptr; michael@0: } michael@0: michael@0: nsCOMPtr hiddenWindowWidget; michael@0: if (NS_FAILED(baseHiddenWindow->GetMainWidget(getter_AddRefs(hiddenWindowWidget)))) { michael@0: NS_WARNING("Couldn't get nsIWidget from hidden window (nsIBaseWindow)"); michael@0: return nullptr; michael@0: } michael@0: michael@0: return hiddenWindowWidget; michael@0: } michael@0: michael@0: void nsCocoaUtils::PrepareForNativeAppModalDialog() michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: // Don't do anything if this is embedding. We'll assume that if there is no hidden michael@0: // window we shouldn't do anything, and that should cover the embedding case. michael@0: nsMenuBarX* hiddenWindowMenuBar = nsMenuUtilsX::GetHiddenWindowMenuBar(); michael@0: if (!hiddenWindowMenuBar) michael@0: return; michael@0: michael@0: // First put up the hidden window menu bar so that app menu event handling is correct. michael@0: hiddenWindowMenuBar->Paint(); michael@0: michael@0: NSMenu* mainMenu = [NSApp mainMenu]; michael@0: NS_ASSERTION([mainMenu numberOfItems] > 0, "Main menu does not have any items, something is terribly wrong!"); michael@0: michael@0: // Create new menu bar for use with modal dialog michael@0: NSMenu* newMenuBar = [[NSMenu alloc] initWithTitle:@""]; michael@0: michael@0: // Swap in our app menu. Note that the event target is whatever window is up when michael@0: // the app modal dialog goes up. michael@0: NSMenuItem* firstMenuItem = [[mainMenu itemAtIndex:0] retain]; michael@0: [mainMenu removeItemAtIndex:0]; michael@0: [newMenuBar insertItem:firstMenuItem atIndex:0]; michael@0: [firstMenuItem release]; michael@0: michael@0: // Add standard edit menu michael@0: [newMenuBar addItem:nsMenuUtilsX::GetStandardEditMenuItem()]; michael@0: michael@0: // Show the new menu bar michael@0: [NSApp setMainMenu:newMenuBar]; michael@0: [newMenuBar release]; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: void nsCocoaUtils::CleanUpAfterNativeAppModalDialog() michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: // Don't do anything if this is embedding. We'll assume that if there is no hidden michael@0: // window we shouldn't do anything, and that should cover the embedding case. michael@0: nsMenuBarX* hiddenWindowMenuBar = nsMenuUtilsX::GetHiddenWindowMenuBar(); michael@0: if (!hiddenWindowMenuBar) michael@0: return; michael@0: michael@0: NSWindow* mainWindow = [NSApp mainWindow]; michael@0: if (!mainWindow) michael@0: hiddenWindowMenuBar->Paint(); michael@0: else michael@0: [WindowDelegate paintMenubarForWindow:mainWindow]; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: void data_ss_release_callback(void *aDataSourceSurface, michael@0: const void *data, michael@0: size_t size) michael@0: { michael@0: if (aDataSourceSurface) { michael@0: static_cast(aDataSourceSurface)->Unmap(); michael@0: static_cast(aDataSourceSurface)->Release(); michael@0: } michael@0: } michael@0: michael@0: nsresult nsCocoaUtils::CreateCGImageFromSurface(SourceSurface* aSurface, michael@0: CGImageRef* aResult) michael@0: { michael@0: RefPtr dataSurface; michael@0: michael@0: if (aSurface->GetFormat() == SurfaceFormat::B8G8R8A8) { michael@0: dataSurface = aSurface->GetDataSurface(); michael@0: } else { michael@0: // CGImageCreate only supports 16- and 32-bit bit-depth michael@0: // Convert format to SurfaceFormat::B8G8R8A8 michael@0: dataSurface = gfxUtils:: michael@0: CopySurfaceToDataSourceSurfaceWithFormat(aSurface, michael@0: SurfaceFormat::B8G8R8A8); michael@0: } michael@0: michael@0: NS_ENSURE_TRUE(dataSurface, NS_ERROR_FAILURE); michael@0: michael@0: int32_t width = dataSurface->GetSize().width; michael@0: int32_t height = dataSurface->GetSize().height; michael@0: if (height < 1 || width < 1) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: DataSourceSurface::MappedSurface map; michael@0: if (!dataSurface->Map(DataSourceSurface::MapType::READ, &map)) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: // The Unmap() call happens in data_ss_release_callback michael@0: michael@0: // Create a CGImageRef with the bits from the image, taking into account michael@0: // the alpha ordering and endianness of the machine so we don't have to michael@0: // touch the bits ourselves. michael@0: CGDataProviderRef dataProvider = ::CGDataProviderCreateWithData(dataSurface.forget().drop(), michael@0: map.mData, michael@0: map.mStride * height, michael@0: data_ss_release_callback); michael@0: CGColorSpaceRef colorSpace = ::CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB); michael@0: *aResult = ::CGImageCreate(width, michael@0: height, michael@0: 8, michael@0: 32, michael@0: map.mStride, michael@0: colorSpace, michael@0: kCGBitmapByteOrder32Host | kCGImageAlphaPremultipliedFirst, michael@0: dataProvider, michael@0: NULL, michael@0: 0, michael@0: kCGRenderingIntentDefault); michael@0: ::CGColorSpaceRelease(colorSpace); michael@0: ::CGDataProviderRelease(dataProvider); michael@0: return *aResult ? NS_OK : NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: nsresult nsCocoaUtils::CreateNSImageFromCGImage(CGImageRef aInputImage, NSImage **aResult) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; michael@0: michael@0: // Be very careful when creating the NSImage that the backing NSImageRep is michael@0: // exactly 1:1 with the input image. On a retina display, both [NSImage michael@0: // lockFocus] and [NSImage initWithCGImage:size:] will create an image with a michael@0: // 2x backing NSImageRep. This prevents NSCursor from recognizing a retina michael@0: // cursor, which only occurs if pixelsWide and pixelsHigh are exactly 2x the michael@0: // size of the NSImage. michael@0: // michael@0: // For example, if a 32x32 SVG cursor is rendered on a retina display, then michael@0: // aInputImage will be 64x64. The resulting NSImage will be scaled back down michael@0: // to 32x32 so it stays the correct size on the screen by changing its size michael@0: // (resizing a NSImage only scales the image and doesn't resample the data). michael@0: // If aInputImage is converted using [NSImage initWithCGImage:size:] then the michael@0: // bitmap will be 128x128 and NSCursor won't recognize a retina cursor, since michael@0: // it will expect a 64x64 bitmap. michael@0: michael@0: int32_t width = ::CGImageGetWidth(aInputImage); michael@0: int32_t height = ::CGImageGetHeight(aInputImage); michael@0: NSRect imageRect = ::NSMakeRect(0.0, 0.0, width, height); michael@0: michael@0: NSBitmapImageRep *offscreenRep = [[NSBitmapImageRep alloc] michael@0: initWithBitmapDataPlanes:NULL michael@0: pixelsWide:width michael@0: pixelsHigh:height michael@0: bitsPerSample:8 michael@0: samplesPerPixel:4 michael@0: hasAlpha:YES michael@0: isPlanar:NO michael@0: colorSpaceName:NSDeviceRGBColorSpace michael@0: bitmapFormat:NSAlphaFirstBitmapFormat michael@0: bytesPerRow:0 michael@0: bitsPerPixel:0]; michael@0: michael@0: NSGraphicsContext *context = [NSGraphicsContext graphicsContextWithBitmapImageRep:offscreenRep]; michael@0: [NSGraphicsContext saveGraphicsState]; michael@0: [NSGraphicsContext setCurrentContext:context]; michael@0: michael@0: // Get the Quartz context and draw. michael@0: CGContextRef imageContext = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort]; michael@0: ::CGContextDrawImage(imageContext, *(CGRect*)&imageRect, aInputImage); michael@0: michael@0: [NSGraphicsContext restoreGraphicsState]; michael@0: michael@0: *aResult = [[NSImage alloc] initWithSize:NSMakeSize(width, height)]; michael@0: [*aResult addRepresentation:offscreenRep]; michael@0: [offscreenRep release]; michael@0: return NS_OK; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; michael@0: } michael@0: michael@0: nsresult nsCocoaUtils::CreateNSImageFromImageContainer(imgIContainer *aImage, uint32_t aWhichFrame, NSImage **aResult, CGFloat scaleFactor) michael@0: { michael@0: RefPtr surface; michael@0: int32_t width = 0, height = 0; michael@0: aImage->GetWidth(&width); michael@0: aImage->GetHeight(&height); michael@0: michael@0: // Render a vector image at the correct resolution on a retina display michael@0: if (aImage->GetType() == imgIContainer::TYPE_VECTOR && scaleFactor != 1.0f) { michael@0: int scaledWidth = (int)ceilf(width * scaleFactor); michael@0: int scaledHeight = (int)ceilf(height * scaleFactor); michael@0: michael@0: RefPtr drawTarget = gfxPlatform::GetPlatform()-> michael@0: CreateOffscreenContentDrawTarget(IntSize(scaledWidth, scaledHeight), michael@0: SurfaceFormat::B8G8R8A8); michael@0: if (!drawTarget) { michael@0: NS_ERROR("Failed to create DrawTarget"); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: nsRefPtr context = new gfxContext(drawTarget); michael@0: if (!context) { michael@0: NS_ERROR("Failed to create gfxContext"); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: aImage->Draw(context, GraphicsFilter::FILTER_NEAREST, gfxMatrix(), michael@0: gfxRect(0.0f, 0.0f, scaledWidth, scaledHeight), michael@0: nsIntRect(0, 0, width, height), michael@0: nsIntSize(scaledWidth, scaledHeight), michael@0: nullptr, aWhichFrame, imgIContainer::FLAG_SYNC_DECODE); michael@0: michael@0: surface = drawTarget->Snapshot(); michael@0: } else { michael@0: surface = aImage->GetFrame(aWhichFrame, imgIContainer::FLAG_SYNC_DECODE); michael@0: } michael@0: michael@0: NS_ENSURE_TRUE(surface, NS_ERROR_FAILURE); michael@0: michael@0: CGImageRef imageRef = NULL; michael@0: nsresult rv = nsCocoaUtils::CreateCGImageFromSurface(surface, &imageRef); michael@0: if (NS_FAILED(rv) || !imageRef) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: rv = nsCocoaUtils::CreateNSImageFromCGImage(imageRef, aResult); michael@0: if (NS_FAILED(rv) || !aResult) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: ::CGImageRelease(imageRef); michael@0: michael@0: // Ensure the image will be rendered the correct size on a retina display michael@0: NSSize size = NSMakeSize(width, height); michael@0: [*aResult setSize:size]; michael@0: [[[*aResult representations] objectAtIndex:0] setSize:size]; michael@0: return NS_OK; michael@0: } michael@0: michael@0: // static michael@0: void michael@0: nsCocoaUtils::GetStringForNSString(const NSString *aSrc, nsAString& aDist) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: if (!aSrc) { michael@0: aDist.Truncate(); michael@0: return; michael@0: } michael@0: michael@0: aDist.SetLength([aSrc length]); michael@0: [aSrc getCharacters: reinterpret_cast(aDist.BeginWriting())]; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: // static michael@0: NSString* michael@0: nsCocoaUtils::ToNSString(const nsAString& aString) michael@0: { michael@0: if (aString.IsEmpty()) { michael@0: return [NSString string]; michael@0: } michael@0: return [NSString stringWithCharacters:reinterpret_cast(aString.BeginReading()) michael@0: length:aString.Length()]; michael@0: } michael@0: michael@0: // static michael@0: void michael@0: nsCocoaUtils::GeckoRectToNSRect(const nsIntRect& aGeckoRect, michael@0: NSRect& aOutCocoaRect) michael@0: { michael@0: aOutCocoaRect.origin.x = aGeckoRect.x; michael@0: aOutCocoaRect.origin.y = aGeckoRect.y; michael@0: aOutCocoaRect.size.width = aGeckoRect.width; michael@0: aOutCocoaRect.size.height = aGeckoRect.height; michael@0: } michael@0: michael@0: // static michael@0: void michael@0: nsCocoaUtils::NSRectToGeckoRect(const NSRect& aCocoaRect, michael@0: nsIntRect& aOutGeckoRect) michael@0: { michael@0: aOutGeckoRect.x = NSToIntRound(aCocoaRect.origin.x); michael@0: aOutGeckoRect.y = NSToIntRound(aCocoaRect.origin.y); michael@0: aOutGeckoRect.width = NSToIntRound(aCocoaRect.origin.x + aCocoaRect.size.width) - aOutGeckoRect.x; michael@0: aOutGeckoRect.height = NSToIntRound(aCocoaRect.origin.y + aCocoaRect.size.height) - aOutGeckoRect.y; michael@0: } michael@0: michael@0: // static michael@0: NSEvent* michael@0: nsCocoaUtils::MakeNewCocoaEventWithType(NSEventType aEventType, NSEvent *aEvent) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; michael@0: michael@0: NSEvent* newEvent = michael@0: [NSEvent keyEventWithType:aEventType michael@0: location:[aEvent locationInWindow] michael@0: modifierFlags:[aEvent modifierFlags] michael@0: timestamp:[aEvent timestamp] michael@0: windowNumber:[aEvent windowNumber] michael@0: context:[aEvent context] michael@0: characters:[aEvent characters] michael@0: charactersIgnoringModifiers:[aEvent charactersIgnoringModifiers] michael@0: isARepeat:[aEvent isARepeat] michael@0: keyCode:[aEvent keyCode]]; michael@0: return newEvent; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_NIL; michael@0: } michael@0: michael@0: // static michael@0: void michael@0: nsCocoaUtils::InitNPCocoaEvent(NPCocoaEvent* aNPCocoaEvent) michael@0: { michael@0: memset(aNPCocoaEvent, 0, sizeof(NPCocoaEvent)); michael@0: } michael@0: michael@0: // static michael@0: void michael@0: nsCocoaUtils::InitPluginEvent(WidgetPluginEvent &aPluginEvent, michael@0: NPCocoaEvent &aCocoaEvent) michael@0: { michael@0: aPluginEvent.time = PR_IntervalNow(); michael@0: aPluginEvent.pluginEvent = (void*)&aCocoaEvent; michael@0: aPluginEvent.retargetToFocusedDocument = false; michael@0: } michael@0: michael@0: // static michael@0: void michael@0: nsCocoaUtils::InitInputEvent(WidgetInputEvent& aInputEvent, michael@0: NSEvent* aNativeEvent) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: NSUInteger modifiers = michael@0: aNativeEvent ? [aNativeEvent modifierFlags] : [NSEvent modifierFlags]; michael@0: InitInputEvent(aInputEvent, modifiers); michael@0: michael@0: aInputEvent.time = PR_IntervalNow(); michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: // static michael@0: void michael@0: nsCocoaUtils::InitInputEvent(WidgetInputEvent& aInputEvent, michael@0: NSUInteger aModifiers) michael@0: { michael@0: aInputEvent.modifiers = 0; michael@0: if (aModifiers & NSShiftKeyMask) { michael@0: aInputEvent.modifiers |= MODIFIER_SHIFT; michael@0: } michael@0: if (aModifiers & NSControlKeyMask) { michael@0: aInputEvent.modifiers |= MODIFIER_CONTROL; michael@0: } michael@0: if (aModifiers & NSAlternateKeyMask) { michael@0: aInputEvent.modifiers |= MODIFIER_ALT; michael@0: // Mac's option key is similar to other platforms' AltGr key. michael@0: // Let's set AltGr flag when option key is pressed for consistency with michael@0: // other platforms. michael@0: aInputEvent.modifiers |= MODIFIER_ALTGRAPH; michael@0: } michael@0: if (aModifiers & NSCommandKeyMask) { michael@0: aInputEvent.modifiers |= MODIFIER_META; michael@0: } michael@0: michael@0: if (aModifiers & NSAlphaShiftKeyMask) { michael@0: aInputEvent.modifiers |= MODIFIER_CAPSLOCK; michael@0: } michael@0: // Mac doesn't have NumLock key. We can assume that NumLock is always locked michael@0: // if user is using a keyboard which has numpad. Otherwise, if user is using michael@0: // a keyboard which doesn't have numpad, e.g., MacBook's keyboard, we can michael@0: // assume that NumLock is always unlocked. michael@0: // Unfortunately, we cannot know whether current keyboard has numpad or not. michael@0: // We should notify locked state only when keys in numpad are pressed. michael@0: // By this, web applications may not be confused by unexpected numpad key's michael@0: // key event with unlocked state. michael@0: if (aModifiers & NSNumericPadKeyMask) { michael@0: aInputEvent.modifiers |= MODIFIER_NUMLOCK; michael@0: } michael@0: michael@0: // Be aware, NSFunctionKeyMask is included when arrow keys, home key or some michael@0: // other keys are pressed. We cannot check whether 'fn' key is pressed or michael@0: // not by the flag. michael@0: michael@0: } michael@0: michael@0: // static michael@0: UInt32 michael@0: nsCocoaUtils::ConvertToCarbonModifier(NSUInteger aCocoaModifier) michael@0: { michael@0: UInt32 carbonModifier = 0; michael@0: if (aCocoaModifier & NSAlphaShiftKeyMask) { michael@0: carbonModifier |= alphaLock; michael@0: } michael@0: if (aCocoaModifier & NSControlKeyMask) { michael@0: carbonModifier |= controlKey; michael@0: } michael@0: if (aCocoaModifier & NSAlternateKeyMask) { michael@0: carbonModifier |= optionKey; michael@0: } michael@0: if (aCocoaModifier & NSShiftKeyMask) { michael@0: carbonModifier |= shiftKey; michael@0: } michael@0: if (aCocoaModifier & NSCommandKeyMask) { michael@0: carbonModifier |= cmdKey; michael@0: } michael@0: if (aCocoaModifier & NSNumericPadKeyMask) { michael@0: carbonModifier |= kEventKeyModifierNumLockMask; michael@0: } michael@0: if (aCocoaModifier & NSFunctionKeyMask) { michael@0: carbonModifier |= kEventKeyModifierFnMask; michael@0: } michael@0: return carbonModifier; michael@0: } michael@0: michael@0: // While HiDPI support is not 100% complete and tested, we'll have a pref michael@0: // to allow it to be turned off in case of problems (or for testing purposes). michael@0: michael@0: // gfx.hidpi.enabled is an integer with the meaning: michael@0: // <= 0 : HiDPI support is disabled michael@0: // 1 : HiDPI enabled provided all screens have the same backing resolution michael@0: // > 1 : HiDPI enabled even if there are a mixture of screen modes michael@0: michael@0: // All the following code is to be removed once HiDPI work is more complete. michael@0: michael@0: static bool sHiDPIEnabled = false; michael@0: static bool sHiDPIPrefInitialized = false; michael@0: michael@0: // static michael@0: bool michael@0: nsCocoaUtils::HiDPIEnabled() michael@0: { michael@0: if (!sHiDPIPrefInitialized) { michael@0: sHiDPIPrefInitialized = true; michael@0: michael@0: int prefSetting = Preferences::GetInt("gfx.hidpi.enabled", 1); michael@0: if (prefSetting <= 0) { michael@0: return false; michael@0: } michael@0: michael@0: // prefSetting is at least 1, need to check attached screens... michael@0: michael@0: int scaleFactors = 0; // used as a bitset to track the screen types found michael@0: NSEnumerator *screenEnum = [[NSScreen screens] objectEnumerator]; michael@0: while (NSScreen *screen = [screenEnum nextObject]) { michael@0: NSDictionary *desc = [screen deviceDescription]; michael@0: if ([desc objectForKey:NSDeviceIsScreen] == nil) { michael@0: continue; michael@0: } michael@0: CGFloat scale = michael@0: [screen respondsToSelector:@selector(backingScaleFactor)] ? michael@0: [screen backingScaleFactor] : 1.0; michael@0: // Currently, we only care about differentiating "1.0" and "2.0", michael@0: // so we set one of the two low bits to record which. michael@0: if (scale > 1.0) { michael@0: scaleFactors |= 2; michael@0: } else { michael@0: scaleFactors |= 1; michael@0: } michael@0: } michael@0: michael@0: // Now scaleFactors will be: michael@0: // 0 if no screens (supporting backingScaleFactor) found michael@0: // 1 if only lo-DPI screens michael@0: // 2 if only hi-DPI screens michael@0: // 3 if both lo- and hi-DPI screens michael@0: // We'll enable HiDPI support if there's only a single screen type, michael@0: // OR if the pref setting is explicitly greater than 1. michael@0: sHiDPIEnabled = (scaleFactors <= 2) || (prefSetting > 1); michael@0: } michael@0: michael@0: return sHiDPIEnabled; michael@0: } michael@0: michael@0: void michael@0: nsCocoaUtils::GetCommandsFromKeyEvent(NSEvent* aEvent, michael@0: nsTArray& aCommands) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: MOZ_ASSERT(aEvent); michael@0: michael@0: static NativeKeyBindingsRecorder* sNativeKeyBindingsRecorder; michael@0: if (!sNativeKeyBindingsRecorder) { michael@0: sNativeKeyBindingsRecorder = [NativeKeyBindingsRecorder new]; michael@0: } michael@0: michael@0: [sNativeKeyBindingsRecorder startRecording:aCommands]; michael@0: michael@0: // This will trigger 0 - N calls to doCommandBySelector: and insertText: michael@0: [sNativeKeyBindingsRecorder michael@0: interpretKeyEvents:[NSArray arrayWithObject:aEvent]]; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: @implementation NativeKeyBindingsRecorder michael@0: michael@0: - (void)startRecording:(nsTArray&)aCommands michael@0: { michael@0: mCommands = &aCommands; michael@0: mCommands->Clear(); michael@0: } michael@0: michael@0: - (void)doCommandBySelector:(SEL)aSelector michael@0: { michael@0: KeyBindingsCommand command = { michael@0: aSelector, michael@0: nil michael@0: }; michael@0: michael@0: mCommands->AppendElement(command); michael@0: } michael@0: michael@0: - (void)insertText:(id)aString michael@0: { michael@0: KeyBindingsCommand command = { michael@0: @selector(insertText:), michael@0: aString michael@0: }; michael@0: michael@0: mCommands->AppendElement(command); michael@0: } michael@0: michael@0: @end // NativeKeyBindingsRecorder michael@0: michael@0: struct KeyConversionData michael@0: { michael@0: const char* str; michael@0: size_t strLength; michael@0: uint32_t geckoKeyCode; michael@0: uint32_t charCode; michael@0: }; michael@0: michael@0: static const KeyConversionData gKeyConversions[] = { michael@0: michael@0: #define KEYCODE_ENTRY(aStr, aCode) \ michael@0: {#aStr, sizeof(#aStr) - 1, NS_##aStr, aCode} michael@0: michael@0: // Some keycodes may have different name in nsIDOMKeyEvent from its key name. michael@0: #define KEYCODE_ENTRY2(aStr, aNSName, aCode) \ michael@0: {#aStr, sizeof(#aStr) - 1, NS_##aNSName, aCode} michael@0: michael@0: KEYCODE_ENTRY(VK_CANCEL, 0x001B), michael@0: KEYCODE_ENTRY(VK_DELETE, NSDeleteFunctionKey), michael@0: KEYCODE_ENTRY(VK_BACK, NSBackspaceCharacter), michael@0: KEYCODE_ENTRY2(VK_BACK_SPACE, VK_BACK, NSBackspaceCharacter), michael@0: KEYCODE_ENTRY(VK_TAB, NSTabCharacter), michael@0: KEYCODE_ENTRY(VK_CLEAR, NSClearLineFunctionKey), michael@0: KEYCODE_ENTRY(VK_RETURN, NSEnterCharacter), michael@0: KEYCODE_ENTRY(VK_SHIFT, 0), michael@0: KEYCODE_ENTRY(VK_CONTROL, 0), michael@0: KEYCODE_ENTRY(VK_ALT, 0), michael@0: KEYCODE_ENTRY(VK_PAUSE, NSPauseFunctionKey), michael@0: KEYCODE_ENTRY(VK_CAPS_LOCK, 0), michael@0: KEYCODE_ENTRY(VK_ESCAPE, 0), michael@0: KEYCODE_ENTRY(VK_SPACE, ' '), michael@0: KEYCODE_ENTRY(VK_PAGE_UP, NSPageUpFunctionKey), michael@0: KEYCODE_ENTRY(VK_PAGE_DOWN, NSPageDownFunctionKey), michael@0: KEYCODE_ENTRY(VK_END, NSEndFunctionKey), michael@0: KEYCODE_ENTRY(VK_HOME, NSHomeFunctionKey), michael@0: KEYCODE_ENTRY(VK_LEFT, NSLeftArrowFunctionKey), michael@0: KEYCODE_ENTRY(VK_UP, NSUpArrowFunctionKey), michael@0: KEYCODE_ENTRY(VK_RIGHT, NSRightArrowFunctionKey), michael@0: KEYCODE_ENTRY(VK_DOWN, NSDownArrowFunctionKey), michael@0: KEYCODE_ENTRY(VK_PRINTSCREEN, NSPrintScreenFunctionKey), michael@0: KEYCODE_ENTRY(VK_INSERT, NSInsertFunctionKey), michael@0: KEYCODE_ENTRY(VK_HELP, NSHelpFunctionKey), michael@0: KEYCODE_ENTRY(VK_0, '0'), michael@0: KEYCODE_ENTRY(VK_1, '1'), michael@0: KEYCODE_ENTRY(VK_2, '2'), michael@0: KEYCODE_ENTRY(VK_3, '3'), michael@0: KEYCODE_ENTRY(VK_4, '4'), michael@0: KEYCODE_ENTRY(VK_5, '5'), michael@0: KEYCODE_ENTRY(VK_6, '6'), michael@0: KEYCODE_ENTRY(VK_7, '7'), michael@0: KEYCODE_ENTRY(VK_8, '8'), michael@0: KEYCODE_ENTRY(VK_9, '9'), michael@0: KEYCODE_ENTRY(VK_SEMICOLON, ':'), michael@0: KEYCODE_ENTRY(VK_EQUALS, '='), michael@0: KEYCODE_ENTRY(VK_A, 'A'), michael@0: KEYCODE_ENTRY(VK_B, 'B'), michael@0: KEYCODE_ENTRY(VK_C, 'C'), michael@0: KEYCODE_ENTRY(VK_D, 'D'), michael@0: KEYCODE_ENTRY(VK_E, 'E'), michael@0: KEYCODE_ENTRY(VK_F, 'F'), michael@0: KEYCODE_ENTRY(VK_G, 'G'), michael@0: KEYCODE_ENTRY(VK_H, 'H'), michael@0: KEYCODE_ENTRY(VK_I, 'I'), michael@0: KEYCODE_ENTRY(VK_J, 'J'), michael@0: KEYCODE_ENTRY(VK_K, 'K'), michael@0: KEYCODE_ENTRY(VK_L, 'L'), michael@0: KEYCODE_ENTRY(VK_M, 'M'), michael@0: KEYCODE_ENTRY(VK_N, 'N'), michael@0: KEYCODE_ENTRY(VK_O, 'O'), michael@0: KEYCODE_ENTRY(VK_P, 'P'), michael@0: KEYCODE_ENTRY(VK_Q, 'Q'), michael@0: KEYCODE_ENTRY(VK_R, 'R'), michael@0: KEYCODE_ENTRY(VK_S, 'S'), michael@0: KEYCODE_ENTRY(VK_T, 'T'), michael@0: KEYCODE_ENTRY(VK_U, 'U'), michael@0: KEYCODE_ENTRY(VK_V, 'V'), michael@0: KEYCODE_ENTRY(VK_W, 'W'), michael@0: KEYCODE_ENTRY(VK_X, 'X'), michael@0: KEYCODE_ENTRY(VK_Y, 'Y'), michael@0: KEYCODE_ENTRY(VK_Z, 'Z'), michael@0: KEYCODE_ENTRY(VK_CONTEXT_MENU, NSMenuFunctionKey), michael@0: KEYCODE_ENTRY(VK_NUMPAD0, '0'), michael@0: KEYCODE_ENTRY(VK_NUMPAD1, '1'), michael@0: KEYCODE_ENTRY(VK_NUMPAD2, '2'), michael@0: KEYCODE_ENTRY(VK_NUMPAD3, '3'), michael@0: KEYCODE_ENTRY(VK_NUMPAD4, '4'), michael@0: KEYCODE_ENTRY(VK_NUMPAD5, '5'), michael@0: KEYCODE_ENTRY(VK_NUMPAD6, '6'), michael@0: KEYCODE_ENTRY(VK_NUMPAD7, '7'), michael@0: KEYCODE_ENTRY(VK_NUMPAD8, '8'), michael@0: KEYCODE_ENTRY(VK_NUMPAD9, '9'), michael@0: KEYCODE_ENTRY(VK_MULTIPLY, '*'), michael@0: KEYCODE_ENTRY(VK_ADD, '+'), michael@0: KEYCODE_ENTRY(VK_SEPARATOR, 0), michael@0: KEYCODE_ENTRY(VK_SUBTRACT, '-'), michael@0: KEYCODE_ENTRY(VK_DECIMAL, '.'), michael@0: KEYCODE_ENTRY(VK_DIVIDE, '/'), michael@0: KEYCODE_ENTRY(VK_F1, NSF1FunctionKey), michael@0: KEYCODE_ENTRY(VK_F2, NSF2FunctionKey), michael@0: KEYCODE_ENTRY(VK_F3, NSF3FunctionKey), michael@0: KEYCODE_ENTRY(VK_F4, NSF4FunctionKey), michael@0: KEYCODE_ENTRY(VK_F5, NSF5FunctionKey), michael@0: KEYCODE_ENTRY(VK_F6, NSF6FunctionKey), michael@0: KEYCODE_ENTRY(VK_F7, NSF7FunctionKey), michael@0: KEYCODE_ENTRY(VK_F8, NSF8FunctionKey), michael@0: KEYCODE_ENTRY(VK_F9, NSF9FunctionKey), michael@0: KEYCODE_ENTRY(VK_F10, NSF10FunctionKey), michael@0: KEYCODE_ENTRY(VK_F11, NSF11FunctionKey), michael@0: KEYCODE_ENTRY(VK_F12, NSF12FunctionKey), michael@0: KEYCODE_ENTRY(VK_F13, NSF13FunctionKey), michael@0: KEYCODE_ENTRY(VK_F14, NSF14FunctionKey), michael@0: KEYCODE_ENTRY(VK_F15, NSF15FunctionKey), michael@0: KEYCODE_ENTRY(VK_F16, NSF16FunctionKey), michael@0: KEYCODE_ENTRY(VK_F17, NSF17FunctionKey), michael@0: KEYCODE_ENTRY(VK_F18, NSF18FunctionKey), michael@0: KEYCODE_ENTRY(VK_F19, NSF19FunctionKey), michael@0: KEYCODE_ENTRY(VK_F20, NSF20FunctionKey), michael@0: KEYCODE_ENTRY(VK_F21, NSF21FunctionKey), michael@0: KEYCODE_ENTRY(VK_F22, NSF22FunctionKey), michael@0: KEYCODE_ENTRY(VK_F23, NSF23FunctionKey), michael@0: KEYCODE_ENTRY(VK_F24, NSF24FunctionKey), michael@0: KEYCODE_ENTRY(VK_NUM_LOCK, NSClearLineFunctionKey), michael@0: KEYCODE_ENTRY(VK_SCROLL_LOCK, NSScrollLockFunctionKey), michael@0: KEYCODE_ENTRY(VK_COMMA, ','), michael@0: KEYCODE_ENTRY(VK_PERIOD, '.'), michael@0: KEYCODE_ENTRY(VK_SLASH, '/'), michael@0: KEYCODE_ENTRY(VK_BACK_QUOTE, '`'), michael@0: KEYCODE_ENTRY(VK_OPEN_BRACKET, '['), michael@0: KEYCODE_ENTRY(VK_BACK_SLASH, '\\'), michael@0: KEYCODE_ENTRY(VK_CLOSE_BRACKET, ']'), michael@0: KEYCODE_ENTRY(VK_QUOTE, '\'') michael@0: michael@0: #undef KEYCODE_ENTRY michael@0: michael@0: }; michael@0: michael@0: uint32_t michael@0: nsCocoaUtils::ConvertGeckoNameToMacCharCode(const nsAString& aKeyCodeName) michael@0: { michael@0: if (aKeyCodeName.IsEmpty()) { michael@0: return 0; michael@0: } michael@0: michael@0: nsAutoCString keyCodeName; michael@0: keyCodeName.AssignWithConversion(aKeyCodeName); michael@0: // We want case-insensitive comparison with data stored as uppercase. michael@0: ToUpperCase(keyCodeName); michael@0: michael@0: uint32_t keyCodeNameLength = keyCodeName.Length(); michael@0: const char* keyCodeNameStr = keyCodeName.get(); michael@0: for (uint16_t i = 0; i < ArrayLength(gKeyConversions); ++i) { michael@0: if (keyCodeNameLength == gKeyConversions[i].strLength && michael@0: nsCRT::strcmp(gKeyConversions[i].str, keyCodeNameStr) == 0) { michael@0: return gKeyConversions[i].charCode; michael@0: } michael@0: } michael@0: michael@0: return 0; michael@0: } michael@0: michael@0: uint32_t michael@0: nsCocoaUtils::ConvertGeckoKeyCodeToMacCharCode(uint32_t aKeyCode) michael@0: { michael@0: if (!aKeyCode) { michael@0: return 0; michael@0: } michael@0: michael@0: for (uint16_t i = 0; i < ArrayLength(gKeyConversions); ++i) { michael@0: if (gKeyConversions[i].geckoKeyCode == aKeyCode) { michael@0: return gKeyConversions[i].charCode; michael@0: } michael@0: } michael@0: michael@0: return 0; michael@0: }