1 /**
2  * Consoleur: a package for interaction with character-oriented terminal emulators
3  *
4  * Copyright: Maxim Freck, 2017.
5  * Authors:   Maxim Freck <maxim@freck.pp.ru>
6  * License:   $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
7  */
8 module consoleur.core.posix;
9 version(Posix) {
10 
11 ///STDIN file descriptor
12 int STDIN_FILENO = 0;
13 ///STDOUT file descriptor
14 enum STDOUT_FILENO = 1;
15 ///STERR file descriptor
16 enum STDERR_FILENO = 2;
17 
18 /*******
19  * Console point: row and column
20  */
21 struct Point {
22 	int row;
23 	int col;
24 }
25 
26 /*******
27  * Tests whether a STDOUT descriptor refers to a terminal
28  * Returns: true on success, false in case of failure
29  */
30 bool isAttyOut() @safe
31 {
32 	import core.sys.posix.unistd: isatty;
33 	return cast(bool)isatty(STDOUT_FILENO);
34 }
35 
36 /*******
37  * Tests whether a STDIN descriptor refers to a terminal
38  * Returns: true on success, false in case of failure
39  */
40 bool isAttyIn() @safe
41 {
42 	import core.sys.posix.unistd: isatty;
43 	return cast(bool)isatty(STDIN_FILENO);
44 }
45 
46 /*******
47  * Flushes STDOUT buffer
48  */
49 bool flushStdout()
50 {
51 	import core.stdc.stdio: fflush;
52 	import std.stdio: stdout;
53 	return fflush(stdout.getFP) == 0;
54 }
55 
56 
57 /*******
58  * Writes raw data to stdout without buffering
59  *
60  * Params:
61  *  buffer = The buffer
62  */
63 nothrow size_t rawStdout(string buffer) @trusted
64 {
65 	import core.sys.posix.unistd: write;
66 	return write(STDOUT_FILENO, buffer.ptr, buffer.length);
67 }
68 
69 private auto buffer = new ubyte[MAX_STDIN_BUFFER_SIZE];
70 private ubyte[] stdinBuffer;
71 private size_t stdinPosition;
72 private enum MAX_STDIN_BUFFER_SIZE = 32;
73 
74 /*******
75  * Reads raw ubyte from stdin
76  * Returns: true on success, false in case of failure
77  *
78  * Params:
79  *  b = The variable to read byte into
80  */
81 nothrow bool popStdin(ref ubyte b) @safe
82 {
83 	if (stdinBuffer.length == stdinPosition) fillStdinBuffer();
84 
85 	if (stdinBuffer.length == stdinPosition) return false;
86 	b = stdinBuffer[stdinPosition++];
87 
88 	return true;
89 }
90 
91 private nothrow void fillStdinBuffer() @trusted
92 {
93 	import core.sys.posix.unistd: read;
94 
95 	auto len = read(STDIN_FILENO, buffer.ptr, MAX_STDIN_BUFFER_SIZE);
96 
97 	if (stdinPosition == stdinBuffer.length) {
98 		stdinPosition = 0;
99 		stdinBuffer = buffer[0 .. len];
100 	} else if (len > 0) {
101 		stdinBuffer = buffer[0 .. len] ~ stdinBuffer;
102 	}
103 }
104 
105 /*******
106  * Returns raw ubyte from stdin
107  * Returns: ubyte on success, 0 in case of failure
108  */
109 nothrow ubyte popStdin() @safe
110 {
111 	if (stdinBuffer.length == stdinPosition) fillStdinBuffer();
112 
113 	if (stdinBuffer.length == stdinPosition) return 0;
114 	return stdinBuffer[stdinPosition++];
115 }
116 
117 /*******
118  * Puts back ubyte to STDIN buffer
119  * Returns: true on success, false in case of failure
120  *
121  * Params:
122  *  b = The ubyte to write
123  */
124 nothrow bool pushStdin(immutable ubyte b) @safe
125 {
126 	if (stdinPosition == 0) {
127 		stdinBuffer = b ~ stdinBuffer;
128 	} else {
129 		stdinBuffer[--stdinPosition] = b;
130 	}
131 
132 	return true;
133 }
134 
135 /*******
136  * Puts back string to STDIN buffer
137  * Returns: true on success, false in case of failure
138  *
139  * Params:
140  *  str = The string to write
141  */
142 nothrow bool pushStdin(string str) @safe
143 {
144 	foreach_reverse(immutable ubyte b; str) if (!pushStdin(b)) return false;
145 	return true;
146 }
147 
148 /*******
149  * Flushes STDIN buffer
150  */
151 void flushStdin() @trusted
152 {
153 	import consoleur.core.termparam: setTermparam, Term;
154 
155 	immutable tparam = setTermparam(Term.quiet|Term.raw|Term.async, 0);
156 	while(true) {
157 		stdinBuffer.length = 0;
158 		stdinPosition = 0;
159 		fillStdinBuffer();
160 		if (stdinBuffer.length == 0) break;
161 	}
162 }
163 
164 
165 /*******
166  * Reads escape sequence from STDIN
167  * Returns: string, containing escape command without Control Sequence Introducer
168  */
169 string readEscapeSequence() @safe
170 {
171 	ubyte b;
172 	ubyte b1;
173 
174 	if (!popStdin(b)) return "";
175 
176 	if (b != 0x1b && b != 0xc2) {
177 		pushStdin(b);
178 		return "";
179 	}
180 
181 	if (!popStdin(b1)){
182 		pushStdin(b);
183 		return "";
184 	}
185 
186 	if ( (b == 0x1b && b1 != 0x5b) || (b == 0xc2 && b1 != 0x9b)){
187 		pushStdin(b1);
188 		pushStdin(b);
189 		return "";
190 	}
191 
192 	string csi;
193 	while (popStdin(b)) {
194 		csi~=b;
195 		if (b >= 0x40 && b <= 0x7e && b != 0x5b) break;
196 	}
197 
198 	return csi;
199 }
200 
201 
202 shared static this()
203 {
204 	import core.sys.posix.fcntl: open, O_RDONLY;
205 	import core.sys.posix.unistd: isatty;
206 
207 	if (!isatty(STDIN_FILENO)) {
208 		STDIN_FILENO = open("/dev/tty", O_RDONLY);
209 	}
210 }
211 
212 }