910e62b5创建于 1月15日历史提交
// 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