whisker_driver_sys/lib.rs
1//! Raw `extern "C"` declarations matching `bridge/include/whisker_bridge.h`.
2//!
3//! Everything here is `unsafe` to call. Safe wrappers (and the host shim
4//! `whisker_app_main` / `whisker_tick` exports) live in `whisker-driver`. Users
5//! never depend on this crate directly.
6
7use std::ffi::{c_char, c_int, c_void};
8
9#[repr(C)]
10pub struct WhiskerEngine {
11 _private: [u8; 0],
12}
13
14#[repr(C)]
15pub struct WhiskerElement {
16 _private: [u8; 0],
17}
18
19#[repr(u32)]
20#[derive(Debug, Clone, Copy)]
21pub enum WhiskerElementTag {
22 Page = 1,
23 View = 2,
24 Text = 3,
25 RawText = 4,
26 ScrollView = 5,
27}
28
29pub type WhiskerTasmCallback = extern "C" fn(user_data: *mut c_void);
30pub type WhiskerEventCallback = extern "C" fn(user_data: *mut c_void);
31
32// ----- List native item provider --------------------------------------------
33//
34// C-ABI callback set for `whisker_bridge_list_set_native_item_provider`.
35// Mirrors `lynx_list_*` typedefs in `whiskerrs/lynx#9` — the bridge wires
36// these through to `ListNativeItemProvider` on the C++ ListElement.
37//
38// Whisker users do NOT construct these directly. A higher-level safe
39// wrapper in `whisker-driver::lynx::list_provider` (boxed `FnMut` +
40// lifetime management) is the supported surface.
41
42/// Called by Lynx's list machinery when it needs the element for `index`.
43/// Returns the FiberElement's `impl_id` (sign) or
44/// [`LYNX_LIST_INVALID_INDEX`] on failure. `reuse_notification` is 1 if
45/// the embedder may reuse an existing element for this index.
46pub type LynxListComponentAtIndexFn = extern "C" fn(
47 index: u32,
48 operation_id: i64,
49 reuse_notification: c_int,
50 user_data: *mut c_void,
51) -> i32;
52
53/// Called when the element at `sign` leaves the viewport. The provider
54/// may pool or release it.
55pub type LynxListEnqueueComponentFn = extern "C" fn(sign: i32, user_data: *mut c_void);
56
57/// Free-callback for the `user_data` cookie. Invoked by the bridge when
58/// the list element is destroyed (or the provider is replaced) so a Rust
59/// `Box<dyn FnMut>` packed into `user_data` can be dropped.
60pub type LynxUserDataFreeFn = extern "C" fn(user_data: *mut c_void);
61
62/// Mirror of `LYNX_LIST_INVALID_INDEX` (the C macro in
63/// `lynx_capi.h`) — returned by
64/// [`LynxListComponentAtIndexFn`] to signal "no element could be
65/// produced for this index". Matches Lynx's
66/// `lynx::tasm::list::kInvalidIndex`; 0 is a real `impl_id` and
67/// would be silently consumed.
68pub const LYNX_LIST_INVALID_INDEX: i32 = -1;
69/// Value-payload event callback. `payload` is a `WhiskerValueRaw`
70/// tree (never NULL — the bridge normalises a missing body to a
71/// `WHISKER_VALUE_NULL` value), owned by the bridge and only valid
72/// for the duration of the call (copy out via `from_raw`). See
73/// `whisker_bridge_set_event_listener_with_value`.
74pub type WhiskerEventValueCallback =
75 extern "C" fn(user_data: *mut c_void, payload: *const WhiskerValueRaw);
76
77/// The Rust event dispatcher the bridge calls when its reporter hook
78/// fires. Receives the hit-tested target's element sign, the event
79/// name (NUL-terminated), and the event body (`WhiskerValueRaw` tree,
80/// never NULL). Returns whether the event was consumed (so the
81/// reporter can tell Lynx to skip its native chain). See
82/// `whisker_bridge_register_event_dispatcher`.
83pub type WhiskerEventDispatcher = extern "C" fn(
84 target_sign: i32,
85 event_name: *const c_char,
86 body: *const WhiskerValueRaw,
87) -> bool;
88
89// ----- Platform module invocation (Phase 7-Φ.E) ------------------------------
90//
91// `#[repr(C)]` mirror of the C tagged-union in `whisker_bridge.h`.
92// Each variant has its own pure-Rust struct so the layout matches
93// the C compiler's union member layout byte-for-byte — without the
94// opaque-storage approach the E.1 draft tried (which silently
95// disagreed on total size with the C side).
96//
97// Native callers (Rust runtime, proc-macro-generated proxies)
98// don't touch this `Raw` form directly — `whisker-runtime::view::
99// module` exposes a typed `WhiskerValue` enum with conversions in
100// both directions.
101
102/// Discriminant for [`WhiskerValueRaw::type_`]. Must stay in lock
103/// step with `enum WhiskerValueType` in `whisker_bridge.h`.
104#[repr(u8)]
105#[derive(Debug, Clone, Copy, PartialEq, Eq)]
106pub enum WhiskerValueType {
107 Null = 0,
108 Bool = 1,
109 Int = 2,
110 Float = 3,
111 String = 4,
112 Bytes = 5,
113 Array = 6,
114 Map = 7,
115 Error = 8,
116}
117
118/// `struct { const char* ptr; size_t len; }` member of the C union
119/// (also used as the `key` field of `WhiskerKeyValueRaw`).
120#[repr(C)]
121#[derive(Copy, Clone)]
122pub struct WhiskerStringRef {
123 pub ptr: *const c_char,
124 pub len: usize,
125}
126
127/// `struct { const uint8_t* ptr; size_t len; }` member.
128#[repr(C)]
129#[derive(Copy, Clone)]
130pub struct WhiskerBytesRef {
131 pub ptr: *const u8,
132 pub len: usize,
133}
134
135/// `struct WhiskerValueArrayRec` — pointer to `count`
136/// `WhiskerValueRaw` items.
137#[repr(C)]
138#[derive(Copy, Clone)]
139pub struct WhiskerValueArray {
140 pub items: *mut WhiskerValueRaw,
141 pub count: usize,
142}
143
144/// `struct WhiskerValueMapRec` — pointer to `count`
145/// `WhiskerKeyValueRaw` entries.
146#[repr(C)]
147#[derive(Copy, Clone)]
148pub struct WhiskerValueMap {
149 pub entries: *mut WhiskerKeyValueRaw,
150 pub count: usize,
151}
152
153/// `#[repr(C)] union` mirroring the inner anonymous union of
154/// `WhiskerValueRec`. Field access is unsafe — discriminate on the
155/// outer struct's [`type_`](WhiskerValueRaw::type_) before reading.
156#[repr(C)]
157#[derive(Copy, Clone)]
158pub union WhiskerValueUnion {
159 pub b: bool,
160 pub i: i64,
161 pub f: f64,
162 pub s: WhiskerStringRef,
163 pub bytes: WhiskerBytesRef,
164 pub array: WhiskerValueArray,
165 pub map: WhiskerValueMap,
166}
167
168/// Raw FFI form of `WhiskerValue` — byte-for-byte compatible with
169/// the C `struct WhiskerValueRec`. Total size = 24 bytes
170/// (1 discriminant + 7 padding + 16 union = 24).
171#[repr(C)]
172#[derive(Copy, Clone)]
173pub struct WhiskerValueRaw {
174 /// Discriminant — cast to [`WhiskerValueType`] before use.
175 pub type_: u8,
176 /// Padding to align the union on the natural 8-byte boundary
177 /// the C compiler picks for `{ptr, len}` members.
178 pub _pad: [u8; 7],
179 /// Variant payload — see [`WhiskerValueUnion`].
180 pub v: WhiskerValueUnion,
181}
182
183/// `struct WhiskerKeyValueRec` — string-keyed entry of the `map`
184/// variant.
185#[repr(C)]
186#[derive(Copy, Clone)]
187pub struct WhiskerKeyValueRaw {
188 pub key: WhiskerStringRef,
189 pub value: WhiskerValueRaw,
190}
191
192/// Callback type for `whisker_bridge_invoke_module_async`. The
193/// `result` pointer is borrowed for the duration of the call only —
194/// the bridge frees the underlying allocations once the callback
195/// returns, so closures that need to retain the value must copy
196/// into Rust-owned storage via the `whisker-runtime` wrapper.
197pub type WhiskerModuleCallback =
198 extern "C" fn(user_data: *mut c_void, result: *const WhiskerValueRaw);
199
200/// Callback type for module event subscriptions. Fired by the bridge
201/// when a registered `(module, event)` pair receives a
202/// `whisker_bridge_module_send_event` call. `payload` is borrowed —
203/// the bridge frees its allocations once the callback returns.
204pub type WhiskerModuleEventCallback =
205 extern "C" fn(user_data: *mut c_void, payload: *const WhiskerValueRaw);
206
207/// Callback type for `OnStartObserving` / `OnStopObserving` hooks.
208/// The bridge fires these on the 0↔1 listener-count transition for
209/// a `(module, event)` pair. Both `module_name` and `event_name` are
210/// borrowed (NUL-terminated UTF-8) — copy if you need to retain them
211/// past the call. `module_name` lets a shared platform-side
212/// trampoline index its own per-module table.
213pub type WhiskerModuleObserverHook =
214 extern "C" fn(module_name: *const c_char, event_name: *const c_char);
215
216/// Per-module dispatch function — the platform-side Swift Macro or
217/// KSP processor emits one of these per `@WhiskerModule`-annotated
218/// class. The bridge stores `(module_name → dispatch_fn)` in a
219/// lookup table; `whisker_bridge_invoke_module` then routes calls
220/// through the registered function directly (Phase 7-Φ.F).
221pub type WhiskerModuleDispatchFn = extern "C" fn(
222 method_name: *const c_char,
223 args: *const WhiskerValueRaw,
224 arg_count: usize,
225) -> WhiskerValueRaw;
226
227extern "C" {
228 pub fn whisker_bridge_engine_attach(lynx_view_ptr: *mut c_void) -> *mut WhiskerEngine;
229 pub fn whisker_bridge_engine_release(engine: *mut WhiskerEngine);
230
231 pub fn whisker_bridge_dispatch(
232 engine: *mut WhiskerEngine,
233 callback: WhiskerTasmCallback,
234 user_data: *mut c_void,
235 ) -> bool;
236
237 pub fn whisker_bridge_create_element(
238 engine: *mut WhiskerEngine,
239 tag: WhiskerElementTag,
240 ) -> *mut WhiskerElement;
241 pub fn whisker_bridge_create_element_by_name(
242 engine: *mut WhiskerEngine,
243 tag_name: *const c_char,
244 ) -> *mut WhiskerElement;
245 pub fn whisker_bridge_release_element(element: *mut WhiskerElement);
246
247 pub fn whisker_bridge_set_attribute(
248 element: *mut WhiskerElement,
249 key: *const c_char,
250 value: *const c_char,
251 );
252 // Typed-attr variants — see `whisker_bridge_common.cc` for the
253 // rationale. Use them for props the Lynx prop-dispatch gates on
254 // `value.IsNumber()` / `value.IsBool()` (e.g. `<list>`'s
255 // `span-count`, `<scroll-view>`'s `bounces`). String dispatch
256 // silently no-ops in those branches.
257 pub fn whisker_bridge_set_attribute_int(
258 element: *mut WhiskerElement,
259 key: *const c_char,
260 value: i64,
261 );
262 pub fn whisker_bridge_set_attribute_bool(
263 element: *mut WhiskerElement,
264 key: *const c_char,
265 value: bool,
266 );
267 pub fn whisker_bridge_set_attribute_double(
268 element: *mut WhiskerElement,
269 key: *const c_char,
270 value: f64,
271 );
272 pub fn whisker_bridge_set_inline_styles(element: *mut WhiskerElement, css: *const c_char);
273
274 pub fn whisker_bridge_list_set_item_count(element: *mut WhiskerElement, count: i32);
275
276 pub fn whisker_bridge_list_set_native_item_provider(
277 element: *mut WhiskerElement,
278 component_at_index: LynxListComponentAtIndexFn,
279 enqueue_component: LynxListEnqueueComponentFn,
280 user_data: *mut c_void,
281 user_data_free: LynxUserDataFreeFn,
282 );
283
284 // Diagnostic only (Android bridge logs the int as ERROR-level under
285 // the given tag). Stub on iOS — symbol present but no-op.
286 pub fn whisker_bridge_debug_log_i32(tag: *const c_char, value: i32);
287
288 pub fn whisker_bridge_append_child(parent: *mut WhiskerElement, child: *mut WhiskerElement);
289 pub fn whisker_bridge_remove_child(parent: *mut WhiskerElement, child: *mut WhiskerElement);
290
291 pub fn whisker_bridge_set_event_listener(
292 element: *mut WhiskerElement,
293 event_name: *const c_char,
294 callback: WhiskerEventCallback,
295 user_data: *mut c_void,
296 );
297
298 pub fn whisker_bridge_set_event_listener_with_value(
299 element: *mut WhiskerElement,
300 event_name: *const c_char,
301 callback: WhiskerEventValueCallback,
302 user_data: *mut c_void,
303 );
304
305 /// Register the Rust event dispatcher (the reporter hook forwards
306 /// to it). Called once by the driver at bootstrap. See
307 /// [`WhiskerEventDispatcher`].
308 pub fn whisker_bridge_register_event_dispatcher(dispatcher: WhiskerEventDispatcher);
309
310 /// The Lynx element sign for `element` — same id the reporter
311 /// reports as the event target, used as the key for the driver's
312 /// tree + listener maps. Returns 0 for a null element.
313 pub fn whisker_bridge_element_sign(element: *mut WhiskerElement) -> i32;
314
315 /// Register a bubble-phase event handler for `event_name` on
316 /// `element`, populating its Lynx event set so the UI component
317 /// emits the event. The driver calls this for non-gesture
318 /// (component) events; touch/gesture events don't need it.
319 pub fn whisker_bridge_set_native_event_handler(
320 element: *mut WhiskerElement,
321 event_name: *const c_char,
322 );
323
324 pub fn whisker_bridge_set_root(engine: *mut WhiskerEngine, page: *mut WhiskerElement);
325 pub fn whisker_bridge_flush(engine: *mut WhiskerEngine);
326
327 /// Invoke a registered Whisker platform module's method,
328 /// synchronously. See `whisker_bridge.h` for ownership rules
329 /// around the returned `WhiskerValueRaw`.
330 pub fn whisker_bridge_invoke_module(
331 module_name: *const c_char,
332 method_name: *const c_char,
333 args: *const WhiskerValueRaw,
334 arg_count: usize,
335 ) -> WhiskerValueRaw;
336
337 /// Async variant. Caller-supplied `callback` fires once the
338 /// method completes. `user_data` is opaque — caller owns
339 /// lifetime / dropping.
340 pub fn whisker_bridge_invoke_module_async(
341 module_name: *const c_char,
342 method_name: *const c_char,
343 args: *const WhiskerValueRaw,
344 arg_count: usize,
345 callback: WhiskerModuleCallback,
346 user_data: *mut c_void,
347 ) -> bool;
348
349 /// Free any heap allocations the bridge attached to `value` —
350 /// caller of `whisker_bridge_invoke_module` MUST eventually
351 /// call this on the returned value (no-op for scalars).
352 pub fn whisker_bridge_value_release(value: *mut WhiskerValueRaw);
353
354 /// Register a dispatch function for `module_name`. Called by
355 /// the platform-side generated code at app launch (Swift Macro
356 /// emits a `@_cdecl` fn + registration call; KSP emits a JNI
357 /// wrapper that does the equivalent). Phase 7-Φ.F.
358 pub fn whisker_bridge_register_module_dispatch(
359 module_name: *const c_char,
360 dispatch: WhiskerModuleDispatchFn,
361 );
362
363 // ------------------------------------------------------------------
364 // Module event subscription (Phase L-2c)
365 // ------------------------------------------------------------------
366
367 /// Register `callback` against `(module_name, event_name)`.
368 /// Returns a positive listener id on success, or <= 0 on a
369 /// precondition failure. The Rust wrapper hands the caller a
370 /// `ModuleSubscription` that calls
371 /// `whisker_bridge_module_remove_event_listener` on drop.
372 pub fn whisker_bridge_module_add_event_listener(
373 module_name: *const c_char,
374 event_name: *const c_char,
375 callback: WhiskerModuleEventCallback,
376 user_data: *mut c_void,
377 ) -> i32;
378
379 /// Remove a previously-registered listener. No-op if `listener_id`
380 /// is unknown. The bridge does not free the caller's `user_data`.
381 pub fn whisker_bridge_module_remove_event_listener(listener_id: i32);
382
383 /// Dispatch `payload` to every listener registered against
384 /// `(module_name, event_name)`. Called from the native module
385 /// side (`sendEvent` on Swift / Kotlin). `payload` may be NULL
386 /// for an unparameterised ping.
387 pub fn whisker_bridge_module_send_event(
388 module_name: *const c_char,
389 event_name: *const c_char,
390 payload: *const WhiskerValueRaw,
391 );
392
393 /// Register OnStart / OnStopObserving hooks for `module_name`.
394 /// The bridge calls `started(event)` on the 0→1 listener-count
395 /// transition and `stopped(event)` on 1→0, so the native module
396 /// can spin up / tear down an expensive source (e.g. an
397 /// `OnBackInvokedCallback` registration) only while needed.
398 pub fn whisker_bridge_module_register_observer_hooks(
399 module_name: *const c_char,
400 started: WhiskerModuleObserverHook,
401 stopped: WhiskerModuleObserverHook,
402 );
403
404 /// Write `msg` to `adb logcat` (Android only — no-op on iOS;
405 /// debug print path that survives Android's stderr-is-dropped
406 /// policy). `tag == NULL` defaults to "WhiskerRust".
407 pub fn whisker_bridge_log_info(tag: *const c_char, msg: *const c_char);
408
409 /// Invoke a Lynx UI method on a mounted element. Synchronous —
410 /// dispatches through Lynx's `LynxUIMethodProcessor` (iOS) /
411 /// `LynxUIMethodsExecutor` (Android), which in turn calls the
412 /// `@WhiskerUIMethod`-emitted forwarder on the element's
413 /// `WhiskerUI<View>` subclass.
414 ///
415 /// `element` is the `WhiskerElement*` originally returned by
416 /// `whisker_bridge_create_element_by_name`. The bridge looks up
417 /// the Lynx UI sign from this element and routes the method
418 /// call to the matching mounted `LynxUI`.
419 ///
420 /// `args` matches the `invoke_module` shape — a flat
421 /// `WhiskerValueRaw[]` the platform side decodes into
422 /// `[WhiskerValue]` before dispatch.
423 ///
424 /// Returns `WhiskerValueRaw` whose ownership matches
425 /// `invoke_module` — caller MUST eventually pass it to
426 /// `whisker_bridge_value_release`. A bridge-side failure (no
427 /// such method, element not mounted, args wrong shape, …)
428 /// surfaces as `WHISKER_VALUE_ERROR`.
429 ///
430 /// Phase 7-Φ.H.2.5: implementation is currently a stub
431 /// returning `WHISKER_VALUE_ERROR` — the real wiring lives in
432 /// Phase 7-Φ.H.2.7 once the Lynx fork exposes the C wrappers
433 /// over `LynxShell::GetUIOwner` / `LynxUIOwner::FindUIBySign` /
434 /// `LynxUIMethodProcessor::InvokeMethod`.
435 pub fn whisker_bridge_invoke_element_method(
436 element: *mut WhiskerElement,
437 method_name: *const c_char,
438 args: *const WhiskerValueRaw,
439 arg_count: usize,
440 ) -> WhiskerValueRaw;
441
442 /// Fire-and-forget dispatch of a built-in Lynx UI method whose
443 /// arguments are named fields of the params object (`scrollTo`,
444 /// `scrollBy`, `autoScroll`, `scrollIntoView`, `requestUIInfo`, …)
445 /// rather than the `{"args": […]}` wrapper
446 /// `whisker_bridge_invoke_element_method` builds. `params` is a
447 /// single `WHISKER_VALUE_MAP`; it (and any nested maps / arrays) is
448 /// passed through as the params object directly. Returns
449 /// `WHISKER_VALUE_NULL` once dispatch is scheduled (caller still
450 /// passes it to `whisker_bridge_value_release`).
451 pub fn whisker_bridge_invoke_element_method_with_params(
452 element: *mut WhiskerElement,
453 method_name: *const c_char,
454 params: *const WhiskerValueRaw,
455 ) -> WhiskerValueRaw;
456
457 /// Element-level animation dispatch — `element.animate(...)` shape.
458 ///
459 /// Wraps the new `lynx_element_animate` capi (DOM layer, distinct
460 /// from `lynx_ui_invoke_method_*`). `operation` follows
461 /// `JavaScriptElement::AnimationOperation`:
462 /// 0 = START, 1 = PLAY, 2 = PAUSE, 3 = CANCEL, 4 = FINISH.
463 ///
464 /// For `START` the full quartet is required: `animation_name` plus a
465 /// `WHISKER_VALUE_MAP` of `"0%"/"50%"/"100%"` → CSS-prop map for
466 /// `keyframes`, and a `WHISKER_VALUE_MAP` of
467 /// `name`/`duration`/`easing`/`iterations`/`direction`/`fill`/`delay`
468 /// for `options`. Other operations only consult `animation_name` —
469 /// pass NULL for `keyframes` / `options`.
470 ///
471 /// Returns `WHISKER_VALUE_NULL` on dispatch success;
472 /// `WHISKER_VALUE_ERROR` on precondition failure.
473 pub fn whisker_bridge_element_animate(
474 element: *mut WhiskerElement,
475 operation: i32,
476 animation_name: *const c_char,
477 keyframes: *const WhiskerValueRaw,
478 options: *const WhiskerValueRaw,
479 ) -> WhiskerValueRaw;
480
481 /// Async, result-returning element-method dispatch
482 /// (`boundingClientRect` / `takeScreenshot`). Returns immediately;
483 /// `callback(user_data, &result)` fires once the method completes
484 /// (typically on the UI thread). On precondition failure / an
485 /// unsupported platform the bridge invokes `callback` synchronously
486 /// with a `WHISKER_VALUE_ERROR` and returns `false`.
487 pub fn whisker_bridge_invoke_element_method_async(
488 element: *mut WhiskerElement,
489 method_name: *const c_char,
490 args: *const WhiskerValueRaw,
491 arg_count: usize,
492 callback: WhiskerModuleCallback,
493 user_data: *mut c_void,
494 ) -> bool;
495
496 /// The unified element-method dispatch: `params` (a single
497 /// `WHISKER_VALUE_MAP`) is passed through as the method's params
498 /// object directly, and the result arrives via `callback`. The one
499 /// entry `ElementRef::invoke` / `invoke_typed` build on — both
500 /// fire-and-forget actions (callback ignored) and result methods.
501 pub fn whisker_bridge_invoke_element_method_async_with_params(
502 element: *mut WhiskerElement,
503 method_name: *const c_char,
504 params: *const WhiskerValueRaw,
505 callback: WhiskerModuleCallback,
506 user_data: *mut c_void,
507 ) -> bool;
508}