src/core/linux/SDL_ibus.c
author Alex Baines <alex@abaines.me.uk>
Wed, 18 Jun 2014 20:11:39 +0100
changeset 8889 26a6243b27c2
child 9009 ddbca09f8f9d
permissions -rw-r--r--
Add IBus IME Support, move DBus code to its own file. (v3.3 squashed)
     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_ibus.h"
    26 #include "SDL_dbus.h"
    27 #include "../../video/SDL_sysvideo.h"
    28 #include "../../events/SDL_keyboard_c.h"
    29 #include <sys/inotify.h>
    30 #include <unistd.h>
    31 #include <fcntl.h>
    32 
    33 static const char IBUS_SERVICE[]         = "org.freedesktop.IBus";
    34 static const char IBUS_PATH[]            = "/org/freedesktop/IBus";
    35 static const char IBUS_INTERFACE[]       = "org.freedesktop.IBus";
    36 static const char IBUS_INPUT_INTERFACE[] = "org.freedesktop.IBus.InputContext";
    37 
    38 static char *input_ctx_path = NULL;
    39 static SDL_Rect ibus_cursor_rect = {0};
    40 static DBusConnection *ibus_conn = NULL;
    41 static char *ibus_addr_file = NULL;
    42 int inotify_fd = -1;
    43 
    44 static Uint32
    45 IBus_ModState(void)
    46 {
    47     Uint32 ibus_mods = 0;
    48     SDL_Keymod sdl_mods = SDL_GetModState();
    49     
    50     /* Not sure about MOD3, MOD4 and HYPER mappings */
    51     if(sdl_mods & KMOD_LSHIFT) ibus_mods |= IBUS_SHIFT_MASK;
    52     if(sdl_mods & KMOD_CAPS)   ibus_mods |= IBUS_LOCK_MASK;
    53     if(sdl_mods & KMOD_LCTRL)  ibus_mods |= IBUS_CONTROL_MASK;
    54     if(sdl_mods & KMOD_LALT)   ibus_mods |= IBUS_MOD1_MASK;
    55     if(sdl_mods & KMOD_NUM)    ibus_mods |= IBUS_MOD2_MASK;
    56     if(sdl_mods & KMOD_MODE)   ibus_mods |= IBUS_MOD5_MASK;
    57     if(sdl_mods & KMOD_LGUI)   ibus_mods |= IBUS_SUPER_MASK;
    58     if(sdl_mods & KMOD_RGUI)   ibus_mods |= IBUS_META_MASK;
    59 
    60     return ibus_mods;
    61 }
    62 
    63 static const char *
    64 IBus_GetVariantText(DBusConnection *conn, DBusMessageIter *iter, SDL_DBusContext *dbus)
    65 {
    66     /* The text we need is nested weirdly, use dbus-monitor to see the structure better */
    67     const char *text = NULL;
    68     DBusMessageIter sub1, sub2;
    69 
    70     if(dbus->message_iter_get_arg_type(iter) != DBUS_TYPE_VARIANT){
    71         return NULL;
    72     }
    73     
    74     dbus->message_iter_recurse(iter, &sub1);
    75     
    76     if(dbus->message_iter_get_arg_type(&sub1) != DBUS_TYPE_STRUCT){
    77         return NULL;
    78     }
    79     
    80     dbus->message_iter_recurse(&sub1, &sub2);
    81     
    82     if(dbus->message_iter_get_arg_type(&sub2) != DBUS_TYPE_STRING){
    83         return NULL;
    84     }
    85     
    86     const char *struct_id = NULL;
    87     dbus->message_iter_get_basic(&sub2, &struct_id);
    88     if(!struct_id || SDL_strncmp(struct_id, "IBusText", sizeof("IBusText")) != 0){
    89         return NULL;
    90     }
    91     
    92     dbus->message_iter_next(&sub2);
    93     dbus->message_iter_next(&sub2);
    94     
    95     if(dbus->message_iter_get_arg_type(&sub2) != DBUS_TYPE_STRING){
    96         return NULL;
    97     }
    98     
    99     dbus->message_iter_get_basic(&sub2, &text);
   100     
   101     return text;
   102 }
   103 
   104 static size_t 
   105 IBus_utf8_strlen(const char *str)
   106 {
   107     size_t utf8_len = 0;
   108     const char *p;
   109     
   110     for(p = str; *p; ++p){
   111         if(!((*p & 0x80) && !(*p & 0x40))){
   112             ++utf8_len;
   113         }
   114     }
   115     
   116     return utf8_len;
   117 }
   118 
   119 static DBusHandlerResult
   120 IBus_MessageFilter(DBusConnection *conn, DBusMessage *msg, void *user_data)
   121 {
   122     SDL_DBusContext *dbus = (SDL_DBusContext *)user_data;
   123         
   124     if(dbus->message_is_signal(msg, IBUS_INPUT_INTERFACE, "CommitText")){
   125         DBusMessageIter iter;
   126         dbus->message_iter_init(msg, &iter);
   127         
   128         const char *text = IBus_GetVariantText(conn, &iter, dbus);
   129         if(text && *text){
   130             char buf[SDL_TEXTEDITINGEVENT_TEXT_SIZE];
   131             size_t text_bytes = SDL_strlen(text), i = 0;
   132             
   133             while(i < text_bytes){
   134                 size_t sz = SDL_utf8strlcpy(buf, text+i, sizeof(buf));
   135                 SDL_SendKeyboardText(buf);
   136                 
   137                 i += sz;
   138             }
   139         }
   140         
   141         return DBUS_HANDLER_RESULT_HANDLED;
   142     }
   143     
   144     if(dbus->message_is_signal(msg, IBUS_INPUT_INTERFACE, "UpdatePreeditText")){
   145         DBusMessageIter iter;
   146         dbus->message_iter_init(msg, &iter);
   147         const char *text = IBus_GetVariantText(conn, &iter, dbus);
   148         
   149         if(text && *text){
   150             char buf[SDL_TEXTEDITINGEVENT_TEXT_SIZE];
   151             size_t text_bytes = SDL_strlen(text), i = 0;
   152             size_t cursor = 0;
   153             
   154             while(i < text_bytes){
   155                 size_t sz = SDL_utf8strlcpy(buf, text+i, sizeof(buf));
   156                 size_t chars = IBus_utf8_strlen(buf);
   157                 
   158                 SDL_SendEditingText(buf, cursor, chars);
   159 
   160                 i += sz;
   161                 cursor += chars;
   162             }
   163         } else {
   164             SDL_SendEditingText("", 0, 0);
   165         }
   166         
   167         SDL_IBus_UpdateTextRect(NULL);
   168         
   169         return DBUS_HANDLER_RESULT_HANDLED;
   170     }
   171     
   172     return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
   173 }
   174 
   175 static char *
   176 IBus_ReadAddressFromFile(const char *file_path)
   177 {
   178     FILE *addr_file = fopen(file_path, "r");
   179     if(!addr_file){
   180         return NULL;
   181     }
   182 
   183     char addr_buf[1024];
   184     SDL_bool success = SDL_FALSE;
   185 
   186     while(fgets(addr_buf, sizeof(addr_buf), addr_file)){
   187         if(SDL_strncmp(addr_buf, "IBUS_ADDRESS=", sizeof("IBUS_ADDRESS=")-1) == 0){
   188             size_t sz = SDL_strlen(addr_buf);
   189             if(addr_buf[sz-1] == '\n') addr_buf[sz-1] = 0;
   190             if(addr_buf[sz-2] == '\r') addr_buf[sz-2] = 0;
   191             success = SDL_TRUE;
   192             break;
   193         }
   194     }
   195 
   196     fclose(addr_file);
   197 
   198     if(success){
   199         return SDL_strdup(addr_buf + (sizeof("IBUS_ADDRESS=") - 1));
   200     } else {
   201         return NULL;
   202     }
   203 }
   204 
   205 static char *
   206 IBus_GetDBusAddressFilename(void)
   207 {
   208     if(ibus_addr_file){
   209         return SDL_strdup(ibus_addr_file);
   210     }
   211     
   212     SDL_DBusContext *dbus = SDL_DBus_GetContext();
   213     
   214     if(!dbus){
   215         return NULL;
   216     }
   217     
   218     /* Use this environment variable if it exists. */
   219     const char *addr = SDL_getenv("IBUS_ADDRESS");
   220     if(addr && *addr){
   221         return SDL_strdup(addr);
   222     }
   223     
   224     /* Otherwise, we have to get the hostname, display, machine id, config dir
   225        and look up the address from a filepath using all those bits, eek. */
   226     const char *disp_env = SDL_getenv("DISPLAY");
   227     char *display = NULL;
   228     
   229     if(!disp_env || !*disp_env){
   230         display = SDL_strdup(":0.0");
   231     } else {
   232         display = SDL_strdup(disp_env);
   233     }
   234     
   235     const char *host = display;
   236     char *disp_num   = SDL_strrchr(display, ':'), 
   237          *screen_num = SDL_strrchr(display, '.');
   238     
   239     if(!disp_num){
   240         SDL_free(display);
   241         return NULL;
   242     }
   243     
   244     *disp_num = 0;
   245     disp_num++;
   246     
   247     if(screen_num){
   248         *screen_num = 0;
   249     }
   250     
   251     if(!*host){
   252         host = "unix";
   253     }
   254         
   255     char config_dir[PATH_MAX];
   256     SDL_memset(config_dir, 0, sizeof(config_dir));
   257     
   258     const char *conf_env = SDL_getenv("XDG_CONFIG_HOME");
   259     if(conf_env && *conf_env){
   260         SDL_strlcpy(config_dir, conf_env, sizeof(config_dir));
   261     } else {
   262         const char *home_env = SDL_getenv("HOME");
   263         if(!home_env || !*home_env){
   264             SDL_free(display);
   265             return NULL;
   266         }
   267         SDL_snprintf(config_dir, sizeof(config_dir), "%s/.config", home_env);
   268     }
   269     
   270     char *key = dbus->get_local_machine_id();
   271     
   272     char file_path[PATH_MAX];
   273     SDL_memset(file_path, 0, sizeof(file_path));
   274     SDL_snprintf(file_path, sizeof(file_path), "%s/ibus/bus/%s-%s-%s", 
   275                                                config_dir, key, host, disp_num);
   276     dbus->free(key);
   277     SDL_free(display);
   278     
   279     return SDL_strdup(file_path);
   280 }
   281 
   282 static SDL_bool
   283 IBus_SetupConnection(SDL_DBusContext *dbus, const char* addr)
   284 {
   285     const char *path = NULL;
   286     SDL_bool result = SDL_FALSE;
   287     
   288     ibus_conn = dbus->connection_open_private(addr, NULL);
   289 
   290     if(!ibus_conn){
   291         return SDL_FALSE;
   292     }
   293 
   294     dbus->connection_flush(ibus_conn);
   295     
   296     if(!dbus->bus_register(ibus_conn, NULL)){
   297         ibus_conn = NULL;
   298         return SDL_FALSE;
   299     }
   300     
   301     dbus->connection_flush(ibus_conn);
   302 
   303     DBusMessage *msg = dbus->message_new_method_call(IBUS_SERVICE,
   304                                                      IBUS_PATH,
   305                                                      IBUS_INTERFACE,
   306                                                      "CreateInputContext");
   307     if(msg){
   308         const char *client_name = "SDL2_Application";
   309         dbus->message_append_args(msg,
   310                                   DBUS_TYPE_STRING, &client_name,
   311                                   DBUS_TYPE_INVALID);
   312     }
   313     
   314     if(msg){
   315         DBusMessage *reply;
   316         
   317         reply = dbus->connection_send_with_reply_and_block(ibus_conn, msg, 1000, NULL);
   318         if(reply){
   319             if(dbus->message_get_args(reply, NULL,
   320                                        DBUS_TYPE_OBJECT_PATH, &path,
   321                                        DBUS_TYPE_INVALID)){
   322                 if(input_ctx_path){
   323                     SDL_free(input_ctx_path);
   324                 }
   325                 input_ctx_path = SDL_strdup(path);
   326                 result = SDL_TRUE;                          
   327             }
   328             dbus->message_unref(reply);
   329         }
   330         dbus->message_unref(msg);
   331     }
   332 
   333     if(result){
   334         DBusMessage *msg = dbus->message_new_method_call(IBUS_SERVICE,
   335                                                          input_ctx_path,
   336                                                          IBUS_INPUT_INTERFACE,
   337                                                          "SetCapabilities");
   338         if(msg){
   339             Uint32 caps = IBUS_CAP_FOCUS | IBUS_CAP_PREEDIT_TEXT;
   340             dbus->message_append_args(msg,
   341                                       DBUS_TYPE_UINT32, &caps,
   342                                       DBUS_TYPE_INVALID);
   343         }
   344         
   345         if(msg){
   346             if(dbus->connection_send(ibus_conn, msg, NULL)){
   347                 dbus->connection_flush(ibus_conn);
   348             }
   349             dbus->message_unref(msg);
   350         }
   351         
   352         dbus->bus_add_match(ibus_conn, "type='signal',interface='org.freedesktop.IBus.InputContext'", NULL);
   353         dbus->connection_add_filter(ibus_conn, &IBus_MessageFilter, dbus, NULL);
   354         dbus->connection_flush(ibus_conn);
   355     }
   356 
   357     SDL_IBus_SetFocus(SDL_GetFocusWindow() != NULL);
   358     SDL_IBus_UpdateTextRect(NULL);
   359     
   360     return result;
   361 }
   362 
   363 static SDL_bool
   364 IBus_CheckConnection(SDL_DBusContext *dbus)
   365 {
   366     if(!dbus) return SDL_FALSE;
   367     
   368     if(ibus_conn && dbus->connection_get_is_connected(ibus_conn)){
   369         return SDL_TRUE;
   370     }
   371     
   372     if(inotify_fd != -1){
   373         char buf[1024];
   374         ssize_t readsize = read(inotify_fd, buf, sizeof(buf));
   375         if(readsize > 0){
   376         
   377             char *p;
   378             SDL_bool file_updated = SDL_FALSE;
   379             
   380             for(p = buf; p < buf + readsize; /**/){
   381                 struct inotify_event *event = (struct inotify_event*) p;
   382                 if(event->len > 0){
   383                     char *addr_file_no_path = SDL_strrchr(ibus_addr_file, '/');
   384                     if(!addr_file_no_path) return SDL_FALSE;
   385                  
   386                     if(SDL_strcmp(addr_file_no_path + 1, event->name) == 0){
   387                         file_updated = SDL_TRUE;
   388                         break;
   389                     }
   390                 }
   391                 
   392                 p += sizeof(struct inotify_event) + event->len;
   393             }
   394             
   395             if(file_updated){
   396                 char *addr = IBus_ReadAddressFromFile(ibus_addr_file);
   397                 if(addr){
   398                     SDL_bool result = IBus_SetupConnection(dbus, addr);
   399                     SDL_free(addr);
   400                     return result;
   401                 }
   402             }
   403         }
   404     }
   405     
   406     return SDL_FALSE;
   407 }
   408 
   409 SDL_bool
   410 SDL_IBus_Init(void)
   411 {
   412     SDL_bool result = SDL_FALSE;
   413     SDL_DBusContext *dbus = SDL_DBus_GetContext();
   414     
   415     if(dbus){
   416         char *addr_file = IBus_GetDBusAddressFilename();
   417         if(!addr_file){
   418             return SDL_FALSE;
   419         }
   420         
   421         ibus_addr_file = SDL_strdup(addr_file);
   422         
   423         char *addr = IBus_ReadAddressFromFile(addr_file);
   424         
   425         inotify_fd = inotify_init();
   426         fcntl(inotify_fd, F_SETFL, O_NONBLOCK);
   427         
   428         char *addr_file_dir = SDL_strrchr(addr_file, '/');
   429         if(addr_file_dir){
   430             *addr_file_dir = 0;
   431         }
   432         
   433         inotify_add_watch(inotify_fd, addr_file, IN_CREATE | IN_MODIFY);
   434         SDL_free(addr_file);
   435         
   436         result = IBus_SetupConnection(dbus, addr);
   437         SDL_free(addr);
   438     }
   439     
   440     return result;
   441 }
   442 
   443 void
   444 SDL_IBus_Quit(void)
   445 {   
   446     if(input_ctx_path){
   447         SDL_free(input_ctx_path);
   448         input_ctx_path = NULL;
   449     }
   450     
   451     if(ibus_addr_file){
   452         SDL_free(ibus_addr_file);
   453         ibus_addr_file = NULL;
   454     }
   455     
   456     SDL_DBusContext *dbus = SDL_DBus_GetContext();
   457     
   458     if(dbus && ibus_conn){
   459         dbus->connection_close(ibus_conn);
   460         dbus->connection_unref(ibus_conn);
   461     }
   462     
   463     SDL_memset(&ibus_cursor_rect, 0, sizeof(ibus_cursor_rect));
   464 }
   465 
   466 static void
   467 IBus_SimpleMessage(const char *method)
   468 {   
   469     SDL_DBusContext *dbus = SDL_DBus_GetContext();
   470     
   471     if(IBus_CheckConnection(dbus)){
   472         DBusMessage *msg = dbus->message_new_method_call(IBUS_SERVICE,
   473                                                          input_ctx_path,
   474                                                          IBUS_INPUT_INTERFACE,
   475                                                          method);
   476         if(msg){
   477             if(dbus->connection_send(ibus_conn, msg, NULL)){
   478                 dbus->connection_flush(ibus_conn);
   479             }
   480             dbus->message_unref(msg);
   481         }
   482     }
   483 }
   484 
   485 void
   486 SDL_IBus_SetFocus(SDL_bool focused)
   487 { 
   488     const char *method = focused ? "FocusIn" : "FocusOut";
   489     IBus_SimpleMessage(method);
   490 }
   491 
   492 void
   493 SDL_IBus_Reset(void)
   494 {
   495     IBus_SimpleMessage("Reset");
   496 }
   497 
   498 SDL_bool
   499 SDL_IBus_ProcessKeyEvent(Uint32 keysym, Uint32 keycode)
   500 { 
   501     SDL_bool result = SDL_FALSE;   
   502     SDL_DBusContext *dbus = SDL_DBus_GetContext();
   503     
   504     if(IBus_CheckConnection(dbus)){
   505         DBusMessage *msg = dbus->message_new_method_call(IBUS_SERVICE,
   506                                                          input_ctx_path,
   507                                                          IBUS_INPUT_INTERFACE,
   508                                                          "ProcessKeyEvent");
   509         if(msg){
   510             Uint32 mods = IBus_ModState();
   511             dbus->message_append_args(msg,
   512                                       DBUS_TYPE_UINT32, &keysym,
   513                                       DBUS_TYPE_UINT32, &keycode,
   514                                       DBUS_TYPE_UINT32, &mods,
   515                                       DBUS_TYPE_INVALID);
   516         }
   517         
   518         if(msg){
   519             DBusMessage *reply;
   520             
   521             reply = dbus->connection_send_with_reply_and_block(ibus_conn, msg, 300, NULL);
   522             if(reply){
   523                 if(!dbus->message_get_args(reply, NULL,
   524                                            DBUS_TYPE_BOOLEAN, &result,
   525                                            DBUS_TYPE_INVALID)){
   526                     result = SDL_FALSE;                         
   527                 }
   528                 dbus->message_unref(reply);
   529             }
   530             dbus->message_unref(msg);
   531         }
   532         
   533     }
   534     
   535     return result;
   536 }
   537 
   538 void
   539 SDL_IBus_UpdateTextRect(SDL_Rect *rect)
   540 {
   541     if(rect){
   542         SDL_memcpy(&ibus_cursor_rect, rect, sizeof(ibus_cursor_rect));
   543     }
   544     
   545     SDL_Window *focused_win = SDL_GetFocusWindow();
   546 
   547     if(!focused_win) return;
   548 
   549     int x = 0, y = 0;
   550     SDL_GetWindowPosition(focused_win, &x, &y);
   551     x += ibus_cursor_rect.x;
   552     y += ibus_cursor_rect.y;
   553         
   554     SDL_DBusContext *dbus = SDL_DBus_GetContext();
   555     
   556     if(IBus_CheckConnection(dbus)){
   557         DBusMessage *msg = dbus->message_new_method_call(IBUS_SERVICE,
   558                                                          input_ctx_path,
   559                                                          IBUS_INPUT_INTERFACE,
   560                                                          "SetCursorLocation");
   561         if(msg){
   562             dbus->message_append_args(msg,
   563                                       DBUS_TYPE_INT32, &x,
   564                                       DBUS_TYPE_INT32, &y,
   565                                       DBUS_TYPE_INT32, &ibus_cursor_rect.w,
   566                                       DBUS_TYPE_INT32, &ibus_cursor_rect.h,
   567                                       DBUS_TYPE_INVALID);
   568         }
   569         
   570         if(msg){
   571             if(dbus->connection_send(ibus_conn, msg, NULL)){
   572                 dbus->connection_flush(ibus_conn);
   573             }
   574             dbus->message_unref(msg);
   575         }
   576     }
   577 }
   578 
   579 void
   580 SDL_IBus_PumpEvents(void)
   581 {
   582     SDL_DBusContext *dbus = SDL_DBus_GetContext();
   583     
   584     if(IBus_CheckConnection(dbus)){
   585         dbus->connection_read_write(ibus_conn, 0);
   586     
   587         while(dbus->connection_dispatch(ibus_conn) == DBUS_DISPATCH_DATA_REMAINS){
   588             /* Do nothing, actual work happens in IBus_MessageFilter */
   589         }
   590     }
   591 }
   592 
   593 #endif