UFO: Alien Invasion
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
unix_console.cpp
Go to the documentation of this file.
1 
6 /*
7 Copyright (C) 1997-2001 Id Software, Inc.
8 
9 This program is free software; you can redistribute it and/or
10 modify it under the terms of the GNU General Public License
11 as published by the Free Software Foundation; either version 2
12 of the License, or (at your option) any later version.
13 
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
17 
18 See the GNU General Public License for more details.
19 
20 You should have received a copy of the GNU General Public License
21 along with this program; if not, write to the Free Software
22 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23 
24 */
25 
26 #include "../../common/common.h"
27 #include "../system.h"
28 #include <unistd.h>
29 #include <signal.h>
30 #include <termios.h>
31 #include <fcntl.h>
32 #include <sys/time.h>
33 
34 typedef struct {
35  uint32_t cursor;
36  char buffer[256];
38 
39 static bool stdinActive;
40 /* general flag to tell about tty console mode */
41 static bool ttyConsoleActivated = false;
42 
43 /* some key codes that the terminal may be using, initialised on start up */
44 static int TTY_erase;
45 static int TTY_eof;
46 
47 static struct termios TTY_tc;
48 
50 
51 /* This is somewhat of a duplicate of the graphical console history
52  * but it's safer more modular to have our own here */
53 #define CON_HISTORY 32
55 static int histCurrent = -1, histCount = 0;
56 
61 static void CON_FlushIn (void)
62 {
63  char key;
64  while (read(STDIN_FILENO, &key, 1) != -1)
65  ;
66 }
67 
74 static void Sys_TTYDeleteCharacter (void)
75 {
76  char key = '\b';
77  write(STDOUT_FILENO, &key, 1) || 0;
78  key = ' ';
79  write(STDOUT_FILENO, &key, 1) || 0;
80  key = '\b';
81  write(STDOUT_FILENO, &key, 1) || 0;
82 }
83 
88 static void Sys_TTYConsoleHide (void)
89 {
90  if (ttyConsoleHistory.cursor > 0) {
91  for (unsigned int i = 0; i < ttyConsoleHistory.cursor; i++)
93  }
94  Sys_TTYDeleteCharacter(); /* Delete "]" */
95 }
96 
101 static void Sys_TTYConsoleShow (void)
102 {
103  write(STDOUT_FILENO, "]", 1) || 0;
104  if (ttyConsoleHistory.cursor) {
105  for (unsigned int i = 0; i < ttyConsoleHistory.cursor; i++) {
106  write(STDOUT_FILENO, ttyConsoleHistory.buffer + i, 1) || 0;
107  }
108  }
109 }
110 
112 {
113  const size_t size = lengthof(ttyEditLines);
114 
115  assert(histCount <= size);
116  assert(histCount >= 0);
117  assert(histCurrent >= -1);
118  assert(histCurrent <= histCount);
119  /* make some room */
120  for (int i = size - 1; i > 0; i--)
121  ttyEditLines[i] = ttyEditLines[i - 1];
122 
123  ttyEditLines[0] = *field;
124  if (histCount < size)
125  histCount++;
126 
127  histCurrent = -1; /* re-init */
128 }
129 
131 {
132  assert(histCount <= lengthof(ttyEditLines));
133  assert(histCount >= 0);
134  assert(histCurrent >= -1);
135  assert(histCurrent <= histCount);
136 
137  const int histPrev = histCurrent + 1;
138  if (histPrev >= histCount)
139  return nullptr;
140 
141  histCurrent++;
142  return &(ttyEditLines[histCurrent]);
143 }
144 
146 {
147  assert(histCount <= CON_HISTORY);
148  assert(histCount >= 0);
149  assert(histCurrent >= -1);
150  assert(histCurrent <= histCount);
151  if (histCurrent >= 0)
152  histCurrent--;
153 
154  if (histCurrent == -1)
155  return nullptr;
156 
157  return &(ttyEditLines[histCurrent]);
158 }
159 
164 static void Sys_TTYConsoleSigCont (int signum)
165 {
166  Sys_ConsoleInit();
167 }
168 
169 void Sys_ShowConsole (bool show)
170 {
171  static int ttyConsoleHide = 0;
172 
173  if (!ttyConsoleActivated)
174  return;
175 
176  if (show) {
177  assert(ttyConsoleHide > 0);
178  ttyConsoleHide--;
179  if (ttyConsoleHide == 0)
181  } else {
182  if (ttyConsoleHide == 0)
184  ttyConsoleHide++;
185  }
186 }
187 
193 {
194  if (ttyConsoleActivated) {
195  Sys_TTYDeleteCharacter(); /* Delete "]" */
196  tcsetattr(STDIN_FILENO, TCSADRAIN, &TTY_tc);
197  }
198 
199  /* Restore blocking to stdin reads */
200  fcntl(STDIN_FILENO, F_SETFL, fcntl(STDIN_FILENO, F_GETFL, 0) & ~O_NONBLOCK);
201 }
202 
204 {
205  OBJZERO(*edit);
206 }
207 
208 static bool Sys_IsATTY (void)
209 {
210  const char* term = getenv("TERM");
211  return isatty(STDIN_FILENO) && !(term && (Q_streq(term, "raw") || Q_streq(term, "dumb")));
212 }
213 
217 void Sys_ConsoleInit (void)
218 {
219  /* If the process is backgrounded (running non interactively)
220  * then SIGTTIN or SIGTOU is emitted, if not caught, turns into a SIGSTP */
221  signal(SIGTTIN, SIG_IGN);
222  signal(SIGTTOU, SIG_IGN);
223 
224  /* If SIGCONT is received, reinitialize console */
225  signal(SIGCONT, Sys_TTYConsoleSigCont);
226 
227  /* Make stdin reads non-blocking */
228  fcntl(STDIN_FILENO, F_SETFL, fcntl(STDIN_FILENO, F_GETFL, 0) | O_NONBLOCK);
229 
230  if (!Sys_IsATTY()) {
231  Com_Printf("tty console mode disabled\n");
232  ttyConsoleActivated = false;
233  stdinActive = true;
234  return;
235  }
236 
237  Sys_TTYConsoleHistoryClear(&ttyConsoleHistory);
238  tcgetattr(STDIN_FILENO, &TTY_tc);
239  TTY_erase = TTY_tc.c_cc[VERASE];
240  TTY_eof = TTY_tc.c_cc[VEOF];
241  struct termios tc = TTY_tc;
242 
243  /*
244  * ECHO: don't echo input characters.
245  * ICANON: enable canonical mode. This enables the special characters EOF, EOL,
246  * EOL2, ERASE, KILL, REPRINT, STATUS, and WERASE, and buffers by lines.
247  * ISIG: when any of the characters INTR, QUIT, SUSP or DSUSP are received,
248  * generate the corresponding sigĀ­nal.
249  */
250  tc.c_lflag &= ~(ECHO | ICANON);
251 
252  /*
253  * ISTRIP strip off bit 8
254  * INPCK enable input parity checking
255  */
256  tc.c_iflag &= ~(ISTRIP | INPCK);
257  tc.c_cc[VMIN] = 1;
258  tc.c_cc[VTIME] = 0;
259  tcsetattr(STDIN_FILENO, TCSADRAIN, &tc);
260  ttyConsoleActivated = true;
261 }
262 
263 const char* Sys_ConsoleInput (void)
264 {
265  /* we use this when sending back commands */
266  static char text[256];
267 
268  if (ttyConsoleActivated) {
269  char key;
270  int avail = read(STDIN_FILENO, &key, 1);
271  if (avail != -1) {
272  /* we have something
273  * backspace?
274  * NOTE TTimo testing a lot of values .. seems it's the only way to get it to work everywhere */
275  if (key == TTY_erase || key == 127 || key == 8) {
276  if (ttyConsoleHistory.cursor > 0) {
277  ttyConsoleHistory.cursor--;
278  ttyConsoleHistory.buffer[ttyConsoleHistory.cursor] = '\0';
280  }
281  return nullptr;
282  }
283  /* check if this is a control char */
284  if (key && key < ' ') {
285  if (key == '\n') {
286  /* push it in history */
287  Sys_TTYConsoleHistoryAdd(&ttyConsoleHistory);
288  Q_strncpyz(text, ttyConsoleHistory.buffer, sizeof(text));
289  Sys_TTYConsoleHistoryClear(&ttyConsoleHistory);
290  key = '\n';
291  write(1, &key, 1) || 0;
292  write(1, "]", 1) || 0;
293  return text;
294  }
295  if (key == '\t') {
296  const size_t size = sizeof(ttyConsoleHistory.buffer);
297  const char* s = ttyConsoleHistory.buffer;
298  char* target = ttyConsoleHistory.buffer;
299  Sys_ShowConsole(false);
300  Com_ConsoleCompleteCommand(s, target, size, &ttyConsoleHistory.cursor, 0);
301  Sys_ShowConsole(true);
302  return nullptr;
303  }
304  avail = read(STDIN_FILENO, &key, 1);
305  if (avail != -1) {
306  /* VT 100 keys */
307  if (key == '[' || key == 'O') {
308  consoleHistory_t* history;
309  avail = read(STDIN_FILENO, &key, 1);
310  if (avail != -1) {
311  switch (key) {
312  case 'A':
313  history = Sys_TTYConsoleHistoryPrevious();
314  if (history) {
315  Sys_ShowConsole(false);
316  ttyConsoleHistory = *history;
317  Sys_ShowConsole(true);
318  }
319  CON_FlushIn();
320  return nullptr;
321  break;
322  case 'B':
323  history = Sys_TTYConsoleHistoryNext();
324  Sys_ShowConsole(false);
325  if (history) {
326  ttyConsoleHistory = *history;
327  } else {
328  Sys_TTYConsoleHistoryClear(&ttyConsoleHistory);
329  }
330  Sys_ShowConsole(true);
331  CON_FlushIn();
332  return nullptr;
333  break;
334  case 'C':
335  return nullptr;
336  case 'D':
337  return nullptr;
338  }
339  }
340  }
341  }
342  CON_FlushIn();
343  return nullptr;
344  }
345  if (ttyConsoleHistory.cursor >= sizeof(text) - 1)
346  return nullptr;
347  /* push regular character */
348  ttyConsoleHistory.buffer[ttyConsoleHistory.cursor] = key;
349  ttyConsoleHistory.cursor++;
350  /* print the current line (this is differential) */
351  write(STDOUT_FILENO, &key, 1) || 0;
352  }
353 
354  return nullptr;
355  } else if (stdinActive) {
356  fd_set fdset;
357  struct timeval timeout;
358 
359  FD_ZERO(&fdset);
360  FD_SET(STDIN_FILENO, &fdset); /* stdin */
361  timeout.tv_sec = 0;
362  timeout.tv_usec = 0;
363  if (select(STDIN_FILENO + 1, &fdset, nullptr, nullptr, &timeout) == -1
364  || !FD_ISSET(STDIN_FILENO, &fdset))
365  return nullptr;
366 
367  const int len = read(STDIN_FILENO, text, sizeof(text));
368  if (len == 0) { /* eof! */
369  stdinActive = false;
370  return nullptr;
371  }
372 
373  if (len < 1)
374  return nullptr;
375  text[len - 1] = 0; /* rip off the /n and terminate */
376 
377  return text;
378  }
379  return nullptr;
380 }
381 
382 void Sys_ConsoleOutput (const char* string)
383 {
384  /* BUG: for some reason, NDELAY also affects stdout (1) when used on stdin (0). */
385  const int origflags = fcntl(STDOUT_FILENO, F_GETFL, 0);
386 
387  fcntl(STDOUT_FILENO, F_SETFL, origflags & ~FNDELAY);
388  while (*string) {
389  const ssize_t written = write(STDOUT_FILENO, string, strlen(string));
390  if (written <= 0)
391  break; /* sorry, I cannot do anything about this error - without an output */
392  string += written;
393  }
394  fcntl(STDOUT_FILENO, F_SETFL, origflags);
395 }
void Sys_ShowConsole(bool show)
static void Sys_TTYConsoleShow(void)
Show the current line.
static bool ttyConsoleActivated
QGL_EXTERN GLenum field
Definition: r_gl.h:101
static void Sys_TTYConsoleHide(void)
Clear the display of the line currently edited bring cursor back to beginning of line.
void Com_Printf(const char *const fmt,...)
Definition: common.cpp:386
void Sys_ConsoleShutdown(void)
Shutdown the console.
static consoleHistory_t ttyConsoleHistory
static bool Sys_IsATTY(void)
void Sys_ConsoleInit(void)
Initialize the console input (tty mode if possible)
void Q_strncpyz(char *dest, const char *src, size_t destsize)
Safe strncpy that ensures a trailing zero.
Definition: shared.cpp:457
unsigned int key
Definition: cl_input.cpp:68
GLsizei size
Definition: r_gl.h:152
#define OBJZERO(obj)
Definition: shared.h:178
static void Sys_TTYConsoleSigCont(int signum)
Reinitialize console input after receiving SIGCONT, as on Linux the terminal seems to lose all set at...
static void Sys_TTYConsoleHistoryAdd(consoleHistory_t *field)
static void Sys_TTYConsoleHistoryClear(consoleHistory_t *edit)
void Sys_ConsoleOutput(const char *string)
const char * Sys_ConsoleInput(void)
static int TTY_erase
static consoleHistory_t * Sys_TTYConsoleHistoryPrevious(void)
static struct termios TTY_tc
static int histCount
#define CON_HISTORY
static bool stdinActive
bool Com_ConsoleCompleteCommand(const char *s, char *target, size_t bufSize, uint32_t *pos, uint32_t offset)
Console completion for command and variables.
Definition: common.cpp:717
QGL_EXTERN GLint i
Definition: r_gl.h:113
QGL_EXTERN GLuint GLchar GLuint * len
Definition: r_gl.h:99
static consoleHistory_t ttyEditLines[CON_HISTORY]
static int histCurrent
static void CON_FlushIn(void)
Flush stdin, I suspect some terminals are sending a LOT of shit.
#define lengthof(x)
Definition: shared.h:105
#define Q_streq(a, b)
Definition: shared.h:136
static int TTY_eof
static void Sys_TTYDeleteCharacter(void)
Output a backspace.
static consoleHistory_t * Sys_TTYConsoleHistoryNext(void)