Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/DRessedAlarm184/LWXGL/llms.txt

Use this file to discover all available pages before exploring further.

LWXGL dispatches X11 events internally via GHandleWindowEvents(). Attach C function callbacks to receive keyboard, mouse, and window events before entering the loop, and query live input state mid-frame using the query functions.
Button onclick callbacks, the key callback, and the click callback all run inside GHandleWindowEvents. They must be non-blocking — do not call sleep, perform heavy computation, or do blocking I/O inside them, as this will stall the entire event loop and freeze the window.

Event Processing

void GHandleWindowEvents(void);
GHandleWindowEvents calls XPending in a loop and processes every queued Xlib event — motion, key presses and releases, button presses and releases, WM messages, and resize notifications. It must be called once per frame in the manual loop. GSimpleWindowLoop calls it automatically. Event handlers fire synchronously during this call.

Keyboard Events

void GEventAttachKey(void (*Key)(int key));
Attaches a callback that fires on each key press. Only one callback can be registered at a time — calling this again replaces the previous one. The key argument is:
  • The ASCII value of the character for printable keys (e.g., 'a' = 97, ' ' = 32).
  • A special constant for non-printable keys:
ConstantValueKey
LWXGL_KEY_LEFT170Left arrow
LWXGL_KEY_RIGHT171Right arrow
LWXGL_KEY_UP172Up arrow
LWXGL_KEY_DOWN173Down arrow
LWXGL_KEY_FN+1151F1
LWXGL_KEY_FN+2152F2
LWXGL_KEY_FN+12162F12
LWXGL_KEY_FN is defined as 150, so F-key N has value LWXGL_KEY_FN + N.
The key callback is not fired when Ctrl+Escape is pressed (that triggers GDeleteWindow directly), nor when F12 is pressed (that toggles the debug overlay when using GSimpleWindowLoop). Input element keystrokes and modal dialogs also intercept key events before the callback fires.
#include "libLWXGL.h"
#include <stdio.h>

void on_key(int key) {
    if (key == LWXGL_KEY_UP)    printf("Up arrow\n");
    if (key == LWXGL_KEY_DOWN)  printf("Down arrow\n");
    if (key == LWXGL_KEY_LEFT)  printf("Left arrow\n");
    if (key == LWXGL_KEY_RIGHT) printf("Right arrow\n");

    if (key == (LWXGL_KEY_FN + 1)) printf("F1 pressed\n");

    if (key >= 32 && key < 127)
        printf("Character: %c\n", (char)key);
}

/* Before the loop: */
GEventAttachKey(on_key);

Mouse Events

void GEventAttachClick(void (*Click)(int x, int y, int btn));
Attaches a callback that fires on mouse button release. The callback only fires if:
  • No modal dialog is currently open (GQueryModalOpen() returns 0).
  • The release did not land on a button, checkbox, or scrollable console element — those widgets consume the event first.
ParameterDescription
x, yWindow-relative position of the cursor at the time of release.
btnX11 button number: 1 = left, 2 = middle, 3 = right, 4 = scroll up, 5 = scroll down.
void on_click(int x, int y, int btn) {
    if (btn == 1) {
        printf("Left click at (%d, %d)\n", x, y);
    } else if (btn == 3) {
        printf("Right click at (%d, %d)\n", x, y);
    }
}

GEventAttachClick(on_click);

Querying Mouse State

void GQueryMouse(int* x, int* y, int* btn);
Fills the provided pointers with the current mouse position and pressed button. Can be called at any point during a frame — the values reflect the most recent MotionNotify and ButtonPress events seen by GHandleWindowEvents.
  • *x, *y — cursor position in window coordinates. Both are -1 when the cursor is outside the window.
  • *btn — the currently held button number (1/2/3), or 0 if no button is pressed.
void on_frame(int tick, float dt) {
    int mx, my, mb;
    GQueryMouse(&mx, &my, &mb);

    if (mb == 1) {
        /* Paint a pixel on the canvas wherever the mouse is held */
        unsigned char* pixels = GGetImageData(CANVAS_ID);
        if (mx >= 0 && my >= 0 && mx < CANVAS_W && my < CANVAS_H) {
            pixels[my * CANVAS_W + mx] = 4; /* dark red */
            GUpdateImage(CANVAS_ID);
        }
    }
    (void)tick; (void)dt;
}

Querying Keyboard State

unsigned char* GQueryKeyboard(void);
int            GQueryKeyDown(int ch);
GQueryKeyboard returns a pointer to an internal 8-element unsigned char array of currently held key values. Each slot holds the translated character value of a key that is currently pressed (same values as the Key callback). Empty slots are 0. GQueryKeyDown scans the same array and returns 1 if ch is currently held, 0 otherwise. This is the convenient one-liner for polling a specific key. Both functions reflect the state maintained by EKeyPress and EKeyRelease events — the array is updated inside GHandleWindowEvents.
/* Move a sprite with WASD keys, framerate-independent using delta time */
float sprite_x = 100.0f, sprite_y = 100.0f;
const float SPEED = 150.0f; /* pixels per second */

void on_frame(int tick, float dt) {
    if (GQueryKeyDown('w')) sprite_y -= SPEED * dt;
    if (GQueryKeyDown('s')) sprite_y += SPEED * dt;
    if (GQueryKeyDown('a')) sprite_x -= SPEED * dt;
    if (GQueryKeyDown('d')) sprite_x += SPEED * dt;

    /* Update element position */
    GElemModifyBounds(SPRITE_ID,
        (int)sprite_x, (int)sprite_y,
        SPRITE_W, SPRITE_H);

    (void)tick;
}

Window Close Event

void GEventAttachDelete(int (*on_exit)(void));
Attaches a callback that fires when the user requests to close the window — either via the WM close button, GDeleteWindow(), or Ctrl+Escape.
  • Return 1 to allow the close (sets the close flag, GWindowShouldClose() will return 1).
  • Return 0 to block the close (the window stays open).
If no delete handler is registered, closing is always allowed.
int has_unsaved = 0;

int on_close(void) {
    if (has_unsaved) {
        /* Block close and show a confirmation modal instead */
        GSpawnModal(1, "Unsaved changes!\nClose anyway?", NULL);
        return 0; /* block */
    }
    return 1; /* allow */
}

GEventAttachDelete(on_close);

Resize Events

After calling GEnableResizing, the resize callback registered there fires with the new w and h every time the window dimensions change:
void GEnableResizing(void (*Resize)(int w, int h));
The resize event is debounced — if multiple ConfigureNotify events are queued, only the last one is processed. The internal back-buffer pixmap is automatically recreated at the new size before the callback fires.
void on_resize(int w, int h) {
    /* Reposition or scale elements to fit the new window size */
    GElemModifyBounds(BG_RECT_ID, 0, 0, w, h);
    GElemModifyBounds(CANVAS_ID, 0, 0, w, h);
}

GEnableResizing(on_resize);

Build docs developers (and LLMs) love