src/core/linux/SDL_fcitx.c
author Sam Lantinga <slouken@libsdl.org>
Fri, 07 Oct 2016 18:57:40 -0700
changeset 10496 6660aa9120d6
child 10500 c3874aa1f2d1
permissions -rw-r--r--
Fixed bug 2824 - Add Fcitx Input Method Support

Weitian Leung

Just moved ibus direct call to SDL_IME_* related functions, and adds fcitx IME support (uses DBus, too),
enable with env: SDL_IM_MODULE=fcitx (ibus still the default one)
     1 /*
     2   Simple DirectMedia Layer
     3   Copyright (C) 1997-2016 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 
    22 #include <fcitx/frontend.h>
    23 #include <unistd.h>
    24 
    25 #include "SDL_fcitx.h"
    26 #include "SDL_keycode.h"
    27 #include "SDL_keyboard.h"
    28 #include "../../events/SDL_keyboard_c.h"
    29 #include "SDL_dbus.h"
    30 #include "SDL_syswm.h"
    31 #if SDL_VIDEO_DRIVER_X11
    32 #  include "../../video/x11/SDL_x11video.h"
    33 #endif
    34 #include "SDL_hints.h"
    35 
    36 #define FCITX_DBUS_SERVICE "org.fcitx.Fcitx"
    37 
    38 #define FCITX_IM_DBUS_PATH "/inputmethod"
    39 #define FCITX_IC_DBUS_PATH "/inputcontext_%d"
    40 
    41 #define FCITX_IM_DBUS_INTERFACE "org.fcitx.Fcitx.InputMethod"
    42 #define FCITX_IC_DBUS_INTERFACE "org.fcitx.Fcitx.InputContext"
    43 
    44 #define IC_NAME_MAX 64
    45 #define DBUS_TIMEOUT 500
    46 
    47 typedef struct _FcitxClient
    48 {
    49     SDL_DBusContext *dbus;
    50 
    51     char servicename[IC_NAME_MAX];
    52     char icname[IC_NAME_MAX];
    53 
    54     int id;
    55 
    56     SDL_Rect cursor_rect;
    57 } FcitxClient;
    58 
    59 static FcitxClient fcitx_client;
    60 
    61 static int
    62 GetDisplayNumber()
    63 {
    64     const char *display = SDL_getenv("DISPLAY");
    65     const char *p = NULL;;
    66     int number = 0;
    67 
    68     if (display == NULL)
    69         return 0;
    70 
    71     display = SDL_strchr(display, ':');
    72     if (display == NULL)
    73         return 0;
    74 
    75     display++;
    76     p = SDL_strchr(display, '.');
    77     if (p == NULL && display != NULL) {
    78         number = SDL_strtod(display, NULL);
    79     } else {
    80         char *buffer = SDL_strdup(display);
    81         buffer[p - display] = '\0';
    82         number = SDL_strtod(buffer, NULL);
    83         SDL_free(buffer);
    84     }
    85 
    86     return number;
    87 }
    88 
    89 static char*
    90 GetAppName()
    91 {
    92 #if defined(__LINUX__) || defined(__FREEBSD__)
    93     char *spot;
    94     char procfile[1024];
    95     char linkfile[1024];
    96     int linksize;
    97 
    98 #if defined(__LINUX__)
    99     SDL_snprintf(procfile, sizeof(procfile), "/proc/%d/exe", getpid());
   100 #elif defined(__FREEBSD__)
   101     SDL_snprintf(procfile, sizeof(procfile), "/proc/%d/file", getpid());
   102 #endif
   103     linksize = readlink(procfile, linkfile, sizeof(linkfile) - 1);
   104     if (linksize > 0) {
   105         linkfile[linksize] = '\0';
   106         spot = SDL_strrchr(linkfile, '/');
   107         if (spot) {
   108             return SDL_strdup(spot + 1);
   109         } else {
   110             return SDL_strdup(linkfile);
   111         }
   112     }
   113 #endif /* __LINUX__ || __FREEBSD__ */
   114 
   115     return SDL_strdup("SDL_App");
   116 }
   117 
   118 /*
   119  * Copied from fcitx source
   120  */
   121 #define CONT(i)   ISUTF8_CB(in[i])
   122 #define VAL(i, s) ((in[i]&0x3f) << s)
   123 
   124 static char *
   125 _fcitx_utf8_get_char(const char *i, uint32_t *chr)
   126 {
   127     const unsigned char* in = (const unsigned char *)i;
   128     if (!(in[0] & 0x80)) {
   129         *(chr) = *(in);
   130         return (char *)in + 1;
   131     }
   132 
   133     /* 2-byte, 0x80-0x7ff */
   134     if ((in[0] & 0xe0) == 0xc0 && CONT(1)) {
   135         *chr = ((in[0] & 0x1f) << 6) | VAL(1, 0);
   136         return (char *)in + 2;
   137     }
   138 
   139     /* 3-byte, 0x800-0xffff */
   140     if ((in[0] & 0xf0) == 0xe0 && CONT(1) && CONT(2)) {
   141         *chr = ((in[0] & 0xf) << 12) | VAL(1, 6) | VAL(2, 0);
   142         return (char *)in + 3;
   143     }
   144 
   145     /* 4-byte, 0x10000-0x1FFFFF */
   146     if ((in[0] & 0xf8) == 0xf0 && CONT(1) && CONT(2) && CONT(3)) {
   147         *chr = ((in[0] & 0x7) << 18) | VAL(1, 12) | VAL(2, 6) | VAL(3, 0);
   148         return (char *)in + 4;
   149     }
   150 
   151     /* 5-byte, 0x200000-0x3FFFFFF */
   152     if ((in[0] & 0xfc) == 0xf8 && CONT(1) && CONT(2) && CONT(3) && CONT(4)) {
   153         *chr = ((in[0] & 0x3) << 24) | VAL(1, 18) | VAL(2, 12) | VAL(3, 6) | VAL(4, 0);
   154         return (char *)in + 5;
   155     }
   156 
   157     /* 6-byte, 0x400000-0x7FFFFFF */
   158     if ((in[0] & 0xfe) == 0xfc && CONT(1) && CONT(2) && CONT(3) && CONT(4) && CONT(5)) {
   159         *chr = ((in[0] & 0x1) << 30) | VAL(1, 24) | VAL(2, 18) | VAL(3, 12) | VAL(4, 6) | VAL(5, 0);
   160         return (char *)in + 6;
   161     }
   162 
   163     *chr = *in;
   164 
   165     return (char *)in + 1;
   166 }
   167 
   168 static size_t
   169 _fcitx_utf8_strlen(const char *s)
   170 {
   171     unsigned int l = 0;
   172 
   173     while (*s) {
   174         uint32_t chr;
   175 
   176         s = _fcitx_utf8_get_char(s, &chr);
   177         l++;
   178     }
   179 
   180     return l;
   181 }
   182 
   183 static DBusHandlerResult
   184 DBus_MessageFilter(DBusConnection *conn, DBusMessage *msg, void *data)
   185 {
   186     SDL_DBusContext *dbus = (SDL_DBusContext *)data;
   187 
   188     if (dbus->message_is_signal(msg, FCITX_IC_DBUS_INTERFACE, "CommitString")) {
   189         DBusMessageIter iter;
   190         const char *text = NULL;
   191 
   192         dbus->message_iter_init(msg, &iter);
   193         dbus->message_iter_get_basic(&iter, &text);
   194 
   195         if (text)
   196             SDL_SendKeyboardText(text);
   197 
   198         return DBUS_HANDLER_RESULT_HANDLED;
   199     }
   200 
   201     if (dbus->message_is_signal(msg, FCITX_IC_DBUS_INTERFACE, "UpdatePreedit")) {
   202         DBusMessageIter iter;
   203         const char *text;
   204 
   205         dbus->message_iter_init(msg, &iter);
   206         dbus->message_iter_get_basic(&iter, &text);
   207 
   208         if (text && *text) {
   209             char buf[SDL_TEXTEDITINGEVENT_TEXT_SIZE];
   210             size_t text_bytes = SDL_strlen(text), i = 0;
   211             size_t cursor = 0;
   212 
   213             while (i < text_bytes) {
   214                 size_t sz = SDL_utf8strlcpy(buf, text + i, sizeof(buf));
   215                 size_t chars = _fcitx_utf8_strlen(buf);
   216 
   217                 SDL_SendEditingText(buf, cursor, chars);
   218 
   219                 i += sz;
   220                 cursor += chars;
   221             }
   222         }
   223 
   224         SDL_Fcitx_UpdateTextRect(NULL);
   225         return DBUS_HANDLER_RESULT_HANDLED;
   226     }
   227 
   228     return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
   229 }
   230 
   231 static DBusMessage*
   232 FcitxClientICNewMethod(FcitxClient *client,
   233         const char *method)
   234 {
   235     SDL_DBusContext *dbus = client->dbus;
   236     return dbus->message_new_method_call(
   237             client->servicename,
   238             client->icname,
   239             FCITX_IC_DBUS_INTERFACE,
   240             method);
   241 }
   242 
   243 static void
   244 FcitxClientICCallMethod(FcitxClient *client,
   245         const char *method)
   246 {
   247     SDL_DBusContext *dbus = client->dbus;
   248     DBusMessage *msg = FcitxClientICNewMethod(client, method);
   249 
   250     if (msg == NULL)
   251         return ;
   252 
   253     if (dbus->connection_send(dbus->session_conn, msg, NULL)) {
   254         dbus->connection_flush(dbus->session_conn);
   255     }
   256 
   257     dbus->message_unref(msg);
   258 }
   259 
   260 static void
   261 Fcitx_SetCapabilities(void *data,
   262         const char *name,
   263         const char *old_val,
   264         const char *internal_editing)
   265 {
   266     FcitxClient *client = (FcitxClient *)data;
   267     SDL_DBusContext *dbus = client->dbus;
   268     Uint32 caps = CAPACITY_NONE;
   269 
   270     DBusMessage *msg = FcitxClientICNewMethod(client, "SetCapacity");
   271     if (msg == NULL)
   272         return ;
   273 
   274     if (!(internal_editing && *internal_editing == '1')) {
   275         caps |= CAPACITY_PREEDIT;
   276     }
   277 
   278     dbus->message_append_args(msg,
   279             DBUS_TYPE_UINT32, &caps,
   280             DBUS_TYPE_INVALID);
   281     if (dbus->connection_send(dbus->session_conn, msg, NULL)) {
   282         dbus->connection_flush(dbus->session_conn);
   283     }
   284 
   285     dbus->message_unref(msg);
   286 }
   287 
   288 static void
   289 FcitxClientCreateIC(FcitxClient *client)
   290 {
   291     char *appname = NULL;
   292     pid_t pid = 0;
   293     int id = 0;
   294     SDL_bool enable;
   295     Uint32 arg1, arg2, arg3, arg4;
   296 
   297     SDL_DBusContext *dbus = client->dbus;
   298     DBusMessage *reply = NULL;
   299     DBusMessage *msg = dbus->message_new_method_call(
   300             client->servicename,
   301             FCITX_IM_DBUS_PATH,
   302             FCITX_IM_DBUS_INTERFACE,
   303             "CreateICv3"
   304             );
   305 
   306     if (msg == NULL)
   307         return ;
   308 
   309     appname = GetAppName();
   310     pid = getpid();
   311     dbus->message_append_args(msg,
   312             DBUS_TYPE_STRING, &appname,
   313             DBUS_TYPE_INT32, &pid,
   314             DBUS_TYPE_INVALID);
   315 
   316     do {
   317         reply = dbus->connection_send_with_reply_and_block(
   318                 dbus->session_conn,
   319                 msg,
   320                 DBUS_TIMEOUT,
   321                 NULL);
   322 
   323         if (!reply)
   324             break;
   325         if (!dbus->message_get_args(reply, NULL,
   326                 DBUS_TYPE_INT32, &id,
   327                 DBUS_TYPE_BOOLEAN, &enable,
   328                 DBUS_TYPE_UINT32, &arg1,
   329                 DBUS_TYPE_UINT32, &arg2,
   330                 DBUS_TYPE_UINT32, &arg3,
   331                 DBUS_TYPE_UINT32, &arg4,
   332                 DBUS_TYPE_INVALID))
   333             break;
   334 
   335         if (id < 0)
   336             break;
   337         client->id = id;
   338 
   339         SDL_snprintf(client->icname, IC_NAME_MAX,
   340                 FCITX_IC_DBUS_PATH, client->id);
   341 
   342         dbus->bus_add_match(dbus->session_conn,
   343                 "type='signal', interface='org.fcitx.Fcitx.InputContext'",
   344                 NULL);
   345         dbus->connection_add_filter(dbus->session_conn,
   346                 &DBus_MessageFilter, dbus,
   347                 NULL);
   348         dbus->connection_flush(dbus->session_conn);
   349 
   350         SDL_AddHintCallback(SDL_HINT_IME_INTERNAL_EDITING, &Fcitx_SetCapabilities, client);
   351     }
   352     while (0);
   353 
   354     if (reply)
   355         dbus->message_unref(reply);
   356     dbus->message_unref(msg);
   357     SDL_free(appname);
   358 }
   359 
   360 static Uint32
   361 Fcitx_ModState(void)
   362 {
   363     Uint32 fcitx_mods = 0;
   364     SDL_Keymod sdl_mods = SDL_GetModState();
   365 
   366     if (sdl_mods & KMOD_SHIFT) fcitx_mods |= FcitxKeyState_Shift;
   367     if (sdl_mods & KMOD_CAPS)   fcitx_mods |= FcitxKeyState_CapsLock;
   368     if (sdl_mods & KMOD_CTRL)  fcitx_mods |= FcitxKeyState_Ctrl;
   369     if (sdl_mods & KMOD_ALT)   fcitx_mods |= FcitxKeyState_Alt;
   370     if (sdl_mods & KMOD_NUM)    fcitx_mods |= FcitxKeyState_NumLock;
   371     if (sdl_mods & KMOD_LGUI)   fcitx_mods |= FcitxKeyState_Super;
   372     if (sdl_mods & KMOD_RGUI)   fcitx_mods |= FcitxKeyState_Meta;
   373 
   374     return fcitx_mods;
   375 }
   376 
   377 SDL_bool
   378 SDL_Fcitx_Init()
   379 {
   380     fcitx_client.dbus = SDL_DBus_GetContext();
   381 
   382     fcitx_client.cursor_rect.x = -1;
   383     fcitx_client.cursor_rect.y = -1;
   384     fcitx_client.cursor_rect.w = 0;
   385     fcitx_client.cursor_rect.h = 0;
   386 
   387     SDL_snprintf(fcitx_client.servicename, IC_NAME_MAX,
   388             "%s-%d",
   389             FCITX_DBUS_SERVICE, GetDisplayNumber());
   390 
   391     FcitxClientCreateIC(&fcitx_client);
   392 
   393     return SDL_TRUE;
   394 }
   395 
   396 void
   397 SDL_Fcitx_Quit()
   398 {
   399     FcitxClientICCallMethod(&fcitx_client, "DestroyIC");
   400 }
   401 
   402 void
   403 SDL_Fcitx_SetFocus(SDL_bool focused)
   404 {
   405     if (focused) {
   406         FcitxClientICCallMethod(&fcitx_client, "FocusIn");
   407     } else {
   408         FcitxClientICCallMethod(&fcitx_client, "FocusOut");
   409     }
   410 }
   411 
   412 void
   413 SDL_Fcitx_Reset(void)
   414 {
   415     FcitxClientICCallMethod(&fcitx_client, "Reset");
   416     FcitxClientICCallMethod(&fcitx_client, "CloseIC");
   417 }
   418 
   419 SDL_bool
   420 SDL_Fcitx_ProcessKeyEvent(Uint32 keysym, Uint32 keycode)
   421 {
   422     DBusMessage *msg = NULL;
   423     DBusMessage *reply = NULL;
   424     SDL_DBusContext *dbus = fcitx_client.dbus;
   425 
   426     Uint32 state = 0;
   427     SDL_bool handled = SDL_FALSE;
   428     int type = FCITX_PRESS_KEY;
   429     Uint32 event_time = 0;
   430 
   431     msg = FcitxClientICNewMethod(&fcitx_client, "ProcessKeyEvent");
   432     if (msg == NULL)
   433         return SDL_FALSE;
   434 
   435     state = Fcitx_ModState();
   436     dbus->message_append_args(msg,
   437             DBUS_TYPE_UINT32, &keysym,
   438             DBUS_TYPE_UINT32, &keycode,
   439             DBUS_TYPE_UINT32, &state,
   440             DBUS_TYPE_INT32, &type,
   441             DBUS_TYPE_UINT32, &event_time,
   442             DBUS_TYPE_INVALID);
   443 
   444     reply = dbus->connection_send_with_reply_and_block(dbus->session_conn,
   445             msg,
   446             -1,
   447             NULL);
   448 
   449     if (reply) {
   450         dbus->message_get_args(reply,
   451                 NULL,
   452                 DBUS_TYPE_INT32, &handled,
   453                 DBUS_TYPE_INVALID);
   454 
   455         dbus->message_unref(reply);
   456     }
   457 
   458     if (handled) {
   459         SDL_Fcitx_UpdateTextRect(NULL);
   460     }
   461 
   462     return handled;
   463 }
   464 
   465 void
   466 SDL_Fcitx_UpdateTextRect(SDL_Rect *rect)
   467 {
   468     SDL_Window *focused_win = NULL;
   469     SDL_SysWMinfo info;
   470     int x = 0, y = 0;
   471     SDL_Rect *cursor = &fcitx_client.cursor_rect;
   472 
   473     SDL_DBusContext *dbus = fcitx_client.dbus;
   474     DBusMessage *msg = NULL;
   475     DBusConnection *conn;
   476 
   477     if (rect) {
   478         SDL_memcpy(cursor, rect, sizeof(SDL_Rect));
   479     }
   480 
   481     focused_win = SDL_GetKeyboardFocus();
   482     if (!focused_win) {
   483         return ;
   484     }
   485 
   486     SDL_VERSION(&info.version);
   487     if (!SDL_GetWindowWMInfo(focused_win, &info)) {
   488         return;
   489     }
   490 
   491     SDL_GetWindowPosition(focused_win, &x, &y);
   492 
   493 #if SDL_VIDEO_DRIVER_X11
   494     if (info.subsystem == SDL_SYSWM_X11) {
   495         SDL_DisplayData *displaydata = (SDL_DisplayData *) SDL_GetDisplayForWindow(focused_win)->driverdata;
   496 
   497         Display *x_disp = info.info.x11.display;
   498         Window x_win = info.info.x11.window;
   499         int x_screen = displaydata->screen;
   500         Window unused;
   501         X11_XTranslateCoordinates(x_disp, x_win, RootWindow(x_disp, x_screen), 0, 0, &x, &y, &unused);
   502     }
   503 #endif
   504 
   505     if (cursor->x == -1 && cursor->y == -1 && cursor->w == 0 && cursor->h == 0) {
   506         // move to bottom left
   507         int w = 0, h = 0;
   508         SDL_GetWindowSize(focused_win, &w, &h);
   509         cursor->x = 0;
   510         cursor->y = h;
   511     }
   512 
   513     x += cursor->x;
   514     y += cursor->y;
   515 
   516     msg = FcitxClientICNewMethod(&fcitx_client, "SetCursorRect");
   517     if (msg == NULL)
   518         return ;
   519 
   520     dbus->message_append_args(msg,
   521             DBUS_TYPE_INT32, &x,
   522             DBUS_TYPE_INT32, &y,
   523             DBUS_TYPE_INT32, &cursor->w,
   524             DBUS_TYPE_INT32, &cursor->h,
   525             DBUS_TYPE_INVALID);
   526 
   527     conn = dbus->session_conn;
   528     if (dbus->connection_send(conn, msg, NULL))
   529         dbus->connection_flush(conn);
   530 
   531     dbus->message_unref(msg);
   532 }
   533 
   534 void
   535 SDL_Fcitx_PumpEvents()
   536 {
   537     SDL_DBusContext *dbus = fcitx_client.dbus;
   538     DBusConnection *conn = dbus->session_conn;
   539 
   540     dbus->connection_read_write(conn, 0);
   541 
   542     while (dbus->connection_dispatch(conn) == DBUS_DISPATCH_DATA_REMAINS) {
   543         /* Do nothing, actual work happens in DBus_MessageFilter */
   544         usleep(10);
   545     }
   546 }
   547 
   548 /* vi: set ts=4 sw=4 expandtab: */