| // 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 <X11/extensions/XInput2.h> |
| #include <X11/Xlib.h> |
| #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; |
| |
| virtual void DispatchScrollEvent(ui::ScrollEvent* 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. |
| if (event->IsMouseWheelEvent()) { |
| ui::MouseWheelEvent new_event(*static_cast<ui::MouseWheelEvent*>(event)); |
| target->GetDispatcher()->AsRootWindowHostDelegate() |
| ->OnHostMouseEvent(&new_event); |
| } else { |
| ui::MouseEvent new_event(*event, target, target->GetRootWindow()); |
| target->GetDispatcher()->AsRootWindowHostDelegate() |
| ->OnHostMouseEvent(&new_event); |
| } |
| } |
| |
| void StickyKeysHandlerDelegateImpl::DispatchScrollEvent( |
| ui::ScrollEvent* event, |
| aura::Window* target) { |
| DCHECK(target); |
| target->GetDispatcher()->AsRootWindowHostDelegate() |
| ->OnHostScrollEvent(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); |
| } |
| |
| bool StickyKeys::HandleScrollEvent(ui::ScrollEvent* event) { |
| return shift_sticky_key_->HandleScrollEvent(event) || |
| alt_sticky_key_->HandleScrollEvent(event) || |
| ctrl_sticky_key_->HandleScrollEvent(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(); |
| } |
| |
| void StickyKeys::OnScrollEvent(ui::ScrollEvent* event) { |
| if (enabled_ && HandleScrollEvent(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), |
| scroll_delta_(0), |
| 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) { |
| preparing_to_enable_ = false; |
| 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; |
| } |
| |
| bool StickyKeysHandler::HandleScrollEvent(ui::ScrollEvent* event) { |
| preparing_to_enable_ = false; |
| if (event_from_myself_ || current_state_ == DISABLED) |
| return false; |
| DCHECK(current_state_ == ENABLED || current_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_ == 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) |
| AppendModifier(event); |
| |
| // 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_ == ENABLED && |
| (event->type() == ui::ET_SCROLL_FLING_START || direction_changed)) { |
| current_state_ = DISABLED; |
| scroll_delta_ = 0; |
| 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; |
| scroll_delta_ = 0; |
| current_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(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() || |
| event->IsScrollEvent()); |
| DCHECK(modifier_up_event_.get()); |
| aura::Window* target = static_cast<aura::Window*>(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<ui::KeyEvent*>(event), target); |
| } else if (event->IsMouseEvent()) { |
| delegate_->DispatchMouseEvent(static_cast<ui::MouseEvent*>(event), target); |
| } else { |
| delegate_->DispatchScrollEvent( |
| static_cast<ui::ScrollEvent*>(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(); |
| if (xev) { |
| 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(); |
| if (xev) { |
| 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_); |
| } |
| |
| void StickyKeysHandler::AppendModifier(ui::ScrollEvent* event) { |
| #if defined(USE_X11) |
| XEvent* xev = event->native_event(); |
| if (xev) { |
| XIDeviceEvent* xievent = |
| static_cast<XIDeviceEvent*>(xev->xcookie.data); |
| if (xievent) { |
| AppendNativeEventMask(reinterpret_cast<unsigned int*>( |
| &xievent->mods.effective)); |
| } |
| } |
| #elif defined(USE_OZONE) |
| NOTIMPLEMENTED() << "Modifier key is not handled"; |
| #endif |
| event->set_flags(event->flags() | modifier_flag_); |
| } |
| |
| } // namespace ash |