Browse Source

Add fancy escape sequence support

Kajetan Johannes Hammerle 3 weeks ago
parent
commit
d23022dfc6
3 changed files with 189 additions and 24 deletions
  1. 37 6
      include/core/Terminal.h
  2. 115 10
      src/Terminal.c
  3. 37 8
      test/modules/TerminalTests.c

+ 37 - 6
include/core/Terminal.h

@@ -57,13 +57,34 @@
 #define TERMINAL_BG_BRIGHT_MAGENTA ESC "105m"
 #define TERMINAL_BG_BRIGHT_CYAN ESC "106m"
 #define TERMINAL_BG_BRIGHT_WHITE ESC "107m"
-
+// keycodes
 #define TERMINAL_KEY_UNKNOWN 0x1'0000'0000lu
-#define TERMINAL_KEY_ARROW_LEFT 0x2'0000'0000lu
-#define TERMINAL_KEY_ARROW_RIGHT 0x3'0000'0000lu
-#define TERMINAL_KEY_ARROW_UP 0x4'0000'0000lu
-#define TERMINAL_KEY_ARROW_DOWN 0x5'0000'0000lu
-#define TERMINAL_KEY_DELETE 0x6'0000'0000lu
+// default keycodes
+#define TERMINAL_KEY_ARROW_LEFT 0x1'0000'0001lu
+#define TERMINAL_KEY_ARROW_RIGHT 0x1'0000'0002lu
+#define TERMINAL_KEY_ARROW_UP 0x1'0000'0003lu
+#define TERMINAL_KEY_ARROW_DOWN 0x1'0000'0004lu
+#define TERMINAL_KEY_DELETE 0x1'0000'0005lu
+#define TERMINAL_KEY_F1 0x1'0000'0006lu
+#define TERMINAL_KEY_F2 0x1'0000'0007lu
+#define TERMINAL_KEY_F3 0x1'0000'0008lu
+#define TERMINAL_KEY_F4 0x1'0000'0009lu
+#define TERMINAL_KEY_F5 0x1'0000'000Alu
+#define TERMINAL_KEY_F6 0x1'0000'000Blu
+#define TERMINAL_KEY_F7 0x1'0000'000Clu
+#define TERMINAL_KEY_F8 0x1'0000'000Dlu
+#define TERMINAL_KEY_F9 0x1'0000'000Elu
+#define TERMINAL_KEY_F10 0x1'0000'000Flu
+#define TERMINAL_KEY_F11 0x1'0000'0010lu
+#define TERMINAL_KEY_F12 0x1'0000'0011lu
+#define TERMINAL_KEY_PAGE_UP 0x1'0000'0012lu
+#define TERMINAL_KEY_PAGE_DOWN 0x1'0000'0013lu
+#define TERMINAL_KEY_HOME 0x1'0000'0014lu
+#define TERMINAL_KEY_END 0x1'0000'0015lu
+// key modifiers
+#define TERMINAL_KEY_CTRL 0x2'0000'0000lu
+#define TERMINAL_KEY_SHIFT 0x4'0000'0000lu
+#define TERMINAL_KEY_ALT 0x8'0000'0000lu
 
 void enterAlternativeTerminal();
 void leaveAlternativeTerminal();
@@ -82,5 +103,15 @@ void moveCursorDown(int i);
 bool enterRawTerminal();
 bool leaveRawTerminal();
 u64 getRawChar();
+bool isSpecialChar(u64 u);
+
+typedef struct {
+    u64 key;
+    bool control;
+    bool shift;
+    bool alt;
+} SpecialChar;
+
+SpecialChar convertToSpecialChar(u64 u);
 
 #endif

+ 115 - 10
src/Terminal.c

@@ -5,6 +5,7 @@
 #include <termios.h>
 #include <unistd.h>
 
+#include "core/Logger.h"
 #include "core/Unicode.h"
 
 #define ESC "\33["
@@ -12,6 +13,78 @@
 
 static struct termios originalTerminal;
 
+typedef struct {
+    const u8* path;
+    u64 mapping;
+} EscapeSequence;
+
+#define K_CTRL TERMINAL_KEY_CTRL
+#define K_SHIFT TERMINAL_KEY_SHIFT
+#define K_ALT TERMINAL_KEY_ALT
+
+#define KEY_WITH_MODIFIER(c, m, n) {c, m | TERMINAL_KEY_##n}
+#define KEYS_WITH_MODIFIER(c, m)                       \
+    {u8"[1;" c "A", m | TERMINAL_KEY_ARROW_UP},        \
+        {u8"[1;" c "B", m | TERMINAL_KEY_ARROW_DOWN},  \
+        {u8"[1;" c "C", m | TERMINAL_KEY_ARROW_RIGHT}, \
+        {u8"[1;" c "D", m | TERMINAL_KEY_ARROW_LEFT},  \
+        {u8"[3;" c "~", m | TERMINAL_KEY_DELETE},      \
+        {u8"[1;" c "P", m | TERMINAL_KEY_F1},          \
+        {u8"[1;" c "Q", m | TERMINAL_KEY_F2},          \
+        {u8"[1;" c "R", m | TERMINAL_KEY_F3},          \
+        {u8"[1;" c "S", m | TERMINAL_KEY_F4},          \
+        {u8"[15;" c "~", m | TERMINAL_KEY_F5},         \
+        {u8"[17;" c "~", m | TERMINAL_KEY_F6},         \
+        {u8"[18;" c "~", m | TERMINAL_KEY_F7},         \
+        {u8"[19;" c "~", m | TERMINAL_KEY_F8},         \
+        {u8"[20;" c "~", m | TERMINAL_KEY_F9},         \
+        {u8"[21;" c "~", m | TERMINAL_KEY_F10},        \
+        {u8"[23;" c "~", m | TERMINAL_KEY_F11},        \
+        {u8"[24;" c "~", m | TERMINAL_KEY_F12},        \
+        {u8"[5;" c "~", m | TERMINAL_KEY_PAGE_UP},     \
+        {u8"[6;" c "~", m | TERMINAL_KEY_PAGE_DOWN},   \
+        {u8"[1;" c "H", m | TERMINAL_KEY_HOME}, {      \
+        u8"[1;" c "F", m | TERMINAL_KEY_END            \
+    }
+
+static const EscapeSequence ESCAPE_SEQUENCES[] = {
+    {u8"[A", TERMINAL_KEY_ARROW_UP},
+    {u8"[B", TERMINAL_KEY_ARROW_DOWN},
+    {u8"[C", TERMINAL_KEY_ARROW_RIGHT},
+    {u8"[D", TERMINAL_KEY_ARROW_LEFT},
+    {u8"[3~", TERMINAL_KEY_DELETE},
+    {u8"OP", TERMINAL_KEY_F1},
+    {u8"[[A", TERMINAL_KEY_F1},
+    {u8"OQ", TERMINAL_KEY_F2},
+    {u8"[[B", TERMINAL_KEY_F2},
+    {u8"OR", TERMINAL_KEY_F3},
+    {u8"[[C", TERMINAL_KEY_F3},
+    {u8"OS", TERMINAL_KEY_F4},
+    {u8"[[D", TERMINAL_KEY_F4},
+    {u8"[15~", TERMINAL_KEY_F5},
+    {u8"[[E", TERMINAL_KEY_F5},
+    {u8"[17~", TERMINAL_KEY_F6},
+    {u8"[18~", TERMINAL_KEY_F7},
+    {u8"[19~", TERMINAL_KEY_F8},
+    {u8"[20~", TERMINAL_KEY_F9},
+    {u8"[21~", TERMINAL_KEY_F10},
+    {u8"[23~", TERMINAL_KEY_F11},
+    {u8"[24~", TERMINAL_KEY_F12},
+    {u8"[5~", TERMINAL_KEY_PAGE_UP},
+    {u8"[6~", TERMINAL_KEY_PAGE_DOWN},
+    {u8"[H", TERMINAL_KEY_HOME},
+    {u8"[1~", TERMINAL_KEY_HOME},
+    {u8"[F", TERMINAL_KEY_END},
+    {u8"[4~", TERMINAL_KEY_END},
+    KEYS_WITH_MODIFIER("2", K_SHIFT),
+    KEYS_WITH_MODIFIER("3", K_ALT),
+    KEYS_WITH_MODIFIER("4", K_ALT | K_SHIFT),
+    KEYS_WITH_MODIFIER("5", K_CTRL),
+    KEYS_WITH_MODIFIER("6", K_CTRL | K_SHIFT),
+    KEYS_WITH_MODIFIER("7", K_CTRL | K_ALT),
+    KEYS_WITH_MODIFIER("8", K_CTRL | K_ALT | K_SHIFT),
+};
+
 void enterAlternativeTerminal() {
     esc("?1049h");
 }
@@ -86,21 +159,40 @@ static u8 readChar() {
     return bytes <= 0 ? '\0' : c;
 }
 
+static bool couldMatch(const u8* s, const u8* e, size_t l) {
+    while(l > 0 && *s != 0 && *e != 0 && *s == *e) {
+        s++;
+        e++;
+        l--;
+    }
+    return l == 0;
+}
+
 static u64 readEscapeSequence() {
-    if(readChar() != '[') {
-        return TERMINAL_KEY_UNKNOWN;
+    u8 s[10] = {0};
+    s[0] = readChar();
+    s[1] = readChar();
+    if(s[1] == 0) {
+        return s[0] | K_ALT;
     }
-    switch(readChar()) {
-        case 'A': return TERMINAL_KEY_ARROW_UP;
-        case 'B': return TERMINAL_KEY_ARROW_DOWN;
-        case 'C': return TERMINAL_KEY_ARROW_RIGHT;
-        case 'D': return TERMINAL_KEY_ARROW_LEFT;
-        case '3':
-            if(readChar() == '~') {
-                return TERMINAL_KEY_DELETE;
+    size_t l = 2;
+    while(true) {
+        bool matchFound = false;
+        for(size_t i = 0; i < ARRAY_LENGTH(ESCAPE_SEQUENCES); i++) {
+            const EscapeSequence* e = ESCAPE_SEQUENCES + i;
+            if(couldMatch(s, e->path, l)) {
+                matchFound = true;
+                if(e->path[l] == 0) {
+                    return e->mapping;
+                }
             }
+        }
+        if(!matchFound || l >= ARRAY_LENGTH(s)) {
             break;
+        }
+        s[l++] = readChar();
     }
+    LOG_WARNING("Unknown escape sequence starting with '%s'", s);
     return TERMINAL_KEY_UNKNOWN;
 }
 
@@ -121,3 +213,16 @@ u64 getRawChar() {
     }
     return c;
 }
+
+bool isSpecialChar(u64 u) {
+    return u >= TERMINAL_KEY_UNKNOWN;
+}
+
+SpecialChar convertToSpecialChar(u64 u) {
+    SpecialChar c = {0};
+    c.shift = TERMINAL_KEY_SHIFT & u;
+    c.control = TERMINAL_KEY_CTRL & u;
+    c.alt = TERMINAL_KEY_ALT & u;
+    c.key = u & 0x1'FFFF'FFFF;
+    return c;
+}

+ 37 - 8
test/modules/TerminalTests.c

@@ -60,14 +60,43 @@ void testInteractiveTerminal(void) {
             break;
         } else if(c == 0) {
             continue;
-        }
-        switch(c) {
-            KEY_CASE(TERMINAL_KEY_ARROW_LEFT);
-            KEY_CASE(TERMINAL_KEY_ARROW_RIGHT);
-            KEY_CASE(TERMINAL_KEY_ARROW_UP);
-            KEY_CASE(TERMINAL_KEY_ARROW_DOWN);
-            KEY_CASE(TERMINAL_KEY_DELETE);
-            default: printf("%lu\n", c); break;
+        } else if(isSpecialChar(c)) {
+            SpecialChar sc = convertToSpecialChar(c);
+            if(sc.shift) {
+                printf("SHIFT + ");
+            }
+            if(sc.control) {
+                printf("CTRL + ");
+            }
+            if(sc.alt) {
+                printf("ALT + ");
+            }
+            switch(sc.key) {
+                KEY_CASE(TERMINAL_KEY_ARROW_LEFT);
+                KEY_CASE(TERMINAL_KEY_ARROW_RIGHT);
+                KEY_CASE(TERMINAL_KEY_ARROW_UP);
+                KEY_CASE(TERMINAL_KEY_ARROW_DOWN);
+                KEY_CASE(TERMINAL_KEY_DELETE);
+                KEY_CASE(TERMINAL_KEY_F1);
+                KEY_CASE(TERMINAL_KEY_F2);
+                KEY_CASE(TERMINAL_KEY_F3);
+                KEY_CASE(TERMINAL_KEY_F4);
+                KEY_CASE(TERMINAL_KEY_F5);
+                KEY_CASE(TERMINAL_KEY_F6);
+                KEY_CASE(TERMINAL_KEY_F7);
+                KEY_CASE(TERMINAL_KEY_F8);
+                KEY_CASE(TERMINAL_KEY_F9);
+                KEY_CASE(TERMINAL_KEY_F10);
+                KEY_CASE(TERMINAL_KEY_F11);
+                KEY_CASE(TERMINAL_KEY_F12);
+                KEY_CASE(TERMINAL_KEY_PAGE_UP);
+                KEY_CASE(TERMINAL_KEY_PAGE_DOWN);
+                KEY_CASE(TERMINAL_KEY_HOME);
+                KEY_CASE(TERMINAL_KEY_END);
+                default: printf("%lu\n", sc.key); break;
+            }
+        } else {
+            printf("%lu\n", c);
         }
     }