summaryrefslogtreecommitdiff
path: root/kon/konwinlib.h
diff options
context:
space:
mode:
authorKonAki <konaki@anokonaki.org>2026-10-10 18:37:04 +0800
committerKonAki <konaki@anokonaki.org>2026-10-10 18:37:04 +0800
commit0bfbeec1abdf937e3658fe07f75de22fdb2e7bea (patch)
tree735916fff4b2884fc5d622c4cc934991e9c3c103 /kon/konwinlib.h
parent996dcc5002d1d5961d54d924ae4a44f31898024e (diff)
add mouse capture and relative mouse movement
Diffstat (limited to 'kon/konwinlib.h')
-rw-r--r--kon/konwinlib.h171
1 files changed, 170 insertions, 1 deletions
diff --git a/kon/konwinlib.h b/kon/konwinlib.h
index 0a1f365..1570931 100644
--- a/kon/konwinlib.h
+++ b/kon/konwinlib.h
@@ -110,6 +110,7 @@ typedef struct kon_event {
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;
@@ -128,6 +129,11 @@ 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 {
@@ -182,7 +188,13 @@ static void kon_pushEvent_(kon_eventQueue_t *q, const kon_event_t *event) {
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;
}
}
@@ -212,6 +224,7 @@ struct kon_context {
struct kon_window {
char canvasID[64];
+ bool captured;
bool shouldClose;
int exitKey;
kon_eventQueue_t queue;
@@ -369,6 +382,8 @@ static EM_BOOL kon_mouseCallback_(int eventType, const EmscriptenMouseEvent *e,
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;
@@ -428,9 +443,27 @@ kon_window_t *kon_createWindow(const char *title, int x, int y, int width, int h
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);
@@ -702,6 +735,9 @@ struct kon_window {
bool shouldClose;
int exitKey;
int width, height;
+ bool captured, hasLast;
+ int lastX, lastY;
+ Cursor blankCursor;
kon_eventQueue_t queue;
};
@@ -802,7 +838,7 @@ kon_window_t *kon_createWindow(const char *title, int x, int y, int width, int h
}
XStoreName(kon_ctx->display, window->window, title);
- XSelectInput(kon_ctx->display, window->window, ExposureMask | StructureNotifyMask | KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | PointerMotionMask);
+ 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);
@@ -845,10 +881,51 @@ kon_window_t *kon_createWindow(const char *title, int x, int y, int width, int h
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);
@@ -990,9 +1067,28 @@ static void kon_pumpX11_(kon_window_t *window) {
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;
}
@@ -1499,6 +1595,8 @@ struct kon_window {
unsigned char alpha;
int exitKey;
unsigned short highSurrogate;
+ bool captured, hasLast;
+ int lastX, lastY;
kon_mainLoopFn liveFn;
void *liveData;
bool inLive;
@@ -1688,9 +1786,32 @@ LRESULT CALLBACK kon_wndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
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};
@@ -1823,8 +1944,56 @@ kon_window_t *kon_createWindow(const char *title, int x, int y, int width, int h
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);
}