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