unix_console.c

Go to the documentation of this file.
00001 
00006 /*
00007 Copyright (C) 1997-2001 Id Software, Inc.
00008 
00009 This program is free software; you can redistribute it and/or
00010 modify it under the terms of the GNU General Public License
00011 as published by the Free Software Foundation; either version 2
00012 of the License, or (at your option) any later version.
00013 
00014 This program is distributed in the hope that it will be useful,
00015 but WITHOUT ANY WARRANTY; without even the implied warranty of
00016 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
00017 
00018 See the GNU General Public License for more details.
00019 
00020 You should have received a copy of the GNU General Public License
00021 along with this program; if not, write to the Free Software
00022 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
00023 
00024 */
00025 
00026 #include "../../common/common.h"
00027 #include "../system.h"
00028 #include <unistd.h>
00029 #include <signal.h>
00030 #include <termios.h>
00031 #include <fcntl.h>
00032 #include <sys/time.h>
00033 
00034 typedef struct {
00035     int     cursor;
00036     char    buffer[256];
00037 } consoleHistory_t;
00038 
00039 static qboolean stdinActive;
00040 /* general flag to tell about tty console mode */
00041 static qboolean ttyConsoleActivated = qfalse;
00042 
00043 /* some key codes that the terminal may be using, initialised on start up */
00044 static int TTY_erase;
00045 static int TTY_eof;
00046 
00047 static struct termios TTY_tc;
00048 
00049 static consoleHistory_t ttyConsoleHistory;
00050 
00051 /* This is somewhat of a duplicate of the graphical console history
00052  * but it's safer more modular to have our own here */
00053 #define CON_HISTORY 32
00054 static consoleHistory_t ttyEditLines[CON_HISTORY];
00055 static int histCurrent = -1, histCount = 0;
00056 
00061 static void CON_FlushIn (void)
00062 {
00063     char key;
00064     while (read(STDIN_FILENO, &key, 1) != -1)
00065         ;
00066 }
00067 
00074 static void Sys_TTYDeleteCharacter (void)
00075 {
00076     char key;
00077     size_t size;
00078 
00079     key = '\b';
00080     size = write(STDOUT_FILENO, &key, 1);
00081     key = ' ';
00082     size = write(STDOUT_FILENO, &key, 1);
00083     key = '\b';
00084     size = write(STDOUT_FILENO, &key, 1);
00085 }
00086 
00091 static void Sys_TTYConsoleHide (void)
00092 {
00093     if (ttyConsoleHistory.cursor > 0) {
00094         int i;
00095         for (i = 0; i < ttyConsoleHistory.cursor; i++)
00096             Sys_TTYDeleteCharacter();
00097     }
00098     Sys_TTYDeleteCharacter(); /* Delete "]" */
00099 }
00100 
00105 static void Sys_TTYConsoleShow (void)
00106 {
00107     size_t size = write(STDOUT_FILENO, "]", 1);
00108     if (ttyConsoleHistory.cursor) {
00109         int i;
00110         for (i = 0; i < ttyConsoleHistory.cursor; i++) {
00111             size = write(STDOUT_FILENO, ttyConsoleHistory.buffer + i, 1);
00112         }
00113     }
00114 }
00115 
00116 static void Sys_TTYConsoleHistoryAdd (consoleHistory_t *field)
00117 {
00118     int i;
00119     const size_t size = lengthof(ttyEditLines);
00120 
00121     assert(histCount <= size);
00122     assert(histCount >= 0);
00123     assert(histCurrent >= -1);
00124     assert(histCurrent <= histCount);
00125     /* make some room */
00126     for (i = size - 1; i > 0; i--)
00127         ttyEditLines[i] = ttyEditLines[i - 1];
00128 
00129     ttyEditLines[0] = *field;
00130     if (histCount < size)
00131         histCount++;
00132 
00133     histCurrent = -1; /* re-init */
00134 }
00135 
00136 static consoleHistory_t *Sys_TTYConsoleHistoryPrevious (void)
00137 {
00138     int histPrev;
00139 
00140     assert(histCount <= lengthof(ttyEditLines));
00141     assert(histCount >= 0);
00142     assert(histCurrent >= -1);
00143     assert(histCurrent <= histCount);
00144 
00145     histPrev = histCurrent + 1;
00146     if (histPrev >= histCount)
00147         return NULL;
00148 
00149     histCurrent++;
00150     return &(ttyEditLines[histCurrent]);
00151 }
00152 
00153 static consoleHistory_t *Sys_TTYConsoleHistoryNext (void)
00154 {
00155     assert(histCount <= CON_HISTORY);
00156     assert(histCount >= 0);
00157     assert(histCurrent >= -1);
00158     assert(histCurrent <= histCount);
00159     if (histCurrent >= 0)
00160         histCurrent--;
00161 
00162     if (histCurrent == -1)
00163         return NULL;
00164 
00165     return &(ttyEditLines[histCurrent]);
00166 }
00167 
00172 static void Sys_TTYConsoleSigCont (int signum)
00173 {
00174     Sys_ConsoleInit();
00175 }
00176 
00177 void Sys_ShowConsole (qboolean show)
00178 {
00179     static int ttyConsoleHide = 0;
00180 
00181     if (!ttyConsoleActivated)
00182         return;
00183 
00184     if (show) {
00185         assert(ttyConsoleHide > 0);
00186         ttyConsoleHide--;
00187         if (ttyConsoleHide == 0)
00188             Sys_TTYConsoleShow();
00189     } else {
00190         if (ttyConsoleHide == 0)
00191             Sys_TTYConsoleHide();
00192         ttyConsoleHide++;
00193     }
00194 }
00195 
00200 void Sys_ConsoleShutdown (void)
00201 {
00202     if (ttyConsoleActivated) {
00203         Sys_TTYDeleteCharacter(); /* Delete "]" */
00204         tcsetattr(STDIN_FILENO, TCSADRAIN, &TTY_tc);
00205     }
00206 
00207     /* Restore blocking to stdin reads */
00208     fcntl(STDIN_FILENO, F_SETFL, fcntl(STDIN_FILENO, F_GETFL, 0) & ~O_NONBLOCK);
00209 }
00210 
00211 static void Sys_TTYConsoleHistoryClear (consoleHistory_t *edit)
00212 {
00213     memset(edit, 0, sizeof(*edit));
00214 }
00215 
00216 static qboolean Sys_IsATTY (void)
00217 {
00218     const char* term = getenv("TERM");
00219     return isatty(STDIN_FILENO) && !(term && (!strcmp(term, "raw") || !strcmp(term, "dumb")));
00220 }
00221 
00225 void Sys_ConsoleInit (void)
00226 {
00227     struct termios tc;
00228 
00229     /* If the process is backgrounded (running non interactively)
00230      * then SIGTTIN or SIGTOU is emitted, if not caught, turns into a SIGSTP */
00231     signal(SIGTTIN, SIG_IGN);
00232     signal(SIGTTOU, SIG_IGN);
00233 
00234     /* If SIGCONT is received, reinitialize console */
00235     signal(SIGCONT, Sys_TTYConsoleSigCont);
00236 
00237     /* Make stdin reads non-blocking */
00238     fcntl(STDIN_FILENO, F_SETFL, fcntl(STDIN_FILENO, F_GETFL, 0) | O_NONBLOCK);
00239 
00240     if (!Sys_IsATTY()) {
00241         Com_Printf("tty console mode disabled\n");
00242         ttyConsoleActivated = qfalse;
00243         stdinActive = qtrue;
00244         return;
00245     }
00246 
00247     Sys_TTYConsoleHistoryClear(&ttyConsoleHistory);
00248     tcgetattr(STDIN_FILENO, &TTY_tc);
00249     TTY_erase = TTY_tc.c_cc[VERASE];
00250     TTY_eof = TTY_tc.c_cc[VEOF];
00251     tc = TTY_tc;
00252 
00253     /*
00254      * ECHO: don't echo input characters.
00255      * ICANON: enable canonical mode. This enables the special characters EOF, EOL,
00256      *   EOL2, ERASE, KILL, REPRINT, STATUS, and WERASE, and buffers by lines.
00257      * ISIG: when any of the characters INTR, QUIT, SUSP or DSUSP are received,
00258      *   generate the corresponding sigĀ­nal.
00259      */
00260     tc.c_lflag &= ~(ECHO | ICANON);
00261 
00262     /*
00263      * ISTRIP strip off bit 8
00264      * INPCK enable input parity checking
00265      */
00266     tc.c_iflag &= ~(ISTRIP | INPCK);
00267     tc.c_cc[VMIN] = 1;
00268     tc.c_cc[VTIME] = 0;
00269     tcsetattr(STDIN_FILENO, TCSADRAIN, &tc);
00270     ttyConsoleActivated = qtrue;
00271 }
00272 
00273 const char *Sys_ConsoleInput (void)
00274 {
00275     /* we use this when sending back commands */
00276     static char text[256];
00277 
00278     if (ttyConsoleActivated) {
00279         char key;
00280         int avail = read(STDIN_FILENO, &key, 1);
00281         if (avail != -1) {
00282             size_t size;
00283             /* we have something
00284              * backspace?
00285              * NOTE TTimo testing a lot of values .. seems it's the only way to get it to work everywhere */
00286             if (key == TTY_erase || key == 127 || key == 8) {
00287                 if (ttyConsoleHistory.cursor > 0) {
00288                     ttyConsoleHistory.cursor--;
00289                     ttyConsoleHistory.buffer[ttyConsoleHistory.cursor] = '\0';
00290                     Sys_TTYDeleteCharacter();
00291                 }
00292                 return NULL;
00293             }
00294             /* check if this is a control char */
00295             if (key && key < ' ') {
00296                 if (key == '\n') {
00297                     /* push it in history */
00298                     Sys_TTYConsoleHistoryAdd(&ttyConsoleHistory);
00299                     Q_strncpyz(text, ttyConsoleHistory.buffer, sizeof(text));
00300                     Sys_TTYConsoleHistoryClear(&ttyConsoleHistory);
00301                     key = '\n';
00302                     size = write(1, &key, 1);
00303                     size = write(1, "]", 1);
00304                     return text;
00305                 }
00306                 if (key == '\t') {
00307                     const size_t size = sizeof(ttyConsoleHistory.buffer);
00308                     const char *s = ttyConsoleHistory.buffer;
00309                     char *target = ttyConsoleHistory.buffer;
00310                     Sys_ShowConsole(qfalse);
00311                     Com_ConsoleCompleteCommand(s, target, size, &ttyConsoleHistory.cursor, 0);
00312                     Sys_ShowConsole(qtrue);
00313                     return NULL;
00314                 }
00315                 avail = read(STDIN_FILENO, &key, 1);
00316                 if (avail != -1) {
00317                     /* VT 100 keys */
00318                     if (key == '[' || key == 'O') {
00319                         consoleHistory_t *history;
00320                         avail = read(STDIN_FILENO, &key, 1);
00321                         if (avail != -1) {
00322                             switch (key) {
00323                             case 'A':
00324                                 history = Sys_TTYConsoleHistoryPrevious();
00325                                 if (history) {
00326                                     Sys_ShowConsole(qfalse);
00327                                     ttyConsoleHistory = *history;
00328                                     Sys_ShowConsole(qtrue);
00329                                 }
00330                                 CON_FlushIn();
00331                                 return NULL;
00332                                 break;
00333                             case 'B':
00334                                 history = Sys_TTYConsoleHistoryNext();
00335                                 Sys_ShowConsole(qfalse);
00336                                 if (history) {
00337                                     ttyConsoleHistory = *history;
00338                                 } else {
00339                                     Sys_TTYConsoleHistoryClear(&ttyConsoleHistory);
00340                                 }
00341                                 Sys_ShowConsole(qtrue);
00342                                 CON_FlushIn();
00343                                 return NULL;
00344                                 break;
00345                             case 'C':
00346                                 return NULL;
00347                             case 'D':
00348                                 return NULL;
00349                             }
00350                         }
00351                     }
00352                 }
00353                 CON_FlushIn();
00354                 return NULL;
00355             }
00356             if (ttyConsoleHistory.cursor >= sizeof(text) - 1)
00357                 return NULL;
00358             /* push regular character */
00359             ttyConsoleHistory.buffer[ttyConsoleHistory.cursor] = key;
00360             ttyConsoleHistory.cursor++;
00361             /* print the current line (this is differential) */
00362             size = write(STDOUT_FILENO, &key, 1);
00363         }
00364 
00365         return NULL;
00366     } else if (stdinActive) {
00367         int len;
00368         fd_set fdset;
00369         struct timeval timeout;
00370 
00371         FD_ZERO(&fdset);
00372         FD_SET(STDIN_FILENO, &fdset); /* stdin */
00373         timeout.tv_sec = 0;
00374         timeout.tv_usec = 0;
00375         if (select(STDIN_FILENO + 1, &fdset, NULL, NULL, &timeout) == -1
00376                 || !FD_ISSET(STDIN_FILENO, &fdset))
00377             return NULL;
00378 
00379         len = read(STDIN_FILENO, text, sizeof(text));
00380         if (len == 0) { /* eof! */
00381             stdinActive = qfalse;
00382             return NULL;
00383         }
00384 
00385         if (len < 1)
00386             return NULL;
00387         text[len - 1] = 0; /* rip off the /n and terminate */
00388 
00389         return text;
00390     }
00391     return NULL;
00392 }
00393 
00399 void Sys_ConsoleOutput (const char *string)
00400 {
00401     /* BUG: for some reason, NDELAY also affects stdout (1) when used on stdin (0). */
00402     const int origflags = fcntl(STDOUT_FILENO, F_GETFL, 0);
00403 
00404     Sys_ShowConsole(qfalse);
00405 
00406     /* skip color char */
00407     if (!strncmp(string, COLORED_GREEN, strlen(COLORED_GREEN)))
00408         string += strlen(COLORED_GREEN);
00409 
00410     fcntl(STDOUT_FILENO, F_SETFL, origflags & ~FNDELAY);
00411     while (*string) {
00412         const ssize_t written = write(STDOUT_FILENO, string, strlen(string));
00413         if (written <= 0)
00414             break; /* sorry, I cannot do anything about this error - without an output */
00415         string += written;
00416     }
00417     fcntl(STDOUT_FILENO, F_SETFL, origflags);
00418 
00419 
00420     Sys_ShowConsole(qtrue);
00421 }

Generated by  doxygen 1.6.2