Add ICU message format support
[chromium-blink-merge.git] / content / browser / power_save_blocker_x11.cc
blob4ffe2e6b0eb7226e49481d83dac0ca57bfc408f2
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/nix/xdg_util.h"
25 #include "base/synchronization/lock.h"
26 #include "content/public/browser/browser_thread.h"
27 #include "dbus/bus.h"
28 #include "dbus/message.h"
29 #include "dbus/object_path.h"
30 #include "dbus/object_proxy.h"
31 #include "ui/gfx/x/x11_types.h"
33 namespace {
35 enum DBusAPI {
36 NO_API, // Disable. No supported API available.
37 GNOME_API, // Use the GNOME API. (Supports more features.)
38 FREEDESKTOP_API, // Use the FreeDesktop API, for KDE4 and XFCE.
41 // Inhibit flags defined in the org.gnome.SessionManager interface.
42 // Can be OR'd together and passed as argument to the Inhibit() method
43 // to specify which power management features we want to suspend.
44 enum GnomeAPIInhibitFlags {
45 INHIBIT_LOGOUT = 1,
46 INHIBIT_SWITCH_USER = 2,
47 INHIBIT_SUSPEND_SESSION = 4,
48 INHIBIT_MARK_SESSION_IDLE = 8
51 const char kGnomeAPIServiceName[] = "org.gnome.SessionManager";
52 const char kGnomeAPIInterfaceName[] = "org.gnome.SessionManager";
53 const char kGnomeAPIObjectPath[] = "/org/gnome/SessionManager";
55 const char kFreeDesktopAPIServiceName[] = "org.freedesktop.PowerManagement";
56 const char kFreeDesktopAPIInterfaceName[] =
57 "org.freedesktop.PowerManagement.Inhibit";
58 const char kFreeDesktopAPIObjectPath[] =
59 "/org/freedesktop/PowerManagement/Inhibit";
61 } // namespace
63 namespace content {
65 class PowerSaveBlockerImpl::Delegate
66 : public base::RefCountedThreadSafe<PowerSaveBlockerImpl::Delegate> {
67 public:
68 // Picks an appropriate D-Bus API to use based on the desktop environment.
69 Delegate(PowerSaveBlockerType type, const std::string& description);
71 // Post a task to initialize the delegate on the UI thread, which will itself
72 // then post a task to apply the power save block on the FILE thread.
73 void Init();
75 // Post a task to remove the power save block on the FILE thread, unless it
76 // hasn't yet been applied, in which case we just prevent it from applying.
77 void CleanUp();
79 private:
80 friend class base::RefCountedThreadSafe<Delegate>;
81 ~Delegate() {}
83 // Selects an appropriate D-Bus API to use for this object. Must be called on
84 // the UI thread. Checks enqueue_apply_ once an API has been selected, and
85 // enqueues a call back to ApplyBlock() if it is true. See the comments for
86 // enqueue_apply_ below.
87 void InitOnUIThread();
89 // Apply or remove the power save block, respectively. These methods should be
90 // called once each, on the same thread, per instance. They block waiting for
91 // the action to complete (with a timeout); the thread must thus allow I/O.
92 void ApplyBlock(DBusAPI api);
93 void RemoveBlock(DBusAPI api);
95 // Asynchronous callback functions for ApplyBlock and RemoveBlock.
96 // Functions do not receive ownership of |response|.
97 void ApplyBlockFinished(DBusAPI api, dbus::Response* response);
98 void RemoveBlockFinished(dbus::Response* response);
100 // If DPMS (the power saving system in X11) is not enabled, then we don't want
101 // to try to disable power saving, since on some desktop environments that may
102 // enable DPMS with very poor default settings (e.g. turning off the display
103 // after only 1 second). Must be called on the UI thread.
104 static bool DPMSEnabled();
106 // Returns an appropriate D-Bus API to use based on the desktop environment.
107 // Must be called on the UI thread, as it may call DPMSEnabled() above.
108 static DBusAPI SelectAPI();
110 const PowerSaveBlockerType type_;
111 const std::string description_;
113 // Initially, we post a message to the UI thread to select an API. When it
114 // finishes, it will post a message to the FILE thread to perform the actual
115 // application of the block, unless enqueue_apply_ is false. We set it to
116 // false when we post that message, or when RemoveBlock() is called before
117 // ApplyBlock() has run. Both api_ and enqueue_apply_ are guarded by lock_.
118 DBusAPI api_;
119 bool enqueue_apply_;
120 base::Lock lock_;
122 // Indicates that a D-Bus power save blocking request is in flight.
123 bool block_inflight_;
124 // Used to detect erronous redundant calls to RemoveBlock().
125 bool unblock_inflight_;
126 // Indicates that RemoveBlock() is called before ApplyBlock() has finished.
127 // If it's true, then the RemoveBlock() call will be processed immediately
128 // after ApplyBlock() has finished.
129 bool enqueue_unblock_;
131 scoped_refptr<dbus::Bus> bus_;
133 // The cookie that identifies our inhibit request,
134 // or 0 if there is no active inhibit request.
135 uint32 inhibit_cookie_;
137 DISALLOW_COPY_AND_ASSIGN(Delegate);
140 PowerSaveBlockerImpl::Delegate::Delegate(PowerSaveBlockerType type,
141 const std::string& description)
142 : type_(type),
143 description_(description),
144 api_(NO_API),
145 enqueue_apply_(false),
146 inhibit_cookie_(0) {
147 // We're on the client's thread here, so we don't allocate the dbus::Bus
148 // object yet. We'll do it later in ApplyBlock(), on the FILE thread.
151 void PowerSaveBlockerImpl::Delegate::Init() {
152 base::AutoLock lock(lock_);
153 DCHECK(!enqueue_apply_);
154 enqueue_apply_ = true;
155 block_inflight_ = false;
156 unblock_inflight_ = false;
157 enqueue_unblock_ = false;
158 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
159 base::Bind(&Delegate::InitOnUIThread, this));
162 void PowerSaveBlockerImpl::Delegate::CleanUp() {
163 base::AutoLock lock(lock_);
164 if (enqueue_apply_) {
165 // If a call to ApplyBlock() has not yet been enqueued because we are still
166 // initializing on the UI thread, then just cancel it. We don't need to
167 // remove the block because we haven't even applied it yet.
168 enqueue_apply_ = false;
169 } else if (api_ != NO_API) {
170 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
171 base::Bind(&Delegate::RemoveBlock, this, api_));
175 void PowerSaveBlockerImpl::Delegate::InitOnUIThread() {
176 DCHECK_CURRENTLY_ON(BrowserThread::UI);
177 base::AutoLock lock(lock_);
178 api_ = SelectAPI();
179 if (enqueue_apply_ && api_ != NO_API) {
180 // The thread we use here becomes the origin and D-Bus thread for the D-Bus
181 // library, so we need to use the same thread above for RemoveBlock(). It
182 // must be a thread that allows I/O operations, so we use the FILE thread.
183 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
184 base::Bind(&Delegate::ApplyBlock, this, api_));
186 enqueue_apply_ = false;
189 void PowerSaveBlockerImpl::Delegate::ApplyBlock(DBusAPI api) {
190 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
191 DCHECK(!bus_); // ApplyBlock() should only be called once.
192 DCHECK(!block_inflight_);
194 dbus::Bus::Options options;
195 options.bus_type = dbus::Bus::SESSION;
196 options.connection_type = dbus::Bus::PRIVATE;
197 bus_ = new dbus::Bus(options);
199 scoped_refptr<dbus::ObjectProxy> object_proxy;
200 scoped_ptr<dbus::MethodCall> method_call;
201 scoped_ptr<dbus::MessageWriter> message_writer;
203 switch (api) {
204 case NO_API:
205 NOTREACHED(); // We should never call this method with this value.
206 return;
207 case GNOME_API:
208 object_proxy = bus_->GetObjectProxy(
209 kGnomeAPIServiceName,
210 dbus::ObjectPath(kGnomeAPIObjectPath));
211 method_call.reset(
212 new dbus::MethodCall(kGnomeAPIInterfaceName, "Inhibit"));
213 message_writer.reset(new dbus::MessageWriter(method_call.get()));
214 // The arguments of the method are:
215 // app_id: The application identifier
216 // toplevel_xid: The toplevel X window identifier
217 // reason: The reason for the inhibit
218 // flags: Flags that spefify what should be inhibited
219 message_writer->AppendString(
220 base::CommandLine::ForCurrentProcess()->GetProgram().value());
221 message_writer->AppendUint32(0); // should be toplevel_xid
222 message_writer->AppendString(description_);
224 uint32 flags = 0;
225 switch (type_) {
226 case kPowerSaveBlockPreventDisplaySleep:
227 flags |= INHIBIT_MARK_SESSION_IDLE;
228 flags |= INHIBIT_SUSPEND_SESSION;
229 break;
230 case kPowerSaveBlockPreventAppSuspension:
231 flags |= INHIBIT_SUSPEND_SESSION;
232 break;
234 message_writer->AppendUint32(flags);
236 break;
237 case FREEDESKTOP_API:
238 object_proxy = bus_->GetObjectProxy(
239 kFreeDesktopAPIServiceName,
240 dbus::ObjectPath(kFreeDesktopAPIObjectPath));
241 method_call.reset(
242 new dbus::MethodCall(kFreeDesktopAPIInterfaceName, "Inhibit"));
243 message_writer.reset(new dbus::MessageWriter(method_call.get()));
244 // The arguments of the method are:
245 // app_id: The application identifier
246 // reason: The reason for the inhibit
247 message_writer->AppendString(
248 base::CommandLine::ForCurrentProcess()->GetProgram().value());
249 message_writer->AppendString(description_);
250 break;
253 block_inflight_ = true;
254 object_proxy->CallMethod(
255 method_call.get(), dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
256 base::Bind(&PowerSaveBlockerImpl::Delegate::ApplyBlockFinished, this,
257 api));
260 void PowerSaveBlockerImpl::Delegate::ApplyBlockFinished(
261 DBusAPI api,
262 dbus::Response* response) {
263 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
264 DCHECK(bus_);
265 DCHECK(block_inflight_);
266 block_inflight_ = false;
268 if (response) {
269 // The method returns an inhibit_cookie, used to uniquely identify
270 // this request. It should be used as an argument to Uninhibit()
271 // in order to remove the request.
272 dbus::MessageReader message_reader(response);
273 if (!message_reader.PopUint32(&inhibit_cookie_))
274 LOG(ERROR) << "Invalid Inhibit() response: " << response->ToString();
275 } else {
276 LOG(ERROR) << "No response to Inhibit() request!";
279 if (enqueue_unblock_) {
280 enqueue_unblock_ = false;
281 // RemoveBlock() was called while the Inhibit operation was in flight,
282 // so go ahead and remove the block now.
283 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
284 base::Bind(&Delegate::RemoveBlock, this, api_));
288 void PowerSaveBlockerImpl::Delegate::RemoveBlock(DBusAPI api) {
289 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
290 DCHECK(bus_); // RemoveBlock() should only be called once.
291 DCHECK(!unblock_inflight_);
293 if (block_inflight_) {
294 DCHECK(!enqueue_unblock_);
295 // Can't call RemoveBlock until ApplyBlock's async operation has
296 // finished. Enqueue it for execution once ApplyBlock is done.
297 enqueue_unblock_ = true;
298 return;
301 scoped_refptr<dbus::ObjectProxy> object_proxy;
302 scoped_ptr<dbus::MethodCall> method_call;
304 switch (api) {
305 case NO_API:
306 NOTREACHED(); // We should never call this method with this value.
307 return;
308 case GNOME_API:
309 object_proxy = bus_->GetObjectProxy(
310 kGnomeAPIServiceName,
311 dbus::ObjectPath(kGnomeAPIObjectPath));
312 method_call.reset(
313 new dbus::MethodCall(kGnomeAPIInterfaceName, "Uninhibit"));
314 break;
315 case FREEDESKTOP_API:
316 object_proxy = bus_->GetObjectProxy(
317 kFreeDesktopAPIServiceName,
318 dbus::ObjectPath(kFreeDesktopAPIObjectPath));
319 method_call.reset(
320 new dbus::MethodCall(kFreeDesktopAPIInterfaceName, "UnInhibit"));
321 break;
324 dbus::MessageWriter message_writer(method_call.get());
325 message_writer.AppendUint32(inhibit_cookie_);
326 unblock_inflight_ = true;
327 object_proxy->CallMethod(
328 method_call.get(), dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
329 base::Bind(&PowerSaveBlockerImpl::Delegate::RemoveBlockFinished, this));
332 void PowerSaveBlockerImpl::Delegate::RemoveBlockFinished(
333 dbus::Response* response) {
334 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
335 DCHECK(bus_);
336 unblock_inflight_ = false;
338 if (!response)
339 LOG(ERROR) << "No response to Uninhibit() request!";
340 // We don't care about checking the result. We assume it works; we can't
341 // really do anything about it anyway if it fails.
342 inhibit_cookie_ = 0;
344 bus_->ShutdownAndBlock();
345 bus_ = NULL;
348 // static
349 bool PowerSaveBlockerImpl::Delegate::DPMSEnabled() {
350 XDisplay* display = gfx::GetXDisplay();
351 BOOL enabled = false;
352 int dummy;
353 if (DPMSQueryExtension(display, &dummy, &dummy) && DPMSCapable(display)) {
354 CARD16 state;
355 DPMSInfo(display, &state, &enabled);
357 return enabled;
360 // static
361 DBusAPI PowerSaveBlockerImpl::Delegate::SelectAPI() {
362 scoped_ptr<base::Environment> env(base::Environment::Create());
363 switch (base::nix::GetDesktopEnvironment(env.get())) {
364 case base::nix::DESKTOP_ENVIRONMENT_GNOME:
365 case base::nix::DESKTOP_ENVIRONMENT_UNITY:
366 if (DPMSEnabled())
367 return GNOME_API;
368 break;
369 case base::nix::DESKTOP_ENVIRONMENT_XFCE:
370 case base::nix::DESKTOP_ENVIRONMENT_KDE4:
371 if (DPMSEnabled())
372 return FREEDESKTOP_API;
373 break;
374 case base::nix::DESKTOP_ENVIRONMENT_KDE3:
375 case base::nix::DESKTOP_ENVIRONMENT_OTHER:
376 // Not supported.
377 break;
379 return NO_API;
382 PowerSaveBlockerImpl::PowerSaveBlockerImpl(PowerSaveBlockerType type,
383 Reason reason,
384 const std::string& description)
385 : delegate_(new Delegate(type, description)) {
386 delegate_->Init();
389 PowerSaveBlockerImpl::~PowerSaveBlockerImpl() {
390 delegate_->CleanUp();
393 } // namespace content