1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:expandtab:shiftwidth=2:tabstop=2:
3  */
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/. */
7 
8 #ifdef MOZ_ENABLE_DBUS
9 
10 #  include "WakeLockListener.h"
11 
12 #  include <dbus/dbus.h>
13 #  include <dbus/dbus-glib-lowlevel.h>
14 
15 #  include "WidgetUtilsGtk.h"
16 
17 #  if defined(MOZ_X11)
18 #    include "prlink.h"
19 #    include <gdk/gdk.h>
20 #    include <gdk/gdkx.h>
21 #  endif
22 
23 #  if defined(MOZ_WAYLAND)
24 #    include "mozilla/widget/nsWaylandDisplay.h"
25 #    include "nsWindow.h"
26 #    include "mozilla/dom/power/PowerManagerService.h"
27 #  endif
28 
29 #  define FREEDESKTOP_SCREENSAVER_TARGET "org.freedesktop.ScreenSaver"
30 #  define FREEDESKTOP_SCREENSAVER_OBJECT "/ScreenSaver"
31 #  define FREEDESKTOP_SCREENSAVER_INTERFACE "org.freedesktop.ScreenSaver"
32 
33 #  define SESSION_MANAGER_TARGET "org.gnome.SessionManager"
34 #  define SESSION_MANAGER_OBJECT "/org/gnome/SessionManager"
35 #  define SESSION_MANAGER_INTERFACE "org.gnome.SessionManager"
36 
37 #  define DBUS_TIMEOUT (-1)
38 
39 using namespace mozilla;
40 using namespace mozilla::widget;
41 
42 NS_IMPL_ISUPPORTS(WakeLockListener, nsIDOMMozWakeLockListener)
43 
44 StaticRefPtr<WakeLockListener> WakeLockListener::sSingleton;
45 
46 #  define WAKE_LOCK_LOG(...) \
47     MOZ_LOG(gLinuxWakeLockLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
48 static mozilla::LazyLogModule gLinuxWakeLockLog("LinuxWakeLock");
49 
50 enum WakeLockDesktopEnvironment {
51   FreeDesktop,
52   GNOME,
53 #  if defined(MOZ_X11)
54   XScreenSaver,
55 #  endif
56 #  if defined(MOZ_WAYLAND)
57   WaylandIdleInhibit,
58 #  endif
59   Unsupported,
60 };
61 
62 class WakeLockTopic {
63  public:
WakeLockTopic(const nsAString & aTopic,DBusConnection * aConnection)64   WakeLockTopic(const nsAString& aTopic, DBusConnection* aConnection)
65       :
66 #  if defined(MOZ_WAYLAND)
67         mWaylandInhibitor(nullptr),
68 #  endif
69         mTopic(NS_ConvertUTF16toUTF8(aTopic)),
70         mConnection(aConnection),
71         mDesktopEnvironment(FreeDesktop),
72         mInhibitRequest(0),
73         mShouldInhibit(false),
74         mWaitingForReply(false) {
75   }
76 
77   nsresult InhibitScreensaver(void);
78   nsresult UninhibitScreensaver(void);
79 
80  private:
81   bool SendInhibit();
82   bool SendUninhibit();
83 
84   bool SendFreeDesktopInhibitMessage();
85   bool SendGNOMEInhibitMessage();
86   bool SendMessage(DBusMessage* aMessage);
87 
88 #  if defined(MOZ_X11)
89   static bool CheckXScreenSaverSupport();
90   static bool InhibitXScreenSaver(bool inhibit);
91 #  endif
92 
93 #  if defined(MOZ_WAYLAND)
94   zwp_idle_inhibitor_v1* mWaylandInhibitor;
95   static bool CheckWaylandIdleInhibitSupport();
96   bool InhibitWaylandIdle();
97   bool UninhibitWaylandIdle();
98 #  endif
99 
100   static void ReceiveInhibitReply(DBusPendingCall* aPending, void* aUserData);
101   void InhibitFailed();
102   void InhibitSucceeded(uint32_t aInhibitRequest);
103 
104   nsCString mTopic;
105   RefPtr<DBusConnection> mConnection;
106 
107   WakeLockDesktopEnvironment mDesktopEnvironment;
108 
109   uint32_t mInhibitRequest;
110 
111   bool mShouldInhibit;
112   bool mWaitingForReply;
113 };
114 
SendMessage(DBusMessage * aMessage)115 bool WakeLockTopic::SendMessage(DBusMessage* aMessage) {
116   // send message and get a handle for a reply
117   RefPtr<DBusPendingCall> reply;
118   dbus_connection_send_with_reply(mConnection, aMessage,
119                                   reply.StartAssignment(), DBUS_TIMEOUT);
120   if (!reply) {
121     return false;
122   }
123 
124   dbus_pending_call_set_notify(reply, &ReceiveInhibitReply, this, NULL);
125 
126   return true;
127 }
128 
SendFreeDesktopInhibitMessage()129 bool WakeLockTopic::SendFreeDesktopInhibitMessage() {
130   RefPtr<DBusMessage> message =
131       already_AddRefed<DBusMessage>(dbus_message_new_method_call(
132           FREEDESKTOP_SCREENSAVER_TARGET, FREEDESKTOP_SCREENSAVER_OBJECT,
133           FREEDESKTOP_SCREENSAVER_INTERFACE, "Inhibit"));
134 
135   if (!message) {
136     return false;
137   }
138 
139   const char* app = g_get_prgname();
140   const char* topic = mTopic.get();
141   dbus_message_append_args(message, DBUS_TYPE_STRING, &app, DBUS_TYPE_STRING,
142                            &topic, DBUS_TYPE_INVALID);
143 
144   return SendMessage(message);
145 }
146 
SendGNOMEInhibitMessage()147 bool WakeLockTopic::SendGNOMEInhibitMessage() {
148   RefPtr<DBusMessage> message =
149       already_AddRefed<DBusMessage>(dbus_message_new_method_call(
150           SESSION_MANAGER_TARGET, SESSION_MANAGER_OBJECT,
151           SESSION_MANAGER_INTERFACE, "Inhibit"));
152 
153   if (!message) {
154     return false;
155   }
156 
157   static const uint32_t xid = 0;
158   static const uint32_t flags = (1 << 3);  // Inhibit idle
159   const char* app = g_get_prgname();
160   const char* topic = mTopic.get();
161   dbus_message_append_args(message, DBUS_TYPE_STRING, &app, DBUS_TYPE_UINT32,
162                            &xid, DBUS_TYPE_STRING, &topic, DBUS_TYPE_UINT32,
163                            &flags, DBUS_TYPE_INVALID);
164 
165   return SendMessage(message);
166 }
167 
168 #  if defined(MOZ_X11)
169 
170 typedef Bool (*_XScreenSaverQueryExtension_fn)(Display* dpy, int* event_base,
171                                                int* error_base);
172 typedef Bool (*_XScreenSaverQueryVersion_fn)(Display* dpy, int* major,
173                                              int* minor);
174 typedef void (*_XScreenSaverSuspend_fn)(Display* dpy, Bool suspend);
175 
176 static PRLibrary* sXssLib = nullptr;
177 static _XScreenSaverQueryExtension_fn _XSSQueryExtension = nullptr;
178 static _XScreenSaverQueryVersion_fn _XSSQueryVersion = nullptr;
179 static _XScreenSaverSuspend_fn _XSSSuspend = nullptr;
180 
181 /* static */
CheckXScreenSaverSupport()182 bool WakeLockTopic::CheckXScreenSaverSupport() {
183   if (!sXssLib) {
184     sXssLib = PR_LoadLibrary("libXss.so.1");
185     if (!sXssLib) {
186       return false;
187     }
188   }
189 
190   _XSSQueryExtension = (_XScreenSaverQueryExtension_fn)PR_FindFunctionSymbol(
191       sXssLib, "XScreenSaverQueryExtension");
192   _XSSQueryVersion = (_XScreenSaverQueryVersion_fn)PR_FindFunctionSymbol(
193       sXssLib, "XScreenSaverQueryVersion");
194   _XSSSuspend = (_XScreenSaverSuspend_fn)PR_FindFunctionSymbol(
195       sXssLib, "XScreenSaverSuspend");
196   if (!_XSSQueryExtension || !_XSSQueryVersion || !_XSSSuspend) {
197     return false;
198   }
199 
200   GdkDisplay* gDisplay = gdk_display_get_default();
201   if (!GdkIsX11Display(gDisplay)) {
202     return false;
203   }
204   Display* display = GDK_DISPLAY_XDISPLAY(gDisplay);
205 
206   int throwaway;
207   if (!_XSSQueryExtension(display, &throwaway, &throwaway)) return false;
208 
209   int major, minor;
210   if (!_XSSQueryVersion(display, &major, &minor)) return false;
211   // Needs to be compatible with version 1.1
212   if (major != 1) return false;
213   if (minor < 1) return false;
214 
215   return true;
216 }
217 
218 /* static */
InhibitXScreenSaver(bool inhibit)219 bool WakeLockTopic::InhibitXScreenSaver(bool inhibit) {
220   // Should only be called if CheckXScreenSaverSupport returns true.
221   // There's a couple of safety checks here nonetheless.
222   if (!_XSSSuspend) {
223     return false;
224   }
225   GdkDisplay* gDisplay = gdk_display_get_default();
226   if (!GdkIsX11Display(gDisplay)) {
227     return false;
228   }
229   Display* display = GDK_DISPLAY_XDISPLAY(gDisplay);
230   _XSSSuspend(display, inhibit);
231   return true;
232 }
233 
234 #  endif
235 
236 #  if defined(MOZ_WAYLAND)
237 
238 /* static */
CheckWaylandIdleInhibitSupport()239 bool WakeLockTopic::CheckWaylandIdleInhibitSupport() {
240   RefPtr<nsWaylandDisplay> waylandDisplay = WaylandDisplayGet();
241   return waylandDisplay && waylandDisplay->GetIdleInhibitManager() != nullptr;
242 }
243 
InhibitWaylandIdle()244 bool WakeLockTopic::InhibitWaylandIdle() {
245   RefPtr<nsWaylandDisplay> waylandDisplay = WaylandDisplayGet();
246   if (!waylandDisplay) {
247     return false;
248   }
249 
250   nsWindow* focusedWindow = nsWindow::GetFocusedWindow();
251   if (!focusedWindow) {
252     return false;
253   }
254 
255   UninhibitWaylandIdle();
256 
257   MozContainer* container = focusedWindow->GetMozContainer();
258   wl_surface* waylandSurface = moz_container_wayland_surface_lock(container);
259   if (waylandSurface) {
260     mWaylandInhibitor = zwp_idle_inhibit_manager_v1_create_inhibitor(
261         waylandDisplay->GetIdleInhibitManager(), waylandSurface);
262     moz_container_wayland_surface_unlock(container, &waylandSurface);
263   }
264   return true;
265 }
266 
UninhibitWaylandIdle()267 bool WakeLockTopic::UninhibitWaylandIdle() {
268   if (mWaylandInhibitor == nullptr) return false;
269 
270   zwp_idle_inhibitor_v1_destroy(mWaylandInhibitor);
271   mWaylandInhibitor = nullptr;
272 
273   return true;
274 }
275 
276 #  endif
277 
SendInhibit()278 bool WakeLockTopic::SendInhibit() {
279   bool sendOk = false;
280 
281   switch (mDesktopEnvironment) {
282     case FreeDesktop:
283       sendOk = SendFreeDesktopInhibitMessage();
284       break;
285     case GNOME:
286       sendOk = SendGNOMEInhibitMessage();
287       break;
288 #  if defined(MOZ_X11)
289     case XScreenSaver:
290       return InhibitXScreenSaver(true);
291 #  endif
292 #  if defined(MOZ_WAYLAND)
293     case WaylandIdleInhibit:
294       return InhibitWaylandIdle();
295 #  endif
296     case Unsupported:
297       return false;
298   }
299 
300   if (sendOk) {
301     mWaitingForReply = true;
302   }
303 
304   return sendOk;
305 }
306 
SendUninhibit()307 bool WakeLockTopic::SendUninhibit() {
308   RefPtr<DBusMessage> message;
309 
310   if (mDesktopEnvironment == FreeDesktop) {
311     message = already_AddRefed<DBusMessage>(dbus_message_new_method_call(
312         FREEDESKTOP_SCREENSAVER_TARGET, FREEDESKTOP_SCREENSAVER_OBJECT,
313         FREEDESKTOP_SCREENSAVER_INTERFACE, "UnInhibit"));
314   } else if (mDesktopEnvironment == GNOME) {
315     message = already_AddRefed<DBusMessage>(dbus_message_new_method_call(
316         SESSION_MANAGER_TARGET, SESSION_MANAGER_OBJECT,
317         SESSION_MANAGER_INTERFACE, "Uninhibit"));
318   }
319 #  if defined(MOZ_X11)
320   else if (mDesktopEnvironment == XScreenSaver) {
321     return InhibitXScreenSaver(false);
322   }
323 #  endif
324 #  if defined(MOZ_WAYLAND)
325   else if (mDesktopEnvironment == WaylandIdleInhibit) {
326     return UninhibitWaylandIdle();
327   }
328 #  endif
329 
330   if (!message) {
331     return false;
332   }
333 
334   dbus_message_append_args(message, DBUS_TYPE_UINT32, &mInhibitRequest,
335                            DBUS_TYPE_INVALID);
336 
337   dbus_connection_send(mConnection, message, nullptr);
338   dbus_connection_flush(mConnection);
339 
340   mInhibitRequest = 0;
341 
342   return true;
343 }
344 
InhibitScreensaver()345 nsresult WakeLockTopic::InhibitScreensaver() {
346   if (mShouldInhibit) {
347     // Screensaver is inhibited. Nothing to do here.
348     return NS_OK;
349   }
350 
351   mShouldInhibit = true;
352 
353   if (mWaitingForReply) {
354     // We already have a screensaver inhibit request pending. This can happen
355     // if InhibitScreensaver is called, then UninhibitScreensaver, then
356     // InhibitScreensaver again quickly.
357     return NS_OK;
358   }
359 
360   return SendInhibit() ? NS_OK : NS_ERROR_FAILURE;
361 }
362 
UninhibitScreensaver()363 nsresult WakeLockTopic::UninhibitScreensaver() {
364   if (!mShouldInhibit) {
365     // Screensaver isn't inhibited. Nothing to do here.
366     return NS_OK;
367   }
368 
369   mShouldInhibit = false;
370 
371   if (mWaitingForReply) {
372     // If we're still waiting for a response to our inhibit request, we can't
373     // do anything until we get a dbus message back. The callbacks below will
374     // check |mShouldInhibit| and act accordingly.
375     return NS_OK;
376   }
377 
378   return SendUninhibit() ? NS_OK : NS_ERROR_FAILURE;
379 }
380 
InhibitFailed()381 void WakeLockTopic::InhibitFailed() {
382   mWaitingForReply = false;
383 
384   if (mDesktopEnvironment == FreeDesktop) {
385     mDesktopEnvironment = GNOME;
386 #  if defined(MOZ_X11)
387   } else if (mDesktopEnvironment == GNOME && CheckXScreenSaverSupport()) {
388     mDesktopEnvironment = XScreenSaver;
389 #  endif
390 #  if defined(MOZ_WAYLAND)
391   } else if (mDesktopEnvironment == GNOME && CheckWaylandIdleInhibitSupport()) {
392     mDesktopEnvironment = WaylandIdleInhibit;
393 #  endif
394   } else {
395     mDesktopEnvironment = Unsupported;
396     mShouldInhibit = false;
397   }
398 
399   if (!mShouldInhibit) {
400     // We were interrupted by UninhibitScreensaver() before we could find the
401     // correct desktop environment.
402     return;
403   }
404 
405   SendInhibit();
406 }
407 
InhibitSucceeded(uint32_t aInhibitRequest)408 void WakeLockTopic::InhibitSucceeded(uint32_t aInhibitRequest) {
409   mWaitingForReply = false;
410   mInhibitRequest = aInhibitRequest;
411 
412   if (!mShouldInhibit) {
413     // We successfully inhibited the screensaver, but UninhibitScreensaver()
414     // was called while we were waiting for a reply.
415     SendUninhibit();
416   }
417 }
418 
419 /* static */
ReceiveInhibitReply(DBusPendingCall * pending,void * user_data)420 void WakeLockTopic::ReceiveInhibitReply(DBusPendingCall* pending,
421                                         void* user_data) {
422   if (!WakeLockListener::GetSingleton(false)) {
423     // The WakeLockListener (and therefore our topic) was deleted while we were
424     // waiting for a reply.
425     return;
426   }
427 
428   WakeLockTopic* self = static_cast<WakeLockTopic*>(user_data);
429 
430   RefPtr<DBusMessage> msg =
431       already_AddRefed<DBusMessage>(dbus_pending_call_steal_reply(pending));
432   if (!msg) {
433     return;
434   }
435 
436   if (dbus_message_get_type(msg) == DBUS_MESSAGE_TYPE_METHOD_RETURN) {
437     uint32_t inhibitRequest;
438 
439     if (dbus_message_get_args(msg, nullptr, DBUS_TYPE_UINT32, &inhibitRequest,
440                               DBUS_TYPE_INVALID)) {
441       self->InhibitSucceeded(inhibitRequest);
442     }
443   } else {
444     self->InhibitFailed();
445   }
446 }
447 
WakeLockListener()448 WakeLockListener::WakeLockListener() : mConnection(nullptr) {}
449 
450 /* static */
GetSingleton(bool aCreate)451 WakeLockListener* WakeLockListener::GetSingleton(bool aCreate) {
452   if (!sSingleton && aCreate) {
453     sSingleton = new WakeLockListener();
454   }
455 
456   return sSingleton;
457 }
458 
459 /* static */
Shutdown()460 void WakeLockListener::Shutdown() { sSingleton = nullptr; }
461 
EnsureDBusConnection()462 bool WakeLockListener::EnsureDBusConnection() {
463   if (!mConnection) {
464     mConnection = already_AddRefed<DBusConnection>(
465         dbus_bus_get(DBUS_BUS_SESSION, nullptr));
466 
467     if (mConnection) {
468       dbus_connection_set_exit_on_disconnect(mConnection, false);
469       dbus_connection_setup_with_g_main(mConnection, nullptr);
470     }
471   }
472 
473   return mConnection != nullptr;
474 }
475 
Callback(const nsAString & topic,const nsAString & state)476 nsresult WakeLockListener::Callback(const nsAString& topic,
477                                     const nsAString& state) {
478   if (!EnsureDBusConnection()) {
479     return NS_ERROR_FAILURE;
480   }
481 
482   if (!topic.Equals(u"screen"_ns) && !topic.Equals(u"audio-playing"_ns) &&
483       !topic.Equals(u"video-playing"_ns))
484     return NS_OK;
485 
486   WakeLockTopic* const topicLock =
487       mTopics.GetOrInsertNew(topic, topic, mConnection);
488 
489   // Treat "locked-background" the same as "unlocked" on desktop linux.
490   bool shouldLock = state.EqualsLiteral("locked-foreground");
491   WAKE_LOCK_LOG("topic=%s, shouldLock=%d", NS_ConvertUTF16toUTF8(topic).get(),
492                 shouldLock);
493 
494   return shouldLock ? topicLock->InhibitScreensaver()
495                     : topicLock->UninhibitScreensaver();
496 }
497 
498 #endif
499