こんにちは、@toshi00です。
IME使ってますか? 文字入力をサポートする便利なソフトウェアIME。ですが、今回はプログラミングには絶対に使えない超不便なIMEを作る話です。RustでmacOS向けのIMEを制作しました。
traP LT2022で発表したものをまとめ直した内容となっています。
夏のブログリレー2022の46日目の記事です。
TypoIMEのコンセプト
TypoIME はあなたの英数字入力を"支援"します。アルファベットがこっそり変換される最悪のIMEです。
世の中には、フォントによってはほとんど見分けのつかない文字があります。例えば、l
とI
や、0
とO
はうっかり入れ替わっていても気づきにくいでしょう。資料やブログ記事程度なら気にせず読み飛ばしてしまう程度の差です。
しかし、プログラミングにおいては1文字の違いがあまりにも重大で悲惨な結果を生み出します。
例えば、
conso1e.log("hoge")
たった1文字、l
が1
に変わっただけですが、conso1e is not defined
と怒られ、動いてくれません。悲しい。
プログラミングを繰り返す中で、1文字の違いにも注意を向けられるエンジニアだからこそ、使えない最悪のIME、それがTypoIMEです。
TypoIMEを使ってみた
コードはGithubで公開しています。
入力に対して、ランダムに文字が変換されていることを確認できます。
input : hello world!
output: heI1o worId!
設定画面に自分の作ったIMEが表示されているとなんだかわくわくしますね。
TypoIMEはmacOS向けのIMEです。make app
でビルド&パッケージ化してください。生成されたtypoIME.app
を/Library/Input Methods
に配置し、PCでログアウト→再ログインすることで読み込まれます。その後、環境設定 > キーボード > 入力ソース:英語
からTypoIMEを追加し、選択することで(たぶん)動きます。
技術の話
ざっくり、TypoIMEの構成要素についてまとめます。ほとんど触ったことがなかったという理由で言語はRustを選択しました。
Cocoa
macOSには、Cocoaというアプリケーション構築用フレームワーク(API)があります。これを利用してIMEを実装していきます。今回使ったのは、InputMethodKitと呼ばれる、文字入力を取り扱う機能です。主に下の2つを利用しました。
- IMKServer
- 入力と他のアプリの仲介役。IMEは入力と他アプリの間に入ってあれこれする
- IMKInputController
- IMEとしての処理を記述する
CocoaAPIを使ったIMEの実装方針は3種類くらいありそうです。今回はその中でもシンプルそうな、キーバインディングを利用した方法で実装しました。IMKInputControllerを継承して、TypoInputControllerを実装しました。
今回は放置したのですが、候補選択を実装したい場合は IMKCandidates というクラスを触るようです。
RustからCocoaを触る
InputMethodKitは、主にObjective-Cで記述されています。Rustから使えるクレートを探したのですが、見つけることができず……
結局、Rustからobjcクレートを使い、直接Objective-Cを記述することで実装しました。
こんな感じで、こつこつObjective-CのコードをRustに置き換えていきます。
// [IMKServer alloc]
let server_alloc: id = msg_send![class!(IMKServer), alloc];
この部分は、正直なところあまりRustの嬉しさを感じられませんでした。Rustは触り始めたばかりなので、なにか良い方法を見つけたら修正していきたいですね。
実行ファイルをApp形式にする
実行ファイルとアイコンや設定をApp形式というパッケージにする必要があります。実態は、.app
の拡張子と以下のディレクトリ構造です。
typoIME.app
└── Contents
├── Info.plist
├── MacOS
│ └── typo_ime
└── Resources
└── icon.icns
それぞれファイルは以下の内容になっています。
- Info.plist: XML形式の設定ファイル
- typo_ime: ビルドした実行ファイル
- icon.icns: アプリのアイコンなど
Info.plistの設定項目についていくつか紹介します。
項目 | 内容 |
---|---|
CFBundleExecutable | 実行ファイル名、typo_imeと指定 |
CFBundleIdentifier | .inputmethod. を含んだ id を指定 |
LSBackgroundOnly | 1 (=True)と設定。IMEはバックグラウンドのみで実行されるため。 |
InputMethodConnectionName | 他のIMEと競合しないコネクション名を指定 |
InputMethodServerControllerClass | InputControllerを継承したサブクラス名 |
tsInputMethodIconFileKey | キーボード環境設定に表示されるアイコン画像 |
tsInputMethodCharacterRepertoireKey | 今回は英数字用なので Latn |
まとめ
RustでしょうもないIME、「TypoIME」を制作しました。macOS向けIMEは、SwiftやObjective-C、Rubyで作られている例が多そうです。が、あえてRustで作ってみるのもいかがでしょうか?
後日談
traP LTで発表したとき、様々な反応をいただき大変嬉しかったです。その中でも記憶に残っているのがこちら。
競プロの A 問題レベルを TypoIME 使って早解き対決するみたいなネタとか、面白そうなことができそう
……天才!? エディタの拡張機能とかあったらいいんじゃね……?
ということで、VSCode向けの拡張機能を制作しました。次回へ続きます!!!
明日の記事も @toshi00 がお送りします!