狠狠撸

狠狠撸Share a Scribd company logo
L ? R [email_address] [email_address]
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
今回の手法の位置関係
紹介順序 手軽なものから順に紹介 手軽≒高レベル より低レベルな方法を追求する 「普通のやつらの下を行け」 「下には下がいる」
方式 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 )を監視、変更できる
キーボード入力の通知経路 上位が kbdclass.sys に対して IRP_MJ_READ を発行する kbdclass.sys は上位に STATUS_PENDING を返し、待つ キー入力があると、キーボードドライバが kbdclass.sys のサービスコールバックを呼び出す キーのデータがバッファに書き込まれる kbdclass.sys が上位に読み取り完了を通知する
フィルタドライバ 1 Windows サブシステムとキーボードクラスドライバ( kbclass.sys )との間に配置 上位からの IRP_MJ_READ を横取りし、完了(キーデータの通知)を待つ キーデータが通知されたら、変更して上位に返す
フィルタドライバ 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 )
フィルタドライバ 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

More Related Content

L-R

  • 1. L ? R [email_address] [email_address]
  • 2. Windows でキーを入れ替える 7 つの方法 既存のソフト レジストリ MSKLC メッセージフック LL フック フィルタドライバ その他
  • 3. 対象範囲 以下の入れ替えを 7 種類紹介 「 L 」キーを押すと「 R 」が入力される 「 R 」キーを押すと「 L 」が入力される 対象 OS: Windows 2000/XP たぶん Vista/7 でも可
  • 4. なぜ L-R 入れ替えなのか 日本人は L と R の区別が苦手 ↓ 入れ替えても気づかないかも!
  • 5. 参考 ULR Vista/Windows 7 におけるキーボードカスタマイズ問題 http://d.hatena.ne.jp/LM-7/20090614/1244980470
  • 7. 紹介順序 手軽なものから順に紹介 手軽≒高レベル より低レベルな方法を追求する 「普通のやつらの下を行け」 「下には下がいる」
  • 8. 方式 1 既存のソフト お手軽な方法 マクロなど凝ったことができる
  • 9. 既存のソフトと手法 メッセージフック XKeymacs ( emacs な人用) LL フック AutoHotkey (スクリプトで色々やりたい人用) フィルタドライバ 窓使いの憂鬱( XP まで) のどか( Vista 以降)
  • 10. 方式 2 レジストリ 単純な入れ替えに最適 設定方法 「 windows keyboard layout 」でぐぐる ヘルパーアプリもある( KeyLayout Fix など)
  • 11. レジストリのメリット Windows 標準 安全、安定、比較的簡単 処理コストがたぶん低い 全ユーザー、またはユーザー毎に変更できる 共通のキーとユーザー毎のキーがある 特殊なキーも入れ替えられる 定番は Caps キーと Ctrl キーの交換
  • 12. 方式 3 MSKLC Microsoft Keyboard Layout Creator 「サポートされていない言語のキーボードレイアウトを、簡単に定義できます」 キーマップを定義する DLL を作成?変更できる オレオレ kbd???.dll が作成可能 日本語キーボードには未対応( ! )
  • 13. MSKLC のメリット 発音記号やデッドキーの割り当てが可能 ウムラウトや 2 ストローク入力 「ほげキー」が作れる
  • 14. 方式 4 メッセージフック SetWindowsHookEx WH_KEYBOARD または WH_GETMESSAGE グローバルフックの場合、 DLL 内で設定する 全プロセス、全スレッドに対してフック可能 ただし 64bit 環境では、同じ環境に対してのみ有効(両方フックするには、 32bit と 64bit 版が必要になる) メッセージの発生の捕捉と消去が可能 交換に使える
  • 15. 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 ); }
  • 16. WH_KEYBOARD について補足 プロセス間通信が必要 処理に必要な DLL がプロセス毎に読み込まれるため 今回はお手軽な共有メモリを使用 自分で挿入したキー操作に対しても反応する 対策しないと無限ループする 今回は、スキャンコードがゼロかどうかで識別 キーを離した操作も交換すること
  • 17. WH_GETMESSAGE の場合 処理方法は WH_KEYBOARD と大差なし 全てのメッセージが取れるので、パフォーマンスに影響するかもしれない メッセージを消したい場合は、メッセージの種類を WM_NULL に変更する
  • 18. 方式 5 LL フック Lightweight Language × ふせいかい Low Level ○ ふつう
  • 19. 方式 5 LL フック SetWindowsHookEx(WH_KEYBOARD_LL) グローバルフック専用 API DLL 内である必要はない 使い勝手は WH_KEYBOARD とほぼ一緒 NT 系のみ( Windows 9x 系では使えない) C# でも使える 自分で挿入したかどうかがわかる
  • 20. 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 ); }
  • 21. 方式 6 フィルタドライバ デバイスドライバの一種 ドライバとドライバの間に配置する中間ドライバ すでに存在するデバイスオブジェクト同士の間にはさみこむ 上位と下位の間の入出力(主に IRP )を監視、変更できる
  • 22. キーボード入力の通知経路 上位が kbdclass.sys に対して IRP_MJ_READ を発行する kbdclass.sys は上位に STATUS_PENDING を返し、待つ キー入力があると、キーボードドライバが kbdclass.sys のサービスコールバックを呼び出す キーのデータがバッファに書き込まれる kbdclass.sys が上位に読み取り完了を通知する
  • 23. フィルタドライバ 1 Windows サブシステムとキーボードクラスドライバ( kbclass.sys )との間に配置 上位からの IRP_MJ_READ を横取りし、完了(キーデータの通知)を待つ キーデータが通知されたら、変更して上位に返す
  • 24. フィルタドライバ 1 の処理(準備) NTSTATUS ReadDispatch() { ... // 読み取り完了ルーチンを設定し、ペンディングにする IoSetCompletionRoutine(Irp, OnReadComplete, Extension, TRUE, TRUE, TRUE); IoMarkIrpPending(Irp); IoCallDriver(Extension->NextLowerDriver, Irp); return STATUS_PENDING; }
  • 25. フィルタドライバ 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; }
  • 26. フィルタドライバ 1 について補足 IRP のフィルタドライバとしては素直な実装 マウスにも適用可能 kbdclass.sys が mouclass.sys になるだけ 処理待ちしている IRP を管理する必要がある 抜かれたときにキャンセルする責務がある kbdclass.sys よりも後にインスタンス化する UpperFilters に追記する
  • 27. フィルタドライバ 2 kbdclass.sys とキーボードドライバとの間に配置 kbdclass.sys が提供するサービスコールバックをフックする キーボードドライバに自分の関数をサービスコールバックとして教える キー入力データを変更し、 kbdclass.sys のサービスコールバックを呼び出す WDK のサンプル( kbfiltr )
  • 28. フィルタドライバ 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; } }
  • 29. フィルタドライバ 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); }
  • 30. フィルタドライバ 2 について補足 WDK のサンプルにもある通り、キーボードでは定番の方法 マウスにも適用可能 IRP のキャンセルが不要 サービスコールバックは DPC レベルなので注意 kbdclass.sys よりも先にインスタンス化する UpperFilters の先頭に記述する
  • 31. フィルタドライバについて補足 64bit 環境では 32bit プロセスにも有効 ただし配布するには署名が必須 ドライバの処理時点では、レジストリによる交換は反映されていない
  • 32. 方式 7 その他 エクストリームダイレクト フィジカルフォースメソッド 訳 : ぶっちゃけ直接交換しちゃえば