| // 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. |
| |
| #ifndef ASH_CAPTURE_MODE_VIDEO_RECORDING_WATCHER_H_ |
| #define ASH_CAPTURE_MODE_VIDEO_RECORDING_WATCHER_H_ |
| |
| #include "ash/ash_export.h" |
| #include "ash/capture_mode/capture_mode_types.h" |
| #include "ash/display/cursor_window_controller.h" |
| #include "ash/public/cpp/tablet_mode_observer.h" |
| #include "ash/wm/window_dimmer.h" |
| #include "base/timer/timer.h" |
| #include "mojo/public/cpp/bindings/pending_remote.h" |
| #include "mojo/public/cpp/bindings/remote.h" |
| #include "services/viz/privileged/mojom/compositing/frame_sink_video_capture.mojom.h" |
| #include "third_party/abseil-cpp/absl/types/optional.h" |
| #include "ui/aura/scoped_window_capture_request.h" |
| #include "ui/aura/window_observer.h" |
| #include "ui/base/cursor/cursor.h" |
| #include "ui/compositor/layer_delegate.h" |
| #include "ui/compositor/layer_owner.h" |
| #include "ui/display/display_observer.h" |
| #include "ui/events/event_handler.h" |
| #include "ui/gfx/geometry/rect_f.h" |
| #include "ui/gfx/native_widget_types.h" |
| #include "ui/wm/public/activation_change_observer.h" |
| |
| namespace wm { |
| class CursorManager; |
| } // namespace wm |
| |
| namespace ash { |
| |
| class CaptureModeController; |
| class RecordingOverlayController; |
| class RecordedWindowRootObserver; |
| |
| // An instance of this class is created while video recording is in progress to |
| // watch for events that end video recording, such as a window being recorded |
| // gets closed or moved between displays, or a display being fullscreen-recorded |
| // gets disconnected. |
| // This also paints a dimming shield to distinguish the area being recorded, but |
| // only when recording a window or a partial region. |
| // Note that this object doesn't create a new layer, rather the controller makes |
| // it acquire and reuse the layer of the |CaptureModeSession| prior to the |
| // session ending. |
| // It also controls the overlay created on the video capturer to efficiently |
| // record the mouse cursor on top of the video frames. |
| class ASH_EXPORT VideoRecordingWatcher |
| : public aura::WindowObserver, |
| public ui::LayerOwner, |
| public ui::LayerDelegate, |
| public wm::ActivationChangeObserver, |
| public display::DisplayObserver, |
| public WindowDimmer::Delegate, |
| public ui::EventHandler, |
| public TabletModeObserver, |
| public CursorWindowController::Observer { |
| public: |
| VideoRecordingWatcher( |
| CaptureModeController* controller, |
| aura::Window* window_being_recorded, |
| mojo::PendingRemote<viz::mojom::FrameSinkVideoCaptureOverlay> |
| cursor_capture_overlay, |
| bool projector_mode); |
| ~VideoRecordingWatcher() override; |
| |
| aura::Window* window_being_recorded() const { return window_being_recorded_; } |
| bool is_in_projector_mode() const { return is_in_projector_mode_; } |
| bool should_paint_layer() const { return should_paint_layer_; } |
| bool is_shutting_down() const { return is_shutting_down_; } |
| |
| // Toggles the Projector mode's overlay widget on or off. Can only be called |
| // if |is_in_projector_mode()| is true. |
| void ToggleRecordingOverlayEnabled(); |
| |
| // Clean up prior to deletion. |
| void ShutDown(); |
| |
| // Returns the current parent window for |
| // `CaptureModeCameraController::camera_preview_widget_` when recording is in |
| // progress. |
| aura::Window* GetCameraPreviewParentWindow() const; |
| |
| // Returns the confine bounds for the camera preview when recording is in |
| // progress. |
| gfx::Rect GetCameraPreviewConfineBounds() const; |
| |
| // aura::WindowObserver: |
| void OnWindowParentChanged(aura::Window* window, |
| aura::Window* parent) override; |
| void OnWindowVisibilityChanged(aura::Window* window, bool visible) override; |
| void OnWindowBoundsChanged(aura::Window* window, |
| const gfx::Rect& old_bounds, |
| const gfx::Rect& new_bounds, |
| ui::PropertyChangeReason reason) override; |
| void OnWindowOpacitySet(aura::Window* window, |
| ui::PropertyChangeReason reason) override; |
| void OnWindowStackingChanged(aura::Window* window) override; |
| void OnWindowDestroying(aura::Window* window) override; |
| void OnWindowDestroyed(aura::Window* window) override; |
| void OnWindowRemovingFromRootWindow(aura::Window* window, |
| aura::Window* new_root) override; |
| |
| // ui::LayerDelegate: |
| void OnPaintLayer(const ui::PaintContext& context) override; |
| void OnDeviceScaleFactorChanged(float old_device_scale_factor, |
| float new_device_scale_factor) override {} |
| |
| // wm::ActivationChangeObserver: |
| void OnWindowActivated(ActivationReason reason, |
| aura::Window* gained_active, |
| aura::Window* lost_active) override; |
| |
| // display::DisplayObserver: |
| void OnDisplayMetricsChanged(const display::Display& display, |
| uint32_t metrics) override; |
| |
| // WindowDimmer::Delegate: |
| void OnDimmedWindowDestroying(aura::Window* window) override; |
| void OnDimmedWindowParentChanged(aura::Window* dimmed_window) override; |
| |
| // ui::EventHandler: |
| void OnKeyEvent(ui::KeyEvent* event) override; |
| void OnMouseEvent(ui::MouseEvent* event) override; |
| |
| // TabletModeObserver: |
| void OnTabletModeStarted() override; |
| void OnTabletModeEnded() override; |
| |
| // CursorWindowController::Observer: |
| void OnCursorCompositingStateChanged(bool enabled) override; |
| |
| // Returns the `partial_region_bounds_` clamped to the bounds of the |
| // `current_root_`. It should only be called if `recording_source_` is |
| // `kRegion`. |
| gfx::Rect GetEffectivePartialRegionBounds() const; |
| |
| bool IsWindowDimmedForTesting(aura::Window* window) const; |
| |
| void BindCursorOverlayForTesting( |
| mojo::PendingRemote<viz::mojom::FrameSinkVideoCaptureOverlay> overlay); |
| |
| void FlushCursorOverlayForTesting(); |
| |
| void SendThrottledWindowSizeChangedNowForTesting(); |
| |
| protected: |
| // ui::LayerOwner: |
| void SetLayer(std::unique_ptr<ui::Layer> layer) override; |
| |
| private: |
| friend class CaptureModeTestApi; |
| friend class RecordedWindowRootObserver; |
| |
| // Called by |RecordedWindowRootObserver| to notify us with a hierarchy change |
| // event received by the |current_root_| window. The |target| window is the |
| // window that was added to or removed from the hierarchy. |
| void OnRootHierarchyChanged(aura::Window* target); |
| |
| bool CalculateShouldPaintLayer() const; |
| |
| // Uses CalculateShouldPaintLayer() to update whether we should paint the |
| // recording shield, and stores the value in |should_paint_layer_|. If the |
| // value of |should_paint_layer_| is changed, it schedules painting on our |
| // layer. |
| void UpdateShouldPaintLayer(); |
| |
| // Updates our layer's parent and stacking order within its parent. It also |
| // determines whether some windows need to be dimmed individually because they |
| // are above the shield layer in z-order. |
| void UpdateLayerStackingAndDimmers(); |
| |
| // Returns the current native cursor from |cursor_manager_|. |
| gfx::NativeCursor GetCurrentCursor() const; |
| |
| // Updates the cursor overlay using the given |location| in the coordinates of |
| // the |window_being_recorded_|. This is used for non-pressed/-released mouse |
| // events which can be too frequent. Recording is performed at a rate of 30 |
| // FPS, so we don't need to send every mouse event to the capturer overlay on |
| // Viz via mojo. Such events can be throttled using the |
| // |cursor_events_throttle_timer_|. |
| void UpdateOrThrottleCursorOverlay(const gfx::PointF& location); |
| |
| // As opposed to the above UpdateOrThrottleCursorOverlay(), this updates the |
| // cursor capturer overlay immediately without throttling, if such an update |
| // is needed (e.g. the cursor bitmap changed, or the location changed, or |
| // both). This also cancels any pending throttled update, since this immediate |
| // one is more recent. |
| void UpdateCursorOverlayNow(const gfx::PointF& location); |
| |
| // Hides the cursor overlay in the video capturer. Note that this doesn't |
| // necessarily mean that the video won't contain a cursor, since the software- |
| // composited cursor might be enabled. See |force_cursor_overlay_hidden_|. |
| void HideCursorOverlay(); |
| |
| // Invoked when the |cursor_events_throttle_timer_| fires, in order to update |
| // the cursor overlay with the pending most recently throttled mouse event |
| // location in |throttled_cursor_location_| if any. |
| void OnCursorThrottleTimerFiring(); |
| |
| // Invoked when the |window_size_change_throttle_timer_| fires, in order to |
| // push the current size of the window being recorded to the service. |
| void OnWindowSizeChangeThrottleTimerFiring(); |
| |
| // Returns the bounds that should be used for the recording overlay widget |
| // relative to its parent |window_being_recorded_|. |
| gfx::Rect GetOverlayWidgetBounds() const; |
| |
| CaptureModeController* const controller_; |
| wm::CursorManager* const cursor_manager_; |
| aura::Window* const window_being_recorded_; |
| aura::Window* current_root_; |
| const CaptureModeSource recording_source_; |
| |
| // The end point of the overlay owned by the video capturer on Viz, which is |
| // used to blit the mouse cursor onto the recorded video frames. |
| mojo::Remote<viz::mojom::FrameSinkVideoCaptureOverlay> |
| cursor_capture_overlay_remote_; |
| |
| // Observes the hierarchy changes of the root window of the recorded window. |
| // Only constructed when performing a window recording (i.e. |
| // |recording_source_| is |kWindow|). |
| std::unique_ptr<RecordedWindowRootObserver> root_observer_; |
| |
| // The last cursor we used to update the cursor overlay. This is used to |
| // determine whether we need to update the cursor bitmap. |
| gfx::NativeCursor last_cursor_; |
| |
| // The last bounds we used to update the cursor overlay. This is used to skip |
| // the update if the bounds didn't change. |
| // Note that these bounds are relative within the bounds of the recorded frame |
| // sink, i.e. in the range [0.f, 1.f) for both origin() and size(). |
| // See documentation of FrameSinkVideoCaptureOverlay for more details. |
| gfx::RectF last_cursor_overlay_bounds_; |
| |
| // Since recording happens at a rate of 30 FPS, there's no need to send every |
| // mouse move event (or equivalent events such as enter, exit, dragged, ... |
| // etc.) to the cursor overlay. This timer is used to throttle such events |
| // received while this timer is running, and their location will overwrite the |
| // value of |throttled_cursor_location_|. Once the timer fires, |
| // OnCursorThrottleTimerFiring() will be called to update the overlay with the |
| // most recent received throttled event. |
| base::OneShotTimer cursor_events_throttle_timer_; |
| |
| // Stores the location of the most recent throttled mouse event (i.e. received |
| // while the |cursor_events_throttle_timer_| was running). The location is in |
| // the |window_being_recorded_| coordinates. |
| absl::optional<gfx::PointF> throttled_cursor_location_; |
| |
| // Resizing a window can generate many intermediate steps, and it would be |
| // inefficient to push all of them to the recording service, causing a |
| // repeated reconfiguration of the video encoder. This timer is used to |
| // throttle such events. |
| base::OneShotTimer window_size_change_throttle_timer_; |
| |
| // True if the current in progress recording is for a Projector mode session. |
| const bool is_in_projector_mode_; |
| |
| // True if we force hiding the cursor overlay. This happens when we record a |
| // fullscreen, or a partial screen region, and the software-composited cursor |
| // gets enabled. The software-composited cursor is already part of the root |
| // window's frame sink which we record, so we don't need to show the cursor |
| // overlay. Otherwise, the video will end up with two overlapping cursors. |
| bool force_cursor_overlay_hidden_ = false; |
| |
| // Whether or not to paint the layer content in OnPaintLayer(). The value of |
| // this field is calculated and updated in UpdateShouldPaintLayer(). |
| bool should_paint_layer_ = false; |
| |
| // The user-selected region for the current ongoing recording. This is only |
| // valid and non empty when the |recording_source_| is |kRegion|. Note that |
| // this differs from |CaptureModeController::user_capture_region_| in that the |
| // latter may change during the recording if the user opens capture mode again |
| // to take a partial screenshot. |
| gfx::Rect partial_region_bounds_; |
| |
| // Controls and owns the overlay widget, which is used to host Projector mode |
| // recording overlay contents such as annotations. |
| std::unique_ptr<RecordingOverlayController> recording_overlay_controller_; |
| |
| // Maintains window dimmers where each is mapped by the window it dims. These |
| // are created for the windows that are above the |window_being_recorded_| in |
| // z-order on the same display so as to clearly show they're not being |
| // recorded. The ones that are below |window_being_recorded_| in z-order are |
| // dimmed by the shield layer owned by |this|. |
| base::flat_map<aura::Window*, std::unique_ptr<WindowDimmer>> dimmers_; |
| |
| // If |window_being_recorded_| is not a root window, we must make a request to |
| // make it capturable by the |FrameSinkVideoCapturer|. |
| aura::ScopedWindowCaptureRequest non_root_window_capture_request_; |
| |
| // True if the shutting down process has been triggered. |
| bool is_shutting_down_ = false; |
| }; |
| |
| } // namespace ash |
| |
| #endif // ASH_CAPTURE_MODE_VIDEO_RECORDING_WATCHER_H_ |