1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:expandtab:shiftwidth=2:tabstop=2:
4 /* This Source Code Form is subject to the terms of the Mozilla Public
5 * License, v. 2.0. If a copy of the MPL was not distributed with this
6 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
8 #include "WakeLockListener.h"
9 #include "WidgetUtilsGtk.h"
10 #include "mozilla/ScopeExit.h"
12 #ifdef MOZ_ENABLE_DBUS
14 # include "AsyncDBus.h"
20 # include <gdk/gdkx.h>
21 # include "X11UndefineNone.h"
24 #if defined(MOZ_WAYLAND)
25 # include "mozilla/widget/nsWaylandDisplay.h"
26 # include "nsWindow.h"
27 # include "mozilla/dom/power/PowerManagerService.h"
30 #ifdef MOZ_ENABLE_DBUS
31 # define FREEDESKTOP_SCREENSAVER_TARGET "org.freedesktop.ScreenSaver"
32 # define FREEDESKTOP_SCREENSAVER_OBJECT "/ScreenSaver"
33 # define FREEDESKTOP_SCREENSAVER_INTERFACE "org.freedesktop.ScreenSaver"
35 # define FREEDESKTOP_POWER_TARGET "org.freedesktop.PowerManagement"
36 # define FREEDESKTOP_POWER_OBJECT "/org/freedesktop/PowerManagement/Inhibit"
37 # define FREEDESKTOP_POWER_INTERFACE "org.freedesktop.PowerManagement.Inhibit"
39 # define SESSION_MANAGER_TARGET "org.gnome.SessionManager"
40 # define SESSION_MANAGER_OBJECT "/org/gnome/SessionManager"
41 # define SESSION_MANAGER_INTERFACE "org.gnome.SessionManager"
43 # define DBUS_TIMEOUT (-1)
46 using namespace mozilla
;
47 using namespace mozilla::widget
;
49 NS_IMPL_ISUPPORTS(WakeLockListener
, nsIDOMMozWakeLockListener
)
51 StaticRefPtr
<WakeLockListener
> WakeLockListener::sSingleton
;
53 #define WAKE_LOCK_LOG(...) \
54 MOZ_LOG(gLinuxWakeLockLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
55 static mozilla::LazyLogModule
gLinuxWakeLockLog("LinuxWakeLock");
59 #if defined(MOZ_ENABLE_DBUS)
60 FreeDesktopScreensaver
= 1,
67 #if defined(MOZ_WAYLAND)
68 WaylandIdleInhibit
= 5,
74 const char* WakeLockTypeNames
[7] = {
75 "Initial", "FreeDesktopScreensaver", "FreeDesktopPower", "GNOME",
76 "XScreenSaver", "WaylandIdleInhibit", "Unsupported",
82 NS_INLINE_DECL_REFCOUNTING(WakeLockTopic
)
84 explicit WakeLockTopic(const nsAString
& aTopic
) {
85 CopyUTF16toUTF8(aTopic
, mTopic
);
86 if (sWakeLockType
== Initial
) {
87 SwitchToNextWakeLockType();
91 nsresult
InhibitScreensaver(void);
92 nsresult
UninhibitScreensaver(void);
99 static bool CheckXScreenSaverSupport();
100 bool InhibitXScreenSaver(bool inhibit
);
103 #if defined(MOZ_WAYLAND)
104 zwp_idle_inhibitor_v1
* mWaylandInhibitor
= nullptr;
105 static bool CheckWaylandIdleInhibitSupport();
106 bool InhibitWaylandIdle();
107 bool UninhibitWaylandIdle();
110 bool IsWakeLockTypeAvailable(int aWakeLockType
);
111 bool SwitchToNextWakeLockType();
113 #ifdef MOZ_ENABLE_DBUS
114 void DBusPrepareCancellable();
115 void DBusInhibitScreensaver(const char* aName
, const char* aPath
,
116 const char* aCall
, const char* aMethod
,
117 RefPtr
<GVariant
> aArgs
);
118 void DBusUninhibitScreensaver(const char* aName
, const char* aPath
,
119 const char* aCall
, const char* aMethod
);
121 void InhibitFreeDesktopScreensaver();
122 void InhibitFreeDesktopPower();
125 void UninhibitFreeDesktopScreensaver();
126 void UninhibitFreeDesktopPower();
127 void UninhibitGNOME();
129 void DBusInhibitSucceeded(uint32_t aInhibitRequestID
);
130 void DBusInhibitFailed(bool aFatal
);
131 void DBusUninhibitSucceeded();
132 void DBusUninhibitFailed();
136 WAKE_LOCK_LOG("WakeLockTopic::~WakeLockTopic() state %d", mInhibited
);
138 UninhibitScreensaver();
142 // Why is screensaver inhibited
146 bool mShouldInhibit
= false;
149 bool mInhibited
= false;
151 #ifdef MOZ_ENABLE_DBUS
152 // We're waiting for DBus reply (inhibit/uninhibit call).
153 bool mWaitingForDBusReply
= false;
155 // mInhibitRequestID is received from success screen saver inhibit call
156 // and it's needed for screen saver enablement.
157 uint32_t mInhibitRequestID
= 0;
160 static int sWakeLockType
;
163 int WakeLockTopic::sWakeLockType
= Initial
;
165 #ifdef MOZ_ENABLE_DBUS
166 void WakeLockTopic::DBusInhibitSucceeded(uint32_t aInhibitRequestID
) {
167 mWaitingForDBusReply
= false;
168 mInhibitRequestID
= aInhibitRequestID
;
171 WAKE_LOCK_LOG("WakeLockTopic::DBusInhibitSucceeded(), mInhibitRequestID %u",
174 // Uninhibit was requested before inhibit request was finished.
175 // So ask for it now.
176 if (!mShouldInhibit
) {
177 UninhibitScreensaver();
181 void WakeLockTopic::DBusInhibitFailed(bool aFatal
) {
182 WAKE_LOCK_LOG("WakeLockTopic::DBusInhibitFailed(%d)", aFatal
);
184 mWaitingForDBusReply
= false;
185 mInhibitRequestID
= 0;
187 // Non-recoverable DBus error. Switch to another wake lock type.
188 if (aFatal
&& SwitchToNextWakeLockType()) {
193 void WakeLockTopic::DBusUninhibitSucceeded() {
194 WAKE_LOCK_LOG("WakeLockTopic::DBusInhibitSucceeded()");
196 mWaitingForDBusReply
= false;
197 mInhibitRequestID
= 0;
200 // Inhibit was requested before uninhibit request was finished.
201 // So ask for it now.
202 if (mShouldInhibit
) {
203 InhibitScreensaver();
207 void WakeLockTopic::DBusUninhibitFailed() {
208 WAKE_LOCK_LOG("WakeLockTopic::DBusUninhibitFailed()");
209 mWaitingForDBusReply
= false;
212 void WakeLockTopic::DBusInhibitScreensaver(const char* aName
, const char* aPath
,
215 RefPtr
<GVariant
> aArgs
) {
216 WAKE_LOCK_LOG("WakeLockTopic::DBusInhibitScreensaver() waiting %d",
217 mWaitingForDBusReply
);
218 if (mWaitingForDBusReply
) {
219 // g_cancellable_cancel(mCancellable);
221 mWaitingForDBusReply
= true;
223 widget::CreateDBusProxyForBus(
225 GDBusProxyFlags(G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS
|
226 G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES
),
227 /* aInterfaceInfo = */ nullptr, aName
, aPath
, aCall
)
229 GetCurrentSerialEventTarget(), __func__
,
230 [self
= RefPtr
{this}, args
= RefPtr
{aArgs
},
231 aMethod
](RefPtr
<GDBusProxy
>&& aProxy
) {
233 "WakeLockTopic::DBusInhibitScreensaver() proxy created");
234 DBusProxyCall(aProxy
.get(), aMethod
, args
.get(),
235 G_DBUS_CALL_FLAGS_NONE
, DBUS_TIMEOUT
)
237 GetCurrentSerialEventTarget(), __func__
,
238 [s
= RefPtr
{self
}](RefPtr
<GVariant
>&& aResult
) {
239 if (!g_variant_is_of_type(aResult
.get(),
240 G_VARIANT_TYPE_TUPLE
) ||
241 g_variant_n_children(aResult
.get()) != 1) {
243 "WakeLockTopic::DBusInhibitScreensaver() wrong "
245 g_variant_get_type_string(aResult
.get()));
246 s
->DBusInhibitFailed(/* aFatal */ true);
249 RefPtr
<GVariant
> variant
= dont_AddRef(
250 g_variant_get_child_value(aResult
.get(), 0));
251 if (!g_variant_is_of_type(variant
,
252 G_VARIANT_TYPE_UINT32
)) {
254 "WakeLockTopic::DBusInhibitScreensaver() wrong "
256 g_variant_get_type_string(aResult
.get()));
257 s
->DBusInhibitFailed(/* aFatal */ true);
260 s
->DBusInhibitSucceeded(g_variant_get_uint32(variant
));
262 [s
= RefPtr
{self
}, aMethod
](GUniquePtr
<GError
>&& aError
) {
263 // Failed to send inhibit request over proxy.
264 // Switch to another wake lock type.
266 "WakeLockTopic::DBusInhibitFailed() %s call failed : "
268 aMethod
, aError
->message
);
269 s
->DBusInhibitFailed(/* aFatal */ true);
272 [self
= RefPtr
{this}](GUniquePtr
<GError
>&& aError
) {
273 // We failed to create DBus proxy. Switch to another
276 "WakeLockTopic::DBusInhibitScreensaver() Proxy creation "
279 self
->DBusInhibitFailed(/* aFatal */ true);
283 void WakeLockTopic::DBusUninhibitScreensaver(const char* aName
,
286 const char* aMethod
) {
288 "WakeLockTopic::DBusUninhibitScreensaver() waiting %d request id %u",
289 mWaitingForDBusReply
, mInhibitRequestID
);
291 if (mWaitingForDBusReply
) {
294 if (!mInhibitRequestID
) {
295 // missing uninhibit token, just quit.
298 mWaitingForDBusReply
= true;
300 RefPtr
<GVariant
> variant
=
301 dont_AddRef(g_variant_ref_sink(g_variant_new("(u)", mInhibitRequestID
)));
302 widget::CreateDBusProxyForBus(
304 GDBusProxyFlags(G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS
|
305 G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES
),
306 /* aInterfaceInfo = */ nullptr, aName
, aPath
, aCall
)
308 GetCurrentSerialEventTarget(), __func__
,
309 [self
= RefPtr
{this}, args
= RefPtr
{variant
},
310 aMethod
](RefPtr
<GDBusProxy
>&& aProxy
) {
312 "WakeLockTopic::DBusUninhibitScreensaver() proxy created");
313 DBusProxyCall(aProxy
.get(), aMethod
, args
.get(),
314 G_DBUS_CALL_FLAGS_NONE
, DBUS_TIMEOUT
)
316 GetCurrentSerialEventTarget(), __func__
,
317 [s
= RefPtr
{self
}](RefPtr
<GVariant
>&& aResult
) {
318 s
->DBusUninhibitSucceeded();
320 [s
= RefPtr
{self
}, aMethod
](GUniquePtr
<GError
>&& aError
) {
322 "WakeLockTopic::DBusUninhibitFailed() %s call failed "
324 aMethod
, aError
->message
);
325 s
->DBusUninhibitFailed();
328 [self
= RefPtr
{this}](GUniquePtr
<GError
>&& aError
) {
330 "WakeLockTopic::DBusUninhibitFailed() Proxy creation failed: "
333 self
->DBusUninhibitFailed();
337 void WakeLockTopic::InhibitFreeDesktopScreensaver() {
338 WAKE_LOCK_LOG("InhibitFreeDesktopScreensaver()");
339 DBusInhibitScreensaver(FREEDESKTOP_SCREENSAVER_TARGET
,
340 FREEDESKTOP_SCREENSAVER_OBJECT
,
341 FREEDESKTOP_SCREENSAVER_INTERFACE
, "Inhibit",
342 dont_AddRef(g_variant_ref_sink(g_variant_new(
343 "(ss)", g_get_prgname(), mTopic
.get()))));
346 void WakeLockTopic::InhibitFreeDesktopPower() {
347 WAKE_LOCK_LOG("InhibitFreeDesktopPower()");
348 DBusInhibitScreensaver(FREEDESKTOP_POWER_TARGET
, FREEDESKTOP_POWER_OBJECT
,
349 FREEDESKTOP_POWER_INTERFACE
, "Inhibit",
350 dont_AddRef(g_variant_ref_sink(g_variant_new(
351 "(ss)", g_get_prgname(), mTopic
.get()))));
354 void WakeLockTopic::InhibitGNOME() {
355 WAKE_LOCK_LOG("InhibitGNOME()");
356 static const uint32_t xid
= 0;
357 static const uint32_t flags
= (1 << 3); // Inhibit idle
358 DBusInhibitScreensaver(
359 SESSION_MANAGER_TARGET
, SESSION_MANAGER_OBJECT
, SESSION_MANAGER_INTERFACE
,
361 dont_AddRef(g_variant_ref_sink(
362 g_variant_new("(susu)", g_get_prgname(), xid
, mTopic
.get(), flags
))));
365 void WakeLockTopic::UninhibitFreeDesktopScreensaver() {
366 WAKE_LOCK_LOG("UninhibitFreeDesktopScreensaver()");
367 DBusUninhibitScreensaver(FREEDESKTOP_SCREENSAVER_TARGET
,
368 FREEDESKTOP_SCREENSAVER_OBJECT
,
369 FREEDESKTOP_SCREENSAVER_INTERFACE
, "UnInhibit");
372 void WakeLockTopic::UninhibitFreeDesktopPower() {
373 WAKE_LOCK_LOG("UninhibitFreeDesktopPower()");
374 DBusUninhibitScreensaver(FREEDESKTOP_POWER_TARGET
, FREEDESKTOP_POWER_OBJECT
,
375 FREEDESKTOP_POWER_INTERFACE
, "UnInhibit");
378 void WakeLockTopic::UninhibitGNOME() {
379 WAKE_LOCK_LOG("UninhibitGNOME()");
380 DBusUninhibitScreensaver(SESSION_MANAGER_TARGET
, SESSION_MANAGER_OBJECT
,
381 SESSION_MANAGER_INTERFACE
, "Uninhibit");
386 // TODO: Merge with Idle service?
387 typedef Bool (*_XScreenSaverQueryExtension_fn
)(Display
* dpy
, int* event_base
,
389 typedef Bool (*_XScreenSaverQueryVersion_fn
)(Display
* dpy
, int* major
,
391 typedef void (*_XScreenSaverSuspend_fn
)(Display
* dpy
, Bool suspend
);
393 static PRLibrary
* sXssLib
= nullptr;
394 static _XScreenSaverQueryExtension_fn _XSSQueryExtension
= nullptr;
395 static _XScreenSaverQueryVersion_fn _XSSQueryVersion
= nullptr;
396 static _XScreenSaverSuspend_fn _XSSSuspend
= nullptr;
399 bool WakeLockTopic::CheckXScreenSaverSupport() {
401 sXssLib
= PR_LoadLibrary("libXss.so.1");
407 _XSSQueryExtension
= (_XScreenSaverQueryExtension_fn
)PR_FindFunctionSymbol(
408 sXssLib
, "XScreenSaverQueryExtension");
409 _XSSQueryVersion
= (_XScreenSaverQueryVersion_fn
)PR_FindFunctionSymbol(
410 sXssLib
, "XScreenSaverQueryVersion");
411 _XSSSuspend
= (_XScreenSaverSuspend_fn
)PR_FindFunctionSymbol(
412 sXssLib
, "XScreenSaverSuspend");
413 if (!_XSSQueryExtension
|| !_XSSQueryVersion
|| !_XSSSuspend
) {
417 GdkDisplay
* gDisplay
= gdk_display_get_default();
418 if (!GdkIsX11Display(gDisplay
)) {
421 Display
* display
= GDK_DISPLAY_XDISPLAY(gDisplay
);
424 if (!_XSSQueryExtension(display
, &throwaway
, &throwaway
)) return false;
427 if (!_XSSQueryVersion(display
, &major
, &minor
)) return false;
428 // Needs to be compatible with version 1.1
429 if (major
!= 1) return false;
430 if (minor
< 1) return false;
432 WAKE_LOCK_LOG("XScreenSaver supported.");
437 bool WakeLockTopic::InhibitXScreenSaver(bool inhibit
) {
438 WAKE_LOCK_LOG("InhibitXScreenSaver %d", inhibit
);
440 // Should only be called if CheckXScreenSaverSupport returns true.
441 // There's a couple of safety checks here nonetheless.
445 GdkDisplay
* gDisplay
= gdk_display_get_default();
446 if (!GdkIsX11Display(gDisplay
)) {
449 Display
* display
= GDK_DISPLAY_XDISPLAY(gDisplay
);
450 _XSSSuspend(display
, inhibit
);
452 WAKE_LOCK_LOG("InhibitXScreenSaver %d succeeded", inhibit
);
453 mInhibited
= inhibit
;
458 #if defined(MOZ_WAYLAND)
460 bool WakeLockTopic::CheckWaylandIdleInhibitSupport() {
461 nsWaylandDisplay
* waylandDisplay
= WaylandDisplayGet();
462 return waylandDisplay
&& waylandDisplay
->GetIdleInhibitManager() != nullptr;
465 bool WakeLockTopic::InhibitWaylandIdle() {
466 WAKE_LOCK_LOG("InhibitWaylandIdle()");
468 nsWaylandDisplay
* waylandDisplay
= WaylandDisplayGet();
469 if (!waylandDisplay
) {
473 nsWindow
* focusedWindow
= nsWindow::GetFocusedWindow();
474 if (!focusedWindow
) {
478 UninhibitWaylandIdle();
480 MozContainerSurfaceLock
lock(focusedWindow
->GetMozContainer());
481 struct wl_surface
* waylandSurface
= lock
.GetSurface();
482 if (waylandSurface
) {
483 mWaylandInhibitor
= zwp_idle_inhibit_manager_v1_create_inhibitor(
484 waylandDisplay
->GetIdleInhibitManager(), waylandSurface
);
488 WAKE_LOCK_LOG("InhibitWaylandIdle() %s",
489 !!mWaylandInhibitor
? "succeeded" : "failed");
490 return !!mWaylandInhibitor
;
493 bool WakeLockTopic::UninhibitWaylandIdle() {
494 WAKE_LOCK_LOG("UninhibitWaylandIdle() mWaylandInhibitor %p",
498 if (!mWaylandInhibitor
) {
501 zwp_idle_inhibitor_v1_destroy(mWaylandInhibitor
);
502 mWaylandInhibitor
= nullptr;
507 bool WakeLockTopic::SendInhibit() {
508 WAKE_LOCK_LOG("WakeLockTopic::SendInhibit() WakeLockType %s",
509 WakeLockTypeNames
[sWakeLockType
]);
510 MOZ_ASSERT(sWakeLockType
!= Initial
);
512 switch (sWakeLockType
) {
513 #if defined(MOZ_ENABLE_DBUS)
514 case FreeDesktopScreensaver
:
515 InhibitFreeDesktopScreensaver();
517 case FreeDesktopPower
:
518 InhibitFreeDesktopPower();
526 return InhibitXScreenSaver(true);
528 #if defined(MOZ_WAYLAND)
529 case WaylandIdleInhibit
:
530 return InhibitWaylandIdle();
538 bool WakeLockTopic::SendUninhibit() {
539 WAKE_LOCK_LOG("WakeLockTopic::SendUninhibit() WakeLockType %s",
540 WakeLockTypeNames
[sWakeLockType
]);
541 MOZ_ASSERT(sWakeLockType
!= Initial
);
542 switch (sWakeLockType
) {
543 #if defined(MOZ_ENABLE_DBUS)
544 case FreeDesktopScreensaver
:
545 UninhibitFreeDesktopScreensaver();
547 case FreeDesktopPower
:
548 UninhibitFreeDesktopPower();
556 return InhibitXScreenSaver(false);
558 #if defined(MOZ_WAYLAND)
559 case WaylandIdleInhibit
:
560 return UninhibitWaylandIdle();
568 nsresult
WakeLockTopic::InhibitScreensaver() {
569 WAKE_LOCK_LOG("WakeLockTopic::InhibitScreensaver() state %d", mInhibited
);
572 // Screensaver is inhibited. Nothing to do here.
575 mShouldInhibit
= true;
577 // Iterate through wake lock types in case of failure.
578 while (!SendInhibit() && SwitchToNextWakeLockType()) {
582 return (sWakeLockType
!= Unsupported
) ? NS_OK
: NS_ERROR_FAILURE
;
585 nsresult
WakeLockTopic::UninhibitScreensaver() {
586 WAKE_LOCK_LOG("WakeLockTopic::UninhibitScreensaver() state %d", mInhibited
);
589 // Screensaver isn't inhibited. Nothing to do here.
592 mShouldInhibit
= false;
594 // Don't switch wake lock type in case of failure.
595 // We need to use the same lock/unlock type.
596 return SendUninhibit() ? NS_OK
: NS_ERROR_FAILURE
;
599 bool WakeLockTopic::IsWakeLockTypeAvailable(int aWakeLockType
) {
600 switch (aWakeLockType
) {
601 #if defined(MOZ_ENABLE_DBUS)
602 case FreeDesktopScreensaver
:
603 case FreeDesktopPower
:
609 if (!GdkIsX11Display()) {
612 if (!CheckXScreenSaverSupport()) {
613 WAKE_LOCK_LOG(" XScreenSaverSupport is missing!");
618 #if defined(MOZ_WAYLAND)
619 case WaylandIdleInhibit
:
620 if (!GdkIsWaylandDisplay()) {
623 if (!CheckWaylandIdleInhibitSupport()) {
624 WAKE_LOCK_LOG(" WaylandIdleInhibitSupport is missing!");
634 bool WakeLockTopic::SwitchToNextWakeLockType() {
635 WAKE_LOCK_LOG("WakeLockTopic::SwitchToNextWakeLockType() WakeLockType %s",
636 WakeLockTypeNames
[sWakeLockType
]);
638 if (sWakeLockType
== Unsupported
) {
643 auto printWakeLocktype
= MakeScopeExit([&] {
644 WAKE_LOCK_LOG(" switched to WakeLockType %s",
645 WakeLockTypeNames
[sWakeLockType
]);
649 while (sWakeLockType
!= Unsupported
) {
651 if (IsWakeLockTypeAvailable(sWakeLockType
)) {
659 WakeLockListener
* WakeLockListener::GetSingleton(bool aCreate
) {
660 if (!sSingleton
&& aCreate
) {
661 sSingleton
= new WakeLockListener();
667 void WakeLockListener::Shutdown() {
668 WAKE_LOCK_LOG("WakeLockListener::Shutdown()");
669 sSingleton
= nullptr;
672 nsresult
WakeLockListener::Callback(const nsAString
& topic
,
673 const nsAString
& state
) {
674 if (!topic
.Equals(u
"screen"_ns
) && !topic
.Equals(u
"video-playing"_ns
) &&
675 !topic
.Equals(u
"autoscroll"_ns
)) {
679 WAKE_LOCK_LOG("WakeLockListener %s state %s",
680 NS_ConvertUTF16toUTF8(topic
).get(),
681 NS_ConvertUTF16toUTF8(state
).get());
683 RefPtr
<WakeLockTopic
>* topicLock
=
684 mTopics
.GetOrInsertNew(topic
, MakeRefPtr
<WakeLockTopic
>(topic
));
686 // Treat "locked-background" the same as "unlocked" on desktop linux.
687 bool shouldLock
= state
.EqualsLiteral("locked-foreground");
688 WAKE_LOCK_LOG("shouldLock %d", shouldLock
);
690 return shouldLock
? topicLock
->get()->InhibitScreensaver()
691 : topicLock
->get()->UninhibitScreensaver();