Skip to content

Commit

Permalink
Add SSH terminal client example
Browse files Browse the repository at this point in the history
  • Loading branch information
Joannis committed Nov 8, 2024
1 parent 819bb80 commit ee57891
Show file tree
Hide file tree
Showing 13 changed files with 1,075 additions and 0 deletions.
606 changes: 606 additions & 0 deletions Examples/CitadelCLI/CitadelCLI.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1600"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES"
buildArchitectures = "Automatic">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "247A3D352CDB31BF003B8A01"
BuildableName = "CitadelCLI.app"
BlueprintName = "CitadelCLI"
ReferencedContainer = "container:CitadelCLI.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
<Testables>
<TestableReference
skipped = "NO"
parallelizable = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "247A3D462CDB31C1003B8A01"
BuildableName = "CitadelCLITests.xctest"
BlueprintName = "CitadelCLITests"
ReferencedContainer = "container:CitadelCLI.xcodeproj">
</BuildableReference>
</TestableReference>
<TestableReference
skipped = "NO"
parallelizable = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "247A3D502CDB31C1003B8A01"
BuildableName = "CitadelCLIUITests.xctest"
BlueprintName = "CitadelCLIUITests"
ReferencedContainer = "container:CitadelCLI.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "247A3D352CDB31BF003B8A01"
BuildableName = "CitadelCLI.app"
BlueprintName = "CitadelCLI"
ReferencedContainer = "container:CitadelCLI.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "247A3D352CDB31BF003B8A01"
BuildableName = "CitadelCLI.app"
BlueprintName = "CitadelCLI"
ReferencedContainer = "container:CitadelCLI.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"colors" : [
{
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
{
"images" : [
{
"idiom" : "mac",
"scale" : "1x",
"size" : "16x16"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "16x16"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "32x32"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "32x32"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "128x128"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "128x128"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "256x256"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "256x256"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "512x512"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "512x512"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
6 changes: 6 additions & 0 deletions Examples/CitadelCLI/CitadelCLI/Assets.xcassets/Contents.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
12 changes: 12 additions & 0 deletions Examples/CitadelCLI/CitadelCLI/CitadelCLI.entitlements
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.files.user-selected.read-only</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
</dict>
</plist>
150 changes: 150 additions & 0 deletions Examples/CitadelCLI/CitadelCLI/CitadelCLIApp.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
//
// CitadelCLIApp.swift
// CitadelCLI
//
// Created by Joannis Orlandos on 06/11/2024.
//

import SwiftUI
import NIOCore
import Citadel
import SwiftTerm

@main
struct CitadelCLIApp: App {
var body: some Scene {
WindowGroup {
SSHView()
.frame(width: 500, height: 500)
}
}
}

struct SSHView: View {
@State var client: SSHClient?

var body: some View {
if let client {
Terminal(client: client)
} else {
ProgressView().task {
self.client = try! await SSHClient
.connect(
host: "127.0.0.1",
port: 2222,
authenticationMethod: .passwordBased(username: "ubuntu", password: "test"),
hostKeyValidator: .acceptAnything(),
reconnect: .never
)
}
}
}
}

struct Terminal: NSViewRepresentable {
let client: SSHClient

enum Event {
case send(ByteBuffer)
case changeSize(cols: Int, rows: Int)
}

final class Coordinator: TerminalViewDelegate {
let client: SSHClient
weak var terminalView: TerminalView!
private let events = AsyncStream<Event>.makeStream()

init(client: SSHClient) {
self.client = client
}

func sizeChanged(source: TerminalView, newCols: Int, newRows: Int) {
guard newCols > 0, newRows > 0 else {
return
}

events.continuation.yield(.changeSize(cols: newCols, rows: newRows))
}

func setTerminalTitle(source: TerminalView, title: String) {

}

func hostCurrentDirectoryUpdate(source: TerminalView, directory: String?) {

}

func send(source: TerminalView, data: ArraySlice<UInt8>) {
events.continuation.yield(.send(ByteBuffer(bytes: data)))
}

func scrolled(source: TerminalView, position: Double) {

}

func clipboardCopy(source: TerminalView, content: Data) {

}

func rangeChanged(source: TerminalView, startY: Int, endY: Int) {

}

func run() async throws {
try await client.withPTY(
.init(
wantReply: true,
term: "",
terminalCharacterWidth: 0,
terminalRowHeight: 0,
terminalPixelWidth: 0,
terminalPixelHeight: 0,
terminalModes: .init([.ECHO: 5])
)
) { [events = events.stream] inbound, outbound in
await withThrowingTaskGroup(of: Void.self) { taskGroup in
taskGroup.addTask {
for try await input in inbound {
switch input {
case .stdout(var buffer), .stderr(var buffer):
let bytes = buffer.readBytes(length: buffer.readableBytes)![...]
await self.terminalView!.feed(byteArray: bytes)
}
}
}

taskGroup.addTask {
for try await event in events {
switch event {
case .send(let buffer):
try await outbound.write(buffer)
case .changeSize(let cols, let rows):
try await outbound.changeSize(cols: cols, rows: rows)
}
}
}
}
}
}
}

func makeNSView(context: Context) -> TerminalView {
let terminalView = TerminalView()
terminalView.terminalDelegate = context.coordinator
context.coordinator.terminalView = terminalView

Task {
try await context.coordinator.run()
}

return terminalView
}

func updateNSView(_ nsView: TerminalView, context: Context) {

}

func makeCoordinator() -> Coordinator {
Coordinator(client: client)
}
}
24 changes: 24 additions & 0 deletions Examples/CitadelCLI/CitadelCLI/ContentView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//
// ContentView.swift
// CitadelCLI
//
// Created by Joannis Orlandos on 06/11/2024.
//

import SwiftUI

struct ContentView: View {
var body: some View {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundStyle(.tint)
Text("Hello, world!")
}
.padding()
}
}

#Preview {
ContentView()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
Loading

0 comments on commit ee57891

Please sign in to comment.