You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1575 lines
46 KiB
1575 lines
46 KiB
//========= 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_bTouchInitialized = 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 ); |
|
|
|
#ifdef USE_SDL |
|
if( !m_bConsoleTextMode ) |
|
InitializeTouch(); |
|
#endif |
|
|
|
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 ) |
|
{ |
|
#ifdef USE_SDL |
|
SetXDeviceRumble( fLeftMotor, fRightMotor, userId ); |
|
#endif |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// 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 |
|
} |
|
|
|
bool CInputSystem::GetTouchAccumulators( InputEventType_t &event, int &fingerId, int& accumX, int& accumY ) |
|
{ |
|
event = m_touchAccumEvent; |
|
fingerId = m_touchAccumFingerId; |
|
accumX = m_touchAccumX; |
|
accumY = m_touchAccumY; |
|
|
|
return m_bJoystickInitialized; |
|
} |
|
|
|
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(); |
|
} |
|
} |
|
|
|
void CInputSystem::StartTextInput() |
|
{ |
|
#ifdef USE_SDL |
|
SDL_StartTextInput(); |
|
#endif |
|
}
|
|
|