mirror of
https://github.com/neon443/ShhShell.git
synced 2026-03-11 13:26:16 +00:00
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:
@@ -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 {
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user