//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: Implementation of the VGUI ISurface interface using the // material system to implement it // //===========================================================================// #if defined( WIN32 ) && !defined( _X360 ) #include #include #endif #include "inputsystem/iinputsystem.h" #include "tier2/tier2.h" #include "Input.h" #include "vguimatsurface.h" #include "../vgui2/src/VPanel.h" #include #include #include #include #include #include #include "inputsystem/ButtonCode.h" #include "Cursor.h" #include "tier0/dbg.h" #include "../vgui2/src/vgui_key_translation.h" #include #include "tier0/icommandline.h" #ifdef _X360 #include "xbox/xbox_win32stubs.h" #endif // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" using namespace vgui; //----------------------------------------------------------------------------- // Vgui input events //----------------------------------------------------------------------------- enum VguiInputEventType_t { IE_Close = IE_FirstVguiEvent, IE_LocateMouseClick, IE_SetCursor, IE_KeyTyped, IE_KeyCodeTyped, IE_InputLanguageChanged, IE_IMESetWindow, IE_IMEStartComposition, IE_IMEComposition, IE_IMEEndComposition, IE_IMEShowCandidates, IE_IMEChangeCandidates, IE_IMECloseCandidates, IE_IMERecomputeModes, }; void InitInput() { EnableInput( true ); } static bool s_bInputEnabled = true; #ifdef WIN32 //----------------------------------------------------------------------------- // Translates actual keys into VGUI ids //----------------------------------------------------------------------------- static WNDPROC s_ChainedWindowProc = NULL; extern HWND thisWindow; //----------------------------------------------------------------------------- // Initializes the input system //----------------------------------------------------------------------------- static bool s_bIMEComposing = false; static HWND s_hLastHWnd = 0; //----------------------------------------------------------------------------- // Handles input messages //----------------------------------------------------------------------------- static LRESULT CALLBACK MatSurfaceWindowProc( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam ) { if ( !s_bInputEnabled ) goto chainWndProc; InputEvent_t event; memset( &event, 0, sizeof(event) ); event.m_nTick = g_pInputSystem->GetPollTick(); if ( hwnd != s_hLastHWnd ) { s_hLastHWnd = hwnd; event.m_nType = IE_IMESetWindow; event.m_nData = (int)s_hLastHWnd; g_pInputSystem->PostUserEvent( event ); } switch(uMsg) { case WM_QUIT: // According to windows docs, WM_QUIT should never be passed to wndprocs Assert( 0 ); break; case WM_CLOSE: // Handle close messages { LONG_PTR wndProc = GetWindowLongPtrW( hwnd, GWLP_WNDPROC ); if ( wndProc == (LONG_PTR)MatSurfaceWindowProc ) { event.m_nType = IE_Close; g_pInputSystem->PostUserEvent( event ); } } return 0; // All mouse messages need to mark where the click occurred before chaining down case WM_LBUTTONDOWN: case WM_RBUTTONDOWN: case WM_MBUTTONDOWN: case MS_WM_XBUTTONDOWN: case WM_LBUTTONUP: case WM_RBUTTONUP: case WM_MBUTTONUP: case MS_WM_XBUTTONUP: case WM_LBUTTONDBLCLK: case WM_RBUTTONDBLCLK: case WM_MBUTTONDBLCLK: case MS_WM_XBUTTONDBLCLK: event.m_nType = IE_LocateMouseClick; event.m_nData = (short)LOWORD(lParam); event.m_nData2 = (short)HIWORD(lParam); g_pInputSystem->PostUserEvent( event ); break; case WM_SETCURSOR: event.m_nType = IE_SetCursor; g_pInputSystem->PostUserEvent( event ); break; case WM_XCONTROLLER_KEY: if ( IsX360() ) { // First have to insert the edge case event int nRetVal = 0; if ( s_ChainedWindowProc ) { nRetVal = CallWindowProcW( s_ChainedWindowProc, hwnd, uMsg, wParam, lParam ); } // xboxissue - as yet HL2 input hasn't been made aware of analog inputs or ports // so just digital step on the sample range int sample = LOWORD( lParam ); if ( sample ) { event.m_nType = IE_KeyCodeTyped; event.m_nData = (vgui::KeyCode)wParam; g_pInputSystem->PostUserEvent( event ); } } break; // Need to deal with key repeat for keydown since inputsystem doesn't case WM_KEYDOWN: case WM_SYSKEYDOWN: { // First have to insert the edge case event int nRetVal = 0; if ( s_ChainedWindowProc ) { nRetVal = CallWindowProcW( s_ChainedWindowProc, hwnd, uMsg, wParam, lParam ); } int nKeyRepeat = LOWORD( lParam ); for ( int i = 0; i < nKeyRepeat; ++i ) { event.m_nType = IE_KeyCodeTyped; event.m_nData = KeyCode_VirtualKeyToVGUI( wParam ); g_pInputSystem->PostUserEvent( event ); } return nRetVal; } case WM_SYSCHAR: case WM_CHAR: if ( !s_bIMEComposing ) { event.m_nType = IE_KeyTyped; event.m_nData = (int)wParam; g_pInputSystem->PostUserEvent( event ); } break; case WM_INPUTLANGCHANGE: event.m_nType = IE_InputLanguageChanged; g_pInputSystem->PostUserEvent( event ); break; case WM_IME_STARTCOMPOSITION: s_bIMEComposing = true; event.m_nType = IE_IMEStartComposition; g_pInputSystem->PostUserEvent( event ); return TRUE; case WM_IME_COMPOSITION: event.m_nType = IE_IMEComposition; event.m_nData = (int)lParam; g_pInputSystem->PostUserEvent( event ); return TRUE; case WM_IME_ENDCOMPOSITION: s_bIMEComposing = false; event.m_nType = IE_IMEEndComposition; g_pInputSystem->PostUserEvent( event ); return TRUE; case WM_IME_NOTIFY: { switch (wParam) { default: break; case 14: // Chinese Traditional IMN_PRIVATE... break; case IMN_OPENCANDIDATE: event.m_nType = IE_IMEShowCandidates; g_pInputSystem->PostUserEvent( event ); return 1; case IMN_CHANGECANDIDATE: event.m_nType = IE_IMEChangeCandidates; g_pInputSystem->PostUserEvent( event ); return 0; case IMN_CLOSECANDIDATE: event.m_nType = IE_IMECloseCandidates; g_pInputSystem->PostUserEvent( event ); break; // To detect the change of IME mode, or the toggling of Japanese IME case IMN_SETCONVERSIONMODE: case IMN_SETSENTENCEMODE: case IMN_SETOPENSTATUS: event.m_nType = IE_IMERecomputeModes; g_pInputSystem->PostUserEvent( event ); 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; } } case WM_IME_SETCONTEXT: // We draw all IME windows ourselves lParam &= ~ISC_SHOWUICOMPOSITIONWINDOW; lParam &= ~ISC_SHOWUIGUIDELINE; lParam &= ~ISC_SHOWUIALLCANDIDATEWINDOW; break; case WM_IME_CHAR: // 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; } chainWndProc: if ( s_ChainedWindowProc ) return CallWindowProcW( s_ChainedWindowProc, hwnd, uMsg, wParam, lParam ); // 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; } #endif //----------------------------------------------------------------------------- // Enables/disables input (enabled by default) //----------------------------------------------------------------------------- void EnableInput( bool bEnable ) { #if 0 // #ifdef BENCHMARK s_bInputEnabled = false; #else s_bInputEnabled = bEnable; #endif } #ifdef WIN32 //----------------------------------------------------------------------------- // Hooks input listening up to a window //----------------------------------------------------------------------------- void InputAttachToWindow(void *hwnd) { #if !defined( USE_SDL ) s_ChainedWindowProc = (WNDPROC)GetWindowLongPtrW( (HWND)hwnd, GWLP_WNDPROC ); SetWindowLongPtrW( (HWND)hwnd, GWLP_WNDPROC, (LONG_PTR)MatSurfaceWindowProc ); #endif } void InputDetachFromWindow(void *hwnd) { if (!hwnd) return; if ( s_ChainedWindowProc ) { SetWindowLongPtrW( (HWND)hwnd, GWLP_WNDPROC, (LONG_PTR) s_ChainedWindowProc ); s_ChainedWindowProc = NULL; } } #else void InputAttachToWindow(void *hwnd) { #if !defined( OSX ) && !defined( LINUX ) if ( hwnd && !HushAsserts() ) { // under OSX we use the Cocoa mgr to route events rather than hooking winprocs // and under Linux we use SDL Assert( !"Implement me" ); } #endif } void InputDetachFromWindow(void *hwnd) { #if !defined( OSX ) && !defined( LINUX ) if ( hwnd && !HushAsserts() ) { // under OSX we use the Cocoa mgr to route events rather than hooking winprocs // and under Linux we use SDL Assert( !"Implement me" ); } #endif } #endif //----------------------------------------------------------------------------- // Converts an input system button code to a vgui key code // FIXME: Remove notion of vgui::KeyCode + vgui::MouseCode altogether //----------------------------------------------------------------------------- static vgui::KeyCode ButtonCodeToKeyCode( ButtonCode_t buttonCode ) { return ( vgui::KeyCode )buttonCode; } static vgui::MouseCode ButtonCodeToMouseCode( ButtonCode_t buttonCode ) { return ( vgui::MouseCode )buttonCode; } //----------------------------------------------------------------------------- // Handles an input event, returns true if the event should be filtered // from the rest of the game //----------------------------------------------------------------------------- bool InputHandleInputEvent( const InputEvent_t &event ) { switch( event.m_nType ) { case IE_ButtonPressed: { // NOTE: data2 is the virtual key code (data1 contains the scan-code one) ButtonCode_t code = (ButtonCode_t)event.m_nData2; if ( IsKeyCode( code ) || IsJoystickCode( code ) ) { vgui::KeyCode keyCode = ButtonCodeToKeyCode( code ); return g_pIInput->InternalKeyCodePressed( keyCode ); } if ( IsJoystickCode( code ) ) { vgui::KeyCode keyCode = ButtonCodeToKeyCode( code ); return g_pIInput->InternalKeyCodePressed( keyCode ); } if ( IsMouseCode( code ) ) { vgui::MouseCode mouseCode = ButtonCodeToMouseCode( code ); return g_pIInput->InternalMousePressed( mouseCode ); } } break; case IE_ButtonReleased: { // NOTE: data2 is the virtual key code (data1 contains the scan-code one) ButtonCode_t code = (ButtonCode_t)event.m_nData2; if ( IsKeyCode( code ) || IsJoystickCode( code ) ) { vgui::KeyCode keyCode = ButtonCodeToKeyCode( code ); return g_pIInput->InternalKeyCodeReleased( keyCode ); } if ( IsJoystickCode( code ) ) { vgui::KeyCode keyCode = ButtonCodeToKeyCode( code ); return g_pIInput->InternalKeyCodeReleased( keyCode ); } if ( IsMouseCode( code ) ) { vgui::MouseCode mouseCode = ButtonCodeToMouseCode( code ); return g_pIInput->InternalMouseReleased( mouseCode ); } } break; case IE_ButtonDoubleClicked: { // NOTE: data2 is the virtual key code (data1 contains the scan-code one) ButtonCode_t code = (ButtonCode_t)event.m_nData2; if ( IsMouseCode( code ) ) { vgui::MouseCode mouseCode = ButtonCodeToMouseCode( code ); return g_pIInput->InternalMouseDoublePressed( mouseCode ); } } break; case IE_AnalogValueChanged: { if ( event.m_nData == MOUSE_WHEEL ) return g_pIInput->InternalMouseWheeled( event.m_nData3 ); if ( event.m_nData == MOUSE_XY ) return g_pIInput->InternalCursorMoved( event.m_nData2, event.m_nData3 ); } break; case IE_KeyCodeTyped: { vgui::KeyCode code = (vgui::KeyCode)event.m_nData; g_pIInput->InternalKeyCodeTyped( code ); } return true; case IE_KeyTyped: { vgui::KeyCode code = (vgui::KeyCode)event.m_nData; g_pIInput->InternalKeyTyped( code ); } return true; case IE_Quit: g_pVGui->Stop(); #if defined( USE_SDL ) return false; // also let higher layers consume it #else return true; #endif case IE_Close: // FIXME: Change this so we don't stop until 'save' occurs, etc. g_pVGui->Stop(); return true; case IE_SetCursor: ActivateCurrentCursor(); return true; case IE_IMESetWindow: g_pIInput->SetIMEWindow( (void *)event.m_nData ); return true; case IE_LocateMouseClick: g_pIInput->InternalCursorMoved( event.m_nData, event.m_nData2 ); return true; case IE_InputLanguageChanged: g_pIInput->OnInputLanguageChanged(); return true; case IE_IMEStartComposition: g_pIInput->OnIMEStartComposition(); return true; case IE_IMEComposition: g_pIInput->OnIMEComposition( event.m_nData ); return true; case IE_IMEEndComposition: g_pIInput->OnIMEEndComposition(); return true; case IE_IMEShowCandidates: g_pIInput->OnIMEShowCandidates(); return true; case IE_IMEChangeCandidates: g_pIInput->OnIMEChangeCandidates(); return true; case IE_IMECloseCandidates: g_pIInput->OnIMECloseCandidates(); return true; case IE_IMERecomputeModes: g_pIInput->OnIMERecomputeModes(); return true; } return false; }