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