From ab69e0a316d8f0d35c7403e6fe2b2defead68aa9 Mon Sep 17 00:00:00 2001 From: John Smith Date: Wed, 13 Dec 2023 13:40:00 +0900 Subject: [PATCH 01/79] Issue #5937 - improved VM icon selector UX --- Platform/Shared/VMConfigInfoView.swift | 191 ++++++++++++++++++------- 1 file changed, 140 insertions(+), 51 deletions(-) diff --git a/Platform/Shared/VMConfigInfoView.swift b/Platform/Shared/VMConfigInfoView.swift index 1c574bfa8..08eb0d7af 100644 --- a/Platform/Shared/VMConfigInfoView.swift +++ b/Platform/Shared/VMConfigInfoView.swift @@ -123,16 +123,20 @@ struct VMConfigInfoView: View { switch iconStyle { case .custom: #if os(macOS) - Button(action: { imageSelectVisible.toggle() }, label: { + VStack { IconPreview(url: config.iconURL) - }).fileImporter(isPresented: $imageSelectVisible, allowedContentTypes: [.image]) { result in - switch result { - case .success(let url): - imageCustomSelected(url: url) - case .failure: - break + Button(action: { imageSelectVisible.toggle() }, label: { + Text("Choose") + }).fileImporter(isPresented: $imageSelectVisible, allowedContentTypes: [.image]) { result in + switch result { + case .success(let url): + imageCustomSelected(url: url) + case .failure: + break + } } - }.buttonStyle(.plain) + } + .frame(width: 90) #else Button(action: { imageSelectVisible.toggle() }, label: { IconPreview(url: config.iconURL) @@ -141,18 +145,31 @@ struct VMConfigInfoView: View { }.buttonStyle(.plain) #endif case .operatingSystem: - Button(action: { imageSelectVisible.toggle() }, label: { + #if os(macOS) + VStack { IconPreview(url: config.iconURL) - }).popover(isPresented: $imageSelectVisible, arrowEdge: .bottom) { - IconSelect(onIconSelected: imageSelected) - }.buttonStyle(.plain) + Button(action: { imageSelectVisible.toggle() }, label: { + Text("Choose") + }).popover(isPresented: $imageSelectVisible, arrowEdge: .bottom) { + IconSelect(current: config.iconURL, onIconSelected: imageSelected) + } + } + .frame(width: 90) + #else + IconSelect(current: config.iconURL, onIconSelected: imageSelected) + #endif default: #if os(macOS) - Image(systemName: "desktopcomputer") - .resizable() - .frame(width: 30.0, height: 30.0) - .padding() - .foregroundColor(Color(NSColor.disabledControlTextColor)) + VStack { + Image(systemName: "desktopcomputer") + .resizable() + .frame(width: 30.0, height: 30.0) + .foregroundColor(Color(NSColor.disabledControlTextColor)) + Button {} label: { + Text("Choose") + }.disabled(true) + } + .frame(width: 90) #else EmptyView() #endif @@ -190,7 +207,6 @@ private struct IconPreview: View { Spacer() #endif Logo(logo: PlatformImage(contentsOfURL: url)) - .padding() #if !os(macOS) Spacer() #endif @@ -198,9 +214,16 @@ private struct IconPreview: View { } } +#if os(macOS) +let iconGridSize: CGFloat = 80 +#else +let iconGridSize: CGFloat = 100 +#endif + private struct IconSelect: View { + let current: URL? let onIconSelected: (URL) -> Void - private let gridLayout = [GridItem(.adaptive(minimum: 60))] + private let gridLayout = [GridItem(.adaptive(minimum: iconGridSize))] private var icons: [URL] { let paths = Bundle.main.paths(forResourcesOfType: "png", inDirectory: "Icons") let urls = paths.map({ URL(fileURLWithPath: $0) }) @@ -210,6 +233,7 @@ private struct IconSelect: View { } #if os(macOS) + typealias PlatformImage = NSImage #else typealias PlatformImage = UIImage @@ -218,44 +242,35 @@ private struct IconSelect: View { struct IconSelectModifier: ViewModifier { @Environment(\.presentationMode) private var presentationMode: Binding - #if os(macOS) - let isPhone: Bool = false - #else - var isPhone: Bool { - UIDevice.current.userInterfaceIdiom == .phone - } - #endif - func body(content: Content) -> some View { - if isPhone { - return AnyView( - VStack { - HStack { - Spacer() - Button(action: { presentationMode.wrappedValue.dismiss() }, label: { - Text("Cancel") - }).padding() - } - ScrollView { - content.padding(.bottom) - } - } - ) - } else { - return AnyView( - ScrollView { - content.padding([.top, .bottom]) - }.frame(width: 400, height: 400) - ) - } + #if os(macOS) + return AnyView( + ScrollView { + content.padding(16) + }.frame(width: 480, height: 400) + ) + #else + return AnyView(content) + #endif } } var body: some View { - LazyVGrid(columns: gridLayout, spacing: 30) { + LazyVGrid(columns: gridLayout, spacing: 0) { ForEach(icons, id: \.self) { icon in Button(action: { onIconSelected(icon) }, label: { - Logo(logo: PlatformImage(contentsOfURL: icon)) + VStack { + Logo(logo: PlatformImage(contentsOfURL: icon)) + Text(iconToTitle(icon)) + .lineLimit(2) + .font(.footnote) + } + .padding(8) + .frame(width: iconGridSize, height: iconGridSize) + .overlay( + RoundedRectangle(cornerRadius: 10) + .stroke(current == icon ? Color.accentColor : Color.clear, lineWidth: 2) + ) }).buttonStyle(.plain) } }.modifier(IconSelectModifier()) @@ -271,9 +286,83 @@ struct VMConfigInfoView_Previews: PreviewProvider { #if os(macOS) .scrollable() #endif - IconSelect() { _ in + IconSelect(current: nil) { _ in } } } } + +private func iconToTitle(_ icon: URL?) -> LocalizedStringKey { + guard let fileName = icon?.deletingPathExtension().lastPathComponent else { + return "Custom" + } + return ICON_TITLE_MAP[fileName] ?? "Custom" +} + +private let ICON_TITLE_MAP: [String: LocalizedStringKey] = [ + "AIX": "AIX", + "IOS": "iOS", + "Windows7": "Windows 7", + "almalinux": "AlmaLinux", + "alpine": "Alpine", + "amigaos": "AmigaOS", + "android": "Android", + "apple-tv": "Apple TV", + "arch-linux": "Arch Linux", + "backtrack": "BackTrack", + "bada": "Bada", + "beos": "BeOS", + "centos": "CentOS", + "chrome-os": "Chrome OS", + "cyanogenmod": "CyanogenMod", + "debian": "Debian", + "elementary-os": "Elementary OS", + "fedora": "Fedora", + "firefox-os": "Firefox OS", + "freebsd": "FreeBSD", + "gentoo": "Gentoo", + "haiku-os": "Haiku OS", + "hp-ux": "HP-UX", + "kaios": "KaiOS", + "knoppix": "Knoppix", + "kubuntu": "Kubuntu", + "linux": "Linux", + "lubuntu": "Lubuntu", + "mac": "macOS", + "maemo": "Maemo", + "mandriva": "Mandriva", + "meego": "MeeGo", + "mint": "Linux Mint", + "netbsd": "NetBSD", + "nintendo": "Nintendo", + "nixos": "NixOS", + "openbsd": "OpenBSD", + "openwrt": "OpenWrt", + "os2": "OS/2", + "palmos": "Palm OS", + "playstation-portable": "PlayStation Portable", + "playstation": "PlayStation", + "pop-os": "Pop!_OS", + "red-hat": "Red Hat", + "remix-os": "Remix OS", + "risc-os": "RISC OS", + "sabayon": "Sabayon", + "sailfish-os": "Sailfish OS", + "slackware": "Slackware", + "solaris": "Solaris", + "suse": "openSUSE", + "syllable": "Syllable", + "symbian": "Symbian", + "threadx": "ThreadX", + "tizen": "Tizen", + "ubuntu": "Ubuntu", + "webos": "webOS", + "windows-11": "Windows 11", + "windows-9x": "Windows 9x", + "windows-xp": "Windows XP", + "windows": "Windows", + "xbox": "Xbox", + "xubuntu": "Xubuntu", + "yunos": "YunOS", +] From f338494c910e5c84c7837b1243d6c4d666a94a97 Mon Sep 17 00:00:00 2001 From: John Smith Date: Thu, 14 Dec 2023 14:05:45 +0900 Subject: [PATCH 02/79] Automatically capture cursor and keyboard on window focus #5970 --- .../VMDisplayQemuMetalWindowController.swift | 15 +++++++++++++++ Platform/macOS/SettingsView.swift | 4 ++++ 2 files changed, 19 insertions(+) diff --git a/Platform/macOS/Display/VMDisplayQemuMetalWindowController.swift b/Platform/macOS/Display/VMDisplayQemuMetalWindowController.swift index eaec9a91f..7ac643119 100644 --- a/Platform/macOS/Display/VMDisplayQemuMetalWindowController.swift +++ b/Platform/macOS/Display/VMDisplayQemuMetalWindowController.swift @@ -62,6 +62,7 @@ class VMDisplayQemuMetalWindowController: VMDisplayQemuWindowController { @Setting("NoFullscreenCursorCaptureAlert") private var isFullscreenCursorCaptureAlertShown: Bool = false @Setting("DisplayFixed") private var isDisplayFixed: Bool = false @Setting("FullScreenAutoCapture") private var isFullScreenAutoCapture: Bool = false + @Setting("WindowFocusAutoCapture") private var isWindowFocusAutoCapture: Bool = false @Setting("CtrlRightClick") private var isCtrlRightClick: Bool = false @Setting("AlternativeCaptureKey") private var isAlternativeCaptureKey: Bool = false @Setting("IsCapsLockKey") private var isCapsLockKey: Bool = false @@ -400,6 +401,20 @@ extension VMDisplayQemuMetalWindowController { } } + func windowDidBecomeMain(_ notification: Notification) { + if isWindowFocusAutoCapture { + captureMouseToolbarButton.state = .on + captureMouse() + } + } + + func windowDidResignMain(_ notification: Notification) { + if isWindowFocusAutoCapture { + captureMouseToolbarButton.state = .off + releaseMouse() + } + } + override func windowDidResignKey(_ notification: Notification) { releaseMouse() super.windowDidResignKey(notification) diff --git a/Platform/macOS/SettingsView.swift b/Platform/macOS/SettingsView.swift index 961ac1f31..be9276279 100644 --- a/Platform/macOS/SettingsView.swift +++ b/Platform/macOS/SettingsView.swift @@ -129,6 +129,7 @@ struct SoundSettingsView: View { struct InputSettingsView: View { @AppStorage("FullScreenAutoCapture") var isFullScreenAutoCapture = false + @AppStorage("WindowFocusAutoCapture") var isWindowFocusAutoCapture = false @AppStorage("OptionAsMetaKey") var isOptionAsMetaKey = false @AppStorage("CtrlRightClick") var isCtrlRightClick = false @AppStorage("AlternativeCaptureKey") var isAlternativeCaptureKey = false @@ -143,6 +144,9 @@ struct InputSettingsView: View { Toggle(isOn: $isFullScreenAutoCapture) { Text("Capture input automatically when entering full screen") }.help("If enabled, input capture will toggle automatically when entering and exiting full screen mode.") + Toggle(isOn: $isWindowFocusAutoCapture) { + Text("Capture input automatically when window is focused") + }.help("If enabled, input capture will toggle automatically when the VM's window is focused.") } Section(header: Text("Console")) { From 5a1ed68ab4a9f25089c6f6cde9e123f70d796df1 Mon Sep 17 00:00:00 2001 From: John Smith Date: Thu, 14 Dec 2023 14:12:35 +0900 Subject: [PATCH 03/79] Localizable for zh-Hans --- Platform/zh-Hans.lproj/Localizable.strings | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Platform/zh-Hans.lproj/Localizable.strings b/Platform/zh-Hans.lproj/Localizable.strings index 85c2e7145..20a2de5cc 100644 --- a/Platform/zh-Hans.lproj/Localizable.strings +++ b/Platform/zh-Hans.lproj/Localizable.strings @@ -187,6 +187,12 @@ /* No comment provided by engineer. */ "Capture input automatically when entering full screen" = "进入全屏时自动捕获输入"; +"If enabled, input capture will toggle automatically when entering and exiting full screen mode." = "如果启用此开关,进入和退出全屏模式时将自动切换输入捕获。"; + +"Capture input automatically when window is focused" = "成为活动窗口时自动捕获输入"; + +"If enabled, input capture will toggle automatically when the VM's window is focused." = "如果启用此开关,成为/失去当前活动窗口时自动切换输入捕获。"; + /* VMDisplayQemuMetalWindowController */ "Captured mouse" = "鼠标已捕获"; From 2e9489e9b51a5bb99fa8440ec20c2e3b0b2265c7 Mon Sep 17 00:00:00 2001 From: John Smith Date: Thu, 14 Dec 2023 15:35:32 +0900 Subject: [PATCH 04/79] Bug fixed: VM window will be dragged to the center of screen in some case. --- .../VMDisplayQemuMetalWindowController.swift | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/Platform/macOS/Display/VMDisplayQemuMetalWindowController.swift b/Platform/macOS/Display/VMDisplayQemuMetalWindowController.swift index 7ac643119..ed6b64711 100644 --- a/Platform/macOS/Display/VMDisplayQemuMetalWindowController.swift +++ b/Platform/macOS/Display/VMDisplayQemuMetalWindowController.swift @@ -402,17 +402,20 @@ extension VMDisplayQemuMetalWindowController { } func windowDidBecomeMain(_ notification: Notification) { - if isWindowFocusAutoCapture { - captureMouseToolbarButton.state = .on - captureMouse() + // Do not capture mouse if user did not clicked inside the metalView because the window will be draged if user hold the mouse button. + guard let window = window, + window.mouseLocationOutsideOfEventStream.y < metalView.frame.height, + captureMouseToolbarButton.state == .off, + isWindowFocusAutoCapture else { + return } + captureMouseToolbarButton.state = .on + captureMouse() } func windowDidResignMain(_ notification: Notification) { - if isWindowFocusAutoCapture { - captureMouseToolbarButton.state = .off - releaseMouse() - } + captureMouseToolbarButton.state = .off + releaseMouse() } override func windowDidResignKey(_ notification: Notification) { From 8fb6a4f4656bf0c12e6d09a30049c98f1a3ed4c0 Mon Sep 17 00:00:00 2001 From: gnattu Date: Thu, 23 Nov 2023 10:32:23 +0800 Subject: [PATCH 05/79] config(apple): use nvme as default disk interface on macOS 14+ A workaround for #4840 --- Configuration/UTMAppleConfiguration.swift | 2 ++ Configuration/UTMAppleConfigurationDrive.swift | 10 +++++++++- Platform/Shared/VMWizardState.swift | 12 +++++++++++- Platform/ja.lproj/Localizable.strings | 6 ++++++ Platform/macOS/VMConfigAppleDriveCreateView.swift | 6 ++++++ Platform/macOS/VMConfigAppleDriveDetailsView.swift | 6 ++++++ Platform/zh-HK.lproj/Localizable.strings | 4 ++++ Platform/zh-Hans.lproj/Localizable.strings | 5 +++++ Platform/zh-Hant.lproj/Localizable.strings | 5 +++++ 9 files changed, 54 insertions(+), 2 deletions(-) diff --git a/Configuration/UTMAppleConfiguration.swift b/Configuration/UTMAppleConfiguration.swift index 40fa78f3f..27a0ea3d3 100644 --- a/Configuration/UTMAppleConfiguration.swift +++ b/Configuration/UTMAppleConfiguration.swift @@ -270,6 +270,8 @@ extension UTMAppleConfiguration { } if #available(macOS 13, *), drive.isExternal { return VZUSBMassStorageDeviceConfiguration(attachment: attachment) + } else if #available(macOS 14, *), drive.isNvme, system.boot.operatingSystem == .linux { + return VZNVMExpressControllerDeviceConfiguration(attachment: attachment) } else { return VZVirtioBlockDeviceConfiguration(attachment: attachment) } diff --git a/Configuration/UTMAppleConfigurationDrive.swift b/Configuration/UTMAppleConfigurationDrive.swift index ef53dbbf6..4725f5220 100644 --- a/Configuration/UTMAppleConfigurationDrive.swift +++ b/Configuration/UTMAppleConfigurationDrive.swift @@ -25,6 +25,7 @@ struct UTMAppleConfigurationDrive: UTMConfigurationDrive { var sizeMib: Int = 0 var isReadOnly: Bool var isExternal: Bool + var isNvme: Bool var imageURL: URL? var imageName: String? @@ -36,6 +37,7 @@ struct UTMAppleConfigurationDrive: UTMConfigurationDrive { private enum CodingKeys: String, CodingKey { case isReadOnly = "ReadOnly" + case isNvme = "Nvme" case imageName = "ImageName" case bookmark = "Bookmark" // legacy only case identifier = "Identifier" @@ -55,12 +57,14 @@ struct UTMAppleConfigurationDrive: UTMConfigurationDrive { sizeMib = newSize isReadOnly = false isExternal = false + isNvme = false } - init(existingURL url: URL?, isExternal: Bool = false) { + init(existingURL url: URL?, isExternal: Bool = false, isNvme: Bool = false) { self.imageURL = url self.isReadOnly = isExternal self.isExternal = isExternal + self.isNvme = isNvme } init(from decoder: Decoder) throws { @@ -83,6 +87,7 @@ struct UTMAppleConfigurationDrive: UTMConfigurationDrive { isExternal = true } isReadOnly = try container.decodeIfPresent(Bool.self, forKey: .isReadOnly) ?? isExternal + isNvme = try container.decodeIfPresent(Bool.self, forKey: .isNvme) ?? false id = try container.decode(String.self, forKey: .identifier) } @@ -92,6 +97,7 @@ struct UTMAppleConfigurationDrive: UTMConfigurationDrive { try container.encodeIfPresent(imageName, forKey: .imageName) } try container.encode(isReadOnly, forKey: .isReadOnly) + try container.encode(isNvme, forKey: .isNvme) try container.encode(id, forKey: .identifier) } @@ -107,6 +113,7 @@ struct UTMAppleConfigurationDrive: UTMConfigurationDrive { imageName?.hash(into: &hasher) sizeMib.hash(into: &hasher) isReadOnly.hash(into: &hasher) + isNvme.hash(into: &hasher) isExternal.hash(into: &hasher) id.hash(into: &hasher) } @@ -127,6 +134,7 @@ extension UTMAppleConfigurationDrive { sizeMib = oldDrive.sizeMib isReadOnly = oldDrive.isReadOnly isExternal = oldDrive.isExternal + isNvme = false imageURL = oldDrive.imageURL } } diff --git a/Platform/Shared/VMWizardState.swift b/Platform/Shared/VMWizardState.swift index b4803cb00..7ab6e01f0 100644 --- a/Platform/Shared/VMWizardState.swift +++ b/Platform/Shared/VMWizardState.swift @@ -119,6 +119,7 @@ enum VMWizardOS: String, Identifiable { #if os(macOS) @Published var systemMemoryMib: Int = 4096 @Published var storageSizeGib: Int = 64 + @Published var useNvmeAsDiskInterface = false #else @Published var systemMemoryMib: Int = 512 @Published var storageSizeGib: Int = 8 @@ -251,6 +252,11 @@ enum VMWizardOS: String, Identifiable { if #available(macOS 12, *) { if operatingSystem != .Linux { nextPage = .summary // only support linux currently + } else { + if #available(macOS 14, *) { + // Use NVMe as the default disk interface to avoid filesystem corruption on macOS 14+, only available for Linux + useNvmeAsDiskInterface = true + } } } else { nextPage = .summary @@ -324,7 +330,11 @@ enum VMWizardOS: String, Identifiable { } } if !isSkipDiskCreate { - config.drives.append(UTMAppleConfigurationDrive(newSize: storageSizeGib * bytesInGib / bytesInMib)) + var newDisk = UTMAppleConfigurationDrive(newSize: storageSizeGib * bytesInGib / bytesInMib) + if #available(macOS 14, *), useNvmeAsDiskInterface { + newDisk.isNvme = true + } + config.drives.append(newDisk) } if #available(macOS 12, *), let sharingDirectoryURL = sharingDirectoryURL { config.sharedDirectories = [UTMAppleConfigurationSharedDirectory(directoryURL: sharingDirectoryURL, isReadOnly: sharingReadOnly)] diff --git a/Platform/ja.lproj/Localizable.strings b/Platform/ja.lproj/Localizable.strings index ef338056a..66aa61392 100644 --- a/Platform/ja.lproj/Localizable.strings +++ b/Platform/ja.lproj/Localizable.strings @@ -1041,3 +1041,9 @@ // UTMQemuMonitor.m "Guest panic" = "ゲストがパニック状態に陥りました"; + +/* VMConfigAppleDriveDetailsView + VMConfigAppleDriveCreateView*/ +"Use NVMe Interface" = "NVMe を使用します"; +"If checked, use NVMe instead of virtio as the disk interface, available on macOS 14+ for Linux guests only. This interface is slower but less likely to encounter filesystem errors." = "チェックされている場合、ディスクインターフェースとして virtio の代わりに NVMe を使用します。macOS 14+ では Linux ゲストのみ利用可能です。このインターフェースは遅いですが、ファイルシステムエラーが発生する可能性が低いです。"; + diff --git a/Platform/macOS/VMConfigAppleDriveCreateView.swift b/Platform/macOS/VMConfigAppleDriveCreateView.swift index 5d5244c74..74806d66b 100644 --- a/Platform/macOS/VMConfigAppleDriveCreateView.swift +++ b/Platform/macOS/VMConfigAppleDriveCreateView.swift @@ -33,11 +33,17 @@ struct VMConfigAppleDriveCreateView: View { if newValue { config.sizeMib = 0 config.isReadOnly = true + config.isNvme = false } else { config.sizeMib = 10240 config.isReadOnly = false } } + if #available(macOS 14, *), !config.isExternal { + Toggle(isOn: $config.isNvme.animation(), label: { + Text("Use NVMe Interface") + }).help("If checked, use NVMe instead of virtio as the disk interface, available on macOS 14+ for Linux guests only. This interface is slower but less likely to encounter filesystem errors.") + } if !config.isExternal { SizeTextField($config.sizeMib) } diff --git a/Platform/macOS/VMConfigAppleDriveDetailsView.swift b/Platform/macOS/VMConfigAppleDriveDetailsView.swift index b88cd0415..d96cfbbe1 100644 --- a/Platform/macOS/VMConfigAppleDriveDetailsView.swift +++ b/Platform/macOS/VMConfigAppleDriveDetailsView.swift @@ -28,6 +28,12 @@ struct VMConfigAppleDriveDetailsView: View { TextField("Name", text: .constant(config.imageURL?.lastPathComponent ?? NSLocalizedString("(New Drive)", comment: "VMConfigAppleDriveDetailsView"))) .disabled(true) Toggle("Read Only?", isOn: $config.isReadOnly) + if #available(macOS 14, *), !config.isExternal { + Toggle(isOn: $config.isNvme, + label: { + Text("Use NVMe Interface") + }).help("If checked, use NVMe instead of virtio as the disk interface, available on macOS 14+ for Linux guests only. This interface is slower but less likely to encounter filesystem errors.") + } if #unavailable(macOS 12) { Button { requestDriveDelete = config diff --git a/Platform/zh-HK.lproj/Localizable.strings b/Platform/zh-HK.lproj/Localizable.strings index f2bd35fc3..7dd027100 100644 --- a/Platform/zh-HK.lproj/Localizable.strings +++ b/Platform/zh-HK.lproj/Localizable.strings @@ -2016,3 +2016,7 @@ /* No comment provided by engineer. */ "Zoom" = "縮放"; +/* VMConfigAppleDriveDetailsView + VMConfigAppleDriveCreateView*/ +"Use NVMe Interface" = "使用 NVMe 磁碟介面"; +"If checked, use NVMe instead of virtio as the disk interface, available on macOS 14+ for Linux guests only. This interface is slower but less likely to encounter filesystem errors." = "如果勾選,將使用 NVMe 而非 virtio 作為磁碟介面,僅在 macOS 14+ 中適用於 Linux 客戶機器。這個介面速度較慢,但較不容易遇到檔案系統錯誤。"; diff --git a/Platform/zh-Hans.lproj/Localizable.strings b/Platform/zh-Hans.lproj/Localizable.strings index 875129212..9555e15d6 100644 --- a/Platform/zh-Hans.lproj/Localizable.strings +++ b/Platform/zh-Hans.lproj/Localizable.strings @@ -1225,6 +1225,7 @@ /* ContentView */ "Your version of iOS does not support running VMs while unmodified. You must either run UTM while jailbroken or with a remote debugger attached. See https://getutm.app/install/ for more details." = "你的 iOS 版本不支持在未经修改的情况下运行虚拟机,必须在越狱时运行 UTM,或者连接远程调试器。有关更多详细信息,请参阅 https://getutm.app/install/。"; +<<<<<<< HEAD // Additonal Strings (Unable to be extracted by Xcode) /* No comment provided by engineer. */ @@ -2016,3 +2017,7 @@ /* No comment provided by engineer. */ "Zoom" = "缩放"; +/* VMConfigAppleDriveDetailsView + VMConfigAppleDriveCreateView*/ +"Use NVMe Interface" = "使用 NVMe 磁盘接口"; +"If checked, use NVMe instead of virtio as the disk interface, available on macOS 14+ for Linux guests only. This interface is slower but less likely to encounter filesystem errors." = "如果选中,使用 NVMe 而不是 virtio 作为磁盘接口,仅适用于 macOS 14+ 上的 Linux 客户机。此接口速度较慢,但不太容易遇到文件系统错误。"; diff --git a/Platform/zh-Hant.lproj/Localizable.strings b/Platform/zh-Hant.lproj/Localizable.strings index ee2dd422b..8070b72bd 100644 --- a/Platform/zh-Hant.lproj/Localizable.strings +++ b/Platform/zh-Hant.lproj/Localizable.strings @@ -2002,3 +2002,8 @@ /* No comment provided by engineer. */ "Zoom" = "縮放"; +/* VMConfigAppleDriveDetailsView + VMConfigAppleDriveCreateView*/ +"Use NVMe Interface" = "使用 NVMe 磁碟介面"; +"If checked, use NVMe instead of virtio as the disk interface, available on macOS 14+ for Linux guests only. This interface is slower but less likely to encounter filesystem errors." = "如果選取,將使用 NVMe 而非 virtio 作為磁碟介面,僅在 macOS 14+ 中適用於 Linux 客戶機器。這個介面速度較慢,但較不容易遇到檔案系統錯誤。"; + From 781b06530c44ae7e93c8f419f796949662de3bf7 Mon Sep 17 00:00:00 2001 From: gnattu Date: Fri, 24 Nov 2023 15:48:40 +0800 Subject: [PATCH 06/79] config(apple): use cachingMode: .cached for virtio drive on Linux This also helps prevent filesystem corruption on Apple Virtualization and works for pre-Sonoma hosts. --- Configuration/UTMAppleConfiguration.swift | 2 +- Configuration/UTMAppleConfigurationDrive.swift | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/Configuration/UTMAppleConfiguration.swift b/Configuration/UTMAppleConfiguration.swift index 27a0ea3d3..08c8c206a 100644 --- a/Configuration/UTMAppleConfiguration.swift +++ b/Configuration/UTMAppleConfiguration.swift @@ -265,7 +265,7 @@ extension UTMAppleConfiguration { } if !ignoringDrives { vzconfig.storageDevices = try drives.compactMap { drive in - guard let attachment = try drive.vzDiskImage() else { + guard let attachment = try drive.vzDiskImage(useFsWorkAround: system.boot.operatingSystem == .linux) else { return nil } if #available(macOS 13, *), drive.isExternal { diff --git a/Configuration/UTMAppleConfigurationDrive.swift b/Configuration/UTMAppleConfigurationDrive.swift index 4725f5220..9f902f207 100644 --- a/Configuration/UTMAppleConfigurationDrive.swift +++ b/Configuration/UTMAppleConfigurationDrive.swift @@ -101,9 +101,14 @@ struct UTMAppleConfigurationDrive: UTMConfigurationDrive { try container.encode(id, forKey: .identifier) } - func vzDiskImage() throws -> VZDiskImageStorageDeviceAttachment? { + func vzDiskImage(useFsWorkAround: Bool = false) throws -> VZDiskImageStorageDeviceAttachment? { if let imageURL = imageURL { - return try VZDiskImageStorageDeviceAttachment(url: imageURL, readOnly: isReadOnly) + // Use cached caching mode for virtio drive to prevent fs corruption on linux when possible + if #available(macOS 12.0, *), !isNvme, useFsWorkAround { + return try VZDiskImageStorageDeviceAttachment(url: imageURL, readOnly: isReadOnly, cachingMode: .cached, synchronizationMode: .fsync) + } else { + return try VZDiskImageStorageDeviceAttachment(url: imageURL, readOnly: isReadOnly) + } } else { return nil } From fafadd77b8f0630dfd5d0cf04ad3221edbf3131c Mon Sep 17 00:00:00 2001 From: gnattu Date: Fri, 24 Nov 2023 18:43:35 +0800 Subject: [PATCH 07/79] config(apple): use .full for synchronizationMode as it is the framwork's default --- Configuration/UTMAppleConfigurationDrive.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Configuration/UTMAppleConfigurationDrive.swift b/Configuration/UTMAppleConfigurationDrive.swift index 9f902f207..e14488c35 100644 --- a/Configuration/UTMAppleConfigurationDrive.swift +++ b/Configuration/UTMAppleConfigurationDrive.swift @@ -105,7 +105,7 @@ struct UTMAppleConfigurationDrive: UTMConfigurationDrive { if let imageURL = imageURL { // Use cached caching mode for virtio drive to prevent fs corruption on linux when possible if #available(macOS 12.0, *), !isNvme, useFsWorkAround { - return try VZDiskImageStorageDeviceAttachment(url: imageURL, readOnly: isReadOnly, cachingMode: .cached, synchronizationMode: .fsync) + return try VZDiskImageStorageDeviceAttachment(url: imageURL, readOnly: isReadOnly, cachingMode: .cached, synchronizationMode: .full) } else { return try VZDiskImageStorageDeviceAttachment(url: imageURL, readOnly: isReadOnly) } From a142f248d4f28813be22f82c0c5463186eb7dac1 Mon Sep 17 00:00:00 2001 From: gnattu Date: Sun, 10 Dec 2023 21:51:51 +0800 Subject: [PATCH 08/79] fix: iOS build cannot find useNvmeAsDiskInterface if defined in macro --- Platform/Shared/VMWizardState.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Platform/Shared/VMWizardState.swift b/Platform/Shared/VMWizardState.swift index 7ab6e01f0..aa1b4702d 100644 --- a/Platform/Shared/VMWizardState.swift +++ b/Platform/Shared/VMWizardState.swift @@ -119,7 +119,6 @@ enum VMWizardOS: String, Identifiable { #if os(macOS) @Published var systemMemoryMib: Int = 4096 @Published var storageSizeGib: Int = 64 - @Published var useNvmeAsDiskInterface = false #else @Published var systemMemoryMib: Int = 512 @Published var storageSizeGib: Int = 8 @@ -130,6 +129,7 @@ enum VMWizardOS: String, Identifiable { @Published var sharingReadOnly: Bool = false @Published var name: String? @Published var isOpenSettingsAfterCreation: Bool = false + @Published var useNvmeAsDiskInterface = false /// SwiftUI BUG: on macOS 12, when VoiceOver is enabled and isBusy changes the disable state of a button being clicked, var isNeverDisabledWorkaround: Bool { From 5b84f4bd728689b5e01280f62be4ed34cf44b5ba Mon Sep 17 00:00:00 2001 From: gnattu Date: Sat, 30 Mar 2024 16:22:31 +0800 Subject: [PATCH 09/79] fix: prefer virtio with cached mode enabled --- Platform/Shared/VMWizardState.swift | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Platform/Shared/VMWizardState.swift b/Platform/Shared/VMWizardState.swift index aa1b4702d..036814b71 100644 --- a/Platform/Shared/VMWizardState.swift +++ b/Platform/Shared/VMWizardState.swift @@ -252,11 +252,6 @@ enum VMWizardOS: String, Identifiable { if #available(macOS 12, *) { if operatingSystem != .Linux { nextPage = .summary // only support linux currently - } else { - if #available(macOS 14, *) { - // Use NVMe as the default disk interface to avoid filesystem corruption on macOS 14+, only available for Linux - useNvmeAsDiskInterface = true - } } } else { nextPage = .summary From 9ab50f39b522f54ef005dc6d21906f50e6edb47e Mon Sep 17 00:00:00 2001 From: Tiffany Fung <46277006+changanmoon@users.noreply.github.com> Date: Wed, 3 Apr 2024 13:48:11 +0800 Subject: [PATCH 10/79] Add missing strings for zh-Hans --- Platform/zh-Hans.lproj/Localizable.strings | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Platform/zh-Hans.lproj/Localizable.strings b/Platform/zh-Hans.lproj/Localizable.strings index d089238a8..bd7945cd1 100644 --- a/Platform/zh-Hans.lproj/Localizable.strings +++ b/Platform/zh-Hans.lproj/Localizable.strings @@ -1605,6 +1605,9 @@ /* No comment provided by engineer. */ "Downscaling" = "细化"; +/* No comment provided by engineer. */ +"Dynamic Resolution" = "动态"; + /* No comment provided by engineer. */ "Edit" = "编辑"; @@ -1650,6 +1653,9 @@ /* No comment provided by engineer. */ "Enable Sound" = "启用声音"; +/* No comment provided by engineer. */ +"Enable UTM Server" = "启用 UTM 服务器"; + /* No comment provided by engineer. */ "Engine" = "引擎"; @@ -1824,6 +1830,9 @@ /* No comment provided by engineer. */ "Keyboard" = "键盘"; +/* No comment provided by engineer. */ +"Last Seen" = "最后上线于"; + /* No comment provided by engineer. */ "MAC Address" = "MAC 地址"; @@ -1896,6 +1905,9 @@ /* No comment provided by engineer. */ "Path" = "路径"; +/* No comment provided by engineer. */ +"Pointer" = "鼠标指针"; + /* No comment provided by engineer. */ "Port" = "端口"; @@ -2172,6 +2184,9 @@ /* No comment provided by engineer. */ "Use Virtualization" = "使用虚拟化"; +/* No comment provided by engineer. */ +"UTM Server" = "UTM 服务器"; + /* No comment provided by engineer. */ "VGA Device RAM (MB)" = "VGA 设备内存 (MB)"; From 30271260ce1db2107e7db7023686ef8676f73bac Mon Sep 17 00:00:00 2001 From: Tiffany Fung <46277006+changanmoon@users.noreply.github.com> Date: Wed, 3 Apr 2024 13:51:15 +0800 Subject: [PATCH 11/79] Add missing strings for zh-HK --- Platform/zh-HK.lproj/Localizable.strings | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Platform/zh-HK.lproj/Localizable.strings b/Platform/zh-HK.lproj/Localizable.strings index a0d99fa64..2bb5b1b86 100644 --- a/Platform/zh-HK.lproj/Localizable.strings +++ b/Platform/zh-HK.lproj/Localizable.strings @@ -1605,6 +1605,9 @@ /* No comment provided by engineer. */ "Downscaling" = "細化解像度"; +/* No comment provided by engineer. */ +"Dynamic Resolution" = "動態解像度"; + /* No comment provided by engineer. */ "Edit" = "編輯"; @@ -1650,6 +1653,9 @@ /* No comment provided by engineer. */ "Enable Sound" = "啟用聲音"; +/* No comment provided by engineer. */ +"Enable UTM Server" = "啟用 UTM 伺服器"; + /* No comment provided by engineer. */ "Engine" = "引擎"; @@ -1824,6 +1830,9 @@ /* No comment provided by engineer. */ "Keyboard" = "鍵盤"; +/* No comment provided by engineer. */ +"Last Seen" = "最後檢視時間"; + /* No comment provided by engineer. */ "MAC Address" = "MAC 位址"; @@ -1896,6 +1905,9 @@ /* No comment provided by engineer. */ "Path" = "路徑"; +/* No comment provided by engineer. */ +"Pointer" = "指標"; + /* No comment provided by engineer. */ "Port" = "埠"; @@ -2172,6 +2184,9 @@ /* No comment provided by engineer. */ "Use Virtualization" = "使用虛擬化"; +/* No comment provided by engineer. */ +"UTM Server" = "UTM 伺服器"; + /* No comment provided by engineer. */ "VGA Device RAM (MB)" = "VGA 裝置記憶體 (MB)"; From 4cfd9ebe3556da95bf1dccfaa88403d24b4ecfc3 Mon Sep 17 00:00:00 2001 From: Marcin Mitura Date: Fri, 5 Apr 2024 12:49:21 +0200 Subject: [PATCH 12/79] Correct missed untranslated strings. --- Platform/pl.lproj/Localizable.strings | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/Platform/pl.lproj/Localizable.strings b/Platform/pl.lproj/Localizable.strings index 678ef3ee2..34965726b 100644 --- a/Platform/pl.lproj/Localizable.strings +++ b/Platform/pl.lproj/Localizable.strings @@ -151,11 +151,11 @@ "Delete" = "Usuń"; "Discovered" = "Odnalezione"; "Make sure the latest version of UTM is running on your Mac and UTM Server is enabled. You can download UTM from the Mac App Store." = "Upewnij się, że na twoim Macu zainstalowana jest najnowsza wersja UTM oraz Serwer UTM jest włączony. Możesz pobrać UTM z App Store dla twojego Maca."; -"Name (optional)" = "名前(オプション)"; +"Name (optional)" = "Nazwa (opcjonalne)"; "Hostname or IP address" = "Nazwa hosta lub adres IP"; "Port" = "Port"; "Host" = "Host"; -"Fingerprint" = "Fingerprint"; +"Fingerprint" = "Odcisk palca"; "Password" = "Hasło"; "Save Password" = "Zapisz hasło"; "Close" = "Zamknij"; @@ -345,13 +345,14 @@ "If enabled, num lock will always be on to the guest. Note this may make your keyboard's num lock indicator out of sync." = "Jeśli włączone, NumLock zawsze będzie włączony w systemie gościa. Pamiętaj, że to może sprawić mylne działanie detektora Num Locka na twojej klawiaturze."; "QEMU USB" = "QEMU USB"; "Do not show prompt when USB device is plugged in" = "Nie pokazuj powiadomienia gdy urządzenie USB zostanie podłączone"; -"Startup" = "起動時"; +"Startup" = "Uruchomienie"; "Automatically start UTM server" = "Automatycznie uruchamiaj serwer UTM"; "Reject unknown connections by default" = "Domyślnie odrzucaj nieznane połączenia"; "If checked, you will not be prompted about any unknown connection and they will be rejected." = "Jeśli zaznaczone, nie będziesz informowany o żadnych nieznanych połączeniach, a one będą odrzucane."; "Allow access from external clients" = "Pozwalaj na dostęp dla klientów zewnętrznych"; -"By default, the server is only available on LAN but setting this will use UPnP/NAT-PMP to port forward to WAN." = "デフォルトでは、サーバはLAN上でのみ利用可能ですが、これを設定すると、UPnP/NAT-PMPを使用してWANにポート転送します。"; -"Specify a port number to listen on. This is required if external clients are permitted." = "外部からの接続を受け入れるポート番号を指定します。これは、外部クライアントが許可されている場合に必要です。"; +"By default, the server is only available on LAN but setting this will use UPnP/NAT-PMP to port forward to WAN." = "Domyślnie serwer jest dostępny tylko w sieci LAN, ale ustawienie to spowoduje użycie UPnP/NAT-PMP do przekierowania portów do sieci WAN."; +"Specify a port number to listen on. This is required if external clients are permitted." = "Określ numer portu, na którym chcesz nasłuchiwać. Jest to wymagane, jeśli klienci zewnętrzni są dopuszczeni."; +"Any" = "Ktokolwiek"; "Authentication" = "Uwierzytelnianie"; "Require Password" = "Wymagaj hasła"; "If enabled, clients must enter a password. This is required if you want to access the server externally." = "Jeśli włączone, klienci muszą wprowadzić hasło. Jest to wymagane jeśli chcesz uzyskać dostęp do serwera zewnętrznie."; @@ -993,11 +994,11 @@ "New unknown remote client connection." = "Nowe nieznane zdalne połączenie."; "New trusted remote client connection." = "Nowe zaufane zdalne połączenie"; "Unknown Remote Client" = "Nieznany klient połączenia zdalnego"; -"A client with fingerprint '%@' is attempting to connect." = "Kilent z odciskiem '%@' próbuje się połączyć."; +"A client with fingerprint '%@' is attempting to connect." = "Kilent z odciskiem palca '%@' próbuje się połączyć."; "Remote Client Connected" = "Połączono ze zdalnym klientem"; "Established connection from %@." = "Próba nawiązenia połączenia od '%@'."; -"UTM Remote Server Error" = "UTMリモートサーバエラー"; -"Cannot reserve port '%@' for external access from NAT. Make sure no other device on the network has reserved it." = "NATからの外部アクセス用のポート“%@”を予約できません。ネットワーク上のほかのデバイスが予約していないことを確認してください。"; +"UTM Remote Server Error" = "Błąd serwera zdalnego UTM"; +"Cannot reserve port '%@' for external access from NAT. Make sure no other device on the network has reserved it." = "Nie można zarezerwować portu '%@' dla dostępu zewnętrznego z NAT. Upewnij się, że żadne inne urządzenie w sieci go nie zarezerwowało."; "Not authenticated." = "Nieuwiezytelniony"; "The client interface version does not match the server." = "Wersja interfejsu klienta nie zgadza się z wersją interfejsu serwera."; "Cannot find VM with ID: %@" = "Nie udało się znaleźć maszyny wirtualnej o danym identyfikatorze: %@"; From 628d85922d9a9f1f5bb1fc1706d4da874aad81f4 Mon Sep 17 00:00:00 2001 From: Tiffany Fung <46277006+changanmoon@users.noreply.github.com> Date: Thu, 11 Apr 2024 21:04:20 +0800 Subject: [PATCH 13/79] Update Localizable.strings --- Platform/zh-HK.lproj/Localizable.strings | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Platform/zh-HK.lproj/Localizable.strings b/Platform/zh-HK.lproj/Localizable.strings index 2bb5b1b86..cc5a33363 100644 --- a/Platform/zh-HK.lproj/Localizable.strings +++ b/Platform/zh-HK.lproj/Localizable.strings @@ -1428,12 +1428,18 @@ /* No comment provided by engineer. */ "Advanced. If checked, a raw disk image is used. Raw disk image does not support snapshots and will not dynamically expand in size." = "進階選項。如選取,將會使用 Raw 磁碟映像。Raw 磁碟映像不支援快照,也不會動態擴充套件大小。"; +/* No comment provided by engineer. */ +"Allow access from external clients" = "允許外部客戶端訪問"; + /* No comment provided by engineer. */ "Allow Remote Connection" = "允許遠端連線"; /* No comment provided by engineer. */ "Allows passing through additional input from trackpads. Only supported on macOS 13+ guests." = "允許透過觸控板額外輸入。僅支援 macOS 13+ 客戶端。"; +/* No comment provided by engineer. */ +"Any" = "任意"; + /* No comment provided by engineer. */ "Apple Virtualization is experimental and only for advanced use cases. Leave unchecked to use QEMU, which is recommended." = "Apple 虛擬化為試驗性質,僅可用作進階用例,不選取此剔選框以使用推介的 QEMU。"; @@ -1452,6 +1458,9 @@ /* No comment provided by engineer. */ "Automatic" = "自動"; +/* No comment provided by engineer. */ +"Automatically start UTM server" = "自動開始 UTM 伺服器"; + /* No comment provided by engineer. */ "Background Color" = "背景顏色"; @@ -1944,12 +1953,18 @@ /* No comment provided by engineer. */ "Reclaim Space" = "釋放空間"; +/* No comment provided by engineer. */ +"Reject unknown connections by default" = "於預設情況下拒絕不明連線"; + /* No comment provided by engineer. */ "Remove selected shortcut" = "移除已選取的捷徑"; /* No comment provided by engineer. */ "Renderer Backend" = "渲染器後端"; +/* No comment provided by engineer. */ +"Require Password" = "需要密碼"; + /* No comment provided by engineer. */ "Requires restarting UTM to take affect." = "需要重新開啟 UTM 以生效。"; From 1a319c312a33a477a7688ba7da33891fcf6ae465 Mon Sep 17 00:00:00 2001 From: Tiffany Fung <46277006+changanmoon@users.noreply.github.com> Date: Thu, 11 Apr 2024 21:10:04 +0800 Subject: [PATCH 14/79] Update Localizable.strings --- Platform/zh-Hans.lproj/Localizable.strings | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Platform/zh-Hans.lproj/Localizable.strings b/Platform/zh-Hans.lproj/Localizable.strings index bd7945cd1..5e2ad0acf 100644 --- a/Platform/zh-Hans.lproj/Localizable.strings +++ b/Platform/zh-Hans.lproj/Localizable.strings @@ -1428,12 +1428,18 @@ /* No comment provided by engineer. */ "Advanced. If checked, a raw disk image is used. Raw disk image does not support snapshots and will not dynamically expand in size." = "高级选项。若选中,将使用 Raw 磁盘映像。Raw 磁盘映像不支持快照,也不会动态地扩充大小。"; +/* No comment provided by engineer. */ +"Allow access from external clients" = "允许外部客户机访问"; + /* No comment provided by engineer. */ "Allow Remote Connection" = "允许远程连接"; /* No comment provided by engineer. */ "Allows passing through additional input from trackpads. Only supported on macOS 13+ guests." = "允许通过触控板额外输入。仅支持 macOS 13 及以上的客户机。"; +/* No comment provided by engineer. */ +"Any" = "任意"; + /* No comment provided by engineer. */ "Apple Virtualization is experimental and only for advanced use cases. Leave unchecked to use QEMU, which is recommended." = "Apple 虚拟化属于实验性功能,仅适用于高级用例。推荐不选中此复选框,以使用 QEMU。"; @@ -1452,6 +1458,9 @@ /* No comment provided by engineer. */ "Automatic" = "自动"; +/* No comment provided by engineer. */ +"Automatically start UTM server" = "自动启动 UTM 服务器"; + /* No comment provided by engineer. */ "Background Color" = "背景颜色"; @@ -1944,12 +1953,18 @@ /* No comment provided by engineer. */ "Reclaim Space" = "释放空间"; +/* No comment provided by engineer. */ +"Reject unknown connections by default" = "默认情况下拒绝未知连接"; + /* No comment provided by engineer. */ "Remove selected shortcut" = "移除选中的快捷方式"; /* No comment provided by engineer. */ "Renderer Backend" = "渲染器后端"; +/* No comment provided by engineer. */ +"Require Password" = "需要密码"; + /* No comment provided by engineer. */ "Requires restarting UTM to take affect." = "需要重新打开 UTM 以生效。"; From a308b50e1bca7d87278297b104f5018bf8e7d540 Mon Sep 17 00:00:00 2001 From: Harold Date: Fri, 12 Apr 2024 13:37:18 +0200 Subject: [PATCH 15/79] Re-grab input when automatic capture is enabled Make the window auto-capture the mouse again in full screen when it regains focus (for instance after swiping back to the UTM app). fixes #6242 --- .../Display/VMDisplayQemuMetalWindowController.swift | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Platform/macOS/Display/VMDisplayQemuMetalWindowController.swift b/Platform/macOS/Display/VMDisplayQemuMetalWindowController.swift index 656e03c3a..516f0798d 100644 --- a/Platform/macOS/Display/VMDisplayQemuMetalWindowController.swift +++ b/Platform/macOS/Display/VMDisplayQemuMetalWindowController.swift @@ -403,7 +403,6 @@ extension VMDisplayQemuMetalWindowController { func windowDidEnterFullScreen(_ notification: Notification) { isFullScreen = true if isFullScreenAutoCapture { - captureMouseToolbarButton.state = .on captureMouse() } } @@ -411,11 +410,17 @@ extension VMDisplayQemuMetalWindowController { func windowDidExitFullScreen(_ notification: Notification) { isFullScreen = false if isFullScreenAutoCapture { - captureMouseToolbarButton.state = .off releaseMouse() } } + override func windowDidBecomeKey(_ notification: Notification) { + if isFullScreen && isFullScreenAutoCapture { + captureMouse() + } + super.windowDidBecomeKey(notification) + } + override func windowDidResignKey(_ notification: Notification) { releaseMouse() super.windowDidResignKey(notification) From 74ee0e1aec64b37ce320fde4292b8c886aa02fad Mon Sep 17 00:00:00 2001 From: MMP0 <28616020+MMP0@users.noreply.github.com> Date: Sat, 20 Apr 2024 14:38:12 +0900 Subject: [PATCH 16/79] Update for db72c98..11cf8af --- Platform/ja.lproj/Localizable.strings | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/Platform/ja.lproj/Localizable.strings b/Platform/ja.lproj/Localizable.strings index 96d9abf03..226ec2636 100644 --- a/Platform/ja.lproj/Localizable.strings +++ b/Platform/ja.lproj/Localizable.strings @@ -827,8 +827,14 @@ // VMWizardOSOtherView.swift "Other" = "その他"; +"Boot Device" = "起動デバイス"; +"CD/DVD Image" = "CD/DVDイメージ"; +"Floppy Image" = "フロッピーイメージ"; +"Boot IMG Image" = "起動IMGイメージ"; "Skip ISO boot" = "ISO起動をスキップ"; -"Advanced" = "詳細"; +"Legacy Hardware" = "レガシーハードウェア"; +"If checked, emulated devices with higher compatibility will be instantiated at the cost of performance." = "チェックを入れると、パフォーマンスを犠牲にして、より互換性の高い仮想デバイスがインスタンス化されます。"; +"Options" = "オプション"; // VMWizardOSView.swift "macOS 12+" = "macOS 12以降"; @@ -863,6 +869,10 @@ "Download prebuilt from UTM Gallery…" = "UTMギャラリーからビルド済みパッケージをダウンロード…"; "Existing" = "既存"; +// VMWizardStartViewTCI.swift +"New Machine" = "新規マシン"; +"Create a new emulated machine from scratch." = "新規仮想マシンをゼロから作成します。"; + // VMWizardState.swift "Please select a boot image." = "起動イメージを選択してください。"; "Please select a kernel file." = "カーネルファイルを選択してください。"; @@ -982,7 +992,7 @@ "Remote Client Connected" = "リモートクライアント接続済み"; "Established connection from %@." = "%@からの接続を確立しました。"; "UTM Remote Server Error" = "UTMリモートサーバエラー"; -"Cannot reserve port %d for external access from NAT. Make sure no other device on the network has reserved it." = "NATからの外部アクセス用のポート“%d”を予約できません。ネットワーク上のほかのデバイスが予約していないことを確認してください。"; +"Cannot reserve port %d for external access from NAT. Make sure no other device on the network has reserved it." = "NATからの外部アクセス用のポート%dを予約できません。ネットワーク上のほかのデバイスが予約していないことを確認してください。"; "Not authenticated." = "認証されていません。"; "The client interface version does not match the server." = "クライアントインターフェイスのバージョンがサーバと一致しません。"; "Cannot find VM with ID: %@" = "指定されたIDの仮想マシンが見つかりません: %@"; From 84a67ed8bf2f2600ee8f6208762b7eba02c67009 Mon Sep 17 00:00:00 2001 From: MMP0 <28616020+MMP0@users.noreply.github.com> Date: Sat, 20 Apr 2024 16:39:12 +0900 Subject: [PATCH 17/79] Small fixes --- Platform/ja.lproj/Localizable.strings | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Platform/ja.lproj/Localizable.strings b/Platform/ja.lproj/Localizable.strings index 226ec2636..dbe12fd1a 100644 --- a/Platform/ja.lproj/Localizable.strings +++ b/Platform/ja.lproj/Localizable.strings @@ -266,7 +266,7 @@ "Serial %lld" = "シリアル%lld"; // Display/VMDisplayAppleDisplayWindowController.swift -"%@ (Terminal %lld)" = "%1$@ (ターミナル%2$lld)"; +"%@ (Terminal %lld)" = "%1$@(ターミナル%2$lld)"; // Display/VMDisplayQemuDisplayController.swift "Disposable Mode" = "使い捨てモード"; @@ -831,7 +831,6 @@ "CD/DVD Image" = "CD/DVDイメージ"; "Floppy Image" = "フロッピーイメージ"; "Boot IMG Image" = "起動IMGイメージ"; -"Skip ISO boot" = "ISO起動をスキップ"; "Legacy Hardware" = "レガシーハードウェア"; "If checked, emulated devices with higher compatibility will be instantiated at the cost of performance." = "チェックを入れると、パフォーマンスを犠牲にして、より互換性の高い仮想デバイスがインスタンス化されます。"; "Options" = "オプション"; From 43b4aefd944eb2167ff74d5eedafb5fcf8c90eeb Mon Sep 17 00:00:00 2001 From: Tiffany Fung <46277006+changanmoon@users.noreply.github.com> Date: Sat, 20 Apr 2024 20:07:55 +0800 Subject: [PATCH 18/79] Update Localizable.strings --- Platform/zh-Hans.lproj/Localizable.strings | 36 ++++++++++++++++------ 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/Platform/zh-Hans.lproj/Localizable.strings b/Platform/zh-Hans.lproj/Localizable.strings index 902ce6d7f..f20d60087 100644 --- a/Platform/zh-Hans.lproj/Localizable.strings +++ b/Platform/zh-Hans.lproj/Localizable.strings @@ -62,9 +62,6 @@ /* No comment provided by engineer. */ "Additional Settings" = "附加设置"; -/* No comment provided by engineer. */ -"Advanced" = "高级"; - /* VMConfigSystemView */ "Allocating too much memory will crash the VM." = "分配过多内存会使虚拟机崩溃。"; @@ -144,6 +141,9 @@ /* No comment provided by engineer. */ "Boot Image Type" = "启动映像类型"; +/* No comment provided by engineer. */ +"Boot IMG Image" = "启动 IMG 映像"; + /* No comment provided by engineer. */ "Boot ISO Image" = "启动 ISO 映像"; @@ -218,6 +218,9 @@ UTMQemuConstants */ "CD/DVD (ISO) Image" = "CD/DVD (ISO) 映像"; +/* No comment provided by engineer. */ +"CD/DVD Image" = "CD/DVD 映像"; + /* VMDisplayWindowController */ "Change" = "更改"; @@ -276,6 +279,9 @@ /* No comment provided by engineer. */ "Create" = "创建"; +/* No comment provided by engineer. */ +"Create a new emulated machine from scratch." = "从头开始创建一个新的虚拟机。"; + /* Welcome view */ "Create a New Virtual Machine" = "创建一个新虚拟机"; @@ -461,9 +467,6 @@ /* UTMQemuConfigurationError */ "Failed to migrate configuration from a previous UTM version." = "无法从以前的 UTM 版本迁移配置。"; -/* UTMData */ -"Failed to parse download URL." = "无法解析下载 URL。"; - /* UTMRemoteKeyManager */ "Failed to parse generated key pair." = "无法解析生成的密钥对。"; @@ -496,6 +499,9 @@ UTMQemuConstants */ "Floppy" = "软盘"; +/* No comment provided by engineer. */ +"Floppy Image" = "软盘映像"; + /* No comment provided by engineer. */ "Font Size" = "字体大小"; @@ -733,6 +739,9 @@ /* No comment provided by engineer. */ "New" = "新建"; +/* No comment provided by engineer. */ +"New Machine" = "新建虚拟机"; + /* No comment provided by engineer. */ "New…" = "新建…"; @@ -809,6 +818,9 @@ /* No comment provided by engineer. */ "Option (⌥) is Meta key" = "Option (⌥) 键作为 Meta 键"; +/* No comment provided by engineer. */ +"Options" = "选项"; + /* No comment provided by engineer. */ "Other" = "其他"; @@ -1368,9 +1380,6 @@ /* No comment provided by engineer. */ "Virtualize" = "虚拟化"; -/* No comment provided by engineer. */ -"VM display size is fixed" = "虚拟机显示大小为固定"; - /* No comment provided by engineer. */ "Waiting for VM to connect to display..." = "等待虚拟机连接到显示..."; @@ -1425,6 +1434,9 @@ /* No comment provided by engineer. */ "Add read only" = "添加只读"; +/* No comment provided by engineer. */ +"Advanced" = "高级"; + /* No comment provided by engineer. */ "Advanced. If checked, a raw disk image is used. Raw disk image does not support snapshots and will not dynamically expand in size." = "高级选项。若选中,将使用 Raw 磁盘映像。Raw 磁盘映像不支持快照,也不会动态地扩充大小。"; @@ -1677,6 +1689,9 @@ /* No comment provided by engineer. */ "External Drive" = "外部磁盘"; +/* UTMData */ +"Failed to parse download URL." = "无法解析下载 URL。"; + /* No comment provided by engineer. */ "Fetch latest Windows installer…" = "获取最新的 Windows 安装程序…"; @@ -2211,6 +2226,9 @@ /* No comment provided by engineer. */ "Virtualization Engine" = "虚拟化引擎"; +/* No comment provided by engineer. */ +"VM display size is fixed" = "虚拟机显示大小为固定"; + /* No comment provided by engineer. */ "Wait for Connection" = "等待连接"; From 5e9c41eddcd3f9661c4b12cc766a956f1bbf4d61 Mon Sep 17 00:00:00 2001 From: Tiffany Fung <46277006+changanmoon@users.noreply.github.com> Date: Sat, 20 Apr 2024 20:15:21 +0800 Subject: [PATCH 19/79] Update Localizable.strings --- Platform/zh-HK.lproj/Localizable.strings | 38 +++++++++++++++++------- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/Platform/zh-HK.lproj/Localizable.strings b/Platform/zh-HK.lproj/Localizable.strings index 5fb6a5f4d..25a43ae44 100644 --- a/Platform/zh-HK.lproj/Localizable.strings +++ b/Platform/zh-HK.lproj/Localizable.strings @@ -57,14 +57,11 @@ "Add…" = "新增⋯"; /* No comment provided by engineer. */ -"Additional Options" = "附加項目"; +"Additional Options" = "附加選項"; /* No comment provided by engineer. */ "Additional Settings" = "附加設定"; -/* No comment provided by engineer. */ -"Advanced" = "進階"; - /* VMConfigSystemView */ "Allocating too much memory will crash the VM." = "分配過多記憶體會使虛擬電腦當機。"; @@ -144,6 +141,9 @@ /* No comment provided by engineer. */ "Boot Image Type" = "啟動映像檔種類"; +/* No comment provided by engineer. */ +"Boot IMG Image" = "啟動 IMG 映像檔"; + /* No comment provided by engineer. */ "Boot ISO Image" = "啟動 ISO 映像檔"; @@ -218,6 +218,9 @@ UTMQemuConstants */ "CD/DVD (ISO) Image" = "CD/DVD (ISO) 映像檔"; +/* No comment provided by engineer. */ +"CD/DVD Image" = "CD/DVD 映像檔"; + /* VMDisplayWindowController */ "Change" = "變更"; @@ -276,6 +279,9 @@ /* No comment provided by engineer. */ "Create" = "製作"; +/* No comment provided by engineer. */ +"Create a new emulated machine from scratch." = "從頭開始製作一個新的虛擬電腦。"; + /* Welcome view */ "Create a New Virtual Machine" = "製作一個新虛擬電腦"; @@ -461,9 +467,6 @@ /* UTMQemuConfigurationError */ "Failed to migrate configuration from a previous UTM version." = "無法由之前版本的 UTM 轉移設定。"; -/* UTMData */ -"Failed to parse download URL." = "無法解析已經下載的 URL。"; - /* UTMRemoteKeyManager */ "Failed to parse generated key pair." = "無法解析生成的密鑰對。"; @@ -496,6 +499,9 @@ UTMQemuConstants */ "Floppy" = "軟碟"; +/* No comment provided by engineer. */ +"Floppy Image" = "軟碟映像檔"; + /* No comment provided by engineer. */ "Font Size" = "字體大小"; @@ -733,6 +739,9 @@ /* No comment provided by engineer. */ "New" = "新增"; +/* No comment provided by engineer. */ +"New Machine" = "新增電腦"; + /* No comment provided by engineer. */ "New…" = "新增⋯"; @@ -809,6 +818,9 @@ /* No comment provided by engineer. */ "Option (⌥) is Meta key" = "將 Option (⌥) 作為 Meta 鍵"; +/* No comment provided by engineer. */ +"Options" = "選項"; + /* No comment provided by engineer. */ "Other" = "其他"; @@ -1368,9 +1380,6 @@ /* No comment provided by engineer. */ "Virtualize" = "虛擬化"; -/* No comment provided by engineer. */ -"VM display size is fixed" = "虛擬電腦顯示大小固定"; - /* No comment provided by engineer. */ "Waiting for VM to connect to display..." = "正在等待虛擬電腦連接至顯示..."; @@ -1425,6 +1434,9 @@ /* No comment provided by engineer. */ "Add read only" = "加入唯讀"; +/* No comment provided by engineer. */ +"Advanced" = "進階"; + /* No comment provided by engineer. */ "Advanced. If checked, a raw disk image is used. Raw disk image does not support snapshots and will not dynamically expand in size." = "進階選項。如選取,將會使用 Raw 磁碟映像。Raw 磁碟映像不支援快照,也不會動態擴充套件大小。"; @@ -1677,6 +1689,9 @@ /* No comment provided by engineer. */ "External Drive" = "外部磁碟"; +/* UTMData */ +"Failed to parse download URL." = "無法解析已經下載的 URL。"; + /* No comment provided by engineer. */ "Fetch latest Windows installer…" = "取得最新的 Windows 安裝工具⋯"; @@ -2211,6 +2226,9 @@ /* No comment provided by engineer. */ "Virtualization Engine" = "虛擬化引擎"; +/* No comment provided by engineer. */ +"VM display size is fixed" = "虛擬電腦顯示大小固定"; + /* No comment provided by engineer. */ "Wait for Connection" = "等待連線"; From 00a1bb55aab31f8a52daf70ee91bcb1528ad5b43 Mon Sep 17 00:00:00 2001 From: Tigran Hambardzumyan Date: Wed, 24 Apr 2024 23:41:09 +0400 Subject: [PATCH 20/79] Adding drag and drop feature for VMRemovableDrivesView --- Platform/Shared/VMRemovableDrivesView.swift | 38 ++++++++++++++++++--- Services/UTMQemuVirtualMachine.swift | 6 ++-- 2 files changed, 37 insertions(+), 7 deletions(-) diff --git a/Platform/Shared/VMRemovableDrivesView.swift b/Platform/Shared/VMRemovableDrivesView.swift index 0682533bc..ee8750f55 100644 --- a/Platform/Shared/VMRemovableDrivesView.swift +++ b/Platform/Shared/VMRemovableDrivesView.swift @@ -15,6 +15,7 @@ // import SwiftUI +import UniformTypeIdentifiers struct VMRemovableDrivesView: View { @ObservedObject var vm: VMData @@ -25,7 +26,10 @@ struct VMRemovableDrivesView: View { /// Explanation see "SwiftUI FileImporter modal bug" in the `body` @State private var workaroundFileImporterBug: Bool = false @State private var currentDrive: UTMQemuConfigurationDrive? - + + private static let shareDirectoryUTType = UTType.folder + private static let diskImageUTType = UTType.data + private var qemuVM: (any UTMSpiceVirtualMachine)! { vm.wrapped as? any UTMSpiceVirtualMachine } @@ -73,8 +77,21 @@ struct VMRemovableDrivesView: View { } else { Button("Browse…", action: { shareDirectoryFileImportPresented.toggle() }) } - }.fileImporter(isPresented: $shareDirectoryFileImportPresented, allowedContentTypes: [.folder], onCompletion: selectShareDirectory) - .disabled(mode == .virtfs && vm.state != .stopped) + }.fileImporter(isPresented: $shareDirectoryFileImportPresented, allowedContentTypes: [Self.shareDirectoryUTType], onCompletion: selectShareDirectory) + .disabled(mode == .virtfs && vm.state != .stopped) + .onDrop(of: [Self.shareDirectoryUTType], isTargeted: nil) { providers in + guard let item = providers.first, item.hasItemConformingToTypeIdentifier(Self.shareDirectoryUTType.identifier) else { return false } + + item.loadItem(forTypeIdentifier: Self.shareDirectoryUTType.identifier) { url, error in + if let url = url as? URL { + selectShareDirectory(result: .success(url)) + } + if let error = error { + selectShareDirectory(result: .failure(error)) + } + } + return true + } } ForEach(config.drives.filter { $0.isExternal }) { drive in HStack { @@ -128,12 +145,25 @@ struct VMRemovableDrivesView: View { .lineLimit(1) .truncationMode(.tail) .foregroundColor(.secondary) - }.fileImporter(isPresented: $diskImageFileImportPresented, allowedContentTypes: [.data]) { result in + }.fileImporter(isPresented: $diskImageFileImportPresented, allowedContentTypes: [Self.diskImageUTType]) { result in if let currentDrive = self.currentDrive { selectRemovableImage(forDrive: currentDrive, result: result) self.currentDrive = nil } } + .onDrop(of: [Self.diskImageUTType], isTargeted: nil) { providers in + guard let item = providers.first, item.hasItemConformingToTypeIdentifier(Self.diskImageUTType.identifier) else { return false } + + item.loadItem(forTypeIdentifier: Self.diskImageUTType.identifier) { url, error in + if let url = url as? URL{ + selectRemovableImage(forDrive: drive, result: .success(url)) + } + if let error { + selectRemovableImage(forDrive: drive, result: .failure(error)) + } + } + return true + } } } } diff --git a/Services/UTMQemuVirtualMachine.swift b/Services/UTMQemuVirtualMachine.swift index 122acac91..7d8c824e1 100644 --- a/Services/UTMQemuVirtualMachine.swift +++ b/Services/UTMQemuVirtualMachine.swift @@ -792,10 +792,10 @@ extension UTMQemuVirtualMachine { try await eject(drive, isForced: true) let file = try UTMRegistryEntry.File(url: url, isReadOnly: drive.isReadOnly) await registryEntry.setExternalDrive(file, forId: drive.id) - try await changeMedium(drive, with: tempBookmark, url: url, isSecurityScoped: false, isAccessOnly: isAccessOnly) + try await changeMedium(drive, with: tempBookmark, isSecurityScoped: false, isAccessOnly: isAccessOnly) } - private func changeMedium(_ drive: UTMQemuConfigurationDrive, with bookmark: Data, url: URL?, isSecurityScoped: Bool, isAccessOnly: Bool) async throws { + private func changeMedium(_ drive: UTMQemuConfigurationDrive, with bookmark: Data, isSecurityScoped: Bool, isAccessOnly: Bool) async throws { let system = await system ?? UTMProcess() let (success, bookmark, path) = await system.accessData(withBookmark: bookmark, securityScoped: isSecurityScoped) guard let bookmark = bookmark, let path = path, success else { @@ -818,7 +818,7 @@ extension UTMQemuVirtualMachine { let id = drive.id if let bookmark = await registryEntry.externalDrives[id]?.remoteBookmark { // an image bookmark was saved while QEMU was running - try await changeMedium(drive, with: bookmark, url: nil, isSecurityScoped: true, isAccessOnly: !isMounting) + try await changeMedium(drive, with: bookmark, isSecurityScoped: true, isAccessOnly: !isMounting) } else if let localBookmark = await registryEntry.externalDrives[id]?.bookmark { // an image bookmark was saved while QEMU was NOT running let url = try URL(resolvingPersistentBookmarkData: localBookmark) From ebd371f1eb26b4d0ebdb3266fbaa40dfedfe9124 Mon Sep 17 00:00:00 2001 From: osy <50960678+osy@users.noreply.github.com> Date: Sun, 28 Apr 2024 01:41:17 -0700 Subject: [PATCH 21/79] donation: added new view to handle IAP donations for iOS --- Platform/Shared/VMNavigationListView.swift | 31 +- Platform/iOS/Donation.storekit | 195 +++++++++++++ Platform/iOS/UTMDonateStore.swift | 203 +++++++++++++ Platform/iOS/UTMDonateView.swift | 271 ++++++++++++++++++ UTM.xcodeproj/project.pbxproj | 14 + .../xcshareddata/xcschemes/iOS-SE.xcscheme | 3 + 6 files changed, 716 insertions(+), 1 deletion(-) create mode 100644 Platform/iOS/Donation.storekit create mode 100644 Platform/iOS/UTMDonateStore.swift create mode 100644 Platform/iOS/UTMDonateView.swift diff --git a/Platform/Shared/VMNavigationListView.swift b/Platform/Shared/VMNavigationListView.swift index 5fee68f3f..2b3c2ea31 100644 --- a/Platform/Shared/VMNavigationListView.swift +++ b/Platform/Shared/VMNavigationListView.swift @@ -104,7 +104,8 @@ private struct VMListModifier: ViewModifier { @EnvironmentObject private var data: UTMData @State private var settingsPresented = false @State private var sheetPresented = false - + @State private var donatePresented = false + func body(content: Content) -> some View { content #if os(macOS) @@ -126,6 +127,15 @@ private struct VMListModifier: ViewModifier { newButton } #endif + #if !WITH_REMOTE + ToolbarItem(placement: .navigationBarLeading) { + Button { + donatePresented.toggle() + } label: { + Label("Donate", systemImage: "heart.fill") + } + } + #endif #if !os(visionOS) && !WITH_REMOTE ToolbarItem(placement: .navigationBarTrailing) { Button("Settings") { @@ -147,23 +157,37 @@ private struct VMListModifier: ViewModifier { #if !WITH_REMOTE UTMSettingsView() #endif + } else if donatePresented { + #if !os(macOS) && !WITH_REMOTE + UTMDonateView() + #endif } } .onChange(of: data.showNewVMSheet) { newValue in if newValue { settingsPresented = false + donatePresented = false sheetPresented = true } } .onChange(of: settingsPresented) { newValue in if newValue { data.showNewVMSheet = false + donatePresented = false + sheetPresented = true + } + } + .onChange(of: donatePresented) { newValue in + if newValue { + data.showNewVMSheet = false + settingsPresented = false sheetPresented = true } } .onChange(of: sheetPresented) { newValue in if !newValue { settingsPresented = false + donatePresented = false data.showNewVMSheet = false } } @@ -174,6 +198,11 @@ private struct VMListModifier: ViewModifier { .sheet(isPresented: $data.showNewVMSheet) { VMWizardView() } + #if !os(macOS) && !WITH_REMOTE + .sheet(isPresented: $donatePresented) { + UTMDonateView() + } + #endif .onReceive(NSNotification.OpenVirtualMachine) { _ in data.showNewVMSheet = false } diff --git a/Platform/iOS/Donation.storekit b/Platform/iOS/Donation.storekit new file mode 100644 index 000000000..c05231e8b --- /dev/null +++ b/Platform/iOS/Donation.storekit @@ -0,0 +1,195 @@ +{ + "identifier" : "A2B91788", + "nonRenewingSubscriptions" : [ + + ], + "products" : [ + { + "displayPrice" : "0.99", + "familyShareable" : false, + "internalID" : "4FFB8333", + "localizations" : [ + { + "description" : "The most basic building block.", + "displayName" : "Transistor", + "locale" : "en_US" + } + ], + "productID" : "consumable.small", + "referenceName" : "Transistor", + "type" : "Consumable" + }, + { + "displayPrice" : "4.99", + "familyShareable" : false, + "internalID" : "B6BBB675", + "localizations" : [ + { + "description" : "Each one has a unique functionality.", + "displayName" : "Chip", + "locale" : "en_US" + } + ], + "productID" : "consumable.medium", + "referenceName" : "Chip", + "type" : "Consumable" + }, + { + "displayPrice" : "9.99", + "familyShareable" : false, + "internalID" : "FAEB2D3C", + "localizations" : [ + { + "description" : "Gets the work done.", + "displayName" : "Computer", + "locale" : "en_US" + } + ], + "productID" : "consumable.large", + "referenceName" : "Computer", + "type" : "Consumable" + } + ], + "settings" : { + "_failTransactionsEnabled" : false, + "_locale" : "en_US", + "_storefront" : "USA", + "_storeKitErrors" : [ + { + "current" : null, + "enabled" : false, + "name" : "Load Products" + }, + { + "current" : null, + "enabled" : false, + "name" : "Purchase" + }, + { + "current" : null, + "enabled" : false, + "name" : "Verification" + }, + { + "current" : null, + "enabled" : false, + "name" : "App Store Sync" + }, + { + "current" : null, + "enabled" : false, + "name" : "Subscription Status" + }, + { + "current" : null, + "enabled" : false, + "name" : "App Transaction" + }, + { + "current" : null, + "enabled" : false, + "name" : "Manage Subscriptions Sheet" + }, + { + "current" : null, + "enabled" : false, + "name" : "Refund Request Sheet" + }, + { + "current" : null, + "enabled" : false, + "name" : "Offer Code Redeem Sheet" + } + ] + }, + "subscriptionGroups" : [ + { + "id" : "D96518D8", + "localizations" : [ + + ], + "name" : "Donation", + "subscriptions" : [ + { + "adHocOffers" : [ + + ], + "codeOffers" : [ + + ], + "displayPrice" : "0.99", + "familyShareable" : false, + "groupNumber" : 3, + "internalID" : "C7B7ED7B", + "introductoryOffer" : null, + "localizations" : [ + { + "description" : "Ones and zeros would be lonely without them.", + "displayName" : "Logic", + "locale" : "en_US" + } + ], + "productID" : "subscription.small", + "recurringSubscriptionPeriod" : "P1M", + "referenceName" : "Logic", + "subscriptionGroupID" : "D96518D8", + "type" : "RecurringSubscription" + }, + { + "adHocOffers" : [ + + ], + "codeOffers" : [ + + ], + "displayPrice" : "4.99", + "familyShareable" : false, + "groupNumber" : 2, + "internalID" : "A193204D", + "introductoryOffer" : null, + "localizations" : [ + { + "description" : "Converts inputs to outputs in a fancy way.", + "displayName" : "Function", + "locale" : "en_US" + } + ], + "productID" : "subscription.medium", + "recurringSubscriptionPeriod" : "P1M", + "referenceName" : "Function", + "subscriptionGroupID" : "D96518D8", + "type" : "RecurringSubscription" + }, + { + "adHocOffers" : [ + + ], + "codeOffers" : [ + + ], + "displayPrice" : "9.99", + "familyShareable" : false, + "groupNumber" : 1, + "internalID" : "08CDD5EB", + "introductoryOffer" : null, + "localizations" : [ + { + "description" : "Maybe it was all a mistake.", + "displayName" : "Software", + "locale" : "en_US" + } + ], + "productID" : "subscription.large", + "recurringSubscriptionPeriod" : "P1M", + "referenceName" : "Software", + "subscriptionGroupID" : "D96518D8", + "type" : "RecurringSubscription" + } + ] + } + ], + "version" : { + "major" : 3, + "minor" : 0 + } +} diff --git a/Platform/iOS/UTMDonateStore.swift b/Platform/iOS/UTMDonateStore.swift new file mode 100644 index 000000000..24e0124a8 --- /dev/null +++ b/Platform/iOS/UTMDonateStore.swift @@ -0,0 +1,203 @@ +// +// Copyright © 2024 osy. 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 StoreKit + +@available(iOS 15, *) +class UTMDonateStore: ObservableObject { + typealias RenewalState = StoreKit.Product.SubscriptionInfo.RenewalState + + enum StoreError: Error { + case failedVerification + } + + let productImages: [String: String] = [ + "consumable.small": "switch.2", + "consumable.medium": "memorychip", + "consumable.large": "pc", + "subscription.small": "sum", + "subscription.medium": "function", + "subscription.large": "opticaldisc", + ] + + @Published private(set) var consumables: [Product] + @Published private(set) var subscriptions: [Product] + + @Published private(set) var purchasedSubscriptions: [Product] = [] + @Published private(set) var subscriptionGroupStatus: RenewalState? + + @Published private(set) var isLoaded: Bool = false + @Published private(set) var id: UUID = UUID() + + var updateListenerTask: Task? = nil + + init() { + //Initialize empty products, and then do a product request asynchronously to fill them in. + consumables = [] + subscriptions = [] + + //Start a transaction listener as close to app launch as possible so you don't miss any transactions. + updateListenerTask = listenForTransactions() + + Task { + //During store initialization, request products from the App Store. + await requestProducts() + + //Deliver products that the customer purchases. + await updateCustomerProductStatus() + } + } + + deinit { + updateListenerTask?.cancel() + } + + func listenForTransactions() -> Task { + return Task.detached { + //Iterate through any transactions that don't come from a direct call to `purchase()`. + for await result in Transaction.updates { + do { + let transaction = try self.checkVerified(result) + + //Deliver products to the user. + await self.updateCustomerProductStatus() + + //Always finish a transaction. + await transaction.finish() + } catch { + //StoreKit has a transaction that fails verification. Don't deliver content to the user. + logger.error("Transaction failed verification") + } + } + } + } + + @MainActor + func requestProducts() async { + isLoaded = false + do { + let storeProducts = try await Product.products(for: productImages.keys) + + var newConsumables: [Product] = [] + var newSubscriptions: [Product] = [] + + //Filter the products into categories based on their type. + for product in storeProducts { + switch product.type { + case .consumable: + newConsumables.append(product) + case .autoRenewable: + newSubscriptions.append(product) + default: + //Ignore this product. + logger.error("Unknown product: \(product)") + } + } + + //Sort each product category by price, lowest to highest, to update the store. + consumables = sortByPrice(newConsumables) + subscriptions = sortByPrice(newSubscriptions) + } catch { + logger.error("Failed product request from the App Store server: \(error)") + } + isLoaded = true + } + + func purchase(with action: () async throws -> Product.PurchaseResult) async throws -> Transaction? { + //Begin purchasing the `Product` the user selects. + let result = try await action() + + switch result { + case .success(let verification): + //Check whether the transaction is verified. If it isn't, + //this function rethrows the verification error. + let transaction = try checkVerified(verification) + + //The transaction is verified. Deliver content to the user. + await updateCustomerProductStatus() + + //Always finish a transaction. + await transaction.finish() + + return transaction + case .userCancelled, .pending: + return nil + default: + return nil + } + } + + func isPurchased(_ product: Product) async throws -> Bool { + //Determine whether the user purchases a given product. + switch product.type { + case .autoRenewable: + return purchasedSubscriptions.contains(product) + default: + return false + } + } + + func checkVerified(_ result: VerificationResult) throws -> T { + //Check whether the JWS passes StoreKit verification. + switch result { + case .unverified: + //StoreKit parses the JWS, but it fails verification. + throw StoreError.failedVerification + case .verified(let safe): + //The result is verified. Return the unwrapped value. + return safe + } + } + + @MainActor + func updateCustomerProductStatus() async { + var purchasedSubscriptions: [Product] = [] + + //Iterate through all of the user's purchased products. + for await result in Transaction.currentEntitlements { + do { + //Check whether the transaction is verified. If it isn’t, catch `failedVerification` error. + let transaction = try checkVerified(result) + + //Check the `productType` of the transaction and get the corresponding product from the store. + switch transaction.productType { + case .autoRenewable: + if let subscription = subscriptions.first(where: { $0.id == transaction.productID }) { + purchasedSubscriptions.append(subscription) + } + default: + break + } + } catch { + logger.error("failed to update product status: \(error)") + } + } + + //Update the store information with auto-renewable subscription products. + self.purchasedSubscriptions = purchasedSubscriptions + + //Check the `subscriptionGroupStatus` to learn the auto-renewable subscription state to determine whether the customer + //is new (never subscribed), active, or inactive (expired subscription). This app has only one subscription + //group, so products in the subscriptions array all belong to the same group. The statuses that + //`product.subscription.status` returns apply to the entire subscription group. + subscriptionGroupStatus = try? await subscriptions.first?.subscription?.status.first?.state + } + + func sortByPrice(_ products: [Product]) -> [Product] { + products.sorted(by: { return $0.price < $1.price }) + } +} diff --git a/Platform/iOS/UTMDonateView.swift b/Platform/iOS/UTMDonateView.swift new file mode 100644 index 000000000..a3279ac34 --- /dev/null +++ b/Platform/iOS/UTMDonateView.swift @@ -0,0 +1,271 @@ +// +// Copyright © 2024 osy. 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 SwiftUI +import StoreKit + +struct UTMDonateView: View { + @Environment(\.presentationMode) var presentationMode + + private var appIcon: String? { + guard let icons = Bundle.main.object(forInfoDictionaryKey: "CFBundleIcons") as? [String: Any], + let primaryIcon = icons["CFBundlePrimaryIcon"] as? [String: Any], + let iconFiles = primaryIcon["CFBundleIconFiles"] as? [String], + let iconFileName = iconFiles.last else { + return nil + } + return iconFileName + } + + var body: some View { + NavigationView { + VStack { + if let appIcon = appIcon, let image = UIImage(named: appIcon) { + Image(uiImage: image) + .resizable() + .aspectRatio(contentMode: .fit) + .clipShape(RoundedRectangle(cornerRadius: 12.632, style: .continuous)) + .frame(width: 72, height: 72) + } + Text("Your support is the driving force that helps UTM stay independent. Your contribution, no matter the size, makes a significant difference. It enables us to develop new features and maintain existing ones. Thank you for considering a donation to support us.") + .padding() + if #available(iOS 15, *) { + StoreView() + } else { + List { + Link("GitHub Sponsors", destination: URL(string: "https://github.com/sponsors/utmapp")!) + } + } + }.navigationTitle("Support UTM") + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .topBarTrailing) { + Button("Close") { + presentationMode.wrappedValue.dismiss() + } + } + } + }.navigationViewStyle(.stack) + } +} + +@available(iOS 15, *) +private struct StoreView: View { + @StateObject private var store = UTMDonateStore() + + var body: some View { + if !store.isLoaded { + ProgressView() + Spacer() + } else { + List { + if !store.consumables.isEmpty { + Section("One Time Donation") { + ForEach(store.consumables) { item in + ListCellView(store: store, product: item) + } + } + .listStyle(.grouped) + } + if !store.subscriptions.isEmpty { + Section("Recurring Donation") { + ForEach(store.subscriptions) { item in + ListCellView(store: store, product: item) + } + Link("Manage Subscriptions…", destination: URL(string: "itms-apps://apps.apple.com/account/subscriptions")!) + } + .listStyle(.grouped) + } + if store.consumables.isEmpty && store.subscriptions.isEmpty { + Link("GitHub Sponsors", destination: URL(string: "https://github.com/sponsors/utmapp")!) + } else { + Button("Restore Purchases") { + Task { + try? await AppStore.sync() + } + } + } + } + } + } +} + +@available(iOS 15, *) +private struct ListCellView: View { + @ObservedObject var store: UTMDonateStore + @State var isPurchased: Bool = false + @State var errorTitle = "" + @State var isShowingError: Bool = false + @State var isLoaded: Bool = false + + #if os(visionOS) + @Environment(\.purchase) var purchase + #endif + + let product: Product + let purchasingEnabled: Bool + + var systemImage: String? { + store.productImages[product.id] + } + + init(store: UTMDonateStore, product: Product, purchasingEnabled: Bool = true) { + self.store = store + self.product = product + self.purchasingEnabled = purchasingEnabled + } + + var body: some View { + HStack { + Image(systemName: systemImage ?? "heart.fill") + .font(.system(size: 36)) + .frame(width: 48, height: 48) + .padding(.trailing, 20) + if purchasingEnabled { + productDetail + Spacer() + buyButton + .buttonStyle(BuyButtonStyle(isPurchased: isPurchased)) + .disabled(isPurchased) + } else { + productDetail + } + } + .alert(isPresented: $isShowingError, content: { + Alert(title: Text(errorTitle), message: nil, dismissButton: .default(Text("OK"))) + }) + } + + @ViewBuilder + var productDetail: some View { + VStack(alignment: .leading) { + Text(product.displayName) + .bold() + Text(product.description) + } + } + + func subscribeButton(_ subscription: Product.SubscriptionInfo) -> some View { + let unit: String + let plural = 1 < subscription.subscriptionPeriod.value + switch subscription.subscriptionPeriod.unit { + case .day: + unit = plural ? String.localizedStringWithFormat(NSLocalizedString("%d days", comment: "UTMDonateView"), subscription.subscriptionPeriod.value) : NSLocalizedString("day", comment: "UTMDonateView") + case .week: + unit = plural ? String.localizedStringWithFormat(NSLocalizedString("%d weeks", comment: "UTMDonateView"), subscription.subscriptionPeriod.value) : NSLocalizedString("week", comment: "UTMDonateView") + case .month: + unit = plural ? String.localizedStringWithFormat(NSLocalizedString("%d months", comment: "UTMDonateView"), subscription.subscriptionPeriod.value) : NSLocalizedString("month", comment: "UTMDonateView") + case .year: + unit = plural ? String.localizedStringWithFormat(NSLocalizedString("%d years", comment: "UTMDonateView"), subscription.subscriptionPeriod.value) : NSLocalizedString("year", comment: "UTMDonateView") + @unknown default: + unit = NSLocalizedString("period", comment: "UTMDonateView") + } + + return VStack { + Text(product.displayPrice) + .foregroundColor(.white) + .bold() + .padding(EdgeInsets(top: -4.0, leading: 0.0, bottom: -8.0, trailing: 0.0)) + Divider() + .background(Color.white) + Text(unit) + .foregroundColor(.white) + .font(.system(size: 12)) + .padding(EdgeInsets(top: -8.0, leading: 0.0, bottom: -4.0, trailing: 0.0)) + } + } + + var buyButton: some View { + Button(action: { + Task { + await buy() + } + }) { + if !isLoaded { + ProgressView() + .tint(.white) + } else if isPurchased { + Text(Image(systemName: "checkmark")) + .bold() + .foregroundColor(.white) + } else { + if let subscription = product.subscription { + subscribeButton(subscription) + } else { + Text(product.displayPrice) + .foregroundColor(.white) + .bold() + } + } + } + .onAppear { + Task { + isPurchased = (try? await store.isPurchased(product)) ?? false + isLoaded = true + } + } + .onChange(of: store.purchasedSubscriptions) { _ in + Task { + isPurchased = (try? await store.isPurchased(product)) ?? false + } + } + } + + func buy() async { + do { + if try await store.purchase(with: { + #if os(visionOS) + try await purchase(product) + #else + try await product.purchase() + #endif + }) != nil { + withAnimation { + isPurchased = true + } + } + } catch UTMDonateStore.StoreError.failedVerification { + errorTitle = NSLocalizedString("Your purchase could not be verified by the App Store.", comment: "UTMDonateView") + isShowingError = true + } catch { + logger.error("Failed purchase for \(product.id): \(error)") + } + } +} + +private struct BuyButtonStyle: ButtonStyle { + let isPurchased: Bool + + init(isPurchased: Bool = false) { + self.isPurchased = isPurchased + } + + func makeBody(configuration: Self.Configuration) -> some View { + var bgColor: Color = isPurchased ? Color.green : Color.blue + bgColor = configuration.isPressed ? bgColor.opacity(0.7) : bgColor.opacity(1) + + return configuration.label + .frame(width: 50) + .padding(10) + .background(bgColor) + .clipShape(RoundedRectangle(cornerRadius: 20, style: .continuous)) + .scaleEffect(configuration.isPressed ? 0.9 : 1.0) + } +} + +#Preview { + UTMDonateView() +} diff --git a/UTM.xcodeproj/project.pbxproj b/UTM.xcodeproj/project.pbxproj index 2e2de2cad..da6e84eed 100644 --- a/UTM.xcodeproj/project.pbxproj +++ b/UTM.xcodeproj/project.pbxproj @@ -426,6 +426,10 @@ 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 */; }; + 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 */; }; + CE231D472BDDFD03006D6DC3 /* UTMDonateStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE231D452BDDFD03006D6DC3 /* UTMDonateStore.swift */; }; CE25124729BFDB87000790AB /* UTMScriptingGuestProcessImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE25124629BFDB87000790AB /* UTMScriptingGuestProcessImpl.swift */; }; CE25124929BFDBA6000790AB /* UTMScriptingGuestFileImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE25124829BFDBA6000790AB /* UTMScriptingGuestFileImpl.swift */; }; CE25124B29BFE273000790AB /* UTMScriptable.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE25124A29BFE273000790AB /* UTMScriptable.swift */; }; @@ -1786,6 +1790,9 @@ CE1AEC3E2B78B30700992AFC /* MacDeviceLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MacDeviceLabel.swift; sourceTree = ""; }; 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 = ""; }; + CE231D442BDDFA61006D6DC3 /* Donation.storekit */ = {isa = PBXFileReference; lastKnownFileType = text; path = Donation.storekit; sourceTree = ""; }; + CE231D452BDDFD03006D6DC3 /* UTMDonateStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTMDonateStore.swift; sourceTree = ""; }; CE25124629BFDB87000790AB /* UTMScriptingGuestProcessImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTMScriptingGuestProcessImpl.swift; sourceTree = ""; }; CE25124829BFDBA6000790AB /* UTMScriptingGuestFileImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTMScriptingGuestFileImpl.swift; sourceTree = ""; }; CE25124A29BFE273000790AB /* UTMScriptable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTMScriptable.swift; sourceTree = ""; }; @@ -2674,6 +2681,8 @@ CE08334A2B784FD400522C03 /* RemoteContentView.swift */, 841E58D02893AF5400137A20 /* UTMApp.swift */, CEBBF1A424B56A2900C15049 /* UTMDataExtension.swift */, + CE231D452BDDFD03006D6DC3 /* UTMDonateStore.swift */, + CE231D412BDDF280006D6DC3 /* UTMDonateView.swift */, 841E58CA28937EE200137A20 /* UTMExternalSceneDelegate.swift */, 841E58CD28937FED00137A20 /* UTMSingleWindowView.swift */, 842B9F8C28CC58B700031EE7 /* UTMPatches.swift */, @@ -2698,6 +2707,7 @@ CEB5C1192B8C4CD4008AAE5C /* Info-RemotePlist.strings */, CEC1B00A2BBB211C0088119D /* PrivacyInfo.xcprivacy */, 5286EC91243748AC007E6CBC /* Settings.bundle */, + CE231D442BDDFA61006D6DC3 /* Donation.storekit */, ); path = iOS; sourceTree = ""; @@ -3493,6 +3503,7 @@ buildActionMask = 2147483647; files = ( CE2D926A24AD46670059923A /* VMDisplayMetalViewController+Pointer.h in Sources */, + CE231D462BDDFD03006D6DC3 /* UTMDonateStore.swift in Sources */, 84C4D9022880CA8A00EC3B2B /* VMSettingsAddDeviceMenuView.swift in Sources */, CE2D956F24AD4F990059923A /* VMRemovableDrivesView.swift in Sources */, 8443EFF22845641600B2E6E2 /* UTMQemuConfigurationDrive.swift in Sources */, @@ -3619,6 +3630,7 @@ 848F71EC277A2F47006A0240 /* UTMSerialPortDelegate.swift in Sources */, CE2D955D24AD4F990059923A /* VMConfigSoundView.swift in Sources */, CE2D930424AD46670059923A /* UTMLegacyQemuConfiguration.m in Sources */, + CE231D422BDDF280006D6DC3 /* UTMDonateView.swift in Sources */, 841E999828AC817D003C6CB6 /* UTMQemuVirtualMachine.swift in Sources */, CE2D957924AD4F990059923A /* VMDetailsView.swift in Sources */, CE2D930524AD46670059923A /* VMDisplayMetalViewController.m in Sources */, @@ -3856,6 +3868,7 @@ 84C505AD28C588EC007CE8FF /* SizeTextField.swift in Sources */, CEA45E27263519B5002FA97D /* VMRemovableDrivesView.swift in Sources */, CEF0306E26A2AFDF00667B63 /* VMWizardOSView.swift in Sources */, + CE231D432BDDF280006D6DC3 /* UTMDonateView.swift in Sources */, CE65BABF26A4D8DD0001BD6B /* VMConfigDisplayConsoleView.swift in Sources */, 848F71ED277A2F47006A0240 /* UTMSerialPortDelegate.swift in Sources */, 83A004BA26A8CC95001AC09E /* UTMDownloadTask.swift in Sources */, @@ -3900,6 +3913,7 @@ CEA45E6A263519B5002FA97D /* VMDisplayMetalViewController+Touch.m in Sources */, CEA45E6B263519B5002FA97D /* UTMLegacyQemuConfiguration+Display.m in Sources */, CEA45E6C263519B5002FA97D /* UTMVirtualMachine.swift in Sources */, + CE231D472BDDFD03006D6DC3 /* UTMDonateStore.swift in Sources */, 84B36D2A27B790BE00C22685 /* DestructiveButton.swift in Sources */, CEF469EF2BD2D165005A0B68 /* VMWizardStartViewTCI.swift in Sources */, 842B9F8E28CC58B700031EE7 /* UTMPatches.swift in Sources */, diff --git a/UTM.xcodeproj/xcshareddata/xcschemes/iOS-SE.xcscheme b/UTM.xcodeproj/xcshareddata/xcschemes/iOS-SE.xcscheme index 00a0fb665..e1403330a 100644 --- a/UTM.xcodeproj/xcshareddata/xcschemes/iOS-SE.xcscheme +++ b/UTM.xcodeproj/xcshareddata/xcschemes/iOS-SE.xcscheme @@ -51,6 +51,9 @@ ReferencedContainer = "container:UTM.xcodeproj"> + + Date: Mon, 29 Apr 2024 13:16:55 -0700 Subject: [PATCH 22/79] data: improve performance of APFS spase copy Apple's library has a couple of performance bugs which we fix in our own library. Fixes #6262 --- Platform/UTMData.swift | 8 +--- UTM.xcodeproj/project.pbxproj | 41 +++++++++++++++++++ .../xcshareddata/swiftpm/Package.resolved | 12 +++++- 3 files changed, 54 insertions(+), 7 deletions(-) diff --git a/Platform/UTMData.swift b/Platform/UTMData.swift index 66ee559d1..f72d9e1a5 100644 --- a/Platform/UTMData.swift +++ b/Platform/UTMData.swift @@ -27,6 +27,7 @@ import AltKit #if WITH_SERVER import Combine #endif +import SwiftCopyfile #if WITH_REMOTE import CocoaSpiceNoUsb @@ -661,12 +662,7 @@ struct AlertMessage: Identifiable { } private func copyItemWithCopyfile(at srcURL: URL, to dstURL: URL) async throws { - try await Task.detached(priority: .userInitiated) { - let status = copyfile(srcURL.path, dstURL.path, nil, copyfile_flags_t(COPYFILE_ALL | COPYFILE_RECURSIVE | COPYFILE_CLONE | COPYFILE_DATA_SPARSE)) - if status < 0 { - throw NSError(domain: NSPOSIXErrorDomain, code: Int(errno)) - } - }.value + try await CopyManager.default.copyItem(at: srcURL, to: dstURL, flags: [.all, .recursive, .clone, .dataSparse]) } // MARK: - Downloading VMs diff --git a/UTM.xcodeproj/project.pbxproj b/UTM.xcodeproj/project.pbxproj index da6e84eed..a6829459d 100644 --- a/UTM.xcodeproj/project.pbxproj +++ b/UTM.xcodeproj/project.pbxproj @@ -430,6 +430,10 @@ CE231D432BDDF280006D6DC3 /* UTMDonateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE231D412BDDF280006D6DC3 /* UTMDonateView.swift */; }; CE231D462BDDFD03006D6DC3 /* UTMDonateStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE231D452BDDFD03006D6DC3 /* UTMDonateStore.swift */; }; CE231D472BDDFD03006D6DC3 /* UTMDonateStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE231D452BDDFD03006D6DC3 /* UTMDonateStore.swift */; }; + CE231D522BE03617006D6DC3 /* SwiftCopyfile in Frameworks */ = {isa = PBXBuildFile; productRef = CE231D512BE03617006D6DC3 /* SwiftCopyfile */; }; + CE231D542BE03630006D6DC3 /* SwiftCopyfile in Frameworks */ = {isa = PBXBuildFile; productRef = CE231D532BE03630006D6DC3 /* SwiftCopyfile */; }; + CE231D562BE03636006D6DC3 /* SwiftCopyfile in Frameworks */ = {isa = PBXBuildFile; productRef = CE231D552BE03636006D6DC3 /* SwiftCopyfile */; }; + CE231D5A2BE03791006D6DC3 /* SwiftCopyfile in Frameworks */ = {isa = PBXBuildFile; productRef = CE231D592BE03791006D6DC3 /* SwiftCopyfile */; }; CE25124729BFDB87000790AB /* UTMScriptingGuestProcessImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE25124629BFDB87000790AB /* UTMScriptingGuestProcessImpl.swift */; }; CE25124929BFDBA6000790AB /* UTMScriptingGuestFileImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE25124829BFDBA6000790AB /* UTMScriptingGuestFileImpl.swift */; }; CE25124B29BFE273000790AB /* UTMScriptable.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE25124A29BFE273000790AB /* UTMScriptable.swift */; }; @@ -2119,6 +2123,7 @@ CE2D933024AD46670059923A /* libgstvideoscale.a in Frameworks */, CE93759924BB821F0074066F /* IQKeyboardManagerSwift in Frameworks */, CE2D933124AD46670059923A /* MetalKit.framework in Frameworks */, + CE231D542BE03630006D6DC3 /* SwiftCopyfile in Frameworks */, CE2D933224AD46670059923A /* libgstvolume.a in Frameworks */, CE2D933324AD46670059923A /* libgstcoreelements.a in Frameworks */, CE2D933424AD46670059923A /* libgstvideorate.a in Frameworks */, @@ -2261,6 +2266,7 @@ CE0B6F0224AD677200FE012D /* libgstjpeg.a in Frameworks */, CE0B6EFC24AD677200FE012D /* libgstaudiotestsrc.a in Frameworks */, CE0B6EF824AD677200FE012D /* gstsdp-1.0.0.framework in Frameworks */, + CE231D522BE03617006D6DC3 /* SwiftCopyfile in Frameworks */, CE0B6EEA24AD677200FE012D /* libgstcoreelements.a in Frameworks */, 83993290272F4A400059355F /* ZIPFoundation in Frameworks */, ); @@ -2340,6 +2346,7 @@ CEA45F58263519B5002FA97D /* crypto.1.1.framework in Frameworks */, CEA45F59263519B5002FA97D /* gstpbutils-1.0.0.framework in Frameworks */, CEA45F5A263519B5002FA97D /* gstallocators-1.0.0.framework in Frameworks */, + CE231D562BE03636006D6DC3 /* SwiftCopyfile in Frameworks */, CEA45F5B263519B5002FA97D /* gstcheck-1.0.0.framework in Frameworks */, CEA45F5C263519B5002FA97D /* iconv.2.framework in Frameworks */, CEA45F5D263519B5002FA97D /* gstsdp-1.0.0.framework in Frameworks */, @@ -2424,6 +2431,7 @@ CEF7F66F2AEEDCC400E34952 /* gstallocators-1.0.0.framework in Frameworks */, CEF7F6702AEEDCC400E34952 /* gstcheck-1.0.0.framework in Frameworks */, CEF7F6712AEEDCC400E34952 /* iconv.2.framework in Frameworks */, + CE231D5A2BE03791006D6DC3 /* SwiftCopyfile in Frameworks */, CEF7F6722AEEDCC400E34952 /* gstsdp-1.0.0.framework in Frameworks */, CEF7F6742AEEDCC400E34952 /* ssl.1.1.framework in Frameworks */, CEF7F6762AEEDCC400E34952 /* pixman-1.0.framework in Frameworks */, @@ -3079,6 +3087,7 @@ 84A0A8892A47D5D10038F329 /* QEMUKit */, CE9B15372B11A4A7003A32DD /* SwiftConnect */, CE89CB0D2B8B1B5A006B2CC2 /* VisionKeyboardKit */, + CE231D532BE03630006D6DC3 /* SwiftCopyfile */, ); productName = UTM; productReference = CE2D93BE24AD46670059923A /* UTM.app */; @@ -3112,6 +3121,7 @@ 84A0A8872A47D5C50038F329 /* QEMUKit */, CE9B15352B11A491003A32DD /* SwiftConnect */, CEDD11C02B7C74D7004DDAC6 /* SwiftPortmap */, + CE231D512BE03617006D6DC3 /* SwiftCopyfile */, ); productName = UTM; productReference = CE2D951C24AD48BE0059923A /* UTM.app */; @@ -3162,6 +3172,7 @@ 84A0A88B2A47D5D70038F329 /* QEMUKit */, CE9B15392B11A4AE003A32DD /* SwiftConnect */, CE89CB0F2B8B1B6A006B2CC2 /* VisionKeyboardKit */, + CE231D552BE03636006D6DC3 /* SwiftCopyfile */, ); productName = UTM; productReference = CEA45FB9263519B5002FA97D /* UTM SE.app */; @@ -3211,6 +3222,7 @@ CEF7F6D52AEEEF7D00E34952 /* CocoaSpiceNoUsb */, CE9B153B2B11A4B4003A32DD /* SwiftConnect */, CE89CB112B8B1B7A006B2CC2 /* VisionKeyboardKit */, + CE231D592BE03791006D6DC3 /* SwiftCopyfile */, ); productName = UTM; productReference = CEF7F6D32AEEDCC400E34952 /* UTM Remote.app */; @@ -3284,6 +3296,7 @@ CE9B15342B11A491003A32DD /* XCRemoteSwiftPackageReference "SwiftConnect" */, CEDD11BF2B7C74D7004DDAC6 /* XCRemoteSwiftPackageReference "SwiftPortmap" */, CE89CB0C2B8B1B49006B2CC2 /* XCRemoteSwiftPackageReference "VisionKeyboardKit" */, + CE231D502BE03617006D6DC3 /* XCRemoteSwiftPackageReference "SwiftCopyfile" */, ); productRefGroup = CE550BCA225947990063E575 /* Products */; projectDirPath = ""; @@ -5191,6 +5204,14 @@ minimumVersion = 1.5.3; }; }; + CE231D502BE03617006D6DC3 /* XCRemoteSwiftPackageReference "SwiftCopyfile" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/osy/SwiftCopyfile.git"; + requirement = { + branch = main; + kind = branch; + }; + }; CE89CB0C2B8B1B49006B2CC2 /* XCRemoteSwiftPackageReference "VisionKeyboardKit" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/utmapp/VisionKeyboardKit.git"; @@ -5404,6 +5425,26 @@ package = CE020BA524AEDEF000B44AB6 /* XCRemoteSwiftPackageReference "swift-log" */; productName = Logging; }; + CE231D512BE03617006D6DC3 /* SwiftCopyfile */ = { + isa = XCSwiftPackageProductDependency; + package = CE231D502BE03617006D6DC3 /* XCRemoteSwiftPackageReference "SwiftCopyfile" */; + productName = SwiftCopyfile; + }; + CE231D532BE03630006D6DC3 /* SwiftCopyfile */ = { + isa = XCSwiftPackageProductDependency; + package = CE231D502BE03617006D6DC3 /* XCRemoteSwiftPackageReference "SwiftCopyfile" */; + productName = SwiftCopyfile; + }; + CE231D552BE03636006D6DC3 /* SwiftCopyfile */ = { + isa = XCSwiftPackageProductDependency; + package = CE231D502BE03617006D6DC3 /* XCRemoteSwiftPackageReference "SwiftCopyfile" */; + productName = SwiftCopyfile; + }; + CE231D592BE03791006D6DC3 /* SwiftCopyfile */ = { + isa = XCSwiftPackageProductDependency; + package = CE231D502BE03617006D6DC3 /* XCRemoteSwiftPackageReference "SwiftCopyfile" */; + productName = SwiftCopyfile; + }; CE89CB0D2B8B1B5A006B2CC2 /* VisionKeyboardKit */ = { isa = XCSwiftPackageProductDependency; package = CE89CB0C2B8B1B49006B2CC2 /* XCRemoteSwiftPackageReference "VisionKeyboardKit" */; diff --git a/UTM.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/UTM.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 3d420c8b7..731586155 100644 --- a/UTM.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/UTM.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,4 +1,5 @@ { + "originHash" : "7b5e2ff74c08d3efce9babb7649d891f7ad18d9c868e75db7d9e26ce542055cc", "pins" : [ { "identity" : "altkit", @@ -81,6 +82,15 @@ "revision" : "af855e47ca222da163cc7f4f185230f36ba8694a" } }, + { + "identity" : "swiftcopyfile", + "kind" : "remoteSourceControl", + "location" : "https://github.com/osy/SwiftCopyfile.git", + "state" : { + "branch" : "main", + "revision" : "faac327beaed77c1a3ec8e899887835777782f30" + } + }, { "identity" : "swiftportmap", "kind" : "remoteSourceControl", @@ -127,5 +137,5 @@ } } ], - "version" : 2 + "version" : 3 } From 5fc988f681716e9d96096f70399040bc79cd27c0 Mon Sep 17 00:00:00 2001 From: osy <50960678+osy@users.noreply.github.com> Date: Mon, 29 Apr 2024 17:08:15 -0700 Subject: [PATCH 23/79] config(macOS): show progress indicator for long tasks Resolves #4006 --- Platform/Shared/BusyIndicator.swift | 27 ++++++++++++++++++++++++++- Platform/Shared/BusyOverlay.swift | 2 +- Platform/UTMData.swift | 3 +++ 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/Platform/Shared/BusyIndicator.swift b/Platform/Shared/BusyIndicator.swift index aef05d8e3..7a02d8af8 100644 --- a/Platform/Shared/BusyIndicator.swift +++ b/Platform/Shared/BusyIndicator.swift @@ -17,13 +17,38 @@ import SwiftUI struct BusyIndicator: View { + @Binding var progress: Float? + + init(progress: Binding = .constant(nil)) { + _progress = progress + } + var body: some View { - Spinner(size: .large) + progressView .frame(width: 100, height: 100, alignment: .center) .foregroundColor(.white) .background(Color.gray.opacity(0.5)) .clipShape(RoundedRectangle(cornerRadius: 25.0, style: .continuous)) } + + #if os(macOS) + @ViewBuilder + private var progressView: some View { + if let progress = progress { + ProgressView(value: progress) + .progressViewStyle(.circular) + .controlSize(.large) + } else { + Spinner(size: .large) + } + } + #else + // TODO: implement progress spinner for iOS + @ViewBuilder + private var progressView: some View { + Spinner(size: .large) + } + #endif } struct BusyIndicator_Previews: PreviewProvider { diff --git a/Platform/Shared/BusyOverlay.swift b/Platform/Shared/BusyOverlay.swift index ce6febdc7..17fc29129 100644 --- a/Platform/Shared/BusyOverlay.swift +++ b/Platform/Shared/BusyOverlay.swift @@ -22,7 +22,7 @@ struct BusyOverlay: View { var body: some View { Group { if data.busy { - BusyIndicator() + BusyIndicator(progress: $data.busyProgress) } else { EmptyView() } diff --git a/Platform/UTMData.swift b/Platform/UTMData.swift index f72d9e1a5..01343d802 100644 --- a/Platform/UTMData.swift +++ b/Platform/UTMData.swift @@ -66,6 +66,9 @@ struct AlertMessage: Identifiable { /// View: show busy spinner @Published var busy: Bool + /// View: show a percent progress in the busy spinner + @Published var busyProgress: Float? + /// View: currently selected VM @Published var selectedVM: VMData? From f8f162788e2c3c06bc914b42f9d5fe2eb52fe849 Mon Sep 17 00:00:00 2001 From: osy <50960678+osy@users.noreply.github.com> Date: Mon, 29 Apr 2024 17:08:51 -0700 Subject: [PATCH 24/79] home: show progress for clone/move operations --- Platform/UTMData.swift | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/Platform/UTMData.swift b/Platform/UTMData.swift index 01343d802..d4c5c3127 100644 --- a/Platform/UTMData.swift +++ b/Platform/UTMData.swift @@ -581,9 +581,12 @@ struct AlertMessage: Identifiable { /// - Parameter vm: VM to calculate size /// - Returns: Size in bytes func computeSize(for vm: VMData) async -> Int64 { - let path = vm.pathUrl - guard let enumerator = fileManager.enumerator(at: path, includingPropertiesForKeys: [.totalFileAllocatedSizeKey]) else { - logger.error("failed to create enumerator for \(path)") + return computeSize(recursiveFor: vm.pathUrl) + } + + private func computeSize(recursiveFor url: URL) -> Int64 { + guard let enumerator = fileManager.enumerator(at: url, includingPropertiesForKeys: [.totalFileAllocatedSizeKey]) else { + logger.error("failed to create enumerator for \(url)") return 0 } var total: Int64 = 0 @@ -665,7 +668,26 @@ struct AlertMessage: Identifiable { } private func copyItemWithCopyfile(at srcURL: URL, to dstURL: URL) async throws { - try await CopyManager.default.copyItem(at: srcURL, to: dstURL, flags: [.all, .recursive, .clone, .dataSparse]) + let totalSize = computeSize(recursiveFor: srcURL) + var lastUpdate = Date() + var lastProgress: CopyManager.Progress? + var copiedSize: Int64 = 0 + defer { + busyProgress = nil + } + for try await progress in CopyManager.default.copyItemProgress(at: srcURL, to: dstURL, flags: [.all, .recursive, .clone, .dataSparse]) { + if let _lastProgress = lastProgress, _lastProgress.srcPath != _lastProgress.srcPath { + copiedSize += _lastProgress.bytesCopied + lastProgress = progress + } else { + lastProgress = progress + } + if totalSize > 0 && lastUpdate.timeIntervalSinceNow < -1 { + lastUpdate = Date() + let completed = Float(copiedSize + progress.bytesCopied) / Float(totalSize) + busyProgress = completed > 1.0 ? 1.0 : completed + } + } } // MARK: - Downloading VMs From 0008112d029b1a5d33c10f98bbf89e7238bc8b3c Mon Sep 17 00:00:00 2001 From: osy <50960678+osy@users.noreply.github.com> Date: Mon, 29 Apr 2024 17:09:20 -0700 Subject: [PATCH 25/79] config(qemu): show progress indicator for reclaim operations --- Platform/UTMData.swift | 10 ++++- Services/UTMQemuImage.swift | 38 ++++++++++++++++++- .../xcshareddata/swiftpm/Package.resolved | 4 +- 3 files changed, 47 insertions(+), 5 deletions(-) diff --git a/Platform/UTMData.swift b/Platform/UTMData.swift index d4c5c3127..5af4ccca2 100644 --- a/Platform/UTMData.swift +++ b/Platform/UTMData.swift @@ -776,7 +776,15 @@ struct AlertMessage: Identifiable { func reclaimSpace(for driveUrl: URL, withCompression isCompressed: Bool = false) async throws { let baseUrl = driveUrl.deletingLastPathComponent() let dstUrl = Self.newImage(from: driveUrl, to: baseUrl, withExtension: "qcow2") - try await UTMQemuImage.convert(from: driveUrl, toQcow2: dstUrl, withCompression: isCompressed) + defer { + busyProgress = nil + } + try await UTMQemuImage.convert(from: driveUrl, toQcow2: dstUrl, withCompression: isCompressed) { progress in + Task { @MainActor in + self.busyProgress = progress / 100 + } + } + busyProgress = nil do { try fileManager.replaceItem(at: driveUrl, withItemAt: dstUrl, backupItemName: nil, resultingItemURL: nil) } catch { diff --git a/Services/UTMQemuImage.swift b/Services/UTMQemuImage.swift index b835ccec8..5ad8353db 100644 --- a/Services/UTMQemuImage.swift +++ b/Services/UTMQemuImage.swift @@ -18,9 +18,12 @@ import Foundation import QEMUKitInternal @objc class UTMQemuImage: UTMProcess { + typealias ProgressCallback = (Float) -> Void + private var logOutput: String = "" private var processExitContinuation: CheckedContinuation? - + private var onProgress: ProgressCallback? + private init() { super.init(arguments: []) } @@ -52,11 +55,14 @@ import QEMUKitInternal } } - static func convert(from url: URL, toQcow2 dest: URL, withCompression compressed: Bool = false) async throws { + 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") + } if compressed { qemuImg.pushArgv("-c") qemuImg.pushArgv("-o") @@ -69,8 +75,10 @@ import QEMUKitInternal 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() } @@ -175,8 +183,34 @@ extension UTMQemuImageError: LocalizedError { 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) + } + } } func logging(_ logging: QEMULogging, didRecieveErrorLine line: String) { } } + +extension UTMQemuImage { + private func parseProgress(_ line: String) -> Float? { + let pattern = "\\(([0-9]+\\.[0-9]+)/100\\%\\)" + do { + let regex = try NSRegularExpression(pattern: pattern) + if let match = regex.firstMatch(in: line, range: NSRange(location: 0, length: line.count)) { + let range = match.range(at: 1) + if let swiftRange = Range(range, in: line) { + let floatValueString = line[swiftRange] + if let floatValue = Float(floatValueString) { + return floatValue + } + } + } + } catch { + + } + return nil + } +} diff --git a/UTM.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/UTM.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 731586155..830f4ff2a 100644 --- a/UTM.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/UTM.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "7b5e2ff74c08d3efce9babb7649d891f7ad18d9c868e75db7d9e26ce542055cc", + "originHash" : "c9482d61e795c27df5a7c772a0750aefc9164d93bd871138a9b229c9f08c6fab", "pins" : [ { "identity" : "altkit", @@ -52,7 +52,7 @@ "location" : "https://github.com/utmapp/QEMUKit.git", "state" : { "branch" : "main", - "revision" : "06b806f61aeeea8efff99a98b058defcf3632e2e" + "revision" : "1019ed76278a1d5cbe871ff5e51c62b5d8c9a032" } }, { From 814bcbb9dda2691d1608adc9cc38819c9115f5ef Mon Sep 17 00:00:00 2001 From: MMP0 <28616020+MMP0@users.noreply.github.com> Date: Tue, 30 Apr 2024 12:23:22 +0900 Subject: [PATCH 26/79] Update for 6025783..e28e52d --- Platform/ja.lproj/Localizable.strings | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/Platform/ja.lproj/Localizable.strings b/Platform/ja.lproj/Localizable.strings index dbe12fd1a..e0e129eb5 100644 --- a/Platform/ja.lproj/Localizable.strings +++ b/Platform/ja.lproj/Localizable.strings @@ -136,6 +136,25 @@ // UTMDataExtension.swift "This virtual machine is already running. In order to run it from this device, you must stop it first." = "この仮想マシンはすでに実行されています。このデバイスから実行するには、まず停止する必要があります。"; +// UTMDonateView.swift +"Your support is the driving force that helps UTM stay independent. Your contribution, no matter the size, makes a significant difference. It enables us to develop new features and maintain existing ones. Thank you for considering a donation to support us." = "皆様のご支援がUTMの独立を維持する原動力となります。皆様からのご支援は、その大小にかかわらず、大きな違いをもたらします。これにより、新しい機能を開発し、既存の機能を維持することができます。ぜひご寄付をご検討のほどよろしくお願いいたします。"; +"GitHub Sponsors" = "GitHub Sponsors"; +"Support UTM" = "UTMを支援"; +"One Time Donation" = "1回限りの寄付"; +"Recurring Donation" = "定期寄付"; +"Manage Subscriptions…" = "サブスクリプションを管理…"; +"Restore Purchases" = "購入を復元"; +"%d days" = "%d日間"; +"day" = "1日間"; +"%d weeks" = "%d週間"; +"week" = "1週間"; +"%d months" = "%dか月間"; +"month" = "1か月間"; +"%d years" = "%d年間"; +"year" = "1年間"; +"period" = "期間"; +"Your purchase could not be verified by the App Store." = "App Storeで購入が確認できませんでした。"; + // UTMSingleWindowView.swift "Waiting for VM to connect to display..." = "仮想マシンがディスプレイに接続するのを待機中…"; @@ -756,6 +775,7 @@ "Inactive" = "非アクティブ"; // VMNavigationListView.swift +"Donate" = "寄付"; "Pending" = "保留中"; "New VM" = "新規仮想マシン"; "Create a new VM" = "新規仮想マシンを作成します"; From 933ad5ab783e6ff9c5910381081c7aedc4cee749 Mon Sep 17 00:00:00 2001 From: osy <50960678+osy@users.noreply.github.com> Date: Mon, 29 Apr 2024 22:47:18 -0700 Subject: [PATCH 27/79] display(qemu): if capture on focus is enabled, capture when vm is started or resumed --- .../macOS/Display/VMDisplayQemuMetalWindowController.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Platform/macOS/Display/VMDisplayQemuMetalWindowController.swift b/Platform/macOS/Display/VMDisplayQemuMetalWindowController.swift index c3ad86c15..9c346b992 100644 --- a/Platform/macOS/Display/VMDisplayQemuMetalWindowController.swift +++ b/Platform/macOS/Display/VMDisplayQemuMetalWindowController.swift @@ -150,6 +150,9 @@ class VMDisplayQemuMetalWindowController: VMDisplayQemuWindowController { } super.enterLive() resizeConsoleToolbarItem.isEnabled = false // disable item + if isWindowFocusAutoCapture { + captureMouse() + } } override func enterSuspended(isBusy busy: Bool) { From 1ad94f20130061e6b670234955c289f435005c22 Mon Sep 17 00:00:00 2001 From: osy <50960678+osy@users.noreply.github.com> Date: Mon, 29 Apr 2024 22:48:25 -0700 Subject: [PATCH 28/79] project: bumped version --- Build.xcconfig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Build.xcconfig b/Build.xcconfig index 956e42e25..5ae5fa348 100644 --- a/Build.xcconfig +++ b/Build.xcconfig @@ -17,8 +17,8 @@ // Configuration settings file format documentation can be found at: // https://help.apple.com/xcode/#/dev745c5c974 -MARKETING_VERSION = 4.5.1 -CURRENT_PROJECT_VERSION = 96 +MARKETING_VERSION = 4.5.2 +CURRENT_PROJECT_VERSION = 97 // Codesigning settings defined optionally, see Documentation/iOSDevelopment.md #include? "CodeSigning.xcconfig" From b03486b8825d5a0e8b9f93162a49a4c98ebab6a1 Mon Sep 17 00:00:00 2001 From: osy <50960678+osy@users.noreply.github.com> Date: Mon, 29 Apr 2024 23:31:46 -0700 Subject: [PATCH 29/79] project: update SwiftCopyfile to remove private API usage --- .../project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UTM.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/UTM.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 830f4ff2a..932af6201 100644 --- a/UTM.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/UTM.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -88,7 +88,7 @@ "location" : "https://github.com/osy/SwiftCopyfile.git", "state" : { "branch" : "main", - "revision" : "faac327beaed77c1a3ec8e899887835777782f30" + "revision" : "4da383bceaa5a5aec0ceb5789eed9bef2fa93f26" } }, { From 6a6723791c4bcdfae5416a996b053ae66e3c8d15 Mon Sep 17 00:00:00 2001 From: osy <50960678+osy@users.noreply.github.com> Date: Tue, 30 Apr 2024 13:54:13 -0700 Subject: [PATCH 30/79] display(iOS): hide overlays on iOS 16+ The UIViewController patches do not work on iOS 16+ but for some reason only the Remote builds fail to hide it. --- Platform/iOS/VMWindowView.swift | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Platform/iOS/VMWindowView.swift b/Platform/iOS/VMWindowView.swift index ce4e1b82f..688ff6d00 100644 --- a/Platform/iOS/VMWindowView.swift +++ b/Platform/iOS/VMWindowView.swift @@ -56,8 +56,10 @@ struct VMWindowView: View { switch device { case .display(_, _): VMDisplayHostedView(vm: session.vm, device: device, state: $state) + .prefersPersistentSystemOverlaysHidden() case .serial(_, _): VMDisplayHostedView(vm: session.vm, device: device, state: $state) + .prefersPersistentSystemOverlaysHidden() } } else if !state.isBusy && state.isRunning { // headless @@ -307,3 +309,13 @@ fileprivate struct VMToolbarOrnamentModifier: ViewModifier { } } #endif + +private extension View { + func prefersPersistentSystemOverlaysHidden() -> some View { + if #available(iOS 16, *) { + return self.persistentSystemOverlays(.hidden) + } else { + return self + } + } +} From 21f3c011438ff59efc20b5987e84b20e6896c593 Mon Sep 17 00:00:00 2001 From: MMP0 <28616020+MMP0@users.noreply.github.com> Date: Wed, 1 May 2024 13:26:42 +0900 Subject: [PATCH 31/79] Add missing updates between d245c87..b03486b --- Platform/iOS/ja.lproj/InfoPlist.strings | 2 +- Platform/ja.lproj/Localizable.strings | 69 +++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 1 deletion(-) diff --git a/Platform/iOS/ja.lproj/InfoPlist.strings b/Platform/iOS/ja.lproj/InfoPlist.strings index 8437e0527..54ae442fb 100644 --- a/Platform/iOS/ja.lproj/InfoPlist.strings +++ b/Platform/iOS/ja.lproj/InfoPlist.strings @@ -1,5 +1,5 @@ /* Privacy - Local Network Usage Description */ -"NSLocalNetworkUsageDescription" = "仮想マシンがローカルネットワークにアクセスする可能性があります。UTMもAltServerと通信するためにローカルネットワークを使用します。"; +"NSLocalNetworkUsageDescription" = "仮想マシンがローカルネットワークにアクセスできるようになります。UTMもローカルサーバと通信するためにネットワークを使用することがあります。"; /* Privacy - Location Always and When In Use Usage Description */ "NSLocationAlwaysAndWhenInUseUsageDescription" = "UTMは定期的に位置データを要求することで、システムがバックグラウンドプロセスをアクティブに保つようにします。位置データが送信されることはありません。"; diff --git a/Platform/ja.lproj/Localizable.strings b/Platform/ja.lproj/Localizable.strings index e0e129eb5..b80d8d7ab 100644 --- a/Platform/ja.lproj/Localizable.strings +++ b/Platform/ja.lproj/Localizable.strings @@ -343,6 +343,8 @@ "Mouse/Keyboard" = "マウス/キーボード"; "Capture input automatically when entering full screen" = "フルスクリーンになったときに入力を自動的にキャプチャ"; "If enabled, input capture will toggle automatically when entering and exiting full screen mode." = "有効にすると、フルスクリーンモードの開始時と解除時に入力キャプチャが自動的に切り替わります。"; +"Capture input automatically when window is focused" = "ウインドウがフォーカスされたときに入力を自動的にキャプチャ"; +"If enabled, input capture will toggle automatically when the VM's window is focused." = "有効にすると、仮想マシンのウインドウがフォーカスされたときに入力キャプチャが自動的に切り替わります。"; "Console" = "コンソール"; "Option (⌥) is Meta key" = "メタキーとしてOption(⌥)を使用"; "If enabled, Option will be mapped to the Meta key which can be useful for emacs. Otherwise, option will work as the system intended (such as for entering international text)." = "有効にすると、OptionがEmacsの利用に便利なメタキーにマッピングされます。そうでない場合、Optionはシステムが意図したとおりに動作します(国際テキストを入力する場合など)。"; @@ -491,6 +493,8 @@ // VMSessionState.swift "Connection to the server was lost." = "サーバへの接続が切断されました。"; +"Background task is about to expire" = "バックグラウンドタスクがまもなく期限切れになります"; +"Switch back to UTM to avoid termination." = "終了を防ぐには、UTMに戻ってください。"; // VMConfigQEMUArgumentsView.swift "Arguments" = "引数"; @@ -635,6 +639,71 @@ "Generic" = "一般"; "Notes" = "メモ"; "Icon" = "アイコン"; +"Choose" = "選択"; +"AIX" = "AIX"; +"iOS" = "iOS"; +"Windows 7" = "Windows 7"; +"AlmaLinux" = "AlmaLinux"; +"Alpine" = "Alpine"; +"AmigaOS" = "AmigaOS"; +"Android" = "Android"; +"Apple TV" = "Apple TV"; +"Arch Linux" = "Arch Linux"; +"BackTrack" = "BackTrack"; +"Bada" = "Bada"; +"BeOS" = "BeOS"; +"CentOS" = "CentOS"; +"Chrome OS" = "Chrome OS"; +"CyanogenMod" = "CyanogenMod"; +"Debian" = "Debian"; +"Elementary OS" = "elementary OS"; +"Fedora" = "Fedora"; +"Firefox OS" = "Firefox OS"; +"FreeBSD" = "FreeBSD"; +"Gentoo" = "Gentoo"; +"Haiku OS" = "Haiku OS"; +"HP-UX" = "HP-UX"; +"KaiOS" = "KaiOS"; +"Knoppix" = "Knoppix"; +"Kubuntu" = "Kubuntu"; +"Linux" = "Linux"; +"Lubuntu" = "Lubuntu"; +"macOS" = "macOS"; +"Maemo" = "Maemo"; +"Mandriva" = "Mandriva"; +"MeeGo" = "MeeGo"; +"Linux Mint" = "Linux Mint"; +"NetBSD" = "NetBSD"; +"Nintendo" = "任天堂"; +"NixOS" = "NixOS"; +"OpenBSD" = "OpenBSD"; +"OpenWrt" = "OpenWrt"; +"OS/2" = "OS/2"; +"Palm OS" = "Palm OS"; +"PlayStation Portable" = "PlayStation Portable"; +"PlayStation" = "PlayStation"; +"Pop!_OS" = "Pop!_OS"; +"Red Hat" = "Red Hat"; +"Remix OS" = "Remix OS"; +"RISC OS" = "RISC OS"; +"Sabayon" = "Sabayon"; +"Sailfish OS" = "Sailfish OS"; +"Slackware" = "Slackware"; +"Solaris" = "Solaris"; +"openSUSE" = "openSUSE"; +"Syllable" = "Syllable"; +"Symbian" = "Symbian"; +"ThreadX" = "ThreadX"; +"Tizen" = "Tizen"; +"Ubuntu" = "Ubuntu"; +"webOS" = "webOS"; +"Windows 11" = "Windows 11"; +"Windows 9x" = "Windows 9x系"; +"Windows XP" = "Windows XP"; +"Windows" = "Windows"; +"Xbox" = "Xbox"; +"Xubuntu" = "Xubuntu"; +"YunOS" = "YunOS"; // VMConfigInputView.swift "If enabled, the default input devices will be emulated on the USB bus." = "有効にすると、デフォルトの入力デバイスがUSBバス上でエミュレートされます。"; From f0e06f6b7a066a4d583e082f8815f1b79f37a495 Mon Sep 17 00:00:00 2001 From: Tiffany Fung <46277006+changanmoon@users.noreply.github.com> Date: Sat, 4 May 2024 18:26:13 +0800 Subject: [PATCH 32/79] Import more strings for zh-hk --- .../zh-HK.lproj/Info-Remote-InfoPlist.strings | 9 ++++ Platform/zh-HK.lproj/Localizable.strings | 48 +++++++++++++++++++ UTM.xcodeproj/project.pbxproj | 12 +++++ 3 files changed, 69 insertions(+) create mode 100644 Platform/iOS/zh-HK.lproj/Info-Remote-InfoPlist.strings diff --git a/Platform/iOS/zh-HK.lproj/Info-Remote-InfoPlist.strings b/Platform/iOS/zh-HK.lproj/Info-Remote-InfoPlist.strings new file mode 100644 index 000000000..6387088a3 --- /dev/null +++ b/Platform/iOS/zh-HK.lproj/Info-Remote-InfoPlist.strings @@ -0,0 +1,9 @@ +/* Bundle name */ +"CFBundleName" = "UTM 遠端"; + +/* Privacy - Local Network Usage Description */ +"NSLocalNetworkUsageDescription" = "UTM 使用本地網絡尋找並連接至 UTM 遠端伺服器。"; + +/* Privacy - Microphone Usage Description */ +"NSMicrophoneUsageDescription" = "任何虛擬電腦都需要許可才能由咪高風進行錄製。"; + diff --git a/Platform/zh-HK.lproj/Localizable.strings b/Platform/zh-HK.lproj/Localizable.strings index 25a43ae44..28b8670dd 100644 --- a/Platform/zh-HK.lproj/Localizable.strings +++ b/Platform/zh-HK.lproj/Localizable.strings @@ -44,6 +44,18 @@ /* Format string for download progress and speed, e. g. 5 MB of 6 GB (200 kbit/s) */ "%1$@ of %2$@ (%3$@)" = "%1$@ / %2$@ (%3$@)"; +/* UTMDonateView */ +"%d days" = "%d 日"; + +/* UTMDonateView */ +"%d months" = "%d 月"; + +/* UTMDonateView */ +"%d weeks" = "%d 周"; + +/* UTMDonateView */ +"%d years" = "%d 年"; + /* UTMScriptingAppDelegate */ "A valid backend must be specified." = "必須指定有效的後端。"; @@ -119,6 +131,9 @@ /* UTMQemuConstants */ "Automatic Serial Device (max 4)" = "自動序列裝置 (最大值為 4)"; +/* VMSessionState */ +"Background task is about to expire" = "背景任務即將過期"; + /* UTMLegacyQemuConfiguration UTMQemuConstants */ "BIOS" = "BIOS"; @@ -208,6 +223,9 @@ /* No comment provided by engineer. */ "Capture input automatically when entering full screen" = "進入全螢幕時自動擷取輸入"; +/* No comment provided by engineer. */ +"Capture input automatically when window is focused" = "於視窗聚焦時自動擷取輸入"; + /* VMDisplayQemuMetalWindowController */ "Captured mouse" = "已擷取滑鼠"; @@ -227,6 +245,9 @@ /* VMDisplayAppleController */ "Change…" = "變更⋯"; +/* No comment provided by engineer. */ +"Choose" = "選取"; + /* No comment provided by engineer. */ "Clear" = "清除"; @@ -291,6 +312,9 @@ /* UTMSWTPM */ "Data not specified." = "未指定資料。"; +/* UTMDonateView */ +"day" = "日"; + /* No comment provided by engineer. */ "Debug Logging" = "除錯記錄"; @@ -712,6 +736,9 @@ /* No comment provided by engineer. */ "Minimum size: %@" = "最小大小:%@"; +/* UTMDonateView */ +"month" = "月"; + /* No comment provided by engineer. */ "Mouse/Keyboard" = "滑鼠/鍵盤"; @@ -848,6 +875,9 @@ /* No comment provided by engineer. */ "Pending" = "待定"; +/* UTMDonateView */ +"period" = "期間"; + /* VMDisplayWindowController */ "Play" = "播放"; @@ -1126,6 +1156,9 @@ /* UTMSWTPM */ "SW TPM failed to start. %@" = "SW TPM 無法啟動。%@"; +/* VMSessionState */ +"Switch back to UTM to avoid termination." = "切換回至 UTM 以避免終止。"; + /* No comment provided by engineer. */ "System" = "系統"; @@ -1383,6 +1416,9 @@ /* No comment provided by engineer. */ "Waiting for VM to connect to display..." = "正在等待虛擬電腦連接至顯示..."; +/* UTMDonateView */ +"week" = "周"; + /* No comment provided by engineer. */ "Welcome to UTM" = "歡迎使用 UTM"; @@ -1404,6 +1440,9 @@ /* No comment provided by engineer. */ "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." = "你要重新轉換此磁碟映像檔以回收未使用的空間嗎?請緊記,這將需要足夠的臨時空間來執行轉換。強烈建議你先備份此虛擬電腦,然後再繼續操作。"; +/* UTMDonateView */ +"year" = "年"; + /* No comment provided by engineer. */ "Yes" = "是"; @@ -1414,11 +1453,20 @@ VMWizardOSMacView */ "Your machine does not support running this IPSW." = "你的電腦不支援執行此 IPSW。"; +/* UTMDonateView */ +"Your purchase could not be verified by the App Store." = "App Store 無法驗證你的購買。"; + +/* No comment provided by engineer. */ +"Your support is the driving force that helps UTM stay independent. Your contribution, no matter the size, makes a significant difference. It enables us to develop new features and maintain existing ones. Thank you for considering a donation to support us." = "你的支援是幫助 UTM 保持獨立的動力。無論你的貢獻幾多,都會帶來重大影響。這可以令我們能夠開發新功能,並維護現有的功能。多謝你考慮捐贈來支援我們。"; + /* ContentView */ "Your version of iOS does not support running VMs while unmodified. You must either run UTM while jailbroken or with a remote debugger attached. See https://getutm.app/install/ for more details." = "你的 iOS 版本不支援在未作更動的情況下執行虛擬電腦,必須在越獄 (jailbreak) 時執行 UTM,或是在附加遠程除錯器的情況下執行 UTM。有關更多詳細訊息,請見 https://getutm.app/install/。"; // Additional Strings (These strings are unable to be extracted by Xcode) +/* No comment provided by engineer. */ +"" = ""; + /* No comment provided by engineer. */ "(Delete)" = "(刪除)"; diff --git a/UTM.xcodeproj/project.pbxproj b/UTM.xcodeproj/project.pbxproj index a6829459d..4603c8241 100644 --- a/UTM.xcodeproj/project.pbxproj +++ b/UTM.xcodeproj/project.pbxproj @@ -1216,6 +1216,7 @@ 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 */; }; + F6056EF32BE642F500FAEED8 /* Info-Remote-InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = F6056EF12BE642F500FAEED8 /* Info-Remote-InfoPlist.strings */; }; 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 */; }; @@ -2073,6 +2074,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 = ""; }; + F6056EF22BE642F500FAEED8 /* zh-HK */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-HK"; path = "zh-HK.lproj/Info-Remote-InfoPlist.strings"; sourceTree = ""; }; F6DA2DA52AAFED5F0070DCD1 /* zh-HK */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-HK"; path = "zh-HK.lproj/VMDisplayMetalViewInputAccessory.strings"; 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 = ""; }; @@ -2711,6 +2713,7 @@ CE95877426D74C2A0086BDE8 /* iOS.entitlements */, CE2D954F24AD4F980059923A /* Info.plist */, CECF02572B70909900409FC0 /* Info-Remote.plist */, + F6056EF12BE642F500FAEED8 /* Info-Remote-InfoPlist.strings */, FFB02A8A266CB09C006CD71A /* InfoPlist.strings */, CEB5C1192B8C4CD4008AAE5C /* Info-RemotePlist.strings */, CEC1B00A2BBB211C0088119D /* PrivacyInfo.xcprivacy */, @@ -3394,6 +3397,7 @@ CEF7F6792AEEDCC400E34952 /* Icons in Resources */, CEB5C1172B8C4CD4008AAE5C /* Info-RemotePlist.strings in Resources */, CEF7F67B2AEEDCC400E34952 /* Localizable.strings in Resources */, + F6056EF32BE642F500FAEED8 /* Info-Remote-InfoPlist.strings in Resources */, CEF7F67C2AEEDCC400E34952 /* qemu in Resources */, CEF7F67D2AEEDCC400E34952 /* VMDisplayMetalViewInputAccessory.xib in Resources */, CEF7F67E2AEEDCC400E34952 /* Localizable.stringsdict in Resources */, @@ -4328,6 +4332,14 @@ name = Localizable.stringsdict; sourceTree = ""; }; + F6056EF12BE642F500FAEED8 /* Info-Remote-InfoPlist.strings */ = { + isa = PBXVariantGroup; + children = ( + F6056EF22BE642F500FAEED8 /* zh-HK */, + ); + name = "Info-Remote-InfoPlist.strings"; + sourceTree = ""; + }; FF0307532A84E3B70049979B /* QEMULauncher-InfoPlist.strings */ = { isa = PBXVariantGroup; children = ( From 2bba539c3c951c8d952cf4700ea481bac926ee5f Mon Sep 17 00:00:00 2001 From: Tiffany Fung <46277006+changanmoon@users.noreply.github.com> Date: Sat, 4 May 2024 18:58:09 +0800 Subject: [PATCH 33/79] Import translations for zh-Hans --- Platform/iOS/zh-Hans.lproj/Info-Remote-InfoPlist.strings | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 Platform/iOS/zh-Hans.lproj/Info-Remote-InfoPlist.strings diff --git a/Platform/iOS/zh-Hans.lproj/Info-Remote-InfoPlist.strings b/Platform/iOS/zh-Hans.lproj/Info-Remote-InfoPlist.strings new file mode 100644 index 000000000..3112abb40 --- /dev/null +++ b/Platform/iOS/zh-Hans.lproj/Info-Remote-InfoPlist.strings @@ -0,0 +1,9 @@ +/* Bundle name */ +"CFBundleName" = "UTM 远程"; + +/* Privacy - Local Network Usage Description */ +"NSLocalNetworkUsageDescription" = "UTM 使用本地网络查找和连接 UTM 远程服务器。"; + +/* Privacy - Microphone Usage Description */ +"NSMicrophoneUsageDescription" = "任何虚拟机都需要获得许可才能从麦克风录音。"; + From 9654d3118ea9785ce8c2a9e52196b4a1e46dc924 Mon Sep 17 00:00:00 2001 From: Tiffany Fung <46277006+changanmoon@users.noreply.github.com> Date: Sat, 4 May 2024 19:05:57 +0800 Subject: [PATCH 34/79] Import translations for zh-Hans --- Platform/zh-Hans.lproj/Localizable.strings | 56 +++++++++++++++++++--- 1 file changed, 50 insertions(+), 6 deletions(-) diff --git a/Platform/zh-Hans.lproj/Localizable.strings b/Platform/zh-Hans.lproj/Localizable.strings index e6397014f..8276a54a4 100644 --- a/Platform/zh-Hans.lproj/Localizable.strings +++ b/Platform/zh-Hans.lproj/Localizable.strings @@ -44,6 +44,18 @@ /* Format string for download progress and speed, e. g. 5 MB of 6 GB (200 kbit/s) */ "%1$@ of %2$@ (%3$@)" = "共 %2$@,已下载 %1$@ (%3$@)"; +/* UTMDonateView */ +"%d days" = "%d 天"; + +/* UTMDonateView */ +"%d months" = "%d 月"; + +/* UTMDonateView */ +"%d weeks" = "%d 周"; + +/* UTMDonateView */ +"%d years" = "%d 年"; + /* UTMScriptingAppDelegate */ "A valid backend must be specified." = "必须指定有效的后端。"; @@ -119,6 +131,9 @@ /* UTMQemuConstants */ "Automatic Serial Device (max 4)" = "自动串行设备 (最大值 4)"; +/* VMSessionState */ +"Background task is about to expire" = "后台任务即将终止"; + /* UTMLegacyQemuConfiguration UTMQemuConstants */ "BIOS" = "BIOS"; @@ -208,11 +223,8 @@ /* No comment provided by engineer. */ "Capture input automatically when entering full screen" = "进入全屏时自动捕获输入"; -"If enabled, input capture will toggle automatically when entering and exiting full screen mode." = "如果启用此开关,进入和退出全屏模式时将自动切换输入捕获。"; - -"Capture input automatically when window is focused" = "成为活动窗口时自动捕获输入"; - -"If enabled, input capture will toggle automatically when the VM's window is focused." = "如果启用此开关,成为/失去当前活动窗口时自动切换输入捕获。"; +/* No comment provided by engineer. */ +"Capture input automatically when window is focused" = "聚焦窗口时自动捕获输入"; /* VMDisplayQemuMetalWindowController */ "Captured mouse" = "已捕获鼠标"; @@ -233,6 +245,9 @@ /* VMDisplayAppleController */ "Change…" = "更改…"; +/* No comment provided by engineer. */ +"Choose" = "选择"; + /* No comment provided by engineer. */ "Clear" = "清除"; @@ -297,6 +312,9 @@ /* UTMSWTPM */ "Data not specified." = "未指定数据。"; +/* UTMDonateView */ +"day" = "天"; + /* No comment provided by engineer. */ "Debug Logging" = "调试日志记录"; @@ -718,6 +736,9 @@ /* No comment provided by engineer. */ "Minimum size: %@" = "最小文件大小:%@"; +/* UTMDonateView */ +"month" = "月"; + /* No comment provided by engineer. */ "Mouse/Keyboard" = "鼠标/键盘"; @@ -854,6 +875,9 @@ /* No comment provided by engineer. */ "Pending" = "等待中"; +/* UTMDonateView */ +"period" = "周期"; + /* VMDisplayWindowController */ "Play" = "启动"; @@ -1132,6 +1156,9 @@ /* UTMSWTPM */ "SW TPM failed to start. %@" = "SW TPM 无法启动。%@"; +/* VMSessionState */ +"Switch back to UTM to avoid termination." = "切换回 UTM 以避免终止。"; + /* No comment provided by engineer. */ "System" = "系统"; @@ -1359,7 +1386,7 @@ /* UTMScriptingAppDelegate UTMScriptingUSBDeviceImpl */ -"UTM is not ready to accept commands." = "UTM 未准备好接受命令。"; +"UTM is not ready to accept commands." = "UTM 尚未准备好接受命令。"; /* No comment provided by engineer. */ "Version" = "版本号"; @@ -1389,6 +1416,9 @@ /* No comment provided by engineer. */ "Waiting for VM to connect to display..." = "等待虚拟机连接到显示..."; +/* UTMDonateView */ +"week" = "周"; + /* No comment provided by engineer. */ "Welcome to UTM" = "欢迎使用 UTM"; @@ -1410,6 +1440,9 @@ /* No comment provided by engineer. */ "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." = "要重新转换此磁盘映像以回收未使用的空间吗?请注意,这将需要足够的临时空间来执行转换。在继续操作之前,强烈建议你备份此虚拟机。"; +/* UTMDonateView */ +"year" = "年"; + /* No comment provided by engineer. */ "Yes" = "是"; @@ -1420,11 +1453,20 @@ VMWizardOSMacView */ "Your machine does not support running this IPSW." = "你的机器不支持运行此 IPSW。"; +/* UTMDonateView */ +"Your purchase could not be verified by the App Store." = "App Store 无法验证你的购买。"; + +/* No comment provided by engineer. */ +"Your support is the driving force that helps UTM stay independent. Your contribution, no matter the size, makes a significant difference. It enables us to develop new features and maintain existing ones. Thank you for considering a donation to support us." = "你的支持是 UTM 保持独立的动力。你的贡献无论或大或小,都会产生重大的影响。它可以使我们能开发出功能,并维护现有的功能。感谢你考虑捐赠支持我们。"; + /* ContentView */ "Your version of iOS does not support running VMs while unmodified. You must either run UTM while jailbroken or with a remote debugger attached. See https://getutm.app/install/ for more details." = "你的 iOS 版本不支持在未经修改的情况下运行虚拟机,必须在越狱时运行 UTM,或者连接远程调试器。有关更多详细信息,请参阅 https://getutm.app/install/。"; // Additional Strings (These strings are unable to be extracted by Xcode) +/* No comment provided by engineer. */ +"" = ""; + /* No comment provided by engineer. */ "(Delete)" = "(删除)"; @@ -1791,6 +1833,8 @@ /* No comment provided by engineer. */ "If enabled, input capture will toggle automatically when entering and exiting full screen mode." = "若启用,输入捕捉会在进入和退出全屏模式时自动切换。"; +"If enabled, input capture will toggle automatically when the VM's window is focused." = "若启用,输入捕捉将在虚拟机窗口聚焦时自动切换。"; + /* No comment provided by engineer. */ "If enabled, num lock will always be on to the guest. Note this may make your keyboard's num lock indicator out of sync." = "若启用,Num Lock 将始终对客户机开启。注意,这可能会使键盘的 Num Lock 指示灯不同步。"; From 0cb0e45e402c170e22afa199a6488bc5e78f1d4c Mon Sep 17 00:00:00 2001 From: Tiffany Fung <46277006+changanmoon@users.noreply.github.com> Date: Sat, 4 May 2024 19:13:06 +0800 Subject: [PATCH 35/79] Update Localizable.strings --- Platform/zh-HK.lproj/Localizable.strings | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Platform/zh-HK.lproj/Localizable.strings b/Platform/zh-HK.lproj/Localizable.strings index 28b8670dd..e895ed495 100644 --- a/Platform/zh-HK.lproj/Localizable.strings +++ b/Platform/zh-HK.lproj/Localizable.strings @@ -1833,6 +1833,9 @@ /* No comment provided by engineer. */ "If enabled, input capture will toggle automatically when entering and exiting full screen mode." = "如啟用,於進入和離開全螢幕模式時,將會自動切換輸入擷取。"; +/* No comment provided by engineer. */ +"If enabled, input capture will toggle automatically when the VM's window is focused." = "如啟用,於虛擬電腦聚焦視窗時,將自動切換輸入擷取。"; + /* No comment provided by engineer. */ "If enabled, num lock will always be on to the guest. Note this may make your keyboard's num lock indicator out of sync." = "如啟用,Num Lock 將會始終對客戶端開啟。請緊記,這可能會令鍵盤的 Num Lock 指示器不同步。"; From 349574a242c12c1e4baefcb8e40788d6b3a6def1 Mon Sep 17 00:00:00 2001 From: Tiffany Fung <46277006+changanmoon@users.noreply.github.com> Date: Sat, 4 May 2024 19:15:14 +0800 Subject: [PATCH 36/79] Update Localizable.strings --- Platform/zh-Hans.lproj/Localizable.strings | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Platform/zh-Hans.lproj/Localizable.strings b/Platform/zh-Hans.lproj/Localizable.strings index 8276a54a4..5bea0a52e 100644 --- a/Platform/zh-Hans.lproj/Localizable.strings +++ b/Platform/zh-Hans.lproj/Localizable.strings @@ -1414,7 +1414,7 @@ "Virtualize" = "虚拟化"; /* No comment provided by engineer. */ -"Waiting for VM to connect to display..." = "等待虚拟机连接到显示..."; +"Waiting for VM to connect to display..." = "等待虚拟机连接到显示…"; /* UTMDonateView */ "week" = "周"; @@ -1833,6 +1833,7 @@ /* No comment provided by engineer. */ "If enabled, input capture will toggle automatically when entering and exiting full screen mode." = "若启用,输入捕捉会在进入和退出全屏模式时自动切换。"; +/* No comment provided by engineer. */ "If enabled, input capture will toggle automatically when the VM's window is focused." = "若启用,输入捕捉将在虚拟机窗口聚焦时自动切换。"; /* No comment provided by engineer. */ From a2a99261ef5408a33216c2d6feb9e2c379914ce1 Mon Sep 17 00:00:00 2001 From: Tiffany Fung <46277006+changanmoon@users.noreply.github.com> Date: Sat, 4 May 2024 19:16:03 +0800 Subject: [PATCH 37/79] Update Localizable.strings --- Platform/zh-HK.lproj/Localizable.strings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Platform/zh-HK.lproj/Localizable.strings b/Platform/zh-HK.lproj/Localizable.strings index e895ed495..155befb4e 100644 --- a/Platform/zh-HK.lproj/Localizable.strings +++ b/Platform/zh-HK.lproj/Localizable.strings @@ -1414,7 +1414,7 @@ "Virtualize" = "虛擬化"; /* No comment provided by engineer. */ -"Waiting for VM to connect to display..." = "正在等待虛擬電腦連接至顯示..."; +"Waiting for VM to connect to display..." = "正在等待虛擬電腦連接至顯示⋯"; /* UTMDonateView */ "week" = "周"; From bad69e7d7f6a52300f14919a8b2b4c63e90890e4 Mon Sep 17 00:00:00 2001 From: Tiffany Fung <46277006+changanmoon@users.noreply.github.com> Date: Tue, 7 May 2024 11:50:01 +0800 Subject: [PATCH 38/79] Update Localizable.strings --- Platform/zh-HK.lproj/Localizable.strings | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Platform/zh-HK.lproj/Localizable.strings b/Platform/zh-HK.lproj/Localizable.strings index 155befb4e..6dcfee673 100644 --- a/Platform/zh-HK.lproj/Localizable.strings +++ b/Platform/zh-HK.lproj/Localizable.strings @@ -357,7 +357,7 @@ "Disposable Mode" = "即拋式模式"; /* No comment provided by engineer. */ -"Do not save VM screenshot to disk" = "不要將虛擬電腦快照儲存至磁碟"; +"Do not save VM screenshot to disk" = "不要將虛擬電腦螢幕截圖儲存至磁碟"; /* No comment provided by engineer. */ "Do not show confirmation when closing a running VM" = "關閉正在執行的虛擬電腦時不要顯示確認"; @@ -1825,7 +1825,7 @@ "If enabled, a virtiofs share tagged 'rosetta' will be available on the Linux guest for installing Rosetta for emulating x86_64 on ARM64." = "如啟用,標記為「rosetta」的 virtiofs 分享將會於 Linux 客戶端上可用,用作安裝 Rosetta,可以於 arm64 上仿真 x86_64。"; /* No comment provided by engineer. */ -"If enabled, any existing screenshot will be deleted the next time the VM is started." = "如啟用,下次啟動虛擬電腦時,任何現存的快照將會被刪除。"; +"If enabled, any existing screenshot will be deleted the next time the VM is started." = "如啟用,下次啟動虛擬電腦時,任何現存的螢幕截圖將會被刪除。"; /* No comment provided by engineer. */ "If enabled, caps lock will be handled like other keys. If disabled, it is treated as a toggle that is synchronized with the host." = "如啟用,Caps Lock 將會同其他鍵一樣處理。如停用,它將會被視為開關鍵,並與主機同步。"; From f17459a10ac6613edd3e6dc47258e9fad12ef19a Mon Sep 17 00:00:00 2001 From: Vincenzo Garambone Date: Tue, 7 May 2024 12:44:21 +0200 Subject: [PATCH 39/79] Improved Italian Localization --- Platform/it.lproj/Localizable.strings | 301 ++++++++++++++++++++++++-- 1 file changed, 277 insertions(+), 24 deletions(-) diff --git a/Platform/it.lproj/Localizable.strings b/Platform/it.lproj/Localizable.strings index 2ccc771ce..3dff1a3f8 100644 --- a/Platform/it.lproj/Localizable.strings +++ b/Platform/it.lproj/Localizable.strings @@ -126,6 +126,18 @@ /* No comment provided by engineer. */ "Are you sure you want to permanently delete this disk image?" = "Sei sicuro di voler eliminare questa immagine definitivamente?"; +/* No comment provided by engineer. */ +"Add a new drive." = "Aggiungi un nuovo disco."; + +/* No comment provided by engineer. */ +"Select an existing disk image." = "Selezione un'immagine disco esistente."; + +/* No comment provided by engineer. */ +"Create an empty drive." = "Crea un disco vuoto."; + +/* No comment provided by engineer. */ +"Add a new device." = "Aggiungi un nuovo dispositivo."; + /* No comment provided by engineer. */ "Are you sure you want to reset this VM? Any unsaved changes will be lost." = "Sei sicuro di voler riavviare questa Macchina Virtuale? Le modifiche non salvate andranno perse."; @@ -141,6 +153,9 @@ /* No comment provided by engineer. */ "Balloon Device" = "Dispositivo Balloon"; +/* No comment provided by engineer. */ +"TPM can be used to protect secrets in the guest operating system. Note that the host will always be able to read these secrets and therefore no expectation of physical security is provided." = "TPM può essere utilizzato per conservare informazioni riservate del sistema. Il dispositivo host sarà sempre in grado di accedere a questi dati."; + /* UTMLegacyQemuConfiguration UTMQemuConstants */ "BIOS" = "BIOS"; @@ -157,6 +172,9 @@ /* No comment provided by engineer. */ "Boot from kernel image" = "Avvia da immagine del kernel"; +/* No comment provided by engineer. */ +"Ubuntu Install Guide" = "Guida all'installazione di Ubuntu"; + /* No comment provided by engineer. */ "Boot Arguments" = "Argomenti di Avvio"; @@ -185,7 +203,7 @@ "Bridged Settings" = "Impostazioni Bridged"; /* No comment provided by engineer. */ -"Bridged Interface" = "Interfaccia Bridget"; +"Bridged Interface" = "Interfaccia Bridged"; /* Welcome view */ "Browse UTM Gallery" = "Scopri la UTM Gallery"; @@ -253,7 +271,7 @@ "Clone" = "Clona"; /* No comment provided by engineer. */ -"Clone selected VM" = "Clona la Macchina Virtuale selezionata"; +"Clone selected VM" = "Clona la VM selezionata"; /* No comment provided by engineer. */ "Clone…" = "Clona..."; @@ -304,7 +322,7 @@ "Create" = "Crea"; /* Welcome view */ -"Create a New Virtual Machine" = "Crea una Nuova Macchina Virtuale (VM)"; +"Create a New Virtual Machine" = "Crea una Nuova Macchina Virtuale"; /* No comment provided by engineer. */ "Create a new VM with the same configuration as this one but without any data." = "Crea una nuova Macchina Virtuale con la stessa configurazione, ma senza alcun dato."; @@ -341,7 +359,10 @@ "Delete Drive" = "Elimina Disco"; /* No comment provided by engineer. */ -"Delete selected VM" = "Elimina la Macchina Virtuale selezionata"; +"Delete this drive." = "Elimina questo disco"; + +/* No comment provided by engineer. */ +"Delete selected VM" = "Elimina la VM selezionata"; /* No comment provided by engineer. */ "Delete…" = "Elimina..."; @@ -364,6 +385,9 @@ /* No comment provided by engineer. */ "DHCP Start" = "Inzio DHCP"; +/* No comment provided by engineer. */ +"DHCP End" = "Fine DHCP"; + /* No comment provided by engineer. */ "Directory" = "Cartella"; @@ -398,13 +422,13 @@ "Disk Image" = "Immagine del Disco"; /* VMDisplayAppleWindowController */ -"Display" = "Monitor"; +"Display" = "Schermo"; /* VMDisplayQemuDisplayController */ -"Display %lld: %@" = "Monitor %1$lld: %2$@"; +"Display %lld: %@" = "Schermo %1$lld: %2$@"; /* VMDisplayQemuDisplayController */ -"Disposable Mode" = "Modo desechable"; +"Disposable Mode" = "Modalità usa e getta"; /* No comment provided by engineer. */ "DNS Search Domains" = "Dominio di Ricerca DNS"; @@ -483,7 +507,7 @@ "Edit…" = "Modifica..."; /* No comment provided by engineer. */ -"Edit selected VM" = "Modifica la Macchina Virtuale selezionata"; +"Edit selected VM" = "Modifica la VM selezionata"; /* VMDrivesSettingsView */ "EFI Variables" = "Variabli EFI"; @@ -521,6 +545,9 @@ /* No comment provided by engineer. */ "Enable Clipboard Sharing" = "Abilita Condivisione Appunti"; +/* No comment provided by engineer. */ +"Requires SPICE guest agent tools to be installed." = "Richiede l'installazione degli strumenti SPICE sul sistema guest."; + /* No comment provided by engineer. */ "Enable Directory Sharing" = "Abilita Condivisione Cartella"; @@ -535,6 +562,8 @@ "Enable Rosetta on Linux (x86_64 Emulation)" = "Abilita Rosetta su Linux (Emulazione x86_64)"; +"If enabled, a virtiofs share tagged 'rosetta' will be available on the Linux guest for installing Rosetta for emulating x86_64 on ARM64." = "Se abilitato, verrà resa disponibile la condivisione virtiofs 'rosetta' al guest Linux, per poter installare Rosetta ed emulare x86_64 su ARM64."; + /* No comment provided by engineer. */ "Enable hardware OpenGL acceleration" = "Abilita l'accelerazione hardware OpenGL"; @@ -568,6 +597,9 @@ /* No comment provided by engineer. */ "Export QEMU Command…" = "Esporta comando QEMU…"; +/* No comment provided by engineer. */ +"Export all arguments as a text file. This is only for debugging purposes as UTM's built-in QEMU differs from upstream QEMU in supported arguments." = "Esporta tutti gli argomenti in un file di testo. Consigliato per il debugging: gli argomenti QEMU utilizzati da UTM potrebbero differire da quelli di QEMU upstream"; + /* Word for decompressing a compressed folder */ "Extracting…" = "Estrazione…"; @@ -638,10 +670,6 @@ /* No comment provided by engineer. */ "Fit To Screen" = "Adatta allo Schermo"; -/* Configuration boot device - UTMQemuConstants */ -"Floppy" = "Dischetto"; - /* No comment provided by engineer. */ "Font" = "Font"; @@ -802,6 +830,12 @@ /* No comment provided by engineer. */ "Import VHDX Image" = "Importa un'Immagine VHDX"; +/* No comment provided by engineer. */ +"Fetch latest Windows installer…" = "Ottieni l'installer di Windows più recente"; + +/* No comment provided by engineer. */ +"Windows Install Guide" = "Guida all'installazione di Windows"; + /* No comment provided by engineer. */ "Import Virtual Machine…" = "Importa una Macchina Virtuale..."; @@ -823,6 +857,8 @@ /* No comment provided by engineer. */ "Interface" = "Interfaccia"; +"Hardware interface on the guest used to mount this image. Different operating systems support different interfaces. The default will be the most common interface." = "L'interfaccia hardware utilizzata dal sistema guest per montare questa imnmagine. Ciascun sistema operativo può supportare diversi tipi di interfacce. L'interfaccia predefinita sarà quella utilizzata comunemente."; + /* VMDisplayWindowController */ "Install Windows Guest Tools…" = "Installa gli Strumenti Guest per Windows…"; @@ -886,6 +922,9 @@ /* No comment provided by engineer. */ "Invert scrolling" = "Inverti lo scorrimento"; +/* No comment provided by engineer. */ +"If enabled, scroll wheel input will be inverted." = "Se abilitato, lo scorrimento della rotella del mouse verrà invertito"; + /* No comment provided by engineer. */ "IP Configuration" = "Impostazioni IP"; @@ -916,6 +955,78 @@ /* No comment provided by engineer. */ "Keep UTM running after last window is closed and all VMs are shut down" = "Esegui UTM anche quando tutte le fineste sono chiuse e nessuna VM è in esecuzione"; +/* No comment provided by engineer. */ +"Show dock icon" = "Mostra icona nel dock"; + +/* No comment provided by engineer. */ +"Show menu bar icon" = "Mostra icona nella barra dei menu"; + +/* No comment provided by engineer. */ +"Prevent system from sleeping when any VM is running" = "Impedisci lo stop del sistema quando una VM è in esecuzione"; + +/* No comment provided by engineer. */ +"Do not show confirmation when closing a running VM" = "Non mostrare un avviso alla chiusura di una VM in esecuzione"; + +/* No comment provided by engineer. */ +"Closing a VM without properly shutting it down could result in data loss." = "La chiusura di una VM senza il corretto spegnimento potrebbe comportare perdita di dati."; + +/* No comment provided by engineer. */ +"QEMU Graphics Acceleration" = "Accelerazione Grafica QEMU"; + +/* No comment provided by engineer. */ +"By default, the best renderer for this device will be used. You can override this with to always use a specific renderer. This only applies to QEMU VMs with GPU accelerated graphics." = "Il miglior Renderer per questo dispositivo verrà selezionato automaticamente. Puoi sceglierne un altro con questa opzione. Si applica solo alle VM QEMU con accelerazione GPU"; + +/* No comment provided by engineer. */ +"If set, a frame limit can improve smoothness in rendering by preventing stutters when set to the lowest value your device can handle." = "Se impostato, un limite di frame può migliorare l'esperienza quando impostato al valore minimo di refresh del proprio dispositivo."; + +/* No comment provided by engineer. */ +"By default, the best backend for the target will be used. If the selected backend is not available for any reason, an alternative will automatically be selected." = "Il miglior backand disponibile verrà scelto automaticamente. Se il backend selezionato non fosse disponibile, verrà selezionata un'alternativa automaticamente"; + +/* No comment provided by engineer. */ +"FPS Limit" = "Limite FPS"; + +/* No comment provided by engineer. */ +"QEMU Sound" = "Suono QEMU"; + +/* No comment provided by engineer. */ +"Renderer Backend" = "Backend Renderer"; + +/* No comment provided by engineer. */ +"Sound Backend" = "Backend Suono"; + +/* No comment provided by engineer. */ +"Mouse/Keyboard" = "Mouse/Tastiera"; + +/* No comment provided by engineer. */ +"QEMU Pointer" = "Puntatore QEMU"; + +/* No comment provided by engineer. */ +"QEMU Keyboard" = "Tastiera QEMU"; + +/* No comment provided by engineer. */ +"Capture input automatically when entering full screen" = "Cattura automaticamente l'input in modalità schermo intero"; + +/* No comment provided by engineer. */ +"If enabled, input capture will toggle automatically when entering and exiting full screen mode." = "Se abilitato, la cattura dell'input verrà attivata e disattivata all'entrata e all'uscita dalla modalità a schermo intero"; + +/* No comment provided by engineer. */ +"Capture input automatically when window is focused" = "Cattura automaticamente l'input quando la finestra è in primo piano"; + +/* No comment provided by engineer. */ +"If enabled, input capture will toggle automatically when the VM's window is focused." = "Se abilitato, la cattura dell'imput verrà automaticamente attivata o disattivata quando la finestra della VM è in primo piano o non selezionata"; + +/* No comment provided by engineer. */ +"Option (⌥) is Meta key" = "Option (⌥) come Meta key"; + +/* No comment provided by engineer. */ +"If enabled, Option will be mapped to the Meta key which can be useful for emacs. Otherwise, option will work as the system intended (such as for entering international text)." = "Se abilitata, il tasto Option (⌥) verrà mappato come Meta key, utile per emacs. In caso contrario, il tasto Option funzionerà come da impostazioni di sistema (ad esempio per inserimento di testo speciale o con caratteri internazionali)"; + +/* No comment provided by engineer. */ +"Num Lock is forced on" = "Forza attivazione Num Lock"; + +/* No comment provided by engineer. */ +"Sound Backend" = "Mostra icona nella barra dei menu"; + /* No comment provided by engineer. */ "Legacy" = "Legacy"; @@ -986,7 +1097,7 @@ "Manual Serial Device (advanced)" = "Dispositivo Seriale Manuale (avanzate)"; /* No comment provided by engineer. */ -"Maximum Shared USB Devices" = "Numero Massimo di Dispositivi USB Condivisi"; +"Maximum Shared USB Devices" = "Dispositivi USB Condivisi Massimi"; /* No comment provided by engineer. */ "MB" = "MB"; @@ -1049,10 +1160,10 @@ "Network Mode" = "Modalità di Rete"; /* No comment provided by engineer. */ -"New" = "Nuova"; +"New" = "Crea"; /* No comment provided by engineer. */ -"New…" = "Nuova..."; +"New…" = "Crea..."; /* No comment provided by engineer. */ "New Drive" = "Nuovo Disco"; @@ -1064,7 +1175,7 @@ "New port forward" = "Nuovo Inoltro di Porta"; /* No comment provided by engineer. */ -"New Virtual Machine" = "Nuova Macchina Virtuale (VM)"; +"New Virtual Machine" = "Nuova Macchina Virtuale"; /* No comment provided by engineer. */ "New VM" = "Nuova VM"; @@ -1104,7 +1215,7 @@ /* UTMLegacyQemuConfiguration UTMQemuConstants */ -"None" = "Nessuno"; +"None" = "Nessuno/a"; /* UTMQemuConstants */ "None (Advanced)" = "Nessuno (Avanzato)"; @@ -1146,7 +1257,11 @@ "Optionally select a directory to make accessible inside the VM. Note that support for shared directories varies by the guest operating system and may require additional guest drivers to be installed. See UTM support pages for more details." = "Se vuoi, puoi selezionare una cartella da rendere accessibile all'interno della VM. Il supporto alle cartelle condivise varia in base al sistema operativo e potrebbe essere necessario installare driver sulla VM. Controlla le pagine di supporto di UTM per ulteriori informazioni."; /* No comment provided by engineer. */ +"Skip ISO boot" = "Salta avvio da ISO"; "Other" = "Altro"; +"Options" = "Opzioni"; +"Legacy Hardware" = "Hardware Legacy"; +"If checked, emulated devices with higher compatibility will be instantiated at the cost of performance." = "Se attivo, i dispositivi emulati avranno una compatibilità elevata, al costo di prestazioni peggiori."; /* No comment provided by engineer. */ "Path" = "Percorso"; @@ -1215,7 +1330,7 @@ "QEMU" = "QEMU"; /* No comment provided by engineer. */ -"QEMU Arguments" = "Argomenti di QEMU"; +"QEMU Arguments" = "Argomenti QEMU"; /* UTMQemuVirtualMachine */ "QEMU exited from an error: %@" = "QEMU si è interrotto a causa di un errore: %@"; @@ -1235,6 +1350,9 @@ /* No comment provided by engineer. */ "Quit" = "Esci"; +/* No comment provided by engineer. */ +"Terminate UTM and stop all running VMs." = "Termina UTM e tutte le macchine virtuali in esecuzione."; + /* VMQemuDisplayMetalWindowController */ "Quitting UTM will kill all running VMs." = "L'uscita da UTM causerà l'interruzione di tutte le VM in esecuzione."; @@ -1247,6 +1365,9 @@ /* No comment provided by engineer. */ "Raw Image" = "Immagine Raw"; +/* No comment provided by engineer. */ +"Advanced. If checked, a raw disk image is used. Raw disk image does not support snapshots and will not dynamically expand in size." = "Impostazione Avanzata. Se attivo, verrà utilizzata un'immagine disco raw. Le immagini disco raw non supportano le snapshot e non vengono ridimensionate automaticamente."; + /* VMDisplayAppleController */ "Read Only" = "Sola Lettura"; @@ -1257,7 +1378,19 @@ "Reclaim Space" = "Recupera Spazio"; /* No comment provided by engineer. */ -"Reclaim disk space by re-converting the disk image." = "Recupera spazio su disco ri-convertendo l'immagine disco."; +"Reclaim disk space by re-converting the disk image." = "Recupera spazio su disco riconvertendo l'immagine disco."; + +/* No comment provided by engineer. */ +"Compress" = "Comprimi"; + +/* No comment provided by engineer. */ +"Compress by re-converting the disk image and compressing the data." = "Recupera spazio su disco riconvertendo l'immagine disco e comprimendo i dati."; + +/* No comment provided by engineer. */ +"Resize" = "Ridimensiona"; + +/* No comment provided by engineer. */ +"Increase the size of the disk image." = "Incrementa la dimensione dell'immagine disco."; /* UTMQemuConstants */ "Regular" = "Normale"; @@ -1265,6 +1398,12 @@ /* No comment provided by engineer. */ "Removable" = "Rimovibile"; +/* No comment provided by engineer. */ +"If checked, the drive image will be stored with the VM." = "Se attivo, l'immagine del disco sarà salvata insieme alla VM"; + +/* No comment provided by engineer. */ +"If checked, no drive image will be stored with the VM. Instead you can mount/unmount image while the VM is running." = "Se attivo, l'immagine del disco non sarà salvata insieme alla VM. Può essere, invece, montata e smontata quando la VM è in esecuzione"; + /* No comment provided by engineer. */ "Removable Drive" = "Disco Rimovibile"; @@ -1307,6 +1446,21 @@ /* No comment provided by engineer. */ "Resolution" = "Risoluzione"; +/* No comment provided by engineer. */ +"Width" = "Larghezza"; + +/* No comment provided by engineer. */ +"Height" = "Altezza"; + +/* No comment provided by engineer. */ +"Only available on macOS virtual machines." = "Disponibile solo su macchine virtuali macOS."; + +/* No comment provided by engineer. */ +"Dynamic Resolution" = "Risoluzione Dinamica"; + +/* No comment provided by engineer. */ +"Only available on macOS 14+ virtual machines." = "Disponibile solo su macchine virtuali macOS (versione 14 o superiore)."; + /* No comment provided by engineer. */ "Restart" = "Ricomincia"; @@ -1358,9 +1512,6 @@ /* Save VM overlay */ "Saving %@…" = "Salvataggio di %@..."; -/* No comment provided by engineer. */ -"Scaling" = "Scala"; - /* No comment provided by engineer. */ "Selected:" = "Selezionato:"; @@ -1748,14 +1899,23 @@ "USB Support" = "Supporto USB"; /* No comment provided by engineer. */ -"Use Apple Virtualization" = "Usa la Virtualizzazione di Apple"; +"Use Apple Virtualization" = "Usa la Virtualizzazione Apple"; /* No comment provided by engineer. */ "Use Command+Option (⌘+⌥) for input capture/release" = "Usa Command+Option (⌘+⌥) per catturare/rilasciare l'input"; +/* No comment provided by engineer. */ +"If disabled, the default combination Control+Option (⌃+⌥) will be used." = "Se disabilitato, verrà usata la combinazione di default Control+Option (⌃+⌥)."; + /* No comment provided by engineer. */ "Use Hypervisor" = "Usa Hypervisor"; +/* No comment provided by engineer. */ +"Use TSO" = "Usa TSO"; + +/* No comment provided by engineer. */ +"Only available when Hypervisor is used on supported hardware. TSO speeds up Intel emulation in the guest at the cost of decreased performance in general." = "Disponibile solo con un Hypervisor e hardware supportato. TSO incrementa le prestazioni di emulazione di un guest Intel, al costo di performance generali ridotte."; + /* No comment provided by engineer. */ "Use local time for base clock" = "Usa l'ora locale locale per l'orologio di sistema"; @@ -1779,7 +1939,7 @@ "Virtual Machine" = "Macchina Virtuale"; /* No comment provided by engineer. */ -"Virtual Machine Gallery" = "Libreria di VM (UTM Gallery)"; +"Virtual Machine Gallery" = "Libreria di Macchine Virtuali"; /* New VM window. */ "Virtualization Engine" = "Motore di Virtualizzazione"; @@ -1851,5 +2011,98 @@ /* No comment provided by engineer. */ "Options here only apply on next boot and are not saved." = "Queste impostazioni vengo applicate solo all'avvio successivo e non sono salvate."; +/* No comment provided by engineer. */ +"You can use this if your boot options are corrupted or if you wish to re-enroll in the default keys for secure boot." = "Puoi usare questa opzione se le impostazioni di boot sono compromesse o se desideri ricreare le chiavi di default per il secure boot."; + /* No comment provided by engineer. */ "Create a new VM" = "Crea una nuova VM"; + +/* No comment provided by engineer. */ +"Show UTM" = "Mostra UTM"; + +/* No comment provided by engineer. */ +"Show the main window." = "Mostra la finestra principale."; + +/* No comment provided by engineer. */ +"Hide dock icon on next launch" = "Nascondi icona del dock al prossimo avvio"; + +/* No comment provided by engineer. */ +"No virtual machines found." = "Nessuna macchina virtuale trovata."; + +/* No comment provided by engineer. */ +"Requires restarting UTM to take affect." = "Richiede il riavvio di UTM."; + +/* No comment provided by engineer. */ +"Choose" = "Seleziona"; + +/* No comment provided by engineer. */ +"Arguments" = "Argomenti"; + +/* No comment provided by engineer. */ +"Guest drivers are required for 3D acceleration." = "L'installazione dei driver guest è richiesta per l'accelerazione 3D."; + +/* No comment provided by engineer. */ +"GPU Acceleration supported" = "Accelerazione GPU Supportata"; + +/* No comment provided by engineer. */ +"Automatic" = "Automatica"; + +/* No comment provided by engineer. */ +"Read Only?" = "Sola Lettura"; + +/* No comment provided by engineer. */ +"Update Interface" = "Aggiorna Interfaccia"; + +/* No comment provided by engineer. */ +"Older versions of UTM added each IDE device to a separate bus. Check this to change the configuration to place two units on each bus." = "Le versioni precedenti di UTM aggiungevano ciascun dispositivo IDE ad un bus separato. Seleziona questa opzione per modificare la configurazione e impostare due unità su ciascun bus."; + +/* No comment provided by engineer. */ +"What's New" = "Novità di UTM"; + +/* No comment provided by engineer. */ +"Enable UTM Server" = "Abilita UTM Server"; + +/* No comment provided by engineer. */ +"Last Seen" = "Accesso più recente"; + +/* No comment provided by engineer. */ +"Reset Identity" = "Ripristina Identità"; + +/* No comment provided by engineer. */ +"Do you want to forget all clients and generate a new server identity? Any clients that previously paired with this server will be instructed to manually unpair with this server before they can connect again." = "Vuoi dimenticare tutti i client e generare una nuova identità del server? Verrà chiesto a tutti i client collegati di disassociarsi manualmente prima di poter stabile una nuova connessione."; + +/* No comment provided by engineer. */ +"Reset Identity" = "Ripristina Identità"; + +/* No comment provided by engineer. */ +"Startup" = "Avvio"; + +/* No comment provided by engineer. */ +"Automatically start UTM server" = "Avvia il Server UTM automaticamente"; + +/* No comment provided by engineer. */ +"Reject unknown connections by default" = "Rifiuta connessioni sconosciute"; + +/* No comment provided by engineer. */ +"If checked, you will not be prompted about any unknown connection and they will be rejected." = "Se attivo, non ti verrà chiesto di accettare richieste da origini sconosciute, che verranno automaticamente rifiutate."; + +/* No comment provided by engineer. */ +"Allow access from external clients" = "Permetti l'accesso da client esterni"; + +/* No comment provided by engineer. */ +"By default, the server is only available on LAN but setting this will use UPnP/NAT-PMP to port forward to WAN." = "Di default, il server è solo disponibile su rete locale, attivando questa opzione verrà attivato UPnP/NAT-PMP per il port forwarding su WAN"; + +/* No comment provided by engineer. */ +"Any" = "Qualunque"; + +/* No comment provided by engineer. */ +"Specify a port number to listen on. This is required if external clients are permitted." = "Specifica una porta d'ascolto. Richiesto se l'accesso da client esterni è attivato."; + +/* No comment provided by engineer. */ +"Authentication" = "Autenticazione"; + +/* No comment provided by engineer. */ +"Require Password" = "Richiedi Password"; + +/* No comment provided by engineer. */ +"If enabled, clients must enter a password. This is required if you want to access the server externally." = "Se abilitato, i client dovranno inserire una password. Richiesto se vuoi accedere da client esterni."; From 7b723972eb4c326dab68fb35a37512c419f98bf1 Mon Sep 17 00:00:00 2001 From: Tiffany Fung <46277006+changanmoon@users.noreply.github.com> Date: Sat, 18 May 2024 23:09:49 +0800 Subject: [PATCH 40/79] Update Localizable.strings --- Platform/zh-Hans.lproj/Localizable.strings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Platform/zh-Hans.lproj/Localizable.strings b/Platform/zh-Hans.lproj/Localizable.strings index 5bea0a52e..b51e4e13a 100644 --- a/Platform/zh-Hans.lproj/Localizable.strings +++ b/Platform/zh-Hans.lproj/Localizable.strings @@ -1837,7 +1837,7 @@ "If enabled, input capture will toggle automatically when the VM's window is focused." = "若启用,输入捕捉将在虚拟机窗口聚焦时自动切换。"; /* No comment provided by engineer. */ -"If enabled, num lock will always be on to the guest. Note this may make your keyboard's num lock indicator out of sync." = "若启用,Num Lock 将始终对客户机开启。注意,这可能会使键盘的 Num Lock 指示灯不同步。"; +"If enabled, num lock will always be on to the guest. Note this may make your keyboard's num lock indicator out of sync." = "若启用,Num Lock 将始终对客户机开启。注意,这可能会使键盘的 Num Lock 指示灯不同步。"; /* No comment provided by engineer. */ "If enabled, Option will be mapped to the Meta key which can be useful for emacs. Otherwise, option will work as the system intended (such as for entering international text)." = "若启用,Option 键将映射到 Meta 键,这对 Emacs 很有用。否则,Option 键将按照系统默认方式工作 (例如输入国际文本)。"; From da2a58bd9affba3b383cf433e1d1c1225452807d Mon Sep 17 00:00:00 2001 From: osy <50960678+osy@users.noreply.github.com> Date: Thu, 23 May 2024 15:45:47 -0700 Subject: [PATCH 41/79] config: open icon picker when clicking icon as well Fixes #6316 --- Platform/Shared/VMConfigInfoView.swift | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Platform/Shared/VMConfigInfoView.swift b/Platform/Shared/VMConfigInfoView.swift index 08eb0d7af..f7a90fe39 100644 --- a/Platform/Shared/VMConfigInfoView.swift +++ b/Platform/Shared/VMConfigInfoView.swift @@ -125,6 +125,9 @@ struct VMConfigInfoView: View { #if os(macOS) VStack { IconPreview(url: config.iconURL) + .onTapGesture { + imageSelectVisible.toggle() + } Button(action: { imageSelectVisible.toggle() }, label: { Text("Choose") }).fileImporter(isPresented: $imageSelectVisible, allowedContentTypes: [.image]) { result in @@ -148,6 +151,9 @@ struct VMConfigInfoView: View { #if os(macOS) VStack { IconPreview(url: config.iconURL) + .onTapGesture { + imageSelectVisible.toggle() + } Button(action: { imageSelectVisible.toggle() }, label: { Text("Choose") }).popover(isPresented: $imageSelectVisible, arrowEdge: .bottom) { From 1819196507978afe7da72403a88fd8c41ca2f20a Mon Sep 17 00:00:00 2001 From: osy <50960678+osy@users.noreply.github.com> Date: Thu, 23 May 2024 15:52:23 -0700 Subject: [PATCH 42/79] config: fix alignment issue in icon picker Only for macOS 13+ Fixes #6317 --- Platform/Shared/VMConfigInfoView.swift | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/Platform/Shared/VMConfigInfoView.swift b/Platform/Shared/VMConfigInfoView.swift index f7a90fe39..6721b8359 100644 --- a/Platform/Shared/VMConfigInfoView.swift +++ b/Platform/Shared/VMConfigInfoView.swift @@ -265,11 +265,12 @@ private struct IconSelect: View { LazyVGrid(columns: gridLayout, spacing: 0) { ForEach(icons, id: \.self) { icon in Button(action: { onIconSelected(icon) }, label: { - VStack { + VStack(alignment: .center) { Logo(logo: PlatformImage(contentsOfURL: icon)) Text(iconToTitle(icon)) - .lineLimit(2) + .lineLimit(2, optionalReservesSpace: true) .font(.footnote) + .multilineTextAlignment(.center) } .padding(8) .frame(width: iconGridSize, height: iconGridSize) @@ -283,6 +284,17 @@ private struct IconSelect: View { } } +private extension View { + @ViewBuilder + func lineLimit(_ limit: Int, optionalReservesSpace: Bool) -> some View { + if #available(macOS 13, iOS 16, *) { + self.lineLimit(limit, reservesSpace: optionalReservesSpace) + } else { + self.lineLimit(limit) + } + } +} + struct VMConfigInfoView_Previews: PreviewProvider { @State static private var config = UTMConfigurationInfo() From 55fa8b2db87af1c1fa68d163fa52474f8deecbe5 Mon Sep 17 00:00:00 2001 From: osy <50960678+osy@users.noreply.github.com> Date: Thu, 23 May 2024 16:09:20 -0700 Subject: [PATCH 43/79] display(macOS): force resign key when showing error This should already happen but maybe sometime it does not? Fixes #6352 --- Platform/macOS/Display/VMDisplayWindowController.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Platform/macOS/Display/VMDisplayWindowController.swift b/Platform/macOS/Display/VMDisplayWindowController.swift index 7686b008e..5cc3304cb 100644 --- a/Platform/macOS/Display/VMDisplayWindowController.swift +++ b/Platform/macOS/Display/VMDisplayWindowController.swift @@ -210,6 +210,7 @@ class VMDisplayWindowController: NSWindowController, UTMVirtualMachineDelegate { @MainActor func showErrorAlert(_ message: String, completionHandler handler: ((NSApplication.ModalResponse) -> Void)? = nil) { + window?.resignKey() let alert = NSAlert() alert.alertStyle = .critical alert.messageText = NSLocalizedString("Error", comment: "VMDisplayWindowController") @@ -219,6 +220,7 @@ class VMDisplayWindowController: NSWindowController, UTMVirtualMachineDelegate { @MainActor func showConfirmAlert(_ message: String, confirmHandler handler: (() -> Void)? = nil) { + window?.resignKey() let alert = NSAlert() alert.alertStyle = .informational alert.messageText = NSLocalizedString("Confirmation", comment: "VMDisplayWindowController") From 779178c31697dce085fab21eb77fd025e8c70e0b Mon Sep 17 00:00:00 2001 From: osy <50960678+osy@users.noreply.github.com> Date: Thu, 23 May 2024 20:59:45 -0700 Subject: [PATCH 44/79] project: bumped version --- Build.xcconfig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Build.xcconfig b/Build.xcconfig index 5ae5fa348..945dc0444 100644 --- a/Build.xcconfig +++ b/Build.xcconfig @@ -17,8 +17,8 @@ // Configuration settings file format documentation can be found at: // https://help.apple.com/xcode/#/dev745c5c974 -MARKETING_VERSION = 4.5.2 -CURRENT_PROJECT_VERSION = 97 +MARKETING_VERSION = 4.5.3 +CURRENT_PROJECT_VERSION = 99 // Codesigning settings defined optionally, see Documentation/iOSDevelopment.md #include? "CodeSigning.xcconfig" From 19971d7ce20696c17c2e8989c2bff85f0f2f2f64 Mon Sep 17 00:00:00 2001 From: MMP0 <28616020+MMP0@users.noreply.github.com> Date: Fri, 24 May 2024 13:06:03 +0900 Subject: [PATCH 45/79] Update for 8e84a1e --- Platform/ja.lproj/Localizable.strings | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/Platform/ja.lproj/Localizable.strings b/Platform/ja.lproj/Localizable.strings index c88b81071..9103273b0 100644 --- a/Platform/ja.lproj/Localizable.strings +++ b/Platform/ja.lproj/Localizable.strings @@ -440,6 +440,8 @@ // VMConfigAppleDriveCreateView.swift "Removable" = "リムーバブル"; "If checked, the drive image will be stored with the VM." = "チェックを入れると、ドライブイメージが仮想マシンに保存されます。"; +"Use NVMe Interface" = "NVMeを使用"; +"If checked, use NVMe instead of virtio as the disk interface, available on macOS 14+ for Linux guests only. This interface is slower but less likely to encounter filesystem errors." = "チェックを入れると、virtioの代わりにNVMeをディスクインターフェイスとして使用します。これは、macOS 14以降のLinuxゲストのみで使用できます。このインターフェイスは低速ですが、ファイルシステムエラーが発生する可能性が低くなります。"; // VMConfigAppleDriveDetailsView.swift "Removable Drive" = "リムーバブルドライブ"; @@ -1141,9 +1143,3 @@ // UTMQemuMonitor.m "Guest panic" = "ゲストがパニック状態に陥りました"; - -/* VMConfigAppleDriveDetailsView - VMConfigAppleDriveCreateView*/ -"Use NVMe Interface" = "NVMe を使用します"; -"If checked, use NVMe instead of virtio as the disk interface, available on macOS 14+ for Linux guests only. This interface is slower but less likely to encounter filesystem errors." = "チェックされている場合、ディスクインターフェースとして virtio の代わりに NVMe を使用します。macOS 14+ では Linux ゲストのみ利用可能です。このインターフェースは遅いですが、ファイルシステムエラーが発生する可能性が低いです。"; - From 4d012d1b53275375d8edd9552ec395d4eb35055e Mon Sep 17 00:00:00 2001 From: MMP0 <28616020+MMP0@users.noreply.github.com> Date: Fri, 24 May 2024 13:14:35 +0900 Subject: [PATCH 46/79] Small fixes --- Platform/ja.lproj/Localizable.strings | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Platform/ja.lproj/Localizable.strings b/Platform/ja.lproj/Localizable.strings index 9103273b0..f9e02579b 100644 --- a/Platform/ja.lproj/Localizable.strings +++ b/Platform/ja.lproj/Localizable.strings @@ -440,8 +440,8 @@ // VMConfigAppleDriveCreateView.swift "Removable" = "リムーバブル"; "If checked, the drive image will be stored with the VM." = "チェックを入れると、ドライブイメージが仮想マシンに保存されます。"; -"Use NVMe Interface" = "NVMeを使用"; -"If checked, use NVMe instead of virtio as the disk interface, available on macOS 14+ for Linux guests only. This interface is slower but less likely to encounter filesystem errors." = "チェックを入れると、virtioの代わりにNVMeをディスクインターフェイスとして使用します。これは、macOS 14以降のLinuxゲストのみで使用できます。このインターフェイスは低速ですが、ファイルシステムエラーが発生する可能性が低くなります。"; +"Use NVMe Interface" = "NVMeインターフェイスを使用"; +"If checked, use NVMe instead of virtio as the disk interface, available on macOS 14+ for Linux guests only. This interface is slower but less likely to encounter filesystem errors." = "チェックを入れると、Virtioの代わりにNVMeをディスクインターフェイスとして使用します。これは、macOS 14以降のLinuxゲストでのみ使用できます。このインターフェイスは低速ですが、ファイルシステムエラーが発生する可能性が低くなります。"; // VMConfigAppleDriveDetailsView.swift "Removable Drive" = "リムーバブルドライブ"; From 633239a5aec8f4ce4cf335536a6cf63ef5abbe95 Mon Sep 17 00:00:00 2001 From: Tiffany Fung <46277006+changanmoon@users.noreply.github.com> Date: Mon, 27 May 2024 15:29:58 +0800 Subject: [PATCH 47/79] Update README.zh-HK.md --- README.zh-HK.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/README.zh-HK.md b/README.zh-HK.md index bebba9d9b..1c0b11f0d 100644 --- a/README.zh-HK.md +++ b/README.zh-HK.md @@ -1,7 +1,7 @@ # UTM [![Build](https://github.com/utmapp/UTM/workflows/Build/badge.svg?branch=master&event=push)][1] -> 發明一台可用於計算任何可計算序列的機器是可行的。 +> 發明一個可用於計算任何可計算序列的機器是可行的。 -- 艾倫·圖靈(Alan Turing), 1936 年 UTM 是一個功能完备的系統模擬工具與虛擬电脑主機,適用於 iOS 和 macOS。它基於 QEMU。簡言之,它允許你在 Mac、iPhone 和 iPad 上執行 Windows、Linux 等。更多訊息請見 https://getutm.app/ 與 https://mac.getutm.app/。 @@ -14,33 +14,33 @@ UTM 是一個功能完备的系統模擬工具與虛擬电脑主機,適用於 ## 特性 -* 使用 QEMU 進行全作業系統模擬(MMU、裝置等) +* 使用 QEMU 進行全作業系統仿真(MMU、裝置等) * 支援逾三十種體系結構 CPU,包括 x86_64、ARM64 和 RISC-V * 使用 SPICE 與 QXL 的 VGA 圖形模式 -* 文本終端機模式 +* 文本終端機(TTY)模式 * USB 裝置 * 使用 QEMU TCG 進行基於 JIT 的加速 -* 採用了最新最靚的 API,由頭開始設計前端,支援 macOS 11+ 與 iOS 11+ +* 採用最新最靚的 API,由頭開始設計前端,支援 macOS 11+ 與 iOS 11+ * 於你的裝置上直接製作、管理與執行虛擬機 ## 於 macOS 的附加功能 * 使用 Hypervisor.framework 與 QEMU 實現硬件加速虛擬化 -* 在 macOS 12+ 上使用 Virtualization.framework 來啟動 macOS 客戶端 +* 在 macOS 12+ 上使用 Virtualization.framework 啟動 macOS 客戶端 ## UTM SE UTM/QEMU 需要動態程式碼生成(JIT)以得到最大性能。iOS 上的 JIT 需要已經越獄(Jailbreak)的裝置(iOS 11.0~14.3 無需越獄,iOS 14.4+ 需要),或者為特定版本的 iOS 找到其他變通方法之一(有關更多詳細訊息,請見「安裝」)。 -UTM SE(「較慢版」)使用了「[執行緒解釋器][3]」,其性能優於傳統解釋器,但仍然比 JIT 要慢。此種技術類似 [iSH][4] 的動態執行。因此,UTM SE 無需越獄或任何 JIT 的變通方法,可以作為常規應用程式側載(Sideload)。 +UTM SE(「較慢版」)使用了「[執行緒解釋器][3]」,其性能優於傳統解釋器,但仍然慢過 JIT。此種技術類似 [iSH][4] 的動態執行。因此,UTM SE 無需越獄或者任何 JIT 的其他變通方法,可以作為常規的應用程式側載(Sideload)。 -為了最佳化大小與構建時間,UTM SE 當中只包含以下的體系結構:ARM、PPC、RISC-V 和 x86(均包含 32 位元和 64 位元)。 +為了最佳化大小與構建時間,UTM SE 當中僅包括以下的體系結構:ARM、PPC、RISC-V 和 x86(均包括 32 位元與 64 位元)。 ## 安裝 iOS 版本 UTM(SE):https://getutm.app/install/ -UTM 同時支援 macOS:https://mac.getutm.app/ +UTM 亦支援 macOS:https://mac.getutm.app/ ## 開發 @@ -55,7 +55,7 @@ UTM 同時支援 macOS:https://mac.getutm.app/ ## 許可證 -UTM 於 Apache 2.0 許可證下發佈,但它採用了若干 GPL 與 LGPL 元件,當中大多數元件為動態連接,但 gstreamer 元件為靜態連接,部分程式碼來自 QEMU。如你打算重新分發此應用程式,請務必緊記這一點。 +UTM 於 Apache 2.0 許可證下發佈,但它採用了若干 GPL 與 LGPL 元件,當中大多數元件為動態連接,但是 gstreamer 元件為靜態連接,部分程式碼來自 QEMU。如你打算重新分發此應用程式,請務必緊記這一點。 某些图示由 [Freepik](https://www.freepik.com) 從 [www.flaticon.com](https://www.flaticon.com/) 製作。 From 5b30b75715818e67e136109a18dcdd0f9b7653b2 Mon Sep 17 00:00:00 2001 From: Arman Date: Wed, 26 Jun 2024 15:06:41 +0300 Subject: [PATCH 48/79] Update ru i10n --- Platform/ru.lproj/Localizable.strings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Platform/ru.lproj/Localizable.strings b/Platform/ru.lproj/Localizable.strings index f6f8eff9e..d955bdb03 100644 --- a/Platform/ru.lproj/Localizable.strings +++ b/Platform/ru.lproj/Localizable.strings @@ -244,7 +244,7 @@ "Change…" = "Изменить…"; /* No comment provided by engineer. */ -"Clear" = "Отчистить"; +"Clear" = "Очистить"; /* No comment provided by engineer. */ "Clipboard Sharing" = "Обмен буфером обмена"; From d599cad338a95b1882d5d5946e6620ba22e2c1a3 Mon Sep 17 00:00:00 2001 From: Logkos <65683493+logkos@users.noreply.github.com> Date: Mon, 22 Jul 2024 23:38:34 +0300 Subject: [PATCH 49/79] Update localizations to reflect use of binary units --- Platform/de.lproj/Localizable.strings | 4 ++-- Platform/es-419.lproj/Localizable.strings | 4 ++-- Platform/fr.lproj/Localizable.strings | 4 ++-- Platform/it.lproj/Localizable.strings | 4 ++-- Platform/ja.lproj/Localizable.strings | 4 ++-- Platform/ko.lproj/Localizable.strings | 2 +- Platform/pl.lproj/Localizable.strings | 8 ++++---- Platform/ru.lproj/Localizable.strings | 4 ++-- Platform/zh-HK.lproj/Localizable.strings | 4 ++-- Platform/zh-Hans.lproj/Localizable.strings | 4 ++-- Platform/zh-Hant.lproj/Localizable.strings | 4 ++-- 11 files changed, 23 insertions(+), 23 deletions(-) diff --git a/Platform/de.lproj/Localizable.strings b/Platform/de.lproj/Localizable.strings index 9b0a636de..ad4f2ecd1 100644 --- a/Platform/de.lproj/Localizable.strings +++ b/Platform/de.lproj/Localizable.strings @@ -364,7 +364,7 @@ "Force Multicore" = "Multicore erzwingen"; /* No comment provided by engineer. */ -"GB" = "GB"; +"GiB" = "GiB"; /* UTMQemuConstants */ "GDB Debug Stub" = "GDB Debug Stub"; @@ -520,7 +520,7 @@ "Maximum Shared USB Devices" = "Anzahl weitergeleiteter USB-Geräte (max.)"; /* No comment provided by engineer. */ -"MB" = "MB"; +"MiB" = "MiB"; /* No comment provided by engineer. */ "Memory" = "Speicher"; diff --git a/Platform/es-419.lproj/Localizable.strings b/Platform/es-419.lproj/Localizable.strings index 05f1b542b..a533bc93b 100644 --- a/Platform/es-419.lproj/Localizable.strings +++ b/Platform/es-419.lproj/Localizable.strings @@ -675,7 +675,7 @@ "Full Graphics" = "Gráficos completos"; /* No comment provided by engineer. */ -"GB" = "GB"; +"GiB" = "GiB"; /* UTMQemuConstants */ "GDB Debug Stub" = "GDB Debug Stub"; @@ -989,7 +989,7 @@ "Maximum Shared USB Devices" = "Número máximo de dispositivos USB compartidos"; /* No comment provided by engineer. */ -"MB" = "MB"; +"MiB" = "MiB"; /* No comment provided by engineer. */ "Memory" = "Memoria"; diff --git a/Platform/fr.lproj/Localizable.strings b/Platform/fr.lproj/Localizable.strings index 11f54201c..c628b8eae 100644 --- a/Platform/fr.lproj/Localizable.strings +++ b/Platform/fr.lproj/Localizable.strings @@ -463,11 +463,11 @@ // RAMSlider.swift "Size" = "Taille"; -"MB" = "MB"; +"MiB" = "MiB"; // SizeTextField.swift "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." = "La quantité de stockage à allouer pour cette image. Ignoré si importation d’image. Si c'est une image brute, un fichier vide de la même taille sera enregistré avec la VM. Sinon, l’image disque sera dynamiquement étendue jusqu’à cette taille."; -"GB" = "GB"; +"GiB" = "GiB"; // VMCardView.swift "Run" = "Démarrer"; diff --git a/Platform/it.lproj/Localizable.strings b/Platform/it.lproj/Localizable.strings index 3dff1a3f8..3e1d59fd8 100644 --- a/Platform/it.lproj/Localizable.strings +++ b/Platform/it.lproj/Localizable.strings @@ -703,7 +703,7 @@ "Full Graphics" = "Grafica Completa"; /* No comment provided by engineer. */ -"GB" = "GB"; +"GiB" = "GiB"; /* UTMQemuConstants */ "GDB Debug Stub" = "GDB Debug Stub"; @@ -1100,7 +1100,7 @@ "Maximum Shared USB Devices" = "Dispositivi USB Condivisi Massimi"; /* No comment provided by engineer. */ -"MB" = "MB"; +"MiB" = "MiB"; /* No comment provided by engineer. */ "Memory" = "Memoria"; diff --git a/Platform/ja.lproj/Localizable.strings b/Platform/ja.lproj/Localizable.strings index f9e02579b..12a8f117a 100644 --- a/Platform/ja.lproj/Localizable.strings +++ b/Platform/ja.lproj/Localizable.strings @@ -566,11 +566,11 @@ // RAMSlider.swift "Size" = "サイズ"; -"MB" = "MB"; +"MiB" = "MiB"; // SizeTextField.swift "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." = "このイメージに割り当てるストレージ領域です。イメージを読み込む場合は無視されます。生イメージの場合、このサイズの空のファイルが仮想マシンに保存されます。そうでない場合、ディスクイメージはこのサイズまで動的に拡張されます。"; -"GB" = "GB"; +"GiB" = "GiB"; // VMCardView.swift "Run" = "実行"; diff --git a/Platform/ko.lproj/Localizable.strings b/Platform/ko.lproj/Localizable.strings index f62f6508d..b33febc92 100644 --- a/Platform/ko.lproj/Localizable.strings +++ b/Platform/ko.lproj/Localizable.strings @@ -398,7 +398,7 @@ "Manager being deallocated, killing pending RPC." = "Manager being deallocated, killing pending RPC."; /* No comment provided by engineer. */ -"MB" = "MB"; +"MiB" = "MiB"; /* No comment provided by engineer. */ "Memory" = "메모리"; diff --git a/Platform/pl.lproj/Localizable.strings b/Platform/pl.lproj/Localizable.strings index 34965726b..be379e607 100644 --- a/Platform/pl.lproj/Localizable.strings +++ b/Platform/pl.lproj/Localizable.strings @@ -422,8 +422,8 @@ "If checked, the drive image will be stored with the VM." = "Jeśli zaznaczone, obraz dysku będzie przechowywany wraz z wirtualną maszyną."; "Size" = "Rozmiar"; "The amount of storage to allocate for this image. An empty file of this size will be stored with the VM." = "Ilość pamięci masowej przydzielonej dla tego obrazu. Pusty plik takiego rozmiaru będzie przechowywany wraz z maszyną wirtualną"; -"GB" = "GB"; -"MB" = "MB"; +"GiB" = "GiB"; +"MiB" = "MiB"; /* VMConfigAppleDriveDetailsView.swift */ "Name" = "Nazwa"; @@ -547,14 +547,14 @@ /* RAMSlider.swift */ "Size" = "Rozmiar"; -"MB" = "MB"; +"MiB" = "MiB"; /* FileBrowseField.swift */ "Path" = "Ścieżka"; /* SizeTextField.swift */ "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." = "Ilość pamięci masowej do przydzielenia dla tego obrazu. Ignorowane, jeśli importujesz obraz. Jeśli jest to surowy obraz, wtedy plusty plik tego rozmiaru będzie przechowywany wraz z wirtualną maszyną. W przeciwnym wypadku, obraz dysku będzie dynamicznie się rozszerzał do docelowego rozmiaru."; -"GB" = "GB"; +"GiB" = "GiB"; /* VMCardView.swift */ "Run" = "Uruchom"; diff --git a/Platform/ru.lproj/Localizable.strings b/Platform/ru.lproj/Localizable.strings index f6f8eff9e..0e78cf812 100644 --- a/Platform/ru.lproj/Localizable.strings +++ b/Platform/ru.lproj/Localizable.strings @@ -681,7 +681,7 @@ "Full Graphics" = "Полная графика"; /* No comment provided by engineer. */ -"GB" = "ГБ"; +"GiB" = "ГиБ"; /* UTMQemuConstants */ "GDB Debug Stub" = "Плагин отладки GDB"; @@ -1004,7 +1004,7 @@ "Maximum Shared USB Devices" = "Максимальное кол-во совместно используемых USB-устройств"; /* No comment provided by engineer. */ -"MB" = "МБ"; +"MiB" = "МиБ"; /* No comment provided by engineer. */ "Memory" = "Память"; diff --git a/Platform/zh-HK.lproj/Localizable.strings b/Platform/zh-HK.lproj/Localizable.strings index 330e6fc9e..d148d15f2 100644 --- a/Platform/zh-HK.lproj/Localizable.strings +++ b/Platform/zh-HK.lproj/Localizable.strings @@ -542,7 +542,7 @@ "Force shut down" = "強行關機"; /* No comment provided by engineer. */ -"GB" = "GB"; +"GiB" = "GiB"; /* UTMQemuConstants */ "GDB Debug Stub" = "GDB Debug Stub"; @@ -725,7 +725,7 @@ "Maximum Shared USB Devices" = "最多分享 USB 裝置"; /* No comment provided by engineer. */ -"MB" = "MB"; +"MiB" = "MiB"; /* No comment provided by engineer. */ "Memory" = "記憶體"; diff --git a/Platform/zh-Hans.lproj/Localizable.strings b/Platform/zh-Hans.lproj/Localizable.strings index 81ccee3d4..d2b26cb0f 100644 --- a/Platform/zh-Hans.lproj/Localizable.strings +++ b/Platform/zh-Hans.lproj/Localizable.strings @@ -542,7 +542,7 @@ "Force shut down" = "强制关机"; /* No comment provided by engineer. */ -"GB" = "GB"; +"GiB" = "GiB"; /* UTMQemuConstants */ "GDB Debug Stub" = "GDB 调试存根"; @@ -725,7 +725,7 @@ "Maximum Shared USB Devices" = "最大共享 USB 设备数"; /* No comment provided by engineer. */ -"MB" = "MB"; +"MiB" = "MiB"; /* No comment provided by engineer. */ "Memory" = "内存"; diff --git a/Platform/zh-Hant.lproj/Localizable.strings b/Platform/zh-Hant.lproj/Localizable.strings index 8070b72bd..03be69ded 100644 --- a/Platform/zh-Hant.lproj/Localizable.strings +++ b/Platform/zh-Hant.lproj/Localizable.strings @@ -720,7 +720,7 @@ "FPS Limit" = "FPS 上限"; /* No comment provided by engineer. */ -"GB" = "GB"; +"GiB" = "GiB"; /* UTMQemuConstants */ "GDB Debug Stub" = "GDB 除錯 stub"; @@ -1023,7 +1023,7 @@ "Maximum Shared USB Devices" = "最大共享 USB 裝置"; /* No comment provided by engineer. */ -"MB" = "MB"; +"MiB" = "MiB"; /* No comment provided by engineer. */ "Memory" = "記憶體"; From e0d023285eaf34b7f98c5b8f0d5d3fed352195eb Mon Sep 17 00:00:00 2001 From: Logkos <65683493+logkos@users.noreply.github.com> Date: Mon, 22 Jul 2024 23:40:11 +0300 Subject: [PATCH 50/79] Reflect use of binary units and switch to global abbreviation --- Platform/fi.lproj/Localizable.strings | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Platform/fi.lproj/Localizable.strings b/Platform/fi.lproj/Localizable.strings index 58d87eaef..257095051 100644 --- a/Platform/fi.lproj/Localizable.strings +++ b/Platform/fi.lproj/Localizable.strings @@ -440,7 +440,7 @@ "Full Graphics" = "Täysi grafiikka"; /* No comment provided by engineer. */ -"GB" = "Gt"; +"GiB" = "GiB"; /* No comment provided by engineer. */ "Generate Windows Installer ISO" = "Luo Windows Installer ISO"; @@ -650,7 +650,7 @@ "Maximum Shared USB Devices" = "Jaettujen USB-laitteiden enimmäismäärä"; /* No comment provided by engineer. */ -"MB" = "Mt"; +"MiB" = "MiB"; /* No comment provided by engineer. */ "Memory" = "Muisti"; From b23b61504398da083aa6e40bd7884636b25bda1a Mon Sep 17 00:00:00 2001 From: Logkos <65683493+logkos@users.noreply.github.com> Date: Mon, 22 Jul 2024 23:41:25 +0300 Subject: [PATCH 51/79] Imply use of binary units --- Platform/Shared/RAMSlider.swift | 2 +- Platform/Shared/SizeTextField.swift | 4 ++-- Platform/Shared/VMConfigSystemView.swift | 2 +- Platform/Shared/VMWizardDrivesView.swift | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Platform/Shared/RAMSlider.swift b/Platform/Shared/RAMSlider.swift index 66e0b5458..2b4051355 100644 --- a/Platform/Shared/RAMSlider.swift +++ b/Platform/Shared/RAMSlider.swift @@ -60,7 +60,7 @@ struct RAMSlider: View { } NumberTextField("", number: $systemMemory, prompt: "Size", onEditingChanged: validateMemorySize) .frame(width: 80) - Text("MB") + Text("MiB") } }.frame(height: 30) } diff --git a/Platform/Shared/SizeTextField.swift b/Platform/Shared/SizeTextField.swift index 3997dc6de..1c51c99a1 100644 --- a/Platform/Shared/SizeTextField.swift +++ b/Platform/Shared/SizeTextField.swift @@ -40,9 +40,9 @@ struct SizeTextField: View { Button(action: { isGiB.toggle() }, label: { Group { if isGiB { - Text("GB") + Text("GiB") } else { - Text("MB") + Text("MiB") } }.foregroundColor(.blue) }).buttonStyle(.plain) diff --git a/Platform/Shared/VMConfigSystemView.swift b/Platform/Shared/VMConfigSystemView.swift index a2c1ce884..606f97b76 100644 --- a/Platform/Shared/VMConfigSystemView.swift +++ b/Platform/Shared/VMConfigSystemView.swift @@ -55,7 +55,7 @@ struct VMConfigSystemView: View { HStack { NumberTextField("", number: $config.jitCacheSize, prompt: "Default", onEditingChanged: validateMemorySize) .multilineTextAlignment(.trailing) - Text("MB") + Text("MiB") } } } diff --git a/Platform/Shared/VMWizardDrivesView.swift b/Platform/Shared/VMWizardDrivesView.swift index 3daa362c4..4da457c2d 100644 --- a/Platform/Shared/VMWizardDrivesView.swift +++ b/Platform/Shared/VMWizardDrivesView.swift @@ -28,7 +28,7 @@ struct VMWizardDrivesView: View { NumberTextField("", number: $wizardState.storageSizeGib) .textFieldStyle(.roundedBorder) .frame(maxWidth: 50) - Text("GB") + Text("GiB") } } header: { Text("Size") From b225b14b1497b79f6dada0373d2c23140063b48b Mon Sep 17 00:00:00 2001 From: Thomas Nabord Date: Fri, 2 Aug 2024 17:11:43 +0200 Subject: [PATCH 52/79] Fix typo in "New Drive" --- Platform/fr.lproj/Localizable.strings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Platform/fr.lproj/Localizable.strings b/Platform/fr.lproj/Localizable.strings index 11f54201c..d10130b03 100644 --- a/Platform/fr.lproj/Localizable.strings +++ b/Platform/fr.lproj/Localizable.strings @@ -680,7 +680,7 @@ // VMSettingsAddDeviceMenuView.swift "Import Drive" = "Importer un lecteur"; -"New Drive" = "Nouveu lecteur"; +"New Drive" = "Nouveau lecteur"; // VMToolbarModifier.swift "Remove selected shortcut" = "Supprimer le raccourci sélectionné"; From 657bc7d4754898cc4da18640fc6eaaa3f2d94da0 Mon Sep 17 00:00:00 2001 From: Tiffany Fung <46277006+changanmoon@users.noreply.github.com> Date: Sun, 11 Aug 2024 13:44:26 +0800 Subject: [PATCH 53/79] Update Localizable.strings --- Platform/zh-HK.lproj/Localizable.strings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Platform/zh-HK.lproj/Localizable.strings b/Platform/zh-HK.lproj/Localizable.strings index 330e6fc9e..fadc24082 100644 --- a/Platform/zh-HK.lproj/Localizable.strings +++ b/Platform/zh-HK.lproj/Localizable.strings @@ -357,7 +357,7 @@ "Disposable Mode" = "即拋式模式"; /* No comment provided by engineer. */ -"Do not save VM screenshot to disk" = "不要將虛擬電腦螢幕截圖儲存至磁碟"; +"Do not save VM screenshot to disk" = "不要儲存虛擬電腦的螢幕截圖至磁碟"; /* No comment provided by engineer. */ "Do not show confirmation when closing a running VM" = "關閉正在執行的虛擬電腦時不要顯示確認"; From 54267945d9642b15b1edf140d1ba6ffa8d49725c Mon Sep 17 00:00:00 2001 From: Tiffany Fung <46277006+changanmoon@users.noreply.github.com> Date: Sun, 11 Aug 2024 13:45:23 +0800 Subject: [PATCH 54/79] Update Root.strings --- .../Settings.bundle/zh-HK.lproj/Root.strings | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/Platform/iOS/Settings.bundle/zh-HK.lproj/Root.strings b/Platform/iOS/Settings.bundle/zh-HK.lproj/Root.strings index 9272e39b9..169d83020 100644 --- a/Platform/iOS/Settings.bundle/zh-HK.lproj/Root.strings +++ b/Platform/iOS/Settings.bundle/zh-HK.lproj/Root.strings @@ -145,3 +145,52 @@ /* (No Comment) */ "Up" = "上"; +// Additional Strings (unable to be extracted by Xcode) + +/* (No Comment) */ +"ABOUT" = "關於"; + +/* (No Comment) */ +"Build" = "建立版號"; + +/* (No Comment) */ +"CURSOR - DRAG SPEED" = "指標 - 拖放速度"; + +/* (No Comment) */ +"CURSOR - SCROLL WHEEL" = "指標 - 捲動輪"; + +/* (No Comment) */ +"Default" = "預設值"; + +/* (No Comment) */ +"DEVICES" = "裝置"; + +/* (No Comment) */ +"Disable screen dimming when idle" = "閒置時停用變暗螢幕"; + +/* (No Comment) */ +"Do not save VM screenshot to disk" = "不要儲存虛擬電腦的螢幕截圖至磁碟"; + +/* (No Comment) */ +"FPS Limit" = "FPS 限制"; + +/* (No Comment) */ +"GRAPHICS" = "圖形"; + +/* (No Comment) */ +"IDLE" = "閒置"; + +/* (No Comment) */ +"Invert Scroll" = "反轉捲動"; + +/* (No Comment) */ +"License" = "許可"; + +/* (No Comment) */ +"Prefer device to external microphone" = "偏好外置咪高風裝置"; + +/* (No Comment) */ +"Renderer Backend" = "渲染器後端"; + +/* (No Comment) */ +"Version" = "版本"; From b616586e8deeb40c94a8fc8de7adbfe3dbb942d0 Mon Sep 17 00:00:00 2001 From: Tiffany Fung <46277006+changanmoon@users.noreply.github.com> Date: Sun, 11 Aug 2024 13:49:37 +0800 Subject: [PATCH 55/79] Update Root.strings --- .../zh-Hans.lproj/Root.strings | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/Platform/iOS/Settings.bundle/zh-Hans.lproj/Root.strings b/Platform/iOS/Settings.bundle/zh-Hans.lproj/Root.strings index 85c5f20c8..539afbd1b 100644 --- a/Platform/iOS/Settings.bundle/zh-Hans.lproj/Root.strings +++ b/Platform/iOS/Settings.bundle/zh-Hans.lproj/Root.strings @@ -145,3 +145,52 @@ /* (No Comment) */ "Up" = "上"; +// Additional Strings (unable to be extracted by Xcode) + +/* (No Comment) */ +"ABOUT" = "关于"; + +/* (No Comment) */ +"Build" = "构建"; + +/* (No Comment) */ +"CURSOR - DRAG SPEED" = "指针 - 拖放速度"; + +/* (No Comment) */ +"CURSOR - SCROLL WHEEL" = "指针 - 滚轮"; + +/* (No Comment) */ +"Default" = "默认"; + +/* (No Comment) */ +"DEVICES" = "设备"; + +/* (No Comment) */ +"Disable screen dimming when idle" = "闲置时禁用屏幕变暗"; + +/* (No Comment) */ +"Do not save VM screenshot to disk" = "不将虚拟机截图存储到磁盘"; + +/* (No Comment) */ +"FPS Limit" = "FPS 上限"; + +/* (No Comment) */ +"GRAPHICS" = "图形"; + +/* (No Comment) */ +"IDLE" = "闲置"; + +/* (No Comment) */ +"Invert Scroll" = "反转滚动"; + +/* (No Comment) */ +"License" = "许可"; + +/* (No Comment) */ +"Prefer device to external microphone" = "首选外置麦克风设备"; + +/* (No Comment) */ +"Renderer Backend" = "渲染器后端"; + +/* (No Comment) */ +"Version" = "版本"; From 9eedab014802e6eb8d63cc80117cfaeaae5cd708 Mon Sep 17 00:00:00 2001 From: Tiffany Fung <46277006+changanmoon@users.noreply.github.com> Date: Sun, 11 Aug 2024 13:50:21 +0800 Subject: [PATCH 56/79] Update Localizable.strings --- Platform/zh-Hans.lproj/Localizable.strings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Platform/zh-Hans.lproj/Localizable.strings b/Platform/zh-Hans.lproj/Localizable.strings index 81ccee3d4..0412b26ee 100644 --- a/Platform/zh-Hans.lproj/Localizable.strings +++ b/Platform/zh-Hans.lproj/Localizable.strings @@ -357,7 +357,7 @@ "Disposable Mode" = "一次性模式"; /* No comment provided by engineer. */ -"Do not save VM screenshot to disk" = "不将虚拟机的屏幕截图保存到磁盘"; +"Do not save VM screenshot to disk" = "不将虚拟机截图保存到磁盘"; /* No comment provided by engineer. */ "Do not show confirmation when closing a running VM" = "关闭正在运行的虚拟机时不显示确认"; From c2f3cf892b2a2c8efb35e5f07d683b1506566b2c Mon Sep 17 00:00:00 2001 From: Tiffany Fung <46277006+changanmoon@users.noreply.github.com> Date: Sun, 11 Aug 2024 13:51:17 +0800 Subject: [PATCH 57/79] Update Root.strings --- Platform/iOS/Settings.bundle/zh-Hans.lproj/Root.strings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Platform/iOS/Settings.bundle/zh-Hans.lproj/Root.strings b/Platform/iOS/Settings.bundle/zh-Hans.lproj/Root.strings index 539afbd1b..292198a39 100644 --- a/Platform/iOS/Settings.bundle/zh-Hans.lproj/Root.strings +++ b/Platform/iOS/Settings.bundle/zh-Hans.lproj/Root.strings @@ -169,7 +169,7 @@ "Disable screen dimming when idle" = "闲置时禁用屏幕变暗"; /* (No Comment) */ -"Do not save VM screenshot to disk" = "不将虚拟机截图存储到磁盘"; +"Do not save VM screenshot to disk" = "不将虚拟机截图保存到磁盘"; /* (No Comment) */ "FPS Limit" = "FPS 上限"; From 8779e3624ad43c5ad7090939489840de54de62c7 Mon Sep 17 00:00:00 2001 From: osy <50960678+osy@users.noreply.github.com> Date: Mon, 19 Aug 2024 18:32:28 -0500 Subject: [PATCH 58/79] remote: fix list not showing when a single VM is unavailable Fixes #6380 --- Platform/Shared/UTMUnavailableVMView.swift | 4 ++++ Platform/VMData.swift | 2 +- Remote/UTMRemoteServer.swift | 15 +++++++++------ 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/Platform/Shared/UTMUnavailableVMView.swift b/Platform/Shared/UTMUnavailableVMView.swift index 8e687345d..6252417e2 100644 --- a/Platform/Shared/UTMUnavailableVMView.swift +++ b/Platform/Shared/UTMUnavailableVMView.swift @@ -73,6 +73,8 @@ fileprivate struct WrappedVMDetailsView: View { } #if os(macOS) .frame(width: 230) + #else + .padding() #endif } } @@ -92,6 +94,8 @@ fileprivate struct UnsupportedVMDetailsView: View { } #if os(macOS) .frame(width: 230) + #else + .padding() #endif } } diff --git a/Platform/VMData.swift b/Platform/VMData.swift index f96826e22..fb70720b5 100644 --- a/Platform/VMData.swift +++ b/Platform/VMData.swift @@ -521,7 +521,7 @@ extension VMRemoteDataError: LocalizedError { case .notImplemented: return NSLocalizedString("This function is not implemented.", comment: "VMData") case .backendNotSupported: - return NSLocalizedString("This VM is configured for a backend that does not support remote clients.", comment: "VMData") + return NSLocalizedString("This VM is not available or is configured for a backend that does not support remote clients.", comment: "VMData") } } } diff --git a/Remote/UTMRemoteServer.swift b/Remote/UTMRemoteServer.swift index 7a50dede6..b9fea5f76 100644 --- a/Remote/UTMRemoteServer.swift +++ b/Remote/UTMRemoteServer.swift @@ -683,13 +683,16 @@ extension UTMRemoteServer { } @MainActor - private func findVM(withId id: UUID) throws -> VMData { + private func findVM(withId id: UUID, allowNotLoaded: Bool = false) throws -> VMData { let vm = data.virtualMachines.first(where: { $0.id == id }) - if let vm = vm, let _ = vm.wrapped { - return vm - } else { - throw UTMRemoteServer.ServerError.notFound(id) + if let vm = vm { + if let _ = vm.wrapped { + return vm + } else if allowNotLoaded { + return vm + } } + throw UTMRemoteServer.ServerError.notFound(id) } @MainActor @@ -735,7 +738,7 @@ extension UTMRemoteServer { private func _getVirtualMachineInformation(parameters: M.GetVirtualMachineInformation.Request) async throws -> M.GetVirtualMachineInformation.Reply { let informations = try await Task { @MainActor in try parameters.ids.map { id in - let vm = try findVM(withId: id) + let vm = try findVM(withId: id, allowNotLoaded: true) let mountedDrives = vm.registryEntry?.externalDrives.mapValues({ $0.path }) ?? [:] let isTakeoverAllowed = data.vmWindows[vm] is VMRemoteSessionState && (vm.state == .started || vm.state == .paused) return M.VirtualMachineInformation(id: vm.id, From 78304a5589ffa13a63a3a884ffaffb8af501c6f8 Mon Sep 17 00:00:00 2001 From: osy <50960678+osy@users.noreply.github.com> Date: Mon, 19 Aug 2024 18:32:45 -0500 Subject: [PATCH 59/79] scripts: update usage to reflect options in build_utm.sh --- scripts/build_utm.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/build_utm.sh b/scripts/build_utm.sh index 00fa6a5b3..155eaa149 100755 --- a/scripts/build_utm.sh +++ b/scripts/build_utm.sh @@ -7,7 +7,7 @@ command -v realpath >/dev/null 2>&1 || realpath() { BASEDIR="$(dirname "$(realpath $0)")" usage () { - echo "Usage: $(basename $0) [-t teamid] [-p platform] [-s scheme] [-a architecture] [-t targetversion] [-o output]" + echo "Usage: $(basename $0) [-t teamid] [-k SDK] [-s scheme] [-a architecture] [-o output]" echo "" echo " -t teamid Team Identifier for app groups. Optional for iOS. Required for macOS." echo " -k sdk Target SDK. Default iphoneos. [iphoneos|iphonesimulator|xros|xrsimulator|macosx]" From b7efffcb925e4d759ccc4db938d19f613ff01bd1 Mon Sep 17 00:00:00 2001 From: osy <50960678+osy@users.noreply.github.com> Date: Mon, 19 Aug 2024 19:42:59 -0500 Subject: [PATCH 60/79] remote: immediately start server when autostart is enabled Resolves #6429 --- Platform/Shared/ContentView.swift | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Platform/Shared/ContentView.swift b/Platform/Shared/ContentView.swift index eb19f3362..2199f4b8c 100644 --- a/Platform/Shared/ContentView.swift +++ b/Platform/Shared/ContentView.swift @@ -121,6 +121,17 @@ struct ContentView: View { #endif #endif } + #if WITH_SERVER + .onChange(of: isServerAutostart) { newValue in + if newValue { + Task { + if isServerAutostart && !data.remoteServer.state.isServerActive { + await data.remoteServer.start() + } + } + } + } + #endif } private func handleURL(url: URL) { From 42ddc8c04f88d76119672647698217089d9f3215 Mon Sep 17 00:00:00 2001 From: osy <50960678+osy@users.noreply.github.com> Date: Mon, 19 Aug 2024 21:10:48 -0500 Subject: [PATCH 61/79] settings: fixed excess stop scoped access Fixes #6398 --- Platform/UTMData.swift | 10 +++++++--- Services/UTMQemuVirtualMachine.swift | 6 ++++-- Services/UTMSpiceVirtualMachine.swift | 6 ++++-- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/Platform/UTMData.swift b/Platform/UTMData.swift index 5af4ccca2..b1364bcb8 100644 --- a/Platform/UTMData.swift +++ b/Platform/UTMData.swift @@ -619,9 +619,13 @@ struct AlertMessage: Identifiable { /// - Parameter asShortcut: Create a shortcut rather than a copy func importUTM(from url: URL, asShortcut: Bool = true) async throws { guard url.isFileURL else { return } - _ = url.startAccessingSecurityScopedResource() - defer { url.stopAccessingSecurityScopedResource() } - + let isScopedAccess = url.startAccessingSecurityScopedResource() + defer { + if isScopedAccess { + url.stopAccessingSecurityScopedResource() + } + } + logger.info("importing: \(url)") // attempt to turn temp URL to presistent bookmark early otherwise, // when stopAccessingSecurityScopedResource() is called, we lose access diff --git a/Services/UTMQemuVirtualMachine.swift b/Services/UTMQemuVirtualMachine.swift index 7d8c824e1..608a3a70c 100644 --- a/Services/UTMQemuVirtualMachine.swift +++ b/Services/UTMQemuVirtualMachine.swift @@ -784,9 +784,11 @@ extension UTMQemuVirtualMachine { } private func changeMedium(_ drive: UTMQemuConfigurationDrive, to url: URL, isAccessOnly: Bool) async throws { - _ = url.startAccessingSecurityScopedResource() + let isScopedAccess = url.startAccessingSecurityScopedResource() defer { - url.stopAccessingSecurityScopedResource() + if isScopedAccess { + url.stopAccessingSecurityScopedResource() + } } let tempBookmark = try url.bookmarkData() try await eject(drive, isForced: true) diff --git a/Services/UTMSpiceVirtualMachine.swift b/Services/UTMSpiceVirtualMachine.swift index 7cd50a5b5..0be031bc3 100644 --- a/Services/UTMSpiceVirtualMachine.swift +++ b/Services/UTMSpiceVirtualMachine.swift @@ -105,9 +105,11 @@ extension UTMSpiceVirtualMachine { func changeSharedDirectory(to url: URL) async throws { await clearSharedDirectory() - _ = url.startAccessingSecurityScopedResource() + let isScopedAccess = url.startAccessingSecurityScopedResource() defer { - url.stopAccessingSecurityScopedResource() + if isScopedAccess { + url.stopAccessingSecurityScopedResource() + } } let file = try await UTMRegistryEntry.File(url: url, isReadOnly: config.sharing.isDirectoryShareReadOnly) await registryEntry.setSingleSharedDirectory(file) From a80e089415bc5429ebd1912ec3a0bc42cdeff700 Mon Sep 17 00:00:00 2001 From: osy <50960678+osy@users.noreply.github.com> Date: Tue, 20 Aug 2024 14:57:19 -0500 Subject: [PATCH 62/79] package: update DEB control file to support rootless --- scripts/package.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/package.sh b/scripts/package.sh index b08062675..6465f9bd0 100755 --- a/scripts/package.sh +++ b/scripts/package.sh @@ -142,7 +142,7 @@ cat >"$DEB_TMP/DEBIAN/control" <=14.0), firmware-sbin, net.angelxwind.appsyncunified Installed-Size: ${SIZE_KIB} Maintainer: osy From 7bd45b354c08e97a6f68c1298a6ae869b64ab23a Mon Sep 17 00:00:00 2001 From: osy <50960678+osy@users.noreply.github.com> Date: Wed, 21 Aug 2024 17:45:28 -0500 Subject: [PATCH 63/79] docs: updated development docs Resolves #6469 --- Documentation/MacDevelopment.md | 6 ++++-- Documentation/iOSDevelopment.md | 8 ++++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/Documentation/MacDevelopment.md b/Documentation/MacDevelopment.md index 9d923163d..801ad1b3e 100644 --- a/Documentation/MacDevelopment.md +++ b/Documentation/MacDevelopment.md @@ -16,7 +16,9 @@ git submodule update --init --recursive ## Dependencies -The easy way is to get the prebuilt dependences from [GitHub Actions][1]. Pick the latest release and download all of the `Sysroot-macos-*` artifacts. You need to be logged in to GitHub to download artifacts. If you only intend to run locally, it is alright to just download the sysroot for your architecture. +The easy way is to get the prebuilt dependences from [GitHub Actions][1]. Pick the latest release and download all of the `Sysroot-macos-*` artifacts. You need to be logged in to GitHub to download artifacts. If you only intend to run locally, it is alright to just download the sysroot for your architecture. After downloading the prebuilt artifacts of your choice, extract them to the root directory where you cloned the repository. + +To build UTM, make sure you have the latest version of Xcode installed. ### Building Dependencies (Advanced) @@ -58,7 +60,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 +./scripts/build_utm.sh -t TEAMID -k macosx -s macos -a ARCH -o /path/to/output/directory ``` `ARCH` can be `x86_64` or `arm64` or `"arm64 x86_64"` (quotes are required) for a universal binary. The built artifact is an unsigned `.xcarchive` which you can use with the package tool (see below). diff --git a/Documentation/iOSDevelopment.md b/Documentation/iOSDevelopment.md index 8a22b9912..c5dc51889 100644 --- a/Documentation/iOSDevelopment.md +++ b/Documentation/iOSDevelopment.md @@ -22,6 +22,10 @@ The easy way is to get the prebuilt dependences from [GitHub Actions][1]. Pick t | Simulator | `ios_simulator-x86_64` | `ios_simulator-arm64` | | Simulator SE | `ios_simulator-tci-x86_64` | `ios_simulator-tci-arm64` | +After downloading the prebuilt artifacts of your choice, extract them to the root directory where you cloned the repository. + +To build UTM, make sure you have the latest version of Xcode installed. + ### Building Dependencies (Advanced) If you want to build the dependencies yourself, it is highly recommended that you start with a fresh macOS VM. This is because some of the dependencies attempt to use `/usr/local/lib` even though the architecture does not match. Certain installed packages like `libusb`, `gawk`, and `cmake` will break the build. @@ -39,10 +43,10 @@ If you want to build the dependencies yourself, it is highly recommended that yo ### Command Line -You can build UTM with the script: +You can build UTM for iOS with the script (run `./scripts/build_utm.sh` for all options): ``` -./scripts/build_utm.sh -p ios -a arm64 -o /path/to/output/directory +./scripts/build_utm.sh -k iphoneos -s iOS -a arm64 -o /path/to/output/directory ``` The built artifact is an unsigned `.xcarchive` which you can use with the package tool (see below). Replace `ios` with `ios-tci` to build UTM SE. From 5b94a765b6a2aba43ba7275646e7885917a723c3 Mon Sep 17 00:00:00 2001 From: osy <50960678+osy@users.noreply.github.com> Date: Wed, 21 Aug 2024 19:50:06 -0500 Subject: [PATCH 64/79] display(terminal): fix layout issue with top and bottom Fixes #6482 --- .../VMDisplayTerminalViewController.swift | 27 ++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/Platform/iOS/Display/VMDisplayTerminalViewController.swift b/Platform/iOS/Display/VMDisplayTerminalViewController.swift index b2195884b..4e0442b38 100644 --- a/Platform/iOS/Display/VMDisplayTerminalViewController.swift +++ b/Platform/iOS/Display/VMDisplayTerminalViewController.swift @@ -85,14 +85,26 @@ extension VMDisplayTerminalViewController { var useAutoLayout: Bool { get { true } } - + + // This prevents curved edge from cutting off the content + var additionalTopPadding: CGFloat { + if UIDevice.current.userInterfaceIdiom == .pad { + let scenes = UIApplication.shared.connectedScenes + let windowScene = scenes.first as? UIWindowScene + guard let window = windowScene?.windows.first else { return 0 } + return window.safeAreaInsets.bottom + } else { + return 0 + } + } + func makeFrame (keyboardDelta: CGFloat, _ fn: String = #function, _ ln: Int = #line) -> CGRect { if useAutoLayout { return CGRect.zero } else { return CGRect (x: view.safeAreaInsets.left, - y: view.safeAreaInsets.top, + y: view.safeAreaInsets.top + additionalTopPadding, width: view.frame.width - view.safeAreaInsets.left - view.safeAreaInsets.right, height: view.frame.height - view.safeAreaInsets.top - keyboardDelta) } @@ -101,13 +113,16 @@ extension VMDisplayTerminalViewController { func setupKeyboardMonitor () { if #available(iOS 15.0, *), useAutoLayout { + #if os(visionOS) + let inputAccessoryHeight = 0 + #else + let inputAccessoryHeight = terminalView.inputAccessoryView?.frame.height ?? 0 + #endif terminalView.translatesAutoresizingMaskIntoConstraints = false - terminalView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true + terminalView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: additionalTopPadding).isActive = true terminalView.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor).isActive = true terminalView.rightAnchor.constraint(equalTo: view.safeAreaLayoutGuide.rightAnchor).isActive = true - terminalView.bottomAnchor.constraint(lessThanOrEqualTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true - terminalView.keyboardLayoutGuide.topAnchor.constraint(equalTo: terminalView.bottomAnchor).isActive = true - terminalView.keyboardLayoutGuide.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true + terminalView.bottomAnchor.constraint(equalTo: view.keyboardLayoutGuide.topAnchor, constant: -inputAccessoryHeight).isActive = true } else { NotificationCenter.default.addObserver( self, From 65f381e3a5be7a8dcd06e756a3f87665127dacc5 Mon Sep 17 00:00:00 2001 From: osy <50960678+osy@users.noreply.github.com> Date: Wed, 21 Aug 2024 21:37:52 -0500 Subject: [PATCH 65/79] settings(iOS): fixed importing drive Fixes #6485 --- Platform/Shared/VMSettingsAddDeviceMenuView.swift | 4 ++-- Platform/es-419.lproj/Localizable.strings | 4 ++-- Platform/fi.lproj/Localizable.strings | 4 ++-- Platform/fr.lproj/Localizable.strings | 4 ++-- Platform/iOS/VMDrivesSettingsView.swift | 12 +++++++++++- Platform/it.lproj/Localizable.strings | 4 ++-- Platform/ja.lproj/Localizable.strings | 4 ++-- Platform/pl.lproj/Localizable.strings | 6 +++--- Platform/ru.lproj/Localizable.strings | 4 ++-- Platform/zh-HK.lproj/Localizable.strings | 4 ++-- Platform/zh-Hans.lproj/Localizable.strings | 4 ++-- Platform/zh-Hant.lproj/Localizable.strings | 4 ++-- 12 files changed, 34 insertions(+), 24 deletions(-) diff --git a/Platform/Shared/VMSettingsAddDeviceMenuView.swift b/Platform/Shared/VMSettingsAddDeviceMenuView.swift index e3d8e0126..636266fa0 100644 --- a/Platform/Shared/VMSettingsAddDeviceMenuView.swift +++ b/Platform/Shared/VMSettingsAddDeviceMenuView.swift @@ -74,12 +74,12 @@ struct VMSettingsAddDeviceMenuView: View { Button { isImportDriveShown.toggle() } label: { - Label("Import Drive", systemImage: "externaldrive") + Label("Import Drive…", systemImage: "externaldrive") } Button { isCreateDriveShown.toggle() } label: { - Label("New Drive", systemImage: "externaldrive.badge.plus") + Label("New Drive…", systemImage: "externaldrive.badge.plus") } #endif } label: { diff --git a/Platform/es-419.lproj/Localizable.strings b/Platform/es-419.lproj/Localizable.strings index 05f1b542b..d87c86518 100644 --- a/Platform/es-419.lproj/Localizable.strings +++ b/Platform/es-419.lproj/Localizable.strings @@ -797,7 +797,7 @@ "Import…" = "Importar..."; /* No comment provided by engineer. */ -"Import Drive" = "Importar unidad"; +"Import Drive…" = "Importar unidad..."; /* No comment provided by engineer. */ "Import VHDX Image" = "Importar una imagen VHDX"; @@ -1055,7 +1055,7 @@ "New…" = "Nuevo..."; /* No comment provided by engineer. */ -"New Drive" = "Nueva unidad"; +"New Drive…" = "Nueva unidad..."; /* No comment provided by engineer. */ "New from template…" = "Nuevo desde plantilla..."; diff --git a/Platform/fi.lproj/Localizable.strings b/Platform/fi.lproj/Localizable.strings index 58d87eaef..b1ca10777 100644 --- a/Platform/fi.lproj/Localizable.strings +++ b/Platform/fi.lproj/Localizable.strings @@ -521,7 +521,7 @@ "Import…" = "Tuo..."; /* No comment provided by engineer. */ -"Import Drive" = "Tuo asema"; +"Import Drive…" = "Tuo asema..."; /* No comment provided by engineer. */ "Import VHDX Image" = "Importer une image VHDX"; @@ -680,7 +680,7 @@ "New" = "Uusi"; /* No comment provided by engineer. */ -"New Drive" = "Uusi asema"; +"New Drive…" = "Uusi asema…"; /* VMConfigPortForwardingViewController */ "New port forward" = "Uusi portitus"; diff --git a/Platform/fr.lproj/Localizable.strings b/Platform/fr.lproj/Localizable.strings index 11f54201c..08d70fa78 100644 --- a/Platform/fr.lproj/Localizable.strings +++ b/Platform/fr.lproj/Localizable.strings @@ -679,8 +679,8 @@ "Removable" = "Amovible"; // VMSettingsAddDeviceMenuView.swift -"Import Drive" = "Importer un lecteur"; -"New Drive" = "Nouveu lecteur"; +"Import Drive…" = "Importer un lecteur…"; +"New Drive…" = "Nouveu lecteur…"; // VMToolbarModifier.swift "Remove selected shortcut" = "Supprimer le raccourci sélectionné"; diff --git a/Platform/iOS/VMDrivesSettingsView.swift b/Platform/iOS/VMDrivesSettingsView.swift index c8cab8012..3e703a89f 100644 --- a/Platform/iOS/VMDrivesSettingsView.swift +++ b/Platform/iOS/VMDrivesSettingsView.swift @@ -35,6 +35,16 @@ struct VMDrivesSettingsView: View { attemptDelete = offsets } .onMove(perform: moveDrives) + Button { + isImportDriveShown.toggle() + } label: { + Text("Import Drive…") + } + Button { + isCreateDriveShown.toggle() + } label: { + Text("New Drive…") + } .nonbrokenSheet(isPresented: $isCreateDriveShown) { CreateDrive(newDrive: UTMQemuConfigurationDrive(forArchitecture: config.system.architecture, target: config.system.target), onDismiss: newDrive) } @@ -71,7 +81,7 @@ struct VMDrivesSettingsView: View { switch result { case .success(let url): await MainActor.run { - var drive = UTMQemuConfigurationDrive(forArchitecture: config.system.architecture, target: config.system.target, isExternal: true) + var drive = UTMQemuConfigurationDrive(forArchitecture: config.system.architecture, target: config.system.target, isExternal: false) drive.imageURL = url config.drives.append(drive) } diff --git a/Platform/it.lproj/Localizable.strings b/Platform/it.lproj/Localizable.strings index 3dff1a3f8..f1b3f37cd 100644 --- a/Platform/it.lproj/Localizable.strings +++ b/Platform/it.lproj/Localizable.strings @@ -825,7 +825,7 @@ "Import…" = "Importa..."; /* No comment provided by engineer. */ -"Import Drive" = "Importa Disco"; +"Import Drive…" = "Importa Disco..."; /* No comment provided by engineer. */ "Import VHDX Image" = "Importa un'Immagine VHDX"; @@ -1166,7 +1166,7 @@ "New…" = "Crea..."; /* No comment provided by engineer. */ -"New Drive" = "Nuovo Disco"; +"New Drive…" = "Nuovo Disco..."; /* No comment provided by engineer. */ "New from template…" = "Nuovo da template..."; diff --git a/Platform/ja.lproj/Localizable.strings b/Platform/ja.lproj/Localizable.strings index f9e02579b..80ec497c4 100644 --- a/Platform/ja.lproj/Localizable.strings +++ b/Platform/ja.lproj/Localizable.strings @@ -863,8 +863,8 @@ "%@ %@" = "%1$@ %2$@"; // VMSettingsAddDeviceMenuView.swift -"Import Drive" = "ドライブを読み込む"; -"New Drive" = "新規ドライブ"; +"Import Drive…" = "ドライブを読み込む…"; +"New Drive…" = "新規ドライブ…"; // VMToolbarModifier.swift "Remove selected shortcut" = "選択したショートカットを削除します"; diff --git a/Platform/pl.lproj/Localizable.strings b/Platform/pl.lproj/Localizable.strings index 34965726b..aa9c7c271 100644 --- a/Platform/pl.lproj/Localizable.strings +++ b/Platform/pl.lproj/Localizable.strings @@ -787,8 +787,8 @@ "%@ %@" = "%@ %@"; /* VMSettingsAddDeviceMenuView.swift */ -"Import Drive" = "Importuj dysk"; -"New Drive" = "Nowy dysk"; +"Import Drive…" = "Importuj dysk..."; +"New Drive…" = "Nowy dysk..."; /* VMToolbarModifier.swift */ "Remove selected shortcut" = "Uruchom wybrany skrót"; @@ -1292,4 +1292,4 @@ "Floppy Drive" = "Napęd dyskietek"; "VirtIO Drive" = "Dysk VirtIO"; "NVMe Drive" = "Dysk NVMe"; -"USB Drive" = "Dysk USB"; \ No newline at end of file +"USB Drive" = "Dysk USB"; diff --git a/Platform/ru.lproj/Localizable.strings b/Platform/ru.lproj/Localizable.strings index f6f8eff9e..81491aab3 100644 --- a/Platform/ru.lproj/Localizable.strings +++ b/Platform/ru.lproj/Localizable.strings @@ -809,7 +809,7 @@ "Import…" = "Импорт…"; /* No comment provided by engineer. */ -"Import Drive" = "Импортировать диск"; +"Import Drive…" = "Импортировать диск…"; /* No comment provided by engineer. */ "Import VHDX Image" = "Импортировать образ VHDX"; @@ -1070,7 +1070,7 @@ "New…" = "Новый…"; /* No comment provided by engineer. */ -"New Drive" = "Новый диск"; +"New Drive…" = "Новый диск…"; /* No comment provided by engineer. */ "New from template…" = "Новая из шаблона…"; diff --git a/Platform/zh-HK.lproj/Localizable.strings b/Platform/zh-HK.lproj/Localizable.strings index 330e6fc9e..1a871974a 100644 --- a/Platform/zh-HK.lproj/Localizable.strings +++ b/Platform/zh-HK.lproj/Localizable.strings @@ -1861,7 +1861,7 @@ "Image Type" = "映像檔種類"; /* No comment provided by engineer. */ -"Import Drive" = "輸入磁碟機"; +"Import Drive…" = "輸入磁碟機⋯"; /* No comment provided by engineer. */ "Import VHDX Image" = "輸入 VHDX 映像檔"; @@ -1945,7 +1945,7 @@ "Network Mode" = "網絡模式"; /* No comment provided by engineer. */ -"New Drive" = "新增磁碟機"; +"New Drive…" = "新增磁碟機⋯"; /* No comment provided by engineer. */ "New from template…" = "由此範本新增⋯"; diff --git a/Platform/zh-Hans.lproj/Localizable.strings b/Platform/zh-Hans.lproj/Localizable.strings index 81ccee3d4..5e4f64a29 100644 --- a/Platform/zh-Hans.lproj/Localizable.strings +++ b/Platform/zh-Hans.lproj/Localizable.strings @@ -1861,7 +1861,7 @@ "Image Type" = "映像类型"; /* No comment provided by engineer. */ -"Import Drive" = "导入驱动器"; +"Import Drive…" = "导入驱动器…"; /* No comment provided by engineer. */ "Import VHDX Image" = "导入 VHDX 映像"; @@ -1945,7 +1945,7 @@ "Network Mode" = "网络模式"; /* No comment provided by engineer. */ -"New Drive" = "新建驱动器"; +"New Drive…" = "新建驱动器…"; /* No comment provided by engineer. */ "New from template…" = "从此模板新建…"; diff --git a/Platform/zh-Hant.lproj/Localizable.strings b/Platform/zh-Hant.lproj/Localizable.strings index 8070b72bd..9ae32eb9e 100644 --- a/Platform/zh-Hant.lproj/Localizable.strings +++ b/Platform/zh-Hant.lproj/Localizable.strings @@ -858,7 +858,7 @@ "Image Type" = "映像檔類型"; /* No comment provided by engineer. */ -"Import Drive" = "匯入磁碟機"; +"Import Drive…" = "匯入磁碟機⋯"; /* No comment provided by engineer. */ "Import IPSW" = "匯入 IPSW"; @@ -1080,7 +1080,7 @@ "New" = "新增"; /* No comment provided by engineer. */ -"New Drive" = "新增磁碟機"; +"New Drive…" = "新增磁碟機…"; /* No comment provided by engineer. */ "New from template…" = "從樣板建立…"; From 14c24943d69303b50627973dd7c414439bf46329 Mon Sep 17 00:00:00 2001 From: osy <50960678+osy@users.noreply.github.com> Date: Thu, 22 Aug 2024 09:53:30 -0500 Subject: [PATCH 66/79] vm: update last modified timestamp on start Resolves #6474 --- Services/UTMAppleVirtualMachine.swift | 3 +++ Services/UTMQemuVirtualMachine.swift | 7 +++++++ Services/UTMVirtualMachine.swift | 8 ++++++++ 3 files changed, 18 insertions(+) diff --git a/Services/UTMAppleVirtualMachine.swift b/Services/UTMAppleVirtualMachine.swift index f42008659..71ba4b53a 100644 --- a/Services/UTMAppleVirtualMachine.swift +++ b/Services/UTMAppleVirtualMachine.swift @@ -170,6 +170,7 @@ final class UTMAppleVirtualMachine: UTMVirtualMachine { } } } + try? updateLastModified() } func start(options: UTMVirtualMachineStartOptions = []) async throws { @@ -358,6 +359,7 @@ final class UTMAppleVirtualMachine: UTMVirtualMachine { } } } + try? updateLastModified() } #endif @@ -393,6 +395,7 @@ final class UTMAppleVirtualMachine: UTMVirtualMachine { } await registryEntry.setIsSuspended(false) try FileManager.default.removeItem(at: vmSavedStateURL) + try? updateLastModified() } #if arch(arm64) diff --git a/Services/UTMQemuVirtualMachine.swift b/Services/UTMQemuVirtualMachine.swift index 608a3a70c..7dafe47b4 100644 --- a/Services/UTMQemuVirtualMachine.swift +++ b/Services/UTMQemuVirtualMachine.swift @@ -437,6 +437,11 @@ extension UTMQemuVirtualMachine { self.spicePort = spicePort } #endif + + // update timestamp + if !isRunningAsDisposible { + try? updateLastModified() + } } func start(options: UTMVirtualMachineStartOptions = []) async throws { @@ -549,6 +554,7 @@ extension UTMQemuVirtualMachine { if result.localizedCaseInsensitiveContains("Error") { throw UTMQemuVirtualMachineError.qemuError(result) } + try? updateLastModified() } func saveSnapshot(name: String? = nil) async throws { @@ -580,6 +586,7 @@ extension UTMQemuVirtualMachine { if result.localizedCaseInsensitiveContains("Error") { throw UTMQemuVirtualMachineError.qemuError(result) } + try? updateLastModified() } } diff --git a/Services/UTMVirtualMachine.swift b/Services/UTMVirtualMachine.swift index b7eec04e1..14ae742b2 100644 --- a/Services/UTMVirtualMachine.swift +++ b/Services/UTMVirtualMachine.swift @@ -401,6 +401,14 @@ extension UTMVirtualMachine { try reload(from: newPath) try await updateRegistryBasics() // update bookmark } + // update last modified date + try? updateLastModified() + } + + /// Set the package's last modified time + /// - Parameter date: Last modified date + nonisolated func updateLastModified(to date: Date = Date()) throws { + try FileManager.default.setAttributes([.modificationDate: date], ofItemAtPath: pathUrl.path) } } From c1978640548d9cab2ee11164d96142669c44d237 Mon Sep 17 00:00:00 2001 From: osy <50960678+osy@users.noreply.github.com> Date: Fri, 23 Aug 2024 00:41:50 -0500 Subject: [PATCH 67/79] qemu: disable locking on read-only images When an ISO is mounted by macOS or if it is stored on an network drive that does not support locking, "Failed to lock byte 100: Operation not supported" shows up when you try to mount it. We disable locking in such cases and implement a new QAPI interface for disabling locking with newly mounted media as well. Fixes #6564 --- .../UTMQemuConfiguration+Arguments.swift | 8 +- Services/UTMQemuVirtualMachine.swift | 2 +- .../xcshareddata/swiftpm/Package.resolved | 2 +- patches/qemu-7.2.0-utm.patch | 129 ++++++++++++++++++ 4 files changed, 136 insertions(+), 5 deletions(-) diff --git a/Configuration/UTMQemuConfiguration+Arguments.swift b/Configuration/UTMQemuConfiguration+Arguments.swift index 0857e3dff..43580f6d8 100644 --- a/Configuration/UTMQemuConfiguration+Arguments.swift +++ b/Configuration/UTMQemuConfiguration+Arguments.swift @@ -472,8 +472,9 @@ import Virtualization // for getting network interfaces "if=pflash" "format=raw" "unit=0" - "file=" + "file.filename=" bios + "file.locking=off" "readonly=on" f() f("-drive") @@ -733,13 +734,14 @@ import Virtualization // for getting network interfaces } "id=drive\(drive.id)" if let imageURL = drive.imageURL { - "file=" + "file.filename=" imageURL } else if !isCd { - "file=" + "file.filename=" placeholderUrl } if drive.isReadOnly || isCd { + "file.locking=off" "readonly=on" } else { "discard=unmap" diff --git a/Services/UTMQemuVirtualMachine.swift b/Services/UTMQemuVirtualMachine.swift index 7dafe47b4..2994fea08 100644 --- a/Services/UTMQemuVirtualMachine.swift +++ b/Services/UTMQemuVirtualMachine.swift @@ -812,7 +812,7 @@ extension UTMQemuVirtualMachine { } await registryEntry.updateExternalDriveRemoteBookmark(bookmark, forId: drive.id) if let qemu = await monitor, qemu.isConnected && !isAccessOnly { - try qemu.changeMedium(forDrive: "drive\(drive.id)", path: path) + try qemu.changeMedium(forDrive: "drive\(drive.id)", path: path, locking: false) } } diff --git a/UTM.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/UTM.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 932af6201..d5f72bb46 100644 --- a/UTM.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/UTM.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -52,7 +52,7 @@ "location" : "https://github.com/utmapp/QEMUKit.git", "state" : { "branch" : "main", - "revision" : "1019ed76278a1d5cbe871ff5e51c62b5d8c9a032" + "revision" : "c0c978d5566928b2f2b93005c2aa720bba01157a" } }, { diff --git a/patches/qemu-7.2.0-utm.patch b/patches/qemu-7.2.0-utm.patch index 66fd5f2bb..452529827 100644 --- a/patches/qemu-7.2.0-utm.patch +++ b/patches/qemu-7.2.0-utm.patch @@ -7054,3 +7054,132 @@ index 0df9dd8fd5..0e9a3e469d 100644 -- 2.41.0 +From 7129cfcc829baeb05ba2c7267ef7cc8e19ed9cef Mon Sep 17 00:00:00 2001 +From: osy +Date: Thu, 22 Aug 2024 16:42:50 -0500 +Subject: [PATCH] block: support locking on change medium + +New optional argument for 'blockdev-change-medium' QAPI command to allow +the caller to specify if they wish to enable file locking. +--- + block/qapi-sysemu.c | 22 ++++++++++++++++++++++ + monitor/hmp-cmds.c | 1 + + qapi/block.json | 23 ++++++++++++++++++++++- + ui/cocoa/app_controller.m | 1 + + 4 files changed, 46 insertions(+), 1 deletion(-) + +diff --git a/block/qapi-sysemu.c b/block/qapi-sysemu.c +index 680c7ee342..888e13e539 100644 +--- a/block/qapi-sysemu.c ++++ b/block/qapi-sysemu.c +@@ -321,6 +321,8 @@ void qmp_blockdev_change_medium(bool has_device, const char *device, + bool has_force, bool force, + bool has_read_only, + BlockdevChangeReadOnlyMode read_only, ++ bool has_file_locking_mode, ++ BlockdevChangeFileLockingMode file_locking_mode, + Error **errp) + { + BlockBackend *blk; +@@ -374,6 +376,26 @@ void qmp_blockdev_change_medium(bool has_device, const char *device, + qdict_put_str(options, "driver", format); + } + ++ if (!has_file_locking_mode) { ++ file_locking_mode = BLOCKDEV_CHANGE_FILE_LOCKING_MODE_AUTO; ++ } ++ ++ switch (file_locking_mode) { ++ case BLOCKDEV_CHANGE_FILE_LOCKING_MODE_AUTO: ++ break; ++ ++ case BLOCKDEV_CHANGE_FILE_LOCKING_MODE_OFF: ++ qdict_put_str(options, "file.locking", "off"); ++ break; ++ ++ case BLOCKDEV_CHANGE_FILE_LOCKING_MODE_ON: ++ qdict_put_str(options, "file.locking", "on"); ++ break; ++ ++ default: ++ abort(); ++ } ++ + medium_bs = bdrv_open(filename, NULL, options, bdrv_flags, errp); + if (!medium_bs) { + goto fail; +diff --git a/monitor/hmp-cmds.c b/monitor/hmp-cmds.c +index 01b789a79e..9e42634c17 100644 +--- a/monitor/hmp-cmds.c ++++ b/monitor/hmp-cmds.c +@@ -1499,6 +1499,7 @@ void hmp_change(Monitor *mon, const QDict *qdict) + qmp_blockdev_change_medium(true, device, false, NULL, target, + !!arg, arg, true, force, + !!read_only, read_only_mode, ++ false, 0, + &err); + } + +diff --git a/qapi/block.json b/qapi/block.json +index 5fe068f903..0034ebe941 100644 +--- a/qapi/block.json ++++ b/qapi/block.json +@@ -303,6 +303,23 @@ + { 'enum': 'BlockdevChangeReadOnlyMode', + 'data': ['retain', 'read-only', 'read-write'] } + ++## ++# @BlockdevChangeFileLockingMode: ++# ++# Specifies the new locking mode of a file image passed to the ++# @blockdev-change-medium command. ++# ++# @auto: Use locking if API is available ++# ++# @off: Disable file image locking ++# ++# @on: Enable file image locking ++# ++# Since: 9.2 ++## ++{ 'enum': 'BlockdevChangeFileLockingMode', ++ 'data': ['auto', 'off', 'on'] } ++ + ## + # @blockdev-change-medium: + # +@@ -324,6 +341,9 @@ + # @read-only-mode: change the read-only mode of the device; defaults + # to 'retain' + # ++# @file-locking-mode: change the locking mode of the file image; defaults ++# to 'auto' ++# + # @force: if false (the default), an eject request through blockdev-open-tray + # will be sent to the guest if it has locked the tray (and the tray + # will not be opened immediately); if true, the tray will be opened +@@ -371,7 +391,8 @@ + 'filename': 'str', + '*format': 'str', + '*force': 'bool', +- '*read-only-mode': 'BlockdevChangeReadOnlyMode' } } ++ '*read-only-mode': 'BlockdevChangeReadOnlyMode', ++ '*file-locking-mode': 'BlockdevChangeFileLockingMode' } } + + ## + # @DEVICE_TRAY_MOVED: +diff --git a/ui/cocoa/app_controller.m b/ui/cocoa/app_controller.m +index 496036162d..0a062950cf 100644 +--- a/ui/cocoa/app_controller.m ++++ b/ui/cocoa/app_controller.m +@@ -373,6 +373,7 @@ - (void)changeDeviceMedia:(id)sender + "raw", + true, false, + false, 0, ++ false, 0, + &err); + qemu_mutex_unlock_iothread(); + handleAnyDeviceErrors(err); +-- +2.41.0 + From 6bb5b20670012802b35b50f972b61b08e4965635 Mon Sep 17 00:00:00 2001 From: osy <50960678+osy@users.noreply.github.com> Date: Fri, 23 Aug 2024 00:51:32 -0500 Subject: [PATCH 68/79] remote(server): fix crash when invalid port is specified Fixes #6584 --- Platform/macOS/SettingsView.swift | 5 ++++- Remote/UTMRemoteServer.swift | 4 ++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Platform/macOS/SettingsView.swift b/Platform/macOS/SettingsView.swift index f97779d42..9225a95ea 100644 --- a/Platform/macOS/SettingsView.swift +++ b/Platform/macOS/SettingsView.swift @@ -223,9 +223,12 @@ struct ServerSettingsView: View { .multilineTextAlignment(.trailing) .help("Specify a port number to listen on. This is required if external clients are permitted.") .onChange(of: serverPort) { newValue in - if serverPort == 0 { + if newValue == 0 { isServerExternal = false } + if newValue < 0 || newValue >= UInt16.max { + serverPort = defaultPort + } } } Section(header: Text("Authentication")) { diff --git a/Remote/UTMRemoteServer.swift b/Remote/UTMRemoteServer.swift index b9fea5f76..0dd017167 100644 --- a/Remote/UTMRemoteServer.swift +++ b/Remote/UTMRemoteServer.swift @@ -131,7 +131,7 @@ actor UTMRemoteServer { registerNotifications() listener = Task { await withErrorNotification { - if isServerExternal && serverPort > 0 { + if isServerExternal && serverPort > 0 && serverPort <= UInt16.max { natPort = Port.TCP(internalPort: UInt16(serverPort)) natPort!.mappingChangedHandler = { port in Task { @@ -146,7 +146,7 @@ actor UTMRemoteServer { } } } - let port = serverPort > 0 ? NWEndpoint.Port(integerLiteral: UInt16(serverPort)) : .any + let port = serverPort > 0 && serverPort <= UInt16.max ? NWEndpoint.Port(integerLiteral: UInt16(serverPort)) : .any for try await connection in Connection.advertise(on: port, forServiceType: service, txtRecord: metadata, connectionQueue: connectionQueue, identity: keyManager.identity) { let connection = try? await Connection(connection: connection, connectionQueue: connectionQueue) { connection, error in Task { From 6feeff52352b2cf2e3d34b25cb359694820e7976 Mon Sep 17 00:00:00 2001 From: osy <50960678+osy@users.noreply.github.com> Date: Fri, 23 Aug 2024 00:55:37 -0500 Subject: [PATCH 69/79] display(iOS): support serial output on external display Resolves #6594 --- Platform/iOS/VMToolbarDisplayMenuView.swift | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Platform/iOS/VMToolbarDisplayMenuView.swift b/Platform/iOS/VMToolbarDisplayMenuView.swift index f0d1e4f46..574f8c3ff 100644 --- a/Platform/iOS/VMToolbarDisplayMenuView.swift +++ b/Platform/iOS/VMToolbarDisplayMenuView.swift @@ -55,7 +55,10 @@ struct VMToolbarDisplayMenuView: View { Picker("", selection: externalWindowBinding.device) { MenuLabel("None", systemImage: "rectangle.dashed").tag(nil as VMWindowState.Device?) ForEach(session.devices) { device in - if case .display(_, let index) = device { + switch device { + case .serial(_, let index): + MenuLabel("Serial \(index): \(session.qemuConfig.serials[index].target.prettyValue)", systemImage: "rectangle.connected.to.line.below").tag(device as VMWindowState.Device?) + case .display(_, let index): MenuLabel("Display \(index): \(session.qemuConfig.displays[index].hardware.prettyValue)", systemImage: "display").tag(device as VMWindowState.Device?) } } From a74a93bcc064dd3172458845a33577c0a738882b Mon Sep 17 00:00:00 2001 From: osy <50960678+osy@users.noreply.github.com> Date: Fri, 23 Aug 2024 08:24:44 -0500 Subject: [PATCH 70/79] docs: update iOS development for visionOS instructions --- Documentation/iOSDevelopment.md | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/Documentation/iOSDevelopment.md b/Documentation/iOSDevelopment.md index c5dc51889..5bde558ef 100644 --- a/Documentation/iOSDevelopment.md +++ b/Documentation/iOSDevelopment.md @@ -15,12 +15,16 @@ Alternatively, run `git submodule update --init --recursive` after cloning if yo The easy way is to get the prebuilt dependences from [GitHub Actions][1]. Pick the latest release and download the `Sysroot-*` artifact for the targets you wish to develop on. You need to be logged in to GitHub to download artifacts. -| | Intel | Apple Silicon | -|--------------|----------------------------|---------------------------| -| iOS | N/A | `ios-arm64` | -| iOS SE | N/A | `ios-tci-arm64` | -| Simulator | `ios_simulator-x86_64` | `ios_simulator-arm64` | -| Simulator SE | `ios_simulator-tci-x86_64` | `ios_simulator-tci-arm64` | +| | Intel | Apple Silicon | +|-----------------------|----------------------------|--------------------------------| +| iOS | N/A | `ios-arm64` | +| iOS SE | N/A | `ios-tci-arm64` | +| iOS Simulator | `ios_simulator-x86_64` | `ios_simulator-arm64` | +| iOS Simulator SE | `ios_simulator-tci-x86_64` | `ios_simulator-tci-arm64` | +| visionOS | N/A | `visionos-arm64` | +| visionOS SE | N/A | `visionos-tci-arm64` | +| visionOS Simulator | N/A | `visionos_simulator-arm64` | +| visionOS Simulator SE | N/A | `visionos_simulator-tci-arm64` | After downloading the prebuilt artifacts of your choice, extract them to the root directory where you cloned the repository. @@ -49,7 +53,7 @@ You can build UTM for iOS with the script (run `./scripts/build_utm.sh` for all ./scripts/build_utm.sh -k iphoneos -s iOS -a arm64 -o /path/to/output/directory ``` -The built artifact is an unsigned `.xcarchive` which you can use with the package tool (see below). Replace `ios` with `ios-tci` to build UTM SE. +The built artifact is an unsigned `.xcarchive` which you can use with the package tool (see below). Replace `iOS` with `iOS-SE` to build UTM SE. Replace `iphoneos` with `xros` to build for visionOS. ### Packaging From 08d2ab2ced5cd640c34e3354ea5e56bb08159db0 Mon Sep 17 00:00:00 2001 From: osy <50960678+osy@users.noreply.github.com> Date: Fri, 23 Aug 2024 08:26:17 -0500 Subject: [PATCH 71/79] display(iOS): fix build for visionOS --- Platform/iOS/Display/VMDisplayTerminalViewController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Platform/iOS/Display/VMDisplayTerminalViewController.swift b/Platform/iOS/Display/VMDisplayTerminalViewController.swift index 4e0442b38..f65a52c4e 100644 --- a/Platform/iOS/Display/VMDisplayTerminalViewController.swift +++ b/Platform/iOS/Display/VMDisplayTerminalViewController.swift @@ -114,7 +114,7 @@ extension VMDisplayTerminalViewController { { if #available(iOS 15.0, *), useAutoLayout { #if os(visionOS) - let inputAccessoryHeight = 0 + let inputAccessoryHeight: CGFloat = 0 #else let inputAccessoryHeight = terminalView.inputAccessoryView?.frame.height ?? 0 #endif From e7315dd0d00399dcd38f4f431d7a2de0ad5716c9 Mon Sep 17 00:00:00 2001 From: osy <50960678+osy@users.noreply.github.com> Date: Fri, 23 Aug 2024 10:37:10 -0500 Subject: [PATCH 72/79] home: add tips popup for donations on iOS --- Platform/Main.swift | 5 +++ Platform/Shared/ContentView.swift | 4 +++ Platform/Shared/VMNavigationListView.swift | 38 +++++++++++++++++--- Platform/iOS/UTMTips.swift | 42 ++++++++++++++++++++++ UTM.xcodeproj/project.pbxproj | 8 +++++ 5 files changed, 93 insertions(+), 4 deletions(-) create mode 100644 Platform/iOS/UTMTips.swift diff --git a/Platform/Main.swift b/Platform/Main.swift index 23c448a4c..09dd6bfdb 100644 --- a/Platform/Main.swift +++ b/Platform/Main.swift @@ -15,6 +15,7 @@ // import Logging +import TipKit let logger = Logger(label: "com.utmapp.UTM") { label in var utmLogger = UTMLoggingSwift(label: label) @@ -60,6 +61,10 @@ class Main { #if os(iOS) || os(visionOS) // register defaults registerDefaultsFromSettingsBundle() + // register tips + if #available(iOS 17, macOS 14, *) { + try? Tips.configure() + } #endif UTMApp.main() } diff --git a/Platform/Shared/ContentView.swift b/Platform/Shared/ContentView.swift index 2199f4b8c..6f42399a3 100644 --- a/Platform/Shared/ContentView.swift +++ b/Platform/Shared/ContentView.swift @@ -19,6 +19,7 @@ import UniformTypeIdentifiers #if os(iOS) import IQKeyboardManagerSwift #endif +import TipKit // on visionOS, there is no text to show more than UTM #if WITH_QEMU_TCI && !os(visionOS) @@ -78,6 +79,9 @@ struct ContentView: View { }.fileImporter(isPresented: $openSheetPresented, allowedContentTypes: [.UTM, .UTMextension], allowsMultipleSelection: true, onCompletion: selectImportedUTM) .onDrop(of: [.fileURL], delegate: self) .onAppear { + if #available(iOS 17, macOS 14, *) { + UTMTipDonate.timesLaunched += 1 + } Task { await data.listRefresh() #if os(macOS) diff --git a/Platform/Shared/VMNavigationListView.swift b/Platform/Shared/VMNavigationListView.swift index 2b3c2ea31..2cced331d 100644 --- a/Platform/Shared/VMNavigationListView.swift +++ b/Platform/Shared/VMNavigationListView.swift @@ -15,6 +15,7 @@ // import SwiftUI +import TipKit struct VMNavigationListView: View { @EnvironmentObject private var data: UTMData @@ -106,6 +107,21 @@ private struct VMListModifier: ViewModifier { @State private var sheetPresented = false @State private var donatePresented = false + private let _donateTip: Any? + + @available(iOS 17, macOS 14, *) + private var donateTip: UTMTipDonate { + _donateTip as! UTMTipDonate + } + + init() { + if #available(iOS 17, macOS 14, *) { + _donateTip = UTMTipDonate() + } else { + _donateTip = nil + } + } + func body(content: Content) -> some View { content #if os(macOS) @@ -129,10 +145,24 @@ private struct VMListModifier: ViewModifier { #endif #if !WITH_REMOTE ToolbarItem(placement: .navigationBarLeading) { - Button { - donatePresented.toggle() - } label: { - Label("Donate", systemImage: "heart.fill") + if #available(iOS 17, macOS 14, *) { + Button { + donateTip.invalidate(reason: .actionPerformed) + donatePresented.toggle() + } label: { + Image(systemName: "heart.fill") // SwiftUI bug: tip won't show up if this is a label + }.popoverTip(donateTip, arrowEdge: .top) { action in + donateTip.invalidate(reason: .actionPerformed) + if action.id == "donate" { + donatePresented.toggle() + } + } + } else { + Button { + donatePresented.toggle() + } label: { + Label("Donate", systemImage: "heart.fill") + } } } #endif diff --git a/Platform/iOS/UTMTips.swift b/Platform/iOS/UTMTips.swift new file mode 100644 index 000000000..b134f1e59 --- /dev/null +++ b/Platform/iOS/UTMTips.swift @@ -0,0 +1,42 @@ +// +// Copyright © 2024 osy. 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 TipKit + +@available(iOS 17, macOS 14, *) +struct UTMTipDonate: Tip { + @Parameter + static var timesLaunched: Int = 0 + + var title: Text { + Text("Support UTM") + } + + var message: Text? { + Text("Enjoying the app? Consider making a donation to support development.") + } + + var actions: [Action] { + Action(id: "donate", title: "Donate") + Action(id: "no-thanks", title: "No Thanks") + } + + var rules: [Rule] { + #Rule(Self.$timesLaunched) { + $0 > 3 + } + } +} diff --git a/UTM.xcodeproj/project.pbxproj b/UTM.xcodeproj/project.pbxproj index 4603c8241..d01ff5917 100644 --- a/UTM.xcodeproj/project.pbxproj +++ b/UTM.xcodeproj/project.pbxproj @@ -892,6 +892,9 @@ CEC794BD2949663C00121A9F /* UTMScripting.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEC794BB2949663C00121A9F /* UTMScripting.swift */; }; CED234ED254796E500ED0A57 /* NumberTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = CED234EC254796E500ED0A57 /* NumberTextField.swift */; }; CED234EE254796E500ED0A57 /* NumberTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = CED234EC254796E500ED0A57 /* NumberTextField.swift */; }; + CED779E52C78C82A00EB82AE /* UTMTips.swift in Sources */ = {isa = PBXBuildFile; fileRef = CED779E42C78C82A00EB82AE /* UTMTips.swift */; }; + CED779E62C78C82A00EB82AE /* UTMTips.swift in Sources */ = {isa = PBXBuildFile; fileRef = CED779E42C78C82A00EB82AE /* UTMTips.swift */; }; + CED779E72C78C82A00EB82AE /* UTMTips.swift in Sources */ = {isa = PBXBuildFile; fileRef = CED779E42C78C82A00EB82AE /* UTMTips.swift */; }; CED814E924C79F070042F0F1 /* VMConfigDriveCreateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CED814E824C79F070042F0F1 /* VMConfigDriveCreateView.swift */; }; CED814EA24C79F070042F0F1 /* VMConfigDriveCreateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CED814E824C79F070042F0F1 /* VMConfigDriveCreateView.swift */; }; CED814EC24C7C2850042F0F1 /* VMConfigInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CED814EB24C7C2850042F0F1 /* VMConfigInfoView.swift */; }; @@ -2017,6 +2020,7 @@ CECF02562B706ADD00409FC0 /* UTMRemoteConnectInterface.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = UTMRemoteConnectInterface.h; sourceTree = ""; }; CECF02572B70909900409FC0 /* Info-Remote.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Info-Remote.plist"; sourceTree = ""; }; CED234EC254796E500ED0A57 /* NumberTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NumberTextField.swift; sourceTree = ""; }; + CED779E42C78C82A00EB82AE /* UTMTips.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTMTips.swift; sourceTree = ""; }; CED814E824C79F070042F0F1 /* VMConfigDriveCreateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VMConfigDriveCreateView.swift; sourceTree = ""; }; CED814EB24C7C2850042F0F1 /* VMConfigInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VMConfigInfoView.swift; sourceTree = ""; }; CED814EE24C7EB760042F0F1 /* ImagePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagePicker.swift; sourceTree = ""; }; @@ -2698,6 +2702,7 @@ 842B9F8C28CC58B700031EE7 /* UTMPatches.swift */, CEE06B282B30013500A811AE /* UTMRemoteConnectView.swift */, 84CE3DB02904C7A100FF068B /* UTMSettingsView.swift */, + CED779E42C78C82A00EB82AE /* UTMTips.swift */, CE2D954D24AD4F980059923A /* VMConfigNetworkPortForwardView.swift */, 84CF5DD2288DCE6400D01721 /* VMDisplayHostedView.swift */, 84018685288A3B5B0050AC51 /* VMSessionState.swift */, @@ -3657,6 +3662,7 @@ CE2D958F24AD4FF00059923A /* VMCardView.swift in Sources */, 8432329028C2CDAD00CFBC97 /* VMNavigationListView.swift in Sources */, 841E58CE28937FED00137A20 /* UTMSingleWindowView.swift in Sources */, + CED779E52C78C82A00EB82AE /* UTMTips.swift in Sources */, CE2D930B24AD46670059923A /* UTMLegacyQemuConfiguration+Sharing.m in Sources */, 84C505AC28C588EC007CE8FF /* SizeTextField.swift in Sources */, 8471770627CC974F00D3A50B /* DefaultTextField.swift in Sources */, @@ -3924,6 +3930,7 @@ CEA45E63263519B5002FA97D /* UTMLegacyViewState.m in Sources */, CEA45E64263519B5002FA97D /* UTMLoggingSwift.swift in Sources */, 841619A7284315C1000034B2 /* UTMQemuConfiguration.swift in Sources */, + CED779E62C78C82A00EB82AE /* UTMTips.swift in Sources */, 84C2E8662AA429E800B17308 /* VMWizardContent.swift in Sources */, CEF0305F26A2AFDF00667B63 /* VMWizardState.swift in Sources */, CEA45E69263519B5002FA97D /* VMConfigQEMUView.swift in Sources */, @@ -4152,6 +4159,7 @@ CEF7F5F92AEEDCC400E34952 /* VMToolbarDriveMenuView.swift in Sources */, CE08334B2B784FD400522C03 /* RemoteContentView.swift in Sources */, CEF7F5FA2AEEDCC400E34952 /* VMSettingsView.swift in Sources */, + CED779E72C78C82A00EB82AE /* UTMTips.swift in Sources */, CEF7F5FB2AEEDCC400E34952 /* VMDisplayViewController.swift in Sources */, CEF7F5FC2AEEDCC400E34952 /* VMWizardStartView.swift in Sources */, CEF7F5FD2AEEDCC400E34952 /* QEMUConstantGenerated.swift in Sources */, From e8c6a905ac4dc92776af9011c68c2ffcec905768 Mon Sep 17 00:00:00 2001 From: osy <50960678+osy@users.noreply.github.com> Date: Fri, 23 Aug 2024 11:11:08 -0500 Subject: [PATCH 73/79] toolbar: add tip to hiding toolbar --- Platform/iOS/UTMTips.swift | 20 ++++++++++++++++++ Platform/iOS/VMToolbarView.swift | 36 +++++++++++++++++++++++++++++++- 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/Platform/iOS/UTMTips.swift b/Platform/iOS/UTMTips.swift index b134f1e59..1b4ec6865 100644 --- a/Platform/iOS/UTMTips.swift +++ b/Platform/iOS/UTMTips.swift @@ -40,3 +40,23 @@ struct UTMTipDonate: Tip { } } } + +@available(iOS 17, macOS 14, *) +struct UTMTipHideToolbar: Tip { + @Parameter + static var didHideToolbar: Bool = true + + var title: Text { + Text("Tap to hide/show toolbar") + } + + var message: Text? { + Text("When the toolbar is hidden, the icon will disappear after a few seconds. To show the icon again, tap anywhere on the screen.") + } + + var rules: [Rule] { + #Rule(Self.$didHideToolbar) { + !$0 + } + } +} diff --git a/Platform/iOS/VMToolbarView.swift b/Platform/iOS/VMToolbarView.swift index 5a9d2bfc0..2d7141be5 100644 --- a/Platform/iOS/VMToolbarView.swift +++ b/Platform/iOS/VMToolbarView.swift @@ -15,9 +15,10 @@ // import SwiftUI +import TipKit struct VMToolbarView: View { - @AppStorage("ToolbarIsCollapsed") private var isCollapsed: Bool = true + @AppStorage("ToolbarIsCollapsed") private var isCollapsed: Bool = false @AppStorage("ToolbarLocation") private var location: ToolbarLocation = .topRight @State private var shake: Bool = true @State private var isMoving: Bool = false @@ -145,6 +146,7 @@ struct VMToolbarView: View { } label: { Label("Hide", systemImage: isCollapsed ? nameOfHideIcon : nameOfShowIcon) }.buttonStyle(.toolbar(horizontalSizeClass: horizontalSizeClass, verticalSizeClass: verticalSizeClass)) + .modifier(HideToolbarTipModifier(isCollapsed: $isCollapsed)) .opacity(toolbarToggleOpacity) .modifier(Shake(shake: shake)) .position(position(for: geometry)) @@ -387,3 +389,35 @@ extension MenuStyle where Self == ToolbarMenuStyle { DispatchQueue.main.asyncAfter(deadline: .now() + 15, execute: longIdleTask!) } } + +private struct HideToolbarTipModifier: ViewModifier { + @Binding var isCollapsed: Bool + private let _hideToolbarTip: Any? + + @available(iOS 17, *) + private var hideToolbarTip: UTMTipHideToolbar { + _hideToolbarTip as! UTMTipHideToolbar + } + + init(isCollapsed: Binding) { + _isCollapsed = isCollapsed + if #available(iOS 17, *) { + _hideToolbarTip = UTMTipHideToolbar() + } else { + _hideToolbarTip = nil + } + } + + @ViewBuilder + func body(content: Content) -> some View { + if #available(iOS 17, *) { + content + .popoverTip(hideToolbarTip, arrowEdge: .top) + .onAppear { + UTMTipHideToolbar.didHideToolbar = isCollapsed + } + } else { + content + } + } +} From 257bb20b8c9610bdde4ea95d7e0a036e222a21f1 Mon Sep 17 00:00:00 2001 From: osy <50960678+osy@users.noreply.github.com> Date: Fri, 23 Aug 2024 11:19:35 -0500 Subject: [PATCH 74/79] config(qemu): fix locking when no iso is specified --- Configuration/UTMQemuConfiguration+Arguments.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Configuration/UTMQemuConfiguration+Arguments.swift b/Configuration/UTMQemuConfiguration+Arguments.swift index 43580f6d8..b88110d0f 100644 --- a/Configuration/UTMQemuConfiguration+Arguments.swift +++ b/Configuration/UTMQemuConfiguration+Arguments.swift @@ -741,7 +741,9 @@ import Virtualization // for getting network interfaces placeholderUrl } if drive.isReadOnly || isCd { - "file.locking=off" + if drive.imageURL != nil { + "file.locking=off" + } "readonly=on" } else { "discard=unmap" From 4cc06132c46dd9b45e9bb0022ea0c01edc6e6246 Mon Sep 17 00:00:00 2001 From: osy <50960678+osy@users.noreply.github.com> Date: Fri, 23 Aug 2024 13:00:16 -0500 Subject: [PATCH 75/79] home: add tip for creating a new VM when empty --- Platform/Shared/ContentView.swift | 16 ++++++++++------ Platform/Shared/VMNavigationListView.swift | 20 +++++++++++++++++++- Platform/iOS/UTMTips.swift | 20 ++++++++++++++++++++ 3 files changed, 49 insertions(+), 7 deletions(-) diff --git a/Platform/Shared/ContentView.swift b/Platform/Shared/ContentView.swift index 6f42399a3..d0cca7edc 100644 --- a/Platform/Shared/ContentView.swift +++ b/Platform/Shared/ContentView.swift @@ -49,6 +49,9 @@ struct ContentView: View { .disabled(data.busy && !data.showNewVMSheet && !data.showSettingsModal) .sheet(isPresented: $releaseHelper.isReleaseNotesShown, onDismiss: { releaseHelper.closeReleaseNotes() + if #available(iOS 17, macOS 14, *) { + UTMTipCreateVM.isVMListEmpty = data.virtualMachines.count == 0 + } }, content: { VMReleaseNotesView(helper: releaseHelper).padding() }) @@ -79,20 +82,21 @@ struct ContentView: View { }.fileImporter(isPresented: $openSheetPresented, allowedContentTypes: [.UTM, .UTMextension], allowsMultipleSelection: true, onCompletion: selectImportedUTM) .onDrop(of: [.fileURL], delegate: self) .onAppear { - if #available(iOS 17, macOS 14, *) { - UTMTipDonate.timesLaunched += 1 - } Task { await data.listRefresh() + await releaseHelper.fetchReleaseNotes() + if #available(iOS 17, macOS 14, *) { + if !releaseHelper.isReleaseNotesShown { + UTMTipCreateVM.isVMListEmpty = data.virtualMachines.count == 0 + UTMTipDonate.timesLaunched += 1 + } + } #if os(macOS) if isServerAutostart { await data.remoteServer.start() } #endif } - Task { - await releaseHelper.fetchReleaseNotes() - } #if os(macOS) NSWindow.allowsAutomaticWindowTabbing = false #else diff --git a/Platform/Shared/VMNavigationListView.swift b/Platform/Shared/VMNavigationListView.swift index 2cced331d..1c589ecae 100644 --- a/Platform/Shared/VMNavigationListView.swift +++ b/Platform/Shared/VMNavigationListView.swift @@ -108,17 +108,25 @@ private struct VMListModifier: ViewModifier { @State private var donatePresented = false private let _donateTip: Any? + private let _createTip: Any? @available(iOS 17, macOS 14, *) private var donateTip: UTMTipDonate { _donateTip as! UTMTipDonate } + @available(iOS 17, macOS 14, *) + private var createTip: UTMTipCreateVM { + _createTip as! UTMTipCreateVM + } + init() { if #available(iOS 17, macOS 14, *) { _donateTip = UTMTipDonate() + _createTip = UTMTipCreateVM() } else { _donateTip = nil + _createTip = nil } } @@ -140,7 +148,17 @@ private struct VMListModifier: ViewModifier { #else #if !WITH_REMOTE // FIXME: implement remote feature ToolbarItem(placement: .navigationBarLeading) { - newButton + if #available(iOS 17, *) { + Button { + createTip.invalidate(reason: .actionPerformed) + data.newVM() + } label: { + Image(systemName: "plus") // SwiftUI bug: tip won't show up if this is a label + }.help("Create a new VM") + .popoverTip(createTip, arrowEdge: .top) + } else { + newButton + } } #endif #if !WITH_REMOTE diff --git a/Platform/iOS/UTMTips.swift b/Platform/iOS/UTMTips.swift index 1b4ec6865..c125a806c 100644 --- a/Platform/iOS/UTMTips.swift +++ b/Platform/iOS/UTMTips.swift @@ -60,3 +60,23 @@ struct UTMTipHideToolbar: Tip { } } } + +@available(iOS 17, macOS 14, *) +struct UTMTipCreateVM: Tip { + @Parameter(.transient) + static var isVMListEmpty: Bool = false + + var title: Text { + Text("Start Here") + } + + var message: Text? { + Text("Create a new virtual machine or import an existing one.") + } + + var rules: [Rule] { + #Rule(Self.$isVMListEmpty) { + $0 + } + } +} From 49ff2092420cb5eea9e631fe3cf38b3ceee54d6e Mon Sep 17 00:00:00 2001 From: osy <50960678+osy@users.noreply.github.com> Date: Fri, 23 Aug 2024 13:12:03 -0500 Subject: [PATCH 76/79] tips: add tips to macOS build as well --- Platform/{iOS => Shared}/UTMTips.swift | 0 UTM.xcodeproj/project.pbxproj | 4 +++- 2 files changed, 3 insertions(+), 1 deletion(-) rename Platform/{iOS => Shared}/UTMTips.swift (100%) diff --git a/Platform/iOS/UTMTips.swift b/Platform/Shared/UTMTips.swift similarity index 100% rename from Platform/iOS/UTMTips.swift rename to Platform/Shared/UTMTips.swift diff --git a/UTM.xcodeproj/project.pbxproj b/UTM.xcodeproj/project.pbxproj index d01ff5917..d898c1555 100644 --- a/UTM.xcodeproj/project.pbxproj +++ b/UTM.xcodeproj/project.pbxproj @@ -895,6 +895,7 @@ CED779E52C78C82A00EB82AE /* UTMTips.swift in Sources */ = {isa = PBXBuildFile; fileRef = CED779E42C78C82A00EB82AE /* UTMTips.swift */; }; CED779E62C78C82A00EB82AE /* UTMTips.swift in Sources */ = {isa = PBXBuildFile; fileRef = CED779E42C78C82A00EB82AE /* UTMTips.swift */; }; CED779E72C78C82A00EB82AE /* UTMTips.swift in Sources */ = {isa = PBXBuildFile; fileRef = CED779E42C78C82A00EB82AE /* UTMTips.swift */; }; + CED779E82C79062500EB82AE /* UTMTips.swift in Sources */ = {isa = PBXBuildFile; fileRef = CED779E42C78C82A00EB82AE /* UTMTips.swift */; }; CED814E924C79F070042F0F1 /* VMConfigDriveCreateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CED814E824C79F070042F0F1 /* VMConfigDriveCreateView.swift */; }; CED814EA24C79F070042F0F1 /* VMConfigDriveCreateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CED814E824C79F070042F0F1 /* VMConfigDriveCreateView.swift */; }; CED814EC24C7C2850042F0F1 /* VMConfigInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CED814EB24C7C2850042F0F1 /* VMConfigInfoView.swift */; }; @@ -2702,7 +2703,6 @@ 842B9F8C28CC58B700031EE7 /* UTMPatches.swift */, CEE06B282B30013500A811AE /* UTMRemoteConnectView.swift */, 84CE3DB02904C7A100FF068B /* UTMSettingsView.swift */, - CED779E42C78C82A00EB82AE /* UTMTips.swift */, CE2D954D24AD4F980059923A /* VMConfigNetworkPortForwardView.swift */, 84CF5DD2288DCE6400D01721 /* VMDisplayHostedView.swift */, 84018685288A3B5B0050AC51 /* VMSessionState.swift */, @@ -2977,6 +2977,7 @@ 83034C0626AB630F006B4BAF /* UTMPendingVMView.swift */, 84909A8C27CACD5C005605F1 /* UTMPlaceholderVMView.swift */, 84909A9027CADAE0005605F1 /* UTMUnavailableVMView.swift */, + CED779E42C78C82A00EB82AE /* UTMTips.swift */, ); path = Shared; sourceTree = ""; @@ -3726,6 +3727,7 @@ 8432329628C2ED9000CFBC97 /* FileBrowseField.swift in Sources */, 848A98C2286A2257006F0550 /* UTMAppleConfigurationMacPlatform.swift in Sources */, 84B36D2B27B790BE00C22685 /* DestructiveButton.swift in Sources */, + CED779E82C79062500EB82AE /* UTMTips.swift in Sources */, CE9B154A2B12A87E003A32DD /* GenerateKey.c in Sources */, CE020BAC24AEE00000B44AB6 /* UTMLoggingSwift.swift in Sources */, 848D99BA28630A780055C215 /* VMConfigSerialView.swift in Sources */, From 7c9945d54e3a9b5b5e85ef354cbb6b9dd72d8e54 Mon Sep 17 00:00:00 2001 From: osy <50960678+osy@users.noreply.github.com> Date: Fri, 23 Aug 2024 13:28:50 -0500 Subject: [PATCH 77/79] tips: disable on visionOS (too ugly) --- Platform/Shared/VMNavigationListView.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Platform/Shared/VMNavigationListView.swift b/Platform/Shared/VMNavigationListView.swift index 1c589ecae..acb50c7a2 100644 --- a/Platform/Shared/VMNavigationListView.swift +++ b/Platform/Shared/VMNavigationListView.swift @@ -148,7 +148,7 @@ private struct VMListModifier: ViewModifier { #else #if !WITH_REMOTE // FIXME: implement remote feature ToolbarItem(placement: .navigationBarLeading) { - if #available(iOS 17, *) { + if #available(iOS 17, visionOS 99, *) { Button { createTip.invalidate(reason: .actionPerformed) data.newVM() @@ -163,7 +163,7 @@ private struct VMListModifier: ViewModifier { #endif #if !WITH_REMOTE ToolbarItem(placement: .navigationBarLeading) { - if #available(iOS 17, macOS 14, *) { + if #available(iOS 17, visionOS 99, *) { Button { donateTip.invalidate(reason: .actionPerformed) donatePresented.toggle() From 39d806bbbf1dd87230f6157904a0e3463dd88339 Mon Sep 17 00:00:00 2001 From: osy <50960678+osy@users.noreply.github.com> Date: Fri, 23 Aug 2024 13:29:18 -0500 Subject: [PATCH 78/79] release: allow on cellular and do not wait for connection --- Platform/UTMReleaseHelper.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Platform/UTMReleaseHelper.swift b/Platform/UTMReleaseHelper.swift index 4848aa648..684d529c4 100644 --- a/Platform/UTMReleaseHelper.swift +++ b/Platform/UTMReleaseHelper.swift @@ -47,10 +47,10 @@ class UTMReleaseHelper: ObservableObject { return } let configuration = URLSessionConfiguration.ephemeral - configuration.allowsCellularAccess = false + configuration.allowsCellularAccess = true configuration.allowsExpensiveNetworkAccess = false configuration.allowsConstrainedNetworkAccess = false - configuration.waitsForConnectivity = true + configuration.waitsForConnectivity = false configuration.httpAdditionalHeaders = ["Accept": "application/vnd.github+json", "X-GitHub-Api-Version": "2022-11-28"] let session = URLSession(configuration: configuration) From 9e0bd88b59fbc5dfb338a869a9bb343dba2b50d3 Mon Sep 17 00:00:00 2001 From: Asif Iqbal <53791765+muasiq@users.noreply.github.com> Date: Sat, 24 Aug 2024 03:30:00 +0600 Subject: [PATCH 79/79] Added README.bn.md (#6263) * Create README.bn.md Added a Bengali Version of Readme * Delete README.bn.md * Create README.bn.md --- README.bn.md | 83 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 README.bn.md diff --git a/README.bn.md b/README.bn.md new file mode 100644 index 000000000..6ad88255f --- /dev/null +++ b/README.bn.md @@ -0,0 +1,83 @@ +# UTM +[![Build](https://github.com/utmapp/UTM/workflows/Build/badge.svg?branch=master&event=push)][1] + +> এমন মেশিন আবিষ্কার করা সম্ভব যা যেকোনো ধরনের সিকোয়েনশিয়াল গণনা করতে পারে তা যত কঠিন গণনাই হোক না কেন + +-- অ্যালান টুরিং, ১৯৩৬ + +UTM হল iOS এবং macOS-এর জন্য সমস্ত ফিচার বিশিষ্ট সিস্টেম ইমিউলেটর এবং ভার্চুয়াল মেশিন হোস্ট। এটি QEMU এর উপর বেজ করে বানানো হয়েছে । সংক্ষেপে বলা যায় যে, UTM আপনাকে আপনার ম্যাক, আইফোন এবং আইপ্যাডে উইন্ডোজ, লিনাক্স এবং আরও অনেক অপারেটিং সিস্টেম চালানোর অনুমতি দেয়। লিঙ্ক গুলোতে আরও বিস্তারিত জানতে পারবেনঃ +১। https://getutm.app/ এবং +২। https://mac.getutm.app/ + +

+ একটি আইফোনে UTM চলছে +
+ একটি ম্যাকবুকে UTM চলছে +

+ +## UTM এ যা যা ফিচার আছেঃ + +* QEMU ব্যবহার করে সম্পূর্ণ সিস্টেম ইমিউলেশন (MMU, ডিভাইস, ইত্যাদি) +* x86_64, ARM64, এবং RISC-V সহ 30+ প্রসেসর সাপোর্টেড +* SPICE এবং QXL ব্যবহার করে VGA গ্রাফিক্স মোড +* টেক্সট টার্মিনাল মোড +* ইউএসবি ডিভাইস +* QEMU TCG ব্যবহার করে JIT ভিত্তিক এক্সিলারেশন +* সবচেয়ে লেটেস্ট এবং সেরা API গুলো ব্যবহার করে macOS 11 এবং iOS 11+ এর জন্য স্ক্র্যাচ থেকে ডিজাইন করা ফ্রন্টেন্ড +* আপনার ডিভাইস থেকে সরাসরি VM তৈরি করুন, ম্যানেজ করুন, চালান + +## macOS এর ক্ষেত্রে অতিরিক্ত যা যা ফিচার আছে + +* Hypervisor.framework এবং QEMU ব্যবহার করে হার্ডওয়্যার এক্সিলারেটেড ভার্চুয়ালাইজেশন +* macOS 12+ এ Virtualization.framework সহ macOS গেস্ট বুট করুন + +## UTM SE + +UTM/QEMU-এর সর্বোচ্চ পারফরমেন্স এর জন্য ডায়নামিক কোড জেনারেশন (JIT) প্রয়োজন। iOS ডিভাইসে JIT-এর জন্য দরকার একটি জেলব্রোকেন ডিভাইস, অথবা iOS-এর নির্দিষ্ট ভার্শন এর জন্য যেকোনো ওয়ার্ক এরাউন্ড (আরো বিশদ বিবরণের জন্য "ইনস্টল" পার্ট টি দেখুন)। + +UTM SE ("স্লো এডিশন") একটি [থ্রেডেড ইন্টারপ্রেটার][3] ব্যবহার করে যা একটি ট্র্যাডিশনাল ইন্টারপ্রেটার এর চেয়ে যদিও ভাল পারফর্ম করে কিন্তু এখনও JIT এর চেয়ে স্লো। এই টেকনিকটি ডাইনামিক এক্সিকিউশন এর জন্য [iSH][4] যা করে তার মতোই। ফলস্বরূপ, UTM SE-এর জন্য জেলব্রেকিং বা কোনো JIT সমাধানের প্রয়োজন নেই এবং রেগুলার অ্যাপ হিসেবে সাইডলোড করা যায়। + +সাইজ এবং বিল্ড এর সময় অপ্টিমাইজ করার জন্য, শুধুমাত্র নিচের আর্কিটেকচারগুলি UTM SE-তে ইনক্লুড করা হয়েছে: ARM, PPC, RISC-V, এবং x86 (সমস্তই 32-বিট এবং 64-বিট ভেরিয়েন্টের সাথে)। + +## ইনস্টল + +iOS এর জন্য UTM (SE): https://getutm.app/install/ + + macOS এর জন্য UTM নামাতে পারবেন এখান থেকে: https://mac.getutm.app/ + +## ডেভেলপমেন্ট + +### [macOS ডেভেলপমেন্ট](Documentation/MacDevelopment.md) + +### [iOS ডেভেলপমেন্ট](Documentation/iOSDevelopment.md) + +## রিলেটেড + +* [iSH][4]: iOS এ x86 Linux অ্যাপ্লিকেশন চালানোর জন্য একটি usermode Linux টার্মিনাল ইন্টারফেস ইমিউলেট করে +* [a-shell][5]: সাধারণ ইউনিক্স কমান্ড এবং ইউটিলিটি যেগুলো iOS এর জন্য নেটিভ সেগুলো এটি প্যাকেজ করে দেয় এবং এটি টার্মিনাল ইন্টারফেসের মাধ্যমে অ্যাক্সেস করা যায়। + +## লাইসেন্স + +UTM পারমিসিভ Apache 2.0 লাইসেন্সের অধীনে ডিস্ট্রিবিউট করা হচ্ছে। সেই সাথে এটি বেশ কয়েকটি (L)GPL কম্পোনেন্ট ব্যবহার করছে। বেশিরভাগই ডাইন্যামিক্যালি লিঙ্কড কিন্তু gstreamer প্লাগইনগুলি স্ট্যাটিকভাবে লিঙ্ক করা এবং কোডের কিছু অংশ qemu থেকে নেওয়া। আপনি যদি এই অ্যাপ্লিকেশনটি পুনরায় ডিস্ট্রিবিউট করতে চান তবে দয়া করে এই বিষয় গুলো সম্পর্কে খেয়াল রাখবেন৷ +Some icons made by [Freepik](https://www.freepik.com) [www.flaticon.com](https://www.flaticon.com/). + +[www.flaticon.com](https://www.flaticon.com/) থেকে [ফ্রিপিকের](https://www.freepik.com) তৈরি কিছু আইকন এখানে ব্যবহার করা হয়েছে। + + +এছাড়াও, UTM ফ্রন্টএন্ড নিম্নলিখিত MIT/BSD লাইসেন্স কম্পোনেন্ট এর উপর নির্ভর করে: + +* [IQKeyboardManager](https://github.com/hackiftekhar/IQKeyboardManager) +* [SwiftTerm](https://github.com/migueldeicaza/SwiftTerm) +* [ZIP Foundation](https://github.com/weichsel/ZIPFoundation) +* [InAppSettingsKit](https://github.com/futuretap/InAppSettingsKit) + +কন্টিনিউয়াস ইন্টিগ্রেশন হোস্টিং টি [MacStadium](https://www.macstadium.com/opensource) প্রোভাইড করছে + + +[MacStadium logo](https://www.macstadium.com) + + [1]: https://github.com/utmapp/UTM/actions?query=event%3Arelease+workflow%3ABuild + [2]: screen.png + [3]: https://github.com/ktemkin/qemu/blob/with_tcti/tcg/aarch64-tcti/README.md + [4]: https://github.com/ish-app/ish + [5]: https://github.com/holzschu/a-shell