mirror of
https://github.com/neon443/ShhShell.git
synced 2026-03-11 21:36:17 +00:00
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
This commit is contained in:
@@ -40,6 +40,9 @@
|
|||||||
A96BE6A22E10846B00C0FEE9 /* xcodeWWDC.plist in Resources */ = {isa = PBXBuildFile; fileRef = A96BE6962E10846B00C0FEE9 /* xcodeWWDC.plist */; };
|
A96BE6A22E10846B00C0FEE9 /* xcodeWWDC.plist in Resources */ = {isa = PBXBuildFile; fileRef = A96BE6962E10846B00C0FEE9 /* xcodeWWDC.plist */; };
|
||||||
A96BE6A42E113D9400C0FEE9 /* ThemeCodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = A96BE6A32E113D9400C0FEE9 /* ThemeCodable.swift */; };
|
A96BE6A42E113D9400C0FEE9 /* ThemeCodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = A96BE6A32E113D9400C0FEE9 /* ThemeCodable.swift */; };
|
||||||
A96BE6A62E113DB000C0FEE9 /* ColorCodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = A96BE6A52E113DB000C0FEE9 /* ColorCodable.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 */; };
|
A96C6A8A2E0C0B1100F377FE /* SSHState.swift in Sources */ = {isa = PBXBuildFile; fileRef = A96C6A892E0C0B1100F377FE /* SSHState.swift */; };
|
||||||
A96C6AFE2E0C43B600F377FE /* Keypair.swift in Sources */ = {isa = PBXBuildFile; fileRef = A96C6AFD2E0C43B600F377FE /* Keypair.swift */; };
|
A96C6AFE2E0C43B600F377FE /* Keypair.swift in Sources */ = {isa = PBXBuildFile; fileRef = A96C6AFD2E0C43B600F377FE /* Keypair.swift */; };
|
||||||
A96C6B002E0C45FE00F377FE /* KeyDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A96C6AFF2E0C45FE00F377FE /* KeyDetailView.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 = "<group>"; };
|
A96BE6962E10846B00C0FEE9 /* xcodeWWDC.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = xcodeWWDC.plist; sourceTree = "<group>"; };
|
||||||
A96BE6A32E113D9400C0FEE9 /* ThemeCodable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeCodable.swift; sourceTree = "<group>"; };
|
A96BE6A32E113D9400C0FEE9 /* ThemeCodable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeCodable.swift; sourceTree = "<group>"; };
|
||||||
A96BE6A52E113DB000C0FEE9 /* ColorCodable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorCodable.swift; sourceTree = "<group>"; };
|
A96BE6A52E113DB000C0FEE9 /* ColorCodable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorCodable.swift; sourceTree = "<group>"; };
|
||||||
|
A96BE6A72E116E2B00C0FEE9 /* SessionsListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionsListView.swift; sourceTree = "<group>"; };
|
||||||
|
A96BE6A92E116EC000C0FEE9 /* TerminalViewContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TerminalViewContainer.swift; sourceTree = "<group>"; };
|
||||||
|
A96BE6AC2E11825800C0FEE9 /* SessionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionView.swift; sourceTree = "<group>"; };
|
||||||
A96C6A892E0C0B1100F377FE /* SSHState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSHState.swift; sourceTree = "<group>"; };
|
A96C6A892E0C0B1100F377FE /* SSHState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSHState.swift; sourceTree = "<group>"; };
|
||||||
A96C6AFD2E0C43B600F377FE /* Keypair.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Keypair.swift; sourceTree = "<group>"; };
|
A96C6AFD2E0C43B600F377FE /* Keypair.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Keypair.swift; sourceTree = "<group>"; };
|
||||||
A96C6AFF2E0C45FE00F377FE /* KeyDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyDetailView.swift; sourceTree = "<group>"; };
|
A96C6AFF2E0C45FE00F377FE /* KeyDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyDetailView.swift; sourceTree = "<group>"; };
|
||||||
@@ -190,6 +196,7 @@
|
|||||||
A92317292E07113100ECE1E6 /* TerminalController.swift */,
|
A92317292E07113100ECE1E6 /* TerminalController.swift */,
|
||||||
A923172E2E08851200ECE1E6 /* ShellView.swift */,
|
A923172E2E08851200ECE1E6 /* ShellView.swift */,
|
||||||
A923172C2E07138000ECE1E6 /* SSHTerminalDelegate.swift */,
|
A923172C2E07138000ECE1E6 /* SSHTerminalDelegate.swift */,
|
||||||
|
A96BE6A92E116EC000C0FEE9 /* TerminalViewContainer.swift */,
|
||||||
);
|
);
|
||||||
path = Terminal;
|
path = Terminal;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -253,6 +260,7 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
A92538C52DEE0742007E0A18 /* ContentView.swift */,
|
A92538C52DEE0742007E0A18 /* ContentView.swift */,
|
||||||
|
A96BE6AB2E11824B00C0FEE9 /* Sessions */,
|
||||||
A9D8192B2E0E9EA400442D38 /* Themes */,
|
A9D8192B2E0E9EA400442D38 /* Themes */,
|
||||||
A98554532E05534F009051BD /* Keys */,
|
A98554532E05534F009051BD /* Keys */,
|
||||||
A96C6B042E0C523E00F377FE /* Hosts */,
|
A96C6B042E0C523E00F377FE /* Hosts */,
|
||||||
@@ -311,6 +319,15 @@
|
|||||||
path = ci_scripts;
|
path = ci_scripts;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
A96BE6AB2E11824B00C0FEE9 /* Sessions */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
A96BE6A72E116E2B00C0FEE9 /* SessionsListView.swift */,
|
||||||
|
A96BE6AC2E11825800C0FEE9 /* SessionView.swift */,
|
||||||
|
);
|
||||||
|
path = Sessions;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
A96C6B032E0C523600F377FE /* Misc */ = {
|
A96C6B032E0C523600F377FE /* Misc */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@@ -562,8 +579,10 @@
|
|||||||
A93143C02DF61B3200FCD5DB /* Host.swift in Sources */,
|
A93143C02DF61B3200FCD5DB /* Host.swift in Sources */,
|
||||||
A9B15A9A2E0ABA0400F66E02 /* DialogView.swift in Sources */,
|
A9B15A9A2E0ABA0400F66E02 /* DialogView.swift in Sources */,
|
||||||
A92538C92DEE0742007E0A18 /* ShhShellApp.swift in Sources */,
|
A92538C92DEE0742007E0A18 /* ShhShellApp.swift in Sources */,
|
||||||
|
A96BE6AD2E11825800C0FEE9 /* SessionView.swift in Sources */,
|
||||||
A96C6B002E0C45FE00F377FE /* KeyDetailView.swift in Sources */,
|
A96C6B002E0C45FE00F377FE /* KeyDetailView.swift in Sources */,
|
||||||
A9DA97712E0D30ED00142DDC /* HostSymbol.swift in Sources */,
|
A9DA97712E0D30ED00142DDC /* HostSymbol.swift in Sources */,
|
||||||
|
A96BE6A82E116E2B00C0FEE9 /* SessionsListView.swift in Sources */,
|
||||||
A98554612E058433009051BD /* HostsManager.swift in Sources */,
|
A98554612E058433009051BD /* HostsManager.swift in Sources */,
|
||||||
A985545D2E055D4D009051BD /* ConnectionView.swift in Sources */,
|
A985545D2E055D4D009051BD /* ConnectionView.swift in Sources */,
|
||||||
A98554592E0553AA009051BD /* KeyManager.swift in Sources */,
|
A98554592E0553AA009051BD /* KeyManager.swift in Sources */,
|
||||||
@@ -573,6 +592,7 @@
|
|||||||
A923172D2E07138000ECE1E6 /* SSHTerminalDelegate.swift in Sources */,
|
A923172D2E07138000ECE1E6 /* SSHTerminalDelegate.swift in Sources */,
|
||||||
A96C6AFE2E0C43B600F377FE /* Keypair.swift in Sources */,
|
A96C6AFE2E0C43B600F377FE /* Keypair.swift in Sources */,
|
||||||
A9C4140C2E096DB7005E3047 /* SSHError.swift in Sources */,
|
A9C4140C2E096DB7005E3047 /* SSHError.swift in Sources */,
|
||||||
|
A96BE6AA2E116EC000C0FEE9 /* TerminalViewContainer.swift in Sources */,
|
||||||
A923172A2E07113100ECE1E6 /* TerminalController.swift in Sources */,
|
A923172A2E07113100ECE1E6 /* TerminalController.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
|||||||
@@ -9,11 +9,14 @@ import Foundation
|
|||||||
import LibSSH
|
import LibSSH
|
||||||
import OSLog
|
import OSLog
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import SwiftTerm
|
||||||
|
|
||||||
class SSHHandler: @unchecked Sendable, ObservableObject {
|
class SSHHandler: @unchecked Sendable, ObservableObject {
|
||||||
private var session: ssh_session?
|
private var session: ssh_session?
|
||||||
private var channel: ssh_channel?
|
private var channel: ssh_channel?
|
||||||
|
|
||||||
|
var sessionID: UUID?
|
||||||
|
|
||||||
var scrollback: [String] = []
|
var scrollback: [String] = []
|
||||||
var scrollbackSize = 0.0
|
var scrollbackSize = 0.0
|
||||||
|
|
||||||
@@ -101,6 +104,7 @@ class SSHHandler: @unchecked Sendable, ObservableObject {
|
|||||||
func connect() throws(SSHError) {
|
func connect() 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 = UUID()
|
||||||
|
|
||||||
var verbosity: Int = 0
|
var verbosity: Int = 0
|
||||||
// var verbosity: Int = SSH_LOG_FUNCTIONS
|
// var verbosity: Int = SSH_LOG_FUNCTIONS
|
||||||
@@ -134,6 +138,12 @@ class SSHHandler: @unchecked Sendable, ObservableObject {
|
|||||||
withAnimation { self.testSuceeded = nil }
|
withAnimation { self.testSuceeded = nil }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let sessionID {
|
||||||
|
Task { @MainActor in
|
||||||
|
TerminalViewContainer.shared.removeValue(forKey: sessionID)
|
||||||
|
self.sessionID = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
scrollback = []
|
scrollback = []
|
||||||
scrollbackSize = 0
|
scrollbackSize = 0
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,12 @@ struct ContentView: View {
|
|||||||
var body: some View {
|
var body: some View {
|
||||||
NavigationStack {
|
NavigationStack {
|
||||||
List {
|
List {
|
||||||
|
SessionsListView(
|
||||||
|
handler: handler,
|
||||||
|
hostsManager: hostsManager,
|
||||||
|
keyManager: keyManager
|
||||||
|
)
|
||||||
|
|
||||||
HostsView(
|
HostsView(
|
||||||
handler: handler,
|
handler: handler,
|
||||||
hostsManager: hostsManager,
|
hostsManager: hostsManager,
|
||||||
|
|||||||
@@ -147,7 +147,6 @@ struct ConnectionView: View {
|
|||||||
Button() {
|
Button() {
|
||||||
handler.go()
|
handler.go()
|
||||||
showTerminal = checkShell(handler.state)
|
showTerminal = checkShell(handler.state)
|
||||||
TerminalController.TerminalViewContainer.shared = nil
|
|
||||||
} label: {
|
} label: {
|
||||||
Label(
|
Label(
|
||||||
handler.connected ? "Disconnect" : "Connect",
|
handler.connected ? "Disconnect" : "Connect",
|
||||||
|
|||||||
@@ -17,17 +17,6 @@ struct HostsView: View {
|
|||||||
Text("Add your first Host!")
|
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
|
ForEach(hostsManager.hosts) { host in
|
||||||
NavigationLink() {
|
NavigationLink() {
|
||||||
ConnectionView(
|
ConnectionView(
|
||||||
|
|||||||
34
ShhShell/Views/Sessions/SessionView.swift
Normal file
34
ShhShell/Views/Sessions/SessionView.swift
Normal file
@@ -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()
|
||||||
|
)
|
||||||
|
}
|
||||||
31
ShhShell/Views/Sessions/SessionsListView.swift
Normal file
31
ShhShell/Views/Sessions/SessionsListView.swift
Normal file
@@ -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()
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -18,7 +18,9 @@ struct ShellView: View {
|
|||||||
ZStack {
|
ZStack {
|
||||||
TerminalController(handler: handler, hostsManager: hostsManager)
|
TerminalController(handler: handler, hostsManager: hostsManager)
|
||||||
.onAppear {
|
.onAppear {
|
||||||
TerminalController.TerminalViewContainer.shared?.restoreScrollback()
|
if let sessionID = handler.sessionID {
|
||||||
|
TerminalViewContainer.shared[sessionID]?.terminalView.restoreScrollback()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Group {
|
Group {
|
||||||
|
|||||||
@@ -14,13 +14,11 @@ struct TerminalController: UIViewRepresentable {
|
|||||||
@ObservedObject var handler: SSHHandler
|
@ObservedObject var handler: SSHHandler
|
||||||
@ObservedObject var hostsManager: HostsManager
|
@ObservedObject var hostsManager: HostsManager
|
||||||
|
|
||||||
final class TerminalViewContainer {
|
|
||||||
@MainActor static var shared: SSHTerminalDelegate?
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeUIView(context: Context) -> TerminalView {
|
func makeUIView(context: Context) -> TerminalView {
|
||||||
if let existing = TerminalViewContainer.shared {
|
if let sessionID = handler.sessionID {
|
||||||
return existing
|
if let existing = TerminalViewContainer.shared[sessionID] {
|
||||||
|
return existing.terminalView
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let tv = SSHTerminalDelegate(
|
let tv = SSHTerminalDelegate(
|
||||||
@@ -31,7 +29,12 @@ struct TerminalController: UIViewRepresentable {
|
|||||||
tv.translatesAutoresizingMaskIntoConstraints = false
|
tv.translatesAutoresizingMaskIntoConstraints = false
|
||||||
tv.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
tv.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
||||||
|
|
||||||
TerminalViewContainer.shared = tv
|
if let sessionID = handler.sessionID {
|
||||||
|
TerminalViewContainer.shared[sessionID] = TerminalContainer(
|
||||||
|
handler: handler,
|
||||||
|
terminalView: tv
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return tv
|
return tv
|
||||||
}
|
}
|
||||||
|
|||||||
19
ShhShell/Views/Terminal/TerminalViewContainer.swift
Normal file
19
ShhShell/Views/Terminal/TerminalViewContainer.swift
Normal file
@@ -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
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user