使用原始輸入

本節包含下列用途的範例程序代碼:

註冊原始資料輸入

範例 1

在此範例中,應用程式會指定來自遊戲控制器(遊戲板和遊戲桿)的原始輸入,以及電話使用頁面中除答錄機以外的所有裝置。

RAWINPUTDEVICE Rid[4];
        
Rid[0].usUsagePage = 0x01;          // HID_USAGE_PAGE_GENERIC
Rid[0].usUsage = 0x05;              // HID_USAGE_GENERIC_GAMEPAD
Rid[0].dwFlags = 0;                 // adds game pad
Rid[0].hwndTarget = 0;

Rid[1].usUsagePage = 0x01;          // HID_USAGE_PAGE_GENERIC
Rid[1].usUsage = 0x04;              // HID_USAGE_GENERIC_JOYSTICK
Rid[1].dwFlags = 0;                 // adds joystick
Rid[1].hwndTarget = 0;

Rid[2].usUsagePage = 0x0B;          // HID_USAGE_PAGE_TELEPHONY
Rid[2].usUsage = 0x00; 
Rid[2].dwFlags = RIDEV_PAGEONLY;    // adds all devices from telephony page
Rid[2].hwndTarget = 0;

Rid[3].usUsagePage = 0x0B;          // HID_USAGE_PAGE_TELEPHONY
Rid[3].usUsage = 0x02;              // HID_USAGE_TELEPHONY_ANSWERING_MACHINE
Rid[3].dwFlags = RIDEV_EXCLUDE;     // excludes answering machines
Rid[3].hwndTarget = 0;

if (RegisterRawInputDevices(Rid, 4, sizeof(Rid[0])) == FALSE)
{
    //registration failed. Call GetLastError for the cause of the error.
}

範例 2

在此範例中,應用程式想要從鍵盤和滑鼠進行原始輸入,但想要忽略 舊版鍵盤滑鼠視窗訊息(來自相同鍵盤和滑鼠)。

RAWINPUTDEVICE Rid[2];
        
Rid[0].usUsagePage = 0x01;          // HID_USAGE_PAGE_GENERIC
Rid[0].usUsage = 0x02;              // HID_USAGE_GENERIC_MOUSE
Rid[0].dwFlags = RIDEV_NOLEGACY;    // adds mouse and also ignores legacy mouse messages
Rid[0].hwndTarget = 0;

Rid[1].usUsagePage = 0x01;          // HID_USAGE_PAGE_GENERIC
Rid[1].usUsage = 0x06;              // HID_USAGE_GENERIC_KEYBOARD
Rid[1].dwFlags = RIDEV_NOLEGACY;    // adds keyboard and also ignores legacy keyboard messages
Rid[1].hwndTarget = 0;

if (RegisterRawInputDevices(Rid, 2, sizeof(Rid[0])) == FALSE)
{
    //registration failed. Call GetLastError for the cause of the error
}

執行原始輸入的標準讀取

此範例展示了標準讀取 WM_INPUT 訊息處理器原始輸入的最小模式。 每個WM_INPUT訊息在 lParam 中都帶有一個 HRAWINPUT 句柄,參考當前輸入事件——必須先透過 GetRawInputData 讀取,然後再呼叫 DefWindowProc

對於高頻裝置如滑鼠,在 1000Hz 下,訊息迴圈迭代間可能會累積多個事件。 在這種情況下,請繼續使用 GetRawInputBuffer 來清空剩餘的佇列——詳見下方可選的第二階段。

/* Initialized once at startup */
UINT  g_bufferSize = 64 * sizeof(RAWINPUT);
void* g_pBuffer    = NULL;

/* Call once before entering the message loop: */
/* g_pBuffer = malloc(g_bufferSize); */

void ProcessInput(const RAWINPUT* input)
{
    if (input->header.dwType == RIM_TYPEKEYBOARD)
    {
        const RAWKEYBOARD* kb = &input->data.keyboard;
        const char* transition = (kb->Flags & RI_KEY_BREAK) ? "up" : "down";
        const char* extended   = (kb->Flags & RI_KEY_E0)    ? " e0" :
                                 (kb->Flags & RI_KEY_E1)    ? " e1" : "";
        printf("keyboard: vk=0x%02x scan=0x%02x %s%s msg=0x%04x extra=0x%08x\n",
            kb->VKey, kb->MakeCode, transition, extended,
            kb->Message, kb->ExtraInformation);
    }
    else if (input->header.dwType == RIM_TYPEMOUSE)
    {
        const RAWMOUSE* mouse = &input->data.mouse;
        const char* moveMode  = (mouse->usFlags & MOUSE_MOVE_ABSOLUTE) ? "abs" : "rel";

        printf("mouse: move %s dx=%d dy=%d\n", moveMode, mouse->lLastX, mouse->lLastY);

        if (mouse->usButtonFlags & RI_MOUSE_LEFT_BUTTON_DOWN)   printf("  left down\n");
        if (mouse->usButtonFlags & RI_MOUSE_LEFT_BUTTON_UP)     printf("  left up\n");
        if (mouse->usButtonFlags & RI_MOUSE_RIGHT_BUTTON_DOWN)  printf("  right down\n");
        if (mouse->usButtonFlags & RI_MOUSE_RIGHT_BUTTON_UP)    printf("  right up\n");
        if (mouse->usButtonFlags & RI_MOUSE_MIDDLE_BUTTON_DOWN) printf("  middle down\n");
        if (mouse->usButtonFlags & RI_MOUSE_MIDDLE_BUTTON_UP)   printf("  middle up\n");
        if (mouse->usButtonFlags & RI_MOUSE_BUTTON_4_DOWN)      printf("  x1 down\n");
        if (mouse->usButtonFlags & RI_MOUSE_BUTTON_4_UP)        printf("  x1 up\n");
        if (mouse->usButtonFlags & RI_MOUSE_BUTTON_5_DOWN)      printf("  x2 down\n");
        if (mouse->usButtonFlags & RI_MOUSE_BUTTON_5_UP)        printf("  x2 up\n");

        if (mouse->usButtonFlags & RI_MOUSE_WHEEL)
            printf("  wheel delta=%d\n",  (int)(short)mouse->usButtonData);
        if (mouse->usButtonFlags & RI_MOUSE_HWHEEL)
            printf("  hwheel delta=%d\n", (int)(short)mouse->usButtonData);
    }
    else if (input->header.dwType == RIM_TYPEHID)
    {
        const RAWHID* hid = &input->data.hid;
        printf("hid: count=%u size=%u\n", hid->dwCount, hid->dwSizeHid);
    }
}

void DrainRawInputQueue(void)
{
    for (;;)
    {
        UINT bufferSize = g_bufferSize;
        UINT count = GetRawInputBuffer((RAWINPUT*)g_pBuffer, &bufferSize, sizeof(RAWINPUTHEADER));

        if (count == 0)
            break;

        if (count == (UINT)-1)
        {
            /* Buffer too small — grow and retry. */
            g_bufferSize = max(bufferSize, g_bufferSize * 2);
            g_pBuffer = realloc(g_pBuffer, g_bufferSize);
            if (g_pBuffer == NULL)
                break;
            continue;
        }

        {
            RAWINPUT* ri = (RAWINPUT*)g_pBuffer;
            UINT i;
            for (i = 0; i < count; ++i, ri = NEXTRAWINPUTBLOCK(ri))
                ProcessInput(ri);
        }
        /* Do not break — there may be more events in the queue. */
    }
}

/* ... */

case WM_INPUT:
{
    /* Phase 1: read the event carried by this WM_INPUT message. */
    UINT bufferSize = g_bufferSize;
    if (GetRawInputData((HRAWINPUT)lParam, RID_INPUT, g_pBuffer, &bufferSize, sizeof(RAWINPUTHEADER)) != (UINT)-1)
    {
        ProcessInput((RAWINPUT*)g_pBuffer);
    }

    /* Phase 2 (optional): drain any additional events that accumulated in the queue since this message was posted.
     * Recommended for high-frequency devices such as mice at 1000Hz. */
    DrainRawInputQueue();

    return DefWindowProc(hWnd, uMsg, wParam, lParam);
}

執行原始輸入的緩衝讀取

此範例展示了如何利用週期定時器在固定速率批次中讀取原始輸入。 WM_INPUT 訊息刻意不透過 DispatchMessage 發送——因為 GetMessage 會在返回前從原始輸入佇列中移除訊息,因此只使用帶有明確訊息範圍過濾器的 PeekMessage ,完全跳過 WM_INPUT 。 其他所有訊息則通常透過 DispatchMessage 發送。 這樣會讓所有原始輸入事件都留在佇列中,讓 GetRawInputBuffer 能在每個計時器週期一次清空所有事件。 這種方法非常適合遊戲循環及其他以固定速率處理輸入而非個別反應的應用程式。

備註

不要將RIDEV_DEVNOTIFY與此模式一起使用。

由於WM_INPUT_DEVICE_CHANGE是透過原始輸入佇列傳送,這裡使用的範圍過濾 PeekMessage 呼叫無法取得,導致執行緒旋轉。 若要接收裝置變更通知,請改用 標準讀取 模式。

MSG msg;
BOOL running = TRUE;

HWND hWnd = CreateWindowExW(0, L"Message", NULL, 0, 0, 0, 0, 0, HWND_MESSAGE, NULL, NULL, NULL);

RAWINPUTDEVICE rid[2] = {
    { 0x01, 0x02, RIDEV_INPUTSINK, hWnd }, /* mouse */
    { 0x01, 0x06, RIDEV_INPUTSINK, hWnd }, /* keyboard */
};
RegisterRawInputDevices(rid, 2, sizeof(RAWINPUTDEVICE));

/* Drain raw input queue every 16ms (~60Hz) */
SetTimer(hWnd, 1, 16, NULL);

/* Message loop — WM_INPUT is skipped via range filters so it
 * accumulates in the raw input queue for DrainRawInputQueue to drain. */
while (running)
{
    while (PeekMessageW(&msg, NULL, 0, WM_INPUT - 1, PM_REMOVE) ||
           PeekMessageW(&msg, NULL, WM_INPUT + 1, 0xFFFF, PM_REMOVE))
    {
        if (msg.message == WM_QUIT)
        {
            running = FALSE;
            break;
        }

        if (msg.message == WM_TIMER)
        {
            DrainRawInputQueue();
        }

        DispatchMessageW(&msg);
    }

    if (running)
        WaitMessage();
}