mirror of
https://github.com/RetroDECK/Supermodel.git
synced 2024-11-23 14:15:40 +00:00
03fa9532eb
- Added object interface CThread for OSD threading and synchronization, together with SDL implementation - Added multi-threading to CModel3 so that separate CPUs (PPC of main board, 68K of sound board and Z80 of drive board) can be run in separate threads if requested to improve performance on multi-core PCs - Added -multi-threaded command line option (default is to run single-threaded still)
1870 lines
58 KiB
C++
1870 lines
58 KiB
C++
#include "DirectInputSystem.h"
|
|
#include "Supermodel.h"
|
|
|
|
#include <wbemidl.h>
|
|
#include <oleauto.h>
|
|
|
|
#include <SDL.h>
|
|
#include <SDL_syswm.h>
|
|
|
|
// TODO - need to double check these all correct and see if can fill in any missing codes (although most just don't exist)
|
|
DIKeyMapStruct CDirectInputSystem::s_keyMap[] =
|
|
{
|
|
// General keys
|
|
{ "BACKSPACE", DIK_BACK },
|
|
{ "TAB", DIK_TAB },
|
|
//{ "CLEAR", ?? },
|
|
{ "RETURN", DIK_RETURN },
|
|
{ "PAUSE", DIK_PAUSE },
|
|
{ "ESCAPE", DIK_ESCAPE },
|
|
{ "SPACE", DIK_SPACE },
|
|
//{ "EXCLAIM", ?? },
|
|
//{ "DBLQUOTE", ?? },
|
|
//{ "HASH", ?? },
|
|
//{ "DOLLAR", ?? },
|
|
//{ "AMPERSAND", ?? },
|
|
{ "QUOTE", DIK_APOSTROPHE },
|
|
{ "LEFTPAREN", DIK_LBRACKET },
|
|
{ "RIGHTPAREN", DIK_RBRACKET },
|
|
//{ "ASTERISK", ?? },
|
|
//{ "PLUS", ?? },
|
|
{ "COMMA", DIK_COMMA },
|
|
{ "MINUS", DIK_MINUS },
|
|
{ "PERIOD", DIK_PERIOD },
|
|
{ "SLASH", DIK_SLASH },
|
|
{ "0", DIK_0 },
|
|
{ "1", DIK_1 },
|
|
{ "2", DIK_2 },
|
|
{ "3", DIK_3 },
|
|
{ "4", DIK_4 },
|
|
{ "5", DIK_5 },
|
|
{ "6", DIK_6 },
|
|
{ "7", DIK_7 },
|
|
{ "8", DIK_8 },
|
|
{ "9", DIK_9 },
|
|
//{ "COLON", ?? },
|
|
{ "SEMICOLON", DIK_SEMICOLON },
|
|
{ "LESS", DIK_OEM_102 },
|
|
{ "EQUALS", DIK_EQUALS },
|
|
//{ "GREATER", ?? },
|
|
//{ "QUESTION", ?? },
|
|
//{ "AT", ?? },
|
|
//{ "LEFTBRACKET", ?? },
|
|
//{ "BACKSLASH", ?? },
|
|
//{ "RIGHTBRACKET", ?? },
|
|
//{ "CARET", ?? },
|
|
//{ "UNDERSCORE", ?? },
|
|
{ "BACKQUOTE", DIK_GRAVE },
|
|
{ "A", DIK_A },
|
|
{ "B", DIK_B },
|
|
{ "C", DIK_C },
|
|
{ "D", DIK_D },
|
|
{ "E", DIK_E },
|
|
{ "F", DIK_F },
|
|
{ "G", DIK_G },
|
|
{ "H", DIK_H },
|
|
{ "I", DIK_I },
|
|
{ "J", DIK_J },
|
|
{ "K", DIK_K },
|
|
{ "L", DIK_L },
|
|
{ "M", DIK_M },
|
|
{ "N", DIK_N },
|
|
{ "O", DIK_O },
|
|
{ "P", DIK_P },
|
|
{ "Q", DIK_Q },
|
|
{ "R", DIK_R },
|
|
{ "S", DIK_S },
|
|
{ "T", DIK_T },
|
|
{ "U", DIK_U },
|
|
{ "V", DIK_V },
|
|
{ "W", DIK_W },
|
|
{ "X", DIK_X },
|
|
{ "Y", DIK_Y },
|
|
{ "Z", DIK_Z },
|
|
{ "DEL", DIK_DELETE },
|
|
|
|
// Keypad
|
|
{ "KEYPAD0", DIK_NUMPAD0 },
|
|
{ "KEYPAD1", DIK_NUMPAD1 },
|
|
{ "KEYPAD2", DIK_NUMPAD2 },
|
|
{ "KEYPAD3", DIK_NUMPAD3 },
|
|
{ "KEYPAD4", DIK_NUMPAD4 },
|
|
{ "KEYPAD5", DIK_NUMPAD5 },
|
|
{ "KEYPAD6", DIK_NUMPAD6 },
|
|
{ "KEYPAD7", DIK_NUMPAD7 },
|
|
{ "KEYPAD8", DIK_NUMPAD8 },
|
|
{ "KEYPAD9", DIK_NUMPAD9 },
|
|
{ "KEYPADPERIOD", DIK_DECIMAL },
|
|
{ "KEYPADDIVIDE", DIK_DIVIDE },
|
|
{ "KEYPADMULTIPLY", DIK_MULTIPLY },
|
|
{ "KEYPADMINUS", DIK_SUBTRACT },
|
|
{ "KEYPADPLUS", DIK_ADD },
|
|
{ "KEYPADENTER", DIK_NUMPADENTER },
|
|
{ "KEYPADEQUALS", DIK_NUMPADEQUALS },
|
|
|
|
// Arrows + Home/End Pad
|
|
{ "UP", DIK_UP },
|
|
{ "DOWN", DIK_DOWN },
|
|
{ "RIGHT", DIK_RIGHT },
|
|
{ "LEFT", DIK_LEFT },
|
|
{ "INSERT", DIK_INSERT },
|
|
{ "HOME", DIK_HOME },
|
|
{ "END", DIK_END },
|
|
{ "PGUP", DIK_PRIOR },
|
|
{ "PGDN", DIK_NEXT },
|
|
|
|
// Function Key
|
|
{ "F1", DIK_F1 },
|
|
{ "F2", DIK_F2 },
|
|
{ "F3", DIK_F3 },
|
|
{ "F4", DIK_F4 },
|
|
{ "F5", DIK_F5 },
|
|
{ "F6", DIK_F6 },
|
|
{ "F7", DIK_F7 },
|
|
{ "F8", DIK_F8 },
|
|
{ "F9", DIK_F9 },
|
|
{ "F10", DIK_F10 },
|
|
{ "F11", DIK_F11 },
|
|
{ "F12", DIK_F12 },
|
|
{ "F13", DIK_F13 },
|
|
{ "F14", DIK_F14 },
|
|
{ "F15", DIK_F15 },
|
|
|
|
// Modifier Keys
|
|
{ "NUMLOCK", DIK_NUMLOCK },
|
|
{ "CAPSLOCK", DIK_CAPITAL },
|
|
{ "SCROLLLOCK", DIK_SCROLL },
|
|
{ "RIGHTSHIFT", DIK_RSHIFT },
|
|
{ "LEFTSHIFT", DIK_LSHIFT },
|
|
{ "RIGHTCTRL", DIK_RCONTROL },
|
|
{ "LEFTCTRL", DIK_LCONTROL },
|
|
{ "RIGHTALT", DIK_RMENU },
|
|
{ "LEFTALT", DIK_LMENU },
|
|
//{ "RIGHTMETA", ?? },
|
|
//{ "LEFTMETA", ?? },
|
|
{ "RIGHTWINDOWS", DIK_RWIN },
|
|
{ "LEFTWINDOWS", DIK_LWIN },
|
|
//{ "ALTGR", ?? },
|
|
//{ "COMPOSE", ?? },
|
|
|
|
// Other
|
|
//{ "HELP", ?? },
|
|
{ "PRINT", DIK_SYSRQ },
|
|
//{ "SYSREQ", ?? },
|
|
//{ "BREAK", ?? },
|
|
//{ "MENU", ?? },
|
|
//{ "POWER", ?? },
|
|
//{ "EURO", ?? },
|
|
//{ "UNDO", ?? },
|
|
};
|
|
|
|
bool IsXInputDevice(const GUID &devProdGUID)
|
|
{
|
|
// Following code taken from MSDN
|
|
IWbemLocator* pIWbemLocator = NULL;
|
|
IEnumWbemClassObject* pEnumDevices = NULL;
|
|
IWbemClassObject* pDevices[20] = {0};
|
|
IWbemServices* pIWbemServices = NULL;
|
|
BSTR bstrNamespace = NULL;
|
|
BSTR bstrDeviceID = NULL;
|
|
BSTR bstrClassName = NULL;
|
|
|
|
// Create WMI
|
|
bool isXInpDev = false;
|
|
HRESULT hr = CoCreateInstance(__uuidof(WbemLocator), NULL, CLSCTX_INPROC_SERVER, __uuidof(IWbemLocator), (LPVOID*)&pIWbemLocator);
|
|
if (FAILED(hr) || pIWbemLocator == NULL)
|
|
goto Finish;
|
|
|
|
if ((bstrNamespace = SysAllocString(L"\\\\.\\root\\cimv2")) == NULL) goto Finish;
|
|
if ((bstrClassName = SysAllocString(L"Win32_PNPEntity")) == NULL) goto Finish;
|
|
if ((bstrDeviceID = SysAllocString(L"DeviceID")) == NULL) goto Finish;
|
|
|
|
// Connect to WMI
|
|
hr = pIWbemLocator->ConnectServer(bstrNamespace, NULL, NULL, 0L, 0L, NULL, NULL, &pIWbemServices);
|
|
if (FAILED(hr) || pIWbemServices == NULL)
|
|
goto Finish;
|
|
|
|
// Switch security level to IMPERSONATE
|
|
CoSetProxyBlanket(pIWbemServices, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, NULL, RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE);
|
|
|
|
hr = pIWbemServices->CreateInstanceEnum(bstrClassName, 0, NULL, &pEnumDevices);
|
|
if (FAILED(hr) || pEnumDevices == NULL)
|
|
goto Finish;
|
|
|
|
// Loop over all devices
|
|
for (;;)
|
|
{
|
|
// Get 20 at a time
|
|
DWORD uReturned;
|
|
hr = pEnumDevices->Next(10000, 20, pDevices, &uReturned);
|
|
if (FAILED(hr) || uReturned == 0)
|
|
goto Finish;
|
|
|
|
for (unsigned devNum = 0; devNum < uReturned; devNum++)
|
|
{
|
|
// For each device, get its device ID
|
|
VARIANT var;
|
|
hr = pDevices[devNum]->Get(bstrDeviceID, 0L, &var, NULL, NULL);
|
|
if (SUCCEEDED(hr) && var.vt == VT_BSTR && var.bstrVal != NULL)
|
|
{
|
|
// Check if the device ID contains "IG_", which means it's an XInput device (this can't be determined via DirectInput on its own)
|
|
if (wcsstr(var.bstrVal, L"IG_"))
|
|
{
|
|
// If so, then get VID/PID from var.bstrVal
|
|
DWORD dwPid = 0, dwVid = 0;
|
|
WCHAR* strVid = wcsstr(var.bstrVal, L"VID_");
|
|
if (strVid && swscanf(strVid, L"VID_%4X", &dwVid) != 1)
|
|
dwVid = 0;
|
|
WCHAR* strPid = wcsstr(var.bstrVal, L"PID_");
|
|
if (strPid && swscanf(strPid, L"PID_%4X", &dwPid) != 1)
|
|
dwPid = 0;
|
|
|
|
// Compare VID/PID to values held in DirectInput device's product GUID
|
|
DWORD dwVidPid = MAKELONG(dwVid, dwPid);
|
|
if (dwVidPid == devProdGUID.Data1)
|
|
{
|
|
isXInpDev = true;
|
|
goto Finish;
|
|
}
|
|
}
|
|
}
|
|
if (pDevices[devNum] != NULL)
|
|
{
|
|
pDevices[devNum]->Release();
|
|
pDevices[devNum] = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
Finish:
|
|
if (bstrNamespace)
|
|
SysFreeString(bstrNamespace);
|
|
if (bstrDeviceID)
|
|
SysFreeString(bstrDeviceID);
|
|
if (bstrClassName)
|
|
SysFreeString(bstrClassName);
|
|
for (unsigned devNum = 0; devNum < 20; devNum++)
|
|
{
|
|
if (pDevices[devNum] != NULL)
|
|
pDevices[devNum]->Release();
|
|
}
|
|
if (pEnumDevices != NULL)
|
|
pEnumDevices->Release();
|
|
if (pIWbemLocator != NULL)
|
|
pIWbemLocator->Release();
|
|
if (pIWbemServices != NULL)
|
|
pIWbemServices->Release();
|
|
return isXInpDev;
|
|
}
|
|
|
|
BOOL CALLBACK DI8EnumDevicesCallback(LPCDIDEVICEINSTANCE instance, LPVOID context)
|
|
{
|
|
// Keep track of all joystick device GUIDs
|
|
DIEnumDevsContext *diContext = (DIEnumDevsContext*)context;
|
|
DIJoyInfo info;
|
|
memset(&info, 0, sizeof(DIJoyInfo));
|
|
info.guid = instance->guidInstance;
|
|
// If XInput is enabled, see if device is an XInput device
|
|
info.isXInput = diContext->useXInput && IsXInputDevice(instance->guidProduct);
|
|
diContext->infos->push_back(info);
|
|
return DIENUM_CONTINUE;
|
|
}
|
|
|
|
BOOL CALLBACK DI8EnumAxesCallback(LPCDIDEVICEOBJECTINSTANCE instance, LPVOID context)
|
|
{
|
|
// Workout which axis is currently being enumerated
|
|
int objNum = DIDFT_GETINSTANCE(instance->dwType);
|
|
DIOBJECTDATAFORMAT fmt = c_dfDIJoystick2.rgodf[objNum];
|
|
int axisNum;
|
|
switch (fmt.dwOfs)
|
|
{
|
|
case DIJOFS_X: axisNum = AXIS_X; break;
|
|
case DIJOFS_Y: axisNum = AXIS_Y; break;
|
|
case DIJOFS_Z: axisNum = AXIS_Z; break;
|
|
case DIJOFS_RX: axisNum = AXIS_RX; break;
|
|
case DIJOFS_RY: axisNum = AXIS_RY; break;
|
|
case DIJOFS_RZ: axisNum = AXIS_RZ; break;
|
|
default: return DIENUM_CONTINUE;
|
|
}
|
|
|
|
// Record fact that axis is present and also whether it has force feedback available
|
|
JoyDetails *joyDetails = (JoyDetails*)context;
|
|
joyDetails->hasAxis[axisNum] = true;
|
|
joyDetails->axisHasFF[axisNum] = !!(instance->dwFlags & DIDOI_FFACTUATOR);
|
|
return DIENUM_CONTINUE;
|
|
}
|
|
|
|
BOOL CALLBACK DI8EnumEffectsCallback(LPCDIEFFECTINFO effectInfo, LPVOID context)
|
|
{
|
|
JoyDetails *joyDetails = (JoyDetails*)context;
|
|
// Check joystick has at least one of required types of effects
|
|
if (!!(effectInfo->dwEffType & (DIEFT_CONSTANTFORCE | DIEFT_PERIODIC | DIEFT_CONDITION)))
|
|
joyDetails->hasFFeedback = true;
|
|
return DIENUM_CONTINUE;
|
|
}
|
|
|
|
const char *CDirectInputSystem::ConstructName(bool useRawInput, bool useXInput, bool enableFFeedback)
|
|
{
|
|
if (useRawInput)
|
|
return (useXInput ? "RawInput/XInput" : "RawInput/DirectInput");
|
|
else
|
|
return (useXInput ? "Xinput" : "DirectInput");
|
|
}
|
|
|
|
CDirectInputSystem::CDirectInputSystem(bool useRawInput, bool useXInput, bool enableFFeedback) :
|
|
CInputSystem(ConstructName(useRawInput, useXInput, enableFFeedback)),
|
|
m_useRawInput(useRawInput), m_useXInput(useXInput), m_enableFFeedback(enableFFeedback),
|
|
m_initializedCOM(false), m_activated(false), m_hwnd(NULL), m_screenW(0), m_screenH(0),
|
|
m_getRIDevListPtr(NULL), m_getRIDevInfoPtr(NULL), m_regRIDevsPtr(NULL), m_getRIDataPtr(NULL),
|
|
m_xiGetCapabilitiesPtr(NULL), m_xiGetStatePtr(NULL), m_xiSetStatePtr(NULL), m_di8(NULL), m_di8Keyboard(NULL), m_di8Mouse(NULL)
|
|
{
|
|
// Reset initial states
|
|
memset(&m_combRawMseState, 0, sizeof(RawMseState));
|
|
memset(&m_diKeyState, 0, sizeof(LPDIRECTINPUTDEVICE8));
|
|
memset(&m_diMseState, 0, sizeof(LPDIRECTINPUTDEVICE8));
|
|
}
|
|
|
|
CDirectInputSystem::~CDirectInputSystem()
|
|
{
|
|
CloseKeyboardsAndMice();
|
|
CloseJoysticks();
|
|
|
|
if (m_di8)
|
|
{
|
|
m_di8->Release();
|
|
m_di8 = NULL;
|
|
if (m_initializedCOM)
|
|
CoUninitialize();
|
|
}
|
|
}
|
|
|
|
bool CDirectInputSystem::GetRegString(HKEY regKey, const char *regPath, string &str)
|
|
{
|
|
// Query to get the length
|
|
DWORD dataLen;
|
|
LONG result = RegQueryValueEx(regKey, regPath, NULL, NULL, NULL, &dataLen);
|
|
if (result != ERROR_SUCCESS)
|
|
return false;
|
|
|
|
// Retrieve the actual data
|
|
char data[MAX_PATH];
|
|
dataLen = min<DWORD>(MAX_PATH - 1, dataLen);
|
|
result = RegQueryValueEx(regKey, regPath, NULL, NULL, (LPBYTE)data, &dataLen);
|
|
if (result != ERROR_SUCCESS)
|
|
return false;
|
|
data[MAX_PATH - 1] = '\0';
|
|
str.assign(data);
|
|
return true;
|
|
}
|
|
|
|
bool CDirectInputSystem::GetRegDeviceName(const char *rawDevName, char *name)
|
|
{
|
|
// Check raw device string is in form that can be handled and remove initial 4-char sequence
|
|
// For XP this is: \??\TypeID#HardwareID#InstanceID#{DevicesClasses-id}
|
|
// For Vista/Win7 64bit this is: \\?\TypeID#HardwareID#InstanceID#{DevicesClasses-id}
|
|
string devNameStr(rawDevName);
|
|
if (devNameStr.find("\\??\\") != string::npos || devNameStr.find("\\\\?\\") != string::npos)
|
|
devNameStr.erase(0, 4);
|
|
else
|
|
return false;
|
|
|
|
// Append raw device string to base registry path and convert all #'s to \ in the process
|
|
string regPath = "SYSTEM\\CurrentControlSet\\Enum\\" + devNameStr;
|
|
for (size_t i = 0; i < regPath.size(); i++)
|
|
{
|
|
if (regPath[i] == '#')
|
|
regPath[i] = '\\';
|
|
}
|
|
|
|
// Remove part after last \ in path
|
|
size_t last = regPath.rfind('\\');
|
|
if (last != string::npos)
|
|
regPath = regPath.erase(last);
|
|
|
|
// Try and open registry key with this path
|
|
HKEY regKey;
|
|
LONG result = RegOpenKeyEx(HKEY_LOCAL_MACHINE, regPath.c_str(), 0, KEY_READ, ®Key);
|
|
if (result != ERROR_SUCCESS)
|
|
return false;
|
|
|
|
string parentIdStr;
|
|
|
|
// Fetch device description from registry, if it exists, and use that for name
|
|
string regStr;
|
|
if (GetRegString(regKey, "DeviceDesc", regStr))
|
|
goto Found;
|
|
|
|
// If above failed, then try looking at USB key for HID devices
|
|
RegCloseKey(regKey);
|
|
|
|
// Check it is HID device
|
|
if (devNameStr.find("HID") == string::npos)
|
|
return false;
|
|
|
|
// Get parent id, from after last \ in name
|
|
last = regPath.rfind('\\');
|
|
if (last == regPath.size() - 1 || last == string::npos)
|
|
return false;
|
|
parentIdStr = regPath.substr(last + 1);
|
|
|
|
// Open USB base key
|
|
result = RegOpenKeyEx(HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Enum\\USB", 0, KEY_READ, ®Key);
|
|
if (result != ERROR_SUCCESS)
|
|
return false;
|
|
|
|
// Loop through all USB devices
|
|
for (int usbIndex = 0; result == ERROR_SUCCESS; usbIndex++)
|
|
{
|
|
// Get sub-key name
|
|
char keyName[MAX_PATH];
|
|
DWORD nameLen = MAX_PATH - 1;
|
|
result = RegEnumKeyEx(regKey, usbIndex, keyName, &nameLen, NULL, NULL, NULL, NULL);
|
|
if (result == ERROR_SUCCESS)
|
|
{
|
|
// Open sub-key
|
|
HKEY subRegKey;
|
|
LONG subResult = RegOpenKeyEx(regKey, keyName, 0, KEY_READ, &subRegKey);
|
|
if (subResult != ERROR_SUCCESS)
|
|
continue;
|
|
|
|
// Loop through all sub-keys
|
|
for (int subIndex = 0; subResult == ERROR_SUCCESS; subIndex++)
|
|
{
|
|
// the next enumerated subkey and scan it
|
|
nameLen = MAX_PATH - 1;
|
|
subResult = RegEnumKeyEx(subRegKey, subIndex, keyName, &nameLen, NULL, NULL, NULL, NULL);
|
|
if (subResult == ERROR_SUCCESS)
|
|
{
|
|
// Open final key
|
|
HKEY finalRegKey;
|
|
LONG finalResult = RegOpenKeyEx(subRegKey, keyName, 0, KEY_READ, &finalRegKey);
|
|
if (finalResult != ERROR_SUCCESS)
|
|
continue;
|
|
|
|
// Get parent id prefix and see if it matches
|
|
string finalParentIdStr;
|
|
if (GetRegString(finalRegKey, "ParentIdPrefix", finalParentIdStr) && parentIdStr.compare(0, finalParentIdStr.size(), finalParentIdStr) == 0)
|
|
{
|
|
// Get device description, if it exists, and use that for name
|
|
if (GetRegString(finalRegKey, "DeviceDesc", regStr))
|
|
{
|
|
RegCloseKey(finalRegKey);
|
|
RegCloseKey(subRegKey);
|
|
goto Found;
|
|
}
|
|
}
|
|
|
|
// Close final key
|
|
RegCloseKey(finalRegKey);
|
|
}
|
|
}
|
|
|
|
// Close sub-key
|
|
RegCloseKey(subRegKey);
|
|
}
|
|
}
|
|
|
|
RegCloseKey(regKey);
|
|
return false;
|
|
|
|
Found:
|
|
// If found device description, name will be from final colon
|
|
last = regStr.rfind(';');
|
|
if (last == regStr.size() - 1 || last == string::npos)
|
|
last = 0;
|
|
else
|
|
last++;
|
|
strncpy(name, regStr.c_str() + last, MAX_NAME_LENGTH - 1);
|
|
name[MAX_NAME_LENGTH - 1] = '\0';
|
|
|
|
RegCloseKey(regKey);
|
|
return true;
|
|
}
|
|
|
|
void CDirectInputSystem::OpenKeyboardsAndMice()
|
|
{
|
|
if (m_useRawInput)
|
|
{
|
|
// If RawInput enabled, get list of available devices
|
|
UINT nDevices;
|
|
if (m_getRIDevListPtr(NULL, &nDevices, sizeof(RAWINPUTDEVICELIST)) == 0 && nDevices > 0)
|
|
{
|
|
PRAWINPUTDEVICELIST pDeviceList = new RAWINPUTDEVICELIST[nDevices];
|
|
if (pDeviceList != NULL && m_getRIDevListPtr(pDeviceList, &nDevices, sizeof(RAWINPUTDEVICELIST)) != (UINT)-1)
|
|
{
|
|
// Loop through devices backwards (since new devices are usually added at beginning)
|
|
for (int devNum = nDevices - 1; devNum >= 0; devNum--)
|
|
{
|
|
RAWINPUTDEVICELIST device = pDeviceList[devNum];
|
|
|
|
// Get device name
|
|
UINT nLength;
|
|
if (m_getRIDevInfoPtr(device.hDevice, RIDI_DEVICENAME, NULL, &nLength) != 0)
|
|
continue;
|
|
nLength = min<int>(MAX_NAME_LENGTH, nLength);
|
|
char name[MAX_NAME_LENGTH];
|
|
if (m_getRIDevInfoPtr(device.hDevice, RIDI_DEVICENAME, name, &nLength) == -1)
|
|
continue;
|
|
|
|
// Ignore any RDP devices
|
|
if (strstr(name, "Root#RDP_") != NULL)
|
|
continue;
|
|
|
|
// Store details and device handles for attached keyboards and mice
|
|
if (device.dwType == RIM_TYPEKEYBOARD)
|
|
{
|
|
m_rawKeyboards.push_back(device.hDevice);
|
|
|
|
KeyDetails keyDetails;
|
|
if (!GetRegDeviceName(name, keyDetails.name))
|
|
strcpy(keyDetails.name, "Unknown Keyboard");
|
|
m_keyDetails.push_back(keyDetails);
|
|
|
|
BOOL *pKeyState = new BOOL[255];
|
|
memset(pKeyState, 0, sizeof(BOOL) * 255);
|
|
m_rawKeyStates.push_back(pKeyState);
|
|
}
|
|
else if (device.dwType == RIM_TYPEMOUSE)
|
|
{
|
|
m_rawMice.push_back(device.hDevice);
|
|
|
|
MouseDetails mseDetails;
|
|
if (!GetRegDeviceName(name, mseDetails.name))
|
|
strcpy(mseDetails.name, "Unknown Mouse");
|
|
// TODO mseDetails.isAbsolute = ???
|
|
m_mseDetails.push_back(mseDetails);
|
|
|
|
RawMseState mseState;
|
|
memset(&mseState, 0, sizeof(RawMseState));
|
|
m_rawMseStates.push_back(mseState);
|
|
}
|
|
}
|
|
|
|
DebugLog("RawInput - found %d keyboards and %d mice", m_rawKeyboards.size(), m_rawMice.size());
|
|
|
|
// Check some devices were actually found
|
|
m_useRawInput = m_rawKeyboards.size() > 0 && m_rawMice.size() > 0;
|
|
}
|
|
else
|
|
{
|
|
ErrorLog("Unable to query RawInput API for attached devices (error %d) - switching to DirectInput.\n", GetLastError());
|
|
|
|
m_useRawInput = false;
|
|
}
|
|
|
|
if (pDeviceList != NULL)
|
|
delete[] pDeviceList;
|
|
}
|
|
else
|
|
{
|
|
ErrorLog("Unable to query RawInput API for attached devices (error %d) - switching to DirectInput.\n", GetLastError());
|
|
|
|
m_useRawInput = false;
|
|
}
|
|
|
|
if (m_useRawInput)
|
|
return;
|
|
}
|
|
|
|
// If get here then either RawInput disabled or getting its devices failed so default to DirectInput.
|
|
// Open DirectInput system keyboard and set its data format
|
|
HRESULT hr;
|
|
if (FAILED(hr = m_di8->CreateDevice(GUID_SysKeyboard, &m_di8Keyboard, NULL)))
|
|
{
|
|
ErrorLog("Unable to create DirectInput keyboard device (error %d) - key input will be unavailable.\n", hr);
|
|
|
|
m_di8Keyboard = NULL;
|
|
}
|
|
else if (FAILED(hr = m_di8Keyboard->SetDataFormat(&c_dfDIKeyboard)))
|
|
{
|
|
ErrorLog("Unable to set data format for DirectInput keyboard (error %d) - key input will be unavailable.\n", hr);
|
|
|
|
m_di8Keyboard->Release();
|
|
m_di8Keyboard = NULL;
|
|
}
|
|
|
|
// Open DirectInput system mouse and set its data format
|
|
if (FAILED(hr = m_di8->CreateDevice(GUID_SysMouse, &m_di8Mouse, NULL)))
|
|
{
|
|
ErrorLog("Unable to create DirectInput mouse device (error %d) - mouse input will be unavailable.\n", hr);
|
|
|
|
m_di8Mouse = NULL;
|
|
return;
|
|
}
|
|
if (FAILED(hr = m_di8Mouse->SetDataFormat(&c_dfDIMouse2)))
|
|
{
|
|
ErrorLog("Unable to set data format for DirectInput mouse (error %d) - mouse input will be unavailable.\n", hr);
|
|
|
|
m_di8Mouse->Release();
|
|
m_di8Mouse = NULL;
|
|
return;
|
|
}
|
|
|
|
// Set mouse axis mode to relative
|
|
DIPROPDWORD dipdw;
|
|
dipdw.diph.dwSize = sizeof(DIPROPDWORD);
|
|
dipdw.diph.dwHeaderSize = sizeof(DIPROPHEADER);
|
|
dipdw.diph.dwHow = DIPH_DEVICE;
|
|
dipdw.diph.dwObj = 0;
|
|
dipdw.dwData = DIPROPAXISMODE_REL;
|
|
if (FAILED(hr = m_di8Mouse->SetProperty(DIPROP_AXISMODE, &dipdw.diph)))
|
|
{
|
|
ErrorLog("Unable to set axis mode for DirectInput mouse (error %d) - mouse input will be unavailable.\n", hr);
|
|
|
|
m_di8Mouse->Release();
|
|
m_di8Mouse = NULL;
|
|
}
|
|
}
|
|
|
|
void CDirectInputSystem::ActivateKeyboardsAndMice()
|
|
{
|
|
// Sync up all mice with current cursor position
|
|
ResetMice();
|
|
|
|
if (m_useRawInput)
|
|
{
|
|
// Register for RawInput
|
|
RAWINPUTDEVICE rid[2];
|
|
|
|
// Register for keyboard input
|
|
rid[0].usUsagePage = 0x01;
|
|
rid[0].usUsage = 0x06;
|
|
rid[0].dwFlags = (m_grabMouse ? RIDEV_CAPTUREMOUSE : RIDEV_INPUTSINK) | RIDEV_NOLEGACY;
|
|
rid[0].hwndTarget = m_hwnd;
|
|
|
|
// Register for mouse input
|
|
rid[1].usUsagePage = 0x01;
|
|
rid[1].usUsage = 0x02;
|
|
rid[1].dwFlags = (m_grabMouse ? RIDEV_CAPTUREMOUSE : RIDEV_INPUTSINK) | RIDEV_NOLEGACY;
|
|
rid[1].hwndTarget = m_hwnd;
|
|
|
|
if (!m_regRIDevsPtr(rid, 2, sizeof(RAWINPUTDEVICE)))
|
|
ErrorLog("Unable to register for keyboard and mouse input with RawInput API (error %d) - keyboard and mouse input will be unavailable.\n", GetLastError());
|
|
return;
|
|
}
|
|
|
|
// Set DirectInput cooperative level of keyboard and mouse
|
|
if (m_di8Keyboard != NULL)
|
|
m_di8Keyboard->SetCooperativeLevel(m_hwnd, DISCL_NONEXCLUSIVE | DISCL_BACKGROUND);
|
|
if (m_di8Mouse != NULL)
|
|
m_di8Mouse->SetCooperativeLevel(m_hwnd, DISCL_NONEXCLUSIVE | DISCL_BACKGROUND);
|
|
}
|
|
|
|
void CDirectInputSystem::PollKeyboardsAndMice()
|
|
{
|
|
if (m_useRawInput)
|
|
{
|
|
// For RawInput, only thing to do is update wheelDir from wheelData for each mouse state. Everything else is updated via WM events.
|
|
for (vector<RawMseState>::iterator it = m_rawMseStates.begin(); it != m_rawMseStates.end(); it++)
|
|
{
|
|
if (it->wheelDelta != 0)
|
|
{
|
|
it->wheelDir = (it->wheelDelta > 0 ? 1 : -1);
|
|
it->wheelDelta = 0;
|
|
}
|
|
else
|
|
it->wheelDir = 0;
|
|
}
|
|
if (m_combRawMseState.wheelDelta != 0)
|
|
{
|
|
m_combRawMseState.wheelDir = (m_combRawMseState.wheelDelta > 0 ? 1 : -1);
|
|
m_combRawMseState.wheelDelta = 0;
|
|
}
|
|
else
|
|
m_combRawMseState.wheelDir = 0;
|
|
return;
|
|
}
|
|
|
|
// Get current keyboard state from DirectInput
|
|
HRESULT hr;
|
|
if (m_di8Keyboard != NULL)
|
|
{
|
|
if (FAILED(hr = m_di8Keyboard->Poll()))
|
|
{
|
|
hr = m_di8Keyboard->Acquire();
|
|
while (hr == DIERR_INPUTLOST)
|
|
hr = m_di8Keyboard->Acquire();
|
|
|
|
if (hr == DIERR_OTHERAPPHASPRIO || hr == DIERR_INVALIDPARAM || hr == DIERR_NOTINITIALIZED)
|
|
return;
|
|
}
|
|
|
|
// Keep track of keyboard state
|
|
m_di8Keyboard->GetDeviceState(sizeof(m_diKeyState), m_diKeyState);
|
|
}
|
|
|
|
// Get current mouse state from DirectInput
|
|
if (m_di8Mouse != NULL)
|
|
{
|
|
if (FAILED(hr = m_di8Mouse->Poll()))
|
|
{
|
|
hr = m_di8Mouse->Acquire();
|
|
while (hr == DIERR_INPUTLOST)
|
|
hr = m_di8Mouse->Acquire();
|
|
|
|
if (hr == DIERR_OTHERAPPHASPRIO || hr == DIERR_INVALIDPARAM || hr == DIERR_NOTINITIALIZED)
|
|
return;
|
|
}
|
|
|
|
// Keep track of mouse absolute axis values, clamping them at display edges, aswell as wheel direction and buttons
|
|
DIMOUSESTATE2 mseState;
|
|
m_di8Mouse->GetDeviceState(sizeof(mseState), &mseState);
|
|
m_diMseState.x = CInputSource::Clamp(m_diMseState.x + mseState.lX, m_dispX, m_dispX + m_dispW);
|
|
m_diMseState.y = CInputSource::Clamp(m_diMseState.y + mseState.lY, m_dispY, m_dispY + m_dispH);
|
|
if (mseState.lZ != 0)
|
|
{
|
|
// Z-axis is clamped to range -100 to 100 (DirectInput returns +120 & -120 for wheel delta which are scaled to +5 & -5)
|
|
LONG wheelDelta = 5 * mseState.lZ / 120;
|
|
m_diMseState.z = CInputSource::Clamp(m_diMseState.z + wheelDelta, -100, 100);
|
|
m_diMseState.wheelDir = (wheelDelta > 0 ? 1 : -1);
|
|
}
|
|
else
|
|
m_diMseState.wheelDir = 0;
|
|
memcpy(&m_diMseState.buttons, mseState.rgbButtons, sizeof(m_diMseState.buttons));
|
|
}
|
|
}
|
|
|
|
void CDirectInputSystem::CloseKeyboardsAndMice()
|
|
{
|
|
if (m_useRawInput)
|
|
{
|
|
if (m_activated)
|
|
{
|
|
// If RawInput was registered, then unregister now
|
|
RAWINPUTDEVICE rid[2];
|
|
|
|
// Unregister from keyboard input
|
|
rid[0].usUsagePage = 0x01;
|
|
rid[0].usUsage = 0x06;
|
|
rid[0].dwFlags = RIDEV_REMOVE;
|
|
rid[0].hwndTarget = m_hwnd;
|
|
|
|
// Unregister from mouse input
|
|
rid[1].usUsagePage = 0x01;
|
|
rid[1].usUsage = 0x02;
|
|
rid[1].dwFlags = RIDEV_REMOVE;
|
|
rid[1].hwndTarget = m_hwnd;
|
|
|
|
m_regRIDevsPtr(rid, 2, sizeof(RAWINPUTDEVICE));
|
|
}
|
|
|
|
// Delete storage for keyboards
|
|
for (vector<BOOL*>::iterator it = m_rawKeyStates.begin(); it != m_rawKeyStates.end(); it++)
|
|
delete[] *it;
|
|
m_keyDetails.clear();
|
|
m_rawKeyboards.clear();
|
|
m_rawKeyStates.clear();
|
|
|
|
// Delete storage for mice
|
|
m_mseDetails.clear();
|
|
m_rawMice.clear();
|
|
m_rawMseStates.clear();
|
|
}
|
|
|
|
// If DirectInput keyboard and mouse were created, then release them too
|
|
if (m_di8Keyboard != NULL)
|
|
{
|
|
m_di8Keyboard->Unacquire();
|
|
m_di8Keyboard->Release();
|
|
m_di8Keyboard = NULL;
|
|
}
|
|
if (m_di8Mouse != NULL)
|
|
{
|
|
m_di8Mouse->Unacquire();
|
|
m_di8Mouse->Release();
|
|
m_di8Mouse = NULL;
|
|
}
|
|
}
|
|
|
|
void CDirectInputSystem::ResetMice()
|
|
{
|
|
// Get current mouse cursor position in window
|
|
POINT p;
|
|
if (!GetCursorPos(&p) || !ScreenToClient(m_hwnd, &p))
|
|
return;
|
|
|
|
// Set all mice coords to current cursor position
|
|
if (m_useRawInput)
|
|
{
|
|
m_combRawMseState.x = p.x;
|
|
m_combRawMseState.y = p.y;
|
|
m_combRawMseState.z = 0;
|
|
for (vector<RawMseState>::iterator it = m_rawMseStates.begin(); it != m_rawMseStates.end(); it++)
|
|
{
|
|
it->x = p.x;
|
|
it->y = p.y;
|
|
it->z = 0;
|
|
}
|
|
}
|
|
|
|
m_diMseState.x = p.x;
|
|
m_diMseState.y = p.y;
|
|
m_diMseState.z = 0;
|
|
}
|
|
|
|
void CDirectInputSystem::ProcessRawInput(HRAWINPUT hInput)
|
|
{
|
|
// RawInput data event
|
|
BYTE buffer[4096];
|
|
LPBYTE pBuf = buffer;
|
|
|
|
// Get size of data structure to receive
|
|
UINT dwSize;
|
|
if (m_getRIDataPtr(hInput, RID_INPUT, NULL, &dwSize, sizeof(RAWINPUTHEADER)) != 0)
|
|
return;
|
|
if (dwSize > sizeof(buffer))
|
|
{
|
|
pBuf = new BYTE[dwSize];
|
|
if (pBuf == NULL)
|
|
return;
|
|
}
|
|
|
|
// Get data
|
|
if (m_getRIDataPtr(hInput, RID_INPUT, pBuf, &dwSize, sizeof(RAWINPUTHEADER)) == dwSize)
|
|
{
|
|
RAWINPUT *pData = (RAWINPUT*)pBuf;
|
|
if (pData->header.dwType == RIM_TYPEKEYBOARD)
|
|
{
|
|
// Keyboard event, so identify which keyboard produced event
|
|
BOOL *pKeyState = NULL;
|
|
size_t kbdNum;
|
|
for (kbdNum = 0; kbdNum < m_rawKeyboards.size(); kbdNum++)
|
|
{
|
|
if (m_rawKeyboards[kbdNum] == pData->header.hDevice)
|
|
{
|
|
pKeyState = m_rawKeyStates[kbdNum];
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Check is a valid keyboard
|
|
if (pKeyState != NULL)
|
|
{
|
|
// Get scancode of key and whether key was pressed or released
|
|
BOOL isRight = pData->data.keyboard.Flags & RI_KEY_E0;
|
|
UINT8 scanCode = (pData->data.keyboard.MakeCode & 0x7f) | (isRight ? 0x80 : 0x00);
|
|
BOOL pressed = !(pData->data.keyboard.Flags & RI_KEY_BREAK);
|
|
|
|
// Store current state for key
|
|
if (scanCode != 0xAA)
|
|
pKeyState[scanCode] = pressed;
|
|
}
|
|
}
|
|
else if (pData->header.dwType == RIM_TYPEMOUSE)
|
|
{
|
|
// Mouse event, so identify which mouse produced event
|
|
RawMseState *pMseState = NULL;
|
|
size_t mseNum;
|
|
for (mseNum = 0; mseNum < m_rawMice.size(); mseNum++)
|
|
{
|
|
if (m_rawMice[mseNum] == pData->header.hDevice)
|
|
{
|
|
pMseState = &m_rawMseStates[mseNum];
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Check is a valid mouse
|
|
if (pMseState != NULL)
|
|
{
|
|
// Get X- & Y-axis data
|
|
LONG lx = pData->data.mouse.lLastX;
|
|
LONG ly = pData->data.mouse.lLastY;
|
|
if (pData->data.mouse.usFlags & MOUSE_MOVE_ABSOLUTE)
|
|
{
|
|
// If data is absolute, then scale source values (which range 0 to 65535) to screen coordinates and convert
|
|
// to be relative to game window origin
|
|
POINT p;
|
|
p.x = CInputSource::Scale(lx, 0, 0xFFFF, 0, m_screenW);
|
|
p.y = CInputSource::Scale(ly, 0, 0xFFFF, 0, m_screenH);
|
|
if (ScreenToClient(m_hwnd, &p))
|
|
{
|
|
pMseState->x = p.x;
|
|
pMseState->y = p.y;
|
|
}
|
|
|
|
// Also update combined state
|
|
m_combRawMseState.x = pMseState->x;
|
|
m_combRawMseState.y = pMseState->y;
|
|
}
|
|
else
|
|
{
|
|
// If data is relative, then keep track of absolute position, clamping it at display edges
|
|
pMseState->x = CInputSource::Clamp(pMseState->x + lx, m_dispX, m_dispX + m_dispW);
|
|
pMseState->y = CInputSource::Clamp(pMseState->y + ly, m_dispY, m_dispY + m_dispH);
|
|
|
|
// Also update combined state
|
|
m_combRawMseState.x = CInputSource::Clamp(m_combRawMseState.x + lx, m_dispX, m_dispX + m_dispW);
|
|
m_combRawMseState.y = CInputSource::Clamp(m_combRawMseState.y + ly, m_dispY, m_dispY + m_dispH);
|
|
}
|
|
|
|
// Get button flags and wheel delta (RawInput returns +120 & -120 for the latter which are scaled to +5 & -5)
|
|
USHORT butFlags = pData->data.mouse.usButtonFlags;
|
|
LONG wheelDelta = 5 * (SHORT)pData->data.mouse.usButtonData / 120;
|
|
|
|
// Update Z-axis (wheel) value
|
|
if (butFlags & RI_MOUSE_WHEEL)
|
|
{
|
|
// Z-axis is clamped to range -100 to 100
|
|
pMseState->z = CInputSource::Clamp(pMseState->z + wheelDelta, -100, 100);
|
|
pMseState->wheelDelta += wheelDelta;
|
|
}
|
|
|
|
// Keep track of buttons pressed/released
|
|
if (butFlags & RI_MOUSE_LEFT_BUTTON_DOWN) pMseState->buttons |= 1;
|
|
else if (butFlags & RI_MOUSE_LEFT_BUTTON_UP) pMseState->buttons &= ~1;
|
|
if (butFlags & RI_MOUSE_MIDDLE_BUTTON_DOWN) pMseState->buttons |= 2;
|
|
else if (butFlags & RI_MOUSE_MIDDLE_BUTTON_UP) pMseState->buttons &= ~2;
|
|
if (butFlags & RI_MOUSE_RIGHT_BUTTON_DOWN) pMseState->buttons |= 4;
|
|
else if (butFlags & RI_MOUSE_RIGHT_BUTTON_UP) pMseState->buttons &= ~4;
|
|
if (butFlags & RI_MOUSE_BUTTON_4_DOWN) pMseState->buttons |= 8;
|
|
else if (butFlags & RI_MOUSE_BUTTON_4_UP) pMseState->buttons &= ~8;
|
|
if (butFlags & RI_MOUSE_BUTTON_5_DOWN) pMseState->buttons |= 16;
|
|
else if (butFlags & RI_MOUSE_BUTTON_5_UP) pMseState->buttons &= ~16;
|
|
|
|
// Also update combined state for wheel axis and buttons
|
|
if (butFlags & RI_MOUSE_WHEEL)
|
|
{
|
|
// Z-axis is clamped to range -100 to 100
|
|
m_combRawMseState.z = CInputSource::Clamp(m_combRawMseState.z + wheelDelta, -100, 100);
|
|
m_combRawMseState.wheelDelta += wheelDelta;
|
|
}
|
|
|
|
m_combRawMseState.buttons = 0;
|
|
for (vector<RawMseState>::iterator it = m_rawMseStates.begin(); it != m_rawMseStates.end(); it++)
|
|
m_combRawMseState.buttons |= it->buttons;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (pBuf != buffer)
|
|
delete[] pBuf;
|
|
}
|
|
|
|
void CDirectInputSystem::OpenJoysticks()
|
|
{
|
|
// Get the info about all attached joystick devices
|
|
DIEnumDevsContext diContext;
|
|
diContext.infos = &m_diJoyInfos;
|
|
diContext.useXInput = m_useXInput;
|
|
HRESULT hr;
|
|
if (FAILED(hr = m_di8->EnumDevices(DI8DEVCLASS_GAMECTRL, DI8EnumDevicesCallback, &diContext, DIEDFL_ATTACHEDONLY)))
|
|
return;
|
|
|
|
// Loop through those found
|
|
int joyNum = 0;
|
|
int xNum = 0;
|
|
for (vector<DIJoyInfo>::iterator it = m_diJoyInfos.begin(); it != m_diJoyInfos.end(); it++)
|
|
{
|
|
joyNum++;
|
|
|
|
JoyDetails joyDetails;
|
|
memset(&joyDetails, 0, sizeof(joyDetails));
|
|
|
|
// See if can use XInput for device
|
|
if (it->isXInput)
|
|
{
|
|
// If so, set joystick details (currently XBox controller is only gamepad handled by XInput and so its capabilities are fixed)
|
|
sprintf(joyDetails.name, "Xbox 360 Controller %d (via XInput)", (xNum + 1));
|
|
joyDetails.numAxes = 6; // Left & right triggers are mapped to axes in addition to the two analog sticks, giving a total of 6 axes
|
|
joyDetails.numPOVs = 1; // Digital D-pad
|
|
joyDetails.numButtons = 10;
|
|
joyDetails.hasFFeedback = m_enableFFeedback;
|
|
joyDetails.hasAxis[AXIS_X] = true;
|
|
joyDetails.hasAxis[AXIS_Y] = true;
|
|
joyDetails.hasAxis[AXIS_Z] = true;
|
|
joyDetails.hasAxis[AXIS_RX] = true;
|
|
joyDetails.hasAxis[AXIS_RY] = true;
|
|
joyDetails.hasAxis[AXIS_RZ] = true;
|
|
joyDetails.axisHasFF[AXIS_X] = true; // Force feedback simulated on left and right sticks
|
|
joyDetails.axisHasFF[AXIS_Y] = true;
|
|
joyDetails.axisHasFF[AXIS_Z] = false;
|
|
joyDetails.axisHasFF[AXIS_RX] = true;
|
|
joyDetails.axisHasFF[AXIS_RY] = true;
|
|
joyDetails.axisHasFF[AXIS_RZ] = false;
|
|
|
|
// Keep track of XInput device number
|
|
it->xInputNum = xNum++;
|
|
}
|
|
else
|
|
{
|
|
// Otherwise, open joystick with DirectInput for given GUID and set its data format
|
|
LPDIRECTINPUTDEVICE8 joystick;
|
|
if (FAILED(hr = m_di8->CreateDevice(it->guid, &joystick, NULL)))
|
|
{
|
|
ErrorLog("Unable to create DirectInput joystick device %d (error %d) - skipping joystick.\n", joyNum, hr);
|
|
continue;
|
|
}
|
|
if (FAILED(hr = joystick->SetDataFormat(&c_dfDIJoystick2)))
|
|
{
|
|
ErrorLog("Unable to set data format for DirectInput joystick %d (error %d) - skipping joystick.\n", joyNum, hr);
|
|
|
|
joystick->Release();
|
|
continue;
|
|
}
|
|
|
|
// Get joystick's capabilities
|
|
DIDEVCAPS devCaps;
|
|
devCaps.dwSize = sizeof(DIDEVCAPS);
|
|
if (FAILED(hr = joystick->GetCapabilities(&devCaps)))
|
|
{
|
|
ErrorLog("Unable to query capabilities of DirectInput joystick %d (error %d) - skipping joystick.\n", joyNum, hr);
|
|
|
|
joystick->Release();
|
|
continue;
|
|
}
|
|
|
|
// Gather joystick details (name, num POVs & buttons, which axes are available and whether force feedback is available)
|
|
DIPROPSTRING didps;
|
|
didps.diph.dwSize = sizeof(DIPROPSTRING);
|
|
didps.diph.dwHeaderSize = sizeof(DIPROPHEADER);
|
|
didps.diph.dwHow = DIPH_DEVICE;
|
|
didps.diph.dwObj = 0;
|
|
if (FAILED(hr = joystick->GetProperty(DIPROP_INSTANCENAME, &didps.diph)))
|
|
{
|
|
ErrorLog("Unable to get name of DirectInput joystick %d (error %d) - skipping joystick.\n", joyNum, hr);
|
|
|
|
joystick->Release();
|
|
continue;
|
|
}
|
|
// DInput returns name as Unicode, convert to ASCII
|
|
int len = min<int>(MAX_NAME_LENGTH, (int)wcslen(didps.wsz) + 1);
|
|
WideCharToMultiByte(CP_ACP, 0, didps.wsz, len, joyDetails.name, len, NULL, NULL);
|
|
joyDetails.name[MAX_NAME_LENGTH - 1] = '\0';
|
|
joyDetails.numPOVs = devCaps.dwPOVs;
|
|
joyDetails.numButtons = devCaps.dwButtons;
|
|
|
|
// Enumerate axes
|
|
if (FAILED(hr = joystick->EnumObjects(DI8EnumAxesCallback, &joyDetails, DIDFT_AXIS)))
|
|
{
|
|
ErrorLog("Unable to enumerate axes of DirectInput joystick %d (error %d) - skipping joystick.\n", joyNum, hr);
|
|
|
|
joystick->Release();
|
|
continue;
|
|
}
|
|
joyDetails.numAxes = 0;
|
|
for (int axisNum = 0; axisNum < NUM_JOY_AXES; axisNum++)
|
|
joyDetails.numAxes += joyDetails.hasAxis[axisNum];
|
|
|
|
// See if force feedback enabled and is available for joystick
|
|
if (m_enableFFeedback && (devCaps.dwFlags & DIDC_FORCEFEEDBACK))
|
|
{
|
|
// If so, see what types of effects are available and for which axes
|
|
if (FAILED(hr = joystick->EnumEffects(DI8EnumEffectsCallback, &joyDetails, DIEFT_ALL)))
|
|
ErrorLog("Unable to enumerate effects of DirectInput joystick %d (error %d) - force feedback will be unavailable for joystick.\n", joyNum, hr);
|
|
}
|
|
|
|
// Configure axes, if any
|
|
if (joyDetails.numAxes > 0)
|
|
{
|
|
// Set axis range to be from -32768 to 32767
|
|
DIPROPRANGE didpr;
|
|
didpr.diph.dwSize = sizeof(DIPROPRANGE);
|
|
didpr.diph.dwHeaderSize = sizeof(DIPROPHEADER);
|
|
didpr.diph.dwHow = DIPH_DEVICE;
|
|
didpr.diph.dwObj = 0;
|
|
didpr.lMin = -32768;
|
|
didpr.lMax = 32767;
|
|
if (FAILED(hr = joystick->SetProperty(DIPROP_RANGE, &didpr.diph)))
|
|
{
|
|
ErrorLog("Unable to set axis range of DirectInput joystick %d (error %d) - skipping joystick.\n", joyNum, hr);
|
|
|
|
joystick->Release();
|
|
continue;
|
|
}
|
|
|
|
// Set axis mode to absolute
|
|
DIPROPDWORD dipdw;
|
|
dipdw.diph.dwSize = sizeof(DIPROPDWORD);
|
|
dipdw.diph.dwHeaderSize = sizeof(DIPROPHEADER);
|
|
dipdw.diph.dwHow = DIPH_DEVICE;
|
|
dipdw.diph.dwObj = 0;
|
|
dipdw.dwData = DIPROPAXISMODE_ABS;
|
|
if (FAILED(hr = joystick->SetProperty(DIPROP_AXISMODE, &dipdw.diph)))
|
|
{
|
|
ErrorLog("Unable to set axis mode of DirectInput joystick %d (error %d) - skipping joystick.\n", joyNum, hr);
|
|
|
|
joystick->Release();
|
|
continue;
|
|
}
|
|
|
|
// Turn off deadzone as handled by this class
|
|
dipdw.dwData = 0;
|
|
if (FAILED(hr = joystick->SetProperty(DIPROP_DEADZONE, &dipdw.diph)))
|
|
{
|
|
ErrorLog("Unable to set deadzone of DirectInput joystick %d (error %d) - skipping joystick.\n", joyNum, hr);
|
|
|
|
joystick->Release();
|
|
continue;
|
|
}
|
|
|
|
// Turn off saturation as handle by this class
|
|
dipdw.dwData = 10000;
|
|
if (FAILED(hr = joystick->SetProperty(DIPROP_SATURATION, &dipdw.diph)))
|
|
{
|
|
ErrorLog("Unable to set saturation of DirectInput joystick %d (error %d) - skipping joystick.\n", joyNum, hr);
|
|
|
|
joystick->Release();
|
|
continue;
|
|
}
|
|
|
|
// If joystick has force feedback capabilities then disable auto-center
|
|
if (joyDetails.hasFFeedback)
|
|
{
|
|
dipdw.dwData = FALSE;
|
|
|
|
if (FAILED(hr = joystick->SetProperty(DIPROP_AUTOCENTER, &dipdw.diph)))
|
|
{
|
|
ErrorLog("Unable to unset auto-center of DirectInput joystick %d (error %d) - force feedback will be unavailable for joystick.\n", joyNum, hr);
|
|
|
|
joyDetails.hasFFeedback = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Keep track of DirectInput device number
|
|
it->dInputNum = m_di8Joysticks.size();
|
|
|
|
m_di8Joysticks.push_back(joystick);
|
|
}
|
|
|
|
// Create initial blank joystick state
|
|
DIJOYSTATE2 joyState;
|
|
memset(&joyState, 0, sizeof(DIJOYSTATE2));
|
|
for (int povNum = 0; povNum < 4; povNum++)
|
|
joyState.rgdwPOV[povNum] = -1;
|
|
|
|
m_joyDetails.push_back(joyDetails);
|
|
m_diJoyStates.push_back(joyState);
|
|
}
|
|
}
|
|
|
|
void CDirectInputSystem::ActivateJoysticks()
|
|
{
|
|
// Set DirectInput cooperative level of joysticks
|
|
unsigned joyNum = 0;
|
|
for (vector<DIJoyInfo>::iterator it = m_diJoyInfos.begin(); it != m_diJoyInfos.end(); it++)
|
|
{
|
|
if (!it->isXInput)
|
|
{
|
|
LPDIRECTINPUTDEVICE8 joystick = m_di8Joysticks[it->dInputNum];
|
|
if (m_joyDetails[joyNum].hasFFeedback)
|
|
joystick->SetCooperativeLevel(m_hwnd, DISCL_EXCLUSIVE | DISCL_FOREGROUND);
|
|
else
|
|
joystick->SetCooperativeLevel(m_hwnd, DISCL_NONEXCLUSIVE | DISCL_BACKGROUND);
|
|
}
|
|
joyNum++;
|
|
}
|
|
}
|
|
|
|
void CDirectInputSystem::PollJoysticks()
|
|
{
|
|
// Get current joystick states from XInput and DirectInput
|
|
int i = 0;
|
|
for (vector<DIJoyInfo>::iterator it = m_diJoyInfos.begin(); it != m_diJoyInfos.end(); it++)
|
|
{
|
|
LPDIJOYSTATE2 pJoyState = &m_diJoyStates[i++];
|
|
|
|
HRESULT hr;
|
|
if (it->isXInput)
|
|
{
|
|
// Use XInput to query joystick
|
|
XINPUT_STATE xState;
|
|
memset(&xState, 0, sizeof(XINPUT_STATE));
|
|
if (FAILED(hr = m_xiGetStatePtr(it->xInputNum, &xState)))
|
|
{
|
|
memset(pJoyState, 0, sizeof(DIJOYSTATE2));
|
|
continue;
|
|
}
|
|
|
|
// Map XInput state onto joystick's DirectInput state object
|
|
XINPUT_GAMEPAD gamepad = xState.Gamepad;
|
|
pJoyState->lX = (LONG)gamepad.sThumbLX,
|
|
pJoyState->lY = (LONG)-gamepad.sThumbLY;
|
|
pJoyState->lZ = (LONG)CInputSource::Scale(gamepad.bLeftTrigger, 0, 255, 0, 32767);
|
|
pJoyState->lRx = (LONG)gamepad.sThumbRX;
|
|
pJoyState->lRy = (LONG)-gamepad.sThumbRY;
|
|
pJoyState->lRz = (LONG)CInputSource::Scale(gamepad.bRightTrigger, 0, 255, 0, 32767);
|
|
WORD buttons = gamepad.wButtons;
|
|
BOOL dUp = buttons & XINPUT_GAMEPAD_DPAD_UP;
|
|
BOOL dDown = buttons & XINPUT_GAMEPAD_DPAD_DOWN;
|
|
BOOL dLeft = buttons & XINPUT_GAMEPAD_DPAD_LEFT;
|
|
BOOL dRight = buttons & XINPUT_GAMEPAD_DPAD_RIGHT;
|
|
if (dUp)
|
|
{
|
|
if (dLeft) pJoyState->rgdwPOV[0] = 31500;
|
|
else if (dRight) pJoyState->rgdwPOV[0] = 4500;
|
|
else pJoyState->rgdwPOV[0] = 0;
|
|
}
|
|
else if (dDown)
|
|
{
|
|
if (dLeft) pJoyState->rgdwPOV[0] = 22500;
|
|
else if (dRight) pJoyState->rgdwPOV[0] = 13500;
|
|
else pJoyState->rgdwPOV[0] = 18000;
|
|
}
|
|
else if (dLeft) pJoyState->rgdwPOV[0] = 27000;
|
|
else if (dRight) pJoyState->rgdwPOV[0] = 9000;
|
|
else pJoyState->rgdwPOV[0] = -1;
|
|
pJoyState->rgbButtons[0] = !!(buttons & XINPUT_GAMEPAD_A);
|
|
pJoyState->rgbButtons[1] = !!(buttons & XINPUT_GAMEPAD_B);
|
|
pJoyState->rgbButtons[2] = !!(buttons & XINPUT_GAMEPAD_X);
|
|
pJoyState->rgbButtons[3] = !!(buttons & XINPUT_GAMEPAD_Y);
|
|
pJoyState->rgbButtons[4] = !!(buttons & XINPUT_GAMEPAD_LEFT_SHOULDER);
|
|
pJoyState->rgbButtons[5] = !!(buttons & XINPUT_GAMEPAD_RIGHT_SHOULDER);
|
|
pJoyState->rgbButtons[6] = !!(buttons & XINPUT_GAMEPAD_BACK);
|
|
pJoyState->rgbButtons[7] = !!(buttons & XINPUT_GAMEPAD_START);
|
|
pJoyState->rgbButtons[8] = !!(buttons & XINPUT_GAMEPAD_LEFT_THUMB);
|
|
pJoyState->rgbButtons[9] = !!(buttons & XINPUT_GAMEPAD_RIGHT_THUMB);
|
|
}
|
|
else
|
|
{
|
|
// Use DirectInput to query joystick
|
|
LPDIRECTINPUTDEVICE8 joystick = m_di8Joysticks[it->dInputNum];
|
|
if (FAILED(hr = joystick->Poll()))
|
|
{
|
|
hr = joystick->Acquire();
|
|
while (hr == DIERR_INPUTLOST)
|
|
hr = joystick->Acquire();
|
|
|
|
if (hr == DIERR_OTHERAPPHASPRIO || hr == DIERR_INVALIDPARAM || hr == DIERR_NOTINITIALIZED)
|
|
{
|
|
memset(pJoyState, 0, sizeof(DIJOYSTATE2));
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Update joystick's DirectInput state
|
|
joystick->GetDeviceState(sizeof(DIJOYSTATE2), pJoyState);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CDirectInputSystem::CloseJoysticks()
|
|
{
|
|
// Release any DirectInput force feedback effects that were created
|
|
for (vector<DIJoyInfo>::iterator it = m_diJoyInfos.begin(); it != m_diJoyInfos.end(); it++)
|
|
{
|
|
for (unsigned axisNum = 0; axisNum < NUM_JOY_AXES; axisNum++)
|
|
{
|
|
for (unsigned effNum = 0; effNum < NUM_FF_EFFECTS; effNum++)
|
|
{
|
|
if (it->dInputEffects[axisNum][effNum] != NULL)
|
|
{
|
|
it->dInputEffects[axisNum][effNum]->Release();
|
|
it->dInputEffects[axisNum][effNum] = NULL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Release each DirectInput joystick
|
|
for (vector<LPDIRECTINPUTDEVICE8>::iterator it = m_di8Joysticks.begin(); it != m_di8Joysticks.end(); it++)
|
|
{
|
|
(*it)->Unacquire();
|
|
(*it)->Release();
|
|
}
|
|
|
|
m_joyDetails.clear();
|
|
m_diJoyInfos.clear();
|
|
m_diJoyStates.clear();
|
|
m_di8Joysticks.clear();
|
|
}
|
|
|
|
HRESULT CDirectInputSystem::CreateJoystickEffect(LPDIRECTINPUTDEVICE8 joystick, int axisNum, ForceFeedbackCmd ffCmd, LPDIRECTINPUTEFFECT *pEffect)
|
|
{
|
|
// Map axis number to DI object offset
|
|
DWORD axisOfs;
|
|
switch (axisNum)
|
|
{
|
|
case AXIS_X: axisOfs = DIJOFS_X; break;
|
|
case AXIS_Y: axisOfs = DIJOFS_Y; break;
|
|
case AXIS_Z: axisOfs = DIJOFS_Z; break;
|
|
case AXIS_RX: axisOfs = DIJOFS_RX; break;
|
|
case AXIS_RY: axisOfs = DIJOFS_RY; break;
|
|
case AXIS_RZ: axisOfs = DIJOFS_RZ; break;
|
|
default: return E_FAIL;
|
|
}
|
|
|
|
DWORD rgdwAxes[1] = { axisOfs };
|
|
LONG rglDirection[1] = { 0 };
|
|
DICONSTANTFORCE dicf;
|
|
DICONDITION dic;
|
|
DIPERIODIC dip;
|
|
DIENVELOPE die;
|
|
GUID guid;
|
|
|
|
// Set common effects parameters
|
|
DIEFFECT eff;
|
|
memset(&eff, 0, sizeof(eff));
|
|
eff.dwSize = sizeof(DIEFFECT);
|
|
eff.dwFlags = DIEFF_CARTESIAN | DIEFF_OBJECTOFFSETS;
|
|
eff.dwTriggerButton = DIEB_NOTRIGGER;
|
|
eff.dwTriggerRepeatInterval = 0;
|
|
eff.dwGain = DI_FFNOMINALMAX;
|
|
eff.cAxes = 1;
|
|
eff.rgdwAxes = rgdwAxes;
|
|
eff.rglDirection = rglDirection;
|
|
eff.dwDuration = INFINITE;
|
|
eff.dwStartDelay = 0;
|
|
|
|
// Set specific effects parameters
|
|
switch (ffCmd.id)
|
|
{
|
|
case FFStop:
|
|
return E_FAIL;
|
|
|
|
case FFSelfCenter:
|
|
guid = GUID_Spring;
|
|
|
|
dic.lOffset = 0;
|
|
dic.lPositiveCoefficient = 0;
|
|
dic.lNegativeCoefficient = 0;
|
|
dic.dwPositiveSaturation = DI_FFNOMINALMAX;
|
|
dic.dwNegativeSaturation = DI_FFNOMINALMAX;
|
|
dic.lDeadBand = (LONG)0.05 * DI_FFNOMINALMAX;
|
|
|
|
eff.lpEnvelope = NULL;
|
|
eff.cbTypeSpecificParams = sizeof(DICONDITION);
|
|
eff.lpvTypeSpecificParams = &dic;
|
|
break;
|
|
|
|
case FFConstantForce:
|
|
guid = GUID_ConstantForce;
|
|
|
|
dicf.lMagnitude = 0;
|
|
|
|
eff.lpEnvelope = NULL;
|
|
eff.cbTypeSpecificParams = sizeof(DICONSTANTFORCE);
|
|
eff.lpvTypeSpecificParams = &dicf;
|
|
break;
|
|
|
|
case FFVibrate:
|
|
guid = GUID_Sine;
|
|
|
|
dip.dwMagnitude = 0;
|
|
dip.lOffset = 0;
|
|
dip.dwPhase = 0;
|
|
dip.dwPeriod = (DWORD)0.1 * DI_SECONDS; // 1/10th second
|
|
|
|
die.dwSize = sizeof(die);
|
|
die.dwAttackLevel = 0;
|
|
die.dwAttackTime = (DWORD)0.5 * DI_SECONDS;
|
|
die.dwFadeLevel = 0;
|
|
die.dwFadeTime = (DWORD)0.5 * DI_SECONDS;
|
|
|
|
eff.lpEnvelope = ¨
|
|
eff.cbTypeSpecificParams = sizeof(DIPERIODIC);
|
|
eff.lpvTypeSpecificParams = &dip;
|
|
break;
|
|
}
|
|
|
|
joystick->Acquire();
|
|
|
|
HRESULT hr;
|
|
if (FAILED(hr = joystick->CreateEffect(guid, &eff, pEffect, NULL)))
|
|
return hr;
|
|
if (*pEffect == NULL)
|
|
return E_FAIL;
|
|
(*pEffect)->Start(1, 0);
|
|
return S_OK;
|
|
}
|
|
|
|
bool CDirectInputSystem::InitializeSystem()
|
|
{
|
|
if (m_useRawInput)
|
|
{
|
|
// Dynamically load RawInput API
|
|
HMODULE user32 = LoadLibrary(TEXT("user32.dll"));
|
|
if (user32 != NULL)
|
|
{
|
|
m_getRIDevListPtr = (GetRawInputDeviceListPtr)GetProcAddress(user32, "GetRawInputDeviceList");
|
|
m_getRIDevInfoPtr = (GetRawInputDeviceInfoPtr)GetProcAddress(user32, "GetRawInputDeviceInfoA");
|
|
m_regRIDevsPtr = (RegisterRawInputDevicesPtr)GetProcAddress(user32, "RegisterRawInputDevices");
|
|
m_getRIDataPtr = (GetRawInputDataPtr)GetProcAddress(user32, "GetRawInputData");
|
|
m_useRawInput = m_getRIDevListPtr != NULL && m_getRIDevInfoPtr != NULL && m_regRIDevsPtr != NULL && m_getRIDataPtr != NULL;
|
|
}
|
|
else
|
|
m_useRawInput = false;
|
|
|
|
if (m_useRawInput)
|
|
{
|
|
// Get screen resolution (needed for absolute mouse devices)
|
|
DEVMODEA settings;
|
|
if (!EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, &settings))
|
|
{
|
|
ErrorLog("Unable to read current display settings\n");
|
|
return false;
|
|
}
|
|
m_screenW = settings.dmPelsWidth;
|
|
m_screenH = settings.dmPelsHeight;
|
|
}
|
|
else
|
|
ErrorLog("Unable to initialize RawInput API (library hooks are not available) - switching to DirectInput.\n");
|
|
}
|
|
|
|
if (m_useXInput)
|
|
{
|
|
// Dynamically load XInput API
|
|
HMODULE xInput = LoadLibrary(TEXT(XINPUT_DLL_A));
|
|
if (xInput != NULL)
|
|
{
|
|
m_xiGetCapabilitiesPtr = (XInputGetCapabilitiesPtr)GetProcAddress(xInput, "XInputGetCapabilities");
|
|
m_xiGetStatePtr = (XInputGetStatePtr)GetProcAddress(xInput, "XInputGetState");
|
|
m_xiSetStatePtr = (XInputSetStatePtr)GetProcAddress(xInput, "XInputSetState");
|
|
m_useXInput = m_xiGetCapabilitiesPtr != NULL && m_xiGetStatePtr != NULL && m_xiSetStatePtr != NULL;
|
|
}
|
|
else
|
|
m_useXInput = false;
|
|
|
|
if (!m_useXInput)
|
|
ErrorLog("Unable to initialize XInput API (library hooks are not available) - switching to DirectInput.\n");
|
|
}
|
|
|
|
// Dynamically create DirectInput8 via COM, rather than statically linking to dinput8.dll
|
|
// TODO - if fails, try older versions of DirectInput
|
|
HRESULT hr;
|
|
if (SUCCEEDED(hr = CoInitialize(NULL)))
|
|
m_initializedCOM = true;
|
|
else
|
|
{
|
|
// CoInitialize fails if called from managed context (ie .NET debugger) so check for this and ignore this error
|
|
if (hr != RPC_E_CHANGED_MODE)
|
|
{
|
|
ErrorLog("Unable to initialize COM (error %d).\n", hr);
|
|
|
|
return false;
|
|
}
|
|
}
|
|
if (FAILED(hr = CoCreateInstance(CLSID_DirectInput8, NULL, CLSCTX_INPROC_SERVER, IID_IDirectInput8A, (LPVOID*)&m_di8)))
|
|
{
|
|
ErrorLog("Unable to initialize DirectInput API (error %d) - is DirectX 8 or later installed?\n", hr);
|
|
|
|
if (m_initializedCOM)
|
|
CoUninitialize();
|
|
return false;
|
|
}
|
|
if (FAILED(hr = m_di8->Initialize(GetModuleHandle(NULL), DIRECTINPUT_VERSION)))
|
|
{
|
|
ErrorLog("Unable to initialize DirectInput API (error %d) - is DirectX 8 or later installed?\n", hr);
|
|
|
|
m_di8->Release();
|
|
m_di8 = NULL;
|
|
if (m_initializedCOM)
|
|
CoUninitialize();
|
|
return false;
|
|
}
|
|
|
|
// Open all devices
|
|
OpenKeyboardsAndMice();
|
|
OpenJoysticks();
|
|
|
|
return true;
|
|
}
|
|
|
|
int CDirectInputSystem::GetKeyIndex(const char *keyName)
|
|
{
|
|
for (int i = 0; i < NUM_DI_KEYS; i++)
|
|
{
|
|
if (stricmp(keyName, s_keyMap[i].keyName) == 0)
|
|
return i;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
const char *CDirectInputSystem::GetKeyName(int keyIndex)
|
|
{
|
|
if (keyIndex < 0 || keyIndex >= NUM_DI_KEYS)
|
|
return NULL;
|
|
return s_keyMap[keyIndex].keyName;
|
|
}
|
|
|
|
bool CDirectInputSystem::IsKeyPressed(int kbdNum, int keyIndex)
|
|
{
|
|
// Get DI key code (scancode) for given key index
|
|
int diKey = s_keyMap[keyIndex].diKey;
|
|
|
|
if (m_useRawInput)
|
|
{
|
|
// For RawInput, check if key is currently pressed for given keyboard number
|
|
BOOL *keyState = m_rawKeyStates[kbdNum];
|
|
return !!keyState[diKey];
|
|
}
|
|
|
|
// For DirectInput, just check common keyboard state
|
|
return !!(m_diKeyState[diKey] & 0x80);
|
|
}
|
|
|
|
int CDirectInputSystem::GetMouseAxisValue(int mseNum, int axisNum)
|
|
{
|
|
if (m_useRawInput)
|
|
{
|
|
// For RawInput, get combined or individual mouse state and return value for given axis
|
|
// The cursor is always hidden when using RawInput, so it does not matter if these values don't match with the cursor (with multiple
|
|
// mice the cursor is irrelevant anyway)
|
|
RawMseState *pMseState = (mseNum == ANY_MOUSE ? &m_combRawMseState : &m_rawMseStates[mseNum]);
|
|
switch (axisNum)
|
|
{
|
|
case AXIS_X: return pMseState->x;
|
|
case AXIS_Y: return pMseState->y;
|
|
case AXIS_Z: return pMseState->z;
|
|
default: return 0;
|
|
}
|
|
}
|
|
|
|
// For DirectInput, for X- and Y-axes just use cursor position within window if available (so that mouse movements sync with the cursor)
|
|
if (axisNum == AXIS_X || axisNum == AXIS_Y)
|
|
{
|
|
POINT p;
|
|
if (GetCursorPos(&p) && ScreenToClient(m_hwnd, &p))
|
|
return (axisNum == AXIS_X ? p.x : p.y);
|
|
}
|
|
|
|
// Otherwise, return the raw DirectInput axis values
|
|
switch (axisNum)
|
|
{
|
|
case AXIS_X: return m_diMseState.x;
|
|
case AXIS_Y: return m_diMseState.y;
|
|
case AXIS_Z: return m_diMseState.z;
|
|
default: return 0;
|
|
}
|
|
}
|
|
|
|
int CDirectInputSystem::GetMouseWheelDir(int mseNum)
|
|
{
|
|
if (m_useRawInput)
|
|
{
|
|
// For RawInput, return the wheel value for combined or individual mouse state
|
|
return (mseNum == ANY_MOUSE ? m_combRawMseState.wheelDir : m_rawMseStates[mseNum].wheelDir);
|
|
}
|
|
|
|
// For DirectInput just return the common wheel value
|
|
return m_diMseState.wheelDir;
|
|
}
|
|
|
|
bool CDirectInputSystem::IsMouseButPressed(int mseNum, int butNum)
|
|
{
|
|
if (m_useRawInput)
|
|
{
|
|
// For RawInput, return the button state for combined or individual mouse state
|
|
return !!((mseNum == ANY_MOUSE ? m_combRawMseState.buttons : m_rawMseStates[mseNum].buttons) & (1<<butNum));
|
|
}
|
|
|
|
// For DirectInput just return the common button state (taking care with the middle and right mouse buttons
|
|
// which DirectInput numbers 2 and 1 respectively, rather than 1 and 2)
|
|
if (butNum == 1) butNum = 2;
|
|
else if (butNum == 2) butNum = 1;
|
|
return (butNum < 5 ? !!(m_diMseState.buttons[butNum] & 0x80) : false);
|
|
}
|
|
|
|
int CDirectInputSystem::GetJoyAxisValue(int joyNum, int axisNum)
|
|
{
|
|
// Return raw value for given joystick number and axis (values range from -32768 to 32767)
|
|
switch (axisNum)
|
|
{
|
|
case AXIS_X: return (int)m_diJoyStates[joyNum].lX;
|
|
case AXIS_Y: return (int)m_diJoyStates[joyNum].lY;
|
|
case AXIS_Z: return (int)m_diJoyStates[joyNum].lZ;
|
|
case AXIS_RX: return (int)m_diJoyStates[joyNum].lRx;
|
|
case AXIS_RY: return (int)m_diJoyStates[joyNum].lRy;
|
|
case AXIS_RZ: return (int)m_diJoyStates[joyNum].lRz;
|
|
default: return 0;
|
|
}
|
|
}
|
|
|
|
bool CDirectInputSystem::IsJoyPOVInDir(int joyNum, int povNum, int povDir)
|
|
{
|
|
// Check if POV-hat value for given joystick number and POV is pointing in required direction
|
|
int povVal = m_diJoyStates[joyNum].rgdwPOV[povNum] / 100; // DirectInput value is angle of POV-hat in 100ths of a degree
|
|
switch (povDir)
|
|
{
|
|
case POV_UP: return povVal == 315 || povVal == 0 || povVal == 45;
|
|
case POV_DOWN: return povVal == 135 || povVal == 180 || povVal == 225;
|
|
case POV_RIGHT: return povVal == 45 || povVal == 90 || povVal == 135;
|
|
case POV_LEFT: return povVal == 225 || povVal == 270 || povVal == 315;
|
|
default: return false;
|
|
}
|
|
}
|
|
|
|
bool CDirectInputSystem::IsJoyButPressed(int joyNum, int butNum)
|
|
{
|
|
// Get joystick state for given joystick and return current button value for given button number
|
|
return !!m_diJoyStates[joyNum].rgbButtons[butNum];
|
|
}
|
|
|
|
bool CDirectInputSystem::ProcessForceFeedbackCmd(int joyNum, int axisNum, ForceFeedbackCmd ffCmd)
|
|
{
|
|
DIJoyInfo *pInfo = &m_diJoyInfos[joyNum];
|
|
|
|
HRESULT hr;
|
|
if (pInfo->isXInput)
|
|
{
|
|
XINPUT_VIBRATION vibration;
|
|
switch (ffCmd.id)
|
|
{
|
|
case FFStop:
|
|
if (axisNum == AXIS_X || axisNum == AXIS_Y)
|
|
vibration.wLeftMotorSpeed = 0;
|
|
else if (axisNum == AXIS_RX || axisNum == AXIS_RY)
|
|
vibration.wRightMotorSpeed = 0;
|
|
else
|
|
return false;
|
|
return SUCCEEDED(hr = m_xiSetStatePtr(pInfo->xInputNum, &vibration));
|
|
|
|
case FFConstantForce:
|
|
if (axisNum == AXIS_X || axisNum == AXIS_Y)
|
|
vibration.wLeftMotorSpeed = ffCmd.data; // TODO - scale data to 0-65535
|
|
else if (axisNum == AXIS_RX || axisNum == AXIS_RY)
|
|
vibration.wRightMotorSpeed = ffCmd.data; // TODO - scale data to 0-65535
|
|
else
|
|
return false;
|
|
return SUCCEEDED(hr = m_xiSetStatePtr(pInfo->xInputNum, &vibration));
|
|
|
|
default:
|
|
// TODO - other force feedback types
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LPDIRECTINPUTDEVICE8 joystick = m_di8Joysticks[pInfo->dInputNum];
|
|
|
|
// See if command is to stop all force feedback
|
|
if (ffCmd.id == -1)
|
|
return SUCCEEDED(hr = joystick->SendForceFeedbackCommand(DISFFC_STOPALL));
|
|
|
|
// Create effect for given axis if has not already been created
|
|
int effNum = (int)ffCmd.id;
|
|
LPDIRECTINPUTEFFECT *pEffect = &pInfo->dInputEffects[axisNum][effNum];
|
|
if ((*pEffect) == NULL)
|
|
{
|
|
if (FAILED(hr = CreateJoystickEffect(joystick, axisNum, ffCmd, pEffect)))
|
|
return false;
|
|
|
|
}
|
|
|
|
LONG rglDirection[1] = { 0 };
|
|
DICONSTANTFORCE dicf;
|
|
DICONDITION dic;
|
|
DIPERIODIC dip;
|
|
DIENVELOPE die;
|
|
|
|
// Set common parameters
|
|
DIEFFECT eff;
|
|
memset(&eff, 0, sizeof(eff));
|
|
eff.dwSize = sizeof(DIEFFECT);
|
|
eff.dwFlags = DIEFF_CARTESIAN | DIEFF_OBJECTOFFSETS;
|
|
eff.cAxes = 1;
|
|
eff.rglDirection = rglDirection;
|
|
eff.dwStartDelay = 0;
|
|
|
|
switch (ffCmd.id)
|
|
{
|
|
case FFSelfCenter:
|
|
dic.lPositiveCoefficient = ffCmd.data;
|
|
dic.lNegativeCoefficient = ffCmd.data;
|
|
dic.dwPositiveSaturation = DI_FFNOMINALMAX;
|
|
dic.dwNegativeSaturation = DI_FFNOMINALMAX;
|
|
dic.lDeadBand = (LONG)0.05 * DI_FFNOMINALMAX;
|
|
|
|
eff.lpEnvelope = NULL;
|
|
eff.cbTypeSpecificParams = sizeof(DICONDITION);
|
|
eff.lpvTypeSpecificParams = &dic;
|
|
break;
|
|
|
|
case FFConstantForce:
|
|
dicf.lMagnitude = ffCmd.data; // TODO - scale & cap at +/- DI_FFNOMINALMAX
|
|
|
|
eff.lpEnvelope = NULL;
|
|
eff.cbTypeSpecificParams = sizeof(DICONSTANTFORCE);
|
|
eff.lpvTypeSpecificParams = &dicf;
|
|
break;
|
|
|
|
case FFVibrate:
|
|
dip.dwMagnitude = ffCmd.data;
|
|
dip.lOffset = 0;
|
|
dip.dwPhase = 0;
|
|
dip.dwPeriod = (DWORD)0.1 * DI_SECONDS; // 1/10th second
|
|
|
|
die.dwSize = sizeof(die);
|
|
die.dwAttackLevel = 0;
|
|
die.dwAttackTime = (DWORD)0.5 * DI_SECONDS;
|
|
die.dwFadeLevel = 0;
|
|
die.dwFadeTime = (DWORD)0.5 * DI_SECONDS;
|
|
|
|
eff.lpEnvelope = ¨
|
|
eff.cbTypeSpecificParams = sizeof(DIPERIODIC);
|
|
eff.lpvTypeSpecificParams = &dip;
|
|
break;
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
// Now set the new parameters and start the effect immediately.
|
|
return SUCCEEDED(hr = (*pEffect)->SetParameters(&eff, DIEP_DIRECTION | DIEP_TYPESPECIFICPARAMS | DIEP_START));
|
|
}
|
|
}
|
|
|
|
void CDirectInputSystem::Wait(int ms)
|
|
{
|
|
Sleep(ms);
|
|
}
|
|
|
|
bool CDirectInputSystem::ConfigMouseCentered()
|
|
{
|
|
// When checking if mouse centered, use system cursor rather than raw values (otherwise user's mouse movements won't match up
|
|
// with onscreen cursor during configuration)
|
|
POINT p;
|
|
if (!GetCursorPos(&p) || !ScreenToClient(m_hwnd, &p))
|
|
return false;
|
|
|
|
// See if mouse in center of display
|
|
unsigned lx = m_dispX + m_dispW / 4;
|
|
unsigned ly = m_dispY + m_dispH / 4;
|
|
if (p.x < (LONG)lx || p.x > (LONG)(lx + m_dispW / 2) || p.y < (LONG)ly || p.y > (LONG)(ly + m_dispH / 2))
|
|
return false;
|
|
|
|
// Once mouse has been centered, sync up mice raw values with current cursor position so that movements are detected correctly
|
|
ResetMice();
|
|
return true;
|
|
}
|
|
|
|
CInputSource *CDirectInputSystem::CreateAnyMouseSource(EMousePart msePart)
|
|
{
|
|
// If using RawInput, create a mouse source that uses the combined mouse state m_combRawState, rather than combining all the individual mouse
|
|
// sources in the default manner
|
|
if (m_useRawInput)
|
|
return CreateMouseSource(ANY_MOUSE, msePart);
|
|
|
|
return CInputSystem::CreateAnyMouseSource(msePart);
|
|
}
|
|
|
|
int CDirectInputSystem::GetNumKeyboards()
|
|
{
|
|
// If RawInput enabled, then return number of keyboards found. Otherwise, return ANY_KEYBOARD as DirectInput cannot handle multiple keyboards
|
|
return (m_useRawInput ? m_rawKeyboards.size() : ANY_KEYBOARD);
|
|
}
|
|
|
|
int CDirectInputSystem::GetNumMice()
|
|
{
|
|
// If RawInput enabled, then return number of mice found. Otherwise, return ANY_MOUSE as DirectInput cannot handle multiple keyboards
|
|
return (m_useRawInput ? m_rawMice.size() : ANY_MOUSE);
|
|
}
|
|
|
|
int CDirectInputSystem::GetNumJoysticks()
|
|
{
|
|
// Return number of joysticks found
|
|
return m_diJoyInfos.size();
|
|
}
|
|
|
|
const KeyDetails *CDirectInputSystem::GetKeyDetails(int kbdNum)
|
|
{
|
|
// If RawInput enabled, then return details for given keyboard. Otherwise, return NULL as DirectInput cannot handle multiple keyboards
|
|
return (m_useRawInput ? &m_keyDetails[kbdNum] : NULL);
|
|
}
|
|
|
|
const MouseDetails *CDirectInputSystem::GetMouseDetails(int mseNum)
|
|
{
|
|
// If RawInput enabled, then return details of given mouse. Otherwise, return NULL as DirectInput cannot handle multiple keyboards
|
|
return (m_useRawInput ? &m_mseDetails[mseNum] : NULL);
|
|
}
|
|
|
|
const JoyDetails *CDirectInputSystem::GetJoyDetails(int joyNum)
|
|
{
|
|
return &m_joyDetails[joyNum];
|
|
}
|
|
|
|
bool CDirectInputSystem::Poll()
|
|
{
|
|
// See if keyboard, mice and joysticks have been activated yet
|
|
if (!m_activated)
|
|
{
|
|
// If not, then get Window handle of SDL window
|
|
SDL_SysWMinfo info;
|
|
SDL_VERSION(&info.version);
|
|
if (SDL_GetWMInfo(&info))
|
|
m_hwnd = info.window;
|
|
|
|
// Tell SDL to pass on all Windows events
|
|
// Removed - see below
|
|
//SDL_EventState(SDL_SYSWMEVENT, SDL_ENABLE);
|
|
|
|
// Activate the devices now that a Window handle is available
|
|
ActivateKeyboardsAndMice();
|
|
ActivateJoysticks();
|
|
|
|
m_activated = true;
|
|
}
|
|
|
|
// Wait or poll for event from SDL
|
|
// Removed - see below
|
|
/*
|
|
SDL_Event e;
|
|
while (SDL_PollEvent(&e))
|
|
{
|
|
if (e.type == SDL_QUIT)
|
|
return false;
|
|
else if (e.type == SDL_SYSWMEVENT)
|
|
{
|
|
SDL_SysWMmsg *wmMsg = e.syswm.msg;
|
|
ProcessWMEvent(wmMsg->hwnd, wmMsg->msg, wmMsg->wParam, wmMsg->lParam);
|
|
}
|
|
}*/
|
|
|
|
// Wait or poll for event on Windows message queue (done this way instead of using SDL_PollEvent as above because
|
|
// for some reason this causes RawInput HRAWINPUT handles to arrive stale. Not sure what SDL_PollEvent is doing to cause this
|
|
// but the following code can replace it without any problems as it is effectively what SDL_PollEvent does anyway)
|
|
MSG msg;
|
|
while (PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE))
|
|
{
|
|
int ret = GetMessage(&msg, NULL, 0, 0);
|
|
if (ret == 0)
|
|
return false;
|
|
else if (ret > 0)
|
|
{
|
|
TranslateMessage(&msg);
|
|
|
|
// Handle RawInput messages here
|
|
if (m_useRawInput && msg.message == WM_INPUT)
|
|
ProcessRawInput((HRAWINPUT)msg.lParam);
|
|
|
|
// Propagate all messages to default (SDL) handlers
|
|
DispatchMessage(&msg);
|
|
}
|
|
}
|
|
|
|
// Poll keyboards, mice and joysticks
|
|
PollKeyboardsAndMice();
|
|
PollJoysticks();
|
|
|
|
return true;
|
|
}
|
|
|
|
void CDirectInputSystem::SetMouseVisibility(bool visible)
|
|
{
|
|
if (m_useRawInput)
|
|
ShowCursor(!m_grabMouse && visible ? TRUE : FALSE);
|
|
else
|
|
ShowCursor(visible ? TRUE : FALSE);
|
|
}
|
|
|
|
void CDirectInputSystem::GrabMouse()
|
|
{
|
|
CInputSystem::GrabMouse();
|
|
|
|
SetMouseVisibility(false);
|
|
|
|
// When grabbing mouse, make sure devices get re-activated
|
|
ActivateKeyboardsAndMice();
|
|
ActivateJoysticks();
|
|
}
|
|
|
|
void CDirectInputSystem::UngrabMouse()
|
|
{
|
|
CInputSystem::UngrabMouse();
|
|
|
|
// When ungrabbing mouse place, make sure devices get re-activated
|
|
ActivateKeyboardsAndMice();
|
|
ActivateJoysticks();
|
|
}
|