[LINUX] XIM (X Input Method) Japanese input flow

Created in March 2018

"What about Japanese input in the X Window System?" The author who thought that way examined the flow of Japanese input. This is a sentence about C language and X11 programming. OS used: ubuntu16.04

Programs that do not support Japanese input

First of all, make a program to input characters from the keyboard.

example01.c


#include <stdio.h>
#include <locale.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/keysym.h>

int main(void)
{
    Display* dpy = XOpenDisplay(NULL);

    Window win = XCreateSimpleWindow(dpy, DefaultRootWindow(dpy), 0, 0, 100, 100, 0, 0, 0);
    XMapRaised(dpy, win);
    XSync(dpy, False);

    XSelectInput(dpy, win, KeyPressMask | KeyReleaseMask);

    XEvent ev = {};
    while(1){
        XNextEvent(dpy, &ev);
        switch(ev.type){
            case KeyPress: {
                KeySym keysym = NoSymbol;
                char text[1024] = {};

				XLookupString((XKeyEvent *)&ev, text, sizeof(text) - 1, &keysym, NULL);
                printf("Got chars: (%s)\n", text);
				
                if(keysym == XK_Escape){
                    puts("Exiting because escape was pressed.");
                    return 0;
                }

            }
			break;
        }
    }

	return 0;
}

The compile parameters are as follows. gcc -o example01 example01.c -lX11

When compiled and executed from a terminal emulator (such as a GNOME terminal), the characters entered in the window are simply displayed by printf on the terminal emulator.

Program that supports Japanese input

This program is an improvement of the above program to enable Japanese input.

example02.c


#include <stdio.h>
#include <locale.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/keysym.h>

int main(void){

#if 1
    setlocale(LC_ALL, "");
#endif

    Display* dpy = XOpenDisplay(NULL);

    Window win = XCreateSimpleWindow(dpy, DefaultRootWindow(dpy), 0, 0, 100, 100, 0, 0, 0);
    XMapRaised(dpy, win);
    XSync(dpy, False);

#if 1
    XSetLocaleModifiers("");

    XIM xim = XOpenIM(dpy, 0, 0, 0);
    if(!xim){
        // fallback to internal input method
        XSetLocaleModifiers("@im=none");
        xim = XOpenIM(dpy, 0, 0, 0);
    }

    XIC xic = XCreateIC(xim,
                        XNInputStyle,   XIMPreeditNothing | XIMStatusNothing,
                        XNClientWindow, win,
                        XNFocusWindow,  win,
                        NULL);
    XSetICFocus(xic);
#endif

    XSelectInput(dpy, win, KeyPressMask | KeyReleaseMask);

    XEvent ev = {};
    while(1){
        XNextEvent(dpy, &ev);

#if 1
        if(XFilterEvent(&ev, None) == True) continue;
#endif

        switch(ev.type){
            case KeyPress: {
                KeySym keysym = NoSymbol;
                char text[1024] = {};

#if 1
                Xutf8LookupString(xic, &ev.xkey, text, sizeof(text) - 1, &keysym, NULL);
                printf("Got chars: (%s)\n", text);
#else 
				XLookupString((XKeyEvent *)&ev, text, sizeof(text) - 1, &keysym, NULL);
                printf("Got chars: (%s)\n", text);
#endif
				

                // example of responding to a key
                if(keysym == XK_Escape){
                    puts("Exiting because escape was pressed.");
                    return 0;
                }
            }
            break;
        }
    }

  return 0;

}

The compile parameters are as follows. gcc -o example02 example02.c -lX11

If you can input Japanese in an X11 program (for example, xterm), you can input Japanese in this program as well. (Reference URL for this program https://gist.github.com/baines/5a49f1334281b2685af5dcae81a6fa8a)

I've added a few functions to my program, but there are four functions that are directly related to the IM server:

  XIM xim = XOpenIM()	//Connect to IM server with XIM protocol
  XIC xic = XCreateIC()	//Creating context with IM server
  XSetICFocus()         //Set focus
  XFilterEvent()	//Send event to IM server

Then, the key event is exchanged with the IM server in the following two ways.

  XNextEvent()        //Receive events from the IM server
  XFilterEvent()      //Send an event to the IM server

(IM server refers to fcitx, ibus, etc.)

Rough flow of Japanese input

図.gif

As shown, the X11 program receives the key event with the XNextEvent function and decides whether to send the event to the IM server with the XFilterEvent function. When the XFilterEvent function sends the event to the IM server, it returns a return value of True.

The X11 program also receives key events from the IM server using the XNextEvent function. Even if you pass the event from this IM server to the XFilterEvent function, the return value will be returned as False and will not be sent to the IM server again.

(The interaction between the IM server and IME is not mentioned in this article)

IM server

Let's go a little deeper and find out about IM servers. The IM server is implemented using a function called IMdkit. I found the program code at the URL below, but it is not maintained and I get a compile error. https://www.x.org/releases/unsupported/lib/IMdkit/

Upon further searching, I found that ibus and fcitx use the IMdkit function with their own modifications, and I was able to download the IMdkit source from the URL below. https://github.com/fcitx/fcitx/tree/master/src/frontend/xim/lib

The sample program of IM server was downloaded from the following URL. https://www.x.org/releases/unsupported/lib/IMdkit/Xi18n_sample/

Compiling sampleIM (IM server)

Since the downloaded program code cannot be compiled as it is, I prepared an IMdkit and a sample IM server with some modifications at the following URL. http://ai56go.ivory.ne.jp/sample/sampleIM.zip

Extract this zip file and you will find the build directory. Move to this build directory and type the following command to create the sampleIM program.

chmod 755 build.sh
chmod 755 build2.sh
./build.sh
./build2.sh

(Since the source code of sampleIM.c is old, many warning messages are displayed, but the sampleIM program is created.)

Execution of sampleIM (IM server)

export XMODIFIERS=@im=sampleIM
./sampleIM &
xterm

To start xterm by typing in order. (If you don't have the hint, xterm, use example02 introduced above instead. GNOME terminal etc. can not be used here because it communicates with the IM server by a method other than XIM)

If you type shift + space in xterm and then type a letter from the keyboard, The characters entered in the terminal emulator that started sampleIM are displayed. In the case of an actual IM server, it communicates with the IME at this timing to process Japanese conversion.

If you type ctrl + k`` "This is a definite string from IM." And it will be displayed in xterm. You can enter characters in xterm normally by typing shift + space at the end.

This sampleIM shift + space Input switching ctrl + k confirmed However, since Japanese conversion is not performed, the character string returned by confirmation will always be the same.

To end sampleIM, check the process ID with ps and kill it as shown below.

ps | grep sampleIM
kill (Process ID)

Explanation of sampleIM

This sampleIM has the minimum functions as an IM server. Japanese conversion is an IME job, so I won't cover it in this article.

Looking at the operation of sampleIM from the perspective of the program, First, in line 398 of sampleIM.c     ims = IMOpenIM(dpy, There is, and the IM server is being initialized. Line 401 as an argument to this IMOpenIM function     IMServerName, imname, There is. This imname value is "sampleIM" It must match the value of ʻexport XMODIFIERS = @ im = sampleIM` entered above.

Then on line 425     IMProtocolHandler, MyProtoHandler, And specify the callback function (here, MyProtoHandler function).

Then of 440 lines     XNextEvent(dpy, &event); Receives a key event from an X11 program (such as an xterm) and Line 441 only if the event was from an X11 program     if (XFilterEvent(&event, None) == True)       continue; The XFilterEvent function of is called the MyProtoHandler function, returns True as the return value, and continues ;.

249 lines     MyProtoHandler(ims, call_data) Performs processing as an IM server. If sampleIM is enriched, this MyProtoHandler function will be enriched.

The API of IMdkit is described in the following URL. https://www.x.org/releases/unsupported/lib/IMdkit/doc/API.text

The functions required by IMdkit are listed below and not many.

XIMS IMOpenIM(Display display,...)
char *IMSetIMValues(XIMS ims,...)
char *IMGetIMValues(XIMS ims,...)
void IMCloseIM(XIMS ims)
int IMPreeditStart(XIMS ims, XPointer im_protocol)
int IMPreeditEnd(XIMS ims, XPointer im_protocol)
void IMForwardEvent(XIMS ims, XPointer im_protocol)
void IMCommitString(XIMS ims, XPointer im_protocol)
int IMCallCallback(XIMS ims, XPointer im_protocol)

Finally

This sentence explained the flow of Japanese input using XIM. -Introducing a function (sample program) for inputting Japanese. -Use the XNextEvent function and XFilterEvent function to exchange events. -The IM server uses the IMdkit function. Also, although the original IMdkit sample is in a state where a compile error occurs, I created an IMdkit sample that summarizes so that there is no error.

Recommended Posts

XIM (X Input Method) Japanese input flow
Japanese input with pyautogui
Alpine Linux 3.13 installation, Japanese input