Atlas - SDL_fcitx.c
Home / ext / SDL / src / core / linux Lines: 1 | Size: 14251 bytes [Download] [Show on GitHub] [Search similar files] [Raw] [Raw (proxy)][FILE BEGIN]1/* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2026 Sam Lantinga <[email protected]> 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 "../../video/SDL_sysvideo.h" 27#include "../../events/SDL_keyboard_c.h" 28#include "../../core/unix/SDL_appid.h" 29#include "SDL_dbus.h" 30 31#ifdef SDL_VIDEO_DRIVER_X11 32#include "../../video/x11/SDL_x11video.h" 33#endif 34 35#define FCITX_DBUS_SERVICE "org.freedesktop.portal.Fcitx" 36 37#define FCITX_IM_DBUS_PATH "/org/freedesktop/portal/inputmethod" 38 39#define FCITX_IM_DBUS_INTERFACE "org.fcitx.Fcitx.InputMethod1" 40#define FCITX_IC_DBUS_INTERFACE "org.fcitx.Fcitx.InputContext1" 41 42#define DBUS_TIMEOUT 500 43 44typedef struct FcitxClient 45{ 46 SDL_DBusContext *dbus; 47 48 char *ic_path; 49 50 int id; 51 52 SDL_Rect cursor_rect; 53} FcitxClient; 54 55static FcitxClient fcitx_client; 56 57static const char *GetAppName(void) 58{ 59 const char *exe_name = SDL_GetExeName(); 60 if (exe_name) { 61 return exe_name; 62 } 63 return "SDL_App"; 64} 65 66static size_t Fcitx_GetPreeditString(SDL_DBusContext *dbus, 67 DBusMessage *msg, 68 char **ret, 69 Sint32 *start_pos, 70 Sint32 *end_pos) 71{ 72 char *text = NULL, *subtext; 73 size_t text_bytes = 0; 74 DBusMessageIter iter, array, sub; 75 Sint32 p_start_pos = -1; 76 Sint32 p_end_pos = -1; 77 78 dbus->message_iter_init(msg, &iter); 79 // Message type is a(si)i, we only need string part 80 if (dbus->message_iter_get_arg_type(&iter) == DBUS_TYPE_ARRAY) { 81 size_t pos = 0; 82 // First pass: calculate string length 83 dbus->message_iter_recurse(&iter, &array); 84 while (dbus->message_iter_get_arg_type(&array) == DBUS_TYPE_STRUCT) { 85 dbus->message_iter_recurse(&array, &sub); 86 subtext = NULL; 87 if (dbus->message_iter_get_arg_type(&sub) == DBUS_TYPE_STRING) { 88 dbus->message_iter_get_basic(&sub, &subtext); 89 if (subtext && *subtext) { 90 text_bytes += SDL_strlen(subtext); 91 } 92 } 93 dbus->message_iter_next(&sub); 94 if (dbus->message_iter_get_arg_type(&sub) == DBUS_TYPE_INT32 && p_end_pos == -1) { 95 // Type is a bit field defined as follows: 96 // bit 3: Underline, bit 4: HighLight, bit 5: DontCommit, 97 // bit 6: Bold, bit 7: Strike, bit 8: Italic 98 Sint32 type; 99 dbus->message_iter_get_basic(&sub, &type); 100 // We only consider highlight 101 if (type & (1 << 4)) { 102 if (p_start_pos == -1) { 103 p_start_pos = pos; 104 } 105 } else if (p_start_pos != -1 && p_end_pos == -1) { 106 p_end_pos = pos; 107 } 108 } 109 dbus->message_iter_next(&array); 110 if (subtext && *subtext) { 111 pos += SDL_utf8strlen(subtext); 112 } 113 } 114 if (p_start_pos != -1 && p_end_pos == -1) { 115 p_end_pos = pos; 116 } 117 if (text_bytes) { 118 text = SDL_malloc(text_bytes + 1); 119 } 120 121 if (text) { 122 char *pivot = text; 123 // Second pass: join all the sub string 124 dbus->message_iter_recurse(&iter, &array); 125 while (dbus->message_iter_get_arg_type(&array) == DBUS_TYPE_STRUCT) { 126 dbus->message_iter_recurse(&array, &sub); 127 if (dbus->message_iter_get_arg_type(&sub) == DBUS_TYPE_STRING) { 128 dbus->message_iter_get_basic(&sub, &subtext); 129 if (subtext && *subtext) { 130 size_t length = SDL_strlen(subtext); 131 SDL_strlcpy(pivot, subtext, length + 1); 132 pivot += length; 133 } 134 } 135 dbus->message_iter_next(&array); 136 } 137 } else { 138 text_bytes = 0; 139 } 140 } 141 142 *ret = text; 143 *start_pos = p_start_pos; 144 *end_pos = p_end_pos; 145 return text_bytes; 146} 147 148static Sint32 Fcitx_GetPreeditCursorByte(SDL_DBusContext *dbus, DBusMessage *msg) 149{ 150 Sint32 byte = -1; 151 DBusMessageIter iter; 152 153 dbus->message_iter_init(msg, &iter); 154 155 dbus->message_iter_next(&iter); 156 157 if (dbus->message_iter_get_arg_type(&iter) != DBUS_TYPE_INT32) { 158 return -1; 159 } 160 161 dbus->message_iter_get_basic(&iter, &byte); 162 163 return byte; 164} 165 166static DBusHandlerResult DBus_MessageFilter(DBusConnection *conn, DBusMessage *msg, void *data) 167{ 168 SDL_DBusContext *dbus = (SDL_DBusContext *)data; 169 170 if (dbus->message_is_signal(msg, FCITX_IC_DBUS_INTERFACE, "CommitString")) { 171 DBusMessageIter iter; 172 const char *text = NULL; 173 174 dbus->message_iter_init(msg, &iter); 175 dbus->message_iter_get_basic(&iter, &text); 176 177 SDL_SendKeyboardText(text); 178 179 return DBUS_HANDLER_RESULT_HANDLED; 180 } 181 182 if (dbus->message_is_signal(msg, FCITX_IC_DBUS_INTERFACE, "UpdateFormattedPreedit")) { 183 char *text = NULL; 184 Sint32 start_pos, end_pos; 185 size_t text_bytes = Fcitx_GetPreeditString(dbus, msg, &text, &start_pos, &end_pos); 186 if (text_bytes) { 187 if (start_pos == -1) { 188 Sint32 byte_pos = Fcitx_GetPreeditCursorByte(dbus, msg); 189 start_pos = byte_pos >= 0 ? SDL_utf8strnlen(text, byte_pos) : -1; 190 } 191 SDL_SendEditingText(text, start_pos, end_pos >= 0 ? end_pos - start_pos : -1); 192 SDL_free(text); 193 } else { 194 SDL_SendEditingText("", 0, 0); 195 } 196 197 SDL_Fcitx_UpdateTextInputArea(SDL_GetKeyboardFocus()); 198 return DBUS_HANDLER_RESULT_HANDLED; 199 } 200 201 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; 202} 203 204static void FcitxClientICCallMethod(FcitxClient *client, const char *method) 205{ 206 if (!client->ic_path) { 207 return; 208 } 209 SDL_DBus_CallVoidMethod(FCITX_DBUS_SERVICE, client->ic_path, FCITX_IC_DBUS_INTERFACE, method, DBUS_TYPE_INVALID); 210} 211 212static void SDLCALL Fcitx_SetCapabilities(void *data, 213 const char *name, 214 const char *old_val, 215 const char *hint) 216{ 217 FcitxClient *client = (FcitxClient *)data; 218 Uint64 caps = 0; 219 if (!client->ic_path) { 220 return; 221 } 222 223 if (hint && SDL_strstr(hint, "composition")) { 224 caps |= (1 << 1); // Preedit Flag 225 caps |= (1 << 4); // Formatted Preedit Flag 226 } 227 if (hint && SDL_strstr(hint, "candidates")) { 228 // FIXME, turn off native candidate rendering 229 } 230 231 SDL_DBus_CallVoidMethod(FCITX_DBUS_SERVICE, client->ic_path, FCITX_IC_DBUS_INTERFACE, "SetCapability", DBUS_TYPE_UINT64, &caps, DBUS_TYPE_INVALID); 232} 233 234static bool FcitxCreateInputContext(SDL_DBusContext *dbus, const char *appname, char **ic_path) 235{ 236 const char *program = "program"; 237 bool result = false; 238 239 if (dbus && dbus->session_conn) { 240 DBusMessage *msg = dbus->message_new_method_call(FCITX_DBUS_SERVICE, FCITX_IM_DBUS_PATH, FCITX_IM_DBUS_INTERFACE, "CreateInputContext"); 241 if (msg) { 242 DBusMessage *reply = NULL; 243 DBusMessageIter args, array, sub; 244 dbus->message_iter_init_append(msg, &args); 245 dbus->message_iter_open_container(&args, DBUS_TYPE_ARRAY, "(ss)", &array); 246 dbus->message_iter_open_container(&array, DBUS_TYPE_STRUCT, NULL, &sub); 247 dbus->message_iter_append_basic(&sub, DBUS_TYPE_STRING, &program); 248 dbus->message_iter_append_basic(&sub, DBUS_TYPE_STRING, &appname); 249 dbus->message_iter_close_container(&array, &sub); 250 dbus->message_iter_close_container(&args, &array); 251 reply = dbus->connection_send_with_reply_and_block(dbus->session_conn, msg, 300, NULL); 252 if (reply) { 253 if (dbus->message_get_args(reply, NULL, DBUS_TYPE_OBJECT_PATH, ic_path, DBUS_TYPE_INVALID)) { 254 result = true; 255 } 256 dbus->message_unref(reply); 257 } 258 dbus->message_unref(msg); 259 } 260 } 261 return result; 262} 263 264static bool FcitxClientCreateIC(FcitxClient *client) 265{ 266 const char *appname = GetAppName(); 267 char *ic_path = NULL; 268 SDL_DBusContext *dbus = client->dbus; 269 270 // SDL_DBus_CallMethod cannot handle a(ss) type, call dbus function directly 271 if (!FcitxCreateInputContext(dbus, appname, &ic_path)) { 272 ic_path = NULL; // just in case. 273 } 274 275 if (ic_path) { 276 SDL_free(client->ic_path); 277 client->ic_path = SDL_strdup(ic_path); 278 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 287 SDL_AddHintCallback(SDL_HINT_IME_IMPLEMENTED_UI, Fcitx_SetCapabilities, client); 288 return true; 289 } 290 291 return false; 292} 293 294static Uint32 Fcitx_ModState(void) 295{ 296 Uint32 fcitx_mods = 0; 297 SDL_Keymod sdl_mods = SDL_GetModState(); 298 299 if (sdl_mods & SDL_KMOD_SHIFT) { 300 fcitx_mods |= (1 << 0); 301 } 302 if (sdl_mods & SDL_KMOD_CAPS) { 303 fcitx_mods |= (1 << 1); 304 } 305 if (sdl_mods & SDL_KMOD_CTRL) { 306 fcitx_mods |= (1 << 2); 307 } 308 if (sdl_mods & SDL_KMOD_ALT) { 309 fcitx_mods |= (1 << 3); 310 } 311 if (sdl_mods & SDL_KMOD_NUM) { 312 fcitx_mods |= (1 << 4); 313 } 314 if (sdl_mods & SDL_KMOD_MODE) { 315 fcitx_mods |= (1 << 7); 316 } 317 if (sdl_mods & SDL_KMOD_LGUI) { 318 fcitx_mods |= (1 << 6); 319 } 320 if (sdl_mods & SDL_KMOD_RGUI) { 321 fcitx_mods |= (1 << 28); 322 } 323 324 return fcitx_mods; 325} 326 327bool SDL_Fcitx_Init(void) 328{ 329 fcitx_client.dbus = SDL_DBus_GetContext(); 330 331 fcitx_client.cursor_rect.x = -1; 332 fcitx_client.cursor_rect.y = -1; 333 fcitx_client.cursor_rect.w = 0; 334 fcitx_client.cursor_rect.h = 0; 335 336 return FcitxClientCreateIC(&fcitx_client); 337} 338 339void SDL_Fcitx_Quit(void) 340{ 341 FcitxClientICCallMethod(&fcitx_client, "DestroyIC"); 342 if (fcitx_client.ic_path) { 343 SDL_free(fcitx_client.ic_path); 344 fcitx_client.ic_path = NULL; 345 } 346} 347 348void SDL_Fcitx_SetFocus(bool focused) 349{ 350 if (focused) { 351 FcitxClientICCallMethod(&fcitx_client, "FocusIn"); 352 } else { 353 FcitxClientICCallMethod(&fcitx_client, "FocusOut"); 354 } 355} 356 357void SDL_Fcitx_Reset(void) 358{ 359 FcitxClientICCallMethod(&fcitx_client, "Reset"); 360} 361 362bool SDL_Fcitx_ProcessKeyEvent(Uint32 keysym, Uint32 keycode, bool down) 363{ 364 Uint32 mod_state = Fcitx_ModState(); 365 Uint32 handled = false; 366 Uint32 is_release = !down; 367 Uint32 event_time = 0; 368 369 if (!fcitx_client.ic_path) { 370 return false; 371 } 372 373 if (SDL_DBus_CallMethod(NULL, FCITX_DBUS_SERVICE, fcitx_client.ic_path, FCITX_IC_DBUS_INTERFACE, "ProcessKeyEvent", 374 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, 375 DBUS_TYPE_BOOLEAN, &handled, DBUS_TYPE_INVALID)) { 376 if (handled) { 377 SDL_Fcitx_UpdateTextInputArea(SDL_GetKeyboardFocus()); 378 return true; 379 } 380 } 381 382 return false; 383} 384 385void SDL_Fcitx_UpdateTextInputArea(SDL_Window *window) 386{ 387 int x = 0, y = 0; 388 SDL_Rect *cursor = &fcitx_client.cursor_rect; 389 390 if (!window) { 391 return; 392 } 393 394 // We'll use a square at the text input cursor location for the cursor_rect 395 cursor->x = window->text_input_rect.x + window->text_input_cursor; 396 cursor->y = window->text_input_rect.y; 397 cursor->w = window->text_input_rect.h; 398 cursor->h = window->text_input_rect.h; 399 400 SDL_GetWindowPosition(window, &x, &y); 401 402#ifdef SDL_VIDEO_DRIVER_X11 403 { 404 SDL_PropertiesID props = SDL_GetWindowProperties(window); 405 Display *x_disp = (Display *)SDL_GetPointerProperty(props, SDL_PROP_WINDOW_X11_DISPLAY_POINTER, NULL); 406 int x_screen = SDL_GetNumberProperty(props, SDL_PROP_WINDOW_X11_SCREEN_NUMBER, 0); 407 Window x_win = SDL_GetNumberProperty(props, SDL_PROP_WINDOW_X11_WINDOW_NUMBER, 0); 408 Window unused; 409 if (x_disp && x_win) { 410 X11_XTranslateCoordinates(x_disp, x_win, RootWindow(x_disp, x_screen), 0, 0, &x, &y, &unused); 411 } 412 } 413#endif 414 415 if (cursor->x == -1 && cursor->y == -1 && cursor->w == 0 && cursor->h == 0) { 416 // move to bottom left 417 int w = 0, h = 0; 418 SDL_GetWindowSize(window, &w, &h); 419 cursor->x = 0; 420 cursor->y = h; 421 } 422 423 x += cursor->x; 424 y += cursor->y; 425 426 SDL_DBus_CallVoidMethod(FCITX_DBUS_SERVICE, fcitx_client.ic_path, FCITX_IC_DBUS_INTERFACE, "SetCursorRect", 427 DBUS_TYPE_INT32, &x, DBUS_TYPE_INT32, &y, DBUS_TYPE_INT32, &cursor->w, DBUS_TYPE_INT32, &cursor->h, DBUS_TYPE_INVALID); 428} 429 430void SDL_Fcitx_PumpEvents(void) 431{ 432 SDL_DBusContext *dbus = fcitx_client.dbus; 433 DBusConnection *conn = dbus->session_conn; 434 435 dbus->connection_read_write(conn, 0); 436 437 while (dbus->connection_dispatch(conn) == DBUS_DISPATCH_DATA_REMAINS) { 438 // Do nothing, actual work happens in DBus_MessageFilter 439 } 440} 441[FILE END](C) 2025 0x4248 (C) 2025 4248 Media and 4248 Systems, All part of 0x4248 See LICENCE files for more information. Not all files are by 0x4248 always check Licencing.