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 345