mac-ime: EmacsのIMEパッチなしで快適な日本語入力を実現する
macOSでEmacsを使う時、日本語入力の切り替えストレスですよね。今まではhomebrew-emacsmacportを便利に使っていましたが、tahoeになって起動しなくなったのをきっかけにwindowsで使っていたtr-imeと同じようにmacでも素のEmacsにmacOSのIMEの組み合わせで快適に使うようにできないか…
そんな思いで作ったのが、今回紹介する mac-ime です。
mac-ime とは?
一言で言うと、「Emacsのダイナミックモジュールを使ってmacOSのキーイベントを監視し、外側からIMEを制御する拡張機能」 です。
Emacs自体にパッチを当てるのではなく、Emacs 27.1から導入された「ダイナミックモジュール」機能を使って、OSレベルでキー入力をフックします。これにより、IMEパッチなしのEmacsでも、以下のような挙動を実現できます。
-
C-xなどのプレフィックスキーを押した瞬間にIMEをオフにする(コマンド入力モードへ) - ミニバッファ (
M-xなど) に移動した瞬間にIMEをオフにする - コマンド入力が終わったら元のIME状態に戻す
インストール方法
Apple Silicon (M1/M2/M3...) 搭載のMacなら、ビルド済みのモジュールがそのまま使えるのでリポジトリのクローン&設定のみで動作します。
git clone https://github.com/ma0001/mac-ime.git
基本的な使い方
基本的にはロードして有効化するだけ。
;; ロードパスを通す
(add-to-list 'load-path "/path/to/mac-ime")
(require 'mac-ime)
;; input methodを "mac-ime" に設定
(setq default-input-method "mac-ime")
;; モジュールを有効化(イベント監視開始)
(mac-ime-enable)
これで、C-\ (toggle-input-method) でモードラインに [あ] (macOS System IME) が表示されれば準備OKです。
この状態で日本語入力中に C-x などを押すと、スッとIMEがオフ(Roman)になり、コマンド入力後にまた日本語入力に戻ります。
内部の処理の概要
仕組みとしては、Objective-Cで書かれたダイナミックモジュール (.so) がバックグラウンドで動いています。
こいつが NSEvent を監視して、キー入力情報をキューに溜め込みます。Emacs側 (Elisp) はタイマーを使って定期的にそのキューをポーリングし、特定のキー入力があったらIMEを切り替える、という流れです。
処理概要
実装のポイントを紹介します。
Emacsのダイナミックモジュールでイベント監視
Emacsの中からOSの生のキーイベントを見るために、ダイナミックモジュールを使っています。
NSEvent addLocalMonitorForEventsMatchingMask: を使って、Emacsアプリケーションへのキーダウンイベントをフックしています。
あと、IMEの状態を取得、設定するための関数も定義しています。
これらをC (Objective-C) で作成しています。
擬似的な Input Method "mac-ime"
単にIMEを切り替える関数を作るのではなく、mac-ime という名前のEmacs用 Input Method を実装 しています。
(register-input-method "mac-ime" "Japanese" 'mac-ime-activate-input-method "[あ]" "macOS System IME")
こうすることで、Emacs標準の toggle-input-method (C-\) でオンオフできますし、モードラインの表示も自然になります。
そして、「mac-ime が有効な時だけ、macOSのIMEの一時的なon off処理を有効にする」 という制御としています。
また、バッファが切り替わったらそのバッファのinput-methodの状態に合わせてOSのIMEを切り替える。
逆にOSのIMEの状態が切り替わったらそれに合わせてinput-meyhodの状態を更新することも行います。
プリフィックスキーの監視とIMEオフ
ダイナミックモジュールから通知されるキーイベントをみて、プリフィックスキーに一致するものがきたらIMEをオフします。
そのあとはpre-command-hookを利用してコマンド実行前に元の状態に戻す処理を動作させています。
controlやmetaなどのモディファイヤキーの定義はmac-*-modifierの変数定義をみて行っているのでこれらの変数で設定変更を行っていればほぼ問題なく動作すると思います。特殊なキーバインドやキーボードを使っている場合はmac-ime-prefix-keysを定義するなどを行ってください。
(mac-ime-debug-levelを1に設定すればキー入力イベントがメッセージ出力されます。)
デフォルトでのプリフィックスキー定義は以下の通りです
- C-x
- C-c
- C-h
- M-g
- Esc
ミニバッファ入力時の自動IMEオフ
プリフィックスキー監視だけでは、M-x などをした時のIMEオフを制御できないため、以下の関数に
advice-add :around を使って関数の実行をラップしています。
- read-string
- read-char
- read-from-minibuffer
- y-or-n-p yes-or-no-p
- map-y-or-n-p
関数の入り口でIMEをオフにし、unwind-protect を使って関数を抜ける時(キャンセル時含む)に確実に元の状態に戻しています。
Universal Argument (C-u)
C-u (universal-argument) はちょっと特殊です。
上記の advice-add :around 方式だと、「C-u を押した瞬間」しか制御できません。その後の数字入力などでIMEが戻ってしまいます。
そこで、以下の対応としています(プリフィックスキーと同等です)。
-
関数入り口でIMEオフ:
advice-add ... :beforeを使って、コマンド実行前にIMEをオフにします。 -
pre-command-hookで復帰: 「次のコマンドが実行される直前」にIMEを元に戻すフックを登録します。
さらに、universal-argument は C-u を押した時だけ呼ばれるのですが、その後の数字入力 (C-u 1 0 ...) では内部的に universal-argument--mode という関数が繰り返し呼ばれます。
なので、universal-argument ではなく universal-argument--mode に対してこの処理を登録することで、数字入力中もずっとIMEオフ状態を維持しています。
github copilot
このプロジェクトのコードですが、ほぼ全て GitHub Copilot (Gemini 3 Pro) を使って生成 しました。(この記事の元ネタも)
今までは個人的に作成したコードを人様にお見せできるレベルにあげる前に、興味が薄れたり、
モチベーションが下がったりでお蔵入りになったプロジェクトが多かった…
コード修正も自分の手でやった方が早いと思うような些細な修正もcopilotに指示することで
関係する全てのDocStringの修正を英語でやってくれます。
今まではコードを修正するかに頭を使っていましたが、ソフトの構成や設計方針をどうするかに頭を使うようになりすっきりとしたコードにできたように思います。
注意
ウィンドウのイベントを見ているためターミナルのEmacsでは動作しません。
Emacsをbrewでインストールする場合、brew install emacsでなくてbrew install emacs-appなどでインストールしたものが必要です。
最後に
この「イベントをフックしてIMEを制御する」「Adviceで自動切り替えする」というアイデアは、Windows用の W32-ime や、tr-ime から多大な影響(というかほぼそのまま)を受けています。感謝です。
まとめ
mac-imeを使えば、パッチなしのEmacsでも快適な日本語入力環境が手に入ります。
もし興味があれば、ぜひ使ってみてください!