Browse Source

feat(powerMonitor): expose interface to query system idle state (#11807)

* feat(BrowserWindow): expose interface to query system idle state

* test(BrowserWindow): update test cases for querySystemIdle interface

* docs(BrowserWindow): add querySystemIdle interface documentation

* refactor(powerMonitor): move querySystemIdle into powerMonitor

* test(powerMonitor): split test cases for all platform
OJ Kwon 7 years ago
parent
commit
e7181eb89c

+ 44 - 3
atom/browser/api/atom_api_power_monitor.cc

@@ -5,12 +5,33 @@
 #include "atom/browser/api/atom_api_power_monitor.h"
 
 #include "atom/browser/browser.h"
+#include "atom/common/native_mate_converters/callback.h"
 #include "base/power_monitor/power_monitor.h"
 #include "base/power_monitor/power_monitor_device_source.h"
 #include "native_mate/dictionary.h"
 
 #include "atom/common/node_includes.h"
 
+namespace mate {
+template<>
+struct Converter<ui::IdleState> {
+  static v8::Local<v8::Value> ToV8(v8::Isolate* isolate,
+                                   const ui::IdleState& in) {
+    switch (in) {
+      case ui::IDLE_STATE_ACTIVE:
+        return mate::StringToV8(isolate, "active");
+      case ui::IDLE_STATE_IDLE:
+        return mate::StringToV8(isolate, "idle");
+      case ui::IDLE_STATE_LOCKED:
+        return mate::StringToV8(isolate, "locked");
+      case ui::IDLE_STATE_UNKNOWN:
+      default:
+        return mate::StringToV8(isolate, "unknown");
+    }
+  }
+};
+}  // namespace mate
+
 namespace atom {
 
 namespace api {
@@ -60,6 +81,22 @@ void PowerMonitor::OnResume() {
   Emit("resume");
 }
 
+void PowerMonitor::QuerySystemIdleState(v8::Isolate* isolate,
+                                        int idle_threshold,
+                                        const ui::IdleCallback& callback) {
+  if (idle_threshold > 0) {
+    ui::CalculateIdleState(idle_threshold, callback);
+  } else {
+    isolate->ThrowException(v8::Exception::TypeError(
+        mate::StringToV8(isolate,
+          "Invalid idle threshold, must be greater than 0")));
+  }
+}
+
+void PowerMonitor::QuerySystemIdleTime(const ui::IdleTimeCallback& callback) {
+  ui::CalculateIdleTime(callback);
+}
+
 // static
 v8::Local<v8::Value> PowerMonitor::Create(v8::Isolate* isolate) {
   if (!Browser::Get()->is_ready()) {
@@ -76,11 +113,15 @@ v8::Local<v8::Value> PowerMonitor::Create(v8::Isolate* isolate) {
 void PowerMonitor::BuildPrototype(
     v8::Isolate* isolate, v8::Local<v8::FunctionTemplate> prototype) {
   prototype->SetClassName(mate::StringToV8(isolate, "PowerMonitor"));
-#if defined(OS_LINUX)
+
   mate::ObjectTemplateBuilder(isolate, prototype->PrototypeTemplate())
-      .SetMethod("blockShutdown", &PowerMonitor::BlockShutdown)
-      .SetMethod("unblockShutdown", &PowerMonitor::UnblockShutdown);
+        .MakeDestroyable()
+#if defined(OS_LINUX)
+        .SetMethod("blockShutdown", &PowerMonitor::BlockShutdown)
+        .SetMethod("unblockShutdown", &PowerMonitor::UnblockShutdown)
 #endif
+        .SetMethod("querySystemIdleState", &PowerMonitor::QuerySystemIdleState)
+        .SetMethod("querySystemIdleTime", &PowerMonitor::QuerySystemIdleTime);
 }
 
 }  // namespace api

+ 6 - 0
atom/browser/api/atom_api_power_monitor.h

@@ -9,6 +9,7 @@
 #include "atom/browser/lib/power_observer.h"
 #include "base/compiler_specific.h"
 #include "native_mate/handle.h"
+#include "ui/base/idle/idle.h"
 
 namespace atom {
 
@@ -41,6 +42,11 @@ class PowerMonitor : public mate::TrackableObject<PowerMonitor>,
   void OnResume() override;
 
  private:
+  void QuerySystemIdleState(v8::Isolate* isolate,
+                            int idle_threshold,
+                            const ui::IdleCallback& callback);
+  void QuerySystemIdleTime(const ui::IdleTimeCallback& callback);
+
   DISALLOW_COPY_AND_ASSIGN(PowerMonitor);
 };
 

+ 6 - 0
atom/browser/atom_browser_main_parts.cc

@@ -24,6 +24,7 @@
 #include "content/public/browser/child_process_security_policy.h"
 #include "device/geolocation/geolocation_delegate.h"
 #include "device/geolocation/geolocation_provider.h"
+#include "ui/base/idle/idle.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "v8/include/v8-debug.h"
 
@@ -159,6 +160,11 @@ int AtomBrowserMainParts::PreCreateThreads() {
     fake_browser_process_->SetApplicationLocale(
         brightray::BrowserClient::Get()->GetApplicationLocale());
   }
+
+  #if defined(OS_MACOSX)
+    ui::InitIdleMonitor();
+  #endif
+
   return result;
 }
 

+ 22 - 0
docs/api/power-monitor.md

@@ -46,3 +46,25 @@ Emitted when the system is about to reboot or shut down. If the event handler
 invokes `e.preventDefault()`, Electron will attempt to delay system shutdown in
 order for the app to exit cleanly. If `e.preventDefault()` is called, the app
 should exit as soon as possible by calling something like `app.quit()`.
+
+## Methods
+
+The `powerMonitor` module has the following methods:
+
+#### `powerMonitor.querySystemIdleState(idleThreshold, callback)`
+
+* `idleThreshold` Integer
+* `callback` Function
+  * `idleState` String - Can be `active`, `idle`, `locked` or `unknown`
+
+Calculate the system idle state. `idleThreshold` is the amount of time (in seconds)
+before considered idle. `callback` will be called synchronously on some systems
+and with an `idleState` argument that describes the system's state. `locked` is
+available on supported systems only.
+
+#### `powerMonitor.querySystemIdleTime(callback)`
+
+* `callback` Function
+  * `idleTime` Integer - Idle time in seconds
+
+Calculate system idle time in seconds.

+ 67 - 23
spec/api-power-monitor-spec.js

@@ -10,26 +10,28 @@ const assert = require('assert')
 const dbus = require('dbus-native')
 const Promise = require('bluebird')
 
-const skip = process.platform !== 'linux' || !process.env.DBUS_SYSTEM_BUS_ADDRESS;
-
-(skip ? describe.skip : describe)('powerMonitor', () => {
-  let logindMock, powerMonitor, getCalls, emitSignal, reset
-
-  before(async () => {
-    const systemBus = dbus.systemBus()
-    const loginService = systemBus.getService('org.freedesktop.login1')
-    const getInterface = Promise.promisify(loginService.getInterface, {context: loginService})
-    logindMock = await getInterface('/org/freedesktop/login1', 'org.freedesktop.DBus.Mock')
-    getCalls = Promise.promisify(logindMock.GetCalls, {context: logindMock})
-    emitSignal = Promise.promisify(logindMock.EmitSignal, {context: logindMock})
-    reset = Promise.promisify(logindMock.Reset, {context: logindMock})
-  })
+const skip = process.platform !== 'linux' || !process.env.DBUS_SYSTEM_BUS_ADDRESS
 
-  after(async () => {
-    await reset()
-  })
+describe('powerMonitor', () => {
+  let logindMock, dbusMockPowerMonitor, getCalls, emitSignal, reset
 
-  describe('when powerMonitor module is loaded', () => {
+  if (!skip) {
+    before(async () => {
+      const systemBus = dbus.systemBus()
+      const loginService = systemBus.getService('org.freedesktop.login1')
+      const getInterface = Promise.promisify(loginService.getInterface, {context: loginService})
+      logindMock = await getInterface('/org/freedesktop/login1', 'org.freedesktop.DBus.Mock')
+      getCalls = Promise.promisify(logindMock.GetCalls, {context: logindMock})
+      emitSignal = Promise.promisify(logindMock.EmitSignal, {context: logindMock})
+      reset = Promise.promisify(logindMock.Reset, {context: logindMock})
+    })
+
+    after(async () => {
+      await reset()
+    })
+  }
+
+  (skip ? describe.skip : describe)('when powerMonitor module is loaded with dbus mock', () => {
     function onceMethodCalled (done) {
       function cb () {
         logindMock.removeListener('MethodCalled', cb)
@@ -41,7 +43,7 @@ const skip = process.platform !== 'linux' || !process.env.DBUS_SYSTEM_BUS_ADDRES
     before((done) => {
       logindMock.on('MethodCalled', onceMethodCalled(done))
       // lazy load powerMonitor after we listen to MethodCalled mock signal
-      powerMonitor = require('electron').remote.powerMonitor
+      dbusMockPowerMonitor = require('electron').remote.powerMonitor
     })
 
     it('should call Inhibit to delay suspend', async () => {
@@ -59,14 +61,14 @@ const skip = process.platform !== 'linux' || !process.env.DBUS_SYSTEM_BUS_ADDRES
 
     describe('when PrepareForSleep(true) signal is sent by logind', () => {
       it('should emit "suspend" event', (done) => {
-        powerMonitor.once('suspend', () => done())
+        dbusMockPowerMonitor.once('suspend', () => done())
         emitSignal('org.freedesktop.login1.Manager', 'PrepareForSleep',
           'b', [['b', true]])
       })
 
       describe('when PrepareForSleep(false) signal is sent by logind', () => {
         it('should emit "resume" event', (done) => {
-          powerMonitor.once('resume', () => done())
+          dbusMockPowerMonitor.once('resume', () => done())
           emitSignal('org.freedesktop.login1.Manager', 'PrepareForSleep',
             'b', [['b', false]])
         })
@@ -90,7 +92,7 @@ const skip = process.platform !== 'linux' || !process.env.DBUS_SYSTEM_BUS_ADDRES
       before(async () => {
         const calls = await getCalls()
         assert.equal(calls.length, 2)
-        powerMonitor.once('shutdown', () => { })
+        dbusMockPowerMonitor.once('shutdown', () => { })
       })
 
       it('should call Inhibit to delay shutdown', async () => {
@@ -108,11 +110,53 @@ const skip = process.platform !== 'linux' || !process.env.DBUS_SYSTEM_BUS_ADDRES
 
       describe('when PrepareForShutdown(true) signal is sent by logind', () => {
         it('should emit "shutdown" event', (done) => {
-          powerMonitor.once('shutdown', () => { done() })
+          dbusMockPowerMonitor.once('shutdown', () => { done() })
           emitSignal('org.freedesktop.login1.Manager', 'PrepareForShutdown',
             'b', [['b', true]])
         })
       })
     })
   })
+
+  describe('when powerMonitor module is loaded', () => {
+    let powerMonitor
+    before(() => {
+      powerMonitor = require('electron').remote.powerMonitor
+    })
+
+    describe('powerMonitor.querySystemIdleState', () => {
+      it('notify current system idle state', (done) => {
+        powerMonitor.querySystemIdleState(1, (idleState) => {
+          assert.ok(idleState)
+          done()
+        })
+      })
+
+      it('does not accept non positive integer threshold', () => {
+        assert.throws(() => {
+          powerMonitor.querySystemIdleState(-1, (idleState) => {
+          })
+        })
+
+        assert.throws(() => {
+          powerMonitor.querySystemIdleState(NaN, (idleState) => {
+          })
+        })
+
+        assert.throws(() => {
+          powerMonitor.querySystemIdleState('a', (idleState) => {
+          })
+        })
+      })
+    })
+
+    describe('powerMonitor.querySystemIdleTime', () => {
+      it('notify current system idle time', (done) => {
+        powerMonitor.querySystemIdleTime((idleTime) => {
+          assert.ok(idleTime >= 0)
+          done()
+        })
+      })
+    })
+  })
 })