| // Copyright 2016 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/arc/ime/arc_ime_service.h" |
| |
| #include <utility> |
| |
| #include "base/logging.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "components/arc/ime/arc_ime_bridge_impl.h" |
| #include "components/exo/shell_surface.h" |
| #include "components/exo/surface.h" |
| #include "ui/aura/env.h" |
| #include "ui/aura/window.h" |
| #include "ui/aura/window_tree_host.h" |
| #include "ui/base/ime/input_method.h" |
| #include "ui/events/base_event_utils.h" |
| #include "ui/events/event.h" |
| #include "ui/events/keycodes/keyboard_codes.h" |
| |
| namespace arc { |
| |
| ArcImeService::ArcWindowDetector::~ArcWindowDetector() = default; |
| |
| bool ArcImeService::ArcWindowDetector::IsArcWindow( |
| const aura::Window* window) const { |
| return exo::Surface::AsSurface(window); |
| } |
| |
| bool ArcImeService::ArcWindowDetector::IsArcTopLevelWindow( |
| const aura::Window* window) const { |
| return exo::ShellSurface::GetMainSurface(window); |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // ArcImeService main implementation: |
| |
| ArcImeService::ArcImeService(ArcBridgeService* bridge_service) |
| : ArcService(bridge_service), |
| ime_bridge_(new ArcImeBridgeImpl(this, bridge_service)), |
| arc_window_detector_(new ArcWindowDetector()), |
| ime_type_(ui::TEXT_INPUT_TYPE_NONE), |
| has_composition_text_(false), |
| keyboard_controller_(nullptr), |
| test_input_method_(nullptr), |
| is_focus_observer_installed_(false) { |
| aura::Env* env = aura::Env::GetInstanceDontCreate(); |
| if (env) |
| env->AddObserver(this); |
| } |
| |
| ArcImeService::~ArcImeService() { |
| ui::InputMethod* const input_method = GetInputMethod(); |
| if (input_method) |
| input_method->DetachTextInputClient(this); |
| |
| if (is_focus_observer_installed_ && exo::WMHelper::GetInstance()) |
| exo::WMHelper::GetInstance()->RemoveFocusObserver(this); |
| aura::Env* env = aura::Env::GetInstanceDontCreate(); |
| if (env) |
| env->RemoveObserver(this); |
| // Removing |this| from KeyboardController. |
| keyboard::KeyboardController* keyboard_controller = |
| keyboard::KeyboardController::GetInstance(); |
| if (keyboard_controller && keyboard_controller_ == keyboard_controller) { |
| keyboard_controller_->RemoveObserver(this); |
| } |
| } |
| |
| void ArcImeService::SetImeBridgeForTesting( |
| std::unique_ptr<ArcImeBridge> test_ime_bridge) { |
| ime_bridge_ = std::move(test_ime_bridge); |
| } |
| |
| void ArcImeService::SetInputMethodForTesting( |
| ui::InputMethod* test_input_method) { |
| test_input_method_ = test_input_method; |
| } |
| |
| void ArcImeService::SetArcWindowDetectorForTesting( |
| std::unique_ptr<ArcWindowDetector> detector) { |
| arc_window_detector_ = std::move(detector); |
| } |
| |
| ui::InputMethod* ArcImeService::GetInputMethod() { |
| if (test_input_method_) |
| return test_input_method_; |
| if (focused_arc_window_.windows().empty()) |
| return nullptr; |
| return focused_arc_window_.windows().front()->GetHost()->GetInputMethod(); |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // Overridden from aura::EnvObserver: |
| |
| void ArcImeService::OnWindowInitialized(aura::Window* new_window) { |
| if (arc_window_detector_->IsArcWindow(new_window)) { |
| if (!is_focus_observer_installed_) { |
| exo::WMHelper::GetInstance()->AddFocusObserver(this); |
| is_focus_observer_installed_ = true; |
| } |
| } |
| keyboard::KeyboardController* keyboard_controller = |
| keyboard::KeyboardController::GetInstance(); |
| if (keyboard_controller && keyboard_controller_ != keyboard_controller) { |
| // Registering |this| as an observer only once per KeyboardController. |
| keyboard_controller_ = keyboard_controller; |
| keyboard_controller_->AddObserver(this); |
| } |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // Overridden from exo::WMHelper::FocusChangeObserver: |
| |
| void ArcImeService::OnWindowFocused(aura::Window* gained_focus, |
| aura::Window* lost_focus) { |
| // The Aura focus may or may not be on sub-window of the toplevel ARC++ frame. |
| // To handle all cases, judge the state by always climbing up to the toplevel. |
| gained_focus = gained_focus ? gained_focus->GetToplevelWindow() : nullptr; |
| lost_focus = lost_focus ? lost_focus->GetToplevelWindow() : nullptr; |
| if (lost_focus == gained_focus) |
| return; |
| |
| const bool detach = (lost_focus && focused_arc_window_.Contains(lost_focus)); |
| const bool attach = |
| (gained_focus && arc_window_detector_->IsArcTopLevelWindow(gained_focus)); |
| if (detach) |
| focused_arc_window_.Remove(lost_focus); |
| if (attach) |
| focused_arc_window_.Add(gained_focus); |
| |
| // Notify to the input method, either when this service is detached or |
| // attached. Do nothing when the focus is moving between ARC windows, |
| // to avoid unpexpected context reset in ARC. |
| ui::InputMethod* const input_method = GetInputMethod(); |
| if (input_method) { |
| if (detach && !attach) |
| input_method->DetachTextInputClient(this); |
| if (!detach && attach) |
| input_method->SetFocusedTextInputClient(this); |
| } |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // Overridden from arc::ArcImeBridge::Delegate |
| |
| void ArcImeService::OnTextInputTypeChanged(ui::TextInputType type) { |
| if (ime_type_ == type) |
| return; |
| ime_type_ = type; |
| |
| ui::InputMethod* const input_method = GetInputMethod(); |
| if (input_method) |
| input_method->OnTextInputTypeChanged(this); |
| } |
| |
| void ArcImeService::OnCursorRectChanged(const gfx::Rect& rect) { |
| if (cursor_rect_ == rect) |
| return; |
| cursor_rect_ = rect; |
| |
| ui::InputMethod* const input_method = GetInputMethod(); |
| if (input_method) |
| input_method->OnCaretBoundsChanged(this); |
| } |
| |
| void ArcImeService::OnCancelComposition() { |
| ui::InputMethod* const input_method = GetInputMethod(); |
| if (input_method) |
| input_method->CancelComposition(this); |
| } |
| |
| void ArcImeService::ShowImeIfNeeded() { |
| ui::InputMethod* const input_method = GetInputMethod(); |
| if (input_method && input_method->GetTextInputClient() == this) { |
| input_method->ShowImeIfNeeded(); |
| } |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // Overridden from keyboard::KeyboardControllerObserver |
| void ArcImeService::OnKeyboardBoundsChanging(const gfx::Rect& new_bounds) { |
| if (focused_arc_window_.windows().empty()) |
| return; |
| aura::Window* window = focused_arc_window_.windows().front(); |
| // Multiply by the scale factor. To convert from DPI to physical pixels. |
| gfx::Rect bounds_in_px = gfx::ScaleToEnclosingRect( |
| new_bounds, window->layer()->device_scale_factor()); |
| ime_bridge_->SendOnKeyboardBoundsChanging(bounds_in_px); |
| } |
| |
| void ArcImeService::OnKeyboardClosed() {} |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // Overridden from ui::TextInputClient: |
| |
| void ArcImeService::SetCompositionText( |
| const ui::CompositionText& composition) { |
| has_composition_text_ = !composition.text.empty(); |
| ime_bridge_->SendSetCompositionText(composition); |
| } |
| |
| void ArcImeService::ConfirmCompositionText() { |
| has_composition_text_ = false; |
| ime_bridge_->SendConfirmCompositionText(); |
| } |
| |
| void ArcImeService::ClearCompositionText() { |
| if (has_composition_text_) { |
| has_composition_text_ = false; |
| ime_bridge_->SendInsertText(base::string16()); |
| } |
| } |
| |
| void ArcImeService::InsertText(const base::string16& text) { |
| has_composition_text_ = false; |
| ime_bridge_->SendInsertText(text); |
| } |
| |
| void ArcImeService::InsertChar(const ui::KeyEvent& event) { |
| // According to the document in text_input_client.h, InsertChar() is called |
| // even when the text input type is NONE. We ignore such events, since for |
| // ARC we are only interested in the event as a method of text input. |
| if (ime_type_ == ui::TEXT_INPUT_TYPE_NONE) |
| return; |
| |
| // For apps that doesn't handle hardware keyboard events well, keys that are |
| // 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; |
| if ((event.flags() & kModifierMask) == 0) { |
| if (event.key_code() == ui::VKEY_RETURN) { |
| has_composition_text_ = false; |
| ime_bridge_->SendInsertText(base::ASCIIToUTF16("\n")); |
| return; |
| } |
| if (event.key_code() == ui::VKEY_BACK) { |
| has_composition_text_ = false; |
| ime_bridge_->SendInsertText(base::ASCIIToUTF16("\b")); |
| return; |
| } |
| } |
| |
| // Drop 0x00-0x1f (C0 controls), 0x7f (DEL), and 0x80-0x9f (C1 controls). |
| // See: https://en.wikipedia.org/wiki/Unicode_control_characters |
| // They are control characters and not treated as a text insertion. |
| 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())) { |
| has_composition_text_ = false; |
| ime_bridge_->SendInsertText(base::string16(1, event.GetText())); |
| } |
| } |
| |
| ui::TextInputType ArcImeService::GetTextInputType() const { |
| return ime_type_; |
| } |
| |
| gfx::Rect ArcImeService::GetCaretBounds() const { |
| if (focused_arc_window_.windows().empty()) |
| return gfx::Rect(); |
| aura::Window* window = focused_arc_window_.windows().front(); |
| |
| // |cursor_rect_| holds the rectangle reported from ARC apps, in the "screen |
| // coordinates" in ARC, counted by physical pixels. |
| // Chrome OS input methods expect the coordinates in Chrome OS screen, within |
| // device independent pixels. Two factors are involved for the conversion. |
| |
| // Divide by the scale factor. To convert from physical pixels to DIP. |
| gfx::Rect converted = gfx::ScaleToEnclosingRect( |
| cursor_rect_, 1 / window->layer()->device_scale_factor()); |
| |
| // Add the offset of the window showing the ARC app. |
| converted.Offset(window->GetBoundsInScreen().OffsetFromOrigin()); |
| return converted; |
| } |
| |
| ui::TextInputMode ArcImeService::GetTextInputMode() const { |
| return ui::TEXT_INPUT_MODE_DEFAULT; |
| } |
| |
| base::i18n::TextDirection ArcImeService::GetTextDirection() const { |
| return base::i18n::UNKNOWN_DIRECTION; |
| } |
| |
| void ArcImeService::ExtendSelectionAndDelete(size_t before, size_t after) { |
| ime_bridge_->SendExtendSelectionAndDelete(before, after); |
| } |
| |
| int ArcImeService::GetTextInputFlags() const { |
| return ui::TEXT_INPUT_FLAG_NONE; |
| } |
| |
| bool ArcImeService::CanComposeInline() const { |
| return true; |
| } |
| |
| bool ArcImeService::GetCompositionCharacterBounds( |
| uint32_t index, gfx::Rect* rect) const { |
| return false; |
| } |
| |
| bool ArcImeService::HasCompositionText() const { |
| return has_composition_text_; |
| } |
| |
| bool ArcImeService::GetTextRange(gfx::Range* range) const { |
| return false; |
| } |
| |
| bool ArcImeService::GetCompositionTextRange(gfx::Range* range) const { |
| return false; |
| } |
| |
| bool ArcImeService::GetSelectionRange(gfx::Range* range) const { |
| return false; |
| } |
| |
| bool ArcImeService::SetSelectionRange(const gfx::Range& range) { |
| return false; |
| } |
| |
| bool ArcImeService::DeleteRange(const gfx::Range& range) { |
| return false; |
| } |
| |
| bool ArcImeService::GetTextFromRange( |
| const gfx::Range& range, base::string16* text) const { |
| return false; |
| } |
| |
| bool ArcImeService::ChangeTextDirectionAndLayoutAlignment( |
| base::i18n::TextDirection direction) { |
| return false; |
| } |
| |
| bool ArcImeService::IsTextEditCommandEnabled( |
| ui::TextEditCommand command) const { |
| return false; |
| } |
| |
| } // namespace arc |