/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* 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 "ScreenHelperCocoa.h" #import #include "mozilla/Logging.h" #include "nsCocoaFeatures.h" #include "nsCocoaUtils.h" #include "nsObjCExceptions.h" using namespace mozilla; static LazyLogModule sScreenLog("WidgetScreen"); @interface ScreenHelperDelegate : NSObject { @private mozilla::widget::ScreenHelperCocoa* mHelper; } - (id)initWithScreenHelper:(mozilla::widget::ScreenHelperCocoa*)aScreenHelper; - (void)didChangeScreenParameters:(NSNotification*)aNotification; @end @implementation ScreenHelperDelegate - (id)initWithScreenHelper:(mozilla::widget::ScreenHelperCocoa*)aScreenHelper { if ((self = [self init])) { mHelper = aScreenHelper; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didChangeScreenParameters:) name:NSApplicationDidChangeScreenParametersNotification object:nil]; } return self; } - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; [super dealloc]; } - (void)didChangeScreenParameters:(NSNotification*)aNotification { MOZ_LOG(sScreenLog, LogLevel::Debug, ("Received NSApplicationDidChangeScreenParametersNotification")); mHelper->RefreshScreens(); } @end namespace mozilla { namespace widget { ScreenHelperCocoa::ScreenHelperCocoa() { NS_OBJC_BEGIN_TRY_IGNORE_BLOCK; MOZ_LOG(sScreenLog, LogLevel::Debug, ("ScreenHelperCocoa created")); mDelegate = [[ScreenHelperDelegate alloc] initWithScreenHelper:this]; RefreshScreens(); NS_OBJC_END_TRY_IGNORE_BLOCK; } ScreenHelperCocoa::~ScreenHelperCocoa() { NS_OBJC_BEGIN_TRY_IGNORE_BLOCK; [mDelegate release]; NS_OBJC_END_TRY_IGNORE_BLOCK; } static already_AddRefed MakeScreen(NSScreen* aScreen) { NS_OBJC_BEGIN_TRY_BLOCK_RETURN; DesktopToLayoutDeviceScale contentsScaleFactor( nsCocoaUtils::GetBackingScaleFactor(aScreen)); CSSToLayoutDeviceScale defaultCssScaleFactor(contentsScaleFactor.scale); NSRect frame = [aScreen frame]; LayoutDeviceIntRect rect = nsCocoaUtils::CocoaRectToGeckoRectDevPix( frame, contentsScaleFactor.scale); frame = [aScreen visibleFrame]; LayoutDeviceIntRect availRect = nsCocoaUtils::CocoaRectToGeckoRectDevPix( frame, contentsScaleFactor.scale); // aScreen may be capable of displaying multiple pixel depths, for example by // transitioning to an HDR-capable depth when required by a window displayed // on the screen. We want to note the maximum capabilities of the screen, so // we use the largest depth it offers. uint32_t pixelDepth = 0; const NSWindowDepth* depths = [aScreen supportedWindowDepths]; for (size_t d = 0; NSWindowDepth depth = depths[d]; d++) { uint32_t bpp = NSBitsPerPixelFromDepth(depth); if (bpp > pixelDepth) { pixelDepth = bpp; } } // But it confuses content if we return too-high a value here. Cap depth with // a value that matches what Chrome returns for high bpp screens. static const uint32_t MAX_REPORTED_PIXEL_DEPTH = 30; if (pixelDepth > MAX_REPORTED_PIXEL_DEPTH) { pixelDepth = MAX_REPORTED_PIXEL_DEPTH; } // What's the maximum color component value this screen can display? This // is a reasonable stand-in for measuring peak brightness. CGFloat componentValueMax = aScreen.maximumPotentialExtendedDynamicRangeColorComponentValue; // Should we treat this as HDR? Based on spec at // https://drafts.csswg.org/mediaqueries-5/#dynamic-range, we'll consider it // HDR if it has pixel depth greater than 24, and if has high peak brightness, // which we measure by checking if it can represent component values greater // than 1.0. // // Also, on HDR screens, users may want to force SDR by setting a different // colorspace, for example by using the "Photography (P3 D65)" preset. In that // case, componentValueMax will be 1.0 and we want to treat the display as // SDR. bool isHDR = pixelDepth > 24 && componentValueMax > 1.0; // Double-check HDR against the platform capabilities. isHDR &= nsCocoaFeatures::OnBigSurOrLater(); float dpi = 96.0f; CGDirectDisplayID displayID = [[[aScreen deviceDescription] objectForKey:@"NSScreenNumber"] intValue]; CGFloat heightMM = ::CGDisplayScreenSize(displayID).height; if (heightMM > 0) { dpi = rect.height / (heightMM / MM_PER_INCH_FLOAT); } MOZ_LOG(sScreenLog, LogLevel::Debug, ("New screen [%d %d %d %d (%d %d %d %d) %d %f %f %f]", rect.x, rect.y, rect.width, rect.height, availRect.x, availRect.y, availRect.width, availRect.height, pixelDepth, contentsScaleFactor.scale, defaultCssScaleFactor.scale, dpi)); // Getting the refresh rate is a little hard on OS X. We could use // CVDisplayLinkGetNominalOutputVideoRefreshPeriod, but that's a little // involved. Ideally we could query it from vsync. For now, we leave it out. RefPtr screen = new Screen(rect, availRect, pixelDepth, pixelDepth, 0, contentsScaleFactor, defaultCssScaleFactor, dpi, Screen::IsPseudoDisplay::No, Screen::IsHDR(isHDR)); return screen.forget(); NS_OBJC_END_TRY_BLOCK_RETURN(nullptr); } void ScreenHelperCocoa::RefreshScreens() { NS_OBJC_BEGIN_TRY_IGNORE_BLOCK; MOZ_LOG(sScreenLog, LogLevel::Debug, ("Refreshing screens")); AutoTArray, 4> screens; for (NSScreen* screen in [NSScreen screens]) { NSDictionary* desc = [screen deviceDescription]; if ([desc objectForKey:NSDeviceIsScreen] == nil) { continue; } screens.AppendElement(MakeScreen(screen)); } ScreenManager::Refresh(std::move(screens)); NS_OBJC_END_TRY_IGNORE_BLOCK; } NSScreen* ScreenHelperCocoa::CocoaScreenForScreen(nsIScreen* aScreen) { NS_OBJC_BEGIN_TRY_BLOCK_RETURN; for (NSScreen* screen in [NSScreen screens]) { NSDictionary* desc = [screen deviceDescription]; if ([desc objectForKey:NSDeviceIsScreen] == nil) { continue; } LayoutDeviceIntRect rect; double scale; aScreen->GetRect(&rect.x, &rect.y, &rect.width, &rect.height); aScreen->GetContentsScaleFactor(&scale); NSRect frame = [screen frame]; LayoutDeviceIntRect frameRect = nsCocoaUtils::CocoaRectToGeckoRectDevPix(frame, scale); if (rect == frameRect) { return screen; } } return [NSScreen mainScreen]; NS_OBJC_END_TRY_BLOCK_RETURN(nil); } } // namespace widget } // namespace mozilla