#if defined(__APPLE__)
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#include "lynx_objc_stubs.h"
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <string>
#include <vector>
#include "whisker_bridge.h"
#include "whisker_bridge_internal.h"
namespace {
WhiskerValueRaw WhiskerValueFromNSObject(id obj);
WhiskerStringRef MakeStringRef(NSString* s) {
WhiskerStringRef ref{nullptr, 0};
if (s == nil) return ref;
const char* utf8 = [s UTF8String];
if (utf8 == nullptr) return ref;
size_t len = std::strlen(utf8);
char* buf = static_cast<char*>(std::malloc(len == 0 ? 1 : len));
if (len > 0) std::memcpy(buf, utf8, len);
ref.ptr = buf;
ref.len = len;
return ref;
}
WhiskerValueRaw WhiskerValueFromNSObject(id obj) {
WhiskerValueRaw v;
std::memset(&v, 0, sizeof(v));
if (obj == nil || [obj isKindOfClass:[NSNull class]]) {
v.type = WHISKER_VALUE_NULL;
return v;
}
if ([obj isKindOfClass:[NSString class]]) {
v.type = WHISKER_VALUE_STRING;
v.v.s = MakeStringRef(static_cast<NSString*>(obj));
return v;
}
if ([obj isKindOfClass:[NSNumber class]]) {
NSNumber* n = static_cast<NSNumber*>(obj);
if (CFGetTypeID((__bridge CFTypeRef)n) == CFBooleanGetTypeID()) {
v.type = WHISKER_VALUE_BOOL;
v.v.b = [n boolValue];
return v;
}
const char* t = [n objCType];
if (t != nullptr && (t[0] == 'f' || t[0] == 'd')) {
v.type = WHISKER_VALUE_FLOAT;
v.v.f = [n doubleValue];
} else {
v.type = WHISKER_VALUE_INT;
v.v.i = [n longLongValue];
}
return v;
}
if ([obj isKindOfClass:[NSArray class]]) {
NSArray* arr = static_cast<NSArray*>(obj);
size_t count = static_cast<size_t>(arr.count);
v.type = WHISKER_VALUE_ARRAY;
v.v.array.count = count;
v.v.array.items = count > 0
? static_cast<WhiskerValueRaw*>(std::malloc(sizeof(WhiskerValueRaw) * count))
: nullptr;
for (size_t i = 0; i < count; i++) {
v.v.array.items[i] = WhiskerValueFromNSObject(arr[static_cast<NSUInteger>(i)]);
}
return v;
}
if ([obj isKindOfClass:[NSDictionary class]]) {
NSDictionary* dict = static_cast<NSDictionary*>(obj);
size_t count = static_cast<size_t>(dict.count);
v.type = WHISKER_VALUE_MAP;
v.v.map.count = count;
v.v.map.entries = count > 0
? static_cast<WhiskerKeyValueRaw*>(std::malloc(sizeof(WhiskerKeyValueRaw) * count))
: nullptr;
size_t i = 0;
for (id key in dict) {
if (i >= count) break;
NSString* ks = [key isKindOfClass:[NSString class]]
? static_cast<NSString*>(key)
: [key description];
v.v.map.entries[i].key = MakeStringRef(ks);
v.v.map.entries[i].value = WhiskerValueFromNSObject(dict[key]);
i++;
}
return v;
}
v.type = WHISKER_VALUE_NULL;
return v;
}
void* GetShellPtr(LynxTemplateRender* render) {
if (render == nil) return nullptr;
Ivar ivar = class_getInstanceVariable([render class], "shell_");
if (ivar == nullptr) return nullptr;
ptrdiff_t offset = ivar_getOffset(ivar);
auto* base = reinterpret_cast<uint8_t*>((__bridge void*)render);
return *reinterpret_cast<void* const*>(base + offset);
}
void InstallEventReporterIfNeeded(WhiskerEngine* engine, LynxView* view) {
if (engine == nullptr ||
whisker_bridge_internal_is_event_reporter_installed(engine)) {
return;
}
LynxTemplateRender* render = [view templateRender];
if (render == nil) return;
LynxUIOwner* owner = [render uiOwner];
if (owner == nil) return;
LynxEventHandler* handler = owner.uiContext.eventHandler;
if (handler == nil) return;
LynxEventEmitter* emitter = handler.eventEmitter;
if (emitter == nil) return;
[emitter setEventReporterBlock:^BOOL(LynxEvent* event) {
if (event == nil || event.eventName == nil) return NO;
WhiskerValueRaw value;
bool have_value = false;
@try {
NSMutableDictionary* body = [event generateEventBody];
if (body != nil) {
if (body[@"params"] != nil && body[@"detail"] == nil) {
body[@"detail"] = body[@"params"];
}
Class lynxTouchEventClass = objc_getClass("LynxTouchEvent");
if (lynxTouchEventClass != Nil &&
[event isKindOfClass:lynxTouchEventClass]) {
LynxTouchEvent* touch = (LynxTouchEvent*)event;
if (!touch.isMultiTouch) {
NSDictionary* t = @{
@"identifier": @(0),
@"x": @(touch.pagePoint.x),
@"y": @(touch.pagePoint.y),
@"pageX": @(touch.pagePoint.x),
@"pageY": @(touch.pagePoint.y),
@"clientX": @(touch.clientPoint.x),
@"clientY": @(touch.clientPoint.y),
};
body[@"touches"] = @[t];
body[@"changedTouches"] = @[t];
body[@"detail"] = @{
@"x": @(touch.pagePoint.x),
@"y": @(touch.pagePoint.y),
};
} else if (touch.touchMap != nil) {
NSMutableArray* touches = [NSMutableArray array];
[touch.touchMap enumerateKeysAndObjectsUsingBlock:
^(id key, id obj, BOOL* stop) {
if (![obj isKindOfClass:[NSArray class]]) return;
NSArray* arr = (NSArray*)obj;
if (arr.count < 6) return;
NSNumber* identifier = nil;
if ([key isKindOfClass:[NSNumber class]]) {
identifier = (NSNumber*)key;
} else {
identifier = @([[key description] integerValue]);
}
[touches addObject:@{
@"identifier": identifier,
@"x": arr[2],
@"y": arr[3],
@"pageX": arr[2],
@"pageY": arr[3],
@"clientX": arr[0],
@"clientY": arr[1],
}];
}];
body[@"touches"] = touches;
body[@"changedTouches"] = touches;
if (touches.count > 0) {
NSDictionary* first = touches.firstObject;
body[@"detail"] = @{
@"x": first[@"pageX"],
@"y": first[@"pageY"],
};
}
}
}
value = WhiskerValueFromNSObject(body);
have_value = true;
}
} @catch (NSException* exn) {
have_value = false;
}
bool handled = whisker_bridge_internal_dispatch_event(
(int32_t)event.targetSign,
[event.eventName UTF8String],
have_value ? &value : nullptr);
if (have_value) {
whisker_bridge_value_release(&value);
}
return handled ? YES : NO;
}];
whisker_bridge_internal_mark_event_reporter_installed(engine);
}
}
extern "C" WhiskerEngine* whisker_bridge_engine_attach(void* lynx_view_ptr) {
if (lynx_view_ptr == nullptr) return nullptr;
LynxView* view = (__bridge LynxView*)lynx_view_ptr;
LynxTemplateRender* render = [view templateRender];
if (render == nil) return nullptr;
void* native_shell_ptr = GetShellPtr(render);
if (native_shell_ptr == nullptr) return nullptr;
WhiskerEngine* engine = whisker_bridge_internal_engine_create(native_shell_ptr);
InstallEventReporterIfNeeded(engine, view);
return engine;
}
extern "C" void whisker_bridge_log_hello(void) {
NSLog(@"[WhiskerBridge] Hello from the Obj-C++ bridge");
}
extern "C" void whisker_bridge_debug_log_i32(const char* tag, int32_t value) {
NSLog(@"[%s] diag value=%d", tag != nullptr ? tag : "WHISKER", value);
}
#endif