Поделиться через


Использование необработанных входных данных

В этом разделе приведен пример кода для следующих целей:

Регистрация необработанных входных данных

Пример 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 содержит дескриптор HRAWINPUT в lParam , ссылающийся на текущее событие ввода, необходимо считывать с помощью GetRawInputData перед вызовом DefWindowProc.

Для высокочастотных устройств, таких как мыши на 1000Гц, несколько событий могут накапливаться между итерациями цикла сообщений. В этом случае следуйте инструкциям GetRawInputBuffer для очистки оставшейся очереди — см. необязательный этап 2 ниже.

/* 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 может обработать их все одновременно на каждом такте таймера. Этот подход хорошо подходит для игровых циклов и других приложений, которые обрабатывают входные данные с фиксированной скоростью, а не реагируют на каждое событие по отдельности.

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();
}