implemented "M-`" in TTY mode
[sxed.git] / src / backx11 / rawtty.c
blobdef0baaad4ae15d5845654a7b201bc6362a9e073
1 /*
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/>.
21 #include "rawtty.h"
22 #ifdef USE_TTY_BACKEND
24 #include <errno.h>
25 #include <locale.h>
26 #include <poll.h>
27 #include <signal.h>
28 #include <stdint.h>
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <unistd.h>
33 #include <termios.h>
35 #include <sys/ioctl.h>
36 #include <sys/types.h>
38 #ifdef USE_EMB_SQ3
39 # include "../libsq3/sqlite3.h"
40 #else
41 # include <sqlite3.h>
42 #endif
44 #include "../libsxed/sxed_encode.h"
45 #include "../libsxed/sxed_memman.h"
46 #include "../libsfrm/sxed_frame_db.h"
47 #include "x11_cwin.h"
50 //==========================================================================
52 // ttyGetEventTypeName
54 //==========================================================================
55 const char *ttyGetEventTypeName (const enum TTYEventType type) {
56 switch (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";
114 return "UNKNOWN";
118 // ////////////////////////////////////////////////////////////////////////// //
119 static sxed_bool isAvailable = 0;
120 enum TermType {
121 term_other,
122 term_rxvt,
123 term_urxvt,
124 term_xterm,
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 */
148 } PageKeyDBHdr;
151 static PageKeyDBHdr *keydb_head = nullptr;
152 static PageKeyDBHdr *keydb_tail = nullptr;
153 static uint32_t keydb_item_count = 0;
156 //==========================================================================
158 // keydb_append
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");
167 exit(1);
170 if (!keydb_head) {
171 keydb_head = sxed_mm_alloc_page();
172 if (!keydb_head) {
173 fprintf(stderr, "FATAL: out of memory for tty key database!\n");
174 exit(1);
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) {
182 // need a new page!
183 PageKeyDBHdr *npg = sxed_mm_alloc_page();
184 if (!npg) {
185 fprintf(stderr, "FATAL: out of memory for tty key database!\n");
186 exit(1);
188 keydb_tail->next = npg;
189 keydb_tail = npg;
190 npg->next = nullptr;
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;
197 // esc
198 *pp++ = (uint8_t)elen;
199 memcpy(pp, esc, elen); pp += elen;
200 *pp++ = (uint8_t)nlen;
201 memcpy(pp, name, nlen);
203 ++keydb_item_count;
207 //==========================================================================
209 // kdbStrEquCI
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;
215 while (s0len--) {
216 uint8_t c0 = *s0++;
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;
222 return 1;
226 //==========================================================================
228 // keydb_parse_name
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));
235 // parse modifiers
236 while (nlen > 3 && name[1] == '-') {
237 sxed_bool ok = 1;
238 switch (name[0]) {
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;
243 default: ok = 0;
245 if (!ok) break;
246 nlen -= 2;
247 name += 2;
250 // parse key name
252 // fX?
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');
257 return 1;
260 // f1[12]?
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');
265 return 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; }
323 return 0;
327 //==========================================================================
329 // keydb_find_esc
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) {
340 pp += esclen+1;
341 if (keydb_parse_name(evt, pp+ofs+1, pp[ofs])) {
342 #if 0
343 FILE *fo = fopen("z_tty_kdb.log", "a");
344 fwrite(esc, esclen, 1, fo);
345 fprintf(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));
349 fclose(fo);
350 #endif
351 return 1;
353 return 0;
355 ofs += pp[ofs]+1;
356 ofs += pp[ofs]+1;
359 return 0;
363 //==========================================================================
365 // keydb_load
367 //==========================================================================
368 static void keydb_load (const char *termname) {
369 if (!termname || !termname[0]) return;
371 const char *dbpath = sxed_dbman_get_db_path();
372 if (!dbpath) return;
373 const char *dbfile = "tty.db";
374 char *dbname = sxed_mm_malloc(strlen(dbpath)+strlen(dbfile)+4);
375 if (!dbname) {
376 fprintf(stderr, "FATAL: out of memory for tty key database!\n");
377 exit(1);
379 strcpy(dbname, dbpath);
380 strcat(dbname, dbfile);
382 sqlite3 *dbh;
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);
387 return;
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
397 const char *sql = ""
398 "SELECT esc, name FROM tty_keys\n"
399 "WHERE term=:term\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);
408 return;
411 const int argidx = sqlite3_bind_parameter_index(stmt, ":term");
412 if (argidx <= 0) {
413 fprintf(stderr, "FATAL: internal ttydb error (bind index)!\n");
414 exit(1);
417 if (sqlite3_bind_text(stmt, argidx, termname, (int)strlen(termname), SQLITE_STATIC) != SQLITE_OK) {
418 fprintf(stderr, "FATAL: internal ttydb error (bind)!\n");
419 exit(1);
422 for (;;) {
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");
427 exit(1);
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);
438 #if 0
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"));
443 #endif
447 //==========================================================================
449 // ttyIsUsingKeyDB
451 //==========================================================================
452 sxed_bool ttyIsUsingKeyDB (void) {
453 return (keydb_item_count != 0);
457 //==========================================================================
459 // ttyGetTermType
461 //==========================================================================
462 const char *ttyGetTermType (void) {
463 switch (termType) {
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";
470 return "unknown";
474 //==========================================================================
476 // ttyEnableDebugCharDump
478 //==========================================================================
479 void ttyEnableDebugCharDump (void) {
480 dbgCharDump = 1;
484 //==========================================================================
486 // ttyCanSetTitle
488 //==========================================================================
489 sxed_bool ttyCanSetTitle (void) {
490 return (isAvailable && termType != term_linuxcon);
494 //==========================================================================
496 // ttyGetControlFD
498 //==========================================================================
499 int ttyGetControlFD (void) {
500 return ttyControlFD;
504 //==========================================================================
506 // ttyWasIOError
508 //==========================================================================
509 sxed_bool ttyWasIOError (void) {
510 return wasIOError;
514 //==========================================================================
516 // ttyIsBrokenWrap
518 //==========================================================================
519 sxed_bool ttyIsBrokenWrap (void) {
520 return (termType == term_other);
524 //==========================================================================
526 // ttyIsUtfuck
528 //==========================================================================
529 sxed_bool ttyIsUtfuck (void) {
530 return ttyIsFuckedFlag;
534 //==========================================================================
536 // ttyIsAvailable
538 // returns `false` if TTY is not available at all
540 //==========================================================================
541 sxed_bool ttyIsAvailable (void) {
542 return isAvailable;
546 //==========================================================================
548 // ttyIsGood
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) {
555 return isGood;
559 //==========================================================================
561 // ttyIsInRawMode
563 // returns current TTY mode as was previously set by `ttySetRawMode()`
565 //==========================================================================
566 sxed_bool ttyIsInRawMode (void) {
567 return isRawMode;
571 //==========================================================================
573 // ttyGetWidth
575 //==========================================================================
576 int ttyGetWidth (void) {
577 if (!isGood) return 80;
578 struct winsize sz;
579 if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &sz) != -1) return sz.ws_col;
580 return 80;
584 //==========================================================================
586 // ttyGetHeight
588 //==========================================================================
589 int ttyGetHeight (void) {
590 if (!isGood) return 25;
591 struct winsize sz;
592 if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &sz) != -1) return sz.ws_row;
593 return 25;
597 //==========================================================================
599 // setTTYMode
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;
609 return 0;
613 //==========================================================================
615 // ttySetRawMode
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
623 if (enable) {
624 // go to raw mode
625 //enum { IUCLC = 512 }; //0001000
626 //termios raw = origMode; // modify the original mode
627 #ifdef RAW_TTY_OLD_SWITCH_CODE
628 struct termios raw;
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
641 #else
642 struct termios raw;
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
658 #endif
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));
663 } else {
664 // go to cooked mode
665 if (!setTTYMode(&origMode)) return 0;
666 // turn on autowrap
667 const char *setupStr = "\x1b[?7h";
668 ttyRawWriteCounted(setupStr, strlen(setupStr));
670 isRawMode = enable;
671 return 1;
675 //==========================================================================
677 // ttyIsWaitKey
679 //==========================================================================
680 sxed_bool ttyIsWaitKey (void) {
681 return isWaitKey;
685 //==========================================================================
687 // ttySetWaitKey
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;
695 struct termios raw;
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) {
700 return 0;
703 isWaitKey = doWait;
704 return 1;
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;
717 while (slen) {
718 const ssize_t wr = write(STDOUT_FILENO, buf, slen);
719 if (wr < 0) {
720 if (errno == EINTR) continue;
722 if (wr <= 0) {
723 wasIOError = 1;
724 break;
726 slen -= (uint32_t)wr;
727 buf += (uint32_t)wr;
732 //==========================================================================
734 // ttyRawWrite
736 //==========================================================================
737 void ttyRawWrite (const char *str) {
738 if (str && str[0]) ttyRawWriteCounted(str, strlen(str));
742 //==========================================================================
744 // ttyBeep
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 //==========================================================================
814 // ttyWaitKey
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;
826 struct timeval tv;
827 fd_set fds;
828 FD_ZERO(&fds);
829 FD_SET(STDIN_FILENO, &fds); //STDIN_FILENO is 0
830 if (toMSec <= 0) {
831 tv.tv_sec = 0;
832 tv.tv_usec = 0;
833 } else {
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);
840 for (;;) {
841 struct pollfd pfd;
842 pfd.fd = STDIN_FILENO;
843 pfd.events = POLLIN;
844 pfd.revents = 0;
845 const int res = poll(&pfd, 1, (toMSec < 0 ? -1 : toMSec));
846 if (res == 0) return 0;
847 if (res < 0) {
848 if (toMSec >= 0) return 0;
849 if (errno == EINTR) continue;
850 wasIOError = 1;
851 return -1;
853 if (pfd.revents&(POLLERR|POLLHUP|POLLNVAL)) {
854 wasIOError = 1;
855 return -1;
857 return 1;
862 //==========================================================================
864 // ttyIsKeyHit
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 //==========================================================================
878 // ttyReadKeyByte
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;
890 uint8_t res;
891 for (;;) {
892 ssize_t rd = read(STDIN_FILENO, &res, 1);
893 if (rd < 0 && errno == EINTR) continue;
894 if (rd != 1) {
895 wasIOError = 1;
896 return -1;
898 break;
900 return res;
904 //==========================================================================
906 // skipCSI
908 //==========================================================================
909 static void skipCSI (TTYEvent *type, int toEscMSec) {
910 type->type = TTY_Unknown;
911 for (;;) {
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 //==========================================================================
921 // badCSI
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;
928 //type->time = tsv;
932 //==========================================================================
934 // xtermMods
936 //==========================================================================
937 static sxed_bool xtermMods (TTYEvent *type, unsigned mci) {
938 switch (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;
946 default: break;
948 return 0;
952 //==========================================================================
954 // xtermSpecial
956 //==========================================================================
957 static void xtermSpecial (TTYEvent *type, char ch) {
958 switch (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;
970 case 'Z':
971 type->type = TTY_Tab;
972 type->ch = 9;
973 if ((type->mods&(TTY_Alt|TTY_Ctrl|TTY_Shift)) == 0) type->mods |= TTY_Shift;
974 break;
975 default: badCSI(type); break;
980 //==========================================================================
982 // linconSpecial
984 //==========================================================================
985 static void linconSpecial (TTYEvent *type, char ch) {
986 switch (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 //==========================================================================
998 // csiSpecial
1000 //==========================================================================
1001 static void csiSpecial (TTYEvent *type, unsigned n) {
1002 switch (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 //==========================================================================
1030 // parseMouse
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};
1037 unsigned nc = 0;
1038 sxed_bool press = 0;
1039 for (;;) {
1040 int ch = ttyReadKeyByte(toEscMSec);
1041 if (ch < 0 || ch == 27) { type->type = TTY_Escape; type->ch = 27; return; }
1042 if (ch == ';') {
1043 ++nc;
1044 } else if (ch >= '0' && ch <= '9') {
1045 if (nc < 3) nn[nc] = nn[nc]*10+ch-'0';
1046 } else {
1047 if (ch == 'M') press = 1;
1048 else if (ch == 'm') press = 0;
1049 else { type->type = TTY_Unknown; return; }
1050 break;
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;
1059 switch (nn[0]) {
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;
1070 type->x = nn[1];
1071 type->y = nn[2];
1075 //==========================================================================
1077 // ttyReadKeyUseDB
1079 //==========================================================================
1080 static sxed_bool ttyReadKeyUseDB (TTYEvent *type, int toEscMSec) {
1081 uint8_t esc[129];
1082 uint32_t epos = 1;
1083 sxed_bool urxvt_idiocity = 0;
1084 int ch;
1086 esc[0] = 27;
1088 ch = ttyReadKeyByte(toEscMSec);
1089 if (ch < 0) {
1090 if (wasIOError) {
1091 type->type = TTY_Error;
1092 return 0;
1094 type->type = TTY_Escape;
1095 type->ch = 27;
1096 return 1;
1099 if (ch == 27) {
1100 if (termType != term_urxvt || ttyIsKeyHit() < 1) {
1101 type->type = TTY_Escape;
1102 type->ch = 27;
1103 return 1;
1105 // urxvt does M-Fx as "esc, esc, [, ..."
1106 ch = ttyReadKeyByte(0);
1107 if (ch < 0) {
1108 if (wasIOError) {
1109 type->type = TTY_Error;
1110 return 0;
1112 type->type = TTY_Escape;
1113 type->ch = 27;
1114 return 1;
1116 if (ch != '[') {
1117 //FIXME: need to unread a char
1118 type->type = TTY_Escape;
1119 type->ch = 27;
1120 return 1;
1122 urxvt_idiocity = 1;
1125 if (ch == '[' || ch == ']' || ch == '(' || ch == ')' ||
1126 ch == 'O'/*xterm idiocity*/)
1128 esc[epos++] = (uint8_t)ch;
1129 for (;;) {
1130 ch = ttyReadKeyByte(toEscMSec);
1131 if (ch < 0) {
1132 type->type = TTY_Error;
1133 return 0;
1135 esc[epos] = (uint8_t)ch;
1136 if (epos < 128) ++epos;
1137 if ((ch >= '0' && ch <= '9') || ch == ';') continue;
1138 break;
1140 } else if (ch == 9) {
1141 type->type = TTY_Tab;
1142 type->mods |= TTY_Alt;
1143 type->ch = 9;
1144 return 1;
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; }
1151 return 1;
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;
1158 return 1;
1159 } else {
1160 type->type = TTY_Unknown;
1161 return 1;
1164 if (epos >= 128) {
1165 type->type = TTY_Unknown;
1166 return 1;
1169 if (!keydb_find_esc(type, esc, epos)) {
1170 memset(type, 0, sizeof(*type));
1171 type->type = TTY_Unknown;
1172 } else {
1173 if (urxvt_idiocity) type->mods |= TTY_Alt;
1176 return 1;
1180 //==========================================================================
1182 // ttyReadKey
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);
1199 // error?
1200 if (ch < 0) {
1201 type->type = TTY_Error;
1202 return 0;
1205 // zero char is C-Space
1206 if (ch == 0) {
1207 type->type = TTY_ModChar;
1208 type->mods |= TTY_Ctrl;
1209 type->ch = ' ';
1210 return 1;
1213 // backspace
1214 if (ch == 8 || ch == 127) {
1215 type->type = TTY_Backspace;
1216 type->ch = 8;
1217 return 1;
1220 // tab
1221 if (ch == 9) {
1222 type->type = TTY_Tab;
1223 type->ch = 9;
1224 return 1;
1227 //if (ch == 10) { key.key = TtyEvent.TTY_Enter; key.ch = 13; return key; }
1228 if (ch == 13) {
1229 type->type = TTY_Enter;
1230 type->ch = 13;
1231 return 1;
1234 type->type = TTY_Unknown;
1236 // escape?
1237 if (ch == 27) {
1238 // from db?
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;
1247 type->ch = 27;
1248 return 1;
1250 // urxvt does M-Fx as "esc, esc, [, ..."
1251 ch = ttyReadKeyByte(0);
1252 if (ch != '[') {
1253 //FIXME: need to unread a char
1254 type->type = TTY_Escape;
1255 type->ch = 27;
1256 return 1;
1258 urxvt_idiocity = 1;
1260 // xterm stupidity
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)); }
1266 return 1;
1268 // CSI
1269 if (ch == '[') {
1270 unsigned nn[2] = {0,0};
1271 unsigned nc = 0;
1272 sxed_bool wasDigit = 0;
1273 sxed_bool firstChar = 1;
1274 sxed_bool linuxCon = 0;
1275 // parse csi
1276 for (;;) {
1277 ch = ttyReadKeyByte(toEscMSec);
1278 if (firstChar) {
1279 firstChar = 0;
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; }
1286 if (ch == ';') {
1287 ++nc;
1288 if (nc > 2) { skipCSI(type, toEscMSec); return 1; }
1289 wasDigit = 0;
1290 } else if (ch >= '0' && ch <= '9') {
1291 if (nc >= 2) { skipCSI(type, toEscMSec); return 1; }
1292 nn[nc] = nn[nc]*10+ch-'0';
1293 wasDigit = 1;
1294 } else {
1295 if (wasDigit) ++nc;
1296 break;
1299 if (dbgCharDump) {
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);
1306 fclose(fo);
1308 // process specials
1309 if (nc == 0) {
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; }
1315 switch (ch) {
1316 case '~':
1317 if (urxvt_idiocity) {
1318 switch (nn[0]) {
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;
1342 default: break;
1344 } else {
1345 switch (nn[0]) {
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;
1356 default: break;
1359 break;
1360 case '^': type->mods |= TTY_Ctrl; break;
1361 case '$':
1362 if (urxvt_idiocity) {
1363 switch (nn[0]) {
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;
1369 break;
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]);
1381 } else {
1382 badCSI(type);
1384 return 1;
1385 } // end of CSI
1387 if (ch == 9) {
1388 type->type = TTY_Tab;
1389 type->mods |= TTY_Alt;
1390 type->ch = 9;
1391 return 1;
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; }
1400 return 1;
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;
1409 return 1;
1411 return 1;
1414 if (ch == 9) {
1415 type->type = TTY_Tab;
1416 type->ch = 9;
1417 } else if (ch < 32) {
1418 // ctrl+letter
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; }
1424 } else {
1425 type->type = TTY_Char;
1426 type->ch = (unsigned)ch;
1427 if (ttyIsFuckedFlag && ch >= 0x80) {
1428 uint32_t udc = 0;
1429 for (;;) {
1430 udc = sxed_utf8d_consume(udc, ch&0xff);
1431 if (sxed_utf8_valid_cp(udc)) {
1432 // done
1433 type->ch = (udc == SXED_UTF8_REPLACEMENT_CP ? '?' : udc);
1434 break;
1436 // want more shit!
1437 ch = ttyReadKeyByte(toEscMSec);
1438 if (ch < 0) {
1439 type->ch = '?';
1440 break;
1443 } else {
1444 // xterm does alt+letter with 7th bit set
1445 if (keydb_item_count == 0 && !xtermMetaSendsEscape && termType == term_xterm &&
1446 ch >= 0x80 && ch <= 0xff)
1448 ch -= 0x80;
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;
1455 return 1;
1461 return 1;
1465 //==========================================================================
1467 // isStartsWithCI
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) {
1473 char cs = *s++;
1474 if (cs >= 'A' && cs <= 'Z') cs = cs-'A'+'a';
1475 char cp = *pat++;
1476 if (cp >= 'A' && cp <= 'Z') cp = cp-'A'+'a';
1477 if (cs != cp) return 0;
1479 return (pat[0] == 0);
1483 //==========================================================================
1485 // ttyInitialize
1487 //==========================================================================
1488 void ttyInitialize (void) {
1489 if (isInitialized) return;
1490 isInitialized = 1;
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);
1497 if (lang) {
1498 while (*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;
1504 break;
1506 ++lang;
1510 ttyControlFD = -1;
1511 if (isAvailable) {
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");
1518 if (tt) {
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 //==========================================================================
1529 // ttyLoadDatabase
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) {
1545 if (isRawMode) {
1546 // switch to normal screen
1547 ttyRawWrite("\x1b[?1049l");
1549 ttySetRawMode(0);
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;
1564 switch (type->ch) {
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));
1586 uint32_t kmods = 0;
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) {
1595 case TTY_Char:
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;
1600 textbuf[1] = 0;
1601 kcode->text = textbuf;
1602 kcode->ascii = (char)type->ch;
1603 } else {
1604 const uint32_t uch = (ttyIsUtfuck() ? type->ch : sxed_koi2uni(type->ch));
1605 const uint32_t ulen = sxed_utf8_encode(textbuf, uch);
1606 textbuf[ulen] = 0;
1607 kcode->text = textbuf;
1610 break;
1612 case TTY_ModChar: // char with some modifier
1613 ksym = tty_convert_event_char(type);
1614 break;
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;
1645 default: return 0;
1648 if (ksym == XK_VoidSymbol && !kcode->text) return 0;
1650 kcode->key_mods = kmods|(uint32_t)ksym;
1651 return 1;
1655 #endif