forked from electron/electron
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request electron#11306 from electron/implement-linux-power…
…-monitor Implement powerMonitor "suspend"/"resume" events for Linux.
- Loading branch information
Showing
9 changed files
with
300 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
// Copyright (c) 2017 GitHub, Inc. | ||
// Use of this source code is governed by the MIT license that can be | ||
// found in the LICENSE file. | ||
|
||
#ifndef ATOM_BROWSER_LIB_POWER_OBSERVER_H_ | ||
#define ATOM_BROWSER_LIB_POWER_OBSERVER_H_ | ||
|
||
#include "base/macros.h" | ||
|
||
#if defined(OS_LINUX) | ||
#include "atom/browser/lib/power_observer_linux.h" | ||
#else | ||
#include "base/power_monitor/power_observer.h" | ||
#endif // defined(OS_LINUX) | ||
|
||
namespace atom { | ||
|
||
#if defined(OS_LINUX) | ||
typedef PowerObserverLinux PowerObserver; | ||
#else | ||
typedef base::PowerObserver PowerObserver; | ||
#endif // defined(OS_LINUX) | ||
|
||
} // namespace atom | ||
|
||
#endif // ATOM_BROWSER_LIB_POWER_OBSERVER_H_ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
// Copyright (c) 2017 GitHub, Inc. | ||
// Use of this source code is governed by the MIT license that can be | ||
// found in the LICENSE file. | ||
#include "atom/browser/lib/power_observer_linux.h" | ||
|
||
#include <unistd.h> | ||
#include <uv.h> | ||
#include <iostream> | ||
|
||
#include "base/bind.h" | ||
#include "device/bluetooth/dbus/dbus_thread_manager_linux.h" | ||
|
||
namespace { | ||
|
||
const char kLogindServiceName[] = "org.freedesktop.login1"; | ||
const char kLogindObjectPath[] = "/org/freedesktop/login1"; | ||
const char kLogindManagerInterface[] = "org.freedesktop.login1.Manager"; | ||
|
||
std::string get_executable_basename() { | ||
char buf[4096]; | ||
size_t buf_size = sizeof(buf); | ||
std::string rv("electron"); | ||
if (!uv_exepath(buf, &buf_size)) { | ||
rv = strrchr(static_cast<const char*>(buf), '/') + 1; | ||
} | ||
return std::move(rv); | ||
} | ||
|
||
} // namespace | ||
|
||
namespace atom { | ||
|
||
PowerObserverLinux::PowerObserverLinux() | ||
: lock_owner_name_(get_executable_basename()), weak_ptr_factory_(this) { | ||
auto dbus_thread_manager = bluez::DBusThreadManagerLinux::Get(); | ||
if (dbus_thread_manager) { | ||
bus_ = dbus_thread_manager->GetSystemBus(); | ||
if (bus_) { | ||
logind_ = bus_->GetObjectProxy(kLogindServiceName, | ||
dbus::ObjectPath(kLogindObjectPath)); | ||
logind_->WaitForServiceToBeAvailable( | ||
base::Bind(&PowerObserverLinux::OnLoginServiceAvailable, | ||
weak_ptr_factory_.GetWeakPtr())); | ||
} else { | ||
LOG(WARNING) << "Failed to get system bus connection"; | ||
} | ||
} else { | ||
LOG(WARNING) << "DBusThreadManagerLinux instance isn't available"; | ||
} | ||
} | ||
|
||
void PowerObserverLinux::OnLoginServiceAvailable(bool service_available) { | ||
if (!service_available) { | ||
LOG(WARNING) << kLogindServiceName << " not available"; | ||
return; | ||
} | ||
// listen sleep | ||
logind_->ConnectToSignal(kLogindManagerInterface, "PrepareForSleep", | ||
base::Bind(&PowerObserverLinux::OnPrepareForSleep, | ||
weak_ptr_factory_.GetWeakPtr()), | ||
base::Bind(&PowerObserverLinux::OnSignalConnected, | ||
weak_ptr_factory_.GetWeakPtr())); | ||
TakeSleepLock(); | ||
} | ||
|
||
void PowerObserverLinux::TakeSleepLock() { | ||
dbus::MethodCall sleep_inhibit_call(kLogindManagerInterface, "Inhibit"); | ||
dbus::MessageWriter inhibit_writer(&sleep_inhibit_call); | ||
inhibit_writer.AppendString("sleep"); // what | ||
// Use the executable name as the lock owner, which will list rebrands of the | ||
// electron executable as separate entities. | ||
inhibit_writer.AppendString(lock_owner_name_); // who | ||
inhibit_writer.AppendString("Application cleanup before suspend"); // why | ||
inhibit_writer.AppendString("delay"); // mode | ||
logind_->CallMethod(&sleep_inhibit_call, | ||
dbus::ObjectProxy::TIMEOUT_USE_DEFAULT, | ||
base::Bind(&PowerObserverLinux::OnInhibitResponse, | ||
weak_ptr_factory_.GetWeakPtr(), &sleep_lock_)); | ||
} | ||
|
||
void PowerObserverLinux::OnInhibitResponse(base::ScopedFD* scoped_fd, | ||
dbus::Response* response) { | ||
dbus::MessageReader reader(response); | ||
reader.PopFileDescriptor(scoped_fd); | ||
} | ||
|
||
void PowerObserverLinux::OnPrepareForSleep(dbus::Signal* signal) { | ||
dbus::MessageReader reader(signal); | ||
bool status; | ||
if (!reader.PopBool(&status)) { | ||
LOG(ERROR) << "Invalid signal: " << signal->ToString(); | ||
return; | ||
} | ||
if (status) { | ||
OnSuspend(); | ||
sleep_lock_.reset(); | ||
} else { | ||
TakeSleepLock(); | ||
OnResume(); | ||
} | ||
} | ||
|
||
void PowerObserverLinux::OnSignalConnected(const std::string& interface, | ||
const std::string& signal, | ||
bool success) { | ||
LOG_IF(WARNING, !success) << "Failed to connect to " << signal; | ||
} | ||
|
||
} // namespace atom |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
// Copyright (c) 2017 GitHub, Inc. | ||
// Use of this source code is governed by the MIT license that can be | ||
// found in the LICENSE file. | ||
|
||
#ifndef ATOM_BROWSER_LIB_POWER_OBSERVER_LINUX_H_ | ||
#define ATOM_BROWSER_LIB_POWER_OBSERVER_LINUX_H_ | ||
|
||
#include <string> | ||
|
||
#include "base/macros.h" | ||
#include "base/memory/weak_ptr.h" | ||
#include "base/power_monitor/power_observer.h" | ||
#include "dbus/bus.h" | ||
#include "dbus/message.h" | ||
#include "dbus/object_proxy.h" | ||
|
||
namespace atom { | ||
|
||
class PowerObserverLinux : public base::PowerObserver { | ||
public: | ||
PowerObserverLinux(); | ||
|
||
private: | ||
void TakeSleepLock(); | ||
void OnLoginServiceAvailable(bool available); | ||
void OnInhibitResponse(base::ScopedFD* scoped_fd, dbus::Response* response); | ||
void OnPrepareForSleep(dbus::Signal* signal); | ||
void OnSignalConnected(const std::string& interface, | ||
const std::string& signal, | ||
bool success); | ||
|
||
scoped_refptr<dbus::Bus> bus_; | ||
scoped_refptr<dbus::ObjectProxy> logind_; | ||
std::string lock_owner_name_; | ||
base::ScopedFD sleep_lock_; | ||
base::WeakPtrFactory<PowerObserverLinux> weak_ptr_factory_; | ||
DISALLOW_COPY_AND_ASSIGN(PowerObserverLinux); | ||
}; | ||
|
||
} // namespace atom | ||
|
||
#endif // ATOM_BROWSER_LIB_POWER_OBSERVER_LINUX_H_ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
from dbusmock import DBusTestCase | ||
|
||
import atexit | ||
|
||
def cleanup(): | ||
DBusTestCase.stop_dbus(DBusTestCase.system_bus_pid) | ||
|
||
|
||
atexit.register(cleanup) | ||
DBusTestCase.start_system_bus() | ||
# create a mock for "org.freedesktop.login1" using python-dbusmock | ||
# preconfigured template | ||
(logind_mock, logind) = DBusTestCase.spawn_server_template('logind') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
// For these tests we use a fake DBus daemon to verify powerMonitor module | ||
// interaction with the system bus. This requires python-dbusmock installed and | ||
// running (with the DBUS_SYSTEM_BUS_ADDRESS environment variable set). | ||
// script/test.py will take care of spawning the fake DBus daemon and setting | ||
// DBUS_SYSTEM_BUS_ADDRESS when python-dbusmock is installed. | ||
// | ||
// See https://pypi.python.org/pypi/python-dbusmock for more information about | ||
// python-dbusmock. | ||
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}) | ||
}) | ||
|
||
after(async () => { | ||
await reset() | ||
}) | ||
|
||
describe('when powerMonitor module is loaded', () => { | ||
function onceMethodCalled (done) { | ||
function cb () { | ||
logindMock.removeListener('MethodCalled', cb) | ||
} | ||
done() | ||
return cb | ||
} | ||
|
||
before((done) => { | ||
logindMock.on('MethodCalled', onceMethodCalled(done)) | ||
// lazy load powerMonitor after we listen to MethodCalled mock signal | ||
powerMonitor = require('electron').remote.powerMonitor | ||
}) | ||
|
||
it('should call Inhibit to delay suspend', async () => { | ||
const calls = await getCalls() | ||
assert.equal(calls.length, 1) | ||
assert.deepEqual(calls[0].slice(1), [ | ||
'Inhibit', [ | ||
[[{type: 's', child: []}], ['sleep']], | ||
[[{type: 's', child: []}], ['electron']], | ||
[[{type: 's', child: []}], ['Application cleanup before suspend']], | ||
[[{type: 's', child: []}], ['delay']] | ||
] | ||
]) | ||
}) | ||
|
||
describe('when PrepareForSleep(true) signal is sent by logind', () => { | ||
it('should emit "suspend" event', (done) => { | ||
powerMonitor.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()) | ||
emitSignal('org.freedesktop.login1.Manager', 'PrepareForSleep', | ||
'b', [['b', false]]) | ||
}) | ||
|
||
it('should have called Inhibit again', async () => { | ||
const calls = await getCalls() | ||
assert.equal(calls.length, 2) | ||
assert.deepEqual(calls[1].slice(1), [ | ||
'Inhibit', [ | ||
[[{type: 's', child: []}], ['sleep']], | ||
[[{type: 's', child: []}], ['electron']], | ||
[[{type: 's', child: []}], ['Application cleanup before suspend']], | ||
[[{type: 's', child: []}], ['delay']] | ||
] | ||
]) | ||
}) | ||
}) | ||
}) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters