Skip to main content

Module integration

Module integration 

Source
Expand description

System integration callbacks from NsGui/IntegrationAPI.h (namespace Noesis::GUI). These are process-global host hooks (not per-view) that let the host react to engine requests: update the OS cursor, show or hide the on-screen keyboard, open a URL, play a sound, and set the default culture.

§Pattern

Each set_* registration boxes a Rust closure and hands a thin pointer to it across the FFI as Noesis’s void* user, alongside an extern "C" trampoline. The C++ shim (cpp/noesis_integration.cpp) keeps a single static (user, callback) slot per hook and registers its own translating trampoline with Noesis (converting Cursor*CursorType, const Uri&&str). The returned *Callback guard owns the boxed closure and clears the registration on Drop, mirroring the Registered-guard idiom in crate::texture_provider / crate::font_provider.

§Single-slot, last-registration-wins

These hooks are process-global with exactly one slot per hook. Both Noesis itself and the C++ shim store a single (user, callback) pair. They are therefore not independent, freely-stacked registrations: calling a set_* again replaces the previous registration (last-registration-wins), and the older guard is then logically dead even though it is still alive.

To make Drop safe under that reality, each registration is tagged with a unique generation id (see next_reg_id) and a per-hook atomic records the id of the currently active registration. A guard’s Drop clears the global slot only if it is still the active registration (its id still matches the per-hook atomic); otherwise it just frees its own boxed closure and leaves the slot pointing at whoever overwrote it. Consequences:

  • Dropping the older of two guards for the same hook is a no-op on the slot. The newer registration keeps firing (no clobber). Its box is still freed; the C++ slot never referenced it after the overwrite.
  • Dropping the active guard clears the slot and unregisters the Noesis callback. A previously-overwritten registration is not restored: once replaced, an older registration is gone for good.

Each guard always frees exactly its own boxed closure, so there is no double-free or use-after-free regardless of drop order.

Registration publishes the new generation id before writing the FFI slot, so a stale guard’s Drop can never compare-exchange its way into clobbering a newer registration. This relies on one invariant the caller must uphold: guard drops must be serialized with the view thread’s callback dispatch (see “Lifetime” below). A guard freed concurrently with an in-flight callback on the same hook is a data race regardless of the id bookkeeping.

§Triggering

open_url and play_audio invoke the registered callback synchronously: they are genuine end-to-end round trips and are tested as such. The cursor callback fires from input handling inside a live view’s event pump and is reachable headlessly: a mouse-move over an element with a non-default Cursor drives it (proved in tests/integration.rs). The software-keyboard callback fires only when a virtual-keyboard-enabled element gains focus on a platform that requests it; that path can’t be synthesised headlessly, so for it we verify only that registration / unregistration crosses the FFI cleanly.

§Lifetime

A guard must outlive every Noesis-internal reference that might call back into the closure. Keep it alive until after crate::shutdown returns (or until you explicitly drop it to unregister).

Structs§

CursorCallback
Guard for a registered cursor callback. This is a process-global, single-slot, last-registration-wins hook (see the module docs): dropping the guard clears the slot only while it is still the active registration, and always frees its own boxed closure.
OpenUrlCallback
Guard for a registered open-URL callback. Process-global, single-slot, last-registration-wins; see CursorCallback and the module docs.
PlayAudioCallback
Guard for a registered play-audio callback. Process-global, single-slot, last-registration-wins; see CursorCallback and the module docs.
SoftwareKeyboardCallback
Guard for a registered software-keyboard callback. Process-global, single-slot, last-registration-wins; see CursorCallback and the module docs.

Enums§

CursorType
Built-in cursor types, mirroring Noesis::CursorType in NsGui/Cursor.h. The discriminants match the C++ enum exactly so the value read back from a live Cursor round-trips.

Functions§

get_culture
Return the active default culture’s BCP-47 name. Defaults to "en-US" before any set_culture call.
open_url
Ask Noesis to open url. Invokes the registered open-URL callback synchronously; a no-op if none is registered.
play_audio
Ask Noesis to play the sound at uri at volume. Invokes the registered play-audio callback synchronously; a no-op if none is registered.
set_culture
Set the default culture by BCP-47 name (e.g. "en-US", "fr-FR"). The name backs Noesis’s number / currency / date formatting. Round-trips through get_culture. The C++ shim keeps the name alive in a static buffer for the process lifetime.
set_cursor_callback
Register f as the global cursor-update callback. f receives the borrowed Noesis::IView* (opaque) requesting the change and the desired CursorType. It must be Fn because the callback fires synchronously from input dispatch and may re-enter; use interior mutability for state. Returns a guard; drop it to unregister.
set_open_url_callback
Register f as the global open-URL callback. f receives the URL string whenever the host should open it in a browser. open_url triggers this synchronously, so f must be Fn (a re-entrant call must not alias handler state; use interior mutability).
set_play_audio_callback
Register f as the global play-audio callback. f receives the canonicalized URI string of the sound and a volume in [0.0, 1.0]. play_audio triggers this synchronously, so f must be Fn (a re-entrant call must not alias handler state; use interior mutability).
set_software_keyboard_callback
Register f as the global on-screen-keyboard callback. f receives the borrowed Noesis::UIElement* (opaque) that has focus and a bool that is true to open the keyboard, false to close it. It must be Fn (the callback may re-enter; use interior mutability for state).