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