From 438d7a5305e070777cc962d1f2a5716e95ee2b16 Mon Sep 17 00:00:00 2001 From: Jason Felds Date: Tue, 30 Apr 2013 23:03:45 -0400 Subject: [PATCH] Nested loops were simple. --- .../InputHandler/InputHandler_DirectInput.cpp | 1792 ++++++++--------- 1 file changed, 896 insertions(+), 896 deletions(-) diff --git a/src/arch/InputHandler/InputHandler_DirectInput.cpp b/src/arch/InputHandler/InputHandler_DirectInput.cpp index c74a6bef19..add401ed43 100644 --- a/src/arch/InputHandler/InputHandler_DirectInput.cpp +++ b/src/arch/InputHandler/InputHandler_DirectInput.cpp @@ -1,896 +1,896 @@ -#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 Devices; - -// Number of joysticks found: -static int g_iNumJoysticks; - -static BOOL CALLBACK EnumDevicesCallback( const DIDEVICEINSTANCE *pdidInstance, void *pContext ) -{ - DIDevice device; - - switch( pdidInstance->dwDevType & 0xFF ) - { - case DI8DEVTYPE_KEYBOARD: device.type = device.KEYBOARD; break; - case DI8DEVTYPE_JOYSTICK: device.type = device.JOYSTICK; break; - case DI8DEVTYPE_MOUSE: device.type = device.MOUSE; break; - default: 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( DI8DEVTYPE_JOYSTICK, 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( DI8DEVTYPE_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( DI8DEVTYPE_JOYSTICK, 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( DI8DEVTYPE_MOUSE, 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 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& 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. - */ +#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 Devices; + +// Number of joysticks found: +static int g_iNumJoysticks; + +static BOOL CALLBACK EnumDevicesCallback( const DIDEVICEINSTANCE *pdidInstance, void *pContext ) +{ + DIDevice device; + + switch( pdidInstance->dwDevType & 0xFF ) + { + case DI8DEVTYPE_KEYBOARD: device.type = device.KEYBOARD; break; + case DI8DEVTYPE_JOYSTICK: device.type = device.JOYSTICK; break; + case DI8DEVTYPE_MOUSE: device.type = device.MOUSE; break; + default: 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( DI8DEVTYPE_JOYSTICK, 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( DI8DEVTYPE_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( DI8DEVTYPE_JOYSTICK, 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( DI8DEVTYPE_MOUSE, 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 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& 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'; + } + + for (DIDevice const &d : Devices) + { + if( d.type != DIDevice::KEYBOARD ) + continue; + + for (input_t const &i : d.Inputs) + { + 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. + */