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*
GetAppName()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 
Fcitx_GetPreeditString(SDL_DBusContext * dbus,DBusMessage * msg,char ** ret)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
DBus_MessageFilter(DBusConnection * conn,DBusMessage * msg,void * data)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)
148             SDL_SendKeyboardText(text);
149 
150         return DBUS_HANDLER_RESULT_HANDLED;
151     }
152 
153     if (dbus->message_is_signal(msg, FCITX_IC_DBUS_INTERFACE, "UpdateFormattedPreedit")) {
154         char *text = NULL;
155         size_t text_bytes = Fcitx_GetPreeditString(dbus, msg, &text);
156         if (text_bytes) {
157             char buf[SDL_TEXTEDITINGEVENT_TEXT_SIZE];
158             size_t i = 0;
159             size_t cursor = 0;
160 
161             while (i < text_bytes) {
162                 const size_t sz = SDL_utf8strlcpy(buf, text + i, sizeof(buf));
163                 const size_t chars = SDL_utf8strlen(buf);
164 
165                 SDL_SendEditingText(buf, cursor, chars);
166 
167                 i += sz;
168                 cursor += chars;
169             }
170             SDL_free(text);
171         } else {
172             SDL_SendEditingText("", 0, 0);
173         }
174 
175         SDL_Fcitx_UpdateTextRect(NULL);
176         return DBUS_HANDLER_RESULT_HANDLED;
177     }
178 
179     return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
180 }
181 
182 static void
FcitxClientICCallMethod(FcitxClient * client,const char * method)183 FcitxClientICCallMethod(FcitxClient *client, const char *method)
184 {
185     if (!client->ic_path) {
186         return;
187     }
188     SDL_DBus_CallVoidMethod(FCITX_DBUS_SERVICE, client->ic_path, FCITX_IC_DBUS_INTERFACE, method, DBUS_TYPE_INVALID);
189 }
190 
191 static void SDLCALL
Fcitx_SetCapabilities(void * data,const char * name,const char * old_val,const char * internal_editing)192 Fcitx_SetCapabilities(void *data,
193         const char *name,
194         const char *old_val,
195         const char *internal_editing)
196 {
197     FcitxClient *client = (FcitxClient *)data;
198     Uint32 caps = 0;
199     if (!client->ic_path) {
200         return;
201     }
202 
203     if (!(internal_editing && *internal_editing == '1')) {
204         caps |= (1 << 1); /* Preedit Flag */
205         caps |= (1 << 4); /* Formatted Preedit Flag */
206     }
207 
208     SDL_DBus_CallVoidMethod(FCITX_DBUS_SERVICE, client->ic_path, FCITX_IC_DBUS_INTERFACE, "SetCapability", DBUS_TYPE_UINT64, &caps, DBUS_TYPE_INVALID);
209 }
210 
211 static SDL_bool
FcitxCreateInputContext(SDL_DBusContext * dbus,const char * appname,char ** ic_path)212 FcitxCreateInputContext(SDL_DBusContext* dbus, const char *appname, char **ic_path) {
213     const char *program = "program";
214     SDL_bool retval = SDL_FALSE;
215     if (dbus->session_conn) {
216         DBusMessage *msg = dbus->message_new_method_call(FCITX_DBUS_SERVICE, FCITX_IM_DBUS_PATH, FCITX_IM_DBUS_INTERFACE, "CreateInputContext");
217         if (msg) {
218             DBusMessage *reply = NULL;
219             DBusMessageIter args, array, sub;
220             dbus->message_iter_init_append(msg, &args);
221             dbus->message_iter_open_container(&args, DBUS_TYPE_ARRAY, "(ss)", &array);
222             dbus->message_iter_open_container(&array, DBUS_TYPE_STRUCT, 0, &sub);
223             dbus->message_iter_append_basic(&sub, DBUS_TYPE_STRING, &program);
224             dbus->message_iter_append_basic(&sub, DBUS_TYPE_STRING, &appname);
225             dbus->message_iter_close_container(&array, &sub);
226             dbus->message_iter_close_container(&args, &array);
227             reply = dbus->connection_send_with_reply_and_block(dbus->session_conn, msg, 300, NULL);
228             if (reply) {
229                 if (dbus->message_get_args(reply, NULL, DBUS_TYPE_OBJECT_PATH, ic_path, DBUS_TYPE_INVALID)) {
230                     retval = SDL_TRUE;
231                 }
232                 dbus->message_unref(reply);
233             }
234             dbus->message_unref(msg);
235         }
236     }
237     return retval;
238 }
239 
240 static SDL_bool
FcitxClientCreateIC(FcitxClient * client)241 FcitxClientCreateIC(FcitxClient *client)
242 {
243     char *appname = GetAppName();
244     char *ic_path = NULL;
245     SDL_DBusContext *dbus = client->dbus;
246 
247     /* SDL_DBus_CallMethod cannot handle a(ss) type, call dbus function directly */
248     if (!FcitxCreateInputContext(dbus, appname, &ic_path)) {
249         ic_path = NULL;  /* just in case. */
250     }
251 
252     SDL_free(appname);
253 
254     if (ic_path) {
255         SDL_free(client->ic_path);
256         client->ic_path = SDL_strdup(ic_path);
257 
258         dbus->bus_add_match(dbus->session_conn,
259                 "type='signal', interface='org.fcitx.Fcitx.InputContext1'",
260                 NULL);
261         dbus->connection_add_filter(dbus->session_conn,
262                 &DBus_MessageFilter, dbus,
263                 NULL);
264         dbus->connection_flush(dbus->session_conn);
265 
266         SDL_AddHintCallback(SDL_HINT_IME_INTERNAL_EDITING, Fcitx_SetCapabilities, client);
267         return SDL_TRUE;
268     }
269 
270     return SDL_FALSE;
271 }
272 
273 static Uint32
Fcitx_ModState(void)274 Fcitx_ModState(void)
275 {
276     Uint32 fcitx_mods = 0;
277     SDL_Keymod sdl_mods = SDL_GetModState();
278 
279     if (sdl_mods & KMOD_SHIFT) fcitx_mods |= (1 << 0);
280     if (sdl_mods & KMOD_CAPS)   fcitx_mods |= (1 << 1);
281     if (sdl_mods & KMOD_CTRL)  fcitx_mods |= (1 << 2);
282     if (sdl_mods & KMOD_ALT)   fcitx_mods |= (1 << 3);
283     if (sdl_mods & KMOD_NUM)    fcitx_mods |= (1 << 4);
284     if (sdl_mods & KMOD_MODE)   fcitx_mods |= (1 << 7);
285     if (sdl_mods & KMOD_LGUI)   fcitx_mods |= (1 << 6);
286     if (sdl_mods & KMOD_RGUI)   fcitx_mods |= (1 << 28);
287 
288     return fcitx_mods;
289 }
290 
291 SDL_bool
SDL_Fcitx_Init()292 SDL_Fcitx_Init()
293 {
294     fcitx_client.dbus = SDL_DBus_GetContext();
295 
296     fcitx_client.cursor_rect.x = -1;
297     fcitx_client.cursor_rect.y = -1;
298     fcitx_client.cursor_rect.w = 0;
299     fcitx_client.cursor_rect.h = 0;
300 
301     return FcitxClientCreateIC(&fcitx_client);
302 }
303 
304 void
SDL_Fcitx_Quit()305 SDL_Fcitx_Quit()
306 {
307     FcitxClientICCallMethod(&fcitx_client, "DestroyIC");
308     if (fcitx_client.ic_path) {
309         SDL_free(fcitx_client.ic_path);
310         fcitx_client.ic_path = NULL;
311     }
312 }
313 
314 void
SDL_Fcitx_SetFocus(SDL_bool focused)315 SDL_Fcitx_SetFocus(SDL_bool focused)
316 {
317     if (focused) {
318         FcitxClientICCallMethod(&fcitx_client, "FocusIn");
319     } else {
320         FcitxClientICCallMethod(&fcitx_client, "FocusOut");
321     }
322 }
323 
324 void
SDL_Fcitx_Reset(void)325 SDL_Fcitx_Reset(void)
326 {
327     FcitxClientICCallMethod(&fcitx_client, "Reset");
328     FcitxClientICCallMethod(&fcitx_client, "CloseIC");
329 }
330 
331 SDL_bool
SDL_Fcitx_ProcessKeyEvent(Uint32 keysym,Uint32 keycode)332 SDL_Fcitx_ProcessKeyEvent(Uint32 keysym, Uint32 keycode)
333 {
334     Uint32 state = Fcitx_ModState();
335     Uint32 handled = SDL_FALSE;
336     Uint32 is_release = SDL_FALSE;
337     Uint32 event_time = 0;
338 
339     if (!fcitx_client.ic_path) {
340         return SDL_FALSE;
341     }
342 
343     if (SDL_DBus_CallMethod(FCITX_DBUS_SERVICE, fcitx_client.ic_path, FCITX_IC_DBUS_INTERFACE, "ProcessKeyEvent",
344             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,
345             DBUS_TYPE_BOOLEAN, &handled, DBUS_TYPE_INVALID)) {
346         if (handled) {
347             SDL_Fcitx_UpdateTextRect(NULL);
348             return SDL_TRUE;
349         }
350     }
351 
352     return SDL_FALSE;
353 }
354 
355 void
SDL_Fcitx_UpdateTextRect(SDL_Rect * rect)356 SDL_Fcitx_UpdateTextRect(SDL_Rect *rect)
357 {
358     SDL_Window *focused_win = NULL;
359     SDL_SysWMinfo info;
360     int x = 0, y = 0;
361     SDL_Rect *cursor = &fcitx_client.cursor_rect;
362 
363     if (rect) {
364         SDL_memcpy(cursor, rect, sizeof(SDL_Rect));
365     }
366 
367     focused_win = SDL_GetKeyboardFocus();
368     if (!focused_win) {
369         return ;
370     }
371 
372     SDL_VERSION(&info.version);
373     if (!SDL_GetWindowWMInfo(focused_win, &info)) {
374         return;
375     }
376 
377     SDL_GetWindowPosition(focused_win, &x, &y);
378 
379 #if SDL_VIDEO_DRIVER_X11
380     if (info.subsystem == SDL_SYSWM_X11) {
381         SDL_DisplayData *displaydata = (SDL_DisplayData *) SDL_GetDisplayForWindow(focused_win)->driverdata;
382 
383         Display *x_disp = info.info.x11.display;
384         Window x_win = info.info.x11.window;
385         int x_screen = displaydata->screen;
386         Window unused;
387         X11_XTranslateCoordinates(x_disp, x_win, RootWindow(x_disp, x_screen), 0, 0, &x, &y, &unused);
388     }
389 #endif
390 
391     if (cursor->x == -1 && cursor->y == -1 && cursor->w == 0 && cursor->h == 0) {
392         /* move to bottom left */
393         int w = 0, h = 0;
394         SDL_GetWindowSize(focused_win, &w, &h);
395         cursor->x = 0;
396         cursor->y = h;
397     }
398 
399     x += cursor->x;
400     y += cursor->y;
401 
402     SDL_DBus_CallVoidMethod(FCITX_DBUS_SERVICE, fcitx_client.ic_path, FCITX_IC_DBUS_INTERFACE, "SetCursorRect",
403         DBUS_TYPE_INT32, &x, DBUS_TYPE_INT32, &y, DBUS_TYPE_INT32, &cursor->w, DBUS_TYPE_INT32, &cursor->h, DBUS_TYPE_INVALID);
404 }
405 
406 void
SDL_Fcitx_PumpEvents(void)407 SDL_Fcitx_PumpEvents(void)
408 {
409     SDL_DBusContext *dbus = fcitx_client.dbus;
410     DBusConnection *conn = dbus->session_conn;
411 
412     dbus->connection_read_write(conn, 0);
413 
414     while (dbus->connection_dispatch(conn) == DBUS_DISPATCH_DATA_REMAINS) {
415         /* Do nothing, actual work happens in DBus_MessageFilter */
416         usleep(10);
417     }
418 }
419 
420 /* vi: set ts=4 sw=4 expandtab: */
421