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