# How HallelujahIM Integrates with the macOS InputMethodKit Framework

> Learn how HallelujahIM integrates with the macOS InputMethodKit framework. Discover its use of IMKInputController IMKCandidates setMarkedText and insertText for efficient Chinese input.

- Repository: [dongyuwei/hallelujahim](https://github.com/dongyuwei/hallelujahim)
- Tags: how-to-guide
- Published: 2026-02-28

---

**HallelujahIM implements a Cocoa input method plug-in by subclassing `IMKInputController` to receive system keyboard events, using `IMKCandidates` to display Chinese character suggestions, and calling `setMarkedText:` and `insertText:` to manage composition and text commitment.**

HallelujahIM is an open-source Chinese input method for macOS hosted at `dongyuwei/hallelujahim`. The project demonstrates how to build a fully functional IME by leveraging the **macOS InputMethodKit framework**, Apple's official API for creating custom input methods. By implementing the required IMK protocols and lifecycle methods, HallelujahIM intercepts keystrokes, generates candidates, and commits text to host applications.

## Core Architecture: Subclassing IMKInputController

The foundation of any InputMethodKit plug-in is a controller class that inherits from `IMKInputController`. In [`src/InputController.h`](https://github.com/dongyuwei/hallelujahim/blob/main/src/InputController.h), HallelujahIM declares the `InputController` interface:

```objc
#import <InputMethodKit/InputMethodKit.h>
@interface InputController : IMKInputController { … }
@end

```

By subclassing `IMKInputController`, HallelujahIM gains access to the IMK lifecycle and event-handling hooks. The controller maintains references to the current client, a conversion engine for candidate generation, and the shared candidate window.

## Handling Keyboard Events with InputMethodKit

### Registering for Event Types

InputMethodKit requires the controller to declare which events it wants to receive. In `src/InputController.mm`, the `recognizedEvents:` method returns a bitmask specifying key-down and modifier-change events:

```objc
- (NSUInteger)recognizedEvents:(id)sender {
    return NSEventMaskKeyDown | NSEventMaskFlagsChanged;
}

```

This registration ensures macOS forwards keystrokes to HallelujahIM while ignoring events the IME does not handle.

### Processing Keystrokes in handleEvent:client:

The `handleEvent:client:` method serves as the main entry point for input processing. When the user types alphabetic characters, the controller appends them to the composition buffer and triggers candidate updates:

```objc
if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) {
    [self originalBufferAppend:characters client:sender];
    [sharedCandidates updateCandidates];
    [sharedCandidates show:kIMKLocateCandidatesBelowHint];
    return YES;
}

```

This implementation demonstrates how HallelujahIM uses InputMethodKit to intercept raw keystrokes, buffer them, and request candidate display.

## Managing Candidates with IMKCandidates

### The Shared Candidates Window

HallelujahIM leverages the `IMKCandidates` class to present the Chinese character selection window. A global `sharedCandidates` object (declared in `InputController.mm`) manages the candidate UI throughout the application lifecycle.

### Updating and Displaying Suggestions

When the conversion engine generates candidates, the controller updates the shared instance and controls visibility:

```objc
[sharedCandidates updateCandidates];
[sharedCandidates show:kIMKLocateCandidatesBelowHint];

```

Navigation methods such as `moveUp:`, `moveDown:`, and `clearSelection` allow the controller to respond to arrow keys and digit selections, updating the composition buffer before final commitment.

## Composition and Text Commitment

### Rendering Pre-edit Text with setMarkedText:

While the user types, HallelujahIM displays the raw input as "marked" text—the standard IME visual feedback showing text under composition. The `showPreeditString:` method constructs an attributed string and calls the client's `setMarkedText:selectionRange:replacementRange:`:

```objc
- (void)showPreeditString:(NSString *)input {
    NSDictionary *attrs = [self markForStyle:kTSMHiliteSelectedRawText
                                   atRange:NSMakeRange(0, input.length)];
    NSAttributedString *attrString = [[NSAttributedString alloc] initWithString:input
                                                                    attributes:attrs];
    [_currentClient setMarkedText:attrString
                  selectionRange:NSMakeRange(input.length, 0)
                replacementRange:NSMakeRange(NSNotFound, NSNotFound)];
}

```

This integration with InputMethodKit ensures the pre-edit text appears correctly in any Cocoa or Carbon application.

### Committing Final Text with insertText:

When the user selects a candidate or presses space to commit, the controller finalizes the composition by calling `insertText:replacementRange:` on the client:

```objc
- (void)commitComposition:(id)sender {
    NSString *text = [self composedBuffer] ?: [self originalBuffer];
    [sender insertText:text replacementRange:NSMakeRange(NSNotFound, NSNotFound)];
    [self reset];
}

```

This method completes the InputMethodKit text input cycle, delivering the final Chinese characters to the target application.

## Lifecycle and Keyboard Layout Management

### Activation and Keyboard Override

When the user activates HallelujahIM, macOS calls `activateServer:` on the controller. The implementation in `src/InputController.mm` performs critical setup, including forcing the system keyboard layout to US to ensure raw key codes match the IME's expectations:

```objc
- (void)activateServer:(id)sender {
    // Force US keyboard layout for consistent key code mapping
    [self overrideKeyboardWithKeyboardNamed:@"com.apple.keylayout.US"];
    // Initialize annotation window and reset state
    [self reset];
}

```

This InputMethodKit API ensures the IME receives predictable key codes regardless of the user's physical keyboard layout.

### Deactivation and Cleanup

When the user switches to another input method, `deactivateServer:` clears the composition buffers and hides the candidate window, ensuring no text remains in an incomplete state.

## Menu Integration and User Interface

### Connecting to the macOS Input Method Menu

HallelujahIM provides menu items such as "Preferences" and "About" through the `InputApplicationDelegate` class defined in [`src/InputApplicationDelegate.h`](https://github.com/dongyuwei/hallelujahim/blob/main/src/InputApplicationDelegate.h) and `src/InputApplicationDelegate.m`. This delegate wires IMK selectors like `showIMEPreferences:` and `clickAbout:` to the controller's actions, allowing macOS to invoke the plug-in's UI from the system input method menu.

The delegate also uses `NSWorkspace` to open external URLs, such as the GitHub repository or local preference panes, providing a complete user experience without leaving the InputMethodKit ecosystem.

## Summary

- **HallelujahIM** integrates with the **macOS InputMethodKit framework** by subclassing `IMKInputController` in [`src/InputController.h`](https://github.com/dongyuwei/hallelujahim/blob/main/src/InputController.h), establishing the required plug-in lifecycle.
- The controller implements `recognizedEvents:` and `handleEvent:client:` to intercept keyboard input, filtering for alphabetic characters and special keys.
- Candidate management relies on the global `IMKCandidates` instance (`sharedCandidates`), updated via `updateCandidates` and displayed with `show:`.
- Pre-edit composition uses `setMarkedText:selectionRange:replacementRange:` to display marked text, while `insertText:replacementRange:` commits the final characters.
- The `activateServer:` method calls `overrideKeyboardWithKeyboardNamed:` to enforce US keyboard layout consistency.
- Menu integration occurs through `InputApplicationDelegate`, connecting standard IMK menu actions to the controller's preference and about panels.

## Frequently Asked Questions

### What is the macOS InputMethodKit framework?

The **macOS InputMethodKit framework** (IMK) is Apple's official API for developing custom input methods on macOS. It provides abstract base classes such as `IMKInputController` and `IMKCandidates` that handle the complex machinery of intercepting keystrokes, displaying candidate windows, and communicating with client applications. HallelujahIM leverages this framework to avoid low-level event tapping while maintaining native integration with Cocoa and Carbon apps.

### How does HallelujahIM receive keyboard events?

HallelujahIM receives events by subclassing `IMKInputController` and overriding two critical methods defined in `src/InputController.mm`. First, `recognizedEvents:` returns `NSEventMaskKeyDown | NSEventMaskFlagsChanged` to register interest in keystrokes and modifier changes. Then, `handleEvent:client:` receives the actual `NSEvent` objects, allowing the controller to inspect character codes and either buffer alphabetic input or handle special keys like Space and Return.

### What role does IMKCandidates play in this input method?

`IMKCandidates` provides the floating selection window that displays Chinese character suggestions. HallelujahIM maintains a global `sharedCandidates` object (defined in `InputController.mm`) that persists across input sessions. When the `ConversionEngine` generates candidates, the controller calls `updateCandidates` to refresh the list and `show:` to position the window below the cursor. Navigation methods like `moveUp:` and `moveDown:` handle arrow key input, while `clearSelection` resets the highlight when composition ends.

### How is the pre-edit composition text displayed?

Pre-edit text—the raw alphabetic input shown while typing—is rendered using the `setMarkedText:selectionRange:replacementRange:` API from `IMKInputController`. In `src/InputController.mm`, the `showPreeditString:` method constructs an `NSAttributedString` with styling attributes obtained from `markForStyle:atRange:`, then passes this marked text to the current client. This produces the standard underlined composition appearance that indicates pending input before the user selects a final candidate.