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 | |
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* |
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 | |
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 |
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 && *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 | |
191 | static void |
192 | FcitxClientICCallMethod(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 | |
200 | static void SDLCALL |
201 | Fcitx_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 | |
220 | static SDL_bool |
221 | FcitxCreateInputContext(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 | |
249 | static SDL_bool |
250 | FcitxClientCreateIC(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 | |
282 | static Uint32 |
283 | Fcitx_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 | |
300 | SDL_bool |
301 | SDL_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 | |
313 | void |
314 | SDL_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 | |
323 | void |
324 | SDL_Fcitx_SetFocus(SDL_bool focused) |
325 | { |
326 | if (focused) { |
327 | FcitxClientICCallMethod(&fcitx_client, "FocusIn" ); |
328 | } else { |
329 | FcitxClientICCallMethod(&fcitx_client, "FocusOut" ); |
330 | } |
331 | } |
332 | |
333 | void |
334 | SDL_Fcitx_Reset(void) |
335 | { |
336 | FcitxClientICCallMethod(&fcitx_client, "Reset" ); |
337 | FcitxClientICCallMethod(&fcitx_client, "CloseIC" ); |
338 | } |
339 | |
340 | SDL_bool |
341 | SDL_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 | |
364 | void |
365 | SDL_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 | |
415 | void |
416 | SDL_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 | |