1// Copyright 2013 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/autoclick/autoclick_controller.h"
6
7#include "ash/shell.h"
8#include "ash/wm/coordinate_conversion.h"
9#include "base/timer/timer.h"
10#include "ui/aura/env.h"
11#include "ui/aura/window_tree_host.h"
12#include "ui/events/event.h"
13#include "ui/events/event_constants.h"
14#include "ui/events/event_handler.h"
15#include "ui/events/event_processor.h"
16#include "ui/gfx/point.h"
17#include "ui/gfx/vector2d.h"
18#include "ui/wm/core/coordinate_conversion.h"
19
20namespace ash {
21
22namespace {
23
24// The threshold of mouse movement measured in DIP that will
25// initiate a new autoclick.
26const int kMovementThreshold = 20;
27
28bool IsModifierKey(ui::KeyboardCode key_code) {
29  return key_code == ui::VKEY_SHIFT ||
30      key_code == ui::VKEY_LSHIFT ||
31      key_code == ui::VKEY_CONTROL ||
32      key_code == ui::VKEY_LCONTROL ||
33      key_code == ui::VKEY_RCONTROL ||
34      key_code == ui::VKEY_MENU ||
35      key_code == ui::VKEY_LMENU ||
36      key_code == ui::VKEY_RMENU;
37}
38
39}  // namespace
40
41// static.
42const int AutoclickController::kDefaultAutoclickDelayMs = 400;
43
44class AutoclickControllerImpl : public AutoclickController,
45                                public ui::EventHandler {
46 public:
47  AutoclickControllerImpl();
48  virtual ~AutoclickControllerImpl();
49
50 private:
51  // AutoclickController overrides:
52  virtual void SetEnabled(bool enabled) OVERRIDE;
53  virtual bool IsEnabled() const OVERRIDE;
54  virtual void SetAutoclickDelay(int delay_ms) OVERRIDE;
55  virtual int GetAutoclickDelay() const OVERRIDE;
56
57  // ui::EventHandler overrides:
58  virtual void OnMouseEvent(ui::MouseEvent* event) OVERRIDE;
59  virtual void OnKeyEvent(ui::KeyEvent* event) OVERRIDE;
60  virtual void OnTouchEvent(ui::TouchEvent* event) OVERRIDE;
61  virtual void OnGestureEvent(ui::GestureEvent* event) OVERRIDE;
62  virtual void OnScrollEvent(ui::ScrollEvent* event) OVERRIDE;
63
64  void InitClickTimer();
65
66  void DoAutoclick();
67
68  bool enabled_;
69  int delay_ms_;
70  int mouse_event_flags_;
71  scoped_ptr<base::Timer> autoclick_timer_;
72  // The position in screen coordinates used to determine
73  // the distance the mouse has moved.
74  gfx::Point anchor_location_;
75
76  DISALLOW_COPY_AND_ASSIGN(AutoclickControllerImpl);
77};
78
79
80AutoclickControllerImpl::AutoclickControllerImpl()
81    : enabled_(false),
82      delay_ms_(kDefaultAutoclickDelayMs),
83      mouse_event_flags_(ui::EF_NONE),
84      anchor_location_(-kMovementThreshold, -kMovementThreshold) {
85  InitClickTimer();
86}
87
88AutoclickControllerImpl::~AutoclickControllerImpl() {
89}
90
91void AutoclickControllerImpl::SetEnabled(bool enabled) {
92  if (enabled_ == enabled)
93    return;
94  enabled_ = enabled;
95
96  if (enabled_) {
97    Shell::GetInstance()->AddPreTargetHandler(this);
98    autoclick_timer_->Stop();
99  } else {
100    Shell::GetInstance()->RemovePreTargetHandler(this);
101  }
102}
103
104bool AutoclickControllerImpl::IsEnabled() const {
105  return enabled_;
106}
107
108void AutoclickControllerImpl::SetAutoclickDelay(int delay_ms) {
109  delay_ms_ = delay_ms;
110  InitClickTimer();
111}
112
113int AutoclickControllerImpl::GetAutoclickDelay() const {
114  return delay_ms_;
115}
116
117void AutoclickControllerImpl::InitClickTimer() {
118  autoclick_timer_.reset(new base::Timer(
119      FROM_HERE,
120      base::TimeDelta::FromMilliseconds(delay_ms_),
121      base::Bind(&AutoclickControllerImpl::DoAutoclick,
122                 base::Unretained(this)),
123      false));
124}
125
126void AutoclickControllerImpl::OnMouseEvent(ui::MouseEvent* event) {
127  if (event->type() == ui::ET_MOUSE_MOVED &&
128      !(event->flags() & ui::EF_IS_SYNTHESIZED)) {
129    mouse_event_flags_ = event->flags();
130
131    gfx::Point mouse_location = event->root_location();
132    ::wm::ConvertPointToScreen(wm::GetRootWindowAt(mouse_location),
133                               &mouse_location);
134
135    // The distance between the mouse location and the anchor location
136    // must exceed a certain threshold to initiate a new autoclick countdown.
137    // This ensures that mouse jitter caused by poor motor control does not
138    // 1. initiate an unwanted autoclick from rest
139    // 2. prevent the autoclick from ever occuring when the mouse
140    //    arrives at the target.
141    gfx::Vector2d delta = mouse_location - anchor_location_;
142    if (delta.LengthSquared() >= kMovementThreshold * kMovementThreshold) {
143      anchor_location_ = event->root_location();
144      autoclick_timer_->Reset();
145    }
146  } else if (event->type() == ui::ET_MOUSE_PRESSED) {
147    autoclick_timer_->Stop();
148  } else if (event->type() == ui::ET_MOUSEWHEEL &&
149             autoclick_timer_->IsRunning()) {
150    autoclick_timer_->Reset();
151  }
152}
153
154void AutoclickControllerImpl::OnKeyEvent(ui::KeyEvent* event) {
155  int modifier_mask =
156      ui::EF_SHIFT_DOWN |
157      ui::EF_CONTROL_DOWN |
158      ui::EF_ALT_DOWN |
159      ui::EF_COMMAND_DOWN |
160      ui::EF_EXTENDED;
161  int new_modifiers = event->flags() & modifier_mask;
162  mouse_event_flags_ = (mouse_event_flags_ & ~modifier_mask) | new_modifiers;
163
164  if (!IsModifierKey(event->key_code()))
165    autoclick_timer_->Stop();
166}
167
168void AutoclickControllerImpl::OnTouchEvent(ui::TouchEvent* event) {
169  autoclick_timer_->Stop();
170}
171
172void AutoclickControllerImpl::OnGestureEvent(ui::GestureEvent* event) {
173  autoclick_timer_->Stop();
174}
175
176void AutoclickControllerImpl::OnScrollEvent(ui::ScrollEvent* event) {
177  autoclick_timer_->Stop();
178}
179
180void AutoclickControllerImpl::DoAutoclick() {
181  gfx::Point screen_location =
182      aura::Env::GetInstance()->last_mouse_location();
183  aura::Window* root_window = wm::GetRootWindowAt(screen_location);
184  DCHECK(root_window) << "Root window not found while attempting autoclick.";
185
186  gfx::Point click_location(screen_location);
187  anchor_location_ = click_location;
188  ::wm::ConvertPointFromScreen(root_window, &click_location);
189
190  aura::WindowTreeHost* host = root_window->GetHost();
191  host->ConvertPointToHost(&click_location);
192
193  ui::MouseEvent press_event(ui::ET_MOUSE_PRESSED,
194                             click_location,
195                             click_location,
196                             mouse_event_flags_ | ui::EF_LEFT_MOUSE_BUTTON,
197                             ui::EF_LEFT_MOUSE_BUTTON);
198  ui::MouseEvent release_event(ui::ET_MOUSE_RELEASED,
199                               click_location,
200                               click_location,
201                               mouse_event_flags_ | ui::EF_LEFT_MOUSE_BUTTON,
202                               ui::EF_LEFT_MOUSE_BUTTON);
203
204  ui::EventDispatchDetails details =
205      host->event_processor()->OnEventFromSource(&press_event);
206  if (!details.dispatcher_destroyed)
207    details = host->event_processor()->OnEventFromSource(&release_event);
208  if (details.dispatcher_destroyed)
209    return;
210}
211
212// static.
213AutoclickController* AutoclickController::CreateInstance() {
214  return new AutoclickControllerImpl();
215}
216
217}  // namespace ash
218