/* server/console.cc This file is part of the Osirion project and is distributed under the terms and conditions of the GNU General Public License version 2 */ #include #include #include #include #include #include #include "sys/sys.h" #include "auxiliary/functions.h" #include "core/application.h" #include "core/core.h" #include "core/commandbuffer.h" #include "dedicated/console.h" #include "sys/consoleinterface.h" #include "filesystem/filesystem.h" #ifdef HAVE_CURSES // MOUSE_MOVE has conflicting definitions on win32 #ifdef MOUSE_MOVED #undef MOUSE_MOVED #endif #include #endif namespace dedicated { bool console_initialized = false; bool console_updated = false; float prev_time = 0; Console server_console; #ifdef HAVE_CURSES WINDOW *stdwin; const size_t MAXHISTOLINES = 512; #endif Console *console() { return (&server_console); } void Console::init() { #ifdef HAVE_CURSES stdwin = initscr(); // initialize the ncurses window cbreak(); // disable input line buffering noecho(); // don't show typed characters keypad(stdwin, TRUE); // enable special keys nodelay(stdwin, TRUE); // non-blocking input curs_set(1); // enable cursor if (has_colors() == TRUE) { start_color(); // this is ncurses-specific use_default_colors(); // COLOR_PAIR(0) is terminal default init_pair(1, COLOR_RED, -1); init_pair(2, COLOR_GREEN, -1); init_pair(3, COLOR_YELLOW, -1); init_pair(4, COLOR_BLUE, -1); init_pair(5, COLOR_CYAN, -1); init_pair(6, COLOR_MAGENTA, -1); init_pair(7, COLOR_WHITE, -1); init_pair(8, -1, -1); } #endif // HAVE_CURSES con_print << "^BInitializing console..." << std::endl; #ifdef HAVE_CURSES server_console.history.clear(); server_console.history.push_back(""); server_console.history_pos = server_console.history.rbegin(); server_console.input_pos = 0; server_console.console_scroll = 0; server_console.draw(); #endif // HAVE_CURSES console_initialized = true; console_updated = true; } void Console::shutdown() { con_print << "^BShutting down console..." << std::endl; #ifdef HAVE_CURSES server_console.draw(); endwin(); console_initialized = false; #endif // HAVE_CURSES } Console::Console() { } Console::~Console() { } void Console::open_log() { console_logfilename.assign(filesystem::writedir()); console_logfilename.append("server.log"); std::ofstream logfile; logfile.open(console_logfilename.c_str(), std::ios::app); if (!logfile.is_open()) { std::string errfilename(console_logfilename); console_logfilename.clear(); con_warn << "Could not open logfile " << errfilename << std::endl; } else { // dump console content for (Queue::iterator it = log().begin(); it != log().end(); it++) { int year = 0, month = 0, day = 0, hour = 0, min = 0, sec = 0, milliseconds = 0; sys::get_localtime(year, month, day, hour, min, sec, milliseconds); logfile << std::setw(4) << std::setfill('0') << year << "-" << std::setw(2) << std::setfill('0') << month << "-" << std::setw(2) << std::setfill('0') << day << " " << std::setw(2) << std::setfill('0') << hour << ":" << std::setw(2) << std::setfill('0') << min << ":" << std::setw(2) << std::setfill('0') << sec << " " << std::setw(2) << " " << aux::text_strip((*it)) << std::endl; } logfile.close(); } } void Console::print(const std::string & text) { #ifdef HAVE_CURSES // at this point the text is already saved in the console buffering // Because w're using curses, the actual printing is handled async if (console_initialized && !rcon()) { console_updated = true; draw(); } #else // use fallback print sys::ConsoleInterface::print(text); #endif // HAVE_CURSES if (!rcon() && console_logfilename.size()) { std::ofstream logfile; logfile.open(console_logfilename.c_str(), std::ios::app); if (logfile.is_open()) { if (!sys::console_timestamps()) { int year = 0, month = 0, day = 0, hour = 0, min = 0, sec = 0, milliseconds = 0; sys::get_localtime(year, month, day, hour, min, sec, milliseconds); logfile << std::setw(4) << std::setfill('0') << year << "-" << std::setw(2) << std::setfill('0') << month << "-" << std::setw(2) << std::setfill('0') << day << " " << std::setw(2) << std::setfill('0') << hour << ":" << std::setw(2) << std::setfill('0') << min << ":" << std::setw(2) << std::setfill('0') << sec << " " << std::setw(2) << " "; } logfile << aux::text_strip(text) << std::endl; logfile.close(); } } } #ifdef HAVE_CURSES void Console::resize() { if (!console_initialized) return; endwin(); refresh(); draw(); } void Console::set_color(const char *color_code) { if (!has_colors()) return; int color = 0; bool bold = false; if (aux::is_base_color_code(color_code)) { // base colors // Default=0, Red=1, Green=2, Yellow=3, Blue=4, Cyan=5, Magenta=6, White=7 color = *(color_code + 1) - '0'; if (color == 3 || color == 7) bold = true; else bold = false; } else if (aux::is_core_color_code(color_code)) { switch (*(color_code + 1)) { case 'N': // normal color case 'D': // debug color color = 0; break; case 'B': // bold color color = 0; bold = true; break; case 'W': // warning color color = 3; bold = true; break; case 'R': // error color color = 1; bold = true; break; case 'F': // fancy color color = 2; bold = true; break; } } color_set(color, NULL); if (bold) attron(A_BOLD); else attroff(A_BOLD); } void Console::draw_background() { color_set(0, NULL); attroff(A_BOLD); bkgdset(' '); clear(); // draw version string color_set(2, NULL); std::string versionstr("Project::OSiRiON "); versionstr.append(core::version()); int y = console_width - versionstr.size(); if (y < 0) y = 0; mvaddnstr(0, y, versionstr.c_str(), console_width); } void Console::draw_status() { if (core::game()) { attroff(A_BOLD); color_set(2, NULL); std::stringstream status; // system time int year = 0, month = 0, day = 0, hour = 0, min = 0, sec = 0, milliseconds = 0; sys::get_localtime(year, month, day, hour, min, sec, milliseconds); status << std::setw(4) << std::setfill('0') << year << "-" << std::setw(2) << std::setfill('0') << month << "-" << std::setw(2) << std::setfill('0') << day << " " << std::setw(2) << std::setfill(' ') << hour << ":" << std::setw(2) << std::setfill('0') << min << ":" << std::setw(2) << std::setfill('0') << sec << " " << std::setw(2) << " "; // uptime float uptime = core::game()->time(); const int uptime_days = (int) floorf(uptime / (24.0f * 3600.0f)); uptime -= uptime_days * 24.0f * 3600.0f; const int uptime_hours = (int) floorf(uptime / 3600.0f); uptime -= uptime_hours * 3600.0f; const int uptime_minutes = (int) floorf(uptime / 60.0f); uptime -= uptime_minutes * 60.0f; const int uptime_seconds = (int) floorf(uptime); status << "uptime "; if (uptime_days > 0) { status << uptime_days << " " << aux::plural("day", uptime_days) << " "; } status << std::setfill(' ') << std::setw(2) << uptime_hours << ":" << std::setfill('0') << std::setw(2) << uptime_minutes << ":" << std::setfill('0') << std::setw(2) << uptime_seconds; mvaddnstr(0, 0, status.str().c_str(), status.str().size()); } } void Console::draw_text() { if ((console_width < 4) || (console_height < 3)) return; Queue lines; int bottom = (int) log().size() - console_scroll; int current_line = 0; // parse console text, wrap long lines for (Queue::iterator it = log().begin(); it != log().end() && current_line < bottom; it++) { if (current_line >= bottom - console_height - 1) { std::string linedata(*it); linedata += '\n'; std::string word; int word_length = 0; std::string line; int line_length = 0; const char *c = linedata.c_str(); char pen = 'N'; char wordpen = 'N'; while (*c) { // color code if (aux::is_color_code(c)) { c++; pen = *c; word += '^'; word += pen; } // new word, wrap if necessary else if ((*c == '\n') || (*c == ' ')) { if (line_length + word_length > console_width) { if (line.size()) { lines.push_back(line); line.clear(); line += '^'; line += wordpen; line_length = 0; } } line.append(word); line_length += word_length; word.clear(); word_length = 0; wordpen = pen; // new line if (*c == '\n') { lines.push_back(line); line.clear(); line_length = 0; // new word } else if (*c == ' ') { line += ' '; line_length++; } } // new character else { word += *c; word_length++; if (word_length == console_width) { if (line.size()) { lines.push_back(line); line.clear(); line += '^'; line += wordpen; line_length = 0; } line.append(word); line_length = word_length; word.clear(); word_length = 0; wordpen = pen; } } c++; } } current_line++; } int y = console_height - 1; color_set(0, NULL); attroff(A_BOLD); for (Queue::reverse_iterator rit = lines.rbegin(); (y > 0) && (rit != lines.rend()); ++rit) { const char *c = (*rit).c_str(); int x = 0; while (*c) { if (aux::is_color_code(c)) { set_color(c); c++; } else { mvaddnstr(y, x, c, 1); x++; } c++; } y--; } } void Console::draw_input() { color_set(0, NULL); attron(A_BOLD); // draw input text mvaddstr(console_height, 0, ">"); mvaddstr(console_height, 1, (*history_pos).c_str()); // fill the remainder with spaces for (int i = 1 + (*history_pos).size(); i < console_width; i++) addch(' '); } void Console::draw() { if (!console_initialized) return; #ifdef _WIN32 console_width = stdwin->_maxx - 1; console_height = stdwin->_maxy - 1; #else console_width = stdwin->_maxx; console_height = stdwin->_maxy; #endif draw_background(); draw_status(); draw_text(); draw_input(); move(console_height, 1 + input_pos); wrefresh(stdwin); console_updated = false; } void Console::frame() { const size_t scroll_offset = 3; History::reverse_iterator upit; if (!console_initialized) return; bool input_updated = false; int key = wgetch(stdwin); while (key != ERR) { if (key == KEY_BACKSPACE || key == 8 || key == 127) { if ((*history_pos).size() && input_pos) { (*history_pos).erase(input_pos - 1, 1); input_pos--; input_updated = true; } break; } else if (key == KEY_STAB || key == 9) { core::CommandBuffer::complete((*history_pos), input_pos); input_updated = true; break; } else if (key == KEY_LEFT) { if (input_pos > 0) { input_pos--; } input_updated = true; break; } else if (key == KEY_RIGHT) { if (input_pos < (*history_pos).size()) { input_pos++; } input_updated = true; break; } else if (key == KEY_HOME) { input_pos = 0; input_updated = true; break; } else if (key == KEY_END) { input_pos = (*history_pos).size(); input_updated = true; break; } else if (key == KEY_UP) { upit = history_pos; ++upit; if (upit != history.rend()) { history_pos = upit; input_pos = (*history_pos).size(); input_updated = true; } break; } else if (key == KEY_DOWN) { if (history_pos != history.rbegin()) { --history_pos; input_pos = (*history_pos).size(); input_updated = true; } break; } else if (key == KEY_ENTER || key == '\n') { if ((*history_pos).size()) { // store input into history while (history.size() >= MAXHISTOLINES) { history.pop_front(); } core::cmd() << (*history_pos) << std::endl; con_print << "^B>" << (*history_pos) << std::endl; (*history.rbegin()) = (*history_pos); history.push_back(""); history_pos = history.rbegin(); input_pos = 0; console_updated = true; } break; } else if (key == KEY_PPAGE) { console_scroll += scroll_offset; if (console_scroll > log().size()) console_scroll = log().size(); console_updated = true; break; } else if (key == KEY_NPAGE) { if (console_scroll > scroll_offset) console_scroll -= scroll_offset; else console_scroll = 0; console_updated = true; break; } else if ((key >= 32) && (key < 127) && ((*history_pos).size() < MAXCMDSIZE)) { if (input_pos == (*history_pos).size()) { (*history_pos) += (char)key; } else { (*history_pos).insert(input_pos, 1, (char)key); } input_pos++; input_updated = true; } key = wgetch(stdwin); } if (console_updated) { draw(); } else { if (input_updated) { draw_input(); } if (core::game() && (roundf(core::game()->time()) != prev_time)) { draw_status(); prev_time = roundf(core::game()->time()); input_updated = true; } if (input_updated) { // move the cursor to input position move(console_height, 1 + input_pos); wrefresh(stdwin); } } } #endif // HAVE_CURSES }