| // Copyright 2012 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ui/base/idle/idle.h" |
| |
| #include "base/memory/raw_ptr.h" |
| #include "base/notreached.h" |
| #include "base/time/time.h" |
| #include "build/config/linux/dbus/buildflags.h" |
| #include "ui/base/idle/idle_internal.h" |
| #include "ui/display/screen.h" |
| |
| #if BUILDFLAG(USE_DBUS) |
| #include "base/functional/bind.h" |
| #include "base/functional/callback_helpers.h" |
| #include "base/logging.h" |
| #include "base/memory/scoped_refptr.h" |
| #include "base/no_destructor.h" |
| #include "components/dbus/thread_linux/dbus_thread_linux.h" |
| #include "components/dbus/utils/name_has_owner.h" |
| #include "dbus/bus.h" |
| #include "dbus/message.h" |
| #include "dbus/object_path.h" |
| #include "dbus/object_proxy.h" |
| #endif |
| |
| namespace ui { |
| |
| #if BUILDFLAG(USE_DBUS) |
| |
| namespace { |
| |
| const char kMethodName[] = "GetActive"; |
| const char kSignalName[] = "ActiveChanged"; |
| |
| // Various names under which the service may be found in different Linux desktop |
| // environments. |
| struct Services { |
| const char* service_name; |
| const char* object_path; |
| const char* interface; |
| }; |
| constexpr auto kServices = std::to_array<Services>({ |
| // ksmserver, light-locker, etc. |
| {"org.freedesktop.ScreenSaver", "/org/freedesktop/ScreenSaver", |
| "org.freedesktop.ScreenSaver"}, |
| // cinnamon-screensaver |
| {"org.cinnamon.ScreenSaver", "/org/cinnamon/ScreenSaver", |
| "org.cinnamon.ScreenSaver"}, |
| // gnome-screensaver |
| {"org.gnome.ScreenSaver", "/org/gnome/ScreenSaver", |
| "org.gnome.ScreenSaver"}, |
| // mate-screensaver |
| {"org.mate.ScreenSaver", "/org/mate/ScreenSaver", "org.mate.ScreenSaver"}, |
| // xfce4-screensaver |
| {"org.xfce.ScreenSaver", "/org/xfce/ScreenSaver", "org.xfce.ScreenSaver"}, |
| }); |
| |
| constexpr size_t kServiceCount = sizeof(kServices) / sizeof(kServices[0]); |
| |
| // This class tries to find the name under which the ScreenSaver D-Bus service |
| // is registered, and if found the one, connects to the service and subscribes |
| // to its notifications. |
| class DBusScreenSaverWatcher { |
| public: |
| enum class LockState { |
| kUnknown, |
| kLocked, |
| kUnlocked, |
| }; |
| |
| DBusScreenSaverWatcher() : bus_(dbus_thread_linux::GetSharedSessionBus()) { |
| TryCurrentService(); |
| } |
| |
| LockState lock_state() const { return lock_state_; } |
| |
| private: |
| ~DBusScreenSaverWatcher() = default; |
| |
| // Starts the initialisation sequence for the current service. Failure at any |
| // step will increment the service counter and re-start the process. |
| void TryCurrentService() { |
| // Detach the proxy, if we have one from the previous attempt. |
| if (proxy_) { |
| CHECK_GT(current_service_, 0u); |
| proxy_ = nullptr; |
| bus_->RemoveObjectProxy( |
| kServices[current_service_ - 1].service_name, |
| dbus::ObjectPath(kServices[current_service_ - 1].object_path), |
| base::DoNothing()); |
| } |
| |
| if (current_service_ >= kServiceCount) { |
| if (current_service_ == kServiceCount) { |
| // Log the warning once. |
| LOG(WARNING) |
| << "None of the known D-Bus ScreenSaver services could be used."; |
| ++current_service_; |
| } |
| return; |
| } |
| |
| // Calling methods on a non-existent service will lead to a timeout rather |
| // than an immediate error, so check for service existence first. |
| dbus_utils::NameHasOwner( |
| bus_.get(), kServices[current_service_].service_name, |
| base::BindOnce(&DBusScreenSaverWatcher::OnServiceHasOwner, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| void OnServiceHasOwner(std::optional<bool> name_has_owner) { |
| DCHECK_LT(current_service_, kServiceCount); |
| |
| if (!name_has_owner.value_or(false)) { |
| VLOG(1) << kServices[current_service_].service_name |
| << " D-Bus service does not exist"; |
| ++current_service_; |
| return TryCurrentService(); |
| } |
| |
| // Now connect the ActiveChanged signal. |
| proxy_ = bus_->GetObjectProxy( |
| kServices[current_service_].service_name, |
| dbus::ObjectPath(kServices[current_service_].object_path)); |
| proxy_->ConnectToSignal( |
| kServices[current_service_].interface, kSignalName, |
| base::BindRepeating(&DBusScreenSaverWatcher::OnActiveChanged, |
| weak_factory_.GetWeakPtr()), |
| base::BindOnce(&DBusScreenSaverWatcher::OnActiveChangedSignalConnected, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| void OnActiveChangedSignalConnected(const std::string& interface, |
| const std::string& signal, |
| bool succeeded) { |
| DCHECK_LT(current_service_, kServiceCount); |
| DCHECK_EQ(interface, kServices[current_service_].interface); |
| DCHECK_EQ(signal, kSignalName); |
| |
| if (!succeeded) { |
| VLOG(1) << "Cannot connect to " << kSignalName << " signal of " |
| << interface << " D-Bus service"; |
| ++current_service_; |
| return TryCurrentService(); |
| } |
| |
| // Some service owners (e.g., gsd-screensaver-proxy) advertise the correct |
| // methods on org.freedesktop.ScreenSaver, but calling them will result in |
| // a NotImplemented DBus error. To ensure the service owner will send |
| // state change events, and to have the correct current state of the lock, |
| // make an explicit method call and check that no error is returned. |
| dbus::MethodCall method_call(kServices[current_service_].interface, |
| kMethodName); |
| proxy_->CallMethodWithErrorResponse( |
| &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT, |
| base::BindOnce(&DBusScreenSaverWatcher::OnGetActive, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| void OnGetActive(dbus::Response* response, dbus::ErrorResponse*) { |
| DCHECK_LT(current_service_, kServiceCount); |
| |
| if (!response || !UpdateLockState(response)) { |
| VLOG(1) |
| << "Call to " << kMethodName << " method of " |
| << kServices[current_service_].interface << " D-Bus service failed"; |
| ++current_service_; |
| return TryCurrentService(); |
| } |
| } |
| |
| void OnActiveChanged(dbus::Signal* signal) { UpdateLockState(signal); } |
| |
| bool UpdateLockState(dbus::Message* message) { |
| dbus::MessageReader reader(message); |
| bool active; |
| if (!reader.PopBool(&active) || reader.HasMoreData()) { |
| return false; |
| } |
| lock_state_ = active ? LockState::kLocked : LockState::kUnlocked; |
| return true; |
| } |
| |
| LockState lock_state_ = LockState::kUnknown; |
| |
| // Index of the service (in the kServices array) that is currently being |
| // initialised or used. A value out of bounds means that no working service |
| // was found. |
| size_t current_service_ = 0; |
| |
| scoped_refptr<dbus::Bus> bus_; |
| raw_ptr<dbus::ObjectProxy> proxy_ = nullptr; |
| |
| base::WeakPtrFactory<DBusScreenSaverWatcher> weak_factory_{this}; |
| }; |
| |
| DBusScreenSaverWatcher* GetDBusScreenSaverWatcher() { |
| static base::NoDestructor<DBusScreenSaverWatcher> impl; |
| return impl.get(); |
| } |
| |
| } // namespace |
| |
| #endif // BUILDFLAG(USE_DBUS) |
| |
| int CalculateIdleTime() { |
| auto* const screen = display::Screen::Get(); |
| // The screen can be nullptr in tests. |
| if (!screen) { |
| return 0; |
| } |
| return screen->CalculateIdleTime().InSeconds(); |
| } |
| |
| bool CheckIdleStateIsLocked() { |
| if (IdleStateForTesting().has_value()) { |
| return IdleStateForTesting().value() == IDLE_STATE_LOCKED; |
| } |
| |
| #if BUILDFLAG(USE_DBUS) |
| auto lock_state = GetDBusScreenSaverWatcher()->lock_state(); |
| if (lock_state != DBusScreenSaverWatcher::LockState::kUnknown) { |
| return lock_state == DBusScreenSaverWatcher::LockState::kLocked; |
| } |
| #endif |
| |
| auto* const screen = display::Screen::Get(); |
| // The screen can be nullptr in tests. |
| if (!screen) { |
| return false; |
| } |
| return screen->IsScreenSaverActive(); |
| } |
| |
| } // namespace ui |