diff --git a/config.lib b/config.lib index f273a7278b..3857bb6fff 100644 --- a/config.lib +++ b/config.lib @@ -72,6 +72,7 @@ set_default() { with_lzma="1" with_lzo2="1" with_xdg_basedir="1" + with_fcitx="1" with_png="1" enable_builtin_depend="1" with_makedepend="0" @@ -152,6 +153,7 @@ set_default() { with_lzma with_lzo2 with_xdg_basedir + with_fcitx with_png enable_builtin_depend with_makedepend @@ -364,6 +366,10 @@ detect_params() { --without-libxdg-basedir) with_xdg_basedir="0";; --with-libxdg-basedir=*) with_xdg_basedir="$optarg";; + --with-fcitx) with_fcitx="2";; + --without-fcitx) with_fcitx="0";; + --with-fcitx=*) with_fcitx="$optarg";; + --with-png) with_png="2";; --without-png) with_png="0";; --with-png=*) with_png="$optarg";; @@ -895,6 +901,7 @@ check_params() { fi detect_xdg_basedir + detect_fcitx detect_png detect_freetype detect_fontconfig @@ -2107,6 +2114,39 @@ EOL fi fi + if [ -n "$dbus_config" ]; then + CFLAGS="$CFLAGS -DWITH_DBUS" + CFLAGS="$CFLAGS `$dbus_config --cflags | tr '\n\r' ' '`" + + if [ "$enable_static" != "0" ]; then + LIBS="$LIBS `$dbus_config --libs --static | tr '\n\r' ' '`" + else + LIBS="$LIBS `$dbus_config --libs | tr '\n\r' ' '`" + fi + fi + + if [ -n "$x11_config" ]; then + CFLAGS="$CFLAGS -DWITH_X11" + CFLAGS="$CFLAGS `$x11_config --cflags | tr '\n\r' ' '`" + + if [ "$enable_static" != "0" ]; then + LIBS="$LIBS `$x11_config --libs --static | tr '\n\r' ' '`" + else + LIBS="$LIBS `$x11_config --libs | tr '\n\r' ' '`" + fi + fi + + if [ -n "$fcitx_config" ]; then + CFLAGS="$CFLAGS -DWITH_FCITX" + CFLAGS="$CFLAGS `$fcitx_config --cflags | tr '\n\r' ' '`" + + if [ "$enable_static" != "0" ]; then + LIBS="$LIBS `$fcitx_config --libs --static | tr '\n\r' ' '`" + else + LIBS="$LIBS `$fcitx_config --libs | tr '\n\r' ' '`" + fi + fi + # 64bit machines need -D_SQ64 if [ "$cpu_type" = "64" ] && [ "$enable_universal" = "0" ]; then CFLAGS="$CFLAGS -D_SQ64" @@ -3207,6 +3247,27 @@ detect_xdg_basedir() { detect_pkg_config "$with_xdg_basedir" "libxdg-basedir" "xdg_basedir_config" "1.2" } +detect_fcitx() { + if ([ "$with_fcitx" = "1" ] && [ "$enable_dedicated" = "0" ]) || [ "$with_fcitx" = "2" ]; then + detect_pkg_config "$with_fcitx" "dbus-1" "dbus_config" "1.0" "1" + detect_pkg_config "$with_fcitx" "x11" "x11_config" "1.0" "1" + if [ -z "$dbus_config" ]; then + log 1 "checking fcitx... no dbus, skipping" + fcitx_config="" + dbus_config="" + x11_config="" + return 0 + elif [ -z "$x11_config" ]; then + log 1 "checking fcitx... no x11, skipping" + fcitx_config="" + dbus_config="" + x11_config="" + return 0 + fi + fi + detect_pkg_config "$with_fcitx" "fcitx" "fcitx_config" "4.0" "1" +} + detect_png() { detect_pkg_config "$with_png" "libpng" "png_config" "1.2" } diff --git a/src/video/sdl2_v.cpp b/src/video/sdl2_v.cpp index 9f01295b81..1edd4879cd 100644 --- a/src/video/sdl2_v.cpp +++ b/src/video/sdl2_v.cpp @@ -23,8 +23,7 @@ #include "../core/math_func.hpp" #include "../fileio_func.h" #include "../framerate_type.h" -#include "../window_func.h" -#include "../window_gui.h" +#include "../scope.h" #include "sdl2_v.h" #include #include @@ -35,6 +34,15 @@ #include "../3rdparty/mingw-std-threads/mingw.condition_variable.h" #endif +#if defined(WITH_FCITX) +#include +#include +#include +#include +#include +#include +#endif + #include "../safeguards.h" static FVideoDriver_SDL iFVideoDriver_SDL; @@ -64,6 +72,191 @@ static int _window_size_h; static std::string _editing_text; +static void SetTextInputRect(); + +Point GetFocusedWindowCaret(); +Point GetFocusedWindowTopLeft(); +bool FocusedWindowIsConsole(); +bool EditBoxInGlobalFocus(); + +#if defined(WITH_FCITX) +static bool _fcitx_mode = false; +static char _fcitx_service_name[64]; +static char _fcitx_ic_name[64]; +static DBusConnection *_fcitx_dbus_session_conn = nullptr; + +static void FcitxICMethod(const char *method) +{ + DBusMessage *msg = dbus_message_new_method_call(_fcitx_service_name, _fcitx_ic_name, "org.fcitx.Fcitx.InputContext", method); + if (!msg) return; + dbus_connection_send(_fcitx_dbus_session_conn, msg, NULL); + dbus_connection_flush(_fcitx_dbus_session_conn); + dbus_message_unref(msg); +} + +static int GetXDisplayNum() +{ + const char *display = getenv("DISPLAY"); + if (!display) return 0; + const char *colon = strchr(display, ':'); + if (!colon) return 0; + return atoi(colon + 1); +} + +static void FcitxDeinit() { + if (_fcitx_mode) { + FcitxICMethod("DestroyIC"); + _fcitx_mode = false; + } + if (_fcitx_dbus_session_conn) { + dbus_connection_close(_fcitx_dbus_session_conn); + _fcitx_dbus_session_conn = nullptr; + } +} + +static DBusHandlerResult FcitxDBusMessageFilter(DBusConnection *connection, DBusMessage *message, void *user_data) +{ + if (dbus_message_is_signal(message, "org.fcitx.Fcitx.InputContext", "CommitString")) { + DBusMessageIter iter; + const char *text = nullptr; + dbus_message_iter_init(message, &iter); + dbus_message_iter_get_basic(&iter, &text); + + if (text != nullptr && EditBoxInGlobalFocus()) { + HandleTextInput(nullptr, true); + HandleTextInput(text); + SetTextInputRect(); + } + + return DBUS_HANDLER_RESULT_HANDLED; + } + + if (dbus_message_is_signal(message, "org.fcitx.Fcitx.InputContext", "UpdatePreedit")) { + const char *text = nullptr; + int32 cursor; + if (!dbus_message_get_args(message, nullptr, DBUS_TYPE_STRING, &text, DBUS_TYPE_INT32, &cursor, DBUS_TYPE_INVALID)) return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + + if (text != nullptr && EditBoxInGlobalFocus()) { + HandleTextInput(text, true, text + min(cursor, strlen(text))); + } + return DBUS_HANDLER_RESULT_HANDLED; + } + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +static void FcitxInit() +{ + DBusError err; + dbus_error_init(&err); + _fcitx_dbus_session_conn = dbus_bus_get_private(DBUS_BUS_SESSION, &err); + if (dbus_error_is_set(&err)) { + dbus_error_free(&err); + return; + } + dbus_connection_set_exit_on_disconnect(_fcitx_dbus_session_conn, false); + seprintf(_fcitx_service_name, lastof(_fcitx_service_name), "org.fcitx.Fcitx-%d", GetXDisplayNum()); + + auto guard = scope_guard([]() { + if (!_fcitx_mode) FcitxDeinit(); + }); + + int pid = getpid(); + int id = -1; + uint32 enable, hk1sym, hk1state, hk2sym, hk2state; + DBusMessage *msg = dbus_message_new_method_call(_fcitx_service_name, "/inputmethod", "org.fcitx.Fcitx.InputMethod", "CreateICv3"); + if (!msg) return; + auto guard1 = scope_guard([&]() { + dbus_message_unref(msg); + }); + const char *name = "OpenTTD"; + if (!dbus_message_append_args(msg, DBUS_TYPE_STRING, &name, DBUS_TYPE_INT32, &pid, DBUS_TYPE_INVALID)) return; + DBusMessage *reply = dbus_connection_send_with_reply_and_block(_fcitx_dbus_session_conn, msg, 100, nullptr); + if (!reply) return; + auto guard2 = scope_guard([&]() { + dbus_message_unref(reply); + }); + if (!dbus_message_get_args(reply, nullptr, DBUS_TYPE_INT32, &id, DBUS_TYPE_BOOLEAN, &enable, DBUS_TYPE_UINT32, &hk1sym, DBUS_TYPE_UINT32, &hk1state, DBUS_TYPE_UINT32, &hk2sym, DBUS_TYPE_UINT32, &hk2state, DBUS_TYPE_INVALID)) return; + + if (id < 0) return; + + seprintf(_fcitx_ic_name, lastof(_fcitx_ic_name), "/inputcontext_%d", id); + dbus_bus_add_match(_fcitx_dbus_session_conn, "type='signal', interface='org.fcitx.Fcitx.InputContext'", nullptr); + dbus_connection_add_filter(_fcitx_dbus_session_conn, &FcitxDBusMessageFilter, nullptr, nullptr); + dbus_connection_flush(_fcitx_dbus_session_conn); + + uint32 caps = CAPACITY_PREEDIT; + DBusMessage *msg2 = dbus_message_new_method_call(_fcitx_service_name, _fcitx_ic_name, "org.fcitx.Fcitx.InputContext", "SetCapacity"); + if (!msg2) return; + auto guard3 = scope_guard([&]() { + dbus_message_unref(msg2); + }); + if (!dbus_message_append_args(msg2, DBUS_TYPE_UINT32, &caps, DBUS_TYPE_INVALID)) return; + dbus_connection_send(_fcitx_dbus_session_conn, msg2, NULL); + dbus_connection_flush(_fcitx_dbus_session_conn); + + SDL_EventState(SDL_SYSWMEVENT, 1); + + _fcitx_mode = true; +} + +static uint32 _fcitx_last_keycode; +static uint32 _fcitx_last_keysym; +static bool FcitxProcessKey(const SDL_Keysym &key) +{ + uint32 fcitx_mods = 0; + if (key.mod & KMOD_SHIFT) fcitx_mods |= FcitxKeyState_Shift; + if (key.mod & KMOD_CAPS) fcitx_mods |= FcitxKeyState_CapsLock; + if (key.mod & KMOD_CTRL) fcitx_mods |= FcitxKeyState_Ctrl; + if (key.mod & KMOD_ALT) fcitx_mods |= FcitxKeyState_Alt; + if (key.mod & KMOD_NUM) fcitx_mods |= FcitxKeyState_NumLock; + if (key.mod & KMOD_LGUI) fcitx_mods |= FcitxKeyState_Super; + if (key.mod & KMOD_RGUI) fcitx_mods |= FcitxKeyState_Meta; + + int type = FCITX_PRESS_KEY; + uint32 event_time = 0; + + DBusMessage *msg = dbus_message_new_method_call(_fcitx_service_name, _fcitx_ic_name, "org.fcitx.Fcitx.InputContext", "ProcessKeyEvent"); + if (!msg) return false; + auto guard1 = scope_guard([&]() { + dbus_message_unref(msg); + }); + if (!dbus_message_append_args(msg, DBUS_TYPE_UINT32, &_fcitx_last_keysym, DBUS_TYPE_UINT32, &_fcitx_last_keycode, DBUS_TYPE_UINT32, &fcitx_mods, + DBUS_TYPE_INT32, &type, DBUS_TYPE_UINT32, &event_time, DBUS_TYPE_INVALID)) return false; + DBusMessage *reply = dbus_connection_send_with_reply_and_block(_fcitx_dbus_session_conn, msg, 300, nullptr); + if (!reply) return false; + auto guard2 = scope_guard([&]() { + dbus_message_unref(reply); + }); + uint32 handled = 0; + if (!dbus_message_get_args(reply, nullptr, DBUS_TYPE_INT32, &handled, DBUS_TYPE_INVALID)) return false; + return handled; +} + +static void FcitxPoll() +{ + dbus_connection_read_write(_fcitx_dbus_session_conn, 0); + while (dbus_connection_dispatch(_fcitx_dbus_session_conn) == DBUS_DISPATCH_DATA_REMAINS) {} +} + +static void FcitxFocusChange(bool focused) +{ + FcitxICMethod(focused ? "FocusIn" : "FocusOut"); +} + +static void FcitxSYSWMEVENT(const SDL_SysWMEvent &event) +{ + if (event.msg->subsystem != SDL_SYSWM_X11) return; + XEvent &xevent = event.msg->msg.x11.event; + if (xevent.type == KeyPress) { + KeySym keysym = XLookupKeysym(&xevent.xkey, 0); + _fcitx_last_keycode = xevent.xkey.keycode; + _fcitx_last_keysym = keysym; + } +} +#else +const static bool _fcitx_mode = false; +#endif + void VideoDriver_SDL::MakeDirty(int left, int top, int width, int height) { if (_num_dirty_rects < MAX_DIRTY_RECTS) { @@ -362,21 +555,79 @@ bool VideoDriver_SDL::ClaimMousePointer() return true; } +static void StartTextInput() +{ +#if defined(WITH_FCITX) + if (_fcitx_mode) { + return; + } +#endif + SDL_StartTextInput(); +} + +static void StopTextInput() +{ +#if defined(WITH_FCITX) + if (_fcitx_mode) { + return; + } +#endif + SDL_StopTextInput(); +} + static void SetTextInputRect() { SDL_Rect winrect; - Point pt = _focused_window->GetCaretPosition(); - winrect.x = _focused_window->left + pt.x; - winrect.y = _focused_window->top + pt.y; + Point caret = GetFocusedWindowCaret(); + Point win = GetFocusedWindowTopLeft(); + winrect.x = win.x + caret.x; + winrect.y = win.y + caret.y; winrect.w = 1; winrect.h = FONT_HEIGHT_NORMAL; + +#if defined(WITH_FCITX) + if (_fcitx_mode) { + SDL_SysWMinfo info; + SDL_VERSION(&info.version); + if (!SDL_GetWindowWMInfo(_sdl_window, &info)) { + return; + } + int x = 0; + int y = 0; + if (info.subsystem == SDL_SYSWM_X11) { + Display *x_disp = info.info.x11.display; + Window x_win = info.info.x11.window; + XWindowAttributes attrib; + XGetWindowAttributes(x_disp, x_win, &attrib); + Window unused; + XTranslateCoordinates(x_disp, x_win, attrib.root, 0, 0, &x, &y, &unused); + } else { + SDL_GetWindowPosition(_sdl_window, &x, &y); + } + x += winrect.x; + y += winrect.y; + DBusMessage *msg = dbus_message_new_method_call(_fcitx_service_name, _fcitx_ic_name, "org.fcitx.Fcitx.InputContext", "SetCursorRect"); + if (!msg) return; + auto guard = scope_guard([&]() { + dbus_message_unref(msg); + }); + if (!dbus_message_append_args(msg, DBUS_TYPE_INT32, &x, DBUS_TYPE_INT32, &y, DBUS_TYPE_INT32, &winrect.w, + DBUS_TYPE_INT32, &winrect.h, DBUS_TYPE_INVALID)) return; + dbus_connection_send(_fcitx_dbus_session_conn, msg, NULL); + dbus_connection_flush(_fcitx_dbus_session_conn); + return; + } +#endif + SDL_SetTextInputRect(&winrect); } void VideoDriver_SDL::EditBoxGainedFocus() { if (!this->edit_box_focused) { - SDL_StartTextInput(); + if (!_fcitx_mode) { + StartTextInput(); + } this->edit_box_focused = true; } SetTextInputRect(); @@ -385,7 +636,14 @@ void VideoDriver_SDL::EditBoxGainedFocus() void VideoDriver_SDL::EditBoxLostFocus() { if (this->edit_box_focused) { - SDL_StopTextInput(); + if (_fcitx_mode) { +#if defined(WITH_FCITX) + FcitxICMethod("Reset"); + FcitxICMethod("CloseIC"); +#endif + } else { + StopTextInput(); + } this->edit_box_focused = false; } /* Clear any marked string from the current edit box. */ @@ -525,8 +783,11 @@ static uint ConvertSdlKeycodeIntoMy(SDL_Keycode kc) int VideoDriver_SDL::PollEvent() { - SDL_Event ev; +#if defined(WITH_FCITX) + if (_fcitx_mode) FcitxPoll(); +#endif + SDL_Event ev; if (!SDL_PollEvent(&ev)) return -2; switch (ev.type) { @@ -584,6 +845,15 @@ int VideoDriver_SDL::PollEvent() break; case SDL_KEYDOWN: // Toggle full-screen on ALT + ENTER/F +#if defined(WITH_FCITX) + if (_fcitx_mode && EditBoxInGlobalFocus() && !(FocusedWindowIsConsole() && + ConvertSdlKeycodeIntoMy(SDL_GetKeyFromName(ev.text.text)) == WKC_BACKQUOTE)) { + if (FcitxProcessKey(ev.key.keysym)) { + /* key press handled by Fcitx */ + break; + } + } +#endif if ((ev.key.keysym.mod & (KMOD_ALT | KMOD_GUI)) && (ev.key.keysym.sym == SDLK_RETURN || ev.key.keysym.sym == SDLK_f)) { if (ev.key.repeat == 0) ToggleFullScreen(!_fullscreen); @@ -603,14 +873,15 @@ int VideoDriver_SDL::PollEvent() keycode & WKC_ALT || (keycode >= WKC_F1 && keycode <= WKC_F12) || !IsValidChar(character, CS_ALPHANUMERAL) || - !this->edit_box_focused) { + !this->edit_box_focused || + _fcitx_mode) { HandleKeypress(keycode, character); } } break; case SDL_TEXTINPUT: { - if (EditBoxInGlobalFocus() && !(_focused_window->window_class == WC_CONSOLE && + if (EditBoxInGlobalFocus() && !(FocusedWindowIsConsole() && ConvertSdlKeycodeIntoMy(SDL_GetKeyFromName(ev.text.text)) == WKC_BACKQUOTE)) { HandleTextInput(nullptr, true); HandleTextInput(ev.text.text); @@ -652,9 +923,25 @@ int VideoDriver_SDL::PollEvent() // mouse left the window, undraw cursor UndrawMouseCursor(); _cursor.in_window = false; + } else if (ev.window.event == SDL_WINDOWEVENT_MOVED) { + if (_fcitx_mode) SetTextInputRect(); + } else if (ev.window.event == SDL_WINDOWEVENT_FOCUS_GAINED) { +#if defined(WITH_FCITX) + if (_fcitx_mode) FcitxFocusChange(true); +#endif + } else if (ev.window.event == SDL_WINDOWEVENT_FOCUS_LOST) { +#if defined(WITH_FCITX) + if (_fcitx_mode) FcitxFocusChange(false); +#endif } break; } + + case SDL_SYSWMEVENT: { +#if defined(WITH_FCITX) + if (_fcitx_mode) FcitxSYSWMEVENT(ev.syswm); +#endif + } } return -1; } @@ -690,11 +977,18 @@ const char *VideoDriver_SDL::Start(const char * const *parm) SDL_StopTextInput(); this->edit_box_focused = false; +#if defined(WITH_FCITX) + FcitxInit(); +#endif + return nullptr; } void VideoDriver_SDL::Stop() { +#if defined(WITH_FCITX) + FcitxDeinit(); +#endif SDL_QuitSubSystem(SDL_INIT_VIDEO); if (SDL_WasInit(SDL_INIT_EVERYTHING) == 0) { SDL_Quit(); // If there's nothing left, quit SDL diff --git a/src/window.cpp b/src/window.cpp index 92dc9981d3..710cd8562f 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -454,6 +454,21 @@ void SetFocusedWindow(Window *w) if (_focused_window != nullptr) _focused_window->OnFocus(old_focused); } +Point GetFocusedWindowCaret() +{ + return _focused_window->GetCaretPosition(); +} + +Point GetFocusedWindowTopLeft() +{ + return { _focused_window->left, _focused_window->top }; +} + +bool FocusedWindowIsConsole() +{ + return _focused_window && _focused_window->window_class == WC_CONSOLE; +} + /** * Check if an edit box is in global focus. That is if focused window * has a edit box as focused widget, or if a console is focused.