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
17namespace ash {
18
19namespace {
20
21// Returns true if the type of mouse event should be modified by sticky keys.
22bool 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.
29ui::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
48StickyKeysController::StickyKeysController()
49    : enabled_(false),
50      mod3_enabled_(false),
51      altgr_enabled_(false) {
52}
53
54StickyKeysController::~StickyKeysController() {
55}
56
57void 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
79void 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
89bool 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
105bool 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
120bool 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
135ui::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
148ui::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
160ui::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
172ui::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
188void 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
210StickyKeysOverlay* StickyKeysController::GetOverlayForTest() {
211  return overlay_.get();
212}
213
214///////////////////////////////////////////////////////////////////////////////
215//  StickyKeysHandler
216StickyKeysHandler::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
223StickyKeysHandler::~StickyKeysHandler() {
224}
225
226bool 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
242bool 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
268bool 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
306int 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
316StickyKeysHandler::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
349bool 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
376bool 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
402bool 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