src/core/linux/SDL_ibus.c
author Alex Baines <alex@abaines.me.uk>
Tue, 19 Aug 2014 22:28:53 +0100
changeset 9095 ed277c1c9e7f
parent 9009 ddbca09f8f9d
child 9096 6454f71d6f15
permissions -rw-r--r--
Take the window border size into account when positioning the IBus candidate list.
     1 /*
     2   Simple DirectMedia Layer
     3   Copyright (C) 1997-2014 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};
    46 static DBusConnection *ibus_conn = NULL;
    47 static char *ibus_addr_file = NULL;
    48 int inotify_fd = -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     DBusMessageIter sub1, sub2;
    75 
    76     if(dbus->message_iter_get_arg_type(iter) != DBUS_TYPE_VARIANT){
    77         return NULL;
    78     }
    79     
    80     dbus->message_iter_recurse(iter, &sub1);
    81     
    82     if(dbus->message_iter_get_arg_type(&sub1) != DBUS_TYPE_STRUCT){
    83         return NULL;
    84     }
    85     
    86     dbus->message_iter_recurse(&sub1, &sub2);
    87     
    88     if(dbus->message_iter_get_arg_type(&sub2) != DBUS_TYPE_STRING){
    89         return NULL;
    90     }
    91     
    92     const char *struct_id = NULL;
    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 
   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
   126 IBus_MessageFilter(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         dbus->message_iter_init(msg, &iter);
   133         
   134         const char *text = IBus_GetVariantText(conn, &iter, dbus);
   135         if(text && *text){
   136             char buf[SDL_TEXTEDITINGEVENT_TEXT_SIZE];
   137             size_t text_bytes = SDL_strlen(text), i = 0;
   138             
   139             while(i < text_bytes){
   140                 size_t sz = SDL_utf8strlcpy(buf, text+i, sizeof(buf));
   141                 SDL_SendKeyboardText(buf);
   142                 
   143                 i += sz;
   144             }
   145         }
   146         
   147         return DBUS_HANDLER_RESULT_HANDLED;
   148     }
   149     
   150     if(dbus->message_is_signal(msg, IBUS_INPUT_INTERFACE, "UpdatePreeditText")){
   151         DBusMessageIter iter;
   152         dbus->message_iter_init(msg, &iter);
   153         const char *text = IBus_GetVariantText(conn, &iter, dbus);
   154         
   155         if(text && *text){
   156             char buf[SDL_TEXTEDITINGEVENT_TEXT_SIZE];
   157             size_t text_bytes = SDL_strlen(text), i = 0;
   158             size_t cursor = 0;
   159             
   160             while(i < text_bytes){
   161                 size_t sz = SDL_utf8strlcpy(buf, text+i, sizeof(buf));
   162                 size_t chars = IBus_utf8_strlen(buf);
   163                 
   164                 SDL_SendEditingText(buf, cursor, chars);
   165 
   166                 i += sz;
   167                 cursor += chars;
   168             }
   169         } else {
   170             SDL_SendEditingText("", 0, 0);
   171         }
   172         
   173         SDL_IBus_UpdateTextRect(NULL);
   174         
   175         return DBUS_HANDLER_RESULT_HANDLED;
   176     }
   177     
   178     return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
   179 }
   180 
   181 static char *
   182 IBus_ReadAddressFromFile(const char *file_path)
   183 {
   184     FILE *addr_file = fopen(file_path, "r");
   185     if(!addr_file){
   186         return NULL;
   187     }
   188 
   189     char addr_buf[1024];
   190     SDL_bool success = SDL_FALSE;
   191 
   192     while(fgets(addr_buf, sizeof(addr_buf), addr_file)){
   193         if(SDL_strncmp(addr_buf, "IBUS_ADDRESS=", sizeof("IBUS_ADDRESS=")-1) == 0){
   194             size_t sz = SDL_strlen(addr_buf);
   195             if(addr_buf[sz-1] == '\n') addr_buf[sz-1] = 0;
   196             if(addr_buf[sz-2] == '\r') addr_buf[sz-2] = 0;
   197             success = SDL_TRUE;
   198             break;
   199         }
   200     }
   201 
   202     fclose(addr_file);
   203 
   204     if(success){
   205         return SDL_strdup(addr_buf + (sizeof("IBUS_ADDRESS=") - 1));
   206     } else {
   207         return NULL;
   208     }
   209 }
   210 
   211 static char *
   212 IBus_GetDBusAddressFilename(void)
   213 {
   214     if(ibus_addr_file){
   215         return SDL_strdup(ibus_addr_file);
   216     }
   217     
   218     SDL_DBusContext *dbus = SDL_DBus_GetContext();
   219     
   220     if(!dbus){
   221         return NULL;
   222     }
   223     
   224     /* Use this environment variable if it exists. */
   225     const char *addr = SDL_getenv("IBUS_ADDRESS");
   226     if(addr && *addr){
   227         return SDL_strdup(addr);
   228     }
   229     
   230     /* Otherwise, we have to get the hostname, display, machine id, config dir
   231        and look up the address from a filepath using all those bits, eek. */
   232     const char *disp_env = SDL_getenv("DISPLAY");
   233     char *display = NULL;
   234     
   235     if(!disp_env || !*disp_env){
   236         display = SDL_strdup(":0.0");
   237     } else {
   238         display = SDL_strdup(disp_env);
   239     }
   240     
   241     const char *host = display;
   242     char *disp_num   = SDL_strrchr(display, ':'), 
   243          *screen_num = SDL_strrchr(display, '.');
   244     
   245     if(!disp_num){
   246         SDL_free(display);
   247         return NULL;
   248     }
   249     
   250     *disp_num = 0;
   251     disp_num++;
   252     
   253     if(screen_num){
   254         *screen_num = 0;
   255     }
   256     
   257     if(!*host){
   258         host = "unix";
   259     }
   260         
   261     char config_dir[PATH_MAX];
   262     SDL_memset(config_dir, 0, sizeof(config_dir));
   263     
   264     const char *conf_env = SDL_getenv("XDG_CONFIG_HOME");
   265     if(conf_env && *conf_env){
   266         SDL_strlcpy(config_dir, conf_env, sizeof(config_dir));
   267     } else {
   268         const char *home_env = SDL_getenv("HOME");
   269         if(!home_env || !*home_env){
   270             SDL_free(display);
   271             return NULL;
   272         }
   273         SDL_snprintf(config_dir, sizeof(config_dir), "%s/.config", home_env);
   274     }
   275     
   276     char *key = dbus->get_local_machine_id();
   277     
   278     char file_path[PATH_MAX];
   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
   289 IBus_SetupConnection(SDL_DBusContext *dbus, const char* addr)
   290 {
   291     const char *path = NULL;
   292     SDL_bool result = SDL_FALSE;
   293     
   294     ibus_conn = dbus->connection_open_private(addr, NULL);
   295 
   296     if(!ibus_conn){
   297         return SDL_FALSE;
   298     }
   299 
   300     dbus->connection_flush(ibus_conn);
   301     
   302     if(!dbus->bus_register(ibus_conn, NULL)){
   303         ibus_conn = NULL;
   304         return SDL_FALSE;
   305     }
   306     
   307     dbus->connection_flush(ibus_conn);
   308 
   309     DBusMessage *msg = dbus->message_new_method_call(IBUS_SERVICE,
   310                                                      IBUS_PATH,
   311                                                      IBUS_INTERFACE,
   312                                                      "CreateInputContext");
   313     if(msg){
   314         const char *client_name = "SDL2_Application";
   315         dbus->message_append_args(msg,
   316                                   DBUS_TYPE_STRING, &client_name,
   317                                   DBUS_TYPE_INVALID);
   318     }
   319     
   320     if(msg){
   321         DBusMessage *reply;
   322         
   323         reply = dbus->connection_send_with_reply_and_block(ibus_conn, msg, 1000, NULL);
   324         if(reply){
   325             if(dbus->message_get_args(reply, NULL,
   326                                        DBUS_TYPE_OBJECT_PATH, &path,
   327                                        DBUS_TYPE_INVALID)){
   328                 if(input_ctx_path){
   329                     SDL_free(input_ctx_path);
   330                 }
   331                 input_ctx_path = SDL_strdup(path);
   332                 result = SDL_TRUE;                          
   333             }
   334             dbus->message_unref(reply);
   335         }
   336         dbus->message_unref(msg);
   337     }
   338 
   339     if(result){
   340         msg = dbus->message_new_method_call(IBUS_SERVICE,
   341                                             input_ctx_path,
   342                                             IBUS_INPUT_INTERFACE,
   343                                             "SetCapabilities");
   344         if(msg){
   345             Uint32 caps = IBUS_CAP_FOCUS | IBUS_CAP_PREEDIT_TEXT;
   346             dbus->message_append_args(msg,
   347                                       DBUS_TYPE_UINT32, &caps,
   348                                       DBUS_TYPE_INVALID);
   349         }
   350         
   351         if(msg){
   352             if(dbus->connection_send(ibus_conn, msg, NULL)){
   353                 dbus->connection_flush(ibus_conn);
   354             }
   355             dbus->message_unref(msg);
   356         }
   357         
   358         dbus->bus_add_match(ibus_conn, "type='signal',interface='org.freedesktop.IBus.InputContext'", NULL);
   359         dbus->connection_add_filter(ibus_conn, &IBus_MessageFilter, dbus, NULL);
   360         dbus->connection_flush(ibus_conn);
   361     }
   362 
   363     SDL_IBus_SetFocus(SDL_GetFocusWindow() != NULL);
   364     SDL_IBus_UpdateTextRect(NULL);
   365     
   366     return result;
   367 }
   368 
   369 static SDL_bool
   370 IBus_CheckConnection(SDL_DBusContext *dbus)
   371 {
   372     if(!dbus) return SDL_FALSE;
   373     
   374     if(ibus_conn && dbus->connection_get_is_connected(ibus_conn)){
   375         return SDL_TRUE;
   376     }
   377     
   378     if(inotify_fd != -1){
   379         char buf[1024];
   380         ssize_t readsize = read(inotify_fd, buf, sizeof(buf));
   381         if(readsize > 0){
   382         
   383             char *p;
   384             SDL_bool file_updated = SDL_FALSE;
   385             
   386             for(p = buf; p < buf + readsize; /**/){
   387                 struct inotify_event *event = (struct inotify_event*) p;
   388                 if(event->len > 0){
   389                     char *addr_file_no_path = SDL_strrchr(ibus_addr_file, '/');
   390                     if(!addr_file_no_path) return SDL_FALSE;
   391                  
   392                     if(SDL_strcmp(addr_file_no_path + 1, event->name) == 0){
   393                         file_updated = SDL_TRUE;
   394                         break;
   395                     }
   396                 }
   397                 
   398                 p += sizeof(struct inotify_event) + event->len;
   399             }
   400             
   401             if(file_updated){
   402                 char *addr = IBus_ReadAddressFromFile(ibus_addr_file);
   403                 if(addr){
   404                     SDL_bool result = IBus_SetupConnection(dbus, addr);
   405                     SDL_free(addr);
   406                     return result;
   407                 }
   408             }
   409         }
   410     }
   411     
   412     return SDL_FALSE;
   413 }
   414 
   415 SDL_bool
   416 SDL_IBus_Init(void)
   417 {
   418     SDL_bool result = SDL_FALSE;
   419     SDL_DBusContext *dbus = SDL_DBus_GetContext();
   420     
   421     if(dbus){
   422         char *addr_file = IBus_GetDBusAddressFilename();
   423         if(!addr_file){
   424             return SDL_FALSE;
   425         }
   426         
   427         ibus_addr_file = SDL_strdup(addr_file);
   428         
   429         char *addr = IBus_ReadAddressFromFile(addr_file);
   430         
   431         inotify_fd = inotify_init();
   432         fcntl(inotify_fd, F_SETFL, O_NONBLOCK);
   433         
   434         char *addr_file_dir = SDL_strrchr(addr_file, '/');
   435         if(addr_file_dir){
   436             *addr_file_dir = 0;
   437         }
   438         
   439         inotify_add_watch(inotify_fd, addr_file, IN_CREATE | IN_MODIFY);
   440         SDL_free(addr_file);
   441         
   442         result = IBus_SetupConnection(dbus, addr);
   443         SDL_free(addr);
   444     }
   445     
   446     return result;
   447 }
   448 
   449 void
   450 SDL_IBus_Quit(void)
   451 {   
   452     if(input_ctx_path){
   453         SDL_free(input_ctx_path);
   454         input_ctx_path = NULL;
   455     }
   456     
   457     if(ibus_addr_file){
   458         SDL_free(ibus_addr_file);
   459         ibus_addr_file = NULL;
   460     }
   461     
   462     SDL_DBusContext *dbus = SDL_DBus_GetContext();
   463     
   464     if(dbus && ibus_conn){
   465         dbus->connection_close(ibus_conn);
   466         dbus->connection_unref(ibus_conn);
   467     }
   468     
   469     SDL_memset(&ibus_cursor_rect, 0, sizeof(ibus_cursor_rect));
   470 }
   471 
   472 static void
   473 IBus_SimpleMessage(const char *method)
   474 {   
   475     SDL_DBusContext *dbus = SDL_DBus_GetContext();
   476     
   477     if(IBus_CheckConnection(dbus)){
   478         DBusMessage *msg = dbus->message_new_method_call(IBUS_SERVICE,
   479                                                          input_ctx_path,
   480                                                          IBUS_INPUT_INTERFACE,
   481                                                          method);
   482         if(msg){
   483             if(dbus->connection_send(ibus_conn, msg, NULL)){
   484                 dbus->connection_flush(ibus_conn);
   485             }
   486             dbus->message_unref(msg);
   487         }
   488     }
   489 }
   490 
   491 void
   492 SDL_IBus_SetFocus(SDL_bool focused)
   493 { 
   494     const char *method = focused ? "FocusIn" : "FocusOut";
   495     IBus_SimpleMessage(method);
   496 }
   497 
   498 void
   499 SDL_IBus_Reset(void)
   500 {
   501     IBus_SimpleMessage("Reset");
   502 }
   503 
   504 SDL_bool
   505 SDL_IBus_ProcessKeyEvent(Uint32 keysym, Uint32 keycode)
   506 { 
   507     SDL_bool result = SDL_FALSE;   
   508     SDL_DBusContext *dbus = SDL_DBus_GetContext();
   509     
   510     if(IBus_CheckConnection(dbus)){
   511         DBusMessage *msg = dbus->message_new_method_call(IBUS_SERVICE,
   512                                                          input_ctx_path,
   513                                                          IBUS_INPUT_INTERFACE,
   514                                                          "ProcessKeyEvent");
   515         if(msg){
   516             Uint32 mods = IBus_ModState();
   517             dbus->message_append_args(msg,
   518                                       DBUS_TYPE_UINT32, &keysym,
   519                                       DBUS_TYPE_UINT32, &keycode,
   520                                       DBUS_TYPE_UINT32, &mods,
   521                                       DBUS_TYPE_INVALID);
   522         }
   523         
   524         if(msg){
   525             DBusMessage *reply;
   526             
   527             reply = dbus->connection_send_with_reply_and_block(ibus_conn, msg, 300, NULL);
   528             if(reply){
   529                 if(!dbus->message_get_args(reply, NULL,
   530                                            DBUS_TYPE_BOOLEAN, &result,
   531                                            DBUS_TYPE_INVALID)){
   532                     result = SDL_FALSE;                         
   533                 }
   534                 dbus->message_unref(reply);
   535             }
   536             dbus->message_unref(msg);
   537         }
   538         
   539     }
   540     
   541     return result;
   542 }
   543 
   544 void
   545 SDL_IBus_UpdateTextRect(SDL_Rect *rect)
   546 {
   547     if(rect){
   548         SDL_memcpy(&ibus_cursor_rect, rect, sizeof(ibus_cursor_rect));
   549     }
   550     
   551     SDL_Window *focused_win = SDL_GetFocusWindow();
   552 
   553     if(!focused_win) return;
   554     
   555     SDL_SysWMinfo info;
   556     SDL_VERSION(&info.version);
   557     
   558     if(!SDL_GetWindowWMInfo(focused_win, &info)) return;
   559     
   560     int x = 0, y = 0;
   561     
   562     SDL_GetWindowPosition(focused_win, &x, &y);
   563    
   564 #if SDL_VIDEO_DRIVER_X11    
   565     if(info.subsystem == SDL_SYSWM_X11){
   566         SDL_DisplayData *displaydata =
   567             (SDL_DisplayData *) SDL_GetDisplayForWindow(focused_win)->driverdata;
   568             
   569         Display *x_disp = info.info.x11.display;
   570         Window x_win = info.info.x11.window;
   571         int x_screen = displaydata->screen;
   572         Window unused;
   573             
   574         X11_XTranslateCoordinates(x_disp, x_win, RootWindow(x_disp, x_screen),
   575             0, 0, &x, &y, &unused);
   576     }
   577 #endif
   578 
   579     x += ibus_cursor_rect.x;
   580     y += ibus_cursor_rect.y;
   581         
   582     SDL_DBusContext *dbus = SDL_DBus_GetContext();
   583     
   584     if(IBus_CheckConnection(dbus)){
   585         DBusMessage *msg = dbus->message_new_method_call(IBUS_SERVICE,
   586                                                          input_ctx_path,
   587                                                          IBUS_INPUT_INTERFACE,
   588                                                          "SetCursorLocation");
   589         if(msg){
   590             dbus->message_append_args(msg,
   591                                       DBUS_TYPE_INT32, &x,
   592                                       DBUS_TYPE_INT32, &y,
   593                                       DBUS_TYPE_INT32, &ibus_cursor_rect.w,
   594                                       DBUS_TYPE_INT32, &ibus_cursor_rect.h,
   595                                       DBUS_TYPE_INVALID);
   596         }
   597         
   598         if(msg){
   599             if(dbus->connection_send(ibus_conn, msg, NULL)){
   600                 dbus->connection_flush(ibus_conn);
   601             }
   602             dbus->message_unref(msg);
   603         }
   604     }
   605 }
   606 
   607 void
   608 SDL_IBus_PumpEvents(void)
   609 {
   610     SDL_DBusContext *dbus = SDL_DBus_GetContext();
   611     
   612     if(IBus_CheckConnection(dbus)){
   613         dbus->connection_read_write(ibus_conn, 0);
   614     
   615         while(dbus->connection_dispatch(ibus_conn) == DBUS_DISPATCH_DATA_REMAINS){
   616             /* Do nothing, actual work happens in IBus_MessageFilter */
   617         }
   618     }
   619 }
   620 
   621 #endif