implement scrollback saving

implement a scrollback cap of 10MiB
add prettyscrollback() to return kib, mib etc
tried a different resuming thingy
This commit is contained in:
neon443
2025-06-26 15:46:58 +01:00
parent f082d8b77c
commit c6699a9818
6 changed files with 42 additions and 32 deletions

View File

@@ -15,6 +15,7 @@ class SSHHandler: @unchecked Sendable, ObservableObject {
private var channel: ssh_channel? private var channel: ssh_channel?
var scrollback: [String] = [] var scrollback: [String] = []
var scrollbackSize = 0.0
@Published var title: String = "" @Published var title: String = ""
@Published var state: SSHState = .idle @Published var state: SSHState = .idle
@@ -133,6 +134,9 @@ class SSHHandler: @unchecked Sendable, ObservableObject {
withAnimation { self.testSuceeded = nil } withAnimation { self.testSuceeded = nil }
} }
scrollback = []
scrollbackSize = 0
//send eof if open //send eof if open
if ssh_channel_is_open(channel) == 1 { if ssh_channel_is_open(channel) == 1 {
ssh_channel_send_eof(channel) ssh_channel_send_eof(channel)
@@ -396,7 +400,12 @@ class SSHHandler: @unchecked Sendable, ObservableObject {
// print(String(data: Data(bytes: buffer, count: Int(nbytes)), encoding: .utf8)!) // print(String(data: Data(bytes: buffer, count: Int(nbytes)), encoding: .utf8)!)
#endif #endif
Task { @MainActor in Task { @MainActor in
scrollback.append(string) scrollback.append(string)
if scrollbackSize/1024/1024 > 10 {
scrollback.remove(at: 0)
} else {
scrollbackSize += Double(string.lengthOfBytes(using: .utf8))
}
} }
return string return string
} }
@@ -429,6 +438,16 @@ class SSHHandler: @unchecked Sendable, ObservableObject {
// print("resized tty to \(toRows)rows and \(toCols)cols") // print("resized tty to \(toRows)rows and \(toCols)cols")
} }
func prettyScrollbackSize() -> String {
if (scrollbackSize/1024/1024) > 1 {
return "\(scrollbackSize/1024/1024) MiB scrollback"
} else if scrollbackSize/1024 > 1 {
return "\(scrollbackSize/1024) KiB scrollback"
} else {
return "\(scrollbackSize) B scrollback"
}
}
private func logSshGetError() { private func logSshGetError() {
guard var session = self.session else { return } guard var session = self.session else { return }
logger.critical("\(String(cString: ssh_get_error(&session)))") logger.critical("\(String(cString: ssh_get_error(&session)))")

View File

@@ -12,8 +12,6 @@ struct ConnectionView: View {
@ObservedObject var hostsManager: HostsManager @ObservedObject var hostsManager: HostsManager
@ObservedObject var keyManager: KeyManager @ObservedObject var keyManager: KeyManager
@State var resuming: Bool = false
@State var passphrase: String = "" @State var passphrase: String = ""
@State var pubkeyStr: String = "" @State var pubkeyStr: String = ""
@@ -112,7 +110,6 @@ struct ConnectionView: View {
Button() { Button() {
showTerminal.toggle() showTerminal.toggle()
resuming = true
} label: { } label: {
Label("Show Terminal", systemImage: "apple.terminal") Label("Show Terminal", systemImage: "apple.terminal")
} }
@@ -159,7 +156,7 @@ struct ConnectionView: View {
} }
} }
.fullScreenCover(isPresented: $showTerminal) { .fullScreenCover(isPresented: $showTerminal) {
ShellView(handler: handler, resuming: resuming) ShellView(handler: handler)
} }
.onChange(of: handler.host.key) { _ in .onChange(of: handler.host.key) { _ in
guard let previousKnownHost = hostsManager.getHostMatching(handler.host) else { return } guard let previousKnownHost = hostsManager.getHostMatching(handler.host) else { return }
@@ -169,7 +166,7 @@ struct ConnectionView: View {
} }
} }
.onDisappear { .onDisappear {
// hostsManager.updateHost(handler.host) hostsManager.updateHost(handler.host)
} }
.task { .task {
if let publicKeyData = handler.host.publicKey { if let publicKeyData = handler.host.publicKey {

View File

@@ -29,7 +29,7 @@ struct HostsView: View {
NavigationLink() { NavigationLink() {
ForEach(hostsManager.savedHosts) { host in ForEach(hostsManager.savedHosts) { host in
let miniHandler = SSHHandler(host: host) let miniHandler = SSHHandler(host: host)
TerminalController(handler: miniHandler, resuming: false) TerminalController(handler: miniHandler)
.onAppear { miniHandler.go() } .onAppear { miniHandler.go() }
} }
} label: { } label: {

View File

@@ -12,29 +12,15 @@ import SwiftTerm
@MainActor @MainActor
final class SSHTerminalView: TerminalView, Sendable, @preconcurrency TerminalViewDelegate { final class SSHTerminalView: TerminalView, Sendable, @preconcurrency TerminalViewDelegate {
var handler: SSHHandler? var handler: SSHHandler?
var sshQueue = DispatchQueue(label: "sshQueue") // var sshQueue = DispatchQueue(label: "sshQueue")
var resuming: Bool
public convenience init(frame: CGRect, handler: SSHHandler, resuming: Bool) { public convenience init(frame: CGRect, handler: SSHHandler) {
self.init(frame: frame) self.init(frame: frame)
self.handler = handler self.handler = handler
self.resuming = resuming
sshQueue.async { restoreScrollback()
Task {
if resuming { DispatchQueue.main.async {
if let handler = await self.handler {
for chunk in handler.scrollback {
await MainActor.run {
self.feed(text: chunk)
}
}
}
}
}
}
sshQueue.async {
Task { Task {
guard let handler = await self.handler else { return } guard let handler = await self.handler else { return }
while handler.connected { while handler.connected {
@@ -52,7 +38,6 @@ final class SSHTerminalView: TerminalView, Sendable, @preconcurrency TerminalVie
} }
public override init(frame: CGRect) { public override init(frame: CGRect) {
self.resuming = false
super.init(frame: frame) super.init(frame: frame)
terminalDelegate = self terminalDelegate = self
} }
@@ -100,4 +85,16 @@ final class SSHTerminalView: TerminalView, Sendable, @preconcurrency TerminalVie
public func bell(source: TerminalView) { public func bell(source: TerminalView) {
handler?.ring() handler?.ring()
} }
func restoreScrollback() {
guard let scrollback = handler?.scrollback else { return }
DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) {
for line in scrollback {
self.feed(text: line)
}
self.setNeedsLayout()
self.setNeedsDisplay()
}
}
} }

View File

@@ -9,7 +9,6 @@ import SwiftUI
struct ShellView: View { struct ShellView: View {
@ObservedObject var handler: SSHHandler @ObservedObject var handler: SSHHandler
@State var resuming: Bool = false
@Environment(\.dismiss) var dismiss @Environment(\.dismiss) var dismiss
@@ -33,7 +32,7 @@ struct ShellView: View {
} }
} }
.task { .task {
terminalControllerRef = TerminalController(handler: handler, resuming: resuming) terminalControllerRef = TerminalController(handler: handler)
} }
.toolbar { .toolbar {
ToolbarItem { ToolbarItem {

View File

@@ -12,7 +12,6 @@ import SwiftTerm
struct TerminalController: UIViewRepresentable { struct TerminalController: UIViewRepresentable {
@ObservedObject var handler: SSHHandler @ObservedObject var handler: SSHHandler
@State var resuming: Bool
func makeUIView(context: Context) -> TerminalView { func makeUIView(context: Context) -> TerminalView {
let tv = SSHTerminalView( let tv = SSHTerminalView(
@@ -20,8 +19,7 @@ struct TerminalController: UIViewRepresentable {
origin: CGPoint(x: 0, y: 0), origin: CGPoint(x: 0, y: 0),
size: .zero size: .zero
), ),
handler: handler, handler: handler
resuming: resuming
) )
tv.translatesAutoresizingMaskIntoConstraints = false tv.translatesAutoresizingMaskIntoConstraints = false