90 lines
3.3 KiB
Swift
90 lines
3.3 KiB
Swift
// SPDX-FileCopyrightText: 2026 Kaede Akino
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
import Foundation
|
|
import TermindApplication
|
|
import TermindDomain
|
|
|
|
nonisolated enum ResumableTerminalEngineEvent: Sendable {
|
|
case output(Data)
|
|
case routeChanged(TerminalRouteSnapshot)
|
|
case activity, connected, recovering, failed, ended
|
|
}
|
|
|
|
nonisolated struct ResumableTerminalShellState: Sendable {
|
|
nonisolated struct IntegratedShell: Codable, Sendable {
|
|
let shell: String
|
|
let systemInfo: RemoteSystemInfo?
|
|
let homeDirectory: String?
|
|
}
|
|
|
|
var integration: RemoteShellIntegrationBootstrap?
|
|
var commandFilter: RemoteCommandEventFilter?
|
|
var integratedShell: IntegratedShell?
|
|
var pendingIntegrationCommand: Data?
|
|
}
|
|
|
|
nonisolated struct ResumableTerminalEnginePreparation: Sendable {
|
|
var shell = ResumableTerminalShellState()
|
|
var startupInput: Data?
|
|
}
|
|
|
|
nonisolated struct ResumableTerminalEngineContext: Sendable {
|
|
let request: SSHConnectionRequest
|
|
let options: HostTerminalTransports
|
|
let environment: [HostEnvironmentVariable]
|
|
let shellIntegrationEnabled: Bool
|
|
let checkpoint: Data?
|
|
let startup: ResumableTerminalStartup
|
|
let bootstrap: SSHConnection
|
|
let route: ResumableTerminalClient.RouteFactory
|
|
}
|
|
|
|
/// Engines own protocol resources, not application lifecycle or checkpoint eligibility.
|
|
/// Every event and integration write passes through the client's shared serialization barrier.
|
|
nonisolated protocol ResumableTerminalEngine: Sendable {
|
|
typealias EventHandler = @Sendable (ResumableTerminalEngineEvent) async -> Void
|
|
|
|
func prepare(_ context: ResumableTerminalEngineContext) async throws -> ResumableTerminalEnginePreparation
|
|
func start(columns: UInt16, rows: UInt16) async throws
|
|
func write(_ data: Data) async throws
|
|
func resize(columns: UInt16, rows: UInt16) async throws
|
|
func checkpoint(shell: @Sendable () async -> ResumableTerminalShellState) async throws -> Data
|
|
func beforeResume() async throws
|
|
func resume() async throws
|
|
func prepareRetry() async
|
|
func retry() async throws
|
|
func networkAvailable() async
|
|
func rollback(using bootstrap: SSHConnection) async
|
|
func close() async
|
|
}
|
|
|
|
extension ResumableTerminalEngine {
|
|
func beforeResume() async throws {}
|
|
func prepareRetry() async {}
|
|
func networkAvailable() async {}
|
|
func rollback(using bootstrap: SSHConnection) async {}
|
|
}
|
|
|
|
/// Registrations are immutable and local to a coordinator; no process-global overrides.
|
|
public nonisolated struct ResumableTerminalEngineRegistry: Sendable {
|
|
typealias Factory = @Sendable (@escaping ResumableTerminalEngine.EventHandler) -> any ResumableTerminalEngine
|
|
private let factories: [HostTerminalProtocol: Factory]
|
|
|
|
init(factories: [HostTerminalProtocol: Factory]) {
|
|
self.factories = factories
|
|
}
|
|
|
|
public static let live = Self(factories: [
|
|
.mosh: { MoshTerminalEngine(receive: $0) },
|
|
.eternalTerminal: { EternalTerminalEngine(receive: $0) },
|
|
])
|
|
|
|
func make(
|
|
_ transport: HostTerminalProtocol, receive: @escaping ResumableTerminalEngine.EventHandler
|
|
) throws -> any ResumableTerminalEngine {
|
|
guard let factory = factories[transport] else { throw TerminalTransportError.unsupportedRoute }
|
|
return factory(receive)
|
|
}
|
|
}
|