// 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/wm/sticky_keys.h" #if defined(USE_X11) #include #undef RootWindow #endif #include "base/basictypes.h" #include "base/debug/stack_trace.h" #include "ui/aura/root_window.h" #include "ui/aura/window.h" #include "ui/aura/window_tracker.h" #include "ui/events/event.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(ui::MouseEvent* event) { ui::EventType type = event->type(); return type == ui::ET_MOUSE_PRESSED || type == ui::ET_MOUSE_RELEASED || type == ui::ET_MOUSEWHEEL; } // An implementation of StickyKeysHandler::StickyKeysHandlerDelegate. class StickyKeysHandlerDelegateImpl : public StickyKeysHandler::StickyKeysHandlerDelegate { public: StickyKeysHandlerDelegateImpl(); virtual ~StickyKeysHandlerDelegateImpl(); // StickyKeysHandlerDelegate overrides. virtual void DispatchKeyEvent(ui::KeyEvent* event, aura::Window* target) OVERRIDE; virtual void DispatchMouseEvent(ui::MouseEvent* event, aura::Window* target) OVERRIDE; private: DISALLOW_COPY_AND_ASSIGN(StickyKeysHandlerDelegateImpl); }; StickyKeysHandlerDelegateImpl::StickyKeysHandlerDelegateImpl() { } StickyKeysHandlerDelegateImpl::~StickyKeysHandlerDelegateImpl() { } void StickyKeysHandlerDelegateImpl::DispatchKeyEvent(ui::KeyEvent* event, aura::Window* target) { DCHECK(target); target->GetDispatcher()->AsRootWindowHostDelegate()->OnHostKeyEvent(event); } void StickyKeysHandlerDelegateImpl::DispatchMouseEvent(ui::MouseEvent* event, aura::Window* target) { DCHECK(target); // We need to send a new, untransformed mouse event to the host. ui::MouseEvent release_event(*event, target, target->GetRootWindow()); target->GetDispatcher()->AsRootWindowHostDelegate() ->OnHostMouseEvent(&release_event); } } // namespace /////////////////////////////////////////////////////////////////////////////// // StickyKeys StickyKeys::StickyKeys() : enabled_(false), shift_sticky_key_( new StickyKeysHandler(ui::EF_SHIFT_DOWN, new StickyKeysHandlerDelegateImpl())), alt_sticky_key_( new StickyKeysHandler(ui::EF_ALT_DOWN, new StickyKeysHandlerDelegateImpl())), ctrl_sticky_key_( new StickyKeysHandler(ui::EF_CONTROL_DOWN, new StickyKeysHandlerDelegateImpl())) { } StickyKeys::~StickyKeys() { } void StickyKeys::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, new StickyKeysHandlerDelegateImpl())); alt_sticky_key_.reset( new StickyKeysHandler(ui::EF_ALT_DOWN, new StickyKeysHandlerDelegateImpl())); ctrl_sticky_key_.reset( new StickyKeysHandler(ui::EF_CONTROL_DOWN, new StickyKeysHandlerDelegateImpl())); } } } bool StickyKeys::HandleKeyEvent(ui::KeyEvent* event) { return shift_sticky_key_->HandleKeyEvent(event) || alt_sticky_key_->HandleKeyEvent(event) || ctrl_sticky_key_->HandleKeyEvent(event); return ctrl_sticky_key_->HandleKeyEvent(event); } bool StickyKeys::HandleMouseEvent(ui::MouseEvent* event) { return shift_sticky_key_->HandleMouseEvent(event) || alt_sticky_key_->HandleMouseEvent(event) || ctrl_sticky_key_->HandleMouseEvent(event); } void StickyKeys::OnKeyEvent(ui::KeyEvent* event) { // Do not consume a translated key event which is generated by an IME. if (event->type() == ui::ET_TRANSLATED_KEY_PRESS || event->type() == ui::ET_TRANSLATED_KEY_RELEASE) { return; } if (enabled_ && HandleKeyEvent(event)) event->StopPropagation(); } void StickyKeys::OnMouseEvent(ui::MouseEvent* event) { if (enabled_ && HandleMouseEvent(event)) event->StopPropagation(); } /////////////////////////////////////////////////////////////////////////////// // StickyKeysHandler StickyKeysHandler::StickyKeysHandler(ui::EventFlags target_modifier_flag, StickyKeysHandlerDelegate* delegate) : modifier_flag_(target_modifier_flag), current_state_(DISABLED), event_from_myself_(false), preparing_to_enable_(false), delegate_(delegate) { } StickyKeysHandler::~StickyKeysHandler() { } StickyKeysHandler::StickyKeysHandlerDelegate::StickyKeysHandlerDelegate() { } StickyKeysHandler::StickyKeysHandlerDelegate::~StickyKeysHandlerDelegate() { } bool StickyKeysHandler::HandleKeyEvent(ui::KeyEvent* event) { if (event_from_myself_) return false; // Do not handle self-generated key event. switch (current_state_) { case DISABLED: return HandleDisabledState(event); case ENABLED: return HandleEnabledState(event); case LOCKED: return HandleLockedState(event); } NOTREACHED(); return false; } bool StickyKeysHandler::HandleMouseEvent(ui::MouseEvent* event) { if (event_from_myself_ || current_state_ == DISABLED || !ShouldModifyMouseEvent(event)) { return false; } DCHECK(current_state_ == ENABLED || current_state_ == LOCKED); AppendModifier(event); // Only disable on the mouse released event in normal, non-locked mode. if (current_state_ == ENABLED && event->type() != ui::ET_MOUSE_PRESSED) { current_state_ = DISABLED; DispatchEventAndReleaseModifier(event); return true; } return false; } StickyKeysHandler::KeyEventType StickyKeysHandler::TranslateKeyEvent(ui::KeyEvent* event) { bool is_target_key = false; if (event->key_code() == ui::VKEY_SHIFT || event->key_code() == ui::VKEY_LSHIFT || event->key_code() == ui::VKEY_RSHIFT) { is_target_key = (modifier_flag_ == ui::EF_SHIFT_DOWN); } else if (event->key_code() == ui::VKEY_CONTROL || event->key_code() == ui::VKEY_LCONTROL || event->key_code() == ui::VKEY_RCONTROL) { is_target_key = (modifier_flag_ == ui::EF_CONTROL_DOWN); } else if (event->key_code() == ui::VKEY_MENU || event->key_code() == ui::VKEY_LMENU || event->key_code() == ui::VKEY_RMENU) { is_target_key = (modifier_flag_ == ui::EF_ALT_DOWN); } else { return event->type() == ui::ET_KEY_PRESSED ? NORMAL_KEY_DOWN : NORMAL_KEY_UP; } if (is_target_key) { return event->type() == ui::ET_KEY_PRESSED ? TARGET_MODIFIER_DOWN : TARGET_MODIFIER_UP; } return event->type() == ui::ET_KEY_PRESSED ? OTHER_MODIFIER_DOWN : OTHER_MODIFIER_UP; } bool StickyKeysHandler::HandleDisabledState(ui::KeyEvent* event) { switch (TranslateKeyEvent(event)) { case TARGET_MODIFIER_UP: if (preparing_to_enable_) { preparing_to_enable_ = false; current_state_ = ENABLED; modifier_up_event_.reset(event->Copy()); 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(ui::KeyEvent* event) { switch (TranslateKeyEvent(event)) { case NORMAL_KEY_UP: case TARGET_MODIFIER_DOWN: return true; case TARGET_MODIFIER_UP: current_state_ = LOCKED; modifier_up_event_.reset(); return true; case NORMAL_KEY_DOWN: { current_state_ = DISABLED; AppendModifier(event); DispatchEventAndReleaseModifier(event); return true; } case OTHER_MODIFIER_DOWN: case OTHER_MODIFIER_UP: return false; } NOTREACHED(); return false; } bool StickyKeysHandler::HandleLockedState(ui::KeyEvent* event) { switch (TranslateKeyEvent(event)) { case TARGET_MODIFIER_DOWN: return true; case TARGET_MODIFIER_UP: current_state_ = DISABLED; return false; case NORMAL_KEY_DOWN: case NORMAL_KEY_UP: AppendModifier(event); return false; case OTHER_MODIFIER_DOWN: case OTHER_MODIFIER_UP: return false; } NOTREACHED(); return false; } void StickyKeysHandler::DispatchEventAndReleaseModifier(ui::Event* event) { DCHECK(event->IsKeyEvent() || event->IsMouseEvent()); DCHECK(modifier_up_event_.get()); aura::Window* target = static_cast(event->target()); DCHECK(target); aura::Window* root_window = target->GetRootWindow(); DCHECK(root_window); aura::WindowTracker window_tracker; window_tracker.Add(target); event_from_myself_ = true; if (event->IsKeyEvent()) delegate_->DispatchKeyEvent(static_cast(event), target); else delegate_->DispatchMouseEvent(static_cast(event), target); // The action triggered above may have destroyed the event target, in which // case we will dispatch the modifier up event to the root window instead. aura::Window* modifier_up_target = window_tracker.Contains(target) ? target : root_window; delegate_->DispatchKeyEvent(modifier_up_event_.get(), modifier_up_target); event_from_myself_ = false; } void StickyKeysHandler::AppendNativeEventMask(unsigned int* state) { unsigned int& state_ref = *state; switch (modifier_flag_) { case ui::EF_CONTROL_DOWN: state_ref |= ControlMask; break; case ui::EF_ALT_DOWN: state_ref |= Mod1Mask; break; case ui::EF_SHIFT_DOWN: state_ref |= ShiftMask; break; default: NOTREACHED(); } } void StickyKeysHandler::AppendModifier(ui::KeyEvent* event) { #if defined(USE_X11) XEvent* xev = event->native_event(); XKeyEvent* xkey = &(xev->xkey); AppendNativeEventMask(&xkey->state); #elif defined(USE_OZONE) NOTIMPLEMENTED() << "Modifier key is not handled"; #endif event->set_flags(event->flags() | modifier_flag_); event->set_character(ui::GetCharacterFromKeyCode(event->key_code(), event->flags())); event->NormalizeFlags(); } void StickyKeysHandler::AppendModifier(ui::MouseEvent* event) { #if defined(USE_X11) XEvent* xev = event->native_event(); XButtonEvent* xkey = &(xev->xbutton); AppendNativeEventMask(&xkey->state); #elif defined(USE_OZONE) NOTIMPLEMENTED() << "Modifier key is not handled"; #endif event->set_flags(event->flags() | modifier_flag_); } } // namespace ash