feat(apple): VoiceCatCore Swift package + XCFramework build for macOS/iOS clients
Lays the groundwork for the macOS (AppKit) and iOS (SwiftUI) clients with a shared
Swift core wrapping the C ABI, mirroring the proven Windows VoiceCat.Interop layer.
Architecture decision: macOS UI = AppKit (not SwiftUI) for the most mature VoiceOver
accessibility story — same rationale as the Windows client's WinForms-over-WinUI-3
decision. iOS stays SwiftUI. Recorded in docs/roadmap.md §2.
Build infrastructure (Phase 0):
- clients/apple/scripts/build-xcframework.sh: runs cmake --preset apple-dev, merges
libvoicecat.a + 107 vcpkg static deps into a single ~30 MB fat static library
(libvoicecat-fat.a) via libtool -static (SPM binary targets link one .a per slice),
stages voicecat.h + a generated module.modulemap (module VoiceCatC) into the headers,
runs xcodebuild -create-xcframework -> clients/apple/VoiceCatCore.xcframework.
VoiceCatCore Swift Package (Phase 1):
- Package.swift: binary target (VoiceCatCoreXCF) + library (VoiceCatCore) + test target.
- Sources/VoiceCatCore/: 7 files mirroring the C# VoiceCat.Interop patterns adapted to
Swift native C interop — Enums (9 Swift mirrors of C enums, UInt32-backed), Config,
Event (copies ev.text to String inside the callback — the #1 lifetime rule), Models
(10 Swift value types), Marshaling (C arrays -> Swift + immediate vc_free_*),
Callbacks (@convention(c) + Unmanaged.passUnretained, the Swift analog of C#'s
[UnmanagedCallersOnly] + GCHandle), VoiceCatClient (owns vc_client* as OpaquePointer,
all 38 C ABI functions, deinit -> vc_client_destroy then frees config CStrings, event
delivery on main queue via coalesced DispatchQueue.main drain).
Tests — 6/6 green (swift test against a real voicecat-server):
- testConnectTofuAuthListChannelsRoundTrips, testAdminChannelCrudAccountCrudRoundTrips,
testScreenAudioStreamStartsAndStops, testPerStreamRecvControlsRoundTrip, plus two
static smoke tests. Catches Swift-specific interop bugs (@convention(c) callback
lifetime, Unmanaged pointer resolution, CString memory management, enum raw-value
bridging, struct layout) that C++ ctest cannot. C++ suite still 21/21 green.
Docs updated (house rule): tech-stack.md §2, architecture.md §4, roadmap.md M4 + §2,
clients/apple/README.md (full rewrite), PROGRESS.md, .gitignore.
2026-06-18 14:20:38 +02:00
|
|
|
// swift-tools-version: 5.9
|
|
|
|
|
//
|
|
|
|
|
// VoiceCatCore — the shared Swift core for the VoiceCat macOS (AppKit) and iOS (SwiftUI)
|
|
|
|
|
// clients. It wraps libvoicecat's C ABI (core/include/voicecat.h) as imported through the
|
|
|
|
|
// VoiceCatCore.xcframework binary target's module map (`import VoiceCatC`), and exposes a
|
|
|
|
|
// Swift-idiomatic, @MainActor-safe surface.
|
|
|
|
|
//
|
|
|
|
|
// Architecture: docs/architecture.md §4 ("one core, many faces"). The Windows C# client
|
|
|
|
|
// (clients/windows/VoiceCat.Interop) is the proven mirror of this same layering — the Swift
|
|
|
|
|
// wrapper follows the same patterns (callback-lifetime, string-lifetime, event-delivery
|
|
|
|
|
// thread handoff, immediate vc_free_* on list reads) adapted to Swift's interop model.
|
|
|
|
|
//
|
|
|
|
|
// The XCFramework is a LOCAL BUILD ARTIFACT — run `scripts/build-xcframework.sh` before
|
|
|
|
|
// `swift build` / `swift test`. See clients/apple/README.md.
|
|
|
|
|
import PackageDescription
|
|
|
|
|
|
|
|
|
|
let package = Package(
|
|
|
|
|
name: "VoiceCatCore",
|
2026-06-19 02:10:25 +02:00
|
|
|
// macOS 14 (Sonoma) is the AppKit client's deployment target. iOS 17 is the SwiftUI client
|
|
|
|
|
// target (clients/apple/iOS/). Run `scripts/build-xcframework.sh --all` to produce all
|
|
|
|
|
// three slices: macos-arm64, ios-arm64, ios-arm64-simulator.
|
feat(apple): VoiceCatCore Swift package + XCFramework build for macOS/iOS clients
Lays the groundwork for the macOS (AppKit) and iOS (SwiftUI) clients with a shared
Swift core wrapping the C ABI, mirroring the proven Windows VoiceCat.Interop layer.
Architecture decision: macOS UI = AppKit (not SwiftUI) for the most mature VoiceOver
accessibility story — same rationale as the Windows client's WinForms-over-WinUI-3
decision. iOS stays SwiftUI. Recorded in docs/roadmap.md §2.
Build infrastructure (Phase 0):
- clients/apple/scripts/build-xcframework.sh: runs cmake --preset apple-dev, merges
libvoicecat.a + 107 vcpkg static deps into a single ~30 MB fat static library
(libvoicecat-fat.a) via libtool -static (SPM binary targets link one .a per slice),
stages voicecat.h + a generated module.modulemap (module VoiceCatC) into the headers,
runs xcodebuild -create-xcframework -> clients/apple/VoiceCatCore.xcframework.
VoiceCatCore Swift Package (Phase 1):
- Package.swift: binary target (VoiceCatCoreXCF) + library (VoiceCatCore) + test target.
- Sources/VoiceCatCore/: 7 files mirroring the C# VoiceCat.Interop patterns adapted to
Swift native C interop — Enums (9 Swift mirrors of C enums, UInt32-backed), Config,
Event (copies ev.text to String inside the callback — the #1 lifetime rule), Models
(10 Swift value types), Marshaling (C arrays -> Swift + immediate vc_free_*),
Callbacks (@convention(c) + Unmanaged.passUnretained, the Swift analog of C#'s
[UnmanagedCallersOnly] + GCHandle), VoiceCatClient (owns vc_client* as OpaquePointer,
all 38 C ABI functions, deinit -> vc_client_destroy then frees config CStrings, event
delivery on main queue via coalesced DispatchQueue.main drain).
Tests — 6/6 green (swift test against a real voicecat-server):
- testConnectTofuAuthListChannelsRoundTrips, testAdminChannelCrudAccountCrudRoundTrips,
testScreenAudioStreamStartsAndStops, testPerStreamRecvControlsRoundTrip, plus two
static smoke tests. Catches Swift-specific interop bugs (@convention(c) callback
lifetime, Unmanaged pointer resolution, CString memory management, enum raw-value
bridging, struct layout) that C++ ctest cannot. C++ suite still 21/21 green.
Docs updated (house rule): tech-stack.md §2, architecture.md §4, roadmap.md M4 + §2,
clients/apple/README.md (full rewrite), PROGRESS.md, .gitignore.
2026-06-18 14:20:38 +02:00
|
|
|
platforms: [
|
|
|
|
|
.macOS(.v14),
|
2026-06-19 02:10:25 +02:00
|
|
|
.iOS(.v17),
|
feat(apple): VoiceCatCore Swift package + XCFramework build for macOS/iOS clients
Lays the groundwork for the macOS (AppKit) and iOS (SwiftUI) clients with a shared
Swift core wrapping the C ABI, mirroring the proven Windows VoiceCat.Interop layer.
Architecture decision: macOS UI = AppKit (not SwiftUI) for the most mature VoiceOver
accessibility story — same rationale as the Windows client's WinForms-over-WinUI-3
decision. iOS stays SwiftUI. Recorded in docs/roadmap.md §2.
Build infrastructure (Phase 0):
- clients/apple/scripts/build-xcframework.sh: runs cmake --preset apple-dev, merges
libvoicecat.a + 107 vcpkg static deps into a single ~30 MB fat static library
(libvoicecat-fat.a) via libtool -static (SPM binary targets link one .a per slice),
stages voicecat.h + a generated module.modulemap (module VoiceCatC) into the headers,
runs xcodebuild -create-xcframework -> clients/apple/VoiceCatCore.xcframework.
VoiceCatCore Swift Package (Phase 1):
- Package.swift: binary target (VoiceCatCoreXCF) + library (VoiceCatCore) + test target.
- Sources/VoiceCatCore/: 7 files mirroring the C# VoiceCat.Interop patterns adapted to
Swift native C interop — Enums (9 Swift mirrors of C enums, UInt32-backed), Config,
Event (copies ev.text to String inside the callback — the #1 lifetime rule), Models
(10 Swift value types), Marshaling (C arrays -> Swift + immediate vc_free_*),
Callbacks (@convention(c) + Unmanaged.passUnretained, the Swift analog of C#'s
[UnmanagedCallersOnly] + GCHandle), VoiceCatClient (owns vc_client* as OpaquePointer,
all 38 C ABI functions, deinit -> vc_client_destroy then frees config CStrings, event
delivery on main queue via coalesced DispatchQueue.main drain).
Tests — 6/6 green (swift test against a real voicecat-server):
- testConnectTofuAuthListChannelsRoundTrips, testAdminChannelCrudAccountCrudRoundTrips,
testScreenAudioStreamStartsAndStops, testPerStreamRecvControlsRoundTrip, plus two
static smoke tests. Catches Swift-specific interop bugs (@convention(c) callback
lifetime, Unmanaged pointer resolution, CString memory management, enum raw-value
bridging, struct layout) that C++ ctest cannot. C++ suite still 21/21 green.
Docs updated (house rule): tech-stack.md §2, architecture.md §4, roadmap.md M4 + §2,
clients/apple/README.md (full rewrite), PROGRESS.md, .gitignore.
2026-06-18 14:20:38 +02:00
|
|
|
],
|
|
|
|
|
products: [
|
|
|
|
|
.library(name: "VoiceCatCore", targets: ["VoiceCatCore"]),
|
|
|
|
|
],
|
|
|
|
|
targets: [
|
|
|
|
|
// Binary target — the prebuilt static lib + headers + module map. Produced by
|
|
|
|
|
// scripts/build-xcframework.sh from the `apple-dev` CMake preset.
|
|
|
|
|
.binaryTarget(
|
|
|
|
|
name: "VoiceCatCoreXCF",
|
|
|
|
|
path: "VoiceCatCore.xcframework"
|
|
|
|
|
),
|
|
|
|
|
// The Swift wrapper library — what the macOS/iOS apps import as `import VoiceCatCore`.
|
|
|
|
|
.target(
|
|
|
|
|
name: "VoiceCatCore",
|
|
|
|
|
dependencies: ["VoiceCatCoreXCF"],
|
|
|
|
|
path: "Sources/VoiceCatCore"
|
|
|
|
|
),
|
|
|
|
|
// Smoke tests against a real voicecat-server — mirrors clients/windows/
|
|
|
|
|
// VoiceCat.Interop.Tests/VoiceCatClientSmokeTests.cs. Requires the `dev` CMake preset
|
|
|
|
|
// to be built (build/dev/bin/voicecat-server + voicecat-admin).
|
|
|
|
|
//
|
|
|
|
|
// linkerSettings: libvoicecat.a is a static C++20 library (built by the apple-dev
|
|
|
|
|
// preset with vcpkg's clang), so the final executable must link libc++ (the LLVM C++
|
|
|
|
|
// standard library on macOS). vcpkg's static deps (mbedtls/sodium/opus/protobuf/
|
|
|
|
|
// sqlite3/spdlog/asio) are already compiled into the .a; macOS system frameworks
|
|
|
|
|
// (CoreAudio/CoreFoundation) are auto-discovered by the linker (PROGRESS.md).
|
|
|
|
|
.testTarget(
|
|
|
|
|
name: "VoiceCatCoreTests",
|
|
|
|
|
dependencies: ["VoiceCatCore"],
|
|
|
|
|
path: "Tests/VoiceCatCoreTests",
|
|
|
|
|
linkerSettings: [
|
|
|
|
|
.linkedLibrary("c++"),
|
|
|
|
|
]
|
|
|
|
|
),
|
|
|
|
|
]
|
|
|
|
|
)
|