Develop an simple input method

From Fcitx
Revision as of 01:59, 19 November 2021 by Weng Xuetian (talk | contribs)
Jump to navigation Jump to search
Other languages:

This is a step-by-step instruction for writing a Fcitx 5 input method. The same instruction can be used for developing other types of addons, just input method engine is the most complex ones.

Understand the file structure for a Fcitx shared library addon

Fcitx 5 provides a extensible framework for adding new addon types, but shared library support is built in and is the base of all other addon types. So we will only cover shared library addon in this document.

[fcitx install prefix]
|
|- share/fcitx5
|  |
|  |- addons/[addon name].conf
|  |- inputmethod/[input method name 1].conf
|  |  ...
|  |- inputmethod/[input method name n].conf
|
|- lib/fcitx5
   |
   |- [library name].so

Above is the file structure for an input method addon. For other types of addons, files under inputmethod/ is not needed. The file name of [addon name].conf matters and will be used to uniquely reference this specific addon. Fcitx also follows XDG directory standard, so the files under XDG_DATA_DIR/fcitx5 will also be checked. Similarly, file name of configuration file under inputmethod/ also matters and will be the unique name of a certain input method.

Example of [addon name].conf

[Addon]
Name[ca]=Pinyin
Name[da]=Pinyin
Name[de]=Pinyin
Name[he]=פיניין:
Name[ko]=병음
Name[ru]=Пиньинь
Name[zh_CN]=拼音
Name=Pinyin
Category=InputMethod
Version=5.0.8
Library=pinyin
Type=SharedLibrary
OnDemand=True
Configurable=True

[Addon/Dependencies]
0=punctuation

[Addon/OptionalDependencies]
0=fullwidth
1=quickphrase
2=cloudpinyin
3=notifications
4=spell
5=pinyinhelper
6=chttrans
7=imeapi

Example of [input method name].conf

[InputMethod]
Name[ca]=Pinyin
Name[da]=Pinyin
Name[de]=Pinyin
Name[he]=פיניין:
Name[ko]=병음
Name[ru]=Пиньинь
Name[zh_CN]=拼音
Name=Pinyin
Icon=fcitx-pinyin
Label=拼
LangCode=zh_CN
Addon=pinyin
Configurable=True

The file is in a ini-like format, with certain fcitx specific extensions and rules. It also supports XDG Desktop file style I18N for translation.

Use CMake build system

It is your own freedom to pick whatever build system you would d like to use, as long as you produce the correct files. Fcitx 5 provides lots of support with CMake, so using CMake would be the most convenient way to build a Fcitx project. In this document, we will only cover using CMake as build system.

A quick start: Quwei

Quwei input method is an input method that basically allow you type the digit of GB2312 and produce the Chinese Character that matches this code. It used to be supported by Fcitx 4, but not included by Fcitx 5 anymore. Though it is hard to use, it can serve as a good example on how to implement a simple input method for Fcitx 5.

Project skeleton

So let we start with a skeleton of this project.

├── CMakeLists.txt
├── LICENSES
│   └── BSD-3-Clause.txt        # License for this project
├── po                          # Optional I18n
│   ├── CMakeLists.txt
│   ├── fcitx5-quwei.pot
│   ├── LINGUAS
│   └── zh_CN.po
└── src
    ├── CMakeLists.txt
    ├── quwei-addon.conf.in.in  # Addon registration file
    ├── quwei.conf.in           # Input method registration file
    ├── quwei.cpp               # Engine implementation
    └── quwei.h                 # Engine implementation

You may want to check some CMake tutorial to understand some basic CMake usage.

The CMakeLists.txt in the root directory lookup for the dependency.

cmake_minimum_required(VERSION 3.21)
project(fcitx5-quwei)

find_package(Fcitx5Core REQUIRED)
# Setup some compiler option that is generally useful and compatible with Fcitx 5 (C++17)
include("${FCITX_INSTALL_CMAKECONFIG_DIR}/Fcitx5Utils/Fcitx5CompilerSettings.cmake")

add_subdirectory(src)

The src/CMakeLists.txt would looks like

# Make sure it produce quwei.so instead of libquwei.so
add_library(quwei SHARED quwei.cpp)
target_link_libraries(quwei PRIVATE Fcitx5::Core)
set_target_properties(quwei PROPERTIES PREFIX "")
install(TARGETS quwei DESTINATION "${FCITX_INSTALL_LIBDIR}/fcitx5")

# Addon config file
# We need additional layer of conversion because we want PROJECT_VERSION in it.
configure_file(quwei-addon.conf.in quwei-addon.conf)
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/quwei-addon.conf" RENAME quwei.conf DESTINATION "${FCITX_INSTALL_PKGDATADIR}/addon")

# Input Method registration file
install(FILES "quwei.conf" DESTINATION "${FCITX_INSTALL_PKGDATADIR}/inputmethod")

quwei.conf.in would look like

[InputMethod]
# Translatable name of the input method
Name=Quwei
# Icon name
Icon=fcitx-quwei
# A short label that present the name of input method
Label=区
# ISO 639 language code
LangCode=zh_CN
# Match addon name
Addon=quwei
# Whether this input method support customization
# Configurable=True

This file will be loaded as an InputMethodEntry object.

quwei-addon.conf.in looks like

[Addon]
Name=Quwei
Category=InputMethod
Version=@PROJECT_VERSION@
Library=quwei
Type=SharedLibrary
OnDemand=True
Configurable=True

[Addon/Dependencies]
0=punctuation

[Addon/OptionalDependencies]
0=fullwidth
1=quickphrase
2=chttrans


This file will be loaded as an AddonInfo object.

Basic implementation of InputMethodEngine

Version 1 of quwei.h

/*
 * SPDX-FileCopyrightText: 2021~2021 CSSlayer <wengxt@gmail.com>
 *
 * SPDX-License-Identifier: BSD-3-Clause
 *
 */
#ifndef _FCITX5_QUWEI_QUWEI_H_
#define _FCITX5_QUWEI_QUWEI_H_

#include <fcitx/inputmethodengine.h>
#include <fcitx/addonfactory.h>

class QuweiEngine : public fcitx::InputMethodEngineV2 {
    void keyEvent(const fcitx::InputMethodEntry & entry, fcitx::KeyEvent & keyEvent) override;
};

class QuweiEngineFactory : public fcitx::AddonFactory {
    fcitx::AddonInstance * create(fcitx::AddonManager * manager) override {
        FCITX_UNUSED(manager);
        return new QuweiEngine;
    }
};

#endif // _FCITX5_QUWEI_QUWEI_H_

Version 1 of quwei.cpp

/*
 * SPDX-FileCopyrightText: 2021~2021 CSSlayer <wengxt@gmail.com>
 *
 * SPDX-License-Identifier: BSD-3-Clause
 *
 */
#include "quwei.h"

void QuweiEngine::keyEvent(const fcitx::InputMethodEntry& entry, fcitx::KeyEvent& keyEvent)
{
    FCITX_UNUSED(entry);
    FCITX_INFO() << keyEvent.key() << " isRelease=" << keyEvent.isRelease();
}

FCITX_ADDON_FACTORY(QuweiEngineFactory);

When implement a Fcitx addon, it should be a sub-class of AddonInstance. The instantiation of AddonInstance is done via a AddonFactory. InputMethodEngineV2 is a sub-class of AddonInstance. This class needs to be used when implementing an input method addon.

The minimum implantation of an input method engine only need to contain the implementation of the keyEvent function.

Here, we use a iostream like macro FCITX_INFO() to write every key we pressed to the log.

Assume the prefix your fcitx installation is /usr. The command to build this project would be:

mkdir -p build
cd build
cmake .. -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Debug # use Debug for easy debugging with gdb
make # or ninja, depending on your system
sudo make install # or sudo ninja install

If everything works fine, the install command should print out something like:

-- Install configuration: "Debug"
-- Installing: /usr/lib/fcitx5/quwei.so
-- Installing: /usr/share/fcitx5/addon/quwei.conf
-- Installing: /usr/share/fcitx5/inputmethod/quwei.conf

Now you can restart fcitx5 with fcitx5 -rd and add Quwei to the your configuration with Configtool (Fcitx 5).

After switching to Quwei, the key press in application will make Fcitx 5 print out something like:

I2021-11-16 12:29:32.352702 quwei.cpp:12] Key(f states=0) isRelease=1
I2021-11-16 12:29:32.389935 quwei.cpp:12] Key(s states=0) isRelease=0
I2021-11-16 12:29:32.413689 quwei.cpp:12] Key(d states=0) isRelease=0
I2021-11-16 12:29:32.497661 quwei.cpp:12] Key(s states=0) isRelease=1
I2021-11-16 12:29:32.498021 quwei.cpp:12] Key(f states=0) isRelease=0
I2021-11-16 12:29:32.523816 quwei.cpp:12] Key(a states=0) isRelease=1
I2021-11-16 12:29:32.524051 quwei.cpp:12] Key(d states=0) isRelease=1
I2021-11-16 12:29:32.704919 quwei.cpp:12] Key(f states=0) isRelease=1
I2021-11-16 12:29:32.705006 quwei.cpp:12] Key(d states=0) isRelease=0
I2021-11-16 12:29:32.833024 quwei.cpp:12] Key(d states=0) isRelease=1
I2021-11-16 12:29:34.633936 quwei.cpp:12] Key(Control_L states=0) isRelease=0
I2021-11-16 12:29:35.053817 quwei.cpp:12] Key(Control+C states=4) isRelease=0
I2021-11-16 12:29:35.165617 quwei.cpp:12] Key(Control+C states=4) isRelease=1
I2021-11-16 12:29:35.348654 quwei.cpp:12] Key(Control+Control_L states=4) isRelease=1

So you know your input method engine is now working.

WIP