main pita/uxn12 / etc / alt / uxn11-openbsd.c
   1#include <sys/types.h>
   2#include <sys/event.h>
   3#include <sys/time.h>
   4#include <stdio.h>
   5#include <stdlib.h>
   6#include <unistd.h>
   7#include <string.h>
   8#include <X11/Xlib.h>
   9#include <X11/Xutil.h>
  10#include <X11/keysymdef.h>
  11
  12/*
  13Copyright (c) 2021-2026 Devine Lu Linvega, Andrew Alderwick,
  14Andrew Richards, Eiríkr Åsheim, Sigrid Solveig Haflínudóttir,
  15Lobo Torres
  16
  17Permission to use, copy, modify, and distribute this software for any
  18purpose with or without fee is hereby granted, provided that the above
  19copyright notice and this permission notice appear in all copies.
  20
  21THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  22WITH REGARD TO THIS SOFTWARE.
  23
  24cc -DNDEBUG -O2 -g0 -s src/uxn11.c -L/usr/X11R6/lib -I/usr/X11R6/include -lX11 -lutil -o bin/uxn11
  25*/
  26
  27typedef unsigned char Uint8;
  28typedef unsigned short Uint16;
  29typedef void (*deo_handler)(void);
  30typedef Uint8 (*dei_handler)(void);
  31
  32static Display *emu_dpy;
  33static Window emu_win;
  34static XImage *emu_img;
  35static int emu_x11;
  36static int emu_timer;
  37static Uint8 *ram, dev[0x100], ptr[2], stk[2][0x100];
  38static Uint8 emu_dei(const Uint8 port);
  39static void emu_deo(const Uint8 port, const Uint8 value);
  40
  41/* clang-format off */
  42
  43#define BANKS 0x10
  44#define BANKS_CAP BANKS * 0x10000
  45#define WIDTH (64 * 8)
  46#define HEIGHT (40 * 8)
  47
  48#define CLAMP(v, a, b) { if(v < a) v = a; else if(v >= b) v = b; }
  49#define TWOS(v) (v & 0x8000 ? (int)v - 0x10000 : (int)v)
  50#define PEEK2(d) (*(d) << 8 | (d)[1])
  51
  52#define REM *p -= 1 + _2;
  53#define DEC s[--(*p)]
  54#define INC s[(*p)++]
  55#define MOV { if(_2) pc = a; else pc += (signed char)a; }
  56#define JMP(x) { c = ram[pc] << 8, c |= ram[pc + 1]; pc += x + 2; }
  57#define STK(m) Uint8 *s = stk[m], *p = &ptr[m];
  58#define PO1(o) o = DEC;
  59#define PO2(o) { PO1(o) o |= DEC << 8; }
  60#define POx(o) { PO1(o) if(_2) o |= DEC << 8; }
  61#define POt(o) if(_2) PO1(o[1]) PO1(o[0])
  62#define PU1(i) INC = i;
  63#define PU2(i) PU1(i >> 8) PU1(i)
  64#define PUx(i) c = (i); if(_2) PU1(c >> 8) PU1(c)
  65#define PUt(i) PU1(i[0]) if(_2) PU1(i[1])
  66#define PUr(i) stk[!_r][ptr[!_r]++] = i;
  67#define DEO(o,r) emu_deo(o, r[0]); if(_2) emu_deo(o + 1, r[1]);
  68#define DEI(i,r) r[0] = emu_dei(i); if(_2) r[1] = emu_dei(i + 1);
  69#define POK(o,r,m) ram[o] = r[0]; if(_2) ram[(o + 1) & m] = r[1];
  70#define PEK(i,r,m) r[0] = ram[i]; if(_2) r[1] = ram[(i + 1) & m];
  71
  72#define OPC(opc, A, B) {\
  73	case 0x00|opc: {const int _2=0,_r=0;STK(_r)A B} goto step;\
  74	case 0x20|opc: {const int _2=1,_r=0;STK(_r)A B} goto step;\
  75	case 0x40|opc: {const int _2=0,_r=1;STK(_r)A B} goto step;\
  76	case 0x60|opc: {const int _2=1,_r=1;STK(_r)A B} goto step;\
  77	case 0x80|opc: {const int _2=0,_r=0;STK(_r)int k=*p; A *p=k;B} goto step;\
  78	case 0xa0|opc: {const int _2=1,_r=0;STK(_r)int k=*p; A *p=k;B} goto step;\
  79	case 0xc0|opc: {const int _2=0,_r=1;STK(_r)int k=*p; A *p=k;B} goto step;\
  80	case 0xe0|opc: {const int _2=1,_r=1;STK(_r)int k=*p; A *p=k;B} goto step;}
  81
  82static unsigned int
  83uxn_eval(Uint16 pc)
  84{
  85	Uint16 a, b, c, x[2], y[2], z[2];
  86step:
  87	switch(ram[pc++]) {
  88	/* BRK */ case 0x00: return 1;
  89	/* JCI */ case 0x20: if(stk[0][--ptr[0]]) JMP(c) else pc += 2; goto step;
  90	/* JMI */ case 0x40: JMP(c) goto step;
  91	/* JSI */ case 0x60: { STK(1) JMP(0) PU2(pc) pc += c; } goto step;
  92	/* LI2 */ case 0xa0: { STK(0) INC = ram[pc++]; INC = ram[pc++]; } goto step;
  93	/* LIT */ case 0x80: { stk[0][ptr[0]++] = ram[pc++]; } goto step;
  94	/* L2r */ case 0xe0: { STK(1) INC = ram[pc++]; INC = ram[pc++]; } goto step;
  95	/* LIr */ case 0xc0: { stk[1][ptr[1]++] = ram[pc++]; } goto step;
  96	/* INC */ OPC(0x01,POx(a),PUx(a + 1))
  97	/* POP */ OPC(0x02,REM,(void)s;)
  98	/* NIP */ OPC(0x03,POt(x) REM,PUt(x))
  99	/* SWP */ OPC(0x04,POt(x) POt(y),PUt(x) PUt(y))
 100	/* ROT */ OPC(0x05,POt(x) POt(y) POt(z),PUt(y) PUt(x) PUt(z))
 101	/* DUP */ OPC(0x06,POt(x),PUt(x) PUt(x))
 102	/* OVR */ OPC(0x07,POt(x) POt(y),PUt(y) PUt(x) PUt(y))
 103	/* EQU */ OPC(0x08,POx(a) POx(b),PU1(b == a))
 104	/* NEQ */ OPC(0x09,POx(a) POx(b),PU1(b != a))
 105	/* GTH */ OPC(0x0a,POx(a) POx(b),PU1(b > a))
 106	/* LTH */ OPC(0x0b,POx(a) POx(b),PU1(b < a))
 107	/* JMP */ OPC(0x0c,POx(a),MOV)
 108	/* JCN */ OPC(0x0d,POx(a) PO1(b),if(b) MOV)
 109	/* JSR */ OPC(0x0e,POx(a),PUr(pc >> 8) PUr(pc) MOV)
 110	/* STH */ OPC(0x0f,POt(x),PUr(x[0]) if(_2) PUr(x[1]))
 111	/* LDZ */ OPC(0x10,PO1(a),PEK(a, x, 0xff) PUt(x))
 112	/* STZ */ OPC(0x11,PO1(a) POt(y),POK(a, y, 0xff))
 113	/* LDR */ OPC(0x12,PO1(a),PEK(pc + (signed char)a, x, 0xffff) PUt(x))
 114	/* STR */ OPC(0x13,PO1(a) POt(y),POK(pc + (signed char)a, y, 0xffff))
 115	/* LDA */ OPC(0x14,PO2(a),PEK(a, x, 0xffff) PUt(x))
 116	/* STA */ OPC(0x15,PO2(a) POt(y),POK(a, y, 0xffff))
 117	/* DEI */ OPC(0x16,PO1(a),DEI(a, x) PUt(x))
 118	/* DEO */ OPC(0x17,PO1(a) POt(y),DEO(a, y))
 119	/* ADD */ OPC(0x18,POx(a) POx(b),PUx(b + a))
 120	/* SUB */ OPC(0x19,POx(a) POx(b),PUx(b - a))
 121	/* MUL */ OPC(0x1a,POx(a) POx(b),PUx(b * a))
 122	/* DIV */ OPC(0x1b,POx(a) POx(b),PUx(a ? b / a : 0))
 123	/* AND */ OPC(0x1c,POx(a) POx(b),PUx(b & a))
 124	/* ORA */ OPC(0x1d,POx(a) POx(b),PUx(b | a))
 125	/* EOR */ OPC(0x1e,POx(a) POx(b),PUx(b ^ a))
 126	/* SFT */ OPC(0x1f,PO1(a) POx(b),PUx(b >> (a & 0xf) << (a >> 4)))
 127	}
 128	return 0;
 129}
 130
 131/* clang-format on */
 132
 133/*
 134@|System ------------------------------------------------------------ */
 135
 136static char *system_boot_path;
 137
 138static void
 139system_print(char *name, int r)
 140{
 141	Uint8 i;
 142	fprintf(stderr, "%s%c", name, ptr[r] - 8 ? ' ' : '|');
 143	for(i = ptr[r] - 8; i != ptr[r]; i++)
 144		fprintf(stderr, "%02x%c", stk[r][i], i == 0xff ? '|' : ' ');
 145	fprintf(stderr, "<%02x\n", ptr[r]);
 146}
 147
 148static unsigned int
 149system_load(const char *rom_path)
 150{
 151	FILE *f = fopen(rom_path, "rb");
 152	if(f) {
 153		unsigned int i = 0, l = fread(ram + 0x100, 0x10000 - 0x100, 1, f);
 154		while(l && ++i < BANKS)
 155			l = fread(ram + i * 0x10000, 0x10000, 1, f);
 156		fclose(f);
 157	}
 158	return !!f;
 159}
 160
 161static unsigned int
 162system_boot(char *rom_path, const unsigned int has_args)
 163{
 164	ram = (Uint8 *)calloc(BANKS_CAP, sizeof(Uint8));
 165	system_boot_path = rom_path;
 166	dev[0x17] = has_args;
 167	return ram && system_load(rom_path);
 168}
 169
 170static unsigned int
 171system_reboot(const unsigned int soft)
 172{
 173	memset(dev, 0, 0x100);
 174	memset(stk[0], 0, 0x100);
 175	memset(stk[1], 0, 0x100);
 176	if(soft)
 177		memset(ram + 0x100, 0, 0xff00);
 178	else
 179		memset(ram, 0, 0x10000);
 180	ptr[0] = ptr[1] = 0;
 181	return system_load(system_boot_path);
 182}
 183
 184static void
 185system_deo_expansion(void)
 186{
 187	const Uint16 exp = PEEK2(dev + 2);
 188	Uint8 *aptr = ram + exp;
 189	Uint16 length = PEEK2(aptr + 1);
 190	unsigned int bank = PEEK2(aptr + 3) * 0x10000;
 191	unsigned int addr = PEEK2(aptr + 5);
 192	if(ram[exp] == 0x0) {
 193		unsigned int dst_value = ram[exp + 7];
 194		if(bank < BANKS_CAP)
 195			memset(ram + bank + addr, dst_value, length);
 196	} else if(ram[exp] == 1 || ram[exp] == 2) {
 197		unsigned int dst_bank = PEEK2(aptr + 7) * 0x10000;
 198		unsigned int dst_addr = PEEK2(aptr + 9);
 199		if(bank < BANKS_CAP && dst_bank < BANKS_CAP)
 200			memmove(ram + dst_bank + dst_addr, ram + bank + addr, length);
 201	} else
 202		fprintf(stderr, "Unknown command: %s\n", &ram[exp]);
 203}
 204
 205/* clang-format off */
 206
 207static Uint8 system_dei_wst(void) { return ptr[0]; }
 208static Uint8 system_dei_rst(void) { return ptr[1]; }
 209static void system_deo_wst(void) { ptr[0] = dev[4]; }
 210static void system_deo_rst(void) { ptr[1] = dev[5]; }
 211static void system_deo_print(void) { system_print("WST", 0), system_print("RST", 1); }
 212
 213/* clang-format on */
 214
 215/*
 216@|Console ----------------------------------------------------------- */
 217
 218#define CONSOLE_STD 0x1
 219#define CONSOLE_ARG 0x2
 220#define CONSOLE_EOA 0x3
 221#define CONSOLE_END 0x4
 222
 223static unsigned int console_vector;
 224
 225static unsigned int
 226console_input(int c, unsigned int type)
 227{
 228	if(c == EOF) c = 0, type = CONSOLE_END;
 229	dev[0x12] = c, dev[0x17] = type;
 230	if(console_vector) uxn_eval(console_vector);
 231	return type != CONSOLE_END;
 232}
 233
 234#undef _POSIX_C_SOURCE
 235#define _POSIX_C_SOURCE 200112L
 236
 237#include <signal.h>
 238#include <sys/select.h>
 239#include <sys/wait.h>
 240
 241#ifdef __linux
 242#include <pty.h>
 243#include <sys/prctl.h>
 244#endif
 245
 246#if defined(__NetBSD__) || defined(__OpenBSD__)
 247#include <sys/ioctl.h>
 248#include <util.h>
 249#endif
 250
 251/* subprocess support */
 252static char *fork_args[4] = {"/bin/sh", "-c", "", NULL};
 253static int child_mode;
 254static int to_child_fd[2];
 255static int from_child_fd[2];
 256static int saved_in;
 257static int saved_out;
 258static pid_t child_pid;
 259
 260/* child_mode:
 261* 0x01: writes to child's stdin
 262* 0x02: reads from child's stdout
 263* 0x04: reads from child's stderr
 264* 0x08: kill previous process (if any) but do not start
 265* (other bits ignored for now )
 266*/
 267
 268#define CMD_LIVE 0x15 /* 0x00 not started, 0x01 running, 0xff dead */
 269#define CMD_EXIT 0x16 /* if dead, exit code of process */
 270#define CMD_ADDR 0x1c /* address to read command args from */
 271#define CMD_MODE 0x1e /* mode to execute, 0x00 to 0x07 */
 272
 273/* call after we're sure the process has exited */
 274
 275static void
 276clean_after_child(void)
 277{
 278	child_pid = 0;
 279	if(child_mode & 0x01) {
 280		close(to_child_fd[1]);
 281		dup2(saved_out, 1);
 282	}
 283	if(child_mode & (0x04 | 0x02)) {
 284		close(from_child_fd[0]);
 285		dup2(saved_in, 0);
 286	}
 287	child_mode = 0;
 288	saved_in = -1;
 289	saved_out = -1;
 290}
 291
 292static void
 293start_fork_pipe(void)
 294{
 295	pid_t pid;
 296	pid_t parent_pid = getpid();
 297	int addr = PEEK2(&dev[CMD_ADDR]);
 298	fflush(stdout);
 299	if(child_mode & 0x08) {
 300		dev[CMD_EXIT] = dev[CMD_LIVE] = 0x00;
 301		return;
 302	}
 303	if(child_mode & 0x01) {
 304		/* parent writes to child's stdin */
 305		if(pipe(to_child_fd) == -1) {
 306			dev[CMD_EXIT] = dev[CMD_LIVE] = 0xff;
 307			fprintf(stderr, "Pipe error to child.\n");
 308			return;
 309		}
 310	}
 311	if(child_mode & (0x04 | 0x02)) {
 312		/* parent reads from child's stdout and/or stderr */
 313		if(pipe(from_child_fd) == -1) {
 314			dev[CMD_EXIT] = dev[CMD_LIVE] = 0xff;
 315			fprintf(stderr, "Pipe error from child.\n");
 316			return;
 317		}
 318	}
 319
 320	fork_args[2] = (char *)&ram[addr];
 321	pid = fork();
 322	if(pid < 0) { /* failure */
 323		dev[CMD_EXIT] = dev[CMD_LIVE] = 0xff;
 324		fprintf(stderr, "Fork failure.\n");
 325	} else if(pid == 0) { /* child */
 326
 327#ifdef __linux__
 328		int r = prctl(PR_SET_PDEATHSIG, SIGTERM);
 329		if(r == -1) {
 330			perror(0);
 331			exit(6);
 332		}
 333		if(getppid() != parent_pid) exit(13);
 334#endif
 335
 336		if(child_mode & 0x01) {
 337			dup2(to_child_fd[0], 0);
 338			close(to_child_fd[1]);
 339		}
 340		if(child_mode & (0x04 | 0x02)) {
 341			if(child_mode & 0x02) dup2(from_child_fd[1], 1);
 342			if(child_mode & 0x04) dup2(from_child_fd[1], 2);
 343			close(from_child_fd[0]);
 344		}
 345		fflush(stdout);
 346		execvp(fork_args[0], fork_args);
 347		exit(1);
 348	} else { /*parent*/
 349		child_pid = pid;
 350		dev[CMD_LIVE] = 0x01;
 351		dev[CMD_EXIT] = 0x00;
 352		if(child_mode & 0x01) {
 353			saved_out = dup(1);
 354			dup2(to_child_fd[1], 1);
 355			close(to_child_fd[0]);
 356		}
 357		if(child_mode & (0x04 | 0x02)) {
 358			saved_in = dup(0);
 359			dup2(from_child_fd[0], 0);
 360			close(from_child_fd[1]);
 361		}
 362	}
 363}
 364
 365static void
 366check_child(void)
 367{
 368	int wstatus;
 369	if(child_pid) {
 370		if(waitpid(child_pid, &wstatus, WNOHANG)) {
 371			dev[CMD_LIVE] = 0xff;
 372			dev[CMD_EXIT] = WEXITSTATUS(wstatus);
 373			clean_after_child();
 374		} else {
 375			dev[CMD_LIVE] = 0x01;
 376			dev[CMD_EXIT] = 0x00;
 377		}
 378	}
 379}
 380
 381static void
 382kill_child(void)
 383{
 384	int wstatus;
 385	if(child_pid) {
 386		kill(child_pid, 9);
 387		if(waitpid(child_pid, &wstatus, WNOHANG)) {
 388			dev[CMD_LIVE] = 0xff;
 389			dev[CMD_EXIT] = WEXITSTATUS(wstatus);
 390			clean_after_child();
 391		}
 392	}
 393}
 394
 395static void
 396console_deo_fork(void)
 397{
 398	fflush(stderr);
 399	kill_child();
 400	child_mode = dev[CMD_MODE];
 401	start_fork_pipe();
 402}
 403
 404static void
 405console_close(void)
 406{
 407	kill_child();
 408}
 409
 410/* clang-format off */
 411
 412static Uint8 console_dei_childlive(void) { check_child(); return dev[CMD_LIVE]; }
 413static Uint8 console_dei_childexit(void) { check_child(); return dev[CMD_EXIT]; }
 414static void console_deo_vector(void) { console_vector = PEEK2(&dev[0x10]); }
 415static void console_deo_stdout(void) { fputc(dev[0x18], stdout), fflush(stdout); }
 416static void console_deo_stderr(void) { fputc(dev[0x19], stderr), fflush(stderr); }
 417static void console_deo_hb(void) { fprintf(stderr, "%02x", dev[0x1a]); }
 418static void console_deo_lb(void) { fprintf(stderr, "%02x", dev[0x1b]); }
 419
 420/* clang-format on */
 421
 422/*
 423@|Screen ------------------------------------------------------------ */
 424
 425static int screen_zoom = 1;
 426static int screen_width, screen_height, screen_wmar2, screen_hmar2;
 427static int screen_reqsize, screen_reqdraw;
 428static int screen_x1, screen_y1, screen_x2, screen_y2;
 429static int screen_vector, *screen_pixels, screen_palette[16];
 430static int rX, rY, rA, rMX, rMY, rMA, rML, rDX, rDY;
 431static Uint8 *screen_layers;
 432
 433static const Uint8 blending[16][2][4] = {
 434	{{0, 0, 1, 2}, {0, 0, 4, 8}},
 435	{{0, 1, 2, 3}, {0, 4, 8, 12}},
 436	{{0, 2, 3, 1}, {0, 8, 12, 4}},
 437	{{0, 3, 1, 2}, {0, 12, 4, 8}},
 438	{{1, 0, 1, 2}, {4, 0, 4, 8}},
 439	{{0, 1, 2, 3}, {0, 4, 8, 12}},
 440	{{1, 2, 3, 1}, {4, 8, 12, 4}},
 441	{{1, 3, 1, 2}, {4, 12, 4, 8}},
 442	{{2, 0, 1, 2}, {8, 0, 4, 8}},
 443	{{2, 1, 2, 3}, {8, 4, 8, 12}},
 444	{{0, 2, 3, 1}, {0, 8, 12, 4}},
 445	{{2, 3, 1, 2}, {8, 12, 4, 8}},
 446	{{3, 0, 1, 2}, {12, 0, 4, 8}},
 447	{{3, 1, 2, 3}, {12, 4, 8, 12}},
 448	{{3, 2, 3, 1}, {12, 8, 12, 4}},
 449	{{0, 3, 1, 2}, {0, 12, 4, 8}}};
 450
 451void emu_redraw(void), emu_resize(void);
 452
 453static void
 454screen_change(const int x1, const int y1, const int x2, const int y2)
 455{
 456	screen_x1 = x1 < screen_x1 ? x1 : screen_x1;
 457	screen_y1 = y1 < screen_y1 ? y1 : screen_y1;
 458	screen_x2 = x2 > screen_x2 ? x2 : screen_x2;
 459	screen_y2 = y2 > screen_y2 ? y2 : screen_y2;
 460}
 461
 462static void
 463system_deo_colorize(void)
 464{
 465	unsigned int i, shift, colors[4];
 466	for(i = 0, shift = 4; i < 4; ++i, shift ^= 4) {
 467		Uint8
 468			r = dev[0x8 + i / 2] >> shift & 0xf,
 469			g = dev[0xa + i / 2] >> shift & 0xf,
 470			b = dev[0xc + i / 2] >> shift & 0xf;
 471		colors[i] = 0x0f000000 | r << 16 | g << 8 | b;
 472		colors[i] |= colors[i] << 4;
 473	}
 474	for(i = 0; i < 16; i++)
 475		screen_palette[i] = colors[i >> 2 ? i >> 2 : i & 3];
 476	screen_reqdraw = 1;
 477}
 478
 479static void
 480screen_resize(int width, int height)
 481{
 482	if(width != screen_width || height != screen_height) {
 483		int length;
 484		screen_width = width, screen_wmar2 = width + 0x10;
 485		dev[0x22] = screen_width >> 8, dev[0x23] = screen_width;
 486		screen_height = height, screen_hmar2 = height + 0x10;
 487		dev[0x24] = screen_height >> 8, dev[0x25] = screen_height;
 488		length = screen_wmar2 * screen_hmar2;
 489		screen_layers = realloc(screen_layers, length);
 490		memset(screen_layers, 0, length);
 491		screen_reqsize = screen_reqdraw = 1;
 492	}
 493}
 494
 495static void
 496screen_redraw(void)
 497{
 498	if(screen_zoom == 1) {
 499		const int colmar = (screen_y1 + 8) * screen_wmar2 + (screen_x1 + 8);
 500		const int row_width = screen_x2 - screen_x1;
 501		const int src_stride = screen_wmar2 - row_width;
 502		const int dst_stride = screen_width - row_width;
 503		int y, *dst_ptr = &screen_pixels[screen_y1 * screen_width + screen_x1];
 504		Uint8 *src_ptr = &screen_layers[colmar];
 505		for(y = screen_y1; y < screen_y2; y++) {
 506			int *dst_end = dst_ptr + row_width;
 507			while(dst_ptr < dst_end)
 508				*dst_ptr++ = screen_palette[*src_ptr++];
 509			dst_ptr += dst_stride;
 510			src_ptr += src_stride;
 511		}
 512	} else {
 513		int x, y, i, k, l;
 514		const int stride = screen_width * screen_zoom;
 515		for(y = screen_y1; y < screen_y2; y++) {
 516			const int ys = y * screen_zoom;
 517			for(x = screen_x1, i = (x + 8) + (y + 8) * screen_wmar2; x < screen_x2; x++, i++) {
 518				const int c = screen_palette[screen_layers[i]];
 519				for(k = 0; k < screen_zoom; k++) {
 520					const int oo = (ys + k) * stride + x * screen_zoom;
 521					for(l = 0; l < screen_zoom; l++)
 522						screen_pixels[oo + l] = c;
 523				}
 524			}
 525		}
 526	}
 527	emu_redraw();
 528	screen_x1 = screen_y1 = screen_x2 = screen_y2 = screen_reqdraw = 0;
 529}
 530
 531static void
 532screen_update(void)
 533{
 534	if(screen_vector)
 535		uxn_eval(screen_vector);
 536	if(screen_reqsize) {
 537		static int pixel_capacity;
 538		int need = screen_width * screen_height * screen_zoom * screen_zoom;
 539		if(need > pixel_capacity) {
 540			pixel_capacity = need;
 541			screen_pixels = realloc(screen_pixels, need * sizeof(unsigned int));
 542		}
 543		screen_reqsize = 0;
 544		emu_resize();
 545	}
 546	if(screen_reqdraw) {
 547		screen_x1 = screen_y1 = 0;
 548		screen_x2 = screen_width;
 549		screen_y2 = screen_height;
 550		screen_redraw();
 551	} else if(screen_x2 > screen_x1 && screen_y2 > screen_y1) {
 552		CLAMP(screen_x1, 0, screen_width);
 553		CLAMP(screen_y1, 0, screen_height);
 554		CLAMP(screen_x2, 0, screen_width);
 555		CLAMP(screen_y2, 0, screen_height);
 556		screen_redraw();
 557	}
 558}
 559
 560static void
 561screen_deo_pixel(void)
 562{
 563	const int ctrl = dev[0x2e];
 564	const int layer_mask = (ctrl & 0x40) ? 0x3 : 0xc;
 565	const int color = (ctrl & 0x40) ? ((ctrl & 0x3) << 2) : (ctrl & 0x3);
 566	/* fill mode */
 567	if(ctrl & 0x80) {
 568		int x1, y1, x2, y2, ay, by, px;
 569		if(ctrl & 0x10)
 570			x1 = 0, x2 = rX;
 571		else
 572			x1 = rX, x2 = screen_width;
 573		if(ctrl & 0x20)
 574			y1 = 0, y2 = rY;
 575		else
 576			y1 = rY, y2 = screen_height;
 577		screen_reqdraw = 1;
 578		x1 = x1 + 8, y1 = y1 + 8;
 579		const int hor = (x2 + 8) - x1, ver = (y2 + 8) - y1;
 580		for(ay = y1 * screen_wmar2, by = ay + ver * screen_wmar2; ay < by; ay += screen_wmar2) {
 581			Uint8 *dst = &screen_layers[ay + x1];
 582			for(px = 0; px < hor; px++)
 583				dst[px] = (dst[px] & layer_mask) | color;
 584		}
 585	}
 586	/* pixel mode */
 587	else {
 588		const unsigned int x = rX, y = rY;
 589		if(x < screen_width && y < screen_height) {
 590			Uint8 *dst = &screen_layers[(x + 8) + (y + 8) * screen_wmar2];
 591			*dst = (*dst & layer_mask) | color;
 592		}
 593		screen_reqdraw = 1;
 594		if(rMX) rX++;
 595		if(rMY) rY++;
 596	}
 597}
 598
 599static void
 600screen_deo_sprite(void)
 601{
 602	int i, row, px, x = rX, y = rY;
 603	const int ctrl = dev[0x2f];
 604	const int blend = ctrl & 0xf;
 605	const int opaque = blend % 5;
 606	const int fx = (ctrl & 0x10) ? -1 : 1, dyx = fx * rDY;
 607	const int fy = (ctrl & 0x20) ? -1 : 1, dxy = fy * rDX;
 608	const int qfx = (ctrl & 0x10) ? 0 : 7;
 609	const int qfy = (ctrl & 0x20) ? 7 : 0;
 610	const int layer_mask = (ctrl & 0x40) ? 0x3 : 0xc;
 611	const Uint8 *table = (ctrl & 0x40) ? blending[blend][1] : blending[blend][0];
 612	const int is_2bpp = (ctrl & 0x80) != 0;
 613	const int addr_incr = rMA << (is_2bpp ? 2 : 1);
 614	const int stride = screen_wmar2;
 615	for(i = 0; i <= rML; i++, x += dyx, y += dxy, rA += addr_incr) {
 616		const Uint16 x0 = x + 8, y0 = y + 8;
 617		if(x0 + 8 >= stride || y0 + 8 >= screen_hmar2) continue;
 618		Uint8 *dst = &screen_layers[x0 + y0 * stride];
 619		const Uint8 *src = &ram[rA + qfy];
 620		for(row = 0; row < 8; row++, dst += stride, src += fy) {
 621			int qx = qfx;
 622			const int ch1 = *src, ch2 = is_2bpp ? (src[8] << 1) : 0;
 623			for(px = 0; px < 8; px++, qx -= fx) {
 624				int color = ((ch1 >> qx) & 1) | ((ch2 >> qx) & 2);
 625				if(opaque || color)
 626					dst[px] = (dst[px] & layer_mask) | table[color];
 627			}
 628		}
 629	}
 630	if(!screen_reqdraw) {
 631		int x1 = (fx < 0) ? x : rX;
 632		int x2 = (fx < 0) ? rX : x;
 633		int y1 = (fy < 0) ? y : rY;
 634		int y2 = (fy < 0) ? rY : y;
 635		screen_change(x1 - 8, y1 - 8, x2 + 8, y2 + 8);
 636	}
 637	if(rMX) rX += rDX * fx;
 638	if(rMY) rY += rDY * fy;
 639}
 640
 641/* clang-format off */
 642
 643static Uint8 screen_dei_rxhb(void) { return rX >> 8; }
 644static Uint8 screen_dei_rxlb(void) { return rX; }
 645static Uint8 screen_dei_ryhb(void) { return rY >> 8; }
 646static Uint8 screen_dei_rylb(void) { return rY; }
 647static Uint8 screen_dei_rahb(void) { return rA >> 8; }
 648static Uint8 screen_dei_ralb(void) { return rA; }
 649static void screen_deo_vector(void) { screen_vector = PEEK2(&dev[0x20]); }
 650static void screen_deo_width(void) { screen_resize(PEEK2(&dev[0x22]) & 0xfff, screen_height & 0xfff); }
 651static void screen_deo_height(void) { screen_resize(screen_width & 0xfff, PEEK2(&dev[0x24]) & 0xfff); }
 652static void screen_deo_auto(void) { rMX = dev[0x26] & 0x1, rMY = dev[0x26] & 0x2, rMA = dev[0x26] & 0x4, rML = dev[0x26] >> 4, rDX = rMX << 3, rDY = rMY << 2; }
 653static void screen_deo_x(void) { rX = (dev[0x28] << 8) | dev[0x29], rX = TWOS(rX); }
 654static void screen_deo_y(void) { rY = (dev[0x2a] << 8) | dev[0x2b], rY = TWOS(rY); }
 655static void screen_deo_addr(void) { rA = (dev[0x2c] << 8) | dev[0x2d]; }
 656
 657/* clang-format on */
 658
 659/*
 660@|Controller -------------------------------------------------------- */
 661
 662static unsigned int controller_vector;
 663
 664static void
 665controller_down(Uint8 mask)
 666{
 667	if(mask) {
 668		dev[0x82] |= mask;
 669		if(controller_vector) uxn_eval(controller_vector);
 670	}
 671}
 672
 673static void
 674controller_up(Uint8 mask)
 675{
 676	if(mask) {
 677		dev[0x82] &= (~mask);
 678		if(controller_vector) uxn_eval(controller_vector);
 679	}
 680}
 681
 682static void
 683controller_key(Uint8 key)
 684{
 685	if(key) {
 686		dev[0x83] = key;
 687		if(controller_vector) uxn_eval(controller_vector);
 688		dev[0x83] = 0;
 689	}
 690}
 691
 692void
 693controller_deo_vector(void)
 694{
 695	controller_vector = PEEK2(&dev[0x80]);
 696}
 697
 698/*
 699@|Mouse ------------------------------------------------------------- */
 700
 701static unsigned int mouse_vector;
 702
 703static void
 704mouse_down(Uint8 mask)
 705{
 706	dev[0x96] |= mask;
 707	if(mouse_vector) uxn_eval(mouse_vector);
 708}
 709
 710static void
 711mouse_up(Uint8 mask)
 712{
 713	dev[0x96] &= (~mask);
 714	if(mouse_vector) uxn_eval(mouse_vector);
 715}
 716
 717static void
 718mouse_pos(Uint16 x, Uint16 y)
 719{
 720	dev[0x92] = x >> 8, dev[0x93] = x;
 721	dev[0x94] = y >> 8, dev[0x95] = y;
 722	if(mouse_vector) uxn_eval(mouse_vector);
 723}
 724
 725static void
 726mouse_scroll(Uint16 x, Uint16 y)
 727{
 728	dev[0x9a] = x >> 8, dev[0x9b] = x;
 729	dev[0x9c] = -y >> 8, dev[0x9d] = -y;
 730	if(mouse_vector) uxn_eval(mouse_vector);
 731	dev[0x9a] = 0, dev[0x9b] = 0;
 732	dev[0x9c] = 0, dev[0x9d] = 0;
 733}
 734
 735void
 736mouse_deo_vector(void)
 737{
 738	mouse_vector = PEEK2(&dev[0x90]);
 739}
 740
 741/*
 742@|File -------------------------------------------------------------- */
 743
 744#include <dirent.h>
 745#include <sys/stat.h>
 746#include <string.h>
 747
 748typedef struct {
 749	FILE *f;
 750	DIR *dir;
 751	char *filepath;
 752	enum { IDLE,
 753		FILE_READ,
 754		FILE_WRITE,
 755		DIR_READ,
 756		DIR_WRITE
 757	} state;
 758} UxnFile;
 759
 760static UxnFile ufs[2];
 761static unsigned int rL1, rL2;
 762
 763static void
 764make_pathfile(char *pathbuf, const char *filepath, const char *basename)
 765{
 766	char c = '/';
 767	while(*filepath)
 768		c = *filepath++, *pathbuf = c, pathbuf++;
 769	if(c != '/')
 770		*pathbuf = '/', pathbuf++;
 771	while(*basename)
 772		*pathbuf = *basename++, pathbuf++;
 773	*pathbuf = 0;
 774}
 775
 776static unsigned int
 777put_fill(Uint8 *dest, unsigned int len, char c)
 778{
 779	memset(dest, c, len);
 780	return len;
 781}
 782
 783static unsigned int
 784put_size(Uint8 *dest, unsigned int len, unsigned int size)
 785{
 786	unsigned int i;
 787	for(i = 0, dest += len; i < len; i++, size >>= 4)
 788		*(--dest) = "0123456789abcdef"[(Uint8)(size & 0xf)];
 789	return len;
 790}
 791
 792static unsigned int
 793put_text(Uint8 *dest, const char *text)
 794{
 795	Uint8 *anchor = dest;
 796	while(*text)
 797		*dest = *text++, dest++;
 798	*dest = 0;
 799	return dest - anchor;
 800}
 801
 802static unsigned int
 803put_stat(Uint8 *dest, unsigned int len, unsigned int size, unsigned int err, unsigned int dir, unsigned int capsize)
 804{
 805	if(err) return put_fill(dest, len, '!');
 806	if(dir) return put_fill(dest, len, '-');
 807	if(capsize && size >= 0x10000) return put_fill(dest, len, '?');
 808	return put_size(dest, len, size);
 809}
 810
 811static unsigned int
 812put_statfile(Uint8 *dest, const char *filepath, const char *basename)
 813{
 814	unsigned int err, dir;
 815	struct stat st;
 816	Uint8 *anchor = dest;
 817	char pathbuf[0x2000];
 818	make_pathfile(pathbuf, filepath, basename);
 819	err = stat(pathbuf, &st);
 820	dir = S_ISDIR(st.st_mode);
 821	dest += put_stat(dest, 4, st.st_size, err, dir, 1);
 822	dest += put_text(dest, " ");
 823	dest += put_text(dest, basename);
 824	dest += put_text(dest, dir ? "/\n" : "\n");
 825	return dest - anchor;
 826}
 827
 828static unsigned int
 829put_fdir(Uint8 *dest, unsigned int len, const char *filepath, DIR *dir)
 830{
 831	Uint8 *p = dest, *end = dest + len - 1;
 832	struct dirent *de;
 833	while((de = readdir(dir)) && p < end) {
 834		const char *name = de->d_name;
 835		if(name[0] == '.' && (!name[1] || (name[1] == '.' && !name[2])))
 836			continue;
 837		p += put_statfile(p, filepath, name);
 838	}
 839	*p = 0;
 840	return p - dest;
 841}
 842
 843static unsigned int
 844is_dir_path(const char *p)
 845{
 846	const char *end = p;
 847	while(*end) end++;
 848	return end > p && end[-1] == '/';
 849}
 850
 851static unsigned int
 852is_dir_real(char *p)
 853{
 854	struct stat st;
 855	return stat(p, &st) == 0 && S_ISDIR(st.st_mode);
 856}
 857
 858static unsigned int
 859file_write_dir(const char *p)
 860{
 861	char buf[0x1000];
 862	char *s = buf;
 863	unsigned int ok = 1;
 864	while(*p && ok) {
 865		*s++ = *p;
 866		if(*p++ == '/') {
 867			s[-1] = '\0';
 868			ok = is_dir_real(buf) || mkdir(buf, 0755) == 0;
 869			s[-1] = '/';
 870		}
 871	}
 872	return ok;
 873}
 874
 875static void
 876file_reset(unsigned int id)
 877{
 878	if(ufs[id].f != NULL) fclose(ufs[id].f), ufs[id].f = NULL;
 879	if(ufs[id].dir != NULL) closedir(ufs[id].dir), ufs[id].dir = NULL;
 880	ufs[id].state = IDLE;
 881}
 882
 883static unsigned int
 884file_init(unsigned int id, Uint16 addr)
 885{
 886	file_reset(id);
 887	ufs[id].filepath = (char *)&ram[addr];
 888	return 0;
 889}
 890
 891static unsigned int
 892file_not_ready(unsigned int id)
 893{
 894	return ufs[id].filepath == 0;
 895}
 896
 897static unsigned int
 898file_read(unsigned int id, Uint16 addr, unsigned int len)
 899{
 900	void *dest = &ram[addr];
 901	struct stat st;
 902	unsigned int n;
 903	if(file_not_ready(id))
 904		return 0;
 905	if(addr + len > 0x10000)
 906		len = 0x10000 - addr;
 907	if(ufs[id].state != FILE_READ && ufs[id].state != DIR_READ) {
 908		file_reset(id);
 909		if(stat(ufs[id].filepath, &st) == 0 && S_ISDIR(st.st_mode)) {
 910			if((ufs[id].dir = opendir(ufs[id].filepath)) != NULL)
 911				ufs[id].state = DIR_READ;
 912		} else if((ufs[id].f = fopen(ufs[id].filepath, "rb")) != NULL) {
 913			ufs[id].state = FILE_READ;
 914		}
 915	}
 916	if(ufs[id].state == FILE_READ) {
 917		n = fread(dest, 1, len, ufs[id].f);
 918		if(n == 0) file_reset(id);
 919		return n;
 920	}
 921	if(ufs[id].state == DIR_READ) {
 922		n = put_fdir(dest, len, ufs[id].filepath, ufs[id].dir);
 923		if(n == 0) file_reset(id);
 924		return n;
 925	}
 926	return 0;
 927}
 928
 929static unsigned int
 930file_write(unsigned int id, Uint16 addr, unsigned int len, Uint8 flags)
 931{
 932	unsigned int ret = 0;
 933	if(file_not_ready(id))
 934		return 0;
 935	if(addr + len > 0x10000)
 936		len = 0x10000 - addr;
 937	file_write_dir(ufs[id].filepath);
 938	if(ufs[id].state != FILE_WRITE && ufs[id].state != DIR_WRITE) {
 939		file_reset(id);
 940		if(is_dir_path(ufs[id].filepath))
 941			ufs[id].state = DIR_WRITE;
 942		else if((ufs[id].f = fopen(ufs[id].filepath, (flags & 0x01) ? "ab" : "wb")) != NULL)
 943			ufs[id].state = FILE_WRITE;
 944	}
 945	if(ufs[id].state == FILE_WRITE)
 946		if((ret = fwrite(&ram[addr], 1, len, ufs[id].f)) > 0 && fflush(ufs[id].f) != 0)
 947			ret = 0;
 948	if(ufs[id].state == DIR_WRITE)
 949		ret = is_dir_real(ufs[id].filepath);
 950	return ret;
 951}
 952
 953static unsigned int
 954file_stat(unsigned int id, Uint16 addr, unsigned int len)
 955{
 956	unsigned int err, dir;
 957	struct stat st;
 958	if(file_not_ready(id))
 959		return 0;
 960	if(addr + len > 0x10000)
 961		len = 0x10000 - addr;
 962	err = stat(ufs[id].filepath, &st);
 963	dir = S_ISDIR(st.st_mode);
 964	return put_stat(&ram[addr], len, st.st_size, err, dir, 0);
 965}
 966
 967static unsigned int
 968file_delete(unsigned int id)
 969{
 970	if(file_not_ready(id))
 971		return 0;
 972	return !unlink(ufs[id].filepath);
 973}
 974
 975static void
 976file_success(unsigned int port, unsigned int value)
 977{
 978	dev[port] = value >> 8, dev[port + 1] = value;
 979}
 980
 981/* clang-format off */
 982
 983static void filea_deo_vector(void) { rL1 = PEEK2(&dev[0xaa]); }
 984static void filea_deo_stat(void) { file_success(0xa2, file_stat(0, PEEK2(&dev[0xa4]), rL1)); }
 985static void filea_deo_delete(void) { file_success(0xa2, file_delete(0)); }
 986static void filea_deo_name(void) { file_success(0xa2, file_init(0, PEEK2(&dev[0xa8]))); }
 987static void filea_deo_read(void) { file_success(0xa2, file_read(0, PEEK2(&dev[0xac]), rL1)); }
 988static void filea_deo_write(void) { file_success(0xa2, file_write(0, PEEK2(&dev[0xae]), rL1, dev[0xa7])); }
 989static void fileb_deo_vector(void) { rL2 = PEEK2(&dev[0xba]); }
 990static void fileb_deo_stat(void) { file_success(0xb2, file_stat(1, PEEK2(&dev[0xb4]), rL2)); }
 991static void fileb_deo_delete(void) { file_success(0xb2, file_delete(1)); }
 992static void fileb_deo_name(void) { file_success(0xb2, file_init(1, PEEK2(&dev[0xb8]))); }
 993static void fileb_deo_read(void) { file_success(0xb2, file_read(1, PEEK2(&dev[0xbc]), rL2)); }
 994static void fileb_deo_write(void) { file_success(0xb2, file_write(1, PEEK2(&dev[0xbe]), rL2, dev[0xb7])); }
 995
 996/* clang-format on */
 997
 998/*
 999@|Datetime ---------------------------------------------------------- */
1000
1001#include <time.h>
1002
1003time_t datetime_seconds;
1004struct tm *datetime_t, datetime_zt = {0};
1005
1006void
1007datetime_update(void)
1008{
1009	datetime_seconds = time(NULL);
1010	datetime_t = localtime(&datetime_seconds);
1011	if(datetime_t == NULL)
1012		datetime_t = &datetime_zt;
1013}
1014
1015/* clang-format off */
1016
1017static Uint8 datetime_dei_yhb(void) { datetime_update(); return (datetime_t->tm_year + 1900) >> 8; }
1018static Uint8 datetime_dei_ylb(void) { datetime_update(); return (datetime_t->tm_year + 1900); }
1019static Uint8 datetime_dei_mon(void) { datetime_update(); return datetime_t->tm_mon; }
1020static Uint8 datetime_dei_day(void) { datetime_update(); return datetime_t->tm_mday; }
1021static Uint8 datetime_dei_hou(void) { datetime_update(); return datetime_t->tm_hour; }
1022static Uint8 datetime_dei_min(void) { datetime_update(); return datetime_t->tm_min; }
1023static Uint8 datetime_dei_sec(void) { datetime_update(); return datetime_t->tm_sec; }
1024static Uint8 datetime_dei_wday(void) { datetime_update(); return datetime_t->tm_wday; }
1025static Uint8 datetime_dei_ydayhb(void) { datetime_update(); return datetime_t->tm_yday >> 8; }
1026static Uint8 datetime_dei_ydaylb(void) { datetime_update(); return datetime_t->tm_yday; }
1027static Uint8 datetime_dei_dst(void) { datetime_update(); return datetime_t->tm_isdst; }
1028
1029/* clang-format on */
1030
1031/*
1032@|Core -------------------------------------------------------------- */
1033
1034static const dei_handler dei_handlers[256] = {
1035	[0x04] = system_dei_wst,
1036	[0x05] = system_dei_rst,
1037	[0x15] = console_dei_childlive,
1038	[0x16] = console_dei_childexit,
1039	[0x28] = screen_dei_rxhb,
1040	[0x29] = screen_dei_rxlb,
1041	[0x2a] = screen_dei_ryhb,
1042	[0x2b] = screen_dei_rylb,
1043	[0x2c] = screen_dei_rahb,
1044	[0x2d] = screen_dei_ralb,
1045	[0xc0] = datetime_dei_yhb,
1046	[0xc1] = datetime_dei_ylb,
1047	[0xc2] = datetime_dei_mon,
1048	[0xc3] = datetime_dei_day,
1049	[0xc4] = datetime_dei_hou,
1050	[0xc5] = datetime_dei_min,
1051	[0xc6] = datetime_dei_sec,
1052	[0xc7] = datetime_dei_wday,
1053	[0xc8] = datetime_dei_ydayhb,
1054	[0xc9] = datetime_dei_ydaylb,
1055	[0xca] = datetime_dei_dst,
1056};
1057
1058static const deo_handler deo_handlers[256] = {
1059	[0x03] = system_deo_expansion,
1060	[0x04] = system_deo_wst,
1061	[0x05] = system_deo_rst,
1062	[0x08] = system_deo_colorize,
1063	[0x09] = system_deo_colorize,
1064	[0x0a] = system_deo_colorize,
1065	[0x0b] = system_deo_colorize,
1066	[0x0c] = system_deo_colorize,
1067	[0x0d] = system_deo_colorize,
1068	[0x0e] = system_deo_print,
1069	[0x11] = console_deo_vector,
1070	[0x18] = console_deo_stdout,
1071	[0x19] = console_deo_stderr,
1072	[0x1a] = console_deo_hb,
1073	[0x1b] = console_deo_lb,
1074	[0x1f] = console_deo_fork,
1075	[0x21] = screen_deo_vector,
1076	[0x23] = screen_deo_width,
1077	[0x25] = screen_deo_height,
1078	[0x26] = screen_deo_auto,
1079	[0x28] = screen_deo_x,
1080	[0x29] = screen_deo_x,
1081	[0x2a] = screen_deo_y,
1082	[0x2b] = screen_deo_y,
1083	[0x2c] = screen_deo_addr,
1084	[0x2d] = screen_deo_addr,
1085	[0x2e] = screen_deo_pixel,
1086	[0x2f] = screen_deo_sprite,
1087	[0x81] = controller_deo_vector,
1088	[0x91] = mouse_deo_vector,
1089	[0xab] = filea_deo_vector,
1090	[0xa5] = filea_deo_stat,
1091	[0xa6] = filea_deo_delete,
1092	[0xa9] = filea_deo_name,
1093	[0xad] = filea_deo_read,
1094	[0xaf] = filea_deo_write,
1095	[0xbb] = fileb_deo_vector,
1096	[0xb5] = fileb_deo_stat,
1097	[0xb6] = fileb_deo_delete,
1098	[0xb9] = fileb_deo_name,
1099	[0xbd] = fileb_deo_read,
1100	[0xbf] = fileb_deo_write};
1101
1102#define CONINBUFSIZE 256
1103
1104Uint8
1105emu_dei(const Uint8 port)
1106{
1107	if(dei_handlers[port]) return dei_handlers[port]();
1108	return dev[port];
1109}
1110
1111void
1112emu_deo(const Uint8 port, const Uint8 value)
1113{
1114	dev[port] = value;
1115	if(deo_handlers[port]) deo_handlers[port]();
1116}
1117
1118void
1119emu_resize(void)
1120{
1121	const int width = screen_width * screen_zoom;
1122	const int height = screen_height * screen_zoom;
1123	if(emu_img) {
1124		emu_img->data = NULL;
1125		XDestroyImage(emu_img);
1126	}
1127	emu_img = XCreateImage(emu_dpy, DefaultVisual(emu_dpy, 0), DefaultDepth(emu_dpy, DefaultScreen(emu_dpy)), ZPixmap, 0, (char *)screen_pixels, width, height, 32, 0);
1128	XResizeWindow(emu_dpy, emu_win, width, height);
1129}
1130
1131void
1132emu_redraw(void)
1133{
1134	const int ax = screen_x1 * screen_zoom;
1135	const int ay = screen_y1 * screen_zoom;
1136	const int aw = screen_x2 * screen_zoom - ax;
1137	const int ah = screen_y2 * screen_zoom - ay;
1138	XPutImage(emu_dpy, emu_win, DefaultGC(emu_dpy, 0), emu_img, ax, ay, ax, ay, aw, ah);
1139}
1140
1141static void
1142emu_restart(unsigned int soft)
1143{
1144	screen_resize(WIDTH, HEIGHT), system_reboot(soft), uxn_eval(0x100);
1145}
1146
1147static Uint8
1148get_button(KeySym sym)
1149{
1150	switch(sym) {
1151	case XK_Up: return 0x10;
1152	case XK_Down: return 0x20;
1153	case XK_Left: return 0x40;
1154	case XK_Right: return 0x80;
1155	case XK_Control_L: return 0x01;
1156	case XK_Alt_R:
1157	case XK_Alt_L: return 0x02;
1158	case XK_Shift_L: return 0x04;
1159	case XK_Home: return 0x08;
1160	case XK_Meta_L: return 0x02;
1161	}
1162	return 0x00;
1163}
1164
1165static void
1166display_center(void)
1167{
1168	Screen *screen = DefaultScreenOfDisplay(emu_dpy);
1169	const int w = screen_width * screen_zoom;
1170	const int h = screen_height * screen_zoom;
1171	const int x = screen->width / 2 - w / 2;
1172	const int y = screen->height / 2 - h / 2;
1173	emu_win = XCreateSimpleWindow(emu_dpy, RootWindow(emu_dpy, 0), x, y, w, h, 1, 0, 0);
1174}
1175
1176static void
1177display_hidecursor(void)
1178{
1179	XColor black = {0};
1180	static const char empty[1] = {0};
1181	Pixmap bitmap = XCreateBitmapFromData(emu_dpy, emu_win, empty, 1, 1);
1182	Cursor blank = XCreatePixmapCursor(emu_dpy, bitmap, bitmap, &black, &black, 0, 0);
1183	XDefineCursor(emu_dpy, emu_win, blank);
1184	XFreeCursor(emu_dpy, blank);
1185	XFreePixmap(emu_dpy, bitmap);
1186}
1187
1188static void
1189display_floating(void)
1190{
1191	Atom wmDelete = XInternAtom(emu_dpy, "WM_DELETE_WINDOW", True);
1192	Atom wmType = XInternAtom(emu_dpy, "_NET_WM_WINDOW_TYPE", False);
1193	Atom wmDialog = XInternAtom(emu_dpy, "_NET_WM_WINDOW_TYPE_DIALOG", False);
1194	XClassHint class = {"uxn11", "Uxn"};
1195	XSetWMProtocols(emu_dpy, emu_win, &wmDelete, 1);
1196	XChangeProperty(emu_dpy, emu_win, wmType, 4, 32, PropModeReplace, (Uint8 *)&wmDialog, 1);
1197	XSelectInput(emu_dpy, emu_win, ButtonPressMask | ButtonReleaseMask | PointerMotionMask | ExposureMask | KeyPressMask | KeyReleaseMask | LeaveWindowMask);
1198	XStoreName(emu_dpy, emu_win, "uxn11");
1199	XSetClassHint(emu_dpy, emu_win, &class);
1200}
1201
1202static void
1203emu_event(void)
1204{
1205	char buf[7];
1206	XEvent ev;
1207	XNextEvent(emu_dpy, &ev);
1208	switch(ev.type) {
1209	case Expose:
1210		screen_reqdraw = 1;
1211		break;
1212	case ClientMessage:
1213		dev[0x0f] = 0x80;
1214		break;
1215	case KeyPress: {
1216		KeySym sym;
1217		XLookupString((XKeyPressedEvent *)&ev, buf, 7, &sym, 0);
1218		switch(sym) {
1219		case XK_F1: screen_zoom = (screen_zoom % 3) + 1, screen_reqsize = screen_reqdraw = 1; break;
1220		case XK_F2: emu_deo(0xe, 0x1); break;
1221		case XK_F4: console_close(), emu_restart(0); break;
1222		case XK_F5: console_close(), emu_restart(1); break;
1223		}
1224		controller_down(get_button(sym));
1225		if(sym == XK_Tab)
1226			buf[0] = 0x9;
1227		controller_key(sym < 0x80 ? sym : (Uint8)buf[0]);
1228	} break;
1229	case KeyRelease: {
1230		KeySym sym;
1231		XLookupString((XKeyPressedEvent *)&ev, buf, 7, &sym, 0);
1232		controller_up(get_button(sym));
1233	} break;
1234	case ButtonPress: {
1235		XButtonPressedEvent *e = (XButtonPressedEvent *)&ev;
1236		switch(e->button) {
1237		case 4: mouse_scroll(0, 1); break;
1238		case 5: mouse_scroll(0, -1); break;
1239		case 6: mouse_scroll(1, 0); break;
1240		case 7: mouse_scroll(-1, 0); break;
1241		default: mouse_down(0x1 << (e->button - 1));
1242		}
1243	} break;
1244	case ButtonRelease: {
1245		XButtonPressedEvent *e = (XButtonPressedEvent *)&ev;
1246		mouse_up(0x1 << (e->button - 1));
1247	} break;
1248	case LeaveNotify:
1249	case MotionNotify: {
1250		XMotionEvent *e = (XMotionEvent *)&ev;
1251		mouse_pos(e->x / screen_zoom, e->y / screen_zoom);
1252	} break;
1253	}
1254}
1255
1256static int
1257emu_init(void)
1258{
1259	emu_dpy = XOpenDisplay(NULL);
1260	if(!emu_dpy)
1261		return !fprintf(stderr, "Display: failed\n");
1262	emu_x11 = ConnectionNumber(emu_dpy);
1263	display_center();
1264	display_hidecursor();
1265	display_floating();
1266	XMapWindow(emu_dpy, emu_win);
1267	XFlush(emu_dpy);
1268	return 1;
1269}
1270
1271static void
1272emu_run(void)
1273{
1274	char coninp[CONINBUFSIZE];
1275	int kq, i, xfd;
1276	ssize_t n;
1277	struct kevent evlist[3], ev, timer;
1278	static const struct timespec timeout = { 1, 0 };
1279
1280	if((kq = kqueue()) == -1) {
1281		perror("kqueue");
1282		exit(1);
1283	}
1284
1285	xfd = XConnectionNumber(emu_dpy);
1286	EV_SET(&evlist[0], -1, EVFILT_TIMER, EV_ADD, NOTE_NSECONDS, 16666666, NULL);
1287	EV_SET(&evlist[1], xfd, EVFILT_READ, EV_ADD, 0, 0, NULL);
1288	EV_SET(&evlist[2], STDIN_FILENO, EVFILT_READ, EV_ADD, 0, 0, NULL);
1289
1290	if(kevent(kq, evlist, 3, NULL, 0, NULL) < 0) {
1291		perror("kevent");
1292		exit(1);
1293	}
1294
1295	while(!dev[0x0f]) {
1296		memset(&ev, 0, sizeof(struct kevent));
1297		if(kevent(kq, NULL, 0, &ev, 1, &timeout) < 0) {
1298			perror("kevent");
1299			exit(1);
1300		}
1301
1302		while(XPending(emu_dpy))
1303			emu_event();
1304		
1305		switch(ev.filter) {
1306		case EVFILT_TIMER:
1307			screen_update();
1308			break;
1309		case EVFILT_READ:
1310			if(ev.ident == STDIN_FILENO) {
1311				n = read(STDIN_FILENO, coninp, CONINBUFSIZE - 1);
1312				for(i = 0, coninp[n] = 0; i < n; i++)
1313					console_input(coninp[i], CONSOLE_STD);
1314				if(n == 0)
1315					console_input(0, CONSOLE_STD);
1316			}
1317			break;
1318		}
1319	}
1320	
1321	close(kq);
1322	if(emu_win)
1323		XDestroyWindow(emu_dpy, emu_win);
1324	if(emu_dpy)
1325		XCloseDisplay(emu_dpy);
1326	console_close();
1327}
1328
1329int
1330main(int argc, char **argv)
1331{
1332	int i = 1;
1333	if(argc == 2 && argv[1][0] == '-' && argv[1][1] == 'v')
1334		return !fprintf(stdout, "%s - Varvara Emulator, 9 Jan 2026.\n", argv[0]);
1335	else if(argc == 1)
1336		return !fprintf(stdout, "usage: %s [-v] file.rom [args..]\n", argv[0]);
1337	else if(!system_boot(argv[i++], argc > 2))
1338		return !fprintf(stdout, "Could not load %s.\n", argv[i - 1]);
1339	screen_resize(WIDTH, HEIGHT);
1340	if(uxn_eval(0x100) && console_vector) {
1341		for(; i < argc; i++) {
1342			char *p = argv[i];
1343			while(*p)
1344				console_input(*p++, CONSOLE_ARG);
1345			console_input('\n', i == argc - 1 ? CONSOLE_END : CONSOLE_EOA);
1346		}
1347	}
1348	if(!dev[0x0f]) {
1349		if(!emu_init())
1350			return !fprintf(stdout, "Could not initialize %s.\n", argv[0]);
1351		emu_run();
1352	}
1353	return dev[0x0f] & 0x7f;
1354}