diff --git a/Resources/Assets.xcassets/terminalGreen.colorset/Contents.json b/Resources/Assets.xcassets/terminalGreen.colorset/Contents.json new file mode 100644 index 0000000..c74662a --- /dev/null +++ b/Resources/Assets.xcassets/terminalGreen.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "display-p3", + "components" : { + "alpha" : "1.000", + "blue" : "0.237", + "green" : "0.725", + "red" : "0.350" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ShhShell.xcodeproj/project.pbxproj b/ShhShell.xcodeproj/project.pbxproj index 6612f2f..7bccab1 100644 --- a/ShhShell.xcodeproj/project.pbxproj +++ b/ShhShell.xcodeproj/project.pbxproj @@ -32,6 +32,7 @@ A985545F2E056EDD009051BD /* KeychainLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = A985545E2E056EDD009051BD /* KeychainLayer.swift */; }; A98554612E058433009051BD /* HostsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A98554602E058433009051BD /* HostsManager.swift */; }; A98554632E0587DF009051BD /* HostsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A98554622E0587DF009051BD /* HostsView.swift */; }; + A9B15A9A2E0ABA0400F66E02 /* DialogView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9B15A992E0ABA0400F66E02 /* DialogView.swift */; }; A9C4140C2E096DB7005E3047 /* SSHError.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9C4140B2E096DB7005E3047 /* SSHError.swift */; }; A9C897EF2DF1A9A400EF9A5F /* SSHHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9C897EE2DF1A9A400EF9A5F /* SSHHandler.swift */; }; /* End PBXBuildFile section */ @@ -98,6 +99,7 @@ A985545E2E056EDD009051BD /* KeychainLayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainLayer.swift; sourceTree = ""; }; A98554602E058433009051BD /* HostsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HostsManager.swift; sourceTree = ""; }; A98554622E0587DF009051BD /* HostsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HostsView.swift; sourceTree = ""; }; + A9B15A992E0ABA0400F66E02 /* DialogView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DialogView.swift; sourceTree = ""; }; A9C4140B2E096DB7005E3047 /* SSHError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSHError.swift; sourceTree = ""; }; A9C897EE2DF1A9A400EF9A5F /* SSHHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSHHandler.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -137,6 +139,7 @@ children = ( A92317292E07113100ECE1E6 /* TerminalController.swift */, A923172E2E08851200ECE1E6 /* ShellView.swift */, + A9B15A992E0ABA0400F66E02 /* DialogView.swift */, A923172C2E07138000ECE1E6 /* SSHTerminalView.swift */, ); path = Terminal; @@ -421,6 +424,7 @@ A98554632E0587DF009051BD /* HostsView.swift in Sources */, A92538C82DEE0742007E0A18 /* ContentView.swift in Sources */, A93143C02DF61B3200FCD5DB /* Host.swift in Sources */, + A9B15A9A2E0ABA0400F66E02 /* DialogView.swift in Sources */, A92538C92DEE0742007E0A18 /* ShhShellApp.swift in Sources */, A98554612E058433009051BD /* HostsManager.swift in Sources */, A985545D2E055D4D009051BD /* ConnectionView.swift in Sources */, diff --git a/ShhShell/Host/Host.swift b/ShhShell/Host/Host.swift index d73bc38..7f9a415 100644 --- a/ShhShell/Host/Host.swift +++ b/ShhShell/Host/Host.swift @@ -56,6 +56,15 @@ extension Host { Host(address: "") } static var debug: Host { - Host(address: "localhost", username: "default", password: "") + Host( + address: "localhost", + port: 22, + username: "neon443", + password: "password", + publicKey: nil, + privateKey: nil, + passphrase: "", + hostkey: nil + ) } } diff --git a/ShhShell/SSH/SSHHandler.swift b/ShhShell/SSH/SSHHandler.swift index db39b80..125cbe6 100644 --- a/ShhShell/SSH/SSHHandler.swift +++ b/ShhShell/SSH/SSHHandler.swift @@ -12,7 +12,8 @@ import SwiftUI class SSHHandler: @unchecked Sendable, ObservableObject { private var session: ssh_session? - var channel: ssh_channel? + private var channel: ssh_channel? + private let sshQueue = DispatchQueue(label: "SSH Queue") @Published var connected: Bool = false @Published var authorized: Bool = false @@ -332,22 +333,22 @@ class SSHHandler: @unchecked Sendable, ObservableObject { } func readFromChannel() -> String? { - if !connected { - return nil - } guard connected else { return nil } guard ssh_channel_is_open(channel) != 0 || ssh_channel_is_eof(channel) == 0 else { disconnect() return nil } - var buffer: [CChar] = Array(repeating: 0, count: 512) + var buffer: [CChar] = Array(repeating: 0, count: 4096) let nbytes = ssh_channel_read_nonblocking(channel, &buffer, UInt32(buffer.count), 0) guard nbytes > 0 else { return nil } let data = Data(bytes: buffer, count: Int(nbytes)) if let string = String(data: data, encoding: .utf8) { + #if DEBUG + print(String(data: Data(bytes: buffer, count: Int(nbytes)), encoding: .utf8)!) + #endif return string } return nil diff --git a/ShhShell/ShhShellApp.swift b/ShhShell/ShhShellApp.swift index beb553e..60b0635 100644 --- a/ShhShell/ShhShellApp.swift +++ b/ShhShell/ShhShellApp.swift @@ -20,6 +20,7 @@ struct ShhShellApp: App { keyManager: keyManager, hostsManager: hostsManager ) + .colorScheme(.dark) } } } diff --git a/ShhShell/Views/Terminal/DialogView.swift b/ShhShell/Views/Terminal/DialogView.swift new file mode 100644 index 0000000..752d51a --- /dev/null +++ b/ShhShell/Views/Terminal/DialogView.swift @@ -0,0 +1,67 @@ +// +// DialogView.swift +// ShhShell +// +// Created by neon443 on 24/06/2025. +// + +import SwiftUI + +struct DialogView: View { + @ObservedObject var handler: SSHHandler + @State var showDialog: Bool = true + @State var icon: String = "network.slash" + @State var headline: String = "Disconnected" + @State var text: String = "Connection to the SSH server has been lost, try reconnecting" + + var body: some View { + GeometryReader { geo in + let width = geo.size.width*0.75 + let height = geo.size.height*0.15 + if showDialog { + ZStack(alignment: .center) { + Color.black + .clipShape(RoundedRectangle(cornerRadius: 15)) + .frame(maxWidth: width, maxHeight: height) + .shadow(color: .white, radius: 2) + HStack(alignment: .top) { + Image(systemName: icon) + .resizable().scaledToFit() + .frame(width: width*0.2) + .foregroundStyle(.terminalGreen) + .symbolRenderingMode(.hierarchical) + VStack(alignment: .leading) { + Text(headline) + .foregroundStyle(.terminalGreen) + .font(.title2) + .bold() + Text(text) + .foregroundStyle(.terminalGreen) + .font(.footnote) + } + .frame(width: width*0.7) + } + .frame(maxWidth: width, maxHeight: height) + } + .transition( + .asymmetric( + insertion: .move(edge: .bottom), + removal: .move(edge: .bottom) + ) + .combined(with: .opacity) + ) + .frame(maxWidth: .infinity, maxHeight: .infinity) + } + } + Button("show button") { + withAnimation { showDialog.toggle() } + } + } +} + +#Preview { + ZStack { + Color.black + DialogView(handler: SSHHandler(host: Host.debug)) + } +} diff --git a/ShhShell/Views/Terminal/SSHTerminalView.swift b/ShhShell/Views/Terminal/SSHTerminalView.swift index ab3f9a5..b5d2c96 100644 --- a/ShhShell/Views/Terminal/SSHTerminalView.swift +++ b/ShhShell/Views/Terminal/SSHTerminalView.swift @@ -26,7 +26,6 @@ final class SSHTerminalView: TerminalView, Sendable, @preconcurrency TerminalVie terminalDelegate = self sshQueue.async { guard let handler = self.handler else { return } - guard handler.channel != nil else { return } while handler.connected { if let read = handler.readFromChannel() { @@ -35,7 +34,7 @@ final class SSHTerminalView: TerminalView, Sendable, @preconcurrency TerminalVie await self.feed(text: read) } } else { - usleep(100_000) + usleep(1_000) } // self?.setNeedsDisplay() } diff --git a/ShhShell/Views/Terminal/ShellView.swift b/ShhShell/Views/Terminal/ShellView.swift index 7cd36e3..b1a8707 100644 --- a/ShhShell/Views/Terminal/ShellView.swift +++ b/ShhShell/Views/Terminal/ShellView.swift @@ -13,6 +13,9 @@ struct ShellView: View { var body: some View { NavigationStack { ZStack { + if !handler.connected { + DialogView(handler: handler, showDialog: !handler.connected) + } TerminalController(handler: handler) } .toolbar {