On macOS, it is not so difficult to write an Input Method Engine (IME). Unlike Linux or the other Unix-like OS, macOS provides an official framework, InputMethodKit, to help you write an Input Method Engine.
An IME project is in fact a normal Cocoa app located in a special directory, with a special bundle identifier, some special declarations in plist manifest file, and some special classes/objects in the project. This post may help you initiate your own IME project. Let’s go!
Create the project
First, open XCode, create a new App project:
Then, type your IME name. Here I choose the typical name “HelloWorld”.
Notice that there must be an inputmethod identifier in the bundle identifier. I choose to append a suffix to my organization identifier. However, you should also be able to do it in another way.
Add some magic
In this section, I’ll add some magic to to turn the app into an IME.
Plist magic
In the manifest file Info.plist, there are magic to declare some information of your IME:
| Key | Type |
|---|---|
| InputMethodConnectionName | String |
| InputMethodServerControllerClass | String |
| tsInputMethodCharacterRepertoireKey | Array |
The InputMethodConnectionName will be the name of your IME, recognized by macOS.
The InputMethodServerControllerClass is the name of the Input Method controller class in your IME, which is in charge of communicating with macOS, exchanging the input events and submitting the candidate selected by the user.
The tsInputMethodCharacterRepertoireKey can help you declare the category of your IME. It will depend where your IME will show up in the Input Source panel of the macOS system preference. As an array, there can be multiple items. Thus, an IME for several languages is possible. For example, Squirrel has a zh-Hans string in the array, so it shows up in the Chinese, Simplified language as follow:
If all is set, we can have an Info.plist like this:
I choose InokiHelloWorldIME as the connection name. There will be an Input Method controller class named InokiHelloWorldController in my project, in charge of events. And I added zh-Hans, zh-Hant and Latn for the category.
Class magic
We need to create our Input Method controller class, inheriting IMKInputController class from Input Method Kit.
Add these codes to the implementation(.m) file, so that we can observe what happens when we are “using”(not actually) the IME:
1 | - (void)activateServer:(id)sender |
This method is using one of the controller API to receive input event from macOS:
1 | - (BOOL)inputText:(NSString*)string client:(id)sender |
There are in fact 3 different APIs to handle the input event, such as:
1 | - (BOOL)inputText:(NSString*)string key:(NSInteger)keyCode modifiers:(NSUInteger)flags client:(id)sender; |
For more details, you can take a look at the declaration of IMKInputController in InputMethodKit/IMKInputController.h.
With all these done, macOS should be able to find your Input Controller, thanks to the runtime information in Objective-C.
Main Code magic
Finally, we need a minimum main function like this.
1 |
|
In which we create an instance of IMKServer and make it alive during all the life cycle of our app. The server is also registered to macOS, so that the OS knows which process it should communicate.
Run it
Finally, we can build it and copy it into /Library/Input Methods.
For debug or running, this post (in Chinese) is a good example.
Here, I concentrate more on the potential and undocumented bug (ok, it might be a feature) as follows.
Oups, you may be isolated in a sandbox
You may suffer an error like:
1 | [IMKServer _createConnection]: *Failed* to register NSConnection name=xxxxx |
Same to me. I took several hours to dive into macOS and debug such error.
I found that the reason: IMKServer needs an NSConnection to communicate with macOS. However, by default, the app is sandboxized (by the <project-name.entitlements>) since a version of XCode.
So, the solutions are various:
- Remove the entitlements file;
- Allow
NSConnectionfrom sandbox; - Disable the App sandbox in the entitlements file.
Then, your IME should be good to go.
Conclusion
In this post, there is a brief description for creating an IME project. I show an issue that may generally exist related to the sandbox stuff. Hope this can help you. In the next post, I’ll try to show up the event handle in an IME on macOS.