Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: code/trunk/src/libraries/core/IOConsole.cc @ 6372

Last change on this file since 6372 was 6105, checked in by rgrieder, 15 years ago

Merged console branch back to trunk.

  • Property svn:eol-style set to native
File size: 18.1 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 "core/Game.h"
35#include "core/input/InputBuffer.h"
36
37// ##########################
38// ###   Mutual methods   ###
39// ##########################
40namespace orxonox
41{
42    IOConsole* IOConsole::singletonPtr_s = NULL;
43
44    int IOConsole::extractLogLevel(std::string* text)
45    {
46        // Handle line colouring by inspecting the first letter
47        char level = 0;
48        if (!text->empty())
49        {
50            level = (*text)[0];
51            if (level == -1 || level >= 1 && level <= 6)
52            {
53                *text = text->substr(1);
54                if (level != -1)
55                    return level;
56            }
57        }
58        return 0;
59    }
60
61    inline bool IOConsole::willPrintStatusLines()
62    {
63        return !this->statusLineWidths_.empty()
64             && this->terminalWidth_  >= this->statusLineMaxWidth_
65             && this->terminalHeight_ >= (this->minOutputLines_ + this->statusLineWidths_.size());
66    }
67
68    // ###############################
69    // ###  ShellListener methods  ###
70    // ###############################
71
72    //! Called if all output-lines have to be reprinted
73    void IOConsole::linesChanged()
74    {
75        // Method only gets called upon start to draw all the lines
76        // or when scrolling. But scrolling is disabled and the output
77        // is already in std::cout when we start the IOConsole
78    }
79
80    //! Called if the text in the input-line has changed
81    void IOConsole::inputChanged()
82    {
83        this->printInputLine();
84        this->cout_.flush();
85    }
86
87    //! Called if the position of the cursor in the input-line has changed
88    void IOConsole::cursorChanged()
89    {
90        this->printInputLine();
91        this->cout_.flush();
92    }
93
94    //! Called if a command is about to be executed
95    void IOConsole::executed()
96    {
97        this->shell_->addOutputLine(this->promptString_ + this->shell_->getInput());
98    }
99
100    //! Called if the console gets closed
101    void IOConsole::exit()
102    {
103        // Exit is not an option, just do nothing (Shell doesn't really exit too)
104    }
105}
106
107#ifdef ORXONOX_PLATFORM_UNIX
108// ###############################
109// ###   Unix Implementation   ###
110// ###############################
111
112#include <termios.h>
113#include <sys/ioctl.h>
114
115namespace orxonox
116{
117    namespace EscapeMode
118    {
119        enum Value
120        {
121            None,
122            First,
123            Second
124        };
125    }
126
127    IOConsole::IOConsole()
128        : shell_(new Shell("IOConsole", false, true))
129        , buffer_(shell_->getInputBuffer())
130        , cout_(std::cout.rdbuf())
131        , bStatusPrinted_(false)
132        , promptString_("orxonox # ")
133        , originalTerminalSettings_(new termios())
134    {
135        this->setTerminalMode();
136        this->shell_->registerListener(this);
137
138        // Manually set the widths of the individual status lines
139        this->statusLineWidths_.push_back(29);
140        this->statusLineMaxWidth_ = 29;
141
142        this->getTerminalSize();
143        this->lastTerminalWidth_ = this->terminalWidth_;
144        this->lastTerminalHeight_ = this->terminalHeight_;
145
146        // Disable standard std::cout logging
147        OutputHandler::getInstance().disableCout();
148        // Redirect std::cout to an ostringstream
149        // (Other part is in the initialiser list)
150        std::cout.rdbuf(this->origCout_.rdbuf());
151
152        // Make sure we make way for the status lines
153        this->update(Game::getInstance().getGameClock());
154    }
155
156    IOConsole::~IOConsole()
157    {
158        // Empty all buffers
159        this->update(Game::getInstance().getGameClock());
160        // Erase input and status lines
161        this->cout_ << "\033[1G\033[J";
162        // Move cursor to the bottom
163        this->cout_ << "\033[" << this->statusLineWidths_.size() << 'B';
164        // Scroll terminal to compensate for erased lines
165        this->cout_ << "\033[" << this->statusLineWidths_.size() << 'T';
166
167        resetTerminalMode();
168        delete this->originalTerminalSettings_;
169        this->shell_->destroy();
170
171        // Restore this->cout_ redirection
172        std::cout.rdbuf(this->cout_.rdbuf());
173        // Enable standard std::cout logging again
174        OutputHandler::getInstance().enableCout();
175    }
176
177    void IOConsole::update(const Clock& time)
178    {
179        unsigned char c;
180        std::string escapeSequence;
181        EscapeMode::Value escapeMode = EscapeMode::None;
182        while (std::cin.good())
183        {
184            c = std::cin.get();
185            if (!std::cin.good())
186                break;
187
188            if (escapeMode == EscapeMode::First && (c == '[' || c=='O') )
189                escapeMode = EscapeMode::Second;
190            // Get Alt+Tab combination when switching applications
191            else if (escapeMode == EscapeMode::First && c == '\t')
192            {
193                this->buffer_->buttonPressed(KeyEvent(KeyCode::Tab, '\t', KeyboardModifier::Alt));
194                escapeMode = EscapeMode::None;
195            }
196            else if (escapeMode == EscapeMode::Second)
197            {
198                escapeSequence += c;
199                escapeMode = EscapeMode::None;
200                if      (escapeSequence == "A")
201                    this->buffer_->buttonPressed(KeyEvent(KeyCode::Up,       0, 0));
202                else if (escapeSequence == "B")
203                    this->buffer_->buttonPressed(KeyEvent(KeyCode::Down,     0, 0));
204                else if (escapeSequence == "C")
205                    this->buffer_->buttonPressed(KeyEvent(KeyCode::Right,    0, 0));
206                else if (escapeSequence == "D")
207                    this->buffer_->buttonPressed(KeyEvent(KeyCode::Left,     0, 0));
208                else if (escapeSequence == "1~" || escapeSequence == "H")
209                    this->buffer_->buttonPressed(KeyEvent(KeyCode::Home,     0, 0));
210                else if (escapeSequence == "2~")
211                    this->buffer_->buttonPressed(KeyEvent(KeyCode::Insert,   0, 0));
212                else if (escapeSequence == "3~")
213                    this->buffer_->buttonPressed(KeyEvent(KeyCode::Delete,   0, 0));
214                else if (escapeSequence == "4~" || escapeSequence == "F")
215                    this->buffer_->buttonPressed(KeyEvent(KeyCode::End,      0, 0));
216                else if (escapeSequence == "5~")
217                    this->buffer_->buttonPressed(KeyEvent(KeyCode::PageUp,   0, 0));
218                else if (escapeSequence == "6~")
219                    this->buffer_->buttonPressed(KeyEvent(KeyCode::PageDown, 0, 0));
220                else
221                    // Waiting for sequence to complete
222                    // If the user presses ESC and then '[' or 'O' while the loop is not
223                    // running (for instance while loading), the whole sequence gets dropped
224                    escapeMode = EscapeMode::Second;
225            }
226            else // not in an escape sequence OR user might have pressed just ESC
227            {
228                if (escapeMode == EscapeMode::First)
229                {
230                    this->buffer_->buttonPressed(KeyEvent(KeyCode::Escape, c, 0));
231                    escapeMode = EscapeMode::None;
232                }
233                if (c == '\033')
234                {
235                    escapeMode = EscapeMode::First;
236                    escapeSequence.clear();
237                }
238                else
239                {
240                    KeyCode::ByEnum code;
241                    switch (c)
242                    {
243                    case '\n'  : case '\r': code = KeyCode::Return; break;
244                    case '\177': case '\b': code = KeyCode::Back;   break;
245                    case '\t'             : code = KeyCode::Tab;    break;
246                    default:
247                        // We don't encode the key code (would be a very large switch)
248                        // because the InputBuffer will only insert the text anyway
249                        // Replacement character is simply KeyCode::A
250                        code = KeyCode::A;
251                    }
252                    this->buffer_->buttonPressed(KeyEvent(code, c, 0));
253                }
254            }
255        }
256        // Reset error flags in std::cin
257        std::cin.clear();
258
259        // If there is still an escape key pending (escape key ONLY), then
260        // it sure isn't an escape sequence anymore
261        if (escapeMode == EscapeMode::First)
262            this->buffer_->buttonPressed(KeyEvent(KeyCode::Escape, '\033', 0));
263
264        // Determine terminal width and height
265        this->lastTerminalWidth_ = this->terminalWidth_;
266        this->lastTerminalHeight_ = this->terminalHeight_;
267        this->getTerminalSize();
268
269        int heightDiff = this->terminalHeight_ - this->lastTerminalHeight_;
270        if (this->bStatusPrinted_ && heightDiff < 0)
271        {
272            // Terminal width has shrunk. The cursor will still be on the input line,
273            // but that line might very well be the last
274            int newLines = std::min((int)this->statusLineWidths_.size(), -heightDiff);
275            // Scroll terminal to create new lines
276            this->cout_ << "\033[" << newLines << 'S';
277        }
278
279        if (!this->bStatusPrinted_ && this->willPrintStatusLines())
280        {
281            // Scroll console to make way for status lines
282            this->cout_ << "\033[" << this->statusLineWidths_.size() << 'S';
283            this->bStatusPrinted_ = true;
284        }
285
286        // We always assume that the cursor is on the inputline.
287        // But we cannot always be sure about that, esp. if we scroll the console
288        this->cout_ << "\033[" << this->statusLineWidths_.size() << 'B';
289        this->cout_ << "\033[" << this->statusLineWidths_.size() << 'A';
290
291        // Erase status and input lines
292        this->cout_ << "\033[1G\033[J";
293        this->printInputLine();
294        this->printStatusLines();
295        this->cout_.flush();
296
297        // Process output written to std::cout
298        if (!this->origCout_.str().empty())
299        {
300            this->shell_->addOutputLine(this->origCout_.str());
301            this->origCout_.str("");
302        }
303    }
304
305    void IOConsole::printLogText(const std::string& text)
306    {
307        std::string output = text;
308        /*int level =*/ this->extractLogLevel(&output);
309
310/*
311        // Colour line
312        switch (level)
313        {
314        case -1: this->cout_ << "\033[37m"; break;
315        case  1: this->cout_ << "\033[91m"; break;
316        case  2: this->cout_ << "\033[31m"; break;
317        case  3: this->cout_ << "\033[34m"; break;
318        case  4: this->cout_ << "\033[36m"; break;
319        case  5: this->cout_ << "\033[35m"; break;
320        case  6: this->cout_ << "\033[37m"; break;
321        default: break;
322        }
323*/
324
325        // Print output line
326        this->cout_ << output;
327
328        // Reset colour to white
329//        this->cout_ << "\033[37m";
330    }
331
332    void IOConsole::printInputLine()
333    {
334        // Set cursor to the beginning of the line and erase the line
335        this->cout_ << "\033[1G\033[K";
336        // Indicate a command prompt
337        this->cout_ << this->promptString_;
338        // Save cursor position
339        this->cout_ << "\033[s";
340        // Print command line buffer
341        this->cout_ << this->shell_->getInput();
342        // Restore cursor position and move it to the right
343        this->cout_ << "\033[u";
344        if (this->buffer_->getCursorPosition() > 0)
345            this->cout_ << "\033[" << this->buffer_->getCursorPosition() << "C";
346    }
347
348    void IOConsole::printStatusLines()
349    {
350        if (this->willPrintStatusLines())
351        {
352            // Save cursor position
353            this->cout_ << "\033[s";
354            // Move cursor down (don't create a new line here because the buffer might flush then!)
355            this->cout_ << "\033[1B\033[1G";
356            this->cout_ << std::fixed << std::setprecision(2) << std::setw(5) << Game::getInstance().getAvgFPS() << " fps, ";
357            this->cout_ <<               std::setprecision(2) << std::setw(5) << Game::getInstance().getAvgTickTime() << " ms tick time";
358            // Restore cursor position
359            this->cout_ << "\033[u";
360            this->bStatusPrinted_ = true;
361        }
362        else
363            this->bStatusPrinted_ = false;
364    }
365
366    void IOConsole::setTerminalMode()
367    {
368        termios new_settings;
369
370        tcgetattr(0, this->originalTerminalSettings_);
371        new_settings = *this->originalTerminalSettings_;
372        new_settings.c_lflag &= ~(ICANON | ECHO);
373        //new_settings.c_lflag |= (ISIG | IEXTEN);
374        new_settings.c_cc[VTIME] = 0;
375        new_settings.c_cc[VMIN]  = 0;
376        tcsetattr(0, TCSANOW, &new_settings);
377    }
378
379    void IOConsole::resetTerminalMode()
380    {
381        tcsetattr(0, TCSANOW, IOConsole::originalTerminalSettings_);
382    }
383
384    void IOConsole::getTerminalSize()
385    {
386#ifdef TIOCGSIZE
387        struct ttysize win;
388        if (!ioctl(STDIN_FILENO, TIOCGSIZE, &win))
389        {
390            this->terminalWidth_  = win.ts_cols;
391            this->terminalHeight_ = win.ts_lines;
392            return;
393        }
394#elif defined TIOCGWINSZ
395        struct winsize win;
396        if (!ioctl(STDIN_FILENO, TIOCGWINSZ, &win))
397        {
398            this->terminalWidth_  = win.ws_col;
399            this->terminalHeight_ = win.ws_row;
400            return;
401        }
402#else
403        const char* s = getenv("COLUMNS");
404        this->terminalWidth_  = s ? strtol(s, NULL, 10) : 80;
405        s = getenv("LINES");
406        this->terminalHeight_ = s ? strtol(s, NULL, 10) : 24;
407        return;
408#endif
409        this->terminalWidth_  = 80;
410        this->terminalHeight_ = 24;
411    }
412
413    // ###############################
414    // ###  ShellListener methods  ###
415    // ###############################
416
417    //! Called if only the last output-line has changed
418    void IOConsole::onlyLastLineChanged()
419    {
420        // Save cursor position and move it to the beginning of the first output line
421        this->cout_ << "\033[s\033[1A\033[1G";
422        // Erase the line
423        this->cout_ << "\033[K";
424        // Reprint the last output line
425        this->printLogText(*(this->shell_->getNewestLineIterator()));
426        // Restore cursor
427        this->cout_ << "\033[u";
428        this->cout_.flush();
429    }
430
431    //! Called if a new output-line was added
432    void IOConsole::lineAdded()
433    {
434        int newLines = this->shell_->getNewestLineIterator()->size() / this->terminalWidth_ + 1;
435        // Create new lines by scrolling the screen
436        this->cout_ << "\033[" << newLines << 'S';
437        // Move cursor to the beginning of the new (last) output line
438        this->cout_ << "\033[" << newLines << "A\033[1G";
439        // Erase screen from here
440        this->cout_ << "\033[J";
441        // Print the new output lines
442        for (int i = 0; i < newLines; ++i)
443            this->printLogText(this->shell_->getNewestLineIterator()->substr(i*this->terminalWidth_, this->terminalWidth_));
444        // Move cursor down
445        this->cout_ << "\033[1B\033[1G";
446        // Print status and input lines
447        this->printInputLine();
448        this->printStatusLines();
449        this->cout_.flush();
450    }
451}
452
453#elif defined(ORXONOX_PLATFORM_WINDOWS)
454// ##################################
455// ###   Windows Implementation   ###
456// ##################################
457
458namespace orxonox
459{
460    IOConsole::IOConsole()
461        : shell_(new Shell("IOConsole", false, true))
462        , buffer_(shell_->getInputBuffer())
463        , cout_(std::cout.rdbuf())
464        , bStatusPrinted_(false)
465        , promptString_("orxonox # ")
466    {
467/*
468        this->setTerminalMode();
469        this->shell_->registerListener(this);
470
471        // Manually set the widths of the individual status lines
472        this->statusLineWidths_.push_back(29);
473        this->statusLineMaxWidth_ = 29;
474
475        this->getTerminalSize();
476        this->lastTerminalWidth_ = this->terminalWidth_;
477        this->lastTerminalHeight_ = this->terminalHeight_;
478
479        // Disable standard this->cout_ logging
480        OutputHandler::getInstance().disableCout();
481*/
482    }
483
484    IOConsole::~IOConsole()
485    {
486/*
487        resetTerminalMode();
488        this->shell_->destroy();
489
490        // Enable standard this->cout_ logging again
491        OutputHandler::getInstance().enableCout();
492*/
493    }
494
495    void IOConsole::update(const Clock& time)
496    {
497/*
498        unsigned char c = 0;
499        while (std::cin.good())
500        {
501            c = std::cin.get();
502            if (std::cin.bad())
503                break;
504        }
505        // Reset error flags in std::cin
506        std::cin.clear();
507
508        // Determine terminal width and height
509        this->lastTerminalWidth_ = this->terminalWidth_;
510        this->lastTerminalHeight_ = this->terminalHeight_;
511        this->getTerminalSize();
512*/
513    }
514
515    void IOConsole::printLogText(const std::string& text)
516    {
517    }
518
519    void IOConsole::printInputLine()
520    {
521    }
522
523    void IOConsole::printStatusLines()
524    {
525/*
526        if (this->willPrintStatusLines())
527        {
528            this->bStatusPrinted_ = true;
529        }
530        else
531            this->bStatusPrinted_ = false;
532*/
533    }
534
535    void IOConsole::setTerminalMode()
536    {
537    }
538
539    void IOConsole::resetTerminalMode()
540    {
541    }
542
543    void IOConsole::getTerminalSize()
544    {
545    }
546
547    // ###############################
548    // ###  ShellListener methods  ###
549    // ###############################
550
551    //! Called if only the last output-line has changed
552    void IOConsole::onlyLastLineChanged()
553    {
554    }
555
556    //! Called if a new output-line was added
557    void IOConsole::lineAdded()
558    {
559    }
560}
561
562#endif /* ORXONOX_PLATFORM_UNIX */
Note: See TracBrowser for help on using the repository browser.