From 331a921499440f240285585926731b20e31fc6fe Mon Sep 17 00:00:00 2001 From: neon443 <69979447+neon443@users.noreply.github.com> Date: Sun, 29 Jun 2025 15:40:46 +0100 Subject: [PATCH] MULTI SESSIONS!!! terminalviewcontainer has a dict of [handler, terminalView] with a uuid key each session gets a new uuid, and on disconnect, the session is removed from terminalviewcontainer sessions list view to reopen sessions remove multiview extracted terminalviewcontainer --- ShhShell.xcodeproj/project.pbxproj | 20 +++++++++++ ShhShell/SSH/SSHHandler.swift | 10 ++++++ ShhShell/Views/ContentView.swift | 6 ++++ ShhShell/Views/Hosts/ConnectionView.swift | 1 - ShhShell/Views/Hosts/HostsView.swift | 11 ------ ShhShell/Views/Sessions/SessionView.swift | 34 +++++++++++++++++++ .../Views/Sessions/SessionsListView.swift | 31 +++++++++++++++++ ShhShell/Views/Terminal/ShellView.swift | 4 ++- .../Views/Terminal/TerminalController.swift | 17 ++++++---- .../Terminal/TerminalViewContainer.swift | 19 +++++++++++ 10 files changed, 133 insertions(+), 20 deletions(-) create mode 100644 ShhShell/Views/Sessions/SessionView.swift create mode 100644 ShhShell/Views/Sessions/SessionsListView.swift create mode 100644 ShhShell/Views/Terminal/TerminalViewContainer.swift diff --git a/ShhShell.xcodeproj/project.pbxproj b/ShhShell.xcodeproj/project.pbxproj index 5416bab..9c0e509 100644 --- a/ShhShell.xcodeproj/project.pbxproj +++ b/ShhShell.xcodeproj/project.pbxproj @@ -40,6 +40,9 @@ A96BE6A22E10846B00C0FEE9 /* xcodeWWDC.plist in Resources */ = {isa = PBXBuildFile; fileRef = A96BE6962E10846B00C0FEE9 /* xcodeWWDC.plist */; }; A96BE6A42E113D9400C0FEE9 /* ThemeCodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = A96BE6A32E113D9400C0FEE9 /* ThemeCodable.swift */; }; A96BE6A62E113DB000C0FEE9 /* ColorCodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = A96BE6A52E113DB000C0FEE9 /* ColorCodable.swift */; }; + A96BE6A82E116E2B00C0FEE9 /* SessionsListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A96BE6A72E116E2B00C0FEE9 /* SessionsListView.swift */; }; + A96BE6AA2E116EC000C0FEE9 /* TerminalViewContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = A96BE6A92E116EC000C0FEE9 /* TerminalViewContainer.swift */; }; + A96BE6AD2E11825800C0FEE9 /* SessionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A96BE6AC2E11825800C0FEE9 /* SessionView.swift */; }; A96C6A8A2E0C0B1100F377FE /* SSHState.swift in Sources */ = {isa = PBXBuildFile; fileRef = A96C6A892E0C0B1100F377FE /* SSHState.swift */; }; A96C6AFE2E0C43B600F377FE /* Keypair.swift in Sources */ = {isa = PBXBuildFile; fileRef = A96C6AFD2E0C43B600F377FE /* Keypair.swift */; }; A96C6B002E0C45FE00F377FE /* KeyDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A96C6AFF2E0C45FE00F377FE /* KeyDetailView.swift */; }; @@ -133,6 +136,9 @@ A96BE6962E10846B00C0FEE9 /* xcodeWWDC.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = xcodeWWDC.plist; sourceTree = ""; }; A96BE6A32E113D9400C0FEE9 /* ThemeCodable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeCodable.swift; sourceTree = ""; }; A96BE6A52E113DB000C0FEE9 /* ColorCodable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorCodable.swift; sourceTree = ""; }; + A96BE6A72E116E2B00C0FEE9 /* SessionsListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionsListView.swift; sourceTree = ""; }; + A96BE6A92E116EC000C0FEE9 /* TerminalViewContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TerminalViewContainer.swift; sourceTree = ""; }; + A96BE6AC2E11825800C0FEE9 /* SessionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionView.swift; sourceTree = ""; }; A96C6A892E0C0B1100F377FE /* SSHState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSHState.swift; sourceTree = ""; }; A96C6AFD2E0C43B600F377FE /* Keypair.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Keypair.swift; sourceTree = ""; }; A96C6AFF2E0C45FE00F377FE /* KeyDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyDetailView.swift; sourceTree = ""; }; @@ -190,6 +196,7 @@ A92317292E07113100ECE1E6 /* TerminalController.swift */, A923172E2E08851200ECE1E6 /* ShellView.swift */, A923172C2E07138000ECE1E6 /* SSHTerminalDelegate.swift */, + A96BE6A92E116EC000C0FEE9 /* TerminalViewContainer.swift */, ); path = Terminal; sourceTree = ""; @@ -253,6 +260,7 @@ isa = PBXGroup; children = ( A92538C52DEE0742007E0A18 /* ContentView.swift */, + A96BE6AB2E11824B00C0FEE9 /* Sessions */, A9D8192B2E0E9EA400442D38 /* Themes */, A98554532E05534F009051BD /* Keys */, A96C6B042E0C523E00F377FE /* Hosts */, @@ -311,6 +319,15 @@ path = ci_scripts; sourceTree = ""; }; + A96BE6AB2E11824B00C0FEE9 /* Sessions */ = { + isa = PBXGroup; + children = ( + A96BE6A72E116E2B00C0FEE9 /* SessionsListView.swift */, + A96BE6AC2E11825800C0FEE9 /* SessionView.swift */, + ); + path = Sessions; + sourceTree = ""; + }; A96C6B032E0C523600F377FE /* Misc */ = { isa = PBXGroup; children = ( @@ -562,8 +579,10 @@ A93143C02DF61B3200FCD5DB /* Host.swift in Sources */, A9B15A9A2E0ABA0400F66E02 /* DialogView.swift in Sources */, A92538C92DEE0742007E0A18 /* ShhShellApp.swift in Sources */, + A96BE6AD2E11825800C0FEE9 /* SessionView.swift in Sources */, A96C6B002E0C45FE00F377FE /* KeyDetailView.swift in Sources */, A9DA97712E0D30ED00142DDC /* HostSymbol.swift in Sources */, + A96BE6A82E116E2B00C0FEE9 /* SessionsListView.swift in Sources */, A98554612E058433009051BD /* HostsManager.swift in Sources */, A985545D2E055D4D009051BD /* ConnectionView.swift in Sources */, A98554592E0553AA009051BD /* KeyManager.swift in Sources */, @@ -573,6 +592,7 @@ A923172D2E07138000ECE1E6 /* SSHTerminalDelegate.swift in Sources */, A96C6AFE2E0C43B600F377FE /* Keypair.swift in Sources */, A9C4140C2E096DB7005E3047 /* SSHError.swift in Sources */, + A96BE6AA2E116EC000C0FEE9 /* TerminalViewContainer.swift in Sources */, A923172A2E07113100ECE1E6 /* TerminalController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/ShhShell/SSH/SSHHandler.swift b/ShhShell/SSH/SSHHandler.swift index 8aa2824..c079595 100644 --- a/ShhShell/SSH/SSHHandler.swift +++ b/ShhShell/SSH/SSHHandler.swift @@ -9,11 +9,14 @@ import Foundation import LibSSH import OSLog import SwiftUI +import SwiftTerm class SSHHandler: @unchecked Sendable, ObservableObject { private var session: ssh_session? private var channel: ssh_channel? + var sessionID: UUID? + var scrollback: [String] = [] var scrollbackSize = 0.0 @@ -101,6 +104,7 @@ class SSHHandler: @unchecked Sendable, ObservableObject { func connect() throws(SSHError) { guard !host.address.isEmpty else { throw .connectionFailed("No address to connect to.") } withAnimation { state = .connecting } + sessionID = UUID() var verbosity: Int = 0 // var verbosity: Int = SSH_LOG_FUNCTIONS @@ -134,6 +138,12 @@ class SSHHandler: @unchecked Sendable, ObservableObject { withAnimation { self.testSuceeded = nil } } + if let sessionID { + Task { @MainActor in + TerminalViewContainer.shared.removeValue(forKey: sessionID) + self.sessionID = nil + } + } scrollback = [] scrollbackSize = 0 diff --git a/ShhShell/Views/ContentView.swift b/ShhShell/Views/ContentView.swift index 812aa12..8e122a5 100644 --- a/ShhShell/Views/ContentView.swift +++ b/ShhShell/Views/ContentView.swift @@ -15,6 +15,12 @@ struct ContentView: View { var body: some View { NavigationStack { List { + SessionsListView( + handler: handler, + hostsManager: hostsManager, + keyManager: keyManager + ) + HostsView( handler: handler, hostsManager: hostsManager, diff --git a/ShhShell/Views/Hosts/ConnectionView.swift b/ShhShell/Views/Hosts/ConnectionView.swift index 6fec5df..138ab06 100644 --- a/ShhShell/Views/Hosts/ConnectionView.swift +++ b/ShhShell/Views/Hosts/ConnectionView.swift @@ -147,7 +147,6 @@ struct ConnectionView: View { Button() { handler.go() showTerminal = checkShell(handler.state) - TerminalController.TerminalViewContainer.shared = nil } label: { Label( handler.connected ? "Disconnect" : "Connect", diff --git a/ShhShell/Views/Hosts/HostsView.swift b/ShhShell/Views/Hosts/HostsView.swift index cfa2438..a6e4d39 100644 --- a/ShhShell/Views/Hosts/HostsView.swift +++ b/ShhShell/Views/Hosts/HostsView.swift @@ -17,17 +17,6 @@ struct HostsView: View { Text("Add your first Host!") } - //proves that u can connect to multiple at the same time - NavigationLink() { - ForEach(hostsManager.hosts) { host in - let miniHandler = SSHHandler(host: host) - TerminalController(handler: miniHandler, hostsManager: hostsManager) - .onAppear { miniHandler.go() } - } - } label: { - Label("multiview", systemImage: "square.split.2x2") - } - ForEach(hostsManager.hosts) { host in NavigationLink() { ConnectionView( diff --git a/ShhShell/Views/Sessions/SessionView.swift b/ShhShell/Views/Sessions/SessionView.swift new file mode 100644 index 0000000..5d1974c --- /dev/null +++ b/ShhShell/Views/Sessions/SessionView.swift @@ -0,0 +1,34 @@ +// +// SessionView.swift +// ShhShell +// +// Created by neon443 on 29/06/2025. +// + +import SwiftUI + +struct SessionView: View { + @ObservedObject var hostsManager: HostsManager + @State var key: UUID + @State var shellPresented: Bool = false + + var body: some View { + Text(key.uuidString) + .onTapGesture { + shellPresented.toggle() + } + .fullScreenCover(isPresented: $shellPresented) { + ShellView( + handler: TerminalViewContainer.shared[key]!.handler, + hostsManager: hostsManager + ) + } + } +} + +#Preview { + SessionView( + hostsManager: HostsManager(), + key: UUID() + ) +} diff --git a/ShhShell/Views/Sessions/SessionsListView.swift b/ShhShell/Views/Sessions/SessionsListView.swift new file mode 100644 index 0000000..0768ca1 --- /dev/null +++ b/ShhShell/Views/Sessions/SessionsListView.swift @@ -0,0 +1,31 @@ +// +// SessionsView.swift +// ShhShell +// +// Created by neon443 on 29/06/2025. +// + +import SwiftUI +import SwiftTerm + +struct SessionsListView: View { + @ObservedObject var handler: SSHHandler + @ObservedObject var hostsManager: HostsManager + @ObservedObject var keyManager: KeyManager + + var body: some View { + Section("Sessions") { + ForEach(TerminalViewContainer.shared.map {$0.key}, id: \.self) { key in + SessionView(hostsManager: hostsManager, key: key) + } + } + } +} + +#Preview { + SessionsListView( + handler: SSHHandler(host: Host.debug), + hostsManager: HostsManager(), + keyManager: KeyManager() + ) +} diff --git a/ShhShell/Views/Terminal/ShellView.swift b/ShhShell/Views/Terminal/ShellView.swift index 3cdff3a..edf2424 100644 --- a/ShhShell/Views/Terminal/ShellView.swift +++ b/ShhShell/Views/Terminal/ShellView.swift @@ -18,7 +18,9 @@ struct ShellView: View { ZStack { TerminalController(handler: handler, hostsManager: hostsManager) .onAppear { - TerminalController.TerminalViewContainer.shared?.restoreScrollback() + if let sessionID = handler.sessionID { + TerminalViewContainer.shared[sessionID]?.terminalView.restoreScrollback() + } } Group { diff --git a/ShhShell/Views/Terminal/TerminalController.swift b/ShhShell/Views/Terminal/TerminalController.swift index 11a2ee2..e8d167d 100644 --- a/ShhShell/Views/Terminal/TerminalController.swift +++ b/ShhShell/Views/Terminal/TerminalController.swift @@ -14,13 +14,11 @@ struct TerminalController: UIViewRepresentable { @ObservedObject var handler: SSHHandler @ObservedObject var hostsManager: HostsManager - final class TerminalViewContainer { - @MainActor static var shared: SSHTerminalDelegate? - } - func makeUIView(context: Context) -> TerminalView { - if let existing = TerminalViewContainer.shared { - return existing + if let sessionID = handler.sessionID { + if let existing = TerminalViewContainer.shared[sessionID] { + return existing.terminalView + } } let tv = SSHTerminalDelegate( @@ -31,7 +29,12 @@ struct TerminalController: UIViewRepresentable { tv.translatesAutoresizingMaskIntoConstraints = false tv.autoresizingMask = [.flexibleWidth, .flexibleHeight] - TerminalViewContainer.shared = tv + if let sessionID = handler.sessionID { + TerminalViewContainer.shared[sessionID] = TerminalContainer( + handler: handler, + terminalView: tv + ) + } return tv } diff --git a/ShhShell/Views/Terminal/TerminalViewContainer.swift b/ShhShell/Views/Terminal/TerminalViewContainer.swift new file mode 100644 index 0000000..1b1f27c --- /dev/null +++ b/ShhShell/Views/Terminal/TerminalViewContainer.swift @@ -0,0 +1,19 @@ +// +// TerminalViewContainer.swift +// ShhShell +// +// Created by neon443 on 29/06/2025. +// + +import Foundation + +public final class TerminalViewContainer { + @MainActor static var shared: [ + UUID: TerminalContainer + ] = [:] +} + +struct TerminalContainer { + var handler: SSHHandler + var terminalView: SSHTerminalDelegate +}