/* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. Copyright (C) 2000-2013 Darklegion Development This file is part of Tremulous. Tremulous is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Tremulous; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ #include "../qcommon/q_shared.h" #include "../qcommon/qcommon.h" #include "sys_local.h" #include "windows.h" #define QCONSOLE_HISTORY 32 static WORD qconsole_attrib; static WORD qconsole_backgroundAttrib; // saved console status static DWORD qconsole_orig_mode; static CONSOLE_CURSOR_INFO qconsole_orig_cursorinfo; // cmd history static char qconsole_history[ QCONSOLE_HISTORY ][ MAX_EDIT_LINE ]; static int qconsole_history_pos = -1; static int qconsole_history_lines = 0; static int qconsole_history_oldest = 0; // current edit buffer static char qconsole_line[ MAX_EDIT_LINE ]; static int qconsole_linelen = 0; static qboolean qconsole_drawinput = qtrue; static int qconsole_cursor; static HANDLE qconsole_hout = INVALID_HANDLE_VALUE; static HANDLE qconsole_hin = INVALID_HANDLE_VALUE; /* ================== CON_ColorCharToAttrib Convert Quake color character to Windows text attrib ================== */ static WORD CON_ColorCharToAttrib( char color ) { WORD attrib; if ( color == COLOR_WHITE ) { // use console's foreground and background colors attrib = qconsole_attrib; } else { float *rgba = g_color_table[ ColorIndex( color ) ]; // set foreground color attrib = ( rgba[0] >= 0.5 ? FOREGROUND_RED : 0 ) | ( rgba[1] >= 0.5 ? FOREGROUND_GREEN : 0 ) | ( rgba[2] >= 0.5 ? FOREGROUND_BLUE : 0 ) | ( rgba[3] >= 0.5 ? FOREGROUND_INTENSITY : 0 ); // use console's background color attrib |= qconsole_backgroundAttrib; } return attrib; } /* ================== CON_CtrlHandler The Windows Console doesn't use signals for terminating the application with Ctrl-C, logging off, window closing, etc. Instead it uses a special handler routine. Fortunately, the values for Ctrl signals don't seem to overlap with true signal codes that Windows provides, so calling Sys_SigHandler() with those numbers should be safe for generating unique shutdown messages. ================== */ static BOOL WINAPI CON_CtrlHandler( DWORD sig ) { Sys_SigHandler( sig ); return TRUE; } /* ================== CON_HistAdd ================== */ static void CON_HistAdd( void ) { Q_strncpyz( qconsole_history[ qconsole_history_oldest ], qconsole_line, sizeof( qconsole_history[ qconsole_history_oldest ] ) ); if( qconsole_history_lines < QCONSOLE_HISTORY ) qconsole_history_lines++; if( qconsole_history_oldest >= QCONSOLE_HISTORY - 1 ) qconsole_history_oldest = 0; else qconsole_history_oldest++; qconsole_history_pos = qconsole_history_oldest; } /* ================== CON_HistPrev ================== */ static void CON_HistPrev( void ) { int pos; pos = ( qconsole_history_pos < 1 ) ? ( QCONSOLE_HISTORY - 1 ) : ( qconsole_history_pos - 1 ); // don' t allow looping through history if( pos == qconsole_history_oldest || pos >= qconsole_history_lines ) return; qconsole_history_pos = pos; Q_strncpyz( qconsole_line, qconsole_history[ qconsole_history_pos ], sizeof( qconsole_line ) ); qconsole_linelen = strlen( qconsole_line ); qconsole_cursor = qconsole_linelen; } /* ================== CON_HistNext ================== */ static void CON_HistNext( void ) { int pos; // don' t allow looping through history if( qconsole_history_pos == qconsole_history_oldest ) return; pos = ( qconsole_history_pos >= QCONSOLE_HISTORY - 1 ) ? 0 : ( qconsole_history_pos + 1 ); // clear the edit buffer if they try to advance to a future command if( pos == qconsole_history_oldest ) { qconsole_history_pos = pos; qconsole_line[ 0 ] = '\0'; qconsole_linelen = 0; qconsole_cursor = qconsole_linelen; return; } qconsole_history_pos = pos; Q_strncpyz( qconsole_line, qconsole_history[ qconsole_history_pos ], sizeof( qconsole_line ) ); qconsole_linelen = strlen( qconsole_line ); qconsole_cursor = qconsole_linelen; } /* ================== CON_Show ================== */ static void CON_Show( void ) { CONSOLE_SCREEN_BUFFER_INFO binfo; COORD writeSize = { MAX_EDIT_LINE, 1 }; COORD writePos = { 0, 0 }; SMALL_RECT writeArea = { 0, 0, 0, 0 }; COORD cursorPos; int i; CHAR_INFO line[ MAX_EDIT_LINE ]; WORD attrib; if( qconsole_hout == INVALID_HANDLE_VALUE ) return; GetConsoleScreenBufferInfo( qconsole_hout, &binfo ); // if we're in the middle of printf, don't bother writing the buffer if( !qconsole_drawinput ) return; writeArea.Left = 0; writeArea.Top = binfo.dwCursorPosition.Y; writeArea.Bottom = binfo.dwCursorPosition.Y; writeArea.Right = MAX_EDIT_LINE; // set color to white attrib = CON_ColorCharToAttrib( COLOR_WHITE ); // build a space-padded CHAR_INFO array for( i = 0; i < MAX_EDIT_LINE; i++ ) { if( i < qconsole_linelen ) { if( i + 1 < qconsole_linelen && Q_IsColorString( qconsole_line + i ) ) attrib = CON_ColorCharToAttrib( *( qconsole_line + i + 1 ) ); line[ i ].Char.AsciiChar = qconsole_line[ i ]; } else line[ i ].Char.AsciiChar = ' '; line[ i ].Attributes = attrib; } if( qconsole_linelen > binfo.srWindow.Right ) { WriteConsoleOutput( qconsole_hout, line + (qconsole_linelen - binfo.srWindow.Right ), writeSize, writePos, &writeArea ); } else { WriteConsoleOutput( qconsole_hout, line, writeSize, writePos, &writeArea ); } // set curor position cursorPos.Y = binfo.dwCursorPosition.Y; cursorPos.X = qconsole_cursor < qconsole_linelen ? qconsole_cursor : qconsole_linelen > binfo.srWindow.Right ? binfo.srWindow.Right : qconsole_linelen; SetConsoleCursorPosition( qconsole_hout, cursorPos ); } /* ================== CON_Hide ================== */ static void CON_Hide( void ) { int realLen; realLen = qconsole_linelen; // remove input line from console output buffer qconsole_linelen = 0; CON_Show( ); qconsole_linelen = realLen; } /* ================== CON_Shutdown ================== */ void CON_Shutdown( void ) { CON_Hide( ); if( qconsole_hout != INVALID_HANDLE_VALUE ) { SetConsoleCursorInfo( qconsole_hout, &qconsole_orig_cursorinfo ); SetConsoleTextAttribute( qconsole_hout, qconsole_attrib ); CloseHandle( qconsole_hout ); qconsole_hout = INVALID_HANDLE_VALUE; } if( qconsole_hin != INVALID_HANDLE_VALUE ) { SetConsoleMode( qconsole_hin, qconsole_orig_mode ); CloseHandle( qconsole_hin ); qconsole_hin = INVALID_HANDLE_VALUE; } } /* ================== CON_Init ================== */ void CON_Init( void ) { CONSOLE_SCREEN_BUFFER_INFO info; int i; // handle Ctrl-C or other console termination SetConsoleCtrlHandler( CON_CtrlHandler, TRUE ); qconsole_hin = GetStdHandle( STD_INPUT_HANDLE ); if( qconsole_hin == INVALID_HANDLE_VALUE ) return; GetConsoleMode( qconsole_hin, &qconsole_orig_mode ); // allow mouse wheel scrolling SetConsoleMode( qconsole_hin, qconsole_orig_mode & ~ENABLE_MOUSE_INPUT ); FlushConsoleInputBuffer( qconsole_hin ); qconsole_hout = GetStdHandle( STD_OUTPUT_HANDLE ); if( qconsole_hout == INVALID_HANDLE_VALUE ) return; GetConsoleScreenBufferInfo( qconsole_hout, &info ); qconsole_attrib = info.wAttributes; qconsole_backgroundAttrib = qconsole_attrib & (BACKGROUND_BLUE|BACKGROUND_GREEN|BACKGROUND_RED|BACKGROUND_INTENSITY); SetConsoleTitle("Tremulous Dedicated Server Console"); // initialize history for( i = 0; i < QCONSOLE_HISTORY; i++ ) qconsole_history[ i ][ 0 ] = '\0'; // set text color to white SetConsoleTextAttribute( qconsole_hout, CON_ColorCharToAttrib( COLOR_WHITE ) ); } /* ================== CON_Input ================== */ char *CON_Input( void ) { INPUT_RECORD buff[ MAX_EDIT_LINE ]; DWORD count = 0, events = 0; WORD key = 0; int i; int newlinepos = -1; if( !GetNumberOfConsoleInputEvents( qconsole_hin, &events ) ) return NULL; if( events < 1 ) return NULL; // if we have overflowed, start dropping oldest input events if( events >= MAX_EDIT_LINE ) { ReadConsoleInput( qconsole_hin, buff, 1, &events ); return NULL; } if( !ReadConsoleInput( qconsole_hin, buff, events, &count ) ) return NULL; FlushConsoleInputBuffer( qconsole_hin ); for( i = 0; i < count; i++ ) { if( buff[ i ].EventType != KEY_EVENT ) continue; if( !buff[ i ].Event.KeyEvent.bKeyDown ) continue; key = buff[ i ].Event.KeyEvent.wVirtualKeyCode; if( key == VK_RETURN ) { newlinepos = i; qconsole_cursor = 0; break; } else if( key == VK_UP ) { CON_HistPrev(); break; } else if( key == VK_DOWN ) { CON_HistNext(); break; } else if( key == VK_LEFT ) { qconsole_cursor--; if ( qconsole_cursor < 0 ) { qconsole_cursor = 0; } break; } else if( key == VK_RIGHT ) { qconsole_cursor++; if ( qconsole_cursor > qconsole_linelen ) { qconsole_cursor = qconsole_linelen; } break; } else if( key == VK_HOME ) { qconsole_cursor = 0; break; } else if( key == VK_END ) { qconsole_cursor = qconsole_linelen; break; } else if( key == VK_TAB ) { field_t f; Q_strncpyz( f.buffer, qconsole_line, sizeof( f.buffer ) ); Field_AutoComplete( &f ); Q_strncpyz( qconsole_line, f.buffer, sizeof( qconsole_line ) ); qconsole_linelen = strlen( qconsole_line ); qconsole_cursor = qconsole_linelen; break; } if( qconsole_linelen < sizeof( qconsole_line ) - 1 ) { char c = buff[ i ].Event.KeyEvent.uChar.AsciiChar; if( key == VK_BACK ) { if ( qconsole_cursor > 0 ) { int newlen = ( qconsole_linelen > 0 ) ? qconsole_linelen - 1 : 0; if ( qconsole_cursor < qconsole_linelen ) { memmove( qconsole_line + qconsole_cursor - 1, qconsole_line + qconsole_cursor, qconsole_linelen - qconsole_cursor ); } qconsole_line[ newlen ] = '\0'; qconsole_linelen = newlen; qconsole_cursor--; } } else if( c ) { if ( qconsole_linelen > qconsole_cursor ) { memmove( qconsole_line + qconsole_cursor + 1, qconsole_line + qconsole_cursor, qconsole_linelen - qconsole_cursor ); } qconsole_line[ qconsole_cursor++ ] = c; qconsole_linelen++; qconsole_line[ qconsole_linelen ] = '\0'; } } } if( newlinepos < 0) { CON_Show(); return NULL; } if( !qconsole_linelen ) { CON_Show(); Com_Printf( "\n" ); return NULL; } qconsole_linelen = 0; CON_Show(); CON_HistAdd(); Com_Printf( "%s\n", qconsole_line ); return qconsole_line; } /* ================= CON_WindowsColorPrint Set text colors based on Q3 color codes ================= */ void CON_WindowsColorPrint( const char *msg ) { static char buffer[ MAXPRINTMSG ]; int length = 0; while( *msg ) { qconsole_drawinput = ( *msg == '\n' ); if( Q_IsColorString( msg ) || *msg == '\n' ) { // First empty the buffer if( length > 0 ) { buffer[ length ] = '\0'; fputs( buffer, stderr ); length = 0; } if( *msg == '\n' ) { // Reset color and then add the newline if( qconsole_hout != INVALID_HANDLE_VALUE ) SetConsoleTextAttribute( qconsole_hout, CON_ColorCharToAttrib( COLOR_WHITE ) ); fputs( "\n", stderr ); msg++; } else { // Set the color if( qconsole_hout != INVALID_HANDLE_VALUE ) SetConsoleTextAttribute( qconsole_hout, CON_ColorCharToAttrib( *( msg + 1 ) ) ); msg += 2; } } else { if( length >= MAXPRINTMSG - 1 ) break; buffer[ length ] = *msg; length++; msg++; } } // Empty anything still left in the buffer if( length > 0 ) { buffer[ length ] = '\0'; fputs( buffer, stderr ); } } /* ================== CON_Print ================== */ void CON_Print( const char *msg ) { CON_Hide( ); CON_WindowsColorPrint( msg ); CON_Show( ); }