/* * Copyright (C) 2025 dwlr * * BSD 3-Clause License (BSD-3-Clause) * See LICENSE for details */ #define _DEFAULT_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "bits.c" #include "log.c" #include "plat.c" #include "mem.c" #include "tga.c" #include "x11.c" #include "iui.c" #include "chip8.c" #include "meta/disasm.c" #include "meta/exec.c" void iui_draw(iui* ui) { usize i; xctx* x11 = NULL; assert(ui); x11 = (xctx*)ui->usr; for(i = 0; i < ui->ncmds; i++) { iui_cmd* cmd = ui->cmds + i; switch(cmd->type) { case IUI_FILL: { XGCValues gcvals = {0}; gcvals.line_width = cmd->size; gcvals.foreground = cmd->color; XChangeGC(x11->disp, x11->gc, GCForeground | GCLineWidth, &gcvals); XFillRectangle(x11->disp, x11->wnd, x11->gc, cmd->x, cmd->y, cmd->w, cmd->h); } break; case IUI_BOX: { XGCValues gcvals = {0}; gcvals.line_width = cmd->size; gcvals.foreground = cmd->color; XChangeGC(x11->disp, x11->gc, GCForeground | GCLineWidth, &gcvals); XDrawRectangle(x11->disp, x11->wnd, x11->gc, cmd->x, cmd->y, cmd->w, cmd->h); } break; case IUI_TEXT: { int xx, yy; int di, fa, fd; int width, height; int len; XCharStruct chs = {0}; len = strlen(cmd->text); XTextExtents(x11->xfs, cmd->text, len, &di, &fa, &fd, &chs); width = chs.rbearing - chs.lbearing; height = chs.ascent + chs.descent; if(cmd->flags & IUI_TXT_CENTER) xx = cmd->x + cmd->w / 2 - width / 2; else xx = cmd->x; yy = cmd->y + cmd->h / 2 + height / 2; XSetForeground(x11->disp, x11->gc, cmd->color); XDrawString(x11->disp, x11->wnd, x11->gc, xx, yy, cmd->text, len); } break; case IUI_CLIP: { if(cmd->flags & IUI_CLIP_CLEAR) XSetClipMask(x11->disp, x11->gc, None); else { XRectangle clip_region = {0}; clip_region.x = cmd->x; clip_region.y = cmd->y; clip_region.width = cmd->w; clip_region.height = cmd->h; XSetClipRectangles(x11->disp, x11->gc, 0, 0, &clip_region, 1, Unsorted); } } break; default: report(WARN, "IUI: Unknown draw command %u\n", cmd->type); break; } } ui->ncmds = 0; } void c8_dump_state_iui(chip8* c8, bool stack, iui* ui, u16 x, u16 y) { int i = 0; u16 dx = x, dy = y; u16 w = 80, h = 10, p = 5; for(i = 0; i < (int)sizeof(c8->V); i += 4) { iui_label(ui, dx, dy, w, h, "V%x: %02X (%3d)", i+0, c8->V[i+0], c8->V[i+0]); dx += w + p; iui_label(ui, dx, dy, w, h, "V%x: %02X (%3d)", i+1, c8->V[i+1], c8->V[i+1]); dx += w + p; iui_label(ui, dx, dy, w, h, "V%x: %02X (%3d)", i+2, c8->V[i+2], c8->V[i+2]); dx += w + p; iui_label(ui, dx, dy, w, h, "V%x: %02X (%3d)", i+3, c8->V[i+3], c8->V[i+3]); dx = x; dy += h + p; } iui_label(ui, dx, dy, 1000, h, "I:%04X SP: %02X DT: %02X ST: %02X CYCLES: %llu KEYS: %X", c8->i, c8->sp, c8->dt, c8->st, c8->cycles, c8->keys); #if 0 if(stack) { iui_label(ui, "stack:\n"); for(i = 0; i < (int)sizeof(c8->STACK); i += 2) { iui_label(ui, " %-2d: %04X ", i+0, c8->STACK[i+0]); iui_label(ui, "%-2d: %04X\n", i+1, c8->STACK[i+1]); } } #endif } int main(int argc, char** argv) { chip8 c8 = {0}; xctx x11 = {0}; arena ar = {0}; iui ui = {0}; bool run = true; u64 sz = 0; FILE* f = NULL; char buf[512] = {0}; srand(time(NULL)); if(argc > 1) { u64 got = 0; f = fopen(argv[1], "r"); if(!f) return 1; fseek(f, 0, SEEK_END); sz = (u64)ftell(f); rewind(f); if(sz >= (sizeof(c8.RAM) - 512)) return 2; got = fread(c8.RAM + C8_RESET_VECTOR, 1, sz, f); if(got != sz) return 3; fclose(f); } else { c8.RAM[C8_RESET_VECTOR + iota] = 0x60; c8.RAM[C8_RESET_VECTOR + iota] = 0x04; c8.RAM[C8_RESET_VECTOR + iota] = 0x61; c8.RAM[C8_RESET_VECTOR + iota] = 0x03; c8.RAM[C8_RESET_VECTOR + iota] = 0x72; c8.RAM[C8_RESET_VECTOR + iota] = 0x01; c8.RAM[C8_RESET_VECTOR + iota] = 0x80; c8.RAM[C8_RESET_VECTOR + iota] = 0x14; c8.RAM[C8_RESET_VECTOR + iota] = 0x30; c8.RAM[C8_RESET_VECTOR + iota] = 0x10; c8.RAM[C8_RESET_VECTOR + iota] = 0x12; c8.RAM[C8_RESET_VECTOR + iota] = 0x04; sz = iota; } x11_init(800, 600, "xip-8", argc, argv, &x11); ar_create(4*MB, true, &ar); iui_init(&ui, &ar, &x11); c8_reset(&c8); c8.running = false; while(run) { XEvent ev; while(XCheckTypedWindowEvent(x11.disp, x11.wnd, ClientMessage, &ev)) if((Atom)ev.xclient.data.l[0] == x11.xa_delwnd) run = false; while(XCheckWindowEvent(x11.disp, x11.wnd, max_u32, &ev)) { bool keyup = false; switch(ev.type) { case KeymapNotify: XRefreshKeyboardMapping(&ev.xmapping); break; case KeyRelease: keyup = true; /* fall through */ case KeyPress: { KeySym ks; char name[32] = {0}; int len = 0; len = XLookupString(&ev.xkey, name, sizeof(name) - 1, &ks, NULL); if(keyup && ks == XK_Escape) run = false; if(!ui.kbdfocus) { u16 bits = 0; switch(name[0]) { case 'x': bits = bit(0); break; case '1': bits = bit(1); break; case '2': bits = bit(2); break; case '3': bits = bit(3); break; case 'q': bits = bit(4); break; case 'w': bits = bit(5); break; case 'e': bits = bit(6); break; case 'd': bits = bit(7); break; case 's': bits = bit(8); break; case 'a': bits = bit(9); break; case 'z': bits = bit(10); break; case 'c': bits = bit(11); break; case '4': bits = bit(12); break; case 'r': bits = bit(13); break; case 'f': bits = bit(14); break; case 'v': bits = bit(15); break; default: break; } if(keyup) c8.keys &= ~bits; else c8.keys |= bits; } if(!keyup && len == 1) { if(ui.kbdfocus && (isgraph(name[0]) || name[0] == ' ')) strcat((char*)ui.kbdfocus, name); } if(!keyup && ks == XK_BackSpace) { if(ui.kbdfocus) { ssize i = strlen((char*)ui.kbdfocus) - 1; if(i >= 0) ((char*)ui.kbdfocus)[i] = '\0'; } } } break; case ButtonPress: if(ev.xbutton.button == Button1) ui.btns |= bit(1); if(ev.xbutton.button == Button2) ui.btns |= bit(2); if(ev.xbutton.button == Button3) ui.btns |= bit(3); break; case ButtonRelease: if(ev.xbutton.button == Button1) ui.btns &= ~bit(1); if(ev.xbutton.button == Button2) ui.btns &= ~bit(2); if(ev.xbutton.button == Button3) ui.btns &= ~bit(3); break; case MotionNotify: ui.x = ev.xmotion.x; ui.y = ev.xmotion.y; break; case Expose: { XWindowAttributes wa = {0}; XGCValues gcvals = {0}; gcvals.line_width = 1; gcvals.foreground = x11.cols.as.bg; XGetWindowAttributes(x11.disp, x11.wnd, &wa); XChangeGC(x11.disp, x11.gc, GCForeground | GCLineWidth, &gcvals); XFillRectangle(x11.disp, x11.wnd, x11.gc, 0, 0, wa.width, wa.height); /* TODO: draw ui * for ui draw cmds * draw cmd */ } break; default: break; } } if(c8.running) { c8_disasm(c8.RAM, c8.pc, buf); c8_exec(c8.RAM, c8.pc, &c8); } { int len = 0; char buf[512] = {0}; XGCValues gcvals = {0}; gcvals.foreground = x11.cols.as.bg; XChangeGC(x11.disp, x11.gc, GCForeground | GCLineWidth, &gcvals); XFillRectangle(x11.disp, x11.wnd, x11.gc, 0, 0, 800, 600); XSetForeground(x11.disp, x11.gc, x11.cols.as.fg); len = snprintf(buf, lengthof(buf), "hot: %p", (void*)ui.hot); XDrawString(x11.disp, x11.wnd, x11.gc, 650, 25, buf, len); len = snprintf(buf, lengthof(buf), "act: %p", (void*)ui.active); XDrawString(x11.disp, x11.wnd, x11.gc, 650, 45, buf, len); len = snprintf(buf, lengthof(buf), "cur: %dx%d", ui.x, ui.y); XDrawString(x11.disp, x11.wnd, x11.gc, 650, 65, buf, len); len = snprintf(buf, lengthof(buf), "btn: %d%d%d", ui.btns & bit(1) ? 1 : 0, ui.btns & bit(2) ? 2 : 0, ui.btns & bit(3) ? 3 : 0); XDrawString(x11.disp, x11.wnd, x11.gc, 650, 85, buf, len); len = snprintf(buf, lengthof(buf), "old: %d%d%d", ui.pbtns & bit(1) ? 1 : 0, ui.pbtns & bit(2) ? 2 : 0, ui.pbtns & bit(3) ? 3 : 0); XDrawString(x11.disp, x11.wnd, x11.gc, 650, 105, buf, len); len = snprintf(buf, lengthof(buf), "kbd: %p", (void*)ui.kbdfocus); XDrawString(x11.disp, x11.wnd, x11.gc, 650, 125, buf, len); len = snprintf(buf, lengthof(buf), "ar: %lu bytes", ar.cursor - ar.base); XDrawString(x11.disp, x11.wnd, x11.gc, 650, 145, buf, len); iui_draw(&ui); } iui_start(&ui); c8_dump_state_iui(&c8, false, &ui, 25, 25); iui_editbox(&ui, 25, 600 - 45, 225, 35, buf, lengthof(buf)); if(!c8.running && iui_button(&ui, 270, 600 - 45, 100, 35, "Go")) c8.running = true; else if(c8.running && iui_button(&ui, 270, 600 - 45, 100, 35, "Stop")) c8.running = false; iui_end(&ui); { usize x, y, s = 12; XSetForeground(x11.disp, x11.gc, x11.cols.as.fg); XSetBackground(x11.disp, x11.gc, x11.cols.as.bg); for(y = 0; y < 32; y++) for(x = 0; x < 64; x++) if(c8.DISPLAY[y] & bit(x)) XFillRectangle(x11.disp, x11.wnd, x11.gc, 25 + x*s, 128 + y*s, s, s); } usleep(100); } x11_cleanup(&x11); return 0; }