• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
4 
5 #include "content/browser/power_save_blocker_impl.h"
6 
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
13 
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"
33 
34 namespace {
35 
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.
40 };
41 
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
50 };
51 
52 const char kGnomeAPIServiceName[] = "org.gnome.SessionManager";
53 const char kGnomeAPIInterfaceName[] = "org.gnome.SessionManager";
54 const char kGnomeAPIObjectPath[] = "/org/gnome/SessionManager";
55 
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";
61 
62 }  // namespace
63 
64 namespace content {
65 
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);
71 
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();
75 
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();
79 
80  private:
81   friend class base::RefCountedThreadSafe<Delegate>;
~Delegate()82   ~Delegate() {}
83 
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();
89 
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);
95 
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();
101 
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();
105 
106   const PowerSaveBlockerType type_;
107   const std::string reason_;
108 
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_;
117 
118   scoped_refptr<dbus::Bus> bus_;
119 
120   // The cookie that identifies our inhibit request,
121   // or 0 if there is no active inhibit request.
122   uint32 inhibit_cookie_;
123 
124   DISALLOW_COPY_AND_ASSIGN(Delegate);
125 };
126 
Delegate(PowerSaveBlockerType type,const std::string & reason)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.
136 }
137 
Init()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));
144 }
145 
CleanUp()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_));
156   }
157 }
158 
InitOnUIThread()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_));
169   }
170   enqueue_apply_ = false;
171 }
172 
ApplyBlock(DBusAPI api)173 void PowerSaveBlockerImpl::Delegate::ApplyBlock(DBusAPI api) {
174   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
175   DCHECK(!bus_.get());  // ApplyBlock() should only be called once.
176 
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);
181 
182   scoped_refptr<dbus::ObjectProxy> object_proxy;
183   scoped_ptr<dbus::MethodCall> method_call;
184   scoped_ptr<dbus::MessageWriter> message_writer;
185 
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_);
206       {
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;
216         }
217         message_writer->AppendUint32(flags);
218       }
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;
234   }
235 
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!";
250   }
251 }
252 
RemoveBlock(DBusAPI api)253 void PowerSaveBlockerImpl::Delegate::RemoveBlock(DBusAPI api) {
254   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
255   DCHECK(bus_.get());  // RemoveBlock() should only be called once.
256 
257   scoped_refptr<dbus::ObjectProxy> object_proxy;
258   scoped_ptr<dbus::MethodCall> method_call;
259 
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;
278   }
279 
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;
289 
290   bus_->ShutdownAndBlock();
291   bus_ = NULL;
292 }
293 
294 // static
DPMSEnabled()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);
302   }
303   return enabled;
304 }
305 
306 // static
SelectAPI()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;
324   }
325   return NO_API;
326 }
327 
PowerSaveBlockerImpl(PowerSaveBlockerType type,const std::string & reason)328 PowerSaveBlockerImpl::PowerSaveBlockerImpl(
329     PowerSaveBlockerType type, const std::string& reason)
330     : delegate_(new Delegate(type, reason)) {
331   delegate_->Init();
332 }
333 
~PowerSaveBlockerImpl()334 PowerSaveBlockerImpl::~PowerSaveBlockerImpl() {
335   delegate_->CleanUp();
336 }
337 
338 }  // namespace content
339