Previous Post | Top | Next Post |
TOC
キーボード状況
USB接続の自作外付けキーボードでは、キーマップがQMKを使うとキーマップ改変が自由に構成でき、HOME ROW MOD等を使うと動きの少ない手や指にやさしいタイピングができます。
PC内蔵のキーボードででも、同じようにキーマップ改変ができないかと言う気になり色々調査しました。
interception-toolsを利用すれば、evdev
のデーターをフィルター処理しキーマップを改変できそうです。
ただevdevのデーターの実際に使用されている慣習や制約、また異常時の対応法など不明点もあります。
単なるキーの置き換え以上の適当な先行例が見当たりません。
公式のプラグインのDual Function Keysには、HOME ROW MODが上手く動作しないともかかれています。どうも状況は簡単ではないようです。
実際の状況や解決策が分かりにくいので、まず読みやすいPythonのchorded_keymapを見ました。この中のプログラムをベースにデーターダンプするユーティリティーを作り、更にもう一度出力をオプションでコントロール出きるように、最初から書き直し実状調査をしました。あとから考えるとほぼ同じ機能のコマンドevtestがあるんですが、この経験をしたことで少し実態が分かりました。
本体付属キーボード(i8042)
やっつけで書いたログをstdoutに吐き出すこの解析用のmanglekbdプログラムの出力(時間は差分表示)は、本体付属の英語キーボードだと以下です。
$ sudo intercept /dev/input/by-path/platform-i8042-serio-0-event-kbd | python3 ~/bin/manglekbd -s u
0.024794 MSC,SCAN,0x1c KEY,ENTER,↑ SYN,REPORT,0
0.486209 MSC,SCAN,0x2a KEY,LEFTSHIFT,↓ SYN,REPORT,0
0.161347 MSC,SCAN,0x1e KEY,A,↓ SYN,REPORT,0 A
0.082717 MSC,SCAN,0x1e KEY,A,↑ SYN,REPORT,0
0.147690 MSC,SCAN,0x1f KEY,S,↓ SYN,REPORT,0 S
0.070924 MSC,SCAN,0x1f KEY,S,↑ SYN,REPORT,0
0.216163 MSC,SCAN,0x20 KEY,D,↓ SYN,REPORT,0 D
0.085756 MSC,SCAN,0x20 KEY,D,↑ SYN,REPORT,0
0.212290 MSC,SCAN,0x21 KEY,F,↓ SYN,REPORT,0 F
0.103219 MSC,SCAN,0x21 KEY,F,↑ SYN,REPORT,0
0.220644 MSC,SCAN,0x22 KEY,G,↓ SYN,REPORT,0 G
0.096932 MSC,SCAN,0x22 KEY,G,↑ SYN,REPORT,0
0.205199 MSC,SCAN,0x2a KEY,LEFTSHIFT,↑ SYN,REPORT,0
0.687972 MSC,SCAN,0x34 KEY,DOT,↓ SYN,REPORT,0 .
0.251521 MSC,SCAN,0x34 KEY,DOT,→ SYN,REPORT,0
0.029991 MSC,SCAN,0x34 KEY,DOT,→ SYN,REPORT,0
0.030303 MSC,SCAN,0x34 KEY,DOT,→ SYN,REPORT,0
0.032326 MSC,SCAN,0x34 KEY,DOT,→ SYN,REPORT,0
0.030289 MSC,SCAN,0x34 KEY,DOT,→ SYN,REPORT,0
0.029097 MSC,SCAN,0x34 KEY,DOT,→ SYN,REPORT,0
0.029683 MSC,SCAN,0x34 KEY,DOT,→ SYN,REPORT,0
0.032301 MSC,SCAN,0x34 KEY,DOT,→ SYN,REPORT,0
0.030361 MSC,SCAN,0x34 KEY,DOT,→ SYN,REPORT,0 .
0.030282 MSC,SCAN,0x34 KEY,DOT,→ SYN,REPORT,0 .
0.028590 MSC,SCAN,0x34 KEY,DOT,→ SYN,REPORT,0 .
0.029813 MSC,SCAN,0x34 KEY,DOT,→ SYN,REPORT,0 .
0.030434 MSC,SCAN,0x34 KEY,DOT,→ SYN,REPORT,0 .
0.014303 MSC,SCAN,0x34 KEY,DOT,↑ SYN,REPORT,0
1.000906 MSC,SCAN,0x39 KEY,SPACE,↓ SYN,REPORT,0
0.077423 MSC,SCAN,0x39 KEY,SPACE,↑ SYN,REPORT,0
0.367762 MSC,SCAN,0x39 KEY,SPACE,↓ SYN,REPORT,0
0.077315 MSC,SCAN,0x39 KEY,SPACE,↑ SYN,REPORT,0
1.240154 MSC,SCAN,0x2a KEY,LEFTSHIFT,↓ SYN,REPORT,0
0.082025 MSC,SCAN,0x2a KEY,LEFTSHIFT,↑ SYN,REPORT,0
0.149037 MSC,SCAN,0x1e KEY,A,↓ SYN,REPORT,0 a
0.091403 MSC,SCAN,0x1e KEY,A,↑ SYN,REPORT,0
0.083278 MSC,SCAN,0x1f KEY,S,↓ SYN,REPORT,0 s
0.113698 MSC,SCAN,0x1f KEY,S,↑ SYN,REPORT,0
0.231215 MSC,SCAN,0x20 KEY,D,↓ SYN,REPORT,0 d
...
USB外付けキーボード(QMK)
manglekbdプログラムの出力(時間は差分表示)は、USB外付けキーボード(QMK)だと以下です。
$ sudo intercept /dev/input/by-id/usb-osamuaoki_cgc56-if01-event-kbd | python3 ~/bin/manglekbd -s u
0.751624 MSC,SCAN,0x700e1 KEY,LEFTSHIFT,↓ SYN,REPORT,0
0.277636 KEY,LEFTSHIFT,→ SYN,REPORT,1
0.039919 KEY,LEFTSHIFT,→ SYN,REPORT,1
0.039829 KEY,LEFTSHIFT,→ SYN,REPORT,1
0.040182 KEY,LEFTSHIFT,→ SYN,REPORT,1
0.043845 KEY,LEFTSHIFT,→ SYN,REPORT,1
0.040168 KEY,LEFTSHIFT,→ SYN,REPORT,1
0.040073 KEY,LEFTSHIFT,→ SYN,REPORT,1
0.039748 KEY,LEFTSHIFT,→ SYN,REPORT,1
0.040206 KEY,LEFTSHIFT,→ SYN,REPORT,1
0.040087 KEY,LEFTSHIFT,→ SYN,REPORT,1
0.039937 KEY,LEFTSHIFT,→ SYN,REPORT,1
0.039980 KEY,LEFTSHIFT,→ SYN,REPORT,1 MSC,SCAN,0x700e1 KEY,LEFTSHIFT,↑ SYN,REPORT,0
0.336394 MSC,SCAN,0x700e0 KEY,LEFTCTRL,↓ SYN,REPORT,0
0.087998 MSC,SCAN,0x700e0 KEY,LEFTCTRL,↑ SYN,REPORT,0
0.245960 MSC,SCAN,0x70016 KEY,S,↓ SYN,REPORT,0
0.000777 MSC,SCAN,0x70016 KEY,S,↑ SYN,REPORT,0 s
0.462331 MSC,SCAN,0x70007 KEY,D,↓ SYN,REPORT,0
0.000686 MSC,SCAN,0x70007 KEY,D,↑ SYN,REPORT,0 d
0.316044 MSC,SCAN,0x700e1 KEY,LEFTSHIFT,↓ SYN,REPORT,0
0.281818 KEY,LEFTSHIFT,→ SYN,REPORT,1
0.039781 KEY,LEFTSHIFT,→ SYN,REPORT,1
0.040190 KEY,LEFTSHIFT,→ SYN,REPORT,1
0.040032 KEY,LEFTSHIFT,→ SYN,REPORT,1
0.039996 KEY,LEFTSHIFT,→ SYN,REPORT,1
0.039994 KEY,LEFTSHIFT,→ SYN,REPORT,1
0.039794 KEY,LEFTSHIFT,→ SYN,REPORT,1
0.040185 KEY,LEFTSHIFT,→ SYN,REPORT,1
0.040004 KEY,LEFTSHIFT,→ SYN,REPORT,1
0.039971 KEY,LEFTSHIFT,→ SYN,REPORT,1
0.040058 KEY,LEFTSHIFT,→ SYN,REPORT,1
0.039995 KEY,LEFTSHIFT,→ SYN,REPORT,1 MSC,SCAN,0x700e1 KEY,LEFTSHIFT,↑ SYN,REPORT,0
0.699406 MSC,SCAN,0x7000e KEY,K,↓ SYN,REPORT,0
0.000751 MSC,SCAN,0x7000e KEY,K,↑ SYN,REPORT,0 k
0.099252 MSC,SCAN,0x7000e KEY,K,↓ SYN,REPORT,0 k
0.064789 MSC,SCAN,0x7000e KEY,K,↑ SYN,REPORT,0
0.070250 MSC,SCAN,0x7000e KEY,K,↓ SYN,REPORT,0 k
0.257558 KEY,K,→ SYN,REPORT,1
0.043806 KEY,K,→ SYN,REPORT,1
0.040184 KEY,K,→ SYN,REPORT,1
0.039998 KEY,K,→ SYN,REPORT,1
0.040042 KEY,K,→ SYN,REPORT,1
0.039727 KEY,K,→ SYN,REPORT,1
0.040037 KEY,K,→ SYN,REPORT,1 kk
0.040171 KEY,K,→ SYN,REPORT,1 MSC,SCAN,0x7000e KEY,K,↑ SYN,REPORT,0
気づいたことは:
- タイムスタンプはms単位。(CPUはnsなのでゆっくり)
- 「
SYN REPORT 0
」か「SYN,REPORT,1
」のおまじないが必ずある。時間が同じでもこれがキーイベントの区切り。 - キーを押すと、プレス(↓)となる。
- プレス(↓)の後「
SYN,REPORT,0
」を受けとると、すぐ文字が処理され表示される。(内臓の場合リリースは待っていないぐらい早い) - プレス(↓)の後「
SYN,REPORT,1
」を受けとるときはスキャンコードがない。(外付けキーボードのみ) - キーを長押しすると、250msぐらいで、リピート(→)となる。リピートは30ms毎ぐらい。
- リピート(→)が必ずしも有効追加打鍵にはならない。(最初8回は無視?、合計500ms後から実際の連続打鍵状態)
- リピート(→)の後に同時のキーイベントが発生しやすい。(QMK独特現象)
- キーを離すと、リリース(↑)となる。
- スキャンコードは普通使われないし、外付けキーボードでは標準のATのスキャンコードとはかなり違う。
- キーボードのFn-keyはKC_WAKEUPを出す。
- CapsLock-keyはKEYに続けてLEDのイベントを出す。
これを頭に、現在出回っている、フィルターを見てみました。
HOME-ROW-MODを考えるなら、CHORDED(併せ押し)ではなく、TAP-HOLD(押しの短長)アプローチです。ただ、それもcaps2esc等は、press=HOLDで、RELEASEでタイミングが200ms以下ならTAPを出す感じです。CAPSのような端のキーならまだしも、HOME-ROW-MODでこれをするとまともにタイプできません。
TAP-HOLD時間判断はプログラム内で管理が必要かと思っていましたが、あまりフィルターで使われていないリピート(→)を上手く利用すると複雑なEVENT処理をし無くとも実現できそうな気がします。
Dual Function Keysは、基本イベント処理ループはC
で書かれていて、YAML設定ファイルはC++
で読み込んでいます。別にタイミングチャート図が有り参考になります。TAP-HOLDのスタートや他キーが早くPUSH・RELEASEされた際の対応はいい感じです。他にキー打鍵無い際のHOLD移行にタイマーかREPEATを使わないようなのは気になります。またこれが使うTAP-HOLDのルールーは上記のHOME-ROW-MODで問題の原因の気がします。(最近この辺改善された感もあります)
Interception plugin for vimproved input は、C++
で書かれていて、汎用性は無い形でかかれていて、複雑でタイミングなど読みきれませんがレイヤー処理があります。(SPACE-fn)
interception-k2kは、基本イベント処理ループはC
で書かれていて、設定ファイルはincludeファイルを使う汎用マッパーです。レイヤー機能はありません。設定ファイルがCですが、見通しがいいスタイルですね。REPEATにも対応してそうです。
(Makefileのターゲットの前提要件中に|
を使ってORDER-ONLY-PREREQUISITESを規定するのははじめてみたので、ちょっとと惑いました。)
キーボードのTAP-HOLDタイミング考察
QMKで採用したホームポジションMOD用のパラメーターで確立されているのと同様の遅延処理アプローチが望ましい気がします。そう考えると、キーボードのTAP-HOLDの処理は、HOME-ROW-MODを考えるなら、端のキーの処理では充分な即時処理(immediate)タイプではなく、一見手の込んだ遅延処理(delayed)タイプが必要な気がします。
- 即時処理(immediate): immediate hold (capslock用)
- 遅延処理(delayed): immediate pending state for the key (wait for B↑ or A→) (HOME-ROW-MOD用)
以下キータイミング図で、あるべき動作を思考実験で確認してみました。
どうも、遅延処理の全面導入と、リピートの利用、シフト等の(早期)確定後のシフト解除時にタップが出なくする、等々が必要な感じです。
単一MT キーを使用時の遅延処理有無のタイミング図
これへの対応は必要だが、リピートを使えば意外と簡単。
A: as MT(KC_SHFT, KC_A)
S: KC_SHFT
A: KC_A
----------------------------------------------------------------------------------
<---------200ms--------->
keyboard1: A↓ A↑
immediate: S↓ S↑A↓A↑
delayed: ?? A↓A↑
----------------------------------------------------------------------------------
----------------------------------------------------------------------------------
<---------200ms--------->
keyboard1: A↓ A→ A↑
immediate: S↓ S↑
delayed: ?? S↓ S↑
----------------------------------------------------------------------------------
MTキーと普通キーを両方使用時に、普通キーに遅延処理しない際のベストケースのタイミング図
こういうことができるか考えてみたが、こうすると後から打ったキーが先に処理されるので問題山積でした。やはり次のセクションのように全てのキーを遅延処理するべきみたいですね。
A: as MT(KC_SHFT, KC_A)
S: KC_SHFT (後から打った他キーのリリースでもHOLDにする)
A: KC_A
B: simple key TAP無処理の場合 (これを遅延処理するなら、次のセクションのMT(KC_CTRL, KC_B)と同じ)
----------------------------------------------------------------------------------
<---------200ms---------> (この条件でのシフト入力は高速タイプ対策上必要)
keyboard1: A↓ A↑
keyboard2: B↓ B↑
immediate: S↓ B↓ B↑ S↑ (Aタップ扱いしない処理が必要)
delayed: ?? B↓ B↑S↓ S↑ (Aタップ扱いしない処理が必要)
----------------------------------------------------------------------------------
----------------------------------------------------------------------------------
<---------200ms---------> (普通キーの遅延処理が無いと、MTキーで必然の逆順問題発生)
keyboard1: A↓ A↑
keyboard2: B↓ B↑
immediate: S↓ B↓ S↑A↓A↑ B↑ (逆順出力問題+シフト問題:HOME-ROW-MODで問題)
delayed: ?? B↓ A↓A↑ B↑ (逆順出力問題)
----------------------------------------------------------------------------------
----------------------------------------------------------------------------------
<---------200ms--------->
keyboard1: A↓ A→ A↑
keyboard2: B↓ B↑
immediate: S↓ B↓ B↑ S↑
delayed: ?? B↓ S↓B↑ S↑ (シフト欠落問題)
----------------------------------------------------------------------------------
----------------------------------------------------------------------------------
<---------200ms--------->
keyboard1: A↓ A→ A↑
keyboard2: B↓ B↑
immediate: S↓ B↓ B↑ S↑
delayed: ?? B↓ S↓ B↑ S↑ (シフト欠落問題)
----------------------------------------------------------------------------------
----------------------------------------------------------------------------------
<---------200ms--------->
keyboard1: A↓ A→ A↑
keyboard2: B↓ B↑
immediate: S↓ B↓ B↑ S↑
delayed: ?? S↓ B↓ B↑ S↑
----------------------------------------------------------------------------------
----------------------------------------------------------------------------------
<---------200ms--------->
keyboard1: A↓ A→ A↑
keyboard2: B↓ B↑
immediate: S↓ B↓ S↑ B↑
delayed: ?? S↓ B↓ S↑ B↑
----------------------------------------------------------------------------------
MTキー同士の組み合わせ使用時のタイミング図(遅延処理有りのMTと普通キーとの組み合わせも含む)
どうもこれが望ましい処理ですね。これには遅延処理の待ちリストが必要かな。
A: as MT(KC_SHFT, KC_A)
S: KC_SHFT
A: KC_A
B: as MT(KC_CTRL, KC_B)
C: KC_CTRL
B: KC_B
----------------------------------------------------------------------------------
<---------200ms--------->
keyboard1: A↓ A↑
keyboard2: B↓ B↑
immediate: S↓ B↓B↑ S↑ (Aタップ扱いしない処理が必要)
delayed: ?? ** S↓B↓B↑ S↑ (Aタップ扱いしない処理が必要)
----------------------------------------------------------------------------------
----------------------------------------------------------------------------------
<---------200ms---------> (逆順はMTキー同士では発生しない)
keyboard1: A↓ A↑
keyboard2: B↓ B↑
immediate: S↓ ** S↑A↓A↑ B↓B↑ (シフト問題なし)
delayed: ?? ** A↓A↑ B↓B↑
----------------------------------------------------------------------------------
----------------------------------------------------------------------------------
<---------200ms--------->
keyboard1: A↓ A→ A↑
keyboard2: B↓ B↑
immediate: S↓ B↓B↑ S↑
delayed: ?? ** S↓B↓B↑ S↑
----------------------------------------------------------------------------------
----------------------------------------------------------------------------------
<---------200ms--------->
keyboard1: A↓ A→ A↑
keyboard2: B↓ B↑
immediate: S↓ B↓B↑ S↑
delayed: ?? ** S↓ B↓B↑ S↑
----------------------------------------------------------------------------------
----------------------------------------------------------------------------------
<---------200ms--------->
keyboard1: A↓ A→ A↑
keyboard2: B↓ B↑
immediate: S↓ B↓B↑ S↑
delayed: ?? S↓ B↓B↑ S↑
----------------------------------------------------------------------------------
----------------------------------------------------------------------------------
<---------200ms--------->
keyboard1: A↓ A→ A↑
keyboard2: B↓ B↑
immediate: S↓ B↓S↑B↑
delayed: ?? S↓ ** B↓S↑B↑
----------------------------------------------------------------------------------
----------------------------------------------------------------------------------
<---------200ms--------->
<---------200ms--------->
keyboard1: A↓ A→ A↑
keyboard2: B↓ B→ B↑
immediate: S↓ ?? S↑ C↓ C↑
delayed: ?? ** S↓ S↑ C↓ C↑
----------------------------------------------------------------------------------
----------------------------------------------------------------------------------
<---------200ms--------->
<---------200ms--------->
keyboard1: A↓ A→ A→ A↑
keyboard2: B↓ B→ B↑
immediate: S↓ ?? C↓ C↑ S↑
delayed: ?? ** S↓ C↓ C↑ S↑
----------------------------------------------------------------------------------
フィルタープログラムの考察
フィルタープログラム作成時に必要な配慮点をを列記します。
- 文字差替えの際には同一タイムスタンプのイベントのペア構造や時間順を壊さないようにしたい。
- フィルターによる処理は、論理的にまとまった比較的論理構造が簡単な処理の組み合わせの切り分けデバグできる多段階リレー処理としたい。
- モジュールテスト機能が欲しい。
- 「スキャンコード」、「通常キーコード+キーステート(D/U/R)」、「SYNレポート」の2~3つを1セットの1打鍵としまとめたうえで、処理したい。
- この条件に合わないキーの例外処理は、ログ書き出ししあとで考える。
- 遅延処理の順番待ち管理は状態遷移は、初期(0)、未定(1)、タップ確定(2)、ホールド確定(3)、リリース確定(0)とし、状態遷移毎に処理手続きを決める。
- 1セットの打鍵にレイヤー状態を考慮し、状態遷移にあわせてアクションのシーケンスに置き換え処理したい。
- アクションとは、キー出力(MTだと複数)や、LAYER状態の更新を意味する。
- レイヤー切り替えは、レイヤーを出た後にレイヤー内で押したキーがキーリリースされずに困らないように注意する。
- QMKは、レイヤー切り替えで強引にHOLDによるシフト状態をリリースはせず、実際のリリースまで待ってる。
- 実際の出力は1セット打鍵分ごとにI/O FLUSH
- QMKで言うMTやLT(含むTO, MO)に相当する操作で発生するFAKEのキーイベントには、無理に適当な「スキャンコード」をつけずに「
SYN,REPORT,1
」で対応するのも一策かも。事件検証はする必要有り。 - 本物のキーボード端にあるESCキーの超長押し(リピート20回?)で、フィルター機能をOFFにして完全透過処理にする機能は安全策として考えたい。
フィルター機能の設定法の考察
evdevデーターのフィルター機能の設定法を考えました。
- 実行時に複雑なロジックを組みたくないので、できるだけ簡単なアクション指定のSTRUCTのMATRIXとして、簡単な構造としたい。
- evdevデーターのフィルターの設定ファイルは、ベタで書き下すと、ほとんど無変換なのでかなり冗長(128~256キー*4レイヤーで512~1024行)。手動で書くのは辛い。
- 前処理プログラムで短いデーターから設定ファイルを生成するようにし、手動で冗長な記述をしなくてもいいようにしたい。
- 前処理プログラムでつくる設定ファイルなので、内容は人間に分かりにくくてもプログラムが読み込みしやすい事を優先する。HEXADECIMALの数字のCSV羅列等でもいい。(最初にデーター数、最終データー後のマーカーぐらいつけますか)
- 各キー毎にTAP時/HOLD時のアクションを決める。設定ファイルではTAP時とHOLD時のアクションが同じ時は簡便記述できるようにする。
- 汎用性を持たすには、キーシーケンスのマクロ処理機能があると、ロックキー・一時シフトキーなども扱いやすそう。
- QMKのように物理状態からのキー出力ではなく、シリアル化されたデーター列の変換なので、キーデーター列異常は気になる。
- キーデーター列異常時の対応は、実際に試してみないとわからない面がある。そういった意味でもキー状態異常の監視がプログラム化できるベースとなるのでPythonで書かれたロガーは面白い。
- 各キーポジション毎に状態を保存する大MATRIX変数で管理するのか、比較的短くてすみそうな処理待ち行列変数で対応するのかは考えどころ。
試作中のフィルター evmk
systemdのjournalへのログ出力ができるようにした、改良版のフィルター・ログ用のプログラムevmkを試作中です。
Previous Post | Top | Next Post |