1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2021 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "../../SDL_internal.h"
22
23#ifdef HAVE_IBUS_IBUS_H
24#include "SDL.h"
25#include "SDL_syswm.h"
26#include "SDL_ibus.h"
27#include "SDL_dbus.h"
28#include "../../video/SDL_sysvideo.h"
29#include "../../events/SDL_keyboard_c.h"
30
31#if SDL_VIDEO_DRIVER_X11
32 #include "../../video/x11/SDL_x11video.h"
33#endif
34
35#include <sys/inotify.h>
36#include <unistd.h>
37#include <fcntl.h>
38
39static const char IBUS_SERVICE[] = "org.freedesktop.IBus";
40static const char IBUS_PATH[] = "/org/freedesktop/IBus";
41static const char IBUS_INTERFACE[] = "org.freedesktop.IBus";
42static const char IBUS_INPUT_INTERFACE[] = "org.freedesktop.IBus.InputContext";
43
44static char *input_ctx_path = NULL;
45static SDL_Rect ibus_cursor_rect = { 0, 0, 0, 0 };
46static DBusConnection *ibus_conn = NULL;
47static char *ibus_addr_file = NULL;
48static int inotify_fd = -1, inotify_wd = -1;
49
50static Uint32
51IBus_ModState(void)
52{
53 Uint32 ibus_mods = 0;
54 SDL_Keymod sdl_mods = SDL_GetModState();
55
56 /* Not sure about MOD3, MOD4 and HYPER mappings */
57 if (sdl_mods & KMOD_LSHIFT) ibus_mods |= IBUS_SHIFT_MASK;
58 if (sdl_mods & KMOD_CAPS) ibus_mods |= IBUS_LOCK_MASK;
59 if (sdl_mods & KMOD_LCTRL) ibus_mods |= IBUS_CONTROL_MASK;
60 if (sdl_mods & KMOD_LALT) ibus_mods |= IBUS_MOD1_MASK;
61 if (sdl_mods & KMOD_NUM) ibus_mods |= IBUS_MOD2_MASK;
62 if (sdl_mods & KMOD_MODE) ibus_mods |= IBUS_MOD5_MASK;
63 if (sdl_mods & KMOD_LGUI) ibus_mods |= IBUS_SUPER_MASK;
64 if (sdl_mods & KMOD_RGUI) ibus_mods |= IBUS_META_MASK;
65
66 return ibus_mods;
67}
68
69static const char *
70IBus_GetVariantText(DBusConnection *conn, DBusMessageIter *iter, SDL_DBusContext *dbus)
71{
72 /* The text we need is nested weirdly, use dbus-monitor to see the structure better */
73 const char *text = NULL;
74 const char *struct_id = NULL;
75 DBusMessageIter sub1, sub2;
76
77 if (dbus->message_iter_get_arg_type(iter) != DBUS_TYPE_VARIANT) {
78 return NULL;
79 }
80
81 dbus->message_iter_recurse(iter, &sub1);
82
83 if (dbus->message_iter_get_arg_type(&sub1) != DBUS_TYPE_STRUCT) {
84 return NULL;
85 }
86
87 dbus->message_iter_recurse(&sub1, &sub2);
88
89 if (dbus->message_iter_get_arg_type(&sub2) != DBUS_TYPE_STRING) {
90 return NULL;
91 }
92
93 dbus->message_iter_get_basic(&sub2, &struct_id);
94 if (!struct_id || SDL_strncmp(struct_id, "IBusText", sizeof("IBusText")) != 0) {
95 return NULL;
96 }
97
98 dbus->message_iter_next(&sub2);
99 dbus->message_iter_next(&sub2);
100
101 if (dbus->message_iter_get_arg_type(&sub2) != DBUS_TYPE_STRING) {
102 return NULL;
103 }
104
105 dbus->message_iter_get_basic(&sub2, &text);
106
107 return text;
108}
109
110static DBusHandlerResult
111IBus_MessageHandler(DBusConnection *conn, DBusMessage *msg, void *user_data)
112{
113 SDL_DBusContext *dbus = (SDL_DBusContext *)user_data;
114
115 if (dbus->message_is_signal(msg, IBUS_INPUT_INTERFACE, "CommitText")) {
116 DBusMessageIter iter;
117 const char *text;
118
119 dbus->message_iter_init(msg, &iter);
120
121 text = IBus_GetVariantText(conn, &iter, dbus);
122 if (text && *text) {
123 char buf[SDL_TEXTINPUTEVENT_TEXT_SIZE];
124 size_t text_bytes = SDL_strlen(text), i = 0;
125
126 while (i < text_bytes) {
127 size_t sz = SDL_utf8strlcpy(buf, text+i, sizeof(buf));
128 SDL_SendKeyboardText(buf);
129
130 i += sz;
131 }
132 }
133
134 return DBUS_HANDLER_RESULT_HANDLED;
135 }
136
137 if (dbus->message_is_signal(msg, IBUS_INPUT_INTERFACE, "UpdatePreeditText")) {
138 DBusMessageIter iter;
139 const char *text;
140
141 dbus->message_iter_init(msg, &iter);
142 text = IBus_GetVariantText(conn, &iter, dbus);
143
144 if (text) {
145 char buf[SDL_TEXTEDITINGEVENT_TEXT_SIZE];
146 size_t text_bytes = SDL_strlen(text), i = 0;
147 size_t cursor = 0;
148
149 do {
150 const size_t sz = SDL_utf8strlcpy(buf, text+i, sizeof(buf));
151 const size_t chars = SDL_utf8strlen(buf);
152
153 SDL_SendEditingText(buf, cursor, chars);
154
155 i += sz;
156 cursor += chars;
157 } while (i < text_bytes);
158 }
159
160 SDL_IBus_UpdateTextRect(NULL);
161
162 return DBUS_HANDLER_RESULT_HANDLED;
163 }
164
165 if (dbus->message_is_signal(msg, IBUS_INPUT_INTERFACE, "HidePreeditText")) {
166 SDL_SendEditingText("", 0, 0);
167 return DBUS_HANDLER_RESULT_HANDLED;
168 }
169
170 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
171}
172
173static char *
174IBus_ReadAddressFromFile(const char *file_path)
175{
176 char addr_buf[1024];
177 SDL_bool success = SDL_FALSE;
178 FILE *addr_file;
179
180 addr_file = fopen(file_path, "r");
181 if (!addr_file) {
182 return NULL;
183 }
184
185 while (fgets(addr_buf, sizeof(addr_buf), addr_file)) {
186 if (SDL_strncmp(addr_buf, "IBUS_ADDRESS=", sizeof("IBUS_ADDRESS=")-1) == 0) {
187 size_t sz = SDL_strlen(addr_buf);
188 if (addr_buf[sz-1] == '\n') addr_buf[sz-1] = 0;
189 if (addr_buf[sz-2] == '\r') addr_buf[sz-2] = 0;
190 success = SDL_TRUE;
191 break;
192 }
193 }
194
195 fclose(addr_file);
196
197 if (success) {
198 return SDL_strdup(addr_buf + (sizeof("IBUS_ADDRESS=") - 1));
199 } else {
200 return NULL;
201 }
202}
203
204static char *
205IBus_GetDBusAddressFilename(void)
206{
207 SDL_DBusContext *dbus;
208 const char *disp_env;
209 char config_dir[PATH_MAX];
210 char *display = NULL;
211 const char *addr;
212 const char *conf_env;
213 char *key;
214 char file_path[PATH_MAX];
215 const char *host;
216 char *disp_num, *screen_num;
217
218 if (ibus_addr_file) {
219 return SDL_strdup(ibus_addr_file);
220 }
221
222 dbus = SDL_DBus_GetContext();
223 if (!dbus) {
224 return NULL;
225 }
226
227 /* Use this environment variable if it exists. */
228 addr = SDL_getenv("IBUS_ADDRESS");
229 if (addr && *addr) {
230 return SDL_strdup(addr);
231 }
232
233 /* Otherwise, we have to get the hostname, display, machine id, config dir
234 and look up the address from a filepath using all those bits, eek. */
235 disp_env = SDL_getenv("DISPLAY");
236
237 if (!disp_env || !*disp_env) {
238 display = SDL_strdup(":0.0");
239 } else {
240 display = SDL_strdup(disp_env);
241 }
242
243 host = display;
244 disp_num = SDL_strrchr(display, ':');
245 screen_num = SDL_strrchr(display, '.');
246
247 if (!disp_num) {
248 SDL_free(display);
249 return NULL;
250 }
251
252 *disp_num = 0;
253 disp_num++;
254
255 if (screen_num) {
256 *screen_num = 0;
257 }
258
259 if (!*host) {
260 const char *session = SDL_getenv("XDG_SESSION_TYPE");
261 if (session != NULL && SDL_strcmp(session, "wayland") == 0) {
262 host = "unix-wayland";
263 } else {
264 host = "unix";
265 }
266 }
267
268 SDL_memset(config_dir, 0, sizeof(config_dir));
269
270 conf_env = SDL_getenv("XDG_CONFIG_HOME");
271 if (conf_env && *conf_env) {
272 SDL_strlcpy(config_dir, conf_env, sizeof(config_dir));
273 } else {
274 const char *home_env = SDL_getenv("HOME");
275 if (!home_env || !*home_env) {
276 SDL_free(display);
277 return NULL;
278 }
279 SDL_snprintf(config_dir, sizeof(config_dir), "%s/.config", home_env);
280 }
281
282 key = dbus->get_local_machine_id();
283
284 SDL_memset(file_path, 0, sizeof(file_path));
285 SDL_snprintf(file_path, sizeof(file_path), "%s/ibus/bus/%s-%s-%s",
286 config_dir, key, host, disp_num);
287 dbus->free(key);
288 SDL_free(display);
289
290 return SDL_strdup(file_path);
291}
292
293static SDL_bool IBus_CheckConnection(SDL_DBusContext *dbus);
294
295static void SDLCALL
296IBus_SetCapabilities(void *data, const char *name, const char *old_val,
297 const char *internal_editing)
298{
299 SDL_DBusContext *dbus = SDL_DBus_GetContext();
300
301 if (IBus_CheckConnection(dbus)) {
302 Uint32 caps = IBUS_CAP_FOCUS;
303 if (!(internal_editing && *internal_editing == '1')) {
304 caps |= IBUS_CAP_PREEDIT_TEXT;
305 }
306
307 SDL_DBus_CallVoidMethodOnConnection(ibus_conn, IBUS_SERVICE, input_ctx_path, IBUS_INPUT_INTERFACE, "SetCapabilities",
308 DBUS_TYPE_UINT32, &caps, DBUS_TYPE_INVALID);
309 }
310}
311
312
313static SDL_bool
314IBus_SetupConnection(SDL_DBusContext *dbus, const char* addr)
315{
316 const char *client_name = "SDL2_Application";
317 const char *path = NULL;
318 SDL_bool result = SDL_FALSE;
319 DBusObjectPathVTable ibus_vtable;
320
321 SDL_zero(ibus_vtable);
322 ibus_vtable.message_function = &IBus_MessageHandler;
323
324 ibus_conn = dbus->connection_open_private(addr, NULL);
325
326 if (!ibus_conn) {
327 return SDL_FALSE;
328 }
329
330 dbus->connection_flush(ibus_conn);
331
332 if (!dbus->bus_register(ibus_conn, NULL)) {
333 ibus_conn = NULL;
334 return SDL_FALSE;
335 }
336
337 dbus->connection_flush(ibus_conn);
338
339 if (SDL_DBus_CallMethodOnConnection(ibus_conn, IBUS_SERVICE, IBUS_PATH, IBUS_INTERFACE, "CreateInputContext",
340 DBUS_TYPE_STRING, &client_name, DBUS_TYPE_INVALID,
341 DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID)) {
342 SDL_free(input_ctx_path);
343 input_ctx_path = SDL_strdup(path);
344 SDL_AddHintCallback(SDL_HINT_IME_INTERNAL_EDITING, IBus_SetCapabilities, NULL);
345
346 dbus->bus_add_match(ibus_conn, "type='signal',interface='org.freedesktop.IBus.InputContext'", NULL);
347 dbus->connection_try_register_object_path(ibus_conn, input_ctx_path, &ibus_vtable, dbus, NULL);
348 dbus->connection_flush(ibus_conn);
349 result = SDL_TRUE;
350 }
351
352 SDL_IBus_SetFocus(SDL_GetKeyboardFocus() != NULL);
353 SDL_IBus_UpdateTextRect(NULL);
354
355 return result;
356}
357
358static SDL_bool
359IBus_CheckConnection(SDL_DBusContext *dbus)
360{
361 if (!dbus) return SDL_FALSE;
362
363 if (ibus_conn && dbus->connection_get_is_connected(ibus_conn)) {
364 return SDL_TRUE;
365 }
366
367 if (inotify_fd > 0 && inotify_wd > 0) {
368 char buf[1024];
369 ssize_t readsize = read(inotify_fd, buf, sizeof(buf));
370 if (readsize > 0) {
371
372 char *p;
373 SDL_bool file_updated = SDL_FALSE;
374
375 for (p = buf; p < buf + readsize; /**/) {
376 struct inotify_event *event = (struct inotify_event*) p;
377 if (event->len > 0) {
378 char *addr_file_no_path = SDL_strrchr(ibus_addr_file, '/');
379 if (!addr_file_no_path) return SDL_FALSE;
380
381 if (SDL_strcmp(addr_file_no_path + 1, event->name) == 0) {
382 file_updated = SDL_TRUE;
383 break;
384 }
385 }
386
387 p += sizeof(struct inotify_event) + event->len;
388 }
389
390 if (file_updated) {
391 char *addr = IBus_ReadAddressFromFile(ibus_addr_file);
392 if (addr) {
393 SDL_bool result = IBus_SetupConnection(dbus, addr);
394 SDL_free(addr);
395 return result;
396 }
397 }
398 }
399 }
400
401 return SDL_FALSE;
402}
403
404SDL_bool
405SDL_IBus_Init(void)
406{
407 SDL_bool result = SDL_FALSE;
408 SDL_DBusContext *dbus = SDL_DBus_GetContext();
409
410 if (dbus) {
411 char *addr_file = IBus_GetDBusAddressFilename();
412 char *addr;
413 char *addr_file_dir;
414
415 if (!addr_file) {
416 return SDL_FALSE;
417 }
418
419 /* !!! FIXME: if ibus_addr_file != NULL, this will overwrite it and leak (twice!) */
420 ibus_addr_file = SDL_strdup(addr_file);
421
422 addr = IBus_ReadAddressFromFile(addr_file);
423 if (!addr) {
424 SDL_free(addr_file);
425 return SDL_FALSE;
426 }
427
428 if (inotify_fd < 0) {
429 inotify_fd = inotify_init();
430 fcntl(inotify_fd, F_SETFL, O_NONBLOCK);
431 }
432
433 addr_file_dir = SDL_strrchr(addr_file, '/');
434 if (addr_file_dir) {
435 *addr_file_dir = 0;
436 }
437
438 inotify_wd = inotify_add_watch(inotify_fd, addr_file, IN_CREATE | IN_MODIFY);
439 SDL_free(addr_file);
440
441 if (addr) {
442 result = IBus_SetupConnection(dbus, addr);
443 SDL_free(addr);
444 }
445 }
446
447 return result;
448}
449
450void
451SDL_IBus_Quit(void)
452{
453 SDL_DBusContext *dbus;
454
455 if (input_ctx_path) {
456 SDL_free(input_ctx_path);
457 input_ctx_path = NULL;
458 }
459
460 if (ibus_addr_file) {
461 SDL_free(ibus_addr_file);
462 ibus_addr_file = NULL;
463 }
464
465 dbus = SDL_DBus_GetContext();
466
467 if (dbus && ibus_conn) {
468 dbus->connection_close(ibus_conn);
469 dbus->connection_unref(ibus_conn);
470 }
471
472 if (inotify_fd > 0 && inotify_wd > 0) {
473 inotify_rm_watch(inotify_fd, inotify_wd);
474 inotify_wd = -1;
475 }
476
477 SDL_DelHintCallback(SDL_HINT_IME_INTERNAL_EDITING, IBus_SetCapabilities, NULL);
478
479 SDL_memset(&ibus_cursor_rect, 0, sizeof(ibus_cursor_rect));
480}
481
482static void
483IBus_SimpleMessage(const char *method)
484{
485 SDL_DBusContext *dbus = SDL_DBus_GetContext();
486
487 if ((input_ctx_path != NULL) && (IBus_CheckConnection(dbus))) {
488 SDL_DBus_CallVoidMethodOnConnection(ibus_conn, IBUS_SERVICE, input_ctx_path, IBUS_INPUT_INTERFACE, method, DBUS_TYPE_INVALID);
489 }
490}
491
492void
493SDL_IBus_SetFocus(SDL_bool focused)
494{
495 const char *method = focused ? "FocusIn" : "FocusOut";
496 IBus_SimpleMessage(method);
497}
498
499void
500SDL_IBus_Reset(void)
501{
502 IBus_SimpleMessage("Reset");
503}
504
505SDL_bool
506SDL_IBus_ProcessKeyEvent(Uint32 keysym, Uint32 keycode)
507{
508 Uint32 result = 0;
509 SDL_DBusContext *dbus = SDL_DBus_GetContext();
510
511 if (IBus_CheckConnection(dbus)) {
512 Uint32 mods = IBus_ModState();
513 if (!SDL_DBus_CallMethodOnConnection(ibus_conn, IBUS_SERVICE, input_ctx_path, IBUS_INPUT_INTERFACE, "ProcessKeyEvent",
514 DBUS_TYPE_UINT32, &keysym, DBUS_TYPE_UINT32, &keycode, DBUS_TYPE_UINT32, &mods, DBUS_TYPE_INVALID,
515 DBUS_TYPE_BOOLEAN, &result, DBUS_TYPE_INVALID)) {
516 result = 0;
517 }
518 }
519
520 SDL_IBus_UpdateTextRect(NULL);
521
522 return result ? SDL_TRUE : SDL_FALSE;
523}
524
525void
526SDL_IBus_UpdateTextRect(SDL_Rect *rect)
527{
528 SDL_Window *focused_win;
529 SDL_SysWMinfo info;
530 int x = 0, y = 0;
531 SDL_DBusContext *dbus;
532
533 if (rect) {
534 SDL_memcpy(&ibus_cursor_rect, rect, sizeof(ibus_cursor_rect));
535 }
536
537 focused_win = SDL_GetKeyboardFocus();
538 if (!focused_win) {
539 return;
540 }
541
542 SDL_VERSION(&info.version);
543 if (!SDL_GetWindowWMInfo(focused_win, &info)) {
544 return;
545 }
546
547 SDL_GetWindowPosition(focused_win, &x, &y);
548
549#if SDL_VIDEO_DRIVER_X11
550 if (info.subsystem == SDL_SYSWM_X11) {
551 SDL_DisplayData *displaydata = (SDL_DisplayData *) SDL_GetDisplayForWindow(focused_win)->driverdata;
552
553 Display *x_disp = info.info.x11.display;
554 Window x_win = info.info.x11.window;
555 int x_screen = displaydata->screen;
556 Window unused;
557
558 X11_XTranslateCoordinates(x_disp, x_win, RootWindow(x_disp, x_screen), 0, 0, &x, &y, &unused);
559 }
560#endif
561
562 x += ibus_cursor_rect.x;
563 y += ibus_cursor_rect.y;
564
565 dbus = SDL_DBus_GetContext();
566
567 if (IBus_CheckConnection(dbus)) {
568 SDL_DBus_CallVoidMethodOnConnection(ibus_conn, IBUS_SERVICE, input_ctx_path, IBUS_INPUT_INTERFACE, "SetCursorLocation",
569 DBUS_TYPE_INT32, &x, DBUS_TYPE_INT32, &y, DBUS_TYPE_INT32, &ibus_cursor_rect.w, DBUS_TYPE_INT32, &ibus_cursor_rect.h, DBUS_TYPE_INVALID);
570 }
571}
572
573void
574SDL_IBus_PumpEvents(void)
575{
576 SDL_DBusContext *dbus = SDL_DBus_GetContext();
577
578 if (IBus_CheckConnection(dbus)) {
579 dbus->connection_read_write(ibus_conn, 0);
580
581 while (dbus->connection_dispatch(ibus_conn) == DBUS_DISPATCH_DATA_REMAINS) {
582 /* Do nothing, actual work happens in IBus_MessageHandler */
583 }
584 }
585}
586
587#endif
588
589/* vi: set ts=4 sw=4 expandtab: */
590