Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: code/trunk/src/libraries/core/command/IOConsolePOSIX.cc @ 8729

Last change on this file since 8729 was 8729, checked in by rgrieder, 13 years ago

Merged unity_build branch back to trunk.

Features:

  • Implemented fully automatic build units to speed up compilation if requested
  • Added DOUT macro for quick debug output
  • Activated text colouring in the POSIX IOConsole
  • DeclareToluaInterface is not necessary anymore

Improvements:

  • Output levels now change appropriately when switch back and forth from dev mode
  • Log level for the file output is now also correct during startup
  • Removed some header file dependencies in core and tools to speed up compilation

no more file for command line options

  • Improved util::tribool by adapting some concepts from boost::tribool

Regressions:

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