Chapter 11

DLC1:帰ってきた、謎の Unicode 群

zetamatta
zetamatta
2021.02.15に更新

ある日、一行入力パッケージ:go-readline-nyでのタブ文字についての取り扱いについて検討するため、ちょっと動かして調べてたら、とんでもないことに気づきました。

👨‍🌾(農夫)が、右クリックで WindowsTerminal にペーストできなくなってるぅぅぅぅ<

さっそく、go-console-test で検証です(以下のコードはちょっと長いですが、エビデンス的に載せてるだけなので、一応、読まなくても大丈夫です)

$ ./go-console-test.exe
Hit ESCAPE key to stop.
KeyEventRecord: &{KeyDown:0 RepeatCount:1 VirtualKeyCode:13 VirtualScanCode:28 UnicodeChar:13 ControlKeyState:0}

→ 起動時の Enter 入力

KeyEventRecord: &{KeyDown:1 RepeatCount:1 VirtualKeyCode:18 VirtualScanCode:56 UnicodeChar:0 ControlKeyState:2}
KeyEventRecord: &{KeyDown:1 RepeatCount:1 VirtualKeyCode:102 VirtualScanCode:77 UnicodeChar:0 ControlKeyState:2}
KeyEventRecord: &{KeyDown:0 RepeatCount:1 VirtualKeyCode:102 VirtualScanCode:77 UnicodeChar:0 ControlKeyState:2}
KeyEventRecord: &{KeyDown:1 RepeatCount:1 VirtualKeyCode:99 VirtualScanCode:81 UnicodeChar:0 ControlKeyState:2}
KeyEventRecord: &{KeyDown:0 RepeatCount:1 VirtualKeyCode:99 VirtualScanCode:81 UnicodeChar:0 ControlKeyState:2}
KeyEventRecord: &{KeyDown:0 RepeatCount:1 VirtualKeyCode:18 VirtualScanCode:56 UnicodeChar:55357 ControlKeyState:0}

→ U+D83D (MANのサロゲートペア前半っぽいが、KeyDownコードがない)

KeyEventRecord: &{KeyDown:1 RepeatCount:1 VirtualKeyCode:18 VirtualScanCode:56 UnicodeChar:0 ControlKeyState:2}
KeyEventRecord: &{KeyDown:1 RepeatCount:1 VirtualKeyCode:102 VirtualScanCode:77 UnicodeChar:0 ControlKeyState:2}
KeyEventRecord: &{KeyDown:0 RepeatCount:1 VirtualKeyCode:102 VirtualScanCode:77 UnicodeChar:0 ControlKeyState:2}
KeyEventRecord: &{KeyDown:1 RepeatCount:1 VirtualKeyCode:99 VirtualScanCode:81 UnicodeChar:0 ControlKeyState:2}
KeyEventRecord: &{KeyDown:0 RepeatCount:1 VirtualKeyCode:99 VirtualScanCode:81 UnicodeChar:0 ControlKeyState:2}
KeyEventRecord: &{KeyDown:0 RepeatCount:1 VirtualKeyCode:18 VirtualScanCode:56 UnicodeChar:56424 ControlKeyState:0}

→ U+DC68(MAN のサロゲートペア後半っぽいが、KeyDownコードがない)

KeyEventRecord: &{KeyDown:1 RepeatCount:1 VirtualKeyCode:18 VirtualScanCode:56 UnicodeChar:0 ControlKeyState:2}
KeyEventRecord: &{KeyDown:1 RepeatCount:1 VirtualKeyCode:102 VirtualScanCode:77 UnicodeChar:0 ControlKeyState:2}
KeyEventRecord: &{KeyDown:0 RepeatCount:1 VirtualKeyCode:102 VirtualScanCode:77 UnicodeChar:0 ControlKeyState:2}
KeyEventRecord: &{KeyDown:1 RepeatCount:1 VirtualKeyCode:99 VirtualScanCode:81 UnicodeChar:0 ControlKeyState:2}
KeyEventRecord: &{KeyDown:0 RepeatCount:1 VirtualKeyCode:99 VirtualScanCode:81 UnicodeChar:0 ControlKeyState:2}
KeyEventRecord: &{KeyDown:0 RepeatCount:1 VirtualKeyCode:18 VirtualScanCode:56 UnicodeChar:8205 ControlKeyState:0}

→ U+200D(ZeroWidthJoiner。これも KeyDownコードがない)

KeyEventRecord: &{KeyDown:1 RepeatCount:1 VirtualKeyCode:18 VirtualScanCode:56 UnicodeChar:0 ControlKeyState:2}
KeyEventRecord: &{KeyDown:1 RepeatCount:1 VirtualKeyCode:102 VirtualScanCode:77 UnicodeChar:0 ControlKeyState:2}
KeyEventRecord: &{KeyDown:0 RepeatCount:1 VirtualKeyCode:102 VirtualScanCode:77 UnicodeChar:0 ControlKeyState:2}
KeyEventRecord: &{KeyDown:1 RepeatCount:1 VirtualKeyCode:99 VirtualScanCode:81 UnicodeChar:0 ControlKeyState:2}
KeyEventRecord: &{KeyDown:0 RepeatCount:1 VirtualKeyCode:99 VirtualScanCode:81 UnicodeChar:0 ControlKeyState:2}
KeyEventRecord: &{KeyDown:0 RepeatCount:1 VirtualKeyCode:18 VirtualScanCode:56 UnicodeChar:55356 ControlKeyState:0}

→ U+D83C(稲穂のコードのサロゲートペアの前半。KeyDo…)

KeyEventRecord: &{KeyDown:1 RepeatCount:1 VirtualKeyCode:18 VirtualScanCode:56 UnicodeChar:0 ControlKeyState:2}
KeyEventRecord: &{KeyDown:1 RepeatCount:1 VirtualKeyCode:102 VirtualScanCode:77 UnicodeChar:0 ControlKeyState:2}
KeyEventRecord: &{KeyDown:0 RepeatCount:1 VirtualKeyCode:102 VirtualScanCode:77 UnicodeChar:0 ControlKeyState:2}
KeyEventRecord: &{KeyDown:1 RepeatCount:1 VirtualKeyCode:99 VirtualScanCode:81 UnicodeChar:0 ControlKeyState:2}
KeyEventRecord: &{KeyDown:0 RepeatCount:1 VirtualKeyCode:99 VirtualScanCode:81 UnicodeChar:0 ControlKeyState:2}
KeyEventRecord: &{KeyDown:0 RepeatCount:1 VirtualKeyCode:18 VirtualScanCode:56 UnicodeChar:57150 ControlKeyState:0}

→ U+DF3E(あああああ

KeyEventRecord: &{KeyDown:1 RepeatCount:1 VirtualKeyCode:27 VirtualScanCode:1 UnicodeChar:27 ControlKeyState:0}
<DESKTOP-LGGUCRA:~/go/src/github.com/zetamatta/go-console-test>
$

APIから入力がとれなかった謎の Unicode 群!の不具合「キーを押した時のレコードの UnicodeChar フィールドがゼロになっていて、キーを離した時の UnicodeChar フィールドにしか文字コードが入っていない」という症状がより悪化して、その範囲が広がっているのDEATH!…orz

しかしです。「異体字」と格闘していたころは 1.4.3243.0 で、そのころはまだ農夫は普通に入力できていました。入力できなくなった現在の WindowsTerminal は 1.5.10271.0 です。この間に WindowsTerminal の仕様が変更されたと見てよいでしょう。でも、変わってしまったものは仕方がありません。対策を打たねばなりません。

とはいえ… CMD.EXE を WindowsTerminal 1.5 で動かした時は化けて入るものの、ちゃんと入力自体は出来ているんですよね

これはもしかしたら、ちゃんとした「仕様」なのかもしれません。いろいろと考えた結果、次のように結論づけました。

  • 一般の入力は、オートリピート:押しっぱなしにすると、そのキーが押している間何回も入力されるという仕様を実現するために「キーが押された瞬間に文字コードが入力される」という動作になっている
  • IMEがない環境で利用されるALT+テンキーによる文字入力に関しては、ALTを離した瞬間になって初めて Unicode が確定するので「ALTキーが離されたタイミングで文字コードが入力される」という動作にせざるをえない
    • ALTキーが離されるまで文字コードは確定されないから、途中のALT+テンキーの数字が何であろうと実は関係ない。なぜなら、最終的に ALT を離した時の UnicodeChar フィールドだけを参照すればよいから。ALTキー+テンキーは一回以上押下が確認されば十分であり、入っているキー操作自体は「キーボード入力をエミュレーションするだけの右クリックによるペーストにおいては」ダミーで適当なものが入っているだけだと考えて差し支えない

ということで、文字コードを ReadConsoleInput から拾い上げるところを次のように改めました。

  • ALTキーが離された時に文字コードを UnicodeChar フィールドから読み上げる条件として
    • 「ALTキーが押された」だけでなく「ALTキーが押されている間にテンキーが1回以上押された」ことも条件に追加
    • そのかわり、以前入れていた、対象とする Unicode を U+2000~U+2FFF に限るという条件を撤廃

まぁ、これで、とりあえず見かけ上は期待どおりには動くようになりました。この修正が果たして適切なものであるかは Microsoft さんのみが知るところですが…なかなか難しい話です。

( WindowsTerminal に issue を立ててもよかったんですが、上のように「おそらく仕様」である可能性を考えると、ちょっとそれにエネルギーを使うパワーは捻出できませんでした、モチベ的にも、語学的にも )