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