1 /*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2020 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
39 static const char IBUS_SERVICE[] = "org.freedesktop.IBus";
40 static const char IBUS_PATH[] = "/org/freedesktop/IBus";
41 static const char IBUS_INTERFACE[] = "org.freedesktop.IBus";
42 static const char IBUS_INPUT_INTERFACE[] = "org.freedesktop.IBus.InputContext";
43
44 static char *input_ctx_path = NULL;
45 static SDL_Rect ibus_cursor_rect = { 0, 0, 0, 0 };
46 static DBusConnection *ibus_conn = NULL;
47 static char *ibus_addr_file = NULL;
48 static int inotify_fd = -1, inotify_wd = -1;
49
50 static Uint32
IBus_ModState(void)51 IBus_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
69 static const char *
IBus_GetVariantText(DBusConnection * conn,DBusMessageIter * iter,SDL_DBusContext * dbus)70 IBus_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
110 static DBusHandlerResult
IBus_MessageHandler(DBusConnection * conn,DBusMessage * msg,void * user_data)111 IBus_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
173 static char *
IBus_ReadAddressFromFile(const char * file_path)174 IBus_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
204 static char *
IBus_GetDBusAddressFilename(void)205 IBus_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 host = "unix";
261 }
262
263 SDL_memset(config_dir, 0, sizeof(config_dir));
264
265 conf_env = SDL_getenv("XDG_CONFIG_HOME");
266 if (conf_env && *conf_env) {
267 SDL_strlcpy(config_dir, conf_env, sizeof(config_dir));
268 } else {
269 const char *home_env = SDL_getenv("HOME");
270 if (!home_env || !*home_env) {
271 SDL_free(display);
272 return NULL;
273 }
274 SDL_snprintf(config_dir, sizeof(config_dir), "%s/.config", home_env);
275 }
276
277 key = dbus->get_local_machine_id();
278
279 SDL_memset(file_path, 0, sizeof(file_path));
280 SDL_snprintf(file_path, sizeof(file_path), "%s/ibus/bus/%s-%s-%s",
281 config_dir, key, host, disp_num);
282 dbus->free(key);
283 SDL_free(display);
284
285 return SDL_strdup(file_path);
286 }
287
288 static SDL_bool IBus_CheckConnection(SDL_DBusContext *dbus);
289
290 static void SDLCALL
IBus_SetCapabilities(void * data,const char * name,const char * old_val,const char * internal_editing)291 IBus_SetCapabilities(void *data, const char *name, const char *old_val,
292 const char *internal_editing)
293 {
294 SDL_DBusContext *dbus = SDL_DBus_GetContext();
295
296 if (IBus_CheckConnection(dbus)) {
297 Uint32 caps = IBUS_CAP_FOCUS;
298 if (!(internal_editing && *internal_editing == '1')) {
299 caps |= IBUS_CAP_PREEDIT_TEXT;
300 }
301
302 SDL_DBus_CallVoidMethodOnConnection(ibus_conn, IBUS_SERVICE, input_ctx_path, IBUS_INPUT_INTERFACE, "SetCapabilities",
303 DBUS_TYPE_UINT32, &caps, DBUS_TYPE_INVALID);
304 }
305 }
306
307
308 static SDL_bool
IBus_SetupConnection(SDL_DBusContext * dbus,const char * addr)309 IBus_SetupConnection(SDL_DBusContext *dbus, const char* addr)
310 {
311 const char *client_name = "SDL2_Application";
312 const char *path = NULL;
313 SDL_bool result = SDL_FALSE;
314 DBusObjectPathVTable ibus_vtable;
315
316 SDL_zero(ibus_vtable);
317 ibus_vtable.message_function = &IBus_MessageHandler;
318
319 ibus_conn = dbus->connection_open_private(addr, NULL);
320
321 if (!ibus_conn) {
322 return SDL_FALSE;
323 }
324
325 dbus->connection_flush(ibus_conn);
326
327 if (!dbus->bus_register(ibus_conn, NULL)) {
328 ibus_conn = NULL;
329 return SDL_FALSE;
330 }
331
332 dbus->connection_flush(ibus_conn);
333
334 if (SDL_DBus_CallMethodOnConnection(ibus_conn, IBUS_SERVICE, IBUS_PATH, IBUS_INTERFACE, "CreateInputContext",
335 DBUS_TYPE_STRING, &client_name, DBUS_TYPE_INVALID,
336 DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID)) {
337 SDL_free(input_ctx_path);
338 input_ctx_path = SDL_strdup(path);
339 SDL_AddHintCallback(SDL_HINT_IME_INTERNAL_EDITING, IBus_SetCapabilities, NULL);
340
341 dbus->bus_add_match(ibus_conn, "type='signal',interface='org.freedesktop.IBus.InputContext'", NULL);
342 dbus->connection_try_register_object_path(ibus_conn, input_ctx_path, &ibus_vtable, dbus, NULL);
343 dbus->connection_flush(ibus_conn);
344 result = SDL_TRUE;
345 }
346
347 SDL_IBus_SetFocus(SDL_GetKeyboardFocus() != NULL);
348 SDL_IBus_UpdateTextRect(NULL);
349
350 return result;
351 }
352
353 static SDL_bool
IBus_CheckConnection(SDL_DBusContext * dbus)354 IBus_CheckConnection(SDL_DBusContext *dbus)
355 {
356 if (!dbus) return SDL_FALSE;
357
358 if (ibus_conn && dbus->connection_get_is_connected(ibus_conn)) {
359 return SDL_TRUE;
360 }
361
362 if (inotify_fd > 0 && inotify_wd > 0) {
363 char buf[1024];
364 ssize_t readsize = read(inotify_fd, buf, sizeof(buf));
365 if (readsize > 0) {
366
367 char *p;
368 SDL_bool file_updated = SDL_FALSE;
369
370 for (p = buf; p < buf + readsize; /**/) {
371 struct inotify_event *event = (struct inotify_event*) p;
372 if (event->len > 0) {
373 char *addr_file_no_path = SDL_strrchr(ibus_addr_file, '/');
374 if (!addr_file_no_path) return SDL_FALSE;
375
376 if (SDL_strcmp(addr_file_no_path + 1, event->name) == 0) {
377 file_updated = SDL_TRUE;
378 break;
379 }
380 }
381
382 p += sizeof(struct inotify_event) + event->len;
383 }
384
385 if (file_updated) {
386 char *addr = IBus_ReadAddressFromFile(ibus_addr_file);
387 if (addr) {
388 SDL_bool result = IBus_SetupConnection(dbus, addr);
389 SDL_free(addr);
390 return result;
391 }
392 }
393 }
394 }
395
396 return SDL_FALSE;
397 }
398
399 SDL_bool
SDL_IBus_Init(void)400 SDL_IBus_Init(void)
401 {
402 SDL_bool result = SDL_FALSE;
403 SDL_DBusContext *dbus = SDL_DBus_GetContext();
404
405 if (dbus) {
406 char *addr_file = IBus_GetDBusAddressFilename();
407 char *addr;
408 char *addr_file_dir;
409
410 if (!addr_file) {
411 return SDL_FALSE;
412 }
413
414 /* !!! FIXME: if ibus_addr_file != NULL, this will overwrite it and leak (twice!) */
415 ibus_addr_file = SDL_strdup(addr_file);
416
417 addr = IBus_ReadAddressFromFile(addr_file);
418 if (!addr) {
419 SDL_free(addr_file);
420 return SDL_FALSE;
421 }
422
423 if (inotify_fd < 0) {
424 inotify_fd = inotify_init();
425 fcntl(inotify_fd, F_SETFL, O_NONBLOCK);
426 }
427
428 addr_file_dir = SDL_strrchr(addr_file, '/');
429 if (addr_file_dir) {
430 *addr_file_dir = 0;
431 }
432
433 inotify_wd = inotify_add_watch(inotify_fd, addr_file, IN_CREATE | IN_MODIFY);
434 SDL_free(addr_file);
435
436 if (addr) {
437 result = IBus_SetupConnection(dbus, addr);
438 SDL_free(addr);
439 }
440 }
441
442 return result;
443 }
444
445 void
SDL_IBus_Quit(void)446 SDL_IBus_Quit(void)
447 {
448 SDL_DBusContext *dbus;
449
450 if (input_ctx_path) {
451 SDL_free(input_ctx_path);
452 input_ctx_path = NULL;
453 }
454
455 if (ibus_addr_file) {
456 SDL_free(ibus_addr_file);
457 ibus_addr_file = NULL;
458 }
459
460 dbus = SDL_DBus_GetContext();
461
462 if (dbus && ibus_conn) {
463 dbus->connection_close(ibus_conn);
464 dbus->connection_unref(ibus_conn);
465 }
466
467 if (inotify_fd > 0 && inotify_wd > 0) {
468 inotify_rm_watch(inotify_fd, inotify_wd);
469 inotify_wd = -1;
470 }
471
472 SDL_DelHintCallback(SDL_HINT_IME_INTERNAL_EDITING, IBus_SetCapabilities, NULL);
473
474 SDL_memset(&ibus_cursor_rect, 0, sizeof(ibus_cursor_rect));
475 }
476
477 static void
IBus_SimpleMessage(const char * method)478 IBus_SimpleMessage(const char *method)
479 {
480 SDL_DBusContext *dbus = SDL_DBus_GetContext();
481
482 if (IBus_CheckConnection(dbus)) {
483 SDL_DBus_CallVoidMethodOnConnection(ibus_conn, IBUS_SERVICE, input_ctx_path, IBUS_INPUT_INTERFACE, method, DBUS_TYPE_INVALID);
484 }
485 }
486
487 void
SDL_IBus_SetFocus(SDL_bool focused)488 SDL_IBus_SetFocus(SDL_bool focused)
489 {
490 const char *method = focused ? "FocusIn" : "FocusOut";
491 IBus_SimpleMessage(method);
492 }
493
494 void
SDL_IBus_Reset(void)495 SDL_IBus_Reset(void)
496 {
497 IBus_SimpleMessage("Reset");
498 }
499
500 SDL_bool
SDL_IBus_ProcessKeyEvent(Uint32 keysym,Uint32 keycode)501 SDL_IBus_ProcessKeyEvent(Uint32 keysym, Uint32 keycode)
502 {
503 Uint32 result = 0;
504 SDL_DBusContext *dbus = SDL_DBus_GetContext();
505
506 if (IBus_CheckConnection(dbus)) {
507 Uint32 mods = IBus_ModState();
508 if (!SDL_DBus_CallMethodOnConnection(ibus_conn, IBUS_SERVICE, input_ctx_path, IBUS_INPUT_INTERFACE, "ProcessKeyEvent",
509 DBUS_TYPE_UINT32, &keysym, DBUS_TYPE_UINT32, &keycode, DBUS_TYPE_UINT32, &mods, DBUS_TYPE_INVALID,
510 DBUS_TYPE_BOOLEAN, &result, DBUS_TYPE_INVALID)) {
511 result = 0;
512 }
513 }
514
515 SDL_IBus_UpdateTextRect(NULL);
516
517 return result ? SDL_TRUE : SDL_FALSE;
518 }
519
520 void
SDL_IBus_UpdateTextRect(SDL_Rect * rect)521 SDL_IBus_UpdateTextRect(SDL_Rect *rect)
522 {
523 SDL_Window *focused_win;
524 SDL_SysWMinfo info;
525 int x = 0, y = 0;
526 SDL_DBusContext *dbus;
527
528 if (rect) {
529 SDL_memcpy(&ibus_cursor_rect, rect, sizeof(ibus_cursor_rect));
530 }
531
532 focused_win = SDL_GetKeyboardFocus();
533 if (!focused_win) {
534 return;
535 }
536
537 SDL_VERSION(&info.version);
538 if (!SDL_GetWindowWMInfo(focused_win, &info)) {
539 return;
540 }
541
542 SDL_GetWindowPosition(focused_win, &x, &y);
543
544 #if SDL_VIDEO_DRIVER_X11
545 if (info.subsystem == SDL_SYSWM_X11) {
546 SDL_DisplayData *displaydata = (SDL_DisplayData *) SDL_GetDisplayForWindow(focused_win)->driverdata;
547
548 Display *x_disp = info.info.x11.display;
549 Window x_win = info.info.x11.window;
550 int x_screen = displaydata->screen;
551 Window unused;
552
553 X11_XTranslateCoordinates(x_disp, x_win, RootWindow(x_disp, x_screen), 0, 0, &x, &y, &unused);
554 }
555 #endif
556
557 x += ibus_cursor_rect.x;
558 y += ibus_cursor_rect.y;
559
560 dbus = SDL_DBus_GetContext();
561
562 if (IBus_CheckConnection(dbus)) {
563 SDL_DBus_CallVoidMethodOnConnection(ibus_conn, IBUS_SERVICE, input_ctx_path, IBUS_INPUT_INTERFACE, "SetCursorLocation",
564 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);
565 }
566 }
567
568 void
SDL_IBus_PumpEvents(void)569 SDL_IBus_PumpEvents(void)
570 {
571 SDL_DBusContext *dbus = SDL_DBus_GetContext();
572
573 if (IBus_CheckConnection(dbus)) {
574 dbus->connection_read_write(ibus_conn, 0);
575
576 while (dbus->connection_dispatch(ibus_conn) == DBUS_DISPATCH_DATA_REMAINS) {
577 /* Do nothing, actual work happens in IBus_MessageHandler */
578 }
579 }
580 }
581
582 #endif
583
584 /* vi: set ts=4 sw=4 expandtab: */
585