bell ringing backend done now need to actually trigger it

added ring() to ring the bell
terminal is now a fullscreencover
can be dismissed and reopened or disconnected
 - need to make it keep the scrollback
increased sleep between reads
added support for title
added setTitle function, defaults to username@server
increased the buffer to 16384 bytes
This commit is contained in:
neon443
2025-06-25 11:12:10 +01:00
parent bf4a273a46
commit f71bd0ddf2
8 changed files with 76 additions and 40 deletions

View File

@@ -6,7 +6,7 @@
// //
VERSION = 1.0 VERSION = 1.0
BUILD = 3 BUILD = 27
// Configuration settings file format documentation can be found at: // Configuration settings file format documentation can be found at:
// https://developer.apple.com/documentation/xcode/adding-a-build-configuration-file-to-your-project // https://developer.apple.com/documentation/xcode/adding-a-build-configuration-file-to-your-project

View File

@@ -8,7 +8,6 @@
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
A9083E402DF2226F0042906E /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = A9083E3F2DF2225A0042906E /* libz.tbd */; }; A9083E402DF2226F0042906E /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = A9083E3F2DF2225A0042906E /* libz.tbd */; };
A92317282E07111E00ECE1E6 /* SwiftTerm in Frameworks */ = {isa = PBXBuildFile; productRef = A92317272E07111E00ECE1E6 /* SwiftTerm */; };
A923172A2E07113100ECE1E6 /* TerminalController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A92317292E07113100ECE1E6 /* TerminalController.swift */; }; A923172A2E07113100ECE1E6 /* TerminalController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A92317292E07113100ECE1E6 /* TerminalController.swift */; };
A923172D2E07138000ECE1E6 /* SSHTerminalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A923172C2E07138000ECE1E6 /* SSHTerminalView.swift */; }; A923172D2E07138000ECE1E6 /* SSHTerminalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A923172C2E07138000ECE1E6 /* SSHTerminalView.swift */; };
A923172F2E08851200ECE1E6 /* ShellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A923172E2E08851200ECE1E6 /* ShellView.swift */; }; A923172F2E08851200ECE1E6 /* ShellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A923172E2E08851200ECE1E6 /* ShellView.swift */; };
@@ -32,6 +31,7 @@
A985545F2E056EDD009051BD /* KeychainLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = A985545E2E056EDD009051BD /* KeychainLayer.swift */; }; A985545F2E056EDD009051BD /* KeychainLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = A985545E2E056EDD009051BD /* KeychainLayer.swift */; };
A98554612E058433009051BD /* HostsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A98554602E058433009051BD /* HostsManager.swift */; }; A98554612E058433009051BD /* HostsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A98554602E058433009051BD /* HostsManager.swift */; };
A98554632E0587DF009051BD /* HostsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A98554622E0587DF009051BD /* HostsView.swift */; }; A98554632E0587DF009051BD /* HostsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A98554622E0587DF009051BD /* HostsView.swift */; };
A9A587202E0BF220006B31E6 /* SwiftTerm in Frameworks */ = {isa = PBXBuildFile; productRef = A9A5871F2E0BF220006B31E6 /* SwiftTerm */; };
A9B15A9A2E0ABA0400F66E02 /* DialogView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9B15A992E0ABA0400F66E02 /* DialogView.swift */; }; A9B15A9A2E0ABA0400F66E02 /* DialogView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9B15A992E0ABA0400F66E02 /* DialogView.swift */; };
A9C4140C2E096DB7005E3047 /* SSHError.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9C4140B2E096DB7005E3047 /* SSHError.swift */; }; A9C4140C2E096DB7005E3047 /* SSHError.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9C4140B2E096DB7005E3047 /* SSHError.swift */; };
A9C897EF2DF1A9A400EF9A5F /* SSHHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9C897EE2DF1A9A400EF9A5F /* SSHHandler.swift */; }; A9C897EF2DF1A9A400EF9A5F /* SSHHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9C897EE2DF1A9A400EF9A5F /* SSHHandler.swift */; };
@@ -110,7 +110,7 @@
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
A95FAA542DF4B62900DE2F5A /* LibSSH.xcframework in Frameworks */, A95FAA542DF4B62900DE2F5A /* LibSSH.xcframework in Frameworks */,
A92317282E07111E00ECE1E6 /* SwiftTerm in Frameworks */, A9A587202E0BF220006B31E6 /* SwiftTerm in Frameworks */,
A93143BE2DF4D0B300FCD5DB /* libpthread.tbd in Frameworks */, A93143BE2DF4D0B300FCD5DB /* libpthread.tbd in Frameworks */,
A9083E402DF2226F0042906E /* libz.tbd in Frameworks */, A9083E402DF2226F0042906E /* libz.tbd in Frameworks */,
A95FAA562DF4B62A00DE2F5A /* openssl.xcframework in Frameworks */, A95FAA562DF4B62A00DE2F5A /* openssl.xcframework in Frameworks */,
@@ -294,7 +294,7 @@
); );
name = ShhShell; name = ShhShell;
packageProductDependencies = ( packageProductDependencies = (
A92317272E07111E00ECE1E6 /* SwiftTerm */, A9A5871F2E0BF220006B31E6 /* SwiftTerm */,
); );
productName = ShhShell; productName = ShhShell;
productReference = A925389A2DEE06DC007E0A18 /* ShhShell.app */; productReference = A925389A2DEE06DC007E0A18 /* ShhShell.app */;
@@ -373,7 +373,7 @@
mainGroup = A92538912DEE06DC007E0A18; mainGroup = A92538912DEE06DC007E0A18;
minimizedProjectReferenceProxies = 1; minimizedProjectReferenceProxies = 1;
packageReferences = ( packageReferences = (
A92317262E07111E00ECE1E6 /* XCRemoteSwiftPackageReference "SwiftTerm" */, A9A5871E2E0BF220006B31E6 /* XCRemoteSwiftPackageReference "SwiftTerm" */,
); );
preferredProjectObjectVersion = 77; preferredProjectObjectVersion = 77;
productRefGroup = A925389B2DEE06DC007E0A18 /* Products */; productRefGroup = A925389B2DEE06DC007E0A18 /* Products */;
@@ -624,7 +624,7 @@
SUPPORTS_MACCATALYST = NO; SUPPORTS_MACCATALYST = NO;
SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_STRICT_CONCURRENCY = complete; SWIFT_STRICT_CONCURRENCY = complete;
SWIFT_VERSION = 5.0; SWIFT_VERSION = 6.0;
TARGETED_DEVICE_FAMILY = "1,2"; TARGETED_DEVICE_FAMILY = "1,2";
}; };
name = Debug; name = Debug;
@@ -660,7 +660,7 @@
SUPPORTS_MACCATALYST = NO; SUPPORTS_MACCATALYST = NO;
SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_STRICT_CONCURRENCY = complete; SWIFT_STRICT_CONCURRENCY = complete;
SWIFT_VERSION = 5.0; SWIFT_VERSION = 6.0;
TARGETED_DEVICE_FAMILY = "1,2"; TARGETED_DEVICE_FAMILY = "1,2";
}; };
name = Release; name = Release;
@@ -779,20 +779,20 @@
/* End XCConfigurationList section */ /* End XCConfigurationList section */
/* Begin XCRemoteSwiftPackageReference section */ /* Begin XCRemoteSwiftPackageReference section */
A92317262E07111E00ECE1E6 /* XCRemoteSwiftPackageReference "SwiftTerm" */ = { A9A5871E2E0BF220006B31E6 /* XCRemoteSwiftPackageReference "SwiftTerm" */ = {
isa = XCRemoteSwiftPackageReference; isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/migueldeicaza/SwiftTerm"; repositoryURL = "https://github.com/migueldeicaza/SwiftTerm";
requirement = { requirement = {
kind = upToNextMajorVersion; branch = main;
minimumVersion = 1.2.5; kind = branch;
}; };
}; };
/* End XCRemoteSwiftPackageReference section */ /* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */ /* Begin XCSwiftPackageProductDependency section */
A92317272E07111E00ECE1E6 /* SwiftTerm */ = { A9A5871F2E0BF220006B31E6 /* SwiftTerm */ = {
isa = XCSwiftPackageProductDependency; isa = XCSwiftPackageProductDependency;
package = A92317262E07111E00ECE1E6 /* XCRemoteSwiftPackageReference "SwiftTerm" */; package = A9A5871E2E0BF220006B31E6 /* XCRemoteSwiftPackageReference "SwiftTerm" */;
productName = SwiftTerm; productName = SwiftTerm;
}; };
/* End XCSwiftPackageProductDependency section */ /* End XCSwiftPackageProductDependency section */

View File

@@ -6,8 +6,8 @@
"kind" : "remoteSourceControl", "kind" : "remoteSourceControl",
"location" : "https://github.com/migueldeicaza/SwiftTerm", "location" : "https://github.com/migueldeicaza/SwiftTerm",
"state" : { "state" : {
"revision" : "e2b431dbf73f775fb4807a33e4572ffd3dc6933a", "branch" : "main",
"version" : "1.2.5" "revision" : "f8e6e08b5a8c8eb0b18b9c250f36121e55c68f49"
} }
} }
], ],

View File

@@ -17,11 +17,12 @@ class SSHHandler: @unchecked Sendable, ObservableObject {
// @Published var hostsManager = HostsManager() // @Published var hostsManager = HostsManager()
@Published var title: String = ""
@Published var connected: Bool = false @Published var connected: Bool = false
@Published var authorized: Bool = false @Published var authorized: Bool = false
@Published var testSuceeded: Bool? = nil @Published var testSuceeded: Bool? = nil
@Published var bell: UUID? @Published var bell: UUID? = nil
@Published var host: Host @Published var host: Host
@@ -76,6 +77,7 @@ class SSHHandler: @unchecked Sendable, ObservableObject {
} }
} }
openShell() openShell()
setTitle("\(host.username)@\(host.address)")
ssh_channel_request_env(channel, "TERM", "xterm-256color") ssh_channel_request_env(channel, "TERM", "xterm-256color")
ssh_channel_request_env(channel, "LANG", "en_US.UTF-8") ssh_channel_request_env(channel, "LANG", "en_US.UTF-8")
ssh_channel_request_env(channel, "LC_ALL", "en_US.UTF-8") ssh_channel_request_env(channel, "LC_ALL", "en_US.UTF-8")
@@ -132,6 +134,17 @@ class SSHHandler: @unchecked Sendable, ObservableObject {
} }
func ring() {
withAnimation { bell = UUID() }
DispatchQueue.main.asyncAfter(deadline: .now()+1) {
withAnimation { self.bell = nil }
}
}
func setTitle(_ newTitle: String) {
self.title = newTitle
}
func testExec() { func testExec() {
if ssh_is_connected(session) == 0 { if ssh_is_connected(session) == 0 {
withAnimation { testSuceeded = false } withAnimation { testSuceeded = false }
@@ -208,6 +221,7 @@ class SSHHandler: @unchecked Sendable, ObservableObject {
return return
} }
//MARK: auth
func authWithPubkey(pub pubInp: Data, priv privInp: Data, pass: String) throws(KeyError) { func authWithPubkey(pub pubInp: Data, priv privInp: Data, pass: String) throws(KeyError) {
guard session != nil else { guard session != nil else {
withAnimation { authorized = false } withAnimation { authorized = false }
@@ -309,6 +323,7 @@ class SSHHandler: @unchecked Sendable, ObservableObject {
print(recievedMethod) print(recievedMethod)
} }
//MARK: shell
func openShell() { func openShell() {
var status: CInt var status: CInt
@@ -346,7 +361,7 @@ class SSHHandler: @unchecked Sendable, ObservableObject {
return nil return nil
} }
var buffer: [CChar] = Array(repeating: 0, count: 4096) var buffer: [CChar] = Array(repeating: 0, count: 16_384)
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 }
@@ -354,7 +369,7 @@ class SSHHandler: @unchecked Sendable, ObservableObject {
let data = Data(bytes: buffer, count: Int(nbytes)) let data = Data(bytes: buffer, count: Int(nbytes))
if let string = String(data: data, encoding: .utf8) { if let string = String(data: data, encoding: .utf8) {
#if DEBUG #if DEBUG
print(String(data: Data(bytes: buffer, count: Int(nbytes)), encoding: .utf8)!) // print(String(data: Data(bytes: buffer, count: Int(nbytes)), encoding: .utf8)!)
#endif #endif
return string return string
} }

View File

@@ -17,6 +17,7 @@ struct ConnectionView: View {
@State var pubkeyStr: String = "" @State var pubkeyStr: String = ""
@State var privkeyStr: String = "" @State var privkeyStr: String = ""
@State var showTerminal: Bool = false
@State var privPickerPresented: Bool = false @State var privPickerPresented: Bool = false
@State var pubPickerPresented: Bool = false @State var pubPickerPresented: Bool = false
@@ -128,12 +129,12 @@ struct ConnectionView: View {
} }
} }
NavigationLink() { Button() {
ShellView(handler: handler) showTerminal.toggle()
} label: { } label: {
Label("Open Terminal", systemImage: "apple.terminal") Label("Show Terminal", systemImage: "apple.terminal")
} }
.disabled(!(handler.connected && handler.authorized)) .disabled(!handler.connected || !handler.authorized)
Button() { Button() {
if handler.authorized && handler.connected { if handler.authorized && handler.connected {
@@ -169,6 +170,7 @@ struct ConnectionView: View {
ToolbarItem() { ToolbarItem() {
Button() { Button() {
handler.go() handler.go()
showTerminal = handler.connected && handler.authorized
} label: { } label: {
Label( Label(
handler.connected ? "Disconnect" : "Connect", handler.connected ? "Disconnect" : "Connect",
@@ -178,6 +180,9 @@ struct ConnectionView: View {
} }
} }
} }
.fullScreenCover(isPresented: $showTerminal) {
ShellView(handler: handler)
}
.onDisappear { .onDisappear {
guard hostsManager.getHostMatching(handler.host) == handler.host else { guard hostsManager.getHostMatching(handler.host) == handler.host else {
hostsManager.updateHost(handler.host) hostsManager.updateHost(handler.host)

View File

@@ -26,15 +26,15 @@ struct HostsView: View {
} }
//proves that u can connect to multiple at the same time //proves that u can connect to multiple at the same time
// NavigationLink() { NavigationLink() {
// ForEach(hostsManager.savedHosts) { host in ForEach(hostsManager.savedHosts) { host in
// let miniHandler = SSHHandler(host: host) let miniHandler = SSHHandler(host: host)
// TerminalController(handler: miniHandler) TerminalController(handler: miniHandler)
// .onAppear { miniHandler.go() } .onAppear { miniHandler.go() }
// } }
// } label: { } label: {
// Label("multiview", systemImage: "square.split.2x2") Label("multiview", systemImage: "square.split.2x2")
// } }
ForEach(hostsManager.savedHosts) { host in ForEach(hostsManager.savedHosts) { host in
NavigationLink() { NavigationLink() {

View File

@@ -35,7 +35,7 @@ final class SSHTerminalView: TerminalView, Sendable, @preconcurrency TerminalVie
await self.feed(text: read) await self.feed(text: read)
} }
} else { } else {
try? await Task.sleep(nanoseconds: 1_000_000) //1ms try? await Task.sleep(nanoseconds: 10_000_000) //10ms
} }
} }
} }
@@ -54,7 +54,7 @@ final class SSHTerminalView: TerminalView, Sendable, @preconcurrency TerminalVie
await self.feed(text: read) await self.feed(text: read)
} }
} else { } else {
Task{ try? await Task.sleep(nanoseconds: 1_000_000) } Task{ try? await Task.sleep(nanoseconds: 10_000_000) }
} }
} }
} }
@@ -69,7 +69,9 @@ final class SSHTerminalView: TerminalView, Sendable, @preconcurrency TerminalVie
} }
nonisolated public func setTerminalTitle(source: TerminalView, title: String) { nonisolated public func setTerminalTitle(source: TerminalView, title: String) {
print("set title to \(title)") Task {
await MainActor.run { handler?.setTitle(title) }
}
} }
public func sizeChanged(source: TerminalView, newCols: Int, newRows: Int) { public func sizeChanged(source: TerminalView, newCols: Int, newRows: Int) {

View File

@@ -16,6 +16,11 @@ struct ShellView: View {
var body: some View { var body: some View {
NavigationStack { NavigationStack {
ZStack { ZStack {
if handler.bell != nil {
Text("🔔")
.font(.title)
.transition(.scale)
}
if !handler.connected { if !handler.connected {
DialogView(handler: handler, showDialog: !handler.connected) DialogView(handler: handler, showDialog: !handler.connected)
} }
@@ -27,18 +32,27 @@ struct ShellView: View {
.toolbar { .toolbar {
ToolbarItem { ToolbarItem {
Button() { Button() {
handler.go() Task {
if !handler.connected { await handler.disconnect()
dismiss() if !handler.connected { dismiss() }
} }
} label: { } label: {
Label( Label("Disconnect", systemImage: "xmark.app.fill")
handler.connected ? "Disconnect" : "Connect", }
systemImage: handler.connected ? "xmark.app.fill" : "power" }
) ToolbarItem(placement: .cancellationAction) {
Button() {
dismiss()
} label: {
Label("Close", systemImage: "arrow.down.right.and.arrow.up.left")
} }
} }
} }
.onChange(of: handler.connected) { _ in
if !handler.connected { dismiss() }
}
.navigationTitle(handler.title)
.navigationBarTitleDisplayMode(.inline)
} }
} }
} }