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, ®ion);
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 }