summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordweller <dweller@cabin.digital>2025-03-28 19:52:47 +0200
committerdweller <dweller@cabin.digital>2025-03-28 19:52:47 +0200
commitb41ec911812e66931f01939378979845716b6119 (patch)
tree08be727857d9881182a723af382680c48fb89434
parent2bcb97cade32e4781135ff4c1500b95fcf351889 (diff)
experimenting with UI
-rw-r--r--Makefile24
-rwxr-xr-xbuild/x1123
-rwxr-xr-xclean2
-rw-r--r--resources/icon.tgabin0 -> 1068 bytes
-rw-r--r--resources/icon.xcfbin0 -> 2778 bytes
-rw-r--r--sources/bits.c72
-rw-r--r--sources/main.c343
-rw-r--r--sources/tga.c138
-rw-r--r--sources/x11.c294
-rwxr-xr-xutils/bin2.c79
10 files changed, 937 insertions, 38 deletions
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..4f32fb4
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,24 @@
+.POSIX:
+.SUFFIXES:
+
+CC = gcc
+CFLAGS = -std=c89 -Wall -Wextra -fwhole-program
+X11FLAGS = -lm -lX11 -lxcb -lXau -lXdmcp
+
+x11: artifacts/xip-8
+
+artifacts/xip-8: sources/*.c resources/icon.tga.h
+ $(CC) $(CFLAGS) $(X11FLAGS) sources/main.c -o artifacts/xip-8
+
+artifacts/bin2c: utils/bin2.c
+ $(CC) $(CFLAGS) utils/bin2.c -o artifacts/bin2c
+
+resources/icon.tga.h: resources/icon.tga artifacts/bin2c
+ ./artifacts/bin2c resources/icon.tga
+
+run: x11
+ ./artifacts/xip-8
+
+clean:
+ rm -f resources/*.h
+ rm -rf artifacts/*
diff --git a/build/x11 b/build/x11
deleted file mode 100755
index cae1518..0000000
--- a/build/x11
+++ /dev/null
@@ -1,23 +0,0 @@
-#!/bin/sh -e
-
-CC=${CC:-musl-gcc}
-STATIC=${STATIC:-"-static"}
-CFLAGS=" \
- -std=c89 \
- -Wall -Wextra -Wno-comment \
- -ggdb -Og \
-"
-
-bench()
-{
- if [ x = "x$(which time 2>/dev/null)" ]; then
- $@
- echo "time not installed"
- else
- time $@
- fi
-}
-
-mkdir -p artifacts
->&2 bench $CC $CFLAGS sources/main.c $STATIC -o artifacts/xip-8
->&2 echo "------------------------------------------------\n"
diff --git a/clean b/clean
deleted file mode 100755
index 1dd2ea1..0000000
--- a/clean
+++ /dev/null
@@ -1,2 +0,0 @@
-#!/bin/sh -e
-rm -rf artifacts/*
diff --git a/resources/icon.tga b/resources/icon.tga
new file mode 100644
index 0000000..73d0cd3
--- /dev/null
+++ b/resources/icon.tga
Binary files differ
diff --git a/resources/icon.xcf b/resources/icon.xcf
new file mode 100644
index 0000000..25cb8c1
--- /dev/null
+++ b/resources/icon.xcf
Binary files differ
diff --git a/sources/bits.c b/sources/bits.c
index 0197436..de0aa0d 100644
--- a/sources/bits.c
+++ b/sources/bits.c
@@ -5,22 +5,70 @@
* See LICENSE for details
*/
+typedef unsigned char u8;
+typedef unsigned short u16;
+typedef unsigned int u32;
+typedef unsigned long long u64; /* XXX: GCC extension */
+typedef signed char s8;
+typedef signed short s16;
+typedef signed int s32;
+typedef signed long long s64; /* XXX: GCC extension */
+
+typedef s8 bool;
+
+typedef u64 usize;
+typedef s64 ssize;
+
+typedef float f32;
+typedef double f64;
+
+
+#define min_s8 ((s8) (0x80))
+#define min_s16 ((s16)(0x8000))
+#define min_s32 ((s32)(0x80000000L))
+#define min_s64 ((s64)(0x8000000000000000LL))
+#define min_ssize min_s64
+
+#define max_s8 ((s8) (0x7F))
+#define max_s16 ((s16)(0x7FFF))
+#define max_s32 ((s32)(0x7FFFFFFFL))
+#define max_s64 ((s64)(0x7FFFFFFFFFFFFFFFLL))
+#define max_ssize max_s64
+
+#define max_u8 ((u8) (0xFF))
+#define max_u16 ((u16)(0xFFFF))
+#define max_u32 ((u32)(0xFFFFFFFFUL))
+#define max_u64 ((u64)(0xFFFFFFFFFFFFFFFFULL))
+#define max_usize max_u64
+
+#ifndef true
+ #define true 1
+#endif
+
+#ifndef false
+ #define false 0
+#endif
#define iota __COUNTER__
+#define nil {0}
-#define lengthof(x) (sizeof(x) / sizeof((x)[0]))
+#define KB bit(10LL)
+#define MB bit(20LL)
+#define GB bit(30LL)
+#define TB bit(40LL)
-#define bit(x) (1 << (x))
+#define packed __attribute__((packed))
-#define KB bit(10)
-#define MB bit(11)
-#define GB bit(12)
+#define bit(n) (1LL << (n))
+#define align(what, to) (((what) + ((to) - 1)) & ~((to) - 1)) /* NOTE: `to` *must* be pow. of 2 */
-typedef unsigned char u8;
-typedef unsigned short u16;
-typedef unsigned long long u64;
-typedef u64 usize;
+#define lengthof(arr) (sizeof(arr) / sizeof(arr[0]))
+#ifndef offsetof
+ #define offsetof(strct, memb) ((ssize)(&((strct*)(0)->memb)))
+#endif
+
+#define min(a, b) (((a) <= (b)) ? (a) : (b))
+#define max(a, b) (((a) >= (b)) ? (a) : (b))
+#define clamp(x, mn, mx) (min((mn), max((x), (mx))))
-typedef u8 bool;
-#define true 1
-#define false 0
+#define unused(expr) ((void)(expr))
diff --git a/sources/main.c b/sources/main.c
index 5bc1c42..791cf43 100644
--- a/sources/main.c
+++ b/sources/main.c
@@ -5,7 +5,6 @@
* See LICENSE for details
*/
-
#define _DEFAULT_SOURCE
#include <stdlib.h>
#include <stdio.h>
@@ -13,19 +12,360 @@
#include <assert.h>
#include <time.h>
#include <stdarg.h>
+#include <ctype.h>
#include <endian.h>
+#include <unistd.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include <X11/Xlib.h>
+#include <X11/Xatom.h>
+#include <X11/Xutil.h>
+#include <X11/Xresource.h>
+
#include "bits.c"
#include "log.c"
+#include "tga.c"
+#include "x11.c"
#include "chip8.c"
#include "meta/disasm.c"
#include "meta/exec.c"
+typedef struct
+{
+ void* usr;
+
+ usize hot;
+ usize active;
+ usize kbdfocus;
+
+ /* mouse */
+ u16 x;
+ u16 y;
+
+ u8 pbtns;
+ u8 btns;
+
+} iui;
+
+void iui_start(iui* ui, void* usr)
+{
+ assert(ui);
+
+ ui->usr = usr;
+ ui->hot = 0;
+}
+
+void iui_end(iui* ui)
+{
+ assert(ui);
+
+ if(!ui->btns) ui->active = 0;
+ else
+ {
+ if(!ui->active) ui->kbdfocus = 0;
+ }
+
+ ui->pbtns = ui->btns;
+}
+
+bool iui_mouse_inside(iui* ui, u16 x, u16 y, u16 w, u16 h)
+{
+ return !(ui->x < x || ui->y < y || ui->x >= x + w || ui->y >= y + h);
+}
+
+int iui_button(iui* ui, u16 x, u16 y, int w, int h, char* label)
+{
+ xctx* x11 = (xctx*)ui->usr;
+ usize id = (usize)label;
+ bool imhot, imactive;
+
+ if(iui_mouse_inside(ui, x, y, w, h))
+ {
+ bool click = ui->btns & bit(1) && !(ui->pbtns & bit(1));
+ if(!ui->active && click) ui->active = id;
+ ui->hot = id;
+ }
+
+ imhot = ui->hot == id;
+ imactive = ui->active == id;
+
+ {
+ XGCValues gcvals = {0};
+
+ if(imhot) gcvals.line_width = 2;
+ else gcvals.line_width = 1;
+
+ if(imactive) gcvals.foreground = x11->cols.as.fg;
+ else gcvals.foreground = x11->cols.as.bg;
+
+ XChangeGC(x11->disp, x11->gc, GCForeground | GCLineWidth, &gcvals);
+ XFillRectangle(x11->disp, x11->wnd, x11->gc, x, y, w, h);
+
+ XSetForeground(x11->disp, x11->gc, x11->cols.as.fg);
+ XDrawLine(x11->disp, x11->wnd, x11->gc, x, y, x, y + h);
+ XDrawLine(x11->disp, x11->wnd, x11->gc, x + w, y, x, y);
+
+ XSetForeground(x11->disp, x11->gc, x11->cols.as.fg);
+ XDrawLine(x11->disp, x11->wnd, x11->gc, x, y + h, x + w, y + h);
+ XDrawLine(x11->disp, x11->wnd, x11->gc, x + w, y + h, x + w, y);
+ }
+
+ {
+ usize len;
+ int xx, yy;
+ int di, fa, fd;
+ int width, height;
+ XCharStruct chs = {0};
+
+ len = strlen(label);
+ XTextExtents(x11->xfs, label, len, &di, &fa, &fd, &chs);
+ width = chs.rbearing - chs.lbearing;
+ height = chs.ascent + chs.descent;
+
+ xx = x + w / 2 - width / 2;
+ yy = y + h / 2 + height / 2;
+
+ if(imactive) XSetForeground(x11->disp, x11->gc, x11->cols.as.bg);
+ else XSetForeground(x11->disp, x11->gc, x11->cols.as.fg);
+ XDrawString(x11->disp, x11->wnd, x11->gc, xx, yy, label, len);
+ }
+
+ return imhot && imactive && !(ui->btns & bit(1));
+}
+
+int iui_textbox(iui* ui, u16 x, u16 y, int w, int h, char* buf, usize buflen)
+{
+ xctx* x11 = (xctx*)ui->usr;
+ usize id = (usize)buf;
+ bool imhot, imactive;
+ usize len;
+
+ len = strlen(buf);
+
+ if(iui_mouse_inside(ui, x, y, w, h))
+ {
+ bool click = ui->btns & bit(1) && !(ui->pbtns & bit(1));
+ if(!ui->active && click) ui->active = id;
+ ui->hot = id;
+ }
+
+ imhot = ui->hot == id;
+ imactive = ui->active == id;
+
+ {
+ XGCValues gcvals = {0};
+
+ if(imhot) gcvals.line_width = 2;
+ else gcvals.line_width = 1;
+
+ gcvals.foreground = x11->cols.as.bg;
+
+ XChangeGC(x11->disp, x11->gc, GCForeground | GCLineWidth, &gcvals);
+ XFillRectangle(x11->disp, x11->wnd, x11->gc, x, y, w, h);
+
+ XSetForeground(x11->disp, x11->gc, x11->cols.as.fg);
+ XDrawLine(x11->disp, x11->wnd, x11->gc, x, y, x, y + h);
+ XDrawLine(x11->disp, x11->wnd, x11->gc, x + w, y, x, y);
+
+ XSetForeground(x11->disp, x11->gc, x11->cols.as.fg);
+ XDrawLine(x11->disp, x11->wnd, x11->gc, x, y + h, x + w, y + h);
+ XDrawLine(x11->disp, x11->wnd, x11->gc, x + w, y + h, x + w, y);
+ }
+
+ if(imactive) ui->kbdfocus = id;
+ if(ui->kbdfocus == id)
+ {
+
+ XGCValues gcvals = {0};
+ gcvals.foreground = x11->cols.as.fg;
+
+ XChangeGC(x11->disp, x11->gc, GCForeground, &gcvals);
+ XFillRectangle(x11->disp, x11->wnd, x11->gc, x + 6 + (6 * len), y + 6, 10, h - 12);
+ }
+ else
+ {
+ XGCValues gcvals = {0};
+ gcvals.foreground = x11->cols.as.fg;
+ gcvals.line_width = 1;
+
+ XChangeGC(x11->disp, x11->gc, GCForeground | GCLineWidth, &gcvals);
+ XDrawRectangle(x11->disp, x11->wnd, x11->gc, x + 6 + (6 * len), y + 6, 9, h - 13);
+ }
+
+ {
+ int xx, yy;
+ int di, fa, fd;
+ int width, height;
+ XCharStruct chs = {0};
+
+ XTextExtents(x11->xfs, buf, buflen, &di, &fa, &fd, &chs);
+ height = chs.ascent + chs.descent;
+
+ xx = x + 6;
+ yy = y + h / 2 + height / 2;
+
+
+ if(imactive) XSetForeground(x11->disp, x11->gc, x11->cols.as.bg);
+ else XSetForeground(x11->disp, x11->gc, x11->cols.as.fg);
+ XDrawString(x11->disp, x11->wnd, x11->gc, xx, yy, buf, len);
+ }
+
+ return imhot && imactive && !(ui->btns & bit(1));
+}
+
+
int main(int argc, char** argv)
{
+ xctx x11 = {0};
+ iui ui = {0};
+ bool run = true;
+ char txtbuf[256] = {0};
+
+ x11_init(800, 600, "xip-8", argc, argv, &x11);
+
+ 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(!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;
+ }
+ }
+
+ {
+ 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);
+ }
+
+ iui_start(&ui, &x11);
+ if(iui_button(&ui, 25, 25, 100, 35, "Click ME!")) report(DBG, "CLICK!\n");
+ if(iui_button(&ui, 150, 25, 100, 35, "NOT ME!!!")) report(DBG, "WHYYYYYY!\n");
+
+ iui_textbox(&ui, 25, 75, 225, 35, txtbuf, lengthof(txtbuf));
+ if(iui_button(&ui, 275, 75, 100, 35, "OK")) report(DBG, "textbox: '%s'!\n", txtbuf);
+ iui_end(&ui);
+
+ usleep(10000);
+ }
+
+ x11_cleanup(&x11);
+ return 0;
+}
+
+int main2(int argc, char** argv)
+{
u64 sz = 0;
FILE* f = NULL;
chip8 c8 = {0};
@@ -93,6 +433,7 @@ int main(int argc, char** argv)
{
char buf[512] = {0};
+
c8_disasm(c8.RAM, c8.pc, buf);
printf("%04X: %s\n", c8.pc, buf);
diff --git a/sources/tga.c b/sources/tga.c
new file mode 100644
index 0000000..ccdd64d
--- /dev/null
+++ b/sources/tga.c
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2024 dwlr <dweller@cabin.digital>
+ *
+ * BSD 3-Clause License (BSD-3-Clause)
+ * See LICENSE for details
+ */
+
+typedef struct packed tga_head
+{
+ u8 id_len;
+ u8 cmap_type;
+ u8 img_type;
+
+ struct packed
+ {
+ u16 first_entry_idx;
+ u16 length;
+ u8 entry_size;
+
+ } cmap_spec;
+
+ struct packed
+ {
+ u16 xorig;
+ u16 yorig;
+ u16 width;
+ u16 height;
+ u8 depth;
+ u8 desc;
+
+ } img_spec;
+
+} tga_head;
+
+typedef enum tga_img_type
+{
+ TGA_IMG_NONE,
+ TGA_IMG_RAW_CMAP,
+ TGA_IMG_RAW_TRUE,
+ TGA_IMG_RAW_BW,
+ TGA_IMG_RLE_CMAP,
+ TGA_IMG_RLE_TRUE,
+ TGA_IMG_RLE_BW,
+
+ TGA_IMG_TYPE_SIZE
+
+} tga_img_type;
+
+typedef union rgba
+{
+ u32 rgba;
+ struct
+ {
+ u8 b;
+ u8 g;
+ u8 r;
+ u8 a;
+ } c;
+
+} rgba;
+
+#define A c.a
+#define R c.r
+#define G c.g
+#define B c.b
+
+
+typedef struct texture
+{
+ ssize width;
+ ssize height;
+
+ rgba* texels;
+
+} texture;
+
+
+/* FIXME: return 1x1 magenta tex on fail? */
+bool tga2tex_from_mem(texture* tex, const u8* data)
+{
+ tga_head* hdr = NULL;
+ rgba* raw = NULL;
+ usize sz = 0;
+
+ assert(tex);
+ assert(data);
+
+ hdr = (tga_head*)data;
+ if(hdr->img_type != TGA_IMG_RAW_TRUE) return false;
+ if(hdr->cmap_type) return false;
+
+ /* FIXME: check pixel depth */
+ /* FIXME: check bit 5 and 4 of img_spec.desc (ordering) */
+
+ tex->width = hdr->img_spec.width;
+ tex->height = hdr->img_spec.height;
+
+ sz = sizeof(tex->texels[0]) * tex->width * tex->height;
+ tex->texels = malloc(sz);
+ if(!tex->texels) return false;
+
+ raw = (rgba*)(((u8*)hdr) + sizeof(*hdr) + hdr->id_len);
+
+ /* FIXME: convert if needed */
+ /* XXX: possibly misaligned */
+ memcpy(tex->texels, raw, sz);
+
+ return true;
+}
+
+
+bool tga2tex_from_file(texture* tex, const char* path)
+{
+ struct stat stat = {0};
+
+ bool ret = false;
+ s32 fd = -1;
+ u8* file = NULL;
+
+ assert(tex);
+ assert(path);
+
+ fd = open(path, O_RDONLY);
+ if(fd < 0) goto clean_close;
+
+ if(fstat(fd, &stat) != 0) goto clean_close;
+
+ file = mmap(NULL, stat.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
+ if(file == MAP_FAILED) goto clean_close;
+
+ ret = tga2tex_from_mem(tex, file);
+
+ munmap(file, stat.st_size);
+clean_close:
+ close(fd);
+
+ return ret;
+}
diff --git a/sources/x11.c b/sources/x11.c
new file mode 100644
index 0000000..b9f27b3
--- /dev/null
+++ b/sources/x11.c
@@ -0,0 +1,294 @@
+#if true != True
+ #error "true != Xlib's True"
+#endif
+
+#if false != False
+ #error "false != Xlib's False"
+#endif
+
+#define RGB(r, g, b) (((r) << 16) | ((g) << 8) | (b))
+
+static const char icon_tga[] =
+{
+ #include "../resources/icon.tga.h"
+};
+
+
+typedef struct xctx
+{
+ Display* disp;
+ int scrn; /* X screen */
+ Window root;
+ Window wnd;
+ GC gc; /* Not sure if I wanna keep just one GC, but works ok on modern linux */
+ XFontStruct* xfs;
+ XrmDatabase xrdb;
+ Atom xa_delwnd;
+
+ union
+ {
+ struct
+ {
+ u32 fg;
+ u32 bg;
+ u32 col[16];
+
+ } as;
+
+ u32 arr[18];
+
+ } cols;
+
+} xctx;
+
+
+static int x11_err_handler(Display* disp, XErrorEvent* eev)
+{
+ char buf[512] = {0};
+
+ assert(disp);
+ assert(eev);
+
+ XGetErrorText(disp, eev->error_code, buf, sizeof(buf) - 1);
+
+ report(ERR, "X11: '%s' resource - %X; opcode %d(%d)\n",
+ buf, eev->resourceid, eev->request_code, eev->minor_code);
+
+ return 0;
+}
+
+
+static char ascii_lower(char c)
+{
+ if(c >= 'A' && c <= 'Z') return c + ('a' - 'A');
+ else return c;
+}
+
+/* TODO: write tests maybe? */
+/* TODO: parse #rgb */
+/* TODO: parse X11 color names */
+static long parse_color(const char* s, long len)
+{
+ long col = 0;
+
+ if(!s || len == 0)
+ {
+ report(WARN, "parse_color: invalid input");
+ goto fail;
+ }
+
+ if(s[0] == '#')
+ {
+ long i;
+
+ if(len < 7)
+ {
+ goto fail;
+ report(WARN, "parse_color: bad length");
+ }
+
+ for(i = 1; i < len; i++)
+ {
+ char c = ascii_lower(s[i]);
+ char r = 0;
+
+ if(c >= '0' && c <= '9') r = c - '0';
+ else if(c >= 'a' && c <= 'f') r = c - 'a' + 10;
+ else
+ {
+ goto fail;
+ report(WARN, "parse_color: bad hexcode");
+ }
+
+ col |= r << (20 - (i - 1)*4);
+ }
+ }
+ else goto fail;
+
+ return col;
+fail:
+ return RGB(255, 0, 255);
+}
+
+static void x11_init(int width, int height, char* title, int argc, char** argv, xctx* ctx)
+{
+ if(!ctx) report(FATAL, "x11_init: ctx == NULL");
+
+ ctx->disp = XOpenDisplay(NULL);
+ if(!ctx->disp) report(FATAL, "XOpenDisplay");
+
+ XSetErrorHandler(x11_err_handler);
+
+ ctx->scrn = DefaultScreen(ctx->disp);
+ ctx->root = RootWindow(ctx->disp, ctx->scrn);
+
+ {
+ int xver = 0;
+ int xrev = 0;
+ int xrel = 0;
+ char* xvendor = NULL;
+
+ xver = XProtocolVersion(ctx->disp);
+ xrev = XProtocolRevision(ctx->disp);
+
+ xvendor = XServerVendor(ctx->disp);
+ xrel = XVendorRelease(ctx->disp);
+
+ report(INFO, "X11: Connected to X server '%s' %d\n", xvendor, xrel);
+ report(INFO, "X11: Protocol ver %d.%d\n", xver, xrev);
+ report(INFO, "X11: Screen %d %dx%d\n", ctx->scrn,
+ DisplayWidth(ctx->disp, ctx->scrn),
+ DisplayHeight(ctx->disp, ctx->scrn));
+ }
+
+ /* TODO: make cfg_load() or something that loads the colorscheme */
+ {
+ int i = 0;
+ char* rcmgr = NULL;
+ const long defcols[16] =
+ {
+ RGB(64,64,64),
+ RGB(128,0,0),
+ RGB(0,128,0),
+ RGB(128,128,0),
+ RGB(0,0,128),
+ RGB(128,0,128),
+ RGB(0,128,128),
+ RGB(220,220,220),
+ RGB(210,210,210),
+ RGB(255,0,0),
+ RGB(0,255,0),
+ RGB(255,255,0),
+ RGB(0,0,255),
+ RGB(255,0,255),
+ RGB(0,255,255),
+ RGB(255,255,255)
+ };
+
+
+ ctx->cols.as.fg = RGB(0, 0, 0);
+ ctx->cols.as.bg = RGB(255, 255, 255);
+ for(i = 0; i < 16; i++) ctx->cols.arr[i+2] = defcols[i];
+
+ XrmInitialize();
+ rcmgr = XResourceManagerString(ctx->disp);
+ if(rcmgr && (ctx->xrdb = XrmGetStringDatabase(rcmgr)))
+ {
+ XrmValue val = {0};
+ char* type = NULL;
+
+ if(XrmGetResource(ctx->xrdb, "foreground", "Foreground", &type, &val))
+ ctx->cols.as.fg = parse_color(val.addr, val.size - 1);
+
+ if(XrmGetResource(ctx->xrdb, "background", "Background", &type, &val))
+ ctx->cols.as.bg = parse_color(val.addr, val.size - 1);
+
+ for(i = 0; i < 16; i++)
+ {
+ char name[8] = {0};
+ char class[8] = {0};
+ snprintf(name, 8, "color%d", i);
+ strcpy(class, name);
+ class[0] = 'C';
+
+ if(XrmGetResource(ctx->xrdb, name, class, &type, &val))
+ ctx->cols.arr[i+2] = parse_color(val.addr, val.size - 1);
+ }
+ }
+ else report(WARN, "X11: failed to load X Resources database\n");
+ }
+
+ ctx->wnd = XCreateSimpleWindow(ctx->disp, ctx->root,
+ 0, 0, width, height, 0,
+ BlackPixel(ctx->disp, ctx->scrn), WhitePixel(ctx->disp, ctx->scrn));
+
+ XSelectInput(ctx->disp, ctx->wnd,
+ KeyPressMask | KeyReleaseMask | KeymapStateMask
+ | ButtonPressMask | ButtonReleaseMask | PointerMotionMask
+ | FocusChangeMask | ExposureMask | StructureNotifyMask
+ );
+
+ {
+ Atom xa_wnd_type = {0};
+ Atom xa_wnd_types[3] = {0};
+ Atom xa_icon = {0};
+
+ XClassHint class = {0};
+ XSizeHints size = {0};
+
+ texture icon_tex = {0};
+ ssize icon_sz = 0;
+ long* icon = NULL;
+
+ char buf[512] = {0};
+
+
+ ctx->xa_delwnd = XInternAtom(ctx->disp, "WM_DELETE_WINDOW", false);
+ xa_wnd_type = XInternAtom(ctx->disp, "_NET_WM_WINDOW_TYPE", false);
+ xa_wnd_types[0] = XInternAtom(ctx->disp, "_NET_WM_WINDOW_TYPE_UTILITY", false);
+ xa_wnd_types[1] = XInternAtom(ctx->disp, "_NET_WM_WINDOW_TYPE_DIALOG", false);
+ xa_wnd_types[2] = XInternAtom(ctx->disp, "_NET_WM_WINDOW_TYPE_NORMAL", false);
+ xa_icon = XInternAtom(ctx->disp, "_NET_WM_ICON", false);
+
+
+ class.res_name = title;
+
+ /* FIXME: add class arg */
+ snprintf(buf, sizeof(buf) - 1, "digital.cabin.%s", title);
+ class.res_class = buf;
+
+ size.flags = PSize | PMinSize | PMaxSize;
+ size.width = size.min_width = size.max_width = width;
+ size.height = size.min_height = size.max_height = height;
+
+ Xutf8SetWMProperties(ctx->disp, ctx->wnd, title, class.res_name, argv, argc, &size, NULL, &class);
+ XSetWMProtocols(ctx->disp, ctx->wnd, &ctx->xa_delwnd, 1);
+ XChangeProperty(ctx->disp, ctx->wnd, xa_wnd_type, XA_ATOM, 32, PropModeReplace,
+ (u8*)xa_wnd_types, lengthof(xa_wnd_types));
+
+ /* FIXME: add u8[] icon arg */
+ if(tga2tex_from_mem(&icon_tex, (u8*)icon_tga))
+ {
+ icon_sz = 2 + icon_tex.width * icon_tex.height;
+ icon = malloc(sizeof(long) * icon_sz);
+ if(icon)
+ {
+ u32 x, y, y2;
+ icon[0] = icon_tex.width;
+ icon[1] = icon_tex.height;
+
+ for(y = 0, y2 = icon_tex.height - 1; y < icon_tex.height; y++, y2--)
+ for(x = 0; x < icon_tex.width; x++)
+ (icon + 2)[x + icon_tex.height * y] =
+ icon_tex.texels[x + icon_tex.height * y2].rgba;
+
+ XChangeProperty(ctx->disp, ctx->wnd, xa_icon, XA_CARDINAL, 32, PropModeReplace,
+ (u8*)icon, icon_sz);
+
+ free(icon);
+ }
+ else report(ERR, "Could not allocate X ICON temporary buffer");
+
+ free(icon_tex.texels);
+ }
+ else report(ERR, "Could not read embedded icon.tga");
+ }
+
+ ctx->gc = XCreateGC(ctx->disp, ctx->wnd, 0, NULL);
+ ctx->xfs = XQueryFont(ctx->disp, XGContextFromGC(ctx->gc)); /* TODO: better font support */
+
+ XClearWindow(ctx->disp, ctx->wnd);
+ XMapWindow(ctx->disp, ctx->wnd);
+}
+
+static void x11_cleanup(xctx* ctx)
+{
+ if(!ctx) return;
+
+ XSetCloseDownMode(ctx->disp, DestroyAll);
+
+ XFreeFontInfo(NULL, ctx->xfs, 1);
+ XFreeGC(ctx->disp, ctx->gc);
+ XDestroyWindow(ctx->disp, ctx->wnd);
+ XCloseDisplay(ctx->disp);
+}
diff --git a/utils/bin2.c b/utils/bin2.c
new file mode 100755
index 0000000..81a1ada
--- /dev/null
+++ b/utils/bin2.c
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2025 dwlr <dweller@cabin.digital>
+ *
+ * BSD 3-Clause License (BSD-3-Clause)
+ * See LICENSE for details
+ */
+
+
+#define _XOPEN_SOURCE 500
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+
+
+int main(int argc, char** argv)
+{
+ int rc = 0;
+ int ret = EXIT_SUCCESS;
+ FILE* in = stdin;
+ FILE* out = stdout;
+ const char* name = "stdin";
+ char buffer[4096] = {0};
+ size_t got = 0;
+
+ if(argc == 2)
+ {
+ name = argv[1];
+ in = fopen(name, "r");
+ if(!in)
+ {
+ perror("fopen");
+ exit(EXIT_FAILURE);
+ }
+
+ rc = snprintf(buffer, sizeof(buffer) - 1, "%s.h", name);
+ if(rc < 0)
+ {
+ perror("snprintf");
+ exit(EXIT_FAILURE);
+ }
+
+ out = fopen(buffer, "w");
+ if(!out)
+ {
+ perror("fopen");
+ fclose(in);
+ exit(EXIT_FAILURE);
+ }
+
+ }
+ else if(argc > 2) fprintf(stderr, "warning: ignoring excess paramters\n");
+
+ for(;;)
+ {
+ size_t i;
+
+ got = fread(buffer, 1, sizeof(buffer), in);
+ rc = ferror(in);
+ if(rc)
+ {
+ perror("fread");
+ ret = EXIT_FAILURE;
+ goto end;
+ }
+
+ for(i = 0; i < got; i++) fprintf(out, "'\\x%02x',", (unsigned char)buffer[i]);
+
+ rc = feof(in);
+ if(rc) break;
+ }
+
+ fprintf(out, "\n");
+
+end:
+ fclose(in);
+ fclose(out);
+ return ret;
+}