Module gui::gui_backend
GUI: the windowing-backend interface.
Everything platform-specific in std/gui sits behind one table of function pointers, struct GuiBackend below. The portable layer (gui_window.sc: GuiWindow, the clipboard, the clock; gui_app.sc: the event loop) calls only through it, and a backend calls back only into the few gui_backend_*() functions declared here. A backend is therefore one .sc file that fills in a GuiBackend and never touches the canvas, text or widget code — those only ever see a GuiWindow's pixel buffer.
Backends in std/gui today:
gui_x11.sc Xlib (Linux, BSD) GUI_NATIVE_X11
gui_wayland.sc Wayland (Linux, dlopen) GUI_NATIVE_WAYLAND
gui_win32.sc Win32 (user32/gdi32) GUI_NATIVE_WIN32
gui_cocoa.sc AppKit via the ObjC runtime GUI_NATIVE_COCOA
gui_web_wasm.sc <canvas> (wasm32/wasm64) GUI_NATIVE_WEB
gui_fb.sc bare-metal framebuffer GUI_NATIVE_FB
gui_headless.sc in memory (any target) GUI_NATIVE_HEADLESS
and the GPU presenters (gui_gpu.h): gui_gpu_gl_x11.sc (EGL/OpenGL ES on X11 and Wayland), gui_gpu_metal_cocoa.sc, gui_gpu_d3d11_win32.sc, gui_gpu_web_wasm.sc (WebGL).
── Choosing a backend ──────────────────────────────────────────────────── Each platform file defines gui_platform_backend(); safeguard links the one whose filename suffix matches the target (safeguard/src/ StdModules.cpp, stdVariantMatches: _x11 on Linux/BSD, _win32 on Windows, _cocoa on macOS, _wasm on wasm32/wasm64, _fb on bare metal). On Linux the X11 file's gui_platform_backend() picks Wayland instead at run time ($SAFEC_GUI_BACKEND, else $WAYLAND_DISPLAY: see gui_wayland.h), and every hosted platform's honors SAFEC_GUI_BACKEND=headless. gui_backend() returns the backend in use: the one installed with gui_set_backend() if any, else gui_platform_backend(). gui_set_backend() works only while no window exists — a program (or a backend module's own setup call, like gui_use_headless()) picks its backend before creating the first window.
So a new backend is added one of two ways:
* As the platform's default: a suffix rule in stdVariantMatches and a
gui_<...>_<suffix>.sc defining gui_platform_backend() (as
gui_web_wasm.sc does for "_wasm"; name the header it implements with
'// safeguard: implements <std/gui/gui.h>' if its stem matches another).
* As an alternative on a platform that already has a default — as
gui_wayland.sc is next to X11: it defines gui_wayland_backend() (NOT
gui_platform_backend(), or both would define it) and
gui_use_wayland(), and the X11 file's gui_platform_backend() prefers
it at run time. A library such an alternative needs is best
dlopen()ed (the binary still starts without it), its package named
with '// safeguard: deb-recommends linux ...' (the .deb's
Recommends) rather than '// safeguard: link' / 'deb'.
── The contract ────────────────────────────────────────────────────────── Threading: every entry is called on the thread that created the first window ("the UI thread"), except wake(), which may be called from any thread.
init() Called once, lazily, before the first window_create (or the first clipboard/clock call). Connect to the display server. Return 1, or 0 if there is none (then every window creation fails cleanly: GuiWindow.platform stays null). window_create(st, opts) Create and (if opts.visible) show a window whose client area is opts.width x opts.height logical units, honoring the other options as far as the platform can. Before returning 1, set:
st.key a nonzero value identifying the native window
(X11 Window id, HWND, NSWindow*), used with
gui_backend_find() to route native events;
st.native the backend's own per-window data, if any;
st.scale buffer pixels per logical unit (1.0 unless
opts.hiDpi and the display is high-DPI);
st.pixelW/H the buffer size in pixels (logical * scale);
st.x/st.y the position if known (else GUI_POS_DEFAULT).
The portable layer allocates the pixel buffer itself.
window_destroy(st) Destroy the native window and free st.native. Events still queued for it are discarded by the caller. window_present(st, pixels, bufW, bufH, x, y, w, h) Show the rectangle (x,y,w,h) of 'pixels' (premultiplied RGBA8888, R first in memory, bufW4 bytes per row) in the window. Convert to the native pixel order here (X11 and Win32 want BGRA; Cocoa takes RGBA). The buffer may be smaller or larger than the window for a moment around a resize; present what overlaps. window_set_title / window_set_size (logical) / window_set_position (logical) / window_set_visible / window_focus / window_set_cursor (GUI_CURSOR_) / window_update_limits (st.minW/minH/maxW/maxH changed, logical units, 0 = none) Apply the change. A resize the window system carries out is reported back like a user resize: post GUI_EVENT_RESIZE. pump(timeoutMs) Read every pending native event and post the resulting GuiEvents with gui_backend_post(). If none was pending, wait up to timeoutMs for one (0 = don't wait, negative = until an event or wake()). Return 1 if anything was posted. The events to post, all in buffer pixels:
CLOSE (the close button; do NOT destroy the window — the
application decides), RESIZE (width/height = new buffer
size; also update st.pixelW/H, st.logicalW/H), MOVE,
FOCUS_IN/OUT (also set st.focused), MOUSE_DOWN/UP/MOVE/
ENTER/LEAVE, SCROLL, KEY_DOWN/UP (keycode as GUI_KEY_*,
modifiers, and on KEY_DOWN the typed character in
codepoint), CHAR (text with no key event, e.g. from an
input method), SCALE (the scale factor changed; set
st.scale and st.pixelW/H first), EXPOSE (contents lost).
gui_backend_post() fills in windowId, timeMs and the
double-click count; the backend doesn't.
wake() Make a blocked pump() return soon. May be null. now_ns() A monotonic clock in nanoseconds. clipboard_set(utf8, len) / clipboard_get(out) The system clipboard as UTF-8 text (1 = ok / 0 = no text). display_scale() The scale factor a hiDpi window would get now. native_handle(st) The handles a GPU presenter needs (struct GuiNativeHandle). primaryMod GUI_MOD_META on macOS, GUI_MOD_CTRL elsewhere.
── Presenters: GPU-accelerated presentation ───────────────────────────── Rendering stays on the CPU (that is what makes it identical everywhere), but getting the finished buffer onto the screen can go through the GPU instead of the backend's software blit (XPutImage, StretchDIBits, a CALayer's contents): upload it into a texture and draw one quad, with OpenGL/EGL (X11, Wayland), Metal (a CAMetalLayer on the Cocoa view) or Direct3D 11 (a DXGI swap chain on the HWND). That is a GuiPresenter: a separate table attached per window with gui_window_set_presenter(); the portable present() calls presenter.present instead of backend.window_present while one is attached. attach() gets the native handles from backend.native_handle() and keeps its own state in st.presenterData; present() gets exactly window_present's arguments (premultiplied RGBA8888, the damaged rectangle — upload only that); detach() runs when the presenter is replaced or the window destroyed. A presenter must cope with the buffer size changing between presents (recreate its texture / resize its swap chain when bufW/bufH change). One that fails for good while presenting (device or context lost) frees st.presenterData, sets st.presenter = null and hands that frame to backend.window_present: the window carries on with the software path.
── Web (WebAssembly <canvas>) ──────────────────────────────────────────── A browser can't block: gui_web_wasm.sc's pump() never waits, and the page drives GuiApp::pump(0) from requestAnimationFrame instead of the program calling GuiApp::run() (gui_web.h's gui_web_run()). window_present hands the buffer to std/gui/gui_web.js (putImageData, un-premultiplied on the way; or WebGL through gui_gpu_web_wasm.sc); the DOM's input events come back through exported functions that call gui_backend_post() on the window found with gui_backend_find().
Structs
| GuiNativeHandle | Pointer-sized native handles, widened to 64 bits so the struct is the same on every target (Win64's unsigned long is 32 bits). |
| GuiWindowState | The backend-neutral state behind GuiWindow.platform. Backends read and update the fields their contract above names; the rest is the portable layer's. |
| GuiBackend | |
| GuiPresenter |
Functions
| gui_platform_backend | Defined by the platform backend file linked for the target (see above). |
| gui_backend | The backend in use (installed one, else the platform's), initialized. |
| gui_set_backend | Installs 'b' as the backend for this process. Returns 0 (and changes nothing) once a window exists. |
| gui_backend_ready | 1 once the backend's init() succeeded (calls it if it hasn't run yet). |
| gui_backend_post | For backends: queue 'ev' for the window (fills windowId, timeMs and, for GUI_EVENT_MOUSE_DOWN, clicks). |
| gui_backend_find | For backends: the live window whose st.key is 'key'. |
| gui_backend_window_count | For backends and the event loop: the live windows, in creation order. |
| gui_backend_window_at | |
| gui_backend_has_events | 1 if any live window has an event queued. |
| gui_window_set_presenter | Attaches 'p' (null: back to the backend's software present) to the window, detaching the previous presenter. Returns 0 if p.attach failed (the window then keeps the backend's present). |
| gui_window_state | The window's state (null for an offscreen window). |
Macros
| GUI_NATIVE_NONE | GuiNativeHandle.kind |
| GUI_NATIVE_X11 | display = Display*, window = Window, visual = Visual* |
| GUI_NATIVE_WIN32 | display = HINSTANCE, window = HWND |
| GUI_NATIVE_COCOA | display = NSApplication*, window = NSWindow*, view = NSView* |
| GUI_NATIVE_WAYLAND | display = wl_display*, window = wl_surface* |
| GUI_NATIVE_WEB | window = the canvas' id in the host page |
| GUI_NATIVE_FB | display = framebuffer base address |
| GUI_NATIVE_HEADLESS |