1 /*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2016 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 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 size_t
IBus_utf8_strlen(const char * str)111 IBus_utf8_strlen(const char *str)
112 {
113 size_t utf8_len = 0;
114 const char *p;
115
116 for (p = str; *p; ++p) {
117 if (!((*p & 0x80) && !(*p & 0x40))) {
118 ++utf8_len;
119 }
120 }
121
122 return utf8_len;
123 }
124
125 static DBusHandlerResult
IBus_MessageHandler(DBusConnection * conn,DBusMessage * msg,void * user_data)126 IBus_MessageHandler(DBusConnection *conn, DBusMessage *msg, void *user_data)
127 {
128 SDL_DBusContext *dbus = (SDL_DBusContext *)user_data;
129
130 if (dbus->message_is_signal(msg, IBUS_INPUT_INTERFACE, "CommitText")) {
131 DBusMessageIter iter;
132 const char *text;
133
134 dbus->message_iter_init(msg, &iter);
135
136 text = IBus_GetVariantText(conn, &iter, dbus);
137 if (text && *text) {
138 char buf[SDL_TEXTEDITINGEVENT_TEXT_SIZE];
139 size_t text_bytes = SDL_strlen(text), i = 0;
140
141 while (i < text_bytes) {
142 size_t sz = SDL_utf8strlcpy(buf, text+i, sizeof(buf));
143 SDL_SendKeyboardText(buf);
144
145 i += sz;
146 }
147 }
148
149 return DBUS_HANDLER_RESULT_HANDLED;
150 }
151
152 if (dbus->message_is_signal(msg, IBUS_INPUT_INTERFACE, "UpdatePreeditText")) {
153 DBusMessageIter iter;
154 const char *text;
155
156 dbus->message_iter_init(msg, &iter);
157 text = IBus_GetVariantText(conn, &iter, dbus);
158
159 if (text) {
160 char buf[SDL_TEXTEDITINGEVENT_TEXT_SIZE];
161 size_t text_bytes = SDL_strlen(text), i = 0;
162 size_t cursor = 0;
163
164 do {
165 size_t sz = SDL_utf8strlcpy(buf, text+i, sizeof(buf));
166 size_t chars = IBus_utf8_strlen(buf);
167
168 SDL_SendEditingText(buf, cursor, chars);
169
170 i += sz;
171 cursor += chars;
172 } while (i < text_bytes);
173 }
174
175 SDL_IBus_UpdateTextRect(NULL);
176
177 return DBUS_HANDLER_RESULT_HANDLED;
178 }
179
180 if (dbus->message_is_signal(msg, IBUS_INPUT_INTERFACE, "HidePreeditText")) {
181 SDL_SendEditingText("", 0, 0);
182 return DBUS_HANDLER_RESULT_HANDLED;
183 }
184
185 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
186 }
187
188 static char *
IBus_ReadAddressFromFile(const char * file_path)189 IBus_ReadAddressFromFile(const char *file_path)
190 {
191 char addr_buf[1024];
192 SDL_bool success = SDL_FALSE;
193 FILE *addr_file;
194
195 addr_file = fopen(file_path, "r");
196 if (!addr_file) {
197 return NULL;
198 }
199
200 while (fgets(addr_buf, sizeof(addr_buf), addr_file)) {
201 if (SDL_strncmp(addr_buf, "IBUS_ADDRESS=", sizeof("IBUS_ADDRESS=")-1) == 0) {
202 size_t sz = SDL_strlen(addr_buf);
203 if (addr_buf[sz-1] == '\n') addr_buf[sz-1] = 0;
204 if (addr_buf[sz-2] == '\r') addr_buf[sz-2] = 0;
205 success = SDL_TRUE;
206 break;
207 }
208 }
209
210 fclose(addr_file);
211
212 if (success) {
213 return SDL_strdup(addr_buf + (sizeof("IBUS_ADDRESS=") - 1));
214 } else {
215 return NULL;
216 }
217 }
218
219 static char *
IBus_GetDBusAddressFilename(void)220 IBus_GetDBusAddressFilename(void)
221 {
222 SDL_DBusContext *dbus;
223 const char *disp_env;
224 char config_dir[PATH_MAX];
225 char *display = NULL;
226 const char *addr;
227 const char *conf_env;
228 char *key;
229 char file_path[PATH_MAX];
230 const char *host;
231 char *disp_num, *screen_num;
232
233 if (ibus_addr_file) {
234 return SDL_strdup(ibus_addr_file);
235 }
236
237 dbus = SDL_DBus_GetContext();
238 if (!dbus) {
239 return NULL;
240 }
241
242 /* Use this environment variable if it exists. */
243 addr = SDL_getenv("IBUS_ADDRESS");
244 if (addr && *addr) {
245 return SDL_strdup(addr);
246 }
247
248 /* Otherwise, we have to get the hostname, display, machine id, config dir
249 and look up the address from a filepath using all those bits, eek. */
250 disp_env = SDL_getenv("DISPLAY");
251
252 if (!disp_env || !*disp_env) {
253 display = SDL_strdup(":0.0");
254 } else {
255 display = SDL_strdup(disp_env);
256 }
257
258 host = display;
259 disp_num = SDL_strrchr(display, ':');
260 screen_num = SDL_strrchr(display, '.');
261
262 if (!disp_num) {
263 SDL_free(display);
264 return NULL;
265 }
266
267 *disp_num = 0;
268 disp_num++;
269
270 if (screen_num) {
271 *screen_num = 0;
272 }
273
274 if (!*host) {
275 host = "unix";
276 }
277
278 SDL_memset(config_dir, 0, sizeof(config_dir));
279
280 conf_env = SDL_getenv("XDG_CONFIG_HOME");
281 if (conf_env && *conf_env) {
282 SDL_strlcpy(config_dir, conf_env, sizeof(config_dir));
283 } else {
284 const char *home_env = SDL_getenv("HOME");
285 if (!home_env || !*home_env) {
286 SDL_free(display);
287 return NULL;
288 }
289 SDL_snprintf(config_dir, sizeof(config_dir), "%s/.config", home_env);
290 }
291
292 key = dbus->get_local_machine_id();
293
294 SDL_memset(file_path, 0, sizeof(file_path));
295 SDL_snprintf(file_path, sizeof(file_path), "%s/ibus/bus/%s-%s-%s",
296 config_dir, key, host, disp_num);
297 dbus->free(key);
298 SDL_free(display);
299
300 return SDL_strdup(file_path);
301 }
302
303 static SDL_bool IBus_CheckConnection(SDL_DBusContext *dbus);
304
305 static void
IBus_SetCapabilities(void * data,const char * name,const char * old_val,const char * internal_editing)306 IBus_SetCapabilities(void *data, const char *name, const char *old_val,
307 const char *internal_editing)
308 {
309 SDL_DBusContext *dbus = SDL_DBus_GetContext();
310
311 if (IBus_CheckConnection(dbus)) {
312
313 DBusMessage *msg = dbus->message_new_method_call(IBUS_SERVICE,
314 input_ctx_path,
315 IBUS_INPUT_INTERFACE,
316 "SetCapabilities");
317 if (msg) {
318 Uint32 caps = IBUS_CAP_FOCUS;
319 if (!(internal_editing && *internal_editing == '1')) {
320 caps |= IBUS_CAP_PREEDIT_TEXT;
321 }
322
323 dbus->message_append_args(msg,
324 DBUS_TYPE_UINT32, &caps,
325 DBUS_TYPE_INVALID);
326 }
327
328 if (msg) {
329 if (dbus->connection_send(ibus_conn, msg, NULL)) {
330 dbus->connection_flush(ibus_conn);
331 }
332 dbus->message_unref(msg);
333 }
334 }
335 }
336
337
338 static SDL_bool
IBus_SetupConnection(SDL_DBusContext * dbus,const char * addr)339 IBus_SetupConnection(SDL_DBusContext *dbus, const char* addr)
340 {
341 const char *path = NULL;
342 SDL_bool result = SDL_FALSE;
343 DBusMessage *msg;
344 DBusObjectPathVTable ibus_vtable;
345
346 SDL_zero(ibus_vtable);
347 ibus_vtable.message_function = &IBus_MessageHandler;
348
349 ibus_conn = dbus->connection_open_private(addr, NULL);
350
351 if (!ibus_conn) {
352 return SDL_FALSE;
353 }
354
355 dbus->connection_flush(ibus_conn);
356
357 if (!dbus->bus_register(ibus_conn, NULL)) {
358 ibus_conn = NULL;
359 return SDL_FALSE;
360 }
361
362 dbus->connection_flush(ibus_conn);
363
364 msg = dbus->message_new_method_call(IBUS_SERVICE, IBUS_PATH, IBUS_INTERFACE, "CreateInputContext");
365 if (msg) {
366 const char *client_name = "SDL2_Application";
367 dbus->message_append_args(msg,
368 DBUS_TYPE_STRING, &client_name,
369 DBUS_TYPE_INVALID);
370 }
371
372 if (msg) {
373 DBusMessage *reply;
374
375 reply = dbus->connection_send_with_reply_and_block(ibus_conn, msg, 1000, NULL);
376 if (reply) {
377 if (dbus->message_get_args(reply, NULL,
378 DBUS_TYPE_OBJECT_PATH, &path,
379 DBUS_TYPE_INVALID)) {
380 if (input_ctx_path) {
381 SDL_free(input_ctx_path);
382 }
383 input_ctx_path = SDL_strdup(path);
384 result = SDL_TRUE;
385 }
386 dbus->message_unref(reply);
387 }
388 dbus->message_unref(msg);
389 }
390
391 if (result) {
392 SDL_AddHintCallback(SDL_HINT_IME_INTERNAL_EDITING, &IBus_SetCapabilities, NULL);
393
394 dbus->bus_add_match(ibus_conn, "type='signal',interface='org.freedesktop.IBus.InputContext'", NULL);
395 dbus->connection_try_register_object_path(ibus_conn, input_ctx_path, &ibus_vtable, dbus, NULL);
396 dbus->connection_flush(ibus_conn);
397 }
398
399 SDL_IBus_SetFocus(SDL_GetKeyboardFocus() != NULL);
400 SDL_IBus_UpdateTextRect(NULL);
401
402 return result;
403 }
404
405 static SDL_bool
IBus_CheckConnection(SDL_DBusContext * dbus)406 IBus_CheckConnection(SDL_DBusContext *dbus)
407 {
408 if (!dbus) return SDL_FALSE;
409
410 if (ibus_conn && dbus->connection_get_is_connected(ibus_conn)) {
411 return SDL_TRUE;
412 }
413
414 if (inotify_fd > 0 && inotify_wd > 0) {
415 char buf[1024];
416 ssize_t readsize = read(inotify_fd, buf, sizeof(buf));
417 if (readsize > 0) {
418
419 char *p;
420 SDL_bool file_updated = SDL_FALSE;
421
422 for (p = buf; p < buf + readsize; /**/) {
423 struct inotify_event *event = (struct inotify_event*) p;
424 if (event->len > 0) {
425 char *addr_file_no_path = SDL_strrchr(ibus_addr_file, '/');
426 if (!addr_file_no_path) return SDL_FALSE;
427
428 if (SDL_strcmp(addr_file_no_path + 1, event->name) == 0) {
429 file_updated = SDL_TRUE;
430 break;
431 }
432 }
433
434 p += sizeof(struct inotify_event) + event->len;
435 }
436
437 if (file_updated) {
438 char *addr = IBus_ReadAddressFromFile(ibus_addr_file);
439 if (addr) {
440 SDL_bool result = IBus_SetupConnection(dbus, addr);
441 SDL_free(addr);
442 return result;
443 }
444 }
445 }
446 }
447
448 return SDL_FALSE;
449 }
450
451 SDL_bool
SDL_IBus_Init(void)452 SDL_IBus_Init(void)
453 {
454 SDL_bool result = SDL_FALSE;
455 SDL_DBusContext *dbus = SDL_DBus_GetContext();
456
457 if (dbus) {
458 char *addr_file = IBus_GetDBusAddressFilename();
459 char *addr;
460 char *addr_file_dir;
461
462 if (!addr_file) {
463 return SDL_FALSE;
464 }
465
466 /* !!! FIXME: if ibus_addr_file != NULL, this will overwrite it and leak (twice!) */
467 ibus_addr_file = SDL_strdup(addr_file);
468
469 addr = IBus_ReadAddressFromFile(addr_file);
470 if (!addr) {
471 SDL_free(addr_file);
472 return SDL_FALSE;
473 }
474
475 if (inotify_fd < 0) {
476 inotify_fd = inotify_init();
477 fcntl(inotify_fd, F_SETFL, O_NONBLOCK);
478 }
479
480 addr_file_dir = SDL_strrchr(addr_file, '/');
481 if (addr_file_dir) {
482 *addr_file_dir = 0;
483 }
484
485 inotify_wd = inotify_add_watch(inotify_fd, addr_file, IN_CREATE | IN_MODIFY);
486 SDL_free(addr_file);
487
488 if (addr) {
489 result = IBus_SetupConnection(dbus, addr);
490 SDL_free(addr);
491 }
492 }
493
494 return result;
495 }
496
497 void
SDL_IBus_Quit(void)498 SDL_IBus_Quit(void)
499 {
500 SDL_DBusContext *dbus;
501
502 if (input_ctx_path) {
503 SDL_free(input_ctx_path);
504 input_ctx_path = NULL;
505 }
506
507 if (ibus_addr_file) {
508 SDL_free(ibus_addr_file);
509 ibus_addr_file = NULL;
510 }
511
512 dbus = SDL_DBus_GetContext();
513
514 if (dbus && ibus_conn) {
515 dbus->connection_close(ibus_conn);
516 dbus->connection_unref(ibus_conn);
517 }
518
519 if (inotify_fd > 0 && inotify_wd > 0) {
520 inotify_rm_watch(inotify_fd, inotify_wd);
521 inotify_wd = -1;
522 }
523
524 SDL_DelHintCallback(SDL_HINT_IME_INTERNAL_EDITING, &IBus_SetCapabilities, NULL);
525
526 SDL_memset(&ibus_cursor_rect, 0, sizeof(ibus_cursor_rect));
527 }
528
529 static void
IBus_SimpleMessage(const char * method)530 IBus_SimpleMessage(const char *method)
531 {
532 SDL_DBusContext *dbus = SDL_DBus_GetContext();
533
534 if (IBus_CheckConnection(dbus)) {
535 DBusMessage *msg = dbus->message_new_method_call(IBUS_SERVICE,
536 input_ctx_path,
537 IBUS_INPUT_INTERFACE,
538 method);
539 if (msg) {
540 if (dbus->connection_send(ibus_conn, msg, NULL)) {
541 dbus->connection_flush(ibus_conn);
542 }
543 dbus->message_unref(msg);
544 }
545 }
546 }
547
548 void
SDL_IBus_SetFocus(SDL_bool focused)549 SDL_IBus_SetFocus(SDL_bool focused)
550 {
551 const char *method = focused ? "FocusIn" : "FocusOut";
552 IBus_SimpleMessage(method);
553 }
554
555 void
SDL_IBus_Reset(void)556 SDL_IBus_Reset(void)
557 {
558 IBus_SimpleMessage("Reset");
559 }
560
561 SDL_bool
SDL_IBus_ProcessKeyEvent(Uint32 keysym,Uint32 keycode)562 SDL_IBus_ProcessKeyEvent(Uint32 keysym, Uint32 keycode)
563 {
564 SDL_bool result = SDL_FALSE;
565 SDL_DBusContext *dbus = SDL_DBus_GetContext();
566
567 if (IBus_CheckConnection(dbus)) {
568 DBusMessage *msg = dbus->message_new_method_call(IBUS_SERVICE,
569 input_ctx_path,
570 IBUS_INPUT_INTERFACE,
571 "ProcessKeyEvent");
572 if (msg) {
573 Uint32 mods = IBus_ModState();
574 dbus->message_append_args(msg,
575 DBUS_TYPE_UINT32, &keysym,
576 DBUS_TYPE_UINT32, &keycode,
577 DBUS_TYPE_UINT32, &mods,
578 DBUS_TYPE_INVALID);
579 }
580
581 if (msg) {
582 DBusMessage *reply;
583
584 reply = dbus->connection_send_with_reply_and_block(ibus_conn, msg, 300, NULL);
585 if (reply) {
586 if (!dbus->message_get_args(reply, NULL,
587 DBUS_TYPE_BOOLEAN, &result,
588 DBUS_TYPE_INVALID)) {
589 result = SDL_FALSE;
590 }
591 dbus->message_unref(reply);
592 }
593 dbus->message_unref(msg);
594 }
595
596 }
597
598 SDL_IBus_UpdateTextRect(NULL);
599
600 return result;
601 }
602
603 void
SDL_IBus_UpdateTextRect(SDL_Rect * rect)604 SDL_IBus_UpdateTextRect(SDL_Rect *rect)
605 {
606 SDL_Window *focused_win;
607 SDL_SysWMinfo info;
608 int x = 0, y = 0;
609 SDL_DBusContext *dbus;
610
611 if (rect) {
612 SDL_memcpy(&ibus_cursor_rect, rect, sizeof(ibus_cursor_rect));
613 }
614
615 focused_win = SDL_GetKeyboardFocus();
616 if (!focused_win) {
617 return;
618 }
619
620 SDL_VERSION(&info.version);
621 if (!SDL_GetWindowWMInfo(focused_win, &info)) {
622 return;
623 }
624
625 SDL_GetWindowPosition(focused_win, &x, &y);
626
627 #if SDL_VIDEO_DRIVER_X11
628 if (info.subsystem == SDL_SYSWM_X11) {
629 SDL_DisplayData *displaydata = (SDL_DisplayData *) SDL_GetDisplayForWindow(focused_win)->driverdata;
630
631 Display *x_disp = info.info.x11.display;
632 Window x_win = info.info.x11.window;
633 int x_screen = displaydata->screen;
634 Window unused;
635
636 X11_XTranslateCoordinates(x_disp, x_win, RootWindow(x_disp, x_screen), 0, 0, &x, &y, &unused);
637 }
638 #endif
639
640 x += ibus_cursor_rect.x;
641 y += ibus_cursor_rect.y;
642
643 dbus = SDL_DBus_GetContext();
644
645 if (IBus_CheckConnection(dbus)) {
646 DBusMessage *msg = dbus->message_new_method_call(IBUS_SERVICE,
647 input_ctx_path,
648 IBUS_INPUT_INTERFACE,
649 "SetCursorLocation");
650 if (msg) {
651 dbus->message_append_args(msg,
652 DBUS_TYPE_INT32, &x,
653 DBUS_TYPE_INT32, &y,
654 DBUS_TYPE_INT32, &ibus_cursor_rect.w,
655 DBUS_TYPE_INT32, &ibus_cursor_rect.h,
656 DBUS_TYPE_INVALID);
657 }
658
659 if (msg) {
660 if (dbus->connection_send(ibus_conn, msg, NULL)) {
661 dbus->connection_flush(ibus_conn);
662 }
663 dbus->message_unref(msg);
664 }
665 }
666 }
667
668 void
SDL_IBus_PumpEvents(void)669 SDL_IBus_PumpEvents(void)
670 {
671 SDL_DBusContext *dbus = SDL_DBus_GetContext();
672
673 if (IBus_CheckConnection(dbus)) {
674 dbus->connection_read_write(ibus_conn, 0);
675
676 while (dbus->connection_dispatch(ibus_conn) == DBUS_DISPATCH_DATA_REMAINS) {
677 /* Do nothing, actual work happens in IBus_MessageHandler */
678 }
679 }
680 }
681
682 #endif
683