Path: blob/master/thirdparty/sdl/core/linux/SDL_ibus.c
10279 views
/*1Simple DirectMedia Layer2Copyright (C) 1997-2025 Sam Lantinga <[email protected]>34This software is provided 'as-is', without any express or implied5warranty. In no event will the authors be held liable for any damages6arising from the use of this software.78Permission is granted to anyone to use this software for any purpose,9including commercial applications, and to alter it and redistribute it10freely, subject to the following restrictions:11121. The origin of this software must not be misrepresented; you must not13claim that you wrote the original software. If you use this software14in a product, an acknowledgment in the product documentation would be15appreciated but is not required.162. Altered source versions must be plainly marked as such, and must not be17misrepresented as being the original software.183. This notice may not be removed or altered from any source distribution.19*/20#include "SDL_internal.h"2122#ifdef HAVE_IBUS_IBUS_H23#include "SDL_ibus.h"24#include "SDL_dbus.h"2526#ifdef SDL_USE_LIBDBUS2728//#include "../../video/SDL_sysvideo.h"29#include "../../events/SDL_keyboard_c.h"3031#ifdef SDL_VIDEO_DRIVER_X1132#include "../../video/x11/SDL_x11video.h"33#endif3435#include <sys/inotify.h>36#include <unistd.h>37#include <fcntl.h>3839static const char IBUS_PATH[] = "/org/freedesktop/IBus";4041static const char IBUS_SERVICE[] = "org.freedesktop.IBus";42static const char IBUS_INTERFACE[] = "org.freedesktop.IBus";43static const char IBUS_INPUT_INTERFACE[] = "org.freedesktop.IBus.InputContext";4445static const char IBUS_PORTAL_SERVICE[] = "org.freedesktop.portal.IBus";46static const char IBUS_PORTAL_INTERFACE[] = "org.freedesktop.IBus.Portal";47static const char IBUS_PORTAL_INPUT_INTERFACE[] = "org.freedesktop.IBus.InputContext";4849static const char *ibus_service = NULL;50static const char *ibus_interface = NULL;51static const char *ibus_input_interface = NULL;52static char *input_ctx_path = NULL;53static SDL_Rect ibus_cursor_rect = { 0, 0, 0, 0 };54static DBusConnection *ibus_conn = NULL;55static bool ibus_is_portal_interface = false;56static char *ibus_addr_file = NULL;57static int inotify_fd = -1, inotify_wd = -1;5859static Uint32 IBus_ModState(void)60{61Uint32 ibus_mods = 0;62SDL_Keymod sdl_mods = SDL_GetModState();6364// Not sure about MOD3, MOD4 and HYPER mappings65if (sdl_mods & SDL_KMOD_LSHIFT) {66ibus_mods |= IBUS_SHIFT_MASK;67}68if (sdl_mods & SDL_KMOD_CAPS) {69ibus_mods |= IBUS_LOCK_MASK;70}71if (sdl_mods & SDL_KMOD_LCTRL) {72ibus_mods |= IBUS_CONTROL_MASK;73}74if (sdl_mods & SDL_KMOD_LALT) {75ibus_mods |= IBUS_MOD1_MASK;76}77if (sdl_mods & SDL_KMOD_NUM) {78ibus_mods |= IBUS_MOD2_MASK;79}80if (sdl_mods & SDL_KMOD_MODE) {81ibus_mods |= IBUS_MOD5_MASK;82}83if (sdl_mods & SDL_KMOD_LGUI) {84ibus_mods |= IBUS_SUPER_MASK;85}86if (sdl_mods & SDL_KMOD_RGUI) {87ibus_mods |= IBUS_META_MASK;88}8990return ibus_mods;91}9293static bool IBus_EnterVariant(DBusConnection *conn, DBusMessageIter *iter, SDL_DBusContext *dbus,94DBusMessageIter *inside, const char *struct_id, size_t id_size)95{96DBusMessageIter sub;97if (dbus->message_iter_get_arg_type(iter) != DBUS_TYPE_VARIANT) {98return false;99}100101dbus->message_iter_recurse(iter, &sub);102103if (dbus->message_iter_get_arg_type(&sub) != DBUS_TYPE_STRUCT) {104return false;105}106107dbus->message_iter_recurse(&sub, inside);108109if (dbus->message_iter_get_arg_type(inside) != DBUS_TYPE_STRING) {110return false;111}112113dbus->message_iter_get_basic(inside, &struct_id);114if (!struct_id || SDL_strncmp(struct_id, struct_id, id_size) != 0) {115return false;116}117return true;118}119120static bool IBus_GetDecorationPosition(DBusConnection *conn, DBusMessageIter *iter, SDL_DBusContext *dbus,121Uint32 *start_pos, Uint32 *end_pos)122{123DBusMessageIter sub1, sub2, array;124125if (!IBus_EnterVariant(conn, iter, dbus, &sub1, "IBusText", sizeof("IBusText"))) {126return false;127}128129dbus->message_iter_next(&sub1);130dbus->message_iter_next(&sub1);131dbus->message_iter_next(&sub1);132133if (!IBus_EnterVariant(conn, &sub1, dbus, &sub2, "IBusAttrList", sizeof("IBusAttrList"))) {134return false;135}136137dbus->message_iter_next(&sub2);138dbus->message_iter_next(&sub2);139140if (dbus->message_iter_get_arg_type(&sub2) != DBUS_TYPE_ARRAY) {141return false;142}143144dbus->message_iter_recurse(&sub2, &array);145146while (dbus->message_iter_get_arg_type(&array) == DBUS_TYPE_VARIANT) {147DBusMessageIter sub;148if (IBus_EnterVariant(conn, &array, dbus, &sub, "IBusAttribute", sizeof("IBusAttribute"))) {149Uint32 type;150151dbus->message_iter_next(&sub);152dbus->message_iter_next(&sub);153154// From here on, the structure looks like this:155// Uint32 type: 1=underline, 2=foreground, 3=background156// Uint32 value: for underline it's 0=NONE, 1=SINGLE, 2=DOUBLE,157// 3=LOW, 4=ERROR158// for foreground and background it's a color159// Uint32 start_index: starting position for the style (utf8-char)160// Uint32 end_index: end position for the style (utf8-char)161162dbus->message_iter_get_basic(&sub, &type);163// We only use the background type to determine the selection164if (type == 3) {165Uint32 start = -1;166dbus->message_iter_next(&sub);167dbus->message_iter_next(&sub);168if (dbus->message_iter_get_arg_type(&sub) == DBUS_TYPE_UINT32) {169dbus->message_iter_get_basic(&sub, &start);170dbus->message_iter_next(&sub);171if (dbus->message_iter_get_arg_type(&sub) == DBUS_TYPE_UINT32) {172dbus->message_iter_get_basic(&sub, end_pos);173*start_pos = start;174return true;175}176}177}178}179dbus->message_iter_next(&array);180}181return false;182}183184static const char *IBus_GetVariantText(DBusConnection *conn, DBusMessageIter *iter, SDL_DBusContext *dbus)185{186// The text we need is nested weirdly, use dbus-monitor to see the structure better187const char *text = NULL;188DBusMessageIter sub;189190if (!IBus_EnterVariant(conn, iter, dbus, &sub, "IBusText", sizeof("IBusText"))) {191return NULL;192}193194dbus->message_iter_next(&sub);195dbus->message_iter_next(&sub);196197if (dbus->message_iter_get_arg_type(&sub) != DBUS_TYPE_STRING) {198return NULL;199}200dbus->message_iter_get_basic(&sub, &text);201202return text;203}204205static bool IBus_GetVariantCursorPos(DBusConnection *conn, DBusMessageIter *iter, SDL_DBusContext *dbus,206Uint32 *pos)207{208dbus->message_iter_next(iter);209210if (dbus->message_iter_get_arg_type(iter) != DBUS_TYPE_UINT32) {211return false;212}213214dbus->message_iter_get_basic(iter, pos);215216return true;217}218219static DBusHandlerResult IBus_MessageHandler(DBusConnection *conn, DBusMessage *msg, void *user_data)220{221SDL_DBusContext *dbus = (SDL_DBusContext *)user_data;222223if (dbus->message_is_signal(msg, ibus_input_interface, "CommitText")) {224DBusMessageIter iter;225const char *text;226227dbus->message_iter_init(msg, &iter);228text = IBus_GetVariantText(conn, &iter, dbus);229230SDL_SendKeyboardText(text);231232return DBUS_HANDLER_RESULT_HANDLED;233}234235if (dbus->message_is_signal(msg, ibus_input_interface, "UpdatePreeditText")) {236DBusMessageIter iter;237const char *text;238239dbus->message_iter_init(msg, &iter);240text = IBus_GetVariantText(conn, &iter, dbus);241242if (text) {243Uint32 pos, start_pos, end_pos;244bool has_pos = false;245bool has_dec_pos = false;246247dbus->message_iter_init(msg, &iter);248has_dec_pos = IBus_GetDecorationPosition(conn, &iter, dbus, &start_pos, &end_pos);249if (!has_dec_pos) {250dbus->message_iter_init(msg, &iter);251has_pos = IBus_GetVariantCursorPos(conn, &iter, dbus, &pos);252}253254if (has_dec_pos) {255SDL_SendEditingText(text, start_pos, end_pos - start_pos);256} else if (has_pos) {257SDL_SendEditingText(text, pos, -1);258} else {259SDL_SendEditingText(text, -1, -1);260}261}262263//SDL_IBus_UpdateTextInputArea(SDL_GetKeyboardFocus());264265return DBUS_HANDLER_RESULT_HANDLED;266}267268if (dbus->message_is_signal(msg, ibus_input_interface, "HidePreeditText")) {269SDL_SendEditingText("", 0, 0);270return DBUS_HANDLER_RESULT_HANDLED;271}272273return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;274}275276static char *IBus_ReadAddressFromFile(const char *file_path)277{278char addr_buf[1024];279bool success = false;280FILE *addr_file;281282addr_file = fopen(file_path, "r");283if (!addr_file) {284return NULL;285}286287while (fgets(addr_buf, sizeof(addr_buf), addr_file)) {288if (SDL_strncmp(addr_buf, "IBUS_ADDRESS=", sizeof("IBUS_ADDRESS=") - 1) == 0) {289size_t sz = SDL_strlen(addr_buf);290if (addr_buf[sz - 1] == '\n') {291addr_buf[sz - 1] = 0;292}293if (addr_buf[sz - 2] == '\r') {294addr_buf[sz - 2] = 0;295}296success = true;297break;298}299}300301(void)fclose(addr_file);302303if (success) {304return SDL_strdup(addr_buf + (sizeof("IBUS_ADDRESS=") - 1));305} else {306return NULL;307}308}309310static char *IBus_GetDBusAddressFilename(void)311{312SDL_DBusContext *dbus;313const char *disp_env;314char config_dir[PATH_MAX];315char *display = NULL;316const char *addr;317const char *conf_env;318char *key;319char file_path[PATH_MAX];320const char *host;321char *disp_num, *screen_num;322323if (ibus_addr_file) {324return SDL_strdup(ibus_addr_file);325}326327dbus = SDL_DBus_GetContext();328if (!dbus) {329return NULL;330}331332// Use this environment variable if it exists.333addr = SDL_getenv("IBUS_ADDRESS");334if (addr && *addr) {335return SDL_strdup(addr);336}337338/* Otherwise, we have to get the hostname, display, machine id, config dir339and look up the address from a filepath using all those bits, eek. */340disp_env = SDL_getenv("DISPLAY");341342if (!disp_env || !*disp_env) {343display = SDL_strdup(":0.0");344} else {345display = SDL_strdup(disp_env);346}347348host = display;349disp_num = SDL_strrchr(display, ':');350screen_num = SDL_strrchr(display, '.');351352if (!disp_num) {353SDL_free(display);354return NULL;355}356357*disp_num = 0;358disp_num++;359360if (screen_num) {361*screen_num = 0;362}363364if (!*host) {365const char *session = SDL_getenv("XDG_SESSION_TYPE");366if (session && SDL_strcmp(session, "wayland") == 0) {367host = "unix-wayland";368} else {369host = "unix";370}371}372373SDL_memset(config_dir, 0, sizeof(config_dir));374375conf_env = SDL_getenv("XDG_CONFIG_HOME");376if (conf_env && *conf_env) {377SDL_strlcpy(config_dir, conf_env, sizeof(config_dir));378} else {379const char *home_env = SDL_getenv("HOME");380if (!home_env || !*home_env) {381SDL_free(display);382return NULL;383}384(void)SDL_snprintf(config_dir, sizeof(config_dir), "%s/.config", home_env);385}386387key = SDL_DBus_GetLocalMachineId();388389if (!key) {390SDL_free(display);391return NULL;392}393394SDL_memset(file_path, 0, sizeof(file_path));395(void)SDL_snprintf(file_path, sizeof(file_path), "%s/ibus/bus/%s-%s-%s",396config_dir, key, host, disp_num);397dbus->free(key);398SDL_free(display);399400return SDL_strdup(file_path);401}402403static bool IBus_CheckConnection(SDL_DBusContext *dbus);404405static void SDLCALL IBus_SetCapabilities(void *data, const char *name, const char *old_val,406const char *hint)407{408SDL_DBusContext *dbus = SDL_DBus_GetContext();409410if (IBus_CheckConnection(dbus)) {411Uint32 caps = IBUS_CAP_FOCUS;412413if (hint && SDL_strstr(hint, "composition")) {414caps |= IBUS_CAP_PREEDIT_TEXT;415}416if (hint && SDL_strstr(hint, "candidates")) {417// FIXME, turn off native candidate rendering418}419420SDL_DBus_CallVoidMethodOnConnection(ibus_conn, ibus_service, input_ctx_path, ibus_input_interface, "SetCapabilities",421DBUS_TYPE_UINT32, &caps, DBUS_TYPE_INVALID);422}423}424425static bool IBus_SetupConnection(SDL_DBusContext *dbus, const char *addr)426{427const char *client_name = "SDL3_Application";428const char *path = NULL;429bool result = false;430DBusObjectPathVTable ibus_vtable;431432SDL_zero(ibus_vtable);433ibus_vtable.message_function = &IBus_MessageHandler;434435/* try the portal interface first. Modern systems have this in general,436and sandbox things like FlakPak and Snaps, etc, require it. */437438ibus_is_portal_interface = true;439ibus_service = IBUS_PORTAL_SERVICE;440ibus_interface = IBUS_PORTAL_INTERFACE;441ibus_input_interface = IBUS_PORTAL_INPUT_INTERFACE;442ibus_conn = dbus->session_conn;443444result = SDL_DBus_CallMethodOnConnection(ibus_conn, ibus_service, IBUS_PATH, ibus_interface, "CreateInputContext",445DBUS_TYPE_STRING, &client_name, DBUS_TYPE_INVALID,446DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID);447if (!result) {448ibus_is_portal_interface = false;449ibus_service = IBUS_SERVICE;450ibus_interface = IBUS_INTERFACE;451ibus_input_interface = IBUS_INPUT_INTERFACE;452ibus_conn = dbus->connection_open_private(addr, NULL);453454if (!ibus_conn) {455return false; // oh well.456}457458dbus->connection_flush(ibus_conn);459460if (!dbus->bus_register(ibus_conn, NULL)) {461ibus_conn = NULL;462return false;463}464465dbus->connection_flush(ibus_conn);466467result = SDL_DBus_CallMethodOnConnection(ibus_conn, ibus_service, IBUS_PATH, ibus_interface, "CreateInputContext",468DBUS_TYPE_STRING, &client_name, DBUS_TYPE_INVALID,469DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID);470} else {471// re-using dbus->session_conn472dbus->connection_ref(ibus_conn);473}474475if (result) {476char matchstr[128];477(void)SDL_snprintf(matchstr, sizeof(matchstr), "type='signal',interface='%s'", ibus_input_interface);478SDL_free(input_ctx_path);479input_ctx_path = SDL_strdup(path);480SDL_AddHintCallback(SDL_HINT_IME_IMPLEMENTED_UI, IBus_SetCapabilities, NULL);481dbus->bus_add_match(ibus_conn, matchstr, NULL);482dbus->connection_try_register_object_path(ibus_conn, input_ctx_path, &ibus_vtable, dbus, NULL);483dbus->connection_flush(ibus_conn);484}485486return result;487}488489static bool IBus_CheckConnection(SDL_DBusContext *dbus)490{491if (!dbus) {492return false;493}494495if (ibus_conn && dbus->connection_get_is_connected(ibus_conn)) {496return true;497}498499if (inotify_fd > 0 && inotify_wd > 0) {500char buf[1024];501ssize_t readsize = read(inotify_fd, buf, sizeof(buf));502if (readsize > 0) {503504char *p;505bool file_updated = false;506507for (p = buf; p < buf + readsize; /**/) {508struct inotify_event *event = (struct inotify_event *)p;509if (event->len > 0) {510char *addr_file_no_path = SDL_strrchr(ibus_addr_file, '/');511if (!addr_file_no_path) {512return false;513}514515if (SDL_strcmp(addr_file_no_path + 1, event->name) == 0) {516file_updated = true;517break;518}519}520521p += sizeof(struct inotify_event) + event->len;522}523524if (file_updated) {525char *addr = IBus_ReadAddressFromFile(ibus_addr_file);526if (addr) {527bool result = IBus_SetupConnection(dbus, addr);528SDL_free(addr);529return result;530}531}532}533}534535return false;536}537538bool SDL_IBus_Init(void)539{540bool result = false;541SDL_DBusContext *dbus = SDL_DBus_GetContext();542543if (dbus) {544char *addr_file = IBus_GetDBusAddressFilename();545char *addr;546char *addr_file_dir;547548if (!addr_file) {549return false;550}551552addr = IBus_ReadAddressFromFile(addr_file);553if (!addr) {554SDL_free(addr_file);555return false;556}557558if (ibus_addr_file) {559SDL_free(ibus_addr_file);560}561ibus_addr_file = SDL_strdup(addr_file);562563if (inotify_fd < 0) {564inotify_fd = inotify_init();565fcntl(inotify_fd, F_SETFL, O_NONBLOCK);566}567568addr_file_dir = SDL_strrchr(addr_file, '/');569if (addr_file_dir) {570*addr_file_dir = 0;571}572573inotify_wd = inotify_add_watch(inotify_fd, addr_file, IN_CREATE | IN_MODIFY);574SDL_free(addr_file);575576result = IBus_SetupConnection(dbus, addr);577SDL_free(addr);578579// don't use the addr_file if using the portal interface.580if (result && ibus_is_portal_interface) {581if (inotify_fd > 0) {582if (inotify_wd > 0) {583inotify_rm_watch(inotify_fd, inotify_wd);584inotify_wd = -1;585}586close(inotify_fd);587inotify_fd = -1;588}589}590}591592return result;593}594595void SDL_IBus_Quit(void)596{597SDL_DBusContext *dbus;598599if (input_ctx_path) {600SDL_free(input_ctx_path);601input_ctx_path = NULL;602}603604if (ibus_addr_file) {605SDL_free(ibus_addr_file);606ibus_addr_file = NULL;607}608609dbus = SDL_DBus_GetContext();610611// if using portal, ibus_conn == session_conn; don't release it here.612if (dbus && ibus_conn && !ibus_is_portal_interface) {613dbus->connection_close(ibus_conn);614dbus->connection_unref(ibus_conn);615}616617ibus_conn = NULL;618ibus_service = NULL;619ibus_interface = NULL;620ibus_input_interface = NULL;621ibus_is_portal_interface = false;622623if (inotify_fd > 0 && inotify_wd > 0) {624inotify_rm_watch(inotify_fd, inotify_wd);625inotify_wd = -1;626}627628// !!! FIXME: should we close(inotify_fd) here?629630SDL_RemoveHintCallback(SDL_HINT_IME_IMPLEMENTED_UI, IBus_SetCapabilities, NULL);631632SDL_memset(&ibus_cursor_rect, 0, sizeof(ibus_cursor_rect));633}634635static void IBus_SimpleMessage(const char *method)636{637SDL_DBusContext *dbus = SDL_DBus_GetContext();638639if ((input_ctx_path) && (IBus_CheckConnection(dbus))) {640SDL_DBus_CallVoidMethodOnConnection(ibus_conn, ibus_service, input_ctx_path, ibus_input_interface, method, DBUS_TYPE_INVALID);641}642}643644void SDL_IBus_SetFocus(bool focused)645{646const char *method = focused ? "FocusIn" : "FocusOut";647IBus_SimpleMessage(method);648}649650void SDL_IBus_Reset(void)651{652IBus_SimpleMessage("Reset");653}654655bool SDL_IBus_ProcessKeyEvent(Uint32 keysym, Uint32 keycode, bool down)656{657Uint32 result = 0;658SDL_DBusContext *dbus = SDL_DBus_GetContext();659660if (IBus_CheckConnection(dbus)) {661Uint32 mods = IBus_ModState();662Uint32 ibus_keycode = keycode - 8;663if (!down) {664mods |= (1 << 30); // IBUS_RELEASE_MASK665}666if (!SDL_DBus_CallMethodOnConnection(ibus_conn, ibus_service, input_ctx_path, ibus_input_interface, "ProcessKeyEvent",667DBUS_TYPE_UINT32, &keysym, DBUS_TYPE_UINT32, &ibus_keycode, DBUS_TYPE_UINT32, &mods, DBUS_TYPE_INVALID,668DBUS_TYPE_BOOLEAN, &result, DBUS_TYPE_INVALID)) {669result = 0;670}671}672673//SDL_IBus_UpdateTextInputArea(SDL_GetKeyboardFocus());674675return (result != 0);676}677678void SDL_IBus_PumpEvents(void)679{680SDL_DBusContext *dbus = SDL_DBus_GetContext();681682if (IBus_CheckConnection(dbus)) {683dbus->connection_read_write(ibus_conn, 0);684685while (dbus->connection_dispatch(ibus_conn) == DBUS_DISPATCH_DATA_REMAINS) {686// Do nothing, actual work happens in IBus_MessageHandler687}688}689}690691#endif // SDL_USE_LIBDBUS692693#endif694695696