/* Copyright (C) 1996-2001 Id Software, Inc. Copyright (C) 2002-2009 John Fitzgibbons and others Copyright (C) 2010-2014 QuakeSpasm developers 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 2 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, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ // console.c #include #include #include #include #include #include #include #ifdef _WIN32 #include #else #include #endif #include "quakedef.hpp" int con_linewidth; float con_cursorspeed = 4; #define CON_TEXTSIZE (1024 * 1024) //ericw -- was 65536. johnfitz -- new default size #define CON_MINSIZE 16384 //johnfitz -- old default, now the minimum size int con_buffersize; //johnfitz -- user can now override default int con_current; // where next message will be printed int con_x; // offset in current line for next print char *con_text = NULL; convar con_notifytime{"con_notifytime", "3"}; //seconds convar con_logcenterprint{"con_logcenterprint", "1"}; //johnfitz char con_lastcenterstring[1024]; //johnfitz #define NUM_CON_TIMES 4 float con_times[NUM_CON_TIMES]; // realtime time the line was generated // for transparent notify lines int con_vislines; bool con_debuglog = false; extern int history_line; constexpr std::size_t MAX_PRINT_MSG = 4096; namespace console { bool initialized{false}; bool forced_up{false}; // because no entities to refresh int total_lines{0}; // total lines in console scrollback int back_scroll{0}; // lines up from bottom to display namespace { void _toggle_console() { toggle_console(); } void _message_mode() { if (cls.state != ca_connected || cls.demoplayback) return; chat_team = false; key_dest = key_message; } void _message_mode2() { if (cls.state != ca_connected || cls.demoplayback) return; chat_team = true; key_dest = key_message; } void _clear() { if (con_text) std::memset(con_text, ' ', con_buffersize); //johnfitz -- con_buffersize replaces CON_TEXTSIZE console::back_scroll = 0; //johnfitz -- if console is empty, being scrolled up is confusing } void _dump() { int l, x; const char *line; FILE *f; char buffer[1024]; char name[MAX_OSPATH]; q_snprintf(name, sizeof(name), "%s/condump.txt", com_gamedir); COM_CreatePath(name); f = fopen(name, "w"); if (!f) { info("ERROR: couldn't open file %s.\n", name); return; } // skip initial empty lines for (l = con_current - total_lines + 1; l <= con_current; l++) { line = con_text + (l % total_lines) * con_linewidth; for (x = 0; x < con_linewidth; x++) if (line[x] != ' ') break; if (x != con_linewidth) break; } // write the remaining lines buffer[con_linewidth] = 0; for (; l <= con_current; l++) { line = con_text + (l % total_lines) * con_linewidth; strncpy(buffer, line, con_linewidth); for (x = con_linewidth - 1; x >= 0; x--) { if (buffer[x] == ' ') buffer[x] = 0; else break; } for (x = 0; buffer[x]; x++) buffer[x] &= 0x7f; fprintf(f, "%s\n", buffer); } fclose(f); info("Dumped console text to %s.\n", name); } void print(const std::string_view text) { int y; int l; static int cr; int mask; bool boundary; //console::back_scroll = 0; //johnfitz -- better console scrolling std::istringstream ss{text}; switch (ss.peek()) { case 1: mask = 128; S_LocalSound("misc/talk.wav"); // play talk wav ss.get(); break; case 2: mask = 128; ss.get(); break; default: mask = 0; break; } boundary = true; while (!ss.eof()) { const int c = ss.peek(); if (ss.eof()) { break; } if (c <= ' ') { boundary = true; } else if (boundary) { // count word length for (l = 0; l < con_linewidth; l++) { if (ss.peek() <= ' ') break; ss.get(); } for (auto i = l; i > 0; i--) { ss.unget(); } // word wrap if (l != con_linewidth && (con_x + l > con_linewidth)) con_x = 0; boundary = false; } ss.get(); if (cr) { con_current--; cr = false; } if (!con_x) { line_feed(); // mark time for transparent overlay if (con_current >= 0) con_times[con_current % NUM_CON_TIMES] = realtime; } switch (c) { case '\n': con_x = 0; break; case '\r': con_x = 0; cr = 1; break; default: // display character and advance y = con_current % total_lines; con_text[y * con_linewidth + con_x] = c | mask; con_x++; if (con_x >= con_linewidth) con_x = 0; break; } } } void center_print(int line_width, const char *fmt, ...) { va_list argptr; char msg[MAX_PRINT_MSG]; //the original message char line[MAX_PRINT_MSG]; //one line from the message char spaces[21]; //buffer for spaces char *src, *dst; int len, s; va_start(argptr, fmt); q_vsnprintf(msg, sizeof(msg), fmt, argptr); va_end(argptr); line_width = std::min(line_width, con_linewidth); for (src = msg; *src;) { dst = line; while (*src && *src != '\n') *dst++ = *src++; *dst = 0; if (*src == '\n') src++; len = strlen(line); if (len < line_width) { s = (line_width - len) / 2; memset(spaces, ' ', s); spaces[s] = 0; info("%s%s\n", spaces, line); } else info("%s\n", line); } } } void init() { if (const auto i = common::check_param("-consize"); i.has_value() && i.value() < com_argc - 1) { con_buffersize = std::atoi(com_argv[i.value() + 1]) * 1024; if (con_buffersize < CON_MINSIZE) con_buffersize = CON_MINSIZE; } else con_buffersize = CON_TEXTSIZE; //johnfitz con_text = (char *) Hunk_AllocName(con_buffersize, "context"); //johnfitz -- con_buffersize replaces CON_TEXTSIZE std::memset(con_text, ' ', con_buffersize); //johnfitz -- con_buffersize replaces CON_TEXTSIZE con_linewidth = -1; //johnfitz -- no need to run Con_CheckResize here con_linewidth = 38; total_lines = con_buffersize / con_linewidth; //johnfitz -- con_buffersize replaces CON_TEXTSIZE console::back_scroll = 0; con_current = total_lines - 1; //johnfitz info("Console initialized.\n"); con_notifytime.inscribe(); con_logcenterprint.inscribe(); //johnfitz command::add("toggleconsole", _toggle_console); command::add("messagemode", _message_mode); command::add("messagemode2", _message_mode2); command::add("clear", _clear); command::add("condump", _dump); //johnfitz initialized = true; } void line_feed() { //johnfitz -- improved scrolling if (console::back_scroll) console::back_scroll++; if (console::back_scroll > total_lines - (glheight >> 3) - 1) console::back_scroll = total_lines - (glheight >> 3) - 1; //johnfitz con_x = 0; con_current++; std::memset(&con_text[(con_current % total_lines) * con_linewidth], ' ', con_linewidth); } void info(const char *fmt, ...) { va_list argptr; char msg[MAX_PRINT_MSG]; static bool inupdate; va_start(argptr, fmt); q_vsnprintf(msg, sizeof(msg), fmt, argptr); va_end(argptr); // also echo to debugging console Sys_Printf("%s", msg); // log all messages to file if (con_debuglog) { Con_DebugLog(msg); } if (!initialized) { return; } if (cls.state == ca_dedicated) return; // no graphics mode // write it to the scrollable buffer print(msg); // update the screen if the console is displayed if (cls.signon != SIGNONS && !scr_disabled_for_loading) { // protect against infinite loop if something in SCR_UpdateScreen calls // Con_Printd if (!inupdate) { inupdate = true; SCR_UpdateScreen(); inupdate = false; } } } void warn(const char *fmt, ...) { va_list argptr; char msg[MAX_PRINT_MSG]; va_start(argptr, fmt); std::vsnprintf(msg, sizeof(msg), fmt, argptr); va_end(argptr); safe_print("\x02Warning: "); info("%s", msg); } void dev_warn(const char *fmt, ...) { va_list argptr; char msg[MAX_PRINT_MSG]; if (!developer.value) return; // don't confuse non-developers with techie stuff... va_start(argptr, fmt); q_vsnprintf(msg, sizeof(msg), fmt, argptr); va_end(argptr); safe_print("\x02Warning: "); info("%s", msg); } void debug(const char *fmt, ...) { va_list argptr; char msg[MAX_PRINT_MSG]; if (developer.value == 0) return; // don't confuse non-developers with techie stuff... va_start(argptr, fmt); q_vsnprintf(msg, sizeof(msg), fmt, argptr); va_end(argptr); safe_print("%s", msg); //johnfitz -- was console::info } void trace(const char *fmt, ...) { va_list argptr; char msg[MAX_PRINT_MSG]; if (developer.value >= 2) { va_start(argptr, fmt); q_vsnprintf(msg, sizeof(msg), fmt, argptr); va_end(argptr); info("%s", msg); } } void safe_print(const char *fmt, ...) { va_list argptr; char msg[MAX_PRINT_MSG]; int temp; va_start(argptr, fmt); q_vsnprintf(msg, sizeof(msg), fmt, argptr); va_end(argptr); temp = scr_disabled_for_loading; scr_disabled_for_loading = true; info("%s", msg); scr_disabled_for_loading = temp; } void toggle_console() { if (key_dest == key_console/* || (key_dest == key_game && con_forcedup)*/) { key_lines[edit_line][1] = 0; // clear any typing key_linepos = 1; console::back_scroll = 0; //johnfitz -- toggleconsole should return you to the bottom of the scrollback history_line = edit_line; //johnfitz -- it should also return you to the bottom of the command history if (cls.state == ca_connected) { IN_Activate(); key_dest = key_game; } else { M_Menu_Main_f(); } } else { IN_Deactivate(modestate == MS_WINDOWED); key_dest = key_console; } SCR_EndLoadingPlaque(); memset(con_times, 0, sizeof(con_times)); } } /* ================ Con_Quakebar -- johnfitz -- returns a bar of the desired length, but never wider than the console includes a newline, unless len >= con_linewidth. ================ */ const char *Con_Quakebar(int len) { static char bar[42]; int i; len = std::min(len, (int) sizeof(bar) - 2); len = std::min(len, con_linewidth); bar[0] = '\35'; for (i = 1; i < len - 1; i++) bar[i] = '\36'; bar[len - 1] = '\37'; if (len < con_linewidth) { bar[len] = '\n'; bar[len + 1] = 0; } else bar[len] = 0; return bar; } /* ================ Con_ClearNotify ================ */ void Con_ClearNotify(void) { int i; for (i = 0; i < NUM_CON_TIMES; i++) con_times[i] = 0; } /* ================ Con_CheckResize If the line width has changed, reformat the buffer. ================ */ void Con_CheckResize(void) { int i, j, width, oldwidth, oldtotallines, numlines, numchars; char *tbuf; //johnfitz -- tbuf no longer a static array int mark; //johnfitz width = (vid.conwidth >> 3) - 2; //johnfitz -- use vid.conwidth instead of vid.width if (width == con_linewidth) return; oldwidth = con_linewidth; con_linewidth = width; oldtotallines = console::total_lines; console::total_lines = con_buffersize / con_linewidth; //johnfitz -- con_buffersize replaces CON_TEXTSIZE numlines = oldtotallines; if (console::total_lines < numlines) numlines = console::total_lines; numchars = oldwidth; if (con_linewidth < numchars) numchars = con_linewidth; mark = Hunk_LowMark(); //johnfitz tbuf = (char *) Hunk_Alloc(con_buffersize); //johnfitz std::memcpy(tbuf, con_text, con_buffersize); //johnfitz -- con_buffersize replaces CON_TEXTSIZE std::memset(con_text, ' ', con_buffersize); //johnfitz -- con_buffersize replaces CON_TEXTSIZE for (i = 0; i < numlines; i++) { for (j = 0; j < numchars; j++) { con_text[(console::total_lines - 1 - i) * con_linewidth + j] = tbuf[((con_current - i + oldtotallines) % oldtotallines) * oldwidth + j]; } } Hunk_FreeToLowMark(mark); //johnfitz Con_ClearNotify(); console::back_scroll = 0; con_current = console::total_lines - 1; } // borrowed from uhexen2 by S.A. for new procs, LOG_Init, LOG_Close static char logfilename[MAX_OSPATH]; // current logfile name static int log_fd = -1; // log file descriptor /* ================ Con_DebugLog ================ */ void Con_DebugLog(const char *msg) { if (log_fd == -1) return; if (write(log_fd, msg, strlen(msg)) < 0) { close(log_fd); log_fd = -1; con_debuglog = false; fprintf(stderr, "Error writing to log file\n"); } } /* ================== Con_LogCenterPrint -- johnfitz -- echo centerprint message to the console ================== */ void Con_LogCenterPrint(const char *str) { if (!strcmp(str, con_lastcenterstring)) return; //ignore duplicates if (cl.gametype == GAME_DEATHMATCH && con_logcenterprint.value != 2) return; //don't log in deathmatch strcpy(con_lastcenterstring, str); if (con_logcenterprint.value) { console::info("%s", Con_Quakebar(40)); console::center_print(40, "%s\n", str); console::info("%s", Con_Quakebar(40)); Con_ClearNotify(); } } /* ============================================================================== TAB COMPLETION ============================================================================== */ //johnfitz -- tab completion stuff //unique defs char key_tabpartial[MAXCMDLINE]; typedef struct tab_s { const char *name; const char *type; struct tab_s *next; struct tab_s *prev; } tab_t; tab_t *tablist; //defs from elsewhere extern bool keydown[256]; #define MAX_ALIAS_NAME 32 /* ============ AddToTabList -- johnfitz tablist is a doubly-linked loop, alphabetized by name ============ */ // bash_partial is the string that can be expanded, // aka Linux Bash shell. -- S.A. static char bash_partial[80]; static bool bash_singlematch; void AddToTabList(const char *name, const char *type) { tab_t *t, *insert; char *i_bash; const char *i_name; if (!*bash_partial) { strncpy(bash_partial, name, 79); bash_partial[79] = '\0'; } else { bash_singlematch = 0; // find max common between bash_partial and name i_bash = bash_partial; i_name = name; while (*i_bash && (*i_bash == *i_name)) { i_bash++; i_name++; } *i_bash = 0; } t = (tab_t *) Hunk_Alloc(sizeof(tab_t)); t->name = name; t->type = type; if (!tablist) //create list { tablist = t; t->next = t; t->prev = t; } else if (strcmp(name, tablist->name) < 0) //insert at front { t->next = tablist; t->prev = tablist->prev; t->next->prev = t; t->prev->next = t; tablist = t; } else //insert later { insert = tablist; do { if (strcmp(name, insert->name) < 0) break; insert = insert->next; } while (insert != tablist); t->next = insert; t->prev = insert->prev; t->next->prev = t; t->prev->next = t; } } typedef struct arg_completion_type_s { const char *command; filelist_item_t **filelist; } arg_completion_type_t; static const arg_completion_type_t arg_completion_types[] = { {"map ", &extralevels}, {"changelevel ", &extralevels}, {"game ", &modlist}, {"record ", &demolist}, {"playdemo ", &demolist}, {"timedemo ", &demolist} }; static constexpr int num_arg_completion_types = std::size(arg_completion_types); /* ============ FindCompletion -- stevenaaus ============ */ const char *FindCompletion(const char *partial, filelist_item_t *filelist, int *nummatches_out) { static char matched[40]; char *i_matched, *i_name; filelist_item_t *file; int init, match, plen; memset(matched, 0, sizeof(matched)); plen = strlen(partial); match = 0; for (file = filelist, init = 0; file; file = file->next) { if (!strncmp(file->name, partial, plen)) { if (init == 0) { init = 1; strncpy(matched, file->name, sizeof(matched) - 1); matched[sizeof(matched) - 1] = '\0'; } else { // find max common i_matched = matched; i_name = file->name; while (*i_matched && (*i_matched == *i_name)) { i_matched++; i_name++; } *i_matched = 0; } match++; } } *nummatches_out = match; if (match > 1) { for (file = filelist; file; file = file->next) { if (!strncmp(file->name, partial, plen)) console::safe_print(" %s\n", file->name); } console::safe_print("\n"); } return matched; } /* ============ BuildTabList -- johnfitz ============ */ void BuildTabList(const char *partial) { int len; tablist = NULL; len = strlen(partial); bash_partial[0] = 0; bash_singlematch = 1; for (const auto cvar: convar::get_variables()) if (!std::strncmp(partial, cvar->name.c_str(), len)) AddToTabList(cvar->name.c_str(), "cvar"); for (const auto &ccmd: command::get_commands()) if (!std::strncmp(partial, ccmd.name.c_str(), len)) AddToTabList(ccmd.name.c_str(), "command"); for (const auto &alias: command::get_aliases()) if (!std::strncmp(partial, alias.name.c_str(), len)) AddToTabList(alias.name.c_str(), "alias"); } /* ============ Con_TabComplete -- johnfitz ============ */ void Con_TabComplete(void) { char partial[MAXCMDLINE]; const char *match; static char *c; tab_t *t; int mark, i, j; // if editline is empty, return if (key_lines[edit_line][1] == 0) return; // get partial string (space -> cursor) if (!key_tabpartial[0]) //first time through, find new insert point. (Otherwise, use previous.) { //work back from cursor until you find a space, quote, semicolon, or prompt c = key_lines[edit_line] + key_linepos - 1; //start one space left of cursor while (*c != ' ' && *c != '\"' && *c != ';' && c != key_lines[edit_line]) c--; c++; //start 1 char after the separator we just found } for (i = 0; c + i < key_lines[edit_line] + key_linepos; i++) partial[i] = c[i]; partial[i] = 0; // Map autocomplete function -- S.A // Since we don't have argument completion, this hack will do for now... for (j = 0; j < num_arg_completion_types; j++) { // arg_completion contains a command we can complete the arguments // for (like "map ") and a list of all the maps. arg_completion_type_t arg_completion = arg_completion_types[j]; const char *command_name = arg_completion.command; if (!strncmp(key_lines[edit_line] + 1, command_name, strlen(command_name))) { int nummatches = 0; const char *matched_map = FindCompletion(partial, *arg_completion.filelist, &nummatches); if (!*matched_map) return; q_strlcpy(partial, matched_map, MAXCMDLINE); *c = '\0'; q_strlcat(key_lines[edit_line], partial, MAXCMDLINE); key_linepos = c - key_lines[edit_line] + std::strlen(matched_map); //set new cursor position if (key_linepos >= MAXCMDLINE) key_linepos = MAXCMDLINE - 1; // if only one match, append a space if (key_linepos < MAXCMDLINE - 1 && key_lines[edit_line][key_linepos] == 0 && (nummatches == 1)) { key_lines[edit_line][key_linepos] = ' '; key_linepos++; key_lines[edit_line][key_linepos] = 0; } c = key_lines[edit_line] + key_linepos; return; } } //if partial is empty, return if (partial[0] == 0) return; //trim trailing space becuase it screws up string comparisons if (i > 0 && partial[i - 1] == ' ') partial[i - 1] = 0; // find a match mark = Hunk_LowMark(); if (!key_tabpartial[0]) //first time through { q_strlcpy(key_tabpartial, partial, MAXCMDLINE); BuildTabList(key_tabpartial); if (!tablist) return; // print list if length > 1 if (tablist->next != tablist) { t = tablist; console::safe_print("\n"); do { console::safe_print(" %s (%s)\n", t->name, t->type); t = t->next; } while (t != tablist); console::safe_print("\n"); } // match = tablist->name; // First time, just show maximum matching chars -- S.A. match = bash_partial; } else { BuildTabList(key_tabpartial); if (!tablist) return; //find current match -- can't save a pointer because the list will be rebuilt each time t = tablist; match = keydown[K_SHIFT] ? t->prev->name : t->name; do { if (!std::strcmp(t->name, partial)) { match = keydown[K_SHIFT] ? t->prev->name : t->next->name; break; } t = t->next; } while (t != tablist); } Hunk_FreeToLowMark(mark); //it's okay to free it here because match is a pointer to persistent data // insert new match into edit line q_strlcpy(partial, match, MAXCMDLINE); //first copy match string q_strlcat(partial, key_lines[edit_line] + key_linepos, MAXCMDLINE); //then add chars after cursor *c = '\0'; //now copy all of this into edit line q_strlcat(key_lines[edit_line], partial, MAXCMDLINE); key_linepos = c - key_lines[edit_line] + std::strlen(match); //set new cursor position if (key_linepos >= MAXCMDLINE) key_linepos = MAXCMDLINE - 1; // if cursor is at end of string, let's append a space to make life easier if (key_linepos < MAXCMDLINE - 1 && key_lines[edit_line][key_linepos] == 0 && bash_singlematch) { key_lines[edit_line][key_linepos] = ' '; key_linepos++; key_lines[edit_line][key_linepos] = 0; // S.A.: the map argument completion (may be in combination with the bash-style // display behavior changes, causes weirdness when completing the arguments for // the changelevel command. the line below "fixes" it, although I'm not sure about // the reason, yet, neither do I know any possible side effects of it: c = key_lines[edit_line] + key_linepos; } } /* ============================================================================== DRAWING ============================================================================== */ /* ================ Con_DrawNotify Draws the last few lines of output transparently over the game top ================ */ void Con_DrawNotify(void) { int i, x, v; const char *text; float time; GL_SetCanvas(CANVAS_CONSOLE); //johnfitz v = vid.conheight; //johnfitz for (i = con_current - NUM_CON_TIMES + 1; i <= con_current; i++) { if (i < 0) continue; time = con_times[i % NUM_CON_TIMES]; if (time == 0) continue; time = realtime - time; if (time > con_notifytime.value) continue; text = con_text + (i % console::total_lines) * con_linewidth; clearnotify = 0; for (x = 0; x < con_linewidth; x++) Draw_Character((x + 1) << 3, v, text[x]); v += 8; scr_tileclear_updates = 0; //johnfitz } if (key_dest == key_message) { clearnotify = 0; if (chat_team) { Draw_String(8, v, "say_team:"); x = 11; } else { Draw_String(8, v, "say:"); x = 6; } text = Key_GetChatBuffer(); i = Key_GetChatMsgLen(); if (i > con_linewidth - x - 1) text += i - con_linewidth + x + 1; while (*text) { Draw_Character(x << 3, v, *text); x++; text++; } Draw_Character(x << 3, v, 10 + ((int) (realtime * con_cursorspeed) & 1)); v += 8; scr_tileclear_updates = 0; //johnfitz } } /* ================ Con_DrawInput -- johnfitz -- modified to allow insert editing The input line scrolls horizontally if typing goes beyond the right edge ================ */ extern qpic_t *pic_ovr, *pic_ins; //johnfitz -- new cursor handling void Con_DrawInput(void) { int i, ofs; if (key_dest != key_console && !console::forced_up) return; // don't draw anything // prestep if horizontally scrolling if (key_linepos >= con_linewidth) ofs = 1 + key_linepos - con_linewidth; else ofs = 0; // draw input string for (i = 0; key_lines[edit_line][i + ofs] && i < con_linewidth; i++) Draw_Character((i + 1) << 3, vid.conheight - 16, key_lines[edit_line][i + ofs]); // johnfitz -- new cursor handling if (!((int) ((realtime - key_blinktime) * con_cursorspeed) & 1)) { i = key_linepos - ofs; Draw_Pic((i + 1) << 3, vid.conheight - 16, key_insert ? pic_ins : pic_ovr); } } /* ================ Con_DrawConsole -- johnfitz -- heavy revision Draws the console with the solid background The typing input line at the bottom should only be drawn if typing is allowed ================ */ void Con_DrawConsole(int lines, bool drawinput) { int i, x, y, j, sb, rows; const char *text; char ver[32]; if (lines <= 0) return; con_vislines = lines * vid.conheight / glheight; GL_SetCanvas(CANVAS_CONSOLE); // draw the background Draw_ConsoleBackground(); // draw the buffer text rows = (con_vislines + 7) / 8; y = vid.conheight - rows * 8; rows -= 2; //for input and version lines sb = (console::back_scroll) ? 2 : 0; for (i = con_current - rows + 1; i <= con_current - sb; i++, y += 8) { j = i - console::back_scroll; if (j < 0) j = 0; text = con_text + (j % console::total_lines) * con_linewidth; for (x = 0; x < con_linewidth; x++) Draw_Character((x + 1) << 3, y, text[x]); } // draw scrollback arrows if (console::back_scroll) { y += 8; // blank line for (x = 0; x < con_linewidth; x += 4) Draw_Character((x + 1) << 3, y, '^'); y += 8; } // draw the input prompt, user text, and cursor if (drawinput) Con_DrawInput(); //draw version number in bottom right y += 8; q_snprintf(ver, sizeof(ver), "QuakeSpasm " QUAKESPASM_VER_STRING); for (x = 0; x < (int) strlen(ver); x++) Draw_Character((con_linewidth - strlen(ver) + x + 2) << 3, y, ver[x] /*+ 128*/); } /* ================== Con_NotifyBox ================== */ void Con_NotifyBox(const char *text) { double t1, t2; int lastkey, lastchar; // during startup for sound / cd warnings console::info("\n\n%s", Con_Quakebar(40)); //johnfitz console::info("%s", text); console::info("Press a key.\n"); console::info("%s", Con_Quakebar(40)); //johnfitz IN_Deactivate(modestate == MS_WINDOWED); key_dest = key_console; Key_BeginInputGrab(); do { t1 = Sys_DoubleTime(); SCR_UpdateScreen(); Sys_SendKeyEvents(); Key_GetGrabbedInput(&lastkey, &lastchar); Sys_Sleep(16); t2 = Sys_DoubleTime(); realtime += t2 - t1; // make the cursor blink } while (lastkey == -1 && lastchar == -1); Key_EndInputGrab(); console::info("\n"); IN_Activate(); key_dest = key_game; realtime = 0; // put the cursor back to invisible } void LOG_Init(quakeparms_t *parms) { time_t inittime; char session[24]; if (!common::check_param("-condebug").has_value()) return; inittime = time(NULL); strftime(session, sizeof(session), "%m/%d/%Y %H:%M:%S", localtime(&inittime)); q_snprintf(logfilename, sizeof(logfilename), "%s/qconsole.log", parms->basedir); // unlink (logfilename); log_fd = open(logfilename, O_WRONLY | O_CREAT | O_TRUNC, 0666); if (log_fd == -1) { fprintf(stderr, "Error: Unable to create log file %s\n", logfilename); return; } con_debuglog = true; Con_DebugLog(va("LOG started on: %s \n", session)); } void LOG_Close(void) { if (log_fd == -1) return; close(log_fd); log_fd = -1; }