Skip to content

Commit

Permalink
initial commit for multiple DInput devices.
Browse files Browse the repository at this point in the history
allow multiple dinput devices but still block all dinput devices if an xinput device is present.
This still has some issues with devices not being able to be dynamically added or removed but
even many commercial games react to hot-plug DInput events. Also, the axis handling is not really
ideal yet but it works for now.
  • Loading branch information
Bigpet committed Jun 1, 2014
1 parent 0c7c736 commit 1024eb7
Show file tree
Hide file tree
Showing 3 changed files with 128 additions and 44 deletions.
139 changes: 100 additions & 39 deletions Windows/DinputDevice.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@
#undef max
#endif

//initialize static members of DinputDevice
unsigned int DinputDevice::pInstances = 0;
LPDIRECTINPUT8 DinputDevice::pDI = NULL;
std::vector<DIDEVICEINSTANCE> DinputDevice::devices;

// In order from 0. There can be 128, but most controllers do not have that many.
static const int dinput_buttons[] = {
NKCODE_BUTTON_1,
Expand Down Expand Up @@ -80,9 +85,50 @@ bool IsXInputDevice( const GUID* pGuidProductFromDirectInput ) {
return false;
}

DinputDevice::DinputDevice() {
LPDIRECTINPUT8 DinputDevice::getPDI()
{
if (pDI == NULL)
{
if (FAILED(DirectInput8Create(GetModuleHandle(NULL), DIRECTINPUT_VERSION, IID_IDirectInput8, (void**)&pDI, NULL)))
{
pDI = NULL;
}
}
return pDI;
}

BOOL DinputDevice::DevicesCallback(
LPCDIDEVICEINSTANCE lpddi,
LPVOID pvRef
)
{
//check if a device with the same Instance guid is already saved
auto res = std::find_if(devices.begin(), devices.end(),
[lpddi](const DIDEVICEINSTANCE &to_consider){
return lpddi->guidInstance == to_consider.guidInstance;
});
if (res == devices.end()) //not yet in the devices list
{
// Ignore if device supports XInput
if (!IsXInputDevice(&lpddi->guidProduct)) {
devices.push_back(*lpddi);
}
}
return DIENUM_CONTINUE;
}

void DinputDevice::getDevices()
{
if (devices.empty())
{
getPDI()->EnumDevices(DI8DEVCLASS_GAMECTRL, &DinputDevice::DevicesCallback, NULL, DIEDFL_ATTACHEDONLY);
}
}

DinputDevice::DinputDevice(int devnum) {
pInstances++;
pDevNum = devnum;
pJoystick = NULL;
pDI = NULL;
memset(lastButtons_, 0, sizeof(lastButtons_));
memset(lastPOV_, 0, sizeof(lastPOV_));
last_lX_ = 0;
Expand All @@ -92,50 +138,42 @@ DinputDevice::DinputDevice() {
last_lRy_ = 0;
last_lRz_ = 0;

if(FAILED(DirectInput8Create(GetModuleHandle(NULL),DIRECTINPUT_VERSION,IID_IDirectInput8,(void**)&pDI,NULL)))
return;

if(FAILED(pDI->CreateDevice(GUID_Joystick, &pJoystick, NULL ))) {
pDI->Release();
pDI = NULL;
if (getPDI() == NULL)
{
return;
}

if(FAILED(pJoystick->SetDataFormat(&c_dfDIJoystick2))) {
pJoystick->Release();
pJoystick = NULL;
getDevices();
if ( (devnum > devices.size()) || FAILED(getPDI()->CreateDevice(devices.at(devnum).guidInstance, &pJoystick, NULL)))
{
return;
}

// Ignore if device supports XInput
DIDEVICEINSTANCE dinfo = {0};
pJoystick->GetDeviceInfo(&dinfo);
if (IsXInputDevice(&dinfo.guidProduct)) {
pDI->Release();
pDI = NULL;
if (FAILED(pJoystick->SetDataFormat(&c_dfDIJoystick2))) {
pJoystick->Release();
pJoystick = NULL;
return;
}

DIPROPRANGE diprg;
diprg.diph.dwSize = sizeof(DIPROPRANGE);
DIPROPRANGE diprg;
diprg.diph.dwSize = sizeof(DIPROPRANGE);
diprg.diph.dwHeaderSize = sizeof(DIPROPHEADER);
diprg.diph.dwHow = DIPH_DEVICE;
diprg.diph.dwObj = 0;
diprg.lMin = -10000;
diprg.lMax = 10000;
diprg.diph.dwHow = DIPH_DEVICE;
diprg.diph.dwObj = 0;
diprg.lMin = -10000;
diprg.lMax = 10000;

analog = FAILED(pJoystick->SetProperty(DIPROP_RANGE, &diprg.diph)) ? false : true;

// Other devices suffer if the deadzone is not set.
// TODO: The dead zone will be made configurable in the Control dialog.
DIPROPDWORD dipw;
dipw.diph.dwSize = sizeof(DIPROPDWORD);
dipw.diph.dwSize = sizeof(DIPROPDWORD);
dipw.diph.dwHeaderSize = sizeof(DIPROPHEADER);
dipw.diph.dwHow = DIPH_DEVICE;
dipw.diph.dwObj = 0;
dipw.diph.dwHow = DIPH_DEVICE;
dipw.diph.dwObj = 0;
// dwData 1000 is deadzone(0% - 10%)
dipw.dwData = 1000;
dipw.dwData = 1000;

analog |= FAILED(pJoystick->SetProperty(DIPROP_DEADZONE, &dipw.diph)) ? false : true;
}
Expand All @@ -146,7 +184,13 @@ DinputDevice::~DinputDevice() {
pJoystick = NULL;
}

if (pDI) {
pInstances--;

//the whole instance counter is obviously highly thread-unsafe
//but I don't think creation and destruction operations will be
//happening at the same time and other values like pDI are
//unsafe as well anyway
if (pInstances == 0 && pDI) {
pDI->Release();
pDI = NULL;
}
Expand Down Expand Up @@ -182,17 +226,26 @@ int DinputDevice::UpdateState(InputState &input_state) {

if (analog) {
AxisInput axis;
axis.deviceId = DEVICE_ID_PAD_0;

SendNativeAxis(DEVICE_ID_PAD_0, js.lX, last_lX_, JOYSTICK_AXIS_X);
SendNativeAxis(DEVICE_ID_PAD_0, js.lY, last_lY_, JOYSTICK_AXIS_Y);
SendNativeAxis(DEVICE_ID_PAD_0, js.lZ, last_lZ_, JOYSTICK_AXIS_Z);
SendNativeAxis(DEVICE_ID_PAD_0, js.lRx, last_lRx_, JOYSTICK_AXIS_RX);
SendNativeAxis(DEVICE_ID_PAD_0, js.lRy, last_lRy_, JOYSTICK_AXIS_RY);
SendNativeAxis(DEVICE_ID_PAD_0, js.lRz, last_lRz_, JOYSTICK_AXIS_RZ);
axis.deviceId = DEVICE_ID_PAD_0 + pDevNum;

SendNativeAxis(DEVICE_ID_PAD_0 + pDevNum, js.lX, last_lX_, JOYSTICK_AXIS_X);
SendNativeAxis(DEVICE_ID_PAD_0 + pDevNum, js.lY, last_lY_, JOYSTICK_AXIS_Y);
SendNativeAxis(DEVICE_ID_PAD_0 + pDevNum, js.lZ, last_lZ_, JOYSTICK_AXIS_Z);
SendNativeAxis(DEVICE_ID_PAD_0 + pDevNum, js.lRx, last_lRx_, JOYSTICK_AXIS_RX);
SendNativeAxis(DEVICE_ID_PAD_0 + pDevNum, js.lRy, last_lRy_, JOYSTICK_AXIS_RY);
SendNativeAxis(DEVICE_ID_PAD_0 + pDevNum, js.lRz, last_lRz_, JOYSTICK_AXIS_RZ);
}

return UPDATESTATE_SKIP_PAD;
//check if the values have changed from last time and skip polling the rest of the dinput devices if they did
//this doesn't seem to quite work if only the axis have changed
if ((memcmp(js.rgbButtons, pPrevState.rgbButtons, sizeof(BYTE) * 128) != 0)
|| (memcmp(js.rgdwPOV, pPrevState.rgdwPOV, sizeof(DWORD) * 4) != 0)
|| js.lVX != 0 || js.lVY != 0 || js.lVZ != 0 || js.lVRx != 0 || js.lVRy != 0 || js.lVRz != 0)
{
pPrevState = js;
return UPDATESTATE_SKIP_PAD;
}
return -1;
}

static float NormalizedDeadzoneFilter(short value) {
Expand All @@ -215,7 +268,7 @@ void DinputDevice::ApplyButtons(DIJOYSTATE2 &state, InputState &input_state) {

bool down = (state.rgbButtons[i] & downMask) == downMask;
KeyInput key;
key.deviceId = DEVICE_ID_PAD_0;
key.deviceId = DEVICE_ID_PAD_0 + pDevNum;
key.flags = down ? KEY_DOWN : KEY_UP;
key.keyCode = dinput_buttons[i];
NativeKey(key);
Expand All @@ -227,7 +280,7 @@ void DinputDevice::ApplyButtons(DIJOYSTATE2 &state, InputState &input_state) {
if (LOWORD(state.rgdwPOV[0]) != lastPOV_[0]) {
KeyInput dpad[4];
for (int i = 0; i < 4; ++i) {
dpad[i].deviceId = DEVICE_ID_PAD_0;
dpad[i].deviceId = DEVICE_ID_PAD_0 + pDevNum;
dpad[i].flags = KEY_UP;
}
dpad[0].keyCode = NKCODE_DPAD_UP;
Expand Down Expand Up @@ -260,3 +313,11 @@ void DinputDevice::ApplyButtons(DIJOYSTATE2 &state, InputState &input_state) {
}
}

int DinputDevice::getNumPads()
{
if (devices.empty())
{
getDevices();
}
return devices.size();
}
23 changes: 21 additions & 2 deletions Windows/DinputDevice.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,33 @@ class DinputDevice :
public InputDevice
{
public:
DinputDevice();
//instantiates device number devnum as explored by the first call to
//getDevices(), enumerates all devices if not done yet
DinputDevice(int devnum);
~DinputDevice();
virtual int UpdateState(InputState &input_state);
virtual bool IsPad() { return true; }
static int getNumPads();
private:
void ApplyButtons(DIJOYSTATE2 &state, InputState &input_state);
LPDIRECTINPUT8 pDI;
//unfortunate and unclean way to keep only one DirectInput instance around
static LPDIRECTINPUT8 getPDI();
//unfortunate and unclean way to keep track of the number of devices and the
//GUIDs of the plugged in devices. This function will only search for devices
//if none have been found yet and will only list plugged in devices
//also, it excludes the devices that are compatible with XInput
static void getDevices();
//callback for the WinAPI to call
static BOOL DevicesCallback(
LPCDIDEVICEINSTANCE lpddi,
LPVOID pvRef
);
static unsigned int pInstances;
static std::vector<DIDEVICEINSTANCE> devices;
static LPDIRECTINPUT8 pDI;
int pDevNum;
LPDIRECTINPUTDEVICE8 pJoystick;
DIJOYSTATE2 pPrevState;
bool analog;
BYTE lastButtons_[128];
WORD lastPOV_[4];
Expand Down
10 changes: 7 additions & 3 deletions Windows/WindowsHost.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,14 @@ WindowsHost::WindowsHost(HWND mainWindow) {
mouseDeltaX = 0;
mouseDeltaY = 0;

#define PUSH_BACK(Cls) do { list.push_back(std::shared_ptr<InputDevice>(new Cls())); } while (0)

//add first XInput device to respond
input.push_back(std::shared_ptr<InputDevice>(new XinputDevice()));
input.push_back(std::shared_ptr<InputDevice>(new DinputDevice()));
//find all connected DInput devices of class GamePad
int numDInputDevs = DinputDevice::getNumPads();
for (int i = 0; i < numDInputDevs; i++)
{
input.push_back(std::shared_ptr<InputDevice>(new DinputDevice(i)));
}
keyboard = std::shared_ptr<KeyboardDevice>(new KeyboardDevice());
input.push_back(keyboard);

Expand Down

0 comments on commit 1024eb7

Please sign in to comment.