Chapter 07

APIから入力がとれなかった謎の Unicode 群!

zetamatta
zetamatta
2021.02.07に更新
このチャプターの目次

発端

拙作のコマンドラインシェル nyagos にこんな issue が立てられました(残念ながら、今は issue は消えてしまったので、下記は GitHub のメンションメールから起こしたものです)

I see that 2 byte characters are currently supported:
https://en.wikipedia.org/wiki/UTF-8
that is, essentially everything up to U+07FF. Example:

Ç (U+00C7)
You can paste this or enter it with Alt + 128 on Windows, assuming Windows
Terminal is being used. However it seems 3 and 4 byte characters are not
supported. Example:

₧ (U+20A7)
with PowerShell, you can paste the character, or on Windows you can enter as
Alt + 158. However Yori does not accept paste or keyboard input of this
character.

Windowsでは、ALTキー+テンキーの数字キー(例:ALT+128:フルキー側の数字はダメ)などで、ASCIIにない、ドイツの文字やギリシャ文字などヨーロッパで使われている文字・記号が入力できるようになっています。が、リポートによると、nyagosでは Ç (U+00C7)の入力は ALT+128 で出来るのに、₧ (U+20A7)の入力はAlt+158で出来ないそうです。

こちらの環境にはテンキーはなかったのですが、マウスの右クリックでのペーストで同じ状況が再現できました(なお、Ctrl-Y によるペーストはコンソールを経由せず、直接クリップボードを見ているので発生しない)。その後の調査により

  • ⌂ (U+2302)
  • ⌐ (U+2310)
  • ░ (U+2591)
  • ▒ (U+2592)
  • ▓ (U+2593)

といった文字についても同様の現象が起きていました。範囲を絞ってみたところ、どうも U+2000~U+2FFFあたりの文字が入力できないようです。なんじゃこりゃ!

症状の分析

そこで簡単な検証プログラムを書いて、どういうことが起きているかを調べてみました。

Ç (U+00C7) が入力された時

$ ./go-console-test.exe
Hit ESCAPE key to stop.
KeyEventRecord: &{KeyDown:0 RepeatCount:1 VirtualKeyCode:13 VirtualScanCode:28 UnicodeChar:13 ControlKeyState:0}
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}
KeyEventRecord: &{KeyDown:1 RepeatCount:1 VirtualKeyCode:27 VirtualScanCode:1 UnicodeChar:27 ControlKeyState:0}

最初の 13 というキーコードは、go-console-test.exe を実行する際に入力した Enter キーを離した時のコード、最後の 27 は go-console-test.exe を終了させるための Esc キーを押した時のコードなので無視してください。
その間で Ç という文字を右クリックでペーストしているのですが、該当する Unicode の 199=U+00C7 のキーが押され、次に離したコードがちゃんと受信できています。

₧ (U+20A7)が入力された時

$ ./go-console-test.exe
Hit ESCAPE key to stop.
KeyEventRecord: &{KeyDown:0 RepeatCount:1 VirtualKeyCode:13 VirtualScanCode:28 UnicodeChar:13 ControlKeyState:0}
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:8359 ControlKeyState:0}
KeyEventRecord: &{KeyDown:1 RepeatCount:1 VirtualKeyCode:27 VirtualScanCode:1 UnicodeChar:27 ControlKeyState:0}

何かおかしい!₧ (U+20A7)を表すコード 8359 について離した時のコード(ただし、仮想キーコードは 18==0x12 (VK_MENU) となっている)しか入っておらず、押した時のコードがありません。そのかわり

  • RepeatCount:1 VirtualKeyCode:18 VirtualScanCode:56 UnicodeChar:0 ControlKeyState:2
    • 左ALT + VK_MENU(18=0x12):ALTキーを押したコード。ただし、Unicodeはゼロ
  • VirtualKeyCode:102 VirtualScanCode:77 UnicodeChar:0 ControlKeyState:2
    • 左ALT + VK_NUMPAD6(102=0x66):テンキーの6を押したコードと離したコード
  • VirtualKeyCode:99 VirtualScanCode:81 with ALTキーの押し&離し
    • 左ALT + VK_NUMPAD3(99=0x63):テンキーの3を押したコードと離したコード

が挿入されていました(参考:KT Software - 仮想キーコード一覧)。

つまり、ALT,テンキーの6,3を順に押したかのようなシーケンスが入っているのです。勘がよい人は「お、これは ALT+テンキー入力を再現するシーケンスでは?」と思われるのではないかと思います。が、₧ (U+20A7)を入力するためのシーケンスは Alt+158なので微妙に一致しないんですよね。何か法則性がありそうな気はするんですが

発生する環境

この現象、どこでも起きるかと思えば、さにあらず。Windows 8.1 環境では起こりません。k_takata さんより寄せられた情報も合わせて再現環境を調べてみると、次のような状況のようです。

  • Windows 8.1 : OK (問題は発生しない)
  • Windows 10
    • 1803 : OK
    • 1909 : NG (KeyDown で UnicodeChar がゼロになってしまう)
    • 2004 : NG (KeyDown で UnicodeChar がゼロになってしまう)

どうも、1803~1909 の間で、ReadConsoleInput の挙動が変わった疑いがあります。しかしながら、CMD.EXE ではどの環境でも ₧ (U+20A7)の入力に問題はありません。

対応編

結局、Goのコンソール用のキー入力ライブラリの go-tty に、次のような駄パッチをお送りしました(現在はマージ済み)

その後、これを任意の Unicode に対してやっているとALT+TABなどでコンソールを切り替えた時に同じ文字が二回入ってしまうことが分かったので、次のパッチで対象とする Unicode を U+2000~U+2FFF までとするようにしていただきました。

これによって、とりあえず U+2000~U+2FFF のコードが入力できないという現象については一旦解決しました。が、いかにも workaround という感じで、あまり美しくない回避策なので、本当は根本的な解決をしたいところです(正直、mattn先生の go-tty を汚してしまった感が拭えません…)