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