初始提交:terminalX 可运行态(M0/M1/M1.5 已真机验证)

- M0: libghostty SSH 终端(渲染/输入/连接)+ 白屏修复(OutputGate) + 会话状态机/自动重连
- M1: tsnet 用户态组网 + SSH-over-tsnet(fd 桥),shell 级真机验证;R5(Go+gvisor+C+++Swift 同进程) retire
- M1.5: tmux -CC 原生 tab(MVP)
- 结构: packages/(TXCore·TXTransport), apps/TerminalX, vendor/(libghostty-spm/libssh2/mbedtls/tsnet-bridge), artifacts/
- 文档: CLAUDE.md + docs/HANDOFF.md(新会话入口)
- 环境: 认证代理→依赖 vendor 本地化;Go 在 ~/.local/go;仅模拟器/未签名
- 待续: M2 mosh, tmux 多 pane, M4 安全(host key/SE)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
kid
2026-07-24 10:20:46 +08:00
commit aa92d0e676
2761 changed files with 803505 additions and 0 deletions

View File

@@ -0,0 +1,52 @@
import Cocoa
import GhosttyTerminal
final class AppDelegate: NSObject, NSApplicationDelegate {
private let defaultContentSize = NSSize(width: 720, height: 480)
private let minimumContentSize = NSSize(width: 480, height: 320)
private var window: NSWindow?
func applicationDidFinishLaunching(_: Notification) {
TerminalDebugLog.enable(.standard)
let window = NSWindow(
contentRect: NSRect(origin: .zero, size: defaultContentSize),
styleMask: [.titled, .closable, .miniaturizable, .resizable],
backing: .buffered,
defer: false
)
window.title = "GhosttyKit Sandbox Demo"
window.isOpaque = true
window.backgroundColor = .windowBackgroundColor
window.titlebarAppearsTransparent = false
window.contentMinSize = minimumContentSize
window.contentViewController = ViewController()
window.center()
window.makeKeyAndOrderFront(nil)
repairRestoredWindowSizeIfNeeded(window)
NSApp.activate(ignoringOtherApps: true)
self.window = window
}
func applicationSupportsSecureRestorableState(_: NSApplication) -> Bool {
true
}
func applicationShouldTerminateAfterLastWindowClosed(_: NSApplication) -> Bool {
true
}
private func repairRestoredWindowSizeIfNeeded(_ window: NSWindow) {
DispatchQueue.main.async { [defaultContentSize, minimumContentSize] in
let contentRect = window.contentRect(forFrameRect: window.frame)
guard contentRect.width < minimumContentSize.width
|| contentRect.height < minimumContentSize.height
else {
return
}
window.setContentSize(defaultContentSize)
window.center()
}
}
}

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--Generator: Apple Native CoreSVG 341-->
<!DOCTYPE svg
PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 403.625 361.875">
<g>
<rect height="361.875" opacity="0" width="403.625" x="0" y="0"/>
<path d="M93.5 332.625C146.625 332.625 185.75 302.875 185.75 249.25C185.75 201.25 154 181.875 110.625 168.625L86 161.25C56.125 152.25 31.5 137.5 31.5 105.125C31.5 70.375 59.625 51.5 90.875 51.5C121.125 51.5 148.25 68 154.25 100.75C155.75 107.75 159.875 111.375 166.875 111.375C173.875 111.375 179.25 107 179.25 99C179.25 96.375 178.875 93.25 178.125 90.25C168.875 49.625 132.375 27.25 91.25 27.25C42.75 27.25 4.625 58.625 4.625 106.5C4.625 151.75 34 173.5 77.5 186.625L102.25 194.25C134.25 203.875 158.75 218.375 158.75 251.75C158.75 287.25 130.625 308.375 94.625 308.375C58.625 308.375 31 289.5 24.75 255C22.875 247.375 18.75 244.25 12.125 244.25C4.875 244.25 0 249.5 0 257C0 261.75 0.625 265.75 1.75 269.625C11.875 309.75 49.25 332.625 93.5 332.625ZM314.375 322.625C362.875 322.625 403.625 297.125 403.625 248C403.625 204 372.25 186 334.125 171.75L306.625 161.5C281.75 152.5 254.5 137.75 254.5 108C254.5 77.25 282.5 60.875 313.5 60.875C345.75 60.875 367.625 78.25 373.25 110.25C375 117.25 379.25 121.625 386.5 121.625C394.25 121.625 399 116.875 399 109.25C399 107.25 398.875 103.75 398.125 100.125C390.25 58.5 354.75 37.375 313.5 37.375C268.125 37.375 228.125 63.375 228.125 109.375C228.125 149.5 257.75 170.25 295.875 184.5L323.375 194.875C352.375 205.375 377.5 218.75 377.5 250.125C377.5 282.875 350.25 299.125 314.5 299.125C278.625 299.125 253.875 282 248.875 246.75C247.625 239.75 242.875 235.125 235.625 235.125C228.625 235.125 223 239.75 223 247.875C223 249.75 223.25 251.875 223.5 253.75C231.125 300.625 269.375 322.625 314.375 322.625ZM313.25 361.75C318.25 361.75 322.375 357.625 322.375 352.75L322.375 9C322.375 4.125 318.25 0 313.25 0C308.25 0 304.125 4.125 304.125 9L304.125 352.75C304.125 357.625 308.25 361.75 313.25 361.75Z" fill="white" fill-opacity="0.85"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -0,0 +1,30 @@
{
"fill" : {
"automatic-gradient" : "srgb:1.00000,0.54098,0.84731,1.00000"
},
"groups" : [
{
"layers" : [
{
"glass" : false,
"image-name" : "singaporedollarsign.svg",
"name" : "singaporedollarsign"
}
],
"shadow" : {
"kind" : "neutral",
"opacity" : 0.5
},
"translucency" : {
"enabled" : true,
"value" : 0.5
}
}
],
"supported-platforms" : {
"circles" : [
"watchOS"
],
"squares" : "shared"
}
}

View File

@@ -0,0 +1,10 @@
<?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/>
</dict>
</plist>

View File

@@ -0,0 +1,108 @@
import Cocoa
import GhosttyTerminal
import ShellCraftKit
private final class AppearanceAwareView: NSView {
var onAppearanceChange: (() -> Void)?
override func viewDidChangeEffectiveAppearance() {
super.viewDidChangeEffectiveAppearance()
onAppearanceChange?()
}
}
final class ViewController: NSViewController {
private lazy var terminalView: TerminalView = .init(
frame: NSRect(x: 0, y: 0, width: 720, height: 480)
)
private lazy var shellSession: ShellSession = .init(shell: defaultSandboxShell)
private lazy var controller: TerminalController = .init { builder in
builder.withBackgroundOpacity(0)
builder.withCustom("keybind", "super+k=text:\\x0c")
}
override func loadView() {
let container = AppearanceAwareView()
container.onAppearanceChange = { [weak self] in
self?.applyWindowBackgroundColor()
}
view = container
}
override func viewDidLoad() {
super.viewDidLoad()
configureView()
configureTerminalView()
}
override func viewDidAppear() {
super.viewDidAppear()
activateTerminal()
}
override func viewDidLayout() {
super.viewDidLayout()
terminalView.fitToSize()
}
private func configureView() {
view.wantsLayer = true
applyWindowBackgroundColor()
}
private func applyWindowBackgroundColor() {
// `NSColor.windowBackgroundColor.cgColor` snapshots whichever
// appearance is current at the call site, so drawing it naively
// caches yesterday's light/dark value on the layer. Resolve it
// under the view's effective appearance so the layer follows
// system toggles.
view.effectiveAppearance.performAsCurrentDrawingAppearance {
view.layer?.backgroundColor = NSColor.windowBackgroundColor.cgColor
}
}
private func configureTerminalView() {
terminalView.delegate = self
terminalView.setAccessibilityElement(true)
terminalView.setAccessibilityIdentifier("terminal.surface")
terminalView.setAccessibilityLabel("Terminal Surface")
terminalView.configuration = TerminalSurfaceOptions(
backend: .inMemory(shellSession.terminalSession)
)
terminalView.controller = controller
terminalView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(terminalView)
NSLayoutConstraint.activate([
terminalView.topAnchor.constraint(equalTo: view.topAnchor),
terminalView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
terminalView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
terminalView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
])
}
private func activateTerminal() {
view.window?.makeFirstResponder(terminalView)
shellSession.start()
}
}
// MARK: - Terminal Callbacks
extension ViewController:
TerminalSurfaceTitleDelegate,
TerminalSurfaceResizeDelegate,
TerminalSurfaceCloseDelegate
{
func terminalDidChangeTitle(_ title: String) {
view.window?.title = title
}
func terminalDidResize(columns _: Int, rows _: Int) {}
func terminalDidClose(processAlive _: Bool) {
view.window?.close()
}
}

View File

@@ -0,0 +1,17 @@
//
// main.swift
// GhosttyTerminalApp
//
// Created by qaq on 19/3/2026.
//
import AppKit
MainActor.assumeIsolated {
let delegate = AppDelegate()
let app = NSApplication.shared
app.setActivationPolicy(.regular)
app.delegate = delegate
app.run()
fatalError()
}