1 /// Utils to work with POSIX terminal 2 module sily.terminal; 3 4 static this() { 5 version(windows) { 6 import core.stdc.stdlib: exit; 7 exit(2); 8 } 9 10 // To prevent from killing terminal by calling reset before set 11 tcgetattr(stdin.fileno, &originalTermios); 12 } 13 14 /* ------------------------------ TERMINAL SIZE ----------------------------- */ 15 import core.sys.posix.sys.ioctl: winsize, ioctl, TIOCGWINSZ; 16 17 /// Returns bash terminal width 18 int terminalWidth() { 19 winsize w; 20 ioctl(0, TIOCGWINSZ, &w); 21 return w.ws_col; 22 } 23 24 /// Returns bash terminal height 25 int terminalHeight() { 26 winsize w; 27 ioctl(0, TIOCGWINSZ, &w); 28 return w.ws_row; 29 } 30 31 /* -------------------------------- RAW MODE -------------------------------- */ 32 import core.stdc.stdio: setvbuf, _IONBF, _IOLBF; 33 import core.stdc.stdlib: atexit; 34 import core.stdc.string: memcpy; 35 import core.sys.posix.termios: termios, tcgetattr, tcsetattr, TCSANOW; 36 import core.sys.posix.unistd: read; 37 import core.sys.posix.sys.select: select, fd_set, FD_ZERO, FD_SET; 38 import core.sys.posix.sys.time: timeval; 39 40 import std.stdio: stdin, stdout; 41 42 private extern(C) void cfmakeraw(termios *termios_p); 43 44 private termios originalTermios; 45 46 private bool __isTermiosRaw = false; 47 48 /// Is terminal in raw mode (have `setTerminalModeRaw` been called yet?) 49 bool isTerminalRaw() nothrow { 50 return __isTermiosRaw; 51 } 52 53 /// Resets termios back to default and buffers stdout 54 extern(C) alias terminalModeReset = function() { 55 tcsetattr(0, TCSANOW, &originalTermios); 56 setvbuf(stdout.getFP, null, _IOLBF, 1024); 57 __isTermiosRaw = false; 58 }; 59 60 /** 61 Creates new termios and unbuffers stdout. Required for `kbhit` and `getch` 62 DO NOT USE IF YOU DON'T KNOW WHAT YOU'RE DOING 63 64 Note that in raw mode CRLF (`\r\n`) newline will be 65 required instead of normal LF (`\n`) 66 Params: 67 removeStdoutBuffer = Sets stdout buffer to null allowing immediate render without flush() 68 */ 69 void terminalModeSetRaw(bool removeStdoutBuffer = true) { 70 import core.sys.posix.termios; 71 termios newTermios; 72 73 tcgetattr(stdin.fileno, &originalTermios); 74 memcpy(&newTermios, &originalTermios, termios.sizeof); 75 76 cfmakeraw(&newTermios); 77 78 newTermios.c_lflag &= ~(ICANON | ECHO | ISIG | IEXTEN); 79 // newTermios.c_lflag &= ~(ICANON | ECHO); 80 newTermios.c_iflag &= ~(ICRNL | INLCR | OPOST); 81 newTermios.c_cc[VMIN] = 1; 82 newTermios.c_cc[VTIME] = 0; 83 84 if (removeStdoutBuffer) setvbuf(stdout.getFP, null, _IONBF, 0); 85 86 tcsetattr(stdin.fileno, TCSANOW, &newTermios); 87 88 atexit(terminalModeReset); 89 __isTermiosRaw = true; 90 } 91 92 /// Returns 1 if any key was pressed 93 int kbhit() { 94 timeval tv = { 0, 0 }; 95 fd_set fds; 96 FD_ZERO(&fds); 97 FD_SET(stdin.fileno, &fds); 98 return select(1, &fds, null, null, &tv); 99 } 100 101 /// Returns last pressed key 102 int getch() { 103 int r; 104 uint c; 105 106 if ((r = cast(int) read(stdin.fileno, &c, ubyte.sizeof)) < 0) { 107 return r; 108 } else { 109 return c; 110 } 111 } 112 113 /* ---------------------------------- MISC ---------------------------------- */ 114 import core.sys.posix.unistd: posixIsATTY = isatty; 115 import std.stdio: File; 116 import core.stdc.stdio: FILE, cfileno = fileno; 117 import core.stdc.stdlib: cexit = exit; 118 import core.stdc.errno; 119 import core.thread: Thread; 120 import core.time: dmsecs = msecs; 121 122 /// Returns true if file is a tty 123 bool isatty(File file) { 124 return cast(bool) posixIsATTY(file.fileno); 125 } 126 /// Ditto 127 bool isatty(FILE* handle) { 128 return cast(bool) posixIsATTY(handle.cfileno); 129 } 130 /// Ditto 131 bool isatty(int fd_set) { 132 return cast(bool) posixIsATTY(fd_set); 133 } 134 135 /// Forcefully closes application 136 void exit(ErrorCode code = ErrorCode.general) { 137 cexit(cast(int) code); 138 } 139 140 /// Alias to ErrorCode enum 141 alias ExitCode = ErrorCode; 142 143 /// Enum containing common exit codes 144 enum ErrorCode { 145 /// Program completed correctly 146 success = 0, 147 /// Catchall for general errors (misc errors, such as `x / 0`) 148 general = 1, 149 /// Operation not permitted (missing keyword/command or permission problem) 150 noperm = 2, 151 /// Command invoked cannot execute (permission problem or command is not executable) 152 noexec = 126, 153 /// Command not found (possible problem with `$PATH` or typo) 154 notfound = 127, 155 /// Invalid argument to exit (see ErrorCode.nocode) 156 noexit = 128, 157 /// Fatal error (further execution is not possible or might harm the OS) 158 fatal = 129, 159 /// Terminated with `Ctrl-C` 160 sigint = 130, 161 /// Exit status out of range (maximal exit code) 162 nocode = 255 163 } 164 165 /// Sleeps for set amount of msecs 166 void sleep(uint msecs) { 167 Thread.sleep(msecs.dmsecs); 168 }