q3rally/engine/code/sys/con_tty.c
zturtleman 0d5fb492cd ioquake3 resync to revision 3444 from 3393.
Fix GCC 6 misleading-indentation warning
add SECURITY.md
OpenGL2: Restore adding fixed ambient light when HDR is enabled
Few LCC memory fixes.
fix a few potential buffer overwrite in Game VM
Enable compiler optimization on all macOS architectures
Don't allow qagame module to create "botlib.log" at ANY filesystem location
Make FS_BuildOSPath for botlib.log consistent with typical usage
tiny readme thing
Remove extra plus sign from Huff_Compress()
Fix VMs being able to change CVAR_PROTECTED cvars
Don't register fs_game cvar everywhere just to get the value
Don't let VMs change engine latch cvars immediately
Fix fs_game '..' reading outside of home and base path
Fix VMs forcing engine latch cvar to update to latched value
Revert my recent cvar latch changes
Revert "Don't let VMs change engine latch cvars immediately"
Partially revert "Fix fs_game '..' reading outside of home and base path"
Revert "Fix VMs forcing engine latch cvar to update to latched value"
Fix exploit to bypass filename restrictions on Windows
Changes to systemd q3a.service
Fix Q_vsnprintf for mingw-w64
Fix timelimit causing an infinite map ending loop
Fix invalid access to cluster 0 in AAS_AreaRouteToGoalArea()
Fix negative frag/capturelimit causing an infinite map end loop
OpenGL2: Fix dark lightmap on shader in mpteam6
Make FS_InvalidGameDir() consider subdirectories invalid
[qcommon] Remove dead serialization code
[qcommon] Make several zone variables and functions static.
Fix MAC_OS_X_VERSION_MIN_REQUIRED for macOS 10.10 and later
Increase q3_ui .arena filename list buffer size to 4096 bytes
OpenGL2: Fix crash when BSP has deluxe maps and vertex lit surfaces
Support Unicode characters greater than 0xFF in cl_consoleKeys
Fix macOS app bundle with space in name
OpenGL1: Use glGenTextures instead of hardcoded values
Remove CON_FlushIn function and where STDIN needs flushing, use tcflush POSIX function
Update libogg from 1.3.2 to 1.3.3
Rename (already updated) libogg-1.3.2 to libogg-1.3.3
Update libvorbis from 1.3.5 to 1.3.6
* Fix CVE-2018-5146 - out-of-bounds write on codebook decoding.
* Fix CVE-2017-14632 - free() on unitialized data
* Fix CVE-2017-14633 - out-of-bounds read
Rename (already updated) libvorbis-1.3.5 to libvorbis-1.3.6
Update opus from 1.1.4 to 1.2.1
Rename (already updated) opus-1.1.4 to opus-1.2.1
Update opusfile from 0.8 to 0.9
Rename (already updated) opusfile-0.8 to opusfile-0.9
First swing at a CONTRIBUTING.md
Allow loading system OpenAL library on macOS again
Remove duplicate setting of FREETYPE_CFLAGS in Makefile
Fix exploit to reset player by sending wrong serverId
Fix "Going to CS_ZOMBIE for [clientname]" developer message
Fix MSG_Read*String*() functions not being able to read last byte from message
2018-04-07 23:02:52 +00:00

537 lines
12 KiB
C

/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.
This file is part of Quake III Arena source code.
Quake III Arena source code 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.
Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
===========================================================================
*/
#include "../qcommon/q_shared.h"
#include "../qcommon/qcommon.h"
#include "sys_local.h"
#ifndef DEDICATED
#include "../client/client.h"
#endif
#include <unistd.h>
#include <signal.h>
#include <termios.h>
#include <fcntl.h>
#include <sys/time.h>
/*
=============================================================
tty console routines
NOTE: if the user is editing a line when something gets printed to the early
console then it won't look good so we provide CON_Hide and CON_Show to be
called before and after a stdout or stderr output
=============================================================
*/
extern qboolean stdinIsATTY;
static qboolean stdin_active;
// general flag to tell about tty console mode
static qboolean ttycon_on = qfalse;
static int ttycon_hide = 0;
static int ttycon_show_overdue = 0;
// some key codes that the terminal may be using, initialised on start up
static int TTY_erase;
static int TTY_eof;
static struct termios TTY_tc;
static field_t TTY_con;
// This is somewhat of aduplicate of the graphical console history
// but it's safer more modular to have our own here
#define CON_HISTORY 32
static field_t ttyEditLines[ CON_HISTORY ];
static int hist_current = -1, hist_count = 0;
#ifndef DEDICATED
// Don't use "]" as it would be the same as in-game console,
// this makes it clear where input came from.
#define TTY_CONSOLE_PROMPT "tty]"
#else
#define TTY_CONSOLE_PROMPT "]"
#endif
/*
==================
CON_Back
Output a backspace
NOTE: it seems on some terminals just sending '\b' is not enough so instead we
send "\b \b"
(FIXME there may be a way to find out if '\b' alone would work though)
==================
*/
static void CON_Back( void )
{
char key;
size_t UNUSED_VAR size;
key = '\b';
size = write(STDOUT_FILENO, &key, 1);
key = ' ';
size = write(STDOUT_FILENO, &key, 1);
key = '\b';
size = write(STDOUT_FILENO, &key, 1);
}
/*
==================
CON_Hide
Clear the display of the line currently edited
bring cursor back to beginning of line
==================
*/
static void CON_Hide( void )
{
if( ttycon_on )
{
int i;
if (ttycon_hide)
{
ttycon_hide++;
return;
}
if (TTY_con.cursor>0)
{
for (i=0; i<TTY_con.cursor; i++)
{
CON_Back();
}
}
// Delete prompt
for (i = strlen(TTY_CONSOLE_PROMPT); i > 0; i--) {
CON_Back();
}
ttycon_hide++;
}
}
/*
==================
CON_Show
Show the current line
FIXME need to position the cursor if needed?
==================
*/
static void CON_Show( void )
{
if( ttycon_on )
{
int i;
assert(ttycon_hide>0);
ttycon_hide--;
if (ttycon_hide == 0)
{
size_t UNUSED_VAR size;
size = write(STDOUT_FILENO, TTY_CONSOLE_PROMPT, strlen(TTY_CONSOLE_PROMPT));
if (TTY_con.cursor)
{
for (i=0; i<TTY_con.cursor; i++)
{
size = write(STDOUT_FILENO, TTY_con.buffer+i, 1);
}
}
}
}
}
/*
==================
CON_Shutdown
Never exit without calling this, or your terminal will be left in a pretty bad state
==================
*/
void CON_Shutdown( void )
{
if (ttycon_on)
{
CON_Hide();
tcsetattr (STDIN_FILENO, TCSADRAIN, &TTY_tc);
}
// Restore blocking to stdin reads
fcntl(STDIN_FILENO, F_SETFL, fcntl(STDIN_FILENO, F_GETFL, 0) & ~O_NONBLOCK);
}
/*
==================
Hist_Add
==================
*/
void Hist_Add(field_t *field)
{
int i;
// Don't save blank lines in history.
if (!field->cursor)
return;
assert(hist_count <= CON_HISTORY);
assert(hist_count >= 0);
assert(hist_current >= -1);
assert(hist_current <= hist_count);
// make some room
for (i=CON_HISTORY-1; i>0; i--)
{
ttyEditLines[i] = ttyEditLines[i-1];
}
ttyEditLines[0] = *field;
if (hist_count<CON_HISTORY)
{
hist_count++;
}
hist_current = -1; // re-init
}
/*
==================
Hist_Prev
==================
*/
field_t *Hist_Prev( void )
{
int hist_prev;
assert(hist_count <= CON_HISTORY);
assert(hist_count >= 0);
assert(hist_current >= -1);
assert(hist_current <= hist_count);
hist_prev = hist_current + 1;
if (hist_prev >= hist_count)
{
return NULL;
}
hist_current++;
return &(ttyEditLines[hist_current]);
}
/*
==================
Hist_Next
==================
*/
field_t *Hist_Next( void )
{
assert(hist_count <= CON_HISTORY);
assert(hist_count >= 0);
assert(hist_current >= -1);
assert(hist_current <= hist_count);
if (hist_current >= 0)
{
hist_current--;
}
if (hist_current == -1)
{
return NULL;
}
return &(ttyEditLines[hist_current]);
}
/*
==================
CON_SigCont
Reinitialize console input after receiving SIGCONT, as on Linux the terminal seems to lose all
set attributes if user did CTRL+Z and then does fg again.
==================
*/
void CON_SigCont(int signum)
{
CON_Init();
}
/*
==================
CON_Init
Initialize the console input (tty mode if possible)
==================
*/
void CON_Init( void )
{
struct termios tc;
// If the process is backgrounded (running non interactively)
// then SIGTTIN or SIGTOU is emitted, if not caught, turns into a SIGSTP
signal(SIGTTIN, SIG_IGN);
signal(SIGTTOU, SIG_IGN);
// If SIGCONT is received, reinitialize console
signal(SIGCONT, CON_SigCont);
// Make stdin reads non-blocking
fcntl(STDIN_FILENO, F_SETFL, fcntl(STDIN_FILENO, F_GETFL, 0) | O_NONBLOCK );
if (!stdinIsATTY)
{
Com_Printf("tty console mode disabled\n");
ttycon_on = qfalse;
stdin_active = qtrue;
return;
}
Field_Clear(&TTY_con);
tcgetattr (STDIN_FILENO, &TTY_tc);
TTY_erase = TTY_tc.c_cc[VERASE];
TTY_eof = TTY_tc.c_cc[VEOF];
tc = TTY_tc;
/*
ECHO: don't echo input characters
ICANON: enable canonical mode. This enables the special
characters EOF, EOL, EOL2, ERASE, KILL, REPRINT,
STATUS, and WERASE, and buffers by lines.
ISIG: when any of the characters INTR, QUIT, SUSP, or
DSUSP are received, generate the corresponding signal
*/
tc.c_lflag &= ~(ECHO | ICANON);
/*
ISTRIP strip off bit 8
INPCK enable input parity checking
*/
tc.c_iflag &= ~(ISTRIP | INPCK);
tc.c_cc[VMIN] = 1;
tc.c_cc[VTIME] = 0;
tcsetattr (STDIN_FILENO, TCSADRAIN, &tc);
ttycon_on = qtrue;
ttycon_hide = 1; // Mark as hidden, so prompt is shown in CON_Show
CON_Show();
}
/*
==================
CON_Input
==================
*/
char *CON_Input( void )
{
// we use this when sending back commands
static char text[MAX_EDIT_LINE];
int avail;
char key;
field_t *history;
size_t UNUSED_VAR size;
if(ttycon_on)
{
avail = read(STDIN_FILENO, &key, 1);
if (avail != -1)
{
// we have something
// backspace?
// NOTE TTimo testing a lot of values .. seems it's the only way to get it to work everywhere
if ((key == TTY_erase) || (key == 127) || (key == 8))
{
if (TTY_con.cursor > 0)
{
TTY_con.cursor--;
TTY_con.buffer[TTY_con.cursor] = '\0';
CON_Back();
}
return NULL;
}
// check if this is a control char
if ((key) && (key) < ' ')
{
if (key == '\n')
{
#ifndef DEDICATED
// if not in the game explicitly prepend a slash if needed
if (clc.state != CA_ACTIVE && con_autochat->integer && TTY_con.cursor &&
TTY_con.buffer[0] != '/' && TTY_con.buffer[0] != '\\')
{
memmove(TTY_con.buffer + 1, TTY_con.buffer, sizeof(TTY_con.buffer) - 1);
TTY_con.buffer[0] = '\\';
TTY_con.cursor++;
}
if (TTY_con.buffer[0] == '/' || TTY_con.buffer[0] == '\\') {
Q_strncpyz(text, TTY_con.buffer + 1, sizeof(text));
} else if (TTY_con.cursor) {
if (con_autochat->integer) {
Com_sprintf(text, sizeof(text), "cmd say %s", TTY_con.buffer);
} else {
Q_strncpyz(text, TTY_con.buffer, sizeof(text));
}
} else {
text[0] = '\0';
}
// push it in history
Hist_Add(&TTY_con);
CON_Hide();
Com_Printf("%s%s\n", TTY_CONSOLE_PROMPT, TTY_con.buffer);
Field_Clear(&TTY_con);
CON_Show();
#else
// push it in history
Hist_Add(&TTY_con);
Q_strncpyz(text, TTY_con.buffer, sizeof(text));
Field_Clear(&TTY_con);
key = '\n';
size = write(STDOUT_FILENO, &key, 1);
size = write(STDOUT_FILENO, TTY_CONSOLE_PROMPT, strlen(TTY_CONSOLE_PROMPT));
#endif
return text;
}
if (key == '\t')
{
CON_Hide();
Field_AutoComplete( &TTY_con );
CON_Show();
return NULL;
}
avail = read(STDIN_FILENO, &key, 1);
if (avail != -1)
{
// VT 100 keys
if (key == '[' || key == 'O')
{
avail = read(STDIN_FILENO, &key, 1);
if (avail != -1)
{
switch (key)
{
case 'A':
history = Hist_Prev();
if (history)
{
CON_Hide();
TTY_con = *history;
CON_Show();
}
tcflush(STDIN_FILENO, TCIFLUSH);
return NULL;
break;
case 'B':
history = Hist_Next();
CON_Hide();
if (history)
{
TTY_con = *history;
} else
{
Field_Clear(&TTY_con);
}
CON_Show();
tcflush(STDIN_FILENO, TCIFLUSH);
return NULL;
break;
case 'C':
return NULL;
case 'D':
return NULL;
}
}
}
}
Com_DPrintf("droping ISCTL sequence: %d, TTY_erase: %d\n", key, TTY_erase);
tcflush(STDIN_FILENO, TCIFLUSH);
return NULL;
}
if (TTY_con.cursor >= sizeof(text) - 1)
return NULL;
// push regular character
TTY_con.buffer[TTY_con.cursor] = key;
TTY_con.cursor++; // next char will always be '\0'
// print the current line (this is differential)
size = write(STDOUT_FILENO, &key, 1);
}
return NULL;
}
else if (stdin_active)
{
int len;
fd_set fdset;
struct timeval timeout;
FD_ZERO(&fdset);
FD_SET(STDIN_FILENO, &fdset); // stdin
timeout.tv_sec = 0;
timeout.tv_usec = 0;
if(select (STDIN_FILENO + 1, &fdset, NULL, NULL, &timeout) == -1 || !FD_ISSET(STDIN_FILENO, &fdset))
return NULL;
len = read(STDIN_FILENO, text, sizeof(text));
if (len == 0)
{ // eof!
stdin_active = qfalse;
return NULL;
}
if (len < 1)
return NULL;
text[len-1] = 0; // rip off the /n and terminate
return text;
}
return NULL;
}
/*
==================
CON_Print
==================
*/
void CON_Print( const char *msg )
{
if (!msg[0])
return;
CON_Hide( );
if( com_ansiColor && com_ansiColor->integer )
Sys_AnsiColorPrint( msg );
else
fputs( msg, stderr );
if (!ttycon_on) {
// CON_Hide didn't do anything.
return;
}
// Only print prompt when msg ends with a newline, otherwise the console
// might get garbled when output does not fit on one line.
if (msg[strlen(msg) - 1] == '\n') {
CON_Show();
// Run CON_Show the number of times it was deferred.
while (ttycon_show_overdue > 0) {
CON_Show();
ttycon_show_overdue--;
}
}
else
{
// Defer calling CON_Show
ttycon_show_overdue++;
}
}