diff options
Diffstat (limited to '')
-rw-r--r-- | widget/ScreenManager.cpp | 261 |
1 files changed, 261 insertions, 0 deletions
diff --git a/widget/ScreenManager.cpp b/widget/ScreenManager.cpp new file mode 100644 index 0000000000..58e20806eb --- /dev/null +++ b/widget/ScreenManager.cpp @@ -0,0 +1,261 @@ +/* -*- 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<ScreenManager> sSingleton; + +ScreenManager& ScreenManager::GetSingleton() { + if (!sSingleton) { + sSingleton = new ScreenManager(); + ClearOnShutdown(&sSingleton); + } + return *sSingleton; +} + +already_AddRefed<ScreenManager> ScreenManager::GetAddRefedSingleton() { + RefPtr<ScreenManager> sm = &GetSingleton(); + return sm.forget(); +} + +void ScreenManager::SetHelper(UniquePtr<Helper> aHelper) { + mHelper = std::move(aHelper); +} + +// static +void ScreenManager::Refresh(nsTArray<RefPtr<Screen>>&& 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<mozilla::dom::ScreenDetails>&& aScreens) { + MOZ_LOG(sScreenLog, LogLevel::Debug, ("Refresh screens from IPC")); + + AutoTArray<RefPtr<Screen>, 4> screens; + for (auto& screen : aScreens) { + screens.AppendElement(new Screen(screen)); + } + RefreshInternal(std::move(screens)); +} + +void ScreenManager::RefreshInternal(nsTArray<RefPtr<Screen>>&& aScreens) { + mScreenList = std::move(aScreens); + + CopyScreensToAllRemotesIfIsParent(); + if (nsCOMPtr<nsIObserverService> s = services::GetObserverService()) { + s->NotifyObservers(nullptr, "screen-information-changed", nullptr); + } +} + +template <class Range> +void ScreenManager::CopyScreensToRemoteRange(Range aRemoteRange) { + AutoTArray<dom::ScreenDetails, 4> 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<nsIScreen> screen = ScreenForRect(rect); + screen.forget(aOutScreen); + return NS_OK; +} + +already_AddRefed<Screen> 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<Screen>( + 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<Screen> 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<Screen> ret = which; + return ret.forget(); +} + +// The screen with the menubar/taskbar. This shouldn't be needed very +// often. +// +already_AddRefed<Screen> ScreenManager::GetPrimaryScreen() { + if (mScreenList.IsEmpty()) { + MOZ_LOG(sScreenLog, LogLevel::Warning, + ("No screen available. This can happen in xpcshell.")); + return MakeAndAddRef<Screen>( + 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<nsIScreen> 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 |