# How Karukan Implements the fcitx5 Linux Frontend Using C FFI and C++ Addons

> Learn how Karukan uses C FFI and C++ addons to implement the fcitx5 Linux frontend, seamlessly integrating Rust with the IME logic without duplication. Explore the technical details.

- Repository: [Hitoshi Togasaki/karukan](https://github.com/togatoga/karukan)
- Tags: deep-dive
- Published: 2026-07-03

---

**Karukan bridges the fcitx5 Linux input framework to a Rust core engine through a thin C FFI layer, allowing a C++ addon to forward key events and synchronize UI state without duplicating IME logic.**

The open-source Japanese IME Karukan (`togatoga/karukan`) delivers a native Linux input experience by integrating with the fcitx5 framework. This integration relies on a carefully designed boundary between Rust and C++ code, using a C FFI bridge to maintain memory safety while achieving low-latency key processing. Understanding this architecture reveals how modern Rust-based input methods can plug into existing Linux input frameworks.

## Architectural Overview

Karukan’s fcitx5 integration consists of three tightly-coupled layers that keep the heavy conversion logic in Rust while letting the C++ side handle fcitx5-specific UI contracts:

| Layer | Language | Role |
|------|----------|------|
| **Rust engine** | Rust | Core IME logic lives in `karukan-im`. An opaque handle (`*mut KarukanEngine`) is exposed through a C-compatible FFI. |
| **C FFI bridge** | Rust → C | The `karukan-fcitx5/src/ffi` module implements a thin C API that the C++ addon can call. It converts Rust data (UTF-8 strings, candidate structures, timing information) into C-friendly types (null-terminated `char*`, `uint32_t`, `CString`). |
| **C++ addon** | C++ | The fcitx5 addon ([`karukan-fcitx5/fcitx5-addon/src/karukan.cpp`](https://github.com/togatoga/karukan/blob/main/karukan-fcitx5/fcitx5-addon/src/karukan.cpp)) links against the generated static library, receives key events from fcitx5, forwards them to the Rust engine via the C API, and updates the fcitx5 UI (preedit, candidates, auxiliary text). |

## The C FFI Bridge in [`karukan-fcitx5/src/ffi/mod.rs`](https://github.com/togatoga/karukan/blob/main/karukan-fcitx5/src/ffi/mod.rs)

The Rust side of the FFI defines an opaque `KarukanEngine` struct that encapsulates the engine state and several cache structures. These caches avoid allocating memory on every C call, which is critical for performance when the C++ addon polls for UI updates.

### Opaque Engine Handle and Cache Strategy

The bridge holds an `InputMethodEngine` instance alongside user settings and cache structs for preedit text, candidates, commits, and auxiliary text:

```rust
/// Opaque handle to an IME engine instance
pub struct KarukanEngine {
    engine: InputMethodEngine,
    settings: Settings,
    preedit: PreeditCache,
    candidates: CandidateCache,
    commit: CommitCache,
    aux: AuxCache,
    last_conversion_ms: u64,
    last_process_key_ms: u64,
}

```

Helper macros `ffi_ref!` and `ffi_mut!` guard against null pointers coming from C code, ensuring that the Rust side never dereferences invalid handles passed by the addon.

### Applying Engine Actions Safely

When the Rust engine returns an `EngineAction` (such as updating the preedit or committing text), the FFI bridge applies the action and fills the corresponding cache. For example, processing an `UpdatePreedit` action converts the caret position from character offsets to byte offsets and stores a null-terminated `CString`:

```rust
EngineAction::UpdatePreedit(preedit) => {
    let caret_bytes = /* convert caret chars → byte offset */;
    self.preedit.caret_bytes = caret_bytes as u32;
    self.preedit.text = CString::new(preedit.text()).unwrap_or_default();
    self.preedit.dirty = true;
}

```

## Exported C API Surface

The C header at [`karukan-fcitx5/include/karukan.h`](https://github.com/togatoga/karukan/blob/main/karukan-fcitx5/include/karukan.h) declares the stable interface used by the C++ addon. The following functions form the contract between the Rust core and the fcitx5 frontend:

- **`karukan_engine_new()`** – Allocates and initializes a new `KarukanEngine` instance.
- **`karukan_engine_free(*mut KarukanEngine)`** – De-allocates the engine and persists learning data.
- **`karukan_engine_process_key(*mut KarukanEngine, uint32_t keysym, uint32_t state, int is_release)`** – Forwards a key event; returns non-zero if the key was consumed.
- **`karukan_engine_has_preedit(*mut KarukanEngine)`** – Checks whether a preedit string is available.
- **`karukan_engine_get_preedit` / `get_preedit_len` / `get_preedit_caret`** – Reads from `PreeditCache` to provide the preedit text, length, and caret byte offset.
- **`karukan_engine_has_candidates` / `get_candidate_count` / `get_candidate` / `get_candidate_description`** – Queries the candidate list and per-candidate descriptions from `CandidateCache`.
- **`karukan_engine_has_commit` / `get_commit` / `get_commit_len`** – Returns text that the engine wants to commit.
- **`karukan_engine_has_aux` / `get_aux` / `get_aux_len`** – Returns auxiliary (reading-hint) text.
- **`karukan_engine_set_surrounding_text`** – Provides left-hand context for accurate conversion when the editor supports surrounding text.

## The C++ Addon in [`karukan-fcitx5/fcitx5-addon/src/karukan.cpp`](https://github.com/togatoga/karukan/blob/main/karukan-fcitx5/fcitx5-addon/src/karukan.cpp)

The fcitx5 addon follows the standard fcitx5 input method pattern, implementing `InputMethodEngine` and managing `InputContext` state through a custom `KarukanState` class.

### State Management and Lifecycle

Each input context owns a `KarukanState` that holds a raw pointer to the Rust engine (`rustEngine_`). When the state is created, it allocates the Rust side via the FFI:

```cpp
KarukanState::KarukanState(KarukanEngine* engine, InputContext* ic) : engine_(engine), ic_(ic) {
    rustEngine_ = karukan_engine_new();
}

```

On deactivation, the addon calls `karukan_engine_free` to clean up resources and trigger learning persistence.

### Key Event Translation

When fcitx5 delivers a key event, the addon translates the fcitx5 `KeyEvent` into the three-argument C API (`keysym`, `state`, `is_release`). Modifier bits are mapped to the constants defined in the addon header:

```cpp
uint32_t state = 0;
if (keyEvent.key().states().test(KeyState::Shift)) state |= kShiftMask;
if (keyEvent.key().states().test(KeyState::Ctrl)) state |= kControlMask;
if (keyEvent.key().states().test(KeyState::Alt)) state |= kAltMask;
if (keyEvent.key().states().test(KeyState::Super)) state |= kSuperMask;

int isRelease = keyEvent.isRelease() ? 1 : 0;
int consumed = karukan_engine_process_key(rustEngine_, keysym, state, isRelease);
if (consumed) keyEvent.filterAndAccept();

```

If the Rust engine consumes the key, the addon marks the event as filtered and accepted, preventing further processing by other fcitx5 components.

### UI Synchronization and Candidate Rendering

After processing a key, the addon calls `updateUI()`, which queries the Rust engine through the getter functions and mirrors the data into fcitx5 UI components:

- **Preedit** – Constructed with underline formatting and cursor positioning based on `karukan_engine_get_preedit_caret`.
- **Auxiliary text** – Retrieved via `karukan_engine_get_aux` and shown above the candidate list.
- **Candidates** – The `KarukanCandidateList` class populates itself by iterating over the candidate count and calling `karukan_engine_get_candidate` and `karukan_engine_get_candidate_description`:

```cpp
void KarukanCandidateList::updateCandidates(::KarukanEngine* rustEngine) {
    uint32_t count = karukan_engine_get_candidate_count(rustEngine);
    uint32_t cursor = karukan_engine_get_candidate_cursor(rustEngine);
    for (uint32_t i = 0; i < count; i++) {
        const char* text = karukan_engine_get_candidate(rustEngine, i);
        const char* desc = karukan_engine_get_candidate_description(rustEngine, i);
        append<KarukanCandidateWord>(engine_, std::move(candidateText), i, description);
    }
    setGlobalCursorIndex(static_cast<int>(cursor));
}

```

For fcitx5 versions 5.1.9 and later, descriptions are rendered as right-side comments using `setComment`; older versions concatenate the description to the candidate text.

## End-to-End Data Flow

1. **Initialization** – When the user switches to Karukan, fcitx5 creates a `KarukanState`, which calls `karukan_engine_new()` to allocate the Rust engine.
2. **Key event** – Each key press is translated to a keysym and modifier mask, then passed to `karukan_engine_process_key`. The Rust engine returns a list of `EngineAction`s (e.g., update preedit, show candidates).
3. **Caching** – The C FFI bridge stores the results in the per-field caches (`PreeditCache`, `CandidateCache`, etc.) without allocating on the C heap.
4. **UI update** – The C++ addon queries those caches through the exported getter functions and populates fcitx5 UI widgets (preedit, auxiliary text, candidate window).
5. **Commit and reset** – When the engine signals a commit, the addon calls `ic_->commitString()` with the text from `karukan_engine_get_commit`, then clears the panel; on deactivation, `karukan_engine_save_learning` persists the neural model's state.

## Summary

- **Three-layer architecture** separates Rust conversion logic from C++ fcitx5 plumbing via a C FFI bridge.
- **Opaque handles** (`*mut KarukanEngine`) keep Rust memory safe while exposing a minimal C API in [`karukan-fcitx5/include/karukan.h`](https://github.com/togatoga/karukan/blob/main/karukan-fcitx5/include/karukan.h).
- **Zero-copy caching** in the FFI layer (`PreeditCache`, `CandidateCache`) prevents repeated allocations during UI polling.
- **Key event translation** maps fcitx5 modifier states to bitmasks consumed by `karukan_engine_process_key`.
- **Lifecycle hooks** ensure the Rust engine receives surrounding text for context and persists learning data on deactivation.

## Frequently Asked Questions

### What is the role of the C FFI bridge in Karukan?

The C FFI bridge acts as a language boundary that exposes the Rust `InputMethodEngine` to C code. It manages an opaque handle (`KarukanEngine`) and caches results (preedit, candidates, commits) so that the C++ addon can query UI state without triggering Rust allocations on every call.

### How does the C++ addon communicate with the Rust engine?

The addon links against the static library produced by the `karukan-fcitx5` crate and calls the C functions declared in [`karukan.h`](https://github.com/togatoga/karukan/blob/main/karukan.h). It forwards key events via `karukan_engine_process_key` and retrieves UI data through getter functions like `karukan_engine_get_preedit` and `karukan_engine_get_candidate`.

### Why does Karukan use caching in the FFI layer?

Caching structures (`PreeditCache`, `CandidateCache`, etc.) store converted strings and offsets as C-compatible types (null-terminated `char*`, `uint32_t`). This design prevents the C++ addon from triggering Rust heap allocations during UI refresh cycles, ensuring low latency when fcitx5 polls for preedit or candidate updates.

### Where is the fcitx5 addon source code located?

The C++ addon implementation resides in [`karukan-fcitx5/fcitx5-addon/src/karukan.cpp`](https://github.com/togatoga/karukan/blob/main/karukan-fcitx5/fcitx5-addon/src/karukan.cpp), with its build configuration in [`karukan-fcitx5/fcitx5-addon/CMakeLists.txt`](https://github.com/togatoga/karukan/blob/main/karukan-fcitx5/fcitx5-addon/CMakeLists.txt). The C FFI bridge is located in [`karukan-fcitx5/src/ffi/mod.rs`](https://github.com/togatoga/karukan/blob/main/karukan-fcitx5/src/ffi/mod.rs), and the shared C header is at [`karukan-fcitx5/include/karukan.h`](https://github.com/togatoga/karukan/blob/main/karukan-fcitx5/include/karukan.h).