diff options
| author | KonAki <konaki@anokonaki.org> | 2026-10-09 02:50:22 +0800 |
|---|---|---|
| committer | KonAki <konaki@anokonaki.org> | 2026-10-09 02:50:22 +0800 |
| commit | 72ab7350ba8d5a1471f2bbbadd7f225065dd125d (patch) | |
| tree | 7c48c117067bda1eb41d741cd3249286250c036a | |
| parent | 12c955cf1911edc3fbab9d675c14204058b4a134 (diff) | |
Add window tricks: opacity, topmost, click through, message box
| -rwxr-xr-x | build.sh | 2 | ||||
| -rw-r--r-- | kon/konwinlib.h | 308 | ||||
| -rw-r--r-- | src/engine.c | 72 |
3 files changed, 378 insertions, 4 deletions
@@ -42,7 +42,7 @@ EXE= DEFS= case $platform in linux|bsd) - LDLIBS="-lX11 -lXrandr -lm" + LDLIBS="-lX11 -lXext -lXrandr -lm" DEFS="-D_POSIX_C_SOURCE=200809L" ;; windows) diff --git a/kon/konwinlib.h b/kon/konwinlib.h index a718147..17b7068 100644 --- a/kon/konwinlib.h +++ b/kon/konwinlib.h @@ -23,6 +23,10 @@ * SOFTWARE. */ +#if !defined(_WIN32) && !defined(_POSIX_C_SOURCE) +#define _POSIX_C_SOURCE 200809L +#endif + #ifndef KONWINLIB_H #define KONWINLIB_H @@ -122,6 +126,32 @@ 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); +/*** 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); + +/* a real os dialog, waits until it is closed. works without a window. X11 needs zenity, kdialog or xmessage, otherwise it prints to stderr */ +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); @@ -431,6 +461,53 @@ void kon_getWindowSize(kon_window_t *window, int *width, int *height) { *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; @@ -477,6 +554,9 @@ void kon_blitPixels(kon_window_t *window, const uint32_t *pixels, int width, int #include <X11/Xutil.h> #include <X11/Xatom.h> #include <X11/extensions/Xrandr.h> +#include <X11/extensions/shape.h> +#include <unistd.h> +#include <sys/wait.h> #include <X11/keysym.h> #include <string.h> @@ -891,6 +971,139 @@ static void kon_pumpX11_(kon_window_t *window) { } } +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); +} + +/* returns 0 when the program does not exist */ +static int kon_runTool_(char *const argv[]) { + pid_t pid = fork(); + + if (pid < 0) return 0; + if (pid == 0) { + execvp(argv[0], argv); + _exit(127); + } + + int status = 0; + if (waitpid(pid, &status, 0) < 0) return 0; + return WIFEXITED(status) && WEXITSTATUS(status) != 127; +} + +void kon_messageBox(const char *title, const char *message, kon_messageType_t type) { + if (!title) title = ""; + if (!message) message = ""; + + const char *zenityKind = type == KON_MESSAGE_ERROR ? "--error" : (type == KON_MESSAGE_WARNING ? "--warning" : "--info"); + const char *kdialogKind = type == KON_MESSAGE_ERROR ? "--error" : (type == KON_MESSAGE_WARNING ? "--sorry" : "--msgbox"); + + char *zenity[] = { "zenity", (char *)zenityKind, "--title", (char *)title, "--text", (char *)message, NULL }; + char *kdialog[] = { "kdialog", (char *)kdialogKind, (char *)message, "--title", (char *)title, NULL }; + char *xmessage[] = { "xmessage", "-center", "-title", (char *)title, (char *)message, NULL }; + + if (kon_runTool_(zenity)) return; + if (kon_runTool_(kdialog)) return; + if (kon_runTool_(xmessage)) 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; @@ -943,6 +1156,8 @@ struct kon_window { HWND hwnd; bool shouldClose; bool isTransparent; + bool resizable; + unsigned char alpha; int exitKey; unsigned short highSurrogate; kon_mainLoopFn liveFn; @@ -1198,6 +1413,8 @@ kon_window_t *kon_createWindow(const char *title, int x, int y, int width, int h 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; @@ -1304,6 +1521,95 @@ void kon_getWindowSize(kon_window_t *window, int *width, int *height) { *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; @@ -1354,7 +1660,7 @@ void kon_blitPixels(kon_window_t *window, const uint32_t *pixels, int width, int POINT dstPos = { winRect.left, winRect.top }; POINT srcPos = { 0, 0 }; SIZE size = { width, height }; - BLENDFUNCTION blend = { AC_SRC_OVER, 0, 255, AC_SRC_ALPHA }; + BLENDFUNCTION blend = { AC_SRC_OVER, 0, window->alpha, AC_SRC_ALPHA }; UpdateLayeredWindow(window->hwnd, screenDC, &dstPos, &size, memDC, &srcPos, 0, &blend, ULW_ALPHA); diff --git a/src/engine.c b/src/engine.c index efe760e..3dc442c 100644 --- a/src/engine.c +++ b/src/engine.c @@ -1,5 +1,7 @@ #include "konwinlib.h" #include "konsofren.h" +#include <stdlib.h> + #include "kontime.h" #ifdef KON_MODULE_DEBUG @@ -23,15 +25,66 @@ typedef struct app { kon_assets_t *assets; kon_font_t *font; kon_debug_t *debug; + float shakeLeft; + int baseX, baseY; #endif } app_t; +#ifdef KON_MODULE_DEBUG +static void cmdOpacity(kon_debug_t *debug, void *user, int argc, char **argv) +{ + app_t *app = user; + + (void)debug; + if (argc > 1) kon_setWindowOpacity(app->window, (float)atof(argv[1])); +} + +static void cmdTopmost(kon_debug_t *debug, void *user, int argc, char **argv) +{ + app_t *app = user; + + (void)debug; + if (argc > 1) kon_setWindowTopmost(app->window, atoi(argv[1]) != 0); +} + +static void cmdBorderless(kon_debug_t *debug, void *user, int argc, char **argv) +{ + app_t *app = user; + + (void)debug; + if (argc > 1) kon_setWindowDecorated(app->window, atoi(argv[1]) == 0); +} + +static void cmdClickThrough(kon_debug_t *debug, void *user, int argc, char **argv) +{ + app_t *app = user; + + (void)debug; + if (argc > 1) kon_setWindowClickThrough(app->window, atoi(argv[1]) != 0); +} + +static void cmdMessage(kon_debug_t *debug, void *user, int argc, char **argv) +{ + (void)debug; (void)user; + kon_messageBox("konengine", argc > 1 ? argv[1] : "hello", KON_MESSAGE_INFO); +} + +static void cmdShake(kon_debug_t *debug, void *user, int argc, char **argv) +{ + app_t *app = user; + + (void)debug; (void)argc; (void)argv; + kon_getWindowPos(app->window, &app->baseX, &app->baseY); + app->shakeLeft = 0.5f; +} +#endif + static void frame(void *data) { app_t *app = (app_t *)data; kon_display_t *display = app->display; kon_event_t ev; - kon_time now; + kon_time now, dt; kon_mat4_t model, viewProjection; while (kon_pollEvent(app->window, &ev)) { @@ -48,9 +101,18 @@ static void frame(void *data) } now = kon_getTime(); - app->angle += (float)(now - app->last); + dt = now - app->last; + app->angle += (float)dt; app->last = now; +#ifdef KON_MODULE_DEBUG + if (app->shakeLeft > 0.0f) { + app->shakeLeft -= (float)dt; + if (app->shakeLeft > 0.0f) kon_setWindowPos(app->window, app->baseX + rand() % 21 - 10, app->baseY + rand() % 21 - 10); + else kon_setWindowPos(app->window, app->baseX, app->baseY); + } +#endif + model = kon_mat4Identity(); model = kon_mat4Rotate(model, KON_VEC3(app->angle * 0.5f, app->angle, 0)); viewProjection = kon_cameraViewProjection(&app->camera, display->game->width, display->game->height); @@ -99,6 +161,12 @@ int main(void) if (ttf) app.font = kon_loadFont(ttf->data, ttf->size, 14.0f); app.debug = kon_debugCreate(app.font); kon_debugSetAssets(app.debug, app.assets); + kon_debugCommand(app.debug, "opacity", "window opacity 0 to 1", cmdOpacity, &app); + kon_debugCommand(app.debug, "topmost", "topmost 0 or 1", cmdTopmost, &app); + kon_debugCommand(app.debug, "borderless", "borderless 0 or 1", cmdBorderless, &app); + kon_debugCommand(app.debug, "clickthrough", "clickthrough 0 or 1", cmdClickThrough, &app); + kon_debugCommand(app.debug, "message", "message \"text\"", cmdMessage, &app); + kon_debugCommand(app.debug, "shake", "shake the window", cmdShake, &app); kon_debugLog(app.debug, ttf ? "loaded font.ttf" : "put a font.ttf in assets/ for the console"); } #endif |
