01

キーを横取りする仕組み — キーボードフック

awase はキーボードドライバとアプリケーションの「間」に割り込んでいます。

Windows にはキーボードの入力を全アプリの手前で受け取れる仕組みがあります。 ローレベルキーボードフック(WH_KEYBOARD_LL)と呼ばれるもので、 物理キーが押されるたびに awase が最初に通知を受け取れます。

物理キーボード │ キー押下 ▼ 【 awase のフック 】 ← ここで全キーを最初に受け取る │ ├─ 親指シフトに関係するキー → awase が処理を引き受ける │ └─ 関係ないキー → そのまま通過させる │ ▼ Windows / アプリケーション

ただし「横取り」には制限があります。処理に手間取ると Windows がフックを強制解除してしまうのです。 awase はすべての判断をできるだけ素早く終わらせ、重い処理は別スレッドに渡すことで この制限に対応しています。また解除された場合も自動で再登録します。

DLL インジェクションとの違い: 他の親指シフトツールの一部は、アプリのプロセスに直接コードを埋め込む「DLL インジェクション」を使います。 より深いところに入り込めますが、ウイルス対策ソフトに誤検知されやすくなります。 awase はこの方法を使わず、Windows の標準 API だけで動作します。
02

すべてのキーを再注入する — リレーモード

awase は一度すべてのキーを飲み込み、処理後に送り直します。

通常のキーボードフックには2つの動作モードがあります。

フィルターモード リレーモード(awase のデフォルト)
動作 関係するキーだけ横取り、他は素通し すべてのキーをいったん飲み込み、処理後に送り直す
他フックとの共存 順番によっては競合 再注入なので他のフックにもキーが届く
安定性 タイミングに依存しやすい FIFO キューで順番が保証される

リレーモードでは、awase はキーを受け取ったら必ず自分で送り直す必要があります。 このとき「自分が送ったキーを自分でまた受け取る」という無限ループが起きないよう、 再注入したキーには目印(マーカー)を付けてフックで素通りさせています。

03

「同時押し」をどう判定するか — 状態機械とタイマー

親指シフトの核心は「2つのキーが同時に押された」と判断する部分です。

NICOLA 親指シフトでは、文字キーと親指キーが100ms以内に重なったとき「同時打鍵」と判定します。 しかしコンピュータには「同時」という概念がなく、必ずどちらかが先に来ます。 しかも高速タイピングでは、前の親指キーを離す前に次の文字キーが来ることもあります。

例: 「が(文字キー: A + 右親指キー)」を入力するとき ケース1(文字が先): A↓ ─── A↑ 右親指↓ ─ 右親指↑ ↑ここ 100ms 以内なら「が」 ケース2(親指が先): 右親指↓ ─── 右親指↑ A↓ ─── A↑ ↑ここ 100ms 以内なら「が」 ケース3(3打鍵問題): か↓ ─────────── か↑ 右親指↓ ──────────── 右親指↑ ら↓ ─ ら↑ ↑どちらと組む? →「が+ら」か「か+ら」か

これを解決するのが 状態機械(FSM: Finite State Machine)タイマー の組み合わせです。 awase はキーが来るたびに「現在の状態」と「キーの種類」から次の状態へ遷移し、 どこかのタイミングで「これは同時打鍵だ」または「これは単独打鍵だ」と確定させます。

状態機械のイメージ(简略版): 待機中 │ 文字キー↓ ▼ 文字キー保留中 ─── タイムアウト(100ms)→ 単独文字として出力 │ 親指キー↓(100ms以内) ▼ 両方押し中 ─── 親指キー↑ → シフト文字として出力 │ 文字キー↑ ▼ 親指キーのみ押し中 → 次の文字キーを待つ

ケース3の「3打鍵問題」では、状態機械が d1(文字1→親指)と d2(親指→文字2)の時間差を比べます。 d1 < d2 なら文字1と親指が近いので「文字1+親指を同時打鍵」、 d1 ≥ d2 なら文字2と親指が近いので「文字2+親指を同時打鍵」と判断します。 しかし d1 と d2 が拮抗しているとき、タイミングだけでは決めきれません。

確定モードとは: 「いつ出力を確定するか」の戦略です。タイムアウトまで待つ(wait)、 とりあえず出力してシフトキーが来たら差し替える(speculative)など5種類あり、 タイピングスタイルに合わせて選べます。
04

日本語の統計で誤判定を防ぐ — n-gram モデル

3打鍵問題でタイミングが拮抗しているとき、「日本語として自然か」で決着をつけます。

高速タイピングでは、文字キー・親指キー・次の文字キーが3つ同時に重なる「3打鍵問題」が起きます。 d1 と d2 がほぼ同じで、タイミングだけでは「前の文字と親指が組む(シフト文字)」か 「後の文字と親指が組む(シフト文字)」か判断できないケースです。

awase の ngram_predictive モードはこの曖昧さを日本語統計で解消します。 タイミングが拮抗しているとき、2通りの解釈それぞれの「ひらがなの連なり」を n-gram コーパスに照らして、より自然な日本語になるほうを選びます

例: 3キーが重なったとき、n-gram は「直前のかな→候補かな」という バイグラムの出現確率を2通りの解釈について比べます。

解釈A(か+右親指 = が)のスコア:直前の文字「ら」→「が」=「らが」の出現確率
解釈B(か を単独出力)のスコア:直前の文字「よ」→「か」=「よか」の出現確率

日本語コーパスでは「よかった」「よかれ」など「よか」を含む語が豊富なのに対し、 「らがった」のような「らが」の連続は少ない。 そのため「よか」のスコアが勝ち、「よかった」を選びます。

逆に文脈が変わって「らが」のスコアが高くなれば「らがった」を選びます。 タイミングの数値だけで判断せず、直前の文字を文脈として使うことで精度が上がっています。

このコーパスは大量の日本語テキストから学習した統計データです。 「ひらがな2文字の組み合わせがどれだけ頻繁に現れるか」を数値化したもので、 awase に同梱されています。

05

見えない IME 状態を追いかける — shadow モデル

「今 IME は ON なのか OFF なのか」を awase はどうやって知るのか。

親指シフト入力を有効にするには「IME が ON の状態」が必要です。 しかし Windows の IME 状態は、外部のプログラムから確実に取得できない場合があります。 特に Google 日本語入力では、内部状態を正確に読み取る公開 API がありません。

3つの「状態」を区別して管理する

awase はひとつの IME 状態ではなく、3つの異なる「状態」を管理しています。

┌─────────────────────────────────────────────────┐ │ desired(ユーザーの意図) │ │ Ctrl+変換 → ON にしたい │ │ Ctrl+無変換 → OFF にしたい │ └─────────────────────────┬───────────────────────┘ │ 指示 ▼ ┌─────────────────────────────────────────────────┐ │ applied(最後に適用した結果) │ │ 「ON にするコマンドを送って、確認が取れた」 │ └─────────────────────────┬───────────────────────┘ │ 観測で照合 ▼ ┌─────────────────────────────────────────────────┐ │ observed(実際に OS/IME から観測した状態) │ │ 定期ポーリングや観察から得た実態 │ └─────────────────────────────────────────────────┘

「意図」と「実態」がずれることがあります。たとえばアプリ切り替え時に アプリ側が独自に IME を OFF にすることがあります。 awase はこのずれを定期的な観測(ポーリング)で検出し、 ずれていれば自動的に修正します。

冪等な制御とは

従来の親指シフトツールの多くは「IME をトグルする」という方式を使っていました。 「今 ON なら OFF に、OFF なら ON に」という操作です。 この方式の弱点は、現在の状態を正確に知らないと意図と逆になることです。

awase は「IME を ON にする」「IME を OFF にする」という 冪等(べきとう)な操作を使います。 何回実行しても結果が変わらない操作です。 もし状態がずれていても「ON にする」を実行すれば必ず ON になります。

冪等とは: 電灯のスイッチで例えると、「押すたびに ON/OFF が切り替わる」 のがトグル(従来方式)。「このボタンを押すと必ず ON になる」のが冪等(awase の方式)です。 状態を知らなくてよいので、ずれが起きても必ず意図通りになります。

観測の「信頼できるかどうか」を一元管理する — ObservationAdmission

awase はバックグラウンドで複数の「プローブ」を非同期に走らせています。 プローブが結果を返してきたとき、その観測は本当に「今フォーカスしているウィンドウ」のものでしょうか?

たとえば ALT+TAB でアプリを切り替えるとき、OS は一瞬だけタスクスイッチャーのウィンドウにフォーカスを移します。 このわずかな時間に起動したプローブが「IME OFF」と返してくると、 元のアプリに戻った後もエンジンが停止してしまいます。

ALT+TAB のタイムライン: LINE にフォーカス中 │ ALT+TAB 押下 → タスクスイッチャー表示 │ OS: フォーカス = スイッチャーウィンドウ │ └─ ImmCrossProbe 起動 → 「IME OFF」を返す ← stale な観測 │ ALT+TAB 離す → LINE に戻る │ └─ probe の結果が届く → エンジン停止!(バグ) ▼ awase の対策: フォーカスが変わっていた → この観測は棄却

awase はこれを「エポック(epoch)」で解決しています。 フォーカスが変わるたびに内部カウンタ(FocusEpoch)を 1 増やします。 プローブはスタート時のエポック番号を記録しておき、 結果が届いたときに現在のエポック番号と照合します。 番号が違えば「その間にフォーカスが変わった = stale」と判断して結果を棄てます。

型で保証する: 「エポック照合を通った観測だけが shadow モデルに書き込める」ことを、 コンパイラが強制しています。 エポック照合を通ると AcceptedObservation というトークンが発行され、 このトークンなしでは write_* 関数を呼べません。 将来プローブを追加した際も、照合を忘れると コンパイルエラーになります。
06

アプリごとに制御方式が違う理由

「IME を ON にしてください」という指示の届け方が、アプリによって異なります。

Windows のアプリが IME と通信する方法は大きく2種類あります。 古い IMM32 という仕組みと、新しい TSF(Text Services Framework) です。 さらに Google 日本語入力は独自の内部構造を持ちます。 awase はアプリの種類を自動判定し、3段階の制御方式を試みます。

1

IMM32 クロスプロセス制御

アプリの IME ウィンドウに直接「ON にしてください」というメッセージを送る。 最も確実な方法で、成功すれば即座に状態が変わったことを確認できます。

対象: メモ帳・ワードなど一般的なアプリ

2

Google 日本語入力 直接制御(VK_IME_ON / VK_IME_OFF)

Windows の仮想キー VK_IME_ON(ひらがなモードへ)と VK_IME_OFF(直接入力へ)を送る。 冪等な操作なので、何度送っても意図通りになります。

対象: Google 日本語入力が起動しているすべてのアプリ

3

KANJI キートグル(最終手段)

半角/全角キー(VK_KANJI)を送って IME を切り替える。 トグル操作なので現在の状態を正確に把握していないと意図と逆になる危険があります。 awase は shadow モデルで状態を確認してから送信します。

対象: 上記2方式が使えない場合のフォールバック

戦略1が成功すれば戦略2・3は試みません。戦略1が失敗したら戦略2を試みます。 こうしてアプリの種類に関係なく確実に制御できます。

LINE・Qt アプリの特殊ケース: LINE などの Qt ベースアプリは、IME に関するキー(半角/全角など)を受け取ると 独自の IME 状態遷移を始めてしまいます。 awase は LINE に対して「物理的な IME キーを一切通さない」という方針を取り、 IMM32 クロスプロセス制御だけで IME を操作します。
07

LINE / Qt アプリがとくに厳しい理由

LINE は IME キーを受け取ると独自の状態遷移を始めてしまいます。

LINE などの Qt フレームワーク製アプリは、 半角/全角キー(VK_KANJI)などの IME 関連キーを受け取ると アプリ自身が独自の IME 状態遷移を開始します。 awase が「IME ON にする」と送ったキーを LINE が別の意味で解釈し、 意図と逆の状態になってしまうのです。

通常のアプリ(メモ帳など): awase → VK_KANJI 送信 → OS の IME が受け取る → IME ON LINE(Qt アプリ): awase → VK_KANJI 送信 → LINE が横取り → LINE 独自の IME 状態遷移が始まる → 意図と逆になることがある

この問題を解決するために、awase は LINE に対して 物理的な IME キーを一切通さないという方針を取っています。 LINE が IME キーを見る機会そのものをなくし、 IMM32 クロスプロセス制御(戦略1)だけで IME を操作します。

ALT+TAB との組み合わせ

LINE は「IME キーを通さない」だけでは十分ではありませんでした。 ALT+TAB でウィンドウを切り替えるとき、 OS はタスクスイッチャーのウィンドウに一瞬フォーカスを移します。 この瞬間に IME 状態を読み取ると誤って「IME OFF」と判定され、 LINE に戻ったときにエンジンが停止してしまいます。

awase は「フォーカスが変わるたびにカウンタ(エポック)を増やし、 古いカウンタの観測は棄却する」という仕組みでこれを解決しています (セクション5の ObservationAdmission 参照)。 LINE での ALT+TAB 問題も、この epoch 照合によって構造的に解消されています。

08

Google 日本語入力と MS-IME で制御方式が変わる理由

同じ「IME ON にする」でも、IME ソフトごとにコマンドが異なります。

Windows には「どの IME ソフトを使うか」を自由に選べる仕組みがあります。 代表的なのは Windows 標準の MS-IME と、 別途インストールする Google 日本語入力です。 同じ「ひらがなモードにしてください」という操作でも、 受け付けるコマンドが違います。

操作 Google 日本語入力 MS-IME
ひらがなモードへ VK_IME_ON (0x16) VK_DBE_HIRAGANA (0xF2)
直接入力(英数)へ VK_IME_OFF (0x1A) VK_DBE_ALPHANUMERIC (0xF0)
どちらも冪等か はい(何度送っても結果が変わらない) はい(同様)

awase はどちらの IME が使われているかを、 TSF(Text Services Framework)の API で自動判別します。 具体的には IME ごとに固有の識別子(CLSID)を動的に取得し、 起動後初めて識別した IME 種別を cache.toml に保存します。 次回起動時はこのキャッシュを使うため、判別コストがかかりません。

なぜ CLSID で判別するのか: かつては「Google 日本語入力のプロセス(GoogleIMEJaConverter.exe)が 動いているか」でプロセス名ベースの判別をしていました。 しかしプロセス名は環境によって変わることがあります。 TSF の EnumProfiles で CLSID を取得する方式は OS レベルの公式 API を使うため、確実で安定しています。

IME が切り替わったとき(たとえば Google 日本語入力をアンインストールして MS-IME に戻したとき)は、WM_IME_KIND_CHANGED メッセージで awase に通知が来ます。awase はこのメッセージを受け取ると 制御方式をリアルタイムに切り替えます。再起動は不要です。

09

キーの届け方を自動で選ぶ — InjectionMode

同じキーでも「届け方」が違うと IME の動作が変わります。

awase がキーを送り返すとき(リレーモード)、 実は「どんな形式で送るか」にも選択肢があります。

モード 説明 向いているケース
Unicode モード 文字コードとしてキーを送る。シンプルだが IME を経由しないことがある IME 経由が不要なアプリ
TSF モード 物理キーと同等の形式で送る。IME が確実に受け取れる Google 日本語入力 / TSF アプリ

Google 日本語入力 が動いている環境では TSF モードが必要です。 しかし awase は起動時に Google 日本語入力 が動いているかを毎回確認するのではなく、 自動昇格という仕組みを使います。

InjectionMode 自動昇格の流れ: 起動直後: Unicode モード │ │ フォーカス変化 → Unicode モードでキーを送信 │ ├─ Google 日本語入力 の WriteTransferCount を観測 │ ├─ 増加あり(+350B 以上) │ │ → 「Google 日本語入力 がキーを処理した」と判断 │ │ → TSF モードへ自動昇格 │ │ → cache.toml に保存(再起動後も維持) │ │ │ └─ 増加なし → Unicode モードのまま継続

WriteTransferCount は Google 日本語入力 プロセスの I/O 書き込みバイト数です。Google 日本語入力 がキーを受け取ると 辞書参照などのために書き込みが発生します。 この数値を監視することで「Google 日本語入力 が実際にキーを処理したか」を 外部から確認できます。

ユーザーが設定不要な理由: 自動昇格により、Google 日本語入力 をインストールした環境では 最初のフォーカス変化のタイミングで自動的に TSF モードに切り替わります。 MS-IME 環境や Google 日本語入力 が入っていない環境では Unicode モードのまま動作します。 設定画面で IME の種類を選ぶ必要はありません。
10

Chrome / Edge がとくに難しい理由

Chrome は複数のプロセスが協調するため、通常の方法が通じません。

Chrome や Edge は「マルチプロセス」アーキテクチャを採用しています。 表示を担当するプロセス、ネットワークを担当するプロセス、入力を担当するプロセスが 分離して動作します。

IMM32 の「アプリの IME ウィンドウに直接メッセージを送る」方法は、 送り先と受け取り先が同じプロセスにいることを前提としています。 Chrome ではこれが当てはまらず、方法1(IMM32 制御)が使えません。

Chrome のプロセス構成(简略): ┌──────────────────────────────────────┐ │ ブラウザプロセス(フレーム・UI) │ │ ↕ IMM32 通信できない │ │ レンダラープロセス(ページ表示) │← IME はここと通信 │ ↕ IPC │ │ ネットワークプロセス ... │ └──────────────────────────────────────┘ awase から直接 IME ウィンドウに 届けられない

そこで awase は Google 日本語入力へ VK_IME_ON(ひらがなモード)/ VK_IME_OFF(直接入力)を送信します。 Google 日本語入力の内部プロセスに直接届くため、Chrome のプロセス構成に関係なく動作します。

Google 日本語入力 I/O 監視でタイミングを合わせる

さらに Chrome には別の難問があります。 IME がキーを受け取れる状態(TSF composition context)になるまでに、 数百ミリ秒かかることがあるのです。この準備ができる前に文字キーが届くと、 文字化けが起きます。

awase は Google 日本語入力のディスクアクセス(I/O)をバックグラウンドで監視しています。 Google 日本語入力が「静止」したタイミングを「準備完了」のサインとみなし、 そこから文字を送信します。

なぜ I/O 監視が有効なのか: Google 日本語入力は辞書参照などにファイル読み書きを行います。 IME の composition context が確立されると内部処理が落ち着き、I/O が静かになります。 DLL インジェクションなどの侵襲的手法を使わずに「内部の準備状態」を外から推測する方法です。
11

最初の1文字が化ける — TSF cold-start 問題

TSF アプリでは「入力を受け付ける準備」に時間がかかります。

Windows Terminal などの TSF ネイティブアプリや Chrome で、 「こ」と打ったつもりが「ko」と出る現象が起きることがあります。 これは「TSF cold-start 問題」と呼ばれます。

なぜ起きるのか

TSF アプリは「composition context」という入力バッファを持っています。 アプリを起動した直後、または IME ON に切り替えた直後は、 このバッファの初期化に数百ミリ秒かかります。 初期化が完了する前にローマ字キー(「k」「o」)が届くと、 IME がそれを変換できず英字のまま出力してしまいます。

cold-start の発生タイムライン: 0ms IME ON に切り替え 「こ」を打ち始める: K↓ K↑ 50ms O↓ O↑ 100ms (composition context 初期化完了)← ここまでに届いたキーは変換されない 結果: 「ko」が英字で出力される

awase の解決策

awase は「確定キー(Enter やスペース)が押された直後」や 「長時間入力がなかった後」に warmup 処理を行います。 composition context をあらかじめ活性化しておく「予熱」です。 次の文字が来るまでの空き時間を使って準備を済ませ、cold-start を未然に防ぎます。

それでも予測できないタイミングで cold-start が起きた場合も、 awase は Google 日本語入力 I/O 監視で「準備できた」タイミングを検知してから文字を送るため、 化けが起きにくくなっています。

捨て打ち機能(Chrome・Windows Terminal の確実な暖機)

Chrome や Windows Terminal などの TSF ネイティブアプリでは、上記の予熱だけでは composition context が確実に活性化されたか判断できないケースがあります。 awase はこれを解決するために捨て打ち機能を備えています。

捨て打ちとは、本来打ちたい文字の直前に「捨て駒」として A キーを バックスペースとセットで送り込む動作です。 ABS を同一バッチで送るため、 Chrome は画面を描画する前に両方を処理し、「あ」が一瞬でも表示されることはありません。

捨て打ち機能の流れ: [cold-start 検出] │ ├─ VK_A + BS を同一バッチで送信(捨て打ち) │ ← 画面には何も表示されない │ ├─ Google 日本語入力 の I/O 書き込みバイト数(WriteTransferCount)を観測 │ ├─ +350B 以上 → composition 確立を確認 → 本物のローマ字を送信 │ └─ タイムアウト → VK_IME_OFF→VK_IME_ON で Google 日本語入力 を強制リセット → 再試行 │ └─ 本物のローマ字を再送(追加 BS 不要:捨て打ちで1文字分は消去済み)

捨て打ちには2つの役割があります。 第一に強制暖機A キーで Google 日本語入力 が composition を開始するため、 context を確実に活性化できます。 第二に状態観測:Google 日本語入力 が A を処理すると プロセス内部の I/O 書き込みバイト数が増加します(「あ」の変換候補データ送信のため)。 この増加量を WriteTransferCount で計測することで、 「composition が本当に確立されたか」を直接確認できます。 タイミングの競合に左右されない、コンテンツベースの確認方法です。

Windows Terminal での注意: awase が Google 日本語入力に送る VK_IME_ON (0x16) / VK_IME_OFF (0x1A) は Windows 標準の仮想キーコードですが、物理キーボードには存在しません。 VT エスケープシーケンスも生成しないため、Windows Terminal での追加設定は不要です。
12

長時間アイドル後に文字が消える — WezTerm long-idle 問題

WezTerm は長時間使わないでいると TSF の文脈が「冷え」てしまいます。

WezTerm(Rust 製ターミナル)では、しばらく入力しないでいた後に 最初の1文字が消えてしまうことがありました。 たとえば10分放置してから「な」と打つと、「な」が出ずに何も起きない、という現象です。

なぜ起きるのか

WezTerm は TSF ネイティブアプリです。 TSF では IME と通信する「composition context」が 長時間使われないと内部的にリセットされます。 次にキーが来たときにはすでに context が失われており、 最初のキーは IME に届かずに消えてしまいます。

WezTerm long-idle の発生タイムライン: 0分 IME ON で入力中 ↓ 10〜15分 放置 15分 TSF composition context がリセット(内部) ↓ ユーザーがキーを押す 15分 最初の「な」→ IME に届かず消滅 次の「に」→ IME が context を初期化 → 正常に届く 結果: 「なにほ」と打ったつもりが「にほ」になる

awase の解決策 — 検出して再送

固定のタイムアウトで「何分おきに warmup する」という方法も試みましたが、 WezTerm が context をリセットするタイミングは環境によって異なり、 閾値を固定しても競合条件が別のタイミングに移るだけでした。

awase は「タイムアウトで予防する」のではなく、 「消えたことを検出して修復する」パターンを採用しています。 最初のキーが IME に届かなかったことを観測し、同じキーをもう一度送り直します。

LiteralDetect(検出して修復)の流れ: キーが届く │ ├─ IME が受け取ったか確認(TSF mode の write bytes 観測) │ │ │ ├─ 受け取った → 通常の入力として処理 │ │ │ └─ 受け取っていない(literal 化を検出) │ │ │ ├─ WezTerm が表示した「生の文字(例: k)」を BS で消去 │ └─ 同じキーを再送 → 今度は context が初期化されているので届く
「予防」より「検出・修復」: タイムアウト延長で予防しようとすると「このタイミングでは大丈夫だが あのタイミングでは失敗する」という競合条件が常に残ります。 WezTerm の場合は「失敗したことがわかる」ため、 検出して修復するほうが競合条件を根本的に消せます。 awase ではこのパターンを LiteralDetect と呼んでいます。
13

ひらがな以外でも正しく動く — カタカナ・JIS かな対応

IME がひらがな以外のモードのとき、親指シフトをどう扱うかは複雑です。

IME には「ひらがな」だけでなく「カタカナ」「JIS かな」「半角カタカナ」など 複数の入力モードがあります。 awase の親指シフトエンジンはひらがな前提で設計されているため、 これらのモードを適切に扱う追加の仕組みが必要でした。

「カタカナ = ObservedRomaji」という中心原則

カタカナモードでは、ローマ字入力が有効なまま IME が出力をカタカナに変換します。 awase はカタカナモードを「ローマ字入力中」と同等に扱います。 これにより親指シフトエンジンが誤って非活性化されません。

入力モード awase の扱い エンジン状態
ひらがな(ローマ字) 通常の親指シフト入力 Active
カタカナ(全角) ローマ字入力中と同等 Active
半角カタカナ ローマ字入力中と同等 Active
英数(直接入力) 親指シフト不要 Inactive
JIS かな ローマ字を使わない入力。エンジンは維持 Active(旁受け)

ObservedEisu — 英数直接入力の自動検出

IME が ON のまま「英数入力モード」に切り替わることがあります。 この状態は「shadow = ON」「実態 = 英数直接入力」という 食い違いを引き起こします。

awase はこれを ObservedEisu(英数観測)として独立したモードで管理します。 ObservedEisu を検出すると、500ms 以内に自動的に IME OFF(直接入力)に切り替え、 エンジンも非活性化します。 ユーザーが気づいてから手動で対処しなくてよくなります。

なぜ英数モードのまま放置してはいけないのか: shadow モデルが「IME ON」と思っている状態で英数モードが続くと、 awase がひらがな入力を期待してキーを変換しようとする一方で 実際には英字が出力されるという不整合が続きます。 ObservedEisu を明示的なモードとして扱うことで、 「この状態は想定済み」として安全に自動回復できます。
14

スリープ・ロック解除後に自動回復する仕組み

PC がスリープから復帰した直後は、IME の状態を正しく読み取れません。

PC をスリープさせて復帰させると、Windows の IME サブシステムも 一時的に「初期化中」の状態になります。 この瞬間に awase が IME 状態を読もうとすると、 is_japanese_ime = false(日本語 IME なし)という 誤った値が返ってくることがあります。

スリープ復帰後のタイムライン: スリープ前: Google 日本語入力 が動作中、IME ON │ └─ スリープ │ 復帰 ├─ OS: IME サブシステムを再初期化中 │ ↓ この間に awase が読み取ると │ is_japanese_ime = false(誤) │ → エンジン停止(「日本語 IME なし」判定) │ └─ 初期化完了(数百 ms 後) → 正常な値が返ってくるが、エンジンはすでに止まっている

grace 保護による解決

awase はスリープ復帰直後に「grace 期間」を設定します。 この期間中は is_japanese_ime = false への ダウングレードを無視します。 一方で is_japanese_ime = true(正常)への更新は 常時受け付けます。

これは「悪いニュースは一定時間保留、良いニュースはすぐ受け付ける」という非対称な設計です。 スリープ復帰直後の誤った false を捨て、 初期化完了後の正しい true が届いた時点でエンジンが回復します。

shadow grace と epoch の使い分け: 「フォーカスが変わった後の stale 観測」には FocusEpoch を使います(セクション5)。 「スリープ復帰後の一時的な誤値」には grace 期間を使います。 前者は「観測が古い」という問題、後者は「観測自体が一時的に信用できない」という問題です。 それぞれ異なるアプローチで対処しています。