Skip to content

Instantly share code, notes, and snippets.

@JosefLitos
Last active July 20, 2023 13:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JosefLitos/daace9d2a12c432e9f766138638e5666 to your computer and use it in GitHub Desktop.
Save JosefLitos/daace9d2a12c432e9f766138638e5666 to your computer and use it in GitHub Desktop.
simple parser for all terminal input with keycombo string parser + display, supports all modifiers + multiclick (no-movement based)
// compile: gcc -O2 terminput_parser.c -o input
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
// #define PRINT_POS // enables mouse position printing
// #define DEBUG // print the numeric representation of the key + raw received string
typedef u_int32_t u32;
// Tin = terminal input
typedef u32 TinEvent;
static unsigned FLAGS = 0;
enum TinFlag {
SEND_MOUSE = 0x1, // allow sending basic mouse events
SEND_MOVE = 0x2, // allow sending mouse movement/drag events (^SEND_MOUSE)
SEND_FOCUS = 0x4, // allow sending terminal focus received and lost events (In/Out)
USE_APP_MODE = 0x8, // enter/use alternate screen buffer
// Kitty keyboard protocol (KKP) -depending options
USE_RELEASE = 0x10, // whether to send release key events
USE_REPEAT = 0x20, // whether to send repeated key events (unwanted in games)
USE_SHIFTED = 0x40, // use the shifted keys, not SHIFT - useful for switching between keyboards
USE_BASEKEY = 0x80, // ^SHIFTED_KEYS, use standard layout mappings for modified keys
USE_KP = 0x100, // numpad/KP-modified keys won't show as keypad specific
USE_CAPS = 0x200, // convert Caps to META, otherwise ignore completely
USE_NUMLOCK = 0x400, // let numlock as a valid modifier, otherwise ignore completely
};
unsigned short getTinFlags() {
return FLAGS;
}
/**
* @param flags meanings of individual bits are represented by enum TinFlag
* @param mode how to set flags: 0= replace, 1=toggle, 2=filter, 3=add, 4=subtract,
*/
void setTinFlags(unsigned flags, unsigned char mode) {
flags = mode <= 1 ? mode ? FLAGS ^ flags : flags
: mode <= 3 ? mode == 2 ? FLAGS & flags : FLAGS | flags
: FLAGS & ~flags;
// basic correction, can be overcome by setting from 0 to both
if ((flags & SEND_MOUSE) && (flags & SEND_MOVE)) flags ^= FLAGS & (SEND_MOUSE | SEND_MOVE);
unsigned diff = FLAGS ^ flags;
if (diff & USE_APP_MODE)
fprintf(
stderr, "\033[<u\033[?1049%c\033[>%du", flags & USE_APP_MODE ? 'h' : 'l',
1 | (flags & (USE_RELEASE | USE_REPEAT) ? 2 : 0) |
(flags & (USE_SHIFTED | USE_BASEKEY) ? 4 : 0)
);
if (diff & (USE_SHIFTED | USE_BASEKEY | USE_RELEASE | USE_REPEAT))
fprintf(
stderr, "\033[=%du",
1 | (flags & (USE_RELEASE | USE_REPEAT) ? 2 : 0) |
(flags & (USE_SHIFTED | USE_BASEKEY) ? 4 : 0)
);
// https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
if (diff & SEND_MOUSE) fprintf(stderr, "\033[?1000%c", flags & SEND_MOUSE ? 'h' : 'l');
if (diff & SEND_MOVE) fprintf(stderr, "\033[?1003%c", flags & SEND_MOVE ? 'h' : 'l');
if (diff & SEND_FOCUS) fprintf(stderr, "\033[?1004%c", flags & SEND_FOCUS ? 'h' : 'l');
FLAGS = flags;
#ifdef DEBUG
fprintf(stderr, "{%X}", flags);
#endif
}
static struct {
TinEvent* buff;
u32 n, alloc;
u32 first, last;
} inputQueue = {NULL, 0, 0, 0, 0};
/// @param flags see setTinFlags
void initTin(unsigned flags) {
system("/bin/stty raw -echo");
// 1006 for '<' prefix, 1016 adds pixel tracking (hidpi screens overflow at 1024/2048)
// 4;2m => modifyOtherKeys=2; 1u => enable Kitty full keyboard protocol
fprintf(stderr, "\033[?1006h\033[>4;2m\033[>1u");
setTinFlags(flags, 0);
inputQueue.buff = (TinEvent*) malloc(sizeof(TinEvent) * (inputQueue.alloc = 8));
}
void endTin() {
free(inputQueue.buff);
inputQueue.buff = NULL;
inputQueue.alloc = inputQueue.n = inputQueue.first = inputQueue.last = 0;
setTinFlags(0, 0);
fprintf(stderr, "\033[?1006l\033[>4m\033[<u");
system("/bin/stty sane");
}
enum Input : u32 {
KEY = 0,
CLICK = 1 << 30, // includes multiclick + release event if other event happened meanwhile
SCROLL = CLICK * 2,
MOVE = CLICK * 3, // move and drag
};
#define INPUT_FILTER MOVE
enum Mod {
SHIFT = CLICK >> 1,
ALT = SHIFT >> 1,
CTRL = ALT >> 1,
};
#define MOD_FILTER (SHIFT | ALT | CTRL)
enum ScrollDir {
UMS = 0,
DMS = CTRL >> 2,
LMS = DMS * 2,
RMS = DMS * 3,
};
#define SCROLLDIR_FILTER RMS
enum Button {
NOMB = 0,
LMB = CTRL >> 3,
MMB = LMB * 2,
RMB = LMB * 3,
E1MB = LMB * 4,
E2MB = LMB * 5,
E3MB = LMB * 6,
E4MB = LMB * 7,
};
#define BUTTON_FILTER E4MB
enum ClickCount {
NO_CLICK = 0,
SINGLE_CLICK = LMB >> 2,
DOUBLE_CLICK = 2 * SINGLE_CLICK,
TRIPLE_CLICK = 3 * SINGLE_CLICK,
};
#define CLICKCOUNT_FILTER TRIPLE_CLICK
#define MOUSE_FILTER (BUTTON_FILTER | CLICKCOUNT_FILTER)
#define MOUSE_POS_BITS 10 // 10: 1023=max columns/rows
#define getMouseCol(e) (e & ((1 << MOUSE_POS_BITS) - 1))
#define getMouseRow(e) getMouseCol(e >> MOUSE_POS_BITS)
enum KeyMod {
WINKEY = CTRL >> 1,
HYPER = WINKEY >> 1,
META = HYPER >> 1,
NUM_LOCK = META >> 1,
};
#define KEYMOD_FILTER (MOD_FILTER | WINKEY | HYPER | META | NUM_LOCK)
enum KeyType {
KEY_PRESS = 0,
KEY_REPEAT = NUM_LOCK >> 2,
KEY_RELEASE = KEY_REPEAT * 2,
};
#define KEYTYPE_FILTER (KEY_PRESS | KEY_REPEAT)
typedef enum {
BS = 8, // translated from 127
TAB = 9,
CR = 13,
ESC = 27,
UTF8_END = 0x110000,
UP = UTF8_END + 1, // 'A'
DOWN = UP + 1, // 'B'
RIGHT = DOWN + 1, // 'C'
LEFT = RIGHT + 1, // 'D'
BEGIN = LEFT + 1, // 'E'
END = BEGIN + 1, // 'F'
HOME = END + 2, // 'H'
FOCUS_IN = HOME + 1, // 'I'
INS = FOCUS_IN + 1, // 'J'
DEL = INS + 1, // 'K'
PG_UP = DEL + 2, // 'M'
PG_DOWN = PG_UP + 1, // 'N'
FOCUS_OUT = PG_DOWN + 1, // 'O'
F1 = FOCUS_OUT + 1, // 'P'
F2 = F1 + 1, // 'Q'
F3 = F2 + 1, // 'R'
F4 = F3 + 1, // 'S'
F5 = F4 + 1, // 'T'
F6 = F5 + 1, // 'U'
F7 = F6 + 1, // 'V'
F8 = F7 + 1, // 'W'
F9 = F8 + 1, // 'X'
F10 = F9 + 1, // 'Y'
F11 = F10 + 1, // 'Z'
F12 = F11 + 1, // -- result of missing 'G' and 'L' in the terminal encoding
} Key;
#define isText(e) (' ' <= e && e < UTF8_END)
#define KP_TEXT_OFFSET (F12 + 1)
#define KP_CONTROL_OFFSET (KP_TEXT_OFFSET + '9' + 1)
#define KP_CONTROL_DIFF (KP_CONTROL_OFFSET - UP)
#define toKP(e) (e > UTF8_END ? e + KP_CONTROL_DIFF : e + KP_TEXT_OFFSET)
#define fromKP(e) (e >= KP_CONTROL_OFFSET ? e - KP_CONTROL_DIFF : e - KP_TEXT_OFFSET)
enum StatusCode : u32 {
INPUT_IGNORED = MOVE | NOMB | SINGLE_CLICK,
INPUT_SHORT = MOVE | NOMB | DOUBLE_CLICK,
};
#define VALUE_FILTER ((1 << 21) - 1) // 21b
#define ATTR_FILTER ~VALUE_FILTER // 11b
#define nameIdx(specialKey) (specialKey + 32 - UTF8_END)
static const char* keyNames[] = {
[BS] = "BS", [TAB] = "Tab", [CR] = "CR", [ESC] = "Esc", [nameIdx(UP)] = "Up",
"Down", "Right", "Left", "Begin", "End",
"", "Home", "FocusIn", "Ins", "Del",
"", "PgUp", "PgDown", "FocusOut",
};
u32 strToInt(const char** pos) {
u32 ret = 0;
const char* seq = *pos;
while ('0' <= *seq && *seq <= '9') ret = ret * 10 + *seq++ - '0';
*pos = seq;
return ret;
}
static enum KeyMod parseMods(u32 x) {
enum KeyMod e = 0;
x--;
if ((FLAGS & USE_NUMLOCK) && (x & 128)) e |= NUM_LOCK;
if ((x & 32) || ((FLAGS & USE_CAPS) && (x & 64))) e |= META;
if (x & 16) e |= HYPER;
if (x & 8) e |= WINKEY;
if (x & 4) e |= CTRL;
if (x & 2) e |= ALT;
if (x & 1) e |= SHIFT;
return e;
}
static TinEvent parseMouseInput(const char** pos) {
const char* seq = *pos;
TinEvent kind = strToInt(&seq);
TinEvent e = 0;
if (kind & 4) e |= SHIFT;
if (kind & 8) e |= ALT;
if (kind & 16) e |= CTRL;
if (kind & 64) e |= SCROLL | DMS * (kind & 3);
else
e |= (kind & 32 ? MOVE : CLICK) |
LMB *
(kind & 128 ? 4 | (kind & 3) // extra btn
: (kind & 3) != 3 ? 1 + (kind & 3) // normal btn
: 0); // no btn
if (*seq++ != ';') return seq[-1] ? 0 : INPUT_SHORT;
e |= strToInt(&seq);
if (*seq++ != ';') return seq[-1] ? 0 : INPUT_SHORT;
e |= strToInt(&seq) << MOUSE_POS_BITS;
if (*seq != 'm' && *seq != 'M') return *seq ? 0 : INPUT_SHORT;
*pos = 1 + seq;
if (*seq != 'M' && (e & INPUT_FILTER) != CLICK) return INPUT_IGNORED;
static TinEvent last = 0;
static enum ClickCount clickCount = NO_CLICK;
if ((e & INPUT_FILTER) == CLICK) {
if (e == last) {
if (*seq == 'm') return INPUT_IGNORED; // send MouseRelease only when other events occured
if (clickCount < TRIPLE_CLICK) clickCount += SINGLE_CLICK;
else clickCount = SINGLE_CLICK; // unmoved-cursor-based multiclick detection
} else clickCount = SINGLE_CLICK;
if (*seq == 'm') return e;
else return (last = e) | clickCount;
} else {
last = 0;
return (e & INPUT_FILTER) == MOVE && (e & BUTTON_FILTER) != NOMB ? e | SINGLE_CLICK : e;
}
}
TinEvent parseUtf8(const char** pos) {
const char* seq = *pos;
int left = 1;
TinEvent k = *seq++ ^ -128;
for (TinEvent i = 64; k & i; i >>= 1, left++) k ^= i;
if (left == 1 || left > 4)
while (*seq++ < 0) {}
else
while (--left && (*seq >> 6) == -2) k = (k << 6) + (*seq++ ^ -128);
*pos = seq;
return left ? INPUT_IGNORED : k;
}
static TinEvent parseCharInput(const char** pos) {
if (**pos >= 0) {
const char* seq = (*pos)++;
if (' ' <= *seq) return *seq == 127 ? BS : *seq;
if (keyNames[(unsigned char) *seq]) return *seq == 8 ? CTRL | BS : *seq;
else return CTRL | (*seq ? *seq | 96 : ' ');
} else return parseUtf8(pos);
}
static TinEvent parseKeypadInput(TinEvent e) {
if (e < 57409) e = e - 57399 + '0';
else if (e <= 57426) {
Key val[] = {',', '/', '*', '-', '+', CR, '?', '?', LEFT,
RIGHT, UP, DOWN, PG_UP, PG_DOWN, HOME, END, INS, DEL};
e = val[e - 57409];
}
return FLAGS & USE_KP ? toKP(e) : e;
}
/// @param overFill if sequence was possibly trimmed by previous buffer overfill
static TinEvent parseRaw(const char** pos, unsigned char len, unsigned char overFill) {
#ifdef DEBUG
fprintf(stderr, "(");
for (const char* x = *pos; *x; x++) {
if (*x < 0) fprintf(stderr, "\\%d", (unsigned char) *x);
else if (*x < ' ') fprintf(stderr, "^%c", *x + 64);
else fprintf(stderr, "%c", *x);
}
fprintf(stderr, ")");
#endif
const char* seq = *pos;
TinEvent e = 0;
if (*seq != 27) e = parseCharInput(pos);
else if ((!overFill && len == 1) || seq[1] == 27) e = *(*pos)++;
else if (seq[1] == 'O') { // Arrows
if (!seq[2]) return INPUT_SHORT;
*pos += 3;
e = seq[2] + UP - 'A';
} else if (seq[1] == '[') { // CSI
if (!seq[2]) return INPUT_SHORT; // breaks <A-[> when terminal ignores \[>4;2m
if (seq[2] == '<') { // Mouse input - click/hover/drag/scroll
*pos += 3;
e = parseMouseInput(pos);
} else if (seq[2]) {
const char* at = seq + (seq[2] == '-' ? 3 : 2);
e = strToInt(&at);
if (*at == ':') { // KKP - shifted key
if (*++at != ':' && (FLAGS & USE_SHIFTED)) e = strToInt(&at);
else strToInt(&at);
if (*at == ':') { // KKP standard/base layout key (unshifted)
at++;
if (FLAGS & USE_BASEKEY) e = strToInt(&at);
else strToInt(&at);
}
}
TinEvent attr = 0;
if (*at == ';') {
at++;
attr = parseMods(strToInt(&at));
if (*at == ':') { // KKP key state - press/repeat/release
at++;
char s = strToInt(&at);
if (s == 2 && (FLAGS & USE_REPEAT)) attr |= KEY_REPEAT;
else if (s == 3 && (FLAGS & USE_RELEASE)) attr |= KEY_RELEASE;
else if (s != 1) {
*pos = 1 + at;
return *at ? INPUT_IGNORED : INPUT_SHORT;
}
}
}
if (!*at) return INPUT_SHORT;
*pos = 1 + at;
char end = *at;
if ('A' <= end && end <= 'Z') { // Arrows/Tab/F1-F4
e = attr | (end == 'Z' ? TAB : end + UP - 'A');
} else if (end == 'u') { // KKP
if (seq[2] == '-') { // (neo)vim weird horizontal scroll (with mods) handling
if ((e & VALUE_FILTER) == 19965) e = attr | SCROLL | RMS;
else if ((e & VALUE_FILTER) == 20221) e = attr | SCROLL | LMS;
else e = INPUT_IGNORED;
} else if (57399 <= e && e <= 57426) e = parseKeypadInput(e) | attr;
else if (e == 127) e = BS | attr;
else if (e < ' ' || e >= UTF8_END || !(FLAGS & USE_SHIFTED)) e |= attr;
else e |= attr & ~SHIFT;
} else if (end == '~') { // INS-PG_DOWN, F5-F12
if (e <= 6) e = e == 1 ? HOME : e == 4 ? END : INS + (seq[2] - '2');
else if (11 <= e && e <= 24) e = F1 + e - (e < 16 ? 11 : e < 22 ? 12 : 13);
else e = INPUT_IGNORED;
if (e != INPUT_IGNORED) e |= attr;
} else if (e == 27 && end == ';') { // \033[27;mod;key~ when only \033[>4;2m is recognized
e = strToInt(pos);
if (e == 127) e = BS | attr; // always like USE_SHIFTED
else e |= ' ' < e && e < UTF8_END ? attr & ~SHIFT : attr;
if ((end = *(*pos)++) != '~') e = end ? INPUT_IGNORED : INPUT_SHORT;
} else e = end ? INPUT_IGNORED : INPUT_SHORT;
}
if (!e) { // breaks <A-[> when terminal ignores special mode escape sequences
if (len < 7) return INPUT_SHORT;
*pos += 2;
e = ALT | '[';
}
} else {
++*pos;
e = parseCharInput(pos); // always like USE_SHIFTED
if (e != INPUT_IGNORED) e |= ALT;
}
#ifdef DEBUG
// if (e != INPUT_IGNORED) fprintf(stderr, "[%u/%x] ", e, e);
#endif
return e;
}
void queueTin(TinEvent e) {
if (e == INPUT_IGNORED || e == INPUT_SHORT) return;
if (inputQueue.n == inputQueue.alloc)
inputQueue.buff = realloc(inputQueue.buff, sizeof(TinEvent) * (inputQueue.alloc *= 2));
inputQueue.buff[inputQueue.last] = e;
if (++inputQueue.last == inputQueue.alloc) inputQueue.last = 0;
inputQueue.n++;
}
TinEvent getDirectTin() {
static char seq[20];
static int len = 0;
static int overFill = 0;
TinEvent e;
do {
e = INPUT_SHORT;
const char* pos;
if (len) {
seq[len] = 0;
pos = seq;
e = parseRaw(&pos, len, overFill);
if (len < 0) len = -len;
}
overFill = 0;
while (e == INPUT_SHORT) {
if ((len = read(0, seq + len, sizeof(seq) - 1 - len) + len)) {
seq[len] = 0;
pos = seq;
e = parseRaw(&pos, len, 0);
}
}
if (!e && *seq) { // unknown sequence fallback - print one char to move forward
pos = seq;
e = parseCharInput(&pos);
}
int leftOver = 0;
const char* end = seq + len;
while (pos < end) seq[leftOver++] = *pos++;
overFill = len == sizeof(seq) - 1; // last reserved for '\0'
len = leftOver;
} while (e == INPUT_IGNORED);
return e;
}
TinEvent getNextTin() {
if (!inputQueue.n) return getDirectTin();
TinEvent e = inputQueue.buff[inputQueue.first];
if (++inputQueue.first == inputQueue.alloc) inputQueue.first = 0;
inputQueue.n--;
return e;
}
/// @returns `INPUT_IGNORED` on error, otherwise the input code
/// unknown keynames get parsed as some other key
TinEvent stringToCode(const char** pos) {
const char* str = *pos;
if (!str) return 0;
if (*str != '<' || !str[1]) return parseCharInput(pos);
TinEvent e = 0;
char m = *++str;
while (m && str[1] == '-') { // key/mouse modifiers/types parsing
if (m <= 'C') e |= m <= 'C' ? m == 'A' ? ALT : CTRL : m == '2' ? DOUBLE_CLICK : TRIPLE_CLICK;
else if (m <= 'R') e |= m <= 'M' ? m == 'H' ? HYPER : META : m == 'R' ? KEY_RELEASE : NUM_LOCK;
else e |= m == 'S' ? SHIFT : WINKEY;
m = *(str += 2);
}
if (!*str || !str[1] || (*str == '>' && str[1] != '>')) return INPUT_IGNORED;
const char* end = str + 1;
while (*end && *end != '>') end++;
if (!end) return INPUT_IGNORED;
int len = end - str;
unsigned char isKP = 0;
*pos = end + 1;
if (str[1] == '>') return *str < ' ' ? INPUT_IGNORED : e | *str;
else if (*str < 0) return e | parseCharInput(&str);
else if (*str == 'F') { // function keys
str++;
return e | (F1 - 1 + strToInt(&str));
} else if (end[-2] == 'M' && end[-1] != 'E') { // Mouse (not HOME)
char kind = end[-1];
if (kind == 'M') return e | MOVE;
if (kind == 'S') return e | SCROLL | (m == 'U' ? UMS : m == 'D' ? DMS : m == 'R' ? RMS : LMS);
if (m == 'E') e |= LMB * (str[1] - '1' + 4); // extended buttons (E1MB-E4MB)
else e |= m == 'L' ? LMB : m == 'R' ? RMB : MMB;
if (kind == 'D' || kind == 'M') return e | MOVE;
if (kind == 'R' || (e & CLICKCOUNT_FILTER)) return e | CLICK; // release or multiclick
else return e | CLICK | SINGLE_CLICK;
} else if (*str == 'k') { // keypad
len--;
str++;
isKP = 1;
}
// should use a tree/hashmap here, but this is much faster
if (len == 2) e |= *str < 'E' ? *str == 'C' ? CR : BS : *str == 'l' ? '<' : UP;
else if (len == 3)
e |= *str == 'E' ? str[1] == 's' ? ESC : END : *str == 'I' ? INS : *str == 'D' ? DEL : TAB;
else if (len == 4) e |= *str == 'D' ? DOWN : *str == 'H' ? HOME : PG_UP;
else if (len == 5) e |= BEGIN;
else e |= PG_DOWN;
return isKP ? toKP(e) : e;
}
typedef struct {
char* data;
TinEvent len;
TinEvent alloc;
} String;
void add(char c, String* str) {
if (str->alloc <= str->len + 1) str->data = realloc(str->data, sizeof(char) * (str->alloc *= 2));
str->data[str->len++] = c;
}
void appendUtf8(Key k, String* str) {
int extra = k > 0xffff ? 3 : k > 0x7ff ? 2 : 1;
int first = 128;
for (int i = extra; i; i--) first |= 1 << (7 - i);
add(first | k >> (extra * 6), str);
while (extra--) add(128 | ((k >> (extra * 6)) & 63), str);
}
void append(const char* text, String* str) {
if (str->len && !str->data[str->len - 1]) str->len--;
do add(*text++, str);
while (*text);
}
void appendNum(u32 n, String* str) {
TinEvent order = 10;
while (n >= order) order *= 10;
do {
add((n % order) / (order / 10) + '0', str);
order /= 10;
} while (order > 1);
}
static void appendKeyEvent(Key k, String* str) {
if (k >= KP_TEXT_OFFSET) {
add('k', str);
k -= k < KP_CONTROL_OFFSET ? KP_TEXT_OFFSET : KP_CONTROL_DIFF;
}
if (k < 128) {
if (k < ' ' && keyNames[k]) append(keyNames[k], str);
// else if (k == '<') append("lt", str);
else add(k, str);
} else if (k < 0x110000) appendUtf8(k, str);
else if (nameIdx(k) < sizeof(keyNames) / sizeof(char*)) append(keyNames[nameIdx(k)], str);
else if (k <= F12) {
add('F', str);
appendNum(k - F1 + 1, str);
}
}
static void appendMouseEvent(enum Input e, String* str) {
u32 v = e & BUTTON_FILTER;
if (e & CLICK) {
if (!v) append("MM", str); // mouse movement
else {
enum ClickCount cc = e & CLICKCOUNT_FILTER;
if (cc > SINGLE_CLICK) append(cc == DOUBLE_CLICK ? "2-" : "3-", str);
if (v < E1MB) {
add(v == LMB ? 'L' : v == MMB ? 'M' : 'R', str); // left, middle, right
} else {
add('E', str); // extra mouse buttons
add(v / LMB - 3 + '0', str);
}
add('M', str);
add(e & SCROLL ? 'D' : cc ? 'C' : 'R', str); // drag, click, release
}
} else {
add(v == UMS ? 'U' : v == DMS ? 'D' : v == LMS ? 'L' : 'R', str);
append("MS", str); // mouse scroll
}
#ifdef PRINT_POS
add('(', str);
appendNum((e & MOUSE_ROW_FILTER) >> MOUSE_POS_BITS, str);
add(';', str);
appendNum(e & MOUSE_COL_FILTER, str);
add(')', str);
#endif
}
void appendInputEvent(TinEvent e, String* str) {
if (e < 32 || e > UTF8_END || e == '<') add('<', str);
if (e & INPUT_FILTER) {
if (e & MOD_FILTER) {
if (e & CTRL) append("C-", str);
if (e & SHIFT) append("S-", str);
if (e & ALT) append("A-", str);
}
appendMouseEvent(e, str);
} else {
if (e & KEYMOD_FILTER) {
if (e & NUM_LOCK) append("N-", str);
if (e & META) append("M-", str);
if (e & HYPER) append("H-", str);
if (e & WINKEY) append("W-", str);
if (e & CTRL) append("C-", str); // cannot join because of order
if (e & ALT) append("A-", str);
if (e & SHIFT) append("S-", str);
if (e & KEY_RELEASE) append("R-", str);
else if (e & KEY_REPEAT) append("r-", str); // not user-mappable, only programmatically
}
appendKeyEvent(e & VALUE_FILTER, str);
}
if (e < 32 || e > UTF8_END || e == '<') add('>', str);
}
String* mkStr(u32 buff) {
String* ret = malloc(sizeof(String));
ret->data = malloc(sizeof(char) * buff);
ret->len = 0;
ret->alloc = buff;
return ret;
}
void freeStr(String* str) {
if (str->data != NULL) free(str->data);
free(str);
}
String* codeToString(TinEvent e) {
String* str = mkStr(4);
appendInputEvent(e, str);
add('\0', str);
return str;
}
void printInputCode(TinEvent e) {
static String str = {NULL, 0, 0};
if (e == INPUT_IGNORED && str.data != NULL) {
free(str.data);
str.data = NULL;
} else {
if (str.data == NULL) {
str.data = malloc(sizeof(char) * 4);
str.alloc = 4;
}
str.len = 0;
appendInputEvent(e, &str); // doesn't append '\0'
write(1, str.data, str.len);
}
}
static int handleStringToKeyMode(TinEvent e) {
static String buff = {NULL, 0, 0};
if (buff.data == NULL) {
buff.data = malloc(sizeof(char) * 4);
buff.alloc = 4;
}
if (isText(e)) {
String evStr = {malloc(sizeof(char) * 1), 0, 1};
appendKeyEvent(e, &evStr);
write(1, evStr.data, evStr.len);
add('\0', &evStr);
append(evStr.data, &buff);
free(evStr.data);
} else if (e == BS) {
if (buff.len) buff.len--; // TODO
} else if (e == (ALT | 'i')) {
add('\0', &buff);
printf("\r\n%s\r\n", buff.data);
buff.data--;
} else if (e == CR) {
printf("\r\n");
if (!buff.len) return 2;
add('\0', &buff);
const char* tmp = buff.data;
while (*tmp) {
TinEvent code = stringToCode(&tmp);
if (code == INPUT_IGNORED) {
fprintf(stderr, "\033[31mkeycombo invalid\033[0m\r\n");
buff.len = tmp - buff.data;
return 2;
} else queueTin(code);
}
buff.len--;
} else if (e == (CTRL | BS)) {
while (buff.len && buff.data[--buff.len] != ' ') {}
} else if (e == TAB) {
setTinFlags(USE_APP_MODE, 1);
return 1;
} else if (e == ESC) {
setTinFlags(USE_APP_MODE, 1);
free(buff.data);
buff.data = NULL;
return 1;
} else {
write(1, "\rno binding for: (\033[31m", 23);
printInputCode(e);
write(1, "\033[m)\r\n", 6);
}
return 2;
}
static int handleInputParsingMode(TinEvent e) {
static int separated = 0;
if (e == CR || (separated && !isText(e))) printf("\r\n");
if (e == (ALT | CR)) separated = !separated;
else if (e == ESC || e == (CTRL | 'q')) {
printf("\033[1;1H\033[2J");
return 0;
} else if (e == (SHIFT | ESC)) return 0;
else if (e == TAB) {
setTinFlags(USE_APP_MODE, 1);
return 2;
}
printInputCode(e);
return 1;
}
void printFlags() {
printf(
"\0337\033[H" // (re)set cursor
"Btn/Move Focus Rel/Rep Shifted/Base KP/Caps/NumL\r\n"
"( 0,1)=%d (2)=%d ( 3,4)=%d ( 5 ,6)=%d (7, 8 ,9)=%d\r\n",
FLAGS & 3, (FLAGS & SEND_FOCUS) / SEND_FOCUS,
(FLAGS & (USE_RELEASE | USE_REPEAT)) / USE_RELEASE,
(FLAGS & (USE_SHIFTED | USE_BASEKEY)) / USE_SHIFTED,
(FLAGS & (USE_KP | USE_CAPS | USE_NUMLOCK)) / USE_KP
);
fprintf(stderr, "\0338");
}
int main(int argc, char* argv[]) {
initTin(SEND_MOVE | SEND_FOCUS | USE_KP);
printFlags();
printf("\n");
int mode = 1;
while (mode) {
TinEvent e = getNextTin();
if (e == (CTRL | 'l')) {
printf("\033[1;1H\033[2J");
printFlags();
printf("\n\n");
}
if ((ALT | '0') <= e && e <= (ALT | '9')) {
unsigned f = (e ^ ALT) - '0';
setTinFlags(1 << (f < 3 ? f : f + 1), 1);
printFlags();
} else if ((e & ATTR_FILTER) == (CLICK | LMB | SINGLE_CLICK) && (getMouseRow(e) <= 2)) {
int box[][2] = {{1, 3}, {5, 8}, {10, 14}, {16, 18}, {20, 22},
{25, 31}, {33, 36}, {38, 39}, {41, 44}, {46, 49}};
int col = getMouseCol(e);
for (int i = 0; i < 10; i++) {
if (box[i][0] <= col && col <= box[i][1]) {
setTinFlags(1 << (i < 3 ? i : i + 1), 1);
printFlags();
break;
}
}
} else if (mode == 2) mode = handleStringToKeyMode(e);
else mode = handleInputParsingMode(e);
}
printInputCode(INPUT_IGNORED);
endTin();
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment