Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: code/branches/testing/src/libraries/core/command/IOConsolePOSIX.cc @ 9548

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

eclipse missed some files…

  • 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"
[9536]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
[9548]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
[9548]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.