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>; 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 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 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 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 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 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 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 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 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 328 PowerSaveBlockerImpl::PowerSaveBlockerImpl( 329 PowerSaveBlockerType type, const std::string& reason) 330 : delegate_(new Delegate(type, reason)) { 331 delegate_->Init(); 332 } 333 334 PowerSaveBlockerImpl::~PowerSaveBlockerImpl() { 335 delegate_->CleanUp(); 336 } 337 338 } // namespace content 339