Allgemein

How to use multiple mice with GLFW

I wanted to use multiple mice for a split screen game written in C++ and OpenGL, using GLFW. After several hours of frustrating research and trial’n’error, I made it work. Here’s how (sample project download):

Since GLFW doesn’t support multiple mice, we have to get the raw input data from the Windows API in order to differentiate which mouse the input data is coming from.

First up, we need some special #includes and #defines:

#define GLFW_EXPOSE_NATIVE_WIN32
#define GLFW_EXPOSE_NATIVE_WGL

#include <windows.h>
#include <GLFW\glfw3native.h>

The #defines must match those the library was compiled for. This is individual and failure to do this will cause a link-time error. More info here. “windows.h” gives you access to the Windows API. “glfw3native.h” exposes some extra functionality. More on why we need that later.

In order to recieve the raw input, we have to register a callback function on our window, which get’s called everytime a Windows event occurs. But since the callback function needs to be registered on the original window, we first have to get the original window from GLFW. That’s why we need the  “glfw3native.h”. This is done like so:

window = glfwCreateWindow(width, height,"Window", (fullscreen) ? glfwGetPrimaryMonitor() : nullptr, nullptr);

handle = glfwGetWin32Window(window);

whereas “window” is our GLFW window and the variable “handle” is of the type HWND. After that we need to register our callback function:

currentWndProc = (WNDPROC)GetWindowLongPtr(handle, GWL_WNDPROC);
SetWindowLongPtr(handle, GWL_WNDPROC, (long)winProc);

whereas “currentWndProc” is of the type WNDPROC. Just as HWND, this is another Windows API specific type. Not really of interest for our purposes. In the above code, we define “winProc” as our callback method. This method gets called whenever a Windows event occurs in our window. The name of the method is irrelevant, but the parameters have to match a specific template. That’s how it could look like (taken from my sample project):

LRESULT CALLBACK Application::winProc(HWND hwnd, UINT Msg, WPARAM wParam, LPARAM lParam){

switch (Msg)
 {
 case WM_INPUT:
    application->OnRawInput(GET_RAWINPUT_CODE_WPARAM(wParam) == RIM_INPUT, (HRAWINPUT)lParam);
 break;

 case WM_CLOSE:
    return DefWindowProc(hwnd, Msg, wParam, lParam);
 break;

 default:
    return CallWindowProc(currentWndProc, handle, Msg, wParam, lParam);
 break;
 }
 return 0;
}

In the example above, the method “OnRawInput()” gets called, whenever input data arrives (keyboard, mouse, gamepad etc.). There we’ll seperate between mice.

But before we can do that, we first have to register input from mouse devices, so we actually recieve input from the Windows API. So here it goes:


RAWINPUTDEVICE device[1];

device[0].usUsagePage = 1;
device[0].usUsage = 2;
device[0].dwFlags = 0;
device[0].hwndTarget = NULL;

if (RegisterRawInputDevices(device, 1, sizeof(RAWINPUTDEVICE)) == FALSE)
    std::cout << "Error registering mouse" << std::endl;

“usUsagePage” defines the kind of device it is. Here are the categories.

1 - generic desktop controls // we use this
2 - simulation controls
3 - vr
4 - sport
5 - game
6 - generic device
7 - keyboard
8 - LEDs
9 - button

Depending on the value of “usUsagePage”, “usUsage” has different meanings. In our case it’s 1. So “usUsage” values are mapped like:

0 - undefined
1 - pointer
2 - mouse // we use this
3 - reserved
4 - joystick
5 - game pad
6 - keyboard 
7 - keypad
8 - multi-axis controller
9 - Tablet PC controls

Now the API sends us raw input data for mouse devices. All we have left to do is extract the data and do whatever we want with it. Here’s how you could do that. The following code prints out all the raw mouse data it recieves:


void Application::OnRawInput(bool inForeground, HRAWINPUT hRawInput)
{
    UINT dataSize;
    GetRawInputData(hRawInput, RID_INPUT, NULL,&dataSize, sizeof(RAWINPUTHEADER));

    if (dataSize == 0)
        return;
    if (dataSize > m_RawInputMessageData.size())
        m_RawInputMessageData.resize(dataSize);

    void* dataBuf = &m_RawInputMessageData[0];
    GetRawInputData(hRawInput, RID_INPUT, dataBuf, &dataSize, sizeof(RAWINPUTHEADER));

    const RAWINPUT *raw = (const RAWINPUT*)dataBuf;

    if (raw->header.dwType == RIM_TYPEMOUSE)
    {
        HANDLE deviceHandle = raw->header.hDevice;

        const RAWMOUSE& mouseData = raw->;data.mouse;

        USHORT flags = mouseData.usButtonFlags;
        short wheelDelta = (short)mouseData.usButtonData;
        LONG x = mouseData.lLastX, y = mouseData.lLastY;

        wprintf(L"Mouse: Device=0x%08X, Flags=%04x, WheelDelta=%d, X=%d, Y=%d\n",deviceHandle, flags, wheelDelta, x, y);
    }
}

 

That’s it. I hope it helped. Here’s the sample project on GitHub or a direct download.

Advertisements