* rewrite of the win32 dedicated console:

1) NET_Sleep() no longer watches for input, Sys_Sleep() added for waiting
     on input.
  2) Added "CtrlHandler" for trapping Ctrl-C and other quit methods not
     handled by signals on windows
  3) Added history support
  4) Added tab completion
  5) Removed automatic cursor/scroll adjustment (too problematic)
  6) Enable mousewheel scrolling
  7) Stop using the InputBuffer for editing

  This seems to work pretty well now, but I jumped the gun on a previous
  commit message by saying you can scroll now without locking up your server.
  That was only true up until the point that a server tried to print to
  the console, at that point it will hang until you release the scroll bar :(
  It may be possible to get around this by using a seperate thread for
  console output, but that's a whole new can of worms.
This commit is contained in:
Tony J. White = 2007-09-15 02:22:58 +00:00
parent 2052b94adc
commit e46fe24426
8 changed files with 321 additions and 217 deletions

View file

@ -1026,43 +1026,27 @@ void NET_Shutdown( void ) {
==================== ====================
NET_Sleep NET_Sleep
Sleeps msec or until something happens on the network or stdin Sleeps msec or until something happens on the network
==================== ====================
*/ */
void NET_Sleep( int msec ) { void NET_Sleep( int msec ) {
struct timeval timeout; struct timeval timeout;
fd_set fdset; fd_set fdset;
int highestfd = 0;
if (!com_dedicated->integer) if (!com_dedicated->integer)
return; // we're not a server, just run full speed return; // we're not a server, just run full speed
if (!ip_socket)
return;
if (msec < 0 )
return;
FD_ZERO(&fdset); FD_ZERO(&fdset);
FD_SET(ip_socket, &fdset);
FD_SET(fileno(stdin), &fdset); timeout.tv_sec = msec/1000;
highestfd = fileno(stdin) + 1; timeout.tv_usec = (msec%1000)*1000;
select(ip_socket+1, &fdset, NULL, NULL, &timeout);
if(ip_socket)
{
FD_SET(ip_socket, &fdset); // network socket
if(ip_socket >= highestfd)
highestfd = ip_socket + 1;
}
if(highestfd)
{
if(msec >= 0)
{
timeout.tv_sec = msec/1000;
timeout.tv_usec = (msec%1000)*1000;
select(highestfd, &fdset, NULL, NULL, &timeout);
}
else
{
// Block indefinitely
select(highestfd, &fdset, NULL, NULL, NULL);
}
}
} }

View file

@ -1030,6 +1030,7 @@ char *Sys_ConsoleInput(void);
char **Sys_ListFiles( const char *directory, const char *extension, char *filter, int *numfiles, qboolean wantsubs ); char **Sys_ListFiles( const char *directory, const char *extension, char *filter, int *numfiles, qboolean wantsubs );
void Sys_FreeFileList( char **list ); void Sys_FreeFileList( char **list );
void Sys_Sleep(int msec);
qboolean Sys_LowPhysicalMemory( void ); qboolean Sys_LowPhysicalMemory( void );

View file

@ -774,7 +774,7 @@ void SV_Frame( int msec ) {
// Running as a server, but no map loaded // Running as a server, but no map loaded
#ifdef DEDICATED #ifdef DEDICATED
// Block until something interesting happens // Block until something interesting happens
NET_Sleep(-1); Sys_Sleep(-1);
#endif #endif
return; return;

View file

@ -22,21 +22,113 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#include "../qcommon/q_shared.h" #include "../qcommon/q_shared.h"
#include "../qcommon/qcommon.h" #include "../qcommon/qcommon.h"
#include "sys_local.h"
#include "windows.h" #include "windows.h"
#define QCONSOLE_HISTORY 32
#define QCONSOLE_THEME FOREGROUND_RED | \ static WORD qconsole_attrib;
BACKGROUND_RED | \
BACKGROUND_GREEN | \
BACKGROUND_BLUE
#define QCONSOLE_INPUT_RECORDS 1024 // saved console status
static DWORD qconsole_orig_mode;
static CONSOLE_CURSOR_INFO qconsole_orig_cursorinfo;
// used to track key input // cmd history
static int qconsole_chars = 0; 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 );
}
// used to restore original color theme
static int qconsole_orig_attrib;
/* /*
================== ==================
@ -54,6 +146,46 @@ CON_Show
*/ */
void CON_Show( void ) 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 );
}
} }
/* /*
@ -63,15 +195,10 @@ CON_Shutdown
*/ */
void CON_Shutdown( void ) void CON_Shutdown( void )
{ {
HANDLE hout; SetConsoleMode( qconsole_hin, qconsole_orig_mode );
COORD screen = { 0, 0 }; SetConsoleCursorInfo( qconsole_hout, &qconsole_orig_cursorinfo );
DWORD written; CloseHandle( qconsole_hout );
CloseHandle( qconsole_hin );
hout = GetStdHandle( STD_OUTPUT_HANDLE );
SetConsoleTextAttribute( hout, qconsole_orig_attrib );
FillConsoleOutputAttribute( hout, qconsole_orig_attrib, 63999,
screen, &written );
} }
/* /*
@ -81,34 +208,43 @@ CON_Init
*/ */
void CON_Init( void ) void CON_Init( void )
{ {
HANDLE hout; CONSOLE_CURSOR_INFO curs;
COORD screen = { 0, 0 }; CONSOLE_SCREEN_BUFFER_INFO info;
DWORD written, read; int i;
CONSOLE_SCREEN_BUFFER_INFO binfo;
SMALL_RECT rect;
WORD oldattrib;
hout = GetStdHandle( STD_OUTPUT_HANDLE ); // handle Ctrl-C or other console termination
SetConsoleCtrlHandler( CON_CtrlHandler, TRUE );
// remember original color theme qconsole_hin = GetStdHandle( STD_INPUT_HANDLE );
ReadConsoleOutputAttribute( hout, &oldattrib, 1, screen, &read ); if( qconsole_hin == INVALID_HANDLE_VALUE )
qconsole_orig_attrib = oldattrib; return;
SetConsoleTitle("ioquake3 Dedicated Server Console"); qconsole_hout = GetStdHandle( STD_OUTPUT_HANDLE );
if( qconsole_hout == INVALID_HANDLE_VALUE )
return;
SetConsoleTextAttribute( hout, QCONSOLE_THEME ); GetConsoleMode( qconsole_hin, &qconsole_orig_mode );
FillConsoleOutputAttribute( hout, QCONSOLE_THEME, 63999, screen, &written );
// adjust console scroll to match up with cursor position // allow mouse wheel scrolling
GetConsoleScreenBufferInfo( hout, &binfo ); SetConsoleMode( qconsole_hin,
rect.Top = binfo.srWindow.Top; qconsole_orig_mode & ~ENABLE_MOUSE_INPUT );
rect.Left = binfo.srWindow.Left;
rect.Bottom = binfo.srWindow.Bottom;
rect.Right = binfo.srWindow.Right;
rect.Top += ( binfo.dwCursorPosition.Y - binfo.srWindow.Bottom );
rect.Bottom = binfo.dwCursorPosition.Y;
SetConsoleWindowInfo( hout, TRUE, &rect );
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';
} }
/* /*
@ -118,167 +254,102 @@ CON_ConsoleInput
*/ */
char *CON_ConsoleInput( void ) char *CON_ConsoleInput( void )
{ {
HANDLE hin, hout; INPUT_RECORD buff[ MAX_EDIT_LINE ];
INPUT_RECORD buff[ QCONSOLE_INPUT_RECORDS ]; DWORD count = 0, events = 0;
DWORD count = 0; WORD key = 0;
int i; int i;
static char input[ 1024 ] = { "" }; int newlinepos = -1;
int inputlen;
int newlinepos = -1;
CHAR_INFO line[ QCONSOLE_INPUT_RECORDS ];
int linelen = 0;
inputlen = 0; if( !GetNumberOfConsoleInputEvents( qconsole_hin, &events ) )
input[ 0 ] = '\0'; return NULL;
hin = GetStdHandle( STD_INPUT_HANDLE ); if( events < 1 )
if( hin == INVALID_HANDLE_VALUE ) return NULL;
return NULL;
hout = GetStdHandle( STD_OUTPUT_HANDLE );
if( hout == INVALID_HANDLE_VALUE )
return NULL;
if( !PeekConsoleInput( hin, buff, QCONSOLE_INPUT_RECORDS, &count ) ) // if we have overflowed, start dropping oldest input events
return NULL; if( events >= MAX_EDIT_LINE )
{
ReadConsoleInput( qconsole_hin, buff, 1, &events );
return NULL;
}
// if we have overflowed, start dropping oldest input events if( !ReadConsoleInput( qconsole_hin, buff, events, &count ) )
if( count == QCONSOLE_INPUT_RECORDS ) return NULL;
{
ReadConsoleInput( hin, buff, 1, &count );
return NULL;
}
for( i = 0; i < count; i++ ) FlushConsoleInputBuffer( qconsole_hin );
{
if( buff[ i ].EventType == KEY_EVENT && buff[ i ].Event.KeyEvent.bKeyDown )
{
if( buff[ i ].Event.KeyEvent.wVirtualKeyCode == VK_RETURN )
{
newlinepos = i;
break;
}
if( linelen < QCONSOLE_INPUT_RECORDS && for( i = 0; i < count; i++ )
buff[ i ].Event.KeyEvent.uChar.AsciiChar ) {
{ if( buff[ i ].EventType != KEY_EVENT )
if( buff[ i ].Event.KeyEvent.wVirtualKeyCode == VK_BACK ) continue;
{ if( !buff[ i ].Event.KeyEvent.bKeyDown )
if( linelen > 0 ) continue;
linelen--;
} key = buff[ i ].Event.KeyEvent.wVirtualKeyCode;
else
{
line[ linelen ].Attributes = QCONSOLE_THEME;
line[ linelen++ ].Char.AsciiChar =
buff[ i ].Event.KeyEvent.uChar.AsciiChar;
}
}
}
}
// provide visual feedback for incomplete commands if( key == VK_RETURN )
if( linelen != qconsole_chars ) {
{ newlinepos = i;
CONSOLE_SCREEN_BUFFER_INFO binfo; break;
COORD writeSize = { QCONSOLE_INPUT_RECORDS, 1 }; }
COORD writePos = { 0, 0 }; else if( key == VK_UP )
SMALL_RECT writeArea = { 0, 0, 0, 0 }; {
int i; CON_HistPrev();
break;
}
else if( key == VK_DOWN )
{
CON_HistNext();
break;
}
else if( key == VK_TAB )
{
field_t f;
// keep track of this so we don't need to re-write to console every frame Q_strncpyz( f.buffer, qconsole_line,
qconsole_chars = linelen; sizeof( f.buffer ) );
Field_AutoComplete( &f );
Q_strncpyz( qconsole_line, f.buffer,
sizeof( qconsole_line ) );
qconsole_linelen = strlen( qconsole_line );
break;
}
GetConsoleScreenBufferInfo( hout, &binfo ); if( qconsole_linelen < sizeof( qconsole_line ) - 1 )
{
char c = buff[ i ].Event.KeyEvent.uChar.AsciiChar;
// adjust scrolling to cursor when typing if( key == VK_BACK )
if( binfo.dwCursorPosition.Y > binfo.srWindow.Bottom ) {
{ int pos = ( qconsole_linelen > 0 ) ?
SMALL_RECT rect; qconsole_linelen - 1 : 0;
rect.Top = binfo.srWindow.Top; qconsole_line[ pos ] = '\0';
rect.Left = binfo.srWindow.Left; qconsole_linelen = pos;
rect.Bottom = binfo.srWindow.Bottom; }
rect.Right = binfo.srWindow.Right; else if( c )
{
qconsole_line[ qconsole_linelen++ ] = c;
qconsole_line[ qconsole_linelen ] = '\0';
}
}
}
rect.Top += ( binfo.dwCursorPosition.Y - binfo.srWindow.Bottom ); CON_Show();
rect.Bottom = binfo.dwCursorPosition.Y;
SetConsoleWindowInfo( hout, TRUE, &rect ); if( newlinepos < 0)
GetConsoleScreenBufferInfo( hout, &binfo ); return NULL;
}
writeArea.Left = 0; if( !qconsole_linelen )
writeArea.Top = binfo.srWindow.Bottom; {
writeArea.Bottom = binfo.srWindow.Bottom; Com_Printf( "\n" );
writeArea.Right = QCONSOLE_INPUT_RECORDS; return NULL;
}
// pad line with ' ' to handle VK_BACK CON_HistAdd();
for( i = linelen; i < QCONSOLE_INPUT_RECORDS; i++ ) Com_Printf( "%s\n", qconsole_line );
{
line[ i ].Char.AsciiChar = ' ';
line[ i ].Attributes = QCONSOLE_THEME;
}
if( linelen > binfo.srWindow.Right ) qconsole_linelen = 0;
{
WriteConsoleOutput( hout, line + (linelen - binfo.srWindow.Right ),
writeSize, writePos, &writeArea );
}
else
{
WriteConsoleOutput( hout, line, writeSize, writePos, &writeArea );
}
if( binfo.dwCursorPosition.X != linelen ) return qconsole_line;
{
COORD cursorPos = { 0, 0 };
cursorPos.X = linelen;
cursorPos.Y = binfo.srWindow.Bottom;
SetConsoleCursorPosition( hout, cursorPos );
}
}
// don't touch the input buffer if this is an incomplete command
if( newlinepos < 0)
{
return NULL;
}
else
{
// add a newline
COORD cursorPos = { 0, 0 };
CONSOLE_SCREEN_BUFFER_INFO binfo;
GetConsoleScreenBufferInfo( hout, &binfo );
cursorPos.Y = binfo.srWindow.Bottom + 1;
SetConsoleCursorPosition( hout, cursorPos );
}
if( !ReadConsoleInput( hin, buff, newlinepos+1, &count ) )
return NULL;
for( i = 0; i < count; i++ )
{
if( buff[ i ].EventType == KEY_EVENT && buff[ i ].Event.KeyEvent.bKeyDown )
{
if( buff[ i ].Event.KeyEvent.wVirtualKeyCode == VK_BACK )
{
if( inputlen > 0 )
input[ --inputlen ] = '\0';
continue;
}
if( inputlen < ( sizeof( input ) - 1 ) &&
buff[ i ].Event.KeyEvent.uChar.AsciiChar )
{
input[ inputlen++ ] = buff[ i ].Event.KeyEvent.uChar.AsciiChar;
input[ inputlen ] = '\0';
}
}
}
if( !inputlen )
return NULL;
return input;
} }

View file

@ -38,3 +38,5 @@ char *CON_ConsoleInput(void);
#ifdef MACOS_X #ifdef MACOS_X
char *Sys_StripAppBundle( char *pwd ); char *Sys_StripAppBundle( char *pwd );
#endif #endif
void Sys_SigHandler( int signal );

View file

@ -573,7 +573,7 @@ void Sys_ParseArgs( int argc, char **argv )
Sys_SigHandler Sys_SigHandler
================= =================
*/ */
static void Sys_SigHandler( int signal ) void Sys_SigHandler( int signal )
{ {
static qboolean signalcaught = qfalse; static qboolean signalcaught = qfalse;
@ -641,6 +641,7 @@ int main( int argc, char **argv )
#ifndef _WIN32 #ifndef _WIN32
// Windows doesn't have these signals // Windows doesn't have these signals
// see CON_CtrlHandler() in con_win32.c
signal( SIGHUP, Sys_SigHandler ); signal( SIGHUP, Sys_SigHandler );
signal( SIGQUIT, Sys_SigHandler ); signal( SIGQUIT, Sys_SigHandler );
signal( SIGTRAP, Sys_SigHandler ); signal( SIGTRAP, Sys_SigHandler );

View file

@ -448,3 +448,31 @@ char *Sys_StripAppBundle( char *dir )
return cwd; return cwd;
} }
#endif // MACOS_X #endif // MACOS_X
/*
==================
Sys_Sleep
Block execution for msec or until input is recieved.
==================
*/
void Sys_Sleep( int msec )
{
fd_set fdset;
FD_ZERO(&fdset);
FD_SET(fileno(stdin), &fdset);
if( msec < 0 )
{
select((fileno(stdin) + 1), &fdset, NULL, NULL, NULL);
}
else
{
struct timeval timeout;
timeout.tv_sec = msec/1000;
timeout.tv_usec = (msec%1000)*1000;
select((fileno(stdin) + 1), &fdset, NULL, NULL, &timeout);
}
}

View file

@ -508,3 +508,20 @@ void Sys_FreeFileList( char **list )
Z_Free( list ); Z_Free( list );
} }
/*
==============
Sys_Sleep
Block execution for msec or until input is recieved.
==============
*/
void Sys_Sleep( int msec )
{
if( msec < 0 )
WaitForSingleObject( GetStdHandle( STD_INPUT_HANDLE ), INFINITE );
else
WaitForSingleObject( GetStdHandle( STD_INPUT_HANDLE ), msec );
}