Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(61)

Side by Side Diff: ash/utility/screenshot_controller.cc

Issue 1909873002: Allow taking screen shot of a non child window which has delegate to paint. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: rebase Created 4 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « ash/utility/screenshot_controller.h ('k') | ash/utility/screenshot_controller_unittest.cc » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright 2014 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
5 #include "ash/utility/screenshot_controller.h"
6
7 #include <cmath>
8
9 #include "ash/display/mouse_cursor_event_filter.h"
10 #include "ash/screenshot_delegate.h"
11 #include "ash/shell.h"
12 #include "ash/shell_window_ids.h"
13 #include "ash/wm/window_util.h"
14 #include "base/stl_util.h"
15 #include "ui/aura/client/capture_client.h"
16 #include "ui/aura/client/screen_position_client.h"
17 #include "ui/aura/window_targeter.h"
18 #include "ui/compositor/paint_recorder.h"
19 #include "ui/events/event.h"
20 #include "ui/events/event_handler.h"
21 #include "ui/gfx/canvas.h"
22 #include "ui/gfx/screen.h"
23 #include "ui/views/widget/widget.h"
24 #include "ui/wm/core/cursor_manager.h"
25
26 namespace ash {
27
28 namespace {
29
30 // The size to increase the invalidated area in the layer to repaint. The area
31 // should be slightly bigger than the actual region because the region indicator
32 // rectangles are drawn outside of the selected region.
33 const int kInvalidateRegionAdditionalSize = 3;
34
35 // This will prevent the user from taking a screenshot across multiple
36 // monitors. it will stop the mouse at the any edge of the screen. must
37 // swtich back on when the screenshot is complete.
38 void EnableMouseWarp(bool enable) {
39 Shell::GetInstance()->mouse_cursor_filter()->set_mouse_warp_enabled(enable);
40 }
41
42 class ScreenshotWindowTargeter : public aura::WindowTargeter {
43 public:
44 ScreenshotWindowTargeter() = default;
45 ~ScreenshotWindowTargeter() override = default;
46
47 aura::Window* FindWindowForEvent(ui::LocatedEvent* event) {
48 aura::Window* target = static_cast<aura::Window*>(event->target());
49 aura::Window* target_root = target->GetRootWindow();
50
51 aura::client::ScreenPositionClient* position_client =
52 aura::client::GetScreenPositionClient(target_root);
53 gfx::Point location = event->location();
54 position_client->ConvertPointToScreen(target, &location);
55
56 gfx::Display display =
57 gfx::Screen::GetScreen()->GetDisplayNearestPoint(location);
58
59 aura::Window* root_window = Shell::GetInstance()
60 ->window_tree_host_manager()
61 ->GetRootWindowForDisplayId(display.id());
62
63 position_client->ConvertPointFromScreen(root_window, &location);
64
65 gfx::Point target_location = event->location();
66 event->set_location(location);
67
68 // Ignore capture window when finding the target for located event.
69 aura::client::CaptureClient* original_capture_client =
70 aura::client::GetCaptureClient(root_window);
71 aura::client::SetCaptureClient(root_window, nullptr);
72
73 aura::Window* selected =
74 static_cast<aura::Window*>(FindTargetForEvent(root_window, event));
75
76 // Restore State.
77 aura::client::SetCaptureClient(root_window, original_capture_client);
78 event->set_location(target_location);
79 return selected;
80 }
81
82 private:
83 DISALLOW_COPY_AND_ASSIGN(ScreenshotWindowTargeter);
84 };
85
86 } // namespace
87
88 class ScreenshotController::ScreenshotLayer : public ui::LayerOwner,
89 public ui::LayerDelegate {
90 public:
91 ScreenshotLayer(ui::Layer* parent) {
92 SetLayer(new ui::Layer(ui::LAYER_TEXTURED));
93 layer()->SetFillsBoundsOpaquely(false);
94 layer()->SetBounds(parent->bounds());
95 parent->Add(layer());
96 parent->StackAtTop(layer());
97 layer()->SetVisible(true);
98 layer()->set_delegate(this);
99 }
100 ~ScreenshotLayer() override {}
101
102 const gfx::Rect& region() const { return region_; }
103
104 void SetRegion(const gfx::Rect& region) {
105 // Invalidates the region which covers the current and new region.
106 gfx::Rect union_rect(region_);
107 union_rect.Union(region);
108 union_rect.Inset(-kInvalidateRegionAdditionalSize,
109 -kInvalidateRegionAdditionalSize);
110 union_rect.Intersects(layer()->bounds());
111 region_ = region;
112 layer()->SchedulePaint(union_rect);
113 }
114
115 private:
116 // ui::LayerDelegate:
117 void OnPaintLayer(const ui::PaintContext& context) override {
118 const SkColor kSelectedAreaOverlayColor = 0x40000000;
119 if (region_.IsEmpty())
120 return;
121 // Screenshot area representation: black rectangle with white
122 // rectangle inside. To avoid capturing these rectangles when mouse
123 // release, they should be outside of the actual capturing area.
124 gfx::Rect rect(region_);
125 ui::PaintRecorder recorder(context, layer()->size());
126
127 recorder.canvas()->FillRect(region_, kSelectedAreaOverlayColor);
128
129 rect.Inset(-1, -1);
130 recorder.canvas()->DrawRect(rect, SK_ColorWHITE);
131 rect.Inset(-1, -1);
132 recorder.canvas()->DrawRect(rect, SK_ColorBLACK);
133 }
134
135 void OnDelegatedFrameDamage(const gfx::Rect& damage_rect_in_dip) override {}
136
137 void OnDeviceScaleFactorChanged(float device_scale_factor) override {}
138
139 base::Closure PrepareForLayerBoundsChange() override {
140 return base::Closure();
141 }
142
143 gfx::Rect region_;
144
145 DISALLOW_COPY_AND_ASSIGN(ScreenshotLayer);
146 };
147
148 class ScreenshotController::ScopedCursorSetter {
149 public:
150 ScopedCursorSetter(::wm::CursorManager* cursor_manager,
151 gfx::NativeCursor cursor)
152 : cursor_manager_(nullptr) {
153 if (cursor_manager->IsCursorLocked())
154 return;
155 gfx::NativeCursor original_cursor = cursor_manager->GetCursor();
156 cursor_manager_ = cursor_manager;
157 cursor_manager_->SetCursor(cursor);
158 if (!cursor_manager_->IsCursorVisible())
159 cursor_manager_->ShowCursor();
160 cursor_manager_->LockCursor();
161 // SetCursor does not make any effects at this point but it sets back to
162 // the original cursor when unlocked.
163 cursor_manager_->SetCursor(original_cursor);
164 }
165
166 ~ScopedCursorSetter() {
167 if (cursor_manager_)
168 cursor_manager_->UnlockCursor();
169 }
170
171 private:
172 ::wm::CursorManager* cursor_manager_;
173
174 DISALLOW_COPY_AND_ASSIGN(ScopedCursorSetter);
175 };
176
177 ScreenshotController::ScreenshotController()
178 : mode_(NONE),
179 root_window_(nullptr),
180 selected_(nullptr),
181 screenshot_delegate_(nullptr) {
182 // Keep this here and don't move it to StartPartialScreenshotSession(), as it
183 // needs to be pre-pended by MouseCursorEventFilter in Shell::Init().
184 Shell::GetInstance()->PrependPreTargetHandler(this);
185 }
186
187 ScreenshotController::~ScreenshotController() {
188 if (screenshot_delegate_)
189 Cancel();
190 Shell::GetInstance()->RemovePreTargetHandler(this);
191 }
192
193 void ScreenshotController::StartWindowScreenshotSession(
194 ScreenshotDelegate* screenshot_delegate) {
195 if (screenshot_delegate_) {
196 DCHECK_EQ(screenshot_delegate_, screenshot_delegate);
197 return;
198 }
199 screenshot_delegate_ = screenshot_delegate;
200 mode_ = WINDOW;
201
202 gfx::Screen::GetScreen()->AddObserver(this);
203 for (aura::Window* root : Shell::GetAllRootWindows()) {
204 layers_[root] = new ScreenshotLayer(
205 Shell::GetContainer(root, kShellWindowId_OverlayContainer)->layer());
206 }
207 SetSelectedWindow(wm::GetActiveWindow());
208
209 cursor_setter_.reset(new ScopedCursorSetter(
210 Shell::GetInstance()->cursor_manager(), ui::kCursorCross));
211
212 EnableMouseWarp(true);
213 }
214
215 void ScreenshotController::StartPartialScreenshotSession(
216 ScreenshotDelegate* screenshot_delegate) {
217 // Already in a screenshot session.
218 if (screenshot_delegate_) {
219 DCHECK_EQ(screenshot_delegate_, screenshot_delegate);
220 return;
221 }
222
223 screenshot_delegate_ = screenshot_delegate;
224 mode_ = PARTIAL;
225 gfx::Screen::GetScreen()->AddObserver(this);
226 for (aura::Window* root : Shell::GetAllRootWindows()) {
227 layers_[root] = new ScreenshotLayer(
228 Shell::GetContainer(root, kShellWindowId_OverlayContainer)->layer());
229 }
230
231 cursor_setter_.reset(new ScopedCursorSetter(
232 Shell::GetInstance()->cursor_manager(), ui::kCursorCross));
233
234 EnableMouseWarp(false);
235 }
236
237 void ScreenshotController::MaybeStart(const ui::LocatedEvent& event) {
238 aura::Window* current_root =
239 static_cast<aura::Window*>(event.target())->GetRootWindow();
240 if (root_window_) {
241 // It's already started. This can happen when the second finger touches
242 // the screen, or combination of the touch and mouse. We should grab the
243 // partial screenshot instead of restarting.
244 if (current_root == root_window_) {
245 Update(event);
246 CompletePartialScreenshot();
247 }
248 } else {
249 root_window_ = current_root;
250 start_position_ = event.root_location();
251 }
252 }
253
254 void ScreenshotController::CompleteWindowScreenshot() {
255 if (selected_)
256 screenshot_delegate_->HandleTakeWindowScreenshot(selected_);
257 Cancel();
258 }
259
260 void ScreenshotController::CompletePartialScreenshot() {
261 if (!root_window_) {
262 // If we received a released event before we ever got a pressed event
263 // (resulting in setting |root_window_|), we just return without canceling
264 // to keep the screenshot session active waiting for the next press.
265 //
266 // This is to avoid a crash that used to happen when we start the screenshot
267 // session while the mouse is pressed and then release without moving the
268 // mouse. crbug.com/581432.
269 return;
270 }
271
272 DCHECK(layers_.count(root_window_));
273 const gfx::Rect& region = layers_.at(root_window_)->region();
274 if (!region.IsEmpty()) {
275 screenshot_delegate_->HandleTakePartialScreenshot(
276 root_window_, gfx::IntersectRects(root_window_->bounds(), region));
277 }
278 Cancel();
279 }
280
281 void ScreenshotController::Cancel() {
282 mode_ = NONE;
283 root_window_ = nullptr;
284 SetSelectedWindow(nullptr);
285 screenshot_delegate_ = nullptr;
286 gfx::Screen::GetScreen()->RemoveObserver(this);
287 STLDeleteValues(&layers_);
288 cursor_setter_.reset();
289 EnableMouseWarp(true);
290 }
291
292 void ScreenshotController::Update(const ui::LocatedEvent& event) {
293 // Update may happen without MaybeStart() if the partial screenshot session
294 // starts when dragging.
295 if (!root_window_)
296 MaybeStart(event);
297
298 DCHECK(layers_.find(root_window_) != layers_.end());
299 layers_.at(root_window_)
300 ->SetRegion(
301 gfx::Rect(std::min(start_position_.x(), event.root_location().x()),
302 std::min(start_position_.y(), event.root_location().y()),
303 ::abs(start_position_.x() - event.root_location().x()),
304 ::abs(start_position_.y() - event.root_location().y())));
305 }
306
307 void ScreenshotController::UpdateSelectedWindow(ui::LocatedEvent* event) {
308 aura::Window* selected = ScreenshotWindowTargeter().FindWindowForEvent(event);
309
310 // Find a window that is backed with a widget.
311 while (selected && (selected->type() == ui::wm::WINDOW_TYPE_CONTROL ||
312 !selected->delegate())) {
313 selected = selected->parent();
314 }
315
316 if (selected->parent()->id() == kShellWindowId_DesktopBackgroundContainer ||
317 selected->parent()->id() == kShellWindowId_LockScreenBackgroundContainer)
318 selected = nullptr;
319
320 SetSelectedWindow(selected);
321 }
322
323 void ScreenshotController::SetSelectedWindow(aura::Window* selected) {
324 if (selected_ == selected)
325 return;
326
327 if (selected_) {
328 selected_->RemoveObserver(this);
329 layers_.at(selected_->GetRootWindow())->SetRegion(gfx::Rect());
330 }
331
332 selected_ = selected;
333
334 if (selected_) {
335 selected_->AddObserver(this);
336 layers_.at(selected_->GetRootWindow())->SetRegion(selected_->bounds());
337 }
338 }
339
340 void ScreenshotController::OnKeyEvent(ui::KeyEvent* event) {
341 if (!screenshot_delegate_)
342 return;
343
344 if (event->type() == ui::ET_KEY_RELEASED) {
345 if (event->key_code() == ui::VKEY_ESCAPE) {
346 Cancel();
347 } else if (event->key_code() == ui::VKEY_RETURN && mode_ == WINDOW) {
348 CompleteWindowScreenshot();
349 }
350 }
351
352 // Intercepts all key events.
353 event->StopPropagation();
354 }
355
356 void ScreenshotController::OnMouseEvent(ui::MouseEvent* event) {
357 if (!screenshot_delegate_)
358 return;
359 switch (mode_) {
360 case NONE:
361 NOTREACHED();
362 break;
363 case WINDOW:
364 switch (event->type()) {
365 case ui::ET_MOUSE_MOVED:
366 case ui::ET_MOUSE_DRAGGED:
367 UpdateSelectedWindow(event);
368 break;
369 case ui::ET_MOUSE_RELEASED:
370 CompleteWindowScreenshot();
371 break;
372 default:
373 // Do nothing.
374 break;
375 }
376 break;
377 case PARTIAL:
378 switch (event->type()) {
379 case ui::ET_MOUSE_PRESSED:
380 MaybeStart(*event);
381 break;
382 case ui::ET_MOUSE_DRAGGED:
383 Update(*event);
384 break;
385 case ui::ET_MOUSE_RELEASED:
386 CompletePartialScreenshot();
387 break;
388 default:
389 // Do nothing.
390 break;
391 }
392 break;
393 }
394 event->StopPropagation();
395 }
396
397 void ScreenshotController::OnTouchEvent(ui::TouchEvent* event) {
398 if (!screenshot_delegate_)
399 return;
400 switch (mode_) {
401 case NONE:
402 NOTREACHED();
403 break;
404 case WINDOW:
405 switch (event->type()) {
406 case ui::ET_TOUCH_PRESSED:
407 case ui::ET_TOUCH_MOVED:
408 UpdateSelectedWindow(event);
409 break;
410 case ui::ET_TOUCH_RELEASED:
411 CompleteWindowScreenshot();
412 break;
413 default:
414 // Do nothing.
415 break;
416 }
417 break;
418 case PARTIAL:
419 switch (event->type()) {
420 case ui::ET_TOUCH_PRESSED:
421 MaybeStart(*event);
422 break;
423 case ui::ET_TOUCH_MOVED:
424 Update(*event);
425 break;
426 case ui::ET_TOUCH_RELEASED:
427 CompletePartialScreenshot();
428 break;
429 default:
430 // Do nothing.
431 break;
432 }
433 break;
434 }
435 event->StopPropagation();
436 }
437
438 void ScreenshotController::OnDisplayAdded(const gfx::Display& new_display) {
439 if (!screenshot_delegate_)
440 return;
441 Cancel();
442 }
443
444 void ScreenshotController::OnDisplayRemoved(const gfx::Display& old_display) {
445 if (!screenshot_delegate_)
446 return;
447 Cancel();
448 }
449
450 void ScreenshotController::OnDisplayMetricsChanged(const gfx::Display& display,
451 uint32_t changed_metrics) {}
452
453 void ScreenshotController::OnWindowDestroying(aura::Window* window) {
454 SetSelectedWindow(nullptr);
455 }
456
457 } // namespace ash
OLDNEW
« no previous file with comments | « ash/utility/screenshot_controller.h ('k') | ash/utility/screenshot_controller_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698