diff options
Diffstat (limited to 'kon/konwinlib.h')
| -rw-r--r-- | kon/konwinlib.h | 1357 |
1 files changed, 1357 insertions, 0 deletions
diff --git a/kon/konwinlib.h b/kon/konwinlib.h new file mode 100644 index 0000000..c50f828 --- /dev/null +++ b/kon/konwinlib.h @@ -0,0 +1,1357 @@ +/* SPDX-License-Identifier: MIT */ +/* + * konwinlib.h + * + * Copyright (c) 2026 KonAki + * + * 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. + */ + +#ifndef KONWINLIB_H +#define KONWINLIB_H + +#include <stdint.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> + +/*** definitions ***/ + +typedef struct kon_context kon_context_t; + +bool kon_init(void); +void kon_deinit(void); + +typedef struct kon_window kon_window_t; + +typedef enum kon_windowFlags { + KON_WINDOW_NONE = 0, + KON_WINDOW_TRANSPARENT = 1 << 0, + KON_WINDOW_NO_DECOR = 1 << 1, + KON_WINDOW_ALWAYS_ONTOP = 1 << 2, + KON_WINDOW_CENTER = 1 << 3, + KON_WINDOW_RESIZABLE = 1 << 4 +} kon_windowFlags_t; + +typedef enum kon_eventType { + KON_EVENT_NONE = 0, + KON_EVENT_CLOSE, + KON_EVENT_RESIZE, + KON_EVENT_KEY_DOWN, + KON_EVENT_KEY_UP, + KON_EVENT_MOUSE_DOWN, + KON_EVENT_MOUSE_UP, + KON_EVENT_MOUSE_MOVE +} kon_eventType_t; + +typedef enum kon_mouseButton { + KON_MOUSE_LEFT = 0, + KON_MOUSE_MIDDLE, + KON_MOUSE_RIGHT +} kon_mouseButton_t; + +typedef enum kon_key { + KON_KEY_UNKNOWN = 0, + + KON_KEY_A, KON_KEY_B, KON_KEY_C, KON_KEY_D, KON_KEY_E, KON_KEY_F, KON_KEY_G, + KON_KEY_H, KON_KEY_I, KON_KEY_J, KON_KEY_K, KON_KEY_L, KON_KEY_M, KON_KEY_N, KON_KEY_O, KON_KEY_P, + KON_KEY_Q, KON_KEY_R, KON_KEY_S, KON_KEY_T, KON_KEY_U, KON_KEY_V, + KON_KEY_W, KON_KEY_X, KON_KEY_Y, KON_KEY_Z, + + KON_KEY_0, KON_KEY_1, KON_KEY_2, KON_KEY_3, KON_KEY_4, KON_KEY_5, KON_KEY_6, KON_KEY_7, KON_KEY_8, KON_KEY_9, + + KON_KEY_F1, KON_KEY_F2, KON_KEY_F3, KON_KEY_F4, KON_KEY_F5, KON_KEY_F6, + KON_KEY_F7, KON_KEY_F8, KON_KEY_F9, KON_KEY_F10, KON_KEY_F11, KON_KEY_F12, + + KON_KEY_LEFT, KON_KEY_RIGHT, KON_KEY_UP, KON_KEY_DOWN, + + KON_KEY_SPACE, KON_KEY_ENTER, KON_KEY_TAB, KON_KEY_BACKSPACE, + KON_KEY_ESCAPE, KON_KEY_DELETE, + + KON_KEY_LEFT_SHIFT, KON_KEY_RIGHT_SHIFT, + KON_KEY_LEFT_CTRL, KON_KEY_RIGHT_CTRL, + KON_KEY_LEFT_ALT, KON_KEY_RIGHT_ALT, + + KON_KEY_MINUS, KON_KEY_EQUAL, + KON_KEY_LEFT_BRACKET, KON_KEY_RIGHT_BRACKET, + KON_KEY_SEMICOLON, KON_KEY_APOSTROPHE, + KON_KEY_COMMA, KON_KEY_PERIOD, KON_KEY_SLASH, KON_KEY_BACKSLASH, + KON_KEY_GRAVE, + + KON_KEY_COUNT +} kon_key_t; + +typedef struct kon_event { + kon_eventType_t type; + int width, height; /* will be used to store data for resize events */ + int key; + int mouseX, mouseY; + kon_mouseButton_t mouseButton; +} kon_event_t; + +kon_window_t *kon_createWindow(const char *title, int x, int y, int width, int height, kon_windowFlags_t flags); +void kon_destroyWindow(kon_window_t *window); + +bool kon_windowShouldClose(kon_window_t *window); + +void kon_setExitKey(kon_window_t *window, int key); + +void kon_setWindowPos(kon_window_t *window, int x, int y); +void kon_setWindowSize(kon_window_t *window, int width, int height); + +void kon_getWindowPos(kon_window_t *window, int *x, int *y); +void kon_getWindowSize(kon_window_t *window, int *width, int *height); + +int kon_pollEvent(kon_window_t *window, kon_event_t *event); +void kon_blitPixels(kon_window_t *window, const uint32_t *pixels, int width, int height); + +// added these for web dev +typedef void (*kon_mainLoopFn)(void *userData); +void kon_runMainLoop(kon_window_t *window, kon_mainLoopFn callback, void *userData); + +/*** implementation ***/ + +#ifdef KONWINLIB_IMPLEMENTATION + +static kon_context_t *kon_ctx; + +#define KON_EVENT_QUEUE_SIZE 256 + +typedef struct kon_eventQueue { + kon_event_t events[KON_EVENT_QUEUE_SIZE]; + int head, count; +} kon_eventQueue_t; + +static void kon_pushEvent_(kon_eventQueue_t *q, const kon_event_t *event) { + /* only the newest mouse move or resize matters, so overwrite instead of piling up */ + if (q->count > 0 && (event->type == KON_EVENT_MOUSE_MOVE || event->type == KON_EVENT_RESIZE)) { + kon_event_t *last = &q->events[(q->head + q->count - 1) % KON_EVENT_QUEUE_SIZE]; + if (last->type == event->type) { + *last = *event; + return; + } + } + + if (q->count == KON_EVENT_QUEUE_SIZE) return; + q->events[(q->head + q->count) % KON_EVENT_QUEUE_SIZE] = *event; + q->count++; +} + +static bool kon_popEvent_(kon_eventQueue_t *q, kon_event_t *event) { + if (q->count == 0) return false; + *event = q->events[q->head]; + q->head = (q->head + 1) % KON_EVENT_QUEUE_SIZE; + q->count--; + return true; +} + +#ifdef __EMSCRIPTEN__ + +#include <emscripten/emscripten.h> +#include <emscripten/html5.h> +#include <string.h> + +struct kon_context { + int initialized; +}; + +struct kon_window { + char canvasID[64]; + bool shouldClose; + int exitKey; + + bool hasKeyEvent; + kon_eventType_t keyEventType; + int keyEventKey; + + bool hasResizeEvent; + int resizeWidth, resizeHeight; + + bool hasMouseEvent; + kon_eventType_t mouseEventType; + int mouseEventX, mouseEventY; + kon_mouseButton_t mouseEventButton; +}; + +static kon_key_t kon_translateEmKey_(int keyCode) { + switch (keyCode) { + case 65: return KON_KEY_A; + case 66: return KON_KEY_B; + case 67: return KON_KEY_C; + case 68: return KON_KEY_D; + case 69: return KON_KEY_E; + case 70: return KON_KEY_F; + case 71: return KON_KEY_G; + case 72: return KON_KEY_H; + case 73: return KON_KEY_I; + case 74: return KON_KEY_J; + case 75: return KON_KEY_K; + case 76: return KON_KEY_L; + case 77: return KON_KEY_M; + case 78: return KON_KEY_N; + case 79: return KON_KEY_O; + case 80: return KON_KEY_P; + case 81: return KON_KEY_Q; + case 82: return KON_KEY_R; + case 83: return KON_KEY_S; + case 84: return KON_KEY_T; + case 85: return KON_KEY_U; + case 86: return KON_KEY_V; + case 87: return KON_KEY_W; + case 88: return KON_KEY_X; + case 89: return KON_KEY_Y; + case 90: return KON_KEY_Z; + + case 48: return KON_KEY_0; + case 49: return KON_KEY_1; + case 50: return KON_KEY_2; + case 51: return KON_KEY_3; + case 52: return KON_KEY_4; + case 53: return KON_KEY_5; + case 54: return KON_KEY_6; + case 55: return KON_KEY_7; + case 56: return KON_KEY_8; + case 57: return KON_KEY_9; + + case 112: return KON_KEY_F1; + case 113: return KON_KEY_F2; + case 114: return KON_KEY_F3; + case 115: return KON_KEY_F4; + case 116: return KON_KEY_F5; + case 117: return KON_KEY_F6; + case 118: return KON_KEY_F7; + case 119: return KON_KEY_F8; + case 120: return KON_KEY_F9; + case 121: return KON_KEY_F10; + case 122: return KON_KEY_F11; + case 123: return KON_KEY_F12; + + case 37: return KON_KEY_LEFT; + case 39: return KON_KEY_RIGHT; + case 38: return KON_KEY_UP; + case 40: return KON_KEY_DOWN; + + case 32: return KON_KEY_SPACE; + case 13: return KON_KEY_ENTER; + case 9: return KON_KEY_TAB; + case 8: return KON_KEY_BACKSPACE; + case 27: return KON_KEY_ESCAPE; + case 46: return KON_KEY_DELETE; + + case 16: return KON_KEY_LEFT_SHIFT; + case 17: return KON_KEY_LEFT_CTRL; + case 18: return KON_KEY_LEFT_ALT; + + case 189: return KON_KEY_MINUS; + case 187: return KON_KEY_EQUAL; + case 219: return KON_KEY_LEFT_BRACKET; + case 221: return KON_KEY_RIGHT_BRACKET; + case 186: return KON_KEY_SEMICOLON; + case 222: return KON_KEY_APOSTROPHE; + case 188: return KON_KEY_COMMA; + case 190: return KON_KEY_PERIOD; + case 191: return KON_KEY_SLASH; + case 220: return KON_KEY_BACKSLASH; + case 192: return KON_KEY_GRAVE; + + default: return KON_KEY_UNKNOWN; + } +} +bool kon_init(void) { + kon_context_t *ctx = malloc(sizeof(kon_context_t)); + if (!ctx) return false; + + ctx->initialized = 1; + kon_ctx = ctx; + return true; +} + +void kon_deinit(void) { + if (!kon_ctx) return; + + free(kon_ctx); + kon_ctx = NULL; +} + +static EM_BOOL kon_keyDownCallback_(int eventType, const EmscriptenKeyboardEvent *e, void *userData) { + (void)eventType; + kon_window_t *window = (kon_window_t *)userData; + + window->hasKeyEvent = true; + window->keyEventType = KON_EVENT_KEY_DOWN; + window->keyEventKey = kon_translateEmKey_(e->keyCode); + + if (window->exitKey != 0 && window->keyEventKey == window->exitKey) { + window->shouldClose = true; + } + + return EM_TRUE; +} + +static EM_BOOL kon_keyUpCallback_(int eventType, const EmscriptenKeyboardEvent *e, void *userData) { + (void)eventType; + kon_window_t *window = (kon_window_t *)userData; + + window->hasKeyEvent = true; + window->keyEventType = KON_EVENT_KEY_UP; + window->keyEventKey = kon_translateEmKey_(e->keyCode); + + return EM_TRUE; +} + +static EM_BOOL kon_mouseDownCallback_(int eventType, const EmscriptenMouseEvent *e, void *userData) { + (void)eventType; + kon_window_t *window = (kon_window_t *)userData; + + if (e->button > KON_MOUSE_RIGHT) return EM_FALSE; + + window->hasMouseEvent = true; + window->mouseEventType = KON_EVENT_MOUSE_DOWN; + window->mouseEventButton = (kon_windowButton_t)e->button; + window->mouseEventX = e->targetX; + window->mouseEventY = e->targetY; + + return EM_TRUE; +} + +static EM_BOOL kon_mouseUpCallback_(int eventType, const EmscriptenMouseEvent *e, void *userData) { + (void)eventType; + kon_window_t *window = (kon_window_t *)userData; + + if (e->button > KON_MOUSE_RIGHT) return EM_FALSE: + + window->hasMouseEvent = true; + window->mouseEventType = KON_EVENT_MOUSE_UP; + window->mouseEventButton = (kon_windowButton_t)e->button; + window->mouseEventX = e->targetX; + window->mouseEventY = e->targetY; + + return EM_TRUE; +} + +static EM_BOOL kon_mouseMoveCallback_(int eventType, const EmscriptenMouseEvent *e, void *userData) { + (void)eventType; + kon_window_t *window = (kon_window_t *)userData; + + window->hasMouseEvent = true; + window->mouseEventType = KON_EVENT_MOUSE_MOVE; + window->mouseEventX = e->targetX; + window->mouseEventY = e->targetY; + + return EM_TRUE; +} + +kon_window_t *kon_createWindow(const char *title, int x, int y, int width, int height, kon_windowFlags_t flags) { + (void)x; (void)y; (void)flags; + + if (!kon_ctx) { + fprintf(stderr, "kon_createWindow: error creating window, no ctx\n"); + return NULL; + } + + kon_window_t *window = malloc(sizeof(kon_window_t)); + if (!window) return NULL; + + strncpy(window->canvasID, "#canvas", sizeof(window->canvasID) - 1); + window->canvasID[sizeof(window->canvasID) - 1] = '\0'; + + window->shouldClose = false; + window->exitKey = 0; + window->hasKeyEvent = false; + window->hasResizeEvent = false; + window->hasMouseEvent = false; + + emscripten_set_canvas_element_size(window->canvasID, width, height); + + EM_ASM({ + try { document.title = UTF8ToString($0); } catch (e) {} + }, title); + + emscripten_set_keydown_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, window, EM_FALSE, kon_keyDownCallback_); + emscripten_set_keyup_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, window, EM_FALSE, kon_keyUpCallback_); + emscripten_set_mousedown_callback(window->canvasID, window, EM_FALSE, kon_mouseDownCallback_); + emscripten_set_mouseup_callback(window->canvasID, window, EM_FALSE, kon_mouseUpCallback_); + emscripten_set_mousemove_callback(window->canvasID, window, EM_FALSE, kon_mouseMoveCallback_); + + return window; +} + +void kon_destroyWindow(kon_window_t *window) { + if (!window) return; + + emscripten_set_keydown_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, EM_FALSE, NULL); + emscripten_set_keyup_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, EM_FALSE, NULL); + emscripten_set_mousedown_callback(window->canvasID, NULL, EM_FALSE, NULL); + emscripten_set_mouseup_callback(window->canvasID, NULL, EM_FALSE, NULL); + emscripten_set_mousemove_callback(window->canvasID, NULL, EM_FALSE, NULL); + + free(window); +} + +bool kon_windowShouldClose(kon_window_t *window) { + if (!window) return true; + return window->shouldClose; +} + +void kon_setExitKey(kon_window_t *window, int key) { + if (!window) return; + window->exitKey = key; +} + +void kon_setWindowPos(kon_window_t *window, int x, int y) { + (void)window; (void)x; (void)y; +} + +void kon_setWindowSize(kon_window_t *window, int width, int height) { + if (!window) return; + emscripten_set_canvas_element_size(window->canvasID, width, height); +} + +void kon_getWindowPos(kon_window_t *window, int *x, int *y) { + (void)window; + if (x) *x = 0; + if (y) *y = 0; +} + +void kon_getWindowSize(kon_window_t *window, int *width, int *height) { + if (!window || !width || !height) return; + + double w, h; + emscripten_get_element_css_size(window->canvasID, &w, &h); + *width = (int)w; + *height = (int)h; +} + +int kon_pollEvent(kon_window_t *window, kon_event_t *event) { + if (!window || !event) return 0; + + event->type = KON_EVENT_NONE; + + if (window->hasResizeEvent) { + event->type = KON_EVENT_RESIZE; + event->width = window->resizeWidth; + event->height = window->resizeHeight; + window->hasResizeEvent = false; + return 1; + } + + if (window->hasKeyEvent) { + event->type = window->keyEventType; + event->key = window->keyEventKey; + window->hasKeyEvent = false; + return 1; + } + + if (window->hasMouseEvent) { + event->type = window->mouseEventType; + event->mouseX = window->mouseEventX; + event->mouseY = window-<mouseEventY; + event->mouseButton = window->mouseEventButton; + window->hasMouseEvent = false; + return 1; + } + + return 0; +} + +EM_JS(void, kon_jsBlit_, (const char *canvasID, uint32_t *pixels, int width, int height), { + var sel = UTF8ToString(canvasID); + var canvas = document.querySelector(sel); + if (!canvas) return; + + if (!canvas.__konCtx) canvas.__konCtx = canvas.getContext('2d'); + var ctx = canvas.__konCtx; + + if (!canvas.__konImgData || canvas.__konImgData.width !== width || canvas.__konImgData.height !== height) { + canvas.__konImgData = ctx.createImageData(width, height); + } + var imgData = canvas.__konImgData; + + var dst = new Uint32Array(imgData.data.buffer); + var srcIdx = pixels >> 2; + var count = width * height; + + for (var i = 0; i < count; i++) { + var px = HEAPU32[srcIdx + i]; + dst[i] = (px & 0xFF00FF00) | ((px & 0x00FF0000) >>> 16) | ((px & 0x000000FF) << 16); + } + + ctx.putImageData(imgData, 0, 0); +}); + +void kon_blitPixels(kon_window_t *window, const uint32_t *pixels, int width, int height) { + if (!window || !pixels) return; + kon_jsBlit_(window->canvasID, (uint32_t *)pixels, width, height); +} + +#elif defined(__linux__) || defined(__unix__) + +#include <X11/Xlib.h> +#include <X11/Xutil.h> +#include <X11/Xatom.h> +#include <X11/extensions/Xrandr.h> +#include <X11/keysym.h> +#include <string.h> + +static kon_key_t kon_translateX11Key_(KeySym sym) { + switch (sym) { + case XK_a: return KON_KEY_A; + case XK_b: return KON_KEY_B; + case XK_c: return KON_KEY_C; + case XK_d: return KON_KEY_D; + case XK_e: return KON_KEY_E; + case XK_f: return KON_KEY_F; + case XK_g: return KON_KEY_G; + case XK_h: return KON_KEY_H; + case XK_i: return KON_KEY_I; + case XK_j: return KON_KEY_J; + case XK_k: return KON_KEY_K; + case XK_l: return KON_KEY_L; + case XK_m: return KON_KEY_M; + case XK_n: return KON_KEY_N; + case XK_o: return KON_KEY_O; + case XK_p: return KON_KEY_P; + case XK_q: return KON_KEY_Q; + case XK_r: return KON_KEY_R; + case XK_s: return KON_KEY_S; + case XK_t: return KON_KEY_T; + case XK_u: return KON_KEY_U; + case XK_v: return KON_KEY_V; + case XK_w: return KON_KEY_W; + case XK_x: return KON_KEY_X; + case XK_y: return KON_KEY_Y; + case XK_z: return KON_KEY_Z; + + case XK_0: return KON_KEY_0; + case XK_1: return KON_KEY_1; + case XK_2: return KON_KEY_2; + case XK_3: return KON_KEY_3; + case XK_4: return KON_KEY_4; + case XK_5: return KON_KEY_5; + case XK_6: return KON_KEY_6; + case XK_7: return KON_KEY_7; + case XK_8: return KON_KEY_8; + case XK_9: return KON_KEY_9; + + case XK_F1: return KON_KEY_F1; + case XK_F2: return KON_KEY_F2; + case XK_F3: return KON_KEY_F3; + case XK_F4: return KON_KEY_F4; + case XK_F5: return KON_KEY_F5; + case XK_F6: return KON_KEY_F6; + case XK_F7: return KON_KEY_F7; + case XK_F8: return KON_KEY_F8; + case XK_F9: return KON_KEY_F9; + case XK_F10: return KON_KEY_F10; + case XK_F11: return KON_KEY_F11; + case XK_F12: return KON_KEY_F12; + + case XK_Left: return KON_KEY_LEFT; + case XK_Right: return KON_KEY_RIGHT; + case XK_Up: return KON_KEY_UP; + case XK_Down: return KON_KEY_DOWN; + + case XK_space: return KON_KEY_SPACE; + case XK_Return: return KON_KEY_ENTER; + case XK_Tab: return KON_KEY_TAB; + case XK_BackSpace: return KON_KEY_BACKSPACE; + case XK_Escape: return KON_KEY_ESCAPE; + case XK_Delete: return KON_KEY_DELETE; + + case XK_Shift_L: return KON_KEY_LEFT_SHIFT; + case XK_Shift_R: return KON_KEY_RIGHT_SHIFT; + case XK_Control_L: return KON_KEY_LEFT_CTRL; + case XK_Control_R: return KON_KEY_RIGHT_CTRL; + case XK_Alt_L: return KON_KEY_LEFT_ALT; + case XK_Alt_R: return KON_KEY_RIGHT_ALT; + + case XK_minus: return KON_KEY_MINUS; + case XK_equal: return KON_KEY_EQUAL; + case XK_bracketleft: return KON_KEY_LEFT_BRACKET; + case XK_bracketright: return KON_KEY_RIGHT_BRACKET; + case XK_semicolon: return KON_KEY_SEMICOLON; + case XK_apostrophe: return KON_KEY_APOSTROPHE; + case XK_comma: return KON_KEY_COMMA; + case XK_period: return KON_KEY_PERIOD; + case XK_slash: return KON_KEY_SLASH; + case XK_backslash: return KON_KEY_BACKSLASH; + case XK_grave: return KON_KEY_GRAVE; + + default: return KON_KEY_UNKNOWN; + } +} + +struct kon_context { + Display *display; +}; + +bool kon_init(void) { + kon_context_t *ctx = malloc(sizeof(kon_context_t)); + if (!ctx) return false; + ctx->display = XOpenDisplay(NULL); + if (!ctx->display) return false; + kon_ctx = ctx; + return true; +} + +void kon_deinit(void) { + if (!kon_ctx) return; + + XCloseDisplay(kon_ctx->display); + free(kon_ctx); + kon_ctx=NULL; +} + +struct kon_window { + Window window; + GC gc; + Visual *visual; + int depth; + Colormap colormap; + bool owns_colormap; + Atom wm_delete; + bool shouldClose; + int exitKey; +}; + +kon_window_t *kon_createWindow(const char *title, int x, int y, int width, int height, kon_windowFlags_t flags) { + if (!kon_ctx) { + fprintf(stderr, "kon_createWindow: There appears to be no kon_ctx. \nPlease, do kon_init(); before doing anything with konwinlib, thank you.\n"); + return NULL; + } + + kon_window_t *window = malloc(sizeof(kon_window_t)); + if (!window) return NULL; + + int screen = DefaultScreen(kon_ctx->display); + + if (flags & KON_WINDOW_CENTER) { + Window root = RootWindow(kon_ctx->display, screen); + + int mon_x = 0, mon_y = 0; + int mon_width = DisplayWidth(kon_ctx->display, screen); + int mon_height = DisplayHeight(kon_ctx->display, screen); + + Window root_ret, child_ret; + int root_x, root_y, win_x, win_y; + unsigned int mask; + XQueryPointer(kon_ctx->display, root, &root_ret, &child_ret, &root_x, &root_y, &win_x, &win_y, &mask); + + XRRScreenResources *res = XRRGetScreenResourcesCurrent(kon_ctx->display, root); + if (res) { + for (int i = 0; i < res->noutput; i++) { + XRROutputInfo *out_info = XRRGetOutputInfo(kon_ctx->display, res, res->outputs[i]); + + if (out_info && out_info->crtc) { + XRRCrtcInfo *crtc = XRRGetCrtcInfo(kon_ctx->display, res, out_info->crtc); + if (crtc) { + + if (root_x >= crtc->x && root_x < crtc->x + (int)crtc->width && + root_y >= crtc->y && root_y < crtc->y + (int)crtc->height) { + mon_x = crtc->x; + mon_y = crtc->y; + mon_width = crtc->width; + mon_height = crtc->height; + XRRFreeCrtcInfo(crtc); + if (out_info) XRRFreeOutputInfo(out_info); + break; + } + XRRFreeCrtcInfo(crtc); + } + } + if (out_info) XRRFreeOutputInfo(out_info); + } + XRRFreeScreenResources(res); + } + + x = mon_x + (mon_width - width ) / 2; + y = mon_y + (mon_height - height) / 2; + } + + if (flags & KON_WINDOW_TRANSPARENT) { + XVisualInfo vinfo; + if (!XMatchVisualInfo(kon_ctx->display, screen, 32, TrueColor, &vinfo)) { + free(window); + return NULL; + } + + window->visual = vinfo.visual; + window->depth = vinfo.depth; + window->colormap = XCreateColormap(kon_ctx->display, RootWindow(kon_ctx->display, screen), window->visual, AllocNone); + + XSetWindowAttributes attrs; + attrs.colormap = window->colormap; + attrs.background_pixel = 0; + attrs.border_pixel = 0; + + window->window = XCreateWindow( + kon_ctx->display, RootWindow(kon_ctx->display, screen), + x, y, width, height, 0, window->depth, InputOutput, + window->visual, CWColormap | CWBackPixel | CWBorderPixel, &attrs); + + window->owns_colormap = true; + } else { + window->visual = DefaultVisual(kon_ctx->display, screen); + window->depth = DefaultDepth(kon_ctx->display, screen); + window->colormap = DefaultColormap(kon_ctx->display, screen); + + window->window = XCreateSimpleWindow( + kon_ctx->display, RootWindow(kon_ctx->display, screen), + x, y, width, height, 0, 0, 0); + + window->owns_colormap = false; + } + + if (!window->window) { + if (window->owns_colormap) { + XFreeColormap(kon_ctx->display, window->colormap); + } + free(window); + return NULL; + } + + XStoreName(kon_ctx->display, window->window, title); + XSelectInput(kon_ctx->display, window->window, ExposureMask | StructureNotifyMask | KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | PointerMotionMask); + + if (flags & KON_WINDOW_NO_DECOR) { + Atom motif_hints = XInternAtom(kon_ctx->display, "_MOTIF_WM_HINTS", False); + struct { unsigned long flags, functions, decorations; long input_mode; unsigned long status; } hints; + memset(&hints, 0, sizeof(hints)); + hints.flags = 2; + hints.decorations = 0; + XChangeProperty(kon_ctx->display, window->window, motif_hints, + motif_hints, 32, PropModeReplace, (unsigned char *)&hints, 5); + } + + if (flags & KON_WINDOW_ALWAYS_ONTOP) { + Atom wm_state = XInternAtom(kon_ctx->display, "_NET_WM_STATE", False); + Atom wm_above = XInternAtom(kon_ctx->display, "_NET_WM_STATE_ABOVE", False); + XChangeProperty(kon_ctx->display, window->window, wm_state, XA_ATOM, 32, + PropModeReplace, (unsigned char *)&wm_above, 1); + } + + if (!(flags & KON_WINDOW_RESIZABLE)) { + XSizeHints hints; + hints.flags = PMinSize | PMaxSize; + hints.min_width = hints.max_width = width; + hints.min_height = hints.max_height = height; + XSetWMNormalHints(kon_ctx->display, window->window, &hints); + } + + window->wm_delete = XInternAtom(kon_ctx->display, "WM_DELETE_WINDOW", False); + XSetWMProtocols(kon_ctx->display, window->window, &window->wm_delete, 1); + + window->gc = XCreateGC(kon_ctx->display, window->window, 0, NULL); + + XMapWindow(kon_ctx->display, window->window); + XFlush(kon_ctx->display); + + window->exitKey = 0; + + window->shouldClose = false; + + return window; +} + +void kon_destroyWindow(kon_window_t *window) { + if (!window) return; + if (!kon_ctx) { free(window); return; } + + if (window->gc) XFreeGC(kon_ctx->display, window->gc); + if (window->window) XDestroyWindow(kon_ctx->display, window->window); + if (window->owns_colormap && window->colormap) XFreeColormap(kon_ctx->display, window->colormap); + + XFlush(kon_ctx->display); + + free(window); +} + +void kon_setExitKey(kon_window_t *window, int key) { + if (!window) return; + window->exitKey = key; +} + +void kon_setWindowPos(kon_window_t *window, int x, int y) { + if (!window || !kon_ctx) return; + + XMoveWindow(kon_ctx->display, window->window, x, y); + XFlush(kon_ctx->display); +} + +void kon_setWindowSize(kon_window_t *window, int width, int height) { + if (!window || !kon_ctx) return; + + XResizeWindow(kon_ctx->display, window->window, width, height); + XFlush(kon_ctx->display); +} + +void kon_getWindowPos(kon_window_t *window, int *x, int *y) { + if (!window || !kon_ctx || !x || !y) return; + + Window child; + XTranslateCoordinates(kon_ctx->display, window->window, RootWindow(kon_ctx->display, DefaultScreen(kon_ctx->display)), + 0, 0, x, y, &child); +} + +void kon_getWindowSize(kon_window_t *window, int *width, int *height) { + if (!window || !kon_ctx || !width || !height) return; + + XWindowAttributes attrs; + XGetWindowAttributes(kon_ctx->display, window->window, &attrs); + *width = attrs.width; + *height = attrs.height; +} + +int kon_pollEvent(kon_window_t *window, kon_event_t *event) { + if (!window || !kon_ctx || !event) return 0; + + event->type = KON_EVENT_NONE; + + if (!XPending(kon_ctx->display)) return 0; + + XEvent xev; + XNextEvent(kon_ctx->display, &xev); + + if (xev.type == ClientMessage) { + if ((Atom)xev.xclient.data.l[0] == window->wm_delete) { + event->type = KON_EVENT_CLOSE; + window->shouldClose = true; + } + } + + if (xev.type == ConfigureNotify) { + event->type = KON_EVENT_RESIZE; + event->width = xev.xconfigure.width; + event->height = xev.xconfigure.height; + return 1; + } + + if (xev.type == KeyPress) { + event->type = KON_EVENT_KEY_DOWN; + event->key = (int)kon_translateX11Key_(XLookupKeysym(&xev.xkey, 0)); + if (window->exitKey != 0 && event->key == window->exitKey) { + window->shouldClose = true; + } + return 1; + } + + if (xev.type == KeyRelease) { + event->type = KON_EVENT_KEY_UP; + event->key = (int)kon_translateX11Key_(XLookupKeysym(&xev.xkey, 0)); + return 1; + } + + if (xev.type == ButtonPress || xev.type == ButtonRelease) { + int konButton = -1; + switch (xev.xbutton.button) { + case Button1: + konButton = KON_MOUSE_LEFT; + break; + case Button2: + konButton = KON_MOUSE_MIDDLE; + break; + case Button3: + konButton = KON_MOUSE_RIGHT; + break; + default: + break; + } + + if (konButton != -1) { + event->type = (xev.type == ButtonPress) ? KON_EVENT_MOUSE_DOWN : KON_EVENT_MOUSE_UP; + event->mouseButton = (kon_mouseButton_t)konButton; + event->mouseX = xev.xbutton.x; + event->mouseY = xev.xbutton.y; + } + return 1; + } + + if (xev.type == MotionNotify) { + event->type = KON_EVENT_MOUSE_MOVE; + event->mouseX = xev.xmotion.x; + event->mouseY = xev.xmotion.y; + return 1; + } + + return 1; +} + +bool kon_windowShouldClose(kon_window_t *window) { + if (!window || !kon_ctx) return true; + return window->shouldClose; +} + +/* TODO: implement the rest of these */ + +void kon_blitPixels(kon_window_t *window, const uint32_t *pixels, int width, int height) { + if (!window || !kon_ctx || !pixels) return; + + XImage *ximg = XCreateImage(kon_ctx->display, window->visual, window->depth, ZPixmap, 0, (char *)pixels, width, height, 32, 0); + if (!ximg) return; + + XPutImage(kon_ctx->display, window->window, window->gc, ximg, 0, 0, 0, 0, width, height); + + ximg->data = NULL; + XDestroyImage(ximg); + + XFlush(kon_ctx->display); +} + +/*** windows implementation ***/ + +#elif defined(_WIN32) + +#include <windows.h> +#include <string.h> + +struct kon_context { + HINSTANCE hInstance; +}; + +struct kon_window { + HWND hwnd; + bool shouldClose; + bool isTransparent; + int exitKey; + kon_eventQueue_t queue; +}; + +static kon_key_t kon_translateWin32Key_(WPARAM vk, LPARAM lParam) { + if (vk == VK_SHIFT) { + UINT scancode = (lParam >> 16) & 0xFF; + UINT lshift = MapVirtualKey(VK_LSHIFT, MAPVK_VK_TO_VSC); + return (scancode == lshift) ? KON_KEY_LEFT_SHIFT : KON_KEY_RIGHT_SHIFT; + } + if (vk == VK_CONTROL) { + return (lParam & 0x01000000) ? KON_KEY_RIGHT_CTRL : KON_KEY_LEFT_CTRL; + } + if (vk == VK_MENU) { + return (lParam & 0x01000000) ? KON_KEY_RIGHT_ALT : KON_KEY_LEFT_ALT; + } + + switch (vk) { + case 'A': return KON_KEY_A; + case 'B': return KON_KEY_B; + case 'C': return KON_KEY_C; + case 'D': return KON_KEY_D; + case 'E': return KON_KEY_E; + case 'F': return KON_KEY_F; + case 'G': return KON_KEY_G; + case 'H': return KON_KEY_H; + case 'I': return KON_KEY_I; + case 'J': return KON_KEY_J; + case 'K': return KON_KEY_K; + case 'L': return KON_KEY_L; + case 'M': return KON_KEY_M; + case 'N': return KON_KEY_N; + case 'O': return KON_KEY_O; + case 'P': return KON_KEY_P; + case 'Q': return KON_KEY_Q; + case 'R': return KON_KEY_R; + case 'S': return KON_KEY_S; + case 'T': return KON_KEY_T; + case 'U': return KON_KEY_U; + case 'V': return KON_KEY_V; + case 'W': return KON_KEY_W; + case 'X': return KON_KEY_X; + case 'Y': return KON_KEY_Y; + case 'Z': return KON_KEY_Z; + + case '0': return KON_KEY_0; + case '1': return KON_KEY_1; + case '2': return KON_KEY_2; + case '3': return KON_KEY_3; + case '4': return KON_KEY_4; + case '5': return KON_KEY_5; + case '6': return KON_KEY_6; + case '7': return KON_KEY_7; + case '8': return KON_KEY_8; + case '9': return KON_KEY_9; + + case VK_F1: return KON_KEY_F1; + case VK_F2: return KON_KEY_F2; + case VK_F3: return KON_KEY_F3; + case VK_F4: return KON_KEY_F4; + case VK_F5: return KON_KEY_F5; + case VK_F6: return KON_KEY_F6; + case VK_F7: return KON_KEY_F7; + case VK_F8: return KON_KEY_F8; + case VK_F9: return KON_KEY_F9; + case VK_F10: return KON_KEY_F10; + case VK_F11: return KON_KEY_F11; + case VK_F12: return KON_KEY_F12; + + case VK_LEFT: return KON_KEY_LEFT; + case VK_RIGHT: return KON_KEY_RIGHT; + case VK_UP: return KON_KEY_UP; + case VK_DOWN: return KON_KEY_DOWN; + + case VK_SPACE: return KON_KEY_SPACE; + case VK_RETURN: return KON_KEY_ENTER; + case VK_TAB: return KON_KEY_TAB; + case VK_BACK: return KON_KEY_BACKSPACE; + case VK_ESCAPE: return KON_KEY_ESCAPE; + case VK_DELETE: return KON_KEY_DELETE; + + case VK_OEM_MINUS: return KON_KEY_MINUS; + case VK_OEM_PLUS: return KON_KEY_EQUAL; + case VK_OEM_4: return KON_KEY_LEFT_BRACKET; + case VK_OEM_6: return KON_KEY_RIGHT_BRACKET; + case VK_OEM_1: return KON_KEY_SEMICOLON; + case VK_OEM_7: return KON_KEY_APOSTROPHE; + case VK_OEM_COMMA: return KON_KEY_COMMA; + case VK_OEM_PERIOD: return KON_KEY_PERIOD; + case VK_OEM_2: return KON_KEY_SLASH; + case VK_OEM_5: return KON_KEY_BACKSLASH; + case VK_OEM_3: return KON_KEY_GRAVE; + + default: return KON_KEY_UNKNOWN; + } +} + +LRESULT CALLBACK kon_wndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { + kon_window_t *window = (kon_window_t *)GetWindowLongPtr(hwnd, GWLP_USERDATA); + + switch (msg) { + case WM_NCCREATE: { + CREATESTRUCT *cs = (CREATESTRUCT *)lParam; + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)cs->lpCreateParams); + break; + } + case WM_KEYDOWN: + if (window) { + kon_event_t ev = {0}; + ev.type = KON_EVENT_KEY_DOWN; + ev.key = (int)kon_translateWin32Key_(wParam, lParam); + kon_pushEvent_(&window->queue, &ev); + if (window->exitKey != 0 && ev.key == window->exitKey) { + window->shouldClose = true; + ev.type = KON_EVENT_CLOSE; + kon_pushEvent_(&window->queue, &ev); + } + } + break; + case WM_KEYUP: + if (window) { + kon_event_t ev = {0}; + ev.type = KON_EVENT_KEY_UP; + ev.key = (int)kon_translateWin32Key_(wParam, lParam); + kon_pushEvent_(&window->queue, &ev); + } + break; + case WM_LBUTTONDOWN: case WM_LBUTTONUP: + case WM_RBUTTONDOWN: case WM_RBUTTONUP: + case WM_MBUTTONDOWN: case WM_MBUTTONUP: + if (window) { + kon_event_t ev = {0}; + ev.type = (msg == WM_LBUTTONDOWN || msg == WM_RBUTTONDOWN || msg == WM_MBUTTONDOWN) + ? KON_EVENT_MOUSE_DOWN : KON_EVENT_MOUSE_UP; + ev.mouseButton = (msg == WM_LBUTTONDOWN || msg == WM_LBUTTONUP) ? KON_MOUSE_LEFT + : (msg == WM_RBUTTONDOWN || msg == WM_RBUTTONUP) ? KON_MOUSE_RIGHT + : KON_MOUSE_MIDDLE; + ev.mouseX = (int)(short)LOWORD(lParam); + ev.mouseY = (int)(short)HIWORD(lParam); + kon_pushEvent_(&window->queue, &ev); + } + break; + case WM_MOUSEMOVE: + if (window) { + kon_event_t ev = {0}; + ev.type = KON_EVENT_MOUSE_MOVE; + ev.mouseX = (int)(short)LOWORD(lParam); + ev.mouseY = (int)(short)HIWORD(lParam); + kon_pushEvent_(&window->queue, &ev); + } + break; + case WM_CLOSE: + if (window) { + kon_event_t ev = {0}; + ev.type = KON_EVENT_CLOSE; + window->shouldClose = true; + kon_pushEvent_(&window->queue, &ev); + } + break; + case WM_SIZE: + /* minimizing reports 0x0, which would wreck the framebuffer */ + if (window && wParam != SIZE_MINIMIZED && LOWORD(lParam) > 0 && HIWORD(lParam) > 0) { + kon_event_t ev = {0}; + ev.type = KON_EVENT_RESIZE; + ev.width = LOWORD(lParam); + ev.height = HIWORD(lParam); + kon_pushEvent_(&window->queue, &ev); + } + break; + case WM_DESTROY: + PostQuitMessage(0); + return 0; + break; + case WM_ERASEBKGND: + return 1; + } + return DefWindowProc(hwnd, msg, wParam, lParam); +} + +bool kon_init(void) { + kon_context_t *ctx = malloc(sizeof(kon_context_t)); + if (!ctx) return false; + + ctx->hInstance = GetModuleHandle(NULL); + + WNDCLASSEX wc = {0}; + wc.cbSize = sizeof(WNDCLASSEX); + wc.lpfnWndProc = kon_wndProc; + wc.hInstance = ctx->hInstance; + wc.lpszClassName = "KonWinLibClass"; + wc.hCursor = LoadCursor(NULL, IDC_ARROW); + + if (!RegisterClassEx(&wc)) { + free(ctx); + return false; + } + + kon_ctx = ctx; + return true; +} + +void kon_deinit(void) { + if (!kon_ctx) return; + + free(kon_ctx); + kon_ctx = NULL; +} + + +kon_window_t *kon_createWindow(const char *title, int x, int y, int width, int height, kon_windowFlags_t flags) { + if (!kon_ctx) return NULL; + + kon_window_t *window = calloc(1, sizeof(kon_window_t)); + if (!window) return NULL; + + window->isTransparent = (flags & KON_WINDOW_TRANSPARENT); + window->exitKey = 0; + + DWORD style; + if (flags & KON_WINDOW_NO_DECOR) { + style = WS_POPUP; + } else { + style = WS_OVERLAPPEDWINDOW; + if (!(flags & KON_WINDOW_RESIZABLE)) { + style &= ~(WS_THICKFRAME | WS_MAXIMIZEBOX); + } + } + + DWORD exStyle = 0; + if (flags & KON_WINDOW_ALWAYS_ONTOP) exStyle |= WS_EX_TOPMOST; + if (flags & KON_WINDOW_TRANSPARENT) exStyle |= WS_EX_LAYERED; + + RECT adj = { 0, 0, width, height }; + AdjustWindowRectEx(&adj, style, FALSE, exStyle); + int winW = adj.right - adj.left; + int winH = adj.bottom - adj.top; + + if (flags & KON_WINDOW_CENTER) { + POINT cursor; + GetCursorPos(&cursor); + + HMONITOR hmon = MonitorFromPoint(cursor, MONITOR_DEFAULTTONEAREST); + + MONITORINFO mi; + mi.cbSize = sizeof(MONITORINFO); + GetMonitorInfo(hmon, &mi); + + int mon_x = mi.rcMonitor.left; + int mon_y = mi.rcMonitor.top; + int mon_width = mi.rcMonitor.right - mi.rcMonitor.left; + int mon_height = mi.rcMonitor.bottom - mi.rcMonitor.top; + + x = mon_x + (mon_width - winW) / 2; + y = mon_y + (mon_height - winH) / 2; + } + + window->hwnd = CreateWindowEx(exStyle, "KonWinLibClass", title, style, x, y, winW, winH, NULL, NULL, kon_ctx->hInstance, window); + + if (!window->hwnd) { + free(window); + return NULL; + } + + ShowWindow(window->hwnd, SW_SHOW); + + return window; +} + +void kon_destroyWindow(kon_window_t *window) { + if (!window) return; + if (window->hwnd) DestroyWindow(window->hwnd); + free(window); +} + +bool kon_windowShouldClose(kon_window_t *window) { + if (!window) return true; + return window->shouldClose; +} + + +void kon_setExitKey(kon_window_t *window, int key) { + if (!window) return; + window->exitKey = key; +} + +void kon_setWindowPos(kon_window_t *window, int x, int y) { + if (!window || !kon_ctx) return; + + SetWindowPos(window->hwnd, NULL, x, y, 0, 0, SWP_NOSIZE | SWP_NOZORDER); +} + +void kon_setWindowSize(kon_window_t *window, int width, int height) { + if (!window || !kon_ctx) return; + + RECT r = { 0, 0, width, height }; + AdjustWindowRectEx(&r, (DWORD)GetWindowLongPtr(window->hwnd, GWL_STYLE), FALSE, (DWORD)GetWindowLongPtr(window->hwnd, GWL_EXSTYLE)); + SetWindowPos(window->hwnd, NULL, 0, 0, r.right - r.left, r.bottom - r.top, SWP_NOMOVE | SWP_NOZORDER); +} + + +void kon_getWindowPos(kon_window_t *window, int *x, int *y) { + if (!window || !x || !y) return; + + RECT rect; + GetWindowRect(window->hwnd, &rect); + *x = rect.left; + *y = rect.top; +} + +void kon_getWindowSize(kon_window_t *window, int *width, int *height) { + if (!window || !width || !height) return; + + RECT rect; + GetClientRect(window->hwnd, &rect); + *width = rect.right - rect.left; + *height = rect.bottom - rect.top; +} + +int kon_pollEvent(kon_window_t *window, kon_event_t *event) { + if (!window || !event) return 0; + + MSG msg; + while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + if (!kon_popEvent_(&window->queue, event)) { + event->type = KON_EVENT_NONE; + return 0; + } + return 1; +} + +void kon_blitPixels(kon_window_t *window, const uint32_t *pixels, int width, int height) { + if (!window || !pixels) return; + + BITMAPINFO bmi = {0}; + bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + bmi.bmiHeader.biWidth = width; + bmi.bmiHeader.biHeight = -height; /* top-down */ + bmi.bmiHeader.biPlanes = 1; + bmi.bmiHeader.biBitCount = 32; + bmi.bmiHeader.biCompression = BI_RGB; + + if (window->isTransparent) { + HDC screenDC = GetDC(NULL); + HDC memDC = CreateCompatibleDC(screenDC); + + void *dibPixels; + HBITMAP hbm = CreateDIBSection(screenDC, &bmi, DIB_RGB_COLORS, &dibPixels, NULL, 0); + if (hbm) { + memcpy(dibPixels, pixels, (size_t)width * height * 4); + HBITMAP oldBmp = (HBITMAP)SelectObject(memDC, hbm); + + RECT winRect; + GetWindowRect(window->hwnd, &winRect); + POINT dstPos = { winRect.left, winRect.top }; + POINT srcPos = { 0, 0 }; + SIZE size = { width, height }; + BLENDFUNCTION blend = { AC_SRC_OVER, 0, 255, AC_SRC_ALPHA }; + + UpdateLayeredWindow(window->hwnd, screenDC, &dstPos, &size, memDC, &srcPos, 0, &blend, ULW_ALPHA); + + SelectObject(memDC, oldBmp); + DeleteObject(hbm); + } + + DeleteDC(memDC); + ReleaseDC(NULL, screenDC); + } else { + HDC winDC = GetDC(window->hwnd); + SetDIBitsToDevice(winDC, 0, 0, width, height, 0, 0, 0, height, pixels, &bmi, DIB_RGB_COLORS); + ReleaseDC(window->hwnd, winDC); + } +} + +#else + #error "konwinlib.h: unsupported platform" +#endif /* end of platform switch */ + +/*** main loop ***/ + +#ifdef __EMSCRIPTEN__ + +typedef struct { + kon_window_t *window; + kon_mainLoopFn callback; + void *userData; +} kon_mainLoopCtx_; + +static void kon_mainLoopTrampoline_(void *arg) { + kon_mainLoopCtx_ *ctx = (kon_mainLoopCtx_ *)arg; + + if (kon_windowShouldClose(ctx->window)) { + emscripten_cancel_main_loop(); + free(ctx); + return; + } + + ctx->callback(ctx->userData); +} + +void kon_runMainLoop(kon_window_t *window, kon_mainLoopFn callback, void *userData) { + kon_mainLoopCtx_ *ctx = malloc(sizeof(kon_mainLoopCtx_)); + if (!ctx) return; + + ctx->window = window; + ctx->callback = callback; + ctx->userData = userData; + + emscripten_set_main_loop_arg(kon_mainLoopTrampoline_, ctx, 0, 1); +} + +#else + +void kon_runMainLoop(kon_window_t *window, kon_mainLoopFn callback, void *userData) { + while (!kon_windowShouldClose(window)) { + callback(userData); + } +} + +#endif /* __EMSCRIPTEN__ */ + +#endif /* end of KONWINLIB_IMPLEMENTATION */ + +#endif |
