Refactor and Rename to CommandNotch
This commit is contained in:
131
Downterm/CommandNotch/Models/TerminalSession.swift
Normal file
131
Downterm/CommandNotch/Models/TerminalSession.swift
Normal file
@@ -0,0 +1,131 @@
|
||||
import AppKit
|
||||
import SwiftTerm
|
||||
import Combine
|
||||
|
||||
/// Wraps a single SwiftTerm TerminalView + LocalProcess pair.
|
||||
@MainActor
|
||||
class TerminalSession: NSObject, ObservableObject, LocalProcessDelegate, TerminalViewDelegate {
|
||||
|
||||
let id = UUID()
|
||||
let terminalView: TerminalView
|
||||
private var process: LocalProcess?
|
||||
|
||||
@Published var title: String = "shell"
|
||||
@Published var isRunning: Bool = true
|
||||
@Published var currentDirectory: String?
|
||||
|
||||
init(fontSize: CGFloat) {
|
||||
terminalView = TerminalView(frame: NSRect(x: 0, y: 0, width: 600, height: 300))
|
||||
super.init()
|
||||
|
||||
terminalView.terminalDelegate = self
|
||||
|
||||
// Solid black — matches every other element in the notch.
|
||||
// The single `.opacity(notchOpacity)` on ContentView makes
|
||||
// everything uniformly transparent.
|
||||
terminalView.nativeBackgroundColor = .black
|
||||
terminalView.nativeForegroundColor = .init(white: 0.9, alpha: 1.0)
|
||||
|
||||
let font = NSFont.monospacedSystemFont(ofSize: fontSize, weight: .regular)
|
||||
terminalView.font = font
|
||||
|
||||
startShell()
|
||||
}
|
||||
|
||||
// MARK: - Shell management
|
||||
|
||||
private func startShell() {
|
||||
let shellPath = resolveShell()
|
||||
let shellName = (shellPath as NSString).lastPathComponent
|
||||
let loginExecName = "-\(shellName)"
|
||||
|
||||
FileManager.default.changeCurrentDirectoryPath(NSHomeDirectory())
|
||||
let proc = LocalProcess(delegate: self)
|
||||
// Launch as a login shell so user startup files initialize PATH/tools.
|
||||
proc.startProcess(
|
||||
executable: shellPath,
|
||||
args: ["-l"],
|
||||
environment: nil,
|
||||
execName: loginExecName
|
||||
)
|
||||
process = proc
|
||||
title = shellName
|
||||
}
|
||||
|
||||
private func resolveShell() -> String {
|
||||
let custom = UserDefaults.standard.string(forKey: NotchSettings.Keys.terminalShell) ?? ""
|
||||
if !custom.isEmpty && FileManager.default.isExecutableFile(atPath: custom) {
|
||||
return custom
|
||||
}
|
||||
return ProcessInfo.processInfo.environment["SHELL"] ?? "/bin/zsh"
|
||||
}
|
||||
|
||||
func updateFontSize(_ size: CGFloat) {
|
||||
terminalView.font = NSFont.monospacedSystemFont(ofSize: size, weight: .regular)
|
||||
}
|
||||
|
||||
func terminate() {
|
||||
process?.terminate()
|
||||
process = nil
|
||||
isRunning = false
|
||||
}
|
||||
|
||||
// MARK: - LocalProcessDelegate
|
||||
|
||||
nonisolated func processTerminated(_ source: LocalProcess, exitCode: Int32?) {
|
||||
Task { @MainActor in self.isRunning = false }
|
||||
}
|
||||
|
||||
nonisolated func dataReceived(slice: ArraySlice<UInt8>) {
|
||||
let data = slice
|
||||
Task { @MainActor in self.terminalView.feed(byteArray: data) }
|
||||
}
|
||||
|
||||
nonisolated func getWindowSize() -> winsize {
|
||||
var ws = winsize()
|
||||
ws.ws_col = 80
|
||||
ws.ws_row = 24
|
||||
return ws
|
||||
}
|
||||
|
||||
// MARK: - TerminalViewDelegate
|
||||
|
||||
func send(source: TerminalView, data: ArraySlice<UInt8>) {
|
||||
process?.send(data: data)
|
||||
}
|
||||
|
||||
func setTerminalTitle(source: TerminalView, title: String) {
|
||||
self.title = title.isEmpty ? "shell" : title
|
||||
}
|
||||
|
||||
func sizeChanged(source: TerminalView, newCols: Int, newRows: Int) {
|
||||
guard newCols > 0, newRows > 0 else { return }
|
||||
guard let proc = process else { return }
|
||||
let fd = proc.childfd
|
||||
guard fd >= 0 else { return }
|
||||
|
||||
var ws = winsize()
|
||||
ws.ws_col = UInt16(newCols)
|
||||
ws.ws_row = UInt16(newRows)
|
||||
_ = ioctl(fd, TIOCSWINSZ, &ws)
|
||||
}
|
||||
|
||||
func hostCurrentDirectoryUpdate(source: TerminalView, directory: String?) {
|
||||
currentDirectory = directory
|
||||
}
|
||||
|
||||
func scrolled(source: TerminalView, position: Double) {}
|
||||
func rangeChanged(source: TerminalView, startY: Int, endY: Int) {}
|
||||
|
||||
func clipboardCopy(source: TerminalView, content: Data) {
|
||||
NSPasteboard.general.clearContents()
|
||||
NSPasteboard.general.setData(content, forType: .string)
|
||||
}
|
||||
|
||||
func requestOpenLink(source: TerminalView, link: String, params: [String : String]) {
|
||||
if let url = URL(string: link) { NSWorkspace.shared.open(url) }
|
||||
}
|
||||
|
||||
func bell(source: TerminalView) { NSSound.beep() }
|
||||
func iTermContent(source: TerminalView, content: ArraySlice<UInt8>) {}
|
||||
}
|
||||
Reference in New Issue
Block a user