// 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/utility/partial_screenshot_controller.h" #include #include "ash/display/mouse_cursor_event_filter.h" #include "ash/screenshot_delegate.h" #include "ash/shell.h" #include "ash/shell_window_ids.h" #include "base/stl_util.h" #include "ui/compositor/paint_recorder.h" #include "ui/events/event.h" #include "ui/events/event_handler.h" #include "ui/gfx/canvas.h" #include "ui/wm/core/cursor_manager.h" namespace ash { namespace { // The size to increase the invalidated area in the layer to repaint. The area // should be slightly bigger than the actual region because the region indicator // rectangles are drawn outside of the selected region. const int kInvalidateRegionAdditionalSize = 3; // This will prevent the user from taking a screenshot across multiple // monitors. it will stop the mouse at the any edge of the screen. must // swtich back on when the screenshot is complete. void EnableMouseWarp(bool enable) { Shell::GetInstance()->mouse_cursor_filter()->set_mouse_warp_enabled(enable); } } // namespace class PartialScreenshotController::PartialScreenshotLayer : public ui::LayerOwner, public ui::LayerDelegate { public: PartialScreenshotLayer(ui::Layer* parent) { SetLayer(new ui::Layer(ui::LAYER_TEXTURED)); layer()->SetFillsBoundsOpaquely(false); layer()->SetBounds(parent->bounds()); parent->Add(layer()); parent->StackAtTop(layer()); layer()->SetVisible(true); layer()->set_delegate(this); } ~PartialScreenshotLayer() override {} const gfx::Rect& region() const { return region_; } void SetRegion(const gfx::Rect& region) { // Invalidates the region which covers the current and new region. gfx::Rect union_rect(region_); union_rect.Union(region); union_rect.Inset(-kInvalidateRegionAdditionalSize, -kInvalidateRegionAdditionalSize); union_rect.Intersects(layer()->bounds()); region_ = region; layer()->SchedulePaint(union_rect); } private: // ui::LayerDelegate: void OnPaintLayer(const ui::PaintContext& context) override { if (region_.IsEmpty()) return; // Screenshot area representation: black rectangle with white // rectangle inside. To avoid capturing these rectangles when mouse // release, they should be outside of the actual capturing area. gfx::Rect rect(region_); ui::PaintRecorder recorder(context, layer()->size()); rect.Inset(-1, -1); recorder.canvas()->DrawRect(rect, SK_ColorWHITE); rect.Inset(-1, -1); recorder.canvas()->DrawRect(rect, SK_ColorBLACK); } void OnDelegatedFrameDamage(const gfx::Rect& damage_rect_in_dip) override {} void OnDeviceScaleFactorChanged(float device_scale_factor) override {} base::Closure PrepareForLayerBoundsChange() override { return base::Closure(); } gfx::Rect region_; DISALLOW_COPY_AND_ASSIGN(PartialScreenshotLayer); }; class PartialScreenshotController::ScopedCursorSetter { public: ScopedCursorSetter(::wm::CursorManager* cursor_manager, gfx::NativeCursor cursor) : cursor_manager_(nullptr) { if (cursor_manager->IsCursorLocked()) return; gfx::NativeCursor original_cursor = cursor_manager->GetCursor(); cursor_manager_ = cursor_manager; cursor_manager_->SetCursor(cursor); if (!cursor_manager_->IsCursorVisible()) cursor_manager_->ShowCursor(); cursor_manager_->LockCursor(); // SetCursor does not make any effects at this point but it sets back to // the original cursor when unlocked. cursor_manager_->SetCursor(original_cursor); } ~ScopedCursorSetter() { if (cursor_manager_) cursor_manager_->UnlockCursor(); } private: ::wm::CursorManager* cursor_manager_; DISALLOW_COPY_AND_ASSIGN(ScopedCursorSetter); }; PartialScreenshotController::PartialScreenshotController() : root_window_(nullptr), screenshot_delegate_(nullptr) { Shell* shell = Shell::GetInstance(); shell->PrependPreTargetHandler(this); } PartialScreenshotController::~PartialScreenshotController() { if (screenshot_delegate_) Cancel(); Shell::GetInstance()->RemovePreTargetHandler(this); } void PartialScreenshotController::StartPartialScreenshotSession( ScreenshotDelegate* screenshot_delegate) { // Already in a screenshot session. if (screenshot_delegate_) { DCHECK_EQ(screenshot_delegate_, screenshot_delegate); return; } screenshot_delegate_ = screenshot_delegate; Shell::GetScreen()->AddObserver(this); for (aura::Window* root : Shell::GetAllRootWindows()) { layers_[root] = new PartialScreenshotLayer( Shell::GetContainer(root, kShellWindowId_OverlayContainer)->layer()); } cursor_setter_.reset(new ScopedCursorSetter( Shell::GetInstance()->cursor_manager(), ui::kCursorCross)); EnableMouseWarp(false); } void PartialScreenshotController::MaybeStart(const ui::LocatedEvent& event) { aura::Window* current_root = static_cast(event.target())->GetRootWindow(); if (root_window_) { // It's already started. This can happen when the second finger touches // the screen, or combination of the touch and mouse. We should grab the // partial screenshot instead of restarting. if (current_root == root_window_) { Update(event); Complete(); } } else { root_window_ = current_root; start_position_ = event.root_location(); } } void PartialScreenshotController::Complete() { const gfx::Rect& region = layers_[root_window_]->region(); if (!region.IsEmpty()) { screenshot_delegate_->HandleTakePartialScreenshot( root_window_, gfx::IntersectRects(root_window_->bounds(), region)); } Cancel(); } void PartialScreenshotController::Cancel() { root_window_ = nullptr; screenshot_delegate_ = nullptr; Shell::GetScreen()->RemoveObserver(this); STLDeleteValues(&layers_); cursor_setter_.reset(); EnableMouseWarp(true); } void PartialScreenshotController::Update(const ui::LocatedEvent& event) { // Update may happen without MaybeStart() if the partial screenshot session // starts when dragging. if (!root_window_) MaybeStart(event); DCHECK(layers_.find(root_window_) != layers_.end()); layers_[root_window_]->SetRegion( gfx::Rect(std::min(start_position_.x(), event.root_location().x()), std::min(start_position_.y(), event.root_location().y()), ::abs(start_position_.x() - event.root_location().x()), ::abs(start_position_.y() - event.root_location().y()))); } void PartialScreenshotController::OnKeyEvent(ui::KeyEvent* event) { if (!screenshot_delegate_) return; if (event->type() == ui::ET_KEY_RELEASED && event->key_code() == ui::VKEY_ESCAPE) { Cancel(); } // Intercepts all key events. event->StopPropagation(); } void PartialScreenshotController::OnMouseEvent(ui::MouseEvent* event) { if (!screenshot_delegate_) return; switch (event->type()) { case ui::ET_MOUSE_PRESSED: MaybeStart(*event); break; case ui::ET_MOUSE_DRAGGED: Update(*event); break; case ui::ET_MOUSE_RELEASED: Complete(); break; default: // Do nothing. break; } event->StopPropagation(); } void PartialScreenshotController::OnTouchEvent(ui::TouchEvent* event) { if (!screenshot_delegate_) return; switch (event->type()) { case ui::ET_TOUCH_PRESSED: MaybeStart(*event); break; case ui::ET_TOUCH_MOVED: Update(*event); break; case ui::ET_TOUCH_RELEASED: Complete(); break; default: // Do nothing. break; } event->StopPropagation(); } void PartialScreenshotController::OnDisplayAdded( const gfx::Display& new_display) { if (!screenshot_delegate_) return; Cancel(); } void PartialScreenshotController::OnDisplayRemoved( const gfx::Display& old_display) { if (!screenshot_delegate_) return; Cancel(); } void PartialScreenshotController::OnDisplayMetricsChanged( const gfx::Display& display, uint32_t changed_metrics) { } } // namespace ash