From 0f5d45cc85f65d74d0b70221325bfddbe2bc3a36 Mon Sep 17 00:00:00 2001 From: neon443 <69979447+neon443@users.noreply.github.com> Date: Tue, 24 Jun 2025 14:57:25 +0100 Subject: [PATCH] integrated hostsmanager into sshhandler lets see where this goes made disconnct async rewrote async reading from ssh usleep -> Task.sleep() to prevent blocking --- ShhShell/SSH/SSHHandler.swift | 49 ++++++++++--------- ShhShell/ShhShellApp.swift | 4 +- ShhShell/Views/ConnectionView.swift | 20 ++++---- ShhShell/Views/ContentView.swift | 10 ++-- ShhShell/Views/HostsView.swift | 27 +++++----- ShhShell/Views/Terminal/SSHTerminalView.swift | 29 +++++++++-- ShhShell/Views/Terminal/ShellView.swift | 7 ++- .../Views/Terminal/TerminalController.swift | 2 +- 8 files changed, 87 insertions(+), 61 deletions(-) diff --git a/ShhShell/SSH/SSHHandler.swift b/ShhShell/SSH/SSHHandler.swift index 125cbe6..46b64e1 100644 --- a/ShhShell/SSH/SSHHandler.swift +++ b/ShhShell/SSH/SSHHandler.swift @@ -15,6 +15,8 @@ class SSHHandler: @unchecked Sendable, ObservableObject { private var channel: ssh_channel? private let sshQueue = DispatchQueue(label: "SSH Queue") + @Published var hostsManager = HostsManager() + @Published var connected: Bool = false @Published var authorized: Bool = false @Published var testSuceeded: Bool? = nil @@ -28,8 +30,10 @@ class SSHHandler: @unchecked Sendable, ObservableObject { init( host: Host +// hostsManager: HostsManager ) { self.host = host +// self.hostsManager = hostsManager } func getHostkey() -> Data? { @@ -47,11 +51,16 @@ class SSHHandler: @unchecked Sendable, ObservableObject { func go() { guard !connected else { - disconnect() + Task { + await disconnect() + } return } guard let _ = try? connect() else { return } + + + if !host.password.isEmpty { do { try authWithPw() } catch { print("pw auth error") @@ -103,19 +112,24 @@ class SSHHandler: @unchecked Sendable, ObservableObject { return } - func disconnect() { - withAnimation { connected = false } - withAnimation { authorized = false } - withAnimation { testSuceeded = nil } + func disconnect() async { + await MainActor.run { + withAnimation { connected = false } + withAnimation { authorized = false } + withAnimation { testSuceeded = nil } + } ssh_channel_send_eof(self.channel) - ssh_channel_close(self.channel) ssh_channel_free(self.channel) -// self.channel = nil + self.channel = nil ssh_disconnect(self.session) ssh_free(self.session) -// self.session = nil + self.session = nil + } + + func checkHostkey() { + } func testExec() { @@ -321,21 +335,14 @@ class SSHHandler: @unchecked Sendable, ObservableObject { status = ssh_channel_request_shell(self.channel) guard status == SSH_OK else { return } - } - - func asyncReadFromChannel() async -> String? { - return await withCheckedContinuation { continuation in - DispatchQueue.global(qos: .userInteractive).async { - let result = self.readFromChannel() - continuation.resume(returning: result) - } - } + + } func readFromChannel() -> String? { guard connected else { return nil } guard ssh_channel_is_open(channel) != 0 || ssh_channel_is_eof(channel) == 0 else { - disconnect() + Task { await disconnect() } return nil } @@ -365,10 +372,8 @@ class SSHHandler: @unchecked Sendable, ObservableObject { func writeToChannel(_ string: String?) { guard let string = string else { return } guard channel != nil else { return } - guard ssh_channel_is_open(channel) != 0 || ssh_channel_is_eof(channel) == 0 else { - disconnect() - return - } + guard ssh_channel_is_open(channel) != 0 else { return } + guard ssh_channel_is_eof(channel) == 0 else { return } var buffer: [CChar] = [] for byte in string.utf8 { diff --git a/ShhShell/ShhShellApp.swift b/ShhShell/ShhShellApp.swift index 60b0635..04585e6 100644 --- a/ShhShell/ShhShellApp.swift +++ b/ShhShell/ShhShellApp.swift @@ -11,14 +11,12 @@ import SwiftUI struct ShhShellApp: App { @StateObject var sshHandler: SSHHandler = SSHHandler(host: Host.blank) @StateObject var keyManager: KeyManager = KeyManager() - @StateObject var hostsManager: HostsManager = HostsManager() var body: some Scene { WindowGroup { ContentView( handler: sshHandler, - keyManager: keyManager, - hostsManager: hostsManager + keyManager: keyManager ) .colorScheme(.dark) } diff --git a/ShhShell/Views/ConnectionView.swift b/ShhShell/Views/ConnectionView.swift index 036f3f9..9c0a309 100644 --- a/ShhShell/Views/ConnectionView.swift +++ b/ShhShell/Views/ConnectionView.swift @@ -10,7 +10,6 @@ import SwiftUI struct ConnectionView: View { @StateObject var handler: SSHHandler @StateObject var keyManager: KeyManager - @StateObject var hostsManager: HostsManager @State var passphrase: String = "" @@ -120,7 +119,7 @@ struct ConnectionView: View { if handler.host.key != nil { Text("Hostkey: \(handler.host.key!.base64EncodedString())") .onChange(of: handler.host.key) { _ in - guard let previousKnownHost = hostsManager.getHostMatching(handler.host) else { return } + guard let previousKnownHost = handler.hostsManager.getHostMatching(handler.host) else { return } guard handler.host.key == previousKnownHost.key else { hostKeyChangedAlert = true return @@ -152,15 +151,17 @@ struct ConnectionView: View { } .alert("Hostkey changed", isPresented: $hostKeyChangedAlert) { Button("Accept New Hostkey", role: .destructive) { - hostsManager.updateHost(handler.host) + handler.hostsManager.updateHost(handler.host) } Button("Disconnect", role: .cancel) { - handler.disconnect() - handler.host.key = hostsManager.getHostMatching(handler.host)?.key + Task { + await handler.disconnect() + handler.host.key = handler.hostsManager.getHostMatching(handler.host)?.key + } } } message: { - Text("Expected \(hostsManager.getHostMatching(handler.host)?.key?.base64EncodedString() ?? "null")\nbut recieved \(handler.host.key?.base64EncodedString() ?? "null" ) from the server") + Text("Expected \(handler.hostsManager.getHostMatching(handler.host)?.key?.base64EncodedString() ?? "null")\nbut recieved \(handler.host.key?.base64EncodedString() ?? "null" ) from the server") } .transition(.opacity) .toolbar { @@ -177,8 +178,8 @@ struct ConnectionView: View { } } .onDisappear { - guard hostsManager.getHostMatching(handler.host) == handler.host else { - hostsManager.updateHost(handler.host) + guard handler.hostsManager.getHostMatching(handler.host) == handler.host else { + handler.hostsManager.updateHost(handler.host) return } } @@ -197,7 +198,6 @@ struct ConnectionView: View { #Preview { ConnectionView( handler: SSHHandler(host: Host.debug), - keyManager: KeyManager(), - hostsManager: HostsManager() + keyManager: KeyManager() ) } diff --git a/ShhShell/Views/ContentView.swift b/ShhShell/Views/ContentView.swift index 7e53c8c..448d99a 100644 --- a/ShhShell/Views/ContentView.swift +++ b/ShhShell/Views/ContentView.swift @@ -10,13 +10,12 @@ import SwiftUI struct ContentView: View { @ObservedObject var handler: SSHHandler @ObservedObject var keyManager: KeyManager - @ObservedObject var hostsManager: HostsManager - + var body: some View { TabView { HostsView( - keyManager: keyManager, - hostsManager: hostsManager + handler: handler, + keyManager: keyManager ) .tabItem { Label("Hosts", systemImage: "server.rack") @@ -32,7 +31,6 @@ struct ContentView: View { #Preview { ContentView( handler: SSHHandler(host: Host.debug), - keyManager: KeyManager(), - hostsManager: HostsManager() + keyManager: KeyManager() ) } diff --git a/ShhShell/Views/HostsView.swift b/ShhShell/Views/HostsView.swift index ca2bb9b..35064f1 100644 --- a/ShhShell/Views/HostsView.swift +++ b/ShhShell/Views/HostsView.swift @@ -8,28 +8,27 @@ import SwiftUI struct HostsView: View { + @ObservedObject var handler: SSHHandler @ObservedObject var keyManager: KeyManager - @ObservedObject var hostsManager: HostsManager var body: some View { NavigationStack { List { - if hostsManager.savedHosts.isEmpty { + if handler.hostsManager.savedHosts.isEmpty { Text("Add your first Host!") Button() { - withAnimation { hostsManager.savedHosts.append(Host.blank) } + withAnimation { handler.hostsManager.savedHosts.append(Host.blank) } } label: { Text("Create") // .font() } .buttonStyle(.borderedProminent) } - ForEach(hostsManager.savedHosts) { host in + ForEach(handler.hostsManager.savedHosts) { host in NavigationLink() { ConnectionView( handler: SSHHandler(host: host), - keyManager: keyManager, - hostsManager: hostsManager + keyManager: keyManager ) } label: { if host.address.isEmpty { @@ -41,9 +40,9 @@ struct HostsView: View { .animation(.default, value: host) .swipeActions(edge: .trailing) { Button(role: .destructive) { - if let index = hostsManager.savedHosts.firstIndex(where: { $0.id == host.id }) { - let _ = withAnimation { hostsManager.savedHosts.remove(at: index) } - hostsManager.saveSavedHosts() + if let index = handler.hostsManager.savedHosts.firstIndex(where: { $0.id == host.id }) { + let _ = withAnimation { handler.hostsManager.savedHosts.remove(at: index) } + handler.hostsManager.saveSavedHosts() } } label: { Label("Delete", systemImage: "trash") @@ -58,11 +57,10 @@ struct HostsView: View { NavigationLink { ConnectionView( handler: SSHHandler(host: host), - keyManager: keyManager, - hostsManager: hostsManager + keyManager: keyManager ) .task(priority: .userInitiated) { - withAnimation { hostsManager.savedHosts.append(host) } + withAnimation { handler.hostsManager.savedHosts.append(host) } } } label: { Label("Add", systemImage: "plus") @@ -74,5 +72,8 @@ struct HostsView: View { } #Preview { - HostsView(keyManager: KeyManager(), hostsManager: HostsManager()) + HostsView( + handler: SSHHandler(host: Host.debug), + keyManager: KeyManager() + ) } diff --git a/ShhShell/Views/Terminal/SSHTerminalView.swift b/ShhShell/Views/Terminal/SSHTerminalView.swift index 5713824..6d67043 100644 --- a/ShhShell/Views/Terminal/SSHTerminalView.swift +++ b/ShhShell/Views/Terminal/SSHTerminalView.swift @@ -24,9 +24,29 @@ final class SSHTerminalView: TerminalView, Sendable, @preconcurrency TerminalVie super.init(frame: frame) terminalDelegate = self - sshQueue.async { [self] in - guard let handler = handler else { return } - + sshQueue.async { + Task { + guard let handler = await self.handler else { return } + + while handler.connected { + if let read = handler.readFromChannel() { + Task { [weak self] in + guard let self = self else { return } + await self.feed(text: read) + } + } else { + try? await Task.sleep(nanoseconds: 1_000_000) //1ms + } + } + } + } + } + + public func resetTerminalView(handler: SSHHandler) { + self.handler = handler +// terminal.softReset() + self.setNeedsDisplay() + sshQueue.async { while handler.connected { if let read = handler.readFromChannel() { Task { [weak self] in @@ -34,9 +54,8 @@ final class SSHTerminalView: TerminalView, Sendable, @preconcurrency TerminalVie await self.feed(text: read) } } else { - usleep(1_000) + Task{ try? await Task.sleep(nanoseconds: 1_000_000) } } -// self?.setNeedsDisplay() } } } diff --git a/ShhShell/Views/Terminal/ShellView.swift b/ShhShell/Views/Terminal/ShellView.swift index b9fa435..1c48d3b 100644 --- a/ShhShell/Views/Terminal/ShellView.swift +++ b/ShhShell/Views/Terminal/ShellView.swift @@ -11,13 +11,18 @@ struct ShellView: View { @ObservedObject var handler: SSHHandler @Environment(\.dismiss) var dismiss + @State private var terminalControllerRef: TerminalController? + var body: some View { NavigationStack { ZStack { if !handler.connected { DialogView(handler: handler, showDialog: !handler.connected) } - TerminalController(handler: handler) + terminalControllerRef + } + .task { + terminalControllerRef = TerminalController(handler: handler) } .toolbar { ToolbarItem { diff --git a/ShhShell/Views/Terminal/TerminalController.swift b/ShhShell/Views/Terminal/TerminalController.swift index 10f2655..c801d31 100644 --- a/ShhShell/Views/Terminal/TerminalController.swift +++ b/ShhShell/Views/Terminal/TerminalController.swift @@ -17,7 +17,7 @@ struct TerminalController: UIViewRepresentable { let tv = SSHTerminalView( frame: CGRect( origin: CGPoint(x: 0, y: 0), - size: CGSize(width: 100, height: 100) + size: .zero ), handler: handler )