ARC-Explorer / Sources /Console.swift
will-taggart's picture
Publish ARC Explorer v0.1 source and illustrated ACM technical report
0dc8570 verified
Raw History Blame Contribute Delete
2.49 kB
import SwiftUI
import AppKit
import SwiftTerm
// SSH runs under a local PTY so keyboard input, terminal modes and resizing
// propagate to the remote login-node PTY.
@MainActor final class ARCConsole:NSObject,ObservableObject,@preconcurrency LocalProcessTerminalViewDelegate {
@Published var running=false
@Published var session="No session"
let view=LocalProcessTerminalView(frame:NSRect(x:0,y:0,width:1000,height:500),font:NSFont.monospacedSystemFont(ofSize:14,weight:.regular),options:TerminalOptions(scrollback:5000))
override init(){super.init();view.processDelegate=self;view.feed(text:"Connect to ARC, then choose Start login terminal.\r\n")}
static func request(_ p:Profile)throws->String {
try p.validate()
return "exec env TERM=xterm-256color bash -il"
}
func start(profile:Profile,ssh:[String],target:String)throws {
guard !running else{return}
let command=try Self.request(profile)
var env=ProcessInfo.processInfo.environment;env["TERM"]="xterm-256color"
view.feed(text:"\r\nConnecting to login node \(target)\r\nLogin node is not for compute intensive jobs.\r\n")
view.startProcess(executable:"/usr/bin/ssh",args:ssh+["-o","BatchMode=yes","-tt",target,"bash -lc "+shellQuote(command)],environment:env.map{"\($0.key)=\($0.value)"})
guard view.process.running else{throw Failure(message:"Unable to start SSH terminal.")}
running=true;session="\(target) · login-node shell";view.window?.makeFirstResponder(view)
}
func write(_ text:String){guard running else{return};view.send(txt:text)}
func stop(){guard running else{return};view.terminate();session="Closing SSH session…"}
func sizeChanged(source:LocalProcessTerminalView,newCols:Int,newRows:Int){}
func setTerminalTitle(source:LocalProcessTerminalView,title:String){}
func hostCurrentDirectoryUpdate(source:TerminalView,directory:String?){}
func processTerminated(source:TerminalView,exitCode:Int32?){running=false;session="Session ended (SSH exit \(exitCode.map(String.init) ?? "unknown"))";view.feed(text:"\r\n[\(session)]\r\n")}
}
struct NativeTerminal:NSViewRepresentable {
@ObservedObject var console:ARCConsole
let dark:Bool
func makeNSView(context:Context)->LocalProcessTerminalView{configure();return console.view}
func updateNSView(_ view:LocalProcessTerminalView,context:Context){configure()}
private func configure(){
console.view.nativeForegroundColor=dark ? .white:.black
console.view.nativeBackgroundColor=dark ? NSColor(calibratedWhite:0.04,alpha:1):.white
}
}