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"
8 #include <X11/extensions/dpms.h>
9 // Xlib #defines Status, but we can't have that for some of our headers.
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"
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"
34 #if defined(TOOLKIT_GTK)
35 #include "base/message_loop/message_pump_gtk.h"
37 #include "base/message_loop/message_pump_x11.h"
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.
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
{
53 INHIBIT_SWITCH_USER
= 2,
54 INHIBIT_SUSPEND_SESSION
= 4,
55 INHIBIT_MARK_SESSION_IDLE
= 8
58 const char kGnomeAPIServiceName
[] = "org.gnome.SessionManager";
59 const char kGnomeAPIInterfaceName
[] = "org.gnome.SessionManager";
60 const char kGnomeAPIObjectPath
[] = "/org/gnome/SessionManager";
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";
72 class PowerSaveBlockerImpl::Delegate
73 : public base::RefCountedThreadSafe
<PowerSaveBlockerImpl::Delegate
> {
75 // Picks an appropriate D-Bus API to use based on the desktop environment.
76 Delegate(PowerSaveBlockerType type
, const std::string
& reason
);
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.
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.
87 friend class base::RefCountedThreadSafe
<Delegate
>;
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();
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
);
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();
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();
112 const PowerSaveBlockerType type_
;
113 const std::string reason_
;
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_.
124 scoped_refptr
<dbus::Bus
> bus_
;
126 // The cookie that identifies our inhibit request,
127 // or 0 if there is no active inhibit request.
128 uint32 inhibit_cookie_
;
130 DISALLOW_COPY_AND_ASSIGN(Delegate
);
133 PowerSaveBlockerImpl::Delegate::Delegate(PowerSaveBlockerType type
,
134 const std::string
& reason
)
138 enqueue_apply_(false),
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.
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));
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_
));
165 void PowerSaveBlockerImpl::Delegate::InitOnUIThread() {
166 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
167 base::AutoLock
lock(lock_
);
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_
));
176 enqueue_apply_
= false;
179 void PowerSaveBlockerImpl::Delegate::ApplyBlock(DBusAPI api
) {
180 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
181 DCHECK(!bus_
.get()); // ApplyBlock() should only be called once.
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
);
188 scoped_refptr
<dbus::ObjectProxy
> object_proxy
;
189 scoped_ptr
<dbus::MethodCall
> method_call
;
190 scoped_ptr
<dbus::MessageWriter
> message_writer
;
194 NOTREACHED(); // We should never call this method with this value.
197 object_proxy
= bus_
->GetObjectProxy(
198 kGnomeAPIServiceName
,
199 dbus::ObjectPath(kGnomeAPIObjectPath
));
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_
);
215 case kPowerSaveBlockPreventDisplaySleep
:
216 flags
|= INHIBIT_MARK_SESSION_IDLE
;
217 flags
|= INHIBIT_SUSPEND_SESSION
;
219 case kPowerSaveBlockPreventAppSuspension
:
220 flags
|= INHIBIT_SUSPEND_SESSION
;
223 message_writer
->AppendUint32(flags
);
226 case FREEDESKTOP_API
:
227 object_proxy
= bus_
->GetObjectProxy(
228 kFreeDesktopAPIServiceName
,
229 dbus::ObjectPath(kFreeDesktopAPIObjectPath
));
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_
);
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
));
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();
255 LOG(ERROR
) << "No response to Inhibit() request!";
259 void PowerSaveBlockerImpl::Delegate::RemoveBlock(DBusAPI api
) {
260 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
261 DCHECK(bus_
.get()); // RemoveBlock() should only be called once.
263 scoped_refptr
<dbus::ObjectProxy
> object_proxy
;
264 scoped_ptr
<dbus::MethodCall
> method_call
;
268 NOTREACHED(); // We should never call this method with this value.
271 object_proxy
= bus_
->GetObjectProxy(
272 kGnomeAPIServiceName
,
273 dbus::ObjectPath(kGnomeAPIObjectPath
));
275 new dbus::MethodCall(kGnomeAPIInterfaceName
, "Uninhibit"));
277 case FREEDESKTOP_API
:
278 object_proxy
= bus_
->GetObjectProxy(
279 kFreeDesktopAPIServiceName
,
280 dbus::ObjectPath(kFreeDesktopAPIObjectPath
));
282 new dbus::MethodCall(kFreeDesktopAPIInterfaceName
, "UnInhibit"));
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
));
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.
296 bus_
->ShutdownAndBlock();
301 bool PowerSaveBlockerImpl::Delegate::DPMSEnabled() {
302 XDisplay
* display
= base::MessagePumpForUI::GetDefaultXDisplay();
303 BOOL enabled
= false;
305 if (DPMSQueryExtension(display
, &dummy
, &dummy
) && DPMSCapable(display
)) {
307 DPMSInfo(display
, &state
, &enabled
);
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
:
321 case base::nix::DESKTOP_ENVIRONMENT_XFCE
:
322 case base::nix::DESKTOP_ENVIRONMENT_KDE4
:
324 return FREEDESKTOP_API
;
326 case base::nix::DESKTOP_ENVIRONMENT_KDE3
:
327 case base::nix::DESKTOP_ENVIRONMENT_OTHER
:
334 PowerSaveBlockerImpl::PowerSaveBlockerImpl(
335 PowerSaveBlockerType type
, const std::string
& reason
)
336 : delegate_(new Delegate(type
, reason
)) {
340 PowerSaveBlockerImpl::~PowerSaveBlockerImpl() {
341 delegate_
->CleanUp();
344 } // namespace content