修复 OpenFL 的 TextField 不显示输入法候选词列表的问题

在前文《试用跨平台 GUI 框架 HaxeUI》中提到了 OpenFLTextField 在启用输入法时无法显示候选词列表的问题。经过一番摸索,发现了问题出在了 SDL 库的身上。

OpenFL 的底层基于 Lime ,而 Lime 的桌面后端又是基于 SDL 。OpenFL 默认使用的 SDL 代码位于 openfl/libsdl 。问题定位于源文件 SDL_windowskeyboard.c 中的 IME_init() 函数中,只要将第 373 行的代码注释掉,就可以正常显示输入法候选词列表了:

C++static void
IME_Init(SDL_VideoData *videodata, HWND hwnd)
{
    if (videodata->ime_initialized)
        return;

    videodata->ime_hwnd_main = hwnd;
    if (SUCCEEDED(WIN_CoInitialize())) {
        videodata->ime_com_initialized = SDL_TRUE;
        CoCreateInstance(&CLSID_TF_ThreadMgr, NULL, CLSCTX_INPROC_SERVER, &IID_ITfThreadMgr, (LPVOID *)&videodata->ime_threadmgr);
    }
    videodata->ime_initialized = SDL_TRUE;
    videodata->ime_himm32 = SDL_LoadObject("imm32.dll");
    if (!videodata->ime_himm32) {
        videodata->ime_available = SDL_FALSE;
        SDL_ClearError();
        return;
    }
    videodata->ImmLockIMC = (LPINPUTCONTEXT2 (WINAPI *)(HIMC))SDL_LoadFunction(videodata->ime_himm32, "ImmLockIMC");
    videodata->ImmUnlockIMC = (BOOL (WINAPI *)(HIMC))SDL_LoadFunction(videodata->ime_himm32, "ImmUnlockIMC");
    videodata->ImmLockIMCC = (LPVOID (WINAPI *)(HIMCC))SDL_LoadFunction(videodata->ime_himm32, "ImmLockIMCC");
    videodata->ImmUnlockIMCC = (BOOL (WINAPI *)(HIMCC))SDL_LoadFunction(videodata->ime_himm32, "ImmUnlockIMCC");

    IME_SetWindow(videodata, hwnd);
    videodata->ime_himc = ImmGetContext(hwnd);
    ImmReleaseContext(hwnd, videodata->ime_himc);
    if (!videodata->ime_himc) {
        videodata->ime_available = SDL_FALSE;
        IME_Disable(videodata, hwnd);
        return;
    }
    videodata->ime_available = SDL_TRUE;
    IME_UpdateInputLocale(videodata);
    IME_SetupAPI(videodata);
    videodata->ime_uiless = UILess_SetupSinks(videodata);
    IME_UpdateInputLocale(videodata);
    IME_Disable(videodata, hwnd);
}
src/video/windows/SDL_windowskeyboard.c

事实上,最新版的 SDL 已经修复了这个问题1,但是 OpenFL 使用的 SDL 代码已经很久没更新了。SDL 官方的修复方案是此处增加了一个条件判断:

C++if (WIN_ShouldShowNativeUI()) {
    videodata->ime_uiless = SDL_FALSE;
} else {
    videodata->ime_uiless = UILess_SetupSinks(videodata);
}

其中 WIN_ShouldShowNativeUI() 的代码如下:

C++static SDL_bool WIN_ShouldShowNativeUI()
{
    return SDL_GetHintBoolean(SDL_HINT_IME_SHOW_UI, SDL_FALSE);
}

遗憾的是,旧版的 SDL 代码中未定义 SDL_HINT_IME_SHOW_UI 宏,上面的代码不可用。不过我们只需要将这行代码注释掉或者改成 videodata->ime_uiless = SDL_FALSE; 就可以了。

现在,需要用我们修改过并重新编译的 Lime 库来替代 haxelib 上的官方库。

首先,从 GitHub 上克隆 Lime 项目的代码:

SHELLgit clone --recursive https://github.com/openfl/lime

可以切换到 master 分支,也可以直接使用 develop 分支。

假设刚才克隆的 Lime 项目路径为 D:\Projects\lime ,将已安装的 Lime 库重定位到本地项目:

SHELLhaxelib dev lime D:\Projects\lime

使用 haxelib list 命令查看所有已安装的 Haxe 库。如果结果为:

lime: 8.1.1 [dev:D:\Projects\lime]

则表示命令已经生效。

修改项目下 project/lib/sdl/src/video/windows/SDL_windowskeyboard.c 文件的代码,按照之前提到的方法,将 videodata->ime_uiless = UILess_SetupSinks(videodata); 注释掉,或者修改为 videodata->ime_uiless = SDL_FALSE;

重新编译 Lime 库:

SHELLlime rebuild windows

如果编译成功,会在项目下 ndll/Windowsndll/Windows64 下分别生成如下文件:

最后,重新编译 HaxeUI 项目:

SHELLhaxelib run haxeui-core build openfl windows

现在,输入法候选词列表已经可以正常显示了。

ime-candidate-list


所幸,从下一个版本 (8.2.0) 开始,Lime 依赖的 SDL 代码会从 openfl/libsdl 切换到 libsdl-org/SDL ,这个问题应该会被修复。不过我克隆了 8.2.0-Dev 分支,发现 SDL_HINT_IME_SHOW_UI 并未默认开启,仍需要修改代码来修复。

首先克隆下 8.2.0-Dev 分支:

SHELLgit clone --recursive -b 8.2.0-Dev https://github.com/openfl/lime D:\Projects\lime-8.2.0

打开项目下的 project/src/backend/sdl/SDLWindow.cpp 文件,在 SDLWindow::SDLWindow() 函数中添加代码:

C++#if defined (HX_WINDOWS)
SDL_SetHint (SDL_HINT_IME_SHOW_UI, "1");
#endif

切换 Lime 库路径并重新编译:

SHELLhaxelib dev lime D:\Projects\lime-8.2.0
lime rebuild windows

最后希望 8.2.0 正式发布时能够真正修复这个 BUG 。


修复 NME 的 TextField 不显示输入法候选词列表的问题

按照相同的方法,我们也可以修复NME 的 TextField 不显示输入法候选词列表的问题。

安装依赖包和下载 NME 项目代码:

SHELLhaxelib install gm2d
haxelib install format
haxelib install nme-toolkit
haxelib install hxcpp
git clone --recursive https://github.com/haxenme/nme D:\Projects\nme

编辑 haxe 安装目录下的 lib\nme-toolkit\6,3,4\sdl\src\video\windows\SDL_windowskeyboard.c 源代码文件,按照上文进行修改。

重新编译 NME:

SHELLcd D:\Projects\nme
haxelib dev nme .
cd D:\Projects\nme\tools\nme
haxe compile.hxml
cd D:\Projects\nme\project
haxelib run hxcpp ToolkitBuild.xml -DHXCPP_M64

编译完成后会在目录 D:\Projects\nme\ndll\Windows64 下生成 nme.ndll 文件。将此文件替换项目中的同名文件即可。

不过修复后仍有两个问题:

  1. 输入法候选词列表不跟随 TextField,而处在桌面底部
  2. 开启输入法输入时,一次只能输入一个字符