1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "ScreenManager.h"
9 #include "mozilla/ClearOnShutdown.h"
10 #include "mozilla/dom/ContentParent.h"
11 #include "mozilla/dom/DOMTypes.h"
12 #include "mozilla/Logging.h"
13 #include "mozilla/StaticPtr.h"
15 # include "mozilla/WidgetUtilsGtk.h"
16 #endif /* MOZ_WAYLAND */
18 static mozilla::LazyLogModule
sScreenLog("WidgetScreen");
20 namespace mozilla::widget
{
22 NS_IMPL_ISUPPORTS(ScreenManager
, nsIScreenManager
)
24 ScreenManager::ScreenManager() = default;
26 ScreenManager::~ScreenManager() = default;
28 static StaticRefPtr
<ScreenManager
> sSingleton
;
30 ScreenManager
& ScreenManager::GetSingleton() {
32 sSingleton
= new ScreenManager();
33 ClearOnShutdown(&sSingleton
);
38 already_AddRefed
<ScreenManager
> ScreenManager::GetAddRefedSingleton() {
39 RefPtr
<ScreenManager
> sm
= &GetSingleton();
43 void ScreenManager::SetHelper(UniquePtr
<Helper
> aHelper
) {
44 mHelper
= std::move(aHelper
);
48 void ScreenManager::Refresh(nsTArray
<RefPtr
<Screen
>>&& aScreens
) {
49 if (PastShutdownPhase(ShutdownPhase::XPCOMShutdown
)) {
50 // We don't refresh screen data if starting XPCOM shutdown path.
51 // GetSingleton returns invalid data since it is freed.
54 MOZ_LOG(sScreenLog
, LogLevel::Debug
, ("Refresh screens"));
55 GetSingleton().RefreshInternal(std::move(aScreens
));
58 void ScreenManager::Refresh(nsTArray
<mozilla::dom::ScreenDetails
>&& aScreens
) {
59 MOZ_LOG(sScreenLog
, LogLevel::Debug
, ("Refresh screens from IPC"));
61 AutoTArray
<RefPtr
<Screen
>, 4> screens
;
62 for (auto& screen
: aScreens
) {
63 screens
.AppendElement(new Screen(screen
));
65 RefreshInternal(std::move(screens
));
68 void ScreenManager::RefreshInternal(nsTArray
<RefPtr
<Screen
>>&& aScreens
) {
69 mScreenList
= std::move(aScreens
);
71 CopyScreensToAllRemotesIfIsParent();
72 if (nsCOMPtr
<nsIObserverService
> s
= services::GetObserverService()) {
73 s
->NotifyObservers(nullptr, "screen-information-changed", nullptr);
77 template <class Range
>
78 void ScreenManager::CopyScreensToRemoteRange(Range aRemoteRange
) {
79 AutoTArray
<dom::ScreenDetails
, 4> screens
;
80 for (auto& screen
: mScreenList
) {
81 screens
.AppendElement(screen
->ToScreenDetails());
83 for (auto cp
: aRemoteRange
) {
84 MOZ_LOG(sScreenLog
, LogLevel::Debug
,
85 ("Send screens to [Pid %d]", cp
->Pid()));
86 if (!cp
->SendRefreshScreens(screens
)) {
87 MOZ_LOG(sScreenLog
, LogLevel::Error
,
88 ("SendRefreshScreens to [Pid %d] failed", cp
->Pid()));
93 void ScreenManager::CopyScreensToRemote(dom::ContentParent
* aContentParent
) {
94 MOZ_ASSERT(aContentParent
);
95 MOZ_ASSERT(XRE_IsParentProcess());
97 auto range
= {aContentParent
};
98 CopyScreensToRemoteRange(range
);
101 void ScreenManager::CopyScreensToAllRemotesIfIsParent() {
102 if (XRE_IsContentProcess()) {
106 MOZ_LOG(sScreenLog
, LogLevel::Debug
, ("Refreshing all ContentParents"));
108 CopyScreensToRemoteRange(
109 dom::ContentParent::AllProcesses(dom::ContentParent::eLive
));
112 // Returns the screen that contains the rectangle. If the rect overlaps
113 // multiple screens, it picks the screen with the greatest area of intersection.
115 // The coordinates are in desktop pixels.
118 ScreenManager::ScreenForRect(int32_t aX
, int32_t aY
, int32_t aWidth
,
119 int32_t aHeight
, nsIScreen
** aOutScreen
) {
120 DesktopIntRect
rect(aX
, aY
, aWidth
, aHeight
);
121 nsCOMPtr
<nsIScreen
> screen
= ScreenForRect(rect
);
122 screen
.forget(aOutScreen
);
126 already_AddRefed
<Screen
> ScreenManager::ScreenForRect(
127 const DesktopIntRect
& aRect
) {
128 #if defined(MOZ_WAYLAND) && defined(MOZ_LOGGING)
129 static bool inWayland
= GdkIsWaylandDisplay();
131 MOZ_LOG(sScreenLog
, LogLevel::Warning
,
132 ("Getting screen in wayland, primary display will be returned."));
136 if (mScreenList
.IsEmpty()) {
137 MOZ_LOG(sScreenLog
, LogLevel::Warning
,
138 ("No screen available. This can happen in xpcshell."));
139 auto screen
= MakeRefPtr
<Screen
>(
140 LayoutDeviceIntRect(), LayoutDeviceIntRect(), 0, 0, 0,
141 DesktopToLayoutDeviceScale(), CSSToLayoutDeviceScale(), 96 /* dpi */,
142 Screen::IsPseudoDisplay::No
, Screen::IsHDR::No
,
143 hal::ScreenOrientation::None
, 0);
144 return screen
.forget();
147 // Optimize for the common case. If the number of screens is only
148 // one then just return the primary screen.
149 if (mScreenList
.Length() == 1) {
150 return GetPrimaryScreen();
153 // which screen should we return?
154 Screen
* which
= mScreenList
[0].get();
156 // walk the list of screens and find the one that has the most
159 for (auto& screen
: mScreenList
) {
160 int32_t x
, y
, width
, height
;
161 x
= y
= width
= height
= 0;
162 screen
->GetRectDisplayPix(&x
, &y
, &width
, &height
);
163 // calculate the surface area
164 DesktopIntRect
screenRect(x
, y
, width
, height
);
165 screenRect
.IntersectRect(screenRect
, aRect
);
166 uint32_t tempArea
= screenRect
.Area();
167 if (tempArea
> area
) {
168 which
= screen
.get();
173 // If the rect intersects one or more screen,
174 // return the screen that has the largest intersection.
176 RefPtr
<Screen
> ret
= which
;
180 // If the rect does not intersect a screen, find
181 // a screen that is nearest to the rect.
182 uint32_t distance
= UINT32_MAX
;
183 for (auto& screen
: mScreenList
) {
184 int32_t x
, y
, width
, height
;
185 x
= y
= width
= height
= 0;
186 screen
->GetRectDisplayPix(&x
, &y
, &width
, &height
);
188 uint32_t distanceX
= 0;
189 if (aRect
.x
> (x
+ width
)) {
190 distanceX
= aRect
.x
- (x
+ width
);
191 } else if (aRect
.XMost() < x
) {
192 distanceX
= x
- aRect
.XMost();
195 uint32_t distanceY
= 0;
196 if (aRect
.y
> (y
+ height
)) {
197 distanceY
= aRect
.y
- (y
+ height
);
198 } else if (aRect
.YMost() < y
) {
199 distanceY
= y
- aRect
.YMost();
202 uint32_t tempDistance
= distanceX
* distanceX
+ distanceY
* distanceY
;
203 if (tempDistance
< distance
) {
204 which
= screen
.get();
205 distance
= tempDistance
;
212 RefPtr
<Screen
> ret
= which
;
216 // The screen with the menubar/taskbar. This shouldn't be needed very
219 already_AddRefed
<Screen
> ScreenManager::GetPrimaryScreen() {
220 if (mScreenList
.IsEmpty()) {
221 MOZ_LOG(sScreenLog
, LogLevel::Warning
,
222 ("No screen available. This can happen in xpcshell."));
223 return MakeAndAddRef
<Screen
>(LayoutDeviceIntRect(), LayoutDeviceIntRect(),
224 0, 0, 0, DesktopToLayoutDeviceScale(),
225 CSSToLayoutDeviceScale(), 96 /* dpi */,
226 Screen::IsPseudoDisplay::No
, Screen::IsHDR::No
,
227 hal::ScreenOrientation::None
, 0);
230 return do_AddRef(mScreenList
[0]);
234 ScreenManager::GetPrimaryScreen(nsIScreen
** aPrimaryScreen
) {
235 nsCOMPtr
<nsIScreen
> screen
= GetPrimaryScreen();
236 screen
.forget(aPrimaryScreen
);
241 ScreenManager::GetTotalScreenPixels(int64_t* aTotalScreenPixels
) {
242 MOZ_ASSERT(aTotalScreenPixels
);
244 if (mScreenList
.IsEmpty()) {
245 MOZ_LOG(sScreenLog
, LogLevel::Warning
,
246 ("No screen available. This can happen in xpcshell."));
247 *aTotalScreenPixels
= 0;
252 for (auto& screen
: mScreenList
) {
253 int32_t x
, y
, width
, height
;
254 x
= y
= width
= height
= 0;
255 screen
->GetRect(&x
, &y
, &width
, &height
);
256 pixels
+= width
* height
;
259 *aTotalScreenPixels
= pixels
;
263 } // namespace mozilla::widget