Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

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

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

Shell and its derivatives are now based on the new output system

  • Property svn:eol-style set to native
File size: 16.5 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("IOConsole", 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() + '\n', 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 only the last output-line has changed
394    void IOConsole::onlyLastLineChanged()
395    {
396        // Save cursor position and move it to the beginning of the first output line
397        this->cout_ << "\033[s\033[1A\033[1G";
398        // Erase the line
399        this->cout_ << "\033[K";
400        // Reprint the last output line
401        this->printOutputLine(this->shell_->getNewestLineIterator()->first, this->shell_->getNewestLineIterator()->second);
402        // Restore cursor
403        this->cout_ << "\033[u";
404        this->cout_.flush();
405    }
406
407    //! Called if a new output-line was added
408    void IOConsole::lineAdded()
409    {
410        int newLines = this->shell_->getNewestLineIterator()->first.size() / this->terminalWidth_ + 1;
411        // Create new lines by scrolling the screen
412        this->cout_ << "\033[" << newLines << 'S';
413        // Move cursor to the beginning of the new (last) output line
414        this->cout_ << "\033[" << newLines << "A\033[1G";
415        // Erase screen from here
416        this->cout_ << "\033[J";
417        // Print the new output lines
418        for (int i = 0; i < newLines; ++i)
419        {
420            Shell::LineList::const_iterator it = this->shell_->getNewestLineIterator();
421            this->printOutputLine(it->first.substr(i*this->terminalWidth_, this->terminalWidth_), it->second);
422        }
423        // Move cursor down
424        this->cout_ << "\033[1B\033[1G";
425        // Print status and input lines
426        this->printInputLine();
427        this->printStatusLines();
428        this->cout_.flush();
429    }
430
431    //! Called if the text in the input-line has changed
432    void IOConsole::inputChanged()
433    {
434        this->printInputLine();
435        this->cout_.flush();
436    }
437
438    //! Called if the position of the cursor in the input-line has changed
439    void IOConsole::cursorChanged()
440    {
441        this->printInputLine();
442        this->cout_.flush();
443    }
444}
Note: See TracBrowser for help on using the repository browser.