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) {
let oldID = updatedHost.id
if let index = savedHosts.firstIndex(where: { $0.id == updatedHost.id }) {
var updateHostWithNewID = updatedHost
updateHostWithNewID.id = UUID()
withAnimation { savedHosts[index] = updateHostWithNewID }
updateHostWithNewID.id = oldID
withAnimation { savedHosts[index] = updateHostWithNewID }
saveSavedHosts()
}
}

View File

@@ -14,6 +14,8 @@ class SSHHandler: @unchecked Sendable, ObservableObject {
private var session: ssh_session?
private var channel: ssh_channel?
var scrollback: [String] = []
@Published var title: String = ""
@Published var state: SSHState = .idle
var connected: Bool {
@@ -393,6 +395,9 @@ class SSHHandler: @unchecked Sendable, ObservableObject {
#if DEBUG
// print(String(data: Data(bytes: buffer, count: Int(nbytes)), encoding: .utf8)!)
#endif
Task { @MainActor in
scrollback.append(string)
}
return string
}
return nil

View File

@@ -6,6 +6,7 @@
//
import Foundation
import SwiftUI
enum SSHState {
case idle
@@ -16,6 +17,22 @@ enum SSHState {
case connectionFailed
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 {

View File

@@ -12,6 +12,8 @@ struct ConnectionView: View {
@ObservedObject var hostsManager: HostsManager
@ObservedObject var keyManager: KeyManager
@State var resuming: Bool = false
@State var passphrase: String = ""
@State var pubkeyStr: String = ""
@@ -54,14 +56,8 @@ struct ConnectionView: View {
}
}
Section {
HStack {
Text(handler.connected ? "connected" : "not connected")
.modifier(foregroundColorStyle(handler.connected ? .green : .red))
Text(checkAuth(handler.state) ? "authorized" : "unauthorized")
.modifier(foregroundColorStyle(checkAuth(handler.state) ? .green : .red))
Text("\(handler.state)")
}
Text("\(handler.state)")
.foregroundStyle(handler.state.color)
TextField("name", text: $handler.host.name)
.textFieldStyle(.roundedBorder)
@@ -116,6 +112,7 @@ struct ConnectionView: View {
Button() {
showTerminal.toggle()
resuming = true
} label: {
Label("Show Terminal", systemImage: "apple.terminal")
}
@@ -162,7 +159,7 @@ struct ConnectionView: View {
}
}
.fullScreenCover(isPresented: $showTerminal) {
ShellView(handler: handler)
ShellView(handler: handler, resuming: resuming)
}
.onChange(of: handler.host.key) { _ in
guard let previousKnownHost = hostsManager.getHostMatching(handler.host) else { return }
@@ -172,7 +169,7 @@ struct ConnectionView: View {
}
}
.onDisappear {
hostsManager.updateHost(handler.host)
// hostsManager.updateHost(handler.host)
}
.task {
if let publicKeyData = handler.host.publicKey {

View File

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

View File

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

View File

@@ -12,25 +12,34 @@ import SwiftTerm
@MainActor
final class SSHTerminalView: TerminalView, Sendable, @preconcurrency TerminalViewDelegate {
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.handler = handler
}
public override init(frame: CGRect) {
sshQueue = DispatchQueue(label: "sshQueue")
super.init(frame: frame)
terminalDelegate = self
self.resuming = resuming
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 {
Task {
guard let handler = await self.handler else { return }
while handler.connected {
if let read = handler.readFromChannel() {
Task { @MainActor in
await MainActor.run {
self.feed(text: read)
}
} 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) {
fatalError("unimplemented")
}

View File

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

View File

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