diff --git a/Platform/Shared/ContentView.swift b/Platform/Shared/ContentView.swift index 9781ca586..eb19f3362 100644 --- a/Platform/Shared/ContentView.swift +++ b/Platform/Shared/ContentView.swift @@ -154,67 +154,6 @@ struct ContentView: View { } } } - - @MainActor private func handleUTMURL(with components: URLComponents) async throws { - func findVM() -> VMData? { - if let vmName = components.queryItems?.first(where: { $0.name == "name" })?.value { - return data.virtualMachines.first(where: { $0.detailsTitleLabel == vmName }) - } else { - return nil - } - } - - if let action = components.host { - switch action { - case "start": - if let vm = findVM(), vm.state == .stopped { - data.run(vm: vm) - } - break - case "stop": - if let vm = findVM(), vm.state == .started { - try await vm.wrapped!.stop(usingMethod: .force) - data.stop(vm: vm) - } - break - case "restart": - if let vm = findVM(), vm.state == .started { - try await vm.wrapped!.restart() - } - break - case "pause": - if let vm = findVM(), vm.state == .started { - let shouldSaveOnPause: Bool - if let vm = vm.wrapped as? (any UTMSpiceVirtualMachine) { - shouldSaveOnPause = !vm.isRunningAsDisposible - } else { - shouldSaveOnPause = true - } - try await vm.wrapped!.pause() - if shouldSaveOnPause { - try? await vm.wrapped!.saveSnapshot(name: nil) - } - } - case "resume": - if let vm = findVM(), vm.state == .paused { - try await vm.wrapped!.resume() - } - break - case "sendText": - if let vm = findVM(), vm.state == .started { - data.automationSendText(to: vm, urlComponents: components) - } - break - case "click": - if let vm = findVM(), vm.state == .started { - data.automationSendMouse(to: vm, urlComponents: components) - } - break - default: - return - } - } - } } extension ContentView: DropDelegate { diff --git a/Platform/UTMData.swift b/Platform/UTMData.swift index b1367b03a..66ee559d1 100644 --- a/Platform/UTMData.swift +++ b/Platform/UTMData.swift @@ -901,69 +901,6 @@ struct AlertMessage: Identifiable { } } } - - // MARK: - Automation Features - - /// Send text as keyboard input to VM - /// - Parameters: - /// - vm: VM to send text to - /// - components: Data (see UTM Wiki for details) - func automationSendText(to vm: VMData, urlComponents components: URLComponents) { - guard let queryItems = components.queryItems else { return } - guard let text = queryItems.first(where: { $0.name == "text" })?.value else { return } - #if os(macOS) - trySendTextSpice(vm: vm, text: text) - #else - trySendTextSpice(text) - #endif - } - - /// Send mouse/tablet coordinates to VM - /// - Parameters: - /// - vm: VM to send mouse/tablet coordinates to - /// - components: Data (see UTM Wiki for details) - func automationSendMouse(to vm: VMData, urlComponents components: URLComponents) { - guard let qemuVm = vm.wrapped as? any UTMSpiceVirtualMachine else { return } // FIXME: implement for Apple VM - guard !qemuVm.config.displays.isEmpty else { return } - guard let queryItems = components.queryItems else { return } - /// Parse targeted position - var x: CGFloat? = nil - var y: CGFloat? = nil - let nf = NumberFormatter() - nf.allowsFloats = false - if let xStr = components.queryItems?.first(where: { item in - item.name == "x" - })?.value { - x = nf.number(from: xStr) as? CGFloat - } - if let yStr = components.queryItems?.first(where: { item in - item.name == "y" - })?.value { - y = nf.number(from: yStr) as? CGFloat - } - guard let xPos = x, let yPos = y else { return } - let point = CGPoint(x: xPos, y: yPos) - /// Parse which button should be clicked - var button: CSInputButton = .left - if let buttonStr = queryItems.first(where: { $0.name == "button"})?.value { - switch buttonStr { - case "middle": - button = .middle - break - case "right": - button = .right - break - default: - break - } - } - /// All parameters parsed, perform the click - #if os(macOS) - tryClickAtPoint(vm: vm, point: point, button: button) - #else - tryClickAtPoint(point: point, button: button) - #endif - } // MARK: - AltKit diff --git a/Platform/iOS/UTMDataExtension.swift b/Platform/iOS/UTMDataExtension.swift index 3c109a489..8e8ef1442 100644 --- a/Platform/iOS/UTMDataExtension.swift +++ b/Platform/iOS/UTMDataExtension.swift @@ -51,23 +51,4 @@ extension UTMData { func close(vm: VMData) { // do nothing } - - func tryClickAtPoint(point: CGPoint, button: CSInputButton) { - if let vc = vmVC as? VMDisplayMetalViewController, let input = vc.vmInput { - input.sendMouseButton(button, pressed: true) - DispatchQueue.main.asyncAfter(deadline: .now() + 0.02) { - input.sendMouseButton(button, pressed: false) - } - } - } - - func trySendTextSpice(_ text: String) { - if let vc = vmVC as? VMDisplayMetalViewController { - #if !os(visionOS) // FIXME: broken in visionOS - vc.keyboardView.insertText(text) - #endif - } else if let vc = vmVC as? VMDisplayTerminalViewController { - vc.vmSerialPort.write(text.data(using: .nonLossyASCII)!) - } - } } diff --git a/Platform/macOS/UTMDataExtension.swift b/Platform/macOS/UTMDataExtension.swift index 3acdb861e..c32d7d5e1 100644 --- a/Platform/macOS/UTMDataExtension.swift +++ b/Platform/macOS/UTMDataExtension.swift @@ -129,111 +129,4 @@ extension UTMData { } } } - - func trySendTextSpice(vm: VMData, text: String) { - guard text.count > 0 else { return } - if let vc = vmWindows[vm] as? VMDisplayQemuMetalWindowController { - KeyCodeMap.createKeyMapIfNeeded() - - func sleep() { - Thread.sleep(forTimeInterval: 0.05) - } - func keyDown(keyCode: Int) { - if let scanCodes = KeyCodeMap.keyCodeToScanCodes[keyCode] { - vc.keyDown(scanCode: Int(scanCodes.down)) - sleep() - } - } - func keyUp(keyCode: Int) { - /// Due to how Spice works we need to send keyUp for the .down scan code - /// instead of sending the key down for the scan code that indicates key up. - if let scanCodes = KeyCodeMap.keyCodeToScanCodes[keyCode] { - vc.keyUp(scanCode: Int(scanCodes.down)) - sleep() - } - } - func press(keyCode: Int) { - keyDown(keyCode: keyCode) - keyUp(keyCode: keyCode) - } - - func simulateKeyPress(_ keyCodeDict: [String: Int]) { - /// Press modifier keys if necessary - let optionUsed = keyCodeDict["option"] == 1 - if optionUsed { - keyDown(keyCode: kVK_Option) - sleep() - } - let shiftUsed = keyCodeDict["shift"] == 1 - if shiftUsed { - keyDown(keyCode: kVK_Shift) - sleep() - } - let fnUsed = keyCodeDict["function"] == 1 - if fnUsed { - keyDown(keyCode: kVK_Function) - sleep() - } - let ctrlUsed = keyCodeDict["control"] == 1 - if ctrlUsed { - keyDown(keyCode: kVK_Control) - sleep() - } - let cmdUsed = keyCodeDict["command"] == 1 - if cmdUsed { - keyDown(keyCode: kVK_Command) - sleep() - } - /// Press the key now - let keyCode = keyCodeDict["virtKeyCode"]! - press(keyCode: keyCode) - /// Release modifiers - if optionUsed { - keyUp(keyCode: kVK_Option) - sleep() - } - if shiftUsed { - keyUp(keyCode: kVK_Shift) - sleep() - } - if fnUsed { - keyUp(keyCode: kVK_Function) - sleep() - } - if ctrlUsed { - keyUp(keyCode: kVK_Control) - sleep() - } - if cmdUsed { - keyUp(keyCode: kVK_Command) - sleep() - } - } - DispatchQueue.global(qos: .userInitiated).async { - text.enumerated().forEach { stringItem in - let char = stringItem.element - /// drop unknown chars - if let keyCodeDict = KeyCodeMap.characterToKeyCode(character: char) { - simulateKeyPress(keyCodeDict) - } else { - logger.warning("SendText dropping unknown char: \(char)") - } - } - } - } else if let terminal = vmWindows[vm] as? VMDisplayTerminal { - terminal.sendString(text) - } - } - - func tryClickAtPoint(vm: VMData, point: CGPoint, button: CSInputButton) { - if let vc = vmWindows[vm] as? VMDisplayQemuMetalWindowController { - vc.mouseMove(absolutePoint: point, button: []) - DispatchQueue.main.asyncAfter(deadline: .now() + 0.02) { - vc.mouseDown(button: button) - DispatchQueue.main.asyncAfter(deadline: .now() + 0.02) { - vc.mouseUp(button: button) - } - } - } - } }