SDL_fcitx.c 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490
  1. /*
  2. Simple DirectMedia Layer
  3. Copyright (C) 1997-2022 Sam Lantinga <[email protected]>
  4. This software is provided 'as-is', without any express or implied
  5. warranty. In no event will the authors be held liable for any damages
  6. arising from the use of this software.
  7. Permission is granted to anyone to use this software for any purpose,
  8. including commercial applications, and to alter it and redistribute it
  9. freely, subject to the following restrictions:
  10. 1. The origin of this software must not be misrepresented; you must not
  11. claim that you wrote the original software. If you use this software
  12. in a product, an acknowledgment in the product documentation would be
  13. appreciated but is not required.
  14. 2. Altered source versions must be plainly marked as such, and must not be
  15. misrepresented as being the original software.
  16. 3. This notice may not be removed or altered from any source distribution.
  17. */
  18. #include "../../SDL_internal.h"
  19. #include <unistd.h>
  20. #include "SDL_fcitx.h"
  21. #include "SDL_keycode.h"
  22. #include "SDL_keyboard.h"
  23. #include "../../events/SDL_keyboard_c.h"
  24. #include "SDL_dbus.h"
  25. #include "SDL_syswm.h"
  26. #if SDL_VIDEO_DRIVER_X11
  27. # include "../../video/x11/SDL_x11video.h"
  28. #endif
  29. #include "SDL_hints.h"
  30. #define FCITX_DBUS_SERVICE "org.freedesktop.portal.Fcitx"
  31. #define FCITX_IM_DBUS_PATH "/org/freedesktop/portal/inputmethod"
  32. #define FCITX_IM_DBUS_INTERFACE "org.fcitx.Fcitx.InputMethod1"
  33. #define FCITX_IC_DBUS_INTERFACE "org.fcitx.Fcitx.InputContext1"
  34. #define DBUS_TIMEOUT 500
  35. typedef struct _FcitxClient
  36. {
  37. SDL_DBusContext *dbus;
  38. char *ic_path;
  39. int id;
  40. SDL_Rect cursor_rect;
  41. } FcitxClient;
  42. static FcitxClient fcitx_client;
  43. static char*
  44. GetAppName()
  45. {
  46. #if defined(__LINUX__) || defined(__FREEBSD__)
  47. char *spot;
  48. char procfile[1024];
  49. char linkfile[1024];
  50. int linksize;
  51. #if defined(__LINUX__)
  52. SDL_snprintf(procfile, sizeof(procfile), "/proc/%d/exe", getpid());
  53. #elif defined(__FREEBSD__)
  54. SDL_snprintf(procfile, sizeof(procfile), "/proc/%d/file", getpid());
  55. #endif
  56. linksize = readlink(procfile, linkfile, sizeof(linkfile) - 1);
  57. if (linksize > 0) {
  58. linkfile[linksize] = '\0';
  59. spot = SDL_strrchr(linkfile, '/');
  60. if (spot) {
  61. return SDL_strdup(spot + 1);
  62. } else {
  63. return SDL_strdup(linkfile);
  64. }
  65. }
  66. #endif /* __LINUX__ || __FREEBSD__ */
  67. return SDL_strdup("SDL_App");
  68. }
  69. static size_t
  70. Fcitx_GetPreeditString(SDL_DBusContext *dbus,
  71. DBusMessage *msg,
  72. char **ret,
  73. Sint32 *start_pos,
  74. Sint32 *end_pos)
  75. {
  76. char *text = NULL, *subtext;
  77. size_t text_bytes = 0;
  78. DBusMessageIter iter, array, sub;
  79. Sint32 p_start_pos = -1;
  80. Sint32 p_end_pos = -1;
  81. dbus->message_iter_init(msg, &iter);
  82. /* Message type is a(si)i, we only need string part */
  83. if (dbus->message_iter_get_arg_type(&iter) == DBUS_TYPE_ARRAY) {
  84. size_t pos = 0;
  85. /* First pass: calculate string length */
  86. dbus->message_iter_recurse(&iter, &array);
  87. while (dbus->message_iter_get_arg_type(&array) == DBUS_TYPE_STRUCT) {
  88. dbus->message_iter_recurse(&array, &sub);
  89. if (dbus->message_iter_get_arg_type(&sub) == DBUS_TYPE_STRING) {
  90. dbus->message_iter_get_basic(&sub, &subtext);
  91. if (subtext && *subtext) {
  92. text_bytes += SDL_strlen(subtext);
  93. }
  94. }
  95. dbus->message_iter_next(&sub);
  96. if (dbus->message_iter_get_arg_type(&sub) == DBUS_TYPE_INT32 && p_end_pos == -1) {
  97. /* Type is a bit field defined as follows: */
  98. /* bit 3: Underline, bit 4: HighLight, bit 5: DontCommit, */
  99. /* bit 6: Bold, bit 7: Strike, bit 8: Italic */
  100. Sint32 type;
  101. dbus->message_iter_get_basic(&sub, &type);
  102. /* We only consider highlight */
  103. if (type & (1 << 4)) {
  104. if (p_start_pos == -1) {
  105. p_start_pos = pos;
  106. }
  107. } else if (p_start_pos != -1 && p_end_pos == -1) {
  108. p_end_pos = pos;
  109. }
  110. }
  111. dbus->message_iter_next(&array);
  112. if (subtext && *subtext) {
  113. pos += SDL_utf8strlen(subtext);
  114. }
  115. }
  116. if (p_start_pos != -1 && p_end_pos == -1) {
  117. p_end_pos = pos;
  118. }
  119. if (text_bytes) {
  120. text = SDL_malloc(text_bytes + 1);
  121. }
  122. if (text) {
  123. char* pivot = text;
  124. /* Second pass: join all the sub string */
  125. dbus->message_iter_recurse(&iter, &array);
  126. while (dbus->message_iter_get_arg_type(&array) == DBUS_TYPE_STRUCT) {
  127. dbus->message_iter_recurse(&array, &sub);
  128. if (dbus->message_iter_get_arg_type(&sub) == DBUS_TYPE_STRING) {
  129. dbus->message_iter_get_basic(&sub, &subtext);
  130. if (subtext && *subtext) {
  131. size_t length = SDL_strlen(subtext);
  132. SDL_strlcpy(pivot, subtext, length + 1);
  133. pivot += length;
  134. }
  135. }
  136. dbus->message_iter_next(&array);
  137. }
  138. } else {
  139. text_bytes = 0;
  140. }
  141. }
  142. *ret = text;
  143. *start_pos = p_start_pos;
  144. *end_pos = p_end_pos;
  145. return text_bytes;
  146. }
  147. static Sint32
  148. Fcitx_GetPreeditCursorByte(SDL_DBusContext *dbus, DBusMessage *msg)
  149. {
  150. Sint32 byte = -1;
  151. DBusMessageIter iter;
  152. dbus->message_iter_init(msg, &iter);
  153. dbus->message_iter_next(&iter);
  154. if (dbus->message_iter_get_arg_type(&iter) != DBUS_TYPE_INT32) {
  155. return -1;
  156. }
  157. dbus->message_iter_get_basic(&iter, &byte);
  158. return byte;
  159. }
  160. static DBusHandlerResult
  161. DBus_MessageFilter(DBusConnection *conn, DBusMessage *msg, void *data)
  162. {
  163. SDL_DBusContext *dbus = (SDL_DBusContext *)data;
  164. if (dbus->message_is_signal(msg, FCITX_IC_DBUS_INTERFACE, "CommitString")) {
  165. DBusMessageIter iter;
  166. const char *text = NULL;
  167. dbus->message_iter_init(msg, &iter);
  168. dbus->message_iter_get_basic(&iter, &text);
  169. if (text && *text) {
  170. char buf[SDL_TEXTINPUTEVENT_TEXT_SIZE];
  171. size_t text_bytes = SDL_strlen(text), i = 0;
  172. while (i < text_bytes) {
  173. size_t sz = SDL_utf8strlcpy(buf, text+i, sizeof(buf));
  174. SDL_SendKeyboardText(buf);
  175. i += sz;
  176. }
  177. }
  178. return DBUS_HANDLER_RESULT_HANDLED;
  179. }
  180. if (dbus->message_is_signal(msg, FCITX_IC_DBUS_INTERFACE, "UpdateFormattedPreedit")) {
  181. char *text = NULL;
  182. Sint32 start_pos, end_pos;
  183. size_t text_bytes = Fcitx_GetPreeditString(dbus, msg, &text, &start_pos, &end_pos);
  184. if (text_bytes) {
  185. if (SDL_GetHintBoolean(SDL_HINT_IME_SUPPORT_EXTENDED_TEXT, SDL_FALSE)) {
  186. if (start_pos == -1) {
  187. Sint32 byte_pos = Fcitx_GetPreeditCursorByte(dbus, msg);
  188. start_pos = byte_pos >= 0 ? SDL_utf8strnlen(text, byte_pos) : -1;
  189. }
  190. SDL_SendEditingText(text, start_pos, end_pos >= 0 ? end_pos - start_pos : -1);
  191. } else {
  192. char buf[SDL_TEXTEDITINGEVENT_TEXT_SIZE];
  193. size_t i = 0;
  194. size_t cursor = 0;
  195. while (i < text_bytes) {
  196. const size_t sz = SDL_utf8strlcpy(buf, text + i, sizeof(buf));
  197. const size_t chars = SDL_utf8strlen(buf);
  198. SDL_SendEditingText(buf, cursor, chars);
  199. i += sz;
  200. cursor += chars;
  201. }
  202. }
  203. SDL_free(text);
  204. } else {
  205. SDL_SendEditingText("", 0, 0);
  206. }
  207. SDL_Fcitx_UpdateTextRect(NULL);
  208. return DBUS_HANDLER_RESULT_HANDLED;
  209. }
  210. return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
  211. }
  212. static void
  213. FcitxClientICCallMethod(FcitxClient *client, const char *method)
  214. {
  215. if (!client->ic_path) {
  216. return;
  217. }
  218. SDL_DBus_CallVoidMethod(FCITX_DBUS_SERVICE, client->ic_path, FCITX_IC_DBUS_INTERFACE, method, DBUS_TYPE_INVALID);
  219. }
  220. static void SDLCALL
  221. Fcitx_SetCapabilities(void *data,
  222. const char *name,
  223. const char *old_val,
  224. const char *internal_editing)
  225. {
  226. FcitxClient *client = (FcitxClient *)data;
  227. Uint64 caps = 0;
  228. if (!client->ic_path) {
  229. return;
  230. }
  231. if (!(internal_editing && *internal_editing == '1')) {
  232. caps |= (1 << 1); /* Preedit Flag */
  233. caps |= (1 << 4); /* Formatted Preedit Flag */
  234. }
  235. SDL_DBus_CallVoidMethod(FCITX_DBUS_SERVICE, client->ic_path, FCITX_IC_DBUS_INTERFACE, "SetCapability", DBUS_TYPE_UINT64, &caps, DBUS_TYPE_INVALID);
  236. }
  237. static SDL_bool
  238. FcitxCreateInputContext(SDL_DBusContext* dbus, const char *appname, char **ic_path) {
  239. const char *program = "program";
  240. SDL_bool retval = SDL_FALSE;
  241. if (dbus->session_conn) {
  242. DBusMessage *msg = dbus->message_new_method_call(FCITX_DBUS_SERVICE, FCITX_IM_DBUS_PATH, FCITX_IM_DBUS_INTERFACE, "CreateInputContext");
  243. if (msg) {
  244. DBusMessage *reply = NULL;
  245. DBusMessageIter args, array, sub;
  246. dbus->message_iter_init_append(msg, &args);
  247. dbus->message_iter_open_container(&args, DBUS_TYPE_ARRAY, "(ss)", &array);
  248. dbus->message_iter_open_container(&array, DBUS_TYPE_STRUCT, 0, &sub);
  249. dbus->message_iter_append_basic(&sub, DBUS_TYPE_STRING, &program);
  250. dbus->message_iter_append_basic(&sub, DBUS_TYPE_STRING, &appname);
  251. dbus->message_iter_close_container(&array, &sub);
  252. dbus->message_iter_close_container(&args, &array);
  253. reply = dbus->connection_send_with_reply_and_block(dbus->session_conn, msg, 300, NULL);
  254. if (reply) {
  255. if (dbus->message_get_args(reply, NULL, DBUS_TYPE_OBJECT_PATH, ic_path, DBUS_TYPE_INVALID)) {
  256. retval = SDL_TRUE;
  257. }
  258. dbus->message_unref(reply);
  259. }
  260. dbus->message_unref(msg);
  261. }
  262. }
  263. return retval;
  264. }
  265. static SDL_bool
  266. FcitxClientCreateIC(FcitxClient *client)
  267. {
  268. char *appname = GetAppName();
  269. char *ic_path = NULL;
  270. SDL_DBusContext *dbus = client->dbus;
  271. /* SDL_DBus_CallMethod cannot handle a(ss) type, call dbus function directly */
  272. if (!FcitxCreateInputContext(dbus, appname, &ic_path)) {
  273. ic_path = NULL; /* just in case. */
  274. }
  275. SDL_free(appname);
  276. if (ic_path) {
  277. SDL_free(client->ic_path);
  278. client->ic_path = SDL_strdup(ic_path);
  279. dbus->bus_add_match(dbus->session_conn,
  280. "type='signal', interface='org.fcitx.Fcitx.InputContext1'",
  281. NULL);
  282. dbus->connection_add_filter(dbus->session_conn,
  283. &DBus_MessageFilter, dbus,
  284. NULL);
  285. dbus->connection_flush(dbus->session_conn);
  286. SDL_AddHintCallback(SDL_HINT_IME_INTERNAL_EDITING, Fcitx_SetCapabilities, client);
  287. return SDL_TRUE;
  288. }
  289. return SDL_FALSE;
  290. }
  291. static Uint32
  292. Fcitx_ModState(void)
  293. {
  294. Uint32 fcitx_mods = 0;
  295. SDL_Keymod sdl_mods = SDL_GetModState();
  296. if (sdl_mods & KMOD_SHIFT) fcitx_mods |= (1 << 0);
  297. if (sdl_mods & KMOD_CAPS) fcitx_mods |= (1 << 1);
  298. if (sdl_mods & KMOD_CTRL) fcitx_mods |= (1 << 2);
  299. if (sdl_mods & KMOD_ALT) fcitx_mods |= (1 << 3);
  300. if (sdl_mods & KMOD_NUM) fcitx_mods |= (1 << 4);
  301. if (sdl_mods & KMOD_MODE) fcitx_mods |= (1 << 7);
  302. if (sdl_mods & KMOD_LGUI) fcitx_mods |= (1 << 6);
  303. if (sdl_mods & KMOD_RGUI) fcitx_mods |= (1 << 28);
  304. return fcitx_mods;
  305. }
  306. SDL_bool
  307. SDL_Fcitx_Init()
  308. {
  309. fcitx_client.dbus = SDL_DBus_GetContext();
  310. fcitx_client.cursor_rect.x = -1;
  311. fcitx_client.cursor_rect.y = -1;
  312. fcitx_client.cursor_rect.w = 0;
  313. fcitx_client.cursor_rect.h = 0;
  314. return FcitxClientCreateIC(&fcitx_client);
  315. }
  316. void
  317. SDL_Fcitx_Quit()
  318. {
  319. FcitxClientICCallMethod(&fcitx_client, "DestroyIC");
  320. if (fcitx_client.ic_path) {
  321. SDL_free(fcitx_client.ic_path);
  322. fcitx_client.ic_path = NULL;
  323. }
  324. }
  325. void
  326. SDL_Fcitx_SetFocus(SDL_bool focused)
  327. {
  328. if (focused) {
  329. FcitxClientICCallMethod(&fcitx_client, "FocusIn");
  330. } else {
  331. FcitxClientICCallMethod(&fcitx_client, "FocusOut");
  332. }
  333. }
  334. void
  335. SDL_Fcitx_Reset(void)
  336. {
  337. FcitxClientICCallMethod(&fcitx_client, "Reset");
  338. FcitxClientICCallMethod(&fcitx_client, "CloseIC");
  339. }
  340. SDL_bool
  341. SDL_Fcitx_ProcessKeyEvent(Uint32 keysym, Uint32 keycode, Uint8 state)
  342. {
  343. Uint32 mod_state = Fcitx_ModState();
  344. Uint32 handled = SDL_FALSE;
  345. Uint32 is_release = (state == SDL_RELEASED);
  346. Uint32 event_time = 0;
  347. if (!fcitx_client.ic_path) {
  348. return SDL_FALSE;
  349. }
  350. if (SDL_DBus_CallMethod(FCITX_DBUS_SERVICE, fcitx_client.ic_path, FCITX_IC_DBUS_INTERFACE, "ProcessKeyEvent",
  351. DBUS_TYPE_UINT32, &keysym, DBUS_TYPE_UINT32, &keycode, DBUS_TYPE_UINT32, &mod_state, DBUS_TYPE_BOOLEAN, &is_release, DBUS_TYPE_UINT32, &event_time, DBUS_TYPE_INVALID,
  352. DBUS_TYPE_BOOLEAN, &handled, DBUS_TYPE_INVALID)) {
  353. if (handled) {
  354. SDL_Fcitx_UpdateTextRect(NULL);
  355. return SDL_TRUE;
  356. }
  357. }
  358. return SDL_FALSE;
  359. }
  360. void
  361. SDL_Fcitx_UpdateTextRect(SDL_Rect *rect)
  362. {
  363. SDL_Window *focused_win = NULL;
  364. SDL_SysWMinfo info;
  365. int x = 0, y = 0;
  366. SDL_Rect *cursor = &fcitx_client.cursor_rect;
  367. if (rect) {
  368. SDL_memcpy(cursor, rect, sizeof(SDL_Rect));
  369. }
  370. focused_win = SDL_GetKeyboardFocus();
  371. if (!focused_win) {
  372. return ;
  373. }
  374. SDL_VERSION(&info.version);
  375. if (!SDL_GetWindowWMInfo(focused_win, &info)) {
  376. return;
  377. }
  378. SDL_GetWindowPosition(focused_win, &x, &y);
  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. Display *x_disp = info.info.x11.display;
  383. Window x_win = info.info.x11.window;
  384. int x_screen = displaydata->screen;
  385. Window unused;
  386. X11_XTranslateCoordinates(x_disp, x_win, RootWindow(x_disp, x_screen), 0, 0, &x, &y, &unused);
  387. }
  388. #endif
  389. if (cursor->x == -1 && cursor->y == -1 && cursor->w == 0 && cursor->h == 0) {
  390. /* move to bottom left */
  391. int w = 0, h = 0;
  392. SDL_GetWindowSize(focused_win, &w, &h);
  393. cursor->x = 0;
  394. cursor->y = h;
  395. }
  396. x += cursor->x;
  397. y += cursor->y;
  398. SDL_DBus_CallVoidMethod(FCITX_DBUS_SERVICE, fcitx_client.ic_path, FCITX_IC_DBUS_INTERFACE, "SetCursorRect",
  399. DBUS_TYPE_INT32, &x, DBUS_TYPE_INT32, &y, DBUS_TYPE_INT32, &cursor->w, DBUS_TYPE_INT32, &cursor->h, DBUS_TYPE_INVALID);
  400. }
  401. void
  402. SDL_Fcitx_PumpEvents(void)
  403. {
  404. SDL_DBusContext *dbus = fcitx_client.dbus;
  405. DBusConnection *conn = dbus->session_conn;
  406. dbus->connection_read_write(conn, 0);
  407. while (dbus->connection_dispatch(conn) == DBUS_DISPATCH_DATA_REMAINS) {
  408. /* Do nothing, actual work happens in DBus_MessageFilter */
  409. usleep(10);
  410. }
  411. }
  412. /* vi: set ts=4 sw=4 expandtab: */