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/. */
10 # include "WakeLockListener.h"
12 # include <dbus/dbus.h>
13 # include <dbus/dbus-glib-lowlevel.h>
15 # include "WidgetUtilsGtk.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 # define FREEDESKTOP_SCREENSAVER_TARGET "org.freedesktop.ScreenSaver"
31 # define FREEDESKTOP_SCREENSAVER_OBJECT "/ScreenSaver"
32 # define FREEDESKTOP_SCREENSAVER_INTERFACE "org.freedesktop.ScreenSaver"
34 # define FREEDESKTOP_POWER_TARGET "org.freedesktop.PowerManagement"
35 # define FREEDESKTOP_POWER_OBJECT "/org/freedesktop/PowerManagement/Inhibit"
36 # define FREEDESKTOP_POWER_INTERFACE "org.freedesktop.PowerManagement.Inhibit"
38 # define SESSION_MANAGER_TARGET "org.gnome.SessionManager"
39 # define SESSION_MANAGER_OBJECT "/org/gnome/SessionManager"
40 # define SESSION_MANAGER_INTERFACE "org.gnome.SessionManager"
42 # define DBUS_TIMEOUT (-1)
44 using namespace mozilla
;
45 using namespace mozilla::widget
;
47 NS_IMPL_ISUPPORTS(WakeLockListener
, nsIDOMMozWakeLockListener
)
49 StaticRefPtr
<WakeLockListener
> WakeLockListener::sSingleton
;
51 # define WAKE_LOCK_LOG(...) \
52 MOZ_LOG(gLinuxWakeLockLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
53 static mozilla::LazyLogModule
gLinuxWakeLockLog("LinuxWakeLock");
55 enum WakeLockDesktopEnvironment
{
56 FreeDesktopScreensaver
,
62 # if defined(MOZ_WAYLAND)
70 WakeLockTopic(const nsAString
& aTopic
, DBusConnection
* aConnection
)
72 # if defined(MOZ_WAYLAND)
73 mWaylandInhibitor(nullptr),
75 mTopic(NS_ConvertUTF16toUTF8(aTopic
)),
76 mConnection(aConnection
),
77 mDesktopEnvironment(FreeDesktopScreensaver
),
79 mShouldInhibit(false),
80 mWaitingForReply(false) {
83 nsresult
InhibitScreensaver(void);
84 nsresult
UninhibitScreensaver(void);
90 bool SendFreeDesktopPowerInhibitMessage();
91 bool SendFreeDesktopScreensaverInhibitMessage();
92 bool SendGNOMEInhibitMessage();
93 bool SendMessage(DBusMessage
* aMessage
);
96 static bool CheckXScreenSaverSupport();
97 static bool InhibitXScreenSaver(bool inhibit
);
100 # if defined(MOZ_WAYLAND)
101 zwp_idle_inhibitor_v1
* mWaylandInhibitor
;
102 static bool CheckWaylandIdleInhibitSupport();
103 bool InhibitWaylandIdle();
104 bool UninhibitWaylandIdle();
107 static void ReceiveInhibitReply(DBusPendingCall
* aPending
, void* aUserData
);
108 void InhibitFailed();
109 void InhibitSucceeded(uint32_t aInhibitRequest
);
112 RefPtr
<DBusConnection
> mConnection
;
114 WakeLockDesktopEnvironment mDesktopEnvironment
;
116 uint32_t mInhibitRequest
;
119 bool mWaitingForReply
;
122 bool WakeLockTopic::SendMessage(DBusMessage
* aMessage
) {
123 // send message and get a handle for a reply
124 RefPtr
<DBusPendingCall
> reply
;
125 dbus_connection_send_with_reply(mConnection
, aMessage
,
126 reply
.StartAssignment(), DBUS_TIMEOUT
);
131 dbus_pending_call_set_notify(reply
, &ReceiveInhibitReply
, this, NULL
);
136 bool WakeLockTopic::SendFreeDesktopPowerInhibitMessage() {
137 RefPtr
<DBusMessage
> message
=
138 already_AddRefed
<DBusMessage
>(dbus_message_new_method_call(
139 FREEDESKTOP_POWER_TARGET
, FREEDESKTOP_POWER_OBJECT
,
140 FREEDESKTOP_POWER_INTERFACE
, "Inhibit"));
146 const char* app
= g_get_prgname();
147 const char* topic
= mTopic
.get();
148 dbus_message_append_args(message
, DBUS_TYPE_STRING
, &app
, DBUS_TYPE_STRING
,
149 &topic
, DBUS_TYPE_INVALID
);
151 return SendMessage(message
);
154 bool WakeLockTopic::SendFreeDesktopScreensaverInhibitMessage() {
155 RefPtr
<DBusMessage
> message
=
156 already_AddRefed
<DBusMessage
>(dbus_message_new_method_call(
157 FREEDESKTOP_SCREENSAVER_TARGET
, FREEDESKTOP_SCREENSAVER_OBJECT
,
158 FREEDESKTOP_SCREENSAVER_INTERFACE
, "Inhibit"));
164 const char* app
= g_get_prgname();
165 const char* topic
= mTopic
.get();
166 dbus_message_append_args(message
, DBUS_TYPE_STRING
, &app
, DBUS_TYPE_STRING
,
167 &topic
, DBUS_TYPE_INVALID
);
169 return SendMessage(message
);
172 bool WakeLockTopic::SendGNOMEInhibitMessage() {
173 RefPtr
<DBusMessage
> message
=
174 already_AddRefed
<DBusMessage
>(dbus_message_new_method_call(
175 SESSION_MANAGER_TARGET
, SESSION_MANAGER_OBJECT
,
176 SESSION_MANAGER_INTERFACE
, "Inhibit"));
182 static const uint32_t xid
= 0;
183 static const uint32_t flags
= (1 << 3); // Inhibit idle
184 const char* app
= g_get_prgname();
185 const char* topic
= mTopic
.get();
186 dbus_message_append_args(message
, DBUS_TYPE_STRING
, &app
, DBUS_TYPE_UINT32
,
187 &xid
, DBUS_TYPE_STRING
, &topic
, DBUS_TYPE_UINT32
,
188 &flags
, DBUS_TYPE_INVALID
);
190 return SendMessage(message
);
193 # if defined(MOZ_X11)
195 typedef Bool (*_XScreenSaverQueryExtension_fn
)(Display
* dpy
, int* event_base
,
197 typedef Bool (*_XScreenSaverQueryVersion_fn
)(Display
* dpy
, int* major
,
199 typedef void (*_XScreenSaverSuspend_fn
)(Display
* dpy
, Bool suspend
);
201 static PRLibrary
* sXssLib
= nullptr;
202 static _XScreenSaverQueryExtension_fn _XSSQueryExtension
= nullptr;
203 static _XScreenSaverQueryVersion_fn _XSSQueryVersion
= nullptr;
204 static _XScreenSaverSuspend_fn _XSSSuspend
= nullptr;
207 bool WakeLockTopic::CheckXScreenSaverSupport() {
209 sXssLib
= PR_LoadLibrary("libXss.so.1");
215 _XSSQueryExtension
= (_XScreenSaverQueryExtension_fn
)PR_FindFunctionSymbol(
216 sXssLib
, "XScreenSaverQueryExtension");
217 _XSSQueryVersion
= (_XScreenSaverQueryVersion_fn
)PR_FindFunctionSymbol(
218 sXssLib
, "XScreenSaverQueryVersion");
219 _XSSSuspend
= (_XScreenSaverSuspend_fn
)PR_FindFunctionSymbol(
220 sXssLib
, "XScreenSaverSuspend");
221 if (!_XSSQueryExtension
|| !_XSSQueryVersion
|| !_XSSSuspend
) {
225 GdkDisplay
* gDisplay
= gdk_display_get_default();
226 if (!GdkIsX11Display(gDisplay
)) {
229 Display
* display
= GDK_DISPLAY_XDISPLAY(gDisplay
);
232 if (!_XSSQueryExtension(display
, &throwaway
, &throwaway
)) return false;
235 if (!_XSSQueryVersion(display
, &major
, &minor
)) return false;
236 // Needs to be compatible with version 1.1
237 if (major
!= 1) return false;
238 if (minor
< 1) return false;
244 bool WakeLockTopic::InhibitXScreenSaver(bool inhibit
) {
245 // Should only be called if CheckXScreenSaverSupport returns true.
246 // There's a couple of safety checks here nonetheless.
250 GdkDisplay
* gDisplay
= gdk_display_get_default();
251 if (!GdkIsX11Display(gDisplay
)) {
254 Display
* display
= GDK_DISPLAY_XDISPLAY(gDisplay
);
255 _XSSSuspend(display
, inhibit
);
261 # if defined(MOZ_WAYLAND)
264 bool WakeLockTopic::CheckWaylandIdleInhibitSupport() {
265 RefPtr
<nsWaylandDisplay
> waylandDisplay
= WaylandDisplayGet();
266 return waylandDisplay
&& waylandDisplay
->GetIdleInhibitManager() != nullptr;
269 bool WakeLockTopic::InhibitWaylandIdle() {
270 RefPtr
<nsWaylandDisplay
> waylandDisplay
= WaylandDisplayGet();
271 if (!waylandDisplay
) {
275 nsWindow
* focusedWindow
= nsWindow::GetFocusedWindow();
276 if (!focusedWindow
) {
280 UninhibitWaylandIdle();
282 MozContainer
* container
= focusedWindow
->GetMozContainer();
283 wl_surface
* waylandSurface
= moz_container_wayland_surface_lock(container
);
284 if (waylandSurface
) {
285 mWaylandInhibitor
= zwp_idle_inhibit_manager_v1_create_inhibitor(
286 waylandDisplay
->GetIdleInhibitManager(), waylandSurface
);
287 moz_container_wayland_surface_unlock(container
, &waylandSurface
);
292 bool WakeLockTopic::UninhibitWaylandIdle() {
293 if (mWaylandInhibitor
== nullptr) return false;
295 zwp_idle_inhibitor_v1_destroy(mWaylandInhibitor
);
296 mWaylandInhibitor
= nullptr;
303 bool WakeLockTopic::SendInhibit() {
306 switch (mDesktopEnvironment
) {
307 case FreeDesktopScreensaver
:
308 sendOk
= SendFreeDesktopScreensaverInhibitMessage();
310 case FreeDesktopPower
:
311 sendOk
= SendFreeDesktopPowerInhibitMessage();
314 sendOk
= SendGNOMEInhibitMessage();
316 # if defined(MOZ_X11)
318 return InhibitXScreenSaver(true);
320 # if defined(MOZ_WAYLAND)
321 case WaylandIdleInhibit
:
322 return InhibitWaylandIdle();
329 mWaitingForReply
= true;
335 bool WakeLockTopic::SendUninhibit() {
336 RefPtr
<DBusMessage
> message
;
338 if (mDesktopEnvironment
== FreeDesktopScreensaver
) {
339 message
= already_AddRefed
<DBusMessage
>(dbus_message_new_method_call(
340 FREEDESKTOP_SCREENSAVER_TARGET
, FREEDESKTOP_SCREENSAVER_OBJECT
,
341 FREEDESKTOP_SCREENSAVER_INTERFACE
, "UnInhibit"));
342 } else if (mDesktopEnvironment
== FreeDesktopPower
) {
343 message
= already_AddRefed
<DBusMessage
>(dbus_message_new_method_call(
344 FREEDESKTOP_POWER_TARGET
, FREEDESKTOP_POWER_OBJECT
,
345 FREEDESKTOP_POWER_INTERFACE
, "UnInhibit"));
346 } else if (mDesktopEnvironment
== GNOME
) {
347 message
= already_AddRefed
<DBusMessage
>(dbus_message_new_method_call(
348 SESSION_MANAGER_TARGET
, SESSION_MANAGER_OBJECT
,
349 SESSION_MANAGER_INTERFACE
, "Uninhibit"));
351 # if defined(MOZ_X11)
352 else if (mDesktopEnvironment
== XScreenSaver
) {
353 return InhibitXScreenSaver(false);
356 # if defined(MOZ_WAYLAND)
357 else if (mDesktopEnvironment
== WaylandIdleInhibit
) {
358 return UninhibitWaylandIdle();
366 dbus_message_append_args(message
, DBUS_TYPE_UINT32
, &mInhibitRequest
,
369 dbus_connection_send(mConnection
, message
, nullptr);
370 dbus_connection_flush(mConnection
);
377 nsresult
WakeLockTopic::InhibitScreensaver() {
378 if (mShouldInhibit
) {
379 // Screensaver is inhibited. Nothing to do here.
383 mShouldInhibit
= true;
385 if (mWaitingForReply
) {
386 // We already have a screensaver inhibit request pending. This can happen
387 // if InhibitScreensaver is called, then UninhibitScreensaver, then
388 // InhibitScreensaver again quickly.
392 return SendInhibit() ? NS_OK
: NS_ERROR_FAILURE
;
395 nsresult
WakeLockTopic::UninhibitScreensaver() {
396 if (!mShouldInhibit
) {
397 // Screensaver isn't inhibited. Nothing to do here.
401 mShouldInhibit
= false;
403 if (mWaitingForReply
) {
404 // If we're still waiting for a response to our inhibit request, we can't
405 // do anything until we get a dbus message back. The callbacks below will
406 // check |mShouldInhibit| and act accordingly.
410 return SendUninhibit() ? NS_OK
: NS_ERROR_FAILURE
;
413 void WakeLockTopic::InhibitFailed() {
414 mWaitingForReply
= false;
416 if (mDesktopEnvironment
== FreeDesktopScreensaver
) {
417 mDesktopEnvironment
= GNOME
;
418 } else if (mDesktopEnvironment
== GNOME
) {
419 mDesktopEnvironment
= FreeDesktopPower
;
420 # if defined(MOZ_X11)
421 } else if (mDesktopEnvironment
== FreeDesktopPower
&&
422 CheckXScreenSaverSupport()) {
423 mDesktopEnvironment
= XScreenSaver
;
425 # if defined(MOZ_WAYLAND)
426 } else if (mDesktopEnvironment
== FreeDesktopPower
&&
427 CheckWaylandIdleInhibitSupport()) {
428 mDesktopEnvironment
= WaylandIdleInhibit
;
431 mDesktopEnvironment
= Unsupported
;
432 mShouldInhibit
= false;
435 if (!mShouldInhibit
) {
436 // We were interrupted by UninhibitScreensaver() before we could find the
437 // correct desktop environment.
444 void WakeLockTopic::InhibitSucceeded(uint32_t aInhibitRequest
) {
445 mWaitingForReply
= false;
446 mInhibitRequest
= aInhibitRequest
;
448 if (!mShouldInhibit
) {
449 // We successfully inhibited the screensaver, but UninhibitScreensaver()
450 // was called while we were waiting for a reply.
456 void WakeLockTopic::ReceiveInhibitReply(DBusPendingCall
* pending
,
458 if (!WakeLockListener::GetSingleton(false)) {
459 // The WakeLockListener (and therefore our topic) was deleted while we were
460 // waiting for a reply.
464 WakeLockTopic
* self
= static_cast<WakeLockTopic
*>(user_data
);
466 RefPtr
<DBusMessage
> msg
=
467 already_AddRefed
<DBusMessage
>(dbus_pending_call_steal_reply(pending
));
472 if (dbus_message_get_type(msg
) == DBUS_MESSAGE_TYPE_METHOD_RETURN
) {
473 uint32_t inhibitRequest
;
475 if (dbus_message_get_args(msg
, nullptr, DBUS_TYPE_UINT32
, &inhibitRequest
,
476 DBUS_TYPE_INVALID
)) {
477 self
->InhibitSucceeded(inhibitRequest
);
480 self
->InhibitFailed();
484 WakeLockListener::WakeLockListener() : mConnection(nullptr) {}
487 WakeLockListener
* WakeLockListener::GetSingleton(bool aCreate
) {
488 if (!sSingleton
&& aCreate
) {
489 sSingleton
= new WakeLockListener();
496 void WakeLockListener::Shutdown() { sSingleton
= nullptr; }
498 bool WakeLockListener::EnsureDBusConnection() {
500 mConnection
= already_AddRefed
<DBusConnection
>(
501 dbus_bus_get(DBUS_BUS_SESSION
, nullptr));
504 dbus_connection_set_exit_on_disconnect(mConnection
, false);
505 dbus_connection_setup_with_g_main(mConnection
, nullptr);
509 return mConnection
!= nullptr;
512 nsresult
WakeLockListener::Callback(const nsAString
& topic
,
513 const nsAString
& state
) {
514 if (!EnsureDBusConnection()) {
515 return NS_ERROR_FAILURE
;
518 if (!topic
.Equals(u
"screen"_ns
) && !topic
.Equals(u
"audio-playing"_ns
) &&
519 !topic
.Equals(u
"video-playing"_ns
))
522 WakeLockTopic
* const topicLock
=
523 mTopics
.GetOrInsertNew(topic
, topic
, mConnection
);
525 // Treat "locked-background" the same as "unlocked" on desktop linux.
526 bool shouldLock
= state
.EqualsLiteral("locked-foreground");
527 WAKE_LOCK_LOG("topic=%s, shouldLock=%d", NS_ConvertUTF16toUTF8(topic
).get(),
530 return shouldLock
? topicLock
->InhibitScreensaver()
531 : topicLock
->UninhibitScreensaver();