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 }