123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831 |
- // Copyright 2020 The Chromium Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style license that can be
- // found in the LICENSE file.
- #include "ash/capture_mode/video_recording_watcher.h"
- #include <memory>
- #include "ash/accessibility/magnifier/docked_magnifier_controller.h"
- #include "ash/capture_mode/capture_mode_camera_controller.h"
- #include "ash/capture_mode/capture_mode_camera_preview_view.h"
- #include "ash/capture_mode/capture_mode_constants.h"
- #include "ash/capture_mode/capture_mode_controller.h"
- #include "ash/capture_mode/capture_mode_metrics.h"
- #include "ash/capture_mode/recording_overlay_controller.h"
- #include "ash/constants/ash_features.h"
- #include "ash/projector/projector_controller_impl.h"
- #include "ash/shell.h"
- #include "ash/style/ash_color_id.h"
- #include "ash/style/ash_color_provider.h"
- #include "ash/wm/desks/desks_util.h"
- #include "ash/wm/mru_window_tracker.h"
- #include "ash/wm/tablet_mode/tablet_mode_controller.h"
- #include "base/check.h"
- #include "base/check_op.h"
- #include "base/notreached.h"
- #include "base/time/time.h"
- #include "third_party/skia/include/core/SkBitmap.h"
- #include "ui/aura/window.h"
- #include "ui/aura/window_tree_host.h"
- #include "ui/compositor/layer.h"
- #include "ui/compositor/paint_recorder.h"
- #include "ui/display/screen.h"
- #include "ui/gfx/canvas.h"
- #include "ui/gfx/geometry/dip_util.h"
- #include "ui/gfx/geometry/point_f.h"
- #include "ui/gfx/geometry/rect_f.h"
- #include "ui/gfx/geometry/size_f.h"
- #include "ui/gfx/native_widget_types.h"
- #include "ui/gfx/scoped_canvas.h"
- #include "ui/wm/core/coordinate_conversion.h"
- #include "ui/wm/core/cursor_lookup.h"
- #include "ui/wm/public/activation_client.h"
- namespace ash {
- namespace {
- // Recording is performed at a rate of 30 FPS. Any non-pressed/-released mouse
- // events that are too frequent will be throttled. We use the frame duration as
- // the minimum delay between any two successive such events that we use to
- // update the cursor overlay.
- constexpr base::TimeDelta kCursorEventsThrottleDelay = base::Hertz(30);
- // Window resizes can be done on many intermediate steps. This delay is used to
- // throttle these resize events so that we send the final size of the window to
- // the recording service when it stabilizes.
- constexpr base::TimeDelta kWindowSizeChangeThrottleDelay =
- base::Milliseconds(250);
- // Returns true if |window_1| and |window_2| are both windows that belong to
- // the same Desk. Note that it will return false for windows that don't belong
- // to any desk (such as always-on-top windows or PIPs).
- bool AreWindowsOnSameDesk(aura::Window* window_1, aura::Window* window_2) {
- auto* container_1 = desks_util::GetDeskContainerForContext(window_1);
- auto* container_2 = desks_util::GetDeskContainerForContext(window_2);
- return container_1 && container_2 && container_1 == container_2;
- }
- // Gets the mouse cursor location in the coordinates of the given |window|. Use
- // this if a mouse event is not available.
- gfx::PointF GetCursorLocationInWindow(aura::Window* window) {
- gfx::PointF cursor_point(
- display::Screen::GetScreen()->GetCursorScreenPoint());
- wm::ConvertPointFromScreen(window, &cursor_point);
- return cursor_point;
- }
- // Gets the location of the given mouse |event| in the coordinates of the given
- // |window|.
- gfx::PointF GetEventLocationInWindow(aura::Window* window,
- const ui::MouseEvent& event) {
- aura::Window* target = static_cast<aura::Window*>(event.target());
- gfx::PointF location = event.location_f();
- if (target != window)
- aura::Window::ConvertPointToTarget(target, window, &location);
- return location;
- }
- // Returns the cursor overlay bounds as defined by the documentation of the
- // FrameSinkVideoCaptureOverlay. The bounds should be relative within the bounds
- // of the recorded frame sink (i.e. in the range [0.f, 1.f) for both cursor
- // origin and size).
- gfx::RectF GetCursorOverlayBounds(
- const aura::Window* recorded_window,
- const gfx::PointF& location_in_recorded_window,
- const gfx::Point& cursor_hotspot,
- float cursor_image_scale_factor,
- const SkBitmap& cursor_bitmap) {
- DCHECK(recorded_window);
- DCHECK_GT(cursor_image_scale_factor, 0);
- // The video size, and the resolution constraints will be matching the size of
- // the recorded window (whether a root or a non-root window). Hence, the
- // bounds of the cursor overlay should be relative to that size.
- const auto window_size = recorded_window->bounds().size();
- if (window_size.IsEmpty())
- return gfx::RectF();
- const gfx::PointF cursor_hotspot_dip =
- gfx::ConvertPointToDips(cursor_hotspot, cursor_image_scale_factor);
- const gfx::SizeF cursor_size_dip = gfx::ConvertSizeToDips(
- gfx::SizeF(cursor_bitmap.width(), cursor_bitmap.height()),
- cursor_image_scale_factor);
- gfx::RectF cursor_relative_bounds(
- location_in_recorded_window - cursor_hotspot_dip.OffsetFromOrigin(),
- cursor_size_dip);
- cursor_relative_bounds.Scale(1.f / window_size.width(),
- 1.f / window_size.height());
- return cursor_relative_bounds;
- }
- CameraPreviewView* GetCameraPreviewView() {
- return CaptureModeController::Get()
- ->camera_controller()
- ->camera_preview_view();
- }
- } // namespace
- // -----------------------------------------------------------------------------
- // RecordedWindowRootObserver:
- // Defines an observer to observe the hierarchy changes of the root window under
- // which the recorded window resides. This is only constructed when performing a
- // window recording type.
- class RecordedWindowRootObserver : public aura::WindowObserver {
- public:
- RecordedWindowRootObserver(aura::Window* root, VideoRecordingWatcher* owner)
- : root_(root), owner_(owner) {
- DCHECK(root_);
- DCHECK(owner_);
- DCHECK(root_->IsRootWindow());
- DCHECK_EQ(owner_->recording_source_, CaptureModeSource::kWindow);
- root_->AddObserver(this);
- }
- RecordedWindowRootObserver(const RecordedWindowRootObserver&) = delete;
- RecordedWindowRootObserver& operator=(const RecordedWindowRootObserver&) =
- delete;
- ~RecordedWindowRootObserver() override { root_->RemoveObserver(this); }
- // aura::WindowObserver:
- void OnWindowHierarchyChanged(const HierarchyChangeParams& params) override {
- DCHECK_EQ(params.receiver, root_);
- owner_->OnRootHierarchyChanged(params.target);
- }
- void OnWindowDestroying(aura::Window* window) override {
- // We should never get here, as the recorded window gets moved to a
- // different display before the root of another is destroyed. So this root
- // observer should have been destroyed already.
- NOTREACHED();
- }
- private:
- aura::Window* const root_;
- VideoRecordingWatcher* const owner_;
- };
- // -----------------------------------------------------------------------------
- // VideoRecordingWatcher:
- VideoRecordingWatcher::VideoRecordingWatcher(
- CaptureModeController* controller,
- aura::Window* window_being_recorded,
- mojo::PendingRemote<viz::mojom::FrameSinkVideoCaptureOverlay>
- cursor_capture_overlay,
- bool projector_mode)
- : controller_(controller),
- cursor_manager_(Shell::Get()->cursor_manager()),
- window_being_recorded_(window_being_recorded),
- current_root_(window_being_recorded->GetRootWindow()),
- recording_source_(controller_->source()),
- cursor_capture_overlay_remote_(std::move(cursor_capture_overlay)),
- is_in_projector_mode_(projector_mode) {
- DCHECK(controller_);
- DCHECK(window_being_recorded_);
- DCHECK(current_root_);
- DCHECK(!is_in_projector_mode_ || features::IsProjectorEnabled());
- if (!window_being_recorded_->IsRootWindow()) {
- DCHECK_EQ(recording_source_, CaptureModeSource::kWindow);
- non_root_window_capture_request_ =
- window_being_recorded_->MakeWindowCapturable();
- root_observer_ =
- std::make_unique<RecordedWindowRootObserver>(current_root_, this);
- Shell::Get()->activation_client()->AddObserver(this);
- } else {
- // We only need to observe the changes in the state of the software-
- // composited cursor when recording a root window (i.e. fullscreen or
- // partial region capture), since the software cursor is in the layer
- // subtree of the root window, and will be captured by the frame sink video
- // capturer automatically without the need for the cursor overlay. In this
- // case we need to avoid producing a video with two overlapping cursors.
- // When recording a window however, the software cursor is not in its layer
- // subtree, and has to always be captured using the cursor overlay.
- auto* cursor_window_controller =
- Shell::Get()->window_tree_host_manager()->cursor_window_controller();
- // Note that the software cursor might have already been enabled prior to
- // the recording starting.
- force_cursor_overlay_hidden_ =
- cursor_window_controller->is_cursor_compositing_enabled();
- cursor_window_controller->AddObserver(this);
- }
- if (recording_source_ == CaptureModeSource::kRegion)
- partial_region_bounds_ = controller_->user_capture_region();
- display::Screen::GetScreen()->AddObserver(this);
- window_being_recorded_->AddObserver(this);
- TabletModeController::Get()->AddObserver(this);
- // Note the following:
- // 1- We add |this| as a pre-target handler of the |window_being_recorded_| as
- // opposed to |Env|. This ensures that we only get mouse events when the
- // window being recorded is the target. This is more efficient since we
- // won't get any event when the curosr is in a different display, or
- // targeting a different window.
- // 2- We use the |kAccessibility| priority to ensure that we get these events
- // before other pre-target handlers can consume them (e.g. when opening a
- // capture mode session to take a screenshot while recording a video).
- window_being_recorded_->AddPreTargetHandler(
- this, ui::EventTarget::Priority::kAccessibility);
- controller_->camera_controller()->OnRecordingStarted(is_in_projector_mode_);
- if (is_in_projector_mode_) {
- recording_overlay_controller_ =
- std::make_unique<RecordingOverlayController>(window_being_recorded_,
- GetOverlayWidgetBounds());
- }
- if (features::IsProjectorEnabled()) {
- ProjectorControllerImpl::Get()->OnRecordingStarted(current_root_,
- is_in_projector_mode_);
- }
- }
- VideoRecordingWatcher::~VideoRecordingWatcher() {
- DCHECK(is_shutting_down_);
- }
- void VideoRecordingWatcher::ToggleRecordingOverlayEnabled() {
- DCHECK(is_in_projector_mode_);
- DCHECK(!is_shutting_down_);
- DCHECK(recording_overlay_controller_);
- recording_overlay_controller_->Toggle();
- }
- void VideoRecordingWatcher::ShutDown() {
- is_shutting_down_ = true;
- DCHECK(window_being_recorded_);
- window_size_change_throttle_timer_.Stop();
- cursor_events_throttle_timer_.Stop();
- cursor_capture_overlay_remote_.reset();
- root_observer_.reset();
- recording_overlay_controller_.reset();
- dimmers_.clear();
- if (features::IsProjectorEnabled())
- ProjectorControllerImpl::Get()->OnRecordingEnded(is_in_projector_mode_);
- window_being_recorded_->RemovePreTargetHandler(this);
- TabletModeController::Get()->RemoveObserver(this);
- if (recording_source_ == CaptureModeSource::kWindow) {
- Shell::Get()->activation_client()->RemoveObserver(this);
- } else {
- Shell::Get()
- ->window_tree_host_manager()
- ->cursor_window_controller()
- ->RemoveObserver(this);
- }
- // Move the `non_root_window_capture_request_` so that the
- // `window_being_recorded_` is not capturable.
- auto to_be_removed_request = std::move(non_root_window_capture_request_);
- window_being_recorded_->RemoveObserver(this);
- display::Screen::GetScreen()->RemoveObserver(this);
- controller_->camera_controller()->OnRecordingEnded();
- }
- aura::Window* VideoRecordingWatcher::GetCameraPreviewParentWindow() const {
- DCHECK(window_being_recorded_);
- return window_being_recorded_->IsRootWindow()
- ? window_being_recorded_->GetChildById(
- kShellWindowId_MenuContainer)
- : window_being_recorded_;
- }
- gfx::Rect VideoRecordingWatcher::GetCameraPreviewConfineBounds() const {
- DCHECK(window_being_recorded_);
- switch (recording_source_) {
- case CaptureModeSource::kFullscreen:
- return display::Screen::GetScreen()
- ->GetDisplayNearestWindow(window_being_recorded_)
- .work_area();
- case CaptureModeSource::kRegion: {
- gfx::Rect capture_region = GetEffectivePartialRegionBounds();
- wm::ConvertRectToScreen(current_root_, &capture_region);
- return capture_region;
- }
- case CaptureModeSource::kWindow:
- return gfx::Rect(window_being_recorded_->bounds().size());
- }
- }
- void VideoRecordingWatcher::OnWindowParentChanged(aura::Window* window,
- aura::Window* parent) {
- DCHECK_EQ(window, window_being_recorded_);
- DCHECK_EQ(recording_source_, CaptureModeSource::kWindow);
- UpdateLayerStackingAndDimmers();
- }
- void VideoRecordingWatcher::OnWindowVisibilityChanged(aura::Window* window,
- bool visible) {
- if (window == window_being_recorded_)
- UpdateShouldPaintLayer();
- }
- void VideoRecordingWatcher::OnWindowBoundsChanged(
- aura::Window* window,
- const gfx::Rect& old_bounds,
- const gfx::Rect& new_bounds,
- ui::PropertyChangeReason reason) {
- if (is_in_projector_mode_)
- recording_overlay_controller_->SetBounds(GetOverlayWidgetBounds());
- if (recording_source_ != CaptureModeSource::kWindow)
- return;
- // We care only about size changes, since the location of the window won't
- // affect the recorded video frames of it, however, the size of the window
- // affects the size of the frames.
- if (old_bounds.size() == new_bounds.size())
- return;
- window_size_change_throttle_timer_.Start(
- FROM_HERE, kWindowSizeChangeThrottleDelay, this,
- &VideoRecordingWatcher::OnWindowSizeChangeThrottleTimerFiring);
- // The bounds of the camera preview should be updated if the bounds of the
- // window being recorded is changed.
- controller_->camera_controller()->MaybeUpdatePreviewWidget();
- }
- void VideoRecordingWatcher::OnWindowOpacitySet(
- aura::Window* window,
- ui::PropertyChangeReason reason) {
- if (window == window_being_recorded_)
- UpdateShouldPaintLayer();
- }
- void VideoRecordingWatcher::OnWindowStackingChanged(aura::Window* window) {
- DCHECK_EQ(window, window_being_recorded_);
- DCHECK_EQ(recording_source_, CaptureModeSource::kWindow);
- UpdateLayerStackingAndDimmers();
- }
- void VideoRecordingWatcher::OnWindowDestroying(aura::Window* window) {
- DCHECK_EQ(window, window_being_recorded_);
- // EndVideoRecording() destroys |this|. No need to remove observer here, since
- // it will be done in the destructor.
- controller_->EndVideoRecording(EndRecordingReason::kDisplayOrWindowClosing);
- }
- void VideoRecordingWatcher::OnWindowDestroyed(aura::Window* window) {
- DCHECK_EQ(window, window_being_recorded_);
- // We should never get here, since OnWindowDestroying() calls
- // EndVideoRecording() which deletes us.
- NOTREACHED();
- }
- void VideoRecordingWatcher::OnWindowRemovingFromRootWindow(
- aura::Window* window,
- aura::Window* new_root) {
- DCHECK_EQ(window, window_being_recorded_);
- DCHECK_EQ(recording_source_, CaptureModeSource::kWindow);
- root_observer_.reset();
- current_root_ = new_root;
- if (!new_root) {
- // EndVideoRecording() destroys |this|.
- controller_->EndVideoRecording(EndRecordingReason::kDisplayOrWindowClosing);
- return;
- }
- root_observer_ =
- std::make_unique<RecordedWindowRootObserver>(current_root_, this);
- controller_->OnRecordedWindowChangingRoot(window_being_recorded_, new_root);
- if (is_in_projector_mode_)
- ProjectorControllerImpl::Get()->OnRecordedWindowChangingRoot(new_root);
- }
- void VideoRecordingWatcher::OnPaintLayer(const ui::PaintContext& context) {
- if (!should_paint_layer_)
- return;
- DCHECK_NE(recording_source_, CaptureModeSource::kFullscreen);
- ui::PaintRecorder recorder(context, layer()->size());
- gfx::Canvas* canvas = recorder.canvas();
- canvas->DrawColor(capture_mode::kDimmingShieldColor);
- // We don't draw a region border around the recorded window. We just paint the
- // above shield as a backdrop.
- if (recording_source_ == CaptureModeSource::kWindow)
- return;
- gfx::ScopedCanvas scoped_canvas(canvas);
- const float dsf = canvas->UndoDeviceScaleFactor();
- gfx::Rect region =
- gfx::ScaleToEnclosingRect(GetEffectivePartialRegionBounds(), dsf);
- region.Inset(-capture_mode::kCaptureRegionBorderStrokePx);
- canvas->FillRect(region, SK_ColorTRANSPARENT, SkBlendMode::kClear);
- // Draw the region border.
- cc::PaintFlags border_flags;
- border_flags.setColor(capture_mode::kRegionBorderColor);
- border_flags.setStyle(cc::PaintFlags::kStroke_Style);
- border_flags.setStrokeWidth(capture_mode::kCaptureRegionBorderStrokePx);
- canvas->DrawRect(gfx::RectF(region), border_flags);
- }
- void VideoRecordingWatcher::OnWindowActivated(ActivationReason reason,
- aura::Window* gained_active,
- aura::Window* lost_active) {
- DCHECK_EQ(recording_source_, CaptureModeSource::kWindow);
- UpdateLayerStackingAndDimmers();
- }
- void VideoRecordingWatcher::OnDisplayMetricsChanged(
- const display::Display& display,
- uint32_t metrics) {
- // A change in the work area, could mean that the docked magnifier state has
- // changed, therefore we must update the overlay widget's bounds if any.
- if (is_in_projector_mode_ && (metrics & DISPLAY_METRIC_WORK_AREA))
- recording_overlay_controller_->SetBounds(GetOverlayWidgetBounds());
- if (!(metrics &
- (DISPLAY_METRIC_BOUNDS | DISPLAY_METRIC_ROTATION |
- DISPLAY_METRIC_DEVICE_SCALE_FACTOR | DISPLAY_METRIC_WORK_AREA))) {
- return;
- }
- const int64_t display_id =
- display::Screen::GetScreen()->GetDisplayNearestWindow(current_root_).id();
- if (display_id != display.id())
- return;
- const auto& root_bounds = current_root_->bounds();
- controller_->PushNewRootSizeToRecordingService(
- root_bounds.size(), current_root_->GetHost()->device_scale_factor());
- // The bounds of camera preview should be updated accordingly if the display
- // metrics is changed. When the capture source is `kWindow`, it will be
- // handled in `OnWindowBoundsChanged`;
- if (recording_source_ != CaptureModeSource::kWindow)
- controller_->camera_controller()->MaybeUpdatePreviewWidget();
- // We don't show a dimming overlay when recording a fullscreen.
- if (recording_source_ == CaptureModeSource::kFullscreen)
- return;
- DCHECK(layer());
- layer()->SetBounds(root_bounds);
- }
- void VideoRecordingWatcher::OnDimmedWindowDestroying(
- aura::Window* dimmed_window) {
- dimmers_.erase(dimmed_window);
- }
- void VideoRecordingWatcher::OnDimmedWindowParentChanged(
- aura::Window* dimmed_window) {
- // If the dimmed window moves to another display or a different desk, we no
- // longer dim it.
- if (dimmed_window->GetRootWindow() != current_root_ ||
- !AreWindowsOnSameDesk(dimmed_window, window_being_recorded_)) {
- dimmers_.erase(dimmed_window);
- }
- }
- void VideoRecordingWatcher::OnKeyEvent(ui::KeyEvent* event) {
- if (event->type() != ui::ET_KEY_PRESSED)
- return;
- auto* camera_preview_view = GetCameraPreviewView();
- if (camera_preview_view && camera_preview_view->MaybeHandleKeyEvent(event)) {
- event->StopPropagation();
- event->SetHandled();
- return;
- }
- }
- void VideoRecordingWatcher::OnMouseEvent(ui::MouseEvent* event) {
- switch (event->type()) {
- case ui::ET_MOUSEWHEEL:
- case ui::ET_MOUSE_CAPTURE_CHANGED:
- return;
- case ui::ET_MOUSE_PRESSED: {
- auto* camera_preview_view = GetCameraPreviewView();
- if (camera_preview_view)
- camera_preview_view->MaybeBlurFocus(*event);
- }
- [[fallthrough]];
- case ui::ET_MOUSE_RELEASED:
- // Pressed/released events are important, so we handle them immediately.
- UpdateCursorOverlayNow(
- GetEventLocationInWindow(window_being_recorded_, *event));
- return;
- default:
- UpdateOrThrottleCursorOverlay(
- GetEventLocationInWindow(window_being_recorded_, *event));
- }
- }
- void VideoRecordingWatcher::OnTabletModeStarted() {
- UpdateCursorOverlayNow(gfx::PointF());
- }
- void VideoRecordingWatcher::OnTabletModeEnded() {
- UpdateCursorOverlayNow(GetCursorLocationInWindow(window_being_recorded_));
- }
- void VideoRecordingWatcher::OnCursorCompositingStateChanged(bool enabled) {
- DCHECK_NE(recording_source_, CaptureModeSource::kWindow);
- force_cursor_overlay_hidden_ = enabled;
- UpdateCursorOverlayNow(
- force_cursor_overlay_hidden_
- ? gfx::PointF()
- : GetCursorLocationInWindow(window_being_recorded_));
- }
- gfx::Rect VideoRecordingWatcher::GetEffectivePartialRegionBounds() const {
- DCHECK_EQ(recording_source_, CaptureModeSource::kRegion);
- // TODO(afakhry): Consider having the region to anchor to the nearest corner,
- // so that screen rotation doesn't result in the apparent change of the region
- // position. Discussion with PM/UX determined that this is a low priority for
- // now.
- gfx::Rect result = partial_region_bounds_;
- result.AdjustToFit(current_root_->bounds());
- return result;
- }
- bool VideoRecordingWatcher::IsWindowDimmedForTesting(
- aura::Window* window) const {
- return dimmers_.contains(window);
- }
- void VideoRecordingWatcher::BindCursorOverlayForTesting(
- mojo::PendingRemote<viz::mojom::FrameSinkVideoCaptureOverlay> overlay) {
- cursor_capture_overlay_remote_.reset();
- cursor_capture_overlay_remote_.Bind(std::move(overlay));
- }
- void VideoRecordingWatcher::FlushCursorOverlayForTesting() {
- cursor_capture_overlay_remote_.FlushForTesting();
- }
- void VideoRecordingWatcher::SendThrottledWindowSizeChangedNowForTesting() {
- window_size_change_throttle_timer_.FireNow();
- }
- void VideoRecordingWatcher::SetLayer(std::unique_ptr<ui::Layer> layer) {
- if (layer) {
- layer->set_delegate(this);
- layer->SetName("Recording Shield");
- }
- LayerOwner::SetLayer(std::move(layer));
- UpdateShouldPaintLayer();
- UpdateLayerStackingAndDimmers();
- }
- void VideoRecordingWatcher::OnRootHierarchyChanged(aura::Window* target) {
- DCHECK_EQ(recording_source_, CaptureModeSource::kWindow);
- if (target != window_being_recorded_ && !dimmers_.contains(target) &&
- CanIncludeWindowInMruList(target)) {
- UpdateLayerStackingAndDimmers();
- }
- }
- bool VideoRecordingWatcher::CalculateShouldPaintLayer() const {
- if (recording_source_ == CaptureModeSource::kFullscreen)
- return false;
- if (recording_source_ == CaptureModeSource::kRegion)
- return true;
- DCHECK_EQ(recording_source_, CaptureModeSource::kWindow);
- return window_being_recorded_->TargetVisibility() &&
- window_being_recorded_->layer()->GetTargetVisibility() &&
- window_being_recorded_->layer()->GetTargetOpacity() > 0;
- }
- void VideoRecordingWatcher::UpdateShouldPaintLayer() {
- const bool new_value = CalculateShouldPaintLayer();
- if (new_value == should_paint_layer_)
- return;
- should_paint_layer_ = new_value;
- if (!should_paint_layer_) {
- // If we're not painting the shield, we don't need the individual dimmers
- // either.
- dimmers_.clear();
- }
- if (layer())
- layer()->SchedulePaint(layer()->bounds());
- }
- void VideoRecordingWatcher::UpdateLayerStackingAndDimmers() {
- if (!layer())
- return;
- DCHECK_NE(recording_source_, CaptureModeSource::kFullscreen);
- const bool is_recording_window =
- recording_source_ == CaptureModeSource::kWindow;
- ui::Layer* new_parent =
- is_recording_window
- ? window_being_recorded_->layer()->parent()
- : current_root_->GetChildById(kShellWindowId_OverlayContainer)
- ->layer();
- ui::Layer* old_parent = layer()->parent();
- DCHECK(new_parent || is_recording_window);
- if (!new_parent && old_parent) {
- // If the window gets removed from the hierarchy, we remove the shield layer
- // too, as well as any dimming of windows we have.
- old_parent->Remove(layer());
- dimmers_.clear();
- return;
- }
- if (new_parent != old_parent) {
- // We get here the first time we parent the shield layer under the overlay
- // container when we're recording a partial region, or when recording a
- // window, and it gets moved to another display, or moved to a different
- // desk.
- new_parent->Add(layer());
- layer()->SetBounds(current_root_->bounds());
- }
- // When recording a partial region, the shield layer is stacked at the top of
- // everything in the overlay container.
- if (!is_recording_window) {
- new_parent->StackAtTop(layer());
- return;
- }
- // However, when recording a window, we stack the shield layer below the
- // recorded window's layer. This takes care of dimming any windows below the
- // recorded window in the z-order.
- new_parent->StackBelow(layer(), window_being_recorded_->layer());
- // If the shield is not painted, all the individual dimmers should be removed.
- if (!should_paint_layer_) {
- dimmers_.clear();
- return;
- }
- // We use |kAllDesks| here for the following reasons:
- // 1- A dimmed window can move out from the desk where the window being
- // recorded is (either by keyboard shortcut or drag and drop in overview).
- // 2- The recorded window itself can move out from the active desk to an
- // inactive desk that has other windows.
- // In (1), we want to remove the dimmers of those windows that moved out.
- // In (2), we want to remove the dimmers of the window on the active desk, and
- // create ones for the windows in the inactive desk (if any of them is above
- // the recorded window).
- const auto mru_windows =
- Shell::Get()->mru_window_tracker()->BuildWindowListIgnoreModal(
- DesksMruType::kAllDesks);
- bool did_find_recorded_window = false;
- // Note that the order of |mru_windows| are from top-most first.
- for (auto* window : mru_windows) {
- if (window == window_being_recorded_) {
- did_find_recorded_window = true;
- continue;
- }
- // No need to dim windows that are below the window being recorded in
- // z-order, or those on other displays, or other desks.
- if (did_find_recorded_window || window->GetRootWindow() != current_root_ ||
- !AreWindowsOnSameDesk(window, window_being_recorded_)) {
- dimmers_.erase(window);
- continue;
- }
- // Dim windows that are above the recorded window in the z-order and on the
- // same display.
- auto& dimmer = dimmers_[window];
- if (!dimmer) {
- dimmer = std::make_unique<WindowDimmer>(window, /*animate=*/false, this);
- dimmer->SetDimColor(kColorAshShieldAndBase40);
- dimmer->window()->Show();
- }
- }
- }
- gfx::NativeCursor VideoRecordingWatcher::GetCurrentCursor() const {
- const auto cursor = cursor_manager_->GetCursor();
- // See the documentation in cursor_type.mojom. |kNull| is treated exactly as
- // |kPointer|.
- return (cursor.type() == ui::mojom::CursorType::kNull)
- ? gfx::NativeCursor(ui::mojom::CursorType::kPointer)
- : cursor;
- }
- void VideoRecordingWatcher::UpdateOrThrottleCursorOverlay(
- const gfx::PointF& location) {
- if (cursor_events_throttle_timer_.IsRunning()) {
- throttled_cursor_location_ = location;
- return;
- }
- UpdateCursorOverlayNow(location);
- cursor_events_throttle_timer_.Start(
- FROM_HERE, kCursorEventsThrottleDelay, this,
- &VideoRecordingWatcher::OnCursorThrottleTimerFiring);
- }
- void VideoRecordingWatcher::UpdateCursorOverlayNow(
- const gfx::PointF& location) {
- // Cancel any pending throttled event.
- cursor_events_throttle_timer_.Stop();
- throttled_cursor_location_.reset();
- if (!cursor_capture_overlay_remote_)
- return;
- if (force_cursor_overlay_hidden_ ||
- TabletModeController::Get()->InTabletMode()) {
- HideCursorOverlay();
- return;
- }
- const gfx::RectF window_local_bounds(
- gfx::SizeF(window_being_recorded_->bounds().size()));
- if (!window_local_bounds.Contains(location)) {
- HideCursorOverlay();
- return;
- }
- const gfx::NativeCursor cursor = GetCurrentCursor();
- DCHECK_NE(cursor.type(), ui::mojom::CursorType::kNull);
- const float cursor_image_scale_factor = cursor.image_scale_factor();
- const SkBitmap cursor_image = wm::GetCursorBitmap(cursor);
- const gfx::RectF cursor_overlay_bounds = GetCursorOverlayBounds(
- window_being_recorded_, location, wm::GetCursorHotspot(cursor),
- cursor_image_scale_factor, cursor_image);
- if (cursor != last_cursor_) {
- if (cursor_image.drawsNothing()) {
- last_cursor_ = gfx::NativeCursor();
- HideCursorOverlay();
- return;
- }
- last_cursor_ = cursor;
- last_cursor_overlay_bounds_ = cursor_overlay_bounds;
- cursor_capture_overlay_remote_->SetImageAndBounds(
- cursor_image, last_cursor_overlay_bounds_);
- return;
- }
- if (last_cursor_overlay_bounds_ == cursor_overlay_bounds)
- return;
- last_cursor_overlay_bounds_ = cursor_overlay_bounds;
- cursor_capture_overlay_remote_->SetBounds(last_cursor_overlay_bounds_);
- }
- void VideoRecordingWatcher::HideCursorOverlay() {
- DCHECK(cursor_capture_overlay_remote_);
- // No need to rehide if already hidden.
- if (last_cursor_overlay_bounds_ == gfx::RectF())
- return;
- last_cursor_overlay_bounds_ = gfx::RectF();
- cursor_capture_overlay_remote_->SetBounds(last_cursor_overlay_bounds_);
- }
- void VideoRecordingWatcher::OnCursorThrottleTimerFiring() {
- if (throttled_cursor_location_)
- UpdateCursorOverlayNow(*throttled_cursor_location_);
- }
- void VideoRecordingWatcher::OnWindowSizeChangeThrottleTimerFiring() {
- DCHECK_EQ(recording_source_, CaptureModeSource::kWindow);
- controller_->OnRecordedWindowSizeChanged(
- window_being_recorded_->bounds().size());
- }
- gfx::Rect VideoRecordingWatcher::GetOverlayWidgetBounds() const {
- gfx::Rect bounds = recording_source_ == CaptureModeSource::kRegion
- ? GetEffectivePartialRegionBounds()
- : gfx::Rect(window_being_recorded_->bounds().size());
- bounds.Subtract(Shell::Get()
- ->docked_magnifier_controller()
- ->GetTotalMagnifierBoundsForRoot(
- window_being_recorded_->GetRootWindow()));
- return bounds;
- }
- } // namespace ash
|