chromium/ui/ozone/platform/drm/host/drm_cursor.cc

// Copyright 2015 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/ozone/platform/drm/host/drm_cursor.h"

#include <memory>
#include <utility>

#include "base/memory/scoped_refptr.h"
#include "base/task/current_thread.h"
#include "base/task/single_thread_task_runner.h"
#include "base/threading/thread_checker.h"
#include "base/time/time.h"
#include "base/trace_event/trace_event.h"
#include "build/chromeos_buildflags.h"
#include "ui/base/cursor/mojom/cursor_type.mojom.h"
#include "ui/gfx/geometry/point_conversions.h"
#include "ui/ozone/common/bitmap_cursor.h"
#include "ui/ozone/platform/drm/host/drm_window_host.h"
#include "ui/ozone/platform/drm/host/drm_window_host_manager.h"

#if BUILDFLAG(IS_CHROMEOS_ASH)
#include "ui/events/ozone/chromeos/cursor_controller.h"
#endif

namespace ui {
namespace {

using mojom::CursorType;

class NullProxy : public DrmCursorProxy {
 public:
  NullProxy() = default;

  NullProxy(const NullProxy&) = delete;
  NullProxy& operator=(const NullProxy&) = delete;

  ~NullProxy() override = default;

  void CursorSet(gfx::AcceleratedWidget window,
                 const std::vector<SkBitmap>& bitmaps,
                 const std::optional<gfx::Point>& point,
                 base::TimeDelta frame_delay) override {}
  void Move(gfx::AcceleratedWidget window, const gfx::Point& point) override {}
  void InitializeOnEvdevIfNecessary() override {}
};

}  // namespace

DrmCursor::DrmCursor(DrmWindowHostManager* window_manager)
    : ui_thread_(base::SingleThreadTaskRunner::GetCurrentDefault()),
      window_(gfx::kNullAcceleratedWidget),
      window_manager_(window_manager),
      proxy_(new NullProxy()) {
  DETACH_FROM_THREAD(evdev_thread_checker_);
}

DrmCursor::~DrmCursor() = default;

void DrmCursor::SetDrmCursorProxy(std::unique_ptr<DrmCursorProxy> proxy) {
  TRACE_EVENT0("drmcursor", "DrmCursor::SetDrmCursorProxy");
  DCHECK_CALLED_ON_VALID_THREAD(ui_thread_checker_);
  base::AutoLock lock(lock_);
  proxy_ = std::move(proxy);
  if (window_ != gfx::kNullAcceleratedWidget)
    SendCursorShowLocked();
}

void DrmCursor::ResetDrmCursorProxy() {
  TRACE_EVENT0("drmcursor", "DrmCursor::ResetDrmCursorProxy");
  DCHECK_CALLED_ON_VALID_THREAD(ui_thread_checker_);

  NullProxy* np = new NullProxy();
  base::AutoLock lock(lock_);
  proxy_.reset(np);
}

gfx::Point DrmCursor::GetBitmapLocationLocked()
    EXCLUSIVE_LOCKS_REQUIRED(lock_) {
  return gfx::ToFlooredPoint(location_) - cursor_->hotspot().OffsetFromOrigin();
}

void DrmCursor::SetCursor(gfx::AcceleratedWidget window,
                          scoped_refptr<BitmapCursor> platform_cursor) {
  TRACE_EVENT0("drmcursor", "DrmCursor::SetCursor");
  DCHECK_CALLED_ON_VALID_THREAD(ui_thread_checker_);
  DCHECK_NE(window, gfx::kNullAcceleratedWidget);
  DCHECK(platform_cursor);

  base::AutoLock lock(lock_);

  if (window_ != window || cursor_ == platform_cursor)
    return;

  cursor_ = platform_cursor;

  SendCursorShowLocked();
}

void DrmCursor::OnWindowAdded(gfx::AcceleratedWidget window,
                              const gfx::Rect& bounds_in_screen,
                              const gfx::Rect& cursor_confined_bounds) {
  TRACE_EVENT0("drmcursor", "DrmCursor::OnWindowAdded");
  DCHECK_CALLED_ON_VALID_THREAD(ui_thread_checker_);
  base::AutoLock lock(lock_);

  if (window_ == gfx::kNullAcceleratedWidget) {
    // First window added & cursor is not placed. Place it.
    window_ = window;
    display_bounds_in_screen_ = bounds_in_screen;
    confined_bounds_ = cursor_confined_bounds;
    SetCursorLocationLocked(gfx::PointF(cursor_confined_bounds.CenterPoint()));
  }
}

void DrmCursor::OnWindowRemoved(gfx::AcceleratedWidget window) {
  TRACE_EVENT0("drmcursor", "DrmCursor::OnWindowRemoved");
  DCHECK_CALLED_ON_VALID_THREAD(ui_thread_checker_);
  base::AutoLock lock(lock_);

  if (window_ == window) {
    // Try to find a new location for the cursor.
    DrmWindowHost* dest_window = window_manager_->GetPrimaryWindow();

    if (dest_window) {
      window_ = dest_window->GetAcceleratedWidget();
      display_bounds_in_screen_ = dest_window->GetBoundsInPixels();
      confined_bounds_ = dest_window->GetCursorConfinedBounds();
      SetCursorLocationLocked(gfx::PointF(confined_bounds_.CenterPoint()));
      SendCursorShowLocked();
    } else {
      window_ = gfx::kNullAcceleratedWidget;
      display_bounds_in_screen_ = gfx::Rect();
      confined_bounds_ = gfx::Rect();
      location_ = gfx::PointF();
    }
  }
}

void DrmCursor::CommitBoundsChange(
    gfx::AcceleratedWidget window,
    const gfx::Rect& new_display_bounds_in_screen,
    const gfx::Rect& new_confined_bounds) {
  TRACE_EVENT0("drmcursor", "DrmCursor::CommitBoundsChange");
  DCHECK_CALLED_ON_VALID_THREAD(ui_thread_checker_);
  base::AutoLock lock(lock_);

  if (window_ == window) {
    display_bounds_in_screen_ = new_display_bounds_in_screen;
    confined_bounds_ = new_confined_bounds;
    SetCursorLocationLocked(location_);
    SendCursorShowLocked();
  }
}

void DrmCursor::MoveCursorTo(gfx::AcceleratedWidget window,
                             const gfx::PointF& location) {
  TRACE_EVENT0("drmcursor", "DrmCursor::MoveCursorTo (window)");
  DCHECK_CALLED_ON_VALID_THREAD(ui_thread_checker_);
  base::AutoLock lock(lock_);
  gfx::AcceleratedWidget old_window = window_;

  if (window != old_window) {
    // When moving between displays, hide the cursor on the old display
    // prior to showing it on the new display.
    if (old_window != gfx::kNullAcceleratedWidget)
      SendCursorHideLocked();

    // TODO(rjk): pass this in?
    DrmWindowHost* drm_window_host = window_manager_->GetWindow(window);
    display_bounds_in_screen_ = drm_window_host->GetBoundsInPixels();
    confined_bounds_ = drm_window_host->GetCursorConfinedBounds();
    window_ = window;
  }

  SetCursorLocationLocked(location);
  if (window != old_window)
    SendCursorShowLocked();
  else
    SendCursorMoveLocked();
}

void DrmCursor::MoveCursorTo(const gfx::PointF& screen_location) {
  TRACE_EVENT0("drmcursor", "DrmCursor::MoveCursorTo");
  if (ui_thread_->BelongsToCurrentThread())
    MoveCursorToOnUiThread(screen_location);
  else
    MoveCursorToOnEvdevThread(screen_location);
}

void DrmCursor::MoveCursorToOnUiThread(const gfx::PointF& screen_location) {
  DCHECK_CALLED_ON_VALID_THREAD(ui_thread_checker_);

  const auto* window =
      window_manager_->GetWindowAt(gfx::ToRoundedPoint(screen_location));
  if (!window) {
    // See b/221758935#comment18 for why we need to silently ignore this error
    // here.
    return;
  }

  auto location_in_window =
      screen_location - window->GetBoundsInPixels().OffsetFromOrigin();
  MoveCursorTo(window->GetAcceleratedWidget(), location_in_window);
}

void DrmCursor::MoveCursorToOnEvdevThread(const gfx::PointF& screen_location) {
  DCHECK_CALLED_ON_VALID_THREAD(evdev_thread_checker_);

  base::AutoLock lock(lock_);

  // TODO(spang): Moving between windows doesn't work here, but
  // is not needed for current uses.
  SetCursorLocationLocked(screen_location -
                          display_bounds_in_screen_.OffsetFromOrigin());

  SendCursorMoveLocked();
}

void DrmCursor::MoveCursor(const gfx::Vector2dF& delta) {
#if DCHECK_IS_ON()
  // We explicitly check for DCHECK_IS_ON() as |ui_thread_checker_| and
  // |evdev_thread_checker_| do not exist on non-DCHECK builds.
  DCHECK(evdev_thread_checker_.CalledOnValidThread() ||
         ui_thread_checker_.CalledOnValidThread());
#endif
  TRACE_EVENT0("drmcursor", "DrmCursor::MoveCursor");
  base::AutoLock lock(lock_);

  if (window_ == gfx::kNullAcceleratedWidget)
    return;

#if BUILDFLAG(IS_CHROMEOS_ASH)
  gfx::Vector2dF transformed_delta = delta;
  ui::CursorController::GetInstance()->ApplyCursorConfigForWindow(
      window_, &transformed_delta);
  SetCursorLocationLocked(location_ + transformed_delta);
#else
  SetCursorLocationLocked(location_ + delta);
#endif
  SendCursorMoveLocked();
}

bool DrmCursor::IsCursorVisible() {
  base::AutoLock lock(lock_);
  return cursor_ != nullptr && cursor_->type() != CursorType::kNone;
}

gfx::PointF DrmCursor::GetLocation() {
  base::AutoLock lock(lock_);
  return location_ + display_bounds_in_screen_.OffsetFromOrigin();
}

gfx::Rect DrmCursor::GetCursorConfinedBounds() {
  base::AutoLock lock(lock_);
  return confined_bounds_ + display_bounds_in_screen_.OffsetFromOrigin();
}

void DrmCursor::InitializeOnEvdev() {
  DCHECK_CALLED_ON_VALID_THREAD(evdev_thread_checker_);
  base::AutoLock lock(lock_);
  proxy_->InitializeOnEvdevIfNecessary();
}

void DrmCursor::SetCursorLocationLocked(const gfx::PointF& location)
    EXCLUSIVE_LOCKS_REQUIRED(lock_) {
  gfx::PointF clamped_location = location;
  clamped_location.SetToMax(gfx::PointF(confined_bounds_.origin()));
  // Right and bottom edges are exclusive.
  clamped_location.SetToMin(
      gfx::PointF(confined_bounds_.right() - 1, confined_bounds_.bottom() - 1));

  location_ = clamped_location;
#if BUILDFLAG(IS_CHROMEOS_ASH)
  ui::CursorController::GetInstance()->SetCursorLocation(location_);
#endif
}

void DrmCursor::SendCursorShowLocked() EXCLUSIVE_LOCKS_REQUIRED(lock_) {
  if (!cursor_ || cursor_->type() == CursorType::kNone) {
    SendCursorHideLocked();
    return;
  }

  CursorSetLockTested(window_, cursor_->bitmaps(), GetBitmapLocationLocked(),
                      cursor_->frame_delay());
}

void DrmCursor::SendCursorHideLocked() EXCLUSIVE_LOCKS_REQUIRED(lock_) {
  CursorSetLockTested(window_, std::vector<SkBitmap>(), std::nullopt,
                      base::TimeDelta());
}

void DrmCursor::SendCursorMoveLocked() EXCLUSIVE_LOCKS_REQUIRED(lock_) {
  if (!cursor_ || cursor_->type() == CursorType::kNone)
    return;

  MoveLockTested(window_, GetBitmapLocationLocked());
}

// Lock-testing helpers.
void DrmCursor::CursorSetLockTested(gfx::AcceleratedWidget window,
                                    const std::vector<SkBitmap>& bitmaps,
                                    const std::optional<gfx::Point>& point,
                                    base::TimeDelta frame_delay) {
  lock_.AssertAcquired();
  proxy_->CursorSet(window, bitmaps, point, frame_delay);
}

void DrmCursor::MoveLockTested(gfx::AcceleratedWidget window,
                               const gfx::Point& point) {
  lock_.AssertAcquired();
  proxy_->Move(window, point);
}

}  // namespace ui