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 }