1 #define _POSIX_C_SOURCE 200809L
2 #include <GLES2/gl2.h>
3 #include <stdio.h>
4 #include <stdlib.h>
5 #include <string.h>
6 #include <unistd.h>
7 #include <wayland-client.h>
8 #include <wayland-egl.h>
9 #include <wlr/render/egl.h>
10 #include "text-input-unstable-v3-client-protocol.h"
11 #include "xdg-shell-client-protocol.h"
12 
13 const char usage[] = "Usage: text-input [seconds [width height]]\n\
14 \n\
15 Creates a xdg-toplevel using the text-input protocol.\n\
16 It will be solid black when it has no text input focus, yellow when it\n\
17 has focus, and red when it was notified that the focus moved away\n\
18 but still didn't give up the text input ability.\n\
19 \n\
20 The \"seconds\" argument is optional and defines the delay between getting\n\
21 notified of lost focus and releasing text input.\n\
22 \n\
23 The \"width\" and \"height\" arguments define the window shape.\n\
24 \n\
25 The console will print the internal state of the text field:\n\
26 - the text in the 1st line\n\
27 - \".\" under each preedit character\n\
28 - \"_\" under each selected preedit character\n\
29 - \"|\" at the cursor position if there are no selected characters in the\n\
30 preedit.\n\
31 \n\
32 The cursor positions may be inaccurate, especially in presence of zero-width\n\
33 characters or non-monospaced fonts.\n";
34 
35 struct text_input_state {
36 	char *commit;
37 	struct {
38 		char *text;
39 		int32_t cursor_begin;
40 		int32_t cursor_end;
41 	} preedit;
42 	struct {
43 		uint32_t after_length;
44 		uint32_t before_length;
45 	} delete_surrounding;
46 };
47 
48 static struct text_input_state pending = {0};
49 static struct text_input_state current = {0};
50 static bool entered = false;
51 static uint32_t serial;
52 static char *buffer; // text buffer
53 // cursor is not present, there's no way to move it outside of preedit
54 
55 static int sleeptime = 0;
56 static int width = 100, height = 200;
57 static int enabled = 0;
58 
59 static struct wl_display *display = NULL;
60 static struct wl_compositor *compositor = NULL;
61 static struct wl_seat *seat = NULL;
62 static struct xdg_wm_base *wm_base = NULL;
63 static struct zwp_text_input_manager_v3 *text_input_manager = NULL;
64 static struct zwp_text_input_v3 *text_input	= NULL;
65 
66 struct wlr_egl egl;
67 struct wl_egl_window *egl_window;
68 struct wlr_egl_surface *egl_surface;
69 
draw(void)70 static void draw(void) {
71 	eglMakeCurrent(egl.display, egl_surface, egl_surface, egl.context);
72 
73 	float color[] = {1.0, 1.0, 0.0, 1.0};
74 	color[0] = enabled * 1.0;
75 	color[1] = entered * 1.0;
76 
77 	glViewport(0, 0, width, height);
78 	glClearColor(color[0], color[1], color[2], 1.0);
79 	glClear(GL_COLOR_BUFFER_BIT);
80 
81 	eglSwapBuffers(egl.display, egl_surface);
82 }
83 
utf8_strlen(char * str)84 static size_t utf8_strlen(char *str) {
85 	size_t cp_count = 0;
86 	for (; *str != '\0'; str++) {
87 		if ((*str & 0xc0) != 0x80) {
88 			cp_count++;
89 		}
90 	}
91 	return cp_count;
92 }
93 
utf8_offset(char * utf8_str,size_t byte_offset)94 static size_t utf8_offset(char *utf8_str, size_t byte_offset) {
95 	size_t cp_count = 0;
96 	for (char *c = utf8_str; c < utf8_str + byte_offset; c++) {
97 		if ((*c & 0xc0) != 0x80) {
98 			cp_count++;
99 		}
100 	}
101 	return cp_count;
102 }
103 
104 // TODO: would be nicer to have this text display inside the window
show_status(void)105 static void show_status(void) {
106 	printf("State %d:", serial);
107 	if (!enabled) {
108 		printf(" disabled");
109 	}
110 
111 	char *preedit_text = current.preedit.text;
112 	if (!preedit_text) {
113 		preedit_text = "";
114 	}
115 
116 	printf("\n");
117 	printf("%s", buffer);
118 	printf("%s\n", preedit_text);
119 
120 	// Positioning of the cursor requires UTF8 offsets to match monospaced
121 	// glyphs
122 	for (unsigned i = 0; i < utf8_strlen(buffer); i++) {
123 		printf(" ");
124 	}
125 	char *cursor_mark = calloc(utf8_strlen(preedit_text) + 2, sizeof(char));
126 	for (unsigned i = 0; i < utf8_strlen(preedit_text); i++) {
127 		cursor_mark[i] = '.';
128 	}
129 	if (current.preedit.cursor_begin == -1
130 			&& current.preedit.cursor_end == -1) {
131 		goto end;
132 	}
133 	if (current.preedit.cursor_begin == -1
134 			|| current.preedit.cursor_end == -1) {
135 		printf("Only one cursor side is defined: %d to %d\n",
136 			current.preedit.cursor_begin, current.preedit.cursor_end);
137 		goto end;
138 	}
139 
140 	if ((unsigned)current.preedit.cursor_begin > strlen(preedit_text)) {
141 		printf("Cursor out of bounds\n");
142 		goto end;
143 	}
144 
145 	if (current.preedit.cursor_begin == current.preedit.cursor_end) {
146 		cursor_mark[utf8_offset(preedit_text, current.preedit.cursor_begin)]
147 			= '|';
148 		goto print;
149 	}
150 
151 	if (current.preedit.cursor_begin > current.preedit.cursor_end) {
152 		printf("End cursor is before start cursor\n");
153 		goto end;
154 	}
155 
156 	// negative offsets already checked before
157 	for (unsigned i = utf8_offset(preedit_text, current.preedit.cursor_begin);
158 			i < utf8_offset(preedit_text, current.preedit.cursor_end); i++) {
159 		cursor_mark[i] = '_';
160 	}
161 print:
162 	printf("%s\n", cursor_mark);
163 end:
164 	free(cursor_mark);
165 }
166 
commit(struct zwp_text_input_v3 * text_input)167 static void commit(struct zwp_text_input_v3 *text_input) {
168 	zwp_text_input_v3_commit(text_input);
169 	serial++;
170 }
171 
send_status_update(struct zwp_text_input_v3 * text_input)172 static void send_status_update(struct zwp_text_input_v3 *text_input) {
173 	zwp_text_input_v3_set_surrounding_text(text_input, buffer, strlen(buffer), strlen(buffer));
174 	zwp_text_input_v3_set_text_change_cause(text_input, ZWP_TEXT_INPUT_V3_CHANGE_CAUSE_INPUT_METHOD);
175 	commit(text_input);
176 }
177 
text_input_handle_enter(void * data,struct zwp_text_input_v3 * zwp_text_input_v3,struct wl_surface * surface)178 static void text_input_handle_enter(void *data,
179 		struct zwp_text_input_v3 *zwp_text_input_v3,
180 		struct wl_surface *surface) {
181 	entered = true;
182 	zwp_text_input_v3_enable(zwp_text_input_v3);
183 	commit(zwp_text_input_v3);
184 	enabled = true;
185 	draw();
186 	show_status();
187 }
188 
text_input_handle_leave(void * data,struct zwp_text_input_v3 * zwp_text_input_v3,struct wl_surface * surface)189 static void text_input_handle_leave(void *data,
190 		struct zwp_text_input_v3 *zwp_text_input_v3,
191 		struct wl_surface *surface) {
192 	entered = false;
193 	draw();
194 	wl_display_roundtrip(display);
195 	sleep(sleeptime);
196 	zwp_text_input_v3_disable(zwp_text_input_v3);
197 	commit(zwp_text_input_v3);
198 	enabled = false;
199 	draw();
200 	show_status();
201 }
202 
text_input_commit_string(void * data,struct zwp_text_input_v3 * zwp_text_input_v3,const char * text)203 static void text_input_commit_string(void *data,
204 		struct zwp_text_input_v3 *zwp_text_input_v3,
205 		const char *text) {
206 	free(pending.commit);
207 	pending.commit = strdup(text);
208 }
209 
text_input_delete_surrounding_text(void * data,struct zwp_text_input_v3 * zwp_text_input_v3,uint32_t before_length,uint32_t after_length)210 static void text_input_delete_surrounding_text(void *data,
211 		struct zwp_text_input_v3 *zwp_text_input_v3,
212 		uint32_t before_length, uint32_t after_length) {
213 	pending.delete_surrounding.before_length = before_length;
214 	pending.delete_surrounding.after_length = after_length;
215 }
216 
text_input_preedit_string(void * data,struct zwp_text_input_v3 * zwp_text_input_v3,const char * text,int32_t cursor_begin,int32_t cursor_end)217 static void text_input_preedit_string(void *data,
218 		struct zwp_text_input_v3 *zwp_text_input_v3,
219 		const char *text, int32_t cursor_begin, int32_t cursor_end) {
220 	free(pending.preedit.text);
221 	pending.preedit.text = strdup(text);
222 	pending.preedit.cursor_begin = cursor_begin;
223 	pending.preedit.cursor_end = cursor_end;
224 }
225 
text_input_handle_done(void * data,struct zwp_text_input_v3 * zwp_text_input_v3,uint32_t incoming_serial)226 static void text_input_handle_done(void *data,
227 		 struct zwp_text_input_v3 *zwp_text_input_v3,
228 		 uint32_t incoming_serial) {
229 	if (serial != incoming_serial) {
230 		fprintf(stderr, "Received serial %d while expecting %d\n", incoming_serial, serial);
231 		return;
232 	}
233 	free(current.preedit.text);
234 	free(current.commit);
235 	current = pending;
236 	struct text_input_state empty = {0};
237 	pending = empty;
238 
239 	if (current.delete_surrounding.after_length + current.delete_surrounding.before_length > 0) {
240 		// cursor is always after committed text, after_length != 0 will never happen
241 		unsigned delete_before = current.delete_surrounding.before_length;
242 		if (delete_before > strlen(buffer)) {
243 			delete_before = strlen(buffer);
244 		}
245 		buffer[strlen(buffer) - delete_before] = '\0';
246 	}
247 
248 	char *commit_string = current.commit;
249 	if (!commit_string) {
250 		commit_string = "";
251 	}
252 	char *old_buffer = buffer;
253 	buffer = calloc(strlen(buffer) + strlen(commit_string) + 1, sizeof(char)); // realloc may fail anyway
254 	strcpy(buffer, old_buffer);
255 	free(old_buffer);
256 	strcat(buffer, commit_string);
257 
258 	send_status_update(zwp_text_input_v3);
259 	show_status();
260 }
261 
262 static const struct zwp_text_input_v3_listener text_input_listener = {
263 	.enter = text_input_handle_enter,
264 	.leave = text_input_handle_leave,
265 	.commit_string = text_input_commit_string,
266 	.delete_surrounding_text = text_input_delete_surrounding_text,
267 	.preedit_string = text_input_preedit_string,
268 	.done = text_input_handle_done,
269 };
270 
xdg_surface_handle_configure(void * data,struct xdg_surface * xdg_surface,uint32_t serial)271 static void xdg_surface_handle_configure(void *data,
272 		struct xdg_surface *xdg_surface, uint32_t serial) {
273 	xdg_surface_ack_configure(xdg_surface, serial);
274 	wl_egl_window_resize(egl_window, width, height, 0, 0);
275 	draw();
276 }
277 
278 static const struct xdg_surface_listener xdg_surface_listener = {
279 	.configure = xdg_surface_handle_configure,
280 };
281 
xdg_toplevel_handle_configure(void * data,struct xdg_toplevel * xdg_toplevel,int32_t w,int32_t h,struct wl_array * states)282 static void xdg_toplevel_handle_configure(void *data,
283 		struct xdg_toplevel *xdg_toplevel, int32_t w, int32_t h,
284 		struct wl_array *states) {
285 	width = w;
286 	height = h;
287 }
288 
xdg_toplevel_handle_close(void * data,struct xdg_toplevel * xdg_toplevel)289 static void xdg_toplevel_handle_close(void *data,
290 		struct xdg_toplevel *xdg_toplevel) {
291 	exit(EXIT_SUCCESS);
292 }
293 
294 static const struct xdg_toplevel_listener xdg_toplevel_listener = {
295 	.configure = xdg_toplevel_handle_configure,
296 	.close = xdg_toplevel_handle_close,
297 };
298 
handle_global(void * data,struct wl_registry * registry,uint32_t name,const char * interface,uint32_t version)299 static void handle_global(void *data, struct wl_registry *registry,
300 		uint32_t name, const char *interface, uint32_t version) {
301 	if (strcmp(interface, "wl_compositor") == 0) {
302 		compositor = wl_registry_bind(registry, name,
303 			&wl_compositor_interface, 1);
304 	} else if (strcmp(interface, xdg_wm_base_interface.name) == 0) {
305 		wm_base = wl_registry_bind(registry, name, &xdg_wm_base_interface, 1);
306 	} else if (strcmp(interface, zwp_text_input_manager_v3_interface.name) == 0) {
307 		text_input_manager = wl_registry_bind(registry, name,
308 			&zwp_text_input_manager_v3_interface, 1);
309 	} else if (strcmp(interface, wl_seat_interface.name) == 0) {
310 		seat = wl_registry_bind(registry, name, &wl_seat_interface, version);
311 	}
312 }
313 
handle_global_remove(void * data,struct wl_registry * registry,uint32_t name)314 static void handle_global_remove(void *data, struct wl_registry *registry,
315 		uint32_t name) {
316 	// who cares
317 }
318 
319 static const struct wl_registry_listener registry_listener = {
320 	.global = handle_global,
321 	.global_remove = handle_global_remove,
322 };
323 
main(int argc,char ** argv)324 int main(int argc, char **argv) {
325 	if (argc > 1) {
326 		if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-h") == 0) {
327 			printf(usage);
328 			return 0;
329 		}
330 		sleeptime = atoi(argv[1]);
331 		if (argc > 3) {
332 			width = atoi(argv[2]);
333 			height = atoi(argv[3]);
334 		}
335 	}
336 
337 	buffer = calloc(1, sizeof(char));
338 
339 	display = wl_display_connect(NULL);
340 	if (display == NULL) {
341 		fprintf(stderr, "Failed to create display\n");
342 		return EXIT_FAILURE;
343 	}
344 
345 	struct wl_registry *registry = wl_display_get_registry(display);
346 	wl_registry_add_listener(registry, &registry_listener, NULL);
347 	wl_display_roundtrip(display);
348 
349 	if (compositor == NULL) {
350 		fprintf(stderr, "wl-compositor not available\n");
351 		return EXIT_FAILURE;
352 	}
353 	if (wm_base == NULL) {
354 		fprintf(stderr, "xdg-shell not available\n");
355 		return EXIT_FAILURE;
356 	}
357 	if (text_input_manager == NULL) {
358 		fprintf(stderr, "text-input not available\n");
359 		return EXIT_FAILURE;
360 	}
361 
362 	text_input = zwp_text_input_manager_v3_get_text_input(text_input_manager, seat);
363 
364 	zwp_text_input_v3_add_listener(text_input, &text_input_listener, NULL);
365 
366 
367 	wlr_egl_init(&egl, EGL_PLATFORM_WAYLAND_EXT, display, NULL,
368 		WL_SHM_FORMAT_ARGB8888);
369 
370 	struct wl_surface *surface = wl_compositor_create_surface(compositor);
371 	struct xdg_surface *xdg_surface =
372 		xdg_wm_base_get_xdg_surface(wm_base, surface);
373 	struct xdg_toplevel *xdg_toplevel = xdg_surface_get_toplevel(xdg_surface);
374 
375 	xdg_surface_add_listener(xdg_surface, &xdg_surface_listener, NULL);
376 	xdg_toplevel_add_listener(xdg_toplevel, &xdg_toplevel_listener, NULL);
377 
378 	wl_surface_commit(surface);
379 
380 	egl_window = wl_egl_window_create(surface, width, height);
381 	egl_surface = wlr_egl_create_surface(&egl, egl_window);
382 
383 	wl_display_roundtrip(display);
384 
385 	draw();
386 
387 	while (wl_display_dispatch(display) != -1) {
388 		// This space intentionally left blank
389 	}
390 
391 	return EXIT_SUCCESS;
392 }
393