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/sticky_keys/sticky_keys_controller.h"
6
7 #include "ash/sticky_keys/sticky_keys_overlay.h"
8 #include "base/basictypes.h"
9 #include "base/debug/stack_trace.h"
10 #include "ui/aura/window.h"
11 #include "ui/aura/window_tracker.h"
12 #include "ui/aura/window_tree_host.h"
13 #include "ui/events/event.h"
14 #include "ui/events/event_processor.h"
15 #include "ui/events/keycodes/keyboard_code_conversion.h"
16
17 namespace ash {
18
19 namespace {
20
21 // Returns true if the type of mouse event should be modified by sticky keys.
ShouldModifyMouseEvent(const ui::MouseEvent & event)22 bool ShouldModifyMouseEvent(const ui::MouseEvent& event) {
23 ui::EventType type = event.type();
24 return type == ui::ET_MOUSE_PRESSED || type == ui::ET_MOUSE_RELEASED ||
25 type == ui::ET_MOUSEWHEEL;
26 }
27
28 // Handle the common tail of event rewriting.
RewriteUpdate(bool consumed,bool released,int mod_down_flags,int * flags)29 ui::EventRewriteStatus RewriteUpdate(bool consumed,
30 bool released,
31 int mod_down_flags,
32 int* flags) {
33 int changed_down_flags = mod_down_flags & ~*flags;
34 *flags |= mod_down_flags;
35 if (consumed)
36 return ui::EVENT_REWRITE_DISCARD;
37 if (released)
38 return ui::EVENT_REWRITE_DISPATCH_ANOTHER;
39 if (changed_down_flags)
40 return ui::EVENT_REWRITE_REWRITTEN;
41 return ui::EVENT_REWRITE_CONTINUE;
42 }
43
44 } // namespace
45
46 ///////////////////////////////////////////////////////////////////////////////
47 // StickyKeys
StickyKeysController()48 StickyKeysController::StickyKeysController()
49 : enabled_(false),
50 mod3_enabled_(false),
51 altgr_enabled_(false) {
52 }
53
~StickyKeysController()54 StickyKeysController::~StickyKeysController() {
55 }
56
Enable(bool enabled)57 void StickyKeysController::Enable(bool enabled) {
58 if (enabled_ != enabled) {
59 enabled_ = enabled;
60
61 // Reset key handlers when activating sticky keys to ensure all
62 // the handlers' states are reset.
63 if (enabled_) {
64 shift_sticky_key_.reset(new StickyKeysHandler(ui::EF_SHIFT_DOWN));
65 alt_sticky_key_.reset(new StickyKeysHandler(ui::EF_ALT_DOWN));
66 altgr_sticky_key_.reset(new StickyKeysHandler(ui::EF_ALTGR_DOWN));
67 ctrl_sticky_key_.reset(new StickyKeysHandler(ui::EF_CONTROL_DOWN));
68 mod3_sticky_key_.reset(new StickyKeysHandler(ui::EF_MOD3_DOWN));
69
70 overlay_.reset(new StickyKeysOverlay());
71 overlay_->SetModifierVisible(ui::EF_ALTGR_DOWN, altgr_enabled_);
72 overlay_->SetModifierVisible(ui::EF_MOD3_DOWN, mod3_enabled_);
73 } else if (overlay_) {
74 overlay_->Show(false);
75 }
76 }
77 }
78
SetModifiersEnabled(bool mod3_enabled,bool altgr_enabled)79 void StickyKeysController::SetModifiersEnabled(bool mod3_enabled,
80 bool altgr_enabled) {
81 mod3_enabled_ = mod3_enabled;
82 altgr_enabled_ = altgr_enabled;
83 if (overlay_) {
84 overlay_->SetModifierVisible(ui::EF_ALTGR_DOWN, altgr_enabled_);
85 overlay_->SetModifierVisible(ui::EF_MOD3_DOWN, mod3_enabled_);
86 }
87 }
88
HandleKeyEvent(const ui::KeyEvent & event,ui::KeyboardCode key_code,int * mod_down_flags,bool * released)89 bool StickyKeysController::HandleKeyEvent(const ui::KeyEvent& event,
90 ui::KeyboardCode key_code,
91 int* mod_down_flags,
92 bool* released) {
93 return shift_sticky_key_->HandleKeyEvent(
94 event, key_code, mod_down_flags, released) ||
95 alt_sticky_key_->HandleKeyEvent(
96 event, key_code, mod_down_flags, released) ||
97 altgr_sticky_key_->HandleKeyEvent(
98 event, key_code, mod_down_flags, released) ||
99 ctrl_sticky_key_->HandleKeyEvent(
100 event, key_code, mod_down_flags, released) ||
101 mod3_sticky_key_->HandleKeyEvent(
102 event, key_code, mod_down_flags, released);
103 }
104
HandleMouseEvent(const ui::MouseEvent & event,int * mod_down_flags,bool * released)105 bool StickyKeysController::HandleMouseEvent(const ui::MouseEvent& event,
106 int* mod_down_flags,
107 bool* released) {
108 return shift_sticky_key_->HandleMouseEvent(
109 event, mod_down_flags, released) ||
110 alt_sticky_key_->HandleMouseEvent(
111 event, mod_down_flags, released) ||
112 altgr_sticky_key_->HandleMouseEvent(
113 event, mod_down_flags, released) ||
114 ctrl_sticky_key_->HandleMouseEvent(
115 event, mod_down_flags, released) ||
116 mod3_sticky_key_->HandleMouseEvent(
117 event, mod_down_flags, released);
118 }
119
HandleScrollEvent(const ui::ScrollEvent & event,int * mod_down_flags,bool * released)120 bool StickyKeysController::HandleScrollEvent(const ui::ScrollEvent& event,
121 int* mod_down_flags,
122 bool* released) {
123 return shift_sticky_key_->HandleScrollEvent(
124 event, mod_down_flags, released) ||
125 alt_sticky_key_->HandleScrollEvent(
126 event, mod_down_flags, released) ||
127 altgr_sticky_key_->HandleScrollEvent(
128 event, mod_down_flags, released) ||
129 ctrl_sticky_key_->HandleScrollEvent(
130 event, mod_down_flags, released) ||
131 mod3_sticky_key_->HandleScrollEvent(
132 event, mod_down_flags, released);
133 }
134
RewriteKeyEvent(const ui::KeyEvent & event,ui::KeyboardCode key_code,int * flags)135 ui::EventRewriteStatus StickyKeysController::RewriteKeyEvent(
136 const ui::KeyEvent& event,
137 ui::KeyboardCode key_code,
138 int* flags) {
139 if (!enabled_)
140 return ui::EVENT_REWRITE_CONTINUE;
141 int mod_down_flags = 0;
142 bool released = false;
143 bool consumed = HandleKeyEvent(event, key_code, &mod_down_flags, &released);
144 UpdateOverlay();
145 return RewriteUpdate(consumed, released, mod_down_flags, flags);
146 }
147
RewriteMouseEvent(const ui::MouseEvent & event,int * flags)148 ui::EventRewriteStatus StickyKeysController::RewriteMouseEvent(
149 const ui::MouseEvent& event,
150 int* flags) {
151 if (!enabled_)
152 return ui::EVENT_REWRITE_CONTINUE;
153 int mod_down_flags = 0;
154 bool released = false;
155 bool consumed = HandleMouseEvent(event, &mod_down_flags, &released);
156 UpdateOverlay();
157 return RewriteUpdate(consumed, released, mod_down_flags, flags);
158 }
159
RewriteScrollEvent(const ui::ScrollEvent & event,int * flags)160 ui::EventRewriteStatus StickyKeysController::RewriteScrollEvent(
161 const ui::ScrollEvent& event,
162 int* flags) {
163 if (!enabled_)
164 return ui::EVENT_REWRITE_CONTINUE;
165 int mod_down_flags = 0;
166 bool released = false;
167 bool consumed = HandleScrollEvent(event, &mod_down_flags, &released);
168 UpdateOverlay();
169 return RewriteUpdate(consumed, released, mod_down_flags, flags);
170 }
171
NextDispatchEvent(scoped_ptr<ui::Event> * new_event)172 ui::EventRewriteStatus StickyKeysController::NextDispatchEvent(
173 scoped_ptr<ui::Event>* new_event) {
174 DCHECK(new_event);
175 new_event->reset();
176 int remaining = shift_sticky_key_->GetModifierUpEvent(new_event) +
177 alt_sticky_key_->GetModifierUpEvent(new_event) +
178 altgr_sticky_key_->GetModifierUpEvent(new_event) +
179 ctrl_sticky_key_->GetModifierUpEvent(new_event) +
180 mod3_sticky_key_->GetModifierUpEvent(new_event);
181 if (!new_event)
182 return ui::EVENT_REWRITE_CONTINUE;
183 if (remaining)
184 return ui::EVENT_REWRITE_DISPATCH_ANOTHER;
185 return ui::EVENT_REWRITE_REWRITTEN;
186 }
187
UpdateOverlay()188 void StickyKeysController::UpdateOverlay() {
189 overlay_->SetModifierKeyState(
190 ui::EF_SHIFT_DOWN, shift_sticky_key_->current_state());
191 overlay_->SetModifierKeyState(
192 ui::EF_CONTROL_DOWN, ctrl_sticky_key_->current_state());
193 overlay_->SetModifierKeyState(
194 ui::EF_ALT_DOWN, alt_sticky_key_->current_state());
195 overlay_->SetModifierKeyState(
196 ui::EF_ALTGR_DOWN, altgr_sticky_key_->current_state());
197 overlay_->SetModifierKeyState(
198 ui::EF_MOD3_DOWN, mod3_sticky_key_->current_state());
199
200 bool key_in_use =
201 shift_sticky_key_->current_state() != STICKY_KEY_STATE_DISABLED ||
202 alt_sticky_key_->current_state() != STICKY_KEY_STATE_DISABLED ||
203 altgr_sticky_key_->current_state() != STICKY_KEY_STATE_DISABLED ||
204 ctrl_sticky_key_->current_state() != STICKY_KEY_STATE_DISABLED ||
205 mod3_sticky_key_->current_state() != STICKY_KEY_STATE_DISABLED;
206
207 overlay_->Show(enabled_ && key_in_use);
208 }
209
GetOverlayForTest()210 StickyKeysOverlay* StickyKeysController::GetOverlayForTest() {
211 return overlay_.get();
212 }
213
214 ///////////////////////////////////////////////////////////////////////////////
215 // StickyKeysHandler
StickyKeysHandler(ui::EventFlags modifier_flag)216 StickyKeysHandler::StickyKeysHandler(ui::EventFlags modifier_flag)
217 : modifier_flag_(modifier_flag),
218 current_state_(STICKY_KEY_STATE_DISABLED),
219 preparing_to_enable_(false),
220 scroll_delta_(0) {
221 }
222
~StickyKeysHandler()223 StickyKeysHandler::~StickyKeysHandler() {
224 }
225
HandleKeyEvent(const ui::KeyEvent & event,ui::KeyboardCode key_code,int * mod_down_flags,bool * released)226 bool StickyKeysHandler::HandleKeyEvent(const ui::KeyEvent& event,
227 ui::KeyboardCode key_code,
228 int* mod_down_flags,
229 bool* released) {
230 switch (current_state_) {
231 case STICKY_KEY_STATE_DISABLED:
232 return HandleDisabledState(event, key_code);
233 case STICKY_KEY_STATE_ENABLED:
234 return HandleEnabledState(event, key_code, mod_down_flags, released);
235 case STICKY_KEY_STATE_LOCKED:
236 return HandleLockedState(event, key_code, mod_down_flags, released);
237 }
238 NOTREACHED();
239 return false;
240 }
241
HandleMouseEvent(const ui::MouseEvent & event,int * mod_down_flags,bool * released)242 bool StickyKeysHandler::HandleMouseEvent(
243 const ui::MouseEvent& event,
244 int* mod_down_flags,
245 bool* released) {
246 if (ShouldModifyMouseEvent(event))
247 preparing_to_enable_ = false;
248
249 if (current_state_ == STICKY_KEY_STATE_DISABLED ||
250 !ShouldModifyMouseEvent(event)) {
251 return false;
252 }
253 DCHECK(current_state_ == STICKY_KEY_STATE_ENABLED ||
254 current_state_ == STICKY_KEY_STATE_LOCKED);
255
256 *mod_down_flags |= modifier_flag_;
257 // Only disable on the mouse released event in normal, non-locked mode.
258 if (current_state_ == STICKY_KEY_STATE_ENABLED &&
259 event.type() != ui::ET_MOUSE_PRESSED) {
260 current_state_ = STICKY_KEY_STATE_DISABLED;
261 *released = true;
262 return false;
263 }
264
265 return false;
266 }
267
HandleScrollEvent(const ui::ScrollEvent & event,int * mod_down_flags,bool * released)268 bool StickyKeysHandler::HandleScrollEvent(
269 const ui::ScrollEvent& event,
270 int* mod_down_flags,
271 bool* released) {
272 preparing_to_enable_ = false;
273 if (current_state_ == STICKY_KEY_STATE_DISABLED)
274 return false;
275 DCHECK(current_state_ == STICKY_KEY_STATE_ENABLED ||
276 current_state_ == STICKY_KEY_STATE_LOCKED);
277
278 // We detect a direction change if the current |scroll_delta_| is assigned
279 // and the offset of the current scroll event has the opposing sign.
280 bool direction_changed = false;
281 if (current_state_ == STICKY_KEY_STATE_ENABLED &&
282 event.type() == ui::ET_SCROLL) {
283 int offset = event.y_offset();
284 if (scroll_delta_)
285 direction_changed = offset * scroll_delta_ <= 0;
286 scroll_delta_ = offset;
287 }
288
289 if (!direction_changed)
290 *mod_down_flags |= modifier_flag_;
291
292 // We want to modify all the scroll events in the scroll sequence, which ends
293 // with a fling start event. We also stop when the scroll sequence changes
294 // direction.
295 if (current_state_ == STICKY_KEY_STATE_ENABLED &&
296 (event.type() == ui::ET_SCROLL_FLING_START || direction_changed)) {
297 current_state_ = STICKY_KEY_STATE_DISABLED;
298 scroll_delta_ = 0;
299 *released = true;
300 return false;
301 }
302
303 return false;
304 }
305
GetModifierUpEvent(scoped_ptr<ui::Event> * new_event)306 int StickyKeysHandler::GetModifierUpEvent(scoped_ptr<ui::Event>* new_event) {
307 if (current_state_ != STICKY_KEY_STATE_DISABLED || !modifier_up_event_)
308 return 0;
309 DCHECK(new_event);
310 if (*new_event)
311 return 1;
312 new_event->reset(modifier_up_event_.release());
313 return 0;
314 }
315
TranslateKeyEvent(ui::EventType type,ui::KeyboardCode key_code)316 StickyKeysHandler::KeyEventType StickyKeysHandler::TranslateKeyEvent(
317 ui::EventType type,
318 ui::KeyboardCode key_code) {
319 bool is_target_key = false;
320 if (key_code == ui::VKEY_SHIFT ||
321 key_code == ui::VKEY_LSHIFT ||
322 key_code == ui::VKEY_RSHIFT) {
323 is_target_key = (modifier_flag_ == ui::EF_SHIFT_DOWN);
324 } else if (key_code == ui::VKEY_CONTROL ||
325 key_code == ui::VKEY_LCONTROL ||
326 key_code == ui::VKEY_RCONTROL) {
327 is_target_key = (modifier_flag_ == ui::EF_CONTROL_DOWN);
328 } else if (key_code == ui::VKEY_MENU ||
329 key_code == ui::VKEY_LMENU ||
330 key_code == ui::VKEY_RMENU) {
331 is_target_key = (modifier_flag_ == ui::EF_ALT_DOWN);
332 } else if (key_code == ui::VKEY_ALTGR) {
333 is_target_key = (modifier_flag_ == ui::EF_ALTGR_DOWN);
334 } else if (key_code == ui::VKEY_OEM_8) {
335 is_target_key = (modifier_flag_ == ui::EF_MOD3_DOWN);
336 } else {
337 return type == ui::ET_KEY_PRESSED ?
338 NORMAL_KEY_DOWN : NORMAL_KEY_UP;
339 }
340
341 if (is_target_key) {
342 return type == ui::ET_KEY_PRESSED ?
343 TARGET_MODIFIER_DOWN : TARGET_MODIFIER_UP;
344 }
345 return type == ui::ET_KEY_PRESSED ?
346 OTHER_MODIFIER_DOWN : OTHER_MODIFIER_UP;
347 }
348
HandleDisabledState(const ui::KeyEvent & event,ui::KeyboardCode key_code)349 bool StickyKeysHandler::HandleDisabledState(const ui::KeyEvent& event,
350 ui::KeyboardCode key_code) {
351 switch (TranslateKeyEvent(event.type(), key_code)) {
352 case TARGET_MODIFIER_UP:
353 if (preparing_to_enable_) {
354 preparing_to_enable_ = false;
355 scroll_delta_ = 0;
356 current_state_ = STICKY_KEY_STATE_ENABLED;
357 modifier_up_event_.reset(new ui::KeyEvent(event));
358 return true;
359 }
360 return false;
361 case TARGET_MODIFIER_DOWN:
362 preparing_to_enable_ = true;
363 return false;
364 case NORMAL_KEY_DOWN:
365 preparing_to_enable_ = false;
366 return false;
367 case NORMAL_KEY_UP:
368 case OTHER_MODIFIER_DOWN:
369 case OTHER_MODIFIER_UP:
370 return false;
371 }
372 NOTREACHED();
373 return false;
374 }
375
HandleEnabledState(const ui::KeyEvent & event,ui::KeyboardCode key_code,int * mod_down_flags,bool * released)376 bool StickyKeysHandler::HandleEnabledState(const ui::KeyEvent& event,
377 ui::KeyboardCode key_code,
378 int* mod_down_flags,
379 bool* released) {
380 switch (TranslateKeyEvent(event.type(), key_code)) {
381 case NORMAL_KEY_UP:
382 case TARGET_MODIFIER_DOWN:
383 return false;
384 case TARGET_MODIFIER_UP:
385 current_state_ = STICKY_KEY_STATE_LOCKED;
386 modifier_up_event_.reset();
387 return true;
388 case NORMAL_KEY_DOWN: {
389 current_state_ = STICKY_KEY_STATE_DISABLED;
390 *mod_down_flags |= modifier_flag_;
391 *released = true;
392 return false;
393 }
394 case OTHER_MODIFIER_DOWN:
395 case OTHER_MODIFIER_UP:
396 return false;
397 }
398 NOTREACHED();
399 return false;
400 }
401
HandleLockedState(const ui::KeyEvent & event,ui::KeyboardCode key_code,int * mod_down_flags,bool * released)402 bool StickyKeysHandler::HandleLockedState(const ui::KeyEvent& event,
403 ui::KeyboardCode key_code,
404 int* mod_down_flags,
405 bool* released) {
406 switch (TranslateKeyEvent(event.type(), key_code)) {
407 case TARGET_MODIFIER_DOWN:
408 return true;
409 case TARGET_MODIFIER_UP:
410 current_state_ = STICKY_KEY_STATE_DISABLED;
411 return false;
412 case NORMAL_KEY_DOWN:
413 case NORMAL_KEY_UP:
414 *mod_down_flags |= modifier_flag_;
415 return false;
416 case OTHER_MODIFIER_DOWN:
417 case OTHER_MODIFIER_UP:
418 return false;
419 }
420 NOTREACHED();
421 return false;
422 }
423
424 } // namespace ash
425