integrated hostsmanager into sshhandler lets see where this goes

made disconnct async
rewrote async reading from ssh
usleep -> Task.sleep() to prevent blocking
This commit is contained in:
neon443
2025-06-24 14:57:25 +01:00
parent 507c533b46
commit 0f5d45cc85
8 changed files with 87 additions and 61 deletions

View File

@@ -15,6 +15,8 @@ class SSHHandler: @unchecked Sendable, ObservableObject {
private var channel: ssh_channel? private var channel: ssh_channel?
private let sshQueue = DispatchQueue(label: "SSH Queue") private let sshQueue = DispatchQueue(label: "SSH Queue")
@Published var hostsManager = HostsManager()
@Published var connected: Bool = false @Published var connected: Bool = false
@Published var authorized: Bool = false @Published var authorized: Bool = false
@Published var testSuceeded: Bool? = nil @Published var testSuceeded: Bool? = nil
@@ -28,8 +30,10 @@ class SSHHandler: @unchecked Sendable, ObservableObject {
init( init(
host: Host host: Host
// hostsManager: HostsManager
) { ) {
self.host = host self.host = host
// self.hostsManager = hostsManager
} }
func getHostkey() -> Data? { func getHostkey() -> Data? {
@@ -47,11 +51,16 @@ class SSHHandler: @unchecked Sendable, ObservableObject {
func go() { func go() {
guard !connected else { guard !connected else {
disconnect() Task {
await disconnect()
}
return return
} }
guard let _ = try? connect() else { return } guard let _ = try? connect() else { return }
if !host.password.isEmpty { if !host.password.isEmpty {
do { try authWithPw() } catch { do { try authWithPw() } catch {
print("pw auth error") print("pw auth error")
@@ -103,19 +112,24 @@ class SSHHandler: @unchecked Sendable, ObservableObject {
return return
} }
func disconnect() { func disconnect() async {
withAnimation { connected = false } await MainActor.run {
withAnimation { authorized = false } withAnimation { connected = false }
withAnimation { testSuceeded = nil } withAnimation { authorized = false }
withAnimation { testSuceeded = nil }
}
ssh_channel_send_eof(self.channel) ssh_channel_send_eof(self.channel)
ssh_channel_close(self.channel)
ssh_channel_free(self.channel) ssh_channel_free(self.channel)
// self.channel = nil self.channel = nil
ssh_disconnect(self.session) ssh_disconnect(self.session)
ssh_free(self.session) ssh_free(self.session)
// self.session = nil self.session = nil
}
func checkHostkey() {
} }
func testExec() { func testExec() {
@@ -321,21 +335,14 @@ class SSHHandler: @unchecked Sendable, ObservableObject {
status = ssh_channel_request_shell(self.channel) status = ssh_channel_request_shell(self.channel)
guard status == SSH_OK else { return } 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? { func readFromChannel() -> String? {
guard connected else { return nil } guard connected else { return nil }
guard ssh_channel_is_open(channel) != 0 || ssh_channel_is_eof(channel) == 0 else { guard ssh_channel_is_open(channel) != 0 || ssh_channel_is_eof(channel) == 0 else {
disconnect() Task { await disconnect() }
return nil return nil
} }
@@ -365,10 +372,8 @@ class SSHHandler: @unchecked Sendable, ObservableObject {
func writeToChannel(_ string: String?) { func writeToChannel(_ string: String?) {
guard let string = string else { return } guard let string = string else { return }
guard channel != nil else { return } guard channel != nil else { return }
guard ssh_channel_is_open(channel) != 0 || ssh_channel_is_eof(channel) == 0 else { guard ssh_channel_is_open(channel) != 0 else { return }
disconnect() guard ssh_channel_is_eof(channel) == 0 else { return }
return
}
var buffer: [CChar] = [] var buffer: [CChar] = []
for byte in string.utf8 { for byte in string.utf8 {

View File

@@ -11,14 +11,12 @@ import SwiftUI
struct ShhShellApp: App { struct ShhShellApp: App {
@StateObject var sshHandler: SSHHandler = SSHHandler(host: Host.blank) @StateObject var sshHandler: SSHHandler = SSHHandler(host: Host.blank)
@StateObject var keyManager: KeyManager = KeyManager() @StateObject var keyManager: KeyManager = KeyManager()
@StateObject var hostsManager: HostsManager = HostsManager()
var body: some Scene { var body: some Scene {
WindowGroup { WindowGroup {
ContentView( ContentView(
handler: sshHandler, handler: sshHandler,
keyManager: keyManager, keyManager: keyManager
hostsManager: hostsManager
) )
.colorScheme(.dark) .colorScheme(.dark)
} }

View File

@@ -10,7 +10,6 @@ import SwiftUI
struct ConnectionView: View { struct ConnectionView: View {
@StateObject var handler: SSHHandler @StateObject var handler: SSHHandler
@StateObject var keyManager: KeyManager @StateObject var keyManager: KeyManager
@StateObject var hostsManager: HostsManager
@State var passphrase: String = "" @State var passphrase: String = ""
@@ -120,7 +119,7 @@ struct ConnectionView: View {
if handler.host.key != nil { if handler.host.key != nil {
Text("Hostkey: \(handler.host.key!.base64EncodedString())") Text("Hostkey: \(handler.host.key!.base64EncodedString())")
.onChange(of: handler.host.key) { _ in .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 { guard handler.host.key == previousKnownHost.key else {
hostKeyChangedAlert = true hostKeyChangedAlert = true
return return
@@ -152,15 +151,17 @@ struct ConnectionView: View {
} }
.alert("Hostkey changed", isPresented: $hostKeyChangedAlert) { .alert("Hostkey changed", isPresented: $hostKeyChangedAlert) {
Button("Accept New Hostkey", role: .destructive) { Button("Accept New Hostkey", role: .destructive) {
hostsManager.updateHost(handler.host) handler.hostsManager.updateHost(handler.host)
} }
Button("Disconnect", role: .cancel) { Button("Disconnect", role: .cancel) {
handler.disconnect() Task {
handler.host.key = hostsManager.getHostMatching(handler.host)?.key await handler.disconnect()
handler.host.key = handler.hostsManager.getHostMatching(handler.host)?.key
}
} }
} message: { } 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) .transition(.opacity)
.toolbar { .toolbar {
@@ -177,8 +178,8 @@ struct ConnectionView: View {
} }
} }
.onDisappear { .onDisappear {
guard hostsManager.getHostMatching(handler.host) == handler.host else { guard handler.hostsManager.getHostMatching(handler.host) == handler.host else {
hostsManager.updateHost(handler.host) handler.hostsManager.updateHost(handler.host)
return return
} }
} }
@@ -197,7 +198,6 @@ struct ConnectionView: View {
#Preview { #Preview {
ConnectionView( ConnectionView(
handler: SSHHandler(host: Host.debug), handler: SSHHandler(host: Host.debug),
keyManager: KeyManager(), keyManager: KeyManager()
hostsManager: HostsManager()
) )
} }

View File

@@ -10,13 +10,12 @@ import SwiftUI
struct ContentView: View { struct ContentView: View {
@ObservedObject var handler: SSHHandler @ObservedObject var handler: SSHHandler
@ObservedObject var keyManager: KeyManager @ObservedObject var keyManager: KeyManager
@ObservedObject var hostsManager: HostsManager
var body: some View { var body: some View {
TabView { TabView {
HostsView( HostsView(
keyManager: keyManager, handler: handler,
hostsManager: hostsManager keyManager: keyManager
) )
.tabItem { .tabItem {
Label("Hosts", systemImage: "server.rack") Label("Hosts", systemImage: "server.rack")
@@ -32,7 +31,6 @@ struct ContentView: View {
#Preview { #Preview {
ContentView( ContentView(
handler: SSHHandler(host: Host.debug), handler: SSHHandler(host: Host.debug),
keyManager: KeyManager(), keyManager: KeyManager()
hostsManager: HostsManager()
) )
} }

View File

@@ -8,28 +8,27 @@
import SwiftUI import SwiftUI
struct HostsView: View { struct HostsView: View {
@ObservedObject var handler: SSHHandler
@ObservedObject var keyManager: KeyManager @ObservedObject var keyManager: KeyManager
@ObservedObject var hostsManager: HostsManager
var body: some View { var body: some View {
NavigationStack { NavigationStack {
List { List {
if hostsManager.savedHosts.isEmpty { if handler.hostsManager.savedHosts.isEmpty {
Text("Add your first Host!") Text("Add your first Host!")
Button() { Button() {
withAnimation { hostsManager.savedHosts.append(Host.blank) } withAnimation { handler.hostsManager.savedHosts.append(Host.blank) }
} label: { } label: {
Text("Create") Text("Create")
// .font() // .font()
} }
.buttonStyle(.borderedProminent) .buttonStyle(.borderedProminent)
} }
ForEach(hostsManager.savedHosts) { host in ForEach(handler.hostsManager.savedHosts) { host in
NavigationLink() { NavigationLink() {
ConnectionView( ConnectionView(
handler: SSHHandler(host: host), handler: SSHHandler(host: host),
keyManager: keyManager, keyManager: keyManager
hostsManager: hostsManager
) )
} label: { } label: {
if host.address.isEmpty { if host.address.isEmpty {
@@ -41,9 +40,9 @@ struct HostsView: View {
.animation(.default, value: host) .animation(.default, value: host)
.swipeActions(edge: .trailing) { .swipeActions(edge: .trailing) {
Button(role: .destructive) { Button(role: .destructive) {
if let index = hostsManager.savedHosts.firstIndex(where: { $0.id == host.id }) { if let index = handler.hostsManager.savedHosts.firstIndex(where: { $0.id == host.id }) {
let _ = withAnimation { hostsManager.savedHosts.remove(at: index) } let _ = withAnimation { handler.hostsManager.savedHosts.remove(at: index) }
hostsManager.saveSavedHosts() handler.hostsManager.saveSavedHosts()
} }
} label: { } label: {
Label("Delete", systemImage: "trash") Label("Delete", systemImage: "trash")
@@ -58,11 +57,10 @@ struct HostsView: View {
NavigationLink { NavigationLink {
ConnectionView( ConnectionView(
handler: SSHHandler(host: host), handler: SSHHandler(host: host),
keyManager: keyManager, keyManager: keyManager
hostsManager: hostsManager
) )
.task(priority: .userInitiated) { .task(priority: .userInitiated) {
withAnimation { hostsManager.savedHosts.append(host) } withAnimation { handler.hostsManager.savedHosts.append(host) }
} }
} label: { } label: {
Label("Add", systemImage: "plus") Label("Add", systemImage: "plus")
@@ -74,5 +72,8 @@ struct HostsView: View {
} }
#Preview { #Preview {
HostsView(keyManager: KeyManager(), hostsManager: HostsManager()) HostsView(
handler: SSHHandler(host: Host.debug),
keyManager: KeyManager()
)
} }

View File

@@ -24,9 +24,29 @@ final class SSHTerminalView: TerminalView, Sendable, @preconcurrency TerminalVie
super.init(frame: frame) super.init(frame: frame)
terminalDelegate = self terminalDelegate = self
sshQueue.async { [self] in sshQueue.async {
guard let handler = handler else { return } 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 { while handler.connected {
if let read = handler.readFromChannel() { if let read = handler.readFromChannel() {
Task { [weak self] in Task { [weak self] in
@@ -34,9 +54,8 @@ final class SSHTerminalView: TerminalView, Sendable, @preconcurrency TerminalVie
await self.feed(text: read) await self.feed(text: read)
} }
} else { } else {
usleep(1_000) Task{ try? await Task.sleep(nanoseconds: 1_000_000) }
} }
// self?.setNeedsDisplay()
} }
} }
} }

View File

@@ -11,13 +11,18 @@ struct ShellView: View {
@ObservedObject var handler: SSHHandler @ObservedObject var handler: SSHHandler
@Environment(\.dismiss) var dismiss @Environment(\.dismiss) var dismiss
@State private var terminalControllerRef: TerminalController?
var body: some View { var body: some View {
NavigationStack { NavigationStack {
ZStack { ZStack {
if !handler.connected { if !handler.connected {
DialogView(handler: handler, showDialog: !handler.connected) DialogView(handler: handler, showDialog: !handler.connected)
} }
TerminalController(handler: handler) terminalControllerRef
}
.task {
terminalControllerRef = TerminalController(handler: handler)
} }
.toolbar { .toolbar {
ToolbarItem { ToolbarItem {

View File

@@ -17,7 +17,7 @@ struct TerminalController: UIViewRepresentable {
let tv = SSHTerminalView( let tv = SSHTerminalView(
frame: CGRect( frame: CGRect(
origin: CGPoint(x: 0, y: 0), origin: CGPoint(x: 0, y: 0),
size: CGSize(width: 100, height: 100) size: .zero
), ),
handler: handler handler: handler
) )