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 }