tried to implement resuming

This commit is contained in:
neon443
2025-06-26 15:03:59 +01:00
parent 84b0b3cc7a
commit f082d8b77c
9 changed files with 78 additions and 31 deletions

View File

@@ -36,10 +36,15 @@ class HostsManager: ObservableObject, @unchecked Sendable {
} }
func updateHost(_ updatedHost: Host) { func updateHost(_ updatedHost: Host) {
let oldID = updatedHost.id
if let index = savedHosts.firstIndex(where: { $0.id == updatedHost.id }) { if let index = savedHosts.firstIndex(where: { $0.id == updatedHost.id }) {
var updateHostWithNewID = updatedHost var updateHostWithNewID = updatedHost
updateHostWithNewID.id = UUID() updateHostWithNewID.id = UUID()
withAnimation { savedHosts[index] = updateHostWithNewID } withAnimation { savedHosts[index] = updateHostWithNewID }
updateHostWithNewID.id = oldID
withAnimation { savedHosts[index] = updateHostWithNewID }
saveSavedHosts() saveSavedHosts()
} }
} }

View File

@@ -14,6 +14,8 @@ class SSHHandler: @unchecked Sendable, ObservableObject {
private var session: ssh_session? private var session: ssh_session?
private var channel: ssh_channel? private var channel: ssh_channel?
var scrollback: [String] = []
@Published var title: String = "" @Published var title: String = ""
@Published var state: SSHState = .idle @Published var state: SSHState = .idle
var connected: Bool { var connected: Bool {
@@ -393,6 +395,9 @@ class SSHHandler: @unchecked Sendable, ObservableObject {
#if DEBUG #if DEBUG
// 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
scrollback.append(string)
}
return string return string
} }
return nil return nil

View File

@@ -6,6 +6,7 @@
// //
import Foundation import Foundation
import SwiftUI
enum SSHState { enum SSHState {
case idle case idle
@@ -16,6 +17,22 @@ enum SSHState {
case connectionFailed case connectionFailed
case authFailed case authFailed
var color: Color {
switch self {
case .idle:
return .gray
case .connecting, .authorizing:
return .orange
case .authorized, .shellOpen:
return .green
case .connectionFailed, .authFailed:
return .red
}
}
} }
func checkConnected(_ state: SSHState) -> Bool { func checkConnected(_ state: SSHState) -> Bool {

View File

@@ -12,6 +12,8 @@ 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 = ""
@@ -54,14 +56,8 @@ struct ConnectionView: View {
} }
} }
Section { Section {
HStack { Text("\(handler.state)")
Text(handler.connected ? "connected" : "not connected") .foregroundStyle(handler.state.color)
.modifier(foregroundColorStyle(handler.connected ? .green : .red))
Text(checkAuth(handler.state) ? "authorized" : "unauthorized")
.modifier(foregroundColorStyle(checkAuth(handler.state) ? .green : .red))
Text("\(handler.state)")
}
TextField("name", text: $handler.host.name) TextField("name", text: $handler.host.name)
.textFieldStyle(.roundedBorder) .textFieldStyle(.roundedBorder)
@@ -116,6 +112,7 @@ 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")
} }
@@ -162,7 +159,7 @@ struct ConnectionView: View {
} }
} }
.fullScreenCover(isPresented: $showTerminal) { .fullScreenCover(isPresented: $showTerminal) {
ShellView(handler: handler) ShellView(handler: handler, resuming: resuming)
} }
.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 }
@@ -172,7 +169,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) TerminalController(handler: miniHandler, resuming: false)
.onAppear { miniHandler.go() } .onAppear { miniHandler.go() }
} }
} label: { } label: {

View File

@@ -46,7 +46,11 @@ struct KeyManagerView: View {
} }
ForEach(hostsManager.savedHosts) { host in ForEach(hostsManager.savedHosts) { host in
VStack(alignment: .leading) { VStack(alignment: .leading) {
Text(host.name + "\n" + host.address) if !host.name.isEmpty {
Text(host.name)
.bold()
}
Text(host.address)
.bold() .bold()
Text(host.key ?? "nil") Text(host.key ?? "nil")
} }

View File

@@ -12,25 +12,34 @@ 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 var sshQueue = DispatchQueue(label: "sshQueue")
var resuming: Bool
public convenience init(frame: CGRect, handler: SSHHandler) { public convenience init(frame: CGRect, handler: SSHHandler, resuming: Bool) {
self.init(frame: frame) self.init(frame: frame)
self.handler = handler self.handler = handler
} self.resuming = resuming
public override init(frame: CGRect) {
sshQueue = DispatchQueue(label: "sshQueue")
super.init(frame: frame)
terminalDelegate = self
sshQueue.async {
Task {
if resuming {
if let handler = await self.handler {
for chunk in handler.scrollback {
await MainActor.run {
self.feed(text: chunk)
}
}
}
}
}
}
sshQueue.async { 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 {
if let read = handler.readFromChannel() { if let read = handler.readFromChannel() {
Task { @MainActor in await MainActor.run {
self.feed(text: read) self.feed(text: read)
} }
} else { } else {
@@ -42,6 +51,12 @@ final class SSHTerminalView: TerminalView, Sendable, @preconcurrency TerminalVie
} }
} }
public override init(frame: CGRect) {
self.resuming = false
super.init(frame: frame)
terminalDelegate = self
}
required init?(coder: NSCoder) { required init?(coder: NSCoder) {
fatalError("unimplemented") fatalError("unimplemented")
} }

View File

@@ -9,6 +9,8 @@ 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
@State private var terminalControllerRef: TerminalController? @State private var terminalControllerRef: TerminalController?
@@ -31,7 +33,7 @@ struct ShellView: View {
} }
} }
.task { .task {
terminalControllerRef = TerminalController(handler: handler) terminalControllerRef = TerminalController(handler: handler, resuming: resuming)
} }
.toolbar { .toolbar {
ToolbarItem { ToolbarItem {
@@ -43,13 +45,13 @@ struct ShellView: View {
} }
} }
//TODO: FIX //TODO: FIX
// ToolbarItem(placement: .cancellationAction) { ToolbarItem(placement: .cancellationAction) {
// Button() { Button() {
// dismiss() dismiss()
// } label: { } label: {
// Label("Close", systemImage: "arrow.down.right.and.arrow.up.left") Label("Close", systemImage: "arrow.down.right.and.arrow.up.left")
// } }
// } }
} }
.onChange(of: handler.connected) { _ in .onChange(of: handler.connected) { _ in
if !handler.connected { dismiss() } if !handler.connected { dismiss() }

View File

@@ -12,6 +12,7 @@ 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(
@@ -19,7 +20,8 @@ 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