// Copyright 2013 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "ash/sticky_keys/sticky_keys_controller.h" #include "ash/sticky_keys/sticky_keys_overlay.h" #include "base/debug/stack_trace.h" #include "ui/aura/window.h" #include "ui/aura/window_tracker.h" #include "ui/aura/window_tree_host.h" #include "ui/events/event.h" #include "ui/events/event_processor.h" #include "ui/events/keycodes/keyboard_code_conversion.h" namespace ash { namespace { // Returns true if the type of mouse event should be modified by sticky keys. bool ShouldModifyMouseEvent(const ui::MouseEvent& event) { ui::EventType type = event.type(); return type == ui::ET_MOUSE_PRESSED || type == ui::ET_MOUSE_RELEASED || type == ui::ET_MOUSEWHEEL; } // Handle the common tail of event rewriting. ui::EventRewriteStatus RewriteUpdate(bool consumed, bool released, int mod_down_flags, int* flags) { int changed_down_flags = mod_down_flags & ~*flags; *flags |= mod_down_flags; if (consumed) return ui::EVENT_REWRITE_DISCARD; if (released) return ui::EVENT_REWRITE_DISPATCH_ANOTHER; if (changed_down_flags) return ui::EVENT_REWRITE_REWRITTEN; return ui::EVENT_REWRITE_CONTINUE; } } // namespace /////////////////////////////////////////////////////////////////////////////// // StickyKeys StickyKeysController::StickyKeysController() : enabled_(false), mod3_enabled_(false), altgr_enabled_(false) { } StickyKeysController::~StickyKeysController() { } void StickyKeysController::Enable(bool enabled) { if (enabled_ != enabled) { enabled_ = enabled; // Reset key handlers when activating sticky keys to ensure all // the handlers' states are reset. if (enabled_) { shift_sticky_key_.reset(new StickyKeysHandler(ui::EF_SHIFT_DOWN)); alt_sticky_key_.reset(new StickyKeysHandler(ui::EF_ALT_DOWN)); altgr_sticky_key_.reset(new StickyKeysHandler(ui::EF_ALTGR_DOWN)); ctrl_sticky_key_.reset(new StickyKeysHandler(ui::EF_CONTROL_DOWN)); mod3_sticky_key_.reset(new StickyKeysHandler(ui::EF_MOD3_DOWN)); search_sticky_key_.reset(new StickyKeysHandler(ui::EF_COMMAND_DOWN)); overlay_.reset(new StickyKeysOverlay()); overlay_->SetModifierVisible(ui::EF_ALTGR_DOWN, altgr_enabled_); overlay_->SetModifierVisible(ui::EF_MOD3_DOWN, mod3_enabled_); } else if (overlay_) { overlay_.reset(); } } } void StickyKeysController::SetModifiersEnabled(bool mod3_enabled, bool altgr_enabled) { mod3_enabled_ = mod3_enabled; altgr_enabled_ = altgr_enabled; if (overlay_) { overlay_->SetModifierVisible(ui::EF_ALTGR_DOWN, altgr_enabled_); overlay_->SetModifierVisible(ui::EF_MOD3_DOWN, mod3_enabled_); } } bool StickyKeysController::HandleKeyEvent(const ui::KeyEvent& event, ui::KeyboardCode key_code, int* mod_down_flags, bool* released) { return shift_sticky_key_->HandleKeyEvent( event, key_code, mod_down_flags, released) || alt_sticky_key_->HandleKeyEvent( event, key_code, mod_down_flags, released) || altgr_sticky_key_->HandleKeyEvent( event, key_code, mod_down_flags, released) || ctrl_sticky_key_->HandleKeyEvent( event, key_code, mod_down_flags, released) || mod3_sticky_key_->HandleKeyEvent( event, key_code, mod_down_flags, released) || search_sticky_key_->HandleKeyEvent( event, key_code, mod_down_flags, released); } bool StickyKeysController::HandleMouseEvent(const ui::MouseEvent& event, int* mod_down_flags, bool* released) { return shift_sticky_key_->HandleMouseEvent( event, mod_down_flags, released) || alt_sticky_key_->HandleMouseEvent( event, mod_down_flags, released) || altgr_sticky_key_->HandleMouseEvent( event, mod_down_flags, released) || ctrl_sticky_key_->HandleMouseEvent( event, mod_down_flags, released) || mod3_sticky_key_->HandleMouseEvent( event, mod_down_flags, released); } bool StickyKeysController::HandleScrollEvent(const ui::ScrollEvent& event, int* mod_down_flags, bool* released) { return shift_sticky_key_->HandleScrollEvent( event, mod_down_flags, released) || alt_sticky_key_->HandleScrollEvent( event, mod_down_flags, released) || altgr_sticky_key_->HandleScrollEvent( event, mod_down_flags, released) || ctrl_sticky_key_->HandleScrollEvent( event, mod_down_flags, released) || mod3_sticky_key_->HandleScrollEvent( event, mod_down_flags, released); } ui::EventRewriteStatus StickyKeysController::RewriteKeyEvent( const ui::KeyEvent& event, ui::KeyboardCode key_code, int* flags) { if (!enabled_) return ui::EVENT_REWRITE_CONTINUE; int mod_down_flags = 0; bool released = false; bool consumed = HandleKeyEvent(event, key_code, &mod_down_flags, &released); UpdateOverlay(); return RewriteUpdate(consumed, released, mod_down_flags, flags); } ui::EventRewriteStatus StickyKeysController::RewriteMouseEvent( const ui::MouseEvent& event, int* flags) { if (!enabled_) return ui::EVENT_REWRITE_CONTINUE; int mod_down_flags = 0; bool released = false; bool consumed = HandleMouseEvent(event, &mod_down_flags, &released); UpdateOverlay(); return RewriteUpdate(consumed, released, mod_down_flags, flags); } ui::EventRewriteStatus StickyKeysController::RewriteScrollEvent( const ui::ScrollEvent& event, int* flags) { if (!enabled_) return ui::EVENT_REWRITE_CONTINUE; int mod_down_flags = 0; bool released = false; bool consumed = HandleScrollEvent(event, &mod_down_flags, &released); UpdateOverlay(); return RewriteUpdate(consumed, released, mod_down_flags, flags); } ui::EventRewriteStatus StickyKeysController::NextDispatchEvent( scoped_ptr* new_event) { DCHECK(new_event); new_event->reset(); int remaining = shift_sticky_key_->GetModifierUpEvent(new_event) + alt_sticky_key_->GetModifierUpEvent(new_event) + altgr_sticky_key_->GetModifierUpEvent(new_event) + ctrl_sticky_key_->GetModifierUpEvent(new_event) + mod3_sticky_key_->GetModifierUpEvent(new_event) + search_sticky_key_->GetModifierUpEvent(new_event); if (!new_event) return ui::EVENT_REWRITE_CONTINUE; if (remaining) return ui::EVENT_REWRITE_DISPATCH_ANOTHER; return ui::EVENT_REWRITE_REWRITTEN; } void StickyKeysController::UpdateOverlay() { overlay_->SetModifierKeyState( ui::EF_SHIFT_DOWN, shift_sticky_key_->current_state()); overlay_->SetModifierKeyState( ui::EF_CONTROL_DOWN, ctrl_sticky_key_->current_state()); overlay_->SetModifierKeyState( ui::EF_ALT_DOWN, alt_sticky_key_->current_state()); overlay_->SetModifierKeyState( ui::EF_COMMAND_DOWN, search_sticky_key_->current_state()); overlay_->SetModifierKeyState( ui::EF_ALTGR_DOWN, altgr_sticky_key_->current_state()); overlay_->SetModifierKeyState( ui::EF_MOD3_DOWN, mod3_sticky_key_->current_state()); bool key_in_use = shift_sticky_key_->current_state() != STICKY_KEY_STATE_DISABLED || alt_sticky_key_->current_state() != STICKY_KEY_STATE_DISABLED || altgr_sticky_key_->current_state() != STICKY_KEY_STATE_DISABLED || ctrl_sticky_key_->current_state() != STICKY_KEY_STATE_DISABLED || search_sticky_key_->current_state() != STICKY_KEY_STATE_DISABLED || mod3_sticky_key_->current_state() != STICKY_KEY_STATE_DISABLED; overlay_->Show(enabled_ && key_in_use); } StickyKeysOverlay* StickyKeysController::GetOverlayForTest() { return overlay_.get(); } /////////////////////////////////////////////////////////////////////////////// // StickyKeysHandler StickyKeysHandler::StickyKeysHandler(ui::EventFlags modifier_flag) : modifier_flag_(modifier_flag), current_state_(STICKY_KEY_STATE_DISABLED), preparing_to_enable_(false), scroll_delta_(0) { } StickyKeysHandler::~StickyKeysHandler() { } bool StickyKeysHandler::HandleKeyEvent(const ui::KeyEvent& event, ui::KeyboardCode key_code, int* mod_down_flags, bool* released) { switch (current_state_) { case STICKY_KEY_STATE_DISABLED: return HandleDisabledState(event, key_code); case STICKY_KEY_STATE_ENABLED: return HandleEnabledState(event, key_code, mod_down_flags, released); case STICKY_KEY_STATE_LOCKED: return HandleLockedState(event, key_code, mod_down_flags, released); } NOTREACHED(); return false; } bool StickyKeysHandler::HandleMouseEvent( const ui::MouseEvent& event, int* mod_down_flags, bool* released) { if (ShouldModifyMouseEvent(event)) preparing_to_enable_ = false; if (current_state_ == STICKY_KEY_STATE_DISABLED || !ShouldModifyMouseEvent(event)) { return false; } DCHECK(current_state_ == STICKY_KEY_STATE_ENABLED || current_state_ == STICKY_KEY_STATE_LOCKED); *mod_down_flags |= modifier_flag_; // Only disable on the mouse released event in normal, non-locked mode. if (current_state_ == STICKY_KEY_STATE_ENABLED && event.type() != ui::ET_MOUSE_PRESSED) { current_state_ = STICKY_KEY_STATE_DISABLED; *released = true; return false; } return false; } bool StickyKeysHandler::HandleScrollEvent( const ui::ScrollEvent& event, int* mod_down_flags, bool* released) { preparing_to_enable_ = false; if (current_state_ == STICKY_KEY_STATE_DISABLED) return false; DCHECK(current_state_ == STICKY_KEY_STATE_ENABLED || current_state_ == STICKY_KEY_STATE_LOCKED); // We detect a direction change if the current |scroll_delta_| is assigned // and the offset of the current scroll event has the opposing sign. bool direction_changed = false; if (current_state_ == STICKY_KEY_STATE_ENABLED && event.type() == ui::ET_SCROLL) { int offset = event.y_offset(); if (scroll_delta_) direction_changed = offset * scroll_delta_ <= 0; scroll_delta_ = offset; } if (!direction_changed) *mod_down_flags |= modifier_flag_; // We want to modify all the scroll events in the scroll sequence, which ends // with a fling start event. We also stop when the scroll sequence changes // direction. if (current_state_ == STICKY_KEY_STATE_ENABLED && (event.type() == ui::ET_SCROLL_FLING_START || direction_changed)) { current_state_ = STICKY_KEY_STATE_DISABLED; scroll_delta_ = 0; *released = true; return false; } return false; } int StickyKeysHandler::GetModifierUpEvent(scoped_ptr* new_event) { if (current_state_ != STICKY_KEY_STATE_DISABLED || !modifier_up_event_) return 0; DCHECK(new_event); if (*new_event) return 1; new_event->reset(modifier_up_event_.release()); return 0; } StickyKeysHandler::KeyEventType StickyKeysHandler::TranslateKeyEvent( ui::EventType type, ui::KeyboardCode key_code) { bool is_target_key = false; if (key_code == ui::VKEY_SHIFT || key_code == ui::VKEY_LSHIFT || key_code == ui::VKEY_RSHIFT) { is_target_key = (modifier_flag_ == ui::EF_SHIFT_DOWN); } else if (key_code == ui::VKEY_CONTROL || key_code == ui::VKEY_LCONTROL || key_code == ui::VKEY_RCONTROL) { is_target_key = (modifier_flag_ == ui::EF_CONTROL_DOWN); } else if (key_code == ui::VKEY_MENU || key_code == ui::VKEY_LMENU || key_code == ui::VKEY_RMENU) { is_target_key = (modifier_flag_ == ui::EF_ALT_DOWN); } else if (key_code == ui::VKEY_ALTGR) { is_target_key = (modifier_flag_ == ui::EF_ALTGR_DOWN); } else if (key_code == ui::VKEY_OEM_8) { is_target_key = (modifier_flag_ == ui::EF_MOD3_DOWN); } else if (key_code == ui::VKEY_LWIN) { is_target_key = (modifier_flag_ == ui::EF_COMMAND_DOWN); } else { return type == ui::ET_KEY_PRESSED ? NORMAL_KEY_DOWN : NORMAL_KEY_UP; } if (is_target_key) { return type == ui::ET_KEY_PRESSED ? TARGET_MODIFIER_DOWN : TARGET_MODIFIER_UP; } return type == ui::ET_KEY_PRESSED ? OTHER_MODIFIER_DOWN : OTHER_MODIFIER_UP; } bool StickyKeysHandler::HandleDisabledState(const ui::KeyEvent& event, ui::KeyboardCode key_code) { switch (TranslateKeyEvent(event.type(), key_code)) { case TARGET_MODIFIER_UP: if (preparing_to_enable_) { preparing_to_enable_ = false; scroll_delta_ = 0; current_state_ = STICKY_KEY_STATE_ENABLED; modifier_up_event_.reset(new ui::KeyEvent(event)); return true; } return false; case TARGET_MODIFIER_DOWN: preparing_to_enable_ = true; return false; case NORMAL_KEY_DOWN: preparing_to_enable_ = false; return false; case NORMAL_KEY_UP: case OTHER_MODIFIER_DOWN: case OTHER_MODIFIER_UP: return false; } NOTREACHED(); return false; } bool StickyKeysHandler::HandleEnabledState(const ui::KeyEvent& event, ui::KeyboardCode key_code, int* mod_down_flags, bool* released) { switch (TranslateKeyEvent(event.type(), key_code)) { case NORMAL_KEY_UP: case TARGET_MODIFIER_DOWN: return false; case TARGET_MODIFIER_UP: current_state_ = STICKY_KEY_STATE_LOCKED; modifier_up_event_.reset(); return true; case NORMAL_KEY_DOWN: { current_state_ = STICKY_KEY_STATE_DISABLED; *mod_down_flags |= modifier_flag_; *released = true; return false; } case OTHER_MODIFIER_DOWN: case OTHER_MODIFIER_UP: return false; } NOTREACHED(); return false; } bool StickyKeysHandler::HandleLockedState(const ui::KeyEvent& event, ui::KeyboardCode key_code, int* mod_down_flags, bool* released) { switch (TranslateKeyEvent(event.type(), key_code)) { case TARGET_MODIFIER_DOWN: return true; case TARGET_MODIFIER_UP: current_state_ = STICKY_KEY_STATE_DISABLED; return false; case NORMAL_KEY_DOWN: case NORMAL_KEY_UP: *mod_down_flags |= modifier_flag_; return false; case OTHER_MODIFIER_DOWN: case OTHER_MODIFIER_UP: return false; } NOTREACHED(); return false; } } // namespace ash