/* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code 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. Quake III Arena source code 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 Quake III Arena source code; 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; // 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_oldest = 0; // current edit buffer static char qconsole_line[ MAX_EDIT_LINE ]; static int qconsole_linelen = 0; static HANDLE qconsole_hout; static HANDLE qconsole_hin; /* ================== 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_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 ) return; qconsole_history_pos = pos; Q_strncpyz( qconsole_line, qconsole_history[ qconsole_history_pos ], sizeof( qconsole_line ) ); qconsole_linelen = strlen( qconsole_line ); } /* ================== CON_HistNext ================== */ static void CON_HistNext( void ) { int pos; 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_line[ 0 ] = '\0'; qconsole_linelen = 0; return; } qconsole_history_pos = pos; Q_strncpyz( qconsole_line, qconsole_history[ qconsole_history_pos ], sizeof( qconsole_line ) ); qconsole_linelen = strlen( qconsole_line ); } /* ================== 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 }; int i; CHAR_INFO line[ MAX_EDIT_LINE ]; GetConsoleScreenBufferInfo( qconsole_hout, &binfo ); // if we're in the middle of printf, don't bother writing the buffer if( binfo.dwCursorPosition.X != 0 ) return; writeArea.Left = 0; writeArea.Top = binfo.dwCursorPosition.Y; writeArea.Bottom = binfo.dwCursorPosition.Y; writeArea.Right = MAX_EDIT_LINE; // build a space-padded CHAR_INFO array for( i = 0; i < MAX_EDIT_LINE; i++ ) { if( i < qconsole_linelen ) line[ i ].Char.AsciiChar = qconsole_line[ i ]; else line[ i ].Char.AsciiChar = ' '; line[ i ].Attributes = qconsole_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 ); } } /* ================== CON_Shutdown ================== */ void CON_Shutdown( void ) { SetConsoleMode( qconsole_hin, qconsole_orig_mode ); SetConsoleCursorInfo( qconsole_hout, &qconsole_orig_cursorinfo ); CloseHandle( qconsole_hout ); CloseHandle( qconsole_hin ); } /* ================== CON_Init ================== */ void CON_Init( void ) { CONSOLE_CURSOR_INFO curs; 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; qconsole_hout = GetStdHandle( STD_OUTPUT_HANDLE ); if( qconsole_hout == 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 ); GetConsoleScreenBufferInfo( qconsole_hout, &info ); qconsole_attrib = info.wAttributes; SetConsoleTitle("ioquake3 Dedicated Server Console"); // make cursor invisible GetConsoleCursorInfo( qconsole_hout, &qconsole_orig_cursorinfo ); curs.dwSize = 1; curs.bVisible = FALSE; SetConsoleCursorInfo( qconsole_hout, &curs ); // initialize history for( i = 0; i < QCONSOLE_HISTORY; i++ ) qconsole_history[ i ][ 0 ] = '\0'; } /* ================== 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; break; } else if( key == VK_UP ) { CON_HistPrev(); break; } else if( key == VK_DOWN ) { CON_HistNext(); 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 ); break; } if( qconsole_linelen < sizeof( qconsole_line ) - 1 ) { char c = buff[ i ].Event.KeyEvent.uChar.AsciiChar; if( key == VK_BACK ) { int pos = ( qconsole_linelen > 0 ) ? qconsole_linelen - 1 : 0; qconsole_line[ pos ] = '\0'; qconsole_linelen = pos; } else if( c ) { qconsole_line[ qconsole_linelen++ ] = c; qconsole_line[ qconsole_linelen ] = '\0'; } } } CON_Show(); if( newlinepos < 0) return NULL; if( !qconsole_linelen ) { Com_Printf( "\n" ); return NULL; } CON_HistAdd(); Com_Printf( "%s\n", qconsole_line ); qconsole_linelen = 0; return qconsole_line; } /* ================== CON_Print ================== */ void CON_Print( const char *msg ) { fputs( msg, stderr ); CON_Show( ); }