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