using a strikethrough for disconnected sessions

add cancel to disconnected alert
stop jumping to last session if disconnected
re-add to TerminalViewContainer if reconnect happens
move ssh cleanup stuff to cleanup func, only run if we indicate we want a new session
This commit is contained in:
neon443
2025-09-01 21:40:01 +01:00
parent 68fb7d4844
commit 0cd92ee12a
4 changed files with 57 additions and 31 deletions

View File

@@ -59,7 +59,7 @@ class SSHHandler: @unchecked Sendable, ObservableObject {
return String(cString: cString) return String(cString: cString)
} }
func go(id: UUID = UUID()) { func go(id: UUID? = nil) {
guard !connected else { disconnect(); return } guard !connected else { disconnect(); return }
do { try connect(id: id) } catch { do { try connect(id: id) } catch {
@@ -108,10 +108,15 @@ class SSHHandler: @unchecked Sendable, ObservableObject {
setTitle("\(host.username)@\(host.address)") 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.") } guard !host.address.isEmpty else { throw .connectionFailed("No address to connect to.") }
withAnimation { state = .connecting } withAnimation { state = .connecting }
sessionID = id if let id {
sessionID = id
} else {
cleanup()
sessionID = UUID()
}
var verbosity: Int = 0 var verbosity: Int = 0
// var verbosity: Int = SSH_LOG_FUNCTIONS // var verbosity: Int = SSH_LOG_FUNCTIONS
@@ -145,21 +150,7 @@ class SSHHandler: @unchecked Sendable, ObservableObject {
} }
func disconnect() { func disconnect() {
// Task { withAnimation { self.state = .idle }
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
//send eof if open //send eof if open
if ssh_channel_is_open(channel) == 1 { if ssh_channel_is_open(channel) == 1 {
ssh_channel_send_eof(channel) ssh_channel_send_eof(channel)
@@ -174,6 +165,20 @@ class SSHHandler: @unchecked Sendable, ObservableObject {
self.session = nil 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?) { func checkHostkey(recieved: String?) {
guard host.key == recieved else { guard host.key == recieved else {
self.hostkeyChanged = true self.hostkeyChanged = true

View File

@@ -101,13 +101,19 @@ final class SSHTerminalDelegate: TerminalView, Sendable, @preconcurrency Termina
func startFeedLoop() { func startFeedLoop() {
guard readTimer == nil else { return } guard readTimer == nil else { return }
readTimer = Timer(timeInterval: 0.01, repeats: true) { timer in readTimer = Timer(timeInterval: 0.01, repeats: true) { timer in
Task(priority: .high) { Task(priority: .high) { @MainActor in
guard let handler = await self.handler else { return } guard let handler = self.handler else { return }
guard let sessionID = handler.sessionID else { return }
if let read = handler.readFromChannel() { if let read = handler.readFromChannel() {
Task { @MainActor in Task { @MainActor in
self.feed(text: read) 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) RunLoop.main.add(readTimer!, forMode: .common)

View File

@@ -50,12 +50,12 @@ struct ShellTabView: View {
background background
.ignoresSafeArea(.all) .ignoresSafeArea(.all)
VStack(spacing: 0) { VStack(spacing: 0) {
let oneTabWidth = max(100, (UIScreen.main.bounds.width)/CGFloat(container.sessionIDs.count)) //header
HStack(alignment: .center, spacing: 10) { HStack(alignment: .center, spacing: 10) {
Button() { Button() {
for session in container.sessions.values { for session in container.sessions.values {
session.handler.disconnect() session.handler.disconnect()
session.handler.cleanup()
} }
dismiss() dismiss()
} label: { } label: {
@@ -73,12 +73,14 @@ struct ShellTabView: View {
.foregroundStyle(foreground) .foregroundStyle(foreground)
.monospaced() .monospaced()
.contentTransition(.numericText()) .contentTransition(.numericText())
.strikethrough(selectedHandler.state != .shellOpen)
if container.sessionIDs.count == 1 { if container.sessionIDs.count == 1 {
Text(selectedHandler.host.description) Text(selectedHandler.host.description)
.bold() .bold()
.foregroundStyle(foreground) .foregroundStyle(foreground)
.monospaced() .monospaced()
.font(.caption2) .font(.caption2)
.strikethrough(selectedHandler.state != .shellOpen)
} }
} }
Spacer() Spacer()
@@ -116,11 +118,14 @@ struct ShellTabView: View {
.background(hostsManager.tint, ignoresSafeAreaEdges: .all) .background(hostsManager.tint, ignoresSafeAreaEdges: .all)
.frame(height: 40) .frame(height: 40)
//tab strip
if container.sessionIDs.count > 1 { if container.sessionIDs.count > 1 {
ScrollView(.horizontal, showsIndicators: false) { ScrollView(.horizontal, showsIndicators: false) {
let oneTabWidth: CGFloat = max(100, (UIScreen.main.bounds.width)/CGFloat(container.sessionIDs.count))
HStack(spacing: 0) { HStack(spacing: 0) {
ForEach(container.sessionIDs, id: \.self) { id in ForEach(container.sessionIDs, id: \.self) { id in
let selected: Bool = selectedID == id let selected: Bool = selectedID == id
let thisHandler: SSHHandler = container.sessions[id]!.handler
ZStack { ZStack {
Rectangle() Rectangle()
.fill(selected ? hostsManager.tint : background) .fill(selected ? hostsManager.tint : background)
@@ -133,6 +138,7 @@ struct ShellTabView: View {
.foregroundStyle(selected ? foreground : hostsManager.tint) .foregroundStyle(selected ? foreground : hostsManager.tint)
.opacity(0.7) .opacity(0.7)
.font(.callout) .font(.callout)
.strikethrough(thisHandler.state != .shellOpen)
} }
Text(container.sessions[id]!.handler.host.description) Text(container.sessions[id]!.handler.host.description)
.foregroundStyle(selected ? foreground : hostsManager.tint) .foregroundStyle(selected ? foreground : hostsManager.tint)
@@ -140,6 +146,7 @@ struct ShellTabView: View {
.monospaced() .monospaced()
.bold(selected) .bold(selected)
.font(.caption2) .font(.caption2)
.strikethrough(thisHandler.state != .shellOpen)
} }
Spacer() Spacer()
} }
@@ -168,13 +175,6 @@ struct ShellTabView: View {
} }
} }
.onDisappear { .onDisappear {
if !checkShell(session.handler.state) {
if let lastSession = container.sessionIDs.last {
withAnimation { self.selectedID = lastSession }
} else {
dismiss()
}
}
UIApplication.shared.isIdleTimerDisabled = false UIApplication.shared.isIdleTimerDisabled = false
if container.sessions.isEmpty { if container.sessions.isEmpty {
Backgrounder.shared.stopBgTracking() Backgrounder.shared.stopBgTracking()

View File

@@ -13,6 +13,8 @@ struct ShellView: View {
@ObservedObject var hostsManager: HostsManager @ObservedObject var hostsManager: HostsManager
@ObservedObject var container = TerminalViewContainer.shared @ObservedObject var container = TerminalViewContainer.shared
@State private var forceDismissDisconnectAlert: Bool = false
@Environment(\.dismiss) var dismiss @Environment(\.dismiss) var dismiss
var body: some View { var body: some View {
@@ -33,6 +35,9 @@ struct ShellView: View {
.allowsHitTesting(false) .allowsHitTesting(false)
} }
} }
.onAppear {
forceDismissDisconnectAlert = false
}
Group { Group {
Color.gray.opacity(0.2) Color.gray.opacity(0.2)
@@ -56,7 +61,7 @@ struct ShellView: View {
} }
} }
if !checkShell(handler.state) { if handler.state != .shellOpen && !forceDismissDisconnectAlert {
ZStack { ZStack {
RoundedRectangle(cornerRadius: 25) RoundedRectangle(cornerRadius: 25)
.fill(hostsManager.selectedTheme.foreground.suiColor) .fill(hostsManager.selectedTheme.foreground.suiColor)
@@ -74,7 +79,7 @@ struct ShellView: View {
.font(.title) .font(.title)
} }
Button { Button {
handler.go() try! handler.reconnect()
} label: { } label: {
Text("Connect") Text("Connect")
.foregroundStyle(hostsManager.selectedTheme.background.suiColor) .foregroundStyle(hostsManager.selectedTheme.background.suiColor)
@@ -83,6 +88,16 @@ struct ShellView: View {
.background(.tint) .background(.tint)
.clipShape(RoundedRectangle(cornerRadius: 15)) .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) .padding(10)
} }