背景
在ubuntu上编译出的鸿蒙UI框架输入框不能输入中文。分析整个输入流程没有什么问题,发现输入系统应该给UI框架返回一个unicode字符
,但输入法没有返回。UI框架底层使用的glfw框架,但独对glfw框架进行中文输入测试,发现输入法也不能正常返回unicode字符。输入法是否在任何系统都会返回一个unicode字符存疑,查询资料发现在Window系统中有一个V_CHAR消息返回的就是unicode字符。后在mac系统上测试glfw,发现可以正常输入中文,这几乎就证实了输入法输入中文的规则,就是要发送一个unicode字符到UI框架。查看glfw源码,发现在Windows平台,glfw就是针对V_CHAR进行处理;在Mac系统,使用的Application的insertText来处理中文输入;在linux使用的x11窗口系统,需要单独对按键事件进行处理。
问题根因
没有正确设置语言(setlocale),无论是否使用utf8都需要设置此宏
。
在glfw代码中判断未定义X_HAVE_UTF8_STRING宏时进行语言的设置,代码全局搜索发现未定义此宏,认为此代码已执行,最终通过打印宏值,发现此宏已定义,猜测可能是在x11库相关的头文件已经定义好了此宏。
以下三种方式都可以将keycode转换成相关的unicode字符,都需要设置语言
- Xutf8LookupString
- XmbLookupString
- XwcLookupString
以下问题可能导致不能正常输入中文:
- 没有使用setlocate设置语言
- 没有使用XFilterEvent来处理事件。输入法中合成的unicode字符由它来合成
- 没有处理XBufferOverflow状态。如果将buffer状态设置过小会出现溢出问题,在glfw库中将其设置为100,防止溢出。
#if !defined(X_HAVE_UTF8_STRING)
// HACK: If the current locale is C, apply the environment's locale
// This is done because the C locale breaks wide character input
if (strcmp(setlocale(LC_CTYPE, NULL), "C") == 0)
setlocale(LC_CTYPE, "");
#endif
在ubuntu系统上X_HAVE_UTF8_STRING 宏默认为1,导致没有使用setlocale设置语言,后续不能正常输入中文。
../third_party/flutter/glfw/src/x11_init.c:718:9: note: #pragma message: X_HAVE_UTF8_STRING=1
718 | #pragma message(PRINT_MACRO(X_HAVE_UTF8_STRING))
| ^~~~~~~
相关demo
1. x11 demo
在ubuntu16.04上可正常输入中文的x11 demo
编译: gcc -o x112 x112.c -lX11
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <locale.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#define DEFAULT_SIZE 8
int main()
{
Display *dpy;
Window root, win;
XEvent event;
XIM xim;
XIC xic;
if (strcmp(setlocale(LC_CTYPE, NULL), "C") == 0)
setlocale(LC_CTYPE, "");
XSetLocaleModifiers("");
dpy = XOpenDisplay(NULL);
if (!dpy)
{
fprintf(stderr, "Failed to open display.\n");
exit(1);
}
root = DefaultRootWindow(dpy);
win = XCreateSimpleWindow(dpy, root, 0, 0, 400, 300, 0, 0, WhitePixel(dpy, 0));
XMapWindow(dpy, win);
xim = XOpenIM(dpy, NULL, NULL, NULL);
if (!xim)
{
fprintf(stderr, "Failed to open input method.\n");
exit(1);
}
xic = XCreateIC(xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, XNClientWindow, win, NULL);
if (!xic)
{
fprintf(stderr, "Failed to create input context.\n");
exit(1);
}
XSetICFocus(xic);
XSelectInput(dpy, win, ExposureMask | KeyPressMask);
while (1)
{
XNextEvent(dpy, &event);
XFilterEvent(&event, None);
switch (event.type)
{
case KeyPress:
{
Status status;
KeySym keysym;
char *pBuff;
int nSize = DEFAULT_SIZE;
pBuff = (char *)malloc(nSize);
memset(pBuff, 0, nSize);
nSize = Xutf8LookupString(xic, &event.xkey, pBuff, nSize - 1, &keysym, &status);
if (status == XBufferOverflow)
{
printf("LookupString size: %d realloc: %d\n", nSize, nSize + 1);
pBuff = (char *)realloc(pBuff, nSize + 1);
memset(pBuff, 0, nSize + 1);
nSize = Xutf8LookupString(xic, &event.xkey, pBuff, nSize, &keysym, &status);
}
printf("LookupString:: status %d keycode %4u keysym 0x%08lx size %2d string '%s'\n", status, event.xkey.keycode, keysym, nSize, pBuff);
break;
}
}
}
/* 进入事件循环 */
// loopEvent(xic, dpy);
XCloseIM(xim);
XDestroyIC(xic);
XDestroyWindow(dpy, win);
XCloseDisplay(dpy);
return 0;
}
2. glfw demo
#include <GLFW/glfw3.h>
#include <stdlib.h>
#include <stdio.h>
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods)
{
printf("Key: %i, Scancode: %i\n", key, scancode);
printf("glfwGetKeyName: %s\n\n", glfwGetKeyName(key, scancode));
}
void char_callback(GLFWwindow* window, unsigned int codepoing)
{
printf("---> Char: %d\n", codepoing);
}
int main()
{
GLFWwindow* window;
/* Initialize the library */
if (!glfwInit())
return -1;
/* Create a windowed mode window */
window = glfwCreateWindow(640, 480, "Hello World", NULL, NULL);
if (!window)
{
glfwTerminate();
return -1;
}
/* Register a callback */
glfwSetKeyCallback(window, key_callback);
glfwSetCharCallback(window, char_callback);
/* Loop until the user closes the window */
while (!glfwWindowShouldClose(window))
{
/* Poll for and process events */
glfwPollEvents();
}
glfwTerminate();
return 0;
}