From c7ee83399f754bbc1fe554b64efed9fadf9e0e05 Mon Sep 17 00:00:00 2001 From: Serge Zaitsev Date: Fri, 13 Jan 2023 16:47:22 +0100 Subject: [PATCH] initial commit --- LICENSE | 21 ++++ Makefile | 15 +++ README.md | 88 ++++++++++++++++ example.c | 237 ++++++++++++++++++++++++++++++++++++++++++ fenster.h | 302 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 663 insertions(+) create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README.md create mode 100644 example.c create mode 100644 fenster.h diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..601510a --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Serge Zaitsev + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..6dd58f8 --- /dev/null +++ b/Makefile @@ -0,0 +1,15 @@ +CFLAGS ?= -Wall -Wextra -std=c99 + +ifeq ($(OS),Windows_NT) + LDFLAGS = -lgdi32 +else + UNAME_S := $(shell uname -s) + ifeq ($(UNAME_S),Darwin) + LDFLAGS = -framework Cocoa + else + LDFLAGS = -lX11 + endif +endif + +example: example.c fenster.h + $(CC) example.c -Os -o $@ $(CFLAGS) $(LDFLAGS) diff --git a/README.md b/README.md new file mode 100644 index 0000000..b0c9704 --- /dev/null +++ b/README.md @@ -0,0 +1,88 @@ +# Fenster + +Fenster /ˈfɛnstɐ/ -- a German language word for "window". + +This library provides the most minimal and highly opinionated way to display a cross-platform 2D canvas. If you remember Borland BGI or drawing things in QBASIC or `INT 10h`- you know what I mean. + +## What it does for you + +* Single application window of given size with a title. +* Application lifecycle and system events are all handled automatically. +* Minimal 24-bit RGB framebuffer. +* Cross-platform keyboard events (keycodes). +* Cross-platform timers to have a stable FPS rate. +* It's a single header in plain C99 of ~300LOC with no memory allocations. + +## What it might do for you in the next version + +* Mouse events (at least left button click + XY) +* Audio playback (WinMM, CoreAudio, ALSA) +* Bindings for Go, Zig, Rust, Nim, Python... + +## What it will never do for you + +* GUI widgets - use Qt or Gtk or WxWidgets. +* Complex drawing routines or OpenGL - use Sokol or MiniFB or Tigr. +* Low-latency audio - use PortAudio, RtAudio, libsoundio etc. + +In other words, you get a single super tiny C file, with a very simple API and it allows you to build graphical apps (simple games, emulators) in a very low-level retrocomputing manner. + +## Example + +Here's how to draw white noise: + +```c +#include "fenster.h" +#define W 320 +#define H 240 +int main() { + uint32_t buf[W * H]; + struct fenster f = { .title = "hello", .width = W, .height = H, .buf = buf }; + fenster_open(&f); + while (fenster_loop(&f) == 0) { + for (int i = 0; i < 320; i++) { + for (int j = 0; j < 240; j++) { + fenster_pixel(&f, i, j) = rand(); + } + } + } + fenster_close(&f); + return 0; +} +``` + +Compile it and run: + +``` +# Linux +cc main.c -lX11 -o main && ./main +# macOS +cc main.c -framework Cocoa -o main && ./main +# windows +# TODO, but link with -lgdi32 +``` + +That's it. + +## API + +```c +struct fenster { + const char *title; /* window title */ + const int width; /* window width */ + const int height; /* window height */ + uint32_t *buf; /* window pixels, 24-bit RGB, row by row, pixel by pixel */ + int keys[256]; /* keys are mostly ASCII, but arrows are 17..20 */ + int mod; /* mod is 4 bits mask, ctrl=1, shift=2, alt=4, meta=8 */ +}; +``` + +* `int fenster_open(struct fenster *f)` - opens a new app window. +* `int fenster_loop(struct fenster *f)` - handles system events and refreshes the canvas. Returns negative values when app window is clsoed. +* `void fenster_close(struct fenster *f)` - closes the window and exists the graphical app. +* `void fenster_sleep(int ms)` - pauses for `ms` milliseconds. +* `int64_t fenster_time()` - returns current time in milliseconds. + +## License + +Code is distributed under MIT license, feel free to use it in your proprietary projects as well. diff --git a/example.c b/example.c new file mode 100644 index 0000000..571095d --- /dev/null +++ b/example.c @@ -0,0 +1,237 @@ +#include "fenster.h" + +#define W 320 +#define H 240 + +/* ============================================================ + * A very minimal example of a Fenster app: + * - Opens a window + * - Starts a loop + * - Changes pixel colours based on some "shader" formula + * - Sleeps if needed to maintain a frame rate of 60 FPS + * - Closes a window + * ============================================================ */ +static int demo_minimal_framebuffer() { + uint32_t buf[W * H]; + struct fenster f = { + .title = "hello", + .width = W, + .height = H, + .buf = buf, + }; + fenster_open(&f); + uint32_t t = 0; + int64_t now = fenster_time(); + while (fenster_loop(&f) == 0) { + t++; + for (int i = 0; i < 320; i++) { + for (int j = 0; j < 240; j++) { + /* White noise: */ + /* fenster_pixel(&f, i, j) = (rand() << 16) ^ (rand() << 8) ^ rand(); */ + + /* Colourful and moving: */ + /* fenster_pixel(&f, i, j) = i * j * t; */ + + /* Munching squares: */ + fenster_pixel(&f, i, j) = i ^ j ^ t; + } + } + int64_t time = fenster_time(); + if (time - now < 1000 / 60) { + fenster_sleep(time - now); + } + now = time; + } + fenster_close(&f); + return 0; +} + +/* ============================================================ + * An example of using raster graphics with Fenster + * - Line: https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm + * - Circle: https://en.wikipedia.org/wiki/Midpoint_circle_algorithm + * - Rectangle: well, it's obvious + * - Flood fill: very inefficient, using recursion + * - Text: small 5x3 bitmap font is used to render glyps + * ============================================================ */ +static void fenster_line(struct fenster *f, int x0, int y0, int x1, int y1, + uint32_t c) { + int dx = abs(x1 - x0), sx = x0 < x1 ? 1 : -1; + int dy = abs(y1 - y0), sy = y0 < y1 ? 1 : -1; + int err = (dx > dy ? dx : -dy) / 2, e2; + for (;;) { + fenster_pixel(f, x0, y0) = c; + if (x0 == x1 && y0 == y1) { + break; + } + e2 = err; + if (e2 > -dx) { + err -= dy; + x0 += sx; + } + if (e2 < dy) { + err += dx; + y0 += sy; + } + } +} + +static void fenster_rect(struct fenster *f, int x, int y, int w, int h, + uint32_t c) { + for (int row = 0; row < h; row++) { + for (int col = 0; col < w; col++) { + fenster_pixel(f, x + col, y + row) = c; + } + } +} + +static void fenster_circle(struct fenster *f, int x, int y, int r, uint32_t c) { + for (int dy = -r; dy <= r; dy++) { + for (int dx = -r; dx <= r; dx++) { + if (dx * dx + dy * dy <= r * r) { + fenster_pixel(f, x + dx, y + dy) = c; + } + } + } +} + +static void fenster_fill(struct fenster *f, int x, int y, uint32_t old, + uint32_t c) { + if (x < 0 || y < 0 || x >= f->width || y >= f->height) { + return; + } + if (fenster_pixel(f, x, y) == old) { + fenster_pixel(f, x, y) = c; + fenster_fill(f, x - 1, y, old, c); + fenster_fill(f, x + 1, y, old, c); + fenster_fill(f, x, y - 1, old, c); + fenster_fill(f, x, y + 1, old, c); + } +} + +// clang-format off +static uint16_t font5x3[] = {0x0000,0x2092,0x002d,0x5f7d,0x279e,0x52a5,0x7ad6,0x0012,0x4494,0x1491,0x017a,0x05d0,0x1400,0x01c0,0x0400,0x12a4,0x2b6a,0x749a,0x752a,0x38a3,0x4f4a,0x38cf,0x3bce,0x12a7,0x3aae,0x49ae,0x0410,0x1410,0x4454,0x0e38,0x1511,0x10e3,0x73ee,0x5f7a,0x3beb,0x624e,0x3b6b,0x73cf,0x13cf,0x6b4e,0x5bed,0x7497,0x2b27,0x5add,0x7249,0x5b7d,0x5b6b,0x3b6e,0x12eb,0x4f6b,0x5aeb,0x388e,0x2497,0x6b6d,0x256d,0x5f6d,0x5aad,0x24ad,0x72a7,0x6496,0x4889,0x3493,0x002a,0xf000,0x0011,0x6b98,0x3b79,0x7270,0x7b74,0x6750,0x95d6,0xb9ee,0x5b59,0x6410,0xb482,0x56e8,0x6492,0x5be8,0x5b58,0x3b70,0x976a,0xcd6a,0x1370,0x38f0,0x64ba,0x3b68,0x2568,0x5f68,0x54a8,0xb9ad,0x73b8,0x64d6,0x2492,0x3593,0x03e0}; +// clang-format on +static void fenster_text(struct fenster *f, int x, int y, char *s, int scale, + uint32_t c) { + while (*s) { + char chr = *s++; + if (chr > 32) { + uint16_t bmp = font5x3[chr - 32]; + for (int dy = 0; dy < 5; dy++) { + for (int dx = 0; dx < 3; dx++) { + if (bmp >> (dy * 3 + dx) & 1) { + fenster_rect(f, x + dx * scale, y + dy * scale, scale, scale, c); + } + } + } + } + x = x + 4 * scale; + } +} + +static int demo_raster_graphics() { + uint32_t buf[W * H]; + struct fenster f = { + .title = "hello", + .width = W, + .height = H, + .buf = buf, + }; + fenster_open(&f); + uint32_t t = 0; + int64_t now = fenster_time(); + while (fenster_loop(&f) == 0) { + t++; + fenster_rect(&f, 0, 0, W, H, 0x00333333); + fenster_rect(&f, W / 4, H / 2, W / 2, H / 3, 0x00ff0000); + fenster_rect(&f, W / 2, H / 2 + H / 12, W / 6, H / 3 - H / 12, 0x00ffffff); + fenster_circle(&f, W / 2 - W / 8, H / 2 + H / 6, W / 20, 0x00ffffff); + fenster_line(&f, W / 4 - 25, H / 2, W / 2, H / 4, 0x0000ffff); + fenster_line(&f, W - W / 4 + 25, H / 2, W / 2, H / 4, 0x0000ffff); + fenster_line(&f, W - W / 4 + 25, H / 2, W / 4 - 25, H / 2, 0x0000ffff); + fenster_fill(&f, W / 2, H / 3, 0x00333333, 0x00ff00ff); + fenster_text(&f, 10, 10, "House", 8, 0x00ffffff); + int64_t time = fenster_time(); + if (time - now < 1000 / 60) { + fenster_sleep(time - now); + } + now = time; + } + fenster_close(&f); + return 0; +} + +/* ============================================================ + * Another small example demostrating keymaps/keycodes: + * - On all platforms keys usually correspond to upper-case ASCII + * - Enter code is 10, Tab is 9, Backspace is 8, Escape is 27 + * - Delete is 127, Space is 32 + * - Modifiers are: Ctrl=1, Shift=2, Ctrl+Shift=3 + * + * This demo prints currently pressed keys with modifiers. + * ============================================================ */ +static int demo_keys() { + uint32_t buf[W * H] = {0}; + struct fenster f = { + .title = "Press any key...", + .width = W, + .height = H, + .buf = buf, + }; + fenster_open(&f); + int64_t now = fenster_time(); + while (fenster_loop(&f) == 0) { + int has_keys = 0; + char s[32]; + char *p = s; + for (int i = 0; i < 128; i++) { + if (f.keys[i]) { + has_keys = 1; + *p++ = i; + } + } + *p = '\0'; + fenster_rect(&f, 8, 8, W, 100, 0); + fenster_text(&f, 8, 8, s, 4, 0xffffff); + if (has_keys) { + if (f.mod & 1) { + fenster_text(&f, 8, 40, "Ctrl", 4, 0xffffff); + } + if (f.mod & 2) { + fenster_text(&f, 8, 80, "Shift", 4, 0xffffff); + } + } + if (f.keys[27]) { + break; + } + int64_t time = fenster_time(); + if (time - now < 1000 / 60) { + fenster_sleep(time - now); + } + now = time; + } + fenster_close(&f); + return 0; +} + +static int run() { + (void) demo_minimal_framebuffer; + (void) demo_raster_graphics; + (void) demo_keys; + + /*return demo_minimal_framebuffer();*/ + return demo_raster_graphics(); + /*return demo_keys();*/ +} + +#if defined(_WIN32) +int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR pCmdLine, + int nCmdShow) { + (void)hInstance, (void)hPrevInstance, (void)pCmdLine, (void)nCmdShow; + return run(); +} +#else +int main() { return run(); } +#endif + diff --git a/fenster.h b/fenster.h new file mode 100644 index 0000000..c38b7bf --- /dev/null +++ b/fenster.h @@ -0,0 +1,302 @@ +#ifndef FENSTER_H +#define FENSTER_H + +#include + +#if defined(__APPLE__) +#include +#include +#include +#include +#elif defined(_WIN32) +#include +#else +#include +#include +#include +#include +#endif + +struct fenster { + const char *title; + const int width; + const int height; + uint32_t *buf; + int keys[256]; /* keys are mostly ASCII, but arrows are 17..20 */ + int mod; /* mod is 4 bits mask, ctrl=1, shift=2, alt=4, meta=8 */ +#if defined(__APPLE__) + id wnd; +#elif defined(_WIN32) + HWND hwnd; +#else + Display *dpy; + Window w; + GC gc; + XImage *img; +#endif +}; + +#define FENSTER_API static +FENSTER_API int fenster_open(struct fenster *f); +FENSTER_API int fenster_loop(struct fenster *f); +FENSTER_API void fenster_close(struct fenster *f); +FENSTER_API void fenster_sleep(int ms); +FENSTER_API int64_t fenster_time(); +#define fenster_pixel(f, x, y) ((f)->buf[((y) * (f)->width) + (x)]) + +#if defined(__APPLE__) +#define msg(r, o, s) ((r(*)(id, SEL))objc_msgSend)(o, sel_getUid(s)) +#define msg1(r, o, s, A, a) \ + ((r(*)(id, SEL, A))objc_msgSend)(o, sel_getUid(s), a) +#define msg2(r, o, s, A, a, B, b) \ + ((r(*)(id, SEL, A, B))objc_msgSend)(o, sel_getUid(s), a, b) +#define msg3(r, o, s, A, a, B, b, C, c) \ + ((r(*)(id, SEL, A, B, C))objc_msgSend)(o, sel_getUid(s), a, b, c) +#define msg4(r, o, s, A, a, B, b, C, c, D, d) \ + ((r(*)(id, SEL, A, B, C, D))objc_msgSend)(o, sel_getUid(s), a, b, c, d) + +#define cls(x) ((id)objc_getClass(x)) + +extern id const NSDefaultRunLoopMode; +extern id const NSApp; + +static void fenster_draw_rect(id v, SEL s, CGRect r) { + (void)r, (void)s; + struct fenster *f = (struct fenster *)objc_getAssociatedObject(v, "fenster"); + CGContextRef context = + msg(CGContextRef, msg(id, cls("NSGraphicsContext"), "currentContext"), + "graphicsPort"); + CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB(); + CGDataProviderRef provider = CGDataProviderCreateWithData( + NULL, f->buf, f->width * f->height * 4, NULL); + CGImageRef img = + CGImageCreate(f->width, f->height, 8, 32, f->width * 4, space, + kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Little, + provider, NULL, false, kCGRenderingIntentDefault); + CGColorSpaceRelease(space); + CGDataProviderRelease(provider); + CGContextDrawImage(context, CGRectMake(0, 0, f->width, f->height), img); + CGImageRelease(img); +} + +static BOOL fenster_should_close(id v, SEL s, id w) { + (void)v, (void)s, (void)w; + msg1(void, NSApp, "terminate:", id, NSApp); + return YES; +} + +FENSTER_API int fenster_open(struct fenster *f) { + msg(id, cls("NSApplication"), "sharedApplication"); + msg1(void, NSApp, "setActivationPolicy:", NSInteger, 0); + f->wnd = msg4(id, msg(id, cls("NSWindow"), "alloc"), + "initWithContentRect:styleMask:backing:defer:", CGRect, + CGRectMake(0, 0, f->width, f->height), NSUInteger, 3, + NSUInteger, 2, BOOL, NO); + Class windelegate = + objc_allocateClassPair((Class)cls("NSObject"), "FensterDelegate", 0); + class_addMethod(windelegate, sel_getUid("windowShouldClose:"), + (IMP)fenster_should_close, "c@:@"); + objc_registerClassPair(windelegate); + msg1(void, f->wnd, "setDelegate:", id, + msg(id, msg(id, (id)windelegate, "alloc"), "init")); + Class c = objc_allocateClassPair((Class)cls("NSView"), "FensterView", 0); + class_addMethod(c, sel_getUid("drawRect:"), (IMP)fenster_draw_rect, "i@:@@"); + objc_registerClassPair(c); + + id v = msg(id, msg(id, (id)c, "alloc"), "init"); + msg1(void, f->wnd, "setContentView:", id, v); + objc_setAssociatedObject(v, "fenster", (id)f, OBJC_ASSOCIATION_ASSIGN); + + id title = msg1(id, cls("NSString"), "stringWithUTF8String:", const char *, + f->title); + msg1(void, f->wnd, "setTitle:", id, title); + msg1(void, f->wnd, "makeKeyAndOrderFront:", id, nil); + msg(void, f->wnd, "center"); + msg1(void, NSApp, "activateIgnoringOtherApps:", BOOL, YES); + return 0; +} + +FENSTER_API void fenster_close(struct fenster *f) { + msg(void, f->wnd, "close"); +} + +// clang-format off +static const uint8_t FENSTER_KEYCODES[128] = { +65,83,68,70,72,71,90,88,67,86,0,66,81,87,69,82,89,84,49,50,51,52,54,53,61,57,55,45,56,48,93,79,85,91,73,80,10,76,74,39,75,59,92,44,47,78,77,46,9,32,96,8,0,27,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,26,2,3,127,0,5,0,4,0,20,19,18,17,0}; +// clang-format on +FENSTER_API int fenster_loop(struct fenster *f) { + msg1(void, msg(id, f->wnd, "contentView"), "setNeedsDisplay:", BOOL, YES); + id ev = msg4(id, NSApp, + "nextEventMatchingMask:untilDate:inMode:dequeue:", NSUInteger, + NSUIntegerMax, id, NULL, id, NSDefaultRunLoopMode, BOOL, YES); + if (!ev) { + return 0; + } + NSUInteger evtype = msg(NSUInteger, ev, "type"); + switch (evtype) { + case 10: /*NSEventTypeKeyDown*/ + case 11: /*NSEventTypeKeyUp:*/ { + NSUInteger k = msg(NSUInteger, ev, "keyCode"); + f->keys[k < 127 ? FENSTER_KEYCODES[k] : 0] = evtype == 10; + NSUInteger mod = msg(NSUInteger, ev, "modifierFlags") >> 17; + f->mod = (mod & 0xc) | ((mod & 1) << 1) | ((mod >> 1) & 1); + return 0; + } + } + msg1(void, NSApp, "sendEvent:", id, ev); + return 0; +} +#elif defined(_WIN32) +// clang-format off +static const uint8_t FENSTER_KEYCODES[] = { +0,27,49,50,51,52,53,54,55,56,57,48,45,61,8,9,81,87,69,82,84,89,85,73,79,80,91,93,10,0,65,83,68,70,71,72,74,75,76,59,39,96,0,92,90,88,67,86,66,78,77,44,46,47,0,0,0,32,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,17,3,0,20,0,19,0,5,18,4,26,127}; +// clang-format on +static LRESULT CALLBACK fenster_wndproc(HWND hwnd, UINT msg, WPARAM wParam, + LPARAM lParam) { + struct fenster *f = (struct fenster *)GetWindowLongPtr(hwnd, GWLP_USERDATA); + switch (msg) { + case WM_PAINT: { + PAINTSTRUCT ps; + HDC hdc = BeginPaint(hwnd, &ps); + HDC memdc = CreateCompatibleDC(hdc); + HBITMAP hbmp = CreateCompatibleBitmap(hdc, f->width, f->height); + HBITMAP oldbmp = SelectObject(memdc, hbmp); + BITMAPINFO bi = {{sizeof(bi), f->width, -f->height, 1, 32, BI_BITFIELDS}}; + bi.bmiColors[0].rgbRed = 0xff; + bi.bmiColors[1].rgbGreen = 0xff; + bi.bmiColors[2].rgbBlue = 0xff; + SetDIBitsToDevice(memdc, 0, 0, f->width, f->height, 0, 0, 0, f->height, + f->buf, (BITMAPINFO *)&bi, DIB_RGB_COLORS); + BitBlt(hdc, 0, 0, f->width, f->height, memdc, 0, 0, SRCCOPY); + SelectObject(memdc, oldbmp); + DeleteObject(hbmp); + DeleteDC(memdc); + EndPaint(hwnd, &ps); + } break; + case WM_CLOSE: + DestroyWindow(hwnd); + break; + case WM_KEYDOWN: + case WM_KEYUP: { + f->mod = ((GetKeyState(VK_CONTROL) & 0x8000) >> 15) | + ((GetKeyState(VK_SHIFT) & 0x8000) >> 14) | + ((GetKeyState(VK_MENU) & 0x8000) >> 13) | + (((GetKeyState(VK_LWIN) | GetKeyState(VK_RWIN)) & 0x8000) >> 12); + f->keys[FENSTER_KEYCODES[HIWORD(lParam) & 0x1ff]] = !((lParam >> 31) & 1); + } break; + case WM_DESTROY: + PostQuitMessage(0); + break; + /* TODO: process other events */ + default: + return DefWindowProc(hwnd, msg, wParam, lParam); + } + return 0; +} + +FENSTER_API int fenster_open(struct fenster *f) { + HINSTANCE hInstance = GetModuleHandle(NULL); + WNDCLASSEX wc = {0}; + wc.cbSize = sizeof(WNDCLASSEX); + wc.style = CS_VREDRAW | CS_HREDRAW; + wc.lpfnWndProc = fenster_wndproc; + wc.hInstance = hInstance; + wc.lpszClassName = f->title; + RegisterClassEx(&wc); + f->hwnd = CreateWindowEx(WS_EX_CLIENTEDGE, f->title, f->title, + WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, + f->width, f->height, NULL, NULL, hInstance, NULL); + + if (f->hwnd == NULL) { + return -1; + } + SetWindowLongPtr(f->hwnd, GWLP_USERDATA, (LONG_PTR)f); + ShowWindow(f->hwnd, SW_NORMAL); + UpdateWindow(f->hwnd); + return 0; +} + +FENSTER_API void fenster_close(struct fenster *f) { (void)f; } + +FENSTER_API int fenster_loop(struct fenster *f) { + MSG msg; + while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { + if (msg.message == WM_QUIT) { + return -1; + } + TranslateMessage(&msg); + DispatchMessage(&msg); + } + InvalidateRect(f->hwnd, NULL, TRUE); + return 0; +} +#else +// clang-format off +static int FENSTER_KEYCODES[124] = { +XK_BackSpace,8,XK_Delete,127,XK_Down,18,XK_End,5,XK_Escape,27,XK_Home,2,XK_Insert,26,XK_Left,20,XK_Page_Down,4,XK_Page_Up,3,XK_Return,10,XK_Right,19,XK_Tab,9,XK_Up,17,XK_apostrophe,39,XK_backslash,92,XK_bracketleft,91,XK_bracketright,93,XK_comma,44,XK_equal,61,XK_grave,96,XK_minus,45,XK_period,46,XK_semicolon,59,XK_slash,47,XK_space,32,XK_a,65,XK_b,66,XK_c,67,XK_d,68,XK_e,69,XK_f,70,XK_g,71,XK_h,72,XK_i,73,XK_j,74,XK_k,75,XK_l,76,XK_m,77,XK_n,78,XK_o,79,XK_p,80,XK_q,81,XK_r,82,XK_s,83,XK_t,84,XK_u,85,XK_v,86,XK_w,87,XK_x,88,XK_y,89,XK_z,90,XK_0,48,XK_1,49,XK_2,50,XK_3,51,XK_4,52,XK_5,53,XK_6,54,XK_7,55,XK_8,56,XK_9,57}; +// clang-format on +FENSTER_API int fenster_open(struct fenster *f) { + f->dpy = XOpenDisplay(NULL); + int screen = DefaultScreen(f->dpy); + f->w = XCreateSimpleWindow(f->dpy, RootWindow(f->dpy, screen), 0, 0, f->width, + f->height, 0, BlackPixel(f->dpy, screen), + WhitePixel(f->dpy, screen)); + f->gc = XCreateGC(f->dpy, f->w, 0, 0); + XSelectInput(f->dpy, f->w, ExposureMask | KeyPressMask | KeyReleaseMask); + XStoreName(f->dpy, f->w, f->title); + XMapWindow(f->dpy, f->w); + XSync(f->dpy, f->w); + f->img = XCreateImage(f->dpy, DefaultVisual(f->dpy, 0), 24, ZPixmap, 0, + (char *)f->buf, f->width, f->height, 32, 0); + return 0; +} +FENSTER_API void fenster_close(struct fenster *f) { XCloseDisplay(f->dpy); } +FENSTER_API int fenster_loop(struct fenster *f) { + XEvent ev; + XPutImage(f->dpy, f->w, f->gc, f->img, 0, 0, 0, 0, f->width, f->height); + XFlush(f->dpy); + while (XPending(f->dpy)) { + XNextEvent(f->dpy, &ev); + switch (ev.type) { + case KeyPress: + case KeyRelease: { + int m = ev.xkey.state; + int k = XkbKeycodeToKeysym(f->dpy, ev.xkey.keycode, 0, 0); + for (unsigned int i = 0; i < 124; i += 2) { + if (FENSTER_KEYCODES[i] == k) { + f->keys[FENSTER_KEYCODES[i + 1]] = (ev.type == KeyPress); + break; + } + } + f->mod = !!(m & ControlMask) | (!!(m & ShiftMask) << 1) | + (!!(m & Mod1Mask) << 2) | (!!(m & Mod4Mask) << 3); + } break; + } + } + return 0; +} +#endif + +#ifdef WIN32 +FENSTER_API void fenster_sleep(int ms) { Sleep(ms); } +FENSTER_API int64_t fenster_time() { + LARGE_INTEGER freq, count; + QueryPerformanceFrequency(&freq); + QueryPerformanceCounter(&count); + return (int64_t)(count.QuadPart * 1000.0 / freq.QuadPart); +} +#else +FENSTER_API void fenster_sleep(int ms) { + struct timespec ts; + ts.tv_sec = ms / 1000; + ts.tv_nsec = (ms % 1000) * 1000000; + nanosleep(&ts, NULL); +} +FENSTER_API int64_t fenster_time() { + struct timespec time; + clock_gettime(CLOCK_REALTIME, &time); + return time.tv_sec * 1000 + (time.tv_nsec / 1000000); +} +#endif + +#endif /* FENSTER_H */