1
    2
    3
    4
    5
    6
    7
    8
    9
   10
   11
   12
   13
   14
   15
   16
   17
   18
   19
   20
   21
   22
   23
   24
   25
   26
   27
   28
   29
   30
   31
   32
   33
   34
   35
   36
   37
   38
   39
   40
   41
   42
   43
   44
   45
   46
   47
   48
   49
   50
   51
   52
   53
   54
   55
   56
   57
   58
   59
   60
   61
   62
   63
   64
   65
   66
   67
   68
   69
   70
   71
   72
   73
   74
   75
   76
   77
   78
   79
   80
   81
   82
   83
   84
   85
   86
   87
   88
   89
   90
   91
   92
   93
   94
   95
   96
   97
   98
   99
  100
  101
  102
  103
  104
  105
  106
  107
  108
  109
  110
  111
  112
  113
  114
  115
  116
  117
  118
  119
  120
  121
  122
  123
  124
  125
  126
  127
  128
  129
  130
  131
  132
  133
  134
  135
  136
  137
  138
  139
  140
  141
  142
  143
  144
  145
  146
  147
  148
  149
  150
  151
  152
  153
  154
  155
  156
  157
  158
  159
  160
  161
  162
  163
  164
  165
  166
  167
  168
  169
  170
  171
  172
  173
  174
  175
  176
  177
  178
  179
  180
  181
  182
  183
  184
  185
  186
  187
  188
  189
  190
  191
  192
  193
  194
  195
  196
  197
  198
  199
  200
  201
  202
  203
  204
  205
  206
  207
  208
  209
  210
  211
  212
  213
  214
  215
  216
  217
  218
  219
  220
  221
  222
  223
  224
  225
  226
  227
  228
  229
  230
  231
  232

content / browser / media / capture / mouse_cursor_overlay_controller.h [blame]

// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef CONTENT_BROWSER_MEDIA_CAPTURE_MOUSE_CURSOR_OVERLAY_CONTROLLER_H_
#define CONTENT_BROWSER_MEDIA_CAPTURE_MOUSE_CURSOR_OVERLAY_CONTROLLER_H_

#include <atomic>
#include <memory>

#include "base/memory/weak_ptr.h"
#include "base/sequence_checker.h"
#include "base/task/sequenced_task_runner.h"
#include "base/timer/timer.h"
#include "content/common/content_export.h"
#include "services/viz/privileged/mojom/compositing/frame_sink_video_capture.mojom.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/base/cursor/cursor.h"
#include "ui/gfx/geometry/point_f.h"
#include "ui/gfx/geometry/rect_f.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/native_widget_types.h"

namespace base {
class TickClock;
}  // namespace base

namespace content {

// MouseCursorOverlayController is used by FrameSinkVideoCaptureDevice to manage
// the mouse cursor overlay in the viz::FrameSinkVideoCapturer session based on
// the behavior of the mouse cursor reported by the windowing system.
//
// All parts of this class are meant to run on the UI BrowserThread, except for
// IsUserInteractingWithView(), which may be called from any thread. It is up to
// the client code to ensure the controller's lifetime while in use across
// multiple threads.
class CONTENT_EXPORT MouseCursorOverlayController {
 public:
  using Overlay = viz::mojom::FrameSinkVideoCaptureOverlay;

  MouseCursorOverlayController();

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

  ~MouseCursorOverlayController();

  // Sets a new target view to monitor for mouse cursor updates.
  void SetTargetView(gfx::NativeView view);

  // If the target view is not a gfx::NativeView (which is the case when
  // capturing a NSWindow on macOS), this function may be used to set the size
  // of the target. This function will only have an effect if SetTargetView has
  // not been called (it doesn't make sense to call both functions). The units
  // for |target_size| are different on different platforms (DIPs on macOS,
  // pixels on all other platforms).
  void SetTargetSize(const gfx::Size& target_size) {
    target_size_ = target_size;
  }

  // Takes ownership of and starts controlling the given |overlay|, invoking its
  // methods (and destruction) via the given |task_runner|.
  void Start(std::unique_ptr<Overlay> overlay,
             scoped_refptr<base::SequencedTaskRunner> task_runner);

  // Stops controlling the Overlay (passed to Start()) and schedules its
  // destruction.
  void Stop();

  // Returns true if the user has recently interacted with the target (by
  // moving or clicking the mouse).
  bool IsUserInteractingWithView() const;

  // Called from platform-specific code to report on mouse events within the
  // captured view. The units for |location| depend on the platform (DIPs on
  // macOS and pixels on other platforms).
  void OnMouseMoved(const gfx::PointF& location);
  void OnMouseClicked(const gfx::PointF& location);

  // Returns a weak pointer.
  base::WeakPtr<MouseCursorOverlayController> GetWeakPtr();

  bool ShouldSendMouseEvents() const { return should_send_mouse_events_; }

 private:
  friend class MouseCursorOverlayControllerBrowserTest;

  // Observes mouse events from the windowing system and reports them via
  // OnMouseMoved(), OnMouseClicked(), and OnMouseHasGoneIdle().
  class Observer;

  enum MouseMoveBehavior {
    kNotMoving,               // Mouse has not moved recently.
    kStartingToMove,          // Mouse has moved, but not significantly.
    kRecentlyMovedOrClicked,  // Sufficient mouse activity present.
  };

  // Called by the |mouse_activity_ended_timer_| once no mouse events have
  // occurred for kIdleTimeout. Also, called by platform-specific code when
  // changing the target view.
  void OnMouseHasGoneIdle();

  // Accessors for |mouse_move_behavior_atomic_|. See comments below.
  MouseMoveBehavior mouse_move_behavior() const {
    return mouse_move_behavior_atomic_.load(std::memory_order_relaxed);
  }
  void set_mouse_move_behavior(MouseMoveBehavior behavior) {
    mouse_move_behavior_atomic_.store(behavior, std::memory_order_relaxed);
  }

  // Send the latest mouse event reported by the captured view to the associated
  // mouse cursor overlay.
  void SendMouseEvent();

  // Examines the current mouse movement behavior, view properties, and cursor
  // changes to determine whether to show or hide the overlay. |location| is the
  // current mouse cursor location.
  void UpdateOverlay(const gfx::PointF& location);

  // Returns the current mouse cursor. The default "arrow pointer" cursor will
  // be returned in lieu of a null cursor.
  gfx::NativeCursor GetCurrentCursorOrDefault() const;

  // Computes where the overlay should be shown, in terms of relative
  // coordinates. This takes the view size, coordinate systems of the view and
  // cursor bitmap, and cursor hotspot offset; all into account.
  gfx::RectF ComputeRelativeBoundsForOverlay(const gfx::NativeCursor& cursor,
                                             const gfx::PointF& location) const;

  // Called after SetTargetView() to ignore mouse events from the
  // platform/toolkit and set a default mouse cursor. This is used by the
  // browser tests to prevent actual mouse movement from interfering with the
  // testing of the control logic.
  void DisconnectFromToolkitForTesting();

  // Returns the image of the mouse cursor.
  static SkBitmap GetCursorImage(const gfx::NativeCursor&);

  // Called from platform-specific code to report on mouse events within the
  // captured view.
  void OnMouseCoordinatesUpdated(const gfx::Point& coordinates);

  // Overrides the tick clock used by |this| for testing.
  void SetTickClockForTesting(const base::TickClock* tick_clock);

  // Platform-specific mouse event observer. Updated by SetTargetView().
  std::unique_ptr<Observer> observer_;

  // Updated in the mouse event handlers and used to decide whether the user is
  // interacting with the view and whether to update the overlay.
  gfx::PointF mouse_move_start_location_;
  base::RetainingOneShotTimer mouse_activity_ended_timer_;

  // Updated in the mouse event handlers and read by IsUserInteractingWithView()
  // (on any thread). This is not protected by a mutex since strict memory
  // ordering semantics are not necessary, just atomicity between threads. All
  // code should use the accessors to read or set this value.
  std::atomic<MouseMoveBehavior> mouse_move_behavior_atomic_;

  // The overlay being controlled, and the task runner to use to invoke its
  // methods and destruction.
  std::unique_ptr<Overlay> overlay_;
  scoped_refptr<base::SequencedTaskRunner> overlay_task_runner_;

  // The last-shown mouse cursor. UpdateOverlay() uses this to determine whether
  // to update the cursor image, or just the overlay position.
  gfx::NativeCursor last_cursor_ = gfx::NativeCursor();

  // This is empty if the overlay should be hidden. Otherwise, it represents a
  // shown overlay with a relative position within the view in terms of the
  // range [0.0,1.0). It can sometimes be a little bit outside of that range,
  // depending on the cursor's hotspot.
  gfx::RectF bounds_;

  // The target's size, if set explicitly by SetTargetSize.
  gfx::Size target_size_;

  // Whether to transmit mouse events to the renderer process for dispatching
  // it via the Captured Mouse Events API.
  // See https://screen-share.github.io/captured-mouse-events/
  bool should_send_mouse_events_ = false;

  // Latest coordinates reported to OnMouseCoordinatesUpdated() and timer to
  // postpone forwarding them to SendMouseEvent(), if necessary. Note that the
  // initial value of last_observed_coordinates_ does not matter here, since
  // it is always set in OnMouseCoordinatesUpdated() before being read in
  // SendMouseEvent().
  gfx::Point last_observed_coordinates_;
  base::OneShotTimer last_observed_coordinates_timer_;

  // Latest coordinates sent to the the overlay and time when it happened.
  std::optional<gfx::Point> last_emitted_coordinates_;
  base::TimeTicks last_emitted_coordinates_time_;

  // Everything except the constructor and IsUserInteractingWithView() must be
  // called on the UI BrowserThread.
  SEQUENCE_CHECKER(ui_sequence_checker_);

  // Tick clock used for controlling event delays.
  raw_ptr<const base::TickClock> tick_clock_ = nullptr;

  base::WeakPtrFactory<MouseCursorOverlayController> weak_factory_{this};

  // Minium movement before the cursor has been considered intentionally moved
  // by the user.
  static constexpr int kMinMovementPixels = 15;

  // Amount of time to elapse with no mouse activity before the cursor should
  // stop showing.
  static constexpr base::TimeDelta kIdleTimeout = base::Seconds(2);

  // Special value from the Captured Mouse Events specification to indicate that
  // the mouse is outside the captured view.
  // See
  // https://screen-share.github.io/captured-mouse-events/#captured-mouse-change-event
  static constexpr gfx::Point kOutsideSurface = {-1, -1};

  // The specification contains some hints to limit the frequency with which
  // events are fired. This is implemented using a minimal time to wait between
  // two reports of mouse coordinates (corresponding to a max frequency of 30
  // dispatched events per seconds).
  // See
  // https://screen-share.github.io/captured-mouse-events/#captured-mouse-change-event
  static constexpr base::TimeDelta kMinWaitInterval =
      base::Milliseconds(1000 / (30 - 1));
};

}  // namespace content

#endif  // CONTENT_BROWSER_MEDIA_CAPTURE_MOUSE_CURSOR_OVERLAY_CONTROLLER_H_