video_recording_watcher.cc 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831
  1. // Copyright 2020 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "ash/capture_mode/video_recording_watcher.h"
  5. #include <memory>
  6. #include "ash/accessibility/magnifier/docked_magnifier_controller.h"
  7. #include "ash/capture_mode/capture_mode_camera_controller.h"
  8. #include "ash/capture_mode/capture_mode_camera_preview_view.h"
  9. #include "ash/capture_mode/capture_mode_constants.h"
  10. #include "ash/capture_mode/capture_mode_controller.h"
  11. #include "ash/capture_mode/capture_mode_metrics.h"
  12. #include "ash/capture_mode/recording_overlay_controller.h"
  13. #include "ash/constants/ash_features.h"
  14. #include "ash/projector/projector_controller_impl.h"
  15. #include "ash/shell.h"
  16. #include "ash/style/ash_color_id.h"
  17. #include "ash/style/ash_color_provider.h"
  18. #include "ash/wm/desks/desks_util.h"
  19. #include "ash/wm/mru_window_tracker.h"
  20. #include "ash/wm/tablet_mode/tablet_mode_controller.h"
  21. #include "base/check.h"
  22. #include "base/check_op.h"
  23. #include "base/notreached.h"
  24. #include "base/time/time.h"
  25. #include "third_party/skia/include/core/SkBitmap.h"
  26. #include "ui/aura/window.h"
  27. #include "ui/aura/window_tree_host.h"
  28. #include "ui/compositor/layer.h"
  29. #include "ui/compositor/paint_recorder.h"
  30. #include "ui/display/screen.h"
  31. #include "ui/gfx/canvas.h"
  32. #include "ui/gfx/geometry/dip_util.h"
  33. #include "ui/gfx/geometry/point_f.h"
  34. #include "ui/gfx/geometry/rect_f.h"
  35. #include "ui/gfx/geometry/size_f.h"
  36. #include "ui/gfx/native_widget_types.h"
  37. #include "ui/gfx/scoped_canvas.h"
  38. #include "ui/wm/core/coordinate_conversion.h"
  39. #include "ui/wm/core/cursor_lookup.h"
  40. #include "ui/wm/public/activation_client.h"
  41. namespace ash {
  42. namespace {
  43. // Recording is performed at a rate of 30 FPS. Any non-pressed/-released mouse
  44. // events that are too frequent will be throttled. We use the frame duration as
  45. // the minimum delay between any two successive such events that we use to
  46. // update the cursor overlay.
  47. constexpr base::TimeDelta kCursorEventsThrottleDelay = base::Hertz(30);
  48. // Window resizes can be done on many intermediate steps. This delay is used to
  49. // throttle these resize events so that we send the final size of the window to
  50. // the recording service when it stabilizes.
  51. constexpr base::TimeDelta kWindowSizeChangeThrottleDelay =
  52. base::Milliseconds(250);
  53. // Returns true if |window_1| and |window_2| are both windows that belong to
  54. // the same Desk. Note that it will return false for windows that don't belong
  55. // to any desk (such as always-on-top windows or PIPs).
  56. bool AreWindowsOnSameDesk(aura::Window* window_1, aura::Window* window_2) {
  57. auto* container_1 = desks_util::GetDeskContainerForContext(window_1);
  58. auto* container_2 = desks_util::GetDeskContainerForContext(window_2);
  59. return container_1 && container_2 && container_1 == container_2;
  60. }
  61. // Gets the mouse cursor location in the coordinates of the given |window|. Use
  62. // this if a mouse event is not available.
  63. gfx::PointF GetCursorLocationInWindow(aura::Window* window) {
  64. gfx::PointF cursor_point(
  65. display::Screen::GetScreen()->GetCursorScreenPoint());
  66. wm::ConvertPointFromScreen(window, &cursor_point);
  67. return cursor_point;
  68. }
  69. // Gets the location of the given mouse |event| in the coordinates of the given
  70. // |window|.
  71. gfx::PointF GetEventLocationInWindow(aura::Window* window,
  72. const ui::MouseEvent& event) {
  73. aura::Window* target = static_cast<aura::Window*>(event.target());
  74. gfx::PointF location = event.location_f();
  75. if (target != window)
  76. aura::Window::ConvertPointToTarget(target, window, &location);
  77. return location;
  78. }
  79. // Returns the cursor overlay bounds as defined by the documentation of the
  80. // FrameSinkVideoCaptureOverlay. The bounds should be relative within the bounds
  81. // of the recorded frame sink (i.e. in the range [0.f, 1.f) for both cursor
  82. // origin and size).
  83. gfx::RectF GetCursorOverlayBounds(
  84. const aura::Window* recorded_window,
  85. const gfx::PointF& location_in_recorded_window,
  86. const gfx::Point& cursor_hotspot,
  87. float cursor_image_scale_factor,
  88. const SkBitmap& cursor_bitmap) {
  89. DCHECK(recorded_window);
  90. DCHECK_GT(cursor_image_scale_factor, 0);
  91. // The video size, and the resolution constraints will be matching the size of
  92. // the recorded window (whether a root or a non-root window). Hence, the
  93. // bounds of the cursor overlay should be relative to that size.
  94. const auto window_size = recorded_window->bounds().size();
  95. if (window_size.IsEmpty())
  96. return gfx::RectF();
  97. const gfx::PointF cursor_hotspot_dip =
  98. gfx::ConvertPointToDips(cursor_hotspot, cursor_image_scale_factor);
  99. const gfx::SizeF cursor_size_dip = gfx::ConvertSizeToDips(
  100. gfx::SizeF(cursor_bitmap.width(), cursor_bitmap.height()),
  101. cursor_image_scale_factor);
  102. gfx::RectF cursor_relative_bounds(
  103. location_in_recorded_window - cursor_hotspot_dip.OffsetFromOrigin(),
  104. cursor_size_dip);
  105. cursor_relative_bounds.Scale(1.f / window_size.width(),
  106. 1.f / window_size.height());
  107. return cursor_relative_bounds;
  108. }
  109. CameraPreviewView* GetCameraPreviewView() {
  110. return CaptureModeController::Get()
  111. ->camera_controller()
  112. ->camera_preview_view();
  113. }
  114. } // namespace
  115. // -----------------------------------------------------------------------------
  116. // RecordedWindowRootObserver:
  117. // Defines an observer to observe the hierarchy changes of the root window under
  118. // which the recorded window resides. This is only constructed when performing a
  119. // window recording type.
  120. class RecordedWindowRootObserver : public aura::WindowObserver {
  121. public:
  122. RecordedWindowRootObserver(aura::Window* root, VideoRecordingWatcher* owner)
  123. : root_(root), owner_(owner) {
  124. DCHECK(root_);
  125. DCHECK(owner_);
  126. DCHECK(root_->IsRootWindow());
  127. DCHECK_EQ(owner_->recording_source_, CaptureModeSource::kWindow);
  128. root_->AddObserver(this);
  129. }
  130. RecordedWindowRootObserver(const RecordedWindowRootObserver&) = delete;
  131. RecordedWindowRootObserver& operator=(const RecordedWindowRootObserver&) =
  132. delete;
  133. ~RecordedWindowRootObserver() override { root_->RemoveObserver(this); }
  134. // aura::WindowObserver:
  135. void OnWindowHierarchyChanged(const HierarchyChangeParams& params) override {
  136. DCHECK_EQ(params.receiver, root_);
  137. owner_->OnRootHierarchyChanged(params.target);
  138. }
  139. void OnWindowDestroying(aura::Window* window) override {
  140. // We should never get here, as the recorded window gets moved to a
  141. // different display before the root of another is destroyed. So this root
  142. // observer should have been destroyed already.
  143. NOTREACHED();
  144. }
  145. private:
  146. aura::Window* const root_;
  147. VideoRecordingWatcher* const owner_;
  148. };
  149. // -----------------------------------------------------------------------------
  150. // VideoRecordingWatcher:
  151. VideoRecordingWatcher::VideoRecordingWatcher(
  152. CaptureModeController* controller,
  153. aura::Window* window_being_recorded,
  154. mojo::PendingRemote<viz::mojom::FrameSinkVideoCaptureOverlay>
  155. cursor_capture_overlay,
  156. bool projector_mode)
  157. : controller_(controller),
  158. cursor_manager_(Shell::Get()->cursor_manager()),
  159. window_being_recorded_(window_being_recorded),
  160. current_root_(window_being_recorded->GetRootWindow()),
  161. recording_source_(controller_->source()),
  162. cursor_capture_overlay_remote_(std::move(cursor_capture_overlay)),
  163. is_in_projector_mode_(projector_mode) {
  164. DCHECK(controller_);
  165. DCHECK(window_being_recorded_);
  166. DCHECK(current_root_);
  167. DCHECK(!is_in_projector_mode_ || features::IsProjectorEnabled());
  168. if (!window_being_recorded_->IsRootWindow()) {
  169. DCHECK_EQ(recording_source_, CaptureModeSource::kWindow);
  170. non_root_window_capture_request_ =
  171. window_being_recorded_->MakeWindowCapturable();
  172. root_observer_ =
  173. std::make_unique<RecordedWindowRootObserver>(current_root_, this);
  174. Shell::Get()->activation_client()->AddObserver(this);
  175. } else {
  176. // We only need to observe the changes in the state of the software-
  177. // composited cursor when recording a root window (i.e. fullscreen or
  178. // partial region capture), since the software cursor is in the layer
  179. // subtree of the root window, and will be captured by the frame sink video
  180. // capturer automatically without the need for the cursor overlay. In this
  181. // case we need to avoid producing a video with two overlapping cursors.
  182. // When recording a window however, the software cursor is not in its layer
  183. // subtree, and has to always be captured using the cursor overlay.
  184. auto* cursor_window_controller =
  185. Shell::Get()->window_tree_host_manager()->cursor_window_controller();
  186. // Note that the software cursor might have already been enabled prior to
  187. // the recording starting.
  188. force_cursor_overlay_hidden_ =
  189. cursor_window_controller->is_cursor_compositing_enabled();
  190. cursor_window_controller->AddObserver(this);
  191. }
  192. if (recording_source_ == CaptureModeSource::kRegion)
  193. partial_region_bounds_ = controller_->user_capture_region();
  194. display::Screen::GetScreen()->AddObserver(this);
  195. window_being_recorded_->AddObserver(this);
  196. TabletModeController::Get()->AddObserver(this);
  197. // Note the following:
  198. // 1- We add |this| as a pre-target handler of the |window_being_recorded_| as
  199. // opposed to |Env|. This ensures that we only get mouse events when the
  200. // window being recorded is the target. This is more efficient since we
  201. // won't get any event when the curosr is in a different display, or
  202. // targeting a different window.
  203. // 2- We use the |kAccessibility| priority to ensure that we get these events
  204. // before other pre-target handlers can consume them (e.g. when opening a
  205. // capture mode session to take a screenshot while recording a video).
  206. window_being_recorded_->AddPreTargetHandler(
  207. this, ui::EventTarget::Priority::kAccessibility);
  208. controller_->camera_controller()->OnRecordingStarted(is_in_projector_mode_);
  209. if (is_in_projector_mode_) {
  210. recording_overlay_controller_ =
  211. std::make_unique<RecordingOverlayController>(window_being_recorded_,
  212. GetOverlayWidgetBounds());
  213. }
  214. if (features::IsProjectorEnabled()) {
  215. ProjectorControllerImpl::Get()->OnRecordingStarted(current_root_,
  216. is_in_projector_mode_);
  217. }
  218. }
  219. VideoRecordingWatcher::~VideoRecordingWatcher() {
  220. DCHECK(is_shutting_down_);
  221. }
  222. void VideoRecordingWatcher::ToggleRecordingOverlayEnabled() {
  223. DCHECK(is_in_projector_mode_);
  224. DCHECK(!is_shutting_down_);
  225. DCHECK(recording_overlay_controller_);
  226. recording_overlay_controller_->Toggle();
  227. }
  228. void VideoRecordingWatcher::ShutDown() {
  229. is_shutting_down_ = true;
  230. DCHECK(window_being_recorded_);
  231. window_size_change_throttle_timer_.Stop();
  232. cursor_events_throttle_timer_.Stop();
  233. cursor_capture_overlay_remote_.reset();
  234. root_observer_.reset();
  235. recording_overlay_controller_.reset();
  236. dimmers_.clear();
  237. if (features::IsProjectorEnabled())
  238. ProjectorControllerImpl::Get()->OnRecordingEnded(is_in_projector_mode_);
  239. window_being_recorded_->RemovePreTargetHandler(this);
  240. TabletModeController::Get()->RemoveObserver(this);
  241. if (recording_source_ == CaptureModeSource::kWindow) {
  242. Shell::Get()->activation_client()->RemoveObserver(this);
  243. } else {
  244. Shell::Get()
  245. ->window_tree_host_manager()
  246. ->cursor_window_controller()
  247. ->RemoveObserver(this);
  248. }
  249. // Move the `non_root_window_capture_request_` so that the
  250. // `window_being_recorded_` is not capturable.
  251. auto to_be_removed_request = std::move(non_root_window_capture_request_);
  252. window_being_recorded_->RemoveObserver(this);
  253. display::Screen::GetScreen()->RemoveObserver(this);
  254. controller_->camera_controller()->OnRecordingEnded();
  255. }
  256. aura::Window* VideoRecordingWatcher::GetCameraPreviewParentWindow() const {
  257. DCHECK(window_being_recorded_);
  258. return window_being_recorded_->IsRootWindow()
  259. ? window_being_recorded_->GetChildById(
  260. kShellWindowId_MenuContainer)
  261. : window_being_recorded_;
  262. }
  263. gfx::Rect VideoRecordingWatcher::GetCameraPreviewConfineBounds() const {
  264. DCHECK(window_being_recorded_);
  265. switch (recording_source_) {
  266. case CaptureModeSource::kFullscreen:
  267. return display::Screen::GetScreen()
  268. ->GetDisplayNearestWindow(window_being_recorded_)
  269. .work_area();
  270. case CaptureModeSource::kRegion: {
  271. gfx::Rect capture_region = GetEffectivePartialRegionBounds();
  272. wm::ConvertRectToScreen(current_root_, &capture_region);
  273. return capture_region;
  274. }
  275. case CaptureModeSource::kWindow:
  276. return gfx::Rect(window_being_recorded_->bounds().size());
  277. }
  278. }
  279. void VideoRecordingWatcher::OnWindowParentChanged(aura::Window* window,
  280. aura::Window* parent) {
  281. DCHECK_EQ(window, window_being_recorded_);
  282. DCHECK_EQ(recording_source_, CaptureModeSource::kWindow);
  283. UpdateLayerStackingAndDimmers();
  284. }
  285. void VideoRecordingWatcher::OnWindowVisibilityChanged(aura::Window* window,
  286. bool visible) {
  287. if (window == window_being_recorded_)
  288. UpdateShouldPaintLayer();
  289. }
  290. void VideoRecordingWatcher::OnWindowBoundsChanged(
  291. aura::Window* window,
  292. const gfx::Rect& old_bounds,
  293. const gfx::Rect& new_bounds,
  294. ui::PropertyChangeReason reason) {
  295. if (is_in_projector_mode_)
  296. recording_overlay_controller_->SetBounds(GetOverlayWidgetBounds());
  297. if (recording_source_ != CaptureModeSource::kWindow)
  298. return;
  299. // We care only about size changes, since the location of the window won't
  300. // affect the recorded video frames of it, however, the size of the window
  301. // affects the size of the frames.
  302. if (old_bounds.size() == new_bounds.size())
  303. return;
  304. window_size_change_throttle_timer_.Start(
  305. FROM_HERE, kWindowSizeChangeThrottleDelay, this,
  306. &VideoRecordingWatcher::OnWindowSizeChangeThrottleTimerFiring);
  307. // The bounds of the camera preview should be updated if the bounds of the
  308. // window being recorded is changed.
  309. controller_->camera_controller()->MaybeUpdatePreviewWidget();
  310. }
  311. void VideoRecordingWatcher::OnWindowOpacitySet(
  312. aura::Window* window,
  313. ui::PropertyChangeReason reason) {
  314. if (window == window_being_recorded_)
  315. UpdateShouldPaintLayer();
  316. }
  317. void VideoRecordingWatcher::OnWindowStackingChanged(aura::Window* window) {
  318. DCHECK_EQ(window, window_being_recorded_);
  319. DCHECK_EQ(recording_source_, CaptureModeSource::kWindow);
  320. UpdateLayerStackingAndDimmers();
  321. }
  322. void VideoRecordingWatcher::OnWindowDestroying(aura::Window* window) {
  323. DCHECK_EQ(window, window_being_recorded_);
  324. // EndVideoRecording() destroys |this|. No need to remove observer here, since
  325. // it will be done in the destructor.
  326. controller_->EndVideoRecording(EndRecordingReason::kDisplayOrWindowClosing);
  327. }
  328. void VideoRecordingWatcher::OnWindowDestroyed(aura::Window* window) {
  329. DCHECK_EQ(window, window_being_recorded_);
  330. // We should never get here, since OnWindowDestroying() calls
  331. // EndVideoRecording() which deletes us.
  332. NOTREACHED();
  333. }
  334. void VideoRecordingWatcher::OnWindowRemovingFromRootWindow(
  335. aura::Window* window,
  336. aura::Window* new_root) {
  337. DCHECK_EQ(window, window_being_recorded_);
  338. DCHECK_EQ(recording_source_, CaptureModeSource::kWindow);
  339. root_observer_.reset();
  340. current_root_ = new_root;
  341. if (!new_root) {
  342. // EndVideoRecording() destroys |this|.
  343. controller_->EndVideoRecording(EndRecordingReason::kDisplayOrWindowClosing);
  344. return;
  345. }
  346. root_observer_ =
  347. std::make_unique<RecordedWindowRootObserver>(current_root_, this);
  348. controller_->OnRecordedWindowChangingRoot(window_being_recorded_, new_root);
  349. if (is_in_projector_mode_)
  350. ProjectorControllerImpl::Get()->OnRecordedWindowChangingRoot(new_root);
  351. }
  352. void VideoRecordingWatcher::OnPaintLayer(const ui::PaintContext& context) {
  353. if (!should_paint_layer_)
  354. return;
  355. DCHECK_NE(recording_source_, CaptureModeSource::kFullscreen);
  356. ui::PaintRecorder recorder(context, layer()->size());
  357. gfx::Canvas* canvas = recorder.canvas();
  358. canvas->DrawColor(capture_mode::kDimmingShieldColor);
  359. // We don't draw a region border around the recorded window. We just paint the
  360. // above shield as a backdrop.
  361. if (recording_source_ == CaptureModeSource::kWindow)
  362. return;
  363. gfx::ScopedCanvas scoped_canvas(canvas);
  364. const float dsf = canvas->UndoDeviceScaleFactor();
  365. gfx::Rect region =
  366. gfx::ScaleToEnclosingRect(GetEffectivePartialRegionBounds(), dsf);
  367. region.Inset(-capture_mode::kCaptureRegionBorderStrokePx);
  368. canvas->FillRect(region, SK_ColorTRANSPARENT, SkBlendMode::kClear);
  369. // Draw the region border.
  370. cc::PaintFlags border_flags;
  371. border_flags.setColor(capture_mode::kRegionBorderColor);
  372. border_flags.setStyle(cc::PaintFlags::kStroke_Style);
  373. border_flags.setStrokeWidth(capture_mode::kCaptureRegionBorderStrokePx);
  374. canvas->DrawRect(gfx::RectF(region), border_flags);
  375. }
  376. void VideoRecordingWatcher::OnWindowActivated(ActivationReason reason,
  377. aura::Window* gained_active,
  378. aura::Window* lost_active) {
  379. DCHECK_EQ(recording_source_, CaptureModeSource::kWindow);
  380. UpdateLayerStackingAndDimmers();
  381. }
  382. void VideoRecordingWatcher::OnDisplayMetricsChanged(
  383. const display::Display& display,
  384. uint32_t metrics) {
  385. // A change in the work area, could mean that the docked magnifier state has
  386. // changed, therefore we must update the overlay widget's bounds if any.
  387. if (is_in_projector_mode_ && (metrics & DISPLAY_METRIC_WORK_AREA))
  388. recording_overlay_controller_->SetBounds(GetOverlayWidgetBounds());
  389. if (!(metrics &
  390. (DISPLAY_METRIC_BOUNDS | DISPLAY_METRIC_ROTATION |
  391. DISPLAY_METRIC_DEVICE_SCALE_FACTOR | DISPLAY_METRIC_WORK_AREA))) {
  392. return;
  393. }
  394. const int64_t display_id =
  395. display::Screen::GetScreen()->GetDisplayNearestWindow(current_root_).id();
  396. if (display_id != display.id())
  397. return;
  398. const auto& root_bounds = current_root_->bounds();
  399. controller_->PushNewRootSizeToRecordingService(
  400. root_bounds.size(), current_root_->GetHost()->device_scale_factor());
  401. // The bounds of camera preview should be updated accordingly if the display
  402. // metrics is changed. When the capture source is `kWindow`, it will be
  403. // handled in `OnWindowBoundsChanged`;
  404. if (recording_source_ != CaptureModeSource::kWindow)
  405. controller_->camera_controller()->MaybeUpdatePreviewWidget();
  406. // We don't show a dimming overlay when recording a fullscreen.
  407. if (recording_source_ == CaptureModeSource::kFullscreen)
  408. return;
  409. DCHECK(layer());
  410. layer()->SetBounds(root_bounds);
  411. }
  412. void VideoRecordingWatcher::OnDimmedWindowDestroying(
  413. aura::Window* dimmed_window) {
  414. dimmers_.erase(dimmed_window);
  415. }
  416. void VideoRecordingWatcher::OnDimmedWindowParentChanged(
  417. aura::Window* dimmed_window) {
  418. // If the dimmed window moves to another display or a different desk, we no
  419. // longer dim it.
  420. if (dimmed_window->GetRootWindow() != current_root_ ||
  421. !AreWindowsOnSameDesk(dimmed_window, window_being_recorded_)) {
  422. dimmers_.erase(dimmed_window);
  423. }
  424. }
  425. void VideoRecordingWatcher::OnKeyEvent(ui::KeyEvent* event) {
  426. if (event->type() != ui::ET_KEY_PRESSED)
  427. return;
  428. auto* camera_preview_view = GetCameraPreviewView();
  429. if (camera_preview_view && camera_preview_view->MaybeHandleKeyEvent(event)) {
  430. event->StopPropagation();
  431. event->SetHandled();
  432. return;
  433. }
  434. }
  435. void VideoRecordingWatcher::OnMouseEvent(ui::MouseEvent* event) {
  436. switch (event->type()) {
  437. case ui::ET_MOUSEWHEEL:
  438. case ui::ET_MOUSE_CAPTURE_CHANGED:
  439. return;
  440. case ui::ET_MOUSE_PRESSED: {
  441. auto* camera_preview_view = GetCameraPreviewView();
  442. if (camera_preview_view)
  443. camera_preview_view->MaybeBlurFocus(*event);
  444. }
  445. [[fallthrough]];
  446. case ui::ET_MOUSE_RELEASED:
  447. // Pressed/released events are important, so we handle them immediately.
  448. UpdateCursorOverlayNow(
  449. GetEventLocationInWindow(window_being_recorded_, *event));
  450. return;
  451. default:
  452. UpdateOrThrottleCursorOverlay(
  453. GetEventLocationInWindow(window_being_recorded_, *event));
  454. }
  455. }
  456. void VideoRecordingWatcher::OnTabletModeStarted() {
  457. UpdateCursorOverlayNow(gfx::PointF());
  458. }
  459. void VideoRecordingWatcher::OnTabletModeEnded() {
  460. UpdateCursorOverlayNow(GetCursorLocationInWindow(window_being_recorded_));
  461. }
  462. void VideoRecordingWatcher::OnCursorCompositingStateChanged(bool enabled) {
  463. DCHECK_NE(recording_source_, CaptureModeSource::kWindow);
  464. force_cursor_overlay_hidden_ = enabled;
  465. UpdateCursorOverlayNow(
  466. force_cursor_overlay_hidden_
  467. ? gfx::PointF()
  468. : GetCursorLocationInWindow(window_being_recorded_));
  469. }
  470. gfx::Rect VideoRecordingWatcher::GetEffectivePartialRegionBounds() const {
  471. DCHECK_EQ(recording_source_, CaptureModeSource::kRegion);
  472. // TODO(afakhry): Consider having the region to anchor to the nearest corner,
  473. // so that screen rotation doesn't result in the apparent change of the region
  474. // position. Discussion with PM/UX determined that this is a low priority for
  475. // now.
  476. gfx::Rect result = partial_region_bounds_;
  477. result.AdjustToFit(current_root_->bounds());
  478. return result;
  479. }
  480. bool VideoRecordingWatcher::IsWindowDimmedForTesting(
  481. aura::Window* window) const {
  482. return dimmers_.contains(window);
  483. }
  484. void VideoRecordingWatcher::BindCursorOverlayForTesting(
  485. mojo::PendingRemote<viz::mojom::FrameSinkVideoCaptureOverlay> overlay) {
  486. cursor_capture_overlay_remote_.reset();
  487. cursor_capture_overlay_remote_.Bind(std::move(overlay));
  488. }
  489. void VideoRecordingWatcher::FlushCursorOverlayForTesting() {
  490. cursor_capture_overlay_remote_.FlushForTesting();
  491. }
  492. void VideoRecordingWatcher::SendThrottledWindowSizeChangedNowForTesting() {
  493. window_size_change_throttle_timer_.FireNow();
  494. }
  495. void VideoRecordingWatcher::SetLayer(std::unique_ptr<ui::Layer> layer) {
  496. if (layer) {
  497. layer->set_delegate(this);
  498. layer->SetName("Recording Shield");
  499. }
  500. LayerOwner::SetLayer(std::move(layer));
  501. UpdateShouldPaintLayer();
  502. UpdateLayerStackingAndDimmers();
  503. }
  504. void VideoRecordingWatcher::OnRootHierarchyChanged(aura::Window* target) {
  505. DCHECK_EQ(recording_source_, CaptureModeSource::kWindow);
  506. if (target != window_being_recorded_ && !dimmers_.contains(target) &&
  507. CanIncludeWindowInMruList(target)) {
  508. UpdateLayerStackingAndDimmers();
  509. }
  510. }
  511. bool VideoRecordingWatcher::CalculateShouldPaintLayer() const {
  512. if (recording_source_ == CaptureModeSource::kFullscreen)
  513. return false;
  514. if (recording_source_ == CaptureModeSource::kRegion)
  515. return true;
  516. DCHECK_EQ(recording_source_, CaptureModeSource::kWindow);
  517. return window_being_recorded_->TargetVisibility() &&
  518. window_being_recorded_->layer()->GetTargetVisibility() &&
  519. window_being_recorded_->layer()->GetTargetOpacity() > 0;
  520. }
  521. void VideoRecordingWatcher::UpdateShouldPaintLayer() {
  522. const bool new_value = CalculateShouldPaintLayer();
  523. if (new_value == should_paint_layer_)
  524. return;
  525. should_paint_layer_ = new_value;
  526. if (!should_paint_layer_) {
  527. // If we're not painting the shield, we don't need the individual dimmers
  528. // either.
  529. dimmers_.clear();
  530. }
  531. if (layer())
  532. layer()->SchedulePaint(layer()->bounds());
  533. }
  534. void VideoRecordingWatcher::UpdateLayerStackingAndDimmers() {
  535. if (!layer())
  536. return;
  537. DCHECK_NE(recording_source_, CaptureModeSource::kFullscreen);
  538. const bool is_recording_window =
  539. recording_source_ == CaptureModeSource::kWindow;
  540. ui::Layer* new_parent =
  541. is_recording_window
  542. ? window_being_recorded_->layer()->parent()
  543. : current_root_->GetChildById(kShellWindowId_OverlayContainer)
  544. ->layer();
  545. ui::Layer* old_parent = layer()->parent();
  546. DCHECK(new_parent || is_recording_window);
  547. if (!new_parent && old_parent) {
  548. // If the window gets removed from the hierarchy, we remove the shield layer
  549. // too, as well as any dimming of windows we have.
  550. old_parent->Remove(layer());
  551. dimmers_.clear();
  552. return;
  553. }
  554. if (new_parent != old_parent) {
  555. // We get here the first time we parent the shield layer under the overlay
  556. // container when we're recording a partial region, or when recording a
  557. // window, and it gets moved to another display, or moved to a different
  558. // desk.
  559. new_parent->Add(layer());
  560. layer()->SetBounds(current_root_->bounds());
  561. }
  562. // When recording a partial region, the shield layer is stacked at the top of
  563. // everything in the overlay container.
  564. if (!is_recording_window) {
  565. new_parent->StackAtTop(layer());
  566. return;
  567. }
  568. // However, when recording a window, we stack the shield layer below the
  569. // recorded window's layer. This takes care of dimming any windows below the
  570. // recorded window in the z-order.
  571. new_parent->StackBelow(layer(), window_being_recorded_->layer());
  572. // If the shield is not painted, all the individual dimmers should be removed.
  573. if (!should_paint_layer_) {
  574. dimmers_.clear();
  575. return;
  576. }
  577. // We use |kAllDesks| here for the following reasons:
  578. // 1- A dimmed window can move out from the desk where the window being
  579. // recorded is (either by keyboard shortcut or drag and drop in overview).
  580. // 2- The recorded window itself can move out from the active desk to an
  581. // inactive desk that has other windows.
  582. // In (1), we want to remove the dimmers of those windows that moved out.
  583. // In (2), we want to remove the dimmers of the window on the active desk, and
  584. // create ones for the windows in the inactive desk (if any of them is above
  585. // the recorded window).
  586. const auto mru_windows =
  587. Shell::Get()->mru_window_tracker()->BuildWindowListIgnoreModal(
  588. DesksMruType::kAllDesks);
  589. bool did_find_recorded_window = false;
  590. // Note that the order of |mru_windows| are from top-most first.
  591. for (auto* window : mru_windows) {
  592. if (window == window_being_recorded_) {
  593. did_find_recorded_window = true;
  594. continue;
  595. }
  596. // No need to dim windows that are below the window being recorded in
  597. // z-order, or those on other displays, or other desks.
  598. if (did_find_recorded_window || window->GetRootWindow() != current_root_ ||
  599. !AreWindowsOnSameDesk(window, window_being_recorded_)) {
  600. dimmers_.erase(window);
  601. continue;
  602. }
  603. // Dim windows that are above the recorded window in the z-order and on the
  604. // same display.
  605. auto& dimmer = dimmers_[window];
  606. if (!dimmer) {
  607. dimmer = std::make_unique<WindowDimmer>(window, /*animate=*/false, this);
  608. dimmer->SetDimColor(kColorAshShieldAndBase40);
  609. dimmer->window()->Show();
  610. }
  611. }
  612. }
  613. gfx::NativeCursor VideoRecordingWatcher::GetCurrentCursor() const {
  614. const auto cursor = cursor_manager_->GetCursor();
  615. // See the documentation in cursor_type.mojom. |kNull| is treated exactly as
  616. // |kPointer|.
  617. return (cursor.type() == ui::mojom::CursorType::kNull)
  618. ? gfx::NativeCursor(ui::mojom::CursorType::kPointer)
  619. : cursor;
  620. }
  621. void VideoRecordingWatcher::UpdateOrThrottleCursorOverlay(
  622. const gfx::PointF& location) {
  623. if (cursor_events_throttle_timer_.IsRunning()) {
  624. throttled_cursor_location_ = location;
  625. return;
  626. }
  627. UpdateCursorOverlayNow(location);
  628. cursor_events_throttle_timer_.Start(
  629. FROM_HERE, kCursorEventsThrottleDelay, this,
  630. &VideoRecordingWatcher::OnCursorThrottleTimerFiring);
  631. }
  632. void VideoRecordingWatcher::UpdateCursorOverlayNow(
  633. const gfx::PointF& location) {
  634. // Cancel any pending throttled event.
  635. cursor_events_throttle_timer_.Stop();
  636. throttled_cursor_location_.reset();
  637. if (!cursor_capture_overlay_remote_)
  638. return;
  639. if (force_cursor_overlay_hidden_ ||
  640. TabletModeController::Get()->InTabletMode()) {
  641. HideCursorOverlay();
  642. return;
  643. }
  644. const gfx::RectF window_local_bounds(
  645. gfx::SizeF(window_being_recorded_->bounds().size()));
  646. if (!window_local_bounds.Contains(location)) {
  647. HideCursorOverlay();
  648. return;
  649. }
  650. const gfx::NativeCursor cursor = GetCurrentCursor();
  651. DCHECK_NE(cursor.type(), ui::mojom::CursorType::kNull);
  652. const float cursor_image_scale_factor = cursor.image_scale_factor();
  653. const SkBitmap cursor_image = wm::GetCursorBitmap(cursor);
  654. const gfx::RectF cursor_overlay_bounds = GetCursorOverlayBounds(
  655. window_being_recorded_, location, wm::GetCursorHotspot(cursor),
  656. cursor_image_scale_factor, cursor_image);
  657. if (cursor != last_cursor_) {
  658. if (cursor_image.drawsNothing()) {
  659. last_cursor_ = gfx::NativeCursor();
  660. HideCursorOverlay();
  661. return;
  662. }
  663. last_cursor_ = cursor;
  664. last_cursor_overlay_bounds_ = cursor_overlay_bounds;
  665. cursor_capture_overlay_remote_->SetImageAndBounds(
  666. cursor_image, last_cursor_overlay_bounds_);
  667. return;
  668. }
  669. if (last_cursor_overlay_bounds_ == cursor_overlay_bounds)
  670. return;
  671. last_cursor_overlay_bounds_ = cursor_overlay_bounds;
  672. cursor_capture_overlay_remote_->SetBounds(last_cursor_overlay_bounds_);
  673. }
  674. void VideoRecordingWatcher::HideCursorOverlay() {
  675. DCHECK(cursor_capture_overlay_remote_);
  676. // No need to rehide if already hidden.
  677. if (last_cursor_overlay_bounds_ == gfx::RectF())
  678. return;
  679. last_cursor_overlay_bounds_ = gfx::RectF();
  680. cursor_capture_overlay_remote_->SetBounds(last_cursor_overlay_bounds_);
  681. }
  682. void VideoRecordingWatcher::OnCursorThrottleTimerFiring() {
  683. if (throttled_cursor_location_)
  684. UpdateCursorOverlayNow(*throttled_cursor_location_);
  685. }
  686. void VideoRecordingWatcher::OnWindowSizeChangeThrottleTimerFiring() {
  687. DCHECK_EQ(recording_source_, CaptureModeSource::kWindow);
  688. controller_->OnRecordedWindowSizeChanged(
  689. window_being_recorded_->bounds().size());
  690. }
  691. gfx::Rect VideoRecordingWatcher::GetOverlayWidgetBounds() const {
  692. gfx::Rect bounds = recording_source_ == CaptureModeSource::kRegion
  693. ? GetEffectivePartialRegionBounds()
  694. : gfx::Rect(window_being_recorded_->bounds().size());
  695. bounds.Subtract(Shell::Get()
  696. ->docked_magnifier_controller()
  697. ->GetTotalMagnifierBoundsForRoot(
  698. window_being_recorded_->GetRootWindow()));
  699. return bounds;
  700. }
  701. } // namespace ash