flycast/core/windows/rawinput.cpp

486 lines
13 KiB
C++

/*
Copyright 2021 flyinghead
This file is part of Flycast.
Flycast is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
Flycast is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Flycast. If not, see <https://www.gnu.org/licenses/>.
*/
#include "rawinput.h"
#ifndef TARGET_UWP
#include <hidusage.h>
#include <cfgmgr32.h>
#include <initguid.h>
#include <devpkey.h>
#include "hw/maple/maple_devs.h"
#include <algorithm>
#include <cstring>
#include <map>
#include <memory>
#ifndef CALLBACK
#define CALLBACK
#endif
HWND getNativeHwnd();
namespace rawinput {
static std::map<HANDLE, std::shared_ptr<RawMouse>> mice;
static std::map<HANDLE, std::shared_ptr<RawKeyboard>> keyboards;
static HWND hWnd;
const u8 Ps2toUsb[0x80] {
// 00
0xff,
0x29, // Esc
0x1e, // 1
0x1f, // 2
0x20, // 3
0x21, // 4
0x22, // 5
0x23, // 6
0x24, // 7
0x25, // 8
0x26, // 9
0x27, // 0
0x2d, // - _
0x2e, // = +
0x2a, // Backspace
0x2b, // Tab
// 10
0x14, // Q
0x1a, // W
0x08, // E
0x15, // R
0x17, // T
0x1c, // Y
0x18, // U
0x0c, // I
0x12, // O
0x13, // P
0x2f, // [ {
0x30, // ] }
0x28, // Return
0xe0, // Left Control
0x04, // A
0x16, // S
// 20
0x07, // D
0x09, // F
0x0a, // G
0x0b, // H
0x0d, // J
0x0e, // K
0x0f, // L
0x33, // ; :
0x34, // ' "
0x35, // ` ~
0xe1, // Left Shift
0x31, // \ | (US)
0x1d, // Z
0x1b, // X
0x06, // C
0x19, // V
// 30
0x05, // B
0x11, // N
0x10, // M
0x36, // , <
0x37, // . >
0x38, // / ?
0xe5, // Right Shift
0x55, // Keypad *
0xe2, // Left Alt
0x2c, // Space
0x39, // Caps Lock
0x3a, // F1
0x3b, // F2
0x3c, // F3
0x3d, // F4
0x3e, // F5
// 40
0x3f, // F6
0x40, // F7
0x41, // F8
0x42, // F9
0x43, // F10
0x53, // Num Lock
0x47, // Scroll Lock
0x5f, // Keypad 7
0x60, // Keypad 8
0x61, // Keypad 9
0x56, // Keypad -
0x5c, // Keypad 4
0x5d, // Keypad 5
0x5e, // Keypad 6
0x57, // Keypad +
0x59, // Keypad 1
// 50
0x5a, // Keypad 2
0x5b, // Keypad 3
0x62, // Keypad 0
0x63, // Keypad .
0xff,
0xff,
0x64, // (Europe2)
0x44, // F11
0x45, // F12
0x67, // Keypad =
0xff,
0xff,
0x8c, // Int'l 6
0xff,
0xff,
0xff,
// 60
0xff,
0xff,
0xff,
0xff,
0x68, // F13
0x69, // F14
0x6a, // F15
0x6b, // F16
0x6c, // F17
0x6d, // F18
0x6e, // F19
0x6f, // F20
0x70, // F21
0x71, // F22
0x72, // F23
0xff,
// 70
0x88, // Int'l 2 (Katakana/Hiragana)
0xff,
0xff,
0x87, // Int'l 1 (Ro)
0xff,
0xff,
0x73, // F24
0x93, // Lang 4 Hiragana
0x92, // Lang 3 Katakana
0x8a, // Int'l 4 (Henkan)
0xff,
0x8b, // Int'l 5 (Muhenkan)
0xff,
0x89, // Int'l 3 (Yen)
0x85, // Keypad ,
0xff,
};
const u8 Ps2toUsbE0[][2] {
{ 0x1c, 0x58 }, // Keypad Enter
{ 0x1d, 0xe4 }, // Right Control
{ 0x35, 0x54 }, // Keypad /
{ 0x37, 0x46 }, // Print Screen
{ 0x38, 0xe6 }, // Right Alt
{ 0x46, 0x48 }, // Break
{ 0x47, 0x4a }, // Home
{ 0x48, 0x52 }, // Up
{ 0x49, 0x4b }, // Page Up
{ 0x4b, 0x50 }, // Left
{ 0x4d, 0x4f }, // Right
{ 0x4f, 0x4d }, // End
{ 0x50, 0x51 }, // Down
{ 0x51, 0x4e }, // Page Down
{ 0x52, 0x49 }, // Insert
{ 0x53, 0x4c }, // Delete
{ 0x5b, 0xe3 }, // Left GUI
{ 0x5c, 0xe7 }, // Right GUI
{ 0x5d, 0x65 }, // App
};
RawMouse::RawMouse(int maple_port, const std::string& name, const std::string& uniqueId, HANDLE handle) :
Mouse("RAW", maple_port), handle(handle)
{
this->_name = name;
this->_unique_id = uniqueId;
std::replace(this->_unique_id.begin(), this->_unique_id.end(), '=', '_');
std::replace(this->_unique_id.begin(), this->_unique_id.end(), '[', '_');
std::replace(this->_unique_id.begin(), this->_unique_id.end(), ']', '_');
loadMapping();
setAbsPos(settings.display.width / 2, settings.display.height / 2, settings.display.width, settings.display.height);
}
void RawMouse::buttonInput(Button button, u16 flags, u16 downFlag, u16 upFlag)
{
if (flags & (downFlag | upFlag))
setButton(button, flags & downFlag);
}
void RawMouse::updateState(RAWMOUSE* state)
{
if (state->usFlags & MOUSE_MOVE_ABSOLUTE)
{
bool isVirtualDesktop = (state->usFlags & MOUSE_VIRTUAL_DESKTOP) == MOUSE_VIRTUAL_DESKTOP;
int width = GetSystemMetrics(isVirtualDesktop ? SM_CXVIRTUALSCREEN : SM_CXSCREEN);
int height = GetSystemMetrics(isVirtualDesktop ? SM_CYVIRTUALSCREEN : SM_CYSCREEN);
POINT pt { long(state->lLastX / 65535.0f * width), long(state->lLastY / 65535.0f * height) };
HWND hwnd = getNativeHwnd();
if (hwnd != NULL)
{
ScreenToClient(hwnd, &pt);
setAbsPos(pt.x, pt.y, settings.display.width, settings.display.height);
}
}
else if (state->lLastX != 0 || state->lLastY != 0)
setRelPos(state->lLastX, state->lLastY);
buttonInput(LEFT_BUTTON, state->usButtonFlags, RI_MOUSE_LEFT_BUTTON_DOWN, RI_MOUSE_LEFT_BUTTON_UP);
buttonInput(MIDDLE_BUTTON, state->usButtonFlags, RI_MOUSE_MIDDLE_BUTTON_DOWN, RI_MOUSE_MIDDLE_BUTTON_UP);
buttonInput(RIGHT_BUTTON, state->usButtonFlags, RI_MOUSE_RIGHT_BUTTON_DOWN, RI_MOUSE_RIGHT_BUTTON_UP);
buttonInput(BUTTON_4, state->usButtonFlags, RI_MOUSE_BUTTON_4_DOWN, RI_MOUSE_BUTTON_4_UP);
buttonInput(BUTTON_5, state->usButtonFlags, RI_MOUSE_BUTTON_5_DOWN, RI_MOUSE_BUTTON_5_UP);
if ((state->usButtonFlags & RI_MOUSE_WHEEL))
setWheel(-(short)state->usButtonData / WHEEL_DELTA);
}
static LRESULT CALLBACK rawWindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
if (msg != WM_INPUT)
return DefWindowProcA(hWnd, msg, wParam, lParam);
RAWINPUT ri;
UINT size = sizeof(ri);
if (GET_RAWINPUT_CODE_WPARAM(wParam) != RIM_INPUT // app isn't in the foreground
|| GetRawInputData((HRAWINPUT)lParam, RID_INPUT, &ri, &size, sizeof(RAWINPUTHEADER)) == (UINT)-1)
{
DefWindowProcA(hWnd, msg, wParam, lParam);
return 0;
}
switch (ri.header.dwType)
{
case RIM_TYPEKEYBOARD:
{
if ((ri.data.keyboard.Flags & RI_KEY_E1) != 0)
break;
auto it = keyboards.find(ri.header.hDevice);
if (it == keyboards.end())
break;
u16 scancode = ri.data.keyboard.MakeCode;
bool pressed = (ri.data.keyboard.Flags & RI_KEY_BREAK) == 0;
u8 keycode = 0xff;
if ((ri.data.keyboard.Flags & RI_KEY_E0) != 0)
{
for (u32 i = 0; i < ARRAY_SIZE(Ps2toUsbE0); i++)
if (Ps2toUsbE0[i][0] == scancode)
{
keycode = Ps2toUsbE0[i][1];
DEBUG_LOG(INPUT, "[%d] E0 key %x -> %x", it->second->maple_port(), scancode, keycode);
break;
}
}
else
{
keycode = Ps2toUsb[scancode];
DEBUG_LOG(INPUT, "[%d] key %x -> %x", it->second->maple_port(), scancode, keycode);
}
if (keycode != 0xff)
it->second->keyboard_input(keycode, pressed);
}
break;
case RIM_TYPEMOUSE:
{
auto it = mice.find(ri.header.hDevice);
if (it != mice.end())
it->second->updateState(&ri.data.mouse);
}
break;
}
DefWindowProcA(hWnd, msg, wParam, lParam);
return 0;
}
static void createWindow()
{
WNDCLASSA wndClass {};
wndClass.hInstance = GetModuleHandleA(nullptr);
if (!wndClass.hInstance)
return;
wndClass.lpfnWndProc = rawWindowProc;
wndClass.lpszClassName = "flycastRawInput";
if (RegisterClassA(&wndClass) == 0 && GetLastError() != ERROR_CLASS_ALREADY_EXISTS)
return;
hWnd = CreateWindowExA(0, wndClass.lpszClassName, nullptr, 0, 0, 0, 0, 0, HWND_MESSAGE, nullptr, nullptr, nullptr);
if (hWnd == nullptr)
UnregisterClassA(wndClass.lpszClassName, nullptr);
}
static void destroyWindow()
{
if (hWnd == nullptr)
return;
DestroyWindow(hWnd);
hWnd = nullptr;
UnregisterClassA("flycastRawInput", nullptr);
}
#ifndef _MSC_VER
// missing from mingw's cfgmgr32.h
extern "C" CMAPI CONFIGRET CM_Get_Device_Interface_PropertyW(
LPCWSTR pszDeviceInterface,
const DEVPROPKEY *PropertyKey,
DEVPROPTYPE *PropertyType,
PBYTE PropertyBuffer,
PULONG PropertyBufferSize,
ULONG ulFlags);
#endif
static void findDevices()
{
u32 numDevices;
GetRawInputDeviceList(NULL, &numDevices, sizeof(RAWINPUTDEVICELIST));
if (numDevices == 0)
return;
RAWINPUTDEVICELIST *deviceList = new RAWINPUTDEVICELIST[numDevices];
GetRawInputDeviceList(deviceList, &numDevices, sizeof(RAWINPUTDEVICELIST));
for (u32 i = 0; i < numDevices; ++i)
{
RAWINPUTDEVICELIST& device = deviceList[i];
if (device.dwType == RIM_TYPEMOUSE || device.dwType == RIM_TYPEKEYBOARD)
{
// Get the device interface instance id
std::string name;
std::string uniqueId;
u32 size = 0;
GetRawInputDeviceInfo(device.hDevice, RIDI_DEVICENAME, nullptr, &size);
if (size > 0)
{
std::vector<char> deviceNameData(size);
u32 res = GetRawInputDeviceInfo(device.hDevice, RIDI_DEVICENAME, &deviceNameData[0], &size);
if (res != (u32)-1)
{
std::string deviceId(&deviceNameData[0], std::strlen(&deviceNameData[0]));
// Now get the device instance id from the interface instance
nowide::wstackstring wname;
if (wname.convert(deviceId.c_str()))
{
DEVPROPTYPE propType;
ULONG bufSize = 0;
if (CM_Get_Device_Interface_PropertyW(wname.c_str(), &DEVPKEY_Device_InstanceId, &propType, nullptr, &bufSize, 0) == CR_BUFFER_SMALL)
{
std::vector<wchar_t> buf;
buf.resize(bufSize / sizeof(wchar_t));
if (CM_Get_Device_Interface_PropertyW(wname.c_str(), &DEVPKEY_Device_InstanceId, &propType, (PBYTE)buf.data(), &bufSize, 0) == CR_SUCCESS)
{
// Locate the device using the device instance id
DEVINST devInst;
if (CM_Locate_DevNodeW(&devInst, &buf[0], CM_LOCATE_DEVNODE_NORMAL) == CR_SUCCESS)
{
// Finally, get the "friendly" device name
bufSize = 0;
if (CM_Get_DevNode_PropertyW(devInst, &DEVPKEY_NAME, &propType, nullptr, &bufSize, 0) == CR_BUFFER_SMALL)
{
buf.resize(bufSize / sizeof(wchar_t));
if (CM_Get_DevNode_PropertyW(devInst, &DEVPKEY_NAME, &propType, (PBYTE)buf.data(), &bufSize, 0) == CR_SUCCESS)
{
nowide::stackstring nwname;
if (nwname.convert(&buf[0]))
name = nwname.c_str();
}
}
}
}
}
}
if (deviceId.substr(0, 8) == "\\\\?\\HID#")
deviceId = deviceId.substr(8);
uniqueId = (device.dwType == RIM_TYPEMOUSE ? "raw_mouse_" : "raw_keyboard_") + deviceId;
if (name.empty())
{
name = deviceId;
if (name.length() > 17 && name.substr(0, 4) == "VID_" && name.substr(8, 5) == "&PID_")
name = name.substr(0, 17);
name = (device.dwType == RIM_TYPEMOUSE ? "Mouse " : "Keyboard ") + name;
}
}
}
uintptr_t handle = (uintptr_t)device.hDevice;
if (name.empty())
name = (device.dwType == RIM_TYPEMOUSE ? "Mouse " : "Keyboard ") + std::to_string(handle);
if (uniqueId.empty())
uniqueId = (device.dwType == RIM_TYPEMOUSE ? "raw_mouse_" : "raw_keyboard_") + std::to_string(handle);
NOTICE_LOG(INPUT, "Found RawInput %s name \"%s\" id %s", device.dwType == RIM_TYPEMOUSE ? "mouse" : "keyboard", name.c_str(), uniqueId.c_str());
if (device.dwType == RIM_TYPEMOUSE)
{
auto ptr = std::make_shared<RawMouse>(mice.size() >= 4 ? 3 : mice.size(), name, uniqueId, device.hDevice);
mice[device.hDevice] = ptr;
GamepadDevice::Register(ptr);
}
else
{
auto ptr = std::make_shared<RawKeyboard>(keyboards.size() >= 4 ? 3 : keyboards.size(), name, uniqueId, device.hDevice);
keyboards[device.hDevice] = ptr;
GamepadDevice::Register(ptr);
}
}
}
delete [] deviceList;
}
void init()
{
createWindow();
verify(hWnd != NULL);
findDevices();
RAWINPUTDEVICE rid[2];
rid[0].dwFlags = 0;
rid[0].hwndTarget = hWnd;
rid[0].usUsagePage = HID_USAGE_PAGE_GENERIC;
rid[0].usUsage = HID_USAGE_GENERIC_MOUSE;
rid[1].dwFlags = 0;
rid[1].hwndTarget = hWnd;
rid[1].usUsagePage = HID_USAGE_PAGE_GENERIC;
rid[1].usUsage = HID_USAGE_GENERIC_KEYBOARD;
RegisterRawInputDevices(rid, 2, sizeof(RAWINPUTDEVICE));
}
void term()
{
RAWINPUTDEVICE rid[2];
rid[0].dwFlags = RIDEV_REMOVE;
rid[0].hwndTarget = nullptr;
rid[0].usUsagePage = HID_USAGE_PAGE_GENERIC;
rid[0].usUsage = HID_USAGE_GENERIC_MOUSE;
rid[1].dwFlags = RIDEV_REMOVE;
rid[1].hwndTarget = nullptr;
rid[1].usUsagePage = HID_USAGE_PAGE_GENERIC;
rid[1].usUsage = HID_USAGE_GENERIC_KEYBOARD;
RegisterRawInputDevices(rid, 2, sizeof(RAWINPUTDEVICE));
destroyWindow();
for (auto& mouse : mice)
GamepadDevice::Unregister(mouse.second);
mice.clear();
for (auto& keyboard : keyboards)
GamepadDevice::Unregister(keyboard.second);
keyboards.clear();
}
}
#endif