mozc_emacs_helper を使う

mozc_emacs_helper は emacs から mozc を使用するための外部プログラム。

sudo apt-get install emacs-mozc-bin

emacs-mozc-bin パッケージは emacs への依存がないので emacs がまるごと付いてきたりしない。

mozc_emacs_helper プログラムとのやり取りは S 式で行う。

利用できるコマンドは以下のもの。

(EVENT_ID SendKey SESSION_ID KEY ...)
(EVENT_ID CreateSession)
(EVENT_ID DeleteSession SESSION_ID)

括弧の後にLF(\n)が必要です。KEY は整数値、バックスラッシュによるエスケープされた文字、またはキー。

SESSION_ID は変換クライアントの選択に必要なため、セッションの作成ごとに割り当てられた値を使う。SESSION_ID はいつも 1 から始まるため、セッションが一つなら固定値の 1 を使うことができる。

EVENT_ID は mozc_emacs_helper 側では使われないため、任意の値を使える。要求と返信が一致しているかどうかなど、クライアント側が確認のために使用できる。複数スレッドから呼び出すのでなければ、固定値でも問題ない。

ターミナルで実行すると以下のようになる。

> mozc_emacs_helper
((mozc-emacs-helper . t)(version . "2.17.2116.102")(config . ((preedit-method . roman))))
(1 CreateSession)
((emacs-event-id . 1)(emacs-session-id . 1)(output . ()))

グリーティングメッセージが出るので、まずセッションを作成する。応答でイベント ID とセッション ID が確認できる。

SendKey コマンドでキー押し下げを送信する。

(2 SendKey 1 "r")
((emacs-event-id . 2)(emacs-session-id . 1)
(output . (
    (id . "6309519535730520822")(mode . hiragana)(consumed . t)
    (preedit . (
        (cursor . 1)(segment 
            ((annotation . underline)(value . "r")(value-length . 1)(key . "r")))))
    (candidates . (
        (size . 1)(candidate ((index . 0)(value . "r")
            (annotation . (
                (description . "[半] アルファベット")))
            (id . 0)))
        (position . 0)(category . suggestion)(display-type . main)
        (footer . (
            (label . "Tabキーで選択")))
        (page-size . 9)))
    (status . (
        (activated . t)(mode . hiragana)(comeback-mode . hiragana)))
    (all-candidate-words . (
        (candidates 
            ((id . 0)(index . 0)(value . "r")(annotation . ((description . "[半] アルファベット")))))
            (category . suggestion)))
)))

(3 SendKey 1 "a")
((emacs-event-id . 3)(emacs-session-id . 1)
(output . (
    (id . "6309519535730520822")(mode . hiragana)(consumed . t)
    (preedit . (
        (cursor . 1)(segment 
            ((annotation . underline)(value . "ら")(value-length . 1)(key . "ら")))))
    (candidates . (
        (size . 3)(candidate 
            ((index . 0)(value . "楽")(annotation . ((deletable . t)))(id . 0))
            ((index . 1)(value . "ラフト")(annotation . ((description . "[全] カタカナ")(deletable . t)))(id . 1))
            ((index . 2)(value . "ライセンス")(annotation . ((description . "[全] カタカナ")(deletable . t)))(id . 2)))
        (position . 0)(category . suggestion)(display-type . main)
        (footer . ((label . "Tabキーで選択")))
        (page-size . 9)))
    (status . (
        (activated . t)(mode . hiragana)(comeback-mode . hiragana)))
    (all-candidate-words . (
        (candidates 
            ((id . 0)(index . 0)(key . "らく")(value . "楽")(annotation . ((deletable . t))))
            ((id . 1)(index . 1)(key . "らふと")(value . "ラフト")(annotation . ((description . "[全] カタカナ")(deletable . t))))
            ((id . 2)(index . 2)(key . "らいせんす")(value . "ライセンス")(annotation . ((description . "[全] カタカナ")(deletable . t)))))
        (category . suggestion)))
)))

(4 SendKey 1 space)
((emacs-event-id . 4)(emacs-session-id . 1)
(output . (
    (id . "6309519535730520822")(mode . hiragana)(consumed . t)
    (preedit . (
        (cursor . 1)(segment 
            ((annotation . highlight)(value . "ラ")(value-length . 1)(key . "ら")))
            (highlighted-position . 0)))
    (status . (
        (activated . t)(mode . hiragana)(comeback-mode . hiragana)))
    (all-candidate-words . (
        (focused-index . 0)(candidates 
            ((id . 0)(index . 0)(value . "ラ")(annotation . ((description . "[全] カタカナ"))))
            ((id . 1)(index . 1)(value . "ら")(annotation . ((description . "ひらがな"))))
            ((id . 2)(index . 2)(value . "等"))
            ((id . 3)(index . 3)(value . "羅"))
            ((id . 7)(index . 4)(value . "裸"))
            ((id . 8)(index . 5)(value . "拉"))
            ((id . 9)(index . 6)(value . "瘰"))
            ((id . 10)(index . 7)(value . "蘿"))
            ((id . 11)(index . 8)(value . "蠡"))
            ((id . 12)(index . 9)(value . "鑼"))
            ((id . 13)(index . 10)(value . "楽"))
            ((id . 15)(index . 11)(value . "良"))
            ((id . 16)(index . 12)(value . "樂")(annotation . ((description . "楽の旧字体"))))
            ((id . 17)(index . 13)(value . "浦"))
            ((id . 18)(index . 14)(value . "十"))
            ((id . 19)(index . 15)(value . "彰"))
            ((id . 20)(index . 16)(value . "晶"))
            ((id . 21)(index . 17)(value . "税"))
            ((id . 22)(index . 18)(value . "倉"))
            ((id . 23)(index . 19)(value . "螺"))
            ((id . 24)(index . 20)(value . "蘭"))
            ((id . 25)(index . 21)(value . "浪"))
            ((id . 26)(index . 22)(value . "篭")(annotation . ((description . "籠の略字"))))
            ((id . 27)(index . 23)(value . "籠")(annotation . ((description . "篭の印刷標準字体"))))
            ((id . 28)(index . 24)(value . "薇"))
            ((id . 29)(index . 25)(value . "邏"))
            ((id . 30)(index . 26)(value . "騾"))
            ((id . -3)(index . 27)(value . "ra")(annotation . ((description . "[半] アルファベット"))))
            ((id . -4)(index . 28)(value . "RA")(annotation . ((description . "[半] アルファベット"))))
            ((id . -6)(index . 29)(value . "Ra")(annotation . ((description . "[半] アルファベット"))))
            ((id . -7)(index . 30)(value . "ra")(annotation . ((description . "[全] アルファベット"))))
            ((id . -8)(index . 31)(value . "RA")(annotation . ((description . "[全] アルファベット"))))
            ((id . -10)(index . 32)(value . "Ra")(annotation . ((description . "[全] アルファベット"))))
            ((id . -11)(index . 33)(value . "ラ")(annotation . ((description . "[半] カタカナ")))))
        (category . conversion))))))

(5 SendKey 1 space)
((emacs-event-id . 5)(emacs-session-id . 1)
(output . (
    (id . "6309519535730520822")(mode . hiragana)(consumed . t)
    (preedit . (
        (cursor . 1)(segment 
            ((annotation . highlight)(value . "ら")(value-length . 1)(key . "ら")))
        (highlighted-position . 0)))
    (candidates . (
        (focused-index . 1)(size . 34)(candidate 
            ((index . 0)(value . "ラ")(annotation . ((description . "[全] カタカナ")(shortcut . "1")))(id . 0))
            ((index . 1)(value . "ら")(annotation . ((description . "ひらがな")(shortcut . "2")))(id . 1))
            ((index . 2)(value . "等")(annotation . ((shortcut . "3")))(id . 2))
            ((index . 3)(value . "羅")(annotation . ((shortcut . "4")))(id . 3))
            ((index . 4)(value . "裸")(annotation . ((shortcut . "5")))(id . 7))
            ((index . 5)(value . "拉")(annotation . ((shortcut . "6")))(id . 8))
            ((index . 6)(value . "瘰")(annotation . ((shortcut . "7")))(id . 9))
            ((index . 7)(value . "蘿")(annotation . ((shortcut . "8")))(id . 10))
            ((index . 8)(value . "蠡")(annotation . ((shortcut . "9")))(id . 11)))
        (position . 0)(category . conversion)(display-type . main)
        (footer . ((index-visible . t)(logo-visible . t)))(page-size . 9)))
    (status . ((activated . t)(mode . hiragana)(comeback-mode . hiragana)))
    (all-candidate-words . (
        (focused-index . 1)(candidates 
            ((id . 0)(index . 0)(value . "ラ")(annotation . ((description . "[全] カタカナ"))))
            ((id . 1)(index . 1)(value . "ら")(annotation . ((description . "ひらがな"))))
            ((id . 2)(index . 2)(value . "等"))
            ((id . 3)(index . 3)(value . "羅"))
            ((id . 7)(index . 4)(value . "裸"))
            ((id . 8)(index . 5)(value . "拉"))
            ((id . 9)(index . 6)(value . "瘰"))
            ((id . 10)(index . 7)(value . "蘿"))
            ((id . 11)(index . 8)(value . "蠡"))
            ((id . 12)(index . 9)(value . "鑼"))
            ((id . 13)(index . 10)(value . "楽"))
            ((id . 15)(index . 11)(value . "良"))
            ((id . 16)(index . 12)(value . "樂")(annotation . ((description . "楽の旧字体"))))
            ((id . 17)(index . 13)(value . "浦"))
            ((id . 18)(index . 14)(value . "十"))
            ((id . 19)(index . 15)(value . "彰"))
            ((id . 20)(index . 16)(value . "晶"))
            ((id . 21)(index . 17)(value . "税"))
            ((id . 22)(index . 18)(value . "倉"))
            ((id . 23)(index . 19)(value . "螺"))
            ((id . 24)(index . 20)(value . "蘭"))
            ((id . 25)(index . 21)(value . "浪"))
            ((id . 26)(index . 22)(value . "篭")(annotation . ((description . "籠の略字"))))
            ((id . 27)(index . 23)(value . "籠")(annotation . ((description . "篭の印刷標準字体"))))
            ((id . 28)(index . 24)(value . "薇"))
            ((id . 29)(index . 25)(value . "邏"))
            ((id . 30)(index . 26)(value . "騾"))
            ((id . -3)(index . 27)(value . "ra")(annotation . ((description . "[半] アルファベット"))))
            ((id . -4)(index . 28)(value . "RA")(annotation . ((description . "[半] アルファベット"))))
            ((id . -6)(index . 29)(value . "Ra")(annotation . ((description . "[半] アルファベット"))))
            ((id . -7)(index . 30)(value . "ra")(annotation . ((description . "[全] アルファベット"))))
            ((id . -8)(index . 31)(value . "RA")(annotation . ((description . "[全] アルファベット"))))
            ((id . -10)(index . 32)(value . "Ra")(annotation . ((description . "[全] アルファベット"))))
            ((id . -11)(index . 33)(value . "ラ")(annotation . ((description . "[半] カタカナ")))))
        (category . conversion)))
)))

(6 SendKey 1 enter)
((emacs-event-id . 6)(emacs-session-id . 1)
(output . (
    (id . "6309519535730520822")(mode . hiragana)(consumed . t)
    (result . (
        (type . string)(value . "ら")(key . "ら")))
    (status . (
        (activated . t)(mode . hiragana)(comeback-mode . hiragana)))
)))

これでr, a, space, space, enterと入力したことになる。

不要になったらセッションを削除する。

(4 DeleteSession 1)
((emacs-event-id . 7)(emacs-session-id . 1)(output . ()))

SendKey コマンドの引数には以下のものが指定できる。

ローマ字を一文字ずつ、またはキーコードを一つずつ送って変換すると、ローマ字の小文字と大文字の候補が含まれる。しかし、平仮名を一度に送るとローマ字候補は含まれない。

Python の subprocess から実行すると communicate メソッドを一度呼び出すと stdin が close されてしまう。stdin に write することになる。読み出しは stdout と stderr でブロックされないように別スレッドで読み込んでタイムアウトなどが必要。