diff --git a/build.sh b/build.sh index ae2e905..740bf6f 100755 --- a/build.sh +++ b/build.sh @@ -60,6 +60,8 @@ then clang-format -i src/devices/controller.c clang-format -i src/devices/datetime.h clang-format -i src/devices/datetime.c + clang-format -i src/devices/console.h + clang-format -i src/devices/console.c clang-format -i src/uxnasm.c clang-format -i src/uxnemu.c clang-format -i src/uxncli.c @@ -83,7 +85,7 @@ Darwin) # macOS UXNEMU_LDFLAGS="$(brew --prefix)/lib/libSDL2.a $(sdl2-config --cflags --static-libs | sed -e 's/-lSDL2 //')" ;; Linux|*) - UXNEMU_LDFLAGS="-L/usr/local/lib $(sdl2-config --cflags --libs)" + UXNEMU_LDFLAGS="-L/usr/local/lib $(sdl2-config --cflags --libs) -lutil" ;; esac @@ -99,7 +101,7 @@ fi echo "Building.." ${CC} ${CFLAGS} src/uxnasm.c -o bin/uxnasm -${CC} ${CFLAGS} ${CORE} src/devices/system.c src/devices/file.c src/devices/datetime.c src/devices/mouse.c src/devices/controller.c src/devices/screen.c src/devices/audio.c src/uxnemu.c ${UXNEMU_LDFLAGS} ${FILE_LDFLAGS} -o bin/uxnemu +${CC} ${CFLAGS} ${CORE} src/devices/system.c src/devices/file.c src/devices/datetime.c src/devices/mouse.c src/devices/controller.c src/devices/screen.c src/devices/audio.c src/devices/console.c src/uxnemu.c ${UXNEMU_LDFLAGS} ${FILE_LDFLAGS} -o bin/uxnemu ${CC} ${CFLAGS} ${CORE} src/devices/system.c src/devices/file.c src/devices/datetime.c src/uxncli.c ${FILE_LDFLAGS} -o bin/uxncli if [ $install = 1 ] diff --git a/src/devices/console.c b/src/devices/console.c new file mode 100644 index 0000000..eda58ea --- /dev/null +++ b/src/devices/console.c @@ -0,0 +1,212 @@ +/* +Copyright (c) 2023 d_m + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE. +*/ + +#undef _POSIX_C_SOURCE +#define _POSIX_C_SOURCE 200112L + +#include "../uxn.h" + +#include +#include + +#include +#include +#include +#include +#include + +/* process */ +static char *fork_args[32]; +static pid_t child_pid; +static int child_mode; +static int pty_fd; +static int to_child_fd[2]; +static int from_child_fd[2]; +static int saved_in; +static int saved_out; + +static void +parse_args(Uint8 *d, Uxn *u) +{ + int addr = (d[0x3] << 8) | d[0x4]; + char *pos = (char *)&u->ram[addr]; + int i = 0; + do { + fork_args[i++] = pos; + while (*pos != 0) pos++; + pos++; + } while (*pos != '\0'); + fork_args[i] = NULL; +} + +/* call after we're sure the process has exited */ +static void +clean_after_child() +{ + child_pid = 0; + if (child_mode >= 0x80) { + close(pty_fd); + dup2(saved_in, 0); + dup2(saved_out, 1); + } else { + if (child_mode & 0x01) { + close(to_child_fd[1]); + dup2(saved_out, 1); + } + if (child_mode & 0x06) { + close(from_child_fd[0]); + dup2(saved_in, 0); + } + } + child_mode = 0; + saved_in = -1; + saved_out = -1; +} + +static void +kill_child(Uint8 *d) +{ + if (child_pid) { + kill(child_pid, 9); + int wstatus; + if (waitpid(child_pid, &wstatus, 0)) { + d[0x6] = 1; + d[0x7] = WEXITSTATUS(wstatus); + clean_after_child(); + } + } +} + +static void +start_fork_pty(Uint8 *d, Uxn *u) +{ + int fd = -1; + pid_t pid = forkpty(&fd, NULL, NULL, NULL); + if (pid < 0) { /* failure */ + d[0x6] = 0xff; + fprintf(stderr, "fork failure"); + } else if (pid == 0) { /* child */ + setenv("TERM", "ansi", 1); + execvp(fork_args[0], fork_args); + d[0x6] = 0xff; + fprintf(stderr, "exec failure"); + } else { /*parent*/ + child_pid = pid; + pty_fd = fd; + struct winsize ws = {24, 80, 8, 12}; + ioctl(fd, TIOCSWINSZ, &ws); + saved_in = dup(0); + saved_out = dup(1); + dup2(fd, 0); + dup2(fd, 1); + } +} + +static void +start_fork_pipe(Uint8 *d, Uxn *u) +{ + if (child_mode & 0x01) { + /* parent writes to child's stdin */ + if (pipe(to_child_fd) == -1) { + d[0x6] = 0xff; + fprintf(stderr, "pipe error: to child"); + return; + } + } + + if (child_mode & 0x06) { + /* parent reads from child's stdout and/or stderr */ + if (pipe(from_child_fd) == -1) { + d[0x6] = 0xff; + fprintf(stderr, "pipe error: to child"); + return; + } + } + + pid_t pid = fork(); + if (pid < 0) { /* failure */ + d[0x6] = 0xff; + fprintf(stderr, "fork failure"); + } else if (pid == 0) { /* child */ + if (child_mode & 0x01) { + dup2(to_child_fd[0], 0); + close(to_child_fd[1]); + } + if (child_mode & 0x06) { + if (child_mode & 0x02) dup2(from_child_fd[1], 1); + if (child_mode & 0x04) dup2(from_child_fd[1], 2); + close(from_child_fd[0]); + } + execvp(fork_args[0], fork_args); + d[0x6] = 0xff; + fprintf(stderr, "exec failure"); + } else { /*parent*/ + child_pid = pid; + if (child_mode & 0x01) { + saved_out = dup(1); + dup2(to_child_fd[1], 1); + close(to_child_fd[0]); + } + if (child_mode & 0x06) { + saved_in = dup(0); + dup2(from_child_fd[0], 0); + close(from_child_fd[1]); + } + } +} + +static void +start_fork(Uint8 *d, Uxn *u) +{ + kill_child(d); + child_mode = d[0x5]; + parse_args(d, u); + if (child_mode >= 0x80) { + start_fork_pty(d, u); + } else { + start_fork_pipe(d, u); + } +} + +Uint8 +console_dei(Uint8 *d, Uint8 port) +{ + switch(port) { + case 0x6: + case 0x7: + if (child_pid) { + int wstatus; + if (waitpid(child_pid, &wstatus, WNOHANG)) { + d[0x6] = 1; + d[0x7] = WEXITSTATUS(wstatus); + clean_after_child(); + } + } + } + return d[port]; +} + +void +console_deo(Uint8 *d, Uint8 port, Uxn *u) +{ + FILE *fd = NULL; + switch(port) { + case 0x5: start_fork(d, u); break; + case 0x6: kill_child(d); break; + case 0x8: fd = stdout; break; + case 0x9: fd = stderr; break; + } + + if(fd) { + fputc(d[port], fd); + fflush(fd); + } +} diff --git a/src/devices/console.h b/src/devices/console.h new file mode 100644 index 0000000..3eb8e19 --- /dev/null +++ b/src/devices/console.h @@ -0,0 +1,13 @@ +/* +Copyright (c) 2023 d_m + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE. +*/ + +Uint8 console_dei(Uint8 *d, Uint8 port); +void console_deo(Uint8 *d, Uint8 port, Uxn *u); diff --git a/src/uxnemu.c b/src/uxnemu.c index 39c4300..7291d76 100644 --- a/src/uxnemu.c +++ b/src/uxnemu.c @@ -16,12 +16,15 @@ #include "devices/controller.h" #include "devices/mouse.h" #include "devices/datetime.h" +#include "devices/console.h" #ifdef _WIN32 #include #endif #pragma GCC diagnostic pop #pragma clang diagnostic pop +#include + /* Copyright (c) 2021-2023 Devine Lu Linvega, Andrew Alderwick @@ -70,17 +73,6 @@ console_input(Uxn *u, char c) return uxn_eval(u, GETVEC(d)); } -static void -console_deo(Uint8 *d, Uint8 port) -{ - FILE *fd = port == 0x8 ? stdout : port == 0x9 ? stderr - : 0; - if(fd) { - fputc(d[port], fd); - fflush(fd); - } -} - static Uint8 audio_dei(int instance, Uint8 *d, Uint8 port) { @@ -109,6 +101,7 @@ emu_dei(Uxn *u, Uint8 addr) { Uint8 p = addr & 0x0f, d = addr & 0xf0; switch(d) { + case 0x10: return console_dei(&u->dev[d], p); case 0x20: return screen_dei(&u->dev[d], p); case 0x30: return audio_dei(0, &u->dev[d], p); case 0x40: return audio_dei(1, &u->dev[d], p); @@ -133,7 +126,7 @@ emu_deo(Uxn *u, Uint8 addr, Uint8 v) if(p > 0x7 && p < 0xe) screen_palette(&uxn_screen, &u->dev[0x8]); break; - case 0x10: console_deo(&u->dev[d], p); break; + case 0x10: console_deo(&u->dev[d], p, u); break; case 0x20: screen_deo(u->ram, &u->dev[d], p); break; case 0x30: audio_deo(0, &u->dev[d], p, u); break; case 0x40: audio_deo(1, &u->dev[d], p, u); break; @@ -171,9 +164,23 @@ static int stdin_handler(void *p) { SDL_Event event; + fd_set rds; + struct timeval tv; + int retval; + int ok = 1; event.type = stdin_event; - while(read(0, &event.cbutton.button, 1) > 0 && SDL_PushEvent(&event) >= 0) - ; + while(ok) { + FD_ZERO(&rds); + FD_SET(0, &rds); /* read from stdin */ + tv.tv_sec = 0; + tv.tv_usec = 100000; /* wait 100ms */ + retval = select(1, &rds, NULL, NULL, &tv); + if (retval > 0) { + ok = read(0, &event.cbutton.button, 1) > 0 && SDL_PushEvent(&event) >= 0; + } else if (retval < 0) { + ok = 0; + } + } return 0; (void)p; }