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