1 module dlangui.platforms.ansi_console.dconsole;
2 
3 
4 public import dlangui.core.config;
5 static if (BACKEND_CONSOLE):
6 
7 import std.stdio;
8 import dlangui.core.logger;
9 
10 version(Windows) {
11     import core.sys.windows.winbase;
12     import core.sys.windows.wincon;
13     import core.sys.windows.winuser;
14     private import core.sys.windows.basetyps, core.sys.windows.w32api, core.sys.windows.winnt;
15 }
16 import dlangui.core.signals;
17 import dlangui.core.events;
18 
19 /// console cursor type
20 enum ConsoleCursorType {
21     Invisible, /// hidden
22     Insert,    /// insert (usually underscore)
23     Replace,   /// replace (usually square)
24 }
25 
26 enum TextColor : ubyte {
27     BLACK,          // 0
28     BLUE,
29     GREEN,
30     CYAN,
31     RED,
32     MAGENTA,
33     YELLOW,
34     GREY,
35     DARK_GREY,      // 8
36     LIGHT_BLUE,
37     LIGHT_GREEN,
38     LIGHT_CYAN,
39     LIGHT_RED,
40     LIGHT_MAGENTA,
41     LIGHT_YELLOW,
42     WHITE,          // 15
43 }
44 
45 immutable ubyte CONSOLE_TRANSPARENT_BACKGROUND = 0xFF;
46 
47 struct ConsoleChar {
48     dchar ch;
49     uint  attr = 0xFFFFFFFF;
50     @property ubyte backgroundColor() { return cast(ubyte)((attr >> 8) & 0xFF); }
51     @property void backgroundColor(ubyte b) { attr = (attr & 0xFFFF00FF) | ((cast(uint)b) << 8); }
52     @property ubyte textColor() { return cast(ubyte)((attr) & 0xFF); }
53     @property void textColor(ubyte b) { attr = (attr & 0xFFFFFF00) | ((cast(uint)b)); }
54     @property bool underline() { return (attr & 0x10000) != 0; }
55     @property void underline(bool b) { attr = (attr & ~0x10000); if (b) attr |= 0x10000; }
56     /// set value, supporting transparent background
57     void set(ConsoleChar v) {
58         if (v.backgroundColor == CONSOLE_TRANSPARENT_BACKGROUND) {
59             ch = v.ch;
60             textColor = v.textColor;
61             underline = v.underline;
62         } else
63             this = v;
64     }
65 }
66 
67 immutable ConsoleChar UNKNOWN_CHAR = ConsoleChar.init;
68 
69 struct ConsoleBuf {
70     protected int _width;
71     protected int _height;
72     protected int _cursorX;
73     protected int _cursorY;
74     protected ConsoleChar[] _chars;
75 
76 
77     @property int width() { return _width; }
78     @property int height() { return _height; }
79     @property int cursorX() { return _cursorX; }
80     @property int cursorY() { return _cursorY; }
81 
82     void clear(ConsoleChar ch) {
83         _chars[0 .. $] = ch;
84     }
85     void copyFrom(ref ConsoleBuf buf) {
86         _width = buf._width;
87         _height = buf._height;
88         _cursorX = buf._cursorX;
89         _cursorY = buf._cursorY;
90         _chars.length = buf._chars.length;
91         for(int i = 0; i < _chars.length; i++)
92             _chars[i] = buf._chars[i];
93     }
94     void set(int x, int y, ConsoleChar ch) {
95         _chars[y * _width + x].set(ch);
96     }
97     ConsoleChar get(int x, int y) {
98         return _chars[y * _width + x];
99     }
100     ConsoleChar[] line(int y) {
101         return _chars[y * _width .. (y + 1) * _width];
102     }
103     void resize(int w, int h) {
104         if (_width != w || _height != h) {
105             _chars.length = w * h;
106             _width = w;
107             _height = h;
108         }
109         _cursorX = 0;
110         _cursorY = 0;
111         _chars[0 .. $] = UNKNOWN_CHAR;
112     }
113     void scrollUp(uint attr) {
114         for (int i = 0; i + 1 < _height; i++) {
115             _chars[i * _width .. (i + 1) * _width] = _chars[(i + 1) * _width .. (i + 2) * _width];
116         }
117         _chars[(_height - 1) * _width .. _height * _width] = ConsoleChar(' ', attr);
118     }
119     void setCursor(int x, int y) {
120         _cursorX = x;
121         _cursorY = y;
122     }
123     void writeChar(dchar ch, uint attr) {
124         if (_cursorX >= _width) {
125             _cursorY++;
126             _cursorX = 0;
127             if (_cursorY >= _height) {
128                 _cursorY = _height - 1;
129                 scrollUp(attr);
130             }
131         }
132         if (ch == '\n') {
133             _cursorX = 0;
134             _cursorY++;
135             if (_cursorY >= _height) {
136                 scrollUp(attr);
137                 _cursorY = _height - 1;
138             }
139             return;
140         }
141         if (ch == '\r') {
142             _cursorX = 0;
143             return;
144         }
145         set(_cursorX, _cursorY, ConsoleChar(ch, attr));
146         _cursorX++;
147         if (_cursorX >= _width) {
148             if (_cursorY < _height - 1) {
149                 _cursorY++;
150                 _cursorX = 0;
151             }
152         }
153     }
154     void write(dstring str, uint attr) {
155         for (int i = 0; i < str.length; i++) {
156             writeChar(str[i], attr);
157         }
158     }
159 }
160 
161 version (Windows) {
162 } else {
163     import core.sys.posix.signal;
164     import dlangui.core.logger;
165     __gshared bool SIGHUP_flag = false;
166     extern(C) void signalHandler_SIGHUP( int ) nothrow @nogc @system
167     {
168         SIGHUP_flag = true;
169         try {
170             //Log.w("SIGHUP signal fired");
171         } catch (Exception e) {
172         }
173     }
174 
175     void setSignalHandlers() {
176         signal(SIGHUP, &signalHandler_SIGHUP);
177     }
178 }
179 
180 /// console I/O support
181 class Console {
182     private int _cursorX;
183     private int _cursorY;
184     private int _width;
185     private int _height;
186 
187     private ConsoleBuf _buf;
188     private ConsoleBuf _batchBuf;
189     private uint _consoleAttr;
190     private bool _stopped;
191 
192     @property int width() { return _width; }
193     @property int height() { return _height; }
194     @property int cursorX() { return _cursorX; }
195     @property int cursorY() { return _cursorY; }
196     @property void cursorX(int x) { _cursorX = x; }
197     @property void cursorY(int y) { _cursorY = y; }
198 
199     version(Windows) {
200         HANDLE _hstdin;
201         HANDLE _hstdout;
202         WORD _attr;
203         immutable ushort COMMON_LVB_UNDERSCORE = 0x8000;
204     } else {
205         immutable int READ_BUF_SIZE = 1024;
206         char[READ_BUF_SIZE] readBuf;
207         int readBufPos = 0;
208         bool isSequenceCompleted() {
209             if (!readBufPos)
210                 return false;
211             if (readBuf[0] == 0x1B) {
212                 if (readBufPos > 1 && readBuf[1] == '[' && readBuf[2] == 'M')
213                     return readBufPos >= 6;
214                 for (int i = 1; i < readBufPos; i++) {
215                     char ch = readBuf[i];
216                     if (ch == 'O' && i == readBufPos - 1)
217                         continue;
218                     if ((ch >= 'a' && ch <='z') || (ch >= 'A' && ch <='Z') || ch == '@' || ch == '~')
219                         return true;
220                 }
221                 return false;
222             }
223             if (readBuf[0] & 0x80) {
224                 if ((readBuf[0] & 0xE0) == 0xC0)
225                     return readBufPos >= 2;
226                 if ((readBuf[0] & 0xF0) == 0xE0)
227                     return readBufPos >= 3;
228                 if ((readBuf[0] & 0xF8) == 0xF0)
229                     return readBufPos >= 4;
230                 if ((readBuf[0] & 0xFC) == 0xF8)
231                     return readBufPos >= 5;
232                 return readBufPos >= 6;
233             }
234             return true;
235         }
236         string rawRead(int pollTimeout = 3000) {
237             if (_stopped)
238                 return null;
239             import core.thread;
240             import core.stdc.errno;
241             int waitTime = 0;
242             int startPos = readBufPos;
243             while (readBufPos < READ_BUF_SIZE) {
244                 import core.sys.posix.unistd;
245                 char ch = 0;
246                 int res = cast(int)read(STDIN_FILENO, &ch, 1);
247                 if (res < 0) {
248                     auto err = errno;
249                     switch (err) {
250                         case EBADF:
251                             Log.e("rawRead stdin EINVAL - stopping terminal");
252                             _stopped = true;
253                             return null;
254                         case EFAULT:
255                             Log.e("rawRead stdin EINVAL - stopping terminal");
256                             _stopped = true;
257                             return null;
258                         case EINVAL:
259                             Log.e("rawRead stdin EINVAL - stopping terminal");
260                             _stopped = true;
261                             return null;
262                         case EIO:
263                             Log.e("rawRead stdin EIO - stopping terminal");
264                             _stopped = true;
265                             return null;
266                         default:
267                             break;
268                     }
269                 }
270                 if (res <= 0) {
271                     if (readBufPos == startPos && waitTime < pollTimeout) {
272                         Thread.sleep( dur!("msecs")( 10 ) );
273                         waitTime += 10;
274                         continue;
275                     }
276                     break;
277                 }
278                 readBuf[readBufPos++] = ch;
279                 if (isSequenceCompleted())
280                     break;
281             }
282             if (readBufPos > 0 && isSequenceCompleted()) {
283                 string s = readBuf[0 .. readBufPos].idup;
284                 readBufPos = 0;
285                 return s;
286             }
287             return null;
288         }
289         bool rawWrite(string s) {
290             import core.sys.posix.unistd;
291             import core.stdc.errno;
292             int res = cast(int)write(STDOUT_FILENO, s.ptr, s.length);
293             if (res < 0) {
294                 auto err = errno;
295                 while (err == EAGAIN) {
296                     //debug Log.d("rawWrite error EAGAIN - will retry");
297                     res = cast(int)write(STDOUT_FILENO, s.ptr, s.length);
298                     if (res >= 0)
299                         return (res > 0);
300                     err = errno;
301                 }
302                 Log.e("rawWrite error ", err, " - stopping terminal");
303                 _stopped = true;
304             }
305             return (res > 0);
306         }
307     }
308 
309     version (Windows) {
310         DWORD savedStdinMode;
311         DWORD savedStdoutMode;
312     } else {
313         import core.sys.posix.termios;
314         import core.sys.posix.fcntl;
315         import core.sys.posix.sys.ioctl;
316         termios savedStdinState;
317     }
318 
319     void uninit() {
320         version (Windows) {
321             SetConsoleMode(_hstdin, savedStdinMode);
322             SetConsoleMode(_hstdout, savedStdoutMode);
323         } else {
324             import core.sys.posix.unistd;
325             tcsetattr(STDIN_FILENO, TCSANOW, &savedStdinState);
326             // reset terminal state
327             rawWrite("\033c");
328             // reset attributes
329             rawWrite("\x1b[0m");
330             // clear screen
331             rawWrite("\033[2J");
332             // normal cursor
333             rawWrite("\x1b[?25h");
334             // set auto wrapping mode
335             rawWrite("\x1b[?7h");
336         }
337     }
338 
339     bool init() {
340         version(Windows) {
341             _hstdin = GetStdHandle(STD_INPUT_HANDLE);
342             if (_hstdin == INVALID_HANDLE_VALUE)
343                 return false;
344             _hstdout = GetStdHandle(STD_OUTPUT_HANDLE);
345             if (_hstdout == INVALID_HANDLE_VALUE)
346                 return false;
347             CONSOLE_SCREEN_BUFFER_INFO csbi;
348             if (!GetConsoleScreenBufferInfo(_hstdout, &csbi))
349             {
350                 if (!AllocConsole()) {
351                     return false;
352                 }
353                 _hstdin = GetStdHandle(STD_INPUT_HANDLE);
354                 _hstdout = GetStdHandle(STD_OUTPUT_HANDLE);
355                 if (!GetConsoleScreenBufferInfo(_hstdout, &csbi)) {
356                     return false;
357                 }
358                 //printf( "GetConsoleScreenBufferInfo failed: %lu\n", GetLastError());
359             }
360             // update console modes
361             immutable DWORD ENABLE_QUICK_EDIT_MODE = 0x0040;
362             immutable DWORD ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004;
363             immutable DWORD ENABLE_LVB_GRID_WORLDWIDE = 0x0010;
364             DWORD mode = 0;
365             GetConsoleMode(_hstdin, &mode);
366             savedStdinMode = mode;
367             mode = mode & ~ENABLE_ECHO_INPUT;
368             mode = mode & ~ENABLE_LINE_INPUT;
369             mode = mode & ~ENABLE_QUICK_EDIT_MODE;
370             mode |= ENABLE_PROCESSED_INPUT;
371             mode |= ENABLE_MOUSE_INPUT;
372             mode |= ENABLE_WINDOW_INPUT;
373             SetConsoleMode(_hstdin, mode);
374             GetConsoleMode(_hstdout, &mode);
375             savedStdoutMode = mode;
376             mode = mode & ~ENABLE_PROCESSED_OUTPUT;
377             mode = mode & ~ENABLE_WRAP_AT_EOL_OUTPUT;
378             mode = mode & ~ENABLE_VIRTUAL_TERMINAL_PROCESSING;
379             mode |= ENABLE_LVB_GRID_WORLDWIDE;
380             SetConsoleMode(_hstdout, mode);
381 
382             _cursorX = csbi.dwCursorPosition.X;
383             _cursorY = csbi.dwCursorPosition.Y;
384             _width = csbi.srWindow.Right - csbi.srWindow.Left + 1; // csbi.dwSize.X;
385             _height = csbi.srWindow.Bottom - csbi.srWindow.Top + 1; // csbi.dwSize.Y;
386             _attr = csbi.wAttributes;
387             _textColor = _attr & 0x0F;
388             _backgroundColor = (_attr & 0xF0) >> 4;
389             _underline = (_attr & COMMON_LVB_UNDERSCORE) != 0;
390             //writeln("csbi=", csbi);
391         } else {
392             import core.sys.posix.unistd;
393             if (!isatty(1))
394                 return false;
395             setSignalHandlers();
396             fcntl(STDIN_FILENO, F_SETFL, fcntl(STDIN_FILENO, F_GETFL) | O_NONBLOCK);
397             termios ttystate;
398             //get the terminal state
399             tcgetattr(STDIN_FILENO, &ttystate);
400             savedStdinState = ttystate;
401             //turn off canonical mode
402             ttystate.c_lflag &= ~ICANON;
403             ttystate.c_lflag &= ~ECHO;
404             //minimum of number input read.
405             ttystate.c_cc[VMIN] = 1;
406             //set the terminal attributes.
407             tcsetattr(STDIN_FILENO, TCSANOW, &ttystate);
408 
409             winsize w;
410             ioctl(0, TIOCGWINSZ, &w);
411             _width = w.ws_col;
412             _height = w.ws_row;
413 
414             _cursorX = 0;
415             _cursorY = 0;
416 
417             _textColor = 7;
418             _backgroundColor = 0;
419             _underline = false;
420             // enable mouse tracking - all events
421             rawWrite("\033[?1003h");
422             //rawWrite("\x1b[c");
423             //string termType = rawRead();
424             //Log.d("Term type=", termType);
425         }
426         _buf.resize(_width, _height);
427         _batchBuf.resize(_width, _height);
428         return true;
429     }
430 
431     void resize(int width, int height) {
432         if (_width != width || _height != height) {
433             _buf.resize(width, height);
434             _batchBuf.resize(width, height);
435             _width = width;
436             _height = height;
437             clearScreen(); //??
438         }
439     }
440 
441     /// clear screen and set cursor position to 0,0
442     void clearScreen() {
443         calcAttributes();
444         if (!_batchMode) {
445             _buf.clear(ConsoleChar(' ', _consoleAttr));
446             version(Windows) {
447                 DWORD charsWritten;
448                 FillConsoleOutputCharacter(_hstdout, ' ', _width * _height, COORD(0, 0), &charsWritten);
449                 FillConsoleOutputAttribute(_hstdout, _attr, _width * _height, COORD(0, 0), &charsWritten);
450             } else {
451                 rawWrite("\033[2J");
452             }
453         } else {
454             _batchBuf.clear(ConsoleChar(' ', _consoleAttr));
455         }
456         setCursor(0, 0);
457     }
458 
459 
460     /// set cursor position
461     void setCursor(int x, int y) {
462         if (!_batchMode) {
463             _buf.setCursor(x, y);
464             rawSetCursor(x, y);
465             _cursorX = x;
466             _cursorY = y;
467         } else {
468             _batchBuf.setCursor(x, y);
469         }
470     }
471 
472     /// flush batched updates
473     void flush() {
474         if (_batchMode) {
475             bool drawn = false;
476             for (int i = 0; i < _batchBuf.height; i++) {
477                 ConsoleChar[] batchLine = _batchBuf.line(i);
478                 ConsoleChar[] bufLine = _buf.line(i);
479                 for (int x = 0; x < _batchBuf.width; x++) {
480                     if (batchLine[x] != ConsoleChar.init && batchLine[x] != bufLine[x]) {
481                         // found non-empty sequence
482                         int xx = 1;
483                         dchar[] str;
484                         str ~= batchLine[x].ch;
485                         bufLine[x] = batchLine[x];
486                         uint firstAttr = batchLine[x].attr;
487                         for ( ; x + xx < _batchBuf.width; xx++) {
488                             if (batchLine[x + xx] == ConsoleChar.init || batchLine[x + xx].attr != firstAttr)
489                                 break;
490                             str ~= batchLine[x + xx].ch;
491                             bufLine[x + xx].set(batchLine[x + xx]);
492                         }
493                         rawWriteTextAt(x, i, firstAttr, cast(dstring)str);
494                         x += xx - 1;
495                         drawn = true;
496                     }
497                 }
498             }
499             if (drawn || _cursorX != _batchBuf.cursorX || _cursorY != _batchBuf.cursorY) {
500                 _cursorX = _batchBuf.cursorX;
501                 _cursorY = _batchBuf.cursorY;
502                 rawSetCursor(_cursorX, _cursorY);
503                 rawSetCursorType(_cursorType);
504             }
505             _batchBuf.clear(ConsoleChar.init);
506         }
507     }
508 
509     /// write text string
510     void writeText(dstring str) {
511         if (!str.length)
512             return;
513         updateAttributes();
514         if (!_batchMode) {
515             // no batch mode, write directly to screen
516             _buf.write(str, _consoleAttr);
517             rawWriteText(str);
518             _cursorX = _buf.cursorX;
519             _cursorY = _buf.cursorY;
520         } else {
521             // batch mode
522             _batchBuf.write(str, _consoleAttr);
523             _cursorX = _batchBuf.cursorX;
524             _cursorY = _batchBuf.cursorY;
525         }
526     }
527 
528     protected void rawSetCursor(int x, int y) {
529         version(Windows) {
530             SetConsoleCursorPosition(_hstdout, COORD(cast(short)x, cast(short)y));
531         } else {
532             import core.stdc.stdio;
533             import core.stdc.string;
534             char[50] buf;
535             sprintf(buf.ptr, "\x1b[%d;%dH", y + 1, x + 1);
536             rawWrite(cast(string)(buf[0 .. strlen(buf.ptr)]));
537         }
538     }
539 
540 
541     private dstring _windowCaption;
542     void setWindowCaption(dstring str) {
543         if (_windowCaption == str)
544             return;
545         _windowCaption = str;
546         version(Windows) {
547             import std.utf;
548             SetConsoleTitle(toUTF16z(str));
549         } else {
550             // TODO: ANSI terminal caption
551         }
552     }
553 
554     private ConsoleCursorType _rawCursorType = ConsoleCursorType.Insert;
555     protected void rawSetCursorType(ConsoleCursorType type) {
556         if (_rawCursorType == type)
557             return;
558         version(Windows) {
559             CONSOLE_CURSOR_INFO ci;
560             switch(type) {
561                 default:
562                 case ConsoleCursorType.Insert:
563                     ci.dwSize = 10;
564                     ci.bVisible = TRUE;
565                     break;
566                 case ConsoleCursorType.Replace:
567                     ci.dwSize = 100;
568                     ci.bVisible = TRUE;
569                     break;
570                 case ConsoleCursorType.Invisible:
571                     ci.dwSize = 10;
572                     ci.bVisible = FALSE;
573                     break;
574             }
575             SetConsoleCursorInfo(_hstdout, &ci);
576         } else {
577             switch(type) {
578                 default:
579                 case ConsoleCursorType.Insert:
580                     rawWrite("\x1b[?25h");
581                     break;
582                 case ConsoleCursorType.Replace:
583                     rawWrite("\x1b[?25h");
584                     break;
585                 case ConsoleCursorType.Invisible:
586                     rawWrite("\x1b[?25l");
587                     break;
588             }
589         }
590         _rawCursorType = type;
591     }
592 
593     private ConsoleCursorType _cursorType = ConsoleCursorType.Insert;
594     void setCursorType(ConsoleCursorType type) {
595         _cursorType = type;
596         if (!_batchMode)
597             rawSetCursorType(_cursorType);
598     }
599 
600     protected void rawWriteTextAt(int x, int y, uint attr, dstring str) {
601         if (!str.length)
602             return;
603         version (Windows) {
604             CHAR_INFO[1000] lineBuf;
605             WORD newattr = cast(WORD) (
606                                        (attr & 0x0F)
607                                        | (((attr >> 8) & 0x0F) << 4)
608                                        | (((attr >> 16) & 1) ? COMMON_LVB_UNDERSCORE : 0)
609                                        );
610             for (int i = 0; i < str.length; i++) {
611                 lineBuf[i].UnicodeChar = cast(WCHAR)str[i];
612                 lineBuf[i].Attributes = newattr;
613             }
614             COORD bufSize;
615             COORD bufCoord;
616             bufSize.X = cast(short)str.length;
617             bufSize.Y = 1;
618             bufCoord.X = 0;
619             bufCoord.Y = 0;
620             SMALL_RECT region;
621             region.Left = cast(short)x;
622             region.Right = cast(short)(x + cast(int)str.length);
623             region.Top = cast(short)y;
624             region.Bottom = cast(short)y;
625             WriteConsoleOutput(_hstdout, lineBuf.ptr, bufSize, bufCoord, &region);
626         } else {
627             rawSetCursor(x, y);
628             rawSetAttributes(attr);
629             rawWriteText(cast(dstring)str);
630         }
631     }
632 
633     protected void rawWriteText(dstring str) {
634         version(Windows) {
635             import std.utf;
636             wstring s16 = toUTF16(str);
637             DWORD charsWritten;
638             WriteConsole(_hstdout, cast(const(void)*)s16.ptr, cast(uint)s16.length, &charsWritten, cast(void*)null);
639         } else {
640             import std.utf;
641             string s8 = toUTF8(str);
642             rawWrite(s8);
643         }
644     }
645 
646     version (Windows) {
647     } else {
648         private int lastTextColor = -1;
649         private int lastBackgroundColor = -1;
650     }
651     protected void rawSetAttributes(uint attr) {
652         version(Windows) {
653             WORD newattr = cast(WORD) (
654                 (attr & 0x0F)
655                 | (((attr >> 8) & 0x0F) << 4)
656                 | (((attr >> 16) & 1) ? COMMON_LVB_UNDERSCORE : 0)
657                 );
658             if (newattr != _attr) {
659                 _attr = newattr;
660                 SetConsoleTextAttribute(_hstdout, _attr);
661             }
662         } else {
663             int textCol = (attr & 0x0F);
664             int bgCol = ((attr >> 8) & 0x0F);
665             textCol = (textCol & 7) + (textCol & 8 ? 90 : 30);
666             bgCol = (bgCol & 7) + (bgCol & 8 ? 100 : 40);
667             if (textCol == lastTextColor && bgCol == lastBackgroundColor)
668                 return;
669             import core.stdc.stdio;
670             import core.stdc.string;
671             char[50] buf;
672             if (textCol != lastTextColor && bgCol != lastBackgroundColor)
673                 sprintf(buf.ptr, "\x1b[%d;%dm", textCol, bgCol);
674             else if (textCol != lastTextColor && bgCol == lastBackgroundColor)
675                 sprintf(buf.ptr, "\x1b[%dm", textCol);
676             else
677                 sprintf(buf.ptr, "\x1b[%dm", bgCol);
678             lastBackgroundColor = bgCol;
679             lastTextColor = textCol;
680             rawWrite(cast(string)buf[0 .. strlen(buf.ptr)]);
681         }
682     }
683 
684     protected void checkResize() {
685         version(Windows) {
686             CONSOLE_SCREEN_BUFFER_INFO csbi;
687             if (!GetConsoleScreenBufferInfo(_hstdout, &csbi))
688             {
689                 return;
690             }
691             _cursorX = csbi.dwCursorPosition.X;
692             _cursorY = csbi.dwCursorPosition.Y;
693             int w = csbi.srWindow.Right - csbi.srWindow.Left + 1; // csbi.dwSize.X;
694             int h = csbi.srWindow.Bottom - csbi.srWindow.Top + 1; // csbi.dwSize.Y;
695             if (_width != w || _height != h)
696                 handleConsoleResize(w, h);
697         } else {
698             import core.sys.posix.unistd;
699             //import core.sys.posix.fcntl;
700             //import core.sys.posix.termios;
701             import core.sys.posix.sys.ioctl;
702             winsize w;
703             ioctl(STDIN_FILENO, TIOCGWINSZ, &w);
704             if (_width != w.ws_col || _height != w.ws_row) {
705                 handleConsoleResize(w.ws_col, w.ws_row);
706             }
707         }
708     }
709 
710     protected void calcAttributes() {
711         _consoleAttr = cast(uint)_textColor | (cast(uint)_backgroundColor << 8) | (_underline ? 0x10000 : 0);
712         version(Windows) {
713             _attr = cast(WORD) (
714                 _textColor
715                 | (_backgroundColor << 4)
716                 | (_underline ? COMMON_LVB_UNDERSCORE : 0)
717                 );
718         } else {
719         }
720     }
721 
722     protected void updateAttributes() {
723         if (_dirtyAttributes) {
724             calcAttributes();
725             if (!_batchMode) {
726                 version(Windows) {
727                     SetConsoleTextAttribute(_hstdout, _attr);
728                 } else {
729                     rawSetAttributes(_consoleAttr);
730                 }
731             }
732             _dirtyAttributes = false;
733         }
734     }
735 
736     protected bool _batchMode;
737     @property bool batchMode() { return _batchMode; }
738     @property void batchMode(bool batch) {
739         if (_batchMode == batch)
740             return;
741         if (batch) {
742             // batch mode turned ON
743             _batchBuf.clear(ConsoleChar.init);
744             _batchMode = true;
745         } else {
746             // batch mode turned OFF
747             flush();
748             _batchMode = false;
749         }
750     }
751 
752     protected bool _dirtyAttributes;
753     protected ubyte _textColor;
754     protected ubyte _backgroundColor;
755     protected bool _underline;
756     /// get underline text attribute flag
757     @property bool underline() { return _underline; }
758     /// set underline text attrubute flag
759     @property void underline(bool flg) {
760         if (flg != _underline) {
761             _underline = flg;
762             _dirtyAttributes = true;
763         }
764     }
765     /// get text color
766     @property ubyte textColor() { return _textColor; }
767     /// set text color
768     @property void textColor(ubyte color) {
769         if (_textColor != color) {
770             _textColor = color;
771             _dirtyAttributes = true;
772         }
773     }
774     /// get background color
775     @property ubyte backgroundColor() { return _backgroundColor; }
776     /// set background color
777     @property void backgroundColor(ubyte color) {
778         if (_backgroundColor != color) {
779             _backgroundColor = color;
780             _dirtyAttributes = true;
781         }
782     }
783 
784     /// mouse event signal
785     Signal!OnMouseEvent mouseEvent;
786     /// keyboard event signal
787     Signal!OnKeyEvent keyEvent;
788     /// console size changed signal
789     Signal!OnConsoleResize resizeEvent;
790     /// console input is idle
791     Signal!OnInputIdle inputIdleEvent;
792 
793     protected bool handleKeyEvent(KeyEvent event) {
794         if (keyEvent.assigned)
795             return keyEvent(event);
796         return false;
797     }
798     protected bool handleMouseEvent(MouseEvent event) {
799         ButtonDetails * pbuttonDetails = null;
800         if (event.button == MouseButton.Left)
801             pbuttonDetails = &_lbutton;
802         else if (event.button == MouseButton.Right)
803             pbuttonDetails = &_rbutton;
804         else if (event.button == MouseButton.Middle)
805             pbuttonDetails = &_mbutton;
806         if (pbuttonDetails) {
807             if (event.action == MouseAction.ButtonDown) {
808                 pbuttonDetails.down(event.x, event.y, event.flags);
809             } else if (event.action == MouseAction.ButtonUp) {
810                 pbuttonDetails.up(event.x, event.y, event.flags);
811             }
812         }
813         event.lbutton = _lbutton;
814         event.rbutton = _rbutton;
815         event.mbutton = _mbutton;
816         if (mouseEvent.assigned)
817             return mouseEvent(event);
818         return false;
819     }
820     protected bool handleConsoleResize(int width, int height) {
821         resize(width, height);
822         if (resizeEvent.assigned)
823             return resizeEvent(width, height);
824         return false;
825     }
826     protected bool handleInputIdle() {
827         checkResize();
828         if (inputIdleEvent.assigned)
829             return inputIdleEvent();
830         return false;
831     }
832     private ushort lastMouseFlags = 0;
833     private MouseButton lastButtonDown = MouseButton.None;
834 
835     protected ButtonDetails _lbutton;
836     protected ButtonDetails _mbutton;
837     protected ButtonDetails _rbutton;
838 
839     void stop() {
840         // set stopped flag
841         _stopped = true;
842     }
843 
844     /// wait for input, handle input
845     bool pollInput() {
846         if (_stopped) {
847             debug Log.i("Console _stopped flag is set - returning false from pollInput");
848             return false;
849         }
850         version(Windows) {
851             INPUT_RECORD record;
852             DWORD eventsRead;
853             if (PeekConsoleInput(_hstdin, &record, 1, &eventsRead)) {
854                 if (eventsRead) {
855                     if (ReadConsoleInput(_hstdin, &record, 1, &eventsRead)) {
856                         switch (record.EventType) {
857                             case KEY_EVENT:
858                                 KeyAction action = record.KeyEvent.bKeyDown ? KeyAction.KeyDown : KeyAction.KeyUp;
859                                 KeyCode keyCode = KeyCode.NONE;
860                                 ushort flags = 0;
861                                 uint keyState = record.KeyEvent.dwControlKeyState;
862                                 if (keyState & LEFT_ALT_PRESSED)
863                                     flags |= KeyFlag.Alt | KeyFlag.LAlt;
864                                 if (keyState & RIGHT_ALT_PRESSED)
865                                     flags |= KeyFlag.Alt | KeyFlag.RAlt;
866                                 if (keyState & LEFT_CTRL_PRESSED)
867                                     flags |= KeyFlag.Control | KeyFlag.LControl;
868                                 if (keyState & RIGHT_CTRL_PRESSED)
869                                     flags |= KeyFlag.Control | KeyFlag.RControl;
870                                 if (keyState & SHIFT_PRESSED)
871                                     flags |= KeyFlag.Shift;
872                                 keyCode = cast(KeyCode)record.KeyEvent.wVirtualKeyCode;
873                                 dchar ch = record.KeyEvent.UnicodeChar;
874                                 handleKeyEvent(new KeyEvent(action, keyCode, flags));
875                                 if (action == KeyAction.KeyDown && ch) {
876                                     handleKeyEvent(new KeyEvent(KeyAction.Text, keyCode, flags, [ch]));
877                                 }
878                                 break;
879                             case MOUSE_EVENT:
880                                 short x = record.MouseEvent.dwMousePosition.X;
881                                 short y = record.MouseEvent.dwMousePosition.Y;
882                                 uint buttonState = record.MouseEvent.dwButtonState;
883                                 uint keyState = record.MouseEvent.dwControlKeyState;
884                                 uint eventFlags = record.MouseEvent.dwEventFlags;
885                                 ushort flags = 0;
886                                 if ((keyState & LEFT_ALT_PRESSED) || (keyState & RIGHT_ALT_PRESSED))
887                                     flags |= MouseFlag.Alt;
888                                 if ((keyState & LEFT_CTRL_PRESSED) || (keyState & RIGHT_CTRL_PRESSED))
889                                     flags |= MouseFlag.Control;
890                                 if (keyState & SHIFT_PRESSED)
891                                     flags |= MouseFlag.Shift;
892                                 if (buttonState & FROM_LEFT_1ST_BUTTON_PRESSED)
893                                     flags |= MouseFlag.LButton;
894                                 if (buttonState & FROM_LEFT_2ND_BUTTON_PRESSED)
895                                     flags |= MouseFlag.MButton;
896                                 if (buttonState & RIGHTMOST_BUTTON_PRESSED)
897                                     flags |= MouseFlag.RButton;
898                                 bool actionSent = false;
899                                 if ((flags & MouseFlag.ButtonsMask) != (lastMouseFlags & MouseFlag.ButtonsMask)) {
900                                     MouseButton btn = MouseButton.None;
901                                     MouseAction action = MouseAction.Cancel;
902                                     if ((flags & MouseFlag.LButton) != (lastMouseFlags & MouseFlag.LButton)) {
903                                         btn = MouseButton.Left;
904                                         action = (flags & MouseFlag.LButton) ? MouseAction.ButtonDown : MouseAction.ButtonUp;
905                                         handleMouseEvent(new MouseEvent(action, btn, flags, cast(short)x, cast(short)y));
906                                     }
907                                     if ((flags & MouseFlag.RButton) != (lastMouseFlags & MouseFlag.RButton)) {
908                                         btn = MouseButton.Right;
909                                         action = (flags & MouseFlag.RButton) ? MouseAction.ButtonDown : MouseAction.ButtonUp;
910                                         handleMouseEvent(new MouseEvent(action, btn, flags, cast(short)x, cast(short)y));
911                                     }
912                                     if ((flags & MouseFlag.MButton) != (lastMouseFlags & MouseFlag.MButton)) {
913                                         btn = MouseButton.Middle;
914                                         action = (flags & MouseFlag.MButton) ? MouseAction.ButtonDown : MouseAction.ButtonUp;
915                                         handleMouseEvent(new MouseEvent(action, btn, flags, cast(short)x, cast(short)y));
916                                     }
917                                     if (action != MouseAction.Cancel)
918                                         actionSent = true;
919                                 }
920                                 if ((eventFlags & MOUSE_MOVED) && !actionSent) {
921                                     handleMouseEvent(new MouseEvent(MouseAction.Move, MouseButton.None, flags, cast(short)x, cast(short)y));
922                                     actionSent = true;
923                                 }
924                                 if (eventFlags & MOUSE_WHEELED) {
925                                     short delta = (cast(short)(buttonState >> 16));
926                                     handleMouseEvent(new MouseEvent(MouseAction.Wheel, MouseButton.None, flags, cast(short)x, cast(short)y, delta));
927                                     actionSent = true;
928                                 }
929                                 lastMouseFlags = flags;
930                                 break;
931                             case WINDOW_BUFFER_SIZE_EVENT:
932                                 handleConsoleResize(record.WindowBufferSizeEvent.dwSize.X, record.WindowBufferSizeEvent.dwSize.Y);
933                                 break;
934                             default:
935                                 break;
936                         }
937                     } else {
938                         return false;
939                     }
940                 } else {
941                     handleInputIdle();
942                     Sleep(1);
943                 }
944             } else {
945                 DWORD err = GetLastError();
946                 _stopped = true;
947                 return false;
948             }
949         } else {
950             import std.algorithm;
951             if (SIGHUP_flag) {
952                 Log.i("SIGHUP signal fired");
953                 _stopped = true;
954             }
955             import dlangui.core.logger;
956             string s = rawRead(20);
957             if (s is null) {
958                 handleInputIdle();
959                 return !_stopped;
960             }
961             if (s.length == 6 && s[0] == 27 && s[1] == '[' && s[2] == 'M') {
962                 // mouse event
963                 MouseAction a = MouseAction.Cancel;
964                 int mb = s[3] - 32;
965                 int mx = s[4] - 32 - 1;
966                 int my = s[5] - 32 - 1;
967 
968                 int btn = mb & 3;
969                 if (btn < 3)
970                     a = MouseAction.ButtonDown;
971                 else
972                     a = MouseAction.ButtonUp;
973                 if (mb & 32) {
974                     a = MouseAction.Move;
975                 }
976                 MouseButton button = MouseButton.None;
977                 ushort flags = 0;
978                 if (btn == 0) {
979                     flags |= MouseFlag.LButton;
980                     button = MouseButton.Left;
981                 }
982                 if (btn == 2) {
983                     flags |= MouseFlag.RButton;
984                     button = MouseButton.Right;
985                 }
986                 if (btn == 1) {
987                     flags |= MouseFlag.MButton;
988                     button = MouseButton.Middle;
989                 }
990                 if (btn == 3 && a != MouseAction.Move)
991                     a = MouseAction.ButtonUp;
992                 if (button != MouseButton.None)
993                     lastButtonDown = button;
994                 else if (a == MouseAction.ButtonUp)
995                     button = lastButtonDown;
996                 if (mb & 4)
997                     flags |= MouseFlag.Shift;
998                 if (mb & 8)
999                     flags |= MouseFlag.Alt;
1000                 if (mb & 16)
1001                     flags |= MouseFlag.Control;
1002                 //Log.d("mouse evt:", s, " mb=", mb, " mx=", mx, " my=", my, "  action=", a, " button=", button, " flags=", flags);
1003                 MouseEvent evt = new MouseEvent(a, button, flags, cast(short)mx, cast(short)my);
1004                 handleMouseEvent(evt);
1005                 return true;
1006             }
1007             int keyCode = 0;
1008             int keyFlags = 0;
1009             dstring text;
1010             if (s[0] == 27) {
1011                 //
1012                 string escSequence = s[1 .. $];
1013                 //Log.d("ESC ", escSequence);
1014                 char letter = escSequence[$ - 1];
1015                 if (escSequence.startsWith("[") && escSequence.length > 1) {
1016                     import std.string : indexOf;
1017                     string options = escSequence[1 .. $ - 1];
1018                     if (letter == '~') {
1019                         string code = options;
1020                         int semicolonPos = cast(int)options.indexOf(";");
1021                         if (semicolonPos >= 0) {
1022                             code = options[0 .. semicolonPos];
1023                             options = options[semicolonPos + 1 .. $];
1024                         } else {
1025                             options = null;
1026                         }
1027                         switch(options) {
1028                             case "5": keyFlags = KeyFlag.Control; break;
1029                             case "2": keyFlags = KeyFlag.Shift; break;
1030                             case "3": keyFlags = KeyFlag.Alt; break;
1031                             case "4": keyFlags = KeyFlag.Shift | KeyFlag.Alt; break;
1032                             case "6": keyFlags = KeyFlag.Shift | KeyFlag.Control; break;
1033                             case "7": keyFlags = KeyFlag.Alt | KeyFlag.Control; break;
1034                             case "8": keyFlags = KeyFlag.Shift | KeyFlag.Alt | KeyFlag.Control; break;
1035                             default: break;
1036                         }
1037                         switch(code) {
1038                             case "15": keyCode = KeyCode.F5; break;
1039                             case "17": keyCode = KeyCode.F6; break;
1040                             case "18": keyCode = KeyCode.F7; break;
1041                             case "19": keyCode = KeyCode.F8; break;
1042                             case "20": keyCode = KeyCode.F9; break;
1043                             case "21": keyCode = KeyCode.F10; break;
1044                             case "23": keyCode = KeyCode.F11; break;
1045                             case "24": keyCode = KeyCode.F12; break;
1046                             case "5": keyCode = KeyCode.PAGEUP; break;
1047                             case "6": keyCode = KeyCode.PAGEDOWN; break;
1048                             case "2": keyCode = KeyCode.INS; break;
1049                             case "3": keyCode = KeyCode.DEL; break;
1050                             default: break;
1051                         }
1052                     } else {
1053                         switch(options) {
1054                             case "1;5": keyFlags = KeyFlag.Control; break;
1055                             case "1;2": keyFlags = KeyFlag.Shift; break;
1056                             case "1;3": keyFlags = KeyFlag.Alt; break;
1057                             case "1;4": keyFlags = KeyFlag.Shift | KeyFlag.Alt; break;
1058                             case "1;6": keyFlags = KeyFlag.Shift | KeyFlag.Control; break;
1059                             case "1;7": keyFlags = KeyFlag.Alt | KeyFlag.Control; break;
1060                             case "1;8": keyFlags = KeyFlag.Shift | KeyFlag.Alt | KeyFlag.Control; break;
1061                             default: break;
1062                         }
1063                         switch(letter) {
1064                             case 'A': keyCode = KeyCode.UP; break;
1065                             case 'B': keyCode = KeyCode.DOWN; break;
1066                             case 'D': keyCode = KeyCode.LEFT; break;
1067                             case 'C': keyCode = KeyCode.RIGHT; break;
1068                             case 'H': keyCode = KeyCode.HOME; break;
1069                             case 'F': keyCode = KeyCode.END; break;
1070                             default: break;
1071                         }
1072                         switch(letter) {
1073                             case 'P': keyCode = KeyCode.F1; break;
1074                             case 'Q': keyCode = KeyCode.F2; break;
1075                             case 'R': keyCode = KeyCode.F3; break;
1076                             case 'S': keyCode = KeyCode.F4; break;
1077                             default: break;
1078                         }
1079                     }
1080                 } else if (escSequence.startsWith("O")) {
1081                     switch(letter) {
1082                         case 'P': keyCode = KeyCode.F1; break;
1083                         case 'Q': keyCode = KeyCode.F2; break;
1084                         case 'R': keyCode = KeyCode.F3; break;
1085                         case 'S': keyCode = KeyCode.F4; break;
1086                         default: break;
1087                     }
1088                 }
1089             } else {
1090                 import std.utf;
1091                 //Log.d("stdin: ", s);
1092                 try {
1093                     dstring s32 = toUTF32(s);
1094                     switch(s) {
1095                         case " ": keyCode = KeyCode.SPACE; text = " "; break;
1096                         case "\t": keyCode = KeyCode.TAB; break;
1097                         case "\n": keyCode = KeyCode.RETURN; /* text = " " ; */ break;
1098                         case "0": keyCode = KeyCode.KEY_0; text = s32; break;
1099                         case "1": keyCode = KeyCode.KEY_1; text = s32; break;
1100                         case "2": keyCode = KeyCode.KEY_2; text = s32; break;
1101                         case "3": keyCode = KeyCode.KEY_3; text = s32; break;
1102                         case "4": keyCode = KeyCode.KEY_4; text = s32; break;
1103                         case "5": keyCode = KeyCode.KEY_5; text = s32; break;
1104                         case "6": keyCode = KeyCode.KEY_6; text = s32; break;
1105                         case "7": keyCode = KeyCode.KEY_7; text = s32; break;
1106                         case "8": keyCode = KeyCode.KEY_8; text = s32; break;
1107                         case "9": keyCode = KeyCode.KEY_9; text = s32; break;
1108                         case "a":
1109                         case "A":
1110                             keyCode = KeyCode.KEY_A; text = s32; break;
1111                         case "b":
1112                         case "B":
1113                             keyCode = KeyCode.KEY_B; text = s32; break;
1114                         case "c":
1115                         case "C":
1116                             keyCode = KeyCode.KEY_C; text = s32; break;
1117                         case "d":
1118                         case "D":
1119                             keyCode = KeyCode.KEY_D; text = s32; break;
1120                         case "e":
1121                         case "E":
1122                             keyCode = KeyCode.KEY_E; text = s32; break;
1123                         case "f":
1124                         case "F":
1125                             keyCode = KeyCode.KEY_F; text = s32; break;
1126                         case "g":
1127                         case "G":
1128                             keyCode = KeyCode.KEY_G; text = s32; break;
1129                         case "h":
1130                         case "H":
1131                             keyCode = KeyCode.KEY_H; text = s32; break;
1132                         case "i":
1133                         case "I":
1134                             keyCode = KeyCode.KEY_I; text = s32; break;
1135                         case "j":
1136                         case "J":
1137                             keyCode = KeyCode.KEY_J; text = s32; break;
1138                         case "k":
1139                         case "K":
1140                             keyCode = KeyCode.KEY_K; text = s32; break;
1141                         case "l":
1142                         case "L":
1143                             keyCode = KeyCode.KEY_L; text = s32; break;
1144                         case "m":
1145                         case "M":
1146                             keyCode = KeyCode.KEY_M; text = s32; break;
1147                         case "n":
1148                         case "N":
1149                             keyCode = KeyCode.KEY_N; text = s32; break;
1150                         case "o":
1151                         case "O":
1152                             keyCode = KeyCode.KEY_O; text = s32; break;
1153                         case "p":
1154                         case "P":
1155                             keyCode = KeyCode.KEY_P; text = s32; break;
1156                         case "q":
1157                         case "Q":
1158                             keyCode = KeyCode.KEY_Q; text = s32; break;
1159                         case "r":
1160                         case "R":
1161                             keyCode = KeyCode.KEY_R; text = s32; break;
1162                         case "s":
1163                         case "S":
1164                             keyCode = KeyCode.KEY_S; text = s32; break;
1165                         case "t":
1166                         case "T":
1167                             keyCode = KeyCode.KEY_T; text = s32; break;
1168                         case "u":
1169                         case "U":
1170                             keyCode = KeyCode.KEY_U; text = s32; break;
1171                         case "v":
1172                         case "V":
1173                             keyCode = KeyCode.KEY_V; text = s32; break;
1174                         case "w":
1175                         case "W":
1176                             keyCode = KeyCode.KEY_W; text = s32; break;
1177                         case "x":
1178                         case "X":
1179                             keyCode = KeyCode.KEY_X; text = s32; break;
1180                         case "y":
1181                         case "Y":
1182                             keyCode = KeyCode.KEY_Y; text = s32; break;
1183                         case "z":
1184                         case "Z":
1185                             keyCode = KeyCode.KEY_Z; text = s32; break;
1186                         default:
1187                             if (s32[0] >= 32)
1188                                 text = s32;
1189                             keyCode = 0x400000 | s32[0];
1190                             break;
1191                     }
1192                     if (s32.length == 1 && s32[0] >= 1 && s32[0] <= 26) {
1193                         // ctrl + A..Z
1194                         keyCode = KeyCode.KEY_A + s32[0] - 1;
1195                         keyFlags = KeyFlag.Control;
1196                     }
1197                     if (s32.length == 1 && s32[0] >= 'A' && s32[0] <= 'Z') {
1198                         // uppercase letter - with shift
1199                         keyFlags = KeyFlag.Shift;
1200                     }
1201                 } catch (Exception e) {
1202                     // skip invalid utf8 encoding
1203                 }
1204             }
1205             if (keyCode) {
1206                 KeyEvent keyDown = new KeyEvent(KeyAction.KeyDown, keyCode, keyFlags);
1207                 handleKeyEvent(keyDown);
1208                 if (text.length) {
1209                     KeyEvent keyText = new KeyEvent(KeyAction.Text, keyCode, keyFlags, text);
1210                     handleKeyEvent(keyText);
1211                 }
1212                 KeyEvent keyUp = new KeyEvent(KeyAction.KeyUp, keyCode, keyFlags);
1213                 handleKeyEvent(keyUp);
1214             }
1215         }
1216         return !_stopped;
1217     }
1218 }
1219 
1220 /// interface - slot for onMouse
1221 interface OnMouseEvent {
1222     bool onMouse(MouseEvent event);
1223 }
1224 
1225 /// interface - slot for onKey
1226 interface OnKeyEvent {
1227     bool onKey(KeyEvent event);
1228 }
1229 
1230 interface OnConsoleResize {
1231     bool onResize(int width, int height);
1232 }
1233 
1234 interface OnInputIdle {
1235     bool onInputIdle();
1236 }