src/core/linux/SDL_fcitx.c
author Ryan C. Gordon <icculus@icculus.org>
Fri, 18 Aug 2017 20:00:29 -0400
changeset 11323 46861f3fc187
parent 11284 3db78361e751
child 11811 5d94cb6b24d3
permissions -rw-r--r--
cmake: added a FIXME for later.

Have to figure out what cmake version fixed this and bump the minimum to that.
     1 /*
     2   Simple DirectMedia Layer
     3   Copyright (C) 1997-2017 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_FCITX_FRONTEND_H
    24 
    25 #include <fcitx/frontend.h>
    26 #include <unistd.h>
    27 
    28 #include "SDL_fcitx.h"
    29 #include "SDL_keycode.h"
    30 #include "SDL_keyboard.h"
    31 #include "../../events/SDL_keyboard_c.h"
    32 #include "SDL_dbus.h"
    33 #include "SDL_syswm.h"
    34 #if SDL_VIDEO_DRIVER_X11
    35 #  include "../../video/x11/SDL_x11video.h"
    36 #endif
    37 #include "SDL_hints.h"
    38 
    39 #define FCITX_DBUS_SERVICE "org.fcitx.Fcitx"
    40 
    41 #define FCITX_IM_DBUS_PATH "/inputmethod"
    42 #define FCITX_IC_DBUS_PATH "/inputcontext_%d"
    43 
    44 #define FCITX_IM_DBUS_INTERFACE "org.fcitx.Fcitx.InputMethod"
    45 #define FCITX_IC_DBUS_INTERFACE "org.fcitx.Fcitx.InputContext"
    46 
    47 #define IC_NAME_MAX 64
    48 #define DBUS_TIMEOUT 500
    49 
    50 typedef struct _FcitxClient
    51 {
    52     SDL_DBusContext *dbus;
    53 
    54     char servicename[IC_NAME_MAX];
    55     char icname[IC_NAME_MAX];
    56 
    57     int id;
    58 
    59     SDL_Rect cursor_rect;
    60 } FcitxClient;
    61 
    62 static FcitxClient fcitx_client;
    63 
    64 static int
    65 GetDisplayNumber()
    66 {
    67     const char *display = SDL_getenv("DISPLAY");
    68     const char *p = NULL;
    69     int number = 0;
    70 
    71     if (display == NULL)
    72         return 0;
    73 
    74     display = SDL_strchr(display, ':');
    75     if (display == NULL)
    76         return 0;
    77 
    78     display++;
    79     p = SDL_strchr(display, '.');
    80     if (p == NULL && display != NULL) {
    81         number = SDL_strtod(display, NULL);
    82     } else {
    83         char *buffer = SDL_strdup(display);
    84         buffer[p - display] = '\0';
    85         number = SDL_strtod(buffer, NULL);
    86         SDL_free(buffer);
    87     }
    88 
    89     return number;
    90 }
    91 
    92 static char*
    93 GetAppName()
    94 {
    95 #if defined(__LINUX__) || defined(__FREEBSD__)
    96     char *spot;
    97     char procfile[1024];
    98     char linkfile[1024];
    99     int linksize;
   100 
   101 #if defined(__LINUX__)
   102     SDL_snprintf(procfile, sizeof(procfile), "/proc/%d/exe", getpid());
   103 #elif defined(__FREEBSD__)
   104     SDL_snprintf(procfile, sizeof(procfile), "/proc/%d/file", getpid());
   105 #endif
   106     linksize = readlink(procfile, linkfile, sizeof(linkfile) - 1);
   107     if (linksize > 0) {
   108         linkfile[linksize] = '\0';
   109         spot = SDL_strrchr(linkfile, '/');
   110         if (spot) {
   111             return SDL_strdup(spot + 1);
   112         } else {
   113             return SDL_strdup(linkfile);
   114         }
   115     }
   116 #endif /* __LINUX__ || __FREEBSD__ */
   117 
   118     return SDL_strdup("SDL_App");
   119 }
   120 
   121 static DBusHandlerResult
   122 DBus_MessageFilter(DBusConnection *conn, DBusMessage *msg, void *data)
   123 {
   124     SDL_DBusContext *dbus = (SDL_DBusContext *)data;
   125 
   126     if (dbus->message_is_signal(msg, FCITX_IC_DBUS_INTERFACE, "CommitString")) {
   127         DBusMessageIter iter;
   128         const char *text = NULL;
   129 
   130         dbus->message_iter_init(msg, &iter);
   131         dbus->message_iter_get_basic(&iter, &text);
   132 
   133         if (text)
   134             SDL_SendKeyboardText(text);
   135 
   136         return DBUS_HANDLER_RESULT_HANDLED;
   137     }
   138 
   139     if (dbus->message_is_signal(msg, FCITX_IC_DBUS_INTERFACE, "UpdatePreedit")) {
   140         DBusMessageIter iter;
   141         const char *text;
   142 
   143         dbus->message_iter_init(msg, &iter);
   144         dbus->message_iter_get_basic(&iter, &text);
   145 
   146         if (text && *text) {
   147             char buf[SDL_TEXTEDITINGEVENT_TEXT_SIZE];
   148             size_t text_bytes = SDL_strlen(text), i = 0;
   149             size_t cursor = 0;
   150 
   151             while (i < text_bytes) {
   152                 const size_t sz = SDL_utf8strlcpy(buf, text + i, sizeof(buf));
   153                 const size_t chars = SDL_utf8strlen(buf);
   154 
   155                 SDL_SendEditingText(buf, cursor, chars);
   156 
   157                 i += sz;
   158                 cursor += chars;
   159             }
   160         }
   161 
   162         SDL_Fcitx_UpdateTextRect(NULL);
   163         return DBUS_HANDLER_RESULT_HANDLED;
   164     }
   165 
   166     return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
   167 }
   168 
   169 static void
   170 FcitxClientICCallMethod(FcitxClient *client, const char *method)
   171 {
   172     SDL_DBus_CallVoidMethod(client->servicename, client->icname, FCITX_IC_DBUS_INTERFACE, method, DBUS_TYPE_INVALID);
   173 }
   174 
   175 static void SDLCALL
   176 Fcitx_SetCapabilities(void *data,
   177         const char *name,
   178         const char *old_val,
   179         const char *internal_editing)
   180 {
   181     FcitxClient *client = (FcitxClient *)data;
   182     Uint32 caps = CAPACITY_NONE;
   183 
   184     if (!(internal_editing && *internal_editing == '1')) {
   185         caps |= CAPACITY_PREEDIT;
   186     }
   187 
   188     SDL_DBus_CallVoidMethod(client->servicename, client->icname, FCITX_IC_DBUS_INTERFACE, "SetCapacity", DBUS_TYPE_UINT32, &caps, DBUS_TYPE_INVALID);
   189 }
   190 
   191 static SDL_bool
   192 FcitxClientCreateIC(FcitxClient *client)
   193 {
   194     char *appname = GetAppName();
   195     pid_t pid = getpid();
   196     int id = -1;
   197     Uint32 enable, arg1, arg2, arg3, arg4;
   198 
   199     if (!SDL_DBus_CallMethod(client->servicename, FCITX_IM_DBUS_PATH, FCITX_IM_DBUS_INTERFACE, "CreateICv3",
   200             DBUS_TYPE_STRING, &appname, DBUS_TYPE_INT32, &pid, DBUS_TYPE_INVALID,
   201             DBUS_TYPE_INT32, &id, DBUS_TYPE_BOOLEAN, &enable, DBUS_TYPE_UINT32, &arg1, DBUS_TYPE_UINT32, &arg2, DBUS_TYPE_UINT32, &arg3, DBUS_TYPE_UINT32, &arg4, DBUS_TYPE_INVALID)) {
   202         id = -1;  /* just in case. */
   203     }
   204 
   205     SDL_free(appname);
   206 
   207     if (id >= 0) {
   208         SDL_DBusContext *dbus = client->dbus;
   209 
   210         client->id = id;
   211 
   212         SDL_snprintf(client->icname, IC_NAME_MAX, FCITX_IC_DBUS_PATH, client->id);
   213 
   214         dbus->bus_add_match(dbus->session_conn,
   215                 "type='signal', interface='org.fcitx.Fcitx.InputContext'",
   216                 NULL);
   217         dbus->connection_add_filter(dbus->session_conn,
   218                 &DBus_MessageFilter, dbus,
   219                 NULL);
   220         dbus->connection_flush(dbus->session_conn);
   221 
   222         SDL_AddHintCallback(SDL_HINT_IME_INTERNAL_EDITING, Fcitx_SetCapabilities, client);
   223         return SDL_TRUE;
   224     }
   225 
   226     return SDL_FALSE;
   227 }
   228 
   229 static Uint32
   230 Fcitx_ModState(void)
   231 {
   232     Uint32 fcitx_mods = 0;
   233     SDL_Keymod sdl_mods = SDL_GetModState();
   234 
   235     if (sdl_mods & KMOD_SHIFT) fcitx_mods |= FcitxKeyState_Shift;
   236     if (sdl_mods & KMOD_CAPS)   fcitx_mods |= FcitxKeyState_CapsLock;
   237     if (sdl_mods & KMOD_CTRL)  fcitx_mods |= FcitxKeyState_Ctrl;
   238     if (sdl_mods & KMOD_ALT)   fcitx_mods |= FcitxKeyState_Alt;
   239     if (sdl_mods & KMOD_NUM)    fcitx_mods |= FcitxKeyState_NumLock;
   240     if (sdl_mods & KMOD_LGUI)   fcitx_mods |= FcitxKeyState_Super;
   241     if (sdl_mods & KMOD_RGUI)   fcitx_mods |= FcitxKeyState_Meta;
   242 
   243     return fcitx_mods;
   244 }
   245 
   246 SDL_bool
   247 SDL_Fcitx_Init()
   248 {
   249     fcitx_client.dbus = SDL_DBus_GetContext();
   250 
   251     fcitx_client.cursor_rect.x = -1;
   252     fcitx_client.cursor_rect.y = -1;
   253     fcitx_client.cursor_rect.w = 0;
   254     fcitx_client.cursor_rect.h = 0;
   255 
   256     SDL_snprintf(fcitx_client.servicename, IC_NAME_MAX,
   257             "%s-%d",
   258             FCITX_DBUS_SERVICE, GetDisplayNumber());
   259 
   260     return FcitxClientCreateIC(&fcitx_client);
   261 }
   262 
   263 void
   264 SDL_Fcitx_Quit()
   265 {
   266     FcitxClientICCallMethod(&fcitx_client, "DestroyIC");
   267 }
   268 
   269 void
   270 SDL_Fcitx_SetFocus(SDL_bool focused)
   271 {
   272     if (focused) {
   273         FcitxClientICCallMethod(&fcitx_client, "FocusIn");
   274     } else {
   275         FcitxClientICCallMethod(&fcitx_client, "FocusOut");
   276     }
   277 }
   278 
   279 void
   280 SDL_Fcitx_Reset(void)
   281 {
   282     FcitxClientICCallMethod(&fcitx_client, "Reset");
   283     FcitxClientICCallMethod(&fcitx_client, "CloseIC");
   284 }
   285 
   286 SDL_bool
   287 SDL_Fcitx_ProcessKeyEvent(Uint32 keysym, Uint32 keycode)
   288 {
   289     Uint32 state = Fcitx_ModState();
   290     Uint32 handled = SDL_FALSE;
   291     int type = FCITX_PRESS_KEY;
   292     Uint32 event_time = 0;
   293 
   294     if (SDL_DBus_CallMethod(fcitx_client.servicename, fcitx_client.icname, FCITX_IC_DBUS_INTERFACE, "ProcessKeyEvent",
   295             DBUS_TYPE_UINT32, &keysym, DBUS_TYPE_UINT32, &keycode, DBUS_TYPE_UINT32, &state, DBUS_TYPE_INT32, &type, DBUS_TYPE_UINT32, &event_time, DBUS_TYPE_INVALID,
   296             DBUS_TYPE_INT32, &handled, DBUS_TYPE_INVALID)) {
   297         if (handled) {
   298             SDL_Fcitx_UpdateTextRect(NULL);
   299             return SDL_TRUE;
   300         }
   301     }
   302 
   303     return SDL_FALSE;
   304 }
   305 
   306 void
   307 SDL_Fcitx_UpdateTextRect(SDL_Rect *rect)
   308 {
   309     SDL_Window *focused_win = NULL;
   310     SDL_SysWMinfo info;
   311     int x = 0, y = 0;
   312     SDL_Rect *cursor = &fcitx_client.cursor_rect;
   313 
   314     if (rect) {
   315         SDL_memcpy(cursor, rect, sizeof(SDL_Rect));
   316     }
   317 
   318     focused_win = SDL_GetKeyboardFocus();
   319     if (!focused_win) {
   320         return ;
   321     }
   322 
   323     SDL_VERSION(&info.version);
   324     if (!SDL_GetWindowWMInfo(focused_win, &info)) {
   325         return;
   326     }
   327 
   328     SDL_GetWindowPosition(focused_win, &x, &y);
   329 
   330 #if SDL_VIDEO_DRIVER_X11
   331     if (info.subsystem == SDL_SYSWM_X11) {
   332         SDL_DisplayData *displaydata = (SDL_DisplayData *) SDL_GetDisplayForWindow(focused_win)->driverdata;
   333 
   334         Display *x_disp = info.info.x11.display;
   335         Window x_win = info.info.x11.window;
   336         int x_screen = displaydata->screen;
   337         Window unused;
   338         X11_XTranslateCoordinates(x_disp, x_win, RootWindow(x_disp, x_screen), 0, 0, &x, &y, &unused);
   339     }
   340 #endif
   341 
   342     if (cursor->x == -1 && cursor->y == -1 && cursor->w == 0 && cursor->h == 0) {
   343         /* move to bottom left */
   344         int w = 0, h = 0;
   345         SDL_GetWindowSize(focused_win, &w, &h);
   346         cursor->x = 0;
   347         cursor->y = h;
   348     }
   349 
   350     x += cursor->x;
   351     y += cursor->y;
   352 
   353     SDL_DBus_CallVoidMethod(fcitx_client.servicename, fcitx_client.icname, FCITX_IC_DBUS_INTERFACE, "SetCursorRect",
   354         DBUS_TYPE_INT32, &x, DBUS_TYPE_INT32, &y, DBUS_TYPE_INT32, &cursor->w, DBUS_TYPE_INT32, &cursor->h, DBUS_TYPE_INVALID);
   355 }
   356 
   357 void
   358 SDL_Fcitx_PumpEvents(void)
   359 {
   360     SDL_DBusContext *dbus = fcitx_client.dbus;
   361     DBusConnection *conn = dbus->session_conn;
   362 
   363     dbus->connection_read_write(conn, 0);
   364 
   365     while (dbus->connection_dispatch(conn) == DBUS_DISPATCH_DATA_REMAINS) {
   366         /* Do nothing, actual work happens in DBus_MessageFilter */
   367         usleep(10);
   368     }
   369 }
   370 
   371 #endif /* HAVE_FCITX_FRONTEND_H */
   372 
   373 /* vi: set ts=4 sw=4 expandtab: */