SDL  2.0
SDL_ibus.c
Go to the documentation of this file.
1 /*
2  Simple DirectMedia Layer
3  Copyright (C) 1997-2018 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
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 *
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
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) {
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));
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) {
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 *
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 *
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
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
309 IBus_SetupConnection(SDL_DBusContext *dbus, const char* addr)
310 {
311  const char *client_name = "SDL2_Application";
312  const char *path = NULL;
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);
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
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
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
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 
473 
474  SDL_memset(&ibus_cursor_rect, 0, sizeof(ibus_cursor_rect));
475 }
476 
477 static void
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
488 SDL_IBus_SetFocus(SDL_bool focused)
489 {
490  const char *method = focused ? "FocusIn" : "FocusOut";
491  IBus_SimpleMessage(method);
492 }
493 
494 void
495 SDL_IBus_Reset(void)
496 {
497  IBus_SimpleMessage("Reset");
498 }
499 
500 SDL_bool
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
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
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: */
#define SDL_strlcpy
GLuint64EXT * result
#define SDL_utf8strlen
GLint GLint GLint GLint GLint x
Definition: SDL_opengl.h:1574
SDL_Rect rect
Definition: testrelative.c:27
GLfloat GLfloat p
#define SDL_utf8strlcpy
SDL_version version
Definition: SDL_syswm.h:196
GLint GLenum GLsizei GLsizei GLsizei GLint GLsizei const GLvoid * data
Definition: SDL_opengl.h:1974
SDL_SYSWM_TYPE subsystem
Definition: SDL_syswm.h:197
#define SDL_GetKeyboardFocus
uint32_t Uint32
Definition: SDL_stdinc.h:181
#define SDL_strncmp
GLenum GLsizei len
GLuint const GLchar * name
#define SDL_VERSION(x)
Macro to determine SDL version program was compiled against.
Definition: SDL_version.h:79
#define SDL_memcpy
GLuint64 key
Definition: gl2ext.h:2192
int SDL_SendKeyboardText(const char *text)
Definition: SDL_keyboard.c:789
#define SDL_free
struct _cl_event * event
GLenum const void * addr
#define SDL_zero(x)
Definition: SDL_stdinc.h:416
#define SDL_GetWindowPosition
GLint GLint GLint GLint GLint GLint y
Definition: SDL_opengl.h:1574
int x
Definition: SDL_rect.h:66
GLenum GLuint GLenum GLsizei const GLchar * buf
int w
Definition: SDL_rect.h:67
struct SDL_SysWMinfo::@18::@19 x11
#define SDL_HINT_IME_INTERNAL_EDITING
A variable to control whether certain IMEs should handle text editing internally instead of sending S...
Definition: SDL_hints.h:741
SDL_Keymod
Enumeration of valid key mods (possibly OR&#39;d together).
Definition: SDL_keycode.h:325
SDL_Cursor * cursor
#define SDL_getenv
return Display return Display Bool Bool int int int return Display XEvent Bool(*) XPointer return Display return Display Drawable _Xconst char unsigned int unsigned int return Display Pixmap Pixmap XColor XColor unsigned int unsigned int return Display _Xconst char char int char return Display Visual unsigned int int int char unsigned int unsigned int in i)
Definition: SDL_x11sym.h:50
#define SDL_GetWindowWMInfo
#define NULL
Definition: begin_code.h:164
SDL_bool
Definition: SDL_stdinc.h:139
static char text[MAX_TEXT_LENGTH]
Definition: testime.c:47
SDL_VideoDisplay * SDL_GetDisplayForWindow(SDL_Window *window)
Definition: SDL_video.c:1073
#define SDL_strlen
int h
Definition: SDL_rect.h:67
#define SDL_strdup
The type used to identify a window.
Definition: SDL_sysvideo.h:73
#define SDL_AddHintCallback
#define SDL_DelHintCallback
#define SDL_snprintf
union SDL_SysWMinfo::@18 info
GLsizei const GLchar *const * path
#define SDL_TEXTINPUTEVENT_TEXT_SIZE
Definition: SDL_events.h:217
#define SDL_strcmp
#define SDLCALL
Definition: SDL_internal.h:45
#define SDL_strrchr
#define SDL_GetModState
int y
Definition: SDL_rect.h:66
int SDL_SendEditingText(const char *text, int start, int length)
Definition: SDL_keyboard.c:812
#define SDL_memset
A rectangle, with the origin at the upper left.
Definition: SDL_rect.h:64
#define SDL_TEXTEDITINGEVENT_TEXT_SIZE
Definition: SDL_events.h:202