diff options
author | cristian.patrasciuc@gmail.com <cristian.patrasciuc@gmail.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-11-29 00:29:28 +0000 |
---|---|---|
committer | cristian.patrasciuc@gmail.com <cristian.patrasciuc@gmail.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-11-29 00:29:28 +0000 |
commit | 64eea4fdcba959370ccb3e707839c4881740018a (patch) | |
tree | c9137d9a20d54f6a44e27b1dc27f6f17ac648061 /content/browser/power_save_blocker_linux.cc | |
parent | c2db5bf058b6e7da8c1a747e722698c3d1de6608 (diff) | |
download | chromium_src-64eea4fdcba959370ccb3e707839c4881740018a.zip chromium_src-64eea4fdcba959370ccb3e707839c4881740018a.tar.gz chromium_src-64eea4fdcba959370ccb3e707839c4881740018a.tar.bz2 |
Implements the PowerSaveBlocker::ApplyBlock method for Linux systems. It uses
D-Bus power managements interfaces (org.freedesktop.PowerManagement fro
KDE4/XFCE and org.gnome.SessionManager for Gnome).
The implementation consists of a Singleton class, DBusPowerSaveBlocker that
handles the D-Bus connection and IO thread. The specific D-Bus interfaces,
method calls and message processing is done in a DBusPowerSaveBlocker::Delegate.
There are two different implementations for this delegate, one for KDE4/XFCE and
one for Gnome.
If there is an unsupported desktop environment, no D-Bus object is instantiated,
and the ApplyBlock method won't do anything.
BUG=33605
TEST=manual
Review URL: http://codereview.chromium.org/8479005
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@111822 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'content/browser/power_save_blocker_linux.cc')
-rw-r--r-- | content/browser/power_save_blocker_linux.cc | 460 |
1 files changed, 460 insertions, 0 deletions
diff --git a/content/browser/power_save_blocker_linux.cc b/content/browser/power_save_blocker_linux.cc new file mode 100644 index 0000000..f408e59 --- /dev/null +++ b/content/browser/power_save_blocker_linux.cc @@ -0,0 +1,460 @@ +// Copyright (c) 2011 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 "content/browser/power_save_blocker.h" + +#include "base/basictypes.h" +#include "base/bind.h" +#include "base/callback.h" +#include "base/environment.h" +#include "base/logging.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/singleton.h" +#include "base/nix/xdg_util.h" +#include "base/threading/platform_thread.h" +#include "content/public/browser/browser_thread.h" +#include "dbus/bus.h" +#include "dbus/message.h" +#include "dbus/object_proxy.h" + +using content::BrowserThread; + +namespace { + +// This class is used to inhibit Power Management on Linux systems +// using D-Bus interfaces. Mainly, there are two interfaces that +// make this possible. +// org.freedesktop.PowerManagement[.Inhibit] is considered to be +// the desktop-agnostic solution. However, it is only used by +// KDE4 and XFCE. +// org.gnome.SessionManager is the Power Management interface +// available on Gnome desktops. +// Given that there is no generic solution to this problem, +// this class delegates the task of calling specific D-Bus APIs, +// to a DBusPowerSaveBlock::Delegate object. +// This class is a Singleton and the delegate will be instantiated +// internally, when the singleton instance is created, based on +// the desktop environment in which the application is running. +// When the class is instantiated, if it runs under a supported +// desktop environment it creates the Bus object and the +// delegate. Otherwise, no object is created and the ApplyBlock +// method will not do anything. +class DBusPowerSaveBlocker { + public: + // String passed to D-Bus APIs as the reason for which + // the power management features are temporarily disabled. + static const char kPowerSaveReason[]; + + // This delegate interface represents a concrete + // implementation for a specific D-Bus interface. + // It is responsible for obtaining specific object proxies, + // making D-Bus method calls and handling D-Bus responses. + // When a new DBusPowerBlocker is created, only a specific + // implementation of the delegate is instantiated. See the + // DBusPowerSaveBlocker constructor for more details. + // This is ref_counted to make sure that the callbacks + // stay alive even after the DBusPowerSaveBlocker object + // is deleted. + class Delegate : public base::RefCountedThreadSafe<Delegate> { + public: + Delegate() {} + virtual ~Delegate() {} + virtual void ApplyBlock(PowerSaveBlocker::PowerSaveBlockerType type) = 0; + private: + DISALLOW_COPY_AND_ASSIGN(Delegate); + }; + + // Returns a pointer to the sole instance of this class + static DBusPowerSaveBlocker* GetInstance(); + + // Forwards a power save block request to the concrete implementation + // of the Delegate interface. + // If |delegate_| is NULL, the application runs under an unsupported + // desktop environment. In this case, the method doesn't do anything. + void ApplyBlock(PowerSaveBlocker::PowerSaveBlockerType type) { + if (delegate_ != NULL) delegate_->ApplyBlock(type); + } + + // Getter for the Bus object. Used by the Delegates to obtain object proxies. + scoped_refptr<dbus::Bus> bus() const { return bus_; } + + private: + DBusPowerSaveBlocker(); + virtual ~DBusPowerSaveBlocker(); + + // The D-Bus connection. + scoped_refptr<dbus::Bus> bus_; + + // Concrete implementation of the Delegate interface. + scoped_refptr<Delegate> delegate_; + + friend struct DefaultSingletonTraits<DBusPowerSaveBlocker>; + + DISALLOW_COPY_AND_ASSIGN(DBusPowerSaveBlocker); +}; + +// Delegate implementation for KDE4. +// It uses the org.freedesktop.PowerManagement interface. +// Works on XFCE4, too. +class KDEPowerSaveBlocker: public DBusPowerSaveBlocker::Delegate { + public: + KDEPowerSaveBlocker() + : inhibit_cookie_(0), + pending_inhibit_call_(false), + postponed_uninhibit_call_(false) {} + ~KDEPowerSaveBlocker() {} + + virtual void ApplyBlock(PowerSaveBlocker::PowerSaveBlockerType type) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + DCHECK(pending_inhibit_call_ || !postponed_uninhibit_call_); + + // If we have a pending inhibit call, we add a postponed uninhibit + // request, such that it will be canceled as soon as the response arrives. + // If we have an active inhibit request and receive a new one, + // we ignore it since the 'freedesktop' interface has only one Inhibit level + // and we cannot differentiate between SystemSleep and DisplaySleep, + // so there's no need to make additional D-Bus method calls. + if (type == PowerSaveBlocker::kPowerSaveBlockPreventNone) { + if (pending_inhibit_call_ && postponed_uninhibit_call_) { + return; + } else if (pending_inhibit_call_ && !postponed_uninhibit_call_) { + postponed_uninhibit_call_ = true; + return; + } else if (!pending_inhibit_call_ && inhibit_cookie_ == 0) { + return; + } + } else if ((pending_inhibit_call_ && !postponed_uninhibit_call_) || + inhibit_cookie_ > 0) { + return; + } + + scoped_refptr<dbus::ObjectProxy> object_proxy = + DBusPowerSaveBlocker::GetInstance()->bus()->GetObjectProxy( + "org.freedesktop.PowerManagement", + "/org/freedesktop/PowerManagement/Inhibit"); + dbus::MethodCall method_call("org.freedesktop.PowerManagement.Inhibit", + "Inhibit"); + dbus::MessageWriter message_writer(&method_call); + base::Callback<void(dbus::Response*)> bus_callback; + + switch (type) { + case PowerSaveBlocker::kPowerSaveBlockPreventDisplaySleep: + case PowerSaveBlocker::kPowerSaveBlockPreventSystemSleep: + // The org.freedesktop.PowerManagement.Inhibit interface offers only one + // Inhibit() method, that temporarily disables all power management + // features. We cannot differentiate and disable individual features, + // like display sleep or system sleep. + // The first argument of the Inhibit method is the application name. + // The second argument of the Inhibit method is a string containing + // the reason of the power save block request. + // The method returns a cookie (an int), which we must pass back to the + // UnInhibit method when we cancel our request. + message_writer.AppendString(base::PlatformThread::GetName()); + message_writer.AppendString(DBusPowerSaveBlocker::kPowerSaveReason); + bus_callback = base::Bind(&KDEPowerSaveBlocker::OnInhibitResponse, + this); + pending_inhibit_call_ = true; + break; + case PowerSaveBlocker::kPowerSaveBlockPreventNone: + // To cancel our inhibit request, we have to call a different method. + // It takes one argument, the cookie returned by the corresponding + // Inhibit method call. + method_call.SetMember("UnInhibit"); + message_writer.AppendUint32(inhibit_cookie_); + bus_callback = base::Bind(&KDEPowerSaveBlocker::OnUnInhibitResponse, + this); + break; + case PowerSaveBlocker::kPowerSaveBlockPreventStateCount: + // This is an invalid argument + NOTREACHED(); + break; + } + + object_proxy->CallMethod(&method_call, + dbus::ObjectProxy::TIMEOUT_USE_DEFAULT, + bus_callback); + } + + private: + // Inhibit() response callback. + // Stores the cookie so we can use it later when calling UnInhibit(). + // If the response from D-Bus is successful and there is a postponed + // uninhibit request, we cancel the cookie that we just received. + void OnInhibitResponse(dbus::Response* response) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + DCHECK(pending_inhibit_call_); + pending_inhibit_call_ = false; + if (response != NULL) { + dbus::MessageReader message_reader(response); + if (message_reader.PopUint32(&inhibit_cookie_)) { + if (postponed_uninhibit_call_) { + postponed_uninhibit_call_ = false; + ApplyBlock(PowerSaveBlocker::kPowerSaveBlockPreventNone); + } + return; + } else { + LOG(ERROR) << "Invalid Inhibit() response: " << response->ToString(); + } + } + inhibit_cookie_ = 0; + postponed_uninhibit_call_ = false; + } + + // UnInhibit() method callback. + // We set the |inhibit_cookie_| to 0 even if the D-Bus call failed. + void OnUnInhibitResponse(dbus::Response* response) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + inhibit_cookie_ = 0; + }; + + // The cookie that identifies our last inhibit request, + // or 0 if there is no active inhibit request. + uint32 inhibit_cookie_; + + // True if we made an inhibit call for which + // we did not receive a response yet + bool pending_inhibit_call_; + + // True if we have to cancel the cookie we are about to receive + bool postponed_uninhibit_call_; + + DISALLOW_COPY_AND_ASSIGN(KDEPowerSaveBlocker); +}; + +// Delegate implementation for Gnome, based on org.gnome.SessionManager +class GnomePowerSaveBlocker: public DBusPowerSaveBlocker::Delegate { + public: + // 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 InhibitFlags { + kInhibitLogOut = 1, + kInhibitSwitchUser = 2, + kInhibitSuspendSession = 4, + kInhibitMarkSessionAsIdle = 8 + }; + + GnomePowerSaveBlocker() + : inhibit_cookie_(0), + pending_inhibit_calls_(0), + postponed_uninhibit_calls_(0) {} + ~GnomePowerSaveBlocker() {} + + virtual void ApplyBlock(PowerSaveBlocker::PowerSaveBlockerType type) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + DCHECK(postponed_uninhibit_calls_ <= pending_inhibit_calls_); + + // If we have a pending inhibit call, we add a postponed uninhibit + // request, such that it will be canceled as soon as the response arrives. + // We want to cancel the current inhibit request whether |type| is + // kPowerSaveBlockPreventNone or not. If |type| represents an inhibit + // request, we are dealing with the same case as below, just that the + // reply to the previous inhibit request did not arrive yet, so we have + // to wait for the cookie in order to cancel it. + // Meanwhile, we can still make the new request. + // We also have to check that + // postponed_uninhibit_calls_ < pending_inhibit_calls_. + // If this is not the case, then all the pending requests were already + // canceled and we should not increment the number of postponed uninhibit + // requests; otherwise we will cancel unwanted future inhibits, + // that will be made after this call. + // NOTE: The implementation is based on the fact that we receive + // the D-Bus replies in the same order in which the requests are made. + if (pending_inhibit_calls_ > 0 && + postponed_uninhibit_calls_ < pending_inhibit_calls_) { + ++postponed_uninhibit_calls_; + // If the call was an Uninhibit, then we are done for the moment. + if (type == PowerSaveBlocker::kPowerSaveBlockPreventNone) return; + } + + // If we have an active inhibit request and no pending inhibit calls, + // we make an uninhibit request to cancel it now. + if (type != PowerSaveBlocker::kPowerSaveBlockPreventNone && + pending_inhibit_calls_ == 0 && + inhibit_cookie_ > 0) { + ApplyBlock(PowerSaveBlocker::kPowerSaveBlockPreventNone); + } + + scoped_refptr<dbus::ObjectProxy> object_proxy = + DBusPowerSaveBlocker::GetInstance()->bus()->GetObjectProxy( + "org.gnome.SessionManager", + "/org/gnome/SessionManager"); + dbus::MethodCall method_call("org.gnome.SessionManager", "Inhibit"); + dbus::MessageWriter message_writer(&method_call); + base::Callback<void(dbus::Response*)> bus_callback; + + unsigned int flags = 0; + switch (type) { + case PowerSaveBlocker::kPowerSaveBlockPreventDisplaySleep: + flags |= kInhibitMarkSessionAsIdle; + break; + case PowerSaveBlocker::kPowerSaveBlockPreventSystemSleep: + flags |= kInhibitMarkSessionAsIdle; + flags |= kInhibitSuspendSession; + break; + case PowerSaveBlocker::kPowerSaveBlockPreventNone: + break; + case PowerSaveBlocker::kPowerSaveBlockPreventStateCount: + // This is an invalid argument + NOTREACHED(); + break; + } + + switch (type) { + case PowerSaveBlocker::kPowerSaveBlockPreventDisplaySleep: + case PowerSaveBlocker::kPowerSaveBlockPreventSystemSleep: + // To temporarily suspend the power management features on Gnome, + // we call org.gnome.SessionManager.Inhibit(). + // 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 + // The method returns and inhibit_cookie, used to uniquely identify + // this request. It should be used as an argument to Uninhibit() + // in order to remove the request. + message_writer.AppendString(base::PlatformThread::GetName()); + message_writer.AppendUint32(0); // should be toplevel_xid + message_writer.AppendString(DBusPowerSaveBlocker::kPowerSaveReason); + message_writer.AppendUint32(flags); + bus_callback = base::Bind(&GnomePowerSaveBlocker::OnInhibitResponse, + this); + ++pending_inhibit_calls_; + break; + case PowerSaveBlocker::kPowerSaveBlockPreventNone: + // To cancel a previous inhibit request we call + // org.gnome.SessionManager.Uninhibit(). + // It takes only one argument, the cookie that identifies + // the request we want to cancel. + method_call.SetMember("Uninhibit"); + message_writer.AppendUint32(inhibit_cookie_); + bus_callback = base::Bind(&GnomePowerSaveBlocker::OnUnInhibitResponse, + this); + ++pending_inhibit_calls_; + break; + case PowerSaveBlocker::kPowerSaveBlockPreventStateCount: + // This is an invalid argument; + NOTREACHED(); + break; + } + + object_proxy->CallMethod(&method_call, + dbus::ObjectProxy::TIMEOUT_USE_DEFAULT, + bus_callback); + } + + private: + // Inhibit() response callback. + // Stores the cookie so we can use it later when calling UnInhibit(). + // If the response from D-Bus is successful and there is a postponed + // uninhibit request, we cancel the cookie that we just received. + void OnInhibitResponse(dbus::Response* response) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + DCHECK_GT(pending_inhibit_calls_, 0); + --pending_inhibit_calls_; + if (response != NULL) { + dbus::MessageReader message_reader(response); + if (message_reader.PopUint32(&inhibit_cookie_)) { + if (postponed_uninhibit_calls_ > 0) { + --postponed_uninhibit_calls_; + ApplyBlock(PowerSaveBlocker::kPowerSaveBlockPreventNone); + } + return; + } else { + LOG(ERROR) << "Invalid Inhibit() response: " << response->ToString(); + } + } + inhibit_cookie_ = 0; + if (postponed_uninhibit_calls_ > 0) { + --postponed_uninhibit_calls_; + } + } + + // Uninhibit() response callback. + // We set the |inhibit_cookie_| to 0 even if the D-Bus call failed. + void OnUnInhibitResponse(dbus::Response* response) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + inhibit_cookie_ = 0; + }; + + // The cookie that identifies our last inhibit request, + // or 0 if there is no active inhibit request. + uint32 inhibit_cookie_; + + // Store the number of inhibit calls for which + // we did not receive a response yet + int pending_inhibit_calls_; + + // Store the number of Uninhibit requests that arrived, + // before the corresponding Inhibit calls were completed. + int postponed_uninhibit_calls_; + + DISALLOW_COPY_AND_ASSIGN(GnomePowerSaveBlocker); +}; + +const char DBusPowerSaveBlocker::kPowerSaveReason[] = "Power Save Blocker"; + +// Initialize the DBusPowerSaveBlocker instance: +// 1. Instantiate a concrete delegate based on the current desktop environment, +// 2. Instantiate the D-Bus object +DBusPowerSaveBlocker::DBusPowerSaveBlocker() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + + switch (base::nix::GetDesktopEnvironment(base::Environment::Create())) { + case base::nix::DESKTOP_ENVIRONMENT_GNOME: + delegate_ = new GnomePowerSaveBlocker(); + break; + case base::nix::DESKTOP_ENVIRONMENT_XFCE: + case base::nix::DESKTOP_ENVIRONMENT_KDE4: + delegate_ = new KDEPowerSaveBlocker(); + break; + case base::nix::DESKTOP_ENVIRONMENT_KDE3: + case base::nix::DESKTOP_ENVIRONMENT_OTHER: + // Not supported, so we exit. + // We don't create D-Bus objects. + break; + } + + if (delegate_ != NULL) { + dbus::Bus::Options options; + options.bus_type = dbus::Bus::SESSION; + options.connection_type = dbus::Bus::PRIVATE; + // Use the FILE thread to service the D-Bus connection, + // since we need a thread that allows I/O operations. + options.dbus_thread_message_loop_proxy = + BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE); + bus_ = new dbus::Bus(options); + } +} + +DBusPowerSaveBlocker::~DBusPowerSaveBlocker() { + // We try to shut down the bus, but unfortunately in most of the + // cases when we delete the singleton instance, + // the FILE thread is already stopped and there is no way to + // shutdown the bus object on the origin thread (the UI thread). + // However, this is not a crucial problem since at this point + // we are at the very end of the shutting down phase. + // Connection to D-Bus is just a Unix domain socket, which is not + // a persistent resource, hence the operating system will take care + // of closing it when the process terminates. + if (BrowserThread::IsMessageLoopValid(BrowserThread::FILE)) { + bus_->ShutdownOnDBusThreadAndBlock(); + } +} + +// static +DBusPowerSaveBlocker* DBusPowerSaveBlocker::GetInstance() { + return Singleton<DBusPowerSaveBlocker>::get(); +} + +} // namespace + +// Called only from UI thread. +// static +void PowerSaveBlocker::ApplyBlock(PowerSaveBlockerType type) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + DBusPowerSaveBlocker::GetInstance()->ApplyBlock(type); +} + |