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+
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+