Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

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

Last change on this file since 9550 was 9550, checked in by landauf, 11 years ago

merged testing branch back to trunk. unbelievable it took me 13 months to finish this chore…

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