| Index: content/browser/power_save_blocker_x11.cc
|
| diff --git a/content/browser/power_save_blocker_x11.cc b/content/browser/power_save_blocker_x11.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..a177e392ce1466f2469fe0f76908cd1c09959d7d
|
| --- /dev/null
|
| +++ b/content/browser/power_save_blocker_x11.cc
|
| @@ -0,0 +1,508 @@
|
| +// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +#include <X11/Xlib.h>
|
| +#include <X11/extensions/dpms.h>
|
| +#include <X11/extensions/scrnsaver.h>
|
| +#include <stdint.h>
|
| +
|
| +#include <memory>
|
| +
|
| +#include "content/browser/power_save_blocker_impl.h"
|
| +// Xlib #defines Status, but we can't have that for some of our headers.
|
| +#ifdef Status
|
| +#undef Status
|
| +#endif
|
| +
|
| +#include "base/bind.h"
|
| +#include "base/callback.h"
|
| +#include "base/command_line.h"
|
| +#include "base/environment.h"
|
| +#include "base/files/file_path.h"
|
| +#include "base/location.h"
|
| +#include "base/logging.h"
|
| +#include "base/macros.h"
|
| +#include "base/memory/ref_counted.h"
|
| +#include "base/memory/singleton.h"
|
| +#include "base/nix/xdg_util.h"
|
| +#include "base/synchronization/lock.h"
|
| +#include "dbus/bus.h"
|
| +#include "dbus/message.h"
|
| +#include "dbus/object_path.h"
|
| +#include "dbus/object_proxy.h"
|
| +#include "ui/gfx/x/x11_types.h"
|
| +
|
| +namespace {
|
| +
|
| +enum DBusAPI {
|
| + NO_API, // Disable. No supported API available.
|
| + GNOME_API, // Use the GNOME API. (Supports more features.)
|
| + FREEDESKTOP_API, // Use the FreeDesktop API, for KDE4, KDE5, and XFCE.
|
| +};
|
| +
|
| +// Inhibit flags defined in the org.gnome.SessionManager interface.
|
| +// Can be OR'd together and passed as argument to the Inhibit() method
|
| +// to specify which power management features we want to suspend.
|
| +enum GnomeAPIInhibitFlags {
|
| + INHIBIT_LOGOUT = 1,
|
| + INHIBIT_SWITCH_USER = 2,
|
| + INHIBIT_SUSPEND_SESSION = 4,
|
| + INHIBIT_MARK_SESSION_IDLE = 8
|
| +};
|
| +
|
| +const char kGnomeAPIServiceName[] = "org.gnome.SessionManager";
|
| +const char kGnomeAPIInterfaceName[] = "org.gnome.SessionManager";
|
| +const char kGnomeAPIObjectPath[] = "/org/gnome/SessionManager";
|
| +
|
| +const char kFreeDesktopAPIPowerServiceName[] =
|
| + "org.freedesktop.PowerManagement";
|
| +const char kFreeDesktopAPIPowerInterfaceName[] =
|
| + "org.freedesktop.PowerManagement.Inhibit";
|
| +const char kFreeDesktopAPIPowerObjectPath[] =
|
| + "/org/freedesktop/PowerManagement/Inhibit";
|
| +
|
| +const char kFreeDesktopAPIScreenServiceName[] = "org.freedesktop.ScreenSaver";
|
| +const char kFreeDesktopAPIScreenInterfaceName[] = "org.freedesktop.ScreenSaver";
|
| +const char kFreeDesktopAPIScreenObjectPath[] = "/org/freedesktop/ScreenSaver";
|
| +
|
| +} // namespace
|
| +
|
| +namespace content {
|
| +
|
| +class PowerSaveBlockerImpl::Delegate
|
| + : public base::RefCountedThreadSafe<PowerSaveBlockerImpl::Delegate> {
|
| + public:
|
| + // Picks an appropriate D-Bus API to use based on the desktop environment.
|
| + Delegate(PowerSaveBlockerType type,
|
| + const std::string& description,
|
| + bool freedesktop_only,
|
| + scoped_refptr<base::SequencedTaskRunner> ui_task_runner,
|
| + scoped_refptr<base::SingleThreadTaskRunner> blocking_task_runner);
|
| +
|
| + // Post a task to initialize the delegate on the UI thread, which will itself
|
| + // then post a task to apply the power save block on the FILE thread.
|
| + void Init();
|
| +
|
| + // Post a task to remove the power save block on the FILE thread, unless it
|
| + // hasn't yet been applied, in which case we just prevent it from applying.
|
| + void CleanUp();
|
| +
|
| + private:
|
| + friend class base::RefCountedThreadSafe<Delegate>;
|
| + ~Delegate() {}
|
| +
|
| + // Selects an appropriate D-Bus API to use for this object. Must be called on
|
| + // the UI thread. Checks enqueue_apply_ once an API has been selected, and
|
| + // enqueues a call back to ApplyBlock() if it is true. See the comments for
|
| + // enqueue_apply_ below.
|
| + void InitOnUIThread();
|
| +
|
| + // Returns true if ApplyBlock() / RemoveBlock() should be called.
|
| + bool ShouldBlock() const;
|
| +
|
| + // Apply or remove the power save block, respectively. These methods should be
|
| + // called once each, on the same thread, per instance. They block waiting for
|
| + // the action to complete (with a timeout); the thread must thus allow I/O.
|
| + void ApplyBlock();
|
| + void RemoveBlock();
|
| +
|
| + // Asynchronous callback functions for ApplyBlock and RemoveBlock.
|
| + // Functions do not receive ownership of |response|.
|
| + void ApplyBlockFinished(dbus::Response* response);
|
| + void RemoveBlockFinished(dbus::Response* response);
|
| +
|
| + // Wrapper for XScreenSaverSuspend. Checks whether the X11 Screen Saver
|
| + // Extension is available first. If it isn't, this is a no-op.
|
| + // Must be called on the UI thread.
|
| + void XSSSuspendSet(bool suspend);
|
| +
|
| + // If DPMS (the power saving system in X11) is not enabled, then we don't want
|
| + // to try to disable power saving, since on some desktop environments that may
|
| + // enable DPMS with very poor default settings (e.g. turning off the display
|
| + // after only 1 second). Must be called on the UI thread.
|
| + bool DPMSEnabled();
|
| +
|
| + // If no other method is available (i.e. not running under a Desktop
|
| + // Environment) check whether the X11 Screen Saver Extension can be used
|
| + // to disable the screen saver. Must be called on the UI thread.
|
| + bool XSSAvailable();
|
| +
|
| + // Returns an appropriate D-Bus API to use based on the desktop environment.
|
| + // Must be called on the UI thread, as it may call DPMSEnabled() above.
|
| + DBusAPI SelectAPI();
|
| +
|
| + const PowerSaveBlockerType type_;
|
| + const std::string description_;
|
| + const bool freedesktop_only_;
|
| +
|
| + // Initially, we post a message to the UI thread to select an API. When it
|
| + // finishes, it will post a message to the FILE thread to perform the actual
|
| + // application of the block, unless enqueue_apply_ is false. We set it to
|
| + // false when we post that message, or when RemoveBlock() is called before
|
| + // ApplyBlock() has run. Both api_ and enqueue_apply_ are guarded by lock_.
|
| + DBusAPI api_;
|
| + bool enqueue_apply_;
|
| + base::Lock lock_;
|
| +
|
| + // Indicates that a D-Bus power save blocking request is in flight.
|
| + bool block_inflight_;
|
| + // Used to detect erronous redundant calls to RemoveBlock().
|
| + bool unblock_inflight_;
|
| + // Indicates that RemoveBlock() is called before ApplyBlock() has finished.
|
| + // If it's true, then the RemoveBlock() call will be processed immediately
|
| + // after ApplyBlock() has finished.
|
| + bool enqueue_unblock_;
|
| +
|
| + scoped_refptr<dbus::Bus> bus_;
|
| +
|
| + // The cookie that identifies our inhibit request,
|
| + // or 0 if there is no active inhibit request.
|
| + uint32_t inhibit_cookie_;
|
| +
|
| + scoped_refptr<base::SequencedTaskRunner> ui_task_runner_;
|
| + scoped_refptr<base::SingleThreadTaskRunner> blocking_task_runner_;
|
| +
|
| + DISALLOW_COPY_AND_ASSIGN(Delegate);
|
| +};
|
| +
|
| +PowerSaveBlockerImpl::Delegate::Delegate(
|
| + PowerSaveBlockerType type,
|
| + const std::string& description,
|
| + bool freedesktop_only,
|
| + scoped_refptr<base::SequencedTaskRunner> ui_task_runner,
|
| + scoped_refptr<base::SingleThreadTaskRunner> blocking_task_runner)
|
| + : type_(type),
|
| + description_(description),
|
| + freedesktop_only_(freedesktop_only),
|
| + api_(NO_API),
|
| + enqueue_apply_(false),
|
| + inhibit_cookie_(0),
|
| + ui_task_runner_(ui_task_runner),
|
| + blocking_task_runner_(blocking_task_runner) {
|
| + // We're on the client's thread here, so we don't allocate the dbus::Bus
|
| + // object yet. We'll do it later in ApplyBlock(), on the FILE thread.
|
| +}
|
| +
|
| +void PowerSaveBlockerImpl::Delegate::Init() {
|
| + base::AutoLock lock(lock_);
|
| + DCHECK(!enqueue_apply_);
|
| + enqueue_apply_ = true;
|
| + block_inflight_ = false;
|
| + unblock_inflight_ = false;
|
| + enqueue_unblock_ = false;
|
| + ui_task_runner_->PostTask(FROM_HERE,
|
| + base::Bind(&Delegate::InitOnUIThread, this));
|
| +}
|
| +
|
| +void PowerSaveBlockerImpl::Delegate::CleanUp() {
|
| + base::AutoLock lock(lock_);
|
| + if (enqueue_apply_) {
|
| + // If a call to ApplyBlock() has not yet been enqueued because we are still
|
| + // initializing on the UI thread, then just cancel it. We don't need to
|
| + // remove the block because we haven't even applied it yet.
|
| + enqueue_apply_ = false;
|
| + } else {
|
| + if (ShouldBlock()) {
|
| + blocking_task_runner_->PostTask(FROM_HERE,
|
| + base::Bind(&Delegate::RemoveBlock, this));
|
| + }
|
| +
|
| + ui_task_runner_->PostTask(
|
| + FROM_HERE, base::Bind(&Delegate::XSSSuspendSet, this, false));
|
| + }
|
| +}
|
| +
|
| +void PowerSaveBlockerImpl::Delegate::InitOnUIThread() {
|
| + DCHECK(ui_task_runner_->RunsTasksOnCurrentThread());
|
| + base::AutoLock lock(lock_);
|
| + api_ = SelectAPI();
|
| +
|
| + if (enqueue_apply_) {
|
| + if (ShouldBlock()) {
|
| + // The thread we use here becomes the origin and D-Bus thread for the
|
| + // D-Bus library, so we need to use the same thread above for
|
| + // RemoveBlock(). It must be a thread that allows I/O operations, so we
|
| + // use the FILE thread.
|
| + blocking_task_runner_->PostTask(FROM_HERE,
|
| + base::Bind(&Delegate::ApplyBlock, this));
|
| + }
|
| + XSSSuspendSet(true);
|
| + }
|
| + enqueue_apply_ = false;
|
| +}
|
| +
|
| +bool PowerSaveBlockerImpl::Delegate::ShouldBlock() const {
|
| + return freedesktop_only_ ? api_ == FREEDESKTOP_API : api_ != NO_API;
|
| +}
|
| +
|
| +void PowerSaveBlockerImpl::Delegate::ApplyBlock() {
|
| + DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
|
| + DCHECK(!bus_); // ApplyBlock() should only be called once.
|
| + DCHECK(!block_inflight_);
|
| +
|
| + dbus::Bus::Options options;
|
| + options.bus_type = dbus::Bus::SESSION;
|
| + options.connection_type = dbus::Bus::PRIVATE;
|
| + bus_ = new dbus::Bus(options);
|
| +
|
| + scoped_refptr<dbus::ObjectProxy> object_proxy;
|
| + std::unique_ptr<dbus::MethodCall> method_call;
|
| + std::unique_ptr<dbus::MessageWriter> message_writer;
|
| +
|
| + switch (api_) {
|
| + case NO_API:
|
| + NOTREACHED(); // We should never call this method with this value.
|
| + return;
|
| + case GNOME_API:
|
| + object_proxy = bus_->GetObjectProxy(
|
| + kGnomeAPIServiceName,
|
| + dbus::ObjectPath(kGnomeAPIObjectPath));
|
| + method_call.reset(
|
| + new dbus::MethodCall(kGnomeAPIInterfaceName, "Inhibit"));
|
| + message_writer.reset(new dbus::MessageWriter(method_call.get()));
|
| + // The arguments of the method are:
|
| + // app_id: The application identifier
|
| + // toplevel_xid: The toplevel X window identifier
|
| + // reason: The reason for the inhibit
|
| + // flags: Flags that spefify what should be inhibited
|
| + message_writer->AppendString(
|
| + base::CommandLine::ForCurrentProcess()->GetProgram().value());
|
| + message_writer->AppendUint32(0); // should be toplevel_xid
|
| + message_writer->AppendString(description_);
|
| + {
|
| + uint32_t flags = 0;
|
| + switch (type_) {
|
| + case kPowerSaveBlockPreventDisplaySleep:
|
| + flags |= INHIBIT_MARK_SESSION_IDLE;
|
| + flags |= INHIBIT_SUSPEND_SESSION;
|
| + break;
|
| + case kPowerSaveBlockPreventAppSuspension:
|
| + flags |= INHIBIT_SUSPEND_SESSION;
|
| + break;
|
| + }
|
| + message_writer->AppendUint32(flags);
|
| + }
|
| + break;
|
| + case FREEDESKTOP_API:
|
| + switch (type_) {
|
| + case kPowerSaveBlockPreventDisplaySleep:
|
| + object_proxy = bus_->GetObjectProxy(
|
| + kFreeDesktopAPIScreenServiceName,
|
| + dbus::ObjectPath(kFreeDesktopAPIScreenObjectPath));
|
| + method_call.reset(new dbus::MethodCall(
|
| + kFreeDesktopAPIScreenInterfaceName, "Inhibit"));
|
| + break;
|
| + case kPowerSaveBlockPreventAppSuspension:
|
| + object_proxy = bus_->GetObjectProxy(
|
| + kFreeDesktopAPIPowerServiceName,
|
| + dbus::ObjectPath(kFreeDesktopAPIPowerObjectPath));
|
| + method_call.reset(new dbus::MethodCall(
|
| + kFreeDesktopAPIPowerInterfaceName, "Inhibit"));
|
| + break;
|
| + }
|
| + message_writer.reset(new dbus::MessageWriter(method_call.get()));
|
| + // The arguments of the method are:
|
| + // app_id: The application identifier
|
| + // reason: The reason for the inhibit
|
| + message_writer->AppendString(
|
| + base::CommandLine::ForCurrentProcess()->GetProgram().value());
|
| + message_writer->AppendString(description_);
|
| + break;
|
| + }
|
| +
|
| + block_inflight_ = true;
|
| + object_proxy->CallMethod(
|
| + method_call.get(), dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
|
| + base::Bind(&PowerSaveBlockerImpl::Delegate::ApplyBlockFinished, this));
|
| +}
|
| +
|
| +void PowerSaveBlockerImpl::Delegate::ApplyBlockFinished(
|
| + dbus::Response* response) {
|
| + DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
|
| + DCHECK(bus_);
|
| + DCHECK(block_inflight_);
|
| + block_inflight_ = false;
|
| +
|
| + if (response) {
|
| + // The method returns an inhibit_cookie, used to uniquely identify
|
| + // this request. It should be used as an argument to Uninhibit()
|
| + // in order to remove the request.
|
| + dbus::MessageReader message_reader(response);
|
| + if (!message_reader.PopUint32(&inhibit_cookie_))
|
| + LOG(ERROR) << "Invalid Inhibit() response: " << response->ToString();
|
| + } else {
|
| + LOG(ERROR) << "No response to Inhibit() request!";
|
| + }
|
| +
|
| + if (enqueue_unblock_) {
|
| + enqueue_unblock_ = false;
|
| + // RemoveBlock() was called while the Inhibit operation was in flight,
|
| + // so go ahead and remove the block now.
|
| + blocking_task_runner_->PostTask(FROM_HERE,
|
| + base::Bind(&Delegate::RemoveBlock, this));
|
| + }
|
| +}
|
| +
|
| +void PowerSaveBlockerImpl::Delegate::RemoveBlock() {
|
| + DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
|
| + DCHECK(bus_); // RemoveBlock() should only be called once.
|
| + DCHECK(!unblock_inflight_);
|
| +
|
| + if (block_inflight_) {
|
| + DCHECK(!enqueue_unblock_);
|
| + // Can't call RemoveBlock until ApplyBlock's async operation has
|
| + // finished. Enqueue it for execution once ApplyBlock is done.
|
| + enqueue_unblock_ = true;
|
| + return;
|
| + }
|
| +
|
| + scoped_refptr<dbus::ObjectProxy> object_proxy;
|
| + std::unique_ptr<dbus::MethodCall> method_call;
|
| +
|
| + switch (api_) {
|
| + case NO_API:
|
| + NOTREACHED(); // We should never call this method with this value.
|
| + return;
|
| + case GNOME_API:
|
| + object_proxy = bus_->GetObjectProxy(
|
| + kGnomeAPIServiceName,
|
| + dbus::ObjectPath(kGnomeAPIObjectPath));
|
| + method_call.reset(
|
| + new dbus::MethodCall(kGnomeAPIInterfaceName, "Uninhibit"));
|
| + break;
|
| + case FREEDESKTOP_API:
|
| + switch (type_) {
|
| + case kPowerSaveBlockPreventDisplaySleep:
|
| + object_proxy = bus_->GetObjectProxy(
|
| + kFreeDesktopAPIScreenServiceName,
|
| + dbus::ObjectPath(kFreeDesktopAPIScreenObjectPath));
|
| + method_call.reset(new dbus::MethodCall(
|
| + kFreeDesktopAPIScreenInterfaceName, "UnInhibit"));
|
| + break;
|
| + case kPowerSaveBlockPreventAppSuspension:
|
| + object_proxy = bus_->GetObjectProxy(
|
| + kFreeDesktopAPIPowerServiceName,
|
| + dbus::ObjectPath(kFreeDesktopAPIPowerObjectPath));
|
| + method_call.reset(new dbus::MethodCall(
|
| + kFreeDesktopAPIPowerInterfaceName, "UnInhibit"));
|
| + break;
|
| + }
|
| + break;
|
| + }
|
| +
|
| + dbus::MessageWriter message_writer(method_call.get());
|
| + message_writer.AppendUint32(inhibit_cookie_);
|
| + unblock_inflight_ = true;
|
| + object_proxy->CallMethod(
|
| + method_call.get(), dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
|
| + base::Bind(&PowerSaveBlockerImpl::Delegate::RemoveBlockFinished, this));
|
| +}
|
| +
|
| +void PowerSaveBlockerImpl::Delegate::RemoveBlockFinished(
|
| + dbus::Response* response) {
|
| + DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
|
| + DCHECK(bus_);
|
| + unblock_inflight_ = false;
|
| +
|
| + if (!response)
|
| + LOG(ERROR) << "No response to Uninhibit() request!";
|
| + // We don't care about checking the result. We assume it works; we can't
|
| + // really do anything about it anyway if it fails.
|
| + inhibit_cookie_ = 0;
|
| +
|
| + bus_->ShutdownAndBlock();
|
| + bus_ = nullptr;
|
| +}
|
| +
|
| +void PowerSaveBlockerImpl::Delegate::XSSSuspendSet(bool suspend) {
|
| + DCHECK(ui_task_runner_->RunsTasksOnCurrentThread());
|
| +
|
| + if (!XSSAvailable())
|
| + return;
|
| +
|
| + XDisplay* display = gfx::GetXDisplay();
|
| + XScreenSaverSuspend(display, suspend);
|
| +}
|
| +
|
| +bool PowerSaveBlockerImpl::Delegate::DPMSEnabled() {
|
| + DCHECK(ui_task_runner_->RunsTasksOnCurrentThread());
|
| + XDisplay* display = gfx::GetXDisplay();
|
| + BOOL enabled = false;
|
| + int dummy;
|
| + if (DPMSQueryExtension(display, &dummy, &dummy) && DPMSCapable(display)) {
|
| + CARD16 state;
|
| + DPMSInfo(display, &state, &enabled);
|
| + }
|
| + return enabled;
|
| +}
|
| +
|
| +bool PowerSaveBlockerImpl::Delegate::XSSAvailable() {
|
| + DCHECK(ui_task_runner_->RunsTasksOnCurrentThread());
|
| + XDisplay* display = gfx::GetXDisplay();
|
| + int dummy;
|
| + int major;
|
| + int minor;
|
| +
|
| + if (!XScreenSaverQueryExtension(display, &dummy, &dummy))
|
| + return false;
|
| +
|
| + if (!XScreenSaverQueryVersion(display, &major, &minor))
|
| + return false;
|
| +
|
| + return major > 1 || (major == 1 && minor >= 1);
|
| +}
|
| +
|
| +DBusAPI PowerSaveBlockerImpl::Delegate::SelectAPI() {
|
| + DCHECK(ui_task_runner_->RunsTasksOnCurrentThread());
|
| + std::unique_ptr<base::Environment> env(base::Environment::Create());
|
| + switch (base::nix::GetDesktopEnvironment(env.get())) {
|
| + case base::nix::DESKTOP_ENVIRONMENT_GNOME:
|
| + case base::nix::DESKTOP_ENVIRONMENT_UNITY:
|
| + if (DPMSEnabled())
|
| + return GNOME_API;
|
| + break;
|
| + case base::nix::DESKTOP_ENVIRONMENT_XFCE:
|
| + case base::nix::DESKTOP_ENVIRONMENT_KDE4:
|
| + case base::nix::DESKTOP_ENVIRONMENT_KDE5:
|
| + if (DPMSEnabled())
|
| + return FREEDESKTOP_API;
|
| + break;
|
| + case base::nix::DESKTOP_ENVIRONMENT_KDE3:
|
| + case base::nix::DESKTOP_ENVIRONMENT_OTHER:
|
| + // Not supported.
|
| + break;
|
| + }
|
| + return NO_API;
|
| +}
|
| +
|
| +PowerSaveBlockerImpl::PowerSaveBlockerImpl(
|
| + PowerSaveBlockerType type,
|
| + Reason reason,
|
| + const std::string& description,
|
| + scoped_refptr<base::SequencedTaskRunner> ui_task_runner,
|
| + scoped_refptr<base::SingleThreadTaskRunner> blocking_task_runner)
|
| + : delegate_(new Delegate(type,
|
| + description,
|
| + false /* freedesktop_only */,
|
| + ui_task_runner,
|
| + blocking_task_runner)),
|
| + ui_task_runner_(ui_task_runner),
|
| + blocking_task_runner_(blocking_task_runner) {
|
| + delegate_->Init();
|
| +
|
| + if (type == kPowerSaveBlockPreventDisplaySleep) {
|
| + freedesktop_suspend_delegate_ = new Delegate(
|
| + kPowerSaveBlockPreventAppSuspension, description,
|
| + true /* freedesktop_only */, ui_task_runner, blocking_task_runner);
|
| + freedesktop_suspend_delegate_->Init();
|
| + }
|
| +}
|
| +
|
| +PowerSaveBlockerImpl::~PowerSaveBlockerImpl() {
|
| + delegate_->CleanUp();
|
| + if (freedesktop_suspend_delegate_)
|
| + freedesktop_suspend_delegate_->CleanUp();
|
| +}
|
| +
|
| +} // namespace content
|
|
|