Files
tubestation/widget/cocoa/nsCocoaUtils.mm
Masayuki Nakano 2d4bfac20e Bug 1256562 part.1 Implement nsCocoaUtils::GetEventTimeStamp() to convert from timeStamp of NSEvent to TimeStamp r=birtles,mstange
This patch implements nsCocoaUtils::GetEventTimeStamp() which hides how to get TimeStamp from timeStamp of NSEvent from other developers.

Different from Windows and GTK, we don't need to use SystemTimeConverter and implement CurrentTimeGetter class because the internal value of the macOS implementation of TimeStamp is based on mach_absolute_time(), which measures "ticks" since boot. Event timestamps are NSTimeIntervals (seconds) since boot. So the two time representations already have the same base; we only need to convert seconds into ticks.

MozReview-Commit-ID: LvioyJOM7S9
2016-12-26 12:23:37 +09:00

1042 lines
33 KiB
Plaintext

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