mirror of
https://github.com/cirruslabs/tart.git
synced 2026-09-30 03:01:12 +02:00
Add an option to disable USB accessories (#1338)
* Add an option to disable USB accessories * Select USB accessories in platform input factories
This commit is contained in:
@@ -90,6 +90,9 @@ struct Run: AsyncParsableCommand {
|
||||
@Flag(help: "Disable audio pass-through to host.")
|
||||
var noAudio: Bool = false
|
||||
|
||||
@Flag(help: "Disable USB accessories.")
|
||||
var noUSBAccessories: Bool = false
|
||||
|
||||
@Flag(help: ArgumentHelp(
|
||||
"Disable clipboard sharing between host and guest.",
|
||||
discussion: "Clipboard sharing requires spice-vdagent package on Linux and https://github.com/cirruslabs/tart-guest-agent on macOS."))
|
||||
@@ -470,6 +473,7 @@ struct Run: AsyncParsableCommand {
|
||||
nested: nested,
|
||||
audio: !noAudio,
|
||||
clipboard: !noClipboard,
|
||||
noUSBAccessories: noUSBAccessories,
|
||||
sync: VZDiskImageSynchronizationMode(diskOptions.syncModeRaw),
|
||||
caching: VZDiskImageCachingMode(diskOptions.cachingModeRaw),
|
||||
noTrackpad: noTrackpad,
|
||||
|
||||
@@ -104,40 +104,42 @@ struct UnsupportedHostOSError: Error, CustomStringConvertible {
|
||||
return result
|
||||
}
|
||||
|
||||
func keyboards() -> [VZKeyboardConfiguration] {
|
||||
func keyboards(noUSB: Bool) -> [VZKeyboardConfiguration] {
|
||||
var devices: [VZKeyboardConfiguration] = noUSB ? [] : [VZUSBKeyboardConfiguration()]
|
||||
if #available(macOS 14, *) {
|
||||
// Mac keyboard is only supported by guests starting with macOS Ventura
|
||||
return [VZUSBKeyboardConfiguration(), VZMacKeyboardConfiguration()]
|
||||
} else {
|
||||
return [VZUSBKeyboardConfiguration()]
|
||||
devices.append(VZMacKeyboardConfiguration())
|
||||
}
|
||||
return devices
|
||||
}
|
||||
|
||||
func keyboardsSuspendable() -> [VZKeyboardConfiguration] {
|
||||
func keyboardsSuspendable(noUSB: Bool) -> [VZKeyboardConfiguration] {
|
||||
if #available(macOS 14, *) {
|
||||
return [VZMacKeyboardConfiguration()]
|
||||
} else {
|
||||
// fallback to the regular configuration
|
||||
return keyboards()
|
||||
return keyboards(noUSB: noUSB)
|
||||
}
|
||||
}
|
||||
|
||||
func pointingDevices() -> [VZPointingDeviceConfiguration] {
|
||||
func pointingDevices(noUSB: Bool) -> [VZPointingDeviceConfiguration] {
|
||||
// Trackpad is only supported by guests starting with macOS Ventura
|
||||
[VZUSBScreenCoordinatePointingDeviceConfiguration(), VZMacTrackpadConfiguration()]
|
||||
var devices: [VZPointingDeviceConfiguration] = noUSB ? [] : [VZUSBScreenCoordinatePointingDeviceConfiguration()]
|
||||
devices.append(VZMacTrackpadConfiguration())
|
||||
return devices
|
||||
}
|
||||
|
||||
func pointingDevicesSimplified() -> [VZPointingDeviceConfiguration] {
|
||||
func pointingDevicesSimplified(noUSB: Bool) -> [VZPointingDeviceConfiguration] {
|
||||
// Only include the USB pointing device, not the trackpad
|
||||
return [VZUSBScreenCoordinatePointingDeviceConfiguration()]
|
||||
return noUSB ? [] : [VZUSBScreenCoordinatePointingDeviceConfiguration()]
|
||||
}
|
||||
|
||||
func pointingDevicesSuspendable() -> [VZPointingDeviceConfiguration] {
|
||||
func pointingDevicesSuspendable(noUSB: Bool) -> [VZPointingDeviceConfiguration] {
|
||||
if #available(macOS 14, *) {
|
||||
return [VZMacTrackpadConfiguration()]
|
||||
} else {
|
||||
// fallback to the regular configuration
|
||||
return pointingDevices()
|
||||
return pointingDevices(noUSB: noUSB)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,16 +35,16 @@ struct Linux: Platform {
|
||||
return result
|
||||
}
|
||||
|
||||
func keyboards() -> [VZKeyboardConfiguration] {
|
||||
[VZUSBKeyboardConfiguration()]
|
||||
func keyboards(noUSB: Bool) -> [VZKeyboardConfiguration] {
|
||||
noUSB ? [] : [VZUSBKeyboardConfiguration()]
|
||||
}
|
||||
|
||||
func pointingDevices() -> [VZPointingDeviceConfiguration] {
|
||||
[VZUSBScreenCoordinatePointingDeviceConfiguration()]
|
||||
func pointingDevices(noUSB: Bool) -> [VZPointingDeviceConfiguration] {
|
||||
noUSB ? [] : [VZUSBScreenCoordinatePointingDeviceConfiguration()]
|
||||
}
|
||||
|
||||
func pointingDevicesSimplified() -> [VZPointingDeviceConfiguration] {
|
||||
func pointingDevicesSimplified(noUSB: Bool) -> [VZPointingDeviceConfiguration] {
|
||||
// Linux doesn't support trackpad, so just return the regular pointing devices
|
||||
return pointingDevices()
|
||||
return pointingDevices(noUSB: noUSB)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,12 +5,12 @@ protocol Platform: Codable {
|
||||
func bootLoader(nvramURL: URL) throws -> VZBootLoader
|
||||
func platform(nvramURL: URL, needsNestedVirtualization: Bool) throws -> VZPlatformConfiguration
|
||||
func graphicsDevice(vmConfig: VMConfig) -> VZGraphicsDeviceConfiguration
|
||||
func keyboards() -> [VZKeyboardConfiguration]
|
||||
func pointingDevices() -> [VZPointingDeviceConfiguration]
|
||||
func pointingDevicesSimplified() -> [VZPointingDeviceConfiguration]
|
||||
func keyboards(noUSB: Bool) -> [VZKeyboardConfiguration]
|
||||
func pointingDevices(noUSB: Bool) -> [VZPointingDeviceConfiguration]
|
||||
func pointingDevicesSimplified(noUSB: Bool) -> [VZPointingDeviceConfiguration]
|
||||
}
|
||||
|
||||
protocol PlatformSuspendable: Platform {
|
||||
func pointingDevicesSuspendable() -> [VZPointingDeviceConfiguration]
|
||||
func keyboardsSuspendable() -> [VZKeyboardConfiguration]
|
||||
func pointingDevicesSuspendable(noUSB: Bool) -> [VZPointingDeviceConfiguration]
|
||||
func keyboardsSuspendable(noUSB: Bool) -> [VZKeyboardConfiguration]
|
||||
}
|
||||
|
||||
+37
-19
@@ -49,6 +49,7 @@ class VM: NSObject, VZVirtualMachineDelegate, ObservableObject {
|
||||
nested: Bool = false,
|
||||
audio: Bool = true,
|
||||
clipboard: Bool = true,
|
||||
noUSBAccessories: Bool = false,
|
||||
sync: VZDiskImageSynchronizationMode = .full,
|
||||
caching: VZDiskImageCachingMode? = nil,
|
||||
noTrackpad: Bool = false,
|
||||
@@ -73,6 +74,7 @@ class VM: NSObject, VZVirtualMachineDelegate, ObservableObject {
|
||||
nested: nested,
|
||||
audio: audio,
|
||||
clipboard: clipboard,
|
||||
noUSBAccessories: noUSBAccessories,
|
||||
sync: sync,
|
||||
caching: caching,
|
||||
noTrackpad: noTrackpad,
|
||||
@@ -324,6 +326,7 @@ class VM: NSObject, VZVirtualMachineDelegate, ObservableObject {
|
||||
nested: Bool = false,
|
||||
audio: Bool = true,
|
||||
clipboard: Bool = true,
|
||||
noUSBAccessories: Bool = false,
|
||||
sync: VZDiskImageSynchronizationMode = .full,
|
||||
caching: VZDiskImageCachingMode? = nil,
|
||||
noTrackpad: Bool = false,
|
||||
@@ -364,25 +367,15 @@ class VM: NSObject, VZVirtualMachineDelegate, ObservableObject {
|
||||
configuration.audioDevices = [soundDeviceConfiguration]
|
||||
|
||||
// Keyboard and mouse
|
||||
if suspendable, let platformSuspendable = vmConfig.platform.self as? PlatformSuspendable {
|
||||
configuration.keyboards = platformSuspendable.keyboardsSuspendable()
|
||||
configuration.pointingDevices = platformSuspendable.pointingDevicesSuspendable()
|
||||
} else {
|
||||
|
||||
if noKeyboard {
|
||||
configuration.keyboards = []
|
||||
} else {
|
||||
configuration.keyboards = vmConfig.platform.keyboards()
|
||||
}
|
||||
|
||||
if noPointer {
|
||||
configuration.pointingDevices = []
|
||||
} else if noTrackpad {
|
||||
configuration.pointingDevices = vmConfig.platform.pointingDevicesSimplified()
|
||||
} else {
|
||||
configuration.pointingDevices = vmConfig.platform.pointingDevices()
|
||||
}
|
||||
}
|
||||
configureInputDevices(
|
||||
configuration,
|
||||
platform: vmConfig.platform,
|
||||
suspendable: suspendable,
|
||||
noUSBAccessories: noUSBAccessories,
|
||||
noTrackpad: noTrackpad,
|
||||
noPointer: noPointer,
|
||||
noKeyboard: noKeyboard
|
||||
)
|
||||
|
||||
// Networking
|
||||
configuration.networkDevices = network.attachments().map {
|
||||
@@ -460,6 +453,31 @@ class VM: NSObject, VZVirtualMachineDelegate, ObservableObject {
|
||||
return configuration
|
||||
}
|
||||
|
||||
static func configureInputDevices(
|
||||
_ configuration: VZVirtualMachineConfiguration,
|
||||
platform: Platform,
|
||||
suspendable: Bool = false,
|
||||
noUSBAccessories: Bool = false,
|
||||
noTrackpad: Bool = false,
|
||||
noPointer: Bool = false,
|
||||
noKeyboard: Bool = false
|
||||
) {
|
||||
if suspendable, let platformSuspendable = platform as? PlatformSuspendable {
|
||||
configuration.keyboards = platformSuspendable.keyboardsSuspendable(noUSB: noUSBAccessories)
|
||||
configuration.pointingDevices = platformSuspendable.pointingDevicesSuspendable(noUSB: noUSBAccessories)
|
||||
} else {
|
||||
configuration.keyboards = noKeyboard ? [] : platform.keyboards(noUSB: noUSBAccessories)
|
||||
|
||||
if noPointer {
|
||||
configuration.pointingDevices = []
|
||||
} else if noTrackpad {
|
||||
configuration.pointingDevices = platform.pointingDevicesSimplified(noUSB: noUSBAccessories)
|
||||
} else {
|
||||
configuration.pointingDevices = platform.pointingDevices(noUSB: noUSBAccessories)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func guestDidStop(_ virtualMachine: VZVirtualMachine) {
|
||||
print("guest has stopped the virtual machine")
|
||||
sema.signal()
|
||||
|
||||
@@ -4,6 +4,22 @@ import XCTest
|
||||
@testable import tart
|
||||
|
||||
final class CommandBehaviorTests: XCTestCase {
|
||||
func testNoUSBAccessoriesDoesNotEnableSuspendable() throws {
|
||||
try withTemporaryTartHome {
|
||||
let vmDir = try VMStorageLocal().create("no-usb-accessories")
|
||||
try config().save(toURL: vmDir.configURL)
|
||||
XCTAssertTrue(FileManager.default.createFile(atPath: vmDir.nvramURL.path, contents: Data()))
|
||||
XCTAssertTrue(FileManager.default.createFile(atPath: vmDir.diskURL.path, contents: Data()))
|
||||
|
||||
let command = try Run.parseAsRoot(["no-usb-accessories", "--no-usb-accessories"]) as! Run
|
||||
|
||||
XCTAssertTrue(command.noUSBAccessories)
|
||||
XCTAssertFalse(command.suspendable)
|
||||
XCTAssertFalse(command.noAudio)
|
||||
XCTAssertFalse(command.noGraphics)
|
||||
}
|
||||
}
|
||||
|
||||
func testStandaloneDeleteDoesNotInitializeContentStore() throws {
|
||||
try withTemporaryTartHome {
|
||||
let vmDir = try VMStorageLocal().create("standalone")
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
import Virtualization
|
||||
import XCTest
|
||||
@testable import tart
|
||||
|
||||
final class InputDeviceConfigurationTests: XCTestCase {
|
||||
func testLinuxUSBInputsCanBeDisabled() {
|
||||
let configuration = VZVirtualMachineConfiguration()
|
||||
|
||||
VM.configureInputDevices(configuration, platform: Linux())
|
||||
XCTAssertEqual(configuration.keyboards.count, 1)
|
||||
XCTAssertTrue(configuration.keyboards.contains { $0 is VZUSBKeyboardConfiguration })
|
||||
XCTAssertEqual(configuration.pointingDevices.count, 1)
|
||||
XCTAssertTrue(configuration.pointingDevices.contains { $0 is VZUSBScreenCoordinatePointingDeviceConfiguration })
|
||||
|
||||
VM.configureInputDevices(configuration, platform: Linux(), noUSBAccessories: true)
|
||||
XCTAssertTrue(configuration.keyboards.isEmpty)
|
||||
XCTAssertTrue(configuration.pointingDevices.isEmpty)
|
||||
|
||||
VM.configureInputDevices(configuration, platform: Linux(), noUSBAccessories: true, noTrackpad: true)
|
||||
XCTAssertTrue(configuration.keyboards.isEmpty)
|
||||
XCTAssertTrue(configuration.pointingDevices.isEmpty)
|
||||
}
|
||||
|
||||
#if arch(arm64)
|
||||
func testMacOS13RetainsItsNativeTrackpad() {
|
||||
let platform = MacInputPlatform(nativeKeyboard: false)
|
||||
let configuration = VZVirtualMachineConfiguration()
|
||||
|
||||
VM.configureInputDevices(configuration, platform: platform)
|
||||
XCTAssertEqual(configuration.keyboards.count, 1)
|
||||
XCTAssertEqual(configuration.pointingDevices.count, 2)
|
||||
|
||||
VM.configureInputDevices(configuration, platform: platform, noUSBAccessories: true)
|
||||
XCTAssertTrue(configuration.keyboards.isEmpty)
|
||||
XCTAssertEqual(configuration.pointingDevices.count, 1)
|
||||
XCTAssertTrue(configuration.pointingDevices.contains { $0 is VZMacTrackpadConfiguration })
|
||||
}
|
||||
|
||||
func testMacOS14RetainsBothNativeInputs() throws {
|
||||
guard #available(macOS 14, *) else {
|
||||
throw XCTSkip("Mac keyboards require macOS 14")
|
||||
}
|
||||
|
||||
let configuration = VZVirtualMachineConfiguration()
|
||||
VM.configureInputDevices(configuration, platform: MacInputPlatform(nativeKeyboard: true), noUSBAccessories: true)
|
||||
|
||||
XCTAssertEqual(configuration.keyboards.count, 1)
|
||||
XCTAssertTrue(configuration.keyboards.contains { $0 is VZMacKeyboardConfiguration })
|
||||
XCTAssertEqual(configuration.pointingDevices.count, 1)
|
||||
XCTAssertTrue(configuration.pointingDevices.contains { $0 is VZMacTrackpadConfiguration })
|
||||
}
|
||||
|
||||
func testInputFlagsStillSelectTheExpectedDevices() throws {
|
||||
guard #available(macOS 14, *) else {
|
||||
throw XCTSkip("Mac keyboards require macOS 14")
|
||||
}
|
||||
|
||||
let platform = MacInputPlatform(nativeKeyboard: true)
|
||||
for noUSBAccessories in [false, true] {
|
||||
for noKeyboard in [false, true] {
|
||||
for noPointer in [false, true] {
|
||||
for noTrackpad in [false, true] {
|
||||
let configuration = VZVirtualMachineConfiguration()
|
||||
VM.configureInputDevices(
|
||||
configuration,
|
||||
platform: platform,
|
||||
noUSBAccessories: noUSBAccessories,
|
||||
noTrackpad: noTrackpad,
|
||||
noPointer: noPointer,
|
||||
noKeyboard: noKeyboard
|
||||
)
|
||||
|
||||
XCTAssertEqual(configuration.keyboards.contains { $0 is VZUSBKeyboardConfiguration }, !noUSBAccessories && !noKeyboard)
|
||||
XCTAssertEqual(configuration.keyboards.contains { $0 is VZMacKeyboardConfiguration }, !noKeyboard)
|
||||
XCTAssertEqual(configuration.pointingDevices.contains { $0 is VZUSBScreenCoordinatePointingDeviceConfiguration }, !noUSBAccessories && !noPointer)
|
||||
XCTAssertEqual(configuration.pointingDevices.contains { $0 is VZMacTrackpadConfiguration }, !noPointer && !noTrackpad)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testSuspendableFallbackCannotReintroduceUSBInputs() {
|
||||
let configuration = VZVirtualMachineConfiguration()
|
||||
let platform = MacInputPlatform(nativeKeyboard: false)
|
||||
VM.configureInputDevices(configuration, platform: platform, suspendable: true)
|
||||
XCTAssertEqual(configuration.keyboards.count, 1)
|
||||
XCTAssertEqual(configuration.pointingDevices.count, 2)
|
||||
|
||||
VM.configureInputDevices(
|
||||
configuration,
|
||||
platform: platform,
|
||||
suspendable: true,
|
||||
noUSBAccessories: true
|
||||
)
|
||||
|
||||
XCTAssertTrue(configuration.keyboards.isEmpty)
|
||||
XCTAssertEqual(configuration.pointingDevices.count, 1)
|
||||
XCTAssertTrue(configuration.pointingDevices.contains { $0 is VZMacTrackpadConfiguration })
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if arch(arm64)
|
||||
// Model macOS 13 and 14 input availability without requiring a second host.
|
||||
private struct MacInputPlatform: PlatformSuspendable {
|
||||
var nativeKeyboard: Bool
|
||||
|
||||
func os() -> OS { .darwin }
|
||||
|
||||
func bootLoader(nvramURL: URL) throws -> VZBootLoader {
|
||||
try Linux().bootLoader(nvramURL: nvramURL)
|
||||
}
|
||||
|
||||
func platform(nvramURL: URL, needsNestedVirtualization: Bool) throws -> VZPlatformConfiguration {
|
||||
try Linux().platform(nvramURL: nvramURL, needsNestedVirtualization: needsNestedVirtualization)
|
||||
}
|
||||
|
||||
func graphicsDevice(vmConfig: VMConfig) -> VZGraphicsDeviceConfiguration {
|
||||
Linux().graphicsDevice(vmConfig: vmConfig)
|
||||
}
|
||||
|
||||
func keyboards(noUSB: Bool) -> [VZKeyboardConfiguration] {
|
||||
var devices: [VZKeyboardConfiguration] = noUSB ? [] : [VZUSBKeyboardConfiguration()]
|
||||
if nativeKeyboard, #available(macOS 14, *) {
|
||||
devices.append(VZMacKeyboardConfiguration())
|
||||
}
|
||||
return devices
|
||||
}
|
||||
|
||||
func pointingDevices(noUSB: Bool) -> [VZPointingDeviceConfiguration] {
|
||||
var devices: [VZPointingDeviceConfiguration] = noUSB ? [] : [VZUSBScreenCoordinatePointingDeviceConfiguration()]
|
||||
devices.append(VZMacTrackpadConfiguration())
|
||||
return devices
|
||||
}
|
||||
|
||||
func pointingDevicesSimplified(noUSB: Bool) -> [VZPointingDeviceConfiguration] {
|
||||
noUSB ? [] : [VZUSBScreenCoordinatePointingDeviceConfiguration()]
|
||||
}
|
||||
|
||||
func keyboardsSuspendable(noUSB: Bool) -> [VZKeyboardConfiguration] {
|
||||
if nativeKeyboard, #available(macOS 14, *) {
|
||||
return [VZMacKeyboardConfiguration()]
|
||||
}
|
||||
return keyboards(noUSB: noUSB)
|
||||
}
|
||||
|
||||
func pointingDevicesSuspendable(noUSB: Bool) -> [VZPointingDeviceConfiguration] {
|
||||
nativeKeyboard ? [VZMacTrackpadConfiguration()] : pointingDevices(noUSB: noUSB)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
Reference in New Issue
Block a user