diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a8e79b32..5f4f2f26 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -479,12 +479,15 @@ jobs: mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles echo $IOS_REMOTE_PROFILE_DATA | base64 --decode -o ~/Library/MobileDevice/Provisioning\ Profiles/$IOS_REMOTE_PROFILE_UUID.provisionprofile echo $IOS_SE_PROFILE_DATA | base64 --decode -o ~/Library/MobileDevice/Provisioning\ Profiles/$IOS_SE_PROFILE_UUID.provisionprofile + echo $IOS_SE_HELPER_PROFILE_DATA | base64 --decode -o ~/Library/MobileDevice/Provisioning\ Profiles/$IOS_SE_HELPER_PROFILE_UUID.provisionprofile echo $LAUNCHER_PROFILE_DATA | base64 --decode -o ~/Library/MobileDevice/Provisioning\ Profiles/$LAUNCHER_PROFILE_UUID.provisionprofile env: IOS_REMOTE_PROFILE_DATA: ${{ vars.IOS_REMOTE_PROFILE_DATA }} IOS_REMOTE_PROFILE_UUID: ${{ vars.IOS_REMOTE_PROFILE_UUID }} IOS_SE_PROFILE_DATA: ${{ vars.IOS_SE_PROFILE_DATA }} IOS_SE_PROFILE_UUID: ${{ vars.IOS_SE_PROFILE_UUID }} + IOS_SE_HELPER_PROFILE_DATA: ${{ vars.IOS_SE_HELPER_PROFILE_DATA }} + IOS_SE_HELPER_PROFILE_UUID: ${{ vars.IOS_SE_HELPER_PROFILE_UUID }} - name: Download Artifact uses: actions/download-artifact@v4 with: @@ -492,10 +495,11 @@ jobs: - name: Package for App Store run: | tar -xf UTM.xcarchive.tgz - ./scripts/package.sh ${{ matrix.configuration.mode }} UTM.xcarchive . "$SIGNING_TEAM_ID" "$PROFILE_UUID" app-store + ./scripts/package.sh ${{ matrix.configuration.mode }} UTM.xcarchive . "$SIGNING_TEAM_ID" "$PROFILE_UUID" app-store "$HELPER_PROFILE_UUID" env: SIGNING_TEAM_ID: ${{ vars.SIGNING_TEAM_ID }} PROFILE_UUID: ${{ matrix.configuration.scheme == 'iOS-Remote' && vars.IOS_REMOTE_PROFILE_UUID || vars.IOS_SE_PROFILE_UUID }} + HELPER_PROFILE_UUID: ${{ matrix.configuration.scheme == 'iOS-SE' && vars.IOS_SE_HELPER_PROFILE_UUID || '' }} - name: Upload Artifact uses: actions/upload-artifact@v4 with: diff --git a/CodeSigning.xcconfig.sample b/CodeSigning.xcconfig.sample index f1c575c4..0b65ba9a 100644 --- a/CodeSigning.xcconfig.sample +++ b/CodeSigning.xcconfig.sample @@ -4,8 +4,10 @@ DEVELOPMENT_TEAM = XYZ0123456 // Prefix of unique bundle IDs registered to you in Apple Developer Portal. // You need to register: // - com.myuniquename.UTM -// - com.myuniquename.QEMUHelper -// - com.myuniquename.QEMULauncher +// - com.myuniquename.UTM.iOSHelper (iOS) +// - com.myuniquename.UTM-SE.iOSHelper (iOS) +// - com.myuniquename.QEMUHelper (macOS) +// - com.myuniquename.QEMULauncher (macOS) PRODUCT_BUNDLE_PREFIX = com.myuniquename // Set to YES if you have a valid paid Apple Developer account @@ -55,7 +57,10 @@ PROVISIONING_PROFILE_SPECIFIER_LAUNCHER = 00000000-1111-2222-3333-555555555555 // Otherwise, change it to 'Manual' and fill in the profile specifiers below CODE_SIGN_STYLE_IOS = Automatic -// If using manual iOS signing, fill the profile specifiers for each app +// If using manual iOS signing, fill the profile specifiers for each app and +// for the helper extension embedded in UTM and UTM SE PROVISIONING_PROFILE_SPECIFIER_IOS = PROVISIONING_PROFILE_SPECIFIER_SE = PROVISIONING_PROFILE_SPECIFIER_REMOTE = +PROVISIONING_PROFILE_SPECIFIER_IOS_HELPER = +PROVISIONING_PROFILE_SPECIFIER_SE_HELPER = diff --git a/Configuration/UTMConfigurationDrive.swift b/Configuration/UTMConfigurationDrive.swift index 3cc1988d..098bf6a6 100644 --- a/Configuration/UTMConfigurationDrive.swift +++ b/Configuration/UTMConfigurationDrive.swift @@ -66,10 +66,11 @@ extension UTMConfigurationDrive { } let fileManager = FileManager.default if let imageURL = imageURL { - #if os(macOS) - let newURL = try await UTMQemuConfiguration.copyItemIfChanged(from: imageURL, to: dataURL, customCopy: isRawImage ? nil : convertQcow2Image) - #else + #if WITH_REMOTE let newURL = try await UTMQemuConfiguration.copyItemIfChanged(from: imageURL, to: dataURL) + #else + let customCopy = isRawImage || !UTMQemuImage.isSupported ? nil : convertQcow2Image + let newURL = try await UTMQemuConfiguration.copyItemIfChanged(from: imageURL, to: dataURL, customCopy: customCopy) #endif self.imageName = newURL.lastPathComponent self.imageURL = newURL @@ -136,7 +137,9 @@ extension UTMConfigurationDrive { try UTMAppleDiskImage.createASIF(at: newURL, sizeMib: sizeMib) }.value } + #endif + #if !WITH_REMOTE private func convertQcow2Image(at sourceURL: URL, to destFolderURL: URL) async throws -> URL { let destQcow2 = UTMData.newImage(from: sourceURL, to: destFolderURL, diff --git a/Documentation/Architecture.md b/Documentation/Architecture.md index d335e0c1..c7561b79 100644 --- a/Documentation/Architecture.md +++ b/Documentation/Architecture.md @@ -47,6 +47,10 @@ On macOS, spawning new processes is permitted but due to App Sandbox security re The XPC helper spawns `QEMULauncher` with an inherited sandbox. This means it can access files that are accessible to the helper XPC. The launcher process is what runs QEMU. When a new file is opened (for example a new disk image is mounted), the main application will pass a bookmark to the helper XPC where it will call `-startAccessingSecurityScopedResource` which also applies to the child process (`QEMULauncher`). This way, QEMU does not have to have any knowledge of the App Sandbox. +#### iOS Helper + +On iOS 26 and later, tools such as `qemu-img` run out of process in `iOSHelper`, an ExtensionKit extension embedded in the app (`iOSHelper-SE` for UTM SE, as an extension's bundle identifier must start with its host's). It implements the same `QEMUHelperProtocol` as `QEMUHelper`, but loads the tool into its own process like the in-thread QEMU does, so each helper process runs one tool and the app launches a new one for the next. `UTMHelperProcess` launches the helper through `AppExtensionProcess` and hands its connection to `UTMProcess`, which otherwise works as on macOS: the helper has no access to the app's container, so files are passed as bookmarks, whose implicit security scope lets the process that resolves them access the file. QEMU itself still runs in the app process on iOS. + ### UTMConfiguration VM configuration is stored in a PLIST format. This PLIST maps to either a `UTMQemuConfiguration` or `UTMAppleConfiguration` structure which stores the underlying configuration data in a `Codable` interface for easy serialization. diff --git a/Documentation/Release.md b/Documentation/Release.md index 32aab752..f321abaf 100644 --- a/Documentation/Release.md +++ b/Documentation/Release.md @@ -160,6 +160,8 @@ Below is a summary of all the variables and secrets used by GitHub Actions in th |`IOS_REMOTE_PROFILE_UUID` |UUID of provisioning profile above | |`IOS_SE_PROFILE_DATA` |Base64 encoded provisioning profile of iOS SE for App Store submission | |`IOS_SE_PROFILE_UUID` |UUID of provisioning profile above | +|`IOS_SE_HELPER_PROFILE_DATA` |Base64 encoded provisioning profile of the iOS SE helper extension for App Store | +|`IOS_SE_HELPER_PROFILE_UUID` |UUID of provisioning profile above | |`IS_SELF_HOSTED_RUNNER` |Set to `true` to use a self hosted macOS runner set up by the owner | ### Signing for release @@ -172,7 +174,7 @@ The following certificates (and associated private keys) must be exported from K Give a password when prompted and save it to the repository secret `SIGNING_CERTIFICATE_PASSWORD`. Then, in Terminal, convert the PKCS#12 file to Base64 and copy it: `cat Certificates.p12 | base64 | pbcopy` and paste it to `SIGNING_CERTIFICATE_P12_DATA`. -Next you need to get each provisioning profile {3 profiles for macOS} X {1 for Developer ID, 1 for Mac App Store}. Save each UUID of the profile as `*_PROFILE_UUID` and the Base64 encoded data from `cat name.provisionprofile | base64 | pbcopy` as `*_PROFILE_DATA`. +Next you need to get each provisioning profile {3 profiles for macOS} X {1 for Developer ID, 1 for Mac App Store} plus the App Store profiles for iOS: UTM SE, its helper extension (`com.utmapp.UTM-SE.iOSHelper`) and UTM Remote. Save each UUID of the profile as `*_PROFILE_UUID` and the Base64 encoded data from `cat name.provisionprofile | base64 | pbcopy` as `*_PROFILE_DATA`. ### AltStore Repository diff --git a/Documentation/iOSDevelopment.md b/Documentation/iOSDevelopment.md index 5bde558e..044ce530 100644 --- a/Documentation/iOSDevelopment.md +++ b/Documentation/iOSDevelopment.md @@ -64,10 +64,10 @@ Artifacts built with `build_utm.sh` (includes GitHub Actions artifacts) must be For a user friendly option, you can use [iOS App Signer][3] to re-sign the `.xcarchive`. Advanced users can use the package.sh script: ``` -./scripts/package.sh signedipa /path/to/UTM.xcarchive /path/to/output TEAM_ID PROFILE_UUID +./scripts/package.sh ipa-signed /path/to/UTM.xcarchive /path/to/output TEAM_ID PROFILE_UUID development HELPER_PROFILE_UUID ``` -This builds `UTM.ipa` in `/path/to/output` which can be installed by Xcode, iTunes, or AirDrop. Note that you need a "Development" signing certificate and NOT a "Distribution" certificate. This is because UTM requires a provisioning profile with the `get-task-allow` entitlement which Apple only grants for Development signing. +This builds `UTM.ipa` in `/path/to/output` which can be installed by Xcode, iTunes, or AirDrop. Note that you need a "Development" signing certificate and NOT a "Distribution" certificate. This is because UTM requires a provisioning profile with the `get-task-allow` entitlement which Apple only grants for Development signing. `HELPER_PROFILE_UUID` is a second profile for the helper extension embedded in the app (`com.utmapp.UTM.iOSHelper`, or `com.utmapp.UTM-SE.iOSHelper` for `ipa-se-signed`), which runs QEMU tools such as `qemu-img` out of process on iOS 26 and later. #### Unsigned IPA @@ -95,6 +95,10 @@ If you have a free Apple Developer account, you need to generate a new signing c Set `DEVELOPER_ACCOUNT_PAID = YES` if you used a paid Apple Developer account in order to automatically request the increased memory limit entitlement from Apple. +### Helper extension + +On iOS 26 and later, tools such as `qemu-img` (used for disk snapshots, resizing and conversion) run in an ExtensionKit extension embedded in the app. When Address Sanitizer is enabled in the scheme's diagnostics, the extension fails to start on a device and these operations report that the helper quit unexpectedly; turn the sanitizer off to test them. + ### Tethered Launch For JIT to work on the latest version of iOS, it must be launched through the debugger. You can do it from Xcode (and detach the debugger after launching) or you can follow [these instructions](TetheredLaunch.md) for an easier way. diff --git a/Platform/Shared/VMConfigDriveDetailsView.swift b/Platform/Shared/VMConfigDriveDetailsView.swift index a926e6e8..4f935a5f 100644 --- a/Platform/Shared/VMConfigDriveDetailsView.swift +++ b/Platform/Shared/VMConfigDriveDetailsView.swift @@ -23,13 +23,11 @@ struct VMConfigDriveDetailsView: View { private enum ConfirmItem: Identifiable { case reclaim(URL) case compress(URL) - case resize(URL) var id: Int { switch self { case .reclaim(_): return 1 case .compress(_): return 2 - case .resize(_): return 3 } } } @@ -41,7 +39,6 @@ struct VMConfigDriveDetailsView: View { @State private var confirmAlert: ConfirmItem? @State private var isResizePopoverShown: Bool = false - @State private var proposedSizeMib: Int = 0 var body: some View { Form { @@ -103,50 +100,58 @@ struct VMConfigDriveDetailsView: View { DefaultTextField("Size", text: .constant(ByteCountFormatter.string(fromByteCount: Int64(config.sizeMib) * bytesInMib, countStyle: .binary))).disabled(true) } - #if os(macOS) - HStack { - if let imageUrl = config.imageURL, FileManager.default.fileExists(atPath: imageUrl.path) { - Button { - confirmAlert = .reclaim(imageUrl) - } label: { - Label("Reclaim Space", systemImage: "arrow.3.trianglepath") - }.help("Reclaim disk space by re-converting the disk image.") - - Button { - confirmAlert = .compress(imageUrl) - } label: { - Label("Compress", systemImage: "arrowtriangle.right.and.line.vertical.and.arrowtriangle.left") - }.help("Compress by re-converting the disk image and compressing the data.") - - Button { - isResizePopoverShown.toggle() - } label: { - Label("Resize…", systemImage: "arrowtriangle.left.and.line.vertical.and.arrowtriangle.right") - }.help("Increase the size of the disk image.") - .popover(isPresented: $isResizePopoverShown) { - ResizePopoverView(imageURL: imageUrl, proposedSizeMib: $proposedSizeMib) { - confirmAlert = .resize(imageUrl) - }.padding() - .frame(minHeight: 100) - } - } - }.alert(item: $confirmAlert) { item in - switch item { - case .reclaim(let imageURL): - return Alert(title: Text("Would you like to re-convert this disk image to reclaim unused space? Note this will require enough temporary space to perform the conversion. You are strongly encouraged to back-up this VM before proceeding."), primaryButton: .destructive(Text("Reclaim")) { reclaimSpace(for: imageURL, withCompression: false) }, secondaryButton: .cancel()) - case .compress(let imageURL): - return Alert(title: Text("Would you like to re-convert this disk image to reclaim unused space and apply compression? Note this will require enough temporary space to perform the conversion. Compression only applies to existing data and new data will still be written uncompressed. You are strongly encouraged to back-up this VM before proceeding."), primaryButton: .destructive(Text("Reclaim")) { reclaimSpace(for: imageURL, withCompression: true) }, secondaryButton: .cancel()) - case .resize(let imageURL): - return Alert(title: Text("Resizing is experimental and could result in data loss. You are strongly encouraged to back-up this VM before proceeding. Would you like to resize to \(proposedSizeMib / mibInGib) GiB?"), primaryButton: .destructive(Text("Resize")) { - resizeDrive(for: imageURL, sizeInMib: proposedSizeMib) - }, secondaryButton: .cancel()) + #if !WITH_REMOTE + if UTMQemuImage.isSupported, let imageUrl = config.imageURL, FileManager.default.fileExists(atPath: imageUrl.path) { + #if os(macOS) + HStack { + imageTools(for: imageUrl) } + #else + imageTools(for: imageUrl) + #endif } #endif } + #if !WITH_REMOTE + .alert(item: $confirmAlert) { item in + switch item { + case .reclaim(let imageURL): + return Alert(title: Text("Would you like to re-convert this disk image to reclaim unused space? Note this will require enough temporary space to perform the conversion. You are strongly encouraged to back-up this VM before proceeding."), primaryButton: .destructive(Text("Reclaim")) { reclaimSpace(for: imageURL, withCompression: false) }, secondaryButton: .cancel()) + case .compress(let imageURL): + return Alert(title: Text("Would you like to re-convert this disk image to reclaim unused space and apply compression? Note this will require enough temporary space to perform the conversion. Compression only applies to existing data and new data will still be written uncompressed. You are strongly encouraged to back-up this VM before proceeding."), primaryButton: .destructive(Text("Reclaim")) { reclaimSpace(for: imageURL, withCompression: true) }, secondaryButton: .cancel()) + } + } + #endif } - #if os(macOS) + #if !WITH_REMOTE + @ViewBuilder + private func imageTools(for imageUrl: URL) -> some View { + Button { + confirmAlert = .reclaim(imageUrl) + } label: { + Label("Reclaim Space", systemImage: "arrow.3.trianglepath") + }.help("Reclaim disk space by re-converting the disk image.") + + Button { + confirmAlert = .compress(imageUrl) + } label: { + Label("Compress", systemImage: "arrowtriangle.right.and.line.vertical.and.arrowtriangle.left") + }.help("Compress by re-converting the disk image and compressing the data.") + + Button { + isResizePopoverShown.toggle() + } label: { + Label("Resize…", systemImage: "arrowtriangle.left.and.line.vertical.and.arrowtriangle.right") + }.help("Increase the size of the disk image.") + .popover(isPresented: $isResizePopoverShown) { + ResizePopoverView(imageURL: imageUrl) { sizeInMib in + resizeDrive(for: imageUrl, sizeInMib: sizeInMib) + }.padding() + .frame(minHeight: 100) + } + } + private func reclaimSpace(for driveUrl: URL, withCompression isCompressed: Bool) { data.busyWorkAsync { try await data.reclaimSpace(for: driveUrl, withCompression: isCompressed) @@ -161,14 +166,17 @@ struct VMConfigDriveDetailsView: View { #endif } -#if os(macOS) +#if !WITH_REMOTE +/// Asks for the new size and confirms it here, as an alert on the form under this view +/// is dropped on iOS when it is set while the sheet closes. private struct ResizePopoverView: View { let imageURL: URL - @Binding var proposedSizeMib: Int - let onConfirm: () -> Void + let onConfirm: (Int) -> Void @EnvironmentObject private var data: UTMData @State private var currentSize: Int64? + @State private var proposedSizeMib: Int = 0 + @State private var isConfirming: Bool = false @Environment(\.presentationMode) private var presentationMode: Binding @@ -192,14 +200,20 @@ private struct ResizePopoverView: View { SizeTextField($proposedSizeMib, minSizeMib: minSizeMib) Button("Resize") { if proposedSizeMib > minSizeMib { - onConfirm() + isConfirming = true + } else { + presentationMode.wrappedValue.dismiss() } - presentationMode.wrappedValue.dismiss() } } } else { ProgressView("Calculating current size...") } + }.alert(isPresented: $isConfirming) { + Alert(title: Text("Resizing is experimental and could result in data loss. You are strongly encouraged to back-up this VM before proceeding. Would you like to resize to \(proposedSizeMib / mibInGib) GiB?"), primaryButton: .destructive(Text("Resize")) { + onConfirm(proposedSizeMib) + presentationMode.wrappedValue.dismiss() + }, secondaryButton: .cancel()) }.onAppear { Task { @MainActor in currentSize = await data.qcow2DriveSize(for: imageURL) diff --git a/Platform/UTMData.swift b/Platform/UTMData.swift index d66747fb..96dd4001 100644 --- a/Platform/UTMData.swift +++ b/Platform/UTMData.swift @@ -904,7 +904,7 @@ enum AlertItem: Identifiable { // MARK: - Reclaim space - #if os(macOS) + #if !WITH_REMOTE /// Reclaim empty space in a file by (re)-converting it to QCOW2 /// /// This will overwrite driveUrl with the converted file on success! @@ -943,7 +943,9 @@ enum AlertItem: Identifiable { let bytesinMib = 1048576 try await UTMQemuImage.resize(image: driveUrl, size: UInt64(sizeInMib * bytesinMib)) } + #endif + #if os(macOS) @available(macOS 14, *) func appleDriveInfo(for driveUrl: URL) -> (format: String?, size: Int64?) { guard let info = try? UTMAppleDiskImage.info(for: driveUrl) else { diff --git a/QEMUHelper/QEMUHelperProtocol.h b/QEMUHelper/QEMUHelperProtocol.h index 87e51b31..55887bda 100644 --- a/QEMUHelper/QEMUHelperProtocol.h +++ b/QEMUHelper/QEMUHelperProtocol.h @@ -28,7 +28,7 @@ NS_ASSUME_NONNULL_BEGIN - (void)accessDataWithBookmark:(NSData *)bookmark securityScoped:(BOOL)securityScoped completion:(void(^)(BOOL, NSData * _Nullable, NSString * _Nullable))completion; - (void)stopAccessingPath:(nullable NSString *)path; -- (void)startQemu:(NSString *)binName standardOutput:(NSFileHandle *)standardOutput standardError:(NSFileHandle *)standardError libraryBookmark:(NSData *)libBookmark argv:(NSArray *)argv completion:(void(^)(BOOL,NSString *))completion; +- (void)startQemu:(NSString *)binName standardOutput:(NSFileHandle *)standardOutput standardError:(NSFileHandle *)standardError libraryBookmark:(NSData *)libBookmark argv:(NSArray *)argv completion:(void(^)(BOOL, NSString * _Nullable))completion; - (void)terminate; /// Helper holds on to the token to keep this XPC service active diff --git a/Services/Swift-Bridging-Header.h b/Services/Swift-Bridging-Header.h index 9c426072..a78f2f77 100644 --- a/Services/Swift-Bridging-Header.h +++ b/Services/Swift-Bridging-Header.h @@ -31,6 +31,7 @@ #if !defined(WITH_REMOTE) #include "UTMProcess.h" #include "UTMQemuSystem.h" +#include "QEMUHelperProtocol.h" #include "UTMJailbreak.h" #else #include "UTMQemuSystemBackends.h" diff --git a/Services/UTMHelperProcess.swift b/Services/UTMHelperProcess.swift new file mode 100644 index 00000000..3c62da7a --- /dev/null +++ b/Services/UTMHelperProcess.swift @@ -0,0 +1,197 @@ +// +// Copyright © 2026 Turing Software, LLC. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#if os(iOS) || os(visionOS) +import Foundation +import ExtensionFoundation + +@available(iOS 26, visionOS 26, *) +extension AppExtensionPoint { + /// Hosts the helper that runs QEMU tools such as qemu-img out of process + @Definition + static var qemuHelper: AppExtensionPoint { + Name("qemu-helper") + UserInterface(false) + } +} + +/// A helper extension process that runs a QEMU tool once for the app. +/// +/// Helpers run one at a time: the system hands out a helper process that is still going away +/// to a launch that comes too soon after it, and each process can run QEMU only once. +final class UTMHelperProcess { + /// The helper extension exists in this build and can run on this system + static var isSupported: Bool { + if #available(iOS 26, visionOS 26, *) { + return true + } else { + return false + } + } + + private static let gate = UTMHelperProcessGate() + private static let exitTimeout: TimeInterval = 2 + + /// Connection to the helper, to be configured and resumed by the owner + let connection: NSXPCConnection + /// The helper went away before it was asked to, such as when it failed to start + private(set) var isLost = false + /// Set when the system has seen the helper process go away + private let exit: UTMHelperProcessSignal + private let invalidation: () -> Void + private var isInvalidated = false + + private init(connection: NSXPCConnection, exit: UTMHelperProcessSignal, invalidation: @escaping () -> Void) { + self.connection = connection + self.exit = exit + self.invalidation = invalidation + } + + deinit { + invalidate() + } + + /// Launch a helper and connect to it, waiting for any helper that is still running + static func launch() async throws -> UTMHelperProcess { + guard #available(iOS 26, visionOS 26, *) else { + throw UTMHelperProcessError.unsupported + } + await gate.acquire() + do { + let monitor = try await AppExtensionPoint.Monitor(appExtensionPoint: .qemuHelper) + guard let identity = monitor.identities.first else { + throw UTMHelperProcessError.notFound + } + let exit = UTMHelperProcessSignal() + let configuration = AppExtensionProcess.Configuration(appExtensionIdentity: identity) { + exit.signal() + } + let process = try await AppExtensionProcess(configuration: configuration) + let connection = try process.makeXPCConnection() + let helper = UTMHelperProcess(connection: connection, exit: exit) { + process.invalidate() + } + connection.interruptionHandler = { [weak helper] in + helper?.isLost = true + } + return helper + } catch { + await gate.release() + throw error + } + } + + /// Ask the helper to exit and wait for the system to see it go, then let the next helper launch + func terminate() async { + guard !isInvalidated else { + return + } + isInvalidated = true + // this exit is the one asked for + connection.interruptionHandler = nil + (connection.remoteObjectProxy as? QEMUHelperProtocol)?.terminate() + await exit.wait(timeout: Self.exitTimeout) + connection.invalidate() + invalidation() + await Self.gate.release() + } + + /// End the helper without waiting for it + func invalidate() { + guard !isInvalidated else { + return + } + isInvalidated = true + connection.invalidate() + invalidation() + Task { + await Self.gate.release() + } + } +} + +/// Lets one helper run at a time +private actor UTMHelperProcessGate { + private var isHeld = false + private var waiters = [CheckedContinuation]() + + func acquire() async { + guard isHeld else { + isHeld = true + return + } + await withCheckedContinuation { continuation in + waiters.append(continuation) + } + } + + func release() { + if waiters.isEmpty { + isHeld = false + } else { + waiters.removeFirst().resume() + } + } +} + +/// A one-time event that can be waited for with a timeout +private final class UTMHelperProcessSignal: @unchecked Sendable { + private let lock = NSLock() + private var isSignaled = false + private var continuation: CheckedContinuation? + + func signal() { + lock.lock() + isSignaled = true + let continuation = self.continuation + self.continuation = nil + lock.unlock() + continuation?.resume() + } + + func wait(timeout: TimeInterval) async { + await withCheckedContinuation { continuation in + lock.lock() + guard !isSignaled else { + lock.unlock() + continuation.resume() + return + } + self.continuation = continuation + lock.unlock() + DispatchQueue.global().asyncAfter(deadline: .now() + timeout) { + self.signal() + } + } + } +} + +enum UTMHelperProcessError: Error { + case unsupported + case notFound + case lost +} + +extension UTMHelperProcessError: LocalizedError { + var errorDescription: String? { + switch self { + case .unsupported: return NSLocalizedString("This operation requires a newer version of iOS.", comment: "UTMHelperProcess") + case .notFound: return NSLocalizedString("The helper for this operation is not installed.", comment: "UTMHelperProcess") + case .lost: return NSLocalizedString("The helper for this operation quit unexpectedly.", comment: "UTMHelperProcess") + } + } +} +#endif diff --git a/Services/UTMProcess.h b/Services/UTMProcess.h index 70690c48..e3c75d47 100644 --- a/Services/UTMProcess.h +++ b/Services/UTMProcess.h @@ -38,6 +38,8 @@ typedef int (*UTMProcessThreadEntry)(UTMProcess *self, int argc, const char * _N - (instancetype)init; - (instancetype)initWithArguments:(NSArray *)arguments NS_DESIGNATED_INITIALIZER; +/// Runs the process in the helper behind `connection` instead of in a thread of this process +- (instancetype)initWithArguments:(NSArray *)arguments connection:(nullable NSXPCConnection *)connection NS_DESIGNATED_INITIALIZER; - (void)pushArgv:(nullable NSString *)arg; - (void)clearArgv; - (void)startProcess:(nonnull NSString *)name completion:(nonnull void (^)(NSError * _Nullable))completion; diff --git a/Services/UTMProcess.m b/Services/UTMProcess.m index 5f450b44..37149ab4 100644 --- a/Services/UTMProcess.m +++ b/Services/UTMProcess.m @@ -35,9 +35,7 @@ extern NSString *const kUTMErrorDomain; @implementation UTMProcess { NSMutableArray *_argv; NSMutableArray *_urls; -#if TARGET_OS_OSX NSXPCConnection *_connection; -#endif } static void *startProcess(void *args) { @@ -88,17 +86,17 @@ static int defaultEntry(UTMProcess *self, int argc, const char *argv[], const ch - (NSURL *)libraryURL { NSURL *bundleURL = [[NSBundle mainBundle] bundleURL]; +#if TARGET_OS_IPHONE + NSURL *contentsURL = bundleURL; +#else NSURL *contentsURL = [bundleURL URLByAppendingPathComponent:@"Contents" isDirectory:YES]; +#endif NSURL *frameworksURL = [contentsURL URLByAppendingPathComponent:@"Frameworks" isDirectory:YES]; return frameworksURL; } - (BOOL)hasRemoteProcess { -#if TARGET_OS_OSX return _connection != nil; -#else - return NO; -#endif } - (NSString *)arguments { @@ -121,40 +119,64 @@ static int defaultEntry(UTMProcess *self, int argc, const char *argv[], const ch - (instancetype)initWithArguments:(NSArray *)arguments { if (self = [super init]) { - _argv = [arguments mutableCopy]; - _urls = [NSMutableArray array]; - if (![self setupXpc]) { + if (![self setupWithArguments:arguments connection:nil]) { return nil; } - dispatch_queue_attr_t attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_UTILITY, QOS_MIN_RELATIVE_PRIORITY); - self.completionQueue = dispatch_queue_create("QEMU Completion Queue", attr); - self.entry = defaultEntry; - self.done = dispatch_semaphore_create(0); } return self; } +- (instancetype)initWithArguments:(NSArray *)arguments connection:(nullable NSXPCConnection *)connection { + if (self = [super init]) { + if (![self setupWithArguments:arguments connection:connection]) { + return nil; + } + } + return self; +} + +- (BOOL)setupWithArguments:(NSArray *)arguments connection:(nullable NSXPCConnection *)connection { + _argv = [arguments mutableCopy]; + _urls = [NSMutableArray array]; + if (![self setupXpcWithConnection:connection]) { + return NO; + } + dispatch_queue_attr_t attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_UTILITY, QOS_MIN_RELATIVE_PRIORITY); + self.completionQueue = dispatch_queue_create("QEMU Completion Queue", attr); + self.entry = defaultEntry; + self.done = dispatch_semaphore_create(0); + return YES; +} + - (void)dealloc { [self stopProcess]; } #pragma mark - Methods -- (BOOL)setupXpc { -#if TARGET_OS_IPHONE - return YES; -#else // only supported on macOS - NSString *helperIdentifier = NSBundle.mainBundle.infoDictionary[@"HelperIdentifier"]; - if (!helperIdentifier) { - helperIdentifier = @"com.utmapp.QEMUHelper"; +- (BOOL)setupXpcWithConnection:(nullable NSXPCConnection *)connection { +#if TARGET_OS_OSX + if (!connection) { + NSString *helperIdentifier = NSBundle.mainBundle.infoDictionary[@"HelperIdentifier"]; + if (!helperIdentifier) { + helperIdentifier = @"com.utmapp.QEMUHelper"; + } + connection = [[NSXPCConnection alloc] initWithServiceName:helperIdentifier]; + if (!connection) { + return NO; + } } - _connection = [[NSXPCConnection alloc] initWithServiceName:helperIdentifier]; +#else // runs in a thread unless a helper is provided + if (!connection) { + return YES; + } +#endif + _connection = connection; _connection.remoteObjectInterface = [NSXPCInterface interfaceWithProtocol:@protocol(QEMUHelperProtocol)]; _connection.exportedInterface = [NSXPCInterface interfaceWithProtocol:@protocol(QEMUHelperDelegate)]; _connection.exportedObject = self; [_connection resume]; - return _connection != nil; -#endif + return YES; } - (void)pushArgv:(nullable NSString *)arg { @@ -225,7 +247,6 @@ static int defaultEntry(UTMProcess *self, int argc, const char *argv[], const ch completion(nil); } -#if TARGET_OS_OSX - (void)startQemuRemote:(nonnull NSString *)name completion:(nonnull void (^)(NSError * _Nullable))completion { NSError *error; NSData *libBookmark = [self.libraryURL bookmarkDataWithOptions:0 @@ -260,7 +281,6 @@ static int defaultEntry(UTMProcess *self, int argc, const char *argv[], const ch } }]; } -#endif - (void)startProcess:(nonnull NSString *)name completion:(nonnull void (^)(NSError * _Nullable))completion { #if TARGET_OS_IPHONE @@ -270,24 +290,18 @@ static int defaultEntry(UTMProcess *self, int argc, const char *argv[], const ch #endif NSString *dylib = [NSString stringWithFormat:@"%@.framework/%@%@", name, base, name]; self.processName = name; -#if TARGET_OS_OSX if (_connection) { [self startQemuRemote:dylib completion:completion]; } else { -#endif [self startDylibThread:dylib completion:completion]; -#if TARGET_OS_OSX } -#endif } - (void)stopProcess { -#if TARGET_OS_OSX if (_connection) { [[_connection remoteObjectProxy] terminate]; [_connection invalidate]; } -#endif for (NSURL *url in _urls) { [url stopAccessingSecurityScopedResource]; } @@ -334,15 +348,14 @@ static int defaultEntry(UTMProcess *self, int argc, const char *argv[], const ch } - (void)accessDataWithBookmark:(NSData *)bookmark securityScoped:(BOOL)securityScoped completion:(void(^)(BOOL, NSData * _Nullable, NSString * _Nullable))completion { -#if TARGET_OS_OSX if (_connection) { - [[_connection remoteObjectProxy] accessDataWithBookmark:bookmark securityScoped:securityScoped completion:completion]; + [[_connection remoteObjectProxyWithErrorHandler:^(NSError *error) { + UTMLog(@"Failed to pass bookmark to the helper: %@", error); + completion(NO, nil, nil); + }] accessDataWithBookmark:bookmark securityScoped:securityScoped completion:completion]; } else { -#endif [self accessDataWithBookmarkThread:bookmark securityScoped:securityScoped completion:completion]; -#if TARGET_OS_OSX } -#endif } - (void)stopAccessingPathThread:(nullable NSString *)path { @@ -360,15 +373,11 @@ static int defaultEntry(UTMProcess *self, int argc, const char *argv[], const ch } - (void)stopAccessingPath:(nullable NSString *)path { -#if TARGET_OS_OSX if (_connection) { [[_connection remoteObjectProxy] stopAccessingPath:path]; } else { -#endif [self stopAccessingPathThread:path]; -#if TARGET_OS_OSX } -#endif } - (NSError *)errorWithMessage:(nullable NSString *)message { diff --git a/Services/UTMQemuImage.swift b/Services/UTMQemuImage.swift index 14e94219..253ed647 100644 --- a/Services/UTMQemuImage.swift +++ b/Services/UTMQemuImage.swift @@ -15,76 +15,183 @@ // import Foundation -import QEMUKitInternal @objc class UTMQemuImage: UTMProcess { typealias ProgressCallback = (Float) -> Void - private var logOutput: String = "" + private let output = UTMQemuImageOutput() + private let errorOutput = UTMQemuImageOutput() private var processExitContinuation: CheckedContinuation? - private var onProgress: ProgressCallback? + #if os(iOS) || os(visionOS) + private let helper: UTMHelperProcess + #endif + /// qemu-img is available: on iOS it runs in a helper extension + static var isSupported: Bool { + #if os(iOS) || os(visionOS) + return UTMHelperProcess.isSupported + #else + return true + #endif + } + + #if os(iOS) || os(visionOS) + private init(helper: UTMHelperProcess) { + self.helper = helper + super.init(arguments: [], connection: helper.connection) + } + + deinit { + helper.invalidate() + } + #else private init() { super.init(arguments: []) } - + #endif + + private static func makeProcess() async throws -> UTMQemuImage { + #if os(iOS) || os(visionOS) + return UTMQemuImage(helper: try await UTMHelperProcess.launch()) + #else + return UTMQemuImage() + #endif + } + + /// Runs an operation with a fresh qemu-img, then releases it so that the next one can start + private static func withProcess(_ body: (UTMQemuImage) async throws -> T) async throws -> T { + let qemuImg = try await makeProcess() + let result: T + do { + result = try await body(qemuImg) + } catch { + await qemuImg.finish() + throw error + } + await qemuImg.finish() + return result + } + + private func finish() async { + #if os(iOS) || os(visionOS) + await helper.terminate() + #endif + stop() + } + override func processHasExited(_ exitCode: Int, message: String?) { - if let processExitContinuation = processExitContinuation { - self.processExitContinuation = nil - if exitCode != 0 { - if let message = message { - processExitContinuation.resume(throwing: UTMQemuImageError.qemuError(message)) - } else { - processExitContinuation.resume(throwing: UTMQemuImageError.unknown) - } + guard let processExitContinuation = processExitContinuation else { + return + } + self.processExitContinuation = nil + var error: Error? + if exitCode != 0 { + if let message = message { + error = UTMQemuImageError.qemuError(message) + } else { + error = UTMQemuImageError.unknown + } + } + Self.resumeOffXPCQueue { + if let error = error { + processExitContinuation.resume(throwing: error) } else { processExitContinuation.resume() } } } - - private func start() async throws { - try await withCheckedThrowingContinuation { continuation in - processExitContinuation = continuation - start("qemu-img") { error in - if let error = error { - self.processExitContinuation = nil - continuation.resume(throwing: error) + + /// A task resumed on the reply queue of the helper's connection would block on its next call + /// to the helper, as NSXPC makes calls from that queue synchronous. + private static func resumeOffXPCQueue(_ resume: @escaping () -> Void) { + DispatchQueue.global().async(execute: resume) + } + + /// Runs qemu-img with the arguments and waits for it to exit and for its output to end + private func start(onProgress: ProgressCallback? = nil) async throws { + standardOutput = output.pipe + standardError = errorOutput.pipe + if let onProgress = onProgress { + output.onLine = { line in + if let progress = Self.parseProgress(line) { + onProgress(progress) } } } + var exitError: Error? + do { + try await withCheckedThrowingContinuation { continuation in + processExitContinuation = continuation + start("qemu-img") { error in + // the write ends belong to the tool now, so the output ends when it exits + self.output.closeWriter() + self.errorOutput.closeWriter() + if let error = error { + self.processExitContinuation = nil + Self.resumeOffXPCQueue { + continuation.resume(throwing: error) + } + } + } + } + } catch { + exitError = error + } + await output.waitForEnd() + await errorOutput.waitForEnd() + if let exitError = exitError { + if case UTMQemuImageError.unknown = exitError, let message = errorOutput.lines.last(where: { !$0.isEmpty }) { + throw UTMQemuImageError.qemuError(message) + } + throw exitError + } } - + + /// Grants qemu-img access to a file and adds it to the arguments + private func pushArgv(accessing url: URL) async throws { + // a file that does not exist yet is reached through its directory + let accessURL = FileManager.default.fileExists(atPath: url.path) ? url : url.deletingLastPathComponent() + let bookmark = try accessURL.bookmarkData() + let success: Bool = await withCheckedContinuation { continuation in + accessData(withBookmark: bookmark, securityScoped: false) { success, _, _ in + Self.resumeOffXPCQueue { + continuation.resume(returning: success) + } + } + } + guard success else { + #if os(iOS) || os(visionOS) + if helper.isLost { + throw UTMHelperProcessError.lost + } + #endif + throw UTMQemuImageError.accessFailed(url) + } + pushArgv(url.path) + } + static func convert(from url: URL, toQcow2 dest: URL, withCompression compressed: Bool = false, onProgress: ProgressCallback? = nil) async throws { - let qemuImg = UTMQemuImage() - let srcBookmark = try url.bookmarkData() - let dstBookmark = try dest.deletingLastPathComponent().bookmarkData() - qemuImg.pushArgv("convert") - if onProgress != nil { - qemuImg.pushArgv("-p") + try await withProcess { qemuImg in + qemuImg.pushArgv("convert") + if onProgress != nil { + qemuImg.pushArgv("-p") + } + if compressed { + qemuImg.pushArgv("-c") + qemuImg.pushArgv("-o") + qemuImg.pushArgv("compression_type=zstd") + } + qemuImg.pushArgv("-O") + qemuImg.pushArgv("qcow2") + try await qemuImg.pushArgv(accessing: url) + try await qemuImg.pushArgv(accessing: dest) + try await qemuImg.start(onProgress: onProgress) } - if compressed { - qemuImg.pushArgv("-c") - qemuImg.pushArgv("-o") - qemuImg.pushArgv("compression_type=zstd") - } - qemuImg.pushArgv("-O") - qemuImg.pushArgv("qcow2") - qemuImg.accessData(withBookmark: srcBookmark) - qemuImg.pushArgv(url.path) - qemuImg.accessData(withBookmark: dstBookmark) - qemuImg.pushArgv(dest.path) - let logging = QEMULogging() - logging.delegate = qemuImg - qemuImg.standardOutput = logging.standardOutput - qemuImg.standardError = logging.standardError - qemuImg.onProgress = onProgress - try await qemuImg.start() } - + /* The info format looks like: - + $ qemu-img info foo.img --output=json { "virtual-size": 20971520, @@ -152,25 +259,18 @@ import QEMUKitInternal } static func info(image url: URL) async throws -> QemuImageInfo { - let qemuImg = UTMQemuImage() - let srcBookmark = try url.bookmarkData() - qemuImg.pushArgv("info") - qemuImg.pushArgv("--output=json") - qemuImg.accessData(withBookmark: srcBookmark) - qemuImg.pushArgv(url.path) - let logging = QEMULogging() - logging.delegate = qemuImg - qemuImg.standardOutput = logging.standardOutput - qemuImg.standardError = logging.standardError - try await qemuImg.start() + try await withProcess { qemuImg in + qemuImg.pushArgv("info") + qemuImg.pushArgv("--output=json") + try await qemuImg.pushArgv(accessing: url) + try await qemuImg.start() - let decoder = JSONDecoder() - decoder.keyDecodingStrategy = .convertFromSnakeCase + let decoder = JSONDecoder() + decoder.keyDecodingStrategy = .convertFromSnakeCase - let data = qemuImg.logOutput.data(using: .utf8) ?? Data() - let image_info: QemuImageInfo = try decoder.decode(QemuImageInfo.self, from: data) - - return image_info + let data = qemuImg.output.lines.joined(separator: "\n").data(using: .utf8) ?? Data() + return try decoder.decode(QemuImageInfo.self, from: data) + } } /// Internal snapshots stored in an image @@ -185,18 +285,13 @@ import QEMUKitInternal } private static func snapshot(_ operation: SnapshotOperation, name: String, image url: URL) async throws { - let qemuImg = UTMQemuImage() - let srcBookmark = try url.bookmarkData() - qemuImg.pushArgv("snapshot") - qemuImg.pushArgv(operation.rawValue) - qemuImg.pushArgv(name) - qemuImg.accessData(withBookmark: srcBookmark) - qemuImg.pushArgv(url.path) - let logging = QEMULogging() - logging.delegate = qemuImg - qemuImg.standardOutput = logging.standardOutput - qemuImg.standardError = logging.standardError - try await qemuImg.start() + try await withProcess { qemuImg in + qemuImg.pushArgv("snapshot") + qemuImg.pushArgv(operation.rawValue) + qemuImg.pushArgv(name) + try await qemuImg.pushArgv(accessing: url) + try await qemuImg.start() + } } /// Save the contents of the image as an internal snapshot without any VM state @@ -218,24 +313,20 @@ import QEMUKitInternal } static func resize(image url: URL, size : UInt64) async throws { - let qemuImg = UTMQemuImage() - let srcBookmark = try url.bookmarkData() - qemuImg.pushArgv("resize") - qemuImg.pushArgv("-f") - qemuImg.pushArgv("qcow2") - qemuImg.accessData(withBookmark: srcBookmark) - qemuImg.pushArgv(url.path) - qemuImg.pushArgv(String(size)) - let logging = QEMULogging() - logging.delegate = qemuImg - qemuImg.standardOutput = logging.standardOutput - qemuImg.standardError = logging.standardError - try await qemuImg.start() + try await withProcess { qemuImg in + qemuImg.pushArgv("resize") + qemuImg.pushArgv("-f") + qemuImg.pushArgv("qcow2") + try await qemuImg.pushArgv(accessing: url) + qemuImg.pushArgv(String(size)) + try await qemuImg.start() + } } } private enum UTMQemuImageError: Error { case qemuError(String) + case accessFailed(URL) case unknown } @@ -243,29 +334,104 @@ extension UTMQemuImageError: LocalizedError { var errorDescription: String? { switch self { case .qemuError(let message): return message + case .accessFailed(let url): return String.localizedStringWithFormat(NSLocalizedString("Cannot access '%@'.", comment: "UTMQemuImage"), url.lastPathComponent) case .unknown: return NSLocalizedString("An unknown QEMU error has occurred.", comment: "UTMQemuImage") } } } -// MARK: - Logging +// MARK: - Output -extension UTMQemuImage: QEMULoggingDelegate { - func logging(_ logging: QEMULogging, didRecieveOutputLine line: String) { - logOutput += line - if let onProgress = onProgress, line.contains("100%") { - if let progress = parseProgress(line) { - onProgress(progress) +/// Collects the lines a tool writes to a pipe until the tool closes it +private final class UTMQemuImageOutput: @unchecked Sendable { + /// Waited for at most this long after the tool exited + private static let endTimeout: TimeInterval = 5 + + let pipe = Pipe() + /// Called for each line as it arrives, such as the progress of a conversion + var onLine: ((String) -> Void)? + + private let lock = NSLock() + private var buffer = Data() + private var isEnded = false + private var endContinuation: CheckedContinuation? + private var _lines = [String]() + + /// Complete once the output has ended + var lines: [String] { + lock.lock() + defer { + lock.unlock() + } + return _lines + } + + init() { + pipe.fileHandleForReading.readabilityHandler = { [weak self] handle in + self?.read(from: handle) + } + } + + /// Give up the write end so the reader sees the end of the output when the tool exits + func closeWriter() { + try? pipe.fileHandleForWriting.close() + } + + func waitForEnd() async { + await withCheckedContinuation { continuation in + lock.lock() + guard !isEnded else { + lock.unlock() + continuation.resume() + return + } + endContinuation = continuation + lock.unlock() + DispatchQueue.global().asyncAfter(deadline: .now() + Self.endTimeout) { + self.end() } } } - - func logging(_ logging: QEMULogging, didRecieveErrorLine line: String) { + + private func read(from handle: FileHandle) { + let data = handle.availableData + guard !data.isEmpty else { + handle.readabilityHandler = nil + end() + return + } + lock.lock() + buffer.append(data) + var lines = [String]() + while let separator = buffer.firstIndex(where: { $0 == UInt8(ascii: "\n") || $0 == UInt8(ascii: "\r") }) { + lines.append(String(decoding: buffer[buffer.startIndex.. Float? { + private static func parseProgress(_ line: String) -> Float? { let pattern = "\\(([0-9]+\\.[0-9]+)/100\\%\\)" do { let regex = try NSRegularExpression(pattern: pattern) diff --git a/Services/UTMSnapshotService.swift b/Services/UTMSnapshotService.swift index fc5e1d14..8c33846f 100644 --- a/Services/UTMSnapshotService.swift +++ b/Services/UTMSnapshotService.swift @@ -40,14 +40,15 @@ enum UTMSnapshotService { /// Snapshots can be managed for this VM, whether it is running or not static func isSupported(for vm: any UTMVirtualMachine) -> Bool { - #if os(macOS) if vm is UTMQemuVirtualMachine { - return true - } else if #available(macOS 27, *), vm is UTMAppleVirtualMachine { + // qemu-img is needed for a VM that is not running + return UTMQemuImage.isSupported + } + #if os(macOS) + if #available(macOS 27, *), vm is UTMAppleVirtualMachine { return true } #endif - // qemu-img is needed for a VM that is not running and is only part of the macOS app return false } diff --git a/UTM.xcodeproj/project.pbxproj b/UTM.xcodeproj/project.pbxproj index 03151475..087ecff3 100644 --- a/UTM.xcodeproj/project.pbxproj +++ b/UTM.xcodeproj/project.pbxproj @@ -11,13 +11,28 @@ 03FA9C732B9BBDB000C53A5A /* UTMConfigurationHostNetwork.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03FA9C712B9BBDB000C53A5A /* UTMConfigurationHostNetwork.swift */; }; 03FA9C742B9BBDB000C53A5A /* UTMConfigurationHostNetwork.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03FA9C712B9BBDB000C53A5A /* UTMConfigurationHostNetwork.swift */; }; 03FA9C752B9BBDB000C53A5A /* UTMConfigurationHostNetwork.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03FA9C712B9BBDB000C53A5A /* UTMConfigurationHostNetwork.swift */; }; + 10488F379FF913D2E7770E60 /* VMSnapshotData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16001E3BC682D8470EFCF312 /* VMSnapshotData.swift */; }; + 12618B76026270B5C1C8CD29 /* UTMSnapshotBackend.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE6FCF9E7BD060AB81411ABC /* UTMSnapshotBackend.swift */; }; + 1C5910D866A20395225768B9 /* UTMAppleSnapshotBackend.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CBB52BFA0D55FDEC6497878 /* UTMAppleSnapshotBackend.swift */; }; + 29B2D623BB879D2653CA8C53 /* VMSnapshotData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16001E3BC682D8470EFCF312 /* VMSnapshotData.swift */; }; + 2A3EFF2D980175F4D531E0E3 /* VMKeyboardAccessoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB38A22C8D69DB2D2C64107F /* VMKeyboardAccessoryView.swift */; }; 2C33B3A92566C9B100A954A6 /* VMContextMenuModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C33B3A82566C9B100A954A6 /* VMContextMenuModifier.swift */; }; 2C33B3AA2566C9B100A954A6 /* VMContextMenuModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C33B3A82566C9B100A954A6 /* VMContextMenuModifier.swift */; }; 2C6D9E03256EE454003298E6 /* VMDisplayQemuTerminalWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C6D9E02256EE454003298E6 /* VMDisplayQemuTerminalWindowController.swift */; }; + 31D9CB9DD8370CF0CCCDE85C /* VMConfigQEMUCommandLineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03AEFABA6F4DF195E96AD2D5 /* VMConfigQEMUCommandLineView.swift */; }; + 393A92DAE84A47029BA5F829 /* UTMSnapshotService.swift in Sources */ = {isa = PBXBuildFile; fileRef = EFC4D9A586DD424D85B2FA1C /* UTMSnapshotService.swift */; }; + 41FCD5A63CB34D7F8E20F687 /* UTMSnapshotService.swift in Sources */ = {isa = PBXBuildFile; fileRef = EFC4D9A586DD424D85B2FA1C /* UTMSnapshotService.swift */; }; 4B224B9D279D4D8100B63CFF /* InListButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B224B9C279D4D8100B63CFF /* InListButtonStyle.swift */; }; 4B224B9E279D4D8100B63CFF /* InListButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B224B9C279D4D8100B63CFF /* InListButtonStyle.swift */; }; 4B224B9F279D4D8100B63CFF /* InListButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B224B9C279D4D8100B63CFF /* InListButtonStyle.swift */; }; + 5324DE20FD9339463499B17C /* VMConfigQEMUCommandLineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03AEFABA6F4DF195E96AD2D5 /* VMConfigQEMUCommandLineView.swift */; }; 53A0BDD726D79FE40010EDC5 /* SavePanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53A0BDD426D79FE40010EDC5 /* SavePanel.swift */; }; + 5A3966538C7644CE9E598CFE /* UTMAppleVmnetNetworkManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F9F544E438D4BE8A5CA9360 /* UTMAppleVmnetNetworkManager.swift */; }; + 63353664DA55B4F6AC8BD07B /* UTMQemuSnapshotBackend.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7335781A3EBC95B7BE01C90 /* UTMQemuSnapshotBackend.swift */; }; + 6E14FA50A12736E32EA02200 /* UTMSnapshotBackend.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE6FCF9E7BD060AB81411ABC /* UTMSnapshotBackend.swift */; }; + 738DDF2A88F46375A0D72177 /* UTMQemuSnapshotBackend.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7335781A3EBC95B7BE01C90 /* UTMQemuSnapshotBackend.swift */; }; + 7591FABAADBC2544E9F0F37B /* VMConfigQEMUCommandLineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03AEFABA6F4DF195E96AD2D5 /* VMConfigQEMUCommandLineView.swift */; }; + 7EFF134EA0581C7A4CBBBAF5 /* VMConfigQEMUArgumentsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF2D14863C5CDE332BC8698C /* VMConfigQEMUArgumentsView.swift */; }; 83034C0726AB630F006B4BAF /* UTMPendingVMView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83034C0626AB630F006B4BAF /* UTMPendingVMView.swift */; }; 83034C0826AB630F006B4BAF /* UTMPendingVMView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83034C0626AB630F006B4BAF /* UTMPendingVMView.swift */; }; 83034C0926AB630F006B4BAF /* UTMPendingVMView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83034C0626AB630F006B4BAF /* UTMPendingVMView.swift */; }; @@ -32,7 +47,6 @@ 83A004BA26A8CC95001AC09E /* UTMDownloadTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83A004B826A8CC95001AC09E /* UTMDownloadTask.swift */; }; 83A004BB26A8CC95001AC09E /* UTMDownloadTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83A004B826A8CC95001AC09E /* UTMDownloadTask.swift */; }; 83C15C5F26CC441500ADFD45 /* KeyCodeMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83C15C5E26CC441000ADFD45 /* KeyCodeMap.swift */; }; - A1C4F00230C1200000CA9501 /* CapsLockRemapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1C4F00130C1200000CA9501 /* CapsLockRemapper.swift */; }; 8401865A2887AFD50050AC51 /* SwiftTerm in Frameworks */ = {isa = PBXBuildFile; productRef = 840186592887AFD50050AC51 /* SwiftTerm */; }; 8401865C2887AFDC0050AC51 /* SwiftTerm in Frameworks */ = {isa = PBXBuildFile; productRef = 8401865B2887AFDC0050AC51 /* SwiftTerm */; }; 8401865E2887B1620050AC51 /* VMDisplayTerminalViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8401865D2887B1620050AC51 /* VMDisplayTerminalViewController.swift */; }; @@ -139,20 +153,8 @@ 844EC0FB2773EE49003C104A /* UTMDownloadIPSWTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844EC0FA2773EE49003C104A /* UTMDownloadIPSWTask.swift */; }; 8453DCB2278CE3D10037A0DA /* qemu-img.framework in Embed Libraries */ = {isa = PBXBuildFile; fileRef = 8453DCB0278CE33E0037A0DA /* qemu-img.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 8453DCB4278CE5410037A0DA /* UTMQemuImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8453DCB3278CE5410037A0DA /* UTMQemuImage.swift */; }; - 393A92DAE84A47029BA5F829 /* UTMSnapshotService.swift in Sources */ = {isa = PBXBuildFile; fileRef = EFC4D9A586DD424D85B2FA1C /* UTMSnapshotService.swift */; }; - 738DDF2A88F46375A0D72177 /* UTMQemuSnapshotBackend.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7335781A3EBC95B7BE01C90 /* UTMQemuSnapshotBackend.swift */; }; - EFAB3C24B8C39BC85EEFE028 /* UTMSnapshotBackend.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE6FCF9E7BD060AB81411ABC /* UTMSnapshotBackend.swift */; }; - AFC3FBE9C92083C52D800437 /* UTMSnapshotManifest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F9CF0C724EB2633D0B87ACE /* UTMSnapshotManifest.swift */; }; 8453DCB5278CE5410037A0DA /* UTMQemuImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8453DCB3278CE5410037A0DA /* UTMQemuImage.swift */; }; - 41FCD5A63CB34D7F8E20F687 /* UTMSnapshotService.swift in Sources */ = {isa = PBXBuildFile; fileRef = EFC4D9A586DD424D85B2FA1C /* UTMSnapshotService.swift */; }; - 8F8F6CA6377BBED5737019BB /* UTMQemuSnapshotBackend.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7335781A3EBC95B7BE01C90 /* UTMQemuSnapshotBackend.swift */; }; - 12618B76026270B5C1C8CD29 /* UTMSnapshotBackend.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE6FCF9E7BD060AB81411ABC /* UTMSnapshotBackend.swift */; }; - BCB19D49AC1352354193ACB6 /* UTMSnapshotManifest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F9CF0C724EB2633D0B87ACE /* UTMSnapshotManifest.swift */; }; 8453DCB6278CE5410037A0DA /* UTMQemuImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8453DCB3278CE5410037A0DA /* UTMQemuImage.swift */; }; - FA897DF5FDA34A4F8F6782AD /* UTMSnapshotService.swift in Sources */ = {isa = PBXBuildFile; fileRef = EFC4D9A586DD424D85B2FA1C /* UTMSnapshotService.swift */; }; - 63353664DA55B4F6AC8BD07B /* UTMQemuSnapshotBackend.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7335781A3EBC95B7BE01C90 /* UTMQemuSnapshotBackend.swift */; }; - 6E14FA50A12736E32EA02200 /* UTMSnapshotBackend.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE6FCF9E7BD060AB81411ABC /* UTMSnapshotBackend.swift */; }; - E1B70310A596175A0E9F3025 /* UTMSnapshotManifest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F9CF0C724EB2633D0B87ACE /* UTMSnapshotManifest.swift */; }; 845F1705289B1EEB00944904 /* UTMAppleConfigurationGenericPlatform.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845F1704289B1EEB00944904 /* UTMAppleConfigurationGenericPlatform.swift */; }; 845F1707289B5E2600944904 /* VMAppleSettingsAddDeviceMenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845F1706289B5E2600944904 /* VMAppleSettingsAddDeviceMenuView.swift */; }; 845F1709289CA15C00944904 /* VMDisplayAppleTerminalWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845F1708289CA15C00944904 /* VMDisplayAppleTerminalWindowController.swift */; }; @@ -177,11 +179,8 @@ 8471772927CD3CAB00D3A50B /* DetailedSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8471772727CD3CAB00D3A50B /* DetailedSection.swift */; }; 8471772A27CD3CAB00D3A50B /* DetailedSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8471772727CD3CAB00D3A50B /* DetailedSection.swift */; }; 847BF9AA2A49C783000BD9AA /* VMData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 847BF9A92A49C783000BD9AA /* VMData.swift */; }; - 29B2D623BB879D2653CA8C53 /* VMSnapshotData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16001E3BC682D8470EFCF312 /* VMSnapshotData.swift */; }; 847BF9AB2A49C783000BD9AA /* VMData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 847BF9A92A49C783000BD9AA /* VMData.swift */; }; - 10488F379FF913D2E7770E60 /* VMSnapshotData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16001E3BC682D8470EFCF312 /* VMSnapshotData.swift */; }; 847BF9AC2A49C783000BD9AA /* VMData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 847BF9A92A49C783000BD9AA /* VMData.swift */; }; - A5A9A26C444286951EC315D9 /* VMSnapshotData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16001E3BC682D8470EFCF312 /* VMSnapshotData.swift */; }; 84818C0C2898A07A009EDB67 /* AVFAudio.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84818C0B2898A07A009EDB67 /* AVFAudio.framework */; }; 84818C0D2898A07F009EDB67 /* AVFAudio.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84818C0B2898A07A009EDB67 /* AVFAudio.framework */; }; 848308D5278A1F2200E3E474 /* Virtualization.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 848308D4278A1F2200E3E474 /* Virtualization.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; @@ -245,6 +244,14 @@ 84A0A88A2A47D5D10038F329 /* QEMUKit in Frameworks */ = {isa = PBXBuildFile; productRef = 84A0A8892A47D5D10038F329 /* QEMUKit */; }; 84A0A88C2A47D5D70038F329 /* QEMUKit in Frameworks */ = {isa = PBXBuildFile; productRef = 84A0A88B2A47D5D70038F329 /* QEMUKit */; }; 84A381AA268CB30C0048EE4D /* VMDrivesSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A381A9268CB30C0048EE4D /* VMDrivesSettingsView.swift */; }; + 84A57CB830641C6800BA5139 /* iOSHelper.appex in Embed ExtensionKit Extensions */ = {isa = PBXBuildFile; fileRef = 84A57CB130641C6800BA5139 /* iOSHelper.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 84A57CC230641C9100BA5139 /* iOSHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A57CBF30641C9100BA5139 /* iOSHelper.swift */; }; + 84A57CC330641CCE00BA5139 /* iOSHelper-SE.appex in Embed ExtensionKit Extensions */ = {isa = PBXBuildFile; fileRef = 84A57D0930641D0000BA5139 /* iOSHelper-SE.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 84A57D0830641D0000BA5139 /* iOSHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A57CBF30641C9100BA5139 /* iOSHelper.swift */; }; + 84A57D0A30641D0000BA5139 /* qemu-img.framework in Embed Libraries */ = {isa = PBXBuildFile; fileRef = 8453DCB0278CE33E0037A0DA /* qemu-img.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; + 84A57D0B30641D0000BA5139 /* qemu-img.framework in Embed Libraries */ = {isa = PBXBuildFile; fileRef = 8453DCB0278CE33E0037A0DA /* qemu-img.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; + 84A57D0D30641D0000BA5139 /* UTMHelperProcess.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A57D0C30641D0000BA5139 /* UTMHelperProcess.swift */; }; + 84A57D0E30641D0000BA5139 /* UTMHelperProcess.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A57D0C30641D0000BA5139 /* UTMHelperProcess.swift */; }; 84B36D1E27B3264600C22685 /* CocoaSpice in Frameworks */ = {isa = PBXBuildFile; productRef = 84B36D1D27B3264600C22685 /* CocoaSpice */; }; 84B36D2027B3264E00C22685 /* CocoaSpiceNoUsb in Frameworks */ = {isa = PBXBuildFile; productRef = 84B36D1F27B3264E00C22685 /* CocoaSpiceNoUsb */; }; 84B36D2227B3265400C22685 /* CocoaSpice in Frameworks */ = {isa = PBXBuildFile; productRef = 84B36D2127B3265400C22685 /* CocoaSpice */; }; @@ -268,7 +275,6 @@ 84C584E1268E95B3000FCABF /* UTMLegacyAppleConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84C584DE268E95B3000FCABF /* UTMLegacyAppleConfiguration.swift */; }; 84C584E3268F8AE7000FCABF /* VMQEMUSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84C584E2268F8AE7000FCABF /* VMQEMUSettingsView.swift */; }; 84C584E5268F8C65000FCABF /* VMAppleSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84C584E4268F8C65000FCABF /* VMAppleSettingsView.swift */; }; - CE7B3A112EA1B00000C0FFEE /* MultilineTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7B3A102EA1B00000C0FFEE /* MultilineTextField.swift */; }; 84C584EB268FA6D1000FCABF /* VMConfigAppleSystemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84C584EA268FA6D1000FCABF /* VMConfigAppleSystemView.swift */; }; 84C60FB72681A41B00B58C00 /* VMToolbarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84C60FB62681A41B00B58C00 /* VMToolbarView.swift */; platformFilter = ios; }; 84C60FB82681A41B00B58C00 /* VMToolbarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84C60FB62681A41B00B58C00 /* VMToolbarView.swift */; platformFilter = ios; }; @@ -297,8 +303,16 @@ 85EC516427CC8D0F004A51DE /* VMConfigAdvancedNetworkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85EC516327CC8C98004A51DE /* VMConfigAdvancedNetworkView.swift */; }; 85EC516527CC8D0F004A51DE /* VMConfigAdvancedNetworkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85EC516327CC8C98004A51DE /* VMConfigAdvancedNetworkView.swift */; }; 85EC516627CC8D10004A51DE /* VMConfigAdvancedNetworkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85EC516327CC8C98004A51DE /* VMConfigAdvancedNetworkView.swift */; }; + 8F8F6CA6377BBED5737019BB /* UTMQemuSnapshotBackend.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7335781A3EBC95B7BE01C90 /* UTMQemuSnapshotBackend.swift */; }; + 947DDF020475E2433DDC2A21 /* VMConfigQEMUCommandLineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03AEFABA6F4DF195E96AD2D5 /* VMConfigQEMUCommandLineView.swift */; }; + 9DFF2F0A05DA5A4B5FA35917 /* VMKeyboardAccessoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB38A22C8D69DB2D2C64107F /* VMKeyboardAccessoryView.swift */; }; + A1C4F00230C1200000CA9501 /* CapsLockRemapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1C4F00130C1200000CA9501 /* CapsLockRemapper.swift */; }; + A5A9A26C444286951EC315D9 /* VMSnapshotData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16001E3BC682D8470EFCF312 /* VMSnapshotData.swift */; }; + AFC3FBE9C92083C52D800437 /* UTMSnapshotManifest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F9CF0C724EB2633D0B87ACE /* UTMSnapshotManifest.swift */; }; B329049C270FE136002707AC /* AltKit in Frameworks */ = {isa = PBXBuildFile; productRef = B329049B270FE136002707AC /* AltKit */; }; B3DDF57226E9BBA300CE47F0 /* AltKit in Frameworks */ = {isa = PBXBuildFile; productRef = B3DDF57126E9BBA300CE47F0 /* AltKit */; }; + B56B01FE103C463CC3F84753 /* VMConfigQEMUArgumentsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF2D14863C5CDE332BC8698C /* VMConfigQEMUArgumentsView.swift */; }; + BCB19D49AC1352354193ACB6 /* UTMSnapshotManifest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F9CF0C724EB2633D0B87ACE /* UTMSnapshotManifest.swift */; }; CD77BE422CAB51B40074ADD2 /* UTMScriptingExportCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD77BE412CAB519F0074ADD2 /* UTMScriptingExportCommand.swift */; }; CD77BE442CB38F060074ADD2 /* UTMScriptingImportCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD77BE432CB38F060074ADD2 /* UTMScriptingImportCommand.swift */; }; CD84C2092D3B446D00829850 /* UTMScriptingRegistryEntryImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD84C2082D3B446D00829850 /* UTMScriptingRegistryEntryImpl.swift */; }; @@ -454,6 +468,8 @@ CE19392826DCB094005CEC17 /* RAMSlider.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE19392526DCB093005CEC17 /* RAMSlider.swift */; }; CE1AEC3F2B78B30700992AFC /* MacDeviceLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE1AEC3E2B78B30700992AFC /* MacDeviceLabel.swift */; }; CE1AEC402B78B30700992AFC /* MacDeviceLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE1AEC3E2B78B30700992AFC /* MacDeviceLabel.swift */; }; + CE1D15AB2F6B100100D15C01 /* UTMAppleDiskImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE1D15AA2F6B100100D15C01 /* UTMAppleDiskImage.swift */; }; + CE1D15AD2F6B100100D15C01 /* DiskImageKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CE1D15AC2F6B100100D15C01 /* DiskImageKit.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; CE231D422BDDF280006D6DC3 /* UTMDonateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE231D412BDDF280006D6DC3 /* UTMDonateView.swift */; }; CE231D432BDDF280006D6DC3 /* UTMDonateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE231D412BDDF280006D6DC3 /* UTMDonateView.swift */; }; CE231D462BDDFD03006D6DC3 /* UTMDonateStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE231D452BDDFD03006D6DC3 /* UTMDonateStore.swift */; }; @@ -485,7 +501,6 @@ CE2D92DA24AD46670059923A /* VMCursor.m in Sources */ = {isa = PBXBuildFile; fileRef = CE3ADD692411C661002D6A5F /* VMCursor.m */; }; CE2D92E624AD46670059923A /* UTMLegacyQemuConfiguration+Networking.m in Sources */ = {isa = PBXBuildFile; fileRef = CEA02A982436C7A30087E45F /* UTMLegacyQemuConfiguration+Networking.m */; }; CE2D92EB24AD46670059923A /* UTMLocationManager.m in Sources */ = {isa = PBXBuildFile; fileRef = CE059DC7243E9E3400338317 /* UTMLocationManager.m */; platformFilter = ios; }; - FD2E48FCF9EC5A44E0A35855 /* VMKeyboardAccessoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB38A22C8D69DB2D2C64107F /* VMKeyboardAccessoryView.swift */; }; CE2D92F224AD46670059923A /* VMKeyboardButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEEB66452284B942002737B2 /* VMKeyboardButton.swift */; }; CE2D92FC24AD46670059923A /* VMDisplayMetalViewController+Keyboard.m in Sources */ = {isa = PBXBuildFile; fileRef = CE3ADD66240EFBCA002D6A5F /* VMDisplayMetalViewController+Keyboard.m */; }; CE2D930424AD46670059923A /* UTMLegacyQemuConfiguration.m in Sources */ = {isa = PBXBuildFile; fileRef = CE31C244225E555600A965DD /* UTMLegacyQemuConfiguration.m */; }; @@ -628,9 +643,7 @@ CE2D956A24AD4F990059923A /* VMPlaceholderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE2D954224AD4F980059923A /* VMPlaceholderView.swift */; }; CE2D956C24AD4F990059923A /* VMCardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE2D954324AD4F980059923A /* VMCardView.swift */; }; CE2D956F24AD4F990059923A /* VMRemovableDrivesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE2D954524AD4F980059923A /* VMRemovableDrivesView.swift */; }; - F3B87AB8E7FA8B9648FC0EAD /* VMSnapshotsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BB911D9C7D6564198DAC87 /* VMSnapshotsView.swift */; }; CE2D957024AD4F990059923A /* VMRemovableDrivesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE2D954524AD4F980059923A /* VMRemovableDrivesView.swift */; }; - D38CFF45AB0566027A30677F /* VMSnapshotsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BB911D9C7D6564198DAC87 /* VMSnapshotsView.swift */; }; CE2D957124AD4F990059923A /* UTMExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE2D954624AD4F980059923A /* UTMExtensions.swift */; }; CE2D957224AD4F990059923A /* UTMExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE2D954624AD4F980059923A /* UTMExtensions.swift */; }; CE2D957324AD4F990059923A /* VMConfigSharingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE2D954724AD4F980059923A /* VMConfigSharingView.swift */; }; @@ -661,13 +674,13 @@ CE5451AA26AF5F10008594E5 /* GLESv2.framework in Embed Libraries */ = {isa = PBXBuildFile; fileRef = CE5451A326AF5F0F008594E5 /* GLESv2.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; CE5451AC26AF5F10008594E5 /* EGL.framework in Embed Libraries */ = {isa = PBXBuildFile; fileRef = CE5451A426AF5F0F008594E5 /* EGL.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; CE59A7B02AA69AE400E5FFBD /* UTMScriptingUSBDeviceImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE59A7AF2AA69AE400E5FFBD /* UTMScriptingUSBDeviceImpl.swift */; }; + CE5A7E112E9F4A1100C0FFEE /* VMDisplayWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE5A7E102E9F4A1100C0FFEE /* VMDisplayWindow.swift */; }; CE611BE729F50CAD001817BC /* UTMReleaseHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE611BE629F50CAD001817BC /* UTMReleaseHelper.swift */; }; CE611BE829F50CAD001817BC /* UTMReleaseHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE611BE629F50CAD001817BC /* UTMReleaseHelper.swift */; }; CE611BE929F50CAD001817BC /* UTMReleaseHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE611BE629F50CAD001817BC /* UTMReleaseHelper.swift */; }; CE611BEB29F50D3E001817BC /* VMReleaseNotesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE611BEA29F50D3E001817BC /* VMReleaseNotesView.swift */; }; CE611BEC29F50D3E001817BC /* VMReleaseNotesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE611BEA29F50D3E001817BC /* VMReleaseNotesView.swift */; }; CE611BED29F50D3E001817BC /* VMReleaseNotesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE611BEA29F50D3E001817BC /* VMReleaseNotesView.swift */; }; - CE5A7E112E9F4A1100C0FFEE /* VMDisplayWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE5A7E102E9F4A1100C0FFEE /* VMDisplayWindow.swift */; }; CE612AC624D3B50700FA6300 /* VMDisplayWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE612AC524D3B50700FA6300 /* VMDisplayWindowController.swift */; }; CE63B0DB2F0ED4FF00C59D13 /* vulkan_kosmickrisp.framework in Embed Libraries */ = {isa = PBXBuildFile; fileRef = CE63B0D72F0ED4FE00C59D13 /* vulkan_kosmickrisp.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; CE63B0DD2F0ED4FF00C59D13 /* vulkan.1.framework in Embed Libraries */ = {isa = PBXBuildFile; fileRef = CE63B0D82F0ED4FE00C59D13 /* vulkan.1.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; @@ -684,12 +697,6 @@ CE65BABF26A4D8DD0001BD6B /* VMConfigDisplayConsoleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8401FDA5269D44E400265F0D /* VMConfigDisplayConsoleView.swift */; }; CE65BAC026A4D8DE0001BD6B /* VMConfigDisplayConsoleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8401FDA5269D44E400265F0D /* VMConfigDisplayConsoleView.swift */; }; CE6804802E493D71001671E9 /* UTMUSBManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE68047D2E493D71001671E9 /* UTMUSBManager.swift */; }; - CE7A1C022EFA0A01001671E9 /* UTMAppleUSBDevice.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7A1C012EFA0A01001671E9 /* UTMAppleUSBDevice.swift */; }; - CE7A1C042EFA0A01001671E9 /* UTMAppleUSBManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7A1C032EFA0A01001671E9 /* UTMAppleUSBManager.swift */; }; - CE7A1C062EFA0A01001671E9 /* AccessoryAccess.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CE7A1C052EFA0A01001671E9 /* AccessoryAccess.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; - CE1D15AB2F6B100100D15C01 /* UTMAppleDiskImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE1D15AA2F6B100100D15C01 /* UTMAppleDiskImage.swift */; }; - 1C5910D866A20395225768B9 /* UTMAppleSnapshotBackend.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CBB52BFA0D55FDEC6497878 /* UTMAppleSnapshotBackend.swift */; }; - CE1D15AD2F6B100100D15C01 /* DiskImageKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CE1D15AC2F6B100100D15C01 /* DiskImageKit.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; CE6804852E4E5D84001671E9 /* UTMScriptingInputImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE6804842E4E5D84001671E9 /* UTMScriptingInputImpl.swift */; }; CE68E5442E3912E0006B3645 /* VMKeyboardShortcutsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE68E5422E3912E0006B3645 /* VMKeyboardShortcutsView.swift */; }; CE68E5452E3912E0006B3645 /* VMKeyboardShortcutsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE68E5422E3912E0006B3645 /* VMKeyboardShortcutsView.swift */; }; @@ -708,6 +715,10 @@ CE772AAD25C8B0F600E4E379 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE772AAB25C8B0F600E4E379 /* ContentView.swift */; }; CE772AB325C8B7B500E4E379 /* VMCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE772AB225C8B7B500E4E379 /* VMCommands.swift */; }; CE772AB425C8B7B500E4E379 /* VMCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE772AB225C8B7B500E4E379 /* VMCommands.swift */; }; + CE7A1C022EFA0A01001671E9 /* UTMAppleUSBDevice.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7A1C012EFA0A01001671E9 /* UTMAppleUSBDevice.swift */; }; + CE7A1C042EFA0A01001671E9 /* UTMAppleUSBManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7A1C032EFA0A01001671E9 /* UTMAppleUSBManager.swift */; }; + CE7A1C062EFA0A01001671E9 /* AccessoryAccess.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CE7A1C052EFA0A01001671E9 /* AccessoryAccess.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; + CE7B3A112EA1B00000C0FFEE /* MultilineTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7B3A102EA1B00000C0FFEE /* MultilineTextField.swift */; }; CE7D972C24B2B17D0080CB69 /* BusyOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7D972B24B2B17D0080CB69 /* BusyOverlay.swift */; }; CE8011202AD4E9E8009001C2 /* UTMApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE80111F2AD4E9E8009001C2 /* UTMApp.swift */; platformFilters = (xros, ); }; CE8011212AD4E9E8009001C2 /* UTMApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE80111F2AD4E9E8009001C2 /* UTMApp.swift */; platformFilters = (xros, ); }; @@ -741,7 +752,6 @@ CE89CB102B8B1B6A006B2CC2 /* VisionKeyboardKit in Frameworks */ = {isa = PBXBuildFile; platformFilters = (xros, ); productRef = CE89CB0F2B8B1B6A006B2CC2 /* VisionKeyboardKit */; }; CE89CB122B8B1B7A006B2CC2 /* VisionKeyboardKit in Frameworks */ = {isa = PBXBuildFile; platformFilters = (xros, ); productRef = CE89CB112B8B1B7A006B2CC2 /* VisionKeyboardKit */; }; CE928C2A26ABE6690099F293 /* UTMAppleVirtualMachine.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE928C2926ABE6690099F293 /* UTMAppleVirtualMachine.swift */; }; - 5A3966538C7644CE9E598CFE /* UTMAppleVmnetNetworkManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F9F544E438D4BE8A5CA9360 /* UTMAppleVmnetNetworkManager.swift */; }; CE928C3126ACCDEA0099F293 /* VMAppleRemovableDrivesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE928C3026ACCDEA0099F293 /* VMAppleRemovableDrivesView.swift */; }; CE93758924B930270074066F /* BusyOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7D972B24B2B17D0080CB69 /* BusyOverlay.swift */; }; CE93759924BB821F0074066F /* IQKeyboardManagerSwift in Frameworks */ = {isa = PBXBuildFile; platformFilter = ios; productRef = CE93759824BB821F0074066F /* IQKeyboardManagerSwift */; }; @@ -768,7 +778,6 @@ CEA3EE05301691C500AAA8E4 /* QEMURenderServer.app in Embed Executables */ = {isa = PBXBuildFile; fileRef = CEA3EDFA30168B9000AAA8E4 /* QEMURenderServer.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; CEA45E25263519B5002FA97D /* VMDisplayMetalViewController+Pointer.h in Sources */ = {isa = PBXBuildFile; fileRef = 83FBDD53242FA71900D2C5D7 /* VMDisplayMetalViewController+Pointer.h */; }; CEA45E27263519B5002FA97D /* VMRemovableDrivesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE2D954524AD4F980059923A /* VMRemovableDrivesView.swift */; }; - E427638537FF5935C6DCAF11 /* VMSnapshotsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BB911D9C7D6564198DAC87 /* VMSnapshotsView.swift */; }; CEA45E37263519B5002FA97D /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE772AAB25C8B0F600E4E379 /* ContentView.swift */; }; CEA45E3A263519B5002FA97D /* UTMLegacyQemuConfiguration+System.m in Sources */ = {isa = PBXBuildFile; fileRef = CE5425332437C22A00E520F7 /* UTMLegacyQemuConfiguration+System.m */; }; CEA45E3F263519B5002FA97D /* UTMProcess.m in Sources */ = {isa = PBXBuildFile; fileRef = CE9D197B226542FE00355E14 /* UTMProcess.m */; }; @@ -809,7 +818,6 @@ CEA45ECD263519B5002FA97D /* VMConfirmActionModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE6D21DB2553A6ED001D29C5 /* VMConfirmActionModifier.swift */; }; CEA45ECF263519B5002FA97D /* VMConfigPortForwardForm.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE2D955224AD4F980059923A /* VMConfigPortForwardForm.swift */; }; CEA45ED3263519B5002FA97D /* VMSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE2D954C24AD4F980059923A /* VMSettingsView.swift */; }; - 9DFF2F0A05DA5A4B5FA35917 /* VMKeyboardAccessoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB38A22C8D69DB2D2C64107F /* VMKeyboardAccessoryView.swift */; }; CEA45ED8263519B5002FA97D /* VMKeyboardButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEEB66452284B942002737B2 /* VMKeyboardButton.swift */; }; CEA45EE8263519B5002FA97D /* VMDisplayMetalViewController+Keyboard.m in Sources */ = {isa = PBXBuildFile; fileRef = CE3ADD66240EFBCA002D6A5F /* VMDisplayMetalViewController+Keyboard.m */; }; CEA45EEA263519B5002FA97D /* UTMExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE2D954624AD4F980059923A /* UTMExtensions.swift */; }; @@ -975,7 +983,6 @@ CEC1B00C2BBB211C0088119D /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = CEC1B00A2BBB211C0088119D /* PrivacyInfo.xcprivacy */; }; CEC1B00D2BBB211C0088119D /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = CEC1B00A2BBB211C0088119D /* PrivacyInfo.xcprivacy */; }; CEC794BA294924E300121A9F /* UTMScriptingSerialPortImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEC794B9294924E300121A9F /* UTMScriptingSerialPortImpl.swift */; }; - FC6D2EA0328DA5CD033B9518 /* UTMScriptingSnapshotImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38E2C5FE357F6FEDAEA675CA /* UTMScriptingSnapshotImpl.swift */; }; CEC794BC2949663C00121A9F /* UTMScripting.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEC794BB2949663C00121A9F /* UTMScripting.swift */; }; CEC794BD2949663C00121A9F /* UTMScripting.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEC794BB2949663C00121A9F /* UTMScripting.swift */; }; CED234ED254796E500ED0A57 /* NumberTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = CED234EC254796E500ED0A57 /* NumberTextField.swift */; }; @@ -1156,7 +1163,6 @@ CEF7F5FB2AEEDCC400E34952 /* VMDisplayViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84C60FB9268269D700B58C00 /* VMDisplayViewController.swift */; }; CEF7F5FC2AEEDCC400E34952 /* VMWizardStartView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEF0305926A2AFDE00667B63 /* VMWizardStartView.swift */; }; CEF7F5FD2AEEDCC400E34952 /* QEMUConstantGenerated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 843BF82728441FAF0029D60D /* QEMUConstantGenerated.swift */; }; - 2A3EFF2D980175F4D531E0E3 /* VMKeyboardAccessoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB38A22C8D69DB2D2C64107F /* VMKeyboardAccessoryView.swift */; }; CEF7F5FE2AEEDCC400E34952 /* VMKeyboardButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEEB66452284B942002737B2 /* VMKeyboardButton.swift */; }; CEF7F5FF2AEEDCC400E34952 /* UTMDownloadVMTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B36D2427B704C200C22685 /* UTMDownloadVMTask.swift */; }; CEF7F6002AEEDCC400E34952 /* GlobalFileImporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8432329728C3017F00CFBC97 /* GlobalFileImporter.swift */; }; @@ -1317,19 +1323,21 @@ CEFE96772B69A7CC000F00C9 /* VMRemoteSessionState.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEFE96762B69A7CC000F00C9 /* VMRemoteSessionState.swift */; }; CEFE98DF29485237007CB7A8 /* UTM.sdef in Resources */ = {isa = PBXBuildFile; fileRef = CEFE98DE29485237007CB7A8 /* UTM.sdef */; }; CEFE98E129485776007CB7A8 /* UTMScriptingVirtualMachineImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEFE98E029485776007CB7A8 /* UTMScriptingVirtualMachineImpl.swift */; }; + D38CFF45AB0566027A30677F /* VMSnapshotsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BB911D9C7D6564198DAC87 /* VMSnapshotsView.swift */; }; + E1B70310A596175A0E9F3025 /* UTMSnapshotManifest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F9CF0C724EB2633D0B87ACE /* UTMSnapshotManifest.swift */; }; + E427638537FF5935C6DCAF11 /* VMSnapshotsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BB911D9C7D6564198DAC87 /* VMSnapshotsView.swift */; }; + E75442DAFC2ED7AF55CC87CA /* VMConfigQEMUArgumentsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF2D14863C5CDE332BC8698C /* VMConfigQEMUArgumentsView.swift */; }; + EFAB3C24B8C39BC85EEFE028 /* UTMSnapshotBackend.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE6FCF9E7BD060AB81411ABC /* UTMSnapshotBackend.swift */; }; + F3B87AB8E7FA8B9648FC0EAD /* VMSnapshotsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BB911D9C7D6564198DAC87 /* VMSnapshotsView.swift */; }; + FA897DF5FDA34A4F8F6782AD /* UTMSnapshotService.swift in Sources */ = {isa = PBXBuildFile; fileRef = EFC4D9A586DD424D85B2FA1C /* UTMSnapshotService.swift */; }; + FC6D2EA0328DA5CD033B9518 /* UTMScriptingSnapshotImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38E2C5FE357F6FEDAEA675CA /* UTMScriptingSnapshotImpl.swift */; }; + FD2E48FCF9EC5A44E0A35855 /* VMKeyboardAccessoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB38A22C8D69DB2D2C64107F /* VMKeyboardAccessoryView.swift */; }; FF0307552A84E3B70049979B /* QEMULauncher-InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = FF0307532A84E3B70049979B /* QEMULauncher-InfoPlist.strings */; }; FFB02A8C266CB09C006CD71A /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = FFB02A8A266CB09C006CD71A /* InfoPlist.strings */; }; FFB02A8D266CB09C006CD71A /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = FFB02A8A266CB09C006CD71A /* InfoPlist.strings */; }; FFB02A90266CB09C006CD71A /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = FFB02A8E266CB09C006CD71A /* InfoPlist.strings */; }; FFB02A93266CB09C006CD71A /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = FFB02A91266CB09C006CD71A /* InfoPlist.strings */; }; FFB02A96266CB09C006CD71A /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = FFB02A94266CB09C006CD71A /* Localizable.strings */; }; - 7EFF134EA0581C7A4CBBBAF5 /* VMConfigQEMUArgumentsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF2D14863C5CDE332BC8698C /* VMConfigQEMUArgumentsView.swift */; }; - B56B01FE103C463CC3F84753 /* VMConfigQEMUArgumentsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF2D14863C5CDE332BC8698C /* VMConfigQEMUArgumentsView.swift */; }; - E75442DAFC2ED7AF55CC87CA /* VMConfigQEMUArgumentsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF2D14863C5CDE332BC8698C /* VMConfigQEMUArgumentsView.swift */; }; - 7591FABAADBC2544E9F0F37B /* VMConfigQEMUCommandLineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03AEFABA6F4DF195E96AD2D5 /* VMConfigQEMUCommandLineView.swift */; }; - 947DDF020475E2433DDC2A21 /* VMConfigQEMUCommandLineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03AEFABA6F4DF195E96AD2D5 /* VMConfigQEMUCommandLineView.swift */; }; - 31D9CB9DD8370CF0CCCDE85C /* VMConfigQEMUCommandLineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03AEFABA6F4DF195E96AD2D5 /* VMConfigQEMUCommandLineView.swift */; }; - 5324DE20FD9339463499B17C /* VMConfigQEMUCommandLineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03AEFABA6F4DF195E96AD2D5 /* VMConfigQEMUCommandLineView.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -1340,6 +1348,20 @@ remoteGlobalIDString = 8401FD61269BE9C500265F0D; remoteInfo = QEMULauncher; }; + 84A57CB630641C6800BA5139 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = CE550BC1225947990063E575 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 84A57CB030641C6800BA5139; + remoteInfo = iOSHelper; + }; + 84A57CC430641CCE00BA5139 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = CE550BC1225947990063E575 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 84A57D0130641D0000BA5139; + remoteInfo = "iOSHelper-SE"; + }; 84E3A8FC293DB7720024A740 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = CE550BC1225947990063E575 /* Project object */; @@ -1371,6 +1393,28 @@ /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ + 84A57CB930641C6800BA5139 /* Embed ExtensionKit Extensions */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = "$(EXTENSIONS_FOLDER_PATH)"; + dstSubfolderSpec = 16; + files = ( + 84A57CB830641C6800BA5139 /* iOSHelper.appex in Embed ExtensionKit Extensions */, + ); + name = "Embed ExtensionKit Extensions"; + runOnlyForDeploymentPostprocessing = 0; + }; + 84A57CC630641CCE00BA5139 /* Embed ExtensionKit Extensions */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = "$(EXTENSIONS_FOLDER_PATH)"; + dstSubfolderSpec = 16; + files = ( + 84A57CC330641CCE00BA5139 /* iOSHelper-SE.appex in Embed ExtensionKit Extensions */, + ); + name = "Embed ExtensionKit Extensions"; + runOnlyForDeploymentPostprocessing = 0; + }; 84E3A8EE293DB37E0024A740 /* Copy Files */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; @@ -1488,6 +1532,7 @@ dstPath = ""; dstSubfolderSpec = 10; files = ( + 84A57D0A30641D0000BA5139 /* qemu-img.framework in Embed Libraries */, CE30F93730200C3300024DB2 /* dxmt-native.framework in Embed Libraries */, CE2D937624AD46670059923A /* gpg-error.0.framework in Embed Libraries */, CE2D937724AD46670059923A /* qemu-mips64el-softmmu.framework in Embed Libraries */, @@ -1590,6 +1635,7 @@ dstPath = ""; dstSubfolderSpec = 10; files = ( + 84A57D0B30641D0000BA5139 /* qemu-img.framework in Embed Libraries */, CE30F93630200C2000024DB2 /* dxmt-native.framework in Embed Libraries */, CEA45F72263519B5002FA97D /* gpg-error.0.framework in Embed Libraries */, CE63B0FD2F0EFB0700C59D13 /* vulkan_kosmickrisp.framework in Embed Libraries */, @@ -1718,13 +1764,19 @@ 037DAA202B0B92580061ACB3 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = it; path = it.lproj/Localizable.stringsdict; sourceTree = ""; }; 037DAA212B0B92580061ACB3 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Localizable.strings; sourceTree = ""; }; 037DAA222B0B92580061ACB3 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/InfoPlist.strings; sourceTree = ""; }; + 03AEFABA6F4DF195E96AD2D5 /* VMConfigQEMUCommandLineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VMConfigQEMUCommandLineView.swift; sourceTree = ""; }; 03FA9C712B9BBDB000C53A5A /* UTMConfigurationHostNetwork.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTMConfigurationHostNetwork.swift; sourceTree = ""; }; + 0CBB52BFA0D55FDEC6497878 /* UTMAppleSnapshotBackend.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTMAppleSnapshotBackend.swift; sourceTree = ""; }; + 0F9CF0C724EB2633D0B87ACE /* UTMSnapshotManifest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTMSnapshotManifest.swift; sourceTree = ""; }; + 16001E3BC682D8470EFCF312 /* VMSnapshotData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VMSnapshotData.swift; sourceTree = ""; }; 2C33B3A82566C9B100A954A6 /* VMContextMenuModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VMContextMenuModifier.swift; sourceTree = ""; }; 2C6D9E02256EE454003298E6 /* VMDisplayQemuTerminalWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VMDisplayQemuTerminalWindowController.swift; sourceTree = ""; }; + 38E2C5FE357F6FEDAEA675CA /* UTMScriptingSnapshotImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTMScriptingSnapshotImpl.swift; sourceTree = ""; }; 45D72A942B94CE89000D16E9 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/InfoPlist.strings; sourceTree = ""; }; 45D72A952B94CEE0000D16E9 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/InfoPlist.strings; sourceTree = ""; }; 45D72A962B94CEF5000D16E9 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = "pl.lproj/Info-RemotePlist.strings"; sourceTree = ""; }; 4B224B9C279D4D8100B63CFF /* InListButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InListButtonStyle.swift; sourceTree = ""; }; + 4F9F544E438D4BE8A5CA9360 /* UTMAppleVmnetNetworkManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTMAppleVmnetNetworkManager.swift; sourceTree = ""; }; 521F3EFA2414F73800130500 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; 5286EC8E2437488E007E6CBC /* VMDisplayMetalViewController+Gamepad.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "VMDisplayMetalViewController+Gamepad.h"; sourceTree = ""; }; 5286EC8F2437488E007E6CBC /* VMDisplayMetalViewController+Gamepad.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "VMDisplayMetalViewController+Gamepad.m"; sourceTree = ""; }; @@ -1751,7 +1803,6 @@ 836CA97E28FCC39700EB9EF0 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/InfoPlist.strings; sourceTree = ""; }; 83A004B826A8CC95001AC09E /* UTMDownloadTask.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTMDownloadTask.swift; sourceTree = ""; }; 83C15C5E26CC441000ADFD45 /* KeyCodeMap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyCodeMap.swift; sourceTree = ""; }; - A1C4F00130C1200000CA9501 /* CapsLockRemapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CapsLockRemapper.swift; sourceTree = ""; }; 83FBDD53242FA71900D2C5D7 /* VMDisplayMetalViewController+Pointer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "VMDisplayMetalViewController+Pointer.h"; sourceTree = ""; }; 83FBDD55242FA7BC00D2C5D7 /* VMDisplayMetalViewController+Pointer.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "VMDisplayMetalViewController+Pointer.m"; sourceTree = ""; }; 83FE63B728F617CE0047FFEF /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/VMDisplayWindow.strings; sourceTree = ""; }; @@ -1807,10 +1858,6 @@ 844EC0FA2773EE49003C104A /* UTMDownloadIPSWTask.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTMDownloadIPSWTask.swift; sourceTree = ""; }; 8453DCB0278CE33E0037A0DA /* qemu-img.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = "qemu-img.framework"; path = "$(SYSROOT_DIR)/Frameworks/qemu-img.framework"; sourceTree = ""; }; 8453DCB3278CE5410037A0DA /* UTMQemuImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTMQemuImage.swift; sourceTree = ""; }; - EFC4D9A586DD424D85B2FA1C /* UTMSnapshotService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTMSnapshotService.swift; sourceTree = ""; }; - F7335781A3EBC95B7BE01C90 /* UTMQemuSnapshotBackend.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTMQemuSnapshotBackend.swift; sourceTree = ""; }; - DE6FCF9E7BD060AB81411ABC /* UTMSnapshotBackend.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTMSnapshotBackend.swift; sourceTree = ""; }; - 0F9CF0C724EB2633D0B87ACE /* UTMSnapshotManifest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTMSnapshotManifest.swift; sourceTree = ""; }; 845F1704289B1EEB00944904 /* UTMAppleConfigurationGenericPlatform.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTMAppleConfigurationGenericPlatform.swift; sourceTree = ""; }; 845F1706289B5E2600944904 /* VMAppleSettingsAddDeviceMenuView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VMAppleSettingsAddDeviceMenuView.swift; sourceTree = ""; }; 845F1708289CA15C00944904 /* VMDisplayAppleTerminalWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VMDisplayAppleTerminalWindowController.swift; sourceTree = ""; }; @@ -1824,7 +1871,6 @@ 8471770527CC974F00D3A50B /* DefaultTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultTextField.swift; sourceTree = ""; }; 8471772727CD3CAB00D3A50B /* DetailedSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailedSection.swift; sourceTree = ""; }; 847BF9A92A49C783000BD9AA /* VMData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VMData.swift; sourceTree = ""; }; - 16001E3BC682D8470EFCF312 /* VMSnapshotData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VMSnapshotData.swift; sourceTree = ""; }; 84818C0B2898A07A009EDB67 /* AVFAudio.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFAudio.framework; path = System/Library/Frameworks/AVFAudio.framework; sourceTree = SDKROOT; }; 848308D4278A1F2200E3E474 /* Virtualization.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Virtualization.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.1.sdk/System/Library/Frameworks/Virtualization.framework; sourceTree = DEVELOPER_DIR; }; 848A98AF286A0F74006F0550 /* UTMAppleConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTMAppleConfiguration.swift; sourceTree = ""; }; @@ -1864,6 +1910,12 @@ 84A0A8812A47D51D0038F329 /* QEMUHelperDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = QEMUHelperDelegate.h; sourceTree = ""; }; 84A0A8822A47D52E0038F329 /* UTMQemuPort.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UTMQemuPort.swift; sourceTree = ""; }; 84A381A9268CB30C0048EE4D /* VMDrivesSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VMDrivesSettingsView.swift; sourceTree = ""; }; + 84A57CB130641C6800BA5139 /* iOSHelper.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.extensionkit-extension"; includeInIndex = 0; path = iOSHelper.appex; sourceTree = BUILT_PRODUCTS_DIR; }; + 84A57CBE30641C9100BA5139 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 84A57CBF30641C9100BA5139 /* iOSHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iOSHelper.swift; sourceTree = ""; }; + 84A57D0930641D0000BA5139 /* iOSHelper-SE.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.extensionkit-extension"; includeInIndex = 0; path = "iOSHelper-SE.appex"; sourceTree = BUILT_PRODUCTS_DIR; }; + 84A57D0C30641D0000BA5139 /* UTMHelperProcess.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTMHelperProcess.swift; sourceTree = ""; }; + 84A57D0F30641D0000BA5139 /* iOSHelper-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "iOSHelper-Bridging-Header.h"; sourceTree = ""; }; 84B36D2427B704C200C22685 /* UTMDownloadVMTask.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTMDownloadVMTask.swift; sourceTree = ""; }; 84B36D2827B790BE00C22685 /* DestructiveButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DestructiveButton.swift; sourceTree = ""; }; 84BB99392899E8D500DF28B2 /* VMHeadlessSessionState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VMHeadlessSessionState.swift; sourceTree = ""; }; @@ -1874,7 +1926,6 @@ 84C584DE268E95B3000FCABF /* UTMLegacyAppleConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTMLegacyAppleConfiguration.swift; sourceTree = ""; }; 84C584E2268F8AE7000FCABF /* VMQEMUSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VMQEMUSettingsView.swift; sourceTree = ""; }; 84C584E4268F8C65000FCABF /* VMAppleSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VMAppleSettingsView.swift; sourceTree = ""; }; - CE7B3A102EA1B00000C0FFEE /* MultilineTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultilineTextField.swift; sourceTree = ""; }; 84C584EA268FA6D1000FCABF /* VMConfigAppleSystemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VMConfigAppleSystemView.swift; sourceTree = ""; }; 84C60FB62681A41B00B58C00 /* VMToolbarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VMToolbarView.swift; sourceTree = ""; }; 84C60FB9268269D700B58C00 /* VMDisplayViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VMDisplayViewController.swift; sourceTree = ""; }; @@ -1894,6 +1945,8 @@ 84F909FE289488F90008DBE2 /* MenuLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuLabel.swift; sourceTree = ""; }; 85EC516327CC8C98004A51DE /* VMConfigAdvancedNetworkView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VMConfigAdvancedNetworkView.swift; sourceTree = ""; }; 9786BB59294056960032B858 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/InfoPlist.strings; sourceTree = ""; }; + A1C4F00130C1200000CA9501 /* CapsLockRemapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CapsLockRemapper.swift; sourceTree = ""; }; + B5BB911D9C7D6564198DAC87 /* VMSnapshotsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VMSnapshotsView.swift; sourceTree = ""; }; C03453AD2709E35100AD51AD /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/InfoPlist.strings"; sourceTree = ""; }; C03453AE2709E35100AD51AD /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/InfoPlist.strings"; sourceTree = ""; }; C03453AF2709E35100AD51AD /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/InfoPlist.strings"; sourceTree = ""; }; @@ -1930,6 +1983,8 @@ CE11C0392BD4656900E103A0 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Info-RemotePlist.strings"; sourceTree = ""; }; CE19392526DCB093005CEC17 /* RAMSlider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RAMSlider.swift; sourceTree = ""; }; CE1AEC3E2B78B30700992AFC /* MacDeviceLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MacDeviceLabel.swift; sourceTree = ""; }; + CE1D15AA2F6B100100D15C01 /* UTMAppleDiskImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTMAppleDiskImage.swift; sourceTree = ""; }; + CE1D15AC2F6B100100D15C01 /* DiskImageKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = DiskImageKit.framework; path = System/Library/Frameworks/DiskImageKit.framework; sourceTree = SDKROOT; }; CE20FAE62448D2BE0059AE11 /* VMScroll.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = VMScroll.h; sourceTree = ""; }; CE20FAE72448D2BE0059AE11 /* VMScroll.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = VMScroll.m; sourceTree = ""; }; CE231D412BDDF280006D6DC3 /* UTMDonateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTMDonateView.swift; sourceTree = ""; }; @@ -2017,7 +2072,6 @@ CE2D954224AD4F980059923A /* VMPlaceholderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VMPlaceholderView.swift; sourceTree = ""; }; CE2D954324AD4F980059923A /* VMCardView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VMCardView.swift; sourceTree = ""; }; CE2D954524AD4F980059923A /* VMRemovableDrivesView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VMRemovableDrivesView.swift; sourceTree = ""; }; - B5BB911D9C7D6564198DAC87 /* VMSnapshotsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VMSnapshotsView.swift; sourceTree = ""; }; CE2D954624AD4F980059923A /* UTMExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UTMExtensions.swift; sourceTree = ""; }; CE2D954724AD4F980059923A /* VMConfigSharingView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VMConfigSharingView.swift; sourceTree = ""; }; CE2D954824AD4F980059923A /* VMConfigInputView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VMConfigInputView.swift; sourceTree = ""; }; @@ -2057,9 +2111,9 @@ CE5451A426AF5F0F008594E5 /* EGL.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = EGL.framework; path = "$(SYSROOT_DIR)/Frameworks/EGL.framework"; sourceTree = ""; }; CE550BD52259479D0063E575 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; CE59A7AF2AA69AE400E5FFBD /* UTMScriptingUSBDeviceImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTMScriptingUSBDeviceImpl.swift; sourceTree = ""; }; + CE5A7E102E9F4A1100C0FFEE /* VMDisplayWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VMDisplayWindow.swift; sourceTree = ""; }; CE611BE629F50CAD001817BC /* UTMReleaseHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTMReleaseHelper.swift; sourceTree = ""; }; CE611BEA29F50D3E001817BC /* VMReleaseNotesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VMReleaseNotesView.swift; sourceTree = ""; }; - CE5A7E102E9F4A1100C0FFEE /* VMDisplayWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VMDisplayWindow.swift; sourceTree = ""; }; CE612AC524D3B50700FA6300 /* VMDisplayWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VMDisplayWindowController.swift; sourceTree = ""; }; CE63B0D72F0ED4FE00C59D13 /* vulkan_kosmickrisp.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = vulkan_kosmickrisp.framework; path = "$(SYSROOT_DIR)/Frameworks/vulkan_kosmickrisp.framework"; sourceTree = ""; }; CE63B0D82F0ED4FE00C59D13 /* vulkan.1.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = vulkan.1.framework; path = "$(SYSROOT_DIR)/Frameworks/vulkan.1.framework"; sourceTree = ""; }; @@ -2067,12 +2121,6 @@ CE63B0F02F0ED67700C59D13 /* vulkan */ = {isa = PBXFileReference; lastKnownFileType = text.json; name = vulkan; path = "$(SYSROOT_DIR)/share/vulkan"; sourceTree = ""; }; CE66450C2269313200B0849A /* MetalKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MetalKit.framework; path = System/Library/Frameworks/MetalKit.framework; sourceTree = SDKROOT; }; CE68047D2E493D71001671E9 /* UTMUSBManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTMUSBManager.swift; sourceTree = ""; }; - CE7A1C012EFA0A01001671E9 /* UTMAppleUSBDevice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTMAppleUSBDevice.swift; sourceTree = ""; }; - CE7A1C032EFA0A01001671E9 /* UTMAppleUSBManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTMAppleUSBManager.swift; sourceTree = ""; }; - CE7A1C052EFA0A01001671E9 /* AccessoryAccess.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AccessoryAccess.framework; path = System/Library/Frameworks/AccessoryAccess.framework; sourceTree = SDKROOT; }; - CE1D15AA2F6B100100D15C01 /* UTMAppleDiskImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTMAppleDiskImage.swift; sourceTree = ""; }; - 0CBB52BFA0D55FDEC6497878 /* UTMAppleSnapshotBackend.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTMAppleSnapshotBackend.swift; sourceTree = ""; }; - CE1D15AC2F6B100100D15C01 /* DiskImageKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = DiskImageKit.framework; path = System/Library/Frameworks/DiskImageKit.framework; sourceTree = SDKROOT; }; CE6804842E4E5D84001671E9 /* UTMScriptingInputImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTMScriptingInputImpl.swift; sourceTree = ""; }; CE68E5422E3912E0006B3645 /* VMKeyboardShortcutsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VMKeyboardShortcutsView.swift; sourceTree = ""; }; CE68E5472E3C3E0A006B3645 /* VMWizardOSClassicMacView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VMWizardOSClassicMacView.swift; sourceTree = ""; }; @@ -2090,6 +2138,10 @@ CE72B4AC2463579D00716A11 /* VMDisplayViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = VMDisplayViewController.m; sourceTree = ""; }; CE772AAB25C8B0F600E4E379 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; CE772AB225C8B7B500E4E379 /* VMCommands.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VMCommands.swift; sourceTree = ""; }; + CE7A1C012EFA0A01001671E9 /* UTMAppleUSBDevice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTMAppleUSBDevice.swift; sourceTree = ""; }; + CE7A1C032EFA0A01001671E9 /* UTMAppleUSBManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTMAppleUSBManager.swift; sourceTree = ""; }; + CE7A1C052EFA0A01001671E9 /* AccessoryAccess.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AccessoryAccess.framework; path = System/Library/Frameworks/AccessoryAccess.framework; sourceTree = SDKROOT; }; + CE7B3A102EA1B00000C0FFEE /* MultilineTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultilineTextField.swift; sourceTree = ""; }; CE7D972B24B2B17D0080CB69 /* BusyOverlay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BusyOverlay.swift; sourceTree = ""; }; CE80111F2AD4E9E8009001C2 /* UTMApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTMApp.swift; sourceTree = ""; }; CE8813D224CD230300532628 /* ActivityView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityView.swift; sourceTree = ""; }; @@ -2104,7 +2156,6 @@ CE88A1632E24E4C000EAA28E /* VMKeyboardMap.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = VMKeyboardMap.h; sourceTree = ""; }; CE88A1642E24E4C000EAA28E /* VMKeyboardMap.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = VMKeyboardMap.m; sourceTree = ""; }; CE928C2926ABE6690099F293 /* UTMAppleVirtualMachine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTMAppleVirtualMachine.swift; sourceTree = ""; }; - 4F9F544E438D4BE8A5CA9360 /* UTMAppleVmnetNetworkManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTMAppleVmnetNetworkManager.swift; sourceTree = ""; }; CE928C3026ACCDEA0099F293 /* VMAppleRemovableDrivesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VMAppleRemovableDrivesView.swift; sourceTree = ""; }; CE9375A024BBDDD10074066F /* VMConfigDriveDetailsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VMConfigDriveDetailsView.swift; sourceTree = ""; }; CE95877426D74C2A0086BDE8 /* iOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = iOS.entitlements; sourceTree = ""; }; @@ -2177,7 +2228,6 @@ CEC0A3092A7490D200980857 /* VMConfigQEMUArgumentsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VMConfigQEMUArgumentsView.swift; sourceTree = ""; }; CEC1B00A2BBB211C0088119D /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; CEC794B9294924E300121A9F /* UTMScriptingSerialPortImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTMScriptingSerialPortImpl.swift; sourceTree = ""; }; - 38E2C5FE357F6FEDAEA675CA /* UTMScriptingSnapshotImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTMScriptingSnapshotImpl.swift; sourceTree = ""; }; CEC794BB2949663C00121A9F /* UTMScripting.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UTMScripting.swift; sourceTree = ""; }; CEC9968328AA516000E7A025 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = ja; path = ja.lproj/Localizable.stringsdict; sourceTree = ""; }; CECF02562B706ADD00409FC0 /* UTMRemoteConnectInterface.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = UTMRemoteConnectInterface.h; sourceTree = ""; }; @@ -2204,7 +2254,6 @@ CEE7E935287CFDB100282049 /* UTMLegacyQemuConfiguration+Constants.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UTMLegacyQemuConfiguration+Constants.h"; sourceTree = ""; }; CEE7ED472A90256100E6B4AB /* VMDisplayMetalViewController+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "VMDisplayMetalViewController+Private.h"; sourceTree = ""; }; CEE8B4C12B71DF4C0035AE86 /* UTMQemuSystemBackends.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = UTMQemuSystemBackends.h; sourceTree = ""; }; - DB38A22C8D69DB2D2C64107F /* VMKeyboardAccessoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VMKeyboardAccessoryView.swift; sourceTree = ""; }; CEEB66452284B942002737B2 /* VMKeyboardButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VMKeyboardButton.swift; sourceTree = ""; }; CEEC811A24E48EC600ACB0B3 /* SettingsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; CEECE13B25E47D9500A2AAB8 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; @@ -2232,6 +2281,8 @@ CEFE96762B69A7CC000F00C9 /* VMRemoteSessionState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VMRemoteSessionState.swift; sourceTree = ""; }; CEFE98DE29485237007CB7A8 /* UTM.sdef */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = UTM.sdef; sourceTree = ""; }; CEFE98E029485776007CB7A8 /* UTMScriptingVirtualMachineImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTMScriptingVirtualMachineImpl.swift; sourceTree = ""; }; + DB38A22C8D69DB2D2C64107F /* VMKeyboardAccessoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VMKeyboardAccessoryView.swift; sourceTree = ""; }; + DE6FCF9E7BD060AB81411ABC /* UTMSnapshotBackend.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTMSnapshotBackend.swift; sourceTree = ""; }; E2D64BC7241DB24B0034E0C6 /* UTMSpiceIO.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = UTMSpiceIO.h; sourceTree = ""; }; E2D64BC8241DB24B0034E0C6 /* UTMSpiceIO.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = UTMSpiceIO.m; sourceTree = ""; }; E2D64BE0241EAEBE0034E0C6 /* UTMSpiceIODelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = UTMSpiceIODelegate.h; sourceTree = ""; }; @@ -2249,6 +2300,7 @@ E68D492228AC018E00D34C54 /* es-419 */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "es-419"; path = "es-419.lproj/Localizable.strings"; sourceTree = ""; }; E68D492328AC018E00D34C54 /* es-419 */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "es-419"; path = "es-419.lproj/InfoPlist.strings"; sourceTree = ""; }; E6F791192903EEC6000BAAC9 /* es-419 */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "es-419"; path = "es-419.lproj/InfoPlist.strings"; sourceTree = ""; }; + EFC4D9A586DD424D85B2FA1C /* UTMSnapshotService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTMSnapshotService.swift; sourceTree = ""; }; F6DA2DA62AAFED5F0070DCD1 /* zh-HK */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-HK"; path = "zh-HK.lproj/VMDisplayWindow.strings"; sourceTree = ""; }; F6DA2DA72AAFED5F0070DCD1 /* zh-HK */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-HK"; path = "zh-HK.lproj/InfoPlist.strings"; sourceTree = ""; }; F6DA2DA82AAFED5F0070DCD1 /* zh-HK */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-HK"; path = "zh-HK.lproj/InfoPlist.strings"; sourceTree = ""; }; @@ -2260,15 +2312,15 @@ F6DA2DAF2AAFEE060070DCD1 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/VMDisplayWindow.strings"; sourceTree = ""; }; F6DA2DB02AAFF0640070DCD1 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/QEMULauncher-InfoPlist.strings"; sourceTree = ""; }; F6DA2DB12AAFF0640070DCD1 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.stringsdict"; sourceTree = ""; }; + F7335781A3EBC95B7BE01C90 /* UTMQemuSnapshotBackend.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTMQemuSnapshotBackend.swift; sourceTree = ""; }; FF0307542A84E3B70049979B /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/QEMULauncher-InfoPlist.strings"; sourceTree = ""; }; FF0307562A84E3B70049979B /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "zh-Hant"; path = "zh-Hant.lproj/Localizable.stringsdict"; sourceTree = ""; }; FF0307582A84E3B70049979B /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/VMDisplayWindow.strings"; sourceTree = ""; }; + FF2D14863C5CDE332BC8698C /* VMConfigQEMUArgumentsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VMConfigQEMUArgumentsView.swift; sourceTree = ""; }; FFB02A8B266CB09C006CD71A /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/InfoPlist.strings"; sourceTree = ""; }; FFB02A8F266CB09C006CD71A /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/InfoPlist.strings"; sourceTree = ""; }; FFB02A92266CB09C006CD71A /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/InfoPlist.strings"; sourceTree = ""; }; FFB02A95266CB09C006CD71A /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/Localizable.strings"; sourceTree = ""; }; - FF2D14863C5CDE332BC8698C /* VMConfigQEMUArgumentsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VMConfigQEMUArgumentsView.swift; sourceTree = ""; }; - 03AEFABA6F4DF195E96AD2D5 /* VMConfigQEMUCommandLineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VMConfigQEMUCommandLineView.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -2279,6 +2331,20 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 84A57CAE30641C6800BA5139 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 84A57D0630641D0000BA5139 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 84E3A8ED293DB37E0024A740 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -2660,6 +2726,16 @@ path = visionOS; sourceTree = ""; }; + 84A57CC030641C9100BA5139 /* iOSHelper */ = { + isa = PBXGroup; + children = ( + 84A57CBE30641C9100BA5139 /* Info.plist */, + 84A57CBF30641C9100BA5139 /* iOSHelper.swift */, + 84A57D0F30641D0000BA5139 /* iOSHelper-Bridging-Header.h */, + ); + path = iOSHelper; + sourceTree = ""; + }; 84E3A8F1293DB37E0024A740 /* utmctl */ = { isa = PBXGroup; children = ( @@ -2979,6 +3055,7 @@ CEFE98DD2948518D007CB7A8 /* Scripting */, 84E3A8F1293DB37E0024A740 /* utmctl */, CE88A0A12E23201100EAA28E /* Intents */, + 84A57CC030641C9100BA5139 /* iOSHelper */, CE550BCA225947990063E575 /* Products */, CE2D63D622653C7300FC7E63 /* Frameworks */, ); @@ -2997,6 +3074,8 @@ 84E3A8F0293DB37E0024A740 /* utmctl */, CEF7F6D32AEEDCC400E34952 /* UTM Remote.app */, CEA3EDFA30168B9000AAA8E4 /* QEMURenderServer.app */, + 84A57CB130641C6800BA5139 /* iOSHelper.appex */, + 84A57D0930641D0000BA5139 /* iOSHelper-SE.appex */, ); name = Products; sourceTree = ""; @@ -3021,6 +3100,7 @@ CE9D197A226542FE00355E14 /* UTMProcess.h */, CE9D197B226542FE00355E14 /* UTMProcess.m */, 8453DCB3278CE5410037A0DA /* UTMQemuImage.swift */, + 84A57D0C30641D0000BA5139 /* UTMHelperProcess.swift */, EFC4D9A586DD424D85B2FA1C /* UTMSnapshotService.swift */, F7335781A3EBC95B7BE01C90 /* UTMQemuSnapshotBackend.swift */, DE6FCF9E7BD060AB81411ABC /* UTMSnapshotBackend.swift */, @@ -3286,6 +3366,44 @@ productReference = 8401FD62269BE9C500265F0D /* QEMULauncher.app */; productType = "com.apple.product-type.application"; }; + 84A57CB030641C6800BA5139 /* iOSHelper */ = { + isa = PBXNativeTarget; + buildConfigurationList = 84A57CBD30641C6800BA5139 /* Build configuration list for PBXNativeTarget "iOSHelper" */; + buildPhases = ( + 84A57CAD30641C6800BA5139 /* Sources */, + 84A57CAE30641C6800BA5139 /* Frameworks */, + 84A57CAF30641C6800BA5139 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = iOSHelper; + packageProductDependencies = ( + ); + productName = iOSHelper; + productReference = 84A57CB130641C6800BA5139 /* iOSHelper.appex */; + productType = "com.apple.product-type.extensionkit-extension"; + }; + 84A57D0130641D0000BA5139 /* iOSHelper-SE */ = { + isa = PBXNativeTarget; + buildConfigurationList = 84A57D0230641D0000BA5139 /* Build configuration list for PBXNativeTarget "iOSHelper-SE" */; + buildPhases = ( + 84A57D0530641D0000BA5139 /* Sources */, + 84A57D0630641D0000BA5139 /* Frameworks */, + 84A57D0730641D0000BA5139 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "iOSHelper-SE"; + packageProductDependencies = ( + ); + productName = "iOSHelper-SE"; + productReference = 84A57D0930641D0000BA5139 /* iOSHelper-SE.appex */; + productType = "com.apple.product-type.extensionkit-extension"; + }; 84E3A8EF293DB37E0024A740 /* utmctl */ = { isa = PBXNativeTarget; buildConfigurationList = 84E3A8F6293DB37E0024A740 /* Build configuration list for PBXNativeTarget "utmctl" */; @@ -3315,11 +3433,13 @@ CE2D936924AD46670059923A /* Resources */, CE59A7B12ABCC75F00E5FFBD /* Patch Settings bundle */, CE2D937524AD46670059923A /* Embed Libraries */, + 84A57CB930641C6800BA5139 /* Embed ExtensionKit Extensions */, ); buildRules = ( ); dependencies = ( CE9A353326533A52005077CF /* PBXTargetDependency */, + 84A57CB730641C6800BA5139 /* PBXTargetDependency */, ); name = iOS; packageProductDependencies = ( @@ -3419,10 +3539,12 @@ CEA45F63263519B5002FA97D /* Resources */, CE59A7B22ABCCB7C00E5FFBD /* Patch Settings bundle */, CEA45F71263519B5002FA97D /* Embed Libraries */, + 84A57CC630641CCE00BA5139 /* Embed ExtensionKit Extensions */, ); buildRules = ( ); dependencies = ( + 84A57CC530641CCE00BA5139 /* PBXTargetDependency */, ); name = "iOS-SE"; packageProductDependencies = ( @@ -3500,13 +3622,19 @@ isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = YES; - LastSwiftUpdateCheck = 1410; + LastSwiftUpdateCheck = 2700; LastUpgradeCheck = 1500; ORGANIZATIONNAME = "Turing Software, LLC"; TargetAttributes = { 8401FD61269BE9C500265F0D = { CreatedOnToolsVersion = 12.4; }; + 84A57CB030641C6800BA5139 = { + CreatedOnToolsVersion = 27.0; + }; + 84A57D0130641D0000BA5139 = { + CreatedOnToolsVersion = 27.0; + }; 84E3A8EF293DB37E0024A740 = { CreatedOnToolsVersion = 14.1; LastSwiftMigration = 1410; @@ -3577,6 +3705,8 @@ CEA3EDEF30168B9000AAA8E4 /* QEMURenderServer */, CE9A352C26533A51005077CF /* JailbreakInterposer */, 84E3A8EF293DB37E0024A740 /* utmctl */, + 84A57CB030641C6800BA5139 /* iOSHelper */, + 84A57D0130641D0000BA5139 /* iOSHelper-SE */, ); }; /* End PBXProject section */ @@ -3591,6 +3721,20 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 84A57CAF30641C6800BA5139 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 84A57D0730641D0000BA5139 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; CE2D936924AD46670059923A /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -3835,6 +3979,22 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 84A57CAD30641C6800BA5139 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 84A57CC230641C9100BA5139 /* iOSHelper.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 84A57D0530641D0000BA5139 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 84A57D0830641D0000BA5139 /* iOSHelper.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 84E3A8EC293DB37E0024A740 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -3850,6 +4010,7 @@ files = ( 7591FABAADBC2544E9F0F37B /* VMConfigQEMUCommandLineView.swift in Sources */, 7EFF134EA0581C7A4CBBBAF5 /* VMConfigQEMUArgumentsView.swift in Sources */, + 84A57D0D30641D0000BA5139 /* UTMHelperProcess.swift in Sources */, CE2D926A24AD46670059923A /* VMDisplayMetalViewController+Pointer.h in Sources */, CE231D462BDDFD03006D6DC3 /* UTMDonateStore.swift in Sources */, 84C4D9022880CA8A00EC3B2B /* VMSettingsAddDeviceMenuView.swift in Sources */, @@ -4267,6 +4428,7 @@ files = ( 31D9CB9DD8370CF0CCCDE85C /* VMConfigQEMUCommandLineView.swift in Sources */, B56B01FE103C463CC3F84753 /* VMConfigQEMUArgumentsView.swift in Sources */, + 84A57D0E30641D0000BA5139 /* UTMHelperProcess.swift in Sources */, CEA45E25263519B5002FA97D /* VMDisplayMetalViewController+Pointer.h in Sources */, 84C505AD28C588EC007CE8FF /* SizeTextField.swift in Sources */, CEA45E27263519B5002FA97D /* VMRemovableDrivesView.swift in Sources */, @@ -4621,6 +4783,16 @@ target = 8401FD61269BE9C500265F0D /* QEMULauncher */; targetProxy = 8401FD7B269BECF200265F0D /* PBXContainerItemProxy */; }; + 84A57CB730641C6800BA5139 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 84A57CB030641C6800BA5139 /* iOSHelper */; + targetProxy = 84A57CB630641C6800BA5139 /* PBXContainerItemProxy */; + }; + 84A57CC530641CCE00BA5139 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 84A57D0130641D0000BA5139 /* iOSHelper-SE */; + targetProxy = 84A57CC430641CCE00BA5139 /* PBXContainerItemProxy */; + }; 84E3A8FD293DB7720024A740 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 84E3A8EF293DB37E0024A740 /* utmctl */; @@ -4871,6 +5043,158 @@ }; name = Release; }; + 84A57CBA30641C6800BA5139 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CODE_SIGN_IDENTITY = "$(CODE_SIGN_IDENTITY_IOS:default=Apple Development)"; + CODE_SIGN_STYLE = "$(CODE_SIGN_STYLE_IOS:default=)"; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = iOSHelper/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = iOSHelper; + INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2026 Turing Software, LLC. All rights reserved."; + IPHONEOS_DEPLOYMENT_TARGET = 26.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + PRODUCT_BUNDLE_IDENTIFIER = "$(UTM_HOST_BUNDLE_IDENTIFIER).iOSHelper"; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = "$(PROVISIONING_PROFILE_SPECIFIER_IOS_HELPER:default=)"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + STRING_CATALOG_GENERATE_SYMBOLS = YES; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator xros xrsimulator"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OBJC_BRIDGING_HEADER = "iOSHelper/iOSHelper-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2,7"; + UTM_HOST_BUNDLE_IDENTIFIER = "$(PRODUCT_BUNDLE_PREFIX:default=com.utmapp).UTM"; + XROS_DEPLOYMENT_TARGET = 26.0; + }; + name = Debug; + }; + 84A57CBB30641C6800BA5139 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CODE_SIGN_IDENTITY = "$(CODE_SIGN_IDENTITY_IOS:default=Apple Development)"; + CODE_SIGN_STYLE = "$(CODE_SIGN_STYLE_IOS:default=)"; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = iOSHelper/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = iOSHelper; + INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2026 Turing Software, LLC. All rights reserved."; + IPHONEOS_DEPLOYMENT_TARGET = 26.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + PRODUCT_BUNDLE_IDENTIFIER = "$(UTM_HOST_BUNDLE_IDENTIFIER).iOSHelper"; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = "$(PROVISIONING_PROFILE_SPECIFIER_IOS_HELPER:default=)"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + STRING_CATALOG_GENERATE_SYMBOLS = YES; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator xros xrsimulator"; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OBJC_BRIDGING_HEADER = "iOSHelper/iOSHelper-Bridging-Header.h"; + SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2,7"; + UTM_HOST_BUNDLE_IDENTIFIER = "$(PRODUCT_BUNDLE_PREFIX:default=com.utmapp).UTM"; + XROS_DEPLOYMENT_TARGET = 26.0; + }; + name = Release; + }; + 84A57D0330641D0000BA5139 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CODE_SIGN_IDENTITY = "$(CODE_SIGN_IDENTITY_IOS:default=Apple Development)"; + CODE_SIGN_STYLE = "$(CODE_SIGN_STYLE_IOS:default=)"; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = iOSHelper/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = iOSHelper; + INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2026 Turing Software, LLC. All rights reserved."; + IPHONEOS_DEPLOYMENT_TARGET = 26.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + PRODUCT_BUNDLE_IDENTIFIER = "$(UTM_HOST_BUNDLE_IDENTIFIER).iOSHelper"; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = "$(PROVISIONING_PROFILE_SPECIFIER_SE_HELPER:default=)"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + STRING_CATALOG_GENERATE_SYMBOLS = YES; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator xros xrsimulator"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OBJC_BRIDGING_HEADER = "iOSHelper/iOSHelper-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2,7"; + UTM_HOST_BUNDLE_IDENTIFIER = "$(PRODUCT_BUNDLE_PREFIX:default=com.utmapp).UTM-SE"; + XROS_DEPLOYMENT_TARGET = 26.0; + }; + name = Debug; + }; + 84A57D0430641D0000BA5139 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CODE_SIGN_IDENTITY = "$(CODE_SIGN_IDENTITY_IOS:default=Apple Development)"; + CODE_SIGN_STYLE = "$(CODE_SIGN_STYLE_IOS:default=)"; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = iOSHelper/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = iOSHelper; + INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2026 Turing Software, LLC. All rights reserved."; + IPHONEOS_DEPLOYMENT_TARGET = 26.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + PRODUCT_BUNDLE_IDENTIFIER = "$(UTM_HOST_BUNDLE_IDENTIFIER).iOSHelper"; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = "$(PROVISIONING_PROFILE_SPECIFIER_SE_HELPER:default=)"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + STRING_CATALOG_GENERATE_SYMBOLS = YES; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator xros xrsimulator"; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OBJC_BRIDGING_HEADER = "iOSHelper/iOSHelper-Bridging-Header.h"; + SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2,7"; + UTM_HOST_BUNDLE_IDENTIFIER = "$(PRODUCT_BUNDLE_PREFIX:default=com.utmapp).UTM-SE"; + XROS_DEPLOYMENT_TARGET = 26.0; + }; + name = Release; + }; 84E3A8F4293DB37E0024A740 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -4921,6 +5245,7 @@ CODE_SIGN_STYLE = "$(CODE_SIGN_STYLE_IOS:default=)"; ENABLE_BITCODE = NO; ENABLE_PREVIEWS = YES; + EX_ENABLE_EXTENSION_POINT_GENERATION = YES; GCC_PREPROCESSOR_DEFINITIONS = ( "WITH_JIT=1", "WITH_SOLO_VM=1", @@ -4959,6 +5284,7 @@ CODE_SIGN_STYLE = "$(CODE_SIGN_STYLE_IOS:default=)"; ENABLE_BITCODE = NO; ENABLE_PREVIEWS = YES; + EX_ENABLE_EXTENSION_POINT_GENERATION = YES; GCC_PREPROCESSOR_DEFINITIONS = ( "WITH_JIT=1", "WITH_SOLO_VM=1", @@ -5331,6 +5657,7 @@ CODE_SIGN_STYLE = "$(CODE_SIGN_STYLE_IOS:default=)"; ENABLE_BITCODE = NO; ENABLE_PREVIEWS = YES; + EX_ENABLE_EXTENSION_POINT_GENERATION = YES; GCC_PREPROCESSOR_DEFINITIONS = ( "WITH_QEMU_TCI=1", "WITH_SOLO_VM=1", @@ -5370,6 +5697,7 @@ CODE_SIGN_STYLE = "$(CODE_SIGN_STYLE_IOS:default=)"; ENABLE_BITCODE = NO; ENABLE_PREVIEWS = YES; + EX_ENABLE_EXTENSION_POINT_GENERATION = YES; GCC_PREPROCESSOR_DEFINITIONS = ( "WITH_QEMU_TCI=1", "WITH_SOLO_VM=1", @@ -5521,6 +5849,24 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + 84A57CBD30641C6800BA5139 /* Build configuration list for PBXNativeTarget "iOSHelper" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 84A57CBA30641C6800BA5139 /* Debug */, + 84A57CBB30641C6800BA5139 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 84A57D0230641D0000BA5139 /* Build configuration list for PBXNativeTarget "iOSHelper-SE" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 84A57D0330641D0000BA5139 /* Debug */, + 84A57D0430641D0000BA5139 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 84E3A8F6293DB37E0024A740 /* Build configuration list for PBXNativeTarget "utmctl" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/iOSHelper/Info.plist b/iOSHelper/Info.plist new file mode 100644 index 00000000..b53d45d1 --- /dev/null +++ b/iOSHelper/Info.plist @@ -0,0 +1,11 @@ + + + + + EXAppExtensionAttributes + + EXExtensionPointIdentifier + $(UTM_HOST_BUNDLE_IDENTIFIER).qemu-helper + + + diff --git a/iOSHelper/iOSHelper-Bridging-Header.h b/iOSHelper/iOSHelper-Bridging-Header.h new file mode 100644 index 00000000..8441e7aa --- /dev/null +++ b/iOSHelper/iOSHelper-Bridging-Header.h @@ -0,0 +1,18 @@ +// +// Copyright © 2026 Turing Software, LLC. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "QEMUHelperProtocol.h" +#include "QEMUHelperDelegate.h" diff --git a/iOSHelper/iOSHelper.swift b/iOSHelper/iOSHelper.swift new file mode 100644 index 00000000..5532db03 --- /dev/null +++ b/iOSHelper/iOSHelper.swift @@ -0,0 +1,170 @@ +// +// Copyright © 2026 Turing Software, LLC. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import ExtensionFoundation + +/// Runs a QEMU tool such as qemu-img for the app, out of the app's process. +/// +/// Like the macOS `QEMUHelper`, this speaks `QEMUHelperProtocol` and reaches the app's files +/// through the bookmarks it is passed, but QEMU is loaded into the helper itself as iOS cannot +/// spawn processes. A QEMU tool can run once per process, so the helper exits when its +/// connection ends and the app launches a new one for the next tool. +@main +final class iOSHelper: AppExtension { + required init() { + } + + var configuration: some AppExtensionConfiguration { + ConnectionHandler { connection in + connection.exportedInterface = NSXPCInterface(with: QEMUHelperProtocol.self) + connection.exportedObject = QEMUHelperService(connection: connection) + connection.remoteObjectInterface = NSXPCInterface(with: QEMUHelperDelegate.self) + connection.invalidationHandler = { + _exit(0) + } + connection.resume() + return true + } + } +} + +final class QEMUHelperService: NSObject, QEMUHelperProtocol { + typealias MainFunction = @convention(c) (Int32, UnsafeMutablePointer?>?) -> Int32 + + var environment: [String: String]? + var currentDirectoryPath: String? + + private weak var connection: NSXPCConnection? + private var urls = [URL]() + private var activeToken: tokenCallback_t? + private var isStarted = false + + init(connection: NSXPCConnection) { + self.connection = connection + } + + func accessData(withBookmark bookmark: Data, securityScoped: Bool, completion: @escaping (Bool, Data?, String?) -> Void) { + var isStale = false + let url: URL + do { + url = try URL(resolvingBookmarkData: bookmark, bookmarkDataIsStale: &isStale) + } catch { + NSLog("Failed to access bookmark data: \(error)") + completion(false, nil, nil) + return + } + if url.startAccessingSecurityScopedResource() { + urls.append(url) + } else { + NSLog("Failed to access security scoped resource for: \(url)") + } + completion(true, bookmark, url.path) + } + + func stopAccessingPath(_ path: String?) { + guard let path = path, let index = urls.firstIndex(where: { $0.path == path }) else { + return + } + urls[index].stopAccessingSecurityScopedResource() + urls.remove(at: index) + } + + func startQemu(_ binName: String, standardOutput: FileHandle, standardError: FileHandle, libraryBookmark libBookmark: Data, argv: [String], completion: @escaping (Bool, String?) -> Void) { + guard !isStarted else { + completion(false, NSLocalizedString("The helper is already in use.", comment: "iOSHelper")) + return + } + isStarted = true + var isStale = false + guard let libraryURL = try? URL(resolvingBookmarkData: libBookmark, bookmarkDataIsStale: &isStale), + FileManager.default.fileExists(atPath: libraryURL.path) else { + NSLog("Cannot resolve library path") + completion(false, NSLocalizedString("Cannot find QEMU support libraries.", comment: "iOSHelper")) + return + } + // the tool writes to the app's pipes as if it were a process of its own + dup2(standardOutput.fileDescriptor, STDOUT_FILENO) + dup2(standardError.fileDescriptor, STDERR_FILENO) + try? standardOutput.close() + try? standardError.close() + let dylibURL = libraryURL.appendingPathComponent(binName) + let thread = Thread { [self] in + runQemu(dylib: dylibURL, name: dylibURL.lastPathComponent, argv: argv) + } + thread.qualityOfService = .userInitiated + thread.start() + completion(true, nil) + } + + func terminate() { + _exit(0) + } + + func assertActive(token: @escaping tokenCallback_t) { + activeToken?(false) + activeToken = token + } + + private func runQemu(dylib: URL, name: String, argv: [String]) { + guard let handle = dlopen(dylib.path, RTLD_LOCAL | RTLD_FIRST) else { + processHasExited(-1, message: String(cString: dlerror())) + return + } + guard let symbol = dlsym(handle, "main") else { + processHasExited(-1, message: String(cString: dlerror())) + return + } + let main = unsafeBitCast(symbol, to: MainFunction.self) + for (key, value) in environment ?? [:] { + setenv(key, value, 1) + } + setenv("TMPDIR", FileManager.default.temporaryDirectory.path, 1) + if let currentDirectoryPath = currentDirectoryPath { + chdir(currentDirectoryPath) + } + // a tool that calls exit() is reported as a failure instead of taking the helper down silently + let qemuThread = pthread_self() + atexit_b { [self] in + if pthread_equal(pthread_self(), qemuThread) != 0 { + endOutput() + processHasExited(-1, message: nil) + pthread_exit(nil) + } + } + var cArgv = ([name] + argv).map { strdup($0) } + [nil] + let status = main(Int32(argv.count + 1), &cArgv) + endOutput() + processHasExited(Int(status), message: nil) + } + + /// Gives the app's pipes back so it sees the end of the output + private func endOutput() { + fflush(nil) + let null = open("/dev/null", O_WRONLY) + if null >= 0 { + dup2(null, STDOUT_FILENO) + dup2(null, STDERR_FILENO) + close(null) + } + } + + private func processHasExited(_ exitCode: Int, message: String?) { + (connection?.remoteObjectProxy as? QEMUHelperDelegate)?.processHasExited(exitCode, message: message) + activeToken?(true) + activeToken = nil + } +} diff --git a/scripts/package.sh b/scripts/package.sh index 64af72e6..73616856 100755 --- a/scripts/package.sh +++ b/scripts/package.sh @@ -8,7 +8,7 @@ command -v realpath >/dev/null 2>&1 || realpath() { BASEDIR="$(dirname "$(realpath $0)")" usage() { - echo "usage: $0 MODE inputXcarchive outputPath [TEAM_ID PROFILE_NAME SIGNING_METHOD]" + echo "usage: $0 MODE inputXcarchive outputPath [TEAM_ID PROFILE_NAME SIGNING_METHOD HELPER_PROFILE_NAME]" echo " MODE is one of:" echo " deb (Cydia DEB)" echo " ipa (unsigned IPA of full build with all entitlements)" @@ -21,6 +21,7 @@ usage() { echo " TEAM_ID is only used for ipa-signed and is the name of the team matching the profile" echo " PROFILE_NAME is only used for ipa-signed and is the name of the signing profile" echo " SIGNING_METHOD is only used for ipa-signed and is either 'development' (default) or 'app-store'" + echo " HELPER_PROFILE_NAME is only used for ipa-signed and ipa-se-signed and is the name of the signing profile of the helper extension" exit 1 } @@ -70,7 +71,13 @@ itunes_sign() { local TEAM_ID=$3 local PROFILE_NAME=$4 local SIGNING_METHOD=$5 + local HELPER_PROFILE_NAME=$6 local OPTIONS="/tmp/options.$$.plist" + local HELPER_PROFILE= + + if [ ! -z "$HELPER_PROFILE_NAME" ]; then + HELPER_PROFILE="${BUNDLE_ID}.iOSHelper${HELPER_PROFILE_NAME}" + fi if [ -z "$PROFILE_NAME" -o -z "$TEAM_ID" ]; then echo "Invalid profile name or team id!" @@ -93,6 +100,7 @@ itunes_sign() { ${BUNDLE_ID} ${PROFILE_NAME} + ${HELPER_PROFILE} signingStyle manual @@ -120,6 +128,7 @@ fake_sign() { mkdir -p "$_output" cp -a "$_input" "$_output/" find "$_output" -type d -path '*/Frameworks/*.framework' -exec ldid -S \{\} \; + find "$_output" -type d -path '*/Extensions/*.appex' -exec ldid -S \{\} \; if [ ! -z "${_fakeent}" ]; then ldid -S${_fakeent} -I${_bundle_id} "$_output/Applications/$_name.app/$_name" fi @@ -351,7 +360,7 @@ EOL if [ "$PLATFORM" != "xros" ]; then codesign --force --sign - --entitlements "$FAKEENT" --timestamp=none "$BUILT_PATH" fi - itunes_sign "$TMPINPUT" "$OUTPUT" $4 $5 $6 + itunes_sign "$TMPINPUT" "$OUTPUT" $4 $5 $6 $7 rm -rf "$TMPINPUT" ;; ipa-remote-signed )