sdl

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

SDL_ibus.c (16850B)


      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 #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, 0, 0, 0 };
     46 static DBusConnection *ibus_conn = NULL;
     47 static char *ibus_addr_file = NULL;
     48 static 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     const char *struct_id = NULL;
     75     DBusMessageIter sub1, sub2;
     76 
     77     if (dbus->message_iter_get_arg_type(iter) != DBUS_TYPE_VARIANT) {
     78         return NULL;
     79     }
     80     
     81     dbus->message_iter_recurse(iter, &sub1);
     82     
     83     if (dbus->message_iter_get_arg_type(&sub1) != DBUS_TYPE_STRUCT) {
     84         return NULL;
     85     }
     86     
     87     dbus->message_iter_recurse(&sub1, &sub2);
     88     
     89     if (dbus->message_iter_get_arg_type(&sub2) != DBUS_TYPE_STRING) {
     90         return NULL;
     91     }
     92     
     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 DBusHandlerResult
    111 IBus_MessageHandler(DBusConnection *conn, DBusMessage *msg, void *user_data)
    112 {
    113     SDL_DBusContext *dbus = (SDL_DBusContext *)user_data;
    114         
    115     if (dbus->message_is_signal(msg, IBUS_INPUT_INTERFACE, "CommitText")) {
    116         DBusMessageIter iter;
    117         const char *text;
    118 
    119         dbus->message_iter_init(msg, &iter);
    120         
    121         text = IBus_GetVariantText(conn, &iter, dbus);
    122         if (text && *text) {
    123             char buf[SDL_TEXTINPUTEVENT_TEXT_SIZE];
    124             size_t text_bytes = SDL_strlen(text), i = 0;
    125             
    126             while (i < text_bytes) {
    127                 size_t sz = SDL_utf8strlcpy(buf, text+i, sizeof(buf));
    128                 SDL_SendKeyboardText(buf);
    129                 
    130                 i += sz;
    131             }
    132         }
    133         
    134         return DBUS_HANDLER_RESULT_HANDLED;
    135     }
    136     
    137     if (dbus->message_is_signal(msg, IBUS_INPUT_INTERFACE, "UpdatePreeditText")) {
    138         DBusMessageIter iter;
    139         const char *text;
    140 
    141         dbus->message_iter_init(msg, &iter);
    142         text = IBus_GetVariantText(conn, &iter, dbus);
    143         
    144         if (text) {
    145             char buf[SDL_TEXTEDITINGEVENT_TEXT_SIZE];
    146             size_t text_bytes = SDL_strlen(text), i = 0;
    147             size_t cursor = 0;
    148             
    149             do {
    150                 const size_t sz = SDL_utf8strlcpy(buf, text+i, sizeof(buf));
    151                 const size_t chars = SDL_utf8strlen(buf);
    152                 
    153                 SDL_SendEditingText(buf, cursor, chars);
    154 
    155                 i += sz;
    156                 cursor += chars;
    157             } while (i < text_bytes);
    158         }
    159         
    160         SDL_IBus_UpdateTextRect(NULL);
    161         
    162         return DBUS_HANDLER_RESULT_HANDLED;
    163     }
    164     
    165     if (dbus->message_is_signal(msg, IBUS_INPUT_INTERFACE, "HidePreeditText")) {
    166         SDL_SendEditingText("", 0, 0);
    167         return DBUS_HANDLER_RESULT_HANDLED;
    168     }
    169     
    170     return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
    171 }
    172 
    173 static char *
    174 IBus_ReadAddressFromFile(const char *file_path)
    175 {
    176     char addr_buf[1024];
    177     SDL_bool success = SDL_FALSE;
    178     FILE *addr_file;
    179 
    180     addr_file = fopen(file_path, "r");
    181     if (!addr_file) {
    182         return NULL;
    183     }
    184 
    185     while (fgets(addr_buf, sizeof(addr_buf), addr_file)) {
    186         if (SDL_strncmp(addr_buf, "IBUS_ADDRESS=", sizeof("IBUS_ADDRESS=")-1) == 0) {
    187             size_t sz = SDL_strlen(addr_buf);
    188             if (addr_buf[sz-1] == '\n') addr_buf[sz-1] = 0;
    189             if (addr_buf[sz-2] == '\r') addr_buf[sz-2] = 0;
    190             success = SDL_TRUE;
    191             break;
    192         }
    193     }
    194 
    195     fclose(addr_file);
    196 
    197     if (success) {
    198         return SDL_strdup(addr_buf + (sizeof("IBUS_ADDRESS=") - 1));
    199     } else {
    200         return NULL;
    201     }
    202 }
    203 
    204 static char *
    205 IBus_GetDBusAddressFilename(void)
    206 {
    207     SDL_DBusContext *dbus;
    208     const char *disp_env;
    209     char config_dir[PATH_MAX];
    210     char *display = NULL;
    211     const char *addr;
    212     const char *conf_env;
    213     char *key;
    214     char file_path[PATH_MAX];
    215     const char *host;
    216     char *disp_num, *screen_num;
    217 
    218     if (ibus_addr_file) {
    219         return SDL_strdup(ibus_addr_file);
    220     }
    221     
    222     dbus = SDL_DBus_GetContext();
    223     if (!dbus) {
    224         return NULL;
    225     }
    226     
    227     /* Use this environment variable if it exists. */
    228     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     disp_env = SDL_getenv("DISPLAY");
    236 
    237     if (!disp_env || !*disp_env) {
    238         display = SDL_strdup(":0.0");
    239     } else {
    240         display = SDL_strdup(disp_env);
    241     }
    242     
    243     host = display;
    244     disp_num   = SDL_strrchr(display, ':');
    245     screen_num = SDL_strrchr(display, '.');
    246     
    247     if (!disp_num) {
    248         SDL_free(display);
    249         return NULL;
    250     }
    251     
    252     *disp_num = 0;
    253     disp_num++;
    254     
    255     if (screen_num) {
    256         *screen_num = 0;
    257     }
    258     
    259     if (!*host) {
    260         host = "unix";
    261     }
    262         
    263     SDL_memset(config_dir, 0, sizeof(config_dir));
    264     
    265     conf_env = SDL_getenv("XDG_CONFIG_HOME");
    266     if (conf_env && *conf_env) {
    267         SDL_strlcpy(config_dir, conf_env, sizeof(config_dir));
    268     } else {
    269         const char *home_env = SDL_getenv("HOME");
    270         if (!home_env || !*home_env) {
    271             SDL_free(display);
    272             return NULL;
    273         }
    274         SDL_snprintf(config_dir, sizeof(config_dir), "%s/.config", home_env);
    275     }
    276     
    277     key = dbus->get_local_machine_id();
    278 
    279     SDL_memset(file_path, 0, sizeof(file_path));
    280     SDL_snprintf(file_path, sizeof(file_path), "%s/ibus/bus/%s-%s-%s", 
    281                                                config_dir, key, host, disp_num);
    282     dbus->free(key);
    283     SDL_free(display);
    284     
    285     return SDL_strdup(file_path);
    286 }
    287 
    288 static SDL_bool IBus_CheckConnection(SDL_DBusContext *dbus);
    289 
    290 static void SDLCALL
    291 IBus_SetCapabilities(void *data, const char *name, const char *old_val,
    292                                                    const char *internal_editing)
    293 {
    294     SDL_DBusContext *dbus = SDL_DBus_GetContext();
    295     
    296     if (IBus_CheckConnection(dbus)) {
    297         Uint32 caps = IBUS_CAP_FOCUS;
    298         if (!(internal_editing && *internal_editing == '1')) {
    299             caps |= IBUS_CAP_PREEDIT_TEXT;
    300         }
    301 
    302         SDL_DBus_CallVoidMethodOnConnection(ibus_conn, IBUS_SERVICE, input_ctx_path, IBUS_INPUT_INTERFACE, "SetCapabilities",
    303                                 DBUS_TYPE_UINT32, &caps, DBUS_TYPE_INVALID);
    304     }
    305 }
    306 
    307 
    308 static SDL_bool
    309 IBus_SetupConnection(SDL_DBusContext *dbus, const char* addr)
    310 {
    311     const char *client_name = "SDL2_Application";
    312     const char *path = NULL;
    313     SDL_bool result = SDL_FALSE;
    314     DBusObjectPathVTable ibus_vtable;
    315 
    316     SDL_zero(ibus_vtable);
    317     ibus_vtable.message_function = &IBus_MessageHandler;
    318 
    319     ibus_conn = dbus->connection_open_private(addr, NULL);
    320 
    321     if (!ibus_conn) {
    322         return SDL_FALSE;
    323     }
    324 
    325     dbus->connection_flush(ibus_conn);
    326     
    327     if (!dbus->bus_register(ibus_conn, NULL)) {
    328         ibus_conn = NULL;
    329         return SDL_FALSE;
    330     }
    331     
    332     dbus->connection_flush(ibus_conn);
    333 
    334     if (SDL_DBus_CallMethodOnConnection(ibus_conn, IBUS_SERVICE, IBUS_PATH, IBUS_INTERFACE, "CreateInputContext",
    335             DBUS_TYPE_STRING, &client_name, DBUS_TYPE_INVALID,
    336             DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID)) {
    337         SDL_free(input_ctx_path);
    338         input_ctx_path = SDL_strdup(path);
    339         SDL_AddHintCallback(SDL_HINT_IME_INTERNAL_EDITING, IBus_SetCapabilities, NULL);
    340         
    341         dbus->bus_add_match(ibus_conn, "type='signal',interface='org.freedesktop.IBus.InputContext'", NULL);
    342         dbus->connection_try_register_object_path(ibus_conn, input_ctx_path, &ibus_vtable, dbus, NULL);
    343         dbus->connection_flush(ibus_conn);
    344         result = SDL_TRUE;
    345     }
    346 
    347     SDL_IBus_SetFocus(SDL_GetKeyboardFocus() != NULL);
    348     SDL_IBus_UpdateTextRect(NULL);
    349     
    350     return result;
    351 }
    352 
    353 static SDL_bool
    354 IBus_CheckConnection(SDL_DBusContext *dbus)
    355 {
    356     if (!dbus) return SDL_FALSE;
    357     
    358     if (ibus_conn && dbus->connection_get_is_connected(ibus_conn)) {
    359         return SDL_TRUE;
    360     }
    361     
    362     if (inotify_fd > 0 && inotify_wd > 0) {
    363         char buf[1024];
    364         ssize_t readsize = read(inotify_fd, buf, sizeof(buf));
    365         if (readsize > 0) {
    366         
    367             char *p;
    368             SDL_bool file_updated = SDL_FALSE;
    369             
    370             for (p = buf; p < buf + readsize; /**/) {
    371                 struct inotify_event *event = (struct inotify_event*) p;
    372                 if (event->len > 0) {
    373                     char *addr_file_no_path = SDL_strrchr(ibus_addr_file, '/');
    374                     if (!addr_file_no_path) return SDL_FALSE;
    375                  
    376                     if (SDL_strcmp(addr_file_no_path + 1, event->name) == 0) {
    377                         file_updated = SDL_TRUE;
    378                         break;
    379                     }
    380                 }
    381                 
    382                 p += sizeof(struct inotify_event) + event->len;
    383             }
    384             
    385             if (file_updated) {
    386                 char *addr = IBus_ReadAddressFromFile(ibus_addr_file);
    387                 if (addr) {
    388                     SDL_bool result = IBus_SetupConnection(dbus, addr);
    389                     SDL_free(addr);
    390                     return result;
    391                 }
    392             }
    393         }
    394     }
    395     
    396     return SDL_FALSE;
    397 }
    398 
    399 SDL_bool
    400 SDL_IBus_Init(void)
    401 {
    402     SDL_bool result = SDL_FALSE;
    403     SDL_DBusContext *dbus = SDL_DBus_GetContext();
    404     
    405     if (dbus) {
    406         char *addr_file = IBus_GetDBusAddressFilename();
    407         char *addr;
    408         char *addr_file_dir;
    409 
    410         if (!addr_file) {
    411             return SDL_FALSE;
    412         }
    413         
    414         /* !!! FIXME: if ibus_addr_file != NULL, this will overwrite it and leak (twice!) */
    415         ibus_addr_file = SDL_strdup(addr_file);
    416         
    417         addr = IBus_ReadAddressFromFile(addr_file);
    418         if (!addr) {
    419             SDL_free(addr_file);
    420             return SDL_FALSE;
    421         }
    422         
    423         if (inotify_fd < 0) {
    424             inotify_fd = inotify_init();
    425             fcntl(inotify_fd, F_SETFL, O_NONBLOCK);
    426         }
    427         
    428         addr_file_dir = SDL_strrchr(addr_file, '/');
    429         if (addr_file_dir) {
    430             *addr_file_dir = 0;
    431         }
    432         
    433         inotify_wd = inotify_add_watch(inotify_fd, addr_file, IN_CREATE | IN_MODIFY);
    434         SDL_free(addr_file);
    435         
    436         if (addr) {
    437             result = IBus_SetupConnection(dbus, addr);
    438             SDL_free(addr);
    439         }
    440     }
    441     
    442     return result;
    443 }
    444 
    445 void
    446 SDL_IBus_Quit(void)
    447 {   
    448     SDL_DBusContext *dbus;
    449 
    450     if (input_ctx_path) {
    451         SDL_free(input_ctx_path);
    452         input_ctx_path = NULL;
    453     }
    454     
    455     if (ibus_addr_file) {
    456         SDL_free(ibus_addr_file);
    457         ibus_addr_file = NULL;
    458     }
    459     
    460     dbus = SDL_DBus_GetContext();
    461     
    462     if (dbus && ibus_conn) {
    463         dbus->connection_close(ibus_conn);
    464         dbus->connection_unref(ibus_conn);
    465     }
    466     
    467     if (inotify_fd > 0 && inotify_wd > 0) {
    468         inotify_rm_watch(inotify_fd, inotify_wd);
    469         inotify_wd = -1;
    470     }
    471     
    472     SDL_DelHintCallback(SDL_HINT_IME_INTERNAL_EDITING, IBus_SetCapabilities, NULL);
    473     
    474     SDL_memset(&ibus_cursor_rect, 0, sizeof(ibus_cursor_rect));
    475 }
    476 
    477 static void
    478 IBus_SimpleMessage(const char *method)
    479 {   
    480     SDL_DBusContext *dbus = SDL_DBus_GetContext();
    481     
    482     if (IBus_CheckConnection(dbus)) {
    483         SDL_DBus_CallVoidMethodOnConnection(ibus_conn, IBUS_SERVICE, input_ctx_path, IBUS_INPUT_INTERFACE, method, DBUS_TYPE_INVALID);
    484     }
    485 }
    486 
    487 void
    488 SDL_IBus_SetFocus(SDL_bool focused)
    489 { 
    490     const char *method = focused ? "FocusIn" : "FocusOut";
    491     IBus_SimpleMessage(method);
    492 }
    493 
    494 void
    495 SDL_IBus_Reset(void)
    496 {
    497     IBus_SimpleMessage("Reset");
    498 }
    499 
    500 SDL_bool
    501 SDL_IBus_ProcessKeyEvent(Uint32 keysym, Uint32 keycode)
    502 { 
    503     Uint32 result = 0;
    504     SDL_DBusContext *dbus = SDL_DBus_GetContext();
    505     
    506     if (IBus_CheckConnection(dbus)) {
    507         Uint32 mods = IBus_ModState();
    508         if (!SDL_DBus_CallMethodOnConnection(ibus_conn, IBUS_SERVICE, input_ctx_path, IBUS_INPUT_INTERFACE, "ProcessKeyEvent",
    509                 DBUS_TYPE_UINT32, &keysym, DBUS_TYPE_UINT32, &keycode, DBUS_TYPE_UINT32, &mods, DBUS_TYPE_INVALID,
    510                 DBUS_TYPE_BOOLEAN, &result, DBUS_TYPE_INVALID)) {
    511             result = 0;
    512         }
    513     }
    514     
    515     SDL_IBus_UpdateTextRect(NULL);
    516 
    517     return result ? SDL_TRUE : SDL_FALSE;
    518 }
    519 
    520 void
    521 SDL_IBus_UpdateTextRect(SDL_Rect *rect)
    522 {
    523     SDL_Window *focused_win;
    524     SDL_SysWMinfo info;
    525     int x = 0, y = 0;
    526     SDL_DBusContext *dbus;
    527 
    528     if (rect) {
    529         SDL_memcpy(&ibus_cursor_rect, rect, sizeof(ibus_cursor_rect));
    530     }
    531 
    532     focused_win = SDL_GetKeyboardFocus();
    533     if (!focused_win) {
    534         return;
    535     }
    536 
    537     SDL_VERSION(&info.version);
    538     if (!SDL_GetWindowWMInfo(focused_win, &info)) {
    539         return;
    540     }
    541 
    542     SDL_GetWindowPosition(focused_win, &x, &y);
    543    
    544 #if SDL_VIDEO_DRIVER_X11    
    545     if (info.subsystem == SDL_SYSWM_X11) {
    546         SDL_DisplayData *displaydata = (SDL_DisplayData *) SDL_GetDisplayForWindow(focused_win)->driverdata;
    547             
    548         Display *x_disp = info.info.x11.display;
    549         Window x_win = info.info.x11.window;
    550         int x_screen = displaydata->screen;
    551         Window unused;
    552             
    553         X11_XTranslateCoordinates(x_disp, x_win, RootWindow(x_disp, x_screen), 0, 0, &x, &y, &unused);
    554     }
    555 #endif
    556 
    557     x += ibus_cursor_rect.x;
    558     y += ibus_cursor_rect.y;
    559         
    560     dbus = SDL_DBus_GetContext();
    561     
    562     if (IBus_CheckConnection(dbus)) {
    563         SDL_DBus_CallVoidMethodOnConnection(ibus_conn, IBUS_SERVICE, input_ctx_path, IBUS_INPUT_INTERFACE, "SetCursorLocation",
    564                 DBUS_TYPE_INT32, &x, DBUS_TYPE_INT32, &y, DBUS_TYPE_INT32, &ibus_cursor_rect.w, DBUS_TYPE_INT32, &ibus_cursor_rect.h, DBUS_TYPE_INVALID);
    565     }
    566 }
    567 
    568 void
    569 SDL_IBus_PumpEvents(void)
    570 {
    571     SDL_DBusContext *dbus = SDL_DBus_GetContext();
    572     
    573     if (IBus_CheckConnection(dbus)) {
    574         dbus->connection_read_write(ibus_conn, 0);
    575     
    576         while (dbus->connection_dispatch(ibus_conn) == DBUS_DISPATCH_DATA_REMAINS) {
    577             /* Do nothing, actual work happens in IBus_MessageHandler */
    578         }
    579     }
    580 }
    581 
    582 #endif
    583 
    584 /* vi: set ts=4 sw=4 expandtab: */