Add better multiprocess capabilities to uxn11.

The changes are summarized in doc/console.txt.
This commit is contained in:
~d6 2024-02-21 12:29:35 -05:00
parent 897e0a5651
commit f727d413bf
6 changed files with 614 additions and 183 deletions

150
doc/console.txt Normal file
View File

@ -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).

View File

@ -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;
}
}

View File

@ -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

View File

@ -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];

View File

@ -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;
}

View File

@ -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);