SDL  2.0
SDL_ibus.c
Go to the documentation of this file.
1 /*
2  Simple DirectMedia Layer
3  Copyright (C) 1997-2017 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_CallVoidMethod(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  }
345 
346  SDL_IBus_SetFocus(SDL_GetKeyboardFocus() != NULL);
347  SDL_IBus_UpdateTextRect(NULL);
348 
349  return result;
350 }
351 
352 static SDL_bool
353 IBus_CheckConnection(SDL_DBusContext *dbus)
354 {
355  if (!dbus) return SDL_FALSE;
356 
357  if (ibus_conn && dbus->connection_get_is_connected(ibus_conn)) {
358  return SDL_TRUE;
359  }
360 
361  if (inotify_fd > 0 && inotify_wd > 0) {
362  char buf[1024];
363  ssize_t readsize = read(inotify_fd, buf, sizeof(buf));
364  if (readsize > 0) {
365 
366  char *p;
367  SDL_bool file_updated = SDL_FALSE;
368 
369  for (p = buf; p < buf + readsize; /**/) {
370  struct inotify_event *event = (struct inotify_event*) p;
371  if (event->len > 0) {
372  char *addr_file_no_path = SDL_strrchr(ibus_addr_file, '/');
373  if (!addr_file_no_path) return SDL_FALSE;
374 
375  if (SDL_strcmp(addr_file_no_path + 1, event->name) == 0) {
376  file_updated = SDL_TRUE;
377  break;
378  }
379  }
380 
381  p += sizeof(struct inotify_event) + event->len;
382  }
383 
384  if (file_updated) {
385  char *addr = IBus_ReadAddressFromFile(ibus_addr_file);
386  if (addr) {
387  SDL_bool result = IBus_SetupConnection(dbus, addr);
388  SDL_free(addr);
389  return result;
390  }
391  }
392  }
393  }
394 
395  return SDL_FALSE;
396 }
397 
398 SDL_bool
399 SDL_IBus_Init(void)
400 {
401  SDL_bool result = SDL_FALSE;
402  SDL_DBusContext *dbus = SDL_DBus_GetContext();
403 
404  if (dbus) {
405  char *addr_file = IBus_GetDBusAddressFilename();
406  char *addr;
407  char *addr_file_dir;
408 
409  if (!addr_file) {
410  return SDL_FALSE;
411  }
412 
413  /* !!! FIXME: if ibus_addr_file != NULL, this will overwrite it and leak (twice!) */
414  ibus_addr_file = SDL_strdup(addr_file);
415 
416  addr = IBus_ReadAddressFromFile(addr_file);
417  if (!addr) {
418  SDL_free(addr_file);
419  return SDL_FALSE;
420  }
421 
422  if (inotify_fd < 0) {
423  inotify_fd = inotify_init();
424  fcntl(inotify_fd, F_SETFL, O_NONBLOCK);
425  }
426 
427  addr_file_dir = SDL_strrchr(addr_file, '/');
428  if (addr_file_dir) {
429  *addr_file_dir = 0;
430  }
431 
432  inotify_wd = inotify_add_watch(inotify_fd, addr_file, IN_CREATE | IN_MODIFY);
433  SDL_free(addr_file);
434 
435  if (addr) {
436  result = IBus_SetupConnection(dbus, addr);
437  SDL_free(addr);
438  }
439  }
440 
441  return result;
442 }
443 
444 void
445 SDL_IBus_Quit(void)
446 {
447  SDL_DBusContext *dbus;
448 
449  if (input_ctx_path) {
450  SDL_free(input_ctx_path);
451  input_ctx_path = NULL;
452  }
453 
454  if (ibus_addr_file) {
455  SDL_free(ibus_addr_file);
456  ibus_addr_file = NULL;
457  }
458 
459  dbus = SDL_DBus_GetContext();
460 
461  if (dbus && ibus_conn) {
462  dbus->connection_close(ibus_conn);
463  dbus->connection_unref(ibus_conn);
464  }
465 
466  if (inotify_fd > 0 && inotify_wd > 0) {
467  inotify_rm_watch(inotify_fd, inotify_wd);
468  inotify_wd = -1;
469  }
470 
472 
473  SDL_memset(&ibus_cursor_rect, 0, sizeof(ibus_cursor_rect));
474 }
475 
476 static void
477 IBus_SimpleMessage(const char *method)
478 {
479  SDL_DBusContext *dbus = SDL_DBus_GetContext();
480 
481  if (IBus_CheckConnection(dbus)) {
482  SDL_DBus_CallVoidMethodOnConnection(ibus_conn, IBUS_SERVICE, input_ctx_path, IBUS_INPUT_INTERFACE, method, DBUS_TYPE_INVALID);
483  }
484 }
485 
486 void
487 SDL_IBus_SetFocus(SDL_bool focused)
488 {
489  const char *method = focused ? "FocusIn" : "FocusOut";
490  IBus_SimpleMessage(method);
491 }
492 
493 void
494 SDL_IBus_Reset(void)
495 {
496  IBus_SimpleMessage("Reset");
497 }
498 
499 SDL_bool
500 SDL_IBus_ProcessKeyEvent(Uint32 keysym, Uint32 keycode)
501 {
502  Uint32 result = 0;
503  SDL_DBusContext *dbus = SDL_DBus_GetContext();
504 
505  if (IBus_CheckConnection(dbus)) {
506  Uint32 mods = IBus_ModState();
507  if (!SDL_DBus_CallMethodOnConnection(ibus_conn, IBUS_SERVICE, input_ctx_path, IBUS_INPUT_INTERFACE, "ProcessKeyEvent",
508  DBUS_TYPE_UINT32, &keysym, DBUS_TYPE_UINT32, &keycode, DBUS_TYPE_UINT32, &mods, DBUS_TYPE_INVALID,
509  DBUS_TYPE_BOOLEAN, &result, DBUS_TYPE_INVALID)) {
510  result = 0;
511  }
512  }
513 
514  SDL_IBus_UpdateTextRect(NULL);
515 
516  return result ? SDL_TRUE : SDL_FALSE;
517 }
518 
519 void
520 SDL_IBus_UpdateTextRect(SDL_Rect *rect)
521 {
522  SDL_Window *focused_win;
523  SDL_SysWMinfo info;
524  int x = 0, y = 0;
525  SDL_DBusContext *dbus;
526 
527  if (rect) {
528  SDL_memcpy(&ibus_cursor_rect, rect, sizeof(ibus_cursor_rect));
529  }
530 
531  focused_win = SDL_GetKeyboardFocus();
532  if (!focused_win) {
533  return;
534  }
535 
536  SDL_VERSION(&info.version);
537  if (!SDL_GetWindowWMInfo(focused_win, &info)) {
538  return;
539  }
540 
541  SDL_GetWindowPosition(focused_win, &x, &y);
542 
543 #if SDL_VIDEO_DRIVER_X11
544  if (info.subsystem == SDL_SYSWM_X11) {
545  SDL_DisplayData *displaydata = (SDL_DisplayData *) SDL_GetDisplayForWindow(focused_win)->driverdata;
546 
547  Display *x_disp = info.info.x11.display;
548  Window x_win = info.info.x11.window;
549  int x_screen = displaydata->screen;
550  Window unused;
551 
552  X11_XTranslateCoordinates(x_disp, x_win, RootWindow(x_disp, x_screen), 0, 0, &x, &y, &unused);
553  }
554 #endif
555 
556  x += ibus_cursor_rect.x;
557  y += ibus_cursor_rect.y;
558 
559  dbus = SDL_DBus_GetContext();
560 
561  if (IBus_CheckConnection(dbus)) {
562  SDL_DBus_CallVoidMethodOnConnection(ibus_conn, IBUS_SERVICE, input_ctx_path, IBUS_INPUT_INTERFACE, "SetCursorLocation",
563  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);
564  }
565 }
566 
567 void
568 SDL_IBus_PumpEvents(void)
569 {
570  SDL_DBusContext *dbus = SDL_DBus_GetContext();
571 
572  if (IBus_CheckConnection(dbus)) {
573  dbus->connection_read_write(ibus_conn, 0);
574 
575  while (dbus->connection_dispatch(ibus_conn) == DBUS_DISPATCH_DATA_REMAINS) {
576  /* Do nothing, actual work happens in IBus_MessageHandler */
577  }
578  }
579 }
580 
581 #endif
582 
583 /* 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:708
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