diff --git a/atom/browser/api/atom_api_power_monitor.cc b/atom/browser/api/atom_api_power_monitor.cc index 429d3fc26e80..95b5624951b9 100644 --- a/atom/browser/api/atom_api_power_monitor.cc +++ b/atom/browser/api/atom_api_power_monitor.cc @@ -24,6 +24,14 @@ PowerMonitor::~PowerMonitor() { base::PowerMonitor::Get()->RemoveObserver(this); } +void PowerMonitor::BlockShutdown(mate::Arguments* args) { + atom::PowerObserver::BlockShutdown(); +} + +void PowerMonitor::UnblockShutdown(mate::Arguments* args) { + atom::PowerObserver::UnblockShutdown(); +} + void PowerMonitor::OnPowerStateChange(bool on_battery_power) { if (on_battery_power) Emit("on-battery"); @@ -39,6 +47,10 @@ void PowerMonitor::OnResume() { Emit("resume"); } +bool PowerMonitor::OnShutdown() { + return Emit("shutdown"); +} + // static v8::Local PowerMonitor::Create(v8::Isolate* isolate) { if (!Browser::Get()->is_ready()) { @@ -55,6 +67,9 @@ v8::Local PowerMonitor::Create(v8::Isolate* isolate) { void PowerMonitor::BuildPrototype( v8::Isolate* isolate, v8::Local prototype) { prototype->SetClassName(mate::StringToV8(isolate, "PowerMonitor")); + mate::ObjectTemplateBuilder(isolate, prototype->PrototypeTemplate()) + .SetMethod("blockShutdown", &PowerMonitor::BlockShutdown) + .SetMethod("unblockShutdown", &PowerMonitor::UnblockShutdown); } } // namespace api diff --git a/atom/browser/api/atom_api_power_monitor.h b/atom/browser/api/atom_api_power_monitor.h index 1a5f1ec6bd3b..c8174494ade8 100644 --- a/atom/browser/api/atom_api_power_monitor.h +++ b/atom/browser/api/atom_api_power_monitor.h @@ -26,11 +26,17 @@ class PowerMonitor : public mate::TrackableObject, explicit PowerMonitor(v8::Isolate* isolate); ~PowerMonitor() override; + void BlockShutdown(mate::Arguments* args); + void UnblockShutdown(mate::Arguments* args); + // base::PowerObserver implementations: void OnPowerStateChange(bool on_battery_power) override; void OnSuspend() override; void OnResume() override; + // atom::PowerObserver + bool OnShutdown() override; + private: DISALLOW_COPY_AND_ASSIGN(PowerMonitor); }; diff --git a/atom/browser/lib/power_observer.h b/atom/browser/lib/power_observer.h index 0935b221e33d..f729235e46c0 100644 --- a/atom/browser/lib/power_observer.h +++ b/atom/browser/lib/power_observer.h @@ -18,7 +18,19 @@ namespace atom { #if defined(OS_LINUX) typedef PowerObserverLinux PowerObserver; #else -typedef base::PowerObserver PowerObserver; +class PowerObserver : public base::PowerObserver { + public: + PowerObserver() {} + void BlockShutdown() {} + void UnblockShutdown() {} + // Notification that the system is rebooting or shutting down. If the + // implementation returns true, the PowerObserver instance should try to delay + // OS shutdown so the application can perform cleanup before exiting. + virtual bool OnShutdown() { return false; } + + private: + DISALLOW_COPY_AND_ASSIGN(PowerObserver); +}; #endif // defined(OS_LINUX) } // namespace atom diff --git a/atom/browser/lib/power_observer_linux.cc b/atom/browser/lib/power_observer_linux.cc index da4b8ea82a7b..ac641a2fe0a6 100644 --- a/atom/browser/lib/power_observer_linux.cc +++ b/atom/browser/lib/power_observer_linux.cc @@ -16,6 +16,10 @@ const char kLogindServiceName[] = "org.freedesktop.login1"; const char kLogindObjectPath[] = "/org/freedesktop/login1"; const char kLogindManagerInterface[] = "org.freedesktop.login1.Manager"; +// Store shutdown lock as a global, since we only want to release it when the +// main process exits. +base::ScopedFD shutdown_lock; + std::string get_executable_basename() { char buf[4096]; size_t buf_size = sizeof(buf); @@ -54,12 +58,18 @@ void PowerObserverLinux::OnLoginServiceAvailable(bool service_available) { LOG(WARNING) << kLogindServiceName << " not available"; 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", base::Bind(&PowerObserverLinux::OnPrepareForSleep, weak_ptr_factory_.GetWeakPtr()), base::Bind(&PowerObserverLinux::OnSignalConnected, weak_ptr_factory_.GetWeakPtr())); + // Take sleep inhibit lock TakeSleepLock(); } @@ -78,6 +88,32 @@ void PowerObserverLinux::TakeSleepLock() { weak_ptr_factory_.GetWeakPtr(), &sleep_lock_)); } +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::OnInhibitResponse(base::ScopedFD* scoped_fd, dbus::Response* response) { dbus::MessageReader reader(response); @@ -100,6 +136,22 @@ void PowerObserverLinux::OnPrepareForSleep(dbus::Signal* signal) { } } +void PowerObserverLinux::OnPrepareForShutdown(dbus::Signal* signal) { + dbus::MessageReader reader(signal); + bool status; + if (!reader.PopBool(&status)) { + LOG(ERROR) << "Invalid signal: " << signal->ToString(); + return; + } + if (status) { + if (!OnShutdown()) { + // 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, const std::string& signal, bool success) { diff --git a/atom/browser/lib/power_observer_linux.h b/atom/browser/lib/power_observer_linux.h index 60691f721183..2ea8f36a9cbd 100644 --- a/atom/browser/lib/power_observer_linux.h +++ b/atom/browser/lib/power_observer_linux.h @@ -20,11 +20,16 @@ class PowerObserverLinux : public base::PowerObserver { public: PowerObserverLinux(); + void BlockShutdown(); + void UnblockShutdown(); + virtual bool OnShutdown() { return false; } + private: void TakeSleepLock(); void OnLoginServiceAvailable(bool available); void OnInhibitResponse(base::ScopedFD* scoped_fd, dbus::Response* response); void OnPrepareForSleep(dbus::Signal* signal); + void OnPrepareForShutdown(dbus::Signal* signal); void OnSignalConnected(const std::string& interface, const std::string& signal, bool success); diff --git a/lib/browser/api/power-monitor.js b/lib/browser/api/power-monitor.js index e1dff2c3b73c..18bc7b7c2095 100644 --- a/lib/browser/api/power-monitor.js +++ b/lib/browser/api/power-monitor.js @@ -5,4 +5,16 @@ const {powerMonitor, PowerMonitor} = process.atomBinding('power_monitor') Object.setPrototypeOf(PowerMonitor.prototype, EventEmitter.prototype) EventEmitter.call(powerMonitor) +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