2011年10月28日金曜日

昨日の「VC++でグローバルフック」を書き直し

昨日投稿した「VC++でグローバルフック」のコードがあんまりだったので書き直しました。 「不便に思ったら作り直す」とかって言ってたけどさすがに酷いかと思ったので。 変更点は、

  • コードの整理
  • タスクトレイに入れる
  • キーリピートに反応する

タスクトレイに入れるのは、5年前に自分で書いたコードを参考にして書きました。 アレ? ホントに5年前に作ったんだっけ? もっと前にやったような...

いつ作ったにしても、win32のコードを取っておいて役に立つとは思いませんでした。 取っておいて良かったのかな? 「元のコードは何を参考にして書いたのか?」とか「細かいところで大丈夫か?」とか相変わらず?な部分が多いけど、まぁ、動いてます。(結局コレか)

キーリピートに反応する部分は、グローバルフックで自分で送ったキーイベントが自分に届くのに気付かずに少し詰まりました。 やっていいのかどうか分からない適当な回避策でやり過ごしてみたり。

あぁ、こういう「古臭いノウハウ」を今やって良いのかとちょっと自己問答です。

とりあえず、コード載せときます。 まずはdllのヘッダから。

// CreateRandomString_KeyHook.h
#ifdef CREATERANDOMSTRING_KEYHOOK_EXPORTS
#define CREATERANDOMSTRING_KEYHOOK_API __declspec(dllexport)
#else
#define CREATERANDOMSTRING_KEYHOOK_API __declspec(dllimport)
#endif

extern CREATERANDOMSTRING_KEYHOOK_API HINSTANCE g_hInst;
extern CREATERANDOMSTRING_KEYHOOK_API HWND      g_hOwner;
extern CREATERANDOMSTRING_KEYHOOK_API int       g_shortcutKey;
extern CREATERANDOMSTRING_KEYHOOK_API int       g_hashLength;
extern CREATERANDOMSTRING_KEYHOOK_API HHOOK     g_hKeyHook;

CREATERANDOMSTRING_KEYHOOK_API bool AddHook();
CREATERANDOMSTRING_KEYHOOK_API bool RemoveHook(void);
CREATERANDOMSTRING_KEYHOOK_API bool SetShortcutKey(int key);
CREATERANDOMSTRING_KEYHOOK_API bool SetHashLength(int len);
CREATERANDOMSTRING_KEYHOOK_API LRESULT CALLBACK KeyHookProc(int nCode, WPARAM wp, LPARAM lp);

void SendKeyEvent(int key, DWORD state);
void PushKey(int key);
int* CreateHash();
int RegistTaskTray(HWND hWnd, HINSTANCE hInst, LPCWSTR tip);
int ReleseTaskTray(HWND hWnd);

dllの本体です。

// CreateRandomString_KeyHook.cpp
#include "stdafx.h"
#include "CreateRandomString_KeyHook.h"

#define HASH_LENGTH_DEFAULT       (10)
#define HASH_LENGTH_MIN           (3)
#define HASH_LENGTH_MAX           (1024)
#define DEFAULT_SHORTCUT_KEY_CODE ('F')
#define SEND_KEY_DOWN             (0)
#define SEND_KEY_UP               KEYEVENTF_KEYUP
#define KEY_RELEASED_MASK         (0x80000000)
#define APP_VK_DUMMY              (0xff)

CREATERANDOMSTRING_KEYHOOK_API HINSTANCE g_hInst = NULL;
CREATERANDOMSTRING_KEYHOOK_API int       g_shortcutKey = 0;
CREATERANDOMSTRING_KEYHOOK_API int       g_hashLength = 0;
CREATERANDOMSTRING_KEYHOOK_API HHOOK     g_hKeyHook = 0;

CREATERANDOMSTRING_KEYHOOK_API bool AddHook()
{
    srand((unsigned int)time(NULL));

    g_shortcutKey = DEFAULT_SHORTCUT_KEY_CODE;
    g_hashLength  = HASH_LENGTH_DEFAULT;

    g_hKeyHook = SetWindowsHookEx(WH_KEYBOARD, KeyHookProc, g_hInst, 0);
    return g_hKeyHook != NULL;
}

CREATERANDOMSTRING_KEYHOOK_API bool RemoveHook()
{
    return UnhookWindowsHookEx(g_hKeyHook) != 0;
}

CREATERANDOMSTRING_KEYHOOK_API bool SetShortcutKey(int key)
{
    if(key < 'A' || 'Z' < key)
        return FALSE;

    g_shortcutKey = key;
    return TRUE;
}

CREATERANDOMSTRING_KEYHOOK_API bool SetHashLength(int len)
{
    if(len < HASH_LENGTH_MIN || HASH_LENGTH_MAX < len)
        return FALSE;

    g_hashLength = len;
    return TRUE;
}

// 自分が送ったSendInputを処理しないように文字列を送ったらAPP_VK_DUMMYキーを最後に「押す」
// sendMyselfがtrueの間は自分が送ったキーの情報が来ている
// 最後にさっき送ったAPP_VK_DUMMYが来るので、そこで自己処理防止をOFF
CREATERANDOMSTRING_KEYHOOK_API LRESULT CALLBACK KeyHookProc(int nCode, WPARAM wp, LPARAM lp)
{
    static bool sendMyself = false;

    if (nCode < 0)
        return CallNextHookEx(g_hKeyHook, nCode, wp, lp);

    if(wp == APP_VK_DUMMY && !(lp & KEY_RELEASED_MASK) )
    {
        sendMyself = false;
        SendKeyEvent(APP_VK_DUMMY, SEND_KEY_UP);
        return TRUE;
    }

    if(sendMyself)
        return CallNextHookEx(g_hKeyHook, nCode, wp, lp);
    
    bool shift = GetKeyState(VK_SHIFT) < 0;
    bool ctrl = GetKeyState(VK_CONTROL) < 0;

    if (shift && ctrl && wp == g_shortcutKey && !(lp & KEY_RELEASED_MASK) )
    {
        sendMyself = true;

        SendKeyEvent(VK_CONTROL, SEND_KEY_UP);
        SendKeyEvent(VK_SHIFT, SEND_KEY_UP);

        int *hash = CreateHash();

        for(int i = 0; i < g_hashLength; i++)
        {
            PushKey(hash[i]);
        }

        delete hash;

        SendKeyEvent(VK_SHIFT, SEND_KEY_DOWN);
        SendKeyEvent(VK_CONTROL, SEND_KEY_DOWN);

        SendKeyEvent(APP_VK_DUMMY, SEND_KEY_DOWN);

        return TRUE;
    }

    return CallNextHookEx(g_hKeyHook, nCode, wp, lp);
}

void SendKeyEvent(int key, DWORD state)
{
    INPUT input;
    memset(&input, 0, sizeof(INPUT) );
    input.type = INPUT_KEYBOARD;
    input.ki.wVk = key;
    input.ki.dwFlags = state;
    SendInput(1, &input, sizeof(INPUT) );
}

// 他のキーイベントを考慮して1度に送らないとならないらしい
void PushKey(int key)
{
    INPUT input[4];
    memset(input, 0, sizeof(INPUT) * 4);
    if('A' <= key && key <= 'Z')
    {
        input[0].type = INPUT_KEYBOARD;
        input[0].ki.wVk = VK_SHIFT;
        input[1].type = INPUT_KEYBOARD;
        input[1].ki.wVk = key;
        input[2].type = INPUT_KEYBOARD;
        input[2].ki.wVk = key;
        input[2].ki.dwFlags = KEYEVENTF_KEYUP;
        input[3].type = INPUT_KEYBOARD;
        input[3].ki.wVk = VK_SHIFT;
        input[3].ki.dwFlags = KEYEVENTF_KEYUP;
        SendInput(4, input, sizeof(INPUT));
    }
    else
    {
        if('a' <= key && key <= 'z')
        {
            key = key - 'a' + 'A';
        }

        input[0].type = INPUT_KEYBOARD;
        input[0].ki.wVk = key;
        input[1].type = INPUT_KEYBOARD;
        input[1].ki.wVk = key;
        input[1].ki.dwFlags = KEYEVENTF_KEYUP;
        SendInput(2, input, sizeof(INPUT));
    }
}

int* CreateHash()
{
    WCHAR *charTable = TEXT("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ");
    int len = g_hashLength;

    int* res = new int[len];
    memset(res, 0, sizeof(int) * len);

    unsigned long long t = 0;

    for(int i = 0; i < len; i++)
    {
        if(t == 0)
        {
            t = (((unsigned long long)rand() ) << 32) | ((unsigned long long)rand());
        }

        int c = t % 62;
        t /= 62;
        res[i] = (int)charTable[c];
    }

    return res;
}

リソースはアイコンとタスクトレイ用メニューに使います。 rcファイルはVC++ 2010 expressでは編集できないので、テキストエディタで書きます。

// CreateRandomString.rc
#include "resource.h"

APP_ICON ICON DISCARDABLE "CreateRandomString.ico"

// メニュー(IDM_POPUP)
IDM_POPUP MENU DISCARDABLE
{
    POPUP "DUMMY"
    {
        MENUITEM "終了(&X)", IDM_EXIT
    }

}

リソースヘッダはタスクトレイ用メニューの1項目のためだけに記述。 こういうコードのばらけ方はレトロですね。

// resource.h
// メニュー IDM_POPUP
#define IDM_EXIT 200

アプリケーション本体です。

// CreateRandomString.cpp
#include <windows.h>
#include "..\CreateRandomString_KeyHook\CreateRandomString_KeyHook.h"
#include "resource.h"

#pragma comment(lib, "CreateRandomString_KeyHook.lib")

#define WINDOW_TITLE     TEXT("ランダム文字列(0-9a-zA-Z)を挿入")
#define WINDOW_WIDTH     (500)
#define WINDOW_HEIGHT    (70)
#define UM_TASKTRAYICON  (WM_APP + 101)
#define ID_ICON_TASKTRAY (1001)

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
ATOM InitApp(HINSTANCE);
BOOL InitInstance(HINSTANCE, int);

WCHAR g_szClassName[] = TEXT("CreateRandomStringWin32");        //ウィンドウクラス

int WINAPI WinMain(HINSTANCE hCurInst, HINSTANCE hPrevInst,
                   LPSTR lpsCmdLine, int nCmdShow)
{
    MSG msg;
    
    if (!InitApp(hCurInst))
        return FALSE;
    if (!InitInstance(hCurInst, nCmdShow)) 
        return FALSE;
    while (GetMessage(&msg, NULL, 0, 0)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    return (int)msg.wParam;
}

ATOM InitApp(HINSTANCE hInst)
{
    WNDCLASSEX wc;
    wc.cbSize        = sizeof(WNDCLASSEX);
    wc.style         = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc   = WndProc;
    wc.cbClsExtra    = 0;
    wc.cbWndExtra    = 0;
    wc.hInstance     = hInst;
    wc.hIcon         = LoadIcon(hInst, TEXT("APP_ICON"));
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = (LPCWSTR)g_szClassName;
    wc.hIconSm       = LoadIcon(hInst, TEXT("APP_ICON"));
    return (RegisterClassEx(&wc));
}

//ウィンドウの生成

BOOL InitInstance(HINSTANCE hInst, int nCmdShow)
{
    HWND hWnd;

    hWnd = CreateWindowEx(
            WS_EX_TOPMOST | WS_EX_CLIENTEDGE,
            g_szClassName,
            WINDOW_TITLE,
            WS_OVERLAPPED | WS_SYSMENU,
            0,              //X座標
            0,              //Y座標
            WINDOW_WIDTH,   //幅
            WINDOW_HEIGHT,  //高さ
            NULL,           //親ウィンドウのハンドル、親を作るときはNULL
            NULL,           //メニューハンドル、クラスメニューを使うときはNULL
            hInst,
            NULL);
    if (!hWnd)
        return FALSE;
    ShowWindow(hWnd, SW_HIDE);
    UpdateWindow(hWnd);

    return TRUE;
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp)
{
    static HINSTANCE hInst;
    WCHAR* cmdLine = NULL;
    int lc = 0;
    size_t tlen = 0;
    POINT point;
    HMENU hMenu, hSubmenu;

    switch (msg) {
    case WM_CREATE:
        hInst = ( (LPCREATESTRUCT)lp)->hInstance;

        AddHook();

        cmdLine = GetCommandLine();
        lc = cmdLine[wcslen(cmdLine) - 1];
        if('A' <= lc && lc <= 'Z')
        {
            SetShortcutKey(lc);

            tlen = wcslen(WINDOW_TITLE);
            WCHAR* title = new WCHAR[tlen + 3];
            wcscpy_s(title, tlen + 3, WINDOW_TITLE);
            title[tlen] = ' ';
            title[tlen + 1] = lc;
            title[tlen + 2] = '\0';

            SetWindowText(hWnd, title);
            RegistTaskTray(hWnd, hInst, title);

            delete title;
        }
        else
        {
            RegistTaskTray(hWnd, hInst, WINDOW_TITLE);
        }
        break;
    case WM_COMMAND:
        switch(LOWORD(wp) )
        {
        case IDM_EXIT:
            SendMessage(hWnd, WM_DESTROY, (WPARAM)0, (LPARAM)0 );
            break;
        }
        break;
    case UM_TASKTRAYICON:
        if(lp == WM_LBUTTONUP)
        {
            hMenu = LoadMenu(hInst, TEXT("IDM_POPUP") );
            hSubmenu = GetSubMenu(hMenu, 0);
            GetCursorPos(&point);
            TrackPopupMenu(hSubmenu, TPM_LEFTALIGN, point.x, point.y, 0, hWnd, NULL);
            DestroyMenu(hMenu);
        }
        break;
    case WM_CLOSE:
        DestroyWindow(hWnd);
        break;
    case WM_DESTROY:
        RemoveHook();
        ReleseTaskTray(hWnd);
        PostQuitMessage(0);
        break;
    default:
        return (DefWindowProc(hWnd, msg, wp, lp));
    }
    return 0;
}

int RegistTaskTray(HWND hWnd, HINSTANCE hInst, LPCWSTR tip)
{
    NOTIFYICONDATA TrayIcon;
    
    TrayIcon.cbSize           = sizeof(NOTIFYICONDATA);
    TrayIcon.hWnd             = hWnd;
    TrayIcon.uCallbackMessage = UM_TASKTRAYICON;
    TrayIcon.uFlags           = NIF_MESSAGE | NIF_ICON | NIF_TIP;
    TrayIcon.uID              = ID_ICON_TASKTRAY;
    TrayIcon.hIcon            = LoadIcon(hInst, TEXT("APP_ICON"));
    wcscpy_s(TrayIcon.szTip, 64, tip); // 64 ... NOTIFYICONDATA構造体の定義より
    
    return Shell_NotifyIcon(NIM_ADD, &TrayIcon);
}

int ReleseTaskTray(HWND hWnd)
{
    NOTIFYICONDATA TrayIcon;
    
    TrayIcon.cbSize = sizeof(NOTIFYICONDATA);
    TrayIcon.hWnd   = hWnd;
    TrayIcon.uFlags = 0;
    TrayIcon.uID    = ID_ICON_TASKTRAY;
    
    return Shell_NotifyIcon(NIM_DELETE , &TrayIcon);
}