diff --git a/Configuration/UTMAppleConfigurationDrive.swift b/Configuration/UTMAppleConfigurationDrive.swift index 1ebd9fb30..0758882f1 100644 --- a/Configuration/UTMAppleConfigurationDrive.swift +++ b/Configuration/UTMAppleConfigurationDrive.swift @@ -48,7 +48,7 @@ struct UTMAppleConfigurationDrive: UTMConfigurationDrive { } else { sizeBytes = Int64(sizeMib) * Int64(bytesInMib) } - return ByteCountFormatter.string(fromByteCount: sizeBytes, countStyle: .file) + return ByteCountFormatter.string(fromByteCount: sizeBytes, countStyle: .binary) } init(newSize: Int) { diff --git a/Documentation/MacDevelopment.md b/Documentation/MacDevelopment.md index 5351be828..9d923163d 100644 --- a/Documentation/MacDevelopment.md +++ b/Documentation/MacDevelopment.md @@ -5,11 +5,14 @@ Because UTM is a sand-boxed Mac app, there are a few extra steps needed for a pr ## Getting the Source Make sure you perform a recursive clone to get all the submodules: -``` +```sh git clone --recursive https://github.com/utmapp/UTM.git ``` -Alternatively, run `git submodule update --init --recursive` after cloning if you did not do a recursive clone. +Alternatively, run the following after cloning if you did not do a recursive clone. +```sh +git submodule update --init --recursive +``` ## Dependencies @@ -21,15 +24,28 @@ If you want to build the dependencies yourself, it is highly recommended that yo 1. Install Xcode command line and [Homebrew][1] 2. Install the following build prerequisites - `brew install bison pkg-config gettext glib-utils libgpg-error nasm meson` - `pip3 install six pyparsing` - Make sure to add `bison` to your `$PATH` environment variable! - `export PATH=/usr/local/opt/bison/bin:/opt/homebrew/opt/bison/bin:$PATH` -3. Run `./scripts/build_dependencies.sh -p macos -a ARCH` where `ARCH` is either `arm64` or `x86_64`. + ```sh + brew install bison pkg-config gettext glib-utils libgpg-error nasm meson + ``` + + ```sh + pip3 install six pyparsing + ``` + + Make sure to add `bison` to your `$PATH` environment variable! + + ```sh + export PATH=/usr/local/opt/bison/bin:/opt/homebrew/opt/bison/bin:$PATH + ``` +3. Run + ```sh + ./scripts/build_dependencies.sh -p macos -a ARCH + ``` + where `ARCH` is either `arm64` or `x86_64`. If you want to build universal binaries, you need to run `build_dependencies.sh` for both `arm64` and `x86_64` and then run -``` +```sh ./scripts/pack_dependencies.sh . macos arm64 x86_64 ``` @@ -41,7 +57,7 @@ If you are developing QEMU and wish to pass in a custom path to QEMU, you can us You can build UTM with the script: -``` +```sh ./scripts/build_utm.sh -t TEAMID -p macos -a ARCH -o /path/to/output/directory ``` @@ -55,7 +71,7 @@ Artifacts built with `build_utm.sh` (includes GitHub Actions artifacts) must be #### Unsigned packages -``` +```sh ./scripts/package_mac.sh unsigned /path/to/UTM.xcarchive /path/to/output ``` @@ -63,7 +79,7 @@ This builds `UTM.dmg` in `/path/to/output` which can be installed to `/Applicati #### Signed packages -``` +```sh ./scripts/package_mac.sh developer-id /path/to/UTM.xcarchive /path/to/output TEAM_ID PROFILE_UUID HELPER_PROFILE_UUID LAUNCHER_PROFILE_UUID ``` @@ -73,7 +89,7 @@ Once properly signed, you can ask Apple to notarize the DMG. #### Mac App Store -``` +```sh ./scripts/package_mac.sh app-store /path/to/UTM.xcarchive /path/to/output TEAM_ID PROFILE_UUID HELPER_PROFILE_UUID LAUNCHER_PROFILE_UUID ``` diff --git a/Icons/backtrack.png b/Icons/backtrack.png index 0fe1b7b6e..08a006d4b 100644 Binary files a/Icons/backtrack.png and b/Icons/backtrack.png differ diff --git a/Managers/UTMAppleVirtualMachine.swift b/Managers/UTMAppleVirtualMachine.swift index 71503b376..ab10ff6c4 100644 --- a/Managers/UTMAppleVirtualMachine.swift +++ b/Managers/UTMAppleVirtualMachine.swift @@ -48,7 +48,7 @@ import Virtualization @MainActor override var detailsSystemMemoryLabel: String { let bytesInMib = Int64(1048576) - return ByteCountFormatter.string(fromByteCount: Int64(appleConfig.system.memorySize) * bytesInMib, countStyle: .memory) + return ByteCountFormatter.string(fromByteCount: Int64(appleConfig.system.memorySize) * bytesInMib, countStyle: .binary) } override var hasSaveState: Bool { diff --git a/Managers/UTMPendingVirtualMachine.swift b/Managers/UTMPendingVirtualMachine.swift index 0d771ff0f..938ae936d 100644 --- a/Managers/UTMPendingVirtualMachine.swift +++ b/Managers/UTMPendingVirtualMachine.swift @@ -97,13 +97,13 @@ import Foundation lastDownloadSpeedUpdate = Date() let bytesPerSecond = bytesWrittenSinceLastDownloadSpeedUpdate bytesWrittenSinceLastDownloadSpeedUpdate = 0 - let bytesString = ByteCountFormatter.string(fromByteCount: bytesPerSecond, countStyle: .file) + let bytesString = ByteCountFormatter.string(fromByteCount: bytesPerSecond, countStyle: .binary) let speedFormat = NSLocalizedString("%@/s", comment: "Format string for the 'per second' part of a download speed.") estimatedDownloadSpeed = String.localizedStringWithFormat(speedFormat, bytesString) /// sizes - downloadedSize = ByteCountFormatter.string(fromByteCount: totalBytesWritten, countStyle: .file) - estimatedDownloadSize = ByteCountFormatter.string(fromByteCount: totalBytesExpectedToWrite, countStyle: .file) + downloadedSize = ByteCountFormatter.string(fromByteCount: totalBytesWritten, countStyle: .binary) + estimatedDownloadSize = ByteCountFormatter.string(fromByteCount: totalBytesExpectedToWrite, countStyle: .binary) } public func setDownloadProgress(new newBytesWritten: Int64, currentTotal totalBytesWritten: Int64, estimatedTotal totalBytesExpectedToWrite: Int64) { diff --git a/Managers/UTMQemuVirtualMachine.h b/Managers/UTMQemuVirtualMachine.h index b86b21101..92976e6e3 100644 --- a/Managers/UTMQemuVirtualMachine.h +++ b/Managers/UTMQemuVirtualMachine.h @@ -28,6 +28,9 @@ NS_ASSUME_NONNULL_BEGIN /// This property is observable and must only be accessed on the main thread. @property (nonatomic) BOOL isGuestToolsInstallRequested; +/// Sends power off request to the guest +- (void)requestGuestPowerDown; + @end NS_ASSUME_NONNULL_END diff --git a/Managers/UTMQemuVirtualMachine.m b/Managers/UTMQemuVirtualMachine.m index 3ce21a0ba..9b3239dc4 100644 --- a/Managers/UTMQemuVirtualMachine.m +++ b/Managers/UTMQemuVirtualMachine.m @@ -567,6 +567,16 @@ - (void)vmResumeWithCompletion:(void (^)(NSError * _Nullable))completion { }); } +- (void)requestGuestPowerDown { + dispatch_async(self.vmOperations, ^{ + [self.qemu qemuPowerDownWithCompletion:^(NSError *err) { + if (err) { + UTMLog(@"Error requesting power down: %@", err.localizedDescription); + } + }]; + }); +} + #pragma mark - Qemu manager delegate - (void)qemuHasWakeup:(UTMQemuManager *)manager { diff --git a/Managers/UTMQemuVirtualMachine.swift b/Managers/UTMQemuVirtualMachine.swift index 6b0ff83a4..cca412fca 100644 --- a/Managers/UTMQemuVirtualMachine.swift +++ b/Managers/UTMQemuVirtualMachine.swift @@ -44,7 +44,7 @@ public extension UTMQemuVirtualMachine { @MainActor override var detailsSystemMemoryLabel: String { let bytesInMib = Int64(1048576) - return ByteCountFormatter.string(fromByteCount: Int64(qemuConfig.system.memorySize) * bytesInMib, countStyle: .memory) + return ByteCountFormatter.string(fromByteCount: Int64(qemuConfig.system.memorySize) * bytesInMib, countStyle: .binary) } /// Check if a QEMU target is supported diff --git a/Managers/UTMVirtualMachine.h b/Managers/UTMVirtualMachine.h index e5fcb2618..4db698685 100644 --- a/Managers/UTMVirtualMachine.h +++ b/Managers/UTMVirtualMachine.h @@ -106,7 +106,7 @@ NS_ASSUME_NONNULL_BEGIN /// `-saveUTMWithCompletion:` should be called to save to disk. /// @param configuration VM configuration /// @param packageURL Location of the VM -+ (UTMVirtualMachine *)virtualMachineWithConfiguration:(UTMConfigurationWrapper *)configuration packageURL:(NSURL *)packageURL; ++ (UTMVirtualMachine *)virtualMachineWithConfigurationWrapper:(UTMConfigurationWrapper *)configuration packageURL:(NSURL *)packageURL; /// Discard any changes to configuration by reloading from disk /// @param err Error thrown diff --git a/Managers/UTMVirtualMachine.m b/Managers/UTMVirtualMachine.m index b5cd96605..3371271ed 100644 --- a/Managers/UTMVirtualMachine.m +++ b/Managers/UTMVirtualMachine.m @@ -143,7 +143,7 @@ + (nullable UTMVirtualMachine *)virtualMachineWithURL:(NSURL *)url { UTMConfigurationWrapper *config = [[UTMConfigurationWrapper alloc] initFrom:url]; [url stopAccessingSecurityScopedResource]; if (config) { - UTMVirtualMachine *vm = [UTMVirtualMachine virtualMachineWithConfiguration:config packageURL:url]; + UTMVirtualMachine *vm = [UTMVirtualMachine virtualMachineWithConfigurationWrapper:config packageURL:url]; dispatch_async(dispatch_get_main_queue(), ^{ [vm updateConfigFromRegistry]; }); @@ -153,7 +153,7 @@ + (nullable UTMVirtualMachine *)virtualMachineWithURL:(NSURL *)url { } } -+ (UTMVirtualMachine *)virtualMachineWithConfiguration:(UTMConfigurationWrapper *)configuration packageURL:(nonnull NSURL *)packageURL { ++ (UTMVirtualMachine *)virtualMachineWithConfigurationWrapper:(UTMConfigurationWrapper *)configuration packageURL:(nonnull NSURL *)packageURL { #if TARGET_OS_OSX if (@available(macOS 11, *)) { if (configuration.isAppleVirtualization) { diff --git a/Managers/UTMVirtualMachine.swift b/Managers/UTMVirtualMachine.swift index cb31f3c2a..1f77d36b9 100644 --- a/Managers/UTMVirtualMachine.swift +++ b/Managers/UTMVirtualMachine.swift @@ -58,7 +58,7 @@ extension UTMVirtualMachine: ObservableObject { @nonobjc convenience init(newConfig: Config, destinationURL: URL) { let packageURL = UTMVirtualMachine.virtualMachinePath(newConfig.information.name, inParentURL: destinationURL) let configuration = UTMConfigurationWrapper(wrapping: newConfig) - self.init(configuration: configuration, packageURL: packageURL) + self.init(configurationWrapper: configuration, packageURL: packageURL) } } diff --git a/Platform/Main.swift b/Platform/Main.swift index 67f5d46b4..7b9a948ef 100644 --- a/Platform/Main.swift +++ b/Platform/Main.swift @@ -34,8 +34,7 @@ class Main { static var jitAvailable = true static func main() { - #if os(iOS) - #if !WITH_QEMU_TCI + #if os(iOS) && !WITH_QEMU_TCI // check if we have jailbreak if jb_spawn_ptrace_child(CommandLine.argc, CommandLine.unsafeArgv) { logger.info("JIT: ptrace() child spawn trick") @@ -56,15 +55,12 @@ class Main { logger.info("MEM: successfully removed memory limits") } #endif - // UIViewController patches - UTMViewControllerPatches.patchAll() + // do patches + UTMPatches.patchAll() + #if os(iOS) // register defaults registerDefaultsFromSettingsBundle() #endif - #if os(macOS) - // SwiftUI bug: works around crash due to "already had more Update Constraints in Window passes than there are views in the window" exception - UserDefaults.standard.set(false, forKey: "NSWindowAssertWhenDisplayCycleLimitReached") - #endif UTMApp.main() } diff --git a/Platform/Shared/DetailedSection.swift b/Platform/Shared/DetailedSection.swift index bfb91716d..05e01b84a 100644 --- a/Platform/Shared/DetailedSection.swift +++ b/Platform/Shared/DetailedSection.swift @@ -44,8 +44,11 @@ struct DetailedSection: View where Content: View { struct DetailedSection_Previews: PreviewProvider { static var previews: some View { - DetailedSection("Section", description: "Description") { - EmptyView() + Form { + DetailedSection("Section", description: "Description") { + EmptyView() + } } + .frame(width: 200) } } diff --git a/Platform/Shared/SizeTextField.swift b/Platform/Shared/SizeTextField.swift index 31b0728f5..3997dc6de 100644 --- a/Platform/Shared/SizeTextField.swift +++ b/Platform/Shared/SizeTextField.swift @@ -38,8 +38,13 @@ struct SizeTextField: View { .multilineTextAlignment(.trailing) .help("The amount of storage to allocate for this image. Ignored if importing an image. If this is a raw image, then an empty file of this size will be stored with the VM. Otherwise, the disk image will dynamically expand up to this size.") Button(action: { isGiB.toggle() }, label: { - Text(isGiB ? "GB" : "MB") - .foregroundColor(.blue) + Group { + if isGiB { + Text("GB") + } else { + Text("MB") + } + }.foregroundColor(.blue) }).buttonStyle(.plain) } } diff --git a/Platform/Shared/UTMDownloadTask.swift b/Platform/Shared/UTMDownloadTask.swift index 7953ecd2f..db820d2dd 100644 --- a/Platform/Shared/UTMDownloadTask.swift +++ b/Platform/Shared/UTMDownloadTask.swift @@ -25,6 +25,9 @@ class UTMDownloadTask: NSObject, URLSessionDelegate, URLSessionDownloadDelegate private var taskContinuation: CheckedContinuation? @MainActor private(set) lazy var pendingVM: UTMPendingVirtualMachine = createPendingVM() + private let kMaxRetries = 5 + private var retries = 0 + var fileManager: FileManager { FileManager.default } @@ -82,6 +85,7 @@ class UTMDownloadTask: NSObject, URLSessionDelegate, URLSessionDownloadDelegate sessionTask.cancel() return } + retries = 0 // reset retry counter on success Task { await pendingVM.setDownloadProgress(new: bytesWritten, currentTotal: totalBytesWritten, @@ -91,12 +95,22 @@ class UTMDownloadTask: NSObject, URLSessionDelegate, URLSessionDownloadDelegate /// when the session ends with an error, it could be cancelled or an actual error internal func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) { + let error = error as? NSError + if let resumeData = error?.userInfo[NSURLSessionDownloadTaskResumeData] as? Data { + retries += 1 + guard retries > kMaxRetries else { + logger.warning("Retrying download due to connection error...") + let task = session.downloadTask(withResumeData: resumeData) + task.resume() + return + } + } guard let taskContinuation = taskContinuation else { return } self.taskContinuation = nil + self.retries = 0 // reset retry counter if let error = error { - let error = error as NSError if error.code == NSURLErrorCancelled { /// download was cancelled normally taskContinuation.resume(returning: nil) @@ -135,7 +149,7 @@ class UTMDownloadTask: NSObject, URLSessionDelegate, URLSessionDownloadDelegate /// - Returns: Completed download or nil if canceled func download() async throws -> UTMVirtualMachine? { /// begin the download - let session = URLSession(configuration: .ephemeral, delegate: self, delegateQueue: nil) + let session = URLSession(configuration: .default, delegate: self, delegateQueue: nil) downloadTask = Task.detached { [self] in let sessionDownload = session.downloadTask(with: url) await pendingVM.setDownloadStarting() diff --git a/Platform/Shared/VMConfigDisplayConsoleView.swift b/Platform/Shared/VMConfigDisplayConsoleView.swift index 7a9f59c3e..abc636b3f 100644 --- a/Platform/Shared/VMConfigDisplayConsoleView.swift +++ b/Platform/Shared/VMConfigDisplayConsoleView.swift @@ -71,6 +71,11 @@ struct VMConfigDisplayConsoleView_Previews: PreviewProvider { @State static private var config = UTMConfigurationTerminal() static var previews: some View { - VMConfigDisplayConsoleView(config: $config) + Form { + VMConfigDisplayConsoleView(config: $config) + } + #if os(macOS) + .scrollable() + #endif } } diff --git a/Platform/Shared/VMConfigDriveDetailsView.swift b/Platform/Shared/VMConfigDriveDetailsView.swift index 82bdab282..86211b592 100644 --- a/Platform/Shared/VMConfigDriveDetailsView.swift +++ b/Platform/Shared/VMConfigDriveDetailsView.swift @@ -35,7 +35,7 @@ struct VMConfigDriveDetailsView: View { } @Binding var config: UTMQemuConfigurationDrive - let onDelete: (() -> Void)? + @Binding var requestDriveDelete: UTMQemuConfigurationDrive? @EnvironmentObject private var data: UTMData @State private var isImporterPresented: Bool = false @@ -78,19 +78,19 @@ struct VMConfigDriveDetailsView: View { } if let imageUrl = config.imageURL, let fileSize = data.computeSize(for: imageUrl) { - DefaultTextField("Size", text: .constant(ByteCountFormatter.string(fromByteCount: fileSize, countStyle: .file))).disabled(true) + DefaultTextField("Size", text: .constant(ByteCountFormatter.string(fromByteCount: fileSize, countStyle: .binary))).disabled(true) } else if config.sizeMib > 0 { - DefaultTextField("Size", text: .constant(ByteCountFormatter.string(fromByteCount: Int64(config.sizeMib) * bytesInMib, countStyle: .file))).disabled(true) + DefaultTextField("Size", text: .constant(ByteCountFormatter.string(fromByteCount: Int64(config.sizeMib) * bytesInMib, countStyle: .binary))).disabled(true) } #if os(macOS) HStack { - if let onDelete = onDelete { - Button(action: onDelete) { - Label("Delete Drive", systemImage: "externaldrive.badge.minus") - .foregroundColor(.red) - }.help("Delete this drive.") - } + Button { + requestDriveDelete = config + } label: { + Label("Delete Drive", systemImage: "externaldrive.badge.minus") + .foregroundColor(.red) + }.help("Delete this drive.") if let imageUrl = config.imageURL, FileManager.default.fileExists(atPath: imageUrl.path) { Button { @@ -160,7 +160,7 @@ private struct ResizePopoverView: View { private var sizeString: String? { if let currentSize = currentSize { - return ByteCountFormatter.string(fromByteCount: currentSize, countStyle: .file) + return ByteCountFormatter.string(fromByteCount: currentSize, countStyle: .binary) } else { return nil } diff --git a/Platform/Shared/VMConfigInfoView.swift b/Platform/Shared/VMConfigInfoView.swift index 7b9ef050b..9e6d95b52 100644 --- a/Platform/Shared/VMConfigInfoView.swift +++ b/Platform/Shared/VMConfigInfoView.swift @@ -267,6 +267,9 @@ struct VMConfigInfoView_Previews: PreviewProvider { static var previews: some View { Group { VMConfigInfoView(config: $config) + #if os(macOS) + .scrollable() + #endif IconSelect() { _ in } diff --git a/Platform/Shared/VMConfigInputView.swift b/Platform/Shared/VMConfigInputView.swift index 1f6fc2a59..ddb00c8f3 100644 --- a/Platform/Shared/VMConfigInputView.swift +++ b/Platform/Shared/VMConfigInputView.swift @@ -100,5 +100,8 @@ struct VMConfigInputView_Previews: PreviewProvider { static var previews: some View { VMConfigInputView(config: $config) + #if os(macOS) + .scrollable() + #endif } } diff --git a/Platform/Shared/VMConfigQEMUView.swift b/Platform/Shared/VMConfigQEMUView.swift index 4f1ec9d33..af8bed018 100644 --- a/Platform/Shared/VMConfigQEMUView.swift +++ b/Platform/Shared/VMConfigQEMUView.swift @@ -210,5 +210,6 @@ struct VMConfigQEMUView_Previews: PreviewProvider { static var previews: some View { VMConfigQEMUView(config: $config, system: $system, fetchFixedArguments: { [] }) + .frame(minHeight: 500) } } diff --git a/Platform/Shared/VMConfigSerialView.swift b/Platform/Shared/VMConfigSerialView.swift index 9b78aabe5..05ae98253 100644 --- a/Platform/Shared/VMConfigSerialView.swift +++ b/Platform/Shared/VMConfigSerialView.swift @@ -99,5 +99,8 @@ struct VMConfigSerialView_Previews: PreviewProvider { static var previews: some View { VMConfigSerialView(config: $config, system: $system) + #if os(macOS) + .scrollable() + #endif } } diff --git a/Platform/Shared/VMConfigSharingView.swift b/Platform/Shared/VMConfigSharingView.swift index 7ed001e68..ce9659060 100644 --- a/Platform/Shared/VMConfigSharingView.swift +++ b/Platform/Shared/VMConfigSharingView.swift @@ -56,5 +56,8 @@ struct VMConfigSharingView_Previews: PreviewProvider { static var previews: some View { VMConfigSharingView(config: $config) + #if os(macOS) + .scrollable() + #endif } } diff --git a/Platform/Shared/VMConfigSystemView.swift b/Platform/Shared/VMConfigSystemView.swift index 911de5d22..84d001cc6 100644 --- a/Platform/Shared/VMConfigSystemView.swift +++ b/Platform/Shared/VMConfigSystemView.swift @@ -283,5 +283,8 @@ struct VMConfigSystemView_Previews: PreviewProvider { static var previews: some View { VMConfigSystemView(config: $config, isResetConfig: .constant(false)) + #if os(macOS) + .scrollable() + #endif } } diff --git a/Platform/Shared/VMDetailsView.swift b/Platform/Shared/VMDetailsView.swift index 61dd25c2d..9485ad158 100644 --- a/Platform/Shared/VMDetailsView.swift +++ b/Platform/Shared/VMDetailsView.swift @@ -32,7 +32,7 @@ struct VMDetailsView: View { private var sizeLabel: String { let size = data.computeSize(for: vm) - return ByteCountFormatter.string(fromByteCount: size, countStyle: .file) + return ByteCountFormatter.string(fromByteCount: size, countStyle: .binary) } var body: some View { diff --git a/Platform/Shared/VMWizardSummaryView.swift b/Platform/Shared/VMWizardSummaryView.swift index 023fe5d9a..6239030aa 100644 --- a/Platform/Shared/VMWizardSummaryView.swift +++ b/Platform/Shared/VMWizardSummaryView.swift @@ -29,7 +29,7 @@ struct VMWizardSummaryView: View { } } #endif - return ByteCountFormatter.string(fromByteCount: size, countStyle: .file) + return ByteCountFormatter.string(fromByteCount: size, countStyle: .binary) } var coreDescription: String { @@ -123,7 +123,7 @@ struct VMWizardSummaryView: View { TextField("Architecture", text: .constant(wizardState.systemArchitecture.prettyValue)) TextField("System", text: .constant(wizardState.systemTarget.prettyValue)) } - TextField("RAM", text: .constant(ByteCountFormatter.string(fromByteCount: Int64(wizardState.systemMemoryMib * wizardState.bytesInMib), countStyle: .memory))) + TextField("RAM", text: .constant(ByteCountFormatter.string(fromByteCount: Int64(wizardState.systemMemoryMib * wizardState.bytesInMib), countStyle: .binary))) TextField("CPU", text: .constant(coreDescription)) TextField("Storage", text: .constant(storageDescription)) if !wizardState.useAppleVirtualization && wizardState.operatingSystem == .Linux { diff --git a/Platform/de.lproj/Localizable.strings b/Platform/de.lproj/Localizable.strings index b8f9c5b2c..a294b4ab2 100644 --- a/Platform/de.lproj/Localizable.strings +++ b/Platform/de.lproj/Localizable.strings @@ -574,9 +574,6 @@ /* UTMQemuConstants */ "None (Advanced)" = "Keine (erweitert)"; -/* No comment provided by engineer. */ -"Note: Shared directories will not be saved and will be reset when UTM quits." = "Hinweis: freigegebene Ordner werden nicht gespeichert und beim Beenden von UTM zurückgesetzt."; - /* No comment provided by engineer. */ "Notes" = "Notizen"; diff --git a/Platform/es-419.lproj/Localizable.strings b/Platform/es-419.lproj/Localizable.strings index f5a4e8489..bc27c387e 100644 --- a/Platform/es-419.lproj/Localizable.strings +++ b/Platform/es-419.lproj/Localizable.strings @@ -574,9 +574,6 @@ /* UTMQemuConstants */ "None (Advanced)" = "Ninguno (avanzado)"; -/* No comment provided by engineer. */ -"Note: Shared directories will not be saved and will be reset when UTM quits." = "Nota: Los directorios compartidos no serán guardados y se restablecerán cuando UTM se cierre."; - /* No comment provided by engineer. */ "Notes" = "Notas"; diff --git a/Platform/fr.lproj/Localizable.strings b/Platform/fr.lproj/Localizable.strings index 9943febab..3031fd33f 100644 --- a/Platform/fr.lproj/Localizable.strings +++ b/Platform/fr.lproj/Localizable.strings @@ -158,6 +158,10 @@ "Port Forward" = "Redirection de port"; "New" = "Ajouter"; +// UTMSettingsView.swift +"Settings" = "Réglages"; +"Close" = "Fermer"; + // VMConfigNetworkPortForwardView.swift "%@ ➡️ %@" = "%1$@ ➡️ %2$@"; @@ -181,7 +185,6 @@ "Serial" = "Série"; "Network" = "Réseau"; "Sound" = "Audio"; -"Settings" = "Réglages"; "Save" = "Enregistrer"; "Power Off" = "Éteindre"; "Quit" = "Quitter"; diff --git a/Platform/iOS/Display/fr.lproj/VMDisplayMetalViewInputAccessory.strings b/Platform/iOS/Display/fr.lproj/VMDisplayMetalViewInputAccessory.strings new file mode 100644 index 000000000..317b1e964 --- /dev/null +++ b/Platform/iOS/Display/fr.lproj/VMDisplayMetalViewInputAccessory.strings @@ -0,0 +1,162 @@ + +/* Class = "UIButton"; normalTitle = "F7"; ObjectID = "3yi-Pr-1ih"; */ +"3yi-Pr-1ih.normalTitle" = "F7"; + +/* Class = "UIButton"; accessibilityLabel = "Paste"; ObjectID = "740-aI-39P"; */ +"740-aI-39P.accessibilityLabel" = "Coller"; + +/* Class = "UIButton"; accessibilityLabel = "Tab"; ObjectID = "7pj-Jz-7JR"; */ +"7pj-Jz-7JR.accessibilityLabel" = "Tab"; + +/* Class = "UIButton"; normalTitle = "⇥"; ObjectID = "7pj-Jz-7JR"; */ +"7pj-Jz-7JR.normalTitle" = "⇥"; + +/* Class = "UIButton"; accessibilityLabel = "Right"; ObjectID = "8Lh-4D-Fz6"; */ +"8Lh-4D-Fz6.accessibilityLabel" = "Droite"; + +/* Class = "UIButton"; normalTitle = "→"; ObjectID = "8Lh-4D-Fz6"; */ +"8Lh-4D-Fz6.normalTitle" = "→"; + +/* Class = "UIButton"; accessibilityLabel = "Right"; ObjectID = "AY8-eJ-bAP"; */ +"AY8-eJ-bAP.accessibilityLabel" = "Droite"; + +/* Class = "UIButton"; normalTitle = "Del"; ObjectID = "AY8-eJ-bAP"; */ +"AY8-eJ-bAP.normalTitle" = "Suppr"; + +/* Class = "UIButton"; normalTitle = "F10"; ObjectID = "AhH-ij-IF8"; */ +"AhH-ij-IF8.normalTitle" = "F10"; + +/* Class = "UIButton"; accessibilityLabel = "Up"; ObjectID = "BUL-js-yMh"; */ +"BUL-js-yMh.accessibilityLabel" = "Haut"; + +/* Class = "UIButton"; normalTitle = "↑"; ObjectID = "BUL-js-yMh"; */ +"BUL-js-yMh.normalTitle" = "↑"; + +/* Class = "UIButton"; accessibilityLabel = "Num Lock"; ObjectID = "BUk-Vf-yE5"; */ +"BUk-Vf-yE5.accessibilityLabel" = "Verr. Num."; + +/* Class = "UIButton"; normalTitle = "Num"; ObjectID = "BUk-Vf-yE5"; */ +"BUk-Vf-yE5.normalTitle" = "Num"; + +/* Class = "UIButton"; normalTitle = "F5"; ObjectID = "DxX-zu-urb"; */ +"DxX-zu-urb.normalTitle" = "F5"; + +/* Class = "UIButton"; normalTitle = "F12"; ObjectID = "EDi-KP-KwO"; */ +"EDi-KP-KwO.normalTitle" = "F12"; + +/* Class = "UIButton"; accessibilityLabel = "Left"; ObjectID = "EVa-2J-CRA"; */ +"EVa-2J-CRA.accessibilityLabel" = "Gauche"; + +/* Class = "UIButton"; normalTitle = "←"; ObjectID = "EVa-2J-CRA"; */ +"EVa-2J-CRA.normalTitle" = "←"; + +/* Class = "UIButton"; accessibilityLabel = "Caps Lock"; ObjectID = "FDV-W6-qlO"; */ +"FDV-W6-qlO.accessibilityLabel" = "Verr. Maj."; + +/* Class = "UIButton"; normalTitle = "Caps"; ObjectID = "FDV-W6-qlO"; */ +"FDV-W6-qlO.normalTitle" = "Majuscules"; + +/* Class = "UIButton"; accessibilityLabel = "Home"; ObjectID = "LU6-kH-vN3"; */ +"LU6-kH-vN3.accessibilityLabel" = "Début"; + +/* Class = "UIButton"; normalTitle = "Home"; ObjectID = "LU6-kH-vN3"; */ +"LU6-kH-vN3.normalTitle" = "Début"; + +/* Class = "UIButton"; normalTitle = "F8"; ObjectID = "LlV-Ae-CrL"; */ +"LlV-Ae-CrL.normalTitle" = "F8"; + +/* Class = "UIButton"; normalTitle = "F1"; ObjectID = "PWe-Va-Qi1"; */ +"PWe-Va-Qi1.normalTitle" = "F1"; + +/* Class = "UIButton"; accessibilityLabel = "Print Screen"; ObjectID = "Pes-KN-KzU"; */ +"Pes-KN-KzU.accessibilityLabel" = "Impression écran"; + +/* Class = "UIButton"; normalTitle = "Pr Scr"; ObjectID = "Pes-KN-KzU"; */ +"Pes-KN-KzU.normalTitle" = "Impr. écran"; + +/* Class = "UIButton"; accessibilityLabel = "Command"; ObjectID = "Pjh-3m-tFX"; */ +"Pjh-3m-tFX.accessibilityLabel" = "Command"; + +/* Class = "UIButton"; normalTitle = "⌘"; ObjectID = "Pjh-3m-tFX"; */ +"Pjh-3m-tFX.normalTitle" = "⌘"; + +/* Class = "UIButton"; accessibilityLabel = "Shift"; ObjectID = "QPo-cD-UlK"; */ +"QPo-cD-UlK.accessibilityLabel" = "Maj"; + +/* Class = "UIButton"; normalTitle = "⇧"; ObjectID = "QPo-cD-UlK"; */ +"QPo-cD-UlK.normalTitle" = "⇧"; + +/* Class = "UIButton"; accessibilityLabel = "Down"; ObjectID = "RCo-l7-gvf"; */ +"RCo-l7-gvf.accessibilityLabel" = "Bas"; + +/* Class = "UIButton"; normalTitle = "↓"; ObjectID = "RCo-l7-gvf"; */ +"RCo-l7-gvf.normalTitle" = "↓"; + +/* Class = "UIButton"; normalTitle = "F6"; ObjectID = "Rb5-vO-sIx"; */ +"Rb5-vO-sIx.normalTitle" = "F6"; + +/* Class = "UIButton"; accessibilityLabel = "End"; ObjectID = "TOV-fV-TTa"; */ +"TOV-fV-TTa.accessibilityLabel" = "Fin"; + +/* Class = "UIButton"; normalTitle = "End"; ObjectID = "TOV-fV-TTa"; */ +"TOV-fV-TTa.normalTitle" = "Fin"; + +/* Class = "UIButton"; normalTitle = "F9"; ObjectID = "UNT-ei-lIn"; */ +"UNT-ei-lIn.normalTitle" = "F9"; + +/* Class = "UIButton"; accessibilityLabel = "Control"; ObjectID = "bCv-uH-SSy"; */ +"bCv-uH-SSy.accessibilityLabel" = "Control"; + +/* Class = "UIButton"; normalTitle = "⌃"; ObjectID = "bCv-uH-SSy"; */ +"bCv-uH-SSy.normalTitle" = "⌃"; + +/* Class = "UIButton"; normalTitle = "F4"; ObjectID = "c7C-CG-EBg"; */ +"c7C-CG-EBg.normalTitle" = "F4"; + +/* Class = "UIButton"; normalTitle = "F3"; ObjectID = "gUX-ez-mbt"; */ +"gUX-ez-mbt.normalTitle" = "F3"; + +/* Class = "UIButton"; accessibilityLabel = "Page Down"; ObjectID = "h4q-XF-UMn"; */ +"h4q-XF-UMn.accessibilityLabel" = "Page Bas"; + +/* Class = "UIButton"; normalTitle = "Pg Dn"; ObjectID = "h4q-XF-UMn"; */ +"h4q-XF-UMn.normalTitle" = "Pg Bas"; + +/* Class = "UIButton"; accessibilityLabel = "Option"; ObjectID = "jxu-AQ-u8c"; */ +"jxu-AQ-u8c.accessibilityLabel" = "Option"; + +/* Class = "UIButton"; normalTitle = "⌥"; ObjectID = "jxu-AQ-u8c"; */ +"jxu-AQ-u8c.normalTitle" = "⌥"; + +/* Class = "UIButton"; accessibilityLabel = "Insert"; ObjectID = "kO0-HZ-5w2"; */ +"kO0-HZ-5w2.accessibilityLabel" = "Insérer"; + +/* Class = "UIButton"; normalTitle = "Ins"; ObjectID = "kO0-HZ-5w2"; */ +"kO0-HZ-5w2.normalTitle" = "Ins"; + +/* Class = "UIButton"; normalTitle = "F2"; ObjectID = "kd1-fj-kXM"; */ +"kd1-fj-kXM.normalTitle" = "F2"; + +/* Class = "UIButton"; accessibilityLabel = "Escape"; ObjectID = "n12-9R-99C"; */ +"n12-9R-99C.accessibilityLabel" = "Échap"; + +/* Class = "UIButton"; normalTitle = "⎋"; ObjectID = "n12-9R-99C"; */ +"n12-9R-99C.normalTitle" = "⎋"; + +/* Class = "UIButton"; accessibilityLabel = "Page Up"; ObjectID = "pX1-7o-dbU"; */ +"pX1-7o-dbU.accessibilityLabel" = "Page Haut"; + +/* Class = "UIButton"; normalTitle = "Pg Up"; ObjectID = "pX1-7o-dbU"; */ +"pX1-7o-dbU.normalTitle" = "Pg Haut"; + +/* Class = "UIButton"; normalTitle = "F11"; ObjectID = "rfk-su-cFq"; */ +"rfk-su-cFq.normalTitle" = "F11"; + +/* Class = "UIButton"; accessibilityLabel = "Hide Keyboard"; ObjectID = "rtU-Yt-FhT"; */ +"rtU-Yt-FhT.accessibilityLabel" = "Masquer le clavier"; + +/* Class = "UIButton"; accessibilityLabel = "Scroll Lock"; ObjectID = "sF1-tj-hUG"; */ +"sF1-tj-hUG.accessibilityLabel" = "Arrêt défil."; + +/* Class = "UIButton"; normalTitle = "Scroll"; ObjectID = "sF1-tj-hUG"; */ +"sF1-tj-hUG.normalTitle" = "Défilement"; diff --git a/Platform/iOS/UTMViewControllerPatches.swift b/Platform/iOS/UTMPatches.swift similarity index 99% rename from Platform/iOS/UTMViewControllerPatches.swift rename to Platform/iOS/UTMPatches.swift index 0c716a318..5c13dda1d 100644 --- a/Platform/iOS/UTMViewControllerPatches.swift +++ b/Platform/iOS/UTMPatches.swift @@ -17,7 +17,7 @@ import UIKit /// Handles Obj-C patches to fix SwiftUI issues -final class UTMViewControllerPatches { +final class UTMPatches { static private var isPatched: Bool = false /// Installs the patches diff --git a/Platform/iOS/VMDrivesSettingsView.swift b/Platform/iOS/VMDrivesSettingsView.swift index b48623fdc..c8cab8012 100644 --- a/Platform/iOS/VMDrivesSettingsView.swift +++ b/Platform/iOS/VMDrivesSettingsView.swift @@ -28,7 +28,7 @@ struct VMDrivesSettingsView: View { var body: some View { ForEach($config.drives) { $drive in NavigationLink( - destination: VMConfigDriveDetailsView(config: $drive, onDelete: nil), label: { + destination: VMConfigDriveDetailsView(config: $drive, requestDriveDelete: .constant(nil)), label: { Label(title: { labelTitle(for: drive) }, icon: { Image(systemName: "externaldrive") }) }) }.onDelete { offsets in diff --git a/Platform/ja.lproj/Localizable.strings b/Platform/ja.lproj/Localizable.strings index 7ff5ba43f..08144cd09 100644 --- a/Platform/ja.lproj/Localizable.strings +++ b/Platform/ja.lproj/Localizable.strings @@ -340,7 +340,6 @@ // VMConfigAppleSerialView.swift "Connection" = "接続"; "Mode" = "モード"; -"Note: Shared directories will not be saved and will be reset when UTM quits." = "注意: 共有ディレクトリは保存されず、UTMを終了するとリセットされます。"; "Shared Path" = "共有パス"; "Add" = "追加"; "This directory is already being shared." = "このディレクトリはすでに共有されています。"; diff --git a/Platform/macOS/Display/Base.lproj/VMDisplayWindow.xib b/Platform/macOS/Display/Base.lproj/VMDisplayWindow.xib index 56fc3a725..769ee8ea9 100644 --- a/Platform/macOS/Display/Base.lproj/VMDisplayWindow.xib +++ b/Platform/macOS/Display/Base.lproj/VMDisplayWindow.xib @@ -103,7 +103,7 @@ - + + + +