diff --git a/ShhShell/SSH/SSHHandler.swift b/ShhShell/SSH/SSHHandler.swift index 1272bb5..64adeb5 100644 --- a/ShhShell/SSH/SSHHandler.swift +++ b/ShhShell/SSH/SSHHandler.swift @@ -59,7 +59,7 @@ class SSHHandler: @unchecked Sendable, ObservableObject { return String(cString: cString) } - func go(id: UUID = UUID()) { + func go(id: UUID? = nil) { guard !connected else { disconnect(); return } do { try connect(id: id) } catch { @@ -108,10 +108,15 @@ class SSHHandler: @unchecked Sendable, ObservableObject { setTitle("\(host.username)@\(host.address)") } - func connect(id: UUID) throws(SSHError) { + func connect(id: UUID?) throws(SSHError) { guard !host.address.isEmpty else { throw .connectionFailed("No address to connect to.") } withAnimation { state = .connecting } - sessionID = id + if let id { + sessionID = id + } else { + cleanup() + sessionID = UUID() + } var verbosity: Int = 0 // var verbosity: Int = SSH_LOG_FUNCTIONS @@ -145,21 +150,7 @@ class SSHHandler: @unchecked Sendable, ObservableObject { } func disconnect() { -// Task { - self.hostkeyChanged = false - withAnimation { self.state = .idle } - withAnimation { self.testSuceeded = nil } -// } - - if let sessionID { - Task { @MainActor in - container.sessions.removeValue(forKey: sessionID) - self.sessionID = nil - } - } - scrollback = [] -// scrollbackSize = 0 - + withAnimation { self.state = .idle } //send eof if open if ssh_channel_is_open(channel) == 1 { ssh_channel_send_eof(channel) @@ -174,6 +165,20 @@ class SSHHandler: @unchecked Sendable, ObservableObject { self.session = nil } + func cleanup() { + self.hostkeyChanged = false + withAnimation { self.state = .idle } + withAnimation { self.testSuceeded = nil } + scrollback = [] + + if let sessionID { + Task { @MainActor in + container.sessions.removeValue(forKey: sessionID) + self.sessionID = nil + } + } + } + func checkHostkey(recieved: String?) { guard host.key == recieved else { self.hostkeyChanged = true diff --git a/ShhShell/Terminal/SSHTerminalDelegate.swift b/ShhShell/Terminal/SSHTerminalDelegate.swift index 1dd7450..43dca40 100644 --- a/ShhShell/Terminal/SSHTerminalDelegate.swift +++ b/ShhShell/Terminal/SSHTerminalDelegate.swift @@ -101,13 +101,19 @@ final class SSHTerminalDelegate: TerminalView, Sendable, @preconcurrency Termina func startFeedLoop() { guard readTimer == nil else { return } readTimer = Timer(timeInterval: 0.01, repeats: true) { timer in - Task(priority: .high) { - guard let handler = await self.handler else { return } + Task(priority: .high) { @MainActor in + guard let handler = self.handler else { return } + guard let sessionID = handler.sessionID else { return } if let read = handler.readFromChannel() { Task { @MainActor in self.feed(text: read) } } + if !TerminalViewContainer.shared.sessionIDs.contains(sessionID) { + Task(priority: .high) { @MainActor in + TerminalViewContainer.shared.sessions[sessionID] = TerminalContainer(handler: handler, terminalView: self) + } + } } } RunLoop.main.add(readTimer!, forMode: .common) diff --git a/ShhShell/Views/Terminal/ShellTabView.swift b/ShhShell/Views/Terminal/ShellTabView.swift index ee004ac..c75f24f 100644 --- a/ShhShell/Views/Terminal/ShellTabView.swift +++ b/ShhShell/Views/Terminal/ShellTabView.swift @@ -50,12 +50,12 @@ struct ShellTabView: View { background .ignoresSafeArea(.all) VStack(spacing: 0) { - let oneTabWidth = max(100, (UIScreen.main.bounds.width)/CGFloat(container.sessionIDs.count)) - + //header HStack(alignment: .center, spacing: 10) { Button() { for session in container.sessions.values { session.handler.disconnect() + session.handler.cleanup() } dismiss() } label: { @@ -73,12 +73,14 @@ struct ShellTabView: View { .foregroundStyle(foreground) .monospaced() .contentTransition(.numericText()) + .strikethrough(selectedHandler.state != .shellOpen) if container.sessionIDs.count == 1 { Text(selectedHandler.host.description) .bold() .foregroundStyle(foreground) .monospaced() .font(.caption2) + .strikethrough(selectedHandler.state != .shellOpen) } } Spacer() @@ -116,11 +118,14 @@ struct ShellTabView: View { .background(hostsManager.tint, ignoresSafeAreaEdges: .all) .frame(height: 40) + //tab strip if container.sessionIDs.count > 1 { ScrollView(.horizontal, showsIndicators: false) { + let oneTabWidth: CGFloat = max(100, (UIScreen.main.bounds.width)/CGFloat(container.sessionIDs.count)) HStack(spacing: 0) { ForEach(container.sessionIDs, id: \.self) { id in let selected: Bool = selectedID == id + let thisHandler: SSHHandler = container.sessions[id]!.handler ZStack { Rectangle() .fill(selected ? hostsManager.tint : background) @@ -133,6 +138,7 @@ struct ShellTabView: View { .foregroundStyle(selected ? foreground : hostsManager.tint) .opacity(0.7) .font(.callout) + .strikethrough(thisHandler.state != .shellOpen) } Text(container.sessions[id]!.handler.host.description) .foregroundStyle(selected ? foreground : hostsManager.tint) @@ -140,6 +146,7 @@ struct ShellTabView: View { .monospaced() .bold(selected) .font(.caption2) + .strikethrough(thisHandler.state != .shellOpen) } Spacer() } @@ -168,13 +175,6 @@ struct ShellTabView: View { } } .onDisappear { - if !checkShell(session.handler.state) { - if let lastSession = container.sessionIDs.last { - withAnimation { self.selectedID = lastSession } - } else { - dismiss() - } - } UIApplication.shared.isIdleTimerDisabled = false if container.sessions.isEmpty { Backgrounder.shared.stopBgTracking() diff --git a/ShhShell/Views/Terminal/ShellView.swift b/ShhShell/Views/Terminal/ShellView.swift index d7456a2..74868ce 100644 --- a/ShhShell/Views/Terminal/ShellView.swift +++ b/ShhShell/Views/Terminal/ShellView.swift @@ -13,6 +13,8 @@ struct ShellView: View { @ObservedObject var hostsManager: HostsManager @ObservedObject var container = TerminalViewContainer.shared + @State private var forceDismissDisconnectAlert: Bool = false + @Environment(\.dismiss) var dismiss var body: some View { @@ -33,6 +35,9 @@ struct ShellView: View { .allowsHitTesting(false) } } + .onAppear { + forceDismissDisconnectAlert = false + } Group { Color.gray.opacity(0.2) @@ -56,7 +61,7 @@ struct ShellView: View { } } - if !checkShell(handler.state) { + if handler.state != .shellOpen && !forceDismissDisconnectAlert { ZStack { RoundedRectangle(cornerRadius: 25) .fill(hostsManager.selectedTheme.foreground.suiColor) @@ -74,7 +79,7 @@ struct ShellView: View { .font(.title) } Button { - handler.go() + try! handler.reconnect() } label: { Text("Connect") .foregroundStyle(hostsManager.selectedTheme.background.suiColor) @@ -83,6 +88,16 @@ struct ShellView: View { .background(.tint) .clipShape(RoundedRectangle(cornerRadius: 15)) } + Button { + forceDismissDisconnectAlert = true + } label: { + Text("Cancel") + .foregroundStyle(hostsManager.selectedTheme.background.suiColor) + .padding(5) + .frame(maxWidth: .infinity) + .background(.tint.opacity(0.5)) + .clipShape(RoundedRectangle(cornerRadius: 15)) + } } .padding(10) }