src/core/linux/SDL_ibus.c
author Sam Lantinga <slouken@libsdl.org>
Fri, 04 Jan 2019 22:01:14 -0800
changeset 12503 806492103856
parent 11870 b548450528c9
permissions -rw-r--r--
Updated copyright for 2019
     1 /*
     2   Simple DirectMedia Layer
     3   Copyright (C) 1997-2019 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_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;
   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
   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     
   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
   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: */