Scim Bridge Developer Manual: The clients

Up

The introduction

The clients of scim-bridge communicates with the agent with utf8 string messages, but you don't have to understand them at all. There are the client library to handle them. All you have to do is to understand how to use this library.

Let's learn about the event loop

Most GUI applications have their own event loop. The event loop is unique for each process, and all the GUI events are handled in this big loop. Immodules could have another thread, but I don't recommand that because most GUI libraries are not "thread-safe". You should handle GUI events in the GUI thread and change the preedit only from the same thread. The most difficult point is the communication with the agent.

How should I handle messages?

The first step you should do is to add the pool trigger for your GUI library. This invokes an event when the message from the agent available. Creating another thread to pooling the socket is not recommanded as many GUI library is not thread safe. The ancient scim-bridge-0.1.* always has troubles with the violation of the GUI thread policy.

Example:

    // This function is called while initializing the immodule.
    void scim_bridge_client_gtk_initialize ()
    {
        ...

        // Try to initialize scim client library.
        if (!scim_bridge_client_initialize ()) {

            // Add the IO channel for the socket.
            messenger_iochannel = g_io_channel_unix_new (scim_bridge_client_get_socket_fd ());

            // Register the IO channel to the GUI event loop,
            // so that it calls handle_message () when the message is arrived.
            messenger_event_source = g_io_add_watch (messenger_iochannel, G_IO_IN, &handle_message, NULL);

        } else {
            scim_bridge_perrorln ("Error: Cannot initialize the client library");
            return;
        }

        ...
    }

In the event handler, you have to read_and_dispatch the message. It reads the message, translate it into C function calls for you. Note, read_and_dispatch might be block itself when there is no message at all.

- retval_t scim_bridge_client_read_and_dispatch ()
  - Read and dispatch the message.
  - It returns RETVAL_SUCCEEDED if succeeded, otherwise it returns RETVAL_FAILED.

Example:

    // This function is called when a message is arrived from the agent.
    static gboolean handle_message (GIOChannel *source, GIOCondition condition, gpointer data)
    {
        // Get the file discriptor for the socket.
        const int socket_fd = scim_bridge_client_get_socket_fd ();

        fd_set read_set;
        FD_ZERO (&read_set);
        FD_SET (socket_fd, &read_set);

        struct timeval timeout;
        timeout.tv_sec = 0;
        timeout.tv_usec = 0;

        // The GTK+ has some bug that it sometimes give us invalid pool events.
        // I have to check if an message is really available.
        if (select (socket_fd + 1, &read_set, NULL, NULL, &timeout) > 0) {

            // There should be an message arraived.
            // Now, read and dispatch the message.
            if (scim_bridge_client_read_and_dispatch ()) {
                // Error.
                scim_bridge_perrorln ("Error: IO exception");
            }
        }

        return TRUE;
    }

Note, some of the functions may block until the agent returns a response, but another message from the agent may interrupt to invoke the helper function while blocking. You might make a serious bug without understanding this feature. :<

The registration

When a new IMContext is allocated, you have to register it to the agent. You can get the unique ID for the IMContext if registration is succeeded. Of course, you also have to unregister it when it's freed. Note, the agent would free all the imcontext of the client when the connection is lost by accidents.

- retval_t scim_bridge_client_register_imcontext (ScimBridgeClientIMContext *imcontext)
  - Register the new IMContext to the agent.
  - It gives the IMContext an uniqe ID if succeeded. Otherwise, the ID of the IMContext is set to -1.
- retval_t scim_bridge_client_deregister_imcontext (ScimBridgeClientIMContext *imcontext)
  - Deregister the IMContext from the agent.
  - It sets the ID of the IMContext to -1.

The preedit

You can change the way to show the preedit for the client by following function. This is useful especially in the case the agent cannot show the preedit for you; The client can stop the agent showing the preedit and show the one by itself.

- retval_t scim_bridge_client_set_preedit_mode (const ScimBridgeClientIMContext *imcontext, scim_bridge_preedit_mode_t mode)
  - Set the way to show the preedit.
  - The default value is SCIM_BRIDGE_PREEDIT_ANY, but you can chage that by giving NULL to imcontext.

The key event

As it says, the agent sometimes ignores the key events. In that case, the client can handle it by itself. If the "A" key is pressed and ignored, the client can commit "A" by its own. Otherwise, the client should not be handle it by its own. There is some arguments about the acclerator key events. Some say that the accelerator key events and nmemonic key events should not be passed the IM but be handled by the clients, while others say that IM should handle all the key events first. There is no standard for this, but I personally believe that the accelerators should be handled by the clients first and be passed to the agent later if it has not been consumed. :)

- retval_t scim_bridge_handle_key_event (const ScimBridgeClientIMContext *imcontext, const ScimBridgeKeyEvent *key_event, boolean *consumed)
  - Request the agent to handle a key event.
  - When the event is consumed, The flag consumed gives TRUE.

Example:

    // This function is called everytime a key event occures unless it's an accelerator key event.
    gboolean scim_bridge_client_imcontext_filter_key_event (GtkIMContext *context, GdkEventKey *event)
    {
        ...

        // Get the imcontext which is currently focused.
        ScimBridgeClientIMContext *imcontext = SCIM_BRIDGE_CLIENT_IMCONTEXT (context);

        // Check if the imcontext is valid.
        if (scim_bridge_client_is_active () && imcontext != NULL && !key_snooper_used) {
            ...

            // Translate the key event of Gtk+ into the one of scim-bridge.
            ScimBridgeKeyEvent *bridge_key_event = scim_bridge_alloc_key_event ();
            scim_bridge_key_event_gdk_to_bridge (bridge_key_event, imcontext->client_window, event);

            // Pass it to the agent.
            boolean consumed = FALSE;
            const retval_t retval_error = scim_bridge_client_handle_key_event (imcontext, bridge_key_event, &consumed);

            if (retval_error) {
                // Error
                scim_bridge_perrorln ("IO error occured");
            } else if (consumed) {
                // The key event has been consumed.
                // Do not handle it any more.
                return TRUE;
            }
        }

        // The key event has not been consumed.
        // Handle it by the fallback handler.
        return gtk_im_context_filter_keypress (fallback_imcontext, event);
    }

The focus and the cursor location

The information about the focus is very important. Without it, the agent can't handle IM events properly. You have to tell the agent everytime an IMContext loses its focus or gains a focus. When the focus is moved from an IMContext to another one, please tell focus-out first and then tell focus-in.

- retval_t scim_bridge_client_set_focus (const ScimBridgeClientIMContext *imcontext, boolean focus_in)
  - The focus of an IMContext has been changed.
  - focus_in stands for the type of this focus changing. Give it TRUE if the IMContext gets the focus newly, otherwise give it FALSE.

Example:

    // This function is called when it gains the focus.
    void scim_bridge_client_imcontext_focus_in (GtkIMContext *context)
    {
        ...

        // Get the imcontex which gets the focus now.
        ScimBridgeClientIMContext *imcontext = SCIM_BRIDGE_CLIENT_IMCONTEXT (context);

        // If another IMContext still has the focus, get it back.
        if (focused_imcontext != NULL && focused_imcontext != imcontext) {
            scim_bridge_client_imcontext_focus_out (GTK_IM_CONTEXT (focused_imcontext));
        }

        focused_imcontext = imcontext;

        ...
        if (scim_bridge_client_is_active () && imcontext != NULL) {

            // Tell the agent the focus changing.
            if (scim_bridge_client_set_focus (imcontext, TRUE)) {
                // Error
                scim_bridge_perrorln ("Cannot handle `focus-in` properly");
            }
        }

        ....
    }

    // This function is called when it loses the focus.
    void scim_bridge_client_imcontext_focus_out (GtkIMContext *context)
    {
        ...

        ScimBridgeClientIMContext *imcontext = SCIM_BRIDGE_CLIENT_IMCONTEXT (context);

        focused_imcontext = imcontext;

        // Hide the preedit. (Not necessary, but recommanded)
        if (imcontext->preedit_shown) {
            scim_bridge_client_imcontext_set_preedit_shown (imcontext, FALSE);
            scim_bridge_client_imcontext_update_preedit (imcontext);
        }
        if (scim_bridge_client_is_active () && imcontext != NULL) {

            // Tell the agent the focus changing.
            if (scim_bridge_client_set_focus (imcontext, FALSE)) {
                scim_bridge_perrorln ("Cannot handle `focus-out` properly");
            }
        }

        ...
        focused_imcontext = NULL;
    }

The cursor location is required by the agent when it shows the preedit and the lookup table. You have to tell the agent the cursor position in the display continually to update the location of them properly, otherwise they won't show up in the correct positions. On the other hand, you don't have to tell it when all of them are showen by the clients.

- retval_t scim_bridge_client_set_cursor_location (const ScimBridgeClientIMContext *imcontext, int x, int y)
  - Change the cursor location in the display.

Copyright (c) 2006 Ryo Dairiki <ryo-dairiki@users.sourceforge.net>