remove terminal string

rewrote authWithPubkey() to not write files and take data direcytly
remove read loop (comment adding it)
readfromchannel returns string
using swifterm to do terminal emulation
This commit is contained in:
neon443
2025-06-22 10:51:21 +01:00
parent ed1e7ab25d
commit 1cf9518e0f
8 changed files with 138 additions and 238 deletions

View File

@@ -8,13 +8,9 @@
/* Begin PBXBuildFile section */
A9083E402DF2226F0042906E /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = A9083E3F2DF2225A0042906E /* libz.tbd */; };
A91AE38A2DF722A700FF3537 /* Runestone in Frameworks */ = {isa = PBXBuildFile; productRef = A91AE3892DF722A600FF3537 /* Runestone */; };
A91AE3B22DF73E0900FF3537 /* TerminalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A91AE3B12DF73E0900FF3537 /* TerminalView.swift */; };
A91AE3B52DF73F1D00FF3537 /* TreeSitterBash in Frameworks */ = {isa = PBXBuildFile; productRef = A91AE3B42DF73F1D00FF3537 /* TreeSitterBash */; };
A91AE3B72DF73F1D00FF3537 /* TreeSitterBashQueries in Frameworks */ = {isa = PBXBuildFile; productRef = A91AE3B62DF73F1D00FF3537 /* TreeSitterBashQueries */; };
A91AE3B92DF73F1D00FF3537 /* TreeSitterBashRunestone in Frameworks */ = {isa = PBXBuildFile; productRef = A91AE3B82DF73F1D00FF3537 /* TreeSitterBashRunestone */; };
A91AE3BB2DF73F1D00FF3537 /* TreeSitterLanguagesCommon in Frameworks */ = {isa = PBXBuildFile; productRef = A91AE3BA2DF73F1D00FF3537 /* TreeSitterLanguagesCommon */; };
A91AE3BD2DF7402100FF3537 /* TextViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A91AE3BC2DF7402100FF3537 /* TextViewController.swift */; };
A92317282E07111E00ECE1E6 /* SwiftTerm in Frameworks */ = {isa = PBXBuildFile; productRef = A92317272E07111E00ECE1E6 /* SwiftTerm */; };
A923172A2E07113100ECE1E6 /* TerminalController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A92317292E07113100ECE1E6 /* TerminalController.swift */; };
A923172D2E07138000ECE1E6 /* TerminalDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = A923172C2E07138000ECE1E6 /* TerminalDelegate.swift */; };
A92538C82DEE0742007E0A18 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A92538C52DEE0742007E0A18 /* ContentView.swift */; };
A92538C92DEE0742007E0A18 /* ShhShellApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = A92538C62DEE0742007E0A18 /* ShhShellApp.swift */; };
A92538CA2DEE0742007E0A18 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A92538C42DEE0742007E0A18 /* Assets.xcassets */; };
@@ -72,8 +68,8 @@
/* Begin PBXFileReference section */
A9083E3F2DF2225A0042906E /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; };
A91AE3B12DF73E0900FF3537 /* TerminalView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TerminalView.swift; sourceTree = "<group>"; };
A91AE3BC2DF7402100FF3537 /* TextViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextViewController.swift; sourceTree = "<group>"; };
A92317292E07113100ECE1E6 /* TerminalController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TerminalController.swift; sourceTree = "<group>"; };
A923172C2E07138000ECE1E6 /* TerminalDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TerminalDelegate.swift; sourceTree = "<group>"; };
A925389A2DEE06DC007E0A18 /* ShhShell.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ShhShell.app; sourceTree = BUILT_PRODUCTS_DIR; };
A92538A72DEE06DE007E0A18 /* ShhShellTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ShhShellTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
A92538B12DEE06DE007E0A18 /* ShhShellUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ShhShellUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -107,13 +103,9 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
A91AE3B72DF73F1D00FF3537 /* TreeSitterBashQueries in Frameworks */,
A95FAA542DF4B62900DE2F5A /* LibSSH.xcframework in Frameworks */,
A91AE3B52DF73F1D00FF3537 /* TreeSitterBash in Frameworks */,
A91AE3B92DF73F1D00FF3537 /* TreeSitterBashRunestone in Frameworks */,
A91AE38A2DF722A700FF3537 /* Runestone in Frameworks */,
A92317282E07111E00ECE1E6 /* SwiftTerm in Frameworks */,
A93143BE2DF4D0B300FCD5DB /* libpthread.tbd in Frameworks */,
A91AE3BB2DF73F1D00FF3537 /* TreeSitterLanguagesCommon in Frameworks */,
A9083E402DF2226F0042906E /* libz.tbd in Frameworks */,
A95FAA562DF4B62A00DE2F5A /* openssl.xcframework in Frameworks */,
);
@@ -136,6 +128,15 @@
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
A923172B2E0712F200ECE1E6 /* Terminal */ = {
isa = PBXGroup;
children = (
A92317292E07113100ECE1E6 /* TerminalController.swift */,
A923172C2E07138000ECE1E6 /* TerminalDelegate.swift */,
);
path = Terminal;
sourceTree = "<group>";
};
A92538912DEE06DC007E0A18 = {
isa = PBXGroup;
children = (
@@ -197,8 +198,8 @@
A92538C52DEE0742007E0A18 /* ContentView.swift */,
A98554622E0587DF009051BD /* HostsView.swift */,
A985545C2E055D4D009051BD /* ConnectionView.swift */,
A98554522E055347009051BD /* Terminal */,
A93143C52DF61FE300FCD5DB /* ViewModifiers.swift */,
A923172B2E0712F200ECE1E6 /* Terminal */,
);
path = Views;
sourceTree = "<group>";
@@ -229,15 +230,6 @@
path = ci_scripts;
sourceTree = "<group>";
};
A98554522E055347009051BD /* Terminal */ = {
isa = PBXGroup;
children = (
A91AE3B12DF73E0900FF3537 /* TerminalView.swift */,
A91AE3BC2DF7402100FF3537 /* TextViewController.swift */,
);
path = Terminal;
sourceTree = "<group>";
};
A98554532E05534F009051BD /* Keys */ = {
isa = PBXGroup;
children = (
@@ -293,11 +285,7 @@
);
name = ShhShell;
packageProductDependencies = (
A91AE3892DF722A600FF3537 /* Runestone */,
A91AE3B42DF73F1D00FF3537 /* TreeSitterBash */,
A91AE3B62DF73F1D00FF3537 /* TreeSitterBashQueries */,
A91AE3B82DF73F1D00FF3537 /* TreeSitterBashRunestone */,
A91AE3BA2DF73F1D00FF3537 /* TreeSitterLanguagesCommon */,
A92317272E07111E00ECE1E6 /* SwiftTerm */,
);
productName = ShhShell;
productReference = A925389A2DEE06DC007E0A18 /* ShhShell.app */;
@@ -376,8 +364,7 @@
mainGroup = A92538912DEE06DC007E0A18;
minimizedProjectReferenceProxies = 1;
packageReferences = (
A91AE3882DF722A600FF3537 /* XCRemoteSwiftPackageReference "runestone" */,
A91AE3B32DF73F1D00FF3537 /* XCRemoteSwiftPackageReference "treesitterlanguages" */,
A92317262E07111E00ECE1E6 /* XCRemoteSwiftPackageReference "SwiftTerm" */,
);
preferredProjectObjectVersion = 77;
productRefGroup = A925389B2DEE06DC007E0A18 /* Products */;
@@ -430,11 +417,11 @@
A92538C92DEE0742007E0A18 /* ShhShellApp.swift in Sources */,
A98554612E058433009051BD /* HostsManager.swift in Sources */,
A985545D2E055D4D009051BD /* ConnectionView.swift in Sources */,
A91AE3B22DF73E0900FF3537 /* TerminalView.swift in Sources */,
A98554592E0553AA009051BD /* KeyManager.swift in Sources */,
A91AE3BD2DF7402100FF3537 /* TextViewController.swift in Sources */,
A9C897EF2DF1A9A400EF9A5F /* SSHHandler.swift in Sources */,
A98554552E05535F009051BD /* KeyManagerView.swift in Sources */,
A923172D2E07138000ECE1E6 /* TerminalDelegate.swift in Sources */,
A923172A2E07113100ECE1E6 /* TerminalController.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -770,49 +757,21 @@
/* End XCConfigurationList section */
/* Begin XCRemoteSwiftPackageReference section */
A91AE3882DF722A600FF3537 /* XCRemoteSwiftPackageReference "runestone" */ = {
A92317262E07111E00ECE1E6 /* XCRemoteSwiftPackageReference "SwiftTerm" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/simonbs/runestone";
repositoryURL = "https://github.com/migueldeicaza/SwiftTerm";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 0.5.1;
};
};
A91AE3B32DF73F1D00FF3537 /* XCRemoteSwiftPackageReference "treesitterlanguages" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/simonbs/treesitterlanguages";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 0.1.10;
minimumVersion = 1.2.5;
};
};
/* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
A91AE3892DF722A600FF3537 /* Runestone */ = {
A92317272E07111E00ECE1E6 /* SwiftTerm */ = {
isa = XCSwiftPackageProductDependency;
package = A91AE3882DF722A600FF3537 /* XCRemoteSwiftPackageReference "runestone" */;
productName = Runestone;
};
A91AE3B42DF73F1D00FF3537 /* TreeSitterBash */ = {
isa = XCSwiftPackageProductDependency;
package = A91AE3B32DF73F1D00FF3537 /* XCRemoteSwiftPackageReference "treesitterlanguages" */;
productName = TreeSitterBash;
};
A91AE3B62DF73F1D00FF3537 /* TreeSitterBashQueries */ = {
isa = XCSwiftPackageProductDependency;
package = A91AE3B32DF73F1D00FF3537 /* XCRemoteSwiftPackageReference "treesitterlanguages" */;
productName = TreeSitterBashQueries;
};
A91AE3B82DF73F1D00FF3537 /* TreeSitterBashRunestone */ = {
isa = XCSwiftPackageProductDependency;
package = A91AE3B32DF73F1D00FF3537 /* XCRemoteSwiftPackageReference "treesitterlanguages" */;
productName = TreeSitterBashRunestone;
};
A91AE3BA2DF73F1D00FF3537 /* TreeSitterLanguagesCommon */ = {
isa = XCSwiftPackageProductDependency;
package = A91AE3B32DF73F1D00FF3537 /* XCRemoteSwiftPackageReference "treesitterlanguages" */;
productName = TreeSitterLanguagesCommon;
package = A92317262E07111E00ECE1E6 /* XCRemoteSwiftPackageReference "SwiftTerm" */;
productName = SwiftTerm;
};
/* End XCSwiftPackageProductDependency section */
};

View File

@@ -1,31 +1,13 @@
{
"originHash" : "7b91b8af81dcd3e1958c781705a4b5920fdcdef24313a6e025be307b7bc092d5",
"originHash" : "4d9d9af82f23f3c708bdd502fed3939413b4f2a95a79ae568364cc92bca1527e",
"pins" : [
{
"identity" : "runestone",
"identity" : "swiftterm",
"kind" : "remoteSourceControl",
"location" : "https://github.com/simonbs/Runestone",
"location" : "https://github.com/migueldeicaza/SwiftTerm",
"state" : {
"revision" : "1fad339aab99cf2136ce6bf8c32da3265b2e85e5",
"version" : "0.5.1"
}
},
{
"identity" : "tree-sitter",
"kind" : "remoteSourceControl",
"location" : "https://github.com/tree-sitter/tree-sitter",
"state" : {
"revision" : "98be227227af10cc7a269cb3ffb23686c0610b17",
"version" : "0.20.9"
}
},
{
"identity" : "treesitterlanguages",
"kind" : "remoteSourceControl",
"location" : "https://github.com/simonbs/treesitterlanguages",
"state" : {
"revision" : "15cf3a9ec3ab95e0d058b7df9f35619123c9e02d",
"version" : "0.1.10"
"revision" : "e2b431dbf73f775fb4807a33e4572ffd3dc6933a",
"version" : "1.2.5"
}
}
],

View File

@@ -20,7 +20,6 @@ class SSHHandler: ObservableObject {
@Published var testSuceeded: Bool? = nil
@Published var host: Host
@Published var terminal: String = ""
private let userDefaults = NSUbiquitousKeyValueStore.default
private let logger = Logger(subsystem: "xy", category: "sshHandler")
@@ -88,7 +87,6 @@ class SSHHandler: ObservableObject {
withAnimation { connected = false }
withAnimation { testSuceeded = nil }
session = nil
terminal = ""
// host.key = nil
}
@@ -173,28 +171,29 @@ class SSHHandler: ObservableObject {
withAnimation { authorized = false }
return
}
var status: Int32
let fileManager = FileManager.default
let tempDir = fileManager.temporaryDirectory
let tempPubkey = tempDir.appendingPathComponent("key.pub")
let tempKey = tempDir.appendingPathComponent("key")
fileManager.createFile(atPath: tempPubkey.path(), contents: nil)
fileManager.createFile(atPath: tempKey.path(), contents: nil)
print(pubInp)
var pubInpCChar: [Int8] = []
for byte in pubInp {
pubInpCChar.append(Int8(byte))
}
try? pubInp.write(to: tempPubkey)
try? privInp.write(to: tempKey)
var privInpCChar: [Int8] = []
for byte in privInp {
privInpCChar.append(Int8(byte))
}
var pubkey: ssh_key?
ssh_pki_import_pubkey_file(tempPubkey.path(), &pubkey)
ssh_pki_import_pubkey_base64(&pubInpCChar, SSH_KEYTYPE_SK_ED25519, &pubkey)
status = ssh_userauth_try_publickey(session, nil, pubkey)
print(status)
var privkey: ssh_key?
if ssh_pki_import_privkey_file(tempKey.path(), pass, nil, nil, &privkey) != 0 {
if ssh_pki_import_privkey_base64(&privInpCChar, pass, nil, nil, &privkey) != 0 {
print("help?!?")
print("likeley password is incorrect")
print("likeley passphrase is incorrect")
}
status = ssh_userauth_publickey(session, nil, privkey)
@@ -207,11 +206,6 @@ class SSHHandler: ObservableObject {
withAnimation { authorized = true }
return
//if u got this far, youre authed!
//cleanpu here:
// ssh_key_free(pubkey)
// ssh_key_free(privkey)
// try? fileManager.removeItem(at: tempPubkey)
// try? fileManager.removeItem(at: tempKey)
}
func authWithPw() -> Bool {
@@ -305,26 +299,25 @@ class SSHHandler: ObservableObject {
self.readTimer = nil
return
}
self.readFromChannel()
}
RunLoop.main.add(self.readTimer!, forMode: .common)
// RunLoop.main.add(self.readTimer!, forMode: .common)
}
func readFromChannel() {
guard ssh_channel_is_open(channel) != 0 else { return }
guard ssh_channel_is_eof(channel) == 0 else { return }
func readFromChannel() -> String {
guard ssh_channel_is_open(channel) != 0 else { return "" }
guard ssh_channel_is_eof(channel) == 0 else { return "" }
var buffer: [CChar] = Array(repeating: 0, count: 256)
let nbytes = ssh_channel_read_nonblocking(channel, &buffer, UInt32(buffer.count), 0)
guard nbytes > 0 else { return }
guard nbytes > 0 else { return "" }
write(1, buffer, Int(nbytes))
let data = Data(bytes: buffer, count: buffer.count)
if let string = String(data: data, encoding: .utf8) {
self.terminal.append(string)
terminal = self.terminal.replacingOccurrences(of: "[K", with: "\n")
return string
}
return ""
}
private func logSshGetError() {

View File

@@ -75,7 +75,7 @@ struct ConnectionView: View {
.fileImporter(isPresented: $pubPickerPresented, allowedContentTypes: [.item, .content, .data]) { (Result) in
do {
let fileURL = try Result.get()
pubkey = try! Data(contentsOf: fileURL)
pubkey = try? Data(contentsOf: fileURL)
print(fileURL)
} catch {
print(error.localizedDescription)
@@ -97,7 +97,8 @@ struct ConnectionView: View {
.fileImporter(isPresented: $privPickerPresented, allowedContentTypes: [.item, .content, .data]) { (Result) in
do {
let fileURL = try Result.get()
privkey = try! Data(contentsOf: fileURL)
privkey = try? Data(contentsOf: fileURL)
print(privkey)
print(fileURL)
} catch {
print(error.localizedDescription)
@@ -119,7 +120,10 @@ struct ConnectionView: View {
}
NavigationLink() {
TerminalView(handler: handler)
Button("Reload") {
handler.readFromChannel()
}
TerminalController(handler: handler)
} label: {
Label("Open Terminal", systemImage: "apple.terminal")
}

View File

@@ -0,0 +1,28 @@
//
// TerminalController.swift
// ShhShell
//
// Created by neon443 on 21/06/2025.
//
import Foundation
import UIKit
import SwiftUI
import SwiftTerm
struct TerminalController: UIViewRepresentable {
@ObservedObject var handler: SSHHandler
func makeUIView(context: Context) -> TerminalView {
let tv = TerminalView()
let terminalDelegate = ShhTerminalViewDelegate()
tv.terminalDelegate = terminalDelegate
tv.getTerminal().feed(text: handler.readFromChannel())
return tv
}
func updateUIView(_ tv: TerminalView, context: Context) {
tv.getTerminal().feed(text: handler.readFromChannel())
}
}

View File

@@ -0,0 +1,52 @@
//
// TerminalDelegate.swift
// ShhShell
//
// Created by neon443 on 21/06/2025.
//
import Foundation
import SwiftTerm
class ShhTerminalViewDelegate: TerminalViewDelegate {
func sizeChanged(source: TerminalView, newCols: Int, newRows: Int) {
print(newRows, newCols)
}
func setTerminalTitle(source: TerminalView, title: String) {
print(title)
}
func hostCurrentDirectoryUpdate(source: TerminalView, directory: String?) {
print(directory)
}
func send(source: TerminalView, data: ArraySlice<UInt8>) {
print(data)
}
func scrolled(source: TerminalView, position: Double) {
print(position)
}
func requestOpenLink(source: TerminalView, link: String, params: [String : String]) {
print(link)
print(params)
}
func bell(source: TerminalView) {
print("ding")
}
func clipboardCopy(source: TerminalView, content: Data) {
print(content)
}
func iTermContent(source: TerminalView, content: ArraySlice<UInt8>) {
print("idk what this does")
}
func rangeChanged(source: TerminalView, startY: Int, endY: Int) {
print(startY, endY)
}
}

View File

@@ -1,43 +0,0 @@
//
// TerminalView.swift
// ShhShell
//
// Created by neon443 on 09/06/2025.
//
import SwiftUI
import Runestone
struct TerminalView: View {
@ObservedObject var handler: SSHHandler
@Environment(\.dismiss) var dismiss
var body: some View {
Button("write") {
handler.writeToChannel("top\n")
}
TextViewController(handler: handler)
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button("reload") {
handler.readFromChannel()
}
}
ToolbarItem(placement: .confirmationAction) {
Button() {
handler.disconnect()
withAnimation { handler.testSuceeded = false }
withAnimation { handler.connected = false }
dismiss()
} label: {
Label("Exit", systemImage: "xmark.square.fill")
}
.disabled(!handler.connected)
}
}
}
}
#Preview {
TerminalView(handler: SSHHandler(host: Host.debug))
}

View File

@@ -1,75 +0,0 @@
//
// TextViewController.swift
// ShhShell
//
// Created by neon443 on 09/06/2025.
//
import Foundation
import UIKit
import SwiftUI
import Runestone
import TreeSitterBashRunestone
struct TextViewController: UIViewRepresentable {
@ObservedObject var handler: SSHHandler
func makeUIView(context: Context) -> TextView {
let languageMode = TreeSitterLanguageMode(language: .bash)
let textView = TextView()
setTextViewState(on: textView)
var editorDelegate = textView.editorDelegate
editorDelegate = TerminalViewDelegate(handler: handler)
textView.editorDelegate = editorDelegate
textView.translatesAutoresizingMaskIntoConstraints = false
textView.backgroundColor = .systemBackground
return textView
}
func updateUIView(_ textView: TextView, context: Context) {
textView.text = handler.terminal
}
private func setTextViewState(on textView: TextView) {
let text = handler.terminal
DispatchQueue.global(qos: .userInitiated).async {
let state = TextViewState(text: text, language: .bash)
DispatchQueue.main.async {
textView.setState(state)
}
}
}
}
class TerminalViewDelegate: TextViewDelegate {
@ObservedObject var handler: SSHHandler
init(handler: SSHHandler) {
self.handler = handler
}
func textViewDidChangeGutterWidth(_ textView: TextView) {
print(textView.gutterWidth)
}
func textViewDidChange(_ textView: TextView) {
handler.writeToChannel(textView.text)
}
func textView(_ textView: TextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
print(range)
print(text)
return true
}
func textViewShouldBeginEditing(_ textView: TextView) -> Bool {
print("can edit")
return true
}
func textView(_ textView: TextView, shouldInsert characterPair: any CharacterPair, in range: NSRange) -> Bool {
return false
}
}