sdl

FORK: Simple Directmedia Layer
git clone https://git.neptards.moe/neptards/sdl.git
Log | Files | Refs

SDL_fcitx.c (13162B)


      1 /*
      2   Simple DirectMedia Layer
      3   Copyright (C) 1997-2020 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 #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.freedesktop.portal.Fcitx"
     37 
     38 #define FCITX_IM_DBUS_PATH "/org/freedesktop/portal/inputmethod"
     39 
     40 #define FCITX_IM_DBUS_INTERFACE "org.fcitx.Fcitx.InputMethod1"
     41 #define FCITX_IC_DBUS_INTERFACE "org.fcitx.Fcitx.InputContext1"
     42 
     43 #define DBUS_TIMEOUT 500
     44 
     45 typedef struct _FcitxClient
     46 {
     47     SDL_DBusContext *dbus;
     48 
     49     char *ic_path;
     50 
     51     int id;
     52 
     53     SDL_Rect cursor_rect;
     54 } FcitxClient;
     55 
     56 static FcitxClient fcitx_client;
     57 
     58 static char*
     59 GetAppName()
     60 {
     61 #if defined(__LINUX__) || defined(__FREEBSD__)
     62     char *spot;
     63     char procfile[1024];
     64     char linkfile[1024];
     65     int linksize;
     66 
     67 #if defined(__LINUX__)
     68     SDL_snprintf(procfile, sizeof(procfile), "/proc/%d/exe", getpid());
     69 #elif defined(__FREEBSD__)
     70     SDL_snprintf(procfile, sizeof(procfile), "/proc/%d/file", getpid());
     71 #endif
     72     linksize = readlink(procfile, linkfile, sizeof(linkfile) - 1);
     73     if (linksize > 0) {
     74         linkfile[linksize] = '\0';
     75         spot = SDL_strrchr(linkfile, '/');
     76         if (spot) {
     77             return SDL_strdup(spot + 1);
     78         } else {
     79             return SDL_strdup(linkfile);
     80         }
     81     }
     82 #endif /* __LINUX__ || __FREEBSD__ */
     83 
     84     return SDL_strdup("SDL_App");
     85 }
     86 
     87 size_t Fcitx_GetPreeditString(SDL_DBusContext *dbus, DBusMessage *msg, char **ret) {
     88     char *text = NULL, *subtext;
     89     size_t text_bytes = 0;
     90     DBusMessageIter iter, array, sub;
     91 
     92     dbus->message_iter_init(msg, &iter);
     93     /* Message type is a(si)i, we only need string part */
     94     if (dbus->message_iter_get_arg_type(&iter) == DBUS_TYPE_ARRAY) {
     95         /* First pass: calculate string length */
     96         dbus->message_iter_recurse(&iter, &array);
     97         while (dbus->message_iter_get_arg_type(&array) == DBUS_TYPE_STRUCT) {
     98             dbus->message_iter_recurse(&array, &sub);
     99             if (dbus->message_iter_get_arg_type(&sub) == DBUS_TYPE_STRING) {
    100                 dbus->message_iter_get_basic(&sub, &subtext);
    101                 if (subtext && *subtext) {
    102                     text_bytes += SDL_strlen(subtext);
    103                 }
    104             }
    105             dbus->message_iter_next(&array);
    106         }
    107         if (text_bytes) {
    108             text = SDL_malloc(text_bytes + 1);
    109         }
    110 
    111         if (text) {
    112             char* pivot = text;
    113             /* Second pass: join all the sub string */
    114             dbus->message_iter_recurse(&iter, &array);
    115             while (dbus->message_iter_get_arg_type(&array) == DBUS_TYPE_STRUCT) {
    116                 dbus->message_iter_recurse(&array, &sub);
    117                 if (dbus->message_iter_get_arg_type(&sub) == DBUS_TYPE_STRING) {
    118                     dbus->message_iter_get_basic(&sub, &subtext);
    119                     if (subtext && *subtext) {
    120                         size_t length = SDL_strlen(subtext);
    121                         SDL_strlcpy(pivot, subtext, length + 1);
    122                         pivot += length;
    123                     }
    124                 }
    125                 dbus->message_iter_next(&array);
    126             }
    127         } else {
    128             text_bytes = 0;
    129         }
    130     }
    131     *ret= text;
    132     return text_bytes;
    133 }
    134 
    135 static DBusHandlerResult
    136 DBus_MessageFilter(DBusConnection *conn, DBusMessage *msg, void *data)
    137 {
    138     SDL_DBusContext *dbus = (SDL_DBusContext *)data;
    139 
    140     if (dbus->message_is_signal(msg, FCITX_IC_DBUS_INTERFACE, "CommitString")) {
    141         DBusMessageIter iter;
    142         const char *text = NULL;
    143 
    144         dbus->message_iter_init(msg, &iter);
    145         dbus->message_iter_get_basic(&iter, &text);
    146 
    147         if (text && *text) {
    148             char buf[SDL_TEXTINPUTEVENT_TEXT_SIZE];
    149             size_t text_bytes = SDL_strlen(text), i = 0;
    150 
    151             while (i < text_bytes) {
    152                 size_t sz = SDL_utf8strlcpy(buf, text+i, sizeof(buf));
    153                 SDL_SendKeyboardText(buf);
    154 
    155                 i += sz;
    156             }
    157         }
    158 
    159         return DBUS_HANDLER_RESULT_HANDLED;
    160     }
    161 
    162     if (dbus->message_is_signal(msg, FCITX_IC_DBUS_INTERFACE, "UpdateFormattedPreedit")) {
    163         char *text = NULL;
    164         size_t text_bytes = Fcitx_GetPreeditString(dbus, msg, &text);
    165         if (text_bytes) {
    166             char buf[SDL_TEXTEDITINGEVENT_TEXT_SIZE];
    167             size_t i = 0;
    168             size_t cursor = 0;
    169 
    170             while (i < text_bytes) {
    171                 const size_t sz = SDL_utf8strlcpy(buf, text + i, sizeof(buf));
    172                 const size_t chars = SDL_utf8strlen(buf);
    173 
    174                 SDL_SendEditingText(buf, cursor, chars);
    175 
    176                 i += sz;
    177                 cursor += chars;
    178             }
    179             SDL_free(text);
    180         } else {
    181             SDL_SendEditingText("", 0, 0);
    182         }
    183 
    184         SDL_Fcitx_UpdateTextRect(NULL);
    185         return DBUS_HANDLER_RESULT_HANDLED;
    186     }
    187 
    188     return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
    189 }
    190 
    191 static void
    192 FcitxClientICCallMethod(FcitxClient *client, const char *method)
    193 {
    194     if (!client->ic_path) {
    195         return;
    196     }
    197     SDL_DBus_CallVoidMethod(FCITX_DBUS_SERVICE, client->ic_path, FCITX_IC_DBUS_INTERFACE, method, DBUS_TYPE_INVALID);
    198 }
    199 
    200 static void SDLCALL
    201 Fcitx_SetCapabilities(void *data,
    202         const char *name,
    203         const char *old_val,
    204         const char *internal_editing)
    205 {
    206     FcitxClient *client = (FcitxClient *)data;
    207     Uint32 caps = 0;
    208     if (!client->ic_path) {
    209         return;
    210     }
    211 
    212     if (!(internal_editing && *internal_editing == '1')) {
    213         caps |= (1 << 1); /* Preedit Flag */
    214         caps |= (1 << 4); /* Formatted Preedit Flag */
    215     }
    216 
    217     SDL_DBus_CallVoidMethod(FCITX_DBUS_SERVICE, client->ic_path, FCITX_IC_DBUS_INTERFACE, "SetCapability", DBUS_TYPE_UINT64, &caps, DBUS_TYPE_INVALID);
    218 }
    219 
    220 static SDL_bool
    221 FcitxCreateInputContext(SDL_DBusContext* dbus, const char *appname, char **ic_path) {
    222     const char *program = "program";
    223     SDL_bool retval = SDL_FALSE;
    224     if (dbus->session_conn) {
    225         DBusMessage *msg = dbus->message_new_method_call(FCITX_DBUS_SERVICE, FCITX_IM_DBUS_PATH, FCITX_IM_DBUS_INTERFACE, "CreateInputContext");
    226         if (msg) {
    227             DBusMessage *reply = NULL;
    228             DBusMessageIter args, array, sub;
    229             dbus->message_iter_init_append(msg, &args);
    230             dbus->message_iter_open_container(&args, DBUS_TYPE_ARRAY, "(ss)", &array);
    231             dbus->message_iter_open_container(&array, DBUS_TYPE_STRUCT, 0, &sub);
    232             dbus->message_iter_append_basic(&sub, DBUS_TYPE_STRING, &program);
    233             dbus->message_iter_append_basic(&sub, DBUS_TYPE_STRING, &appname);
    234             dbus->message_iter_close_container(&array, &sub);
    235             dbus->message_iter_close_container(&args, &array);
    236             reply = dbus->connection_send_with_reply_and_block(dbus->session_conn, msg, 300, NULL);
    237             if (reply) {
    238                 if (dbus->message_get_args(reply, NULL, DBUS_TYPE_OBJECT_PATH, ic_path, DBUS_TYPE_INVALID)) {
    239                     retval = SDL_TRUE;
    240                 }
    241                 dbus->message_unref(reply);
    242             }
    243             dbus->message_unref(msg);
    244         }
    245     }
    246     return retval;
    247 }
    248 
    249 static SDL_bool
    250 FcitxClientCreateIC(FcitxClient *client)
    251 {
    252     char *appname = GetAppName();
    253     char *ic_path = NULL;
    254     SDL_DBusContext *dbus = client->dbus;
    255 
    256     /* SDL_DBus_CallMethod cannot handle a(ss) type, call dbus function directly */
    257     if (!FcitxCreateInputContext(dbus, appname, &ic_path)) {
    258         ic_path = NULL;  /* just in case. */
    259     }
    260 
    261     SDL_free(appname);
    262 
    263     if (ic_path) {
    264         SDL_free(client->ic_path);
    265         client->ic_path = SDL_strdup(ic_path);
    266 
    267         dbus->bus_add_match(dbus->session_conn,
    268                 "type='signal', interface='org.fcitx.Fcitx.InputContext1'",
    269                 NULL);
    270         dbus->connection_add_filter(dbus->session_conn,
    271                 &DBus_MessageFilter, dbus,
    272                 NULL);
    273         dbus->connection_flush(dbus->session_conn);
    274 
    275         SDL_AddHintCallback(SDL_HINT_IME_INTERNAL_EDITING, Fcitx_SetCapabilities, client);
    276         return SDL_TRUE;
    277     }
    278 
    279     return SDL_FALSE;
    280 }
    281 
    282 static Uint32
    283 Fcitx_ModState(void)
    284 {
    285     Uint32 fcitx_mods = 0;
    286     SDL_Keymod sdl_mods = SDL_GetModState();
    287 
    288     if (sdl_mods & KMOD_SHIFT) fcitx_mods |= (1 << 0);
    289     if (sdl_mods & KMOD_CAPS)   fcitx_mods |= (1 << 1);
    290     if (sdl_mods & KMOD_CTRL)  fcitx_mods |= (1 << 2);
    291     if (sdl_mods & KMOD_ALT)   fcitx_mods |= (1 << 3);
    292     if (sdl_mods & KMOD_NUM)    fcitx_mods |= (1 << 4);
    293     if (sdl_mods & KMOD_MODE)   fcitx_mods |= (1 << 7);
    294     if (sdl_mods & KMOD_LGUI)   fcitx_mods |= (1 << 6);
    295     if (sdl_mods & KMOD_RGUI)   fcitx_mods |= (1 << 28);
    296 
    297     return fcitx_mods;
    298 }
    299 
    300 SDL_bool
    301 SDL_Fcitx_Init()
    302 {
    303     fcitx_client.dbus = SDL_DBus_GetContext();
    304 
    305     fcitx_client.cursor_rect.x = -1;
    306     fcitx_client.cursor_rect.y = -1;
    307     fcitx_client.cursor_rect.w = 0;
    308     fcitx_client.cursor_rect.h = 0;
    309 
    310     return FcitxClientCreateIC(&fcitx_client);
    311 }
    312 
    313 void
    314 SDL_Fcitx_Quit()
    315 {
    316     FcitxClientICCallMethod(&fcitx_client, "DestroyIC");
    317     if (fcitx_client.ic_path) {
    318         SDL_free(fcitx_client.ic_path);
    319         fcitx_client.ic_path = NULL;
    320     }
    321 }
    322 
    323 void
    324 SDL_Fcitx_SetFocus(SDL_bool focused)
    325 {
    326     if (focused) {
    327         FcitxClientICCallMethod(&fcitx_client, "FocusIn");
    328     } else {
    329         FcitxClientICCallMethod(&fcitx_client, "FocusOut");
    330     }
    331 }
    332 
    333 void
    334 SDL_Fcitx_Reset(void)
    335 {
    336     FcitxClientICCallMethod(&fcitx_client, "Reset");
    337     FcitxClientICCallMethod(&fcitx_client, "CloseIC");
    338 }
    339 
    340 SDL_bool
    341 SDL_Fcitx_ProcessKeyEvent(Uint32 keysym, Uint32 keycode)
    342 {
    343     Uint32 state = Fcitx_ModState();
    344     Uint32 handled = SDL_FALSE;
    345     Uint32 is_release = SDL_FALSE;
    346     Uint32 event_time = 0;
    347 
    348     if (!fcitx_client.ic_path) {
    349         return SDL_FALSE;
    350     }
    351 
    352     if (SDL_DBus_CallMethod(FCITX_DBUS_SERVICE, fcitx_client.ic_path, FCITX_IC_DBUS_INTERFACE, "ProcessKeyEvent",
    353             DBUS_TYPE_UINT32, &keysym, DBUS_TYPE_UINT32, &keycode, DBUS_TYPE_UINT32, &state, DBUS_TYPE_BOOLEAN, &is_release, DBUS_TYPE_UINT32, &event_time, DBUS_TYPE_INVALID,
    354             DBUS_TYPE_BOOLEAN, &handled, DBUS_TYPE_INVALID)) {
    355         if (handled) {
    356             SDL_Fcitx_UpdateTextRect(NULL);
    357             return SDL_TRUE;
    358         }
    359     }
    360 
    361     return SDL_FALSE;
    362 }
    363 
    364 void
    365 SDL_Fcitx_UpdateTextRect(SDL_Rect *rect)
    366 {
    367     SDL_Window *focused_win = NULL;
    368     SDL_SysWMinfo info;
    369     int x = 0, y = 0;
    370     SDL_Rect *cursor = &fcitx_client.cursor_rect;
    371 
    372     if (rect) {
    373         SDL_memcpy(cursor, rect, sizeof(SDL_Rect));
    374     }
    375 
    376     focused_win = SDL_GetKeyboardFocus();
    377     if (!focused_win) {
    378         return ;
    379     }
    380 
    381     SDL_VERSION(&info.version);
    382     if (!SDL_GetWindowWMInfo(focused_win, &info)) {
    383         return;
    384     }
    385 
    386     SDL_GetWindowPosition(focused_win, &x, &y);
    387 
    388 #if SDL_VIDEO_DRIVER_X11
    389     if (info.subsystem == SDL_SYSWM_X11) {
    390         SDL_DisplayData *displaydata = (SDL_DisplayData *) SDL_GetDisplayForWindow(focused_win)->driverdata;
    391 
    392         Display *x_disp = info.info.x11.display;
    393         Window x_win = info.info.x11.window;
    394         int x_screen = displaydata->screen;
    395         Window unused;
    396         X11_XTranslateCoordinates(x_disp, x_win, RootWindow(x_disp, x_screen), 0, 0, &x, &y, &unused);
    397     }
    398 #endif
    399 
    400     if (cursor->x == -1 && cursor->y == -1 && cursor->w == 0 && cursor->h == 0) {
    401         /* move to bottom left */
    402         int w = 0, h = 0;
    403         SDL_GetWindowSize(focused_win, &w, &h);
    404         cursor->x = 0;
    405         cursor->y = h;
    406     }
    407 
    408     x += cursor->x;
    409     y += cursor->y;
    410 
    411     SDL_DBus_CallVoidMethod(FCITX_DBUS_SERVICE, fcitx_client.ic_path, FCITX_IC_DBUS_INTERFACE, "SetCursorRect",
    412         DBUS_TYPE_INT32, &x, DBUS_TYPE_INT32, &y, DBUS_TYPE_INT32, &cursor->w, DBUS_TYPE_INT32, &cursor->h, DBUS_TYPE_INVALID);
    413 }
    414 
    415 void
    416 SDL_Fcitx_PumpEvents(void)
    417 {
    418     SDL_DBusContext *dbus = fcitx_client.dbus;
    419     DBusConnection *conn = dbus->session_conn;
    420 
    421     dbus->connection_read_write(conn, 0);
    422 
    423     while (dbus->connection_dispatch(conn) == DBUS_DISPATCH_DATA_REMAINS) {
    424         /* Do nothing, actual work happens in DBus_MessageFilter */
    425         usleep(10);
    426     }
    427 }
    428 
    429 /* vi: set ts=4 sw=4 expandtab: */