SDL_fcitx.c 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435
  1. /*
  2. Simple DirectMedia Layer
  3. Copyright (C) 1997-2017 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. #ifdef HAVE_FCITX_FRONTEND_H
  20. #include <fcitx/frontend.h>
  21. #include <unistd.h>
  22. #include "SDL_fcitx.h"
  23. #include "SDL_keycode.h"
  24. #include "SDL_keyboard.h"
  25. #include "../../events/SDL_keyboard_c.h"
  26. #include "SDL_dbus.h"
  27. #include "SDL_syswm.h"
  28. #if SDL_VIDEO_DRIVER_X11
  29. # include "../../video/x11/SDL_x11video.h"
  30. #endif
  31. #include "SDL_hints.h"
  32. #define FCITX_DBUS_SERVICE "org.fcitx.Fcitx"
  33. #define FCITX_IM_DBUS_PATH "/inputmethod"
  34. #define FCITX_IC_DBUS_PATH "/inputcontext_%d"
  35. #define FCITX_IM_DBUS_INTERFACE "org.fcitx.Fcitx.InputMethod"
  36. #define FCITX_IC_DBUS_INTERFACE "org.fcitx.Fcitx.InputContext"
  37. #define IC_NAME_MAX 64
  38. #define DBUS_TIMEOUT 500
  39. typedef struct _FcitxClient
  40. {
  41. SDL_DBusContext *dbus;
  42. char servicename[IC_NAME_MAX];
  43. char icname[IC_NAME_MAX];
  44. int id;
  45. SDL_Rect cursor_rect;
  46. } FcitxClient;
  47. static FcitxClient fcitx_client;
  48. static int
  49. GetDisplayNumber()
  50. {
  51. const char *display = SDL_getenv("DISPLAY");
  52. const char *p = NULL;
  53. int number = 0;
  54. if (display == NULL)
  55. return 0;
  56. display = SDL_strchr(display, ':');
  57. if (display == NULL)
  58. return 0;
  59. display++;
  60. p = SDL_strchr(display, '.');
  61. if (p == NULL && display != NULL) {
  62. number = SDL_strtod(display, NULL);
  63. } else {
  64. char *buffer = SDL_strdup(display);
  65. buffer[p - display] = '\0';
  66. number = SDL_strtod(buffer, NULL);
  67. SDL_free(buffer);
  68. }
  69. return number;
  70. }
  71. static char*
  72. GetAppName()
  73. {
  74. #if defined(__LINUX__) || defined(__FREEBSD__)
  75. char *spot;
  76. char procfile[1024];
  77. char linkfile[1024];
  78. int linksize;
  79. #if defined(__LINUX__)
  80. SDL_snprintf(procfile, sizeof(procfile), "/proc/%d/exe", getpid());
  81. #elif defined(__FREEBSD__)
  82. SDL_snprintf(procfile, sizeof(procfile), "/proc/%d/file", getpid());
  83. #endif
  84. linksize = readlink(procfile, linkfile, sizeof(linkfile) - 1);
  85. if (linksize > 0) {
  86. linkfile[linksize] = '\0';
  87. spot = SDL_strrchr(linkfile, '/');
  88. if (spot) {
  89. return SDL_strdup(spot + 1);
  90. } else {
  91. return SDL_strdup(linkfile);
  92. }
  93. }
  94. #endif /* __LINUX__ || __FREEBSD__ */
  95. return SDL_strdup("SDL_App");
  96. }
  97. /*
  98. * Copied from fcitx source
  99. */
  100. #define CONT(i) ISUTF8_CB(in[i])
  101. #define VAL(i, s) ((in[i]&0x3f) << s)
  102. static char *
  103. _fcitx_utf8_get_char(const char *i, uint32_t *chr)
  104. {
  105. const unsigned char* in = (const unsigned char *)i;
  106. if (!(in[0] & 0x80)) {
  107. *(chr) = *(in);
  108. return (char *)in + 1;
  109. }
  110. /* 2-byte, 0x80-0x7ff */
  111. if ((in[0] & 0xe0) == 0xc0 && CONT(1)) {
  112. *chr = ((in[0] & 0x1f) << 6) | VAL(1, 0);
  113. return (char *)in + 2;
  114. }
  115. /* 3-byte, 0x800-0xffff */
  116. if ((in[0] & 0xf0) == 0xe0 && CONT(1) && CONT(2)) {
  117. *chr = ((in[0] & 0xf) << 12) | VAL(1, 6) | VAL(2, 0);
  118. return (char *)in + 3;
  119. }
  120. /* 4-byte, 0x10000-0x1FFFFF */
  121. if ((in[0] & 0xf8) == 0xf0 && CONT(1) && CONT(2) && CONT(3)) {
  122. *chr = ((in[0] & 0x7) << 18) | VAL(1, 12) | VAL(2, 6) | VAL(3, 0);
  123. return (char *)in + 4;
  124. }
  125. /* 5-byte, 0x200000-0x3FFFFFF */
  126. if ((in[0] & 0xfc) == 0xf8 && CONT(1) && CONT(2) && CONT(3) && CONT(4)) {
  127. *chr = ((in[0] & 0x3) << 24) | VAL(1, 18) | VAL(2, 12) | VAL(3, 6) | VAL(4, 0);
  128. return (char *)in + 5;
  129. }
  130. /* 6-byte, 0x400000-0x7FFFFFF */
  131. if ((in[0] & 0xfe) == 0xfc && CONT(1) && CONT(2) && CONT(3) && CONT(4) && CONT(5)) {
  132. *chr = ((in[0] & 0x1) << 30) | VAL(1, 24) | VAL(2, 18) | VAL(3, 12) | VAL(4, 6) | VAL(5, 0);
  133. return (char *)in + 6;
  134. }
  135. *chr = *in;
  136. return (char *)in + 1;
  137. }
  138. static size_t
  139. _fcitx_utf8_strlen(const char *s)
  140. {
  141. unsigned int l = 0;
  142. while (*s) {
  143. uint32_t chr;
  144. s = _fcitx_utf8_get_char(s, &chr);
  145. l++;
  146. }
  147. return l;
  148. }
  149. static DBusHandlerResult
  150. DBus_MessageFilter(DBusConnection *conn, DBusMessage *msg, void *data)
  151. {
  152. SDL_DBusContext *dbus = (SDL_DBusContext *)data;
  153. if (dbus->message_is_signal(msg, FCITX_IC_DBUS_INTERFACE, "CommitString")) {
  154. DBusMessageIter iter;
  155. const char *text = NULL;
  156. dbus->message_iter_init(msg, &iter);
  157. dbus->message_iter_get_basic(&iter, &text);
  158. if (text)
  159. SDL_SendKeyboardText(text);
  160. return DBUS_HANDLER_RESULT_HANDLED;
  161. }
  162. if (dbus->message_is_signal(msg, FCITX_IC_DBUS_INTERFACE, "UpdatePreedit")) {
  163. DBusMessageIter iter;
  164. const char *text;
  165. dbus->message_iter_init(msg, &iter);
  166. dbus->message_iter_get_basic(&iter, &text);
  167. if (text && *text) {
  168. char buf[SDL_TEXTEDITINGEVENT_TEXT_SIZE];
  169. size_t text_bytes = SDL_strlen(text), i = 0;
  170. size_t cursor = 0;
  171. while (i < text_bytes) {
  172. size_t sz = SDL_utf8strlcpy(buf, text + i, sizeof(buf));
  173. size_t chars = _fcitx_utf8_strlen(buf);
  174. SDL_SendEditingText(buf, cursor, chars);
  175. i += sz;
  176. cursor += chars;
  177. }
  178. }
  179. SDL_Fcitx_UpdateTextRect(NULL);
  180. return DBUS_HANDLER_RESULT_HANDLED;
  181. }
  182. return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
  183. }
  184. static void
  185. FcitxClientICCallMethod(FcitxClient *client, const char *method)
  186. {
  187. SDL_DBus_CallVoidMethod(client->servicename, client->icname, FCITX_IC_DBUS_INTERFACE, method, DBUS_TYPE_INVALID);
  188. }
  189. static void
  190. Fcitx_SetCapabilities(void *data,
  191. const char *name,
  192. const char *old_val,
  193. const char *internal_editing)
  194. {
  195. FcitxClient *client = (FcitxClient *)data;
  196. Uint32 caps = CAPACITY_NONE;
  197. if (!(internal_editing && *internal_editing == '1')) {
  198. caps |= CAPACITY_PREEDIT;
  199. }
  200. SDL_DBus_CallVoidMethod(client->servicename, client->icname, FCITX_IC_DBUS_INTERFACE, "SetCapacity", DBUS_TYPE_UINT32, &caps, DBUS_TYPE_INVALID);
  201. }
  202. static void
  203. FcitxClientCreateIC(FcitxClient *client)
  204. {
  205. char *appname = GetAppName();
  206. pid_t pid = getpid();
  207. int id = -1;
  208. Uint32 enable, arg1, arg2, arg3, arg4;
  209. SDL_DBus_CallMethod(client->servicename, FCITX_IM_DBUS_PATH, FCITX_IM_DBUS_INTERFACE, "CreateICv3",
  210. DBUS_TYPE_STRING, &appname, DBUS_TYPE_INT32, &pid, DBUS_TYPE_INVALID,
  211. DBUS_TYPE_INT32, &id, DBUS_TYPE_BOOLEAN, &enable, DBUS_TYPE_UINT32, &arg1, DBUS_TYPE_UINT32, &arg2, DBUS_TYPE_UINT32, &arg3, DBUS_TYPE_UINT32, &arg4, DBUS_TYPE_INVALID);
  212. SDL_free(appname);
  213. if (id >= 0) {
  214. SDL_DBusContext *dbus = client->dbus;
  215. client->id = id;
  216. SDL_snprintf(client->icname, IC_NAME_MAX, FCITX_IC_DBUS_PATH, client->id);
  217. dbus->bus_add_match(dbus->session_conn,
  218. "type='signal', interface='org.fcitx.Fcitx.InputContext'",
  219. NULL);
  220. dbus->connection_add_filter(dbus->session_conn,
  221. &DBus_MessageFilter, dbus,
  222. NULL);
  223. dbus->connection_flush(dbus->session_conn);
  224. SDL_AddHintCallback(SDL_HINT_IME_INTERNAL_EDITING, &Fcitx_SetCapabilities, client);
  225. }
  226. }
  227. static Uint32
  228. Fcitx_ModState(void)
  229. {
  230. Uint32 fcitx_mods = 0;
  231. SDL_Keymod sdl_mods = SDL_GetModState();
  232. if (sdl_mods & KMOD_SHIFT) fcitx_mods |= FcitxKeyState_Shift;
  233. if (sdl_mods & KMOD_CAPS) fcitx_mods |= FcitxKeyState_CapsLock;
  234. if (sdl_mods & KMOD_CTRL) fcitx_mods |= FcitxKeyState_Ctrl;
  235. if (sdl_mods & KMOD_ALT) fcitx_mods |= FcitxKeyState_Alt;
  236. if (sdl_mods & KMOD_NUM) fcitx_mods |= FcitxKeyState_NumLock;
  237. if (sdl_mods & KMOD_LGUI) fcitx_mods |= FcitxKeyState_Super;
  238. if (sdl_mods & KMOD_RGUI) fcitx_mods |= FcitxKeyState_Meta;
  239. return fcitx_mods;
  240. }
  241. SDL_bool
  242. SDL_Fcitx_Init()
  243. {
  244. fcitx_client.dbus = SDL_DBus_GetContext();
  245. fcitx_client.cursor_rect.x = -1;
  246. fcitx_client.cursor_rect.y = -1;
  247. fcitx_client.cursor_rect.w = 0;
  248. fcitx_client.cursor_rect.h = 0;
  249. SDL_snprintf(fcitx_client.servicename, IC_NAME_MAX,
  250. "%s-%d",
  251. FCITX_DBUS_SERVICE, GetDisplayNumber());
  252. FcitxClientCreateIC(&fcitx_client);
  253. return SDL_TRUE;
  254. }
  255. void
  256. SDL_Fcitx_Quit()
  257. {
  258. FcitxClientICCallMethod(&fcitx_client, "DestroyIC");
  259. }
  260. void
  261. SDL_Fcitx_SetFocus(SDL_bool focused)
  262. {
  263. if (focused) {
  264. FcitxClientICCallMethod(&fcitx_client, "FocusIn");
  265. } else {
  266. FcitxClientICCallMethod(&fcitx_client, "FocusOut");
  267. }
  268. }
  269. void
  270. SDL_Fcitx_Reset(void)
  271. {
  272. FcitxClientICCallMethod(&fcitx_client, "Reset");
  273. FcitxClientICCallMethod(&fcitx_client, "CloseIC");
  274. }
  275. SDL_bool
  276. SDL_Fcitx_ProcessKeyEvent(Uint32 keysym, Uint32 keycode)
  277. {
  278. Uint32 state = Fcitx_ModState();
  279. Uint32 handled = SDL_FALSE;
  280. int type = FCITX_PRESS_KEY;
  281. Uint32 event_time = 0;
  282. if (SDL_DBus_CallMethod(fcitx_client.servicename, fcitx_client.icname, FCITX_IC_DBUS_INTERFACE, "ProcessKeyEvent",
  283. DBUS_TYPE_UINT32, &keysym, DBUS_TYPE_UINT32, &keycode, DBUS_TYPE_UINT32, &state, DBUS_TYPE_INT32, &type, DBUS_TYPE_UINT32, &event_time, DBUS_TYPE_INVALID,
  284. DBUS_TYPE_INT32, &handled, DBUS_TYPE_INVALID)) {
  285. if (handled) {
  286. SDL_Fcitx_UpdateTextRect(NULL);
  287. return SDL_TRUE;
  288. }
  289. }
  290. return SDL_FALSE;
  291. }
  292. void
  293. SDL_Fcitx_UpdateTextRect(SDL_Rect *rect)
  294. {
  295. SDL_Window *focused_win = NULL;
  296. SDL_SysWMinfo info;
  297. int x = 0, y = 0;
  298. SDL_Rect *cursor = &fcitx_client.cursor_rect;
  299. if (rect) {
  300. SDL_memcpy(cursor, rect, sizeof(SDL_Rect));
  301. }
  302. focused_win = SDL_GetKeyboardFocus();
  303. if (!focused_win) {
  304. return ;
  305. }
  306. SDL_VERSION(&info.version);
  307. if (!SDL_GetWindowWMInfo(focused_win, &info)) {
  308. return;
  309. }
  310. SDL_GetWindowPosition(focused_win, &x, &y);
  311. #if SDL_VIDEO_DRIVER_X11
  312. if (info.subsystem == SDL_SYSWM_X11) {
  313. SDL_DisplayData *displaydata = (SDL_DisplayData *) SDL_GetDisplayForWindow(focused_win)->driverdata;
  314. Display *x_disp = info.info.x11.display;
  315. Window x_win = info.info.x11.window;
  316. int x_screen = displaydata->screen;
  317. Window unused;
  318. X11_XTranslateCoordinates(x_disp, x_win, RootWindow(x_disp, x_screen), 0, 0, &x, &y, &unused);
  319. }
  320. #endif
  321. if (cursor->x == -1 && cursor->y == -1 && cursor->w == 0 && cursor->h == 0) {
  322. /* move to bottom left */
  323. int w = 0, h = 0;
  324. SDL_GetWindowSize(focused_win, &w, &h);
  325. cursor->x = 0;
  326. cursor->y = h;
  327. }
  328. x += cursor->x;
  329. y += cursor->y;
  330. SDL_DBus_CallVoidMethod(fcitx_client.servicename, fcitx_client.icname, FCITX_IC_DBUS_INTERFACE, "SetCursorRect",
  331. DBUS_TYPE_INT32, &x, DBUS_TYPE_INT32, &y, DBUS_TYPE_INT32, &cursor->w, DBUS_TYPE_INT32, &cursor->h, DBUS_TYPE_INVALID);
  332. }
  333. void
  334. SDL_Fcitx_PumpEvents(void)
  335. {
  336. SDL_DBusContext *dbus = fcitx_client.dbus;
  337. DBusConnection *conn = dbus->session_conn;
  338. dbus->connection_read_write(conn, 0);
  339. while (dbus->connection_dispatch(conn) == DBUS_DISPATCH_DATA_REMAINS) {
  340. /* Do nothing, actual work happens in DBus_MessageFilter */
  341. usleep(10);
  342. }
  343. }
  344. #endif /* HAVE_FCITX_FRONTEND_H */
  345. /* vi: set ts=4 sw=4 expandtab: */