that should fix a lot more pads that were being reported as not being detected. Also added some debug log messages that output each device's type and name so in the future if other devices fail to be detected we can easily see what it is in the info log.
906 lines
26 KiB
C++
906 lines
26 KiB
C++
#include "global.h"
|
|
#include "InputHandler_DirectInput.h"
|
|
|
|
#include "RageUtil.h"
|
|
#include "RageLog.h"
|
|
#include "archutils/Win32/AppInstance.h"
|
|
#include "archutils/Win32/DirectXHelpers.h"
|
|
#include "archutils/Win32/ErrorStrings.h"
|
|
#include "archutils/Win32/GraphicsWindow.h"
|
|
#include "archutils/Win32/RegistryAccess.h"
|
|
#include "InputFilter.h"
|
|
#include "PrefsManager.h"
|
|
#include "Foreach.h"
|
|
|
|
#include "InputHandler_DirectInputHelper.h"
|
|
|
|
REGISTER_INPUT_HANDLER_CLASS2( DirectInput, DInput );
|
|
|
|
static vector<DIDevice> Devices;
|
|
|
|
// Number of joysticks found:
|
|
static int g_iNumJoysticks;
|
|
|
|
static BOOL CALLBACK EnumDevicesCallback( const DIDEVICEINSTANCE *pdidInstance, void *pContext )
|
|
{
|
|
DIDevice device;
|
|
|
|
LOG->Info( "DInput: Enumerating device - Type: 0x%08X Instance Name: \"%s\" Product Name: \"%s\"", pdidInstance->dwDevType, pdidInstance->tszInstanceName, pdidInstance->tszProductName );
|
|
|
|
switch( GET_DIDEVICE_TYPE(pdidInstance->dwDevType) )
|
|
{
|
|
case DI8DEVTYPE_JOYSTICK:
|
|
case DI8DEVTYPE_GAMEPAD:
|
|
case DI8DEVTYPE_SUPPLEMENTAL:
|
|
{
|
|
device.type = device.JOYSTICK;
|
|
break;
|
|
}
|
|
|
|
case DI8DEVTYPE_KEYBOARD: device.type = device.KEYBOARD; break;
|
|
case DI8DEVTYPE_MOUSE: device.type = device.MOUSE; break;
|
|
default: LOG->Info( "DInput: Unrecognized device ignored." ); return DIENUM_CONTINUE;
|
|
}
|
|
|
|
device.JoystickInst = *pdidInstance;
|
|
|
|
switch( device.type )
|
|
{
|
|
case device.JOYSTICK:
|
|
if( g_iNumJoysticks == NUM_JOYSTICKS )
|
|
return DIENUM_CONTINUE;
|
|
|
|
device.dev = enum_add2( DEVICE_JOY1, g_iNumJoysticks );
|
|
g_iNumJoysticks++;
|
|
break;
|
|
|
|
case device.KEYBOARD:
|
|
device.dev = DEVICE_KEYBOARD;
|
|
break;
|
|
|
|
case device.MOUSE:
|
|
device.dev = DEVICE_MOUSE;
|
|
break;
|
|
}
|
|
|
|
Devices.push_back(device);
|
|
|
|
return DIENUM_CONTINUE;
|
|
}
|
|
|
|
static void CheckForDirectInputDebugMode()
|
|
{
|
|
int iVal;
|
|
if( RegistryAccess::GetRegValue("HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\DirectInput", "emulation", iVal) )
|
|
{
|
|
if( iVal & 0x8 )
|
|
LOG->Warn("DirectInput keyboard debug mode appears to be enabled. This reduces\n"
|
|
"input timing accuracy significantly. Disabling this is strongly recommended." );
|
|
}
|
|
}
|
|
|
|
static BOOL CALLBACK CountDevicesCallback( const DIDEVICEINSTANCE *pdidInstance, void *pContext )
|
|
{
|
|
(*(int*)pContext)++;
|
|
return DIENUM_CONTINUE;
|
|
}
|
|
|
|
static int GetNumHidDevices()
|
|
{
|
|
int i = 0;
|
|
RegistryAccess::GetRegValue( "HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\HidUsb\\Enum", "Count", i, false ); // don't warn on error
|
|
return i;
|
|
}
|
|
|
|
static int GetNumJoysticksSlow()
|
|
{
|
|
int iCount = 0;
|
|
HRESULT hr = g_dinput->EnumDevices( DI8DEVCLASS_GAMECTRL, CountDevicesCallback, &iCount, DIEDFL_ATTACHEDONLY );
|
|
if( hr != DI_OK )
|
|
{
|
|
LOG->Warn( hr_ssprintf(hr, "g_dinput->EnumDevices") );
|
|
}
|
|
return iCount;
|
|
}
|
|
|
|
InputHandler_DInput::InputHandler_DInput()
|
|
{
|
|
LOG->Trace( "InputHandler_DInput::InputHandler_DInput()" );
|
|
|
|
CheckForDirectInputDebugMode();
|
|
|
|
m_bShutdown = false;
|
|
g_iNumJoysticks = 0;
|
|
|
|
AppInstance inst;
|
|
HRESULT hr = DirectInput8Create(inst.Get(), DIRECTINPUT_VERSION, IID_IDirectInput8, (LPVOID *) &g_dinput, NULL);
|
|
if( hr != DI_OK )
|
|
RageException::Throw( hr_ssprintf(hr, "InputHandler_DInput: DirectInputCreate") );
|
|
|
|
LOG->Trace( "InputHandler_DInput: IDirectInput::EnumDevices(DIDEVTYPE_KEYBOARD)" );
|
|
hr = g_dinput->EnumDevices( DI8DEVCLASS_KEYBOARD, EnumDevicesCallback, NULL, DIEDFL_ATTACHEDONLY );
|
|
if( hr != DI_OK )
|
|
RageException::Throw( hr_ssprintf(hr, "InputHandler_DInput: IDirectInput::EnumDevices") );
|
|
|
|
LOG->Trace( "InputHandler_DInput: IDirectInput::EnumDevices(DIDEVTYPE_JOYSTICK)" );
|
|
hr = g_dinput->EnumDevices( DI8DEVCLASS_GAMECTRL, EnumDevicesCallback, NULL, DIEDFL_ATTACHEDONLY );
|
|
if( hr != DI_OK )
|
|
RageException::Throw( hr_ssprintf(hr, "InputHandler_DInput: IDirectInput::EnumDevices") );
|
|
|
|
// mouse
|
|
LOG->Trace( "InputHandler_DInput: IDirectInput::EnumDevices(DIDEVTYPE_MOUSE)" );
|
|
hr = g_dinput->EnumDevices( DI8DEVCLASS_POINTER, EnumDevicesCallback, NULL, DIEDFL_ATTACHEDONLY );
|
|
if( hr != DI_OK )
|
|
RageException::Throw( hr_ssprintf(hr, "InputHandler_DInput: IDirectInput::EnumDevices") );
|
|
|
|
for( unsigned i = 0; i < Devices.size(); ++i )
|
|
{
|
|
if( Devices[i].Open() )
|
|
continue;
|
|
|
|
Devices.erase( Devices.begin() + i );
|
|
i--;
|
|
continue;
|
|
}
|
|
|
|
LOG->Info( "Found %u DirectInput devices:", Devices.size() );
|
|
for( unsigned i = 0; i < Devices.size(); ++i )
|
|
{
|
|
LOG->Info( " %d: '%s' axes: %d, hats: %d, buttons: %d (%s)",
|
|
i,
|
|
Devices[i].m_sName.c_str(),
|
|
Devices[i].axes,
|
|
Devices[i].hats,
|
|
Devices[i].buttons,
|
|
Devices[i].buffered? "buffered": "unbuffered" );
|
|
}
|
|
|
|
m_iLastSeenNumHidDevices = GetNumHidDevices();
|
|
m_iNumTimesLeftToPollForJoysticksChanged = 0;
|
|
m_iLastSeenNumJoysticks = GetNumJoysticksSlow();
|
|
|
|
StartThread();
|
|
}
|
|
|
|
void InputHandler_DInput::StartThread()
|
|
{
|
|
ASSERT( !m_InputThread.IsCreated() );
|
|
if( PREFSMAN->m_bThreadedInput )
|
|
{
|
|
m_InputThread.SetName( "DirectInput thread" );
|
|
m_InputThread.Create( InputThread_Start, this );
|
|
}
|
|
}
|
|
|
|
void InputHandler_DInput::ShutdownThread()
|
|
{
|
|
m_bShutdown = true;
|
|
if( m_InputThread.IsCreated() )
|
|
{
|
|
LOG->Trace( "Shutting down DirectInput thread ..." );
|
|
m_InputThread.Wait();
|
|
LOG->Trace( "DirectInput thread shut down." );
|
|
}
|
|
m_bShutdown = false;
|
|
}
|
|
|
|
InputHandler_DInput::~InputHandler_DInput()
|
|
{
|
|
ShutdownThread();
|
|
|
|
for( unsigned i = 0; i < Devices.size(); ++i )
|
|
Devices[i].Close();
|
|
|
|
Devices.clear();
|
|
g_dinput->Release();
|
|
g_dinput = NULL;
|
|
}
|
|
|
|
void InputHandler_DInput::WindowReset()
|
|
{
|
|
// We need to reopen keyboards.
|
|
ShutdownThread();
|
|
|
|
for( unsigned i = 0; i < Devices.size(); ++i )
|
|
{
|
|
if( Devices[i].type != Devices[i].KEYBOARD )
|
|
continue;
|
|
|
|
Devices[i].Close();
|
|
|
|
// We lose buffered inputs here, so we need to clear all pressed keys.
|
|
INPUTFILTER->ResetDevice( Devices[i].dev );
|
|
|
|
bool ret = Devices[i].Open();
|
|
|
|
// Reopening it should succeed.
|
|
ASSERT( ret );
|
|
}
|
|
|
|
StartThread();
|
|
}
|
|
|
|
#define HAT_UP_MASK 1
|
|
#define HAT_DOWN_MASK 2
|
|
#define HAT_LEFT_MASK 4
|
|
#define HAT_RIGHT_MASK 8
|
|
|
|
static int TranslatePOV(DWORD value)
|
|
{
|
|
const int HAT_VALS[] =
|
|
{
|
|
HAT_UP_MASK,
|
|
HAT_UP_MASK | HAT_RIGHT_MASK,
|
|
HAT_RIGHT_MASK,
|
|
HAT_DOWN_MASK | HAT_RIGHT_MASK,
|
|
HAT_DOWN_MASK,
|
|
HAT_DOWN_MASK | HAT_LEFT_MASK,
|
|
HAT_LEFT_MASK,
|
|
HAT_UP_MASK | HAT_LEFT_MASK
|
|
};
|
|
|
|
if( LOWORD(value) == 0xFFFF )
|
|
return 0;
|
|
|
|
// Round the value up:
|
|
value += 4500 / 2;
|
|
value %= 36000;
|
|
value /= 4500;
|
|
|
|
if( value >= 8 )
|
|
return 0; // shouldn't happen
|
|
|
|
return HAT_VALS[value];
|
|
}
|
|
|
|
static HRESULT GetDeviceState( LPDIRECTINPUTDEVICE8 dev, int size, void *ptr )
|
|
{
|
|
HRESULT hr = dev->GetDeviceState( size, ptr );
|
|
if( hr == DIERR_INPUTLOST || hr == DIERR_NOTACQUIRED )
|
|
{
|
|
hr = dev->Acquire();
|
|
if( hr != DI_OK )
|
|
{
|
|
LOG->Trace( hr_ssprintf(hr, "?") );
|
|
return hr;
|
|
}
|
|
|
|
hr = dev->GetDeviceState( size, ptr );
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
/* This doesn't take a timestamp; instead, we let InputHandler::ButtonPressed
|
|
* figure it out. Be sure to call InputHandler::Update() between each poll. */
|
|
void InputHandler_DInput::UpdatePolled( DIDevice &device, const RageTimer &tm )
|
|
{
|
|
switch( device.type )
|
|
{
|
|
default:
|
|
FAIL_M(ssprintf("Unsupported DI device type: %i", device.type));
|
|
case device.KEYBOARD:
|
|
{
|
|
unsigned char keys[256];
|
|
|
|
HRESULT hr = GetDeviceState( device.Device, 256, keys );
|
|
if( hr == DIERR_INPUTLOST || hr == DIERR_NOTACQUIRED )
|
|
return;
|
|
|
|
if( hr != DI_OK )
|
|
{
|
|
LOG->MapLog( "UpdatePolled", hr_ssprintf(hr, "Failures on polled keyboard update") );
|
|
return;
|
|
}
|
|
|
|
for( int k = 0; k < 256; ++k )
|
|
{
|
|
const DeviceButton key = (DeviceButton) device.Inputs[k].num;
|
|
ButtonPressed( DeviceInput(device.dev, key, !!(keys[k] & 0x80) ) );
|
|
}
|
|
}
|
|
break;
|
|
case device.JOYSTICK:
|
|
{
|
|
DIJOYSTATE state;
|
|
|
|
HRESULT hr = GetDeviceState(device.Device, sizeof(state), &state);
|
|
if( hr == DIERR_INPUTLOST || hr == DIERR_NOTACQUIRED )
|
|
return;
|
|
|
|
// Set each known axis, button and POV.
|
|
for( unsigned i = 0; i < device.Inputs.size(); ++i )
|
|
{
|
|
const input_t &in = device.Inputs[i];
|
|
const InputDevice dev = device.dev;
|
|
|
|
switch(in.type)
|
|
{
|
|
case in.BUTTON:
|
|
{
|
|
DeviceInput di( dev, enum_add2(JOY_BUTTON_1, in.num), !!state.rgbButtons[in.ofs - DIJOFS_BUTTON0], tm );
|
|
ButtonPressed( di );
|
|
break;
|
|
}
|
|
|
|
case in.AXIS:
|
|
{
|
|
DeviceButton neg = DeviceButton_Invalid, pos = DeviceButton_Invalid;
|
|
int val = 0;
|
|
switch( in.ofs )
|
|
{
|
|
case DIJOFS_X: neg = JOY_LEFT; pos = JOY_RIGHT;
|
|
val = state.lX;
|
|
break;
|
|
case DIJOFS_Y: neg = JOY_UP; pos = JOY_DOWN;
|
|
val = state.lY;
|
|
break;
|
|
case DIJOFS_Z: neg = JOY_Z_UP; pos = JOY_Z_DOWN;
|
|
val = state.lZ;
|
|
break;
|
|
case DIJOFS_RX: neg = JOY_ROT_LEFT; pos = JOY_ROT_RIGHT;
|
|
val = state.lRx;
|
|
break;
|
|
case DIJOFS_RY: neg = JOY_ROT_UP; pos = JOY_ROT_DOWN;
|
|
val = state.lRy;
|
|
break;
|
|
case DIJOFS_RZ: neg = JOY_ROT_Z_UP; pos = JOY_ROT_Z_DOWN;
|
|
val = state.lRz;
|
|
break;
|
|
case DIJOFS_SLIDER(0):
|
|
neg = JOY_AUX_1; pos = JOY_AUX_2;
|
|
val = state.rglSlider[0];
|
|
break;
|
|
case DIJOFS_SLIDER(1):
|
|
neg = JOY_AUX_3; pos = JOY_AUX_4;
|
|
val = state.rglSlider[1];
|
|
break;
|
|
default: LOG->MapLog( "unknown input",
|
|
"Controller '%s' is returning an unknown joystick offset, %i",
|
|
device.m_sName.c_str(), in.ofs );
|
|
continue;
|
|
}
|
|
if( neg != DeviceButton_Invalid )
|
|
{
|
|
float l = SCALE( int(val), 0.0f, 100.0f, 0.0f, 1.0f );
|
|
ButtonPressed( DeviceInput(dev, neg, max(-l,0), tm) );
|
|
ButtonPressed( DeviceInput(dev, pos, max(+l,0), tm) );
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case in.HAT:
|
|
if( in.num == 0 )
|
|
{
|
|
const int pos = TranslatePOV( state.rgdwPOV[in.ofs - DIJOFS_POV(0)] );
|
|
ButtonPressed( DeviceInput(dev, JOY_HAT_UP, !!(pos & HAT_UP_MASK), tm) );
|
|
ButtonPressed( DeviceInput(dev, JOY_HAT_DOWN, !!(pos & HAT_DOWN_MASK), tm) );
|
|
ButtonPressed( DeviceInput(dev, JOY_HAT_LEFT, !!(pos & HAT_LEFT_MASK), tm) );
|
|
ButtonPressed( DeviceInput(dev, JOY_HAT_RIGHT, !!(pos & HAT_RIGHT_MASK), tm) );
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case device.MOUSE:
|
|
// Not 100% perfect (or tested much) yet. -aj
|
|
DIMOUSESTATE state;
|
|
|
|
HRESULT hr = GetDeviceState(device.Device, sizeof(state), &state);
|
|
if( hr == DIERR_INPUTLOST || hr == DIERR_NOTACQUIRED )
|
|
return;
|
|
|
|
// reset mousewheel
|
|
ButtonPressed( DeviceInput(device.dev, MOUSE_WHEELUP, 0, tm) );
|
|
ButtonPressed( DeviceInput(device.dev, MOUSE_WHEELDOWN, 0, tm) );
|
|
|
|
for( unsigned i = 0; i < device.Inputs.size(); ++i )
|
|
{
|
|
const input_t &in = device.Inputs[i];
|
|
const InputDevice dev = device.dev;
|
|
|
|
switch(in.type)
|
|
{
|
|
case in.BUTTON:
|
|
{
|
|
DeviceInput di( dev, enum_add2(MOUSE_LEFT, in.num), !!state.rgbButtons[in.ofs - DIMOFS_BUTTON0], tm );
|
|
ButtonPressed( di );
|
|
break;
|
|
}
|
|
case in.AXIS:
|
|
{
|
|
DeviceButton neg = DeviceButton_Invalid, pos = DeviceButton_Invalid;
|
|
int val = 0;
|
|
switch( in.ofs )
|
|
{
|
|
case DIMOFS_X:
|
|
neg = MOUSE_X_LEFT; pos = MOUSE_X_RIGHT;
|
|
val = state.lX;
|
|
break;
|
|
case DIMOFS_Y:
|
|
neg = MOUSE_Y_UP; pos = MOUSE_Y_DOWN;
|
|
val = state.lY;
|
|
break;
|
|
case DIMOFS_Z:
|
|
{
|
|
neg = MOUSE_WHEELDOWN; pos = MOUSE_WHEELUP;
|
|
val = state.lZ;
|
|
//LOG->Trace("MouseWheel polled: %i",val);
|
|
INPUTFILTER->UpdateMouseWheel(val);
|
|
if( val == 0 )
|
|
{
|
|
// release all
|
|
ButtonPressed( DeviceInput(dev, pos, 0, tm) );
|
|
ButtonPressed( DeviceInput(dev, neg, 0, tm) );
|
|
}
|
|
else if( int(val) > 0 )
|
|
{
|
|
// positive values: WheelUp
|
|
ButtonPressed( DeviceInput(dev, pos, 1, tm) );
|
|
ButtonPressed( DeviceInput(dev, neg, 0, tm) );
|
|
}
|
|
else if( int(val) < 0 )
|
|
{
|
|
// negative values: WheelDown
|
|
ButtonPressed( DeviceInput(dev, neg, 1, tm) );
|
|
ButtonPressed( DeviceInput(dev, pos, 0, tm) );
|
|
}
|
|
}
|
|
break;
|
|
default: LOG->MapLog( "unknown input",
|
|
"Mouse '%s' is returning an unknown mouse offset, %i",
|
|
device.m_sName.c_str(), in.ofs );
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
void InputHandler_DInput::UpdateBuffered( DIDevice &device, const RageTimer &tm )
|
|
{
|
|
DWORD numevents;
|
|
DIDEVICEOBJECTDATA evtbuf[INPUT_QSIZE];
|
|
|
|
numevents = INPUT_QSIZE;
|
|
HRESULT hr = device.Device->GetDeviceData( sizeof(DIDEVICEOBJECTDATA), evtbuf, &numevents, 0 );
|
|
if( hr == DIERR_INPUTLOST || hr == DIERR_NOTACQUIRED )
|
|
{
|
|
INPUTFILTER->ResetDevice( device.dev );
|
|
return;
|
|
}
|
|
|
|
if( hr != DI_OK )
|
|
{
|
|
LOG->Trace( hr_ssprintf(hr, "UpdateBuffered: IDirectInputDevice2_GetDeviceData") );
|
|
return;
|
|
}
|
|
|
|
if( GetForegroundWindow() != GraphicsWindow::GetHwnd() )
|
|
{
|
|
// Discard input when not focused, and release all keys.
|
|
INPUTFILTER->ResetDevice( device.dev );
|
|
return;
|
|
}
|
|
|
|
// reset mousewheel
|
|
DeviceInput diUp = DeviceInput(device.dev, MOUSE_WHEELUP, 0.0f, tm);
|
|
ButtonPressed( diUp );
|
|
DeviceInput diDown = DeviceInput(device.dev, MOUSE_WHEELDOWN, 0.0f, tm);
|
|
ButtonPressed( diDown );
|
|
|
|
for( int i = 0; i < (int) numevents; ++i )
|
|
{
|
|
for(unsigned j = 0; j < device.Inputs.size(); ++j)
|
|
{
|
|
const input_t &in = device.Inputs[j];
|
|
const InputDevice dev = device.dev;
|
|
|
|
if( evtbuf[i].dwOfs != in.ofs )
|
|
continue;
|
|
|
|
switch( in.type )
|
|
{
|
|
case in.KEY:
|
|
ButtonPressed( DeviceInput(dev, (DeviceButton) in.num, !!(evtbuf[i].dwData & 0x80), tm) );
|
|
break;
|
|
|
|
case in.BUTTON:
|
|
if(dev == DEVICE_MOUSE)
|
|
{
|
|
DeviceButton mouseInput = DeviceButton_Invalid;
|
|
switch(in.ofs)
|
|
{
|
|
case DIMOFS_BUTTON0: mouseInput = MOUSE_LEFT; break;
|
|
case DIMOFS_BUTTON1: mouseInput = MOUSE_RIGHT; break;
|
|
case DIMOFS_BUTTON2: mouseInput = MOUSE_MIDDLE; break;
|
|
default: LOG->MapLog( "unknown input",
|
|
"Mouse '%s' is returning an unknown mouse offset [button], %i",
|
|
device.m_sName.c_str(), in.ofs );
|
|
continue;
|
|
}
|
|
ButtonPressed( DeviceInput(dev, mouseInput, !!evtbuf[i].dwData, tm) );
|
|
}
|
|
else
|
|
ButtonPressed( DeviceInput(dev, enum_add2(JOY_BUTTON_1, in.num), !!evtbuf[i].dwData, tm) );
|
|
break;
|
|
|
|
case in.AXIS:
|
|
{
|
|
DeviceButton up = DeviceButton_Invalid, down = DeviceButton_Invalid;
|
|
if(dev == DEVICE_MOUSE)
|
|
{
|
|
float l = int(evtbuf[i].dwData);
|
|
POINT cursorPos;
|
|
GetCursorPos(&cursorPos);
|
|
// convert screen coordinates to client
|
|
ScreenToClient(GraphicsWindow::GetHwnd(), &cursorPos);
|
|
|
|
switch(in.ofs)
|
|
{
|
|
case DIMOFS_X:
|
|
INPUTFILTER->UpdateCursorLocation((float)cursorPos.x,(float)cursorPos.y);
|
|
break;
|
|
case DIMOFS_Y:
|
|
INPUTFILTER->UpdateCursorLocation((float)cursorPos.x,(float)cursorPos.y);
|
|
break;
|
|
case DIMOFS_Z:
|
|
// positive values: WheelUp
|
|
// negative values: WheelDown
|
|
INPUTFILTER->UpdateMouseWheel(l);
|
|
{
|
|
up = MOUSE_WHEELUP; down = MOUSE_WHEELDOWN;
|
|
float fWheelDelta = l;
|
|
//l = SCALE( int(evtbuf[i].dwData), -WHEEL_DELTA, WHEEL_DELTA, 1.0f, -1.0f );
|
|
if( l > 0 )
|
|
{
|
|
DeviceInput diUp = DeviceInput(dev, up, 1.0f, tm);
|
|
DeviceInput diDown = DeviceInput(dev, down, 0.0f, tm);
|
|
while( fWheelDelta >= WHEEL_DELTA )
|
|
{
|
|
ButtonPressed( diUp );
|
|
ButtonPressed( diDown );
|
|
INPUTFILTER->UpdateMouseWheel(fWheelDelta);
|
|
fWheelDelta -= WHEEL_DELTA;
|
|
}
|
|
}
|
|
else if( l < 0 )
|
|
{
|
|
DeviceInput diDown = DeviceInput(dev, down, 1.0f, tm);
|
|
DeviceInput diUp = DeviceInput(dev, up, 0.0f, tm);
|
|
while( fWheelDelta <= -WHEEL_DELTA )
|
|
{
|
|
ButtonPressed( diDown );
|
|
ButtonPressed( diUp );
|
|
INPUTFILTER->UpdateMouseWheel(fWheelDelta);
|
|
fWheelDelta += WHEEL_DELTA;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
DeviceInput diUp = DeviceInput(dev, up, 0.0f, tm);
|
|
ButtonPressed( diUp );
|
|
DeviceInput diDown = DeviceInput(dev, down, 0.0f, tm);
|
|
ButtonPressed( diDown );
|
|
}
|
|
}
|
|
break;
|
|
default: LOG->MapLog( "unknown input",
|
|
"Mouse '%s' is returning an unknown mouse offset [axis], %i",
|
|
device.m_sName.c_str(), in.ofs );
|
|
continue;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
switch(in.ofs)
|
|
{
|
|
// joystick
|
|
case DIJOFS_X: up = JOY_LEFT; down = JOY_RIGHT; break;
|
|
case DIJOFS_Y: up = JOY_UP; down = JOY_DOWN; break;
|
|
case DIJOFS_Z: up = JOY_Z_UP; down = JOY_Z_DOWN; break;
|
|
case DIJOFS_RX: up = JOY_ROT_UP; down = JOY_ROT_DOWN; break;
|
|
case DIJOFS_RY: up = JOY_ROT_LEFT; down = JOY_ROT_RIGHT; break;
|
|
case DIJOFS_RZ: up = JOY_ROT_Z_UP; down = JOY_ROT_Z_DOWN; break;
|
|
case DIJOFS_SLIDER(0): up = JOY_AUX_1; down = JOY_AUX_2; break;
|
|
case DIJOFS_SLIDER(1): up = JOY_AUX_3; down = JOY_AUX_4; break;
|
|
default: LOG->MapLog( "unknown input",
|
|
"Controller '%s' is returning an unknown joystick offset, %i",
|
|
device.m_sName.c_str(), in.ofs );
|
|
continue;
|
|
}
|
|
float l = SCALE( int(evtbuf[i].dwData), 0.0f, 100.0f, 0.0f, 1.0f );
|
|
ButtonPressed( DeviceInput(dev, up, max(-l,0), tm) );
|
|
ButtonPressed( DeviceInput(dev, down, max(+l,0), tm) );
|
|
}
|
|
break;
|
|
}
|
|
|
|
case in.HAT:
|
|
{
|
|
const int pos = TranslatePOV( evtbuf[i].dwData );
|
|
ButtonPressed( DeviceInput(dev, JOY_HAT_UP, !!(pos & HAT_UP_MASK), tm) );
|
|
ButtonPressed( DeviceInput(dev, JOY_HAT_DOWN, !!(pos & HAT_DOWN_MASK), tm) );
|
|
ButtonPressed( DeviceInput(dev, JOY_HAT_LEFT, !!(pos & HAT_LEFT_MASK), tm) );
|
|
ButtonPressed( DeviceInput(dev, JOY_HAT_RIGHT, !!(pos & HAT_RIGHT_MASK), tm) );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void InputHandler_DInput::PollAndAcquireDevices( bool bBuffered )
|
|
{
|
|
for( unsigned i = 0; i < Devices.size(); ++i )
|
|
{
|
|
if( Devices[i].buffered != bBuffered )
|
|
continue;
|
|
|
|
HRESULT hr = Devices[i].Device->Poll();
|
|
if( hr == DIERR_INPUTLOST || hr == DIERR_NOTACQUIRED )
|
|
{
|
|
INPUTFILTER->ResetDevice( Devices[i].dev );
|
|
|
|
/* This will fail with "access denied" on the keyboard if we don't
|
|
* have focus. */
|
|
hr = Devices[i].Device->Acquire();
|
|
if( hr != DI_OK )
|
|
continue;
|
|
|
|
Devices[i].Device->Poll();
|
|
}
|
|
}
|
|
}
|
|
|
|
void InputHandler_DInput::Update()
|
|
{
|
|
// Handle polled devices. Handle buffered, too, if there's no input thread to do it.
|
|
PollAndAcquireDevices( false );
|
|
if( !m_InputThread.IsCreated() )
|
|
PollAndAcquireDevices( true );
|
|
|
|
for( unsigned i = 0; i < Devices.size(); ++i )
|
|
{
|
|
if( !Devices[i].buffered )
|
|
{
|
|
UpdatePolled( Devices[i], RageZeroTimer );
|
|
}
|
|
else if( !m_InputThread.IsCreated() )
|
|
{
|
|
// If we have an input thread, it'll handle buffered devices.
|
|
UpdateBuffered( Devices[i], RageZeroTimer );
|
|
}
|
|
}
|
|
|
|
InputHandler::UpdateTimer();
|
|
}
|
|
|
|
const float POLL_FOR_JOYSTICK_CHANGES_LENGTH_SECONDS = 15.0f;
|
|
const float POLL_FOR_JOYSTICK_CHANGES_EVERY_SECONDS = 0.25f;
|
|
|
|
bool InputHandler_DInput::DevicesChanged()
|
|
{
|
|
/* GetNumJoysticksSlow() blocks DirectInput for a while even if called from a
|
|
* different thread, so we can't poll with it. GetNumHidDevices() is fast,
|
|
* but sometimes the DirectInput joysticks haven't updated by the time the
|
|
* HID registry value changes. So, poll using GetNumHidDevices(). When that
|
|
* changes, poll using GetNumJoysticksSlow() for a little while to give
|
|
* DirectInput time to catch up. On this XP machine (which? -aj), it takes
|
|
* 2-10 DirectInput polls (0.5-2.5 seconds) to catch a newly installed device
|
|
* after the registry value changes, and catches non-new plugged/unplugged
|
|
* devices on the first DirectInputPoll. Note that this "poll for N seconds"
|
|
* method will not work if the Add New Hardware wizard halts device
|
|
* installation to wait for a driver. Most of the joysticks people would want
|
|
* to use don't prompt for a driver though, and the wizard adds them pretty quickly. */
|
|
|
|
int iOldNumHidDevices = m_iLastSeenNumHidDevices;
|
|
m_iLastSeenNumHidDevices = GetNumHidDevices();
|
|
if( iOldNumHidDevices != m_iLastSeenNumHidDevices )
|
|
{
|
|
LOG->Warn( "HID devices changes" );
|
|
m_iNumTimesLeftToPollForJoysticksChanged = (int)(POLL_FOR_JOYSTICK_CHANGES_LENGTH_SECONDS / POLL_FOR_JOYSTICK_CHANGES_EVERY_SECONDS);
|
|
}
|
|
|
|
if( m_iNumTimesLeftToPollForJoysticksChanged > 0 )
|
|
{
|
|
static RageTimer timerPollJoysticks;
|
|
if( timerPollJoysticks.Ago() >= POLL_FOR_JOYSTICK_CHANGES_EVERY_SECONDS )
|
|
{
|
|
m_iNumTimesLeftToPollForJoysticksChanged--;
|
|
timerPollJoysticks.Touch();
|
|
LOG->Warn( "polling for joystick changes" );
|
|
|
|
int iOldNumJoysticks = m_iLastSeenNumJoysticks;
|
|
m_iLastSeenNumJoysticks = GetNumJoysticksSlow();
|
|
if( iOldNumJoysticks != m_iLastSeenNumJoysticks )
|
|
{
|
|
LOG->Warn( "joysticks changed" );
|
|
m_iNumTimesLeftToPollForJoysticksChanged = 0;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void InputHandler_DInput::InputThreadMain()
|
|
{
|
|
if(!SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST))
|
|
LOG->Warn(werr_ssprintf(GetLastError(), "Failed to set DirectInput thread priority"));
|
|
|
|
// Enable priority boosting.
|
|
SetThreadPriorityBoost( GetCurrentThread(), FALSE );
|
|
|
|
vector<DIDevice*> BufferedDevices;
|
|
HANDLE Handle = CreateEvent( NULL, FALSE, FALSE, NULL );
|
|
for( unsigned i = 0; i < Devices.size(); ++i )
|
|
{
|
|
if( !Devices[i].buffered )
|
|
continue;
|
|
|
|
BufferedDevices.push_back( &Devices[i] );
|
|
|
|
Devices[i].Device->Unacquire();
|
|
HRESULT hr = Devices[i].Device->SetEventNotification( Handle );
|
|
if( FAILED(hr) )
|
|
LOG->Warn( "IDirectInputDevice2_SetEventNotification failed on %i", i );
|
|
Devices[i].Device->Acquire();
|
|
}
|
|
|
|
while( !m_bShutdown )
|
|
{
|
|
CHECKPOINT;
|
|
if( BufferedDevices.size() )
|
|
{
|
|
// Update buffered devices.
|
|
PollAndAcquireDevices( true );
|
|
|
|
int ret = WaitForSingleObjectEx( Handle, 50, true );
|
|
if( ret == -1 )
|
|
{
|
|
LOG->Trace( werr_ssprintf(GetLastError(), "WaitForSingleObjectEx failed") );
|
|
continue;
|
|
}
|
|
|
|
/* Update devices even if no event was triggered, since this also
|
|
* checks for focus loss. */
|
|
RageTimer now;
|
|
for( unsigned i = 0; i < BufferedDevices.size(); ++i )
|
|
UpdateBuffered( *BufferedDevices[i], now );
|
|
}
|
|
CHECKPOINT;
|
|
|
|
// If we have no buffered devices, we didn't delay at WaitForMultipleObjectsEx.
|
|
if( BufferedDevices.size() == 0 )
|
|
usleep( 50000 );
|
|
CHECKPOINT;
|
|
}
|
|
CHECKPOINT;
|
|
|
|
for( unsigned i = 0; i < Devices.size(); ++i )
|
|
{
|
|
if( !Devices[i].buffered )
|
|
continue;
|
|
|
|
Devices[i].Device->Unacquire();
|
|
Devices[i].Device->SetEventNotification( NULL );
|
|
}
|
|
|
|
CloseHandle(Handle);
|
|
}
|
|
|
|
void InputHandler_DInput::GetDevicesAndDescriptions( vector<InputDeviceInfo>& vDevicesOut )
|
|
{
|
|
for( unsigned i=0; i < Devices.size(); ++i )
|
|
vDevicesOut.push_back( InputDeviceInfo(Devices[i].dev, Devices[i].m_sName) );
|
|
}
|
|
|
|
static wchar_t ScancodeAndKeysToChar( DWORD scancode, unsigned char keys[256] )
|
|
{
|
|
static HKL layout = GetKeyboardLayout(0); // 0 == current thread
|
|
UINT vk = MapVirtualKeyEx( scancode, 1, layout );
|
|
|
|
static bool bInitialized = false;
|
|
|
|
typedef int (WINAPI TOUNICODEEX)( IN UINT wVirtKey, IN UINT wScanCode, IN CONST BYTE *lpKeyState, OUT LPWSTR pwszBuff, IN int cchBuff, IN UINT wFlags, IN HKL dwhkl );
|
|
static TOUNICODEEX *pToUnicodeEx;
|
|
|
|
if( !bInitialized )
|
|
{
|
|
bInitialized = true;
|
|
HMODULE hModule = GetModuleHandle( "user32.dll" );
|
|
pToUnicodeEx = (TOUNICODEEX *) GetProcAddress( hModule, "ToUnicodeEx" );
|
|
}
|
|
|
|
unsigned short result[2]; // ToAscii writes a max of 2 chars
|
|
ZERO( result );
|
|
|
|
if( pToUnicodeEx != NULL )
|
|
{
|
|
int iNum = pToUnicodeEx( vk, scancode, keys, (LPWSTR)result, 2, 0, layout );
|
|
if( iNum == 1 )
|
|
return result[0];
|
|
}
|
|
else
|
|
{
|
|
int iNum = ToAsciiEx( vk, scancode, keys, result, 0, layout );
|
|
// iNum == 2 will happen only for dead keys. See MSDN for ToAsciiEx.
|
|
if( iNum == 1 )
|
|
{
|
|
RString s = RString()+(char)result[0];
|
|
return ConvertCodepageToWString( s, CP_ACP )[0];
|
|
}
|
|
}
|
|
|
|
return '\0';
|
|
}
|
|
|
|
wchar_t InputHandler_DInput::DeviceButtonToChar( DeviceButton button, bool bUseCurrentKeyModifiers )
|
|
{
|
|
// ToAsciiEx maps these keys to a character. They shouldn't be mapped to any character.
|
|
switch( button )
|
|
{
|
|
case KEY_ESC:
|
|
case KEY_TAB:
|
|
case KEY_ENTER:
|
|
case KEY_BACK:
|
|
return '\0';
|
|
}
|
|
|
|
FOREACH_CONST( DIDevice, Devices, d )
|
|
{
|
|
if( d->type != DIDevice::KEYBOARD )
|
|
continue;
|
|
|
|
FOREACH_CONST( input_t, d->Inputs, i )
|
|
{
|
|
if( button != i->num )
|
|
continue;
|
|
|
|
unsigned char keys[256];
|
|
ZERO( keys );
|
|
if( bUseCurrentKeyModifiers )
|
|
GetKeyboardState(keys);
|
|
// todo: handle Caps Lock -freem
|
|
wchar_t c = ScancodeAndKeysToChar( i->ofs, keys );
|
|
if( c )
|
|
return c;
|
|
}
|
|
}
|
|
|
|
return InputHandler::DeviceButtonToChar( button, bUseCurrentKeyModifiers );
|
|
}
|
|
|
|
/*
|
|
* (c) 2003-2004 Glenn Maynard
|
|
* All rights reserved.
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
|
* copy of this software and associated documentation files (the
|
|
* "Software"), to deal in the Software without restriction, including
|
|
* without limitation the rights to use, copy, modify, merge, publish,
|
|
* distribute, and/or sell copies of the Software, and to permit persons to
|
|
* whom the Software is furnished to do so, provided that the above
|
|
* copyright notice(s) and this permission notice appear in all copies of
|
|
* the Software and that both the above copyright notice(s) and this
|
|
* permission notice appear in supporting documentation.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
|
|
* THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS
|
|
* INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT
|
|
* OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
|
* OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
* PERFORMANCE OF THIS SOFTWARE.
|
|
*/
|