commit b414a04

uint  ·  2026-07-13 15:52:58 +0000 UTC
parent 62b1937
add libmaus dependancy
25 files changed,  +2425, -0
+15, -0
 1@@ -0,0 +1,15 @@
 2+*.o
 3+*.swp
 4+*.a
 5+*.exe
 6+*.obj
 7+*.lib
 8+
 9+compile_flags.txt
10+
11+fenster/
12+
13+examples/basic-window/basic-window
14+
15+config.mk
16+
+16, -0
 1@@ -0,0 +1,16 @@
 2+ISC License
 3+
 4+Copyright 2026 uint
 5+
 6+Permission to use, copy, modify, and/or distribute this software for any
 7+purpose with or without fee is hereby granted, provided that the above
 8+copyright notice and this permission notice appear in all copies.
 9+
10+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
11+REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
12+FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
13+INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
14+LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
15+OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
16+PERFORMANCE OF THIS SOFTWARE.
17+
+45, -0
 1@@ -0,0 +1,45 @@
 2+include config.mk
 3+
 4+CC = cc
 5+AR = ar
 6+CFLAGS = -std=c99 -Wall -Wextra -O2
 7+CPPFLAGS = ${CONF_CPPFLAGS} -Iinclude
 8+LDFLAGS = ${CONF_LDFLAGS}
 9+
10+LIB_NAME = build/${CONF_LIB_NAME}
11+LIB_SRCS = source/maus.c      \
12+           source/maus_font.c \
13+           source/utils.c     \
14+           ${CONF_SRC}
15+LIB_OBJS = build/maus.o       \
16+           build/maus_font.o  \
17+           build/utils.o      \
18+           ${CONF_OBJS}
19+
20+all: ${LIB_NAME}
21+
22+${LIB_NAME}: ${LIB_OBJS}
23+	${AR} -rcs $@ ${LIB_OBJS}
24+
25+build/maus.o: source/maus.c
26+	mkdir -p build
27+	${CC} ${CFLAGS} ${CPPFLAGS} -c source/maus.c -o $@
28+build/maus_font.o: source/maus_font.c
29+	mkdir -p build
30+	${CC} ${CFLAGS} ${CPPFLAGS} -c source/maus_font.c -o $@
31+build/utils.o: source/utils.c
32+	mkdir -p build
33+	${CC} ${CFLAGS} ${CPPFLAGS} -c source/utils.c -o $@
34+build/maus_x11.o: source/maus_x11.c
35+	mkdir -p build
36+	${CC} ${CFLAGS} ${CPPFLAGS} -c source/maus_x11.c -o $@
37+
38+clean:
39+	rm -rf build ${LIB_NAME} config.mk compile_flags.txt
40+
41+compile_flags:
42+	rm -f compile_flags.txt
43+	for f in ${CFLAGS} ${CPPFLAGS}; do echo $$f >> compile_flags.txt; done
44+
45+.PHONY: all clean compile_flags
46+
+81, -0
 1@@ -0,0 +1,81 @@
 2+![logo](assets/logo_256x112.png)  
 3+is a small, cross-platform, software windowing library
 4+
 5+### Building
 6+Unix:
 7+```sh
 8+./configure --backend=[x11|wayland|mac]
 9+make # That's it!
10+```
11+
12+Windows:
13+```
14+.\build.bat [msvc|gcc|clang]
15+```
16+
17+After building, you have a library which you link _your_ sources with. It is
18+located in `build/`.
19+
20+### Usage
21+#### libmaus
22+Here is an annoted example for a basic window program which counts the number of
23+frames that have elapsed, draw the text and displays if you've pressed a key
24+(specifically 'A').
25+
26+For the full list of functions and what they do, look at
27+[the header](include/maus.h).
28+
29+```c
30+#include "maus.h"
31+#include "maus_font.h"
32+
33+int main(void)
34+{
35+	int x=50, y=50, width=640, height=480;
36+	Maus* ctx = maus_init("example!", x, y, width, height);
37+	maus_create_window(ctx);
38+	MausFont* font = maus_font_load("assets/fonts/font1.bdf");
39+
40+	int running = true;
41+	long long frames = 0;
42+	int a_pressed = false;
43+
44+	while (running) {
45+		MausEvent ev;
46+		while (maus_event_poll(ctx, &ev)) {
47+			// to see the full list of event types, look at
48+			// the enum `MausEventType` in `maus.h`
49+			if (ev.type == MAUS_EV_CLOSE)  {
50+				running = false;
51+				continue;
52+			}
53+
54+			if (ev.type == MAUS_EV_KEY && ev.key.pressed)  {
55+				if (ctx->key_syms[MAUS_KEY_A])
56+					a_pressed = 1;
57+			}
58+		}
59+
60+		// clear framebuffer with color white
61+		// colors are in format ARGB
62+		maus_clear(ctx, (MausColor){255, 255, 255, 255});
63+
64+		if (a_pressed) {
65+			const char* msg = "how dare you press a!!";
66+			maus_draw_text(ctx, font, 10, 50, msg, (MausColor){255, 255, 0, 0});
67+		}
68+
69+		char frames_text[256];
70+		snprintf(frames_text, sizeof(frames_text), "%lld", frames++);
71+		maus_draw_text(ctx, font, 10, 20, frames_text, (MausColor){255, 255, 0, 0});
72+
73+		maus_target_fps(ctx, 512);
74+		maus_present(ctx);
75+	}
76+
77+	// free resources
78+	maus_font_free(font);
79+	maus_close(ctx);
80+}
81+```
82+
+11, -0
 1@@ -0,0 +1,11 @@
 2+Maus:
 3+[?] Cursor warping
 4+[?] Custom cursor
 5+
 6+MausFont:
 7+
 8+X11:
 9+Wayland:
10+Windows:
11+macOS:
12+
+0, -0
+0, -0
+38, -0
 1@@ -0,0 +1,38 @@
 2+@echo off
 3+
 4+if not exist build mkdir build
 5+
 6+set "COMPILER=%~1"
 7+if "%COMPILER%"=="" set "COMPILER=gcc"
 8+
 9+if /I "%COMPILER%"=="msvc" goto msvc
10+if /I "%COMPILER%"=="gcc" goto gcc
11+if /I "%COMPILER%"=="clang" goto clang
12+
13+echo Usage: build.bat [msvc^|gcc^|clang]
14+exit /b 1
15+
16+:: MSVC
17+:msvc
18+cl /diagnostics:color /nologo /W3 /O2 /Iinclude /DBACKEND_WIN /c ^
19+   source/maus.c source/maus_win.c source/maus_font.c source/utils.c
20+lib /nologo /OUT:build\libmaus_win.lib *.obj
21+move /Y *.obj build\
22+goto :eof
23+
24+:: GCC
25+:gcc
26+cc -std=c99 -Wall -Wextra -O2 -Iinclude -DBACKEND_WIN -c ^
27+   source/maus.c source/maus_win.c source/maus_font.c source/utils.c
28+move *.o build\
29+ar rcs build\libmaus_win.a build\maus.o build\maus_win.o build\maus_font.o build\utils.o
30+goto :eof
31+
32+:: GCC
33+:clang
34+clang -std=c99 -Wall -Wextra -O2 -Iinclude -DBACKEND_WIN -c ^
35+   source/maus.c source/maus_win.c source/maus_font.c source/utils.c
36+move *.o build\
37+ar rcs build\libmaus_win.a build\maus.o build\maus_win.o build\maus_font.o build\utils.o
38+goto :eof
39+
+43, -0
 1@@ -0,0 +1,43 @@
 2+#!/bin/sh
 3+
 4+for arg do
 5+	case "$arg" in
 6+		--backend=*) backend="${arg#*=}" ;;
 7+		--help)
 8+			echo "Usage: ./configure [--backend=x11|wayland|mac]"
 9+			exit 0
10+			;;
11+		*)
12+			echo "Unknown argument: $arg"
13+			exit 1
14+			;;
15+	esac
16+done
17+
18+cat > config.mk << EOF
19+# Generated by configure script
20+BACKEND = $backend
21+EOF
22+
23+case "$backend" in
24+	x11)
25+		echo "CONF_CPPFLAGS = -DBACKEND_X11" >> config.mk
26+		echo "CONF_LDFLAGS = -lX11 -lXext" >> config.mk
27+		echo "CONF_SRC = source/maus_x11.c" >> config.mk
28+		echo "CONF_OBJS = build/maus_x11.o" >> config.mk
29+		echo "CONF_LIB_NAME = libmaus_x11.a" >> config.mk
30+		;;
31+	wayland)
32+		# TODO
33+		;;
34+	mac)
35+		# TODO
36+		;;
37+	*)
38+		echo "Unsupported backend '$backend'"
39+		rm -f config.mk
40+		exit 1
41+		;;
42+esac
43+echo "Configuration done"
44+
+10, -0
 1@@ -0,0 +1,10 @@
 2+CC = cc
 3+CFLAGS = -std=c99 -Wall -Wextra -g
 4+CPPFLAGS = -DBACKEND_X11 -I../../include
 5+LIBS = -lX11 -lXext
 6+MAUS_LIB = ../../build/libmaus_x11.a
 7+
 8+SRC = main.c
 9+
10+all:
11+	${CC} ${CFLAGS} ${CPPFLAGS} ${SRC} ${MAUS_LIB} ${LIBS} -o basic-window
+27, -0
 1@@ -0,0 +1,27 @@
 2+@echo off
 3+
 4+set "COMPILER=%~1"
 5+if "%COMPILER%"=="" set "COMPILER=gcc"
 6+
 7+if /I "%COMPILER%"=="msvc" goto msvc
 8+if /I "%COMPILER%"=="gcc" goto gcc
 9+if /I "%COMPILER%"=="clang" goto clang
10+
11+echo Usage: build.bat [msvc^|gcc^|clang]
12+exit /b 1
13+
14+:msvc
15+cl /diagnostics:color /nologo /W3 /O2 /I..\..\include main.c ..\..\build\libmaus_win.lib user32.lib gdi32.lib /link /OUT:basic-window.exe
16+echo Build complete
17+goto :eof
18+
19+:gcc
20+cc -std=c99 -Wall -Wextra -g -I../../include main.c ../../build/libmaus_win.a -lgdi32 -luser32 -o basic-window.exe
21+echo Build complete
22+goto :eof
23+
24+:clang
25+clang -std=c99 -Wall -Wextra -g -I../../include main.c ../../build/libmaus_win.a -lgdi32 -luser32 -o basic-window.exe
26+echo Build complete
27+goto :eof
28+
+135, -0
  1@@ -0,0 +1,135 @@
  2+#include <stdbool.h>
  3+#include <stdint.h>
  4+#include <stdlib.h>
  5+
  6+#include "maus.h"
  7+#include "maus_font.h"
  8+
  9+int mx, my;
 10+bool cur_visible = true;
 11+bool cur_locked = false;
 12+bool mb1_pressed = false;
 13+char txtbuf[4096] = {'\0'};
 14+int current_char = 0;
 15+bool resize = false;
 16+int rszw;
 17+int rszh;
 18+
 19+void handle_ev(Maus* mw, MausEvent* ev)
 20+{
 21+	switch (ev->type) {
 22+		case MAUS_EV_CLOSE:
 23+			maus_close(mw);
 24+			exit(EXIT_SUCCESS);
 25+			break;
 26+		case MAUS_EV_KEY: {
 27+			(void)0;
 28+
 29+			bool* keys = mw->key_syms;
 30+			if (keys[MAUS_KEY_CONTROL_L] && keys[MAUS_KEY_Q]) {
 31+				maus_close(mw);
 32+				exit(EXIT_SUCCESS);
 33+			};
 34+			if (keys[MAUS_KEY_CONTROL_L] && keys[MAUS_KEY_P]) {
 35+				cur_visible ?
 36+				maus_cur_set_mode(mw, MAUS_CURSOR_STATE_HIDDEN) :
 37+				maus_cur_set_mode(mw, MAUS_CURSOR_STATE_VISIBLE);
 38+
 39+				cur_visible = !cur_visible;
 40+			}
 41+			if (keys[MAUS_KEY_CONTROL_L] && keys[MAUS_KEY_L]) {
 42+				cur_locked ?
 43+				maus_cur_set_mode(mw, MAUS_CURSOR_STATE_FREE) :
 44+				maus_cur_set_mode(mw, MAUS_CURSOR_STATE_LOCKED);
 45+
 46+				cur_locked = !cur_locked;
 47+			}
 48+
 49+			if (ev->key.pressed == false)
 50+				break;
 51+
 52+			if (!((ev->key.key >= 32 && ev->key.key <= 126) || ev->key.key == '\n'))
 53+				break;
 54+
 55+			if (current_char + 1 < 4095 && current_char >= 0)
 56+				txtbuf[current_char++] = ev->key.text;
 57+			else
 58+				maus_log(stderr, "txtbuf full");
 59+
 60+			break;
 61+		}
 62+		case MAUS_EV_MOUSE_BUTTON: {
 63+			mb1_pressed =
 64+			mw->mouse_buttons[MAUS_MOUSE_BUTTON_LEFT] ?
 65+			true : false;
 66+			break;
 67+		}
 68+		case MAUS_EV_MOUSE_MOTION: {
 69+			mx = ev->mouse.motion.x;
 70+			my = ev->mouse.motion.y;
 71+
 72+		} break;
 73+		case MAUS_EV_RESIZE: {
 74+			resize = true;
 75+			rszw = ev->resize.width;
 76+			rszh = ev->resize.height;
 77+		} break;
 78+		case MAUS_EV_NONE:
 79+			break;
 80+	}
 81+}
 82+
 83+int main(void)
 84+{
 85+	Maus* mw = maus_init("basic window", 0, 0, 800, 600);
 86+	if (!mw)
 87+		return EXIT_FAILURE;
 88+	maus_create_window(mw);
 89+	MausFont* font = maus_font_load("assets/fonts/font1.bdf");
 90+	if (!font)
 91+		return EXIT_FAILURE;
 92+
 93+	MausEvent ev;
 94+	MausColor red = { 255, 255, 0, 0 };
 95+	/* unsigned long long ticks = 0; */
 96+	for (;;) {
 97+		while (maus_event_poll(mw, &ev))
 98+			(void) handle_ev(mw, &ev);
 99+
100+		/* only resize window once per fram */
101+		if (resize) {
102+			maus_resize(mw, rszw, rszh);
103+			resize = false;
104+		}
105+
106+		maus_clear(mw, MAUS_COL_RGBA(255, 255, 255, 255));
107+
108+		uint32_t red_unpacked = MAUS_UNPACK_COL(red);
109+		if (mx >= 0 && my >= 0 &&
110+		    mx < (int32_t)mw->width &&
111+		    my < (int32_t)mw->height && mb1_pressed) {
112+			MAUS_PIXEL_AT(mw, mx, my) = red_unpacked;
113+		}
114+
115+		for (uint32_t y = 0; y < mw->height/2; y++) {
116+			for (uint32_t x = 0; x < mw->width/2; x++) {
117+				MAUS_PIXEL_AT(mw, x, y) = red_unpacked;
118+			}
119+		}
120+		red.b++;
121+
122+		/* maus_draw_text(mw, font, 700, 50, "maus font\nloading example", red); */
123+
124+		/* char buf[512] = {0}; */
125+		/* snprintf(buf, 512, "maus' basic-window has been running for %lld ticks!", ticks); */
126+		/* maus_clipboard_set_text(mw, buf); */
127+
128+		maus_draw_text(mw, font, 700, 50, txtbuf, red);
129+
130+		maus_target_fps(mw, 60);
131+		maus_present(mw);
132+	}
133+	maus_font_free(font);
134+	maus_close(mw);
135+}
136+
+180, -0
  1@@ -0,0 +1,180 @@
  2+#ifndef MAUSWIN_H
  3+#define MAUSWIN_H
  4+
  5+#include <stdbool.h>
  6+#include <stdio.h>
  7+#include <stdint.h>
  8+
  9+#include "maus_input.h"
 10+
 11+/* auto selection */
 12+#if (!defined(BACKEND_WIN) && !defined(BACKEND_MAC) && \
 13+    !defined(BACKEND_X11) && !defined(BACKEND_WAY)) || \
 14+    defined(BACKEND_AUTO)
 15+
 16+#ifdef BACKEND_AUTO
 17+#define MAUS_WARN_BACKEND_AUTO_SEL 0
 18+#else
 19+#define MAUS_WARN_BACKEND_AUTO_SEL 1
 20+#endif /* BACKEND_AUTO */
 21+
 22+	#if defined(_WIN32)
 23+	#define BACKEND_WIN
 24+
 25+	#elif defined(__APPLE__)
 26+	#define BACKEND_MAC
 27+
 28+	#else /* default to X */
 29+	#define BACKEND_X11
 30+
 31+	#endif
 32+#else
 33+#define MAUS_WARN_BACKEND_AUTO_SEL 0
 34+#endif /* auto selection */
 35+
 36+#if defined(BACKEND_X11)
 37+	#include "maus_x11.h"
 38+
 39+#elif defined(BACKEND_WAY)
 40+	/* ... */
 41+
 42+#elif defined(BACKEND_WIN)
 43+	#include "maus_win.h"
 44+
 45+#elif defined(BACKEND_MAC)
 46+	/* ... */
 47+#endif
 48+
 49+#define MAUS_KEYCODE_LAST (256)
 50+#define MAUS_COL_ARGB(a, r, g, b) (MausColor){a, r, g, b}
 51+#define MAUS_COL_RGBA(r, g, b, a) (MausColor){a, r, g, b}
 52+#define MAUS_UNPACK_COL(c) \
 53+	(((uint32_t)(c).a << 24) | \
 54+	 ((uint32_t)(c).r << 16) | \
 55+	 ((uint32_t)(c).g <<  8) | \
 56+	 ((uint32_t)(c).b))
 57+/* ... */
 58+#define MAUS_PIXEL_AT(mw, x, y) ((mw)->bfb[(y) * (mw)->stride + (x)])
 59+
 60+typedef enum {
 61+	MAUS_EV_NONE,
 62+	MAUS_EV_CLOSE,
 63+	MAUS_EV_KEY,
 64+	MAUS_EV_MOUSE_BUTTON,
 65+	MAUS_EV_MOUSE_MOTION,
 66+	MAUS_EV_RESIZE,
 67+} MausEventType;
 68+
 69+typedef struct {
 70+	uint8_t        a;
 71+	uint8_t        r;
 72+	uint8_t        g;
 73+	uint8_t        b;
 74+} MausColor;
 75+
 76+typedef struct {
 77+	MausEventType type;
 78+
 79+	union {
 80+		struct {
 81+			uint32_t  code; /* raw, backend keycode */
 82+			MausKey   key;  /* logical, mapped key */
 83+			char      text; /* translated text from key */
 84+			bool      pressed;
 85+		} key;
 86+
 87+		struct {
 88+			struct {
 89+				MausMouseButton button;
 90+				bool            pressed;
 91+			} button;
 92+
 93+			struct {
 94+				int32_t  x;
 95+				int32_t  y;
 96+			} motion;
 97+		} mouse;
 98+
 99+		struct {
100+			uint32_t width;
101+			uint32_t height;
102+		} resize;
103+	};
104+} MausEvent;
105+
106+typedef struct {
107+	MausBackend    backend;
108+	uint64_t       frame_time_last;
109+	char*          clipboard;
110+
111+	uint32_t*      fb;  /* front frame buffer */
112+	uint32_t*      bfb; /* back frame buffer */
113+	uint32_t       stride;
114+
115+	const char*    title;
116+	uint32_t       width;
117+	uint32_t       height;
118+	int32_t        x;
119+	int32_t        y;
120+
121+	bool           key_codes[MAUS_KEYCODE_LAST]; /* physical keys */
122+	bool           key_syms[MAUS_KEY_LAST];      /* logical keys */
123+
124+	MausCursor     cursor;
125+	bool           mouse_buttons[MAUS_MOUSE_BUTTON_LAST];
126+} Maus;
127+
128+/* clear screen with color: `col` */
129+void maus_clear(Maus* mw, MausColor col);
130+
131+/* set text to system clipboard */
132+void maus_clipboard_set_text(Maus* mw, const char* text);
133+
134+/* get text from system clipboard */
135+char* maus_clipboard_get_text(Maus* mw);
136+
137+/* close a Maus. returns false on fail */
138+void maus_close(Maus* mw);
139+
140+/* close a window without the whole Maus. returns false on fail */
141+bool maus_close_window(Maus* mw);
142+
143+/* create window from Maus. returns false on fail */
144+bool maus_create_window(Maus* mw);
145+
146+/* log message to output `fd` and die */
147+void maus_die(const char* fmt, ...);
148+
149+/* get time since arbitrary start point (ns) */
150+uint64_t maus_get_time_ns(void);
151+
152+/* initialise and fills the Maus. returns NULL on fail */
153+Maus* maus_init(const char* title, int x, int y, int width, int height);
154+
155+/* log message to output `fd` */
156+void maus_log(FILE* fd, const char* fmt, ...);
157+
158+/* poll for events then fill `ev` with retrieved events.
159+   returns true if event polled, else false.
160+   note: can burn cpu cycles */
161+bool maus_event_poll(Maus* mw, MausEvent* ev);
162+
163+/* poll for events then fill `ev` with retrieved events. returns true if event
164+   polled, else false. note, thread goes to sleep until an event arrives */
165+void maus_event_wait(Maus* mw, MausEvent* ev);
166+
167+/* present the pixelbuffer to the screen */
168+void maus_present(Maus* mw);
169+
170+/* resize window to specified width and height. returns true on resize success,
171+   else false */
172+bool maus_resize(Maus* mw, uint32_t width, uint32_t height);
173+
174+/* cap framerate to targetted fps */
175+void maus_target_fps(Maus* mw, uint32_t fps);
176+
177+/* change behavior of mouse based on state passed */
178+void maus_cur_set_mode(Maus* mw, MausCursorState state);
179+
180+#endif /* MAUSWIN_H */
181+
+40, -0
 1@@ -0,0 +1,40 @@
 2+#ifndef MAUS_FONT_H
 3+#define MAUS_FONT_H
 4+
 5+#include <stdint.h>
 6+#include <stdbool.h>
 7+
 8+#include "maus.h"
 9+
10+#define MAUS_BDF_GLYPHS_MAX 256
11+
12+typedef struct {
13+    uint8_t*    bmp;
14+    uint16_t    w;
15+    uint16_t    h;
16+    int16_t     xoff;
17+    int16_t     yoff;
18+    uint16_t    adv;
19+    bool        valid;
20+} MausGlyph;
21+
22+typedef struct {
23+    MausGlyph glyphs[MAUS_BDF_GLYPHS_MAX];
24+    int32_t     asc;
25+    int32_t     dsc;
26+    int32_t     cellh;
27+    int32_t     cellw;
28+} MausFont;
29+
30+/* Loops through text passing parameters to glyph drawer */
31+void maus_draw_text(Maus* mw, MausFont* font, int32_t x, int32_t y,
32+                    const char* text, MausColor col);
33+
34+/* load a bdf font into MausFont. returns NULL on fail */
35+MausFont* maus_font_load(const char* path);
36+
37+/* release all allocations made with a MausFont */
38+void maus_font_free(MausFont* font);
39+
40+#endif /* MAUS_FONT_H */
41+
+88, -0
 1@@ -0,0 +1,88 @@
 2+#ifndef MAUS_INPUT_H
 3+#define MAUS_INPUT_H
 4+
 5+#include <stdint.h>
 6+
 7+typedef enum {
 8+	MAUS_KEY_NONE = 0,
 9+
10+	MAUS_KEY_TAB = '\t',     MAUS_KEY_ENTER = '\n',   MAUS_KEY_SPACE = ' ',
11+	MAUS_KEY_APOSTROPHE = '\'', MAUS_KEY_COMMA = ',',  MAUS_KEY_MINUS = '-',
12+	MAUS_KEY_PERIOD = '.',   MAUS_KEY_SLASH = '/',    MAUS_KEY_SEMICOLON = ';',
13+	MAUS_KEY_EQUAL = '=',    MAUS_KEY_LEFT_BRACKET = '[', MAUS_KEY_BACKSLASH = '\\',
14+	MAUS_KEY_RIGHT_BRACKET = ']', MAUS_KEY_GRAVE = '`',
15+
16+	/* numbers */
17+	MAUS_KEY_0 = '0', MAUS_KEY_1, MAUS_KEY_2, MAUS_KEY_3, MAUS_KEY_4,
18+	MAUS_KEY_5,       MAUS_KEY_6, MAUS_KEY_7, MAUS_KEY_8, MAUS_KEY_9,
19+
20+	/* uppercase */
21+	MAUS_KEY_A_UP = 'A', MAUS_KEY_B_UP, MAUS_KEY_C_UP, MAUS_KEY_D_UP, MAUS_KEY_E_UP,
22+	MAUS_KEY_F_UP,       MAUS_KEY_G_UP, MAUS_KEY_H_UP, MAUS_KEY_I_UP, MAUS_KEY_J_UP,
23+	MAUS_KEY_K_UP,       MAUS_KEY_L_UP, MAUS_KEY_M_UP, MAUS_KEY_N_UP, MAUS_KEY_O_UP,
24+	MAUS_KEY_P_UP,       MAUS_KEY_Q_UP, MAUS_KEY_R_UP, MAUS_KEY_S_UP, MAUS_KEY_T_UP,
25+	MAUS_KEY_U_UP,       MAUS_KEY_V_UP, MAUS_KEY_W_UP, MAUS_KEY_X_UP, MAUS_KEY_Y_UP,
26+	MAUS_KEY_Z_UP,
27+
28+	/* lowercase */
29+	MAUS_KEY_A = 'a', MAUS_KEY_B, MAUS_KEY_C, MAUS_KEY_D, MAUS_KEY_E,
30+	MAUS_KEY_F,       MAUS_KEY_G, MAUS_KEY_H, MAUS_KEY_I, MAUS_KEY_J,
31+	MAUS_KEY_K,       MAUS_KEY_L, MAUS_KEY_M, MAUS_KEY_N, MAUS_KEY_O,
32+	MAUS_KEY_P,       MAUS_KEY_Q, MAUS_KEY_R, MAUS_KEY_S, MAUS_KEY_T,
33+	MAUS_KEY_U,       MAUS_KEY_V, MAUS_KEY_W, MAUS_KEY_X, MAUS_KEY_Y,
34+	MAUS_KEY_Z,
35+
36+	MAUS_KEY_BACKSPACE = 128, MAUS_KEY_ESCAPE,      MAUS_KEY_DELETE,
37+
38+	/* navigation */
39+	MAUS_KEY_LEFT,          MAUS_KEY_RIGHT,     MAUS_KEY_UP,        MAUS_KEY_DOWN,
40+	MAUS_KEY_HOME,          MAUS_KEY_END,       MAUS_KEY_PAGE_UP,   MAUS_KEY_PAGE_DOWN,
41+	MAUS_KEY_INSERT,
42+
43+	/* modifiers */
44+	MAUS_KEY_SHIFT_L,       MAUS_KEY_SHIFT_R,   MAUS_KEY_CONTROL_L, MAUS_KEY_CONTROL_R,
45+	MAUS_KEY_ALT_L,         MAUS_KEY_ALT_R,     MAUS_KEY_SUPER_L,   MAUS_KEY_SUPER_R,
46+	MAUS_KEY_CAPS_LOCK,     MAUS_KEY_NUM_LOCK,  MAUS_KEY_SCROLL_LOCK,
47+
48+	/* function */
49+	MAUS_KEY_F1,            MAUS_KEY_F2,        MAUS_KEY_F3,        MAUS_KEY_F4,
50+	MAUS_KEY_F5,            MAUS_KEY_F6,        MAUS_KEY_F7,        MAUS_KEY_F8,
51+	MAUS_KEY_F9,            MAUS_KEY_F10,       MAUS_KEY_F11,       MAUS_KEY_F12,
52+
53+	MAUS_KEY_PRINT_SCREEN,  MAUS_KEY_PAUSE,     MAUS_KEY_MENU,
54+
55+	/* keypad */
56+	MAUS_KEY_KP_0,          MAUS_KEY_KP_1,      MAUS_KEY_KP_2,      MAUS_KEY_KP_3,
57+	MAUS_KEY_KP_4,          MAUS_KEY_KP_5,      MAUS_KEY_KP_6,      MAUS_KEY_KP_7,
58+	MAUS_KEY_KP_8,          MAUS_KEY_KP_9,
59+
60+	MAUS_KEY_KP_DECIMAL,    MAUS_KEY_KP_DIVIDE, MAUS_KEY_KP_MULTIPLY,
61+	MAUS_KEY_KP_SUBTRACT,   MAUS_KEY_KP_ADD,    MAUS_KEY_KP_ENTER,  MAUS_KEY_KP_EQUAL,
62+
63+	MAUS_KEY_LAST,
64+} MausKey;
65+
66+typedef struct {
67+	int32_t        x;
68+	int32_t        y;
69+} MausCursor;
70+
71+typedef enum {
72+	MAUS_CURSOR_STATE_VISIBLE,
73+	MAUS_CURSOR_STATE_HIDDEN,
74+	MAUS_CURSOR_STATE_LOCKED,
75+	MAUS_CURSOR_STATE_FREE,
76+} MausCursorState;
77+
78+typedef enum {
79+	MAUS_MOUSE_BUTTON_NONE = 0,
80+	MAUS_MOUSE_BUTTON_LEFT,
81+	MAUS_MOUSE_BUTTON_MIDDLE,
82+	MAUS_MOUSE_BUTTON_RIGHT,
83+	MAUS_MOUSE_BUTTON_SCROLL_UP,
84+	MAUS_MOUSE_BUTTON_SCROLL_DOWN,
85+	MAUS_MOUSE_BUTTON_LAST,
86+} MausMouseButton;
87+
88+#endif /* MAUS_KEYS_H */
89+
+21, -0
 1@@ -0,0 +1,21 @@
 2+#ifndef MAUS_WIN_H
 3+#define MAUS_WIN_H
 4+
 5+#include <stdbool.h>
 6+#include <stdint.h>
 7+
 8+#include <windows.h>
 9+
10+typedef struct {
11+	HWND           hwnd;
12+	HINSTANCE      hInst;
13+	HDC            hdc;
14+	HBITMAP        hbm;
15+	HDC            memdc;
16+	bool           resized;
17+	uint32_t       rw;
18+	uint32_t       rh;
19+} MausBackend;
20+
21+#endif /* MAUS_WIN_H */
22+
+27, -0
 1@@ -0,0 +1,27 @@
 2+#ifndef MAUS_X11_H
 3+#define MAUS_X11_H
 4+
 5+#include <stdbool.h>
 6+
 7+#include <X11/Xlib.h>
 8+#include <X11/extensions/XShm.h>
 9+
10+typedef enum {
11+	MAUS_ATOM_WM_DELETE_WINDOW,
12+	MAUS_ATOM_LAST,
13+} MausX11Atoms;
14+
15+typedef struct {
16+	Display*       display;
17+	Window         root;
18+	Window         win;
19+	Atom           atoms[MAUS_ATOM_LAST];
20+	GC             gc;
21+
22+	XImage*        image;
23+	XShmSegmentInfo shm;
24+	bool           shmat;
25+} MausBackend;
26+
27+#endif /* MAUS_X11_H */
28+
+11, -0
 1@@ -0,0 +1,11 @@
 2+#ifndef UTIL_H
 3+#define UTIL_H
 4+
 5+/* duplicate a string. returns pointer to address of
 6+   newly allocated string buffer. if NULL, allocation
 7+   failed or src is NULL
 8+   note: you must free this newly allocated string */
 9+char* strdup(const char* src);
10+
11+#endif /* UTIL_H */
12+
+86, -0
 1@@ -0,0 +1,86 @@
 2+#if !defined(BACKEND_WIN)
 3+#define _POSIX_C_SOURCE 199309L
 4+#else
 5+#include <windows.h>
 6+#endif
 7+
 8+#include <stdarg.h>
 9+#include <stdio.h>
10+#include <stdlib.h>
11+#include <time.h>
12+
13+#include "maus.h"
14+
15+static void vlog(FILE* fd, const char* fmt, va_list ap);
16+
17+static void vlog(FILE* fd, const char* fmt, va_list ap)
18+{
19+	fprintf(fd, "maus: ");
20+	vfprintf(fd, fmt, ap);
21+}
22+
23+void maus_die(const char* fmt, ...)
24+{
25+	va_list ap;
26+	va_start(ap, fmt);
27+	vlog(stderr, fmt, ap);
28+	va_end(ap);
29+
30+	exit(EXIT_FAILURE);
31+}
32+
33+uint64_t maus_get_time_ns(void)
34+{
35+#if !defined(_WIN32)
36+	struct timespec ts;
37+	clock_gettime(CLOCK_MONOTONIC, &ts);
38+	return ts.tv_sec * 1000000000ULL + ts.tv_nsec;
39+#else
40+	static LARGE_INTEGER frequency = {0};
41+	if (frequency.QuadPart == 0)
42+		QueryPerformanceFrequency(&frequency);
43+
44+	LARGE_INTEGER counter;
45+	QueryPerformanceCounter(&counter);
46+
47+	uint64_t sec = counter.QuadPart / frequency.QuadPart;
48+	uint64_t rem = counter.QuadPart % frequency.QuadPart;
49+
50+	return (sec * 1000000000ULL) + ((rem * 1000000000ULL) / frequency.QuadPart);
51+#endif
52+}
53+
54+void maus_log(FILE* fd, const char* fmt, ...)
55+{
56+	va_list ap;
57+	va_start(ap, fmt);
58+	vlog(fd, fmt, ap);
59+	fputc('\n', fd);
60+	va_end(ap);
61+}
62+
63+void maus_target_fps(Maus* mw, uint32_t fps)
64+{
65+	if (fps == 0)
66+		return;
67+
68+	uint64_t ns_frame = 1000000000ULL / fps; /* max ns a frame can take */
69+	uint64_t cur_time = maus_get_time_ns();
70+	uint64_t elapsed = cur_time - mw->frame_time_last;
71+
72+	/* if early finish, sleep for the remaining time balance */
73+	if (elapsed < ns_frame) {
74+		uint64_t sleep = ns_frame - elapsed;
75+	#if !defined(_WIN32)
76+		struct timespec ts;
77+		ts.tv_sec = sleep / 1000000000ULL;
78+		ts.tv_nsec = sleep % 1000000000ULL;
79+		nanosleep(&ts, NULL);
80+	#else /* unix */
81+		Sleep((DWORD)((sleep + 999999ULL) / 1000000ULL));
82+	#endif /* platform */
83+	}
84+
85+	mw->frame_time_last = maus_get_time_ns();
86+}
87+
+241, -0
  1@@ -0,0 +1,241 @@
  2+#include <stdio.h>
  3+#include <stdlib.h>
  4+#include <string.h>
  5+
  6+#include "maus.h"
  7+#include "maus_font.h"
  8+
  9+void maus_draw_text(Maus* mw, MausFont* font, int32_t x, int32_t y,
 10+                    const char* text, MausColor col)
 11+{
 12+	if (!mw || !mw->bfb || !font || !text)
 13+		return;
 14+
 15+	uint32_t col_up = MAUS_UNPACK_COL(col);
 16+	int32_t cx = x; /* current x */
 17+	int32_t cy = y; /* current y */
 18+
 19+	for (size_t i = 0; text[i] != '\0'; i++) {
 20+		char c = text[i];
 21+
 22+		if (c == '\n') {
 23+			cx = x;
 24+			cy += font->cellh;
 25+			continue;
 26+		}
 27+
 28+		MausGlyph* g = &font->glyphs[(uint8_t)c];
 29+		if (!g->valid) {
 30+			/* still advance cx if character invalid */
 31+			cx += (font->cellw > 0 ? font->cellw : 8);
 32+			continue;
 33+		}
 34+
 35+		/* origins to start drawing glyph from */
 36+		int32_t gox = cx + g->xoff;
 37+		int32_t goy = cy + font->asc - g->h - g->yoff;
 38+
 39+		/* starting coordinates */
 40+		int sgx = (-gox > 0) ? -gox : 0;
 41+		int sgy = (-goy > 0) ? -goy : 0;
 42+
 43+		/* ending coordinates. if glyph runs over right/bottom, clamp it */
 44+		int end_gx = (mw->width - gox < g->w) ? (mw->width - gox) : g->w;
 45+		int end_gy = (mw->height - goy < g->h) ?(mw->height - goy) : g->h;
 46+
 47+		/* cull glyphs which start off screen */
 48+		if (sgx >= end_gx || sgy >= end_gy) {
 49+			cx += g->adv;
 50+			continue;
 51+		}
 52+
 53+		/* "blit" font to framebuffer */
 54+		for (int gy = sgy; gy < end_gy; gy++) {
 55+			int dsty = goy + gy;
 56+
 57+			/* calculate row pointer once per scanline */
 58+			uint32_t* row = mw->bfb + (dsty * mw->stride);
 59+			int offset = gy * g->w;
 60+
 61+			for (int gx = sgx; gx < end_gx; gx++) {
 62+				if (g->bmp[offset + gx] > 0) {
 63+					int dstx = gox + gx;
 64+					row[dstx] = col_up;
 65+				}
 66+			}
 67+		}
 68+
 69+		cx += g->adv;
 70+	}
 71+}
 72+
 73+MausFont* maus_font_load(const char* path)
 74+{
 75+	FILE* fp = fopen(path, "r");
 76+	if (!fp) {
 77+		maus_log(stderr, "failed to load font \"%s\"", path);
 78+		return NULL;
 79+	}
 80+
 81+	MausFont* font = calloc(1, sizeof(MausFont));
 82+	if (!font) {
 83+		maus_log(stderr, "failed to calloc font");
 84+		fclose(fp);
 85+		return NULL;
 86+	}
 87+
 88+	char line[1024];
 89+	int asc = -1;
 90+	int dsc = -1;
 91+	int encoding = -1;
 92+	int advance = 0;
 93+	int bw = 0; /* bmp width */
 94+	int bh = 0; /* bmp height */
 95+	int xoff = 0;
 96+	int yoff = 0;
 97+	bool seen_any_glyph = 0;
 98+
 99+	while (fgets(line, sizeof(line), fp)) {
100+		if (sscanf(line, "FONT_ASCENT %d", &asc) == 1)
101+			continue;
102+		if (sscanf(line, "FONT_DESCENT %d", &dsc) == 1)
103+			continue;
104+
105+		/* ignore everything until glyphs start */
106+		if (strncmp(line, "STARTCHAR", 9) != 0)
107+			continue;
108+
109+		/* reset vaules for new glyph */
110+		encoding = -1;
111+		advance = 0;
112+		bw = 0;
113+		bh = 0;
114+		xoff = 0;
115+		yoff = 0;
116+
117+		while (fgets(line, sizeof(line), fp)) {
118+			if (sscanf(line, "ENCODING %d", &encoding) == 1)
119+				continue;
120+			if (sscanf(line, "DWIDTH %d", &advance) == 1)
121+				continue;
122+			if (sscanf(line, "BBX %d %d %d %d", &bw, &bh, &xoff, &yoff) == 4)
123+				continue;
124+
125+			if (strncmp(line, "BITMAP", 6) == 0) {
126+				int rowbits;
127+
128+				/* invalid character code
129+				   TODO: just skip it */
130+				if (encoding < 0 || encoding >= MAUS_BDF_GLYPHS_MAX)
131+					break;
132+
133+				/* empty/invalid size */
134+				if (bw <= 0 || bh <= 0)
135+					break;
136+
137+				MausGlyph* g = &font->glyphs[encoding];
138+				free(g->bmp); /* prevent leak on hot reload */
139+				memset(g, 0, sizeof(*g));
140+
141+				g->w = bw;
142+				g->h = bh;
143+				g->xoff = xoff;
144+				g->yoff = yoff;
145+				g->adv = advance;
146+
147+				g->bmp = calloc((size_t)bw * bh, 1);
148+				if (!g->bmp) {
149+					fclose(fp);
150+					maus_font_free(font);
151+					return NULL;
152+				}
153+
154+				/* each bmp row in BDF is padded as a multiple
155+				   of 8 bits. this rounds width up to the next
156+				   multiple of 8 */
157+				rowbits = (bw + 7) & ~7;
158+
159+				for (int y = 0; y < bh; y++) {
160+					uint64_t bits;
161+
162+					/* missing row */
163+					if (!fgets(line, sizeof(line), fp)) {
164+						fclose(fp);
165+						maus_font_free(font);
166+						return NULL;
167+					}
168+
169+					bits = strtoull(line, NULL, 16); /* hex to bits */
170+					for (int x = 0; x < bw; x++) {
171+						int bit = rowbits - 1 - x;
172+
173+						if ((bits >> bit) & 1)
174+							g->bmp[y * bw + x] = 255; /* solid */
175+					}
176+				}
177+
178+				g->valid = true;
179+				if (advance > font->cellw)
180+					font->cellw = advance;
181+				seen_any_glyph = true;
182+
183+				continue;
184+			}
185+
186+			if (strncmp(line, "ENDCHAR", 7) == 0)
187+				break;
188+		}
189+	}
190+
191+	fclose(fp);
192+
193+	if (!seen_any_glyph) {
194+		free(font);
195+		return NULL;
196+	}
197+
198+	/* estimate asc/dsc if they werent provided */
199+	if (asc < 0 || dsc < 0) {
200+		asc = 0;
201+		dsc = 0;
202+
203+		for (int i = 0; i < MAUS_BDF_GLYPHS_MAX; i++) {
204+			MausGlyph* g = &font->glyphs[i];
205+
206+			/* unloaded glpyh */
207+			if (!g->valid)
208+				continue;
209+
210+			/* highest point above baseline */
211+			if (g->h + g->yoff > asc)
212+				asc = g->h + g->yoff;
213+
214+			/* lowest point below baseline */
215+			if (-g->yoff > dsc)
216+				dsc = -g->yoff;
217+		}
218+	}
219+
220+	font->asc = asc;
221+	font->dsc = -dsc;
222+	font->cellh = asc + dsc;
223+
224+	if (font->cellw <= 0 || font->cellh <= 0) {
225+		maus_font_free(font);
226+		return NULL;
227+	}
228+
229+	return font;
230+}
231+
232+void maus_font_free(MausFont* font)
233+{
234+	if (!font)
235+		return;
236+
237+	for (int i = 0; i < MAUS_BDF_GLYPHS_MAX; i++)
238+		free(font->glyphs[i].bmp);
239+
240+	free(font);
241+}
242+
+558, -0
  1@@ -0,0 +1,558 @@
  2+#include <windows.h>
  3+#include <windowsx.h>
  4+
  5+#include "maus.h"
  6+#include "maus_input.h"
  7+#include "maus_win.h"
  8+
  9+typedef struct {
 10+	UINT    vk;
 11+	MausKey maus;
 12+} KeyMapEntry;
 13+
 14+static bool fb_create(Maus* mw);
 15+static void fb_destroy(Maus* mw);
 16+static bool handle_event(const MSG* msg, MausEvent* ev, Maus* mw);
 17+static MausKey vk_to_mauskey(UINT vk);
 18+static UINT resolve_lr(UINT vk, LPARAM lparam);
 19+static char translate_text(UINT vk, UINT scan);
 20+static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp);
 21+
 22+static const KeyMapEntry keymap[] = {
 23+	{ VK_BACK,     MAUS_KEY_BACKSPACE }, { VK_TAB,      MAUS_KEY_TAB },
 24+	{ VK_RETURN,   MAUS_KEY_ENTER },     { VK_ESCAPE,   MAUS_KEY_ESCAPE },
 25+	{ VK_SPACE,    MAUS_KEY_SPACE },     { VK_DELETE,   MAUS_KEY_DELETE },
 26+	{ VK_LEFT,     MAUS_KEY_LEFT },      { VK_RIGHT,    MAUS_KEY_RIGHT },
 27+	{ VK_UP,       MAUS_KEY_UP },        { VK_DOWN,     MAUS_KEY_DOWN },
 28+	{ VK_HOME,     MAUS_KEY_HOME },      { VK_END,      MAUS_KEY_END },
 29+	{ VK_PRIOR,    MAUS_KEY_PAGE_UP },   { VK_NEXT,     MAUS_KEY_PAGE_DOWN },
 30+	{ VK_INSERT,   MAUS_KEY_INSERT },    { VK_CAPITAL,  MAUS_KEY_CAPS_LOCK },
 31+	{ VK_NUMLOCK,  MAUS_KEY_NUM_LOCK },  { VK_SCROLL,   MAUS_KEY_SCROLL_LOCK },
 32+	{ VK_PAUSE,    MAUS_KEY_PAUSE },     { VK_SNAPSHOT, MAUS_KEY_PRINT_SCREEN },
 33+	{ VK_APPS,     MAUS_KEY_MENU },      { VK_LSHIFT,   MAUS_KEY_SHIFT_L },
 34+	{ VK_RSHIFT,   MAUS_KEY_SHIFT_R },   { VK_LCONTROL, MAUS_KEY_CONTROL_L },
 35+	{ VK_RCONTROL, MAUS_KEY_CONTROL_R }, { VK_LMENU,    MAUS_KEY_ALT_L },
 36+	{ VK_RMENU,    MAUS_KEY_ALT_R },     { VK_LWIN,     MAUS_KEY_SUPER_L },
 37+	{ VK_RWIN,     MAUS_KEY_SUPER_R },   { VK_ADD,      MAUS_KEY_KP_ADD },
 38+	{ VK_SUBTRACT, MAUS_KEY_KP_SUBTRACT },{ VK_MULTIPLY, MAUS_KEY_KP_MULTIPLY },
 39+	{ VK_DIVIDE,   MAUS_KEY_KP_DIVIDE }, { VK_DECIMAL,  MAUS_KEY_KP_DECIMAL },
 40+};
 41+
 42+static bool fb_create(Maus* mw)
 43+{
 44+	MausBackend* be = &mw->backend;
 45+	be->hbm = NULL;
 46+	mw->fb = NULL;
 47+	mw->stride = 0;
 48+
 49+	BITMAPINFO bmi = {0};
 50+	bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
 51+	bmi.bmiHeader.biWidth = mw->width;
 52+	bmi.bmiHeader.biHeight = -mw->height;
 53+	bmi.bmiHeader.biPlanes = 1;
 54+	bmi.bmiHeader.biBitCount = 32;
 55+	bmi.bmiHeader.biCompression = BI_RGB;
 56+
 57+	HDC dc = GetDC(NULL);
 58+	be->memdc = CreateCompatibleDC(dc);
 59+	be->hbm = CreateDIBSection(dc, &bmi, DIB_RGB_COLORS, (void**)&mw->fb, NULL, 0);
 60+	ReleaseDC(NULL, dc);
 61+
 62+	if (!be->hbm || !mw->fb) {
 63+		if (be->memdc)
 64+			DeleteDC(be->memdc);
 65+		maus_log(stderr, "CreateDIBSection failed");
 66+		return false;
 67+	}
 68+
 69+	SelectObject(be->memdc, be->hbm);
 70+
 71+	mw->stride = mw->width;
 72+	return true;
 73+}
 74+
 75+static void fb_destroy(Maus* mw)
 76+{
 77+	MausBackend* be = &mw->backend;
 78+
 79+	if (be->memdc) {
 80+		DeleteDC(be->memdc);
 81+		be->memdc = NULL;
 82+	}
 83+	if (be->hbm) {
 84+		DeleteObject(be->hbm);
 85+		be->hbm = NULL;
 86+	}
 87+
 88+	mw->fb = NULL;
 89+	mw->stride = 0;
 90+}
 91+
 92+static bool handle_event(const MSG* msg, MausEvent* ev, Maus* mw)
 93+{
 94+	UINT scan;
 95+	UINT vk;
 96+	int delta;
 97+	MausMouseButton mbtype;
 98+
 99+	switch (msg->message) {
100+		case WM_QUIT:
101+			ev->type = MAUS_EV_CLOSE;
102+			return true;
103+
104+		case WM_MOUSEMOVE:
105+			ev->type = MAUS_EV_MOUSE_MOTION;
106+			ev->mouse.motion.x = GET_X_LPARAM(msg->lParam);
107+			ev->mouse.motion.y = GET_Y_LPARAM(msg->lParam);
108+			return true;
109+
110+		case WM_LBUTTONDOWN:
111+			ev->type = MAUS_EV_MOUSE_BUTTON;
112+			ev->mouse.button.button = MAUS_MOUSE_BUTTON_LEFT;
113+			ev->mouse.button.pressed = true;
114+			mw->mouse_buttons[MAUS_MOUSE_BUTTON_LEFT] = true;
115+			return true;
116+		case WM_LBUTTONUP:
117+			ev->type = MAUS_EV_MOUSE_BUTTON;
118+			ev->mouse.button.button = MAUS_MOUSE_BUTTON_LEFT;
119+			ev->mouse.button.pressed = false;
120+			mw->mouse_buttons[MAUS_MOUSE_BUTTON_LEFT] = false;
121+			return true;
122+
123+		case WM_RBUTTONDOWN:
124+			ev->type = MAUS_EV_MOUSE_BUTTON;
125+			ev->mouse.button.button = MAUS_MOUSE_BUTTON_RIGHT;
126+			ev->mouse.button.pressed = true;
127+			mw->mouse_buttons[MAUS_MOUSE_BUTTON_RIGHT] = true;
128+			return true;
129+		case WM_RBUTTONUP:
130+			ev->type = MAUS_EV_MOUSE_BUTTON;
131+			ev->mouse.button.button = MAUS_MOUSE_BUTTON_RIGHT;
132+			ev->mouse.button.pressed = false;
133+			mw->mouse_buttons[MAUS_MOUSE_BUTTON_RIGHT] = false;
134+			return true;
135+
136+		case WM_MBUTTONDOWN:
137+			ev->type = MAUS_EV_MOUSE_BUTTON;
138+			ev->mouse.button.button = MAUS_MOUSE_BUTTON_MIDDLE;
139+			ev->mouse.button.pressed = true;
140+			mw->mouse_buttons[MAUS_MOUSE_BUTTON_MIDDLE] = true;
141+			return true;
142+		case WM_MBUTTONUP:
143+			ev->type = MAUS_EV_MOUSE_BUTTON;
144+			ev->mouse.button.button = MAUS_MOUSE_BUTTON_MIDDLE;
145+			ev->mouse.button.pressed = false;
146+			mw->mouse_buttons[MAUS_MOUSE_BUTTON_MIDDLE] = false;
147+			return true;
148+
149+		case WM_MOUSEWHEEL:
150+			delta = GET_WHEEL_DELTA_WPARAM(msg->wParam);
151+			mbtype = (delta > 0) ? MAUS_MOUSE_BUTTON_SCROLL_UP
152+			                     : MAUS_MOUSE_BUTTON_SCROLL_DOWN;
153+			ev->type = MAUS_EV_MOUSE_BUTTON;
154+			ev->mouse.button.button = mbtype;
155+			ev->mouse.button.pressed = true;
156+			return true;
157+
158+		case WM_KEYDOWN:
159+		case WM_SYSKEYDOWN:
160+			scan = (UINT)((msg->lParam >> 16) & 0xFF);
161+			vk = resolve_lr((UINT)msg->wParam, msg->lParam);
162+
163+			ev->type = MAUS_EV_KEY;
164+			ev->key.code = (uint32_t)msg->wParam;
165+			ev->key.pressed = true;
166+			ev->key.key = vk_to_mauskey(vk);
167+			ev->key.text = translate_text((UINT)msg->wParam, scan);
168+
169+			if (ev->key.code < MAUS_KEYCODE_LAST)
170+				mw->key_codes[ev->key.code] = true;
171+			if (ev->key.key != MAUS_KEY_NONE)
172+				mw->key_syms[ev->key.key] = true;
173+			return true;
174+
175+		case WM_KEYUP:
176+		case WM_SYSKEYUP:
177+			vk = resolve_lr((UINT)msg->wParam, msg->lParam);
178+
179+			ev->type = MAUS_EV_KEY;
180+			ev->key.code = (uint32_t)msg->wParam;
181+			ev->key.pressed = false;
182+			ev->key.key = vk_to_mauskey(vk);
183+			ev->key.text = 0;
184+
185+			if (ev->key.code < MAUS_KEYCODE_LAST)
186+				mw->key_codes[ev->key.code] = false;
187+			if (ev->key.key != MAUS_KEY_NONE)
188+				mw->key_syms[ev->key.key] = false;
189+			return true;
190+	}
191+
192+	return false;
193+}
194+
195+static MausKey vk_to_mauskey(UINT vk)
196+{
197+	if (vk >= 'A' && vk <= 'Z')
198+		return (MausKey)(MAUS_KEY_A + (vk - 'A'));
199+	if (vk >= '0' && vk <= '9')
200+		return (MausKey)(MAUS_KEY_0 + (vk - '0'));
201+	if (vk >= VK_F1 && vk <= VK_F12)
202+		return (MausKey)(MAUS_KEY_F1 + (vk - VK_F1));
203+	if (vk >= VK_NUMPAD0 && vk <= VK_NUMPAD9)
204+		return (MausKey)(MAUS_KEY_KP_0 + (vk - VK_NUMPAD0));
205+
206+	for (size_t i = 0; i < sizeof(keymap) / sizeof(keymap[0]); i++) {
207+		if (keymap[i].vk == vk)
208+			return keymap[i].maus;
209+	}
210+
211+	return MAUS_KEY_NONE;
212+}
213+
214+static UINT resolve_lr(UINT vk, LPARAM lparam)
215+{
216+	UINT scan = (UINT)((lparam >> 16) & 0xFF);
217+	int  ext  = (lparam & (1 << 24)) != 0;
218+
219+	if (vk == VK_SHIFT)
220+		return MapVirtualKey(scan, MAPVK_VSC_TO_VK_EX);
221+	if (vk == VK_CONTROL)
222+		return ext ? VK_RCONTROL : VK_LCONTROL;
223+	if (vk == VK_MENU)
224+		return ext ? VK_RMENU : VK_LMENU;
225+
226+	return vk;
227+}
228+
229+static char translate_text(UINT vk, UINT scan)
230+{
231+	BYTE state[256];
232+	if (!GetKeyboardState(state))
233+		return 0;
234+
235+	WCHAR buf[4];
236+	int n = ToUnicode(vk, scan, state, buf, 4, 0);
237+	if (n == 1 && buf[0] < 0x80)
238+		return (buf[0] == '\r') ? '\n' : (char)buf[0];
239+
240+	return 0;
241+}
242+
243+static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp)
244+{
245+	if (msg == WM_DESTROY) {
246+		PostQuitMessage(EXIT_SUCCESS);
247+		return 0;
248+	}
249+
250+	if (msg == WM_SIZE) {
251+		Maus* mw = (Maus*)GetWindowLongPtr(hwnd, GWLP_USERDATA);
252+		if (mw) {
253+			MausBackend* be = &mw->backend;
254+			be->resized = true;
255+			be->rw = LOWORD(lp);
256+			be->rh = HIWORD(lp);
257+		}
258+		return 0;
259+	}
260+
261+	return DefWindowProc(hwnd, msg, wp, lp);
262+}
263+
264+void maus_clear(Maus* mw, MausColor col)
265+{
266+	uint32_t col_up = MAUS_UNPACK_COL(col);
267+	uint32_t pxs = mw->height * mw->width;
268+	for (uint32_t i = 0; i < pxs; i++)
269+		mw->bfb[i] = col_up;
270+}
271+
272+void maus_clipboard_set_text(Maus* mw, const char* text)
273+{
274+	MausBackend* be = &mw->backend;
275+	if (!text || !OpenClipboard(be->hwnd))
276+		return;
277+	EmptyClipboard();
278+	size_t len = strlen(text) + 1;
279+	HGLOBAL hmem = GlobalAlloc(GMEM_MOVEABLE, len);
280+	if (hmem) {
281+		void* ptr = GlobalLock(hmem);
282+		if (ptr) {
283+			memcpy(ptr, text, len);
284+			GlobalUnlock(hmem);
285+
286+			if (!SetClipboardData(CF_TEXT, hmem))
287+				GlobalFree(hmem); /* failed */
288+		}
289+		else {
290+			GlobalFree(hmem);
291+		}
292+	}
293+
294+	CloseClipboard();
295+}
296+
297+char* maus_clipboard_get_text(Maus* mw)
298+{
299+	MausBackend* be = &mw->backend;
300+	if (!OpenClipboard(be->hwnd))
301+		return NULL;
302+
303+	HANDLE hmem = GetClipboardData(CF_TEXT);
304+	if (!hmem) {
305+		CloseClipboard();
306+		return NULL;
307+	}
308+
309+	const char* text = GlobalLock(hmem);
310+	if (text) {
311+		if (mw->clipboard) {
312+			free(mw->clipboard);
313+			mw->clipboard = NULL;
314+		}
315+
316+		size_t len = strlen(text) + 1;
317+		mw->clipboard = malloc(len);
318+		if (mw->clipboard)
319+			memcpy(mw->clipboard, text, len);
320+
321+		GlobalUnlock(hmem);
322+	}
323+
324+	CloseClipboard();
325+	return mw->clipboard;
326+}
327+
328+void maus_close(Maus* mw)
329+{
330+	MausBackend* be = &mw->backend;
331+	fb_destroy(mw);
332+	if (mw->bfb) {
333+		free(mw->bfb);
334+		mw->bfb = NULL;
335+	}
336+	if (mw->clipboard) {
337+		free(mw->clipboard);
338+		mw->clipboard = NULL;
339+	}
340+	if (be->hwnd)
341+		(void) maus_close_window(mw);
342+}
343+
344+bool maus_close_window(Maus* mw)
345+{
346+	MausBackend* be = &mw->backend;
347+
348+	if (be->hwnd) {
349+		DestroyWindow(be->hwnd);
350+		be->hwnd = NULL;
351+		return true;
352+	}
353+
354+	return false;
355+}
356+
357+
358+bool maus_create_window(Maus* mw)
359+{
360+	MausBackend* be = &mw->backend;
361+	be->hInst = GetModuleHandle(NULL);
362+
363+	WNDCLASS wc = {0};
364+	wc.lpfnWndProc = WndProc;
365+	wc.hInstance = be->hInst;
366+	wc.lpszClassName = "MausWindow";
367+	wc.hCursor = LoadCursor(NULL, IDC_ARROW);
368+	if (!RegisterClass(&wc))
369+		return false;
370+
371+	RECT r = { 0, 0, mw->width, mw->height };
372+	AdjustWindowRect(&r, WS_OVERLAPPEDWINDOW, FALSE);
373+
374+	be->hwnd = CreateWindowEx(
375+		0, "MausWindow", mw->title,
376+		WS_OVERLAPPEDWINDOW, mw->x, mw->y,
377+		r.right - r.left, r.bottom - r.top,
378+		NULL, NULL, be->hInst, NULL
379+	);
380+
381+	if (!be->hwnd)
382+		return false;
383+
384+	SetWindowLongPtr(be->hwnd, GWLP_USERDATA, (LONG_PTR)mw);
385+	ShowWindow(be->hwnd, SW_SHOW);
386+	UpdateWindow(be->hwnd);
387+	return true;
388+}
389+
390+Maus* maus_init(const char* title, int x, int y, int width, int height)
391+{
392+	Maus* mw = calloc(1, sizeof(Maus));
393+	MausBackend* be = &mw->backend;
394+
395+	mw->frame_time_last = maus_get_time_ns();
396+	mw->title = title;
397+	mw->width = width;
398+	mw->height = height;
399+	mw->x = x;
400+	mw->y = y;
401+	be->hInst = GetModuleHandle(NULL);
402+	be->hwnd = NULL;
403+	be->hbm = NULL;
404+
405+	if (!fb_create(mw)) {
406+		maus_log(stderr, "failed to create framebuffer");
407+		free(mw);
408+		return NULL;
409+	}
410+
411+	mw->bfb = calloc(mw->stride * mw->height, sizeof(uint32_t));
412+	if (!mw->bfb) {
413+		maus_log(stderr, "failed to allocate back buffer");
414+		maus_close(mw);
415+		free(mw);
416+		return NULL;
417+	}
418+
419+	return mw;
420+}
421+
422+bool maus_event_poll(Maus* mw, MausEvent* ev)
423+{
424+	MausBackend* be = &mw->backend;
425+	MSG msg;
426+	while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
427+		TranslateMessage(&msg);
428+		DispatchMessage(&msg);
429+		ev->type = MAUS_EV_NONE;
430+
431+		if (handle_event(&msg, ev, mw))
432+			return true;
433+	}
434+
435+	/* WM_SIZE doesnt is handled separately */
436+	if (be->resized) {
437+		be->resized = false;
438+		ev->type = MAUS_EV_RESIZE;
439+		ev->resize.width = be->rw;
440+		ev->resize.height = be->rh;
441+		return true;
442+	}
443+
444+	return false;
445+}
446+
447+void maus_event_wait(Maus* mw, MausEvent* ev)
448+{
449+	MausBackend* be = &mw->backend;
450+	MSG msg;
451+
452+	for (;;) {
453+		int r = GetMessage(&msg, NULL, 0, 0);
454+		if (r <= 0) {
455+			ev->type = MAUS_EV_CLOSE;
456+			return;
457+		}
458+
459+		TranslateMessage(&msg);
460+		DispatchMessage(&msg);
461+		ev->type = MAUS_EV_NONE;
462+
463+		/* WM_SIZE doesnt is handled separately */
464+                if (be->resized) {
465+			be->resized = false;
466+			ev->type = MAUS_EV_RESIZE;
467+			ev->resize.width = be->rw;
468+			ev->resize.height = be->rh;
469+			return;
470+		}
471+
472+		if (handle_event(&msg, ev, mw))
473+			return;
474+	}
475+}
476+
477+void maus_present(Maus* mw)
478+{
479+	MausBackend* be = &mw->backend;
480+	if (!be->hwnd || !be->memdc)
481+		return;
482+	HDC wdc = GetDC(be->hwnd);
483+
484+	uint32_t bytes = mw->stride * mw->height * sizeof(uint32_t);
485+	memcpy(mw->fb, mw->bfb, bytes);
486+
487+	BitBlt(wdc, 0, 0, mw->width, mw->height, be->memdc, 0, 0, SRCCOPY);
488+	ReleaseDC(be->hwnd, wdc);
489+}
490+
491+bool maus_resize(Maus* mw, uint32_t width, uint32_t height)
492+{
493+	if (width == 0 || height == 0 ||
494+	    (mw->width == width && mw->height == height))
495+		return false;
496+
497+	fb_destroy(mw);
498+	if (mw->bfb) {
499+		free(mw->bfb);
500+		mw->bfb = NULL;
501+	}
502+
503+	mw->width = width;
504+	mw->height = height;
505+
506+	if (!fb_create(mw))
507+		return false;
508+
509+	mw->bfb = calloc(mw->stride * mw->height, sizeof(uint32_t));
510+	if (!mw->bfb)
511+		return false;
512+
513+	return true;
514+}
515+
516+void maus_cur_set_mode(Maus* mw, MausCursorState state)
517+{
518+	MausBackend* be = &mw->backend;
519+	HWND hwnd = be->hwnd;
520+
521+	if (!hwnd) {
522+		maus_log(stderr, "hwnd is NULL");
523+		return;
524+	}
525+
526+	/* show cursor */
527+	if (state == MAUS_CURSOR_STATE_VISIBLE) {
528+		while (ShowCursor(TRUE) < 0);
529+		return;
530+	}
531+
532+	/* hide cursor */
533+	if (state == MAUS_CURSOR_STATE_HIDDEN) {
534+		while (ShowCursor(FALSE) >= 0);
535+		return;
536+	}
537+
538+
539+	/* lock cursor */
540+	if (state == MAUS_CURSOR_STATE_LOCKED) {
541+		RECT r;
542+		GetClientRect(hwnd, &r);
543+		MapWindowPoints(hwnd, NULL, (POINT*)&r, 2);
544+
545+		if (!ClipCursor(&r)) {
546+			maus_log(stderr, "failed to grab mouse pointer");
547+			return;
548+		}
549+
550+		return;
551+	}
552+
553+	/* unlock cursor */
554+	if (state == MAUS_CURSOR_STATE_FREE) {
555+		ClipCursor(NULL);
556+		return;
557+	}
558+}
559+
+695, -0
  1@@ -0,0 +1,695 @@
  2+#include <limits.h>
  3+#include <stdbool.h>
  4+#include <stdlib.h>
  5+#include <string.h>
  6+#include <sys/shm.h>
  7+#include <sys/ipc.h>
  8+
  9+#include <X11/Xlib.h>
 10+#include <X11/Xutil.h>
 11+#include <X11/keysym.h>
 12+#include <X11/cursorfont.h>
 13+#include <X11/extensions/XShm.h>
 14+
 15+#include "maus.h"
 16+#include "maus_input.h"
 17+#include "maus_x11.h"
 18+#include "utils.h"
 19+
 20+typedef struct {
 21+	KeySym  x11;
 22+	MausKey maus;
 23+} KeyMapEntry;
 24+
 25+static bool fb_create(Maus* mw);
 26+static bool fb_create_shm(Maus* mw);
 27+/* TODO? static bool fb_create_ximage(Maus* mw); */
 28+static void fb_destroy(Maus* mw);
 29+static bool handle_event(XEvent* xev, MausEvent* ev, Maus* mw);
 30+static MausKey keysym_to_mauskey(KeySym sym);
 31+static MausMouseButton mouse_button_to_maus(int btn);
 32+static int xerr(Display* dpy, XErrorEvent* ev);
 33+
 34+static const KeyMapEntry keymap[] = {
 35+	{ XK_BackSpace,    MAUS_KEY_BACKSPACE }, { XK_Tab,              MAUS_KEY_TAB },
 36+	{ XK_Return,       MAUS_KEY_ENTER },     { XK_Escape,           MAUS_KEY_ESCAPE },
 37+	{ XK_apostrophe,   MAUS_KEY_APOSTROPHE },{ XK_comma,            MAUS_KEY_COMMA },
 38+	{ XK_space,        MAUS_KEY_SPACE },     { XK_minus,            MAUS_KEY_MINUS },
 39+	{ XK_period,       MAUS_KEY_PERIOD },    { XK_slash,            MAUS_KEY_SLASH },
 40+	{ XK_semicolon,    MAUS_KEY_SEMICOLON }, { XK_equal,            MAUS_KEY_EQUAL },
 41+	{ XK_Delete,       MAUS_KEY_DELETE },    { XK_Left,             MAUS_KEY_LEFT },
 42+	{ XK_Right,        MAUS_KEY_RIGHT },     { XK_Up,               MAUS_KEY_UP },
 43+	{ XK_Down,         MAUS_KEY_DOWN },      { XK_Home,             MAUS_KEY_HOME },
 44+	{ XK_End,          MAUS_KEY_END },       { XK_Page_Up,          MAUS_KEY_PAGE_UP },
 45+	{ XK_Page_Down,    MAUS_KEY_PAGE_DOWN }, { XK_Insert,           MAUS_KEY_INSERT },
 46+	{ XK_Shift_L,      MAUS_KEY_SHIFT_L },   { XK_Shift_R,          MAUS_KEY_SHIFT_R },
 47+	{ XK_Control_L,    MAUS_KEY_CONTROL_L }, { XK_Control_R,        MAUS_KEY_CONTROL_R },
 48+	{ XK_Alt_L,        MAUS_KEY_ALT_L },     { XK_Alt_R,            MAUS_KEY_ALT_R },
 49+	{ XK_Super_L,      MAUS_KEY_SUPER_L },   { XK_Super_R,          MAUS_KEY_SUPER_R },
 50+	{ XK_Caps_Lock,    MAUS_KEY_CAPS_LOCK }, { XK_Num_Lock,         MAUS_KEY_NUM_LOCK },
 51+	{ XK_Pause,        MAUS_KEY_PAUSE },     { XK_Menu,             MAUS_KEY_MENU },
 52+	{ XK_KP_Add,       MAUS_KEY_KP_ADD },    { XK_KP_Enter,         MAUS_KEY_KP_ENTER },
 53+	{ XK_bracketright, MAUS_KEY_RIGHT_BRACKET }, { XK_grave,        MAUS_KEY_GRAVE },
 54+	{ XK_Scroll_Lock,  MAUS_KEY_SCROLL_LOCK },   { XK_Print,        MAUS_KEY_PRINT_SCREEN },
 55+	{ XK_KP_Decimal,   MAUS_KEY_KP_DECIMAL },    { XK_KP_Divide,    MAUS_KEY_KP_DIVIDE },
 56+	{ XK_KP_Multiply,  MAUS_KEY_KP_MULTIPLY },   { XK_KP_Subtract,  MAUS_KEY_KP_SUBTRACT },
 57+	{ XK_bracketleft,  MAUS_KEY_LEFT_BRACKET },  { XK_backslash,    MAUS_KEY_BACKSLASH },
 58+	{ XK_KP_Equal,     MAUS_KEY_KP_EQUAL },
 59+};
 60+
 61+static int xerrored;
 62+
 63+static bool fb_create(Maus* mw)
 64+{
 65+	MausBackend* be = &mw->backend;
 66+	be->image = NULL;
 67+	be->shmat = false;
 68+	be->shm.shmid = -1;
 69+	be->shm.shmaddr = NULL;
 70+	mw->fb = NULL;
 71+	mw->stride = 0;
 72+
 73+	return fb_create_shm(mw);
 74+}
 75+
 76+/* allocate and store a new shm for the framebuffer */
 77+static bool fb_create_shm(Maus* mw)
 78+{
 79+	MausBackend* be = &mw->backend;
 80+	if (!XShmQueryExtension(be->display))
 81+		return false;
 82+
 83+	int scr = DefaultScreen(be->display);
 84+	int depth = DefaultDepth(be->display, scr);
 85+	Visual* vis = DefaultVisual(be->display, scr);
 86+	if (!vis) {
 87+		maus_log(stderr, "could not get default visual");
 88+		return false;
 89+	}
 90+
 91+	be->image = XShmCreateImage(
 92+		be->display, vis, depth, ZPixmap, NULL,
 93+		&be->shm, mw->width, mw->height
 94+	);
 95+	if (!be->image) {
 96+		maus_log(stderr, "could not create XImage (shm)");
 97+		return false;
 98+	}
 99+
100+	if (be->image->bits_per_pixel != 32) {
101+		XDestroyImage(be->image);
102+		be->image = NULL;
103+		maus_log(stderr, "XImage not 32bpp");
104+		return false;
105+	}
106+
107+	size_t size = be->image->bytes_per_line * be->image->height;
108+	be->shm.shmid = shmget(IPC_PRIVATE, size, IPC_CREAT | 0600);
109+	if (be->shm.shmid < 0) {
110+		XDestroyImage(be->image);
111+		be->image = NULL;
112+		maus_log(stderr, "shmget() failed");
113+		return false;
114+	}
115+
116+	be->shm.shmaddr = shmat(be->shm.shmid, NULL, 0);
117+	if (be->shm.shmaddr == (char*)-1) {
118+		shmctl(be->shm.shmid, IPC_RMID, NULL);
119+		XDestroyImage(be->image);
120+		be->image = NULL;
121+		maus_log(stderr, "shmat() failed");
122+		return false;
123+	}
124+
125+	be->shm.readOnly = False;
126+	be->image->data = be->shm.shmaddr;
127+
128+	xerrored = 0;
129+	int (*old_xerr)(Display*, XErrorEvent*) = XSetErrorHandler(xerr);
130+
131+	if (!XShmAttach(be->display, &be->shm))
132+		xerrored = 1;
133+
134+	XSync(be->display, False);
135+	XSetErrorHandler(old_xerr);
136+	if (xerrored) {
137+		shmdt(be->shm.shmaddr);
138+		shmctl(be->shm.shmid, IPC_RMID, NULL);
139+		XDestroyImage(be->image);
140+
141+		be->image = NULL;
142+		mw->fb = NULL;
143+		be->shm.shmaddr = NULL;
144+		be->shm.shmid = -1;
145+		maus_log(stderr, "XShmAttach failed");
146+		return false;
147+	}
148+
149+	shmctl(be->shm.shmid, IPC_RMID, NULL);
150+
151+	be->shmat = true;
152+	mw->fb = (uint32_t*) be->image->data;
153+	mw->stride = be->image->bytes_per_line / sizeof(uint32_t);
154+
155+	return true;
156+}
157+
158+static void fb_destroy(Maus* mw)
159+{
160+	MausBackend* be = &mw->backend;
161+	Display* dpy = be->display;
162+	if (be->shmat) {
163+		XShmDetach(dpy, &be->shm);
164+		XSync(dpy, False);
165+		be->shmat = false;
166+	}
167+
168+	if (be->image) {
169+		XDestroyImage(be->image);
170+		be->image = NULL;
171+	}
172+
173+	if (be->shm.shmaddr && be->shm.shmaddr != (char*) -1) {
174+		shmdt(be->shm.shmaddr);
175+		be->shm.shmaddr = NULL;
176+	}
177+
178+	/* normally already removed after attach but
179+	   can check anyways */
180+	if (be->shm.shmid >= 0) {
181+		shmctl(be->shm.shmid, IPC_RMID, NULL);
182+		be->shm.shmid = -1;
183+	}
184+
185+	mw->fb = NULL;
186+	mw->stride = 0;
187+}
188+
189+static bool handle_event(XEvent* xev, MausEvent* ev, Maus* mw)
190+{
191+	MausBackend* be = &mw->backend;
192+	uint32_t code = 0;
193+	unsigned int mb;
194+	MausMouseButton mbtype;
195+
196+	switch (xev->type) {
197+		case ClientMessage:
198+			if ((Atom)xev->xclient.data.l[0] == be->atoms[MAUS_ATOM_WM_DELETE_WINDOW]) {
199+				ev->type = MAUS_EV_CLOSE;
200+				return true;
201+			}
202+			break;
203+
204+		case MappingNotify:
205+			XRefreshKeyboardMapping(&xev->xmapping);
206+			return true;
207+			break;
208+
209+		case KeyPress: {
210+			ev->type = MAUS_EV_KEY;
211+			code = xev->xkey.keycode;
212+			ev->key.code = code;
213+			ev->key.pressed = true;
214+			if (code < MAUS_KEYCODE_LAST)
215+			        mw->key_codes[code] = true;
216+
217+			/* grab the raw, base sym */
218+			KeySym sym = XLookupKeysym(&xev->xkey, 0);
219+			ev->key.key = keysym_to_mauskey(sym);
220+			if (ev->key.key != MAUS_KEY_NONE)
221+			        mw->key_syms[ev->key.key] = true;
222+
223+			char buf[8] = {0};
224+			int len = XLookupString(&xev->xkey, buf, sizeof(buf), NULL, NULL);
225+			if (len > 0)
226+			        ev->key.text = (buf[0] == '\r') ? '\n' : buf[0];
227+			else
228+			        ev->key.text = 0;
229+
230+			return true;
231+		}
232+
233+		case KeyRelease: {
234+			ev->type = MAUS_EV_KEY;
235+			code = xev->xkey.keycode;
236+			ev->key.code = code;
237+			ev->key.pressed = false;
238+			ev->key.text = 0;
239+
240+			if (code < MAUS_KEYCODE_LAST)
241+			        mw->key_codes[code] = false;
242+
243+			/* unset base sym set on `KeyPress` */
244+			KeySym sym = XLookupKeysym(&xev->xkey, 0);
245+			ev->key.key = keysym_to_mauskey(sym);
246+			if (ev->key.key != MAUS_KEY_NONE)
247+			        mw->key_syms[ev->key.key] = false;
248+
249+			return true;
250+		}
251+
252+		case ButtonPress:
253+			ev->type = MAUS_EV_MOUSE_BUTTON;
254+			mb = xev->xbutton.button;
255+			mbtype = mouse_button_to_maus(mb);
256+			ev->mouse.button.button = mbtype;
257+			ev->mouse.button.pressed = true;
258+			mw->mouse_buttons[mbtype] = true;
259+			return true;
260+
261+		case ButtonRelease:
262+			ev->type = MAUS_EV_MOUSE_BUTTON;
263+			mb = xev->xbutton.button;
264+			mbtype = mouse_button_to_maus(mb);
265+			ev->mouse.button.button = mbtype;
266+			ev->mouse.button.pressed = false;
267+			mw->mouse_buttons[mbtype] = false;
268+			return true;
269+
270+		case MotionNotify:
271+			ev->type = MAUS_EV_MOUSE_MOTION;
272+			ev->mouse.motion.x = xev->xmotion.x;
273+			ev->mouse.motion.y = xev->xmotion.y;
274+			return true;
275+
276+		case ConfigureNotify:
277+			ev->type = MAUS_EV_RESIZE;
278+			ev->resize.width = xev->xconfigure.width;
279+			ev->resize.height = xev->xconfigure.height;
280+			return true;
281+
282+		case SelectionRequest: {
283+			XSelectionRequestEvent* req = &xev->xselectionrequest;
284+			XEvent rsp; /* response */
285+
286+			Atom utf8_string = XInternAtom(be->display, "UTF8_STRING", False);
287+			Atom clipboard = XInternAtom(be->display, "CLIPBOARD", False);
288+
289+			rsp.type = SelectionNotify;
290+			rsp.xselection.display = req->display;
291+			rsp.xselection.requestor = req->requestor;
292+			rsp.xselection.selection = req->selection;
293+			rsp.xselection.target = req->target;
294+			rsp.xselection.property = None; 
295+			rsp.xselection.time = req->time;
296+
297+			/* assign clipboard text to requesting programs
298+			   window property */
299+			if (req->selection == clipboard &&
300+			    req->target == utf8_string &&
301+			    mw->clipboard) {
302+			        XChangeProperty(
303+					req->display, req->requestor,
304+					req->property, utf8_string, 8,
305+					PropModeReplace, (unsigned char*)mw->clipboard, 
306+					strlen(mw->clipboard)
307+			 	);
308+				rsp.xselection.property = req->property; 
309+			}
310+
311+			/* send reply back to the prog requesting paste */
312+			XSendEvent(req->display, req->requestor, False, 0, &rsp);
313+			XFlush(be->display);
314+
315+			return false; /* skip maus polling it */
316+		}
317+	}
318+
319+	return false;
320+}
321+
322+static MausKey keysym_to_mauskey(KeySym sym)
323+{
324+	if (sym >= 0x20 && sym <= 0x7E)
325+		return (MausKey)sym;
326+
327+	if (sym >= XK_F1 && sym <= XK_F12)
328+		return MAUS_KEY_F1 + (sym - XK_F1);
329+	if (sym >= XK_KP_0 && sym <= XK_KP_9)
330+		return MAUS_KEY_KP_0 + (sym - XK_KP_0);
331+
332+	for (size_t i = 0; i < sizeof(keymap)/ sizeof(keymap[0]); i++) {
333+		if (keymap[i].x11 == sym)
334+			return keymap[i].maus;
335+	}
336+
337+	return MAUS_KEY_NONE;
338+}
339+
340+static MausMouseButton mouse_button_to_maus(int btn)
341+{
342+	switch (btn) {
343+		case Button1: return MAUS_MOUSE_BUTTON_LEFT;
344+		case Button2: return MAUS_MOUSE_BUTTON_MIDDLE;
345+		case Button3: return MAUS_MOUSE_BUTTON_RIGHT;
346+		case Button4: return MAUS_MOUSE_BUTTON_SCROLL_UP;
347+		case Button5: return MAUS_MOUSE_BUTTON_SCROLL_DOWN;
348+		default:      return MAUS_MOUSE_BUTTON_NONE;
349+	}
350+}
351+
352+static int xerr(Display* dpy, XErrorEvent* ev)
353+{
354+	(void) dpy;
355+	(void) ev;
356+	xerrored = 1;
357+	return 0;
358+}
359+
360+void maus_clipboard_set_text(Maus* mw, const char* text)
361+{
362+	MausBackend* be = &mw->backend;
363+	if (!be->display || be->win == None)
364+		return;
365+
366+	/* clear old entry */
367+	if (mw->clipboard) {
368+		free(mw->clipboard);
369+		mw->clipboard = NULL;
370+	}
371+
372+	if (text)
373+		mw->clipboard = strdup(text);
374+
375+	Atom clipboard = XInternAtom(be->display, "CLIPBOARD", False);
376+	XSetSelectionOwner(be->display, clipboard, be->win, CurrentTime);
377+	XFlush(be->display);
378+}
379+
380+char* maus_clipboard_get_text(Maus* mw)
381+{
382+	MausBackend* be = &mw->backend;
383+	Atom clipboard = XInternAtom(be->display, "CLIPBOARD", False);
384+	Atom utf8_string = XInternAtom(be->display, "UTF8_STRING", False);
385+	Atom maus_clipboard_prop = XInternAtom(be->display, "MAUS_CLIPBOARD_PROP", False);
386+
387+	/* fetch clipboard data to `maus_clipboard_prop`
388+	   window property */
389+	XConvertSelection(
390+		be->display, clipboard, utf8_string,
391+		maus_clipboard_prop, be->win, CurrentTime
392+	);
393+	XFlush(be->display);
394+
395+	/* check for selection response */
396+	XEvent xev;
397+	bool ok = false;
398+	for (int timeout = 0; timeout < 10000; timeout++) { 
399+		if (XCheckTypedWindowEvent(be->display, be->win, SelectionNotify, &xev)) {
400+			if (xev.xselection.property != None)
401+				ok = true;
402+			break;
403+		}
404+	}
405+	if (!ok)
406+		return NULL;
407+
408+	/* read string off property */
409+	Atom actual_type;
410+	int actual_fmt;
411+	unsigned long nitems;
412+	unsigned long bytes_after;
413+	unsigned char* prop_data = NULL;
414+	XGetWindowProperty(
415+		be->display, be->win, maus_clipboard_prop, 0,LONG_MAX,
416+		True, utf8_string, &actual_type, &actual_fmt, &nitems,
417+		&bytes_after, &prop_data
418+	);
419+
420+	char* res = NULL;
421+	if (prop_data) {
422+		res = strdup((char*)prop_data);
423+		XFree(prop_data);
424+	}
425+
426+	return res;
427+}
428+
429+void maus_close(Maus* mw)
430+{
431+	MausBackend* be = &mw->backend;
432+	fb_destroy(mw);
433+	if (mw->bfb) {
434+		free(mw->bfb);
435+		mw->bfb = NULL;
436+	}
437+	if (mw->clipboard) {
438+		free(mw->clipboard);
439+		mw->clipboard = NULL;
440+	}
441+	if (be->win != None) {
442+		(void) maus_close_window(mw);
443+	}
444+	if (be->display)
445+		XCloseDisplay(be->display);
446+}
447+
448+bool maus_close_window(Maus* mw)
449+{
450+	MausBackend* be = &mw->backend;
451+	if (be->gc) {
452+		XFreeGC(be->display, be->gc);
453+		be->gc = 0;
454+	}
455+
456+	if (be->win != None) {
457+		XUnmapWindow(be->display, be->win);
458+		XDestroyWindow(be->display, be->win);
459+	}
460+
461+	return true;
462+}
463+
464+bool maus_create_window(Maus* mw)
465+{
466+	MausBackend* be = &mw->backend;
467+	Display* dpy = be->display;
468+	Window* win = &be->win;
469+
470+	*win = XCreateSimpleWindow(
471+		dpy, be->root,
472+		mw->x, mw->y,
473+		mw->width, mw->height,
474+		0u, 0u, 0u
475+	);
476+
477+	if (*win == None)
478+		return false;
479+
480+	XSelectInput(dpy, *win,
481+		ExposureMask | KeyPressMask | KeyReleaseMask | ButtonPressMask |
482+		ButtonReleaseMask | PointerMotionMask | StructureNotifyMask
483+	);
484+	/* no black flashing when resizing */
485+	XSetWindowBackgroundPixmap(dpy, *win, None);
486+
487+	/* atoms */
488+	be->atoms[MAUS_ATOM_WM_DELETE_WINDOW] = XInternAtom(dpy, "WM_DELETE_WINDOW", False);
489+
490+	XSetWMProtocols(dpy, *win, &be->atoms[MAUS_ATOM_WM_DELETE_WINDOW], 1);
491+	XStoreName(dpy, *win, mw->title);
492+
493+	XMapWindow(dpy, *win);
494+	XFlush(dpy);
495+
496+	be->gc = XCreateGC(dpy, *win, 0, NULL);
497+	if (!be->gc) {
498+		maus_log(stderr, "failed to create window GC");
499+		maus_close_window(mw);
500+		return false;
501+	}
502+
503+	return true;
504+}
505+
506+void maus_clear(Maus* mw, MausColor col)
507+{
508+	uint32_t col_up = MAUS_UNPACK_COL(col);
509+	uint32_t pxs = mw->height * mw->width;
510+	for (uint32_t i = 0; i < pxs; i++)
511+		mw->bfb[i] = col_up;
512+}
513+
514+Maus* maus_init(const char* title, int x, int y, int width, int height)
515+{
516+	if (MAUS_WARN_BACKEND_AUTO_SEL)
517+		maus_log(stderr, "backend auto-selected: X11");
518+
519+	Display* d = XOpenDisplay(NULL);
520+	if (!d) {
521+		maus_die("failed to open X11 display");
522+		return NULL;
523+	}
524+
525+	Maus* mw = calloc(1, sizeof(Maus));
526+	MausBackend* be = &mw->backend;
527+	be->display = d;
528+	be->root = DefaultRootWindow(d);
529+	be->win = None;
530+	mw->frame_time_last = maus_get_time_ns();
531+	mw->title = title;
532+	mw->width = width;
533+	mw->height = height;
534+	mw->x = x;
535+	mw->y = y;
536+	mw->fb = NULL;
537+
538+	be->gc = 0;
539+	be->image = NULL;
540+	be->shmat = false;
541+	be->shm.shmid = -1;
542+	be->shm.shmaddr = NULL;
543+	if (!fb_create(mw)) {
544+		maus_log(stderr, "failed to create framebuffer");
545+		maus_close(mw);
546+		free(mw);
547+		return NULL;
548+	}
549+	mw->bfb = calloc(mw->stride * mw->height, sizeof(uint32_t));
550+	if (!mw->bfb) {
551+		maus_log(stderr, "failed to allocate back buffer");
552+		maus_close(mw);
553+		free(mw);
554+		return NULL;
555+	}
556+
557+	return mw;
558+}
559+
560+bool maus_event_poll(Maus* mw, MausEvent* ev)
561+{
562+	MausBackend* be = &mw->backend;
563+	XEvent xev;
564+	while (XPending(be->display)) {
565+		XNextEvent(be->display, &xev);
566+		ev->type = MAUS_EV_NONE;
567+		if (handle_event(&xev, ev, mw))
568+			return true;
569+
570+	}
571+
572+	return false;
573+}
574+
575+void maus_event_wait(Maus* mw, MausEvent* ev)
576+{
577+	MausBackend* be = &mw->backend;
578+	XEvent xev;
579+	for (;;) {
580+		XNextEvent(be->display, &xev);
581+		ev->type = MAUS_EV_NONE;
582+		if (handle_event(&xev, ev, mw))
583+			return;
584+	}
585+}
586+
587+void maus_present(Maus* mw)
588+{
589+	MausBackend* be = &mw->backend;
590+	if (!be->image || be->win == None)
591+		return;
592+
593+	uint32_t bytes = mw->stride * mw->height * sizeof(uint32_t);
594+	memcpy(mw->fb, mw->bfb, bytes);
595+
596+	XShmPutImage(
597+		be->display, be->win, be->gc, be->image,
598+		0, 0, 0, 0, mw->width, mw->height, False
599+	);
600+
601+	XFlush(be->display);
602+}
603+
604+bool maus_resize(Maus* mw, uint32_t width, uint32_t height)
605+{
606+	MausBackend* be = &mw->backend;
607+	if (width == 0 || height == 0 ||
608+	    (mw->width == width && mw->height == height))
609+		return false;
610+
611+	fb_destroy(mw);
612+	if (mw->bfb) {
613+		free(mw->bfb);
614+		mw->bfb = NULL;
615+	}
616+
617+	mw->width = width;
618+	mw->height = height;
619+
620+	XSync(be->display, False);
621+	XFlush(be->display);
622+
623+	if (!fb_create(mw))
624+		return false;
625+
626+	mw->bfb = calloc(mw->stride * mw->height, sizeof(uint32_t));
627+	if (!mw->bfb)
628+		return false;
629+
630+	return true;
631+}
632+
633+void maus_cur_set_mode(Maus* mw, MausCursorState state)
634+{
635+	MausBackend* be = &mw->backend;
636+	Display* dpy = be->display;
637+	Window win = be->win;
638+
639+	if (!dpy) {
640+		maus_log(stderr, "display is NULL");
641+		return;
642+	}
643+	if (win == None) {
644+		maus_log(stderr, "win is None");
645+		return;
646+	}
647+
648+	/* show cursor */
649+	if (state == MAUS_CURSOR_STATE_VISIBLE) {
650+		XUndefineCursor(dpy, win);
651+		XFlush(dpy);
652+		return;
653+	}
654+
655+	/* hide cursor */
656+	if (state == MAUS_CURSOR_STATE_HIDDEN) {
657+		Pixmap blank_pm = XCreatePixmap(dpy, win, 1, 1, 1);
658+		XColor black = {.red=0, .green=0, .blue=0};
659+		Cursor blank_cur = XCreatePixmapCursor(
660+			dpy, blank_pm, blank_pm, &black, &black, 0, 0
661+		);
662+		XFreePixmap(dpy, blank_pm);
663+
664+		XDefineCursor(dpy, win, blank_cur);
665+		XFreeCursor(dpy, blank_cur);
666+		XFlush(dpy);
667+		return;
668+	}
669+
670+	/* lock cursor */
671+	if (state == MAUS_CURSOR_STATE_LOCKED) {
672+		Mask mask = ButtonPressMask | ButtonReleaseMask | PointerMotionMask;
673+		int grab = XGrabPointer(
674+			dpy, win, True, mask, GrabModeAsync,
675+			GrabModeAsync, win, None, CurrentTime
676+		);
677+
678+		if (grab != GrabSuccess) {
679+			maus_log(stderr, "failed to grab mouse pointer");
680+			return;
681+		}
682+
683+		/* TODO keep position just snap to window dimensions */
684+		XWarpPointer(dpy, None, win, 0, 0, 0, 0, mw->width/2, mw->height/2);
685+		XFlush(dpy);
686+		return;
687+	}
688+
689+	/* unlock cursor */
690+	if (state == MAUS_CURSOR_STATE_FREE) {
691+		XUngrabPointer(dpy, CurrentTime);
692+		XFlush(dpy);
693+		return;
694+	}
695+}
696+
+27, -0
 1@@ -0,0 +1,27 @@
 2+#ifndef MAUS_X11_H
 3+#define MAUS_X11_H
 4+
 5+#include <stdbool.h>
 6+
 7+#include <X11/Xlib.h>
 8+#include <X11/extensions/XShm.h>
 9+
10+typedef enum {
11+	MAUS_ATOM_WM_DELETE_WINDOW,
12+	MAUS_ATOM_LAST,
13+} MausX11Atoms;
14+
15+typedef struct {
16+	Display*       display;
17+	Window         root;
18+	Window         win;
19+	Atom           atoms[MAUS_ATOM_LAST];
20+	GC             gc;
21+
22+	XImage*        image;
23+	XShmSegmentInfo shm;
24+	bool           shmat;
25+} MausBackend;
26+
27+#endif /* MAUS_X11_H */
28+
+19, -0
 1@@ -0,0 +1,19 @@
 2+#include <stdlib.h>
 3+#include <string.h>
 4+
 5+#include "utils.h"
 6+
 7+char* strdup(const char* src)
 8+{
 9+	if (src == NULL)
10+		return NULL;
11+
12+	size_t len = strlen(src);
13+	char* dst = malloc(len + 1);
14+	if (dst == NULL)
15+		return NULL;
16+
17+	memcpy(dst, src, len + 1);
18+	return dst;
19+}
20+
+11, -0
 1@@ -0,0 +1,11 @@
 2+#ifndef UTIL_H
 3+#define UTIL_H
 4+
 5+/* duplicate a string. returns pointer to address of
 6+   newly allocated string buffer. if NULL, allocation
 7+   failed or src is NULL
 8+   note: you must free this newly allocated string */
 9+char* strdup(const char* src);
10+
11+#endif /* UTIL_H */
12+