From 8fcc868a6474e5baa0cebb444ab9df5f61f7db81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Str=C3=A1nsk=C3=BD=20ext=3A=28=252C=2520Edwin=252?= =?UTF-8?q?0Flores=2520=253Cedwin=2540mozilla=2Ecom=253E=29?= Date: Wed, 14 May 2014 17:34:49 +1200 Subject: [PATCH] Bug 811261 - Implement WakeLockListener on Linux to disable screensaver while video is playing r=karl --- widget/gtk/Makefile.in | 3 + widget/gtk/WakeLockListener.cpp | 365 ++++++++++++++++++++++++++++++++ widget/gtk/WakeLockListener.h | 47 ++++ widget/gtk/moz.build | 1 + widget/gtk/nsAppShell.cpp | 15 ++ widget/gtk/nsWidgetFactory.cpp | 4 + 6 files changed, 435 insertions(+) create mode 100644 widget/gtk/WakeLockListener.cpp create mode 100644 widget/gtk/WakeLockListener.h diff --git a/widget/gtk/Makefile.in b/widget/gtk/Makefile.in index 740da54bcd61..d651bfefe8bf 100644 --- a/widget/gtk/Makefile.in +++ b/widget/gtk/Makefile.in @@ -11,3 +11,6 @@ CXXFLAGS += $(MOZ_CAIRO_CFLAGS) $(MOZ_STARTUP_NOTIFICATION_CFLAGS) CFLAGS += $(TK_CFLAGS) CXXFLAGS += $(TK_CFLAGS) +ifdef MOZ_ENABLE_DBUS +CXXFLAGS += $(MOZ_DBUS_GLIB_CFLAGS) +endif diff --git a/widget/gtk/WakeLockListener.cpp b/widget/gtk/WakeLockListener.cpp new file mode 100644 index 000000000000..1254df041e96 --- /dev/null +++ b/widget/gtk/WakeLockListener.cpp @@ -0,0 +1,365 @@ +/* -*- 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/. */ + +#include +#include + +#include "WakeLockListener.h" + +#ifdef MOZ_ENABLE_DBUS + +#define FREEDESKTOP_SCREENSAVER_TARGET "org.freedesktop.ScreenSaver" +#define FREEDESKTOP_SCREENSAVER_OBJECT "/ScreenSaver" +#define FREEDESKTOP_SCREENSAVER_INTERFACE "org.freedesktop.ScreenSaver" + +#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; + +NS_IMPL_ISUPPORTS(WakeLockListener, nsIDOMMozWakeLockListener) + +WakeLockListener* WakeLockListener::sSingleton = nullptr; + + +enum DesktopEnvironment { + FreeDesktop, + GNOME, + Unsupported, +}; + +class WakeLockTopic +{ +public: + WakeLockTopic(const nsAString& aTopic, DBusConnection* aConnection) + : mTopic(NS_ConvertUTF16toUTF8(aTopic)) + , mConnection(aConnection) + , mDesktopEnvironment(FreeDesktop) + , mInhibitRequest(0) + , mShouldInhibit(false) + , mWaitingForReply(false) + { + } + + nsresult InhibitScreensaver(void); + nsresult UninhibitScreensaver(void); + +private: + bool SendInhibit(); + bool SendUninhibit(); + + bool SendFreeDesktopInhibitMessage(); + bool SendGNOMEInhibitMessage(); + bool SendMessage(DBusMessage* aMessage); + + static void ReceiveInhibitReply(DBusPendingCall* aPending, void* aUserData); + void InhibitFailed(); + void InhibitSucceeded(uint32_t aInhibitRequest); + + nsAutoCString mTopic; + DBusConnection* mConnection; + + DesktopEnvironment mDesktopEnvironment; + + uint32_t mInhibitRequest; + + bool mShouldInhibit; + bool mWaitingForReply; +}; + + +bool +WakeLockTopic::SendMessage(DBusMessage* aMessage) +{ + // send message and get a handle for a reply + DBusPendingCall* reply; + dbus_connection_send_with_reply(mConnection, aMessage, &reply, + DBUS_TIMEOUT); + dbus_message_unref(aMessage); + + if (!reply) { + return false; + } + + dbus_pending_call_set_notify(reply, &ReceiveInhibitReply, this, NULL); + dbus_pending_call_unref(reply); + + return true; +} + +bool +WakeLockTopic::SendFreeDesktopInhibitMessage() +{ + DBusMessage* message = + 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() +{ + DBusMessage* message = + 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); +} + + +bool +WakeLockTopic::SendInhibit() +{ + bool sendOk = false; + + switch (mDesktopEnvironment) + { + case FreeDesktop: + sendOk = SendFreeDesktopInhibitMessage(); + break; + case GNOME: + sendOk = SendGNOMEInhibitMessage(); + break; + case Unsupported: + return false; + } + + if (sendOk) { + mWaitingForReply = true; + } + + return sendOk; +} + +bool +WakeLockTopic::SendUninhibit() +{ + DBusMessage* message = nullptr; + + if (mDesktopEnvironment == FreeDesktop) { + message = + dbus_message_new_method_call(FREEDESKTOP_SCREENSAVER_TARGET, + FREEDESKTOP_SCREENSAVER_OBJECT, + FREEDESKTOP_SCREENSAVER_INTERFACE, + "UnInhibit"); + } else if (mDesktopEnvironment == GNOME) { + message = + dbus_message_new_method_call(SESSION_MANAGER_TARGET, + SESSION_MANAGER_OBJECT, + SESSION_MANAGER_INTERFACE, + "Uninhibit"); + } + + 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); + dbus_message_unref(message); + + mInhibitRequest = 0; + + return true; +} + +nsresult +WakeLockTopic::InhibitScreensaver() +{ + NS_ASSERTION(!mShouldInhibit, "Screensaver is already inhibited"); + 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 == FreeDesktop) { + mDesktopEnvironment = GNOME; + } else { + NS_ASSERTION(mDesktopEnvironment == GNOME, "Unknown desktop environment"); + 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); + + DBusMessage* msg = 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(); + } + + dbus_message_unref(msg); +} + + +WakeLockListener::WakeLockListener() + : mConnection(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); + } +} + +WakeLockListener::~WakeLockListener() +{ + if (mConnection) { + dbus_connection_unref(mConnection); + } +} + +/* static */ WakeLockListener* +WakeLockListener::GetSingleton(bool aCreate) +{ + if (!sSingleton && aCreate) { + sSingleton = new WakeLockListener(); + sSingleton->AddRef(); + } + + return sSingleton; +} + +/* static */ void +WakeLockListener::Shutdown() +{ + sSingleton->Release(); + sSingleton = nullptr; +} + +nsresult +WakeLockListener::Callback(const nsAString& topic, const nsAString& state) +{ + if (!mConnection) { + return NS_ERROR_FAILURE; + } + + WakeLockTopic* topicLock = mTopics.Get(topic); + if (!topicLock) { + topicLock = new WakeLockTopic(topic, mConnection); + mTopics.Put(topic, topicLock); + } + + // Treat "locked-background" the same as "unlocked" on desktop linux. + bool shouldLock = state.EqualsLiteral("locked-foreground"); + + return shouldLock ? + topicLock->InhibitScreensaver() : + topicLock->UninhibitScreensaver(); +} + +#endif diff --git a/widget/gtk/WakeLockListener.h b/widget/gtk/WakeLockListener.h new file mode 100644 index 000000000000..b4c5a09de700 --- /dev/null +++ b/widget/gtk/WakeLockListener.h @@ -0,0 +1,47 @@ +/* -*- 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/. */ + +#include + +#ifndef __WakeLockListener_h__ +#define __WakeLockListener_h__ + +#include "nsHashKeys.h" +#include "nsClassHashtable.h" + +#include "nsIDOMWakeLockListener.h" + +struct DBusConnection; +class WakeLockTopic; + +/** + * Receives WakeLock events and simply passes it on to the right WakeLockTopic + * to inhibit the screensaver. + */ +class WakeLockListener MOZ_FINAL : public nsIDOMMozWakeLockListener +{ +public: + NS_DECL_ISUPPORTS; + + static WakeLockListener* GetSingleton(bool aCreate = true); + static void Shutdown(); + + nsresult Callback(const nsAString& topic, const nsAString& state); + +private: + WakeLockListener(); + ~WakeLockListener(); + + static WakeLockListener* sSingleton; + + DBusConnection* mConnection; + // Map of topic names to |WakeLockTopic|s. + // We assume a small, finite-sized set of topics. + nsClassHashtable mTopics; +}; + +#endif // __WakeLockListener_h__ diff --git a/widget/gtk/moz.build b/widget/gtk/moz.build index 6ea91d0279ab..8e56f368e0ea 100644 --- a/widget/gtk/moz.build +++ b/widget/gtk/moz.build @@ -29,6 +29,7 @@ UNIFIED_SOURCES += [ 'nsScreenManagerGtk.cpp', 'nsSound.cpp', 'nsToolkit.cpp', + 'WakeLockListener.cpp', 'WidgetTraceEvent.cpp', ] diff --git a/widget/gtk/nsAppShell.cpp b/widget/gtk/nsAppShell.cpp index f8eb98ae046e..cf1359fd0f01 100644 --- a/widget/gtk/nsAppShell.cpp +++ b/widget/gtk/nsAppShell.cpp @@ -17,6 +17,10 @@ #include "mozilla/HangMonitor.h" #include "mozilla/unused.h" #include "GeckoProfiler.h" +#include "nsIPowerManagerService.h" +#ifdef MOZ_ENABLE_DBUS +#include "WakeLockListener.h" +#endif using mozilla::unused; @@ -82,6 +86,17 @@ nsAppShell::Init() gWidgetDrawLog = PR_NewLogModule("WidgetDraw"); #endif +#ifdef MOZ_ENABLE_DBUS + nsCOMPtr powerManagerService = + do_GetService(POWERMANAGERSERVICE_CONTRACTID); + + if (powerManagerService) { + powerManagerService->AddWakeLockListener(WakeLockListener::GetSingleton()); + } else { + NS_WARNING("Failed to retrieve PowerManagerService, wakelocks will be broken!"); + } +#endif + if (!sPollFunc) { sPollFunc = g_main_context_get_poll_func(nullptr); g_main_context_set_poll_func(nullptr, &PollWrapper); diff --git a/widget/gtk/nsWidgetFactory.cpp b/widget/gtk/nsWidgetFactory.cpp index 5ab99d3708b3..266262877840 100644 --- a/widget/gtk/nsWidgetFactory.cpp +++ b/widget/gtk/nsWidgetFactory.cpp @@ -26,6 +26,7 @@ #include "nsBidiKeyboard.h" #include "nsScreenManagerGtk.h" #include "nsGTKToolkit.h" +#include "WakeLockListener.h" #ifdef NS_PRINTING #include "nsPrintOptionsGTK.h" @@ -280,6 +281,9 @@ nsWidgetGtk2ModuleDtor() nsWindow::ReleaseGlobals(); nsGTKToolkit::Shutdown(); nsAppShellShutdown(); +#ifdef MOZ_ENABLE_DBUS + WakeLockListener::Shutdown(); +#endif } static const mozilla::Module kWidgetModule = {