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*/;
21 import core
.sys
.posix
.termios
: termios
;
22 import iv
.strex
: strEquCI
;
25 public import iv
.termrgb
;
28 // ////////////////////////////////////////////////////////////////////////// //
29 alias TtyRgb2Color
= TtyRGB
;
30 alias ttyRgb2Color
= ttyRGB
;
33 // ////////////////////////////////////////////////////////////////////////// //
34 private __gshared termios origMode
;
35 private __gshared
bool doRestoreOrig
= false;
36 private shared bool inRawMode
= false;
38 private class XLock
{}
41 // ////////////////////////////////////////////////////////////////////////// //
44 Bad
= -1, /// some error occured
45 Normal
, /// normal ('cooked') mode
50 __gshared
bool xtermMetaSendsEscape
= true; /// you should add `XTerm*metaSendsEscape: true` to "~/.Xdefaults"
51 private __gshared
bool ttyIsFuckedFlag
= false;
54 // ////////////////////////////////////////////////////////////////////////// //
55 /// is TTY fucked with utfuck?
56 @property bool ttyIsUtfucked () nothrow @trusted @nogc { pragma(inline
, true); return ttyIsFuckedFlag
; }
59 // ////////////////////////////////////////////////////////////////////////// //
61 @property int ttyWidth () nothrow @trusted @nogc {
62 if (!ttyIsRedirected
) {
63 import core
.sys
.posix
.sys
.ioctl
: ioctl
, winsize
, TIOCGWINSZ
;
65 if (ioctl(1, TIOCGWINSZ
, &sz
) != -1) return sz
.ws_col
;
72 @property int ttyHeight () nothrow @trusted @nogc {
73 if (!ttyIsRedirected
) {
74 import core
.sys
.posix
.sys
.ioctl
: ioctl
, winsize
, TIOCGWINSZ
;
76 if (ioctl(1, TIOCGWINSZ
, &sz
) != -1) return sz
.ws_row
;
83 // ////////////////////////////////////////////////////////////////////////// //
84 void ttyRawWrite (const(char)[] str...) nothrow @trusted @nogc {
85 import core
.sys
.posix
.unistd
: write
;
86 if (str.length
) write(1, str.ptr
, str.length
);
90 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)) {
91 import core
.stdc
.stdio
: snprintf
;
92 import core
.sys
.posix
.unistd
: write
;
94 static if (__traits(isUnsigned
, T
)) {
95 static if (T
.sizeof
> 4) {
96 auto len
= snprintf(buf
.ptr
, buf
.length
, "%llu", n
);
98 auto len
= snprintf(buf
.ptr
, buf
.length
, "%u", cast(uint)n
);
101 static if (T
.sizeof
> 4) {
102 auto len
= snprintf(buf
.ptr
, buf
.length
, "%lld", n
);
104 auto len
= snprintf(buf
.ptr
, buf
.length
, "%d", cast(int)n
);
107 if (len
> 0) write(1, buf
.ptr
, len
);
111 void ttyBeep () nothrow @trusted @nogc {
112 import core
.sys
.posix
.unistd
: write
;
114 write(1, str.ptr
, str.length
);
118 void ttyEnableBracketedPaste () nothrow @trusted @nogc {
119 import core
.sys
.posix
.unistd
: write
;
120 enum str = "\x1b[?2004h";
121 write(1, str.ptr
, str.length
);
125 void ttyDisableBracketedPaste () nothrow @trusted @nogc {
126 import core
.sys
.posix
.unistd
: write
;
127 enum str = "\x1b[?2004l";
128 write(1, str.ptr
, str.length
);
132 void ttyEnableFocusReports () nothrow @trusted @nogc {
133 import core
.sys
.posix
.unistd
: write
;
134 enum str = "\x1b[?1004h";
135 write(1, str.ptr
, str.length
);
139 void ttyDisableFocusReports () nothrow @trusted @nogc {
140 import core
.sys
.posix
.unistd
: write
;
141 enum str = "\x1b[?1004l";
142 write(1, str.ptr
, str.length
);
146 void ttyEnableMouseReports () nothrow @trusted @nogc {
147 import core
.sys
.posix
.unistd
: write
;
148 enum str = "\x1b[?1000h\x1b[?1006h\x1b[?1002h";
149 write(1, str.ptr
, str.length
);
153 void ttyDisableMouseReports () nothrow @trusted @nogc {
154 import core
.sys
.posix
.unistd
: write
;
155 enum str = "\x1b[?1002l\x1b[?1006l\x1b[?1000l";
156 write(1, str.ptr
, str.length
);
160 // ////////////////////////////////////////////////////////////////////////// //
161 /// get current TTY mode
162 TTYMode
ttyGetMode () nothrow @trusted @nogc {
164 return (atomicLoad(inRawMode
) ? TTYMode
.Raw
: TTYMode
.Normal
);
168 /// Restore terminal mode we had at program startup
169 void ttyRestoreOrigMode () {
171 import core
.sys
.posix
.termios
: tcflush
, tcsetattr
;
172 import core
.sys
.posix
.termios
: TCIOFLUSH
, TCSAFLUSH
;
173 import core
.sys
.posix
.unistd
: STDIN_FILENO
;
174 //tcflush(STDIN_FILENO, TCIOFLUSH);
175 if (doRestoreOrig
) tcsetattr(STDIN_FILENO
, TCSAFLUSH
, &origMode
);
176 atomicStore(inRawMode
, false);
180 /// returns previous mode or Bad
181 TTYMode
ttySetNormal () @trusted @nogc {
183 if (!doRestoreOrig
) return TTYMode
.Bad
;
184 synchronized(XLock
.classinfo
) {
185 if (atomicLoad(inRawMode
)) {
186 import core
.sys
.posix
.termios
: tcflush
, tcsetattr
;
187 import core
.sys
.posix
.termios
: TCIOFLUSH
, TCSAFLUSH
;
188 import core
.sys
.posix
.unistd
: STDIN_FILENO
;
189 //tcflush(STDIN_FILENO, TCIOFLUSH);
190 if (tcsetattr(STDIN_FILENO
, TCSAFLUSH
, &origMode
) < 0) return TTYMode
.Bad
;
191 atomicStore(inRawMode
, false);
194 return TTYMode
.Normal
;
199 /// returns previous mode or Bad
200 TTYMode
ttySetRaw (bool waitkey
=true) @trusted @nogc {
202 if (ttyIsRedirected ||
!doRestoreOrig
) return TTYMode
.Bad
;
203 synchronized(XLock
.classinfo
) {
204 if (!atomicLoad(inRawMode
)) {
205 //import core.sys.posix.termios : tcflush, tcsetattr;
206 //import core.sys.posix.termios : TCIOFLUSH, TCSAFLUSH;
207 //import core.sys.posix.termios : BRKINT, CS8, ECHO, ICANON, IEXTEN, INPCK, ISIG, ISTRIP, IXOFF, IGNCR, INLCR, ONLCR, OPOST, VMIN, VTIME;
208 import core
.sys
.posix
.termios
;
209 import core
.sys
.posix
.unistd
: STDIN_FILENO
;
210 import core
.stdc
.string
: memset
;
211 enum IUCLC
= 512; //0001000
212 //termios raw = origMode; // modify the original mode
214 memset(&raw
, 0, raw
.sizeof
);
215 //tcflush(STDIN_FILENO, TCIOFLUSH);
216 raw
.c_iflag
= IGNBRK
;
217 // output modes: disable post processing
218 raw
.c_oflag
= OPOST|ONLCR
;
219 // control modes: set 8 bit chars
220 raw
.c_cflag
= CS8|CLOCAL
;
221 // control chars: set return condition: min number of bytes and timer; we want read to return every single byte, without timeout
222 raw
.c_cc
[VMIN
] = (waitkey ?
1 : 0); // wait/poll mode
223 raw
.c_cc
[VTIME
] = 0; // no timer
224 // put terminal in raw mode after flushing
225 if (tcsetattr(STDIN_FILENO
, TCSAFLUSH
, &raw
) < 0) return TTYMode
.Bad
;
227 import core
.sys
.posix
.unistd
: write
;
228 // G0 is ASCII, G1 is graphics
229 enum setupStr
= "\x1b(B\x1b)0\x0f";
230 write(1, setupStr
.ptr
, setupStr
.length
);
232 atomicStore(inRawMode
, true);
233 return TTYMode
.Normal
;
240 /// change TTY mode if possible
241 /// returns previous mode or Bad
242 TTYMode
ttySetMode (TTYMode mode
) @trusted @nogc {
243 // check what we can without locking
244 if (mode
== TTYMode
.Bad
) return TTYMode
.Bad
;
245 if (ttyIsRedirected
) return (mode
== TTYMode
.Normal ? TTYMode
.Normal
: TTYMode
.Bad
);
246 synchronized(XLock
.classinfo
) return (mode
== TTYMode
.Normal ?
ttySetNormal() : ttySetRaw());
250 // ////////////////////////////////////////////////////////////////////////// //
251 /// set wait/poll mode
252 bool ttySetWaitKey (bool doWait
) @trusted @nogc {
254 if (ttyIsRedirected
) return false;
255 synchronized(XLock
.classinfo
) {
256 if (atomicLoad(inRawMode
)) {
257 import core
.sys
.posix
.termios
: tcflush
, tcgetattr
, tcsetattr
;
258 import core
.sys
.posix
.termios
: TCIOFLUSH
, TCSAFLUSH
;
259 import core
.sys
.posix
.termios
: VMIN
;
260 import core
.sys
.posix
.unistd
: STDIN_FILENO
;
262 //tcflush(STDIN_FILENO, TCIOFLUSH);
263 if (tcgetattr(STDIN_FILENO
, &raw
) != 0) return false; //redirected = false;
264 raw
.c_cc
[VMIN
] = (doWait ?
1 : 0); // wait/poll mode
265 if (tcsetattr(STDIN_FILENO
, TCSAFLUSH
, &raw
) < 0) return false;
273 // ////////////////////////////////////////////////////////////////////////// //
278 * toMSec = timeout in milliseconds; <0: infinite; 0: don't wait; default is -1
281 * true if key was pressed, false if no key was pressed in the given time
283 bool ttyWaitKey (int toMSec
=-1) @trusted @nogc {
285 if (!ttyIsRedirected
&& atomicLoad(inRawMode
)) {
286 import core
.sys
.posix
.sys
.select
: fd_set
, select
, timeval
, FD_ISSET
, FD_SET
, FD_ZERO
;
287 import core
.sys
.posix
.unistd
: STDIN_FILENO
;
291 FD_SET(STDIN_FILENO
, &fds
); //STDIN_FILENO is 0
296 tv
.tv_sec
= cast(int)(toMSec
/1000);
297 tv
.tv_usec
= (toMSec
%1000)*1000;
299 select(STDIN_FILENO
+1, &fds
, null, null, (toMSec
< 0 ?
null : &tv
));
300 return FD_ISSET(STDIN_FILENO
, &fds
);
307 * Check if key was pressed. Don't block.
310 * true if key was pressed, false if no key was pressed
312 bool ttyIsKeyHit () @trusted @nogc { return ttyWaitKey(0); }
316 * Read one byte from stdin.
319 * toMSec = timeout in milliseconds; <0: infinite; 0: don't wait; default is -1
322 * read byte or -1 on error/timeout
324 int ttyReadKeyByte (int toMSec
=-1) @trusted @nogc {
326 if (!ttyIsRedirected
&& atomicLoad(inRawMode
)) {
327 import core
.sys
.posix
.unistd
: read
, STDIN_FILENO
;
330 synchronized(XLock
.classinfo
) if (ttyWaitKey(toMSec
) && read(STDIN_FILENO
, &res
, 1) == 1) return res
;
332 if (read(STDIN_FILENO
, &res
, 1) == 1) {
333 //{ import core.stdc.stdio; if (res > 32 && res != 127) printf("[%c]\n", res); else printf("{%d}\n", res); }
342 // ////////////////////////////////////////////////////////////////////////// //
344 public align(1) struct TtyEvent
{
345 align(1): // make it tightly packed
349 Error
, /// error reading key
350 Unknown
, /// can't interpret escape code
354 // for bracketed paste mode
358 ModChar
, /// char with some modifier
376 Pad5
, /// xterm can return this
391 //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,
392 //N0, N1, N2, N3, N4, N5, N6, N7, N8, N9,
407 MMotion
, /// mouse motion without buttons, not read from tty by now, but can be useful for other backends
409 // synthesized events, used in tui
423 enum ModFlag
: ubyte {
440 Key key
; /// key type/sym
441 ubyte mods
; /// set of ModFlag
442 dchar ch
= 0; /// can be 0 for special key
443 short x
, y
; /// for mouse reports
444 //void* udata; /// arbitrary user data
446 @property const pure nothrow @safe @nogc {
448 MButton
button () { pragma(inline
, true); return
449 key
== Key
.MLeftDown || key
== Key
.MLeftUp || key
== Key
.MLeftMotion || key
== Key
.MLeftClick || key
== Key
.MLeftDouble ? MButton
.Left
:
450 key
== Key
.MRightDown || key
== Key
.MRightUp || key
== Key
.MRightMotion || key
== Key
.MRightClick || key
== Key
.MRightDouble ? MButton
.Right
:
451 key
== Key
.MMiddleDown || key
== Key
.MMiddleUp || key
== Key
.MMiddleMotion || key
== Key
.MMiddleClick || key
== Key
.MMiddleDouble ? MButton
.Middle
:
452 key
== Key
.MWheelUp ? MButton
.WheelUp
:
453 key
== Key
.MWheelDown ? MButton
.WheelDown
:
456 bool mouse () { pragma(inline
, true); return (key
>= Key
.MLeftDown
&& key
<= Key
.MRightDouble
); } ///
457 bool mpress () { pragma(inline
, true); return (key
== Key
.MLeftDown || key
== Key
.MRightDown || key
== Key
.MMiddleDown
); } ///
458 bool mrelease () { pragma(inline
, true); return (key
== Key
.MLeftUp || key
== Key
.MRightUp || key
== Key
.MMiddleUp
); } ///
459 bool mclick () { pragma(inline
, true); return (key
== Key
.MLeftClick || key
== Key
.MRightClick || key
== Key
.MMiddleClick
); } ///
460 bool mdouble () { pragma(inline
, true); return (key
== Key
.MLeftDouble || key
== Key
.MRightDouble || key
== Key
.MMiddleDouble
); } ///
461 bool mmotion () { pragma(inline
, true); return (key
== Key
.MLeftMotion || key
== Key
.MRightMotion || key
== Key
.MMiddleMotion || key
== Key
.MMotion
); } ///
462 bool mwheel () { pragma(inline
, true); return (key
== Key
.MWheelUp || key
== Key
.MWheelDown
); } ///
463 bool focusin () { pragma(inline
, true); return (key
== Key
.FocusIn
); } ///
464 bool focusout () { pragma(inline
, true); return (key
== Key
.FocusOut
); } ///
465 bool ctrl () { pragma(inline
, true); return ((mods
&ModFlag
.Ctrl
) != 0); } ///
466 bool alt () { pragma(inline
, true); return ((mods
&ModFlag
.Alt
) != 0); } ///
467 bool shift () { pragma(inline
, true); return ((mods
&ModFlag
.Shift
) != 0); } ///
470 @property pure nothrow @safe @nogc {
471 void ctrl (bool v
) { pragma(inline
, true); if (v
) mods |
= ModFlag
.Ctrl
; else mods
&= ~(ModFlag
.Ctrl
); } ///
472 void alt (bool v
) { pragma(inline
, true); if (v
) mods |
= ModFlag
.Alt
; else mods
&= ~(ModFlag
.Alt
); } ///
473 void shift (bool v
) { pragma(inline
, true); if (v
) mods |
= ModFlag
.Shift
; else mods
&= ~(ModFlag
.Shift
); } ///
476 this (const(char)[] s
) pure nothrow @safe @nogc {
477 if (TtyEvent
.parse(this, s
).length
!= 0) {
484 bool opEquals (in TtyEvent k
) const pure nothrow @safe @nogc {
485 pragma(inline
, true);
488 (key
== Key
.Char ?
(ch
== k
.ch
) :
489 key
== Key
.ModChar ?
(mods
== k
.mods
&& ch
== k
.ch
) :
490 //key >= Key.MLeftDown && key <= MWheelDown ? true :
491 key
> Key
.ModChar ?
(mods
== k
.mods
) :
497 bool opEquals (const(char)[] s
) const pure nothrow @safe @nogc {
499 if (TtyEvent
.parse(k
, s
).length
!= 0) return false;
504 string
toString () const nothrow {
505 char[128] buf
= void;
506 return toCharBuf(buf
[]).idup
;
510 char[] toCharBuf (char[] dest
) const nothrow @trusted @nogc {
511 static immutable string hexD
= "0123456789abcdef";
513 void put (const(char)[] s
...) nothrow @nogc {
514 foreach (char ch
; s
) {
515 if (dpos
>= dest
.length
) break;
516 dest
.ptr
[dpos
++] = ch
;
519 void putMods () nothrow @nogc {
522 if (shift
) put("S-");
524 if (key
== Key
.ModChar
) putMods();
525 if (key
== Key
.Char || key
== Key
.ModChar
) {
526 if (ch
< ' ' || ch
== 127) {
528 put(hexD
.ptr
[(ch
>>4)&0x0f]);
529 put(hexD
.ptr
[ch
&0x0f]);
530 } else if (ch
== ' ') {
532 } else if (ch
< 256) {
534 } else if (ch
<= 0xffff) {
536 put(hexD
.ptr
[(ch
>>12)&0x0f]);
537 put(hexD
.ptr
[(ch
>>8)&0x0f]);
538 put(hexD
.ptr
[(ch
>>4)&0x0f]);
539 put(hexD
.ptr
[ch
&0x0f]);
543 return dest
[0..dpos
];
545 if (key
== Key
.None
) { put("none"); return dest
[0..dpos
]; }
546 if (key
== Key
.Error
) { put("error"); return dest
[0..dpos
]; }
547 if (key
== Key
.Unknown
) { put("unknown"); return dest
[0..dpos
]; }
548 foreach (string kn
; __traits(allMembers
, TtyEvent
.Key
)) {
549 if (__traits(getMember
, TtyEvent
.Key
, kn
) == key
) {
552 return dest
[0..dpos
];
556 return dest
[0..dpos
];
559 /** parse key name. get first word, return rest of the string (with trailing spaces removed)
561 * "C-<home>" (emacs-like syntax is recognized)
565 * mods: C(trl), M(eta:alt), S(hift)
567 * `key` will be `TtyEvent.Key.Error` on error, `TtyEvent.Key.None` on empty string
569 static T
parse(T
) (out TtyEvent key
, T s
) pure nothrow @trusted @nogc if (is(T
: const(char)[])) {
570 static if (is(T
== typeof(null))) {
573 while (s
.length
&& s
.ptr
[0] <= ' ') s
= s
[1..$];
574 if (s
.length
== 0) return s
; // no more
575 // get space-delimited word
576 int pos
= 1; // 0 is always non-space here
577 while (pos
< s
.length
&& s
.ptr
[pos
] > ' ') { if (++pos
>= 1024) return s
; }
578 auto olds
= s
; // return this in case of error
579 const(char)[] str = s
[0..pos
]; // string to parse
580 // `s` will be our result; remove leading spaces for convenience
581 while (pos
< s
.length
&& s
.ptr
[pos
] <= ' ') ++pos
;
584 while (str.length
> 0) {
585 if (str.length
>= 2 && str.ptr
[1] == '-') {
587 switch (str.ptr
[0]) {
588 case 'C': case 'c': key
.ctrl
= true; break;
589 case 'M': case 'm': key
.alt
= true; break;
590 case 'S': case 's': key
.shift
= true; break;
591 default: goto error
; // unknown modifier
596 if (str.length
> 1 && str.ptr
[0] == '^') {
600 } else if (str.length
> 2 && str.ptr
[0] == '<' && str[$-1] == '>') {
603 if (str.length
== 0) goto error
; // just in case
604 if (str.strEquCI("space")) str = " ";
605 if (str.length
== 1) {
608 if (key
.ctrl || key
.alt
) {
609 key
.key
= TtyEvent
.Key
.ModChar
;
610 if (key
.ch
>= 'a' && key
.ch
<= 'z') key
.ch
-= 32; // toupper
612 key
.key
= TtyEvent
.Key
.Char
;
614 if (key
.ch
>= 'a' && key
.ch
<= 'z') key
.ch
-= 32; // toupper
615 else switch (key
.ch
) {
616 case '`': key
.ch
= '~'; break;
617 case '1': key
.ch
= '!'; break;
618 case '2': key
.ch
= '@'; break;
619 case '3': key
.ch
= '#'; break;
620 case '4': key
.ch
= '$'; break;
621 case '5': key
.ch
= '%'; break;
622 case '6': key
.ch
= '^'; break;
623 case '7': key
.ch
= '&'; break;
624 case '8': key
.ch
= '*'; break;
625 case '9': key
.ch
= '('; break;
626 case '0': key
.ch
= ')'; break;
627 case '-': 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;
644 if (str.strEquCI("return")) str = "enter";
645 if (str.strEquCI("esc")) str = "escape";
646 if (str.strEquCI("bs")) str = "backspace";
647 if (str.strEquCI("PasteStart") ||
str.strEquCI("Paste-Start")) {
648 key
.key
= TtyEvent
.Key
.PasteStart
;
651 } else if (str.strEquCI("PasteEnd") ||
str.strEquCI("Paste-End")) {
652 key
.key
= TtyEvent
.Key
.PasteEnd
;
657 foreach (string kn
; __traits(allMembers
, TtyEvent
.Key
)) {
658 if (!found
&& str.strEquCI(kn
)) {
660 key
.key
= __traits(getMember
, TtyEvent
.Key
, kn
);
664 if (!found || key
.key
< TtyEvent
.Key
.Up
) goto error
;
667 if (key
.key
== TtyEvent
.Key
.Enter
) key
.ch
= 13;
668 else if (key
.key
== TtyEvent
.Key
.Tab
) key
.ch
= 9;
669 else if (key
.key
== TtyEvent
.Key
.Escape
) key
.ch
= 27;
670 else if (key
.key
== TtyEvent
.Key
.Backspace
) key
.ch
= 8;
677 key
.key
= TtyEvent
.Key
.Error
;
685 * Read key from stdin.
687 * WARNING! no utf-8 support yet!
690 * toMSec = timeout in milliseconds; <0: infinite; 0: don't wait; default is -1
691 * toEscMSec = timeout in milliseconds for escape sequences
694 * null on error or keyname
696 TtyEvent
ttyReadKey (int toMSec
=-1, int toEscMSec
=-1/*300*/) @trusted @nogc {
699 void skipCSI () @nogc {
700 key
.key
= TtyEvent
.Key
.Unknown
;
702 auto ch
= ttyReadKeyByte(toEscMSec
);
703 if (ch
< 0 || ch
== 27) { key
.key
= TtyEvent
.Key
.Escape
; key
.ch
= 27; break; }
704 if (ch
!= ';' && (ch
< '0' || ch
> '9')) break;
708 void badCSI () @nogc {
710 key
.key
= TtyEvent
.Key
.Unknown
;
713 bool xtermMods (uint mci
) @nogc {
715 case 2: key
.shift
= true; return true;
716 case 3: key
.alt
= true; return true;
717 case 4: key
.alt
= true; key
.shift
= true; return true;
718 case 5: key
.ctrl
= true; return true;
719 case 6: key
.ctrl
= true; key
.shift
= true; return true;
720 case 7: key
.alt
= true; key
.ctrl
= true; return true;
721 case 8: key
.alt
= true; key
.ctrl
= true; key
.shift
= true; return true;
727 void xtermSpecial (char ch
) @nogc {
729 case 'A': key
.key
= TtyEvent
.Key
.Up
; break;
730 case 'B': key
.key
= TtyEvent
.Key
.Down
; break;
731 case 'C': key
.key
= TtyEvent
.Key
.Right
; break;
732 case 'D': key
.key
= TtyEvent
.Key
.Left
; break;
733 case 'E': key
.key
= TtyEvent
.Key
.Pad5
; break;
734 case 'H': key
.key
= TtyEvent
.Key
.Home
; break;
735 case 'F': key
.key
= TtyEvent
.Key
.End
; break;
736 case 'P': key
.key
= TtyEvent
.Key
.F1
; break;
737 case 'Q': key
.key
= TtyEvent
.Key
.F2
; break;
738 case 'R': key
.key
= TtyEvent
.Key
.F3
; break;
739 case 'S': key
.key
= TtyEvent
.Key
.F4
; break;
740 case 'Z': key
.key
= TtyEvent
.Key
.Tab
; key
.ch
= 9; if (!key
.shift
&& !key
.alt
&& !key
.ctrl
) key
.shift
= true; break;
741 default: badCSI(); break;
745 void linconSpecial (char ch
) @nogc {
747 case 'A': key
.key
= TtyEvent
.Key
.F1
; break;
748 case 'B': key
.key
= TtyEvent
.Key
.F2
; break;
749 case 'C': key
.key
= TtyEvent
.Key
.F3
; break;
750 case 'D': key
.key
= TtyEvent
.Key
.F4
; break;
751 default: badCSI(); break;
755 void csiSpecial (uint n
) @nogc {
757 case 1: key
.key
= TtyEvent
.Key
.Home
; return; // xterm
758 case 2: key
.key
= TtyEvent
.Key
.Insert
; return;
759 case 3: key
.key
= TtyEvent
.Key
.Delete
; return;
760 case 4: key
.key
= TtyEvent
.Key
.End
; return;
761 case 5: key
.key
= TtyEvent
.Key
.PageUp
; return;
762 case 6: key
.key
= TtyEvent
.Key
.PageDown
; return;
763 case 7: key
.key
= TtyEvent
.Key
.Home
; return; // rxvt
764 case 8: key
.key
= TtyEvent
.Key
.End
; return;
765 case 1+10: key
.key
= TtyEvent
.Key
.F1
; return;
766 case 2+10: key
.key
= TtyEvent
.Key
.F2
; return;
767 case 3+10: key
.key
= TtyEvent
.Key
.F3
; return;
768 case 4+10: key
.key
= TtyEvent
.Key
.F4
; return;
769 case 5+10: key
.key
= TtyEvent
.Key
.F5
; return;
770 case 6+11: key
.key
= TtyEvent
.Key
.F6
; return;
771 case 7+11: key
.key
= TtyEvent
.Key
.F7
; return;
772 case 8+11: key
.key
= TtyEvent
.Key
.F8
; return;
773 case 9+11: key
.key
= TtyEvent
.Key
.F9
; return;
774 case 10+11: key
.key
= TtyEvent
.Key
.F10
; return;
775 case 11+12: key
.key
= TtyEvent
.Key
.F11
; return;
776 case 12+12: key
.key
= TtyEvent
.Key
.F12
; return;
777 default: badCSI(); break;
781 // {\e}[<0;58;32M (button;x;y;[Mm])
782 void parseMouse () @nogc {
787 auto ch
= ttyReadKeyByte(toEscMSec
);
788 if (ch
< 0 || ch
== 27) { key
.key
= TtyEvent
.Key
.Escape
; key
.ch
= 27; return; }
791 } else if (ch
>= '0' && ch
<= '9') {
792 if (nc
< nn
.length
) nn
.ptr
[nc
] = nn
.ptr
[nc
]*10+ch
-'0';
794 if (ch
== 'M') press
= true;
795 else if (ch
== 'm') press
= false;
796 else { key
.key
= TtyEvent
.Key
.Unknown
; return; }
800 if (nn
[1] > 0) --nn
[1];
801 if (nn
[2] > 0) --nn
[2];
802 if (nn
[1] < 0) nn
[1] = 1;
803 if (nn
[1] > short.max
) nn
[1] = short.max
;
804 if (nn
[2] < 0) nn
[2] = 1;
805 if (nn
[2] > short.max
) nn
[2] = short.max
;
807 case 0: key
.key
= (press ? TtyEvent
.Key
.MLeftDown
: TtyEvent
.Key
.MLeftUp
); break;
808 case 1: key
.key
= (press ? TtyEvent
.Key
.MMiddleDown
: TtyEvent
.Key
.MMiddleUp
); break;
809 case 2: key
.key
= (press ? TtyEvent
.Key
.MRightDown
: TtyEvent
.Key
.MRightUp
); break;
810 case 32: if (!press
) { key
.key
= TtyEvent
.Key
.Unknown
; return; } key
.key
= TtyEvent
.Key
.MLeftMotion
; break;
811 case 33: if (!press
) { key
.key
= TtyEvent
.Key
.Unknown
; return; } key
.key
= TtyEvent
.Key
.MMiddleMotion
; break;
812 case 34: if (!press
) { key
.key
= TtyEvent
.Key
.Unknown
; return; } key
.key
= TtyEvent
.Key
.MRightMotion
; break;
813 case 64: if (!press
) { key
.key
= TtyEvent
.Key
.Unknown
; return; } key
.key
= TtyEvent
.Key
.MWheelUp
; break;
814 case 65: if (!press
) { key
.key
= TtyEvent
.Key
.Unknown
; return; } key
.key
= TtyEvent
.Key
.MWheelDown
; break;
815 default: key
.key
= TtyEvent
.Key
.Unknown
; return;
817 key
.x
= cast(short)nn
[1];
818 key
.y
= cast(short)nn
[2];
821 int ch
= ttyReadKeyByte(toMSec
);
822 if (ch
< 0) { key
.key
= TtyEvent
.Key
.Error
; return key
; } // error
823 if (ch
== 0) { key
.key
= TtyEvent
.Key
.ModChar
; key
.ctrl
= true; key
.ch
= ' '; return key
; }
824 if (ch
== 8 || ch
== 127) { key
.key
= TtyEvent
.Key
.Backspace
; key
.ch
= 8; return key
; }
825 if (ch
== 9) { key
.key
= TtyEvent
.Key
.Tab
; key
.ch
= 9; return key
; }
826 //if (ch == 10) { key.key = TtyEvent.Key.Enter; key.ch = 13; return key; }
827 if (ch
== 13) { key
.key
= TtyEvent
.Key
.Enter
; key
.ch
= 13; return key
; }
829 key
.key
= TtyEvent
.Key
.Unknown
;
833 ch
= ttyReadKeyByte(toEscMSec
);
834 if (ch
< 0 || ch
== 27) { key
.key
= TtyEvent
.Key
.Escape
; key
.ch
= 27; return key
; }
836 if (termType
!= TermType
.rxvt
&& ch
== 'O') {
837 ch
= ttyReadKeyByte(toEscMSec
);
838 if (ch
< 0 || ch
== 27) { key
.key
= TtyEvent
.Key
.Escape
; key
.ch
= 27; return key
; }
839 if (ch
>= 'A' && ch
<= 'Z') xtermSpecial(cast(char)ch
);
840 if (ch
>= 'a' && ch
<= 'z') { key
.shift
= true; xtermSpecial(cast(char)(ch
-32)); }
847 bool wasDigit
= false;
848 bool firstChar
= true;
849 bool linuxCon
= false;
852 ch
= ttyReadKeyByte(toEscMSec
);
853 if (firstChar
&& ch
== '<') { parseMouse(); return key
; }
854 if (firstChar
&& ch
== 'I') { key
.key
= TtyEvent
.Key
.FocusIn
; return key
; }
855 if (firstChar
&& ch
== 'O') { key
.key
= TtyEvent
.Key
.FocusOut
; return key
; }
856 if (firstChar
&& ch
== '[') { linuxCon
= true; firstChar
= false; continue; }
858 if (ch
< 0 || ch
== 27) { key
.key
= TtyEvent
.Key
.Escape
; key
.ch
= 27; return key
; }
861 if (nc
> nn
.length
) { skipCSI(); return key
; }
862 } else if (ch
>= '0' && ch
<= '9') {
863 if (nc
>= nn
.length
) { skipCSI(); return key
; }
864 nn
.ptr
[nc
] = nn
.ptr
[nc
]*10+ch
-'0';
871 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
); }
874 if (linuxCon
) linconSpecial(cast(char)ch
);
875 else if (ch
>= 'A' && ch
<= 'Z') xtermSpecial(cast(char)ch
);
876 } else if (nc
== 1) {
877 if (ch
== '~' && nn
.ptr
[0] == 200) { key
.key
= TtyEvent
.Key
.PasteStart
; return key
; }
878 if (ch
== '~' && nn
.ptr
[0] == 201) { key
.key
= TtyEvent
.Key
.PasteEnd
; return key
; }
882 case 23: key
.shift
= true; key
.key
= TtyEvent
.Key
.F1
; return key
;
883 case 24: key
.shift
= true; key
.key
= TtyEvent
.Key
.F2
; return key
;
884 case 25: key
.shift
= true; key
.key
= TtyEvent
.Key
.F3
; return key
;
885 case 26: key
.shift
= true; key
.key
= TtyEvent
.Key
.F4
; return key
;
886 case 28: key
.shift
= true; key
.key
= TtyEvent
.Key
.F5
; return key
;
887 case 29: key
.shift
= true; key
.key
= TtyEvent
.Key
.F6
; return key
;
888 case 31: key
.shift
= true; key
.key
= TtyEvent
.Key
.F7
; return key
;
889 case 32: key
.shift
= true; key
.key
= TtyEvent
.Key
.F8
; return key
;
890 case 33: key
.shift
= true; key
.key
= TtyEvent
.Key
.F9
; return key
;
891 case 34: key
.shift
= true; key
.key
= TtyEvent
.Key
.F10
; return key
;
895 case '^': key
.ctrl
= true; break;
896 case '$': key
.shift
= true; break;
897 case '@': key
.ctrl
= true; key
.shift
= true; break;
898 case 'A': .. case 'Z': xtermMods(nn
.ptr
[0]); xtermSpecial(cast(char)ch
); return key
;
899 default: badCSI(); return key
;
901 csiSpecial(nn
.ptr
[0]);
902 } else if (nc
== 2 && xtermMods(nn
.ptr
[1])) {
903 if (nn
.ptr
[0] == 1 && ch
>= 'A' && ch
<= 'Z') {
904 xtermSpecial(cast(char)ch
);
905 } else if (ch
== '~') {
906 csiSpecial(nn
.ptr
[0]);
914 key
.key
= TtyEvent
.Key
.Tab
;
919 if (ch
>= 1 && ch
<= 26) {
920 key
.key
= TtyEvent
.Key
.ModChar
;
923 key
.ch
= cast(dchar)(ch
+64);
924 if (key
.ch
== 'H') { key
.key
= TtyEvent
.Key
.Backspace
; key
.ctrl
= false; key
.ch
= 8; }
925 else if (key
.ch
== 'J') { key
.key
= TtyEvent
.Key
.Enter
; key
.ctrl
= false; key
.ch
= 13; }
928 if (/*(ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9') || ch == '_' || ch == '`'*/true) {
930 key
.key
= TtyEvent
.Key
.ModChar
;
931 key
.shift
= (ch
>= 'A' && ch
<= 'Z'); // ignore capslock
932 if (ch
>= 'a' && ch
<= 'z') ch
-= 32;
933 key
.ch
= cast(dchar)ch
;
940 key
.key
= TtyEvent
.Key
.Tab
;
942 } else if (ch
< 32) {
944 key
.key
= TtyEvent
.Key
.ModChar
;
946 key
.ch
= cast(dchar)(ch
+64);
947 if (key
.ch
== 'H') { key
.key
= TtyEvent
.Key
.Backspace
; key
.ctrl
= false; key
.ch
= 8; }
948 else if (key
.ch
== 'J') { key
.key
= TtyEvent
.Key
.Enter
; key
.ctrl
= false; key
.ch
= 13; }
950 key
.key
= TtyEvent
.Key
.Char
;
951 key
.ch
= cast(dchar)(ch
);
952 if (ttyIsFuckedFlag
&& ch
>= 0x80) {
955 auto dch
= udc
.decode(cast(ubyte)ch
);
956 if (dch
<= dchar.max
) break;
958 ch
= ttyReadKeyByte(toEscMSec
);
962 key
.ch
= uni2koi(udc
.currCodePoint
);
965 // xterm does alt+letter with 7th bit set
966 if (!xtermMetaSendsEscape
&& termType
== TermType
.xterm
&& ch
>= 0x80 && ch
<= 0xff) {
968 if ((ch
>= 'A' && ch
<= 'Z') ||
(ch
>= 'a' && ch
<= 'z') ||
(ch
>= '0' && ch
<= '9') || ch
== '_') {
970 key
.key
= TtyEvent
.Key
.ModChar
;
971 key
.shift
= (ch
>= 'A' && ch
<= 'Z'); // ignore capslock
972 if (ch
>= 'a' && ch
<= 'z') ch
-= 32;
973 key
.ch
= cast(dchar)ch
;
983 // ////////////////////////////////////////////////////////////////////////// //
985 private extern(C
) void ttyExitRestore () {
987 if (atomicLoad(inRawMode
)) {
988 import core
.sys
.posix
.termios
: tcflush
, tcsetattr
;
989 import core
.sys
.posix
.termios
: TCIOFLUSH
, TCSAFLUSH
;
990 import core
.sys
.posix
.unistd
: STDIN_FILENO
;
991 //tcflush(STDIN_FILENO, TCIOFLUSH);
992 if (doRestoreOrig
) tcsetattr(STDIN_FILENO
, TCSAFLUSH
, &origMode
);
996 shared static this () {
998 import core
.stdc
.stdlib
: atexit
;
999 atexit(&ttyExitRestore
);
1002 import core
.sys
.posix
.unistd
: isatty
, STDIN_FILENO
, STDOUT_FILENO
;
1003 import core
.sys
.posix
.termios
: tcgetattr
;
1004 import core
.sys
.posix
.termios
: termios
;
1005 doRestoreOrig
= false;
1006 if (isatty(STDIN_FILENO
) && isatty(STDOUT_FILENO
)) {
1007 doRestoreOrig
= (tcgetattr(STDIN_FILENO
, &origMode
) == 0);
1013 shared static ~this () {
1018 // ////////////////////////////////////////////////////////////////////////// //
1019 shared static this () {
1020 import core
.sys
.posix
.stdlib
: getenv
;
1022 ttyIsFuckedFlag
= false;
1024 auto lang
= getenv("LANG");
1025 if (lang
is null) return;
1027 static char tolower (char ch
) pure nothrow @safe @nogc { return (ch
>= 'A' && ch
<= 'Z' ?
cast(char)(ch
-'A'+'a') : ch
); }
1030 if (tolower(lang
[0]) == 'u' && tolower(lang
[1]) == 't' && tolower(lang
[2]) == 'f') { ttyIsFuckedFlag
= true; return; }