hoboNicola 同時打鍵ステートマシンの説明

hoboNicolaライブラリの同時打鍵処理は、かつて公開されていた親指ひゅんQ(4.30)の状態遷移図(同時打鍵ステートマシン)に基づいて、ふつうの日本語キーボードでNICOLAキーボードのような操作感を実現するために実装した。この資料や、日本語入力コンソーシアムが公開しているNICOLA配列規格書に基づいて、hoboNicolaでの同時打鍵ステートマシンの概略をまとめた。

親指ひゅんQの状態遷移図は、Linuxで親指シフト入力を可能にするための oyainputでも同時打鍵ロジックの実装用に使われているそうです。

hoboNicolaライブラリ全体については、こちらのページを参照してください。ライブラリをダウンロードするためのリンクも用意しています。

同時打鍵処理について

かつての親指シフトキーボードを含むNICOLA方式のキーボードの特徴として、文字キーと親指キーを同時を意図して押したとき、一定の時間内ならばどちらが先でもよい、というものがある。したがって、実装するときにはどちらが先に押されてもいいように作る必要がある。

Shiftキー + 文字キーの入力のように、先に修飾キーありきならば修飾キーに応じた出力コードを選択することになるが、NICOLA方式の場合最大3つのキーとそれらが押されたときの時間の関係から出力コードを決める必要があり、話が若干複雑になる。
同時打鍵ステートマシンには、NICOLA方式の同時打鍵処理をわかりやすく表現するため、いくつかの状態やイベントの定義と、状態ごとのイベントに対応した挙動を記述している。

同時打鍵ステートマシン

以下、hoboNicola 1.11版での動作を記述している。

状態の定義

以下の4状態を定義している。

  • 初期状態
    ステートマシンに、何もイベントが与えられていない状態。
  • 文字キー押下中状態
    NICOLA配列内の文字キーが押下された状態。
  • 親指キー押下中状態
    親指キーが押下された状態。
  • 文字キー親指キー押下中状態
    文字キー押下中状態において親指キーの押下された状態。
  • リピート中状態
    キーボードのリピートが発生しうる状態。
イベントの定義

イベントとは、ステートマシンの状態を遷移させるために外部から与えられる要素(キーの押下やリリース、時間の経過、プログラムによる操作)である。各状態は、イベントに応じた処理を行った後に別の状態に遷移したり、同じ状態を維持したりする。

  • 初期化要求
    ステートマシンを初期状態に戻す。
  • 文字キー押下
    NICOLA配列内の文字キーが押下された。
  • 親指キー押下
    親指キーが押下された。
  • キーのリリース
    文字キーまたは親指キーがリリースされた。
  • 範囲外キーの押下
    NICOLA配列範囲外のキー、または、Ctrl, Alt, GUIキーが押下された状態で任意のキーが押下された。
  • タイムアウト
    タイムアウト設定から所定の時間が経過した。
  • キーのリピート
    内部タイマーからのリピート通知が来た。
※リピートイベントについて

HIDキーボードをイベントソースとして用いることから、本来キーボード由来のリピートイベントは起きない。ただ、キーリピート動作が欲しい場合もあるので、実装の中でリピートイベントを作り、ステートマシンに与えられるようにした(日本語入力時のリピート動作の有無は設定可能)。

一方、PS/2キーボードはタイパマチック動作を禁止できないので、キーを押下し続ければMAKEスキャンコードの連続出力によるキーリピートが発生する。hoboNicolaでは、PS/2キーボードのインタフェース(ps2_kbdクラス)でリピートを抑止し、HIDキーボードと同様に扱えるようにしている。

なお、HIDキーボードをPC他のホストデバイスに接続したとき、キーを押し続ければふつうにリピートするが、これは一定の時間以上キーがオンのままのとき、ホストデバイスの中でリピートを生成しているために起きている。この同時打鍵ステートマシンが扱うのは日本語入力時のリピート処理のみで、英数字の入力時にはホスト側がリピートを処理する。

状態遷移に必要な変数および定数

同時打鍵ステートマシンは、内部に以下の変数や定数をもつ。

  • moji
    押下された文字キーを記憶する変数。
  • oyayubi
    押下された親指キーを記憶する変数。
  • repeat_moji
    リピート通知が発生しうる状態で文字キーを記憶する変数。
  • repeat_oyayubi
    リピート通知が発生しうる状態で親指キーを記憶する変数。
  • param
    文字または親指キーの押下またはリリースに伴ってセットされる変数(ステートマシンの引数の一つ)で、押下またはリリースされたキーの情報を保持している。
  • immediate
    文字即時出力済のときにtrue、そうでないときにfalseをもつ変数。
  • moji_time
    文字キーが押下された時刻を記憶する変数。
  • oyayubi_time
    親指キーが押下された時刻を記憶する変数。
  • event_time
    タイムアウトイベントを起こす時刻を記憶する変数。
  • now
    現在時刻を表す。Arduinoのmillis()で得られた値。
  • e_charTime
    文字→親指同時打鍵検出許容時間をもつ定数。
  • e_oyaTime
    親指→文字同時打鍵検出許容時間をもつ定数。
  • e_nicolaTime
    親指キー単独打鍵みなし時間をもつ定数。

キーボードが専用の親指キーをもつ場合と、同時打鍵用親指キーを他のキー(変換・無変換・空白など)と共用する場合とがある。特に変数や定数としては定義していないが、専用か共用かによって親指キー押下中状態の処理が若干異なる。
このステートマシンでは、専用親指キーは単独のキーとしては何も出力しないことを前提としている。

「時刻」という表記は、時間の経過にしたがってインクリメントされ続け、ある瞬間を表す値という意味で使っている。

Arduinoでの実装では、時刻としてunsigned long millis() の戻り値を使っている。millis()はマイコンの動作開始時からの経過時間をミリ秒単位で返し、50日間程度で0に戻る。オリジナルのステートマシン解説では10msec程度の分解能とある。


初期状態 Initial_State

エントリ時にparam以外の変数を初期化。

文字キー押下(E11)

      • 文字キーセット
      • 文字キー押下中状態へ。

親指キー押下(E12)

      • 親指キーセット
      • 親指キー押下中状態へ

その他のイベント

何もしない。

文字キー押下中状態 Character_State

(任意の)キーのリリース(E21)
範囲外キー検出(E22)

      • キーコード出力。
      • 初期状態へ。

文字キー押下(E24)

      • キーコード出力。
      • 文字キーセット。 [ 補足参照 ]
      • 状態遷移なし

親指キー押下(E25)

      • 親指キーセット
      • 文字キー親指キー押下中状態へ

タイムアウト(E23)

      • repeat_mojiにmojiをセット
      • キーコード出力
      • リピートタイマセットしてリピート中状態へ

親指キー押下中状態

範囲外キー検出(E31)

      • キーコード出力。
      • 初期状態へ。

文字キー押下(E33)

      • mojiにparamをセット。
      • repeat_mojiにmojiをセット
      • repeat_oyayubiにoyayubiをセット
      • キーコード出力。
      • リピートタイマセットしてリピート中状態へ。

親指キー押下(E34)

      • キーコード出力。
      • 親指キーセット。
      • 状態遷移なし

キーのリリース(E35) [ 補足参照 ]

      • paramとoyayubiが同じキーならばキーコード出力。
      • 初期状態へ。

タイムアウト(E32) [ 補足参照 ]

      • 専用親指キーならば何もせず状態遷移なし。
        以下は共用親指キーの場合。
      • repeat_oyayubiにoyayubiをセット
      • キーコード出力。
      • リピートタイマセットしてリピート中状態へ

文字キー親指キー押下中状態

範囲外キー検出(E41)

      • キーコード出力。
      • 初期状態へ。

親指キー押下(E43)

      • キーコード出力。
      • 親指キーセット
      • 親指キー押下状態へ。

文字キー押下(E44) [ 補足参照 ]

      • oyayubi_time – moji_time < now – oyayubi_timeの場合、
        • キーコード出力
        • mojiにparamをセット
        • oyayubi をクリア。
        • moji_timeにnowをセット。
        • event_time に now + e_charTimeをセット。
        • 文字キー押下中状態へ
      • oyayubi_time – moji_time ≧ now – oyayubi_timeの場合、
        • immediate がfalseならばmojiのみキーコード出力(oyayubiは維持)、trueならばfalseをセット。
        • mojiにparamをセット。
        • repeat_mojiにmojiをセット
        • repeat_oyayubiにoyayubiをセット
        • キーコード出力
        • リピートタイマセットしてリピート中状態へ

キーのリリース(E45) [ 補足参照 ]

      • mojiがparamと一致しており、(now – oyayubi_time < e_nicolaTime) && (oyayubi_time – moji_time) > (now – oyayubi_time) を満たす場合、
        • immediate がfalseならば mojiのみキーコード出力(oyayubiは維持)、trueならばfalseをセット。
        • 親指キー押下状態へ。
      • その他の場合、
        • キーコード出力。
        • 初期状態へ。

タイムアウト(E42)

      • repeat_mojiにmojiをセット
      • repeat_oyayubiにoyayubiをセット
      • キーコード出力。
      • リピートタイマセットしてリピート中状態へ。

リピート中状態 [ 補足参照 ]

キーのリピート(E51)

      • キーコード出力
      • 状態遷移無し

文字キー押下(E52)

      • 文字キーセット
      • 文字キー押下中状態へ。

親指キー押下(E53)

      • 親指キーセット
      • 親指キー押下中状態へ

タイムアウト(E54) [ 補足参照 ]

      • 何もしない。
      • 状態遷移無し

ステートマシンの補足説明

初期化要求

すべての状態において、初期化要求に対しては出力操作は行わずに初期状態に遷移する。

文字キーセット

「文字キーセット」と表記されている場合、以下のように処理する。

    • mojiparamをセット。
    • moji_timenowをセット。
    • event_time に now + e_charTimeをセット。
    • 文字即時出力モードならば即時出力(後述)する。即時出力したらimmediate をtrueに。
親指キーセット

「親指キーセット」と表記されている場合、以下のように処理する。

    • oyayubiparamをセット。
    • oyayubi_timenowをセット。
    • event_time nowe_oyaTimeをセット。
キーコード出力 ( output() )

ステートマシンがもつmojiおよびoyayubi(または、repeat_mojiと repeat_oyayubi)から出力すべきコードを決定し、キーのストローク(押下とリリース)動作を行う。

repeat_mojiまたは repeat_oyayubiがセットされている場合、moji, oyayubiの代わりにそれらを用いて出力コードを決定する。

    • oyayubi == 0
      単独打鍵時のコード表から得て出力。
      ただし、即時出力モードでimmediateがtrueならば既に出力済なので出力なし。
    • oyayubiが親指左キー
      左親指同時打鍵用のコード表から得て出力。
    • oyayubiが親指右キー
      右親指同時打鍵用のコード表から得て出力。
    • moji  == 0ならば、各親指キーが単独で出力するべきコード(無変換、変換、空白、または、無し)を使ってストロークを出力。

※ 専用親指キーの場合、親指キーが単独出力すべきコードはない。共用親指キーと同様に処理するが、最終的に出力は行わない。

出力後、mojiおよびoyayubiはクリア、repeat_mojiと repeat_oyayubiは維持する。

即時出力モード時の挙動については後述する。

親指キー押下中状態でのE32について

専用親指キーがある場合、親指キー単独では何も文字を出力しない。このため、タイムアウトイベントが起きても同じ状態が維持され続ける。結果として、親指キーを押したままの状態を長く続けた後で文字キーを押すと、文字キー押下イベント( E33 )となって、同時打鍵成立後のリピート状態に遷移する。
共用親指キーの場合、その親指キーが本来果たすべき機能(変換、無変換、空白など)のために単独での出力動作を行う。

親指キー押下中状態でのE35について

親指左での同時打鍵に引き続いて親指右での同時打鍵が起きるような場合(例えば「ある」など)、ある程度高速に入力していると「る」の入力中に「あ」のための親指左キーを離すことになる。このリリース操作により「る」のつもりが変換+「く」にならないよう対象キーを限定している(共用親指キー特有の現象への対処)。

文字キー親指キー押下中状態のE44について

文字キーを押下し親指キーを押下した後、いずれも押下している状態で別の文字キーを押下した場合にどうするか、という処理。タイミング図で表すと以下のようになる。

文字キー親指キー押下中のタイミング図1

最初に押された文字1を親指キーとの同時打鍵として打ち込みたいのか、あるいは、2番目に押された文字2を同時打鍵として打ち込みたいのか、操作者の意図を判定するため、3つのキーの打鍵時刻の差(図のd1およびd2)を比較している。

d1 < d2 ならば、文字1を押してすぐに親指キーを押したと判断して文字1と親指キーでの同時打鍵成立とし、d1 ≧ d2ならば文字1は単独打鍵、文字2と親指キーでの同時打鍵成立となる。

文字キー親指キー押下中状態のE45について

文字キーを押下して親指キーを押下したのち、親指キーを離す前に文字キーを離した場合にどうするかという処理で、親指キーと変換/無変換キーなどを共用するとき、親指キー単独打鍵(つまり、変換や無変換操作)のつもりで打ったのに、直前に押下した文字キーとの同時打鍵になりやすい。という現象に対処しようという意図で規定されている。

文字キー親指キー押下中のタイミング図2

E45に書いてある式をこのタイミング図に合わせると、
   (d2 < e_nicolaTime) && (d1 > d2)
という式になる。

この式は、文字キーが単独で押されている時間の方が長く(d1 > d2)、かつ、親指キーの押下から文字キーのリリースまでの時間がある程度より短い(d2 < e_nicolaTime)ときにtrueになる。
実装では、trueならば文字キー単独打鍵とし、あとから押された親指キーは引き続く操作によって行方が決まることになる。

e_nicolaTimeは、この実装では80msecとしている。

リピート状態について

hoboNicolaでは、日本語入力時のリピート動作の有無を実行時に設定可能としている。リピート動作を禁止している場合でも、ステートマシンはリピート状態に遷移するようにしているが、リピート状態の各イベントの扱いは初期状態とほぼ同じになっており、実質的に影響しない。

リピート中状態のE54について

文字キーと親指キーのいずれも押下されている状態(親指キー押下中での文字キー入力、文字キー親指キー押下中での文字キー入力)からリピート状態に入ったときには、既にキーコード出力済のため、event_timeには親指キー押下時刻 + e_oyaTime がセットされたままになっている。

初期状態に戻っていればタイムアウトイベントは何の影響ももたないが、リピート状態でタイムアウトを検出したとき、E54で処理しておかないと、リピート動作を阻害することになる。なお、今回の実装では、リピート状態に遷移する前に event_time をクリアすることによっても対処できる。

文字即時出力(零遅延)モードについて

聖人さんが開発し公開中の親指の友 Mk-2 キーボードドライバ  が備えている、文字キー出力零遅延モード に倣って、このステートマシンに文字即時出力モードを追加した。この動作モードが許可されている場合、文字キーの押下を検出した時点で即時出力( immediate_output() )を行う。即時出力を行わないときは、別のイベントが生じるまで入力したキーに対応する文字は出力されない。

文字即時出力1

上図は文字キーを単独で押下したときの様子を示したもので、通常の動作ではe_charTime経過(タイムアウト)した時点でキーに対応する文字出力を行うが、即時出力モードでは押下検出と同時に出力し、e_charTime経過時点では何も出力しない。

文字即時出力2

上図は、文字キーを即時出力した後そのキーと親指キーとの同時打鍵が成立した場合を表している。この図では親指キーのリリースによって同時打鍵が成立しているが、まずBackSpaceキーのコードを出力して即時出力した文字を消し、その後oyayubimojiによるコードを出力する。

BackSpaceキーによって即時出力した文字を削除する動作が伴うため、利用するアプリケーションによっては、本来削除されてはいけない文字が失われる可能性があることに注意が必要。


改版および修正
  • 2018.12.1 : 1.2.1版
    専用親指キーをもつASkeyboard版の作成に伴って、専用親指キーについての記述を追加。
    このページの題名を変更。
  • 2018.10.27 : 1.01版。
    実装の追加に伴って、リピート状態を追加。
    文字即時出力モードを追加しタイミング図も追加。
  • 2018.10.1 : 初版作成