Skip to content

Commit

Permalink
config(apple): support legacy keyboard configurations
Browse files Browse the repository at this point in the history
Determine from the wizard which keyboard configuration to use.

Fixes #5810
  • Loading branch information
osy committed Oct 20, 2023
1 parent e439e53 commit 1b004c7
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 37 deletions.
62 changes: 38 additions & 24 deletions Configuration/UTMAppleConfigurationVirtualization.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,22 @@ struct UTMAppleConfigurationVirtualization: Codable {
var prettyValue: String {
switch self {
case .disabled: return NSLocalizedString("Disabled", comment: "UTMAppleConfigurationDevices")
case .mouse: return NSLocalizedString("Mouse", comment: "UTMAppleConfigurationDevices")
case .trackpad: return NSLocalizedString("Trackpad", comment: "UTMAppleConfigurationDevices")
case .mouse: return NSLocalizedString("Generic Mouse", comment: "UTMAppleConfigurationDevices")
case .trackpad: return NSLocalizedString("Mac Trackpad (macOS 13+)", comment: "UTMAppleConfigurationDevices")
}
}
}

enum KeyboardDevice: String, CaseIterable, QEMUConstant {
case disabled = "Disabled"
case generic = "Generic"
case mac = "Mac"

var prettyValue: String {
switch self {
case .disabled: return NSLocalizedString("Disabled", comment: "UTMAppleConfigurationDevices")
case .generic: return NSLocalizedString("Generic USB", comment: "UTMAppleConfigurationDevices")
case .mac: return NSLocalizedString("Mac Keyboard (macOS 14+)", comment: "UTMAppleConfigurationDevices")
}
}
}
Expand All @@ -41,11 +55,9 @@ struct UTMAppleConfigurationVirtualization: Codable {

var hasEntropy: Bool = true

var hasKeyboard: Bool = false
var keyboard: KeyboardDevice = .disabled

var hasPointer: Bool = false

var hasTrackpad: Bool = false
var pointer: PointerDevice = .disabled

var hasRosetta: Bool?

Expand All @@ -55,8 +67,8 @@ struct UTMAppleConfigurationVirtualization: Codable {
case hasAudio = "Audio"
case hasBalloon = "Balloon"
case hasEntropy = "Entropy"
case hasKeyboard = "Keyboard"
case hasPointer = "Pointer"
case keyboard = "Keyboard"
case pointer = "Pointer"
case hasTrackpad = "Trackpad"
case rosetta = "Rosetta"
case hasClipboardSharing = "ClipboardSharing"
Expand All @@ -70,13 +82,16 @@ struct UTMAppleConfigurationVirtualization: Codable {
hasAudio = try values.decode(Bool.self, forKey: .hasAudio)
hasBalloon = try values.decode(Bool.self, forKey: .hasBalloon)
hasEntropy = try values.decode(Bool.self, forKey: .hasEntropy)
hasKeyboard = try values.decode(Bool.self, forKey: .hasKeyboard)
if let legacyPointer = try? values.decode(PointerDevice.self, forKey: .hasPointer) {
hasPointer = legacyPointer != .disabled
hasTrackpad = legacyPointer == .trackpad
if let hasKeyboard = try? values.decode(Bool.self, forKey: .keyboard) {
keyboard = hasKeyboard ? .generic : .disabled
} else {
keyboard = try values.decode(KeyboardDevice.self, forKey: .keyboard)
}
if let hasPointer = try? values.decode(Bool.self, forKey: .pointer) {
let hasTrackpad = try values.decodeIfPresent(Bool.self, forKey: .hasTrackpad) ?? false
pointer = hasTrackpad ? .trackpad : hasPointer ? .mouse : .disabled
} else {
hasPointer = try values.decode(Bool.self, forKey: .hasPointer)
hasTrackpad = try values.decodeIfPresent(Bool.self, forKey: .hasTrackpad) ?? false
pointer = try values.decode(PointerDevice.self, forKey: .pointer)
}
if #available(macOS 13, *) {
hasRosetta = try values.decodeIfPresent(Bool.self, forKey: .rosetta)
Expand All @@ -89,9 +104,8 @@ struct UTMAppleConfigurationVirtualization: Codable {
try container.encode(hasAudio, forKey: .hasAudio)
try container.encode(hasBalloon, forKey: .hasBalloon)
try container.encode(hasEntropy, forKey: .hasEntropy)
try container.encode(hasKeyboard, forKey: .hasKeyboard)
try container.encode(hasPointer, forKey: .hasPointer)
try container.encode(hasTrackpad, forKey: .hasTrackpad)
try container.encode(keyboard, forKey: .keyboard)
try container.encode(pointer, forKey: .pointer)
try container.encodeIfPresent(hasRosetta, forKey: .rosetta)
try container.encode(hasClipboardSharing, forKey: .hasClipboardSharing)
}
Expand All @@ -108,8 +122,8 @@ extension UTMAppleConfigurationVirtualization {
hasEntropy = oldConfig.isEntropyEnabled
if #available(macOS 12, *) {
hasAudio = oldConfig.isAudioEnabled
hasKeyboard = oldConfig.isKeyboardEnabled
hasPointer = oldConfig.isPointingEnabled
keyboard = oldConfig.isKeyboardEnabled ? .generic : .disabled
pointer = oldConfig.isPointingEnabled ? .mouse : .disabled
}
}
}
Expand Down Expand Up @@ -138,25 +152,25 @@ extension UTMAppleConfigurationVirtualization {
audioOutputConfiguration.streams = [audioOutput]
vzconfig.audioDevices = [audioInputConfiguration, audioOutputConfiguration]
}
if hasKeyboard {
if keyboard != .disabled {
vzconfig.keyboards = [VZUSBKeyboardConfiguration()]
#if arch(arm64)
if #available(macOS 14, *), isMacOSGuest {
if #available(macOS 14, *), isMacOSGuest && keyboard == .mac {
vzconfig.keyboards = [VZMacKeyboardConfiguration()]
}
#endif
}
if hasPointer {
if pointer != .disabled {
vzconfig.pointingDevices = [VZUSBScreenCoordinatePointingDeviceConfiguration()]
#if arch(arm64)
if #available(macOS 13, *), isMacOSGuest && hasTrackpad {
if #available(macOS 13, *), isMacOSGuest && pointer == .trackpad {
// replace with trackpad device
vzconfig.pointingDevices = [VZMacTrackpadConfiguration()]
}
#endif
}
} else {
if hasAudio || hasKeyboard || hasPointer {
if hasAudio || keyboard != .disabled || pointer != .disabled {
throw UTMAppleConfigurationError.featureNotSupported
}
}
Expand Down
2 changes: 1 addition & 1 deletion Platform/Shared/VMWizardOSMacView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ struct VMWizardOSMacView: View {
await MainActor.run {
wizardState.macPlatform = UTMAppleConfigurationMacPlatform(newHardware: model)
wizardState.macRecoveryIpswURL = url
wizardState.macIsMonterey = image.buildVersion.hasPrefix("21")
wizardState.macPlatformVersion = image.buildVersion.integerPrefix()
wizardState.isSkipBootImage = true
wizardState.bootImageURL = nil
wizardState.next()
Expand Down
32 changes: 26 additions & 6 deletions Platform/Shared/VMWizardState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,21 @@ enum VMWizardOS: String, Identifiable {
#if os(macOS) && arch(arm64)
@Published var macPlatform: UTMAppleConfigurationMacPlatform?
@Published var macRecoveryIpswURL: URL?
@Published var macIsMonterey: Bool = false
@Published var macPlatformVersion: Int?
var macIsLeastVentura: Bool {
if let macPlatformVersion = macPlatformVersion {
return macPlatformVersion >= 22
} else {
return false
}
}
var macIsLeastSonoma: Bool {
if let macPlatformVersion = macPlatformVersion {
return macPlatformVersion >= 23
} else {
return false
}
}
#endif
@Published var isSkipBootImage: Bool = false
@Published var bootImageURL: URL?
Expand Down Expand Up @@ -282,7 +296,6 @@ enum VMWizardOS: String, Identifiable {
config.system.boot = try! UTMAppleConfigurationBoot(for: .macOS)
config.system.boot.macRecoveryIpswURL = macRecoveryIpswURL
config.system.macPlatform = macPlatform
config.virtualization.hasTrackpad = !macIsMonterey
}
#endif
case .Linux:
Expand Down Expand Up @@ -318,15 +331,22 @@ enum VMWizardOS: String, Identifiable {
}
// some meaningful defaults
if #available(macOS 12, *) {
var hasDisplay = operatingSystem == .macOS
let isMac = operatingSystem == .macOS
var hasDisplay = isMac
if #available(macOS 13, *) {
hasDisplay = hasDisplay || (operatingSystem == .Linux)
}
if hasDisplay {
config.displays = [UTMAppleConfigurationDisplay(width: 1920, height: 1200)]
config.virtualization.hasAudio = true
config.virtualization.hasKeyboard = true
config.virtualization.hasPointer = true
config.virtualization.keyboard = .generic
config.virtualization.pointer = .mouse
}
if isMac && macIsLeastVentura {
config.virtualization.pointer = .trackpad
}
if isMac && macIsLeastSonoma {
config.virtualization.keyboard = .mac
}
}
config.virtualization.hasBalloon = true
Expand All @@ -351,7 +371,7 @@ enum VMWizardOS: String, Identifiable {
if let hardwareModel = restoreImage.mostFeaturefulSupportedConfiguration?.hardwareModel {
self.macPlatform = UTMAppleConfigurationMacPlatform(newHardware: hardwareModel)
self.macRecoveryIpswURL = restoreImage.url
self.macIsMonterey = restoreImage.buildVersion.hasPrefix("21")
self.macPlatformVersion = restoreImage.buildVersion.integerPrefix()
} else {
self.alertMessage = AlertMessage(NSLocalizedString("Failed to get latest macOS version from Apple.", comment: "VMWizardState"))
}
Expand Down
8 changes: 2 additions & 6 deletions Platform/macOS/VMConfigAppleVirtualizationView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,8 @@ struct VMConfigAppleVirtualizationView: View {
Toggle("Enable Entropy Device", isOn: $config.hasEntropy)
if #available(macOS 12, *) {
Toggle("Enable Sound", isOn: $config.hasAudio)
Toggle("Enable Keyboard", isOn: $config.hasKeyboard)
Toggle("Enable Pointer", isOn: $config.hasPointer)
}
if #available(macOS 13, *), config.hasPointer {
Toggle("Use Trackpad", isOn: $config.hasTrackpad)
.help("Allows passing through additional input from trackpads. Only supported on macOS 13+ guests.")
VMConfigConstantPicker("Keyboard", selection: $config.keyboard)
VMConfigConstantPicker("Pointer", selection: $config.pointer)
}
if #available(macOS 13, *), operatingSystem == .linux {
#if arch(arm64)
Expand Down
13 changes: 13 additions & 0 deletions Services/UTMExtensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -371,3 +371,16 @@ extension URL {
bookmarkDataIsStale: &stale)
}
}

extension String {
func integerPrefix() -> Int? {
var numeric = ""
for char in self {
if !char.isNumber {
break
}
numeric.append(char)
}
return Int(numeric)
}
}

0 comments on commit 1b004c7

Please sign in to comment.