InputManager: Support multiple mice via raw input

Only on Windows for now.
This commit is contained in:
Stenzek
2024-08-23 22:31:59 +10:00
parent 8b3fd538ea
commit 9e3507e0f4
37 changed files with 1480 additions and 1040 deletions

View File

@ -233,14 +233,11 @@ float ImGuiManager::GetWindowHeight()
return s_window_height;
}
void ImGuiManager::WindowResized()
void ImGuiManager::WindowResized(float width, float height)
{
const u32 new_width = g_gpu_device ? g_gpu_device->GetWindowWidth() : 0;
const u32 new_height = g_gpu_device ? g_gpu_device->GetWindowHeight() : 0;
s_window_width = static_cast<float>(new_width);
s_window_height = static_cast<float>(new_height);
ImGui::GetIO().DisplaySize = ImVec2(s_window_width, s_window_height);
s_window_width = width;
s_window_height = height;
ImGui::GetIO().DisplaySize = ImVec2(width, height);
// Scale might have changed as a result of window resize.
RequestScaleUpdate();
@ -1053,7 +1050,7 @@ void ImGuiManager::DrawSoftwareCursor(const SoftwareCursor& sc, const std::pair<
void ImGuiManager::RenderSoftwareCursors()
{
// This one's okay to race, worst that happens is we render the wrong number of cursors for a frame.
const u32 pointer_count = InputManager::MAX_POINTER_DEVICES;
const u32 pointer_count = InputManager::GetPointerCount();
for (u32 i = 0; i < pointer_count; i++)
DrawSoftwareCursor(s_software_cursors[i], InputManager::GetPointerAbsolutePosition(i));
@ -1076,8 +1073,8 @@ void ImGuiManager::SetSoftwareCursor(u32 index, std::string image_path, float im
UpdateSoftwareCursorTexture(index);
// Hide the system cursor when we activate a software cursor.
if (is_hiding_or_showing && index == 0)
InputManager::UpdateHostMouseMode();
if (is_hiding_or_showing && index <= InputManager::MAX_POINTER_DEVICES)
InputManager::UpdateRelativeMouseMode();
}
bool ImGuiManager::HasSoftwareCursor(u32 index)

View File

@ -35,7 +35,7 @@ float GetWindowWidth();
float GetWindowHeight();
/// Updates internal state when the window is size.
void WindowResized();
void WindowResized(float width, float height);
/// Updates scaling of the on-screen elements.
void RequestScaleUpdate();

View File

@ -108,6 +108,7 @@ static std::vector<std::string_view> SplitChord(std::string_view binding);
static bool SplitBinding(std::string_view binding, std::string_view* source, std::string_view* sub_binding);
static void PrettifyInputBindingPart(std::string_view binding, SmallString& ret, bool& changed);
static void AddBindings(const std::vector<std::string>& bindings, const InputEventHandler& handler);
static void UpdatePointerCount();
static bool IsAxisHandler(const InputEventHandler& handler);
static float ApplySingleBindingScale(float sensitivity, float deadzone, float value);
@ -180,11 +181,19 @@ static std::array<std::array<float, static_cast<u8>(InputPointerAxis::Count)>, I
static std::array<std::array<PointerAxisState, static_cast<u8>(InputPointerAxis::Count)>,
InputManager::MAX_POINTER_DEVICES>
s_pointer_state;
static u32 s_pointer_count = 0;
static std::array<float, static_cast<u8>(InputPointerAxis::Count)> s_pointer_axis_scale;
using PointerMoveCallback = std::function<void(InputBindingKey key, float value)>;
static std::vector<std::pair<u32, PointerMoveCallback>> s_pointer_move_callbacks;
// Window size, used for clamping the mouse position in raw input modes.
static std::array<float, 2> s_window_size = {};
static bool s_relative_mouse_mode = false;
static bool s_relative_mouse_mode_active = false;
static bool s_hide_host_mouse_cursor = false;
static bool s_hide_host_mouse_cusor_active = false;
// ------------------------------------------------------------------------
// Binding Parsing
// ------------------------------------------------------------------------
@ -293,12 +302,12 @@ bool InputManager::ParseBindingAndGetSource(std::string_view binding, InputBindi
std::string InputManager::ConvertInputBindingKeyToString(InputBindingInfo::Type binding_type, InputBindingKey key)
{
if (binding_type == InputBindingInfo::Type::Pointer)
if (binding_type == InputBindingInfo::Type::Pointer || binding_type == InputBindingInfo::Type::AbsolutePointer)
{
// pointer and device bindings don't have a data part
if (key.source_type == InputSourceType::Pointer)
{
return GetPointerDeviceName(key.data);
return GetPointerDeviceName(key.source_index);
}
else if (key.source_type < InputSourceType::Count && s_input_sources[static_cast<u32>(key.source_type)])
{
@ -346,7 +355,7 @@ std::string InputManager::ConvertInputBindingKeysToString(InputBindingInfo::Type
const InputBindingKey* keys, size_t num_keys)
{
// can't have a chord of devices/pointers
if (binding_type == InputBindingInfo::Type::Pointer)
if (binding_type == InputBindingInfo::Type::Pointer || binding_type == InputBindingInfo::Type::AbsolutePointer)
{
// so only take the first
if (num_keys > 0)
@ -598,10 +607,10 @@ static std::array<const char*, static_cast<u32>(InputSourceType::Count)> s_input
#ifdef _WIN32
"DInput",
"XInput",
"RawInput",
#endif
#ifndef __ANDROID__
"SDL",
"RawInput",
#else
"Android",
#endif
@ -631,13 +640,13 @@ bool InputManager::GetInputSourceDefaultEnabled(InputSourceType type)
case InputSourceType::XInput:
return false;
case InputSourceType::RawInput:
return false;
#endif
#ifndef __ANDROID__
case InputSourceType::SDL:
return true;
case InputSourceType::RawInput:
return false;
#else
case InputSourceType::Android:
return true;
@ -844,6 +853,7 @@ void InputManager::AddPadBindings(SettingsInterface& si, const std::string& sect
break;
case InputBindingInfo::Type::Pointer:
case InputBindingInfo::Type::AbsolutePointer:
{
auto cb = [pad_index, base = bi.bind_index](InputBindingKey key, float value) {
if (!System::IsValid())
@ -867,7 +877,7 @@ void InputManager::AddPadBindings(SettingsInterface& si, const std::string& sect
if (!key.has_value())
continue;
s_pointer_move_callbacks.emplace_back(0, cb);
s_pointer_move_callbacks.emplace_back(key.value(), cb);
}
}
}
@ -1171,7 +1181,7 @@ void InputManager::GenerateRelativeMouseEvents()
{
const bool system_running = System::IsRunning();
for (u32 device = 0; device < MAX_POINTER_DEVICES; device++)
for (u32 device = 0; device < s_pointer_count; device++)
{
for (u32 axis = 0; axis < static_cast<u32>(static_cast<u8>(InputPointerAxis::Count)); axis++)
{
@ -1208,14 +1218,44 @@ void InputManager::GenerateRelativeMouseEvents()
}
}
void InputManager::UpdatePointerCount()
{
if (!IsUsingRawInput())
{
s_pointer_count = 1;
return;
}
#ifndef __ANDROID__
InputSource* ris = GetInputSourceInterface(InputSourceType::RawInput);
DebugAssert(ris);
s_pointer_count = 0;
for (const std::pair<std::string, std::string>& it : ris->EnumerateDevices())
{
if (it.first.starts_with("Pointer-"))
s_pointer_count++;
}
#endif
}
u32 InputManager::GetPointerCount()
{
return s_pointer_count;
}
std::pair<float, float> InputManager::GetPointerAbsolutePosition(u32 index)
{
DebugAssert(index < s_host_pointer_positions.size());
return std::make_pair(s_host_pointer_positions[index][static_cast<u8>(InputPointerAxis::X)],
s_host_pointer_positions[index][static_cast<u8>(InputPointerAxis::Y)]);
}
void InputManager::UpdatePointerAbsolutePosition(u32 index, float x, float y)
{
if (index >= MAX_POINTER_DEVICES || s_relative_mouse_mode_active) [[unlikely]]
return;
const float dx = x - std::exchange(s_host_pointer_positions[index][static_cast<u8>(InputPointerAxis::X)], x);
const float dy = y - std::exchange(s_host_pointer_positions[index][static_cast<u8>(InputPointerAxis::Y)], y);
@ -1236,18 +1276,26 @@ void InputManager::UpdatePointerAbsolutePosition(u32 index, float x, float y)
void InputManager::UpdatePointerRelativeDelta(u32 index, InputPointerAxis axis, float d, bool raw_input)
{
if (raw_input != IsUsingRawInput())
if (index >= MAX_POINTER_DEVICES || !s_relative_mouse_mode_active)
return;
s_host_pointer_positions[index][static_cast<u8>(axis)] += d;
s_pointer_state[index][static_cast<u8>(axis)].delta.fetch_add(static_cast<s32>(d * 65536.0f),
std::memory_order_release);
if (index == 0 && axis <= InputPointerAxis::Y)
ImGuiManager::UpdateMousePosition(s_host_pointer_positions[0][0], s_host_pointer_positions[0][1]);
// We need to clamp the position ourselves in relative mode.
if (axis <= InputPointerAxis::Y)
{
s_host_pointer_positions[index][static_cast<u8>(axis)] =
std::clamp(s_host_pointer_positions[index][static_cast<u8>(axis)], 0.0f, s_window_size[static_cast<u8>(axis)]);
// Imgui also needs to be updated, since the absolute position won't be set above.
if (index == 0)
ImGuiManager::UpdateMousePosition(s_host_pointer_positions[0][0], s_host_pointer_positions[0][1]);
}
}
void InputManager::UpdateHostMouseMode()
void InputManager::UpdateRelativeMouseMode()
{
// Check for relative mode bindings, and enable if there's anything using it.
bool has_relative_mode_bindings = !s_pointer_move_callbacks.empty();
@ -1265,8 +1313,29 @@ void InputManager::UpdateHostMouseMode()
}
}
const bool has_software_cursor = ImGuiManager::HasSoftwareCursor(0);
Host::SetMouseMode(has_relative_mode_bindings, has_relative_mode_bindings || has_software_cursor);
const bool hide_mouse_cursor = has_relative_mode_bindings || ImGuiManager::HasSoftwareCursor(0);
if (s_relative_mouse_mode == has_relative_mode_bindings && s_hide_host_mouse_cursor == hide_mouse_cursor)
return;
s_relative_mouse_mode = has_relative_mode_bindings;
s_hide_host_mouse_cursor = hide_mouse_cursor;
UpdateRelativeMouseMode();
}
void InputManager::UpdateHostMouseMode()
{
const bool can_change = System::IsRunning();
const bool wanted_relative_mouse_mode = (s_relative_mouse_mode && can_change);
const bool wanted_hide_host_mouse_cursor = (s_hide_host_mouse_cursor && can_change);
if (wanted_relative_mouse_mode == s_relative_mouse_mode_active &&
wanted_hide_host_mouse_cursor == s_hide_host_mouse_cusor_active)
{
return;
}
s_relative_mouse_mode_active = wanted_relative_mouse_mode;
s_hide_host_mouse_cusor_active = wanted_hide_host_mouse_cursor;
Host::SetMouseMode(wanted_relative_mouse_mode, wanted_hide_host_mouse_cursor);
}
bool InputManager::IsUsingRawInput()
@ -1278,6 +1347,12 @@ bool InputManager::IsUsingRawInput()
#endif
}
void InputManager::SetDisplayWindowSize(float width, float height)
{
s_window_size[0] = width;
s_window_size[1] = height;
}
void InputManager::SetDefaultSourceConfig(SettingsInterface& si)
{
si.ClearSection("InputSources");
@ -1454,11 +1529,13 @@ std::vector<std::string> InputManager::GetInputProfileNames()
void InputManager::OnInputDeviceConnected(std::string_view identifier, std::string_view device_name)
{
INFO_LOG("Device '{}' connected: '{}'", identifier, device_name);
Host::OnInputDeviceConnected(identifier, device_name);
}
void InputManager::OnInputDeviceDisconnected(InputBindingKey key, std::string_view identifier)
{
INFO_LOG("Device '{}' disconnected", identifier);
Host::OnInputDeviceDisconnected(key, identifier);
}
@ -1771,7 +1848,7 @@ void InputManager::ReloadBindings(SettingsInterface& si, SettingsInterface& bind
1.0f);
}
UpdateHostMouseMode();
UpdateRelativeMouseMode();
}
// ------------------------------------------------------------------------
@ -1788,6 +1865,8 @@ bool InputManager::ReloadDevices()
changed |= s_input_sources[i]->ReloadDevices();
}
UpdatePointerCount();
return changed;
}
@ -1972,4 +2051,6 @@ void InputManager::ReloadSources(SettingsInterface& si, std::unique_lock<std::mu
#else
UpdateInputSourceState(si, settings_lock, InputSourceType::Android, &InputSource::CreateAndroidSource);
#endif
UpdatePointerCount();
}

View File

@ -27,10 +27,10 @@ enum class InputSourceType : u32
#ifdef _WIN32
DInput,
XInput,
RawInput,
#endif
#ifndef __ANDROID__
SDL,
RawInput,
#else
Android,
#endif
@ -174,12 +174,12 @@ namespace InputManager {
static constexpr double VIBRATION_UPDATE_INTERVAL_SECONDS = 0.5; // 500ms
/// Maximum number of host mouse devices.
static constexpr u32 MAX_POINTER_DEVICES = 1;
static constexpr u32 MAX_POINTER_DEVICES = 8;
static constexpr u32 MAX_POINTER_BUTTONS = 3;
/// Maximum number of software cursors. We allocate an extra two for controllers with
/// positioning data from the controller instead of a mouse.
static constexpr u32 MAX_SOFTWARE_CURSORS = MAX_POINTER_BUTTONS + 2;
static constexpr u32 MAX_SOFTWARE_CURSORS = MAX_POINTER_DEVICES + 2;
/// Number of macro buttons per controller.
static constexpr u32 NUM_MACRO_BUTTONS_PER_CONTROLLER = 4;
@ -311,6 +311,9 @@ void SetPadVibrationIntensity(u32 pad_index, float large_or_single_motor_intensi
/// The pad vibration state will internally remain, so that when emulation is unpaused, the effect resumes.
void PauseVibration();
/// Returns the number of currently-connected pointer devices.
u32 GetPointerCount();
/// Reads absolute pointer position.
std::pair<float, float> GetPointerAbsolutePosition(u32 index);
@ -322,6 +325,7 @@ void UpdatePointerAbsolutePosition(u32 index, float x, float y);
void UpdatePointerRelativeDelta(u32 index, InputPointerAxis axis, float d, bool raw_input = false);
/// Updates host mouse mode (relative/cursor hiding).
void UpdateRelativeMouseMode();
void UpdateHostMouseMode();
/// Sets the state of the specified macro button.
@ -330,6 +334,9 @@ void SetMacroButtonState(u32 pad, u32 index, bool state);
/// Returns true if the raw input source is being used.
bool IsUsingRawInput();
/// Updates InputManager's view of the window size, used for clamping raw input coordinates.
void SetDisplayWindowSize(float width, float height);
/// Restores default configuration.
void SetDefaultSourceConfig(SettingsInterface& si);

View File

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
#include "win32_raw_input_source.h"
@ -8,7 +8,9 @@
#include "core/host.h"
#include "core/system.h"
#include "input_manager.h"
#include <cmath>
#include <hidsdi.h>
#include <hidusage.h>
#include <malloc.h>
@ -71,7 +73,11 @@ void Win32RawInputSource::PollEvents()
std::vector<std::pair<std::string, std::string>> Win32RawInputSource::EnumerateDevices()
{
return {};
std::vector<std::pair<std::string, std::string>> ret;
for (u32 pointer_index = 0; pointer_index < static_cast<u32>(m_mice.size()); pointer_index++)
ret.emplace_back(InputManager::GetPointerDeviceName(pointer_index), GetMouseDeviceName(pointer_index));
return ret;
}
void Win32RawInputSource::UpdateMotorState(InputBindingKey key, float intensity)
@ -117,7 +123,8 @@ bool Win32RawInputSource::RegisterDummyClass()
wc.hInstance = GetModuleHandleW(nullptr);
wc.lpfnWndProc = DummyWindowProc;
wc.lpszClassName = WINDOW_CLASS_NAME;
return (RegisterClassW(&wc) != 0);
s_window_class_registered = (RegisterClassW(&wc) != 0);
return s_window_class_registered;
}
bool Win32RawInputSource::CreateDummyWindow()
@ -160,12 +167,58 @@ LRESULT CALLBACK Win32RawInputSource::DummyWindowProc(HWND hwnd, UINT msg, WPARA
return DefWindowProcW(hwnd, msg, wParam, lParam);
}
std::string Win32RawInputSource::GetMouseDeviceName(u32 index)
{
#if 0
// Doesn't work for mice :(
const HANDLE device = m_mice[index].device;
std::wstring wdevice_name;
UINT size = 0;
if (GetRawInputDeviceInfoW(device, RIDI_DEVICENAME, nullptr, &size) == static_cast<UINT>(-1))
goto error;
wdevice_name.resize(size);
UINT written_size = GetRawInputDeviceInfoW(device, RIDI_DEVICENAME, wdevice_name.data(), &size);
if (written_size == static_cast<UINT>(-1))
goto error;
wdevice_name.resize(written_size);
if (wdevice_name.empty())
goto error;
const HANDLE hFile = CreateFileW(wdevice_name.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr,
OPEN_EXISTING, 0, NULL);
if (hFile == INVALID_HANDLE_VALUE)
goto error;
wchar_t product_string[256];
if (!HidD_GetProductString(hFile, product_string, sizeof(product_string)))
{
CloseHandle(hFile);
goto error;
}
CloseHandle(hFile);
return StringUtil::WideStringToUTF8String(product_string);
error:
return "Unknown Device";
#else
return fmt::format("Raw Input Pointer {}", index);
#endif
}
bool Win32RawInputSource::OpenDevices()
{
UINT num_devices = 0;
if (GetRawInputDeviceList(nullptr, &num_devices, sizeof(RAWINPUTDEVICELIST)) == static_cast<UINT>(-1) ||
num_devices == 0)
{
return false;
}
std::vector<RAWINPUTDEVICELIST> devices(num_devices);
if (GetRawInputDeviceList(devices.data(), &num_devices, sizeof(RAWINPUTDEVICELIST)) == static_cast<UINT>(-1))
@ -174,28 +227,37 @@ bool Win32RawInputSource::OpenDevices()
for (const RAWINPUTDEVICELIST& rid : devices)
{
#if 0
if (rid.dwType == RIM_TYPEKEYBOARD)
m_num_keyboards++;
#endif
if (rid.dwType == RIM_TYPEMOUSE)
m_mice.push_back({rid.hDevice, 0u, 0, 0});
{
// Make sure it's a real mouse with buttons.
// My goal with this was to stop my silly Corsair keyboard from showing up as a mouse... but it reports 32
// buttons.
RID_DEVICE_INFO devinfo = {
.cbSize = sizeof(devinfo),
.dwType = RIM_TYPEMOUSE,
};
UINT devinfo_size = sizeof(devinfo);
if (GetRawInputDeviceInfoW(rid.hDevice, RIDI_DEVICEINFO, &devinfo, &devinfo_size) <= 0 ||
devinfo.mouse.dwNumberOfButtons == 0)
{
continue;
}
m_mice.push_back({.device = rid.hDevice, .button_state = 0, .last_x = 0, .last_y = 0});
}
}
DEV_LOG("Found {} keyboards and {} mice", m_num_keyboards, m_mice.size());
DEV_LOG("Found {} mice", m_mice.size());
// Grab all keyboard/mouse input.
if (m_num_keyboards > 0)
{
const RAWINPUTDEVICE rrid = {HID_USAGE_PAGE_GENERIC, HID_USAGE_GENERIC_KEYBOARD, 0, m_dummy_window};
if (!RegisterRawInputDevices(&rrid, 1, sizeof(rrid)))
return false;
}
// Grab all mouse input.
if (!m_mice.empty())
{
const RAWINPUTDEVICE rrid = {HID_USAGE_PAGE_GENERIC, HID_USAGE_GENERIC_MOUSE, 0, m_dummy_window};
if (!RegisterRawInputDevices(&rrid, 1, sizeof(rrid)))
return false;
for (u32 i = 0; i < static_cast<u32>(m_mice.size()); i++)
InputManager::OnInputDeviceConnected(InputManager::GetPointerDeviceName(i), GetMouseDeviceName(i));
}
return true;
@ -203,17 +265,16 @@ bool Win32RawInputSource::OpenDevices()
void Win32RawInputSource::CloseDevices()
{
if (m_num_keyboards > 0)
{
const RAWINPUTDEVICE rrid = {HID_USAGE_PAGE_GENERIC, HID_USAGE_GENERIC_MOUSE, RIDEV_REMOVE, m_dummy_window};
RegisterRawInputDevices(&rrid, 1, sizeof(rrid));
m_num_keyboards = 0;
}
if (!m_mice.empty())
{
const RAWINPUTDEVICE rrid = {HID_USAGE_PAGE_GENERIC, HID_USAGE_GENERIC_KEYBOARD, RIDEV_REMOVE, m_dummy_window};
RegisterRawInputDevices(&rrid, 1, sizeof(rrid));
for (u32 i = 0; i < static_cast<u32>(m_mice.size()); i++)
{
InputManager::OnInputDeviceDisconnected(InputManager::MakePointerAxisKey(i, InputPointerAxis::X),
InputManager::GetPointerDeviceName(i));
}
m_mice.clear();
}
}
@ -222,9 +283,9 @@ bool Win32RawInputSource::ProcessRawInputEvent(const RAWINPUT* event)
{
if (event->header.dwType == RIM_TYPEMOUSE)
{
const u32 mouse_index = 0;
for (MouseState& state : m_mice)
for (u32 pointer_index = 0; pointer_index < static_cast<u32>(m_mice.size()); pointer_index++)
{
MouseState& state = m_mice[pointer_index];
if (state.device != event->header.hDevice)
continue;
@ -244,10 +305,6 @@ bool Win32RawInputSource::ProcessRawInputEvent(const RAWINPUT* event)
(rm.usButtonFlags & (rm.usButtonFlags ^ std::exchange(state.button_state, rm.usButtonFlags))) &
ALL_BUTTON_MASKS;
// when the VM isn't running, allow events to run as normal (so we don't break the UI)
if (System::GetState() != System::State::Running)
return false;
while (button_mask != 0)
{
unsigned long bit_index;
@ -255,17 +312,17 @@ bool Win32RawInputSource::ProcessRawInputEvent(const RAWINPUT* event)
// these are ordered down..up for each button
const u32 button_number = bit_index >> 1;
const bool button_pressed = (bit_index & 1u) != 0;
InputManager::InvokeEvents(InputManager::MakePointerButtonKey(mouse_index, button_number),
const bool button_pressed = (bit_index & 1u) == 0;
InputManager::InvokeEvents(InputManager::MakePointerButtonKey(pointer_index, button_number),
static_cast<float>(button_pressed), GenericInputBinding::Unknown);
button_mask &= ~(1u << bit_index);
}
if (dx != 0)
InputManager::UpdatePointerRelativeDelta(mouse_index, InputPointerAxis::X, static_cast<float>(dx), true);
InputManager::UpdatePointerRelativeDelta(pointer_index, InputPointerAxis::X, static_cast<float>(dx), true);
if (dy != 0)
InputManager::UpdatePointerRelativeDelta(mouse_index, InputPointerAxis::Y, static_cast<float>(dy), true);
InputManager::UpdatePointerRelativeDelta(pointer_index, InputPointerAxis::Y, static_cast<float>(dy), true);
return true;
}

View File

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
#pragma once
@ -46,6 +46,8 @@ private:
static bool RegisterDummyClass();
static LRESULT CALLBACK DummyWindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
static std::string GetMouseDeviceName(u32 index);
bool CreateDummyWindow();
void DestroyDummyWindow();
bool OpenDevices();
@ -54,7 +56,6 @@ private:
bool ProcessRawInputEvent(const RAWINPUT* event);
HWND m_dummy_window = {};
u32 m_num_keyboards = 0;
std::vector<MouseState> m_mice;
};