Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 9fb2ac3

Browse files
committed
Add Linux multi-step input method support for Linux
1 parent 30e3bca commit 9fb2ac3

File tree

3 files changed

+166
-9
lines changed

3 files changed

+166
-9
lines changed

shell/platform/linux/fl_text_input_plugin.cc

Lines changed: 162 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ static constexpr char kHideMethod[] = "TextInput.hide";
2222
static constexpr char kUpdateEditingStateMethod[] =
2323
"TextInputClient.updateEditingState";
2424
static constexpr char kPerformActionMethod[] = "TextInputClient.performAction";
25+
static constexpr char kSetEditableSizeAndTransform[] =
26+
"TextInput.setEditableSizeAndTransform";
27+
static constexpr char kSetMarkedTextRect[] = "TextInput.setMarkedTextRect";
2528

2629
static constexpr char kInputActionKey[] = "inputAction";
2730
static constexpr char kTextInputTypeKey[] = "inputType";
@@ -34,6 +37,8 @@ static constexpr char kSelectionIsDirectionalKey[] = "selectionIsDirectional";
3437
static constexpr char kComposingBaseKey[] = "composingBase";
3538
static constexpr char kComposingExtentKey[] = "composingExtent";
3639

40+
static constexpr char kTransform[] = "transform";
41+
3742
static constexpr char kTextAffinityDownstream[] = "TextAffinity.downstream";
3843
static constexpr char kMultilineInputType[] = "TextInputType.multiline";
3944

@@ -57,6 +62,23 @@ struct _FlTextInputPlugin {
5762
GtkIMContext* im_context;
5863

5964
flutter::TextInputModel* text_model;
65+
66+
// The owning Flutter view.
67+
FlView* view;
68+
69+
// A 4x4 matrix that maps from `EditableText` local coordinates to the
70+
// coordinate system of `PipelineOwner.rootNode`.
71+
double editabletext_transform[4][4];
72+
73+
// The smallest rect, in local coordinates, of the text in the composing
74+
// range, or of the caret in the case where there is no current composing
75+
// range. This value is updated via `TextInput.setMarkedTextRect` messages
76+
// over the text input channel.
77+
GdkRectangle composing_rect;
78+
79+
// A cached copy of the composing (pre-edit in Gtk terminology) rect in
80+
// window coordinates.
81+
GdkRectangle preedit_rect;
6082
};
6183

6284
G_DEFINE_TYPE(FlTextInputPlugin, fl_text_input_plugin, G_TYPE_OBJECT)
@@ -100,13 +122,20 @@ static void update_editing_state(FlTextInputPlugin* self) {
100122
value, kSelectionExtentKey,
101123
fl_value_new_int(self->text_model->selection_extent()));
102124

125+
int composing_base =
126+
self->text_model->composing() ? self->text_model->composing_base() : -1;
127+
int composing_extent =
128+
self->text_model->composing() ? self->text_model->composing_extent() : -1;
129+
fl_value_set_string_take(value, kComposingBaseKey,
130+
fl_value_new_int(composing_base));
131+
fl_value_set_string_take(value, kComposingExtentKey,
132+
fl_value_new_int(composing_extent));
133+
103134
// The following keys are not implemented and set to default values.
104135
fl_value_set_string_take(value, kSelectionAffinityKey,
105136
fl_value_new_string(kTextAffinityDownstream));
106137
fl_value_set_string_take(value, kSelectionIsDirectionalKey,
107138
fl_value_new_bool(FALSE));
108-
fl_value_set_string_take(value, kComposingBaseKey, fl_value_new_int(-1));
109-
fl_value_set_string_take(value, kComposingExtentKey, fl_value_new_int(-1));
110139

111140
fl_value_append(args, value);
112141

@@ -139,9 +168,50 @@ static void perform_action(FlTextInputPlugin* self) {
139168
nullptr, perform_action_response_cb, self);
140169
}
141170

171+
// Signal handler for GtkIMContext::preedit-start
172+
static void im_preedit_start_cb(FlTextInputPlugin* self) {
173+
self->text_model->BeginComposing();
174+
175+
// Set the top-level window used for system input method windows.
176+
GdkWindow* window =
177+
gtk_widget_get_window(gtk_widget_get_toplevel(GTK_WIDGET(self->view)));
178+
gtk_im_context_set_client_window(self->im_context, window);
179+
180+
// Set the cursor location in window co-ordinates so that Gtk can position
181+
// any system input method windows.
182+
gtk_im_context_set_cursor_location(self->im_context, &self->preedit_rect);
183+
}
184+
185+
// Signal handler for GtkIMContext::preedit-changed
186+
static void im_preedit_changed_cb(FlTextInputPlugin* self) {
187+
// Set the cursor location in window co-ordinates so that Gtk can position
188+
// any system input method windows.
189+
gtk_im_context_set_cursor_location(self->im_context, &self->preedit_rect);
190+
191+
gchar* buf = NULL;
192+
gint cursor_offset = 0;
193+
gtk_im_context_get_preedit_string(self->im_context, &buf, NULL,
194+
&cursor_offset);
195+
cursor_offset += self->text_model->composing_base();
196+
self->text_model->UpdateComposingText(buf);
197+
self->text_model->SetSelection(cursor_offset, cursor_offset);
198+
199+
update_editing_state(self);
200+
}
201+
142202
// Signal handler for GtkIMContext::commit
143203
static void im_commit_cb(FlTextInputPlugin* self, const gchar* text) {
144-
self->text_model->AddText(text);
204+
if (self->text_model->composing()) {
205+
self->text_model->CommitComposing();
206+
} else {
207+
self->text_model->AddText(text);
208+
}
209+
update_editing_state(self);
210+
}
211+
212+
// Signal handler for GtkIMContext::preedit-end
213+
static void im_preedit_end_cb(FlTextInputPlugin* self) {
214+
self->text_model->EndComposing();
145215
update_editing_state(self);
146216
}
147217

@@ -209,6 +279,8 @@ static FlMethodResponse* set_editing_state(FlTextInputPlugin* self,
209279
FlValue* args) {
210280
const gchar* text =
211281
fl_value_get_string(fl_value_lookup_string(args, kTextKey));
282+
self->text_model->SetText(text);
283+
212284
int64_t selection_base =
213285
fl_value_get_int(fl_value_lookup_string(args, kSelectionBaseKey));
214286
int64_t selection_extent =
@@ -217,10 +289,21 @@ static FlMethodResponse* set_editing_state(FlTextInputPlugin* self,
217289
if (selection_base == -1 && selection_extent == -1) {
218290
selection_base = selection_extent = 0;
219291
}
220-
221-
self->text_model->SetText(text);
222292
self->text_model->SetSelection(selection_base, selection_extent);
223293

294+
int64_t composing_base =
295+
fl_value_get_int(fl_value_lookup_string(args, kComposingBaseKey));
296+
int64_t composing_extent =
297+
fl_value_get_int(fl_value_lookup_string(args, kComposingExtentKey));
298+
if (composing_base == -1 && composing_extent == -1) {
299+
self->text_model->EndComposing();
300+
} else {
301+
size_t composing_start = std::min(composing_base, composing_extent);
302+
size_t cursor_offset = selection_base - composing_start;
303+
self->text_model->SetComposingRange(composing_base, composing_extent,
304+
cursor_offset);
305+
}
306+
224307
return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr));
225308
}
226309

@@ -238,6 +321,58 @@ static FlMethodResponse* hide(FlTextInputPlugin* self) {
238321
return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr));
239322
}
240323

324+
static void update_preedit_rect(FlTextInputPlugin* self) {
325+
// Skip update if not composing since composing base/extent are 0.
326+
if (!self->text_model->composing()) {
327+
return;
328+
}
329+
gint fl_x = self->composing_rect.x * self->editabletext_transform[0][0] +
330+
self->composing_rect.y * self->editabletext_transform[1][0] +
331+
self->editabletext_transform[3][0] + self->composing_rect.width;
332+
gint fl_y = self->composing_rect.x * self->editabletext_transform[0][1] +
333+
self->composing_rect.y * self->editabletext_transform[1][1] +
334+
self->editabletext_transform[3][1] + self->composing_rect.height;
335+
336+
gtk_widget_translate_coordinates(
337+
GTK_WIDGET(self->view), gtk_widget_get_toplevel(GTK_WIDGET(self->view)),
338+
fl_x, fl_y, &self->preedit_rect.x, &self->preedit_rect.y);
339+
}
340+
341+
// Handle updates from the framework to the cached coordinate transform of the
342+
// `EditableText` relative to the root coordinate system.
343+
static FlMethodResponse* set_editable_size_and_transform(
344+
FlTextInputPlugin* self,
345+
FlValue* args) {
346+
FlValue* transform = fl_value_lookup_string(args, kTransform);
347+
size_t transform_len = fl_value_get_length(transform);
348+
g_warn_if_fail(transform_len == 16);
349+
350+
for (size_t i = 0; i < transform_len; ++i) {
351+
double val = fl_value_get_float(fl_value_get_list_value(transform, i));
352+
self->editabletext_transform[i / 4][i % 4] = val;
353+
}
354+
update_preedit_rect(self);
355+
356+
return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr));
357+
}
358+
359+
// Handle updates from the framework to the cached composing rect in local
360+
// coordinates.
361+
static FlMethodResponse* set_marked_text_rect(FlTextInputPlugin* self,
362+
FlValue* args) {
363+
self->composing_rect.x =
364+
fl_value_get_float(fl_value_lookup_string(args, "x"));
365+
self->composing_rect.y =
366+
fl_value_get_float(fl_value_lookup_string(args, "y"));
367+
self->composing_rect.width =
368+
fl_value_get_float(fl_value_lookup_string(args, "width"));
369+
self->composing_rect.height =
370+
fl_value_get_float(fl_value_lookup_string(args, "height"));
371+
update_preedit_rect(self);
372+
373+
return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr));
374+
}
375+
241376
// Called when a method call is received from Flutter.
242377
static void method_call_cb(FlMethodChannel* channel,
243378
FlMethodCall* method_call,
@@ -258,6 +393,10 @@ static void method_call_cb(FlMethodChannel* channel,
258393
response = clear_client(self);
259394
} else if (strcmp(method, kHideMethod) == 0) {
260395
response = hide(self);
396+
} else if (strcmp(method, kSetEditableSizeAndTransform) == 0) {
397+
response = set_editable_size_and_transform(self, args);
398+
} else if (strcmp(method, kSetMarkedTextRect) == 0) {
399+
response = set_marked_text_rect(self, args);
261400
} else {
262401
response = FL_METHOD_RESPONSE(fl_method_not_implemented_response_new());
263402
}
@@ -268,6 +407,11 @@ static void method_call_cb(FlMethodChannel* channel,
268407
}
269408
}
270409

410+
static void view_weak_notify_cb(gpointer user_data, GObject* object) {
411+
FlTextInputPlugin* self = FL_TEXT_INPUT_PLUGIN(object);
412+
self->view = nullptr;
413+
}
414+
271415
static void fl_text_input_plugin_dispose(GObject* object) {
272416
FlTextInputPlugin* self = FL_TEXT_INPUT_PLUGIN(object);
273417

@@ -290,6 +434,15 @@ static void fl_text_input_plugin_init(FlTextInputPlugin* self) {
290434
self->client_id = kClientIdUnset;
291435
self->im_context = gtk_im_multicontext_new();
292436
self->input_multiline = FALSE;
437+
g_signal_connect_object(self->im_context, "preedit-start",
438+
G_CALLBACK(im_preedit_start_cb), self,
439+
G_CONNECT_SWAPPED);
440+
g_signal_connect_object(self->im_context, "preedit-end",
441+
G_CALLBACK(im_preedit_end_cb), self,
442+
G_CONNECT_SWAPPED);
443+
g_signal_connect_object(self->im_context, "preedit-changed",
444+
G_CALLBACK(im_preedit_changed_cb), self,
445+
G_CONNECT_SWAPPED);
293446
g_signal_connect_object(self->im_context, "commit", G_CALLBACK(im_commit_cb),
294447
self, G_CONNECT_SWAPPED);
295448
g_signal_connect_object(self->im_context, "retrieve-surrounding",
@@ -301,7 +454,8 @@ static void fl_text_input_plugin_init(FlTextInputPlugin* self) {
301454
self->text_model = new flutter::TextInputModel();
302455
}
303456

304-
FlTextInputPlugin* fl_text_input_plugin_new(FlBinaryMessenger* messenger) {
457+
FlTextInputPlugin* fl_text_input_plugin_new(FlBinaryMessenger* messenger,
458+
FlView* view) {
305459
g_return_val_if_fail(FL_IS_BINARY_MESSENGER(messenger), nullptr);
306460

307461
FlTextInputPlugin* self = FL_TEXT_INPUT_PLUGIN(
@@ -312,7 +466,8 @@ FlTextInputPlugin* fl_text_input_plugin_new(FlBinaryMessenger* messenger) {
312466
fl_method_channel_new(messenger, kChannelName, FL_METHOD_CODEC(codec));
313467
fl_method_channel_set_method_call_handler(self->channel, method_call_cb, self,
314468
nullptr);
315-
469+
self->view = view;
470+
g_object_weak_ref(G_OBJECT(view), view_weak_notify_cb, self);
316471
return self;
317472
}
318473

shell/platform/linux/fl_text_input_plugin.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include <gdk/gdk.h>
99

1010
#include "flutter/shell/platform/linux/public/flutter_linux/fl_binary_messenger.h"
11+
#include "flutter/shell/platform/linux/public/flutter_linux/fl_view.h"
1112

1213
G_BEGIN_DECLS
1314

@@ -33,7 +34,8 @@ G_DECLARE_FINAL_TYPE(FlTextInputPlugin,
3334
*
3435
* Returns: a new #FlTextInputPlugin.
3536
*/
36-
FlTextInputPlugin* fl_text_input_plugin_new(FlBinaryMessenger* messenger);
37+
FlTextInputPlugin* fl_text_input_plugin_new(FlBinaryMessenger* messenger,
38+
FlView* view);
3739

3840
/**
3941
* fl_text_input_plugin_filter_keypress

shell/platform/linux/fl_view.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ static void fl_view_constructed(GObject* object) {
161161
self->key_event_plugin = fl_key_event_plugin_new(messenger);
162162
self->mouse_cursor_plugin = fl_mouse_cursor_plugin_new(messenger, self);
163163
self->platform_plugin = fl_platform_plugin_new(messenger);
164-
self->text_input_plugin = fl_text_input_plugin_new(messenger);
164+
self->text_input_plugin = fl_text_input_plugin_new(messenger, self);
165165
}
166166

167167
static void fl_view_set_property(GObject* object,

0 commit comments

Comments
 (0)