Team Fortress 2 Source Code as on 22/4/2020
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

644 lines
13 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //
  7. //=============================================================================//
  8. // CTextConsoleWin32.cpp: Win32 implementation of the TextConsole class.
  9. //
  10. //////////////////////////////////////////////////////////////////////
  11. #include "TextConsoleWin32.h"
  12. #include "tier0/dbg.h"
  13. #include "utlvector.h"
  14. // Could possibly switch all this code over to using readline. This:
  15. // http://mingweditline.sourceforge.net/?Description
  16. // readline() / add_history(char *)
  17. #ifdef _WIN32
  18. BOOL WINAPI ConsoleHandlerRoutine( DWORD CtrlType )
  19. {
  20. NOTE_UNUSED( CtrlType );
  21. /* TODO ?
  22. if ( CtrlType != CTRL_C_EVENT && CtrlType != CTRL_BREAK_EVENT )
  23. m_System->Stop(); // don't quit on break or ctrl+c
  24. */
  25. return TRUE;
  26. }
  27. // GetConsoleHwnd() helper function from MSDN Knowledge Base Article Q124103
  28. // needed, because HWND GetConsoleWindow(VOID) is not avaliable under Win95/98/ME
  29. HWND GetConsoleHwnd(void)
  30. {
  31. typedef HWND (WINAPI *PFNGETCONSOLEWINDOW)( VOID );
  32. static PFNGETCONSOLEWINDOW s_pfnGetConsoleWindow = (PFNGETCONSOLEWINDOW) GetProcAddress( GetModuleHandle("kernel32"), "GetConsoleWindow" );
  33. if ( s_pfnGetConsoleWindow )
  34. return s_pfnGetConsoleWindow();
  35. HWND hwndFound; // This is what is returned to the caller.
  36. char pszNewWindowTitle[1024]; // Contains fabricated WindowTitle
  37. char pszOldWindowTitle[1024]; // Contains original WindowTitle
  38. // Fetch current window title.
  39. GetConsoleTitle( pszOldWindowTitle, 1024 );
  40. // Format a "unique" NewWindowTitle.
  41. wsprintf( pszNewWindowTitle,"%d/%d", GetTickCount(), GetCurrentProcessId() );
  42. // Change current window title.
  43. SetConsoleTitle(pszNewWindowTitle);
  44. // Ensure window title has been updated.
  45. Sleep(40);
  46. // Look for NewWindowTitle.
  47. hwndFound = FindWindow( NULL, pszNewWindowTitle );
  48. // Restore original window title.
  49. SetConsoleTitle( pszOldWindowTitle );
  50. return hwndFound;
  51. }
  52. CTextConsoleWin32::CTextConsoleWin32()
  53. {
  54. hinput = NULL;
  55. houtput = NULL;
  56. Attrib = 0;
  57. statusline[0] = '\0';
  58. }
  59. bool CTextConsoleWin32::Init()
  60. {
  61. (void) AllocConsole();
  62. SetTitle( "SOURCE DEDICATED SERVER" );
  63. hinput = GetStdHandle ( STD_INPUT_HANDLE );
  64. houtput = GetStdHandle ( STD_OUTPUT_HANDLE );
  65. if ( !SetConsoleCtrlHandler( &ConsoleHandlerRoutine, TRUE) )
  66. {
  67. Print( "WARNING! TextConsole::Init: Could not attach console hook.\n" );
  68. }
  69. Attrib = FOREGROUND_GREEN | FOREGROUND_INTENSITY | BACKGROUND_INTENSITY ;
  70. SetWindowPos( GetConsoleHwnd(), HWND_TOP, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_SHOWWINDOW );
  71. memset( m_szConsoleText, 0, sizeof( m_szConsoleText ) );
  72. m_nConsoleTextLen = 0;
  73. m_nCursorPosition = 0;
  74. memset( m_szSavedConsoleText, 0, sizeof( m_szSavedConsoleText ) );
  75. m_nSavedConsoleTextLen = 0;
  76. memset( m_aszLineBuffer, 0, sizeof( m_aszLineBuffer ) );
  77. m_nTotalLines = 0;
  78. m_nInputLine = 0;
  79. m_nBrowseLine = 0;
  80. // these are log messages, not related to console
  81. Msg( "\n" );
  82. Msg( "Console initialized.\n" );
  83. return CTextConsole::Init();
  84. }
  85. void CTextConsoleWin32::ShutDown( void )
  86. {
  87. FreeConsole();
  88. }
  89. void CTextConsoleWin32::SetVisible( bool visible )
  90. {
  91. ShowWindow ( GetConsoleHwnd(), visible ? SW_SHOW : SW_HIDE );
  92. m_ConsoleVisible = visible;
  93. }
  94. char * CTextConsoleWin32::GetLine( int index, char *buf, int buflen )
  95. {
  96. while ( 1 )
  97. {
  98. INPUT_RECORD recs[ 1024 ];
  99. unsigned long numread;
  100. unsigned long numevents;
  101. if ( !GetNumberOfConsoleInputEvents( hinput, &numevents ) )
  102. {
  103. Error("CTextConsoleWin32::GetLine: !GetNumberOfConsoleInputEvents");
  104. return NULL;
  105. }
  106. if ( numevents <= 0 )
  107. break;
  108. if ( !ReadConsoleInput( hinput, recs, ARRAYSIZE( recs ), &numread ) )
  109. {
  110. Error("CTextConsoleWin32::GetLine: !ReadConsoleInput");
  111. return NULL;
  112. }
  113. if ( numread == 0 )
  114. return NULL;
  115. for ( int i=0; i < (int)numread; i++ )
  116. {
  117. INPUT_RECORD *pRec = &recs[i];
  118. if ( pRec->EventType != KEY_EVENT )
  119. continue;
  120. if ( pRec->Event.KeyEvent.bKeyDown )
  121. {
  122. // check for cursor keys
  123. if ( pRec->Event.KeyEvent.wVirtualKeyCode == VK_UP )
  124. {
  125. ReceiveUpArrow();
  126. }
  127. else if ( pRec->Event.KeyEvent.wVirtualKeyCode == VK_DOWN )
  128. {
  129. ReceiveDownArrow();
  130. }
  131. else if ( pRec->Event.KeyEvent.wVirtualKeyCode == VK_LEFT )
  132. {
  133. ReceiveLeftArrow();
  134. }
  135. else if ( pRec->Event.KeyEvent.wVirtualKeyCode == VK_RIGHT )
  136. {
  137. ReceiveRightArrow();
  138. }
  139. else
  140. {
  141. char ch;
  142. int nLen;
  143. ch = pRec->Event.KeyEvent.uChar.AsciiChar;
  144. switch ( ch )
  145. {
  146. case '\r': // Enter
  147. nLen = ReceiveNewline();
  148. if ( nLen )
  149. {
  150. strncpy( buf, m_szConsoleText, buflen );
  151. buf[ buflen - 1 ] = 0;
  152. return buf;
  153. }
  154. break;
  155. case '\b': // Backspace
  156. ReceiveBackspace();
  157. break;
  158. case '\t': // TAB
  159. ReceiveTab();
  160. break;
  161. default:
  162. if ( ( ch >= ' ') && ( ch <= '~' ) ) // dont' accept nonprintable chars
  163. {
  164. ReceiveStandardChar( ch );
  165. }
  166. break;
  167. }
  168. }
  169. }
  170. }
  171. }
  172. return NULL;
  173. }
  174. void CTextConsoleWin32::Print( char * pszMsg )
  175. {
  176. if ( m_nConsoleTextLen )
  177. {
  178. int nLen;
  179. nLen = m_nConsoleTextLen;
  180. while ( nLen-- )
  181. {
  182. PrintRaw( "\b \b" );
  183. }
  184. }
  185. PrintRaw( pszMsg );
  186. if ( m_nConsoleTextLen )
  187. {
  188. PrintRaw( m_szConsoleText, m_nConsoleTextLen );
  189. }
  190. UpdateStatus();
  191. }
  192. void CTextConsoleWin32::PrintRaw( const char * pszMsg, int nChars )
  193. {
  194. unsigned long dummy;
  195. if ( houtput == NULL )
  196. {
  197. houtput = GetStdHandle ( STD_OUTPUT_HANDLE );
  198. if ( houtput == NULL )
  199. return;
  200. }
  201. if ( nChars <= 0 )
  202. {
  203. nChars = strlen( pszMsg );
  204. if ( nChars <= 0 )
  205. return;
  206. }
  207. // filter out ASCII BEL characters because windows actually plays a
  208. // bell sound, which can be used to lag the server in a DOS attack.
  209. char * pTempBuf = NULL;
  210. for ( int i = 0; i < nChars; ++i )
  211. {
  212. if ( pszMsg[i] == 0x07 /*BEL*/ )
  213. {
  214. if ( !pTempBuf )
  215. {
  216. pTempBuf = ( char * ) malloc( nChars );
  217. memcpy( pTempBuf, pszMsg, nChars );
  218. }
  219. pTempBuf[i] = '.';
  220. }
  221. }
  222. WriteFile( houtput, pTempBuf ? pTempBuf : pszMsg, nChars, &dummy, NULL );
  223. free( pTempBuf ); // usually NULL
  224. }
  225. int CTextConsoleWin32::GetWidth( void )
  226. {
  227. CONSOLE_SCREEN_BUFFER_INFO csbi;
  228. int nWidth;
  229. nWidth = 0;
  230. if ( GetConsoleScreenBufferInfo( houtput, &csbi ) )
  231. {
  232. nWidth = csbi.dwSize.X;
  233. }
  234. if ( nWidth <= 1 )
  235. nWidth = 80;
  236. return nWidth;
  237. }
  238. void CTextConsoleWin32::SetStatusLine( char * pszStatus )
  239. {
  240. strncpy( statusline, pszStatus, 80 );
  241. statusline[ 79 ] = '\0';
  242. UpdateStatus();
  243. }
  244. void CTextConsoleWin32::UpdateStatus( void )
  245. {
  246. COORD coord;
  247. DWORD dwWritten = 0;
  248. WORD wAttrib[ 80 ];
  249. for ( int i = 0; i < 80; i++ )
  250. {
  251. wAttrib[i] = Attrib; //FOREGROUND_GREEN | FOREGROUND_INTENSITY | BACKGROUND_INTENSITY ;
  252. }
  253. coord.X = coord.Y = 0;
  254. WriteConsoleOutputAttribute( houtput, wAttrib, 80, coord, &dwWritten );
  255. WriteConsoleOutputCharacter( houtput, statusline, 80, coord, &dwWritten );
  256. }
  257. void CTextConsoleWin32::SetTitle( char * pszTitle )
  258. {
  259. SetConsoleTitle( pszTitle );
  260. }
  261. void CTextConsoleWin32::SetColor(WORD attrib)
  262. {
  263. Attrib = attrib;
  264. }
  265. int CTextConsoleWin32::ReceiveNewline( void )
  266. {
  267. int nLen = 0;
  268. PrintRaw( "\n" );
  269. if ( m_nConsoleTextLen )
  270. {
  271. nLen = m_nConsoleTextLen;
  272. m_szConsoleText[ m_nConsoleTextLen ] = 0;
  273. m_nConsoleTextLen = 0;
  274. m_nCursorPosition = 0;
  275. // cache line in buffer, but only if it's not a duplicate of the previous line
  276. if ( ( m_nInputLine == 0 ) || ( strcmp( m_aszLineBuffer[ m_nInputLine - 1 ], m_szConsoleText ) ) )
  277. {
  278. strncpy( m_aszLineBuffer[ m_nInputLine ], m_szConsoleText, MAX_CONSOLE_TEXTLEN );
  279. m_nInputLine++;
  280. if ( m_nInputLine > m_nTotalLines )
  281. m_nTotalLines = m_nInputLine;
  282. if ( m_nInputLine >= MAX_BUFFER_LINES )
  283. m_nInputLine = 0;
  284. }
  285. m_nBrowseLine = m_nInputLine;
  286. }
  287. return nLen;
  288. }
  289. void CTextConsoleWin32::ReceiveBackspace( void )
  290. {
  291. int nCount;
  292. if ( m_nCursorPosition == 0 )
  293. {
  294. return;
  295. }
  296. m_nConsoleTextLen--;
  297. m_nCursorPosition--;
  298. PrintRaw( "\b" );
  299. for ( nCount = m_nCursorPosition; nCount < m_nConsoleTextLen; nCount++ )
  300. {
  301. m_szConsoleText[ nCount ] = m_szConsoleText[ nCount + 1 ];
  302. PrintRaw( m_szConsoleText + nCount, 1 );
  303. }
  304. PrintRaw( " " );
  305. nCount = m_nConsoleTextLen;
  306. while ( nCount >= m_nCursorPosition )
  307. {
  308. PrintRaw( "\b" );
  309. nCount--;
  310. }
  311. m_nBrowseLine = m_nInputLine;
  312. }
  313. void CTextConsoleWin32::ReceiveTab( void )
  314. {
  315. CUtlVector<char *> matches;
  316. m_szConsoleText[ m_nConsoleTextLen ] = 0;
  317. if ( matches.Count() == 0 )
  318. {
  319. return;
  320. }
  321. if ( matches.Count() == 1 )
  322. {
  323. char * pszCmdName;
  324. char * pszRest;
  325. pszCmdName = matches[0];
  326. pszRest = pszCmdName + strlen( m_szConsoleText );
  327. if ( pszRest )
  328. {
  329. PrintRaw( pszRest );
  330. strcat( m_szConsoleText, pszRest );
  331. m_nConsoleTextLen += strlen( pszRest );
  332. PrintRaw( " " );
  333. strcat( m_szConsoleText, " " );
  334. m_nConsoleTextLen++;
  335. }
  336. }
  337. else
  338. {
  339. int nLongestCmd;
  340. int nTotalColumns;
  341. int nCurrentColumn;
  342. char * pszCurrentCmd;
  343. int i = 0;
  344. nLongestCmd = 0;
  345. pszCurrentCmd = matches[0];
  346. while ( pszCurrentCmd )
  347. {
  348. if ( (int)strlen( pszCurrentCmd) > nLongestCmd )
  349. {
  350. nLongestCmd = strlen( pszCurrentCmd);
  351. }
  352. i++;
  353. pszCurrentCmd = (char *)matches[i];
  354. }
  355. nTotalColumns = ( GetWidth() - 1 ) / ( nLongestCmd + 1 );
  356. nCurrentColumn = 0;
  357. PrintRaw( "\n" );
  358. // Would be nice if these were sorted, but not that big a deal
  359. pszCurrentCmd = matches[0];
  360. i = 0;
  361. while ( pszCurrentCmd )
  362. {
  363. char szFormatCmd[ 256 ];
  364. nCurrentColumn++;
  365. if ( nCurrentColumn > nTotalColumns )
  366. {
  367. PrintRaw( "\n" );
  368. nCurrentColumn = 1;
  369. }
  370. Q_snprintf( szFormatCmd, sizeof(szFormatCmd), "%-*s ", nLongestCmd, pszCurrentCmd );
  371. PrintRaw( szFormatCmd );
  372. i++;
  373. pszCurrentCmd = matches[i];
  374. }
  375. PrintRaw( "\n" );
  376. PrintRaw( m_szConsoleText );
  377. // TODO: Tack on 'common' chars in all the matches, i.e. if I typed 'con' and all the
  378. // matches begin with 'connect_' then print the matches but also complete the
  379. // command up to that point at least.
  380. }
  381. m_nCursorPosition = m_nConsoleTextLen;
  382. m_nBrowseLine = m_nInputLine;
  383. }
  384. void CTextConsoleWin32::ReceiveStandardChar( const char ch )
  385. {
  386. int nCount;
  387. // If the line buffer is maxed out, ignore this char
  388. if ( m_nConsoleTextLen >= ( sizeof( m_szConsoleText ) - 2 ) )
  389. {
  390. return;
  391. }
  392. nCount = m_nConsoleTextLen;
  393. while ( nCount > m_nCursorPosition )
  394. {
  395. m_szConsoleText[ nCount ] = m_szConsoleText[ nCount - 1 ];
  396. nCount--;
  397. }
  398. m_szConsoleText[ m_nCursorPosition ] = ch;
  399. PrintRaw( m_szConsoleText + m_nCursorPosition, m_nConsoleTextLen - m_nCursorPosition + 1 );
  400. m_nConsoleTextLen++;
  401. m_nCursorPosition++;
  402. nCount = m_nConsoleTextLen;
  403. while ( nCount > m_nCursorPosition )
  404. {
  405. PrintRaw( "\b" );
  406. nCount--;
  407. }
  408. m_nBrowseLine = m_nInputLine;
  409. }
  410. void CTextConsoleWin32::ReceiveUpArrow( void )
  411. {
  412. int nLastCommandInHistory;
  413. nLastCommandInHistory = m_nInputLine + 1;
  414. if ( nLastCommandInHistory > m_nTotalLines )
  415. {
  416. nLastCommandInHistory = 0;
  417. }
  418. if ( m_nBrowseLine == nLastCommandInHistory )
  419. {
  420. return;
  421. }
  422. if ( m_nBrowseLine == m_nInputLine )
  423. {
  424. if ( m_nConsoleTextLen > 0 )
  425. {
  426. // Save off current text
  427. strncpy( m_szSavedConsoleText, m_szConsoleText, m_nConsoleTextLen );
  428. // No terminator, it's a raw buffer we always know the length of
  429. }
  430. m_nSavedConsoleTextLen = m_nConsoleTextLen;
  431. }
  432. m_nBrowseLine--;
  433. if ( m_nBrowseLine < 0 )
  434. {
  435. m_nBrowseLine = m_nTotalLines - 1;
  436. }
  437. while ( m_nConsoleTextLen-- ) // delete old line
  438. {
  439. PrintRaw( "\b \b" );
  440. }
  441. // copy buffered line
  442. PrintRaw( m_aszLineBuffer[ m_nBrowseLine ] );
  443. strncpy( m_szConsoleText, m_aszLineBuffer[ m_nBrowseLine ], MAX_CONSOLE_TEXTLEN );
  444. m_nConsoleTextLen = strlen( m_aszLineBuffer[ m_nBrowseLine ] );
  445. m_nCursorPosition = m_nConsoleTextLen;
  446. }
  447. void CTextConsoleWin32::ReceiveDownArrow( void )
  448. {
  449. if ( m_nBrowseLine == m_nInputLine )
  450. {
  451. return;
  452. }
  453. m_nBrowseLine++;
  454. if ( m_nBrowseLine > m_nTotalLines )
  455. {
  456. m_nBrowseLine = 0;
  457. }
  458. while ( m_nConsoleTextLen-- ) // delete old line
  459. {
  460. PrintRaw( "\b \b" );
  461. }
  462. if ( m_nBrowseLine == m_nInputLine )
  463. {
  464. if ( m_nSavedConsoleTextLen > 0 )
  465. {
  466. // Restore current text
  467. strncpy( m_szConsoleText, m_szSavedConsoleText, m_nSavedConsoleTextLen );
  468. // No terminator, it's a raw buffer we always know the length of
  469. PrintRaw( m_szConsoleText, m_nSavedConsoleTextLen );
  470. }
  471. m_nConsoleTextLen = m_nSavedConsoleTextLen;
  472. }
  473. else
  474. {
  475. // copy buffered line
  476. PrintRaw( m_aszLineBuffer[ m_nBrowseLine ] );
  477. strncpy( m_szConsoleText, m_aszLineBuffer[ m_nBrowseLine ], MAX_CONSOLE_TEXTLEN );
  478. m_nConsoleTextLen = strlen( m_aszLineBuffer[ m_nBrowseLine ] );
  479. }
  480. m_nCursorPosition = m_nConsoleTextLen;
  481. }
  482. void CTextConsoleWin32::ReceiveLeftArrow( void )
  483. {
  484. if ( m_nCursorPosition == 0 )
  485. {
  486. return;
  487. }
  488. PrintRaw( "\b" );
  489. m_nCursorPosition--;
  490. }
  491. void CTextConsoleWin32::ReceiveRightArrow( void )
  492. {
  493. if ( m_nCursorPosition == m_nConsoleTextLen )
  494. {
  495. return;
  496. }
  497. PrintRaw( m_szConsoleText + m_nCursorPosition, 1 );
  498. m_nCursorPosition++;
  499. }
  500. #endif // _WIN32