1123 lines
31 KiB
C++
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() < 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");
|
|
|
|
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;
|
|
}
|