// 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 #include // Xlib #defines Status, but we can't have that for some of our headers. #ifdef Status #undef Status #endif #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" #if defined(TOOLKIT_GTK) #include "base/message_pump_gtk.h" #else #include "base/message_pump_x.h" #endif #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 { public: Delegate() {} virtual void ApplyBlock(PowerSaveBlocker::PowerSaveBlockerType type) = 0; protected: virtual ~Delegate() {} private: friend class base::RefCountedThreadSafe; 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 does nothing. 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 bus() const { return bus_; } private: DBusPowerSaveBlocker(); virtual ~DBusPowerSaveBlocker(); // If DPMS 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). static bool DPMSEnabled(); // The D-Bus connection. scoped_refptr bus_; // Concrete implementation of the Delegate interface. scoped_refptr delegate_; friend struct DefaultSingletonTraits; DISALLOW_COPY_AND_ASSIGN(DBusPowerSaveBlocker); }; // Delegate implementation for KDE4. It uses the // org.freedesktop.PowerManagement interface. It works on XFCE4, too. class KDEPowerSaveBlocker : public DBusPowerSaveBlocker::Delegate { public: KDEPowerSaveBlocker() : inhibit_cookie_(0), pending_inhibit_call_(false), postponed_uninhibit_call_(false) { } 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 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 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); } protected: virtual ~KDEPowerSaveBlocker() {} 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) {} 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 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 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); } protected: virtual ~GnomePowerSaveBlocker() {} 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 env(base::Environment::Create()); switch (base::nix::GetDesktopEnvironment(env.get())) { case base::nix::DESKTOP_ENVIRONMENT_GNOME: if (DPMSEnabled()) delegate_ = new GnomePowerSaveBlocker(); break; case base::nix::DESKTOP_ENVIRONMENT_XFCE: case base::nix::DESKTOP_ENVIRONMENT_KDE4: if (DPMSEnabled()) 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 bool DBusPowerSaveBlocker::DPMSEnabled() { #if defined(TOOLKIT_GTK) Display* display = base::MessagePumpGtk::GetDefaultXDisplay(); #else Display* display = base::MessagePumpX::GetDefaultXDisplay(); #endif BOOL enabled = false; int dummy; if (DPMSQueryExtension(display, &dummy, &dummy) && DPMSCapable(display)) { CARD16 state; DPMSInfo(display, &state, &enabled); } return enabled; } // static DBusPowerSaveBlocker* DBusPowerSaveBlocker::GetInstance() { return Singleton::get(); } } // namespace // Called only from UI thread. // static void PowerSaveBlocker::ApplyBlock(PowerSaveBlockerType type) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); DBusPowerSaveBlocker::GetInstance()->ApplyBlock(type); }