Add QR scanner support for macOS with camera selection

- Refactor QR scanner into separate Scanner module for iOS and macOS
- Add camera selection menu for devices with multiple cameras
- Use Vision framework for QR detection on macOS
- Remove tvOS QR scanner support (continuity camera)
- Add camera entitlement and usage description for macOS
- Fix minor issues in Intents and ExtensionProvider
This commit is contained in:
世界
2026-01-01 18:18:03 +08:00
parent 251a79f5d7
commit e2c378d42b
15 changed files with 687 additions and 108 deletions
@@ -9,14 +9,15 @@ public struct NewProfileMenuView: View {
@EnvironmentObject private var environments: ExtensionEnvironments @EnvironmentObject private var environments: ExtensionEnvironments
@Environment(\.dismiss) private var dismiss @Environment(\.dismiss) private var dismiss
@State private var alert: AlertState? @State private var alert: AlertState?
@State private var showFileImporter = false
@State private var importRequest: NewProfileView.ImportRequest? @State private var importRequest: NewProfileView.ImportRequest?
@State private var localImportRequest: NewProfileView.LocalImportRequest? @State private var localImportRequest: NewProfileView.LocalImportRequest?
#if os(iOS) #if os(tvOS)
@State private var showQRScanner = false
#elseif os(tvOS)
@State private var importCompleted = false @State private var importCompleted = false
#elseif os(macOS) #else
@State private var showFileImporter = false
@State private var showQRScanner = false
#endif
#if os(macOS)
@State private var showNewProfile = false @State private var showNewProfile = false
#endif #endif
@@ -76,6 +77,19 @@ public struct NewProfileMenuView: View {
}) })
.environmentObject(environments) .environmentObject(environments)
} }
.sheet(isPresented: $showQRScanner) {
QRScannerView { result in
handleQRScanResult(result)
}
.frame(minWidth: 500, minHeight: 400)
}
.sheet(item: $importRequest) { request in
NewProfileView(request, onSuccess: { profile in
await SharedPreferences.selectedProfileID.set(profile.mustID)
dismiss()
})
.environmentObject(environments)
}
} }
#endif #endif
@@ -117,10 +131,10 @@ public struct NewProfileMenuView: View {
handleFileImport(result) handleFileImport(result)
} }
#endif #endif
#if os(iOS) #if !os(tvOS)
.sheet(isPresented: $showQRScanner) { .sheet(isPresented: $showQRScanner) {
QRCodeScannerView { remoteProfile in QRScannerView { result in
importRequest = NewProfileView.ImportRequest(name: remoteProfile.name, url: remoteProfile.url) handleQRScanResult(result)
} }
} }
#endif #endif
@@ -148,7 +162,7 @@ public struct NewProfileMenuView: View {
} }
#endif #endif
#if os(iOS) #if !os(tvOS)
FormButton { FormButton {
showQRScanner = true showQRScanner = true
} label: { } label: {
@@ -214,4 +228,26 @@ public struct NewProfileMenuView: View {
} }
} }
#endif #endif
#if !os(tvOS)
private func handleQRScanResult(_ result: QRScanResult) {
var error: NSError?
let remoteProfile = LibboxParseRemoteProfileImportLink(result.string, &error)
if let error {
alert = AlertState(
title: String(localized: "Invalid QR Code"),
message: error.localizedDescription
)
return
}
guard let remoteProfile else {
alert = AlertState(
title: String(localized: "Invalid QR Code"),
message: String(localized: "The QR code does not contain a valid profile import link.")
)
return
}
importRequest = NewProfileView.ImportRequest(name: remoteProfile.name, url: remoteProfile.url)
}
#endif
} }
@@ -135,13 +135,11 @@ public struct NewProfileView: View {
#if os(macOS) #if os(macOS)
private var macOSBody: some View { private var macOSBody: some View {
VStack(alignment: .leading, spacing: 0) { VStack(alignment: .leading, spacing: 0) {
if !viewModel.isImport { Text(viewModel.isImport ? "Import Profile" : "New Profile")
Text("New Profile") .font(.headline)
.font(.headline) .padding(.horizontal, 20)
.padding(.horizontal, 20) .padding(.top, 20)
.padding(.top, 20) .padding(.bottom, 12)
.padding(.bottom, 12)
}
formContent formContent
} }
@@ -1,63 +0,0 @@
#if os(iOS)
import AVFoundation
import CodeScanner
import Libbox
import Library
import SwiftUI
@MainActor
public struct QRCodeScannerView: View {
@Environment(\.dismiss) private var dismiss
@State private var alert: AlertState?
private let onScan: (LibboxImportRemoteProfile) -> Void
public init(onScan: @escaping (LibboxImportRemoteProfile) -> Void) {
self.onScan = onScan
}
public var body: some View {
NavigationStackCompat {
CodeScannerView(codeTypes: [.qr], showViewfinder: true) { response in
handleScan(response)
}
.ignoresSafeArea()
.navigationTitle("Scan QR Code")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button("Cancel") { dismiss() }
}
}
}
.alert($alert)
}
private func handleScan(_ result: Result<ScanResult, ScanError>) {
switch result {
case let .success(scanResult):
var error: NSError?
let remoteProfile = LibboxParseRemoteProfileImportLink(scanResult.string, &error)
if let error {
alert = AlertState(title: String(localized: "Invalid QR Code"), message: error.localizedDescription)
return
}
guard let remoteProfile else {
alert = AlertState(title: String(localized: "Invalid QR Code"), message: String(localized: "The QR code does not contain a valid profile import link."))
return
}
dismiss()
onScan(remoteProfile)
case let .failure(error):
switch error {
case .permissionDenied:
alert = AlertState(title: String(localized: "Camera Access Denied"), message: String(localized: "Please enable camera access in Settings to scan QR codes."))
default:
alert = AlertState(title: String(localized: "Scanner Error"), message: String(describing: error))
}
}
}
}
#endif
@@ -0,0 +1,32 @@
import AVFoundation
import SwiftUI
public enum QRScanError: Error, LocalizedError {
case cameraUnavailable
case permissionDenied
case scanFailed(Error)
case invalidCode
public var errorDescription: String? {
switch self {
case .cameraUnavailable:
return String(localized: "Camera is not available")
case .permissionDenied:
return String(localized: "Camera access denied")
case let .scanFailed(error):
return error.localizedDescription
case .invalidCode:
return String(localized: "Invalid QR code")
}
}
}
public struct QRScanResult: Sendable {
public let string: String
public let type: AVMetadataObject.ObjectType
public init(string: String, type: AVMetadataObject.ObjectType) {
self.string = string
self.type = type
}
}
@@ -0,0 +1,227 @@
#if os(iOS)
import AVFoundation
import SwiftUI
import UIKit
@MainActor
final class QRScannerController: NSObject, ObservableObject {
private var captureSession: AVCaptureSession?
private var previewLayer: AVCaptureVideoPreviewLayer?
private var metadataOutput: AVCaptureMetadataOutput?
private var didFinishScanning = false
@Published var availableCameras: [AVCaptureDevice] = []
@Published var selectedCamera: AVCaptureDevice?
let previewView = UIView()
var onScan: ((Result<QRScanResult, QRScanError>) -> Void)?
override init() {
super.init()
previewView.backgroundColor = .black
refreshCameraList()
}
func refreshCameraList() {
var deviceTypes: [AVCaptureDevice.DeviceType] = [
.builtInWideAngleCamera,
.builtInUltraWideCamera,
.builtInTelephotoCamera,
]
if #available(iOS 17.0, *) {
deviceTypes.append(.external)
}
let discoverySession = AVCaptureDevice.DiscoverySession(
deviceTypes: deviceTypes,
mediaType: .video,
position: .unspecified
)
availableCameras = discoverySession.devices
if selectedCamera == nil {
selectedCamera = availableCameras.first
}
}
func selectCamera(_ camera: AVCaptureDevice) {
guard camera.uniqueID != selectedCamera?.uniqueID else { return }
selectedCamera = camera
didFinishScanning = false
if captureSession != nil {
stopScanning()
captureSession = nil
previewLayer?.removeFromSuperlayer()
previewLayer = nil
startScanning()
}
}
func startScanning() {
guard captureSession == nil else {
if captureSession?.isRunning == false {
DispatchQueue.global(qos: .userInteractive).async { [weak self] in
self?.captureSession?.startRunning()
}
}
return
}
switch AVCaptureDevice.authorizationStatus(for: .video) {
case .authorized:
setupCaptureSession()
case .notDetermined:
AVCaptureDevice.requestAccess(for: .video) { [weak self] granted in
DispatchQueue.main.async {
if granted {
self?.setupCaptureSession()
} else {
self?.onScan?(.failure(.permissionDenied))
}
}
}
case .denied, .restricted:
onScan?(.failure(.permissionDenied))
@unknown default:
onScan?(.failure(.cameraUnavailable))
}
}
func stopScanning() {
DispatchQueue.global(qos: .userInteractive).async { [weak self] in
self?.captureSession?.stopRunning()
}
}
func reset() {
didFinishScanning = false
}
private func setupCaptureSession() {
let session = AVCaptureSession()
guard let videoCaptureDevice = selectedCamera ?? AVCaptureDevice.default(for: .video) else {
onScan?(.failure(.cameraUnavailable))
return
}
let videoInput: AVCaptureDeviceInput
do {
videoInput = try AVCaptureDeviceInput(device: videoCaptureDevice)
} catch {
onScan?(.failure(.scanFailed(error)))
return
}
guard session.canAddInput(videoInput) else {
onScan?(.failure(.cameraUnavailable))
return
}
session.addInput(videoInput)
let metadataOutput = AVCaptureMetadataOutput()
guard session.canAddOutput(metadataOutput) else {
onScan?(.failure(.cameraUnavailable))
return
}
session.addOutput(metadataOutput)
metadataOutput.setMetadataObjectsDelegate(self, queue: .main)
metadataOutput.metadataObjectTypes = [.qr]
self.metadataOutput = metadataOutput
self.captureSession = session
let previewLayer = AVCaptureVideoPreviewLayer(session: session)
previewLayer.videoGravity = .resizeAspectFill
previewLayer.frame = previewView.bounds
previewView.layer.addSublayer(previewLayer)
self.previewLayer = previewLayer
DispatchQueue.global(qos: .userInteractive).async { [weak self] in
self?.captureSession?.startRunning()
}
}
func updatePreviewFrame(_ frame: CGRect) {
previewLayer?.frame = frame
}
}
extension QRScannerController: AVCaptureMetadataOutputObjectsDelegate {
nonisolated func metadataOutput(
_ output: AVCaptureMetadataOutput,
didOutput metadataObjects: [AVMetadataObject],
from connection: AVCaptureConnection
) {
Task { @MainActor in
guard !didFinishScanning,
let metadataObject = metadataObjects.first,
let readableObject = metadataObject as? AVMetadataMachineReadableCodeObject,
let stringValue = readableObject.stringValue
else {
return
}
didFinishScanning = true
AudioServicesPlaySystemSound(SystemSoundID(kSystemSoundID_Vibrate))
let result = QRScanResult(string: stringValue, type: readableObject.type)
onScan?(.success(result))
}
}
}
struct QRScannerControllerView: UIViewControllerRepresentable {
let controller: QRScannerController
func makeUIViewController(context: Context) -> UIViewController {
let viewController = QRScannerViewController(controller: controller)
return viewController
}
func updateUIViewController(_ uiViewController: UIViewController, context: Context) {}
}
private class QRScannerViewController: UIViewController {
let controller: QRScannerController
init(controller: QRScannerController) {
self.controller = controller
super.init(nibName: nil, bundle: nil)
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .black
view.addSubview(controller.previewView)
controller.previewView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
controller.previewView.topAnchor.constraint(equalTo: view.topAnchor),
controller.previewView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
controller.previewView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
controller.previewView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
])
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
controller.updatePreviewFrame(view.bounds)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
controller.startScanning()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
controller.stopScanning()
}
}
#endif
@@ -0,0 +1,241 @@
#if os(macOS)
import AppKit
import AVFoundation
import SwiftUI
import Vision
@MainActor
final class QRScannerController: NSObject, ObservableObject {
private var captureSession: AVCaptureSession?
private var previewLayer: AVCaptureVideoPreviewLayer?
private var videoOutput: AVCaptureVideoDataOutput?
private var didFinishScanning = false
@Published var availableCameras: [AVCaptureDevice] = []
@Published var selectedCamera: AVCaptureDevice?
let previewView = NSView()
var onScan: ((Result<QRScanResult, QRScanError>) -> Void)?
override init() {
super.init()
previewView.wantsLayer = true
previewView.layer?.backgroundColor = NSColor.black.cgColor
refreshCameraList()
}
func refreshCameraList() {
let discoverySession = AVCaptureDevice.DiscoverySession(
deviceTypes: [.builtInWideAngleCamera, .externalUnknown],
mediaType: .video,
position: .unspecified
)
availableCameras = discoverySession.devices
if selectedCamera == nil {
selectedCamera = availableCameras.first
}
}
func selectCamera(_ camera: AVCaptureDevice) {
guard camera.uniqueID != selectedCamera?.uniqueID else { return }
selectedCamera = camera
didFinishScanning = false
if captureSession != nil {
stopScanning()
captureSession = nil
previewLayer?.removeFromSuperlayer()
previewLayer = nil
startScanning()
}
}
func startScanning() {
guard captureSession == nil else {
if captureSession?.isRunning == false {
DispatchQueue.global(qos: .userInteractive).async { [weak self] in
self?.captureSession?.startRunning()
}
}
return
}
switch AVCaptureDevice.authorizationStatus(for: .video) {
case .authorized:
setupCaptureSession()
case .notDetermined:
AVCaptureDevice.requestAccess(for: .video) { [weak self] granted in
DispatchQueue.main.async {
if granted {
self?.setupCaptureSession()
} else {
self?.onScan?(.failure(.permissionDenied))
}
}
}
case .denied, .restricted:
onScan?(.failure(.permissionDenied))
@unknown default:
onScan?(.failure(.cameraUnavailable))
}
}
func stopScanning() {
DispatchQueue.global(qos: .userInteractive).async { [weak self] in
self?.captureSession?.stopRunning()
}
}
func reset() {
didFinishScanning = false
}
private func setupCaptureSession() {
let session = AVCaptureSession()
guard let videoCaptureDevice = selectedCamera ?? AVCaptureDevice.default(for: .video) else {
onScan?(.failure(.cameraUnavailable))
return
}
let videoInput: AVCaptureDeviceInput
do {
videoInput = try AVCaptureDeviceInput(device: videoCaptureDevice)
} catch {
onScan?(.failure(.scanFailed(error)))
return
}
guard session.canAddInput(videoInput) else {
onScan?(.failure(.cameraUnavailable))
return
}
session.addInput(videoInput)
let videoOutput = AVCaptureVideoDataOutput()
videoOutput.videoSettings = [
kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_32BGRA,
]
videoOutput.setSampleBufferDelegate(self, queue: DispatchQueue(label: "QRScannerQueue"))
guard session.canAddOutput(videoOutput) else {
onScan?(.failure(.cameraUnavailable))
return
}
session.addOutput(videoOutput)
self.videoOutput = videoOutput
self.captureSession = session
let previewLayer = AVCaptureVideoPreviewLayer(session: session)
previewLayer.videoGravity = .resizeAspectFill
previewLayer.frame = previewView.bounds
previewView.layer?.addSublayer(previewLayer)
self.previewLayer = previewLayer
DispatchQueue.global(qos: .userInteractive).async { [weak self] in
self?.captureSession?.startRunning()
}
}
func updatePreviewFrame(_ frame: CGRect) {
previewLayer?.frame = frame
}
private func processQRCode(_ payloadString: String) {
guard !didFinishScanning else { return }
didFinishScanning = true
DispatchQueue.main.async { [weak self] in
NSSound.beep()
let result = QRScanResult(string: payloadString, type: .qr)
self?.onScan?(.success(result))
}
}
}
extension QRScannerController: AVCaptureVideoDataOutputSampleBufferDelegate {
nonisolated func captureOutput(
_ output: AVCaptureOutput,
didOutput sampleBuffer: CMSampleBuffer,
from connection: AVCaptureConnection
) {
guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return }
let request = VNDetectBarcodesRequest { [weak self] request, _ in
guard let results = request.results as? [VNBarcodeObservation] else { return }
for result in results {
if result.symbology == .qr, let payload = result.payloadStringValue {
self?.processQRCode(payload)
return
}
}
}
request.symbologies = [.qr]
let handler = VNImageRequestHandler(cvPixelBuffer: pixelBuffer, options: [:])
try? handler.perform([request])
}
}
struct QRScannerControllerView: NSViewControllerRepresentable {
let controller: QRScannerController
func makeNSViewController(context: Context) -> NSViewController {
let viewController = QRScannerViewController(controller: controller)
return viewController
}
func updateNSViewController(_ nsViewController: NSViewController, context: Context) {}
}
private class QRScannerViewController: NSViewController {
let controller: QRScannerController
init(controller: QRScannerController) {
self.controller = controller
super.init(nibName: nil, bundle: nil)
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func loadView() {
view = NSView()
view.wantsLayer = true
view.layer?.backgroundColor = NSColor.black.cgColor
}
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(controller.previewView)
controller.previewView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
controller.previewView.topAnchor.constraint(equalTo: view.topAnchor),
controller.previewView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
controller.previewView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
controller.previewView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
])
}
override func viewDidLayout() {
super.viewDidLayout()
controller.updatePreviewFrame(view.bounds)
}
override func viewWillAppear() {
super.viewWillAppear()
controller.startScanning()
}
override func viewWillDisappear() {
super.viewWillDisappear()
controller.stopScanning()
}
}
#endif
@@ -0,0 +1,127 @@
#if !os(tvOS)
import AVFoundation
import Library
import SwiftUI
@MainActor
public struct QRScannerView: View {
@Environment(\.dismiss) private var dismiss
@State private var alert: AlertState?
@StateObject private var controller = QRScannerController()
private let onScan: (QRScanResult) -> Void
public init(onScan: @escaping (QRScanResult) -> Void) {
self.onScan = onScan
}
public var body: some View {
#if os(iOS)
iOSBody
#elseif os(macOS)
macOSBody
#endif
}
#if os(iOS)
private var iOSBody: some View {
NavigationStackCompat {
QRScannerControllerView(controller: controller)
.ignoresSafeArea()
.navigationTitle("Scan QR Code")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button("Cancel") { dismiss() }
}
ToolbarItem(placement: .primaryAction) {
Menu {
if controller.availableCameras.count > 1 {
Menu("Camera") {
ForEach(controller.availableCameras, id: \.uniqueID) { camera in
Button {
controller.selectCamera(camera)
} label: {
if camera.uniqueID == controller.selectedCamera?.uniqueID {
Label(camera.localizedName, systemImage: "checkmark")
} else {
Text(camera.localizedName)
}
}
}
}
}
} label: {
Image(systemName: "ellipsis.circle")
}
}
}
}
.alert($alert)
.onAppear {
controller.onScan = handleScanResult
}
}
#endif
#if os(macOS)
private var macOSBody: some View {
VStack(spacing: 0) {
QRScannerControllerView(controller: controller)
.frame(minWidth: 400, minHeight: 300)
Divider()
HStack {
if controller.availableCameras.count > 1 {
Picker("Camera", selection: Binding(
get: { controller.selectedCamera },
set: { camera in
if let camera {
controller.selectCamera(camera)
}
}
)) {
ForEach(controller.availableCameras, id: \.uniqueID) { camera in
Text(camera.localizedName).tag(camera as AVCaptureDevice?)
}
}
.frame(maxWidth: 250)
}
Spacer()
Button("Cancel") { dismiss() }
.keyboardShortcut(.cancelAction)
}
.padding()
}
.alert($alert)
.onAppear {
controller.onScan = handleScanResult
}
}
#endif
private func handleScanResult(_ result: Result<QRScanResult, QRScanError>) {
switch result {
case let .success(scanResult):
dismiss()
onScan(scanResult)
case let .failure(error):
switch error {
case .permissionDenied:
alert = AlertState(
title: String(localized: "Camera Access Denied"),
message: String(localized: "Please enable camera access in Settings to scan QR codes.")
)
default:
alert = AlertState(
title: String(localized: "Scanner Error"),
message: error.localizedDescription
)
}
}
}
}
#endif
+1 -1
View File
@@ -144,7 +144,7 @@ struct GetCurrentProfile: AppIntent {
} }
func perform() async throws -> some IntentResult & ReturnsValue<String> { func perform() async throws -> some IntentResult & ReturnsValue<String> {
guard let profile = try await ProfileManager.get(await SharedPreferences.selectedProfileID.get()) else { guard let profile = try await ProfileManager.get(SharedPreferences.selectedProfileID.get()) else {
throw NSError(domain: "No profile selected", code: 0) throw NSError(domain: "No profile selected", code: 0)
} }
return .result(value: profile.name) return .result(value: profile.name)
@@ -249,7 +249,8 @@ public class ExtensionPlatformInterface: NSObject, LibboxPlatformInterfaceProtoc
private func onUpdateDefaultInterface(_ listener: LibboxInterfaceUpdateListenerProtocol, _ path: Network.NWPath) { private func onUpdateDefaultInterface(_ listener: LibboxInterfaceUpdateListenerProtocol, _ path: Network.NWPath) {
guard path.status != .unsatisfied, guard path.status != .unsatisfied,
let defaultInterface = path.availableInterfaces.first else { let defaultInterface = path.availableInterfaces.first
else {
listener.updateDefaultInterface("", interfaceIndex: -1, isExpensive: false, isConstrained: false) listener.updateDefaultInterface("", interfaceIndex: -1, isExpensive: false, isConstrained: false)
return return
} }
+1 -1
View File
@@ -13,7 +13,7 @@ open class ExtensionProvider: NEPacketTunnelProvider {
private var commandServer: LibboxCommandServer! private var commandServer: LibboxCommandServer!
private var platformInterface: ExtensionPlatformInterface! private var platformInterface: ExtensionPlatformInterface!
override open func startTunnel(options startOptions: [String: NSObject]?) async throws { override open func startTunnel(options _: [String: NSObject]?) async throws {
let options = LibboxSetupOptions() let options = LibboxSetupOptions()
options.basePath = FilePath.sharedDirectory.relativePath options.basePath = FilePath.sharedDirectory.relativePath
options.workingPath = FilePath.workingDirectory.relativePath options.workingPath = FilePath.workingDirectory.relativePath
+2
View File
@@ -30,5 +30,7 @@
<true/> <true/>
<key>com.apple.security.network.client</key> <key>com.apple.security.network.client</key>
<true/> <true/>
<key>com.apple.security.device.camera</key>
<true/>
</dict> </dict>
</plist> </plist>
+2
View File
@@ -115,5 +115,7 @@
<string>sing-box uses the Location permission to provide users with routing based on WIFI SSID and BSSID rules, without reading your location.</string> <string>sing-box uses the Location permission to provide users with routing based on WIFI SSID and BSSID rules, without reading your location.</string>
<key>NSLocalNetworkUsageDescription</key> <key>NSLocalNetworkUsageDescription</key>
<string>As a universal proxy platform, sing-box configures routing according to your configuration.</string> <string>As a universal proxy platform, sing-box configures routing according to your configuration.</string>
<key>NSCameraUsageDescription</key>
<string>Camera access is required to scan QR codes for importing profiles.</string>
</dict> </dict>
</plist> </plist>
+2
View File
@@ -28,5 +28,7 @@
<true/> <true/>
<key>com.apple.security.network.client</key> <key>com.apple.security.network.client</key>
<true/> <true/>
<key>com.apple.security.device.camera</key>
<true/>
</dict> </dict>
</plist> </plist>
-17
View File
@@ -9,7 +9,6 @@
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
3A017F922A4AB2E4009149FA /* GRDB in Frameworks */ = {isa = PBXBuildFile; productRef = 3A017F912A4AB2E4009149FA /* GRDB */; }; 3A017F922A4AB2E4009149FA /* GRDB in Frameworks */ = {isa = PBXBuildFile; productRef = 3A017F912A4AB2E4009149FA /* GRDB */; };
3A096F8F2A4ED3DE00D4A2ED /* Extension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 3A096F862A4ED3DE00D4A2ED /* Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 3A096F8F2A4ED3DE00D4A2ED /* Extension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 3A096F862A4ED3DE00D4A2ED /* Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
3A1E8E692EDEC1AF00ADD104 /* CodeScanner in Frameworks */ = {isa = PBXBuildFile; productRef = 3A1E8E682EDEC1AF00ADD104 /* CodeScanner */; };
3A2E87F22ED5A91100644195 /* Runestone in Frameworks */ = {isa = PBXBuildFile; productRef = 3A2E87F12ED5A91100644195 /* Runestone */; }; 3A2E87F22ED5A91100644195 /* Runestone in Frameworks */ = {isa = PBXBuildFile; productRef = 3A2E87F12ED5A91100644195 /* Runestone */; };
3A2E87FB2ED5ABDA00644195 /* TreeSitterJSON5Runestone in Frameworks */ = {isa = PBXBuildFile; productRef = 3A2E87FA2ED5ABDA00644195 /* TreeSitterJSON5Runestone */; }; 3A2E87FB2ED5ABDA00644195 /* TreeSitterJSON5Runestone in Frameworks */ = {isa = PBXBuildFile; productRef = 3A2E87FA2ED5ABDA00644195 /* TreeSitterJSON5Runestone */; };
3A3AA7FC2A4EFDAE002F78AB /* Library.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3AEC211D2A459B4700A63465 /* Library.framework */; }; 3A3AA7FC2A4EFDAE002F78AB /* Library.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3AEC211D2A459B4700A63465 /* Library.framework */; };
@@ -564,7 +563,6 @@
files = ( files = (
3A4EAD1B2A4FEB02005435B3 /* Library.framework in Frameworks */, 3A4EAD1B2A4FEB02005435B3 /* Library.framework in Frameworks */,
3A4A020D2B53E3DC004EFB87 /* QRCode in Frameworks */, 3A4A020D2B53E3DC004EFB87 /* QRCode in Frameworks */,
3A1E8E692EDEC1AF00ADD104 /* CodeScanner in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@@ -803,7 +801,6 @@
name = ApplicationLibrary; name = ApplicationLibrary;
packageProductDependencies = ( packageProductDependencies = (
3A4A020C2B53E3DC004EFB87 /* QRCode */, 3A4A020C2B53E3DC004EFB87 /* QRCode */,
3A1E8E682EDEC1AF00ADD104 /* CodeScanner */,
); );
productName = ApplicationLibrary; productName = ApplicationLibrary;
productReference = 3A4EAD102A4FEAE6005435B3 /* ApplicationLibrary.framework */; productReference = 3A4EAD102A4FEAE6005435B3 /* ApplicationLibrary.framework */;
@@ -1140,7 +1137,6 @@
3A2E87F02ED5A91100644195 /* XCLocalSwiftPackageReference "Frameworks/Runestone" */, 3A2E87F02ED5A91100644195 /* XCLocalSwiftPackageReference "Frameworks/Runestone" */,
3A2E87F92ED5ABCF00644195 /* XCLocalSwiftPackageReference "Frameworks/TreeSitterJSON5" */, 3A2E87F92ED5ABCF00644195 /* XCLocalSwiftPackageReference "Frameworks/TreeSitterJSON5" */,
3ACE5E012EE1A91100644196 /* XCRemoteSwiftPackageReference "CodeEditSourceEditor" */, 3ACE5E012EE1A91100644196 /* XCRemoteSwiftPackageReference "CodeEditSourceEditor" */,
3A1E8E672EDEC1AF00ADD104 /* XCRemoteSwiftPackageReference "CodeScanner" */,
); );
productRefGroup = 3AEC20C72A45991900A63465 /* Products */; productRefGroup = 3AEC20C72A45991900A63465 /* Products */;
projectDirPath = ""; projectDirPath = "";
@@ -2750,14 +2746,6 @@
minimumVersion = 6.15.1; minimumVersion = 6.15.1;
}; };
}; };
3A1E8E672EDEC1AF00ADD104 /* XCRemoteSwiftPackageReference "CodeScanner" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/twostraws/CodeScanner.git";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 2.5.2;
};
};
3A4A020B2B53E3DC004EFB87 /* XCRemoteSwiftPackageReference "qrcode" */ = { 3A4A020B2B53E3DC004EFB87 /* XCRemoteSwiftPackageReference "qrcode" */ = {
isa = XCRemoteSwiftPackageReference; isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/dagronf/qrcode.git"; repositoryURL = "https://github.com/dagronf/qrcode.git";
@@ -2798,11 +2786,6 @@
package = 3A017F902A4AB2E4009149FA /* XCRemoteSwiftPackageReference "GRDB" */; package = 3A017F902A4AB2E4009149FA /* XCRemoteSwiftPackageReference "GRDB" */;
productName = GRDB; productName = GRDB;
}; };
3A1E8E682EDEC1AF00ADD104 /* CodeScanner */ = {
isa = XCSwiftPackageProductDependency;
package = 3A1E8E672EDEC1AF00ADD104 /* XCRemoteSwiftPackageReference "CodeScanner" */;
productName = CodeScanner;
};
3A2E87F12ED5A91100644195 /* Runestone */ = { 3A2E87F12ED5A91100644195 /* Runestone */ = {
isa = XCSwiftPackageProductDependency; isa = XCSwiftPackageProductDependency;
package = 3A2E87F02ED5A91100644195 /* XCLocalSwiftPackageReference "Frameworks/Runestone" */; package = 3A2E87F02ED5A91100644195 /* XCLocalSwiftPackageReference "Frameworks/Runestone" */;
@@ -46,15 +46,6 @@
"version" : "0.12.1" "version" : "0.12.1"
} }
}, },
{
"identity" : "codescanner",
"kind" : "remoteSourceControl",
"location" : "https://github.com/twostraws/CodeScanner.git",
"state" : {
"revision" : "5e886430238944c7200fc9e10dbf2d9550dba865",
"version" : "2.5.2"
}
},
{ {
"identity" : "grdb.swift", "identity" : "grdb.swift",
"kind" : "remoteSourceControl", "kind" : "remoteSourceControl",