mirror of
https://github.com/utmapp/UTM
synced 2024-11-12 22:17:05 +00:00
parent
1193b80725
commit
8d7b51b878
2 changed files with 149 additions and 1 deletions
|
@ -21,6 +21,7 @@ public enum UTMScripting: String {
|
|||
case guestFile = "guest file"
|
||||
case guestProcess = "guest process"
|
||||
case serialPort = "serial port"
|
||||
case usbDevice = "usb device"
|
||||
case virtualMachine = "virtual machine"
|
||||
}
|
||||
|
||||
|
@ -161,6 +162,7 @@ import ScriptingBridge
|
|||
@objc optional func virtualMachines() -> SBElementArray
|
||||
@objc optional var autoTerminate: Bool { get } // Auto terminate the application when all windows are closed?
|
||||
@objc optional func setAutoTerminate(_ autoTerminate: Bool) // Auto terminate the application when all windows are closed?
|
||||
@objc optional func usbDevices() -> SBElementArray
|
||||
}
|
||||
extension SBApplication: UTMScriptingApplication {}
|
||||
|
||||
|
@ -204,6 +206,8 @@ extension SBObject: UTMScriptingWindow {}
|
|||
@objc optional func startSaving(_ saving: Bool) // Start a virtual machine or resume a suspended virtual machine.
|
||||
@objc optional func suspendSaving(_ saving: Bool) // Suspend a running virtual machine to memory.
|
||||
@objc optional func stopBy(_ by: UTMScriptingStopMethod) // Shuts down a running virtual machine.
|
||||
@objc optional func delete() // Delete a virtual machine. All data will be deleted, there is no confirmation!
|
||||
@objc optional func duplicateWithProperties(_ withProperties: [AnyHashable : Any]!) // Copy an virtual machine and all its data.
|
||||
@objc optional func openFileAt(_ at: String!, for for_: UTMScriptingOpenMode, updating: Bool) -> UTMScriptingGuestFile // Open a file on the guest. You must close the file when you are done to prevent leaking guest resources.
|
||||
@objc optional func executeAt(_ at: String!, withArguments: [String]!, withEnvironment: [String]!, usingInput: String!, base64Encoding: Bool, outputCapturing: Bool) -> UTMScriptingGuestProcess // Execute a command or script on the guest.
|
||||
@objc optional func queryIp() -> [Any] // Query the guest for all IP addresses on its network interfaces (excluding loopback).
|
||||
|
@ -211,6 +215,7 @@ extension SBObject: UTMScriptingWindow {}
|
|||
@objc optional func guestFiles() -> SBElementArray
|
||||
@objc optional func guestProcesses() -> SBElementArray
|
||||
@objc optional var configuration: Any { get } // The configuration of the virtual machine.
|
||||
@objc optional func usbDevices() -> SBElementArray
|
||||
}
|
||||
extension SBObject: UTMScriptingVirtualMachine {}
|
||||
|
||||
|
@ -241,3 +246,16 @@ extension SBObject: UTMScriptingGuestFile {}
|
|||
}
|
||||
extension SBObject: UTMScriptingGuestProcess {}
|
||||
|
||||
// MARK: UTMScriptingUsbDevice
|
||||
@objc public protocol UTMScriptingUsbDevice: SBObjectProtocol, UTMScriptingGenericMethods {
|
||||
@objc optional func id() -> Int // A unique identifier corrosponding to the USB bus and port number.
|
||||
@objc optional var name: String { get } // The name of the USB device.
|
||||
@objc optional var manufacturerName: String { get } // The product name described by the iManufacturer descriptor.
|
||||
@objc optional var productName: String { get } // The product name described by the iProduct descriptor.
|
||||
@objc optional var vendorId: Int { get } // The vendor ID described by the idVendor descriptor.
|
||||
@objc optional var productId: Int { get } // The product ID described by the idProduct descriptor.
|
||||
@objc optional func connectTo(_ to: UTMScriptingVirtualMachine!) // Connect a USB device to a running VM and remove it from the host.
|
||||
@objc optional func disconnect() // Disconnect a USB device from the guest and re-assign it to the host.
|
||||
}
|
||||
extension SBObject: UTMScriptingUsbDevice {}
|
||||
|
||||
|
|
|
@ -24,7 +24,20 @@ struct UTMCtl: ParsableCommand {
|
|||
static var configuration = CommandConfiguration(
|
||||
commandName: "utmctl",
|
||||
abstract: "CLI tool for controlling UTM virtual machines.",
|
||||
subcommands: [List.self, Status.self, Start.self, Suspend.self, Stop.self, Attach.self, File.self, Exec.self, IPAddress.self, Clone.self, Delete.self]
|
||||
subcommands: [
|
||||
List.self,
|
||||
Status.self,
|
||||
Start.self,
|
||||
Suspend.self,
|
||||
Stop.self,
|
||||
Attach.self,
|
||||
File.self,
|
||||
Exec.self,
|
||||
IPAddress.self,
|
||||
Clone.self,
|
||||
Delete.self,
|
||||
USB.self
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -123,11 +136,15 @@ extension UTMCtl {
|
|||
enum APIError: Error, LocalizedError {
|
||||
case applicationNotFound
|
||||
case virtualMachineNotFound
|
||||
case invalidIdentifier(String)
|
||||
case deviceNotFound
|
||||
|
||||
var errorDescription: String? {
|
||||
switch self {
|
||||
case .applicationNotFound: return "Application not found."
|
||||
case .virtualMachineNotFound: return "Virtual machine not found."
|
||||
case .invalidIdentifier(let identifier): return "Identifier '\(identifier)' is invalid."
|
||||
case .deviceNotFound: return "Device not found."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -505,6 +522,119 @@ extension UTMCtl {
|
|||
}
|
||||
}
|
||||
|
||||
extension UTMCtl {
|
||||
struct USB: ParsableCommand {
|
||||
static var configuration = CommandConfiguration(
|
||||
abstract: "USB device handling.",
|
||||
subcommands: [USBList.self, USBConnect.self, USBDisconnect.self]
|
||||
)
|
||||
|
||||
/// Find a USB device using an identifier
|
||||
/// - Parameters:
|
||||
/// - identifier: Either VID:PID or a location
|
||||
/// - application: Scripting application
|
||||
/// - Returns: USB device
|
||||
static func usbDevice(forIdentifier identifier: String, in application: UTMScriptingApplication) throws -> UTMScriptingUsbDevice {
|
||||
let parts = identifier.split(separator: ":")
|
||||
if parts.count == 2 {
|
||||
let vid = Int(parts[0], radix: 16)
|
||||
let pid = Int(parts[1], radix: 16)
|
||||
if let vid = vid, let pid = pid {
|
||||
return try usbDevice(forVid: vid, pid: pid, in: application)
|
||||
}
|
||||
}
|
||||
if let location = Int(identifier, radix: 10) {
|
||||
return try usbDevice(forLocation: location, in: application)
|
||||
}
|
||||
throw APIError.invalidIdentifier(identifier)
|
||||
}
|
||||
|
||||
static private func usbDevice(forVid vid: Int, pid: Int, in application: UTMScriptingApplication) throws -> UTMScriptingUsbDevice {
|
||||
if let list = application.usbDevices!() as? [UTMScriptingUsbDevice] {
|
||||
if let device = list.first(where: { $0.vendorId == vid && $0.productId == pid }) {
|
||||
return device
|
||||
}
|
||||
}
|
||||
throw APIError.deviceNotFound
|
||||
}
|
||||
|
||||
static private func usbDevice(forLocation location: Int, in application: UTMScriptingApplication) throws -> UTMScriptingUsbDevice {
|
||||
if let list = application.usbDevices!() as? [UTMScriptingUsbDevice] {
|
||||
if let device = list.first(where: { $0.id!() == location }) {
|
||||
return device
|
||||
}
|
||||
}
|
||||
throw APIError.deviceNotFound
|
||||
}
|
||||
}
|
||||
|
||||
struct USBList: UTMAPICommand {
|
||||
static var configuration = CommandConfiguration(
|
||||
commandName: "list",
|
||||
abstract: "List connected devices."
|
||||
)
|
||||
|
||||
@OptionGroup var environment: EnvironmentOptions
|
||||
|
||||
func run(with application: UTMScriptingApplication) throws {
|
||||
if let list = application.usbDevices!() as? [UTMScriptingUsbDevice] {
|
||||
printResponse(list)
|
||||
}
|
||||
}
|
||||
|
||||
func printResponse(_ response: [UTMScriptingUsbDevice]) {
|
||||
guard !response.isEmpty else {
|
||||
print("No devices found. Make sure a USB sharing enabled VM is running.")
|
||||
return
|
||||
}
|
||||
print("Name VID :PID Location")
|
||||
for entry in response {
|
||||
let name = entry.name!.padding(toLength: 32, withPad: " ", startingAt: 0)
|
||||
let vid = String(format: "%04X", entry.vendorId!)
|
||||
let pid = String(format: "%04X", entry.productId!)
|
||||
print("\(name) \(vid):\(pid) \(entry.id!())")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct USBConnect: UTMAPICommand {
|
||||
static var configuration = CommandConfiguration(
|
||||
commandName: "connect",
|
||||
abstract: "Connect a USB device to a virtual machine."
|
||||
)
|
||||
|
||||
@OptionGroup var environment: EnvironmentOptions
|
||||
|
||||
@OptionGroup var identifer: VMIdentifier
|
||||
|
||||
@Argument(help: "Device identifier either as a VID:PID pair (e.g. DEAD:BEEF) or a location (e.g. 4).")
|
||||
var device: String
|
||||
|
||||
func run(with application: UTMScriptingApplication) throws {
|
||||
let vm = try virtualMachine(forIdentifier: identifer, in: application)
|
||||
let device = try USB.usbDevice(forIdentifier: device, in: application)
|
||||
device.connectTo!(vm)
|
||||
}
|
||||
}
|
||||
|
||||
struct USBDisconnect: UTMAPICommand {
|
||||
static var configuration = CommandConfiguration(
|
||||
commandName: "disconnect",
|
||||
abstract: "Disconnect a USB device from a virtual machine."
|
||||
)
|
||||
|
||||
@OptionGroup var environment: EnvironmentOptions
|
||||
|
||||
@Argument(help: "Device identifier either as a VID:PID pair (e.g. DEAD:BEEF) or a location (e.g. 4).")
|
||||
var device: String
|
||||
|
||||
func run(with application: UTMScriptingApplication) throws {
|
||||
let device = try USB.usbDevice(forIdentifier: device, in: application)
|
||||
device.disconnect!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension UTMCtl {
|
||||
struct VMIdentifier: ParsableArguments {
|
||||
@Argument(help: "Either the UUID or the complete name of the virtual machine.")
|
||||
|
|
Loading…
Reference in a new issue