New console readline-like interface for the SDL port

This commit is contained in:
Lior Halphon 2021-09-09 00:13:09 +03:00
parent 336bc65dbf
commit c5d91fc448
6 changed files with 1225 additions and 5 deletions

View File

@ -128,10 +128,10 @@ endif
ifeq (,$(PKG_CONFIG))
SDL_CFLAGS := $(shell sdl2-config --cflags)
SDL_LDFLAGS := $(shell sdl2-config --libs)
SDL_LDFLAGS := $(shell sdl2-config --libs) -lpthread
else
SDL_CFLAGS := $(shell $(PKG_CONFIG) --cflags sdl2)
SDL_LDFLAGS := $(shell $(PKG_CONFIG) --libs sdl2)
SDL_LDFLAGS := $(shell $(PKG_CONFIG) --libs sdl2) -lpthread
endif
ifeq (,$(PKG_CONFIG))
GL_LDFLAGS := -lGL

998
SDL/console.c Normal file
View File

@ -0,0 +1,998 @@
#include "console.h"
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdbool.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#include <ctype.h>
#include <assert.h>
#include <pthread.h>
#include <stdarg.h>
#include <stdint.h>
#define ESC(x) "\x1B" x
#define CSI(x) ESC("[" x)
#define SGR(x) CSI(x "m")
static bool initialized = false;
typedef struct listent_s listent_t;
struct listent_s {
listent_t *prev;
listent_t *next;
char content[];
};
typedef struct {
listent_t *first;
listent_t *last;
} fifo_t;
static fifo_t lines;
static fifo_t history;
static void remove_entry(fifo_t *fifo, listent_t *entry)
{
if (fifo->last == entry) {
fifo->last = entry->prev;
}
if (fifo->first == entry) {
fifo->first = entry->next;
}
if (entry->next) {
entry->next->prev = entry->prev;
}
if (entry->prev) {
entry->prev->next = entry->next;
}
free(entry);
}
static void add_entry(fifo_t *fifo, const char *content)
{
size_t length = strlen(content);
listent_t *entry = malloc(sizeof(*entry) + length + 1);
entry->next = NULL;
entry->prev = fifo->last;
memcpy(entry->content, content, length);
entry->content[length] = 0;
if (fifo->last) {
fifo->last->next = entry;
}
fifo->last = entry;
if (!fifo->first) {
fifo->first = entry;
}
}
static listent_t *reverse_find(listent_t *entry, const char *string, bool exact)
{
while (entry) {
if (exact && strcmp(entry->content, string) == 0) {
return entry;
}
if (!exact && strstr(entry->content, string)) {
return entry;
}
entry = entry->prev;
}
return NULL;
}
static bool is_term(void)
{
#ifdef _WIN32
if (AllocConsole()) {
FreeConsole();
return false;
}
#endif
return isatty(STDIN_FILENO) && isatty(STDOUT_FILENO)
#ifndef _WIN32
&& getenv("TERM")
#endif
;
}
static unsigned width, height;
static char raw_getc(void)
{
#ifdef _WIN32
char c;
unsigned long ret;
ReadConsole(GetStdHandle(STD_INPUT_HANDLE), &c, 1, &ret, NULL);
#else
ssize_t ret;
char c;
do {
ret = read(STDIN_FILENO, &c, 1);
} while (ret == -1 && errno == EINTR);
#endif
return ret == 1? c : EOF;
}
#ifdef _WIN32
#pragma clang diagnostic ignored "-Wmacro-redefined"
#include <Windows.h>
static void update_size(void)
{
CONSOLE_SCREEN_BUFFER_INFO csbi;
GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi);
width = csbi.srWindow.Right - csbi.srWindow.Left + 1;
height = csbi.srWindow.Bottom - csbi.srWindow.Top + 1;
}
static unsigned long input_mode, output_mode;
static void cleanup(void)
{
printf(CSI("!p")); // reset
SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), input_mode);
SetConsoleMode(GetStdHandle(STD_OUTPUT_HANDLE), output_mode);
fflush(stdout);
}
static bool initialize(void)
{
if (!is_term()) return false;
update_size();
if (width == 0 || height == 0) {
return false;
}
static bool once = false;
if (!once) {
atexit(cleanup);
GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), &input_mode);
GetConsoleMode(GetStdHandle(STD_OUTPUT_HANDLE), &output_mode);
once = true;
}
SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), ENABLE_VIRTUAL_TERMINAL_INPUT);
SetConsoleMode(GetStdHandle(STD_OUTPUT_HANDLE), ENABLE_WRAP_AT_EOL_OUTPUT | ENABLE_PROCESSED_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING);
printf(CSI("%dB") "\n" CSI("A") ESC("7") CSI("B"), height);
fflush(stdout);
initialized = true;
return true;
}
#else
#include <sys/ioctl.h>
static void update_size(void)
{
struct winsize winsize;
ioctl(STDOUT_FILENO, TIOCGWINSZ, &winsize);
width = winsize.ws_col;
height = winsize.ws_row;
}
static void terminal_resized(int ignored)
{
update_size();
}
#include <termios.h>
static struct termios terminal;
static void cleanup(void)
{
printf(CSI("!p")); // reset
tcsetattr(STDIN_FILENO, TCSAFLUSH, &terminal);
fflush(stdout);
}
static bool initialize(void)
{
if (!is_term()) return false;
update_size();
if (width == 0 || height == 0) {
return false;
}
static bool once = false;
if (!once) {
atexit(cleanup);
signal(SIGWINCH, terminal_resized);
tcgetattr(STDIN_FILENO, &terminal);
#ifdef _WIN32
_setmode(STDIN_FILENO, _O_TEXT);
#endif
once = true;
}
struct termios raw_terminal;
raw_terminal = terminal;
raw_terminal.c_lflag = 0;
tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw_terminal);
printf(CSI("%dB") "\n" CSI("A") ESC("7") CSI("B"), height);
fflush(stdout);
initialized = true;
return true;
}
#endif
static struct {
char *content;
size_t allocation_size;
size_t length;
size_t position;
size_t scroll;
bool reverse_search;
listent_t *search_line;
} line;
#define CTL(x) ((x) - 'A' + 1)
static const char *prompt = "";
static size_t prompt_length = 0;
static bool repeat_empty = false;
static bool redraw_prompt(bool force)
{
if (line.reverse_search) {
if (!force) return false;
if (line.length == 0) {
printf("\r" CSI("K") "%s" SGR("2") "Reverse Search..." SGR("0") CSI("%zuG"), prompt, prompt_length + 1);
return true;
}
if (!line.search_line) {
printf("\r" CSI("K") "%s" SGR("1") "%s" SGR("0"), prompt, line.content);
return true;
}
const char *loc = strstr(line.search_line->content, line.content);
printf("\r" CSI("K") "%s" "%.*s" SGR("1") "%s" SGR("0") "%s" CSI("%uG"),
prompt,
(int)(loc - line.search_line->content),
line.search_line->content,
line.content,
loc + line.length,
(unsigned)(loc - line.search_line->content + line.length + prompt_length + 1));
return true;
}
size_t max = width - 1 - prompt_length;
if (line.scroll && line.length <= max) {
line.scroll = 0;
force = true;
}
if (line.scroll > line.length - max) {
line.scroll = line.length - max;
force = true;
}
if (line.position < line.scroll + 1 && line.position) {
line.scroll = line.position - 1;
force = true;
}
if (line.position == 0 && line.scroll) {
line.scroll = 0;
force = true;
}
if (line.position > line.scroll + max) {
line.scroll = line.position - max;
force = true;
}
if (!force && line.length <= max) {
return false;
}
if (line.length <= max) {
printf("\r" CSI("K") "%s%s" CSI("%uG"), prompt, line.content, (unsigned)(line.position + prompt_length + 1));
return true;
}
size_t left = max;
const char *string = line.content + line.scroll;
printf("\r" CSI("K") "%s", prompt);
if (line.scroll) {
printf(SGR("2") "%c" SGR("0"), *string);
string++;
left--;
}
if (line.scroll + max == line.length) {
printf("%s", string);
}
else {
printf("%.*s", (int)(left - 1), string);
string += left;
left = 1;
printf(SGR("2") "%c" SGR("0"), *string);
}
printf(CSI("%uG"), (unsigned)(line.position - line.scroll + prompt_length + 1));
return true;
}
static void set_position(size_t position)
{
if (position > line.length) {
printf("\a");
return;
}
line.position = position;
if (!redraw_prompt(false)) {
printf(CSI("%uG"), (unsigned)(position + prompt_length + 1));
}
}
static void set_line(const char *content)
{
line.length = strlen(content);
if (line.length + 1 > line.allocation_size) {
line.content = realloc(line.content, line.length + 1);
line.allocation_size = line.length + 1;
}
else if (line.allocation_size > 256 && line.length < 128) {
line.content = realloc(line.content, line.length + 1);
line.allocation_size = line.length + 1;
}
line.position = line.length;
strcpy(line.content, content);
redraw_prompt(true);
}
static void insert(const char *string)
{
size_t insertion_length = strlen(string);
size_t new_length = insertion_length + line.length;
bool need_realloc = false;
while (line.allocation_size < new_length + 1) {
line.allocation_size *= 2;
need_realloc = true;
}
if (need_realloc) {
line.content = realloc(line.content, line.allocation_size);
}
memmove(line.content + line.position + insertion_length,
line.content + line.position,
line.length - line.position);
memcpy(line.content + line.position, string, insertion_length);
line.position += insertion_length;
line.content[new_length] = 0;
line.length = new_length;
if (!redraw_prompt(line.position != line.length)) {
printf("%s", string);
}
}
static void delete(size_t size, bool forward)
{
if (line.length < size) {
printf("\a");
return;
}
if (forward) {
if (line.position > line.length - size) {
printf("\a");
return;
}
else {
line.position += size;
}
}
else if (line.position < size) {
printf("\a");
return;
}
memmove(line.content + line.position - size,
line.content + line.position,
line.length - line.position);
line.length -= size;
line.content[line.length] = 0;
line.position -= size;
if (!redraw_prompt(line.position != line.length)) {
printf(CSI("%uG") CSI("K"),
(unsigned)(line.position + prompt_length + 1));
}
}
static void move_word(bool forward)
{
signed offset = forward? 1 : -1;
size_t end = forward? line.length : 0;
signed check_offset = forward? 0 : -1;
if (line.position == end) {
printf("\a");
return;
}
line.position += offset;
while (line.position != end && isalnum(line.content[line.position + check_offset])) {
line.position += offset;
}
if (!redraw_prompt(false)) {
printf(CSI("%uG"), (unsigned)(line.position + prompt_length + 1));
}
}
static void delete_word(bool forward)
{
size_t original_pos = line.position;
signed offset = forward? 1 : -1;
size_t end = forward? line.length : 0;
signed check_offset = forward? 0 : -1;
if (line.position == end) {
printf("\a");
return;
}
line.position += offset;
while (line.position != end && isalnum(line.content[line.position + check_offset])) {
line.position += offset;
}
if (forward) {
delete(line.position - original_pos, false);
}
else {
delete(original_pos - line.position, true);
}
}
#define MOD_ALT(x) (0x100 | x)
#define MOD_SHIFT(x) (0x200 | x)
#define MOD_CTRL(x) (0x400 | x)
#define MOD_SPECIAL(x) (0x800 | x)
static unsigned get_extended_key(void)
{
unsigned modifiers = 0;
char c = 0;
restart:
c = raw_getc();
if (c == 0x1B) {
modifiers = MOD_SHIFT(MOD_ALT(0));
goto restart;
}
else if (c != '[' && c != 'O') {
return MOD_ALT(c);
}
unsigned ret = 0;
while (true) {
c = raw_getc();
if (c >= '0' && c <= '9') {
ret = ret * 10 + c - '0';
}
else if (c == ';') {
if (ret == 1) {
modifiers |= MOD_ALT(0);
}
else if (ret == 2) {
modifiers |= MOD_SHIFT(0);
}
else if (ret == 5) {
modifiers |= MOD_CTRL(0);
}
ret = 0;
}
else if (c == '~') {
return MOD_SPECIAL(ret) | modifiers;
}
else {
if (ret == 1) {
modifiers |= MOD_ALT(0);
}
else if (ret == 2) {
modifiers |= MOD_SHIFT(0);
}
else if (ret == 5) {
modifiers |= MOD_CTRL(0);
}
return c | modifiers;
}
}
}
#define SWAP(x, y) do {typeof(*(x)) _tmp = *(x); *(x) = *(y);*(y) = _tmp;} while (0)
static pthread_mutex_t terminal_lock = PTHREAD_MUTEX_INITIALIZER;
static pthread_mutex_t lines_lock = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t lines_cond = PTHREAD_COND_INITIALIZER;
static char reverse_search_mainloop(void)
{
while (true) {
char c = raw_getc();
pthread_mutex_lock(&terminal_lock);
switch (c) {
case CTL('C'):
line.search_line = NULL;
set_line("");
pthread_mutex_unlock(&terminal_lock);
return CTL('A');
case CTL('R'):
line.search_line = reverse_find(line.search_line? line.search_line->prev : history.last, line.content, false);
if (!line.search_line) {
printf("\a");
}
redraw_prompt(true);
break;
case CTL('W'):
delete_word(false);
redraw_prompt(true);
break;
#ifndef _WIN32
case CTL('Z'):
set_line("");
raise(SIGSTOP);
initialize(); // Reinitialize
redraw_prompt(true);
break;
#endif
case CTL('H'):
case 0x7F: // Backspace
delete(1, false);
redraw_prompt(true);
break;
default:
if (c >= ' ') {
char string[2] = {c, 0};
insert(string);
line.search_line = reverse_find(line.search_line?: history.last, line.content, false);
if (!line.search_line) {
printf("\a");
}
redraw_prompt(true);
}
else {
pthread_mutex_unlock(&terminal_lock);
return c;
}
break;
}
pthread_mutex_unlock(&terminal_lock);
fflush(stdout);
}
}
static
#ifdef _WIN32
int __stdcall
#else
void *
#endif
mainloop(char *(*completer)(const char *substring, uintptr_t *context))
{
listent_t *history_line = NULL;
uintptr_t complete_context = 0;
size_t completion_length = 0;
while (true) {
char c;
if (line.reverse_search) {
c = reverse_search_mainloop();
line.reverse_search = false;
if (line.search_line) {
size_t pos = strstr(line.search_line->content, line.content) - line.search_line->content + line.length;
set_line(line.search_line->content);
line.search_line = NULL;
set_position(pos);
}
else {
redraw_prompt(true);
}
}
else {
c = raw_getc();
}
pthread_mutex_lock(&terminal_lock);
switch (c) {
case CTL('A'):
set_position(0);
complete_context = completion_length = 0;
break;
case CTL('B'):
set_position(line.position - 1);
complete_context = completion_length = 0;
break;
case CTL('C'):
if (line.length) {
set_line("");
history_line = NULL;
complete_context = completion_length = 0;
}
else {
#ifdef _WIN32
raise(SIGINT);
#else
kill(getpid(), SIGINT);
#endif
}
break;
case CTL('D'):
if (line.length) {
delete(1, true);
complete_context = completion_length = 0;
}
else {
pthread_mutex_lock(&lines_lock);
add_entry(&lines, CON_EOF);
pthread_cond_signal(&lines_cond);
pthread_mutex_unlock(&lines_lock);
}
break;
case CTL('E'):
set_position(line.length);
complete_context = completion_length = 0;
break;
case CTL('F'):
set_position(line.position + 1);
complete_context = completion_length = 0;
break;
case CTL('K'):
printf(CSI("K"));
if (!redraw_prompt(false)) {
line.length = line.position;
line.content[line.length] = 0;
}
complete_context = completion_length = 0;
break;
case CTL('R'):
complete_context = completion_length = 0;
line.reverse_search = true;
set_line("");
break;
case CTL('T'):
if (line.length < 2) {
printf("\a");
break;
}
if (line.position && line.position == line.length) {
line.position--;
}
if (line.position == 0) {
printf("\a");
break;
}
SWAP(line.content + line.position,
line.content + line.position - 1);
line.position++;
redraw_prompt(true);
complete_context = completion_length = 0;
break;
case CTL('W'):
delete_word(false);
complete_context = completion_length = 0;
break;
#ifndef _WIN32
case CTL('Z'):
set_line("");
complete_context = completion_length = 0;
raise(SIGSTOP);
initialize(); // Reinitialize
break;
#endif
case '\r':
case '\n':
pthread_mutex_lock(&lines_lock);
if (line.length == 0 && repeat_empty && history.last) {
add_entry(&lines, history.last->content);
}
else {
add_entry(&lines, line.content);
}
pthread_cond_signal(&lines_cond);
pthread_mutex_unlock(&lines_lock);
if (line.length) {
listent_t *dup = reverse_find(history.last, line.content, true);
if (dup) {
remove_entry(&history, dup);
}
add_entry(&history, line.content);
set_line("");
history_line = NULL;
}
complete_context = completion_length = 0;
break;
case CTL('H'):
case 0x7F: // Backspace
delete(1, false);
complete_context = completion_length = 0;
break;
case 0x1B:
switch (get_extended_key()) {
case MOD_SPECIAL(1): // Home
case MOD_SPECIAL(7):
case 'H':
set_position(0);
complete_context = completion_length = 0;
break;
case MOD_SPECIAL(8): // End
case 'F':
set_position(line.length);
complete_context = completion_length = 0;
break;
case MOD_SPECIAL(3): // Delete
delete(1, true);
complete_context = completion_length = 0;
break;
case 'A': // Up
if (!history_line) {
history_line = history.last;
}
else {
history_line = history_line->prev;
}
if (history_line) {
set_line(history_line->content);
complete_context = completion_length = 0;
}
else {
history_line = history.first;
printf("\a");
}
break;
case 'B': // Down
if (!history_line) {
printf("\a");
break;
}
history_line = history_line->next;
if (history_line) {
set_line(history_line->content);
complete_context = completion_length = 0;
}
else {
set_line("");
complete_context = completion_length = 0;
}
break;
case 'C': // Right
set_position(line.position + 1);
complete_context = completion_length = 0;
break;
case 'D': // Left
set_position(line.position - 1);
complete_context = completion_length = 0;
break;
case MOD_ALT('b'):
case MOD_ALT('D'):
move_word(false);
complete_context = completion_length = 0;
break;
case MOD_ALT('f'):
case MOD_ALT('C'):
move_word(true);
complete_context = completion_length = 0;
break;
case MOD_ALT(0x7f): // ALT+Backspace
delete_word(false);
complete_context = completion_length = 0;
break;
case MOD_ALT('('): // ALT+Delete
delete_word(true);
complete_context = completion_length = 0;
break;
default:
printf("Unsupported extended key %x\n", c);
printf("\a");
break;
}
break;
case '\t': {
char temp = line.content[line.position - completion_length];
line.content[line.position - completion_length] = 0;
char *completion = completer? completer(line.content, &complete_context) : NULL;
line.content[line.position - completion_length] = temp;
if (completion) {
if (completion_length) {
delete(completion_length, false);
}
insert(completion);
completion_length = strlen(completion);
free(completion);
}
else {
printf("\a");
}
break;
}
default:
if (c >= ' ') {
char string[2] = {c, 0};
insert(string);
complete_context = completion_length = 0;
}
else {
printf("Unsupported key %x\n", c);
printf("\a");
}
break;
}
fflush(stdout);
pthread_mutex_unlock(&terminal_lock);
}
return 0;
}
char *CON_readline(const char *new_prompt)
{
pthread_mutex_lock(&terminal_lock);
const char *old_prompt = prompt;
prompt = new_prompt;
prompt_length = strlen(prompt);
redraw_prompt(true);
fflush(stdout);
pthread_mutex_unlock(&terminal_lock);
pthread_mutex_lock(&lines_lock);
while (!lines.first) {
pthread_cond_wait(&lines_cond, &lines_lock);
}
char *ret = strdup(lines.first->content);
remove_entry(&lines, lines.first);
pthread_mutex_unlock(&lines_lock);
pthread_mutex_lock(&terminal_lock);
prompt = old_prompt;
prompt_length = strlen(prompt);
redraw_prompt(true);
fflush(stdout);
pthread_mutex_unlock(&terminal_lock);
return ret;
}
char *CON_readline_async(void)
{
char *ret = NULL;
pthread_mutex_lock(&lines_lock);
if (lines.first) {
ret = strdup(lines.first->content);
remove_entry(&lines, lines.first);
}
pthread_mutex_unlock(&lines_lock);
return ret;
}
bool CON_start(char *(*completer)(const char *substring, uintptr_t *context))
{
if (!initialize()) {
return false;
}
set_line("");
pthread_t thread;
return pthread_create(&thread, NULL, (void *)mainloop, completer) == 0;
}
void CON_attributed_print(const char *string, CON_attributes_t *attributes)
{
if (!initialized) {
printf("%s", string);
return;
}
static bool pending_newline = false;
pthread_mutex_lock(&terminal_lock);
printf(ESC("8"));
bool needs_reset = false;
if (attributes) {
if (attributes->color) {
if (attributes->color >= 0x10) {
printf(SGR("%d"), attributes->color - 0x11 + 90);
}
else {
printf(SGR("%d"), attributes->color - 1 + 30);
}
needs_reset = true;
}
if (attributes->background) {
if (attributes->background >= 0x10) {
printf(SGR("%d"), attributes->background - 0x11 + 100);
}
else {
printf(SGR("%d"), attributes->background - 1 + 40);
}
needs_reset = true;
}
if (attributes->bold) {
printf(SGR("1"));
needs_reset = true;
}
if (attributes->italic) {
printf(SGR("3"));
needs_reset = true;
}
if (attributes->underline) {
printf(SGR("4"));
needs_reset = true;
}
}
const char *it = string;
bool need_redraw_prompt = false;
while (*it) {
if (pending_newline) {
need_redraw_prompt = true;
printf("\n" CSI("K") "\n" CSI("A"));
pending_newline = false;
continue;
}
if (*it == '\n') {
printf("%.*s", (int)(it - string), string);
string = it + 1;
pending_newline = true;
}
it++;
}
if (*string) {
printf("%s", string);
}
if (needs_reset) {
printf(SGR("0"));
}
printf(ESC("7") CSI("B"));
if (need_redraw_prompt) {
redraw_prompt(true);
}
else {
set_position(line.position);
}
fflush(stdout);
pthread_mutex_unlock(&terminal_lock);
}
void CON_print(const char *string)
{
CON_attributed_print(string, NULL);
}
void CON_vprintf(const char *fmt, va_list args)
{
char *string = NULL;
vasprintf(&string, fmt, args);
CON_attributed_print(string, NULL);
free(string);
}
void CON_attributed_vprintf(const char *fmt, CON_attributes_t *attributes, va_list args)
{
char *string = NULL;
vasprintf(&string, fmt, args);
CON_attributed_print(string, attributes);
free(string);
}
void CON_printf(const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
CON_vprintf(fmt, args);
va_end(args);
}
void CON_attributed_printf(const char *fmt, CON_attributes_t *attributes,...)
{
va_list args;
va_start(args, attributes);
CON_attributed_vprintf(fmt, attributes, args);
va_end(args);
}
void CON_set_async_prompt(const char *string)
{
pthread_mutex_lock(&terminal_lock);
prompt = string;
prompt_length = strlen(string);
redraw_prompt(true);
fflush(stdout);
pthread_mutex_unlock(&terminal_lock);
}
void CON_set_repeat_empty(bool repeat)
{
repeat_empty = repeat;
}

49
SDL/console.h Normal file
View File

@ -0,0 +1,49 @@
#include <stdbool.h>
#include <stdint.h>
#include <stdarg.h>
#define CON_EOF "\x04"
bool CON_start(char *(*completer)(const char *substring, uintptr_t *context));
char *CON_readline(const char *prompt);
char *CON_readline_async(void);
typedef struct {
enum {
CON_COLOR_NONE = 0,
CON_COLOR_BLACK,
CON_COLOR_RED,
CON_COLOR_GREEN,
CON_COLOR_YELLOW,
CON_COLOR_BLUE,
CON_COLOR_MAGENTA,
CON_COLOR_CYAN,
CON_COLOR_LIGHT_GREY,
CON_COLOR_DARK_GREY = CON_COLOR_BLACK + 0x10,
CON_COLOR_BRIGHT_RED = CON_COLOR_RED + 0x10,
CON_COLOR_BRIGHT_GREEN = CON_COLOR_GREEN + 0x10,
CON_COLOR_BRIGHT_YELLOW = CON_COLOR_YELLOW + 0x10,
CON_COLOR_BRIGHT_BLUE = CON_COLOR_BLUE + 0x10,
CON_COLOR_BRIGHT_MAGENTA = CON_COLOR_MAGENTA + 0x10,
CON_COLOR_BRIGHT_CYAN = CON_COLOR_CYAN + 0x10,
CON_COLOR_WHITE = CON_COLOR_LIGHT_GREY + 0x10,
} color:8, background:8;
bool bold;
bool italic;
bool underline;
} CON_attributes_t;
#ifndef __printflike
/* Missing from Linux headers. */
#define __printflike(fmtarg, firstvararg) \
__attribute__((__format__ (__printf__, fmtarg, firstvararg)))
#endif
void CON_print(const char *string);
void CON_attributed_print(const char *string, CON_attributes_t *attributes);
void CON_vprintf(const char *fmt, va_list args);
void CON_attributed_vprintf(const char *fmt, CON_attributes_t *attributes, va_list args);
void CON_printf(const char *fmt, ...) __printflike(1, 2);
void CON_attributed_printf(const char *fmt, CON_attributes_t *attributes,...) __printflike(1, 3);
void CON_set_async_prompt(const char *string);
void CON_set_repeat_empty(bool repeat);

View File

@ -10,6 +10,7 @@
#include "gui.h"
#include "shader.h"
#include "audio/audio.h"
#include "console.h"
#ifndef _WIN32
#include <unistd.h>
@ -29,6 +30,7 @@ static char *filename = NULL;
static typeof(free) *free_function = NULL;
static char *battery_save_path_ptr = NULL;
static SDL_GLContext gl_context = NULL;
static bool console_supported = false;
bool uses_gl(void)
{
@ -44,6 +46,79 @@ void set_filename(const char *new_filename, typeof(free) *new_free_function)
free_function = new_free_function;
}
static char *completer(const char *substring, uintptr_t *context)
{
char *temp = strdup(substring);
char *ret = GB_debugger_complete_substring(&gb, temp, context);
free(temp);
return ret;
}
static void log_callback(GB_gameboy_t *gb, const char *string, GB_log_attributes attributes)
{
CON_attributes_t con_attributes = {0,};
con_attributes.bold = attributes & GB_LOG_BOLD;
con_attributes.underline = attributes & GB_LOG_UNDERLINE;
if (attributes & GB_LOG_DASHED_UNDERLINE) {
while (*string) {
con_attributes.underline ^= true;
CON_attributed_printf("%c", &con_attributes, *string);
string++;
}
}
else {
CON_attributed_print(string, &con_attributes);
}
}
static void handle_eof(void)
{
CON_set_async_prompt("");
char *line = CON_readline("Quit? [y]/n > ");
if (line[0] == 'n' || line[0] == 'N') {
free(line);
CON_set_async_prompt("> ");
}
else {
exit(0);
}
}
static char *input_callback(GB_gameboy_t *gb)
{
retry: {
char *ret = CON_readline("Stopped> ");
if (strcmp(ret, CON_EOF) == 0) {
handle_eof();
free(ret);
goto retry;
}
else {
CON_attributes_t attributes = {.bold = true};
CON_attributed_printf("> %s\n", &attributes, ret);
}
return ret;
}
}
static char *asyc_input_callback(GB_gameboy_t *gb)
{
retry: {
char *ret = CON_readline_async();
if (ret && strcmp(ret, CON_EOF) == 0) {
handle_eof();
free(ret);
goto retry;
}
else if (ret) {
CON_attributes_t attributes = {.bold = true};
CON_attributed_printf("> %s\n", &attributes, ret);
}
return ret;
}
}
static char *captured_log = NULL;
static void log_capture_callback(GB_gameboy_t *gb, const char *string, GB_log_attributes attributes)
@ -67,7 +142,7 @@ static void start_capturing_logs(void)
static const char *end_capturing_logs(bool show_popup, bool should_exit, uint32_t popup_flags, const char *title)
{
GB_set_log_callback(&gb, NULL);
GB_set_log_callback(&gb, console_supported? log_callback : NULL);
if (captured_log[0] == 0) {
free(captured_log);
captured_log = NULL;
@ -256,6 +331,7 @@ static void handle_events(GB_gameboy_t *gb)
}
case SDL_SCANCODE_C:
if (event.type == SDL_KEYDOWN && (event.key.keysym.mod & KMOD_CTRL)) {
CON_print("^C\a\n");
GB_debugger_break(gb);
}
break;
@ -408,12 +484,15 @@ static void rumble(GB_gameboy_t *gb, double amp)
static void debugger_interrupt(int ignore)
{
if (!GB_is_inited(&gb)) return;
if (!GB_is_inited(&gb)) exit(0);
/* ^C twice to exit */
if (GB_debugger_is_stopped(&gb)) {
GB_save_battery(&gb, battery_save_path_ptr);
exit(0);
}
if (console_supported) {
CON_print("^C\n");
}
GB_debugger_break(&gb);
}
@ -442,7 +521,6 @@ static void gb_audio_callback(GB_gameboy_t *gb, GB_sample_t *sample)
GB_audio_queue_sample(sample);
}
static bool handle_pending_command(void)
{
@ -577,6 +655,14 @@ restart:
GB_set_rtc_mode(&gb, configuration.rtc_mode);
GB_set_update_input_hint_callback(&gb, handle_events);
GB_apu_set_sample_callback(&gb, gb_audio_callback);
if ((console_supported = CON_start(completer))) {
CON_set_async_prompt("> ");
CON_set_repeat_empty(true);
GB_set_log_callback(&gb, log_callback);
GB_set_input_callback(&gb, input_callback);
GB_set_async_input_callback(&gb, asyc_input_callback);
}
}
if (stop_on_start) {
stop_on_start = false;

86
Windows/pthread.h Normal file
View File

@ -0,0 +1,86 @@
/* Very minimal pthread implementation for Windows */
#include <Windows.h>
#include <assert.h>
typedef HANDLE pthread_t;
typedef struct pthread_attr_s pthread_attr_t;
static inline int pthread_create(pthread_t *pthread,
const pthread_attr_t *attrs,
LPTHREAD_START_ROUTINE function,
void *context)
{
assert(!attrs);
*pthread = CreateThread(NULL, 0, function,
context, 0, NULL);
return *pthread? 0 : GetLastError();
}
typedef struct {
unsigned status;
CRITICAL_SECTION cs;
} pthread_mutex_t;
#define PTHREAD_MUTEX_INITIALIZER {0,}
static inline CRITICAL_SECTION *pthread_mutex_to_cs(pthread_mutex_t *mutex)
{
retry:
if (mutex->status == 2) return &mutex->cs;
unsigned expected = 0;
unsigned desired = 1;
if (__atomic_compare_exchange(&mutex->status, &expected, &desired, false, __ATOMIC_RELAXED, __ATOMIC_RELAXED)) {
InitializeCriticalSection(&mutex->cs);
mutex->status = 2;
return &mutex->cs;
}
goto retry;
}
static inline int pthread_mutex_lock(pthread_mutex_t *mutex)
{
EnterCriticalSection(pthread_mutex_to_cs(mutex));
return 0;
}
static inline int pthread_mutex_unlock(pthread_mutex_t *mutex)
{
LeaveCriticalSection(pthread_mutex_to_cs(mutex));
return 0;
}
typedef struct {
unsigned status;
CONDITION_VARIABLE cond;
} pthread_cond_t;
#define PTHREAD_COND_INITIALIZER {0,}
static inline CONDITION_VARIABLE *pthread_cond_to_win(pthread_cond_t *cond)
{
retry:
if (cond->status == 2) return &cond->cond;
unsigned expected = 0;
unsigned desired = 1;
if (__atomic_compare_exchange(&cond->status, &expected, &desired, false, __ATOMIC_RELAXED, __ATOMIC_RELAXED)) {
InitializeConditionVariable(&cond->cond);
cond->status = 2;
return &cond->cond;
}
goto retry;
}
static inline int pthread_cond_signal(pthread_cond_t *cond)
{
WakeConditionVariable(pthread_cond_to_win(cond));
return 0;
}
static inline int pthread_cond_wait(pthread_cond_t *cond,
pthread_mutex_t *mutex)
{
// Mutex is locked therefore already initialized
return SleepConditionVariableCS(pthread_cond_to_win(cond), &mutex->cs, INFINITE);
}

View File

@ -5,3 +5,4 @@
#define read(...) _read(__VA_ARGS__)
#define write(...) _write(__VA_ARGS__)
#define isatty(...) _isatty(__VA_ARGS__)