Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(74)

Side by Side Diff: content/browser/power_save_blocker_linux.cc

Issue 10542089: Power save blocker: switch to new implementation. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src/
Patch Set: Created 8 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 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 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "content/browser/power_save_blocker.h" 5 #include "content/browser/power_save_blocker.h"
6 6
7 #include <X11/Xlib.h> 7 #include <X11/Xlib.h>
8 #include <X11/extensions/dpms.h> 8 #include <X11/extensions/dpms.h>
9 // Xlib #defines Status, but we can't have that for some of our headers. 9 // Xlib #defines Status, but we can't have that for some of our headers.
10 #ifdef Status 10 #ifdef Status
(...skipping 16 matching lines...) Expand all
27 #else 27 #else
28 #include "base/message_pump_aurax11.h" 28 #include "base/message_pump_aurax11.h"
29 #endif 29 #endif
30 #include "base/nix/xdg_util.h" 30 #include "base/nix/xdg_util.h"
31 #include "content/public/browser/browser_thread.h" 31 #include "content/public/browser/browser_thread.h"
32 #include "dbus/bus.h" 32 #include "dbus/bus.h"
33 #include "dbus/message.h" 33 #include "dbus/message.h"
34 #include "dbus/object_path.h" 34 #include "dbus/object_path.h"
35 #include "dbus/object_proxy.h" 35 #include "dbus/object_proxy.h"
36 36
37 using content::BrowserThread;
38
39 namespace {
40
41 // This class is used to inhibit Power Management on Linux systems using D-Bus
42 // interfaces. Mainly, there are two interfaces that make this possible.
43 // org.freedesktop.PowerManagement[.Inhibit] is considered to be the
44 // desktop-agnostic solution. However, it is only used by KDE4 and XFCE.
45 //
46 // org.gnome.SessionManager is the Power Management interface available on GNOME
47 // desktops. Given that there is no generic solution to this problem, this class
48 // delegates the task of calling specific D-Bus APIs, to a
49 // DBusPowerSaveBlock::Delegate object.
50 //
51 // This class is a Singleton and the delegate will be instantiated internally,
52 // when the singleton instance is created, based on the desktop environment in
53 // which the application is running. When the class is instantiated, if it runs
54 // under a supported desktop environment it creates the Bus object and the
55 // delegate. Otherwise, no object is created and the ApplyBlock method will not
56 // do anything.
57 class DBusPowerSaveBlocker {
58 public:
59 // String passed to D-Bus APIs as the reason for which
60 // the power management features are temporarily disabled.
61 static const char kPowerSaveReason[];
62
63 // This delegate interface represents a concrete implementation for a specific
64 // D-Bus interface. It is responsible for obtaining specific object proxies,
65 // making D-Bus method calls and handling D-Bus responses.
66 //
67 // When a new DBusPowerBlocker is created, only a specific implementation of
68 // the delegate is instantiated. See the DBusPowerSaveBlocker constructor for
69 // more details. This is ref_counted to make sure that the callbacks stay
70 // alive even after the DBusPowerSaveBlocker object is deleted.
71 class Delegate : public base::RefCountedThreadSafe<Delegate> {
72 public:
73 Delegate() {}
74 virtual void ApplyBlock(PowerSaveBlocker::PowerSaveBlockerType type) = 0;
75
76 protected:
77 virtual ~Delegate() {}
78
79 private:
80 friend class base::RefCountedThreadSafe<Delegate>;
81
82 DISALLOW_COPY_AND_ASSIGN(Delegate);
83 };
84
85 // Returns a pointer to the sole instance of this class
86 static DBusPowerSaveBlocker* GetInstance();
87
88 // Forwards a power save block request to the concrete implementation of the
89 // Delegate interface. If |delegate_| is NULL, the application runs under an
90 // unsupported desktop environment. In this case, the method does nothing.
91 void ApplyBlock(PowerSaveBlocker::PowerSaveBlockerType type) {
92 if (delegate_)
93 delegate_->ApplyBlock(type);
94 }
95
96 // Getter for the Bus object. Used by the Delegates to obtain object proxies.
97 scoped_refptr<dbus::Bus> bus() const { return bus_; }
98
99 private:
100 DBusPowerSaveBlocker();
101 virtual ~DBusPowerSaveBlocker();
102
103 // If DPMS is not enabled, then we don't want to try to disable power saving,
104 // since on some desktop environments that may enable DPMS with very poor
105 // default settings (e.g. turning off the display after only 1 second).
106 static bool DPMSEnabled();
107
108 // The D-Bus connection.
109 scoped_refptr<dbus::Bus> bus_;
110
111 // Concrete implementation of the Delegate interface.
112 scoped_refptr<Delegate> delegate_;
113
114 friend struct DefaultSingletonTraits<DBusPowerSaveBlocker>;
115
116 DISALLOW_COPY_AND_ASSIGN(DBusPowerSaveBlocker);
117 };
118
119 // Delegate implementation for KDE4. It uses the
120 // org.freedesktop.PowerManagement interface. It works on XFCE4, too.
121 class KDEPowerSaveBlocker : public DBusPowerSaveBlocker::Delegate {
122 public:
123 KDEPowerSaveBlocker()
124 : inhibit_cookie_(0),
125 pending_inhibit_call_(false),
126 postponed_uninhibit_call_(false) {
127 }
128
129 virtual void ApplyBlock(
130 PowerSaveBlocker::PowerSaveBlockerType type) OVERRIDE {
131 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
132 DCHECK(pending_inhibit_call_ || !postponed_uninhibit_call_);
133
134 // If we have a pending inhibit call, we add a postponed uninhibit
135 // request, such that it will be canceled as soon as the response arrives.
136 // If we have an active inhibit request and receive a new one,
137 // we ignore it since the 'freedesktop' interface has only one Inhibit level
138 // and we cannot differentiate between SystemSleep and DisplaySleep,
139 // so there's no need to make additional D-Bus method calls.
140 if (type == PowerSaveBlocker::kPowerSaveBlockPreventNone) {
141 if (pending_inhibit_call_ && postponed_uninhibit_call_) {
142 return;
143 } else if (pending_inhibit_call_ && !postponed_uninhibit_call_) {
144 postponed_uninhibit_call_ = true;
145 return;
146 } else if (!pending_inhibit_call_ && inhibit_cookie_ == 0) {
147 return;
148 }
149 } else if ((pending_inhibit_call_ && !postponed_uninhibit_call_) ||
150 inhibit_cookie_ > 0) {
151 return;
152 }
153
154 scoped_refptr<dbus::ObjectProxy> object_proxy =
155 DBusPowerSaveBlocker::GetInstance()->bus()->GetObjectProxy(
156 "org.freedesktop.PowerManagement",
157 dbus::ObjectPath("/org/freedesktop/PowerManagement/Inhibit"));
158 dbus::MethodCall method_call("org.freedesktop.PowerManagement.Inhibit",
159 "Inhibit");
160 dbus::MessageWriter message_writer(&method_call);
161 base::Callback<void(dbus::Response*)> bus_callback;
162
163 switch (type) {
164 case PowerSaveBlocker::kPowerSaveBlockPreventDisplaySleep:
165 case PowerSaveBlocker::kPowerSaveBlockPreventSystemSleep:
166 // The org.freedesktop.PowerManagement.Inhibit interface offers only one
167 // Inhibit() method, that temporarily disables all power management
168 // features. We cannot differentiate and disable individual features,
169 // like display sleep or system sleep.
170 // The first argument of the Inhibit method is the application name.
171 // The second argument of the Inhibit method is a string containing
172 // the reason of the power save block request.
173 // The method returns a cookie (an int), which we must pass back to the
174 // UnInhibit method when we cancel our request.
175 message_writer.AppendString(
176 CommandLine::ForCurrentProcess()->GetProgram().value());
177 message_writer.AppendString(DBusPowerSaveBlocker::kPowerSaveReason);
178 bus_callback = base::Bind(&KDEPowerSaveBlocker::OnInhibitResponse,
179 this);
180 pending_inhibit_call_ = true;
181 break;
182 case PowerSaveBlocker::kPowerSaveBlockPreventNone:
183 // To cancel our inhibit request, we have to call a different method.
184 // It takes one argument, the cookie returned by the corresponding
185 // Inhibit method call.
186 method_call.SetMember("UnInhibit");
187 message_writer.AppendUint32(inhibit_cookie_);
188 bus_callback = base::Bind(&KDEPowerSaveBlocker::OnUnInhibitResponse,
189 this);
190 break;
191 case PowerSaveBlocker::kPowerSaveBlockPreventStateCount:
192 // This is an invalid argument
193 NOTREACHED();
194 break;
195 }
196
197 object_proxy->CallMethod(&method_call,
198 dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
199 bus_callback);
200 }
201
202 protected:
203 virtual ~KDEPowerSaveBlocker() {}
204
205 private:
206 // Inhibit() response callback.
207 // Stores the cookie so we can use it later when calling UnInhibit().
208 // If the response from D-Bus is successful and there is a postponed
209 // uninhibit request, we cancel the cookie that we just received.
210 void OnInhibitResponse(dbus::Response* response) {
211 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
212 DCHECK(pending_inhibit_call_);
213 pending_inhibit_call_ = false;
214 if (response) {
215 dbus::MessageReader message_reader(response);
216 if (message_reader.PopUint32(&inhibit_cookie_)) {
217 if (postponed_uninhibit_call_) {
218 postponed_uninhibit_call_ = false;
219 ApplyBlock(PowerSaveBlocker::kPowerSaveBlockPreventNone);
220 }
221 return;
222 } else {
223 LOG(ERROR) << "Invalid Inhibit() response: " << response->ToString();
224 }
225 }
226 inhibit_cookie_ = 0;
227 postponed_uninhibit_call_ = false;
228 }
229
230 // UnInhibit() method callback.
231 // We set the |inhibit_cookie_| to 0 even if the D-Bus call failed.
232 void OnUnInhibitResponse(dbus::Response* response) {
233 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
234 inhibit_cookie_ = 0;
235 };
236
237 // The cookie that identifies our last inhibit request,
238 // or 0 if there is no active inhibit request.
239 uint32 inhibit_cookie_;
240
241 // True if we made an inhibit call for which
242 // we did not receive a response yet
243 bool pending_inhibit_call_;
244
245 // True if we have to cancel the cookie we are about to receive
246 bool postponed_uninhibit_call_;
247
248 DISALLOW_COPY_AND_ASSIGN(KDEPowerSaveBlocker);
249 };
250
251 // Delegate implementation for Gnome, based on org.gnome.SessionManager
252 class GnomePowerSaveBlocker : public DBusPowerSaveBlocker::Delegate {
253 public:
254 // Inhibit flags defined in the org.gnome.SessionManager interface.
255 // Can be OR'd together and passed as argument to the Inhibit() method
256 // to specify which power management features we want to suspend.
257 enum InhibitFlags {
258 kInhibitLogOut = 1,
259 kInhibitSwitchUser = 2,
260 kInhibitSuspendSession = 4,
261 kInhibitMarkSessionAsIdle = 8
262 };
263
264 GnomePowerSaveBlocker()
265 : inhibit_cookie_(0),
266 pending_inhibit_calls_(0),
267 postponed_uninhibit_calls_(0) {}
268
269 virtual void ApplyBlock(
270 PowerSaveBlocker::PowerSaveBlockerType type) OVERRIDE {
271 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
272 DCHECK(postponed_uninhibit_calls_ <= pending_inhibit_calls_);
273
274 // If we have a pending inhibit call, we add a postponed uninhibit request,
275 // such that it will be canceled as soon as the response arrives. We want to
276 // cancel the current inhibit request whether |type| is
277 // kPowerSaveBlockPreventNone or not. If |type| represents an inhibit
278 // request, we are dealing with the same case as below, just that the reply
279 // to the previous inhibit request did not arrive yet, so we have to wait
280 // for the cookie in order to cancel it. Meanwhile, we can still make the
281 // new request.
282 //
283 // We also have to check that postponed_uninhibit_calls_ <
284 // pending_inhibit_calls_. If this is not the case, then all the pending
285 // requests were already canceled and we should not increment the number of
286 // postponed uninhibit requests; otherwise we will cancel unwanted future
287 // inhibits, that will be made after this call.
288 //
289 // NOTE: The implementation is based on the fact that we receive the D-Bus
290 // replies in the same order in which the requests are made.
291 if (pending_inhibit_calls_ > 0 &&
292 postponed_uninhibit_calls_ < pending_inhibit_calls_) {
293 ++postponed_uninhibit_calls_;
294 // If the call was an Uninhibit, then we are done for the moment.
295 if (type == PowerSaveBlocker::kPowerSaveBlockPreventNone)
296 return;
297 }
298
299 // If we have an active inhibit request and no pending inhibit calls,
300 // we make an uninhibit request to cancel it now.
301 if (type != PowerSaveBlocker::kPowerSaveBlockPreventNone &&
302 pending_inhibit_calls_ == 0 &&
303 inhibit_cookie_ > 0) {
304 ApplyBlock(PowerSaveBlocker::kPowerSaveBlockPreventNone);
305 }
306
307 static const char kGnomeSessionManagerName[] = "org.gnome.SessionManager";
308 scoped_refptr<dbus::ObjectProxy> object_proxy =
309 DBusPowerSaveBlocker::GetInstance()->bus()->GetObjectProxy(
310 kGnomeSessionManagerName,
311 dbus::ObjectPath("/org/gnome/SessionManager"));
312 dbus::MethodCall method_call(kGnomeSessionManagerName, "Inhibit");
313 dbus::MessageWriter message_writer(&method_call);
314 base::Callback<void(dbus::Response*)> bus_callback;
315
316 unsigned int flags = 0;
317 switch (type) {
318 case PowerSaveBlocker::kPowerSaveBlockPreventDisplaySleep:
319 flags |= kInhibitMarkSessionAsIdle;
320 break;
321 case PowerSaveBlocker::kPowerSaveBlockPreventSystemSleep:
322 flags |= kInhibitMarkSessionAsIdle;
323 flags |= kInhibitSuspendSession;
324 break;
325 case PowerSaveBlocker::kPowerSaveBlockPreventNone:
326 break;
327 case PowerSaveBlocker::kPowerSaveBlockPreventStateCount:
328 // This is an invalid argument
329 NOTREACHED();
330 break;
331 }
332
333 switch (type) {
334 case PowerSaveBlocker::kPowerSaveBlockPreventDisplaySleep:
335 case PowerSaveBlocker::kPowerSaveBlockPreventSystemSleep:
336 // To temporarily suspend the power management features on Gnome,
337 // we call org.gnome.SessionManager.Inhibit().
338 // The arguments of the method are:
339 // app_id: The application identifier
340 // toplevel_xid: The toplevel X window identifier
341 // reason: The reason for the inhibit
342 // flags: Flags that spefify what should be inhibited
343 // The method returns and inhibit_cookie, used to uniquely identify
344 // this request. It should be used as an argument to Uninhibit()
345 // in order to remove the request.
346 message_writer.AppendString(
347 CommandLine::ForCurrentProcess()->GetProgram().value());
348 message_writer.AppendUint32(0); // should be toplevel_xid
349 message_writer.AppendString(DBusPowerSaveBlocker::kPowerSaveReason);
350 message_writer.AppendUint32(flags);
351 bus_callback = base::Bind(&GnomePowerSaveBlocker::OnInhibitResponse,
352 this);
353 ++pending_inhibit_calls_;
354 break;
355 case PowerSaveBlocker::kPowerSaveBlockPreventNone:
356 // To cancel a previous inhibit request we call
357 // org.gnome.SessionManager.Uninhibit().
358 // It takes only one argument, the cookie that identifies
359 // the request we want to cancel.
360 method_call.SetMember("Uninhibit");
361 message_writer.AppendUint32(inhibit_cookie_);
362 bus_callback = base::Bind(&GnomePowerSaveBlocker::OnUnInhibitResponse,
363 this);
364 ++pending_inhibit_calls_;
365 break;
366 case PowerSaveBlocker::kPowerSaveBlockPreventStateCount:
367 // This is an invalid argument.
368 NOTREACHED();
369 break;
370 }
371
372 object_proxy->CallMethod(&method_call,
373 dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
374 bus_callback);
375 }
376
377 protected:
378 virtual ~GnomePowerSaveBlocker() {}
379
380 private:
381 // Inhibit() response callback.
382 // Stores the cookie so we can use it later when calling UnInhibit().
383 // If the response from D-Bus is successful and there is a postponed
384 // uninhibit request, we cancel the cookie that we just received.
385 void OnInhibitResponse(dbus::Response* response) {
386 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
387 DCHECK_GT(pending_inhibit_calls_, 0);
388 --pending_inhibit_calls_;
389 if (response) {
390 dbus::MessageReader message_reader(response);
391 if (message_reader.PopUint32(&inhibit_cookie_)) {
392 if (postponed_uninhibit_calls_ > 0) {
393 --postponed_uninhibit_calls_;
394 ApplyBlock(PowerSaveBlocker::kPowerSaveBlockPreventNone);
395 }
396 return;
397 } else {
398 LOG(ERROR) << "Invalid Inhibit() response: " << response->ToString();
399 }
400 }
401 inhibit_cookie_ = 0;
402 if (postponed_uninhibit_calls_ > 0) {
403 --postponed_uninhibit_calls_;
404 }
405 }
406
407 // Uninhibit() response callback.
408 // We set the |inhibit_cookie_| to 0 even if the D-Bus call failed.
409 void OnUnInhibitResponse(dbus::Response* response) {
410 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
411 inhibit_cookie_ = 0;
412 };
413
414 // The cookie that identifies our last inhibit request,
415 // or 0 if there is no active inhibit request.
416 uint32 inhibit_cookie_;
417
418 // Store the number of inhibit calls for which
419 // we did not receive a response yet
420 int pending_inhibit_calls_;
421
422 // Store the number of Uninhibit requests that arrived,
423 // before the corresponding Inhibit calls were completed.
424 int postponed_uninhibit_calls_;
425
426 DISALLOW_COPY_AND_ASSIGN(GnomePowerSaveBlocker);
427 };
428
429 const char DBusPowerSaveBlocker::kPowerSaveReason[] = "Power Save Blocker";
430
431 // Initialize the DBusPowerSaveBlocker instance:
432 // 1. Instantiate a concrete delegate based on the current desktop environment,
433 // 2. Instantiate the D-Bus object
434 DBusPowerSaveBlocker::DBusPowerSaveBlocker() {
435 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
436
437 scoped_ptr<base::Environment> env(base::Environment::Create());
438 switch (base::nix::GetDesktopEnvironment(env.get())) {
439 case base::nix::DESKTOP_ENVIRONMENT_GNOME:
440 if (DPMSEnabled())
441 delegate_ = new GnomePowerSaveBlocker();
442 break;
443 case base::nix::DESKTOP_ENVIRONMENT_XFCE:
444 case base::nix::DESKTOP_ENVIRONMENT_KDE4:
445 if (DPMSEnabled())
446 delegate_ = new KDEPowerSaveBlocker();
447 break;
448 case base::nix::DESKTOP_ENVIRONMENT_KDE3:
449 case base::nix::DESKTOP_ENVIRONMENT_OTHER:
450 // Not supported, so we exit.
451 // We don't create D-Bus objects.
452 break;
453 }
454
455 if (delegate_) {
456 dbus::Bus::Options options;
457 options.bus_type = dbus::Bus::SESSION;
458 options.connection_type = dbus::Bus::PRIVATE;
459 // Use the FILE thread to service the D-Bus connection,
460 // since we need a thread that allows I/O operations.
461 options.dbus_thread_message_loop_proxy =
462 BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE);
463 bus_ = new dbus::Bus(options);
464 }
465 }
466
467 DBusPowerSaveBlocker::~DBusPowerSaveBlocker() {
468 // We try to shut down the bus, but unfortunately in most of the
469 // cases when we delete the singleton instance,
470 // the FILE thread is already stopped and there is no way to
471 // shutdown the bus object on the origin thread (the UI thread).
472 // However, this is not a crucial problem since at this point
473 // we are at the very end of the shutting down phase.
474 // Connection to D-Bus is just a Unix domain socket, which is not
475 // a persistent resource, hence the operating system will take care
476 // of closing it when the process terminates.
477 if (BrowserThread::IsMessageLoopValid(BrowserThread::FILE)) {
478 bus_->ShutdownOnDBusThreadAndBlock();
479 }
480 }
481
482 // static
483 bool DBusPowerSaveBlocker::DPMSEnabled() {
484 Display* display = base::MessagePumpForUI::GetDefaultXDisplay();
485 BOOL enabled = false;
486 int dummy;
487 if (DPMSQueryExtension(display, &dummy, &dummy) && DPMSCapable(display)) {
488 CARD16 state;
489 DPMSInfo(display, &state, &enabled);
490 }
491 return enabled;
492 }
493
494 // static
495 DBusPowerSaveBlocker* DBusPowerSaveBlocker::GetInstance() {
496 return Singleton<DBusPowerSaveBlocker>::get();
497 }
498
499 } // namespace
500
501 // Called only from UI thread.
502 // static
503 void PowerSaveBlocker::ApplyBlock(PowerSaveBlockerType type) {
504 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
505 DBusPowerSaveBlocker::GetInstance()->ApplyBlock(type);
506 }
507
508 // TODO(mdm): remove from the beginning of the file up to (and including) this
509 // line to switch to the new implementation.
510 #define PowerSaveBlocker PowerSaveBlocker2
511
512 namespace { 37 namespace {
513 38
514 enum DBusAPI { 39 enum DBusAPI {
515 NO_API, // Disable. No supported API available. 40 NO_API, // Disable. No supported API available.
516 GNOME_API, // Use the GNOME API. (Supports more features.) 41 GNOME_API, // Use the GNOME API. (Supports more features.)
517 FREEDESKTOP_API, // Use the FreeDesktop API, for KDE4 and XFCE. 42 FREEDESKTOP_API, // Use the FreeDesktop API, for KDE4 and XFCE.
518 }; 43 };
519 44
520 // Inhibit flags defined in the org.gnome.SessionManager interface. 45 // Inhibit flags defined in the org.gnome.SessionManager interface.
521 // Can be OR'd together and passed as argument to the Inhibit() method 46 // Can be OR'd together and passed as argument to the Inhibit() method
(...skipping 234 matching lines...) Expand 10 before | Expand all | Expand 10 after
756 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, 281 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
757 base::Bind(&Delegate::ApplyBlock, delegate_)); 282 base::Bind(&Delegate::ApplyBlock, delegate_));
758 } 283 }
759 284
760 PowerSaveBlocker::~PowerSaveBlocker() { 285 PowerSaveBlocker::~PowerSaveBlocker() {
761 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, 286 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
762 base::Bind(&Delegate::RemoveBlock, delegate_)); 287 base::Bind(&Delegate::RemoveBlock, delegate_));
763 } 288 }
764 289
765 } // namespace content 290 } // namespace content
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698