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, either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 // linux tty utilities
19 module iv
.rawtty
/*is aliced*/;
22 import core
.sys
.posix
.termios
: termios
;
23 import iv
.strex
: strEquCI
;
26 public import iv
.termrgb
;
29 // ////////////////////////////////////////////////////////////////////////// //
30 alias TtyRgb2Color
= TtyRGB
;
31 alias ttyRgb2Color
= ttyRGB
;
34 // ////////////////////////////////////////////////////////////////////////// //
35 private __gshared termios origMode
;
36 private __gshared
bool doRestoreOrig
= false;
37 private shared bool inRawMode
= false;
39 private class XLock
{}
42 // ////////////////////////////////////////////////////////////////////////// //
45 Bad
= -1, /// some error occured
46 Normal
, /// normal ('cooked') mode
51 __gshared
bool xtermMetaSendsEscape
= true; /// you should add `XTerm*metaSendsEscape: true` to "~/.Xdefaults"
52 private __gshared
bool ttyIsFuckedFlag
= false;
55 // ////////////////////////////////////////////////////////////////////////// //
56 /// is TTY fucked with utfuck?
57 @property bool ttyIsUtfucked () nothrow @trusted @nogc { pragma(inline
, true); return ttyIsFuckedFlag
; }
60 // ////////////////////////////////////////////////////////////////////////// //
62 @property int ttyWidth () nothrow @trusted @nogc {
63 if (!ttyIsRedirected
) {
64 import core
.sys
.posix
.sys
.ioctl
: ioctl
, winsize
, TIOCGWINSZ
;
66 if (ioctl(1, TIOCGWINSZ
, &sz
) != -1) return sz
.ws_col
;
73 @property int ttyHeight () nothrow @trusted @nogc {
74 if (!ttyIsRedirected
) {
75 import core
.sys
.posix
.sys
.ioctl
: ioctl
, winsize
, TIOCGWINSZ
;
77 if (ioctl(1, TIOCGWINSZ
, &sz
) != -1) return sz
.ws_row
;
84 // ////////////////////////////////////////////////////////////////////////// //
85 void ttyRawWrite (const(char)[] str...) nothrow @trusted @nogc {
86 import core
.sys
.posix
.unistd
: write
;
87 if (str.length
) write(1, str.ptr
, str.length
);
91 void ttyRawWriteInt(T
) (T n
) nothrow @trusted @nogc if (__traits(isIntegral
, T
) && !is(T
== char) && !is(T
== wchar) && !is(T
== dchar) && !is(T
== bool) && !is(T
== enum)) {
92 import core
.stdc
.stdio
: snprintf
;
93 import core
.sys
.posix
.unistd
: write
;
95 static if (__traits(isUnsigned
, T
)) {
96 static if (T
.sizeof
> 4) {
97 auto len
= snprintf(buf
.ptr
, buf
.length
, "%llu", n
);
99 auto len
= snprintf(buf
.ptr
, buf
.length
, "%u", cast(uint)n
);
102 static if (T
.sizeof
> 4) {
103 auto len
= snprintf(buf
.ptr
, buf
.length
, "%lld", n
);
105 auto len
= snprintf(buf
.ptr
, buf
.length
, "%d", cast(int)n
);
108 if (len
> 0) write(1, buf
.ptr
, len
);
112 void ttyBeep () nothrow @trusted @nogc {
113 import core
.sys
.posix
.unistd
: write
;
115 write(1, str.ptr
, str.length
);
119 void ttyEnableBracketedPaste () nothrow @trusted @nogc {
120 import core
.sys
.posix
.unistd
: write
;
121 enum str = "\x1b[?2004h";
122 write(1, str.ptr
, str.length
);
126 void ttyDisableBracketedPaste () nothrow @trusted @nogc {
127 import core
.sys
.posix
.unistd
: write
;
128 enum str = "\x1b[?2004l";
129 write(1, str.ptr
, str.length
);
133 void ttyEnableFocusReports () nothrow @trusted @nogc {
134 import core
.sys
.posix
.unistd
: write
;
135 enum str = "\x1b[?1004h";
136 write(1, str.ptr
, str.length
);
140 void ttyDisableFocusReports () nothrow @trusted @nogc {
141 import core
.sys
.posix
.unistd
: write
;
142 enum str = "\x1b[?1004l";
143 write(1, str.ptr
, str.length
);
147 void ttyEnableMouseReports () nothrow @trusted @nogc {
148 import core
.sys
.posix
.unistd
: write
;
149 enum str = "\x1b[?1000h\x1b[?1006h\x1b[?1002h";
150 write(1, str.ptr
, str.length
);
154 void ttyDisableMouseReports () nothrow @trusted @nogc {
155 import core
.sys
.posix
.unistd
: write
;
156 enum str = "\x1b[?1002l\x1b[?1006l\x1b[?1000l";
157 write(1, str.ptr
, str.length
);
161 // ////////////////////////////////////////////////////////////////////////// //
162 /// get current TTY mode
163 TTYMode
ttyGetMode () nothrow @trusted @nogc {
165 return (atomicLoad(inRawMode
) ? TTYMode
.Raw
: TTYMode
.Normal
);
169 /// Restore terminal mode we had at program startup
170 void ttyRestoreOrigMode () {
172 import core
.sys
.posix
.termios
: tcflush
, tcsetattr
;
173 import core
.sys
.posix
.termios
: TCIOFLUSH
, TCSAFLUSH
;
174 import core
.sys
.posix
.unistd
: STDIN_FILENO
;
175 //tcflush(STDIN_FILENO, TCIOFLUSH);
176 if (doRestoreOrig
) tcsetattr(STDIN_FILENO
, TCSAFLUSH
, &origMode
);
177 atomicStore(inRawMode
, false);
181 /// returns previous mode or Bad
182 TTYMode
ttySetNormal () @trusted @nogc {
184 if (!doRestoreOrig
) return TTYMode
.Bad
;
185 synchronized(XLock
.classinfo
) {
186 if (atomicLoad(inRawMode
)) {
187 import core
.sys
.posix
.termios
: tcflush
, tcsetattr
;
188 import core
.sys
.posix
.termios
: TCIOFLUSH
, TCSAFLUSH
;
189 import core
.sys
.posix
.unistd
: STDIN_FILENO
;
190 //tcflush(STDIN_FILENO, TCIOFLUSH);
191 if (tcsetattr(STDIN_FILENO
, TCSAFLUSH
, &origMode
) < 0) return TTYMode
.Bad
;
192 atomicStore(inRawMode
, false);
195 return TTYMode
.Normal
;
200 /// returns previous mode or Bad
201 TTYMode
ttySetRaw (bool waitkey
=true) @trusted @nogc {
203 if (ttyIsRedirected ||
!doRestoreOrig
) return TTYMode
.Bad
;
204 synchronized(XLock
.classinfo
) {
205 if (!atomicLoad(inRawMode
)) {
206 //import core.sys.posix.termios : tcflush, tcsetattr;
207 //import core.sys.posix.termios : TCIOFLUSH, TCSAFLUSH;
208 //import core.sys.posix.termios : BRKINT, CS8, ECHO, ICANON, IEXTEN, INPCK, ISIG, ISTRIP, IXOFF, IGNCR, INLCR, ONLCR, OPOST, VMIN, VTIME;
209 import core
.sys
.posix
.termios
;
210 import core
.sys
.posix
.unistd
: STDIN_FILENO
;
211 import core
.stdc
.string
: memset
;
212 enum IUCLC
= 512; //0001000
213 //termios raw = origMode; // modify the original mode
215 memset(&raw
, 0, raw
.sizeof
);
216 //tcflush(STDIN_FILENO, TCIOFLUSH);
217 raw
.c_iflag
= IGNBRK
;
218 // output modes: disable post processing
219 raw
.c_oflag
= OPOST|ONLCR
;
220 // control modes: set 8 bit chars
221 raw
.c_cflag
= CS8|CLOCAL
;
222 // control chars: set return condition: min number of bytes and timer; we want read to return every single byte, without timeout
223 raw
.c_cc
[VMIN
] = (waitkey ?
1 : 0); // wait/poll mode
224 raw
.c_cc
[VTIME
] = 0; // no timer
225 // put terminal in raw mode after flushing
226 if (tcsetattr(STDIN_FILENO
, TCSAFLUSH
, &raw
) < 0) return TTYMode
.Bad
;
228 import core
.sys
.posix
.unistd
: write
;
229 // G0 is ASCII, G1 is graphics
230 enum setupStr
= "\x1b(B\x1b)0\x0f";
231 write(1, setupStr
.ptr
, setupStr
.length
);
233 atomicStore(inRawMode
, true);
234 return TTYMode
.Normal
;
241 /// change TTY mode if possible
242 /// returns previous mode or Bad
243 TTYMode
ttySetMode (TTYMode mode
) @trusted @nogc {
244 // check what we can without locking
245 if (mode
== TTYMode
.Bad
) return TTYMode
.Bad
;
246 if (ttyIsRedirected
) return (mode
== TTYMode
.Normal ? TTYMode
.Normal
: TTYMode
.Bad
);
247 synchronized(XLock
.classinfo
) return (mode
== TTYMode
.Normal ?
ttySetNormal() : ttySetRaw());
251 // ////////////////////////////////////////////////////////////////////////// //
252 /// set wait/poll mode
253 bool ttySetWaitKey (bool doWait
) @trusted @nogc {
255 if (ttyIsRedirected
) return false;
256 synchronized(XLock
.classinfo
) {
257 if (atomicLoad(inRawMode
)) {
258 import core
.sys
.posix
.termios
: tcflush
, tcgetattr
, tcsetattr
;
259 import core
.sys
.posix
.termios
: TCIOFLUSH
, TCSAFLUSH
;
260 import core
.sys
.posix
.termios
: VMIN
;
261 import core
.sys
.posix
.unistd
: STDIN_FILENO
;
263 //tcflush(STDIN_FILENO, TCIOFLUSH);
264 if (tcgetattr(STDIN_FILENO
, &raw
) != 0) return false; //redirected = false;
265 raw
.c_cc
[VMIN
] = (doWait ?
1 : 0); // wait/poll mode
266 if (tcsetattr(STDIN_FILENO
, TCSAFLUSH
, &raw
) < 0) return false;
274 // ////////////////////////////////////////////////////////////////////////// //
279 * toMSec = timeout in milliseconds; <0: infinite; 0: don't wait; default is -1
282 * true if key was pressed, false if no key was pressed in the given time
284 bool ttyWaitKey (int toMSec
=-1) @trusted @nogc {
286 if (!ttyIsRedirected
&& atomicLoad(inRawMode
)) {
287 import core
.sys
.posix
.sys
.select
: fd_set
, select
, timeval
, FD_ISSET
, FD_SET
, FD_ZERO
;
288 import core
.sys
.posix
.unistd
: STDIN_FILENO
;
292 FD_SET(STDIN_FILENO
, &fds
); //STDIN_FILENO is 0
297 tv
.tv_sec
= cast(int)(toMSec
/1000);
298 tv
.tv_usec
= (toMSec
%1000)*1000;
300 select(STDIN_FILENO
+1, &fds
, null, null, (toMSec
< 0 ?
null : &tv
));
301 return FD_ISSET(STDIN_FILENO
, &fds
);
308 * Check if key was pressed. Don't block.
311 * true if key was pressed, false if no key was pressed
313 bool ttyIsKeyHit () @trusted @nogc { return ttyWaitKey(0); }
317 * Read one byte from stdin.
320 * toMSec = timeout in milliseconds; <0: infinite; 0: don't wait; default is -1
323 * read byte or -1 on error/timeout
325 int ttyReadKeyByte (int toMSec
=-1) @trusted @nogc {
327 if (!ttyIsRedirected
&& atomicLoad(inRawMode
)) {
328 import core
.sys
.posix
.unistd
: read
, STDIN_FILENO
;
331 synchronized(XLock
.classinfo
) if (ttyWaitKey(toMSec
) && read(STDIN_FILENO
, &res
, 1) == 1) return res
;
333 if (read(STDIN_FILENO
, &res
, 1) == 1) {
334 //{ import core.stdc.stdio; if (res > 32 && res != 127) printf("[%c]\n", res); else printf("{%d}\n", res); }
343 // ////////////////////////////////////////////////////////////////////////// //
345 public align(1) struct TtyEvent
{
346 align(1): // make it tightly packed
350 Error
, /// error reading key
351 Unknown
, /// can't interpret escape code
355 // for bracketed paste mode
359 ModChar
, /// char with some modifier
377 Pad5
, /// xterm can return this
392 //A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z,
393 //N0, N1, N2, N3, N4, N5, N6, N7, N8, N9,
408 MMotion
, /// mouse motion without buttons, not read from tty by now, but can be useful for other backends
410 // synthesized events, used in tui
424 enum ModFlag
: ubyte {
441 Key key
; /// key type/sym
442 ubyte mods
; /// set of ModFlag
443 dchar ch
= 0; /// can be 0 for special key
444 short x
, y
; /// for mouse reports
445 //void* udata; /// arbitrary user data
447 @property const pure nothrow @safe @nogc {
449 MButton
button () { pragma(inline
, true); return
450 key
== Key
.MLeftDown || key
== Key
.MLeftUp || key
== Key
.MLeftMotion || key
== Key
.MLeftClick || key
== Key
.MLeftDouble ? MButton
.Left
:
451 key
== Key
.MRightDown || key
== Key
.MRightUp || key
== Key
.MRightMotion || key
== Key
.MRightClick || key
== Key
.MRightDouble ? MButton
.Right
:
452 key
== Key
.MMiddleDown || key
== Key
.MMiddleUp || key
== Key
.MMiddleMotion || key
== Key
.MMiddleClick || key
== Key
.MMiddleDouble ? MButton
.Middle
:
453 key
== Key
.MWheelUp ? MButton
.WheelUp
:
454 key
== Key
.MWheelDown ? MButton
.WheelDown
:
457 bool mouse () { pragma(inline
, true); return (key
>= Key
.MLeftDown
&& key
<= Key
.MRightDouble
); } ///
458 bool mpress () { pragma(inline
, true); return (key
== Key
.MLeftDown || key
== Key
.MRightDown || key
== Key
.MMiddleDown
); } ///
459 bool mrelease () { pragma(inline
, true); return (key
== Key
.MLeftUp || key
== Key
.MRightUp || key
== Key
.MMiddleUp
); } ///
460 bool mclick () { pragma(inline
, true); return (key
== Key
.MLeftClick || key
== Key
.MRightClick || key
== Key
.MMiddleClick
); } ///
461 bool mdouble () { pragma(inline
, true); return (key
== Key
.MLeftDouble || key
== Key
.MRightDouble || key
== Key
.MMiddleDouble
); } ///
462 bool mmotion () { pragma(inline
, true); return (key
== Key
.MLeftMotion || key
== Key
.MRightMotion || key
== Key
.MMiddleMotion || key
== Key
.MMotion
); } ///
463 bool mwheel () { pragma(inline
, true); return (key
== Key
.MWheelUp || key
== Key
.MWheelDown
); } ///
464 bool focusin () { pragma(inline
, true); return (key
== Key
.FocusIn
); } ///
465 bool focusout () { pragma(inline
, true); return (key
== Key
.FocusOut
); } ///
466 bool ctrl () { pragma(inline
, true); return ((mods
&ModFlag
.Ctrl
) != 0); } ///
467 bool alt () { pragma(inline
, true); return ((mods
&ModFlag
.Alt
) != 0); } ///
468 bool shift () { pragma(inline
, true); return ((mods
&ModFlag
.Shift
) != 0); } ///
471 @property pure nothrow @safe @nogc {
472 void ctrl (bool v
) { pragma(inline
, true); if (v
) mods |
= ModFlag
.Ctrl
; else mods
&= ~(ModFlag
.Ctrl
); } ///
473 void alt (bool v
) { pragma(inline
, true); if (v
) mods |
= ModFlag
.Alt
; else mods
&= ~(ModFlag
.Alt
); } ///
474 void shift (bool v
) { pragma(inline
, true); if (v
) mods |
= ModFlag
.Shift
; else mods
&= ~(ModFlag
.Shift
); } ///
477 this (const(char)[] s
) pure nothrow @safe @nogc {
478 if (TtyEvent
.parse(this, s
).length
!= 0) {
485 bool opEquals (in TtyEvent k
) const pure nothrow @safe @nogc {
486 pragma(inline
, true);
489 (key
== Key
.Char ?
(ch
== k
.ch
) :
490 key
== Key
.ModChar ?
(mods
== k
.mods
&& ch
== k
.ch
) :
491 //key >= Key.MLeftDown && key <= MWheelDown ? true :
492 key
> Key
.ModChar ?
(mods
== k
.mods
) :
498 bool opEquals (const(char)[] s
) const pure nothrow @safe @nogc {
500 if (TtyEvent
.parse(k
, s
).length
!= 0) return false;
505 string
toString () const nothrow {
506 char[128] buf
= void;
507 return toCharBuf(buf
[]).idup
;
511 char[] toCharBuf (char[] dest
) const nothrow @trusted @nogc {
512 static immutable string hexD
= "0123456789abcdef";
514 void put (const(char)[] s
...) nothrow @nogc {
515 foreach (char ch
; s
) {
516 if (dpos
>= dest
.length
) break;
517 dest
.ptr
[dpos
++] = ch
;
520 void putMods () nothrow @nogc {
523 if (shift
) put("S-");
525 if (key
== Key
.ModChar
) putMods();
526 if (key
== Key
.Char || key
== Key
.ModChar
) {
527 if (ch
< ' ' || ch
== 127) {
529 put(hexD
.ptr
[(ch
>>4)&0x0f]);
530 put(hexD
.ptr
[ch
&0x0f]);
531 } else if (ch
== ' ') {
533 } else if (ch
< 256) {
535 } else if (ch
<= 0xffff) {
537 put(hexD
.ptr
[(ch
>>12)&0x0f]);
538 put(hexD
.ptr
[(ch
>>8)&0x0f]);
539 put(hexD
.ptr
[(ch
>>4)&0x0f]);
540 put(hexD
.ptr
[ch
&0x0f]);
544 return dest
[0..dpos
];
546 if (key
== Key
.None
) { put("none"); return dest
[0..dpos
]; }
547 if (key
== Key
.Error
) { put("error"); return dest
[0..dpos
]; }
548 if (key
== Key
.Unknown
) { put("unknown"); return dest
[0..dpos
]; }
549 foreach (string kn
; __traits(allMembers
, TtyEvent
.Key
)) {
550 if (__traits(getMember
, TtyEvent
.Key
, kn
) == key
) {
553 return dest
[0..dpos
];
557 return dest
[0..dpos
];
560 /** parse key name. get first word, return rest of the string (with trailing spaces removed)
562 * "C-<home>" (emacs-like syntax is recognized)
566 * mods: C(trl), M(eta:alt), S(hift)
568 * `key` will be `TtyEvent.Key.Error` on error, `TtyEvent.Key.None` on empty string
570 static T
parse(T
) (out TtyEvent key
, T s
) pure nothrow @trusted @nogc if (is(T
: const(char)[])) {
571 static if (is(T
== typeof(null))) {
574 while (s
.length
&& s
.ptr
[0] <= ' ') s
= s
[1..$];
575 if (s
.length
== 0) return s
; // no more
576 // get space-delimited word
577 int pos
= 1; // 0 is always non-space here
578 while (pos
< s
.length
&& s
.ptr
[pos
] > ' ') { if (++pos
>= 1024) return s
; }
579 auto olds
= s
; // return this in case of error
580 const(char)[] str = s
[0..pos
]; // string to parse
581 // `s` will be our result; remove leading spaces for convenience
582 while (pos
< s
.length
&& s
.ptr
[pos
] <= ' ') ++pos
;
585 while (str.length
> 0) {
586 if (str.length
>= 2 && str.ptr
[1] == '-') {
588 switch (str.ptr
[0]) {
589 case 'C': case 'c': key
.ctrl
= true; break;
590 case 'M': case 'm': key
.alt
= true; break;
591 case 'S': case 's': key
.shift
= true; break;
592 default: goto error
; // unknown modifier
597 if (str.length
> 1 && str.ptr
[0] == '^') {
601 } else if (str.length
> 2 && str.ptr
[0] == '<' && str[$-1] == '>') {
604 if (str.length
== 0) goto error
; // just in case
605 if (str.strEquCI("space")) str = " ";
606 if (str.length
== 1) {
609 if (key
.ctrl || key
.alt
) {
610 key
.key
= TtyEvent
.Key
.ModChar
;
611 if (key
.ch
>= 'a' && key
.ch
<= 'z') key
.ch
-= 32; // toupper
613 key
.key
= TtyEvent
.Key
.Char
;
615 if (key
.ch
>= 'a' && key
.ch
<= 'z') key
.ch
-= 32; // toupper
616 else switch (key
.ch
) {
617 case '`': key
.ch
= '~'; break;
618 case '1': key
.ch
= '!'; break;
619 case '2': key
.ch
= '@'; break;
620 case '3': key
.ch
= '#'; break;
621 case '4': key
.ch
= '$'; break;
622 case '5': key
.ch
= '%'; break;
623 case '6': key
.ch
= '^'; break;
624 case '7': key
.ch
= '&'; break;
625 case '8': key
.ch
= '*'; break;
626 case '9': key
.ch
= '('; break;
627 case '0': key
.ch
= ')'; break;
628 case '-': key
.ch
= '_'; break;
629 case '=': key
.ch
= '+'; break;
630 case '[': key
.ch
= '{'; break;
631 case ']': key
.ch
= '}'; break;
632 case ';': key
.ch
= ':'; break;
633 case '\'': key
.ch
= '"'; break;
634 case '\\': key
.ch
= '|'; break;
635 case ',': key
.ch
= '<'; break;
636 case '.': key
.ch
= '>'; break;
637 case '/': key
.ch
= '?'; break;
645 if (str.strEquCI("return")) str = "enter";
646 if (str.strEquCI("esc")) str = "escape";
647 if (str.strEquCI("bs")) str = "backspace";
648 if (str.strEquCI("PasteStart") ||
str.strEquCI("Paste-Start")) {
649 key
.key
= TtyEvent
.Key
.PasteStart
;
652 } else if (str.strEquCI("PasteEnd") ||
str.strEquCI("Paste-End")) {
653 key
.key
= TtyEvent
.Key
.PasteEnd
;
658 foreach (string kn
; __traits(allMembers
, TtyEvent
.Key
)) {
659 if (!found
&& str.strEquCI(kn
)) {
661 key
.key
= __traits(getMember
, TtyEvent
.Key
, kn
);
665 if (!found || key
.key
< TtyEvent
.Key
.Up
) goto error
;
668 if (key
.key
== TtyEvent
.Key
.Enter
) key
.ch
= 13;
669 else if (key
.key
== TtyEvent
.Key
.Tab
) key
.ch
= 9;
670 else if (key
.key
== TtyEvent
.Key
.Escape
) key
.ch
= 27;
671 else if (key
.key
== TtyEvent
.Key
.Backspace
) key
.ch
= 8;
678 key
.key
= TtyEvent
.Key
.Error
;
686 * Read key from stdin.
688 * WARNING! no utf-8 support yet!
691 * toMSec = timeout in milliseconds; <0: infinite; 0: don't wait; default is -1
692 * toEscMSec = timeout in milliseconds for escape sequences
695 * null on error or keyname
697 TtyEvent
ttyReadKey (int toMSec
=-1, int toEscMSec
=-1/*300*/) @trusted @nogc {
700 void skipCSI () @nogc {
701 key
.key
= TtyEvent
.Key
.Unknown
;
703 auto ch
= ttyReadKeyByte(toEscMSec
);
704 if (ch
< 0 || ch
== 27) { key
.key
= TtyEvent
.Key
.Escape
; key
.ch
= 27; break; }
705 if (ch
!= ';' && (ch
< '0' || ch
> '9')) break;
709 void badCSI () @nogc {
711 key
.key
= TtyEvent
.Key
.Unknown
;
714 bool xtermMods (uint mci
) @nogc {
716 case 2: key
.shift
= true; return true;
717 case 3: key
.alt
= true; return true;
718 case 4: key
.alt
= true; key
.shift
= true; return true;
719 case 5: key
.ctrl
= true; return true;
720 case 6: key
.ctrl
= true; key
.shift
= true; return true;
721 case 7: key
.alt
= true; key
.ctrl
= true; return true;
722 case 8: key
.alt
= true; key
.ctrl
= true; key
.shift
= true; return true;
728 void xtermSpecial (char ch
) @nogc {
730 case 'A': key
.key
= TtyEvent
.Key
.Up
; break;
731 case 'B': key
.key
= TtyEvent
.Key
.Down
; break;
732 case 'C': key
.key
= TtyEvent
.Key
.Right
; break;
733 case 'D': key
.key
= TtyEvent
.Key
.Left
; break;
734 case 'E': key
.key
= TtyEvent
.Key
.Pad5
; break;
735 case 'H': key
.key
= TtyEvent
.Key
.Home
; break;
736 case 'F': key
.key
= TtyEvent
.Key
.End
; break;
737 case 'P': key
.key
= TtyEvent
.Key
.F1
; break;
738 case 'Q': key
.key
= TtyEvent
.Key
.F2
; break;
739 case 'R': key
.key
= TtyEvent
.Key
.F3
; break;
740 case 'S': key
.key
= TtyEvent
.Key
.F4
; break;
741 case 'Z': key
.key
= TtyEvent
.Key
.Tab
; key
.ch
= 9; if (!key
.shift
&& !key
.alt
&& !key
.ctrl
) key
.shift
= true; break;
742 default: badCSI(); break;
746 void linconSpecial (char ch
) @nogc {
748 case 'A': key
.key
= TtyEvent
.Key
.F1
; break;
749 case 'B': key
.key
= TtyEvent
.Key
.F2
; break;
750 case 'C': key
.key
= TtyEvent
.Key
.F3
; break;
751 case 'D': key
.key
= TtyEvent
.Key
.F4
; break;
752 default: badCSI(); break;
756 void csiSpecial (uint n
) @nogc {
758 case 1: key
.key
= TtyEvent
.Key
.Home
; return; // xterm
759 case 2: key
.key
= TtyEvent
.Key
.Insert
; return;
760 case 3: key
.key
= TtyEvent
.Key
.Delete
; return;
761 case 4: key
.key
= TtyEvent
.Key
.End
; return;
762 case 5: key
.key
= TtyEvent
.Key
.PageUp
; return;
763 case 6: key
.key
= TtyEvent
.Key
.PageDown
; return;
764 case 7: key
.key
= TtyEvent
.Key
.Home
; return; // rxvt
765 case 8: key
.key
= TtyEvent
.Key
.End
; return;
766 case 1+10: key
.key
= TtyEvent
.Key
.F1
; return;
767 case 2+10: key
.key
= TtyEvent
.Key
.F2
; return;
768 case 3+10: key
.key
= TtyEvent
.Key
.F3
; return;
769 case 4+10: key
.key
= TtyEvent
.Key
.F4
; return;
770 case 5+10: key
.key
= TtyEvent
.Key
.F5
; return;
771 case 6+11: key
.key
= TtyEvent
.Key
.F6
; return;
772 case 7+11: key
.key
= TtyEvent
.Key
.F7
; return;
773 case 8+11: key
.key
= TtyEvent
.Key
.F8
; return;
774 case 9+11: key
.key
= TtyEvent
.Key
.F9
; return;
775 case 10+11: key
.key
= TtyEvent
.Key
.F10
; return;
776 case 11+12: key
.key
= TtyEvent
.Key
.F11
; return;
777 case 12+12: key
.key
= TtyEvent
.Key
.F12
; return;
778 default: badCSI(); break;
782 // {\e}[<0;58;32M (button;x;y;[Mm])
783 void parseMouse () @nogc {
788 auto ch
= ttyReadKeyByte(toEscMSec
);
789 if (ch
< 0 || ch
== 27) { key
.key
= TtyEvent
.Key
.Escape
; key
.ch
= 27; return; }
792 } else if (ch
>= '0' && ch
<= '9') {
793 if (nc
< nn
.length
) nn
.ptr
[nc
] = nn
.ptr
[nc
]*10+ch
-'0';
795 if (ch
== 'M') press
= true;
796 else if (ch
== 'm') press
= false;
797 else { key
.key
= TtyEvent
.Key
.Unknown
; return; }
801 if (nn
[1] > 0) --nn
[1];
802 if (nn
[2] > 0) --nn
[2];
803 if (nn
[1] < 0) nn
[1] = 1;
804 if (nn
[1] > short.max
) nn
[1] = short.max
;
805 if (nn
[2] < 0) nn
[2] = 1;
806 if (nn
[2] > short.max
) nn
[2] = short.max
;
808 case 0: key
.key
= (press ? TtyEvent
.Key
.MLeftDown
: TtyEvent
.Key
.MLeftUp
); break;
809 case 1: key
.key
= (press ? TtyEvent
.Key
.MMiddleDown
: TtyEvent
.Key
.MMiddleUp
); break;
810 case 2: key
.key
= (press ? TtyEvent
.Key
.MRightDown
: TtyEvent
.Key
.MRightUp
); break;
811 case 32: if (!press
) { key
.key
= TtyEvent
.Key
.Unknown
; return; } key
.key
= TtyEvent
.Key
.MLeftMotion
; break;
812 case 33: if (!press
) { key
.key
= TtyEvent
.Key
.Unknown
; return; } key
.key
= TtyEvent
.Key
.MMiddleMotion
; break;
813 case 34: if (!press
) { key
.key
= TtyEvent
.Key
.Unknown
; return; } key
.key
= TtyEvent
.Key
.MRightMotion
; break;
814 case 64: if (!press
) { key
.key
= TtyEvent
.Key
.Unknown
; return; } key
.key
= TtyEvent
.Key
.MWheelUp
; break;
815 case 65: if (!press
) { key
.key
= TtyEvent
.Key
.Unknown
; return; } key
.key
= TtyEvent
.Key
.MWheelDown
; break;
816 default: key
.key
= TtyEvent
.Key
.Unknown
; return;
818 key
.x
= cast(short)nn
[1];
819 key
.y
= cast(short)nn
[2];
822 int ch
= ttyReadKeyByte(toMSec
);
823 if (ch
< 0) { key
.key
= TtyEvent
.Key
.Error
; return key
; } // error
824 if (ch
== 0) { key
.key
= TtyEvent
.Key
.ModChar
; key
.ctrl
= true; key
.ch
= ' '; return key
; }
825 if (ch
== 8 || ch
== 127) { key
.key
= TtyEvent
.Key
.Backspace
; key
.ch
= 8; return key
; }
826 if (ch
== 9) { key
.key
= TtyEvent
.Key
.Tab
; key
.ch
= 9; return key
; }
827 //if (ch == 10) { key.key = TtyEvent.Key.Enter; key.ch = 13; return key; }
828 if (ch
== 13) { key
.key
= TtyEvent
.Key
.Enter
; key
.ch
= 13; return key
; }
830 key
.key
= TtyEvent
.Key
.Unknown
;
834 ch
= ttyReadKeyByte(toEscMSec
);
835 if (ch
< 0 || ch
== 27) { key
.key
= TtyEvent
.Key
.Escape
; key
.ch
= 27; return key
; }
837 if (termType
!= TermType
.rxvt
&& ch
== 'O') {
838 ch
= ttyReadKeyByte(toEscMSec
);
839 if (ch
< 0 || ch
== 27) { key
.key
= TtyEvent
.Key
.Escape
; key
.ch
= 27; return key
; }
840 if (ch
>= 'A' && ch
<= 'Z') xtermSpecial(cast(char)ch
);
841 if (ch
>= 'a' && ch
<= 'z') { key
.shift
= true; xtermSpecial(cast(char)(ch
-32)); }
848 bool wasDigit
= false;
849 bool firstChar
= true;
850 bool linuxCon
= false;
853 ch
= ttyReadKeyByte(toEscMSec
);
854 if (firstChar
&& ch
== '<') { parseMouse(); return key
; }
855 if (firstChar
&& ch
== 'I') { key
.key
= TtyEvent
.Key
.FocusIn
; return key
; }
856 if (firstChar
&& ch
== 'O') { key
.key
= TtyEvent
.Key
.FocusOut
; return key
; }
857 if (firstChar
&& ch
== '[') { linuxCon
= true; firstChar
= false; continue; }
859 if (ch
< 0 || ch
== 27) { key
.key
= TtyEvent
.Key
.Escape
; key
.ch
= 27; return key
; }
862 if (nc
> nn
.length
) { skipCSI(); return key
; }
863 } else if (ch
>= '0' && ch
<= '9') {
864 if (nc
>= nn
.length
) { skipCSI(); return key
; }
865 nn
.ptr
[nc
] = nn
.ptr
[nc
]*10+ch
-'0';
872 debug(rawtty_show_csi
) { import core
.stdc
.stdio
: printf
; printf("nc=%u", nc
); foreach (uint idx
; 0..nc
) printf("; n%u=%u", idx
, nn
.ptr
[idx
]); printf("; ch=%c\n", ch
); }
875 if (linuxCon
) linconSpecial(cast(char)ch
);
876 else if (ch
>= 'A' && ch
<= 'Z') xtermSpecial(cast(char)ch
);
877 } else if (nc
== 1) {
878 if (ch
== '~' && nn
.ptr
[0] == 200) { key
.key
= TtyEvent
.Key
.PasteStart
; return key
; }
879 if (ch
== '~' && nn
.ptr
[0] == 201) { key
.key
= TtyEvent
.Key
.PasteEnd
; return key
; }
883 case 23: key
.shift
= true; key
.key
= TtyEvent
.Key
.F1
; return key
;
884 case 24: key
.shift
= true; key
.key
= TtyEvent
.Key
.F2
; return key
;
885 case 25: key
.shift
= true; key
.key
= TtyEvent
.Key
.F3
; return key
;
886 case 26: key
.shift
= true; key
.key
= TtyEvent
.Key
.F4
; return key
;
887 case 28: key
.shift
= true; key
.key
= TtyEvent
.Key
.F5
; return key
;
888 case 29: key
.shift
= true; key
.key
= TtyEvent
.Key
.F6
; return key
;
889 case 31: key
.shift
= true; key
.key
= TtyEvent
.Key
.F7
; return key
;
890 case 32: key
.shift
= true; key
.key
= TtyEvent
.Key
.F8
; return key
;
891 case 33: key
.shift
= true; key
.key
= TtyEvent
.Key
.F9
; return key
;
892 case 34: key
.shift
= true; key
.key
= TtyEvent
.Key
.F10
; return key
;
896 case '^': key
.ctrl
= true; break;
897 case '$': key
.shift
= true; break;
898 case '@': key
.ctrl
= true; key
.shift
= true; break;
899 case 'A': .. case 'Z': xtermMods(nn
.ptr
[0]); xtermSpecial(cast(char)ch
); return key
;
900 default: badCSI(); return key
;
902 csiSpecial(nn
.ptr
[0]);
903 } else if (nc
== 2 && xtermMods(nn
.ptr
[1])) {
904 if (nn
.ptr
[0] == 1 && ch
>= 'A' && ch
<= 'Z') {
905 xtermSpecial(cast(char)ch
);
906 } else if (ch
== '~') {
907 csiSpecial(nn
.ptr
[0]);
915 key
.key
= TtyEvent
.Key
.Tab
;
920 if (ch
>= 1 && ch
<= 26) {
921 key
.key
= TtyEvent
.Key
.ModChar
;
924 key
.ch
= cast(dchar)(ch
+64);
925 if (key
.ch
== 'H') { key
.key
= TtyEvent
.Key
.Backspace
; key
.ctrl
= false; key
.ch
= 8; }
926 else if (key
.ch
== 'J') { key
.key
= TtyEvent
.Key
.Enter
; key
.ctrl
= false; key
.ch
= 13; }
929 if (/*(ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9') || ch == '_' || ch == '`'*/true) {
931 key
.key
= TtyEvent
.Key
.ModChar
;
932 key
.shift
= (ch
>= 'A' && ch
<= 'Z'); // ignore capslock
933 if (ch
>= 'a' && ch
<= 'z') ch
-= 32;
934 key
.ch
= cast(dchar)ch
;
941 key
.key
= TtyEvent
.Key
.Tab
;
943 } else if (ch
< 32) {
945 key
.key
= TtyEvent
.Key
.ModChar
;
947 key
.ch
= cast(dchar)(ch
+64);
948 if (key
.ch
== 'H') { key
.key
= TtyEvent
.Key
.Backspace
; key
.ctrl
= false; key
.ch
= 8; }
949 else if (key
.ch
== 'J') { key
.key
= TtyEvent
.Key
.Enter
; key
.ctrl
= false; key
.ch
= 13; }
951 key
.key
= TtyEvent
.Key
.Char
;
952 key
.ch
= cast(dchar)(ch
);
953 if (ttyIsFuckedFlag
&& ch
>= 0x80) {
956 auto dch
= udc
.decode(cast(ubyte)ch
);
957 if (dch
<= dchar.max
) break;
959 ch
= ttyReadKeyByte(toEscMSec
);
963 key
.ch
= uni2koi(udc
.currCodePoint
);
966 // xterm does alt+letter with 7th bit set
967 if (!xtermMetaSendsEscape
&& termType
== TermType
.xterm
&& ch
>= 0x80 && ch
<= 0xff) {
969 if ((ch
>= 'A' && ch
<= 'Z') ||
(ch
>= 'a' && ch
<= 'z') ||
(ch
>= '0' && ch
<= '9') || ch
== '_') {
971 key
.key
= TtyEvent
.Key
.ModChar
;
972 key
.shift
= (ch
>= 'A' && ch
<= 'Z'); // ignore capslock
973 if (ch
>= 'a' && ch
<= 'z') ch
-= 32;
974 key
.ch
= cast(dchar)ch
;
984 // ////////////////////////////////////////////////////////////////////////// //
986 private extern(C
) void ttyExitRestore () {
988 if (atomicLoad(inRawMode
)) {
989 import core
.sys
.posix
.termios
: tcflush
, tcsetattr
;
990 import core
.sys
.posix
.termios
: TCIOFLUSH
, TCSAFLUSH
;
991 import core
.sys
.posix
.unistd
: STDIN_FILENO
;
992 //tcflush(STDIN_FILENO, TCIOFLUSH);
993 if (doRestoreOrig
) tcsetattr(STDIN_FILENO
, TCSAFLUSH
, &origMode
);
997 shared static this () {
999 import core
.stdc
.stdlib
: atexit
;
1000 atexit(&ttyExitRestore
);
1003 import core
.sys
.posix
.unistd
: isatty
, STDIN_FILENO
, STDOUT_FILENO
;
1004 import core
.sys
.posix
.termios
: tcgetattr
;
1005 import core
.sys
.posix
.termios
: termios
;
1006 doRestoreOrig
= false;
1007 if (isatty(STDIN_FILENO
) && isatty(STDOUT_FILENO
)) {
1008 doRestoreOrig
= (tcgetattr(STDIN_FILENO
, &origMode
) == 0);
1014 shared static ~this () {
1019 // ////////////////////////////////////////////////////////////////////////// //
1020 shared static this () {
1021 import core
.sys
.posix
.stdlib
: getenv
;
1023 ttyIsFuckedFlag
= false;
1025 auto lang
= getenv("LANG");
1026 if (lang
is null) return;
1028 static char tolower (char ch
) pure nothrow @safe @nogc { return (ch
>= 'A' && ch
<= 'Z' ?
cast(char)(ch
-'A'+'a') : ch
); }
1031 if (tolower(lang
[0]) == 'u' && tolower(lang
[1]) == 't' && tolower(lang
[2]) == 'f') { ttyIsFuckedFlag
= true; return; }