1 /* Invisible Vector Library
2 * coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
3 * Understanding is not required. Only obedience.
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, version 3 of the License ONLY.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 // linux tty utilities
18 module iv
.rawtty
/*is aliced*/;
20 import core
.sys
.posix
.termios
: termios
;
21 public import std
.typecons
: Flag
, Yes
, No
;
24 private __gshared termios origMode
;
25 private __gshared
bool inRawMode
= false;
26 private __gshared
bool redirected
= true; // can be used without synchronization
28 private class XLock
{}
29 private shared XLock xlock
;
34 BAD
= -1, /// some error occured
35 NORMAL
, /// normal ('cooked') mode
46 __gshared TermType termType
= TermType
.other
;
49 /// is TTY stdin or stdout redirected?
50 @property bool ttyIsRedirected () @trusted nothrow @nogc {
55 /// get current TTY mode
56 TTYMode
ttyGetMode () @trusted nothrow @nogc {
57 return (inRawMode ? TTYMode
.RAW
: TTYMode
.NORMAL
);
61 /// returns previous mode or BAD
62 TTYMode
ttySetNormal () @trusted @nogc {
65 import core
.sys
.posix
.termios
: tcflush
, tcsetattr
;
66 import core
.sys
.posix
.termios
: TCIOFLUSH
, TCSAFLUSH
;
67 import core
.sys
.posix
.unistd
: STDIN_FILENO
;
68 //tcflush(STDIN_FILENO, TCIOFLUSH);
69 if (tcsetattr(STDIN_FILENO
, TCSAFLUSH
, &origMode
) < 0) return TTYMode
.BAD
;
73 return TTYMode
.NORMAL
;
78 /// returns previous mode or BAD
79 TTYMode
ttySetRaw (Flag
!"waitkey" waitKey
=Yes
.waitkey
) @trusted @nogc {
80 if (redirected
) return TTYMode
.BAD
;
83 import core
.sys
.posix
.termios
: tcflush
, tcsetattr
;
84 import core
.sys
.posix
.termios
: TCIOFLUSH
, TCSAFLUSH
;
85 import core
.sys
.posix
.termios
: BRKINT
, CS8
, ECHO
, ICANON
, IEXTEN
, INPCK
, ISIG
, ISTRIP
, IXON
, ONLCR
, OPOST
, VMIN
, VTIME
;
86 import core
.sys
.posix
.unistd
: STDIN_FILENO
;
87 termios raw
= origMode
; // modify the original mode
88 //tcflush(STDIN_FILENO, TCIOFLUSH);
89 // input modes: no break, no CR to NL, no parity check, no strip char, no start/stop output control
90 //raw.c_iflag &= ~(BRKINT|ICRNL|INPCK|ISTRIP|IXON);
91 // input modes: no break, no parity check, no strip char, no start/stop output control
92 raw
.c_iflag
&= ~(BRKINT|INPCK|ISTRIP|IXON
);
93 // output modes: disable post processing
94 raw
.c_oflag
&= ~OPOST
;
96 raw
.c_oflag
= OPOST|ONLCR
;
97 // control modes: set 8 bit chars
99 // local modes: echoing off, canonical off, no extended functions, no signal chars (^Z,^C)
100 raw
.c_lflag
&= ~(ECHO|ICANON|IEXTEN|ISIG
);
101 // control chars: set return condition: min number of bytes and timer; we want read to return every single byte, without timeout
102 raw
.c_cc
[VMIN
] = (waitKey ?
1 : 0); // wait/poll mode
103 raw
.c_cc
[VTIME
] = 0; // no timer
104 // put terminal in raw mode after flushing
105 if (tcsetattr(STDIN_FILENO
, TCSAFLUSH
, &raw
) < 0) return TTYMode
.BAD
;
107 return TTYMode
.NORMAL
;
114 /// set wait/poll mode
115 bool ttySetWaitKey (bool doWait
) @trusted @nogc {
116 if (redirected
) return false;
117 synchronized(xlock
) {
119 import core
.sys
.posix
.termios
: tcflush
, tcgetattr
, tcsetattr
;
120 import core
.sys
.posix
.termios
: TCIOFLUSH
, TCSAFLUSH
;
121 import core
.sys
.posix
.termios
: VMIN
;
122 import core
.sys
.posix
.unistd
: STDIN_FILENO
;
124 //tcflush(STDIN_FILENO, TCIOFLUSH);
125 if (tcgetattr(STDIN_FILENO
, &raw
) == 0) redirected
= false;
126 raw
.c_cc
[VMIN
] = (doWait ?
1 : 0); // wait/poll mode
127 if (tcsetattr(STDIN_FILENO
, TCSAFLUSH
, &raw
) < 0) return false;
135 /// change TTY mode if possible
136 /// returns previous mode or BAD
137 TTYMode
ttySetMode (TTYMode mode
) @trusted @nogc {
138 // check what we can without locking
139 if (mode
== TTYMode
.BAD
) return TTYMode
.BAD
;
140 if (redirected
) return (mode
== TTYMode
.NORMAL ? TTYMode
.NORMAL
: TTYMode
.BAD
);
141 synchronized(xlock
) return (mode
== TTYMode
.NORMAL ?
ttySetNormal() : ttySetRaw());
146 @property int ttyWidth () @trusted nothrow @nogc {
148 import core
.sys
.posix
.sys
.ioctl
: ioctl
, winsize
, TIOCGWINSZ
;
150 if (ioctl(1, TIOCGWINSZ
, &sz
) != -1) return sz
.ws_col
;
156 /// return TTY height
157 @property int ttyHeight () @trusted nothrow @nogc {
159 import core
.sys
.posix
.sys
.ioctl
: ioctl
, winsize
, TIOCGWINSZ
;
161 if (ioctl(1, TIOCGWINSZ
, &sz
) != -1) return sz
.ws_row
;
172 * toMSec = timeout in milliseconds; <0: infinite; 0: don't wait; default is -1
175 * true if key was pressed, false if no key was pressed in the given time
177 bool ttyWaitKey (long toMSec
=-1) @trusted nothrow @nogc {
179 import core
.sys
.posix
.sys
.select
: fd_set
, select
, timeval
, FD_ISSET
, FD_SET
, FD_ZERO
;
180 import core
.sys
.posix
.unistd
: STDIN_FILENO
;
184 FD_SET(STDIN_FILENO
, &fds
); //STDIN_FILENO is 0
189 tv
.tv_sec
= cast(int)(toMSec
/1000);
190 tv
.tv_usec
= (toMSec
%1000)*1000;
192 select(STDIN_FILENO
+1, &fds
, null, null, (toMSec
< 0 ?
null : &tv
));
193 return FD_ISSET(STDIN_FILENO
, &fds
);
200 * Check if key was pressed. Don't block.
203 * true if key was pressed, false if no key was pressed
205 bool ttyIsKeyHit () @trusted nothrow @nogc {
206 return ttyWaitKey(0);
211 * Read one byte from stdin.
214 * toMSec = timeout in milliseconds; <0: infinite; 0: don't wait; default is -1
217 * read byte or -1 on error/timeout
219 int ttyReadKeyByte (long toMSec
=-1) @trusted @nogc {
221 import core
.sys
.posix
.unistd
: read
, STDIN_FILENO
;
224 synchronized(xlock
) if (ttyWaitKey(toMSec
) && read(STDIN_FILENO
, &res
, 1) == 1) return res
;
226 if (read(STDIN_FILENO
, &res
, 1) == 1) return res
;
233 /// escape sequences --> key names
234 __gshared string
[string
] ttyKeyTrans
;
238 * Read key from stdin.
240 * WARNING! no utf-8 support yet!
243 * toMSec = timeout in milliseconds; <0: infinite; 0: don't wait; default is -1
244 * toEscMSec = timeout in milliseconds for escape sequences
247 * null on error or keyname
249 string
ttyReadKey (long toMSec
=-1, long toEscMSec
=300) @trusted {
250 import std
.string
: format
;
251 int ch
= ttyReadKeyByte(toMSec
);
252 if (ch
< 0) return null; // error
253 if (ch
== 8 || ch
== 127) return "backspace";
254 if (ch
== 9) return "tab";
255 if (ch
== 10) return "return";
258 ch
= ttyReadKeyByte(toEscMSec
);
259 if (ch
< 0 || ch
== 27) return "escape";
261 if (termType
!= TermType
.rxvt
&& ch
== 'O') {
262 ch
= ttyReadKeyByte(toEscMSec
);
263 if (ch
< 0) return "escape";
264 if (ch
>= 'A' && ch
<= 'Z') kk
= "O%c".format(cast(dchar)ch
);
265 } else if (ch
== '[') {
268 ch
= ttyReadKeyByte(toEscMSec
);
269 if (ch
< 0 || ch
== 27) return "escape";
271 if (ch
!= ';' && (ch
< '0' || ch
> '9')) break;
273 } else if (ch
== 9) {
275 } else if (ch
>= 1 && ch
<= 26) {
276 return "alt+^%c".format(cast(dchar)(ch
+64));
277 } else if ((ch
>= 'A' && ch
<= 'Z') ||
(ch
>= 'a' && ch
<= 'z') ||
(ch
>= '0' && ch
<= '9')) {
278 return "alt+%c".format(cast(dchar)ch
);
281 auto kn
= kk
in ttyKeyTrans
;
282 return (kn ?
*kn
: "unknown");
286 if (ch
< 32) return "^%c".format(cast(dchar)(ch
+64)); // ^X
288 return "%c".format(cast(dchar)ch
);
292 private void initKeyTrans () @trusted {
293 import std
.string
: format
;
296 ttyKeyTrans
["[A"] = "up";
297 ttyKeyTrans
["[B"] = "down";
298 ttyKeyTrans
["[C"] = "right";
299 ttyKeyTrans
["[D"] = "left";
300 ttyKeyTrans
["[2~"] = "insert";
301 ttyKeyTrans
["[3~"] = "delete";
302 ttyKeyTrans
["[5~"] = "pageup";
303 ttyKeyTrans
["[6~"] = "pagedown";
304 ttyKeyTrans
["[7~"] = "home";
305 ttyKeyTrans
["[8~"] = "end";
306 ttyKeyTrans
["[1~"] = "home";
307 ttyKeyTrans
["[4~"] = "end";
309 ttyKeyTrans
["OA"] = "up";
310 ttyKeyTrans
["OB"] = "down";
311 ttyKeyTrans
["OC"] = "right";
312 ttyKeyTrans
["OD"] = "left";
313 ttyKeyTrans
["[H"] = "home";
314 ttyKeyTrans
["[F"] = "end";
315 // arrows and specials with modifiers
316 foreach (immutable i
, immutable c
; ["shift+", "alt+", "alt+shift+", "ctrl+", "ctrl+shift+", "alt+ctrl+", "alt+ctrl+shift+"]) {
317 string t
= "[1;%d".format(i
+2);
318 ttyKeyTrans
[t
~"A"] = c
~"up";
319 ttyKeyTrans
[t
~"B"] = c
~"down";
320 ttyKeyTrans
[t
~"C"] = c
~"right";
321 ttyKeyTrans
[t
~"D"] = c
~"left";
323 string t1
= ";%d~".format(i
+2);
324 ttyKeyTrans
["[2"~t1
] = c
~"insert";
325 ttyKeyTrans
["[3"~t1
] = c
~"delete";
326 // xterm, spec+f1..f4
327 ttyKeyTrans
[t
~"P"] = c
~"f1";
328 ttyKeyTrans
[t
~"Q"] = c
~"f2";
329 ttyKeyTrans
[t
~"R"] = c
~"f3";
330 ttyKeyTrans
[t
~"S"] = c
~"f4";
331 // xterm, spec+f5..f12
332 foreach (immutable idx
, immutable fn
; [15, 17, 18, 19, 20, 21, 23, 24]) {
333 string fs
= "[%d".format(fn
);
334 ttyKeyTrans
[fs
~t1
] = c
~format("f%d", idx
+5);
337 ttyKeyTrans
["[5"~t1
] = c
~"pageup";
338 ttyKeyTrans
["[6"~t1
] = c
~"pagedown";
339 ttyKeyTrans
[t
~"H"] = c
~"home";
340 ttyKeyTrans
[t
~"F"] = c
~"end";
342 ttyKeyTrans
["[2^"] = "ctrl+insert";
343 ttyKeyTrans
["[3^"] = "ctrl+delete";
344 ttyKeyTrans
["[5^"] = "ctrl+pageup";
345 ttyKeyTrans
["[6^"] = "ctrl+pagedown";
346 ttyKeyTrans
["[7^"] = "ctrl+home";
347 ttyKeyTrans
["[8^"] = "ctrl+end";
348 ttyKeyTrans
["[1^"] = "ctrl+home";
349 ttyKeyTrans
["[4^"] = "ctrl+end";
350 ttyKeyTrans
["[2$"] = "shift+insert";
351 ttyKeyTrans
["[3$"] = "shift+delete";
352 ttyKeyTrans
["[5$"] = "shift+pageup";
353 ttyKeyTrans
["[6$"] = "shift+pagedown";
354 ttyKeyTrans
["[7$"] = "shift+home";
355 ttyKeyTrans
["[8$"] = "shift+end";
356 ttyKeyTrans
["[1$"] = "shift+home";
357 ttyKeyTrans
["[4$"] = "shift+end";
359 ttyKeyTrans
["[E"] = "num5"; // xterm
361 foreach (immutable i
; 1..6) {
362 ttyKeyTrans
["[%d~".format(i
+10)] = "f%d".format(i
);
363 ttyKeyTrans
["[%d^".format(i
+10)] = "ctrl+f%d".format(i
);
364 ttyKeyTrans
["[%d@".format(i
+10)] = "ctrl+shift+f%d".format(i
);
366 foreach (immutable i
; 6..11) {
367 ttyKeyTrans
["[%d~".format(i
+11)] = "f%d".format(i
);
368 ttyKeyTrans
["[%d^".format(i
+11)] = "ctrl+f%d".format(i
);
369 ttyKeyTrans
["[%d@".format(i
+11)] = "ctrl+shift+f%d".format(i
);
371 foreach (immutable i
; 11..15) {
372 ttyKeyTrans
["[%d~".format(i
+12)] = "f%d".format(i
);
373 ttyKeyTrans
["[%d^".format(i
+12)] = "ctrl+f%d".format(i
);
374 ttyKeyTrans
["[%d@".format(i
+12)] = "ctrl+shift+f%d".format(i
);
376 foreach (immutable i
; 15..17) {
377 ttyKeyTrans
["[%d~".format(i
+13)] = "f%d".format(i
);
378 ttyKeyTrans
["[%d^".format(i
+13)] = "ctrl+f%d".format(i
);
379 ttyKeyTrans
["[%d@".format(i
+13)] = "ctrl+shift+f%d".format(i
);
381 foreach (immutable i
; 17..21) {
382 ttyKeyTrans
["[%d~".format(i
+14)] = "f%d".format(i
);
383 ttyKeyTrans
["[%d^".format(i
+14)] = "ctrl+f%d".format(i
);
384 ttyKeyTrans
["[%d@".format(i
+14)] = "ctrl+shift+f%d".format(i
);
388 ttyKeyTrans
["OP"] = "f1";
389 ttyKeyTrans
["OQ"] = "f2";
390 ttyKeyTrans
["OR"] = "f3";
391 ttyKeyTrans
["OS"] = "f4";
395 shared static this () {
397 import core
.stdc
.stdlib
: getenv
;
398 import core
.stdc
.string
: strcmp
;
399 auto tt
= getenv("TERM");
401 if (strcmp(tt
, "rxvt") == 0) termType
= TermType
.rxvt
;
402 else if (strcmp(tt
, "xterm") == 0) termType
= TermType
.xterm
;
405 import core
.sys
.posix
.unistd
: isatty
, STDIN_FILENO
, STDOUT_FILENO
;
406 import core
.sys
.posix
.termios
: tcgetattr
;
408 if (isatty(STDIN_FILENO
) && isatty(STDOUT_FILENO
)) {
409 if (tcgetattr(STDIN_FILENO
, &origMode
) == 0) redirected
= false;
415 shared static ~this () {