Index Types & Handles Window & Input GDI System & Files File I/O Multimedia

Window Management & Input

This page covers the complete WinUser subset: window class registration, window creation and lifetime, the WinAPI message loop, keyboard and mouse input, cursor control, user-mode timers, and geometry helpers.

WNDCLASSA & RegisterClassA

Before creating a window you must register a window class. The class defines the window procedure that will receive messages.

WNDCLASSA structure

FieldTypeUsed by Free API?Notes
lpfnWndProcWNDPROCYesMandatory. The message handler function called by DispatchMessageA.
lpszClassNameLPCSTRYesName used in CreateWindowA to look up this class.
hInstanceHINSTANCEStored, ignoredPass NULL or the value from WinMain; not used internally.
styleUINTIgnoredCS_VREDRAW, CS_HREDRAW defined but not acted upon.
hIcon, hCursor, hbrBackgroundhandlesStored, ignoredIcon / cursor / background paint not implemented.
lpszMenuNameLPCSTRIgnoredMenus not supported.
cbClsExtra, cbWndExtraintIgnoredExtra bytes — not allocated.

RegisterClassA

Signature
ATOM WINAPI RegisterClassA(const WNDCLASSA* lpWndClass);

Stores the class-name → WNDPROC mapping in an internal table. Returns a non-zero atom on success, 0 on failure (e.g. NULL lpWndClass or NULL lpszClassName).

C++ — register a window class
WNDCLASSA wc = {};                       // zero-init all fields
wc.lpfnWndProc   = MyWindowProc;        // required
wc.hInstance     = hInstance;
wc.lpszClassName = "GameWindow";        // required
wc.style         = CS_HREDRAW | CS_VREDRAW;

ATOM a = RegisterClass(&wc);
if (!a) {
    OutputDebugString("RegisterClass failed!");
    return -1;
}
      

Creating and managing windows

CreateWindowExA / CreateWindowA

Signature
HWND WINAPI CreateWindowExA(
    DWORD   dwExStyle,     // extended style, e.g. WS_EX_TOPMOST
    LPCSTR  lpClassName,   // class name from RegisterClassA
    LPCSTR  lpWindowName,  // title bar text
    DWORD   dwStyle,       // window style flags
    int     X, int Y,      // initial position
    int     nWidth, int nHeight, // initial size
    HWND    hWndParent,    // parent (ignored: child semantics not supported)
    HMENU   hMenu,         // NULL
    HINSTANCE hInstance,
    LPVOID  lpParam        // passed in WM_CREATE's CREATESTRUCT.lpCreateParams
);

// Convenience macro (no extended style):
HWND WINAPI CreateWindowA(lpClassName, lpWindowName, dwStyle,
                          X, Y, nWidth, nHeight,
                          hWndParent, hMenu, hInstance, lpParam);
      

Window style flags

ConstantValueBehaviour in Free API
WS_OVERLAPPEDWINDOW0x00CF0000Standard window with title bar and borders (default for most games).
WS_POPUP0x80000000Borderless window — SDL window created without decorations.
WS_POPUPWINDOW0x80880000Popup with border.
WS_CAPTION0x00C00000Title bar.
WS_VISIBLE0x10000000Window is shown immediately (equivalent to ShowWindow(SW_SHOW)).
WS_CHILD0x40000000Parsed but child-window coordinate semantics are not implemented.
WS_EX_TOPMOST0x00000008Extended style: always-on-top SDL hint set.
C++ — create a game window
// Full-screen-like popup window (no title bar)
HWND hwnd = CreateWindowEx(
    0,                   // no extended styles
    "GameWindow",        // class name registered above
    "Speedy Blupi",      // window title
    WS_POPUP | WS_VISIBLE,
    0, 0,                // top-left corner
    640, 480,            // resolution
    NULL, NULL, hInstance, NULL);

if (!hwnd) {
    OutputDebugString("CreateWindow failed");
    return -1;
}
      

ShowWindow, DestroyWindow, UpdateWindow

FunctionStatusDescription
ShowWindow(HWND, SW_SHOW)PARTIALRaises and shows the SDL window. SW_HIDE hides it.
DestroyWindow(HWND)PARTIALSends WM_DESTROY, destroys the SDL window, removes the HWND from the registry.
UpdateWindow(HWND)PARTIALPresents / raises the SDL window.
MoveWindow(HWND, x, y, w, h, repaint)PARTIALRepositions and resizes via SDL. repaint is ignored.
SetWindowTextA(HWND, LPCSTR)PARTIALSets the SDL window title.
GetClientRect(HWND, RECT*)PARTIALReturns {0, 0, w, h} from SDL window size.
AdjustWindowRect(RECT*, style, menu)STUBReturns TRUE without modifying the rect.
InvalidateRect(HWND, RECT*, erase)STUBReturns TRUE; no WM_PAINT queued.
GetSystemMetrics(nIndex)PARTIALSupports SM_CXSCREEN, SM_CYSCREEN (display size), SM_CYCAPTION (returns 19).
SetFocus(HWND)PARTIALRaises the SDL window.
MessageBoxA(HWND, text, caption, type)STUBLogs to SDL log; returns IDOK (1).

WinAPI message loop

The message loop is the heart of any Win32 application. Free API translates SDL3 events into WM_* messages and delivers them through the same PeekMessage → DispatchMessage → WNDPROC pipeline as real Windows.

Game calls PeekMessageA() each frame
    │
    ├─ SDL_PumpEvents()          pull OS events into SDL internal queue
    ├─ PumpSdlEvents()           translate SDL events → Free API WM_* queue
    │
    └─ dequeue one message or return FALSE
              │
              ▼
        TranslateMessage()        WM_KEYDOWN → WM_CHAR for printable keys
              │
              ▼
        DispatchMessageA()        look up HWND → WNDPROC, call it
              │
              ▼
        Your WNDPROC(hWnd, uMsg, wParam, lParam)
      

Loop functions

FunctionStatusDescription
PeekMessageA(MSG*, HWND, min, max, flags) IMPLEMENTED Pumps SDL on every call (prevents input starvation). Returns TRUE and fills MSG if a message is available. flags: PM_REMOVE dequeues the message; PM_NOREMOVE peeks without removing.
GetMessageA(MSG*, HWND, min, max) PARTIAL Blocks until a message arrives (busy-waits with 1 ms sleep). Returns FALSE when WM_QUIT is dequeued.
TranslateMessage(MSG*) PARTIAL When msg.message == WM_KEYDOWN and wParam is a printable ASCII character, posts a WM_CHAR message. Required for name-entry screens.
DispatchMessageA(MSG*) PARTIAL Looks up the WNDPROC for msg.hwnd and calls it. Falls back to DefWindowProcA if not found.
PostMessageA(HWND, Msg, wParam, lParam) PARTIAL Enqueues a message into the global mutex-protected queue. Thread-safe — can be called from a multimedia timer callback.
PostQuitMessage(nExitCode) IMPLEMENTED Posts WM_QUIT with the given exit code as wParam. Causes GetMessageA to return FALSE.
WaitMessage() PARTIAL Busy-waits with 1 ms sleeps until the queue has at least one message.
DefWindowProcA(HWND, Msg, wParam, lParam) PARTIAL Default handler. Handles WM_CLOSE by posting WM_QUIT. All other messages return 0.
C++ — two common message loop patterns
// Pattern 1: PeekMessage loop (non-blocking, good for games that render every frame)
MSG msg = {};
while (msg.message != WM_QUIT) {
    if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    } else {
        GameUpdate();  // called every frame when no messages are pending
        GameRender();
    }
}

// Pattern 2: GetMessage loop (blocking, good for event-driven apps)
MSG msg;
while (GetMessage(&msg, NULL, 0, 0)) {
    TranslateMessage(&msg);
    DispatchMessage(&msg);
}
return (int)msg.wParam;  // exit code from PostQuitMessage
      

Win32 message constants

ConstantValueWhen sentwParam / lParam
WM_NULL0x0000No-op message.
WM_CREATE0x0001Window just created (inside CreateWindowA).lParam = CREATESTRUCT*
WM_DESTROY0x0002Window is being destroyed.
WM_CLOSE0x0010User closed the window (title bar X).
WM_QUIT0x0012Posted by PostQuitMessage; terminates the loop.wParam = exit code
WM_ACTIVATEAPP0x001CApp focus changed. Focus-lost is suppressed.wParam=1 (activated)
WM_KEYDOWN0x0100Key pressed.wParam = VK_* code
WM_KEYUP0x0101Key released.wParam = VK_* code
WM_CHAR0x0102Printable character (from TranslateMessage or SDL_EVENT_TEXT_INPUT).wParam = char code
WM_SYSKEYDOWN0x0104System key pressed (F10 on Windows parity).wParam = VK_F10
WM_SYSKEYUP0x0105System key released.wParam = VK_F10
WM_TIMER0x0113User timer fired (from SetTimer).wParam = timer ID
WM_MOUSEMOVE0x0200Mouse cursor moved.lParam=(y<<16)|x; wParam=MK_*
WM_LBUTTONDOWN0x0201Left button pressed.lParam=(y<<16)|x; wParam=MK_*
WM_LBUTTONUP0x0202Left button released.same
WM_RBUTTONDOWN0x0204Right button pressed.same
WM_RBUTTONUP0x0205Right button released.same
WM_MBUTTONDOWN0x0207Middle button pressed.same
WM_MBUTTONUP0x0208Middle button released.same
WM_MOUSEWHEEL0x020AScroll wheel moved.wParam high word = delta (WHEEL_DELTA=120)
WM_USER0x0400Base for app-defined messages.Application-defined

Keyboard — VK_* virtual-key codes

SDL3 key events are translated to WM_KEYDOWN / WM_KEYUP messages with a VK_* code in wParam. The full set used by Speedy Blupi and Planet Blupi is implemented.

ConstantValueKey
VK_BACK0x08Backspace
VK_TAB0x09Tab
VK_RETURN0x0DEnter / Return
VK_SHIFT0x10Shift (either)
VK_CONTROL0x11Ctrl (either)
VK_MENU0x12Alt (either)
VK_PAUSE0x13Pause / Break
VK_ESCAPE0x1BEscape
VK_SPACE0x20Space bar
VK_PRIOR0x21Page Up
VK_NEXT0x22Page Down
VK_END0x23End
VK_HOME0x24Home
VK_LEFT0x25Left arrow
VK_UP0x26Up arrow
VK_RIGHT0x27Right arrow
VK_DOWN0x28Down arrow
VK_INSERT0x2DInsert
VK_DELETE0x2EDelete
0x300x39Digits 0–9 (ASCII value)
0x410x5ALetters A–Z (uppercase ASCII)
VK_F1VK_F90x70–0x78Function keys F1–F9
VK_F100x79F10 — uses WM_SYSKEYDOWN/UP
VK_F110x7AF11
VK_F120x7BF12
C++ — keyboard handler in WNDPROC
case WM_KEYDOWN:
    switch (wParam) {

    // Navigation
    case VK_LEFT:   MovePlayerLeft();  break;
    case VK_RIGHT:  MovePlayerRight(); break;
    case VK_UP:     MovePlayerUp();    break;
    case VK_DOWN:   MovePlayerDown();  break;

    // Actions
    case VK_SPACE:  Jump();   break;
    case VK_RETURN: Action(); break;

    // Function keys
    case VK_F1:  ShowHelp();  break;
    case VK_F5:  QuickSave(); break;
    case VK_F9:  QuickLoad(); break;

    // Letters: wParam is uppercase ASCII
    case 'P':  TogglePause();  break;
    case 'M':  ToggleMusic();  break;

    // Quit
    case VK_ESCAPE: PostQuitMessage(0); break;
    }
    return 0;

case WM_CHAR:
    // Character input (name entry screen)
    HandleCharacterInput((char)wParam);
    return 0;
          
F10 special case On real Windows, F10 triggers WM_SYSKEYDOWN / WM_SYSKEYUP instead of the normal WM_KEYDOWN/UP. Free API replicates this behaviour exactly so games that check for F10 work correctly.

Mouse events & cursor

Mouse button state flags (wParam)

FlagValueMeaning
MK_LBUTTON0x0001Left mouse button is currently held down.
MK_RBUTTON0x0002Right mouse button is currently held down.
MK_SHIFT0x0004Shift key is held.
MK_CONTROL0x0008Ctrl key is held.
MK_MBUTTON0x0010Middle mouse button is currently held down.
C++ — mouse handler in WNDPROC
case WM_MOUSEMOVE: {
    int x = LOWORD(lParam);                  // cursor X in client coords
    int y = HIWORD(lParam);                  // cursor Y in client coords
    BOOL lBtn = (wParam & MK_LBUTTON) != 0; // left button held?
    BOOL shift = (wParam & MK_SHIFT) != 0;
    OnMouseMove(x, y, lBtn, shift);
    return 0;
}

case WM_LBUTTONDOWN: {
    int x = LOWORD(lParam);
    int y = HIWORD(lParam);
    OnLeftClick(x, y);
    return 0;
}

case WM_LBUTTONUP:
    OnLeftRelease(LOWORD(lParam), HIWORD(lParam));
    return 0;

case WM_RBUTTONDOWN:
    OnRightClick(LOWORD(lParam), HIWORD(lParam));
    return 0;
      

Cursor functions

FunctionStatusDescription
GetCursorPos(LPPOINT lpPoint)PARTIALReads screen-space cursor position via SDL_GetGlobalMouseState. Returns TRUE on success.
SetCursorPos(int X, int Y)PARTIALWarps cursor to screen position via SDL_WarpMouseGlobal. Returns TRUE on success.
ClientToScreen(HWND, LPPOINT)PARTIALConverts client coordinates to screen coordinates by adding the SDL window's screen position.
ScreenToClient(HWND, LPPOINT)PARTIALConverts screen coordinates to client coordinates by subtracting the SDL window's screen position.
ShowCursor(BOOL)STUBReturns a display counter; does not actually hide/show the SDL cursor.
SetCursor(HCURSOR)STUBReturns NULL.
LoadCursorA(hInst, name)STUBReturns NULL. Pass IDC_ARROW or any value — it is ignored.
LoadIconA(hInst, name)STUBReturns NULL.
C++ — cursor position in client coordinates
POINT pt;
GetCursorPos(&pt);           // pt is in screen (global) coordinates
ScreenToClient(hwnd, &pt);   // convert to client-area coordinates

if (pt.x >= 0 && pt.y >= 0 && pt.x < 640 && pt.y < 480) {
    // cursor is inside the window client area
}
      

SetTimer / KillTimer

User-mode timers deliver WM_TIMER messages through the normal message queue. They are polled by PeekMessageA — no separate thread is involved. For high-frequency timers, see multimedia timers.

Signatures
UINT_PTR WINAPI SetTimer(
    HWND     hWnd,       // window to receive WM_TIMER
    UINT_PTR nIDEvent,   // timer ID (0 = auto-assign unique ID)
    UINT     uElapse,    // interval in milliseconds
    void*    lpTimerFunc // ignored — always use NULL
);

BOOL WINAPI KillTimer(HWND hWnd, UINT_PTR uIDEvent);
      

Behaviour: SetTimer stores the timer in an internal list. Each call to PeekMessageA / GetMessageA checks whether any timer has elapsed and, if so, enqueues a WM_TIMER message with wParam = timer ID. lpTimerFunc is always ignored — route timer actions through the WNDPROC instead.

C++ — user timer example
#define TIMER_ANIMATION  1
#define TIMER_AUTOSAVE   2

// In WM_CREATE:
SetTimer(hWnd, TIMER_ANIMATION, 50, NULL);    // 20 Hz animation tick
SetTimer(hWnd, TIMER_AUTOSAVE,  30000, NULL); // auto-save every 30 s

// In WNDPROC:
case WM_TIMER:
    switch (wParam) {
    case TIMER_ANIMATION: AnimationStep(); break;
    case TIMER_AUTOSAVE:  AutoSave();      break;
    }
    return 0;

// In WM_DESTROY:
KillTimer(hWnd, TIMER_ANIMATION);
KillTimer(hWnd, TIMER_AUTOSAVE);
PostQuitMessage(0);
      

RECT operations (inline)

These functions are defined as inline functions in the header and require no library linkage.

FunctionDescription
SetRect(RECT*, l, t, r, b)Fills all four fields. Returns TRUE, FALSE on NULL pointer.
IntersectRect(dst, src1, src2)Sets dst to the intersection of src1 and src2. Returns FALSE if the intersection is empty.
UnionRect(dst, src1, src2)Sets dst to the bounding rectangle that contains both src1 and src2. Returns TRUE.
C++ — RECT helpers
RECT r1, r2, result;
SetRect(&r1, 0, 0, 100, 100);
SetRect(&r2, 50, 50, 200, 200);

if (IntersectRect(&result, &r1, &r2)) {
    // result = {50, 50, 100, 100}
}

RECT bound;
UnionRect(&bound, &r1, &r2);
// bound = {0, 0, 200, 200}
      

wsprintfA

Signature & usage
int WINAPIV wsprintfA(LPSTR lpOut, LPCSTR lpFmt, ...);

// 1024-byte fixed internal buffer — do not use for long strings
char buf[64];
wsprintfA(buf, "Score: %d  Level: %d", score, level);
      

Implemented via vsnprintf with a 1024-byte stack buffer. The output is truncated if longer than 1023 characters. For modern code, prefer snprintf.