Blink roll 25b6bd3a7a131ffe68d809546ad1a20707915cdc:3a503f41ae42e5b79cfcd2ff10e65afde...
[chromium-blink-merge.git] / content / browser / power_save_blocker_x11.cc
blobf1d028c7f459e51bbc234af18050b3095b9b7618
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "content/browser/power_save_blocker_impl.h"
7 #include <X11/Xlib.h>
8 #include <X11/extensions/dpms.h>
9 // Xlib #defines Status, but we can't have that for some of our headers.
10 #ifdef Status
11 #undef Status
12 #endif
14 #include "base/basictypes.h"
15 #include "base/bind.h"
16 #include "base/callback.h"
17 #include "base/command_line.h"
18 #include "base/environment.h"
19 #include "base/files/file_path.h"
20 #include "base/logging.h"
21 #include "base/memory/ref_counted.h"
22 #include "base/memory/scoped_ptr.h"
23 #include "base/memory/singleton.h"
24 #include "base/message_loop/message_loop_proxy.h"
25 #include "base/nix/xdg_util.h"
26 #include "base/synchronization/lock.h"
27 #include "content/public/browser/browser_thread.h"
28 #include "dbus/bus.h"
29 #include "dbus/message.h"
30 #include "dbus/object_path.h"
31 #include "dbus/object_proxy.h"
32 #include "ui/gfx/x/x11_types.h"
34 namespace {
36 enum DBusAPI {
37 NO_API, // Disable. No supported API available.
38 GNOME_API, // Use the GNOME API. (Supports more features.)
39 FREEDESKTOP_API, // Use the FreeDesktop API, for KDE4 and XFCE.
42 // Inhibit flags defined in the org.gnome.SessionManager interface.
43 // Can be OR'd together and passed as argument to the Inhibit() method
44 // to specify which power management features we want to suspend.
45 enum GnomeAPIInhibitFlags {
46 INHIBIT_LOGOUT = 1,
47 INHIBIT_SWITCH_USER = 2,
48 INHIBIT_SUSPEND_SESSION = 4,
49 INHIBIT_MARK_SESSION_IDLE = 8
52 const char kGnomeAPIServiceName[] = "org.gnome.SessionManager";
53 const char kGnomeAPIInterfaceName[] = "org.gnome.SessionManager";
54 const char kGnomeAPIObjectPath[] = "/org/gnome/SessionManager";
56 const char kFreeDesktopAPIServiceName[] = "org.freedesktop.PowerManagement";
57 const char kFreeDesktopAPIInterfaceName[] =
58 "org.freedesktop.PowerManagement.Inhibit";
59 const char kFreeDesktopAPIObjectPath[] =
60 "/org/freedesktop/PowerManagement/Inhibit";
62 } // namespace
64 namespace content {
66 class PowerSaveBlockerImpl::Delegate
67 : public base::RefCountedThreadSafe<PowerSaveBlockerImpl::Delegate> {
68 public:
69 // Picks an appropriate D-Bus API to use based on the desktop environment.
70 Delegate(PowerSaveBlockerType type, const std::string& reason);
72 // Post a task to initialize the delegate on the UI thread, which will itself
73 // then post a task to apply the power save block on the FILE thread.
74 void Init();
76 // Post a task to remove the power save block on the FILE thread, unless it
77 // hasn't yet been applied, in which case we just prevent it from applying.
78 void CleanUp();
80 private:
81 friend class base::RefCountedThreadSafe<Delegate>;
82 ~Delegate() {}
84 // Selects an appropriate D-Bus API to use for this object. Must be called on
85 // the UI thread. Checks enqueue_apply_ once an API has been selected, and
86 // enqueues a call back to ApplyBlock() if it is true. See the comments for
87 // enqueue_apply_ below.
88 void InitOnUIThread();
90 // Apply or remove the power save block, respectively. These methods should be
91 // called once each, on the same thread, per instance. They block waiting for
92 // the action to complete (with a timeout); the thread must thus allow I/O.
93 void ApplyBlock(DBusAPI api);
94 void RemoveBlock(DBusAPI api);
96 // If DPMS (the power saving system in X11) is not enabled, then we don't want
97 // to try to disable power saving, since on some desktop environments that may
98 // enable DPMS with very poor default settings (e.g. turning off the display
99 // after only 1 second). Must be called on the UI thread.
100 static bool DPMSEnabled();
102 // Returns an appropriate D-Bus API to use based on the desktop environment.
103 // Must be called on the UI thread, as it may call DPMSEnabled() above.
104 static DBusAPI SelectAPI();
106 const PowerSaveBlockerType type_;
107 const std::string reason_;
109 // Initially, we post a message to the UI thread to select an API. When it
110 // finishes, it will post a message to the FILE thread to perform the actual
111 // application of the block, unless enqueue_apply_ is false. We set it to
112 // false when we post that message, or when RemoveBlock() is called before
113 // ApplyBlock() has run. Both api_ and enqueue_apply_ are guarded by lock_.
114 DBusAPI api_;
115 bool enqueue_apply_;
116 base::Lock lock_;
118 scoped_refptr<dbus::Bus> bus_;
120 // The cookie that identifies our inhibit request,
121 // or 0 if there is no active inhibit request.
122 uint32 inhibit_cookie_;
124 DISALLOW_COPY_AND_ASSIGN(Delegate);
127 PowerSaveBlockerImpl::Delegate::Delegate(PowerSaveBlockerType type,
128 const std::string& reason)
129 : type_(type),
130 reason_(reason),
131 api_(NO_API),
132 enqueue_apply_(false),
133 inhibit_cookie_(0) {
134 // We're on the client's thread here, so we don't allocate the dbus::Bus
135 // object yet. We'll do it later in ApplyBlock(), on the FILE thread.
138 void PowerSaveBlockerImpl::Delegate::Init() {
139 base::AutoLock lock(lock_);
140 DCHECK(!enqueue_apply_);
141 enqueue_apply_ = true;
142 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
143 base::Bind(&Delegate::InitOnUIThread, this));
146 void PowerSaveBlockerImpl::Delegate::CleanUp() {
147 base::AutoLock lock(lock_);
148 if (enqueue_apply_) {
149 // If a call to ApplyBlock() has not yet been enqueued because we are still
150 // initializing on the UI thread, then just cancel it. We don't need to
151 // remove the block because we haven't even applied it yet.
152 enqueue_apply_ = false;
153 } else if (api_ != NO_API) {
154 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
155 base::Bind(&Delegate::RemoveBlock, this, api_));
159 void PowerSaveBlockerImpl::Delegate::InitOnUIThread() {
160 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
161 base::AutoLock lock(lock_);
162 api_ = SelectAPI();
163 if (enqueue_apply_ && api_ != NO_API) {
164 // The thread we use here becomes the origin and D-Bus thread for the D-Bus
165 // library, so we need to use the same thread above for RemoveBlock(). It
166 // must be a thread that allows I/O operations, so we use the FILE thread.
167 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
168 base::Bind(&Delegate::ApplyBlock, this, api_));
170 enqueue_apply_ = false;
173 void PowerSaveBlockerImpl::Delegate::ApplyBlock(DBusAPI api) {
174 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
175 DCHECK(!bus_.get()); // ApplyBlock() should only be called once.
177 dbus::Bus::Options options;
178 options.bus_type = dbus::Bus::SESSION;
179 options.connection_type = dbus::Bus::PRIVATE;
180 bus_ = new dbus::Bus(options);
182 scoped_refptr<dbus::ObjectProxy> object_proxy;
183 scoped_ptr<dbus::MethodCall> method_call;
184 scoped_ptr<dbus::MessageWriter> message_writer;
186 switch (api) {
187 case NO_API:
188 NOTREACHED(); // We should never call this method with this value.
189 return;
190 case GNOME_API:
191 object_proxy = bus_->GetObjectProxy(
192 kGnomeAPIServiceName,
193 dbus::ObjectPath(kGnomeAPIObjectPath));
194 method_call.reset(
195 new dbus::MethodCall(kGnomeAPIInterfaceName, "Inhibit"));
196 message_writer.reset(new dbus::MessageWriter(method_call.get()));
197 // The arguments of the method are:
198 // app_id: The application identifier
199 // toplevel_xid: The toplevel X window identifier
200 // reason: The reason for the inhibit
201 // flags: Flags that spefify what should be inhibited
202 message_writer->AppendString(
203 base::CommandLine::ForCurrentProcess()->GetProgram().value());
204 message_writer->AppendUint32(0); // should be toplevel_xid
205 message_writer->AppendString(reason_);
207 uint32 flags = 0;
208 switch (type_) {
209 case kPowerSaveBlockPreventDisplaySleep:
210 flags |= INHIBIT_MARK_SESSION_IDLE;
211 flags |= INHIBIT_SUSPEND_SESSION;
212 break;
213 case kPowerSaveBlockPreventAppSuspension:
214 flags |= INHIBIT_SUSPEND_SESSION;
215 break;
217 message_writer->AppendUint32(flags);
219 break;
220 case FREEDESKTOP_API:
221 object_proxy = bus_->GetObjectProxy(
222 kFreeDesktopAPIServiceName,
223 dbus::ObjectPath(kFreeDesktopAPIObjectPath));
224 method_call.reset(
225 new dbus::MethodCall(kFreeDesktopAPIInterfaceName, "Inhibit"));
226 message_writer.reset(new dbus::MessageWriter(method_call.get()));
227 // The arguments of the method are:
228 // app_id: The application identifier
229 // reason: The reason for the inhibit
230 message_writer->AppendString(
231 base::CommandLine::ForCurrentProcess()->GetProgram().value());
232 message_writer->AppendString(reason_);
233 break;
236 // We could do this method call asynchronously, but if we did, we'd need to
237 // handle the case where we want to cancel the block before we get a reply.
238 // We're on the FILE thread so it should be OK to block briefly here.
239 scoped_ptr<dbus::Response> response(object_proxy->CallMethodAndBlock(
240 method_call.get(), dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
241 if (response) {
242 // The method returns an inhibit_cookie, used to uniquely identify
243 // this request. It should be used as an argument to Uninhibit()
244 // in order to remove the request.
245 dbus::MessageReader message_reader(response.get());
246 if (!message_reader.PopUint32(&inhibit_cookie_))
247 LOG(ERROR) << "Invalid Inhibit() response: " << response->ToString();
248 } else {
249 LOG(ERROR) << "No response to Inhibit() request!";
253 void PowerSaveBlockerImpl::Delegate::RemoveBlock(DBusAPI api) {
254 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
255 DCHECK(bus_.get()); // RemoveBlock() should only be called once.
257 scoped_refptr<dbus::ObjectProxy> object_proxy;
258 scoped_ptr<dbus::MethodCall> method_call;
260 switch (api) {
261 case NO_API:
262 NOTREACHED(); // We should never call this method with this value.
263 return;
264 case GNOME_API:
265 object_proxy = bus_->GetObjectProxy(
266 kGnomeAPIServiceName,
267 dbus::ObjectPath(kGnomeAPIObjectPath));
268 method_call.reset(
269 new dbus::MethodCall(kGnomeAPIInterfaceName, "Uninhibit"));
270 break;
271 case FREEDESKTOP_API:
272 object_proxy = bus_->GetObjectProxy(
273 kFreeDesktopAPIServiceName,
274 dbus::ObjectPath(kFreeDesktopAPIObjectPath));
275 method_call.reset(
276 new dbus::MethodCall(kFreeDesktopAPIInterfaceName, "UnInhibit"));
277 break;
280 dbus::MessageWriter message_writer(method_call.get());
281 message_writer.AppendUint32(inhibit_cookie_);
282 scoped_ptr<dbus::Response> response(object_proxy->CallMethodAndBlock(
283 method_call.get(), dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
284 if (!response)
285 LOG(ERROR) << "No response to Uninhibit() request!";
286 // We don't care about checking the result. We assume it works; we can't
287 // really do anything about it anyway if it fails.
288 inhibit_cookie_ = 0;
290 bus_->ShutdownAndBlock();
291 bus_ = NULL;
294 // static
295 bool PowerSaveBlockerImpl::Delegate::DPMSEnabled() {
296 XDisplay* display = gfx::GetXDisplay();
297 BOOL enabled = false;
298 int dummy;
299 if (DPMSQueryExtension(display, &dummy, &dummy) && DPMSCapable(display)) {
300 CARD16 state;
301 DPMSInfo(display, &state, &enabled);
303 return enabled;
306 // static
307 DBusAPI PowerSaveBlockerImpl::Delegate::SelectAPI() {
308 scoped_ptr<base::Environment> env(base::Environment::Create());
309 switch (base::nix::GetDesktopEnvironment(env.get())) {
310 case base::nix::DESKTOP_ENVIRONMENT_GNOME:
311 case base::nix::DESKTOP_ENVIRONMENT_UNITY:
312 if (DPMSEnabled())
313 return GNOME_API;
314 break;
315 case base::nix::DESKTOP_ENVIRONMENT_XFCE:
316 case base::nix::DESKTOP_ENVIRONMENT_KDE4:
317 if (DPMSEnabled())
318 return FREEDESKTOP_API;
319 break;
320 case base::nix::DESKTOP_ENVIRONMENT_KDE3:
321 case base::nix::DESKTOP_ENVIRONMENT_OTHER:
322 // Not supported.
323 break;
325 return NO_API;
328 PowerSaveBlockerImpl::PowerSaveBlockerImpl(
329 PowerSaveBlockerType type, const std::string& reason)
330 : delegate_(new Delegate(type, reason)) {
331 delegate_->Init();
334 PowerSaveBlockerImpl::~PowerSaveBlockerImpl() {
335 delegate_->CleanUp();
338 } // namespace content