git.y1.nz

SameBoy

Accurate GB/GBC emulator
download: https://git.y1.nz/archives/sameboy.tar.gz
README | Files | Log | Refs | LICENSE

SDL/console.c

      1 #include "console.h"
      2 #include <stdio.h>
      3 #include <unistd.h>
      4 #include <stdlib.h>
      5 #include <stdbool.h>
      6 #include <errno.h>
      7 #include <string.h>
      8 #include <signal.h>
      9 #include <ctype.h>
     10 #include <assert.h>
     11 #include <pthread.h>
     12 #include <stdarg.h>
     13 #include <stdint.h>
     14 
     15 #define ESC(x) "\x1B" x
     16 #define CSI(x) ESC("[" x)
     17 #define SGR(x) CSI(x "m")
     18 
     19 static bool initialized = false;
     20 static bool no_csi = false;
     21 typedef struct listent_s listent_t;
     22 
     23 struct listent_s {
     24     listent_t *prev;
     25     listent_t *next;
     26     char content[];
     27 };
     28 
     29 typedef struct {
     30     listent_t *first;
     31     listent_t *last;
     32 } fifo_t;
     33 
     34 static fifo_t lines;
     35 static fifo_t history;
     36 
     37 static void remove_entry(fifo_t *fifo, listent_t *entry)
     38 {
     39     if (fifo->last == entry) {
     40         fifo->last = entry->prev;
     41     }
     42     if (fifo->first == entry) {
     43         fifo->first = entry->next;
     44     }
     45     if (entry->next) {
     46         entry->next->prev = entry->prev;
     47     }
     48     if (entry->prev) {
     49         entry->prev->next = entry->next;
     50     }
     51     free(entry);
     52 }
     53 
     54 static void add_entry(fifo_t *fifo, const char *content)
     55 {
     56     size_t length = strlen(content);
     57     listent_t *entry = malloc(sizeof(*entry) + length + 1);
     58     entry->next = NULL;
     59     entry->prev = fifo->last;
     60     memcpy(entry->content, content, length);
     61     entry->content[length] = 0;
     62     if (fifo->last) {
     63         fifo->last->next = entry;
     64     }
     65     fifo->last = entry;
     66     if (!fifo->first) {
     67         fifo->first = entry;
     68     }
     69 }
     70 
     71 static listent_t *reverse_find(listent_t *entry, const char *string, bool exact)
     72 {
     73     while (entry) {
     74         if (exact && strcmp(entry->content, string) == 0) {
     75             return entry;
     76         }
     77         if (!exact && strstr(entry->content, string)) {
     78             return entry;
     79         }
     80         entry = entry->prev;
     81     }
     82     return NULL;
     83 }
     84 
     85 static bool is_term(void)
     86 {
     87     if (!isatty(STDIN_FILENO) || !isatty(STDOUT_FILENO)) return false;
     88 #ifdef _WIN32
     89     unsigned long input_mode, output_mode;
     90     bool has_con_output;
     91     
     92     HANDLE stdin_handle = GetStdHandle(STD_INPUT_HANDLE);
     93     HANDLE stdout_handle = GetStdHandle(STD_OUTPUT_HANDLE);
     94     
     95     GetConsoleMode(stdin_handle, &input_mode);
     96     has_con_output = GetConsoleMode(stdout_handle, &output_mode);
     97     if (!has_con_output) {
     98         return false; // stdout has been redirected to a file or pipe
     99     }
    100     
    101     SetConsoleMode(stdin_handle, ENABLE_VIRTUAL_TERMINAL_INPUT);
    102     SetConsoleMode(stdout_handle, ENABLE_WRAP_AT_EOL_OUTPUT | ENABLE_PROCESSED_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING);
    103     
    104     CONSOLE_SCREEN_BUFFER_INFO before = {0,};
    105     GetConsoleScreenBufferInfo(stdout_handle, &before);
    106     
    107     printf(SGR("0"));
    108     
    109     CONSOLE_SCREEN_BUFFER_INFO after = {0,};
    110     GetConsoleScreenBufferInfo(stdout_handle, &after);
    111     
    112     SetConsoleMode(stdin_handle, input_mode);
    113     SetConsoleMode(stdout_handle, output_mode);
    114 
    115     if (before.dwCursorPosition.X != after.dwCursorPosition.X ||
    116         before.dwCursorPosition.Y != after.dwCursorPosition.Y) {
    117         printf("\r      \r");
    118         no_csi = true;
    119     }
    120     
    121     return true;
    122 #else
    123     return getenv("TERM");
    124 #endif
    125 }
    126 
    127 static unsigned width, height;
    128 
    129 static char raw_getc(void)
    130 {
    131 #ifdef _WIN32
    132     char c;
    133     unsigned long ret;
    134     ReadFile(GetStdHandle(STD_INPUT_HANDLE), &c, 1, &ret, NULL);
    135 #else
    136     ssize_t ret;
    137     char c;
    138     
    139     do {
    140         ret = read(STDIN_FILENO, &c, 1);
    141     } while (ret == -1 && errno == EINTR);
    142 #endif
    143     return ret == 1? c : EOF;
    144 }
    145 
    146 #ifdef _WIN32
    147 #pragma clang diagnostic ignored "-Wmacro-redefined"
    148 #include <Windows.h>
    149 
    150 static void update_size(void)
    151 {
    152     CONSOLE_SCREEN_BUFFER_INFO csbi;
    153     GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi);
    154     width = csbi.srWindow.Right - csbi.srWindow.Left + 1;
    155     height = csbi.srWindow.Bottom - csbi.srWindow.Top + 1;
    156 }
    157 
    158 static unsigned long input_mode, output_mode;
    159 
    160 static void cleanup(void)
    161 {
    162     if (!no_csi) {
    163         printf(CSI("!p")); // reset
    164     }
    165     fflush(stdout);
    166     SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), input_mode);
    167     SetConsoleMode(GetStdHandle(STD_OUTPUT_HANDLE), output_mode);
    168 }
    169 
    170 static bool initialize(void)
    171 {
    172     if (!is_term()) return false;
    173     update_size();
    174     if (width == 0 || height == 0) {
    175         return false;
    176     }
    177     
    178     static bool once = false;
    179     if (!once) {
    180         atexit(cleanup);
    181         GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), &input_mode);
    182         GetConsoleMode(GetStdHandle(STD_OUTPUT_HANDLE), &output_mode);
    183         once = true;
    184     }
    185     if (no_csi) {
    186         initialized = true;
    187         return true;
    188     }
    189     SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), ENABLE_VIRTUAL_TERMINAL_INPUT);
    190     SetConsoleMode(GetStdHandle(STD_OUTPUT_HANDLE), ENABLE_WRAP_AT_EOL_OUTPUT | ENABLE_PROCESSED_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING);
    191 
    192     fprintf(stdout, CSI("%dB") "\n" CSI("A") ESC("7") CSI("B"), height);
    193     
    194     fflush(stdout);
    195     initialized = true;
    196     return true;
    197 }
    198 #else
    199 #include <sys/ioctl.h>
    200 
    201 static void update_size(void)
    202 {
    203     struct winsize winsize;
    204     ioctl(STDOUT_FILENO, TIOCGWINSZ, &winsize);
    205     width = winsize.ws_col;
    206     height = winsize.ws_row;
    207 }
    208 
    209 static void terminal_resized(int ignored)
    210 {
    211     update_size();
    212 }
    213 
    214 #include <termios.h>
    215 static struct termios terminal;
    216 
    217 
    218 static void cleanup(void)
    219 {
    220     if (!no_csi) {
    221         printf(CSI("!p")); // reset
    222     }
    223     tcsetattr(STDIN_FILENO, TCSAFLUSH, &terminal);
    224     fflush(stdout);
    225 }
    226 
    227 static bool initialize(void)
    228 {
    229     if (!is_term()) return false;
    230     update_size();
    231     if (width == 0 || height == 0) {
    232         return false;
    233     }
    234     
    235     static bool once = false;
    236     if (!once) {
    237         atexit(cleanup);
    238         signal(SIGWINCH, terminal_resized);
    239         tcgetattr(STDIN_FILENO, &terminal);
    240 #ifdef _WIN32
    241         _setmode(STDIN_FILENO, _O_TEXT);
    242 #endif
    243         once = true;
    244     }
    245     struct termios raw_terminal;
    246     raw_terminal = terminal;
    247     raw_terminal.c_lflag = 0;
    248     tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw_terminal);
    249     
    250     printf(CSI("%dB") "\n" CSI("A") ESC("7") CSI("B"), height);
    251     
    252     fflush(stdout);
    253     initialized = true;
    254     return true;
    255 }
    256 #endif
    257 
    258 static struct {
    259     char *content;
    260     size_t allocation_size;
    261     size_t length;
    262     size_t position;
    263     size_t scroll;
    264     bool reverse_search;
    265     listent_t *search_line;
    266 } line;
    267 
    268 #define CTL(x) ((x) - 'A' + 1)
    269 
    270 static const char *prompt = "";
    271 static size_t prompt_length = 0;
    272 static bool repeat_empty = false;
    273 
    274 static bool redraw_prompt(bool force)
    275 {
    276     if (no_csi) return true;
    277     
    278     if (line.reverse_search) {
    279         if (!force) return false;
    280         if (line.length == 0) {
    281             printf("\r" CSI("K") "%s" SGR("2") "Reverse Search..." SGR("0") CSI("%zuG"), prompt, prompt_length + 1);
    282             return true;
    283         }
    284         if (!line.search_line) {
    285             printf("\r" CSI("K") "%s" SGR("1") "%s" SGR("0"), prompt, line.content);
    286             return true;
    287         }
    288         const char *loc = strstr(line.search_line->content, line.content);
    289         printf("\r" CSI("K") "%s" "%.*s" SGR("1") "%s" SGR("0") "%s" CSI("%uG"),
    290                prompt,
    291                (int)(loc - line.search_line->content),
    292                line.search_line->content,
    293                line.content,
    294                loc + line.length,
    295                (unsigned)(loc - line.search_line->content + line.length + prompt_length + 1));
    296         return true;
    297     }
    298     
    299     size_t max = width - 1 - prompt_length;
    300     
    301     if (line.scroll && line.length <= max) {
    302         line.scroll = 0;
    303         force = true;
    304     }
    305     
    306     if (line.scroll > line.length - max) {
    307         line.scroll = line.length - max;
    308         force = true;
    309     }
    310     
    311     if (line.position < line.scroll + 1 && line.position) {
    312         line.scroll = line.position - 1;
    313         force = true;
    314     }
    315     
    316     if (line.position == 0 && line.scroll) {
    317         line.scroll = 0;
    318         force = true;
    319     }
    320     
    321     if (line.position > line.scroll + max) {
    322         line.scroll = line.position - max;
    323         force = true;
    324     }
    325     
    326     if (!force && line.length <= max) {
    327         return false;
    328     }
    329     
    330     if (line.length <= max) {
    331         printf("\r" CSI("K") "%s%s" CSI("%uG"), prompt, line.content, (unsigned)(line.position + prompt_length + 1));
    332         return true;
    333     }
    334     
    335     size_t left = max;
    336     const char *string = line.content + line.scroll;
    337     printf("\r" CSI("K") "%s", prompt);
    338     if (line.scroll) {
    339         printf(SGR("2") "%c" SGR("0"), *string);
    340         string++;
    341         left--;
    342     }
    343     if (line.scroll + max == line.length) {
    344         printf("%s", string);
    345     }
    346     else {
    347         printf("%.*s", (int)(left - 1), string);
    348         string += left;
    349         printf(SGR("2") "%c" SGR("0"), *string);
    350     }
    351     printf(CSI("%uG"), (unsigned)(line.position - line.scroll + prompt_length + 1));
    352     
    353     return true;
    354 }
    355 
    356 static void set_position(size_t position)
    357 {
    358     if (position > line.length) {
    359         printf("\a");
    360         return;
    361     }
    362     line.position = position;
    363     if (!redraw_prompt(false)) {
    364         printf(CSI("%uG"), (unsigned)(position + prompt_length + 1));
    365     }
    366 }
    367 
    368 static void set_line(const char *content)
    369 {
    370     line.length = strlen(content);
    371     if (line.length + 1 > line.allocation_size) {
    372         line.content = realloc(line.content, line.length + 1);
    373         line.allocation_size = line.length + 1;
    374     }
    375     else if (line.allocation_size > 256 && line.length < 128) {
    376         line.content = realloc(line.content, line.length + 1);
    377         line.allocation_size = line.length + 1;
    378     }
    379     line.position = line.length;
    380     strcpy(line.content, content);
    381     redraw_prompt(true);
    382 }
    383 
    384 static void insert(const char *string)
    385 {
    386     size_t insertion_length = strlen(string);
    387     size_t new_length = insertion_length + line.length;
    388     bool need_realloc = false;
    389     while (line.allocation_size < new_length + 1) {
    390         line.allocation_size *= 2;
    391         need_realloc = true;
    392     }
    393     if (need_realloc) {
    394         line.content = realloc(line.content, line.allocation_size);
    395     }
    396     memmove(line.content + line.position + insertion_length,
    397             line.content + line.position,
    398             line.length - line.position);
    399     memcpy(line.content + line.position, string, insertion_length);
    400     line.position += insertion_length;
    401     line.content[new_length] = 0;
    402     line.length = new_length;
    403     if (!redraw_prompt(line.position != line.length)) {
    404         printf("%s", string);
    405     }
    406 }
    407 
    408 static void delete(size_t size, bool forward)
    409 {
    410     if (line.length < size) {
    411         printf("\a");
    412         return;
    413     }
    414     if (forward) {
    415         if (line.position > line.length - size) {
    416             printf("\a");
    417             return;
    418         }
    419         else {
    420             line.position += size;
    421         }
    422     }
    423     else if (line.position < size) {
    424         printf("\a");
    425         return;
    426     }
    427     memmove(line.content + line.position - size,
    428             line.content + line.position,
    429             line.length - line.position);
    430     line.length -= size;
    431     line.content[line.length] = 0;
    432     line.position -= size;
    433     
    434     if (!redraw_prompt(line.position != line.length)) {
    435         printf(CSI("%uG") CSI("K"),
    436                (unsigned)(line.position + prompt_length + 1));
    437     }
    438 }
    439 
    440 static void move_word(bool forward)
    441 {
    442     signed offset = forward? 1 : -1;
    443     size_t end = forward? line.length : 0;
    444     signed check_offset = forward? 0 : -1;
    445     if (line.position == end) {
    446         printf("\a");
    447         return;
    448     }
    449     line.position += offset;
    450     while (line.position != end && isalnum(line.content[line.position + check_offset])) {
    451         line.position += offset;
    452     }
    453     if (!redraw_prompt(false)) {
    454         printf(CSI("%uG"), (unsigned)(line.position + prompt_length + 1));
    455     }
    456 }
    457 
    458 static void delete_word(bool forward)
    459 {
    460     size_t original_pos = line.position;
    461     signed offset = forward? 1 : -1;
    462     size_t end = forward? line.length : 0;
    463     signed check_offset = forward? 0 : -1;
    464     if (line.position == end) {
    465         printf("\a");
    466         return;
    467     }
    468     line.position += offset;
    469     while (line.position != end && isalnum(line.content[line.position + check_offset])) {
    470         line.position += offset;
    471     }
    472     if (forward) {
    473         delete(line.position - original_pos, false);
    474     }
    475     else {
    476         delete(original_pos - line.position, true);
    477     }
    478 }
    479 
    480 #define MOD_ALT(x) (0x100 | x)
    481 #define MOD_SHIFT(x) (0x200 | x)
    482 #define MOD_CTRL(x) (0x400 | x)
    483 #define MOD_SPECIAL(x) (0x800 | x)
    484 
    485 static unsigned get_extended_key(void)
    486 {
    487     unsigned modifiers = 0;
    488     char c = 0;
    489 restart:
    490     c = raw_getc();
    491     if (c == 0x1B) {
    492         modifiers = MOD_SHIFT(MOD_ALT(0));
    493         goto restart;
    494     }
    495     else if (c != '[' && c != 'O') {
    496         return MOD_ALT(c);
    497     }
    498     unsigned ret = 0;
    499     while (true) {
    500         c = raw_getc();
    501         if (c >= '0' && c <= '9') {
    502             ret = ret * 10 + c - '0';
    503         }
    504         else if (c == ';') {
    505             if (ret == 1) {
    506                 modifiers |= MOD_ALT(0);
    507             }
    508             else if (ret == 2) {
    509                 modifiers |= MOD_SHIFT(0);
    510             }
    511             else if (ret == 5) {
    512                 modifiers |= MOD_CTRL(0);
    513             }
    514             ret = 0;
    515         }
    516         else if (c == '~') {
    517             return MOD_SPECIAL(ret) | modifiers;
    518         }
    519         else {
    520             if (ret == 1) {
    521                 modifiers |= MOD_ALT(0);
    522             }
    523             else if (ret == 2) {
    524                 modifiers |= MOD_SHIFT(0);
    525             }
    526             else if (ret == 5) {
    527                 modifiers |= MOD_CTRL(0);
    528             }
    529             return c | modifiers;
    530         }
    531     }
    532 }
    533 
    534 #define SWAP(x, y) do {typeof(*(x)) _tmp = *(x); *(x) = *(y);*(y) = _tmp;} while (0)
    535 static pthread_mutex_t terminal_lock = PTHREAD_MUTEX_INITIALIZER;
    536 static pthread_mutex_t lines_lock = PTHREAD_MUTEX_INITIALIZER;
    537 static pthread_cond_t lines_cond = PTHREAD_COND_INITIALIZER;
    538 static void (*line_ready_callback)(void) = NULL;
    539 
    540 static char reverse_search_mainloop(void)
    541 {
    542     while (true) {
    543         char c = raw_getc();
    544         pthread_mutex_lock(&terminal_lock);
    545         
    546         switch (c) {
    547             case CTL('C'):
    548                 line.search_line = NULL;
    549                 set_line("");
    550                 pthread_mutex_unlock(&terminal_lock);
    551                 return CTL('A');
    552             case CTL('R'):
    553                 line.search_line = reverse_find(line.search_line? line.search_line->prev : history.last, line.content, false);
    554                 if (!line.search_line) {
    555                     printf("\a");
    556                 }
    557                 redraw_prompt(true);
    558                 break;
    559             case CTL('W'):
    560                 delete_word(false);
    561                 redraw_prompt(true);
    562                 break;
    563             case CTL('H'):
    564             case 0x7F: // Backspace
    565                 delete(1, false);
    566                 redraw_prompt(true);
    567                 break;
    568             default:
    569                 if (c >= ' ') {
    570                     char string[2] = {c, 0};
    571                     insert(string);
    572                     line.search_line = reverse_find(line.search_line?: history.last, line.content, false);
    573                     if (!line.search_line) {
    574                         printf("\a");
    575                     }
    576                     redraw_prompt(true);
    577                 }
    578                 else {
    579                     pthread_mutex_unlock(&terminal_lock);
    580                     return c;
    581                 }
    582                 break;
    583         }
    584         pthread_mutex_unlock(&terminal_lock);
    585         fflush(stdout);
    586     }
    587     
    588 }
    589 static void no_csi_mainloop(void)
    590 {
    591     while (true) {
    592         char *expression = NULL;
    593         size_t size = 0;
    594         
    595         errno = 0;
    596         if (getline(&expression, &size, stdin) <= 0) {
    597             if (expression) {
    598                 free(expression);
    599             }
    600             if (!errno) {
    601                 continue;
    602             }
    603             return;
    604         }
    605         
    606         pthread_mutex_lock(&lines_lock);
    607         if (!expression) {
    608             add_entry(&lines, "");
    609         }
    610         else {
    611             size_t length = strlen(expression);
    612             if (expression[length - 1] == '\n') {
    613                 expression[length - 1] = 0;
    614             }
    615             add_entry(&lines, expression);
    616             free(expression);
    617         }
    618         pthread_cond_signal(&lines_cond);
    619         pthread_mutex_unlock(&lines_lock);
    620         if (line_ready_callback) {
    621             line_ready_callback();
    622         }
    623     }
    624 }
    625 
    626 
    627 static
    628 #ifdef _WIN32
    629 int __stdcall
    630 #else
    631 void *
    632 #endif
    633 mainloop(char *(*completer)(const char *substring, uintptr_t *context))
    634 {
    635     if (no_csi) {
    636         no_csi_mainloop();
    637         return 0;
    638     }
    639     
    640     listent_t *history_line = NULL;
    641     uintptr_t complete_context = 0;
    642     size_t completion_length = 0;
    643     while (true) {
    644         char c;
    645         if (line.reverse_search) {
    646             c = reverse_search_mainloop();
    647             line.reverse_search = false;
    648             if (line.search_line) {
    649                 size_t pos = strstr(line.search_line->content, line.content) - line.search_line->content + line.length;
    650                 set_line(line.search_line->content);
    651                 line.search_line = NULL;
    652                 set_position(pos);
    653             }
    654             else {
    655                 redraw_prompt(true);
    656             }
    657         }
    658         else {
    659             c = raw_getc();
    660         }
    661         if (c == (char)EOF) {
    662             return 0;
    663         }
    664 
    665         pthread_mutex_lock(&terminal_lock);
    666         
    667         switch (c) {
    668             case CTL('A'):
    669                 set_position(0);
    670                 complete_context = completion_length = 0;
    671                 break;
    672             case CTL('B'):
    673                 set_position(line.position - 1);
    674                 complete_context = completion_length = 0;
    675                 break;
    676             case CTL('C'):
    677                 if (line.length) {
    678                     set_line("");
    679                     history_line = NULL;
    680                     complete_context = completion_length = 0;
    681                 }
    682                 else {
    683 #ifdef _WIN32
    684                     raise(SIGINT);
    685 #else
    686                     kill(getpid(), SIGINT);
    687 #endif
    688                 }
    689                 break;
    690             case CTL('D'):
    691                 if (line.length) {
    692                     delete(1, true);
    693                     complete_context = completion_length = 0;
    694                 }
    695                 else {
    696                     pthread_mutex_lock(&lines_lock);
    697                     add_entry(&lines, CON_EOF);
    698                     pthread_cond_signal(&lines_cond);
    699                     pthread_mutex_unlock(&lines_lock);
    700                     if (line_ready_callback) {
    701                         line_ready_callback();
    702                     }
    703                 }
    704                 break;
    705             case CTL('E'):
    706                 set_position(line.length);
    707                 complete_context = completion_length = 0;
    708                 break;
    709             case CTL('F'):
    710                 set_position(line.position + 1);
    711                 complete_context = completion_length = 0;
    712                 break;
    713             case CTL('K'):
    714                 printf(CSI("K"));
    715                 if (!redraw_prompt(false)) {
    716                     line.length = line.position;
    717                     line.content[line.length] = 0;
    718                 }
    719                 complete_context = completion_length = 0;
    720                 break;
    721             case CTL('R'):
    722                 complete_context = completion_length = 0;
    723                 line.reverse_search = true;
    724                 set_line("");
    725                 
    726                 break;
    727             case CTL('T'):
    728                 if (line.length < 2) {
    729                     printf("\a");
    730                     break;
    731                 }
    732                 if (line.position && line.position == line.length) {
    733                     line.position--;
    734                 }
    735                 if (line.position == 0) {
    736                     printf("\a");
    737                     break;
    738                 }
    739                 SWAP(line.content + line.position,
    740                      line.content + line.position - 1);
    741                 line.position++;
    742                 redraw_prompt(true);
    743                 complete_context = completion_length = 0;
    744                 break;
    745             case CTL('W'):
    746                 delete_word(false);
    747                 complete_context = completion_length = 0;
    748                 break;
    749             case '\r':
    750             case '\n':
    751                 pthread_mutex_lock(&lines_lock);
    752                 if (line.length == 0 && repeat_empty && history.last) {
    753                     add_entry(&lines, history.last->content);
    754                 }
    755                 else {
    756                     add_entry(&lines, line.content);
    757                 }
    758                 pthread_cond_signal(&lines_cond);
    759                 pthread_mutex_unlock(&lines_lock);
    760                 if (line_ready_callback) {
    761                     line_ready_callback();
    762                 }
    763                 if (line.length) {
    764                     listent_t *dup = reverse_find(history.last, line.content, true);
    765                     if (dup) {
    766                         remove_entry(&history, dup);
    767                     }
    768                     add_entry(&history, line.content);
    769                     set_line("");
    770                     history_line = NULL;
    771                 }
    772                 complete_context = completion_length = 0;
    773                 break;
    774             case CTL('H'):
    775             case 0x7F: // Backspace
    776                 delete(1, false);
    777                 complete_context = completion_length = 0;
    778                 break;
    779             case 0x1B:
    780                 switch (get_extended_key()) {
    781                     case MOD_SPECIAL(1): // Home
    782                     case MOD_SPECIAL(7):
    783                     case 'H':
    784                         set_position(0);
    785                         complete_context = completion_length = 0;
    786                         break;
    787                     case MOD_SPECIAL(8): // End
    788                     case 'F':
    789                         set_position(line.length);
    790                         complete_context = completion_length = 0;
    791                         break;
    792                     case MOD_SPECIAL(3): // Delete
    793                         delete(1, true);
    794                         complete_context = completion_length = 0;
    795                         break;
    796                     case 'A': // Up
    797                         if (!history_line) {
    798                             history_line = history.last;
    799                         }
    800                         else {
    801                             history_line = history_line->prev;
    802                         }
    803                         if (history_line) {
    804                             set_line(history_line->content);
    805                             complete_context = completion_length = 0;
    806                         }
    807                         else {
    808                             history_line = history.first;
    809                             printf("\a");
    810                         }
    811                         
    812                         break;
    813                     case 'B': // Down
    814                         if (!history_line) {
    815                             printf("\a");
    816                             break;
    817                         }
    818                         history_line = history_line->next;
    819                         if (history_line) {
    820                             set_line(history_line->content);
    821                             complete_context = completion_length = 0;
    822                         }
    823                         else {
    824                             set_line("");
    825                             complete_context = completion_length = 0;
    826                         }
    827                         break;
    828                     case 'C': // Right
    829                         set_position(line.position + 1);
    830                         complete_context = completion_length = 0;
    831                         break;
    832                     case 'D': // Left
    833                         set_position(line.position - 1);
    834                         complete_context = completion_length = 0;
    835                         break;
    836                     case MOD_ALT('b'):
    837                     case MOD_ALT('D'):
    838                         move_word(false);
    839                         complete_context = completion_length = 0;
    840                         break;
    841                     case MOD_ALT('f'):
    842                     case MOD_ALT('C'):
    843                         move_word(true);
    844                         complete_context = completion_length = 0;
    845                         break;
    846                     case MOD_ALT(0x7F): // ALT+Backspace
    847                         delete_word(false);
    848                         complete_context = completion_length = 0;
    849                         break;
    850                     case MOD_ALT('('): // ALT+Delete
    851                         delete_word(true);
    852                         complete_context = completion_length = 0;
    853                         break;
    854                     default:
    855                         printf("\a");
    856                         break;
    857                 }
    858                 break;
    859             case '\t': {
    860                 if (!no_csi) {
    861                     char temp = line.content[line.position - completion_length];
    862                     line.content[line.position - completion_length] = 0;
    863                     char *completion = completer? completer(line.content, &complete_context) : NULL;
    864                     line.content[line.position - completion_length] = temp;
    865                     if (completion) {
    866                         if (completion_length) {
    867                             delete(completion_length, false);
    868                         }
    869                         insert(completion);
    870                         completion_length = strlen(completion);
    871                         free(completion);
    872                     }
    873                     else {
    874                         printf("\a");
    875                     }
    876                     break;
    877                 }
    878             }
    879             default:
    880                 if (c >= ' ') {
    881                     char string[2] = {c, 0};
    882                     insert(string);
    883                     complete_context = completion_length = 0;
    884                 }
    885                 else {
    886                     printf("\a");
    887                 }
    888                 break;
    889         }
    890         fflush(stdout);
    891         pthread_mutex_unlock(&terminal_lock);
    892     }
    893     return 0;
    894 }
    895 
    896 
    897 void CON_set_line_ready_callback(void (*callback)(void))
    898 {
    899     line_ready_callback = callback;
    900 }
    901 
    902 char *CON_readline(const char *new_prompt)
    903 {
    904     pthread_mutex_lock(&terminal_lock);
    905     const char *old_prompt = prompt;
    906     prompt = new_prompt;
    907     prompt_length = strlen(prompt);
    908     redraw_prompt(true);
    909     fflush(stdout);
    910     pthread_mutex_unlock(&terminal_lock);
    911     
    912     pthread_mutex_lock(&lines_lock);
    913     while (!lines.first) {
    914         pthread_cond_wait(&lines_cond, &lines_lock);
    915     }
    916     char *ret = strdup(lines.first->content);
    917     remove_entry(&lines, lines.first);
    918     pthread_mutex_unlock(&lines_lock);
    919     
    920     pthread_mutex_lock(&terminal_lock);
    921     prompt = old_prompt;
    922     prompt_length = strlen(prompt);
    923     redraw_prompt(true);
    924     fflush(stdout);
    925     pthread_mutex_unlock(&terminal_lock);
    926     return ret;
    927 }
    928 
    929 char *CON_readline_async(void)
    930 {
    931     char *ret = NULL;
    932     pthread_mutex_lock(&lines_lock);
    933     if (lines.first) {
    934         ret = strdup(lines.first->content);
    935         remove_entry(&lines, lines.first);
    936     }
    937     pthread_mutex_unlock(&lines_lock);
    938     return ret;
    939 }
    940 
    941 bool CON_start(char *(*completer)(const char *substring, uintptr_t *context))
    942 {
    943     if (!initialize()) {
    944         return false;
    945     }
    946     set_line("");
    947     pthread_t thread;
    948     return pthread_create(&thread, NULL, (void *)mainloop, completer) == 0;
    949 }
    950 
    951 void CON_attributed_print(const char *string, CON_attributes_t *attributes)
    952 {
    953     if (!initialized || no_csi) {
    954         fprintf(stdout, "%s", string);
    955         return;
    956     }
    957     static bool pending_newline = false;
    958     pthread_mutex_lock(&terminal_lock);
    959     printf(ESC("8"));
    960     bool needs_reset = false;
    961     if (attributes) {
    962         if (attributes->color) {
    963             if (attributes->color >= 0x10) {
    964                 printf(SGR("%d"), attributes->color - 0x11 + 90);
    965             }
    966             else {
    967                 printf(SGR("%d"), attributes->color - 1 + 30);
    968             }
    969             needs_reset = true;
    970         }
    971         if (attributes->background) {
    972             if (attributes->background >= 0x10) {
    973                 printf(SGR("%d"), attributes->background - 0x11 + 100);
    974             }
    975             else {
    976                 printf(SGR("%d"), attributes->background - 1 + 40);
    977             }
    978             needs_reset = true;
    979         }
    980         if (attributes->bold) {
    981             printf(SGR("1"));
    982             needs_reset = true;
    983         }
    984         if (attributes->italic) {
    985             printf(SGR("3"));
    986             needs_reset = true;
    987         }
    988         if (attributes->underline) {
    989             printf(SGR("4"));
    990             needs_reset = true;
    991         }
    992     }
    993     const char *it = string;
    994     bool need_redraw_prompt = false;
    995     while (*it) {
    996         if (pending_newline) {
    997             need_redraw_prompt = true;
    998             printf("\n" CSI("K") "\n" CSI("A"));
    999             pending_newline = false;
   1000             continue;
   1001         }
   1002         if (*it == '\n') {
   1003             printf("%.*s", (int)(it - string), string);
   1004             string = it + 1;
   1005             pending_newline = true;
   1006         }
   1007         it++;
   1008     }
   1009     if (*string) {
   1010         printf("%s", string);
   1011     }
   1012     if (needs_reset) {
   1013         printf(SGR("0"));
   1014     }
   1015     printf(ESC("7") CSI("B"));
   1016     if (need_redraw_prompt) {
   1017         redraw_prompt(true);
   1018     }
   1019     else {
   1020         set_position(line.position);
   1021     }
   1022     fflush(stdout);
   1023     pthread_mutex_unlock(&terminal_lock);
   1024 }
   1025 
   1026 void CON_print(const char *string)
   1027 {
   1028     CON_attributed_print(string, NULL);
   1029 }
   1030 
   1031 void CON_vprintf(const char *fmt, va_list args)
   1032 {
   1033     char *string = NULL;
   1034     vasprintf(&string, fmt, args);
   1035     CON_attributed_print(string, NULL);
   1036     free(string);
   1037 }
   1038 
   1039 void CON_attributed_vprintf(const char *fmt, CON_attributes_t *attributes, va_list args)
   1040 {
   1041     char *string = NULL;
   1042     vasprintf(&string, fmt, args);
   1043     CON_attributed_print(string, attributes);
   1044     free(string);
   1045 }
   1046 
   1047 void CON_printf(const char *fmt, ...)
   1048 {
   1049     va_list args;
   1050     va_start(args, fmt);
   1051     CON_vprintf(fmt, args);
   1052     va_end(args);
   1053 }
   1054 
   1055 
   1056 void CON_attributed_printf(const char *fmt, CON_attributes_t *attributes,...)
   1057 {
   1058     va_list args;
   1059     va_start(args, attributes);
   1060     CON_attributed_vprintf(fmt, attributes, args);
   1061     va_end(args);
   1062 }
   1063 
   1064 void CON_set_async_prompt(const char *string)
   1065 {
   1066     pthread_mutex_lock(&terminal_lock);
   1067     prompt = string;
   1068     prompt_length = strlen(string);
   1069     redraw_prompt(true);
   1070     fflush(stdout);
   1071     pthread_mutex_unlock(&terminal_lock);
   1072 }
   1073 
   1074 void CON_set_repeat_empty(bool repeat)
   1075 {
   1076     repeat_empty = repeat;
   1077 }
   1078 
   1079 bool CON_no_csi_mode(void)
   1080 {
   1081     return no_csi;
   1082 }

This webpage is intended to be an accessible preview of this repository. To get a fuller picture, clone it and use the git CLI.