/* 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. */ #if !defined(_WIN32) && !defined(_POSIX_C_SOURCE) #define _POSIX_C_SOURCE 200809L #endif #ifndef KONWINLIB_H #define KONWINLIB_H #include #include #include #include #ifdef __cplusplus extern "C" { #endif /*** 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_EVENT_TEXT, KON_EVENT_MOUSE_WHEEL } 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_PAGE_UP, KON_KEY_PAGE_DOWN, 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; unsigned int codepoint; /* unicode codepoint, only set for KON_EVENT_TEXT */ int mouseX, mouseY; int mouseDX, mouseDY; /* how far the mouse moved, set for KON_EVENT_MOUSE_MOVE. keeps working while the mouse is captured */ kon_mouseButton_t mouseButton; int wheel; /* only set for KON_EVENT_MOUSE_WHEEL, 1 for each notch away from you and -1 towards you */ } 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_setShouldClose(kon_window_t *window, bool value); 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); /* hides the cursor and keeps it in the window so the mouse can turn a camera, move events keep reporting mouseDX and mouseDY. returns 1 when it worked. the window lets go by itself when it loses focus. on the web this has to be called from a click or key press */ int kon_setMouseCapture(kon_window_t *window, bool captured); bool kon_isMouseCaptured(const kon_window_t *window); /*** window tricks ***/ typedef enum kon_messageType { KON_MESSAGE_INFO = 0, KON_MESSAGE_WARNING, KON_MESSAGE_ERROR } kon_messageType_t; /* the whole screen, and the cursor on it, not in your window */ void kon_getScreenSize(int *width, int *height); void kon_getCursorPos(int *x, int *y); void kon_setCursorPos(int x, int y); void kon_setWindowTitle(kon_window_t *window, const char *title); void kon_setWindowDecorated(kon_window_t *window, bool decorated); void kon_setWindowTopmost(kon_window_t *window, bool topmost); /* 0 to 1. on X11 this needs a compositor */ void kon_setWindowOpacity(kon_window_t *window, float opacity); /* mouse clicks go to whatever is behind the window */ void kon_setWindowClickThrough(kon_window_t *window, bool enabled); /* waits until it is closed, works without a window. native on windows and the web, drawn by konwinlib on X11 */ void kon_messageBox(const char *title, const char *message, kon_messageType_t type); 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); /* Windows stops your loop while the window is being dragged or resized, this keeps calling callback meanwhile */ void kon_setLiveCallback(kon_window_t *window, kon_mainLoopFn callback, void *userData); /*** implementation ***/ #ifdef __cplusplus } #endif #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) { int dx = last->mouseDX + event->mouseDX, dy = last->mouseDY + event->mouseDY; *last = *event; if (event->type == KON_EVENT_MOUSE_MOVE) { last->mouseDX = dx; last->mouseDY = dy; } 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 #include #include struct kon_context { int initialized; }; struct kon_window { char canvasID[64]; bool captured; bool shouldClose; int exitKey; kon_eventQueue_t queue; }; 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 33: return KON_KEY_PAGE_UP; case 34: return KON_KEY_PAGE_DOWN; 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; kon_event_t ev = {0}; ev.type = KON_EVENT_KEY_DOWN; ev.key = (int)kon_translateEmKey_((int)e->keyCode); 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); } 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; kon_event_t ev = {0}; ev.type = KON_EVENT_KEY_UP; ev.key = (int)kon_translateEmKey_((int)e->keyCode); kon_pushEvent_(&window->queue, &ev); return EM_TRUE; } static EM_BOOL kon_keyPressCallback_(int eventType, const EmscriptenKeyboardEvent *e, void *userData) { (void)eventType; kon_window_t *window = (kon_window_t *)userData; kon_event_t ev = {0}; if (e->charCode < 32 || e->charCode == 127) return EM_FALSE; ev.type = KON_EVENT_TEXT; ev.codepoint = (unsigned int)e->charCode; kon_pushEvent_(&window->queue, &ev); return EM_TRUE; } static EM_BOOL kon_mouseCallback_(int eventType, const EmscriptenMouseEvent *e, void *userData) { kon_window_t *window = (kon_window_t *)userData; kon_event_t ev = {0}; if (eventType == EMSCRIPTEN_EVENT_MOUSEMOVE) { ev.type = KON_EVENT_MOUSE_MOVE; ev.mouseDX = (int)e->movementX; ev.mouseDY = (int)e->movementY; } else { if (e->button > KON_MOUSE_RIGHT) return EM_FALSE; ev.type = (eventType == EMSCRIPTEN_EVENT_MOUSEDOWN) ? KON_EVENT_MOUSE_DOWN : KON_EVENT_MOUSE_UP; ev.mouseButton = (kon_mouseButton_t)e->button; } ev.mouseX = (int)e->targetX; ev.mouseY = (int)e->targetY; kon_pushEvent_(&window->queue, &ev); return EM_TRUE; } static EM_BOOL kon_wheelCallback_(int eventType, const EmscriptenWheelEvent *e, void *userData) { kon_window_t *window = (kon_window_t *)userData; kon_event_t ev = {0}; (void)eventType; if (e->deltaY == 0.0) return EM_FALSE; ev.type = KON_EVENT_MOUSE_WHEEL; ev.wheel = e->deltaY < 0.0 ? 1 : -1; ev.mouseX = (int)e->mouse.targetX; ev.mouseY = (int)e->mouse.targetY; kon_pushEvent_(&window->queue, &ev); 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 = calloc(1, sizeof(kon_window_t)); if (!window) return NULL; strncpy(window->canvasID, "#canvas", sizeof(window->canvasID) - 1); window->canvasID[sizeof(window->canvasID) - 1] = '\0'; 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_keypress_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, window, EM_FALSE, kon_keyPressCallback_); emscripten_set_mousedown_callback(window->canvasID, window, EM_FALSE, kon_mouseCallback_); emscripten_set_mouseup_callback(window->canvasID, window, EM_FALSE, kon_mouseCallback_); emscripten_set_mousemove_callback(window->canvasID, window, EM_FALSE, kon_mouseCallback_); emscripten_set_wheel_callback(window->canvasID, window, EM_FALSE, kon_wheelCallback_); return window; } int kon_setMouseCapture(kon_window_t *window, bool captured) { if (!window) return 0; if (captured) { if (emscripten_request_pointerlock(window->canvasID, EM_TRUE) < 0) return 0; window->captured = true; } else { emscripten_exit_pointerlock(); window->captured = false; } return 1; } bool kon_isMouseCaptured(const kon_window_t *window) { return window && window->captured; } void kon_destroyWindow(kon_window_t *window) { if (!window) return; if (window->captured) emscripten_exit_pointerlock(); 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_keypress_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); emscripten_set_wheel_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_setShouldClose(kon_window_t *window, bool value) { if (window) window->shouldClose = value; } 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; } EM_JS(void, kon_jsTitle_, (const char *title), { document.title = UTF8ToString(title); }); EM_JS(void, kon_jsAlert_, (const char *title, const char *message), { alert(UTF8ToString(title) + "\n\n" + UTF8ToString(message)); }); EM_JS(int, kon_jsScreenWidth_, (void), { return screen.width; }); EM_JS(int, kon_jsScreenHeight_, (void), { return screen.height; }); EM_JS(void, kon_jsOpacity_, (const char *id, float value), { var c = document.querySelector(UTF8ToString(id)); if (c) c.style.opacity = value; }); EM_JS(void, kon_jsClickThrough_, (const char *id, int on), { var c = document.querySelector(UTF8ToString(id)); if (c) c.style.pointerEvents = on ? "none" : "auto"; }); void kon_getScreenSize(int *width, int *height) { if (width) *width = kon_jsScreenWidth_(); if (height) *height = kon_jsScreenHeight_(); } void kon_getCursorPos(int *x, int *y) { if (x) *x = 0; if (y) *y = 0; } void kon_setCursorPos(int x, int y) { (void)x; (void)y; } void kon_setWindowTitle(kon_window_t *window, const char *title) { (void)window; if (title) kon_jsTitle_(title); } void kon_setWindowDecorated(kon_window_t *window, bool decorated) { (void)window; (void)decorated; } void kon_setWindowTopmost(kon_window_t *window, bool topmost) { (void)window; (void)topmost; } void kon_setWindowOpacity(kon_window_t *window, float opacity) { if (window) kon_jsOpacity_(window->canvasID, opacity < 0.0f ? 0.0f : (opacity > 1.0f ? 1.0f : opacity)); } void kon_setWindowClickThrough(kon_window_t *window, bool enabled) { if (window) kon_jsClickThrough_(window->canvasID, enabled ? 1 : 0); } void kon_messageBox(const char *title, const char *message, kon_messageType_t type) { (void)type; kon_jsAlert_(title ? title : "", message ? message : ""); } int kon_pollEvent(kon_window_t *window, kon_event_t *event) { if (!window || !event) return 0; if (!kon_popEvent_(&window->queue, event)) { event->type = KON_EVENT_NONE; return 0; } return 1; } 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 #include #include #include #include #include #include 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_Prior: return KON_KEY_PAGE_UP; case XK_Next: return KON_KEY_PAGE_DOWN; 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) { free(ctx); 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; int width, height; bool captured, hasLast; int lastX, lastY; Cursor blankCursor; kon_eventQueue_t queue; }; 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 = calloc(1, 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 | FocusChangeMask); 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; window->width = width; window->height = height; return window; } int kon_setMouseCapture(kon_window_t *window, bool captured) { if (!window || !kon_ctx) return 0; if (!captured) { if (!window->captured) return 1; XUngrabPointer(kon_ctx->display, CurrentTime); if (window->blankCursor) XFreeCursor(kon_ctx->display, window->blankCursor); window->blankCursor = 0; window->captured = false; window->hasLast = false; XFlush(kon_ctx->display); return 1; } if (window->captured) return 1; { char data[1] = {0}; XColor black = {0}; Pixmap blank = XCreateBitmapFromData(kon_ctx->display, window->window, data, 1, 1); window->blankCursor = XCreatePixmapCursor(kon_ctx->display, blank, blank, &black, &black, 0, 0); XFreePixmap(kon_ctx->display, blank); } if (XGrabPointer(kon_ctx->display, window->window, True, ButtonPressMask | ButtonReleaseMask | PointerMotionMask, GrabModeAsync, GrabModeAsync, window->window, window->blankCursor, CurrentTime) != GrabSuccess) { XFreeCursor(kon_ctx->display, window->blankCursor); window->blankCursor = 0; return 0; } window->captured = true; XWarpPointer(kon_ctx->display, None, window->window, 0, 0, 0, 0, window->width / 2, window->height / 2); XFlush(kon_ctx->display); return 1; } bool kon_isMouseCaptured(const kon_window_t *window) { return window && window->captured; } void kon_destroyWindow(kon_window_t *window) { if (!window) return; if (!kon_ctx) { free(window); return; } kon_setMouseCapture(window, false); 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; } static void kon_pumpX11_(kon_window_t *window) { while (XPending(kon_ctx->display)) { XEvent xev; kon_event_t ev = {0}; XNextEvent(kon_ctx->display, &xev); switch (xev.type) { case ClientMessage: if ((Atom)xev.xclient.data.l[0] == window->wm_delete) { ev.type = KON_EVENT_CLOSE; window->shouldClose = true; kon_pushEvent_(&window->queue, &ev); } break; case ConfigureNotify: /* moving the window also sends this, only report real size changes */ if (xev.xconfigure.width != window->width || xev.xconfigure.height != window->height) { window->width = xev.xconfigure.width; window->height = xev.xconfigure.height; ev.type = KON_EVENT_RESIZE; ev.width = window->width; ev.height = window->height; kon_pushEvent_(&window->queue, &ev); } break; case KeyPress: { char text[8]; int len; ev.type = KON_EVENT_KEY_DOWN; ev.key = (int)kon_translateX11Key_(XLookupKeysym(&xev.xkey, 0)); 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); } len = XLookupString(&xev.xkey, text, (int)sizeof(text), NULL, NULL); if (len == 1 && (unsigned char)text[0] >= 32 && (unsigned char)text[0] != 127 && !(xev.xkey.state & (ControlMask | Mod1Mask))) { ev.type = KON_EVENT_TEXT; ev.key = 0; ev.codepoint = (unsigned char)text[0]; kon_pushEvent_(&window->queue, &ev); } break; } case KeyRelease: /* X reports key repeat as a release followed by a press with the same time, drop the release */ if (XPending(kon_ctx->display)) { XEvent next; XPeekEvent(kon_ctx->display, &next); if (next.type == KeyPress && next.xkey.time == xev.xkey.time && next.xkey.keycode == xev.xkey.keycode) { break; } } ev.type = KON_EVENT_KEY_UP; ev.key = (int)kon_translateX11Key_(XLookupKeysym(&xev.xkey, 0)); kon_pushEvent_(&window->queue, &ev); break; case ButtonPress: case ButtonRelease: switch (xev.xbutton.button) { case Button1: ev.mouseButton = KON_MOUSE_LEFT; break; case Button2: ev.mouseButton = KON_MOUSE_MIDDLE; break; case Button3: ev.mouseButton = KON_MOUSE_RIGHT; break; case Button4: case Button5: /* the wheel shows up as button 4 and 5 being pressed, the release is not interesting */ if (xev.type == ButtonPress) { ev.type = KON_EVENT_MOUSE_WHEEL; ev.wheel = xev.xbutton.button == Button4 ? 1 : -1; ev.mouseX = xev.xbutton.x; ev.mouseY = xev.xbutton.y; kon_pushEvent_(&window->queue, &ev); } continue; default: continue; } ev.type = (xev.type == ButtonPress) ? KON_EVENT_MOUSE_DOWN : KON_EVENT_MOUSE_UP; ev.mouseX = xev.xbutton.x; ev.mouseY = xev.xbutton.y; kon_pushEvent_(&window->queue, &ev); break; case MotionNotify: ev.type = KON_EVENT_MOUSE_MOVE; ev.mouseX = xev.xmotion.x; ev.mouseY = xev.xmotion.y; if (window->captured) { /* the cursor gets put back in the middle every time, so how far it got from there is how far it moved */ int cx = window->width / 2, cy = window->height / 2; ev.mouseDX = ev.mouseX - cx; ev.mouseDY = ev.mouseY - cy; if (ev.mouseDX == 0 && ev.mouseDY == 0) break; XWarpPointer(kon_ctx->display, None, window->window, 0, 0, 0, 0, cx, cy); } else if (window->hasLast) { ev.mouseDX = ev.mouseX - window->lastX; ev.mouseDY = ev.mouseY - window->lastY; } window->lastX = ev.mouseX; window->lastY = ev.mouseY; window->hasLast = true; kon_pushEvent_(&window->queue, &ev); break; case FocusOut: if (xev.xfocus.mode != NotifyGrab && xev.xfocus.mode != NotifyUngrab) kon_setMouseCapture(window, false); break; default: break; } } } void kon_getScreenSize(int *width, int *height) { if (!kon_ctx) return; int screen = DefaultScreen(kon_ctx->display); if (width) *width = DisplayWidth(kon_ctx->display, screen); if (height) *height = DisplayHeight(kon_ctx->display, screen); } void kon_getCursorPos(int *x, int *y) { if (!kon_ctx) return; Window root = RootWindow(kon_ctx->display, DefaultScreen(kon_ctx->display)); Window rootRet, childRet; int rootX = 0, rootY = 0, winX, winY; unsigned int mask; XQueryPointer(kon_ctx->display, root, &rootRet, &childRet, &rootX, &rootY, &winX, &winY, &mask); if (x) *x = rootX; if (y) *y = rootY; } void kon_setCursorPos(int x, int y) { if (!kon_ctx) return; Window root = RootWindow(kon_ctx->display, DefaultScreen(kon_ctx->display)); XWarpPointer(kon_ctx->display, None, root, 0, 0, 0, 0, x, y); XFlush(kon_ctx->display); } void kon_setWindowTitle(kon_window_t *window, const char *title) { if (!window || !kon_ctx || !title) return; Atom netName = XInternAtom(kon_ctx->display, "_NET_WM_NAME", False); Atom utf8 = XInternAtom(kon_ctx->display, "UTF8_STRING", False); XStoreName(kon_ctx->display, window->window, title); XChangeProperty(kon_ctx->display, window->window, netName, utf8, 8, PropModeReplace, (const unsigned char *)title, (int)strlen(title)); XFlush(kon_ctx->display); } void kon_setWindowDecorated(kon_window_t *window, bool decorated) { if (!window || !kon_ctx) return; Atom motif = 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 = decorated ? 1 : 0; XChangeProperty(kon_ctx->display, window->window, motif, motif, 32, PropModeReplace, (unsigned char *)&hints, 5); XFlush(kon_ctx->display); } void kon_setWindowTopmost(kon_window_t *window, bool topmost) { if (!window || !kon_ctx) return; XEvent ev; memset(&ev, 0, sizeof(ev)); ev.xclient.type = ClientMessage; ev.xclient.window = window->window; ev.xclient.message_type = XInternAtom(kon_ctx->display, "_NET_WM_STATE", False); ev.xclient.format = 32; ev.xclient.data.l[0] = topmost ? 1 : 0; ev.xclient.data.l[1] = (long)XInternAtom(kon_ctx->display, "_NET_WM_STATE_ABOVE", False); ev.xclient.data.l[3] = 1; XSendEvent(kon_ctx->display, RootWindow(kon_ctx->display, DefaultScreen(kon_ctx->display)), False, SubstructureRedirectMask | SubstructureNotifyMask, &ev); XFlush(kon_ctx->display); } void kon_setWindowOpacity(kon_window_t *window, float opacity) { if (!window || !kon_ctx) return; Atom prop = XInternAtom(kon_ctx->display, "_NET_WM_WINDOW_OPACITY", False); if (opacity >= 1.0f) { XDeleteProperty(kon_ctx->display, window->window, prop); } else { if (opacity < 0.0f) opacity = 0.0f; unsigned long value = (unsigned long)((double)opacity * 4294967295.0); XChangeProperty(kon_ctx->display, window->window, prop, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&value, 1); } XFlush(kon_ctx->display); } void kon_setWindowClickThrough(kon_window_t *window, bool enabled) { if (!window || !kon_ctx) return; int eventBase, errorBase; if (!XShapeQueryExtension(kon_ctx->display, &eventBase, &errorBase)) return; if (enabled) { XShapeCombineRectangles(kon_ctx->display, window->window, ShapeInput, 0, 0, NULL, 0, ShapeSet, Unsorted); } else { XShapeCombineMask(kon_ctx->display, window->window, ShapeInput, 0, 0, None, ShapeSet); } XFlush(kon_ctx->display); } /*** message box, drawn by us so it looks the same everywhere ***/ /* 8x13 glyphs for ascii 32 to 126, one byte per row, leftmost pixel is the top bit. public domain misc-fixed derived font */ static const unsigned char kon_dialogFont_[95][13] = { { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, /* */ { 0x00, 0x00, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x00, 0x10, 0x00, 0x00 }, /* ! */ { 0x00, 0x00, 0x24, 0x24, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, /* " */ { 0x00, 0x00, 0x00, 0x24, 0x24, 0x7E, 0x24, 0x7E, 0x24, 0x24, 0x00, 0x00, 0x00 }, /* # */ { 0x00, 0x00, 0x10, 0x3C, 0x50, 0x50, 0x38, 0x14, 0x14, 0x78, 0x10, 0x00, 0x00 }, /* $ */ { 0x00, 0x00, 0x22, 0x52, 0x24, 0x08, 0x08, 0x10, 0x24, 0x2A, 0x44, 0x00, 0x00 }, /* % */ { 0x00, 0x00, 0x00, 0x00, 0x30, 0x48, 0x48, 0x30, 0x4A, 0x44, 0x3A, 0x00, 0x00 }, /* & */ { 0x00, 0x00, 0x10, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, /* ' */ { 0x00, 0x00, 0x04, 0x08, 0x08, 0x10, 0x10, 0x10, 0x08, 0x08, 0x04, 0x00, 0x00 }, /* ( */ { 0x00, 0x00, 0x20, 0x10, 0x10, 0x08, 0x08, 0x08, 0x10, 0x10, 0x20, 0x00, 0x00 }, /* ) */ { 0x00, 0x00, 0x00, 0x00, 0x24, 0x18, 0x7E, 0x18, 0x24, 0x00, 0x00, 0x00, 0x00 }, /* ch42 */ { 0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0x7C, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00 }, /* + */ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x30, 0x40, 0x00 }, /* , */ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, /* - */ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x38, 0x10, 0x00 }, /* . */ { 0x00, 0x00, 0x02, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x80, 0x00, 0x00 }, /* ch47 */ { 0x00, 0x00, 0x18, 0x24, 0x42, 0x42, 0x42, 0x42, 0x42, 0x24, 0x18, 0x00, 0x00 }, /* 0 */ { 0x00, 0x00, 0x10, 0x30, 0x50, 0x10, 0x10, 0x10, 0x10, 0x10, 0x7C, 0x00, 0x00 }, /* 1 */ { 0x00, 0x00, 0x3C, 0x42, 0x42, 0x02, 0x04, 0x18, 0x20, 0x40, 0x7E, 0x00, 0x00 }, /* 2 */ { 0x00, 0x00, 0x7E, 0x02, 0x04, 0x08, 0x1C, 0x02, 0x02, 0x42, 0x3C, 0x00, 0x00 }, /* 3 */ { 0x00, 0x00, 0x04, 0x0C, 0x14, 0x24, 0x44, 0x44, 0x7E, 0x04, 0x04, 0x00, 0x00 }, /* 4 */ { 0x00, 0x00, 0x7E, 0x40, 0x40, 0x5C, 0x62, 0x02, 0x02, 0x42, 0x3C, 0x00, 0x00 }, /* 5 */ { 0x00, 0x00, 0x1C, 0x20, 0x40, 0x40, 0x5C, 0x62, 0x42, 0x42, 0x3C, 0x00, 0x00 }, /* 6 */ { 0x00, 0x00, 0x7E, 0x02, 0x04, 0x08, 0x08, 0x10, 0x10, 0x20, 0x20, 0x00, 0x00 }, /* 7 */ { 0x00, 0x00, 0x3C, 0x42, 0x42, 0x42, 0x3C, 0x42, 0x42, 0x42, 0x3C, 0x00, 0x00 }, /* 8 */ { 0x00, 0x00, 0x3C, 0x42, 0x42, 0x46, 0x3A, 0x02, 0x02, 0x04, 0x38, 0x00, 0x00 }, /* 9 */ { 0x00, 0x00, 0x00, 0x00, 0x10, 0x38, 0x10, 0x00, 0x00, 0x10, 0x38, 0x10, 0x00 }, /* : */ { 0x00, 0x00, 0x00, 0x00, 0x10, 0x38, 0x10, 0x00, 0x00, 0x38, 0x30, 0x40, 0x00 }, /* ; */ { 0x00, 0x00, 0x02, 0x04, 0x08, 0x10, 0x20, 0x10, 0x08, 0x04, 0x02, 0x00, 0x00 }, /* < */ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x7E, 0x00, 0x00, 0x7E, 0x00, 0x00, 0x00, 0x00 }, /* = */ { 0x00, 0x00, 0x40, 0x20, 0x10, 0x08, 0x04, 0x08, 0x10, 0x20, 0x40, 0x00, 0x00 }, /* > */ { 0x00, 0x00, 0x3C, 0x42, 0x42, 0x02, 0x04, 0x08, 0x08, 0x00, 0x08, 0x00, 0x00 }, /* ? */ { 0x00, 0x00, 0x3C, 0x42, 0x42, 0x4E, 0x52, 0x56, 0x4A, 0x40, 0x3C, 0x00, 0x00 }, /* @ */ { 0x00, 0x00, 0x18, 0x24, 0x42, 0x42, 0x42, 0x7E, 0x42, 0x42, 0x42, 0x00, 0x00 }, /* A */ { 0x00, 0x00, 0x78, 0x44, 0x42, 0x44, 0x78, 0x44, 0x42, 0x44, 0x78, 0x00, 0x00 }, /* B */ { 0x00, 0x00, 0x3C, 0x42, 0x40, 0x40, 0x40, 0x40, 0x40, 0x42, 0x3C, 0x00, 0x00 }, /* C */ { 0x00, 0x00, 0x78, 0x44, 0x42, 0x42, 0x42, 0x42, 0x42, 0x44, 0x78, 0x00, 0x00 }, /* D */ { 0x00, 0x00, 0x7E, 0x40, 0x40, 0x40, 0x78, 0x40, 0x40, 0x40, 0x7E, 0x00, 0x00 }, /* E */ { 0x00, 0x00, 0x7E, 0x40, 0x40, 0x40, 0x78, 0x40, 0x40, 0x40, 0x40, 0x00, 0x00 }, /* F */ { 0x00, 0x00, 0x3C, 0x42, 0x40, 0x40, 0x40, 0x4E, 0x42, 0x46, 0x3A, 0x00, 0x00 }, /* G */ { 0x00, 0x00, 0x42, 0x42, 0x42, 0x42, 0x7E, 0x42, 0x42, 0x42, 0x42, 0x00, 0x00 }, /* H */ { 0x00, 0x00, 0x7C, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x7C, 0x00, 0x00 }, /* I */ { 0x00, 0x00, 0x1F, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x44, 0x38, 0x00, 0x00 }, /* J */ { 0x00, 0x00, 0x42, 0x44, 0x48, 0x50, 0x60, 0x50, 0x48, 0x44, 0x42, 0x00, 0x00 }, /* K */ { 0x00, 0x00, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x7E, 0x00, 0x00 }, /* L */ { 0x00, 0x00, 0x82, 0x82, 0xC6, 0xAA, 0x92, 0x92, 0x82, 0x82, 0x82, 0x00, 0x00 }, /* M */ { 0x00, 0x00, 0x42, 0x42, 0x62, 0x52, 0x4A, 0x46, 0x42, 0x42, 0x42, 0x00, 0x00 }, /* N */ { 0x00, 0x00, 0x3C, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x3C, 0x00, 0x00 }, /* O */ { 0x00, 0x00, 0x7C, 0x42, 0x42, 0x42, 0x7C, 0x40, 0x40, 0x40, 0x40, 0x00, 0x00 }, /* P */ { 0x00, 0x00, 0x3C, 0x42, 0x42, 0x42, 0x42, 0x42, 0x52, 0x4A, 0x3C, 0x02, 0x00 }, /* Q */ { 0x00, 0x00, 0x7C, 0x42, 0x42, 0x42, 0x7C, 0x50, 0x48, 0x44, 0x42, 0x00, 0x00 }, /* R */ { 0x00, 0x00, 0x3C, 0x42, 0x40, 0x40, 0x3C, 0x02, 0x02, 0x42, 0x3C, 0x00, 0x00 }, /* S */ { 0x00, 0x00, 0xFE, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x00, 0x00 }, /* T */ { 0x00, 0x00, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x3C, 0x00, 0x00 }, /* U */ { 0x00, 0x00, 0x82, 0x82, 0x44, 0x44, 0x44, 0x28, 0x28, 0x28, 0x10, 0x00, 0x00 }, /* V */ { 0x00, 0x00, 0x82, 0x82, 0x82, 0x82, 0x92, 0x92, 0x92, 0xAA, 0x44, 0x00, 0x00 }, /* W */ { 0x00, 0x00, 0x82, 0x82, 0x44, 0x28, 0x10, 0x28, 0x44, 0x82, 0x82, 0x00, 0x00 }, /* X */ { 0x00, 0x00, 0x82, 0x82, 0x44, 0x28, 0x10, 0x10, 0x10, 0x10, 0x10, 0x00, 0x00 }, /* Y */ { 0x00, 0x00, 0x7E, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x40, 0x7E, 0x00, 0x00 }, /* Z */ { 0x00, 0x00, 0x3C, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3C, 0x00, 0x00 }, /* [ */ { 0x00, 0x00, 0x80, 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x02, 0x00, 0x00 }, /* ch92 */ { 0x00, 0x00, 0x78, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x78, 0x00, 0x00 }, /* ] */ { 0x00, 0x00, 0x10, 0x28, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, /* ^ */ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0x00 }, /* _ */ { 0x00, 0x10, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, /* ` */ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x02, 0x3E, 0x42, 0x46, 0x3A, 0x00, 0x00 }, /* a */ { 0x00, 0x00, 0x40, 0x40, 0x40, 0x5C, 0x62, 0x42, 0x42, 0x62, 0x5C, 0x00, 0x00 }, /* b */ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x42, 0x40, 0x40, 0x42, 0x3C, 0x00, 0x00 }, /* c */ { 0x00, 0x00, 0x02, 0x02, 0x02, 0x3A, 0x46, 0x42, 0x42, 0x46, 0x3A, 0x00, 0x00 }, /* d */ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x42, 0x7E, 0x40, 0x42, 0x3C, 0x00, 0x00 }, /* e */ { 0x00, 0x00, 0x1C, 0x22, 0x20, 0x20, 0x7C, 0x20, 0x20, 0x20, 0x20, 0x00, 0x00 }, /* f */ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x3A, 0x44, 0x44, 0x38, 0x40, 0x3C, 0x42, 0x3C }, /* g */ { 0x00, 0x00, 0x40, 0x40, 0x40, 0x5C, 0x62, 0x42, 0x42, 0x42, 0x42, 0x00, 0x00 }, /* h */ { 0x00, 0x00, 0x00, 0x10, 0x00, 0x30, 0x10, 0x10, 0x10, 0x10, 0x7C, 0x00, 0x00 }, /* i */ { 0x00, 0x00, 0x00, 0x04, 0x00, 0x0C, 0x04, 0x04, 0x04, 0x04, 0x44, 0x44, 0x38 }, /* j */ { 0x00, 0x00, 0x40, 0x40, 0x40, 0x44, 0x48, 0x70, 0x48, 0x44, 0x42, 0x00, 0x00 }, /* k */ { 0x00, 0x00, 0x30, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x7C, 0x00, 0x00 }, /* l */ { 0x00, 0x00, 0x00, 0x00, 0x00, 0xEC, 0x92, 0x92, 0x92, 0x92, 0x82, 0x00, 0x00 }, /* m */ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x5C, 0x62, 0x42, 0x42, 0x42, 0x42, 0x00, 0x00 }, /* n */ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x42, 0x42, 0x42, 0x42, 0x3C, 0x00, 0x00 }, /* o */ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x5C, 0x62, 0x42, 0x62, 0x5C, 0x40, 0x40, 0x40 }, /* p */ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x3A, 0x46, 0x42, 0x46, 0x3A, 0x02, 0x02, 0x02 }, /* q */ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x5C, 0x22, 0x20, 0x20, 0x20, 0x20, 0x00, 0x00 }, /* r */ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x42, 0x30, 0x0C, 0x42, 0x3C, 0x00, 0x00 }, /* s */ { 0x00, 0x00, 0x00, 0x20, 0x20, 0x7C, 0x20, 0x20, 0x20, 0x22, 0x1C, 0x00, 0x00 }, /* t */ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0x44, 0x44, 0x44, 0x44, 0x3A, 0x00, 0x00 }, /* u */ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0x44, 0x44, 0x28, 0x28, 0x10, 0x00, 0x00 }, /* v */ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x82, 0x82, 0x92, 0x92, 0xAA, 0x44, 0x00, 0x00 }, /* w */ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 0x00 }, /* x */ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x42, 0x42, 0x46, 0x3A, 0x02, 0x42, 0x3C }, /* y */ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x7E, 0x04, 0x08, 0x10, 0x20, 0x7E, 0x00, 0x00 }, /* z */ { 0x00, 0x00, 0x0E, 0x10, 0x10, 0x08, 0x30, 0x08, 0x10, 0x10, 0x0E, 0x00, 0x00 }, /* { */ { 0x00, 0x00, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x00, 0x00 }, /* | */ { 0x00, 0x00, 0x70, 0x08, 0x08, 0x10, 0x0C, 0x10, 0x08, 0x08, 0x70, 0x00, 0x00 }, /* } */ { 0x00, 0x00, 0x24, 0x54, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, /* ~ */ }; #define KON_DIALOG_COLUMNS 60 #define KON_DIALOG_LINES 24 typedef struct kon_dialogLayout { int scale, width, height; int lines; char text[KON_DIALOG_LINES][KON_DIALOG_COLUMNS + 1]; int buttonX, buttonY, buttonW, buttonH; } kon_dialogLayout_t; static void kon_dialogWrap_(kon_dialogLayout_t *layout, const char *message) { int col = 0; layout->lines = 1; layout->text[0][0] = 0; for (const char *p = message; *p; p++) { unsigned char c = (unsigned char)*p; if (c == '\r') continue; if (c == '\t') c = ' '; if (c == '\n' || col == KON_DIALOG_COLUMNS) { if (layout->lines == KON_DIALOG_LINES) { if (col >= 3) memcpy(layout->text[layout->lines - 1] + col - 3, "...", 4); return; } /* wrapping a long line, move the unfinished word down with it */ int carry = 0; if (c != '\n') { int space = col; while (space > 0 && layout->text[layout->lines - 1][space - 1] != ' ') space--; if (space > 0) carry = col - space; } layout->text[layout->lines][0] = 0; if (carry > 0) { memcpy(layout->text[layout->lines], layout->text[layout->lines - 1] + col - carry, (size_t)carry); layout->text[layout->lines][carry] = 0; col -= carry; while (col > 0 && layout->text[layout->lines - 1][col - 1] == ' ') col--; layout->text[layout->lines - 1][col] = 0; } else { layout->text[layout->lines - 1][col] = 0; } layout->lines++; col = carry; if (c == '\n') continue; if (c == ' ') continue; } if (c < 32 || c > 126) c = '?'; layout->text[layout->lines - 1][col++] = (char)c; layout->text[layout->lines - 1][col] = 0; } } static void kon_dialogLayoutInit_(kon_dialogLayout_t *layout, const char *message, int screenHeight) { memset(layout, 0, sizeof(*layout)); layout->scale = screenHeight >= 1400 ? 2 : 1; kon_dialogWrap_(layout, message); int s = layout->scale, widest = 0; for (int i = 0; i < layout->lines; i++) { int n = (int)strlen(layout->text[i]); if (n > widest) widest = n; } layout->width = widest * 8 * s + 44 * s; if (layout->width < 260 * s) layout->width = 260 * s; layout->buttonW = 80 * s; layout->buttonH = 26 * s; layout->buttonX = (layout->width - layout->buttonW) / 2; layout->buttonY = 16 * s + layout->lines * 15 * s + 16 * s; layout->height = layout->buttonY + layout->buttonH + 16 * s; } static void kon_dialogFill_(uint32_t *px, const kon_dialogLayout_t *l, int x, int y, int w, int h, uint32_t color) { if (x < 0) { w += x; x = 0; } if (y < 0) { h += y; y = 0; } if (x + w > l->width) w = l->width - x; if (y + h > l->height) h = l->height - y; for (int j = 0; j < h; j++) { for (int i = 0; i < w; i++) px[(y + j) * l->width + x + i] = color; } } static void kon_dialogText_(uint32_t *px, const kon_dialogLayout_t *l, int x, int y, const char *text, uint32_t color) { int s = l->scale; for (; *text; text++, x += 8 * s) { unsigned char c = (unsigned char)*text; if (c < 32 || c > 126) continue; for (int row = 0; row < 13; row++) { unsigned char bits = kon_dialogFont_[c - 32][row]; for (int bit = 0; bit < 8; bit++) { if (bits & (0x80 >> bit)) kon_dialogFill_(px, l, x + bit * s, y + row * s, s, s, color); } } } } static void kon_dialogRender_(uint32_t *px, const kon_dialogLayout_t *l, kon_messageType_t type, int hover, int pressed) { int s = l->scale; uint32_t stripe = type == KON_MESSAGE_ERROR ? 0x00E05050u : (type == KON_MESSAGE_WARNING ? 0x00E0A030u : 0x005090E0u); uint32_t button = pressed ? 0x006060A0u : (hover ? 0x00505070u : 0x00404058u); kon_dialogFill_(px, l, 0, 0, l->width, l->height, 0x00202028u); kon_dialogFill_(px, l, 0, 0, 6 * s, l->height, stripe); for (int i = 0; i < l->lines; i++) { kon_dialogText_(px, l, 22 * s, 16 * s + i * 15 * s, l->text[i], 0x00E8E8F0u); } kon_dialogFill_(px, l, l->buttonX, l->buttonY, l->buttonW, l->buttonH, 0x00101018u); kon_dialogFill_(px, l, l->buttonX + s, l->buttonY + s, l->buttonW - 2 * s, l->buttonH - 2 * s, button); kon_dialogText_(px, l, l->buttonX + (l->buttonW - 16 * s) / 2, l->buttonY + (l->buttonH - 13 * s) / 2, "OK", 0x00FFFFFFu); } static int kon_dialogInButton_(const kon_dialogLayout_t *l, int x, int y) { return x >= l->buttonX && x < l->buttonX + l->buttonW && y >= l->buttonY && y < l->buttonY + l->buttonH; } /* its own connection, so the game window never loses events to the dialog. returns 0 if there is no display */ static int kon_dialogShow_(const char *title, const char *message, kon_messageType_t type) { Display *dpy = XOpenDisplay(NULL); if (!dpy) return 0; int screen = DefaultScreen(dpy); kon_dialogLayout_t layout; kon_dialogLayoutInit_(&layout, message, DisplayHeight(dpy, screen)); uint32_t *pixels = malloc((size_t)layout.width * (size_t)layout.height * sizeof(uint32_t)); if (!pixels) { XCloseDisplay(dpy); return 0; } int x = (DisplayWidth(dpy, screen) - layout.width) / 2; int y = (DisplayHeight(dpy, screen) - layout.height) / 3; Window win = XCreateSimpleWindow(dpy, RootWindow(dpy, screen), x, y, (unsigned)layout.width, (unsigned)layout.height, 0, 0, 0); XStoreName(dpy, win, title); XSelectInput(dpy, win, ExposureMask | KeyPressMask | ButtonPressMask | ButtonReleaseMask | PointerMotionMask | LeaveWindowMask); XSizeHints hints; memset(&hints, 0, sizeof(hints)); hints.flags = PMinSize | PMaxSize | PPosition; hints.min_width = hints.max_width = layout.width; hints.min_height = hints.max_height = layout.height; hints.x = x; hints.y = y; XSetWMNormalHints(dpy, win, &hints); Atom wmDelete = XInternAtom(dpy, "WM_DELETE_WINDOW", False); XSetWMProtocols(dpy, win, &wmDelete, 1); Atom windowType = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE", False); Atom typeDialog = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE_DIALOG", False); XChangeProperty(dpy, win, windowType, XA_ATOM, 32, PropModeReplace, (unsigned char *)&typeDialog, 1); Atom wmState = XInternAtom(dpy, "_NET_WM_STATE", False); Atom wmAbove = XInternAtom(dpy, "_NET_WM_STATE_ABOVE", False); XChangeProperty(dpy, win, wmState, XA_ATOM, 32, PropModeReplace, (unsigned char *)&wmAbove, 1); GC gc = XCreateGC(dpy, win, 0, NULL); XMapRaised(dpy, win); int hover = 0, pressed = 0, done = 0; while (!done) { XEvent ev; XNextEvent(dpy, &ev); switch (ev.type) { case KeyPress: { KeySym sym = XLookupKeysym(&ev.xkey, 0); if (sym == XK_Return || sym == XK_KP_Enter || sym == XK_Escape || sym == XK_space) done = 1; } break; case MotionNotify: hover = kon_dialogInButton_(&layout, ev.xmotion.x, ev.xmotion.y); ev.type = Expose; ev.xexpose.count = 0; break; case LeaveNotify: hover = 0; pressed = 0; ev.type = Expose; ev.xexpose.count = 0; break; case ButtonPress: if (ev.xbutton.button == Button1 && kon_dialogInButton_(&layout, ev.xbutton.x, ev.xbutton.y)) pressed = 1; ev.type = Expose; ev.xexpose.count = 0; break; case ButtonRelease: if (ev.xbutton.button == Button1) { if (pressed && kon_dialogInButton_(&layout, ev.xbutton.x, ev.xbutton.y)) done = 1; pressed = 0; } ev.type = Expose; ev.xexpose.count = 0; break; case ClientMessage: if ((Atom)ev.xclient.data.l[0] == wmDelete) done = 1; break; default: break; } if (!done && ev.type == Expose && ev.xexpose.count == 0) { kon_dialogRender_(pixels, &layout, type, hover, pressed); XImage *image = XCreateImage(dpy, DefaultVisual(dpy, screen), (unsigned)DefaultDepth(dpy, screen), ZPixmap, 0, (char *)pixels, (unsigned)layout.width, (unsigned)layout.height, 32, 0); if (image) { XPutImage(dpy, win, gc, image, 0, 0, 0, 0, (unsigned)layout.width, (unsigned)layout.height); image->data = NULL; XDestroyImage(image); } XFlush(dpy); } } XFreeGC(dpy, gc); XDestroyWindow(dpy, win); XCloseDisplay(dpy); free(pixels); return 1; } void kon_messageBox(const char *title, const char *message, kon_messageType_t type) { if (!title) title = ""; if (!message) message = ""; if (kon_dialogShow_(title, message, type)) return; fprintf(stderr, "%s: %s\n", title, message); } int kon_pollEvent(kon_window_t *window, kon_event_t *event) { if (!window || !kon_ctx || !event) return 0; kon_pumpX11_(window); if (!kon_popEvent_(&window->queue, event)) { event->type = KON_EVENT_NONE; return 0; } return 1; } bool kon_windowShouldClose(kon_window_t *window) { if (!window || !kon_ctx) return true; return window->shouldClose; } void kon_setShouldClose(kon_window_t *window, bool value) { if (window) window->shouldClose = value; } /* 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 #include struct kon_context { HINSTANCE hInstance; }; struct kon_window { HWND hwnd; bool shouldClose; bool isTransparent; bool resizable; unsigned char alpha; int exitKey; unsigned short highSurrogate; bool captured, hasLast; int lastX, lastY; kon_mainLoopFn liveFn; void *liveData; bool inLive; 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_PRIOR: return KON_KEY_PAGE_UP; case VK_NEXT: return KON_KEY_PAGE_DOWN; 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_CHAR: if (window) { unsigned int c = (unsigned int)wParam; kon_event_t ev = {0}; if (c >= 0xD800 && c <= 0xDBFF) { window->highSurrogate = (unsigned short)c; break; } if (c >= 0xDC00 && c <= 0xDFFF) { c = 0x10000 + (((unsigned int)window->highSurrogate - 0xD800) << 10) + (c - 0xDC00); window->highSurrogate = 0; } if (c < 32 || c == 127) break; ev.type = KON_EVENT_TEXT; ev.codepoint = c; 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_MOUSEWHEEL: if (window) { kon_event_t ev = {0}; POINT point; int delta = (int)(short)HIWORD(wParam); if (delta == 0) break; point.x = (int)(short)LOWORD(lParam); point.y = (int)(short)HIWORD(lParam); ScreenToClient(hwnd, &point); ev.type = KON_EVENT_MOUSE_WHEEL; ev.wheel = delta > 0 ? 1 : -1; ev.mouseX = (int)point.x; ev.mouseY = (int)point.y; 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); if (window->captured) { /* the cursor gets put back in the middle every time, so how far it got from there is how far it moved */ RECT area; POINT middle; GetClientRect(hwnd, &area); middle.x = (area.right - area.left) / 2; middle.y = (area.bottom - area.top) / 2; ev.mouseDX = ev.mouseX - (int)middle.x; ev.mouseDY = ev.mouseY - (int)middle.y; if (ev.mouseDX == 0 && ev.mouseDY == 0) break; ClientToScreen(hwnd, &middle); SetCursorPos(middle.x, middle.y); } else if (window->hasLast) { ev.mouseDX = ev.mouseX - window->lastX; ev.mouseDY = ev.mouseY - window->lastY; } window->lastX = ev.mouseX; window->lastY = ev.mouseY; window->hasLast = true; kon_pushEvent_(&window->queue, &ev); } break; case WM_KILLFOCUS: if (window) kon_setMouseCapture(window, false); 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_ENTERSIZEMOVE: if (window && window->liveFn) SetTimer(hwnd, 1, 8, NULL); break; case WM_EXITSIZEMOVE: KillTimer(hwnd, 1); break; case WM_TIMER: if (window && wParam == 1 && window->liveFn && !window->inLive) { window->inLive = true; window->liveFn(window->liveData); window->inLive = false; } return 0; 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->resizable = (flags & KON_WINDOW_RESIZABLE) != 0; window->alpha = 255; 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; } int kon_setMouseCapture(kon_window_t *window, bool captured) { if (!window || !window->hwnd) return 0; if (!captured) { if (!window->captured) return 1; window->captured = false; window->hasLast = false; ClipCursor(NULL); ReleaseCapture(); ShowCursor(TRUE); return 1; } if (window->captured) return 1; if (GetForegroundWindow() != window->hwnd) return 0; { RECT area; POINT topLeft, bottomRight, middle; GetClientRect(window->hwnd, &area); topLeft.x = area.left; topLeft.y = area.top; bottomRight.x = area.right; bottomRight.y = area.bottom; ClientToScreen(window->hwnd, &topLeft); ClientToScreen(window->hwnd, &bottomRight); area.left = topLeft.x; area.top = topLeft.y; area.right = bottomRight.x; area.bottom = bottomRight.y; ClipCursor(&area); middle.x = (area.right - area.left) / 2; middle.y = (area.bottom - area.top) / 2; ClientToScreen(window->hwnd, &middle); SetCursorPos(middle.x, middle.y); } SetCapture(window->hwnd); ShowCursor(FALSE); window->captured = true; return 1; } bool kon_isMouseCaptured(const kon_window_t *window) { return window && window->captured; } void kon_destroyWindow(kon_window_t *window) { if (!window) return; kon_setMouseCapture(window, false); if (window->hwnd) DestroyWindow(window->hwnd); free(window); } bool kon_windowShouldClose(kon_window_t *window) { if (!window) return true; return window->shouldClose; } void kon_setShouldClose(kon_window_t *window, bool value) { if (window) window->shouldClose = value; } 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; } void kon_getScreenSize(int *width, int *height) { if (width) *width = GetSystemMetrics(SM_CXSCREEN); if (height) *height = GetSystemMetrics(SM_CYSCREEN); } void kon_getCursorPos(int *x, int *y) { POINT p = { 0, 0 }; GetCursorPos(&p); if (x) *x = p.x; if (y) *y = p.y; } void kon_setCursorPos(int x, int y) { SetCursorPos(x, y); } void kon_setWindowTitle(kon_window_t *window, const char *title) { if (!window || !title) return; SetWindowText(window->hwnd, title); } void kon_setWindowDecorated(kon_window_t *window, bool decorated) { if (!window) return; DWORD style = (DWORD)GetWindowLongPtr(window->hwnd, GWL_STYLE); DWORD exStyle = (DWORD)GetWindowLongPtr(window->hwnd, GWL_EXSTYLE); RECT client; POINT topLeft = { 0, 0 }; GetClientRect(window->hwnd, &client); ClientToScreen(window->hwnd, &topLeft); style &= ~(DWORD)(WS_OVERLAPPEDWINDOW | WS_POPUP); if (decorated) { style |= WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX; if (window->resizable) style |= WS_THICKFRAME | WS_MAXIMIZEBOX; } else { style |= WS_POPUP; } SetWindowLongPtr(window->hwnd, GWL_STYLE, (LONG_PTR)style); RECT outer = { 0, 0, client.right, client.bottom }; AdjustWindowRectEx(&outer, style, FALSE, exStyle); SetWindowPos(window->hwnd, NULL, topLeft.x + outer.left, topLeft.y + outer.top, outer.right - outer.left, outer.bottom - outer.top, SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED); } void kon_setWindowTopmost(kon_window_t *window, bool topmost) { if (!window) return; SetWindowPos(window->hwnd, topmost ? HWND_TOPMOST : HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE); } static void kon_applyAlpha_(kon_window_t *window) { if (window->isTransparent) return; DWORD exStyle = (DWORD)GetWindowLongPtr(window->hwnd, GWL_EXSTYLE); SetWindowLongPtr(window->hwnd, GWL_EXSTYLE, (LONG_PTR)(exStyle | WS_EX_LAYERED)); SetLayeredWindowAttributes(window->hwnd, 0, window->alpha, LWA_ALPHA); } void kon_setWindowOpacity(kon_window_t *window, float opacity) { if (!window) return; if (opacity < 0.0f) opacity = 0.0f; if (opacity > 1.0f) opacity = 1.0f; window->alpha = (unsigned char)(opacity * 255.0f + 0.5f); kon_applyAlpha_(window); } void kon_setWindowClickThrough(kon_window_t *window, bool enabled) { if (!window) return; DWORD exStyle = (DWORD)GetWindowLongPtr(window->hwnd, GWL_EXSTYLE); if (enabled) exStyle |= WS_EX_TRANSPARENT | WS_EX_LAYERED; else exStyle &= ~(DWORD)WS_EX_TRANSPARENT; SetWindowLongPtr(window->hwnd, GWL_EXSTYLE, (LONG_PTR)exStyle); if (enabled) kon_applyAlpha_(window); } void kon_messageBox(const char *title, const char *message, kon_messageType_t type) { UINT icon = type == KON_MESSAGE_ERROR ? MB_ICONERROR : (type == KON_MESSAGE_WARNING ? MB_ICONWARNING : MB_ICONINFORMATION); MessageBox(NULL, message ? message : "", title ? title : "", MB_OK | MB_SETFOREGROUND | icon); } int kon_pollEvent(kon_window_t *window, kon_event_t *event) { if (!window || !event) return 0; if (!window->inLive) { 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_setLiveCallback(kon_window_t *window, kon_mainLoopFn callback, void *userData) { if (!window) return; window->liveFn = callback; window->liveData = userData; } 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, window->alpha, 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 */ #ifndef _WIN32 void kon_setLiveCallback(kon_window_t *window, kon_mainLoopFn callback, void *userData) { (void)window; (void)callback; (void)userData; } #endif /*** 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) { kon_setLiveCallback(window, callback, userData); while (!kon_windowShouldClose(window)) { callback(userData); } } #endif /* __EMSCRIPTEN__ */ #endif /* end of KONWINLIB_IMPLEMENTATION */ #endif