diff options
Diffstat (limited to 'inputsystem')
| -rw-r--r-- | inputsystem/Xbox/xbox.def | 3 | ||||
| -rw-r--r-- | inputsystem/inputsystem.cpp | 1550 | ||||
| -rw-r--r-- | inputsystem/inputsystem.h | 469 | ||||
| -rw-r--r-- | inputsystem/inputsystem.vpc | 55 | ||||
| -rw-r--r-- | inputsystem/joystick_sdl.cpp | 615 | ||||
| -rw-r--r-- | inputsystem/key_translation.cpp | 911 | ||||
| -rw-r--r-- | inputsystem/key_translation.h | 38 | ||||
| -rw-r--r-- | inputsystem/novint.cpp | 138 | ||||
| -rw-r--r-- | inputsystem/posix_stubs.h | 75 | ||||
| -rw-r--r-- | inputsystem/steamcontroller.cpp | 695 |
10 files changed, 4549 insertions, 0 deletions
diff --git a/inputsystem/Xbox/xbox.def b/inputsystem/Xbox/xbox.def new file mode 100644 index 0000000..715750d --- /dev/null +++ b/inputsystem/Xbox/xbox.def @@ -0,0 +1,3 @@ +LIBRARY inputsystem_360.dll +EXPORTS + CreateInterface @1 diff --git a/inputsystem/inputsystem.cpp b/inputsystem/inputsystem.cpp new file mode 100644 index 0000000..5f11ac7 --- /dev/null +++ b/inputsystem/inputsystem.cpp @@ -0,0 +1,1550 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//===========================================================================// + +#include "inputsystem.h" +#include "key_translation.h" +#include "inputsystem/ButtonCode.h" +#include "inputsystem/AnalogCode.h" +#include "tier0/etwprof.h" +#include "tier1/convar.h" +#include "tier0/icommandline.h" + +#if defined( USE_SDL ) +#undef M_PI +#include "SDL.h" +static void initKeymap(void); +#endif + +#ifdef _X360 +#include "xbox/xbox_win32stubs.h" +#endif +ConVar joy_xcontroller_found( "joy_xcontroller_found", "1", FCVAR_HIDDEN, "Automatically set to 1 if an xcontroller has been detected." ); + +//----------------------------------------------------------------------------- +// Singleton instance +//----------------------------------------------------------------------------- +static CInputSystem g_InputSystem; +EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CInputSystem, IInputSystem, + INPUTSYSTEM_INTERFACE_VERSION, g_InputSystem ); + + + +#if defined( WIN32 ) && !defined( _X360 ) +typedef BOOL (WINAPI *RegisterRawInputDevices_t) +( + PCRAWINPUTDEVICE pRawInputDevices, + UINT uiNumDevices, + UINT cbSize +); + +typedef UINT (WINAPI *GetRawInputData_t) +( + HRAWINPUT hRawInput, + UINT uiCommand, + LPVOID pData, + PUINT pcbSize, + UINT cbSizeHeader +); + +RegisterRawInputDevices_t pfnRegisterRawInputDevices; +GetRawInputData_t pfnGetRawInputData; +#endif + + + +//----------------------------------------------------------------------------- +// Constructor, destructor +//----------------------------------------------------------------------------- +CInputSystem::CInputSystem() +{ + m_nLastPollTick = m_nLastSampleTick = m_StartupTimeTick = 0; + m_ChainedWndProc = 0; + m_hAttachedHWnd = 0; + m_hEvent = NULL; + m_bEnabled = true; + m_bPumpEnabled = true; + m_bIsPolling = false; + m_JoysticksEnabled.ClearAllFlags(); + m_nJoystickCount = 0; + m_bJoystickInitialized = false; + m_nPollCount = 0; + m_PrimaryUserId = INVALID_USER_ID; + m_uiMouseWheel = 0; + m_bXController = false; + m_bRawInputSupported = false; + m_bSteamController = false; + m_bSteamControllerActionsInitialized = false; + m_bSteamControllerActive = false; + + Assert( (MAX_JOYSTICKS + 7) >> 3 << sizeof(unsigned short) ); + + m_pXInputDLL = NULL; + m_pRawInputDLL = NULL; + +#if defined ( _WIN32 ) && !defined ( _X360 ) + // NVNT DLL + m_pNovintDLL = NULL; +#endif + + m_bConsoleTextMode = false; + m_bSkipControllerInitialization = false; + + if ( CommandLine()->CheckParm( "-nosteamcontroller" ) ) + { + m_bSkipControllerInitialization = true; + } +} + +CInputSystem::~CInputSystem() +{ + if ( m_pXInputDLL ) + { + Sys_UnloadModule( m_pXInputDLL ); + m_pXInputDLL = NULL; + } + + if ( m_pRawInputDLL ) + { + Sys_UnloadModule( m_pRawInputDLL ); + m_pRawInputDLL = NULL; + } + +#if defined ( _WIN32 ) && !defined ( _X360 ) + // NVNT DLL unload + if ( m_pNovintDLL ) + { + Sys_UnloadModule( m_pNovintDLL ); + m_pNovintDLL = NULL; + } +#endif +} + + +//----------------------------------------------------------------------------- +// Initialization +//----------------------------------------------------------------------------- +InitReturnVal_t CInputSystem::Init() +{ + InitReturnVal_t nRetVal = BaseClass::Init(); + if ( nRetVal != INIT_OK ) + return nRetVal; + + m_StartupTimeTick = Plat_MSTime(); + + +#if !defined( POSIX ) + if ( IsPC() ) + { + m_uiMouseWheel = RegisterWindowMessage( "MSWHEEL_ROLLMSG" ); + } + + m_hEvent = CreateEvent( NULL, FALSE, FALSE, NULL ); + if ( !m_hEvent ) + return INIT_FAILED; +#endif + + // Initialize the input system copy of the steam API context, for use by controller stuff (don't do this if we're a dedicated server). + if ( !m_bSkipControllerInitialization && SteamAPI_InitSafe() ) + { + m_SteamAPIContext.Init(); + if ( m_SteamAPIContext.SteamController() ) + { + m_SteamAPIContext.SteamController()->Init(); + m_bSteamController = InitializeSteamControllers(); + m_bSteamControllerActionsInitialized = m_bSteamController && InitializeSteamControllerActionSets(); + if ( m_bSteamControllerActionsInitialized ) + { + ActivateSteamControllerActionSet( GAME_ACTION_SET_MENUCONTROLS ); + } + } + } + + ButtonCode_InitKeyTranslationTable(); + ButtonCode_UpdateScanCodeLayout(); + + joy_xcontroller_found.SetValue( 0 ); + if ( IsPC() && !m_bConsoleTextMode ) + { + InitializeJoysticks(); + if ( m_bXController ) + joy_xcontroller_found.SetValue( 1 ); + + +#if defined( PLATFORM_WINDOWS_PC ) + // NVNT try and load and initialize through the haptic dll, but only if the drivers are installed + HMODULE hdl = LoadLibraryEx( "hdl.dll", NULL, LOAD_LIBRARY_AS_DATAFILE ); + + if ( hdl ) + { + m_pNovintDLL = Sys_LoadModule( "haptics.dll" ); + if ( m_pNovintDLL ) + { + InitializeNovintDevices(); + } + FreeLibrary( hdl ); + } +#endif + } + +#if defined( _X360 ) + SetPrimaryUserId( XBX_GetPrimaryUserId() ); + InitializeXDevices(); + m_bXController = true; +#endif + +#if defined( USE_SDL ) + + m_bRawInputSupported = true; + initKeymap(); + +#elif defined( WIN32 ) && !defined( _X360 ) + + // Check if this version of windows supports raw mouse input (later than win2k) + m_bRawInputSupported = false; + + CSysModule *m_pRawInputDLL = Sys_LoadModule( "USER32.dll" ); + if ( m_pRawInputDLL ) + { + pfnRegisterRawInputDevices = (RegisterRawInputDevices_t)GetProcAddress( (HMODULE)m_pRawInputDLL, "RegisterRawInputDevices" ); + pfnGetRawInputData = (GetRawInputData_t)GetProcAddress( (HMODULE)m_pRawInputDLL, "GetRawInputData" ); + if ( pfnRegisterRawInputDevices && pfnGetRawInputData ) + m_bRawInputSupported = true; + } + +#endif + + return INIT_OK; +} + +bool CInputSystem::Connect( CreateInterfaceFn factory ) +{ + if ( !BaseClass::Connect( factory ) ) + return false; + +#if defined( USE_SDL ) + m_pLauncherMgr = (ILauncherMgr *)factory( SDLMGR_INTERFACE_VERSION, NULL ); +#endif + + return true; +} + + +//----------------------------------------------------------------------------- +// Shutdown +//----------------------------------------------------------------------------- +void CInputSystem::Shutdown() +{ +#if !defined( POSIX ) + if ( m_hEvent != NULL ) + { + CloseHandle( m_hEvent ); + m_hEvent = NULL; + } +#endif + + if ( IsPC() ) + { + ShutdownJoysticks(); + } + + BaseClass::Shutdown(); +} + + +//----------------------------------------------------------------------------- +// Sleep until input +//----------------------------------------------------------------------------- +void CInputSystem::SleepUntilInput( int nMaxSleepTimeMS ) +{ +#if defined( _WIN32 ) && !defined( USE_SDL ) + if ( nMaxSleepTimeMS < 0 ) + { + nMaxSleepTimeMS = INFINITE; + } + + MsgWaitForMultipleObjects( 1, &m_hEvent, FALSE, nMaxSleepTimeMS, QS_ALLEVENTS ); +#elif defined( USE_SDL ) + m_pLauncherMgr->WaitUntilUserInput( nMaxSleepTimeMS ); +#else +#warning "need a SleepUntilInput impl" +#endif +} + + + +//----------------------------------------------------------------------------- +// Callback to call into our class +//----------------------------------------------------------------------------- +#if defined( PLATFORM_WINDOWS ) +static LRESULT CALLBACK InputSystemWindowProc( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam ) +{ + return g_InputSystem.WindowProc( hwnd, uMsg, wParam, lParam ); +} +#endif + + +//----------------------------------------------------------------------------- +// Hooks input listening up to a window +//----------------------------------------------------------------------------- +void CInputSystem::AttachToWindow( void* hWnd ) +{ + Assert( m_hAttachedHWnd == 0 ); + if ( m_hAttachedHWnd ) + { + Warning( "CInputSystem::AttachToWindow: Cannot attach to two windows at once!\n" ); + return; + } + +#if defined( PLATFORM_WINDOWS ) + m_ChainedWndProc = (WNDPROC)GetWindowLongPtrW( (HWND)hWnd, GWLP_WNDPROC ); + SetWindowLongPtrW( (HWND)hWnd, GWLP_WNDPROC, (LONG_PTR)InputSystemWindowProc ); +#endif + + m_hAttachedHWnd = (HWND)hWnd; + +#if defined( PLATFORM_WINDOWS_PC ) && !defined( USE_SDL ) + // NVNT inform novint devices of window + AttachWindowToNovintDevices( hWnd ); + + // register to read raw mouse input + +#if !defined(HID_USAGE_PAGE_GENERIC) +#define HID_USAGE_PAGE_GENERIC ((USHORT) 0x01) +#endif +#if !defined(HID_USAGE_GENERIC_MOUSE) +#define HID_USAGE_GENERIC_MOUSE ((USHORT) 0x02) +#endif + + if ( m_bRawInputSupported ) + { + RAWINPUTDEVICE Rid[1]; + Rid[0].usUsagePage = HID_USAGE_PAGE_GENERIC; + Rid[0].usUsage = HID_USAGE_GENERIC_MOUSE; + Rid[0].dwFlags = RIDEV_INPUTSINK; + Rid[0].hwndTarget = g_InputSystem.m_hAttachedHWnd; // GetHhWnd; + pfnRegisterRawInputDevices(Rid, ARRAYSIZE(Rid), sizeof(Rid[0])); + } +#endif + + // New window, clear input state + ClearInputState(); +} + + +//----------------------------------------------------------------------------- +// Unhooks input listening from a window +//----------------------------------------------------------------------------- +void CInputSystem::DetachFromWindow( ) +{ + if ( !m_hAttachedHWnd ) + return; + + ResetInputState(); + +#if defined( PLATFORM_WINDOWS ) + if ( m_ChainedWndProc ) + { + SetWindowLongPtrW( m_hAttachedHWnd, GWLP_WNDPROC, (LONG_PTR)m_ChainedWndProc ); + m_ChainedWndProc = 0; + } +#endif + +#if defined( PLATFORM_WINDOWS_PC ) + // NVNT inform novint devices loss of window + DetachWindowFromNovintDevices( ); +#endif + m_hAttachedHWnd = 0; +} + + +//----------------------------------------------------------------------------- +// Enables/disables input +//----------------------------------------------------------------------------- +void CInputSystem::EnableInput( bool bEnable ) +{ + m_bEnabled = bEnable; +} + + +//----------------------------------------------------------------------------- +// Enables/disables the inputsystem windows message pump +//----------------------------------------------------------------------------- +void CInputSystem::EnableMessagePump( bool bEnable ) +{ + m_bPumpEnabled = bEnable; +} + + +//----------------------------------------------------------------------------- +// Clears the input state, doesn't generate key-up messages +//----------------------------------------------------------------------------- +void CInputSystem::ClearInputState() +{ + for ( int i = 0; i < INPUT_STATE_COUNT; ++i ) + { + InputState_t& state = m_InputState[i]; + state.m_ButtonState.ClearAll(); + memset( state.m_pAnalogDelta, 0, ANALOG_CODE_LAST * sizeof(int) ); + memset( state.m_pAnalogValue, 0, ANALOG_CODE_LAST * sizeof(int) ); + memset( state.m_ButtonPressedTick, 0, BUTTON_CODE_LAST * sizeof(int) ); + memset( state.m_ButtonReleasedTick, 0, BUTTON_CODE_LAST * sizeof(int) ); + state.m_Events.Purge(); + state.m_bDirty = false; + } + memset( m_appXKeys, 0, XUSER_MAX_COUNT * XK_MAX_KEYS * sizeof(appKey_t) ); +} + +//----------------------------------------------------------------------------- +// Resets the input state +//----------------------------------------------------------------------------- +void CInputSystem::ResetInputState() +{ + ReleaseAllButtons(); + ZeroAnalogState( 0, ANALOG_CODE_LAST - 1 ); + memset( m_appXKeys, 0, XUSER_MAX_COUNT * XK_MAX_KEYS * sizeof(appKey_t) ); + + m_mouseRawAccumX = m_mouseRawAccumY = 0; +} + + +//----------------------------------------------------------------------------- +// Convert back + forth between ButtonCode/AnalogCode + strings +//----------------------------------------------------------------------------- +const char *CInputSystem::ButtonCodeToString( ButtonCode_t code ) const +{ + return ButtonCode_ButtonCodeToString( code, m_bXController ); +} + +const char *CInputSystem::AnalogCodeToString( AnalogCode_t code ) const +{ + return AnalogCode_AnalogCodeToString( code ); +} + +ButtonCode_t CInputSystem::StringToButtonCode( const char *pString ) const +{ + return ButtonCode_StringToButtonCode( pString, m_bXController ); +} + +AnalogCode_t CInputSystem::StringToAnalogCode( const char *pString ) const +{ + return AnalogCode_StringToAnalogCode( pString ); +} + + +//----------------------------------------------------------------------------- +// Convert back + forth between virtual codes + button codes +// FIXME: This is a temporary piece of code +//----------------------------------------------------------------------------- +ButtonCode_t CInputSystem::VirtualKeyToButtonCode( int nVirtualKey ) const +{ + return ButtonCode_VirtualKeyToButtonCode( nVirtualKey ); +} + +int CInputSystem::ButtonCodeToVirtualKey( ButtonCode_t code ) const +{ + return ButtonCode_ButtonCodeToVirtualKey( code ); +} + +ButtonCode_t CInputSystem::XKeyToButtonCode( int nPort, int nXKey ) const +{ + if ( m_bXController ) + return ButtonCode_XKeyToButtonCode( nPort, nXKey ); + return KEY_NONE; +} + +ButtonCode_t CInputSystem::ScanCodeToButtonCode( int lParam ) const +{ + return ButtonCode_ScanCodeToButtonCode( lParam ); +} + +ButtonCode_t CInputSystem::SKeyToButtonCode( int nPort, int nXKey ) const +{ + return ButtonCode_SKeyToButtonCode( nPort, nXKey ); +} + +//----------------------------------------------------------------------------- +// Post an event to the queue +//----------------------------------------------------------------------------- +void CInputSystem::PostEvent( int nType, int nTick, int nData, int nData2, int nData3 ) +{ + InputEvent_t event; + + memset( &event, 0, sizeof(event) ); + event.m_nType = nType; + event.m_nTick = nTick; + event.m_nData = nData; + event.m_nData2 = nData2; + event.m_nData3 = nData3; + + PostUserEvent( event ); +} + + +//----------------------------------------------------------------------------- +// Post an button press event to the queue +//----------------------------------------------------------------------------- +void CInputSystem::PostButtonPressedEvent( InputEventType_t nType, int nTick, ButtonCode_t scanCode, ButtonCode_t virtualCode ) +{ + InputState_t &state = m_InputState[ m_bIsPolling ]; + if ( !state.m_ButtonState.IsBitSet( scanCode ) ) + { + // Update button state + state.m_ButtonState.Set( scanCode ); + state.m_ButtonPressedTick[ scanCode ] = nTick; + + // Add this event to the app-visible event queue + PostEvent( nType, nTick, scanCode, virtualCode ); + +#if defined( _X360 ) + // FIXME: Remove! Fake a windows message for vguimatsurface's input handler + if ( IsJoystickCode( scanCode ) ) + { + ProcessEvent( WM_XCONTROLLER_KEY, scanCode, 1 ); + } +#endif + } +} + + +//----------------------------------------------------------------------------- +// Post an button release event to the queue +//----------------------------------------------------------------------------- +void CInputSystem::PostButtonReleasedEvent( InputEventType_t nType, int nTick, ButtonCode_t scanCode, ButtonCode_t virtualCode ) +{ + InputState_t &state = m_InputState[ m_bIsPolling ]; + if ( state.m_ButtonState.IsBitSet( scanCode ) ) + { + // Update button state + state.m_ButtonState.Clear( scanCode ); + state.m_ButtonReleasedTick[ scanCode ] = nTick; + + // Add this event to the app-visible event queue + PostEvent( nType, nTick, scanCode, virtualCode ); + +#if defined( _X360 ) + // FIXME: Remove! Fake a windows message for vguimatsurface's input handler + if ( IsJoystickCode( scanCode ) ) + { + ProcessEvent( WM_XCONTROLLER_KEY, scanCode, 0 ); + } +#endif + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Pass Joystick button events through the engine's window procs +//----------------------------------------------------------------------------- +void CInputSystem::ProcessEvent( UINT uMsg, WPARAM wParam, LPARAM lParam ) +{ +#if !defined( POSIX ) + // To prevent subtle input timing bugs, all button events must be fed + // through the window proc once per frame, same as the keyboard and mouse. + HWND hWnd = GetFocus(); + WNDPROC windowProc = (WNDPROC)GetWindowLongPtrW(hWnd, GWLP_WNDPROC ); + if ( windowProc ) + { + windowProc( hWnd, uMsg, wParam, lParam ); + } +#endif +} + + +//----------------------------------------------------------------------------- +// Copies the input state record over +//----------------------------------------------------------------------------- +void CInputSystem::CopyInputState( InputState_t *pDest, const InputState_t &src, bool bCopyEvents ) +{ + pDest->m_Events.RemoveAll(); + pDest->m_bDirty = false; + if ( src.m_bDirty ) + { + pDest->m_ButtonState = src.m_ButtonState; + memcpy( &pDest->m_ButtonPressedTick, &src.m_ButtonPressedTick, sizeof( pDest->m_ButtonPressedTick ) ); + memcpy( &pDest->m_ButtonReleasedTick, &src.m_ButtonReleasedTick, sizeof( pDest->m_ButtonReleasedTick ) ); + memcpy( &pDest->m_pAnalogDelta, &src.m_pAnalogDelta, sizeof( pDest->m_pAnalogDelta ) ); + memcpy( &pDest->m_pAnalogValue, &src.m_pAnalogValue, sizeof( pDest->m_pAnalogValue ) ); + if ( bCopyEvents ) + { + if ( src.m_Events.Count() > 0 ) + { + pDest->m_Events.EnsureCount( src.m_Events.Count() ); + memcpy( pDest->m_Events.Base(), src.m_Events.Base(), src.m_Events.Count() * sizeof(InputEvent_t) ); + } + } + } +} + + +#if defined( PLATFORM_WINDOWS_PC ) +void CInputSystem::PollInputState_Windows() +{ + if ( m_bPumpEnabled ) + { + // Poll mouse + keyboard + MSG msg; + while ( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) ) + { + if ( msg.message == WM_QUIT ) + { + PostEvent( IE_Quit, m_nLastSampleTick ); + break; + } + + TranslateMessage( &msg ); + DispatchMessage( &msg ); + } + + // NOTE: Under some implementations of Win9x, + // dispatching messages can cause the FPU control word to change + SetupFPUControlWord(); + } +} +#endif + +#if defined( USE_SDL ) + +static BYTE scantokey[SDL_NUM_SCANCODES]; + +static void initKeymap(void) +{ + memset(scantokey, '\0', sizeof (scantokey)); + + for (int i = SDL_SCANCODE_A; i <= SDL_SCANCODE_Z; i++) + scantokey[i] = KEY_A + (i - SDL_SCANCODE_A); + for (int i = SDL_SCANCODE_1; i <= SDL_SCANCODE_9; i++) + scantokey[i] = KEY_1 + (i - SDL_SCANCODE_1); + for (int i = SDL_SCANCODE_F1; i <= SDL_SCANCODE_F12; i++) + scantokey[i] = KEY_F1 + (i - SDL_SCANCODE_F1); + for (int i = SDL_SCANCODE_KP_1; i <= SDL_SCANCODE_KP_9; i++) + scantokey[i] = KEY_PAD_1 + (i - SDL_SCANCODE_KP_1); + + scantokey[SDL_SCANCODE_0] = KEY_0; + scantokey[SDL_SCANCODE_KP_0] = KEY_PAD_0; + scantokey[SDL_SCANCODE_RETURN] = KEY_ENTER; + scantokey[SDL_SCANCODE_ESCAPE] = KEY_ESCAPE; + scantokey[SDL_SCANCODE_BACKSPACE] = KEY_BACKSPACE; + scantokey[SDL_SCANCODE_TAB] = KEY_TAB; + scantokey[SDL_SCANCODE_SPACE] = KEY_SPACE; + scantokey[SDL_SCANCODE_MINUS] = KEY_MINUS; + scantokey[SDL_SCANCODE_EQUALS] = KEY_EQUAL; + scantokey[SDL_SCANCODE_LEFTBRACKET] = KEY_LBRACKET; + scantokey[SDL_SCANCODE_RIGHTBRACKET] = KEY_RBRACKET; + scantokey[SDL_SCANCODE_BACKSLASH] = KEY_BACKSLASH; + scantokey[SDL_SCANCODE_SEMICOLON] = KEY_SEMICOLON; + scantokey[SDL_SCANCODE_APOSTROPHE] = KEY_APOSTROPHE; + scantokey[SDL_SCANCODE_GRAVE] = KEY_BACKQUOTE; + scantokey[SDL_SCANCODE_COMMA] = KEY_COMMA; + scantokey[SDL_SCANCODE_PERIOD] = KEY_PERIOD; + scantokey[SDL_SCANCODE_SLASH] = KEY_SLASH; + scantokey[SDL_SCANCODE_CAPSLOCK] = KEY_CAPSLOCK; + scantokey[SDL_SCANCODE_SCROLLLOCK] = KEY_SCROLLLOCK; + scantokey[SDL_SCANCODE_INSERT] = KEY_INSERT; + scantokey[SDL_SCANCODE_HOME] = KEY_HOME; + scantokey[SDL_SCANCODE_PAGEUP] = KEY_PAGEUP; + scantokey[SDL_SCANCODE_DELETE] = KEY_DELETE; + scantokey[SDL_SCANCODE_END] = KEY_END; + scantokey[SDL_SCANCODE_PAGEDOWN] = KEY_PAGEDOWN; + scantokey[SDL_SCANCODE_RIGHT] = KEY_RIGHT; + scantokey[SDL_SCANCODE_LEFT] = KEY_LEFT; + scantokey[SDL_SCANCODE_DOWN] = KEY_DOWN; + scantokey[SDL_SCANCODE_UP] = KEY_UP; + scantokey[SDL_SCANCODE_NUMLOCKCLEAR] = KEY_NUMLOCK; + scantokey[SDL_SCANCODE_KP_DIVIDE] = KEY_PAD_DIVIDE; + scantokey[SDL_SCANCODE_KP_MULTIPLY] = KEY_PAD_MULTIPLY; + scantokey[SDL_SCANCODE_KP_MINUS] = KEY_PAD_MINUS; + scantokey[SDL_SCANCODE_KP_PLUS] = KEY_PAD_PLUS; + // Map keybad enter to enter for vgui. This means vgui dialog won't ever see KEY_PAD_ENTER + scantokey[SDL_SCANCODE_KP_ENTER] = KEY_ENTER; + scantokey[SDL_SCANCODE_KP_PERIOD] = KEY_PAD_DECIMAL; + scantokey[SDL_SCANCODE_APPLICATION] = KEY_APP; + scantokey[SDL_SCANCODE_LCTRL] = KEY_LCONTROL; + scantokey[SDL_SCANCODE_LSHIFT] = KEY_LSHIFT; + scantokey[SDL_SCANCODE_LALT] = KEY_LALT; + scantokey[SDL_SCANCODE_LGUI] = KEY_LWIN; + scantokey[SDL_SCANCODE_RCTRL] = KEY_RCONTROL; + scantokey[SDL_SCANCODE_RSHIFT] = KEY_RSHIFT; + scantokey[SDL_SCANCODE_RALT] = KEY_RALT; + scantokey[SDL_SCANCODE_RGUI] = KEY_RWIN; +} + +bool MapCocoaVirtualKeyToButtonCode( int nCocoaVirtualKeyCode, ButtonCode_t *pOut ) +{ + if ( nCocoaVirtualKeyCode < 0 ) + *pOut = (ButtonCode_t)(-1 * nCocoaVirtualKeyCode); + else + { + nCocoaVirtualKeyCode &= 0x000000ff; + + *pOut = (ButtonCode_t)scantokey[nCocoaVirtualKeyCode]; + } + + return true; +} + +void CInputSystem::PollInputState_Platform() +{ + InputState_t &state = m_InputState[ m_bIsPolling ]; + + if ( m_bPumpEnabled ) + m_pLauncherMgr->PumpWindowsMessageLoop(); + // These are Carbon virtual key codes. AFAIK they don't have a header that defines these, but they are supposed to map + // to the same letters across international keyboards, so our mapping here should work. + CCocoaEvent events[32]; + while ( 1 ) + { + int nEvents = m_pLauncherMgr->GetEvents( events, ARRAYSIZE( events ) ); + if ( nEvents == 0 ) + break; + + for ( int iEvent=0; iEvent < nEvents; iEvent++ ) + { + CCocoaEvent *pEvent = &events[iEvent]; + + switch( pEvent->m_EventType ) + { + case CocoaEvent_Deleted: + break; + + case CocoaEvent_KeyDown: + { + ButtonCode_t virtualCode; + if ( MapCocoaVirtualKeyToButtonCode( pEvent->m_VirtualKeyCode, &virtualCode ) ) + { + ButtonCode_t scanCode = virtualCode; + + if( ( scanCode != BUTTON_CODE_NONE ) ) + { + // For SDL, hitting spacebar causes a SDL_KEYDOWN event, then SDL_TEXTINPUT with + // event.text.text[0] = ' ', and then we get here and wind up sending two events + // to PostButtonPressedEvent. The first is virtualCode = ' ', the 2nd has virtualCode = 0. + // This will confuse Button::OnKeyCodePressed(), which is checking for space keydown + // followed by space keyup. So we ignore all BUTTON_CODE_NONE events here. + PostButtonPressedEvent( IE_ButtonPressed, m_nLastSampleTick, scanCode, virtualCode ); + } + + InputEvent_t event; + memset( &event, 0, sizeof(event) ); + event.m_nTick = GetPollTick(); + // IE_KeyCodeTyped + event.m_nType = IE_FirstVguiEvent + 4; + event.m_nData = scanCode; + g_pInputSystem->PostUserEvent( event ); + + } + + if ( !(pEvent->m_ModifierKeyMask & (1<<eCommandKey) ) && pEvent->m_VirtualKeyCode >= 0 && pEvent->m_UnicodeKey > 0 ) + { + InputEvent_t event; + memset( &event, 0, sizeof(event) ); + event.m_nTick = GetPollTick(); + // IE_KeyTyped + event.m_nType = IE_FirstVguiEvent + 3; + event.m_nData = (int)pEvent->m_UnicodeKey; + g_pInputSystem->PostUserEvent( event ); + } + + } + break; + + case CocoaEvent_KeyUp: + { + ButtonCode_t virtualCode; + if ( MapCocoaVirtualKeyToButtonCode( pEvent->m_VirtualKeyCode, &virtualCode ) ) + { + if( virtualCode != BUTTON_CODE_NONE ) + { + ButtonCode_t scanCode = virtualCode; + PostButtonReleasedEvent( IE_ButtonReleased, m_nLastSampleTick, scanCode, virtualCode ); + } + } + } + break; + + case CocoaEvent_MouseButtonDown: + { + int nButtonMask = pEvent->m_MouseButtonFlags; + ButtonCode_t dblClickCode = BUTTON_CODE_INVALID; + if ( pEvent->m_nMouseClickCount > 1 ) + { + switch( pEvent->m_MouseButton ) + { + default: + case COCOABUTTON_LEFT: + dblClickCode = MOUSE_LEFT; + break; + case COCOABUTTON_RIGHT: + dblClickCode = MOUSE_RIGHT; + break; + case COCOABUTTON_MIDDLE: + dblClickCode = MOUSE_MIDDLE; + break; + case COCOABUTTON_4: + dblClickCode = MOUSE_4; + break; + case COCOABUTTON_5: + dblClickCode = MOUSE_5; + break; + } + } + UpdateMouseButtonState( nButtonMask, dblClickCode ); + } + break; + + case CocoaEvent_MouseButtonUp: + { + int nButtonMask = pEvent->m_MouseButtonFlags; + UpdateMouseButtonState( nButtonMask ); + } + break; + + case CocoaEvent_MouseMove: + { + UpdateMousePositionState( state, (short)pEvent->m_MousePos[0], (short)pEvent->m_MousePos[1] ); + + InputEvent_t event; + memset( &event, 0, sizeof(event) ); + event.m_nTick = GetPollTick(); + // IE_LocateMouseClick + event.m_nType = IE_FirstVguiEvent + 1; + event.m_nData = (short)pEvent->m_MousePos[0]; + event.m_nData2 = (short)pEvent->m_MousePos[1]; + g_pInputSystem->PostUserEvent( event ); + } + break; + + case CocoaEvent_MouseScroll: + { + ButtonCode_t code = (short)pEvent->m_MousePos[1] > 0 ? MOUSE_WHEEL_UP : MOUSE_WHEEL_DOWN; + state.m_ButtonPressedTick[ code ] = state.m_ButtonReleasedTick[ code ] = m_nLastSampleTick; + PostEvent( IE_ButtonPressed, m_nLastSampleTick, code, code ); + PostEvent( IE_ButtonReleased, m_nLastSampleTick, code, code ); + + state.m_pAnalogDelta[ MOUSE_WHEEL ] = pEvent->m_MousePos[1]; + state.m_pAnalogValue[ MOUSE_WHEEL ] += state.m_pAnalogDelta[ MOUSE_WHEEL ]; + PostEvent( IE_AnalogValueChanged, m_nLastSampleTick, MOUSE_WHEEL, state.m_pAnalogValue[ MOUSE_WHEEL ], state.m_pAnalogDelta[ MOUSE_WHEEL ] ); + } + break; + + case CocoaEvent_AppActivate: + { + InputEvent_t event; + memset( &event, 0, sizeof(event) ); + event.m_nType = IE_FirstAppEvent + 2; // IE_AppActivated (defined in sys_mainwind.cpp). + event.m_nData = (bool)(pEvent->m_ModifierKeyMask != 0); + + g_pInputSystem->PostUserEvent( event ); + + if( pEvent->m_ModifierKeyMask == 0 ) + { + // App just lost focus. Handle like WM_ACTIVATEAPP in CInputSystem::WindowProc(). + // Otherwise alt+tab will bring focus away from our app, vgui will still think that + // the alt key is down, and when we regain focus, fun ensues. + g_pInputSystem->ResetInputState(); + } + } + break; + case CocoaEvent_AppQuit: + { + PostEvent( IE_Quit, m_nLastSampleTick ); + + } + break; + break; + } + } + } +} +#endif // USE_SDL + + +//----------------------------------------------------------------------------- +// Polls the current input state +//----------------------------------------------------------------------------- +void CInputSystem::PollInputState() +{ + m_bIsPolling = true; + ++m_nPollCount; + + // Deals with polled input events + InputState_t &queuedState = m_InputState[ INPUT_STATE_QUEUED ]; + CopyInputState( &m_InputState[ INPUT_STATE_CURRENT ], queuedState, true ); + + // Sample the joystick + SampleDevices(); + + // NOTE: This happens after SampleDevices since that updates LastSampleTick + // Also, I believe it's correct to post the joystick events with + // the LastPollTick not updated (not 100% sure though) + m_nLastPollTick = m_nLastSampleTick; + +#if defined( PLATFORM_WINDOWS_PC ) + PollInputState_Windows(); +#endif + +#if defined( USE_SDL ) + PollInputState_Platform(); +#endif + + // Leave the queued state up-to-date with the current + CopyInputState( &queuedState, m_InputState[ INPUT_STATE_CURRENT ], false ); + + m_bIsPolling = false; +} + + +//----------------------------------------------------------------------------- +// Computes the sample tick +//----------------------------------------------------------------------------- +int CInputSystem::ComputeSampleTick() +{ + // This logic will only fail if the app has been running for 49.7 days + int nSampleTick; + + DWORD nCurrentTick = Plat_MSTime(); + if ( nCurrentTick >= m_StartupTimeTick ) + { + nSampleTick = (int)( nCurrentTick - m_StartupTimeTick ); + } + else + { + DWORD nDelta = (DWORD)0xFFFFFFFF - m_StartupTimeTick; + nSampleTick = (int)( nCurrentTick + nDelta ) + 1; + } + return nSampleTick; +} + + +//----------------------------------------------------------------------------- +// How many times has poll been called? +//----------------------------------------------------------------------------- +int CInputSystem::GetPollCount() const +{ + return m_nPollCount; +} + + +//----------------------------------------------------------------------------- +// Samples attached devices and appends events to the input queue +//----------------------------------------------------------------------------- +void CInputSystem::SampleDevices( void ) +{ + m_nLastSampleTick = ComputeSampleTick(); + + PollJoystick(); + +#if defined( PLATFORM_WINDOWS_PC ) + // NVNT if we have device/s poll them. + if ( m_bNovintDevices ) + { + PollNovintDevices(); + } +#endif + + PollSteamControllers(); +} + +//----------------------------------------------------------------------------- +// Purpose: Sets a player as the primary user - all other controllers will be ignored. +//----------------------------------------------------------------------------- +void CInputSystem::SetPrimaryUserId( int userId ) +{ + if ( userId >= XUSER_MAX_COUNT || userId < 0 ) + { + m_PrimaryUserId = INVALID_USER_ID; + } + else + { + m_PrimaryUserId = userId; + } +#if !defined(POSIX) + XBX_SetPrimaryUserId( m_PrimaryUserId ); +#endif + ConMsg("PrimaryUserId is %d\n", m_PrimaryUserId ); +} + +//----------------------------------------------------------------------------- +// Purpose: Forwards rumble info to attached devices +//----------------------------------------------------------------------------- +void CInputSystem::SetRumble( float fLeftMotor, float fRightMotor, int userId ) +{ + SetXDeviceRumble( fLeftMotor, fRightMotor, userId ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Force an immediate stop, transmits immediately to all devices +//----------------------------------------------------------------------------- +void CInputSystem::StopRumble( void ) +{ +#ifdef _X360 + xdevice_t* pXDevice = &m_XDevices[0]; + + for ( int i = 0; i < XUSER_MAX_COUNT; ++i, ++pXDevice ) + { + if ( pXDevice->active ) + { + pXDevice->vibration.wLeftMotorSpeed = 0; + pXDevice->vibration.wRightMotorSpeed = 0; + pXDevice->pendingRumbleUpdate = true; + WriteToXDevice( pXDevice ); + } + } +#else + for ( int i = 0; i < XUSER_MAX_COUNT; ++i ) + { + SetRumble(0.0, 0.0, i); + } +#endif +} + + +//----------------------------------------------------------------------------- +// Joystick interface +//----------------------------------------------------------------------------- +int CInputSystem::GetJoystickCount() const +{ + return m_nJoystickCount; +} + +void CInputSystem::EnableJoystickInput( int nJoystick, bool bEnable ) +{ + m_JoysticksEnabled.SetFlag( 1 << nJoystick, bEnable ); +} + +void CInputSystem::EnableJoystickDiagonalPOV( int nJoystick, bool bEnable ) +{ + m_pJoystickInfo[ nJoystick ].m_bDiagonalPOVControlEnabled = bEnable; +} + +//----------------------------------------------------------------------------- +// Poll current state +//----------------------------------------------------------------------------- +int CInputSystem::GetPollTick() const +{ + return m_nLastPollTick; +} + +bool CInputSystem::IsButtonDown( ButtonCode_t code ) const +{ + return m_InputState[INPUT_STATE_CURRENT].m_ButtonState.IsBitSet( code ); +} + +int CInputSystem::GetAnalogValue( AnalogCode_t code ) const +{ + return m_InputState[INPUT_STATE_CURRENT].m_pAnalogValue[code]; +} + +int CInputSystem::GetAnalogDelta( AnalogCode_t code ) const +{ + return m_InputState[INPUT_STATE_CURRENT].m_pAnalogDelta[code]; +} + +int CInputSystem::GetButtonPressedTick( ButtonCode_t code ) const +{ + return m_InputState[INPUT_STATE_CURRENT].m_ButtonPressedTick[code]; +} + +int CInputSystem::GetButtonReleasedTick( ButtonCode_t code ) const +{ + return m_InputState[INPUT_STATE_CURRENT].m_ButtonReleasedTick[code]; +} + + +//----------------------------------------------------------------------------- +// Returns the input events since the last poll +//----------------------------------------------------------------------------- +int CInputSystem::GetEventCount() const +{ + return m_InputState[INPUT_STATE_CURRENT].m_Events.Count(); +} + +const InputEvent_t* CInputSystem::GetEventData( ) const +{ + return m_InputState[INPUT_STATE_CURRENT].m_Events.Base(); +} + + +//----------------------------------------------------------------------------- +// Posts a user-defined event into the event queue; this is expected +// to be called in overridden wndprocs connected to the root panel. +//----------------------------------------------------------------------------- +void CInputSystem::PostUserEvent( const InputEvent_t &event ) +{ + InputState_t &state = m_InputState[ m_bIsPolling ]; + state.m_Events.AddToTail( event ); + state.m_bDirty = true; +} + + +//----------------------------------------------------------------------------- +// Chains the window message to the previous wndproc +//----------------------------------------------------------------------------- +inline LRESULT CInputSystem::ChainWindowMessage( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam ) +{ +#if !defined( POSIX ) + if ( m_ChainedWndProc ) + return CallWindowProc( m_ChainedWndProc, hwnd, uMsg, wParam, lParam ); +#endif + // FIXME: This comment is lifted from vguimatsurface; + // may not apply in future when the system is completed. + + // This means the application is driving the messages (calling our window procedure manually) + // rather than us hooking their window procedure. The engine needs to do this in order for VCR + // mode to play back properly. + return 0; +} + + +//----------------------------------------------------------------------------- +// Release all buttons +//----------------------------------------------------------------------------- +void CInputSystem::ReleaseAllButtons( int nFirstButton, int nLastButton ) +{ + // Force button up messages for all down buttons + for ( int i = nFirstButton; i <= nLastButton; ++i ) + { + PostButtonReleasedEvent( IE_ButtonReleased, m_nLastSampleTick, (ButtonCode_t)i, (ButtonCode_t)i ); + } +} + + +//----------------------------------------------------------------------------- +// Zero analog state +//----------------------------------------------------------------------------- +void CInputSystem::ZeroAnalogState( int nFirstState, int nLastState ) +{ + InputState_t &state = m_InputState[ m_bIsPolling ]; + memset( &state.m_pAnalogDelta[nFirstState], 0, ( nLastState - nFirstState + 1 ) * sizeof(int) ); + memset( &state.m_pAnalogValue[nFirstState], 0, ( nLastState - nFirstState + 1 ) * sizeof(int) ); +} + + +//----------------------------------------------------------------------------- +// Determines all mouse button presses +//----------------------------------------------------------------------------- +int CInputSystem::ButtonMaskFromMouseWParam( WPARAM wParam, ButtonCode_t code, bool bDown ) const +{ + int nButtonMask = 0; + +#if defined( PLATFORM_WINDOWS ) + if ( wParam & MK_LBUTTON ) + { + nButtonMask |= 1; + } + + if ( wParam & MK_RBUTTON ) + { + nButtonMask |= 2; + } + + if ( wParam & MK_MBUTTON ) + { + nButtonMask |= 4; + } + + if ( wParam & MS_MK_BUTTON4 ) + { + nButtonMask |= 8; + } + + if ( wParam & MS_MK_BUTTON5 ) + { + nButtonMask |= 16; + } +#endif + +#ifdef _DEBUG + if ( code != BUTTON_CODE_INVALID ) + { + int nMsgMask = 1 << ( code - MOUSE_FIRST ); + int nTestMask = bDown ? nMsgMask : 0; + Assert( ( nButtonMask & nMsgMask ) == nTestMask ); + } +#endif + + return nButtonMask; +} + + +//----------------------------------------------------------------------------- +// Updates the state of all mouse buttons +//----------------------------------------------------------------------------- +void CInputSystem::UpdateMouseButtonState( int nButtonMask, ButtonCode_t dblClickCode ) +{ + for ( int i = 0; i < 5; ++i ) + { + ButtonCode_t code = (ButtonCode_t)( MOUSE_FIRST + i ); + bool bDown = ( nButtonMask & ( 1 << i ) ) != 0; + if ( bDown ) + { + InputEventType_t type = ( code != dblClickCode ) ? IE_ButtonPressed : IE_ButtonDoubleClicked; + PostButtonPressedEvent( type, m_nLastSampleTick, code, code ); + } + else + { + PostButtonReleasedEvent( IE_ButtonReleased, m_nLastSampleTick, code, code ); + } + } +} + + +//----------------------------------------------------------------------------- +// Handles input messages +//----------------------------------------------------------------------------- +void CInputSystem::SetCursorPosition( int x, int y ) +{ + if ( !m_hAttachedHWnd ) + return; + +#if defined( PLATFORM_WINDOWS ) + POINT pt; + pt.x = x; pt.y = y; + ClientToScreen( (HWND)m_hAttachedHWnd, &pt ); + SetCursorPos( pt.x, pt.y ); +#elif defined( USE_SDL ) + m_pLauncherMgr->SetCursorPosition( x, y ); +#endif + + InputState_t &state = m_InputState[ m_bIsPolling ]; + bool bXChanged = ( state.m_pAnalogValue[ MOUSE_X ] != x ); + bool bYChanged = ( state.m_pAnalogValue[ MOUSE_Y ] != y ); + + state.m_pAnalogValue[ MOUSE_X ] = x; + state.m_pAnalogValue[ MOUSE_Y ] = y; + state.m_pAnalogDelta[ MOUSE_X ] = 0; + state.m_pAnalogDelta[ MOUSE_Y ] = 0; + + if ( bXChanged ) + { + PostEvent( IE_AnalogValueChanged, m_nLastSampleTick, MOUSE_X, state.m_pAnalogValue[ MOUSE_X ], state.m_pAnalogDelta[ MOUSE_X ] ); + } + if ( bYChanged ) + { + PostEvent( IE_AnalogValueChanged, m_nLastSampleTick, MOUSE_Y, state.m_pAnalogValue[ MOUSE_Y ], state.m_pAnalogDelta[ MOUSE_Y ] ); + } + if ( bXChanged || bYChanged ) + { + PostEvent( IE_AnalogValueChanged, m_nLastSampleTick, MOUSE_XY, state.m_pAnalogValue[ MOUSE_X ], state.m_pAnalogValue[ MOUSE_Y ] ); + } +} + + +void CInputSystem::UpdateMousePositionState( InputState_t &state, short x, short y ) +{ + int nOldX = state.m_pAnalogValue[ MOUSE_X ]; + int nOldY = state.m_pAnalogValue[ MOUSE_Y ]; + + state.m_pAnalogValue[ MOUSE_X ] = x; + state.m_pAnalogValue[ MOUSE_Y ] = y; + state.m_pAnalogDelta[ MOUSE_X ] = state.m_pAnalogValue[ MOUSE_X ] - nOldX; + state.m_pAnalogDelta[ MOUSE_Y ] = state.m_pAnalogValue[ MOUSE_Y ] - nOldY; + + if ( state.m_pAnalogDelta[ MOUSE_X ] != 0 ) + { + PostEvent( IE_AnalogValueChanged, m_nLastSampleTick, MOUSE_X, state.m_pAnalogValue[ MOUSE_X ], state.m_pAnalogDelta[ MOUSE_X ] ); + } + if ( state.m_pAnalogDelta[ MOUSE_Y ] != 0 ) + { + PostEvent( IE_AnalogValueChanged, m_nLastSampleTick, MOUSE_Y, state.m_pAnalogValue[ MOUSE_Y ], state.m_pAnalogDelta[ MOUSE_Y ] ); + } + if ( state.m_pAnalogDelta[ MOUSE_X ] != 0 || state.m_pAnalogDelta[ MOUSE_Y ] != 0 ) + { + PostEvent( IE_AnalogValueChanged, m_nLastSampleTick, MOUSE_XY, state.m_pAnalogValue[ MOUSE_X ], state.m_pAnalogValue[ MOUSE_Y ] ); + } +} + + +//----------------------------------------------------------------------------- +// Handles input messages +//----------------------------------------------------------------------------- +LRESULT CInputSystem::WindowProc( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam ) +{ +#if defined( PLATFORM_WINDOWS ) // We use this even for SDL to handle mouse move. + if ( !m_bEnabled ) + return ChainWindowMessage( hwnd, uMsg, wParam, lParam ); + + if ( hwnd != m_hAttachedHWnd ) + return ChainWindowMessage( hwnd, uMsg, wParam, lParam ); + + InputState_t &state = m_InputState[ m_bIsPolling ]; + switch( uMsg ) + { + +#if !defined( USE_SDL ) + case WM_ACTIVATEAPP: + if ( hwnd == m_hAttachedHWnd ) + { + bool bActivated = ( wParam == 1 ); + if ( !bActivated ) + { + ResetInputState(); + } + } + break; + + case WM_LBUTTONDOWN: + { + int nButtonMask = ButtonMaskFromMouseWParam( wParam, MOUSE_LEFT, true ); + ETWMouseDown( 0, (short)LOWORD(lParam), (short)HIWORD(lParam) ); + UpdateMouseButtonState( nButtonMask ); + } + break; + + case WM_LBUTTONUP: + { + int nButtonMask = ButtonMaskFromMouseWParam( wParam, MOUSE_LEFT, false ); + ETWMouseUp( 0, (short)LOWORD(lParam), (short)HIWORD(lParam) ); + UpdateMouseButtonState( nButtonMask ); + } + break; + + case WM_RBUTTONDOWN: + { + int nButtonMask = ButtonMaskFromMouseWParam( wParam, MOUSE_RIGHT, true ); + ETWMouseDown( 2, (short)LOWORD(lParam), (short)HIWORD(lParam) ); + UpdateMouseButtonState( nButtonMask ); + } + break; + + case WM_RBUTTONUP: + { + int nButtonMask = ButtonMaskFromMouseWParam( wParam, MOUSE_RIGHT, false ); + ETWMouseUp( 2, (short)LOWORD(lParam), (short)HIWORD(lParam) ); + UpdateMouseButtonState( nButtonMask ); + } + break; + + case WM_MBUTTONDOWN: + { + int nButtonMask = ButtonMaskFromMouseWParam( wParam, MOUSE_MIDDLE, true ); + ETWMouseDown( 1, (short)LOWORD(lParam), (short)HIWORD(lParam) ); + UpdateMouseButtonState( nButtonMask ); + } + break; + + case WM_MBUTTONUP: + { + int nButtonMask = ButtonMaskFromMouseWParam( wParam, MOUSE_MIDDLE, false ); + ETWMouseUp( 1, (short)LOWORD(lParam), (short)HIWORD(lParam) ); + UpdateMouseButtonState( nButtonMask ); + } + break; + + case MS_WM_XBUTTONDOWN: + { + ButtonCode_t code = ( HIWORD( wParam ) == 1 ) ? MOUSE_4 : MOUSE_5; + int nButtonMask = ButtonMaskFromMouseWParam( wParam, code, true ); + UpdateMouseButtonState( nButtonMask ); + + // Windows docs say the XBUTTON messages we should return true from + return TRUE; + } + break; + + case MS_WM_XBUTTONUP: + { + ButtonCode_t code = ( HIWORD( wParam ) == 1 ) ? MOUSE_4 : MOUSE_5; + int nButtonMask = ButtonMaskFromMouseWParam( wParam, code, false ); + UpdateMouseButtonState( nButtonMask ); + + // Windows docs say the XBUTTON messages we should return true from + return TRUE; + } + break; + + case WM_LBUTTONDBLCLK: + { + int nButtonMask = ButtonMaskFromMouseWParam( wParam, MOUSE_LEFT, true ); + ETWMouseDown( 0, (short)LOWORD(lParam), (short)HIWORD(lParam) ); + UpdateMouseButtonState( nButtonMask, MOUSE_LEFT ); + } + break; + + case WM_RBUTTONDBLCLK: + { + int nButtonMask = ButtonMaskFromMouseWParam( wParam, MOUSE_RIGHT, true ); + ETWMouseDown( 2, (short)LOWORD(lParam), (short)HIWORD(lParam) ); + UpdateMouseButtonState( nButtonMask, MOUSE_RIGHT ); + } + break; + + case WM_MBUTTONDBLCLK: + { + int nButtonMask = ButtonMaskFromMouseWParam( wParam, MOUSE_MIDDLE, true ); + ETWMouseDown( 1, (short)LOWORD(lParam), (short)HIWORD(lParam) ); + UpdateMouseButtonState( nButtonMask, MOUSE_MIDDLE ); + } + break; + + case MS_WM_XBUTTONDBLCLK: + { + ButtonCode_t code = ( HIWORD( wParam ) == 1 ) ? MOUSE_4 : MOUSE_5; + int nButtonMask = ButtonMaskFromMouseWParam( wParam, code, true ); + UpdateMouseButtonState( nButtonMask, code ); + + // Windows docs say the XBUTTON messages we should return true from + return TRUE; + } + break; + + case WM_KEYDOWN: + case WM_SYSKEYDOWN: + { + // Suppress key repeats + if ( !( lParam & ( 1<<30 ) ) ) + { + // NOTE: These two can be unequal! For example, keypad enter + // which returns KEY_ENTER from virtual keys, and KEY_PAD_ENTER from scan codes + // Since things like vgui care about virtual keys; we're going to + // put both scan codes in the input message + ButtonCode_t virtualCode = ButtonCode_VirtualKeyToButtonCode( wParam ); + ButtonCode_t scanCode = ButtonCode_ScanCodeToButtonCode( lParam ); + PostButtonPressedEvent( IE_ButtonPressed, m_nLastSampleTick, scanCode, virtualCode ); + + // Post ETW events describing key presses to help correlate input events to performance + // problems in the game. + ETWKeyDown( scanCode, virtualCode, ButtonCodeToString( virtualCode ) ); + + // Deal with toggles + if ( scanCode == KEY_CAPSLOCK || scanCode == KEY_SCROLLLOCK || scanCode == KEY_NUMLOCK ) + { + int nVirtualKey; + ButtonCode_t toggleCode; + switch( scanCode ) + { + default: case KEY_CAPSLOCK: nVirtualKey = VK_CAPITAL; toggleCode = KEY_CAPSLOCKTOGGLE; break; + case KEY_SCROLLLOCK: nVirtualKey = VK_SCROLL; toggleCode = KEY_SCROLLLOCKTOGGLE; break; + case KEY_NUMLOCK: nVirtualKey = VK_NUMLOCK; toggleCode = KEY_NUMLOCKTOGGLE; break; + }; + + SHORT wState = GetKeyState( nVirtualKey ); + bool bToggleState = ( wState & 0x1 ) != 0; + PostButtonPressedEvent( bToggleState ? IE_ButtonPressed : IE_ButtonReleased, m_nLastSampleTick, toggleCode, toggleCode ); + } + } + } + break; + + case WM_KEYUP: + case WM_SYSKEYUP: + { + // Don't handle key ups if the key's already up. This can happen when we alt-tab back to the engine. + ButtonCode_t virtualCode = ButtonCode_VirtualKeyToButtonCode( wParam ); + ButtonCode_t scanCode = ButtonCode_ScanCodeToButtonCode( lParam ); + PostButtonReleasedEvent( IE_ButtonReleased, m_nLastSampleTick, scanCode, virtualCode ); + } + break; + + case WM_MOUSEWHEEL: + { + ButtonCode_t code = (short)HIWORD( wParam ) > 0 ? MOUSE_WHEEL_UP : MOUSE_WHEEL_DOWN; + state.m_ButtonPressedTick[ code ] = state.m_ButtonReleasedTick[ code ] = m_nLastSampleTick; + PostEvent( IE_ButtonPressed, m_nLastSampleTick, code, code ); + PostEvent( IE_ButtonReleased, m_nLastSampleTick, code, code ); + + state.m_pAnalogDelta[ MOUSE_WHEEL ] = ( (short)HIWORD(wParam) ) / WHEEL_DELTA; + state.m_pAnalogValue[ MOUSE_WHEEL ] += state.m_pAnalogDelta[ MOUSE_WHEEL ]; + PostEvent( IE_AnalogValueChanged, m_nLastSampleTick, MOUSE_WHEEL, state.m_pAnalogValue[ MOUSE_WHEEL ], state.m_pAnalogDelta[ MOUSE_WHEEL ] ); + } + break; + +#if defined( PLATFORM_WINDOWS_PC ) + case WM_INPUT: + { + if ( m_bRawInputSupported ) + { + UINT dwSize = 40; + static BYTE lpb[40]; + + pfnGetRawInputData((HRAWINPUT)lParam, RID_INPUT, lpb, &dwSize, sizeof(RAWINPUTHEADER)); + + RAWINPUT* raw = (RAWINPUT*)lpb; + if (raw->header.dwType == RIM_TYPEMOUSE) + { + m_mouseRawAccumX += raw->data.mouse.lLastX; + m_mouseRawAccumY += raw->data.mouse.lLastY; + } + } + } + break; +#endif + +#endif // !USE_SDL + + case WM_MOUSEMOVE: + { + UpdateMousePositionState( state, (short)LOWORD(lParam), (short)HIWORD(lParam) ); + + int nButtonMask = ButtonMaskFromMouseWParam( wParam ); + UpdateMouseButtonState( nButtonMask ); + } + break; + + } + +#if defined( PLATFORM_WINDOWS_PC ) && !defined( USE_SDL ) + // Can't put this in the case statement, it's not constant + if ( uMsg == m_uiMouseWheel ) + { + ButtonCode_t code = ( ( int )wParam ) > 0 ? MOUSE_WHEEL_UP : MOUSE_WHEEL_DOWN; + state.m_ButtonPressedTick[ code ] = state.m_ButtonReleasedTick[ code ] = m_nLastSampleTick; + PostEvent( IE_ButtonPressed, m_nLastSampleTick, code, code ); + PostEvent( IE_ButtonReleased, m_nLastSampleTick, code, code ); + + state.m_pAnalogDelta[ MOUSE_WHEEL ] = ( ( int )wParam ) / WHEEL_DELTA; + state.m_pAnalogValue[ MOUSE_WHEEL ] += state.m_pAnalogDelta[ MOUSE_WHEEL ]; + PostEvent( IE_AnalogValueChanged, m_nLastSampleTick, MOUSE_WHEEL, state.m_pAnalogValue[ MOUSE_WHEEL ], state.m_pAnalogDelta[ MOUSE_WHEEL ] ); + } +#endif + +#endif // PLATFORM_WINDOWS + return ChainWindowMessage( hwnd, uMsg, wParam, lParam ); +} + +bool CInputSystem::GetRawMouseAccumulators( int& accumX, int& accumY ) +{ +#if defined( USE_SDL ) + + if ( m_pLauncherMgr ) + { + m_pLauncherMgr->GetMouseDelta( accumX, accumY, false ); + return true; + } + return false; + +#else + + accumX = m_mouseRawAccumX; + accumY = m_mouseRawAccumY; + m_mouseRawAccumX = m_mouseRawAccumY = 0; + return m_bRawInputSupported; + +#endif +} + +void CInputSystem::SetConsoleTextMode( bool bConsoleTextMode ) +{ + /* If someone calls this after init, shut it down. */ + if ( bConsoleTextMode && m_bJoystickInitialized ) + { + ShutdownJoysticks(); + } + + m_bConsoleTextMode = bConsoleTextMode; +} + +ISteamController* CInputSystem::SteamControllerInterface() +{ + if ( m_bSkipControllerInitialization ) + { + return nullptr; + } + else + { + return m_SteamAPIContext.SteamController(); + } +} diff --git a/inputsystem/inputsystem.h b/inputsystem/inputsystem.h new file mode 100644 index 0000000..9a85148 --- /dev/null +++ b/inputsystem/inputsystem.h @@ -0,0 +1,469 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//===========================================================================// +#ifndef INPUTSYSTEM_H +#define INPUTSYSTEM_H +#ifdef _WIN32 +#pragma once +#endif + + +#ifdef WIN32 +#if !defined( _X360 ) +#define _WIN32_WINNT 0x502 +#include <windows.h> +#include <zmouse.h> +#include "xbox/xboxstubs.h" +#include "../../dx9sdk/include/XInput.h" +#endif +#endif + +#ifdef POSIX +#include "posix_stubs.h" +#endif // POSIX + +#include "appframework/ilaunchermgr.h" + +#include "inputsystem/iinputsystem.h" +#include "tier2/tier2.h" + +#include "inputsystem/ButtonCode.h" +#include "inputsystem/AnalogCode.h" +#include "bitvec.h" +#include "tier1/utlvector.h" +#include "tier1/utlflags.h" + +#if defined( _X360 ) +#include "xbox/xbox_win32stubs.h" +#include "xbox/xbox_console.h" +#endif + +#include "steam/steam_api.h" + +//----------------------------------------------------------------------------- +// Implementation of the input system +//----------------------------------------------------------------------------- +class CInputSystem : public CTier2AppSystem< IInputSystem > +{ + typedef CTier2AppSystem< IInputSystem > BaseClass; + +public: + // Constructor, destructor + CInputSystem(); + virtual ~CInputSystem(); + + // Inherited from IAppSystem + virtual InitReturnVal_t Init(); + virtual bool Connect( CreateInterfaceFn factory ); + + virtual void Shutdown(); + + // Inherited from IInputSystem + virtual void AttachToWindow( void* hWnd ); + virtual void DetachFromWindow(); + virtual void EnableInput( bool bEnable ); + virtual void EnableMessagePump( bool bEnable ); + virtual int GetPollTick() const; + virtual void PollInputState(); + virtual bool IsButtonDown( ButtonCode_t code ) const; + virtual int GetButtonPressedTick( ButtonCode_t code ) const; + virtual int GetButtonReleasedTick( ButtonCode_t code ) const; + virtual int GetAnalogValue( AnalogCode_t code ) const; + virtual int GetAnalogDelta( AnalogCode_t code ) const; + virtual int GetEventCount() const; + virtual const InputEvent_t* GetEventData() const; + virtual void PostUserEvent( const InputEvent_t &event ); + virtual int GetJoystickCount() const; + virtual void EnableJoystickInput( int nJoystick, bool bEnable ); + virtual void EnableJoystickDiagonalPOV( int nJoystick, bool bEnable ); + virtual void SampleDevices( void ); + virtual void SetRumble( float fLeftMotor, float fRightMotor, int userId ); + virtual void StopRumble( void ); + virtual void ResetInputState( void ); + virtual void SetPrimaryUserId( int userId ); + virtual const char *ButtonCodeToString( ButtonCode_t code ) const; + virtual const char *AnalogCodeToString( AnalogCode_t code ) const; + virtual ButtonCode_t StringToButtonCode( const char *pString ) const; + virtual AnalogCode_t StringToAnalogCode( const char *pString ) const; + virtual ButtonCode_t VirtualKeyToButtonCode( int nVirtualKey ) const; + virtual int ButtonCodeToVirtualKey( ButtonCode_t code ) const; + virtual ButtonCode_t ScanCodeToButtonCode( int lParam ) const; + virtual void SleepUntilInput( int nMaxSleepTimeMS ); + virtual int GetPollCount() const; + virtual void SetCursorPosition( int x, int y ); +#if defined( WIN32 ) && !defined ( _X360 ) + virtual void *GetHapticsInterfaceAddress() const; +#else + virtual void *GetHapticsInterfaceAddress() const { return NULL;} +#endif + bool GetRawMouseAccumulators( int& accumX, int& accumY ); + virtual void SetConsoleTextMode( bool bConsoleTextMode ); + + // Windows proc + LRESULT WindowProc( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam ); + + +private: + enum + { + STICK1_AXIS_LEFT, + STICK1_AXIS_RIGHT, + STICK1_AXIS_DOWN, + STICK1_AXIS_UP, + STICK2_AXIS_LEFT, + STICK2_AXIS_RIGHT, + STICK2_AXIS_DOWN, + STICK2_AXIS_UP, + MAX_STICKAXIS + }; + + // track Xbox stick keys from previous frame + enum + { + LASTKEY_STICK1_X, + LASTKEY_STICK1_Y, + LASTKEY_STICK2_X, + LASTKEY_STICK2_Y, + MAX_LASTKEY, + }; + + enum + { + INPUT_STATE_QUEUED = 0, + INPUT_STATE_CURRENT, + + INPUT_STATE_COUNT, + }; + +public: + + struct JoystickInfo_t + { + void *m_pDevice; // Really an SDL_GameController*, NULL if not present. + void *m_pHaptic; // Really an SDL_Haptic* + float m_fCurrentRumble; + bool m_bRumbleEnabled; + int m_nButtonCount; + int m_nAxisFlags; + int m_nDeviceId; + bool m_bHasPOVControl; + bool m_bDiagonalPOVControlEnabled; + unsigned int m_nFlags; + unsigned int m_nLastPolledButtons; + unsigned int m_nLastPolledAxisButtons; + unsigned int m_nLastPolledPOVState; + unsigned long m_pLastPolledAxes[MAX_JOYSTICK_AXES]; + }; + + struct xdevice_t + { + int userId; + byte type; + byte subtype; + word flags; + bool active; + XINPUT_STATE states[2]; + int newState; + xKey_t lastStickKeys[MAX_LASTKEY]; + int stickThreshold[MAX_STICKAXIS]; + float stickScale[MAX_STICKAXIS]; + int quitTimeout; + int dpadLock; + // rumble + XINPUT_VIBRATION vibration; + bool pendingRumbleUpdate; + }; + + struct appKey_t + { + int repeats; + int sample; + }; + + struct InputState_t + { + // Analog states + CBitVec<BUTTON_CODE_LAST> m_ButtonState; + int m_ButtonPressedTick[BUTTON_CODE_LAST]; + int m_ButtonReleasedTick[BUTTON_CODE_LAST]; + int m_pAnalogDelta[ANALOG_CODE_LAST]; + int m_pAnalogValue[ANALOG_CODE_LAST]; + CUtlVector< InputEvent_t > m_Events; + bool m_bDirty; + }; + + // Initializes all Xbox controllers + void InitializeXDevices( void ); + + // Opens an Xbox controller + void OpenXDevice( xdevice_t* pXDevice, int userId ); + + // Closes an Xbox controller + void CloseXDevice( xdevice_t* pXDevice ); + + // Samples the Xbox controllers + void PollXDevices( void ); + + // Samples an Xbox controller and queues input events + void ReadXDevice( xdevice_t* pXDevice ); + + // Submits force feedback data to an Xbox controller + void WriteToXDevice( xdevice_t* pXDevice ); + + // Sets rumble values for an Xbox controller + void SetXDeviceRumble( float fLeftMotor, float fRightMotor, int userId ); + + // Posts an Xbox key event, ignoring key repeats + void PostXKeyEvent( int nUserId, xKey_t xKey, int nSample ); + + // Dispatches all joystick button events through the game's window procs + void ProcessEvent( UINT uMsg, WPARAM wParam, LPARAM lParam ); + + // Initializes joysticks + void InitializeJoysticks( void ); + + // Shut down joysticks + void ShutdownJoysticks( void ); + + // Samples the joystick + void PollJoystick( void ); + + // Update the joystick button state + void UpdateJoystickButtonState( int nJoystick ); + + // Update the joystick POV control + void UpdateJoystickPOVControl( int nJoystick ); + + // Record button state and post the event + void JoystickButtonEvent( ButtonCode_t button, int sample ); + +#if defined( WIN32 ) && !defined ( _X360 ) + // NVNT attaches window to novint devices + void AttachWindowToNovintDevices( void * hWnd ); + + // NVNT detaches window from novint input + void DetachWindowFromNovintDevices( void ); + + // NVNT Initializes novint devices + void InitializeNovintDevices( void ); + + // NVNT Samples a novint device + void PollNovintDevices( void ); + + // NVNT Update the novint device button state + void UpdateNovintDeviceButtonState( int nDevice ); + + // NVNT Record button state and post the event + void NovintDeviceButtonEvent( ButtonCode_t button, int sample ); + + //Added called and set to true when binding input and set to false once bound + void SetNovintPure( bool bPure ); +#else + void SetNovintPure( bool bPure ) {} // to satify the IInput virtual interface +#endif + // Chains the window message to the previous wndproc + LRESULT ChainWindowMessage( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam ); + + // Post an event to the queue + void PostEvent( int nType, int nTick, int nData = 0, int nData2 = 0, int nData3 = 0 ); + + // Deals with app deactivation (sends a bunch of button released messages) + void ActivateInputSystem( bool bActivated ); + + // Determines all mouse button presses + int ButtonMaskFromMouseWParam( WPARAM wParam, ButtonCode_t code = BUTTON_CODE_INVALID, bool bDown = false ) const; + + // Updates the state of all mouse buttons + void UpdateMouseButtonState( int nButtonMask, ButtonCode_t dblClickCode = BUTTON_CODE_INVALID ); + + // Copies the input state record over + void CopyInputState( InputState_t *pDest, const InputState_t &src, bool bCopyEvents ); + + // Post an button press/release event to the queue + void PostButtonPressedEvent( InputEventType_t nType, int nTick, ButtonCode_t scanCode, ButtonCode_t virtualCode ); + void PostButtonReleasedEvent( InputEventType_t nType, int nTick, ButtonCode_t scanCode, ButtonCode_t virtualCode ); + + // Release all buttons + void ReleaseAllButtons( int nFirstButton = 0, int nLastButton = BUTTON_CODE_LAST - 1 ); + + // Zero analog state + void ZeroAnalogState( int nFirstState, int nLastState ); + + // Converts xbox keys to button codes + ButtonCode_t XKeyToButtonCode( int nUserId, int nXKey ) const; + + // Computes the sample tick + int ComputeSampleTick(); + + // Clears the input state, doesn't generate key-up messages + void ClearInputState(); + + // Called for mouse move events. Sets the current x and y and posts events for the mouse moving. + void UpdateMousePositionState( InputState_t &state, short x, short y ); + + // Initializes SteamControllers - Returns true if steam is running and finds controllers, otherwise false + bool InitializeSteamControllers( void ); + + // Returns number of connected Steam Controllers + uint32 GetNumSteamControllersConnected(); + + // Update and sample steam controllers + void PollSteamControllers( void ); + +#if defined( PLATFORM_WINDOWS_PC ) + void PollInputState_Windows(); +#endif + + void JoystickHotplugAdded( int joystickIndex ); + void JoystickHotplugRemoved( int joystickId ); + void JoystickButtonPress( int joystickId, int button ); // button is a SDL_CONTROLLER_BUTTON; + void JoystickButtonRelease( int joystickId, int button ); // same as above. + void JoystickAxisMotion( int joystickId, int axis, int value ); + + // Steam Controller + void ReadSteamController( int iIndex ); + void PostKeyEvent( int iIndex, sKey_t sKey, int nSample ); + const int GetSteamPadDeadZone( ESteamPadAxis axis ); + bool IsSteamControllerConnected( void ) { return m_bSteamController; } + bool IsSteamControllerActive( void ); + void ActivateSteamControllerActionSetForSlot( uint64 nSlot, GameActionSet_t eActionSet ); + ControllerActionSetHandle_t GetActionSetHandle( GameActionSet_t eActionSet ); + ControllerActionSetHandle_t GetActionSetHandle( const char* szActionSet ); + bool GetControllerStateForSlot( int nSlot ); + int GetSteamControllerIndexForSlot( int nSlot ); + bool GetRadialMenuStickValues( int nSlot, float &fX, float &fY ); + virtual ISteamController* SteamControllerInterface(); + bool InitializeSteamControllerActionSets(); + + // Gets the action origin (i.e. which physical input) maps to the given action name + virtual EControllerActionOrigin GetSteamControllerActionOrigin( const char* action, GameActionSet_t action_set ); + virtual EControllerActionOrigin GetSteamControllerActionOrigin( const char* action, ControllerActionSetHandle_t action_set_handle ); + + // Maps a Steam Controller action origin to a string (consisting of a single character) in our SC icon font + virtual const wchar_t* GetSteamControllerFontCharacterForActionOrigin( EControllerActionOrigin origin ); + + // Maps a Steam Controller action origin to a short text string (e.g. "X", "LB", "LDOWN") describing the control. + // Prefer to actually use the icon font wherever possible. + virtual const wchar_t* GetSteamControllerDescriptionForActionOrigin( EControllerActionOrigin origin ); + + // Converts SteamController keys to button codes + ButtonCode_t SKeyToButtonCode( int nUserId, int nXKey ) const; + + // This is called with "true" by dedicated server initialization (before calling Init) in order to + // force us to skip initialization of Steam (which messes up dedicated servers). + virtual void SetSkipControllerInitialization( bool bSkip ) + { + m_bSkipControllerInitialization = bSkip; + } + +#if defined( USE_SDL ) + void PollInputState_Platform(); + + ILauncherMgr *m_pLauncherMgr; +#endif + + WNDPROC m_ChainedWndProc; + HWND m_hAttachedHWnd; + bool m_bEnabled; + bool m_bPumpEnabled; + bool m_bIsPolling; + + // Current button state + InputState_t m_InputState[INPUT_STATE_COUNT]; + + // Current action set + GameActionSet_t m_currentActionSet[STEAM_CONTROLLER_MAX_COUNT]; + + DWORD m_StartupTimeTick; + int m_nLastPollTick; + int m_nLastSampleTick; + int m_nPollCount; + + // Mouse wheel hack + UINT m_uiMouseWheel; + + // Joystick info + CUtlFlags<unsigned short> m_JoysticksEnabled; + int m_nJoystickCount; + bool m_bJoystickInitialized; + bool m_bXController; + JoystickInfo_t m_pJoystickInfo[ MAX_JOYSTICKS ]; + + // Steam Controller + struct steampad_t + { + steampad_t() + { + m_nHardwareIndex = 0; + m_nJoystickIndex = INVALID_USER_ID; + m_nLastPacketIndex = 0; + active = false; + memset( lastAnalogKeys, 0, sizeof( lastAnalogKeys ) ); + } + bool active; + + sKey_t lastAnalogKeys[MAX_STEAMPADAXIS]; + appKey_t m_appSKeys[SK_MAX_KEYS]; + // Hardware index and joystick index don't necessarily match + // Joystick index will depend on the order of multiple initialized devices + // Which could include other controller types + // Hardware index should line up 1:1 with the order they're polled + // and could change based on removing devices, unlike Joystick Index + uint32 m_nHardwareIndex; + int m_nJoystickIndex; + uint32 m_nLastPacketIndex; + }; + + float m_pRadialMenuStickVal[STEAM_CONTROLLER_MAX_COUNT][2]; + steampad_t m_Device[STEAM_CONTROLLER_MAX_COUNT]; + uint32 m_unNumConnected; + float m_flLastSteamControllerInput; + int m_nJoystickBaseline; + int m_nControllerType[MAX_JOYSTICKS+STEAM_CONTROLLER_MAX_COUNT]; + + bool m_bSteamController; // true if the Steam Controller system has been initialized successfully (this doesn't mean one is actually connected necessarily) + bool m_bSteamControllerActionsInitialized; // true if our action sets and handles were successfully initialized (this doesn't mean a controller is necessarily connected, or that in-game client actions were initialized) + bool m_bSteamControllerActive; // true if our action sets and handles were successfully initialized *and* that at least one controller is actually connected and switched on. + +#if defined( WIN32 ) && !defined ( _X360 ) + // NVNT Novint device info + int m_nNovintDeviceCount; + bool m_bNovintDevices; +#endif + + // Xbox controller info + appKey_t m_appXKeys[ XUSER_MAX_COUNT ][ XK_MAX_KEYS ]; + xdevice_t m_XDevices[ XUSER_MAX_COUNT ]; + int m_PrimaryUserId; + + // raw mouse input + bool m_bRawInputSupported; + int m_mouseRawAccumX, m_mouseRawAccumY; + + // For the 'SleepUntilInput' feature + HANDLE m_hEvent; + + CSysModule *m_pXInputDLL; + CSysModule *m_pRawInputDLL; + +#if defined( WIN32 ) && !defined ( _X360 ) + // NVNT falcon module + CSysModule *m_pNovintDLL; +#endif + + bool m_bConsoleTextMode; + +public: + // Steam API context for use by input system for access to steam controllers. + CSteamAPIContext& SteamAPIContext() { return m_SteamAPIContext; } + +private: + CSteamAPIContext m_SteamAPIContext; + bool m_bSkipControllerInitialization; +}; + +#endif // INPUTSYSTEM_H diff --git a/inputsystem/inputsystem.vpc b/inputsystem/inputsystem.vpc new file mode 100644 index 0000000..a9c2956 --- /dev/null +++ b/inputsystem/inputsystem.vpc @@ -0,0 +1,55 @@ +//----------------------------------------------------------------------------- +// INPUTSYSTEM.VPC +// +// Project Script +//----------------------------------------------------------------------------- + +$macro SRCDIR ".." +$Macro OUTBINDIR "$SRCDIR\..\game\bin" + +$include "$SRCDIR\vpc_scripts\source_dll_base.vpc" + +$Configuration +{ + $Compiler + { + $AdditionalIncludeDirectories "$BASE;$SRCDIR\thirdparty\SDL2" [!$SDL] // If $SDL, then we already have this from source_video_base.vpc. + $PreprocessorDefinitions "$BASE;VERSION_SAFE_STEAM_API_INTERFACES" + } + $Linker + { + $SystemLibraries "iconv" [$OSXALL] + $SystemFrameworks "IOKit;Carbon;ForceFeedback" + } +} + +$Project "inputsystem" +{ + $Folder "Source Files" + { + $File "inputsystem.cpp" + $File "inputsystem.h" + $File "joystick_sdl.cpp" + $File "novint.cpp" [$WIN32] + $File "key_translation.cpp" + $File "key_translation.h" + $File "steamcontroller.cpp" + } + + $Folder "Public Headers" + { + $File "$SRCDIR\public\inputsystem\AnalogCode.h" + $File "$SRCDIR\public\inputsystem\ButtonCode.h" + $File "$SRCDIR\public\inputsystem\iinputsystem.h" + $File "$SRCDIR\public\inputsystem\InputEnums.h" + $File "$SRCDIR\dx9sdk\include\xinput.h" [$WIN32] + } + + $Folder "Link Libraries" + { + $Lib tier1 + $Lib tier2 + $ImpLib SDL2 + $Implib "steam_api" [($WIN32||$WIN64||$POSIX||$PS3)&&!$NO_STEAM] + } +} diff --git a/inputsystem/joystick_sdl.cpp b/inputsystem/joystick_sdl.cpp new file mode 100644 index 0000000..aef5c48 --- /dev/null +++ b/inputsystem/joystick_sdl.cpp @@ -0,0 +1,615 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Linux Joystick implementation for inputsystem.dll +// +//===========================================================================// + +/* For force feedback testing. */ +#include "inputsystem.h" +#include "tier1/convar.h" +#include "tier0/icommandline.h" + +#include "SDL.h" +#include "SDL_gamecontroller.h" +#include "SDL_haptic.h" + +// NOTE: This has to be the last file included! +#include "tier0/memdbgon.h" + +static ButtonCode_t ControllerButtonToButtonCode( SDL_GameControllerButton button ); +static AnalogCode_t ControllerAxisToAnalogCode( SDL_GameControllerAxis axis ); +static int JoystickSDLWatcher( void *userInfo, SDL_Event *event ); + +ConVar joy_axisbutton_threshold( "joy_axisbutton_threshold", "0.3", FCVAR_ARCHIVE, "Analog axis range before a button press is registered." ); +ConVar joy_axis_deadzone( "joy_axis_deadzone", "0.2", FCVAR_ARCHIVE, "Dead zone near the zero point to not report movement." ); + +static void joy_active_changed_f( IConVar *var, const char *pOldValue, float flOldValue ); +ConVar joy_active( "joy_active", "-1", FCVAR_NONE, "Which of the connected joysticks / gamepads to use (-1 means first found)", &joy_active_changed_f); + +static void joy_gamecontroller_config_changed_f( IConVar *var, const char *pOldValue, float flOldValue ); +ConVar joy_gamecontroller_config( "joy_gamecontroller_config", "", FCVAR_ARCHIVE, "Game controller mapping (passed to SDL with SDL_HINT_GAMECONTROLLERCONFIG), can also be configured in Steam Big Picture mode.", &joy_gamecontroller_config_changed_f ); + +void SearchForDevice() +{ + int newJoystickId = joy_active.GetInt(); + CInputSystem *pInputSystem = (CInputSystem *)g_pInputSystem; + + if ( !pInputSystem ) + { + return; + } + // -1 means "first available." + if ( newJoystickId < 0 ) + { + pInputSystem->JoystickHotplugAdded(0); + return; + } + + for ( int device_index = 0; device_index < SDL_NumJoysticks(); ++device_index ) + { + SDL_Joystick *joystick = SDL_JoystickOpen(device_index); + if ( joystick == NULL ) + { + continue; + } + + int joystickId = SDL_JoystickInstanceID(joystick); + SDL_JoystickClose(joystick); + + if ( joystickId == newJoystickId ) + { + pInputSystem->JoystickHotplugAdded(device_index); + break; + } + } +} + +//--------------------------------------------------------------------------------------- +// Switch our active joystick to another device +//--------------------------------------------------------------------------------------- +void joy_active_changed_f( IConVar *var, const char *pOldValue, float flOldValue ) +{ + SearchForDevice(); +} + +//--------------------------------------------------------------------------------------- +// Reinitialize the game controller layer when the joy_gamecontroller_config is updated. +//--------------------------------------------------------------------------------------- +void joy_gamecontroller_config_changed_f( IConVar *var, const char *pOldValue, float flOldValue ) +{ + CInputSystem *pInputSystem = (CInputSystem *)g_pInputSystem; + if ( pInputSystem && SDL_WasInit(SDL_INIT_GAMECONTROLLER) ) + { + bool oldValuePresent = pOldValue && ( strlen( pOldValue ) > 0 ); + bool newValuePresent = ( strlen( joy_gamecontroller_config.GetString() ) > 0 ); + if ( !oldValuePresent && !newValuePresent ) + { + return; + } + + // We need to reinitialize the whole thing (i.e. undo CInputSystem::InitializeJoysticks and then call it again) + // due to SDL_GameController only reading the SDL_HINT_GAMECONTROLLERCONFIG on init. + pInputSystem->ShutdownJoysticks(); + pInputSystem->InitializeJoysticks(); + } +} + +//----------------------------------------------------------------------------- +// Handle the events coming from the GameController SDL subsystem. +//----------------------------------------------------------------------------- +int JoystickSDLWatcher( void *userInfo, SDL_Event *event ) +{ + CInputSystem *pInputSystem = (CInputSystem *)userInfo; + Assert(pInputSystem != NULL); + Assert(event != NULL); + + if ( event == NULL || pInputSystem == NULL ) + { + Warning("No input system\n"); + return 1; + } + + switch ( event->type ) + { + case SDL_CONTROLLERAXISMOTION: + case SDL_CONTROLLERBUTTONDOWN: + case SDL_CONTROLLERBUTTONUP: + case SDL_CONTROLLERDEVICEADDED: + case SDL_CONTROLLERDEVICEREMOVED: + break; + default: + return 1; + } + + // This is executed on the same thread as SDL_PollEvent, as PollEvent + // updates the joystick subsystem, which then calls SDL_PushEvent for + // the various events below. PushEvent invokes this callback. + // SDL_PollEvent is called in PumpWindowsMessageLoop which is coming + // from PollInputState_Linux, so there's no worry about calling + // PostEvent (which doesn't seem to be thread safe) from other threads. + Assert(ThreadInMainThread()); + + switch ( event->type ) + { + case SDL_CONTROLLERAXISMOTION: + { + pInputSystem->JoystickAxisMotion(event->caxis.which, event->caxis.axis, event->caxis.value); + break; + } + + case SDL_CONTROLLERBUTTONDOWN: + pInputSystem->JoystickButtonPress(event->cbutton.which, event->cbutton.button); + break; + case SDL_CONTROLLERBUTTONUP: + pInputSystem->JoystickButtonRelease(event->cbutton.which, event->cbutton.button); + break; + + case SDL_CONTROLLERDEVICEADDED: + pInputSystem->JoystickHotplugAdded(event->cdevice.which); + break; + case SDL_CONTROLLERDEVICEREMOVED: + pInputSystem->JoystickHotplugRemoved(event->cdevice.which); + SearchForDevice(); + break; + } + + return 1; +} + +//----------------------------------------------------------------------------- +// Initialize all joysticks +//----------------------------------------------------------------------------- +void CInputSystem::InitializeJoysticks( void ) +{ + if ( m_bJoystickInitialized ) + { + ShutdownJoysticks(); + } + + // assume no joystick + m_nJoystickCount = 0; + memset( m_pJoystickInfo, 0, sizeof( m_pJoystickInfo ) ); + for ( int i = 0; i < MAX_JOYSTICKS; ++i ) + { + m_pJoystickInfo[ i ].m_nDeviceId = -1; + } + + // abort startup if user requests no joystick + if ( CommandLine()->FindParm("-nojoy") ) return; + + const char *controllerConfig = joy_gamecontroller_config.GetString(); + if ( strlen(controllerConfig) > 0 ) + { + DevMsg("Passing joy_gamecontroller_config to SDL ('%s').\n", controllerConfig); + // We need to pass this hint to SDL *before* we init the gamecontroller subsystem, otherwise it gets ignored. + SDL_SetHint(SDL_HINT_GAMECONTROLLERCONFIG, controllerConfig); + } + + if ( SDL_InitSubSystem( SDL_INIT_GAMECONTROLLER | SDL_INIT_HAPTIC ) == -1 ) + { + Warning("Joystick init failed -- SDL_Init(SDL_INIT_GAMECONTROLLER|SDL_INIT_HAPTIC) failed: %s.\n", SDL_GetError()); + return; + } + + m_bJoystickInitialized = true; + + SDL_AddEventWatch(JoystickSDLWatcher, this); + + const int totalSticks = SDL_NumJoysticks(); + for ( int i = 0; i < totalSticks; i++ ) + { + if ( SDL_IsGameController(i) ) + { + JoystickHotplugAdded(i); + } + else + { + SDL_JoystickGUID joyGUID = SDL_JoystickGetDeviceGUID(i); + char szGUID[sizeof(joyGUID.data)*2 + 1]; + SDL_JoystickGetGUIDString(joyGUID, szGUID, sizeof(szGUID)); + + Msg("Found joystick '%s' (%s), but no recognized controller configuration for it.\n", SDL_JoystickNameForIndex(i), szGUID); + } + } + + if ( totalSticks < 1 ) + { + Msg("Did not detect any valid joysticks.\n"); + } +} + +void CInputSystem::ShutdownJoysticks() +{ + if ( !m_bJoystickInitialized ) + { + return; + } + + SDL_DelEventWatch( JoystickSDLWatcher, this ); + if ( m_pJoystickInfo[ 0 ].m_pDevice != NULL ) + { + JoystickHotplugRemoved( m_pJoystickInfo[ 0 ].m_nDeviceId ); + } + SDL_QuitSubSystem( SDL_INIT_GAMECONTROLLER | SDL_INIT_HAPTIC ); + + m_bJoystickInitialized = false; +} + +// Update the joy_xcontroller_found convar to force CInput::JoyStickMove to re-exec 360controller-linux.cfg +static void SetJoyXControllerFound( bool found ) +{ + static ConVarRef xcontrollerVar( "joy_xcontroller_found" ); + static ConVarRef joystickVar( "joystick" ); + if ( xcontrollerVar.IsValid() ) + { + xcontrollerVar.SetValue(found); + } + + if ( found && joystickVar.IsValid() ) + { + joystickVar.SetValue(true); + } +} + +void CInputSystem::JoystickHotplugAdded( int joystickIndex ) +{ + // SDL_IsGameController doesn't bounds check its inputs. + if ( joystickIndex < 0 || joystickIndex >= SDL_NumJoysticks() ) + { + return; + } + + if ( !SDL_IsGameController(joystickIndex) ) + { + Warning("Joystick is not recognized by the game controller system. You can configure the controller in Steam Big Picture mode.\n"); + return; + } + + SDL_Joystick *joystick = SDL_JoystickOpen(joystickIndex); + if ( joystick == NULL ) + { + Warning("Could not open joystick %i: %s", joystickIndex, SDL_GetError()); + return; + } + + int joystickId = SDL_JoystickInstanceID(joystick); + SDL_JoystickClose(joystick); + + int activeJoystick = joy_active.GetInt(); + JoystickInfo_t& info = m_pJoystickInfo[ 0 ]; + if ( activeJoystick < 0 ) + { + // Only opportunistically open devices if we don't have one open already. + if ( info.m_nDeviceId != -1 ) + { + Msg("Detected supported joystick #%i '%s'. Currently active joystick is #%i.\n", joystickId, SDL_JoystickNameForIndex(joystickIndex), info.m_nDeviceId); + return; + } + } + else if ( activeJoystick != joystickId ) + { + Msg("Detected supported joystick #%i '%s'. Currently active joystick is #%i.\n", joystickId, SDL_JoystickNameForIndex(joystickIndex), activeJoystick); + return; + } + + if ( info.m_nDeviceId != -1 ) + { + // Don't try to open the device we already have open. + if ( info.m_nDeviceId == joystickId ) + { + return; + } + + DevMsg("Joystick #%i already initialized, removing it first.\n", info.m_nDeviceId); + JoystickHotplugRemoved(info.m_nDeviceId); + } + + Msg("Initializing joystick #%i and making it active.\n", joystickId); + + SDL_GameController *controller = SDL_GameControllerOpen(joystickIndex); + if ( controller == NULL ) + { + Warning("Failed to open joystick %i: %s\n", joystickId, SDL_GetError()); + return; + } + + // XXX: This will fail if this is a *real* hotplug event (and not coming from the initial InitializeJoysticks call). + // That's because the SDL haptic subsystem currently doesn't do hotplugging. Everything but haptics will work fine. + SDL_Haptic *haptic = SDL_HapticOpenFromJoystick(SDL_GameControllerGetJoystick(controller)); + if ( haptic == NULL || SDL_HapticRumbleInit(haptic) != 0 ) + { + Warning("Unable to initialize rumble for joystick #%i: %s\n", joystickId, SDL_GetError()); + haptic = NULL; + } + + info.m_pDevice = controller; + info.m_pHaptic = haptic; + info.m_nDeviceId = SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(controller)); + info.m_nButtonCount = SDL_CONTROLLER_BUTTON_MAX; + info.m_bRumbleEnabled = false; + + SetJoyXControllerFound(true); + EnableJoystickInput(0, true); + m_nJoystickCount = 1; + m_bXController = true; + + // We reset joy_active to -1 because joystick ids are never reused - until you restart. + // Setting it to -1 means that you get expected hotplugging behavior if you disconnect the current joystick. + joy_active.SetValue(-1); +} + +void CInputSystem::JoystickHotplugRemoved( int joystickId ) +{ + JoystickInfo_t& info = m_pJoystickInfo[ 0 ]; + if ( info.m_nDeviceId != joystickId ) + { + DevMsg("Ignoring hotplug remove for #%i, active joystick is #%i.\n", joystickId, info.m_nDeviceId); + return; + } + + if ( info.m_pDevice == NULL ) + { + info.m_nDeviceId = -1; + DevMsg("Got hotplug remove event for removed joystick #%i, ignoring.\n", joystickId); + return; + } + + m_nJoystickCount = 0; + m_bXController = false; + EnableJoystickInput(0, false); + SetJoyXControllerFound(false); + + SDL_HapticClose((SDL_Haptic *)info.m_pHaptic); + SDL_GameControllerClose((SDL_GameController *)info.m_pDevice); + + info.m_pHaptic = NULL; + info.m_pDevice = NULL; + info.m_nButtonCount = 0; + info.m_nDeviceId = -1; + info.m_bRumbleEnabled = false; + + Msg("Joystick %i removed.\n", joystickId); +} + +void CInputSystem::JoystickButtonPress( int joystickId, int button ) +{ + JoystickInfo_t& info = m_pJoystickInfo[ 0 ]; + if ( info.m_nDeviceId != joystickId ) + { + Warning("Not active device input system (%i x %i)\n", info.m_nDeviceId, joystickId); + return; + } + + ButtonCode_t buttonCode = ControllerButtonToButtonCode((SDL_GameControllerButton)button); + PostButtonPressedEvent(IE_ButtonPressed, m_nLastSampleTick, buttonCode, buttonCode); +} + +void CInputSystem::JoystickButtonRelease( int joystickId, int button ) +{ + JoystickInfo_t& info = m_pJoystickInfo[ 0 ]; + if ( info.m_nDeviceId != joystickId ) + { + return; + } + + ButtonCode_t buttonCode = ControllerButtonToButtonCode((SDL_GameControllerButton)button); + PostButtonReleasedEvent(IE_ButtonReleased, m_nLastSampleTick, buttonCode, buttonCode); +} + + +void CInputSystem::JoystickAxisMotion( int joystickId, int axis, int value ) +{ + JoystickInfo_t& info = m_pJoystickInfo[ 0 ]; + if ( info.m_nDeviceId != joystickId ) + { + return; + } + + AnalogCode_t code = ControllerAxisToAnalogCode((SDL_GameControllerAxis)axis); + if ( code == ANALOG_CODE_INVALID ) + { + Warning("Invalid code for axis %i\n", axis); + return; + } + + ButtonCode_t buttonCode = BUTTON_CODE_NONE; + switch ( axis ) + { + case SDL_CONTROLLER_AXIS_TRIGGERRIGHT: + buttonCode = KEY_XBUTTON_RTRIGGER; + break; + case SDL_CONTROLLER_AXIS_TRIGGERLEFT: + buttonCode = KEY_XBUTTON_LTRIGGER; + break; + } + + if ( buttonCode != BUTTON_CODE_NONE ) + { + int pressThreshold = joy_axisbutton_threshold.GetFloat() * 32767; + int keyIndex = buttonCode - KEY_XBUTTON_LTRIGGER; + Assert( keyIndex < ARRAYSIZE( m_appXKeys[0] ) && keyIndex >= 0 ); + + appKey_t &key = m_appXKeys[0][keyIndex]; + if ( value > pressThreshold ) + { + if ( key.repeats < 1 ) + { + PostButtonPressedEvent( IE_ButtonPressed, m_nLastSampleTick, buttonCode, buttonCode ); + } + key.repeats++; + } + else + { + PostButtonReleasedEvent( IE_ButtonReleased, m_nLastSampleTick, buttonCode, buttonCode ); + key.repeats = 0; + } + } + + int minValue = joy_axis_deadzone.GetFloat() * 32767; + if ( abs(value) < minValue ) + { + value = 0; + } + + InputState_t& state = m_InputState[ m_bIsPolling ]; + state.m_pAnalogDelta[ code ] = value - state.m_pAnalogValue[ code ]; + state.m_pAnalogValue[ code ] = value; + if ( state.m_pAnalogDelta[ code ] != 0 ) + { + PostEvent(IE_AnalogValueChanged, m_nLastSampleTick, code, value, 0); + } +} + +//----------------------------------------------------------------------------- +// Process the event +//----------------------------------------------------------------------------- +void CInputSystem::JoystickButtonEvent( ButtonCode_t button, int sample ) +{ + // Not used - we post button events from JoystickButtonPress/Release. +} + + +//----------------------------------------------------------------------------- +// Update the joystick button state +//----------------------------------------------------------------------------- +void CInputSystem::UpdateJoystickButtonState( int nJoystick ) +{ + // We don't sample - we get events posted by SDL_GameController in JoystickSDLWatcher +} + + +//----------------------------------------------------------------------------- +// Update the joystick POV control +//----------------------------------------------------------------------------- +void CInputSystem::UpdateJoystickPOVControl( int nJoystick ) +{ + // SDL GameController does not support joystick POV. Should we poll? +} + + +//----------------------------------------------------------------------------- +// Purpose: Sample the joystick +//----------------------------------------------------------------------------- +void CInputSystem::PollJoystick( void ) +{ + // We only pump the SDL event loop if we're not an SDL app, since otherwise PollInputState_Platform calls into CSDLMgr to pump it. + // Our state updates happen in events posted by SDL_GameController in JoystickSDLWatcher, so the loop is empty. +#if !defined( USE_SDL ) + SDL_Event event; + int nEventsProcessed = 0; + + SDL_PumpEvents(); + while ( SDL_PollEvent( &event ) && nEventsProcessed < 100 ) + { + nEventsProcessed++; + } +#endif +} + +void CInputSystem::SetXDeviceRumble( float fLeftMotor, float fRightMotor, int userId ) +{ + JoystickInfo_t& info = m_pJoystickInfo[ 0 ]; + if ( info.m_nDeviceId < 0 || info.m_pHaptic == NULL ) + { + return; + } + + float strength = (fLeftMotor + fRightMotor) / 2.f; + static ConVarRef joystickVar( "joystick" ); + + // 0f means "stop". + bool shouldStop = ( strength < 0.01f ); + // If they've disabled the gamecontroller in settings, never rumble. + if ( !joystickVar.IsValid() || !joystickVar.GetBool() ) + { + shouldStop = true; + } + + if ( shouldStop ) + { + if ( info.m_bRumbleEnabled ) + { + SDL_HapticRumbleStop( (SDL_Haptic *)info.m_pHaptic ); + info.m_bRumbleEnabled = false; + info.m_fCurrentRumble = 0.0f; + } + + return; + } + + // If there's little change, then don't change the rumble strength. + if ( info.m_bRumbleEnabled && abs(info.m_fCurrentRumble - strength) < 0.01f ) + { + return; + } + + info.m_bRumbleEnabled = true; + info.m_fCurrentRumble = strength; + + if ( SDL_HapticRumblePlay((SDL_Haptic *)info.m_pHaptic, strength, SDL_HAPTIC_INFINITY) != 0 ) + { + Warning("Couldn't play rumble (strength %.1f): %s\n", strength, SDL_GetError()); + } +} + +ButtonCode_t ControllerButtonToButtonCode( SDL_GameControllerButton button ) +{ + switch ( button ) + { + case SDL_CONTROLLER_BUTTON_A: // KEY_XBUTTON_A + case SDL_CONTROLLER_BUTTON_B: // KEY_XBUTTON_B + case SDL_CONTROLLER_BUTTON_X: // KEY_XBUTTON_X + case SDL_CONTROLLER_BUTTON_Y: // KEY_XBUTTON_Y + return JOYSTICK_BUTTON(0, button); + + case SDL_CONTROLLER_BUTTON_BACK: + return KEY_XBUTTON_BACK; + case SDL_CONTROLLER_BUTTON_START: + return KEY_XBUTTON_START; + + case SDL_CONTROLLER_BUTTON_GUIDE: + return KEY_XBUTTON_BACK; // XXX: How are we supposed to handle this? Steam overlay etc. + + case SDL_CONTROLLER_BUTTON_LEFTSTICK: + return KEY_XBUTTON_STICK1; + case SDL_CONTROLLER_BUTTON_RIGHTSTICK: + return KEY_XBUTTON_STICK2; + case SDL_CONTROLLER_BUTTON_LEFTSHOULDER: + return KEY_XBUTTON_LEFT_SHOULDER; + case SDL_CONTROLLER_BUTTON_RIGHTSHOULDER: + return KEY_XBUTTON_RIGHT_SHOULDER; + + case SDL_CONTROLLER_BUTTON_DPAD_UP: + return KEY_XBUTTON_UP; + case SDL_CONTROLLER_BUTTON_DPAD_DOWN: + return KEY_XBUTTON_DOWN; + case SDL_CONTROLLER_BUTTON_DPAD_LEFT: + return KEY_XBUTTON_LEFT; + case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: + return KEY_XBUTTON_RIGHT; + } + + return BUTTON_CODE_NONE; +} + +AnalogCode_t ControllerAxisToAnalogCode( SDL_GameControllerAxis axis ) +{ + switch ( axis ) + { + case SDL_CONTROLLER_AXIS_LEFTX: + return JOYSTICK_AXIS(0, JOY_AXIS_X); + case SDL_CONTROLLER_AXIS_LEFTY: + return JOYSTICK_AXIS(0, JOY_AXIS_Y); + + case SDL_CONTROLLER_AXIS_RIGHTX: + return JOYSTICK_AXIS(0, JOY_AXIS_U); + case SDL_CONTROLLER_AXIS_RIGHTY: + return JOYSTICK_AXIS(0, JOY_AXIS_R); + + case SDL_CONTROLLER_AXIS_TRIGGERRIGHT: + case SDL_CONTROLLER_AXIS_TRIGGERLEFT: + return JOYSTICK_AXIS(0, JOY_AXIS_Z); + } + + return ANALOG_CODE_INVALID; +} diff --git a/inputsystem/key_translation.cpp b/inputsystem/key_translation.cpp new file mode 100644 index 0000000..f8474fb --- /dev/null +++ b/inputsystem/key_translation.cpp @@ -0,0 +1,911 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//===========================================================================// + + +#if defined( WIN32 ) +#if !defined( _X360 ) +#include <wtypes.h> +#include <winuser.h> +#include "xbox/xboxstubs.h" +#else +#include "xbox/xbox_win32stubs.h" +#endif +#endif // WIN32 + +#include "key_translation.h" +#include "tier1/convar.h" +#include "tier1/strtools.h" +#include "tier0/dbg.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#if defined(__clang__) + #pragma GCC diagnostic ignored "-Wchar-subscripts" +#endif + +static ButtonCode_t s_pVirtualKeyToButtonCode[256]; + +static ButtonCode_t s_pSKeytoButtonCode[SK_MAX_KEYS]; + +#if !defined( POSIX ) +static ButtonCode_t s_pXKeyTrans[XK_MAX_KEYS]; +#endif + +#define SCONTROLLERBUTTONS_BUTTONS( x ) \ + "SC_A",\ + "SC_B",\ + "SC_X",\ + "SC_Y",\ + "SC_DPAD_UP",\ + "SC_DPAD_RIGHT",\ + "SC_DPAD_DOWN",\ + "SC_DPAD_LEFT",\ + "SC_LEFT_BUMPER",\ + "SC_RIGHT_BUMPER",\ + "SC_LEFT_TRIGGER",\ + "SC_RIGHT_TRIGGER",\ + "SC_LEFT_GRIP",\ + "SC_RIGHT_GRIP",\ + "SC_LEFT_PAD_TOUCH",\ + "SC_RIGHT_PAD_TOUCH",\ + "SC_LEFT_PAD_CLICK",\ + "SC_RIGHT_PAD_CLICK",\ + "SC_LPAD_UP",\ + "SC_LPAD_RIGHT",\ + "SC_LPAD_DOWN",\ + "SC_LPAD_LEFT",\ + "SC_RPAD_UP",\ + "SC_RPAD_RIGHT",\ + "SC_RPAD_DOWN",\ + "SC_RPAD_LEFT",\ + "SC_SELECT",\ + "SC_START",\ + "SC_STEAM",\ + "SC_NULL" + +#define SCONTROLLERBUTTONS_AXIS( x ) \ + "SC_LPAD_AXIS_RIGHT",\ + "SC_LPAD_AXIS_LEFT",\ + "SC_LPAD_AXIS_DOWN",\ + "SC_LPAD_AXIS_UP",\ + "SC_AXIS_L_TRIGGER",\ + "SC_AXIS_R_TRIGGER",\ + "SC_RPAD_AXIS_RIGHT",\ + "SC_RPAD_AXIS_LEFT",\ + "SC_RPAD_AXIS_DOWN",\ + "SC_RPAD_AXIS_UP",\ + "SC_GYRO_AXIS_PITCH_POSITIVE",\ + "SC_GYRO_AXIS_PITCH_NEGATIVE",\ + "SC_GYRO_AXIS_ROLL_POSITIVE",\ + "SC_GYRO_AXIS_ROLL_NEGATIVE",\ + "SC_GYRO_AXIS_YAW_POSITIVE",\ + "SC_GYRO_AXIS_YAW_NEGATIVE" + +#define SCONTROLLERBUTTONS_VBUTTONS( x ) \ + "SC_F1",\ + "SC_F2",\ + "SC_F3",\ + "SC_F4",\ + "SC_F5",\ + "SC_F6",\ + "SC_F7",\ + "SC_F8",\ + "SC_F9",\ + "SC_F10",\ + "SC_F11",\ + "SC_F12" + +static int s_pButtonCodeToVirtual[BUTTON_CODE_LAST]; + +static const char *s_pButtonCodeName[ ] = +{ + "", // KEY_NONE + "0", // KEY_0, + "1", // KEY_1, + "2", // KEY_2, + "3", // KEY_3, + "4", // KEY_4, + "5", // KEY_5, + "6", // KEY_6, + "7", // KEY_7, + "8", // KEY_8, + "9", // KEY_9, + "a", // KEY_A, + "b", // KEY_B, + "c", // KEY_C, + "d", // KEY_D, + "e", // KEY_E, + "f", // KEY_F, + "g", // KEY_G, + "h", // KEY_H, + "i", // KEY_I, + "j", // KEY_J, + "k", // KEY_K, + "l", // KEY_L, + "m", // KEY_M, + "n", // KEY_N, + "o", // KEY_O, + "p", // KEY_P, + "q", // KEY_Q, + "r", // KEY_R, + "s", // KEY_S, + "t", // KEY_T, + "u", // KEY_U, + "v", // KEY_V, + "w", // KEY_W, + "x", // KEY_X, + "y", // KEY_Y, + "z", // KEY_Z, + "KP_INS", // KEY_PAD_0, + "KP_END", // KEY_PAD_1, + "KP_DOWNARROW", // KEY_PAD_2, + "KP_PGDN", // KEY_PAD_3, + "KP_LEFTARROW", // KEY_PAD_4, + "KP_5", // KEY_PAD_5, + "KP_RIGHTARROW",// KEY_PAD_6, + "KP_HOME", // KEY_PAD_7, + "KP_UPARROW", // KEY_PAD_8, + "KP_PGUP", // KEY_PAD_9, + "KP_SLASH", // KEY_PAD_DIVIDE, + "KP_MULTIPLY", // KEY_PAD_MULTIPLY, + "KP_MINUS", // KEY_PAD_MINUS, + "KP_PLUS", // KEY_PAD_PLUS, + "KP_ENTER", // KEY_PAD_ENTER, + "KP_DEL", // KEY_PAD_DECIMAL, + "[", // KEY_LBRACKET, + "]", // KEY_RBRACKET, + "SEMICOLON", // KEY_SEMICOLON, + "'", // KEY_APOSTROPHE, + "`", // KEY_BACKQUOTE, + ",", // KEY_COMMA, + ".", // KEY_PERIOD, + "/", // KEY_SLASH, + "\\", // KEY_BACKSLASH, + "-", // KEY_MINUS, + "=", // KEY_EQUAL, + "ENTER", // KEY_ENTER, + "SPACE", // KEY_SPACE, + "BACKSPACE", // KEY_BACKSPACE, + "TAB", // KEY_TAB, + "CAPSLOCK", // KEY_CAPSLOCK, + "NUMLOCK", // KEY_NUMLOCK, + "ESCAPE", // KEY_ESCAPE, + "SCROLLLOCK", // KEY_SCROLLLOCK, + "INS", // KEY_INSERT, + "DEL", // KEY_DELETE, + "HOME", // KEY_HOME, + "END", // KEY_END, + "PGUP", // KEY_PAGEUP, + "PGDN", // KEY_PAGEDOWN, + "PAUSE", // KEY_BREAK, + "SHIFT", // KEY_LSHIFT, + "RSHIFT", // KEY_RSHIFT, + "ALT", // KEY_LALT, + "RALT", // KEY_RALT, + "CTRL", // KEY_LCONTROL, + "RCTRL", // KEY_RCONTROL, + "LWIN", // KEY_LWIN, + "RWIN", // KEY_RWIN, + "APP", // KEY_APP, + "UPARROW", // KEY_UP, + "LEFTARROW", // KEY_LEFT, + "DOWNARROW", // KEY_DOWN, + "RIGHTARROW", // KEY_RIGHT, + "F1", // KEY_F1, + "F2", // KEY_F2, + "F3", // KEY_F3, + "F4", // KEY_F4, + "F5", // KEY_F5, + "F6", // KEY_F6, + "F7", // KEY_F7, + "F8", // KEY_F8, + "F9", // KEY_F9, + "F10", // KEY_F10, + "F11", // KEY_F11, + "F12", // KEY_F12, + + // FIXME: CAPSLOCK/NUMLOCK/SCROLLLOCK all appear above. What are these for?! + // They only appear in CInputWin32::UpdateToggleButtonState in vgui2 + "CAPSLOCKTOGGLE", // KEY_CAPSLOCKTOGGLE, + "NUMLOCKTOGGLE", // KEY_NUMLOCKTOGGLE, + "SCROLLLOCKTOGGLE", // KEY_SCROLLLOCKTOGGLE, + + // Mouse + "MOUSE1", // MOUSE_LEFT, + "MOUSE2", // MOUSE_RIGHT, + "MOUSE3", // MOUSE_MIDDLE, + "MOUSE4", // MOUSE_4, + "MOUSE5", // MOUSE_5, + + "MWHEELUP", // MOUSE_WHEEL_UP + "MWHEELDOWN", // MOUSE_WHEEL_DOWN + +#if defined ( _X360 ) || defined ( _LINUX ) + "A_BUTTON", // JOYSTICK_FIRST_BUTTON + "B_BUTTON", + "X_BUTTON", + "Y_BUTTON", + "L_SHOULDER", + "R_SHOULDER", + "BACK", + "START", + "STICK1", + "STICK2", +#else + "JOY1", // JOYSTICK_FIRST_BUTTON + "JOY2", + "JOY3", + "JOY4", + "JOY5", + "JOY6", + "JOY7", + "JOY8", + "JOY9", + "JOY10", +#endif + + "JOY11", + "JOY12", + "JOY13", + "JOY14", + "JOY15", + "JOY16", + "JOY17", + "JOY18", + "JOY19", + "JOY20", + "JOY21", + "JOY22", + "JOY23", + "JOY24", + "JOY25", + "JOY26", + "JOY27", + "JOY28", + "JOY29", + "JOY30", + "JOY31", + "JOY32", // JOYSTICK_LAST_BUTTON + +#if defined ( _X360 ) + "UP", // JOYSTICK_FIRST_POV_BUTTON + "RIGHT", + "DOWN", + "LEFT", // JOYSTICK_LAST_POV_BUTTON + + "S1_RIGHT", // JOYSTICK_FIRST_AXIS_BUTTON + "S1_LEFT", + "S1_DOWN", + "S1_UP", + "L_TRIGGER", + "R_TRIGGER", + "S2_RIGHT", + "S2_LEFT", + "S2_DOWN", + "S2_UP", // JOYSTICK_LAST_AXIS_BUTTON + "V AXIS POS", + "V AXIS NEG", +#else + "POV_UP", // JOYSTICK_FIRST_POV_BUTTON + "POV_RIGHT", + "POV_DOWN", + "POV_LEFT", // JOYSTICK_LAST_POV_BUTTON + + "X AXIS POS", // JOYSTICK_FIRST_AXIS_BUTTON + "X AXIS NEG", + "Y AXIS POS", + "Y AXIS NEG", + "Z AXIS POS", + "Z AXIS NEG", + "R AXIS POS", + "R AXIS NEG", + "U AXIS POS", + "U AXIS NEG", + "V AXIS POS", + "V AXIS NEG", // JOYSTICK_LAST_AXIS_BUTTON + "FALCON_NULL", // NVNT temp Fix for unaligned joystick enumeration + "FALCON_1", // NOVINT_FIRST + "FALCON_2", + "FALCON_3", + "FALCON_4", + "FALCON2_1", + "FALCON2_2", + "FALCON2_3", + "FALCON2_4", // NOVINT_LAST +#endif + + SCONTROLLERBUTTONS_BUTTONS( 0 ), + SCONTROLLERBUTTONS_BUTTONS( 1 ), + SCONTROLLERBUTTONS_BUTTONS( 2 ), + SCONTROLLERBUTTONS_BUTTONS( 3 ), + SCONTROLLERBUTTONS_BUTTONS( 4 ), + SCONTROLLERBUTTONS_BUTTONS( 5 ), + SCONTROLLERBUTTONS_BUTTONS( 6 ), + SCONTROLLERBUTTONS_BUTTONS( 7 ), + + SCONTROLLERBUTTONS_AXIS( 0 ), + SCONTROLLERBUTTONS_AXIS( 1 ), + SCONTROLLERBUTTONS_AXIS( 2 ), + SCONTROLLERBUTTONS_AXIS( 3 ), + SCONTROLLERBUTTONS_AXIS( 4 ), + SCONTROLLERBUTTONS_AXIS( 5 ), + SCONTROLLERBUTTONS_AXIS( 6 ), + SCONTROLLERBUTTONS_AXIS( 7 ), + + SCONTROLLERBUTTONS_VBUTTONS( 0 ), + SCONTROLLERBUTTONS_VBUTTONS( 1 ), + SCONTROLLERBUTTONS_VBUTTONS( 2 ), + SCONTROLLERBUTTONS_VBUTTONS( 3 ), + SCONTROLLERBUTTONS_VBUTTONS( 4 ), + SCONTROLLERBUTTONS_VBUTTONS( 5 ), + SCONTROLLERBUTTONS_VBUTTONS( 6 ), + SCONTROLLERBUTTONS_VBUTTONS( 7 ), +}; + +static const char *s_pAnalogCodeName[ ] = +{ + "MOUSE_X", // MOUSE_X = 0, + "MOUSE_Y", // MOUSE_Y, + "MOUSE_XY", // MOUSE_XY, // Invoked when either x or y changes + "MOUSE_WHEEL", // MOUSE_WHEEL, + + "X AXIS", // JOY_AXIS_X + "Y AXIS", // JOY_AXIS_Y + "Z AXIS", // JOY_AXIS_Z + "R AXIS", // JOY_AXIS_R + "U AXIS", // JOY_AXIS_U + "V AXIS", // JOY_AXIS_V +}; + +#if !defined ( _X360 ) +static const char *s_pXControllerButtonCodeNames[ ] = +{ + "A_BUTTON", // JOYSTICK_FIRST_BUTTON + "B_BUTTON", + "X_BUTTON", + "Y_BUTTON", + "L_SHOULDER", + "R_SHOULDER", + "BACK", + "START", + "STICK1", + "STICK2", + "JOY11", + "JOY12", + "JOY13", + "JOY14", + "JOY15", + "JOY16", + "JOY17", + "JOY18", + "JOY19", + "JOY20", + "JOY21", + "JOY22", + "JOY23", + "JOY24", + "JOY25", + "JOY26", + "JOY27", + "JOY28", + "JOY29", + "JOY30", + "JOY31", + "JOY32", // JOYSTICK_LAST_BUTTON + + "UP", // JOYSTICK_FIRST_POV_BUTTON + "RIGHT", + "DOWN", + "LEFT", // JOYSTICK_LAST_POV_BUTTON + + "S1_RIGHT", // JOYSTICK_FIRST_AXIS_BUTTON + "S1_LEFT", + "S1_DOWN", + "S1_UP", + "L_TRIGGER", + "R_TRIGGER", + "S2_RIGHT", + "S2_LEFT", + "S2_DOWN", + "S2_UP", // JOYSTICK_LAST_AXIS_BUTTON + "V AXIS POS", + "V AXIS NEG", +}; +#endif + +// this maps non-translated keyboard scan codes to engine key codes +// Google for 'Keyboard Scan Code Specification' +static ButtonCode_t s_pScanToButtonCode_QWERTY[128] = +{ + // 0 1 2 3 4 5 6 7 + // 8 9 A B C D E F + KEY_NONE, KEY_ESCAPE, KEY_1, KEY_2, KEY_3, KEY_4, KEY_5, KEY_6, // 0 + KEY_7, KEY_8, KEY_9, KEY_0, KEY_MINUS, KEY_EQUAL, KEY_BACKSPACE, KEY_TAB, // 0 + + KEY_Q, KEY_W, KEY_E, KEY_R, KEY_T, KEY_Y, KEY_U, KEY_I, // 1 + KEY_O, KEY_P, KEY_LBRACKET, KEY_RBRACKET, KEY_ENTER, KEY_LCONTROL, KEY_A, KEY_S, // 1 + + KEY_D, KEY_F, KEY_G, KEY_H, KEY_J, KEY_K, KEY_L, KEY_SEMICOLON, // 2 + KEY_APOSTROPHE, KEY_BACKQUOTE, KEY_LSHIFT, KEY_BACKSLASH, KEY_Z, KEY_X, KEY_C, KEY_V, // 2 + + KEY_B, KEY_N, KEY_M, KEY_COMMA, KEY_PERIOD, KEY_SLASH, KEY_RSHIFT, KEY_PAD_MULTIPLY,// 3 + KEY_LALT, KEY_SPACE, KEY_CAPSLOCK, KEY_F1, KEY_F2, KEY_F3, KEY_F4, KEY_F5, // 3 + + KEY_F6, KEY_F7, KEY_F8, KEY_F9, KEY_F10, KEY_NUMLOCK, KEY_SCROLLLOCK, KEY_HOME, // 4 + KEY_UP, KEY_PAGEUP, KEY_PAD_MINUS, KEY_LEFT, KEY_PAD_5, KEY_RIGHT, KEY_PAD_PLUS, KEY_END, // 4 + + KEY_DOWN, KEY_PAGEDOWN, KEY_INSERT, KEY_DELETE, KEY_NONE, KEY_NONE, KEY_NONE, KEY_F11, // 5 + KEY_F12, KEY_BREAK, KEY_NONE, KEY_NONE, KEY_NONE, KEY_NONE, KEY_NONE, KEY_NONE, // 5 + + KEY_NONE, KEY_NONE, KEY_NONE, KEY_NONE, KEY_NONE, KEY_NONE, KEY_NONE, KEY_NONE, // 6 + KEY_NONE, KEY_NONE, KEY_NONE, KEY_NONE, KEY_NONE, KEY_NONE, KEY_NONE, KEY_NONE, // 6 + + KEY_NONE, KEY_NONE, KEY_NONE, KEY_NONE, KEY_NONE, KEY_NONE, KEY_NONE, KEY_NONE, // 7 + KEY_NONE, KEY_NONE, KEY_NONE, KEY_NONE, KEY_NONE, KEY_NONE, KEY_NONE, KEY_NONE // 7 +}; + +static ButtonCode_t s_pScanToButtonCode[128]; + + +void ButtonCode_InitKeyTranslationTable() +{ + COMPILE_TIME_ASSERT( sizeof(s_pButtonCodeName) / sizeof( const char * ) == BUTTON_CODE_LAST ); + COMPILE_TIME_ASSERT( sizeof(s_pAnalogCodeName) / sizeof( const char * ) == ANALOG_CODE_LAST ); + + // set virtual key translation table + memset( s_pVirtualKeyToButtonCode, KEY_NONE, sizeof(s_pVirtualKeyToButtonCode) ); + + s_pVirtualKeyToButtonCode['0'] =KEY_0; + s_pVirtualKeyToButtonCode['1'] =KEY_1; + s_pVirtualKeyToButtonCode['2'] =KEY_2; + s_pVirtualKeyToButtonCode['3'] =KEY_3; + s_pVirtualKeyToButtonCode['4'] =KEY_4; + s_pVirtualKeyToButtonCode['5'] =KEY_5; + s_pVirtualKeyToButtonCode['6'] =KEY_6; + s_pVirtualKeyToButtonCode['7'] =KEY_7; + s_pVirtualKeyToButtonCode['8'] =KEY_8; + s_pVirtualKeyToButtonCode['9'] =KEY_9; + s_pVirtualKeyToButtonCode['A'] =KEY_A; + s_pVirtualKeyToButtonCode['B'] =KEY_B; + s_pVirtualKeyToButtonCode['C'] =KEY_C; + s_pVirtualKeyToButtonCode['D'] =KEY_D; + s_pVirtualKeyToButtonCode['E'] =KEY_E; + s_pVirtualKeyToButtonCode['F'] =KEY_F; + s_pVirtualKeyToButtonCode['G'] =KEY_G; + s_pVirtualKeyToButtonCode['H'] =KEY_H; + s_pVirtualKeyToButtonCode['I'] =KEY_I; + s_pVirtualKeyToButtonCode['J'] =KEY_J; + s_pVirtualKeyToButtonCode['K'] =KEY_K; + s_pVirtualKeyToButtonCode['L'] =KEY_L; + s_pVirtualKeyToButtonCode['M'] =KEY_M; + s_pVirtualKeyToButtonCode['N'] =KEY_N; + s_pVirtualKeyToButtonCode['O'] =KEY_O; + s_pVirtualKeyToButtonCode['P'] =KEY_P; + s_pVirtualKeyToButtonCode['Q'] =KEY_Q; + s_pVirtualKeyToButtonCode['R'] =KEY_R; + s_pVirtualKeyToButtonCode['S'] =KEY_S; + s_pVirtualKeyToButtonCode['T'] =KEY_T; + s_pVirtualKeyToButtonCode['U'] =KEY_U; + s_pVirtualKeyToButtonCode['V'] =KEY_V; + s_pVirtualKeyToButtonCode['W'] =KEY_W; + s_pVirtualKeyToButtonCode['X'] =KEY_X; + s_pVirtualKeyToButtonCode['Y'] =KEY_Y; + s_pVirtualKeyToButtonCode['Z'] =KEY_Z; +#if !defined( POSIX ) + s_pVirtualKeyToButtonCode[VK_NUMPAD0] =KEY_PAD_0; + s_pVirtualKeyToButtonCode[VK_NUMPAD1] =KEY_PAD_1; + s_pVirtualKeyToButtonCode[VK_NUMPAD2] =KEY_PAD_2; + s_pVirtualKeyToButtonCode[VK_NUMPAD3] =KEY_PAD_3; + s_pVirtualKeyToButtonCode[VK_NUMPAD4] =KEY_PAD_4; + s_pVirtualKeyToButtonCode[VK_NUMPAD5] =KEY_PAD_5; + s_pVirtualKeyToButtonCode[VK_NUMPAD6] =KEY_PAD_6; + s_pVirtualKeyToButtonCode[VK_NUMPAD7] =KEY_PAD_7; + s_pVirtualKeyToButtonCode[VK_NUMPAD8] =KEY_PAD_8; + s_pVirtualKeyToButtonCode[VK_NUMPAD9] =KEY_PAD_9; + s_pVirtualKeyToButtonCode[VK_DIVIDE] =KEY_PAD_DIVIDE; + s_pVirtualKeyToButtonCode[VK_MULTIPLY] =KEY_PAD_MULTIPLY; + s_pVirtualKeyToButtonCode[VK_SUBTRACT] =KEY_PAD_MINUS; + s_pVirtualKeyToButtonCode[VK_ADD] =KEY_PAD_PLUS; + s_pVirtualKeyToButtonCode[VK_RETURN] =KEY_PAD_ENTER; + s_pVirtualKeyToButtonCode[VK_DECIMAL] =KEY_PAD_DECIMAL; +#endif + s_pVirtualKeyToButtonCode[0xdb] =KEY_LBRACKET; + s_pVirtualKeyToButtonCode[0xdd] =KEY_RBRACKET; + s_pVirtualKeyToButtonCode[0xba] =KEY_SEMICOLON; + s_pVirtualKeyToButtonCode[0xde] =KEY_APOSTROPHE; + s_pVirtualKeyToButtonCode[0xc0] =KEY_BACKQUOTE; + s_pVirtualKeyToButtonCode[0xbc] =KEY_COMMA; + s_pVirtualKeyToButtonCode[0xbe] =KEY_PERIOD; + s_pVirtualKeyToButtonCode[0xbf] =KEY_SLASH; + s_pVirtualKeyToButtonCode[0xdc] =KEY_BACKSLASH; + s_pVirtualKeyToButtonCode[0xbd] =KEY_MINUS; + s_pVirtualKeyToButtonCode[0xbb] =KEY_EQUAL; +#if !defined( POSIX ) + s_pVirtualKeyToButtonCode[VK_RETURN] =KEY_ENTER; + s_pVirtualKeyToButtonCode[VK_SPACE] =KEY_SPACE; + s_pVirtualKeyToButtonCode[VK_BACK] =KEY_BACKSPACE; + s_pVirtualKeyToButtonCode[VK_TAB] =KEY_TAB; + s_pVirtualKeyToButtonCode[VK_CAPITAL] =KEY_CAPSLOCK; + s_pVirtualKeyToButtonCode[VK_NUMLOCK] =KEY_NUMLOCK; + s_pVirtualKeyToButtonCode[VK_ESCAPE] =KEY_ESCAPE; + s_pVirtualKeyToButtonCode[VK_SCROLL] =KEY_SCROLLLOCK; + s_pVirtualKeyToButtonCode[VK_INSERT] =KEY_INSERT; + s_pVirtualKeyToButtonCode[VK_DELETE] =KEY_DELETE; + s_pVirtualKeyToButtonCode[VK_HOME] =KEY_HOME; + s_pVirtualKeyToButtonCode[VK_END] =KEY_END; + s_pVirtualKeyToButtonCode[VK_PRIOR] =KEY_PAGEUP; + s_pVirtualKeyToButtonCode[VK_NEXT] =KEY_PAGEDOWN; + s_pVirtualKeyToButtonCode[VK_PAUSE] =KEY_BREAK; + s_pVirtualKeyToButtonCode[VK_SHIFT] =KEY_RSHIFT; + s_pVirtualKeyToButtonCode[VK_SHIFT] =KEY_LSHIFT; // SHIFT -> left SHIFT + s_pVirtualKeyToButtonCode[VK_MENU] =KEY_RALT; + s_pVirtualKeyToButtonCode[VK_MENU] =KEY_LALT; // ALT -> left ALT + s_pVirtualKeyToButtonCode[VK_CONTROL] =KEY_RCONTROL; + s_pVirtualKeyToButtonCode[VK_CONTROL] =KEY_LCONTROL; // CTRL -> left CTRL + s_pVirtualKeyToButtonCode[VK_LWIN] =KEY_LWIN; + s_pVirtualKeyToButtonCode[VK_RWIN] =KEY_RWIN; + s_pVirtualKeyToButtonCode[VK_APPS] =KEY_APP; + s_pVirtualKeyToButtonCode[VK_UP] =KEY_UP; + s_pVirtualKeyToButtonCode[VK_LEFT] =KEY_LEFT; + s_pVirtualKeyToButtonCode[VK_DOWN] =KEY_DOWN; + s_pVirtualKeyToButtonCode[VK_RIGHT] =KEY_RIGHT; + s_pVirtualKeyToButtonCode[VK_F1] =KEY_F1; + s_pVirtualKeyToButtonCode[VK_F2] =KEY_F2; + s_pVirtualKeyToButtonCode[VK_F3] =KEY_F3; + s_pVirtualKeyToButtonCode[VK_F4] =KEY_F4; + s_pVirtualKeyToButtonCode[VK_F5] =KEY_F5; + s_pVirtualKeyToButtonCode[VK_F6] =KEY_F6; + s_pVirtualKeyToButtonCode[VK_F7] =KEY_F7; + s_pVirtualKeyToButtonCode[VK_F8] =KEY_F8; + s_pVirtualKeyToButtonCode[VK_F9] =KEY_F9; + s_pVirtualKeyToButtonCode[VK_F10] =KEY_F10; + s_pVirtualKeyToButtonCode[VK_F11] =KEY_F11; + s_pVirtualKeyToButtonCode[VK_F12] =KEY_F12; +#endif + + // init the xkey translation table +#if !defined( POSIX ) + s_pXKeyTrans[XK_NULL] = KEY_NONE; + s_pXKeyTrans[XK_BUTTON_UP] = KEY_XBUTTON_UP; + s_pXKeyTrans[XK_BUTTON_DOWN] = KEY_XBUTTON_DOWN; + s_pXKeyTrans[XK_BUTTON_LEFT] = KEY_XBUTTON_LEFT; + s_pXKeyTrans[XK_BUTTON_RIGHT] = KEY_XBUTTON_RIGHT; + s_pXKeyTrans[XK_BUTTON_START] = KEY_XBUTTON_START; + s_pXKeyTrans[XK_BUTTON_BACK] = KEY_XBUTTON_BACK; + s_pXKeyTrans[XK_BUTTON_STICK1] = KEY_XBUTTON_STICK1; + s_pXKeyTrans[XK_BUTTON_STICK2] = KEY_XBUTTON_STICK2; + s_pXKeyTrans[XK_BUTTON_A] = KEY_XBUTTON_A; + s_pXKeyTrans[XK_BUTTON_B] = KEY_XBUTTON_B; + s_pXKeyTrans[XK_BUTTON_X] = KEY_XBUTTON_X; + s_pXKeyTrans[XK_BUTTON_Y] = KEY_XBUTTON_Y; + s_pXKeyTrans[XK_BUTTON_LEFT_SHOULDER] = KEY_XBUTTON_LEFT_SHOULDER; + s_pXKeyTrans[XK_BUTTON_RIGHT_SHOULDER] = KEY_XBUTTON_RIGHT_SHOULDER; + s_pXKeyTrans[XK_BUTTON_LTRIGGER] = KEY_XBUTTON_LTRIGGER; + s_pXKeyTrans[XK_BUTTON_RTRIGGER] = KEY_XBUTTON_RTRIGGER; + s_pXKeyTrans[XK_STICK1_UP] = KEY_XSTICK1_UP; + s_pXKeyTrans[XK_STICK1_DOWN] = KEY_XSTICK1_DOWN; + s_pXKeyTrans[XK_STICK1_LEFT] = KEY_XSTICK1_LEFT; + s_pXKeyTrans[XK_STICK1_RIGHT] = KEY_XSTICK1_RIGHT; + s_pXKeyTrans[XK_STICK2_UP] = KEY_XSTICK2_UP; + s_pXKeyTrans[XK_STICK2_DOWN] = KEY_XSTICK2_DOWN; + s_pXKeyTrans[XK_STICK2_LEFT] = KEY_XSTICK2_LEFT; + s_pXKeyTrans[XK_STICK2_RIGHT] = KEY_XSTICK2_RIGHT; +#endif // PLATFORM_POSIX + + // create reverse table engine to virtual + for ( int i = 0; i < ARRAYSIZE( s_pVirtualKeyToButtonCode ); i++ ) + { + s_pButtonCodeToVirtual[ s_pVirtualKeyToButtonCode[i] ] = i; + } + + s_pButtonCodeToVirtual[0] = 0; + + s_pSKeytoButtonCode[SK_NULL] = KEY_NONE; + s_pSKeytoButtonCode[SK_BUTTON_A] = STEAMCONTROLLER_A; + s_pSKeytoButtonCode[SK_BUTTON_B] = STEAMCONTROLLER_B; + s_pSKeytoButtonCode[SK_BUTTON_X] = STEAMCONTROLLER_X; + s_pSKeytoButtonCode[SK_BUTTON_Y] = STEAMCONTROLLER_Y; + s_pSKeytoButtonCode[SK_BUTTON_UP] = STEAMCONTROLLER_DPAD_UP; + s_pSKeytoButtonCode[SK_BUTTON_RIGHT] = STEAMCONTROLLER_DPAD_RIGHT; + s_pSKeytoButtonCode[SK_BUTTON_DOWN] = STEAMCONTROLLER_DPAD_DOWN; + s_pSKeytoButtonCode[SK_BUTTON_LEFT] = STEAMCONTROLLER_DPAD_LEFT; + s_pSKeytoButtonCode[SK_BUTTON_LEFT_BUMPER] = STEAMCONTROLLER_LEFT_BUMPER; + s_pSKeytoButtonCode[SK_BUTTON_RIGHT_BUMPER] = STEAMCONTROLLER_RIGHT_BUMPER; + s_pSKeytoButtonCode[SK_BUTTON_LEFT_TRIGGER] = STEAMCONTROLLER_LEFT_TRIGGER; + s_pSKeytoButtonCode[SK_BUTTON_RIGHT_TRIGGER] = STEAMCONTROLLER_RIGHT_TRIGGER; + s_pSKeytoButtonCode[SK_BUTTON_LEFT_GRIP] = STEAMCONTROLLER_LEFT_GRIP; + s_pSKeytoButtonCode[SK_BUTTON_RIGHT_GRIP] = STEAMCONTROLLER_RIGHT_GRIP; + s_pSKeytoButtonCode[SK_BUTTON_LPAD_TOUCH] = STEAMCONTROLLER_LEFT_PAD_FINGERDOWN; + s_pSKeytoButtonCode[SK_BUTTON_RPAD_TOUCH] = STEAMCONTROLLER_RIGHT_PAD_FINGERDOWN; + s_pSKeytoButtonCode[SK_BUTTON_LPAD_CLICK] = STEAMCONTROLLER_LEFT_PAD_CLICK; + s_pSKeytoButtonCode[SK_BUTTON_RPAD_CLICK] = STEAMCONTROLLER_RIGHT_PAD_CLICK; + s_pSKeytoButtonCode[SK_BUTTON_LPAD_UP] = STEAMCONTROLLER_LEFT_PAD_UP; + s_pSKeytoButtonCode[SK_BUTTON_LPAD_RIGHT] = STEAMCONTROLLER_LEFT_PAD_RIGHT; + s_pSKeytoButtonCode[SK_BUTTON_LPAD_DOWN] = STEAMCONTROLLER_LEFT_PAD_DOWN; + s_pSKeytoButtonCode[SK_BUTTON_LPAD_LEFT] = STEAMCONTROLLER_LEFT_PAD_LEFT; + s_pSKeytoButtonCode[SK_BUTTON_RPAD_UP] = STEAMCONTROLLER_RIGHT_PAD_UP; + s_pSKeytoButtonCode[SK_BUTTON_RPAD_RIGHT] = STEAMCONTROLLER_RIGHT_PAD_RIGHT; + s_pSKeytoButtonCode[SK_BUTTON_RPAD_DOWN] = STEAMCONTROLLER_RIGHT_PAD_DOWN; + s_pSKeytoButtonCode[SK_BUTTON_RPAD_LEFT] = STEAMCONTROLLER_RIGHT_PAD_LEFT; + s_pSKeytoButtonCode[SK_BUTTON_SELECT] = STEAMCONTROLLER_SELECT; + s_pSKeytoButtonCode[SK_BUTTON_START] = STEAMCONTROLLER_START; + s_pSKeytoButtonCode[SK_BUTTON_STEAM] = STEAMCONTROLLER_STEAM; + s_pSKeytoButtonCode[SK_BUTTON_INACTIVE_START] = STEAMCONTROLLER_INACTIVE_START; + + // These are fake ("virtual") steam controller buttons that don't physically exist, but we can manufacture to make internal routing + // to old school UI (which is expecting button code rather than actions) without clashing with other butt + s_pSKeytoButtonCode[SK_VBUTTON_F1] = STEAMCONTROLLER_F1; + s_pSKeytoButtonCode[SK_VBUTTON_F2] = STEAMCONTROLLER_F2; + s_pSKeytoButtonCode[SK_VBUTTON_F3] = STEAMCONTROLLER_F3; + s_pSKeytoButtonCode[SK_VBUTTON_F4] = STEAMCONTROLLER_F4; + s_pSKeytoButtonCode[SK_VBUTTON_F5] = STEAMCONTROLLER_F5; + s_pSKeytoButtonCode[SK_VBUTTON_F6] = STEAMCONTROLLER_F6; + s_pSKeytoButtonCode[SK_VBUTTON_F7] = STEAMCONTROLLER_F7; + s_pSKeytoButtonCode[SK_VBUTTON_F8] = STEAMCONTROLLER_F8; + s_pSKeytoButtonCode[SK_VBUTTON_F9] = STEAMCONTROLLER_F9; + s_pSKeytoButtonCode[SK_VBUTTON_F10] = STEAMCONTROLLER_F10; + s_pSKeytoButtonCode[SK_VBUTTON_F11] = STEAMCONTROLLER_F11; + s_pSKeytoButtonCode[SK_VBUTTON_F12] = STEAMCONTROLLER_F12; +} + +ButtonCode_t ButtonCode_VirtualKeyToButtonCode( int keyCode ) +{ + if ( keyCode < 0 || keyCode >= sizeof( s_pVirtualKeyToButtonCode ) / sizeof( s_pVirtualKeyToButtonCode[0] ) ) + { + Assert( false ); + return KEY_NONE; + } + return s_pVirtualKeyToButtonCode[keyCode]; +} + +int ButtonCode_ButtonCodeToVirtualKey( ButtonCode_t code ) +{ + return s_pButtonCodeToVirtual[code]; +} + +ButtonCode_t ButtonCode_XKeyToButtonCode( int nPort, int keyCode ) +{ +#if !defined( POSIX ) + if ( keyCode < 0 || keyCode >= sizeof( s_pXKeyTrans ) / sizeof( s_pXKeyTrans[0] ) ) + { + Assert( false ); + return KEY_NONE; + } + + ButtonCode_t code = s_pXKeyTrans[keyCode]; + if ( IsJoystickButtonCode( code ) ) + { + int nOffset = code - JOYSTICK_FIRST_BUTTON; + return JOYSTICK_BUTTON( nPort, nOffset ); + } + + if ( IsJoystickPOVCode( code ) ) + { + int nOffset = code - JOYSTICK_FIRST_POV_BUTTON; + return JOYSTICK_POV_BUTTON( nPort, nOffset ); + } + + if ( IsJoystickAxisCode( code ) ) + { + int nOffset = code - JOYSTICK_FIRST_AXIS_BUTTON; + return JOYSTICK_AXIS_BUTTON( nPort, nOffset ); + } + + return code; +#else // POSIX + return KEY_NONE; +#endif // POSIX +} + +// Convert back + forth between ButtonCode/AnalogCode + strings +const char *ButtonCode_ButtonCodeToString( ButtonCode_t code, bool bXController ) +{ +#if !defined ( _X360 ) + if ( bXController && code >= JOYSTICK_FIRST_BUTTON && code <= JOYSTICK_LAST_AXIS_BUTTON ) + return s_pXControllerButtonCodeNames[ code - JOYSTICK_FIRST_BUTTON ]; +#endif + + return s_pButtonCodeName[ code ]; +} + +const char *AnalogCode_AnalogCodeToString( AnalogCode_t code ) +{ + return s_pAnalogCodeName[ code ]; +} + +ButtonCode_t ButtonCode_StringToButtonCode( const char *pString, bool bXController ) +{ + if ( !pString || !pString[0] ) + return BUTTON_CODE_INVALID; + + // Backward compat for screwed up previous joystick button names + if ( !Q_strnicmp( pString, "aux", 3 ) ) + { + int nIndex = atoi( &pString[3] ); + if ( nIndex < 29 ) + return JOYSTICK_BUTTON( 0, nIndex ); + if ( ( nIndex >= 29 ) && ( nIndex <= 32 ) ) + return JOYSTICK_POV_BUTTON( 0, nIndex - 29 ); + return BUTTON_CODE_INVALID; + } + + for ( int i = 0; i < BUTTON_CODE_LAST; ++i ) + { + if ( !Q_stricmp( s_pButtonCodeName[i], pString ) ) + return (ButtonCode_t)i; + } + +#if !defined ( _X360 ) + if ( bXController ) + { + for ( int i = 0; i < ARRAYSIZE(s_pXControllerButtonCodeNames); ++i ) + { + if ( !Q_stricmp( s_pXControllerButtonCodeNames[i], pString ) ) + return (ButtonCode_t)(JOYSTICK_FIRST_BUTTON + i); + } + } +#endif + + return BUTTON_CODE_INVALID; +} + +ButtonCode_t ButtonCode_SKeyToButtonCode( int nPort, int keyCode ) +{ +#if !defined( _GAMECONSOLE ) + if ( keyCode < 0 || keyCode >= sizeof( s_pSKeytoButtonCode ) / sizeof( s_pSKeytoButtonCode[0] ) ) + { + Assert( false ); + return KEY_NONE; + } + + ButtonCode_t code = s_pSKeytoButtonCode[keyCode]; + // if ( IsSteamControllerCode( code ) ) + // { + // // Need Per Controller Offset here. + // return code; + // } + + if ( IsSteamControllerButtonCode( code ) ) + { + int nOffset = code - STEAMCONTROLLER_FIRST_BUTTON; + return STEAMCONTROLLER_BUTTON( nPort, nOffset ); + } + + if ( IsSteamControllerAxisCode( code ) ) + { + int nOffset = code - STEAMCONTROLLER_FIRST_AXIS_BUTTON; + return STEAMCONTROLLER_AXIS_BUTTON( nPort, nOffset ); + } + + return code; +#else // _GAMECONSOLE + return KEY_NONE; +#endif // _GAMECONSOLE +} + +AnalogCode_t AnalogCode_StringToAnalogCode( const char *pString ) +{ + if ( !pString || !pString[0] ) + return ANALOG_CODE_INVALID; + + for ( int i = 0; i < ANALOG_CODE_LAST; ++i ) + { + if ( !Q_stricmp( s_pAnalogCodeName[i], pString ) ) + return (AnalogCode_t)i; + } + + return ANALOG_CODE_INVALID; +} + +ButtonCode_t ButtonCode_ScanCodeToButtonCode( int lParam ) +{ + int nScanCode = ( lParam >> 16 ) & 0xFF; + if ( nScanCode > 127 ) + return KEY_NONE; + + ButtonCode_t result = s_pScanToButtonCode[nScanCode]; + + bool bIsExtended = ( lParam & ( 1 << 24 ) ) != 0; + if ( !bIsExtended ) + { + switch ( result ) + { + case KEY_HOME: + return KEY_PAD_7; + case KEY_UP: + return KEY_PAD_8; + case KEY_PAGEUP: + return KEY_PAD_9; + case KEY_LEFT: + return KEY_PAD_4; + case KEY_RIGHT: + return KEY_PAD_6; + case KEY_END: + return KEY_PAD_1; + case KEY_DOWN: + return KEY_PAD_2; + case KEY_PAGEDOWN: + return KEY_PAD_3; + case KEY_INSERT: + return KEY_PAD_0; + case KEY_DELETE: + return KEY_PAD_DECIMAL; + default: + break; + } + } + else + { + switch ( result ) + { + case KEY_ENTER: + return KEY_PAD_ENTER; + case KEY_LALT: + return KEY_RALT; + case KEY_LCONTROL: + return KEY_RCONTROL; + case KEY_SLASH: + return KEY_PAD_DIVIDE; + case KEY_CAPSLOCK: + return KEY_PAD_PLUS; + } + } + + return result; +} + + +//----------------------------------------------------------------------------- +// Update scan codes for foreign keyboards +//----------------------------------------------------------------------------- +void ButtonCode_UpdateScanCodeLayout( ) +{ + // reset the keyboard + memcpy( s_pScanToButtonCode, s_pScanToButtonCode_QWERTY, sizeof(s_pScanToButtonCode) ); + +#if !defined( _X360 ) && !defined( POSIX ) + // fix up keyboard layout for other languages + HKL currentKb = ::GetKeyboardLayout( 0 ); + HKL englishKb = ::LoadKeyboardLayout("00000409", 0); + + if (englishKb && englishKb != currentKb) + { + for ( int i = 0; i < ARRAYSIZE(s_pScanToButtonCode); i++ ) + { + // take the english/QWERTY + ButtonCode_t code = s_pScanToButtonCode_QWERTY[ i ]; + + // only remap printable keys + if ( code != KEY_NONE && code != KEY_BACKQUOTE && ( IsAlphaNumeric( code ) || IsPunctuation( code ) ) ) + { + // get it's virtual key based on the old layout + int vk = ::MapVirtualKeyEx( i, 1, englishKb ); + + // turn in into a scancode on the new layout + int newScanCode = ::MapVirtualKeyEx( vk, 0, currentKb ); + + // strip off any high bits + newScanCode &= 0x0000007F; + + // set in the new layout + s_pScanToButtonCode[newScanCode] = code; + } + } + } + + s_pScanToButtonCode[0] = KEY_NONE; +#endif +} + + +//----------------------------------------------------------------------------- +// Purpose: Updates the current keyboard layout +//----------------------------------------------------------------------------- +CON_COMMAND( key_updatelayout, "Updates game keyboard layout to current windows keyboard setting." ) +{ + ButtonCode_UpdateScanCodeLayout(); +} diff --git a/inputsystem/key_translation.h b/inputsystem/key_translation.h new file mode 100644 index 0000000..97ee7dd --- /dev/null +++ b/inputsystem/key_translation.h @@ -0,0 +1,38 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//===========================================================================// + +#ifndef KEY_TRANSLATION_H +#define KEY_TRANSLATION_H +#ifdef _WIN32 +#pragma once +#endif + +#include "inputsystem/ButtonCode.h" +#include "inputsystem/AnalogCode.h" + +// Call this to initialize the system +void ButtonCode_InitKeyTranslationTable(); + +// Convert from Windows scan codes to Button codes. +ButtonCode_t ButtonCode_ScanCodeToButtonCode( int lParam ); + +// Update scan codes for foreign keyboards +void ButtonCode_UpdateScanCodeLayout( ); + +// Convert from Windows virtual key codes to Button codes. +ButtonCode_t ButtonCode_VirtualKeyToButtonCode( int keyCode ); +int ButtonCode_ButtonCodeToVirtualKey( ButtonCode_t code ); + +ButtonCode_t ButtonCode_XKeyToButtonCode( int nPort, int keyCode ); + +// Convert back + forth between ButtonCode/AnalogCode + strings +const char *ButtonCode_ButtonCodeToString( ButtonCode_t code, bool bXController ); +const char *AnalogCode_AnalogCodeToString( AnalogCode_t code ); +ButtonCode_t ButtonCode_StringToButtonCode( const char *pString, bool bXController ); +AnalogCode_t AnalogCode_StringToAnalogCode( const char *pString ); +ButtonCode_t ButtonCode_SKeyToButtonCode( int nPort, int keyCode ); + +#endif // KEY_TRANSLATION_H diff --git a/inputsystem/novint.cpp b/inputsystem/novint.cpp new file mode 100644 index 0000000..32bd481 --- /dev/null +++ b/inputsystem/novint.cpp @@ -0,0 +1,138 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//===========================================================================// + +#include "inputsystem.h" +#include "key_translation.h" +#include "inputsystem/ButtonCode.h" +#include "inputsystem/AnalogCode.h" +#include "tier1/convar.h" + +typedef void*(*NovintGetIHaptics_t)(void); +typedef bool (*NovintAttemptHWND_t)(void *hWnd); +typedef void (*NovintDisableHWND_t)(void); +typedef void (*NovintPollDevices_t)(void); +typedef bool (*NovintButtonState_t)(int nDevice, + int &down, + int &pressed, + int &released); +typedef int (*NovintDeviceCount_t)(); +typedef int (*NovintButtonCount_t)(int nDevice); +typedef bool (__cdecl *NovintMouseModeCallback_t) (void); +typedef void (*NovintInputActive_t)(bool bGameInput); +typedef __int64 (*NovintGetDeviceID_t)(int nDevice); + +NovintGetIHaptics_t NovintGetIHaptics = NULL; +NovintAttemptHWND_t NovintAttemptHWND = NULL; +NovintDisableHWND_t NovintDisableHWND = NULL; +NovintPollDevices_t NovintPollDevices = NULL; +NovintButtonState_t NovintButtonState = NULL; +NovintDeviceCount_t NovintDeviceCount = NULL; +NovintButtonCount_t NovintButtonCount = NULL; +NovintInputActive_t NovintInputActive = NULL; +NovintGetDeviceID_t NovintGetDeviceID = NULL; + +static CInputSystem *pNovintInputSystem = 0; + +static bool bNovintPure = false; + +bool __cdecl NovintDevicesInputMode(void) +{ + if(pNovintInputSystem) + { + return !bNovintPure; + } + return false; +} + +void *CInputSystem::GetHapticsInterfaceAddress(void)const +{ + if( NovintGetIHaptics ) + return NovintGetIHaptics(); + + return NULL; +} + +void CInputSystem::AttachWindowToNovintDevices( void * hWnd ) +{ + if( NovintAttemptHWND ) + NovintAttemptHWND( hWnd ); +} + +void CInputSystem::DetachWindowFromNovintDevices(void) +{ + if( NovintDisableHWND ) + NovintDisableHWND(); +} + +void CInputSystem::InitializeNovintDevices() +{ + pNovintInputSystem = this; + // assume no novint devices + m_bNovintDevices = false; + m_nNovintDeviceCount = 0; + + if(!m_pNovintDLL) + return; + + NovintGetIHaptics = (NovintGetIHaptics_t)GetProcAddress( (HMODULE)m_pNovintDLL, "NovintGetIHaptics" ); + NovintAttemptHWND = (NovintAttemptHWND_t)GetProcAddress( (HMODULE)m_pNovintDLL, "NovintAttemptHWND" ); + NovintDisableHWND = (NovintDisableHWND_t)GetProcAddress( (HMODULE)m_pNovintDLL, "NovintDisableHWND" ); + NovintPollDevices = (NovintPollDevices_t)GetProcAddress( (HMODULE)m_pNovintDLL, "NovintPollDevices" ); + NovintButtonState = (NovintButtonState_t)GetProcAddress( (HMODULE)m_pNovintDLL, "NovintButtonState" ); + NovintDeviceCount = (NovintDeviceCount_t)GetProcAddress( (HMODULE)m_pNovintDLL, "NovintDeviceCount" ); + NovintButtonCount = (NovintButtonCount_t)GetProcAddress( (HMODULE)m_pNovintDLL, "NovintButtonCount" ); + NovintGetDeviceID = (NovintGetDeviceID_t)GetProcAddress( (HMODULE)m_pNovintDLL, "NovintGetDeviceID" ); + NovintInputActive = (NovintInputActive_t)GetProcAddress( (HMODULE)m_pNovintDLL, "NovintInputActive" ); + + + m_nNovintDeviceCount = NovintDeviceCount(); + + if( m_nNovintDeviceCount > 0 ) + m_bNovintDevices = true; +} + +void CInputSystem::PollNovintDevices() +{ + if( NovintPollDevices ) + NovintPollDevices(); + + for ( int i = 0; i < m_nNovintDeviceCount; i++ ) + { + UpdateNovintDeviceButtonState(i); + } +} + +void CInputSystem::UpdateNovintDeviceButtonState(int nDevice) +{ + int nDown; + int nPressed; + int nReleased; + if(NovintButtonState(nDevice, nDown, nPressed, nReleased)) + { + for ( int i = 0; i < 4; i++ ) + { + ButtonCode_t code = (ButtonCode_t)( NOVINT_FIRST + ( nDevice * 4 ) + i ); + + if( nPressed & (1 << i) ) + { + PostButtonPressedEvent(IE_ButtonPressed, m_nLastSampleTick, code, KEY_NONE); + } + + if( nReleased & (1 << i) ) + { + PostButtonReleasedEvent(IE_ButtonReleased, m_nLastSampleTick, code, KEY_NONE); + } + } + } +} + +void CInputSystem::SetNovintPure( bool bPure ) +{ + bNovintPure = bPure; + if(NovintInputActive) + NovintInputActive(bNovintPure); + +}
\ No newline at end of file diff --git a/inputsystem/posix_stubs.h b/inputsystem/posix_stubs.h new file mode 100644 index 0000000..776c230 --- /dev/null +++ b/inputsystem/posix_stubs.h @@ -0,0 +1,75 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// +// +//================================================================================================== + +#ifndef POSIX_WIN32STUBS_H +#define POSIX_WIN32STUBS_H +#ifdef _WIN32 +#pragma once +#endif + +#include "tier0/basetypes.h" +#include "tier0/platform.h" + +typedef int32 LRESULT; +typedef void* HWND; +typedef uint32 UINT; +typedef uintp WPARAM; +typedef uintp LPARAM; + +typedef uint8 BYTE; +typedef int16 SHORT; + +typedef void* WNDPROC; +typedef void* HANDLE; + +typedef char xKey_t; + +#define XUSER_MAX_COUNT 2 +#define XK_MAX_KEYS 5 + +typedef struct joyinfoex_tag +{ + DWORD dwSize; + DWORD dwFlags; + DWORD dwXpos; + DWORD dwYpos; + DWORD dwZpos; + DWORD dwRpos; + DWORD dwUpos; + DWORD dwVpos; + DWORD dwButtons; + DWORD dwButtonNumber; + DWORD dwPOV; + DWORD dwReserved1; + DWORD dwReserved2; +} JOYINFOEX, *LPJOYINFOEX; + + +typedef struct _XINPUT_GAMEPAD +{ + WORD wButtons; + BYTE bLeftTrigger; + BYTE bRightTrigger; + SHORT sThumbLX; + SHORT sThumbLY; + SHORT sThumbRX; + SHORT sThumbRY; +} XINPUT_GAMEPAD, *PXINPUT_GAMEPAD; + +typedef struct _XINPUT_STATE +{ + DWORD dwPacketNumber; + XINPUT_GAMEPAD Gamepad; +} XINPUT_STATE, *PXINPUT_STATE; + +typedef struct _XINPUT_VIBRATION +{ + WORD wLeftMotorSpeed; + WORD wRightMotorSpeed; +} XINPUT_VIBRATION, *PXINPUT_VIBRATION; + + +#endif // POSIX_WIN32STUBS_H diff --git a/inputsystem/steamcontroller.cpp b/inputsystem/steamcontroller.cpp new file mode 100644 index 0000000..007ccec --- /dev/null +++ b/inputsystem/steamcontroller.cpp @@ -0,0 +1,695 @@ +//=========== Copyright Valve Corporation, All rights reserved. ===============// +// +// Purpose: Native Steam Controller Interface +//=============================================================================// +#include "inputsystem.h" +#include "key_translation.h" +#include "filesystem.h" +#include "steam/isteamcontroller.h" +#include "math.h" + +#ifndef UINT64_MAX +const uint64 UINT64_MAX = 0xffffffffffffffff; +#endif + +#if !defined( NO_STEAM ) +#include "steam/steam_api.h" +#endif //NO_STEAM + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +ConVar sc_joystick_map( "sc_joystick_map", "1", FCVAR_ARCHIVE, "How to map the analog joystick deadzone and extents 0 = Scaled Cross, 1 = Concentric Mapping to Square." ); + +#define STEAMPAD_MAX_ANALOGSAMPLE_GYRO 32768 +#define STEAMPAD_MAX_ANALOGSAMPLE_TRIGGER 32768 +#define STEAMPAD_MAX_ANALOGSAMPLE_LEFT 32768 +#define STEAMPAD_MAX_ANALOGSAMPLE_RIGHT 32767 +#define STEAMPAD_MAX_ANALOGSAMPLE_DOWN 32768 +#define STEAMPAD_MAX_ANALOGSAMPLE_UP 32767 +#define STEAMPAD_MAX_ANALOGSAMPLE_MAX 32768 +#define STEAMPAD_ANALOG_SCALE_LEFT(x) ( ( float )STEAMPAD_MAX_ANALOGSAMPLE_LEFT/( float )( STEAMPAD_MAX_ANALOGSAMPLE_LEFT-(x) ) ) +#define STEAMPAD_ANALOG_SCALE_RIGHT(x) ( ( float )STEAMPAD_MAX_ANALOGSAMPLE_RIGHT/( float )( STEAMPAD_MAX_ANALOGSAMPLE_RIGHT-(x) ) ) +#define STEAMPAD_ANALOG_SCALE_DOWN(x) ( ( float )STEAMPAD_MAX_ANALOGSAMPLE_DOWN/( float )( STEAMPAD_MAX_ANALOGSAMPLE_DOWN-(x) ) ) +#define STEAMPAD_ANALOG_SCALE_UP(x) ( ( float )STEAMPAD_MAX_ANALOGSAMPLE_UP/( float )( STEAMPAD_MAX_ANALOGSAMPLE_UP-(x) ) ) + + +#define STEAMPAD_ANALOG_TRIGGER_THRESHOLD ( ( int )( STEAMPAD_MAX_ANALOGSAMPLE_TRIGGER * 0.5f ) ) +#define STEAMPAD_ANALOG_PAD_THRESHOLD ( ( int )( STEAMPAD_MAX_ANALOGSAMPLE_MAX * 0.25f ) ) +#define STEAMPAD_ANALOG_GYRO_THRESHOLD ( ( int ) ( STEAMPAD_MAX_ANALOGSAMPLE_GYRO * 0.3f ) ) +#define STEAMPAD_DIGITAL_PAD_THRESHOLD ( ( int )( STEAMPAD_MAX_ANALOGSAMPLE_MAX * 0.52f ) ) + +#define STEAMPAD_AXIS_REPEAT_INTERVAL_START 0.6f +#define STEAMPAD_AXIS_REPEAT_INTERVAL_END 0.05f +#define STEAMPAD_AXIS_REPEAT_CURVE_TIME 1.75f + +#define PAD_ANALOG_BUTTON_THRESHOLD ( ( int )( STEAMPAD_MAX_ANALOGSAMPLE_MAX * 0.285f ) ) +#define PAD_ANALOG_BUTTON_THRESHOLD_STRONG ( ( int )( STEAMPAD_MAX_ANALOGSAMPLE_MAX * 0.68f ) ) + +#define SQRT2 1.414213562 + +// key translation +typedef struct +{ + uint64 steampadinput; + ButtonCode_t steampadkey; +} steampadInputTosteampadKey_t; + +static const int s_nSteamPadDeadZoneTable[] = +{ + STEAMPAD_ANALOG_PAD_THRESHOLD, // LEFTPAD_AXIS_X + STEAMPAD_ANALOG_PAD_THRESHOLD, // LEFTPAD_AXIS_Y + STEAMPAD_ANALOG_PAD_THRESHOLD, // RIGHTPAD_AXIS_X + STEAMPAD_ANALOG_PAD_THRESHOLD, // RIGHTPAD_AXIS_Y + STEAMPAD_ANALOG_TRIGGER_THRESHOLD, //LEFT_TRIGGER_AXIS + STEAMPAD_ANALOG_TRIGGER_THRESHOLD, //RIGHT_TRIGGER_AXIS + STEAMPAD_ANALOG_GYRO_THRESHOLD, //GYRO_AXIS_PITCH + STEAMPAD_ANALOG_GYRO_THRESHOLD, //GYRO_AXIS_ROLL + STEAMPAD_ANALOG_GYRO_THRESHOLD, //GYRO_AXIS_YAW +}; + +//----------------------------------------------------------------------------- +// Purpose: Counts the number of active gamepads connected +//----------------------------------------------------------------------------- +uint32 CInputSystem::GetNumSteamControllersConnected() +{ + return m_unNumConnected; +} + +struct SDigitalMenuAction +{ + const char *strName; + ButtonCode_t buttonCode; + ControllerDigitalActionHandle_t handle; + bool bState[STEAM_CONTROLLER_MAX_COUNT]; + bool bAwaitingDebounce[STEAM_CONTROLLER_MAX_COUNT]; +}; + +static SDigitalMenuAction g_DigitalMenuActions[] = { + { "menu_left", STEAMCONTROLLER_DPAD_LEFT, 0, { false }, { false } }, + { "menu_right", STEAMCONTROLLER_DPAD_RIGHT, 0, { false }, { false } }, + { "menu_up", STEAMCONTROLLER_DPAD_UP, 0, { false }, { false } }, + { "menu_down", STEAMCONTROLLER_DPAD_DOWN, 0, { false }, { false } }, + { "menu_cancel", STEAMCONTROLLER_B, 0, { false }, { false } }, + { "menu_select", STEAMCONTROLLER_A, 0, { false }, { false } }, + { "resume_esc", STEAMCONTROLLER_START, 0, { false }, { false } }, + { "cl_trigger_first_notification", STEAMCONTROLLER_F1, 0, { false }, { false } }, // Command is in the in-game action set (use for hitting accept/ok on dialogs) + { "cl_decline_first_notification", STEAMCONTROLLER_F2, 0, { false }, { false } }, // Command is in the in-game action set (use for hitting cancel/decline on dialogs) + { "menu_toggle_function", STEAMCONTROLLER_Y, 0, { false }, { false }, }, // Command is in the in-game HUD action set + { "menu_alt_function", STEAMCONTROLLER_X, 0, { false }, { false } }, // Command is in the in-game HUD action set + { "cancelselect", STEAMCONTROLLER_START, 0, { false }, { false } }, // Command is in the in-game action set + { "toggleready", STEAMCONTROLLER_F4, 0, { false }, { false } }, // Command is in the in-game action set +}; + +struct SAnalogAction +{ + const char *strName; + JoystickAxis_t joystickAxisX, joystickAxisY; + ControllerAnalogActionHandle_t handle; +}; + +struct SGameActionSet +{ + const char *strName; + GameActionSet_t eGameActionSet; + ControllerActionSetHandle_t handle; +}; +static SGameActionSet g_GameActionSets[] = { + { "MenuControls", GAME_ACTION_SET_MENUCONTROLS, 0 }, + { "FPSControls", GAME_ACTION_SET_FPSCONTROLS, 0 }, + { "InGameHUDControls", GAME_ACTION_SET_IN_GAME_HUD, 0 }, + { "SpectatorControls", GAME_ACTION_SET_SPECTATOR, 0 }, +}; + + +// Table that maps a physical steam controller origin to the corresponding character in our icon font. +// If the EControllerActionOrigin enum or the font changes, this table must be changed to match. +static const wchar_t* g_MapSteamControllerOriginToIconFont[] = { + L"", // k_EControllerActionOrigin_None + L"A", // k_EControllerActionOrigin_A + L"B", // k_EControllerActionOrigin_B + L"X", // k_EControllerActionOrigin_X + L"Y", // k_EControllerActionOrigin_Y + L"2", // k_EControllerActionOrigin_LeftBumper + L"3", // k_EControllerActionOrigin_RightBumper + L"(", // k_EControllerActionOrigin_LeftGrip + L")", // k_EControllerActionOrigin_RightGrip + L"5", // k_EControllerActionOrigin_Start + L"4", // k_EControllerActionOrigin_Back + L"q", // k_EControllerActionOrigin_LeftPad_Touch + L"w", // k_EControllerActionOrigin_LeftPad_Swipe + L"e", // k_EControllerActionOrigin_LeftPad_Click + L"a", // k_EControllerActionOrigin_LeftPad_DPadNorth + L"s", // k_EControllerActionOrigin_LeftPad_DPadSouth + L"d", // k_EControllerActionOrigin_LeftPad_DPadWest + L"f", // k_EControllerActionOrigin_LeftPad_DPadEast + L"y", // k_EControllerActionOrigin_RightPad_Touch + L"u", // k_EControllerActionOrigin_RightPad_Swipe + L"i", // k_EControllerActionOrigin_RightPad_Click + L"h", // k_EControllerActionOrigin_RightPad_DPadNorth + L"j", // k_EControllerActionOrigin_RightPad_DPadSouth + L"k", // k_EControllerActionOrigin_RightPad_DPadWest + L"l", // k_EControllerActionOrigin_RightPad_DEast + L"z", // k_EControllerActionOrigin_LeftTrigger_Pull + L"x", // k_EControllerActionOrigin_LeftTrigger_Click + L"n", // k_EControllerActionOrigin_RightTrigger_Pull + L"m", // k_EControllerActionOrigin_RightTrigger_Click + L"C", // k_EControllerActionOrigin_LeftStick_Move + L"V", // k_EControllerActionOrigin_LeftStick_Click + L"7", // k_EControllerActionOrigin_LeftStick_DPadNorth + L"8", // k_EControllerActionOrigin_LeftStick_DPadSouth + L"9", // k_EControllerActionOrigin_LeftStick_DPadWest + L"0", // k_EControllerActionOrigin_LeftStick_DPadEast + L"6", // k_EControllerActionOrigin_Gyro_Move + L"6", // k_EControllerActionOrigin_Gyro_Pitch + L"6", // k_EControllerActionOrigin_Gyro_Yaw + L"6", // k_EControllerActionOrigin_Gyro_Roll +}; + +// Table that maps a physical steam controller origin to a short description string for user display (only use this +// if it's impractical to use the icon font). +// If the EControllerActionOrigin enum changes, this table must be changed to match. +static const wchar_t* g_MapSteamControllerOriginToDescription[] = { + L"", // k_EControllerActionOrigin_None + L"A", // k_EControllerActionOrigin_A + L"B", // k_EControllerActionOrigin_B + L"X", // k_EControllerActionOrigin_X + L"Y", // k_EControllerActionOrigin_Y + L"LB", // k_EControllerActionOrigin_LeftBumper + L"RB", // k_EControllerActionOrigin_RightBumper + L"LG", // k_EControllerActionOrigin_LeftGrip + L"RG", // k_EControllerActionOrigin_RightGrip + L"START", // k_EControllerActionOrigin_Start + L"BACK", // k_EControllerActionOrigin_Back + L"LPTOUCH", // k_EControllerActionOrigin_LeftPad_Touch + L"LPSWIPE", // k_EControllerActionOrigin_LeftPad_Swipe + L"LPCLICK", // k_EControllerActionOrigin_LeftPad_Click + L"LPUP", // k_EControllerActionOrigin_LeftPad_DPadNorth + L"LPDOWN", // k_EControllerActionOrigin_LeftPad_DPadSouth + L"LPLEFT", // k_EControllerActionOrigin_LeftPad_DPadWest + L"LPRIGHT", // k_EControllerActionOrigin_LeftPad_DPadEast + L"RPTOUCH", // k_EControllerActionOrigin_RightPad_Touch + L"RPSWIPE", // k_EControllerActionOrigin_RightPad_Swipe + L"RPCLICK", // k_EControllerActionOrigin_RightPad_Click + L"RPUP", // k_EControllerActionOrigin_RightPad_DPadNorth + L"RPDOWN", // k_EControllerActionOrigin_RightPad_DPadSouth + L"RPLEFT", // k_EControllerActionOrigin_RightPad_DPadWest + L"RPRIGHT", // k_EControllerActionOrigin_RightPad_DEast + L"LT", // k_EControllerActionOrigin_LeftTrigger_Pull + L"LT", // k_EControllerActionOrigin_LeftTrigger_Click + L"LT", // k_EControllerActionOrigin_RightTrigger_Pull + L"LT", // k_EControllerActionOrigin_RightTrigger_Click + L"LS", // k_EControllerActionOrigin_LeftStick_Move + L"LSCLICK", // k_EControllerActionOrigin_LeftStick_Click + L"LSUP", // k_EControllerActionOrigin_LeftStick_DPadNorth + L"LSDOWN", // k_EControllerActionOrigin_LeftStick_DPadSouth + L"LSLEFT", // k_EControllerActionOrigin_LeftStick_DPadWest + L"LSRIGHT", // k_EControllerActionOrigin_LeftStick_DPadEast + L"GYRO", // k_EControllerActionOrigin_Gyro_Move + L"GYRO", // k_EControllerActionOrigin_Gyro_Pitch + L"GYRO", // k_EControllerActionOrigin_Gyro_Yaw + L"GYRO", // k_EControllerActionOrigin_Gyro_Roll +}; + + +bool CInputSystem::InitializeSteamControllerActionSets() +{ + auto psteamcontroller = g_pInputSystem->SteamControllerInterface(); + if ( !psteamcontroller ) + { + return false; + } + + bool bGotHandles = true; + + for ( int i = 0; i != ARRAYSIZE( g_DigitalMenuActions ); ++i ) + { + g_DigitalMenuActions[i].handle = psteamcontroller->GetDigitalActionHandle( g_DigitalMenuActions[i].strName ); + bGotHandles = bGotHandles && ( g_DigitalMenuActions[i].handle != 0 ); + + // Init the button state array + for( int j = 0; j < STEAM_CONTROLLER_MAX_COUNT; j++ ) + { + g_DigitalMenuActions[i].bState[j] = false; + } + } + + for ( int i = 0; i != ARRAYSIZE( g_GameActionSets ); ++i ) + { + g_GameActionSets[i].handle = psteamcontroller->GetActionSetHandle( g_GameActionSets[i].strName ); + bGotHandles = bGotHandles && g_GameActionSets[i].handle; + } + + return bGotHandles; +} + +ConVar sc_debug_sets( "sc_debug_sets", "0", FCVAR_ARCHIVE, "Debugging" ); + +void CInputSystem::PollSteamControllers( void ) +{ + // Make sure we've got the appropriate connections to Steam + auto steamcontroller = SteamControllerInterface(); + if ( !steamcontroller ) + { + return; + } + + steamcontroller->RunFrame(); + + uint64 nControllerHandles[STEAM_CONTROLLER_MAX_COUNT]; + m_unNumConnected = steamcontroller->GetConnectedControllers( nControllerHandles ); + + if ( m_unNumConnected > 0 ) + { + if ( !m_bSteamControllerActionsInitialized ) + { + // Retry initialization of controller actions if we didn't acquire them all before for some reason. + m_bSteamControllerActionsInitialized = m_bSteamController && InitializeSteamControllerActionSets(); + g_pInputSystem->ActivateSteamControllerActionSet( GAME_ACTION_SET_MENUCONTROLS ); + } + + if ( m_bSteamControllerActionsInitialized ) + { + // If we successfully initialized all the actions, and we have a connected controller, then we're considered "active". + m_bSteamControllerActive = true; + + // For each digital action + for ( int i = 0; i != ARRAYSIZE( g_DigitalMenuActions ); ++i ) + { + SDigitalMenuAction& action = g_DigitalMenuActions[i]; + + // and for each controller + for ( uint64 j = 0; j < m_unNumConnected; ++j ) + { + + // Get the action's current state + ControllerDigitalActionData_t data = steamcontroller->GetDigitalActionData( nControllerHandles[j], action.handle ); + + // We only care if the action is active + if ( data.bActive ) + { + action.bAwaitingDebounce[j] = action.bAwaitingDebounce[j] && data.bState; + + // If the action's state has changed + if ( data.bState != action.bState[j] ) + { + action.bState[j] = data.bState; + + // Press the key for the correct controller + ButtonCode_t buttonCode = ButtonCodeToJoystickButtonCode( action.buttonCode, j ); + + if ( action.bState[j] ) + { + if ( !action.bAwaitingDebounce[j] ) + { + PostButtonPressedEvent( IE_ButtonPressed, m_nLastSampleTick, buttonCode, buttonCode ); + } + } + else + { + PostButtonReleasedEvent( IE_ButtonReleased, m_nLastSampleTick, buttonCode, buttonCode ); + } + } + } + } + } + } + } + else + { + // If no controllers are connected, unflag the active state. + m_bSteamControllerActive = false; + } +} + +bool CInputSystem::GetRadialMenuStickValues( int nSlot, float &fX, float &fY ) +{ + fX = m_pRadialMenuStickVal[nSlot][0]; + fY = m_pRadialMenuStickVal[nSlot][1]; + + return true; +} + +bool CInputSystem::IsSteamControllerActive( void ) +{ + return m_bSteamControllerActive; +} + +bool CInputSystem::InitializeSteamControllers() +{ + m_flLastSteamControllerInput = -FLT_MAX; + auto steamcontroller = SteamControllerInterface(); + if ( steamcontroller ) + { + if ( !steamcontroller->Init() ) + { + return false; + } + } + else + { + return false; + } + + for( int i=0; i<STEAM_CONTROLLER_MAX_COUNT; i++ ) + { + m_pRadialMenuStickVal[i][0] = 0.0f; + m_pRadialMenuStickVal[i][1] = 0.0f; + } + + // We have to account for other joysticks prior to adding steam controllers + // So we get the baseline number here when first initializing + m_nJoystickBaseline = m_nJoystickCount; + if ( steamcontroller ) + { + uint64 nControllerHandles[STEAM_CONTROLLER_MAX_COUNT]; + m_unNumConnected = steamcontroller->GetConnectedControllers( nControllerHandles ); + steamcontroller->RunFrame(); + + if ( m_unNumConnected > 0 ) + { + for ( uint32 i = 0; i < m_unNumConnected; i++ ) + { + if ( m_Device[i].m_nJoystickIndex == INVALID_USER_ID ) + { + int nJoystickIndex = i; + m_Device[i].m_nJoystickIndex = nJoystickIndex; + m_Device[i].m_nHardwareIndex = i; + } + m_nControllerType[m_Device[i].m_nJoystickIndex] = INPUT_TYPE_STEAMCONTROLLER; + } + } + + return true; + } + + return false; +} + +ControllerActionSetHandle_t CInputSystem::GetActionSetHandle( GameActionSet_t eActionSet ) +{ + return g_GameActionSets[eActionSet].handle; +} + +ControllerActionSetHandle_t CInputSystem::GetActionSetHandle( const char* szActionSet ) +{ + for ( int i = 0; i != ARRAYSIZE( g_GameActionSets ); ++i ) + { + if ( !Q_strcmp( szActionSet, g_GameActionSets[i].strName ) ) + { + return g_GameActionSets[i].handle; + } + } + + return 0; +} + + +void CInputSystem::ActivateSteamControllerActionSetForSlot( uint64 nSlot, GameActionSet_t eActionSet ) +{ + auto steamcontroller = SteamControllerInterface(); + bool bChangedActionSet = false; + + if ( steamcontroller ) + { + if ( nSlot == STEAM_CONTROLLER_HANDLE_ALL_CONTROLLERS ) + { + for ( int i = 0; i < STEAM_CONTROLLER_MAX_COUNT; i++ ) + { + if ( m_currentActionSet[i] != eActionSet ) + { + bChangedActionSet = true; + m_currentActionSet[i] = eActionSet; + } + } + + steamcontroller->ActivateActionSet( STEAM_CONTROLLER_HANDLE_ALL_CONTROLLERS, g_GameActionSets[eActionSet].handle ); + } + else + { + uint64 nControllerHandles[STEAM_CONTROLLER_MAX_COUNT]; + int unNumConnected = steamcontroller->GetConnectedControllers( nControllerHandles ); + + if( nSlot < unNumConnected ) + { + if ( m_currentActionSet[nSlot] != eActionSet ) + { + bChangedActionSet = true; + m_currentActionSet[nSlot] = eActionSet; + } + + steamcontroller->ActivateActionSet( nControllerHandles[nSlot], g_GameActionSets[eActionSet].handle ); + } + } + } + + if ( bChangedActionSet ) + { + // If we changed action set, then flag everything for a debounce (meaning we demand to see an unpressed state before we'll register a pressed one) + for ( int i = 0; i < STEAM_CONTROLLER_MAX_COUNT; i++ ) + { + for ( int j = 0; j != ARRAYSIZE( g_DigitalMenuActions ); ++j ) + { + g_DigitalMenuActions[j].bAwaitingDebounce[i] = true; + } + } + } +} + +const int CInputSystem::GetSteamPadDeadZone( ESteamPadAxis axis ) +{ + int nDeadzone = s_nSteamPadDeadZoneTable[ axis ]; + + // Do modifications if required here? + + return nDeadzone; +} + +//----------------------------------------------------------------------------- +// Purpose: Processes data for controller +//----------------------------------------------------------------------------- +void CInputSystem::ReadSteamController( int iIndex ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: Pulse haptic feedback +//----------------------------------------------------------------------------- +/* void CInputSystem::PulseHapticOnSteamController( uint32 nControllerIndex, ESteamControllerPad ePad, unsigned short durationMicroSec ) +{ + auto steamcontroller = SteamControllerInterface(); + if ( steamcontroller ) + { + steamcontroller->TriggerHapticPulse( nControllerIndex, ePad, durationMicroSec ); + } +}*/ + +//----------------------------------------------------------------------------- +// Purpose: Returns the controller State for a particular joystick slot +//----------------------------------------------------------------------------- +bool CInputSystem::GetControllerStateForSlot( int nSlot ) +{ + return false; +} + +int CInputSystem::GetSteamControllerIndexForSlot( int nSlot ) +{ + for ( int i = 0; i < Q_ARRAYSIZE( m_Device ); i++ ) + { + if ( m_Device[i].active && (int)m_Device[i].m_nJoystickIndex == nSlot ) + { + return m_Device[i].m_nHardwareIndex; + } + } + return -1; +} + +//----------------------------------------------------------------------------- +// Purpose: Post events, ignoring key repeats +//----------------------------------------------------------------------------- +void CInputSystem::PostKeyEvent( int iIndex, sKey_t sKey, int nSample ) +{ + // Rework of xbox code here : + + AnalogCode_t code = ANALOG_CODE_LAST; + float value = 0.f; + //int nMsgSlot = iIndex; + int nMsgSlot = m_Device[iIndex].m_nJoystickIndex; + int nSampleThreshold = 1; + + // Look for changes on the analog axes + switch( sKey ) + { + case SK_BUTTON_LPAD_LEFT: + case SK_BUTTON_LPAD_RIGHT: + { + code = (AnalogCode_t)JOYSTICK_AXIS( nMsgSlot, JOY_AXIS_X ); + value = ( sKey == SK_BUTTON_LPAD_LEFT ) ? -nSample : nSample; + // Kind of a hack to horizontal values for menu selection items in Portal 2 + // The additional 5k helps accidental horizontals in menus. + nSampleThreshold = ( int )( STEAMPAD_DIGITAL_PAD_THRESHOLD ) + 5000; + } + break; + + case SK_BUTTON_LPAD_UP: + case SK_BUTTON_LPAD_DOWN: + { + code = (AnalogCode_t)JOYSTICK_AXIS( nMsgSlot, JOY_AXIS_Y ); + value = ( sKey == SK_BUTTON_LPAD_UP ) ? -nSample : nSample; + nSampleThreshold = ( int )( STEAMPAD_DIGITAL_PAD_THRESHOLD ); + } + break; + + case SK_BUTTON_RPAD_LEFT: + case SK_BUTTON_RPAD_RIGHT: + { + code = (AnalogCode_t)JOYSTICK_AXIS( nMsgSlot, JOY_AXIS_U ); + value = ( sKey == SK_BUTTON_RPAD_LEFT ) ? -nSample : nSample; + // Kind of a hack to horizontal values for menu selection items in Portal 2 + // The additional 5k helps accidental horizontals in menus. + nSampleThreshold = ( int )( STEAMPAD_DIGITAL_PAD_THRESHOLD ) + 5000;; + } + break; + + case SK_BUTTON_RPAD_UP: + case SK_BUTTON_RPAD_DOWN: + { + code = (AnalogCode_t)JOYSTICK_AXIS( nMsgSlot, JOY_AXIS_R ); + value = ( sKey == SK_BUTTON_RPAD_UP ) ? -nSample : nSample; + nSampleThreshold = ( int )( STEAMPAD_DIGITAL_PAD_THRESHOLD ); + } + break; + } + + // Store the analog event + if ( ANALOG_CODE_LAST != code ) + { + InputState_t &state = m_InputState[ m_bIsPolling ]; + state.m_pAnalogDelta[ code ] = ( int )( value - state.m_pAnalogValue[ code ] ); + state.m_pAnalogValue[ code ] = ( int )value; + if ( state.m_pAnalogDelta[ code ] != 0 ) + { + PostEvent( IE_AnalogValueChanged, m_nLastSampleTick, code, ( int )value, state.m_pAnalogDelta[ code ] ); + } + } + + // store the key + m_Device[iIndex].m_appSKeys[sKey].sample = nSample; + if ( nSample > nSampleThreshold ) + { + m_Device[iIndex].m_appSKeys[sKey].repeats++; + } + else + { + m_Device[iIndex].m_appSKeys[sKey].repeats = 0; + nSample = 0; + } + + if ( m_Device[iIndex].m_appSKeys[sKey].repeats > 1 ) + { + // application cannot handle streaming keys + // first keypress is the only edge trigger + return; + } + + // package the key + ButtonCode_t buttonCode = SKeyToButtonCode( nMsgSlot, sKey ); + if ( nSample ) + { + PostButtonPressedEvent( IE_ButtonPressed, m_nLastSampleTick, buttonCode, buttonCode ); + } + else + { + PostButtonReleasedEvent( IE_ButtonReleased, m_nLastSampleTick, buttonCode, buttonCode ); + } +} + +// Gets the action origin (i.e. which physical input) maps to the given virtual button for the given action set +EControllerActionOrigin CInputSystem::GetSteamControllerActionOrigin( const char* action, GameActionSet_t action_set ) +{ + auto pSC = SteamControllerInterface(); + if ( pSC ) + { + ControllerHandle_t hConnected[STEAM_CONTROLLER_MAX_COUNT]; + auto nConnected = pSC->GetConnectedControllers( hConnected ); + if ( nConnected == 0 ) + { + return k_EControllerActionOrigin_None; + } + + SGameActionSet* pActionSet = nullptr; + for ( int i = 0; i < ARRAYSIZE( g_GameActionSets ); i++ ) + { + if ( g_GameActionSets[i].eGameActionSet == action_set ) + { + pActionSet = &g_GameActionSets[i]; + break; + } + } + + if ( pActionSet ) + { + auto actionHandle = pSC->GetDigitalActionHandle( action ); + EControllerActionOrigin origins[STEAM_CONTROLLER_MAX_ORIGINS]; + int nOrigins = pSC->GetDigitalActionOrigins( hConnected[0], pActionSet->handle, actionHandle, origins ); + if ( nOrigins > 0 ) + { + return origins[0]; + } + } + } + + return k_EControllerActionOrigin_None; +} + +// Gets the action origin (i.e. which physical input) maps to the given virtual button for the given action set +EControllerActionOrigin CInputSystem::GetSteamControllerActionOrigin( const char* action, ControllerActionSetHandle_t action_set_handle ) +{ + auto pSC = SteamControllerInterface(); + if ( pSC && action_set_handle ) + { + ControllerHandle_t hConnected[STEAM_CONTROLLER_MAX_COUNT]; + auto nConnected = pSC->GetConnectedControllers( hConnected ); + if ( nConnected == 0 ) + { + return k_EControllerActionOrigin_None; + } + + auto actionHandle = pSC->GetDigitalActionHandle( action ); + EControllerActionOrigin origins[STEAM_CONTROLLER_MAX_ORIGINS]; + int nOrigins = pSC->GetDigitalActionOrigins( hConnected[0], action_set_handle, actionHandle, origins ); + if ( nOrigins > 0 ) + { + return origins[0]; + } + } + + return k_EControllerActionOrigin_None; +} + +// Maps a Steam Controller action origin to a string (consisting of a single character) in our SC icon font +const wchar_t* CInputSystem::GetSteamControllerFontCharacterForActionOrigin( EControllerActionOrigin origin ) +{ + if ( origin >= 0 && origin < ARRAYSIZE( g_MapSteamControllerOriginToIconFont ) ) + { + return g_MapSteamControllerOriginToIconFont[origin]; + } + else + { + return L""; + } +} + +// Maps a Steam Controller action origin to a short text string (e.g. "X", "LB", "LDOWN") describing the control. +// Prefer to actually use the icon font wherever possible. +const wchar_t* CInputSystem::GetSteamControllerDescriptionForActionOrigin( EControllerActionOrigin origin ) +{ + if ( origin >= 0 && origin < ARRAYSIZE( g_MapSteamControllerOriginToDescription ) ) + { + return g_MapSteamControllerOriginToDescription[origin]; + } + else + { + return L""; + } +} |