Files

76 lines
2.5 KiB
Swift

// SPDX-FileCopyrightText: 2026 Kaede Akino
// SPDX-License-Identifier: GPL-3.0-only
import Foundation
nonisolated struct SSHPTYSize: Equatable, Sendable {
var columns: UInt16
var rows: UInt16
var widthPixels: UInt32 = 0
var heightPixels: UInt32 = 0
var isValid: Bool { columns > 0 && rows > 0 }
}
/// Owned exclusively by the SSH worker's serial queue. A viewport can arrive
/// before authentication finishes, or even before the first connect is queued.
nonisolated struct SSHPTYResizeState {
private(set) var generation: UUID?
private(set) var requestedSize: SSHPTYSize?
private var appliedSize: SSHPTYSize?
private var inFlightSize: SSHPTYSize?
private var acceptsRequests = true
var isSynchronized: Bool {
appliedSize != nil && inFlightSize == nil && requestedSize == appliedSize
}
mutating func beginConnection(columns: UInt16, rows: UInt16) -> UUID {
let initial = generation == nil && acceptsRequests ? requestedSize : nil
reset()
let generation = UUID()
self.generation = generation
acceptsRequests = true
requestedSize = initial ?? SSHPTYSize(columns: max(1, columns), rows: max(1, rows))
return generation
}
mutating func request(_ size: SSHPTYSize) {
guard acceptsRequests, size.isValid else { return }
requestedSize = size
}
mutating func didCreatePTY(size: SSHPTYSize) {
appliedSize = size
}
/// libssh2 retains its window-change packet on EAGAIN. Retry that exact
/// size first; only after it succeeds may the newest request replace it.
mutating func flush(send: (SSHPTYSize) -> Int32) -> Int32 {
guard appliedSize != nil else { return 0 }
while let size = inFlightSize ?? requestedSize, inFlightSize != nil || size != appliedSize {
inFlightSize = size
let result = send(size)
guard result == 0 else { return result }
appliedSize = size
inFlightSize = nil
}
return 0
}
/// Drop only the failed packet, retaining any newer viewport. The same
/// size may be requested again without closing the PTY Channel's connection.
mutating func discardFailedRequest() {
if requestedSize == inFlightSize { requestedSize = nil }
inFlightSize = nil
}
mutating func reset() {
generation = nil
requestedSize = nil
appliedSize = nil
inFlightSize = nil
acceptsRequests = false
}
}