mirror of
https://github.com/SagerNet/sing-box-for-apple.git
synced 2026-06-03 11:37:44 +00:00
133 lines
5.2 KiB
Swift
133 lines
5.2 KiB
Swift
#if canImport(GhosttyTerminal) && os(iOS)
|
|
import GhosttyTerminal
|
|
import Library
|
|
import SwiftUI
|
|
#if !targetEnvironment(macCatalyst)
|
|
import UIKit
|
|
#endif
|
|
|
|
@MainActor
|
|
struct TerminalSessionContainerView: View {
|
|
@StateObject private var sessionManager = TerminalSessionManager()
|
|
private let initialSession: TailscaleSSHPresentedSession
|
|
@Environment(\.dismiss) private var dismiss
|
|
@Environment(\.openURL) private var openURL
|
|
|
|
init(_ initialSession: TailscaleSSHPresentedSession) {
|
|
self.initialSession = initialSession
|
|
}
|
|
|
|
var body: some View {
|
|
ZStack {
|
|
ForEach(sessionManager.sessions) { managed in
|
|
let isActive = managed.id == sessionManager.activeSessionID
|
|
TerminalSessionContentView(
|
|
viewModel: managed.viewModel,
|
|
presentedSession: managed.presentedSession,
|
|
isActive: isActive,
|
|
onCloseSession: { sessionManager.closeSession(id: managed.id) }
|
|
)
|
|
.opacity(isActive ? 1 : 0)
|
|
.zIndex(isActive ? 1 : 0)
|
|
.allowsHitTesting(isActive)
|
|
.onAppear {
|
|
setupCallbacks(for: managed)
|
|
}
|
|
}
|
|
}
|
|
.navigationTitle(sessionManager.activeDisplayTitle)
|
|
.navigationBarTitleDisplayMode(.inline)
|
|
.toolbar {
|
|
ToolbarItem(placement: .cancellationAction) {
|
|
Button {
|
|
dismiss()
|
|
} label: {
|
|
Image(systemName: "chevron.backward")
|
|
}
|
|
}
|
|
ToolbarItem(placement: .primaryAction) {
|
|
TerminalSessionMenuButton(sessionManager: sessionManager)
|
|
}
|
|
}
|
|
.background(
|
|
Button(action: {
|
|
if let id = sessionManager.activeSessionID {
|
|
sessionManager.closeSession(id: id)
|
|
}
|
|
}) {}
|
|
.keyboardShortcut("w", modifiers: .command)
|
|
.opacity(0)
|
|
.frame(width: 0, height: 0)
|
|
.accessibilityHidden(true)
|
|
)
|
|
.background(
|
|
Button(action: {
|
|
sessionManager.createDuplicateSession()
|
|
}) {}
|
|
.keyboardShortcut("n", modifiers: .command)
|
|
.opacity(0)
|
|
.frame(width: 0, height: 0)
|
|
.accessibilityHidden(true)
|
|
)
|
|
.onAppear {
|
|
sessionManager.onDismissAll = { dismiss() }
|
|
sessionManager.addSession(from: initialSession)
|
|
}
|
|
.onDisappear {
|
|
sessionManager.disconnectAll()
|
|
}
|
|
}
|
|
|
|
private func setupCallbacks(for managed: TerminalSessionManager.ManagedSession) {
|
|
managed.viewModel.extras.onOpenURL = { urlString, _ in
|
|
guard let url = URL(string: urlString) else { return }
|
|
openURL(url)
|
|
}
|
|
#if !targetEnvironment(macCatalyst)
|
|
managed.viewModel.extras.onRequestTextSelection = { request in
|
|
presentTerminalSelectionSheet(request: request)
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#if !targetEnvironment(macCatalyst)
|
|
@MainActor
|
|
private func presentTerminalSelectionSheet(request: TerminalTextSelectionRequest) {
|
|
guard let presenter = topmostViewController() else { return }
|
|
let selectionVC = TailsshTerminalSelectionViewController(
|
|
text: request.text,
|
|
anchorRange: request.anchorRange
|
|
)
|
|
selectionVC.onOpenURL = { url in
|
|
openURL(url)
|
|
}
|
|
let nav = UINavigationController(rootViewController: selectionVC)
|
|
nav.modalPresentationStyle = .pageSheet
|
|
if let sheet = nav.sheetPresentationController {
|
|
sheet.detents = [.medium(), .large()]
|
|
sheet.prefersGrabberVisible = true
|
|
}
|
|
presenter.present(nav, animated: true)
|
|
}
|
|
|
|
@MainActor
|
|
private func topmostViewController() -> UIViewController? {
|
|
let scene = UIApplication.shared.connectedScenes
|
|
.compactMap { $0 as? UIWindowScene }
|
|
.first { $0.activationState == .foregroundActive }
|
|
?? UIApplication.shared.connectedScenes
|
|
.compactMap { $0 as? UIWindowScene }
|
|
.first
|
|
guard let root = scene?.windows.first(where: { $0.isKeyWindow })?.rootViewController
|
|
?? scene?.windows.first?.rootViewController
|
|
else { return nil }
|
|
var top = root
|
|
while let presented = top.presentedViewController {
|
|
top = presented
|
|
}
|
|
return top
|
|
}
|
|
#endif
|
|
}
|
|
#endif
|