IME: Add direct support for Fcitx IME input, with SDL2 on *nix/X11.

pull/104/head
Jonathan G Rennison 5 years ago
parent 91e05f6306
commit 24b5cb0fdb

@ -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"
}

@ -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 <SDL.h>
#include <mutex>
@ -35,6 +34,15 @@
#include "../3rdparty/mingw-std-threads/mingw.condition_variable.h"
#endif
#if defined(WITH_FCITX)
#include <fcitx/frontend.h>
#include <dbus/dbus.h>
#include <SDL_syswm.h>
#include <X11/Xlib.h>
#include <X11/keysym.h>
#include <unistd.h>
#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<uint>(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

@ -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.

Loading…
Cancel
Save