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

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

Issue 2075153002: Reland of 'Move content/browser/power_save_blocker to //device/power_save_blocker' (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: missing libs for component mac Created 4 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
OLDNEW
(Empty)
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 <X11/Xlib.h>
6 #include <X11/extensions/dpms.h>
7 #include <X11/extensions/scrnsaver.h>
8 #include <stdint.h>
9
10 #include <memory>
11
12 #include "content/browser/power_save_blocker_impl.h"
13 // Xlib #defines Status, but we can't have that for some of our headers.
14 #ifdef Status
15 #undef Status
16 #endif
17
18 #include "base/bind.h"
19 #include "base/callback.h"
20 #include "base/command_line.h"
21 #include "base/environment.h"
22 #include "base/files/file_path.h"
23 #include "base/location.h"
24 #include "base/logging.h"
25 #include "base/macros.h"
26 #include "base/memory/ref_counted.h"
27 #include "base/memory/singleton.h"
28 #include "base/nix/xdg_util.h"
29 #include "base/synchronization/lock.h"
30 #include "dbus/bus.h"
31 #include "dbus/message.h"
32 #include "dbus/object_path.h"
33 #include "dbus/object_proxy.h"
34 #include "ui/gfx/x/x11_types.h"
35
36 namespace {
37
38 enum DBusAPI {
39 NO_API, // Disable. No supported API available.
40 GNOME_API, // Use the GNOME API. (Supports more features.)
41 FREEDESKTOP_API, // Use the FreeDesktop API, for KDE4, KDE5, and XFCE.
42 };
43
44 // Inhibit flags defined in the org.gnome.SessionManager interface.
45 // Can be OR'd together and passed as argument to the Inhibit() method
46 // to specify which power management features we want to suspend.
47 enum GnomeAPIInhibitFlags {
48 INHIBIT_LOGOUT = 1,
49 INHIBIT_SWITCH_USER = 2,
50 INHIBIT_SUSPEND_SESSION = 4,
51 INHIBIT_MARK_SESSION_IDLE = 8
52 };
53
54 const char kGnomeAPIServiceName[] = "org.gnome.SessionManager";
55 const char kGnomeAPIInterfaceName[] = "org.gnome.SessionManager";
56 const char kGnomeAPIObjectPath[] = "/org/gnome/SessionManager";
57
58 const char kFreeDesktopAPIPowerServiceName[] =
59 "org.freedesktop.PowerManagement";
60 const char kFreeDesktopAPIPowerInterfaceName[] =
61 "org.freedesktop.PowerManagement.Inhibit";
62 const char kFreeDesktopAPIPowerObjectPath[] =
63 "/org/freedesktop/PowerManagement/Inhibit";
64
65 const char kFreeDesktopAPIScreenServiceName[] = "org.freedesktop.ScreenSaver";
66 const char kFreeDesktopAPIScreenInterfaceName[] = "org.freedesktop.ScreenSaver";
67 const char kFreeDesktopAPIScreenObjectPath[] = "/org/freedesktop/ScreenSaver";
68
69 } // namespace
70
71 namespace content {
72
73 class PowerSaveBlockerImpl::Delegate
74 : public base::RefCountedThreadSafe<PowerSaveBlockerImpl::Delegate> {
75 public:
76 // Picks an appropriate D-Bus API to use based on the desktop environment.
77 Delegate(PowerSaveBlockerType type,
78 const std::string& description,
79 bool freedesktop_only,
80 scoped_refptr<base::SequencedTaskRunner> ui_task_runner,
81 scoped_refptr<base::SingleThreadTaskRunner> blocking_task_runner);
82
83 // Post a task to initialize the delegate on the UI thread, which will itself
84 // then post a task to apply the power save block on the FILE thread.
85 void Init();
86
87 // Post a task to remove the power save block on the FILE thread, unless it
88 // hasn't yet been applied, in which case we just prevent it from applying.
89 void CleanUp();
90
91 private:
92 friend class base::RefCountedThreadSafe<Delegate>;
93 ~Delegate() {}
94
95 // Selects an appropriate D-Bus API to use for this object. Must be called on
96 // the UI thread. Checks enqueue_apply_ once an API has been selected, and
97 // enqueues a call back to ApplyBlock() if it is true. See the comments for
98 // enqueue_apply_ below.
99 void InitOnUIThread();
100
101 // Returns true if ApplyBlock() / RemoveBlock() should be called.
102 bool ShouldBlock() const;
103
104 // Apply or remove the power save block, respectively. These methods should be
105 // called once each, on the same thread, per instance. They block waiting for
106 // the action to complete (with a timeout); the thread must thus allow I/O.
107 void ApplyBlock();
108 void RemoveBlock();
109
110 // Asynchronous callback functions for ApplyBlock and RemoveBlock.
111 // Functions do not receive ownership of |response|.
112 void ApplyBlockFinished(dbus::Response* response);
113 void RemoveBlockFinished(dbus::Response* response);
114
115 // Wrapper for XScreenSaverSuspend. Checks whether the X11 Screen Saver
116 // Extension is available first. If it isn't, this is a no-op.
117 // Must be called on the UI thread.
118 void XSSSuspendSet(bool suspend);
119
120 // If DPMS (the power saving system in X11) is not enabled, then we don't want
121 // to try to disable power saving, since on some desktop environments that may
122 // enable DPMS with very poor default settings (e.g. turning off the display
123 // after only 1 second). Must be called on the UI thread.
124 bool DPMSEnabled();
125
126 // If no other method is available (i.e. not running under a Desktop
127 // Environment) check whether the X11 Screen Saver Extension can be used
128 // to disable the screen saver. Must be called on the UI thread.
129 bool XSSAvailable();
130
131 // Returns an appropriate D-Bus API to use based on the desktop environment.
132 // Must be called on the UI thread, as it may call DPMSEnabled() above.
133 DBusAPI SelectAPI();
134
135 const PowerSaveBlockerType type_;
136 const std::string description_;
137 const bool freedesktop_only_;
138
139 // Initially, we post a message to the UI thread to select an API. When it
140 // finishes, it will post a message to the FILE thread to perform the actual
141 // application of the block, unless enqueue_apply_ is false. We set it to
142 // false when we post that message, or when RemoveBlock() is called before
143 // ApplyBlock() has run. Both api_ and enqueue_apply_ are guarded by lock_.
144 DBusAPI api_;
145 bool enqueue_apply_;
146 base::Lock lock_;
147
148 // Indicates that a D-Bus power save blocking request is in flight.
149 bool block_inflight_;
150 // Used to detect erronous redundant calls to RemoveBlock().
151 bool unblock_inflight_;
152 // Indicates that RemoveBlock() is called before ApplyBlock() has finished.
153 // If it's true, then the RemoveBlock() call will be processed immediately
154 // after ApplyBlock() has finished.
155 bool enqueue_unblock_;
156
157 scoped_refptr<dbus::Bus> bus_;
158
159 // The cookie that identifies our inhibit request,
160 // or 0 if there is no active inhibit request.
161 uint32_t inhibit_cookie_;
162
163 scoped_refptr<base::SequencedTaskRunner> ui_task_runner_;
164 scoped_refptr<base::SingleThreadTaskRunner> blocking_task_runner_;
165
166 DISALLOW_COPY_AND_ASSIGN(Delegate);
167 };
168
169 PowerSaveBlockerImpl::Delegate::Delegate(
170 PowerSaveBlockerType type,
171 const std::string& description,
172 bool freedesktop_only,
173 scoped_refptr<base::SequencedTaskRunner> ui_task_runner,
174 scoped_refptr<base::SingleThreadTaskRunner> blocking_task_runner)
175 : type_(type),
176 description_(description),
177 freedesktop_only_(freedesktop_only),
178 api_(NO_API),
179 enqueue_apply_(false),
180 inhibit_cookie_(0),
181 ui_task_runner_(ui_task_runner),
182 blocking_task_runner_(blocking_task_runner) {
183 // We're on the client's thread here, so we don't allocate the dbus::Bus
184 // object yet. We'll do it later in ApplyBlock(), on the FILE thread.
185 }
186
187 void PowerSaveBlockerImpl::Delegate::Init() {
188 base::AutoLock lock(lock_);
189 DCHECK(!enqueue_apply_);
190 enqueue_apply_ = true;
191 block_inflight_ = false;
192 unblock_inflight_ = false;
193 enqueue_unblock_ = false;
194 ui_task_runner_->PostTask(FROM_HERE,
195 base::Bind(&Delegate::InitOnUIThread, this));
196 }
197
198 void PowerSaveBlockerImpl::Delegate::CleanUp() {
199 base::AutoLock lock(lock_);
200 if (enqueue_apply_) {
201 // If a call to ApplyBlock() has not yet been enqueued because we are still
202 // initializing on the UI thread, then just cancel it. We don't need to
203 // remove the block because we haven't even applied it yet.
204 enqueue_apply_ = false;
205 } else {
206 if (ShouldBlock()) {
207 blocking_task_runner_->PostTask(FROM_HERE,
208 base::Bind(&Delegate::RemoveBlock, this));
209 }
210
211 ui_task_runner_->PostTask(
212 FROM_HERE, base::Bind(&Delegate::XSSSuspendSet, this, false));
213 }
214 }
215
216 void PowerSaveBlockerImpl::Delegate::InitOnUIThread() {
217 DCHECK(ui_task_runner_->RunsTasksOnCurrentThread());
218 base::AutoLock lock(lock_);
219 api_ = SelectAPI();
220
221 if (enqueue_apply_) {
222 if (ShouldBlock()) {
223 // The thread we use here becomes the origin and D-Bus thread for the
224 // D-Bus library, so we need to use the same thread above for
225 // RemoveBlock(). It must be a thread that allows I/O operations, so we
226 // use the FILE thread.
227 blocking_task_runner_->PostTask(FROM_HERE,
228 base::Bind(&Delegate::ApplyBlock, this));
229 }
230 XSSSuspendSet(true);
231 }
232 enqueue_apply_ = false;
233 }
234
235 bool PowerSaveBlockerImpl::Delegate::ShouldBlock() const {
236 return freedesktop_only_ ? api_ == FREEDESKTOP_API : api_ != NO_API;
237 }
238
239 void PowerSaveBlockerImpl::Delegate::ApplyBlock() {
240 DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
241 DCHECK(!bus_); // ApplyBlock() should only be called once.
242 DCHECK(!block_inflight_);
243
244 dbus::Bus::Options options;
245 options.bus_type = dbus::Bus::SESSION;
246 options.connection_type = dbus::Bus::PRIVATE;
247 bus_ = new dbus::Bus(options);
248
249 scoped_refptr<dbus::ObjectProxy> object_proxy;
250 std::unique_ptr<dbus::MethodCall> method_call;
251 std::unique_ptr<dbus::MessageWriter> message_writer;
252
253 switch (api_) {
254 case NO_API:
255 NOTREACHED(); // We should never call this method with this value.
256 return;
257 case GNOME_API:
258 object_proxy = bus_->GetObjectProxy(
259 kGnomeAPIServiceName,
260 dbus::ObjectPath(kGnomeAPIObjectPath));
261 method_call.reset(
262 new dbus::MethodCall(kGnomeAPIInterfaceName, "Inhibit"));
263 message_writer.reset(new dbus::MessageWriter(method_call.get()));
264 // The arguments of the method are:
265 // app_id: The application identifier
266 // toplevel_xid: The toplevel X window identifier
267 // reason: The reason for the inhibit
268 // flags: Flags that spefify what should be inhibited
269 message_writer->AppendString(
270 base::CommandLine::ForCurrentProcess()->GetProgram().value());
271 message_writer->AppendUint32(0); // should be toplevel_xid
272 message_writer->AppendString(description_);
273 {
274 uint32_t flags = 0;
275 switch (type_) {
276 case kPowerSaveBlockPreventDisplaySleep:
277 flags |= INHIBIT_MARK_SESSION_IDLE;
278 flags |= INHIBIT_SUSPEND_SESSION;
279 break;
280 case kPowerSaveBlockPreventAppSuspension:
281 flags |= INHIBIT_SUSPEND_SESSION;
282 break;
283 }
284 message_writer->AppendUint32(flags);
285 }
286 break;
287 case FREEDESKTOP_API:
288 switch (type_) {
289 case kPowerSaveBlockPreventDisplaySleep:
290 object_proxy = bus_->GetObjectProxy(
291 kFreeDesktopAPIScreenServiceName,
292 dbus::ObjectPath(kFreeDesktopAPIScreenObjectPath));
293 method_call.reset(new dbus::MethodCall(
294 kFreeDesktopAPIScreenInterfaceName, "Inhibit"));
295 break;
296 case kPowerSaveBlockPreventAppSuspension:
297 object_proxy = bus_->GetObjectProxy(
298 kFreeDesktopAPIPowerServiceName,
299 dbus::ObjectPath(kFreeDesktopAPIPowerObjectPath));
300 method_call.reset(new dbus::MethodCall(
301 kFreeDesktopAPIPowerInterfaceName, "Inhibit"));
302 break;
303 }
304 message_writer.reset(new dbus::MessageWriter(method_call.get()));
305 // The arguments of the method are:
306 // app_id: The application identifier
307 // reason: The reason for the inhibit
308 message_writer->AppendString(
309 base::CommandLine::ForCurrentProcess()->GetProgram().value());
310 message_writer->AppendString(description_);
311 break;
312 }
313
314 block_inflight_ = true;
315 object_proxy->CallMethod(
316 method_call.get(), dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
317 base::Bind(&PowerSaveBlockerImpl::Delegate::ApplyBlockFinished, this));
318 }
319
320 void PowerSaveBlockerImpl::Delegate::ApplyBlockFinished(
321 dbus::Response* response) {
322 DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
323 DCHECK(bus_);
324 DCHECK(block_inflight_);
325 block_inflight_ = false;
326
327 if (response) {
328 // The method returns an inhibit_cookie, used to uniquely identify
329 // this request. It should be used as an argument to Uninhibit()
330 // in order to remove the request.
331 dbus::MessageReader message_reader(response);
332 if (!message_reader.PopUint32(&inhibit_cookie_))
333 LOG(ERROR) << "Invalid Inhibit() response: " << response->ToString();
334 } else {
335 LOG(ERROR) << "No response to Inhibit() request!";
336 }
337
338 if (enqueue_unblock_) {
339 enqueue_unblock_ = false;
340 // RemoveBlock() was called while the Inhibit operation was in flight,
341 // so go ahead and remove the block now.
342 blocking_task_runner_->PostTask(FROM_HERE,
343 base::Bind(&Delegate::RemoveBlock, this));
344 }
345 }
346
347 void PowerSaveBlockerImpl::Delegate::RemoveBlock() {
348 DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
349 DCHECK(bus_); // RemoveBlock() should only be called once.
350 DCHECK(!unblock_inflight_);
351
352 if (block_inflight_) {
353 DCHECK(!enqueue_unblock_);
354 // Can't call RemoveBlock until ApplyBlock's async operation has
355 // finished. Enqueue it for execution once ApplyBlock is done.
356 enqueue_unblock_ = true;
357 return;
358 }
359
360 scoped_refptr<dbus::ObjectProxy> object_proxy;
361 std::unique_ptr<dbus::MethodCall> method_call;
362
363 switch (api_) {
364 case NO_API:
365 NOTREACHED(); // We should never call this method with this value.
366 return;
367 case GNOME_API:
368 object_proxy = bus_->GetObjectProxy(
369 kGnomeAPIServiceName,
370 dbus::ObjectPath(kGnomeAPIObjectPath));
371 method_call.reset(
372 new dbus::MethodCall(kGnomeAPIInterfaceName, "Uninhibit"));
373 break;
374 case FREEDESKTOP_API:
375 switch (type_) {
376 case kPowerSaveBlockPreventDisplaySleep:
377 object_proxy = bus_->GetObjectProxy(
378 kFreeDesktopAPIScreenServiceName,
379 dbus::ObjectPath(kFreeDesktopAPIScreenObjectPath));
380 method_call.reset(new dbus::MethodCall(
381 kFreeDesktopAPIScreenInterfaceName, "UnInhibit"));
382 break;
383 case kPowerSaveBlockPreventAppSuspension:
384 object_proxy = bus_->GetObjectProxy(
385 kFreeDesktopAPIPowerServiceName,
386 dbus::ObjectPath(kFreeDesktopAPIPowerObjectPath));
387 method_call.reset(new dbus::MethodCall(
388 kFreeDesktopAPIPowerInterfaceName, "UnInhibit"));
389 break;
390 }
391 break;
392 }
393
394 dbus::MessageWriter message_writer(method_call.get());
395 message_writer.AppendUint32(inhibit_cookie_);
396 unblock_inflight_ = true;
397 object_proxy->CallMethod(
398 method_call.get(), dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
399 base::Bind(&PowerSaveBlockerImpl::Delegate::RemoveBlockFinished, this));
400 }
401
402 void PowerSaveBlockerImpl::Delegate::RemoveBlockFinished(
403 dbus::Response* response) {
404 DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
405 DCHECK(bus_);
406 unblock_inflight_ = false;
407
408 if (!response)
409 LOG(ERROR) << "No response to Uninhibit() request!";
410 // We don't care about checking the result. We assume it works; we can't
411 // really do anything about it anyway if it fails.
412 inhibit_cookie_ = 0;
413
414 bus_->ShutdownAndBlock();
415 bus_ = nullptr;
416 }
417
418 void PowerSaveBlockerImpl::Delegate::XSSSuspendSet(bool suspend) {
419 DCHECK(ui_task_runner_->RunsTasksOnCurrentThread());
420
421 if (!XSSAvailable())
422 return;
423
424 XDisplay* display = gfx::GetXDisplay();
425 XScreenSaverSuspend(display, suspend);
426 }
427
428 bool PowerSaveBlockerImpl::Delegate::DPMSEnabled() {
429 DCHECK(ui_task_runner_->RunsTasksOnCurrentThread());
430 XDisplay* display = gfx::GetXDisplay();
431 BOOL enabled = false;
432 int dummy;
433 if (DPMSQueryExtension(display, &dummy, &dummy) && DPMSCapable(display)) {
434 CARD16 state;
435 DPMSInfo(display, &state, &enabled);
436 }
437 return enabled;
438 }
439
440 bool PowerSaveBlockerImpl::Delegate::XSSAvailable() {
441 DCHECK(ui_task_runner_->RunsTasksOnCurrentThread());
442 XDisplay* display = gfx::GetXDisplay();
443 int dummy;
444 int major;
445 int minor;
446
447 if (!XScreenSaverQueryExtension(display, &dummy, &dummy))
448 return false;
449
450 if (!XScreenSaverQueryVersion(display, &major, &minor))
451 return false;
452
453 return major > 1 || (major == 1 && minor >= 1);
454 }
455
456 DBusAPI PowerSaveBlockerImpl::Delegate::SelectAPI() {
457 DCHECK(ui_task_runner_->RunsTasksOnCurrentThread());
458 std::unique_ptr<base::Environment> env(base::Environment::Create());
459 switch (base::nix::GetDesktopEnvironment(env.get())) {
460 case base::nix::DESKTOP_ENVIRONMENT_GNOME:
461 case base::nix::DESKTOP_ENVIRONMENT_UNITY:
462 if (DPMSEnabled())
463 return GNOME_API;
464 break;
465 case base::nix::DESKTOP_ENVIRONMENT_XFCE:
466 case base::nix::DESKTOP_ENVIRONMENT_KDE4:
467 case base::nix::DESKTOP_ENVIRONMENT_KDE5:
468 if (DPMSEnabled())
469 return FREEDESKTOP_API;
470 break;
471 case base::nix::DESKTOP_ENVIRONMENT_KDE3:
472 case base::nix::DESKTOP_ENVIRONMENT_OTHER:
473 // Not supported.
474 break;
475 }
476 return NO_API;
477 }
478
479 PowerSaveBlockerImpl::PowerSaveBlockerImpl(
480 PowerSaveBlockerType type,
481 Reason reason,
482 const std::string& description,
483 scoped_refptr<base::SequencedTaskRunner> ui_task_runner,
484 scoped_refptr<base::SingleThreadTaskRunner> blocking_task_runner)
485 : delegate_(new Delegate(type,
486 description,
487 false /* freedesktop_only */,
488 ui_task_runner,
489 blocking_task_runner)),
490 ui_task_runner_(ui_task_runner),
491 blocking_task_runner_(blocking_task_runner) {
492 delegate_->Init();
493
494 if (type == kPowerSaveBlockPreventDisplaySleep) {
495 freedesktop_suspend_delegate_ = new Delegate(
496 kPowerSaveBlockPreventAppSuspension, description,
497 true /* freedesktop_only */, ui_task_runner, blocking_task_runner);
498 freedesktop_suspend_delegate_->Init();
499 }
500 }
501
502 PowerSaveBlockerImpl::~PowerSaveBlockerImpl() {
503 delegate_->CleanUp();
504 if (freedesktop_suspend_delegate_)
505 freedesktop_suspend_delegate_->CleanUp();
506 }
507
508 } // namespace content
OLDNEW
« no previous file with comments | « content/browser/power_save_blocker_win.cc ('k') | content/browser/renderer_host/render_widget_host_impl.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698