New console readline-like interface for the SDL port
This commit is contained in:
parent
336bc65dbf
commit
c5d91fc448
4
Makefile
4
Makefile
@ -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
998
SDL/console.c
Normal 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
49
SDL/console.h
Normal 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);
|
92
SDL/main.c
92
SDL/main.c
@ -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
86
Windows/pthread.h
Normal 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);
|
||||
}
|
@ -5,3 +5,4 @@
|
||||
|
||||
#define read(...) _read(__VA_ARGS__)
|
||||
#define write(...) _write(__VA_ARGS__)
|
||||
#define isatty(...) _isatty(__VA_ARGS__)
|
||||
|
Loading…
Reference in New Issue
Block a user