diff options
author | mdm@chromium.org <mdm@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-06-08 22:48:02 +0000 |
---|---|---|
committer | mdm@chromium.org <mdm@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-06-08 22:48:02 +0000 |
commit | 94539e9d1e7eb821eba2bdc1fdc8b79a0fb361bd (patch) | |
tree | ce1e13079c21258ecfd5daad4cc7855ca2f0f81c /content | |
parent | 4990ec7c46fd475af1ddbdee5b7ac8e1380fe0d2 (diff) | |
download | chromium_src-94539e9d1e7eb821eba2bdc1fdc8b79a0fb361bd.zip chromium_src-94539e9d1e7eb821eba2bdc1fdc8b79a0fb361bd.tar.gz chromium_src-94539e9d1e7eb821eba2bdc1fdc8b79a0fb361bd.tar.bz2 |
Implement PowerSaveBlocker2 for Linux. Much simpler than the original!
After this CL is committed, I'll remove all the old code and switch over the
callers. I'll also port the Android version, which is just a stub with a
NOTIMPLEMENTED() in it anyway.
BUG=126591
Review URL: https://chromiumcodereview.appspot.com/10545076
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@141311 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'content')
-rw-r--r-- | content/browser/power_save_blocker.h | 8 | ||||
-rw-r--r-- | content/browser/power_save_blocker_linux.cc | 259 |
2 files changed, 265 insertions, 2 deletions
diff --git a/content/browser/power_save_blocker.h b/content/browser/power_save_blocker.h index 2eb6bc0..ee57e27 100644 --- a/content/browser/power_save_blocker.h +++ b/content/browser/power_save_blocker.h @@ -60,6 +60,7 @@ namespace content { // NOT READY YET. PowerSaveBlocker above is soon to be replaced by this class, // but it's not done yet so client code should use the one above for now. // A RAII-style class to block the system from entering low-power (sleep) mode. +// This class is thread-safe; it may be constructed and deleted on any thread. class CONTENT_EXPORT PowerSaveBlocker2 { public: enum PowerSaveBlockerType { @@ -89,9 +90,12 @@ class CONTENT_EXPORT PowerSaveBlocker2 { // Implementations of this class may need a second object with different // lifetime than the RAII container, or additional storage. This member is // here for that purpose. If not used, just define the class as an empty - // RefCounted like so to make it compile: + // RefCounted (or RefCountedThreadSafe) like so to make it compile: // class PowerSaveBlocker2::Delegate - // : public RefCounted<PowerSaveBlocker2::Delegate> { + // : public base::RefCounted<PowerSaveBlocker2::Delegate> { + // private: + // friend class base::RefCounted<Delegate>; + // ~Delegate() {} // }; scoped_refptr<Delegate> delegate_; diff --git a/content/browser/power_save_blocker_linux.cc b/content/browser/power_save_blocker_linux.cc index 776e0a6..e246650 100644 --- a/content/browser/power_save_blocker_linux.cc +++ b/content/browser/power_save_blocker_linux.cc @@ -504,3 +504,262 @@ void PowerSaveBlocker::ApplyBlock(PowerSaveBlockerType type) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); DBusPowerSaveBlocker::GetInstance()->ApplyBlock(type); } + +// TODO(mdm): remove from the beginning of the file up to (and including) this +// line to switch to the new implementation. +#define PowerSaveBlocker PowerSaveBlocker2 + +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 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 kFreeDesktopAPIServiceName[] = "org.freedesktop.PowerManagement"; +const char kFreeDesktopAPIInterfaceName[] = + "org.freedesktop.PowerManagement.Inhibit"; +const char kFreeDesktopAPIObjectPath[] = + "/org/freedesktop/PowerManagement/Inhibit"; + +} // anonymous namespace + +namespace content { + +class PowerSaveBlocker::Delegate + : public base::RefCountedThreadSafe<PowerSaveBlocker::Delegate> { + public: + // Picks an appropriate D-Bus API to use based on the desktop environment. + Delegate(PowerSaveBlockerType type, const std::string& reason); + + // 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 IO. + void ApplyBlock(); + void RemoveBlock(); + + private: + friend class base::RefCountedThreadSafe<Delegate>; + ~Delegate() {} + + // 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). + static bool DPMSEnabled(); + + // Returns an appropriate D-Bus API to use based on the desktop environment. + static DBusAPI SelectAPI(); + + const PowerSaveBlockerType type_; + const std::string reason_; + const DBusAPI api_; + + scoped_refptr<dbus::Bus> bus_; + + // The cookie that identifies our inhibit request, + // or 0 if there is no active inhibit request. + uint32 inhibit_cookie_; + + DISALLOW_COPY_AND_ASSIGN(Delegate); +}; + +PowerSaveBlocker::Delegate::Delegate(PowerSaveBlockerType type, + const std::string& reason) + : type_(type), + reason_(reason), + api_(SelectAPI()), + inhibit_cookie_(0) { + // We're on the client's thread here, so we don't allocate the dbus::Bus + // object yet. We'll do it below in ApplyBlock(), on the FILE thread. +} + +void PowerSaveBlocker::Delegate::ApplyBlock() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); + DCHECK(!bus_.get()); // ApplyBlock() should only be called once. + if (api_ == NO_API) + return; + + 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; + scoped_ptr<dbus::MethodCall> method_call; + scoped_ptr<dbus::MessageWriter> message_writer; + + switch (api_) { + case NO_API: + NOTREACHED(); // We return early above. + 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( + CommandLine::ForCurrentProcess()->GetProgram().value()); + message_writer->AppendUint32(0); // should be toplevel_xid + message_writer->AppendString(reason_); + { + uint32 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: + object_proxy = bus_->GetObjectProxy( + kFreeDesktopAPIServiceName, + dbus::ObjectPath(kFreeDesktopAPIObjectPath)); + method_call.reset( + new dbus::MethodCall(kFreeDesktopAPIInterfaceName, "Inhibit")); + 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( + CommandLine::ForCurrentProcess()->GetProgram().value()); + message_writer->AppendString(reason_); + break; + } + + // We could do this method call asynchronously, but if we did, we'd need to + // handle the case where we want to cancel the block before we get a reply. + // We're on the FILE thread so it should be OK to block briefly here. + scoped_ptr<dbus::Response> response(object_proxy->CallMethodAndBlock( + method_call.get(), dbus::ObjectProxy::TIMEOUT_USE_DEFAULT)); + if (response.get()) { + // 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.get()); + if (!message_reader.PopUint32(&inhibit_cookie_)) + LOG(ERROR) << "Invalid Inhibit() response: " << response->ToString(); + } else { + LOG(ERROR) << "No response to Inhibit() request!"; + } +} + +void PowerSaveBlocker::Delegate::RemoveBlock() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); + if (api_ == NO_API) + return; + DCHECK(bus_.get()); // RemoveBlock() should only be called once. + + scoped_refptr<dbus::ObjectProxy> object_proxy; + scoped_ptr<dbus::MethodCall> method_call; + + switch (api_) { + case NO_API: + NOTREACHED(); // We return early above. + return; + case GNOME_API: + object_proxy = bus_->GetObjectProxy( + kGnomeAPIServiceName, + dbus::ObjectPath(kGnomeAPIObjectPath)); + method_call.reset( + new dbus::MethodCall(kGnomeAPIInterfaceName, "Uninhibit")); + break; + case FREEDESKTOP_API: + object_proxy = bus_->GetObjectProxy( + kFreeDesktopAPIServiceName, + dbus::ObjectPath(kFreeDesktopAPIObjectPath)); + method_call.reset( + new dbus::MethodCall(kFreeDesktopAPIInterfaceName, "Uninhibit")); + break; + } + + dbus::MessageWriter message_writer(method_call.get()); + message_writer.AppendUint32(inhibit_cookie_); + scoped_ptr<dbus::Response> response(object_proxy->CallMethodAndBlock( + method_call.get(), dbus::ObjectProxy::TIMEOUT_USE_DEFAULT)); + if (!response.get()) + 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_ = NULL; +} + +// static +bool PowerSaveBlocker::Delegate::DPMSEnabled() { + Display* display = base::MessagePumpForUI::GetDefaultXDisplay(); + BOOL enabled = false; + int dummy; + if (DPMSQueryExtension(display, &dummy, &dummy) && DPMSCapable(display)) { + CARD16 state; + DPMSInfo(display, &state, &enabled); + } + return enabled; +} + +// static +DBusAPI PowerSaveBlocker::Delegate::SelectAPI() { + scoped_ptr<base::Environment> env(base::Environment::Create()); + switch (base::nix::GetDesktopEnvironment(env.get())) { + case base::nix::DESKTOP_ENVIRONMENT_GNOME: + if (DPMSEnabled()) + return GNOME_API; + break; + case base::nix::DESKTOP_ENVIRONMENT_XFCE: + case base::nix::DESKTOP_ENVIRONMENT_KDE4: + if (DPMSEnabled()) + return FREEDESKTOP_API; + break; + case base::nix::DESKTOP_ENVIRONMENT_KDE3: + case base::nix::DESKTOP_ENVIRONMENT_OTHER: + // Not supported. + break; + } + return NO_API; +} + +PowerSaveBlocker::PowerSaveBlocker( + PowerSaveBlockerType type, const std::string& reason) + : delegate_(new Delegate(type, reason)) { + // 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 below in the destructor. It must + // be a thread that allows I/O operations. + BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, + base::Bind(&Delegate::ApplyBlock, delegate_)); +} + +PowerSaveBlocker::~PowerSaveBlocker() { + BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, + base::Bind(&Delegate::RemoveBlock, delegate_)); +} + +} // namespace content |