ELECOM TK-SLP01のファンクションキーがどうしても慣れなくてどうにかした話

# はじめに
会社のノートPCが変わったんですが、キーボードが微妙に使いづらいので、東京出張の時にヨドバシカメラでELECOM TK-SLP01を購入。
(参考までに[Amazonのリンク](https://www.amazon.co.jp/%E3%82%A8%E3%83%AC%E3%82%B3%E3%83%A0-Bluetooth-%E5%B0%82%E7%94%A8%E3%82%B1%E3%83%BC%E3%82%B9%E3%82%B9%E3%82%BF%E3%83%B3%E3%83%89%E4%BB%98%E3%81%8D-%E3%83%9E%E3%83%AB%E3%83%81%E3%83%9A%E3%82%A2%E3%83%AA%E3%83%B3%E3%82%B0%E5%AF%BE%E5%BF%9C-TK-SLP01BK/dp/B07K9DYFFJ)。気になる方はレビュー読むといいかも)
しかし、ファンクションキーを押すと、F1やF2じゃなくて、ボリューム調整や輝度調整などが割り当てられていて、Fn+ファンクションキーでF1やF2など、通常のファンクションキーの動きになる。
全角で入力して、F7を押してカタカナにしたいのにボリュームを小さくする動作をしちゃうんですね。
で、Fn+F7とかになれると、ほかのPCを触ったときに、Fn+F7と押して、ボリューム調整したり・・・とストレス溜まっちゃうことになります。

そういえば、グローバルフックでなんとかできるよなぁ・・・ということで、ちょこちょこっとツールを作ったのでどんな感じで実装したのかを紹介します。。

余談ですが、昔、グローバルフックでメッセージをフックして、秀丸をタブ化する「ひでたぶ」なんてものを作ったこともあります。
数か月後に秀丸がタブ化しちゃいましたけどｗ
(ちなみに、今でもVectorでも公開してますが、需要はもうないですよね)
秀丸のメッセージを調べて、ユーザーメッセージの連続でそれはそれですごく楽しかったｗ

ひでたぶの時はたしかDelphiで作ったんですが、最近はDelphiなんて使ってないので、C#で作りました。
といっても、もう10年以上前にやったことだし、メモにもちゃんと残してないので、ググって以下のサイトを参考にしました。
[気まま研究所ブログ C# - グローバルキーフックでキーの捕捉と入力を行う](https://aonasuzutsuki.hatenablog.jp/entry/2018/10/15/170958)
これの応用で、お仕事で開発していたWindowsのアプリをEclipseの子ウィンドウにしてタブの中に表示したりするプロトタイプを作ったりしました。[^1]
[^1]: Eclipseのプラグインで起動したWindowsアプリのウィンドウハンドルを取得して、親ウィンドウをEclipseの中に作ったタブにして、グローバルフック使って、メッセージをフックして、不都合のあるメッセージを受け取って、処理を改善したり・・・とおかしなことをしてました

あと、Bluethoothで接続している機器を確認するために32feetを使っています。
[こちら](http://note.websmil.com/category/vb/32feet)を参考に実装しました。

# 前提
C#でWindows APIの呼び出し方とか知ってる前提で書いています。
まぁ、この辺もググればいろいろと出てくるので、わからないところはググりながら読んでください。

# 実装
会社のVVisual StudioがVS2010なので、書き方がちょっと古いかもしれませんが、目をつむってくださいｗ

とりあえず、参考にしたサイトをもとに以下の抽象クラスを実装

```C#:AbstractInterceptKeyboard.cs
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace KeyConf
{
    abstract class AbstractInterceptKeyboard
    {
        protected const int dwExtraInfoNumber = 0xF0;
        #region Win32 Constants
        protected const int WH_KEYBOARD_LL = 0x000D;
        protected const int WM_KEYDOWN = 0x0100;
        protected const int WM_KEYUP = 0x0101;
        protected const int WM_SYSKEYDOWN = 0x0104;
        protected const int WM_SYSKEYUP = 0x0105;

        protected const int VK_ESCAPE = 0x1B;
        protected const int VK_F1 = 0x70;
        protected const int VK_F2 = 0x71;
        protected const int VK_F3 = 0x72;
        protected const int VK_F4 = 0x73;
        protected const int VK_F5 = 0x74;
        protected const int VK_F6 = 0x75;
        protected const int VK_F7 = 0x76;
        protected const int VK_F8 = 0x77;
        protected const int VK_F9 = 0x78;
        protected const int VK_F10 = 0x79;
        protected const int VK_F11 = 0x7A;
        protected const int VK_F12 = 0x7B;
        protected const int VK_F13 = 0x7C;
        protected const int VK_F14 = 0x7D;
        protected const int VK_F15 = 0x7E;
        #endregion

        #region Win32API Structures
        [StructLayout(LayoutKind.Sequential)]
        public class KBDLLHOOKSTRUCT
        {
            public uint vkCode;
            public uint scanCode;
            public KBDLLHOOKSTRUCTFlags flags;
            public uint time;
            public UIntPtr dwExtraInfo;
        }

        [Flags]
        public enum KBDLLHOOKSTRUCTFlags : uint
        {
            KEYEVENTF_EXTENDEDKEY = 0x0001,
            KEYEVENTF_KEYUP = 0x0002,
            KEYEVENTF_SCANCODE = 0x0008,
            KEYEVENTF_UNICODE = 0x0004,
        }
        #endregion

        #region Win32 Methods
        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern IntPtr SetWindowsHookEx(int idHook, KeyboardProc lpfn, IntPtr hMod, uint dwThreadId);

        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool UnhookWindowsHookEx(IntPtr hhk);

        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);

        [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern IntPtr GetModuleHandle(string lpModuleName);

        [DllImport("user32.dll")]
        protected static extern long PostMessage(
          IntPtr hWnd,    // 送信先ウィンドウのハンドル
          uint Msg,       // メッセージ
          uint wParam,    // メッセージの最初のパラメータ
          uint lParam     // メッセージの 2 番目のパラメータ
        );

        [System.Runtime.InteropServices.DllImport("user32.dll")]
        protected static extern short GetKeyState(int nVirtKey);

        
        #endregion

        #region Delegate
        private delegate IntPtr KeyboardProc(int nCode, IntPtr wParam, IntPtr lParam);
        #endregion

        #region Fields
        private KeyboardProc proc;
        private IntPtr hookId = IntPtr.Zero;
        #endregion

        // マウスイベント(mouse_eventの引数と同様のデータ)
        [StructLayout(LayoutKind.Sequential)]
        protected struct MOUSEINPUT
        {
            public int dx;
            public int dy;
            public int mouseData;
            public int dwFlags;
            public int time;
            public int dwExtraInfo;
        };

        // キーボードイベント(keybd_eventの引数と同様のデータ)
        [StructLayout(LayoutKind.Sequential)]
        protected struct KEYBDINPUT
        {
            public short wVk;
            public short wScan;
            public int dwFlags;
            public int time;
            public int dwExtraInfo;
        };

        // ハードウェアイベント
        [StructLayout(LayoutKind.Sequential)]
        protected struct HARDWAREINPUT
        {
            public int uMsg;
            public short wParamL;
            public short wParamH;
        };
        // 各種イベント(SendInputの引数データ)
        [StructLayout(LayoutKind.Explicit)]
        protected struct INPUT
        {
            [FieldOffset(0)]
            public int type;
            [FieldOffset(4)]
            public MOUSEINPUT mi;
            [FieldOffset(4)]
            public KEYBDINPUT ki;
            [FieldOffset(4)]
            public HARDWAREINPUT hi;
        };

        [DllImport("user32.dll")]
        protected extern static void SendInput(int nInputs, ref INPUT pInputs, int cbsize);

        // 仮想キーコードをスキャンコードに変換
        [DllImport("user32.dll", EntryPoint = "MapVirtualKeyA")]
        protected extern static int MapVirtualKey(
            int wCode, int wMapType);

        protected const int INPUT_MOUSE = 0;                  // マウスイベント
        protected const int INPUT_KEYBOARD = 1;               // キーボードイベント
        protected const int INPUT_HARDWARE = 2;               // ハードウェアイベント

        protected const int MOUSEEVENTF_MOVE = 0x1;           // マウスを移動する
        protected const int MOUSEEVENTF_ABSOLUTE = 0x8000;    // 絶対座標指定
        protected const int MOUSEEVENTF_LEFTDOWN = 0x2;       // 左　ボタンを押す
        protected const int MOUSEEVENTF_LEFTUP = 0x4;         // 左　ボタンを離す
        protected const int MOUSEEVENTF_RIGHTDOWN = 0x8;      // 右　ボタンを押す
        protected const int MOUSEEVENTF_RIGHTUP = 0x10;       // 右　ボタンを離す
        protected const int MOUSEEVENTF_MIDDLEDOWN = 0x20;    // 中央ボタンを押す
        protected const int MOUSEEVENTF_MIDDLEUP = 0x40;      // 中央ボタンを離す
        protected const int MOUSEEVENTF_WHEEL = 0x800;        // ホイールを回転する
        protected const int WHEEL_DELTA = 120;                // ホイール回転値

        protected const int KEYEVENTF_KEYDOWN = 0x0;          // キーを押す
        protected const int KEYEVENTF_KEYUP = 0x2;            // キーを離す
        protected const int KEYEVENTF_EXTENDEDKEY = 0x1;      // 拡張コード
        protected const int VK_SHIFT = 0x10;                  // SHIFTキー        

        public void Hook()
        {
            if (hookId == IntPtr.Zero)
            {
                proc = HookProcedure;
                using (var curProcess = Process.GetCurrentProcess())
                {
                    using (ProcessModule curModule = curProcess.MainModule)
                    {
                        hookId = SetWindowsHookEx(WH_KEYBOARD_LL, proc, GetModuleHandle(curModule.ModuleName), 0);
                    }
                }
            }
        }

        public void UnHook()
        {
            UnhookWindowsHookEx(hookId);
            hookId = IntPtr.Zero;
        }

        public virtual IntPtr HookProcedure(int nCode, IntPtr wParam, IntPtr lParam)
        {
            return CallNextHookEx(hookId, nCode, wParam, lParam);
        }
    }
}
```

長いｗ
このクラスは、HookProcedureを実装してらうためのクラス。
また何かの時に再利用しやすいようにするつもりでこんな実装。
フックの開始と終了のメソッドを用意したのは、タスクトレイに常駐させといて、終了させたり、一時的に停止させたりできるようにしたいなと思って外部から呼び出せるようにしています。

で、このクラスを継承したクラスのソース

```C#:InterceptKeyboard.cs
using System;
using System.Runtime.InteropServices;
using System.Windows.Input;

namespace KeyConf
{
    class InterceptKeyboard : AbstractInterceptKeyboard
    {
        #region InputEvent
        public class OriginalKeyEventArg : EventArgs
        {
            public int KeyCode { get; private set; }
            public OriginalKeyEventArg(int keyCode)
            {
                KeyCode = keyCode;
            }
        }
        public delegate void KeyEventHandler(object sender, OriginalKeyEventArg e);
        public event KeyEventHandler KeyDownEvent;
        public event KeyEventHandler KeyUpEvent;
        private uint prevKeyDownCode = 0;
        private uint prevKeyUpCode = 0;

        protected void OnKeyDownEvent(int keyCode)
        {
            if(KeyDownEvent == null){
                return;
            }
            KeyDownEvent.Invoke(this, new OriginalKeyEventArg(keyCode));
        }
        protected void OnKeyUpEvent(int keyCode)
        {
            if (KeyUpEvent == null)
            {
                return;
            }
            KeyUpEvent.Invoke(this, new OriginalKeyEventArg(keyCode));
        }
        #endregion

        public override IntPtr HookProcedure(int nCode, IntPtr wParam, IntPtr lParam)
        {
            if (BTUtil.IsConnected("ELECOM Bluetooth Keyboard") == false)
            {
                return base.HookProcedure(nCode, wParam, lParam);
            }
            var num = 0;
            INPUT[] inp = null;
            if (nCode >= 0 && (wParam == (IntPtr)WM_KEYDOWN || wParam == (IntPtr)WM_SYSKEYDOWN))
            {
                var kb = (KBDLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(KBDLLHOOKSTRUCT));
                if ((int)kb.dwExtraInfo == dwExtraInfoNumber)
                {
                    // この処理で発生したキーイベントは処理しない
                    return base.HookProcedure(nCode, wParam, lParam);
                }

                switch (kb.vkCode)
                {
                    case 172:
                        // 0xAC
                        //ESCに変換
                        num = 1;
                        inp = new INPUT[num];
                        inp[0].type = INPUT_KEYBOARD;
                        inp[0].ki.wVk = (short)VK_ESCAPE;
                        inp[0].ki.wScan = (short)MapVirtualKey(inp[0].ki.wVk, 0);
                        inp[0].ki.dwFlags = KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYDOWN;
                        inp[0].ki.dwExtraInfo = dwExtraInfoNumber;
                        inp[0].ki.time = 0;
                        break;
                    case 170:
                        // 0xAA
                        // F2
                        num = 1;
                        inp = new INPUT[num];
                        inp[0].type = INPUT_KEYBOARD;
                        inp[0].ki.wVk = (short)VK_F2;
                        inp[0].ki.wScan = (short)MapVirtualKey(inp[0].ki.wVk, 0);
                        inp[0].ki.dwFlags = KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYDOWN;
                        inp[0].ki.dwExtraInfo = dwExtraInfoNumber;
                        inp[0].ki.time = 0;
                        break;
                    case 177:
                        // 0xB1
                        // F3
                        num = 1;
                        inp = new INPUT[num];
                        inp[0].type = INPUT_KEYBOARD;
                        inp[0].ki.wVk = (short)VK_F3;
                        inp[0].ki.wScan = (short)MapVirtualKey(inp[0].ki.wVk, 0);
                        inp[0].ki.dwFlags = KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYDOWN;
                        inp[0].ki.dwExtraInfo = dwExtraInfoNumber;
                        inp[0].ki.time = 0;
                        break;
                    case 179:
                        // B3
                        // F4
                        num = 1;
                        inp = new INPUT[num];
                        inp[0].type = INPUT_KEYBOARD;
                        inp[0].ki.wVk = (short)VK_F4;
                        inp[0].ki.wScan = (short)MapVirtualKey(inp[0].ki.wVk, 0);
                        inp[0].ki.dwFlags = KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYDOWN;
                        inp[0].ki.dwExtraInfo = dwExtraInfoNumber;
                        inp[0].ki.time = 0;
                        break;
                    case 176:
                        // 0xB0
                        // F5
                        num = 1;
                        inp = new INPUT[num];
                        inp[0].type = INPUT_KEYBOARD;
                        inp[0].ki.wVk = (short)VK_F5;
                        inp[0].ki.wScan = (short)MapVirtualKey(inp[0].ki.wVk, 0);
                        inp[0].ki.dwFlags = KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYDOWN;
                        inp[0].ki.dwExtraInfo = dwExtraInfoNumber;
                        inp[0].ki.time = 0;
                        break;
                    case 173:
                        // 0xAD
                        // F6
                        num = 1;
                        inp = new INPUT[num];
                        inp[0].type = INPUT_KEYBOARD;
                        inp[0].ki.wVk = (short)VK_F6;
                        inp[0].ki.wScan = (short)MapVirtualKey(inp[0].ki.wVk, 0);
                        inp[0].ki.dwFlags = KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYDOWN;
                        inp[0].ki.dwExtraInfo = dwExtraInfoNumber;
                        inp[0].ki.time = 0;
                        break;
                    case 174:
                        // 0xAE
                        // F7
                        num = 1;
                        inp = new INPUT[num];
                        inp[0].type = INPUT_KEYBOARD;
                        inp[0].ki.wVk = (short)VK_F7;
                        inp[0].ki.wScan = (short)MapVirtualKey(inp[0].ki.wVk, 0);
                        inp[0].ki.dwFlags = KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYDOWN;
                        inp[0].ki.dwExtraInfo = dwExtraInfoNumber;
                        inp[0].ki.time = 0;
                        break;
                    case 175:
                        //0xAF
                        // F8
                        num = 1;
                        inp = new INPUT[num];
                        inp[0].type = INPUT_KEYBOARD;
                        inp[0].ki.wVk = (short)VK_F8;
                        inp[0].ki.wScan = (short)MapVirtualKey(inp[0].ki.wVk, 0);
                        inp[0].ki.dwFlags = KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYDOWN;
                        inp[0].ki.dwExtraInfo = dwExtraInfoNumber;
                        inp[0].ki.time = 0;
                        break;
                    case 91:
                        // 0x5B
                        break;
                    case 9:
                        // 0x09                        // F9 91(VK_LWIN)→9(VK_TAB)の順番
                        if (prevKeyDownCode == 91)
                        //if(GetKeyState(91) < 0)
                        {
                            num = 1;
                            inp = new INPUT[num];
                            inp[0].type = INPUT_KEYBOARD;
                            inp[0].ki.wVk = (short)VK_F9;
                            inp[0].ki.wScan = (short)MapVirtualKey(inp[0].ki.wVk, 0);
                            inp[0].ki.dwFlags = KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYDOWN;
                            inp[0].ki.dwExtraInfo = dwExtraInfoNumber;
                            inp[0].ki.time = 0;
                        }
                        break;
                    case 44:
                        // 0x2C
                        // F10
                        // スクリーンショットと被るので、ctrl/alt/shiftを押していないときだけF10に変換する
                        switch(prevKeyDownCode){
                            case 0xA0:
                                // SHift
                                // VK_LSHIFT
                                break;
                            case 0xA1:
                                // VK_RSHIFT
                                break;
                            case 0x11:
                                // Control
                                break;
                            case 0xA2:
                                // VK_LCONTROL
                                break;
                            case 0xA3:
                                // VK_RCONTROL
                                break;
                            case 0x12:
                                // ALT
                                break;
                            case 0xA4:
                                // VK_LMENU
                                break;
                            case 0xA5:
                                // VK_RMENU
                                break;
                            default:
                                num = 1;
                                inp = new INPUT[num];
                                inp[0].type = INPUT_KEYBOARD;
                                inp[0].ki.wVk = (short)VK_F10;
                                inp[0].ki.wScan = (short)MapVirtualKey(inp[0].ki.wVk, 0);
                                inp[0].ki.dwFlags = KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYDOWN;
                                inp[0].ki.dwExtraInfo = dwExtraInfoNumber;
                                inp[0].ki.time = 0;
                                break;
                        }
                        break;
                }
                prevKeyDownCode = kb.vkCode;

                if (num > 0)
                {
                    SendInput(num, ref inp[0], Marshal.SizeOf(inp[0]));
                    return (IntPtr)1;
                }
            }
            else if (nCode >= 0 && (wParam == (IntPtr)WM_KEYUP || wParam == (IntPtr)WM_SYSKEYUP))
            {
                var kb = (KBDLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(KBDLLHOOKSTRUCT));
                if ((int)kb.dwExtraInfo == dwExtraInfoNumber)
                {
                    // この処理で発生したキーイベントは処理しない
                    return base.HookProcedure(nCode, wParam, lParam);
                }
                switch (kb.vkCode)
                {
                    case 172:
                        //ESCに変換
                        num = 1;
                        inp = new INPUT[num];
                        inp[0].type = INPUT_KEYBOARD;
                        inp[0].ki.wVk = (short)VK_ESCAPE;
                        inp[0].ki.wScan = (short)MapVirtualKey(inp[0].ki.wVk, 0);
                        inp[0].ki.dwFlags = KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP;
                        inp[0].ki.dwExtraInfo = 0;
                        inp[0].ki.time = 0;
                        break;
                    case 170:
                        // F2
                        num = 1;
                        inp = new INPUT[num];
                        inp[0].type = INPUT_KEYBOARD;
                        inp[0].ki.wVk = (short)VK_F2;
                        inp[0].ki.wScan = (short)MapVirtualKey(inp[0].ki.wVk, 0);
                        inp[0].ki.dwFlags = KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP;
                        inp[0].ki.dwExtraInfo = 0;
                        inp[0].ki.time = 0;
                        break;
                    case 177:
                        // F3
                        num = 1;
                        inp = new INPUT[num];
                        inp[0].type = INPUT_KEYBOARD;
                        inp[0].ki.wVk = (short)VK_F3;
                        inp[0].ki.wScan = (short)MapVirtualKey(inp[0].ki.wVk, 0);
                        inp[0].ki.dwFlags = KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP;
                        inp[0].ki.dwExtraInfo = 0;
                        inp[0].ki.time = 0;
                        break;
                    case 179:
                        // F4
                        num = 1;
                        inp = new INPUT[num];
                        inp[0].type = INPUT_KEYBOARD;
                        inp[0].ki.wVk = (short)VK_F4;
                        inp[0].ki.wScan = (short)MapVirtualKey(inp[0].ki.wVk, 0);
                        inp[0].ki.dwFlags = KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP;
                        inp[0].ki.dwExtraInfo = 0;
                        inp[0].ki.time = 0;
                        break;
                    case 176:
                        // F5
                        num = 1;
                        inp = new INPUT[num];
                        inp[0].type = INPUT_KEYBOARD;
                        inp[0].ki.wVk = (short)VK_F5;
                        inp[0].ki.wScan = (short)MapVirtualKey(inp[0].ki.wVk, 0);
                        inp[0].ki.dwFlags = KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP;
                        inp[0].ki.dwExtraInfo = 0;
                        inp[0].ki.time = 0;
                        break;
                    case 173:
                        // F6
                        num = 1;
                        inp = new INPUT[num];
                        inp[0].type = INPUT_KEYBOARD;
                        inp[0].ki.wVk = (short)VK_F6;
                        inp[0].ki.wScan = (short)MapVirtualKey(inp[0].ki.wVk, 0);
                        inp[0].ki.dwFlags = KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP;
                        inp[0].ki.dwExtraInfo = 0;
                        inp[0].ki.time = 0;
                        break;
                    case 174:
                        // F7
                        num = 1;
                        inp = new INPUT[num];
                        inp[0].type = INPUT_KEYBOARD;
                        inp[0].ki.wVk = (short)VK_F7;
                        inp[0].ki.wScan = (short)MapVirtualKey(inp[0].ki.wVk, 0);
                        inp[0].ki.dwFlags = KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP;
                        inp[0].ki.dwExtraInfo = 0;
                        inp[0].ki.time = 0;
                        break;
                    case 175:
                        // F8
                        num = 1;
                        inp = new INPUT[num];
                        inp[0].type = INPUT_KEYBOARD;
                        inp[0].ki.wVk = (short)VK_F8;
                        inp[0].ki.wScan = (short)MapVirtualKey(inp[0].ki.wVk, 0);
                        inp[0].ki.dwFlags = KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP;
                        inp[0].ki.dwExtraInfo = 0;
                        inp[0].ki.time = 0;
                        break;
                    case 44:
                        // F10
                        num = 1;
                        inp = new INPUT[num];
                        inp[0].type = INPUT_KEYBOARD;
                        inp[0].ki.wVk = (short)VK_F10;
                        inp[0].ki.wScan = (short)MapVirtualKey(inp[0].ki.wVk, 0);
                        inp[0].ki.dwFlags = KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP;
                        inp[0].ki.dwExtraInfo = 0;
                        inp[0].ki.time = 0;
                        break;
                }
                prevKeyUpCode = kb.vkCode;
                if (num > 0)
                {
                    SendInput(num, ref inp[0], Marshal.SizeOf(inp[0]));
                    return (System.IntPtr)1;
                }
            }
            return base.HookProcedure(nCode, wParam, lParam);
        }
    }
}
```

もうちょっと考えれば、短くきれいにできそうな気もしますが、それは今後、時間のある時にやるとして。
何やってるかというと、キーのイベントは
- キーダウン(WM_KEYDOWN)
- キーアップ(WM_KEYUP)
があります。
WM_KEYDOWNで置き換えたいキーが押されたら、別のキーイベントを発生させ、置き換え元のイベントはキャンセルさせます。
ポイントとして、
- F9 91(VK_LWIN)→9(VK_TAB)
- F10 がスクリーンショット
という罠がありました。
F9に関してはWK_LWINが来た後に、VK_TABが来たら疑似キーを送信するようにしました。
F10の場合、PrtScのみ、ALT+PrtScというケースもあります。
なので、VK_PRINTSCREENの時のみVK_F10におきかえるようにし、ALT,Shift,Ctrlを押しながらPrtScキーが推されてる場合は、通常のキーの処理を行うようにしました。
書いてて思ったけど、WM_KEYUPの処理、もしかして、いらない？
また今度検証して、アップデートします。

あと、以下の場所でBTUtilのIsCpnnectedメソッドを呼び出してます。

```C#
            if (BTUtil.IsConnected("ELECOM Bluetooth Keyboard") == false)
            {
                return base.HookProcedure(nCode, wParam, lParam);
            }
```

ここで、"ELECOM Bluetooth Keyboard"という名前のデバイスが接続されていない場合は、フックしたメッセージを処理せずに次に回すために、スーパークラスのHookProcedureメソッドを呼び出すようにしています。
で、このBTUtilクラスで32feetsを使っています。

```C#:BTUtil.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

using InTheHand.Net.Sockets;
namespace KeyConf
{
    class BTUtil
    {
        private static List<BluetoothDeviceInfo> btinfos = new List<BluetoothDeviceInfo>();
        private static Task task = null;
        private static CancellationTokenSource cancelToken = null;

        public static void StartCheckBTDevice()
        {
            if (task != null)
            {
                return;
            }
            cancelToken = new CancellationTokenSource();
            task = Task.Factory.StartNew((Action)(() =>
            {
                Console.WriteLine("BlueThoothの監視を始めます");
                while (true)
                {
                    if (cancelToken.Token.IsCancellationRequested)
                    {
                        Console.WriteLine("BlueThoothの監視がキャンセルされた");
                        return;
                    }
                    UpdateBTInfo();
                    Thread.Sleep(1000);
                }
            }), cancelToken.Token);
            task.ContinueWith((obj) =>
            {
                try
                {
                    task.Dispose();
                    cancelToken.Dispose();
                }
                finally
                {
                    task = null;
                    cancelToken = null;
                }
            });
        }

        public static void StopCheckBTDevice()
        {
            if (task == null)
            {
                return;
            }
            cancelToken.Cancel();
        }

        public static void UpdateBTInfo()
        {
            var client = new BluetoothClient();
            var devices = client.DiscoverDevices(100,true,false,false);
            btinfos = new List<BluetoothDeviceInfo>();
            if (devices.Length == 0)
            {
                return;
            }
            foreach (var dev in devices)
            {
                btinfos.Add(dev);
            }
        }


        public static bool IsConnected(String name)
        {
            
            if (btinfos.Count == 0)
            {
                return false;
            }
            foreach (var dev in btinfos)
            {
                //Console.WriteLine(dev.DeviceName);
                
                if (dev.Connected == false) 
                {
                    continue;
                }
                if (dev.DeviceName == name)
                {
                    return true;
                }
            }
            return false;
        }
    }
}
```

BluetoothClient.DiscoverDevicesメソッドが思いのほか時間がかかったので、1秒間隔でチェックするようにしています。
で、その結果を変数に保持し、IsConnectedメソッドでチェックしその結果を返却するようにしています。
非同期でチェックしないと、キーフックのところでメッセージがうまく処理されなくて、残念な結果になります。

# 最後に
コピペして改造しているので結構簡単にできました。
多分、ちょっと変えれば似たようなキーボードも対応できると思います。

あと、グローバルフックは、変な実装すると、面倒なことになったりするので、実装には注意が必要になります。

それにしても、昔やった経験があったので、ググれば何とかなるなぁと思った。
経験って大事だよね！

作ったツール、需要があればGitHubで公開するけど、あるかな？気が向いたら公開するけど。