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 #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