Skip to content

FFI Design

winpane-ffi is a cdylib crate that produces winpane.dll (or winpane_ffi.dll depending on build config). It exposes 35 extern "C" functions with a flat C-compatible API. The header winpane.h is auto-generated by cbindgen during the build.

The C ABI is the foundation for all non-Rust bindings. The Node.js addon uses the Rust API directly (since napi-rs compiles Rust), but C, C++, Go, Zig, C#, Python (via ctypes), and any other language with C FFI support can load the DLL and call these functions.

The API uses opaque pointer handles for all stateful objects:

  • WinpaneContext* - the engine context (one per application)
  • WinpaneSurface* - a surface handle (Hud, Panel, or Pip)
  • WinpaneTray* - a tray icon handle
  • WinpaneCanvas* - a custom draw canvas (valid only between begin_draw/end_draw)

Consumers never see the internal layout of these types. They are created by winpane_create, winpane_hud_create, etc. and destroyed by winpane_destroy, winpane_surface_destroy, etc.

WinpaneSurface is a unified handle type. Whether the underlying surface is a Hud, Panel, or Pip, you use the same functions (winpane_surface_set_text, winpane_surface_show, etc.). Operations that are invalid for a surface type (e.g., set_text on a Pip) return an error.

All functions that can fail return int32_t:

  • 0 = success
  • -1 = error (call winpane_last_error() for the message)
  • -2 = Rust panic caught at the FFI boundary

winpane_last_error() returns a const char* pointing to the last error message for the calling thread. The pointer is valid until the next winpane call on the same thread. Returns NULL if no error has occurred.

Every function body is wrapped in catch_unwind via the ffi_try! macro. Rust panics never propagate across the FFI boundary.

Creation functions take a config struct pointer. Each config struct has version and size fields:

typedef struct {
uint32_t version; // must be WINPANE_CONFIG_VERSION (currently 2)
uint32_t size; // must be sizeof(WinpaneHudConfig)
uint32_t placement_type; // 0 = Position, 1 = Monitor
int32_t position_x; // used when placement_type == 0
int32_t position_y;
uint32_t monitor_index; // used when placement_type == 1
uint32_t monitor_anchor; // 0=TopLeft, 1=TopRight, 2=BottomLeft, 3=BottomRight
uint32_t monitor_margin;
uint32_t width;
uint32_t height;
const char *position_key; // NULL = no persistence
} WinpaneHudConfig;

The version field enables forward compatibility. If future SDK versions add fields to the config struct, old consumers with an older size value still work because the SDK reads only the fields that fit within the declared size.

RustC ABI
ColorWinpaneColor { r, g, b, a } (4 bytes, no padding)
String / &strconst char* (null-terminated UTF-8)
boolint32_t (0 = false, nonzero = true)
Option<String>const char* (NULL = none)
Option<Color>WinpaneColor with has_* flag or separate parameter
f32float
Vec<u8> (pixel data)const uint8_t* data, uint32_t len
SurfaceIduint64_t (via winpane_surface_id())
EventWinpaneEvent struct with event_type enum and union-like fields
WinpaneEvent event;
while (winpane_poll_event(ctx, &event) == 0) {
switch (event.event_type) {
case WINPANE_EVENT_ELEMENT_CLICKED:
printf("clicked: surface=%llu key=%s\n", event.surface_id, event.key);
break;
case WINPANE_EVENT_TYPE_SURFACE_MOVED:
printf("moved: surface=%llu x=%d y=%d\n", event.surface_id, event.x, event.y);
break;
// ...
}
}

The WinpaneEvent struct uses a fixed-size key[256] buffer for the element key string, avoiding heap allocation in the C API.

  • Handles must be destroyed by the consumer (winpane_surface_destroy, winpane_tray_destroy, winpane_destroy)
  • winpane_destroy destroys all remaining surfaces and shuts down the engine
  • String parameters (const char*) are copied internally; the caller can free them immediately after the call
  • Pixel data (const uint8_t*) is copied internally during element creation
  • The winpane_last_error() string is owned by the SDK and must not be freed by the consumer

The canvas API provides in-process custom drawing:

WinpaneCanvas* canvas;
winpane_surface_begin_draw(surface, &canvas);
winpane_canvas_fill_rect(canvas, 10, 10, 100, 50, color);
winpane_canvas_draw_text(canvas, 20, 20, "Hello", 16.0, color);
winpane_surface_end_draw(surface);

The canvas handle is valid only between begin_draw and end_draw. Using it after end_draw is undefined behavior. The canvas translates calls into DrawOp values that are sent to the engine for execution on the GPU thread.