Merge pull request #11417 from electron/power-monitor-shutdown-event-and-delay-api
[RFC] New API: powerMonitor "shutdown" event
This commit is contained in:
commit
ab015e573b
13 changed files with 187 additions and 26 deletions
|
@ -16,6 +16,13 @@ namespace atom {
|
||||||
namespace api {
|
namespace api {
|
||||||
|
|
||||||
PowerMonitor::PowerMonitor(v8::Isolate* isolate) {
|
PowerMonitor::PowerMonitor(v8::Isolate* isolate) {
|
||||||
|
#if defined(OS_LINUX)
|
||||||
|
SetShutdownHandler(base::Bind(&PowerMonitor::ShouldShutdown,
|
||||||
|
base::Unretained(this)));
|
||||||
|
#elif defined(OS_MACOSX)
|
||||||
|
Browser::Get()->SetShutdownHandler(base::Bind(&PowerMonitor::ShouldShutdown,
|
||||||
|
base::Unretained(this)));
|
||||||
|
#endif
|
||||||
base::PowerMonitor::Get()->AddObserver(this);
|
base::PowerMonitor::Get()->AddObserver(this);
|
||||||
Init(isolate);
|
Init(isolate);
|
||||||
}
|
}
|
||||||
|
@ -24,6 +31,20 @@ PowerMonitor::~PowerMonitor() {
|
||||||
base::PowerMonitor::Get()->RemoveObserver(this);
|
base::PowerMonitor::Get()->RemoveObserver(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool PowerMonitor::ShouldShutdown() {
|
||||||
|
return !Emit("shutdown");
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(OS_LINUX)
|
||||||
|
void PowerMonitor::BlockShutdown() {
|
||||||
|
PowerObserverLinux::BlockShutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PowerMonitor::UnblockShutdown() {
|
||||||
|
PowerObserverLinux::UnblockShutdown();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
void PowerMonitor::OnPowerStateChange(bool on_battery_power) {
|
void PowerMonitor::OnPowerStateChange(bool on_battery_power) {
|
||||||
if (on_battery_power)
|
if (on_battery_power)
|
||||||
Emit("on-battery");
|
Emit("on-battery");
|
||||||
|
@ -55,6 +76,11 @@ v8::Local<v8::Value> PowerMonitor::Create(v8::Isolate* isolate) {
|
||||||
void PowerMonitor::BuildPrototype(
|
void PowerMonitor::BuildPrototype(
|
||||||
v8::Isolate* isolate, v8::Local<v8::FunctionTemplate> prototype) {
|
v8::Isolate* isolate, v8::Local<v8::FunctionTemplate> prototype) {
|
||||||
prototype->SetClassName(mate::StringToV8(isolate, "PowerMonitor"));
|
prototype->SetClassName(mate::StringToV8(isolate, "PowerMonitor"));
|
||||||
|
#if defined(OS_LINUX)
|
||||||
|
mate::ObjectTemplateBuilder(isolate, prototype->PrototypeTemplate())
|
||||||
|
.SetMethod("blockShutdown", &PowerMonitor::BlockShutdown)
|
||||||
|
.SetMethod("unblockShutdown", &PowerMonitor::UnblockShutdown);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace api
|
} // namespace api
|
||||||
|
@ -68,10 +94,6 @@ using atom::api::PowerMonitor;
|
||||||
|
|
||||||
void Initialize(v8::Local<v8::Object> exports, v8::Local<v8::Value> unused,
|
void Initialize(v8::Local<v8::Object> exports, v8::Local<v8::Value> unused,
|
||||||
v8::Local<v8::Context> context, void* priv) {
|
v8::Local<v8::Context> context, void* priv) {
|
||||||
#if defined(OS_MACOSX)
|
|
||||||
base::PowerMonitorDeviceSource::AllocateSystemIOPorts();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
v8::Isolate* isolate = context->GetIsolate();
|
v8::Isolate* isolate = context->GetIsolate();
|
||||||
mate::Dictionary dict(isolate, exports);
|
mate::Dictionary dict(isolate, exports);
|
||||||
dict.Set("powerMonitor", PowerMonitor::Create(isolate));
|
dict.Set("powerMonitor", PowerMonitor::Create(isolate));
|
||||||
|
|
|
@ -26,6 +26,15 @@ class PowerMonitor : public mate::TrackableObject<PowerMonitor>,
|
||||||
explicit PowerMonitor(v8::Isolate* isolate);
|
explicit PowerMonitor(v8::Isolate* isolate);
|
||||||
~PowerMonitor() override;
|
~PowerMonitor() override;
|
||||||
|
|
||||||
|
// Called by native calles.
|
||||||
|
bool ShouldShutdown();
|
||||||
|
|
||||||
|
#if defined(OS_LINUX)
|
||||||
|
// Private JS APIs.
|
||||||
|
void BlockShutdown();
|
||||||
|
void UnblockShutdown();
|
||||||
|
#endif
|
||||||
|
|
||||||
// base::PowerObserver implementations:
|
// base::PowerObserver implementations:
|
||||||
void OnPowerStateChange(bool on_battery_power) override;
|
void OnPowerStateChange(bool on_battery_power) override;
|
||||||
void OnSuspend() override;
|
void OnSuspend() override;
|
||||||
|
|
|
@ -105,6 +105,9 @@ class Browser : public WindowListObserver {
|
||||||
LoginItemSettings GetLoginItemSettings(const LoginItemSettings& options);
|
LoginItemSettings GetLoginItemSettings(const LoginItemSettings& options);
|
||||||
|
|
||||||
#if defined(OS_MACOSX)
|
#if defined(OS_MACOSX)
|
||||||
|
// Set the handler which decides whether to shutdown.
|
||||||
|
void SetShutdownHandler(base::Callback<bool()> handler);
|
||||||
|
|
||||||
// Hide the application.
|
// Hide the application.
|
||||||
void Hide();
|
void Hide();
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,10 @@
|
||||||
|
|
||||||
namespace atom {
|
namespace atom {
|
||||||
|
|
||||||
|
void Browser::SetShutdownHandler(base::Callback<bool()> handler) {
|
||||||
|
[[AtomApplication sharedApplication] setShutdownHandler:std::move(handler)];
|
||||||
|
}
|
||||||
|
|
||||||
void Browser::Focus() {
|
void Browser::Focus() {
|
||||||
[[AtomApplication sharedApplication] activateIgnoringOtherApps:YES];
|
[[AtomApplication sharedApplication] activateIgnoringOtherApps:YES];
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,16 +54,22 @@ void PowerObserverLinux::OnLoginServiceAvailable(bool service_available) {
|
||||||
LOG(WARNING) << kLogindServiceName << " not available";
|
LOG(WARNING) << kLogindServiceName << " not available";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// listen sleep
|
// Connect to PrepareForShutdown/PrepareForSleep signals
|
||||||
|
logind_->ConnectToSignal(kLogindManagerInterface, "PrepareForShutdown",
|
||||||
|
base::Bind(&PowerObserverLinux::OnPrepareForShutdown,
|
||||||
|
weak_ptr_factory_.GetWeakPtr()),
|
||||||
|
base::Bind(&PowerObserverLinux::OnSignalConnected,
|
||||||
|
weak_ptr_factory_.GetWeakPtr()));
|
||||||
logind_->ConnectToSignal(kLogindManagerInterface, "PrepareForSleep",
|
logind_->ConnectToSignal(kLogindManagerInterface, "PrepareForSleep",
|
||||||
base::Bind(&PowerObserverLinux::OnPrepareForSleep,
|
base::Bind(&PowerObserverLinux::OnPrepareForSleep,
|
||||||
weak_ptr_factory_.GetWeakPtr()),
|
weak_ptr_factory_.GetWeakPtr()),
|
||||||
base::Bind(&PowerObserverLinux::OnSignalConnected,
|
base::Bind(&PowerObserverLinux::OnSignalConnected,
|
||||||
weak_ptr_factory_.GetWeakPtr()));
|
weak_ptr_factory_.GetWeakPtr()));
|
||||||
TakeSleepLock();
|
// Take sleep inhibit lock
|
||||||
|
BlockSleep();
|
||||||
}
|
}
|
||||||
|
|
||||||
void PowerObserverLinux::TakeSleepLock() {
|
void PowerObserverLinux::BlockSleep() {
|
||||||
dbus::MethodCall sleep_inhibit_call(kLogindManagerInterface, "Inhibit");
|
dbus::MethodCall sleep_inhibit_call(kLogindManagerInterface, "Inhibit");
|
||||||
dbus::MessageWriter inhibit_writer(&sleep_inhibit_call);
|
dbus::MessageWriter inhibit_writer(&sleep_inhibit_call);
|
||||||
inhibit_writer.AppendString("sleep"); // what
|
inhibit_writer.AppendString("sleep"); // what
|
||||||
|
@ -78,6 +84,40 @@ void PowerObserverLinux::TakeSleepLock() {
|
||||||
weak_ptr_factory_.GetWeakPtr(), &sleep_lock_));
|
weak_ptr_factory_.GetWeakPtr(), &sleep_lock_));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PowerObserverLinux::UnblockSleep() {
|
||||||
|
sleep_lock_.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PowerObserverLinux::BlockShutdown() {
|
||||||
|
if (shutdown_lock_.is_valid()) {
|
||||||
|
LOG(WARNING) << "Trying to subscribe to shutdown multiple times";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dbus::MethodCall shutdown_inhibit_call(kLogindManagerInterface, "Inhibit");
|
||||||
|
dbus::MessageWriter inhibit_writer(&shutdown_inhibit_call);
|
||||||
|
inhibit_writer.AppendString("shutdown"); // what
|
||||||
|
inhibit_writer.AppendString(lock_owner_name_); // who
|
||||||
|
inhibit_writer.AppendString("Ensure a clean shutdown"); // why
|
||||||
|
inhibit_writer.AppendString("delay"); // mode
|
||||||
|
logind_->CallMethod(
|
||||||
|
&shutdown_inhibit_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
|
||||||
|
base::Bind(&PowerObserverLinux::OnInhibitResponse,
|
||||||
|
weak_ptr_factory_.GetWeakPtr(), &shutdown_lock_));
|
||||||
|
}
|
||||||
|
|
||||||
|
void PowerObserverLinux::UnblockShutdown() {
|
||||||
|
if (!shutdown_lock_.is_valid()) {
|
||||||
|
LOG(WARNING)
|
||||||
|
<< "Trying to unsubscribe to shutdown without being subscribed";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
shutdown_lock_.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PowerObserverLinux::SetShutdownHandler(base::Callback<bool()> handler) {
|
||||||
|
should_shutdown_ = std::move(handler);
|
||||||
|
}
|
||||||
|
|
||||||
void PowerObserverLinux::OnInhibitResponse(base::ScopedFD* scoped_fd,
|
void PowerObserverLinux::OnInhibitResponse(base::ScopedFD* scoped_fd,
|
||||||
dbus::Response* response) {
|
dbus::Response* response) {
|
||||||
dbus::MessageReader reader(response);
|
dbus::MessageReader reader(response);
|
||||||
|
@ -86,20 +126,36 @@ void PowerObserverLinux::OnInhibitResponse(base::ScopedFD* scoped_fd,
|
||||||
|
|
||||||
void PowerObserverLinux::OnPrepareForSleep(dbus::Signal* signal) {
|
void PowerObserverLinux::OnPrepareForSleep(dbus::Signal* signal) {
|
||||||
dbus::MessageReader reader(signal);
|
dbus::MessageReader reader(signal);
|
||||||
bool status;
|
bool suspending;
|
||||||
if (!reader.PopBool(&status)) {
|
if (!reader.PopBool(&suspending)) {
|
||||||
LOG(ERROR) << "Invalid signal: " << signal->ToString();
|
LOG(ERROR) << "Invalid signal: " << signal->ToString();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (status) {
|
if (suspending) {
|
||||||
OnSuspend();
|
OnSuspend();
|
||||||
sleep_lock_.reset();
|
UnblockSleep();
|
||||||
} else {
|
} else {
|
||||||
TakeSleepLock();
|
BlockSleep();
|
||||||
OnResume();
|
OnResume();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PowerObserverLinux::OnPrepareForShutdown(dbus::Signal* signal) {
|
||||||
|
dbus::MessageReader reader(signal);
|
||||||
|
bool shutting_down;
|
||||||
|
if (!reader.PopBool(&shutting_down)) {
|
||||||
|
LOG(ERROR) << "Invalid signal: " << signal->ToString();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (shutting_down) {
|
||||||
|
if (!should_shutdown_ || should_shutdown_.Run()) {
|
||||||
|
// The user didn't try to prevent shutdown. Release the lock and allow the
|
||||||
|
// shutdown to continue normally.
|
||||||
|
shutdown_lock_.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void PowerObserverLinux::OnSignalConnected(const std::string& interface,
|
void PowerObserverLinux::OnSignalConnected(const std::string& interface,
|
||||||
const std::string& signal,
|
const std::string& signal,
|
||||||
bool success) {
|
bool success) {
|
||||||
|
|
|
@ -20,20 +20,32 @@ class PowerObserverLinux : public base::PowerObserver {
|
||||||
public:
|
public:
|
||||||
PowerObserverLinux();
|
PowerObserverLinux();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void BlockSleep();
|
||||||
|
void UnblockSleep();
|
||||||
|
void BlockShutdown();
|
||||||
|
void UnblockShutdown();
|
||||||
|
|
||||||
|
void SetShutdownHandler(base::Callback<bool()> should_shutdown);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void TakeSleepLock();
|
|
||||||
void OnLoginServiceAvailable(bool available);
|
void OnLoginServiceAvailable(bool available);
|
||||||
void OnInhibitResponse(base::ScopedFD* scoped_fd, dbus::Response* response);
|
void OnInhibitResponse(base::ScopedFD* scoped_fd, dbus::Response* response);
|
||||||
void OnPrepareForSleep(dbus::Signal* signal);
|
void OnPrepareForSleep(dbus::Signal* signal);
|
||||||
|
void OnPrepareForShutdown(dbus::Signal* signal);
|
||||||
void OnSignalConnected(const std::string& interface,
|
void OnSignalConnected(const std::string& interface,
|
||||||
const std::string& signal,
|
const std::string& signal,
|
||||||
bool success);
|
bool success);
|
||||||
|
|
||||||
|
base::Callback<bool()> should_shutdown_;
|
||||||
|
|
||||||
scoped_refptr<dbus::Bus> bus_;
|
scoped_refptr<dbus::Bus> bus_;
|
||||||
scoped_refptr<dbus::ObjectProxy> logind_;
|
scoped_refptr<dbus::ObjectProxy> logind_;
|
||||||
std::string lock_owner_name_;
|
std::string lock_owner_name_;
|
||||||
base::ScopedFD sleep_lock_;
|
base::ScopedFD sleep_lock_;
|
||||||
|
base::ScopedFD shutdown_lock_;
|
||||||
base::WeakPtrFactory<PowerObserverLinux> weak_ptr_factory_;
|
base::WeakPtrFactory<PowerObserverLinux> weak_ptr_factory_;
|
||||||
|
|
||||||
DISALLOW_COPY_AND_ASSIGN(PowerObserverLinux);
|
DISALLOW_COPY_AND_ASSIGN(PowerObserverLinux);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -2,8 +2,9 @@
|
||||||
// Use of this source code is governed by the MIT license that can be
|
// Use of this source code is governed by the MIT license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
#import "base/mac/scoped_sending_event.h"
|
#include "base/callback.h"
|
||||||
#import "base/mac/scoped_nsobject.h"
|
#include "base/mac/scoped_sending_event.h"
|
||||||
|
#include "base/mac/scoped_nsobject.h"
|
||||||
|
|
||||||
@interface AtomApplication : NSApplication<CrAppProtocol,
|
@interface AtomApplication : NSApplication<CrAppProtocol,
|
||||||
CrAppControlProtocol,
|
CrAppControlProtocol,
|
||||||
|
@ -13,10 +14,13 @@
|
||||||
base::scoped_nsobject<NSUserActivity> currentActivity_;
|
base::scoped_nsobject<NSUserActivity> currentActivity_;
|
||||||
NSCondition* handoffLock_;
|
NSCondition* handoffLock_;
|
||||||
BOOL updateReceived_;
|
BOOL updateReceived_;
|
||||||
|
base::Callback<bool()> shouldShutdown_;
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (AtomApplication*)sharedApplication;
|
+ (AtomApplication*)sharedApplication;
|
||||||
|
|
||||||
|
- (void)setShutdownHandler:(base::Callback<bool()>)handler;
|
||||||
|
|
||||||
// CrAppProtocol:
|
// CrAppProtocol:
|
||||||
- (BOOL)isHandlingSendEvent;
|
- (BOOL)isHandlingSendEvent;
|
||||||
|
|
||||||
|
|
|
@ -29,8 +29,17 @@ inline void dispatch_sync_main(dispatch_block_t block) {
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)terminate:(id)sender {
|
- (void)terminate:(id)sender {
|
||||||
AtomApplicationDelegate* atomDelegate = (AtomApplicationDelegate*) [NSApp delegate];
|
if (shouldShutdown_ && !shouldShutdown_.Run())
|
||||||
[atomDelegate tryToTerminateApp:self];
|
return; // User will call Quit later.
|
||||||
|
|
||||||
|
// We simply try to close the browser, which in turn will try to close the
|
||||||
|
// windows. Termination can proceed if all windows are closed or window close
|
||||||
|
// can be cancelled which will abort termination.
|
||||||
|
atom::Browser::Get()->Quit();
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)setShutdownHandler:(base::Callback<bool()>)handler {
|
||||||
|
shouldShutdown_ = std::move(handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)isHandlingSendEvent {
|
- (BOOL)isHandlingSendEvent {
|
||||||
|
|
|
@ -11,8 +11,6 @@
|
||||||
base::scoped_nsobject<AtomMenuController> menu_controller_;
|
base::scoped_nsobject<AtomMenuController> menu_controller_;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)tryToTerminateApp:(NSApplication*)app;
|
|
||||||
|
|
||||||
// Sets the menu that will be returned in "applicationDockMenu:".
|
// Sets the menu that will be returned in "applicationDockMenu:".
|
||||||
- (void)setApplicationDockMenu:(atom::AtomMenuModel*)model;
|
- (void)setApplicationDockMenu:(atom::AtomMenuModel*)model;
|
||||||
|
|
||||||
|
|
|
@ -87,13 +87,6 @@ static base::mac::ScopedObjCClassSwizzler* g_swizzle_imk_input_session;
|
||||||
return atom::Browser::Get()->OpenFile(filename_str) ? YES : NO;
|
return atom::Browser::Get()->OpenFile(filename_str) ? YES : NO;
|
||||||
}
|
}
|
||||||
|
|
||||||
// We simply try to close the browser, which in turn will try to close the windows.
|
|
||||||
// Termination can proceed if all windows are closed or window close can be cancelled
|
|
||||||
// which will abort termination.
|
|
||||||
- (void)tryToTerminateApp:(NSApplication*)app {
|
|
||||||
atom::Browser::Get()->Quit();
|
|
||||||
}
|
|
||||||
|
|
||||||
- (BOOL)applicationShouldHandleReopen:(NSApplication*)theApplication
|
- (BOOL)applicationShouldHandleReopen:(NSApplication*)theApplication
|
||||||
hasVisibleWindows:(BOOL)flag {
|
hasVisibleWindows:(BOOL)flag {
|
||||||
atom::Browser* browser = atom::Browser::Get();
|
atom::Browser* browser = atom::Browser::Get();
|
||||||
|
|
|
@ -39,3 +39,10 @@ Emitted when the system changes to AC power.
|
||||||
### Event: 'on-battery' _Windows_
|
### Event: 'on-battery' _Windows_
|
||||||
|
|
||||||
Emitted when system changes to battery power.
|
Emitted when system changes to battery power.
|
||||||
|
|
||||||
|
### Event: 'shutdown' _Linux_ _macOS_
|
||||||
|
|
||||||
|
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()`.
|
||||||
|
|
|
@ -5,4 +5,19 @@ const {powerMonitor, PowerMonitor} = process.atomBinding('power_monitor')
|
||||||
Object.setPrototypeOf(PowerMonitor.prototype, EventEmitter.prototype)
|
Object.setPrototypeOf(PowerMonitor.prototype, EventEmitter.prototype)
|
||||||
EventEmitter.call(powerMonitor)
|
EventEmitter.call(powerMonitor)
|
||||||
|
|
||||||
|
// On Linux we need to call blockShutdown() to subscribe to shutdown event.
|
||||||
|
if (process.platform === 'linux') {
|
||||||
|
powerMonitor.on('newListener', (event) => {
|
||||||
|
if (event === 'shutdown' && powerMonitor.listenerCount('shutdown') === 0) {
|
||||||
|
powerMonitor.blockShutdown()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
powerMonitor.on('removeListener', (event) => {
|
||||||
|
if (event === 'shutdown' && powerMonitor.listenerCount('shutdown') === 0) {
|
||||||
|
powerMonitor.unblockShutdown()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = powerMonitor
|
module.exports = powerMonitor
|
||||||
|
|
|
@ -85,5 +85,34 @@ const skip = process.platform !== 'linux' || !process.env.DBUS_SYSTEM_BUS_ADDRES
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('when a listener is added to shutdown event', () => {
|
||||||
|
before(async () => {
|
||||||
|
const calls = await getCalls()
|
||||||
|
assert.equal(calls.length, 2)
|
||||||
|
powerMonitor.once('shutdown', () => { })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should call Inhibit to delay shutdown', async () => {
|
||||||
|
const calls = await getCalls()
|
||||||
|
assert.equal(calls.length, 3)
|
||||||
|
assert.deepEqual(calls[2].slice(1), [
|
||||||
|
'Inhibit', [
|
||||||
|
[[{type: 's', child: []}], ['shutdown']],
|
||||||
|
[[{type: 's', child: []}], ['electron']],
|
||||||
|
[[{type: 's', child: []}], ['Ensure a clean shutdown']],
|
||||||
|
[[{type: 's', child: []}], ['delay']]
|
||||||
|
]
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('when PrepareForShutdown(true) signal is sent by logind', () => {
|
||||||
|
it('should emit "shutdown" event', (done) => {
|
||||||
|
powerMonitor.once('shutdown', () => { done() })
|
||||||
|
emitSignal('org.freedesktop.login1.Manager', 'PrepareForShutdown',
|
||||||
|
'b', [['b', true]])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
Loading…
Add table
Reference in a new issue