l-r
TRANSCRIPT
L R⇔
firewood@Hatenahotpepsi@Twitter
Windows でキーを入れ替える 7 つの方法
既存のソフト レジストリ MSKLC メッセージフック LL フック フィルタドライバ その他
対象範囲
以下の入れ替えを 7 種類紹介– 「 L 」キーを押すと「 R 」が入力される
– 「 R 」キーを押すと「 L 」が入力される
対象 OS: Windows 2000/XP– たぶん Vista/7 でも可
なぜ L-R 入れ替えなのか
日本人は L と R の区別が苦手
↓
入れ替えても気づかないかも!
参考 ULR
Vista/Windows 7 におけるキーボードカスタマイズ問題http://d.hatena.ne.jp/LM-7/20090614/1244980470
今回の手法の位置関係
(4)メッセージフック
(5)LL フック
Windows サブシステム
キーボード
クラスドライバ
キーボードドライバ
キーボード
アプリケーション
(2)レジストリ
(3)MSKLC
(6)フィルタドライバ
(7)その他
ユーザ
ー
レベ
ル
カーネ
ル
レベ
ル
ハー
ド
ウェ
ア
紹介順序
手軽なものから順に紹介– 手軽≒高レベル
より低レベルな方法を追求する– 「普通のやつらの下を行け」– 「下には下がいる」
方式 1 既存のソフト
お手軽な方法 マクロなど凝ったことができる
既存のソフトと手法
メッセージフック– XKeymacs ( emacs な人用)
LL フック– AutoHotkey (スクリプトで色々やりたい人用)
フィルタドライバ– 窓使いの憂鬱( XP まで)
– のどか( Vista 以降)
方式 2 レジストリ
単純な入れ替えに最適 設定方法
– 「 windows keyboard layout 」でぐぐる
– ヘルパーアプリもある( KeyLayout Fix など)
レジストリのメリット
Windows 標準– 安全、安定、比較的簡単– 処理コストがたぶん低い
全ユーザー、またはユーザー毎に変更できる– 共通のキーとユーザー毎のキーがある
特殊なキーも入れ替えられる– 定番は Caps キーと Ctrl キーの交換
方式 3 MSKLC
Microsoft Keyboard Layout Creator– 「サポートされていない言語のキーボードレイアウトを、簡単に定義できます」
キーマップを定義する DLL を作成・変更できる– オレオレ kbd???.dll が作成可能
日本語キーボードには未対応( ! )
MSKLC のメリット
発音記号やデッドキーの割り当てが可能– ウムラウトや 2 ストローク入力
「ほげキー」が作れる
方式 4 メッセージフック
SetWindowsHookEx– WH_KEYBOARD または WH_GETMESSAGE– グローバルフックの場合、 DLL 内で設定する
全プロセス、全スレッドに対してフック可能– ただし 64bit 環境では、同じ環境に対してのみ有効(両方フックするには、 32bit と 64bit 版が必要になる)
メッセージの発生の捕捉と消去が可能– 交換に使える
WH_KEYBOARD での交換部
LRESULT CALLBACK KeyboardProc(){ if ( nCode == HC_ACTION && ScanCode > 0 ) { switch ( VirtualKeyCode ) { case VK_L: case VK_R: Input.type = INPUT_KEYBOARD; Input.ki.wVk = (WORD)(VirtualKeyCode ^ (VK_L ^ VK_R)); Input.ki.dwFlags = (lParam & 0x80000000) ? KEYEVENTF_KEYUP : 0; ::SendInput( 1, &Input, sizeof(INPUT) ); return 1; } } return ::CallNextHookEx( HookHandle, nCode, VirtualKeyCode, lParam );}
WH_KEYBOARD について補足
プロセス間通信が必要– 処理に必要な DLL がプロセス毎に読み込まれるため
– 今回はお手軽な共有メモリを使用
自分で挿入したキー操作に対しても反応する– 対策しないと無限ループする– 今回は、スキャンコードがゼロかどうかで識別
キーを離した操作も交換すること
WH_GETMESSAGE の場合
処理方法は WH_KEYBOARD と大差なし 全てのメッセージが取れるので、パフォーマンスに影響するかもしれない
メッセージを消したい場合は、メッセージの種類を WM_NULL に変更する
方式 5 LL フック
Lightweight Language× ふせいかい
Low Level ○ ふつう
方式 5 LL フック
SetWindowsHookEx(WH_KEYBOARD_LL)– グローバルフック専用 API– DLL 内である必要はない
使い勝手は WH_KEYBOARD とほぼ一緒
NT系のみ( Windows 9x系では使えない)
C# でも使える 自分で挿入したかどうかがわかる
LL フックの交換部
LRESULT CALLBACK LowLevelKeyboardProc(){ KBDLLHOOKSTRUCT *kbhs = (KBDLLHOOKSTRUCT *)lParam; if ( nCode == HC_ACTION && (kbhs->flags & LLKHF_INJECTED) == 0 ) { switch ( kbhs->vkCode ) { case VK_L: case VK_R: Input.type = INPUT_KEYBOARD; Input.ki.wVk = (WORD)(kbhs->vkCode ^ (VK_L ^ VK_R)); Input.ki.dwFlags = (kbhs->flags & 0x80) ? KEYEVENTF_KEYUP : 0; ::SendInput( 1, &Input, sizeof(INPUT) ); return 1; } } return ::CallNextHookEx( HookHandle, nCode, wParam, lParam );}
方式 6 フィルタドライバ
デバイスドライバの一種 ドライバとドライバの間に配置する中間ドライバ
– すでに存在するデバイスオブジェクト同士の間にはさみこむ
上位と下位の間の入出力(主にIRP )を監視、変更できる
上位
IRP
フィルタドライバ
下位
IRP
戻り値
戻り値
キーボード入力の通知経路
上位が kbdclass.sys に対してIRP_MJ_READ を発行する
kbdclass.sys は上位にSTATUS_PENDING を返し、待つ
キー入力があると、キーボードドライバが kbdclass.sys のサービスコールバックを呼び出す
– キーのデータがバッファに書き込まれる
kbdclass.sys が上位に読み取り完了を通知する
上位
IRP
kbdclass.sys
キーボードドライバ
ポインタ サービス
コール
バック
戻り値
フィルタドライバ 1
Windows サブシステムとキーボードクラスドライバ( kbclass.sys )との間に配置
上位からの IRP_MJ_READ を横取りし、完了(キーデータの通知)を待つ
キーデータが通知されたら、変更して上位に返す
上位
IRP
フィルタドライバ 1
kbdclass.sys
IRP
戻り値
戻り値
フィルタドライバ 1 の処理(準備)
NTSTATUS ReadDispatch(){ ... // 読み取り完了ルーチンを設定し、ペンディングにする IoSetCompletionRoutine(Irp, OnReadComplete, Extension, TRUE, TRUE, TRUE); IoMarkIrpPending(Irp);
IoCallDriver(Extension->NextLowerDriver, Irp); return STATUS_PENDING;}
フィルタドライバ 1 の処理(交換)
NTSTATUS OnReadComplete(){ if (Buffer[0].MakeCode == MAKE_L) { Buffer[0].MakeCode = MAKE_R; } else if (Buffer[0].MakeCode == MAKE_R) { Buffer[0].MakeCode = MAKE_L; } return STATUS_CONTINUE_COMPLETION;}
フィルタドライバ 1 について補足
IRP のフィルタドライバとしては素直な実装 マウスにも適用可能
– kbdclass.sys が mouclass.sys になるだけ
処理待ちしている IRP を管理する必要がある– 抜かれたときにキャンセルする責務がある
kbdclass.sys よりも後にインスタンス化する– UpperFilters に追記する
フィルタドライバ 2
kbdclass.sys とキーボードドライバとの間に配置
kbdclass.sys が提供するサービスコールバックをフックする
– キーボードドライバに自分の関数をサービスコールバックとして教える
– キー入力データを変更し、 kbdclass.sys のサービスコールバックを呼び出す
WDK のサンプル( kbfiltr )
kbdclass.sys
ポインタ
フィルタドライバ 2
キーボードドライバ
ポインタ サービス
コール
バック
サービス
コール
バック
フィルタドライバ 2 の処理(フック)
NTSTATUS InternalIoControlDispatch(){ switch ( IrpStack->Parameters.DeviceIoControl.IoControlCode ) { case IOCTL_INTERNAL_KEYBOARD_CONNECT: CONNECT_DATA *ConnectData = (CONNECT_DATA *) (IrpStack->Parameters.DeviceIoControl.Type3InputBuffer); Extension->KeyboardConnectData = *ConnectData; ConnectData->ClassDeviceObject = DeviceObject; ConnectData->ClassService = (PSERVICE_CALLBACK_ROUTINE) KeyboardServiceCallback; break; }}
フィルタドライバ 2 の処理(交換)
void KeyboardServiceCallback(){ PKEYBOARD_INPUT_DATA p; for ( p = InputDataStart; p < InputDataEnd; ++p ) { if (p->MakeCode == MAKE_L) { p->MakeCode = MAKE_R; } else if (p->MakeCode == MAKE_R) { p->MakeCode = MAKE_L; } } ClassService(ClassDeviceObject, InputDataStart, InputDataEnd, InputDataConsumed);}
フィルタドライバ 2 について補足
WDK のサンプルにもある通り、キーボードでは定番の方法
マウスにも適用可能 IRP のキャンセルが不要
サービスコールバックは DPC レベルなので注意
kbdclass.sys よりも先にインスタンス化する– UpperFilters の先頭に記述する
フィルタドライバについて補足
64bit 環境では 32bit プロセスにも有効– ただし配布するには署名が必須
ドライバの処理時点では、レジストリによる交換は反映されていない
方式 7 その他
エクストリームダイレクト フィジカルフォースメソッド
訳 : ぶっちゃけ直接交換しちゃえば
End of Fire