| // 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 "content/browser/power_save_blocker.h" |
| |
| #include "base/basictypes.h" |
| #include "base/bind.h" |
| #include "base/callback.h" |
| #include "base/command_line.h" |
| #include "base/environment.h" |
| #include "base/file_path.h" |
| #include "base/logging.h" |
| #include "base/memory/ref_counted.h" |
| #include "base/memory/scoped_ptr.h" |
| #include "base/memory/singleton.h" |
| #include "base/message_loop_proxy.h" |
| #include "base/nix/xdg_util.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "dbus/bus.h" |
| #include "dbus/message.h" |
| #include "dbus/object_path.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_) |
| 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) OVERRIDE { |
| 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", |
| dbus::ObjectPath("/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( |
| CommandLine::ForCurrentProcess()->GetProgram().value()); |
| 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) { |
| 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) OVERRIDE { |
| 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); |
| } |
| |
| static const char kGnomeSessionManagerName[] = "org.gnome.SessionManager"; |
| scoped_refptr<dbus::ObjectProxy> object_proxy = |
| DBusPowerSaveBlocker::GetInstance()->bus()->GetObjectProxy( |
| kGnomeSessionManagerName, |
| dbus::ObjectPath("/org/gnome/SessionManager")); |
| dbus::MethodCall method_call(kGnomeSessionManagerName, "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( |
| CommandLine::ForCurrentProcess()->GetProgram().value()); |
| 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) { |
| 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)); |
| |
| scoped_ptr<base::Environment> env(base::Environment::Create()); |
| switch (base::nix::GetDesktopEnvironment(env.get())) { |
| 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_) { |
| 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); |
| } |