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 }