- add sources.
[platform/framework/web/crosswalk.git] / src / content / browser / power_save_blocker_x11.cc
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 #if defined(TOOLKIT_GTK)
35 #include "base/message_loop/message_pump_gtk.h"
36 #else
37 #include "base/message_loop/message_pump_x11.h"
38 #endif
39
40 namespace {
41
42 enum DBusAPI {
43   NO_API,           // Disable. No supported API available.
44   GNOME_API,        // Use the GNOME API. (Supports more features.)
45   FREEDESKTOP_API,  // Use the FreeDesktop API, for KDE4 and XFCE.
46 };
47
48 // Inhibit flags defined in the org.gnome.SessionManager interface.
49 // Can be OR'd together and passed as argument to the Inhibit() method
50 // to specify which power management features we want to suspend.
51 enum GnomeAPIInhibitFlags {
52   INHIBIT_LOGOUT            = 1,
53   INHIBIT_SWITCH_USER       = 2,
54   INHIBIT_SUSPEND_SESSION   = 4,
55   INHIBIT_MARK_SESSION_IDLE = 8
56 };
57
58 const char kGnomeAPIServiceName[] = "org.gnome.SessionManager";
59 const char kGnomeAPIInterfaceName[] = "org.gnome.SessionManager";
60 const char kGnomeAPIObjectPath[] = "/org/gnome/SessionManager";
61
62 const char kFreeDesktopAPIServiceName[] = "org.freedesktop.PowerManagement";
63 const char kFreeDesktopAPIInterfaceName[] =
64     "org.freedesktop.PowerManagement.Inhibit";
65 const char kFreeDesktopAPIObjectPath[] =
66     "/org/freedesktop/PowerManagement/Inhibit";
67
68 }  // namespace
69
70 namespace content {
71
72 class PowerSaveBlockerImpl::Delegate
73     : public base::RefCountedThreadSafe<PowerSaveBlockerImpl::Delegate> {
74  public:
75   // Picks an appropriate D-Bus API to use based on the desktop environment.
76   Delegate(PowerSaveBlockerType type, const std::string& reason);
77
78   // Post a task to initialize the delegate on the UI thread, which will itself
79   // then post a task to apply the power save block on the FILE thread.
80   void Init();
81
82   // Post a task to remove the power save block on the FILE thread, unless it
83   // hasn't yet been applied, in which case we just prevent it from applying.
84   void CleanUp();
85
86  private:
87   friend class base::RefCountedThreadSafe<Delegate>;
88   ~Delegate() {}
89
90   // Selects an appropriate D-Bus API to use for this object. Must be called on
91   // the UI thread. Checks enqueue_apply_ once an API has been selected, and
92   // enqueues a call back to ApplyBlock() if it is true. See the comments for
93   // enqueue_apply_ below.
94   void InitOnUIThread();
95
96   // Apply or remove the power save block, respectively. These methods should be
97   // called once each, on the same thread, per instance. They block waiting for
98   // the action to complete (with a timeout); the thread must thus allow I/O.
99   void ApplyBlock(DBusAPI api);
100   void RemoveBlock(DBusAPI api);
101
102   // If DPMS (the power saving system in X11) is not enabled, then we don't want
103   // to try to disable power saving, since on some desktop environments that may
104   // enable DPMS with very poor default settings (e.g. turning off the display
105   // after only 1 second). Must be called on the UI thread.
106   static bool DPMSEnabled();
107
108   // Returns an appropriate D-Bus API to use based on the desktop environment.
109   // Must be called on the UI thread, as it may call DPMSEnabled() above.
110   static DBusAPI SelectAPI();
111
112   const PowerSaveBlockerType type_;
113   const std::string reason_;
114
115   // Initially, we post a message to the UI thread to select an API. When it
116   // finishes, it will post a message to the FILE thread to perform the actual
117   // application of the block, unless enqueue_apply_ is false. We set it to
118   // false when we post that message, or when RemoveBlock() is called before
119   // ApplyBlock() has run. Both api_ and enqueue_apply_ are guarded by lock_.
120   DBusAPI api_;
121   bool enqueue_apply_;
122   base::Lock lock_;
123
124   scoped_refptr<dbus::Bus> bus_;
125
126   // The cookie that identifies our inhibit request,
127   // or 0 if there is no active inhibit request.
128   uint32 inhibit_cookie_;
129
130   DISALLOW_COPY_AND_ASSIGN(Delegate);
131 };
132
133 PowerSaveBlockerImpl::Delegate::Delegate(PowerSaveBlockerType type,
134                                          const std::string& reason)
135     : type_(type),
136       reason_(reason),
137       api_(NO_API),
138       enqueue_apply_(false),
139       inhibit_cookie_(0) {
140   // We're on the client's thread here, so we don't allocate the dbus::Bus
141   // object yet. We'll do it later in ApplyBlock(), on the FILE thread.
142 }
143
144 void PowerSaveBlockerImpl::Delegate::Init() {
145   base::AutoLock lock(lock_);
146   DCHECK(!enqueue_apply_);
147   enqueue_apply_ = true;
148   BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
149                           base::Bind(&Delegate::InitOnUIThread, this));
150 }
151
152 void PowerSaveBlockerImpl::Delegate::CleanUp() {
153   base::AutoLock lock(lock_);
154   if (enqueue_apply_) {
155     // If a call to ApplyBlock() has not yet been enqueued because we are still
156     // initializing on the UI thread, then just cancel it. We don't need to
157     // remove the block because we haven't even applied it yet.
158     enqueue_apply_ = false;
159   } else if (api_ != NO_API) {
160     BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
161                             base::Bind(&Delegate::RemoveBlock, this, api_));
162   }
163 }
164
165 void PowerSaveBlockerImpl::Delegate::InitOnUIThread() {
166   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
167   base::AutoLock lock(lock_);
168   api_ = SelectAPI();
169   if (enqueue_apply_ && api_ != NO_API) {
170     // The thread we use here becomes the origin and D-Bus thread for the D-Bus
171     // library, so we need to use the same thread above for RemoveBlock(). It
172     // must be a thread that allows I/O operations, so we use the FILE thread.
173     BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
174                             base::Bind(&Delegate::ApplyBlock, this, api_));
175   }
176   enqueue_apply_ = false;
177 }
178
179 void PowerSaveBlockerImpl::Delegate::ApplyBlock(DBusAPI api) {
180   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
181   DCHECK(!bus_.get());  // ApplyBlock() should only be called once.
182
183   dbus::Bus::Options options;
184   options.bus_type = dbus::Bus::SESSION;
185   options.connection_type = dbus::Bus::PRIVATE;
186   bus_ = new dbus::Bus(options);
187
188   scoped_refptr<dbus::ObjectProxy> object_proxy;
189   scoped_ptr<dbus::MethodCall> method_call;
190   scoped_ptr<dbus::MessageWriter> message_writer;
191
192   switch (api) {
193     case NO_API:
194       NOTREACHED();  // We should never call this method with this value.
195       return;
196     case GNOME_API:
197       object_proxy = bus_->GetObjectProxy(
198           kGnomeAPIServiceName,
199           dbus::ObjectPath(kGnomeAPIObjectPath));
200       method_call.reset(
201           new dbus::MethodCall(kGnomeAPIInterfaceName, "Inhibit"));
202       message_writer.reset(new dbus::MessageWriter(method_call.get()));
203       // The arguments of the method are:
204       //     app_id:        The application identifier
205       //     toplevel_xid:  The toplevel X window identifier
206       //     reason:        The reason for the inhibit
207       //     flags:         Flags that spefify what should be inhibited
208       message_writer->AppendString(
209           CommandLine::ForCurrentProcess()->GetProgram().value());
210       message_writer->AppendUint32(0);  // should be toplevel_xid
211       message_writer->AppendString(reason_);
212       {
213         uint32 flags = 0;
214         switch (type_) {
215           case kPowerSaveBlockPreventDisplaySleep:
216             flags |= INHIBIT_MARK_SESSION_IDLE;
217             flags |= INHIBIT_SUSPEND_SESSION;
218             break;
219           case kPowerSaveBlockPreventAppSuspension:
220             flags |= INHIBIT_SUSPEND_SESSION;
221             break;
222         }
223         message_writer->AppendUint32(flags);
224       }
225       break;
226     case FREEDESKTOP_API:
227       object_proxy = bus_->GetObjectProxy(
228           kFreeDesktopAPIServiceName,
229           dbus::ObjectPath(kFreeDesktopAPIObjectPath));
230       method_call.reset(
231           new dbus::MethodCall(kFreeDesktopAPIInterfaceName, "Inhibit"));
232       message_writer.reset(new dbus::MessageWriter(method_call.get()));
233       // The arguments of the method are:
234       //     app_id:        The application identifier
235       //     reason:        The reason for the inhibit
236       message_writer->AppendString(
237           CommandLine::ForCurrentProcess()->GetProgram().value());
238       message_writer->AppendString(reason_);
239       break;
240   }
241
242   // We could do this method call asynchronously, but if we did, we'd need to
243   // handle the case where we want to cancel the block before we get a reply.
244   // We're on the FILE thread so it should be OK to block briefly here.
245   scoped_ptr<dbus::Response> response(object_proxy->CallMethodAndBlock(
246       method_call.get(), dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
247   if (response) {
248     // The method returns an inhibit_cookie, used to uniquely identify
249     // this request. It should be used as an argument to Uninhibit()
250     // in order to remove the request.
251     dbus::MessageReader message_reader(response.get());
252     if (!message_reader.PopUint32(&inhibit_cookie_))
253       LOG(ERROR) << "Invalid Inhibit() response: " << response->ToString();
254   } else {
255     LOG(ERROR) << "No response to Inhibit() request!";
256   }
257 }
258
259 void PowerSaveBlockerImpl::Delegate::RemoveBlock(DBusAPI api) {
260   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
261   DCHECK(bus_.get());  // RemoveBlock() should only be called once.
262
263   scoped_refptr<dbus::ObjectProxy> object_proxy;
264   scoped_ptr<dbus::MethodCall> method_call;
265
266   switch (api) {
267     case NO_API:
268       NOTREACHED();  // We should never call this method with this value.
269       return;
270     case GNOME_API:
271       object_proxy = bus_->GetObjectProxy(
272           kGnomeAPIServiceName,
273           dbus::ObjectPath(kGnomeAPIObjectPath));
274       method_call.reset(
275           new dbus::MethodCall(kGnomeAPIInterfaceName, "Uninhibit"));
276       break;
277     case FREEDESKTOP_API:
278       object_proxy = bus_->GetObjectProxy(
279           kFreeDesktopAPIServiceName,
280           dbus::ObjectPath(kFreeDesktopAPIObjectPath));
281       method_call.reset(
282           new dbus::MethodCall(kFreeDesktopAPIInterfaceName, "UnInhibit"));
283       break;
284   }
285
286   dbus::MessageWriter message_writer(method_call.get());
287   message_writer.AppendUint32(inhibit_cookie_);
288   scoped_ptr<dbus::Response> response(object_proxy->CallMethodAndBlock(
289       method_call.get(), dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
290   if (!response)
291     LOG(ERROR) << "No response to Uninhibit() request!";
292   // We don't care about checking the result. We assume it works; we can't
293   // really do anything about it anyway if it fails.
294   inhibit_cookie_ = 0;
295
296   bus_->ShutdownAndBlock();
297   bus_ = NULL;
298 }
299
300 // static
301 bool PowerSaveBlockerImpl::Delegate::DPMSEnabled() {
302   XDisplay* display = base::MessagePumpForUI::GetDefaultXDisplay();
303   BOOL enabled = false;
304   int dummy;
305   if (DPMSQueryExtension(display, &dummy, &dummy) && DPMSCapable(display)) {
306     CARD16 state;
307     DPMSInfo(display, &state, &enabled);
308   }
309   return enabled;
310 }
311
312 // static
313 DBusAPI PowerSaveBlockerImpl::Delegate::SelectAPI() {
314   scoped_ptr<base::Environment> env(base::Environment::Create());
315   switch (base::nix::GetDesktopEnvironment(env.get())) {
316     case base::nix::DESKTOP_ENVIRONMENT_GNOME:
317     case base::nix::DESKTOP_ENVIRONMENT_UNITY:
318       if (DPMSEnabled())
319         return GNOME_API;
320       break;
321     case base::nix::DESKTOP_ENVIRONMENT_XFCE:
322     case base::nix::DESKTOP_ENVIRONMENT_KDE4:
323       if (DPMSEnabled())
324         return FREEDESKTOP_API;
325       break;
326     case base::nix::DESKTOP_ENVIRONMENT_KDE3:
327     case base::nix::DESKTOP_ENVIRONMENT_OTHER:
328       // Not supported.
329       break;
330   }
331   return NO_API;
332 }
333
334 PowerSaveBlockerImpl::PowerSaveBlockerImpl(
335     PowerSaveBlockerType type, const std::string& reason)
336     : delegate_(new Delegate(type, reason)) {
337   delegate_->Init();
338 }
339
340 PowerSaveBlockerImpl::~PowerSaveBlockerImpl() {
341   delegate_->CleanUp();
342 }
343
344 }  // namespace content