Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 5 commits into from Mar 14, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
47 changes: 44 additions & 3 deletions atom/browser/api/atom_api_power_monitor.cc
Expand Up @@ -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 {
Expand Down Expand Up @@ -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")));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know another reviewer suggested this, but are we sure that we want this?

Since we're not publishing the ui::CheckIdleStateIsLocked(), if all you want to do is check for system lock it's reasonable to pass a -1 in for idle_threshold.

If we include that method, we can avoid this wart by having a recommended way to test for lock

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm I didn't thought in that way, but also checking lock state is somewhat limited per platform support, so passing -1 doesn't guarantee you can reliable acquire intended state. I think it's rather limit api surface, and expose CheckIdleStateIsLocked separately if there's demand for it.

}
}

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()) {
Expand All @@ -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
Expand Down
6 changes: 6 additions & 0 deletions atom/browser/api/atom_api_power_monitor.h
Expand Up @@ -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 {

Expand Down Expand Up @@ -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);

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's make these methods private since we're only using them with the bindings. Permissions can be loosened later as needed

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

makes sense, moved it.

DISALLOW_COPY_AND_ASSIGN(PowerMonitor);
};

Expand Down
6 changes: 6 additions & 0 deletions atom/browser/atom_browser_main_parts.cc
Expand Up @@ -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"

Expand Down Expand Up @@ -159,6 +160,11 @@ int AtomBrowserMainParts::PreCreateThreads() {
fake_browser_process_->SetApplicationLocale(
brightray::BrowserClient::Get()->GetApplicationLocale());
}

#if defined(OS_MACOSX)
ui::InitIdleMonitor();
#endif

return result;
}

Expand Down
22 changes: 22 additions & 0 deletions docs/api/power-monitor.md
Expand Up @@ -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.
90 changes: 67 additions & 23 deletions spec/api-power-monitor-spec.js
Expand Up @@ -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)
Expand All @@ -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
Copy link
Member

@deepak1556 deepak1556 Feb 2, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tarruda Is it required to lazy initialize the module ? I was hoping we can just initialize the module only once for all the specs here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It has to be lazy loaded if we want to verify that electron inhibits the system suspend (it does this by calling a method via dbus).

})

it('should call Inhibit to delay suspend', async () => {
Expand All @@ -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]])
})
Expand All @@ -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 () => {
Expand All @@ -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()
})
})
})
})
})