Copyright © 2011 CSSlayer
Abstract
Fcitx is an open source input method released under GPLv2.
Table of Contents
List of Tables
Table of Contents
All Input Method Frameworks have a very simple architecture, basically they receive Keyboard Event from Application, which can be also called "Client", then try to do some job with this Keyboard Event, like simply letting it pass, or to block it and commit a string to the application.
The way that Fcitx handles the keyboard event can be separated into 4 phases, PreInput, DoInput, PostInput, and Process Hotkey. All the Input Method, such as "Chinese Pinyin", will be called at the DoInput Phase.
Fcitx Addon can be separated into 4 categories, Frontend, Input Method, Module, and User Interface. Frontend receives key event and pass it to Fcitx; Input Method helps people input their Language with keyboard; Module can do anything via registering hooks; User interface will display the all necessary elements on the screen.
Table of Contents
Each addon needs a configuration file. A common configuration file is like this one, fcitx-pinyin.conf.in.
[Addon] Name=fcitx-pinyin _GeneralName=Pinyin _Comment=Simple Pinyin support for Fcitx Category=InputMethod Enabled=True Library=fcitx-pinyin.so Type=SharedLibrary SubConfig=Shuang Pin Schema:native:pinyin/sp.dat,Symbol:native:pinyin/pySym.mb,fcitx:domain
All Fcitx config file are using ini style format, GeneralName and Comment are I18NString that can be translated into other languages. Here is a example configuration file of fcitx-pinyin merged with translation.
[Addon] Name=fcitx-pinyin GeneralName=Pinyin GeneralName[zh_CN]=拼音 Comment=Simple Pinyin support for Fcitx Comment[zh_CN]=Fcitx 简单的拼音支持 Category=InputMethod Enabled=True Library=fcitx-pinyin.so Type=SharedLibrary SubConfig=Shuang Pin Schema:native:pinyin/sp.dat,Symbol:native:pinyin/pySym.mb,fcitx:domain
fcitx_add_addon_conf_file(CONFFILENAME) can be used in CMakeLists.txt to automatically merge the translation into this file. And use extract_fcitx_addon_conf_postring() to extract strings of those conf.in files from POTFILES.in.
For addon which type is SharedLibrary, each different category requires export a different symbol.
FcitxFrontend frontend; /* for frontend */ FcitxIMClass ime; /* for input method */ FcitxModule module; /* for module */ FcitxUI ui; /* for user interface */
Each of this symbol consist a set of callback functions.
If addon need a multi option configuration file, addon should provides a configuration description file first. The filename need to same with the addon name.
If addon requires a series of sub-configuration file, or some simple text file, the addon configuration file need to contains SubConfig key to list wildcard file name.
[Addon] Name=fcitx-table _GeneralName=Table _Comment=Table Input Method for Fcitx Category=InputMethod Enabled=True Library=fcitx-table.so Type=SharedLibrary Dependency=fcitx-pinyin,fcitx-punc SubConfig=Table:configfile:table/*.conf:table.desc,fcitx:domain
fcitx-table has several table configuration files, SubConfig is defined in following format.
SubConfig=SubConfig1,Subconfig2,...,Subconfig3
Each SubConfig is defined in following format:
Name:type:description
Currently, type can be "configfile", "native", or "domain". "native" defines a simple text filename in description, which cannot have wildcard in filename. Description of "configfile" contains filename with wildcard and configuration description file name. "domain" defines gettext domain of this module. Names of "configfile" and "native" are translatable, translation should live in the "domain" defined by subconfig.
Table of Contents
Frontend is a library that communicates with the client, and processes the key input from client, and sends string to client.
Currently, there are 13 functions need to be implemented for a Frontend. (Defined in fcitx/frontend.h)
void* (*Create)(struct _FcitxInstance*, int frontendindex); boolean (*Destroy)(void *arg); void (*CreateIC)(void* arg, FcitxInputContext*, void* priv); boolean (*CheckIC)(void* arg, FcitxInputContext* arg1, void* arg2); void (*DestroyIC) (void* arg, FcitxInputContext *context); void (*EnableIM)(void* arg, FcitxInputContext* arg1); void (*CloseIM)(void* arg, FcitxInputContext* arg1); void (*CommitString)(void* arg, FcitxInputContext* arg1, char* arg2); void (*ForwardKey)(void* arg, FcitxInputContext* arg1, FcitxKeyEventType event, FcitxKeySym sym, unsigned int state); void (*SetWindowOffset)(void* arg, FcitxInputContext* ic, int x, int y); void (*GetWindowPosition)(void* arg, FcitxInputContext* ic, int* x, int* y); void (*UpdatePreedit)(void* arg, FcitxInputContext* ic); void (*UpdateClientSideUI)(void* arg, FcitxInputContext* ic);
Frontend needs to manage its input contexts. A client at least has one input context with some private data, which can identify the specific input context. The CreateIC and DestroyIC are functions that create and destroy private input context data. A input context usually has a private unique id, which will be used in CheckIC to find the specific input context.
Some input contexts hold a state at the client side, which indicates whether this input context is active or not. EnableIM and CloseIM will be called when fcitx want to enable or disable input context from Fcitx side.
CommitString and ForwardKey will be called when fcitx wants to commit string to client window, or forward a key event to client window.
SetWindowOffset and GetWindowOffset is to set and get the client cursor position.
UpdatePreedit and UpdateClientSideUI will be called when UI elements, like preedit string, or any ui element update. These two function are especially for input context with capacity CAPACITY_PREEDIT or CAPACITY_CLIENT_SIDE_UI. Only when a input context claims to support opposite capacity, these two function will be used.
Table of Contents
Input Methods are most important components of Fcitx. It will process key event, and update the preedit string and set candidate words.
Every single input method addon can add more than one input method to Fcitx. Like fcitx-table, it will load table configuration files first, then add corresponding table input method.
Input methods are registered via FcitxRegisterIM. It will provide two strings, one is Name, which will be displayed directly to user. If addon want to have Name translated, it should do the translation before pass Name to FcitxRegisterIM. Icon Name is a property that will be used in UI display, the actual usage for Icon Name depends on the UI Module implementation. For fcitx-classic-ui, it will be used as the original png file name; for fcitx-kimpanel-ui, it will be used with a "fcitx-" prefix and look for icon in system icon themes.
Init will be called when a input method being switched to. ResetIM is usually being called in ResetInput. DoInput is the key function, which process the key event. And if the return state is IRV_DISPLAY_CANDWORDS, the GetCandWords will be called. Save will be called when fcitx want to save the data, usually when logout.
Priority control the order of input methods, each IM should take care of it's own priority value. If a priority value less than or equal to zero, this input method will not be registered to fcitx.
Fcitx changes the behavior of Candidate Words handling in 4.1.0. Input method should provide complete candidate words, instead of only a single page of candidate words. The paging function is implemented by Fcitx, not by every single input method anymore.
For most CJK input methods, they don't need to implement a separate punctuation function, since there is already a shared punctuation implementation. If there is similar function needed for input method, it should not be implemented in input method, but in a separate module.
Currently fcitx only supports shared library input method, some people might think it's better to implement input method in a separate process, DBus can be used in this case, but there is no easy way to do this currently.
Table of Contents
Event based Modules in Fcitx are using fd to notify the main-loop whether there is new event or not. Following definition is defined in fcitx/module.h.
typedef struct _FcitxModule { /** * @brief construction function */ void* (*Create)(struct _FcitxInstance* instance); /** * @brief set main loop watch fd, no need to implement */ void (*SetFD)(void*); /** * @brief main loop event handle, no need to implement */ void (*ProcessEvent)(void*); /** * @brief destruct function */ void (*Destroy)(void*); /** * @brief reload config, no need to implement */ void (*ReloadConfig)(void*); } FcitxModule;
After select from multiple fd, the ProcessEvent will be called and modules will process there own event.
Module need to update rfds, wfds, efds in FcitxInstance and set the maxfd member in FcitxInstance, After modules process all events, all three fd_set will be set by FD_ZERO and SetFD will be called to set them.
Except Event based modules, all other misc module will use built-in hook to interference the key event processing.
Currently, there are following usable hooks to interference with key event processing.
void RegisterPreInputFilter(struct _FcitxInstance* instance, KeyFilterHook) ; void RegisterPostInputFilter(struct _FcitxInstance* instance, KeyFilterHook); void RegisterOutputFilter(struct _FcitxInstance* instance, StringFilterHook); void RegisterHotkeyFilter(struct _FcitxInstance* instance, HotkeyHook); void RegisterResetInputHook(struct _FcitxInstance* instance, FcitxIMEventHook value); void RegisterTriggerOnHook(struct _FcitxInstance* instance, FcitxIMEventHook value); void RegisterTriggerOffHook(struct _FcitxInstance* instance, FcitxIMEventHook value); void RegisterInputFocusHook(struct _FcitxInstance* instance, FcitxIMEventHook value); void RegisterInputUnFocusHook(struct _FcitxInstance* instance, FcitxIMEventHook value); void RegisterUpdateCandidateWordHook(struct _FcitxInstance* instance, FcitxIMEventHook value);
As mentioned in Architecture before, the key event processing is separated into 4 phases. PreInput are called before Input Method's DoInput function. PostInput will be called after Input Method's DoInput function, if the key event doesn't get processed yet.
Hotkey will be processed at last, but it will not be blocked even if input context state is ENG. That's the main difference to PreInput and PostInput. If an addon only have a status need to be toggled, it should use hotkey instead of Input Filter.
All input state will be set to default if ResetInput is called. Reset input is always usable via hotkey escape.
In order to reusing some common code, Fcitx provides a mechanism to do inter module function call. Although it's call inter module function, but all kinds of addon can provide such functions.
If a module wants to register some inter module function, first it need a header defines correct macro. Here is part of macro defined in fcitx/module/x11stuff.h
#define FCITX_X11_NAME "fcitx-x11" #define FCITX_X11_GETDISPLAY 0 #define FCITX_X11_GETDISPLAY_RETURNTYPE Display* #define FCITX_X11_ADDXEVENTHANDLER 1 #define FCITX_X11_ADDXEVENTHANDLER_RETURNTYPE void #define FCITX_X11_REMOVEXEVENTHANDLER 2 #define FCITX_X11_REMOVEXEVENTHANDLER_RETURNTYPE void #define FCITX_X11_FINDARGBVISUAL 3 #define FCITX_X11_FINDARGBVISUAL_RETURNTYPE Visual* #define FCITX_X11_INITWINDOWATTR 4 #define FCITX_X11_INITWINDOWATTR_RETURNTYPE void #define FCITX_X11_SETWINDOWPROP 5 #define FCITX_X11_SETWINDOWPROP_RETURNTYPE void #define FCITX_X11_GETSCREENSIZE 6 #define FCITX_X11_GETSCREENSIZE_RETURNTYPE void #define FCITX_X11_MOUSECLICK 7 #define FCITX_X11_MOUSECLICK_RETURNTYPE void #define FCITX_X11_ADDCOMPOSITEHANDLER 8 #define FCITX_X11_ADDCOMPOSITEHANDLER_RETURNTYPE void
fcitx_add_addon_header is provided by FcitxMacro.cmake to get this header installed to the correct place.
All functions have the same interface, like this:
void* X11GetDisplay(void* arg, FcitxModuleFunctionArg args);
For any addon need to call a inter module function it should add that module as dependency in the configuration file. Addon is loaded or not can be also checked at runtime via GetAddonByName.
If addon want to call a function in other module, it should use InvokeFunction macro defined in fcitx/module.h.
InvokeFunction(instance, FCITX_X11, GETDISPLAY, arg);
FcitxModuleFunctionArg provides ten void* slot for passing argument, which is enough for passing arguments.
If an addon want to use DBus, it can use interface in fcitx-dbus Module. But pay attention, fcitx doesn't use glib main loop, so the addon should only use low-level dbus api.
Table of Contents
Unlike Frontend, Input Method, and Module, there can be only one user interface running. way.
If there is no special requirement, it's not recommended to implement a new user interface. There are already three existing User Interface Addons in Fcitx, one is fcitx-classic-ui, which provides skin support and uses cairo, pango, and xlib for rendering; another one is fcitx-kimpanel-ui, which uses kimpanel protocol via DBus, and it can provides native look-feeling under with kdeplasma-addons-kimpanel, and even more, can provides advance skin support with kimtoy; The third one is fcitx-light-ui, which only use Xft and Xlib for rendering, it intends to require least dependencies and achieve better rendering spend on old machine.
In order to implement a new user interface, it would be better to use a kimpanel compatible protocol, but not to write a fcitx one, since kimpanel can also benefits other input framework, scim and ibus.
Fcitx provides several abstraction of user interface element, in order to display them on different user interface implementation. Basically, there is a input window, with four text field.
Table 1. Standard Input Window Layout
Auxiliary Text Up | Preedit String |
Auxiliary Text Down | Candidate Words |
There is also a user interface element called status, basically it defines a switch button. The icon name rule can be defined by user interface itself. (Defined in fcitx/ui.h)
/** * @brief Fcitx Status icon to be displayed on the UI **/ typedef struct _FcitxUIStatus { /** * @brief status name, will not displayed on the UI. **/ char name[MAX_STATUS_NAME + 1]; /** * @brief short desription for this status, can be displayed on the UI **/ char shortDescription[MAX_STATUS_SDESC + 1]; /** * @brief long description for this status, can be displayed on the UI **/ char longDescription[MAX_STATUS_LDESC + 1]; /** * @brief toogle function **/ void (*toggleStatus)(void *arg); /** * @brief get current value function **/ boolean (*getCurrentStatus)(void *arg); /** * @brief private data for the UI implementation **/ void *priv; /** * @brief extra argument for tooglefunction **/ void* arg; } FcitxUIStatus;
Short Description and Long Description is tend to be display on the user interface, the addon should have it translated before register the status.
Another user interface element is a menu, which currently only support by fcitx-classic-ui and fcitx-light-ui. Due to the limitation of kimpanel, it cannot support fcitx style menu currently. (Defined in fcitx/ui.h)
/** * @brief a menu entry in a menu. **/ typedef struct _MenuShell { /** * @brief The displayed string **/ char tipstr[MAX_MENU_STRING_LENGTH + 1]; /** * @brief Can be used by ui to mark it's selected or not. **/ int isselect; /** * @brief The type of menu shell **/ MenuShellType type; /** * @brief the submenu to this entry **/ struct _FcitxUIMenu *subMenu; } MenuShell; /** * @brief Fcitx Menu Component, a UI doesn't need to support it, * This struct is used by other module to register a menu. **/ typedef struct _FcitxUIMenu { /** * @brief shell entries for this menu **/ UT_array shell; /** * @brief menu name, can be displayed on the ui **/ char name[MAX_MENU_STRING_LENGTH + 1]; /** * @brief you might want to bind the menu on a status icon, but this is only a hint, * depends on the ui implementation **/ char candStatusBind[MAX_STATUS_NAME + 1]; /** * @brief update the menu content **/ UpdateMenuShellFunction UpdateMenuShell; /** * @brief function for process click on a menu entry **/ MenuActionFunction MenuAction; /** * @brief private data for this menu **/ void *priv; /** * @brief ui implementation private **/ void *uipriv; /** * @brief this is sub menu or not **/ boolean isSubMenu; /** * @brief mark of this menu **/ int mark; } FcitxUIMenu;
Module who want to use menu or status icon should use RegisterStatus and RegisterMenu function. A User interface in order to support status icon or menu, should implement UpdateStatus, RegisterStatus, RegisterMenu callbacks.
There are some special case, which the common user interface cannot works. Input method in fbterm is a good case. In order to support this, frontend should correct set the CAPACITY_CLIENT_SIDE_UI and implements UpdateClientSideUI callback. If anytime the user interface get updated, UpdateClientSideUI will be called and the client side can draw the user interface itself.
Table of Contents
Fcitx have two prefixes of directory, one is named "PREFIX", which is configured during building, the other is "USERPREFIX", which is based on XDG environment variable. Normally, PREFIX is /usr/share/fcitx, and USERPREFIX is ~/.config/fcitx
In order to let Fcitx Configuration Tool find configuration files automatically, and to let Fcitx load addon correctly, addon need to put its configuration files under correct path with correct name.
Each addon has a configuration file, and it should be installed under PREFIX/addon/. This configuration file defines its name, category, library name, type, and priority. Filename need to be addon-name.conf.
If addon can be configured with various options, it need a configuration description file, which should be installed under PREFIX/configdesc/, with name [addon-name].desc. The corresponding configuration file should be placed under USERPREFIX/conf/, with the name [addon-name].config.
There are two case If addon has some data file to load/save. If it only has one file, it can place the data file under PREFIX/data. If addon have more than one data file, it would be better to create a separate directory to place them.
A Ini file in Fcitx contains at least one group, each group contains several options. A group is defined in "[GroupName]" format, an options is defined by "Key=Value".
All configuration file should have a configuration description file in following format.
[GroupName/OptionName] Type=Option Type Description=Description String DefaultValue=value [DescriptionFile] LocaleDomain=Domain
DefaultValue is optional, but if configuration file need to be generated from nothing, all options should have a default value.
The option value type can be "File", "Font", "Enum", "String", "I18NString", "Boolean", "Color", "Integer".
All the types will be display in the configuration tool in corresponding way, "Boolean" will be a checkbox, "String" will be a textbox, etc.
"Description" is translatable, and the string should lies in the defined domain.