2
0
Эх сурвалжийг харах

intents: add App Intents for automation

Initially support functionality removed from the URL scheme (#6155)

Resolves #6245
osy 1 сар өмнө
parent
commit
4cfb9377dc

+ 208 - 0
Intents/UTMActionIntent.swift

@@ -0,0 +1,208 @@
+//
+// Copyright © 2025 osy. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+import AppIntents
+
+@available(iOS 16, macOS 13, tvOS 16, watchOS 9, *)
+struct UTMStatusActionIntent: UTMIntent {
+    static let title: LocalizedStringResource = "Get Virtual Machine Status"
+    static let description = IntentDescription("Get the status of a virtual machine.")
+    static var parameterSummary: some ParameterSummary {
+        Summary("Get \(\.$vmEntity) status")
+    }
+
+    @Dependency
+    var data: UTMData
+
+    @Parameter(title: "Virtual Machine", requestValueDialog: "Select a virtual machine")
+    var vmEntity: UTMVirtualMachineEntity
+
+    @MainActor
+    func perform(with vm: any UTMVirtualMachine, boxed: VMData) async throws -> some ReturnsValue<UTMVirtualMachineState> {
+        return .result(value: vm.state)
+    }
+}
+
+@available(iOS 16, macOS 13, tvOS 16, watchOS 9, *)
+struct UTMStartActionIntent: UTMIntent {
+    static let title: LocalizedStringResource = "Start Virtual Machine"
+    static let description = IntentDescription("Start a virtual machine.")
+    static var parameterSummary: some ParameterSummary {
+        Summary("Start \(\.$vmEntity)") {
+            \.$isRecovery
+            \.$isDisposible
+        }
+    }
+
+    @Dependency
+    var data: UTMData
+
+    @Parameter(title: "Virtual Machine", requestValueDialog: "Select a virtual machine")
+    var vmEntity: UTMVirtualMachineEntity
+
+    @Parameter(title: "Recovery", description: "Boot into recovery mode. Only supported on Apple Virtualization backend.", default: false)
+    var isRecovery: Bool
+
+    @Parameter(title: "Disposible", description: "Do not save any changes to disk. Only supported on QEMU backend.", default: false)
+    var isDisposible: Bool
+
+    @MainActor
+    func perform(with vm: any UTMVirtualMachine, boxed: VMData) async throws -> some IntentResult {
+        var options = UTMVirtualMachineStartOptions()
+        if isRecovery {
+            #if os(macOS)
+            guard vm is UTMAppleVirtualMachine else {
+                throw UTMIntentError.unsupportedBackend
+            }
+            options.insert(.bootRecovery)
+            #else
+            throw UTMIntentError.unsupportedBackend
+            #endif
+        }
+        if isDisposible {
+            guard vm is UTMQemuVirtualMachine else {
+                throw UTMIntentError.unsupportedBackend
+            }
+            options.insert(.bootDisposibleMode)
+        }
+        data.run(vm: boxed, options: options)
+        if !vm.isHeadless {
+            if #available(iOS 26, macOS 26, tvOS 26, watchOS 26, visionOS 26, *), systemContext.currentMode.canContinueInForeground {
+                try await continueInForeground(alwaysConfirm: false)
+            }
+        }
+        return .result()
+    }
+}
+
+@available(iOS 16, macOS 13, tvOS 16, watchOS 9, *)
+struct UTMStopActionIntent: UTMIntent {
+    static let title: LocalizedStringResource = "Stop Virtual Machine"
+    static let description = IntentDescription("Stop a virtual machine.")
+    static var parameterSummary: some ParameterSummary {
+        Summary("Stop \(\.$vmEntity) by \(\.$method)")
+    }
+
+    @Dependency
+    var data: UTMData
+
+    @Parameter(title: "Virtual Machine", requestValueDialog: "Select a virtual machine")
+    var vmEntity: UTMVirtualMachineEntity
+
+    @Parameter(title: "Stop Method", description: "Intensity of the stop action.", default: .force)
+    var method: UTMVirtualMachineStopMethod
+
+    @MainActor
+    func perform(with vm: any UTMVirtualMachine, boxed: VMData) async throws -> some IntentResult {
+        try await vm.stop(usingMethod: method)
+        return .result()
+    }
+}
+
+@available(iOS 16, macOS 13, tvOS 16, watchOS 9, *)
+extension UTMVirtualMachineStopMethod: AppEnum {
+    static let typeDisplayRepresentation: TypeDisplayRepresentation =
+        TypeDisplayRepresentation(
+            name: "Stop Method"
+        )
+
+    static let caseDisplayRepresentations: [UTMVirtualMachineStopMethod: DisplayRepresentation] = [
+        .request: DisplayRepresentation(title: "Request", subtitle: "Sends power down request to the guest. This simulates pressing the power button on a PC."),
+        .force: DisplayRepresentation(title: "Force", subtitle: "Tells the VM process to shut down with risk of data corruption. This simulates holding down the power button on a PC."),
+        .kill: DisplayRepresentation(title: "Killing", subtitle: "Force kill the VM process with high risk of data corruption."),
+    ]
+}
+
+@available(iOS 16, macOS 13, tvOS 16, watchOS 9, *)
+struct UTMPauseActionIntent: UTMIntent {
+    static let title: LocalizedStringResource = "Pause Virtual Machine"
+    static let description = IntentDescription("Pause a virtual machine.")
+    static var parameterSummary: some ParameterSummary {
+        Summary("Pause \(\.$vmEntity)") {
+            \.$isSaveState
+        }
+    }
+
+    @Dependency
+    var data: UTMData
+
+    @Parameter(title: "Virtual Machine", requestValueDialog: "Select a virtual machine")
+    var vmEntity: UTMVirtualMachineEntity
+
+    @Parameter(title: "Save State", description: "Create a snapshot of the virtual machine state.", default: false)
+    var isSaveState: Bool
+
+    @MainActor
+    func perform(with vm: any UTMVirtualMachine, boxed: VMData) async throws -> some IntentResult {
+        try await vm.pause()
+        if isSaveState {
+            try await vm.save()
+        }
+        return .result()
+    }
+}
+
+@available(iOS 16, macOS 13, tvOS 16, watchOS 9, *)
+struct UTMResumeActionIntent: UTMIntent {
+    static let title: LocalizedStringResource = "Resume Virtual Machine"
+    static let description = IntentDescription("Resume a virtual machine.")
+    static var parameterSummary: some ParameterSummary {
+        Summary("Resume \(\.$vmEntity)")
+    }
+
+    @Dependency
+    var data: UTMData
+
+    @Parameter(title: "Virtual Machine", requestValueDialog: "Select a virtual machine")
+    var vmEntity: UTMVirtualMachineEntity
+
+    @MainActor
+    func perform(with vm: any UTMVirtualMachine, boxed: VMData) async throws -> some IntentResult {
+        try await vm.resume()
+        if vm.isHeadless {
+            if #available(iOS 26, macOS 26, tvOS 26, watchOS 26, visionOS 26, *), systemContext.currentMode.canContinueInForeground {
+                try await continueInForeground(alwaysConfirm: false)
+            }
+        }
+        return .result()
+    }
+}
+
+@available(iOS 16, macOS 13, tvOS 16, watchOS 9, *)
+struct UTMRestartActionIntent: UTMIntent {
+    static let title: LocalizedStringResource = "Restart Virtual Machine"
+    static let description = IntentDescription("Restart a virtual machine.")
+    static var parameterSummary: some ParameterSummary {
+        Summary("Restart \(\.$vmEntity)")
+    }
+
+    @Dependency
+    var data: UTMData
+
+    @Parameter(title: "Virtual Machine", requestValueDialog: "Select a virtual machine")
+    var vmEntity: UTMVirtualMachineEntity
+
+    @MainActor
+    func perform(with vm: any UTMVirtualMachine, boxed: VMData) async throws -> some IntentResult {
+        try await vm.restart()
+        if vm.isHeadless {
+            if #available(iOS 26, macOS 26, tvOS 26, watchOS 26, visionOS 26, *), systemContext.currentMode.canContinueInForeground {
+                try await continueInForeground(alwaysConfirm: false)
+            }
+        }
+        return .result()
+    }
+}

+ 134 - 0
Intents/UTMInputIntent.swift

@@ -0,0 +1,134 @@
+//
+// Copyright © 2025 osy. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+import AppIntents
+
+private let kDelayNs: UInt64 = 20000000
+
+@available(iOS 16, macOS 13, tvOS 16, watchOS 9, *)
+struct UTMSendScanCodeIntent: UTMIntent {
+    static let title: LocalizedStringResource = "Send Scan Code"
+    static let description = IntentDescription("Send a sequence of raw keyboard scan codes to the virtual machine. Only supported on QEMU backend.")
+    static var parameterSummary: some ParameterSummary {
+        Summary("Send scan code to \(\.$vmEntity)") {
+            \.$scanCodes
+        }
+    }
+
+    @Dependency
+    var data: UTMData
+
+    @Parameter(title: "Virtual Machine", requestValueDialog: "Select a virtual machine")
+    var vmEntity: UTMVirtualMachineEntity
+
+    @Parameter(title: "Scan Code", description: "List of PC AT scan codes in decimal (0-65535 inclusive).", controlStyle: .field, inclusiveRange: (0, 0xFFFF))
+    var scanCodes: [Int]
+
+    @MainActor
+    func perform(with vm: any UTMVirtualMachine, boxed: VMData) async throws -> some IntentResult {
+        guard let vm = vm as? any UTMSpiceVirtualMachine else {
+            throw UTMIntentError.unsupportedBackend
+        }
+        guard let input = vm.ioService?.primaryInput else {
+            throw UTMIntentError.inputHandlerNotAvailable
+        }
+        for scanCode in scanCodes {
+            var _scanCode = scanCode
+            if (_scanCode & 0xFF00) == 0xE000 {
+                _scanCode = 0x100 | (_scanCode & 0xFF)
+            }
+            if (_scanCode & 0x80) == 0x80 {
+                input.send(.release, code: Int32(_scanCode & 0x17F))
+            } else {
+                input.send(.press, code: Int32(_scanCode))
+            }
+            try await Task.sleep(nanoseconds: kDelayNs)
+        }
+        return .result()
+    }
+}
+
+@available(iOS 16, macOS 13, tvOS 16, watchOS 9, *)
+struct UTMMouseClickIntent: UTMIntent {
+    static let title: LocalizedStringResource = "Send Mouse Click"
+    static let description = IntentDescription("Send a mouse position and click to the virtual machine. Only supported on QEMU backend.")
+    static var parameterSummary: some ParameterSummary {
+        Summary("Send mouse click at (\(\.$xPosition), \(\.$yPosition)) to \(\.$vmEntity)") {
+            \.$mouseButton
+            \.$monitorNumber
+        }
+    }
+
+    enum MouseButton: Int, CaseIterable, AppEnum {
+        case left
+        case right
+        case middle
+
+        static let typeDisplayRepresentation: TypeDisplayRepresentation =
+            TypeDisplayRepresentation(
+                name: "Mouse Button"
+            )
+
+        static let caseDisplayRepresentations: [MouseButton: DisplayRepresentation] = [
+            .left: DisplayRepresentation(title: "Left"),
+            .right: DisplayRepresentation(title: "Right"),
+            .middle: DisplayRepresentation(title: "Middle"),
+        ]
+
+        func toSpiceButton() -> CSInputButton {
+            switch self {
+            case .left: return .left
+            case .right: return .right
+            case .middle: return .middle
+            }
+        }
+    }
+
+    @Dependency
+    var data: UTMData
+
+    @Parameter(title: "Virtual Machine", requestValueDialog: "Select a virtual machine")
+    var vmEntity: UTMVirtualMachineEntity
+
+    @Parameter(title: "X Position", description: "X coordinate of the absolute position.", default: 0, controlStyle: .field)
+    var xPosition: Int
+
+    @Parameter(title: "Y Position", description: "Y coordinate of the absolute position.", default: 0, controlStyle: .field)
+    var yPosition: Int
+
+    @Parameter(title: "Mouse Button", description: "Mouse button to click.", default: .left)
+    var mouseButton: MouseButton
+
+    @Parameter(title: "Monitor Number", description: "Which monitor to target (starting at 1).", default: 1, controlStyle: .stepper)
+    var monitorNumber: Int
+
+    @MainActor
+    func perform(with vm: any UTMVirtualMachine, boxed: VMData) async throws -> some IntentResult {
+        guard let vm = vm as? UTMQemuVirtualMachine else {
+            throw UTMIntentError.unsupportedBackend
+        }
+        guard let input = vm.ioService?.primaryInput else {
+            throw UTMIntentError.inputHandlerNotAvailable
+        }
+        try await vm.changeInputTablet(true)
+        input.sendMousePosition(mouseButton.toSpiceButton(), absolutePoint: CGPoint(x: xPosition, y: yPosition), forMonitorID: monitorNumber-1)
+        try await Task.sleep(nanoseconds: kDelayNs)
+        input.sendMouseButton(mouseButton.toSpiceButton(), pressed: true)
+        try await Task.sleep(nanoseconds: kDelayNs)
+        input.sendMouseButton(mouseButton.toSpiceButton(), pressed: false)
+        return .result()
+    }
+}

+ 73 - 0
Intents/UTMIntent.swift

@@ -0,0 +1,73 @@
+//
+// Copyright © 2025 osy. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+import AppIntents
+
+@available(iOS 16, macOS 13, tvOS 16, watchOS 9, *)
+protocol UTMIntent: AppIntent {
+    associatedtype T : IntentResult
+
+    var data: UTMData { get }
+    var vmEntity: UTMVirtualMachineEntity { get }
+    func perform(with vm: any UTMVirtualMachine, boxed: VMData) async throws -> T
+}
+
+@available(iOS 16, macOS 13, tvOS 16, watchOS 9, *)
+@available(*, deprecated)
+extension UTMIntent {
+    static var openAppWhenRun: Bool { true }
+}
+
+@available(iOS 26, macOS 26, tvOS 26, watchOS 26, visionOS 26, *)
+extension UTMIntent {
+    static var supportedModes: IntentModes { [.background, .foreground(.dynamic)] }
+}
+
+@available(iOS 16, macOS 13, tvOS 16, watchOS 9, *)
+extension UTMIntent {
+    @MainActor
+    func perform() async throws -> T {
+        guard let vm = data.virtualMachines.first(where: { $0.id == vmEntity.id }), vm.isLoaded else {
+            throw UTMIntentError.virtualMachineNotFound
+        }
+        do {
+            return try await perform(with: vm.wrapped!, boxed: vm)
+        } catch {
+            throw UTMIntentError.localizedError(error)
+        }
+    }
+}
+
+@available(iOS 16, macOS 13, tvOS 16, watchOS 9, *)
+enum UTMIntentError: Error, CustomLocalizedStringResourceConvertible {
+    case localizedError(Error)
+    case virtualMachineNotFound
+    case unsupportedBackend
+    case inputHandlerNotAvailable
+
+    var localizedStringResource: LocalizedStringResource {
+        switch self {
+        case .localizedError(let wrapped):
+            return "\(wrapped.localizedDescription)"
+        case .virtualMachineNotFound:
+            return "Virtual machine not found."
+        case .unsupportedBackend:
+            return "Operation not supported by the backend."
+        case .inputHandlerNotAvailable:
+            return "Input handler not available."
+        }
+    }
+}

+ 87 - 0
Intents/UTMVirtualMachineEntity.swift

@@ -0,0 +1,87 @@
+//
+// Copyright © 2025 osy. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+import AppIntents
+
+@available(iOS 16, macOS 13, tvOS 16, watchOS 9, *)
+struct UTMVirtualMachineEntity: AppEntity {
+    static let defaultQuery = UTMVirtualMachineEntityQuery()
+
+    let id: UUID
+
+    var iconURL: URL?
+
+    @Property(title: "Name")
+    var name: String
+
+    @Property(title: "Description")
+    var description: String
+
+    @Property(title: "Status")
+    var state: UTMVirtualMachineState
+
+    static var typeDisplayRepresentation: TypeDisplayRepresentation {
+        TypeDisplayRepresentation(
+            name: "Virtual Machine",
+            numericFormat: "\(placeholder: .int) virtual machines"
+        )
+    }
+
+    var displayRepresentation: DisplayRepresentation {
+        var display = DisplayRepresentation(
+            title: "\(name)",
+            subtitle: "\(description)"
+        )
+        if let iconURL = iconURL {
+            display.image = DisplayRepresentation.Image(url: iconURL)
+        }
+        return display
+    }
+
+    @MainActor
+    init(from vm: VMData) {
+        id = vm.id
+        name = vm.detailsTitleLabel
+        description = vm.detailsSubtitleLabel
+        state = vm.state
+        iconURL = vm.detailsIconUrl
+    }
+}
+
+@available(iOS 18, macOS 15, *)
+extension UTMVirtualMachineEntity: IndexedEntity {
+    
+}
+
+@available(iOS 16, macOS 13, tvOS 16, watchOS 9, *)
+extension UTMVirtualMachineState: AppEnum {
+    static let typeDisplayRepresentation: TypeDisplayRepresentation =
+        TypeDisplayRepresentation(
+            name: "Status"
+        )
+
+    static let caseDisplayRepresentations: [UTMVirtualMachineState: DisplayRepresentation] = [
+        .stopped: DisplayRepresentation(title: "Stopped"),
+        .starting: DisplayRepresentation(title: "Starting"),
+        .started: DisplayRepresentation(title: "Started"),
+        .pausing: DisplayRepresentation(title: "Pausing"),
+        .paused: DisplayRepresentation(title: "Paused"),
+        .resuming: DisplayRepresentation(title: "Resuming"),
+        .saving: DisplayRepresentation(title: "Saving"),
+        .restoring: DisplayRepresentation(title: "Restoring"),
+        .stopping: DisplayRepresentation(title: "Stopping"),
+    ]
+}

+ 184 - 0
Intents/UTMVirtualMachineEntityQuery.swift

@@ -0,0 +1,184 @@
+//
+// Copyright © 2025 osy. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+import AppIntents
+
+@available(iOS 16, macOS 13, tvOS 16, watchOS 9, *)
+struct UTMVirtualMachineEntityQuery: EntityQuery, EntityStringQuery {
+    @Dependency
+    var data: UTMData
+
+    func entities(for identifiers: [UUID]) async throws -> [UTMVirtualMachineEntity] {
+        await MainActor.run {
+            data
+                .virtualMachines
+                .filter({ $0.isLoaded && identifiers.contains($0.id) })
+                .map({ UTMVirtualMachineEntity(from: $0) })
+        }
+    }
+
+    func entities(matching: String) async throws -> [UTMVirtualMachineEntity] {
+        await MainActor.run {
+            data
+                .virtualMachines
+                .filter({ $0.isLoaded && $0.detailsTitleLabel.localizedCaseInsensitiveContains(matching) })
+                .map({ UTMVirtualMachineEntity(from: $0) })
+        }
+    }
+
+    func suggestedEntities() async throws -> [UTMVirtualMachineEntity] {
+        await MainActor.run {
+            data
+                .virtualMachines
+                .filter({ $0.isLoaded })
+                .map({ UTMVirtualMachineEntity(from: $0) })
+        }
+    }
+}
+
+@available(iOS 17, macOS 14, tvOS 17, watchOS 10, *)
+extension UTMVirtualMachineEntityQuery: EntityPropertyQuery {
+
+    /**
+     The type of the comparator to use for the property query. This sample uses `Predicate`, but other apps could use `NSPredicate` (for
+     Core Data) or an entirely custom comparator that works with an existing data model.
+     */
+    typealias ComparatorMappingType = Predicate<UTMVirtualMachineEntity>
+
+    /**
+     Declare the entity properties that are available for queries and in the Find intent, along with the comparator the app uses when querying the
+     property.
+     */
+    static let properties = QueryProperties {
+        Property(\UTMVirtualMachineEntity.$name) {
+            ContainsComparator { searchValue in
+                #Predicate<UTMVirtualMachineEntity> { $0.name.localizedStandardContains(searchValue) }
+            }
+            EqualToComparator { searchValue in
+                #Predicate<UTMVirtualMachineEntity> { $0.name == searchValue }
+            }
+            NotEqualToComparator { searchValue in
+                #Predicate<UTMVirtualMachineEntity> { $0.name != searchValue }
+            }
+        }
+        Property(\UTMVirtualMachineEntity.$state) {
+            EqualToComparator { searchValue in
+                #Predicate<UTMVirtualMachineEntity> { $0.state == searchValue }
+            }
+            NotEqualToComparator { searchValue in
+                #Predicate<UTMVirtualMachineEntity> { $0.state != searchValue }
+            }
+        }
+    }
+
+    /// Declare the entity properties available as sort criteria in the Find intent.
+    static let sortingOptions = SortingOptions {
+        SortableBy(\UTMVirtualMachineEntity.$name)
+    }
+
+    /// The text that people see in the Shortcuts app, describing what this intent does.
+    static var findIntentDescription: IntentDescription? {
+        IntentDescription("Search for a virtual machine.",
+                          searchKeywords: ["virtual machine", "vm"],
+                          resultValueName: "Virtual Machines")
+    }
+
+    /// Performs the Find intent using the predicates that the individual enters in the Shortcuts app.
+    func entities(matching comparators: [Predicate<UTMVirtualMachineEntity>],
+                  mode: ComparatorMode,
+                  sortedBy: [EntityQuerySort<UTMVirtualMachineEntity>],
+                  limit: Int?) async throws -> [UTMVirtualMachineEntity] {
+
+        logger.debug("[UTMVirtualMachineEntityQuery] Property query started")
+
+        /// Get the trail entities that meet the criteria of the comparators.
+        var matchedVms = try await virtualMachines(matching: comparators, mode: mode)
+
+        /**
+         Apply the requested sort. `EntityQuerySort` specifies the value to sort by using a `PartialKeyPath`. This key path builds a
+         `KeyPathComparator` to use default sorting implementations for the value that the key path provides. For example, this approach uses
+         `SortComparator.localizedStandard` when sorting key paths with a `String` value.
+         */
+        logger.debug("[UTMVirtualMachineEntityQuery] Sorting results")
+        for sortOperation in sortedBy {
+            switch sortOperation.by {
+            case \.$name:
+                matchedVms.sort(using: KeyPathComparator(\UTMVirtualMachineEntity.name, order: sortOperation.order.sortOrder))
+            default:
+                break
+            }
+        }
+
+        /**
+         People can optionally customize a limit to the number of results that a query returns.
+         If your data model supports query limits, you can also use the limit parameter when querying
+         your data model, to allow for faster searches.
+         */
+        if let limit, matchedVms.count > limit {
+            logger.debug("[UTMVirtualMachineEntityQuery] Limiting results to \(limit)")
+            matchedVms.removeLast(matchedVms.count - limit)
+        }
+
+        logger.debug("[UTMVirtualMachineEntityQuery] Property query complete")
+        return matchedVms
+    }
+
+    /// - Returns: The trail entities that meet the criteria of `comparators` and `mode`.
+    @MainActor
+    private func virtualMachines(matching comparators: [Predicate<UTMVirtualMachineEntity>], mode: ComparatorMode) throws -> [UTMVirtualMachineEntity] {
+        try data.virtualMachines.compactMap { vm in
+            let entity = UTMVirtualMachineEntity(from: vm)
+
+            /**
+             For an AND search (criteria1 AND criteria2 AND ...), this variable starts as `true`.
+             If any of the comparators don't match, the app sets it to `false`, allowing the comparator loop to break early because a comparator
+             doesn't satisfy the AND requirement.
+
+             For an OR search (criteria1 OR criteria2 OR ...), this variable starts as `false`.
+             If any of the comparators match, the app sets it to `true`, allowing the comparator loop to break early because any comparator that
+             matches satisfies the OR requirement.
+             */
+            var includeAsResult = mode == .and ? true : false
+            let earlyBreakCondition = includeAsResult
+            logger.debug("[UTMVirtualMachineEntityQuery] Starting to evaluate predicates for \(entity.name)")
+            for comparator in comparators {
+                guard includeAsResult == earlyBreakCondition else {
+                    logger.debug("[UTMVirtualMachineEntityQuery] Predicates matched? \(includeAsResult)")
+                    break
+                }
+
+                /// Runs the `Predicate` expression with the specific `TrailEntity` to determine whether the entity matches the conditions.
+                includeAsResult = try comparator.evaluate(entity)
+            }
+
+            logger.debug("[UTMVirtualMachineEntityQuery] Predicates matched? \(includeAsResult)")
+            return includeAsResult ? entity : nil
+        }
+    }
+}
+
+@available(iOS 16, macOS 13, tvOS 16, watchOS 9, *)
+private extension EntityQuerySort.Ordering {
+    /// Convert sort information from `EntityQuerySort` to  Foundation's `SortOrder`.
+    var sortOrder: SortOrder {
+        switch self {
+        case .ascending:
+            return SortOrder.forward
+        case .descending:
+            return SortOrder.reverse
+        }
+    }
+}

+ 20 - 1
Platform/iOS/UTMApp.swift

@@ -15,11 +15,30 @@
 //
 
 import SwiftUI
+import AppIntents
 
 struct UTMApp: App {
+    #if WITH_REMOTE
+    private let data: UTMRemoteData
+    #else
+    private let data: UTMData
+    #endif
+
+    init() {
+        #if WITH_REMOTE
+        let data = UTMRemoteData()
+        #else
+        let data = UTMData()
+        #endif
+        self.data = data
+        if #available(iOS 16, *) {
+            AppDependencyManager.shared.add(dependency: data)
+        }
+    }
+
     var body: some Scene {
         WindowGroup {
-            UTMSingleWindowView()
+            UTMSingleWindowView(data: data)
         }.commands {
             VMCommands()
         }

+ 1 - 1
Platform/iOS/UTMExternalSceneDelegate.swift

@@ -25,7 +25,7 @@ class UTMExternalSceneDelegate: NSObject, UIWindowSceneDelegate, ObservableObjec
         
         if session.role == .windowExternalDisplay {
             let window = UIWindow(windowScene: windowScene)
-            let viewController = UIHostingController(rootView: UTMSingleWindowView(isInteractive: false))
+            let viewController = UIHostingController(rootView: UTMSingleWindowView())
             window.rootViewController = viewController
             self.window = window
             window.isHidden = false

+ 10 - 7
Platform/iOS/UTMSingleWindowView.swift

@@ -18,21 +18,24 @@ import SwiftUI
 
 @MainActor
 struct UTMSingleWindowView: View {
-    let isInteractive: Bool
+    private var isInteractive: Bool {
+        data != nil
+    }
 
     #if WITH_REMOTE
-    @State private var data: UTMRemoteData = UTMRemoteData()
+    typealias DataType = UTMRemoteData
     #else
-    @State private var data: UTMData = UTMData()
+    typealias DataType = UTMData
     #endif
+    private let data: DataType?
     @State private var session: VMSessionState?
     @State private var identifier: VMSessionState.WindowID?
 
     private let vmSessionCreatedNotification = NotificationCenter.default.publisher(for: .vmSessionCreated)
     private let vmSessionEndedNotification = NotificationCenter.default.publisher(for: .vmSessionEnded)
     
-    init(isInteractive: Bool = true) {
-        self.isInteractive = isInteractive
+    init(data: DataType? = nil) {
+        self.data = data
     }
     
     var body: some View {
@@ -43,7 +46,7 @@ struct UTMSingleWindowView: View {
                 #if WITH_REMOTE
                 RemoteContentView(remoteClientState: data.remoteClient.state).environmentObject(data)
                 #else
-                ContentView().environmentObject(data)
+                ContentView().environmentObject(data!)
                 #endif
             } else {
                 VStack {
@@ -79,6 +82,6 @@ struct UTMSingleWindowView: View {
 
 struct UTMSingleWindowView_Previews: PreviewProvider {
     static var previews: some View {
-        UTMSingleWindowView()
+        UTMSingleWindowView(data: UTMSingleWindowView.DataType())
     }
 }

+ 11 - 2
Platform/macOS/UTMApp.swift

@@ -15,11 +15,20 @@
 //
 
 import SwiftUI
+import AppIntents
 
 struct UTMApp: App {
-    @State var data = UTMData()
+    let data: UTMData
     @NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate: AppDelegate
-    
+
+    init() {
+        let data = UTMData()
+        self.data = data
+        if #available(macOS 13, *) {
+            AppDependencyManager.shared.add(dependency: data)
+        }
+    }
+
     @ViewBuilder
     var homeWindow: some View {
         ContentView().environmentObject(data)

+ 10 - 2
Platform/visionOS/UTMApp.swift

@@ -16,20 +16,28 @@
 
 import SwiftUI
 import VisionKeyboardKit
+import AppIntents
 
 @MainActor
 struct UTMApp: App {
     #if WITH_REMOTE
-    @State private var data: UTMRemoteData = UTMRemoteData()
+    private typealias DataType = UTMRemoteData
     #else
-    @State private var data: UTMData = UTMData()
+    private typealias DataType = UTMData
     #endif
+    private let data: DataType
     @Environment(\.openWindow) private var openWindow
     @Environment(\.dismissWindow) private var dismissWindow
 
     private let vmSessionCreatedNotification = NotificationCenter.default.publisher(for: .vmSessionCreated)
     private let vmSessionEndedNotification = NotificationCenter.default.publisher(for: .vmSessionEnded)
 
+    init() {
+        let data = DataType()
+        self.data = data
+        AppDependencyManager.shared.add(dependency: data)
+    }
+
     private var contentView: some View {
         #if WITH_REMOTE
         RemoteContentView(remoteClientState: data.remoteClient.state)

+ 4 - 0
Services/UTMAppleVirtualMachine.swift

@@ -118,6 +118,10 @@ final class UTMAppleVirtualMachine: UTMVirtualMachine {
 
     private var removableDrives: [String: Any] = [:]
 
+    @MainActor var isHeadless: Bool {
+        config.displays.isEmpty && config.serials.filter({ $0.mode == .builtin }).isEmpty
+    }
+
     @MainActor required init(packageUrl: URL, configuration: UTMAppleConfiguration, isShortcut: Bool = false) throws {
         self.isScopedAccess = packageUrl.startAccessingSecurityScopedResource()
         // load configuration

+ 7 - 0
Services/UTMSpiceVirtualMachine.swift

@@ -177,3 +177,10 @@ extension UTMSpiceVirtualMachine {
         }
     }
 }
+
+// MARK: - Headless
+extension UTMSpiceVirtualMachine {
+    @MainActor var isHeadless: Bool {
+        config.displays.isEmpty && config.serials.filter({ $0.mode == .builtin }).isEmpty
+    }
+}

+ 6 - 3
Services/UTMVirtualMachine.swift

@@ -70,7 +70,10 @@ protocol UTMVirtualMachine: AnyObject, Identifiable {
 
     /// If non-null, `saveSnapshot` and `restoreSnapshot` will not work due to the reason specified
     var snapshotUnsupportedError: Error? { get }
-    
+
+    /// If true, this VM does not have any active display
+    var isHeadless: Bool { get }
+
     static func isVirtualMachine(url: URL) -> Bool
     
     /// Get name of UTM virtual machine from a file
@@ -207,7 +210,7 @@ protocol UTMVirtualMachineDelegate: AnyObject {
 }
 
 /// Virtual machine state
-enum UTMVirtualMachineState: Codable {
+enum UTMVirtualMachineState: Int, Codable, CaseIterable, Sendable {
     case stopped
     case starting
     case started
@@ -232,7 +235,7 @@ struct UTMVirtualMachineStartOptions: OptionSet, Codable {
 }
 
 /// Method to stop the VM
-enum UTMVirtualMachineStopMethod: Codable {
+enum UTMVirtualMachineStopMethod: Int, Codable, CaseIterable, Sendable {
     /// Sends a request to the guest to shut down gracefully.
     case request
     /// Sends a hardware power down signal.

+ 48 - 0
UTM.xcodeproj/project.pbxproj

@@ -661,6 +661,21 @@
 		CE88A09E2E1DDB4200EAA28E /* UTMASIFImage.m in Sources */ = {isa = PBXBuildFile; fileRef = CE88A09C2E1DDB4200EAA28E /* UTMASIFImage.m */; };
 		CE88A09F2E1DDB4200EAA28E /* UTMASIFImage.m in Sources */ = {isa = PBXBuildFile; fileRef = CE88A09C2E1DDB4200EAA28E /* UTMASIFImage.m */; };
 		CE88A0A02E1DDB4200EAA28E /* UTMASIFImage.m in Sources */ = {isa = PBXBuildFile; fileRef = CE88A09C2E1DDB4200EAA28E /* UTMASIFImage.m */; };
+		CE88A0A32E2321D200EAA28E /* UTMVirtualMachineEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE88A0A22E2321D200EAA28E /* UTMVirtualMachineEntity.swift */; };
+		CE88A0A42E2321D200EAA28E /* UTMVirtualMachineEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE88A0A22E2321D200EAA28E /* UTMVirtualMachineEntity.swift */; };
+		CE88A0A52E2321D200EAA28E /* UTMVirtualMachineEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE88A0A22E2321D200EAA28E /* UTMVirtualMachineEntity.swift */; };
+		CE88A1502E2344A900EAA28E /* UTMVirtualMachineEntityQuery.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE88A14F2E2344A900EAA28E /* UTMVirtualMachineEntityQuery.swift */; };
+		CE88A1512E2344A900EAA28E /* UTMVirtualMachineEntityQuery.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE88A14F2E2344A900EAA28E /* UTMVirtualMachineEntityQuery.swift */; };
+		CE88A1522E2344A900EAA28E /* UTMVirtualMachineEntityQuery.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE88A14F2E2344A900EAA28E /* UTMVirtualMachineEntityQuery.swift */; };
+		CE88A1542E247CCE00EAA28E /* UTMIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE88A1532E247CCE00EAA28E /* UTMIntent.swift */; };
+		CE88A1552E247CCE00EAA28E /* UTMIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE88A1532E247CCE00EAA28E /* UTMIntent.swift */; };
+		CE88A1562E247CCE00EAA28E /* UTMIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE88A1532E247CCE00EAA28E /* UTMIntent.swift */; };
+		CE88A1582E247D0100EAA28E /* UTMActionIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE88A1572E247D0100EAA28E /* UTMActionIntent.swift */; };
+		CE88A1592E247D0100EAA28E /* UTMActionIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE88A1572E247D0100EAA28E /* UTMActionIntent.swift */; };
+		CE88A15A2E247D0100EAA28E /* UTMActionIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE88A1572E247D0100EAA28E /* UTMActionIntent.swift */; };
+		CE88A1602E24B2B400EAA28E /* UTMInputIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE88A15F2E24B2B400EAA28E /* UTMInputIntent.swift */; };
+		CE88A1612E24B2B400EAA28E /* UTMInputIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE88A15F2E24B2B400EAA28E /* UTMInputIntent.swift */; };
+		CE88A1622E24B2B400EAA28E /* UTMInputIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE88A15F2E24B2B400EAA28E /* UTMInputIntent.swift */; };
 		CE89CB0E2B8B1B5A006B2CC2 /* VisionKeyboardKit in Frameworks */ = {isa = PBXBuildFile; platformFilters = (xros, ); productRef = CE89CB0D2B8B1B5A006B2CC2 /* VisionKeyboardKit */; };
 		CE89CB102B8B1B6A006B2CC2 /* VisionKeyboardKit in Frameworks */ = {isa = PBXBuildFile; platformFilters = (xros, ); productRef = CE89CB0F2B8B1B6A006B2CC2 /* VisionKeyboardKit */; };
 		CE89CB122B8B1B7A006B2CC2 /* VisionKeyboardKit in Frameworks */ = {isa = PBXBuildFile; platformFilters = (xros, ); productRef = CE89CB112B8B1B7A006B2CC2 /* VisionKeyboardKit */; };
@@ -1966,6 +1981,11 @@
 		CE8813D424CD265700532628 /* VMShareFileModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VMShareFileModifier.swift; sourceTree = "<group>"; };
 		CE88A09B2E1DDB4200EAA28E /* UTMASIFImage.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = UTMASIFImage.h; sourceTree = "<group>"; };
 		CE88A09C2E1DDB4200EAA28E /* UTMASIFImage.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = UTMASIFImage.m; sourceTree = "<group>"; };
+		CE88A0A22E2321D200EAA28E /* UTMVirtualMachineEntity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTMVirtualMachineEntity.swift; sourceTree = "<group>"; };
+		CE88A14F2E2344A900EAA28E /* UTMVirtualMachineEntityQuery.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTMVirtualMachineEntityQuery.swift; sourceTree = "<group>"; };
+		CE88A1532E247CCE00EAA28E /* UTMIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTMIntent.swift; sourceTree = "<group>"; };
+		CE88A1572E247D0100EAA28E /* UTMActionIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTMActionIntent.swift; sourceTree = "<group>"; };
+		CE88A15F2E24B2B400EAA28E /* UTMInputIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTMInputIntent.swift; sourceTree = "<group>"; };
 		CE928C2926ABE6690099F293 /* UTMAppleVirtualMachine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTMAppleVirtualMachine.swift; sourceTree = "<group>"; };
 		CE928C3026ACCDEA0099F293 /* VMAppleRemovableDrivesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VMAppleRemovableDrivesView.swift; sourceTree = "<group>"; };
 		CE9375A024BBDDD10074066F /* VMConfigDriveDetailsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VMConfigDriveDetailsView.swift; sourceTree = "<group>"; };
@@ -2816,6 +2836,7 @@
 				CE4698F824C8FBD9008C1BD6 /* Icons */,
 				CEFE98DD2948518D007CB7A8 /* Scripting */,
 				84E3A8F1293DB37E0024A740 /* utmctl */,
+				CE88A0A12E23201100EAA28E /* Intents */,
 				CE550BCA225947990063E575 /* Products */,
 				CE2D63D622653C7300FC7E63 /* Frameworks */,
 			);
@@ -2925,6 +2946,18 @@
 			path = Display;
 			sourceTree = "<group>";
 		};
+		CE88A0A12E23201100EAA28E /* Intents */ = {
+			isa = PBXGroup;
+			children = (
+				CE88A0A22E2321D200EAA28E /* UTMVirtualMachineEntity.swift */,
+				CE88A14F2E2344A900EAA28E /* UTMVirtualMachineEntityQuery.swift */,
+				CE88A1532E247CCE00EAA28E /* UTMIntent.swift */,
+				CE88A1572E247D0100EAA28E /* UTMActionIntent.swift */,
+				CE88A15F2E24B2B400EAA28E /* UTMInputIntent.swift */,
+			);
+			path = Intents;
+			sourceTree = "<group>";
+		};
 		CE9A352E26533A51005077CF /* JailbreakInterposer */ = {
 			isa = PBXGroup;
 			children = (
@@ -3598,6 +3631,7 @@
 				CE8813D524CD265700532628 /* VMShareFileModifier.swift in Sources */,
 				843BF83C2845494C0029D60D /* UTMQemuConfigurationSerial.swift in Sources */,
 				CEF0305126A2AFBF00667B63 /* Spinner.swift in Sources */,
+				CE88A1552E247CCE00EAA28E /* UTMIntent.swift in Sources */,
 				CE88A09F2E1DDB4200EAA28E /* UTMASIFImage.m in Sources */,
 				843BF840284555E70029D60D /* UTMQemuConfigurationPortForward.swift in Sources */,
 				CE611BE729F50CAD001817BC /* UTMReleaseHelper.swift in Sources */,
@@ -3632,7 +3666,9 @@
 				CE19392626DCB094005CEC17 /* RAMSlider.swift in Sources */,
 				CE9B15412B11A74E003A32DD /* UTMRemoteKeyManager.swift in Sources */,
 				CE611BEB29F50D3E001817BC /* VMReleaseNotesView.swift in Sources */,
+				CE88A1502E2344A900EAA28E /* UTMVirtualMachineEntityQuery.swift in Sources */,
 				CEE7E936287CFDB100282049 /* UTMLegacyQemuConfiguration+Constants.m in Sources */,
+				CE88A0A32E2321D200EAA28E /* UTMVirtualMachineEntity.swift in Sources */,
 				4B224B9D279D4D8100B63CFF /* InListButtonStyle.swift in Sources */,
 				2C33B3A92566C9B100A954A6 /* VMContextMenuModifier.swift in Sources */,
 				CE5076DB250AB55D00C26C19 /* VMDisplayMetalViewController+Pencil.m in Sources */,
@@ -3720,7 +3756,9 @@
 				CE2D932224AD46670059923A /* VMScroll.m in Sources */,
 				CE2D957D24AD4F990059923A /* VMConfigNetworkPortForwardView.swift in Sources */,
 				843232B728C4816100CFBC97 /* UTMDownloadSupportToolsTask.swift in Sources */,
+				CE88A1592E247D0100EAA28E /* UTMActionIntent.swift in Sources */,
 				841619A6284315C1000034B2 /* UTMQemuConfiguration.swift in Sources */,
+				CE88A1622E24B2B400EAA28E /* UTMInputIntent.swift in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -3735,6 +3773,7 @@
 				83A004BB26A8CC95001AC09E /* UTMDownloadTask.swift in Sources */,
 				848A98B0286A0F74006F0550 /* UTMAppleConfiguration.swift in Sources */,
 				2C6D9E03256EE454003298E6 /* VMDisplayQemuTerminalWindowController.swift in Sources */,
+				CE88A0A52E2321D200EAA28E /* UTMVirtualMachineEntity.swift in Sources */,
 				CE6D21DD2553A6ED001D29C5 /* VMConfirmActionModifier.swift in Sources */,
 				85EC516627CC8D10004A51DE /* VMConfigAdvancedNetworkView.swift in Sources */,
 				CE1AEC402B78B30700992AFC /* MacDeviceLabel.swift in Sources */,
@@ -3750,6 +3789,7 @@
 				841619B028431952000034B2 /* UTMQemuConfigurationSystem.swift in Sources */,
 				CE2D957224AD4F990059923A /* UTMExtensions.swift in Sources */,
 				843BF8322844853E0029D60D /* UTMQemuConfigurationNetwork.swift in Sources */,
+				CE88A1522E2344A900EAA28E /* UTMVirtualMachineEntityQuery.swift in Sources */,
 				CEBE820926A4C74E007AAB12 /* VMWizardSharingView.swift in Sources */,
 				8471770827CC974F00D3A50B /* DefaultTextField.swift in Sources */,
 				CE03D0D224DCF4B600F76B84 /* VMMetalView.swift in Sources */,
@@ -3777,6 +3817,7 @@
 				CE2D955824AD4F980059923A /* VMConfigDisplayView.swift in Sources */,
 				CEC794BA294924E300121A9F /* UTMScriptingSerialPortImpl.swift in Sources */,
 				CE03D05324D90B4E00F76B84 /* UTMQemuSystem.m in Sources */,
+				CE88A1602E24B2B400EAA28E /* UTMInputIntent.swift in Sources */,
 				841619B82843226B000034B2 /* QEMUConstant.swift in Sources */,
 				84C584EB268FA6D1000FCABF /* VMConfigAppleSystemView.swift in Sources */,
 				848A98BC286A1930006F0550 /* UTMAppleConfigurationSharedDirectory.swift in Sources */,
@@ -3832,6 +3873,7 @@
 				CE0B6D0224AD56AE00FE012D /* UTMProcess.m in Sources */,
 				CEF0306026A2AFDF00667B63 /* VMWizardState.swift in Sources */,
 				CEF0300826A25A6900667B63 /* VMWizardView.swift in Sources */,
+				CE88A15A2E247D0100EAA28E /* UTMActionIntent.swift in Sources */,
 				CE928C2A26ABE6690099F293 /* UTMAppleVirtualMachine.swift in Sources */,
 				CE0B6CF524AD568400FE012D /* UTMLegacyQemuConfiguration+Miscellaneous.m in Sources */,
 				CE25125129C806AF000790AB /* UTMScriptingDeleteCommand.swift in Sources */,
@@ -3918,6 +3960,7 @@
 				03FA9C752B9BBDB000C53A5A /* UTMConfigurationHostNetwork.swift in Sources */,
 				845F170D289CB3DE00944904 /* VMDisplayTerminal.swift in Sources */,
 				84C4D9042880CA8A00EC3B2B /* VMSettingsAddDeviceMenuView.swift in Sources */,
+				CE88A1562E247CCE00EAA28E /* UTMIntent.swift in Sources */,
 				CEBE820526A4C1B5007AAB12 /* VMWizardDrivesView.swift in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
@@ -3968,6 +4011,7 @@
 				84CE3DAF2904C17C00FF068B /* IASKAppSettings.swift in Sources */,
 				84C4D9032880CA8A00EC3B2B /* VMSettingsAddDeviceMenuView.swift in Sources */,
 				CEA45E5A263519B5002FA97D /* VMConfigSystemView.swift in Sources */,
+				CE88A1512E2344A900EAA28E /* UTMVirtualMachineEntityQuery.swift in Sources */,
 				CEA45E5B263519B5002FA97D /* VMShareFileModifier.swift in Sources */,
 				8471770727CC974F00D3A50B /* DefaultTextField.swift in Sources */,
 				CE9B15482B12A87E003A32DD /* GenerateKey.c in Sources */,
@@ -4006,6 +4050,7 @@
 				8401865F2887B1620050AC51 /* VMDisplayTerminalViewController.swift in Sources */,
 				CEA45E8F263519B5002FA97D /* VMContextMenuModifier.swift in Sources */,
 				85EC516527CC8D0F004A51DE /* VMConfigAdvancedNetworkView.swift in Sources */,
+				CE88A1612E24B2B400EAA28E /* UTMInputIntent.swift in Sources */,
 				03FA9C732B9BBDB000C53A5A /* UTMConfigurationHostNetwork.swift in Sources */,
 				CEA45E91263519B5002FA97D /* VMDisplayMetalViewController+Pencil.m in Sources */,
 				CEA45E94263519B5002FA97D /* UTMLegacyQemuConfiguration+Drives.m in Sources */,
@@ -4024,6 +4069,7 @@
 				848D99B928630A780055C215 /* VMConfigSerialView.swift in Sources */,
 				CEA45EB7263519B5002FA97D /* VMToolbarModifier.swift in Sources */,
 				CEA45EB9263519B5002FA97D /* VMCursor.m in Sources */,
+				CE88A1582E247D0100EAA28E /* UTMActionIntent.swift in Sources */,
 				CEA45EBA263519B5002FA97D /* VMConfigDriveDetailsView.swift in Sources */,
 				84B36D2627B704C200C22685 /* UTMDownloadVMTask.swift in Sources */,
 				CEF0305226A2AFBF00667B63 /* Spinner.swift in Sources */,
@@ -4047,6 +4093,7 @@
 				CEA45ED3263519B5002FA97D /* VMSettingsView.swift in Sources */,
 				CE19392726DCB094005CEC17 /* RAMSlider.swift in Sources */,
 				84E6F6FE289319AE00080EEF /* VMToolbarDisplayMenuView.swift in Sources */,
+				CE88A1542E247CCE00EAA28E /* UTMIntent.swift in Sources */,
 				841E58CC28937EE200137A20 /* UTMExternalSceneDelegate.swift in Sources */,
 				CEF01DB82B674BF000725A0F /* UTMPipeInterface.swift in Sources */,
 				CEF0307226A2B04400667B63 /* VMWizardView.swift in Sources */,
@@ -4057,6 +4104,7 @@
 				CE611BEC29F50D3E001817BC /* VMReleaseNotesView.swift in Sources */,
 				CE611BE829F50CAD001817BC /* UTMReleaseHelper.swift in Sources */,
 				CEA45EE8263519B5002FA97D /* VMDisplayMetalViewController+Keyboard.m in Sources */,
+				CE88A0A42E2321D200EAA28E /* UTMVirtualMachineEntity.swift in Sources */,
 				CEA45EEA263519B5002FA97D /* UTMExtensions.swift in Sources */,
 				843BF82928441FAF0029D60D /* QEMUConstantGenerated.swift in Sources */,
 				CEA45EEC263519B5002FA97D /* UTMData.swift in Sources */,