aboutsummaryrefslogtreecommitdiff
path: root/examples/input-method.c
blob: 3e485d1c5bb257de4215bff1fd0ed55c8e84468d (plain)
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
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
#define _POSIX_C_SOURCE 200809L
#include <assert.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/epoll.h>
#include <sys/timerfd.h>
#include <unistd.h>
#include <wayland-client.h>
#include <wayland-egl.h>
#include <wlr/render/egl.h>
#include "input-method-unstable-v2-client-protocol.h"
#include "text-input-unstable-v3-client-protocol.h"
#include "xdg-shell-client-protocol.h"

const char usage[] = "Usage: input-method [seconds]\n\
\n\
Creates an input method using the input-method protocol.\n\
\n\
Whenever a text input is activated, this program sends a few sequences of\n\
commands and checks the validity of the responses, relying on returned\n\
surrounding text.\n\
\n\
The \"seconds\" argument is optional and defines the maximum delay between\n\
stages.";

struct input_method_state {
	enum zwp_text_input_v3_change_cause change_cause;
	struct {
		enum zwp_text_input_v3_content_hint hint;
		enum zwp_text_input_v3_content_purpose purpose;
	} content_type;
	struct {
		char *text;
		uint32_t cursor;
		uint32_t anchor;
	} surrounding;
};

static int sleeptime = 0;

static struct wl_display *display = NULL;
static struct wl_compositor *compositor = NULL;
static struct wl_seat *seat = NULL;
static struct zwp_input_method_manager_v2 *input_method_manager = NULL;
static struct zwp_input_method_v2 *input_method = NULL;

struct input_method_state pending;
struct input_method_state current;

static uint32_t serial = 0;
bool active = false;
bool pending_active = false;
bool unavailable = false;
bool running = false;

uint32_t update_stage = 0;

int timer_fd = 0;

static void print_state_diff(struct input_method_state previous,
		struct input_method_state future) {
	if (previous.content_type.hint != future.content_type.hint) {
		char *strs[] = { "COMPLETION", "SPELLCHECK", "AUTO_CAPITALIZATION",
			"LOWERCASE", "UPPERCASE", "TITLECASE", "HIDDEN_TEXT",
			"SENSITIVE_DATA", "LATIN", "MULTILINE"};
		printf("content_type.hint:");
		uint32_t hint = future.content_type.hint;
		if (!hint) {
			printf(" NONE");
		}
		for (unsigned i = 0; i < sizeof(strs) / sizeof(*strs); i++) {
			if (hint & 1 << i) {
				printf(" %s", strs[i]);
			}
		}
		printf("\n");
	}
	if (previous.content_type.purpose != future.content_type.purpose) {
		char *strs[] = { "NORMAL", "ALPHA", "DIGITS", "NUMBER", "PHONE", "URL",
			"EMAIL", "NAME", "PASSWORD", "PIN", "DATE", "TIME", "DATETIME",
			"TERMINAL" };
		printf("content_type.purpose: %s\n", strs[future.content_type.purpose]);
	}
	if (!!previous.surrounding.text != !!future.surrounding.text
			|| (previous.surrounding.text && future.surrounding.text
				&& strcmp(previous.surrounding.text, future.surrounding.text) != 0)
			|| previous.surrounding.anchor != future.surrounding.anchor
			|| previous.surrounding.cursor != future.surrounding.cursor) {
		char *text = future.surrounding.text;
		if (!text) {
			printf("Removed surrounding text\n");
		} else {
			printf("Surrounding text: %s\n", text);
			uint32_t anchor = future.surrounding.anchor;
			uint32_t cursor = future.surrounding.cursor;
			if (cursor == anchor) {
				char *temp = strndup(text, cursor);
				printf("Cursor after %d: %s\n", cursor, temp);
				free(temp);
			} else {
				if (cursor > anchor) {
					uint32_t tmp = anchor;
					anchor = cursor;
					cursor = tmp;
				}
				char *temp = strndup(&text[cursor], anchor - cursor);
				printf("Selection: %s\n", temp);
				free(temp);
			}
		}
	}
	if (previous.change_cause != future.change_cause) {
		char *strs[] = { "INPUT_METHOD", "OTHER" };
		printf("Change cause: %s\n", strs[future.change_cause]);
	}
}

static void handle_content_type(void *data,
		struct zwp_input_method_v2 *zwp_input_method_v2,
		uint32_t hint, uint32_t purpose) {
	pending.content_type.hint = hint;
	pending.content_type.purpose = purpose;
}

static void handle_surrounding_text(void *data,
		struct zwp_input_method_v2 *zwp_input_method_v2,
		const char *text, uint32_t cursor, uint32_t anchor) {
	free(pending.surrounding.text);
	pending.surrounding.text = strdup(text);
	pending.surrounding.cursor = cursor;
	pending.surrounding.anchor = anchor;
}

static void handle_text_change_cause(void *data,
		struct zwp_input_method_v2 *zwp_input_method_v2,
		uint32_t cause) {
	pending.change_cause = cause;
}

static void handle_activate(void *data,
		struct zwp_input_method_v2 *zwp_input_method_v2) {
	pending_active = true;
}

static void handle_deactivate(void *data,
		struct zwp_input_method_v2 *zwp_input_method_v2) {
	pending_active = false;
}

static void handle_unavailable(void *data,
		struct zwp_input_method_v2 *zwp_input_method_v2) {
	printf("IM disappeared\n");
	zwp_input_method_v2_destroy(zwp_input_method_v2);
	input_method = NULL;
	running = false;
}

static void im_activate(void *data,
		struct zwp_input_method_v2 *id) {
	update_stage = 0;
}

static void timer_arm(unsigned seconds) {
	printf("Timer armed\n");
	struct itimerspec spec = {
		.it_interval = {0},
		.it_value = {
			.tv_sec = seconds,
			.tv_nsec = 0
		}
	};
	if (timerfd_settime(timer_fd, 0, &spec, NULL)) {
		fprintf(stderr, "Failed to arm timer: %s\n", strerror(errno));
	}
}

static void do_updates() {
	printf("Update %d\n", update_stage);
	switch (update_stage) {
	case 0:
		// TODO: remember initial surrounding text
		zwp_input_method_v2_set_preedit_string(input_method, "Preedit", 2, 4);
		zwp_input_method_v2_commit(input_method, serial);
		// don't expect an answer, preedit doesn't change anything visible
		timer_arm(sleeptime);
		update_stage++;
		return;
	case 1:
		zwp_input_method_v2_set_preedit_string(input_method, "Præedit2", strlen("Pr"), strlen("Præed"));
		zwp_input_method_v2_commit_string(input_method, "_Commit_");
		zwp_input_method_v2_commit(input_method, serial);
		update_stage++;
		break;
	case 2:
		if (strcmp(current.surrounding.text, "_Commit_") != 0) {
			return;
		}
		zwp_input_method_v2_commit_string(input_method, "_CommitNoPreed_");
		zwp_input_method_v2_commit(input_method, serial);
		timer_arm(sleeptime);
		update_stage++;
		break;
	case 3:
		if (strcmp(current.surrounding.text, "_Commit__CommitNoPreed_") != 0) {
			return;
		}
		zwp_input_method_v2_commit_string(input_method, "_WaitNo_");
		zwp_input_method_v2_delete_surrounding_text(input_method, strlen("_CommitNoPreed_"), 0);
		zwp_input_method_v2_commit(input_method, serial);
		update_stage++;
		break;
	case 4:
		if (strcmp(current.surrounding.text, "_Commit__WaitNo_") != 0) {
			return;
		}
		zwp_input_method_v2_set_preedit_string(input_method, "PreedWithDel", strlen("Preed"), strlen("Preed"));
		zwp_input_method_v2_delete_surrounding_text(input_method, strlen("_WaitNo_"), 0);
		zwp_input_method_v2_commit(input_method, serial);
		update_stage++;
		break;
	case 5:
		if (strcmp(current.surrounding.text, "_Commit_") != 0) {
			return;
		}
		zwp_input_method_v2_delete_surrounding_text(input_method, strlen("mit_"), 0);
		zwp_input_method_v2_commit(input_method, serial);
		update_stage++;
		break;
	case 6:
		if (strcmp(current.surrounding.text, "_Com") != 0) {
			printf("Failed\n");
		}
		update_stage++;
		break;
	default:
		printf("Submitted everything\n");
		return;
	};
}

static void handle_timer() {
	printf("Timer dispatched at %d\n", update_stage);
	do_updates();
}

static void im_deactivate(void *data,
		struct zwp_input_method_v2 *context) {
	// No special action needed
}

static void handle_done(void *data,
		struct zwp_input_method_v2 *zwp_input_method_v2) {
	bool prev_active = active;
	serial++;
	printf("Handle serial %d\n", serial);
	if (active != pending_active) {
		printf("Now %s\n", pending_active ? "active" : "inactive");
	}
	if (pending_active) {
		print_state_diff(current, pending);
	}
	active = pending_active;
	free(current.surrounding.text);
	struct input_method_state default_state = {0};
	current = pending;
	pending = default_state;
	if (active && !prev_active) {
		im_activate(data, zwp_input_method_v2);
	} else if (!active && prev_active) {
		im_deactivate(data, zwp_input_method_v2);
	}

	do_updates();
}

static const struct zwp_input_method_v2_listener im_listener = {
	.activate = handle_activate,
	.deactivate = handle_deactivate,
	.surrounding_text = handle_surrounding_text,
	.text_change_cause = handle_text_change_cause,
	.content_type = handle_content_type,
	.done = handle_done,
	.unavailable = handle_unavailable,
};

static void handle_global(void *data, struct wl_registry *registry,
		uint32_t name, const char *interface, uint32_t version) {
	if (strcmp(interface, "wl_compositor") == 0) {
		compositor = wl_registry_bind(registry, name,
			&wl_compositor_interface, 1);
	} else if (strcmp(interface, zwp_input_method_manager_v2_interface.name) == 0) {
		input_method_manager = wl_registry_bind(registry, name,
			&zwp_input_method_manager_v2_interface, 1);
	} else if (strcmp(interface, wl_seat_interface.name) == 0) {
		seat = wl_registry_bind(registry, name, &wl_seat_interface, version);
	}
}

static void handle_global_remove(void *data, struct wl_registry *registry,
		uint32_t name) {
	// who cares
}

static const struct wl_registry_listener registry_listener = {
	.global = handle_global,
	.global_remove = handle_global_remove,
};

int main(int argc, char **argv) {
	if (argc > 1) {
		if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-h") == 0) {
			printf(usage);
			return 0;
		}
		sleeptime = atoi(argv[1]);
	}
	display = wl_display_connect(NULL);
	if (display == NULL) {
		fprintf(stderr, "Failed to create display\n");
		return EXIT_FAILURE;
	}

	struct wl_registry *registry = wl_display_get_registry(display);
	wl_registry_add_listener(registry, &registry_listener, NULL);
	wl_display_dispatch(display);
	wl_display_roundtrip(display);

	if (compositor == NULL) {
		fprintf(stderr, "wl-compositor not available\n");
		return EXIT_FAILURE;
	}
	if (input_method_manager == NULL) {
		fprintf(stderr, "input-method not available\n");
		return EXIT_FAILURE;
	}
	if (seat == NULL) {
		fprintf(stderr, "seat not available\n");
		return EXIT_FAILURE;
	}

	input_method = zwp_input_method_manager_v2_get_input_method(
		input_method_manager, seat);
	running = true;
	zwp_input_method_v2_add_listener(input_method, &im_listener, NULL);

	int display_fd = wl_display_get_fd(display);
	timer_fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK);
	if (timer_fd < 0) {
		fprintf(stderr, "Failed to start timer\n");
		return EXIT_FAILURE;
	}
	int epoll = epoll_create1(EPOLL_CLOEXEC);
	if (epoll < 0) {
		fprintf(stderr, "Failed to start epoll\n");
		return EXIT_FAILURE;
	}

	struct epoll_event epoll_display = {
		.events = EPOLLIN | EPOLLOUT,
		.data = {.fd = display_fd},
	};
	if (epoll_ctl(epoll, EPOLL_CTL_ADD, display_fd, &epoll_display)) {
		fprintf(stderr, "Failed to epoll display\n");
		return EXIT_FAILURE;
	}

	wl_display_roundtrip(display); // timer may be armed here

	struct epoll_event epoll_timer = {
		.events = EPOLLIN,
		.data = {.fd = timer_fd},
	};
	if (epoll_ctl(epoll, EPOLL_CTL_ADD, timer_fd, &epoll_timer)) {
		fprintf(stderr, "Failed to epoll timer\n");
		return EXIT_FAILURE;
	}

	timer_arm(2);

	struct epoll_event caught;
	while (epoll_wait(epoll, &caught, 1, -1)) {
		if (!running) {
			printf("Exiting\n");
			return EXIT_SUCCESS;
		}
		if (caught.data.fd == display_fd) {
			if (wl_display_dispatch(display) == -1) {
				break;
			}
		} else if (caught.data.fd == timer_fd) {
			uint64_t expirations;
			ssize_t n = read(timer_fd, &expirations, sizeof(expirations));
			assert(n >= 0);
			handle_timer();
		} else {
			printf("Unknown source\n");
		}
	}
	return EXIT_SUCCESS;
}