src/core/linux/SDL_ibus.c
author Ryan C. Gordon <icculus@icculus.org>
Fri, 18 Aug 2017 20:00:29 -0400
changeset 11323 46861f3fc187
parent 11284 3db78361e751
child 11567 dc7245e3d1f2
permissions -rw-r--r--
cmake: added a FIXME for later.

Have to figure out what cmake version fixed this and bump the minimum to that.
     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) {
   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 *
   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;
   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     }
   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     
   471     SDL_DelHintCallback(SDL_HINT_IME_INTERNAL_EDITING, IBus_SetCapabilities, NULL);
   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);
   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: */