| // Copyright 2015 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 "components/exo/keyboard.h" |
| |
| #include "components/exo/keyboard_delegate.h" |
| #include "components/exo/keyboard_device_configuration_delegate.h" |
| #include "components/exo/shell_surface.h" |
| #include "components/exo/surface.h" |
| #include "ui/aura/client/focus_client.h" |
| #include "ui/aura/window.h" |
| #include "ui/base/ime/input_method.h" |
| #include "ui/events/base_event_utils.h" |
| #include "ui/events/devices/input_device.h" |
| #include "ui/events/devices/input_device_manager.h" |
| #include "ui/events/event.h" |
| #include "ui/views/widget/widget.h" |
| |
| namespace exo { |
| namespace { |
| |
| bool ConsumedByIme(Surface* focus, const ui::KeyEvent* event) { |
| // Check if IME consumed the event, to avoid it to be doubly processed. |
| // First let us see whether IME is active and is in text input mode. |
| views::Widget* widget = |
| focus ? views::Widget::GetTopLevelWidgetForNativeView(focus->window()) |
| : nullptr; |
| ui::InputMethod* ime = widget ? widget->GetInputMethod() : nullptr; |
| if (!ime || ime->GetTextInputType() == ui::TEXT_INPUT_TYPE_NONE) |
| return false; |
| |
| // Case 1: |
| // When IME ate a key event but did not emit character insertion event yet |
| // (e.g., when it is still showing a candidate list UI to the user,) the |
| // consumed key event is re-sent after masked |key_code| by VKEY_PROCESSKEY. |
| if (event->key_code() == ui::VKEY_PROCESSKEY) |
| return true; |
| |
| // Case 2: |
| // When IME ate a key event and generated a single character input, it leaves |
| // the key event as-is, and in addition calls the active ui::TextInputClient's |
| // InsertChar() method. (In our case, arc::ArcImeService::InsertChar()). |
| // |
| // In Chrome OS (and Web) convention, the two calls wont't cause duplicates, |
| // because key-down events do not mean any character inputs there. |
| // (InsertChar issues a DOM "keypress" event, which is distinct from keydown.) |
| // Unfortunately, this is not necessary the case for our clients that may |
| // treat keydown as a trigger of text inputs. We need suppression for keydown. |
| if (event->type() == ui::ET_KEY_PRESSED) { |
| // Same condition as components/arc/ime/arc_ime_service.cc#InsertChar. |
| const base::char16 ch = event->GetCharacter(); |
| const bool is_control_char = |
| (0x00 <= ch && ch <= 0x1f) || (0x7f <= ch && ch <= 0x9f); |
| if (!is_control_char && !ui::IsSystemKeyModifier(event->flags())) |
| return true; |
| } |
| |
| // Case 3: |
| // Workaround for apps that doesn't handle hardware keyboard events well. |
| // Keys typically on software keyboard and lack of them are fatal, namely, |
| // unmodified enter and backspace keys, are sent through IME. |
| constexpr int kModifierMask = ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN | |
| ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN | |
| ui::EF_ALTGR_DOWN | ui::EF_MOD3_DOWN; |
| // Same condition as components/arc/ime/arc_ime_service.cc#InsertChar. |
| if ((event->flags() & kModifierMask) == 0) { |
| if (event->key_code() == ui::VKEY_RETURN || |
| event->key_code() == ui::VKEY_BACK) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| bool IsPhysicalKeyboardEnabled() { |
| // The internal keyboard is enabled if maximize mode is not enabled. |
| if (!WMHelper::GetInstance()->IsMaximizeModeWindowManagerEnabled()) |
| return true; |
| |
| for (auto& keyboard : |
| ui::InputDeviceManager::GetInstance()->GetKeyboardDevices()) { |
| if (keyboard.type != ui::InputDeviceType::INPUT_DEVICE_INTERNAL) |
| return true; |
| } |
| return false; |
| } |
| |
| } // namespace |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // Keyboard, public: |
| |
| Keyboard::Keyboard(KeyboardDelegate* delegate) : delegate_(delegate) { |
| auto* helper = WMHelper::GetInstance(); |
| helper->AddPostTargetHandler(this); |
| helper->AddFocusObserver(this); |
| helper->AddMaximizeModeObserver(this); |
| helper->AddInputDeviceEventObserver(this); |
| OnWindowFocused(helper->GetFocusedWindow(), nullptr); |
| } |
| |
| Keyboard::~Keyboard() { |
| delegate_->OnKeyboardDestroying(this); |
| if (device_configuration_delegate_) |
| device_configuration_delegate_->OnKeyboardDestroying(this); |
| if (focus_) |
| focus_->RemoveSurfaceObserver(this); |
| auto* helper = WMHelper::GetInstance(); |
| helper->RemoveFocusObserver(this); |
| helper->RemovePostTargetHandler(this); |
| helper->RemoveMaximizeModeObserver(this); |
| helper->RemoveInputDeviceEventObserver(this); |
| } |
| |
| bool Keyboard::HasDeviceConfigurationDelegate() const { |
| return !!device_configuration_delegate_; |
| } |
| |
| void Keyboard::SetDeviceConfigurationDelegate( |
| KeyboardDeviceConfigurationDelegate* delegate) { |
| device_configuration_delegate_ = delegate; |
| OnKeyboardDeviceConfigurationChanged(); |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // ui::EventHandler overrides: |
| |
| void Keyboard::OnKeyEvent(ui::KeyEvent* event) { |
| // These modifiers reflect what Wayland is aware of. For example, |
| // EF_SCROLL_LOCK_ON is missing because Wayland doesn't support scroll lock. |
| const int kModifierMask = ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN | |
| ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN | |
| ui::EF_ALTGR_DOWN | ui::EF_MOD3_DOWN | |
| ui::EF_NUM_LOCK_ON | ui::EF_CAPS_LOCK_ON; |
| int modifier_flags = event->flags() & kModifierMask; |
| if (modifier_flags != modifier_flags_) { |
| modifier_flags_ = modifier_flags; |
| if (focus_) |
| delegate_->OnKeyboardModifiers(modifier_flags_); |
| } |
| |
| // When IME ate a key event, we use the event only for tracking key states and |
| // ignore for further processing. Otherwise it is handled in two places (IME |
| // and client) and causes undesired behavior. |
| bool consumed_by_ime = ConsumedByIme(focus_, event); |
| |
| switch (event->type()) { |
| case ui::ET_KEY_PRESSED: { |
| auto it = |
| std::find(pressed_keys_.begin(), pressed_keys_.end(), event->code()); |
| if (it == pressed_keys_.end()) { |
| if (focus_ && !consumed_by_ime) |
| delegate_->OnKeyboardKey(event->time_stamp(), event->code(), true); |
| |
| pressed_keys_.push_back(event->code()); |
| } |
| } break; |
| case ui::ET_KEY_RELEASED: { |
| auto it = |
| std::find(pressed_keys_.begin(), pressed_keys_.end(), event->code()); |
| if (it != pressed_keys_.end()) { |
| if (focus_ && !consumed_by_ime) |
| delegate_->OnKeyboardKey(event->time_stamp(), event->code(), false); |
| |
| pressed_keys_.erase(it); |
| } |
| } break; |
| default: |
| NOTREACHED(); |
| break; |
| } |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // aura::client::FocusChangeObserver overrides: |
| |
| void Keyboard::OnWindowFocused(aura::Window* gained_focus, |
| aura::Window* lost_focus) { |
| Surface* gained_focus_surface = |
| gained_focus ? GetEffectiveFocus(gained_focus) : nullptr; |
| if (gained_focus_surface != focus_) { |
| if (focus_) { |
| delegate_->OnKeyboardLeave(focus_); |
| focus_->RemoveSurfaceObserver(this); |
| focus_ = nullptr; |
| } |
| if (gained_focus_surface) { |
| delegate_->OnKeyboardModifiers(modifier_flags_); |
| delegate_->OnKeyboardEnter(gained_focus_surface, pressed_keys_); |
| focus_ = gained_focus_surface; |
| focus_->AddSurfaceObserver(this); |
| } |
| } |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // SurfaceObserver overrides: |
| |
| void Keyboard::OnSurfaceDestroying(Surface* surface) { |
| DCHECK(surface == focus_); |
| focus_ = nullptr; |
| surface->RemoveSurfaceObserver(this); |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // ui::InputDeviceEventObserver overrides: |
| |
| void Keyboard::OnKeyboardDeviceConfigurationChanged() { |
| if (device_configuration_delegate_) { |
| device_configuration_delegate_->OnKeyboardTypeChanged( |
| IsPhysicalKeyboardEnabled()); |
| } |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // WMHelper::MaximizeModeObserver overrides: |
| |
| void Keyboard::OnMaximizeModeStarted() { |
| OnKeyboardDeviceConfigurationChanged(); |
| } |
| |
| void Keyboard::OnMaximizeModeEnding() {} |
| |
| void Keyboard::OnMaximizeModeEnded() { |
| OnKeyboardDeviceConfigurationChanged(); |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // Keyboard, private: |
| |
| Surface* Keyboard::GetEffectiveFocus(aura::Window* window) const { |
| // Use window surface as effective focus. |
| Surface* focus = Surface::AsSurface(window); |
| if (!focus) { |
| // Fallback to main surface. |
| aura::Window* top_level_window = window->GetToplevelWindow(); |
| if (top_level_window) |
| focus = ShellSurface::GetMainSurface(top_level_window); |
| } |
| |
| return focus && delegate_->CanAcceptKeyboardEventsForSurface(focus) ? focus |
| : nullptr; |
| } |
| |
| } // namespace exo |