1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
|
#include "internal.h"
#include <errno.h>
#define IME_TEXT_MAX (256u * 1024)
static void show_preedit(struct wayland_surface *w, char *text, int begin, int end)
{
bool changed = (text || w->ime_shown) &&
(!text || !w->ime_shown || strcmp(text, w->ime_shown) ||
begin != w->ime_shown_begin || end != w->ime_shown_end);
free(w->ime_shown);
w->ime_shown = text;
w->ime_shown_begin = begin;
w->ime_shown_end = end;
if (changed) {
yasl_private_surface_notify_composition(&w->base, w->ime_shown,
w->ime_shown_begin, w->ime_shown_end);
wayland_surface_schedule_redraw(w);
}
}
void ime_reset(struct wayland_surface *w)
{
free(w->ime_preedit);
w->ime_preedit = NULL;
free(w->ime_commit);
w->ime_commit = NULL;
w->ime_delete_pending = false;
show_preedit(w, NULL, -1, -1);
}
void ime_send_caret(struct wayland_surface *w)
{
wayland_surface_update_text_input(w);
}
bool wayland_surface_update_text_input(struct wayland_surface *w)
{
if (!w || !w->conn) {
errno = EINVAL;
return false;
}
if (!w->conn->text_input_mgr) {
errno = ENOTSUP;
return false;
}
struct seat *seat = w->ime_seat;
if (!seat || !seat->text_input || seat->ime_focus != w)
return true;
if (!w->base.text_input_active) {
if (seat->ime_enabled) {
zwp_text_input_v3_disable(seat->text_input);
zwp_text_input_v3_commit(seat->text_input);
seat->ime_enabled = false;
}
ime_reset(w);
return true;
}
if (!seat->ime_enabled) {
zwp_text_input_v3_enable(seat->text_input);
seat->ime_enabled = true;
}
unsigned purpose = w->base.text_purpose;
if (purpose > ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_TERMINAL)
purpose = ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_NORMAL;
zwp_text_input_v3_set_content_type(seat->text_input,
ZWP_TEXT_INPUT_V3_CONTENT_HINT_NONE, purpose);
const char *surrounding = w->base.text_surrounding ? w->base.text_surrounding : "";
size_t length = w->base.text_surrounding ? w->base.text_length : 0;
size_t cursor = w->base.text_surrounding ? w->base.text_end : 0;
size_t anchor = w->base.text_surrounding ? w->base.text_begin : 0;
if (cursor > length) cursor = length;
if (anchor > length) anchor = length;
if (length <= INT32_MAX && cursor <= INT32_MAX && anchor <= INT32_MAX)
zwp_text_input_v3_set_surrounding_text(seat->text_input, surrounding,
(int32_t)cursor, (int32_t)anchor);
uint32_t scale = w->scale_120 ? w->scale_120 : YASL_PRIVATE_SCALE_UNIT;
zwp_text_input_v3_set_cursor_rectangle(seat->text_input,
yasl_private_scale_to_logical(w->ime_caret.x, scale),
yasl_private_scale_to_logical(w->ime_caret.y, scale),
yasl_private_scale_to_logical(w->ime_caret.width, scale),
yasl_private_scale_to_logical(w->ime_caret.height, scale));
zwp_text_input_v3_commit(seat->text_input);
return true;
}
bool wayland_surface_ime_preedit_cursor(const struct wayland_surface *w, int *begin, int *end)
{
if (!w || !w->ime_shown || w->ime_shown_begin < 0)
return false;
*begin = w->ime_shown_begin;
*end = w->ime_shown_end;
return true;
}
void wayland_surface_set_ime_caret(struct wayland_surface *w, struct yasl_private_rect2i caret)
{
if (!w || (w->ime_caret.x == caret.x && w->ime_caret.y == caret.y &&
w->ime_caret.width == caret.width && w->ime_caret.height == caret.height))
return;
w->ime_caret = caret;
ime_send_caret(w);
}
static void text_input_enter(void *data, struct zwp_text_input_v3 *text_input,
struct wl_surface *surface)
{
(void)text_input;
struct seat *s = data;
struct wayland_surface *w = surface_root(surface ?
(struct wayland_surface *)wl_surface_get_user_data(surface) : NULL);
s->ime_focus = w;
if (!w) return;
w->ime_seat = s;
wayland_surface_update_text_input(w);
}
static void text_input_leave(void *data, struct zwp_text_input_v3 *text_input,
struct wl_surface *surface)
{
(void)surface;
struct seat *s = data;
struct wayland_surface *w = s->ime_focus;
s->ime_focus = NULL;
if (!w) return;
if (s->ime_enabled) {
zwp_text_input_v3_disable(text_input);
zwp_text_input_v3_commit(text_input);
s->ime_enabled = false;
}
if (w->ime_seat == s)
w->ime_seat = NULL;
ime_reset(w);
}
/* Copy protocol text, which libwayland always NUL-terminates, up to
* IME_TEXT_MAX bytes. Measuring with strlen keeps GCC from flagging a
* bounded read past a short caller buffer. */
static char *copy_text(const char *text)
{
size_t len = strlen(text);
if (len > IME_TEXT_MAX) len = IME_TEXT_MAX;
char *copy = malloc(len + 1);
if (!copy) return NULL;
memcpy(copy, text, len);
copy[len] = '\0';
return copy;
}
static void text_input_preedit_string(void *data,
struct zwp_text_input_v3 *text_input, const char *text,
int32_t cursor_begin, int32_t cursor_end)
{
(void)text_input;
struct wayland_surface *w = ((struct seat *)data)->ime_focus;
if (!w || !w->base.text_input_active) return;
keyboard_text_reset((struct seat *)data);
free(w->ime_preedit);
w->ime_preedit = text ? copy_text(text) : NULL;
int len = w->ime_preedit ? (int)strlen(w->ime_preedit) : 0;
bool shown = cursor_begin >= 0 && cursor_end >= cursor_begin &&
cursor_end <= len;
w->ime_cursor_begin = shown ? cursor_begin : -1;
w->ime_cursor_end = shown ? cursor_end : -1;
}
static void text_input_commit_string(void *data,
struct zwp_text_input_v3 *text_input, const char *text)
{
(void)text_input;
struct wayland_surface *w = ((struct seat *)data)->ime_focus;
if (!w || !w->base.text_input_active) return;
keyboard_text_reset((struct seat *)data);
free(w->ime_commit);
w->ime_commit = (text && text[0]) ? copy_text(text) : NULL;
}
static void text_input_delete_surrounding_text(void *data,
struct zwp_text_input_v3 *text_input, uint32_t before_length,
uint32_t after_length)
{
(void)text_input;
struct seat *seat = data;
struct wayland_surface *w = seat->ime_focus;
if (!w || !w->base.text_input_active || !w->base.text_surrounding)
return;
size_t length = w->base.text_length;
size_t cursor = w->base.text_end <= length ? w->base.text_end : length;
while (cursor && cursor < length &&
(((unsigned char)w->base.text_surrounding[cursor] & 0xc0) == 0x80))
cursor--;
size_t begin = before_length > cursor ? 0 : cursor - before_length;
size_t end = after_length > length - cursor ? length : cursor + after_length;
while (begin && begin < length &&
(((unsigned char)w->base.text_surrounding[begin] & 0xc0) == 0x80))
begin--;
while (end < length && (((unsigned char)w->base.text_surrounding[end] & 0xc0) == 0x80))
end++;
/* text_input_v3 batches deletion and commit into one `done` group. Keep
* the replacement range paired with that group's committed text and
* enqueue both together below, preserving snapshot metadata. */
w->ime_delete_pending = true;
w->ime_delete_begin = begin;
w->ime_delete_end = end;
w->ime_delete_revision = w->base.text_revision;
w->ime_delete_generation = w->base.text_generation;
}
static void text_input_done(void *data, struct zwp_text_input_v3 *text_input,
uint32_t serial)
{
(void)text_input; (void)serial;
struct wayland_surface *w = ((struct seat *)data)->ime_focus;
if (!w || !w->base.text_input_active) return;
bool has_snapshot = w->base.text_surrounding != NULL &&
w->base.text_length <= INT32_MAX;
bool has_commit = w->ime_commit && w->ime_commit[0];
bool edit = w->ime_delete_pending || (has_commit && has_snapshot);
if (edit) {
size_t begin = w->ime_delete_pending ? w->ime_delete_begin : w->base.text_begin;
size_t end = w->ime_delete_pending ? w->ime_delete_end : w->base.text_end;
if (begin > w->base.text_length) begin = w->base.text_length;
if (end > w->base.text_length) end = w->base.text_length;
if (end < begin) end = begin;
struct yasl_private_event ev = {
.type = YASL_PRIVATE_EVENT_TEXT_EDIT,
.surface = &w->base,
.text_edit = {
.data = has_commit ? w->ime_commit : "",
.len = has_commit ? strlen(w->ime_commit) : 0,
.revision = w->ime_delete_revision,
.generation = w->ime_delete_generation,
.begin = begin,
.end = end,
},
};
if (!w->ime_delete_pending) {
ev.text_edit.revision = w->base.text_revision;
ev.text_edit.generation = w->base.text_generation;
}
input_queue_push(&w->input, &ev);
w->ime_delete_pending = false;
}
if (w->ime_commit && !edit) {
struct yasl_private_event ev = {
.type = YASL_PRIVATE_EVENT_TEXT,
.surface = &w->base,
.text = { .data = w->ime_commit, .len = strlen(w->ime_commit) },
};
input_queue_push(&w->input, &ev);
}
free(w->ime_commit);
w->ime_commit = NULL;
char *preedit = w->ime_preedit && w->ime_preedit[0] ? w->ime_preedit : NULL;
if (!preedit)
free(w->ime_preedit);
w->ime_preedit = NULL;
show_preedit(w, preedit, preedit ? w->ime_cursor_begin : -1,
preedit ? w->ime_cursor_end : -1);
w->ime_cursor_begin = w->ime_cursor_end = -1;
}
static void text_input_action(void *data, struct zwp_text_input_v3 *text_input,
uint32_t action, uint32_t serial)
{ (void)data; (void)text_input; (void)action; (void)serial; }
static void text_input_language(void *data, struct zwp_text_input_v3 *text_input,
const char *language)
{ (void)data; (void)text_input; (void)language; }
static void text_input_preedit_hint(void *data, struct zwp_text_input_v3 *text_input,
uint32_t start, uint32_t end, uint32_t hint)
{ (void)data; (void)text_input; (void)start; (void)end; (void)hint; }
const struct zwp_text_input_v3_listener text_input_listener = {
.action = text_input_action,
.language = text_input_language,
.preedit_hint = text_input_preedit_hint,
.enter = text_input_enter,
.leave = text_input_leave,
.preedit_string = text_input_preedit_string,
.commit_string = text_input_commit_string,
.delete_surrounding_text = text_input_delete_surrounding_text,
.done = text_input_done,
};
|