Bug 反馈:Web 平台无法输入中文(IME 组合事件未到达 SDL)
06/0453 浏览BUG反馈
问题描述
我在开发一款 DND 风格的回合制策略 RPG,项目中有一个 F9 快捷键调出的 UI 编辑器(NanoVG 即时模式渲染),编辑器中有文本输入功能(备注字段、重命名等)。
遇到的问题是:在 Web 平台上,所有文本输入框(包括自定义 NanoVG 编辑器和引擎自带的 urhox-libs/UI 的 TextField 组件)都无法输入中文。英文字符输入正常,但切换到中文输入法后,无论使用何种 IME(微软拼音、搜狗等),都没有任何字符被输入。粘贴中文内容同样无效。
关键事实:此功能以前是正常工作的。在某次引擎构建更新后失效。


排查过程
第一步:排除编辑器代码问题
最初怀疑是 F9 编辑器代码拆分时引入的 bug(编辑器从单文件拆为 UIEditor.lua / UIEditorDraw.lua / UIEditorInput.lua / UIEditorData.lua 四文件)。
为了验证,编写了独立的 IME 测试脚本,仅使用引擎自带的 UI.TextField 组件,完全不涉及 F9 编辑器代码:
lua复制
lualocal UI = require("urhox-libs/UI") local textField = UI.TextField { placeholder = "在此输入中文测试...", onChange = function(self, value) print("onChange: " .. value) end, }
测试结果:UI.TextField 同样无法输入中文。英文正常、粘贴英文正常、粘贴中文无效、中文输入法无效。
结论:不是编辑器代码的问题,是引擎平台层面的问题。
第二步:编写运行时诊断工具
由于 Web 平台无法同时打开 F12 DevTools 和游戏页面(打开 DevTools 会关闭游戏),编写了屏幕叠加诊断工具 scripts/tools/IMEDiag.lua,使用 NanoVG 在游戏画面上直接渲染输入事件日志。
诊断工具订阅了以下引擎事件:
- KeyDown — 按键按下
- KeyUp — 按键抬起
- TextInput — 文本输入(已提交的字符)
- TextEditing — IME 组合文本(拼音候选等)
第三步:采集诊断数据
通过诊断工具在运行时实际观察到以下行为:


诊断数据与发现
运行时环境状态
screenKeyboardSupport = false (平台报告不支持屏幕键盘)
screenKeyboardVisible = true (SDL 认为 TextInput 已激活)
GetPlatform() = "Web"
发现 1:英文输入完全正常
当系统输入法处于英文模式时,每次按键都会产生一对完整事件:
KeyDown: key=100 scancode=7 qual=0 (D键按下)
TextInput: "d" [64] (收到字符 'd')
英文字母、数字、符号、退格、回车均正常工作。
发现 2:Ctrl+Space IME 切换组合键被浏览器拦截
按下 Ctrl+Space 切换输入法时,诊断工具只收到 Ctrl 的 KeyDown 事件:
KeyDown: key=1073742048 scancode=224 qual=2 (左Ctrl按下)
Space 的 KeyDown 事件从未出现。浏览器在操作系统层面拦截了 Ctrl+Space 组合键用于 IME 切换,不会将 Space 传递给 Canvas/SDL。
但用户确认:系统任务栏的输入法图标确实发生了变化,说明 OS 层面的 IME 切换成功了。
发现 3:IME 激活后所有事件消失
切换到中文输入法后,在游戏中按任何键:
- 没有 KeyDown 事件
- 没有 TextInput 事件
- 没有 TextEditing 事件(IME 组合事件)
完全静默。诊断日志不再有任何新条目。
即使切回英文模式(通过 Shift 或再次 Ctrl+Space),英文字符也不再出现,直到重新点击 Canvas 获取焦点。
发现 4:SetScreenKeyboardVisible 是空操作
lua复制
luainput:SetScreenKeyboardVisible(true) -- 无效果 input:SetScreenKeyboardVisible(false) -- 无效果
由于 screenKeyboardSupport = false,这两个调用在 Web 平台上是完全空操作。SDL 层面的 SDL_StartTextInput() / SDL_StopTextInput() 实际上没有执行(或执行了但没有效果)。
发现 5:UI.TextField 组件同样受影响
引擎自带的 urhox-libs/UI/Widgets/TextField.lua 内部调用的也是 input:SetScreenKeyboardVisible(true),因此同样无法触发 IME。这排除了任何应用层代码的问题。


我尝试过的修复
尝试 1:移除所有 SetScreenKeyboardVisible(false) 调用
理论:可能某处代码调用了 SetScreenKeyboardVisible(false) 导致 SDL_StopTextInput 永久关闭了 IME。
结果:移除所有 false 调用后,问题不变。
尝试 2:在文本框获得焦点时仅调用一次 SetScreenKeyboardVisible(true)
理论:可能需要主动触发 SDL_StartTextInput 来聚焦隐藏的 textarea。
结果:问题不变。screenKeyboardSupport=false 导致此调用是空操作。
尝试 3:从 Lua 层寻找 JavaScript 互操作 API
搜索了 .emmylua/、engine-docs/、urhox-libs/ 所有目录,查找任何 JS 桥接、eval、emscripten 相关 API。
结果:引擎不暴露任何 JavaScript 互操作接口到 Lua 层。无法从 Lua 代码手动创建/聚焦 HTML input 元素。


问题根因分析
基于以上诊断数据,问题的根因是:
SDL/Emscripten 层的隐藏 textarea 没有正确接收浏览器的 IME 组合事件。
在 Web 平台上,IME 组合事件(compositionstart / compositionupdate / compositionend)只会发送给当前 focused 的 <input> 或 <textarea> 元素。SDL 的 Emscripten 后端通常会创建一个隐藏的 textarea 来接收这些事件,然后转化为 SDL 的 SDL_TEXTEDITING 和 SDL_TEXTINPUT 事件。
当前的表现说明以下情况之一:
- SDL 的隐藏 textarea 没有被创建
- 或创建了但没有获得浏览器 focus
- 或 SDL_StartTextInput() 因为某种原因没有被调用(screenKeyboardSupport=false 可能导致跳过了 textarea 创建逻辑)
- 或浏览器/Emscripten SDK 更新后改变了事件传递行为
证据支持:urhox-libs/UI/Widgets/TextField.lua 第 593-598 行有以下注释:
lua复制
lua-- Web + touch: browser paste event via hidden input already injects TextInput, -- skip DoPaste to avoid double-paste.
这说明引擎设计上确实依赖一个隐藏 input 元素来桥接浏览器事件。但当前这个机制失效了。


这个问题的更广泛影响
对所有 Web 项目的影响:这不是个别项目的问题。任何在 Web 平台上需要中文输入的 UrhoX 项目都会受到影响,包括使用引擎自带 UI.TextField 组件的项目。
对开发者的影响:
- 需要中文输入的游戏功能(聊天、角色命名、编辑器文本字段等)在 Web 平台完全不可用
- 此问题在 Lua 层面没有 workaround — 没有 JS 互操作接口可以手动修复
- 排查成本高 — 需要编写自定义运行时诊断工具才能确认原因(Web 平台无法同时使用 DevTools)
对玩家的影响:任何需要输入中文的交互(角色命名、聊天等)对中文用户完全不可用。


期望的修复方向
- 确认 Emscripten 后端是否正确创建了隐藏 textarea 用于 IME 输入
- 确认 SDL_StartTextInput() 在 Web 平台是否正确聚焦该 textarea
- 检查 screenKeyboardSupport 返回 false 是否错误地跳过了 textarea 创建/聚焦逻辑
- 验证浏览器 compositionstart / compositionupdate / compositionend 事件是否能正确触发 SDL_TEXTEDITING / SDL_TEXTINPUT


诊断脚本说明
诊断工具位于 scripts/tools/IMEDiag.lua,在游戏运行时通过 NanoVG 在屏幕左下角渲染半透明日志面板,实时显示所有输入事件(KeyDown/KeyUp/TextInput/TextEditing)。进入 F9 编辑器时自动激活。
IME 功能验证测试脚本位于 scripts/tools/IMETest.lua,使用 UI.TextField 组件提供独立的输入测试环境。


