/* * serverconsole.cpp * * Copyright (C) 2001 Atomic Blue (info@planeshift.it, http://www.atomicblue.org) * * * This program 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 (version 2 of the License) * This program 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 this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * Author: Matthias Braun */ #include #include #include #include #include #include #include "util/eventmanager.h" #ifdef CS_COMPILER_MSVC #include #endif /* Include platform specific socket settings */ #ifdef USE_WINSOCK # include "net/sockwin.h" #endif #ifdef USE_UNISOCK # include "net/sockuni.h" #endif #ifndef STDIN_FILENO #define STDIN_FILENO 0 #endif #ifdef HAVE_READLINE_READLINE_H # include # ifdef HAVE_READLINE_HISTORY_H # include # define USE_HISTORY # endif # define USE_READLINE #endif #ifdef HAVE_READLINE_H /* g++ with glibc hack... */ # ifdef __P # undef __P # endif # include # ifdef HAVE_HISTORY_H # include # define USE_HISTORY # endif # define USE_READLINE #endif #ifndef whitespace # define whitespace(c) (((c) == ' ') || ((c) == '\t') || ((c) == '\r') ) #endif #include "serverconsole.h" #include "command.h" #include "util/pserror.h" const char *ServerConsole::appname=0; const char *ServerConsole::prompt=0; iCommandCatcher *ServerConsole::cmdcatcher=NULL; volatile ServerConsole::Status ServerConsole::running=STOPPED; #ifdef USE_READLINE char **server_command_completion (const char *,int,int); #endif void ServerConsole::Init(const char *name, const char *command_prompt) { appname = name; prompt = command_prompt; cmdcatcher = NULL; #ifdef USE_READLINE rl_readline_name = name; rl_attempted_completion_function = server_command_completion; rl_initialize(); #endif CPrintf (CON_CMDOUTPUT, COL_RED "-Server Console initialized (%s-V0.1)-\n" COL_NORMAL , appname); } // command execution time COMMAND *find_command(const char *name) { for (int i=0; commands[i].name; i++) if (strcmp (name, commands[i].name) == 0) return (&commands[i]); return ((COMMAND *) NULL); } int execute_line(const char *cmd,csString *buffer) { ConsoleOut::SetStringBuffer(buffer); char line[1000]; strcpy (line, cmd); int i; COMMAND *command; char *word; i = 0; while (line[i] && whitespace (line[i])) i++; word = line + i; while (line[i] && !whitespace (line[i])) i++; if (line[i]) line[i++]='\0'; command = find_command(word); if (!command) { CPrintf (CON_CMDOUTPUT, "No such command: %s\n", word); return -1; } while (whitespace (line[i])) i++; word = line + i; return ((*(command->func)) (word)); } char *stripwhite(char *string) { char *s,*t; for (s = string; whitespace (*s); s++) ; if (*s == 0) return s; t = s + strlen(s) - 1; while (t > s && whitespace (*t)) t--; *++t = '\0'; return s; } void ServerConsole::Stop() { running=STOPPED; } void ServerConsole::Abort() { running=ABORTED; } int ServerConsole::MainLoop () { InputRun(); return running; } void ServerConsole::ExecuteScript(const char* script) { const char* bufptr = script; while ( *bufptr != 0) { // skip empty lines while (*bufptr == '\n') bufptr++; char line[1000]; size_t len = strcspn(bufptr, "\n"); memcpy (line, bufptr, len); line[len] = '\0'; bufptr += len; char* strippedline = stripwhite(line); if ((strippedline[0] == '#') || (strippedline[0] == '\0')) continue; char actualcommand[1000]; strcpy(actualcommand, strippedline); size_t commentbegin = strcspn(actualcommand, "#"); actualcommand[commentbegin] = '\0'; if (actualcommand[0] == '\0') continue; CPrintf (CON_CMDOUTPUT, COL_BLUE "%s: " COL_NORMAL, prompt); CPrintf (CON_CMDOUTPUT, "%s\n", actualcommand); execute_line(actualcommand,NULL); } } #ifdef WIN32 #include "windows.h" #endif #ifndef USE_READLINE // simple version without readline... void ServerConsole::InputRun() { char *line = new char[320]; running=RUNNING; CPrintf (CON_CMDOUTPUT, COL_BLUE "%s: " COL_NORMAL, prompt); // This important flag allows clean shutdown outside the console // since stdin is polled asynchronously while (running == RUNNING) { // Windows only allows select to be used on sockets so we have to use this // roundabout method. Note: the running flag won't be checked when the // user is in the middle of typing something. #ifdef WIN32 HANDLE hInput = GetStdHandle(STD_INPUT_HANDLE); DWORD records_read = 0; INPUT_RECORD irec[1]; // Wait until the console receives input if(WaitForSingleObject(hInput, 1000) == WAIT_TIMEOUT) continue; // Check if the record is for a keypress PeekConsoleInput(hInput, irec, 1, (LPDWORD)(&records_read)); if(irec[0].EventType != KEY_EVENT) { // Read and discard this event ReadConsoleInput(hInput, irec, 1, (LPDWORD)(&records_read)); continue; } #else struct timeval tv; fd_set readfds; tv.tv_sec = 1; tv.tv_usec = 0; FD_ZERO(&readfds); FD_SET(STDIN_FILENO, &readfds); // This allows the running flag to be checked every second but only works on Unix. int retval = select(STDIN_FILENO+1, &readfds, NULL, NULL, &tv); if(retval == 0) continue; #endif while (fgets(line,319,stdin) == NULL) { /* TODO: Yield timeslice here maybe? */ } char* s; s = stripwhite(line); // Strip trailing newline if (s[strlen(s)-1]=='\n') s[strlen(s)-1]='\0'; if (*s) { #ifdef USE_HISTORY add_history(s); #endif if (*s != '/') // commands starting with slash are remote commands from npcclient to server { if(!strcmp("quit", s)) execute_line(s, NULL); else EventManager::GetSingleton().Push(new psServerConsoleCommand(s)); } else if (cmdcatcher) { // Give something else access to the command cmdcatcher->CatchCommand(s); CPrintf (CON_CMDOUTPUT, COL_BLUE "%s: " COL_NORMAL, prompt); } } else CPrintf (CON_CMDOUTPUT, COL_BLUE "%s: " COL_NORMAL, prompt); } delete[] line; } #else void ServerConsole::InputRun() { running=RUNNING; while (running == RUNNING) { char *s; char cmdprompt[80]; char *line; cs_snprintf (cmdprompt,80, COL_BLUE "%s: " COL_NORMAL, prompt); line = NULL; while (line == NULL) { line = readline(cmdprompt); /* TODO: Yield timeslice here maybe? */ } s=stripwhite(line); if (*s) { #ifdef USE_HISTORY add_history(s); #endif execute_line(s); } free(line); } } /* more readline stuff */ char *dupstr (char *s) { char *r; r = (char *) malloc (strlen (s) + 1); strcpy (r, s); return (r); } /* Generator function for command completion. STATE lets us know whether to start from scratch; without any state (i.e. STATE == 0), then we start at the top of the list. */ char *command_generator (const char *text, int state) { static int list_index, len; char *name; /* If this is a new word to complete, initialize now. This includes saving the length of TEXT for efficiency, and initializing the index variable to 0. */ if (!state) { list_index = 0; len = strlen (text); } /* Return the next name which partially matches from the command list. */ while ( (name = commands[list_index].name) != 0) { list_index++; if (strncmp (name, text, len) == 0) return (dupstr(name)); } /* If no names matched, then return NULL. */ return ((char *)NULL); } /* Attempt to complete on the contents of TEXT. START and END bound the region of rl_line_buffer that contains the word to complete. TEXT is the word to complete. We can use the entire contents of rl_line_buffer in case we want to do some simple parsing. Return the array of matches, or NULL if there aren't any. */ char **server_command_completion (const char *text,int start,int ) { char **matches; matches = (char **)NULL; /* If this word is at the start of the line, then it is a command to complete. Otherwise it is the name of a file in the current directory. */ if (start == 0) matches = rl_completion_matches (text, command_generator); return (matches); } #endif