/* * Kangaroo Punch MultiPlayer Game Server Mark II * Copyright (C) 2020-2021 Scott Duensing * * 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, either version 3 of the License, or * (at your option) any later version. * * 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, see . * */ #include #include #include #include "console.h" #include "array.h" #include "util.h" static uint8_t _running = 1; static struct termios _termios = { 0 }; static char **_consoleMessageQueue = NULL; static pthread_mutex_t _messageQueueMutex = PTHREAD_MUTEX_INITIALIZER; static uint8_t getch(void); static uint8_t kbhit(void); static void sendToConsole(const char *message, ...); static void terminalModeConioSet(void); static void terminalModeOriginalSet(void); void consoleMessageQueue(const char *message, ...) { va_list args = { 0 }; char buffer[2048] = { 0 }; char *newMessage = NULL; va_start(args, message); vsprintf(buffer, message, args); va_end(args); newMessage = strdup(buffer); pthread_mutex_lock(&_messageQueueMutex); arrput(_consoleMessageQueue, newMessage); pthread_mutex_unlock(&_messageQueueMutex); free(newMessage); } void consoleRun(void) { uint8_t commandEntering = 0; char command[256] = { 0 }; uint16_t commandIndex = 0; char c = 0; uint8_t commandOk = 0; struct timespec remaining = { 0, 0 }; struct timespec sleepTime = { 0, 1000000000/4 }; if (pthread_mutex_init(&_messageQueueMutex, NULL)) utilDie("Unable to create console message queue mutex.\n"); terminalModeConioSet(); while (_running) { if (kbhit()) { // Is this a new command? if (!commandEntering) { // Show prompt. printf(">"); // Command is being entered. commandEntering = 1; } // Read key. c = getch(); switch (c) { // Backspace case 8: case 127: break; // ESC case 27: // Abort command entry. printf("\n\r"); commandIndex = 0; command[0] = 0; commandEntering = 0; break; // ENTER case 13: printf("\n\r"); logWriteToFileOnly(">%s\n", command); commandOk = 0; if (!strcasecmp(command, "HELP") || !strcasecmp(command, "?")) { sendToConsole("HELP or ? - This message.\n"); sendToConsole("SHUTDOWN - Stop the server.\n"); commandOk = 1; } if (!strcasecmp(command, "SHUTDOWN")) { _running = 0; commandOk = 1; } // Did we grok it? if (!commandOk) { sendToConsole("Unknown command! Type HELP (or ?) for help.\n\r"); } commandIndex = 0; command[0] = 0; commandEntering = 0; break; default: // Add to command. command[commandIndex++] = c; command[commandIndex] = 0; printf("%c", c); // Overflow? if (commandIndex == 255) { printf("\n\r"); commandIndex = 0; command[0] = 0; commandEntering = 0; } break; } fflush(stdout); } // Only do this if we're not typing on the console. if (!commandEntering) { // Don't eat all the CPU. nanosleep(&remaining, &sleepTime); // Messages to display? pthread_mutex_lock(&_messageQueueMutex); if (arrlenu(_consoleMessageQueue) > 0) { sendToConsole("%s", _consoleMessageQueue[0]); arrdel(_consoleMessageQueue, 0); } pthread_mutex_unlock(&_messageQueueMutex); } } terminalModeOriginalSet(); pthread_mutex_destroy(&_messageQueueMutex); } static uint8_t getch(void) { int r = 0; uint8_t c = 0; if ((r = read(0, &c, sizeof(c))) < 0) { return r; } else { return c; } } static uint8_t kbhit(void) { struct timeval tv = { 0L, 0L }; fd_set fds = { 0 }; FD_ZERO(&fds); FD_SET(0, &fds); return select(1, &fds, NULL, NULL, &tv) > 0; } static void sendToConsole(const char *message, ...) { va_list args = { 0 }; char buffer[2048] = { 0 }; va_start(args, message); vsprintf(buffer, message, args); printf("%s", buffer); if (buffer[strlen(buffer) - 1] == '\n') { // Console needs a CR with its LF. printf("\r"); } fflush(stdout); logWriteToFileOnly("%s", buffer); va_end(args); } static void terminalModeConioSet(void) { struct termios new_termios = { 0 }; // https://stackoverflow.com/questions/448944/c-non-blocking-keyboard-input // Take two copies - one for now, one for later. tcgetattr(0, &_termios); memcpy(&new_termios, &_termios, sizeof(new_termios)); cfmakeraw(&new_termios); tcsetattr(0, TCSANOW, &new_termios); } static void terminalModeOriginalSet(void) { tcsetattr(0, TCSANOW, &_termios); }