SDL_ibus.c (19660B)
1/* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2014 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#ifdef HAVE_IBUS_IBUS_H 24#include "SDL.h" 25#include "SDL_syswm.h" 26#include "SDL_ibus.h" 27#include "SDL_dbus.h" 28#include "../../video/SDL_sysvideo.h" 29#include "../../events/SDL_keyboard_c.h" 30 31#if SDL_VIDEO_DRIVER_X11 32 #include "../../video/x11/SDL_x11video.h" 33#endif 34 35#include <sys/inotify.h> 36#include <unistd.h> 37#include <fcntl.h> 38 39static const char IBUS_SERVICE[] = "org.freedesktop.IBus"; 40static const char IBUS_PATH[] = "/org/freedesktop/IBus"; 41static const char IBUS_INTERFACE[] = "org.freedesktop.IBus"; 42static const char IBUS_INPUT_INTERFACE[] = "org.freedesktop.IBus.InputContext"; 43 44static char *input_ctx_path = NULL; 45static SDL_Rect ibus_cursor_rect = {0}; 46static DBusConnection *ibus_conn = NULL; 47static char *ibus_addr_file = NULL; 48int inotify_fd = -1, inotify_wd = -1; 49 50static Uint32 51IBus_ModState(void) 52{ 53 Uint32 ibus_mods = 0; 54 SDL_Keymod sdl_mods = SDL_GetModState(); 55 56 /* Not sure about MOD3, MOD4 and HYPER mappings */ 57 if (sdl_mods & KMOD_LSHIFT) ibus_mods |= IBUS_SHIFT_MASK; 58 if (sdl_mods & KMOD_CAPS) ibus_mods |= IBUS_LOCK_MASK; 59 if (sdl_mods & KMOD_LCTRL) ibus_mods |= IBUS_CONTROL_MASK; 60 if (sdl_mods & KMOD_LALT) ibus_mods |= IBUS_MOD1_MASK; 61 if (sdl_mods & KMOD_NUM) ibus_mods |= IBUS_MOD2_MASK; 62 if (sdl_mods & KMOD_MODE) ibus_mods |= IBUS_MOD5_MASK; 63 if (sdl_mods & KMOD_LGUI) ibus_mods |= IBUS_SUPER_MASK; 64 if (sdl_mods & KMOD_RGUI) ibus_mods |= IBUS_META_MASK; 65 66 return ibus_mods; 67} 68 69static const char * 70IBus_GetVariantText(DBusConnection *conn, DBusMessageIter *iter, SDL_DBusContext *dbus) 71{ 72 /* The text we need is nested weirdly, use dbus-monitor to see the structure better */ 73 const char *text = NULL; 74 const char *struct_id = NULL; 75 DBusMessageIter sub1, sub2; 76 77 if (dbus->message_iter_get_arg_type(iter) != DBUS_TYPE_VARIANT) { 78 return NULL; 79 } 80 81 dbus->message_iter_recurse(iter, &sub1); 82 83 if (dbus->message_iter_get_arg_type(&sub1) != DBUS_TYPE_STRUCT) { 84 return NULL; 85 } 86 87 dbus->message_iter_recurse(&sub1, &sub2); 88 89 if (dbus->message_iter_get_arg_type(&sub2) != DBUS_TYPE_STRING) { 90 return NULL; 91 } 92 93 dbus->message_iter_get_basic(&sub2, &struct_id); 94 if (!struct_id || SDL_strncmp(struct_id, "IBusText", sizeof("IBusText")) != 0) { 95 return NULL; 96 } 97 98 dbus->message_iter_next(&sub2); 99 dbus->message_iter_next(&sub2); 100 101 if (dbus->message_iter_get_arg_type(&sub2) != DBUS_TYPE_STRING) { 102 return NULL; 103 } 104 105 dbus->message_iter_get_basic(&sub2, &text); 106 107 return text; 108} 109 110static size_t 111IBus_utf8_strlen(const char *str) 112{ 113 size_t utf8_len = 0; 114 const char *p; 115 116 for (p = str; *p; ++p) { 117 if (!((*p & 0x80) && !(*p & 0x40))) { 118 ++utf8_len; 119 } 120 } 121 122 return utf8_len; 123} 124 125static DBusHandlerResult 126IBus_MessageFilter(DBusConnection *conn, DBusMessage *msg, void *user_data) 127{ 128 SDL_DBusContext *dbus = (SDL_DBusContext *)user_data; 129 130 if (dbus->message_is_signal(msg, IBUS_INPUT_INTERFACE, "CommitText")) { 131 DBusMessageIter iter; 132 const char *text; 133 134 dbus->message_iter_init(msg, &iter); 135 136 text = IBus_GetVariantText(conn, &iter, dbus); 137 if (text && *text) { 138 char buf[SDL_TEXTEDITINGEVENT_TEXT_SIZE]; 139 size_t text_bytes = SDL_strlen(text), i = 0; 140 141 while (i < text_bytes) { 142 size_t sz = SDL_utf8strlcpy(buf, text+i, sizeof(buf)); 143 SDL_SendKeyboardText(buf); 144 145 i += sz; 146 } 147 } 148 149 return DBUS_HANDLER_RESULT_HANDLED; 150 } 151 152 if (dbus->message_is_signal(msg, IBUS_INPUT_INTERFACE, "UpdatePreeditText")) { 153 DBusMessageIter iter; 154 const char *text; 155 156 dbus->message_iter_init(msg, &iter); 157 text = IBus_GetVariantText(conn, &iter, dbus); 158 159 if (text && *text) { 160 char buf[SDL_TEXTEDITINGEVENT_TEXT_SIZE]; 161 size_t text_bytes = SDL_strlen(text), i = 0; 162 size_t cursor = 0; 163 164 while (i < text_bytes) { 165 size_t sz = SDL_utf8strlcpy(buf, text+i, sizeof(buf)); 166 size_t chars = IBus_utf8_strlen(buf); 167 168 SDL_SendEditingText(buf, cursor, chars); 169 170 i += sz; 171 cursor += chars; 172 } 173 } 174 175 SDL_IBus_UpdateTextRect(NULL); 176 177 return DBUS_HANDLER_RESULT_HANDLED; 178 } 179 180 if (dbus->message_is_signal(msg, IBUS_INPUT_INTERFACE, "HidePreeditText")) { 181 SDL_SendEditingText("", 0, 0); 182 return DBUS_HANDLER_RESULT_HANDLED; 183 } 184 185 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; 186} 187 188static char * 189IBus_ReadAddressFromFile(const char *file_path) 190{ 191 char addr_buf[1024]; 192 SDL_bool success = SDL_FALSE; 193 FILE *addr_file; 194 195 addr_file = fopen(file_path, "r"); 196 if (!addr_file) { 197 return NULL; 198 } 199 200 while (fgets(addr_buf, sizeof(addr_buf), addr_file)) { 201 if (SDL_strncmp(addr_buf, "IBUS_ADDRESS=", sizeof("IBUS_ADDRESS=")-1) == 0) { 202 size_t sz = SDL_strlen(addr_buf); 203 if (addr_buf[sz-1] == '\n') addr_buf[sz-1] = 0; 204 if (addr_buf[sz-2] == '\r') addr_buf[sz-2] = 0; 205 success = SDL_TRUE; 206 break; 207 } 208 } 209 210 fclose(addr_file); 211 212 if (success) { 213 return SDL_strdup(addr_buf + (sizeof("IBUS_ADDRESS=") - 1)); 214 } else { 215 return NULL; 216 } 217} 218 219static char * 220IBus_GetDBusAddressFilename(void) 221{ 222 SDL_DBusContext *dbus; 223 const char *disp_env; 224 char config_dir[PATH_MAX]; 225 char *display = NULL; 226 const char *addr; 227 const char *conf_env; 228 char *key; 229 char file_path[PATH_MAX]; 230 const char *host; 231 char *disp_num, *screen_num; 232 233 if (ibus_addr_file) { 234 return SDL_strdup(ibus_addr_file); 235 } 236 237 dbus = SDL_DBus_GetContext(); 238 if (!dbus) { 239 return NULL; 240 } 241 242 /* Use this environment variable if it exists. */ 243 addr = SDL_getenv("IBUS_ADDRESS"); 244 if (addr && *addr) { 245 return SDL_strdup(addr); 246 } 247 248 /* Otherwise, we have to get the hostname, display, machine id, config dir 249 and look up the address from a filepath using all those bits, eek. */ 250 disp_env = SDL_getenv("DISPLAY"); 251 252 if (!disp_env || !*disp_env) { 253 display = SDL_strdup(":0.0"); 254 } else { 255 display = SDL_strdup(disp_env); 256 } 257 258 host = display; 259 disp_num = SDL_strrchr(display, ':'); 260 screen_num = SDL_strrchr(display, '.'); 261 262 if (!disp_num) { 263 SDL_free(display); 264 return NULL; 265 } 266 267 *disp_num = 0; 268 disp_num++; 269 270 if (screen_num) { 271 *screen_num = 0; 272 } 273 274 if (!*host) { 275 host = "unix"; 276 } 277 278 SDL_memset(config_dir, 0, sizeof(config_dir)); 279 280 conf_env = SDL_getenv("XDG_CONFIG_HOME"); 281 if (conf_env && *conf_env) { 282 SDL_strlcpy(config_dir, conf_env, sizeof(config_dir)); 283 } else { 284 const char *home_env = SDL_getenv("HOME"); 285 if (!home_env || !*home_env) { 286 SDL_free(display); 287 return NULL; 288 } 289 SDL_snprintf(config_dir, sizeof(config_dir), "%s/.config", home_env); 290 } 291 292 key = dbus->get_local_machine_id(); 293 294 SDL_memset(file_path, 0, sizeof(file_path)); 295 SDL_snprintf(file_path, sizeof(file_path), "%s/ibus/bus/%s-%s-%s", 296 config_dir, key, host, disp_num); 297 dbus->free(key); 298 SDL_free(display); 299 300 return SDL_strdup(file_path); 301} 302 303static SDL_bool IBus_CheckConnection(SDL_DBusContext *dbus); 304 305static void 306IBus_SetCapabilities(void *data, const char *name, const char *old_val, 307 const char *internal_editing) 308{ 309 SDL_DBusContext *dbus = SDL_DBus_GetContext(); 310 311 if (IBus_CheckConnection(dbus)) { 312 313 DBusMessage *msg = dbus->message_new_method_call(IBUS_SERVICE, 314 input_ctx_path, 315 IBUS_INPUT_INTERFACE, 316 "SetCapabilities"); 317 if (msg) { 318 Uint32 caps = IBUS_CAP_FOCUS; 319 if (!(internal_editing && *internal_editing == '1')) { 320 caps |= IBUS_CAP_PREEDIT_TEXT; 321 } 322 323 dbus->message_append_args(msg, 324 DBUS_TYPE_UINT32, &caps, 325 DBUS_TYPE_INVALID); 326 } 327 328 if (msg) { 329 if (dbus->connection_send(ibus_conn, msg, NULL)) { 330 dbus->connection_flush(ibus_conn); 331 } 332 dbus->message_unref(msg); 333 } 334 } 335} 336 337 338static SDL_bool 339IBus_SetupConnection(SDL_DBusContext *dbus, const char* addr) 340{ 341 const char *path = NULL; 342 SDL_bool result = SDL_FALSE; 343 DBusMessage *msg; 344 345 ibus_conn = dbus->connection_open_private(addr, NULL); 346 347 if (!ibus_conn) { 348 return SDL_FALSE; 349 } 350 351 dbus->connection_flush(ibus_conn); 352 353 if (!dbus->bus_register(ibus_conn, NULL)) { 354 ibus_conn = NULL; 355 return SDL_FALSE; 356 } 357 358 dbus->connection_flush(ibus_conn); 359 360 msg = dbus->message_new_method_call(IBUS_SERVICE, IBUS_PATH, IBUS_INTERFACE, "CreateInputContext"); 361 if (msg) { 362 const char *client_name = "SDL2_Application"; 363 dbus->message_append_args(msg, 364 DBUS_TYPE_STRING, &client_name, 365 DBUS_TYPE_INVALID); 366 } 367 368 if (msg) { 369 DBusMessage *reply; 370 371 reply = dbus->connection_send_with_reply_and_block(ibus_conn, msg, 1000, NULL); 372 if (reply) { 373 if (dbus->message_get_args(reply, NULL, 374 DBUS_TYPE_OBJECT_PATH, &path, 375 DBUS_TYPE_INVALID)) { 376 if (input_ctx_path) { 377 SDL_free(input_ctx_path); 378 } 379 input_ctx_path = SDL_strdup(path); 380 result = SDL_TRUE; 381 } 382 dbus->message_unref(reply); 383 } 384 dbus->message_unref(msg); 385 } 386 387 if (result) { 388 SDL_AddHintCallback(SDL_HINT_IME_INTERNAL_EDITING, &IBus_SetCapabilities, NULL); 389 390 dbus->bus_add_match(ibus_conn, "type='signal',interface='org.freedesktop.IBus.InputContext'", NULL); 391 dbus->connection_add_filter(ibus_conn, &IBus_MessageFilter, dbus, NULL); 392 dbus->connection_flush(ibus_conn); 393 } 394 395 SDL_IBus_SetFocus(SDL_GetKeyboardFocus() != NULL); 396 SDL_IBus_UpdateTextRect(NULL); 397 398 return result; 399} 400 401static SDL_bool 402IBus_CheckConnection(SDL_DBusContext *dbus) 403{ 404 if (!dbus) return SDL_FALSE; 405 406 if (ibus_conn && dbus->connection_get_is_connected(ibus_conn)) { 407 return SDL_TRUE; 408 } 409 410 if (inotify_fd > 0 && inotify_wd > 0) { 411 char buf[1024]; 412 ssize_t readsize = read(inotify_fd, buf, sizeof(buf)); 413 if (readsize > 0) { 414 415 char *p; 416 SDL_bool file_updated = SDL_FALSE; 417 418 for (p = buf; p < buf + readsize; /**/) { 419 struct inotify_event *event = (struct inotify_event*) p; 420 if (event->len > 0) { 421 char *addr_file_no_path = SDL_strrchr(ibus_addr_file, '/'); 422 if (!addr_file_no_path) return SDL_FALSE; 423 424 if (SDL_strcmp(addr_file_no_path + 1, event->name) == 0) { 425 file_updated = SDL_TRUE; 426 break; 427 } 428 } 429 430 p += sizeof(struct inotify_event) + event->len; 431 } 432 433 if (file_updated) { 434 char *addr = IBus_ReadAddressFromFile(ibus_addr_file); 435 if (addr) { 436 SDL_bool result = IBus_SetupConnection(dbus, addr); 437 SDL_free(addr); 438 return result; 439 } 440 } 441 } 442 } 443 444 return SDL_FALSE; 445} 446 447SDL_bool 448SDL_IBus_Init(void) 449{ 450 SDL_bool result = SDL_FALSE; 451 SDL_DBusContext *dbus = SDL_DBus_GetContext(); 452 453 if (dbus) { 454 char *addr_file = IBus_GetDBusAddressFilename(); 455 char *addr; 456 char *addr_file_dir; 457 458 if (!addr_file) { 459 return SDL_FALSE; 460 } 461 462 ibus_addr_file = SDL_strdup(addr_file); 463 464 addr = IBus_ReadAddressFromFile(addr_file); 465 466 if (inotify_fd < 0) { 467 inotify_fd = inotify_init(); 468 fcntl(inotify_fd, F_SETFL, O_NONBLOCK); 469 } 470 471 addr_file_dir = SDL_strrchr(addr_file, '/'); 472 if (addr_file_dir) { 473 *addr_file_dir = 0; 474 } 475 476 inotify_wd = inotify_add_watch(inotify_fd, addr_file, IN_CREATE | IN_MODIFY); 477 SDL_free(addr_file); 478 479 result = IBus_SetupConnection(dbus, addr); 480 SDL_free(addr); 481 } 482 483 return result; 484} 485 486void 487SDL_IBus_Quit(void) 488{ 489 SDL_DBusContext *dbus; 490 491 if (input_ctx_path) { 492 SDL_free(input_ctx_path); 493 input_ctx_path = NULL; 494 } 495 496 if (ibus_addr_file) { 497 SDL_free(ibus_addr_file); 498 ibus_addr_file = NULL; 499 } 500 501 dbus = SDL_DBus_GetContext(); 502 503 if (dbus && ibus_conn) { 504 dbus->connection_close(ibus_conn); 505 dbus->connection_unref(ibus_conn); 506 } 507 508 if (inotify_fd > 0 && inotify_wd > 0) { 509 inotify_rm_watch(inotify_fd, inotify_wd); 510 inotify_wd = -1; 511 } 512 513 SDL_DelHintCallback(SDL_HINT_IME_INTERNAL_EDITING, &IBus_SetCapabilities, NULL); 514 515 SDL_memset(&ibus_cursor_rect, 0, sizeof(ibus_cursor_rect)); 516} 517 518static void 519IBus_SimpleMessage(const char *method) 520{ 521 SDL_DBusContext *dbus = SDL_DBus_GetContext(); 522 523 if (IBus_CheckConnection(dbus)) { 524 DBusMessage *msg = dbus->message_new_method_call(IBUS_SERVICE, 525 input_ctx_path, 526 IBUS_INPUT_INTERFACE, 527 method); 528 if (msg) { 529 if (dbus->connection_send(ibus_conn, msg, NULL)) { 530 dbus->connection_flush(ibus_conn); 531 } 532 dbus->message_unref(msg); 533 } 534 } 535} 536 537void 538SDL_IBus_SetFocus(SDL_bool focused) 539{ 540 const char *method = focused ? "FocusIn" : "FocusOut"; 541 IBus_SimpleMessage(method); 542} 543 544void 545SDL_IBus_Reset(void) 546{ 547 IBus_SimpleMessage("Reset"); 548} 549 550SDL_bool 551SDL_IBus_ProcessKeyEvent(Uint32 keysym, Uint32 keycode) 552{ 553 SDL_bool result = SDL_FALSE; 554 SDL_DBusContext *dbus = SDL_DBus_GetContext(); 555 556 if (IBus_CheckConnection(dbus)) { 557 DBusMessage *msg = dbus->message_new_method_call(IBUS_SERVICE, 558 input_ctx_path, 559 IBUS_INPUT_INTERFACE, 560 "ProcessKeyEvent"); 561 if (msg) { 562 Uint32 mods = IBus_ModState(); 563 dbus->message_append_args(msg, 564 DBUS_TYPE_UINT32, &keysym, 565 DBUS_TYPE_UINT32, &keycode, 566 DBUS_TYPE_UINT32, &mods, 567 DBUS_TYPE_INVALID); 568 } 569 570 if (msg) { 571 DBusMessage *reply; 572 573 reply = dbus->connection_send_with_reply_and_block(ibus_conn, msg, 300, NULL); 574 if (reply) { 575 if (!dbus->message_get_args(reply, NULL, 576 DBUS_TYPE_BOOLEAN, &result, 577 DBUS_TYPE_INVALID)) { 578 result = SDL_FALSE; 579 } 580 dbus->message_unref(reply); 581 } 582 dbus->message_unref(msg); 583 } 584 585 } 586 587 SDL_IBus_UpdateTextRect(NULL); 588 589 return result; 590} 591 592void 593SDL_IBus_UpdateTextRect(SDL_Rect *rect) 594{ 595 SDL_Window *focused_win; 596 SDL_SysWMinfo info; 597 int x = 0, y = 0; 598 SDL_DBusContext *dbus; 599 600 if (rect) { 601 SDL_memcpy(&ibus_cursor_rect, rect, sizeof(ibus_cursor_rect)); 602 } 603 604 focused_win = SDL_GetKeyboardFocus(); 605 if (!focused_win) { 606 return; 607 } 608 609 SDL_VERSION(&info.version); 610 if (!SDL_GetWindowWMInfo(focused_win, &info)) { 611 return; 612 } 613 614 SDL_GetWindowPosition(focused_win, &x, &y); 615 616#if SDL_VIDEO_DRIVER_X11 617 if (info.subsystem == SDL_SYSWM_X11) { 618 SDL_DisplayData *displaydata = (SDL_DisplayData *) SDL_GetDisplayForWindow(focused_win)->driverdata; 619 620 Display *x_disp = info.info.x11.display; 621 Window x_win = info.info.x11.window; 622 int x_screen = displaydata->screen; 623 Window unused; 624 625 X11_XTranslateCoordinates(x_disp, x_win, RootWindow(x_disp, x_screen), 0, 0, &x, &y, &unused); 626 } 627#endif 628 629 x += ibus_cursor_rect.x; 630 y += ibus_cursor_rect.y; 631 632 dbus = SDL_DBus_GetContext(); 633 634 if (IBus_CheckConnection(dbus)) { 635 DBusMessage *msg = dbus->message_new_method_call(IBUS_SERVICE, 636 input_ctx_path, 637 IBUS_INPUT_INTERFACE, 638 "SetCursorLocation"); 639 if (msg) { 640 dbus->message_append_args(msg, 641 DBUS_TYPE_INT32, &x, 642 DBUS_TYPE_INT32, &y, 643 DBUS_TYPE_INT32, &ibus_cursor_rect.w, 644 DBUS_TYPE_INT32, &ibus_cursor_rect.h, 645 DBUS_TYPE_INVALID); 646 } 647 648 if (msg) { 649 if (dbus->connection_send(ibus_conn, msg, NULL)) { 650 dbus->connection_flush(ibus_conn); 651 } 652 dbus->message_unref(msg); 653 } 654 } 655} 656 657void 658SDL_IBus_PumpEvents(void) 659{ 660 SDL_DBusContext *dbus = SDL_DBus_GetContext(); 661 662 if (IBus_CheckConnection(dbus)) { 663 dbus->connection_read_write(ibus_conn, 0); 664 665 while (dbus->connection_dispatch(ibus_conn) == DBUS_DISPATCH_DATA_REMAINS) { 666 /* Do nothing, actual work happens in IBus_MessageFilter */ 667 } 668 } 669} 670 671#endif