1 /// Posix specific terminal utils 2 module sily.terminal.posix; 3 4 version(Posix): 5 6 static this() { 7 // version(windows) { 8 // import core.stdc.stdlib: exit; 9 // exit(2); 10 // } 11 12 // To prevent from killing terminal by calling reset before set 13 tcgetattr(stdin.fileno, &originalTermios); 14 } 15 16 /* ------------------------------ TERMINAL SIZE ----------------------------- */ 17 import core.sys.posix.sys.ioctl: winsize, ioctl, TIOCGWINSZ; 18 19 /// Returns bash terminal width 20 int terminalWidth() { 21 winsize w; 22 ioctl(0, TIOCGWINSZ, &w); 23 return w.ws_col; 24 } 25 26 /// Returns bash terminal height 27 int terminalHeight() { 28 winsize w; 29 ioctl(0, TIOCGWINSZ, &w); 30 return w.ws_row; 31 } 32 33 /* -------------------------------- RAW MODE -------------------------------- */ 34 import core.stdc.stdio: setvbuf, _IONBF, _IOLBF; 35 import core.stdc.stdlib: atexit; 36 import core.stdc.string: memcpy; 37 import core.sys.posix.termios: termios, tcgetattr, tcsetattr, TCSANOW; 38 import core.sys.posix.unistd: read; 39 import core.sys.posix.sys.select: select, fd_set, FD_ZERO, FD_SET; 40 import core.sys.posix.sys.time: timeval; 41 42 import std.stdio: stdin, stdout, File; 43 44 private extern(C) void cfmakeraw(termios *termios_p); 45 46 private termios originalTermios; 47 48 private bool __isTermiosRaw = false; 49 50 /// Is terminal in raw mode (have `setTerminalModeRaw` been called yet?) 51 bool isTerminalRaw() nothrow { 52 return __isTermiosRaw; 53 } 54 55 /// Resets termios back to default and buffers stdout 56 extern(C) alias terminalModeReset = function() { 57 tcsetattr(0, TCSANOW, &originalTermios); 58 setvbuf(stdout.getFP, null, _IOLBF, 1024); 59 __isTermiosRaw = false; 60 }; 61 62 /** 63 Creates new termios and unbuffers stdout. Required for `kbhit` and `getch` 64 DO NOT USE IF YOU DON'T KNOW WHAT YOU'RE DOING 65 66 Note that in raw mode CRLF (`\r\n`) newline will be 67 required instead of normal LF (`\n`) 68 Params: 69 removeStdoutBuffer = Sets stdout buffer to null allowing immediate render without flush() 70 */ 71 void terminalModeSetRaw(bool removeStdoutBuffer = true) { 72 import core.sys.posix.termios; 73 termios newTermios; 74 75 tcgetattr(stdin.fileno, &originalTermios); 76 memcpy(&newTermios, &originalTermios, termios.sizeof); 77 78 cfmakeraw(&newTermios); 79 80 newTermios.c_lflag &= ~(ICANON | ECHO | ISIG | IEXTEN); 81 // newTermios.c_lflag &= ~(ICANON | ECHO); 82 newTermios.c_iflag &= ~(ICRNL | INLCR | OPOST); 83 newTermios.c_cc[VMIN] = 1; 84 newTermios.c_cc[VTIME] = 0; 85 86 if (removeStdoutBuffer) setvbuf(stdout.getFP, null, _IONBF, 0); 87 88 tcsetattr(stdin.fileno, TCSANOW, &newTermios); 89 90 atexit(terminalModeReset); 91 __isTermiosRaw = true; 92 } 93 94 /// Returns true if any key was pressed 95 bool kbhit() { 96 timeval tv = { 0, 0 }; 97 fd_set fds; 98 FD_ZERO(&fds); 99 FD_SET(stdin.fileno, &fds); 100 return select(1, &fds, null, null, &tv) == 1; 101 } 102 103 /// Returns last pressed key 104 int getch() { 105 int r; 106 uint c; 107 108 if ((r = cast(int) read(stdin.fileno, &c, ubyte.sizeof)) < 0) { 109 return r; 110 } else { 111 return c; 112 } 113 } 114 115 /* ---------------------------------- MISC ---------------------------------- */ 116 import core.sys.posix.unistd: posixIsATTY = isatty; 117 // import std.stdio: File; 118 import core.stdc.stdio: FILE, cfileno = fileno; 119 import core.stdc.errno; 120 121 /// Returns true if file is a tty 122 bool isatty(File file) { 123 return cast(bool) posixIsATTY(file.fileno); 124 } 125 /// Ditto 126 bool isatty(FILE* handle) { 127 return cast(bool) posixIsATTY(handle.cfileno); 128 } 129 /// Ditto 130 bool isatty(int fd_set) { 131 return cast(bool) posixIsATTY(fd_set); 132 } 133 134 135 136 /* -------------------------- Terminal Capabilities --------------------------- */ 137 138 // import std.stdio: File; 139 import std.file: readText; 140 141 // TODO: read termcap and put it into struct 142 143 struct Termcap { 144 TermType termType = TermType.vt100; 145 string capPath = ""; 146 // TODO: keys 147 // TODO: commands 148 } 149 150 enum TermType { 151 // TODO: flags 152 vt, 153 vt100, 154 vt101 155 }