初始提交: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:
16
vendor/libghostty-spm/Sources/GhosttyTerminal/View/TerminalView.swift
vendored
Normal file
16
vendor/libghostty-spm/Sources/GhosttyTerminal/View/TerminalView.swift
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
//
|
||||
// TerminalView.swift
|
||||
// libghostty-spm
|
||||
//
|
||||
// Created by Lakr233 on 2026/3/16.
|
||||
//
|
||||
|
||||
#if canImport(UIKit)
|
||||
import UIKit
|
||||
|
||||
public typealias TerminalView = UITerminalView
|
||||
#elseif canImport(AppKit)
|
||||
import AppKit
|
||||
|
||||
public typealias TerminalView = AppTerminalView
|
||||
#endif
|
||||
104
vendor/libghostty-spm/Sources/GhosttyTerminal/View/TerminalViewRepresentable.swift
vendored
Normal file
104
vendor/libghostty-spm/Sources/GhosttyTerminal/View/TerminalViewRepresentable.swift
vendored
Normal file
@@ -0,0 +1,104 @@
|
||||
//
|
||||
// TerminalViewRepresentable.swift
|
||||
// libghostty-spm
|
||||
//
|
||||
// Created by Lakr233 on 2026/3/16.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
#if canImport(UIKit)
|
||||
import UIKit
|
||||
#elseif canImport(AppKit)
|
||||
import AppKit
|
||||
#endif
|
||||
|
||||
@MainActor
|
||||
struct TerminalViewRepresentable {
|
||||
let context: TerminalViewState
|
||||
let controller: TerminalController
|
||||
let configuration: TerminalSurfaceOptions
|
||||
let focusBinding: TerminalFocusBinding?
|
||||
|
||||
func configureView(_ view: TerminalView, initial: Bool) {
|
||||
if initial {
|
||||
view.delegate = context
|
||||
}
|
||||
|
||||
if let currentController = view.controller, currentController === controller {
|
||||
// Keep the current surface.
|
||||
} else {
|
||||
view.controller = controller
|
||||
}
|
||||
|
||||
if !view.configuration.isEquivalent(to: configuration) {
|
||||
view.configuration = configuration
|
||||
}
|
||||
}
|
||||
|
||||
static func synchronizeFocus(_ view: TerminalView, with binding: TerminalFocusBinding?) {
|
||||
guard let binding else { return }
|
||||
|
||||
DispatchQueue.main.async { [weak view] in
|
||||
#if canImport(UIKit)
|
||||
guard let view, view.window != nil else { return }
|
||||
if binding.isFocused {
|
||||
if !view.isFirstResponder { view.becomeFirstResponder() }
|
||||
} else if view.isFirstResponder {
|
||||
_ = view.resignFirstResponder()
|
||||
}
|
||||
#elseif canImport(AppKit)
|
||||
guard let view, let window = view.window else { return }
|
||||
if binding.isFocused {
|
||||
if window.firstResponder !== view {
|
||||
window.makeFirstResponder(view)
|
||||
}
|
||||
} else if window.firstResponder === view {
|
||||
window.makeFirstResponder(nil)
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
struct TerminalFocusBinding {
|
||||
private let read: () -> Bool
|
||||
private let write: (Bool) -> Void
|
||||
|
||||
var isFocused: Bool {
|
||||
read()
|
||||
}
|
||||
|
||||
func setFocused(_ focused: Bool) {
|
||||
write(focused)
|
||||
}
|
||||
|
||||
static func bool(_ binding: FocusState<Bool>.Binding) -> TerminalFocusBinding {
|
||||
TerminalFocusBinding(
|
||||
read: { binding.wrappedValue },
|
||||
write: { binding.wrappedValue = $0 }
|
||||
)
|
||||
}
|
||||
|
||||
static func optional<Value: Hashable>(
|
||||
_ binding: FocusState<Value?>.Binding,
|
||||
equals value: Value
|
||||
) -> TerminalFocusBinding {
|
||||
TerminalFocusBinding(
|
||||
read: { binding.wrappedValue == value },
|
||||
write: { focused in
|
||||
binding.wrappedValue = focused ? value : nil
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
extension TerminalFocusBinding? {
|
||||
func setFocused(_ focused: Bool) {
|
||||
guard let binding = self, binding.isFocused != focused else {
|
||||
return
|
||||
}
|
||||
binding.setFocused(focused)
|
||||
}
|
||||
}
|
||||
35
vendor/libghostty-spm/Sources/GhosttyTerminal/View/TerminalViewRepresentable@AppKit.swift
vendored
Normal file
35
vendor/libghostty-spm/Sources/GhosttyTerminal/View/TerminalViewRepresentable@AppKit.swift
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
//
|
||||
// TerminalViewRepresentable@AppKit.swift
|
||||
// libghostty-spm
|
||||
//
|
||||
// Created by Lakr233 on 2026/3/16.
|
||||
//
|
||||
|
||||
#if canImport(AppKit) && !canImport(UIKit)
|
||||
import AppKit
|
||||
import SwiftUI
|
||||
|
||||
extension TerminalViewRepresentable: NSViewRepresentable {
|
||||
func makeNSView(context _: Context) -> TerminalView {
|
||||
let view = TerminalView(frame: .zero)
|
||||
configureView(view, initial: true)
|
||||
view.onFocusChange = { focused in
|
||||
focusBinding.setFocused(focused)
|
||||
}
|
||||
Self.synchronizeFocus(view, with: focusBinding)
|
||||
return view
|
||||
}
|
||||
|
||||
func updateNSView(_ view: TerminalView, context _: Context) {
|
||||
configureView(view, initial: false)
|
||||
view.onFocusChange = { focused in
|
||||
focusBinding.setFocused(focused)
|
||||
}
|
||||
Self.synchronizeFocus(view, with: focusBinding)
|
||||
}
|
||||
|
||||
static func dismantleNSView(_ view: TerminalView, coordinator _: ()) {
|
||||
view.onFocusChange = nil
|
||||
}
|
||||
}
|
||||
#endif
|
||||
58
vendor/libghostty-spm/Sources/GhosttyTerminal/View/TerminalViewRepresentable@UIKit.swift
vendored
Normal file
58
vendor/libghostty-spm/Sources/GhosttyTerminal/View/TerminalViewRepresentable@UIKit.swift
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
//
|
||||
// TerminalViewRepresentable@UIKit.swift
|
||||
// libghostty-spm
|
||||
//
|
||||
// Created by Lakr233 on 2026/3/16.
|
||||
//
|
||||
|
||||
#if canImport(UIKit)
|
||||
import SwiftUI
|
||||
import UIKit
|
||||
|
||||
extension TerminalViewRepresentable: UIViewRepresentable {
|
||||
func makeCoordinator() -> Coordinator {
|
||||
Coordinator()
|
||||
}
|
||||
|
||||
func makeUIView(context viewContext: Context) -> TerminalView {
|
||||
let view = TerminalView(frame: .zero)
|
||||
configureView(view, initial: true)
|
||||
viewContext.coordinator.attach(to: view, focusBinding: focusBinding)
|
||||
Self.synchronizeFocus(view, with: focusBinding)
|
||||
return view
|
||||
}
|
||||
|
||||
func updateUIView(_ view: TerminalView, context viewContext: Context) {
|
||||
configureView(view, initial: false)
|
||||
viewContext.coordinator.attach(to: view, focusBinding: focusBinding)
|
||||
Self.synchronizeFocus(view, with: focusBinding)
|
||||
}
|
||||
|
||||
static func dismantleUIView(_: TerminalView, coordinator: Coordinator) {
|
||||
coordinator.detach()
|
||||
}
|
||||
|
||||
@MainActor
|
||||
final class Coordinator {
|
||||
private weak var view: TerminalView?
|
||||
private var focusBinding: TerminalFocusBinding?
|
||||
|
||||
func attach(
|
||||
to view: TerminalView,
|
||||
focusBinding: TerminalFocusBinding?
|
||||
) {
|
||||
self.view = view
|
||||
self.focusBinding = focusBinding
|
||||
view.onFocusChange = { [weak self] focused in
|
||||
self?.focusBinding.setFocused(focused)
|
||||
}
|
||||
}
|
||||
|
||||
func detach() {
|
||||
view?.onFocusChange = nil
|
||||
focusBinding = nil
|
||||
view = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
Reference in New Issue
Block a user