1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/widget/cocoa/nsCocoaUtils.mm Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,912 @@ 1.4 +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +#include "gfxImageSurface.h" 1.10 +#include "gfxPlatform.h" 1.11 +#include "gfxUtils.h" 1.12 +#include "nsCocoaUtils.h" 1.13 +#include "nsChildView.h" 1.14 +#include "nsMenuBarX.h" 1.15 +#include "nsCocoaWindow.h" 1.16 +#include "nsCOMPtr.h" 1.17 +#include "nsIInterfaceRequestorUtils.h" 1.18 +#include "nsIAppShellService.h" 1.19 +#include "nsIXULWindow.h" 1.20 +#include "nsIBaseWindow.h" 1.21 +#include "nsIServiceManager.h" 1.22 +#include "nsMenuUtilsX.h" 1.23 +#include "nsToolkit.h" 1.24 +#include "nsCRT.h" 1.25 +#include "mozilla/gfx/2D.h" 1.26 +#include "mozilla/MiscEvents.h" 1.27 +#include "mozilla/Preferences.h" 1.28 +#include "mozilla/TextEvents.h" 1.29 + 1.30 +using namespace mozilla; 1.31 +using namespace mozilla::widget; 1.32 + 1.33 +using mozilla::gfx::BackendType; 1.34 +using mozilla::gfx::DataSourceSurface; 1.35 +using mozilla::gfx::DrawTarget; 1.36 +using mozilla::gfx::Factory; 1.37 +using mozilla::gfx::IntPoint; 1.38 +using mozilla::gfx::IntRect; 1.39 +using mozilla::gfx::IntSize; 1.40 +using mozilla::gfx::SurfaceFormat; 1.41 +using mozilla::gfx::SourceSurface; 1.42 + 1.43 +static float 1.44 +MenuBarScreenHeight() 1.45 +{ 1.46 + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN; 1.47 + 1.48 + NSArray* allScreens = [NSScreen screens]; 1.49 + if ([allScreens count]) { 1.50 + return [[allScreens objectAtIndex:0] frame].size.height; 1.51 + } 1.52 + 1.53 + return 0.0; 1.54 + 1.55 + NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(0.0); 1.56 +} 1.57 + 1.58 +float 1.59 +nsCocoaUtils::FlippedScreenY(float y) 1.60 +{ 1.61 + return MenuBarScreenHeight() - y; 1.62 +} 1.63 + 1.64 +NSRect nsCocoaUtils::GeckoRectToCocoaRect(const nsIntRect &geckoRect) 1.65 +{ 1.66 + // We only need to change the Y coordinate by starting with the primary screen 1.67 + // height and subtracting the gecko Y coordinate of the bottom of the rect. 1.68 + return NSMakeRect(geckoRect.x, 1.69 + MenuBarScreenHeight() - geckoRect.YMost(), 1.70 + geckoRect.width, 1.71 + geckoRect.height); 1.72 +} 1.73 + 1.74 +NSRect nsCocoaUtils::GeckoRectToCocoaRectDevPix(const nsIntRect &aGeckoRect, 1.75 + CGFloat aBackingScale) 1.76 +{ 1.77 + return NSMakeRect(aGeckoRect.x / aBackingScale, 1.78 + MenuBarScreenHeight() - aGeckoRect.YMost() / aBackingScale, 1.79 + aGeckoRect.width / aBackingScale, 1.80 + aGeckoRect.height / aBackingScale); 1.81 +} 1.82 + 1.83 +nsIntRect nsCocoaUtils::CocoaRectToGeckoRect(const NSRect &cocoaRect) 1.84 +{ 1.85 + // We only need to change the Y coordinate by starting with the primary screen 1.86 + // height and subtracting both the cocoa y origin and the height of the 1.87 + // cocoa rect. 1.88 + nsIntRect rect; 1.89 + rect.x = NSToIntRound(cocoaRect.origin.x); 1.90 + rect.y = NSToIntRound(FlippedScreenY(cocoaRect.origin.y + cocoaRect.size.height)); 1.91 + rect.width = NSToIntRound(cocoaRect.origin.x + cocoaRect.size.width) - rect.x; 1.92 + rect.height = NSToIntRound(FlippedScreenY(cocoaRect.origin.y)) - rect.y; 1.93 + return rect; 1.94 +} 1.95 + 1.96 +nsIntRect nsCocoaUtils::CocoaRectToGeckoRectDevPix(const NSRect &aCocoaRect, 1.97 + CGFloat aBackingScale) 1.98 +{ 1.99 + nsIntRect rect; 1.100 + rect.x = NSToIntRound(aCocoaRect.origin.x * aBackingScale); 1.101 + rect.y = NSToIntRound(FlippedScreenY(aCocoaRect.origin.y + aCocoaRect.size.height) * aBackingScale); 1.102 + rect.width = NSToIntRound((aCocoaRect.origin.x + aCocoaRect.size.width) * aBackingScale) - rect.x; 1.103 + rect.height = NSToIntRound(FlippedScreenY(aCocoaRect.origin.y) * aBackingScale) - rect.y; 1.104 + return rect; 1.105 +} 1.106 + 1.107 +NSPoint nsCocoaUtils::ScreenLocationForEvent(NSEvent* anEvent) 1.108 +{ 1.109 + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN; 1.110 + 1.111 + // Don't trust mouse locations of mouse move events, see bug 443178. 1.112 + if (!anEvent || [anEvent type] == NSMouseMoved) 1.113 + return [NSEvent mouseLocation]; 1.114 + 1.115 + // Pin momentum scroll events to the location of the last user-controlled 1.116 + // scroll event. 1.117 + if (IsMomentumScrollEvent(anEvent)) 1.118 + return ChildViewMouseTracker::sLastScrollEventScreenLocation; 1.119 + 1.120 + return [[anEvent window] convertBaseToScreen:[anEvent locationInWindow]]; 1.121 + 1.122 + NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NSMakePoint(0.0, 0.0)); 1.123 +} 1.124 + 1.125 +BOOL nsCocoaUtils::IsEventOverWindow(NSEvent* anEvent, NSWindow* aWindow) 1.126 +{ 1.127 + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN; 1.128 + 1.129 + return NSPointInRect(ScreenLocationForEvent(anEvent), [aWindow frame]); 1.130 + 1.131 + NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NO); 1.132 +} 1.133 + 1.134 +NSPoint nsCocoaUtils::EventLocationForWindow(NSEvent* anEvent, NSWindow* aWindow) 1.135 +{ 1.136 + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN; 1.137 + 1.138 + return [aWindow convertScreenToBase:ScreenLocationForEvent(anEvent)]; 1.139 + 1.140 + NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NSMakePoint(0.0, 0.0)); 1.141 +} 1.142 + 1.143 +BOOL nsCocoaUtils::IsMomentumScrollEvent(NSEvent* aEvent) 1.144 +{ 1.145 + if ([aEvent type] != NSScrollWheel) 1.146 + return NO; 1.147 + 1.148 + if ([aEvent respondsToSelector:@selector(momentumPhase)]) 1.149 + return ([aEvent momentumPhase] & NSEventPhaseChanged) != 0; 1.150 + 1.151 + if ([aEvent respondsToSelector:@selector(_scrollPhase)]) 1.152 + return [aEvent _scrollPhase] != 0; 1.153 + 1.154 + return NO; 1.155 +} 1.156 + 1.157 +void nsCocoaUtils::HideOSChromeOnScreen(bool aShouldHide, NSScreen* aScreen) 1.158 +{ 1.159 + NS_OBJC_BEGIN_TRY_ABORT_BLOCK; 1.160 + 1.161 + // Keep track of how many hiding requests have been made, so that they can 1.162 + // be nested. 1.163 + static int sMenuBarHiddenCount = 0, sDockHiddenCount = 0; 1.164 + 1.165 + // Always hide the Dock, since it's not necessarily on the primary screen. 1.166 + sDockHiddenCount += aShouldHide ? 1 : -1; 1.167 + NS_ASSERTION(sMenuBarHiddenCount >= 0, "Unbalanced HideMenuAndDockForWindow calls"); 1.168 + 1.169 + // Only hide the menu bar if the window is on the same screen. 1.170 + // The menu bar is always on the first screen in the screen list. 1.171 + if (aScreen == [[NSScreen screens] objectAtIndex:0]) { 1.172 + sMenuBarHiddenCount += aShouldHide ? 1 : -1; 1.173 + NS_ASSERTION(sDockHiddenCount >= 0, "Unbalanced HideMenuAndDockForWindow calls"); 1.174 + } 1.175 + 1.176 + // TODO This should be upgraded to use [NSApplication setPresentationOptions:] 1.177 + // when support for 10.5 is dropped. 1.178 + if (sMenuBarHiddenCount > 0) { 1.179 + ::SetSystemUIMode(kUIModeAllHidden, 0); 1.180 + } else if (sDockHiddenCount > 0) { 1.181 + ::SetSystemUIMode(kUIModeContentHidden, 0); 1.182 + } else { 1.183 + ::SetSystemUIMode(kUIModeNormal, 0); 1.184 + } 1.185 + 1.186 + NS_OBJC_END_TRY_ABORT_BLOCK; 1.187 +} 1.188 + 1.189 + 1.190 +#define NS_APPSHELLSERVICE_CONTRACTID "@mozilla.org/appshell/appShellService;1" 1.191 +nsIWidget* nsCocoaUtils::GetHiddenWindowWidget() 1.192 +{ 1.193 + nsCOMPtr<nsIAppShellService> appShell(do_GetService(NS_APPSHELLSERVICE_CONTRACTID)); 1.194 + if (!appShell) { 1.195 + NS_WARNING("Couldn't get AppShellService in order to get hidden window ref"); 1.196 + return nullptr; 1.197 + } 1.198 + 1.199 + nsCOMPtr<nsIXULWindow> hiddenWindow; 1.200 + appShell->GetHiddenWindow(getter_AddRefs(hiddenWindow)); 1.201 + if (!hiddenWindow) { 1.202 + // Don't warn, this happens during shutdown, bug 358607. 1.203 + return nullptr; 1.204 + } 1.205 + 1.206 + nsCOMPtr<nsIBaseWindow> baseHiddenWindow; 1.207 + baseHiddenWindow = do_GetInterface(hiddenWindow); 1.208 + if (!baseHiddenWindow) { 1.209 + NS_WARNING("Couldn't get nsIBaseWindow from hidden window (nsIXULWindow)"); 1.210 + return nullptr; 1.211 + } 1.212 + 1.213 + nsCOMPtr<nsIWidget> hiddenWindowWidget; 1.214 + if (NS_FAILED(baseHiddenWindow->GetMainWidget(getter_AddRefs(hiddenWindowWidget)))) { 1.215 + NS_WARNING("Couldn't get nsIWidget from hidden window (nsIBaseWindow)"); 1.216 + return nullptr; 1.217 + } 1.218 + 1.219 + return hiddenWindowWidget; 1.220 +} 1.221 + 1.222 +void nsCocoaUtils::PrepareForNativeAppModalDialog() 1.223 +{ 1.224 + NS_OBJC_BEGIN_TRY_ABORT_BLOCK; 1.225 + 1.226 + // Don't do anything if this is embedding. We'll assume that if there is no hidden 1.227 + // window we shouldn't do anything, and that should cover the embedding case. 1.228 + nsMenuBarX* hiddenWindowMenuBar = nsMenuUtilsX::GetHiddenWindowMenuBar(); 1.229 + if (!hiddenWindowMenuBar) 1.230 + return; 1.231 + 1.232 + // First put up the hidden window menu bar so that app menu event handling is correct. 1.233 + hiddenWindowMenuBar->Paint(); 1.234 + 1.235 + NSMenu* mainMenu = [NSApp mainMenu]; 1.236 + NS_ASSERTION([mainMenu numberOfItems] > 0, "Main menu does not have any items, something is terribly wrong!"); 1.237 + 1.238 + // Create new menu bar for use with modal dialog 1.239 + NSMenu* newMenuBar = [[NSMenu alloc] initWithTitle:@""]; 1.240 + 1.241 + // Swap in our app menu. Note that the event target is whatever window is up when 1.242 + // the app modal dialog goes up. 1.243 + NSMenuItem* firstMenuItem = [[mainMenu itemAtIndex:0] retain]; 1.244 + [mainMenu removeItemAtIndex:0]; 1.245 + [newMenuBar insertItem:firstMenuItem atIndex:0]; 1.246 + [firstMenuItem release]; 1.247 + 1.248 + // Add standard edit menu 1.249 + [newMenuBar addItem:nsMenuUtilsX::GetStandardEditMenuItem()]; 1.250 + 1.251 + // Show the new menu bar 1.252 + [NSApp setMainMenu:newMenuBar]; 1.253 + [newMenuBar release]; 1.254 + 1.255 + NS_OBJC_END_TRY_ABORT_BLOCK; 1.256 +} 1.257 + 1.258 +void nsCocoaUtils::CleanUpAfterNativeAppModalDialog() 1.259 +{ 1.260 + NS_OBJC_BEGIN_TRY_ABORT_BLOCK; 1.261 + 1.262 + // Don't do anything if this is embedding. We'll assume that if there is no hidden 1.263 + // window we shouldn't do anything, and that should cover the embedding case. 1.264 + nsMenuBarX* hiddenWindowMenuBar = nsMenuUtilsX::GetHiddenWindowMenuBar(); 1.265 + if (!hiddenWindowMenuBar) 1.266 + return; 1.267 + 1.268 + NSWindow* mainWindow = [NSApp mainWindow]; 1.269 + if (!mainWindow) 1.270 + hiddenWindowMenuBar->Paint(); 1.271 + else 1.272 + [WindowDelegate paintMenubarForWindow:mainWindow]; 1.273 + 1.274 + NS_OBJC_END_TRY_ABORT_BLOCK; 1.275 +} 1.276 + 1.277 +void data_ss_release_callback(void *aDataSourceSurface, 1.278 + const void *data, 1.279 + size_t size) 1.280 +{ 1.281 + if (aDataSourceSurface) { 1.282 + static_cast<DataSourceSurface*>(aDataSourceSurface)->Unmap(); 1.283 + static_cast<DataSourceSurface*>(aDataSourceSurface)->Release(); 1.284 + } 1.285 +} 1.286 + 1.287 +nsresult nsCocoaUtils::CreateCGImageFromSurface(SourceSurface* aSurface, 1.288 + CGImageRef* aResult) 1.289 +{ 1.290 + RefPtr<DataSourceSurface> dataSurface; 1.291 + 1.292 + if (aSurface->GetFormat() == SurfaceFormat::B8G8R8A8) { 1.293 + dataSurface = aSurface->GetDataSurface(); 1.294 + } else { 1.295 + // CGImageCreate only supports 16- and 32-bit bit-depth 1.296 + // Convert format to SurfaceFormat::B8G8R8A8 1.297 + dataSurface = gfxUtils:: 1.298 + CopySurfaceToDataSourceSurfaceWithFormat(aSurface, 1.299 + SurfaceFormat::B8G8R8A8); 1.300 + } 1.301 + 1.302 + NS_ENSURE_TRUE(dataSurface, NS_ERROR_FAILURE); 1.303 + 1.304 + int32_t width = dataSurface->GetSize().width; 1.305 + int32_t height = dataSurface->GetSize().height; 1.306 + if (height < 1 || width < 1) { 1.307 + return NS_ERROR_FAILURE; 1.308 + } 1.309 + 1.310 + DataSourceSurface::MappedSurface map; 1.311 + if (!dataSurface->Map(DataSourceSurface::MapType::READ, &map)) { 1.312 + return NS_ERROR_FAILURE; 1.313 + } 1.314 + // The Unmap() call happens in data_ss_release_callback 1.315 + 1.316 + // Create a CGImageRef with the bits from the image, taking into account 1.317 + // the alpha ordering and endianness of the machine so we don't have to 1.318 + // touch the bits ourselves. 1.319 + CGDataProviderRef dataProvider = ::CGDataProviderCreateWithData(dataSurface.forget().drop(), 1.320 + map.mData, 1.321 + map.mStride * height, 1.322 + data_ss_release_callback); 1.323 + CGColorSpaceRef colorSpace = ::CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB); 1.324 + *aResult = ::CGImageCreate(width, 1.325 + height, 1.326 + 8, 1.327 + 32, 1.328 + map.mStride, 1.329 + colorSpace, 1.330 + kCGBitmapByteOrder32Host | kCGImageAlphaPremultipliedFirst, 1.331 + dataProvider, 1.332 + NULL, 1.333 + 0, 1.334 + kCGRenderingIntentDefault); 1.335 + ::CGColorSpaceRelease(colorSpace); 1.336 + ::CGDataProviderRelease(dataProvider); 1.337 + return *aResult ? NS_OK : NS_ERROR_FAILURE; 1.338 +} 1.339 + 1.340 +nsresult nsCocoaUtils::CreateNSImageFromCGImage(CGImageRef aInputImage, NSImage **aResult) 1.341 +{ 1.342 + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; 1.343 + 1.344 + // Be very careful when creating the NSImage that the backing NSImageRep is 1.345 + // exactly 1:1 with the input image. On a retina display, both [NSImage 1.346 + // lockFocus] and [NSImage initWithCGImage:size:] will create an image with a 1.347 + // 2x backing NSImageRep. This prevents NSCursor from recognizing a retina 1.348 + // cursor, which only occurs if pixelsWide and pixelsHigh are exactly 2x the 1.349 + // size of the NSImage. 1.350 + // 1.351 + // For example, if a 32x32 SVG cursor is rendered on a retina display, then 1.352 + // aInputImage will be 64x64. The resulting NSImage will be scaled back down 1.353 + // to 32x32 so it stays the correct size on the screen by changing its size 1.354 + // (resizing a NSImage only scales the image and doesn't resample the data). 1.355 + // If aInputImage is converted using [NSImage initWithCGImage:size:] then the 1.356 + // bitmap will be 128x128 and NSCursor won't recognize a retina cursor, since 1.357 + // it will expect a 64x64 bitmap. 1.358 + 1.359 + int32_t width = ::CGImageGetWidth(aInputImage); 1.360 + int32_t height = ::CGImageGetHeight(aInputImage); 1.361 + NSRect imageRect = ::NSMakeRect(0.0, 0.0, width, height); 1.362 + 1.363 + NSBitmapImageRep *offscreenRep = [[NSBitmapImageRep alloc] 1.364 + initWithBitmapDataPlanes:NULL 1.365 + pixelsWide:width 1.366 + pixelsHigh:height 1.367 + bitsPerSample:8 1.368 + samplesPerPixel:4 1.369 + hasAlpha:YES 1.370 + isPlanar:NO 1.371 + colorSpaceName:NSDeviceRGBColorSpace 1.372 + bitmapFormat:NSAlphaFirstBitmapFormat 1.373 + bytesPerRow:0 1.374 + bitsPerPixel:0]; 1.375 + 1.376 + NSGraphicsContext *context = [NSGraphicsContext graphicsContextWithBitmapImageRep:offscreenRep]; 1.377 + [NSGraphicsContext saveGraphicsState]; 1.378 + [NSGraphicsContext setCurrentContext:context]; 1.379 + 1.380 + // Get the Quartz context and draw. 1.381 + CGContextRef imageContext = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort]; 1.382 + ::CGContextDrawImage(imageContext, *(CGRect*)&imageRect, aInputImage); 1.383 + 1.384 + [NSGraphicsContext restoreGraphicsState]; 1.385 + 1.386 + *aResult = [[NSImage alloc] initWithSize:NSMakeSize(width, height)]; 1.387 + [*aResult addRepresentation:offscreenRep]; 1.388 + [offscreenRep release]; 1.389 + return NS_OK; 1.390 + 1.391 + NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; 1.392 +} 1.393 + 1.394 +nsresult nsCocoaUtils::CreateNSImageFromImageContainer(imgIContainer *aImage, uint32_t aWhichFrame, NSImage **aResult, CGFloat scaleFactor) 1.395 +{ 1.396 + RefPtr<SourceSurface> surface; 1.397 + int32_t width = 0, height = 0; 1.398 + aImage->GetWidth(&width); 1.399 + aImage->GetHeight(&height); 1.400 + 1.401 + // Render a vector image at the correct resolution on a retina display 1.402 + if (aImage->GetType() == imgIContainer::TYPE_VECTOR && scaleFactor != 1.0f) { 1.403 + int scaledWidth = (int)ceilf(width * scaleFactor); 1.404 + int scaledHeight = (int)ceilf(height * scaleFactor); 1.405 + 1.406 + RefPtr<DrawTarget> drawTarget = gfxPlatform::GetPlatform()-> 1.407 + CreateOffscreenContentDrawTarget(IntSize(scaledWidth, scaledHeight), 1.408 + SurfaceFormat::B8G8R8A8); 1.409 + if (!drawTarget) { 1.410 + NS_ERROR("Failed to create DrawTarget"); 1.411 + return NS_ERROR_FAILURE; 1.412 + } 1.413 + 1.414 + nsRefPtr<gfxContext> context = new gfxContext(drawTarget); 1.415 + if (!context) { 1.416 + NS_ERROR("Failed to create gfxContext"); 1.417 + return NS_ERROR_FAILURE; 1.418 + } 1.419 + 1.420 + aImage->Draw(context, GraphicsFilter::FILTER_NEAREST, gfxMatrix(), 1.421 + gfxRect(0.0f, 0.0f, scaledWidth, scaledHeight), 1.422 + nsIntRect(0, 0, width, height), 1.423 + nsIntSize(scaledWidth, scaledHeight), 1.424 + nullptr, aWhichFrame, imgIContainer::FLAG_SYNC_DECODE); 1.425 + 1.426 + surface = drawTarget->Snapshot(); 1.427 + } else { 1.428 + surface = aImage->GetFrame(aWhichFrame, imgIContainer::FLAG_SYNC_DECODE); 1.429 + } 1.430 + 1.431 + NS_ENSURE_TRUE(surface, NS_ERROR_FAILURE); 1.432 + 1.433 + CGImageRef imageRef = NULL; 1.434 + nsresult rv = nsCocoaUtils::CreateCGImageFromSurface(surface, &imageRef); 1.435 + if (NS_FAILED(rv) || !imageRef) { 1.436 + return NS_ERROR_FAILURE; 1.437 + } 1.438 + 1.439 + rv = nsCocoaUtils::CreateNSImageFromCGImage(imageRef, aResult); 1.440 + if (NS_FAILED(rv) || !aResult) { 1.441 + return NS_ERROR_FAILURE; 1.442 + } 1.443 + ::CGImageRelease(imageRef); 1.444 + 1.445 + // Ensure the image will be rendered the correct size on a retina display 1.446 + NSSize size = NSMakeSize(width, height); 1.447 + [*aResult setSize:size]; 1.448 + [[[*aResult representations] objectAtIndex:0] setSize:size]; 1.449 + return NS_OK; 1.450 +} 1.451 + 1.452 +// static 1.453 +void 1.454 +nsCocoaUtils::GetStringForNSString(const NSString *aSrc, nsAString& aDist) 1.455 +{ 1.456 + NS_OBJC_BEGIN_TRY_ABORT_BLOCK; 1.457 + 1.458 + if (!aSrc) { 1.459 + aDist.Truncate(); 1.460 + return; 1.461 + } 1.462 + 1.463 + aDist.SetLength([aSrc length]); 1.464 + [aSrc getCharacters: reinterpret_cast<unichar*>(aDist.BeginWriting())]; 1.465 + 1.466 + NS_OBJC_END_TRY_ABORT_BLOCK; 1.467 +} 1.468 + 1.469 +// static 1.470 +NSString* 1.471 +nsCocoaUtils::ToNSString(const nsAString& aString) 1.472 +{ 1.473 + if (aString.IsEmpty()) { 1.474 + return [NSString string]; 1.475 + } 1.476 + return [NSString stringWithCharacters:reinterpret_cast<const unichar*>(aString.BeginReading()) 1.477 + length:aString.Length()]; 1.478 +} 1.479 + 1.480 +// static 1.481 +void 1.482 +nsCocoaUtils::GeckoRectToNSRect(const nsIntRect& aGeckoRect, 1.483 + NSRect& aOutCocoaRect) 1.484 +{ 1.485 + aOutCocoaRect.origin.x = aGeckoRect.x; 1.486 + aOutCocoaRect.origin.y = aGeckoRect.y; 1.487 + aOutCocoaRect.size.width = aGeckoRect.width; 1.488 + aOutCocoaRect.size.height = aGeckoRect.height; 1.489 +} 1.490 + 1.491 +// static 1.492 +void 1.493 +nsCocoaUtils::NSRectToGeckoRect(const NSRect& aCocoaRect, 1.494 + nsIntRect& aOutGeckoRect) 1.495 +{ 1.496 + aOutGeckoRect.x = NSToIntRound(aCocoaRect.origin.x); 1.497 + aOutGeckoRect.y = NSToIntRound(aCocoaRect.origin.y); 1.498 + aOutGeckoRect.width = NSToIntRound(aCocoaRect.origin.x + aCocoaRect.size.width) - aOutGeckoRect.x; 1.499 + aOutGeckoRect.height = NSToIntRound(aCocoaRect.origin.y + aCocoaRect.size.height) - aOutGeckoRect.y; 1.500 +} 1.501 + 1.502 +// static 1.503 +NSEvent* 1.504 +nsCocoaUtils::MakeNewCocoaEventWithType(NSEventType aEventType, NSEvent *aEvent) 1.505 +{ 1.506 + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; 1.507 + 1.508 + NSEvent* newEvent = 1.509 + [NSEvent keyEventWithType:aEventType 1.510 + location:[aEvent locationInWindow] 1.511 + modifierFlags:[aEvent modifierFlags] 1.512 + timestamp:[aEvent timestamp] 1.513 + windowNumber:[aEvent windowNumber] 1.514 + context:[aEvent context] 1.515 + characters:[aEvent characters] 1.516 + charactersIgnoringModifiers:[aEvent charactersIgnoringModifiers] 1.517 + isARepeat:[aEvent isARepeat] 1.518 + keyCode:[aEvent keyCode]]; 1.519 + return newEvent; 1.520 + 1.521 + NS_OBJC_END_TRY_ABORT_BLOCK_NIL; 1.522 +} 1.523 + 1.524 +// static 1.525 +void 1.526 +nsCocoaUtils::InitNPCocoaEvent(NPCocoaEvent* aNPCocoaEvent) 1.527 +{ 1.528 + memset(aNPCocoaEvent, 0, sizeof(NPCocoaEvent)); 1.529 +} 1.530 + 1.531 +// static 1.532 +void 1.533 +nsCocoaUtils::InitPluginEvent(WidgetPluginEvent &aPluginEvent, 1.534 + NPCocoaEvent &aCocoaEvent) 1.535 +{ 1.536 + aPluginEvent.time = PR_IntervalNow(); 1.537 + aPluginEvent.pluginEvent = (void*)&aCocoaEvent; 1.538 + aPluginEvent.retargetToFocusedDocument = false; 1.539 +} 1.540 + 1.541 +// static 1.542 +void 1.543 +nsCocoaUtils::InitInputEvent(WidgetInputEvent& aInputEvent, 1.544 + NSEvent* aNativeEvent) 1.545 +{ 1.546 + NS_OBJC_BEGIN_TRY_ABORT_BLOCK; 1.547 + 1.548 + NSUInteger modifiers = 1.549 + aNativeEvent ? [aNativeEvent modifierFlags] : [NSEvent modifierFlags]; 1.550 + InitInputEvent(aInputEvent, modifiers); 1.551 + 1.552 + aInputEvent.time = PR_IntervalNow(); 1.553 + 1.554 + NS_OBJC_END_TRY_ABORT_BLOCK; 1.555 +} 1.556 + 1.557 +// static 1.558 +void 1.559 +nsCocoaUtils::InitInputEvent(WidgetInputEvent& aInputEvent, 1.560 + NSUInteger aModifiers) 1.561 +{ 1.562 + aInputEvent.modifiers = 0; 1.563 + if (aModifiers & NSShiftKeyMask) { 1.564 + aInputEvent.modifiers |= MODIFIER_SHIFT; 1.565 + } 1.566 + if (aModifiers & NSControlKeyMask) { 1.567 + aInputEvent.modifiers |= MODIFIER_CONTROL; 1.568 + } 1.569 + if (aModifiers & NSAlternateKeyMask) { 1.570 + aInputEvent.modifiers |= MODIFIER_ALT; 1.571 + // Mac's option key is similar to other platforms' AltGr key. 1.572 + // Let's set AltGr flag when option key is pressed for consistency with 1.573 + // other platforms. 1.574 + aInputEvent.modifiers |= MODIFIER_ALTGRAPH; 1.575 + } 1.576 + if (aModifiers & NSCommandKeyMask) { 1.577 + aInputEvent.modifiers |= MODIFIER_META; 1.578 + } 1.579 + 1.580 + if (aModifiers & NSAlphaShiftKeyMask) { 1.581 + aInputEvent.modifiers |= MODIFIER_CAPSLOCK; 1.582 + } 1.583 + // Mac doesn't have NumLock key. We can assume that NumLock is always locked 1.584 + // if user is using a keyboard which has numpad. Otherwise, if user is using 1.585 + // a keyboard which doesn't have numpad, e.g., MacBook's keyboard, we can 1.586 + // assume that NumLock is always unlocked. 1.587 + // Unfortunately, we cannot know whether current keyboard has numpad or not. 1.588 + // We should notify locked state only when keys in numpad are pressed. 1.589 + // By this, web applications may not be confused by unexpected numpad key's 1.590 + // key event with unlocked state. 1.591 + if (aModifiers & NSNumericPadKeyMask) { 1.592 + aInputEvent.modifiers |= MODIFIER_NUMLOCK; 1.593 + } 1.594 + 1.595 + // Be aware, NSFunctionKeyMask is included when arrow keys, home key or some 1.596 + // other keys are pressed. We cannot check whether 'fn' key is pressed or 1.597 + // not by the flag. 1.598 + 1.599 +} 1.600 + 1.601 +// static 1.602 +UInt32 1.603 +nsCocoaUtils::ConvertToCarbonModifier(NSUInteger aCocoaModifier) 1.604 +{ 1.605 + UInt32 carbonModifier = 0; 1.606 + if (aCocoaModifier & NSAlphaShiftKeyMask) { 1.607 + carbonModifier |= alphaLock; 1.608 + } 1.609 + if (aCocoaModifier & NSControlKeyMask) { 1.610 + carbonModifier |= controlKey; 1.611 + } 1.612 + if (aCocoaModifier & NSAlternateKeyMask) { 1.613 + carbonModifier |= optionKey; 1.614 + } 1.615 + if (aCocoaModifier & NSShiftKeyMask) { 1.616 + carbonModifier |= shiftKey; 1.617 + } 1.618 + if (aCocoaModifier & NSCommandKeyMask) { 1.619 + carbonModifier |= cmdKey; 1.620 + } 1.621 + if (aCocoaModifier & NSNumericPadKeyMask) { 1.622 + carbonModifier |= kEventKeyModifierNumLockMask; 1.623 + } 1.624 + if (aCocoaModifier & NSFunctionKeyMask) { 1.625 + carbonModifier |= kEventKeyModifierFnMask; 1.626 + } 1.627 + return carbonModifier; 1.628 +} 1.629 + 1.630 +// While HiDPI support is not 100% complete and tested, we'll have a pref 1.631 +// to allow it to be turned off in case of problems (or for testing purposes). 1.632 + 1.633 +// gfx.hidpi.enabled is an integer with the meaning: 1.634 +// <= 0 : HiDPI support is disabled 1.635 +// 1 : HiDPI enabled provided all screens have the same backing resolution 1.636 +// > 1 : HiDPI enabled even if there are a mixture of screen modes 1.637 + 1.638 +// All the following code is to be removed once HiDPI work is more complete. 1.639 + 1.640 +static bool sHiDPIEnabled = false; 1.641 +static bool sHiDPIPrefInitialized = false; 1.642 + 1.643 +// static 1.644 +bool 1.645 +nsCocoaUtils::HiDPIEnabled() 1.646 +{ 1.647 + if (!sHiDPIPrefInitialized) { 1.648 + sHiDPIPrefInitialized = true; 1.649 + 1.650 + int prefSetting = Preferences::GetInt("gfx.hidpi.enabled", 1); 1.651 + if (prefSetting <= 0) { 1.652 + return false; 1.653 + } 1.654 + 1.655 + // prefSetting is at least 1, need to check attached screens... 1.656 + 1.657 + int scaleFactors = 0; // used as a bitset to track the screen types found 1.658 + NSEnumerator *screenEnum = [[NSScreen screens] objectEnumerator]; 1.659 + while (NSScreen *screen = [screenEnum nextObject]) { 1.660 + NSDictionary *desc = [screen deviceDescription]; 1.661 + if ([desc objectForKey:NSDeviceIsScreen] == nil) { 1.662 + continue; 1.663 + } 1.664 + CGFloat scale = 1.665 + [screen respondsToSelector:@selector(backingScaleFactor)] ? 1.666 + [screen backingScaleFactor] : 1.0; 1.667 + // Currently, we only care about differentiating "1.0" and "2.0", 1.668 + // so we set one of the two low bits to record which. 1.669 + if (scale > 1.0) { 1.670 + scaleFactors |= 2; 1.671 + } else { 1.672 + scaleFactors |= 1; 1.673 + } 1.674 + } 1.675 + 1.676 + // Now scaleFactors will be: 1.677 + // 0 if no screens (supporting backingScaleFactor) found 1.678 + // 1 if only lo-DPI screens 1.679 + // 2 if only hi-DPI screens 1.680 + // 3 if both lo- and hi-DPI screens 1.681 + // We'll enable HiDPI support if there's only a single screen type, 1.682 + // OR if the pref setting is explicitly greater than 1. 1.683 + sHiDPIEnabled = (scaleFactors <= 2) || (prefSetting > 1); 1.684 + } 1.685 + 1.686 + return sHiDPIEnabled; 1.687 +} 1.688 + 1.689 +void 1.690 +nsCocoaUtils::GetCommandsFromKeyEvent(NSEvent* aEvent, 1.691 + nsTArray<KeyBindingsCommand>& aCommands) 1.692 +{ 1.693 + NS_OBJC_BEGIN_TRY_ABORT_BLOCK; 1.694 + 1.695 + MOZ_ASSERT(aEvent); 1.696 + 1.697 + static NativeKeyBindingsRecorder* sNativeKeyBindingsRecorder; 1.698 + if (!sNativeKeyBindingsRecorder) { 1.699 + sNativeKeyBindingsRecorder = [NativeKeyBindingsRecorder new]; 1.700 + } 1.701 + 1.702 + [sNativeKeyBindingsRecorder startRecording:aCommands]; 1.703 + 1.704 + // This will trigger 0 - N calls to doCommandBySelector: and insertText: 1.705 + [sNativeKeyBindingsRecorder 1.706 + interpretKeyEvents:[NSArray arrayWithObject:aEvent]]; 1.707 + 1.708 + NS_OBJC_END_TRY_ABORT_BLOCK; 1.709 +} 1.710 + 1.711 +@implementation NativeKeyBindingsRecorder 1.712 + 1.713 +- (void)startRecording:(nsTArray<KeyBindingsCommand>&)aCommands 1.714 +{ 1.715 + mCommands = &aCommands; 1.716 + mCommands->Clear(); 1.717 +} 1.718 + 1.719 +- (void)doCommandBySelector:(SEL)aSelector 1.720 +{ 1.721 + KeyBindingsCommand command = { 1.722 + aSelector, 1.723 + nil 1.724 + }; 1.725 + 1.726 + mCommands->AppendElement(command); 1.727 +} 1.728 + 1.729 +- (void)insertText:(id)aString 1.730 +{ 1.731 + KeyBindingsCommand command = { 1.732 + @selector(insertText:), 1.733 + aString 1.734 + }; 1.735 + 1.736 + mCommands->AppendElement(command); 1.737 +} 1.738 + 1.739 +@end // NativeKeyBindingsRecorder 1.740 + 1.741 +struct KeyConversionData 1.742 +{ 1.743 + const char* str; 1.744 + size_t strLength; 1.745 + uint32_t geckoKeyCode; 1.746 + uint32_t charCode; 1.747 +}; 1.748 + 1.749 +static const KeyConversionData gKeyConversions[] = { 1.750 + 1.751 +#define KEYCODE_ENTRY(aStr, aCode) \ 1.752 + {#aStr, sizeof(#aStr) - 1, NS_##aStr, aCode} 1.753 + 1.754 +// Some keycodes may have different name in nsIDOMKeyEvent from its key name. 1.755 +#define KEYCODE_ENTRY2(aStr, aNSName, aCode) \ 1.756 + {#aStr, sizeof(#aStr) - 1, NS_##aNSName, aCode} 1.757 + 1.758 + KEYCODE_ENTRY(VK_CANCEL, 0x001B), 1.759 + KEYCODE_ENTRY(VK_DELETE, NSDeleteFunctionKey), 1.760 + KEYCODE_ENTRY(VK_BACK, NSBackspaceCharacter), 1.761 + KEYCODE_ENTRY2(VK_BACK_SPACE, VK_BACK, NSBackspaceCharacter), 1.762 + KEYCODE_ENTRY(VK_TAB, NSTabCharacter), 1.763 + KEYCODE_ENTRY(VK_CLEAR, NSClearLineFunctionKey), 1.764 + KEYCODE_ENTRY(VK_RETURN, NSEnterCharacter), 1.765 + KEYCODE_ENTRY(VK_SHIFT, 0), 1.766 + KEYCODE_ENTRY(VK_CONTROL, 0), 1.767 + KEYCODE_ENTRY(VK_ALT, 0), 1.768 + KEYCODE_ENTRY(VK_PAUSE, NSPauseFunctionKey), 1.769 + KEYCODE_ENTRY(VK_CAPS_LOCK, 0), 1.770 + KEYCODE_ENTRY(VK_ESCAPE, 0), 1.771 + KEYCODE_ENTRY(VK_SPACE, ' '), 1.772 + KEYCODE_ENTRY(VK_PAGE_UP, NSPageUpFunctionKey), 1.773 + KEYCODE_ENTRY(VK_PAGE_DOWN, NSPageDownFunctionKey), 1.774 + KEYCODE_ENTRY(VK_END, NSEndFunctionKey), 1.775 + KEYCODE_ENTRY(VK_HOME, NSHomeFunctionKey), 1.776 + KEYCODE_ENTRY(VK_LEFT, NSLeftArrowFunctionKey), 1.777 + KEYCODE_ENTRY(VK_UP, NSUpArrowFunctionKey), 1.778 + KEYCODE_ENTRY(VK_RIGHT, NSRightArrowFunctionKey), 1.779 + KEYCODE_ENTRY(VK_DOWN, NSDownArrowFunctionKey), 1.780 + KEYCODE_ENTRY(VK_PRINTSCREEN, NSPrintScreenFunctionKey), 1.781 + KEYCODE_ENTRY(VK_INSERT, NSInsertFunctionKey), 1.782 + KEYCODE_ENTRY(VK_HELP, NSHelpFunctionKey), 1.783 + KEYCODE_ENTRY(VK_0, '0'), 1.784 + KEYCODE_ENTRY(VK_1, '1'), 1.785 + KEYCODE_ENTRY(VK_2, '2'), 1.786 + KEYCODE_ENTRY(VK_3, '3'), 1.787 + KEYCODE_ENTRY(VK_4, '4'), 1.788 + KEYCODE_ENTRY(VK_5, '5'), 1.789 + KEYCODE_ENTRY(VK_6, '6'), 1.790 + KEYCODE_ENTRY(VK_7, '7'), 1.791 + KEYCODE_ENTRY(VK_8, '8'), 1.792 + KEYCODE_ENTRY(VK_9, '9'), 1.793 + KEYCODE_ENTRY(VK_SEMICOLON, ':'), 1.794 + KEYCODE_ENTRY(VK_EQUALS, '='), 1.795 + KEYCODE_ENTRY(VK_A, 'A'), 1.796 + KEYCODE_ENTRY(VK_B, 'B'), 1.797 + KEYCODE_ENTRY(VK_C, 'C'), 1.798 + KEYCODE_ENTRY(VK_D, 'D'), 1.799 + KEYCODE_ENTRY(VK_E, 'E'), 1.800 + KEYCODE_ENTRY(VK_F, 'F'), 1.801 + KEYCODE_ENTRY(VK_G, 'G'), 1.802 + KEYCODE_ENTRY(VK_H, 'H'), 1.803 + KEYCODE_ENTRY(VK_I, 'I'), 1.804 + KEYCODE_ENTRY(VK_J, 'J'), 1.805 + KEYCODE_ENTRY(VK_K, 'K'), 1.806 + KEYCODE_ENTRY(VK_L, 'L'), 1.807 + KEYCODE_ENTRY(VK_M, 'M'), 1.808 + KEYCODE_ENTRY(VK_N, 'N'), 1.809 + KEYCODE_ENTRY(VK_O, 'O'), 1.810 + KEYCODE_ENTRY(VK_P, 'P'), 1.811 + KEYCODE_ENTRY(VK_Q, 'Q'), 1.812 + KEYCODE_ENTRY(VK_R, 'R'), 1.813 + KEYCODE_ENTRY(VK_S, 'S'), 1.814 + KEYCODE_ENTRY(VK_T, 'T'), 1.815 + KEYCODE_ENTRY(VK_U, 'U'), 1.816 + KEYCODE_ENTRY(VK_V, 'V'), 1.817 + KEYCODE_ENTRY(VK_W, 'W'), 1.818 + KEYCODE_ENTRY(VK_X, 'X'), 1.819 + KEYCODE_ENTRY(VK_Y, 'Y'), 1.820 + KEYCODE_ENTRY(VK_Z, 'Z'), 1.821 + KEYCODE_ENTRY(VK_CONTEXT_MENU, NSMenuFunctionKey), 1.822 + KEYCODE_ENTRY(VK_NUMPAD0, '0'), 1.823 + KEYCODE_ENTRY(VK_NUMPAD1, '1'), 1.824 + KEYCODE_ENTRY(VK_NUMPAD2, '2'), 1.825 + KEYCODE_ENTRY(VK_NUMPAD3, '3'), 1.826 + KEYCODE_ENTRY(VK_NUMPAD4, '4'), 1.827 + KEYCODE_ENTRY(VK_NUMPAD5, '5'), 1.828 + KEYCODE_ENTRY(VK_NUMPAD6, '6'), 1.829 + KEYCODE_ENTRY(VK_NUMPAD7, '7'), 1.830 + KEYCODE_ENTRY(VK_NUMPAD8, '8'), 1.831 + KEYCODE_ENTRY(VK_NUMPAD9, '9'), 1.832 + KEYCODE_ENTRY(VK_MULTIPLY, '*'), 1.833 + KEYCODE_ENTRY(VK_ADD, '+'), 1.834 + KEYCODE_ENTRY(VK_SEPARATOR, 0), 1.835 + KEYCODE_ENTRY(VK_SUBTRACT, '-'), 1.836 + KEYCODE_ENTRY(VK_DECIMAL, '.'), 1.837 + KEYCODE_ENTRY(VK_DIVIDE, '/'), 1.838 + KEYCODE_ENTRY(VK_F1, NSF1FunctionKey), 1.839 + KEYCODE_ENTRY(VK_F2, NSF2FunctionKey), 1.840 + KEYCODE_ENTRY(VK_F3, NSF3FunctionKey), 1.841 + KEYCODE_ENTRY(VK_F4, NSF4FunctionKey), 1.842 + KEYCODE_ENTRY(VK_F5, NSF5FunctionKey), 1.843 + KEYCODE_ENTRY(VK_F6, NSF6FunctionKey), 1.844 + KEYCODE_ENTRY(VK_F7, NSF7FunctionKey), 1.845 + KEYCODE_ENTRY(VK_F8, NSF8FunctionKey), 1.846 + KEYCODE_ENTRY(VK_F9, NSF9FunctionKey), 1.847 + KEYCODE_ENTRY(VK_F10, NSF10FunctionKey), 1.848 + KEYCODE_ENTRY(VK_F11, NSF11FunctionKey), 1.849 + KEYCODE_ENTRY(VK_F12, NSF12FunctionKey), 1.850 + KEYCODE_ENTRY(VK_F13, NSF13FunctionKey), 1.851 + KEYCODE_ENTRY(VK_F14, NSF14FunctionKey), 1.852 + KEYCODE_ENTRY(VK_F15, NSF15FunctionKey), 1.853 + KEYCODE_ENTRY(VK_F16, NSF16FunctionKey), 1.854 + KEYCODE_ENTRY(VK_F17, NSF17FunctionKey), 1.855 + KEYCODE_ENTRY(VK_F18, NSF18FunctionKey), 1.856 + KEYCODE_ENTRY(VK_F19, NSF19FunctionKey), 1.857 + KEYCODE_ENTRY(VK_F20, NSF20FunctionKey), 1.858 + KEYCODE_ENTRY(VK_F21, NSF21FunctionKey), 1.859 + KEYCODE_ENTRY(VK_F22, NSF22FunctionKey), 1.860 + KEYCODE_ENTRY(VK_F23, NSF23FunctionKey), 1.861 + KEYCODE_ENTRY(VK_F24, NSF24FunctionKey), 1.862 + KEYCODE_ENTRY(VK_NUM_LOCK, NSClearLineFunctionKey), 1.863 + KEYCODE_ENTRY(VK_SCROLL_LOCK, NSScrollLockFunctionKey), 1.864 + KEYCODE_ENTRY(VK_COMMA, ','), 1.865 + KEYCODE_ENTRY(VK_PERIOD, '.'), 1.866 + KEYCODE_ENTRY(VK_SLASH, '/'), 1.867 + KEYCODE_ENTRY(VK_BACK_QUOTE, '`'), 1.868 + KEYCODE_ENTRY(VK_OPEN_BRACKET, '['), 1.869 + KEYCODE_ENTRY(VK_BACK_SLASH, '\\'), 1.870 + KEYCODE_ENTRY(VK_CLOSE_BRACKET, ']'), 1.871 + KEYCODE_ENTRY(VK_QUOTE, '\'') 1.872 + 1.873 +#undef KEYCODE_ENTRY 1.874 + 1.875 +}; 1.876 + 1.877 +uint32_t 1.878 +nsCocoaUtils::ConvertGeckoNameToMacCharCode(const nsAString& aKeyCodeName) 1.879 +{ 1.880 + if (aKeyCodeName.IsEmpty()) { 1.881 + return 0; 1.882 + } 1.883 + 1.884 + nsAutoCString keyCodeName; 1.885 + keyCodeName.AssignWithConversion(aKeyCodeName); 1.886 + // We want case-insensitive comparison with data stored as uppercase. 1.887 + ToUpperCase(keyCodeName); 1.888 + 1.889 + uint32_t keyCodeNameLength = keyCodeName.Length(); 1.890 + const char* keyCodeNameStr = keyCodeName.get(); 1.891 + for (uint16_t i = 0; i < ArrayLength(gKeyConversions); ++i) { 1.892 + if (keyCodeNameLength == gKeyConversions[i].strLength && 1.893 + nsCRT::strcmp(gKeyConversions[i].str, keyCodeNameStr) == 0) { 1.894 + return gKeyConversions[i].charCode; 1.895 + } 1.896 + } 1.897 + 1.898 + return 0; 1.899 +} 1.900 + 1.901 +uint32_t 1.902 +nsCocoaUtils::ConvertGeckoKeyCodeToMacCharCode(uint32_t aKeyCode) 1.903 +{ 1.904 + if (!aKeyCode) { 1.905 + return 0; 1.906 + } 1.907 + 1.908 + for (uint16_t i = 0; i < ArrayLength(gKeyConversions); ++i) { 1.909 + if (gKeyConversions[i].geckoKeyCode == aKeyCode) { 1.910 + return gKeyConversions[i].charCode; 1.911 + } 1.912 + } 1.913 + 1.914 + return 0; 1.915 +}