Architecture & Specs

FJ Flow キー拡張機能 (Key Extension) 技術解説書

FJ Flow のキー拡張機能(KeyExt)は、ホームポジションを崩さずにタイピング速度と操作性を極限まで高めるためのコアシステムです。 今回の刷新では、従来の「2打固定のダブルタップ」から 「任意...

1. はじめに

FJ Flow のキー拡張機能(KeyExt)は、ホームポジションを崩さずにタイピング速度と操作性を極限まで高めるためのコアシステムです。 今回の刷新では、従来の「2打固定のダブルタップ」から 「任意の文字数(3文字以上)に対応する可変長シーケンス・コンボエンジン」 へとアーキテクチャを根本から再構築しました。

タイピングの快適性を損なわず、誤爆・誤判定をゼロにし、0msの爆速レスポンスと柔軟なマッピングを両立させた実装の全貌を解説します。


2. コア設計思想:判定と出力の完全分離

キー拡張エンジンは、「判定フェーズ」 と 「出力フェーズ」 を完全に分離して設計されています。

┌────────────────────────────────────────────────────────┐
│ 1. 判定フェーズ (FSM Engine)                           │
│   ・純粋な物理キー打鍵イベント & タイムスタンプを受信  │
│   ・修飾ロックやレイヤーのリアルタイム変化を遮断       │
│   ・押下瞬間のレイヤースナップショット (downCond) 保持 │
│   ・完全一致 / 前方一致 / 静寂成立を決定論的に判定     │
└──────────────────────────┬─────────────────────────────┘
                           │ 確定 (Execute / Flush)
┌──────────────────────────▼─────────────────────────────┐
│ 2. 出力フェーズ (Output Pipeline)                      │
│   ・保持されたレイヤースナップショットでターゲット実行 │
│   ・修飾キーロック (Ctrl/Alt/Win/Shift) を合成適用     │
│   ・{Blind} モードによる OS アトミック送信             │
└────────────────────────────────────────────────────────┘

なぜこの分離が必要なのか?

ユーザーがコンボ(例: usa)を打鍵する際、キーを押した直後に修飾キーを離したり、別キーを連打したりすることがあります。出力時にリアルタイムなキー状態を参照してしまうと、レイヤーが戻って誤出力する原因となります。押下瞬間のスナップショットを FSM 内部で保持することで、100% 決定論的な出力 を保証しています。


3. 決定論的 4-State FSM(状態遷移マシン)

エンジンの中心には、4つの状態を持つステートマシンが稼働しています。

4つの状態定義

  1. STATE_READY(初期待機状態)
    • 保留中のキーがない通常状態。
  2. STATE_WAIT_HOLD(長押し判定待ち状態)
    • 前段静寂後に <a> などの長押し対象キーが押され、設定された長押し判定時間(KeyExtHoldTimeout)押し続けられるのを待機している状態。
  3. STATE_WAIT_NEXT(次キー / 静寂単打タイムアウト待ち状態)
    • シーケンスの1打目や [a] などの静寂単打が保留され、次のキー入力またはタイムアウト(中間候補実行 / 単打実行)を待機している状態。
  4. STATE_WAIT_POST_SILENCE(後段静寂待ち状態)
    • 最長完全一致(例: usa ➔ 米国、hh ➔ {Home})に達し、高速な後続打鍵(例: usaa)がないか確認するために一息(後段静寂)を待機している状態。

状態遷移図 (Mermaid)

stateDiagram-v2
    [*] --> STATE_READY

    state STATE_READY {
        [*] --> Idle
    }

    STATE_READY --> STATE_READY : ① a (通常単打)<br/>0ms 即時リマップ実行
    STATE_READY --> STATE_READY : ② タイピング中 (前段静寂なし)<br/>通常キー出力
    STATE_READY --> STATE_WAIT_HOLD : ③ 前段静寂成立 + <a>(長押し定義)<br/>長押しタイマー起動 (holdMs)
    STATE_READY --> STATE_WAIT_NEXT : ④ 前段静寂成立 + [a](静寂単打) / シーケンス<br/>1打目を保留 (comboMs)

    state STATE_WAIT_HOLD {
        [*] --> HoldWaiting
    }

    STATE_WAIT_HOLD --> STATE_READY : ⑤ holdMs 経過 (押し続けた)<br/>長押しターゲット確定実行!
    STATE_WAIT_HOLD --> STATE_READY : ⑥ holdMs 未満で KeyUp (離した)<br/>長押しキャンセル ➔ 単打処理
    STATE_WAIT_HOLD --> STATE_READY : ⑦ holdMs 未満で別キー入力<br/>直ちにキャンセル ➔ 保留キーフラッシュ

    state STATE_WAIT_NEXT {
        [*] --> Pending
    }

    STATE_WAIT_NEXT --> STATE_WAIT_NEXT : ⑧ 前方一致あり (例: 'us' in 'usa')<br/>キー追加保留 (Timer: Nx ms)
    STATE_WAIT_NEXT --> STATE_WAIT_POST_SILENCE : ⑨ 最長完全一致到達 (例: 'usa')<br/>後段静寂待ち (Timer: Nx ms)
    STATE_WAIT_NEXT --> STATE_READY : ⑩ タイムアウト (手を止めた)<br/>[a] 単打 / 中間候補を実行
    STATE_WAIT_NEXT --> STATE_READY : ⑪ コンボ不一致 / 時間切れ<br/>保留キーを順次 Raw 出力

    state STATE_WAIT_POST_SILENCE {
        [*] --> PostSilenceWait
    }

    STATE_WAIT_POST_SILENCE --> STATE_READY : ⑫ 後段静寂成立 (手を止めた)<br/>ターゲット (例: '米国') を確定実行!
    STATE_WAIT_POST_SILENCE --> STATE_READY : ⑬ 後続キー高速打鍵 (例: 'usaa')<br/>後段静寂破綻!全キー Raw 出力

    STATE_WAIT_HOLD --> STATE_READY : ⑭ Shift Up<br/>小文字 Raw フラッシュ
    STATE_WAIT_NEXT --> STATE_READY : ⑭ Shift Up<br/>小文字 Raw フラッシュ
    STATE_WAIT_POST_SILENCE --> STATE_READY : ⑭ Shift Up<br/>小文字 Raw フラッシュ

4. 今回導入された主要機能と技術的詳細

① 可変長シーケンス・マルチキーコンボ(3文字以上対応)

  • 従来の課題: 2打固定(hh や jk 等)にハードコードされていたため、3文字以上の単語(usa, jpn 等)を処理できませんでした。
  • 解決策:
    • g_FsmPendingKeys / g_FsmPendingConds 配列による動的可変長管理へ移行。
    • _ParseSequenceKeys により、"usa" や "[usa]" などの任意の文字列を文字配列 ["u", "s", "a"] へ正確に分解・パース。
    • fjFlow.ahk の _RegisterLayerHotkeys において、シーケンス内の全文字(u, s, a)を L0 レイヤーで網羅フック。

② 文字数比例スケーリング($N \times x$ ms)の連打判定

  • 設計仕様: 設定された基本連打判定時間(KeyExtComboIntervalTime)を $x$ ms(デフォルト 300ms)とし、対象キーの文字数 $N$ に応じて判定間隔およびタイマー待機時間をスケール させます。

$$\text{判定時間} = N \times x \text{ (ms)}$$

対象文字数 ($N$) 判定・待機時間 ($x = 300\text{ms}$ の場合) 適用例
1文字 $1 \times 300 = \mathbf{300\text{ ms}}$ 1打目保留、単打マッピング待機
2文字 $2 \times 300 = \mathbf{600\text{ ms}}$ hh, us などの2文字コンボ
3文字 $3 \times 300 = \mathbf{900\text{ ms}}$ usa, jpn, aaa などの3文字コンボ
$N$文字 $N \times 300\text{ ms}$ 長文シーケンス
  • 効果: 文字数が増えるほど入力の猶予時間が自然に広がるため、長いシーケンスでも焦ることなく確実に入力できます。また、後段静寂の誤爆防止時間も文字数に応じて強固になります。

③ 角括弧表記([])による静寂バイパス(0ms即時リマップ)

  • 仕様: [a] L0 x や [ca] L0 {^} のように定義キーを [] で囲むと、前段静寂および後段静寂を完全にバイパス します。
  • 動作:
    • 単打 [a]: 高速タイピング中であっても、キーを押した瞬間に 0ms 完全無遅延で即座に x へ置換実行 されます(単純なキーリマップ)。
    • シーケンス [ca]: 2打目 a を押した瞬間に、後段静寂(待機時間)を待たずに即時確定実行されます。

④ 中間完全一致とより長い前方一致のスマート共存

  • 課題: us ➔ _(2文字定義)と usa ➔ 米国(3文字定義)が同時に登録されている場合、us の時点で確定してしまうと usa が打てず、逆に us を無視すると _ が出せないという競合が発生します。
  • 解決アルゴリズム:
    1. u ➔ s と入力した時点で、us の完全一致(_)が見つかりますが、さらに長い前方一致(usa)も存在します。
    2. この時、FSM は状態を STATE_WAIT_NEXT(次キー待ち)のまま維持 し、中間候補ターゲット g_FsmCandTarget := "_" を保持してタイマーを回します。
    3. 3打目 a が来た場合 ➔ 最長一致 usa に進み、米国 を実行!
    4. 次のキーが来ずにタイムアウトした場合 ➔ 保持していた中間ターゲット _ を実行!
    5. 関係ないキー(x 等)が来た場合 ➔ 保留キー u, s, x を順番に通常出力!

⑤ Shiftキーによる安全なコンボキャンセル

  • 課題: コンボ入力を中断したいときに Shift キーを押すと、Shift が押された状態で文字が出力されて USA と大文字化してしまう問題がありました。
  • 解決策:
    • Shift Down(押した瞬間): FsmOnShiftDown() で タイマーを直ちに強制停止(勝手にコンボが発動するのを阻止)。この時点では文字を出力しません。
    • Shift Up(離した瞬間): FsmFlushOnShiftUp() で、Shift が離された安全な状態で保留キー(u, s, a)を 小文字のまま画面へフラッシュ出力。

⑥ 全キー網羅フックによる文字順序逆転(追い越し)の完全防止

  • 原理: 一部のキーだけがフックされ、他がフックされない状態になると、OSネイティブ入力とフック入力の間でミリ秒単位のレースコンディションが発生し、sh が hs に入れ替わる物理バグが発生します。
  • 対策: 全レイヤー(SS, L1, L2, L0)に登場するすべてのキーを L0 レイヤーで漏れなくフック登録し、すべて FSM を直列通過させることで、通常タイピング時の文字順序の完全性(100% 正確な打鍵順序) を保証しています。

5. 打鍵シチュエーション別の動作タイムライン

ケース A: usa と打鍵して手を止めた場合(コンボ発動)

  1. u 押下 (0ms) ➔ 前段静寂あり、STATE_WAIT_NEXT(["u"] 保留)
  2. s 押下 (100ms) ➔ 前方一致あり、STATE_WAIT_NEXT(["u", "s"] 保留、候補 _)
  3. a 押下 (200ms) ➔ 最長完全一致、STATE_WAIT_POST_SILENCE(["u", "s", "a"] 保留、候補 米国、タイマー 900ms 起動)
  4. 手を止めて 900ms 経過 ➔ 後段静寂成立! 米国 を画面に出力 🎉

ケース B: usaa と高速打鍵した場合(誤発動防止)

  1. u ➔ s ➔ a 押下 (0ms〜200ms) ➔ STATE_WAIT_POST_SILENCE(タイマー 900ms 起動中)
  2. 4打目 a 押下 (280ms、900ms以内) ➔ 後段静寂破綻!
  3. 保留されていた u, s, a と今回の a が順に出力され、画面には usaa が正しく出力される 🎉

ケース C: us と打鍵して手を止めた場合(中間候補発動)

  1. u ➔ s 押下 ➔ STATE_WAIT_NEXT(候補 _ 保持、タイマー 600ms 起動)
  2. 手を止めて 600ms 経過 ➔ タイムアウト! 候補の _ を画面に出力 🎉

ケース D: [a] をタイピング中に押した場合(即時リマップ)

  1. 前段静寂の有無に関わらず、a を押した瞬間に 0ms で即座に x を画面に出力 🎉

6. まとめ

今回の実装により、FJ Flow のキー拡張エンジンは以下の 4 大メリットを同時に達成しました:

  1. 柔軟性: 1文字の単純リマップから、3文字以上の単語マッピング、連打コマンドまで統一的に定義可能。
  2. 安全性: 前段静寂・後段静寂・文字数スケーリングにより、通常タイピング中の誤爆を完全に防止。
  3. 高速性: 静寂バイパス([])による 0ms リマップと、イベント駆動(TickCount計算)による無遅延レスポンス。
  4. 堅牢性: 決定論的 FSM とスナップショット保持による、タイマー競合・文字順序逆転・修飾キー剥がれの根本排除。