そもそもの発端は、この issue:
∞ (U+221E) という文字が入力できないという話。どうも、Unicode の U+2000~U+2FFF の一部のコードを右クリックで Windows のコンソールにペーストすると、ReadConsoleInput で得られるデータが妙なことになるようだ(だが、それにも関わらず、CMD.EXE はうまいこと拾ってるんだよな)
それを確認するために、go-console-test という検証プログラムを作った。
まずは正常に入力される 「Ç (U+00C7)」を右クリックでペーストした結果:
KeyEventRecord: &{KeyDown:1 RepeatCount:1 VirtualKeyCode:0 VirtualScanCode:0 UnicodeChar:199 ControlKeyState:0}
KeyEventRecord: &{KeyDown:0 RepeatCount:1 VirtualKeyCode:0 VirtualScanCode:0 UnicodeChar:199 ControlKeyState:0}
UnicodeChar フィールドに 199=0xC7 に対する KeyDown と KeyUp がペアで順に入ってくる。
次に問題の「∞ (U+221E)」。これがおかしい
KeyEventRecord: &{KeyDown:1 RepeatCount:1 VirtualKeyCode:18 VirtualScanCode:56 UnicodeChar:0 ControlKeyState:2}
KeyEventRecord: &{KeyDown:0 RepeatCount:1 VirtualKeyCode:18 VirtualScanCode:56 UnicodeChar:8734 ControlKeyState:0}
KeyDown の UnicodeChar がゼロになっているのだ。だが、KeyUp の時の UnicodeChar はちゃんと8734=0x221E が入っている。
報告者によると、⌂(U+2302)、⌐ (U+2310)、░(U+2591)、▒(U+2592)、▓(U+2593)も同じ傾向があるようだ。共通することは
- KeyDown 時に UnicodeChar が 0、ControleKeyState=2 (左ALT)、VirtualKeyCode==0x12 (VK_MENU) というデータがくる
- その KeyDown ~ KeyUp までに0個以上の謎の KeyDown イベントが入ることがある
- 最後に来る KeyUp イベントについては、ちゃんと UnicodeChar に期待する Unicode が入ってくる。
とりあえず、エンドユーザには分からない話だから(彼は nyagos が1文字3バイト以上の UTF8文字を扱えないからだと勘違いしていて、なかなか失礼な話である)、とりあえず以下のようなアドホックな回避コードを go-tty にあてた。
- KeyDown された時に、左ALT (ContolKeyState=2) + VK_MENU (VirtualKeyCode==0x12) の時は、フラグをセットする
- KeyUp された時、普通はこれを無視するところだが、フラグがたっていて、かつ UnicodeChar != 0 の時はその UnicodeChar が入力されたと見なす。この後、当然だがフラグはクリアする
これで一応、同文字が普通にペーストできるようはなった。go-tty に対するプルリクも受理され、マージいただけたようだ。先生もさぞ困惑されたことだろう。ちょっと無理やりな対応コードで申し訳ないことである(リジェクトされても仕方ないと思っていた)。
しかし、これ、どう考えても合理的な挙動に見えない。ReadConsoleInput に、何か、理解できていない仕様があるのだろうなぁ
追記
どうも、このような挙動がまったく起こらない環境もあるようだ。自分の手元の環境では、
でも、Windows10 で問題が出ていない環境もあるそうなので、OS のバージョンとも決めつけられない。どういう条件で発生するんだろう。
追々記
得られた情報を総合すると:
- Windows 8.1 : OK
- Windows 10
- 1803 : OK
- 1909 : NG (KeyDown で UnicodeChar がゼロになってしまう)
- 2004 : NG (KeyDown で UnicodeChar がゼロになってしまう)
どうも、1803~1909 の間で、ReadConsoleInput の挙動が変わった疑いがありますね。
( k_takata さん、情報ありがとうございました (← 追々々記:お名前間違えてすみません。直しました) )
2024.05.07 追記
Windows11 Pro 21H2 では直っている模様
$ go-console-test.exe
Hit ESCAPE key to stop.
KeyDown:0 UnicodeChar:13(0xD) VirtualKeyCode:13(0xD) VirtualScanCode:28(0x1C) ControlKeyState:0(0b0)
KeyDown:1 UnicodeChar:8734(0x221E) VirtualKeyCode:0(0x0) VirtualScanCode:0(0x0) ControlKeyState:0(0b0)
KeyDown:0 UnicodeChar:8734(0x221E) VirtualKeyCode:0(0x0) VirtualScanCode:0(0x0) ControlKeyState:0(0b0)
KeyDown:1 UnicodeChar:27(0x1B) VirtualKeyCode:27(0x1B) VirtualScanCode:1(0x1) ControlKeyState:0(0b0)
go-tty に Merge してもらった回避用Fix、削除してもらった方がいいような気もする一方、巷にこの回避コードがまだ必要な Windows バージョンも残っているかもしれないことを考えると下手に戻すのも危いかもしれない。
また、Windows10 ではUNIX型仮想端末入力機能が実装されたので、そっちを使えという考え方もある。むしろ、もう検証が難しい Windows 8 までの対応を考えると、今動いているコードを下手に触るな原則もある。
悩ましい… (たぶん、何もできない可能性が高い)