Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: code/branches/output/src/libraries/core/command/IOConsolePOSIX.cc @ 8803

Last change on this file since 8803 was 8803, checked in by landauf, 13 years ago

no \n needed after executed command in shell.
different color for return value of a command.
fixed width of text InGameConsole (at least on my system).

  • Property svn:eol-style set to native
File size: 15.9 KB
Line 
1/*
2 *   ORXONOX - the hottest 3D action shooter ever to exist
3 *                    > www.orxonox.net <
4 *
5 *
6 *   License notice:
7 *
8 *   This program is free software; you can redistribute it and/or
9 *   modify it under the terms of the GNU General Public License
10 *   as published by the Free Software Foundation; either version 2
11 *   of the License, or (at your option) any later version.
12 *
13 *   This program is distributed in the hope that it will be useful,
14 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
15 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 *   GNU General Public License for more details.
17 *
18 *   You should have received a copy of the GNU General Public License
19 *   along with this program; if not, write to the Free Software
20 *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
21 *
22 *   Author:
23 *      Oliver Scheuss
24 *      Reto Grieder
25 *   Co-authors:
26 *      ...
27 *
28 */
29
30#include "IOConsole.h"
31
32#include <iomanip>
33#include <iostream>
34#include <termios.h>
35#include <sys/ioctl.h>
36
37#include "util/Clock.h"
38#include "util/Math.h"
39#include "util/output/ConsoleWriter.h"
40#include "core/Game.h"
41#include "core/input/InputBuffer.h"
42
43namespace orxonox
44{
45    IOConsole* IOConsole::singletonPtr_s = NULL;
46
47    namespace EscapeMode
48    {
49        enum Value
50        {
51            None,
52            First,
53            Second
54        };
55    }
56
57    IOConsole::IOConsole()
58        : shell_(new Shell("Console", false))
59        , buffer_(shell_->getInputBuffer())
60        , cout_(std::cout.rdbuf())
61        , promptString_("orxonox # ")
62        , bStatusPrinted_(false)
63        , originalTerminalSettings_(0)
64    {
65        this->setTerminalMode();
66        this->shell_->registerListener(this);
67
68        // Manually set the widths of the individual status lines
69        this->statusLineWidths_.push_back(29);
70        this->statusLineMaxWidth_ = 29;
71
72        this->getTerminalSize();
73        this->lastTerminalWidth_ = this->terminalWidth_;
74        this->lastTerminalHeight_ = this->terminalHeight_;
75
76        // Disable standard std::cout logging
77        ConsoleWriter::getInstance().disable();
78        // Redirect std::cout to an ostringstream
79        // (Other part is in the initialiser list)
80        std::cout.rdbuf(this->origCout_.rdbuf());
81
82        // Make sure we make way for the status lines
83        this->preUpdate(Game::getInstance().getGameClock());
84    }
85
86    IOConsole::~IOConsole()
87    {
88        // Process output written to std::cout in the meantime
89        std::cout.flush();
90        if (!this->origCout_.str().empty())
91            this->shell_->addOutput(this->origCout_.str(), Shell::Cout);
92        // Erase input and status lines
93        this->cout_ << "\033[1G\033[J";
94        // Move cursor to the bottom
95        this->cout_ << "\033[" << this->statusLineWidths_.size() << 'B';
96        // Scroll terminal to compensate for erased lines
97        this->cout_ << "\033[" << this->statusLineWidths_.size() << 'T';
98
99        resetTerminalMode();
100        this->shell_->destroy();
101
102        // Restore this->cout_ redirection
103        std::cout.rdbuf(this->cout_.rdbuf());
104        // Enable standard std::cout logging again
105        ConsoleWriter::getInstance().enable();
106    }
107
108    void IOConsole::preUpdate(const Clock& time)
109    {
110        unsigned char c;
111        std::string escapeSequence;
112        EscapeMode::Value escapeMode = EscapeMode::None;
113        while (std::cin.good())
114        {
115            c = std::cin.get();
116            if (!std::cin.good())
117                break;
118
119            if (escapeMode == EscapeMode::First && (c == '[' || c=='O') )
120                escapeMode = EscapeMode::Second;
121            // Get Alt+Tab combination when switching applications
122            else if (escapeMode == EscapeMode::First && c == '\t')
123            {
124                this->buffer_->buttonPressed(KeyEvent(KeyCode::Tab, '\t', KeyboardModifier::Alt));
125                escapeMode = EscapeMode::None;
126            }
127            else if (escapeMode == EscapeMode::Second)
128            {
129                escapeSequence += c;
130                escapeMode = EscapeMode::None;
131                if      (escapeSequence == "A")
132                    this->buffer_->buttonPressed(KeyEvent(KeyCode::Up,       0, 0));
133                else if (escapeSequence == "B")
134                    this->buffer_->buttonPressed(KeyEvent(KeyCode::Down,     0, 0));
135                else if (escapeSequence == "C")
136                    this->buffer_->buttonPressed(KeyEvent(KeyCode::Right,    0, 0));
137                else if (escapeSequence == "D")
138                    this->buffer_->buttonPressed(KeyEvent(KeyCode::Left,     0, 0));
139                else if (escapeSequence == "1~" || escapeSequence == "H")
140                    this->buffer_->buttonPressed(KeyEvent(KeyCode::Home,     0, 0));
141                else if (escapeSequence == "2~")
142                    this->buffer_->buttonPressed(KeyEvent(KeyCode::Insert,   0, 0));
143                else if (escapeSequence == "3~")
144                    this->buffer_->buttonPressed(KeyEvent(KeyCode::Delete,   0, 0));
145                else if (escapeSequence == "4~" || escapeSequence == "F")
146                    this->buffer_->buttonPressed(KeyEvent(KeyCode::End,      0, 0));
147                else if (escapeSequence == "5~")
148                    this->buffer_->buttonPressed(KeyEvent(KeyCode::PageUp,   0, 0));
149                else if (escapeSequence == "6~")
150                    this->buffer_->buttonPressed(KeyEvent(KeyCode::PageDown, 0, 0));
151                else
152                    // Waiting for sequence to complete
153                    // If the user presses ESC and then '[' or 'O' while the loop is not
154                    // running (for instance while loading), the whole sequence gets dropped
155                    escapeMode = EscapeMode::Second;
156            }
157            else // not in an escape sequence OR user might have pressed just ESC
158            {
159                if (escapeMode == EscapeMode::First)
160                {
161                    this->buffer_->buttonPressed(KeyEvent(KeyCode::Escape, c, 0));
162                    escapeMode = EscapeMode::None;
163                }
164                if (c == '\033')
165                {
166                    escapeMode = EscapeMode::First;
167                    escapeSequence.clear();
168                }
169                else
170                {
171                    KeyCode::ByEnum code;
172                    switch (c)
173                    {
174                    case '\n'  : case '\r': code = KeyCode::Return; break;
175                    case '\177': case '\b': code = KeyCode::Back;   break;
176                    case '\t'             : code = KeyCode::Tab;    break;
177                    default:
178                        // We don't encode the key code (would be a very large switch)
179                        // because the InputBuffer will only insert the text anyway
180                        // Replacement character is simply KeyCode::A
181                        code = KeyCode::A;
182                    }
183                    this->buffer_->buttonPressed(KeyEvent(code, c, 0));
184                }
185            }
186        }
187        // Reset error flags in std::cin
188        std::cin.clear();
189
190        // If there is still an escape key pending (escape key ONLY), then
191        // it sure isn't an escape sequence anymore
192        if (escapeMode == EscapeMode::First)
193            this->buffer_->buttonPressed(KeyEvent(KeyCode::Escape, '\033', 0));
194
195        // Determine terminal width and height
196        this->lastTerminalWidth_ = this->terminalWidth_;
197        this->lastTerminalHeight_ = this->terminalHeight_;
198        this->getTerminalSize();
199
200        int heightDiff = this->terminalHeight_ - this->lastTerminalHeight_;
201        if (this->bStatusPrinted_ && heightDiff < 0)
202        {
203            // Terminal width has shrunk. The cursor will still be on the input line,
204            // but that line might very well be the last
205            int newLines = std::min((int)this->statusLineWidths_.size(), -heightDiff);
206            // Scroll terminal to create new lines
207            this->cout_ << "\033[" << newLines << 'S';
208        }
209
210        if (!this->bStatusPrinted_ && this->willPrintStatusLines())
211        {
212            // Scroll console to make way for status lines
213            this->cout_ << "\033[" << this->statusLineWidths_.size() << 'S';
214            this->bStatusPrinted_ = true;
215        }
216
217        // We always assume that the cursor is on the input line.
218        // But we cannot always be sure about that, esp. if we scroll the console
219        this->cout_ << "\033[" << this->statusLineWidths_.size() << 'B';
220        this->cout_ << "\033[" << this->statusLineWidths_.size() << 'A';
221
222        // Erase status and input lines
223        this->cout_ << "\033[1G\033[J";
224        this->printInputLine();
225        this->printStatusLines();
226        this->cout_.flush();
227
228        // Process output written to std::cout
229        std::cout.flush();
230        if (!this->origCout_.str().empty())
231        {
232            this->shell_->addOutput(this->origCout_.str(), Shell::Cout);
233            this->origCout_.str("");
234        }
235    }
236
237    void IOConsole::printOutputLine(const std::string& text, Shell::LineType type)
238    {
239        // Colour line
240        switch (type)
241        {
242            case Shell::DebugOutput:     this->cout_ << "\033[0m"; break;
243
244            case Shell::UserError:       this->cout_ << "\033[91m"; break;
245            case Shell::UserWarning:     this->cout_ << "\033[93m"; break;
246            case Shell::UserStatus:      this->cout_ << "\033[92m"; break;
247            case Shell::UserInfo:        this->cout_ << "\033[96m"; break;
248
249            case Shell::InternalError:   this->cout_ << "\033[31m"; break;
250            case Shell::InternalWarning: this->cout_ << "\033[33m"; break;
251            case Shell::InternalStatus:  this->cout_ << "\033[32m"; break;
252            case Shell::InternalInfo:    this->cout_ << "\033[36m"; break;
253
254            case Shell::Verbose:         this->cout_ << "\033[94m"; break;
255            case Shell::VerboseMore:     this->cout_ << "\033[34m"; break;
256            case Shell::VerboseUltra:    this->cout_ << "\033[34m"; break;
257
258            case Shell::Command:         this->cout_ << "\033[95m"; break;
259            case Shell::Hint:            this->cout_ << "\033[35m"; break;
260
261            default:                     this->cout_ << "\033[37m"; break;
262        }
263
264        // Print output line
265        this->cout_ << text;
266
267        // Reset colour atributes
268        this->cout_ << "\033[0m";
269    }
270
271    void IOConsole::printInputLine()
272    {
273        // Set cursor to the beginning of the line and erase the line
274        this->cout_ << "\033[1G\033[K";
275        // Indicate a command prompt
276        this->cout_ << this->promptString_;
277        // Save cursor position
278        this->cout_ << "\033[s";
279        // Print command line buffer
280        this->cout_ << this->shell_->getInput();
281        // Restore cursor position and move it to the right
282        this->cout_ << "\033[u";
283        if (this->buffer_->getCursorPosition() > 0)
284            this->cout_ << "\033[" << this->buffer_->getCursorPosition() << 'C';
285    }
286
287    void IOConsole::printStatusLines()
288    {
289        if (this->willPrintStatusLines())
290        {
291            // Save cursor position
292            this->cout_ << "\033[s";
293            // Move cursor down (don't create a new line here because the buffer might flush then!)
294            this->cout_ << "\033[1B\033[1G";
295            this->cout_ << std::fixed << std::setprecision(2) << std::setw(5) << Game::getInstance().getAvgFPS() << " fps, ";
296            this->cout_ <<               std::setprecision(2) << std::setw(5) << Game::getInstance().getAvgTickTime() << " ms tick time";
297            // Restore cursor position
298            this->cout_ << "\033[u";
299            this->bStatusPrinted_ = true;
300        }
301        else
302            this->bStatusPrinted_ = false;
303    }
304
305    void IOConsole::setTerminalMode()
306    {
307        termios new_settings;
308        this->originalTerminalSettings_ = new termios();
309
310        tcgetattr(0, this->originalTerminalSettings_);
311        new_settings = *this->originalTerminalSettings_;
312        new_settings.c_lflag &= ~(ICANON | ECHO);
313        //new_settings.c_lflag |= (ISIG | IEXTEN);
314        new_settings.c_cc[VTIME] = 0;
315        new_settings.c_cc[VMIN]  = 0;
316        tcsetattr(0, TCSANOW, &new_settings);
317        atexit(&IOConsole::resetTerminalMode);
318    }
319
320    /*static*/ void IOConsole::resetTerminalMode()
321    {
322        if (IOConsole::singletonPtr_s && IOConsole::singletonPtr_s->originalTerminalSettings_)
323        {
324            tcsetattr(0, TCSANOW, IOConsole::singletonPtr_s->originalTerminalSettings_);
325            delete IOConsole::singletonPtr_s->originalTerminalSettings_;
326            IOConsole::singletonPtr_s->originalTerminalSettings_ = 0;
327        }
328    }
329
330    void IOConsole::getTerminalSize()
331    {
332        this->terminalWidth_  = 0;
333        this->terminalHeight_ = 0;
334
335#ifdef TIOCGSIZE
336        struct ttysize win;
337        if (!ioctl(STDIN_FILENO, TIOCGSIZE, &win))
338        {
339            this->terminalWidth_  = win.ts_cols;
340            this->terminalHeight_ = win.ts_lines;
341        }
342#elif defined TIOCGWINSZ
343        struct winsize win;
344        if (!ioctl(STDIN_FILENO, TIOCGWINSZ, &win))
345        {
346            this->terminalWidth_  = win.ws_col;
347            this->terminalHeight_ = win.ws_row;
348        }
349#endif
350
351        const char* s;
352        if (!this->terminalWidth_ && (s = getenv("COLUMNS")))
353            this->terminalWidth_  = strtol(s, NULL, 10);
354        if (!this->terminalWidth_)
355            this->terminalWidth_ = 80;
356        if (!this->terminalHeight_ && (s = getenv("LINES")))
357            this->terminalHeight_ = strtol(s, NULL, 10);
358        if (!this->terminalHeight_)
359            this->terminalHeight_ = 24;
360    }
361
362    inline bool IOConsole::willPrintStatusLines()
363    {
364        return !this->statusLineWidths_.empty()
365             && this->terminalWidth_  >= this->statusLineMaxWidth_
366             && this->terminalHeight_ >= this->minOutputLines_ + (int)this->statusLineWidths_.size();
367    }
368
369    // ###############################
370    // ###  ShellListener methods  ###
371    // ###############################
372
373    //! Called if all output-lines have to be reprinted
374    void IOConsole::linesChanged()
375    {
376        // Method only gets called upon start to draw all the lines
377        // or when scrolling. But scrolling is disabled and the output
378        // is already in std::cout when we start the IOConsole
379    }
380
381    //! Called if a command is about to be executed
382    void IOConsole::executed()
383    {
384        this->shell_->addOutput(this->promptString_ + this->shell_->getInput(), Shell::Command);
385    }
386
387    //! Called if the console gets closed
388    void IOConsole::exit()
389    {
390        // Exit is not an option, just do nothing (Shell doesn't really exit too)
391    }
392
393    //! Called if a new output-line was added
394    void IOConsole::lineAdded()
395    {
396        int newLines = this->shell_->getNewestLineIterator()->first.size() / this->terminalWidth_ + 1;
397        // Create new lines by scrolling the screen
398        this->cout_ << "\033[" << newLines << 'S';
399        // Move cursor to the beginning of the new (last) output line
400        this->cout_ << "\033[" << newLines << "A\033[1G";
401        // Erase screen from here
402        this->cout_ << "\033[J";
403        // Print the new output lines
404        for (int i = 0; i < newLines; ++i)
405        {
406            Shell::LineList::const_iterator it = this->shell_->getNewestLineIterator();
407            this->printOutputLine(it->first.substr(i*this->terminalWidth_, this->terminalWidth_), it->second);
408        }
409        // Move cursor down
410        this->cout_ << "\033[1B\033[1G";
411        // Print status and input lines
412        this->printInputLine();
413        this->printStatusLines();
414        this->cout_.flush();
415    }
416
417    //! Called if the text in the input-line has changed
418    void IOConsole::inputChanged()
419    {
420        this->printInputLine();
421        this->cout_.flush();
422    }
423
424    //! Called if the position of the cursor in the input-line has changed
425    void IOConsole::cursorChanged()
426    {
427        this->printInputLine();
428        this->cout_.flush();
429    }
430}
Note: See TracBrowser for help on using the repository browser.