/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim:expandtab:shiftwidth=2:tabstop=2: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifdef MOZ_ENABLE_DBUS # include "WakeLockListener.h" # include # include # include "WidgetUtilsGtk.h" # if defined(MOZ_X11) # include "prlink.h" # include # include # include "X11UndefineNone.h" # endif # if defined(MOZ_WAYLAND) # include "mozilla/widget/nsWaylandDisplay.h" # include "nsWindow.h" # include "mozilla/dom/power/PowerManagerService.h" # endif # define FREEDESKTOP_SCREENSAVER_TARGET "org.freedesktop.ScreenSaver" # define FREEDESKTOP_SCREENSAVER_OBJECT "/ScreenSaver" # define FREEDESKTOP_SCREENSAVER_INTERFACE "org.freedesktop.ScreenSaver" # define FREEDESKTOP_POWER_TARGET "org.freedesktop.PowerManagement" # define FREEDESKTOP_POWER_OBJECT "/org/freedesktop/PowerManagement/Inhibit" # define FREEDESKTOP_POWER_INTERFACE "org.freedesktop.PowerManagement.Inhibit" # define SESSION_MANAGER_TARGET "org.gnome.SessionManager" # define SESSION_MANAGER_OBJECT "/org/gnome/SessionManager" # define SESSION_MANAGER_INTERFACE "org.gnome.SessionManager" # define DBUS_TIMEOUT (-1) using namespace mozilla; using namespace mozilla::widget; NS_IMPL_ISUPPORTS(WakeLockListener, nsIDOMMozWakeLockListener) StaticRefPtr WakeLockListener::sSingleton; # define WAKE_LOCK_LOG(...) \ MOZ_LOG(gLinuxWakeLockLog, mozilla::LogLevel::Debug, (__VA_ARGS__)) static mozilla::LazyLogModule gLinuxWakeLockLog("LinuxWakeLock"); enum WakeLockDesktopEnvironment { FreeDesktopScreensaver, FreeDesktopPower, GNOME, # if defined(MOZ_X11) XScreenSaver, # endif # if defined(MOZ_WAYLAND) WaylandIdleInhibit, # endif Unsupported, }; class WakeLockTopic { public: WakeLockTopic(const nsAString& aTopic, DBusConnection* aConnection) : # if defined(MOZ_WAYLAND) mWaylandInhibitor(nullptr), # endif mTopic(NS_ConvertUTF16toUTF8(aTopic)), mConnection(aConnection), mDesktopEnvironment(FreeDesktopScreensaver), mInhibitRequest(0), mShouldInhibit(false), mWaitingForReply(false) { } nsresult InhibitScreensaver(void); nsresult UninhibitScreensaver(void); private: bool SendInhibit(); bool SendUninhibit(); bool SendFreeDesktopPowerInhibitMessage(); bool SendFreeDesktopScreensaverInhibitMessage(); bool SendGNOMEInhibitMessage(); bool SendMessage(DBusMessage* aMessage); # if defined(MOZ_X11) static bool CheckXScreenSaverSupport(); static bool InhibitXScreenSaver(bool inhibit); # endif # if defined(MOZ_WAYLAND) zwp_idle_inhibitor_v1* mWaylandInhibitor; static bool CheckWaylandIdleInhibitSupport(); bool InhibitWaylandIdle(); bool UninhibitWaylandIdle(); # endif static void ReceiveInhibitReply(DBusPendingCall* aPending, void* aUserData); void InhibitFailed(); void InhibitSucceeded(uint32_t aInhibitRequest); nsCString mTopic; RefPtr mConnection; WakeLockDesktopEnvironment mDesktopEnvironment; uint32_t mInhibitRequest; bool mShouldInhibit; bool mWaitingForReply; }; bool WakeLockTopic::SendMessage(DBusMessage* aMessage) { // send message and get a handle for a reply RefPtr reply; dbus_connection_send_with_reply(mConnection, aMessage, reply.StartAssignment(), DBUS_TIMEOUT); if (!reply) { return false; } dbus_pending_call_set_notify(reply, &ReceiveInhibitReply, this, NULL); return true; } bool WakeLockTopic::SendFreeDesktopPowerInhibitMessage() { RefPtr message = already_AddRefed(dbus_message_new_method_call( FREEDESKTOP_POWER_TARGET, FREEDESKTOP_POWER_OBJECT, FREEDESKTOP_POWER_INTERFACE, "Inhibit")); if (!message) { return false; } const char* app = g_get_prgname(); const char* topic = mTopic.get(); dbus_message_append_args(message, DBUS_TYPE_STRING, &app, DBUS_TYPE_STRING, &topic, DBUS_TYPE_INVALID); return SendMessage(message); } bool WakeLockTopic::SendFreeDesktopScreensaverInhibitMessage() { RefPtr message = already_AddRefed(dbus_message_new_method_call( FREEDESKTOP_SCREENSAVER_TARGET, FREEDESKTOP_SCREENSAVER_OBJECT, FREEDESKTOP_SCREENSAVER_INTERFACE, "Inhibit")); if (!message) { return false; } const char* app = g_get_prgname(); const char* topic = mTopic.get(); dbus_message_append_args(message, DBUS_TYPE_STRING, &app, DBUS_TYPE_STRING, &topic, DBUS_TYPE_INVALID); return SendMessage(message); } bool WakeLockTopic::SendGNOMEInhibitMessage() { RefPtr message = already_AddRefed(dbus_message_new_method_call( SESSION_MANAGER_TARGET, SESSION_MANAGER_OBJECT, SESSION_MANAGER_INTERFACE, "Inhibit")); if (!message) { return false; } static const uint32_t xid = 0; static const uint32_t flags = (1 << 3); // Inhibit idle const char* app = g_get_prgname(); const char* topic = mTopic.get(); dbus_message_append_args(message, DBUS_TYPE_STRING, &app, DBUS_TYPE_UINT32, &xid, DBUS_TYPE_STRING, &topic, DBUS_TYPE_UINT32, &flags, DBUS_TYPE_INVALID); return SendMessage(message); } # if defined(MOZ_X11) typedef Bool (*_XScreenSaverQueryExtension_fn)(Display* dpy, int* event_base, int* error_base); typedef Bool (*_XScreenSaverQueryVersion_fn)(Display* dpy, int* major, int* minor); typedef void (*_XScreenSaverSuspend_fn)(Display* dpy, Bool suspend); static PRLibrary* sXssLib = nullptr; static _XScreenSaverQueryExtension_fn _XSSQueryExtension = nullptr; static _XScreenSaverQueryVersion_fn _XSSQueryVersion = nullptr; static _XScreenSaverSuspend_fn _XSSSuspend = nullptr; /* static */ bool WakeLockTopic::CheckXScreenSaverSupport() { if (!sXssLib) { sXssLib = PR_LoadLibrary("libXss.so.1"); if (!sXssLib) { return false; } } _XSSQueryExtension = (_XScreenSaverQueryExtension_fn)PR_FindFunctionSymbol( sXssLib, "XScreenSaverQueryExtension"); _XSSQueryVersion = (_XScreenSaverQueryVersion_fn)PR_FindFunctionSymbol( sXssLib, "XScreenSaverQueryVersion"); _XSSSuspend = (_XScreenSaverSuspend_fn)PR_FindFunctionSymbol( sXssLib, "XScreenSaverSuspend"); if (!_XSSQueryExtension || !_XSSQueryVersion || !_XSSSuspend) { return false; } GdkDisplay* gDisplay = gdk_display_get_default(); if (!GdkIsX11Display(gDisplay)) { return false; } Display* display = GDK_DISPLAY_XDISPLAY(gDisplay); int throwaway; if (!_XSSQueryExtension(display, &throwaway, &throwaway)) return false; int major, minor; if (!_XSSQueryVersion(display, &major, &minor)) return false; // Needs to be compatible with version 1.1 if (major != 1) return false; if (minor < 1) return false; return true; } /* static */ bool WakeLockTopic::InhibitXScreenSaver(bool inhibit) { // Should only be called if CheckXScreenSaverSupport returns true. // There's a couple of safety checks here nonetheless. if (!_XSSSuspend) { return false; } GdkDisplay* gDisplay = gdk_display_get_default(); if (!GdkIsX11Display(gDisplay)) { return false; } Display* display = GDK_DISPLAY_XDISPLAY(gDisplay); _XSSSuspend(display, inhibit); return true; } # endif # if defined(MOZ_WAYLAND) /* static */ bool WakeLockTopic::CheckWaylandIdleInhibitSupport() { nsWaylandDisplay* waylandDisplay = WaylandDisplayGet(); return waylandDisplay && waylandDisplay->GetIdleInhibitManager() != nullptr; } bool WakeLockTopic::InhibitWaylandIdle() { nsWaylandDisplay* waylandDisplay = WaylandDisplayGet(); if (!waylandDisplay) { return false; } nsWindow* focusedWindow = nsWindow::GetFocusedWindow(); if (!focusedWindow) { return false; } UninhibitWaylandIdle(); MozContainerSurfaceLock lock(focusedWindow->GetMozContainer()); struct wl_surface* waylandSurface = lock.GetSurface(); if (waylandSurface) { mWaylandInhibitor = zwp_idle_inhibit_manager_v1_create_inhibitor( waylandDisplay->GetIdleInhibitManager(), waylandSurface); } return true; } bool WakeLockTopic::UninhibitWaylandIdle() { if (mWaylandInhibitor == nullptr) return false; zwp_idle_inhibitor_v1_destroy(mWaylandInhibitor); mWaylandInhibitor = nullptr; return true; } # endif bool WakeLockTopic::SendInhibit() { bool sendOk = false; switch (mDesktopEnvironment) { case FreeDesktopScreensaver: WAKE_LOCK_LOG("SendInhibit(): FreeDesktopScreensaver"); sendOk = SendFreeDesktopScreensaverInhibitMessage(); break; case FreeDesktopPower: WAKE_LOCK_LOG("SendInhibit(): FreeDesktopPower"); sendOk = SendFreeDesktopPowerInhibitMessage(); break; case GNOME: WAKE_LOCK_LOG("SendInhibit(): GNOME"); sendOk = SendGNOMEInhibitMessage(); break; # if defined(MOZ_X11) case XScreenSaver: WAKE_LOCK_LOG("SendInhibit(): InhibitXScreenSaver"); return InhibitXScreenSaver(true); # endif # if defined(MOZ_WAYLAND) case WaylandIdleInhibit: WAKE_LOCK_LOG("SendInhibit(): WaylandIdleInhibit"); return InhibitWaylandIdle(); # endif case Unsupported: return false; } if (sendOk) { mWaitingForReply = true; } return sendOk; } bool WakeLockTopic::SendUninhibit() { RefPtr message; if (mDesktopEnvironment == FreeDesktopScreensaver) { WAKE_LOCK_LOG("SendUninhibit(): FreeDesktopScreensaver"); message = already_AddRefed(dbus_message_new_method_call( FREEDESKTOP_SCREENSAVER_TARGET, FREEDESKTOP_SCREENSAVER_OBJECT, FREEDESKTOP_SCREENSAVER_INTERFACE, "UnInhibit")); } else if (mDesktopEnvironment == FreeDesktopPower) { WAKE_LOCK_LOG("SendUninhibit(): FreeDesktopPower"); message = already_AddRefed(dbus_message_new_method_call( FREEDESKTOP_POWER_TARGET, FREEDESKTOP_POWER_OBJECT, FREEDESKTOP_POWER_INTERFACE, "UnInhibit")); } else if (mDesktopEnvironment == GNOME) { WAKE_LOCK_LOG("SendUninhibit(): GNOME"); message = already_AddRefed(dbus_message_new_method_call( SESSION_MANAGER_TARGET, SESSION_MANAGER_OBJECT, SESSION_MANAGER_INTERFACE, "Uninhibit")); } # if defined(MOZ_X11) else if (mDesktopEnvironment == XScreenSaver) { WAKE_LOCK_LOG("SendUninhibit(): XScreenSaver"); return InhibitXScreenSaver(false); } # endif # if defined(MOZ_WAYLAND) else if (mDesktopEnvironment == WaylandIdleInhibit) { WAKE_LOCK_LOG("SendUninhibit(): Wayland"); return UninhibitWaylandIdle(); } # endif if (!message) { return false; } dbus_message_append_args(message, DBUS_TYPE_UINT32, &mInhibitRequest, DBUS_TYPE_INVALID); dbus_connection_send(mConnection, message, nullptr); dbus_connection_flush(mConnection); mInhibitRequest = 0; return true; } nsresult WakeLockTopic::InhibitScreensaver() { if (mShouldInhibit) { // Screensaver is inhibited. Nothing to do here. return NS_OK; } mShouldInhibit = true; if (mWaitingForReply) { // We already have a screensaver inhibit request pending. This can happen // if InhibitScreensaver is called, then UninhibitScreensaver, then // InhibitScreensaver again quickly. return NS_OK; } return SendInhibit() ? NS_OK : NS_ERROR_FAILURE; } nsresult WakeLockTopic::UninhibitScreensaver() { if (!mShouldInhibit) { // Screensaver isn't inhibited. Nothing to do here. return NS_OK; } mShouldInhibit = false; if (mWaitingForReply) { // If we're still waiting for a response to our inhibit request, we can't // do anything until we get a dbus message back. The callbacks below will // check |mShouldInhibit| and act accordingly. return NS_OK; } return SendUninhibit() ? NS_OK : NS_ERROR_FAILURE; } void WakeLockTopic::InhibitFailed() { mWaitingForReply = false; if (mDesktopEnvironment == FreeDesktopScreensaver) { mDesktopEnvironment = GNOME; } else if (mDesktopEnvironment == GNOME) { mDesktopEnvironment = FreeDesktopPower; # if defined(MOZ_X11) } else if (mDesktopEnvironment == FreeDesktopPower && CheckXScreenSaverSupport()) { mDesktopEnvironment = XScreenSaver; # endif # if defined(MOZ_WAYLAND) } else if (mDesktopEnvironment == FreeDesktopPower && CheckWaylandIdleInhibitSupport()) { mDesktopEnvironment = WaylandIdleInhibit; # endif } else { mDesktopEnvironment = Unsupported; mShouldInhibit = false; } if (!mShouldInhibit) { // We were interrupted by UninhibitScreensaver() before we could find the // correct desktop environment. return; } SendInhibit(); } void WakeLockTopic::InhibitSucceeded(uint32_t aInhibitRequest) { mWaitingForReply = false; mInhibitRequest = aInhibitRequest; if (!mShouldInhibit) { // We successfully inhibited the screensaver, but UninhibitScreensaver() // was called while we were waiting for a reply. SendUninhibit(); } } /* static */ void WakeLockTopic::ReceiveInhibitReply(DBusPendingCall* pending, void* user_data) { if (!WakeLockListener::GetSingleton(false)) { // The WakeLockListener (and therefore our topic) was deleted while we were // waiting for a reply. return; } WakeLockTopic* self = static_cast(user_data); RefPtr msg = already_AddRefed(dbus_pending_call_steal_reply(pending)); if (!msg) { return; } if (dbus_message_get_type(msg) == DBUS_MESSAGE_TYPE_METHOD_RETURN) { uint32_t inhibitRequest; if (dbus_message_get_args(msg, nullptr, DBUS_TYPE_UINT32, &inhibitRequest, DBUS_TYPE_INVALID)) { self->InhibitSucceeded(inhibitRequest); } } else { self->InhibitFailed(); } } WakeLockListener::WakeLockListener() : mConnection(nullptr) {} /* static */ WakeLockListener* WakeLockListener::GetSingleton(bool aCreate) { if (!sSingleton && aCreate) { sSingleton = new WakeLockListener(); } return sSingleton; } /* static */ void WakeLockListener::Shutdown() { sSingleton = nullptr; } bool WakeLockListener::EnsureDBusConnection() { if (!mConnection) { mConnection = already_AddRefed( dbus_bus_get(DBUS_BUS_SESSION, nullptr)); if (mConnection) { dbus_connection_set_exit_on_disconnect(mConnection, false); dbus_connection_setup_with_g_main(mConnection, nullptr); } } return mConnection != nullptr; } nsresult WakeLockListener::Callback(const nsAString& topic, const nsAString& state) { if (!EnsureDBusConnection()) { return NS_ERROR_FAILURE; } WAKE_LOCK_LOG("WakeLockListener %s state %s", NS_ConvertUTF16toUTF8(topic).get(), NS_ConvertUTF16toUTF8(state).get()); if (!topic.Equals(u"screen"_ns) && !topic.Equals(u"video-playing"_ns) && !topic.Equals(u"autoscroll"_ns)) return NS_OK; WakeLockTopic* const topicLock = mTopics.GetOrInsertNew(topic, topic, mConnection); // Treat "locked-background" the same as "unlocked" on desktop linux. bool shouldLock = state.EqualsLiteral("locked-foreground"); WAKE_LOCK_LOG("shouldLock %d", shouldLock); return shouldLock ? topicLock->InhibitScreensaver() : topicLock->UninhibitScreensaver(); } #endif