win32_raw_input_source.cpp (9901B)
1 // SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com> 2 // SPDX-License-Identifier: (GPL-3.0 OR PolyForm-Strict-1.0.0) 3 4 #include "win32_raw_input_source.h" 5 #include "common/assert.h" 6 #include "common/log.h" 7 #include "common/string_util.h" 8 #include "core/host.h" 9 #include "core/system.h" 10 #include "input_manager.h" 11 12 #include <cmath> 13 #include <hidsdi.h> 14 #include <hidusage.h> 15 #include <malloc.h> 16 17 Log_SetChannel(Win32RawInputSource); 18 19 static const wchar_t* WINDOW_CLASS_NAME = L"Win32RawInputSource"; 20 static bool s_window_class_registered = false; 21 22 static constexpr const u32 ALL_BUTTON_MASKS = RI_MOUSE_BUTTON_1_DOWN | RI_MOUSE_BUTTON_1_UP | RI_MOUSE_BUTTON_2_DOWN | 23 RI_MOUSE_BUTTON_2_UP | RI_MOUSE_BUTTON_3_DOWN | RI_MOUSE_BUTTON_3_UP | 24 RI_MOUSE_BUTTON_4_DOWN | RI_MOUSE_BUTTON_4_UP | RI_MOUSE_BUTTON_5_DOWN | 25 RI_MOUSE_BUTTON_5_UP; 26 27 Win32RawInputSource::Win32RawInputSource() = default; 28 29 Win32RawInputSource::~Win32RawInputSource() = default; 30 31 bool Win32RawInputSource::Initialize(SettingsInterface& si, std::unique_lock<std::mutex>& settings_lock) 32 { 33 if (!RegisterDummyClass()) 34 { 35 ERROR_LOG("Failed to register dummy window class"); 36 return false; 37 } 38 39 if (!CreateDummyWindow()) 40 { 41 ERROR_LOG("Failed to create dummy window"); 42 return false; 43 } 44 45 if (!OpenDevices()) 46 { 47 ERROR_LOG("Failed to open devices"); 48 return false; 49 } 50 51 return true; 52 } 53 54 void Win32RawInputSource::UpdateSettings(SettingsInterface& si, std::unique_lock<std::mutex>& settings_lock) 55 { 56 } 57 58 bool Win32RawInputSource::ReloadDevices() 59 { 60 return false; 61 } 62 63 void Win32RawInputSource::Shutdown() 64 { 65 CloseDevices(); 66 DestroyDummyWindow(); 67 } 68 69 void Win32RawInputSource::PollEvents() 70 { 71 // noop, handled by message pump 72 } 73 74 std::vector<std::pair<std::string, std::string>> Win32RawInputSource::EnumerateDevices() 75 { 76 std::vector<std::pair<std::string, std::string>> ret; 77 for (u32 pointer_index = 0; pointer_index < static_cast<u32>(m_mice.size()); pointer_index++) 78 ret.emplace_back(InputManager::GetPointerDeviceName(pointer_index), GetMouseDeviceName(pointer_index)); 79 80 return ret; 81 } 82 83 void Win32RawInputSource::UpdateMotorState(InputBindingKey key, float intensity) 84 { 85 } 86 87 void Win32RawInputSource::UpdateMotorState(InputBindingKey large_key, InputBindingKey small_key, float large_intensity, 88 float small_intensity) 89 { 90 } 91 92 std::optional<InputBindingKey> Win32RawInputSource::ParseKeyString(std::string_view device, std::string_view binding) 93 { 94 return std::nullopt; 95 } 96 97 TinyString Win32RawInputSource::ConvertKeyToString(InputBindingKey key) 98 { 99 return {}; 100 } 101 102 TinyString Win32RawInputSource::ConvertKeyToIcon(InputBindingKey key) 103 { 104 return {}; 105 } 106 107 std::vector<InputBindingKey> Win32RawInputSource::EnumerateMotors() 108 { 109 return {}; 110 } 111 112 bool Win32RawInputSource::GetGenericBindingMapping(std::string_view device, GenericInputBindingMapping* mapping) 113 { 114 return {}; 115 } 116 117 bool Win32RawInputSource::RegisterDummyClass() 118 { 119 if (s_window_class_registered) 120 return true; 121 122 WNDCLASSW wc = {}; 123 wc.hInstance = GetModuleHandleW(nullptr); 124 wc.lpfnWndProc = DummyWindowProc; 125 wc.lpszClassName = WINDOW_CLASS_NAME; 126 s_window_class_registered = (RegisterClassW(&wc) != 0); 127 return s_window_class_registered; 128 } 129 130 bool Win32RawInputSource::CreateDummyWindow() 131 { 132 m_dummy_window = CreateWindowExW(0, WINDOW_CLASS_NAME, WINDOW_CLASS_NAME, WS_OVERLAPPED, CW_USEDEFAULT, CW_USEDEFAULT, 133 CW_USEDEFAULT, CW_USEDEFAULT, HWND_MESSAGE, NULL, GetModuleHandleW(nullptr), NULL); 134 if (!m_dummy_window) 135 return false; 136 137 SetWindowLongPtrW(m_dummy_window, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(this)); 138 return true; 139 } 140 141 void Win32RawInputSource::DestroyDummyWindow() 142 { 143 if (!m_dummy_window) 144 return; 145 146 DestroyWindow(m_dummy_window); 147 m_dummy_window = {}; 148 } 149 150 LRESULT CALLBACK Win32RawInputSource::DummyWindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) 151 { 152 if (msg != WM_INPUT) 153 return DefWindowProcW(hwnd, msg, wParam, lParam); 154 155 UINT size = 0; 156 GetRawInputData((HRAWINPUT)lParam, RID_INPUT, nullptr, &size, sizeof(RAWINPUTHEADER)); 157 158 PRAWINPUT data = static_cast<PRAWINPUT>(_alloca(size)); 159 GetRawInputData((HRAWINPUT)lParam, RID_INPUT, data, &size, sizeof(RAWINPUTHEADER)); 160 161 // we shouldn't get any WM_INPUT messages prior to SetWindowLongPtr(), so this'll be fine 162 Win32RawInputSource* ris = reinterpret_cast<Win32RawInputSource*>(GetWindowLongPtrW(hwnd, GWLP_USERDATA)); 163 if (ris->ProcessRawInputEvent(data)) 164 return 0; 165 166 // forward through to normal message processing 167 return DefWindowProcW(hwnd, msg, wParam, lParam); 168 } 169 170 std::string Win32RawInputSource::GetMouseDeviceName(u32 index) 171 { 172 #if 0 173 // Doesn't work for mice :( 174 const HANDLE device = m_mice[index].device; 175 std::wstring wdevice_name; 176 177 UINT size = 0; 178 if (GetRawInputDeviceInfoW(device, RIDI_DEVICENAME, nullptr, &size) == static_cast<UINT>(-1)) 179 goto error; 180 181 wdevice_name.resize(size); 182 183 UINT written_size = GetRawInputDeviceInfoW(device, RIDI_DEVICENAME, wdevice_name.data(), &size); 184 if (written_size == static_cast<UINT>(-1)) 185 goto error; 186 187 wdevice_name.resize(written_size); 188 if (wdevice_name.empty()) 189 goto error; 190 191 const HANDLE hFile = CreateFileW(wdevice_name.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, 192 OPEN_EXISTING, 0, NULL); 193 if (hFile == INVALID_HANDLE_VALUE) 194 goto error; 195 196 wchar_t product_string[256]; 197 if (!HidD_GetProductString(hFile, product_string, sizeof(product_string))) 198 { 199 CloseHandle(hFile); 200 goto error; 201 } 202 203 CloseHandle(hFile); 204 205 return StringUtil::WideStringToUTF8String(product_string); 206 207 error: 208 return "Unknown Device"; 209 #else 210 return fmt::format("Raw Input Pointer {}", index); 211 #endif 212 } 213 214 bool Win32RawInputSource::OpenDevices() 215 { 216 UINT num_devices = 0; 217 if (GetRawInputDeviceList(nullptr, &num_devices, sizeof(RAWINPUTDEVICELIST)) == static_cast<UINT>(-1) || 218 num_devices == 0) 219 { 220 return false; 221 } 222 223 std::vector<RAWINPUTDEVICELIST> devices(num_devices); 224 if (GetRawInputDeviceList(devices.data(), &num_devices, sizeof(RAWINPUTDEVICELIST)) == static_cast<UINT>(-1)) 225 return false; 226 devices.resize(num_devices); 227 228 for (const RAWINPUTDEVICELIST& rid : devices) 229 { 230 if (rid.dwType == RIM_TYPEMOUSE) 231 { 232 // Make sure it's a real mouse with buttons. 233 // My goal with this was to stop my silly Corsair keyboard from showing up as a mouse... but it reports 32 234 // buttons. 235 RID_DEVICE_INFO devinfo = { 236 .cbSize = sizeof(devinfo), 237 .dwType = RIM_TYPEMOUSE, 238 }; 239 UINT devinfo_size = sizeof(devinfo); 240 if (GetRawInputDeviceInfoW(rid.hDevice, RIDI_DEVICEINFO, &devinfo, &devinfo_size) <= 0 || 241 devinfo.mouse.dwNumberOfButtons == 0) 242 { 243 continue; 244 } 245 246 m_mice.push_back({.device = rid.hDevice, .button_state = 0, .last_x = 0, .last_y = 0}); 247 } 248 } 249 250 DEV_LOG("Found {} mice", m_mice.size()); 251 252 // Grab all mouse input. 253 if (!m_mice.empty()) 254 { 255 const RAWINPUTDEVICE rrid = {HID_USAGE_PAGE_GENERIC, HID_USAGE_GENERIC_MOUSE, 0, m_dummy_window}; 256 if (!RegisterRawInputDevices(&rrid, 1, sizeof(rrid))) 257 return false; 258 259 for (u32 i = 0; i < static_cast<u32>(m_mice.size()); i++) 260 InputManager::OnInputDeviceConnected(InputManager::GetPointerDeviceName(i), GetMouseDeviceName(i)); 261 } 262 263 return true; 264 } 265 266 void Win32RawInputSource::CloseDevices() 267 { 268 if (!m_mice.empty()) 269 { 270 const RAWINPUTDEVICE rrid = {HID_USAGE_PAGE_GENERIC, HID_USAGE_GENERIC_KEYBOARD, RIDEV_REMOVE, m_dummy_window}; 271 RegisterRawInputDevices(&rrid, 1, sizeof(rrid)); 272 273 for (u32 i = 0; i < static_cast<u32>(m_mice.size()); i++) 274 { 275 InputManager::OnInputDeviceDisconnected(InputManager::MakePointerAxisKey(i, InputPointerAxis::X), 276 InputManager::GetPointerDeviceName(i)); 277 } 278 m_mice.clear(); 279 } 280 } 281 282 bool Win32RawInputSource::ProcessRawInputEvent(const RAWINPUT* event) 283 { 284 if (event->header.dwType == RIM_TYPEMOUSE) 285 { 286 for (u32 pointer_index = 0; pointer_index < static_cast<u32>(m_mice.size()); pointer_index++) 287 { 288 MouseState& state = m_mice[pointer_index]; 289 if (state.device != event->header.hDevice) 290 continue; 291 292 const RAWMOUSE& rm = event->data.mouse; 293 294 s32 dx = rm.lLastX; 295 s32 dy = rm.lLastY; 296 297 // handle absolute positioned devices 298 if ((rm.usFlags & MOUSE_MOVE_ABSOLUTE) == MOUSE_MOVE_ABSOLUTE) 299 { 300 dx -= std::exchange(dx, state.last_x); 301 dy -= std::exchange(dy, state.last_y); 302 } 303 304 unsigned long button_mask = 305 (rm.usButtonFlags & (rm.usButtonFlags ^ std::exchange(state.button_state, rm.usButtonFlags))) & 306 ALL_BUTTON_MASKS; 307 308 while (button_mask != 0) 309 { 310 unsigned long bit_index; 311 _BitScanForward(&bit_index, button_mask); 312 313 // these are ordered down..up for each button 314 const u32 button_number = bit_index >> 1; 315 const bool button_pressed = (bit_index & 1u) == 0; 316 InputManager::InvokeEvents(InputManager::MakePointerButtonKey(pointer_index, button_number), 317 static_cast<float>(button_pressed), GenericInputBinding::Unknown); 318 319 button_mask &= ~(1u << bit_index); 320 } 321 322 if (dx != 0) 323 InputManager::UpdatePointerRelativeDelta(pointer_index, InputPointerAxis::X, static_cast<float>(dx), true); 324 if (dy != 0) 325 InputManager::UpdatePointerRelativeDelta(pointer_index, InputPointerAxis::Y, static_cast<float>(dy), true); 326 327 return true; 328 } 329 } 330 331 return false; 332 } 333 334 std::unique_ptr<InputSource> InputSource::CreateWin32RawInputSource() 335 { 336 return std::make_unique<Win32RawInputSource>(); 337 }