1 // Copyright (c) 2014- PPSSPP Project. 2 3 // This program is free software: you can redistribute it and/or modify 4 // it under the terms of the GNU General Public License as published by 5 // the Free Software Foundation, version 2.0 or later versions. 6 7 // This program is distributed in the hope that it will be useful, 8 // but WITHOUT ANY WARRANTY; without even the implied warranty of 9 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 // GNU General Public License 2.0 for more details. 11 12 // A copy of the GPL 2.0 should have been included with the program. 13 // If not, see http://www.gnu.org/licenses/ 14 15 // Official git repository and contact information can be found at 16 // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. 17 18 #include <set> 19 #include <algorithm> 20 #include <vector> 21 22 #include "Common/System/NativeApp.h" 23 #include "Common/System/Display.h" 24 #include "Common/Input/InputState.h" 25 #include "Common/Log.h" 26 #include "Windows/RawInput.h" 27 #include "Windows/MainWindow.h" 28 #include "Windows/WindowsHost.h" 29 #include "Common/CommonFuncs.h" 30 #include "Common/SysError.h" 31 #include "Core/Config.h" 32 33 #ifndef HID_USAGE_PAGE_GENERIC 34 #define HID_USAGE_PAGE_GENERIC ((USHORT) 0x01) 35 #endif 36 #ifndef HID_USAGE_GENERIC_POINTER 37 #define HID_USAGE_GENERIC_POINTER ((USHORT) 0x01) 38 #endif 39 #ifndef HID_USAGE_GENERIC_MOUSE 40 #define HID_USAGE_GENERIC_MOUSE ((USHORT) 0x02) 41 #endif 42 #ifndef HID_USAGE_GENERIC_JOYSTICK 43 #define HID_USAGE_GENERIC_JOYSTICK ((USHORT) 0x04) 44 #endif 45 #ifndef HID_USAGE_GENERIC_GAMEPAD 46 #define HID_USAGE_GENERIC_GAMEPAD ((USHORT) 0x05) 47 #endif 48 #ifndef HID_USAGE_GENERIC_KEYBOARD 49 #define HID_USAGE_GENERIC_KEYBOARD ((USHORT) 0x06) 50 #endif 51 #ifndef HID_USAGE_GENERIC_KEYPAD 52 #define HID_USAGE_GENERIC_KEYPAD ((USHORT) 0x07) 53 #endif 54 #ifndef HID_USAGE_GENERIC_MULTIAXIS 55 #define HID_USAGE_GENERIC_MULTIAXIS ((USHORT) 0x07) 56 #endif 57 58 namespace WindowsRawInput { 59 static std::set<int> keyboardKeysDown; 60 static void *rawInputBuffer; 61 static size_t rawInputBufferSize; 62 static bool menuActive; 63 static bool focused = true; 64 static bool mouseDown[5] = { false, false, false, false, false }; //left, right, middle, 4, 5 65 static float mouseX = 0.0f; 66 static float mouseY = 0.0f; 67 68 // TODO: More keys need to be added, but this is more than 69 // a fair start. 70 static std::map<int, int> windowsTransTable = { 71 { 'A', NKCODE_A }, 72 { 'B', NKCODE_B }, 73 { 'C', NKCODE_C }, 74 { 'D', NKCODE_D }, 75 { 'E', NKCODE_E }, 76 { 'F', NKCODE_F }, 77 { 'G', NKCODE_G }, 78 { 'H', NKCODE_H }, 79 { 'I', NKCODE_I }, 80 { 'J', NKCODE_J }, 81 { 'K', NKCODE_K }, 82 { 'L', NKCODE_L }, 83 { 'M', NKCODE_M }, 84 { 'N', NKCODE_N }, 85 { 'O', NKCODE_O }, 86 { 'P', NKCODE_P }, 87 { 'Q', NKCODE_Q }, 88 { 'R', NKCODE_R }, 89 { 'S', NKCODE_S }, 90 { 'T', NKCODE_T }, 91 { 'U', NKCODE_U }, 92 { 'V', NKCODE_V }, 93 { 'W', NKCODE_W }, 94 { 'X', NKCODE_X }, 95 { 'Y', NKCODE_Y }, 96 { 'Z', NKCODE_Z }, 97 { '0', NKCODE_0 }, 98 { '1', NKCODE_1 }, 99 { '2', NKCODE_2 }, 100 { '3', NKCODE_3 }, 101 { '4', NKCODE_4 }, 102 { '5', NKCODE_5 }, 103 { '6', NKCODE_6 }, 104 { '7', NKCODE_7 }, 105 { '8', NKCODE_8 }, 106 { '9', NKCODE_9 }, 107 { VK_OEM_PERIOD, NKCODE_PERIOD }, 108 { VK_OEM_COMMA, NKCODE_COMMA }, 109 { VK_NUMPAD0, NKCODE_NUMPAD_0 }, 110 { VK_NUMPAD1, NKCODE_NUMPAD_1 }, 111 { VK_NUMPAD2, NKCODE_NUMPAD_2 }, 112 { VK_NUMPAD3, NKCODE_NUMPAD_3 }, 113 { VK_NUMPAD4, NKCODE_NUMPAD_4 }, 114 { VK_NUMPAD5, NKCODE_NUMPAD_5 }, 115 { VK_NUMPAD6, NKCODE_NUMPAD_6 }, 116 { VK_NUMPAD7, NKCODE_NUMPAD_7 }, 117 { VK_NUMPAD8, NKCODE_NUMPAD_8 }, 118 { VK_NUMPAD9, NKCODE_NUMPAD_9 }, 119 { VK_DECIMAL, NKCODE_NUMPAD_DOT }, 120 { VK_DIVIDE, NKCODE_NUMPAD_DIVIDE }, 121 { VK_MULTIPLY, NKCODE_NUMPAD_MULTIPLY }, 122 { VK_SUBTRACT, NKCODE_NUMPAD_SUBTRACT }, 123 { VK_ADD, NKCODE_NUMPAD_ADD }, 124 { VK_SEPARATOR, NKCODE_NUMPAD_COMMA }, 125 { VK_OEM_MINUS, NKCODE_MINUS }, 126 { VK_OEM_PLUS, NKCODE_PLUS }, 127 { VK_LCONTROL, NKCODE_CTRL_LEFT }, 128 { VK_RCONTROL, NKCODE_CTRL_RIGHT }, 129 { VK_LSHIFT, NKCODE_SHIFT_LEFT }, 130 { VK_RSHIFT, NKCODE_SHIFT_RIGHT }, 131 { VK_LMENU, NKCODE_ALT_LEFT }, 132 { VK_RMENU, NKCODE_ALT_RIGHT }, 133 { VK_BACK, NKCODE_DEL }, // yes! http://stackoverflow.com/questions/4886858/android-edittext-deletebackspace-key-event 134 { VK_SPACE, NKCODE_SPACE }, 135 { VK_ESCAPE, NKCODE_ESCAPE }, 136 { VK_UP, NKCODE_DPAD_UP }, 137 { VK_INSERT, NKCODE_INSERT }, 138 { VK_HOME, NKCODE_MOVE_HOME }, 139 { VK_PRIOR, NKCODE_PAGE_UP }, 140 { VK_NEXT, NKCODE_PAGE_DOWN }, 141 { VK_DELETE, NKCODE_FORWARD_DEL }, 142 { VK_END, NKCODE_MOVE_END }, 143 { VK_TAB, NKCODE_TAB }, 144 { VK_DOWN, NKCODE_DPAD_DOWN }, 145 { VK_LEFT, NKCODE_DPAD_LEFT }, 146 { VK_RIGHT, NKCODE_DPAD_RIGHT }, 147 { VK_CAPITAL, NKCODE_CAPS_LOCK }, 148 { VK_CLEAR, NKCODE_CLEAR }, 149 { VK_SNAPSHOT, NKCODE_SYSRQ }, 150 { VK_SCROLL, NKCODE_SCROLL_LOCK }, 151 { VK_OEM_1, NKCODE_SEMICOLON }, 152 { VK_OEM_2, NKCODE_SLASH }, 153 { VK_OEM_3, NKCODE_GRAVE }, 154 { VK_OEM_4, NKCODE_LEFT_BRACKET }, 155 { VK_OEM_5, NKCODE_BACKSLASH }, 156 { VK_OEM_6, NKCODE_RIGHT_BRACKET }, 157 { VK_OEM_7, NKCODE_APOSTROPHE }, 158 { VK_RETURN, NKCODE_ENTER }, 159 { VK_APPS, NKCODE_MENU }, // Context menu key, let's call this "menu". 160 { VK_PAUSE, NKCODE_BREAK }, 161 { VK_F1, NKCODE_F1 }, 162 { VK_F2, NKCODE_F2 }, 163 { VK_F3, NKCODE_F3 }, 164 { VK_F4, NKCODE_F4 }, 165 { VK_F5, NKCODE_F5 }, 166 { VK_F6, NKCODE_F6 }, 167 { VK_F7, NKCODE_F7 }, 168 { VK_F8, NKCODE_F8 }, 169 { VK_F9, NKCODE_F9 }, 170 { VK_F10, NKCODE_F10 }, 171 { VK_F11, NKCODE_F11 }, 172 { VK_F12, NKCODE_F12 }, 173 { VK_OEM_102, NKCODE_EXT_PIPE }, 174 { VK_LBUTTON, NKCODE_EXT_MOUSEBUTTON_1 }, 175 { VK_RBUTTON, NKCODE_EXT_MOUSEBUTTON_2 }, 176 { VK_MBUTTON, NKCODE_EXT_MOUSEBUTTON_3 }, 177 { VK_XBUTTON1, NKCODE_EXT_MOUSEBUTTON_4 }, 178 { VK_XBUTTON2, NKCODE_EXT_MOUSEBUTTON_5 }, 179 }; 180 Init()181 void Init() { 182 RAWINPUTDEVICE dev[3]; 183 memset(dev, 0, sizeof(dev)); 184 185 dev[0].usUsagePage = HID_USAGE_PAGE_GENERIC; 186 dev[0].usUsage = HID_USAGE_GENERIC_KEYBOARD; 187 dev[0].dwFlags = g_Config.bIgnoreWindowsKey ? RIDEV_NOHOTKEYS : 0; 188 189 dev[1].usUsagePage = HID_USAGE_PAGE_GENERIC; 190 dev[1].usUsage = HID_USAGE_GENERIC_MOUSE; 191 dev[1].dwFlags = 0; 192 193 dev[2].usUsagePage = HID_USAGE_PAGE_GENERIC; 194 dev[2].usUsage = HID_USAGE_GENERIC_JOYSTICK; 195 dev[2].dwFlags = 0; 196 197 if (!RegisterRawInputDevices(dev, 3, sizeof(RAWINPUTDEVICE))) { 198 WARN_LOG(SYSTEM, "Unable to register raw input devices: %s", GetLastErrorMsg().c_str()); 199 } 200 } 201 UpdateMenuActive()202 bool UpdateMenuActive() { 203 MENUBARINFO info; 204 memset(&info, 0, sizeof(info)); 205 info.cbSize = sizeof(info); 206 if (GetMenuBarInfo(MainWindow::GetHWND(), OBJID_MENU, 0, &info) != 0) { 207 menuActive = info.fBarFocused != FALSE; 208 } else { 209 // In fullscreen mode, we remove the menu 210 menuActive = false; 211 } 212 return menuActive; 213 } 214 GetTrueVKey(const RAWKEYBOARD & kb)215 static int GetTrueVKey(const RAWKEYBOARD &kb) { 216 int vKey = kb.VKey; 217 switch (kb.VKey) { 218 case VK_SHIFT: 219 vKey = MapVirtualKey(kb.MakeCode, MAPVK_VSC_TO_VK_EX); 220 break; 221 222 case VK_CONTROL: 223 if (kb.Flags & RI_KEY_E0) 224 vKey = VK_RCONTROL; 225 else 226 vKey = VK_LCONTROL; 227 break; 228 229 case VK_MENU: 230 if (kb.Flags & RI_KEY_E0) 231 vKey = VK_RMENU; // Right Alt / AltGr 232 else 233 vKey = VK_LMENU; // Left Alt 234 break; 235 236 //case VK_RETURN: 237 // if (kb.Flags & RI_KEY_E0) 238 // vKey = VK_RETURN; // Numeric return - no code for this. Can special case. 239 // break; 240 241 // Source: http://molecularmusings.wordpress.com/2011/09/05/properly-handling-keyboard-input/ 242 case VK_NUMLOCK: 243 // correct PAUSE/BREAK and NUM LOCK silliness, and set the extended bit 244 vKey = MapVirtualKey(kb.VKey, MAPVK_VK_TO_VSC) | 0x100; 245 break; 246 247 default: 248 break; 249 } 250 251 return windowsTransTable[vKey]; 252 } 253 ProcessKeyboard(RAWINPUT * raw,bool foreground)254 void ProcessKeyboard(RAWINPUT *raw, bool foreground) { 255 if (menuActive && UpdateMenuActive()) { 256 // Ignore keyboard input while a menu is active, it's probably interacting with the menu. 257 return; 258 } 259 260 KeyInput key; 261 key.deviceId = DEVICE_ID_KEYBOARD; 262 263 if (raw->data.keyboard.Message == WM_KEYDOWN || raw->data.keyboard.Message == WM_SYSKEYDOWN) { 264 key.flags = KEY_DOWN; 265 key.keyCode = GetTrueVKey(raw->data.keyboard); 266 267 if (key.keyCode) { 268 NativeKey(key); 269 keyboardKeysDown.insert(key.keyCode); 270 } 271 } else if (raw->data.keyboard.Message == WM_KEYUP) { 272 key.flags = KEY_UP; 273 key.keyCode = GetTrueVKey(raw->data.keyboard); 274 275 if (key.keyCode) { 276 NativeKey(key); 277 278 auto keyDown = std::find(keyboardKeysDown.begin(), keyboardKeysDown.end(), key.keyCode); 279 if (keyDown != keyboardKeysDown.end()) 280 keyboardKeysDown.erase(keyDown); 281 } 282 } 283 } 284 ProcessChar(HWND hWnd,WPARAM wParam,LPARAM lParam)285 LRESULT ProcessChar(HWND hWnd, WPARAM wParam, LPARAM lParam) { 286 KeyInput key; 287 key.keyCode = (int)wParam; // Note that this is NOT a NKCODE but a Unicode character! 288 key.flags = KEY_CHAR; 289 key.deviceId = DEVICE_ID_KEYBOARD; 290 NativeKey(key); 291 return 0; 292 } 293 MouseInWindow(HWND hWnd)294 static bool MouseInWindow(HWND hWnd) { 295 POINT pt; 296 if (GetCursorPos(&pt) != 0) { 297 RECT rt; 298 if (GetWindowRect(hWnd, &rt) != 0) { 299 return PtInRect(&rt, pt) != 0; 300 } 301 } 302 return true; 303 } 304 ProcessMouse(HWND hWnd,RAWINPUT * raw,bool foreground)305 void ProcessMouse(HWND hWnd, RAWINPUT *raw, bool foreground) { 306 if (menuActive && UpdateMenuActive()) { 307 // Ignore mouse input while a menu is active, it's probably interacting with the menu. 308 return; 309 } 310 311 TouchInput touch; 312 touch.id = 0; 313 touch.flags = TOUCH_MOVE; 314 touch.x = mouseX; 315 touch.y = mouseY; 316 317 KeyInput key; 318 key.deviceId = DEVICE_ID_MOUSE; 319 320 g_mouseDeltaX += raw->data.mouse.lLastX; 321 g_mouseDeltaY += raw->data.mouse.lLastY; 322 323 const int rawInputDownID[5] = { 324 RI_MOUSE_LEFT_BUTTON_DOWN, 325 RI_MOUSE_RIGHT_BUTTON_DOWN, 326 RI_MOUSE_BUTTON_3_DOWN, 327 RI_MOUSE_BUTTON_4_DOWN, 328 RI_MOUSE_BUTTON_5_DOWN 329 }; 330 const int rawInputUpID[5] = { 331 RI_MOUSE_LEFT_BUTTON_UP, 332 RI_MOUSE_RIGHT_BUTTON_UP, 333 RI_MOUSE_BUTTON_3_UP, 334 RI_MOUSE_BUTTON_4_UP, 335 RI_MOUSE_BUTTON_5_UP 336 }; 337 const int vkInputID[5] = { 338 VK_LBUTTON, 339 VK_RBUTTON, 340 VK_MBUTTON, 341 VK_XBUTTON1, 342 VK_XBUTTON2 343 }; 344 345 for (int i = 0; i < 5; i++) { 346 if (i > 0 || (g_Config.bMouseControl && (GetUIState() == UISTATE_INGAME || g_Config.bMapMouse))) { 347 if (raw->data.mouse.usButtonFlags & rawInputDownID[i]) { 348 key.flags = KEY_DOWN; 349 key.keyCode = windowsTransTable[vkInputID[i]]; 350 NativeTouch(touch); 351 if (MouseInWindow(hWnd)) { 352 NativeKey(key); 353 } 354 mouseDown[i] = true; 355 } else if (raw->data.mouse.usButtonFlags & rawInputUpID[i]) { 356 key.flags = KEY_UP; 357 key.keyCode = windowsTransTable[vkInputID[i]]; 358 NativeTouch(touch); 359 if (MouseInWindow(hWnd)) { 360 if (!mouseDown[i]) { 361 // This means they were focused outside, and clicked inside. 362 // Seems intentional, so send a down first. 363 key.flags = KEY_DOWN; 364 NativeKey(key); 365 key.flags = KEY_UP; 366 NativeKey(key); 367 } else { 368 NativeKey(key); 369 } 370 } 371 mouseDown[i] = false; 372 } 373 } 374 } 375 } 376 ProcessHID(RAWINPUT * raw,bool foreground)377 void ProcessHID(RAWINPUT *raw, bool foreground) { 378 // TODO: Use hidparse or something to understand the data. 379 } 380 Process(HWND hWnd,WPARAM wParam,LPARAM lParam)381 LRESULT Process(HWND hWnd, WPARAM wParam, LPARAM lParam) { 382 UINT dwSize; 383 GetRawInputData((HRAWINPUT)lParam, RID_INPUT, NULL, &dwSize, sizeof(RAWINPUTHEADER)); 384 if (!rawInputBuffer) { 385 rawInputBuffer = malloc(dwSize); 386 memset(rawInputBuffer, 0, dwSize); 387 rawInputBufferSize = dwSize; 388 } 389 if (dwSize > rawInputBufferSize) { 390 rawInputBuffer = realloc(rawInputBuffer, dwSize); 391 memset(rawInputBuffer, 0, dwSize); 392 } 393 GetRawInputData((HRAWINPUT)lParam, RID_INPUT, rawInputBuffer, &dwSize, sizeof(RAWINPUTHEADER)); 394 RAWINPUT *raw = (RAWINPUT *)rawInputBuffer; 395 bool foreground = GET_RAWINPUT_CODE_WPARAM(wParam) == RIM_INPUT; 396 397 switch (raw->header.dwType) { 398 case RIM_TYPEKEYBOARD: 399 ProcessKeyboard(raw, foreground); 400 break; 401 402 case RIM_TYPEMOUSE: 403 ProcessMouse(hWnd, raw, foreground); 404 break; 405 406 case RIM_TYPEHID: 407 ProcessHID(raw, foreground); 408 break; 409 } 410 411 // Docs say to call DefWindowProc to perform necessary cleanup. 412 return DefWindowProc(hWnd, WM_INPUT, wParam, lParam); 413 } 414 SetMousePos(float x,float y)415 void SetMousePos(float x, float y) { 416 mouseX = x; 417 mouseY = y; 418 } 419 GainFocus()420 void GainFocus() { 421 focused = true; 422 } 423 LoseFocus()424 void LoseFocus() { 425 // Force-release all held keys on the keyboard to prevent annoying stray inputs. 426 KeyInput key; 427 key.deviceId = DEVICE_ID_KEYBOARD; 428 key.flags = KEY_UP; 429 for (auto i = keyboardKeysDown.begin(); i != keyboardKeysDown.end(); ++i) { 430 key.keyCode = *i; 431 NativeKey(key); 432 } 433 focused = false; 434 } 435 NotifyMenu()436 void NotifyMenu() { 437 UpdateMenuActive(); 438 } 439 Shutdown()440 void Shutdown() { 441 if (rawInputBuffer) { 442 free(rawInputBuffer); 443 } 444 rawInputBuffer = 0; 445 } 446 }; 447