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