// 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 "shell/browser/lib/power_observer_linux.h" #include #include #include #include #include "base/bind.h" #include "base/command_line.h" #include "base/files/file_path.h" #include "device/bluetooth/dbus/bluez_dbus_thread_manager.h" namespace { const char kLogindServiceName[] = "org.freedesktop.login1"; const char kLogindObjectPath[] = "/org/freedesktop/login1"; const char kLogindManagerInterface[] = "org.freedesktop.login1.Manager"; base::FilePath::StringType GetExecutableBaseName() { return base::CommandLine::ForCurrentProcess() ->GetProgram() .BaseName() .value(); } } // namespace namespace electron { PowerObserverLinux::PowerObserverLinux(base::PowerObserver* observer) : observer_(observer), lock_owner_name_(GetExecutableBaseName()) { auto* bus = bluez::BluezDBusThreadManager::Get()->GetSystemBus(); if (!bus) { LOG(WARNING) << "Failed to get system bus connection"; return; } // set up the logind proxy const auto weakThis = weak_ptr_factory_.GetWeakPtr(); logind_ = bus->GetObjectProxy(kLogindServiceName, dbus::ObjectPath(kLogindObjectPath)); logind_->ConnectToSignal( kLogindManagerInterface, "PrepareForShutdown", base::BindRepeating(&PowerObserverLinux::OnPrepareForShutdown, weakThis), base::BindRepeating(&PowerObserverLinux::OnSignalConnected, weakThis)); logind_->ConnectToSignal( kLogindManagerInterface, "PrepareForSleep", base::BindRepeating(&PowerObserverLinux::OnPrepareForSleep, weakThis), base::BindRepeating(&PowerObserverLinux::OnSignalConnected, weakThis)); logind_->WaitForServiceToBeAvailable(base::BindRepeating( &PowerObserverLinux::OnLoginServiceAvailable, weakThis)); } PowerObserverLinux::~PowerObserverLinux() = default; void PowerObserverLinux::OnLoginServiceAvailable(bool service_available) { if (!service_available) { LOG(WARNING) << kLogindServiceName << " not available"; return; } // Take sleep inhibit lock BlockSleep(); } void PowerObserverLinux::BlockSleep() { 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::BindOnce(&PowerObserverLinux::OnInhibitResponse, 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::BindOnce(&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 handler) { // In order to delay system shutdown when e.preventDefault() is invoked // on a powerMonitor 'shutdown' event, we need an org.freedesktop.login1 // shutdown delay lock. For more details see the "Taking Delay Locks" // section of https://www.freedesktop.org/wiki/Software/systemd/inhibit/ if (handler && !should_shutdown_) { BlockShutdown(); } else if (!handler && should_shutdown_) { UnblockShutdown(); } should_shutdown_ = std::move(handler); } void PowerObserverLinux::OnInhibitResponse(base::ScopedFD* scoped_fd, dbus::Response* response) { if (response != nullptr) { dbus::MessageReader reader(response); reader.PopFileDescriptor(scoped_fd); } } void PowerObserverLinux::OnPrepareForSleep(dbus::Signal* signal) { dbus::MessageReader reader(signal); bool suspending; if (!reader.PopBool(&suspending)) { LOG(ERROR) << "Invalid signal: " << signal->ToString(); return; } if (suspending) { observer_->OnSuspend(); UnblockSleep(); } else { BlockSleep(); observer_->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*/, const std::string& signal, bool success) { LOG_IF(WARNING, !success) << "Failed to connect to " << signal; } } // namespace electron