summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKonAki <konaki@anokonaki.org>2026-10-09 02:50:22 +0800
committerKonAki <konaki@anokonaki.org>2026-10-09 02:50:22 +0800
commit72ab7350ba8d5a1471f2bbbadd7f225065dd125d (patch)
tree7c48c117067bda1eb41d741cd3249286250c036a
parent12c955cf1911edc3fbab9d675c14204058b4a134 (diff)
Add window tricks: opacity, topmost, click through, message box
-rwxr-xr-xbuild.sh2
-rw-r--r--kon/konwinlib.h308
-rw-r--r--src/engine.c72
3 files changed, 378 insertions, 4 deletions
diff --git a/build.sh b/build.sh
index b441b9a..de6df2f 100755
--- a/build.sh
+++ b/build.sh
@@ -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