76 lines
2.5 KiB
Swift
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
|
|
}
|
|
}
|