123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485 |
- // Copyright 2014 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/display/cursor_window_controller.h"
- #include "ash/accessibility/magnifier/fullscreen_magnifier_controller.h"
- #include "ash/capture_mode/capture_mode_camera_controller.h"
- #include "ash/capture_mode/capture_mode_controller.h"
- #include "ash/capture_mode/capture_mode_session.h"
- #include "ash/constants/ash_constants.h"
- #include "ash/constants/ash_pref_names.h"
- #include "ash/constants/ash_switches.h"
- #include "ash/display/display_color_manager.h"
- #include "ash/display/mirror_window_controller.h"
- #include "ash/display/window_tree_host_manager.h"
- #include "ash/fast_ink/cursor/cursor_view.h"
- #include "ash/public/cpp/shell_window_ids.h"
- #include "ash/root_window_controller.h"
- #include "ash/session/session_controller_impl.h"
- #include "ash/shell.h"
- #include "base/command_line.h"
- #include "base/metrics/histogram_macros.h"
- #include "components/prefs/pref_service.h"
- #include "ui/aura/env.h"
- #include "ui/aura/window.h"
- #include "ui/aura/window_delegate.h"
- #include "ui/aura/window_event_dispatcher.h"
- #include "ui/base/hit_test.h"
- #include "ui/base/layout.h"
- #include "ui/base/resource/resource_bundle.h"
- #include "ui/compositor/paint_recorder.h"
- #include "ui/display/display.h"
- #include "ui/display/manager/display_manager.h"
- #include "ui/display/screen.h"
- #include "ui/gfx/canvas.h"
- #include "ui/gfx/geometry/dip_util.h"
- #include "ui/gfx/geometry/point_conversions.h"
- #include "ui/gfx/image/image_skia.h"
- #include "ui/gfx/image/image_skia_operations.h"
- #include "ui/views/widget/widget.h"
- #include "ui/wm/core/cursors_aura.h"
- namespace ash {
- namespace {
- const int kMinLargeCursorSize = 25;
- const int kMaxLargeCursorSize = 64;
- } // namespace
- class CursorWindowDelegate : public aura::WindowDelegate {
- public:
- CursorWindowDelegate() = default;
- CursorWindowDelegate(const CursorWindowDelegate&) = delete;
- CursorWindowDelegate& operator=(const CursorWindowDelegate&) = delete;
- ~CursorWindowDelegate() override = default;
- // aura::WindowDelegate overrides:
- gfx::Size GetMinimumSize() const override { return size_; }
- gfx::Size GetMaximumSize() const override { return size_; }
- void OnBoundsChanged(const gfx::Rect& old_bounds,
- const gfx::Rect& new_bounds) override {}
- gfx::NativeCursor GetCursor(const gfx::Point& point) override {
- return gfx::kNullCursor;
- }
- int GetNonClientComponent(const gfx::Point& point) const override {
- return HTNOWHERE;
- }
- bool ShouldDescendIntoChildForEventHandling(
- aura::Window* child,
- const gfx::Point& location) override {
- return false;
- }
- bool CanFocus() override { return false; }
- void OnCaptureLost() override {}
- void OnPaint(const ui::PaintContext& context) override {
- // No need to cache the output here, the CursorWindow is not invalidated.
- ui::PaintRecorder recorder(context, size_);
- recorder.canvas()->DrawImageInt(cursor_image_, 0, 0);
- }
- void OnDeviceScaleFactorChanged(float old_device_scale_factor,
- float new_device_scale_factor) override {}
- void OnWindowDestroying(aura::Window* window) override {}
- void OnWindowDestroyed(aura::Window* window) override {}
- void OnWindowTargetVisibilityChanged(bool visible) override {}
- bool HasHitTestMask() const override { return false; }
- void GetHitTestMask(SkPath* mask) const override {}
- // Sets the cursor image for the |display|'s scale factor.
- void SetCursorImage(const gfx::Size& size, const gfx::ImageSkia& image) {
- size_ = size;
- cursor_image_ = image;
- }
- const gfx::Size& size() const { return size_; }
- const gfx::ImageSkia& cursor_image() const { return cursor_image_; }
- private:
- gfx::ImageSkia cursor_image_;
- gfx::Size size_;
- };
- CursorWindowController::CursorWindowController()
- : delegate_(new CursorWindowDelegate()),
- is_cursor_motion_blur_enabled_(
- base::CommandLine::ForCurrentProcess()->HasSwitch(
- switches::kAshEnableCursorMotionBlur)) {}
- CursorWindowController::~CursorWindowController() {
- SetContainer(NULL);
- }
- void CursorWindowController::AddObserver(Observer* observer) {
- observers_.AddObserver(observer);
- }
- void CursorWindowController::RemoveObserver(Observer* observer) {
- observers_.RemoveObserver(observer);
- }
- void CursorWindowController::SetLargeCursorSizeInDip(
- int large_cursor_size_in_dip) {
- large_cursor_size_in_dip =
- std::min(large_cursor_size_in_dip, kMaxLargeCursorSize);
- large_cursor_size_in_dip =
- std::max(large_cursor_size_in_dip, kMinLargeCursorSize);
- if (large_cursor_size_in_dip_ == large_cursor_size_in_dip)
- return;
- large_cursor_size_in_dip_ = large_cursor_size_in_dip;
- if (display_.is_valid())
- UpdateCursorImage();
- }
- void CursorWindowController::SetCursorColor(SkColor cursor_color) {
- if (cursor_color_ == cursor_color)
- return;
- cursor_color_ = cursor_color;
- if (display_.is_valid())
- UpdateCursorImage();
- }
- bool CursorWindowController::ShouldEnableCursorCompositing() {
- if (is_cursor_motion_blur_enabled_)
- return true;
- auto* controller = CaptureModeController::Get();
- auto* session = controller->capture_mode_session();
- if (session && session->is_drag_in_progress()) {
- // To ensure the cursor is aligned with the dragged region.
- return true;
- }
- if (controller->camera_controller()->is_drag_in_progress()) {
- // To ensure the cursor is aligned with the dragged camera preview.
- return true;
- }
- // During startup, we may not have a preference service yet. We need to check
- // display manager state first so that we don't accidentally ignore it while
- // early outing when there isn't a PrefService yet.
- Shell* shell = Shell::Get();
- display::DisplayManager* display_manager = shell->display_manager();
- if ((display_manager->IsInSoftwareMirrorMode()) ||
- display_manager->IsInUnifiedMode() ||
- display_manager->screen_capture_is_active()) {
- return true;
- }
- if (shell->fullscreen_magnifier_controller()->IsEnabled())
- return true;
- if (cursor_color_ != kDefaultCursorColor)
- return true;
- PrefService* prefs = shell->session_controller()->GetActivePrefService();
- if (!prefs) {
- // The active pref service can be null early in startup.
- return false;
- }
- if (prefs->GetBoolean(prefs::kNightLightEnabled)) {
- // All or some displays don't support setting a CRTC matrix, which means
- // Night Light is using the composited color matrix, and hence software
- // cursor should be used.
- // TODO(afakhry): Instead of switching to the composited cursor on all
- // displays if any of them don't support a CRTC matrix, we should provide
- // the functionality to turn on the composited cursor on a per-display basis
- // (i.e. use it only on the displays that don't support CRTC matrices).
- const DisplayColorManager::DisplayCtmSupport displays_ctm_support =
- shell->display_color_manager()->displays_ctm_support();
- UMA_HISTOGRAM_ENUMERATION("Ash.NightLight.DisplayCrtcCtmSupport",
- displays_ctm_support);
- if (displays_ctm_support != DisplayColorManager::DisplayCtmSupport::kAll)
- return true;
- }
- return prefs->GetBoolean(prefs::kAccessibilityLargeCursorEnabled) ||
- prefs->GetBoolean(prefs::kAccessibilityHighContrastEnabled) ||
- prefs->GetBoolean(prefs::kDockedMagnifierEnabled);
- }
- void CursorWindowController::SetCursorCompositingEnabled(bool enabled) {
- if (is_cursor_compositing_enabled_ == enabled)
- return;
- is_cursor_compositing_enabled_ = enabled;
- if (display_.is_valid())
- UpdateCursorImage();
- UpdateContainer();
- for (auto& obs : observers_)
- obs.OnCursorCompositingStateChanged(is_cursor_compositing_enabled_);
- }
- void CursorWindowController::UpdateContainer() {
- if (is_cursor_compositing_enabled_) {
- display::Screen* screen = display::Screen::GetScreen();
- display::Display display =
- screen->GetDisplayNearestPoint(screen->GetCursorScreenPoint());
- DCHECK(display.is_valid());
- if (display.is_valid())
- SetDisplay(display);
- } else {
- SetContainer(nullptr);
- }
- // Updates the hot point based on the current display.
- UpdateCursorImage();
- }
- void CursorWindowController::SetDisplay(const display::Display& display) {
- if (!is_cursor_compositing_enabled_)
- return;
- // TODO(oshima): Do not update the composition cursor when crossing
- // display in unified desktop mode for now. crbug.com/517222.
- if (Shell::Get()->display_manager()->IsInUnifiedMode() &&
- display.id() != display::kUnifiedDisplayId) {
- return;
- }
- display_ = display;
- aura::Window* root_window = Shell::GetRootWindowForDisplayId(display.id());
- if (!root_window)
- return;
- SetContainer(RootWindowController::ForWindow(root_window)
- ->GetContainer(kShellWindowId_MouseCursorContainer));
- SetBoundsInScreenAndRotation(display.bounds(), display.rotation());
- // Updates the hot point based on the current display.
- UpdateCursorImage();
- }
- void CursorWindowController::OnDockedMagnifierResizingStateChanged(
- bool is_active) {
- const int container_id = is_active ? kShellWindowId_DockedMagnifierContainer
- : kShellWindowId_MouseCursorContainer;
- SetContainer(
- RootWindowController::ForWindow(container_)->GetContainer(container_id));
- }
- void CursorWindowController::UpdateLocation() {
- if (!cursor_window_)
- return;
- gfx::Point point = aura::Env::GetInstance()->last_mouse_location();
- point.Offset(-bounds_in_screen_.x(), -bounds_in_screen_.y());
- point.Offset(-hot_point_.x(), -hot_point_.y());
- gfx::Rect bounds = cursor_window_->bounds();
- bounds.set_origin(point);
- cursor_window_->SetBounds(bounds);
- }
- void CursorWindowController::SetCursor(gfx::NativeCursor cursor) {
- if (cursor_ == cursor)
- return;
- cursor_ = cursor;
- UpdateCursorImage();
- UpdateCursorVisibility();
- }
- void CursorWindowController::SetCursorSize(ui::CursorSize cursor_size) {
- cursor_size_ = cursor_size;
- UpdateCursorImage();
- }
- void CursorWindowController::SetVisibility(bool visible) {
- visible_ = visible;
- UpdateCursorVisibility();
- }
- const aura::Window* CursorWindowController::GetContainerForTest() const {
- return container_;
- }
- void CursorWindowController::SetContainer(aura::Window* container) {
- if (container_ == container)
- return;
- container_ = container;
- if (!container) {
- cursor_window_.reset();
- cursor_view_widget_.reset();
- return;
- }
- bounds_in_screen_ = display_.bounds();
- rotation_ = display_.rotation();
- if (is_cursor_motion_blur_enabled_) {
- UpdateCursorView();
- } else {
- // Reusing the window does not work when the display is disconnected.
- // Just creates a new one instead. crbug.com/384218.
- cursor_window_ = std::make_unique<aura::Window>(delegate_.get());
- cursor_window_->SetTransparent(true);
- cursor_window_->Init(ui::LAYER_TEXTURED);
- cursor_window_->SetEventTargetingPolicy(aura::EventTargetingPolicy::kNone);
- cursor_window_->set_owned_by_parent(false);
- // Call UpdateCursorImage() to figure out |cursor_window_|'s desired size.
- UpdateCursorImage();
- container->AddChild(cursor_window_.get());
- }
- UpdateCursorVisibility();
- UpdateLocation();
- }
- void CursorWindowController::SetBoundsInScreenAndRotation(
- const gfx::Rect& bounds,
- display::Display::Rotation rotation) {
- if (bounds == bounds_in_screen_ && rotation == rotation_)
- return;
- bounds_in_screen_ = bounds;
- rotation_ = rotation;
- if (cursor_view_widget_)
- UpdateCursorView();
- UpdateLocation();
- }
- void CursorWindowController::UpdateCursorImage() {
- if (!is_cursor_compositing_enabled_)
- return;
- // Use the original device scale factor instead of the display's, which
- // might have been adjusted for the UI scale.
- const float original_scale = Shell::Get()
- ->display_manager()
- ->GetDisplayInfo(display_.id())
- .device_scale_factor();
- // And use the nearest resource scale factor.
- float cursor_scale = ui::GetScaleForResourceScaleFactor(
- ui::GetSupportedResourceScaleFactor(original_scale));
- gfx::ImageSkia image;
- gfx::Point hot_point_in_physical_pixels;
- if (cursor_.type() == ui::mojom::CursorType::kCustom) {
- const SkBitmap& bitmap = cursor_.custom_bitmap();
- if (bitmap.isNull())
- return;
- image = gfx::ImageSkia::CreateFrom1xBitmap(bitmap);
- hot_point_in_physical_pixels = cursor_.custom_hotspot();
- } else {
- int resource_id;
- if (!wm::GetCursorDataFor(cursor_size_, cursor_.type(), cursor_scale,
- &resource_id, &hot_point_in_physical_pixels)) {
- return;
- }
- image =
- *ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(resource_id);
- }
- gfx::ImageSkia resized = image;
- // Rescale cursor size. This is used with the combination of accessibility
- // large cursor. We don't need to care about the case where cursor
- // compositing is disabled as we always use cursor compositing if
- // accessibility large cursor is enabled.
- if (cursor_size_ == ui::CursorSize::kLarge &&
- large_cursor_size_in_dip_ != image.size().width()) {
- float rescale = static_cast<float>(large_cursor_size_in_dip_) /
- static_cast<float>(image.size().width());
- resized = gfx::ImageSkiaOperations::CreateResizedImage(
- image, skia::ImageOperations::ResizeMethod::RESIZE_GOOD,
- gfx::ScaleToCeiledSize(image.size(), rescale));
- hot_point_in_physical_pixels =
- gfx::ScaleToCeiledPoint(hot_point_in_physical_pixels, rescale);
- }
- const gfx::ImageSkiaRep& image_rep = resized.GetRepresentation(cursor_scale);
- delegate_->SetCursorImage(resized.size(),
- gfx::ImageSkia::CreateFromBitmap(
- GetAdjustedBitmap(image_rep), cursor_scale));
- // TODO(danakj): Should this be rounded? Or kept as a floating point?
- hot_point_ = gfx::ToFlooredPoint(
- gfx::ConvertPointToDips(hot_point_in_physical_pixels, cursor_scale));
- if (cursor_view_widget_) {
- static_cast<cursor::CursorView*>(cursor_view_widget_->GetContentsView())
- ->SetCursorImage(delegate_->cursor_image(), delegate_->size(),
- hot_point_);
- }
- if (cursor_window_) {
- cursor_window_->SetBounds(gfx::Rect(delegate_->size()));
- cursor_window_->SchedulePaintInRect(
- gfx::Rect(cursor_window_->bounds().size()));
- }
- UpdateLocation();
- }
- void CursorWindowController::UpdateCursorVisibility() {
- bool visible = (visible_ && cursor_.type() != ui::mojom::CursorType::kNone);
- if (visible) {
- if (cursor_view_widget_)
- cursor_view_widget_->Show();
- if (cursor_window_)
- cursor_window_->Show();
- } else {
- if (cursor_view_widget_)
- cursor_view_widget_->Hide();
- if (cursor_window_)
- cursor_window_->Hide();
- }
- }
- void CursorWindowController::UpdateCursorView() {
- cursor_view_widget_ = cursor::CursorView::Create(
- aura::Env::GetInstance()->last_mouse_location(),
- is_cursor_motion_blur_enabled_, container_);
- UpdateCursorImage();
- }
- const gfx::ImageSkia& CursorWindowController::GetCursorImageForTest() const {
- return delegate_->cursor_image();
- }
- SkBitmap CursorWindowController::GetAdjustedBitmap(
- const gfx::ImageSkiaRep& image_rep) const {
- SkBitmap bitmap = image_rep.GetBitmap();
- if (cursor_color_ == kDefaultCursorColor)
- return bitmap;
- // Recolor the black and greyscale parts of the image based on
- // cursor_color_. Do not recolor pure white or tinted portions of the image,
- // this ensures we do not impact the colored portions of cursors or the
- // transition between the colored portion and white outline.
- // TODO(crbug.com/1085442): Programmatically find a way to recolor the white
- // parts in order to draw a black outline, but without impacting cursors
- // like noDrop which contained tinted portions. Or, add new assets with
- // black and white inverted for easier re-coloring.
- SkBitmap recolored;
- recolored.allocN32Pixels(bitmap.width(), bitmap.height());
- recolored.eraseARGB(0, 0, 0, 0);
- SkCanvas canvas(recolored);
- canvas.drawImage(bitmap.asImage(), 0, 0);
- color_utils::HSL cursor_hsl;
- color_utils::SkColorToHSL(cursor_color_, &cursor_hsl);
- for (int y = 0; y < bitmap.height(); ++y) {
- for (int x = 0; x < bitmap.width(); ++x) {
- SkColor color = bitmap.getColor(x, y);
- // If the alpha is lower than 1, it's transparent, skip it.
- if (SkColorGetA(color) < 1)
- continue;
- // Convert to HSL: We want to change the hue and saturation, and
- // map the lightness from 0-100 to cursor_hsl.l-100. This means that
- // things which were black (l=0) become the cursor color lightness, and
- // things which were white (l=100) stay white.
- color_utils::HSL hsl;
- color_utils::SkColorToHSL(color, &hsl);
- // If it has color, do not change it.
- if (hsl.s > 0.01)
- continue;
- color_utils::HSL result;
- result.h = cursor_hsl.h;
- result.s = cursor_hsl.s;
- result.l = hsl.l * (1 - cursor_hsl.l) + cursor_hsl.l;
- SkPaint paint;
- paint.setColor(color_utils::HSLToSkColor(result, SkColorGetA(color)));
- canvas.drawRect(SkRect::MakeXYWH(x, y, 1, 1), paint);
- }
- }
- return recolored;
- }
- } // namespace ash
|