2 * SXED -- sexy text editor engine, 2022-2023
4 * low-level TTY functions
6 * coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
7 * Understanding is not required. Only obedience.
9 * This program is free software: you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation, version 3 of the License ONLY.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program. If not, see <http://www.gnu.org/licenses/>.
22 #ifdef USE_TTY_BACKEND
35 #include <sys/ioctl.h>
36 #include <sys/types.h>
39 # include "../libsq3/sqlite3.h"
44 #include "../libsxed/sxed_encode.h"
45 #include "../libsxed/sxed_memman.h"
46 #include "../libsfrm/sxed_frame_db.h"
50 //==========================================================================
52 // ttyGetEventTypeName
54 //==========================================================================
55 const char *ttyGetEventTypeName (const enum TTYEventType type
) {
57 case TTY_None
: return "None";
58 case TTY_Error
: return "Error";
59 case TTY_Unknown
: return "Unknown";
60 case TTY_FirstKeyEvent
: return "FirstKeyEvent";
61 case TTY_Char
: return "Char";
62 case TTY_ModChar
: return "ModChar";
63 case TTY_Up
: return "Up";
64 case TTY_Down
: return "Down";
65 case TTY_Left
: return "Left";
66 case TTY_Right
: return "Right";
67 case TTY_Insert
: return "Insert";
68 case TTY_Delete
: return "Delete";
69 case TTY_PageUp
: return "PageUp";
70 case TTY_PageDown
: return "PageDown";
71 case TTY_Home
: return "Home";
72 case TTY_End
: return "End";
73 case TTY_Escape
: return "Escape";
74 case TTY_Backspace
: return "Backspace";
75 case TTY_Tab
: return "Tab";
76 case TTY_Enter
: return "Enter";
77 case TTY_Pad5
: return "Pad5";
78 case TTY_F1
: return "F1";
79 case TTY_F2
: return "F2";
80 case TTY_F3
: return "F3";
81 case TTY_F4
: return "F4";
82 case TTY_F5
: return "F5";
83 case TTY_F6
: return "F6";
84 case TTY_F7
: return "F7";
85 case TTY_F8
: return "F8";
86 case TTY_F9
: return "F9";
87 case TTY_F10
: return "F10";
88 case TTY_F11
: return "F11";
89 case TTY_F12
: return "F12";
90 case TTY_LastKeyEvent
: return "LastKeyEvent";
91 case TTY_MLeftDown
: return "MLeftDown";
92 case TTY_MLeftUp
: return "MLeftUp";
93 case TTY_MLeftMotion
: return "MLeftMotion";
94 case TTY_MMiddleDown
: return "MMiddleDown";
95 case TTY_MMiddleUp
: return "MMiddleUp";
96 case TTY_MMiddleMotion
: return "MMiddleMotion";
97 case TTY_MRightDown
: return "MRightDown";
98 case TTY_MRightUp
: return "MRightUp";
99 case TTY_MRightMotion
: return "MRightMotion";
100 case TTY_MWheelUp
: return "MWheelUp";
101 case TTY_MWheelDown
: return "MWheelDown";
102 case TTY_MMotion
: return "MMotion";
103 case TTY_MLeftClick
: return "MLeftClick";
104 case TTY_MMiddleClick
: return "MMiddleClick";
105 case TTY_MRightClick
: return "MRightClick";
106 case TTY_MLeftDouble
: return "MLeftDouble";
107 case TTY_MMiddleDouble
: return "MMiddleDouble";
108 case TTY_MRightDouble
: return "MRightDouble";
109 case TTY_PasteStart
: return "PasteStart";
110 case TTY_PasteEnd
: return "PasteEnd";
111 case TTY_FocusIn
: return "FocusIn";
112 case TTY_FocusOut
: return "FocusOut";
118 // ////////////////////////////////////////////////////////////////////////// //
119 static sxed_bool isAvailable
= 0;
125 term_linuxcon
, // linux console
129 static sxed_bool isInitialized
= 0;
130 static int ttyControlFD
= -1;
131 static sxed_bool isGood
= 0;
132 static sxed_bool isRawMode
= 0;
133 static sxed_bool isWaitKey
= 0;
134 static sxed_bool wasIOError
= 0;
135 static struct termios origMode
;
137 static enum TermType termType
= term_other
;
138 static sxed_bool ttyIsFuckedFlag
= 0;
139 static sxed_bool xtermMetaSendsEscape
= 1; /// you should add `XTerm*metaSendsEscape: true` to "~/.Xdefaults"
141 static sxed_bool dbgCharDump
= 0;
144 typedef struct SXED_PACKED PageKeyDBHdr_t
{
145 struct PageKeyDBHdr_t
*next
;
146 uint16_t used
; // used bytes
147 /* next items are pairs of byte-counted strings: esc, keyname */
151 static PageKeyDBHdr
*keydb_head
= nullptr;
152 static PageKeyDBHdr
*keydb_tail
= nullptr;
153 static uint32_t keydb_item_count
= 0;
156 //==========================================================================
160 //==========================================================================
161 static void keydb_append (const char *name
, const char *esc
) {
162 if (!name
|| !esc
|| !name
[0] || !esc
[0]) return;
163 const size_t nlen
= strlen(name
);
164 const size_t elen
= strlen(esc
);
165 if (nlen
> 255 || elen
> 255) {
166 fprintf(stderr
, "FATAL: invalid tty key database!\n");
171 keydb_head
= sxed_mm_alloc_page();
173 fprintf(stderr
, "FATAL: out of memory for tty key database!\n");
176 keydb_tail
= keydb_head
;
177 keydb_head
->next
= nullptr;
178 keydb_head
->used
= (uint16_t)sizeof(PageKeyDBHdr
);
181 if ((uint32_t)keydb_tail
->used
+(uint32_t)nlen
+(uint32_t)elen
+2 > SXED_MEMPAGE_SIZE
) {
183 PageKeyDBHdr
*npg
= sxed_mm_alloc_page();
185 fprintf(stderr
, "FATAL: out of memory for tty key database!\n");
188 keydb_tail
->next
= npg
;
191 npg
->used
= (uint16_t)sizeof(PageKeyDBHdr
);
194 PageKeyDBHdr
*pg
= keydb_tail
;
195 uint8_t *pp
= ((uint8_t *)pg
)+pg
->used
;
196 pg
->used
+= (uint16_t)nlen
+(uint16_t)elen
+2;
198 *pp
++ = (uint8_t)elen
;
199 memcpy(pp
, esc
, elen
); pp
+= elen
;
200 *pp
++ = (uint8_t)nlen
;
201 memcpy(pp
, name
, nlen
);
207 //==========================================================================
211 //==========================================================================
212 static sxed_bool
kdbStrEquCI (const uint8_t *s0
, uint32_t s0len
, const char *s1
) {
213 const size_t s1len
= strlen(s1
);
214 if (s1len
!= s0len
) return 0;
217 if (c0
>= 'A' && c0
<= 'Z') c0
= c0
-'A'+'a';
218 uint8_t c1
= *(const uint8_t *)s1
; ++s1
;
219 if (c1
>= 'A' && c1
<= 'Z') c1
= c1
-'A'+'a';
220 if (c0
!= c1
) return 0;
226 //==========================================================================
230 //==========================================================================
231 static sxed_bool
keydb_parse_name (TTYEvent
*evt
, const uint8_t *name
, uint32_t nlen
) {
232 if (!name
|| !nlen
|| !evt
) return 0;
233 memset(evt
, 0, sizeof(*evt
));
236 while (nlen
> 3 && name
[1] == '-') {
239 case 'C': evt
->mods
|= TTY_Ctrl
; break;
240 case 'M': evt
->mods
|= TTY_Alt
; break;
241 case 'S': evt
->mods
|= TTY_Shift
; break;
242 case 'H': evt
->mods
|= TTY_Hyper
; break;
253 if (nlen
== 2 && (name
[0] == 'f' || name
[0] == 'F') &&
254 (name
[1] >= '1' && name
[1] <= '9'))
256 evt
->type
= (enum TTYEventType
)(TTY_F1
+name
[1]-'1');
261 if (nlen
== 3 && (name
[0] == 'f' || name
[0] == 'F') &&
262 name
[1] == '1' && (name
[2] == '1' || name
[2] == '2'))
264 evt
->type
= (enum TTYEventType
)(TTY_F11
+name
[2]-'1');
268 if (kdbStrEquCI(name
, nlen
, "left")) { evt
->type
= TTY_Left
; return 1; }
269 if (kdbStrEquCI(name
, nlen
, "right")) { evt
->type
= TTY_Right
; return 1; }
270 if (kdbStrEquCI(name
, nlen
, "up")) { evt
->type
= TTY_Up
; return 1; }
271 if (kdbStrEquCI(name
, nlen
, "down")) { evt
->type
= TTY_Down
; return 1; }
272 if (kdbStrEquCI(name
, nlen
, "home")) { evt
->type
= TTY_Home
; return 1; }
273 if (kdbStrEquCI(name
, nlen
, "end")) { evt
->type
= TTY_End
; return 1; }
275 if (kdbStrEquCI(name
, nlen
, "pgup")) { evt
->type
= TTY_PageUp
; return 1; }
276 if (kdbStrEquCI(name
, nlen
, "pg_up")) { evt
->type
= TTY_PageUp
; return 1; }
277 if (kdbStrEquCI(name
, nlen
, "pageup")) { evt
->type
= TTY_PageUp
; return 1; }
278 if (kdbStrEquCI(name
, nlen
, "page_up")) { evt
->type
= TTY_PageUp
; return 1; }
280 if (kdbStrEquCI(name
, nlen
, "pgdn")) { evt
->type
= TTY_PageDown
; return 1; }
281 if (kdbStrEquCI(name
, nlen
, "pg_dn")) { evt
->type
= TTY_PageDown
; return 1; }
282 if (kdbStrEquCI(name
, nlen
, "pgdown")) { evt
->type
= TTY_PageDown
; return 1; }
283 if (kdbStrEquCI(name
, nlen
, "pg_down")) { evt
->type
= TTY_PageDown
; return 1; }
284 if (kdbStrEquCI(name
, nlen
, "pagedn")) { evt
->type
= TTY_PageDown
; return 1; }
285 if (kdbStrEquCI(name
, nlen
, "page_dn")) { evt
->type
= TTY_PageDown
; return 1; }
286 if (kdbStrEquCI(name
, nlen
, "pagedown")) { evt
->type
= TTY_PageDown
; return 1; }
287 if (kdbStrEquCI(name
, nlen
, "page_down")) { evt
->type
= TTY_PageDown
; return 1; }
289 if (kdbStrEquCI(name
, nlen
, "ins")) { evt
->type
= TTY_Insert
; return 1; }
290 if (kdbStrEquCI(name
, nlen
, "insert")) { evt
->type
= TTY_Insert
; return 1; }
292 if (kdbStrEquCI(name
, nlen
, "del")) { evt
->type
= TTY_Delete
; return 1; }
293 if (kdbStrEquCI(name
, nlen
, "delete")) { evt
->type
= TTY_Delete
; return 1; }
295 if (kdbStrEquCI(name
, nlen
, "ret")) { evt
->type
= TTY_Enter
; return 1; }
296 if (kdbStrEquCI(name
, nlen
, "return")) { evt
->type
= TTY_Enter
; return 1; }
297 if (kdbStrEquCI(name
, nlen
, "ent")) { evt
->type
= TTY_Enter
; return 1; }
298 if (kdbStrEquCI(name
, nlen
, "enter")) { evt
->type
= TTY_Enter
; return 1; }
300 if (kdbStrEquCI(name
, nlen
, "tab")) { evt
->type
= TTY_Tab
; return 1; }
302 if (kdbStrEquCI(name
, nlen
, "bs")) { evt
->type
= TTY_Backspace
; return 1; }
303 if (kdbStrEquCI(name
, nlen
, "back")) { evt
->type
= TTY_Backspace
; return 1; }
304 if (kdbStrEquCI(name
, nlen
, "backspace")) { evt
->type
= TTY_Backspace
; return 1; }
306 if (kdbStrEquCI(name
, nlen
, "esc")) { evt
->type
= TTY_Escape
; return 1; }
307 if (kdbStrEquCI(name
, nlen
, "escape")) { evt
->type
= TTY_Escape
; return 1; }
309 if (kdbStrEquCI(name
, nlen
, "pad5")) { evt
->type
= TTY_Pad5
; return 1; }
311 if (kdbStrEquCI(name
, nlen
, "pastestart")) { evt
->type
= TTY_PasteStart
; return 1; }
312 if (kdbStrEquCI(name
, nlen
, "paste_start")) { evt
->type
= TTY_PasteStart
; return 1; }
314 if (kdbStrEquCI(name
, nlen
, "pasteend")) { evt
->type
= TTY_PasteEnd
; return 1; }
315 if (kdbStrEquCI(name
, nlen
, "paste_end")) { evt
->type
= TTY_PasteEnd
; return 1; }
317 if (kdbStrEquCI(name
, nlen
, "focusin")) { evt
->type
= TTY_FocusIn
; return 1; }
318 if (kdbStrEquCI(name
, nlen
, "focus_in")) { evt
->type
= TTY_FocusIn
; return 1; }
320 if (kdbStrEquCI(name
, nlen
, "focusout")) { evt
->type
= TTY_FocusOut
; return 1; }
321 if (kdbStrEquCI(name
, nlen
, "focus_out")) { evt
->type
= TTY_FocusOut
; return 1; }
327 //==========================================================================
331 //==========================================================================
332 static sxed_bool
keydb_find_esc (TTYEvent
*evt
, const void *esc
, uint32_t esclen
) {
333 if (!esc
|| !esclen
|| esclen
> 255) return 0;
334 if (keydb_item_count
== 0) return 0;
335 for (PageKeyDBHdr
*pg
= keydb_head
; pg
; pg
= pg
->next
) {
336 uint8_t *pp
= (uint8_t *)pg
;
337 uint32_t ofs
= (uint32_t)sizeof(PageKeyDBHdr
);
338 while (ofs
< pg
->used
) {
339 if (pp
[ofs
] == esclen
&& memcmp(pp
+ofs
+1, esc
, esclen
) == 0) {
341 if (keydb_parse_name(evt
, pp
+ofs
+1, pp
[ofs
])) {
343 FILE *fo
= fopen("z_tty_kdb.log", "a");
344 fwrite(esc
, esclen
, 1, fo
);
346 fwrite(pp
+ofs
+1, pp
[ofs
], 1, fo
);
347 fprintf(fo
, " : mods=0x%04x; type=%u (%s)\n", evt
->mods
, (unsigned)evt
->type
,
348 ttyGetEventTypeName(evt
->type
));
363 //==========================================================================
367 //==========================================================================
368 static void keydb_load (const char *termname
) {
369 if (!termname
|| !termname
[0]) return;
371 const char *dbpath
= sxed_dbman_get_db_path();
373 const char *dbfile
= "tty.db";
374 char *dbname
= sxed_mm_malloc(strlen(dbpath
)+strlen(dbfile
)+4);
376 fprintf(stderr
, "FATAL: out of memory for tty key database!\n");
379 strcpy(dbname
, dbpath
);
380 strcat(dbname
, dbfile
);
384 if (sqlite3_open_v2(dbname
, &dbh
, SQLITE_OPEN_READONLY
, nullptr) != SQLITE_OK
) {
385 //sxed_logf("WARNING: cannot open database file '%s'!", dbname);
386 sxed_mm_mfree(dbname
);
389 sxed_mm_mfree(dbname
);
391 // set various options and limits
392 sqlite3_busy_timeout(dbh
, 1000); // busy timeout: 1 second
393 sqlite3_limit(dbh
, SQLITE_LIMIT_LENGTH
, 1024*1024); // 1MB for the whole row is more than enough
394 sqlite3_limit(dbh
, SQLITE_LIMIT_SQL_LENGTH
, 65536); // i believe that this is more than enough
395 sqlite3_limit(dbh
, SQLITE_LIMIT_COLUMN
, 32); // no, really
398 "SELECT esc, name FROM tty_keys\n"
401 sqlite3_stmt
*stmt
= nullptr;
402 const char *sqlEnd
= nullptr;
403 int pres
= sqlite3_prepare_v2(dbh
, sql
, (int)strlen(sql
), &stmt
, &sqlEnd
);
404 if (pres
!= SQLITE_OK
|| *sqlEnd
) {
405 const int err
= sqlite3_extended_errcode(dbh
);
406 sxed_logf("FATAL: cannot prepare SQL statement; err=%d: %s!", err
, sqlite3_errstr(err
));
407 if (stmt
) sqlite3_finalize(stmt
);
411 const int argidx
= sqlite3_bind_parameter_index(stmt
, ":term");
413 fprintf(stderr
, "FATAL: internal ttydb error (bind index)!\n");
417 if (sqlite3_bind_text(stmt
, argidx
, termname
, (int)strlen(termname
), SQLITE_STATIC
) != SQLITE_OK
) {
418 fprintf(stderr
, "FATAL: internal ttydb error (bind)!\n");
423 const int res
= sqlite3_step(stmt
);
424 if (res
== SQLITE_DONE
) break;
425 if (res
!= SQLITE_ROW
) {
426 fprintf(stderr
, "FATAL: internal ttydb error (reading)!\n");
430 const char *esc
= (const char *)sqlite3_column_text(stmt
, 0);
431 const char *name
= (const char *)sqlite3_column_text(stmt
, 1);
432 keydb_append(name
, esc
);
435 sqlite3_finalize(stmt
);
436 sqlite3_close_v2(dbh
);
439 if (keydb_item_count
) {
440 sxed_logf("loaded tty database for '%s' (%u item%s)", termname
, keydb_item_count
,
441 (keydb_item_count
== 1 ? "" : "s"));
447 //==========================================================================
451 //==========================================================================
452 sxed_bool
ttyIsUsingKeyDB (void) {
453 return (keydb_item_count
!= 0);
457 //==========================================================================
461 //==========================================================================
462 const char *ttyGetTermType (void) {
464 case term_other
: return "other";
465 case term_rxvt
: return "rxvt";
466 case term_urxvt
: return "urxvt";
467 case term_xterm
: return "xterm";
468 case term_linuxcon
: return "linux";
474 //==========================================================================
476 // ttyEnableDebugCharDump
478 //==========================================================================
479 void ttyEnableDebugCharDump (void) {
484 //==========================================================================
488 //==========================================================================
489 sxed_bool
ttyCanSetTitle (void) {
490 return (isAvailable
&& termType
!= term_linuxcon
);
494 //==========================================================================
498 //==========================================================================
499 int ttyGetControlFD (void) {
504 //==========================================================================
508 //==========================================================================
509 sxed_bool
ttyWasIOError (void) {
514 //==========================================================================
518 //==========================================================================
519 sxed_bool
ttyIsBrokenWrap (void) {
520 return (termType
== term_other
);
524 //==========================================================================
528 //==========================================================================
529 sxed_bool
ttyIsUtfuck (void) {
530 return ttyIsFuckedFlag
;
534 //==========================================================================
538 // returns `false` if TTY is not available at all
540 //==========================================================================
541 sxed_bool
ttyIsAvailable (void) {
546 //==========================================================================
550 // returns `true` if TTY is good and supports fancy features
551 // if TTY is not good, no other API will work, and calling 'em is UB
553 //==========================================================================
554 sxed_bool
ttyIsGood (void) {
559 //==========================================================================
563 // returns current TTY mode as was previously set by `ttySetRawMode()`
565 //==========================================================================
566 sxed_bool
ttyIsInRawMode (void) {
571 //==========================================================================
575 //==========================================================================
576 int ttyGetWidth (void) {
577 if (!isGood
) return 80;
579 if (ioctl(STDOUT_FILENO
, TIOCGWINSZ
, &sz
) != -1) return sz
.ws_col
;
584 //==========================================================================
588 //==========================================================================
589 int ttyGetHeight (void) {
590 if (!isGood
) return 25;
592 if (ioctl(STDOUT_FILENO
, TIOCGWINSZ
, &sz
) != -1) return sz
.ws_row
;
597 //==========================================================================
601 //==========================================================================
602 static sxed_bool
setTTYMode (const struct termios
*m
) {
603 if (tcsetattr(ttyControlFD
, TCSAFLUSH
, m
) == 0) return 1;
604 if (tcsetattr(ttyControlFD
, TCSAFLUSH
, m
) == 0) return 1;
605 if (tcsetattr(STDIN_FILENO
, TCSAFLUSH
, m
) == 0) return 1;
606 if (tcsetattr(STDIN_FILENO
, TCSANOW
, m
) == 0) return 1;
607 if (tcsetattr(STDOUT_FILENO
, TCSAFLUSH
, m
) == 0) return 1;
608 if (tcsetattr(STDOUT_FILENO
, TCSANOW
, m
) == 0) return 1;
613 //==========================================================================
617 // switch TTY to raw or to normal mode
619 //==========================================================================
620 sxed_bool
ttySetRawMode (sxed_bool enable
) {
621 if (!isGood
) return 0; // oops
622 if (isRawMode
== enable
) return 1; // already done
625 //enum { IUCLC = 512 }; //0001000
626 //termios raw = origMode; // modify the original mode
627 #ifdef RAW_TTY_OLD_SWITCH_CODE
629 memset((void *)&raw
, 0, sizeof(raw
));
630 //tcflush(STDIN_FILENO, TCIOFLUSH);
631 raw
.c_iflag
= IGNBRK
;
632 // output modes: disable post processing
633 raw
.c_oflag
= /*OPOST|*/ONLCR
;
634 // control modes: set 8 bit chars
635 raw
.c_cflag
= CS8
|CLOCAL
;
636 // control chars: set return condition: min number of bytes and timer
637 // we want read to return every single byte, without timeout
638 raw
.c_cc
[VMIN
] = (isWaitKey
? 1 : 0); // wait/poll mode
639 raw
.c_cc
[VTIME
] = 0; // no timer
640 // put terminal in raw mode after flushing
643 tcflush(STDIN_FILENO
, TCIOFLUSH
);
644 tcflush(STDOUT_FILENO
, TCIOFLUSH
);
645 memcpy((void *)&raw
, &origMode
, sizeof(raw
));
646 raw
.c_iflag
&= ~(INLCR
|IGNCR
|ICRNL
|IXON
|IXOFF
);
647 raw
.c_iflag
|= IGNBRK
;
648 // output modes: disable post processing
649 raw
.c_oflag
&= ~(OPOST
|OCRNL
|ONOCR
|ONLRET
);
650 raw
.c_oflag
|= ONLCR
;
651 // control modes: set 8 bit chars
652 //raw.c_cflag = CS8|CLOCAL;
653 raw
.c_lflag
&= ~(ISIG
|ICANON
|ECHO
|IEXTEN
);
654 // control chars: set return condition: min number of bytes and timer
655 // we want read to return every single byte, without timeout
656 raw
.c_cc
[VMIN
] = (isWaitKey
? 1 : 0); // wait/poll mode
657 raw
.c_cc
[VTIME
] = 0; // no timeout
659 if (!setTTYMode(&raw
)) return 0;
660 // G0 is ASCII, G1 is graphics, no autowrap
661 const char *setupStr
= "\x1b(B\x1b)0\x0f\x1b[?7l";
662 ttyRawWriteCounted(setupStr
, strlen(setupStr
));
665 if (!setTTYMode(&origMode
)) return 0;
667 const char *setupStr
= "\x1b[?7h";
668 ttyRawWriteCounted(setupStr
, strlen(setupStr
));
675 //==========================================================================
679 //==========================================================================
680 sxed_bool
ttyIsWaitKey (void) {
685 //==========================================================================
689 // set wait/poll mode
691 //==========================================================================
692 sxed_bool
ttySetWaitKey (sxed_bool doWait
) {
693 if (!isGood
|| !isRawMode
) return 0;
694 if (isWaitKey
== doWait
) return 1;
696 if (tcgetattr(ttyControlFD
, &raw
) != 0) return 0; //redirected = false;
697 raw
.c_cc
[VMIN
] = (doWait
? 1 : 0); // wait/poll mode
698 if (tcsetattr(ttyControlFD
, TCSAFLUSH
, &raw
) != 0) {
699 if (tcsetattr(ttyControlFD
, TCSANOW
, &raw
) != 0) {
708 //==========================================================================
710 // ttyRawWriteCounted
712 //==========================================================================
713 void ttyRawWriteCounted (const void *str
, uint32_t slen
) {
714 if (!str
|| !slen
) return;
715 if (wasIOError
) return;
716 const uint8_t *buf
= (const uint8_t *)str
;
718 const ssize_t wr
= write(STDOUT_FILENO
, buf
, slen
);
720 if (errno
== EINTR
) continue;
726 slen
-= (uint32_t)wr
;
732 //==========================================================================
736 //==========================================================================
737 void ttyRawWrite (const char *str
) {
738 if (str
&& str
[0]) ttyRawWriteCounted(str
, strlen(str
));
742 //==========================================================================
746 //==========================================================================
747 void ttyBeep (void) {
748 if (isAvailable
) ttyRawWriteCounted("\x07", 1);
752 //==========================================================================
754 // ttyEnableBracketedPaste
756 //==========================================================================
757 void ttyEnableBracketedPaste (void) {
758 if (isGood
) ttyRawWrite("\x1b[?2004h");
762 //==========================================================================
764 // ttyDisableBracketedPaste
766 //==========================================================================
767 void ttyDisableBracketedPaste (void) {
768 if (isGood
) ttyRawWrite("\x1b[?2004l");
772 //==========================================================================
774 // ttyEnableFocusReports
776 //==========================================================================
777 void ttyEnableFocusReports (void) {
778 if (isGood
) ttyRawWrite("\x1b[?1004h");
782 //==========================================================================
784 // ttyDisableFocusReports
786 //==========================================================================
787 void ttyDisableFocusReports (void) {
788 if (isGood
) ttyRawWrite("\x1b[?1004l");
792 //==========================================================================
794 // ttyEnableMouseReports
796 //==========================================================================
797 void ttyEnableMouseReports (void) {
798 if (isGood
) ttyRawWrite("\x1b[?1000h\x1b[?1006h\x1b[?1002h");
802 //==========================================================================
804 // ttyDisableMouseReports
806 //==========================================================================
807 void ttyDisableMouseReports (void) {
808 if (isGood
) ttyRawWrite("\x1b[?1002l\x1b[?1006l\x1b[?1000l");
812 //==========================================================================
816 // Wait for keypress.
818 // toMSec = timeout in milliseconds; <0: infinite; 0: don't wait; default is -1
820 // returns `true` if key was pressed, false if no key was pressed in the given time
822 //==========================================================================
823 int ttyWaitKey (int toMSec
) {
824 if (!isGood
|| !isRawMode
|| wasIOError
) return -1;
829 FD_SET(STDIN_FILENO, &fds); //STDIN_FILENO is 0
834 tv.tv_sec = (int)(toMSec/1000);
835 tv.tv_usec = (toMSec%1000)*1000;
837 select(STDIN_FILENO+1, &fds, nullptr, nullptr, (toMSec < 0 ? nullptr : &tv));
838 return FD_ISSET(STDIN_FILENO, &fds);
842 pfd
.fd
= STDIN_FILENO
;
845 const int res
= poll(&pfd
, 1, (toMSec
< 0 ? -1 : toMSec
));
846 if (res
== 0) return 0;
848 if (toMSec
>= 0) return 0;
849 if (errno
== EINTR
) continue;
853 if (pfd
.revents
&(POLLERR
|POLLHUP
|POLLNVAL
)) {
862 //==========================================================================
866 // Check if key was pressed. Don't block.
868 // returns `true` if key was pressed, false if no key was pressed
870 //==========================================================================
871 int ttyIsKeyHit (void) {
872 return ttyWaitKey(0);
876 //==========================================================================
880 // Read one byte from stdin.
882 // toMSec = timeout in milliseconds; <0: infinite; 0: don't wait; default is -1
884 // returns read byte or -1 on error/timeout
886 //==========================================================================
887 int ttyReadKeyByte (int toMSec
) {
888 if (!isGood
|| !isRawMode
|| wasIOError
) return -1;
889 if (ttyWaitKey(toMSec
) < 1) return -1;
892 ssize_t rd
= read(STDIN_FILENO
, &res
, 1);
893 if (rd
< 0 && errno
== EINTR
) continue;
904 //==========================================================================
908 //==========================================================================
909 static void skipCSI (TTYEvent
*type
, int toEscMSec
) {
910 type
->type
= TTY_Unknown
;
912 int ch
= ttyReadKeyByte(toEscMSec
);
913 if (ch
< 0 || ch
== 27) { type
->type
= TTY_Escape
; type
->ch
= 27; break; }
914 if (ch
!= ';' && (ch
< '0' || ch
> '9')) break;
919 //==========================================================================
923 //==========================================================================
924 static void badCSI (TTYEvent
*type
) {
925 //const double tsv = type->time;
926 memset((void *)type
, 0, sizeof(*type
));
927 type
->type
= TTY_Unknown
;
932 //==========================================================================
936 //==========================================================================
937 static sxed_bool
xtermMods (TTYEvent
*type
, unsigned mci
) {
939 case 2: type
->mods
|= TTY_Shift
; return 1;
940 case 3: type
->mods
|= TTY_Alt
; return 1;
941 case 4: type
->mods
|= TTY_Alt
|TTY_Shift
; return 1;
942 case 5: type
->mods
|= TTY_Ctrl
; return 1;
943 case 6: type
->mods
|= TTY_Ctrl
|TTY_Shift
; return 1;
944 case 7: type
->mods
|= TTY_Alt
|TTY_Ctrl
; return 1;
945 case 8: type
->mods
|= TTY_Alt
|TTY_Ctrl
|TTY_Shift
; return 1;
952 //==========================================================================
956 //==========================================================================
957 static void xtermSpecial (TTYEvent
*type
, char ch
) {
959 case 'A': type
->type
= TTY_Up
; break;
960 case 'B': type
->type
= TTY_Down
; break;
961 case 'C': type
->type
= TTY_Right
; break;
962 case 'D': type
->type
= TTY_Left
; break;
963 case 'E': type
->type
= TTY_Pad5
; break;
964 case 'H': type
->type
= TTY_Home
; break;
965 case 'F': type
->type
= TTY_End
; break;
966 case 'P': type
->type
= TTY_F1
; break;
967 case 'Q': type
->type
= TTY_F2
; break;
968 case 'R': type
->type
= TTY_F3
; break;
969 case 'S': type
->type
= TTY_F4
; break;
971 type
->type
= TTY_Tab
;
973 if ((type
->mods
&(TTY_Alt
|TTY_Ctrl
|TTY_Shift
)) == 0) type
->mods
|= TTY_Shift
;
975 default: badCSI(type
); break;
980 //==========================================================================
984 //==========================================================================
985 static void linconSpecial (TTYEvent
*type
, char ch
) {
987 case 'A': type
->type
= TTY_F1
; break;
988 case 'B': type
->type
= TTY_F2
; break;
989 case 'C': type
->type
= TTY_F3
; break;
990 case 'D': type
->type
= TTY_F4
; break;
991 default: badCSI(type
); break;
996 //==========================================================================
1000 //==========================================================================
1001 static void csiSpecial (TTYEvent
*type
, unsigned n
) {
1003 case 1: type
->type
= TTY_Home
; return; // xterm
1004 case 2: type
->type
= TTY_Insert
; return;
1005 case 3: type
->type
= TTY_Delete
; return;
1006 case 4: type
->type
= TTY_End
; return;
1007 case 5: type
->type
= TTY_PageUp
; return;
1008 case 6: type
->type
= TTY_PageDown
; return;
1009 case 7: type
->type
= TTY_Home
; return; // rxvt
1010 case 8: type
->type
= TTY_End
; return;
1011 case 1+10: type
->type
= TTY_F1
; return;
1012 case 2+10: type
->type
= TTY_F2
; return;
1013 case 3+10: type
->type
= TTY_F3
; return;
1014 case 4+10: type
->type
= TTY_F4
; return;
1015 case 5+10: type
->type
= TTY_F5
; return;
1016 case 6+11: type
->type
= TTY_F6
; return;
1017 case 7+11: type
->type
= TTY_F7
; return;
1018 case 8+11: type
->type
= TTY_F8
; return;
1019 case 9+11: type
->type
= TTY_F9
; return;
1020 case 10+11: type
->type
= TTY_F10
; return;
1021 case 11+12: type
->type
= TTY_F11
; return;
1022 case 12+12: type
->type
= TTY_F12
; return;
1023 default: badCSI(type
); break;
1028 //==========================================================================
1032 // {\e}[<0;58;32M (button;x;y;[Mm])
1034 //==========================================================================
1035 static void parseMouse (TTYEvent
*type
, int toEscMSec
) {
1036 unsigned nn
[3] = {0,0,0};
1038 sxed_bool press
= 0;
1040 int ch
= ttyReadKeyByte(toEscMSec
);
1041 if (ch
< 0 || ch
== 27) { type
->type
= TTY_Escape
; type
->ch
= 27; return; }
1044 } else if (ch
>= '0' && ch
<= '9') {
1045 if (nc
< 3) nn
[nc
] = nn
[nc
]*10+ch
-'0';
1047 if (ch
== 'M') press
= 1;
1048 else if (ch
== 'm') press
= 0;
1049 else { type
->type
= TTY_Unknown
; return; }
1053 if (nn
[1] > 0) --nn
[1];
1054 if (nn
[2] > 0) --nn
[2];
1055 //if (nn[1] < 0) nn[1] = 1;
1056 if (nn
[1] > 0x7fffu
) nn
[1] = 0x7fffu
;
1057 //if (nn[2] < 0) nn[2] = 1;
1058 if (nn
[2] > 0x7fffu
) nn
[2] = 0x7fffu
;
1060 case 0: type
->type
= (press
? TTY_MLeftDown
: TTY_MLeftUp
); break;
1061 case 1: type
->type
= (press
? TTY_MMiddleDown
: TTY_MMiddleUp
); break;
1062 case 2: type
->type
= (press
? TTY_MRightDown
: TTY_MRightUp
); break;
1063 case 32: if (!press
) { type
->type
= TTY_Unknown
; return; } type
->type
= TTY_MLeftMotion
; break;
1064 case 33: if (!press
) { type
->type
= TTY_Unknown
; return; } type
->type
= TTY_MMiddleMotion
; break;
1065 case 34: if (!press
) { type
->type
= TTY_Unknown
; return; } type
->type
= TTY_MRightMotion
; break;
1066 case 64: if (!press
) { type
->type
= TTY_Unknown
; return; } type
->type
= TTY_MWheelUp
; break;
1067 case 65: if (!press
) { type
->type
= TTY_Unknown
; return; } type
->type
= TTY_MWheelDown
; break;
1068 default: type
->type
= TTY_Unknown
; return;
1075 //==========================================================================
1079 //==========================================================================
1080 static sxed_bool
ttyReadKeyUseDB (TTYEvent
*type
, int toEscMSec
) {
1083 sxed_bool urxvt_idiocity
= 0;
1088 ch
= ttyReadKeyByte(toEscMSec
);
1091 type
->type
= TTY_Error
;
1094 type
->type
= TTY_Escape
;
1100 if (termType
!= term_urxvt
|| ttyIsKeyHit() < 1) {
1101 type
->type
= TTY_Escape
;
1105 // urxvt does M-Fx as "esc, esc, [, ..."
1106 ch
= ttyReadKeyByte(0);
1109 type
->type
= TTY_Error
;
1112 type
->type
= TTY_Escape
;
1117 //FIXME: need to unread a char
1118 type
->type
= TTY_Escape
;
1125 if (ch
== '[' || ch
== ']' || ch
== '(' || ch
== ')' ||
1126 ch
== 'O'/*xterm idiocity*/)
1128 esc
[epos
++] = (uint8_t)ch
;
1130 ch
= ttyReadKeyByte(toEscMSec
);
1132 type
->type
= TTY_Error
;
1135 esc
[epos
] = (uint8_t)ch
;
1136 if (epos
< 128) ++epos
;
1137 if ((ch
>= '0' && ch
<= '9') || ch
== ';') continue;
1140 } else if (ch
== 9) {
1141 type
->type
= TTY_Tab
;
1142 type
->mods
|= TTY_Alt
;
1145 } else if (ch
>= 1 && ch
<= 26) {
1146 type
->type
= TTY_ModChar
;
1147 type
->mods
|= TTY_Alt
|TTY_Ctrl
;
1148 type
->ch
= (unsigned)(ch
+64);
1149 if (type
->ch
== 'H') { type
->type
= TTY_Backspace
; type
->mods
&= ~TTY_Ctrl
; type
->ch
= 8; }
1150 else if (type
->ch
== 'J') { type
->type
= TTY_Enter
; type
->mods
&= ~TTY_Ctrl
; type
->ch
= 13; }
1152 } else if (/*(ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9') || ch == '_' || ch == '`'*/1) {
1153 type
->mods
|= TTY_Alt
;
1154 type
->type
= TTY_ModChar
;
1155 if (ch
>= 'A' && ch
<= 'Z') type
->mods
|= TTY_Shift
; // ignore capslock
1156 if (ch
>= 'a' && ch
<= 'z') ch
-= 32;
1157 type
->ch
= (unsigned)ch
;
1160 type
->type
= TTY_Unknown
;
1165 type
->type
= TTY_Unknown
;
1169 if (!keydb_find_esc(type
, esc
, epos
)) {
1170 memset(type
, 0, sizeof(*type
));
1171 type
->type
= TTY_Unknown
;
1173 if (urxvt_idiocity
) type
->mods
|= TTY_Alt
;
1180 //==========================================================================
1184 // Read key from stdin.
1186 // WARNING! no utf-8 support yet!
1188 // toMSec = timeout in milliseconds; <0: infinite; 0: don't wait; default is -1
1189 // toEscMSec = timeout in milliseconds for escape sequences
1191 //==========================================================================
1192 SXED_PUBLIC sxed_bool
ttyReadKey (TTYEvent
*type
, int toMSec
, int toEscMSec
) {
1193 if (!type
) return 0;
1194 memset((void *)type
, 0, sizeof(*type
));
1195 //type->time = Sys_Time();
1197 int ch
= ttyReadKeyByte(toMSec
);
1201 type
->type
= TTY_Error
;
1205 // zero char is C-Space
1207 type
->type
= TTY_ModChar
;
1208 type
->mods
|= TTY_Ctrl
;
1214 if (ch
== 8 || ch
== 127) {
1215 type
->type
= TTY_Backspace
;
1222 type
->type
= TTY_Tab
;
1227 //if (ch == 10) { key.key = TtyEvent.TTY_Enter; key.ch = 13; return key; }
1229 type
->type
= TTY_Enter
;
1234 type
->type
= TTY_Unknown
;
1239 if (keydb_item_count
!= 0) {
1240 return ttyReadKeyUseDB(type
, toEscMSec
);
1242 ch
= ttyReadKeyByte(toEscMSec
);
1243 int urxvt_idiocity
= 0;
1244 if (ch
< 0 || ch
== 27) {
1245 if (ch
!= 27 || termType
!= term_urxvt
|| ttyIsKeyHit() < 1) {
1246 type
->type
= TTY_Escape
;
1250 // urxvt does M-Fx as "esc, esc, [, ..."
1251 ch
= ttyReadKeyByte(0);
1253 //FIXME: need to unread a char
1254 type
->type
= TTY_Escape
;
1261 if (termType
!= term_rxvt
&& termType
!= term_urxvt
&& ch
== 'O') {
1262 ch
= ttyReadKeyByte(toEscMSec
);
1263 if (ch
< 0 || ch
== 27) { type
->type
= TTY_Escape
; type
->ch
= 27; return 1; }
1264 if (ch
>= 'A' && ch
<= 'Z') xtermSpecial(type
, (char)ch
);
1265 if (ch
>= 'a' && ch
<= 'z') { type
->mods
|= TTY_Shift
; xtermSpecial(type
, (char)(ch
-32)); }
1270 unsigned nn
[2] = {0,0};
1272 sxed_bool wasDigit
= 0;
1273 sxed_bool firstChar
= 1;
1274 sxed_bool linuxCon
= 0;
1277 ch
= ttyReadKeyByte(toEscMSec
);
1280 if (ch
== '<') { parseMouse(type
, toEscMSec
); return 1; }
1281 if (ch
== 'I') { type
->type
= TTY_FocusIn
; return 1; }
1282 if (ch
== 'O') { type
->type
= TTY_FocusOut
; return 1; }
1283 if (ch
== '[') { linuxCon
= 1; continue; }
1285 if (ch
< 0 || ch
== 27) { type
->type
= TTY_Escape
; type
->ch
= 27; return 1; }
1288 if (nc
> 2) { skipCSI(type
, toEscMSec
); return 1; }
1290 } else if (ch
>= '0' && ch
<= '9') {
1291 if (nc
>= 2) { skipCSI(type
, toEscMSec
); return 1; }
1292 nn
[nc
] = nn
[nc
]*10+ch
-'0';
1300 FILE *fo
= fopen("zzz.zzz", "a");
1301 fprintf(fo
, "nc=%u", nc
);
1302 for (unsigned idx
= 0; idx
< nc
; ++idx
) {
1303 fprintf(fo
, "; n%u=%u", idx
, nn
[idx
]);
1305 fprintf(fo
, "; ch=%c\n", ch
);
1310 if (linuxCon
) linconSpecial(type
, (char)ch
);
1311 else if (ch
>= 'A' && ch
<= 'Z') xtermSpecial(type
, (char)ch
);
1312 } else if (nc
== 1) {
1313 if (ch
== '~' && nn
[0] == 200) { type
->type
= TTY_PasteStart
; return 1; }
1314 if (ch
== '~' && nn
[0] == 201) { type
->type
= TTY_PasteEnd
; return 1; }
1317 if (urxvt_idiocity
) {
1319 case 11: type
->mods
|= TTY_Alt
; type
->type
= TTY_F1
; return 1;
1320 case 12: type
->mods
|= TTY_Alt
; type
->type
= TTY_F2
; return 1;
1321 case 13: type
->mods
|= TTY_Alt
; type
->type
= TTY_F3
; return 1;
1322 case 14: type
->mods
|= TTY_Alt
; type
->type
= TTY_F4
; return 1;
1323 case 15: type
->mods
|= TTY_Alt
; type
->type
= TTY_F5
; return 1;
1324 case 17: type
->mods
|= TTY_Alt
; type
->type
= TTY_F6
; return 1;
1325 case 18: type
->mods
|= TTY_Alt
; type
->type
= TTY_F7
; return 1;
1326 case 19: type
->mods
|= TTY_Alt
; type
->type
= TTY_F8
; return 1;
1327 case 20: type
->mods
|= TTY_Alt
; type
->type
= TTY_F9
; return 1;
1328 case 21: type
->mods
|= TTY_Alt
; type
->type
= TTY_F10
; return 1;
1329 //case 23: type->mods |= TTY_Alt; type->type = TTY_F11; return 1; // M-S-F1
1330 //case 24: type->mods |= TTY_Alt; type->type = TTY_F12; return 1; // M-S-F2
1332 case 23: type
->mods
|= TTY_Alt
|TTY_Shift
; type
->type
= TTY_F1
; return 1;
1333 case 24: type
->mods
|= TTY_Alt
|TTY_Shift
; type
->type
= TTY_F2
; return 1;
1334 case 25: type
->mods
|= TTY_Alt
|TTY_Shift
; type
->type
= TTY_F3
; return 1;
1335 case 26: type
->mods
|= TTY_Alt
|TTY_Shift
; type
->type
= TTY_F4
; return 1;
1336 case 28: type
->mods
|= TTY_Alt
|TTY_Shift
; type
->type
= TTY_F5
; return 1;
1337 case 29: type
->mods
|= TTY_Alt
|TTY_Shift
; type
->type
= TTY_F6
; return 1;
1338 case 31: type
->mods
|= TTY_Alt
|TTY_Shift
; type
->type
= TTY_F7
; return 1;
1339 case 32: type
->mods
|= TTY_Alt
|TTY_Shift
; type
->type
= TTY_F8
; return 1;
1340 case 33: type
->mods
|= TTY_Alt
|TTY_Shift
; type
->type
= TTY_F9
; return 1;
1341 case 34: type
->mods
|= TTY_Alt
|TTY_Shift
; type
->type
= TTY_F10
; return 1;
1346 case 23: type
->mods
|= TTY_Shift
; type
->type
= TTY_F1
; return 1;
1347 case 24: type
->mods
|= TTY_Shift
; type
->type
= TTY_F2
; return 1;
1348 case 25: type
->mods
|= TTY_Shift
; type
->type
= TTY_F3
; return 1;
1349 case 26: type
->mods
|= TTY_Shift
; type
->type
= TTY_F4
; return 1;
1350 case 28: type
->mods
|= TTY_Shift
; type
->type
= TTY_F5
; return 1;
1351 case 29: type
->mods
|= TTY_Shift
; type
->type
= TTY_F6
; return 1;
1352 case 31: type
->mods
|= TTY_Shift
; type
->type
= TTY_F7
; return 1;
1353 case 32: type
->mods
|= TTY_Shift
; type
->type
= TTY_F8
; return 1;
1354 case 33: type
->mods
|= TTY_Shift
; type
->type
= TTY_F9
; return 1;
1355 case 34: type
->mods
|= TTY_Shift
; type
->type
= TTY_F10
; return 1;
1360 case '^': type
->mods
|= TTY_Ctrl
; break;
1362 if (urxvt_idiocity
) {
1364 case 23: type
->mods
|= TTY_Alt
|TTY_Shift
; type
->type
= TTY_F11
; return 1;
1365 case 24: type
->mods
|= TTY_Alt
|TTY_Shift
; type
->type
= TTY_F12
; return 1;
1368 type
->mods
|= TTY_Shift
;
1370 case '@': type
->mods
|= TTY_Ctrl
|TTY_Shift
; break;
1371 case 'A' ... 'Z': xtermMods(type
, nn
[0]); xtermSpecial(type
, (char)ch
); return 1;
1372 default: badCSI(type
); return 1;
1374 csiSpecial(type
, nn
[0]);
1375 } else if (nc
== 2 && xtermMods(type
, nn
[1])) {
1376 if (nn
[0] == 1 && ch
>= 'A' && ch
<= 'Z') {
1377 xtermSpecial(type
, (char)ch
);
1378 } else if (ch
== '~') {
1379 csiSpecial(type
, nn
[0]);
1388 type
->type
= TTY_Tab
;
1389 type
->mods
|= TTY_Alt
;
1394 if (ch
>= 1 && ch
<= 26) {
1395 type
->type
= TTY_ModChar
;
1396 type
->mods
|= TTY_Alt
|TTY_Ctrl
;
1397 type
->ch
= (unsigned)(ch
+64);
1398 if (type
->ch
== 'H') { type
->type
= TTY_Backspace
; type
->mods
&= ~TTY_Ctrl
; type
->ch
= 8; }
1399 else if (type
->ch
== 'J') { type
->type
= TTY_Enter
; type
->mods
&= ~TTY_Ctrl
; type
->ch
= 13; }
1403 if (/*(ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9') || ch == '_' || ch == '`'*/1) {
1404 type
->mods
|= TTY_Alt
;
1405 type
->type
= TTY_ModChar
;
1406 if (ch
>= 'A' && ch
<= 'Z') type
->mods
|= TTY_Shift
; // ignore capslock
1407 if (ch
>= 'a' && ch
<= 'z') ch
-= 32;
1408 type
->ch
= (unsigned)ch
;
1415 type
->type
= TTY_Tab
;
1417 } else if (ch
< 32) {
1419 type
->type
= TTY_ModChar
;
1420 type
->mods
|= TTY_Ctrl
;
1421 type
->ch
= (unsigned)(ch
+64);
1422 if (type
->ch
== 'H') { type
->type
= TTY_Backspace
; type
->mods
&= ~TTY_Ctrl
; type
->ch
= 8; }
1423 else if (type
->ch
== 'J') { type
->type
= TTY_Enter
; type
->mods
&= ~TTY_Ctrl
; type
->ch
= 13; }
1425 type
->type
= TTY_Char
;
1426 type
->ch
= (unsigned)ch
;
1427 if (ttyIsFuckedFlag
&& ch
>= 0x80) {
1430 udc
= sxed_utf8d_consume(udc
, ch
&0xff);
1431 if (sxed_utf8_valid_cp(udc
)) {
1433 type
->ch
= (udc
== SXED_UTF8_REPLACEMENT_CP
? '?' : udc
);
1437 ch
= ttyReadKeyByte(toEscMSec
);
1444 // xterm does alt+letter with 7th bit set
1445 if (keydb_item_count
== 0 && !xtermMetaSendsEscape
&& termType
== term_xterm
&&
1446 ch
>= 0x80 && ch
<= 0xff)
1449 if ((ch
>= 'A' && ch
<= 'Z') || (ch
>= 'a' && ch
<= 'z') || (ch
>= '0' && ch
<= '9') || ch
== '_') {
1450 type
->mods
|= TTY_Alt
;
1451 type
->type
= TTY_ModChar
;
1452 if (ch
>= 'A' && ch
<= 'Z') type
->mods
|= TTY_Shift
; // ignore capslock
1453 if (ch
>= 'a' && ch
<= 'z') ch
-= 32;
1454 type
->ch
= (unsigned)ch
;
1465 //==========================================================================
1469 //==========================================================================
1470 static sxed_bool
isStartsWithCI (const char *s
, const char *pat
) {
1471 if (!s
|| !pat
|| !s
[0] || !pat
[0]) return 0;
1472 while (*s
&& *pat
) {
1474 if (cs
>= 'A' && cs
<= 'Z') cs
= cs
-'A'+'a';
1476 if (cp
>= 'A' && cp
<= 'Z') cp
= cp
-'A'+'a';
1477 if (cs
!= cp
) return 0;
1479 return (pat
[0] == 0);
1483 //==========================================================================
1487 //==========================================================================
1488 void ttyInitialize (void) {
1489 if (isInitialized
) return;
1492 isAvailable
= (isatty(STDIN_FILENO
) && isatty(STDOUT_FILENO
));
1493 ttyIsFuckedFlag
= 0;
1495 const char *lang
= setlocale(LC_ALL
, nullptr);
1496 //fprintf(stderr, "LOCALE: <%s>\n", lang);
1499 if ((lang
[0] == 'U' || lang
[0] == 'u') &&
1500 (lang
[1] == 'T' || lang
[1] == 't') &&
1501 (lang
[2] == 'F' || lang
[2] == 'f'))
1503 ttyIsFuckedFlag
= 1;
1512 if (tcgetattr(STDIN_FILENO
, &origMode
) == 0) ttyControlFD
= STDIN_FILENO
;
1513 else if (tcgetattr(STDOUT_FILENO
, &origMode
) == 0) ttyControlFD
= STDOUT_FILENO
;
1515 isGood
= (ttyControlFD
>= 0);
1517 const char *tt
= getenv("TERM");
1519 if (isStartsWithCI(tt
, "rxvt-unicode")) termType
= term_urxvt
;
1520 else if (isStartsWithCI(tt
, "rxvt")) termType
= term_rxvt
;
1521 else if (isStartsWithCI(tt
, "xterm")) termType
= term_xterm
;
1522 else if (isStartsWithCI(tt
, "linux")) termType
= term_linuxcon
;
1527 //==========================================================================
1531 //==========================================================================
1532 void ttyLoadDatabase (void) {
1533 if (!isInitialized
) ttyInitialize();
1534 if (isGood
&& keydb_item_count
== 0) keydb_load(getenv("TERM"));
1538 //==========================================================================
1540 // rawtty_ctor_deinitializer
1542 //==========================================================================
1543 static __attribute__((destructor
)) void rawtty_ctor_deinitializer (void) {
1544 if (isInitialized
) {
1546 // switch to normal screen
1547 ttyRawWrite("\x1b[?1049l");
1550 if (isGood
) ttyRawWrite("\x1b[0m"); // reset color
1555 //==========================================================================
1557 // tty_convert_event_char
1559 //==========================================================================
1560 static inline KeySym
tty_convert_event_char (const TTYEvent
*type
) {
1561 if (type
->ch
>= 'a' && type
->ch
<= 'z') return type
->ch
-'a'+XK_a
;
1562 if (type
->ch
>= 'A' && type
->ch
<= 'Z') return type
->ch
-'A'+XK_a
;
1563 if (type
->ch
>= '0' && type
->ch
<= '9') return type
->ch
-'0'+XK_0
;
1565 case 9: return XK_Tab
;
1566 case 13: return XK_Return
;
1567 case 27: return XK_Escape
;
1568 case 32: return XK_space
;
1569 case '`': return XK_grave
;
1570 case 127: return XK_BackSpace
;
1572 return XK_VoidSymbol
;
1576 //==========================================================================
1578 // tty_convert_event
1580 //==========================================================================
1581 sxed_bool
tty_convert_event (const TTYEvent
*type
, sxed_keycode
*kcode
, char textbuf
[128]) {
1582 if (!type
|| !kcode
) return 0;
1583 KeySym ksym
= XK_VoidSymbol
;
1584 memset(kcode
, 0, sizeof(*kcode
));
1587 if (type
->mods
&TTY_Shift
) kmods
|= SXED_X11_KMOD_SHIFT
;
1588 if (type
->mods
&TTY_Alt
) kmods
|= SXED_X11_KMOD_ALT
;
1589 if (type
->mods
&TTY_Ctrl
) kmods
|= SXED_X11_KMOD_CTRL
;
1590 if (type
->mods
&TTY_Hyper
) kmods
|= SXED_X11_KMOD_HYPER
;
1592 //if (ksym >= XK_A && ksym <= XK_Z) ksym = ksym-XK_A+XK_a;
1593 //if (ksym == XK_VoidSymbol) ksym = 0;
1594 switch (type
->type
) {
1596 ksym
= tty_convert_event_char(type
);
1597 if (type
->ch
&& type
->ch
!= 127) {
1598 if (type
->ch
>= 32 && type
->ch
< 127) {
1599 textbuf
[0] = (char)type
->ch
;
1601 kcode
->text
= textbuf
;
1602 kcode
->ascii
= (char)type
->ch
;
1604 const uint32_t uch
= (ttyIsUtfuck() ? type
->ch
: sxed_koi2uni(type
->ch
));
1605 const uint32_t ulen
= sxed_utf8_encode(textbuf
, uch
);
1607 kcode
->text
= textbuf
;
1612 case TTY_ModChar
: // char with some modifier
1613 ksym
= tty_convert_event_char(type
);
1616 case TTY_Up
: ksym
= XK_Up
; break;
1617 case TTY_Down
: ksym
= XK_Down
; break;
1618 case TTY_Left
: ksym
= XK_Left
; break;
1619 case TTY_Right
: ksym
= XK_Right
; break;
1620 case TTY_Insert
: ksym
= XK_Insert
; break;
1621 case TTY_Delete
: ksym
= XK_Delete
; break;
1622 case TTY_PageUp
: ksym
= XK_Page_Up
; break;
1623 case TTY_PageDown
: ksym
= XK_Page_Down
; break;
1624 case TTY_Home
: ksym
= XK_Home
; break;
1625 case TTY_End
: ksym
= XK_End
; break;
1627 case TTY_Escape
: ksym
= XK_Escape
; break;
1628 case TTY_Backspace
: ksym
= XK_BackSpace
; break;
1629 case TTY_Tab
: ksym
= XK_Tab
; break;
1630 case TTY_Enter
: ksym
= XK_Return
; break;
1632 case TTY_F1
: ksym
= XK_F1
; break;
1633 case TTY_F2
: ksym
= XK_F2
; break;
1634 case TTY_F3
: ksym
= XK_F3
; break;
1635 case TTY_F4
: ksym
= XK_F4
; break;
1636 case TTY_F5
: ksym
= XK_F5
; break;
1637 case TTY_F6
: ksym
= XK_F6
; break;
1638 case TTY_F7
: ksym
= XK_F7
; break;
1639 case TTY_F8
: ksym
= XK_F8
; break;
1640 case TTY_F9
: ksym
= XK_F9
; break;
1641 case TTY_F10
: ksym
= XK_F10
; break;
1642 case TTY_F11
: ksym
= XK_F11
; break;
1643 case TTY_F12
: ksym
= XK_F12
; break;
1648 if (ksym
== XK_VoidSymbol
&& !kcode
->text
) return 0;
1650 kcode
->key_mods
= kmods
|(uint32_t)ksym
;