Supermodel/Src/OSD/Windows/DirectInputSystem.cpp
Nik Henson 8835d6fe36 Some updates to Supermodel made at beginning of the year but only now got around to checking in (better late than never...):
- hooked up the remaining controls in Supermodel (except for Magical Truck Adventure which does not work at all yet).  The new controls are:
  * InputAnalogJoyTrigger2 and InputAnalogJoyEvent2 for the additional second trigger and event buttons that were missing from Star Wars Trilogy,
  * InputRearBrake and InputMusicSelect for the rear brake and music selection buttons that were missing from Harley Davidson,
  * InputAnalogGunXXX, InputAnalogTriggerXXX, InputAnalogGunXXX2 and InputAnalogTriggerXXX2 for the analogue guns of Ocean Hunter and LA Machineguns (NOTE: these controls must be calibrated in the games' service menus otherwise they will not work properly.  Also, the alignment of the gun cursor does not line up very well with the mouse position at the moment, but at least the games are a bit more playable now, although still with numerous graphical glitches...)
  * InputSkiXXX for the controls of Ski Champ, making the game playable now.
- hooked up existing InputViewChange control to Harley Davidson's view change button
- improved the handling of InputGearShiftUp/Down inputs so that they work better with the driving games.  With Dirt Devils, ECA, Harley and LeMans this means they map directly to the game's own shift up/down controls, while with the 4-speed games such as Daytona 2, Scud Racer and Sega Rally 2, they simulate the user shifting up and down through the gears
- added defaults for the new controls to Supermodel.ini
- other small code tweaks:
  * fix small bug with handling of pos/neg inputs mapping to a control with inverted range (0XFF to 0x00) - this was needed to get Ski Champ's X-axis to work properly
  * removed Wait method from InputSystem and added to CThread as CThread::Sleep instead
  * added FrameTimings struct to hold all frame timings in a single place
No networking code yet as just haven't had a chance to work on it since initial progress at the beginning of the year - am *hoping* might have some time to pick it up again over Christmas...
2013-11-30 19:39:59 +00:00

2128 lines
68 KiB
C++

/**
** Supermodel
** A Sega Model 3 Arcade Emulator.
** Copyright 2011 Bart Trzynadlowski, Nik Henson
**
** This file is part of Supermodel.
**
** Supermodel 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 3 of the License, or (at your option)
** any later version.
**
** Supermodel 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 Supermodel. If not, see <http://www.gnu.org/licenses/>.
**/
/*
* DirectInputSystem.cpp
*
* Implementation of the DirectInput-based input system. Also provides support
* for XInput and Raw Input.
*/
#include "DirectInputSystem.h"
#include "Supermodel.h"
#include <wbemidl.h>
#include <oleauto.h>
#include <SDL.h>
#include <SDL_syswm.h>
/*
* MinGW compatibility: the XInput.h distributed with MinGW is missing these
* definitions. I've copied them from the Microsoft DirectX SDK. If a proper
* header file appears, this hack should be removed.
*/
#if !defined(XINPUT_DLL_A)
#ifndef XINPUT_USE_9_1_0
#define XINPUT_DLL_A "xinput1_3.dll"
#define XINPUT_DLL_W L"xinput1_3.dll"
#else
#define XINPUT_DLL_A "xinput9_1_0.dll"
#define XINPUT_DLL_W L"xinput9_1_0.dll"
#endif
#ifdef UNICODE
#define XINPUT_DLL XINPUT_DLL_W
#else
#define XINPUT_DLL XINPUT_DLL_A
#endif
#endif // XINPUT_DLL_A
// 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", ?? },
};
static 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(CLSID_WbemLocator, NULL, CLSCTX_INPROC_SERVER, IID_IWbemLocator, (LPVOID*)&pIWbemLocator); // this version does not use __uuidof() and works w/ gcc
//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;
}
struct DIEnumDevsContext
{
vector<DIJoyInfo> *infos;
bool useXInput;
};
static BOOL CALLBACK DI8EnumDevicesCallback(LPCDIDEVICEINSTANCE instance, LPVOID context)
{
DIEnumDevsContext *diDevsContext = (DIEnumDevsContext*)context;
// Keep track of all joystick device GUIDs
DIJoyInfo info;
memset(&info, 0, sizeof(info));
info.guid = instance->guidInstance;
// If XInput is enabled, see if device is an XInput device
info.isXInput = diDevsContext->useXInput && IsXInputDevice(instance->guidProduct);
diDevsContext->infos->push_back(info);
return DIENUM_CONTINUE;
}
struct DIEnumObjsContext
{
JoyDetails *joyDetails;
unsigned sliderCount;
bool enumError;
};
static BOOL CALLBACK DI8EnumObjectsCallback(LPCDIDEVICEOBJECTINSTANCE instance, LPVOID context)
{
DIEnumObjsContext *diObjsContext = (DIEnumObjsContext*)context;
// Get data format for object
int objNum = DIDFT_GETINSTANCE(instance->dwType);
DIOBJECTDATAFORMAT fmt = c_dfDIJoystick2.rgodf[objNum];
// Work out which axis or slider is currently being enumerated from the GUID
int axisNum;
if (instance->guidType == GUID_XAxis) axisNum = AXIS_X;
else if (instance->guidType == GUID_YAxis) axisNum = AXIS_Y;
else if (instance->guidType == GUID_ZAxis) axisNum = AXIS_Z;
else if (instance->guidType == GUID_RxAxis) axisNum = AXIS_RX;
else if (instance->guidType == GUID_RyAxis) axisNum = AXIS_RY;
else if (instance->guidType == GUID_RzAxis) axisNum = AXIS_RZ;
else if (instance->guidType == GUID_Slider)
{
// Work out which slider from count
switch (diObjsContext->sliderCount++)
{
case 0: axisNum = AXIS_S1; break;
case 1: axisNum = AXIS_S2; break;
default:
// If couldn't match then ignore slider
return DIENUM_CONTINUE;
}
}
else if (instance->dwType & DIDFT_AXIS)
{
// If is an axis but couldn't match GUID above (which, according to MSDN, is an optional attribute), then flag error and try matching via offset
int objNum = DIDFT_GETINSTANCE(instance->dwType);
DIOBJECTDATAFORMAT fmt = c_dfDIJoystick2.rgodf[objNum];
diObjsContext->enumError = true;
#ifdef _MSC_VER // MS VisualC++
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:
// If still couldn't match then it is not an axis
return DIENUM_CONTINUE;
}
#else // GCC
// DIJOFS_* are not technically constants (at least in the MinGW dinput.h that I'm using)
if (DIJOFS_X == fmt.dwOfs) axisNum = AXIS_X;
else if (DIJOFS_Y == fmt.dwOfs) axisNum = AXIS_Y;
else if (DIJOFS_Z == fmt.dwOfs) axisNum = AXIS_Z;
else if (DIJOFS_Z == fmt.dwOfs) axisNum = AXIS_Z;
else if (DIJOFS_RX == fmt.dwOfs) axisNum = AXIS_RX;
else if (DIJOFS_RY == fmt.dwOfs) axisNum = AXIS_RY;
else if (DIJOFS_RZ == fmt.dwOfs) axisNum = AXIS_RZ;
else
// If still couldn't match then it is not an axis
return DIENUM_CONTINUE;
#endif
}
else
{
// Ignore all other types of object
return DIENUM_CONTINUE;
}
// If axis overlaps with a previous one, flag error
JoyDetails *joyDetails = diObjsContext->joyDetails;
if (joyDetails->hasAxis[axisNum])
diObjsContext->enumError = true;
// Record fact that axis is present and also whether it has force feedback available
joyDetails->hasAxis[axisNum] = true;
joyDetails->axisHasFF[axisNum] = !!(instance->dwFlags & DIDOI_FFACTUATOR);
// Get axis name from DirectInput and store that too
char *axisName = joyDetails->axisName[axisNum];
strcpy(axisName, CInputSystem::GetDefaultAxisName(axisNum));
strcat(axisName, " (");
strncat(axisName, instance->tszName, MAX_NAME_LENGTH - strlen(axisName) - 1);
strcat(axisName, ")");
return DIENUM_CONTINUE;
}
static BOOL CALLBACK DI8EnumEffectsCallback(LPCDIEFFECTINFO effectInfo, LPVOID context)
{
// Check joystick has at least one of required types of effects
JoyDetails *joyDetails = (JoyDetails*)context;
if (!!(effectInfo->dwEffType & (DIEFT_CONSTANTFORCE | DIEFT_PERIODIC | DIEFT_CONDITION)))
joyDetails->hasFFeedback = true;
return DIENUM_CONTINUE;
}
const char *CDirectInputSystem::ConstructName(bool useRawInput, bool useXInput)
{
if (useRawInput)
return (useXInput ? "RawInput/XInput" : "RawInput/DirectInput");
else
return (useXInput ? "Xinput" : "DirectInput");
}
CDirectInputSystem::CDirectInputSystem(bool useRawInput, bool useXInput) :
CInputSystem(ConstructName(useRawInput, useXInput)),
m_useRawInput(useRawInput), m_useXInput(useXInput), m_enableFFeedback(true),
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(m_combRawMseState));
memset(&m_diKeyState, 0, sizeof(m_diKeyState));
memset(&m_diMseState, 0, sizeof(m_diMseState));
}
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, &regKey);
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, &regKey);
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(mseState));
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->Unacquire();
m_di8Keyboard->SetCooperativeLevel(m_hwnd, (m_grabMouse ? DISCL_FOREGROUND : DISCL_BACKGROUND) | DISCL_NONEXCLUSIVE);
m_di8Keyboard->Acquire();
}
if (m_di8Mouse != NULL)
{
m_di8Mouse->Unacquire();
m_di8Mouse->SetCooperativeLevel(m_hwnd, (m_grabMouse ? DISCL_FOREGROUND : DISCL_BACKGROUND) | DISCL_NONEXCLUSIVE);
m_di8Mouse->Acquire();
}
}
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
int 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 diDevsContext;
diDevsContext.infos = &m_diJoyInfos;
diDevsContext.useXInput = m_useXInput;
HRESULT hr;
if (FAILED(hr = m_di8->EnumDevices(DI8DEVCLASS_GAMECTRL, DI8EnumDevicesCallback, &diDevsContext, 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.hasAxis[AXIS_S1] = false;
joyDetails.hasAxis[AXIS_S2] = false;
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;
joyDetails.axisHasFF[AXIS_S1] = false;
joyDetails.axisHasFF[AXIS_S2] = 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
DIEnumObjsContext diObjsContext;
memset(&diObjsContext, 0, sizeof(diObjsContext));
diObjsContext.joyDetails = &joyDetails;
if (FAILED(hr = joystick->EnumObjects(DI8EnumObjectsCallback, &diObjsContext, DIDFT_ALL)))
{
ErrorLog("Unable to enumerate axes of DirectInput joystick %d (error %d) - skipping joystick.\n", joyNum, hr);
joystick->Release();
continue;
}
// If enumeration failed for some reason then include all possible joystick axes so that no axis is left off due to error
if (diObjsContext.enumError)
{
for (int axisNum = 0; axisNum < NUM_JOY_AXES; axisNum++)
{
if (!joyDetails.hasAxis[axisNum])
{
joyDetails.hasAxis[axisNum] = true;
joyDetails.axisHasFF[axisNum] = false;
char *axisName = joyDetails.axisName[axisNum];
strcpy(axisName, CInputSystem::GetDefaultAxisName(axisNum));
}
}
}
// Count number of axes
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(joyState));
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];
joystick->Unacquire();
if (m_grabMouse)
joystick->SetCooperativeLevel(m_hwnd, DISCL_EXCLUSIVE | DISCL_FOREGROUND);
else
joystick->SetCooperativeLevel(m_hwnd, DISCL_NONEXCLUSIVE | DISCL_BACKGROUND);
joystick->Acquire();
}
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(xState));
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;
int dUp = (buttons & XINPUT_GAMEPAD_DPAD_UP);
int dDown = (buttons & XINPUT_GAMEPAD_DPAD_DOWN);
int dLeft = (buttons & XINPUT_GAMEPAD_DPAD_LEFT);
int 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;
case AXIS_S1: axisOfs = DIJOFS_SLIDER(0); break;
case AXIS_S2: axisOfs = DIJOFS_SLIDER(1); break;
default: return E_FAIL;
}
DWORD dwAxis = axisOfs;
LONG lDirection = 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 = &dwAxis;
eff.rglDirection = &lDirection;
eff.dwDuration = INFINITE;
eff.dwStartDelay = 0;
eff.lpEnvelope = NULL;
// Set specific effects parameters
switch (ffCmd.id)
{
case FFStop:
return E_FAIL;
case FFConstantForce:
guid = GUID_ConstantForce;
dicf.lMagnitude = 0;
eff.cbTypeSpecificParams = sizeof(DICONSTANTFORCE);
eff.lpvTypeSpecificParams = &dicf;
break;
case FFSelfCenter:
guid = GUID_Spring;
dic.lOffset = 0; // offset is +ve/-ve bias, 0 = evenly spread in both directions
dic.lPositiveCoefficient = 0;
dic.lNegativeCoefficient = 0;
dic.dwPositiveSaturation = DI_FFNOMINALMAX;
dic.dwNegativeSaturation = DI_FFNOMINALMAX;
dic.lDeadBand = (LONG)(0.05 * DI_FFNOMINALMAX); // 5% deadband
eff.cbTypeSpecificParams = sizeof(DICONDITION);
eff.lpvTypeSpecificParams = &dic;
break;
case FFFriction:
guid = GUID_Friction;
dic.lOffset = 0;
dic.lPositiveCoefficient = 0;
dic.lNegativeCoefficient = 0;
dic.dwPositiveSaturation = DI_FFNOMINALMAX;
dic.dwNegativeSaturation = DI_FFNOMINALMAX;
dic.lDeadBand = 0; // 0% deadband
eff.cbTypeSpecificParams = sizeof(DICONDITION);
eff.lpvTypeSpecificParams = &dic;
break;
case FFVibrate:
guid = GUID_Sine;
dip.dwMagnitude = 0;
dip.lOffset = 0;
dip.dwPhase = 0;
dip.dwPeriod = (DWORD)(0.05 * DI_SECONDS); // 1/20th second
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;
case AXIS_S1: return (int)m_diJoyStates[joyNum].rglSlider[0];
case AXIS_S2: return (int)m_diJoyStates[joyNum].rglSlider[1];
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)
{
if (axisNum != AXIS_X && axisNum != AXIS_Y && axisNum != AXIS_RX && axisNum != AXIS_RY)
return false;
XINPUT_VIBRATION vibration;
bool negForce;
float absForce;
float threshold;
switch (ffCmd.id)
{
case FFStop:
// Stop command halts all vibration
pInfo->xiConstForceLeft = 0;
pInfo->xiConstForceRight = 0;
pInfo->xiVibrateBoth = 0;
break;
case FFConstantForce:
// Check if constant force effect is disabled
if (g_Config.xInputConstForceMax == 0)
return false;
// Constant force effect is mapped to either left or right vibration motor depending on its direction
negForce = ffCmd.force < 0.0f;
absForce = (negForce ? -ffCmd.force : ffCmd.force);
threshold = (float)g_Config.xInputConstForceThreshold / 100.0f;
// Check if constant force effect is being stopped or is below threshold
if (absForce == 0.0f || absForce < threshold)
{
// If so, stop vibration due to force effect
pInfo->xiConstForceLeft = 0;
pInfo->xiConstForceRight = 0;
}
else if (negForce)
{
// If force is negative (to left), set left motor vibrating
pInfo->xiConstForceLeft = (WORD)(absForce * (float)(g_Config.xInputConstForceMax * XI_VIBRATE_SCALE));
pInfo->xiConstForceRight = 0;
}
else
{
// If force positive (to right), set right motor vibrating
pInfo->xiConstForceLeft = 0;
pInfo->xiConstForceRight = (WORD)(absForce * (float)(g_Config.xInputConstForceMax * XI_VIBRATE_SCALE));
}
break;
case FFSelfCenter:
case FFFriction:
// Self center and friction effects are not mapped
return false;
case FFVibrate:
// Check if vibration effect is disabled
if (g_Config.xInputVibrateMax == 0)
return false;
// Check if vibration effect is being stopped
if (ffCmd.force == 0.0f)
{
// If so, stop vibration due to vibration effect
pInfo->xiVibrateBoth = 0;
}
else
{
// Otherwise, set both motors vibrating
pInfo->xiVibrateBoth = (WORD)(ffCmd.force * (float)(g_Config.xInputVibrateMax * XI_VIBRATE_SCALE));
}
break;
default:
// Unknown feedback command
return false;
}
// Combine vibration speeds from both constant force effect and vibration effect and set motors in action
vibration.wLeftMotorSpeed = min<WORD>(pInfo->xiConstForceLeft + pInfo->xiVibrateBoth, XI_VIBRATE_MAX);
vibration.wRightMotorSpeed = min<WORD>(pInfo->xiConstForceRight + pInfo->xiVibrateBoth, XI_VIBRATE_MAX);
return SUCCEEDED(hr = m_xiSetStatePtr(pInfo->xInputNum, &vibration));
}
else
{
LPDIRECTINPUTDEVICE8 joystick = m_di8Joysticks[pInfo->dInputNum];
// See if command is to stop all force feedback, if so send appropriate command
if (ffCmd.id == FFStop)
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 lDirection = 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 = &lDirection;
eff.dwStartDelay = 0;
eff.lpEnvelope = NULL;
// Set command specific parameters
LONG lFFMag;
DWORD dFFMag;
switch (ffCmd.id)
{
case FFConstantForce:
//printf("FFConstantForce %0.2f\n", 100.0f * ffCmd.force);
if (ffCmd.force >= 0.0f)
{
if (g_Config.dInputConstForceRightMax == 0)
return false;
lFFMag = (LONG)(-ffCmd.force * (float)(g_Config.dInputConstForceRightMax * DI_EFFECTS_SCALE)); // Invert sign for DirectInput effect
dicf.lMagnitude = max<LONG>(lFFMag, -DI_EFFECTS_MAX);
}
else
{
if (g_Config.dInputConstForceLeftMax == 0)
return false;
lFFMag = (LONG)(-ffCmd.force * (float)(g_Config.dInputConstForceLeftMax * DI_EFFECTS_SCALE)); // Invert sign for DirectInput effect
dicf.lMagnitude = min<LONG>(lFFMag, DI_EFFECTS_MAX);
}
eff.cbTypeSpecificParams = sizeof(DICONSTANTFORCE);
eff.lpvTypeSpecificParams = &dicf;
break;
case FFSelfCenter:
//printf("FFSelfCenter %0.2f\n", 100.0f * ffCmd.force);
if (g_Config.dInputSelfCenterMax == 0)
return false;
lFFMag = (LONG)(ffCmd.force * (float)(g_Config.dInputSelfCenterMax * DI_EFFECTS_SCALE));
dic.lOffset = 0;
dic.lPositiveCoefficient = max<LONG>(0, min<LONG>(lFFMag, DI_EFFECTS_MAX));
dic.lNegativeCoefficient = max<LONG>(0, min<LONG>(lFFMag, DI_EFFECTS_MAX));
dic.dwPositiveSaturation = DI_FFNOMINALMAX;
dic.dwNegativeSaturation = DI_FFNOMINALMAX;
dic.lDeadBand = (LONG)(0.05 * DI_FFNOMINALMAX);
eff.cbTypeSpecificParams = sizeof(DICONDITION);
eff.lpvTypeSpecificParams = &dic;
break;
case FFFriction:
//printf("FFFriction %0.2f\n", 100.0f * ffCmd.force);
if (g_Config.dInputFrictionMax == 0)
return false;
lFFMag = (LONG)(ffCmd.force * (float)(g_Config.dInputFrictionMax * DI_EFFECTS_SCALE));
dic.lOffset = 0;
dic.lPositiveCoefficient = max<LONG>(0, min<LONG>(lFFMag, DI_EFFECTS_MAX));
dic.lNegativeCoefficient = max<LONG>(0, min<LONG>(lFFMag, DI_EFFECTS_MAX));
dic.dwPositiveSaturation = DI_FFNOMINALMAX;
dic.dwNegativeSaturation = DI_FFNOMINALMAX;
dic.lDeadBand = 0;
eff.cbTypeSpecificParams = sizeof(DICONDITION);
eff.lpvTypeSpecificParams = &dic;
break;
case FFVibrate:
//printf("FFVibrate %0.2f\n", 100.0f * ffCmd.force);
if (g_Config.dInputVibrateMax == 0)
return false;
dFFMag = (DWORD)(ffCmd.force * (float)(g_Config.dInputVibrateMax * DI_EFFECTS_SCALE));
dip.dwMagnitude = max<DWORD>(0, min<DWORD>(dFFMag, DI_EFFECTS_MAX));
dip.lOffset = 0;
dip.dwPhase = 0;
dip.dwPeriod = (DWORD)(0.05 * DI_SECONDS); // 1/20th second
eff.cbTypeSpecificParams = sizeof(DIPERIODIC);
eff.lpvTypeSpecificParams = &dip;
break;
default:
// Unknown feedback command
return false;
}
// Set the new parameters and start effect immediately
return SUCCEEDED(hr = (*pEffect)->SetParameters(&eff, DIEP_DIRECTION | DIEP_TYPESPECIFICPARAMS | DIEP_START));
}
}
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();
if (m_useRawInput)
SetMouseVisibility(false);
// When grabbing mouse, make sure devices get re-activated
if (m_activated)
{
ActivateKeyboardsAndMice();
ActivateJoysticks();
}
}
void CDirectInputSystem::UngrabMouse()
{
CInputSystem::UngrabMouse();
// When ungrabbing mouse, make sure devices get re-activated
if (m_activated)
{
ActivateKeyboardsAndMice();
ActivateJoysticks();
}
}