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