[content shell] implement testRunner.overridePreference
[chromium-blink-merge.git] / content / browser / power_save_blocker_linux.cc
blob2ebf7bed5267b0e608cd88b21cc8810edf759038
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.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/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_proxy.h"
25 #if defined(TOOLKIT_GTK)
26 #include "base/message_pump_gtk.h"
27 #else
28 #include "base/message_pump_aurax11.h"
29 #endif
30 #include "base/nix/xdg_util.h"
31 #include "base/synchronization/lock.h"
32 #include "content/public/browser/browser_thread.h"
33 #include "dbus/bus.h"
34 #include "dbus/message.h"
35 #include "dbus/object_path.h"
36 #include "dbus/object_proxy.h"
38 namespace {
40 enum DBusAPI {
41 NO_API, // Disable. No supported API available.
42 GNOME_API, // Use the GNOME API. (Supports more features.)
43 FREEDESKTOP_API, // Use the FreeDesktop API, for KDE4 and XFCE.
46 // Inhibit flags defined in the org.gnome.SessionManager interface.
47 // Can be OR'd together and passed as argument to the Inhibit() method
48 // to specify which power management features we want to suspend.
49 enum GnomeAPIInhibitFlags {
50 INHIBIT_LOGOUT = 1,
51 INHIBIT_SWITCH_USER = 2,
52 INHIBIT_SUSPEND_SESSION = 4,
53 INHIBIT_MARK_SESSION_IDLE = 8
56 const char kGnomeAPIServiceName[] = "org.gnome.SessionManager";
57 const char kGnomeAPIInterfaceName[] = "org.gnome.SessionManager";
58 const char kGnomeAPIObjectPath[] = "/org/gnome/SessionManager";
60 const char kFreeDesktopAPIServiceName[] = "org.freedesktop.PowerManagement";
61 const char kFreeDesktopAPIInterfaceName[] =
62 "org.freedesktop.PowerManagement.Inhibit";
63 const char kFreeDesktopAPIObjectPath[] =
64 "/org/freedesktop/PowerManagement/Inhibit";
66 } // anonymous namespace
68 namespace content {
70 class PowerSaveBlocker::Delegate
71 : public base::RefCountedThreadSafe<PowerSaveBlocker::Delegate> {
72 public:
73 // Picks an appropriate D-Bus API to use based on the desktop environment.
74 Delegate(PowerSaveBlockerType type, const std::string& reason);
76 // Post a task to initialize the delegate on the UI thread, which will itself
77 // then post a task to apply the power save block on the FILE thread.
78 void Init();
80 // Post a task to remove the power save block on the FILE thread, unless it
81 // hasn't yet been applied, in which case we just prevent it from applying.
82 void CleanUp();
84 private:
85 friend class base::RefCountedThreadSafe<Delegate>;
86 ~Delegate() {}
88 // Selects an appropriate D-Bus API to use for this object. Must be called on
89 // the UI thread. Checks enqueue_apply_ once an API has been selected, and
90 // enqueues a call back to ApplyBlock() if it is true. See the comments for
91 // enqueue_apply_ below.
92 void InitOnUIThread();
94 // Apply or remove the power save block, respectively. These methods should be
95 // called once each, on the same thread, per instance. They block waiting for
96 // the action to complete (with a timeout); the thread must thus allow I/O.
97 void ApplyBlock(DBusAPI api);
98 void RemoveBlock(DBusAPI api);
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 reason_;
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 scoped_refptr<dbus::Bus> bus_;
124 // The cookie that identifies our inhibit request,
125 // or 0 if there is no active inhibit request.
126 uint32 inhibit_cookie_;
128 DISALLOW_COPY_AND_ASSIGN(Delegate);
131 PowerSaveBlocker::Delegate::Delegate(PowerSaveBlockerType type,
132 const std::string& reason)
133 : type_(type),
134 reason_(reason),
135 api_(NO_API),
136 enqueue_apply_(false),
137 inhibit_cookie_(0) {
138 // We're on the client's thread here, so we don't allocate the dbus::Bus
139 // object yet. We'll do it later in ApplyBlock(), on the FILE thread.
142 void PowerSaveBlocker::Delegate::Init() {
143 base::AutoLock lock(lock_);
144 DCHECK(!enqueue_apply_);
145 enqueue_apply_ = true;
146 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
147 base::Bind(&Delegate::InitOnUIThread, this));
150 void PowerSaveBlocker::Delegate::CleanUp() {
151 base::AutoLock lock(lock_);
152 if (enqueue_apply_) {
153 // If a call to ApplyBlock() has not yet been enqueued because we are still
154 // initializing on the UI thread, then just cancel it. We don't need to
155 // remove the block because we haven't even applied it yet.
156 enqueue_apply_ = false;
157 } else if (api_ != NO_API) {
158 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
159 base::Bind(&Delegate::RemoveBlock, this, api_));
163 void PowerSaveBlocker::Delegate::InitOnUIThread() {
164 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
165 base::AutoLock lock(lock_);
166 api_ = SelectAPI();
167 if (enqueue_apply_ && api_ != NO_API) {
168 // The thread we use here becomes the origin and D-Bus thread for the D-Bus
169 // library, so we need to use the same thread above for RemoveBlock(). It
170 // must be a thread that allows I/O operations, so we use the FILE thread.
171 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
172 base::Bind(&Delegate::ApplyBlock, this, api_));
174 enqueue_apply_ = false;
177 void PowerSaveBlocker::Delegate::ApplyBlock(DBusAPI api) {
178 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
179 DCHECK(!bus_.get()); // ApplyBlock() should only be called once.
181 dbus::Bus::Options options;
182 options.bus_type = dbus::Bus::SESSION;
183 options.connection_type = dbus::Bus::PRIVATE;
184 bus_ = new dbus::Bus(options);
186 scoped_refptr<dbus::ObjectProxy> object_proxy;
187 scoped_ptr<dbus::MethodCall> method_call;
188 scoped_ptr<dbus::MessageWriter> message_writer;
190 switch (api) {
191 case NO_API:
192 NOTREACHED(); // We should never call this method with this value.
193 return;
194 case GNOME_API:
195 object_proxy = bus_->GetObjectProxy(
196 kGnomeAPIServiceName,
197 dbus::ObjectPath(kGnomeAPIObjectPath));
198 method_call.reset(
199 new dbus::MethodCall(kGnomeAPIInterfaceName, "Inhibit"));
200 message_writer.reset(new dbus::MessageWriter(method_call.get()));
201 // The arguments of the method are:
202 // app_id: The application identifier
203 // toplevel_xid: The toplevel X window identifier
204 // reason: The reason for the inhibit
205 // flags: Flags that spefify what should be inhibited
206 message_writer->AppendString(
207 CommandLine::ForCurrentProcess()->GetProgram().value());
208 message_writer->AppendUint32(0); // should be toplevel_xid
209 message_writer->AppendString(reason_);
211 uint32 flags = 0;
212 switch (type_) {
213 case kPowerSaveBlockPreventDisplaySleep:
214 flags |= INHIBIT_MARK_SESSION_IDLE;
215 flags |= INHIBIT_SUSPEND_SESSION;
216 break;
217 case kPowerSaveBlockPreventAppSuspension:
218 flags |= INHIBIT_SUSPEND_SESSION;
219 break;
221 message_writer->AppendUint32(flags);
223 break;
224 case FREEDESKTOP_API:
225 object_proxy = bus_->GetObjectProxy(
226 kFreeDesktopAPIServiceName,
227 dbus::ObjectPath(kFreeDesktopAPIObjectPath));
228 method_call.reset(
229 new dbus::MethodCall(kFreeDesktopAPIInterfaceName, "Inhibit"));
230 message_writer.reset(new dbus::MessageWriter(method_call.get()));
231 // The arguments of the method are:
232 // app_id: The application identifier
233 // reason: The reason for the inhibit
234 message_writer->AppendString(
235 CommandLine::ForCurrentProcess()->GetProgram().value());
236 message_writer->AppendString(reason_);
237 break;
240 // We could do this method call asynchronously, but if we did, we'd need to
241 // handle the case where we want to cancel the block before we get a reply.
242 // We're on the FILE thread so it should be OK to block briefly here.
243 scoped_ptr<dbus::Response> response(object_proxy->CallMethodAndBlock(
244 method_call.get(), dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
245 if (response.get()) {
246 // The method returns an inhibit_cookie, used to uniquely identify
247 // this request. It should be used as an argument to Uninhibit()
248 // in order to remove the request.
249 dbus::MessageReader message_reader(response.get());
250 if (!message_reader.PopUint32(&inhibit_cookie_))
251 LOG(ERROR) << "Invalid Inhibit() response: " << response->ToString();
252 } else {
253 LOG(ERROR) << "No response to Inhibit() request!";
257 void PowerSaveBlocker::Delegate::RemoveBlock(DBusAPI api) {
258 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
259 DCHECK(bus_.get()); // RemoveBlock() should only be called once.
261 scoped_refptr<dbus::ObjectProxy> object_proxy;
262 scoped_ptr<dbus::MethodCall> method_call;
264 switch (api) {
265 case NO_API:
266 NOTREACHED(); // We should never call this method with this value.
267 return;
268 case GNOME_API:
269 object_proxy = bus_->GetObjectProxy(
270 kGnomeAPIServiceName,
271 dbus::ObjectPath(kGnomeAPIObjectPath));
272 method_call.reset(
273 new dbus::MethodCall(kGnomeAPIInterfaceName, "Uninhibit"));
274 break;
275 case FREEDESKTOP_API:
276 object_proxy = bus_->GetObjectProxy(
277 kFreeDesktopAPIServiceName,
278 dbus::ObjectPath(kFreeDesktopAPIObjectPath));
279 method_call.reset(
280 new dbus::MethodCall(kFreeDesktopAPIInterfaceName, "UnInhibit"));
281 break;
284 dbus::MessageWriter message_writer(method_call.get());
285 message_writer.AppendUint32(inhibit_cookie_);
286 scoped_ptr<dbus::Response> response(object_proxy->CallMethodAndBlock(
287 method_call.get(), dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
288 if (!response.get())
289 LOG(ERROR) << "No response to Uninhibit() request!";
290 // We don't care about checking the result. We assume it works; we can't
291 // really do anything about it anyway if it fails.
292 inhibit_cookie_ = 0;
294 bus_->ShutdownAndBlock();
295 bus_ = NULL;
298 // static
299 bool PowerSaveBlocker::Delegate::DPMSEnabled() {
300 Display* display = base::MessagePumpForUI::GetDefaultXDisplay();
301 BOOL enabled = false;
302 int dummy;
303 if (DPMSQueryExtension(display, &dummy, &dummy) && DPMSCapable(display)) {
304 CARD16 state;
305 DPMSInfo(display, &state, &enabled);
307 return enabled;
310 // static
311 DBusAPI PowerSaveBlocker::Delegate::SelectAPI() {
312 scoped_ptr<base::Environment> env(base::Environment::Create());
313 switch (base::nix::GetDesktopEnvironment(env.get())) {
314 case base::nix::DESKTOP_ENVIRONMENT_GNOME:
315 case base::nix::DESKTOP_ENVIRONMENT_UNITY:
316 if (DPMSEnabled())
317 return GNOME_API;
318 break;
319 case base::nix::DESKTOP_ENVIRONMENT_XFCE:
320 case base::nix::DESKTOP_ENVIRONMENT_KDE4:
321 if (DPMSEnabled())
322 return FREEDESKTOP_API;
323 break;
324 case base::nix::DESKTOP_ENVIRONMENT_KDE3:
325 case base::nix::DESKTOP_ENVIRONMENT_OTHER:
326 // Not supported.
327 break;
329 return NO_API;
332 PowerSaveBlocker::PowerSaveBlocker(
333 PowerSaveBlockerType type, const std::string& reason)
334 : delegate_(new Delegate(type, reason)) {
335 delegate_->Init();
338 PowerSaveBlocker::~PowerSaveBlocker() {
339 delegate_->CleanUp();
342 } // namespace content