使用X11显示器获取UTF-8输入

8
我一直在尝试阅读互联网上的许多资源,试图找到一种从X显示器获取UTF-8键盘(组合)输入的方法。但我无法使其工作。
我已经尝试了来自link(示例11-4)的示例代码,但没有成功。
我还编写了一个简单的示例(如下),以尝试使其工作。我的简单测试案例是打印一个“é”,这是通过先键入重音符然后键入字母e实现的。
有什么问题吗?
谢谢,
以下是我的示例:
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xresource.h>
#include <X11/Xlocale.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char ** argv)
{
    int screen_num, width, height;
    unsigned long background, border;
    Window win;
    XEvent ev;
    Display *dpy;
    XIM im;
    XIC ic;
    char *failed_arg;
    XIMStyles *styles;
    XIMStyle xim_requested_style;

    /* First connect to the display server, as specified in the DISPLAY 
    environment variable. */
    if (setlocale(LC_ALL, "") == NULL) {
        return 9;
    }

    if (!XSupportsLocale()) {
        return 10;
    }
    if (XSetLocaleModifiers("") == NULL) {
        return 11;
    }

    dpy = XOpenDisplay(NULL);
    if (!dpy) {
        fprintf(stderr, "unable to connect to display");
        return 7;
    }
    /* these are macros that pull useful data out of the display object */
    /* we use these bits of info enough to want them in their own variables */
    screen_num = DefaultScreen(dpy);
    background = BlackPixel(dpy, screen_num);
    border = WhitePixel(dpy, screen_num);

    width = 400; /* start with a small window */
    height = 200;

    win = XCreateSimpleWindow(dpy, DefaultRootWindow(dpy), /* display, parent */
        0,0, /* x, y: the window manager will place the window elsewhere */
        width, height, /* width, height */
        2, border, /* border width & colour, unless you have a window manager */
        background); /* background colour */

    /* tell the display server what kind of events we would like to see */
    XSelectInput(dpy, win, ButtonPressMask|StructureNotifyMask|KeyPressMask|KeyReleaseMask|KeymapStateMask);

    /* okay, put the window on the screen, please */
    XMapWindow(dpy, win);

    im = XOpenIM(dpy, NULL, NULL, NULL);
    if (im == NULL) {
        fputs("Could not open input method\n", stdout);
        return 2;
    }

    failed_arg = XGetIMValues(im, XNQueryInputStyle, &styles, NULL);

    if (failed_arg != NULL) {
      fputs("XIM Can't get styles\n", stdout);
      return 3;
    }

    int i;
    for (i = 0; i < styles->count_styles; i++) {
        printf("style %d\n", styles->supported_styles[i]);
    }
    ic = XCreateIC(im, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, XNClientWindow, win, NULL);
    if (ic == NULL) {
        printf("Could not open IC\n");
        return 4;
    }

    XSetICFocus(ic);

    /* as each event that we asked about occurs, we respond.  In this
     * case we note if the window's shape changed, and exit if a button
     * is pressed inside the window */
    while(1) {
        XNextEvent(dpy, &ev);
        switch(ev.type){
        case KeymapNotify:
            XRefreshKeyboardMapping(&ev.xmapping);
            break;
        case KeyPress:
            {
                int count = 0;
                KeySym keysym = 0;
                char buf[20];
                Status status = 0;
                count = Xutf8LookupString(ic, (XKeyPressedEvent*)&ev, buf, 20, &keysym, &status);

                printf("count: %d\n", count);
                if (status==XBufferOverflow)
                    printf("BufferOverflow\n");

                if (count)
                    printf("buffer: %s\n", buf);

                if (status == XLookupKeySym || status == XLookupBoth) {
                    printf("status: %d\n", status);
                }
                printf("pressed KEY: %d\n", keysym);
            }
            break;
        case KeyRelease:
            {
                int count = 0;
                KeySym keysym = 0;
                char buf[20];
                Status status = 0;
                count = XLookupString((XKeyEvent*)&ev, buf, 20, &keysym, NULL);

                if (count)
                    printf("in release buffer: %s\n", buf);

                printf("released KEY: %d\n", keysym);
            }
            break;
        case ConfigureNotify:
            if (width != ev.xconfigure.width
                    || height != ev.xconfigure.height) {
                width = ev.xconfigure.width;
                height = ev.xconfigure.height;
                printf("Size changed to: %d by %d", width, height);
            }
            break;
        case ButtonPress:
            XCloseDisplay(dpy);
            return 0;
        }
        fflush(stdout);
    }
}
1个回答

13
你必须这样做:
if (XFilterEvent(&ev, win))
    continue;

在你的事件循环中,这将运行输入方法机制,如果没有它,你将得到原始的X事件。例如,当你按下一个带重音符号的键,然后再按一个字母键,如果不调用XFilterEvent,你将像往常一样得到两个KeyPress事件。但是如果你调用了这个函数,你将得到三个事件。有两个原始事件,调用XFilterEvent(&ev, win)会返回True。然后还有一个由输入方法合成的事件,调用XFilterEvent(&ev, win)会返回False。正是这第三个事件包含了带重音符号的字符。
如果你想要同时获取原始事件和输入方法合成的事件,当然可以自己处理原始事件,而不是使用continue。
请注意,为了正确打印buf(或者显式地使用长度),你需要buf[count] = 0;,因为Xutf8LookupString不会在输出中添加空字符。
最后,如评论中所提到的,使用最新版本的X11,您需要指定一个修改XSetLocaleModifiers,例如XSetLocaleModifiers("@im=none"),否则额外的事件将不会被生成。
以下是修正后的代码版本:
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xresource.h>
#include <X11/Xlocale.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
    
int main(int argc, char ** argv)
{
    int screen_num, width, height;
    unsigned long background, border;
    Window win;
    XEvent ev;
    Display *dpy;
    XIM im;
    XIC ic;
    char *failed_arg;
    XIMStyles *styles;
    XIMStyle xim_requested_style;
    
    /* First connect to the display server, as specified in the DISPLAY 
    environment variable. */
    if (setlocale(LC_ALL, "") == NULL) {
        return 9;
    }
    
    if (!XSupportsLocale()) {
        return 10;
    }
    if (XSetLocaleModifiers("@im=none") == NULL) {
        return 11;
    }
    
    dpy = XOpenDisplay(NULL);
    if (!dpy) {
        fprintf(stderr, "unable to connect to display");
        return 7;
    }
    /* these are macros that pull useful data out of the display object */
    /* we use these bits of info enough to want them in their own variables */
    screen_num = DefaultScreen(dpy);
    background = BlackPixel(dpy, screen_num);
    border = WhitePixel(dpy, screen_num);
    
    width = 400; /* start with a small window */
    height = 200;
    
    win = XCreateSimpleWindow(dpy, DefaultRootWindow(dpy), /* display, parent */
        0,0, /* x, y: the window manager will place the window elsewhere */
        width, height, /* width, height */
        2, border, /* border width & colour, unless you have a window manager */
        background); /* background colour */
        
    /* okay, put the window on the screen, please */
    XMapWindow(dpy, win);
    
    im = XOpenIM(dpy, NULL, NULL, NULL);
    if (im == NULL) {
        fputs("Could not open input method\n", stdout);
        return 2;
    }
    
    failed_arg = XGetIMValues(im, XNQueryInputStyle, &styles, NULL);
    
    if (failed_arg != NULL) {
        fputs("XIM Can't get styles\n", stdout);
        return 3;
    }
    
    int i;
    for (i = 0; i < styles->count_styles; i++) {
        printf("style %d\n", (int)styles->supported_styles[i]);
    }
    ic = XCreateIC(im, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, XNClientWindow, win, NULL);
    if (ic == NULL) {
        printf("Could not open IC\n");
        return 4;
    }

    unsigned long mask = 0;                                            
    unsigned long other_masks = ButtonPressMask|StructureNotifyMask|KeyPressMask|KeyReleaseMask;
  
    if (!XGetICValues(ic, XNFilterEvents, &mask, NULL))
    {
        XSelectInput(dpy, win, other_masks | mask);
        printf ("Got IM mask of %lx\n", mask);
    }
    else
    {
        XSelectInput(dpy, win, other_masks);
        fprintf (stderr, "Could not get an IM mask\n");
    }

    XSetICFocus(ic);
    
    /* as each event that we asked about occurs, we respond.  In this
     * case we note if the window's shape changed, and exit if a button
     * is pressed inside the window */
    while(1) {
        XNextEvent(dpy, &ev);
        if (XFilterEvent(&ev, win))
            continue;
        switch(ev.type){
        case MappingNotify:
            XRefreshKeyboardMapping(&ev.xmapping);
            break;
        case KeyPress:
            {
                int count = 0;
                KeySym keysym = 0;
                char buf[20];
                Status status = 0;
                count = Xutf8LookupString(ic, (XKeyPressedEvent*)&ev, buf, 20, &keysym, &status);
    
                printf("count: %d\n", count);
                if (status==XBufferOverflow)
                    printf("BufferOverflow\n");
    
                if (count)
                    printf("buffer: %.*s\n", count, buf);
 
                if (status == XLookupKeySym || status == XLookupBoth) {
                    printf("status: %d\n", status);
                }
                printf("pressed KEY: %d\n", (int)keysym);
            }
            break;
        case KeyRelease:
            {
                int count = 0;
                KeySym keysym = 0;
                char buf[20];
                Status status = 0;
                count = XLookupString((XKeyEvent*)&ev, buf, 20, &keysym, NULL);
    
                if (count)
                    printf("in release buffer: %.*s\n", count, buf);
    
                printf("released KEY: %d\n", (int)keysym);
            }
            break;
        case ConfigureNotify:
            if (width != ev.xconfigure.width
                    || height != ev.xconfigure.height) {
                width = ev.xconfigure.width;
                height = ev.xconfigure.height;
                printf("Size changed to: %d by %d", width, height);
            }
            break;
        case ButtonPress:
            XCloseDisplay(dpy);
            return 0;
        }
        fflush(stdout);
    }
}

1
@exebook 在 XNextEvent 之后应该是正确的位置。输入法可以将任何类型的事件转换为按键。 - n. m.
1
我相信示例代码中还有另一个小错误:case KeymapNotify: 应该改为 case MappingNotify:。如果是这样的话,那么 XSelectInput() 中应该移除 KeymapStateMask - Joseph Quinsey
1
抱歉打扰了,但为什么在按键弹起时使用XLookupString,在按键按下时使用Xutf8LookupString? - Beeeaaar
你不应该将通过XGetICValues(ic, XNFilterEvents, &mask, NULL)获得的掩码作为XSelectInput的参数传递吗? - undefined
@n. m. 可能是一个 AI,请查看 SDL2 的实现。尽管调用了 XGetICValues,但仍然会调用 XFilterEvent。我不明白它是如何工作的。 - undefined
显示剩余8条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接