// InputBuffer.cpp : Implementation of cInputBuffer #include "stdafx.h" #include "Inject.h" #include "InputBuffer.h" #include "Manager.h" ///////////////////////////////////////////////////////////////////////////// // cInputBuffer struct cCharNames { LPCTSTR szName; WORD m_nVKey; }; static cCharNames _charnames[] = { { _T( "BACKSPACE" ), VK_BACK }, { _T( "BS" ), VK_BACK }, { _T( "BKSP" ), VK_BACK }, { _T( "CAPSLOCK" ), VK_CAPITAL }, { _T( "DELETE" ), VK_DELETE }, { _T( "DEL" ), VK_DELETE }, { _T( "DOWN" ), VK_DOWN }, { _T( "END" ), VK_END }, { _T( "ENTER" ), VK_RETURN }, { _T( "ESC" ), VK_ESCAPE }, { _T( "HELP" ), VK_HELP }, { _T( "HOME" ), VK_HOME }, { _T( "INS" ), VK_INSERT }, { _T( "INSERT" ), VK_INSERT }, { _T( "LEFT" ), VK_LEFT }, { _T( "NUMLOCK" ), VK_NUMLOCK }, { _T( "PGDN" ), VK_NEXT }, { _T( "PGUP" ), VK_PRIOR }, { _T( "PRTSC" ), VK_SNAPSHOT }, { _T( "RIGHT" ), VK_RIGHT }, { _T( "SCROLLLOCK" ), VK_SCROLL }, { _T( "TAB" ), VK_TAB }, { _T( "UP" ), VK_UP }, { _T( "F1" ), VK_F1 }, { _T( "F2" ), VK_F2 }, { _T( "F3" ), VK_F3 }, { _T( "F4" ), VK_F4 }, { _T( "F5" ), VK_F5 }, { _T( "F6" ), VK_F6 }, { _T( "F7" ), VK_F7 }, { _T( "F8" ), VK_F8 }, { _T( "F9" ), VK_F9 }, { _T( "F10" ), VK_F10 }, { _T( "F11" ), VK_F11 }, { _T( "F12" ), VK_F12 }, { _T( "F13" ), VK_F13 }, { _T( "F14" ), VK_F14 }, { _T( "F15" ), VK_F15 }, { _T( "F16" ), VK_F16 }, { _T( "+" ), VK_ADD } }, *_end_charnames = _charnames + ( sizeof(_charnames ) / sizeof( cCharNames ) ); #define MASK_SHIFT 0x0100 #define MASK_CTRL 0x0200 #define MASK_ALT 0x0400 #define SHIFT_MASK 0x0700 #define VKEY_MASK 0x00FF void pushVirtualKey( WORD vk, cInputVec &vec, bool bDown ) { INPUT i; ::memset( &i, 0, sizeof( INPUT ) ); i.type = INPUT_KEYBOARD; i.ki.wVk = vk; i.ki.wScan = MapVirtualKey( vk, 0 ); i.ki.dwFlags = ( bDown ) ? 0 : KEYEVENTF_KEYUP; vec.push_back( i ); } void processKeyboardChars( BSTR strChars, cInputVec &vec ) { USES_CONVERSION; typedef std::stack< WORD > cMaskStack; cMaskStack mask; WORD wCurrentMask = 0; std::string strParse = OLE2T( strChars ); for( std::string::iterator i = strParse.begin(); i != strParse.end(); ) { switch( *i ) { case _T( '+' ): _ASSERTE( ( i + 2 ) < strParse.end() ); _ASSERTE( *( i + 1 ) == _T( '(' ) ); _ASSERTE( !( wCurrentMask & MASK_SHIFT ) ); wCurrentMask |= MASK_SHIFT; mask.push( MASK_SHIFT ); // Send the keydown pushVirtualKey( VK_SHIFT, vec, true ); i += 2; break; case _T( '^' ): _ASSERTE( ( i + 2 ) < strParse.end() ); _ASSERTE( *( i + 1 ) == _T( '(' ) ); _ASSERTE( !( wCurrentMask & MASK_CTRL ) ); wCurrentMask |= MASK_CTRL; mask.push( MASK_CTRL ); // Send the keydown pushVirtualKey( VK_CONTROL, vec, true ); i += 2; break; case _T( '%' ): _ASSERTE( ( i + 2 ) < strParse.end() ); _ASSERTE( *( i + 1 ) == _T( '(' ) ); _ASSERTE( !( wCurrentMask & MASK_ALT ) ); wCurrentMask |= MASK_ALT; mask.push( MASK_ALT ); // Send the keydown pushVirtualKey( VK_MENU, vec, true ); i += 2; break; case _T( ')' ): _ASSERTE( mask.size() > 0 ); { DWORD wMask = mask.top(); mask.pop(); switch( wMask ) { case MASK_SHIFT: pushVirtualKey( VK_SHIFT, vec, false ); break; case MASK_CTRL: pushVirtualKey( VK_CONTROL, vec, false ); break; case MASK_ALT: pushVirtualKey( VK_MENU, vec, false ); break; default: // Bad value in the stack _ASSERTE( FALSE ); break; } wCurrentMask &= ~wMask; } ++ i; break; case _T( '{' ): // This is a special key ++ i; { for( std::string::iterator j = i; *j != _T( '}' ); ++ j ) { _ASSERTE( j != strParse.end() ); } std::string strSpecial( i, j ); for( cCharNames *i_name = _charnames; i_name != _end_charnames; ++ i_name ) { if( strSpecial.compare( i_name->szName ) == 0 ) { // We found the match, push and release the vkey pushVirtualKey( i_name->m_nVKey, vec, true ); pushVirtualKey( i_name->m_nVKey, vec, false ); break; } } // The special key was not found in the list _ASSERTE( i_name != _end_charnames ); i = j + 1; } break; default: // Assume everything else is a text character but may require additional // shifting { WORD wVkScan = VkKeyScan( *i ), wAdditionalShift = ( wVkScan & SHIFT_MASK ) & ~wCurrentMask, wVKey = wVkScan & VKEY_MASK; if( wAdditionalShift & MASK_SHIFT ) pushVirtualKey( VK_SHIFT, vec, true ); if( wAdditionalShift & MASK_CTRL ) pushVirtualKey( VK_CONTROL, vec, true ); if( wAdditionalShift & MASK_ALT ) pushVirtualKey( VK_MENU, vec, true ); pushVirtualKey( wVKey, vec, true ); pushVirtualKey( wVKey, vec, false ); if( wAdditionalShift & MASK_ALT ) pushVirtualKey( VK_MENU, vec, false ); if( wAdditionalShift & MASK_CTRL ) pushVirtualKey( VK_CONTROL, vec, false ); if( wAdditionalShift & MASK_SHIFT ) pushVirtualKey( VK_SHIFT, vec, false ); } ++ i; break; } } // Make sure we released all shifts _ASSERTE( wCurrentMask == 0 ); } void pushMouseEvent( DWORD dwFlags, cInputVec &vec ) { INPUT i; ::memset( &i, 0, sizeof( INPUT ) ); i.type = INPUT_MOUSE; i.mi.dwFlags = dwFlags; vec.push_back( i ); } void processMouseInput( eMouseInput eType, cInputVec &vec ) { DWORD dwError = ::GetLastError(); switch( eType ) { case eMouseLeftDoubleClick: pushMouseEvent( MOUSEEVENTF_LEFTDOWN, vec ); pushMouseEvent( MOUSEEVENTF_LEFTUP, vec ); case eMouseLeftClick: pushMouseEvent( MOUSEEVENTF_LEFTDOWN, vec ); pushMouseEvent( MOUSEEVENTF_LEFTUP, vec ); break; case eMouseRightDoubleClick: pushMouseEvent( MOUSEEVENTF_RIGHTDOWN, vec ); pushMouseEvent( MOUSEEVENTF_RIGHTUP, vec ); case eMouseRightClick: pushMouseEvent( MOUSEEVENTF_RIGHTDOWN, vec ); pushMouseEvent( MOUSEEVENTF_RIGHTUP, vec ); break; default: _ASSERTE( FALSE ); break; } } HANDLE cInputBuffer::m_hThread = NULL; HANDLE cInputBuffer::m_hInputWaiting = NULL; HANDLE cInputBuffer::m_hTerm = NULL; CRITICAL_SECTION cInputBuffer::m_csQueue; cInputBuffer::cInputQueue cInputBuffer::m_queue; DWORD WINAPI inputThreadProc( LPVOID pvParam ); void cInputBuffer::init() { ::InitializeCriticalSection( &m_csQueue ); m_hTerm = ::CreateEvent( NULL, TRUE, FALSE, NULL ); m_hInputWaiting = ::CreateEvent( NULL, TRUE, FALSE, NULL ); DWORD dwThreadID; m_hThread = ::CreateThread( NULL, 0, inputThreadProc, NULL, 0, &dwThreadID ); } void cInputBuffer::term() { // Clear out the queue ::EnterCriticalSection( &m_csQueue ); while( m_queue.size() > 0 ) m_queue.pop_front(); ::SetEvent( m_hTerm ); ::LeaveCriticalSection( &m_csQueue ); ::WaitForSingleObject( m_hThread, INFINITE ); ::CloseHandle( m_hThread ); ::CloseHandle( m_hInputWaiting ); ::CloseHandle( m_hTerm ); ::DeleteCriticalSection( &m_csQueue ); } void cInputBuffer::postBuffer() { ::EnterCriticalSection( &m_csQueue ); if( m_queue.empty() ) ::SetEvent( m_hInputWaiting ); // Marshal our interface into a drop in in the queue IStream *pStream; ::CoMarshalInterThreadInterfaceInStream( IID_IInputNotify, static_cast< IInputNotify * >( this ), &pStream ); m_queue.push_back( cInputQueue::value_type( this, pStream ) ); m_status = eInputWaiting; ::LeaveCriticalSection( &m_csQueue ); } cInputVec *cInputBuffer::getLastInput() { if( m_actions.size() == 0 || m_actions.back().first != eActionInput ) // Add in a new buffer m_actions.push_back( cActionList::value_type( eActionInput, VSBridge::auto_ptr< cAction >( new cInput ) ) ); // Return the last buffer return &( static_cast< cInput * >( m_actions.back().second.get() )->m_input ); } DWORD WINAPI inputThreadProc( LPVOID pvParam ) { // This thread must join the mult-threaded apartment becasue it does // not create a message loop ::CoInitializeEx( NULL, COINIT_MULTITHREADED ); BYTE prevKeyboard[ 256 ], blankKeyboard[ 256 ]; ::memset( blankKeyboard, 0, sizeof( blankKeyboard ) ); HANDLE hEvent[2] = { cInputBuffer::m_hInputWaiting, cInputBuffer::m_hTerm }; while( ::WaitForMultipleObjects( 2, hEvent, FALSE, INFINITE ) == WAIT_OBJECT_0 ) { // Extract the next item from the stream ::EnterCriticalSection( &cInputBuffer::m_csQueue ); cInputBuffer *pBuffer = cInputBuffer::m_queue.front().first; CComPtr< IInputNotify > pNotify; ::CoGetInterfaceAndReleaseStream( cInputBuffer::m_queue.front().second, IID_IInputNotify, reinterpret_cast< void ** >( &pNotify ) ); cInputBuffer::m_queue.pop_front(); if( cInputBuffer::m_queue.empty() ) ::ResetEvent( cInputBuffer::m_hInputWaiting ); ::LeaveCriticalSection( &cInputBuffer::m_csQueue ); // Save the keyboard state and take control of the input queue ::BlockInput( TRUE ); ::GetKeyboardState( prevKeyboard ); ::SetKeyboardState( blankKeyboard ); // Looking good, process the messages pNotify->NotifyBegin(); // The delay list contains the breaks in the input, so we use the delay list as a master // and use segments of the input queue for( cInputBuffer::cActionList::iterator i = pBuffer->m_actions.begin(); i != pBuffer->m_actions.end(); ++ i ) i->second->execute( pNotify ); // Restore keyboard state ::SetKeyboardState( prevKeyboard ); pNotify->NotifyEnd(); ::BlockInput( FALSE ); } ::CoUninitialize(); return 0; } // The action execution functions void cInputBuffer::cInput::execute( IInputNotify * ) { UINT nSent = SendInput( m_input.size(), &(*(m_input.begin())), sizeof( INPUT ) ); DWORD dwError = GetLastError(); _ASSERTE( nSent == m_input.size() ); } void cInputBuffer::cDelay::execute( IInputNotify *pNotify ) { if( m_bAllowInput ) { BlockInput( FALSE ); pNotify->NotifyPause(); } ::Sleep( m_nMillis ); if( m_bAllowInput ) { BlockInput( TRUE ); pNotify->NotifyBegin(); } } void cInputBuffer::cMouseMove::execute( IInputNotify *pib ) { pib->SetMousePos( m_ptClient.x, m_ptClient.y ); } STDMETHODIMP cInputBuffer::Clear() { if( m_status != eInputIdle ) { _ASSERTE( FALSE ); return E_FAIL; } m_actions.clear(); return S_OK; } STDMETHODIMP cInputBuffer::TypeText(BSTR strText) { if( m_status != eInputIdle ) { _ASSERTE( FALSE ); return E_FAIL; } processKeyboardChars( strText, *getLastInput() ); return S_OK; } STDMETHODIMP cInputBuffer::Delay(long nMilliseconds, VARIANT_BOOL bAllowInput ) { if( m_status != eInputIdle ) { _ASSERTE( FALSE ); return E_FAIL; } cDelay *pDelay = new cDelay; pDelay->m_bAllowInput = !!bAllowInput; pDelay->m_nMillis = nMilliseconds; m_actions.push_back( cActionList::value_type( eActionDelay, VSBridge::auto_ptr< cAction >( pDelay ) ) ); return S_OK; } STDMETHODIMP cInputBuffer::MouseClick(long nX, long nY, eMouseInput eAction) { if( m_status != eInputIdle ) { _ASSERTE( FALSE ); return E_FAIL; } // First insert a mouse movement cMouseMove *pMM = new cMouseMove; pMM->m_ptClient.x = nX; pMM->m_ptClient.y = nY; m_actions.push_back( cActionList::value_type( eActionMouseMove, VSBridge::auto_ptr< cAction >( pMM ) ) ); processMouseInput( eAction, *getLastInput() ); return S_OK; } STDMETHODIMP cInputBuffer::get_Status(eInputStatus *pVal) { _ASSERTE( pVal != NULL ); *pVal = m_status; return S_OK; } STDMETHODIMP cInputBuffer::Run() { postBuffer(); return S_OK; } STDMETHODIMP cInputBuffer::NotifyBegin() { m_status = eInputRunning; Fire_Begin(); return S_OK; } STDMETHODIMP cInputBuffer::NotifyEnd() { m_status = eInputIdle; Fire_End(); return S_OK; } STDMETHODIMP cInputBuffer::SetMousePos(long nX, long nY) { // Convert to screen coords POINT pt = { nX, nY }; ::ClientToScreen( cManager::_p->m_hMain, &pt ); ::SetCursorPos( pt.x, pt.y ); // Force the mouse message on the into the window proc // This updates AC to be the new message location cManager::_p->m_pfnOld( cManager::_p->m_hMain, WM_MOUSEMOVE, 0, MAKELONG( nX, nY ) ); return S_OK; } STDMETHODIMP cInputBuffer::NotifyPause() { Fire_Pause(); return S_OK; }