chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:29:49 +08:00
commit 505bac6b53
1470 changed files with 457757 additions and 0 deletions
@@ -0,0 +1,12 @@
import UIKit
@main
final class AppDelegate: UIResponder, UIApplicationDelegate {
func application(
_ application: UIApplication,
configurationForConnecting connectingSceneSession: UISceneSession,
options: UIScene.ConnectionOptions
) -> UISceneConfiguration {
UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
}
}
@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>0.1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<false/>
<key>UISceneConfigurations</key>
<dict>
<key>UIWindowSceneSessionRoleApplication</key>
<array>
<dict>
<key>UISceneConfigurationName</key>
<string>Default Configuration</string>
<key>UISceneDelegateClassName</key>
<string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
</dict>
</array>
</dict>
</dict>
</dict>
</plist>
@@ -0,0 +1,18 @@
// Zig's std.debug stack-trace symbolication (pulled in by the embed lib's
// panic path) references `_dyld_get_image_header_containing_address`, which
// the iOS SDK marks __API_UNAVAILABLE(ios). Provide the documented
// replacement (dladdr) under the old symbol so the static lib links; it
// only runs while formatting a panic trace. Mirrors the same shim in the
// mobile-canvas example's ObjC host (examples/mobile-canvas/ios/main.m).
#include <dlfcn.h>
#include <mach-o/dyld.h>
#include <stddef.h>
const struct mach_header *_dyld_get_image_header_containing_address(const void *address) {
Dl_info info;
if (dladdr(address, &info) != 0 && info.dli_fbase != NULL) {
return (const struct mach_header *)info.dli_fbase;
}
return NULL;
}
@@ -0,0 +1,492 @@
import UIKit
import WebKit
final class NativeSdkHostViewController: UIViewController {
private let headerView = UIView()
private let titleLabel = UILabel()
private let subtitleLabel = UILabel()
private let statusLabel = UILabel()
private let backButton = UIButton(type: .system)
private let refreshButton = UIButton(type: .system)
private let webView = WKWebView(frame: .zero)
private var webViewBottomConstraint: NSLayoutConstraint?
private var nativeApp: UnsafeMutableRawPointer?
private var keyboardBottomInset: CGFloat = 0
private var widgetAccessibilityElements: [UIAccessibilityElement] = []
private struct WidgetSemantics {
let id: UInt64
let parentId: UInt64
let role: Int32
let flags: UInt32
let actions: UInt32
let bounds: CGRect
let value: Float?
let label: String
let text: String
let textSelectionStart: Int
let textSelectionEnd: Int
let textCompositionStart: Int
let textCompositionEnd: Int
let gridRowIndex: Int
let gridColumnIndex: Int
let gridRowCount: Int
let gridColumnCount: Int
let listItemIndex: Int
let listItemCount: Int
let scrollOffset: Float
let scrollViewportExtent: Float
let scrollContentExtent: Float
let hasScroll: Bool
}
private struct WidgetTextGeometry {
let id: UInt64
let caretBounds: CGRect?
let selectionBounds: CGRect?
let selectionRectCount: Int
let compositionBounds: CGRect?
let compositionRectCount: Int
}
private final class WidgetAccessibilityElement: UIAccessibilityElement {
private weak var owner: NativeSdkHostViewController?
private let node: WidgetSemantics
init(accessibilityContainer container: Any, owner: NativeSdkHostViewController, node: WidgetSemantics) {
self.owner = owner
self.node = node
super.init(accessibilityContainer: container)
}
override func accessibilityActivate() -> Bool {
owner?.activateWidgetAccessibilityNode(node) ?? false
}
override func accessibilityIncrement() {
_ = owner?.incrementWidgetAccessibilityNode(node)
}
override func accessibilityDecrement() {
_ = owner?.decrementWidgetAccessibilityNode(node)
}
override func accessibilityScroll(_ direction: UIAccessibilityScrollDirection) -> Bool {
switch direction {
case .down, .right:
return owner?.incrementWidgetAccessibilityNode(node) ?? false
case .up, .left:
return owner?.decrementWidgetAccessibilityNode(node) ?? false
default:
return false
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBackground
configureHeader()
headerView.translatesAutoresizingMaskIntoConstraints = false
webView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(headerView)
view.addSubview(webView)
let webViewBottomConstraint = webView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
self.webViewBottomConstraint = webViewBottomConstraint
NSLayoutConstraint.activate([
headerView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
headerView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
headerView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
headerView.heightAnchor.constraint(equalToConstant: 104),
webView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
webView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
webView.topAnchor.constraint(equalTo: headerView.bottomAnchor),
webViewBottomConstraint,
])
NotificationCenter.default.addObserver(self, selector: #selector(keyboardFrameWillChange), name: UIResponder.keyboardWillChangeFrameNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name: UIResponder.keyboardWillHideNotification, object: nil)
nativeApp = native_sdk_app_create()
if let nativeApp {
native_sdk_app_start(nativeApp)
refreshWidgetAccessibility()
}
webView.loadHTMLString(Self.html, baseURL: nil)
}
func activateNativeApp() {
guard let nativeApp else { return }
native_sdk_app_activate(nativeApp)
}
func deactivateNativeApp() {
guard let nativeApp else { return }
native_sdk_app_deactivate(nativeApp)
}
private func configureHeader() {
headerView.backgroundColor = .secondarySystemBackground
titleLabel.text = "Mobile Shell"
titleLabel.font = .preferredFont(forTextStyle: .title2)
titleLabel.adjustsFontForContentSizeCategory = true
subtitleLabel.text = "Native header with WebView workspace"
subtitleLabel.font = .preferredFont(forTextStyle: .subheadline)
subtitleLabel.textColor = .secondaryLabel
subtitleLabel.adjustsFontForContentSizeCategory = true
statusLabel.text = "System WebView"
statusLabel.font = .preferredFont(forTextStyle: .caption1)
statusLabel.textColor = .secondaryLabel
statusLabel.backgroundColor = .tertiarySystemFill
statusLabel.layer.cornerRadius = 11
statusLabel.layer.masksToBounds = true
statusLabel.textAlignment = .center
backButton.setTitle("Back", for: .normal)
backButton.titleLabel?.font = .preferredFont(forTextStyle: .headline)
backButton.addTarget(self, action: #selector(sendBackCommand), for: .touchUpInside)
refreshButton.setTitle("Refresh", for: .normal)
refreshButton.titleLabel?.font = .preferredFont(forTextStyle: .headline)
refreshButton.addTarget(self, action: #selector(sendRefreshCommand), for: .touchUpInside)
[titleLabel, subtitleLabel, statusLabel, backButton, refreshButton].forEach {
$0.translatesAutoresizingMaskIntoConstraints = false
headerView.addSubview($0)
}
NSLayoutConstraint.activate([
titleLabel.leadingAnchor.constraint(equalTo: headerView.leadingAnchor, constant: 20),
titleLabel.topAnchor.constraint(equalTo: headerView.topAnchor, constant: 18),
statusLabel.trailingAnchor.constraint(equalTo: headerView.trailingAnchor, constant: -20),
statusLabel.centerYAnchor.constraint(equalTo: titleLabel.centerYAnchor),
statusLabel.widthAnchor.constraint(greaterThanOrEqualToConstant: 112),
statusLabel.heightAnchor.constraint(equalToConstant: 24),
refreshButton.trailingAnchor.constraint(equalTo: statusLabel.trailingAnchor),
refreshButton.topAnchor.constraint(equalTo: statusLabel.bottomAnchor, constant: 8),
backButton.trailingAnchor.constraint(equalTo: refreshButton.leadingAnchor, constant: -12),
backButton.centerYAnchor.constraint(equalTo: refreshButton.centerYAnchor),
subtitleLabel.leadingAnchor.constraint(equalTo: titleLabel.leadingAnchor),
subtitleLabel.trailingAnchor.constraint(lessThanOrEqualTo: backButton.leadingAnchor, constant: -16),
subtitleLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 6),
])
}
@objc private func sendBackCommand() {
dispatchNativeCommand("mobile.back")
}
@objc private func sendRefreshCommand() {
dispatchNativeCommand("mobile.refresh")
}
private func dispatchNativeCommand(_ command: String) {
guard let nativeApp else { return }
command.withCString { pointer in
native_sdk_app_command(nativeApp, pointer, UInt(command.utf8.count))
}
let count = native_sdk_app_last_command_count(nativeApp)
let name = String(cString: native_sdk_app_last_command_name(nativeApp))
statusLabel.text = "\(name) #\(count)"
native_sdk_app_frame(nativeApp)
refreshWidgetAccessibility()
}
@objc private func keyboardFrameWillChange(_ notification: Notification) {
guard let frameValue = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue else { return }
let keyboardFrame = view.convert(frameValue.cgRectValue, from: nil)
let overlap = max(0, view.bounds.maxY - keyboardFrame.minY)
keyboardBottomInset = overlap
webViewBottomConstraint?.constant = -overlap
animateKeyboardLayout(notification)
sendViewportUpdate()
}
@objc private func keyboardWillHide(_ notification: Notification) {
keyboardBottomInset = 0
webViewBottomConstraint?.constant = 0
animateKeyboardLayout(notification)
sendViewportUpdate()
}
private func animateKeyboardLayout(_ notification: Notification) {
let userInfo = notification.userInfo ?? [:]
let duration = (userInfo[UIResponder.keyboardAnimationDurationUserInfoKey] as? NSNumber)?.doubleValue ?? 0.25
let curve = (userInfo[UIResponder.keyboardAnimationCurveUserInfoKey] as? NSNumber)?.uintValue ?? 0
let options = UIView.AnimationOptions(rawValue: curve << 16)
UIView.animate(withDuration: duration, delay: 0, options: options) {
self.view.layoutIfNeeded()
}
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
sendViewportUpdate()
}
private func sendViewportUpdate() {
guard let nativeApp else { return }
let scale = Float(view.window?.screen.scale ?? UIScreen.main.scale)
let safe = view.safeAreaInsets
native_sdk_app_viewport(
nativeApp,
Float(webView.bounds.width),
Float(webView.bounds.height),
scale,
nil,
Float(safe.top),
Float(safe.right),
Float(safe.bottom),
Float(safe.left),
0,
0,
Float(keyboardBottomInset),
0
)
native_sdk_app_frame(nativeApp)
refreshWidgetAccessibility()
}
private func widgetSemanticsSnapshot() -> [WidgetSemantics] {
guard let nativeApp else { return [] }
let count = Int(native_sdk_app_widget_semantics_count(nativeApp))
var nodes: [WidgetSemantics] = []
nodes.reserveCapacity(count)
for index in 0..<count {
if let node = widgetSemantics(at: index) {
nodes.append(node)
}
}
return nodes
}
private func widgetSemantics(at index: Int) -> WidgetSemantics? {
guard let nativeApp else { return nil }
var node = native_sdk_widget_semantics_t()
guard native_sdk_app_widget_semantics_at(nativeApp, UInt(index), &node) != 0 else { return nil }
return widgetSemantics(from: node)
}
private func widgetSemantics(id: UInt64) -> WidgetSemantics? {
guard let nativeApp else { return nil }
var node = native_sdk_widget_semantics_t()
guard native_sdk_app_widget_semantics_by_id(nativeApp, id, &node) != 0 else { return nil }
return widgetSemantics(from: node)
}
private func widgetSemantics(from node: native_sdk_widget_semantics_t) -> WidgetSemantics {
return WidgetSemantics(
id: node.id,
parentId: node.parent_id,
role: Int32(node.role),
flags: node.flags,
actions: node.actions,
bounds: CGRect(x: CGFloat(node.x), y: CGFloat(node.y), width: CGFloat(node.width), height: CGFloat(node.height)),
value: node.has_value != 0 ? node.value : nil,
label: Self.utf8String(node.label, length: node.label_len),
text: Self.utf8String(node.text, length: node.text_len),
textSelectionStart: Int(node.text_selection_start),
textSelectionEnd: Int(node.text_selection_end),
textCompositionStart: Int(node.text_composition_start),
textCompositionEnd: Int(node.text_composition_end),
gridRowIndex: Int(node.grid_row_index),
gridColumnIndex: Int(node.grid_column_index),
gridRowCount: Int(node.grid_row_count),
gridColumnCount: Int(node.grid_column_count),
listItemIndex: Int(node.list_item_index),
listItemCount: Int(node.list_item_count),
scrollOffset: node.scroll_offset,
scrollViewportExtent: node.scroll_viewport_extent,
scrollContentExtent: node.scroll_content_extent,
hasScroll: node.has_scroll != 0
)
}
private func widgetTextGeometry(id: UInt64) -> WidgetTextGeometry? {
guard let nativeApp else { return nil }
var geometry = native_sdk_widget_text_geometry_t()
guard native_sdk_app_widget_text_geometry(nativeApp, id, &geometry) != 0 else { return nil }
return WidgetTextGeometry(
id: id,
caretBounds: geometry.has_caret_bounds != 0 ? CGRect(x: CGFloat(geometry.caret_x), y: CGFloat(geometry.caret_y), width: CGFloat(geometry.caret_width), height: CGFloat(geometry.caret_height)) : nil,
selectionBounds: geometry.has_selection_bounds != 0 ? CGRect(x: CGFloat(geometry.selection_x), y: CGFloat(geometry.selection_y), width: CGFloat(geometry.selection_width), height: CGFloat(geometry.selection_height)) : nil,
selectionRectCount: Int(geometry.selection_rect_count),
compositionBounds: geometry.has_composition_bounds != 0 ? CGRect(x: CGFloat(geometry.composition_x), y: CGFloat(geometry.composition_y), width: CGFloat(geometry.composition_width), height: CGFloat(geometry.composition_height)) : nil,
compositionRectCount: Int(geometry.composition_rect_count)
)
}
@discardableResult
private func dispatchWidgetAction(
id: UInt64,
action: Int32,
text: String? = nil,
selectionAnchor: UInt = 0,
selectionFocus: UInt = 0,
hasSelection: Bool = false
) -> Bool {
guard let nativeApp else { return false }
var request = native_sdk_widget_action_t()
request.id = id
request.action = action
request.selection_anchor = selectionAnchor
request.selection_focus = selectionFocus
request.has_selection = hasSelection ? 1 : 0
let ok: Int32
if let text {
ok = text.withCString { pointer in
request.text = pointer
request.text_len = UInt(text.utf8.count)
return native_sdk_app_widget_action(nativeApp, &request)
}
} else {
request.text = nil
request.text_len = 0
ok = native_sdk_app_widget_action(nativeApp, &request)
}
if ok != 0 {
native_sdk_app_frame(nativeApp)
refreshWidgetAccessibility()
}
return ok != 0
}
private func refreshWidgetAccessibility() {
let semantics = widgetSemanticsSnapshot()
statusLabel.accessibilityValue = "Accessible items: \(semantics.count)"
widgetAccessibilityElements = semantics.map { node in
let element = WidgetAccessibilityElement(accessibilityContainer: webView, owner: self, node: node)
element.accessibilityIdentifier = "native-sdk-widget-\(node.id)"
element.accessibilityLabel = node.label.isEmpty ? node.text : node.label
element.accessibilityValue = widgetAccessibilityValue(node)
element.accessibilityFrameInContainerSpace = node.bounds
element.accessibilityTraits = widgetAccessibilityTraits(node)
return element
}
webView.accessibilityElements = widgetAccessibilityElements.isEmpty ? nil : widgetAccessibilityElements as [Any]
}
private func widgetAccessibilityValue(_ node: WidgetSemantics) -> String? {
var states: [String] = []
if (node.flags & UInt32(NATIVE_SDK_WIDGET_FLAG_EXPANDED)) != 0 {
states.append("Expanded")
}
if (node.flags & UInt32(NATIVE_SDK_WIDGET_FLAG_COLLAPSED)) != 0 {
states.append("Collapsed")
}
if (node.flags & UInt32(NATIVE_SDK_WIDGET_FLAG_REQUIRED)) != 0 {
states.append("Required")
}
if (node.flags & UInt32(NATIVE_SDK_WIDGET_FLAG_READ_ONLY)) != 0 {
states.append("Read only")
}
if (node.flags & UInt32(NATIVE_SDK_WIDGET_FLAG_INVALID)) != 0 {
states.append("Invalid")
}
if !states.isEmpty {
return states.joined(separator: ", ")
}
if let value = node.value {
switch node.role {
case Int32(NATIVE_SDK_WIDGET_ROLE_CHECKBOX), Int32(NATIVE_SDK_WIDGET_ROLE_SWITCH):
return value >= 0.5 ? "On" : "Off"
case Int32(NATIVE_SDK_WIDGET_ROLE_SLIDER), Int32(NATIVE_SDK_WIDGET_ROLE_PROGRESSBAR):
return "\(Int((value * 100).rounded()))%"
default:
return "\(value)"
}
}
return node.text.isEmpty ? nil : node.text
}
private func activateWidgetAccessibilityNode(_ node: WidgetSemantics) -> Bool {
let current = widgetSemantics(id: node.id) ?? node
if widgetSupportsAction(current, UInt32(NATIVE_SDK_WIDGET_ACTION_TOGGLE)) {
return dispatchWidgetAction(id: current.id, action: Int32(NATIVE_SDK_WIDGET_ACTION_KIND_TOGGLE))
}
if widgetSupportsAction(current, UInt32(NATIVE_SDK_WIDGET_ACTION_PRESS)) {
return dispatchWidgetAction(id: current.id, action: Int32(NATIVE_SDK_WIDGET_ACTION_KIND_PRESS))
}
if widgetSupportsAction(current, UInt32(NATIVE_SDK_WIDGET_ACTION_SELECT)) {
return dispatchWidgetAction(id: current.id, action: Int32(NATIVE_SDK_WIDGET_ACTION_KIND_SELECT))
}
return false
}
private func incrementWidgetAccessibilityNode(_ node: WidgetSemantics) -> Bool {
let current = widgetSemantics(id: node.id) ?? node
guard widgetSupportsAction(current, UInt32(NATIVE_SDK_WIDGET_ACTION_INCREMENT)) else { return false }
return dispatchWidgetAction(id: current.id, action: Int32(NATIVE_SDK_WIDGET_ACTION_KIND_INCREMENT))
}
private func decrementWidgetAccessibilityNode(_ node: WidgetSemantics) -> Bool {
let current = widgetSemantics(id: node.id) ?? node
guard widgetSupportsAction(current, UInt32(NATIVE_SDK_WIDGET_ACTION_DECREMENT)) else { return false }
return dispatchWidgetAction(id: current.id, action: Int32(NATIVE_SDK_WIDGET_ACTION_KIND_DECREMENT))
}
private func widgetSupportsAction(_ node: WidgetSemantics, _ action: UInt32) -> Bool {
return (node.actions & action) != 0
}
private func widgetAccessibilityTraits(_ node: WidgetSemantics) -> UIAccessibilityTraits {
var traits: UIAccessibilityTraits = []
switch node.role {
case Int32(NATIVE_SDK_WIDGET_ROLE_BUTTON), Int32(NATIVE_SDK_WIDGET_ROLE_MENUITEM):
traits.insert(.button)
case Int32(NATIVE_SDK_WIDGET_ROLE_CHECKBOX), Int32(NATIVE_SDK_WIDGET_ROLE_SWITCH), Int32(NATIVE_SDK_WIDGET_ROLE_TAB):
traits.insert(.button)
case Int32(NATIVE_SDK_WIDGET_ROLE_SLIDER):
traits.insert(.adjustable)
case Int32(NATIVE_SDK_WIDGET_ROLE_IMAGE):
traits.insert(.image)
case Int32(NATIVE_SDK_WIDGET_ROLE_TEXT), Int32(NATIVE_SDK_WIDGET_ROLE_PROGRESSBAR):
traits.insert(.staticText)
default:
break
}
if (node.flags & UInt32(NATIVE_SDK_WIDGET_FLAG_SELECTED)) != 0 {
traits.insert(.selected)
}
if (node.flags & UInt32(NATIVE_SDK_WIDGET_FLAG_DISABLED)) != 0 {
traits.insert(.notEnabled)
}
return traits
}
private static func utf8String(_ pointer: UnsafePointer<CChar>?, length: UInt) -> String {
guard let pointer, length > 0 else { return "" }
let bytes = UnsafeBufferPointer(start: UnsafeRawPointer(pointer).assumingMemoryBound(to: UInt8.self), count: Int(length))
return String(decoding: bytes, as: UTF8.self)
}
deinit {
NotificationCenter.default.removeObserver(self)
guard let nativeApp else { return }
native_sdk_app_stop(nativeApp)
native_sdk_app_destroy(nativeApp)
}
private static let html = """
<!doctype html>
<html>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<body style="margin:0;font-family:-apple-system,system-ui;background:#f7f8fa;color:#171717;">
<main style="padding:28px 22px;display:grid;gap:16px;">
<h1 style="margin:0;font-size:30px;letter-spacing:0;">Workspace</h1>
<p style="margin:0;color:#5f6672;line-height:1.5;">This content is rendered by WKWebView while the header remains native UIKit.</p>
<section style="display:grid;gap:10px;">
<div style="padding:14px;border:1px solid #e1e5ea;border-radius:8px;background:white;">Inbox review</div>
<div style="padding:14px;border:1px solid #e1e5ea;border-radius:8px;background:white;">Sync queue</div>
<div style="padding:14px;border:1px solid #e1e5ea;border-radius:8px;background:white;">Offline cache</div>
</section>
</main>
</body>
</html>
"""
}
@@ -0,0 +1,25 @@
import UIKit
final class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(
_ scene: UIScene,
willConnectTo session: UISceneSession,
options connectionOptions: UIScene.ConnectionOptions
) {
guard let windowScene = scene as? UIWindowScene else { return }
let window = UIWindow(windowScene: windowScene)
window.rootViewController = NativeSdkHostViewController()
window.makeKeyAndVisible()
self.window = window
}
func sceneDidBecomeActive(_ scene: UIScene) {
(window?.rootViewController as? NativeSdkHostViewController)?.activateNativeApp()
}
func sceneWillResignActive(_ scene: UIScene) {
(window?.rootViewController as? NativeSdkHostViewController)?.deactivateNativeApp()
}
}
@@ -0,0 +1,320 @@
#pragma once
#include <stddef.h>
#include <stdint.h>
enum {
NATIVE_SDK_WIDGET_ROLE_NONE = 0,
NATIVE_SDK_WIDGET_ROLE_GROUP = 1,
NATIVE_SDK_WIDGET_ROLE_TEXT = 2,
NATIVE_SDK_WIDGET_ROLE_IMAGE = 3,
NATIVE_SDK_WIDGET_ROLE_BUTTON = 4,
NATIVE_SDK_WIDGET_ROLE_TEXTBOX = 5,
NATIVE_SDK_WIDGET_ROLE_TOOLTIP = 6,
NATIVE_SDK_WIDGET_ROLE_DIALOG = 7,
NATIVE_SDK_WIDGET_ROLE_MENU = 8,
NATIVE_SDK_WIDGET_ROLE_MENUITEM = 9,
NATIVE_SDK_WIDGET_ROLE_LIST = 10,
NATIVE_SDK_WIDGET_ROLE_LISTITEM = 11,
NATIVE_SDK_WIDGET_ROLE_ROW = 12,
NATIVE_SDK_WIDGET_ROLE_GRID = 13,
NATIVE_SDK_WIDGET_ROLE_GRIDCELL = 14,
NATIVE_SDK_WIDGET_ROLE_TAB = 15,
NATIVE_SDK_WIDGET_ROLE_CHECKBOX = 16,
NATIVE_SDK_WIDGET_ROLE_SWITCH = 17,
NATIVE_SDK_WIDGET_ROLE_SLIDER = 18,
NATIVE_SDK_WIDGET_ROLE_PROGRESSBAR = 19,
};
enum {
NATIVE_SDK_WIDGET_FLAG_FOCUSED = 1u << 0,
NATIVE_SDK_WIDGET_FLAG_HOVERED = 1u << 1,
NATIVE_SDK_WIDGET_FLAG_PRESSED = 1u << 2,
NATIVE_SDK_WIDGET_FLAG_SELECTED = 1u << 3,
NATIVE_SDK_WIDGET_FLAG_DISABLED = 1u << 4,
NATIVE_SDK_WIDGET_FLAG_FOCUSABLE = 1u << 5,
NATIVE_SDK_WIDGET_FLAG_EXPANDED = 1u << 6,
NATIVE_SDK_WIDGET_FLAG_COLLAPSED = 1u << 7,
NATIVE_SDK_WIDGET_FLAG_REQUIRED = 1u << 8,
NATIVE_SDK_WIDGET_FLAG_READ_ONLY = 1u << 9,
NATIVE_SDK_WIDGET_FLAG_INVALID = 1u << 10,
};
enum {
NATIVE_SDK_WIDGET_ACTION_FOCUS = 1u << 0,
NATIVE_SDK_WIDGET_ACTION_PRESS = 1u << 1,
NATIVE_SDK_WIDGET_ACTION_TOGGLE = 1u << 2,
NATIVE_SDK_WIDGET_ACTION_INCREMENT = 1u << 3,
NATIVE_SDK_WIDGET_ACTION_DECREMENT = 1u << 4,
NATIVE_SDK_WIDGET_ACTION_SET_TEXT = 1u << 5,
NATIVE_SDK_WIDGET_ACTION_SET_SELECTION = 1u << 6,
NATIVE_SDK_WIDGET_ACTION_SELECT = 1u << 7,
NATIVE_SDK_WIDGET_ACTION_DRAG = 1u << 8,
NATIVE_SDK_WIDGET_ACTION_DROP_FILES = 1u << 9,
};
enum {
NATIVE_SDK_WIDGET_ACTION_KIND_FOCUS = 0,
NATIVE_SDK_WIDGET_ACTION_KIND_PRESS = 1,
NATIVE_SDK_WIDGET_ACTION_KIND_TOGGLE = 2,
NATIVE_SDK_WIDGET_ACTION_KIND_INCREMENT = 3,
NATIVE_SDK_WIDGET_ACTION_KIND_DECREMENT = 4,
NATIVE_SDK_WIDGET_ACTION_KIND_SET_TEXT = 5,
NATIVE_SDK_WIDGET_ACTION_KIND_SET_SELECTION = 6,
NATIVE_SDK_WIDGET_ACTION_KIND_SET_COMPOSITION = 7,
NATIVE_SDK_WIDGET_ACTION_KIND_COMMIT_COMPOSITION = 8,
NATIVE_SDK_WIDGET_ACTION_KIND_CANCEL_COMPOSITION = 9,
NATIVE_SDK_WIDGET_ACTION_KIND_SELECT = 10,
NATIVE_SDK_WIDGET_ACTION_KIND_DRAG = 11,
NATIVE_SDK_WIDGET_ACTION_KIND_DROP_FILES = 12,
};
enum {
NATIVE_SDK_GPU_SURFACE_STATUS_UNAVAILABLE = 0,
NATIVE_SDK_GPU_SURFACE_STATUS_INITIALIZING = 1,
NATIVE_SDK_GPU_SURFACE_STATUS_READY = 2,
NATIVE_SDK_GPU_SURFACE_STATUS_LOST = 3,
};
typedef struct native_sdk_widget_semantics {
uint64_t id;
uint64_t parent_id;
int role;
uint32_t flags;
uint32_t actions;
float x;
float y;
float width;
float height;
float value;
int has_value;
const char *label;
uintptr_t label_len;
const char *text;
uintptr_t text_len;
const char *placeholder;
uintptr_t placeholder_len;
intptr_t text_selection_start;
intptr_t text_selection_end;
intptr_t text_composition_start;
intptr_t text_composition_end;
intptr_t grid_row_index;
intptr_t grid_column_index;
intptr_t grid_row_count;
intptr_t grid_column_count;
intptr_t list_item_index;
intptr_t list_item_count;
float scroll_offset;
float scroll_viewport_extent;
float scroll_content_extent;
int has_scroll;
} native_sdk_widget_semantics_t;
typedef struct native_sdk_widget_text_geometry {
uint64_t id;
int has_caret_bounds;
float caret_x;
float caret_y;
float caret_width;
float caret_height;
int has_selection_bounds;
float selection_x;
float selection_y;
float selection_width;
float selection_height;
uintptr_t selection_rect_count;
int has_composition_bounds;
float composition_x;
float composition_y;
float composition_width;
float composition_height;
uintptr_t composition_rect_count;
} native_sdk_widget_text_geometry_t;
typedef struct native_sdk_widget_action {
uint64_t id;
int action;
const char *text;
uintptr_t text_len;
uintptr_t selection_anchor;
uintptr_t selection_focus;
int has_selection;
} native_sdk_widget_action_t;
typedef struct native_sdk_canvas_pixels {
uintptr_t width;
uintptr_t height;
uintptr_t byte_len;
} native_sdk_canvas_pixels_t;
// Result of native_sdk_app_render_pixels_damage: the surface dimensions
// plus the damaged region the call wrote into the caller's RETAINED
// buffer, in device pixels. damage_width == 0 (or damage_height == 0)
// means nothing changed since the previous call: the buffer already
// shows the current frame and the host skips its upload entirely.
typedef struct native_sdk_canvas_pixels_damage {
uintptr_t width;
uintptr_t height;
uintptr_t byte_len;
uintptr_t damage_x;
uintptr_t damage_y;
uintptr_t damage_width;
uintptr_t damage_height;
// The retained-canvas revision the buffer now REFLECTS: gate re-renders
// on canvas_revision != this value (a change whose frame has not
// presented yet reports the OLD revision with empty damage - call again
// next tick), never on your own last sighting of canvas_revision.
uint64_t revision;
} native_sdk_canvas_pixels_damage_t;
typedef struct native_sdk_text_input_state {
int active;
uint64_t widget_id;
float x;
float y;
float width;
float height;
} native_sdk_text_input_state_t;
typedef struct native_sdk_viewport_state {
float width;
float height;
float scale;
int has_surface;
float safe_top;
float safe_right;
float safe_bottom;
float safe_left;
float keyboard_top;
float keyboard_right;
float keyboard_bottom;
float keyboard_left;
float content_x;
float content_y;
float content_width;
float content_height;
} native_sdk_viewport_state_t;
typedef struct native_sdk_gpu_frame_state {
uint64_t surface_id;
uint64_t window_id;
float width;
float height;
float scale;
uint64_t frame_index;
uint64_t timestamp_ns;
uint64_t frame_interval_ns;
uint64_t input_timestamp_ns;
uint64_t input_latency_ns;
uint64_t input_latency_budget_ns;
uintptr_t input_latency_budget_exceeded_count;
int input_latency_budget_ok;
uint64_t first_frame_latency_ns;
uint64_t first_frame_latency_budget_ns;
uintptr_t first_frame_latency_budget_exceeded_count;
int first_frame_latency_budget_ok;
int nonblank;
uint32_t sample_color;
int status;
int vsync;
uint64_t canvas_revision;
uintptr_t canvas_command_count;
int canvas_frame_requires_render;
int canvas_frame_full_repaint;
uintptr_t canvas_frame_batch_count;
uintptr_t canvas_frame_budget_exceeded_count;
int canvas_frame_budget_ok;
uint64_t widget_revision;
uintptr_t widget_node_count;
uintptr_t widget_semantics_count;
} native_sdk_gpu_frame_state_t;
// Form-factor ordinals accepted by native_sdk_app_set_form_factor.
enum {
NATIVE_SDK_FORM_FACTOR_UNKNOWN = 0,
NATIVE_SDK_FORM_FACTOR_COMPACT = 1,
NATIVE_SDK_FORM_FACTOR_REGULAR = 2,
};
// One declared platform-chrome tab (or the primary action) from the
// app's shell metadata. Strings reference static app data (valid for
// the app's lifetime, not NUL-terminated). Layout mirrors
// src/embed/chrome.zig MobileChromeItem.
typedef struct native_sdk_chrome_item {
const char *id;
uintptr_t id_len;
const char *label;
uintptr_t label_len;
const char *icon;
uintptr_t icon_len;
} native_sdk_chrome_item_t;
void *native_sdk_app_create(void);
void native_sdk_app_destroy(void *app);
void native_sdk_app_start(void *app);
void native_sdk_app_activate(void *app);
void native_sdk_app_deactivate(void *app);
void native_sdk_app_stop(void *app);
void native_sdk_app_resize(void *app, float width, float height, float scale, void *surface);
void native_sdk_app_viewport(void *app, float width, float height, float scale, void *surface, float safe_top, float safe_right, float safe_bottom, float safe_left, float keyboard_top, float keyboard_right, float keyboard_bottom, float keyboard_left);
int native_sdk_app_viewport_state(void *app, native_sdk_viewport_state_t *out);
int native_sdk_app_gpu_frame_state(void *app, native_sdk_gpu_frame_state_t *out);
void native_sdk_app_touch(void *app, uint64_t id, int phase, float x, float y, float pressure);
void native_sdk_app_scroll(void *app, uint64_t id, float x, float y, float delta_x, float delta_y);
void native_sdk_app_key(void *app, int phase, const char *key, uintptr_t key_len, const char *text, uintptr_t text_len, uint32_t modifiers_mask);
void native_sdk_app_text(void *app, const char *text, uintptr_t len);
void native_sdk_app_ime(void *app, int kind, const char *text, uintptr_t len, intptr_t cursor);
void native_sdk_app_command(void *app, const char *name, uintptr_t len);
void native_sdk_app_frame(void *app);
// Declared platform chrome (the app's shell-metadata tab set + optional
// primary action): query the declaration once at startup, build REAL
// native controls, poll the model-selected tab index each frame (-1 =
// none), and dispatch taps back through native_sdk_app_command with the
// declared ids. The icon rasterizer renders a declared icon-vocabulary
// glyph as premultiplied white on transparent RGBA8 (size_px * size_px
// * 4 bytes) — a template image the system control tints.
uintptr_t native_sdk_app_chrome_tab_count(void *app);
int native_sdk_app_chrome_tab_at(void *app, uintptr_t index, native_sdk_chrome_item_t *out);
int native_sdk_app_chrome_primary_action(void *app, native_sdk_chrome_item_t *out);
intptr_t native_sdk_app_chrome_selected_tab(void *app);
// Model-driven navigation depth for platform push/pop transitions (0 =
// the root page, 1 = one push in; -1 = the app declares no navigation
// projection), polled each tick, and the declared back command a
// completed platform back gesture dispatches through
// native_sdk_app_command (1 with out->id filled when declared, 0
// otherwise — never arm the interactive back gesture without it).
intptr_t native_sdk_app_chrome_navigation_depth(void *app);
int native_sdk_app_chrome_navigation_back_command(void *app, native_sdk_chrome_item_t *out);
int native_sdk_app_chrome_icon_pixels(void *app, const char *name, uintptr_t name_len, uintptr_t size_px, uint8_t *pixels, uintptr_t pixels_len);
// Host chrome reports on the window-chrome channel: the reported form
// factor (host truth apps prefer over width derivation) and whether the
// declared tabs are currently projected as native controls.
int native_sdk_app_set_form_factor(void *app, int form_factor);
int native_sdk_app_set_chrome_tabs_projected(void *app, int projected);
void native_sdk_app_set_asset_root(void *app, const char *path, uintptr_t len);
void native_sdk_app_set_asset_entry(void *app, const char *path, uintptr_t len);
uintptr_t native_sdk_app_last_command_count(void *app);
const char *native_sdk_app_last_command_name(void *app);
const char *native_sdk_app_last_error_name(void *app);
uintptr_t native_sdk_app_widget_semantics_count(void *app);
int native_sdk_app_widget_semantics_at(void *app, uintptr_t index, native_sdk_widget_semantics_t *out);
int native_sdk_app_widget_semantics_by_id(void *app, uint64_t id, native_sdk_widget_semantics_t *out);
int native_sdk_app_widget_text_geometry(void *app, uint64_t id, native_sdk_widget_text_geometry_t *out);
int native_sdk_app_widget_action(void *app, const native_sdk_widget_action_t *action);
int native_sdk_app_text_input_state(void *app, native_sdk_text_input_state_t *out);
// Platform text measurement for layout: returns the typographic width of a
// single-line UTF-8 run at `size` for `font_id` (1 = sans, 2 = mono),
// measured with the same font resolution presentation draws with. Return a
// negative value to fall back to the deterministic estimator (e.g. invalid
// UTF-8). Register before native_sdk_app_start; pass NULL to fall back to
// the estimator on the next layout.
typedef double (*native_sdk_text_measure_fn)(void *context, uint64_t font_id, double size, const char *text, uintptr_t text_len);
int native_sdk_app_set_text_measure(void *app, native_sdk_text_measure_fn measure, void *context);
int native_sdk_app_set_automation_dir(void *app, const char *path, uintptr_t len);
int native_sdk_app_render_pixel_size(void *app, float scale, native_sdk_canvas_pixels_t *out);
int native_sdk_app_render_pixels(void *app, float scale, uint8_t *pixels, uintptr_t pixels_len, native_sdk_canvas_pixels_t *out);
// Incremental sibling of native_sdk_app_render_pixels for a host that
// keeps `pixels` RETAINED across calls (one buffer, one consumer): the
// fast path copies only the pixels changed since the previous call —
// captured off the runtime's own dirty-scissored raster, no second
// render — and reports that region; the first call (and any size or
// scale change) fills the whole buffer with full damage.
int native_sdk_app_render_pixels_damage(void *app, float scale, uint8_t *pixels, uintptr_t pixels_len, native_sdk_canvas_pixels_damage_t *out);