commit e7d38ed
pita
·
2026-07-24 13:09:51 +0000 UTC
parent 2195006
added -t option to not carry .theme file arround all the time, (by pnthr)
3 files changed,
+1862,
-9
M
makefile
+1,
-0
1@@ -47,6 +47,7 @@ archive:
2 cp etc/tests/perifs.tal ../oscean/etc/varvara.perifs.tal.txt
3 cp etc/tests/file.tal ../oscean/etc/varvara.file.tal.txt
4 cp etc/tests/datetime.tal ../oscean/etc/varvara.datetime.tal.txt
5+ cp etc/tests/audio.tal ../oscean/etc/varvara.audio.tal.txt
6
7 .PHONY: run test clean format install uninstall grab archive
8
+15,
-9
1@@ -641,9 +641,6 @@ audio_start(int instance, Uint8 *d)
2 c->period = NOTE_PERIOD * 337 / 2 / c->len;
3 else
4 c->period = NOTE_PERIOD;
5- /* Debug Print */
6- fprintf(stderr, "[APU %d] PLAY: addr=0x%04x len=%u adsr=0x%04x vol=%d/%d pitch=%d repeat=%d\n",
7- instance, addr, c->len, adsr, c->volume[0], c->volume[1], pitch, c->repeat);
8
9 }
10
11@@ -730,10 +727,6 @@ audio_finished_handler(int instance)
12 {
13 Uint8 *port_value = &dev[0x30 + 0x10 * instance];
14 Uint16 vector = port_value[0] << 8 | port_value[1];
15-
16- /* Debug Print */
17- fprintf(stderr, "[APU %d] FINISHED: vector=0x%04x\n", instance, vector);
18-
19 if(vector)
20 uxn_eval(vector);
21 }
22@@ -877,6 +870,8 @@ mouse_deo_vector(void)
23 #include <sys/stat.h>
24 #include <string.h>
25
26+static char *theme_file = NULL;
27+
28 typedef struct {
29 union {
30 FILE *f;
31@@ -1060,7 +1055,11 @@ static unsigned int
32 file_init(unsigned int id, Uint16 addr)
33 {
34 file_reset(id);
35- ufs[id].filepath = (char *)&ram[addr];
36+ char *path = (char *)&ram[addr];
37+ if(theme_file && strcmp(path, ".theme") == 0)
38+ ufs[id].filepath = theme_file;
39+ else
40+ ufs[id].filepath = path;
41 return 0;
42 }
43
44@@ -1804,8 +1803,15 @@ main(int argc, char **argv)
45 {
46 int i = 1;
47 if(argc == 1)
48- return !fprintf(stdout, "usage: %s [-v] file.rom [args..]\n", argv[0]);
49+ return !fprintf(stdout, "usage: %s [-v] [-t path/to/.theme] [-l x y] file.rom [args..]\n", argv[0]);
50 while(i < argc && argv[i][0] == '-') {
51+ if(!strcmp(argv[i], "-t")) {
52+ if(i + 1 >= argc)
53+ return !fprintf(stdout, "usage: %s [-v] [-t theme] [-l x y] file.rom [args..]\n", argv[0]);
54+ theme_file = argv[++i];
55+ i++;
56+ continue;
57+ }
58 if(!strcmp(argv[i], "-v"))
59 return !fprintf(stdout, "%s - Varvara Emulator(79K), 10 Jul 2026.\n", argv[0]);
60 if(!strcmp(argv[i], "--")){ i++; break; }
+1846,
-0
1@@ -0,0 +1,1846 @@
2+#include <stdio.h>
3+#include <stdlib.h>
4+#include <unistd.h>
5+#include <string.h>
6+#include <sys/mman.h>
7+#include <poll.h>
8+#include <time.h>
9+#include <wayland-client.h>
10+#include <xkbcommon/xkbcommon.h>
11+#include <linux/input-event-codes.h>
12+#include "xdg-shell-client-protocol.h"
13+#include "wlr-layer-shell-client-protocol.h"
14+
15+/*
16+Copyright (c) 2021-2026 Devine Lu Linvega, Andrew Alderwick,
17+Andrew Richards, Eiríkr Åsheim, Sigrid Solveig Haflínudóttir
18+
19+Permission to use, copy, modify, and distribute this software for any
20+purpose with or without fee is hereby granted, provided that the above
21+copyright notice and this permission notice appear in all copies.
22+
23+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
24+WITH REGARD TO THIS SOFTWARE.
25+
26+cc -DNDEBUG -O2 -g0 -s uxn11.c -lX11 -lutil -o uxn11
27+*/
28+
29+extern int mkstemp(char *template);
30+
31+typedef signed char Sint8;
32+typedef unsigned char Uint8;
33+typedef signed short Sint16;
34+typedef unsigned short Uint16;
35+typedef signed int Sint32;
36+typedef unsigned int Uint32;
37+typedef void (*deo_handler)(void);
38+typedef Uint8 (*dei_handler)(void);
39+
40+static unsigned int uxn_eval(Uint16 pc);
41+static Uint8 *ram, dev[0x100], stk[2][0x100], ptr[2];
42+static int console_vector, screen_vector, controller_vector, mouse_vector;
43+static int rX, rY, rA, rMX, rMY, rMA, rML, rDX, rDY, rL1, rL2;
44+
45+#define SAMPLE_FREQUENCY 44100
46+#define POLYPHONY 4
47+#define NOTE_PERIOD (SAMPLE_FREQUENCY * 0x4000 / 11025)
48+#define ADSR_STEP (SAMPLE_FREQUENCY / 0xf)
49+
50+typedef struct {
51+ Uint8 *addr;
52+ Uint32 count, advance, period, age, a, d, s, r;
53+ Uint16 i, len;
54+ Sint8 volume[2];
55+ Uint8 pitch, repeat;
56+} UxnAudio;
57+
58+static UxnAudio uxn_audio[POLYPHONY];
59+
60+static Uint32 advances[12] = {
61+ 0x80000, 0x879c8, 0x8facd, 0x9837f, 0xa1451, 0xaadc1,
62+ 0xb504f, 0xbfc88, 0xcb2ff, 0xd7450, 0xe411f, 0xf1a1c
63+};
64+
65+/* clang-format off */
66+
67+#define BANKS 0x10
68+#define BANKS_CAP BANKS * 0x10000
69+#define WIDTH (64 * 8)
70+#define HEIGHT (40 * 8)
71+
72+static inline Uint16 peek2(const Uint8 *d) { return ((Uint16)d[0] << 8) | d[1]; }
73+static inline void poke2(Uint8 *d, Uint16 v) { d[0] = v >> 8, d[1] = v; }
74+
75+/* clang-format on */
76+
77+/*
78+@|System ------------------------------------------------------------ */
79+
80+static char *system_boot_path;
81+static void emu_screenshot(void);
82+
83+static void
84+system_print(char *name, int r)
85+{
86+ Uint8 i;
87+ fprintf(stderr, "%s%c", name, ptr[r] - 8 ? ' ' : '|');
88+ for(i = ptr[r] - 8; i != ptr[r]; i++)
89+ fprintf(stderr, "%02x%c", stk[r][i], i == 0xff ? '|' : ' ');
90+ fprintf(stderr, "<%02x\n", ptr[r]);
91+}
92+
93+static unsigned int
94+system_load(const char *rom_path)
95+{
96+ FILE *f = fopen(rom_path, "rb");
97+ if(f) {
98+ unsigned int i = 0, l = fread(ram + 0x100, 0x10000 - 0x100, 1, f);
99+ while(l && ++i < BANKS)
100+ l = fread(ram + i * 0x10000, 0x10000, 1, f);
101+ fclose(f);
102+ }
103+ return !!f;
104+}
105+
106+static unsigned int
107+system_boot(char *rom_path, const unsigned int has_args)
108+{
109+ ram = (Uint8 *)calloc(BANKS_CAP, sizeof(Uint8));
110+ system_boot_path = rom_path;
111+ dev[0x17] = has_args;
112+ return ram && system_load(rom_path);
113+}
114+
115+static unsigned int
116+system_reboot(const unsigned int soft)
117+{
118+ memset(dev, 0, 0x100);
119+ memset(stk[0], 0, 0x100);
120+ memset(stk[1], 0, 0x100);
121+ if(soft)
122+ memset(ram + 0x100, 0, 0xff00);
123+ else
124+ memset(ram, 0, 0x10000);
125+ ptr[0] = ptr[1] = 0;
126+ console_vector = screen_vector = controller_vector = mouse_vector = 0;
127+ rX = rY = rA = rMX = rMY = rMA = rML = rDX = rDY = rL1 = rL2 = 0;
128+ return system_load(system_boot_path);
129+}
130+
131+static void
132+system_deo_expansion(void)
133+{
134+ const Uint16 exp = peek2(dev + 2);
135+ Uint8 *aptr = ram + exp;
136+ Uint16 length = peek2(aptr + 1);
137+ unsigned int bank = peek2(aptr + 3) * 0x10000;
138+ unsigned int addr = peek2(aptr + 5);
139+ if(ram[exp] == 0x0) {
140+ if(bank < BANKS_CAP)
141+ memset(ram + bank + addr, ram[exp + 7], length);
142+ } else if(ram[exp] == 1 || ram[exp] == 2) {
143+ unsigned int dst_bank = peek2(aptr + 7) * 0x10000;
144+ unsigned int dst_addr = peek2(aptr + 9);
145+ if(bank < BANKS_CAP && dst_bank < BANKS_CAP)
146+ memmove(ram + dst_bank + dst_addr, ram + bank + addr, length);
147+ } else
148+ fprintf(stderr, "Unknown command: %02x\n", ram[exp]);
149+}
150+
151+static void
152+system_deo_print(void)
153+{
154+ if(dev[0xe] & 0x1)
155+ system_print("WST", 0), system_print("RST", 1);
156+ if(dev[0xe] & 0x2)
157+ emu_screenshot();
158+}
159+
160+/* clang-format off */
161+
162+static Uint8 system_dei_wst(void) { return ptr[0]; }
163+static Uint8 system_dei_rst(void) { return ptr[1]; }
164+static void system_deo_wst(void) { ptr[0] = dev[4]; }
165+static void system_deo_rst(void) { ptr[1] = dev[5]; }
166+
167+/* clang-format on */
168+
169+/*
170+@|Console ----------------------------------------------------------- */
171+
172+#define CONSOLE_STD 0x1
173+#define CONSOLE_ARG 0x2
174+#define CONSOLE_EOA 0x3
175+#define CONSOLE_END 0x4
176+
177+static void
178+console_input(int c, unsigned int type)
179+{
180+ if(c == EOF) c = 0, type = CONSOLE_END;
181+ dev[0x12] = c, dev[0x17] = type;
182+ if(console_vector)
183+ uxn_eval(console_vector);
184+}
185+
186+#include <signal.h>
187+#include <sys/wait.h>
188+
189+#ifdef __linux
190+#include <pty.h>
191+#include <sys/prctl.h>
192+#endif
193+
194+#ifdef __NetBSD__
195+#include <sys/ioctl.h>
196+#include <util.h>
197+#endif
198+
199+/* subprocess support */
200+static char *fork_args[4] = {"/bin/sh", "-c", "", NULL};
201+static int child_mode;
202+static int to_child_fd[2];
203+static int from_child_fd[2];
204+static int saved_in;
205+static int saved_out;
206+static pid_t child_pid;
207+
208+/* child_mode:
209+* 0x01: writes to child's stdin
210+* 0x02: reads from child's stdout
211+* 0x04: reads from child's stderr
212+* 0x08: kill previous process (if any) but do not start
213+* (other bits ignored for now )
214+*/
215+
216+#define CMD_LIVE 0x15 /* 0x00 not started, 0x01 running, 0xff dead */
217+#define CMD_EXIT 0x16 /* if dead, exit code of process */
218+#define CMD_ADDR 0x1c /* address to read command args from */
219+#define CMD_MODE 0x1e /* mode to execute, 0x00 to 0x07 */
220+
221+/* call after we're sure the process has exited */
222+
223+static void
224+clean_after_child(void)
225+{
226+ child_pid = 0;
227+ if(child_mode & 0x01) {
228+ close(to_child_fd[1]);
229+ dup2(saved_out, 1);
230+ }
231+ if(child_mode & (0x04 | 0x02)) {
232+ close(from_child_fd[0]);
233+ dup2(saved_in, 0);
234+ }
235+ child_mode = 0;
236+ saved_in = -1;
237+ saved_out = -1;
238+}
239+
240+static void
241+start_fork_pipe(void)
242+{
243+ pid_t pid;
244+ pid_t parent_pid = getpid();
245+ int addr = peek2(&dev[CMD_ADDR]);
246+ fflush(stdout);
247+ if(child_mode & 0x08) {
248+ dev[CMD_EXIT] = dev[CMD_LIVE] = 0x00;
249+ return;
250+ }
251+ if(child_mode & 0x01) {
252+ /* parent writes to child's stdin */
253+ if(pipe(to_child_fd) == -1) {
254+ dev[CMD_EXIT] = dev[CMD_LIVE] = 0xff;
255+ fprintf(stderr, "Pipe error to child.\n");
256+ return;
257+ }
258+ }
259+ if(child_mode & (0x04 | 0x02)) {
260+ /* parent reads from child's stdout and/or stderr */
261+ if(pipe(from_child_fd) == -1) {
262+ dev[CMD_EXIT] = dev[CMD_LIVE] = 0xff;
263+ fprintf(stderr, "Pipe error from child.\n");
264+ return;
265+ }
266+ }
267+
268+ fork_args[2] = (char *)&ram[addr];
269+ pid = fork();
270+ if(pid < 0) { /* failure */
271+ dev[CMD_EXIT] = dev[CMD_LIVE] = 0xff;
272+ fprintf(stderr, "Fork failure.\n");
273+ } else if(pid == 0) { /* child */
274+
275+#ifdef __linux__
276+ int r = prctl(PR_SET_PDEATHSIG, SIGTERM);
277+ if(r == -1) {
278+ perror(0);
279+ exit(6);
280+ }
281+ if(getppid() != parent_pid) exit(13);
282+#endif
283+
284+ if(child_mode & 0x01) {
285+ dup2(to_child_fd[0], 0);
286+ close(to_child_fd[1]);
287+ }
288+ if(child_mode & (0x04 | 0x02)) {
289+ if(child_mode & 0x02) dup2(from_child_fd[1], 1);
290+ if(child_mode & 0x04) dup2(from_child_fd[1], 2);
291+ close(from_child_fd[0]);
292+ }
293+ fflush(stdout);
294+ execvp(fork_args[0], fork_args);
295+ exit(1);
296+ } else { /*parent*/
297+ child_pid = pid;
298+ dev[CMD_LIVE] = 0x01;
299+ dev[CMD_EXIT] = 0x00;
300+ if(child_mode & 0x01) {
301+ saved_out = dup(1);
302+ dup2(to_child_fd[1], 1);
303+ close(to_child_fd[0]);
304+ }
305+ if(child_mode & (0x04 | 0x02)) {
306+ saved_in = dup(0);
307+ dup2(from_child_fd[0], 0);
308+ close(from_child_fd[1]);
309+ }
310+ }
311+}
312+
313+static void
314+check_child(void)
315+{
316+ int wstatus;
317+ if(child_pid) {
318+ if(waitpid(child_pid, &wstatus, WNOHANG)) {
319+ dev[CMD_LIVE] = 0xff;
320+ dev[CMD_EXIT] = WEXITSTATUS(wstatus);
321+ clean_after_child();
322+ } else {
323+ dev[CMD_LIVE] = 0x01;
324+ dev[CMD_EXIT] = 0x00;
325+ }
326+ }
327+}
328+
329+static void
330+kill_child(void)
331+{
332+ int wstatus;
333+ if(child_pid) {
334+ kill(child_pid, 9);
335+ if(waitpid(child_pid, &wstatus, WNOHANG)) {
336+ dev[CMD_LIVE] = 0xff;
337+ dev[CMD_EXIT] = WEXITSTATUS(wstatus);
338+ clean_after_child();
339+ }
340+ }
341+}
342+
343+static void
344+console_deo_fork(void)
345+{
346+ fflush(stderr);
347+ kill_child();
348+ child_mode = dev[CMD_MODE];
349+ start_fork_pipe();
350+}
351+
352+static void
353+console_close(void)
354+{
355+ kill_child();
356+}
357+
358+/* clang-format off */
359+
360+static Uint8 console_dei_childlive(void) { check_child(); return dev[CMD_LIVE]; }
361+static Uint8 console_dei_childexit(void) { check_child(); return dev[CMD_EXIT]; }
362+static void console_deo_vector(void) { console_vector = peek2(&dev[0x10]); }
363+static void console_deo_stdout(void) { fputc(dev[0x18], stdout), fflush(stdout); }
364+static void console_deo_stderr(void) { fputc(dev[0x19], stderr), fflush(stderr); }
365+static void console_deo_debug1(void) { fprintf(stderr, "%02x", dev[0x1a]); }
366+static void console_deo_debug2(void) { fprintf(stderr, "%02x%02x", dev[0x1a], dev[0x1b]); }
367+
368+/* clang-format on */
369+
370+/*
371+@|Screen ------------------------------------------------------------ */
372+
373+static int screen_zoom = 1, screen_reqsize, screen_reqdraw;
374+static int screen_width, screen_height, screen_wmar2, screen_hmar2;
375+static int *screen_pixels, screen_palette[16];
376+static Uint8 *screen_layers;
377+
378+void emu_redraw(void), emu_resize(void);
379+
380+static const Uint8 blend_lut[16][2][4] = {
381+ {{0, 0, 1, 2}, {0, 0, 4, 8}},
382+ {{0, 1, 2, 3}, {0, 4, 8, 12}},
383+ {{0, 2, 3, 1}, {0, 8, 12, 4}},
384+ {{0, 3, 1, 2}, {0, 12, 4, 8}},
385+ {{1, 0, 1, 2}, {4, 0, 4, 8}},
386+ {{1, 1, 2, 3}, {4, 4, 8, 12}},
387+ {{1, 2, 3, 1}, {4, 8, 12, 4}},
388+ {{1, 3, 1, 2}, {4, 12, 4, 8}},
389+ {{2, 0, 1, 2}, {8, 0, 4, 8}},
390+ {{2, 1, 2, 3}, {8, 4, 8, 12}},
391+ {{2, 2, 3, 1}, {8, 8, 12, 4}},
392+ {{2, 3, 1, 2}, {8, 12, 4, 8}},
393+ {{3, 0, 1, 2}, {12, 0, 4, 8}},
394+ {{3, 1, 2, 3}, {12, 4, 8, 12}},
395+ {{3, 2, 3, 1}, {12, 8, 12, 4}},
396+ {{3, 3, 1, 2}, {12, 12, 4, 8}}};
397+
398+
399+static void
400+system_deo_colorize(void)
401+{
402+ unsigned int i, shift, colors[4];
403+ for(i = 0, shift = 4; i < 4; ++i, shift ^= 4) {
404+ Uint8
405+ r = dev[0x8 + i / 2] >> shift & 0xf,
406+ g = dev[0xa + i / 2] >> shift & 0xf,
407+ b = dev[0xc + i / 2] >> shift & 0xf;
408+ colors[i] = 0x0f000000 | r << 16 | g << 8 | b;
409+ colors[i] |= colors[i] << 4;
410+ }
411+ for(i = 0; i < 16; i++)
412+ screen_palette[i] = colors[i < 4 ? i : i >> 2];
413+ screen_reqdraw = 1;
414+}
415+
416+static void
417+screen_resize(int width, int height)
418+{
419+ if(width != screen_width || height != screen_height) {
420+ int length;
421+ screen_width = width, screen_wmar2 = width + 0x10;
422+ poke2(&dev[0x22], screen_width);
423+ screen_height = height, screen_hmar2 = height + 0x10;
424+ poke2(&dev[0x24], screen_height);
425+ length = screen_wmar2 * screen_hmar2;
426+ screen_layers = realloc(screen_layers, length);
427+ memset(screen_layers, 0, length);
428+ screen_reqsize = screen_reqdraw = 1;
429+ }
430+}
431+
432+static void
433+screen_redraw(void)
434+{
435+ if(screen_zoom == 1) {
436+ int y, *dst_ptr = screen_pixels;
437+ Uint8 *src_ptr = &screen_layers[8 * screen_wmar2 + 8];
438+ for(y = 0; y < screen_height; y++) {
439+ int *dst_end = dst_ptr + screen_width;
440+ while(dst_ptr < dst_end)
441+ *dst_ptr++ = screen_palette[*src_ptr++];
442+ src_ptr += 0x10;
443+ }
444+ } else {
445+ const int stride = screen_width * screen_zoom;
446+ const int zoom_stride = screen_zoom * stride;
447+ int x, y, k, l, row_base = 0, i_base = 8 * screen_wmar2 + 8;
448+ for(y = 0; y < screen_height; y++, row_base += zoom_stride, i_base += screen_wmar2) {
449+ int i = i_base, xs = 0;
450+ for(x = 0; x < screen_width; x++, i++, xs += screen_zoom) {
451+ const int c = screen_palette[screen_layers[i]];
452+ int oo = row_base + xs;
453+ for(k = 0; k < screen_zoom; k++, oo += stride)
454+ for(l = 0; l < screen_zoom; l++)
455+ screen_pixels[oo + l] = c;
456+ }
457+ }
458+ }
459+ screen_reqdraw = 0;
460+}
461+
462+static void
463+screen_update(void)
464+{
465+ if(screen_vector)
466+ uxn_eval(screen_vector);
467+ if(screen_reqsize) {
468+ screen_reqsize = 0;
469+ emu_resize();
470+ }
471+ if(screen_reqdraw)
472+ screen_redraw();
473+}
474+
475+static void
476+screen_deo_pixel(void)
477+{
478+ const int ctrl = dev[0x2e];
479+ const int hi = ctrl & 0x40;
480+ const Uint8 mask = hi ? 0x3 : 0xc;
481+ const Uint8 color = hi ? ((ctrl & 0x3) << 2) : (ctrl & 0x3);
482+ const Uint16 x = rX, y = rY;
483+ if(x < screen_width && y < screen_height) {
484+ /* fill mode */
485+ if(ctrl & 0x80) {
486+ const int x1 = (ctrl & 0x10) ? 8 : x + 8;
487+ const int x2 = (ctrl & 0x10) ? x : screen_width;
488+ const int y1 = (ctrl & 0x20) ? 8 : y + 8;
489+ const int y2 = (ctrl & 0x20) ? y : screen_height;
490+ const int hor = (x2 + 8) - x1;
491+ const int ver = (y2 + 8) - y1;
492+ Uint8 *row = &screen_layers[y1 * screen_wmar2 + x1];
493+ Uint8 *end = row + ver * screen_wmar2;
494+ for(; row < end; row += screen_wmar2) {
495+ int px;
496+ Uint8 *dst = row;
497+ for(px = 0; px < hor; px++)
498+ dst[px] = (dst[px] & mask) | color;
499+ }
500+ }
501+ /* pixel mode */
502+ else {
503+ Uint8 *dst = &screen_layers[(y + 8) * screen_wmar2 + (x + 8)];
504+ *dst = (*dst & mask) | color;
505+ if(rMX) rX += ctrl & 0x10 ? -1 : 1;
506+ if(rMY) rY += ctrl & 0x20 ? -1 : 1;
507+ }
508+ screen_reqdraw = 1;
509+ }
510+}
511+
512+static void
513+screen_deo_sprite(void)
514+{
515+ const int ctrl = dev[0x2f];
516+ const int flipx = ctrl & 0x10, flipy = ctrl & 0x20;
517+ const int dx = flipx ? -rDY : rDY, dy = flipy ? -rDX : rDX;
518+ const int row_start = flipx ? 0 : 7, row_delta = flipx ? 1 : -1;
519+ const int col_start = flipy ? 7 : 0, col_delta = flipy ? -1 : 1;
520+ const int layer = ctrl & 0x40, layer_mask = layer ? 0x3 : 0xc;
521+ const int is_2bpp = ctrl >> 7;
522+ const int addr_incr = rMA << is_2bpp;
523+ const int stride = screen_wmar2;
524+ const int blend = ctrl & 0xf;
525+ const int row_step = dy * stride;
526+ const Uint8 opaque_mask = blend % 5;
527+ const Uint8 *table = blend_lut[blend][layer >> 6];
528+ int i, j, x = rX, y = rY;
529+ int row_base = (y + 8) * stride;
530+ for(i = 0; i <= rML; i++, x += dx, y += dy, rA += addr_incr, row_base += row_step) {
531+ const Uint16 x0 = x + 8, y0 = y + 8;
532+ if(x0 + 8 >= stride || y0 + 8 >= screen_hmar2) continue;
533+ Uint8 *dst = screen_layers + row_base + x0;
534+ const Uint8 *col = &ram[rA + col_start];
535+ for(j = 0; j < 8; j++, dst += stride, col += col_delta) {
536+ Uint8 *d = dst;
537+ const int ch1 = *col;
538+ const int ch2 = is_2bpp ? (col[8] << 1) : 0;
539+ int row = row_start;
540+ for(int k = 0; k < 8; k++, d++, row += row_delta) {
541+ const int color = ((ch1 >> row) & 1) | ((ch2 >> row) & 2);
542+ if(opaque_mask || color)
543+ *d = (*d & layer_mask) | table[color];
544+ }
545+ }
546+ }
547+ screen_reqdraw = 1;
548+ if(rMX) rX += flipx ? -rDX : rDX;
549+ if(rMY) rY += flipy ? -rDY : rDY;
550+}
551+
552+/* clang-format off */
553+
554+static Uint8 screen_dei_rx(void) { dev[0x29] = rX; return rX >> 8; }
555+static Uint8 screen_dei_ry(void) { dev[0x2b] = rY; return rY >> 8; }
556+static Uint8 screen_dei_ra(void) { dev[0x2d] = rA; return rA >> 8; }
557+static void screen_deo_vector(void) { screen_vector = peek2(&dev[0x20]); }
558+static void screen_deo_width(void) { screen_resize(peek2(&dev[0x22]) & 0xfff, screen_height & 0xfff); }
559+static void screen_deo_height(void) { screen_resize(screen_width & 0xfff, peek2(&dev[0x24]) & 0xfff); }
560+static void screen_deo_auto(void) { rMX = dev[0x26] & 0x1, rMY = dev[0x26] & 0x2, rMA = (dev[0x26] & 0x4) << 1, rML = dev[0x26] >> 4, rDX = rMX << 3, rDY = rMY << 2; }
561+static void screen_deo_x(void) { rX = (Sint16)peek2(&dev[0x28]); }
562+static void screen_deo_y(void) { rY = (Sint16)peek2(&dev[0x2a]); }
563+static void screen_deo_addr(void) { rA = peek2(&dev[0x2c]); }
564+
565+/* clang-format on */
566+
567+/*
568+@|Audio ------------------------------------------------------------- */
569+#define MINIAUDIO_IMPLEMENTATION
570+#include "miniaudio.h"
571+
572+static unsigned audio_rate;
573+
574+static Sint32
575+envelope(UxnAudio *c, Uint32 age)
576+{
577+ if(!c->r)
578+ return 0x0888;
579+ if(age < c->a)
580+ return 0x0888 * age / c->a;
581+ if(age < c->d)
582+ return 0x0444 * (2 * c->d - c->a - age) / (c->d - c->a);
583+ if(age < c->s)
584+ return 0x0444;
585+ if(age < c->r)
586+ return 0x0444 * (c->r - age) / (c->r - c->s);
587+ c->advance = 0;
588+ return 0x0000;
589+}
590+
591+static Uint8
592+audio_get_vu(int instance)
593+{
594+ int i;
595+ UxnAudio *c = &uxn_audio[instance];
596+ Sint32 sum[2] = {0, 0};
597+ if(!c->advance || !c->period)
598+ return 0;
599+ for(i = 0; i < 2; i++) {
600+ if(!c->volume[i])
601+ continue;
602+ sum[i] = 1 + envelope(c, c->age) * c->volume[i] / 0x800;
603+ if(sum[i] > 0xf)
604+ sum[i] = 0xf;
605+ }
606+ return (sum[0] << 4) | sum[1];
607+}
608+
609+static Uint16
610+audio_get_position(int instance)
611+{
612+ return uxn_audio[instance].i;
613+}
614+
615+static void
616+audio_start(int instance, Uint8 *d)
617+{
618+ UxnAudio *c = &uxn_audio[instance];
619+ Uint8 pitch = d[0xf] & 0x7f;
620+ Uint16 addr = peek2(d + 0xc);
621+ Uint16 adsr = peek2(d + 0x8);
622+ c->len = peek2(d + 0xa);
623+ if(c->len > 0x10000 - addr)
624+ c->len = 0x10000 - addr;
625+ c->addr = &ram[addr];
626+ c->volume[0] = d[0xe] >> 4;
627+ c->volume[1] = d[0xe] & 0xf;
628+ c->repeat = !(d[0xf] & 0x80);
629+ if(pitch < 108 && c->len)
630+ c->advance = advances[pitch % 12] >> (8 - pitch / 12);
631+ else {
632+ c->advance = 0;
633+ return;
634+ }
635+ c->a = ADSR_STEP * (adsr >> 12);
636+ c->d = ADSR_STEP * (adsr >> 8 & 0xf) + c->a;
637+ c->s = ADSR_STEP * (adsr >> 4 & 0xf) + c->d;
638+ c->r = ADSR_STEP * (adsr >> 0 & 0xf) + c->s;
639+ c->age = 0;
640+ c->i = 0;
641+ if(c->len <= 0x100)
642+ c->period = NOTE_PERIOD * 337 / 2 / c->len;
643+ else
644+ c->period = NOTE_PERIOD;
645+ /* Debug Print */
646+ fprintf(stderr, "[APU %d] PLAY: addr=0x%04x len=%u adsr=0x%04x vol=%d/%d pitch=%d repeat=%d\n",
647+ instance, addr, c->len, adsr, c->volume[0], c->volume[1], pitch, c->repeat);
648+
649+}
650+
651+#define AUDIO_CHAN(n, base) \
652+static void audio##n##_deo_vector (void) {} \
653+static void audio##n##_deo_volume (void) {} \
654+static void audio##n##_deo_pitch (void) { \
655+ audio_start(n, &dev[base]); \
656+} \
657+static Uint8 audio##n##_dei_position (void) { \
658+ return audio_get_position(n) >> 8; \
659+} \
660+static Uint8 audio##n##_dei_position_lo(void) { \
661+ return (Uint8)audio_get_position(n); \
662+} \
663+static Uint8 audio##n##_dei_output (void) { \
664+ return audio_get_vu(n); \
665+}
666+
667+AUDIO_CHAN(0, 0x30)
668+AUDIO_CHAN(1, 0x40)
669+AUDIO_CHAN(2, 0x50)
670+AUDIO_CHAN(3, 0x60)
671+
672+
673+static ma_device audio_device;
674+#define RING_SIZE 8192
675+static Sint16 audio_ring[RING_SIZE];
676+static volatile int audio_ring_w = 0;
677+static volatile int audio_ring_r = 0;
678+
679+static void
680+data_callback(ma_device *device, void *output, const void *input, ma_uint32 framecount)
681+{
682+ Sint16 *out = (Sint16*)output;
683+ ma_uint32 i;
684+ int w = audio_ring_w;
685+ int r = audio_ring_r;
686+ (void)device; (void)input;
687+ for(i = 0; i < framecount * 2; i++) {
688+ if(r != w) {
689+ out[i] = audio_ring[r];
690+ r = (r + 1) % RING_SIZE;
691+ } else
692+ out[i] = 0;
693+ }
694+ audio_ring_r = r;
695+}
696+
697+static int audio_initialized = 0;
698+
699+void
700+audio_init(void)
701+{
702+ ma_device_config config = ma_device_config_init(ma_device_type_playback);
703+ config.playback.format = ma_format_s16;
704+ config.playback.channels = 2;
705+ config.sampleRate = SAMPLE_FREQUENCY;
706+ config.dataCallback = data_callback;
707+ if(ma_device_init(NULL, &config, &audio_device) == MA_SUCCESS) {
708+ ma_device_start(&audio_device);
709+ audio_initialized = 1;
710+ }
711+ audio_rate = SAMPLE_FREQUENCY;
712+}
713+
714+void
715+audio_write(Sint16 *buf, unsigned count)
716+{
717+ (void)buf;
718+ (void)count;
719+ // that white cat thing presenting his hand in a confused matter
720+ unsigned i;
721+ int w = audio_ring_w;
722+ for(i = 0; i < count * 2; i++) {
723+ audio_ring[w] = buf[i];
724+ w = (w + 1) % RING_SIZE;
725+ }
726+ audio_ring_w = w;
727+}
728+
729+static void
730+audio_finished_handler(int instance)
731+{
732+ Uint8 *port_value = &dev[0x30 + 0x10 * instance];
733+ Uint16 vector = port_value[0] << 8 | port_value[1];
734+
735+ /* Debug Print */
736+ fprintf(stderr, "[APU %d] FINISHED: vector=0x%04x\n", instance, vector);
737+
738+ if(vector)
739+ uxn_eval(vector);
740+}
741+
742+static int
743+audio_render(int instance, Sint16 *sample, Sint16 *end)
744+{
745+ UxnAudio *c = &uxn_audio[instance];
746+ Sint32 s;
747+ if(!c->advance || !c->period)
748+ return 0;
749+ while(sample < end) {
750+ c->count += c->advance;
751+ c->i += c->count / c->period;
752+ c->count %= c->period;
753+ if(c->i >= c->len) {
754+ if(!c->repeat) {
755+ c->advance = 0;
756+ break;
757+ }
758+ c->i %= c->len;
759+ }
760+ s = (Sint8)(*(c->addr + c->i) + 0x80) * envelope(c, c->age++);
761+ *sample++ += s * c->volume[0] / 0x180;
762+ *sample++ += s * c->volume[1] / 0x180;
763+ }
764+ if(!c->advance)
765+ audio_finished_handler(instance);
766+ return 1;
767+}
768+
769+static void
770+audio_output(void)
771+{
772+ int c;
773+ Sint16 mix[2048];
774+ int r = audio_ring_r;
775+ int w = audio_ring_w;
776+ int fill = (w - r + RING_SIZE) % RING_SIZE;
777+ if(fill >= 4096)
778+ return;
779+ unsigned count = (4096 - fill) / 2;
780+ if(count > 1024)
781+ count = 1024;
782+ memset(mix, 0, count * 2 * sizeof(Sint16));
783+ for(c = 0; c < POLYPHONY; c++) {
784+ audio_render(c, mix, mix + count * 2);
785+ }
786+ audio_write(mix, count);
787+}
788+
789+/*
790+@|Controller -------------------------------------------------------- */
791+
792+static void
793+controller_down(Uint8 mask)
794+{
795+ if(mask) {
796+ dev[0x82] |= mask;
797+ if(controller_vector)
798+ uxn_eval(controller_vector);
799+ }
800+}
801+
802+static void
803+controller_up(Uint8 mask)
804+{
805+ if(mask) {
806+ dev[0x82] &= (~mask);
807+ if(controller_vector)
808+ uxn_eval(controller_vector);
809+ }
810+}
811+
812+static void
813+controller_key(Uint8 key)
814+{
815+ if(key) {
816+ dev[0x83] = key;
817+ if(controller_vector)
818+ uxn_eval(controller_vector);
819+ dev[0x83] = 0;
820+ }
821+}
822+
823+void
824+controller_deo_vector(void)
825+{
826+ controller_vector = peek2(&dev[0x80]);
827+}
828+
829+/*
830+@|Mouse ------------------------------------------------------------- */
831+
832+static void
833+mouse_down(Uint8 mask)
834+{
835+ dev[0x96] |= mask;
836+ if(mouse_vector)
837+ uxn_eval(mouse_vector);
838+}
839+
840+static void
841+mouse_up(Uint8 mask)
842+{
843+ dev[0x96] &= (~mask);
844+ if(mouse_vector)
845+ uxn_eval(mouse_vector);
846+}
847+
848+static void
849+mouse_pos(Uint16 x, Uint16 y)
850+{
851+ poke2(&dev[0x92], x);
852+ poke2(&dev[0x94], y);
853+ if(mouse_vector)
854+ uxn_eval(mouse_vector);
855+}
856+
857+static void
858+mouse_scroll(Uint16 x, Uint16 y)
859+{
860+ poke2(&dev[0x9a], x);
861+ poke2(&dev[0x9c], -y);
862+ if(mouse_vector)
863+ uxn_eval(mouse_vector);
864+ poke2(&dev[0x9a], 0);
865+ poke2(&dev[0x9c], 0);
866+}
867+
868+void
869+mouse_deo_vector(void)
870+{
871+ mouse_vector = peek2(&dev[0x90]);
872+}
873+
874+/*
875+@|File -------------------------------------------------------------- */
876+
877+#include <dirent.h>
878+#include <sys/stat.h>
879+#include <string.h>
880+
881+typedef struct {
882+ union {
883+ FILE *f;
884+ DIR *dir;
885+ } h;
886+ char *filepath;
887+ enum { IDLE,
888+ FILE_READ,
889+ FILE_WRITE,
890+ DIR_READ,
891+ DIR_WRITE } state;
892+} UxnFile;
893+
894+static UxnFile ufs[2];
895+
896+static inline unsigned int
897+put_text(Uint8 *dest, unsigned int *length, const char *text)
898+{
899+ Uint8 *anchor = dest;
900+ while(*text && *length) {
901+ *dest = *text++, dest++;
902+ *length = *length - 1;
903+ }
904+ *dest = 0;
905+ return dest - anchor;
906+}
907+
908+static inline unsigned int
909+put_size(Uint8 *dest, unsigned int *length, unsigned int len, unsigned int size)
910+{
911+ unsigned int i;
912+ for(i = 0, dest += len; i < len && *length; i++) {
913+ *(--dest) = "0123456789abcdef"[(Uint8)(size & 0xf)];
914+ size >>= 4;
915+ *length = *length - 1;
916+ }
917+ return len;
918+}
919+
920+static unsigned int
921+put_fill(Uint8 *dest, unsigned int *length, unsigned int len, char c)
922+{
923+ unsigned int i;
924+ for(i = 0; i < len && *length; i++) {
925+ *dest++ = c;
926+ *length = *length - 1;
927+ }
928+ return len;
929+}
930+
931+static unsigned int
932+put_stat(Uint8 *dest, unsigned int *length, unsigned int len, unsigned int size, unsigned int err, unsigned int dir)
933+{
934+ unsigned int max = 1 << (len << 2);
935+ if(dir)
936+ return put_fill(dest, length, len, '-');
937+ else if(err)
938+ return put_fill(dest, length, len, '!');
939+ else if(size < max)
940+ return put_size(dest, length, len, size);
941+ return put_fill(dest, length, len, '?');
942+}
943+
944+static unsigned int
945+put_statfile(Uint8 *dest, unsigned int *length, char *pathbuf, unsigned int prefix_len, unsigned int cap, const char *basename)
946+{
947+ unsigned int err, dir;
948+ struct stat st;
949+ Uint8 *anchor = dest;
950+ char *p = pathbuf + prefix_len, *end = pathbuf + cap - 1;
951+ const char *b = basename;
952+ while(*b && p < end)
953+ *p++ = *b++;
954+ *p = 0;
955+ err = stat(pathbuf, &st);
956+ dir = !err && S_ISDIR(st.st_mode);
957+ dest += put_stat(dest, length, 4, st.st_size, err, dir);
958+ dest += put_text(dest, length, "\t");
959+ dest += put_text(dest, length, basename);
960+ dest += put_text(dest, length, dir ? "/\n" : "\n");
961+ return dest - anchor;
962+}
963+
964+static unsigned int
965+put_fdir(Uint8 *dest, unsigned int *length, const char *filepath, DIR *dir)
966+{
967+ Uint8 *p = dest, *end = dest + *length - 1;
968+ struct dirent *de;
969+ char pathbuf[0x200];
970+ unsigned int prefix_len = 0;
971+ char c = '/';
972+ const char *fp = filepath;
973+ while(*fp && prefix_len < sizeof pathbuf - 1)
974+ c = *fp++, pathbuf[prefix_len++] = c;
975+ if(c != '/' && prefix_len < sizeof pathbuf - 1)
976+ pathbuf[prefix_len++] = '/';
977+ while((de = readdir(dir)) && p < end) {
978+ const char *name = de->d_name;
979+ if(name[0] == '.' && (!name[1] || (name[1] == '.' && !name[2])))
980+ continue;
981+ p += put_statfile(p, length, pathbuf, prefix_len, sizeof pathbuf, name);
982+ }
983+ *p = 0;
984+ return p - dest;
985+}
986+
987+static unsigned int
988+is_dir_path(const char *p)
989+{
990+ const char *end = p;
991+ while(*end) end++;
992+ return end > p && end[-1] == '/';
993+}
994+
995+static unsigned int
996+is_dir_real(char *p)
997+{
998+ struct stat st;
999+ return stat(p, &st) == 0 && S_ISDIR(st.st_mode);
1000+}
1001+
1002+static unsigned int
1003+file_write_dir(const char *p)
1004+{
1005+ char buf[0x200];
1006+ char *s = buf, *end = buf + sizeof buf - 1;
1007+ unsigned int ok = 1;
1008+ while(*p && ok && s < end) {
1009+ *s++ = *p;
1010+ if(*p++ == '/') {
1011+ s[-1] = '\0';
1012+ ok = is_dir_real(buf) || mkdir(buf, 0755) == 0;
1013+ s[-1] = '/';
1014+ }
1015+ }
1016+ return ok && !*p;
1017+}
1018+
1019+static void
1020+file_reset(unsigned int id)
1021+{
1022+ switch(ufs[id].state) {
1023+ case FILE_READ:
1024+ case FILE_WRITE: fclose(ufs[id].h.f); break;
1025+ case DIR_READ: closedir(ufs[id].h.dir); break;
1026+ default: break;
1027+ }
1028+ ufs[id].state = IDLE;
1029+}
1030+
1031+static unsigned int
1032+file_open(unsigned int id, unsigned int want_write, Uint8 flags)
1033+{
1034+ UxnFile *u = &ufs[id];
1035+ struct stat st;
1036+ if(want_write) {
1037+ if(u->state == FILE_WRITE || u->state == DIR_WRITE)
1038+ return 1;
1039+ file_reset(id);
1040+ file_write_dir(u->filepath);
1041+ if(is_dir_path(u->filepath)) {
1042+ u->state = is_dir_real(u->filepath) ? DIR_WRITE : IDLE;
1043+ } else if((u->h.f = fopen(u->filepath, (flags & 0x01) ? "ab" : "wb"))) {
1044+ u->state = FILE_WRITE;
1045+ }
1046+ } else {
1047+ if(u->state == FILE_READ || u->state == DIR_READ)
1048+ return 1;
1049+ file_reset(id);
1050+ if(stat(u->filepath, &st) == 0 && S_ISDIR(st.st_mode)) {
1051+ if((u->h.dir = opendir(u->filepath)))
1052+ u->state = DIR_READ;
1053+ } else if((u->h.f = fopen(u->filepath, "rb"))) {
1054+ u->state = FILE_READ;
1055+ }
1056+ }
1057+ return u->state != IDLE;
1058+}
1059+
1060+static unsigned int
1061+file_init(unsigned int id, Uint16 addr)
1062+{
1063+ file_reset(id);
1064+ ufs[id].filepath = (char *)&ram[addr];
1065+ return 0;
1066+}
1067+
1068+static unsigned int
1069+file_read(unsigned int id, Uint16 addr, unsigned int len)
1070+{
1071+ void *dest = &ram[addr];
1072+ unsigned int n, length = len;
1073+ if(ufs[id].filepath == 0)
1074+ return 0;
1075+ if(addr + len > 0x10000)
1076+ length = 0x10000 - addr;
1077+ if(!file_open(id, 0, 0))
1078+ return 0;
1079+ if(ufs[id].state == FILE_READ)
1080+ n = fread(dest, 1, length, ufs[id].h.f);
1081+ else
1082+ n = put_fdir(dest, &length, ufs[id].filepath, ufs[id].h.dir);
1083+ if(len > 0 && n == 0) file_reset(id);
1084+ return n;
1085+}
1086+
1087+static unsigned int
1088+file_write(unsigned int id, Uint16 addr, unsigned int len, Uint8 flags)
1089+{
1090+ if(ufs[id].filepath == 0)
1091+ return 0;
1092+ if(addr + len > 0x10000)
1093+ len = 0x10000 - addr;
1094+ if(!file_open(id, 1, flags))
1095+ return 0;
1096+ if(ufs[id].state == FILE_WRITE) {
1097+ unsigned int ret = fwrite(&ram[addr], 1, len, ufs[id].h.f);
1098+ return (ret > 0 && fflush(ufs[id].h.f) != 0) ? 0 : ret;
1099+ }
1100+ return is_dir_real(ufs[id].filepath);
1101+}
1102+
1103+static unsigned int
1104+file_stat(unsigned int id, Uint16 addr, unsigned int len)
1105+{
1106+ unsigned int err, dir;
1107+ unsigned int length = len;
1108+ struct stat st;
1109+ if(ufs[id].filepath == 0)
1110+ return 0;
1111+ if(addr + len > 0x10000)
1112+ len = 0x10000 - addr;
1113+ err = stat(ufs[id].filepath, &st);
1114+ dir = !err && S_ISDIR(st.st_mode);
1115+ return put_stat(&ram[addr], &length, len, st.st_size, err, dir);
1116+}
1117+
1118+static unsigned int
1119+file_delete(unsigned int id)
1120+{
1121+ if(ufs[id].filepath == 0)
1122+ return 0;
1123+ return !unlink(ufs[id].filepath);
1124+}
1125+
1126+/* clang-format off */
1127+
1128+static void filea_deo_stat(void) { poke2(&dev[0xa2], file_stat(0, peek2(&dev[0xa4]), rL1)); }
1129+static void filea_deo_delete(void) { poke2(&dev[0xa2], file_delete(0)); }
1130+static void filea_deo_name(void) { poke2(&dev[0xa2], file_init(0, peek2(&dev[0xa8]))); }
1131+static void filea_deo_length(void) { rL1 = peek2(&dev[0xaa]); }
1132+static void filea_deo_read(void) { poke2(&dev[0xa2], file_read(0, peek2(&dev[0xac]), rL1)); }
1133+static void filea_deo_write(void) { poke2(&dev[0xa2], file_write(0, peek2(&dev[0xae]), rL1, dev[0xa7])); }
1134+static void fileb_deo_stat(void) { poke2(&dev[0xb2], file_stat(1, peek2(&dev[0xb4]), rL2)); }
1135+static void fileb_deo_delete(void) { poke2(&dev[0xb2], file_delete(1)); }
1136+static void fileb_deo_name(void) { poke2(&dev[0xb2], file_init(1, peek2(&dev[0xb8]))); }
1137+static void fileb_deo_length(void) { rL2 = peek2(&dev[0xba]); }
1138+static void fileb_deo_read(void) { poke2(&dev[0xb2], file_read(1, peek2(&dev[0xbc]), rL2)); }
1139+static void fileb_deo_write(void) { poke2(&dev[0xb2], file_write(1, peek2(&dev[0xbe]), rL2, dev[0xb7])); }
1140+
1141+/* clang-format on */
1142+
1143+/*
1144+@|Datetime ---------------------------------------------------------- */
1145+
1146+int datetime_busy;
1147+time_t datetime_seconds;
1148+struct tm *datetime_t, datetime_zt = {0};
1149+
1150+void
1151+datetime_update(void)
1152+{
1153+ if(!datetime_busy) {
1154+ datetime_seconds = time(NULL);
1155+ datetime_t = localtime(&datetime_seconds);
1156+ if(datetime_t == NULL)
1157+ datetime_t = &datetime_zt;
1158+ datetime_busy = 1;
1159+ }
1160+}
1161+
1162+/* clang-format off */
1163+
1164+static Uint8 datetime_dei_y(void) {
1165+ datetime_update();
1166+ const int v = (datetime_t->tm_year + 1900);
1167+ dev[0xc1] = v;
1168+ return v >> 8; }
1169+static Uint8 datetime_dei_mon(void) { datetime_update(); return datetime_t->tm_mon; }
1170+static Uint8 datetime_dei_day(void) { datetime_update(); return datetime_t->tm_mday; }
1171+static Uint8 datetime_dei_hou(void) { datetime_update(); return datetime_t->tm_hour; }
1172+static Uint8 datetime_dei_min(void) { datetime_update(); return datetime_t->tm_min; }
1173+static Uint8 datetime_dei_sec(void) { datetime_update(); return datetime_t->tm_sec; }
1174+static Uint8 datetime_dei_wday(void) { datetime_update(); return datetime_t->tm_wday; }
1175+static Uint8 datetime_dei_yday(void) {
1176+ datetime_update();
1177+ const int v = datetime_t->tm_yday;
1178+ dev[0xc9] = v;
1179+ return v >> 8; }
1180+static Uint8 datetime_dei_dst(void) { datetime_update(); return datetime_t->tm_isdst; }
1181+
1182+/* clang-format on */
1183+
1184+/*
1185+@|Core -------------------------------------------------------------- */
1186+
1187+static const dei_handler dei_handlers[256] = {
1188+ [0x04] = system_dei_wst,
1189+ [0x05] = system_dei_rst,
1190+ [0x15] = console_dei_childlive,
1191+ [0x16] = console_dei_childexit,
1192+ [0x28] = screen_dei_rx,
1193+ [0x2a] = screen_dei_ry,
1194+ [0x2c] = screen_dei_ra,
1195+ [0x32] = audio0_dei_position,
1196+ [0x33] = audio0_dei_position_lo,
1197+ [0x34] = audio0_dei_output,
1198+ [0x42] = audio1_dei_position,
1199+ [0x43] = audio1_dei_position_lo,
1200+ [0x44] = audio1_dei_output,
1201+ [0x52] = audio2_dei_position,
1202+ [0x53] = audio2_dei_position_lo,
1203+ [0x54] = audio2_dei_output,
1204+ [0x62] = audio3_dei_position,
1205+ [0x63] = audio3_dei_position_lo,
1206+ [0x64] = audio3_dei_output,
1207+ [0xc0] = datetime_dei_y,
1208+ [0xc2] = datetime_dei_mon,
1209+ [0xc3] = datetime_dei_day,
1210+ [0xc4] = datetime_dei_hou,
1211+ [0xc5] = datetime_dei_min,
1212+ [0xc6] = datetime_dei_sec,
1213+ [0xc7] = datetime_dei_wday,
1214+ [0xc8] = datetime_dei_yday,
1215+ [0xca] = datetime_dei_dst};
1216+
1217+static const deo_handler deo_handlers[256] = {
1218+ [0x03] = system_deo_expansion,
1219+ [0x04] = system_deo_wst,
1220+ [0x05] = system_deo_rst,
1221+ [0x09] = system_deo_colorize,
1222+ [0x0b] = system_deo_colorize,
1223+ [0x0d] = system_deo_colorize,
1224+ [0x0e] = system_deo_print,
1225+ [0x11] = console_deo_vector,
1226+ [0x18] = console_deo_stdout,
1227+ [0x19] = console_deo_stderr,
1228+ [0x1a] = console_deo_debug1,
1229+ [0x1b] = console_deo_debug2,
1230+ [0x1f] = console_deo_fork,
1231+ [0x21] = screen_deo_vector,
1232+ [0x23] = screen_deo_width,
1233+ [0x25] = screen_deo_height,
1234+ [0x26] = screen_deo_auto,
1235+ [0x29] = screen_deo_x,
1236+ [0x2b] = screen_deo_y,
1237+ [0x2d] = screen_deo_addr,
1238+ [0x2e] = screen_deo_pixel,
1239+ [0x2f] = screen_deo_sprite,
1240+ [0x30] = audio0_deo_vector,
1241+ [0x3e] = audio0_deo_volume,
1242+ [0x3f] = audio0_deo_pitch,
1243+ [0x40] = audio1_deo_vector,
1244+ [0x4e] = audio1_deo_volume,
1245+ [0x4f] = audio1_deo_pitch,
1246+ [0x50] = audio2_deo_vector,
1247+ [0x5e] = audio2_deo_volume,
1248+ [0x5f] = audio2_deo_pitch,
1249+ [0x60] = audio3_deo_vector,
1250+ [0x6e] = audio3_deo_volume,
1251+ [0x6f] = audio3_deo_pitch,
1252+ [0x81] = controller_deo_vector,
1253+ [0x91] = mouse_deo_vector,
1254+ [0xa5] = filea_deo_stat,
1255+ [0xa6] = filea_deo_delete,
1256+ [0xa9] = filea_deo_name,
1257+ [0xab] = filea_deo_length,
1258+ [0xad] = filea_deo_read,
1259+ [0xaf] = filea_deo_write,
1260+ [0xb5] = fileb_deo_stat,
1261+ [0xb6] = fileb_deo_delete,
1262+ [0xb9] = fileb_deo_name,
1263+ [0xbb] = fileb_deo_length,
1264+ [0xbd] = fileb_deo_read,
1265+ [0xbf] = fileb_deo_write};
1266+
1267+static inline Uint8
1268+emu_dei(const Uint8 port)
1269+{
1270+ dei_handler h = dei_handlers[port];
1271+ return h ? h() : dev[port];
1272+}
1273+
1274+static inline void
1275+emu_deo(const Uint8 port, const Uint8 value)
1276+{
1277+ deo_handler h = deo_handlers[port];
1278+ dev[port] = value;
1279+ if(h) h();
1280+}
1281+
1282+/* clang-format off */
1283+
1284+#define Ld1(o) stk[r][(Uint8)(ptr[r]-(o))]
1285+#define Ld2(o) (Ld1(o) << 8 | stk[r][(Uint8)(ptr[r]-(o)+1)])
1286+#define Ldx(o8,o16) d ? Ld2(o16) : Ld1(o8)
1287+#define Re1(m) ptr[r] -= m;
1288+#define Rex(m1,m2) ptr[r] -= d ? m2 : m1;
1289+#define Pu1(s,v) stk[s][ptr[s]++] = v;
1290+#define Pu2(s,v) Pu1(s,v>>8) Pu1(s,v)
1291+#define Pux(s,v) if(d) Pu1(s,(v)>>8) Pu1(s,v)
1292+#define Lda(s,o) Pu1(r,ram[o]) if(d) Pu1(r,ram[(s)(o+1)])
1293+#define Sta(s,o,u) if(d) ram[o]=u>>8, ram[(s)(o+1)]=u; else ram[o]=u;
1294+#define Dei Pu1(r,emu_dei(x)) if(d) Pu1(r,dev[(x+1)&0xff])
1295+#define Deo if(d) dev[x]=y>>8, emu_deo(x+1,y); else emu_deo(x,y);
1296+
1297+#define MUTE (void)x;(void)y;(void)z;(void)r;(void)d;
1298+
1299+#define OP(opc,X,Y,Z,K,P) \
1300+ case (opc)|0x00: { const int d=0,r=0,x=X,y=Y,z=Z; K P MUTE } break; \
1301+ case (opc)|0x20: { const int d=1,r=0,x=X,y=Y,z=Z; K P MUTE } break; \
1302+ case (opc)|0x40: { const int d=0,r=1,x=X,y=Y,z=Z; K P MUTE } break; \
1303+ case (opc)|0x60: { const int d=1,r=1,x=X,y=Y,z=Z; K P MUTE } break; \
1304+ case (opc)|0x80: { const int d=0,r=0,x=X,y=Y,z=Z; P MUTE } break; \
1305+ case (opc)|0xa0: { const int d=1,r=0,x=X,y=Y,z=Z; P MUTE } break; \
1306+ case (opc)|0xc0: { const int d=0,r=1,x=X,y=Y,z=Z; P MUTE } break; \
1307+ case (opc)|0xe0: { const int d=1,r=1,x=X,y=Y,z=Z; P MUTE } break;
1308+
1309+static unsigned int
1310+uxn_eval(Uint16 start_pc)
1311+{
1312+ Uint16 pc = start_pc;
1313+ for(;;)
1314+ switch(ram[pc++]) {
1315+ /* BRK */ case 0x00: return 1;
1316+ /* JCI */ case 0x20: { Uint16 a=ram[pc]<<8|ram[pc+1]; pc+=2; if(stk[0][--ptr[0]]) pc+=a; } break;
1317+ /* JMI */ case 0x40: { Uint16 a=ram[pc]<<8|ram[pc+1]; pc+=2+a; } break;
1318+ /* JSI */ case 0x60: { Uint16 a=ram[pc]<<8|ram[pc+1]; const Uint8 d=1; pc+=2; Pux(1,pc); pc+=a; } break;
1319+ /* LIT */ case 0x80: { const Uint8 r=0,d=0; Lda(Uint16,pc); pc+=1; } break;
1320+ /* LI2 */ case 0xa0: { const Uint8 r=0,d=1; Lda(Uint16,pc); pc+=2; } break;
1321+ /* LIr */ case 0xc0: { const Uint8 r=1,d=0; Lda(Uint16,pc); pc+=1; } break;
1322+ /* L2r */ case 0xe0: { const Uint8 r=1,d=1; Lda(Uint16,pc); pc+=2; } break;
1323+ /* INC */ OP(0x01, Ldx(1,2),0, 0, Rex(1,2),Pux(r,x+1))
1324+ /* POP */ OP(0x02, 0, 0, 0, Rex(1,2),{})
1325+ /* NIP */ OP(0x03, Ldx(1,2),0, 0, Rex(2,4),Pux(r,x))
1326+ /* SWP */ OP(0x04, Ldx(1,2),Ldx(2,4),0, Rex(2,4),Pux(r,x) Pux(r,y))
1327+ /* ROT */ OP(0x05, Ldx(1,2),Ldx(2,4),Ldx(3,6),Rex(3,6),Pux(r,y) Pux(r,x) Pux(r,z))
1328+ /* DUP */ OP(0x06, Ldx(1,2),0, 0, Rex(1,2),Pux(r,x) Pux(r,x))
1329+ /* OVR */ OP(0x07, Ldx(1,2),Ldx(2,4),0, Rex(2,4),Pux(r,y) Pux(r,x) Pux(r,y))
1330+ /* EQU */ OP(0x08, Ldx(1,2),Ldx(2,4),0, Rex(2,4),Pu1(r,x==y))
1331+ /* NEQ */ OP(0x09, Ldx(1,2),Ldx(2,4),0, Rex(2,4),Pu1(r,y!=x))
1332+ /* GTH */ OP(0x0a, Ldx(1,2),Ldx(2,4),0, Rex(2,4),Pu1(r,y>x))
1333+ /* LTH */ OP(0x0b, Ldx(1,2),Ldx(2,4),0, Rex(2,4),Pu1(r,y<x))
1334+ /* JMP */ OP(0x0c, Ldx(1,2),0, 0, Rex(1,2),pc=d?x:(pc+(Sint8)x);)
1335+ /* JCN */ OP(0x0d, Ldx(1,2),Ld1(d+2),0, Rex(2,3),if(y) pc=d?x:(pc+(Sint8)x);)
1336+ /* JSR */ OP(0x0e, Ldx(1,2),0, 0, Rex(1,2),Pu2(!r,pc) pc=d?x:(pc+(Sint8)x);)
1337+ /* STH */ OP(0x0f, Ldx(1,2),0, 0, Rex(1,2),Pux(!r,x))
1338+ /* LDZ */ OP(0x10, Ld1(1), 0, 0, Re1(1), Lda(Uint8,x))
1339+ /* STZ */ OP(0x11, Ld1(1), Ldx(2,3),0, Rex(2,3),Sta(Uint8,x,y))
1340+ /* LDR */ OP(0x12, Ld1(1), 0, 0, Re1(1), Uint16 t=pc+(Sint8)x; Lda(Uint16,t))
1341+ /* STR */ OP(0x13, Ld1(1), Ldx(2,3),0, Rex(2,3),Uint16 t=pc+(Sint8)x; Sta(Uint16,t,y))
1342+ /* LDA */ OP(0x14, Ld2(2), 0, 0, Re1(2), Lda(Uint16,x))
1343+ /* STA */ OP(0x15, Ld2(2), Ldx(3,4),0, Rex(3,4),Sta(Uint16,x,y))
1344+ /* DEI */ OP(0x16, Ld1(1), 0, 0, Re1(1), Dei)
1345+ /* DEO */ OP(0x17, Ld1(1), Ldx(2,3),0, Rex(2,3),Deo)
1346+ /* ADD */ OP(0x18, Ldx(1,2),Ldx(2,4),0, Rex(2,4),Pux(r,y+x))
1347+ /* SUB */ OP(0x19, Ldx(1,2),Ldx(2,4),0, Rex(2,4),Pux(r,y-x))
1348+ /* MUL */ OP(0x1a, Ldx(1,2),Ldx(2,4),0, Rex(2,4),Pux(r,y*x))
1349+ /* DIV */ OP(0x1b, Ldx(1,2),Ldx(2,4),0, Rex(2,4),Pux(r,x?y/x:0))
1350+ /* AND */ OP(0x1c, Ldx(1,2),Ldx(2,4),0, Rex(2,4),Pux(r,y&x))
1351+ /* ORA */ OP(0x1d, Ldx(1,2),Ldx(2,4),0, Rex(2,4),Pux(r,y|x))
1352+ /* EOR */ OP(0x1e, Ldx(1,2),Ldx(2,4),0, Rex(2,4),Pux(r,y^x))
1353+ /* SFT */ OP(0x1f, Ld1(1) ,Ldx(2,3),0, Rex(2,3),Pux(r,(y>>(x&0xf))<<(x>>4)))
1354+ }
1355+ return 0;
1356+}
1357+
1358+/* clang-format on */
1359+
1360+static struct wl_display *emu_dpy;
1361+static struct wl_registry *emu_reg;
1362+static struct wl_compositor *emu_comp;
1363+static struct wl_shm *emu_shm;
1364+static struct wl_seat *emu_seat;
1365+static struct wl_pointer *emu_ptr;
1366+static struct wl_keyboard *emu_kbd;
1367+static struct xdg_wm_base *emu_wm;
1368+static struct wl_surface *emu_surf;
1369+static struct xdg_surface *emu_xdgsurf;
1370+static struct xdg_toplevel *emu_top;
1371+static struct wl_buffer *emu_bufs[2];
1372+static struct zwlr_layer_shell_v1 *emu_layer_shell;
1373+static struct zwlr_layer_surface_v1 *emu_layer_surf;
1374+static int use_layer_shell = 0;
1375+static int layer_shell_x = 0;
1376+static int layer_shell_y = 0;
1377+static int emu_configured;
1378+static int active_buf;
1379+
1380+static void *shm_data;
1381+static int shm_size, shm_bufsize, shm_width, shm_height;
1382+
1383+static struct xkb_context *xkb_ctx;
1384+static struct xkb_keymap *xkb_map;
1385+static struct xkb_state *xkb_state;
1386+
1387+#define CONINBUFSIZE 256
1388+
1389+void
1390+emu_resize(void)
1391+{
1392+ const int width = screen_width * screen_zoom;
1393+ const int height = screen_height * screen_zoom;
1394+ if(width != shm_width || height != shm_height) {
1395+ const int stride = width * 4;
1396+ const int size = stride * height;
1397+ struct wl_shm_pool *pool;
1398+ int fd;
1399+ if(shm_data)
1400+ munmap(shm_data, shm_size);
1401+ if(emu_bufs[0])
1402+ wl_buffer_destroy(emu_bufs[0]);
1403+ if(emu_bufs[1])
1404+ wl_buffer_destroy(emu_bufs[1]);
1405+ char tmpl[] = "/tmp/uxn-wl-XXXXXX";
1406+ fd = mkstemp(tmpl);
1407+ unlink(tmpl);
1408+ ftruncate(fd, size * 2);
1409+ shm_data = mmap(NULL, size * 2, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
1410+ pool = wl_shm_create_pool(emu_shm, fd, size * 2);
1411+ emu_bufs[0] = wl_shm_pool_create_buffer(pool, 0, width, height, stride, WL_SHM_FORMAT_XRGB8888);
1412+ emu_bufs[1] = wl_shm_pool_create_buffer(pool, size, width, height, stride, WL_SHM_FORMAT_XRGB8888);
1413+ wl_shm_pool_destroy(pool);
1414+ close(fd);
1415+ shm_size = size * 2, shm_bufsize = size, shm_width = width, shm_height = height;
1416+ }
1417+ screen_pixels = (int *)((Uint8 *)shm_data + active_buf * shm_bufsize);
1418+ active_buf = 0;
1419+}
1420+
1421+static void frame_callback(void *, struct wl_callback *, uint32_t);
1422+
1423+static const struct wl_callback_listener frame_listener = {
1424+ .done = frame_callback,
1425+};
1426+
1427+void
1428+emu_redraw(void)
1429+{
1430+ if(!emu_configured)
1431+ return;
1432+ wl_callback_add_listener(wl_surface_frame(emu_surf), &frame_listener, NULL);
1433+ wl_surface_attach(emu_surf, emu_bufs[active_buf], 0, 0);
1434+ wl_surface_damage_buffer(emu_surf, 0, 0, screen_width * screen_zoom, screen_height * screen_zoom);
1435+ wl_surface_commit(emu_surf);
1436+}
1437+
1438+static void
1439+frame_callback(void *data, struct wl_callback *cb, uint32_t time)
1440+{
1441+ wl_callback_destroy(cb);
1442+ int old = active_buf;
1443+ active_buf ^= 1;
1444+ memcpy((Uint8 *)shm_data + active_buf * shm_bufsize,
1445+ (Uint8 *)shm_data + old * shm_bufsize, shm_bufsize);
1446+ screen_pixels = (int *)((Uint8 *)shm_data + active_buf * shm_bufsize);
1447+}
1448+
1449+static void
1450+emu_restart(unsigned int soft)
1451+{
1452+ system_reboot(soft);
1453+ screen_width = screen_height = 0;
1454+ screen_resize(WIDTH, HEIGHT);
1455+ uxn_eval(0x100);
1456+}
1457+
1458+static void
1459+emu_screenshot(void)
1460+{
1461+ FILE *f;
1462+ char filename[] = "_00x00.chr";
1463+ unsigned int length = 4;
1464+ int tx, ty, row, col, tw = screen_width / 8, th = screen_height / 8;
1465+ put_size((Uint8 *)filename + 1, &length, 2, tw);
1466+ put_size((Uint8 *)filename + 4, &length, 2, th);
1467+ f = fopen(filename, "wb");
1468+ if(f) {
1469+ for(ty = 0; ty < th; ty++) {
1470+ for(tx = 0; tx < tw; tx++) {
1471+ Uint8 tile[16] = {0};
1472+ for(row = 0; row < 8; row++) {
1473+ const Uint8 *src = &screen_layers[(ty * 8 + row + 8) * screen_wmar2 + (tx * 8 + 8)];
1474+ Uint8 b1 = 0, b2 = 0;
1475+ for(col = 0; col < 8; col++) {
1476+ Uint8 px = src[col];
1477+ b1 |= ((px & 0x1) << (7 - col)), b2 |= (((px >> 2) & 0x1) << (7 - col));
1478+ }
1479+ tile[row] = b1, tile[row + 8] = b2;
1480+ }
1481+ fwrite(tile, 1, 16, f);
1482+ }
1483+ }
1484+ fclose(f);
1485+ }
1486+}
1487+
1488+static Uint8
1489+get_button(xkb_keysym_t sym)
1490+{
1491+ switch(sym) {
1492+ case XKB_KEY_Up: return 0x10;
1493+ case XKB_KEY_Down: return 0x20;
1494+ case XKB_KEY_Left: return 0x40;
1495+ case XKB_KEY_Right: return 0x80;
1496+ case XKB_KEY_Control_L: return 0x01;
1497+ case XKB_KEY_Alt_R:
1498+ case XKB_KEY_Alt_L: return 0x02;
1499+ case XKB_KEY_Shift_L: return 0x04;
1500+ case XKB_KEY_Home: return 0x08;
1501+ case XKB_KEY_Super_L: return 0x02;
1502+ }
1503+ return 0x00;
1504+}
1505+
1506+static void
1507+kbd_keymap(void *d, struct wl_keyboard *k, uint32_t fmt, int32_t fd, uint32_t size)
1508+{
1509+ char *map;
1510+ if(fmt != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) {
1511+ close(fd);
1512+ return;
1513+ }
1514+ map = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
1515+ if(map != MAP_FAILED) {
1516+ xkb_map = xkb_keymap_new_from_string(xkb_ctx, map, XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS);
1517+ munmap(map, size);
1518+ if(xkb_state)
1519+ xkb_state_unref(xkb_state);
1520+ xkb_state = xkb_state_new(xkb_map);
1521+ }
1522+ close(fd);
1523+}
1524+
1525+static void kbd_enter(void *d, struct wl_keyboard *k, uint32_t serial, struct wl_surface *surf, struct wl_array *keys) { }
1526+static void kbd_leave(void *d, struct wl_keyboard *k, uint32_t serial, struct wl_surface *surf) { }
1527+
1528+static void
1529+kbd_key(void *d, struct wl_keyboard *k, uint32_t serial, uint32_t time, uint32_t key, uint32_t state)
1530+{
1531+ xkb_keysym_t sym;
1532+ if(!xkb_state)
1533+ return;
1534+ sym = xkb_state_key_get_one_sym(xkb_state, key + 8);
1535+ if(state == WL_KEYBOARD_KEY_STATE_PRESSED) {
1536+ char buf[8] = {0};
1537+ switch(sym) {
1538+ case XKB_KEY_F1: screen_zoom = (screen_zoom % 3) + 1, screen_reqsize = screen_reqdraw = 1; break;
1539+ case XKB_KEY_F2: emu_deo(0xe, 0x1); break;
1540+ case XKB_KEY_F4: console_close(), emu_restart(0); break;
1541+ case XKB_KEY_F5: console_close(), emu_restart(1); break;
1542+ case XKB_KEY_F6: emu_deo(0xe, 0x2); break;
1543+ }
1544+ controller_down(get_button(sym));
1545+ xkb_state_key_get_utf8(xkb_state, key + 8, buf, sizeof buf);
1546+ if(sym == XKB_KEY_Tab)
1547+ buf[0] = 0x9;
1548+ controller_key(sym < 0x80 ? (Uint8)sym : (Uint8)buf[0]);
1549+ } else
1550+ controller_up(get_button(sym));
1551+}
1552+
1553+static void
1554+kbd_modifiers(void *d, struct wl_keyboard *k, uint32_t serial, uint32_t depressed, uint32_t latched, uint32_t locked, uint32_t group)
1555+{
1556+ if(xkb_state)
1557+ xkb_state_update_mask(xkb_state, depressed, latched, locked, 0, 0, group);
1558+}
1559+
1560+static const struct wl_keyboard_listener kbd_listener = {
1561+ .keymap = kbd_keymap,
1562+ .enter = kbd_enter,
1563+ .leave = kbd_leave,
1564+ .key = kbd_key,
1565+ .modifiers = kbd_modifiers,
1566+};
1567+
1568+static void
1569+ptr_enter(void *d, struct wl_pointer *p, uint32_t serial, struct wl_surface *surf, wl_fixed_t sx, wl_fixed_t sy)
1570+{
1571+ wl_pointer_set_cursor(p, serial, NULL, 0, 0);
1572+ mouse_pos(wl_fixed_to_int(sx) / screen_zoom, wl_fixed_to_int(sy) / screen_zoom);
1573+}
1574+
1575+static void ptr_leave(void *d, struct wl_pointer *p, uint32_t serial, struct wl_surface *surf) { }
1576+
1577+static void
1578+ptr_motion(void *d, struct wl_pointer *p, uint32_t time, wl_fixed_t sx, wl_fixed_t sy)
1579+{
1580+ mouse_pos(wl_fixed_to_int(sx) / screen_zoom, wl_fixed_to_int(sy) / screen_zoom);
1581+}
1582+
1583+static void
1584+ptr_button(void *d, struct wl_pointer *p, uint32_t serial, uint32_t time, uint32_t button, uint32_t state)
1585+{
1586+ Uint8 mask = 0;
1587+ switch(button) {
1588+ case BTN_LEFT: mask = 0x01; break;
1589+ case BTN_MIDDLE: mask = 0x02; break;
1590+ case BTN_RIGHT: mask = 0x04; break;
1591+ }
1592+ if(state == WL_POINTER_BUTTON_STATE_PRESSED)
1593+ mouse_down(mask);
1594+ else
1595+ mouse_up(mask);
1596+}
1597+
1598+static void
1599+ptr_axis(void *d, struct wl_pointer *p, uint32_t time, uint32_t axis, wl_fixed_t value)
1600+{
1601+ const int v = wl_fixed_to_int(value) / 10;
1602+ if(axis == WL_POINTER_AXIS_VERTICAL_SCROLL)
1603+ mouse_scroll(0, v);
1604+ else if(axis == WL_POINTER_AXIS_HORIZONTAL_SCROLL)
1605+ mouse_scroll(v, 0);
1606+}
1607+
1608+static const struct wl_pointer_listener ptr_listener = {
1609+ .enter = ptr_enter,
1610+ .leave = ptr_leave,
1611+ .motion = ptr_motion,
1612+ .button = ptr_button,
1613+ .axis = ptr_axis,
1614+};
1615+
1616+static void
1617+seat_caps(void *d, struct wl_seat *seat, uint32_t caps)
1618+{
1619+ if((caps & WL_SEAT_CAPABILITY_POINTER) && !emu_ptr) {
1620+ emu_ptr = wl_seat_get_pointer(seat);
1621+ wl_pointer_add_listener(emu_ptr, &ptr_listener, NULL);
1622+ }
1623+ if((caps & WL_SEAT_CAPABILITY_KEYBOARD) && !emu_kbd) {
1624+ emu_kbd = wl_seat_get_keyboard(seat);
1625+ wl_keyboard_add_listener(emu_kbd, &kbd_listener, NULL);
1626+ }
1627+}
1628+
1629+static const struct wl_seat_listener seat_listener = {
1630+ .capabilities = seat_caps,
1631+};
1632+
1633+static void wm_ping(void *d, struct xdg_wm_base *wm, uint32_t serial) { xdg_wm_base_pong(wm, serial); }
1634+static const struct xdg_wm_base_listener wm_listener = { .ping = wm_ping };
1635+
1636+static void
1637+xs_configure(void *d, struct xdg_surface *xs, uint32_t serial)
1638+{
1639+ xdg_surface_ack_configure(xs, serial);
1640+ emu_configured = 1;
1641+ screen_reqdraw = 1;
1642+}
1643+
1644+static const struct xdg_surface_listener xs_listener = {
1645+ .configure = xs_configure,
1646+};
1647+
1648+static void xt_configure(void *d, struct xdg_toplevel *xt, int32_t w, int32_t h, struct wl_array *st) { }
1649+/* xt_close takes over X11's ClientMessage/WM_DELETE_WINDOW case. */
1650+static void xt_close(void *d, struct xdg_toplevel *xt) { dev[0x0f] = 0x80; }
1651+
1652+static const struct xdg_toplevel_listener xt_listener = {
1653+ .configure = xt_configure,
1654+ .close = xt_close,
1655+};
1656+
1657+static void
1658+ls_configure(void *d, struct zwlr_layer_surface_v1 *ls, uint32_t serial, uint32_t w, uint32_t h)
1659+{
1660+ zwlr_layer_surface_v1_ack_configure(ls, serial);
1661+ emu_configured = 1;
1662+ screen_reqdraw = 1;
1663+}
1664+
1665+static void
1666+ls_closed(void *d, struct zwlr_layer_surface_v1 *ls) { dev[0x0f] = 0x80; }
1667+
1668+static const struct zwlr_layer_surface_v1_listener ls_listener = {
1669+ .configure = ls_configure,
1670+ .closed = ls_closed,
1671+};
1672+
1673+static void
1674+reg_global(void *d, struct wl_registry *reg, uint32_t name, const char *iface, uint32_t ver)
1675+{
1676+ if(!strcmp(iface, "wl_compositor"))
1677+ emu_comp = wl_registry_bind(reg, name, &wl_compositor_interface, 4);
1678+ else if(!strcmp(iface, "wl_shm"))
1679+ emu_shm = wl_registry_bind(reg, name, &wl_shm_interface, 1);
1680+ else if(!strcmp(iface, "xdg_wm_base"))
1681+ emu_wm = wl_registry_bind(reg, name, &xdg_wm_base_interface, 1);
1682+ else if(!strcmp(iface, "zwlr_layer_shell_v1"))
1683+ emu_layer_shell = wl_registry_bind(reg, name, &zwlr_layer_shell_v1_interface, 4);
1684+ else if(!strcmp(iface, "wl_seat")) {
1685+ emu_seat = wl_registry_bind(reg, name, &wl_seat_interface, 1);
1686+ wl_seat_add_listener(emu_seat, &seat_listener, NULL);
1687+ }
1688+}
1689+
1690+static const struct wl_registry_listener reg_listener = {
1691+ .global = reg_global,
1692+};
1693+
1694+
1695+static int
1696+emu_init(void)
1697+{
1698+ xkb_ctx = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
1699+ emu_dpy = wl_display_connect(NULL);
1700+ if(!emu_dpy)
1701+ return !fprintf(stderr, "Display: failed\n");
1702+ emu_reg = wl_display_get_registry(emu_dpy);
1703+ wl_registry_add_listener(emu_reg, ®_listener, NULL);
1704+ wl_display_roundtrip(emu_dpy);
1705+ if(!emu_comp || !emu_shm || (!emu_wm && !use_layer_shell ))
1706+ return !fprintf(stderr, "Wayland: missing globals\n");
1707+
1708+ emu_surf = wl_compositor_create_surface(emu_comp);
1709+ if(use_layer_shell && emu_layer_shell) {
1710+ emu_layer_surf = zwlr_layer_shell_v1_get_layer_surface(
1711+ emu_layer_shell, emu_surf, NULL,
1712+ ZWLR_LAYER_SHELL_V1_LAYER_TOP, "uxn-wl");
1713+ zwlr_layer_surface_v1_add_listener(emu_layer_surf, &ls_listener, NULL);
1714+ zwlr_layer_surface_v1_set_size(emu_layer_surf, screen_width, screen_height);
1715+ zwlr_layer_surface_v1_set_anchor(emu_layer_surf,
1716+ ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT);
1717+ zwlr_layer_surface_v1_set_margin(emu_layer_surf, layer_shell_y, 0, 0, layer_shell_x);
1718+ zwlr_layer_surface_v1_set_keyboard_interactivity(emu_layer_surf, 2);
1719+ wl_surface_commit(emu_surf);
1720+ } else if(emu_wm) {
1721+ xdg_wm_base_add_listener(emu_wm, &wm_listener, NULL);
1722+ emu_xdgsurf = xdg_wm_base_get_xdg_surface(emu_wm, emu_surf);
1723+ xdg_surface_add_listener(emu_xdgsurf, &xs_listener, NULL);
1724+ emu_top = xdg_surface_get_toplevel(emu_xdgsurf);
1725+ xdg_toplevel_add_listener(emu_top, &xt_listener, NULL);
1726+ xdg_toplevel_set_title(emu_top, "uxn-wl");
1727+ xdg_toplevel_set_app_id(emu_top, "uxn-wl");
1728+ wl_surface_commit(emu_surf);
1729+ }
1730+ wl_display_roundtrip(emu_dpy);
1731+ return 1;
1732+}
1733+
1734+static void
1735+emu_run(void)
1736+{
1737+ int has_input, stdin_active = 1;
1738+ char coninp[CONINBUFSIZE];
1739+ struct pollfd fds[2];
1740+ struct timespec next_refresh;
1741+ fds[0].fd = wl_display_get_fd(emu_dpy), fds[0].events = POLLIN;
1742+ fds[1].fd = STDIN_FILENO, fds[1].events = POLLIN | POLLHUP;
1743+ clock_gettime(CLOCK_MONOTONIC, &next_refresh);
1744+ while(!dev[0x0f]) {
1745+ wl_display_dispatch_pending(emu_dpy);
1746+ wl_display_flush(emu_dpy);
1747+ struct timespec now;
1748+ clock_gettime(CLOCK_MONOTONIC, &now);
1749+ long long now_ms = (long long)now.tv_sec * 1000 + now.tv_nsec / 1000000;
1750+ long long next_ms = (long long)next_refresh.tv_sec * 1000 + next_refresh.tv_nsec / 1000000;
1751+ if(now_ms >= next_ms) {
1752+ audio_output();
1753+ screen_update();
1754+ emu_redraw();
1755+ next_ms += 16;
1756+ if(now_ms > next_ms + 100)
1757+ next_ms = now_ms +16;
1758+ next_refresh.tv_sec = next_ms / 1000;
1759+ next_refresh.tv_nsec = (next_ms % 1000) * 1000000;
1760+ }
1761+ clock_gettime(CLOCK_MONOTONIC, &now);
1762+ now_ms = (long long)now.tv_sec * 1000 + now.tv_nsec / 1000000;
1763+ int timeout = next_ms - now_ms;
1764+ if(timeout < 0)
1765+ timeout = 0;
1766+ /* only pull stdin while it's still active */
1767+ if(poll(fds, stdin_active ? 2 : 1, timeout) <= 0)
1768+ continue;
1769+ if(fds[0].revents & POLLIN)
1770+ wl_display_dispatch(emu_dpy);
1771+ has_input = stdin_active && (fds[1].revents & POLLIN);
1772+ if(has_input || (stdin_active && (fds[1].revents & POLLHUP))) {
1773+ int i, n = read(STDIN_FILENO, coninp, CONINBUFSIZE - 1);
1774+ if(n > 0) {
1775+ for(i = 0; i < n; i++)
1776+ console_input(coninp[i], CONSOLE_STD);
1777+ } else {
1778+ console_input('\n', CONSOLE_END);
1779+ stdin_active = 0;
1780+ }
1781+ }
1782+ datetime_busy = 0;
1783+ }
1784+ if(emu_bufs[0])
1785+ wl_buffer_destroy(emu_bufs[0]);
1786+ if(emu_bufs[1])
1787+ wl_buffer_destroy(emu_bufs[1]);
1788+ if(emu_top)
1789+ xdg_toplevel_destroy(emu_top);
1790+ if(emu_xdgsurf)
1791+ xdg_surface_destroy(emu_xdgsurf);
1792+ if(emu_layer_surf)
1793+ zwlr_layer_surface_v1_destroy(emu_layer_surf);
1794+ if(emu_surf)
1795+ wl_surface_destroy(emu_surf);
1796+ if(audio_initialized)
1797+ ma_device_uninit(&audio_device);
1798+ if(emu_dpy)
1799+ wl_display_disconnect(emu_dpy);
1800+ console_close();
1801+}
1802+
1803+int
1804+main(int argc, char **argv)
1805+{
1806+ int i = 1;
1807+ if(argc == 1)
1808+ return !fprintf(stdout, "usage: %s [-v] file.rom [args..]\n", argv[0]);
1809+ while(i < argc && argv[i][0] == '-') {
1810+ if(!strcmp(argv[i], "-v"))
1811+ return !fprintf(stdout, "%s - Varvara Emulator(79K), 10 Jul 2026.\n", argv[0]);
1812+ if(!strcmp(argv[i], "--")){ i++; break; }
1813+ if(!strcmp(argv[i], "-l")) {
1814+ if(i + 3 >= argc)
1815+ return !fprintf(stdout, "usage: %s [-l x y] file.rom [args..]\n", argv[0]);
1816+ use_layer_shell = 1;
1817+ layer_shell_x = atoi(argv[++i]);
1818+ layer_shell_y = atoi(argv[++i]);
1819+ i++;
1820+ continue;
1821+ }
1822+ return !fprintf(stdout, "unknown flag: %s\n", argv[i]);
1823+ }
1824+ if(i >= argc)
1825+ return !fprintf(stdout, "usage: %s [-v] file.rom [args..]\n", argv[0]);
1826+ if(!system_boot(argv[i], argc > i + 1))
1827+ return !fprintf(stdout, "Could not load %s.\n", argv[i]);
1828+ i++;
1829+ screen_resize(WIDTH, HEIGHT);
1830+ if(uxn_eval(0x100) && console_vector) {
1831+ for(; i < argc; i++) {
1832+ char *p = argv[i];
1833+ while(*p)
1834+ console_input(*p++, CONSOLE_ARG);
1835+ console_input('\n', i == argc - 1 ? CONSOLE_END : CONSOLE_EOA);
1836+ }
1837+ }
1838+ if(!dev[0x0f]) {
1839+ if(!emu_init())
1840+ return !fprintf(stdout, "Could not initialize %s.\n", argv[0]);
1841+ screen_update();
1842+ emu_redraw();
1843+ audio_init();
1844+ emu_run();
1845+ }
1846+ return dev[0x0f] & 0x7f;
1847+}