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