雑記帳

電子計算機の"明後日"から、他愛もない話まで。

Windows8以降のIME状態通知に関するお詫びと訂正

随分前になりますが、Windows8.1で「親指の友」が動かない件(原因と対策)という記事の中で、「Windows 8.1以降では旧来の親指シフトドライバで必要な通知が飛ばなくなった」という趣旨の記述をしていたのですが、別件を発端とする調査の結果、これが一部事実と異なることがわかりました。

既に当時調査を行った機体もWin10化してしまっているので、再度Win8.1で確認し直すことはできないのですが、少なくともWin10(1709)では、以下の挙動が確認できました。

  • x86版かつPS/2接続のキーボードに紐付くドライバ宛には、正しくIME状態の通知が行われるように見える
    これは、Windows付属KB211用ドライバ(親指シフト PS/2 キーボード)のモード同期処理が正しく動作することを意味します(ストアアプリ相手であってもOKでした)。同じ仕組みを利用する初代親指の友ドライバも正しく動作する可能性が高いです。
  • x64版ではIME状態の通知を受け取る部分の処理が消されているらしく、IME状態の通知を行っても何も起こらない
    つまり、Windows付属の親指シフトキーボードドライバ(FMV-KB211用)を入れても、キーボード側の入力モードがずれます。
    調査手法の都合上、そもそも通知を飛ばす機能自体が生きているのかどうかの確認は行えていません。
  • USB接続のキーボードに紐付くドライバ宛には、一切のIME状態通知が行われない。もしくは、キーボードクラスドライバのインスタンス番号が先頭以外のキーボードに紐付くドライバ宛には、一切のIME状態通知が行われない(この調査のみ、Win10 1803 x86)。
    調査手法・手持ち機材の都合上、どちらが正解なのか確認できていません(USB接続 かつ インスタンス番号先頭の場合や、PS/2接続 かつ インスタンス番号が先頭以外の場合が確認できていません)。

また、x86版側の挙動については、Win XP(SP3)環境でも相違ないことが確認できました。x64版XPは環境が無い為不明です。XPでの挙動から察するに、8.1でも上記と同様の動作だった可能性が高いと考えられます(勿論、一時的におかしくなっていたのが後に修正された可能性もゼロでは無いですが…)。

 

もし、当時その記事(や、その続きの記事)をご覧になり、Win7からのアップグレードを控えられたり、或いはそれを機に(Windows標準ドライバが同じ機能に依存している)KB211等のキーボードを予備役にしてしまった方等がいらっしゃいましたら、大変申し訳ないです。お詫びして訂正します。少なくとも現行Win10のx86版(32bit版)をお使いであれば、PS/2接続など特定の条件下で通知に依存するドライバも正しく動作するようですので、お試しください。

 

また、旧Oyayusbyに付属する説明書等には「WindowsはカナロックLEDの制御を行わない」と書いていましたが、これも誤りで、正確には「カナロックLEDの制御自体は今(Win10 1709/1803 x86/x64)でも行われるが、IMEの状態や実際の入力状況とは特に連動しておらず、IME OFF状態でカナロック操作(Ctrl + Shift + カタカナひらがな)を行った時にトグルする以外は挙動不審(IME OFFでLED点灯状態なのに入力は英字…なども普通に起こる)」というものでした。重ねて訂正いたします。尚、これはカナロックLEDが存在すると通知したUSB HIDキーボード(ようはOyayusby)での挙動なので、AXキーボードなど、AT / PS/2接続のものでも同じかどうかは不明です(自分が持つLEDの通知手段が無いので…)。

 

以下、前者の件についての詳細を書きます。

 

発覚までの経緯と確認内容

掲示板の方でリクエストがあった、OyayusbyをKB211と組み合わせて使う為のカナロック制御アプリ(Oyayusby Utility)を開発するにあたって実験を行っていた時の話です。グローバルフックの実験が上手くいき、IMEの状態も取れたので、試しにPS/2接続用親指シフトキーボードドライバのモード同期を行う機能を組み込んでテストしようとしていました。

ここで比較用として、このアプリを走らせない状態での挙動(x86)を確認したところ、意図に反してIME状態の同期が行われてしまったことから、今回の件が発覚しました。具体的には、Windows標準のKB211用親指シフトキーボードドライバ(親指シフト PS/2 キーボード)をインストールし、マウス操作でIMEの入力モードを変化させ(英小⇔かな)、そのモードで正しく入力できるかどうかを確認しました。かなに切り換えると正しく入力できない(対応するキーのJISかなが入力される)挙動を期待していたのですが、実際には正しく追随されてしまった…という訳です。

原因特定の為に調査を進める過程で、機材の都合からx64版の環境を使用したところ、今度は一切IME状態の同期が行われなかったことから、x64版とx86版で動作が異なることが判明しました。上記実験アプリを使っても同期不可です(この件の詳細は後述します)。本当はJapanistの有無で挙動が異なるかを見ようとしたのですが、こちらは関係無かったようです(2003 / 10共に、あっても無くても挙動は変わりませんでした)。

さらに、当時記事を書くにあたってテストした環境と近い状態で確認すべく、同一の機体(ASUS T100TA-DK32G。但し、OSはWin8.1ではなく、Win10 1709 x86)にて旧親指の友ドライバのかな入力時同時打鍵が効くか試したところ、こちらは当時と同じで正しく入力できませんでした(つまり、IME状態の通知が来ていない)。当該機体にPS/2ポートは無く、キーボードドックのキーボードはUSB HIDキーボードとして認識されている為、USBキーボード限定で通知が来ていなかった可能性が出てきました。

そこで、PS/2ポートを持つ機体(Win10 1709 x86)にOyayusby + 初代親指の友ドライバを接続し、初代親指の友ドライバのレイアウトファイルが使用される状況下で同様の調査を行いましたが、やはりUSB HIDキーボード側ではかな入力時の同時打鍵操作が正しく行えませんでした。

しかし、わざわざIME状態通知を飛ばす側でPS/2キーボードかUSB HIDキーボードかを見て、前者の場合だけ通知を飛ばす…みたいな器用なことをやってるのか甚だ疑問です(できなくはなさそうだが、かなり面倒そうに見える)。特に、T100TA-DK32Gは本体の音量調整ボタンなどもキーボードとして認識されており、キーボードクラスドライバインスタンスはキーボードドック取り付け時点で2つになっています。そこで、キーボードクラスドライバインスタンスの列挙順で先頭のものに決め打ちで通知を飛ばしているのでは? と仮説を立ててみました。キーボードクラスのインスタンス番号とキーボードデバイスの対応は以下のレジストリキー以下で確認できそうです。

HKLM\SYSTEM\CurrentControlSet\Control\Class\{4d36e96b-e325-11ce-bfc1-08002be10318}

これまで試して上手くいかなかったキーボードのインスタンス番号はいずれも0001となっており、先頭に他のデバイスが割り当てられている状態でした(PS/2ポートがある機体ではPS/2接続のキーボード。T100TA-DK32Gでは音量調整等のGPIOボタン)。T100TA-DK32G(但し、上述の調査から時間が空いてから実施した為、Win10 1803 x86環境に変更済)で確認する限り、先頭インスタンスのデバイスを削除・再起動することでこの順序の入れ換えができるようでしたので、この操作の後にキーボードドックの初代親指の友ドライバでかな入力が正しく行えるかを見てみました…が、ダメでした。

改めて確認してみると、

  • 他にもkbdclassのインスタンス列挙に関係してそうなエントリがある
    HKLM\SYSTEM\CurrentControlSet\Services\kbdclass\Enum
    削除・再起動後にここを確認すると、前述のキーと異なり、順序の入れ換えが行えていません(GPIOボタン側が先頭のまま)。
    但し削除後・再起動前に見ると、先頭(0番)にUSBキーボードが来ます(一時的)。
  • 実際にキーボードクラスドライバへ通知を飛ばす際に必要なハンドルを取る手段として、レジストリを参照せずに行う古いやり方がある
    以下のパスをCreateFile()で直接開くことで、該当するキーボードドライバのハンドルを得ることが可能です。
    \\.\Device\KeyboardClass*
    ※(*は0~)
    削除後・再起動前に、SysinternalsのWinObjで上記オブジェクトが存在するか見ると、KeyboardClass0側が消えていて、KeyboardClass1は残っています。
  • 先の列挙方法も含めた3つとも、列挙される順序は連動して無いように見える
    <divそれぞれ番号がずれる場面があるので(削除後・再起動前)。

となっていました。また、GPIOボタン側の削除・再起動を繰り返しても同上でした。PS/2ポートを持つ機体については、そもそもPS/2キーボードを削除したときの挙動が見れなかった為(再起動するまで削除されない・再起動するとPnPで自動的に再インストールされる)、ハンドル側の挙動について上記に当てはまるか不明です。

上記ハンドルを取る手段については他の方法(Setup API経由での列挙。順序は最初に紹介したレジストリキーと同一でした)よりお手軽ですし、Win2000より前にはSetup APIが存在しなかった為、IME状態通知の歴史(恐らく初期のNTから存在する)を考えると、このオブジェクトを使っている可能性が一番疑わしいのではと考えています。具体的に起こっている状況を想像すると、

  1. システム側のIME状態通知送信部の内部実装が\\.\Device\KeyboardClass0を決め打ちで開いている
  2. KeyboardClass*の割り当て順序にある程度の法則性がある
    例えば、脱着不可なレガシデバイス(PS/2)やルートバスに近い方(GPIOボタン等)から順にハンドル生成される等。

なんてことになっていて、上記2つより、

  1. PS/2ポートを持つ機体では、このポートが常にKeyboardClass0になっていて、親指シフトキーボード用ドライバの動作に支障がないように働いている
  2. 脱着可能かつUSBポート経由のキーボードドックはGPIOボタンよりオブジェクト生成順序が後ろになり、IME状態通知が来ることはない

みたいな状況になっている可能性は割と考えられます。このハンドルが具体的にどういう順序で生成されるのか、あるいは現状どのインスタンスと結びついてるかを手軽に見る方法があると良いのですが、前者はざっと調べた限り具体的な情報は見つかりませんでしたし、後者もkd使わないとダメそうなようなので(RS-232Cで2台直結…)、今回これ以上の深追いはあきらめました。

尚、GPIOボタン側にダミーのフィルタドライバ(IME状態通知が来たときにログ出力等)を仕込めば切り分けは可能ですが、今回はそこまでは実施しませんでした(或いは、キーボードクラスドライバのインスタンスがUSB HIDキーボードしか存在しない機材が手元にあれば良かったのですが…)。

 

なんで当時間違えたか

ひとえに「実験環境の組み方が悪かった(ことに気づかなかった)」という点に尽きます。

当該Win8.1機(ASUS T100TA-DK32G)は当時の手持ちで唯一となるWin8以降の環境(但し32bit)であり、かつ唯一PS/2ポートを持たない機体でした(これは今でもそうです)。前述の通り、当該機体のUSB HID経由のキーボードドライバには通知が来ないようなので、これを以て「通知が来ないよう変更された」と思ってしまった訳です。

このドライバは元々PS/2ポートでの使用を前提としていて、複数インスタンスが実行されることは考慮しておらず(一応最低限の行儀は満たしていますので、複数動作させてワークを破壊したりすることは無いです)、この件以前に複数のキーボードクラスが登録された環境での動作や、USBキーボードへのアタッチ動作を確認したことはありませんでした(Oyayusby作る前はUSBキーボードが手元に無かったというのもありますが)。今冷静に考えると「じゃあ真っ先に疑うべきはUSB化 / 複数インスタンス化だったろ」という話なのですが、当時の状況を思い出せる限り確認したり再現実験を行ったりした限り、infの改造でUSBキーボードへのドライバ導入ができることを以て「フィルタドライバから見たキーボードの通信I/Fは同じ」と判断し、IME状態通知が一部のキーボード宛にしか来ないことについては想像が及ばなかったようです…。

また、残りの環境をWin10化した時点では既にMk-2ドライバしか使用しておらず、旧作ドライバ等のテストも(Win8.1時代の結論より、ハナから使えないと思って)しなかったことや、Win7以前のx64版を常用する機会に恵まれなかったことなど、本件に気づくきっかけを悉く逃していたのも重なってしまいました。

 

x64版でIME状態通知を受け付けない件の詳細

無理やり投げてみても動かなかった…というのは上述の通りですが、じゃあなんで?という話です。

Windows付属KB211用ドライバで、実際に通知を受けてキーボードへのコマンドを発行しているのは、Windows標準のPS/2ポートドライバ(i8042prt.sys)です。なんでそんなことがわかるのかというと、実は2003 Server頃の古いDDKには、こいつのソースがサンプルとして同封されていた為です(input\pnpi8042として収録)。現在のWDKサンプルには含まれていないようですが、本記事投稿時点ではまだ2003DDKの入手が可能のようですので、見てみたい方はこちらから。

こいつを見ますと、IME状態通知関連の部分が、軒並み以下のようなifdefで切られています。

#if defined(_X86_)

昔読んだときは純粋に「Alpha / MIPS / PowerPC避け」だと解釈したのですが(多分書いた人の意図も同様と思います。i8042prtのPnP対応が行われたのは恐らくWin2000の時で、当時x64版はまだ存在しませんでした)、改めて今見ると、これじゃぁx64版も除外されてしまうのですよねぇ…(x64版は_AMD64_。x86の表記自体はx64を内包する使い方もあるのでマジ紛らわしい…)。

そもそもx64版とx86版でこの部分の挙動を変える必然性がイマイチ謎ですし(Alpha等の除外については、これらを積んだFMVが無い為…と解釈できます)、x64版にも「親指シフト PS/2 キーボード」のinfエントリやら専用レイアウトファイル(f3ahvoas.dll)やらが含まれているのを考えると、x64版で使えないのは単なるミスで、中の人が誰も気づいてないだけなのでは?…という気がします。

ついでに言うと、たとえ非IA-32機であってもPS/2ポートを持つ機体ならKB211を差すこともできたでしょうから(しかもこいつはスキャンコード3に対応してたので、これが必要なWSとかでも使えたはずなんですよね)、#if definedで切る必要すらないと思うのですが…やっぱしFMVのオプションという位置づけの対応なんですかねぇ…。

尚、ソースが見れるのはi8042prt.sysの中身だけなので、送信側がどういう風になっているのかは不明です。現状送信されているのかどうかについても、ダミーのレイアウト / フィルタドライバを差さないと見れないので、こちらでは調べていません。

 

あとがき

思えばこれまでも、Win8対応富士通製ドライバのうち、x86版だけなぜかWin7までのドライバと同じバージョン表記のバイナリ(但しタイムスタンプやサイズは異なる)になっていたり、Win8.1や10にしつこく標準親指シフトドライバが残っているのを確認していたりしたので、そこでちゃんと深追いしておけばもっと早く気づけたのかなという気がしています…。思い込みと切り分けの甘さは今後注意していく所存です。

しかしながら、このような「割と巨大なブラックボックスを相手にニッチな機能の正確な挙動を調べる」というのを一人の個人だけがやってるのは、どうやっても正確性の面で厳しいのでは? とも感じています。私が用意できる環境には限りがあるので、どうしてもその範囲内で観測できたことしか気づきませんし、その中での調査しかできません。ですので、もし他にも私が思いっきり間違えたことを書いてるような箇所がありましたら、遠慮なくご指摘頂けると幸いです。調査のきっかけは単なる好奇心でだったり必要に迫られたもの(親指絡みではこっちが多いかも)だったりしますが、折角調べたので自分以外の誰かも必要としているなら…と思って掲載しています。間違いがそのままになっているのは不本意ですので、お手元での追試結果が記載内容と違ったりしたら是非教えて頂けると有り難いです。