#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";
struct Services {
const char* service_name;
const char* object_path;
const char* interface;
};
constexpr auto kServices = std::to_array<Services>({
{"org.freedesktop.ScreenSaver", "/org/freedesktop/ScreenSaver",
"org.freedesktop.ScreenSaver"},
{"org.cinnamon.ScreenSaver", "/org/cinnamon/ScreenSaver",
"org.cinnamon.ScreenSaver"},
{"org.gnome.ScreenSaver", "/org/gnome/ScreenSaver",
"org.gnome.ScreenSaver"},
{"org.mate.ScreenSaver", "/org/mate/ScreenSaver", "org.mate.ScreenSaver"},
{"org.xfce.ScreenSaver", "/org/xfce/ScreenSaver", "org.xfce.ScreenSaver"},
});
constexpr size_t kServiceCount = sizeof(kServices) / sizeof(kServices[0]);
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;
void TryCurrentService() {
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(WARNING)
<< "None of the known D-Bus ScreenSaver services could be used.";
++current_service_;
}
return;
}
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();
}
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();
}
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;
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();
}
}
#endif
int CalculateIdleTime() {
auto* const screen = display::Screen::Get();
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();
if (!screen) {
return false;
}
return screen->IsScreenSaverActive();
}
}