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}