Files

1123 lines
31 KiB
C++

/*
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 <cstring>
#include <sys/types.h>
#include <time.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <ranges>
#include <sstream>
#ifdef _WIN32
#include <io.h>
#else
#include <unistd.h>
#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;
#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
std::string last_center_string{};
namespace {
convar notify_time{"con_notifytime", "3"}; //seconds
convar log_center_print{"con_logcenterprint", "1"}; //johnfitz
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;
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;
}
bool 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() < common::_argv.size() - 1) {
con_buffersize = std::atoi(common::_argv[i.value() + 1].c_str()) * 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");
notify_time.inscribe();
log_center_print.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));
}
void check_resize() {
int i, j;
const int width = (vid.conwidth >> 3) - 2; //johnfitz -- use vid.conwidth instead of vid.width
if (width == con_linewidth)
return;
const int oldwidth = con_linewidth;
con_linewidth = width;
const int oldtotallines = total_lines;
total_lines = con_buffersize / con_linewidth; //johnfitz -- con_buffersize replaces CON_TEXTSIZE
int numlines = oldtotallines;
if (total_lines < numlines)
numlines = total_lines;
int numchars = oldwidth;
if (con_linewidth < numchars)
numchars = con_linewidth;
const int mark = Hunk_LowMark(); //johnfitz
char *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[(total_lines - 1 - i) * con_linewidth + j] =
tbuf[((con_current - i + oldtotallines) % oldtotallines) * oldwidth + j];
}
}
Hunk_FreeToLowMark(mark); //johnfitz
Con_ClearNotify();
back_scroll = 0;
con_current = total_lines - 1;
}
}
/*
================
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;
}
// 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 (str == console::last_center_string)
return; //ignore duplicates
if (cl.gametype == GAME_DEATHMATCH && console::log_center_print.value != 2)
return; //don't log in deathmatch
console::last_center_string = str;
if (console::log_center_print.value != 0) {
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 > console::notify_time.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;
}