Chapter 11. Internationalized Text Input

Converting user keystrokes into text in the encoding of the locale is perhaps the most difficult task in internationalization. This chapter is a continuation of the last, and assumes knowledge of the basics of internationalization covered in that chapter. The first two sections provide an overview of the internationalized text input model used by R5, and are valuable to any programmer writing internationalized applications. The remaining sections describe the new Xlib functions and datatypes for internationalized text input, and are quite detailed. Programmers who will be writing output-only applications or who will be using toolkits or widgets with internationalized text input capabilities built in can skip these sections.

In an internationalized program, you can't assume any particular mapping between keystrokes and input characters. An internationalized program must run in any locale on a single workstation, using a single keyboard. The mapping between keystrokes and Japanese characters is very different (and more complex) than the mapping between keystrokes and Latin characters, for example. When there are more characters in the codeset of a locale than there are keys on a keyboard, some sort of input method is required for mapping between multiple keystrokes and input characters. R5 supports the internationalization ofkeyboard input with the new abstractions X Input Method (XIM) and X Input Context (XIC) and the new functions,XmbLookupString() and XwcLookupString(), which return a string in the encoding of the locale. Because internationalized text input is a complex topic, we begin with a discussion of the important issues of internationalized text input in “Issues of Internationalized Text Input” and an overview of the X input method architecture in “Overview of the X Input Method Architecture” The remaining sections explain the individual topics required in order to implement internationalized text input.

Before beginning with internationalized text input, bear in mind that input methods are a technology that has previously been used only in ad hoc ways for specific languages. Driven by industry demand, it has very quickly advanced from research topic to X Consortium standard, and now must operate correctly in any locale. It is a difficult problem and R5 does not provide a complete solution. One frustration is the ambiguity, in places, of the XIM specification, which defines how an input method interacts with an Xlib application. This book attempts to resolve those ambiguities in reasonable ways, but in practice, much remains “implementation defined,” and internationalized programs may have to be tailored to operate correctly with a few particular target input methods. None of the input methods that are shipped with R5 are part of the core distribution, and none are fully robust or well documented (not in English, at least). The XIM designers envision that their internationalized text input mechanism will be incorporated within toolkits and Xt widgets, and thus will be hidden from most programmers. Until these widgets are available, however, performing truly internationalized text input may be a difficult task.

R5 as shipped from MIT contains two separate implementations of theinput method internationalization facilities. The “Xsi” implementation is the default on all but Sony machines, which use the “Ximp” implementation. Each implementation defines its own protocol for communication between Xlib and input methods (which are implemented as Separate processes). Ximp and Xsi each come with contributed input methods which are not compatible with each other. Steps are now going on within the X Consortium to standardize on one of these implementations, so you should enquire about the status of that effort before putting significant effort into a product using one of these implementations.

Issues of Internationalized Text Input

Think for a moment about how we use a keyboard to enter text into a computer. There are not enough keys on a standard keyboard for all the lowercase and uppercase letters used in English as well as the number and punctuation characters, so we use a shift key to effectively double the number of characters we can enter.

But for many European languages, this technique is not sufficient. The most common accented characters may appear directly on a keyboard (the é, è, and ç in French, for example) but this still leaves a variety of other characters that cannot be entered with any single shifted or unshifted keystroke. French typewriters have a key that will produce an umlaut or a caret, without advancing the carriage, so to produce a û, for example, you would strike the caret key followed by the “u” key. In computer systems, a variety of methods have been developed for entering these accented characters. Often they involve a Compose key (found on many DEC keyboards) or any “dead key” which, does not send a code when struck but places the keyboard into a special compose mode (sometimes indicated by a light on the keyboard) in which one or more of the following keystrokes are combined into a single character. If this sort of input method is implemented in the keyboard hardware or in the operating system software, then it behaves transparently to the programmer who can simply read characters, assured that the user will have some way of entering any text desired.

As with internationalized text output, it is with the Asian ideographic languages that things become complicated. Japanese and Korean both have phonetic alphabets that are small enough to physically map onto a keyboard. It is sometimes adequate to leave text in this phonetic alphabet, but usually the user will want the final text to be in the full ideographic language. Input methods for these languages commonly have the user type the phonetic symbols for a particular word or words and signal somehow when this composition or pre-editing is finished. The input method then looks up that string of phonetic characters in a dictionary and converts it to the equivalent character or characters in the ideographic system. Sometimes there will be more than one character with a given phonetic representation, in which case the user will have to select between them.

These methods are obviously more complex than European compose methods. They are modal, and must display a lot of state information. It is not enough to have a keyboard light that tells users that they are composing an ideographic character; the computer must display the phonetic characters as the user types them, allow the user to edit them, and then when the user is done, compose them into an ideographic character or characters. The conversion from phonetic to ideographic characters requires a large dictionary, and finally, as noted above, the input method may have to display a menu or popup dialog box so the user can choose among ideograms with the same phonetic representation.

Because input methods can be so large and complex, and because they vary so much from locale to locale, it does not make sense to link every application with a generic input method which is somehow localized at application startup. Instead, an input manager is usually run as a separate process that communicates with the X server and with the application. At application startup, the setting of the locale or the “im” locale modifier determines to which input manager the application establishes a connection. R5 provides new routines and datatypes in Xlib which support this sort of internationalized text input. The next section provides an overview of the Xlib architecture for internationalized text input.

Overview of the X Input Method Architecture

The sections below present an overview of the concepts, datatypes, and functions used in R5 to support input methods. An understanding of material presented here will make the implementation details presented in later sections easier to follow.

Input Methods and Input Servers

An internationalized X application gets user text input by communicating with an input method. At application startup, the application is localized by opening the particular input method appropriate for the locale. Often, opening an input method causes Xlib to establish a connection to another process known as the “input manager” or “input server.” The input manager can provide input method service to multiple X clients that use the same locale. Sometimes an input manager will connect to a third process, the translation server, which performs dictionary lookup and translation from pre-edit text (often phonetic) to composed text (often ideographic). The details of input method architecture are of course implementation dependent. Simple input methods, for example, can be implemented directly in Xlib, without need of other processes. The default Xsi implementation shipped with the MIT distribution does just this for European compose methods that do not require any dictionary lookup or graphical feedback. Figure 11-1 diagrams several possible connections between a client and its input method.

Figure 11-1. Possible input method architectures


The XIM architecture was designed to support two models of input method, known as front-end and back-end methods. A front-end input method intercepts events from the X server before they reach the application. A back-end method filters events from the application, before the application has processed them. Because internationalized programs must support either model of input method, the distinction is of little importance to the programmer. It is discussed in the XIM specification, however, and you may run across it in other discussions of input methods.

Recall the distinction between internationalized and multilingual applications. There is nothing to prevent an application from opening multiple input methods for multiple locales, but internationalized applications will generally operate only in a single locale and will therefore only need a single input method.

User Interaction with an Input Method

In order for a complex input method to provide feedback or otherwise interact with the user, it must have regions of the screen that it can draw text or bitmaps into. The X Input Method specification defines three of these areas:

  • The Status area is an output-only window in which the input method can display information about its internal state. It can be thought of as a logical extension of the keyboard mode indicators, such as the Caps Lock indicator. The client generally provides this area to the input method, but the input method is solely responsible for its contents.

  • The Preedit area is the region for the display of the intermediate text typed while composing a character. The client generally provides this area to the input method, which is responsible for its contents.

  • The Auxiliary area is a transient window used for any popup menus or dialog boxes that are needed by the input method. This area is managed entirely by the input method.

The location and use of the Preedit and Status areas depend on the interaction style used between the application and the input method. Four interaction styles are defined by the X Input Method specification.

  • In the root-window pre-editing style, the input method displays data outside of the application in a window that is a child of the root window.

  • In the off-the-spot pre-editing style, the input method displays pre-edit data in a fixed location of the application window, often in a “message line” near the bottom.

  • In the over-the-spot pre-editing style, the input method displays pre-edit data a window of its own which is placed over the current insertion point.

  • In the on-the-spot pre-editing style, the input method directs the application to display the pre-edit data. When using this style, the application can display the pre-edit text in a way that matches the display of the already composed text.

The client must choose an interaction style from a list of styles supported by the input method, and must provide the Preedit and Status areas as required by that style. Additionally, in the case of on-the-spot pre-editing, the client must supply callbacks that the input method can call to control the pre-edit process.

The X Input Method

An application that wishes to use an input method must first call XOpenIM(). This function establishes a connection to the input method appropriate for the current locale, and returns an opaque handle of type XIM. Opening an input method is conceptually similar to opening a display, and the XIM returned is analogous to the Display * returned by XOpenDisplay(). An input method is bound to the particular locale that was in effect when it was created, even if this locale is subsequently changed. XOpenIM() and related functions are documented in “Opening and Closing an Input Method”

The X Input Context

Just as the X server can display multiple windows for a single client, an input method can maintain multiple input contexts for an application. The function XCreateIC() creates a new input context in an input method. The function returns an opaque handle of type XIC. Like the Window or GC types, XIC has a number of attributes which can be set. These attributes control the interaction style for input done under that context, the regions to be used for the Preedit and Status areas, the XFontSet with which the text should be drawn, and so on. XCreateIC() and related routines to set and get the values of input context attributes are documented in “Creating and Destroying Input Contexts”

A text editor that supported multiple editing windows within a single top-level window could choose to create one IC for each editing window, or to share only one IC among all such windows. In the first case, each window would have different Preedit and Status areas, and each could be in a different intermediate state of pre-editing. In the second case, there would be a single Preedit and a single Status area shared by all editing windows, and the application would probably reset the state of the IC each time the input focus moved from one window to another.

Input Context Focus Management

Because there is only one keyboard associated with an X display, X allows only one window to have the input focus at a time. For the same reason, only one input context (per application) can have the focus at a time. The function XSetICFocus() causes key events to be directed to a particular IC. It should be called at least once by every application that uses input contexts. In addition, the application should set the FocusWindow attribute of the IC to the window in which the key events will occur.

If an application has multiple text entry windows using multiple input contexts, that application will have to call XSetICFocus() every time the input focus changes. An application that shares a single IC among multiple text entry windows will have to set the FocusWindow attribute of that IC each time the focus changes. Note that focus changes can be changes of the focus window known to the X server, or they can be application-internal focus changes, controlled by event redirection as is done in Xt and other toolkits.

Preedit and Status Area Geometry Management

Depending on interaction style, an input method may require screen space to display pre-edit and status information. The application is responsible for providing these areas, but except for the on-the-spot interaction style, the input method will handle all output to them. When an input method requires screen space, the application should query its desired size and attempt to honor it. Note however that the input method must make do with whatever area it is given. This geometry management and geometry negotiation is handled through attributes of each input context and with a “geometry callback” function. These are described in Sections 11.7 and 11.8.1.

Preedit and Status Callbacks

When using the on-the-spot interaction style, the IM will request the application to display pre-edit and status information for it. This is more complicated for the application, but because the application has finer control over the positioning of the information, it allows the appearance of a seamless interface with the IM. The IM makes requests of the application through a series of callback functions specified as attributes of the IC. The prototypes and responsibilities of these functions will be described in “Geometry, Preedit, and Status Callbacks”

Getting Composed Input

When the application gets a KeyPress event, it should use that event in a call to XmbLookupString() or XwcLookupString(). These functions are analogs of XLookupString(), but return multi-byte or wide-character strings in the codeset of the locale, where XLookupString() can only return Latin-1 strings. Because it may take multiple keystrokes to enter a single character of text, these functions may return a status code that indicates that no composed input is ready.

Some input methods intercept keyboard events before the application has a chance to see them. If this is the case, they will send a synthetic KeyPress event with a keycode of 0 when there is composed input that should be looked up by the application.

Filtering Events

In order for an input method to perform pre-editing of input, it must have access to all KeyPress events. These events are passed to it through one of the internationalized LookupString functions. All but the most simple input methods, however, need access to other events as well. An IM that displays graphical feedback to the user will have to receive expose events, and an IM that displays a menu of homonyms, for example, will need to receive mouse motion and button events. XFilterEvent() provides the hook that makes this possible. This function must be called from within an application's event loop before each new event is processed. If the IM has registered a (Xlib-internal) filter for that event, XFilterEvent() invokes the filter and the IM has a chance to examine the event. If the IM is interested in the event, XFilterEvent() will return True, and the application should not dispatch the event any further. Notice that an IM can use XFilterEvent() to filter KeyPress events before the application can call one of the LookupString functions, but this is not the primary purpose of the function.

It is not safe to assume that the IM will only need events that the application currently receives, so the IM places an event mask for events in which it is interested in an attribute of each IC. The application is responsible for requesting to receive those events in the window of the IC.

The Big Picture

With the above explanations in mind, we can now consider the saga of a keystroke as it is processed through an internationalized application. Figure 11-2 diagrams the path a character follows between being typed on the keyboard and being displayed on the screen in an internationalized application.

Figure 11-2. How a keystroke becomes a displayed character in an internationalized application


  1. When the user strikes a key on the keyboard, the keyboard sends a hardware-specific keycode to the X server.

  2. The X server sends an event to the client or clients that have expressed interest in keystroke events for the window that had focus when the keystroke occurred.

  3. The keystroke event will be received in the client's event loop by a call to XNextEvent().

  4. The event is immediately passed to XFilterEvent() to give the input method the opportunity to use it. Generally, the input method will not filter a KeyPress event.

  5. Back in the application, if XFilterEvent() returns True, then the application will discard the event and wait for the next one.

  6. Otherwise, the application will go ahead and process the event. For every KeyPress event, the application will call XmbLookupString() or XwcLookupString().

  7. The input method now processes the keystroke: it adds a new character to its pre-edit text and updates the display in the Preedit and Status areas of the application. If the keystroke is a control character such as Delete, the input method may modify the pre-edit text.

  8. If the keystroke indicates that the user is done pre-editing and wishes to compose the pre-edited text, the input method does any necessary translation and the result becomes the return value of Xmb/XwcLookupString(). In most applications, this returned string will be immediately echoed in the window with a call to one of the internationalized text drawing functions. If the keystroke merely adds to the pre-edit text, then the status value returned by Xmb/wcLookupString() indicates that there is no composed text ready.

The above sections have presented an overview of the XIM architecture. The sections below describe how to write programs with input methods and input contexts. They explain how to implement each of the steps in the “big picture” above.

XIM Programming Interface

The input method programming interface departs in some ways from the style established by the rest of Xlib. Functions that set, modify, or query the attributes of an XIM or XIC have a variable-length argument list interface, similar to the interface of the X Toolkit XtVaSetValues function, for example. Attributes are specified by a null-terminated list of name/value pairs. Names are null-terminated character strings (of type char *), and values are of type XPointer, which is a new Xlib generic pointer type, like XtPointer, which replaces the non-standard caddr_t. There are predefined symbols for all of the XIM and XIC attribute names. These are named similarly to X Toolkit resource names: they are prefixed with XN (not XtN) and words in the name are separated by capitalization rather than underscores. They differ from the Xt convention in that the first letter after the XN prefix is capitalized. Example 11-1 shows this naming convention and the varargs interface used in C code. There is only a single defined XIM attribute, which is explained in “Querying Input Method Values” There are a number of XIC attributes, which are explained in “Input Context Attributes”

Example 11-1. The XIM varargs interface and attribute naming conventions

status = XSetICValues(ic, XNFocusWindow, w,
                          XNGeometryCallback, HandleIMGeometry,
                          NULL);

The XNPreeditAttributes and XNStatusAttributes attributes of an input context have a number of sub-attributes. In order to set or query these values, the programmer must specify a nested argument list of type XVaNestedList *. A value of this type is created with a call to the function XVaCreateNestedList(). This function takes a dummy integer argument (as required by ANSI-C) followed by a null-terminated variable length list of name/value pairs. XVaCreateNestedList() can be conveniently called from within an argument list to another function, as is shown in Example 11-2.

Example 11-2. A nested call to XVaCreateNestedList()

XVaNestedList nlist;
ic = XCreateIC(im, XNInputStyle, XIMPreeditPosition | XIMStatusNothing,
                   XNPreeditAttributes, nlist = XCreateVaNestedList(
                           0,  /* dummy argument */
                           XNSpotLocation, cursor_location,
                           XNFontSet, font_set,
                           NULL),
                   XNFocusWindow, focus_window,
                   NULL);
XFree(nlist);


Nested argument lists can also be used to specify top-level attributes. To do this, use the special name XNVaNestedList which will cause the contents of the following nested list to be logically inserted into the argument list at the current position.

Note that XVaCreateNestedList() allocates memory for the list it returns, which must be freed with a call to XFree(). Also note that if any of the values in the list are pointer types, the data pointed to must remain valid for the lifetime of the list.

The designers of the XIM specification chose this varargs-and-named-attributes interface over the more familiar structure-and-flags interface used by XChangeWindowAttributes() and XChangeGC(), for example, because they felt it provided “more flexibility.” The perceived flexibility to the programmer is probably a matter of personal taste, but the varargs interface certainly provides more flexibility for future extensions--new attributes and vendor- or IM-specific attributes can easily be added without destroying binary compatibility.

XIM Functions

An XIM is an opaque structure that serves as a handle to the input method. Because input methods are generally implemented as separate processes, we generally talk about “opening,” not “creating,” an input method. In this respect, an XIM can be thought of as analogous to a Display *. The sections below explain how to open and close a connection to an input method, and how to query the values of input method attributes.

Opening and Closing an Input Method

A connection to an input method is opened with a call to XOpenIM(). This function takes as arguments the Display, an XrmDatabase(), and a resource name and resource class of type char *. The database is used by the input method to look up resources private to it. The resource name and class are used as resource name and class prefixes by the input method when looking up resources for input contexts. In an Xt program, the database created when the display is initialized can be used. In Xlib programs, the programmer will have to explicitly build the database, or simply pass an empty one.

XOpenIM() also uses the current locale and locale modifiers as implicit arguments. The locale determines the default input method that XOpenIM() will connect to, as well as the encoding of the strings which will be returned by Xmb/XwcLookupString(). The locale is bound to an input method when it is open--the locale that was in effect when the input method was opened will be used by all input contexts of that input method regardless of the current locale when they are created.

The locale determines a default input method to be opened by XOpenIM(), but it cannot be assumed that only one input method will be available in each locale. Therefore X defines a locale modifier named “im” which can be used to override the default input method of the locale. The programmer should call XSetLocaleModifiers() to set all X locale modifiers (“im” is currently the only one). The user can specify a desired input method by setting the (UNIX) environment variable XMODIFIERS to a string of the form “@im=input method name.”

When an input method will no longer be used, it may be closed with a call to XCloseIM().

Example 11-3 shows how to establish the locale and open a connection to the input method for that locale.

Example 11-3. Establishing the locale and opening an XIM

#include <stdio.h>

#include <X11/Xlib.h>

/*
 * include <locale.h> or the non-standard X substitutes
 * depending on the X_LOCALE compilation flag
 */
#include <X11/Xlocale.h>

main(argc, argv)
int argc;
char *argv[];
{
    Display *dpy;
    XIM im;
    char *program_name = argv[0];
    /*
     * The error messages in this program are all in English.
     * In a truly internationalized program, they would not
     * be hardcoded; they would be looked up in a database of
     * some sort.
     */
    if (setlocale(LC_ALL, "") == NULL) {
        (void) fprintf(stderr, "%s: cannot set locale.,program_name);
        exit(1);
    }
    if ((dpy = XOpenDisplay(NULL)) == NULL) {
        (void) fprintf(stderr, "%s: cannot open Display., program_name);
        exit(1);
    }
    if (!XSupportsLocale()) {
        (void) fprintf(stderr, "%s: X does not support locale %s.,
                       program_name, setlocale(LC_ALL, NULL));
        exit(1);
    }
    if (XSetLocaleModifiers("") == NULL) {
        (void) fprintf(stderr, "%s: Warning: cannot set locale modifiers.,
                       program_name);
    }
    /*
     * Connect to an input method.
     * In this example, we don't pass a resource database
     */
    if ((im = XOpenIM(dpy, NULL, NULL, NULL)) == NULL) {
        (void)fprintf(stderr, "%s: Couldn't open input method,
                      program_name);
        exit(1);
    }
        .
        .
        .


Querying Input Method Values

The function XGetIMValues() is used to query attributes of the input method. At this point, there is only one defined attribute, named XNQueryInputStyle. This is a read-only attribute that specifies the interaction styles supported by the input method. When an input context is created, one of the interaction styles from this list must be specified. Because the one attribute currently defined for input methods is read-only, there is no XSetIMValues procedure.

To get the list of supported interaction styles, call XGetIMValues() passing the IM, the name XNQueryInputStyle, and the address of a variable of type XIMStyles *. The XIMStyles structure is shown in Example 11-4.

Example 11-4. The XIMStyles structure

typedef unsigned long XIMStyle;
typedef struct {
    unsigned short count_styles;
    XIMStyle *supported_styles;
} XIMStyles;

The call to XGetIMValues() will return a pointer to a XIMStyles structure which contains a list of supported styles and the number of styles in the list. The client is responsible for freeing the XIMStyles structure when done with it.

Each XIMStyle in the list of supported styles is an unsigned long in which various bit flags describing the style are set. The valid flags and their meanings are described below:

XIMPreeditCallbacks 

The client must provide pre-edit callback procedures so that the input method can cooperate with the application to perform on-the-spot pre-editing.

XIMPreeditPosition 

The client must provide the location of the insertion cursor so that the input method can do over-the-spot pre-editing.

XIMPreeditArea 

The client must provide geometry management of an area in which the input method can do off-the-spot pre-editing.

XIMPreeditNothing 

The input method can perform root window pre-editing with no geometry management provided by the client.

XIMPreeditNone 

The input method does not do any pre-editing, or does not display any pre-edit data.

XIMStatusCallbacks 

The client must provide status callback procedures so that the input method can request the application to display status data when needed.

XIMStatusArea 

The client must provide geometry management of an area in which the input method can display status values.

XIMStatusNothing 

The input method can display status information in the root window with no geometry management provided by the client.

XIMStatusNone 

The input method does not display any status information.

When examining the supported_styles list, you may assume that each XIMStyle will have only one XIMPreedit flag and one XIMStatus flag set. [37] Example 11-5 in “Creating and Destroying Input Contexts” shows how to query the supported styles of an input method.

XIC Functions

An input context is to an input method almost as a Window is to a Display. Each independent internationalized text input stream requires an IC, and the attributes of an IC define the behavior and appearance of the IM for that input stream. The sections below describe how to choose an input style for an IC, how to create and destroy an IC, how to set and get the attribute values of an IC, how to reset an IC, and how to set focus to an IC. The attributes of an IC are documented in “Input Context Attributes”

Choosing an Interaction Style

The input or interaction style to be used by an input context must be specified when the input context is created. The style chosen must be one of those supported by the input method, and must also be supported by the client. The simplest of applications may choose to provide only minimal interaction with the input method, and may support only the XIMPreeditNothing and XIMStatusNothing interaction styles, forcing the input method to display its information in the root window. More complicated applications will probably support at least XIMPreeditArea and XIMStatusArea styles, as well as the “do nothing” styles. Generally, the right choice of interaction style is the most complicated (and therefore most user-friendly) style supported both by the application and the input method. An application may also choose to provide a resource so that the user can specify a desired style. Note that the choice of Preedit interaction style must be made independently of the Status style.

“Querying Input Method Values” lists the possible interaction styles, and explains how to query an input method for supported styles. Example 11-5 shows how to select Preedit and Status interactions styles and create an IC to use those styles.

Creating and Destroying Input Contexts

An XIC is created with a call to XCreateIC() and destroyed with a call to XDestroyIC(). XCreateIC() takes an XIM as its first argument followed by a NULL-terminated variable-length argument list of attribute name/attribute value pairs. The IC is created in the locale of the IM, regardless of the current locale. The names of the IC attributes and their meanings are described in “Input Context Attributes” Note that the XNInputStyle and XNFontSet attributes must be specified when an input context is created, and depending on the input style, XNSpotLocation and all of the callback attributes may also have to be specified at creation time. The XNClientWindow attribute need not be specified when the IC is created, but must be specified before any input is done with the IC. Example 11-5 shows how to choose an interaction style and create an IC.

Example 11-5. Choosing an interaction style and creating an IC

#include <stdio.h>

#include <X11/Xlib.h>

#include <X11/Xlocale.h>

main(argc, argv)
int argc;
char *argv[];
{
    Display *dpy;
    Window win;
    XFontSet fontset;
    XIM im;
    XIC ic;
    XIMStyles *im_supported_styles;
    XIMStyle app_supported_styles;
    XIMStyle style;
    XIMStyle best_style;
    XVaNestedList list;
    char *program_name = argv[0];
    int i;
        .
        .
        .
    /* figure out which styles the IM can support */
    XGetIMValues(im, XNQueryInputStyle, &im_supported_styles, NULL);
    /* set flags for the styles our application can support */
    app_supported_styles = XIMPreeditNone | XIMPreeditNothing | XIMPreeditArea;
    app_supported_styles |= XIMStatusNone | XIMStatusNothing | XIMStatusArea;
    /*
     * now look at each of the IM supported styles, and
     * chose the "best" one that we can support.
     */
    best_style = 0;
    for(i=0; i < im_supported_styles->count_styles; i++) {
        style = im_supported_styles->supported_styles[i];
        if ((style & app_supported_styles) == style) /* if we can handle it */
            best_style = ChooseBetterStyle(style, best_style);
    }
    /* if we couldn't support any of them, print an error and exit */
    if (best_style == 0) {
        (void)fprintf(stderr, "%s: application and program do not share a,
                      program_name);
        (void)fprintf(stderr, "%s: commonly supported interaction style.,
                      program_name);
        exit(1);
    }
    XFree(im_supported_styles);
    /*
     * Now go create an IC using the style we chose.
     * Also set the window and fontset attributes now.
     */
    list = XVaCreateNestedList(0, XNFontSet, fontset, NULL);
    ic = XCreateIC(im, XNInputStyle, best_style,
                       XNClientWindow, win,
                       XNPreeditAttributes, list,
                       XNStatusAttributes, list,
                       NULL);
    XFree(list);
    if (ic == NULL) {
        (void) fprintf(stderr, "Couldn't create input context);
        exit(1);
    }
        .
        .
        .
}
/*
 * This function chooses the "more desirable" of two input styles.  The
 * style with the more complicated Preedit style is returned, and if the
 * styles have the same Preedit styles, then the style with the more
 * complicated Status style is returned.  There is no "official" way to
 * order interaction styles; this one seems reasonable, though.
 * This is a long procedure for a simple heuristic.
 */
XIMStyle ChooseBetterStyle(style1,style2)
XIMStyle style1, style2;
{
    XIMStyle s,t;
    XIMStyle preedit = XIMPreeditArea | XIMPreeditCallbacks |
        XIMPreeditPosition | XIMPreeditNothing | XIMPreeditNone;
    XIMStyle status = XIMStatusArea | XIMStatusCallbacks |
        XIMStatusNothing | XIMStatusNone;
    if (style1 == 0) return style2;
    if (style2 == 0) return style1;
    if ((style1 & (preedit | status)) == (style2 & (preedit | status)))
        return style1;
    s = style1 & preedit;
    t = style2 & preedit;
    if (s != t) {
        if (s | t | XIMPreeditCallbacks)
            return (s == XIMPreeditCallbacks)?style1:style2;
        else if (s | t | XIMPreeditPosition)
            return (s == XIMPreeditPosition)?style1:style2;
        else if (s | t | XIMPreeditArea)
            return (s == XIMPreeditArea)?style1:style2;
        else if (s | t | XIMPreeditNothing)
            return (s == XIMPreeditNothing)?style1:style2;
    }
    else { /* if preedit flags are the same, compare status flags */
        s = style1 & status;
        t = style2 & status;
        if (s | t | XIMStatusCallbacks)
            return (s == XIMStatusCallbacks)?style1:style2;
        else if (s | t | XIMStatusArea)
            return (s == XIMStatusArea)?style1:style2;
        else if (s | t | XIMStatusNothing)
            return (s == XIMStatusNothing)?style1:style2;
    }
}


Querying and Modifying an XIC

Attributes of an XIC can be set with a call to XSetICValues() and can be queried with a call to XGetICValues(). Both functions take an XIC as their first argument, followed by a NULL-terminated variable-length argument list of attribute name/attribute value pairs. The names, types, and usage of the attributes are explained in “Input Context Attributes” Note that some of the attributes are read-only, some must be specified when the IC is created, and others must be specified once and may not be changed once specified.[38]

The value arguments passed to XGetICValues() must be valid pointers to locations in which to store the requested attribute values. XGetICValues() will allocate memory for the storage of some of these attributes, and this memory must be freed by the client with a call to XFree().[39]

To query the values of Preedit and Status sub-attributes, create a nested list of name/value pairs, where the values are pointers to storage and pass this nested list as the value of the XNPreeditAttributes or XNStatusAttributes attributes. You cannot query the value of all sub-attributes by passing a XVaNestedList * as the value of XNPreeditAttributes or XNStatusAttributes--XGetICValues() does not build and return a nested list of sub-attributes

Both XSetICValues() and XGetICValues() return a char * which is NULL if no errors occurred, or points to the name of the first attribute that could not be set or queried.

Resetting an Input Context

If text input is interrupted while pre-editing is in progress, the input context may be left in a non-initial internal state. To reset the state of an XIC, call XmbResetIC() or XwcResetIC. Both reset the IC to its initial state and discard any pending input. Both functions may return the current pre-edit string, but it is implementation dependent how and whether they do this. The only difference between these functions is in the type of string they return. The returned string, if any, should be freed by the client with XFree().

Setting Input Context Focus

When the focus window of an input context receives the application input focus, the application should call XSetICFocus() on that IC. Use XUnsetICFocus() when the focus window of an IC loses focus, or simply call XSetICFocus() on the IC of the new focus window. This will allow the input method to perform internal housekeeping and display special graphics (such as a highlighted border) in the Pre-Edit and Status windows of the IC that has the focus.

If you are using a single IC to handle input across several windows, and the input focus shifts from one of these windows to another, then the IC's XNFocusWindow attribute should be changed, you needn't call XSetICFocus(). Depending upon your user interface, you may also want to reset the IC when focus changes like this.

Input Context Utility Functions

The following utility functions are sometimes useful when using input methods and input contexts:

XIMOfIC() 

Returns the IM associated with a given IC.

XDisplayOfIM() 

Returns the Display associated with a given IM.

XLocaleOfIM() 

Returns the locale associated with a given IM. The returned string is owned by Xlib and should not be freed by the client. It will be freed by Xlib when the IM is closed.[40]

Input Context Attributes

The behavior of an input method for a particular stream of input is controlled by the attributes of the input context of that stream. There is an attribute, for example, that specifies the interaction style (which must be one of the styles supported by the IM), there are attributes that specify the pre-edit callbacks to be called by the input method when over-the-spot interaction is being used, and there are attributes that specify the foreground and background pixels and colormap for the IM to use when drawing in its Preedit area.

Some attributes are used for communication in the other direction. One is used by the input method to tell the client which types of X events it requires, and another is used by the input method to request a new size for its Preedit and Status areas. Most attributes may be freely modified, but note that some must be set when the IC is created, others must be set exactly once, and others still are read-only and must never be set.

The attributes are listed below. Most attributes provide default values, but recall that some must be specified, either when the IC is created or at some later time before it is used.

XNInputStyle

The XNInputStyle attribute specifies the interaction style to be used by the input method for this input context. It is of type XIMStyle. It must be one of the supported styles queried from the input method with XGetIMValues(). This attribute must be specified when the IC is created. It may be queried but not changed.

XNClientWindow

The XNClientWindow attribute specifies the window in which the input method will display its Preedit and Status areas. It is of type Window. All geometry values for those areas are specified relative to this window. This attribute must be specified (with XCreateIC() or XSetICValues()) before any input is done, and once set may not be changed.

XNFocusWindow

If a single IC is used to handle multiple input streams within a single client window (as in a multi-buffer text editor that displays several paned editing windows and provides pre-editing in a message line at the bottom of the client window), the XNFocusWindow attribute (of type Window) is used to specify which sub-window currently has the focus. The input method may select events on this window, send synthetic events to it, set or change properties on it, or grab the keyboard within it. If not specified, this attribute will default to the value of XNClientWindow. If this attribute is specified, it should generally be a child of the client window. The value of XNFocusWindow may be changed freely.

XNResourceName and XNResourceClass

The XNResourceName and XNResourceClass attributes are null-terminated strings which completely specify the resource name and class used to obtain resources for the client window. If the input method allows per-IC customization using X resources, those resources will be looked up using the name and class hierarchies specified by XNResourceName and XNResourceClass. If your application is named “iedit” with class name “Iedit,” and the client window is a widget named “itext” of class “IText” and it is within a top-level manager widget named “main” of class “Form,” then the XNResourceName attribute should be set to “iedit.main.itext,” and the XNResourceClass attribute should be set to “IEdit.Form.IText.” If these attributes are not set, the input method will not be able to look up resource values for the IC in its resource database. Both attributes may be set at any time, but because resource lookup is generally done only when an IC is created, they will only be useful if specified to XCreateIC(). The specification does not say whether or not the values of these strings are copied. To be safe, the strings passed as values of XNResourceName and XNResourceClass should not be freed or modified until the IC is destroyed or new values are provided for those attributes.

XNGeometryCallback

The XNGeometryCallback attribute, of type XIMCallback *, specifies a procedure which an input method may call to request a different size for it's Preedit or Status areas. Because the client is never obliged to meet IM geometry requests, specifying this attribute is optional.

XNFilterEvents

The XNFilterEvents attribute is used by the input method to notify the client of the X events it needs to receive. It is an event mask, a long integer of the format passed to XSelectInput(). The client must query this resource before any input is done and augment the event mask for the XNClientWindow with it. This attribute is read-only and should never be set.

XNPreeditAttributes and XNStatusAttributes

Each of these attributes specifies a list of sub-attributes that control the position, behavior, and appearance of the Preedit and Status areas of the IC. They have type XVaNestedList and should be created with a call to XVaCreateNestedList(). Most of these attributes are used by the input method for both the Preedit and the Status areas. They are ignored, of course, for XIMPreeditNone and XIMStatusNone interaction styles. All of these attributes except the callbacks are ignored for interaction styles XIMPreeditCallbacks and XIMStatusCallbacks. They are described individually below.

XNArea

The XNArea attribute is a pointer to an XRectangle. If the interaction style is XIMPreeditArea or XIMStatusArea, then the rectangle defines the region of the client window in which pre-editing and status display is to take place. An input method may create sub-windows of the client window that conform to this geometry. If the pre-edit interaction style is XIMPreeditPosition instead of XIMPreeditArea, then this attribute specifies a clipping region in the focus window of the IC to be used in conjunction with the XNSpotLocation attribute to implement over-the-spot pre-editing. This attribute must be specified if any of the above interaction styles are in use. For all other pre-edit and status interaction styles, this attribute is ignored.

XNAreaNeeded

The XNAreaNeeded attribute is also a pointer to an XRectangle. It is used for geometry negotiation between client and input method for the XIMPreeditArea and XIMStatusArea interaction styles, and is ignored for all other styles. The client may provide a hint to the input method about the area it is likely to get by setting a non-zero width or height in this attribute (the x and y values are ignored). The client may query the input method's preferred size for those areas by reading the value of this attribute. A well-behaved input method will not request a size larger than any hints it has received. Note that neither step is required--the client can always set any size it desires with the XNArea attribute. See “Negotiating Preedit and Status Area Geometries” for more details on geometry negotiation.

XNSpotLocation

The XNSpotLocation attribute is a pointer to XPoint. It is used when the Preedit interaction style is XIMPreeditPosition and is ignored for all Status interaction styles and all other Preedit interaction styles. The value of this attribute should be set to the position (in the focus window) at which the next character would be drawn. The input method will use this point and the clipping region specified in XNAreaNeeded to implement over-the-spot pre-editing in a sub-window of the focus window. Each time a newly-composed character is drawn or the text modified in any way, the value of this attribute should be changed to reflect the new value of the “spot.” When the interaction style is XIMPreeditPosition, this attribute must be specified when the IC is created.

XNColormap

The XNColormap attribute specifies the colormap which the input method should use for any windows it creates itself. It is of type Colormap. If the colormap is unspecified, the input method will provide a default.

XNStdColormap

The XNStdColormap attribute provides an alternate method of specifying the colormap to be used by the input method. It is of type Atom, and should be set to a value appropriate for a call to XGetStandardColormap(). If both this attribute and the XNColormap attribute are passed in a call to XSetICValues(), it is implementation-dependent which will take precedence.

XNForeground

The XNForeground attribute specifies the foreground pixel value to be used by the input method. It is of type unsigned long.

XNBackground

The XNBackground attribute specifies the background pixel value to be used by the input method. It is of type unsigned long.

XNBackgroundPixmap

The XNBackgroundPixmap attribute specifies a pixmap to be used as the background of the Preedit or Status window created by the input method. It is of type Pixmap.

XNFontSet

The XNFontSet attribute specifies a fontset to be used by the input method for text drawing in the Preedit or Status window. It is of type XFontSet. The locale of the specified fontset must match the locale of the input method. This attribute must be specified when the IC is created.

XNLineSpacing

The XNLineSpacing attribute specifies the line spacing[41] to be used by the input method when displaying multi-line text. It is of type int.

XNCursor

The XNCursor attribute specifies the mouse cursor to be used in the Preedit or Status windows. It is of type Cursor.

Preedit and Status Callbacks

There are seven callback attributes, four of which must be specified for XIMPreeditCallbacks interaction style, and three of which must be specified for XIMStatusCallbacks interaction style. Each callback attribute is of type XIMCallback *. This type, the callback prototypes, and requirements will be explained in “Geometry, Preedit, and Status Callbacks” If the XIMPreeditCallbacks or XIMStatusCallbacks interaction styles are in use, the appropriate callbacks must be specified when the IC is created. The callback attributes are the following:

  • XNPreeditStartCallback, called when pre-editing starts. It gives the client the opportunity to provide feedback to the user, to rearrange characters in the window to make room for pre-editing, etc.

  • XNPreeditDoneCallback, called when a character is composed and pre-editing stops. It gives the client the opportunity to provide feedback to the user, close up any space opened for pre-editing, etc.

  • XNPreeditDrawCallback, called when the input method wants the client to draw characters in the window.

  • XNPreeditCaretCallback, called when the input method wants the client to move the text-insertion cursor (which for some applications may have the shape of a caret).

  • XNStatusStartCallback, called when the input context gets the focus. It gives the client the chance to provide user feedback.

  • XNStatusDoneCallback, called when the input context loses focus (or is destroyed). It gives the client the chance to provide user feedback.

  • XNStatusDrawCallback, called when the input method wants the client to draw text or a bitmap into the status area.

Negotiating Preedit and Status Area Geometries

For the XIMPreeditArea and XIMStatusArea interaction styles, the input method needs an area of the application window in which it can create a sub-window and perform its necessary pre-editing and display status information. The application is responsible for providing these areas to the input method (with the XNArea sub-attribute) and the input method must accept whatever area it is given.

The simplest applications may simply force the input method to use some pre-defined area, but slightly more flexible applications will want to query the input method for its desired size. To allow this, a protocol for geometry negotiation between application and input method has been defined. The protocol uses the XNAreaNeeded sub-attribute of an input context in two distinct ways: when the application sets this attribute with a non-zero width and/or height, the input method interprets these as hints about the size that will eventually be assigned to it by the client. When the application queries the value of the XNAreaNeeded attribute, it is returned the input method's preferred size which it may choose to honor when setting the size in the XNArea attribute.

An example best demonstrates the use of this protocol: Suppose an internationalized client wants to place the pre-edit area across the bottom of its application window. This means that the width of the area is constrained to be the width of the window, but the height of the area is not constrained. So the application specifies the width of the XNAreaNeeded attribute to be the width of the window and leaves the height of the attribute set to 0. Now the input method may use this information to re-compute its desired size. If it would have liked a one line pre-edit area 500 pixels wide, for example, and has just received a hint that it will not get an area wider than 350 pixels, it might choose to request a pre-edit area that is two lines high. Now when the application queries the XNAreaNeeded attribute it will get the input method's new desired size. If an application has no constraints for the input method, it can omit the first step and simply read from XNAreaNeeded.

This negotiation protocol is not reserved for application startup; it may take place at any time. Note that if the application changes the XNFocusWindow attribute of an IC or the XNFontSet or XNLineSpacing sub-attributes of the pre-edit or status areas, the input method will probably have a new desired size for those areas, and the application should redo the geometry negotiation process. When the application's window is resized, the application will probably want to place the pre-edit and status areas at a new location, and may also have new constraints on their size. The application should set its size constraints in XNAreaNeeded even if those constraints have not changed since the last time geometry was negotiated. [42] Example 11-6 shows a procedure that handles the geometry negotiation process. It was designed to be called from an application's event loop when the main window is resized.

Example 11-6. Negotiating Preedit and Status area geometries

#include <X11/Xlib.h>

/*
 * This procedure sets the application's size constraints and returns
 * the IM's preferred size for either the Preedit or Status areas,
 * depending on the value of the name argument.  The area argument is
 * used to pass the constraints and to return the preferred size.
 */
void GetPreferredGeometry(ic, name, area)
XIC ic;
char *name;           /* XNPreeditAttributes or XNStatusAttributes */
XRectangle *area;     /* in: constraints;  out: IM preferred size */
{
    XVaNestedList list;
    list = XVaCreateNestedList(0, XNAreaNeeded, area, NULL);
    /* set the constraints */
    XSetICValues(ic, name, list, NULL);
    /* query the preferred size */
    XGetICValues(ic, name, list, NULL);
    XFree(list);
}
/*
 * This procedure sets the geometry of either the Preedit or Status
 * Areas, depending on the value of the name argument.
 */
void SetGeometry(ic, name, area)
XIC ic;
char *name;           /* XNPreeditAttributes or XNStatusAttributes */
XRectangle *area;     /* the actual area to set */
{
    XVaNestedList list;
    list = XVaCreateNestedList(0, XNArea, area, NULL);
    XSetICValues(ic, name, list, NULL);
    XFree(list);
}
/*
 * Called when the window is resized.  If the interaction style
 * uses the Preedit or Status areas, then their size needs to
 * be re-negotiated.  This procedure places both the Preedit and
 * Status areas at the bottom of the window, and constrains the
 * Preedit area to occupy no more than 4/5ths of the window width
 * on the right hand side of the window, and constrains the Status
 * area to occupy no more than 1/5th of the window on the left.
 * It does not constrain the height of these areas at all.
 */
void NegotiateICGeometry(ic, event, style, preedit_area, status_area)
XIC ic;
XEvent *event;
XIMStyle style;
XRectangle *preedit_area, *status_area;
{
    if ((preedit_area != NULL) && (style & XIMPreeditArea)) {
        preedit_area->width = event->xconfigure.width*4/5;
        preedit_area->height = 0;
        GetPreferredGeometry(ic, XNPreeditAttributes, preedit_area);
        preedit_area->x = event->xconfigure.width - preedit_area->width;
        preedit_area->y = event->xconfigure.height - preedit_area->height;
        SetGeometry(ic, XNPreeditAttributes, preedit_area);
    }
    if ((status_area != NULL) && (style & XIMStatusArea)) {
        status_area->width = event->xconfigure.width/5;
        status_area->height = 0;
        GetPreferredGeometry(ic, XNStatusAttributes, status_area);
        status_area->x = 0;
        status_area->y = event->xconfigure.height - status_area->height;
        SetGeometry(ic, XNStatusAttributes, status_area);
    }
}

Finally, an application may choose to provide a callback procedure that will be called by the input method to request a new size for its pre-edit or status areas. This callback may be triggered by changes to attributes such as XNFontSet as described above, or may be triggered directly by the user's interactions with the input method (an input method could provide “resize handles” on its pre-edit area, for example). If an application provides a geometry callback, it should attempt to honor any resize requests made by the input method. (An input method might choose whether or not to display “resize handles” on its pre-edit area depending on the presence or absence of such a callback.) The prototype geometry callback is described in “The Geometry Callback”

Geometry, Preedit, and Status Callbacks

An application interacting with an input method using the XIMPreeditArea and/or XIMStatusArea styles may optionally provide a callback to be called when the input method would like to renegotiate the size of its pre-edit or status areas. An application using the XIMPreeditCallbacks style must provide a suite of pre-edit callback routines that allow the input method and application to cooperate and provide pre-editing that appears to be an integral part of the application itself. Similarly, an application using the XIMStatusCallbacks must provide a suite of callbacks for the display of status information.

Each callback attribute is of type XIMCallback, which is shown in Example 11-7.

Example 11-7. The XIMCallback structure

typedef void (*XIMProc)();
typedef struct {
    XPointer client_data;
    XIMProc callback;
} XIMCallback;


If you have used X Toolkit callbacks, you will be familiar with the use of the client_data field. This is untyped data registered with the callback and passed to the callback every time it is invoked. When a single callback procedure is registered on several different callback attributes, the client_data can serve in a switch statment to determine how the callback should behave. It is also often used to pass data to the callback (such as a window ID or a widget pointer), which the callback would otherwise not have access to. The type of client_data is XPointer, which is a new Xlib generic pointer type, like XtPointer.

Most of the callback procedures have the prototype shown in Example 11-8.

Example 11-8. A prototype XIM callback procedure

void CallbackPrototype(ic, client_data, call_data)
    XIC ic;
    XPointer client_data;
    XPointer call_data;

The XIC passed to the callback procedure will be the input context that caused the callback to be invoked. The client_data argument will be the untyped data registered with the callback as described above. It is up to the callback to know the actual type of this data and cast it as appropriate before use. The call_data argument is data passed by the input method to the callback; it is the data required by the callback to perform whatever action the input method needs done. Each callback passes a different type in this argument. Note that the Xlib header files do not actually define CallbackPrototype, only the type XIMProc shown in the previous example. Since the definition of the XIMProc type does not have a prototype, callback procedures may be written with any desired types for client_data and call_data.

The Geometry Callback

The geometry callback (XNGeometryCallback) is triggered when the input method would like to renegotiate the geometry of its pre-edit or status areas. It is not passed any data in its call_data argument. Note that this callback does not indicate whether the input method wants renegotiation of the pre-edit area or the status area or both. If the application and the input method are interacting through both XIMPreeditArea and XIMStatusArea styles, then the application should renegotiate the geometry of both areas. The geometry negotiation process is described in “Negotiating Preedit and Status Area Geometries”

The PreeditStartCallback and the PreeditEndCallback

The XNPreeditStartCallback and XNPreeditEndCallback are called when the input method begins and ends pre-editing. They give the application the opportunity to do any necessary internal setup or cleanup and provide graphical feedback to the user that the application is entering or leaving pre-edit mode. Both callbacks are passed NULL as their call_data values. XNPreeditStartCallback will not be called twice for the same IC without an intervening call to XNPreeditEndCallback.

The XNPreeditStartCallback has one additional requirement. It must return an int (and therefore does not satisfy the general callback prototype given above) to the input method which indicates the maximum number of bytes the application is able to handle in the pre-edit string. If this callback returns a positive value, the input method should not expect the application to be able to successfully display pre-edit strings any longer than that value. If the callback returns the value -1, it indicates that the application can handle pre-edit strings of any length.

The PreeditDrawCallback

This callback is called when the input method wants the application to insert, delete, or replace text in the pre-edit string. It is also used by the input method to request that some characters or substrings be highlighted (to indicate a selected region of the pre-edit string, for example). The callback is expected to display the pre-edit text to the user and will have to maintain an internal pre-edit string. The pre-edit text will likely appear within the running text of the application, but cursor and character positions referred to in this callback are all relative to the beginning of the pre-edit string. The XNPreeditDrawCallback is passed call_data of type XIMPreeditDrawCallbackStruct, which is shown in Example 11-9.

Example 11-9. The XIMPreeditDrawCallbackStruct

typedef unsigned long XIMFeedback;
#define XIMReverse     1L
#define XIMUnderline   (1L<<1)
#define XIMHighlight   (1L<<2)
#define XIMPrimary     (lL<<3)
#define XIMSecondary   (1L<<4)
#define XIMTertiary    (1L<<5)
typedef struct _XIMText {
    unsigned short length;
    XIMFeedback *feedback;
    Bool encoding_is_wchar;
    union {
        char * multi_byte;
        wchar_t * wide_char;
    } string;
} XIMText;
typedef struct _XIMPreeditDrawCallbackStruct {
    int caret;
    int chg_first;
    int chg_length;
    XIMText text;
} XIMPreeditDrawCallbackStruct ;

The XNPreeditDrawCallback must do the following:

  • If chg_length is positive, then the application must delete the characters in the pre-edit string between chg_first and chg_first + chg_length-1 inclusive. [43] Note that manipulations of the pre-edit string are always done on the basis of character positions, so it will generally be most useful to store the pre-edit string in wide-character format.

  • If the text field is non-NULL, and text.string is non-NULL, the application must insert that string at the position specified by chg_first. A position of 0 indicates that the string should be inserted before the first character of the pre-edit string, a position of 1 indicates that the string should be inserted before the second character of the pre-edit string, and so on. If text.encoding_is_wchar is TRUE then the string to be inserted is the wide-character string text.string.wide_char which is text.length characters long. If FALSE, then the string to be inserted is the multi-byte string text.string.multi_byte, which is also text.length characters (not bytes) long. Since there is no way to request that the IM use either wide-character or multi-byte strings, your application will have to be prepared to handle either case. When passed a multi-byte string, it will probably be easiest to convert it to a wide-character string and operate on it in that representation.

  • If there is a string to be inserted, and text.feedback is not NULL then text.feedback is an array of XIMFeedback with text.length elements. Each character of the string to be inserted must be drawn with the “feedback style” indicated by the corresponding element of the text.feedback array. If the array element is 0 then no special highlighting of the character needs to be done. Otherwise the character must be highlighted in one of the following ways:

    • XIMReverse means the character should be drawn with foreground and background colors reversed.

    • XIMUnderline means that a line should be drawn along the character's baseline.

    • XIMHighlight means that the character should be drawn highlighted in some style other than the styles used for XIMReverse and XIMUnderline.[44]

    • XIMPrimary means that the character should be drawn in some application defined highlighting style which is not the same as the style used for XIMSecondary.

    • XIMSecondary means that the character should be drawn in some application defined highlighting style which is not the same as the style used for XIMPrimary.

    • XIMTertiary means that the character should be drawn in some application defined highlighting style.

  • If text.feedback is not NULL, but text.string is NULL, then no string needs to be inserted, but the characters between chg_first and chg_first + text.length-1 inclusive should be redrawn with the highlight style indicated by text.feedback.

  • After any insertions and deletions have been performed, the text insertion cursor (called the “caret” in the XIM spec) should be moved to the position specified in the caret field. If the position is 0, the cursor should be positioned so that new text will be inserted before the first character of the pre-edit string. If it is 1, the cursor should be positioned so that new text will be inserted before the second character of the pre-edit string, and so on.

The PreeditCaretCallback

This callback is called by the input method when it wants the application to move the current position of the text insertion cursor or to change the way the cursor is displayed. It is called with call_data of type XIMPreeditCaretCallbackStruct which is shown in Example 11-10.

Example 11-10. The XIMPreeditCaretCallbackStruct

typedef enum {
    XIMForwardChar, XIMBackwardChar,
    XIMForwardWord, XIMBackwardWord,
    XIMCaretUp, XIMCaretDown,
    XIMNextLine, XIMPreviousLine,
    XIMLineStart, XIMLineEnd,
    XIMAbsolutePosition,
    XIMDontChange,
} XIMCaretDirection;
typedef enum {
    XIMIsInvisible,
    XIMIsPrimary,
    XIMIsSecondary,
} XIMCaretStyle;
typedef struct _XIMPreeditCaretCallbackStruct {
    int position;
    XIMCaretDirection direction;
    XIMCaretStyle style;
} XIMPreeditCaretCallbackStruct;

The XNPreeditCaretCallback is required to move the cursor as specified in the direction field, display it in the style specified in the style field, and return the new character position of the cursor by setting the value of the position field. The position field must be set by the callback because in some cases the input method will not be able to compute it itself. This is the case when the cursor is moved down a line, for example--the new character position of the cursor will depend on the number of characters in each line, which is a figure known to the application but not to the input method. Note that to correctly implement this callback, the application will have to remember the position of the insertion cursor at all times, and this position will have to be updated by both the XNPreeditDrawCallback and the XNPreeditCaretCallback.

The possible values of the direction field and their meanings are listed below. Note that in no case should the insertion cursor be moved to a position before the beginning or after the end of the pre-edit string.[45]

  • XIMForwardChar means move the cursor forward one character.

  • XIMBackwardChar means move the cursor backwards one character.

  • XIMForwardWord means move the cursor forward one word. It is up to the application to decide what constitutes a “word” in a pre-edit string. In many locales, a word will be delimited by characters for which isspace returns True.

  • XIMBackwardWord means move the cursor backwards one word.

  • XIMCaretUp means move the cursor up one line, keeping its position in the line constant if possible.

  • XIMCaretDown means move the cursor down one line, keeping its position in the line constant if possible.

  • XIMPreviousLine means move the cursor to the beginning of the previous line of pre-edit text.

  • XIMNextLine means move the cursor to the beginning of the next line of pre-edit text.

  • XIMLineStart means move the cursor to the beginning of the line it is currently on.

  • XIMLineEnd means move the cursor to the end of the line it is currently on.

  • XIMAbsolutePosition means move the cursor to the absolute character position specified in the position field of the XIMPreeditCallbacksStruct. If the position is 0, the cursor should be positioned so that new text will be inserted before the first character of the pre-edit string. If it is 1, the cursor should be positioned so that new text will be inserted before the second character of the pre-edit string, and so on.

  • XIMDontChange means that the cursor position should not be changed. The current position of the cursor must still be returned in the position field, however.

The XNPreeditCaretCallback can also be called to request that the insertion cursor become hidden or be drawn in a different style. Different cursor appearances may be used by the input method to indicate different pre-editing modes, insert versus overwrite mode, for example. The possible values of the style field and their meanings are as follows:

  • XIMIsInvisible means that the insertion cursor should not be displayed.

  • XIMIsPrimary means that the insertion cursor should be displayed in its primary or normal style. The particular style used is up to the application.

  • XIMIsSecondary means that the insertion cursor should be displayed in its secondary or special style. The particular style used is up to the application.

Note that there is no provision for the handling of mouse clicks (for example, to move the position of the insertion cursor in the pre-edit text) in this interaction style. Since the input method does not know how the pre-edit text is displayed, it cannot interpret mouse clicks over the text, and there is no specified way for the IM to request the application to convert pixel locations to character positions. Furthermore, the application cannot handle mouse clicks on the pre-edit text because it has no way of changing the internal insertion position of the IM. Note that some input methods will allow mouse clicks and drags while pre-editing in the XIMPreeditPosition and XIMPreeditArea interaction styles; in this case these styles may actually provide a more consistent user interface than the XIMPreeditCallbacks style.

The StatusStartCallback and the StatusDoneCallback

These callbacks are called when an IC gains focus or loses focus (possibly by being destroyed). They give the application the chance to set up or clean up any internal structures for handling status display, and allow the application to provide graphical feedback of the new IC focus state to the user. Both are passed NULL call_data and neither has any required actions.

The StatusDrawCallback

The input method invokes the XNStatusDrawCallback when it wants the application to display a string or a bitmap in the status area. The callback procedure is passed call_data of type XIMStatusDrawCallbackStruct, which is shown in Example 11-11.

Example 11-11. The XIMStatusDrawCallbackStruct

typedef enum {XIMTextType, XIMBitmapType} XIMStatusDataType;
typedef struct _XIMStatusDrawCallbackStruct {
    XIMStatusDataType type;
    union {
        XIMText text;
        Pixmap  bitmap;
    } data;
} XIMStatusDrawCallbackStruct ;

If the type field is XIMTextType, then the callback must display the text described by data.text in the status area of the IC. The XIMText type is also used by the XNPreeditDrawCallback, and is shown and explained in “The PreeditDrawCallback” The text may be in multi-byte or wide-character form, so the application must be able to handle either case. Recall that the length field of the XIMText structure gives the number of characters of text, even when the text is in multi-byte form. The length in bytes of a multi-byte string is required for a call to XmbDrawImageString(), so when text is passed in multi-byte form, the application will have to use strlen to determine its length before displaying it.

If the type field is XIMBitmapType, then the callback must display the 1-bit deep Pixmap data.bitmap. [46] Notice that the callback does not return the width or height of the pixmap, so these must be obtained with a call to XGetGeometry() before the pixmap is displayed.

The XIMStatusCallbacks interaction style does not allow for any communication between the application and the input method about the maximum size of the status area. Since it can always be passed data to display that is larger than the area it has allocated, the XNStatusDrawCallback must be prepared either to clip or provide scrolling for the strings and pixmaps it is passed, or to attempt to enlarge the status area. Resizing the status area requires the main application window to be made larger or other windows to be rearranged or resized. The XIMStatusCallbacks interaction style can be useful for an application designed to be used with a single input method which calls the XNStatusDrawCallback with well specified values. In general, however, when you don't know what sort of data your application will be asked to display (or the meaning of that data), you won't be able to do anything beyond displaying the data in some rectangular region of your application, which amounts to the same thing as the XIMStatusArea interaction style. So in these cases it may make more sense to use XIMStatusArea if the input method supports it.

Filtering Events

An input method needs to receive X events other than keystrokes. It must receive expose events when its Preedit or Status areas need refreshing, it needs mouse button events if it is to support full-featured editing of pre-edit text, and it needs mouse motion events if it implements popup menus. The input method needs to get first crack at these events, but will not always be able to intercept them directly from the server, so the application is responsible for passing all events to the input method before processing them itself. This is done with the function XFilterEvent(). It should be called from the event loop of all internationalized applications, generally right after XNextEvent(). XFilterEvent() takes two arguments, the event to filter, and the window to which the event is directed. If the application (or a toolkit used by the application) performs event redirection, this window may not be the same as the window in which the event occurred. If the window argument is None, the window of the event will be used. An application cannot know in advance which events the IM will need to filter; it must pass all events to XFilterEvent(). If XFilterEvent() returns True, it filtered the event the application should dispatch the event no further.

Remember that an input method may be interested in different types of events than the application is. If the application is to pass events to the input method through XFilterEvent(), the application must have registered interest in receiving those events with XSelectInput(). The XNFilterEvents input context attribute contains a mask of events that the input method is interested in receiving, and all clients should read this attribute and use it when selecting events. Example 11-12 shows code that does this and an event loop that uses XFilterEvent().

Example 11-12. Selecting events for an IM and using XFilterEvent() in an event loop

long im_event_mask;
      .
      .
      .
XGetICValues(ic, XNFilterEvents, &im_event_mask, NULL);
XSelectInput(dpy, win, ExposureMask | KeyPressMask
             | StructureNotifyMask | im_event_mask);
for(;;) {
    XEvent e;
    XNextEvent(dpy, &e);
    if (XFilterEvent(&e, None)) continue;
    switch (e.type) {
        .
        .   /* dispatch the event here */
        .
    }
}

The R5 X Toolkit Intrinsics have been modified to make appropriate use of XFilterEvent() in the function XtDispatchEvent() called from XtAppMainLoop().

Getting Composed Text

Prior to R5, XLookupString() was used to convert the keycode returned in a KeyPress event into a KeySym and further into a character string that could be passed to the X text drawing functions. Unfortunately, this function only works for the Latin-1 charset. To support internationalization in a limited way, there were alternate LookupString functions in the Xmu library: XmuLookupLatin2(), XmuLookupJISX0201(), XmuLookupGreek(), etc. In R5, these have been superseded by XmbLookupString() and XwcLookupString(). These functions are identical except in the type of string they return: the Xmb version returns a multi-byte string of char, and Xwc version returns a wide-character string of wchar_t. In both cases the string will be encoded as appropriate for the locale of the IC.[47]

Whenever a KeyPress event is delivered to an application that is performing internationalized text input, the application should use that event in a call to XmbLookupString() or XwcLookupString(). (Note that KeyRelease events should not be passed to these functions--they will result in undefined behavior.) The application should not expect that each call to Xmb/XwcLookupString() will return a string. Depending on the complexity of the input method in use, a user may type many keystrokes before any composed input is ready for the application. Neither should the application expect that Xmb/XwcLookupString() will return a single character at a time--in some input methods a user may type a phrase, a sentence, or more before hitting the key that triggers the conversion from pre-edit to composed text.

XmbLookupString() and XwcLookupString() take as arguments the IC for which input is to be looked up (which is usually the IC with the focus), the X event that triggered the call, a buffer to return the multi-byte or wide-character string in, a pointer to a location to return a keysym, and a pointer to a location to return a status value. The value returned by both functions is an integer which specifies the number of bytes in the returned multi-byte string or the number of wchar_t in the returned wide-character string. There are five status values that these functions return, each of which may require separate processing:

  • XLookupNone means that the input method does not have any composed input ready to pass to the application, and the application need not do any further processing on the current key event. When this status value is returned, the return value of the function will be 0.

  • XLookupKeySym means that a keysym, but no string, has been returned. This likely means that the user has struck a special key of some sort (a function key, an arrow key, Delete, etc.). The application should handle the keysym as appropriate. Because no string is returned, the return value of the function is 0. Be careful to capitalize the constant XLookupKeySym correctly; Xlib also defines the function XLookupKeysym().

  • XLookupChars means that a string, but no keysym, has been returned. The multi-byte or wide-character string is encoded in the codeset of the locale of the IC and is placed in the buffer passed to the function. The return value of the function is the length of the multi-byte string in bytes or the length of the wide-character string in wide characters.

  • XLookupBoth means that both a string and a keysym are returned. This may indicate that a single keystroke has passed through the input method without any pre-editing, as is common in European input methods, for example. The return value of the function is the length of the string, as described for XLookupChars above.

  • XBufferOverflow means that the string to be returned will not fit in the provided buffer. The return value of the function is the required size of the buffer (in bytes or wide characters), and nothing is returned in the string buffers. The input string remains in the IC, waiting to be looked up. The application should allocate a buffer of the required size and look up the string, or should display an error message and flush the pending input with a call to XmbResetIC() or XwcResetIC. If this return status is ignored, the large input string will remain pending and block any further input on that IC.

Some input method architectures allow the input method to intercept events from the X server before the application ever sees them. If these input methods remove all KeyPress events from the input stream, then the application will never be triggered to call Xmb/wcLookupString(). If this is the case, the input method will send a synthetic KeyPress event to the application when it has composed input ready for lookup. By convention, the keycode in this synthetic event should be 0. Note, though, that these are architectural details and do not affect the structure of an internationalized applications.

Example 11-13 shows code that uses XwcLookupString() and handles each of the possible return status values.

Example 11-13. Looking up internationalized input

XEvent event;
int len;
int buf_len = 10;
wchar_t *buffer = (wchar_t *)malloc(buf_len * sizeof(wchar_t));
KeySym keysym;
Status status;
while(1) {
    XNextEvent(dpy, &event);
    if (XFilterEvent(&event, None))
      continue;
    switch (event.type) {
    case Expose:
        Redraw();
        break;
    case KeyPress:
        len = XwcLookupString(ic, &event, buffer, buf_len,
                              &keysym, &status);
        if (status == XBufferOverflow) {
            buf_len = len;
            buffer = (wchar_t *)realloc(buffer, buf_len*sizeof(wchar_t));
            len = XwcLookupString(ic, &event, buffer, buf_len,
                                  &keysym, &status);
        }
        switch (status) {
        case XLookupNone:
            break;
        case XLookupKeySym:
        case XLookupBoth:
            /* Handle backspacing */
            if ((keysym == XK_Delete) || (keysym == XK_BackSpace)) {
                Backspace();
                break;
            }
            if (status == XLookupKeySym) break;
        case XLookupChars:
            Insert(buffer, len);
            break;
        }
        break;
    }
}


XIM Programming Checklist

The following list provides useful guidelines when writing an Xlib or Xt application or Xt widget that uses the R5 internationalized input mechanisms. It is followed by an example Xlib program that performs simple internationalized text input and implements most of the steps in the list.

  • Set the locale with setlocale. Use a locale name from a resource, or specify the empty string (""). In an Xt application do this from the special callback procedure registered with XtSetLanguageProc().

  • Verify that X supports the locale with XSupportsLocale().

  • Set the locale modifiers (i.e., the name of the input method to use) from a resource or with the empty string.

  • If you want your input method to be customizable with resources, create a database or get a handle to an already created one. In an Xt application, use XtDatabase().

  • Open a connection to the IM of the locale with XOpenIM(). Pass a resource database and the name and class the IM should use for looking up its resources in that database. Verify that the IM is successfully opened. If you are writing a widget, you can assume that a valid XIM will be passed as a resource, and skip this step.

  • Query the IM for its supported interaction styles. Choose one that your application can support based on the value of user-specified resources, or upon some criteria for which will provide the best user interface for your application. In a widget, this should be in the initialize method.

  • Create an XFontSet for use by the IC. The base font name list for the XFontSet should be obtained from a resource. In an Xt application, you should use the constant XtDefaultFontSet as the default value for this resource. If you are writing a widget, you can assume that a valid XFontSet will be passed as a resource.

  • Create a Window for use by the IC. If you are programming with Xt, create a widget. If you are writing your own widget, the window will be created for you by the realize method.

  • Create an IC with XCreateIC(), specifying the interaction style you choose, the XNEditWindow, and the XNFontSet sub-attribute for both the Preedit and Status Areas. If you are using the XIMPreeditPosition style, you must also specify the XNAreaNeeded attribute, and if you are using XIMPreeditCallbacks or XIMStatusCallbacks styles, you must specify values for all the applicable callback attributes. You may also specify any other attributes at this point. If you are writing a widget, create the IC in the initialize method, but specify the window in the realize method. In a widget, you should provide widget resources which control the setting of IC attributes like XNLineSpacing and XNCursor.

  • Query the value of the XNFilterEvents attribute of the IC and augment the event mask for your window with those events. If you are writing an Xt program, call XtAddEventHandler for the event mask with a no-op procedure. If you are writing a widget, call XtAddEventHandler() in the same way from the realize method.

  • If you have selected the XIMPreeditArea or the XIMStatusArea interaction styles, negotiate a geometry for either or both of those areas using the XNAreaNeeded attribute of the IC. Set the geometry you decide on in the XNArea attribute. If you are writing a widget, begin the negotiation in the initialize method, and set the XNArea attribute when the window is created in the realize method. Renegotiate geometry whenever your application window changes size.

  • If you have selected the XIMPreeditPosition interaction style, set the initial location of your insertion cursor in the XNSpotLocation attribute, and a region within which pre-editing is allowed in the XNArea resource. If you are writing a widget, do this in the resize method. In a widget, you may want to implement the Preedit and Status areas as sub-widgets.

  • For a simple application that does no focus management, set the focus to your IC with XSetICFocus(). For more complicated applications, you should set and unset IC focus when you receive FocusIn and FocusOut events, or whenever your application-internal or toolkit focus changes. In an Xt program or widget, you can use an event handler or a translation and action to track focus changes.

  • Use XFilterEvent() in your event loop before dispatching an event. If it returns True, discard the event and wait for another. In Xt programs, this is handled for you by XtDispatchEvent() in XtAppMainLoop().

  • When XFilterEvent() returns an unfiltered KeyPress event, use Xmb/wcLookupString() to convert it to a KeySym or a string in the encoding of the locale. In Xt programs or widgets, use an event handler or a translation and action to get these events.

  • Echo the newly input characters with Xmb/wcDrawString() or one of the other R5 text drawing functions.

  • If you are using the XIMPreeditPosition interaction style, update the values of the XNSpotLocation and XNArea attributes of the IC each time you move the insertion cursor.

  • If your application supports the XIMPreeditArea or XIMStatusArea interaction styles, optionally write a GeometryCallback procedure to handle requests from the IM to change the size of those areas. If you are writing a composite widget, the GeometryCallback and the geometry_manager method may be able to share code.

  • If your application supports the XIMPreeditCallbacks or XIMStatusCallbacks interaction styles, write the required callback procedures to support those styles.

Example 11-14 is the complete code of a program that performs simple internationalized text input. Many of the examples in this chapter and the last are fragments of this program.[48]

Example 11-14. Performing internationalized text input: a complete program

/*
 * This program demonstrates some of the R5 internationalized text
 * input functions.  It creates a very simple window, connects to an
 * input method, and displays composed text obtained by calling
 * XwcLookupString.  It backspaces when it receives the Backspace or
 * Delete keysyms.
 *
 * Note that this program contains a work-around for a bug
 * in the Xsi implementation of XwcLookupString.  If you are using
 * the Ximp implementation, or if the bug has been fixed in your Xlib,
 * you will need to undo the workaround.  See the comment below, near
 * the call to XwcLookupString.
 *
 * This program has not been tested with the Ximp implementation.
 */
#include <stdio.h>

#include <malloc.h>

#include <X11/Xlib.h>

#include <X11/keysym.h>

/*
 * include <locale.h> or the non-standard X substitutes
 * depending on the X_LOCALE compilation flag
 */
#include <X11/Xlocale.h>

/*
 * This function chooses the "more desirable" of two input styles.  The
 * style with the more complicated Preedit style is returned, and if the
 * styles have the same Preedit styles, then the style with the more
 * complicated Status style is returned.  There is no "official" way to
 * order interaction styles.  This one makes the most sense to me.
 * This is a long procedure for a simple heuristic.
 */
XIMStyle ChooseBetterStyle(style1,style2)
XIMStyle style1, style2;
{
    XIMStyle s,t;
    XIMStyle preedit = XIMPreeditArea | XIMPreeditCallbacks |
        XIMPreeditPosition | XIMPreeditNothing | XIMPreeditNone;
    XIMStyle status = XIMStatusArea | XIMStatusCallbacks |
        XIMStatusNothing | XIMStatusNone;
    if (style1 == 0) return style2;
    if (style2 == 0) return style1;
    if ((style1 & (preedit | status)) == (style2 & (preedit | status)))
        return style1;
    s = style1 & preedit;
    t = style2 & preedit;
    if (s != t) {
        if (s | t | XIMPreeditCallbacks)
            return (s == XIMPreeditCallbacks)?style1:style2;
        else if (s | t | XIMPreeditPosition)
            return (s == XIMPreeditPosition)?style1:style2;
        else if (s | t | XIMPreeditArea)
            return (s == XIMPreeditArea)?style1:style2;
        else if (s | t | XIMPreeditNothing)
            return (s == XIMPreeditNothing)?style1:style2;
    }
    else { /* if preedit flags are the same, compare status flags */
        s = style1 & status;
        t = style2 & status;
        if (s | t | XIMStatusCallbacks)
            return (s == XIMStatusCallbacks)?style1:style2;
        else if (s | t | XIMStatusArea)
            return (s == XIMStatusArea)?style1:style2;
        else if (s | t | XIMStatusNothing)
            return (s == XIMStatusNothing)?style1:style2;
    }
}
void GetPreferredGeometry(ic, name, area)
XIC ic;
char *name;           /* XNPreEditAttributes or XNStatusAttributes */
XRectangle *area;     /* the constraints on the area */
{
    XVaNestedList list;
    list = XVaCreateNestedList(0, XNAreaNeeded, area, NULL);
    /* set the constraints */
    XSetICValues(ic, name, list, NULL);
    /* Now query the preferred size */
    /* The Xsi input method, Xwnmo, seems to ignore the constraints, */
    /* but we're not going to try to enforce them here. */
    XGetICValues(ic, name, list, NULL);
    XFree(list);
}
void SetGeometry(ic, name, area)
XIC ic;
char *name;           /* XNPreEditAttributes or XNStatusAttributes */
XRectangle *area;     /* the actual area to set */
{
    XVaNestedList list;
    list = XVaCreateNestedList(0, XNArea, area, NULL);
    XSetICValues(ic, name, list, NULL);
    XFree(list);
}
main(argc, argv)
int argc;
char *argv[];
{
    Display *dpy;
    int screen;
    Window win;
    GC gc;
    XGCValues gcv;
    XEvent event;
    XFontSet fontset;
    XIM im;
    XIC ic;
    XIMStyles *im_supported_styles;
    XIMStyle app_supported_styles;
    XIMStyle style;
    XIMStyle best_style;
    XVaNestedList list;
    long im_event_mask;
    XRectangle preedit_area;
    XRectangle status_area;
    char *program_name = argv[0];
    char **missing_charsets;
    int num_missing_charsets = 0;
    char *default_string;
    wchar_t string[200];
    int str_len = 0;
    int i;
    /*
     * The error messages in this program are all in English.
     * In a truly internationalized program, they would not
     * be hardcoded; they would be looked up in a database of
     * some sort.
     */
    if (setlocale(LC_ALL, "") == NULL) {
        (void) fprintf(stderr, "%s: cannot set locale.,program_name);
        exit(1);
    }
    if ((dpy = XOpenDisplay(NULL)) == NULL) {
        (void) fprintf(stderr, "%s: cannot open Display., program_name);
        exit(1);
    }
    if (!XSupportsLocale()) {
        (void) fprintf(stderr, "%s: X does not support locale %s.,
                       program_name, setlocale(LC_ALL, NULL));
        exit(1);
    }
    if (XSetLocaleModifiers("") == NULL) {
        (void) fprintf(stderr, "%s: Warning: cannot set locale modifiers.,
                                argv[0]);
    }
    /*
     * Create the fontset.
     */
    fontset = XCreateFontSet(dpy,
                             "-adobe-helvetica-*-r-*-*-*-120-*-*-*-*-*-*,\
                              -misc-fixed-*-r-*-*-*-130-*-*-*-*-*-*",
                             &missing_charsets, &num_missing_charsets,
                             &default_string);
    /*
     * if there are charsets for which no fonts can
     * be found, print a warning message.
     */
    if (num_missing_charsets > 0) {
        (void)fprintf(stderr, "%s: The following charsets are missing:,
                      program_name);
        for(i=0; i < num_missing_charsets; i++)
            (void)fprintf(stderr, "%s: %s, program_name,
                          missing_charsets[i]);
        XFreeStringList(missing_charsets);
        (void)fprintf(stderr, "%s: The string %s will be used in place,
                      program_name, default_string);
        (void)fprintf(stderr, "%s: of any characters from those sets.,
                      program_name);
    }
    screen = DefaultScreen(dpy);
    win = XCreateSimpleWindow(dpy, RootWindow(dpy, screen), 0, 0, 400, 100,
                             2, WhitePixel(dpy,screen),BlackPixel(dpy,screen));
    gc = XCreateGC(dpy,win,0,&gcv);
    XSetForeground(dpy,gc,WhitePixel(dpy,screen));
    XSetBackground(dpy,gc,BlackPixel(dpy,screen));
    /* Connect to an input method.  */
    /* In this example, we don't pass a resource database */
    if ((im = XOpenIM(dpy, NULL, NULL, NULL)) == NULL) {
        (void)fprintf(stderr, "Couldn't open input method);
        exit(1);
    }
    /* set flags for the styles our application can support */
    app_supported_styles = XIMPreeditNone | XIMPreeditNothing | XIMPreeditArea;
    app_supported_styles |= XIMStatusNone | XIMStatusNothing | XIMStatusArea;
    /* figure out which styles the IM can support */
    XGetIMValues(im, XNQueryInputStyle, &im_supported_styles, NULL);
    /*
     * now look at each of the IM supported styles, and
     * chose the "best" one that we can support.
     */
    best_style = 0;
    for(i=0; i < im_supported_styles->count_styles; i++) {
        style = im_supported_styles->supported_styles[i];
        if ((style & app_supported_styles) == style) /* if we can handle it */
            best_style = ChooseBetterStyle(style, best_style);
    }
    /* if we couldn't support any of them, print an error and exit */
    if (best_style == 0) {
        (void)fprintf(stderr, "%s: application and program do not share a,
                      argv[0]);
        (void)fprintf(stderr, "%s: commonly supported interaction style.,
                      argv[0]);
        exit(1);
    }
    XFree(im_supported_styles);
    /*
     * Now go create an IC using the style we chose.
     * Also set the window and fontset attributes now.
     */
    list = XVaCreateNestedList(0,XNFontSet,fontset,NULL);
    ic = XCreateIC(im,
                   XNInputStyle, best_style,
                   XNClientWindow, win,
                   XNPreeditAttributes, list,
                   XNStatusAttributes, list,
                   NULL);
    XFree(list);
    if (ic == NULL) {
        (void) fprintf(stderr, "Couldn't create input context);
        exit(1);
    }
    XGetICValues(ic, XNFilterEvents, &im_event_mask, NULL);
    XSelectInput(dpy,win, ExposureMask | KeyPressMask
                 | StructureNotifyMask | im_event_mask);
    XSetICFocus(ic);
    XMapWindow(dpy,win);
    while(1) {
        int buf_len = 10;
        wchar_t *buffer = (wchar_t *)malloc(buf_len * sizeof(wchar_t));
        int len;
        KeySym keysym;
        Status status;
        Bool redraw = False;
        XNextEvent(dpy, &event);
        if (XFilterEvent(&event, None))
            continue;
        switch (event.type) {
        case Expose:
            /* draw the string at a hard-coded location */
            if (event.xexpose.count == 0)
                XwcDrawString(dpy, win, fontset, gc, 10, 50, string, str_len);
            break;
        case KeyPress:
            len = XwcLookupString(ic, &event, buffer, buf_len,
                                  &keysym, &status);
            /*
             * Workaround:  the Xsi implementation of XwcLookupString
             * returns a length that is 4 times too big.  If this bug
             * does not exist in your version of Xlib, remove the
             * following line, and the similar line below.
             */
            len = len / 4;
            if (status == XBufferOverflow) {
                buf_len = len;
                buffer = (wchar_t *)realloc((char *)buffer,
                                            buf_len * sizeof(wchar_t));
                len = XwcLookupString(ic, &event, buffer, buf_len,
                                      &keysym, &status);
                /* Workaround */
                len = len / 4;
            }
            redraw = False;
            switch (status) {
            case XLookupNone:
                break;
            case XLookupKeySym:
            case XLookupBoth:
                /* Handle backspacing, and <Return> to exit */
                if ((keysym == XK_Delete) || (keysym == XK_BackSpace)) {
                    if (str_len > 0) str_len--;
                    redraw = True;
                    break;
                }
                if (keysym == XK_Return) exit(0);
                if (status == XLookupKeySym) break;
            case XLookupChars:
                for(i=0; i < len; i++)
                    string[str_len++] = buffer[i];
                redraw = True;
                break;
            }
            /* do a very simple-minded redraw, if needed */
            if (redraw) {
                XClearWindow(dpy, win);
                XwcDrawString(dpy, win, fontset, gc, 10, 50, string, str_len);
            }
            break;
        case ConfigureNotify:
            /*
             * When the window is resized, we should re-negotiate the
             * geometry of the Preedit and Status area, if they are used
             * in the interaction style.
             */
            if (best_style & XIMPreeditArea) {
                preedit_area.width = event.xconfigure.width*4/5;
                preedit_area.height = 0;
                GetPreferredGeometry(ic, XNPreeditAttributes, &preedit_area);
                preedit_area.x = event.xconfigure.width - preedit_area.width;
                preedit_area.y = event.xconfigure.height - preedit_area.height;
                SetGeometry(ic, XNPreeditAttributes, &preedit_area);
            }
            if (best_style & XIMStatusArea) {
                status_area.width = event.xconfigure.width/5;
                status_area.height = 0;
                GetPreferredGeometry(ic, XNStatusAttributes, &status_area);
                status_area.x = 0;
                status_area.y = event.xconfigure.height - status_area.height;
                SetGeometry(ic, XNStatusAttributes, &status_area);
            }
            break;
        }
    }
}




[37] The XIM spec places no restrictions on how many flags may be set in an XIMStyle, but it does not assign any meaning to a style which has multiple XIMPreedit or XIMStatus flags.

[38] Some attributes, such as XNGeometryCallback and XNArea, have values that are pointer types. The spec does not say whether the values pointed to by these attributes are copied. It appears that the Xsi implementation (the default) does make a copy of all these attribute values, with the exception of the XNResourceName and XNResourceClass attributes, which are strings and not of fixed length.

[39] The R5 spec is self-contradictory about which attributes will have memory allocated for them. It says, “Each argument value (following a name) must point to a location where the value is to be stored. XGetICValues() allocates memory to store the values, and client [sic] is responsible for freeing each value by calling XFree().” The first sentence indicates that the program provides memory for the attribute value. The second indicates that the program provides memory for a pointer to the attribute value. The Xsi implementation (the default) takes the first approach, and the Ximp implementation takes the second. So, for example, to query the value of the XNFocusWindow attribute, you would pass the address of a Window to XGetICValues() if using the Xsi implementation, but the address of a Window * if using the Ximp implementation. In the second case, the returned Window * value points to allocated memory which must be freed. When querying attributes like XNResourceName and XNGeometryCallback, which have values that are pointer types, it is not clear what types should be passed in the query, nor is it clear whether the returned pointer points to a copy of the value which must be freed, or to the value itself which must not be freed. As a programmer, your best bet is to avoid the use of XGetICValues(), except when necessary for the XNFilterEvents and XNAreaNeeded attributes.

[40] The R5 spec does not state whether the client should free this string, nor when it will be freed by Xlib.

[41] The R5 spec simply says “line spacing” and does not specify whether the value should be a baseline-to-baseline spacing or just interline spacing. A baseline-to-baseline spacing was probably the intent, but it will be safest to leave this attribute unspecified and use the IM default.

[42] The R5 spec makes no statement about the duration of the validity of the application's constraints.

[43] The R5 spec says, “Characters starting from chg_first to chg_first+chg_length must be deleted.”

[44] The R5 spec says nothing about the XIMHighlight style.

[45] The R5 spec does not say what an application should do if a cursor motion request would take the cursor beyond the pre-edit text. You should probably leave the cursor where it is or move it to one end of the text. In either case simply return the new or unchanged position.

[46] The R5 spec does not say anything about the depth of this Pixmap.

[47] There were bugs in the public R5 version of the Xsi implementation of XwcLookupString(). It is supposed to return as its value the number of characters in the returned string, but appears, at least in some cases, to return the number of bytes instead.

[48] To run this program successfully, you must have an input method running. Because there are no input methods as part of the core R5 distribution, this may be difficult. If your Xlib uses the Xsi implementation of the R5 internationalization features, you can use the input method in contrib/im/Xsi. In order to run this program, I had to do the following:

  • Build everything in contrib/im/Xsi.

  • Install everything in contrib/im/Xsi. This involved installing a number of files under /usr/local/lib/wnn, and adding a new user “wnn” to the /etc/passwd file.

  • Start the “translation server” contrib/im/Xsi/Wnn/jserver/jserver.

  • Start the “input manager” contrib/im/Xsi/Xwnmo/xwnmo/xwnmo which was also installed in /usr/bin/X11.

  • Set the XMODIFIERS environment variable to “@im=_XWNMO.”

  • Set the LANG environment variable to something appropriate, ja_JP.ujis, for example.

With these steps accomplished, I was able to run the program and type Latin characters, but I was never able to figure out how to actually make use of the input method to input Japanese. Since the Xsi input method is contributed software, it may have been updated since this program was written, and the above list may no longer be correct.