#include "lynx_capi.h"
#include <atomic>
#include <cstdint>
#include <dlfcn.h>
#include <mutex>
#if defined(__ANDROID__)
#include <android/log.h>
#define WHISKER_LOG_TAG "whisker-bridge-loader"
#define WHISKER_LOADER_LOGE(...) \
__android_log_print(ANDROID_LOG_ERROR, WHISKER_LOG_TAG, __VA_ARGS__)
#elif defined(__APPLE__)
#include <syslog.h>
#define WHISKER_LOADER_LOGE(...) syslog(LOG_ERR, "[whisker-bridge-loader] " __VA_ARGS__)
#else
#include <cstdio>
#define WHISKER_LOADER_LOGE(...) \
do { \
fprintf(stderr, "[whisker-bridge-loader] " __VA_ARGS__); \
fprintf(stderr, "\n"); \
} while (0)
#endif
namespace {
WhiskerLynxCapi g_capi{};
std::once_flag g_load_once;
std::atomic<int> g_load_result{INT32_MIN};
const char* LynxSoname() {
#if defined(__ANDROID__)
return "liblynx.so";
#elif defined(__APPLE__)
return "@rpath/Lynx.framework/Lynx";
#else
return nullptr;
#endif
}
template <typename Fn>
void BindSymbol(void* handle, const char* name, Fn* out, bool* ok) {
(void)dlerror(); void* sym = dlsym(handle, name);
if (sym == nullptr) {
const char* err = dlerror();
WHISKER_LOADER_LOGE("dlsym(%s) failed: %s",
name,
err != nullptr ? err : "(no dlerror message)");
*ok = false;
return;
}
*out = reinterpret_cast<Fn>(sym);
}
int DoLoad() {
const char* soname = LynxSoname();
if (soname == nullptr) {
WHISKER_LOADER_LOGE("unsupported platform — no Lynx SONAME");
return WHISKER_BRIDGE_LYNX_LOAD_ERR_DLOPEN;
}
void* handle = dlopen(soname, RTLD_NOW);
if (handle == nullptr) {
const char* err = dlerror();
WHISKER_LOADER_LOGE("dlopen(%s) failed: %s",
soname,
err != nullptr ? err : "(no dlerror message)");
return WHISKER_BRIDGE_LYNX_LOAD_ERR_DLOPEN;
}
bool ok = true;
BindSymbol(handle, "lynx_capi_abi_version", &g_capi.abi_version, &ok);
if (!ok) return WHISKER_BRIDGE_LYNX_LOAD_ERR_MISSING_SYMBOL;
int32_t found = g_capi.abi_version();
if (found != WHISKER_LYNX_CAPI_ABI_VERSION) {
WHISKER_LOADER_LOGE(
"Lynx C ABI version mismatch: bridge expects %d, Lynx reports %d. "
"Rebuild Whisker against the matching Lynx fork release.",
WHISKER_LYNX_CAPI_ABI_VERSION,
found);
return WHISKER_BRIDGE_LYNX_LOAD_ERR_ABI_MISMATCH;
}
BindSymbol(handle, "lynx_shell_from_native_ptr", &g_capi.shell_from_native_ptr, &ok);
BindSymbol(handle, "lynx_shell_release", &g_capi.shell_release, &ok);
BindSymbol(handle, "lynx_shell_run_on_tasm_thread", &g_capi.shell_run_on_tasm_thread, &ok);
BindSymbol(handle, "lynx_create_fiber_element", &g_capi.create_fiber_element, &ok);
BindSymbol(handle, "lynx_create_fiber_element_by_name", &g_capi.create_fiber_element_by_name, &ok);
BindSymbol(handle, "lynx_element_release", &g_capi.element_release, &ok);
BindSymbol(handle, "lynx_element_id", &g_capi.element_id, &ok);
BindSymbol(handle, "lynx_element_set_attribute", &g_capi.element_set_attribute, &ok);
BindSymbol(handle, "lynx_element_set_attribute_int", &g_capi.element_set_attribute_int, &ok);
BindSymbol(handle, "lynx_element_set_attribute_bool", &g_capi.element_set_attribute_bool, &ok);
BindSymbol(handle, "lynx_element_set_attribute_double", &g_capi.element_set_attribute_double, &ok);
BindSymbol(handle, "lynx_element_set_inline_styles", &g_capi.element_set_inline_styles, &ok);
BindSymbol(handle, "lynx_element_set_update_list_info", &g_capi.element_set_update_list_info, &ok);
BindSymbol(handle, "lynx_element_set_event_handler", &g_capi.element_set_event_handler, &ok);
BindSymbol(handle, "lynx_element_append_child", &g_capi.element_append_child, &ok);
BindSymbol(handle, "lynx_element_remove_child", &g_capi.element_remove_child, &ok);
BindSymbol(handle, "lynx_list_set_native_item_provider", &g_capi.list_set_native_item_provider, &ok);
BindSymbol(handle, "lynx_shell_set_root_element", &g_capi.shell_set_root_element, &ok);
BindSymbol(handle, "lynx_shell_flush", &g_capi.shell_flush, &ok);
BindSymbol(handle, "lynx_ui_invoke_method", &g_capi.ui_invoke_method, &ok);
BindSymbol(handle, "lynx_ui_invoke_method_with_params", &g_capi.ui_invoke_method_with_params, &ok);
BindSymbol(handle, "lynx_ui_invoke_method_async", &g_capi.ui_invoke_method_async, &ok);
BindSymbol(handle, "lynx_ui_invoke_method_async_with_params", &g_capi.ui_invoke_method_async_with_params, &ok);
BindSymbol(handle, "lynx_element_animate", &g_capi.element_animate, &ok);
if (!ok) return WHISKER_BRIDGE_LYNX_LOAD_ERR_MISSING_SYMBOL;
return WHISKER_BRIDGE_LYNX_LOAD_OK;
}
}
extern "C" int whisker_bridge_load_lynx(void) {
std::call_once(g_load_once, []() {
g_load_result.store(DoLoad(), std::memory_order_release);
});
return g_load_result.load(std::memory_order_acquire);
}
extern "C" const WhiskerLynxCapi* whisker_lynx_capi(void) {
return g_load_result.load(std::memory_order_acquire) == WHISKER_BRIDGE_LYNX_LOAD_OK
? &g_capi
: nullptr;
}