Files
terminalX/vendor/libghostty-spm/Sources/GhosttyTerminal/Controller/TerminalController+Surface.swift
kid aa92d0e676 初始提交: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>
2026-07-24 10:20:46 +08:00

146 lines
4.9 KiB
Swift

//
// TerminalController+Surface.swift
// libghostty-spm
//
import Foundation
import GhosttyKit
extension TerminalController {
/// Creates a new Ghostty surface with the given configuration.
///
/// The `platformSetup` closure lets the caller fill in
/// platform-specific fields (`platform_tag`, `platform`, `scale_factor`)
/// on the raw surface config struct before the surface is created.
func createSurface(
bridge: TerminalCallbackBridge,
configuration: TerminalSurfaceOptions,
platformSetup: (inout ghostty_surface_config_s) -> Void
) -> ghostty_surface_t? {
guard let app else { return nil }
var surfaceConfig = ghostty_surface_config_new()
surfaceConfig.userdata = Unmanaged.passUnretained(bridge).toOpaque()
surfaceConfig.context = configuration.context.ghosttyValue
configureBackend(&surfaceConfig, from: configuration)
if let fontSize = configuration.fontSize {
surfaceConfig.font_size = fontSize
}
// Like `working_directory` below, the pointers only need to outlive
// `ghostty_surface_new`, which copies the values during surface init.
return withEnvVarEntries(configuration.envVars) { entries, count in
surfaceConfig.env_vars = entries
surfaceConfig.env_var_count = count
return finalizeSurface(
app: app,
bridge: bridge,
configuration: configuration,
config: &surfaceConfig,
workingDirectory: configuration.workingDirectory,
platformSetup: platformSetup
)
}
}
/// Runs `body` with a C representation of `envVars` (`ghostty_env_var_s`
/// entries) that stays valid for the duration of the call.
private func withEnvVarEntries<T>(
_ envVars: [String: String],
_ body: (UnsafeMutablePointer<ghostty_env_var_s>?, Int) -> T
) -> T {
guard !envVars.isEmpty else { return body(nil, 0) }
let strings: [(key: UnsafeMutablePointer<CChar>, value: UnsafeMutablePointer<CChar>)] =
envVars.map { (strdup($0.key), strdup($0.value)) }
defer {
for entry in strings {
free(entry.key)
free(entry.value)
}
}
var entries = strings.map { ghostty_env_var_s(key: $0.key, value: $0.value) }
return entries.withUnsafeMutableBufferPointer { buffer in
body(buffer.baseAddress, buffer.count)
}
}
func retain(_ bridge: TerminalCallbackBridge) {
retainedBridges.append(bridge)
}
func remove(_ bridge: TerminalCallbackBridge) {
retainedBridges.removeAll { $0 === bridge }
}
var retainedBridgeCount: Int {
retainedBridges.count
}
private func configureBackend(
_ config: inout ghostty_surface_config_s,
from options: TerminalSurfaceOptions
) {
guard case let .inMemory(session) = options.backend else {
config.backend = GHOSTTY_SURFACE_IO_BACKEND_EXEC
return
}
config.backend = GHOSTTY_SURFACE_IO_BACKEND_HOST_MANAGED
config.receive_userdata = Unmanaged.passUnretained(session).toOpaque()
config.receive_buffer = InMemoryTerminalSession.receiveBufferCallback
config.receive_resize = InMemoryTerminalSession.receiveResizeCallback
}
private func finalizeSurface(
app: ghostty_app_t,
bridge: TerminalCallbackBridge,
configuration: TerminalSurfaceOptions,
config: inout ghostty_surface_config_s,
workingDirectory: String?,
platformSetup: (inout ghostty_surface_config_s) -> Void
) -> ghostty_surface_t? {
guard let workingDirectory else {
return buildSurface(
app: app,
bridge: bridge,
configuration: configuration,
config: &config,
platformSetup: platformSetup
)
}
return workingDirectory.withCString { ptr in
config.working_directory = ptr
return buildSurface(
app: app,
bridge: bridge,
configuration: configuration,
config: &config,
platformSetup: platformSetup
)
}
}
private func buildSurface(
app: ghostty_app_t,
bridge: TerminalCallbackBridge,
configuration: TerminalSurfaceOptions,
config: inout ghostty_surface_config_s,
platformSetup: (inout ghostty_surface_config_s) -> Void
) -> ghostty_surface_t? {
platformSetup(&config)
guard let surface = ghostty_surface_new(app, &config) else {
return nil
}
retain(bridge)
if case let .inMemory(session) = configuration.backend {
session.setSurface(surface)
}
return surface
}
}