Bug 811261 - Implement WakeLockListener on Linux to disable screensaver while video is playing. r=karl

This commit is contained in:
Martin Stransky 2014-06-18 06:11:00 +02:00
parent f84c9426f1
commit 942a1efb0f
6 changed files with 442 additions and 0 deletions

View File

@ -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

View File

@ -0,0 +1,372 @@
/* -*- 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 <dbus/dbus.h>
#include <dbus/dbus-glib-lowlevel.h>
#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()
{
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 == 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<WakeLockTopic*>(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;
}
if(!topic.Equals(NS_LITERAL_STRING("screen")))
return NS_OK;
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

View File

@ -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 <unistd.h>
#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<nsStringHashKey, WakeLockTopic> mTopics;
};
#endif // __WakeLockListener_h__

View File

@ -32,6 +32,7 @@ UNIFIED_SOURCES += [
'nsScreenManagerGtk.cpp',
'nsSound.cpp',
'nsToolkit.cpp',
'WakeLockListener.cpp',
'WidgetTraceEvent.cpp',
]

View File

@ -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<nsIPowerManagerService> 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);

View File

@ -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 = {