//===== Copyright 1996-2005, 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 "filesystem.h" #include "platforminputdevice.h" #ifdef _PS3 #include #endif #ifdef PLATFORM_OSX #include #include "materialsystem/imaterialsystem.h" #endif #if defined( INCLUDE_SCALEFORM ) #include "scaleformui/scaleformui.h" #endif // NOTE: This has to be the last file included! #include "tier0/memdbgon.h" #if defined( INCLUDE_SCALEFORM ) IScaleformUI* g_pScaleformUI = NULL; #endif #if defined( USE_SDL ) #include "SDL.h" static void initKeymap(void); #endif ConVar joy_xcontroller_found( "joy_xcontroller_found", "1", FCVAR_NONE, "Automatically set to 1 if an xcontroller has been detected." ); ConVar joy_deadzone_mode( "joy_deadzone_mode", "0", FCVAR_NONE, "0 => Cross-shaped deadzone (default), 1 => Square deadzone." ); ConVar pc_fake_controller( "pc_fake_controller", "0", FCVAR_DEVELOPMENTONLY, "" ); ConVar dev_force_selected_device( "dev_force_selected_device", "0", FCVAR_DEVELOPMENTONLY, "" ); //----------------------------------------------------------------------------- // Singleton instance //----------------------------------------------------------------------------- static CInputSystem g_InputSystem; EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CInputSystem, IInputSystem, INPUTSYSTEM_INTERFACE_VERSION, g_InputSystem ); #ifdef _PS3 IVJobs * g_pVJobs = NULL; #endif #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 extern int countBits( uint32 iValue ); //----------------------------------------------------------------------------- // 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_bIsInGame = false; m_JoysticksEnabled.ClearAllFlags(); m_nJoystickCount = 0; m_nJoystickBaseline = 0; m_nPollCount = 0; m_uiMouseWheel = 0; m_bXController = false; m_bRawInputSupported = false; m_bIMEComposing = false; m_nUIEventClientCount = 0; m_hLastIMEHWnd = NULL; m_hCurrentCaptureWnd = PLAT_WINDOW_INVALID; m_bCursorVisible = true; m_hCursor = INPUT_CURSOR_HANDLE_INVALID; m_bMotionControllerActive = false; m_qMotionControllerOrientation.Init(); m_fMotionControllerPosX = 0.0f; m_fMotionControllerPosY = 0.0f; m_nMotionControllerStatus = INPUT_DEVICE_MC_STATE_CAMERA_NOT_CONNECTED; m_nMotionControllerStatusFlags = 0; // This is B.S., must be a compile-time assert with valid expression: // Assert( (MAX_JOYSTICKS + 7) >> 3 << sizeof(unsigned short) ); #if !defined( _CERT ) && !defined(LINUX) V_memset( m_press_x360_buttons, 0, sizeof( m_press_x360_buttons ) ); #endif #ifdef _PS3 m_pPS3CellNoPadDataHook = NULL; m_pPS3CellPadDataHook = NULL; m_PS3KeyboardConnected = false; m_PS3MouseConnected = false; #endif m_pXInputDLL = NULL; m_pRawInputDLL = NULL; for ( int i = 0; i < Q_ARRAYSIZE(m_nControllerType); i++) { m_nControllerType[i] = INPUT_TYPE_GENERIC_JOYSTICK; } InitPlatfromInputDeviceInfo(); } #if defined( USE_SDL ) void CInputSystem::DisableHardwareCursor( ) { m_pLauncherMgr->SetMouseVisible(false); } void CInputSystem::EnableHardwareCursor( ) { m_pLauncherMgr->SetMouseVisible(true); } #endif CInputSystem::~CInputSystem() { if ( m_pXInputDLL ) { Sys_UnloadModule( m_pXInputDLL ); m_pXInputDLL = NULL; } if ( m_pRawInputDLL ) { Sys_UnloadModule( m_pRawInputDLL ); m_pRawInputDLL = NULL; } } //----------------------------------------------------------------------------- // Initialization //----------------------------------------------------------------------------- InitReturnVal_t CInputSystem::Init() { InitReturnVal_t nRetVal = BaseClass::Init(); if ( nRetVal != INIT_OK ) return nRetVal; m_StartupTimeTick = Plat_MSTime(); #if !defined( PLATFORM_POSIX ) if ( IsPC() ) { m_uiMouseWheel = RegisterWindowMessage( "MSWHEEL_ROLLMSG" ); } m_hEvent = CreateEvent( NULL, FALSE, FALSE, NULL ); if ( !m_hEvent ) return INIT_FAILED; #endif ButtonCode_InitKeyTranslationTable(); ButtonCode_UpdateScanCodeLayout(); joy_xcontroller_found.SetValue( 0 ); #if !defined( _GAMECONSOLE ) if ( IsPC() ) { #if !defined( PLATFORM_POSIX ) m_pXInputDLL = Sys_LoadModule( "XInput1_3.dll" ); if ( m_pXInputDLL ) { InitializeXDevices(); } #endif if ( !m_nJoystickCount ) { // Didn't find any XControllers. See if we can find other joysticks. InitializeJoysticks(); } else { m_bXController = true; } if ( m_bXController ) joy_xcontroller_found.SetValue( 1 ); } #elif defined( _GAMECONSOLE ) if ( IsGameConsole() ) { InitializeXDevices(); m_bXController = true; joy_xcontroller_found.SetValue( 1 ); } #endif InitCursors(); m_bRawInputSupported = false; #if defined( LINUX ) m_bRawInputSupported = true; #elif defined( WIN32 ) && !defined( _X360 ) // Check if this version of windows supports raw mouse input (later than win2k) 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 #if defined( USE_SDL ) initKeymap(); #endif m_unNumSteamControllerConnected = 0; m_bSteamController = InitializeSteamControllers(); return INIT_OK; } bool CInputSystem::Connect( CreateInterfaceFn factory ) { if ( !BaseClass::Connect( factory ) ) return false; #if defined( INCLUDE_SCALEFORM ) g_pScaleformUI = (IScaleformUI*)factory( SCALEFORMUI_INTERFACE_VERSION, NULL ); #endif #ifdef _PS3 g_pVJobs = ( IVJobs* )factory( VJOBS_INTERFACE_VERSION, NULL ); #endif #if defined( USE_SDL ) m_pLauncherMgr = (ILauncherMgr *)factory( SDLMGR_INTERFACE_VERSION, NULL ); #elif defined( OSX ) m_pLauncherMgr = (ILauncherMgr *)factory( COCOAMGR_INTERFACE_VERSION, NULL ); #endif return true; } #ifdef _PS3 extern void PS3_XInputShutdown(); #endif #ifdef _PS3 void CInputSystem::SetPS3CellPadDataHook( BCellPadDataHook_t hookFunc ) { m_pPS3CellPadDataHook = hookFunc; } void CInputSystem::SetPS3CellPadNoDataHook( BCellPadNoDataHook_t hookFunc ) { m_pPS3CellNoPadDataHook = hookFunc; } #endif //----------------------------------------------------------------------------- // Shutdown //----------------------------------------------------------------------------- void CInputSystem::Shutdown() { #if !defined( PLATFORM_POSIX ) if ( m_hEvent != NULL ) { CloseHandle( m_hEvent ); m_hEvent = NULL; } #endif ShutdownCursors(); BaseClass::Shutdown(); #ifdef _PS3 PS3_XInputShutdown(); #endif } //----------------------------------------------------------------------------- // Sleep until input //----------------------------------------------------------------------------- void CInputSystem::SleepUntilInput( int nMaxSleepTimeMS ) { #if defined( USE_SDL ) || defined( OSX ) m_pLauncherMgr->WaitUntilUserInput( nMaxSleepTimeMS ); #elif defined( _WIN32 ) if ( nMaxSleepTimeMS < 0 ) { nMaxSleepTimeMS = INFINITE; } MsgWaitForMultipleObjects( 1, &m_hEvent, FALSE, nMaxSleepTimeMS, QS_ALLEVENTS ); #elif defined( _PS3 ) // no-op #else #warning "need a SleepUntilInput impl" #endif } //----------------------------------------------------------------------------- // Tells the input system to generate UI-related events, defined //----------------------------------------------------------------------------- void CInputSystem::AddUIEventListener() { ++m_nUIEventClientCount; } void CInputSystem::RemoveUIEventListener() { --m_nUIEventClientCount; } //----------------------------------------------------------------------------- // Returns the currently attached window //----------------------------------------------------------------------------- PlatWindow_t CInputSystem::GetAttachedWindow() const { return (PlatWindow_t)m_hAttachedHWnd; } //----------------------------------------------------------------------------- // Callback to call into our class //----------------------------------------------------------------------------- #if !defined( PLATFORM_POSIX ) 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 ( USE_SDL ) #elif defined( PLATFORM_OSX ) #elif defined( PLATFORM_WINDOWS ) #if defined( PLATFORM_X360 ) //GetWindowLongPtrW/SetWindowLongPtrW don't exist on the 360 m_ChainedWndProc = (WNDPROC)GetWindowLongPtr( (HWND)hWnd, GWLP_WNDPROC ); SetWindowLongPtr( (HWND)hWnd, GWLP_WNDPROC, (LONG_PTR)InputSystemWindowProc ); #else m_ChainedWndProc = (WNDPROC)GetWindowLongPtrW( (HWND)hWnd, GWLP_WNDPROC ); SetWindowLongPtrW( (HWND)hWnd, GWLP_WNDPROC, (LONG_PTR)InputSystemWindowProc ); // 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 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 = (HWND)hWnd; // g_InputSystem.m_hAttachedHWnd; // GetHhWnd; ::RegisterRawInputDevices(Rid, ARRAYSIZE(Rid), sizeof(Rid[0])); #endif #elif defined( _PS3 ) #else #error #endif m_hAttachedHWnd = (HWND)hWnd; #if defined( WIN32 ) && !defined( _X360 ) // 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( true ); } //----------------------------------------------------------------------------- // Unhooks input listening from a window //----------------------------------------------------------------------------- void CInputSystem::DetachFromWindow( ) { if ( !m_hAttachedHWnd ) return; ResetInputState(); #if !defined( PLATFORM_POSIX ) if ( m_ChainedWndProc ) { SetWindowLongPtrW( m_hAttachedHWnd, GWLP_WNDPROC, (LONG_PTR)m_ChainedWndProc ); m_ChainedWndProc = 0; } #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( bool bPurgeState ) { 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) ); if ( bPurgeState ) { state.m_Events.Purge(); state.m_bDirty = false; } } memset( m_appXKeys, 0, XUSER_MAX_COUNT * XK_MAX_KEYS * sizeof(appKey_t) ); m_mouseRawAccumX = m_mouseRawAccumY = 0; m_flLastControllerPollTime = 0; } //----------------------------------------------------------------------------- // Resets the input state //----------------------------------------------------------------------------- void CInputSystem::ResetInputState() { ReleaseAllButtons(); ZeroAnalogState( 0, ANALOG_CODE_LAST - 1 ); ClearInputState( false ); } //----------------------------------------------------------------------------- // 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, true ); } 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 ) { InputState_t &state = m_InputState[ m_bIsPolling ]; int i = state.m_Events.AddToTail(); InputEvent_t &event = state.m_Events[i]; event.m_nType = nType; event.m_nTick = nTick; event.m_nData = nData; event.m_nData2 = nData2; event.m_nData3 = nData3; state.m_bDirty = true; } //----------------------------------------------------------------------------- // 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 ( IsGameConsole() && ShouldGenerateUIEvents() && IsJoystickCode( scanCode ) ) { // xboxissue - as yet input hasn't been made aware of analog inputs or ports // so just digital produce a key typed message PostEvent( IE_KeyCodeTyped, nTick, scanCode ); } } } //----------------------------------------------------------------------------- // 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 ); } } //----------------------------------------------------------------------------- // Purpose: Pass Joystick button events through the engine's window procs //----------------------------------------------------------------------------- void CInputSystem::ProcessEvent( UINT uMsg, WPARAM wParam, LPARAM lParam ) { #if !defined( PLATFORM_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( WIN32 ) && !defined( USE_SDL ) void CInputSystem::PollInputState_Windows() { if ( IsPC() && 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; } #if defined( INCLUDE_SCALEFORM ) if ( g_pScaleformUI ) { // Scaleform IME requirement. Pass these messages to GFxIME BEFORE any TranlsateMessage/DispatchMessage. if ( (msg.message == WM_KEYDOWN) || (msg.message == WM_KEYUP) || ImmIsUIMessage( NULL, msg.message, msg.wParam, msg.lParam ) || (msg.message == WM_LBUTTONDOWN) || (msg.message == WM_LBUTTONUP) ) { g_pScaleformUI->PreProcessKeyboardEvent( (size_t)msg.hwnd, msg.message, msg.wParam, msg.lParam ); } } #endif TranslateMessage( &msg ); DispatchMessage( &msg ); } // NOTE: Under some implementations of Win9x, // dispatching messages can cause the FPU control word to change SetupFPUControlWord(); } } #endif #if defined(OSX) || defined( USE_SDL ) #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; } #elif defined(OSX) static BYTE scantokey[128] = { KEY_A, KEY_S, KEY_D, KEY_F, KEY_H, KEY_G, KEY_Z, KEY_X, KEY_C, KEY_V, KEY_BACKQUOTE /*german backquote char*/ , KEY_B, KEY_Q, KEY_W, KEY_E, KEY_R, //15 KEY_Y, KEY_T, KEY_1, KEY_2, KEY_3, KEY_4, KEY_6, KEY_5, // 23 KEY_EQUAL, KEY_9, KEY_7, KEY_MINUS, KEY_8, KEY_0, KEY_RBRACKET, KEY_O, //31 KEY_U, KEY_LBRACKET, KEY_I, KEY_P, KEY_ENTER , KEY_L, KEY_J, KEY_APOSTROPHE, //39 KEY_K, KEY_SEMICOLON, KEY_BACKSLASH, KEY_COMMA,KEY_SLASH, KEY_N, KEY_M, KEY_PERIOD, // 47 KEY_TAB, KEY_SPACE, KEY_BACKQUOTE, KEY_BACKSPACE, 0, KEY_ESCAPE, KEY_RWIN, KEY_LWIN, //55 KEY_LSHIFT, KEY_CAPSLOCK, KEY_LALT, KEY_LCONTROL, KEY_LSHIFT, 0, KEY_RCONTROL, 0, //63 0, KEY_PAD_DECIMAL, 0 , KEY_PAD_MULTIPLY, 0 , KEY_PAD_PLUS, 0 , KEY_NUMLOCK , // 71 0, 0 , 0 , KEY_PAD_DIVIDE, KEY_PAD_ENTER, 0 , KEY_PAD_MINUS, 0 , // 79 0, KEY_PAD_DIVIDE, KEY_PAD_0, KEY_PAD_1, KEY_PAD_2, KEY_PAD_3, KEY_PAD_4, KEY_PAD_5, // 87 KEY_PAD_6, KEY_PAD_7, 0, KEY_PAD_8, KEY_PAD_9, 0, 0 , 0 , // 95 KEY_F5, KEY_F6, KEY_F7, KEY_F3, KEY_F8, KEY_F9, 0, KEY_F11, // 103 0, 0 , 0 , 0 , 0, KEY_F10, KEY_APP , KEY_F12, // 111 0 , 0, KEY_INSERT, KEY_HOME, KEY_PAGEUP, KEY_DELETE, KEY_F4, KEY_END, // 119 KEY_F2, KEY_PAGEDOWN, KEY_F1, KEY_LEFT, KEY_RIGHT, KEY_DOWN, KEY_UP, 0, // 127 }; #else #error #endif bool MapCocoaVirtualKeyToButtonCode( int nCocoaVirtualKeyCode, ButtonCode_t *pOut ) { if ( nCocoaVirtualKeyCode < 0 ) *pOut = (ButtonCode_t)(-1 * nCocoaVirtualKeyCode); else { #ifdef OSX int modified = nCocoaVirtualKeyCode & 255; if ( modified > 127) { return false; } #else nCocoaVirtualKeyCode &= 0x000000ff; #endif *pOut = (ButtonCode_t)scantokey[nCocoaVirtualKeyCode]; } return true; } #ifdef LINUX void CInputSystem::PollInputState_Linux() #elif defined( OSX ) void CInputSystem::PollInputState_OSX() #elif defined( _WIN32 ) void CInputSystem::PollInputState_Windows() #endif { 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; #ifdef LINUX if( scanCode != BUTTON_CODE_NONE ) #endif { // 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(); event.m_nType = IE_KeyCodeTyped; event.m_nData = scanCode; g_pInputSystem->PostUserEvent( event ); #if defined( LINUX ) || (defined( OSX ) && defined( USE_SDL ) ) if ( scanCode == KEY_BACKSPACE ) { // On Linux (and OS X, when using SDL), we need to fire this event to have backspace keypresses picked up by scaleform. PostEvent( IE_KeyTyped, GetPollTick(), (wchar_t)8 ); } #endif } if ( !(pEvent->m_ModifierKeyMask & (1<m_VirtualKeyCode >= 0 && pEvent->m_UnicodeKey > 0 ) { InputEvent_t event; memset( &event, 0, sizeof(event) ); event.m_nTick = GetPollTick(); event.m_nType = IE_KeyTyped; event.m_nData = (int)pEvent->m_UnicodeKey; g_pInputSystem->PostUserEvent( event ); } #if defined ( CSTRIKE15 ) // [will] - HACK: Allow cmd+a, cmd+c, cmd+v, cmd+x to go through, and treat them as the ctrl modified versions. // This allows these to work in the Scaleform chat window. if ( pEvent->m_ModifierKeyMask & (1<m_UnicodeKey == 'a' || pEvent->m_UnicodeKey == 'c' || pEvent->m_UnicodeKey == 'v' || pEvent->m_UnicodeKey == 'x' ) ) { InputEvent_t event; memset( &event, 0, sizeof(event) ); event.m_nTick = GetPollTick(); event.m_nType = IE_KeyTyped; event.m_nData = (int)pEvent->m_UnicodeKey - 96; // Subtract 96 to give the ctrl version of this character. g_pInputSystem->PostUserEvent( event ); } #endif } break; case CocoaEvent_KeyUp: { ButtonCode_t virtualCode; if ( MapCocoaVirtualKeyToButtonCode( pEvent->m_VirtualKeyCode, &virtualCode ) ) { 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(); event.m_nType = IE_LocateMouseClick; 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 ); #ifdef LINUX state.m_pAnalogDelta[ MOUSE_WHEEL ] = pEvent->m_MousePos[1]; #else state.m_pAnalogDelta[ MOUSE_WHEEL ] = ( (short)pEvent->m_MousePos[1] ) / 10; #endif 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 + 1; event.m_nData = (bool)pEvent->m_ModifierKeyMask; g_pInputSystem->PostUserEvent( event ); } break; case CocoaEvent_AppQuit: { PostEvent( IE_Quit, m_nLastSampleTick ); } break; } } } } #endif // PLATFORM_OSX //----------------------------------------------------------------------------- // Polls the current input state //----------------------------------------------------------------------------- void CInputSystem::PollInputState( bool bIsInGame ) { #if !defined( _CERT ) && !defined(LINUX) PollPressX360Button(); #endif m_bIsPolling = true; ++m_nPollCount; // set whether in a game or not m_bIsInGame = bIsInGame; // 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_OSX ) PollInputState_OSX(); #elif defined( LINUX ) PollInputState_Linux(); #elif defined( WIN32 ) PollInputState_Windows(); #elif defined( _PS3 ) #else #error #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(); static ConVarRef joystick_force_disabled( "joystick_force_disabled" ); #if !defined( PLATFORM_POSIX ) || defined( _GAMECONSOLE ) if ( joystick_force_disabled.IsValid() && joystick_force_disabled.GetBool() == false ) { PollXDevices(); } #endif if ( m_bXController == false && joystick_force_disabled.IsValid() && joystick_force_disabled.GetBool() == false ) { PollJoystick(); } m_bSteamController = PollSteamControllers(); } //----------------------------------------------------------------------------- // Purpose: Forwards rumble info to attached devices //----------------------------------------------------------------------------- void CInputSystem::SetRumble( float fLeftMotor, float fRightMotor, int userId ) { #ifndef LINUX // TODO: send force feedback to rumble-enabled joysticks SetXDeviceRumble( fLeftMotor, fRightMotor, userId ); #endif } //----------------------------------------------------------------------------- // Purpose: Force an immediate stop, transmits immediately to all devices //----------------------------------------------------------------------------- void CInputSystem::StopRumble( int userId ) { if ( IsPlatformWindowsPC() ) { if ( userId == INVALID_USER_ID ) { 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 { xdevice_t* pXDevice = &m_XDevices[userId]; if ( pXDevice->active ) { pXDevice->vibration.wLeftMotorSpeed = 0; pXDevice->vibration.wRightMotorSpeed = 0; pXDevice->pendingRumbleUpdate = true; WriteToXDevice( pXDevice ); } } } else { #ifndef LINUX SetXDeviceRumble( 0, 0, userId ); #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]; } bool CInputSystem::MotionControllerActive( ) const { bool isReadingMotionControllerInput = IsDeviceReadingInput( INPUT_DEVICE_HYDRA ) || IsDeviceReadingInput( INPUT_DEVICE_PLAYSTATION_MOVE ) || IsDeviceReadingInput( INPUT_DEVICE_SHARPSHOOTER ); return ( isReadingMotionControllerInput && m_bMotionControllerActive ); } Quaternion CInputSystem::GetMotionControllerOrientation( ) const { return m_qMotionControllerOrientation; } float CInputSystem::GetMotionControllerPosX( ) const { return m_fMotionControllerPosX; } float CInputSystem::GetMotionControllerPosY( ) const { return m_fMotionControllerPosY; } int CInputSystem::GetMotionControllerDeviceStatus( ) const { return m_nMotionControllerStatus; } void CInputSystem::SetMotionControllerDeviceStatus( int nStatus ) { m_nMotionControllerStatus = nStatus; } uint64 CInputSystem::GetMotionControllerDeviceStatusFlags( ) const { return m_nMotionControllerStatusFlags; } #if defined( _OSX ) || defined (LINUX) // this is defined in xcontroller.cpp, but that file isn't included // in posix builds void CInputSystem::SetMotionControllerCalibrationInvalid( void ) { } void CInputSystem::StepMotionControllerCalibration( void ) { } void CInputSystem::ResetMotionControllerScreenCalibration( void ) { } #endif // _OSX //----------------------------------------------------------------------------- // 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( PLATFORM_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( POSIX ) && !defined( USE_SDL) 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( USE_SDL ) m_pLauncherMgr->SetCursorPosition( x, y ); #elif defined( OSX ) m_pLauncherMgr->SetCursorPosition( x, y ); #elif defined( WIN32 ) POINT pt; pt.x = x; pt.y = y; ClientToScreen( (HWND)m_hAttachedHWnd, &pt ); SetCursorPos( pt.x, pt.y ); #elif defined( PLATFORM_PS3 ) POINT pt; pt.x = x; pt.y = y; SetCursorPos( pt.x, pt.y ); #else #error #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::GetCursorPosition( int *pX, int *pY ) { if ( !m_hAttachedHWnd ) { *pX = *pY = 0; return; } #if defined( USE_SDL ) *pX = m_InputState[INPUT_STATE_CURRENT].m_pAnalogValue[MOUSE_X]; *pY = m_InputState[INPUT_STATE_CURRENT].m_pAnalogValue[MOUSE_Y]; #elif defined( PLATFORM_OSX ) if ( m_bCursorVisible ) { CGEventRef event = CGEventCreate( NULL ); CGPoint pnt = CGEventGetLocation( event ); // [will] - QuickDraw functions removed in 10.7, so using using CocoaMgr for window info instead. unsigned int displayWidth, displayHeight; m_pLauncherMgr->DisplayedSize( displayWidth, displayHeight ); *pX = pnt.x; *pY = pnt.y; CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); int rx, ry, width, height; pRenderContext->GetViewport( rx, ry, width, height ); int windowHeight = (int)displayWidth; int windowWidth = (int)displayHeight; if ( width != windowWidth || abs( height - windowHeight ) > 22 ) { // scale the x/y back into the co-ords of the back buffer, not the scaled up window //DevMsg( "Mouse x:%d y:%d %d %d %d %d\n", x, y, width, windowWidth, height, abs( height - windowHeight ) ); *pX = *pX * (float)width/windowWidth; *pY = *pY * (float)height/windowHeight; } CFRelease( event ); } else { // cursor is invisible, just say the center of the screen CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); int rx, ry, width, height; pRenderContext->GetViewport( rx, ry, width, height ); *pX = width/2; *pY = height/2; } #elif !defined( PLATFORM_POSIX ) POINT pt; ::GetCursorPos( &pt ); ScreenToClient((HWND)m_hAttachedHWnd, &pt); *pX = pt.x; *pY = pt.y; #endif } void CInputSystem::SetMouseCursorVisible( bool bVisible ) { m_bCursorVisible = bVisible; } 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 ] ); } } #ifdef PLATFORM_WINDOWS //----------------------------------------------------------------------------- // Generates LocateMouseClick messages //----------------------------------------------------------------------------- void CInputSystem::LocateMouseClick( LPARAM lParam ) { if ( ShouldGenerateUIEvents() ) { PostEvent( IE_LocateMouseClick, m_nLastSampleTick, (short)LOWORD(lParam), (short)HIWORD(lParam) ); } } //----------------------------------------------------------------------------- // Handles input messages //----------------------------------------------------------------------------- LRESULT CInputSystem::WindowProc( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam ) { #if !defined( POSIX ) && !defined( USE_SDL ) if ( !m_bEnabled ) return ChainWindowMessage( hwnd, uMsg, wParam, lParam ); if ( ShouldGenerateUIEvents() && ( hwnd != m_hLastIMEHWnd ) ) { m_hLastIMEHWnd = hwnd; PostEvent( IE_IMESetWindow, m_nLastSampleTick, (intp)hwnd ); } // Allow ActivateApp messages to get through so we know when to reset input state if ( ( hwnd != m_hAttachedHWnd ) && ( uMsg != WM_ACTIVATEAPP ) ) return ChainWindowMessage( hwnd, uMsg, wParam, lParam ); InputState_t &state = m_InputState[ m_bIsPolling ]; switch( uMsg ) { case WM_ACTIVATEAPP: if ( hwnd == m_hAttachedHWnd ) { bool bActivated = ( wParam == 1 ); if ( !bActivated ) { ResetInputState(); } } break; case WM_CLOSE: // Handle close messages PostEvent( IE_Close, m_nLastSampleTick ); // don't Run default message pump, as that destroys the window return 0; case WM_SETCURSOR: if ( ShouldGenerateUIEvents() ) { PostEvent( IE_SetCursor, m_nLastSampleTick ); } break; case WM_SIZE: { int nWidth = LOWORD( lParam ); int nHeight = HIWORD( lParam ); bool bMinimized = ( wParam == SIZE_MINIMIZED ) || IsIconic( hwnd ); if ( bMinimized ) { nWidth = nHeight = 0; } PostEvent( IE_WindowSizeChanged, m_nLastSampleTick, nWidth, nHeight, bMinimized ); } break; case WM_LBUTTONDOWN: { LocateMouseClick( lParam ); int nButtonMask = ButtonMaskFromMouseWParam( wParam, MOUSE_LEFT, true ); ETWMouseDown( 0, (short)LOWORD(lParam), (short)HIWORD(lParam) ); UpdateMouseButtonState( nButtonMask ); } break; case WM_LBUTTONUP: { LocateMouseClick( lParam ); int nButtonMask = ButtonMaskFromMouseWParam( wParam, MOUSE_LEFT, false ); ETWMouseUp( 0, (short)LOWORD(lParam), (short)HIWORD(lParam) ); UpdateMouseButtonState( nButtonMask ); } break; case WM_RBUTTONDOWN: { LocateMouseClick( lParam ); int nButtonMask = ButtonMaskFromMouseWParam( wParam, MOUSE_RIGHT, true ); ETWMouseDown( 2, (short)LOWORD(lParam), (short)HIWORD(lParam) ); UpdateMouseButtonState( nButtonMask ); } break; case WM_RBUTTONUP: { LocateMouseClick( lParam ); int nButtonMask = ButtonMaskFromMouseWParam( wParam, MOUSE_RIGHT, false ); ETWMouseUp( 2, (short)LOWORD(lParam), (short)HIWORD(lParam) ); UpdateMouseButtonState( nButtonMask ); } break; case WM_MBUTTONDOWN: { LocateMouseClick( lParam ); int nButtonMask = ButtonMaskFromMouseWParam( wParam, MOUSE_MIDDLE, true ); ETWMouseDown( 1, (short)LOWORD(lParam), (short)HIWORD(lParam) ); UpdateMouseButtonState( nButtonMask ); } break; case WM_MBUTTONUP: { LocateMouseClick( lParam ); int nButtonMask = ButtonMaskFromMouseWParam( wParam, MOUSE_MIDDLE, false ); ETWMouseUp( 1, (short)LOWORD(lParam), (short)HIWORD(lParam) ); UpdateMouseButtonState( nButtonMask ); } break; case MS_WM_XBUTTONDOWN: { LocateMouseClick( lParam ); 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: { LocateMouseClick( lParam ); 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: { LocateMouseClick( lParam ); int nButtonMask = ButtonMaskFromMouseWParam( wParam, MOUSE_LEFT, true ); ETWMouseDown( 0, (short)LOWORD(lParam), (short)HIWORD(lParam) ); UpdateMouseButtonState( nButtonMask, MOUSE_LEFT ); } break; case WM_RBUTTONDBLCLK: { LocateMouseClick( lParam ); int nButtonMask = ButtonMaskFromMouseWParam( wParam, MOUSE_RIGHT, true ); ETWMouseDown( 2, (short)LOWORD(lParam), (short)HIWORD(lParam) ); UpdateMouseButtonState( nButtonMask, MOUSE_RIGHT ); } break; case WM_MBUTTONDBLCLK: { LocateMouseClick( lParam ); int nButtonMask = ButtonMaskFromMouseWParam( wParam, MOUSE_MIDDLE, true ); ETWMouseDown( 1, (short)LOWORD(lParam), (short)HIWORD(lParam) ); UpdateMouseButtonState( nButtonMask, MOUSE_MIDDLE ); } break; case MS_WM_XBUTTONDBLCLK: { LocateMouseClick( lParam ); 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 ); } } if ( ShouldGenerateUIEvents() ) { ButtonCode_t virtualCode = ButtonCode_VirtualKeyToButtonCode( wParam ); int nKeyRepeat = LOWORD( lParam ); for ( int i = 0; i < nKeyRepeat; ++i ) { PostEvent( IE_KeyCodeTyped, m_nLastSampleTick, virtualCode ); } } } 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; case WM_MOUSEMOVE: { UpdateMousePositionState( state, (short)LOWORD(lParam), (short)HIWORD(lParam) ); int nButtonMask = ButtonMaskFromMouseWParam( wParam ); UpdateMouseButtonState( nButtonMask ); } break; #if defined ( WIN32 ) && !defined ( _X360 ) case WM_INPUT: { if ( m_bRawInputSupported ) { UINT dwSize = sizeof( RAWINPUT ); static BYTE lpb[ sizeof( RAWINPUT ) ]; 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 case WM_SYSCHAR: case WM_CHAR: if ( ShouldGenerateUIEvents() && !m_bIMEComposing ) { PostEvent( IE_KeyTyped, m_nLastSampleTick, (wchar_t)wParam ); } break; case WM_INPUTLANGCHANGE: // Note that this is passed to IME managers even if the IME is currently // disallowed so that IMEs are still aware of the current language // in case they are allowed in the future. #if defined( INCLUDE_SCALEFORM ) if ( g_pScaleformUI ) { g_pScaleformUI->HandleIMEEvent( (size_t)hwnd, uMsg, wParam, lParam ); } #endif if ( ShouldGenerateUIEvents() ) { PostEvent( IE_InputLanguageChanged, m_nLastSampleTick ); } break; case WM_IME_KEYDOWN: #if defined( INCLUDE_SCALEFORM ) if ( g_pScaleformUI && g_pScaleformUI->HandleIMEEvent( (size_t)hwnd, uMsg, wParam, lParam ) ) return 0; #endif break; case WM_IME_STARTCOMPOSITION: #if defined( INCLUDE_SCALEFORM ) if ( g_pScaleformUI && g_pScaleformUI->HandleIMEEvent( (size_t)hwnd, uMsg, wParam, lParam ) ) { m_bIMEComposing = true; return 0; } #endif if ( ShouldGenerateUIEvents() ) { m_bIMEComposing = true; PostEvent( IE_IMEStartComposition, m_nLastSampleTick ); return TRUE; } break; case WM_IME_COMPOSITION: #if defined( INCLUDE_SCALEFORM ) if ( g_pScaleformUI && g_pScaleformUI->HandleIMEEvent( (size_t)hwnd, uMsg, wParam, lParam ) ) return 0; #endif if ( ShouldGenerateUIEvents() ) { PostEvent( IE_IMEComposition, m_nLastSampleTick, (int)lParam ); return TRUE; } break; case WM_IME_ENDCOMPOSITION: #if defined( INCLUDE_SCALEFORM ) if ( g_pScaleformUI && g_pScaleformUI->HandleIMEEvent( (size_t)hwnd, uMsg, wParam, lParam ) ) { m_bIMEComposing = false; return 0; } #endif if ( ShouldGenerateUIEvents() ) { m_bIMEComposing = false; PostEvent( IE_IMEEndComposition, m_nLastSampleTick ); return TRUE; } break; case WM_IME_NOTIFY: #if defined( INCLUDE_SCALEFORM ) if ( g_pScaleformUI && g_pScaleformUI->HandleIMEEvent( (size_t)hwnd, uMsg, wParam, lParam ) ) return 0; #endif if ( ShouldGenerateUIEvents() ) { switch (wParam) { default: break; case 14: // Chinese Traditional IMN_PRIVATE... break; case IMN_OPENCANDIDATE: PostEvent( IE_IMEShowCandidates, m_nLastSampleTick ); return 1; case IMN_CHANGECANDIDATE: PostEvent( IE_IMEChangeCandidates, m_nLastSampleTick ); return 0; case IMN_CLOSECANDIDATE: PostEvent( IE_IMECloseCandidates, m_nLastSampleTick ); break; // To detect the change of IME mode, or the toggling of Japanese IME case IMN_SETCONVERSIONMODE: case IMN_SETSENTENCEMODE: case IMN_SETOPENSTATUS: PostEvent( IE_IMERecomputeModes, m_nLastSampleTick ); if ( wParam == IMN_SETOPENSTATUS ) return 0; break; case IMN_CLOSESTATUSWINDOW: case IMN_GUIDELINE: case IMN_OPENSTATUSWINDOW: case IMN_SETCANDIDATEPOS: case IMN_SETCOMPOSITIONFONT: case IMN_SETCOMPOSITIONWINDOW: case IMN_SETSTATUSWINDOWPOS: break; } } break; case WM_IME_CHAR: #if defined( INCLUDE_SCALEFORM ) if ( g_pScaleformUI && g_pScaleformUI->HandleIMEEvent( (size_t)hwnd, uMsg, wParam, lParam ) ) return 0; #endif if ( ShouldGenerateUIEvents() ) { // We need to process this message so that the IME doesn't double // convert the unicode IME characters into garbage characters and post // them to our window... (get ? marks after text entry ). return 0; } break; case WM_IME_SETCONTEXT: #if defined( INCLUDE_SCALEFORM ) if ( g_pScaleformUI ) { g_pScaleformUI->HandleIMEEvent( (size_t)hwnd, uMsg, wParam, lParam ); lParam = 0; } else #endif if ( ShouldGenerateUIEvents() ) { // We draw all IME windows ourselves lParam &= ~ISC_SHOWUICOMPOSITIONWINDOW; lParam &= ~ISC_SHOWUIGUIDELINE; lParam &= ~ISC_SHOWUIALLCANDIDATEWINDOW; } break; } // Can't put this in the case statement, it's not constant if ( IsPC() && ( 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 ] ); } return ChainWindowMessage( hwnd, uMsg, wParam, lParam ); #else return 0; #endif } #endif //----------------------------------------------------------------------------- // Initializes, shuts down cursors //----------------------------------------------------------------------------- void CInputSystem::InitCursors() { #ifdef PLATFORM_WINDOWS // load up all default cursors memset( m_pDefaultCursors, 0, sizeof(m_pDefaultCursors) ); m_pDefaultCursors[INPUT_CURSOR_NONE] = INPUT_CURSOR_HANDLE_INVALID; m_pDefaultCursors[INPUT_CURSOR_ARROW] = (InputCursorHandle_t)LoadCursor(NULL, (LPCTSTR)OCR_NORMAL); m_pDefaultCursors[INPUT_CURSOR_IBEAM] = (InputCursorHandle_t)LoadCursor(NULL, (LPCTSTR)OCR_IBEAM); m_pDefaultCursors[INPUT_CURSOR_HOURGLASS] = (InputCursorHandle_t)LoadCursor(NULL, (LPCTSTR)OCR_WAIT); m_pDefaultCursors[INPUT_CURSOR_CROSSHAIR] = (InputCursorHandle_t)LoadCursor(NULL, (LPCTSTR)OCR_CROSS); m_pDefaultCursors[INPUT_CURSOR_WAITARROW] = (InputCursorHandle_t)LoadCursor(NULL, (LPCTSTR)32650); m_pDefaultCursors[INPUT_CURSOR_UP] = (InputCursorHandle_t)LoadCursor(NULL, (LPCTSTR)OCR_UP); m_pDefaultCursors[INPUT_CURSOR_SIZE_NW_SE] = (InputCursorHandle_t)LoadCursor(NULL, (LPCTSTR)OCR_SIZENWSE); m_pDefaultCursors[INPUT_CURSOR_SIZE_NE_SW] = (InputCursorHandle_t)LoadCursor(NULL, (LPCTSTR)OCR_SIZENESW); m_pDefaultCursors[INPUT_CURSOR_SIZE_W_E] = (InputCursorHandle_t)LoadCursor(NULL, (LPCTSTR)OCR_SIZEWE); m_pDefaultCursors[INPUT_CURSOR_SIZE_N_S] = (InputCursorHandle_t)LoadCursor(NULL, (LPCTSTR)OCR_SIZENS); m_pDefaultCursors[INPUT_CURSOR_SIZE_ALL] = (InputCursorHandle_t)LoadCursor(NULL, (LPCTSTR)OCR_SIZEALL); m_pDefaultCursors[INPUT_CURSOR_NO] = (InputCursorHandle_t)LoadCursor(NULL, (LPCTSTR)OCR_NO); m_pDefaultCursors[INPUT_CURSOR_HAND] = (InputCursorHandle_t)LoadCursor(NULL, (LPCTSTR)32649); #endif } void CInputSystem::ShutdownCursors() { #ifdef PLATFORM_WINDOWS int nCount = m_UserCursors.GetNumStrings(); for ( int i = 0; i < nCount; ++i ) { ::DestroyCursor( (HCURSOR)m_UserCursors[ i ] ); } m_UserCursors.Purge(); for ( int i = 0; i < ARRAYSIZE( m_pDefaultCursors ); ++i ) { if ( m_pDefaultCursors[i] != INPUT_CURSOR_HANDLE_INVALID ) { ::DestroyCursor( (HCURSOR)m_pDefaultCursors[ i ] ); m_pDefaultCursors[ i ] = INPUT_CURSOR_HANDLE_INVALID; } } #endif } //----------------------------------------------------------------------------- // Gets the cursor //----------------------------------------------------------------------------- InputCursorHandle_t CInputSystem::GetStandardCursor( InputStandardCursor_t id ) { return m_pDefaultCursors[id]; } InputCursorHandle_t CInputSystem::LoadCursorFromFile( const char *pFileName, const char *pPathID ) { if ( !g_pFullFileSystem ) return INPUT_CURSOR_HANDLE_INVALID; char fn[ 512 ]; Q_strncpy( fn, pFileName, sizeof( fn ) ); Q_strlower( fn ); Q_FixSlashes( fn ); UtlSymId_t nCursorIndex = m_UserCursors.Find( fn ); if ( nCursorIndex != m_UserCursors.InvalidIndex() ) return m_UserCursors[ nCursorIndex ]; g_pFullFileSystem->GetLocalCopy( fn ); #ifdef PLATFORM_WINDOWS char fullpath[ 512 ]; g_pFullFileSystem->RelativePathToFullPath( fn, pPathID, fullpath, sizeof( fullpath ) ); HCURSOR newCursor = (HCURSOR)::LoadCursorFromFile( fullpath ); m_UserCursors[ fn ] = (InputCursorHandle_t)newCursor; return (InputCursorHandle_t)newCursor; #endif return 0; } void CInputSystem::SetCursorIcon( InputCursorHandle_t hCursor ) { #ifdef PLATFORM_WINDOWS m_hCursor = hCursor; HCURSOR hWindowsCursor = (HCURSOR)hCursor; ::SetCursor( hWindowsCursor ); #endif } void CInputSystem::ResetCursorIcon() { SetCursorIcon( m_hCursor ); } void CInputSystem::EnableMouseCapture( PlatWindow_t hWnd ) { #ifdef PLATFORM_WINDOWS if ( m_hCurrentCaptureWnd == hWnd ) return; // Determine if we're the foreground window. If not, force release of the mouse. Otherwise, we can capture the mouse // while we're in the background and then we never get WM_ACTIVATE messages when trying to click on the app. This // causes the app to react like it has mouse focus (firing weapons, etc) but doesn't actually come to the foreground // and doesn't accept keyboard input. // // We're using GetForegroundWindow here, but we really want to ask engine or game if they're the ActiveApp. bool bActiveWindow = true; #if !defined( _GAMECONSOLE ) HWND hInputWnd = reinterpret_cast< HWND >( hWnd ); bActiveWindow = ( hInputWnd == ::GetForegroundWindow() ); #else HWND hInputWnd = reinterpret_cast< HWND >( m_hCurrentCaptureWnd ); #endif if ( m_hCurrentCaptureWnd != PLAT_WINDOW_INVALID || !bActiveWindow ) { ::ReleaseCapture(); } m_hCurrentCaptureWnd = hWnd; if ( m_hCurrentCaptureWnd != PLAT_WINDOW_INVALID && bActiveWindow ) { ::SetCapture( hInputWnd ); } #endif } void CInputSystem::GetRawMouseAccumulators( int& accumX, int& accumY ) { #if defined( USE_SDL ) if ( m_pLauncherMgr ) { m_pLauncherMgr->GetMouseDelta( accumX, accumY, false ); } #else accumX = m_mouseRawAccumX; accumY = m_mouseRawAccumY; m_mouseRawAccumX = m_mouseRawAccumY = 0; #endif } void CInputSystem::DisableMouseCapture() { #ifdef PLATFORM_WINDOWS EnableMouseCapture( PLAT_WINDOW_INVALID ); #endif } // =================================================================== // If we add another support for another input device, we need to // update the platform assignments below to reflect it. From here, // pretty much everything else that uses these interfaces will work // unchanged (obviously UI and device code needs to be added) // Also: Add name to GetInputDeviceNameUI/Internal() in // PlatformInputDevice.cpp // =================================================================== void CInputSystem::InitPlatfromInputDeviceInfo( void ) { PlatformInputDevice::InitPlatfromInputDeviceInfo(); // Set the platform for which this code/client is compiled on and // the input devices that are assumed to be already installed (as // opposed to being queried by the inputsystem) #if defined( PLATFORM_WINDOWS_PC ) m_currentlyConnectedInputDevices = INPUT_DEVICE_KEYBOARD_MOUSE; #elif defined( PLATFORM_OSX ) m_currentlyConnectedInputDevices = INPUT_DEVICE_KEYBOARD_MOUSE; #elif defined( PLATFORM_LINUX ) m_currentlyConnectedInputDevices = INPUT_DEVICE_KEYBOARD_MOUSE; #elif defined( PLATFORM_X360 ) m_currentlyConnectedInputDevices = INPUT_DEVICE_GAMEPAD; #elif defined( PLATFORM_PS3 ) m_currentlyConnectedInputDevices = INPUT_DEVICE_NONE; #else m_currentlyConnectedInputDevices = INPUT_DEVICE_NONE; #endif ResetCurrentInputDevice(); m_setCurrentInputDeviceOnNextButtonPress = false; } void CInputSystem::ResetCurrentInputDevice( void ) { if ( m_currentInputDevice == INPUT_DEVICE_STEAM_CONTROLLER ) { // Disable resetting away from the steam controller if it's being used. return; } #if defined( PLATFORM_WINDOWS_PC ) m_currentInputDevice = INPUT_DEVICE_KEYBOARD_MOUSE; #elif defined( PLATFORM_OSX ) m_currentInputDevice = INPUT_DEVICE_KEYBOARD_MOUSE; #elif defined( PLATFORM_LINUX ) m_currentInputDevice = INPUT_DEVICE_KEYBOARD_MOUSE; #elif defined( PLATFORM_X360 ) m_currentInputDevice = INPUT_DEVICE_GAMEPAD; #elif defined( PLATFORM_PS3 ) m_currentInputDevice = INPUT_DEVICE_NONE; #else m_currentInputDevice = INPUT_DEVICE_NONE; #endif } InputDevice_t CInputSystem::GetConnectedInputDevices( void ) { return m_currentlyConnectedInputDevices; } bool CInputSystem::IsInputDeviceConnected( InputDevice_t device ) { if ( countBits( device ) != 1 || ( device & PlatformInputDevice::s_AllInputDevices ) != device ) { AssertMsg( false, "invalid input device" ); return false; } return ( ( m_currentlyConnectedInputDevices & device ) == device ); } void CInputSystem::SetInputDeviceConnected( InputDevice_t device, bool connected ) { if ( ( countBits( device ) != 1 ) || ( device & PlatformInputDevice::s_validPlatformInputDevices[PlatformInputDevice::s_LocalInputPlatform] ) != device ) { AssertMsg( false, "invalid input device" ); return; } if ( connected ) { // Message if device already connected? m_currentlyConnectedInputDevices = m_currentlyConnectedInputDevices | device; } else { // Message if device not currently connected? m_currentlyConnectedInputDevices = m_currentlyConnectedInputDevices & (~device); } } InputDevice_t CInputSystem::IsOnlySingleDeviceConnected( void ) { int32 mask = 1; // nav controller doesn't need to be considered a seperate device. int32 connectedMask = m_currentlyConnectedInputDevices & (~INPUT_DEVICE_MOVE_NAV_CONTROLLER); if ( IsInputDeviceConnected( INPUT_DEVICE_SHARPSHOOTER ) ) { connectedMask = m_currentlyConnectedInputDevices & ( ~INPUT_DEVICE_PLAYSTATION_MOVE ); } // [dkorus] loop through a mask that represents each possible device. // if one matches our connected mask exactly, we have only that device connected while( mask <= INPUT_DEVICE_MAX ) { if ( connectedMask == mask ) return (InputDevice_t) connectedMask; mask = mask << 1; } return INPUT_DEVICE_NONE; } bool CInputSystem::IsDeviceReadingInput( InputDevice_t device ) const { #ifndef _GAMECONSOLE return true; #endif #if !defined( _CERT ) // [dkorus] test code for the device selection int forceSelected = dev_force_selected_device.GetInt(); if ( forceSelected != 0) { if ( device == forceSelected ) { return true; } else { return false; } } #endif if ( device == m_currentInputDevice || m_currentInputDevice == INPUT_DEVICE_NONE ) { return true; } return false; } InputDevice_t CInputSystem::GetCurrentInputDevice( void ) { return m_currentInputDevice; } void CInputSystem::SetCurrentInputDevice( InputDevice_t device ) { if ( ( device != INPUT_DEVICE_NONE ) && ( ( countBits( device ) != 1 ) || ( device & PlatformInputDevice::s_validPlatformInputDevices[PlatformInputDevice::s_LocalInputPlatform] ) != device ) ) { AssertMsg( false, "invalid input device" ); return; } m_currentInputDevice = device; } void CInputSystem::SampleInputToFindCurrentDevice( bool doSample ) { m_setCurrentInputDeviceOnNextButtonPress = doSample; } bool CInputSystem::IsSamplingForCurrentDevice( void ) { return m_setCurrentInputDeviceOnNextButtonPress; } #ifndef LINUX #if !defined( _CERT ) // [mhansen] Add support for pressing Xbox 360 controller buttons (should work on PS3 too) struct C_press_x360_button_code { char c1; char c2; xKey_t key; }; static const C_press_x360_button_code press_x360_button_codes[] = { { 'l', 't', XK_BUTTON_LTRIGGER }, { 'r', 't', XK_BUTTON_RTRIGGER }, { 's', 't', XK_BUTTON_START }, { 'b', 'a', XK_BUTTON_BACK }, { 'l', 'b', XK_BUTTON_LEFT_SHOULDER }, { 'r', 'b', XK_BUTTON_RIGHT_SHOULDER }, { 'l', 's', XK_BUTTON_LEFT_SHOULDER }, { 'r', 's', XK_BUTTON_RIGHT_SHOULDER }, { 'a', 0, XK_BUTTON_A }, { 'b', 0, XK_BUTTON_B }, { 'x', 0, XK_BUTTON_X }, { 'y', 0, XK_BUTTON_Y }, { 'l', 0, XK_BUTTON_LEFT }, { 'r', 0, XK_BUTTON_RIGHT }, { 'u', 0, XK_BUTTON_UP }, { 'd', 0, XK_BUTTON_DOWN }, }; static const int cNum_press_x360_button_codes = ARRAYSIZE( press_x360_button_codes ); void CInputSystem::PressX360Button( const CCommand &args ) { if ( pc_fake_controller.GetBool( ) && !m_bXController ) { // [dkorus] we're simulating fake controller input and we don't have a controller enabled. Fake a controller so we can accept controller presses. // this fixes the PC so it can use the same scripting engine as the other setups // NOTE: This is wrapped in a !_CERT block. This shouldn't end up in the shipped game. m_bXController = true; } if ( args.ArgC() < 2 ) { Warning( "press_x360_button: requires a key to send (lt, rt, st[art], ba[ck], lb, rb, a, b, x, y, l[eft], r[right], u[p], d[own])" ); return; } const char* pKey = args[1]; // We're stashing this in a bitmask so make sure we don't overflow it //COMPILE_TIME_ASSERT( cNum_press_x360_button_codes < sizeof( m_press_x360_buttons[ 0 ] ) ); xKey_t key = XK_BUTTON_A; for ( uint32 i = 0; i < cNum_press_x360_button_codes; i++ ) { if ( pKey[0] == press_x360_button_codes[i].c1 && ( pKey[1] == press_x360_button_codes[i].c2 || press_x360_button_codes[i].c2 == 0 ) ) { key = press_x360_button_codes[i].key; m_press_x360_buttons[ 0 ] = m_press_x360_buttons[ 0 ] | (1 << i ); break; } } } void CInputSystem::PollPressX360Button( void ) { uint32 pressedButtons = m_press_x360_buttons[ 0 ]; uint32 releasedButtons = m_press_x360_buttons[ 1 ]; // Reset the buttons we pressed this frame m_press_x360_buttons[ 0 ] = 0; // Store the buttons we pressed this frame so we can clear them next frame m_press_x360_buttons[ 1 ] = pressedButtons; // Clear any old button presses and press any new ones for ( uint32 i = 0; i < cNum_press_x360_button_codes; i++ ) { uint32 mask = 1 << i; if ( releasedButtons & mask ) { PostXKeyEvent( 0, press_x360_button_codes[i].key, 0 ); } if ( pressedButtons & mask ) { PostXKeyEvent( 0, press_x360_button_codes[i].key, 32768/*XBX_MAX_BUTTONSAMPLE*/ ); } } } #endif // !_CERT #endif