/* -*- 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 "ScreenManager.h" #include "mozilla/ClearOnShutdown.h" #include "mozilla/dom/ContentParent.h" #include "mozilla/dom/DOMTypes.h" #include "mozilla/Logging.h" #include "mozilla/StaticPtr.h" #ifdef MOZ_WAYLAND # include "mozilla/WidgetUtilsGtk.h" #endif /* MOZ_WAYLAND */ static mozilla::LazyLogModule sScreenLog("WidgetScreen"); namespace mozilla::widget { NS_IMPL_ISUPPORTS(ScreenManager, nsIScreenManager) ScreenManager::ScreenManager() = default; ScreenManager::~ScreenManager() = default; static StaticRefPtr sSingleton; ScreenManager& ScreenManager::GetSingleton() { if (!sSingleton) { sSingleton = new ScreenManager(); ClearOnShutdown(&sSingleton); } return *sSingleton; } already_AddRefed ScreenManager::GetAddRefedSingleton() { RefPtr sm = &GetSingleton(); return sm.forget(); } void ScreenManager::SetHelper(UniquePtr aHelper) { mHelper = std::move(aHelper); } // static void ScreenManager::Refresh(nsTArray>&& aScreens) { if (PastShutdownPhase(ShutdownPhase::XPCOMShutdown)) { // We don't refresh screen data if starting XPCOM shutdown path. // GetSingleton returns invalid data since it is freed. return; } MOZ_LOG(sScreenLog, LogLevel::Debug, ("Refresh screens")); GetSingleton().RefreshInternal(std::move(aScreens)); } void ScreenManager::Refresh(nsTArray&& aScreens) { MOZ_LOG(sScreenLog, LogLevel::Debug, ("Refresh screens from IPC")); AutoTArray, 4> screens; for (auto& screen : aScreens) { screens.AppendElement(new Screen(screen)); } RefreshInternal(std::move(screens)); } void ScreenManager::RefreshInternal(nsTArray>&& aScreens) { mScreenList = std::move(aScreens); CopyScreensToAllRemotesIfIsParent(); if (nsCOMPtr s = services::GetObserverService()) { s->NotifyObservers(nullptr, "screen-information-changed", nullptr); } } template void ScreenManager::CopyScreensToRemoteRange(Range aRemoteRange) { AutoTArray screens; for (auto& screen : mScreenList) { screens.AppendElement(screen->ToScreenDetails()); } for (auto cp : aRemoteRange) { MOZ_LOG(sScreenLog, LogLevel::Debug, ("Send screens to [Pid %d]", cp->Pid())); if (!cp->SendRefreshScreens(screens)) { MOZ_LOG(sScreenLog, LogLevel::Error, ("SendRefreshScreens to [Pid %d] failed", cp->Pid())); } } } void ScreenManager::CopyScreensToRemote(dom::ContentParent* aContentParent) { MOZ_ASSERT(aContentParent); MOZ_ASSERT(XRE_IsParentProcess()); auto range = {aContentParent}; CopyScreensToRemoteRange(range); } void ScreenManager::CopyScreensToAllRemotesIfIsParent() { if (XRE_IsContentProcess()) { return; } MOZ_LOG(sScreenLog, LogLevel::Debug, ("Refreshing all ContentParents")); CopyScreensToRemoteRange( dom::ContentParent::AllProcesses(dom::ContentParent::eLive)); } // Returns the screen that contains the rectangle. If the rect overlaps // multiple screens, it picks the screen with the greatest area of intersection. // // The coordinates are in desktop pixels. // NS_IMETHODIMP ScreenManager::ScreenForRect(int32_t aX, int32_t aY, int32_t aWidth, int32_t aHeight, nsIScreen** aOutScreen) { DesktopIntRect rect(aX, aY, aWidth, aHeight); nsCOMPtr screen = ScreenForRect(rect); screen.forget(aOutScreen); return NS_OK; } already_AddRefed ScreenManager::ScreenForRect( const DesktopIntRect& aRect) { #if defined(MOZ_WAYLAND) && defined(MOZ_LOGGING) static bool inWayland = GdkIsWaylandDisplay(); if (inWayland) { MOZ_LOG(sScreenLog, LogLevel::Warning, ("Getting screen in wayland, primary display will be returned.")); } #endif if (mScreenList.IsEmpty()) { MOZ_LOG(sScreenLog, LogLevel::Warning, ("No screen available. This can happen in xpcshell.")); auto screen = MakeRefPtr( LayoutDeviceIntRect(), LayoutDeviceIntRect(), 0, 0, 0, DesktopToLayoutDeviceScale(), CSSToLayoutDeviceScale(), 96 /* dpi */, Screen::IsPseudoDisplay::No, hal::ScreenOrientation::None, 0); return screen.forget(); } // Optimize for the common case. If the number of screens is only // one then just return the primary screen. if (mScreenList.Length() == 1) { return GetPrimaryScreen(); } // which screen should we return? Screen* which = mScreenList[0].get(); // walk the list of screens and find the one that has the most // surface area. uint32_t area = 0; for (auto& screen : mScreenList) { int32_t x, y, width, height; x = y = width = height = 0; screen->GetRectDisplayPix(&x, &y, &width, &height); // calculate the surface area DesktopIntRect screenRect(x, y, width, height); screenRect.IntersectRect(screenRect, aRect); uint32_t tempArea = screenRect.Area(); if (tempArea > area) { which = screen.get(); area = tempArea; } } // If the rect intersects one or more screen, // return the screen that has the largest intersection. if (area > 0) { RefPtr ret = which; return ret.forget(); } // If the rect does not intersect a screen, find // a screen that is nearest to the rect. uint32_t distance = UINT32_MAX; for (auto& screen : mScreenList) { int32_t x, y, width, height; x = y = width = height = 0; screen->GetRectDisplayPix(&x, &y, &width, &height); uint32_t distanceX = 0; if (aRect.x > (x + width)) { distanceX = aRect.x - (x + width); } else if (aRect.XMost() < x) { distanceX = x - aRect.XMost(); } uint32_t distanceY = 0; if (aRect.y > (y + height)) { distanceY = aRect.y - (y + height); } else if (aRect.YMost() < y) { distanceY = y - aRect.YMost(); } uint32_t tempDistance = distanceX * distanceX + distanceY * distanceY; if (tempDistance < distance) { which = screen.get(); distance = tempDistance; if (distance == 0) { break; } } } RefPtr ret = which; return ret.forget(); } // The screen with the menubar/taskbar. This shouldn't be needed very // often. // already_AddRefed ScreenManager::GetPrimaryScreen() { if (mScreenList.IsEmpty()) { MOZ_LOG(sScreenLog, LogLevel::Warning, ("No screen available. This can happen in xpcshell.")); return MakeAndAddRef( LayoutDeviceIntRect(), LayoutDeviceIntRect(), 0, 0, 0, DesktopToLayoutDeviceScale(), CSSToLayoutDeviceScale(), 96 /* dpi */, Screen::IsPseudoDisplay::No, hal::ScreenOrientation::None, 0); } return do_AddRef(mScreenList[0]); } NS_IMETHODIMP ScreenManager::GetPrimaryScreen(nsIScreen** aPrimaryScreen) { nsCOMPtr screen = GetPrimaryScreen(); screen.forget(aPrimaryScreen); return NS_OK; } NS_IMETHODIMP ScreenManager::GetTotalScreenPixels(int64_t* aTotalScreenPixels) { MOZ_ASSERT(aTotalScreenPixels); if (mScreenList.IsEmpty()) { MOZ_LOG(sScreenLog, LogLevel::Warning, ("No screen available. This can happen in xpcshell.")); *aTotalScreenPixels = 0; return NS_OK; } int64_t pixels = 0; for (auto& screen : mScreenList) { int32_t x, y, width, height; x = y = width = height = 0; screen->GetRect(&x, &y, &width, &height); pixels += width * height; } *aTotalScreenPixels = pixels; return NS_OK; } } // namespace mozilla::widget