Add better multiprocess capabilities to uxn11.
The changes are summarized in doc/console.txt.
This commit is contained in:
parent
897e0a5651
commit
f727d413bf
|
@ -0,0 +1,150 @@
|
|||
CONSOLE DEVICE
|
||||
|
||||
0x10 vector* 0x18 @stdout
|
||||
0x11 (vector) 0x19 @stderr
|
||||
0x12 stdin 0x1a @proc-put
|
||||
0x13 0x1b
|
||||
0x14 0x1c param*
|
||||
0x15 0x1d (param)
|
||||
0x16 0x1e opts
|
||||
0x17 type 0x1f @host-put
|
||||
|
||||
(* denotes a short register, i.e. two bytes wide)
|
||||
(@ denotes a register that causes an immediate effect)
|
||||
|
||||
vector ports (0x10-0x11) contain an address to jump to when input is
|
||||
available. when the vector fires one byte can be read from the read
|
||||
port (0x12), and its meaning is defined by the type (0x17).
|
||||
|
||||
the stdin port (0x12) contains one byte of data to be read. the byte
|
||||
represents input read from one of the following: arguments, stdin,
|
||||
child processes, or host messages. the type port (0x17) says which.
|
||||
|
||||
the type (0x17) field explains how to interpret calls to the console
|
||||
vector (0x10) and where data can be read from:
|
||||
|
||||
- 0x00 - 00000000 - no input (-)
|
||||
- 0x01 - 00000001 - stdin (stdin)
|
||||
- 0x02 - 00000010 - argument (stdin)
|
||||
- 0x03 - 00000011 - argument spacer (-)
|
||||
- 0x04 - 00000100 - argument end (-)
|
||||
- 0x05 - 00000101 - host response
|
||||
- 0x06 - 00000110 - host response end (-)
|
||||
- 0x07 - 00000111 - stdin end (-)
|
||||
- 0x20 - 00100000 - child 0 sent data
|
||||
- 0x21 - 00100001 - child 1 sent data
|
||||
- 0x22 - 00100010 - child 2 sent data
|
||||
- 0x23 - 00100011 - child 3 sent data
|
||||
- 0x40 - 01000000 - child 0 data end
|
||||
- 0x41 - 01000001 - child 1 data end
|
||||
- 0x42 - 01000010 - child 2 data end
|
||||
- 0x43 - 01000011 - child 3 data end
|
||||
- 0x80 - 10000000 - child 0 exited (stdin contains exit code)
|
||||
- 0x81 - 10000001 - child 1 exited (stdin contains exit code)
|
||||
- 0x82 - 10000010 - child 2 exited (stdin contains exit code)
|
||||
- 0x83 - 10000011 - child 3 exited (stdin contains exit code)
|
||||
|
||||
writing a byte to the stdout port (0x18) will send one byte of data to
|
||||
the emulator's stdout.
|
||||
|
||||
writing a byte to the stderr port (0x19) will send one byte of data to
|
||||
the emulator's stderr.
|
||||
|
||||
writing a byte to the proc-put port (0x1a) will send one byte of data
|
||||
to one of the emulator's child processes. the lower 2 bits of the opts
|
||||
port (0x1e) determine which one:
|
||||
|
||||
- 0x00: child 0
|
||||
- 0x01: child 1
|
||||
- 0x02: child 2
|
||||
- 0x03: child 3
|
||||
|
||||
the param ports (0x1c-0x1d) specify the a value to use as a parameter
|
||||
for a host-put (0x1f). the meaning values by host-put value:
|
||||
|
||||
- 0x00 - (nop) unused
|
||||
- 0x01 - (execute) command string (e.g. 'gcc -o "demo" demo.c')
|
||||
- 0x02 - (getpid) unused
|
||||
- 0x03 - (kill) unused
|
||||
- 0x04 - (raw-tty) unused
|
||||
- 0x04 - (restore-tty) unused
|
||||
- 0x10 - (getenv) name string (e.g. "TERM")
|
||||
- 0x11 - (setenv) assignment string (e.g. "TERM=vt100")
|
||||
|
||||
(strings must be null-terminated. commands are parsed by /bin/sh -c.)
|
||||
|
||||
the opts port (0x1e) specifies options that affect host actions run
|
||||
using the host-put port (0x1f):
|
||||
|
||||
- lower 2 bits control which child process to use (when applicable)
|
||||
+ 0x00 - child 0
|
||||
+ 0x01 - child 1
|
||||
+ 0x02 - child 2
|
||||
+ 0x03 - child 3
|
||||
- next 2 bits (0x0c) are unused
|
||||
- upper 4 bits control which pipes to use with execute:
|
||||
+ 0x80 - use child's pty (implies 0x70)
|
||||
+ 0x40 - read from child's stderr
|
||||
+ 0x20 - read from child's stdout
|
||||
+ 0x10 - write to child's stdin
|
||||
|
||||
the host-put port (0x1f) specifies which host action to take:
|
||||
|
||||
- 0x00 - nop: does nothing
|
||||
- 0x01 - execute: reads command string, starts a subprocess
|
||||
- 0x02 - getpid: responds with child process pid (if any)
|
||||
- 0x03 - kill: shuts down child process
|
||||
- 0x04 - put stdin tty into raw mode
|
||||
- 0x05 - restore stdin tty to canonical mode
|
||||
- 0x10 - getenv: looks up a name (e.g. TERM) in env, responds with value
|
||||
- 0x11 - setenv: reads an assignment (e.g. TERM=vt100), updates env
|
||||
|
||||
SYSTEM EXPANSION SUPPORT
|
||||
|
||||
the system expansion port can be used to determine whether or not the
|
||||
console supports these additional capabilities. the convention is to
|
||||
use UUID-based extensions as defined by uxn38. the unix console device
|
||||
uses `01231250-d878-4462-bc41-d0927645a2fa` as its UUID, and uses the
|
||||
following expansion layout:
|
||||
|
||||
- operation (1 byte): 03 (uuid extension)
|
||||
- device (1 byte): 10 (console)
|
||||
- uuid (16 bytes): 0123 1250 d878 4462 bc41 d092 7645 a2fa
|
||||
- version (1 byte): 00 (set to non-zero by supporting emulators)
|
||||
- flags (2 byte): 0000 (updated by supporting emulators)
|
||||
|
||||
after being loaded, version will be non-zero if console expansion is
|
||||
supported, and zero otherwise. flags will contain bits describing the
|
||||
available capabilities (0x0000 if console expansion is unsupported).
|
||||
the flags are as follows:
|
||||
|
||||
- 0x0001: supports `execute`
|
||||
- 0x0002: supports `kill`
|
||||
- 0x0004: supports `raw-tty`, `restore-tty`
|
||||
- 0x0008: supports `getenv` and `setenv`
|
||||
- 0x0010: supports writing to child stdin
|
||||
- 0x0020: supports reading from child stdout
|
||||
- 0x0040: supports reading from child stderr
|
||||
- 0x0080: supports allocating child pty
|
||||
- 0x0100 to 0x8000: (currently unused)
|
||||
|
||||
currently uxn11 will write 0x00ff to the flags field, so programs
|
||||
wishing to detect unx11 can check for that value. here's a snippet
|
||||
that determines whether the console expansion can be used:
|
||||
|
||||
|00 @System [ &unused $2 &expansion $2 ( ... ) ]
|
||||
|
||||
|0010
|
||||
;query .System/expansion DEO2
|
||||
;query/flags LDA2 #00ff EQU2 ?enable-uxn11-console
|
||||
BRK
|
||||
|
||||
@enable-uxn-console ...
|
||||
|
||||
@query 03 10 ( uuid ) 0123 1250 d878 4462 bc41 d092 7645 a2fa &flags 00 0000
|
||||
|
||||
note that uxn11 unconditionally enables the expanded console. so in
|
||||
this case the uuid-based extension mechanism is only used to detect
|
||||
the presence (or absence) of emulator support. if device 0x10 is not
|
||||
provided then flags will be 0x0000 (moving or adding additional
|
||||
console devices is not supported).
|
|
@ -1,11 +1,17 @@
|
|||
#undef _POSIX_C_SOURCE
|
||||
#define _POSIX_C_SOURCE 200112L
|
||||
|
||||
#ifdef __NetBSD__
|
||||
#define _NETBSD_SOURCE
|
||||
#endif
|
||||
|
||||
#include <poll.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/select.h>
|
||||
#include <sys/wait.h>
|
||||
#include <termios.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#ifdef __linux
|
||||
|
@ -14,6 +20,7 @@
|
|||
|
||||
#ifdef __NetBSD__
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/termios.h>
|
||||
#include <util.h>
|
||||
#endif
|
||||
|
||||
|
@ -31,54 +38,293 @@ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|||
WITH REGARD TO THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
/* process */
|
||||
static char *fork_args[32];
|
||||
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 pid_t child_pid;
|
||||
struct winsize ws = {24, 80, 8, 12};
|
||||
#define child_ptys(mode) (mode & CONSOLE_MODE_PTY)
|
||||
#define child_reads(mode) (mode & CONSOLE_CHILD_READS)
|
||||
#define child_writes(mode) (mode & CONSOLE_CHILD_WRITES)
|
||||
#define child_writes_out(mode) (mode & CONSOLE_MODE_OUT)
|
||||
#define child_writes_err(mode) (mode & CONSOLE_MODE_ERR)
|
||||
|
||||
static void
|
||||
parse_args(Uxn *u, Uint8 *d)
|
||||
UxnSubprocess children[CONSOLE_MAX_CHILDREN];
|
||||
static char *fork_args[4] = {"/bin/sh", "-c", "", NULL};
|
||||
static int to_child_fd[2], from_child_fd[2];
|
||||
static char buf[16];
|
||||
static int raw_tty = 0;
|
||||
struct termios old_termios;
|
||||
|
||||
static UxnSubprocess*
|
||||
find_child_by_pid(pid_t pid)
|
||||
{
|
||||
Uint8 *port_addr = d + 0x3;
|
||||
int addr = PEEK2(port_addr);
|
||||
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;
|
||||
for(int n = 0; n < CONSOLE_MAX_CHILDREN; n++)
|
||||
if(children[n].pid == pid)
|
||||
return &children[n];
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* call after we're sure the process has exited */
|
||||
static void
|
||||
clean_after_child(void)
|
||||
handle_sigchld(int sig)
|
||||
{
|
||||
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);
|
||||
pid_t pid;
|
||||
int n, wstatus;
|
||||
while((pid = waitpid(-1, &wstatus, WNOHANG)) > 0) {
|
||||
UxnSubprocess *child = find_child_by_pid(pid);
|
||||
if (child != NULL) {
|
||||
/* set running to negative when exiting for clean up */
|
||||
child->running = WEXITSTATUS(wstatus) - 256;
|
||||
/* close relevant file descriptors */
|
||||
if(child_ptys(child->mode))
|
||||
close(child->pty);
|
||||
else {
|
||||
if(child_reads(child->mode)) close(child->fd_in);
|
||||
if(child_writes(child->mode)) close(child->fd_out);
|
||||
}
|
||||
/* reset fields to starting values */
|
||||
child->pid = child->mode = 0;
|
||||
child->fd_out = child->fd_in = child->pty = -1;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
start_fork_pty(UxnSubprocess *child, int mode)
|
||||
{
|
||||
int fd = -1;
|
||||
struct winsize ws = {23, 80, 8, 12};
|
||||
pid_t pid = forkpty(&fd, NULL, NULL, &ws);
|
||||
if(pid < 0) { /* failure */
|
||||
fprintf(stderr, "parent fork failure\n");
|
||||
} else if(pid == 0) { /* child */
|
||||
execvp(fork_args[0], fork_args);
|
||||
fprintf(stderr, "child exec failure\n");
|
||||
} else { /*parent*/
|
||||
do {
|
||||
child->pid = pid;
|
||||
child->mode = mode;
|
||||
child->running = 1;
|
||||
child->pty = fd;
|
||||
child->fd_in = fd;
|
||||
child->fd_out = fd;
|
||||
} while (child->pid != pid);
|
||||
/* loop to avoid possible race with clean up */
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
start_fork_pipe(UxnSubprocess *child, int mode)
|
||||
{
|
||||
if(child_reads(mode)) {
|
||||
/* parent writes to child's stdin */
|
||||
if(pipe(to_child_fd) == -1) {
|
||||
fprintf(stderr, "pipe error: to child\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if(child_writes(mode)) {
|
||||
/* parent reads from child's stdout and/or stderr */
|
||||
if(pipe(from_child_fd) == -1) {
|
||||
fprintf(stderr, "pipe error: from child\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
pid_t pid = fork();
|
||||
if(pid < 0) { /* failure */
|
||||
fprintf(stderr, "fork failure\n");
|
||||
return;
|
||||
} else if(pid == 0) { /* child */
|
||||
if(child_reads(mode)) {
|
||||
dup2(to_child_fd[0], 0);
|
||||
close(to_child_fd[1]);
|
||||
}
|
||||
if(child_writes(mode)) {
|
||||
if(child_writes_out(mode)) dup2(from_child_fd[1], 1);
|
||||
if(child_writes_err(mode)) dup2(from_child_fd[1], 2);
|
||||
close(from_child_fd[0]);
|
||||
}
|
||||
execvp(fork_args[0], fork_args);
|
||||
fprintf(stderr, "child exec failure\n");
|
||||
exit(1);
|
||||
} else { /*parent*/
|
||||
do {
|
||||
child->pid = pid;
|
||||
child->mode = mode;
|
||||
child->running = 1;
|
||||
child->pty = -1;
|
||||
child->fd_in = child_reads(mode) ? to_child_fd[1] : -1;
|
||||
child->fd_out = child_writes(mode) ? from_child_fd[0] : -1;
|
||||
} while (child->pid != pid);
|
||||
/* loop to avoid possible race with clean up */
|
||||
|
||||
/* close the unused end of pipes we opened */
|
||||
if(child_reads(mode)) close(to_child_fd[0]);
|
||||
if(child_writes(mode)) close(from_child_fd[1]);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
std_put(Uint8 c, FILE *fd)
|
||||
{
|
||||
fputc(c, fd);
|
||||
fflush(fd);
|
||||
}
|
||||
|
||||
static void
|
||||
proc_put(Uxn *u, Uint8 *d)
|
||||
{
|
||||
Uint8 c = d[0xa];
|
||||
int n = d[0xe] & 0x3;
|
||||
write(children[n].fd_in, &c, 1);
|
||||
fdatasync(children[n].fd_in);
|
||||
}
|
||||
|
||||
static void
|
||||
host_execute(Uxn *u, Uint8 *d)
|
||||
{
|
||||
int addr = PEEK2(d + 0xc);
|
||||
int opts = d[0xe];
|
||||
int mode = opts & 0xf0;
|
||||
char *cmd = (char *)&u->ram[addr];
|
||||
UxnSubprocess *child = &children[opts & 0x3];
|
||||
|
||||
fork_args[2] = cmd;
|
||||
if(child->pid > 0)
|
||||
kill(child->pid, 9);
|
||||
|
||||
if(opts >= 0x80)
|
||||
start_fork_pty(child, mode);
|
||||
else
|
||||
start_fork_pipe(child, mode);
|
||||
}
|
||||
|
||||
static void
|
||||
host_response(Uxn *u, char *s)
|
||||
{
|
||||
for(int i = 0;; i++)
|
||||
console_input(u, s[i], CONSOLE_HOST);
|
||||
console_input(u, '\0', CONSOLE_HOST_END);
|
||||
}
|
||||
|
||||
static void
|
||||
host_getpid(Uxn *u, Uint8 *d)
|
||||
{
|
||||
int n = d[0xe] & 0x3;
|
||||
snprintf(buf, 16, "%d", children[n].pid);
|
||||
host_response(u, buf);
|
||||
}
|
||||
|
||||
static void
|
||||
host_kill(Uxn *u, Uint8 *d)
|
||||
{
|
||||
int n = d[0xe] & 0x3;
|
||||
if(children[n].pid > 0)
|
||||
kill(children[n].pid, 15);
|
||||
}
|
||||
|
||||
static void
|
||||
host_raw_tty()
|
||||
{
|
||||
if(!raw_tty) {
|
||||
tcgetattr(STDIN_FILENO, &old_termios);
|
||||
struct termios term = old_termios;
|
||||
/* corresponds to `stty raw -echo` */
|
||||
term.c_cflag |= (CS8);
|
||||
term.c_iflag &= ~(IGNBRK | BRKINT | IGNPAR | PARMRK | INPCK | ISTRIP);
|
||||
term.c_iflag &= ~(INLCR | IGNCR | ICRNL | IXON | IXOFF | IXANY | IMAXBEL);
|
||||
term.c_lflag &= ~(ICANON | ISIG | ECHO);
|
||||
term.c_oflag &= ~(OPOST);
|
||||
term.c_cc[VMIN] = 0;
|
||||
term.c_cc[VTIME] = 1;
|
||||
tcsetattr(0, TCSAFLUSH, &term);
|
||||
raw_tty = 1;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
host_restore_tty()
|
||||
{
|
||||
if(raw_tty) {
|
||||
tcsetattr(0, TCSAFLUSH, &old_termios);
|
||||
raw_tty = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
host_getenv(Uxn *u, Uint8 *d)
|
||||
{
|
||||
int addr = PEEK2(d + 0xc);
|
||||
char *name = (char *)&u->ram[addr];
|
||||
host_response(u, getenv(name));
|
||||
}
|
||||
|
||||
static void
|
||||
host_setenv(Uxn *u, Uint8 *d)
|
||||
{
|
||||
int addr = PEEK2(d + 0xc);
|
||||
char *name = (char *)&u->ram[addr];
|
||||
int i = 0;
|
||||
while(name[i] != '=') i++;
|
||||
if(name[i] == '=') {
|
||||
name[i] = '\0';
|
||||
char *value = &name[i+1];
|
||||
setenv(name, value, 1);
|
||||
} else {
|
||||
fprintf(stderr, "mal-formed setenv\n");
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
host_put(Uxn *u, Uint8 *d)
|
||||
{
|
||||
switch(d[0xf]) {
|
||||
case 0x00: /* nop */ break;
|
||||
case 0x01: host_execute(u, d); break;
|
||||
case 0x02: host_getpid(u, d); break;
|
||||
case 0x03: host_kill(u, d); break;
|
||||
case 0x04: host_raw_tty(); break;
|
||||
case 0x05: host_restore_tty(); break;
|
||||
case 0x10: host_getenv(u, d); break;
|
||||
case 0x11: host_setenv(u, d); break;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
init_console(void)
|
||||
{
|
||||
/* set up signal handler for SIGCHLD */
|
||||
struct sigaction sa;
|
||||
sigemptyset(&sa.sa_mask);
|
||||
sa.sa_flags = 0;
|
||||
sa.sa_handler = handle_sigchld;
|
||||
sigaction(SIGCHLD, &sa, NULL);
|
||||
|
||||
/* initialize (halted) children */
|
||||
for(int i = 0; i < CONSOLE_MAX_CHILDREN; i++) {
|
||||
UxnSubprocess *child = &children[i];
|
||||
child->id = i;
|
||||
child->pid = child->mode = child->running = 0;
|
||||
child->fd_in = child->fd_out = child->pty = -1;
|
||||
}
|
||||
}
|
||||
|
||||
UxnSubprocess
|
||||
*get_child(int n)
|
||||
{
|
||||
return &children[n & 0x3];
|
||||
}
|
||||
|
||||
void
|
||||
console_monitor(Uxn *u)
|
||||
{
|
||||
/*fflush(stdout);*/ /* TODO: needed? stderr? children? */
|
||||
for(int n = 0; n < CONSOLE_MAX_CHILDREN; n++) {
|
||||
UxnSubprocess *child = &children[n];
|
||||
if(child->running < 0) {
|
||||
Uint8 status = child->running + 256;
|
||||
child->running = 0;
|
||||
console_input(u, status, CONSOLE_CHILD_EXIT | n);
|
||||
}
|
||||
}
|
||||
child_mode = 0;
|
||||
saved_in = -1;
|
||||
saved_out = -1;
|
||||
}
|
||||
|
||||
int
|
||||
|
@ -96,137 +342,24 @@ console_listen(Uxn *u, int i, int argc, char **argv)
|
|||
for(; i < argc; i++) {
|
||||
char *p = argv[i];
|
||||
while(*p) console_input(u, *p++, CONSOLE_ARG);
|
||||
console_input(u, '\n', i == argc - 1 ? CONSOLE_END : CONSOLE_EOA);
|
||||
console_input(u, '\n', i == argc - 1 ? CONSOLE_ARG_END : CONSOLE_ARG_SPACER);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
start_fork_pty(Uint8 *d)
|
||||
{
|
||||
int fd = -1;
|
||||
pid_t pid = forkpty(&fd, NULL, NULL, NULL);
|
||||
if(pid < 0) { /* failure */
|
||||
d[0x6] = 0xff;
|
||||
fprintf(stderr, "fork failure\n");
|
||||
} else if(pid == 0) { /* child */
|
||||
setenv("TERM", "ansi", 1);
|
||||
execvp(fork_args[0], fork_args);
|
||||
d[0x6] = 0xff;
|
||||
fprintf(stderr, "exec failure\n");
|
||||
} else { /*parent*/
|
||||
child_pid = pid;
|
||||
pty_fd = fd;
|
||||
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)
|
||||
{
|
||||
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\n");
|
||||
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: from child\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
pid_t pid = fork();
|
||||
if(pid < 0) { /* failure */
|
||||
d[0x6] = 0xff;
|
||||
fprintf(stderr, "fork failure\n");
|
||||
} 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\n");
|
||||
} 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
|
||||
kill_child(Uint8 *d, int options)
|
||||
{
|
||||
if(child_pid) {
|
||||
kill(child_pid, 9);
|
||||
int wstatus;
|
||||
if(waitpid(child_pid, &wstatus, options)) {
|
||||
d[0x6] = 1;
|
||||
d[0x7] = WEXITSTATUS(wstatus);
|
||||
clean_after_child();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
start_fork(Uxn *u, Uint8 *d)
|
||||
{
|
||||
fflush(stderr);
|
||||
kill_child(d, 0);
|
||||
child_mode = d[0x5];
|
||||
parse_args(u, d);
|
||||
if(child_mode >= 0x80)
|
||||
start_fork_pty(d);
|
||||
else
|
||||
start_fork_pipe(d);
|
||||
}
|
||||
|
||||
Uint8
|
||||
console_dei(Uxn *u, Uint8 addr)
|
||||
{
|
||||
Uint8 port = addr & 0x0f, *d = &u->dev[addr & 0xf0];
|
||||
switch(port) {
|
||||
case 0x6:
|
||||
case 0x7: kill_child(d, WNOHANG);
|
||||
}
|
||||
return d[port];
|
||||
}
|
||||
|
||||
void
|
||||
console_deo(Uxn *u, Uint8 *d, Uint8 port)
|
||||
{
|
||||
FILE *fd = NULL;
|
||||
switch(port) {
|
||||
case 0x5: /* Console/dead */ start_fork(u, d); break;
|
||||
case 0x6: /* Console/exit*/ kill_child(d, 0); break;
|
||||
case 0x8: fd = stdout; break;
|
||||
case 0x9: fd = stderr; break;
|
||||
}
|
||||
if(fd) {
|
||||
fputc(d[port], fd);
|
||||
fflush(fd);
|
||||
case 0x8: std_put(d[port], stdout); break;
|
||||
case 0x9: std_put(d[port], stderr); break;
|
||||
case 0xa: proc_put(u, d); break;
|
||||
case 0xf: host_put(u, d); break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,12 +11,47 @@ WITH REGARD TO THIS SOFTWARE.
|
|||
|
||||
#define CONSOLE_VERSION 1
|
||||
|
||||
#define CONSOLE_STD 0x1
|
||||
#define CONSOLE_ARG 0x2
|
||||
#define CONSOLE_EOA 0x3
|
||||
#define CONSOLE_END 0x4
|
||||
#define CONSOLE_NONE 0x00
|
||||
#define CONSOLE_STDIN 0x01
|
||||
#define CONSOLE_ARG 0x02
|
||||
#define CONSOLE_ARG_SPACER 0x03
|
||||
#define CONSOLE_ARG_END 0x04
|
||||
#define CONSOLE_HOST 0x05
|
||||
#define CONSOLE_HOST_END 0x06
|
||||
#define CONSOLE_STDIN_END 0x07
|
||||
|
||||
#define CONSOLE_CHILD_DATA 0x20 /* write from child-n: 0x20 | n */
|
||||
#define CONSOLE_CHILD_END 0x40 /* close from child-n: 0x40 | n */
|
||||
#define CONSOLE_CHILD_EXIT 0x80 /* exits from child-n: 0x80 | n */
|
||||
|
||||
typedef struct UxnSubprocess {
|
||||
int id; /* identifier: 0-3 */
|
||||
pid_t pid; /* when running, pid > 0 */
|
||||
int mode; /* bit flags: CONSOLE_MODE_ */
|
||||
int running; /* 0=stopped, >0 running, <0 exiting */
|
||||
int fd_in; /* fd to send to child; -1 is unset */
|
||||
int fd_out; /* fd to read from child; -1 is unset */
|
||||
int pty; /* pty for child; -1 is unset */
|
||||
} UxnSubprocess;
|
||||
|
||||
void init_console(void);
|
||||
UxnSubprocess *get_child(int n);
|
||||
void console_monitor(Uxn *u);
|
||||
int console_input(Uxn *u, char c, int type);
|
||||
void console_listen(Uxn *u, int i, int argc, char **argv);
|
||||
Uint8 console_dei(Uxn *u, Uint8 addr);
|
||||
void console_deo(Uxn *u, Uint8 *d, Uint8 port);
|
||||
|
||||
#define CONSOLE_MODE_PTY 0x80
|
||||
#define CONSOLE_MODE_ERR 0x40
|
||||
#define CONSOLE_MODE_OUT 0x20
|
||||
#define CONSOLE_MODE_INP 0x10
|
||||
|
||||
/* CONSOLE_MODE_PTY | CONSOLE_MODE_INP */
|
||||
#define CONSOLE_CHILD_READS 0x90
|
||||
|
||||
/* CONSOLE_MODE_PTY | CONSOLE_MODE_OUT | CONSOLE_MODE_ERR */
|
||||
#define CONSOLE_CHILD_WRITES 0xe0
|
||||
|
||||
#define CONSOLE_MAX_CHILDREN 4
|
||||
|
||||
|
|
|
@ -100,6 +100,45 @@ system_boot(Uxn *u, Uint8 *ram, char *rom)
|
|||
return 1;
|
||||
}
|
||||
|
||||
static void
|
||||
system_expansion_copy(Uxn *u, Uint8 *ram, Uint16 addr)
|
||||
{
|
||||
Uint16 i, length = PEEK2(ram + addr + 1);
|
||||
Uint16 a_page = PEEK2(ram + addr + 1 + 2), a_addr = PEEK2(ram + addr + 1 + 4);
|
||||
Uint16 b_page = PEEK2(ram + addr + 1 + 6), b_addr = PEEK2(ram + addr + 1 + 8);
|
||||
int src = (a_page % RAM_PAGES) * 0x10000, dst = (b_page % RAM_PAGES) * 0x10000;
|
||||
for(i = 0; i < length; i++)
|
||||
ram[dst + (Uint16)(b_addr + i)] = ram[src + (Uint16)(a_addr + i)];
|
||||
}
|
||||
|
||||
static int
|
||||
uuid_eq(Uint8 *m, Uint8 uuid[16])
|
||||
{
|
||||
for(int i = 0; i < 16; i++)
|
||||
if (m[i] != uuid[i])
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* console device: 01231250-d878-4462-bc41-d0927645a2fa */
|
||||
static Uint8 console_uuid[16] = {
|
||||
0x01, 0x23, 0x12, 0x50, 0xd8, 0x78, 0x44, 0x62,
|
||||
0xbc, 0x41, 0xd0, 0x92, 0x76, 0x45, 0xa2, 0xfa
|
||||
};
|
||||
|
||||
static void
|
||||
system_expansion_uxn38_device(Uxn *u, Uint8 *ram, Uint16 addr)
|
||||
{
|
||||
Uint8 dev_id = ram[addr + 1];
|
||||
Uint8 *uuid = &ram[addr + 2];
|
||||
if(uuid_eq(uuid, console_uuid) != 0) {
|
||||
int ok = dev_id == 0x10 ? 0xff : 0x00;
|
||||
ram[addr + 18] = ok;
|
||||
ram[addr + 19] = 0x00;
|
||||
ram[addr + 20] = ok;
|
||||
}
|
||||
}
|
||||
|
||||
/* IO */
|
||||
|
||||
Uint8
|
||||
|
@ -121,14 +160,10 @@ system_deo(Uxn *u, Uint8 *d, Uint8 port)
|
|||
case 0x3:
|
||||
ram = u->ram;
|
||||
addr = PEEK2(d + 2);
|
||||
if(ram[addr] == 0x1) {
|
||||
Uint16 i, length = PEEK2(ram + addr + 1);
|
||||
Uint16 a_page = PEEK2(ram + addr + 1 + 2), a_addr = PEEK2(ram + addr + 1 + 4);
|
||||
Uint16 b_page = PEEK2(ram + addr + 1 + 6), b_addr = PEEK2(ram + addr + 1 + 8);
|
||||
int src = (a_page % RAM_PAGES) * 0x10000, dst = (b_page % RAM_PAGES) * 0x10000;
|
||||
for(i = 0; i < length; i++)
|
||||
ram[dst + (Uint16)(b_addr + i)] = ram[src + (Uint16)(a_addr + i)];
|
||||
}
|
||||
if(ram[addr] == 0x1)
|
||||
system_expansion_copy(u, ram, addr);
|
||||
else if(ram[addr] == 0x3)
|
||||
system_expansion_uxn38_device(u, ram, addr);
|
||||
break;
|
||||
case 0x4:
|
||||
u->wst.ptr = d[4];
|
||||
|
|
47
src/uxn11.c
47
src/uxn11.c
|
@ -1,8 +1,10 @@
|
|||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <X11/Xlib.h>
|
||||
#include <X11/Xutil.h>
|
||||
#include <X11/keysymdef.h>
|
||||
#include <string.h>
|
||||
#include <sys/timerfd.h>
|
||||
#include <unistd.h>
|
||||
#include <poll.h>
|
||||
|
@ -102,6 +104,7 @@ emu_end(Uxn *u)
|
|||
XDestroyImage(ximage);
|
||||
XDestroyWindow(display, window);
|
||||
XCloseDisplay(display);
|
||||
kill(0, 15);
|
||||
exit(0);
|
||||
return u->dev[0x0f] & 0x7f;
|
||||
}
|
||||
|
@ -131,6 +134,23 @@ toggle_scale(void)
|
|||
screen_resize(uxn_screen.width, uxn_screen.height, s);
|
||||
}
|
||||
|
||||
/* returns true if the fd ended (has been closed), false otherwise */
|
||||
static int
|
||||
handle_input(Uxn *u, struct pollfd pollfd, char *coninp, int argdata, int argend)
|
||||
{
|
||||
if((pollfd.revents & POLLIN) != 0) {
|
||||
int n = read(pollfd.fd, coninp, CONINBUFSIZE - 1);
|
||||
coninp[n] = 0;
|
||||
if(n == 0)
|
||||
console_input(u, 0, argend);
|
||||
else
|
||||
for(int i = 0; i < n; i++)
|
||||
console_input(u, coninp[i], argdata);
|
||||
return n == 0;
|
||||
} else
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
emu_event(Uxn *u)
|
||||
{
|
||||
|
@ -200,6 +220,7 @@ display_init(void)
|
|||
if(!display)
|
||||
return system_error("init", "Display failed");
|
||||
screen_resize(WIDTH, HEIGHT, 1);
|
||||
init_console();
|
||||
/* start window */
|
||||
visual = DefaultVisual(display, 0);
|
||||
if(visual->class != TrueColor)
|
||||
|
@ -224,9 +245,10 @@ static int
|
|||
emu_run(Uxn *u, char *rom)
|
||||
{
|
||||
int i = 1, n;
|
||||
int *child_fd_outs;
|
||||
char expirations[8];
|
||||
char coninp[CONINBUFSIZE];
|
||||
struct pollfd fds[3];
|
||||
struct pollfd fds[3 + CONSOLE_MAX_CHILDREN];
|
||||
static const struct itimerspec screen_tspec = {{0, 16666666}, {0, 16666666}};
|
||||
/* timer */
|
||||
fds[0].fd = XConnectionNumber(display);
|
||||
|
@ -236,7 +258,11 @@ emu_run(Uxn *u, char *rom)
|
|||
fds[0].events = fds[1].events = fds[2].events = POLLIN;
|
||||
/* main loop */
|
||||
while(!u->dev[0x0f]) {
|
||||
if(poll(fds, 3, 1000) <= 0)
|
||||
for(i = 0; i < CONSOLE_MAX_CHILDREN; i++) {
|
||||
fds[i + 3].fd = get_child(i)->fd_out;
|
||||
fds[i + 3].events = POLLIN;
|
||||
}
|
||||
if(poll(fds, 3 + CONSOLE_MAX_CHILDREN, 1000) <= 0)
|
||||
continue;
|
||||
while(XPending(display))
|
||||
emu_event(u);
|
||||
|
@ -250,13 +276,18 @@ emu_run(Uxn *u, char *rom)
|
|||
XPutImage(display, window, DefaultGC(display, 0), ximage, x, y, x + PAD, y + PAD, w, h);
|
||||
}
|
||||
}
|
||||
if((fds[2].revents & POLLIN) != 0) {
|
||||
n = read(fds[2].fd, coninp, CONINBUFSIZE - 1);
|
||||
coninp[n] = 0;
|
||||
for(i = 0; i < n; i++)
|
||||
console_input(u, coninp[i], CONSOLE_STD);
|
||||
}
|
||||
|
||||
/* read input from stdin */
|
||||
handle_input(u, fds[2], coninp, CONSOLE_STDIN, CONSOLE_STDIN_END);
|
||||
|
||||
/* read input from child processes */
|
||||
for(i = 0; i < CONSOLE_MAX_CHILDREN; i++)
|
||||
handle_input(u, fds[i + 3], coninp, CONSOLE_CHILD_DATA | i, CONSOLE_CHILD_END | i);
|
||||
|
||||
/* check to see if any children exited */
|
||||
console_monitor(u);
|
||||
}
|
||||
kill(0, 15);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
|
57
src/uxncli.c
57
src/uxncli.c
|
@ -1,5 +1,8 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/select.h>
|
||||
#include <sys/time.h>
|
||||
|
||||
#include "uxn.h"
|
||||
#include "devices/system.h"
|
||||
|
@ -42,16 +45,59 @@ emu_deo(Uxn *u, Uint8 addr, Uint8 value)
|
|||
}
|
||||
}
|
||||
|
||||
static void
|
||||
handle_input(Uxn *u, int fd, int port, int argdata, int argend)
|
||||
{
|
||||
char buf[32];
|
||||
int n = read(fd, &buf, 32);
|
||||
if(n == 0)
|
||||
console_input(u, 0x00, argend);
|
||||
else
|
||||
for(int i = 0; i < n; i++)
|
||||
console_input(u, buf[i], argdata);
|
||||
}
|
||||
|
||||
static int
|
||||
init_rfds(fd_set *rfds)
|
||||
{
|
||||
int i, nfds;
|
||||
FD_ZERO(rfds);
|
||||
FD_SET(0, rfds);
|
||||
nfds = 1;
|
||||
for(i = 0; i < 4; i++) {
|
||||
UxnSubprocess *child = get_child(i);
|
||||
int fd = child->fd_out;
|
||||
if (fd >= 0) {
|
||||
FD_SET(fd, rfds);
|
||||
if (fd >= nfds) nfds = fd + 1;
|
||||
}
|
||||
}
|
||||
return nfds;
|
||||
}
|
||||
|
||||
static void
|
||||
emu_run(Uxn *u)
|
||||
{
|
||||
int i;
|
||||
struct timeval tv;
|
||||
fd_set rfds;
|
||||
while(!u->dev[0x0f]) {
|
||||
int c = fgetc(stdin);
|
||||
if(c == EOF) {
|
||||
console_input(u, 0x00, CONSOLE_END);
|
||||
break;
|
||||
int nfds = init_rfds(&rfds);
|
||||
/* tv.tv_sec = 0; */
|
||||
tv.tv_sec = 0;
|
||||
tv.tv_usec = 1000;
|
||||
int retval = select(nfds, &rfds, NULL, NULL, &tv);
|
||||
if(retval > 0) {
|
||||
/* printf("got something!\n"); */
|
||||
if (FD_ISSET(0, &rfds))
|
||||
handle_input(u, 0, 0x2, CONSOLE_STDIN, CONSOLE_STDIN_END);
|
||||
for(i = 0; i < 4; i++) {
|
||||
int fd = get_child(i)->fd_out;
|
||||
if (fd > 2 && FD_ISSET(fd, &rfds))
|
||||
handle_input(u, fd, 0x4, CONSOLE_CHILD_DATA | i, CONSOLE_CHILD_END | i);
|
||||
}
|
||||
}
|
||||
console_input(u, (Uint8)c, CONSOLE_STD);
|
||||
console_monitor(u);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -76,6 +122,7 @@ main(int argc, char **argv)
|
|||
return system_error("Init", "Failed to initialize uxn.");
|
||||
/* Game Loop */
|
||||
u.dev[0x17] = argc - i;
|
||||
init_console();
|
||||
if(uxn_eval(&u, PAGE_PROGRAM) && PEEK2(u.dev + 0x10)) {
|
||||
console_listen(&u, i, argc, argv);
|
||||
emu_run(&u);
|
||||
|
|
Loading…
Reference in New Issue