Home | History | Annotate | Download | only in browser
      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