src/core/linux/SDL_ibus.c
author Alex Baines <alex@abaines.me.uk>
Tue, 19 Aug 2014 23:17:28 +0100
changeset 9096 6454f71d6f15
parent 9095 ed277c1c9e7f
child 9097 56d712662a82
permissions -rw-r--r--
Improvements to the IBus related code:
+ Handle HidePreeditText IBus signal.
+ Use SDL_GetKeyboardFocus instead of SDL_GetFocusWindow.
+ Move the X11 IBus SetFocus calls to the X11_DispatchFocus functions.
+ Simplify the IBus ifdefs when handling X11 KeyEvents.
+ Remove inotify watch when SDL_IBus_Quit is called.
     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, 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     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         }
   170         
   171         SDL_IBus_UpdateTextRect(NULL);
   172         
   173         return DBUS_HANDLER_RESULT_HANDLED;
   174     }
   175     
   176     if(dbus->message_is_signal(msg, IBUS_INPUT_INTERFACE, "HidePreeditText")){
   177     	SDL_SendEditingText("", 0, 0);
   178     	return DBUS_HANDLER_RESULT_HANDLED;
   179     }
   180     
   181     return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
   182 }
   183 
   184 static char *
   185 IBus_ReadAddressFromFile(const char *file_path)
   186 {
   187     FILE *addr_file = fopen(file_path, "r");
   188     if(!addr_file){
   189         return NULL;
   190     }
   191 
   192     char addr_buf[1024];
   193     SDL_bool success = SDL_FALSE;
   194 
   195     while(fgets(addr_buf, sizeof(addr_buf), addr_file)){
   196         if(SDL_strncmp(addr_buf, "IBUS_ADDRESS=", sizeof("IBUS_ADDRESS=")-1) == 0){
   197             size_t sz = SDL_strlen(addr_buf);
   198             if(addr_buf[sz-1] == '\n') addr_buf[sz-1] = 0;
   199             if(addr_buf[sz-2] == '\r') addr_buf[sz-2] = 0;
   200             success = SDL_TRUE;
   201             break;
   202         }
   203     }
   204 
   205     fclose(addr_file);
   206 
   207     if(success){
   208         return SDL_strdup(addr_buf + (sizeof("IBUS_ADDRESS=") - 1));
   209     } else {
   210         return NULL;
   211     }
   212 }
   213 
   214 static char *
   215 IBus_GetDBusAddressFilename(void)
   216 {
   217     if(ibus_addr_file){
   218         return SDL_strdup(ibus_addr_file);
   219     }
   220     
   221     SDL_DBusContext *dbus = SDL_DBus_GetContext();
   222     
   223     if(!dbus){
   224         return NULL;
   225     }
   226     
   227     /* Use this environment variable if it exists. */
   228     const char *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     const char *disp_env = SDL_getenv("DISPLAY");
   236     char *display = NULL;
   237     
   238     if(!disp_env || !*disp_env){
   239         display = SDL_strdup(":0.0");
   240     } else {
   241         display = SDL_strdup(disp_env);
   242     }
   243     
   244     const char *host = display;
   245     char *disp_num   = SDL_strrchr(display, ':'), 
   246          *screen_num = SDL_strrchr(display, '.');
   247     
   248     if(!disp_num){
   249         SDL_free(display);
   250         return NULL;
   251     }
   252     
   253     *disp_num = 0;
   254     disp_num++;
   255     
   256     if(screen_num){
   257         *screen_num = 0;
   258     }
   259     
   260     if(!*host){
   261         host = "unix";
   262     }
   263         
   264     char config_dir[PATH_MAX];
   265     SDL_memset(config_dir, 0, sizeof(config_dir));
   266     
   267     const char *conf_env = SDL_getenv("XDG_CONFIG_HOME");
   268     if(conf_env && *conf_env){
   269         SDL_strlcpy(config_dir, conf_env, sizeof(config_dir));
   270     } else {
   271         const char *home_env = SDL_getenv("HOME");
   272         if(!home_env || !*home_env){
   273             SDL_free(display);
   274             return NULL;
   275         }
   276         SDL_snprintf(config_dir, sizeof(config_dir), "%s/.config", home_env);
   277     }
   278     
   279     char *key = dbus->get_local_machine_id();
   280     
   281     char file_path[PATH_MAX];
   282     SDL_memset(file_path, 0, sizeof(file_path));
   283     SDL_snprintf(file_path, sizeof(file_path), "%s/ibus/bus/%s-%s-%s", 
   284                                                config_dir, key, host, disp_num);
   285     dbus->free(key);
   286     SDL_free(display);
   287     
   288     return SDL_strdup(file_path);
   289 }
   290 
   291 static SDL_bool
   292 IBus_SetupConnection(SDL_DBusContext *dbus, const char* addr)
   293 {
   294     const char *path = NULL;
   295     SDL_bool result = SDL_FALSE;
   296     
   297     ibus_conn = dbus->connection_open_private(addr, NULL);
   298 
   299     if(!ibus_conn){
   300         return SDL_FALSE;
   301     }
   302 
   303     dbus->connection_flush(ibus_conn);
   304     
   305     if(!dbus->bus_register(ibus_conn, NULL)){
   306         ibus_conn = NULL;
   307         return SDL_FALSE;
   308     }
   309     
   310     dbus->connection_flush(ibus_conn);
   311 
   312     DBusMessage *msg = dbus->message_new_method_call(IBUS_SERVICE,
   313                                                      IBUS_PATH,
   314                                                      IBUS_INTERFACE,
   315                                                      "CreateInputContext");
   316     if(msg){
   317         const char *client_name = "SDL2_Application";
   318         dbus->message_append_args(msg,
   319                                   DBUS_TYPE_STRING, &client_name,
   320                                   DBUS_TYPE_INVALID);
   321     }
   322     
   323     if(msg){
   324         DBusMessage *reply;
   325         
   326         reply = dbus->connection_send_with_reply_and_block(ibus_conn, msg, 1000, NULL);
   327         if(reply){
   328             if(dbus->message_get_args(reply, NULL,
   329                                        DBUS_TYPE_OBJECT_PATH, &path,
   330                                        DBUS_TYPE_INVALID)){
   331                 if(input_ctx_path){
   332                     SDL_free(input_ctx_path);
   333                 }
   334                 input_ctx_path = SDL_strdup(path);
   335                 result = SDL_TRUE;                          
   336             }
   337             dbus->message_unref(reply);
   338         }
   339         dbus->message_unref(msg);
   340     }
   341 
   342     if(result){
   343         msg = dbus->message_new_method_call(IBUS_SERVICE,
   344                                             input_ctx_path,
   345                                             IBUS_INPUT_INTERFACE,
   346                                             "SetCapabilities");
   347         if(msg){
   348             Uint32 caps = IBUS_CAP_FOCUS | IBUS_CAP_PREEDIT_TEXT;
   349             dbus->message_append_args(msg,
   350                                       DBUS_TYPE_UINT32, &caps,
   351                                       DBUS_TYPE_INVALID);
   352         }
   353         
   354         if(msg){
   355             if(dbus->connection_send(ibus_conn, msg, NULL)){
   356                 dbus->connection_flush(ibus_conn);
   357             }
   358             dbus->message_unref(msg);
   359         }
   360         
   361         dbus->bus_add_match(ibus_conn, "type='signal',interface='org.freedesktop.IBus.InputContext'", NULL);
   362         dbus->connection_add_filter(ibus_conn, &IBus_MessageFilter, dbus, NULL);
   363         dbus->connection_flush(ibus_conn);
   364     }
   365 
   366     SDL_IBus_SetFocus(SDL_GetKeyboardFocus() != NULL);
   367     SDL_IBus_UpdateTextRect(NULL);
   368     
   369     return result;
   370 }
   371 
   372 static SDL_bool
   373 IBus_CheckConnection(SDL_DBusContext *dbus)
   374 {
   375     if(!dbus) return SDL_FALSE;
   376     
   377     if(ibus_conn && dbus->connection_get_is_connected(ibus_conn)){
   378         return SDL_TRUE;
   379     }
   380     
   381     if(inotify_fd > 0 && inotify_wd > 0){
   382         char buf[1024];
   383         ssize_t readsize = read(inotify_fd, buf, sizeof(buf));
   384         if(readsize > 0){
   385         
   386             char *p;
   387             SDL_bool file_updated = SDL_FALSE;
   388             
   389             for(p = buf; p < buf + readsize; /**/){
   390                 struct inotify_event *event = (struct inotify_event*) p;
   391                 if(event->len > 0){
   392                     char *addr_file_no_path = SDL_strrchr(ibus_addr_file, '/');
   393                     if(!addr_file_no_path) return SDL_FALSE;
   394                  
   395                     if(SDL_strcmp(addr_file_no_path + 1, event->name) == 0){
   396                         file_updated = SDL_TRUE;
   397                         break;
   398                     }
   399                 }
   400                 
   401                 p += sizeof(struct inotify_event) + event->len;
   402             }
   403             
   404             if(file_updated){
   405                 char *addr = IBus_ReadAddressFromFile(ibus_addr_file);
   406                 if(addr){
   407                     SDL_bool result = IBus_SetupConnection(dbus, addr);
   408                     SDL_free(addr);
   409                     return result;
   410                 }
   411             }
   412         }
   413     }
   414     
   415     return SDL_FALSE;
   416 }
   417 
   418 SDL_bool
   419 SDL_IBus_Init(void)
   420 {
   421     SDL_bool result = SDL_FALSE;
   422     SDL_DBusContext *dbus = SDL_DBus_GetContext();
   423     
   424     if(dbus){
   425         char *addr_file = IBus_GetDBusAddressFilename();
   426         if(!addr_file){
   427             return SDL_FALSE;
   428         }
   429         
   430         ibus_addr_file = SDL_strdup(addr_file);
   431         
   432         char *addr = IBus_ReadAddressFromFile(addr_file);
   433         
   434         if(inotify_fd < 0){
   435             inotify_fd = inotify_init();
   436             fcntl(inotify_fd, F_SETFL, O_NONBLOCK);
   437         }
   438         
   439         char *addr_file_dir = SDL_strrchr(addr_file, '/');
   440         if(addr_file_dir){
   441             *addr_file_dir = 0;
   442         }
   443         
   444         inotify_wd = inotify_add_watch(inotify_fd, addr_file, IN_CREATE | IN_MODIFY);
   445         SDL_free(addr_file);
   446         
   447         result = IBus_SetupConnection(dbus, addr);
   448         SDL_free(addr);
   449     }
   450     
   451     return result;
   452 }
   453 
   454 void
   455 SDL_IBus_Quit(void)
   456 {   
   457     if(input_ctx_path){
   458         SDL_free(input_ctx_path);
   459         input_ctx_path = NULL;
   460     }
   461     
   462     if(ibus_addr_file){
   463         SDL_free(ibus_addr_file);
   464         ibus_addr_file = NULL;
   465     }
   466     
   467     SDL_DBusContext *dbus = SDL_DBus_GetContext();
   468     
   469     if(dbus && ibus_conn){
   470         dbus->connection_close(ibus_conn);
   471         dbus->connection_unref(ibus_conn);
   472     }
   473     
   474     if(inotify_fd > 0 && inotify_wd > 0){
   475         inotify_rm_watch(inotify_fd, inotify_wd);
   476         inotify_wd = -1;
   477     }
   478     
   479     SDL_memset(&ibus_cursor_rect, 0, sizeof(ibus_cursor_rect));
   480 }
   481 
   482 static void
   483 IBus_SimpleMessage(const char *method)
   484 {   
   485     SDL_DBusContext *dbus = SDL_DBus_GetContext();
   486     
   487     if(IBus_CheckConnection(dbus)){
   488         DBusMessage *msg = dbus->message_new_method_call(IBUS_SERVICE,
   489                                                          input_ctx_path,
   490                                                          IBUS_INPUT_INTERFACE,
   491                                                          method);
   492         if(msg){
   493             if(dbus->connection_send(ibus_conn, msg, NULL)){
   494                 dbus->connection_flush(ibus_conn);
   495             }
   496             dbus->message_unref(msg);
   497         }
   498     }
   499 }
   500 
   501 void
   502 SDL_IBus_SetFocus(SDL_bool focused)
   503 { 
   504     const char *method = focused ? "FocusIn" : "FocusOut";
   505     IBus_SimpleMessage(method);
   506 }
   507 
   508 void
   509 SDL_IBus_Reset(void)
   510 {
   511     IBus_SimpleMessage("Reset");
   512 }
   513 
   514 SDL_bool
   515 SDL_IBus_ProcessKeyEvent(Uint32 keysym, Uint32 keycode)
   516 { 
   517     SDL_bool result = SDL_FALSE;   
   518     SDL_DBusContext *dbus = SDL_DBus_GetContext();
   519     
   520     if(IBus_CheckConnection(dbus)){
   521         DBusMessage *msg = dbus->message_new_method_call(IBUS_SERVICE,
   522                                                          input_ctx_path,
   523                                                          IBUS_INPUT_INTERFACE,
   524                                                          "ProcessKeyEvent");
   525         if(msg){
   526             Uint32 mods = IBus_ModState();
   527             dbus->message_append_args(msg,
   528                                       DBUS_TYPE_UINT32, &keysym,
   529                                       DBUS_TYPE_UINT32, &keycode,
   530                                       DBUS_TYPE_UINT32, &mods,
   531                                       DBUS_TYPE_INVALID);
   532         }
   533         
   534         if(msg){
   535             DBusMessage *reply;
   536             
   537             reply = dbus->connection_send_with_reply_and_block(ibus_conn, msg, 300, NULL);
   538             if(reply){
   539                 if(!dbus->message_get_args(reply, NULL,
   540                                            DBUS_TYPE_BOOLEAN, &result,
   541                                            DBUS_TYPE_INVALID)){
   542                     result = SDL_FALSE;                         
   543                 }
   544                 dbus->message_unref(reply);
   545             }
   546             dbus->message_unref(msg);
   547         }
   548         
   549     }
   550     
   551     return result;
   552 }
   553 
   554 void
   555 SDL_IBus_UpdateTextRect(SDL_Rect *rect)
   556 {
   557     if(rect){
   558         SDL_memcpy(&ibus_cursor_rect, rect, sizeof(ibus_cursor_rect));
   559     }
   560     
   561     SDL_Window *focused_win = SDL_GetKeyboardFocus();
   562 
   563     if(!focused_win) return;
   564     
   565     SDL_SysWMinfo info;
   566     SDL_VERSION(&info.version);
   567     
   568     if(!SDL_GetWindowWMInfo(focused_win, &info)) return;
   569     
   570     int x = 0, y = 0;
   571     
   572     SDL_GetWindowPosition(focused_win, &x, &y);
   573    
   574 #if SDL_VIDEO_DRIVER_X11    
   575     if(info.subsystem == SDL_SYSWM_X11){
   576         SDL_DisplayData *displaydata =
   577             (SDL_DisplayData *) SDL_GetDisplayForWindow(focused_win)->driverdata;
   578             
   579         Display *x_disp = info.info.x11.display;
   580         Window x_win = info.info.x11.window;
   581         int x_screen = displaydata->screen;
   582         Window unused;
   583             
   584         X11_XTranslateCoordinates(x_disp, x_win, RootWindow(x_disp, x_screen),
   585             0, 0, &x, &y, &unused);
   586     }
   587 #endif
   588 
   589     x += ibus_cursor_rect.x;
   590     y += ibus_cursor_rect.y;
   591         
   592     SDL_DBusContext *dbus = SDL_DBus_GetContext();
   593     
   594     if(IBus_CheckConnection(dbus)){
   595         DBusMessage *msg = dbus->message_new_method_call(IBUS_SERVICE,
   596                                                          input_ctx_path,
   597                                                          IBUS_INPUT_INTERFACE,
   598                                                          "SetCursorLocation");
   599         if(msg){
   600             dbus->message_append_args(msg,
   601                                       DBUS_TYPE_INT32, &x,
   602                                       DBUS_TYPE_INT32, &y,
   603                                       DBUS_TYPE_INT32, &ibus_cursor_rect.w,
   604                                       DBUS_TYPE_INT32, &ibus_cursor_rect.h,
   605                                       DBUS_TYPE_INVALID);
   606         }
   607         
   608         if(msg){
   609             if(dbus->connection_send(ibus_conn, msg, NULL)){
   610                 dbus->connection_flush(ibus_conn);
   611             }
   612             dbus->message_unref(msg);
   613         }
   614     }
   615 }
   616 
   617 void
   618 SDL_IBus_PumpEvents(void)
   619 {
   620     SDL_DBusContext *dbus = SDL_DBus_GetContext();
   621     
   622     if(IBus_CheckConnection(dbus)){
   623         dbus->connection_read_write(ibus_conn, 0);
   624     
   625         while(dbus->connection_dispatch(ibus_conn) == DBUS_DISPATCH_DATA_REMAINS){
   626             /* Do nothing, actual work happens in IBus_MessageFilter */
   627         }
   628     }
   629 }
   630 
   631 #endif