Browse Source

First terminal handling code

Kajetan Johannes Hammerle 3 months ago
parent
commit
bd113b30f1
9 changed files with 270 additions and 25 deletions
  1. 2 0
      CMakeLists.txt
  2. 1 19
      include/core/Logger.h
  3. 85 0
      include/core/Terminal.h
  4. 1 0
      include/core/Utility.h
  5. 117 0
      src/Terminal.c
  6. 4 0
      src/Utility.c
  7. 11 6
      test/Main.c
  8. 1 0
      test/Tests.h
  9. 48 0
      test/modules/TerminalTests.c

+ 2 - 0
CMakeLists.txt

@@ -16,6 +16,7 @@ set(SRC
     "src/Random.c"
     "src/ReadLine.c"
     "src/SpinLock.c"
+    "src/Terminal.c"
     "src/Test.c"
     "src/Thread.c"
     "src/ToString.c"
@@ -40,6 +41,7 @@ set(SRC_TESTS
     "test/modules/RandomTests.c"
     "test/modules/ReadLineTests.c"
     "test/modules/SpinLockTests.c"
+    "test/modules/TerminalTests.c"
     "test/modules/TestTests.c"
     "test/modules/UtilityTests.c"
     "test/modules/VectorTests.c"

+ 1 - 19
include/core/Logger.h

@@ -2,25 +2,7 @@
 #define CORE_LOGGER_H
 
 #include "core/Check.h"
-
-#define TERMINAL_BOLD "\33[1m"
-#define TERMINAL_BLACK "\33[30m"
-#define TERMINAL_RED "\33[31m"
-#define TERMINAL_GREEN "\33[32m"
-#define TERMINAL_YELLOW "\33[33m"
-#define TERMINAL_BLUE "\33[34m"
-#define TERMINAL_MAGENTA "\33[35m"
-#define TERMINAL_CYAN "\33[36m"
-#define TERMINAL_WHITE "\33[37m"
-#define TERMINAL_BRIGHT_BLACK "\33[90m"
-#define TERMINAL_BRIGHT_RED "\33[91m"
-#define TERMINAL_BRIGHT_GREEN "\33[92m"
-#define TERMINAL_BRIGHT_YELLOW "\33[93m"
-#define TERMINAL_BRIGHT_BLUE "\33[94m"
-#define TERMINAL_BRIGHT_MAGENTA "\33[95m"
-#define TERMINAL_BRIGHT_CYAN "\33[96m"
-#define TERMINAL_BRIGHT_WHITE "\33[97m"
-#define TERMINAL_RESET "\33[0m"
+#include "core/Terminal.h"
 
 typedef enum { LOG_NONE, LOG_ERROR, LOG_WARNING, LOG_INFO, LOG_DEBUG } LogLevel;
 

+ 85 - 0
include/core/Terminal.h

@@ -0,0 +1,85 @@
+#ifndef CORE_TERMINAL_H
+#define CORE_TERMINAL_H
+
+#include "core/Vector.h"
+
+#define ESC "\33["
+#define TERMINAL_RESET ESC "0m"
+#define TERMINAL_BOLD ESC "1m"
+
+#define TERMINAL_BLACK ESC "30m"
+#define TERMINAL_RED ESC "31m"
+#define TERMINAL_GREEN ESC "32m"
+#define TERMINAL_YELLOW ESC "33m"
+#define TERMINAL_BLUE ESC "34m"
+#define TERMINAL_MAGENTA ESC "35m"
+#define TERMINAL_CYAN ESC "36m"
+#define TERMINAL_WHITE ESC "37m"
+#define TERMINAL_BRIGHT_BLACK ESC "90m"
+#define TERMINAL_BRIGHT_RED ESC "91m"
+#define TERMINAL_BRIGHT_GREEN ESC "92m"
+#define TERMINAL_BRIGHT_YELLOW ESC "93m"
+#define TERMINAL_BRIGHT_BLUE ESC "94m"
+#define TERMINAL_BRIGHT_MAGENTA ESC "95m"
+#define TERMINAL_BRIGHT_CYAN ESC "96m"
+#define TERMINAL_BRIGHT_WHITE ESC "97m"
+
+#define TERMINAL_FG_BLACK TERMINAL_BLACK
+#define TERMINAL_FG_RED TERMINAL_RED
+#define TERMINAL_FG_GREEN TERMINAL_GREEN
+#define TERMINAL_FG_YELLOW TERMINAL_YELLOW
+#define TERMINAL_FG_BLUE TERMINAL_BLUE
+#define TERMINAL_FG_MAGENTA TERMINAL_MAGENTA
+#define TERMINAL_FG_CYAN TERMINAL_CYAN
+#define TERMINAL_FG_WHITE TERMINAL_WHITE
+#define TERMINAL_FG_BRIGHT_BLACK TERMINAL_BRIGHT_BLACK
+#define TERMINAL_FG_BRIGHT_RED TERMINAL_BRIGHT_RED
+#define TERMINAL_FG_BRIGHT_GREEN TERMINAL_BRIGHT_GREEN
+#define TERMINAL_FG_BRIGHT_YELLOW TERMINAL_BRIGHT_YELLOW
+#define TERMINAL_FG_BRIGHT_BLUE TERMINAL_BRIGHT_BLUE
+#define TERMINAL_FG_BRIGHT_MAGENTA TERMINAL_BRIGHT_MAGENTA
+#define TERMINAL_FG_BRIGHT_CYAN TERMINAL_BRIGHT_CYAN
+#define TERMINAL_FG_BRIGHT_WHITE TERMINAL_BRIGHT_WHITE
+
+#define TERMINAL_BG_BLACK ESC "40m"
+#define TERMINAL_BG_RED ESC "41m"
+#define TERMINAL_BG_GREEN ESC "42m"
+#define TERMINAL_BG_YELLOW ESC "43m"
+#define TERMINAL_BG_BLUE ESC "44m"
+#define TERMINAL_BG_MAGENTA ESC "45m"
+#define TERMINAL_BG_CYAN ESC "46m"
+#define TERMINAL_BG_WHITE ESC "47m"
+#define TERMINAL_BG_BRIGHT_BLACK ESC "100m"
+#define TERMINAL_BG_BRIGHT_RED ESC "101m"
+#define TERMINAL_BG_BRIGHT_GREEN ESC "102m"
+#define TERMINAL_BG_BRIGHT_YELLOW ESC "103m"
+#define TERMINAL_BG_BRIGHT_BLUE ESC "104m"
+#define TERMINAL_BG_BRIGHT_MAGENTA ESC "105m"
+#define TERMINAL_BG_BRIGHT_CYAN ESC "106m"
+#define TERMINAL_BG_BRIGHT_WHITE ESC "107m"
+
+#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_ARROW_DELETE 0x6'0000'0000lu
+
+void enterAlternativeTerminal();
+void leaveAlternativeTerminal();
+IntVector2 getTerminalSize();
+void clearTerminal();
+
+void hideCursor();
+void showCursor();
+void resetCursor();
+void moveCursorLeft(int i);
+void moveCursorRight(int i);
+void moveCursorUp(int i);
+void moveCursorDown(int i);
+
+bool enterRawTerminal();
+bool leaveRawTerminal();
+u64 getRawChar();
+
+#endif

+ 1 - 0
include/core/Utility.h

@@ -50,6 +50,7 @@ void coreFree(void* p);
 #define printMemoryReport()
 #endif
 
+bool sleepMillis(i64 millis);
 bool sleepNanos(i64 nanos);
 i64 getNanos(void);
 

+ 117 - 0
src/Terminal.c

@@ -0,0 +1,117 @@
+#include "core/Terminal.h"
+
+#include <stdio.h>
+#include <sys/ioctl.h>
+#include <termios.h>
+#include <unistd.h>
+
+#define ESC "\33["
+#define esc(s) fputs(ESC s, stdout)
+
+static struct termios originalTerminal;
+
+void enterAlternativeTerminal() {
+    esc("?1049h");
+}
+
+void leaveAlternativeTerminal() {
+    esc("?1049l");
+}
+
+void clearTerminal() {
+    esc("2J");
+}
+
+IntVector2 getTerminalSize() {
+    struct winsize w;
+    if(ioctl(0, TIOCGWINSZ, &w)) {
+        return IV(0, 0);
+    }
+    return IV(w.ws_col, w.ws_row);
+}
+
+void hideCursor() {
+    esc("?25l");
+}
+
+void showCursor() {
+    esc("?25h");
+}
+
+void resetCursor() {
+    esc("H");
+}
+
+void moveCursorLeft(int i) {
+    printf(ESC "%dD", i);
+}
+
+void moveCursorRight(int i) {
+    printf(ESC "%dC", i);
+}
+
+void moveCursorUp(int i) {
+    printf(ESC "%dA", i);
+}
+
+void moveCursorDown(int i) {
+    printf(ESC "%dB", i);
+}
+
+bool enterRawTerminal() {
+    if(tcgetattr(STDIN_FILENO, &originalTerminal)) {
+        return true;
+    }
+    struct termios raw = originalTerminal;
+    raw.c_iflag &= ~(tcflag_t)(ICRNL | IXON);
+    raw.c_lflag &= ~(tcflag_t)(ECHO | ICANON | IEXTEN | ISIG);
+    raw.c_cc[VMIN] = 0;
+    raw.c_cc[VTIME] = 0;
+    return tcsetattr(STDIN_FILENO, TCSANOW, &raw);
+}
+
+bool leaveRawTerminal() {
+    return tcsetattr(STDIN_FILENO, TCSAFLUSH, &originalTerminal);
+}
+
+static u8 readChar() {
+    u8 c = 0;
+    ssize_t bytes = read(STDIN_FILENO, &c, 1);
+    return bytes <= 0 ? '\0' : c;
+}
+
+static u64 readEscapeSequence() {
+    if(readChar() != '[') {
+        return TERMINAL_KEY_UNKNOWN;
+    }
+    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_ARROW_DELETE;
+            }
+            break;
+    }
+    return TERMINAL_KEY_UNKNOWN;
+}
+
+static u64 readUTF8(u64 c) {
+    if((c & 0b1110'0000) == 0b1100'0000) {
+        return ((c & 0b0001'1111) << 6) | (readChar() & 0b0011'1111);
+    }
+    // TODO: 3/4 byte UTF8 characters
+    return c;
+}
+
+u64 getRawChar() {
+    u8 c = readChar();
+    if(c > 127) {
+        return readUTF8(c);
+    } else if(c == 27) {
+        return readEscapeSequence();
+    }
+    return c;
+}

+ 4 - 0
src/Utility.c

@@ -230,6 +230,10 @@ bool sleepNanos(i64 nanos) {
     return thrd_sleep(&t, nullptr) != 0;
 }
 
+bool sleepMillis(i64 millis) {
+    return sleepNanos(millis * 1'000'000l);
+}
+
 i64 getNanos(void) {
     struct timespec ts;
     if(timespec_get(&ts, TIME_UTC) == 0 || TIME_GET_FAIL) {

+ 11 - 6
test/Main.c

@@ -1,6 +1,7 @@
 #include <locale.h>
 #include <stdio.h>
 #include <string.h>
+#include <threads.h>
 
 #include "Tests.h"
 #include "core/Logger.h"
@@ -16,12 +17,13 @@ static void onExit(int code, void* data) {
 
 int main(int argAmount, const char** args) {
     if(argAmount >= 2 && strcmp(args[1], "help") == 0) {
-        puts("alloc");
-        puts("realloc");
-        puts("pre_canary");
-        puts("post_canary");
-        puts("test;ignore");
-        puts("light;readLineTest");
+        // puts("alloc");
+        // puts("realloc");
+        // puts("pre_canary");
+        // puts("post_canary");
+        // puts("test;ignore");
+        // puts("light;readLineTest");
+        puts("terminal");
         return 0;
     }
     setlocale(LC_ALL, "en_US.utf8");
@@ -40,6 +42,9 @@ int main(int argAmount, const char** args) {
         } else if(strcmp(args[i], "test") == 0) {
             testTest();
             return 0;
+        } else if(strcmp(args[i], "terminal") == 0) {
+            testTerminal();
+            return 0;
         }
     }
 

+ 1 - 0
test/Tests.h

@@ -21,6 +21,7 @@ void testQueue(void);
 void testRandom(bool light);
 void testReadLine(void);
 void testSpinLock(void);
+void testTerminal(void);
 void testTest(void);
 void testUtility(bool light);
 void testVector(void);

+ 48 - 0
test/modules/TerminalTests.c

@@ -0,0 +1,48 @@
+#include <stdio.h>
+
+#include "../Tests.h"
+#include "core/Logger.h"
+#include "core/Terminal.h"
+#include "core/Utility.h"
+
+#define KEY_CASE(key)           \
+    case key: puts(#key); break
+
+void testTerminal(void) {
+    enterAlternativeTerminal();
+
+    resetCursor();
+    hideCursor();
+    puts("Hi there");
+    LOG_WARNING("This is a test");
+    IntVector2 v = getTerminalSize();
+    printf("%d %d\n", v.x, v.y);
+    clearTerminal();
+    resetCursor();
+    showCursor();
+    puts("the final!!!");
+    sleepMillis(500);
+
+    enterRawTerminal();
+
+    while(true) {
+        u64 c = getRawChar();
+        if(c == 'q') {
+            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_ARROW_DELETE);
+            default: printf("%lu\n", c); break;
+        }
+    }
+
+    leaveRawTerminal();
+
+    leaveAlternativeTerminal();
+}