SDL_fcitx.c (13162B)
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* 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: */