How to extend Hildon Input Methods
Overview
This document has been reviewed for maemo 3.x.
Maemo platform is intended to be used on embedded devices. It is a quite straightforward request, that one might want to have different input methods than the ones available by default or just simply wants a different layout of the virtual keyboard. For this reason in maemo 3.x we have introduced a way to be able to write custom plugins for Hildon Input Method.
In this document we will describe how to write a simple plugin for Hildon Input Method. This example plugin is going to implement a very basic Virtual Keyboard.
The illustration below shows what area of the Hildon Input Method we can re-define with our custom plugin.
Illustration 1 Layout of the main UI and the common buttons
Technically the plugin is an almost standard GTK widget (with additional steps to support dynamic loading). The plugin's widget will be placed at the Plugin area.
The illustration below show the data flow of a user's input:
Illustration 2 Components involved in input method
User inputs directly to the plugin, then the plugin will send the inputted data to HIM main user interface. The HIM main UI will interact with the IM context then commits the inputted text to the client widget.
In case of a custom plugin, the plugin itself - so it's writer - is responsible to handle all the inputs (even the buttons that are part of HIM main UI, e.g. tab or enter) and propagate them to the right modules (e.g. IM context).
The function and outlook of the buttons in HIM main UI can be customized, but one cannot remove them completely from the UI - only dim them, see handwriting plugin - and one cannot re-arrange them (for further information see chapter: Common buttons).
Plugin features
First we are going to review what are the basic functions that a Hildon Input Method plugin shall implement in addition as a normal GTK widget.
The interface
As we were already mentioning the plugin shall handle all the inputs - both real user input and management signals from the system - that are coming to the HIM main UI, so first let's take a look what functions need to be implemented and provided by the plugin to the Hildon Input Method Plugin Interface. Later on in the we will show how we actually register these functions for the HIM Plugin Interface.
The essential functions that shall be implemented by a basic plugin:
-
void (*enable) (HildonIMPlugin *plugin, gboolean init);
enable is called whenever the plugin becomes available to the user. init holds TRUE whenever this is the initialization time.
/* Called when the plugin is available to the user */ static void enable (HildonIMPlugin *plugin, gboolean init) { HimsdkVKBPrivate *priv; HimsdkVKB *vkb; vkb = HIMSDK_VKB (plugin); priv = HIMSDK_VKB_GET_PRIVATE (vkb); if (init == TRUE) { hildon_im_keyboard_button_set_toggle (priv->keyboard, BUTTON_MODE_A, TRUE); hildon_im_keyboard_button_set_toggle (priv->keyboard, BUTTON_MODE_B, TRUE); hildon_im_keyboard_button_set_label (priv->keyboard, BUTTON_MODE_A, "ABC"); hildon_im_keyboard_button_set_label (priv->keyboard, BUTTON_MODE_B, "Shift"); } }
-
void (*disable) (HildonIMPlugin *plugin)
disable is called whenever the plugin become unavailable to the user (e.g. when the main UI is closed).
/* Called when the plugin is 'disappear' */ static void disable(HildonIMPlugin *plugin) { /* empty, sic. */ }
-
void (*settings_changed) (HildonIMPlugin *plugin, const gchar *key, const GConfValue *value);
settings_changed is called whenever the HIM main UI receive a notification from GConf whenever Hildon Input Method settings are changed. The affected settings are all settings which resides in /apps/osso/inputmethod path. key and value hold the GConf key and it's value respectively.
/* Called when the standard input method settings * has been changed */ static void settings_changed (HildonIMPlugin *plugin, const gchar *key, const GConfValue *value) { /* empty, sic. */ }
-
void (*input_mode_changed) (HildonIMPlugin *plugin);
input_mode_changed is called whenever the input mode is changed. Input mode is changed to what has been specified by the client widget. The input mode put constraints to the plugin to limit what input shall be accepted or ignored.
/* Called when input mode changed */ static void input_mode_changed (HildonIMPlugin *plugin) { /* empty, sic. */ }
-
void (*character_autocase) (HildonIMPlugin *plugin);
character_autocase is called whenever the UI need to check whether auto-capitalization is in effect. The plugin is responsible to activate the auto-capitalization in this function if necessary. Auto-capitalization setting can be obtained from the HIM main UI (which basically means querying the GConf itself). Note that this function is frequently called so auto-capitalization setting shall be cached in order to reduce unecessary traffic to GConf.
-
void (*clear) (HildonIMPlugin *plugin);
clear is called whenever the HIM main UI requests the plugin to clear or refresh it's user interface.
/* Called when the plugin is requested to 'clear'/refresh it's UI */ static void clear(HildonIMPlugin *plugin) { /* empty, sic. */ }
-
void (*client_widget_changed) (HildonIMPlugin *plugin);
client_widget_changed is called whenever the client widget is changed from one to another. For instance a case could be that the user taps to another text entry.
/* Called when the client widget changed */ static void client_widget_changed (HildonIMPlugin *plugin) { /* empty, sic. */ }
-
void (*save_data) (HildonIMPlugin *plugin);
save_data is called whenever the HIM main UI is requested to save it's (and the plugins') data. Usually it is called when the main UI is requested to quit.
/* Called when the plugin is requested to save it's data */ static void save_data(HildonIMPlugin *plugin) { /* empty, sic. */ }
-
void (*mode_a) (HildonIMPlugin *plugin);
mode_a is called whenever the Mode A (Caps Lock in Virtual Keyboard plugin) is pressed.
/* Called when the MODE_A button is pressed */ static void mode_a(HildonIMPlugin *plugin) { HimsdkVKBPrivate *priv; HimsdkVKB *vkb; vkb = HIMSDK_VKB (plugin); priv = HIMSDK_VKB_GET_PRIVATE (vkb); if (hildon_im_keyboard_button_get_active (priv->keyboard, BUTTON_MODE_B)) { hildon_im_keyboard_button_set_active (priv->keyboard, BUTTON_MODE_B, FALSE); if (hildon_im_keyboard_button_get_active (priv->keyboard, BUTTON_MODE_A)) { priv->case_mode = CASE_UPPER; } else { priv->case_mode = CASE_LOWER; } } else { if (hildon_im_keyboard_button_get_active (priv->keyboard, BUTTON_MODE_A)) { priv->case_mode = CASE_UPPER; } else { priv->case_mode = CASE_LOWER; } } update_layout (vkb); }
-
void (*mode_b) (HildonIMPlugin *plugin);
mode_b is called whenever the Mode B (Shift in Virtual Keyboard plugin) is pressed.
/* Called when the MODE_B button is pressed */ static void mode_b(HildonIMPlugin *plugin) { HimsdkVKBPrivate *priv; HimsdkVKB *vkb; vkb = HIMSDK_VKB (plugin); priv = HIMSDK_VKB_GET_PRIVATE (vkb); if (hildon_im_keyboard_button_get_active (priv->keyboard, BUTTON_MODE_B)) { if (hildon_im_keyboard_button_get_active (priv->keyboard, BUTTON_MODE_A)) { priv->case_mode = CASE_LOWER; } else { priv->case_mode = CASE_UPPER; } } else { if (hildon_im_keyboard_button_get_active (priv->keyboard, BUTTON_MODE_A)) { priv->case_mode = CASE_UPPER; } else { priv->case_mode = CASE_LOWER; } } update_layout (vkb); }
-
void (*backspace) (HildonIMPlugin *plugin);
backspace is called whenever the virtual backspace key is pressed.
/* Called when the backspace button is pressed */ static void backspace (HildonIMPlugin *plugin) { HimsdkVKBPrivate *priv; priv = HIMSDK_VKB_GET_PRIVATE (HIMSDK_VKB (plugin)); hildon_im_keyboard_send_communication_message (priv->keyboard, HILDON_IM_CONTEXT_HANDLE_BACKSPACE); }
-
void (*enter) (HildonIMPlugin *plugin);
enter is called whenever the virtual enter key is pressed.
/* Called when the enter button is pressed */ static void enter (HildonIMPlugin *plugin) { HimsdkVKBPrivate *priv; priv = HIMSDK_VKB_GET_PRIVATE (HIMSDK_VKB (plugin)); hildon_im_keyboard_send_communication_message (priv->keyboard, HILDON_IM_CONTEXT_HANDLE_ENTER); }
-
void (*tab) (HildonIMPlugin *plugin);
tab is called whenever the virtual tab key is pressed.
/* Called when the tab button is pressed */ static void tab (HildonIMPlugin *plugin) { HimsdkVKBPrivate *priv; priv = HIMSDK_VKB_GET_PRIVATE (HIMSDK_VKB (plugin)); hildon_im_keyboard_send_communication_message (priv->keyboard, HILDON_IM_CONTEXT_HANDLE_TAB); }
-
void (*fullscreen) (HildonIMPlugin *plugin, gboolean fullscreen);
fullscreen is called whenever the application, to which the client widget attached is changed from or to fullscreen. fullscreen argument holds TRUE if the application changes from normal to fullscreen, and vice versa.
Couple of flavoring functions:
-
void (*language) (HildonIMPlugin *plugin);
language is called whenever a new language is selected from the HIM main UI menu.
/* Called when the language has been changed */ static void language (HildonIMPlugin *plugin) { /* Do nothing, sic. */ return; }
-
void (*language_settings_changed) (HildonIMPlugin *plugin, gint index, gint value);
language_settings_changed is called whenever the language settings has been changed. The changed language can be obtained from the arguments.
index contains:
- 0: primary languange
- 1: secondary language
value is the language code.
/* Called when the language settings has been changed */ static void language_settings_changed (HildonIMPlugin *plugin, gint index, gint value) { /* empty, sic. */ }
-
void (*completion_language_changed) (HildonIMPlugin *plugin);
completion_language_changed is called whenever the word completion settings has been changed. For further information check GConf directly to get the new settings of word completion.
Plugin loading
A nature of a plugin is that it can be dynamically loaded. In our case as well the HIM plugin is loaded in whenever the user selects it, so in order to support the dynamic loading our plugin has to provide the following three specific functions for Hildon Input Method plugin system:
-
void module_init (GTypeModule *module);
In this function we shall initialize out plugin as a module, that means we need to register the type of GTypeInfo and adding the interface and instance information (GInterfaceInfo) to the module.
void module_init(GTypeModule *module) { static const GTypeInfo type_info = { sizeof(HimsdkVKBClass), NULL, /* base_init */ NULL, /* base_finalize */ (GClassInitFunc) himsdk_vkb_class_init, NULL, /* class_finalize */ NULL, /* class_data */ sizeof(HimsdkVKB), 0, /* n_preallocs */ (GInstanceInitFunc) himsdk_vkb_init, }; static const GInterfaceInfo plugin_info = { (GInterfaceInitFunc) himsdk_vkb_iface_init, NULL, /* interface_finalize */ NULL, /* interface_data */ }; himsdk_vkb_type = g_type_module_register_type(module, GTK_TYPE_WIDGET, "HimsdkVKB", &type_info, 0); g_type_module_add_interface(module, HIMSDK_VKB_TYPE, HILDON_IM_TYPE_PLUGIN, &plugin_info); }
The hildon_im_my_plugin_iface_init function shall register our custom interface functions in HildonIMPluginIface:
/* Standard GTK stuff */ static void himsdk_vkb_iface_init (HildonIMPluginIface *iface) { iface->enable = enable; iface->disable = disable; iface->enter = enter; iface->tab = tab; iface->backspace = backspace; iface->clear = clear; iface->input_mode_changed = input_mode_changed; iface->client_widget_changed = client_widget_changed; iface->save_data = save_data; iface->language = language; iface->mode_a = mode_a; iface->mode_b = mode_b; iface->language_settings_changed = language_settings_changed; iface->settings_changed = settings_changed; return; }
-
void module_exit (void);
This function defines what shall be done when the module is unloaded from the memory.
void module_exit(void) { /* free the dynamically allocated memory */ }
-
HildonIMPlugin* module_create (HildonIMPlugin *plugin);
This function shall create and return the plugin widget to the main UI.
HildonIMPlugin* module_create (HildonIMKeyboard *keyboard) { return HILDON_IM_PLUGIN (himsdk_vkb_new (keyboard)); }
Plugin info
We need to provide some basic information of our plugin to the HIM plugin system so it can keep up what kind of plugins are available in the system. This is done via the following two specific functions:
-
const hildon_im_plugin_info* hildon_im_plugin_get_info(void);
The function shall create hildon_im_plugin_info struct in which we can provide the required information.
... /* Input Method plugin information. * This structure tells the main UI about this plugin */ const hildon_im_plugin_info * hildon_im_plugin_get_info(void) { static const hildon_im_plugin_info info = { "himsdk_vkb_new", /* new func */ "himsdk_vkb", /* name */ "Keyboard (SDK)", /* menu title */ NULL, /* gettext */ TRUE, /* visible in menu */ FALSE, /* cached */ IM_TYPE_KEYBOARD, /* type */ IM_GROUP_WESTERN, /* group */ DEFAULT_PRIORITY, /* priority */ NULL, /* no other plugin in SCV button */ "" /* help page */ }; return &info; } ...
-
gchar** hildon_im_plugin_get_available_languages (gboolean *free)
The function shall return a NULL terminated array of string containing language codes supported by the plugin. Set free to TRUE if HIM main UI should free the returned value if it is no longer going to be used.
/* * This function returns the list of available languages supported * by the plugin. * */ gchar ** hildon_im_plugin_get_available_languages (gboolean *free) { static gchar *list[] = { "en_GB", NULL }; *free = FALSE; return list; }
Interaction with the main user interface
As it was said above our plugin is placed inside the HIM main UI. In this chapter we are going to review how we can interact with it. It is mainly done by calling it's functions as defined in hildon-im-keyboard.h.
Handling input
void hildon_im_keyboard_send_utf8(HildonIMKeyboard *main_ui, const gchar *text);
The plugin can request the main UI to commit a UTF-8 encoded text by calling this function.
void hildon_im_keyboard_send_communication_message(HildonIMKeyboard *main_ui, gint message);
The plugin can use this function to tell the main UI that Enter, Backspace, or Tab virtual buttons are pressed. Simply call this function and pass one of the constants below as the message argument:
- HILDON_IM_CONTEXT_HANDLE_ENTER
- HILDON_IM_CONTEXT_HANDLE_BACKSPACE
- HILDON_IM_CONTEXT_HANDLE_TAB
UI visibility
void hildon_im_keyboard_delayed_hide(HildonIMKeyboard *main_ui);
The plugin can request the main UI to hide itself by calling this function.
gboolean hildon_im_keyboard_get_visibility(HildonIMKeyboard *main_ui);
By calling this function the plugin can get the status of visibility of the main UI.
Get input method state
The following two state reader functions could be very handy in case our plugin receives a state change notification (e.g. language change), because this way we do not need to save all the state information.
HildonIMCommand hildon_im_keyboard_get_autocase_mode(HildonIMKeyboard *main_ui);
The function returns the auto-capitalization mode of the current client widget.
gint hildon_im_keyboard_get_current_language(HildonIMKeyboard *main_ui);
The function returns the current language code.
Common buttons
As we were mentioning it in the Overview chapter we can modify the outlook and even the function of the buttons in the main UI. Although please note that it is recommended not to alter buttons except the Mode A and Mode B buttons! Other buttons may have a hard wired behavior inside the main UI.
If the plugin changes the functionality of a button one might want to reflect this on the UI as well by changing the label of the button. The layout of the buttons can be altered by the following two functions:
-
void hildon_im_keyboard_button_set_label(HildonIMKeyboard *keyboard, gint button_enum, const gchar *label);
With this function the plugin can set the label of a button.
Possible values of button_enum (see. hildon-im-keyboard.h):
BUTTON_TAB
BUTTON_MODE_A
BUTTON_MODE_B
BUTTON_INPUT_MENU
BUTTON_BACKSPACE
BUTTON_ENTER
BUTTON_SPECIAL_CHAR
BUTTON_CLOSE
-
void hildon_im_keyboard_button_set_id(HildonIMKeyboard *self, gint button_enum, const gchar *id);
This function sets a name to a particular button.
Since every input to the HIM main UI is caught by our plugin we are responsible to keep the buttons' state (active or in-active) in sync. We can change, query and toggle the state of a particulare button with the following functions:
-
void hildon_im_keyboard_button_set_active(HildonIMKeyboard *keyboard, gint button_enum, gboolean active);
This function sets the active state of a particular button.
-
gboolean hildon_im_keyboard_button_get_active(HildonIMKeyboard *keyboard, gint button_enum);
This function returns the active state of a particular button.
-
void hildon_im_keyboard_button_set_toggle(HildonIMKeyboard *keyboard, gint button_enum, gboolean toggle);
The plugin can set the toggle state of a particular button with this function.
Miscelenaous button manipulation functions:
-
void hildon_im_keyboard_button_set_menu(HildonIMKeyboard *keyboard, gint button_enum, GtkWidget *menu);
With this function the plugin can attach a menu - which is a GtkWidget - to a particular button.
-
void hildon_im_keyboard_button_set_sensitive(HildonIMKeyboard *keyboard, gint button_enum, gboolean sensitive);
We might not want to use all the buttons defined on the HIM main UI or simply in some states we want to switch off the functionality of a button. In this case by calling this function we can set the sensitivity of a particular button.
-
void hildon_im_keyboard_button_set_repeat(HildonIMKeyboard *keyboard, gint button_enum, gboolean repeat);
This function controls whether a particular button will repeat when pressed for a long time.
Component dependencies
In our plugin these headers shall be included at least:
#include <hildon-im-keyboard.h> #include <hildon-im-plugin.h> #include <hildon-im-protocol.h>
libhildon-input-method-header-sdk-dev and libhildon-input-method-framework-header-sdk-dev packages.
Language Codes
This is the language codes recognized, being af_ZA is 0 and so forth.
af_ZA am_ET ar_AE ar_BH ar_DZ ar_EG ar_IN ar_IQ ar_JO ar_KW ar_LB ar_LY ar_MA ar_OM ar_QA ar_SA ar_SD ar_SY ar_TN ar_YE az_AZ be_BY bg_BG bn_IN br_FR bs_BA ca_ES cs_CZ cy_GB da_DK de_AT de_BE de_CH de_DE de_LU el_GR en_AU en_BW en_CA en_DK en_GB en_HK en_IE en_IN en_NZ en_PH en_SG en_US en_ZA en_ZW eo_EO es_AR es_BO es_CL es_CO es_CR es_DO es_EC es_ES es_GT es_HN es_MX es_NI es_PA es_PE es_PR es_PY es_SV es_US es_UY es_VE et_EE eu_ES fa_IR fi_FI fo_FO fr_BE fr_CA fr_CH fr_FR fr_LU ga_IE gd_GB gl_ES gv_GB he_IL hi_IN hr_HR hu_HU hy_AM id_ID is_IS it_CH it_IT iw_IL ja_JP ka_GE kl_GL ko_KR kw_GB lt_LT lv_LV mi_NZ mk_MK mr_IN ms_MY mt_MT nl_BE nl_NL nn_NO no_NO oc_FR pl_PL pt_BR pt_PT ro_RO ru_RU ru_UA se_NO sk_SK sl_SI sq_AL sr_YU sv_FI sv_SE ta_IN te_IN tg_TJ th_TH ti_ER ti_ET tl_PH tr_TR tt_RU uk_UA ur_PK uz_UZ vi_VN wa_BE yi_US zh_CN zh_HK zh_SG zh_TW
Virtual Keyboard
In case if you want to extent standard Virtual Keyboard layout you need to create a .vkb file. In this chapter we will review how one can create a valid .vkb file.
First of all .vkb files are simple .xml files compiled by an application called gen_vkb that comes with libimlayout package. A vkb file could have more than one keyboard layout, and each layout could have more than one sublayout. Each layout can have individual configuration of the number of keys and rows. All labels and strings used here use utf-8 encoding.
There are three kind of keys supported:
- Normal: This is ordinary key.
- Sliding: This is a key which "slides" when pressed. It has more than one label in a key. The last slided label would be committed.
- Modifier: This is a system modifier key, such as alt, ctrl, esc.
- Multiple: This key can contain more than one key. Each key has it's own label and attributes.
Each key could have these attributes:
- Alpha: Keys marked with this attribute are alpha keys.
- Numeric: Keys marked with this attribute are numeric keys.
- Hexa: Keys marked with this attribute are hexadecimal keys.
- Tele: Keys marked with this attribute are telephone keys.
- Special: Keys marked with this attribute are special character keys.
- Dead: Keys marked with this attribute are dead keys.
- Whitespace: Keys marked with this attribute are whitespace keys.
Virtual Keyboard Layout XML file
This section describes what is inside a virtual keyboard layout XML file.
This visualisation shows how the contents are laid out inside the XML file.
File skeleton
<keyboards file="filename" version="1" lang="language_code" name="Description" wc="word_completion_language_code"> <keysizes> <!-- This provides the list of key sizes --> <size height="35" baseline="26" width="35" margin_top="0" margin_left="0"/> <!-- This is key size number 0 --> <size height="35" baseline="26" width="50" margin_top="0" margin_left="0"/> <!-- This is key size number 1 --> <size height="35" baseline="26" width="55" margin_top="0" margin_left="0"/> <!-- This is key size number 2 --> <size height="55" baseline="41" width="72" margin_top="0" margin_left="0"/> <!-- This is key size number 3 --> <size height="55" baseline="41" width="143" margin_top="0" margin_left="0"/> <!-- This is key size number 4 --> </keysizes> <keyboard layout="NORMAL" default_key_size="0"> <!-- default_key_size refers to keysizes above --> <sublayout type="LOWERCASE" variance_index="1"> <!-- If type is LOWERCASE or UPPERCASE, variance_index is the index of the case. You have to provide different number on each type, eg. if LOWERCASE you have variance_index, the UPPERCASE sublayout should have variance_index set to 0. Otherwise case changing will not work --> <keysection> <!-- This defines new key section in virtual keyboard --> <row> <!-- This defines a new row in virtual keyboard --> <key alpha="ALPHA" size="2">q</key> <!-- size refers to keysizes above --> ... </row> <keysection> </sublayout> ... <!-- Put other sublayout layout here --> </keyboard> ... <!-- Put other keyboard layout here --> </keyboards>
XML DTD
<!ELEMENT keyboards (keysizes, keyboard)> <!ELEMENT keysizes (size)> <!ELEMENT keyboard (sublayout)> <!ELEMENT sublayout (keysection+)> <!ELEMENT keysection (row+)> <!ELEMENT row (key+)> <!ELEMENT key (slide*)> <!ATTLIST keyboards file CDATA #REQUIRED version CDATA #REQUIRED lang CDATA #REQUIRED name CDATA #REQUIRED wc CDATA #IMPLIED > <!ATTLIST size height CDATA #REQUIRED baseline CDATA #REQUIRED width CDATA #REQUIRED margin_top CDATA #REQUIRED margin_left CDATA #REQUIRED > <!ATTLIST keyboard layout (NORMAL|THUMB) default_key_size CDATA #REQUIRED numeric CDATA #FIXED "LOWERCASE" > <!ATTLIST sublayout type (UPPERCASE|LOWERCASE|SINGLE) label CDATA #IMPLIED variance_index CDATA #IMPLIED > <!ATTLIST keysection margin_top CDATA #IMPLIED margin_left CDATA #IMPLIED margin_bottom CDATA #IMPLIED margin_right CDATA #IMPLIED > <!ATTLIST key alpha ALPHA hexa HEXA tele TELE dead DEAD special SPECIAL numeric NUMERIC whitespace WHITESPACE size CDATA #IMPLIED type CDATA #IMPLIED >
Code examples
The following tar.gz package contains the full C source code example for the Hildon Input Methods plugin that was used in this document.
Improve this page