| // 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 "ui/base/ime/linux/input_method_auralinux.h" |
| |
| #include "base/auto_reset.h" |
| #include "base/bind.h" |
| #include "base/environment.h" |
| #include "base/strings/utf_offset_string_conversions.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "build/chromeos_buildflags.h" |
| #include "ui/base/ime/constants.h" |
| #include "ui/base/ime/linux/linux_input_method_context_factory.h" |
| #include "ui/base/ime/text_input_client.h" |
| #include "ui/base/ime/text_input_flags.h" |
| #include "ui/events/event.h" |
| |
| namespace { |
| |
| constexpr base::TimeDelta kIgnoreCommitsDuration = base::Milliseconds(100); |
| |
| bool IsEventFromVK(const ui::KeyEvent& event) { |
| if (event.HasNativeEvent()) |
| return false; |
| |
| const auto* properties = event.properties(); |
| return properties && |
| properties->find(ui::kPropertyFromVK) != properties->end(); |
| } |
| |
| bool IsSameKeyEvent(const ui::KeyEvent& lhs, const ui::KeyEvent& rhs) { |
| // Note that we do not check timestamp here in order to support wayland's |
| // text_input::keysym, which does not have timestamp. |
| // Ignore EF_IS_REPEAT here, because they may be calculated in KeyEvent's |
| // ctor, so we cannot rely on it to detect whether key events come from |
| // the same native event. |
| return lhs.type() == rhs.type() && lhs.code() == rhs.code() && |
| (lhs.flags() & ~ui::EF_IS_REPEAT) == (rhs.flags() & ~ui::EF_IS_REPEAT); |
| } |
| |
| } // namespace |
| |
| namespace ui { |
| |
| InputMethodAuraLinux::InputMethodAuraLinux( |
| ImeKeyEventDispatcher* ime_key_event_dispatcher) |
| : InputMethodBase(ime_key_event_dispatcher), |
| text_input_type_(TEXT_INPUT_TYPE_NONE), |
| is_sync_mode_(false), |
| composition_changed_(false) { |
| context_ = CreateLinuxInputMethodContext(this); |
| } |
| |
| InputMethodAuraLinux::~InputMethodAuraLinux() = default; |
| |
| LinuxInputMethodContext* InputMethodAuraLinux::GetContextForTesting() { |
| return context_.get(); |
| } |
| |
| // Overriden from InputMethod. |
| |
| ui::EventDispatchDetails InputMethodAuraLinux::DispatchKeyEvent( |
| ui::KeyEvent* event) { |
| DCHECK(event->type() == ET_KEY_PRESSED || event->type() == ET_KEY_RELEASED); |
| // If there's pending deadkey event, i.e. a key event which is expected to |
| // trigger input method actions (like OnCommit, OnPreedit* invocation) |
| // and to be dispatched from there, but not yet, dispatch the pending event |
| // first. |
| // Dead keys are considered to be consumed by IME. Actually, it updates |
| // input method's internal state. However, it makes no input method actions, |
| // so the event won't be dispatched without this handling. |
| // Note that this is the earliest timing to find the pending deadkey event |
| // needs to be dispatched. It is because InputMethodAuraLinux cannot find |
| // whether input method actions will be followed or not on holding the event. |
| // |
| // Note that we do not apply this for non-deadkey events intentionally. |
| // It is because some input framework sends key events twice to fill the gap |
| // of synchronous API v.s. asynchronous operations. |
| // Specifically: |
| // - The first key event is passed to input method via |context_| below. |
| // - Inside the function, it triggers asynchronous input method operation. |
| // However, the function needs to return whether the event is filtered |
| // or not synchronously, it returns "filtered" regardless of the event |
| // will be actually filtered or not. |
| // - On completion of the input method action, specifically if the input |
| // method does not consume the event, the framework internally re-generates |
| // the same key event, and post it back again to the application. |
| // This happens some common input method framework, such as iBus/fcitx and |
| // GTK-IMmodule. Also, wayland extension implemented by exosphere in |
| // ash-chrome for Lacros behaves in the same way from InputMethodAuraLinux's |
| // point of view. |
| // To avoid dispatching twice, do not dispatch it here. Following code |
| // will handle the second (i.e. fallback) key event, including event |
| // dispatching. |
| // Importantly, the second key press event may be arrived after the first |
| // key release event, because everything is working in asynchronous ways. |
| if (ime_filtered_key_event_.has_value() && |
| !IsSameKeyEvent(*ime_filtered_key_event_, *event) && |
| ime_filtered_key_event_->GetDomKey().IsDeadKey()) { |
| std::ignore = DispatchKeyEventPostIME(&*ime_filtered_key_event_); |
| } |
| ime_filtered_key_event_.reset(); |
| |
| // If no text input client, dispatch immediately. |
| if (!GetTextInputClient()) { |
| // For Wayland, wl_keyboard::key will be sent following the peek key event |
| // if the event is not consumed by IME, so peek key events should not be |
| // dispatched. crbug.com/1225747 |
| // Do not keep release events. Non-peek Release key event is dispatched, |
| // so the event will be stale. See WaylandKeyboard::OnKey for details. |
| if (event->type() == ET_KEY_PRESSED && context_->IsPeekKeyEvent(*event)) { |
| ime_filtered_key_event_ = std::move(*event); |
| return ui::EventDispatchDetails(); |
| } |
| return DispatchKeyEventPostIME(event); |
| } |
| |
| if (IsEventFromVK(*event)) { |
| // Faked key events that are sent from input.ime.sendKeyEvents. |
| ui::EventDispatchDetails details = DispatchKeyEventPostIME(event); |
| if (details.dispatcher_destroyed || details.target_destroyed || |
| event->stopped_propagation()) { |
| return details; |
| } |
| if ((event->is_char() || event->GetDomKey().IsCharacter()) && |
| event->type() == ui::ET_KEY_PRESSED) { |
| GetTextInputClient()->InsertChar(*event); |
| } |
| return details; |
| } |
| |
| // Forward key event to IME. |
| bool filtered = false; |
| { |
| suppress_non_key_input_until_ = base::TimeTicks::UnixEpoch(); |
| composition_changed_ = false; |
| last_commit_result_.reset(); |
| result_text_ = absl::nullopt; |
| base::AutoReset<bool> flipper(&is_sync_mode_, true); |
| filtered = context_->DispatchKeyEvent(*event); |
| } |
| |
| // There are four cases here. They are a pair of two conditions: |
| // - Whether KeyEvent is consumed by IME, which is represented by filtered. |
| // - Whether IME updates the commit/preedit string synchronously |
| // (i.e. which is already completed here), or asynchronously (i.e. which |
| // will be done afterwords, so not yet done). |
| // |
| // Note that there's a case that KeyEvent is reported as NOT consumed by IME, |
| // but IME still updates the commit/preedit. Please see below comment |
| // for more details. |
| // |
| // Conceptually, after IME's update, there're three things to be done. |
| // - Continue to dispatch the KeyEvent. |
| // - Update TextInputClient by using committed text. |
| // - Update TextInputClient by using preedit text. |
| // The following code does those three, except in the case that KeyEvent is |
| // consumed by IME and commit/preedit string update will happen |
| // asynchronously. The remaining case is covered in OnCommit and |
| // OnPreeditChanged/End. |
| // TODO(crbug.com/1199385): On Lacros CTRL+TAB events are sent twice if |
| // user types it on loading page, because the connected client is considered |
| // None type, and so the peek key event is not held here. |
| // To derisk the regression in other platform, and to prioritize the fix |
| // on Lacros, we conditionally do not check whether the connected client |
| // is None type for Lacros only. We should remove this soon. |
| if (filtered && !HasInputMethodResult() |
| #if !BUILDFLAG(IS_CHROMEOS_LACROS) |
| && !IsTextInputTypeNone() |
| #endif |
| ) { |
| ime_filtered_key_event_ = std::move(*event); |
| return ui::EventDispatchDetails(); |
| } |
| |
| // First, if KeyEvent is consumed by IME, continue to dispatch it, |
| // before updating commit/preedit string so that, e.g., JavaScript keydown |
| // event is delivered to the page before keypress. |
| ui::EventDispatchDetails details; |
| if (event->type() == ui::ET_KEY_PRESSED && filtered) { |
| details = DispatchImeFilteredKeyPressEvent(event); |
| if (details.target_destroyed || details.dispatcher_destroyed || |
| event->stopped_propagation()) { |
| return details; |
| } |
| } |
| |
| // Processes the result text before composition for sync mode. |
| const auto commit_result = MaybeCommitResult(filtered, *event); |
| if (commit_result == CommitResult::kTargetDestroyed) { |
| details.target_destroyed = true; |
| event->StopPropagation(); |
| return details; |
| } |
| // Stop the propagation if there's some committed characters. |
| // Note that this have to be done after the key event dispatching, |
| // specifically if key event is not reported as filtered. |
| bool should_stop_propagation = commit_result == CommitResult::kSuccess; |
| |
| // Then update the composition, if necessary. |
| // Should stop propagation of the event when composition is updated, |
| // because the event is considered to be used for the composition. |
| should_stop_propagation |= |
| MaybeUpdateComposition(commit_result == CommitResult::kSuccess); |
| |
| // If the IME has not handled the key event, passes the keyevent back to the |
| // previous processing flow. |
| if (!filtered) { |
| details = DispatchKeyEventPostIME(event); |
| if (details.dispatcher_destroyed) { |
| if (should_stop_propagation) |
| event->StopPropagation(); |
| return details; |
| } |
| if (event->stopped_propagation() || details.target_destroyed) { |
| ResetContext(); |
| } else if (event->type() == ui::ET_KEY_PRESSED) { |
| // If a key event was not filtered by |context_|, |
| // then it means the key event didn't generate any result text. For some |
| // cases, the key event may still generate a valid character, eg. a |
| // control-key event (ctrl-a, return, tab, etc.). We need to send the |
| // character to the focused text input client by calling |
| // TextInputClient::InsertChar(). |
| // Note: don't use |client| and use GetTextInputClient() here because |
| // DispatchKeyEventPostIME may cause the current text input client change. |
| char16_t ch = event->GetCharacter(); |
| if (ch && GetTextInputClient()) |
| GetTextInputClient()->InsertChar(*event); |
| should_stop_propagation = true; |
| } |
| } |
| |
| if (should_stop_propagation) |
| event->StopPropagation(); |
| |
| return details; |
| } |
| |
| ui::EventDispatchDetails InputMethodAuraLinux::DispatchImeFilteredKeyPressEvent( |
| ui::KeyEvent* event) { |
| // In general, 229 (VKEY_PROCESSKEY) should be used. However, in some IME |
| // framework, such as iBus/fcitx + GTK, the behavior is not simple as follows, |
| // in order to deal with synchronous API on asynchronous IME backend: |
| // - First, IM module reports the KeyEvent is filtered synchronously. |
| // - Then, it forwards the event to the IME engine asynchronously. |
| // - When IM module receives the result, and it turns out the event is not |
| // consumed, then IM module generates the same key event (with a special |
| // flag), and sent it to the application (Chrome in our case). |
| // - Then, the application forwards the event to IM module again, and in this |
| // time IM module synchronously commit the character. |
| // (Note: new iBus GTK IMModule changed the behavior, so the second event |
| // dispatch to the application won't happen). |
| // InputMethodAuraLinux detects this case by the following condition: |
| // - If result text is only one character, and |
| // - there's no composing text, and no updated. |
| // If the condition meets, that means IME did not consume the key event |
| // conceptually, so continue to dispatch KeyEvent without overwriting by 229. |
| ui::EventDispatchDetails details = NeedInsertChar(result_text_) |
| ? DispatchKeyEventPostIME(event) |
| : SendFakeProcessKeyEvent(event); |
| if (details.dispatcher_destroyed) |
| return details; |
| // If the KEYDOWN is stopped propagation (e.g. triggered an accelerator), |
| // don't InsertChar/InsertText to the input field. |
| if (event->stopped_propagation() || details.target_destroyed) |
| ResetContext(); |
| |
| return details; |
| } |
| |
| InputMethodAuraLinux::CommitResult InputMethodAuraLinux::MaybeCommitResult( |
| bool filtered, |
| const KeyEvent& event) { |
| // Note: |client| could be NULL because DispatchKeyEventPostIME could have |
| // changed the text input client. |
| TextInputClient* client = GetTextInputClient(); |
| if (!client || !result_text_) |
| return CommitResult::kNoCommitString; |
| |
| // Take the ownership of |result_text_|. |
| std::u16string result_text = std::move(*result_text_); |
| result_text_ = absl::nullopt; |
| |
| if (filtered && NeedInsertChar(result_text)) { |
| for (const auto ch : result_text) { |
| ui::KeyEvent ch_event(event); |
| ch_event.set_character(ch); |
| client->InsertChar(ch_event); |
| // If the client changes we assume that the original target has been |
| // destroyed. |
| if (client != GetTextInputClient()) |
| return CommitResult::kTargetDestroyed; |
| } |
| } else { |
| // If |filtered| is false, that means the IME wants to commit some text |
| // but still release the key to the application. For example, Korean IME |
| // handles ENTER key to confirm its composition but still release it for |
| // the default behavior (e.g. trigger search, etc.) |
| // In such case, don't do InsertChar because a key should only trigger the |
| // keydown event once. |
| client->InsertText( |
| result_text, |
| ui::TextInputClient::InsertTextCursorBehavior::kMoveCursorAfterText); |
| // If the client changes we assume that the original target has been |
| // destroyed. |
| if (client != GetTextInputClient()) |
| return CommitResult::kTargetDestroyed; |
| } |
| |
| return CommitResult::kSuccess; |
| } |
| |
| bool InputMethodAuraLinux::MaybeUpdateComposition(bool text_committed) { |
| TextInputClient* client = GetTextInputClient(); |
| bool update_composition = |
| client && composition_changed_ && !IsTextInputTypeNone(); |
| if (update_composition) { |
| // If composition changed, does SetComposition if composition is not empty. |
| // And ClearComposition if composition is empty. |
| if (!composition_.text.empty()) |
| client->SetCompositionText(composition_); |
| else if (!text_committed) |
| client->ClearCompositionText(); |
| } |
| |
| // Makes sure the cached composition is cleared after committing any text or |
| // cleared composition. |
| if (client && !client->HasCompositionText()) |
| composition_ = CompositionText(); |
| |
| return update_composition; |
| } |
| |
| void InputMethodAuraLinux::UpdateContextFocusState() { |
| auto old_text_input_type = text_input_type_; |
| text_input_type_ = GetTextInputType(); |
| |
| auto* client = GetTextInputClient(); |
| bool has_client = client != nullptr; |
| context_->UpdateFocus(has_client, old_text_input_type, text_input_type_); |
| |
| TextInputMode mode = TEXT_INPUT_MODE_DEFAULT; |
| int flags = TEXT_INPUT_FLAG_NONE; |
| bool should_do_learning = false; |
| if (client) { |
| mode = client->GetTextInputMode(); |
| flags = client->GetTextInputFlags(); |
| should_do_learning = client->ShouldDoLearning(); |
| } |
| context_->SetContentType(text_input_type_, mode, flags, should_do_learning); |
| } |
| |
| void InputMethodAuraLinux::OnTextInputTypeChanged(TextInputClient* client) { |
| UpdateContextFocusState(); |
| InputMethodBase::OnTextInputTypeChanged(client); |
| // TODO(yoichio): Support inputmode HTML attribute. |
| } |
| |
| void InputMethodAuraLinux::OnCaretBoundsChanged(const TextInputClient* client) { |
| if (!IsTextInputClientFocused(client)) |
| return; |
| NotifyTextInputCaretBoundsChanged(client); |
| context_->SetCursorLocation(GetTextInputClient()->GetCaretBounds()); |
| |
| gfx::Range text_range, selection_range; |
| std::u16string text; |
| if (client->GetTextRange(&text_range) && |
| client->GetTextFromRange(text_range, &text) && |
| client->GetEditableSelectionRange(&selection_range)) { |
| #if BUILDFLAG(IS_CHROMEOS_LACROS) |
| // SetGrammarFragmentAtCursor must happen before SetSurroundingText to make |
| // sure it is properly updated before IME needs it. |
| auto grammar_fragment_opt = client->GetGrammarFragmentAtCursor(); |
| if (grammar_fragment_opt) { |
| auto fragment = grammar_fragment_opt.value(); |
| // Convert utf16 offsets to utf8. |
| std::vector<size_t> offsets = {fragment.range.start(), |
| fragment.range.end()}; |
| base::UTF16ToUTF8AndAdjustOffsets(text, &offsets); |
| context_->SetGrammarFragmentAtCursor( |
| ui::GrammarFragment(gfx::Range(static_cast<uint32_t>(offsets[0]), |
| static_cast<uint32_t>(offsets[1])), |
| fragment.suggestion)); |
| } else { |
| context_->SetGrammarFragmentAtCursor( |
| ui::GrammarFragment(gfx::Range(), "")); |
| } |
| |
| // Send the updated autocorrect information before surrounding text, |
| // as surrounding text changes may trigger the IME to ask for the |
| // autocorrect information. |
| context_->SetAutocorrectInfo(client->GetAutocorrectRange(), |
| client->GetAutocorrectCharacterBounds()); |
| #endif |
| |
| context_->SetSurroundingText(text, selection_range); |
| } |
| } |
| |
| void InputMethodAuraLinux::CancelComposition(const TextInputClient* client) { |
| if (!IsTextInputClientFocused(client)) |
| return; |
| |
| ResetContext(); |
| } |
| |
| void InputMethodAuraLinux::ResetContext() { |
| if (!GetTextInputClient()) |
| return; |
| |
| is_sync_mode_ = true; |
| |
| if (!composition_.text.empty()) { |
| // If the IME has an open composition, ignore non-synchronous attempts to |
| // commit text for a brief duration of time. |
| suppress_non_key_input_until_ = |
| base::TimeTicks::Now() + kIgnoreCommitsDuration; |
| } |
| |
| context_->Reset(); |
| |
| composition_ = CompositionText(); |
| result_text_ = absl::nullopt; |
| is_sync_mode_ = false; |
| composition_changed_ = false; |
| } |
| |
| bool InputMethodAuraLinux::IgnoringNonKeyInput() const { |
| return !is_sync_mode_ && |
| base::TimeTicks::Now() < suppress_non_key_input_until_; |
| } |
| |
| bool InputMethodAuraLinux::IsCandidatePopupOpen() const { |
| // There seems no way to detect candidate windows or any popups. |
| return false; |
| } |
| |
| VirtualKeyboardController* |
| InputMethodAuraLinux::GetVirtualKeyboardController() { |
| // This should only be not null when set via testing. |
| if (auto* controller = InputMethodBase::GetVirtualKeyboardController()) |
| return controller; |
| return context_->GetVirtualKeyboardController(); |
| } |
| |
| // Overriden from ui::LinuxInputMethodContextDelegate |
| |
| void InputMethodAuraLinux::OnCommit(const std::u16string& text) { |
| if (IgnoringNonKeyInput() || !GetTextInputClient()) |
| return; |
| |
| // Discard the result iff in async-mode and the TextInputType is None |
| // for backward compatibility. |
| if (is_sync_mode_ || !IsTextInputTypeNone()) { |
| if (result_text_) { |
| result_text_->append(text); |
| } else { |
| result_text_ = text; |
| } |
| } |
| |
| // Sync mode means this is called on a stack of DispatchKeyEvent(), so its |
| // following code should handle the key dispatch and actual committing. |
| // If we are not handling key event, do not bother sending text result if |
| // the focused text input client does not support text input. |
| if (!is_sync_mode_ && !IsTextInputTypeNone()) { |
| ui::KeyEvent event = |
| ui::KeyEvent(ui::ET_KEY_PRESSED, ui::VKEY_PROCESSKEY, 0); |
| if (ime_filtered_key_event_.has_value()) { |
| event = std::move(*ime_filtered_key_event_); |
| ime_filtered_key_event_.reset(); |
| ui::EventDispatchDetails details = |
| DispatchImeFilteredKeyPressEvent(&event); |
| if (details.target_destroyed || details.dispatcher_destroyed || |
| event.stopped_propagation()) { |
| return; |
| } |
| } |
| last_commit_result_ = MaybeCommitResult(/*filtered=*/true, event); |
| composition_ = CompositionText(); |
| } |
| } |
| |
| void InputMethodAuraLinux::OnConfirmCompositionText(bool keep_selection) { |
| ConfirmCompositionText(keep_selection); |
| } |
| |
| void InputMethodAuraLinux::OnDeleteSurroundingText(size_t before, |
| size_t after) { |
| auto* client = GetTextInputClient(); |
| if (client && composition_.text.empty()) |
| client->ExtendSelectionAndDelete(before, after); |
| } |
| |
| void InputMethodAuraLinux::OnPreeditChanged( |
| const CompositionText& composition_text) { |
| OnPreeditUpdate(composition_text, !is_sync_mode_); |
| } |
| |
| void InputMethodAuraLinux::OnPreeditEnd() { |
| TextInputClient* client = GetTextInputClient(); |
| OnPreeditUpdate(CompositionText(), |
| !is_sync_mode_ && client && client->HasCompositionText()); |
| } |
| |
| void InputMethodAuraLinux::OnSetPreeditRegion( |
| const gfx::Range& range, |
| const std::vector<ImeTextSpan>& spans) { |
| auto* text_input_client = GetTextInputClient(); |
| if (!text_input_client) |
| return; |
| text_input_client->SetCompositionFromExistingText(range, spans); |
| |
| std::u16string text; |
| if (text_input_client->GetTextFromRange(range, &text)) { |
| composition_changed_ |= composition_.text != text; |
| composition_.text = text; |
| } |
| last_commit_result_.reset(); |
| } |
| |
| void InputMethodAuraLinux::OnClearGrammarFragments(const gfx::Range& range) { |
| #if BUILDFLAG(IS_CHROMEOS_LACROS) |
| auto* text_input_client = GetTextInputClient(); |
| if (!text_input_client) |
| return; |
| |
| text_input_client->ClearGrammarFragments(range); |
| #endif |
| } |
| |
| void InputMethodAuraLinux::OnAddGrammarFragment( |
| const ui::GrammarFragment& fragment) { |
| #if BUILDFLAG(IS_CHROMEOS_LACROS) |
| auto* text_input_client = GetTextInputClient(); |
| if (!text_input_client) |
| return; |
| |
| text_input_client->AddGrammarFragments({fragment}); |
| #endif |
| } |
| |
| void InputMethodAuraLinux::OnSetAutocorrectRange(const gfx::Range& range) { |
| #if BUILDFLAG(IS_CHROMEOS_LACROS) |
| auto* text_input_client = GetTextInputClient(); |
| if (!text_input_client) |
| return; |
| text_input_client->SetAutocorrectRange(range); |
| #endif |
| } |
| |
| void InputMethodAuraLinux::OnSetVirtualKeyboardOccludedBounds( |
| const gfx::Rect& screen_bounds) { |
| SetVirtualKeyboardBounds(screen_bounds); |
| } |
| |
| // Overridden from InputMethodBase. |
| |
| void InputMethodAuraLinux::OnWillChangeFocusedClient( |
| TextInputClient* focused_before, |
| TextInputClient* focused) { |
| ResetContext(); |
| } |
| |
| void InputMethodAuraLinux::OnDidChangeFocusedClient( |
| TextInputClient* focused_before, |
| TextInputClient* focused) { |
| UpdateContextFocusState(); |
| |
| // Force to update caret bounds, in case the View thinks that the caret |
| // bounds has not changed. |
| if (text_input_type_ != TEXT_INPUT_TYPE_NONE) |
| OnCaretBoundsChanged(GetTextInputClient()); |
| |
| InputMethodBase::OnDidChangeFocusedClient(focused_before, focused); |
| } |
| |
| // private |
| |
| void InputMethodAuraLinux::OnPreeditUpdate( |
| const ui::CompositionText& composition_text, |
| bool force_update_client) { |
| if (IgnoringNonKeyInput() || IsTextInputTypeNone()) |
| return; |
| |
| composition_changed_ |= composition_ != composition_text; |
| composition_ = composition_text; |
| |
| if (!force_update_client) |
| return; |
| |
| if (ime_filtered_key_event_.has_value()) { |
| ui::KeyEvent event = std::move(*ime_filtered_key_event_); |
| ime_filtered_key_event_.reset(); |
| ui::EventDispatchDetails details = DispatchImeFilteredKeyPressEvent(&event); |
| if (details.target_destroyed || details.dispatcher_destroyed || |
| event.stopped_propagation()) { |
| return; |
| } |
| } |
| MaybeUpdateComposition(last_commit_result_ == CommitResult::kSuccess); |
| last_commit_result_.reset(); |
| } |
| |
| bool InputMethodAuraLinux::HasInputMethodResult() { |
| return result_text_ || composition_changed_; |
| } |
| |
| bool InputMethodAuraLinux::NeedInsertChar( |
| const absl::optional<std::u16string>& result_text) const { |
| return IsTextInputTypeNone() || |
| (!composition_changed_ && composition_.text.empty() && result_text && |
| result_text->length() == 1); |
| } |
| |
| ui::EventDispatchDetails InputMethodAuraLinux::SendFakeProcessKeyEvent( |
| ui::KeyEvent* event) const { |
| KeyEvent key_event(ui::ET_KEY_PRESSED, ui::VKEY_PROCESSKEY, event->flags()); |
| ui::EventDispatchDetails details = DispatchKeyEventPostIME(&key_event); |
| if (key_event.stopped_propagation()) |
| event->StopPropagation(); |
| return details; |
| } |
| |
| void InputMethodAuraLinux::ConfirmCompositionText(bool keep_selection) { |
| auto* client = GetTextInputClient(); |
| if (client) |
| client->ConfirmCompositionText(keep_selection); |
| composition_ = CompositionText(); |
| composition_changed_ = false; |
| result_text_.reset(); |
| } |
| |
| } // namespace ui |