blob: 3337e19d8adda56992dffd0288361a7f7557ba7a [file] [log] [blame]
Nick Diego Yamane5c9d092b2019-09-18 00:55:181// Copyright 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
Tom Anderson61fbaaf2020-04-02 23:16:235#include "ui/gtk/input_method_context_impl_gtk.h"
Nick Diego Yamane5c9d092b2019-09-18 00:55:186
Tom Andersonbf89dc62021-04-20 20:19:597#include <cstddef>
Nick Diego Yamane5c9d092b2019-09-18 00:55:188
9#include "base/strings/utf_string_conversions.h"
Nick Diego Yamane5c9d092b2019-09-18 00:55:1810#include "ui/aura/window_tree_host.h"
11#include "ui/base/ime/composition_text.h"
12#include "ui/base/ime/linux/composition_text_util_pango.h"
13#include "ui/base/ime/text_input_client.h"
14#include "ui/events/event.h"
Tom Anderson371899e92021-03-15 20:16:2215#include "ui/events/event_utils.h"
Nick Diego Yamane5c9d092b2019-09-18 00:55:1816#include "ui/gfx/geometry/dip_util.h"
danakj340909ed2020-09-30 17:40:5617#include "ui/gfx/geometry/rect_conversions.h"
Nick Diego Yamane4079d372020-03-28 00:43:0118#include "ui/gfx/native_widget_types.h"
Tom Anderson1a7997902021-04-06 20:24:5619#include "ui/gtk/gtk_compat.h"
Tom Anderson61fbaaf2020-04-02 23:16:2320#include "ui/gtk/gtk_ui.h"
Tom Andersonbf89dc62021-04-20 20:19:5921#include "ui/gtk/gtk_ui_platform.h"
Tom Anderson61fbaaf2020-04-02 23:16:2322#include "ui/gtk/gtk_util.h"
Nick Diego Yamane5c9d092b2019-09-18 00:55:1823#include "ui/views/linux_ui/linux_ui.h"
24
Tom Anderson5630ac272020-01-23 02:39:2025namespace gtk {
Nick Diego Yamane5c9d092b2019-09-18 00:55:1826
27namespace {
28
Tom Anderson1a7997902021-04-06 20:24:5629GdkEventKey* GdkEventToKey(GdkEvent* event) {
30 DCHECK(!GtkCheckVersion(4));
31 auto* key = reinterpret_cast<GdkEventKey*>(event);
Tom Anderson699a51f2021-04-21 00:12:4932 DCHECK(key->type == GdkKeyPress() || key->type == GdkKeyRelease());
Tom Anderson1a7997902021-04-06 20:24:5633 return key;
34}
35
Nick Diego Yamane5c9d092b2019-09-18 00:55:1836// Get IME KeyEvent's target window. Assumes root aura::Window is set to
37// Event::target(), otherwise returns null.
38GdkWindow* GetTargetWindow(const ui::KeyEvent& key_event) {
39 if (!key_event.target())
40 return nullptr;
41
Nick Diego Yamane5c9d092b2019-09-18 00:55:1842 aura::Window* window = static_cast<aura::Window*>(key_event.target());
Nick Diego Yamane4079d372020-03-28 00:43:0143 DCHECK(window) << "KeyEvent target window not set.";
Nick Diego Yamane5c9d092b2019-09-18 00:55:1844
Nick Diego Yamane4079d372020-03-28 00:43:0145 auto window_id = window->GetHost()->GetAcceleratedWidget();
Tom Andersonbf89dc62021-04-20 20:19:5946 return GtkUi::GetPlatform()->GetGdkWindow(window_id);
Nick Diego Yamane5c9d092b2019-09-18 00:55:1847}
48
49// Translate IME ui::KeyEvent to a GdkEventKey.
50GdkEvent* GdkEventFromImeKeyEvent(const ui::KeyEvent& key_event) {
Tom Anderson1a7997902021-04-06 20:24:5651 DCHECK(!GtkCheckVersion(4));
Nick Diego Yamane5c9d092b2019-09-18 00:55:1852 GdkEvent* event = GdkEventFromKeyEvent(key_event);
53 if (!event)
54 return nullptr;
55
56 GdkWindow* target_window = GetTargetWindow(key_event);
57 if (!target_window) {
58 gdk_event_free(event);
59 return nullptr;
60 }
Tom Anderson1a7997902021-04-06 20:24:5661 GdkEventToKey(event)->window = target_window;
Nick Diego Yamane5c9d092b2019-09-18 00:55:1862 return event;
63}
64
65} // namespace
66
67InputMethodContextImplGtk::InputMethodContextImplGtk(
68 ui::LinuxInputMethodContextDelegate* delegate,
69 bool is_simple)
Tom Anderson371899e92021-03-15 20:16:2270 : delegate_(delegate), is_simple_(is_simple) {
Nick Diego Yamane5c9d092b2019-09-18 00:55:1871 CHECK(delegate_);
72
73 gtk_context_ =
74 is_simple ? gtk_im_context_simple_new() : gtk_im_multicontext_new();
75
76 g_signal_connect(gtk_context_, "commit", G_CALLBACK(OnCommitThunk), this);
77 g_signal_connect(gtk_context_, "preedit-changed",
78 G_CALLBACK(OnPreeditChangedThunk), this);
79 g_signal_connect(gtk_context_, "preedit-end", G_CALLBACK(OnPreeditEndThunk),
80 this);
81 g_signal_connect(gtk_context_, "preedit-start",
82 G_CALLBACK(OnPreeditStartThunk), this);
83 // TODO(shuchen): Handle operations on surrounding text.
84 // "delete-surrounding" and "retrieve-surrounding" signals should be
85 // handled.
Tom Anderson371899e92021-03-15 20:16:2286
Tom Anderson1a7997902021-04-06 20:24:5687 if (GtkCheckVersion(4))
88 gtk_im_context_set_client_widget(gtk_context_, GetDummyWindow());
Nick Diego Yamane5c9d092b2019-09-18 00:55:1889}
90
91InputMethodContextImplGtk::~InputMethodContextImplGtk() {
92 if (gtk_context_) {
93 g_object_unref(gtk_context_);
94 gtk_context_ = nullptr;
95 }
96}
97
98// Overridden from ui::LinuxInputMethodContext
99bool InputMethodContextImplGtk::DispatchKeyEvent(
100 const ui::KeyEvent& key_event) {
101 if (!gtk_context_)
102 return false;
103
Tom Anderson1a7997902021-04-06 20:24:56104 GdkEvent* event = nullptr;
105 if (!GtkCheckVersion(4)) {
106 event = GdkEventFromImeKeyEvent(key_event);
107 if (!event) {
108 LOG(ERROR) << "Cannot translate a Keyevent to a GdkEvent.";
109 return false;
110 }
Nick Diego Yamane5c9d092b2019-09-18 00:55:18111
Tom Anderson1a7997902021-04-06 20:24:56112 GdkWindow* target_window = GdkEventToKey(event)->window;
113 if (!target_window) {
114 LOG(ERROR) << "Cannot get target GdkWindow for KeyEvent.";
115 return false;
116 }
Nick Diego Yamane5c9d092b2019-09-18 00:55:18117
Tom Anderson1a7997902021-04-06 20:24:56118 SetContextClientWindow(target_window);
119 }
Nick Diego Yamane5c9d092b2019-09-18 00:55:18120
121 // Convert the last known caret bounds relative to the screen coordinates
122 // to a GdkRectangle relative to the client window.
Tom Anderson6a8b2d132021-03-26 02:04:50123 aura::Window* window = static_cast<aura::Window*>(key_event.target());
124 gint win_x = window->GetBoundsInScreen().x();
125 gint win_y = window->GetBoundsInScreen().y();
126 gint caret_x = last_caret_bounds_.x();
127 gint caret_y = last_caret_bounds_.y();
128 gint caret_w = last_caret_bounds_.width();
129 gint caret_h = last_caret_bounds_.height();
Nick Diego Yamane5c9d092b2019-09-18 00:55:18130 GdkRectangle gdk_rect = {caret_x - win_x, caret_y - win_y, caret_w, caret_h};
131 gtk_im_context_set_cursor_location(gtk_context_, &gdk_rect);
132
Tom Anderson1a7997902021-04-06 20:24:56133 if (!GtkCheckVersion(4)) {
134 const bool handled =
135 GtkImContextFilterKeypress(gtk_context_, GdkEventToKey(event));
136 gdk_event_free(event);
137 return handled;
138 }
Tom Anderson371899e92021-03-15 20:16:22139 // In GTK4, clients can no longer create or modify events. This makes using
140 // the gtk_im_context_filter_keypress() API impossible. Fortunately, an
141 // alternative API called gtk_im_context_filter_key() was added for clients
142 // that would have needed to construct their own event. The parameters to
143 // the new API are just a deconstructed version of a KeyEvent.
144 bool press = key_event.type() == ui::ET_KEY_PRESSED;
145 auto* surface =
146 gtk_native_get_surface(gtk_widget_get_native(GetDummyWindow()));
147 auto* device = gdk_seat_get_keyboard(
148 gdk_display_get_default_seat(gdk_display_get_default()));
149 auto time = (key_event.time_stamp() - base::TimeTicks()).InMilliseconds();
150 auto keycode = GetKeyEventProperty(key_event, ui::kPropertyKeyboardHwKeyCode);
151 auto state = GetGdkKeyEventState(key_event);
152 auto group = GetKeyEventProperty(key_event, ui::kPropertyKeyboardGroup);
153 return gtk_im_context_filter_key(gtk_context_, press, surface, device, time,
154 keycode, state, group);
Nick Diego Yamane5c9d092b2019-09-18 00:55:18155}
156
157void InputMethodContextImplGtk::Reset() {
158 gtk_im_context_reset(gtk_context_);
159
160 // Some input methods may not honour the reset call.
161 // Focusing out/in the to make sure it gets reset correctly.
162 if (!is_simple_ && has_focus_) {
163 Blur();
164 Focus();
165 }
166}
167
168void InputMethodContextImplGtk::Focus() {
169 gtk_im_context_focus_in(gtk_context_);
170 has_focus_ = true;
171}
172
173void InputMethodContextImplGtk::Blur() {
174 gtk_im_context_focus_out(gtk_context_);
175 has_focus_ = false;
176}
177
178void InputMethodContextImplGtk::SetCursorLocation(const gfx::Rect& rect) {
179 // Remember the caret bounds so that we can set the cursor location later.
180 // gtk_im_context_set_cursor_location() takes the location relative to the
181 // client window, which is unknown at this point. So we'll call
Tom Anderson6a8b2d132021-03-26 02:04:50182 // gtk_im_context_set_cursor_location() later in DispatchKeyEvent() where
Nick Diego Yamane5c9d092b2019-09-18 00:55:18183 // (and only where) we know the client window.
Tom Anderson6a8b2d132021-03-26 02:04:50184 last_caret_bounds_ = rect;
Nick Diego Yamane5c9d092b2019-09-18 00:55:18185}
186
187void InputMethodContextImplGtk::SetSurroundingText(
Jan Wilken Dörrie52639572021-03-11 16:49:54188 const std::u16string& text,
Nick Diego Yamane5c9d092b2019-09-18 00:55:18189 const gfx::Range& selection_range) {}
190
191// private:
192
193// GtkIMContext event handlers.
194
195void InputMethodContextImplGtk::OnCommit(GtkIMContext* context, gchar* text) {
196 if (context != gtk_context_)
197 return;
198
199 delegate_->OnCommit(base::UTF8ToUTF16(text));
200}
201
202void InputMethodContextImplGtk::OnPreeditChanged(GtkIMContext* context) {
203 if (context != gtk_context_)
204 return;
205
206 gchar* str = nullptr;
207 PangoAttrList* attrs = nullptr;
208 gint cursor_pos = 0;
209 gtk_im_context_get_preedit_string(context, &str, &attrs, &cursor_pos);
210 ui::CompositionText composition_text;
211 ui::ExtractCompositionTextFromGtkPreedit(str, attrs, cursor_pos,
212 &composition_text);
213 g_free(str);
214 pango_attr_list_unref(attrs);
215
216 delegate_->OnPreeditChanged(composition_text);
217}
218
219void InputMethodContextImplGtk::OnPreeditEnd(GtkIMContext* context) {
220 if (context != gtk_context_)
221 return;
222
223 delegate_->OnPreeditEnd();
224}
225
226void InputMethodContextImplGtk::OnPreeditStart(GtkIMContext* context) {
227 if (context != gtk_context_)
228 return;
229
230 delegate_->OnPreeditStart();
231}
232
233void InputMethodContextImplGtk::SetContextClientWindow(GdkWindow* window) {
Tom Anderson1a7997902021-04-06 20:24:56234 DCHECK(!GtkCheckVersion(4));
Nick Diego Yamane5c9d092b2019-09-18 00:55:18235 if (window == gdk_last_set_client_window_)
236 return;
Nick Diego Yamane5c9d092b2019-09-18 00:55:18237 gtk_im_context_set_client_window(gtk_context_, window);
Nick Diego Yamane5c9d092b2019-09-18 00:55:18238
239 // Prevent leaks when overriding last client window
240 if (gdk_last_set_client_window_)
241 g_object_unref(gdk_last_set_client_window_);
242 gdk_last_set_client_window_ = window;
243}
244
Tom Anderson5630ac272020-01-23 02:39:20245} // namespace gtk