Added support for reconnecting to a server, using the same terminal

added reconnect() to sshhandler
added support to go() and connect() to use an arbritrary sessionID
redid readloop in sshterminaldelegate to use a timer, instead of a while loop in a Task, allowsfor one readloop per terminal instead
updated the ui for the disconnected alert
increased max read size from 1024 to
added reconnecterror
updated tracker to remove print statements and exit start/stop tracking funcs early if tracking/not tracking
This commit is contained in:
neon443
2025-09-01 18:49:48 +01:00
parent ff02122bcc
commit 68fb7d4844
5 changed files with 33 additions and 17 deletions

View File

@@ -10,6 +10,7 @@ import CoreLocation
class Backgrounder: NSObject, CLLocationManagerDelegate, ObservableObject { class Backgrounder: NSObject, CLLocationManagerDelegate, ObservableObject {
private let manager = CLLocationManager() private let manager = CLLocationManager()
var tracking: Bool = false
@MainActor @MainActor
static var shared: Backgrounder = Backgrounder() static var shared: Backgrounder = Backgrounder()
@@ -23,17 +24,19 @@ class Backgrounder: NSObject, CLLocationManagerDelegate, ObservableObject {
} }
func startBgTracking() { func startBgTracking() {
// guard mana guard !tracking else { return }
guard checkPermsStatus() else { return }
manager.allowsBackgroundLocationUpdates = true manager.allowsBackgroundLocationUpdates = true
manager.pausesLocationUpdatesAutomatically = false manager.pausesLocationUpdatesAutomatically = false
manager.startMonitoringSignificantLocationChanges() manager.startMonitoringSignificantLocationChanges()
print("started tgracking") tracking = true
} }
func stopBgTracking() { func stopBgTracking() {
guard tracking else { return }
manager.stopUpdatingLocation() manager.stopUpdatingLocation()
manager.allowsBackgroundLocationUpdates = false manager.allowsBackgroundLocationUpdates = false
print("stopped tracking") tracking = false
} }
func requestPerms() { func requestPerms() {

View File

@@ -25,3 +25,7 @@ enum KeyError: Error {
case pubkeyRejected case pubkeyRejected
case privkeyRejected case privkeyRejected
} }
enum ReconnectError: Error {
case alreadyConnected
}

View File

@@ -59,10 +59,10 @@ class SSHHandler: @unchecked Sendable, ObservableObject {
return String(cString: cString) return String(cString: cString)
} }
func go() { func go(id: UUID = UUID()) {
guard !connected else { disconnect(); return } guard !connected else { disconnect(); return }
do { try connect() } catch { do { try connect(id: id) } catch {
print("error when connecting \(error.localizedDescription)") print("error when connecting \(error.localizedDescription)")
return return
} }
@@ -108,10 +108,10 @@ class SSHHandler: @unchecked Sendable, ObservableObject {
setTitle("\(host.username)@\(host.address)") setTitle("\(host.username)@\(host.address)")
} }
func connect() 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 = UUID() sessionID = id
var verbosity: Int = 0 var verbosity: Int = 0
// var verbosity: Int = SSH_LOG_FUNCTIONS // var verbosity: Int = SSH_LOG_FUNCTIONS
@@ -139,6 +139,11 @@ class SSHHandler: @unchecked Sendable, ObservableObject {
return return
} }
func reconnect() throws(ReconnectError) {
guard !connected else { throw .alreadyConnected }
go(id: sessionID!)
}
func disconnect() { func disconnect() {
// Task { // Task {
self.hostkeyChanged = false self.hostkeyChanged = false
@@ -359,7 +364,7 @@ class SSHHandler: @unchecked Sendable, ObservableObject {
return nil return nil
} }
var buffer: [CChar] = Array(repeating: 0, count: 1024) var buffer: [CChar] = Array(repeating: 0, count: 4096)
let nbytes = ssh_channel_read_nonblocking(channel, &buffer, UInt32(buffer.count), 0) let nbytes = ssh_channel_read_nonblocking(channel, &buffer, UInt32(buffer.count), 0)
guard nbytes > 0 else { return nil } guard nbytes > 0 else { return nil }

View File

@@ -14,6 +14,8 @@ final class SSHTerminalDelegate: TerminalView, Sendable, @preconcurrency Termina
var handler: SSHHandler? var handler: SSHHandler?
var hostsManager: HostsManager? var hostsManager: HostsManager?
var readTimer: Timer?
public convenience init(frame: CGRect, handler: SSHHandler, hostsManager: HostsManager) { public convenience init(frame: CGRect, handler: SSHHandler, hostsManager: HostsManager) {
self.init(frame: frame) self.init(frame: frame)
@@ -97,19 +99,18 @@ final class SSHTerminalDelegate: TerminalView, Sendable, @preconcurrency Termina
} }
func startFeedLoop() { func startFeedLoop() {
Task { guard readTimer == nil else { return }
guard let handler else { return } readTimer = Timer(timeInterval: 0.01, repeats: true) { timer in
while checkShell(handler.state) { Task(priority: .high) {
guard let handler = await self.handler else { return }
if let read = handler.readFromChannel() { if let read = handler.readFromChannel() {
await MainActor.run { Task { @MainActor in
self.feed(text: read) self.feed(text: read)
} }
} else {
try? await Task.sleep(nanoseconds: 10_000_000) //10ms
} }
} }
print("task end?")
} }
RunLoop.main.add(readTimer!, forMode: .common)
} }
func applySelectedTheme() { func applySelectedTheme() {

View File

@@ -59,8 +59,9 @@ struct ShellView: View {
if !checkShell(handler.state) { if !checkShell(handler.state) {
ZStack { ZStack {
RoundedRectangle(cornerRadius: 25) RoundedRectangle(cornerRadius: 25)
.fill(hostsManager.selectedTheme.foreground.suiColor.opacity(0.5)) .fill(hostsManager.selectedTheme.foreground.suiColor)
.blur(radius: 5) .opacity(0.5)
.blur(radius: 2)
.shadow(color: hostsManager.selectedTheme.foreground.suiColor, radius: 5) .shadow(color: hostsManager.selectedTheme.foreground.suiColor, radius: 5)
VStack { VStack {
HStack { HStack {
@@ -86,6 +87,8 @@ struct ShellView: View {
.padding(10) .padding(10)
} }
.fixedSize() .fixedSize()
.transition(.opacity)
.animation(.spring, value: checkShell(handler.state))
} }
} }
} }