Skip to main content

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}