Bug 1769547 - Do not MOZ_CRASH() on missing process r=nika
[gecko.git] / widget / gtk / WakeLockListener.cpp
blobc7d9a58e20256b7ba977f599e644c8ebacc27186
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/. */
8 #ifdef MOZ_ENABLE_DBUS
10 # include "WakeLockListener.h"
12 # include <dbus/dbus.h>
13 # include <dbus/dbus-glib-lowlevel.h>
15 # include "WidgetUtilsGtk.h"
17 # if defined(MOZ_X11)
18 # include "prlink.h"
19 # include <gdk/gdk.h>
20 # include <gdk/gdkx.h>
21 # include "X11UndefineNone.h"
22 # endif
24 # if defined(MOZ_WAYLAND)
25 # include "mozilla/widget/nsWaylandDisplay.h"
26 # include "nsWindow.h"
27 # include "mozilla/dom/power/PowerManagerService.h"
28 # endif
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,
57 FreeDesktopPower,
58 GNOME,
59 # if defined(MOZ_X11)
60 XScreenSaver,
61 # endif
62 # if defined(MOZ_WAYLAND)
63 WaylandIdleInhibit,
64 # endif
65 Unsupported,
68 class WakeLockTopic {
69 public:
70 WakeLockTopic(const nsAString& aTopic, DBusConnection* aConnection)
72 # if defined(MOZ_WAYLAND)
73 mWaylandInhibitor(nullptr),
74 # endif
75 mTopic(NS_ConvertUTF16toUTF8(aTopic)),
76 mConnection(aConnection),
77 mDesktopEnvironment(FreeDesktopScreensaver),
78 mInhibitRequest(0),
79 mShouldInhibit(false),
80 mWaitingForReply(false) {
83 nsresult InhibitScreensaver(void);
84 nsresult UninhibitScreensaver(void);
86 private:
87 bool SendInhibit();
88 bool SendUninhibit();
90 bool SendFreeDesktopPowerInhibitMessage();
91 bool SendFreeDesktopScreensaverInhibitMessage();
92 bool SendGNOMEInhibitMessage();
93 bool SendMessage(DBusMessage* aMessage);
95 # if defined(MOZ_X11)
96 static bool CheckXScreenSaverSupport();
97 static bool InhibitXScreenSaver(bool inhibit);
98 # endif
100 # if defined(MOZ_WAYLAND)
101 zwp_idle_inhibitor_v1* mWaylandInhibitor;
102 static bool CheckWaylandIdleInhibitSupport();
103 bool InhibitWaylandIdle();
104 bool UninhibitWaylandIdle();
105 # endif
107 static void ReceiveInhibitReply(DBusPendingCall* aPending, void* aUserData);
108 void InhibitFailed();
109 void InhibitSucceeded(uint32_t aInhibitRequest);
111 nsCString mTopic;
112 RefPtr<DBusConnection> mConnection;
114 WakeLockDesktopEnvironment mDesktopEnvironment;
116 uint32_t mInhibitRequest;
118 bool mShouldInhibit;
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);
127 if (!reply) {
128 return false;
131 dbus_pending_call_set_notify(reply, &ReceiveInhibitReply, this, NULL);
133 return true;
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"));
142 if (!message) {
143 return false;
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"));
160 if (!message) {
161 return false;
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"));
178 if (!message) {
179 return false;
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,
196 int* error_base);
197 typedef Bool (*_XScreenSaverQueryVersion_fn)(Display* dpy, int* major,
198 int* minor);
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;
206 /* static */
207 bool WakeLockTopic::CheckXScreenSaverSupport() {
208 if (!sXssLib) {
209 sXssLib = PR_LoadLibrary("libXss.so.1");
210 if (!sXssLib) {
211 return false;
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) {
222 return false;
225 GdkDisplay* gDisplay = gdk_display_get_default();
226 if (!GdkIsX11Display(gDisplay)) {
227 return false;
229 Display* display = GDK_DISPLAY_XDISPLAY(gDisplay);
231 int throwaway;
232 if (!_XSSQueryExtension(display, &throwaway, &throwaway)) return false;
234 int major, minor;
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;
240 return true;
243 /* static */
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.
247 if (!_XSSSuspend) {
248 return false;
250 GdkDisplay* gDisplay = gdk_display_get_default();
251 if (!GdkIsX11Display(gDisplay)) {
252 return false;
254 Display* display = GDK_DISPLAY_XDISPLAY(gDisplay);
255 _XSSSuspend(display, inhibit);
256 return true;
259 # endif
261 # if defined(MOZ_WAYLAND)
263 /* static */
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) {
272 return false;
275 nsWindow* focusedWindow = nsWindow::GetFocusedWindow();
276 if (!focusedWindow) {
277 return false;
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);
289 return true;
292 bool WakeLockTopic::UninhibitWaylandIdle() {
293 if (mWaylandInhibitor == nullptr) return false;
295 zwp_idle_inhibitor_v1_destroy(mWaylandInhibitor);
296 mWaylandInhibitor = nullptr;
298 return true;
301 # endif
303 bool WakeLockTopic::SendInhibit() {
304 bool sendOk = false;
306 switch (mDesktopEnvironment) {
307 case FreeDesktopScreensaver:
308 sendOk = SendFreeDesktopScreensaverInhibitMessage();
309 break;
310 case FreeDesktopPower:
311 sendOk = SendFreeDesktopPowerInhibitMessage();
312 break;
313 case GNOME:
314 sendOk = SendGNOMEInhibitMessage();
315 break;
316 # if defined(MOZ_X11)
317 case XScreenSaver:
318 return InhibitXScreenSaver(true);
319 # endif
320 # if defined(MOZ_WAYLAND)
321 case WaylandIdleInhibit:
322 return InhibitWaylandIdle();
323 # endif
324 case Unsupported:
325 return false;
328 if (sendOk) {
329 mWaitingForReply = true;
332 return sendOk;
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);
355 # endif
356 # if defined(MOZ_WAYLAND)
357 else if (mDesktopEnvironment == WaylandIdleInhibit) {
358 return UninhibitWaylandIdle();
360 # endif
362 if (!message) {
363 return false;
366 dbus_message_append_args(message, DBUS_TYPE_UINT32, &mInhibitRequest,
367 DBUS_TYPE_INVALID);
369 dbus_connection_send(mConnection, message, nullptr);
370 dbus_connection_flush(mConnection);
372 mInhibitRequest = 0;
374 return true;
377 nsresult WakeLockTopic::InhibitScreensaver() {
378 if (mShouldInhibit) {
379 // Screensaver is inhibited. Nothing to do here.
380 return NS_OK;
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.
389 return NS_OK;
392 return SendInhibit() ? NS_OK : NS_ERROR_FAILURE;
395 nsresult WakeLockTopic::UninhibitScreensaver() {
396 if (!mShouldInhibit) {
397 // Screensaver isn't inhibited. Nothing to do here.
398 return NS_OK;
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.
407 return NS_OK;
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;
424 # endif
425 # if defined(MOZ_WAYLAND)
426 } else if (mDesktopEnvironment == FreeDesktopPower &&
427 CheckWaylandIdleInhibitSupport()) {
428 mDesktopEnvironment = WaylandIdleInhibit;
429 # endif
430 } else {
431 mDesktopEnvironment = Unsupported;
432 mShouldInhibit = false;
435 if (!mShouldInhibit) {
436 // We were interrupted by UninhibitScreensaver() before we could find the
437 // correct desktop environment.
438 return;
441 SendInhibit();
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.
451 SendUninhibit();
455 /* static */
456 void WakeLockTopic::ReceiveInhibitReply(DBusPendingCall* pending,
457 void* user_data) {
458 if (!WakeLockListener::GetSingleton(false)) {
459 // The WakeLockListener (and therefore our topic) was deleted while we were
460 // waiting for a reply.
461 return;
464 WakeLockTopic* self = static_cast<WakeLockTopic*>(user_data);
466 RefPtr<DBusMessage> msg =
467 already_AddRefed<DBusMessage>(dbus_pending_call_steal_reply(pending));
468 if (!msg) {
469 return;
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);
479 } else {
480 self->InhibitFailed();
484 WakeLockListener::WakeLockListener() : mConnection(nullptr) {}
486 /* static */
487 WakeLockListener* WakeLockListener::GetSingleton(bool aCreate) {
488 if (!sSingleton && aCreate) {
489 sSingleton = new WakeLockListener();
492 return sSingleton;
495 /* static */
496 void WakeLockListener::Shutdown() { sSingleton = nullptr; }
498 bool WakeLockListener::EnsureDBusConnection() {
499 if (!mConnection) {
500 mConnection = already_AddRefed<DBusConnection>(
501 dbus_bus_get(DBUS_BUS_SESSION, nullptr));
503 if (mConnection) {
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))
520 return NS_OK;
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(),
528 shouldLock);
530 return shouldLock ? topicLock->InhibitScreensaver()
531 : topicLock->UninhibitScreensaver();
534 #endif