Skip to content

Commit 46180a9

Browse files
committed
config(apple): support legacy keyboard configurations
Determine from the wizard which keyboard configuration to use. Fixes #5810
1 parent e439e53 commit 46180a9

File tree

5 files changed

+82
-37
lines changed

5 files changed

+82
-37
lines changed

Configuration/UTMAppleConfigurationVirtualization.swift

Lines changed: 38 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,22 @@ struct UTMAppleConfigurationVirtualization: Codable {
2929
var prettyValue: String {
3030
switch self {
3131
case .disabled: return NSLocalizedString("Disabled", comment: "UTMAppleConfigurationDevices")
32-
case .mouse: return NSLocalizedString("Mouse", comment: "UTMAppleConfigurationDevices")
33-
case .trackpad: return NSLocalizedString("Trackpad", comment: "UTMAppleConfigurationDevices")
32+
case .mouse: return NSLocalizedString("Generic Mouse", comment: "UTMAppleConfigurationDevices")
33+
case .trackpad: return NSLocalizedString("Mac Trackpad (macOS 13+)", comment: "UTMAppleConfigurationDevices")
34+
}
35+
}
36+
}
37+
38+
enum KeyboardDevice: String, CaseIterable, QEMUConstant {
39+
case disabled = "Disabled"
40+
case generic = "Generic"
41+
case mac = "Mac"
42+
43+
var prettyValue: String {
44+
switch self {
45+
case .disabled: return NSLocalizedString("Disabled", comment: "UTMAppleConfigurationDevices")
46+
case .generic: return NSLocalizedString("Generic USB", comment: "UTMAppleConfigurationDevices")
47+
case .mac: return NSLocalizedString("Mac Keyboard (macOS 14+)", comment: "UTMAppleConfigurationDevices")
3448
}
3549
}
3650
}
@@ -41,11 +55,9 @@ struct UTMAppleConfigurationVirtualization: Codable {
4155

4256
var hasEntropy: Bool = true
4357

44-
var hasKeyboard: Bool = false
58+
var keyboard: KeyboardDevice = .disabled
4559

46-
var hasPointer: Bool = false
47-
48-
var hasTrackpad: Bool = false
60+
var pointer: PointerDevice = .disabled
4961

5062
var hasRosetta: Bool?
5163

@@ -55,8 +67,8 @@ struct UTMAppleConfigurationVirtualization: Codable {
5567
case hasAudio = "Audio"
5668
case hasBalloon = "Balloon"
5769
case hasEntropy = "Entropy"
58-
case hasKeyboard = "Keyboard"
59-
case hasPointer = "Pointer"
70+
case keyboard = "Keyboard"
71+
case pointer = "Pointer"
6072
case hasTrackpad = "Trackpad"
6173
case rosetta = "Rosetta"
6274
case hasClipboardSharing = "ClipboardSharing"
@@ -70,13 +82,16 @@ struct UTMAppleConfigurationVirtualization: Codable {
7082
hasAudio = try values.decode(Bool.self, forKey: .hasAudio)
7183
hasBalloon = try values.decode(Bool.self, forKey: .hasBalloon)
7284
hasEntropy = try values.decode(Bool.self, forKey: .hasEntropy)
73-
hasKeyboard = try values.decode(Bool.self, forKey: .hasKeyboard)
74-
if let legacyPointer = try? values.decode(PointerDevice.self, forKey: .hasPointer) {
75-
hasPointer = legacyPointer != .disabled
76-
hasTrackpad = legacyPointer == .trackpad
85+
if let hasKeyboard = try? values.decode(Bool.self, forKey: .keyboard) {
86+
keyboard = hasKeyboard ? .generic : .disabled
87+
} else {
88+
keyboard = try values.decode(KeyboardDevice.self, forKey: .keyboard)
89+
}
90+
if let hasPointer = try? values.decode(Bool.self, forKey: .pointer) {
91+
let hasTrackpad = try values.decodeIfPresent(Bool.self, forKey: .hasTrackpad) ?? false
92+
pointer = hasTrackpad ? .trackpad : hasPointer ? .mouse : .disabled
7793
} else {
78-
hasPointer = try values.decode(Bool.self, forKey: .hasPointer)
79-
hasTrackpad = try values.decodeIfPresent(Bool.self, forKey: .hasTrackpad) ?? false
94+
pointer = try values.decode(PointerDevice.self, forKey: .pointer)
8095
}
8196
if #available(macOS 13, *) {
8297
hasRosetta = try values.decodeIfPresent(Bool.self, forKey: .rosetta)
@@ -89,9 +104,8 @@ struct UTMAppleConfigurationVirtualization: Codable {
89104
try container.encode(hasAudio, forKey: .hasAudio)
90105
try container.encode(hasBalloon, forKey: .hasBalloon)
91106
try container.encode(hasEntropy, forKey: .hasEntropy)
92-
try container.encode(hasKeyboard, forKey: .hasKeyboard)
93-
try container.encode(hasPointer, forKey: .hasPointer)
94-
try container.encode(hasTrackpad, forKey: .hasTrackpad)
107+
try container.encode(keyboard, forKey: .keyboard)
108+
try container.encode(pointer, forKey: .pointer)
95109
try container.encodeIfPresent(hasRosetta, forKey: .rosetta)
96110
try container.encode(hasClipboardSharing, forKey: .hasClipboardSharing)
97111
}
@@ -108,8 +122,8 @@ extension UTMAppleConfigurationVirtualization {
108122
hasEntropy = oldConfig.isEntropyEnabled
109123
if #available(macOS 12, *) {
110124
hasAudio = oldConfig.isAudioEnabled
111-
hasKeyboard = oldConfig.isKeyboardEnabled
112-
hasPointer = oldConfig.isPointingEnabled
125+
keyboard = oldConfig.isKeyboardEnabled ? .generic : .disabled
126+
pointer = oldConfig.isPointingEnabled ? .mouse : .disabled
113127
}
114128
}
115129
}
@@ -138,25 +152,25 @@ extension UTMAppleConfigurationVirtualization {
138152
audioOutputConfiguration.streams = [audioOutput]
139153
vzconfig.audioDevices = [audioInputConfiguration, audioOutputConfiguration]
140154
}
141-
if hasKeyboard {
155+
if keyboard != .disabled {
142156
vzconfig.keyboards = [VZUSBKeyboardConfiguration()]
143157
#if arch(arm64)
144-
if #available(macOS 14, *), isMacOSGuest {
158+
if #available(macOS 14, *), isMacOSGuest && keyboard == .mac {
145159
vzconfig.keyboards = [VZMacKeyboardConfiguration()]
146160
}
147161
#endif
148162
}
149-
if hasPointer {
163+
if pointer != .disabled {
150164
vzconfig.pointingDevices = [VZUSBScreenCoordinatePointingDeviceConfiguration()]
151165
#if arch(arm64)
152-
if #available(macOS 13, *), isMacOSGuest && hasTrackpad {
166+
if #available(macOS 13, *), isMacOSGuest && pointer == .trackpad {
153167
// replace with trackpad device
154168
vzconfig.pointingDevices = [VZMacTrackpadConfiguration()]
155169
}
156170
#endif
157171
}
158172
} else {
159-
if hasAudio || hasKeyboard || hasPointer {
173+
if hasAudio || keyboard != .disabled || pointer != .disabled {
160174
throw UTMAppleConfigurationError.featureNotSupported
161175
}
162176
}

Platform/Shared/VMWizardOSMacView.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ struct VMWizardOSMacView: View {
6767
await MainActor.run {
6868
wizardState.macPlatform = UTMAppleConfigurationMacPlatform(newHardware: model)
6969
wizardState.macRecoveryIpswURL = url
70-
wizardState.macIsMonterey = image.buildVersion.hasPrefix("21")
70+
wizardState.macPlatformVersion = image.buildVersion.integerPrefix()
7171
wizardState.isSkipBootImage = true
7272
wizardState.bootImageURL = nil
7373
wizardState.next()

Platform/Shared/VMWizardState.swift

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,21 @@ enum VMWizardOS: String, Identifiable {
8383
#if os(macOS) && arch(arm64)
8484
@Published var macPlatform: UTMAppleConfigurationMacPlatform?
8585
@Published var macRecoveryIpswURL: URL?
86-
@Published var macIsMonterey: Bool = false
86+
@Published var macPlatformVersion: Int?
87+
var macIsLeastVentura: Bool {
88+
if let macPlatformVersion = macPlatformVersion {
89+
return macPlatformVersion >= 22
90+
} else {
91+
return false
92+
}
93+
}
94+
var macIsLeastSonoma: Bool {
95+
if let macPlatformVersion = macPlatformVersion {
96+
return macPlatformVersion >= 23
97+
} else {
98+
return false
99+
}
100+
}
87101
#endif
88102
@Published var isSkipBootImage: Bool = false
89103
@Published var bootImageURL: URL?
@@ -282,7 +296,6 @@ enum VMWizardOS: String, Identifiable {
282296
config.system.boot = try! UTMAppleConfigurationBoot(for: .macOS)
283297
config.system.boot.macRecoveryIpswURL = macRecoveryIpswURL
284298
config.system.macPlatform = macPlatform
285-
config.virtualization.hasTrackpad = !macIsMonterey
286299
}
287300
#endif
288301
case .Linux:
@@ -318,16 +331,25 @@ enum VMWizardOS: String, Identifiable {
318331
}
319332
// some meaningful defaults
320333
if #available(macOS 12, *) {
321-
var hasDisplay = operatingSystem == .macOS
334+
let isMac = operatingSystem == .macOS
335+
var hasDisplay = isMac
322336
if #available(macOS 13, *) {
323337
hasDisplay = hasDisplay || (operatingSystem == .Linux)
324338
}
325339
if hasDisplay {
326340
config.displays = [UTMAppleConfigurationDisplay(width: 1920, height: 1200)]
327341
config.virtualization.hasAudio = true
328-
config.virtualization.hasKeyboard = true
329-
config.virtualization.hasPointer = true
342+
config.virtualization.keyboard = .generic
343+
config.virtualization.pointer = .mouse
344+
}
345+
#if arch(arm64)
346+
if isMac && macIsLeastVentura {
347+
config.virtualization.pointer = .trackpad
348+
}
349+
if isMac && macIsLeastSonoma {
350+
config.virtualization.keyboard = .mac
330351
}
352+
#endif
331353
}
332354
config.virtualization.hasBalloon = true
333355
config.virtualization.hasEntropy = true
@@ -351,7 +373,7 @@ enum VMWizardOS: String, Identifiable {
351373
if let hardwareModel = restoreImage.mostFeaturefulSupportedConfiguration?.hardwareModel {
352374
self.macPlatform = UTMAppleConfigurationMacPlatform(newHardware: hardwareModel)
353375
self.macRecoveryIpswURL = restoreImage.url
354-
self.macIsMonterey = restoreImage.buildVersion.hasPrefix("21")
376+
self.macPlatformVersion = restoreImage.buildVersion.integerPrefix()
355377
} else {
356378
self.alertMessage = AlertMessage(NSLocalizedString("Failed to get latest macOS version from Apple.", comment: "VMWizardState"))
357379
}

Platform/macOS/VMConfigAppleVirtualizationView.swift

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,8 @@ struct VMConfigAppleVirtualizationView: View {
2828
Toggle("Enable Entropy Device", isOn: $config.hasEntropy)
2929
if #available(macOS 12, *) {
3030
Toggle("Enable Sound", isOn: $config.hasAudio)
31-
Toggle("Enable Keyboard", isOn: $config.hasKeyboard)
32-
Toggle("Enable Pointer", isOn: $config.hasPointer)
33-
}
34-
if #available(macOS 13, *), config.hasPointer {
35-
Toggle("Use Trackpad", isOn: $config.hasTrackpad)
36-
.help("Allows passing through additional input from trackpads. Only supported on macOS 13+ guests.")
31+
VMConfigConstantPicker("Keyboard", selection: $config.keyboard)
32+
VMConfigConstantPicker("Pointer", selection: $config.pointer)
3733
}
3834
if #available(macOS 13, *), operatingSystem == .linux {
3935
#if arch(arm64)

Services/UTMExtensions.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,3 +371,16 @@ extension URL {
371371
bookmarkDataIsStale: &stale)
372372
}
373373
}
374+
375+
extension String {
376+
func integerPrefix() -> Int? {
377+
var numeric = ""
378+
for char in self {
379+
if !char.isNumber {
380+
break
381+
}
382+
numeric.append(char)
383+
}
384+
return Int(numeric)
385+
}
386+
}

0 commit comments

Comments
 (0)