MiniFB (Mini FrameBuffer) is a small cross-platform library that makes it easy to render (32-bit) pixels in a window.
An example is the best way to show how it works:
int main() {
struct mfb_window *window = mfb_open_ex("my display", 800, 600, MFB_WF_RESIZABLE);
if (window == NULL)
return 0;
uint32_t *buffer = malloc(800 * 600 * 4);
mfb_update_state state;
do {
// TODO: add some fancy rendering to the buffer of size 800 * 600
state = mfb_update_ex(window, buffer, 800, 600);
if (state != MFB_STATE_OK)
break;
} while(mfb_wait_sync(window));
free(buffer);
buffer = NULL;
window = NULL;
return 0;
}
Note: By default, if ESC key is pressed, mfb_update / mfb_update_ex will return -1 (and the window will have been destroyed internally).
See https://github.com/emoon/minifb/blob/master/examples/noise.c for a complete example.
| Platform | Backends | Status |
|---|---|---|
| Windows | GDI, OpenGL | Fully supported |
| macOS | Cocoa, Metal | Fully supported |
| Linux/Unix | X11, Wayland | Fully supported (X11, Wayland) |
| iOS | Metal | Beta |
| Android | Native | Beta |
| Web | WASM | Beta |
| DOS | DJGPP | Beta |
MiniFB has been tested on Windows, macOS, Linux, iOS, Android, web, and DOSBox-x. Compatibility may vary depending on your setup. Currently, the library does not perform any data conversion if a proper 32-bit display cannot be created.
// Create and manage windows
struct mfb_window * mfb_open(const char *title, unsigned width, unsigned height);
struct mfb_window * mfb_open_ex(const char *title, unsigned width, unsigned height, unsigned flags);
void mfb_close(struct mfb_window *window);
void mfb_set_title(struct mfb_window *window, const char *title);
// Update and synchronization
mfb_update_state mfb_update(struct mfb_window *window, void *buffer);
mfb_update_state mfb_update_ex(struct mfb_window *window, void *buffer, unsigned width, unsigned height);
mfb_update_state mfb_update_events(struct mfb_window *window);
bool mfb_wait_sync(struct mfb_window *window);
// Viewport control
// Coordinates/sizes are in drawable coordinates (same units as mfb_get_window_width/height and resize callback).
bool mfb_set_viewport(struct mfb_window *window, unsigned offset_x, unsigned offset_y, unsigned width, unsigned height);
bool mfb_set_viewport_best_fit(struct mfb_window *window, unsigned old_width, unsigned old_height);
mfb_set_viewport() returns false if:
- width == 0 or height == 0
- viewport bounds exceed the current window drawable size
mfb_open() and mfb_open_ex() return NULL if:
- width == 0 or height == 0
- width * 4 would overflow the internal framebuffer stride
If title is NULL or empty, MiniFB uses "minifb" as the effective window/canvas title.
If both MFB_WF_FULLSCREEN and MFB_WF_FULLSCREEN_DESKTOP are provided, MFB_WF_FULLSCREEN takes precedence.
mfb_update_ex() returns MFB_STATE_INVALID_BUFFER if:
- buffer == NULL
- width == 0 or height == 0
- width * 4 would overflow internal stride calculations
mfb_update_ex() runtime behavior is backend-specific:
- Wayland waits for compositor frame callback inside mfb_update_ex() (can block).
- Android may return MFB_STATE_OK without presenting when ANativeWindow is temporarily unavailable during lifecycle transitions.
Open-time readiness is backend-specific:
- Wayland waits for the initial configure handshake before returning from mfb_open_ex().
- Android may return a window handle before ANativeWindow is ready (rendering starts once the native window becomes available).
mfb_open_ex() flag support by backend:
| Backend | RESIZABLE | BORDERLESS | ALWAYS_ON_TOP | FULLSCREEN | FULLSCREEN_DESKTOP |
|---|---|---|---|---|---|
| Windows | Yes | Yes | Yes | Yes | Yes |
| X11 | Yes | Yes* | Yes* | Yes* | Yes* |
| Wayland | Yes | Yes | No (ignored, warning) | Yes | Yes (maximized) |
| macOS | Yes | Yes | Yes | Yes | Yes (zoom/maximize) |
| Web | No (ignored, warning) | No (ignored, warning) | No (ignored, warning) | Yes** | Yes** |
| DOS | No (ignored, warning) | No (ignored, warning) | No (ignored, warning) | No (ignored, warning) | No (ignored, warning) |
| Android | No (ignored, warning) | No (ignored, warning) | No (ignored, warning) | No (ignored, warning) | No (ignored, warning) |
| iOS | No (ignored, warning) | No (ignored, warning) | No (ignored, warning) | No (ignored, warning) | No (ignored, warning) |
* Best effort via window-manager hints/properties; behavior depends on compositor/WM support.
** Browser-managed fullscreen; typically requires a user gesture before entering fullscreen.
Register callbacks to handle window events:
// Callback types
void mfb_set_active_callback(struct mfb_window *window, mfb_active_func callback);
void mfb_set_resize_callback(struct mfb_window *window, mfb_resize_func callback);
void mfb_set_close_callback(struct mfb_window *window, mfb_close_func callback);
void mfb_set_keyboard_callback(struct mfb_window *window, mfb_keyboard_func callback);
void mfb_set_char_input_callback(struct mfb_window *window, mfb_char_input_func callback);
void mfb_set_mouse_button_callback(struct mfb_window *window, mfb_mouse_button_func callback);
void mfb_set_mouse_move_callback(struct mfb_window *window, mfb_mouse_move_func callback);
void mfb_set_mouse_scroll_callback(struct mfb_window *window, mfb_mouse_scroll_func callback);
void active(struct mfb_window *window, bool is_active) {
// Called when window gains/loses focus
}
void resize(struct mfb_window *window, int width, int height) {
// Called when window is resized (width/height use the same drawable units as mfb_set_viewport)
// Optionally adjust viewport:
// mfb_set_viewport(window, x, y, width, height);
}
bool close(struct mfb_window *window) {
// Called when close is requested
return true; // true => confirm close, false => cancel
}
void keyboard(struct mfb_window *window, mfb_key key, mfb_key_mod mod, bool is_pressed) {
if (key == KB_KEY_ESCAPE) {
mfb_close(window);
}
}
void char_input(struct mfb_window *window, unsigned int char_code) {
// Unicode character input
}
void mouse_btn(struct mfb_window *window, mfb_mouse_button button, mfb_key_mod mod, bool is_pressed) {
// Mouse button events
}
void mouse_move(struct mfb_window *window, int x, int y) {
// Mouse movement (note: fired frequently)
}
void mouse_scroll(struct mfb_window *window, mfb_key_mod mod, float delta_x, float delta_y) {
// Mouse wheel/scroll events
}
If you are using C++, you can set callbacks to class methods or lambda expressions:
// Using object and pointer to member
mfb_set_active_callback(window, &myObject, &MyClass::onActive);
// Using std::bind
mfb_set_active_callback(std::bind(&MyClass::onActive, &myObject, _1, _2), window);
// Using lambda
mfb_set_active_callback([](struct mfb_window *window, bool is_active) {
// Handle event
}, window);
Query window and input state directly (alternative to callbacks):
// Window state
bool mfb_is_window_active(struct mfb_window *window);
unsigned mfb_get_window_width(struct mfb_window *window);
unsigned mfb_get_window_height(struct mfb_window *window);
void mfb_get_window_size(struct mfb_window *window, unsigned *width, unsigned *height);
// Key utilities
const char * mfb_get_key_name(mfb_key key);
// Drawable area (considering viewport scaling/DPI)
unsigned mfb_get_drawable_offset_x(struct mfb_window *window);
unsigned mfb_get_drawable_offset_y(struct mfb_window *window);
unsigned mfb_get_drawable_width(struct mfb_window *window);
unsigned mfb_get_drawable_height(struct mfb_window *window);
void mfb_get_drawable_bounds(struct mfb_window *window, unsigned *offset_x, unsigned *offset_y, unsigned *width, unsigned *height);
// Input state
int mfb_get_mouse_x(struct mfb_window *window);
int mfb_get_mouse_y(struct mfb_window *window);
void mfb_decode_touch(int combined, int *pos, int *id); // Decode packed mobile touch pos/id
int mfb_decode_touch_pos(int combined); // Extract position from a packed touch value
int mfb_decode_touch_id(int combined); // Extract pointer id from a packed touch value
float mfb_get_mouse_scroll_x(struct mfb_window *window); // Mouse wheel delta X from the most recent event pump (0.0f if none)
float mfb_get_mouse_scroll_y(struct mfb_window *window); // Mouse wheel delta Y from the most recent event pump (0.0f if none)
const uint8_t * mfb_get_mouse_button_buffer(struct mfb_window *window); // 1=pressed, 0=released (8 buttons)
const uint8_t * mfb_get_key_buffer(struct mfb_window *window); // 1=pressed, 0=released
On Android/iOS touch paths, mfb_get_mouse_x() and mfb_get_mouse_y() include an encoded touch pointer id in the upper bits.
Use mfb_decode_touch() to decode both at once, or mfb_decode_touch_pos() / mfb_decode_touch_id() individually. On desktop/Web/DOS, id is always 0.
For touch callbacks, the pointer id is also exposed as button in mfb_mouse_button_func (MFB_MOUSE_BTN_0..MFB_MOUSE_BTN_7).
On Android/iOS touch move callbacks, mfb_mouse_move_func receives packed x/y values (same encoding as getters).
On Android, external HOVER_MOVE also uses packed x/y; if Android does not provide a valid pointer id, MiniFB uses fallback id 15.
mfb_get_mouse_scroll_x/y() are pump-local values: MiniFB resets them to 0.0f before each backend event pump, then writes the delta if a scroll event is received during that pump.
Attach and retrieve custom data per window:
void mfb_set_user_data(struct mfb_window *window, void *user_data);
void * mfb_get_user_data(struct mfb_window *window);
Create and manage timers independently:
struct mfb_timer * mfb_timer_create(void);
void mfb_timer_destroy(struct mfb_timer *tmr);
void mfb_timer_reset(struct mfb_timer *tmr);
void mfb_timer_compensated_reset(struct mfb_timer *tmr);
double mfb_timer_now(struct mfb_timer *tmr);
double mfb_timer_delta(struct mfb_timer *tmr);
double mfb_timer_get_frequency(void);
double mfb_timer_get_resolution(void);
Control target FPS and frame synchronization:
void mfb_set_target_fps(uint32_t fps); // Default: 60 fps
unsigned mfb_get_target_fps(void);
bool mfb_wait_sync(struct mfb_window *window); // Frame sync point
Note: Hardware-accelerated syncing (OpenGL / Metal, where supported) will use vertical sync. Other platforms use software pacing.
MiniFB ships with a simple logger that you can redirect or disable:
// Set a custom logger; pass NULL to restore the built-in logger
void mfb_set_logger(mfb_log_func user_logger);
// Control verbosity threshold (inclusive)
void mfb_set_log_level(mfb_log_level level);
MFB_LOG_TRACE, MFB_LOG_DEBUG, MFB_LOG_INFO, MFB_LOG_WARNING, MFB_LOG_ERROR._DEBUG builds the threshold is MFB_LOG_DEBUG; in release builds MFB_LOG_INFO.level + message).TRACE/DEBUG/INFO to stdout and WARNING/ERROR to stderr as [MiniFB (LEVEL)] message.void mfb_show_cursor(struct mfb_window *window, bool show);
Note: Cursor hiding is supported on Windows, macOS, X11, and Wayland only.
void mfb_get_monitor_scale(struct mfb_window *window, float *scale_x, float *scale_y);
void mfb_get_monitor_dpi(struct mfb_window *window, float *dpi_x, float *dpi_y); // [Deprecated]
mfb_get_monitor_scale():
1.0 = 100%).window == NULL, outputs still receive a safe fallback (1.0) when their pointers are non-NULL.1.0.$ claude mcp add minifb \
-- python -m otcore.mcp_server <graph>