Add support for tvOS
This commit is contained in:
@@ -2,7 +2,7 @@ import BackgroundTasks
|
||||
import Foundation
|
||||
import Library
|
||||
|
||||
#if os(iOS)
|
||||
#if os(iOS) || os(tvOS)
|
||||
public class UIProfileUpdateTask: BGAppRefreshTask {
|
||||
public static let taskSchedulerPermittedIdentifier = "\(FilePath.packageName).update_profiles"
|
||||
|
||||
|
||||
@@ -17,19 +17,21 @@ public func FormTextItem(_ name: String, _ value: String) -> some View {
|
||||
Text(value)
|
||||
.multilineTextAlignment(.trailing)
|
||||
.font(Font.system(.caption, design: .monospaced))
|
||||
#if os(iOS) || os(macOS)
|
||||
.textSelection(.enabled)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
public func FormItem(_ title: String, @ViewBuilder content: () -> some View) -> some View {
|
||||
#if os(iOS)
|
||||
#if os(iOS) || os(tvOS)
|
||||
HStack {
|
||||
Text(title)
|
||||
Spacer()
|
||||
Spacer()
|
||||
content()
|
||||
}
|
||||
#else
|
||||
#elseif os(macOS)
|
||||
content()
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -33,11 +33,13 @@ public struct ActiveDashboardView: View {
|
||||
Text("Empty profiles")
|
||||
} else {
|
||||
VStack {
|
||||
#if os(iOS)
|
||||
#if os(iOS) || os(tvOS)
|
||||
if ApplicationLibrary.inPreview || profile.status.isConnected {
|
||||
ExtensionStatusView()
|
||||
.listStyle(.automatic)
|
||||
#if os(iOS)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
#endif
|
||||
}
|
||||
FormView {
|
||||
StartStopButton()
|
||||
@@ -50,7 +52,7 @@ public struct ActiveDashboardView: View {
|
||||
.pickerStyle(.inline)
|
||||
}
|
||||
}
|
||||
#else
|
||||
#elseif os(macOS)
|
||||
if ApplicationLibrary.inPreview || profile.status.isConnected {
|
||||
ExtensionStatusView()
|
||||
}
|
||||
@@ -77,7 +79,19 @@ public struct ActiveDashboardView: View {
|
||||
}
|
||||
}
|
||||
.alertBinding($alert)
|
||||
#if os(iOS)
|
||||
.onChange(of: profile.status, perform: { newValue in
|
||||
if newValue == .disconnecting || newValue == .connected {
|
||||
Task.detached {
|
||||
if let serviceError = try? String(contentsOf: ExtensionProvider.errorFile) {
|
||||
DispatchQueue.main.async {
|
||||
alert = Alert(errorMessage: serviceError)
|
||||
}
|
||||
try? FileManager.default.removeItem(at: ExtensionProvider.errorFile)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
#if os(iOS) || os(tvOS)
|
||||
.onChange(of: scenePhase, perform: { newValue in
|
||||
if newValue == .active {
|
||||
Task.detached {
|
||||
@@ -147,7 +161,7 @@ public struct ActiveDashboardView: View {
|
||||
NotificationCenter.default.post(name: ActiveDashboardView.NotificationUpdateSelectedProfile, object: nil)
|
||||
if profile.status.isConnected {
|
||||
do {
|
||||
try LibboxNewStandaloneCommandClient(FilePath.sharedDirectory.relativePath)?.serviceReload()
|
||||
try LibboxNewStandaloneCommandClient()!.serviceReload()
|
||||
} catch {
|
||||
alert = Alert(error)
|
||||
}
|
||||
|
||||
@@ -116,7 +116,7 @@ public struct ExtensionStatusView: View {
|
||||
|
||||
private func closeConnections() {
|
||||
do {
|
||||
try LibboxNewStandaloneCommandClient(FilePath.sharedDirectory.relativePath)?.closeConnections()
|
||||
try LibboxNewStandaloneCommandClient()!.closeConnections()
|
||||
} catch {
|
||||
alert = Alert(error)
|
||||
}
|
||||
@@ -162,7 +162,11 @@ public struct ExtensionStatusView: View {
|
||||
.font(.system(size: 16))
|
||||
}
|
||||
.frame(minWidth: 125)
|
||||
#if os(tvOS)
|
||||
.padding(EdgeInsets(top: 20, leading: 26, bottom: 20, trailing: 26))
|
||||
#else
|
||||
.padding(EdgeInsets(top: 10, leading: 13, bottom: 10, trailing: 13))
|
||||
#endif
|
||||
.background(backgroundColor)
|
||||
.cornerRadius(10)
|
||||
}
|
||||
@@ -172,6 +176,8 @@ public struct ExtensionStatusView: View {
|
||||
return Color(uiColor: .secondarySystemGroupedBackground)
|
||||
#elseif os(macOS)
|
||||
return Color(nsColor: .textBackgroundColor)
|
||||
#elseif os(tvOS)
|
||||
return Color(uiColor: .black)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ public struct StartStopButton: View {
|
||||
public var body: some View {
|
||||
viewBuilder {
|
||||
if ApplicationLibrary.inPreview {
|
||||
#if os(iOS)
|
||||
#if os(iOS) || os(tvOS)
|
||||
Toggle(isOn: .constant(true)) {
|
||||
Text("Enabled")
|
||||
}
|
||||
@@ -23,7 +23,7 @@ public struct StartStopButton: View {
|
||||
} else if let profile = extensionProfile.wrappedValue {
|
||||
Button0(profile)
|
||||
} else {
|
||||
#if os(iOS)
|
||||
#if os(iOS) || os(tvOS)
|
||||
Toggle(isOn: .constant(false)) {
|
||||
Text("Enabled")
|
||||
}
|
||||
@@ -49,7 +49,7 @@ public struct StartStopButton: View {
|
||||
|
||||
var body: some View {
|
||||
viewBuilder {
|
||||
#if os(iOS)
|
||||
#if os(iOS) || os(tvOS)
|
||||
Toggle(isOn: Binding(get: {
|
||||
profile.status.isConnected
|
||||
}, set: { newValue, _ in
|
||||
|
||||
@@ -14,8 +14,7 @@ public struct GroupItemView: View {
|
||||
self.item = item
|
||||
}
|
||||
|
||||
@State private var errorPresented = false
|
||||
@State private var errorMessage = ""
|
||||
@State private var alert: Alert?
|
||||
|
||||
public var body: some View {
|
||||
HStack {
|
||||
@@ -60,24 +59,17 @@ public struct GroupItemView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
.alert(isPresented: $errorPresented) {
|
||||
Alert(
|
||||
title: Text("Error"),
|
||||
message: Text(errorMessage),
|
||||
dismissButton: .default(Text("Ok"))
|
||||
)
|
||||
}
|
||||
.alertBinding($alert)
|
||||
}
|
||||
|
||||
private func selectOutbound() {
|
||||
do {
|
||||
try LibboxNewStandaloneCommandClient(FilePath.sharedDirectory.relativePath)!.selectOutbound(group.tag, outboundTag: item.tag)
|
||||
try LibboxNewStandaloneCommandClient()!.selectOutbound(group.tag, outboundTag: item.tag)
|
||||
var newGroup = group
|
||||
newGroup.selected = item.tag
|
||||
_group.wrappedValue = newGroup
|
||||
} catch {
|
||||
errorMessage = error.localizedDescription
|
||||
errorPresented = true
|
||||
alert = Alert(error)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -87,6 +79,8 @@ public struct GroupItemView: View {
|
||||
return Color(uiColor: .secondarySystemGroupedBackground)
|
||||
#elseif os(macOS)
|
||||
return Color(nsColor: .textBackgroundColor)
|
||||
#elseif os(tvOS)
|
||||
return Color.black
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,7 +113,7 @@ public struct GroupView: View {
|
||||
}
|
||||
|
||||
private func doURLTest() {
|
||||
try? LibboxNewStandaloneCommandClient(FilePath.sharedDirectory.relativePath)!.urlTest(group.tag)
|
||||
try? LibboxNewStandaloneCommandClient()!.urlTest(group.tag)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -40,6 +40,9 @@ public struct LogView: View {
|
||||
ForEach(Array(logClient.logList.enumerated()), id: \.offset) { it in
|
||||
Text(it.element)
|
||||
.font(logFont)
|
||||
#if os(tvOS)
|
||||
.focusable()
|
||||
#endif
|
||||
Spacer(minLength: 5)
|
||||
}
|
||||
|
||||
@@ -52,6 +55,10 @@ public struct LogView: View {
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
|
||||
.padding()
|
||||
}
|
||||
#if os(tvOS)
|
||||
.focusEffectDisabled()
|
||||
.focusSection()
|
||||
#endif
|
||||
.onAppear {
|
||||
reader.scrollTo(logClient.logList.count - 1)
|
||||
}
|
||||
|
||||
@@ -75,6 +75,10 @@ public extension NavigationPage {
|
||||
}
|
||||
switch self {
|
||||
case .groups:
|
||||
#if os(tvOS)
|
||||
// TODO: fix groups ui
|
||||
return false
|
||||
#endif
|
||||
return profile?.status.isConnectedStrict == true
|
||||
default:
|
||||
return true
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import Foundation
|
||||
import Library
|
||||
import SwiftUI
|
||||
#if os(iOS) || os(macOS)
|
||||
import Foundation
|
||||
import Library
|
||||
import SwiftUI
|
||||
|
||||
public struct EditProfileContentView: View {
|
||||
public struct EditProfileContentView: View {
|
||||
#if os(macOS)
|
||||
public static let windowID = "edit-profile-content"
|
||||
#endif
|
||||
@@ -130,4 +131,6 @@ public struct EditProfileContentView: View {
|
||||
}
|
||||
isChanged = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -46,20 +46,24 @@ public struct EditProfileView: View {
|
||||
FormTextItem("Last Updated", profile.lastUpdatedString)
|
||||
}
|
||||
}
|
||||
#if os(iOS)
|
||||
#if os(iOS) || os(tvOS)
|
||||
Section("Action") {
|
||||
if profile.type != .remote {
|
||||
#if os(iOS)
|
||||
NavigationLink {
|
||||
EditProfileContentView(EditProfileContentView.Context(profileID: profile.id!, readOnly: false))
|
||||
} label: {
|
||||
Text("Edit Content").foregroundColor(.accentColor)
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
#if os(iOS)
|
||||
NavigationLink {
|
||||
EditProfileContentView(EditProfileContentView.Context(profileID: profile.id!, readOnly: true))
|
||||
} label: {
|
||||
Text("View Content").foregroundColor(.accentColor)
|
||||
}
|
||||
#endif
|
||||
Button("Update") {
|
||||
isLoading = true
|
||||
Task.detached {
|
||||
@@ -67,7 +71,7 @@ public struct EditProfileView: View {
|
||||
}
|
||||
}
|
||||
.disabled(isLoading)
|
||||
}
|
||||
#if os(iOS)
|
||||
if #available(iOS 16.0, *) {
|
||||
ShareLink(item: profile.shareLink) {
|
||||
Text("Share")
|
||||
@@ -79,6 +83,8 @@ public struct EditProfileView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -0,0 +1,164 @@
|
||||
#if os(tvOS)
|
||||
|
||||
import DeviceDiscoveryUI
|
||||
import Libbox
|
||||
import Library
|
||||
import SwiftUI
|
||||
|
||||
public struct ImportProfileView: View {
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
|
||||
@State private var isLoading = false
|
||||
@State private var selected = false
|
||||
@State private var alert: Alert?
|
||||
@State private var connection: NWSocket?
|
||||
@State private var profiles: [LibboxProfilePreview]?
|
||||
private let callback: () -> Void
|
||||
|
||||
public init(callback: @escaping () -> Void) {
|
||||
self.callback = callback
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
VStack {
|
||||
if !selected {
|
||||
DevicePicker(
|
||||
.applicationService(name: "sing-box:profile"))
|
||||
{ endpoint in
|
||||
selected = true
|
||||
Task.detached {
|
||||
await handleEndpoint(endpoint)
|
||||
}
|
||||
} label: {
|
||||
Text("Select Device")
|
||||
} fallback: {
|
||||
EmptyView()
|
||||
} parameters: {
|
||||
.applicationService
|
||||
}
|
||||
} else if let profiles {
|
||||
Form {
|
||||
Text("\(profiles.count) Profiles")
|
||||
ForEach(profiles, id: \.profileID) { profile in
|
||||
Button(profile.name) {
|
||||
isLoading = true
|
||||
Task.detached {
|
||||
selectProfile(profileID: profile.profileID)
|
||||
isLoading = false
|
||||
}
|
||||
}.disabled(isLoading)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Text("Connecting...")
|
||||
}
|
||||
}
|
||||
.focusSection()
|
||||
.alertBinding($alert)
|
||||
.navigationTitle("Import Profile")
|
||||
}
|
||||
|
||||
private func reset() {
|
||||
selected = false
|
||||
profiles = nil
|
||||
}
|
||||
|
||||
private func handleEndpoint(_ endpoint: NWEndpoint) async {
|
||||
let connection = NWConnection(to: endpoint, using: NWParameters.applicationService)
|
||||
self.connection = NWSocket(connection)
|
||||
connection.start(queue: .global())
|
||||
do {
|
||||
try loopMessages()
|
||||
} catch {
|
||||
alert = Alert(error)
|
||||
reset()
|
||||
}
|
||||
}
|
||||
|
||||
private func loopMessages() throws {
|
||||
guard let connection else {
|
||||
return
|
||||
}
|
||||
while true {
|
||||
let message = try connection.read()
|
||||
var error: NSError?
|
||||
switch Int64(message[0]) {
|
||||
case LibboxMessageTypeError:
|
||||
let message = LibboxDecodeErrorMessage(message, &error)
|
||||
if let error {
|
||||
throw error
|
||||
}
|
||||
if let message {
|
||||
throw NSError(domain: "remote error: \(message.message)", code: 0)
|
||||
}
|
||||
case LibboxMessageTypeProfileList:
|
||||
let decoder = LibboxProfileDecoder()
|
||||
try decoder.decode(message)
|
||||
let iterator = decoder.iterator()!
|
||||
var profiles = [LibboxProfilePreview]()
|
||||
while iterator.hasNext() {
|
||||
let profile = iterator.next()!
|
||||
if profile.type == LibboxProfileTypeiCloud {
|
||||
// not supported on tvOS
|
||||
continue
|
||||
}
|
||||
profiles.append(profile)
|
||||
}
|
||||
self.profiles = profiles
|
||||
case LibboxMessageTypeProfileContent:
|
||||
let content = LibboxDecodeProfileContent(message, &error)
|
||||
if let error {
|
||||
throw error
|
||||
}
|
||||
try importProfile(content!)
|
||||
default:
|
||||
throw NSError(domain: "unknown message type \(message[0])", code: 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func selectProfile(profileID: Int64) {
|
||||
guard let connection else {
|
||||
return
|
||||
}
|
||||
let request = LibboxProfileContentRequest()
|
||||
request.profileID = profileID
|
||||
do {
|
||||
try connection.write(request.encode())
|
||||
} catch {
|
||||
alert = Alert(error)
|
||||
reset()
|
||||
}
|
||||
}
|
||||
|
||||
private func importProfile(_ content: LibboxProfileContent) throws {
|
||||
var type: ProfileType = .local
|
||||
switch content.type {
|
||||
case LibboxProfileTypeLocal:
|
||||
type = .local
|
||||
case LibboxProfileTypeiCloud:
|
||||
type = .icloud
|
||||
case LibboxProfileTypeRemote:
|
||||
type = .remote
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
let nextProfileID = try ProfileManager.nextID()
|
||||
let profileConfigDirectory = FilePath.sharedDirectory.appendingPathComponent("configs", isDirectory: true)
|
||||
try FileManager.default.createDirectory(at: profileConfigDirectory, withIntermediateDirectories: true)
|
||||
let profileConfig = profileConfigDirectory.appendingPathComponent("config_\(nextProfileID).json")
|
||||
try content.config.write(to: profileConfig, atomically: true, encoding: .utf8)
|
||||
var lastUpdated: Date?
|
||||
if content.lastUpdated > 0 {
|
||||
lastUpdated = Date(timeIntervalSince1970: Double(content.lastUpdated))
|
||||
}
|
||||
try ProfileManager.create(Profile(name: content.name, type: type, path: profileConfig.relativePath, remoteURL: content.remotePath, autoUpdate: content.autoUpdate, lastUpdated: lastUpdated))
|
||||
DispatchQueue.main.async {
|
||||
dismiss()
|
||||
callback()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -41,8 +41,10 @@ public struct NewProfileView: View {
|
||||
.multilineTextAlignment(.trailing)
|
||||
}
|
||||
Picker(selection: $profileType) {
|
||||
#if !os(tvOS)
|
||||
Text("Local").tag(ProfileType.local)
|
||||
Text("iCloud").tag(ProfileType.icloud)
|
||||
#endif
|
||||
Text("Remote").tag(ProfileType.remote)
|
||||
} label: {
|
||||
Text("Type")
|
||||
@@ -54,6 +56,9 @@ public struct NewProfileView: View {
|
||||
} label: {
|
||||
Text("File")
|
||||
}
|
||||
#if os(tvOS)
|
||||
.disabled(true)
|
||||
#endif
|
||||
viewBuilder {
|
||||
if fileImport {
|
||||
HStack {
|
||||
@@ -98,6 +103,7 @@ public struct NewProfileView: View {
|
||||
}
|
||||
.navigationTitle("New Profile")
|
||||
.alertBinding($alert)
|
||||
#if os(iOS) || os(macOS)
|
||||
.fileImporter(
|
||||
isPresented: $pickerPresented,
|
||||
allowedContentTypes: [.json],
|
||||
@@ -113,6 +119,7 @@ public struct NewProfileView: View {
|
||||
return
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
private func createProfile() async {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import Foundation
|
||||
import Libbox
|
||||
import Library
|
||||
import Network
|
||||
import SwiftUI
|
||||
|
||||
public struct ProfileView: View {
|
||||
@@ -16,12 +17,16 @@ public struct ProfileView: View {
|
||||
@State private var alert: Alert?
|
||||
@State private var profileList: [Profile] = []
|
||||
|
||||
#if os(iOS)
|
||||
#if os(iOS) || os(tvOS)
|
||||
@State private var editMode = EditMode.inactive
|
||||
#elseif os(macOS)
|
||||
@Environment(\.openWindow) private var openWindow
|
||||
#endif
|
||||
|
||||
#if os(tvOS)
|
||||
@Environment(\.devicePickerSupports) private var devicePickerSupports
|
||||
#endif
|
||||
|
||||
@State private var observer: Any?
|
||||
|
||||
public init() {}
|
||||
@@ -35,7 +40,7 @@ public struct ProfileView: View {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
#if os(iOS)
|
||||
#if os(iOS) || os(tvOS)
|
||||
ZStack {
|
||||
if let importRemoteProfileRequest {
|
||||
NavigationLink(
|
||||
@@ -51,6 +56,7 @@ public struct ProfileView: View {
|
||||
)
|
||||
}
|
||||
FormView {
|
||||
#if os(iOS) || os(tvOS)
|
||||
NavigationLink {
|
||||
NewProfileView {
|
||||
Task.detached {
|
||||
@@ -61,6 +67,20 @@ public struct ProfileView: View {
|
||||
Text("New Profile").foregroundColor(.accentColor)
|
||||
}
|
||||
.disabled(editMode.isEditing)
|
||||
#endif
|
||||
#if os(tvOS)
|
||||
if devicePickerSupports(.applicationService(name: "sing-box"), parameters: { .applicationService }) {
|
||||
NavigationLink {
|
||||
ImportProfileView {
|
||||
Task.detached {
|
||||
doReload()
|
||||
}
|
||||
}
|
||||
} label: {
|
||||
Text("Import Profile").foregroundColor(.accentColor)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if profileList.isEmpty {
|
||||
Text("Empty Profiles")
|
||||
} else {
|
||||
@@ -77,6 +97,23 @@ public struct ProfileView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
.contextMenu {
|
||||
if profile.type == .remote {
|
||||
Button {
|
||||
isUpdating = true
|
||||
Task.detached {
|
||||
updateProfile(profile)
|
||||
}
|
||||
} label: {
|
||||
Label("Update", systemImage: "arrow.clockwise")
|
||||
}
|
||||
}
|
||||
Button(role: .destructive) {
|
||||
deleteProfile(profile)
|
||||
} label: {
|
||||
Label("Delete", systemImage: "trash.fill")
|
||||
}
|
||||
}
|
||||
}
|
||||
.onMove(perform: moveProfile)
|
||||
.onDelete(perform: deleteProfile)
|
||||
@@ -194,7 +231,7 @@ public struct ProfileView: View {
|
||||
title: Text("Import Remote Profile"),
|
||||
message: Text("Are you sure to import remote configuration \(newValue.name)? You will connect to \(newValue.host) to download the configuration."),
|
||||
primaryButton: .default(Text("Import")) {
|
||||
#if os(iOS)
|
||||
#if os(iOS) || os(tvOS)
|
||||
importRemoteProfilePresented = true
|
||||
#elseif os(macOS)
|
||||
openWindow(id: NewProfileView.windowID, value: importRemoteProfileRequest!)
|
||||
|
||||
@@ -34,12 +34,15 @@ public struct ServiceLogView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
#if !os(tvOS)
|
||||
.toolbar {
|
||||
Button("Export") {
|
||||
fileExporterPresented = true
|
||||
}
|
||||
.disabled(content.isEmpty)
|
||||
}
|
||||
#endif
|
||||
#if !os(tvOS)
|
||||
.fileExporter(
|
||||
isPresented: $fileExporterPresented,
|
||||
document: LogDocument(content),
|
||||
@@ -47,10 +50,14 @@ public struct ServiceLogView: View {
|
||||
defaultFilename: "service-log.txt",
|
||||
onCompletion: { _ in }
|
||||
)
|
||||
#endif
|
||||
.navigationTitle("Service Log")
|
||||
#if os(iOS)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
#endif
|
||||
#if os(tvOS)
|
||||
.focusable()
|
||||
#endif
|
||||
}
|
||||
|
||||
private func loadContent() {
|
||||
@@ -65,6 +72,7 @@ public struct ServiceLogView: View {
|
||||
isLoading = false
|
||||
}
|
||||
|
||||
#if !os(tvOS)
|
||||
private struct LogDocument: FileDocument {
|
||||
static var readableContentTypes = [UTType.text]
|
||||
|
||||
@@ -86,4 +94,5 @@ public struct ServiceLogView: View {
|
||||
FileWrapper(regularFileWithContents: Data(content.utf8))
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ public struct SettingView: View {
|
||||
Section("Core") {
|
||||
FormTextItem("Version", version)
|
||||
FormTextItem("Data Size", dataSize)
|
||||
#if os(iOS)
|
||||
#if os(iOS) || os(tvOS)
|
||||
NavigationLink(destination: ServiceLogView()) {
|
||||
Text("View Service Log")
|
||||
}
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array>
|
||||
<string>arm64</string>
|
||||
</array>
|
||||
<key>NSExtension</key>
|
||||
<dict>
|
||||
<key>NSExtensionPointIdentifier</key>
|
||||
|
||||
@@ -35,7 +35,7 @@ struct StartServiceIntent: AppIntent {
|
||||
if !profileChanged {
|
||||
return .result()
|
||||
}
|
||||
try LibboxNewStandaloneCommandClient(FilePath.sharedDirectory.relativePath)?.serviceReload()
|
||||
try LibboxNewStandaloneCommandClient()!.serviceReload()
|
||||
} else if extensionProfile.status.isConnected {
|
||||
extensionProfile.stop()
|
||||
try await Task.sleep(nanoseconds: UInt64(100 * Double(NSEC_PER_MSEC)))
|
||||
@@ -62,7 +62,7 @@ struct RestartServiceIntent: AppIntent {
|
||||
return .result()
|
||||
}
|
||||
if extensionProfile.status == .connected {
|
||||
try LibboxNewStandaloneCommandClient(FilePath.sharedDirectory.relativePath)?.serviceReload()
|
||||
try LibboxNewStandaloneCommandClient()!.serviceReload()
|
||||
} else if extensionProfile.status.isConnected {
|
||||
extensionProfile.stop()
|
||||
try await Task.sleep(nanoseconds: UInt64(100 * Double(NSEC_PER_MSEC)))
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import Foundation
|
||||
import GRDB
|
||||
import Network
|
||||
|
||||
public class Profile: Record, Identifiable, ObservableObject {
|
||||
public var id: Int64?
|
||||
@@ -15,16 +16,15 @@ public class Profile: Record, Identifiable, ObservableObject {
|
||||
@Published public var autoUpdate: Bool
|
||||
public var lastUpdated: Date?
|
||||
|
||||
public init(id: Int64? = nil, name: String, order: UInt32 = 0, type: ProfileType, path: String, remoteURL: String? = nil, lastUpdated: Date? = nil) {
|
||||
public init(id: Int64? = nil, name: String, order: UInt32 = 0, type: ProfileType, path: String, remoteURL: String? = nil, autoUpdate: Bool = false, lastUpdated: Date? = nil) {
|
||||
self.id = id
|
||||
self.name = name
|
||||
self.order = order
|
||||
self.type = type
|
||||
self.path = path
|
||||
self.remoteURL = remoteURL
|
||||
self.autoUpdate = autoUpdate
|
||||
self.lastUpdated = lastUpdated
|
||||
|
||||
autoUpdate = false
|
||||
super.init()
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
import Foundation
|
||||
import Libbox
|
||||
import Network
|
||||
|
||||
public class NWSocket {
|
||||
private let connection: NWConnection
|
||||
|
||||
public init(_ connection: NWConnection) {
|
||||
self.connection = connection
|
||||
}
|
||||
|
||||
public func read() throws -> Data {
|
||||
let semaphore = DispatchSemaphore(value: 0)
|
||||
var result: Result<Data, Error>!
|
||||
connection.receive(minimumIncompleteLength: 2, maximumLength: 2) { content, _, _, error in
|
||||
if let error {
|
||||
result = .failure(error)
|
||||
} else {
|
||||
result = .success(content!)
|
||||
}
|
||||
semaphore.signal()
|
||||
}
|
||||
semaphore.wait()
|
||||
let lengthChunk = try result.get()
|
||||
let length = Int(LibboxDecodeLengthChunk(lengthChunk))
|
||||
connection.receive(minimumIncompleteLength: length, maximumLength: length) { content, _, _, error in
|
||||
if let error {
|
||||
result = .failure(error)
|
||||
} else {
|
||||
result = .success(content!)
|
||||
}
|
||||
semaphore.signal()
|
||||
}
|
||||
semaphore.wait()
|
||||
return try result.get()
|
||||
}
|
||||
|
||||
public func write(_ data: Data?) throws {
|
||||
guard let data else {
|
||||
return
|
||||
}
|
||||
let semaphore = DispatchSemaphore(value: 0)
|
||||
var result: Error?
|
||||
connection.send(content: LibboxEncodeChunkedMessage(data), isComplete: false, completion: .contentProcessed { error in
|
||||
result = error
|
||||
semaphore.wait()
|
||||
})
|
||||
if let result {
|
||||
throw result
|
||||
}
|
||||
}
|
||||
|
||||
public func send(_ data: Data?) {
|
||||
guard let data else {
|
||||
return
|
||||
}
|
||||
connection.send(content: LibboxEncodeChunkedMessage(data), completion: .idempotent)
|
||||
}
|
||||
|
||||
public func cancel() {
|
||||
connection.cancel()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
import Foundation
|
||||
import Libbox
|
||||
import Network
|
||||
|
||||
public class ProfileServer {
|
||||
private var listener: NWListener
|
||||
|
||||
@available(iOS 16.0, macOS 13.0, *)
|
||||
public init() throws {
|
||||
listener = try NWListener(using: .applicationService)
|
||||
listener.service = NWListener.Service(applicationService: "sing-box:profile")
|
||||
listener.newConnectionHandler = { connection in
|
||||
connection.stateUpdateHandler = { state in
|
||||
if state == .ready {
|
||||
Task.detached {
|
||||
try await Task.sleep(nanoseconds: NSEC_PER_MSEC * 100)
|
||||
ProfileConnection(connection).process()
|
||||
}
|
||||
}
|
||||
}
|
||||
connection.start(queue: .global())
|
||||
}
|
||||
}
|
||||
|
||||
public func start() {
|
||||
listener.start(queue: .global())
|
||||
}
|
||||
|
||||
public func cancel() {
|
||||
listener.cancel()
|
||||
}
|
||||
|
||||
class ProfileConnection {
|
||||
private let connection: NWSocket
|
||||
|
||||
init(_ connection: NWConnection) {
|
||||
self.connection = NWSocket(connection)
|
||||
}
|
||||
|
||||
func process() {
|
||||
do {
|
||||
try writeProfilePreviewList()
|
||||
} catch {
|
||||
NSLog("profile server: write profile list: \(error.localizedDescription)")
|
||||
writeError(error.localizedDescription)
|
||||
return
|
||||
}
|
||||
do {
|
||||
while true {
|
||||
let message = try connection.read()
|
||||
try processMessage(message)
|
||||
}
|
||||
} catch {
|
||||
NSLog("profile server: process connection: \(error.localizedDescription)")
|
||||
writeError(error.localizedDescription)
|
||||
}
|
||||
}
|
||||
|
||||
private func processMessage(_ data: Data) throws {
|
||||
if data.count == 0 {
|
||||
return
|
||||
}
|
||||
let messageType = Int64(data[0])
|
||||
switch messageType {
|
||||
case LibboxMessageTypeProfileContentRequest:
|
||||
var error: NSError?
|
||||
let request = LibboxDecodeProfileContentRequest(data, &error)
|
||||
if let error {
|
||||
throw error
|
||||
}
|
||||
|
||||
let profile = try ProfileManager.get(request!.profileID)
|
||||
guard let profile else {
|
||||
throw NSError(domain: "profile not found", code: 0)
|
||||
}
|
||||
let content = LibboxProfileContent()
|
||||
content.name = profile.name
|
||||
switch profile.type {
|
||||
case .local:
|
||||
content.type = LibboxProfileTypeLocal
|
||||
case .icloud:
|
||||
content.type = LibboxProfileTypeiCloud
|
||||
case .remote:
|
||||
content.type = LibboxProfileTypeRemote
|
||||
}
|
||||
content.config = try profile.read()
|
||||
if profile.type != .local {
|
||||
content.remotePath = profile.remoteURL!
|
||||
}
|
||||
if profile.type == .remote {
|
||||
content.autoUpdate = profile.autoUpdate
|
||||
if let lastUpdated = profile.lastUpdated {
|
||||
content.lastUpdated = Int64(lastUpdated.timeIntervalSince1970)
|
||||
}
|
||||
}
|
||||
try connection.write(content.encode())
|
||||
default:
|
||||
throw NSError(domain: "unexpected message type \(messageType)", code: 0)
|
||||
}
|
||||
}
|
||||
|
||||
private func writeProfilePreviewList() throws {
|
||||
let profiles = try ProfileManager.list()
|
||||
let encoder = LibboxProfileEncoder()
|
||||
for profile in profiles {
|
||||
let preview = LibboxProfilePreview()
|
||||
preview.profileID = profile.mustID
|
||||
preview.name = profile.name
|
||||
switch profile.type {
|
||||
case .local:
|
||||
preview.type = LibboxProfileTypeLocal
|
||||
case .icloud:
|
||||
preview.type = LibboxProfileTypeiCloud
|
||||
case .remote:
|
||||
preview.type = LibboxProfileTypeRemote
|
||||
}
|
||||
encoder.append(preview)
|
||||
}
|
||||
try connection.write(encoder.encode())
|
||||
}
|
||||
|
||||
private func writeError(_ message: String) {
|
||||
let errorMessage = LibboxErrorMessage()
|
||||
errorMessage.message = message
|
||||
try? connection.write(errorMessage.encode())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,8 @@ import Libbox
|
||||
import NetworkExtension
|
||||
|
||||
open class ExtensionProvider: NEPacketTunnelProvider {
|
||||
public static let errorFile = FilePath.workingDirectory.appendingPathComponent("network_extension_error")
|
||||
|
||||
public var username: String? = nil
|
||||
private var commandServer: LibboxCommandServer!
|
||||
private var boxService: LibboxBoxService!
|
||||
@@ -10,6 +12,8 @@ open class ExtensionProvider: NEPacketTunnelProvider {
|
||||
override open func startTunnel(options _: [String: NSObject]?) async throws {
|
||||
NSLog("Here I am")
|
||||
|
||||
try? FileManager.default.removeItem(at: ExtensionProvider.errorFile)
|
||||
|
||||
do {
|
||||
try FileManager.default.createDirectory(at: FilePath.workingDirectory, withIntermediateDirectories: true)
|
||||
} catch {
|
||||
@@ -19,13 +23,17 @@ open class ExtensionProvider: NEPacketTunnelProvider {
|
||||
|
||||
if let username {
|
||||
var error: NSError?
|
||||
LibboxSetupWithUsername(FilePath.workingDirectory.relativePath, FilePath.cacheDirectory.relativePath, username, &error)
|
||||
LibboxSetupWithUsername(FilePath.sharedDirectory.relativePath, FilePath.workingDirectory.relativePath, FilePath.cacheDirectory.relativePath, username, &error)
|
||||
if let error {
|
||||
writeFatalError("(packet-tunnel) error: setup service: \(error.localizedDescription)")
|
||||
return
|
||||
}
|
||||
} else {
|
||||
LibboxSetup(FilePath.workingDirectory.relativePath, FilePath.cacheDirectory.relativePath)
|
||||
var isTVOS = false
|
||||
#if os(tvOS)
|
||||
isTVOS = true
|
||||
#endif
|
||||
LibboxSetup(FilePath.sharedDirectory.relativePath, FilePath.workingDirectory.relativePath, FilePath.cacheDirectory.relativePath, isTVOS)
|
||||
}
|
||||
|
||||
var error: NSError?
|
||||
@@ -36,7 +44,7 @@ open class ExtensionProvider: NEPacketTunnelProvider {
|
||||
|
||||
LibboxSetMemoryLimit(!SharedPreferences.disableMemoryLimit)
|
||||
|
||||
commandServer = LibboxNewCommandServer(FilePath.sharedDirectory.relativePath, serverInterface(self), Int32(SharedPreferences.maxLogLines))
|
||||
commandServer = LibboxNewCommandServer(serverInterface(self), Int32(SharedPreferences.maxLogLines))
|
||||
do {
|
||||
try commandServer.start()
|
||||
} catch {
|
||||
@@ -58,19 +66,13 @@ open class ExtensionProvider: NEPacketTunnelProvider {
|
||||
|
||||
private func writeError(_ message: String) {
|
||||
writeMessage(message)
|
||||
#if os(iOS)
|
||||
ServiceNotification.postServiceNotification(title: "Service Error", message: message)
|
||||
#else
|
||||
if Variant.useSystemExtension {
|
||||
NSLog(message)
|
||||
} else {
|
||||
displayMessage(message) { _ in
|
||||
}
|
||||
}
|
||||
#endif
|
||||
try? message.write(to: ExtensionProvider.errorFile, atomically: true, encoding: .utf8)
|
||||
}
|
||||
|
||||
public func writeFatalError(_ message: String) {
|
||||
#if DEBUG
|
||||
NSLog(message)
|
||||
#endif
|
||||
writeError(message)
|
||||
cancelTunnelWithError(NSError(domain: message, code: 0))
|
||||
}
|
||||
@@ -91,7 +93,7 @@ open class ExtensionProvider: NEPacketTunnelProvider {
|
||||
do {
|
||||
configContent = try profile.read()
|
||||
} catch {
|
||||
writeFatalError("(packet-tunnel) error: read config file: \(error.localizedDescription)")
|
||||
writeFatalError("(packet-tunnel) error: read config file \(profile.path): \(error.localizedDescription)")
|
||||
return
|
||||
}
|
||||
var error: NSError?
|
||||
|
||||
@@ -7,25 +7,42 @@ public enum FilePath {
|
||||
public extension FilePath {
|
||||
static let groupName = "group.\(packageName)"
|
||||
|
||||
static var sharedDirectory = defaultSharedDirectory
|
||||
private static let defaultSharedDirectory: URL! = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: FilePath.groupName)
|
||||
|
||||
private static var defaultSharedDirectory: URL {
|
||||
FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: FilePath.groupName)!
|
||||
}
|
||||
#if os(iOS)
|
||||
static let sharedDirectory = defaultSharedDirectory!
|
||||
#elseif os(tvOS)
|
||||
static let sharedDirectory = defaultSharedDirectory
|
||||
.appendingPathComponent("Library", isDirectory: true)
|
||||
.appendingPathComponent("Caches", isDirectory: true)
|
||||
#elseif os(macOS)
|
||||
static var sharedDirectory: URL! = defaultSharedDirectory
|
||||
#endif
|
||||
|
||||
#if os(iOS)
|
||||
static let cacheDirectory = sharedDirectory
|
||||
.appendingPathComponent("Library", isDirectory: true)
|
||||
.appendingPathComponent("Caches", isDirectory: true)
|
||||
#elseif os(tvOS)
|
||||
static let cacheDirectory = sharedDirectory
|
||||
#elseif os(macOS)
|
||||
static var cacheDirectory: URL {
|
||||
sharedDirectory
|
||||
.appendingPathComponent("Library", isDirectory: true)
|
||||
.appendingPathComponent("Caches", isDirectory: true)
|
||||
}
|
||||
#endif
|
||||
|
||||
#if os(macOS)
|
||||
static var workingDirectory: URL {
|
||||
cacheDirectory.appendingPathComponent("Working", isDirectory: true)
|
||||
}
|
||||
#else
|
||||
static let workingDirectory = cacheDirectory.appendingPathComponent("Working", isDirectory: true)
|
||||
|
||||
static var iCloudDirectory: URL {
|
||||
FileManager.default.url(forUbiquityContainerIdentifier: nil)!.appendingPathComponent("Documents", isDirectory: true)
|
||||
}
|
||||
#endif
|
||||
|
||||
static var iCloudDirectory: URL! = FileManager.default.url(forUbiquityContainerIdentifier: nil)!.appendingPathComponent("Documents", isDirectory: true)
|
||||
}
|
||||
|
||||
public extension URL {
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
import Foundation
|
||||
import UserNotifications
|
||||
|
||||
public enum ServiceNotification {
|
||||
private static let delegate = Delegate()
|
||||
|
||||
public static func register() {
|
||||
UNUserNotificationCenter.current().delegate = delegate
|
||||
UNUserNotificationCenter.current().requestAuthorization(options: [.alert]) {
|
||||
_, _ in
|
||||
}
|
||||
}
|
||||
|
||||
private static var listener: ((UNNotificationContent) -> Void)?
|
||||
|
||||
public static func setServiceNotificationListener(listener: @escaping (UNNotificationContent) -> Void) {
|
||||
ServiceNotification.listener = listener
|
||||
}
|
||||
|
||||
public static func removeServiceNotificationListener() {
|
||||
ServiceNotification.listener = nil
|
||||
}
|
||||
|
||||
public static func postServiceNotification(content: UNNotificationContent) {
|
||||
UNUserNotificationCenter.current().add(UNNotificationRequest(identifier: "service-notification", content: content, trigger: nil))
|
||||
}
|
||||
|
||||
public static func postServiceNotification(title: String, message: String) {
|
||||
let content = UNMutableNotificationContent()
|
||||
content.title = title
|
||||
content.body = message
|
||||
postServiceNotification(content: content)
|
||||
}
|
||||
|
||||
private class Delegate: NSObject, UNUserNotificationCenterDelegate {
|
||||
func userNotificationCenter(_: UNUserNotificationCenter, willPresent notification: UNNotification) async -> UNNotificationPresentationOptions {
|
||||
if let listener = ServiceNotification.listener {
|
||||
listener(notification.request.content)
|
||||
return []
|
||||
} else {
|
||||
return [.alert]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,5 +11,7 @@ public enum Variant {
|
||||
public static let applicationName = "SFI"
|
||||
#elseif os(macOS)
|
||||
public static let applicationName = "SFM"
|
||||
#elseif os(tvOS)
|
||||
public static let applicationName = "SFT"
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import Library
|
||||
open class ApplicationDelegate: NSObject, NSApplicationDelegate {
|
||||
public func applicationDidFinishLaunching(_: Notification) {
|
||||
NSLog("Here I stand")
|
||||
// ServiceNotification.register() // Not work
|
||||
LibboxSetup(FilePath.sharedDirectory.relativePath, FilePath.workingDirectory.relativePath, FilePath.cacheDirectory.relativePath, false)
|
||||
let event = NSAppleEventManager.shared().currentAppleEvent
|
||||
let launchedAsLogInItem =
|
||||
event?.eventID == kAEOpenApplication &&
|
||||
|
||||
@@ -43,14 +43,6 @@ public struct MainView: View {
|
||||
}
|
||||
#endif
|
||||
.alertBinding($alert)
|
||||
.onAppear {
|
||||
ServiceNotification.setServiceNotificationListener { notification in
|
||||
alert = Alert(title: Text(notification.title), message: Text(notification.body))
|
||||
}
|
||||
}
|
||||
.onDisappear {
|
||||
ServiceNotification.removeServiceNotificationListener()
|
||||
}
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigation) {
|
||||
StartStopButton()
|
||||
|
||||
@@ -191,7 +191,7 @@ public struct MenuView: View {
|
||||
NotificationCenter.default.post(name: ActiveDashboardView.NotificationUpdateSelectedProfile, object: nil)
|
||||
if profile.status.isConnected {
|
||||
do {
|
||||
try LibboxNewStandaloneCommandClient(FilePath.sharedDirectory.relativePath)?.serviceReload()
|
||||
try LibboxNewStandaloneCommandClient()?.serviceReload()
|
||||
} catch {
|
||||
alert = Alert(error)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# sing-box-for-apple
|
||||
|
||||
Experimental iOS/macOS client for sing-box, the universal proxy platform.
|
||||
Experimental iOS/macOS/tvOS client for sing-box, the universal proxy platform.
|
||||
|
||||
## Documentation
|
||||
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
import ApplicationLibrary
|
||||
import Foundation
|
||||
import Libbox
|
||||
import Library
|
||||
import Network
|
||||
import UIKit
|
||||
|
||||
class ApplicationDelegate: NSObject, UIApplicationDelegate {
|
||||
private var profileServer: ProfileServer?
|
||||
|
||||
func application(_: UIApplication, didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
|
||||
NSLog("Here I stand")
|
||||
ServiceNotification.register()
|
||||
LibboxSetup(FilePath.sharedDirectory.relativePath, FilePath.workingDirectory.relativePath, FilePath.cacheDirectory.relativePath, false)
|
||||
Task.detached {
|
||||
do {
|
||||
try await UIProfileUpdateTask.setup()
|
||||
@@ -18,9 +22,25 @@ class ApplicationDelegate: NSObject, UIApplicationDelegate {
|
||||
Task.detached {
|
||||
await self.requestNetworkPermission()
|
||||
}
|
||||
if #available(iOS 16.0, *) {
|
||||
Task.detached {
|
||||
await self.setupProfileServer()
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@available(iOS 16.0, *)
|
||||
private func setupProfileServer() {
|
||||
do {
|
||||
let profileServer = try ProfileServer()
|
||||
profileServer.start()
|
||||
self.profileServer = profileServer
|
||||
} catch {
|
||||
NSLog("setup profile server error: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
|
||||
private func requestNetworkPermission() {
|
||||
if UIDevice.current.userInterfaceIdiom != .phone {
|
||||
return
|
||||
|
||||
@@ -2,6 +2,16 @@
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>NSApplicationServices</key>
|
||||
<dict>
|
||||
<key>Advertises</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>NSApplicationServiceIdentifier</key>
|
||||
<string>sing-box:profile</string>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
<key>BGTaskSchedulerPermittedIdentifiers</key>
|
||||
<array>
|
||||
<string>io.nekohasekai.sfa.update_profiles</string>
|
||||
|
||||
@@ -10,11 +10,6 @@ struct MainView: View {
|
||||
@State private var extensionProfile: ExtensionProfile?
|
||||
@State private var profileLoading = true
|
||||
@State private var logClient: LogClient!
|
||||
|
||||
@State private var serviceNotificationTitle = ""
|
||||
@State private var serviceNotificationContent = ""
|
||||
@State private var serviceNotificationPresented = false
|
||||
|
||||
@State private var importRemoteProfile: LibboxImportRemoteProfile?
|
||||
|
||||
var body: some View {
|
||||
@@ -30,23 +25,6 @@ struct MainView: View {
|
||||
ContentView()
|
||||
}
|
||||
}
|
||||
.alert(isPresented: $serviceNotificationPresented, content: {
|
||||
Alert(
|
||||
title: Text(serviceNotificationTitle),
|
||||
message: Text(serviceNotificationContent),
|
||||
dismissButton: .default(Text("Ok"))
|
||||
)
|
||||
})
|
||||
.onAppear {
|
||||
ServiceNotification.setServiceNotificationListener { notification in
|
||||
serviceNotificationTitle = notification.title
|
||||
serviceNotificationContent = notification.body
|
||||
serviceNotificationPresented = true
|
||||
}
|
||||
}
|
||||
.onDisappear {
|
||||
ServiceNotification.removeServiceNotificationListener()
|
||||
}
|
||||
.onChange(of: scenePhase, perform: { newValue in
|
||||
if newValue == .active {
|
||||
Task.detached {
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import SwiftUI
|
||||
|
||||
@main
|
||||
struct Application: App {
|
||||
@UIApplicationDelegateAdaptor private var appDelegate: ApplicationDelegate
|
||||
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
MainView()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import ApplicationLibrary
|
||||
import Foundation
|
||||
import Libbox
|
||||
import Library
|
||||
import UIKit
|
||||
|
||||
class ApplicationDelegate: NSObject, UIApplicationDelegate {
|
||||
func application(_: UIApplication, didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
|
||||
NSLog("Here I stand")
|
||||
LibboxSetup(FilePath.sharedDirectory.relativePath, FilePath.workingDirectory.relativePath, FilePath.cacheDirectory.relativePath, true)
|
||||
Task.detached {
|
||||
do {
|
||||
try await UIProfileUpdateTask.setup()
|
||||
NSLog("setup background task success")
|
||||
} catch {
|
||||
NSLog("setup background task error: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "tv - App Icon - AppStore - Back -1280 x 768 pt.png",
|
||||
"idiom" : "tv"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"layers" : [
|
||||
{
|
||||
"filename" : "Front.imagestacklayer"
|
||||
},
|
||||
{
|
||||
"filename" : "Middle.imagestacklayer"
|
||||
},
|
||||
{
|
||||
"filename" : "Back.imagestacklayer"
|
||||
}
|
||||
]
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "tv"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "tv - App Icon - AppStore - Front -1280 x 768 pt.png",
|
||||
"idiom" : "tv"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 39 KiB |
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "tv- App Icon Small - Back - 400 x 240 pt.png",
|
||||
"idiom" : "tv",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "tv",
|
||||
"scale" : "2x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 7.7 KiB |
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"layers" : [
|
||||
{
|
||||
"filename" : "Front.imagestacklayer"
|
||||
},
|
||||
{
|
||||
"filename" : "Middle.imagestacklayer"
|
||||
},
|
||||
{
|
||||
"filename" : "Back.imagestacklayer"
|
||||
}
|
||||
]
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "tv",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "tv",
|
||||
"scale" : "2x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "tv- App Icon Small - Front - 400 x 240 pt.png",
|
||||
"idiom" : "tv",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "tv",
|
||||
"scale" : "2x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"assets" : [
|
||||
{
|
||||
"filename" : "App Icon - App Store.imagestack",
|
||||
"idiom" : "tv",
|
||||
"role" : "primary-app-icon",
|
||||
"size" : "1280x768"
|
||||
},
|
||||
{
|
||||
"filename" : "App Icon.imagestack",
|
||||
"idiom" : "tv",
|
||||
"role" : "primary-app-icon",
|
||||
"size" : "400x240"
|
||||
},
|
||||
{
|
||||
"filename" : "Top Shelf Image Wide.imageset",
|
||||
"idiom" : "tv",
|
||||
"role" : "top-shelf-image-wide",
|
||||
"size" : "2320x720"
|
||||
},
|
||||
{
|
||||
"filename" : "Top Shelf Image.imageset",
|
||||
"idiom" : "tv",
|
||||
"role" : "top-shelf-image",
|
||||
"size" : "1920x720"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "tv",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "tv",
|
||||
"scale" : "2x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
Vendored
+16
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "tv",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "tv",
|
||||
"scale" : "2x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import ApplicationLibrary
|
||||
import Library
|
||||
import SwiftUI
|
||||
|
||||
struct ContentView: View {
|
||||
@Environment(\.selection) private var selection
|
||||
@Environment(\.extensionProfile) private var extensionProfile
|
||||
|
||||
var body: some View {
|
||||
viewBuilder {
|
||||
if let profile = extensionProfile.wrappedValue {
|
||||
ContentView0().environmentObject(profile)
|
||||
} else {
|
||||
ContentView1()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ContentView0: View {
|
||||
@Environment(\.selection) private var selection
|
||||
@EnvironmentObject private var extensionProfile: ExtensionProfile
|
||||
|
||||
var body: some View {
|
||||
TabView(selection: selection) {
|
||||
ForEach(NavigationPage.allCases.filter { it in
|
||||
it.visible(extensionProfile)
|
||||
}, id: \.self) { page in
|
||||
NavigationView {
|
||||
page.contentView
|
||||
.focusSection()
|
||||
}
|
||||
.navigationViewStyle(.stack)
|
||||
.tag(page)
|
||||
.tabItem { page.label }
|
||||
}
|
||||
}.onChange(of: extensionProfile.status) { _ in
|
||||
if !selection.wrappedValue.visible(extensionProfile) {
|
||||
selection.wrappedValue = NavigationPage.dashboard
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ContentView1: View {
|
||||
@Environment(\.selection) private var selection
|
||||
|
||||
var body: some View {
|
||||
TabView(selection: selection) {
|
||||
ForEach(NavigationPage.allCases.filter { it in
|
||||
it.visible(nil)
|
||||
}, id: \.self) { page in
|
||||
NavigationView {
|
||||
page.contentView
|
||||
.focusSection()
|
||||
}
|
||||
.navigationViewStyle(.stack)
|
||||
.tag(page)
|
||||
.tabItem { page.label }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
<?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>UIRequiredDeviceCapabilities</key>
|
||||
<array>
|
||||
<string>arm64</string>
|
||||
</array>
|
||||
<key>NSApplicationServices</key>
|
||||
<dict>
|
||||
<key>Browses</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>NSApplicationServiceIdentifier</key>
|
||||
<string>sing-box:profile</string>
|
||||
<key>NSApplicationServiceUsageDescription</key>
|
||||
<string>Import sing-box profile from other devices</string>
|
||||
<key>NSApplicationServicePlatformSupport</key>
|
||||
<array>
|
||||
<string>iOS</string>
|
||||
<string>iPadOS</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
<key>BGTaskSchedulerPermittedIdentifiers</key>
|
||||
<array>
|
||||
<string>io.nekohasekai.sfa.update_profiles</string>
|
||||
</array>
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>CFBundleURLIconFile</key>
|
||||
<string>AppIcon.icns</string>
|
||||
<key>CFBundleURLName</key>
|
||||
<string>sing-box</string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>sing-box</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
<false/>
|
||||
<key>UIBackgroundModes</key>
|
||||
<array>
|
||||
<string>fetch</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,83 @@
|
||||
import ApplicationLibrary
|
||||
import Libbox
|
||||
import Library
|
||||
import SwiftUI
|
||||
|
||||
struct MainView: View {
|
||||
@Environment(\.scenePhase) var scenePhase
|
||||
|
||||
@State private var selection = NavigationPage.dashboard
|
||||
@State private var extensionProfile: ExtensionProfile?
|
||||
@State private var profileLoading = true
|
||||
@State private var logClient: LogClient!
|
||||
@State private var importRemoteProfile: LibboxImportRemoteProfile?
|
||||
|
||||
var body: some View {
|
||||
viewBuilder {
|
||||
if profileLoading {
|
||||
ProgressView().onAppear {
|
||||
Task.detached {
|
||||
logClient = LogClient(SharedPreferences.maxLogLines)
|
||||
await loadProfile()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ContentView()
|
||||
}
|
||||
}
|
||||
.onChange(of: scenePhase, perform: { newValue in
|
||||
if newValue == .active {
|
||||
Task.detached {
|
||||
await loadProfile()
|
||||
}
|
||||
}
|
||||
})
|
||||
.environment(\.selection, $selection)
|
||||
.environment(\.extensionProfile, $extensionProfile)
|
||||
.environment(\.logClient, $logClient)
|
||||
.environment(\.importRemoteProfile, $importRemoteProfile)
|
||||
.onOpenURL(perform: openURL)
|
||||
}
|
||||
|
||||
private func openURL(url: URL) {
|
||||
if url.host == "import-remote-profile" {
|
||||
var error: NSError?
|
||||
importRemoteProfile = LibboxParseRemoteProfileImportLink(url.absoluteString, &error)
|
||||
if error != nil {
|
||||
return
|
||||
}
|
||||
if selection != .profiles {
|
||||
selection = .profiles
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func loadProfile() async {
|
||||
defer {
|
||||
profileLoading = false
|
||||
}
|
||||
if ApplicationLibrary.inPreview {
|
||||
return
|
||||
}
|
||||
if let newProfile = try? await ExtensionProfile.load() {
|
||||
if extensionProfile == nil || extensionProfile?.status == .invalid {
|
||||
newProfile.register()
|
||||
extensionProfile = newProfile
|
||||
}
|
||||
} else {
|
||||
extensionProfile = nil
|
||||
}
|
||||
}
|
||||
|
||||
private func connectLog() {
|
||||
guard let profile = extensionProfile else {
|
||||
return
|
||||
}
|
||||
guard let logClient else {
|
||||
return
|
||||
}
|
||||
if profile.status.isConnected, !logClient.isConnected {
|
||||
logClient.reconnect()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?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.developer.networking.networkextension</key>
|
||||
<array>
|
||||
<string>packet-tunnel-provider</string>
|
||||
</array>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>group.io.nekohasekai.sfa</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -48,6 +48,12 @@
|
||||
3A4EAD362A4FEB9C005435B3 /* ProfileUpdateTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A55F9592A4D1554003C4EF4 /* ProfileUpdateTask.swift */; };
|
||||
3A4EAD372A4FEC20005435B3 /* ApplicationLibrary.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3A4EAD102A4FEAE6005435B3 /* ApplicationLibrary.framework */; };
|
||||
3A4EAD3C2A4FECCE005435B3 /* NEVPNStatus+isConnected.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A4EAD3B2A4FECCE005435B3 /* NEVPNStatus+isConnected.swift */; };
|
||||
3A4FB1572A73467F007012B9 /* Library.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3AEC211D2A459B4700A63465 /* Library.framework */; };
|
||||
3A4FB1582A73467F007012B9 /* Library.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3AEC211D2A459B4700A63465 /* Library.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
3A4FB15C2A73468C007012B9 /* ApplicationLibrary.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3A4EAD102A4FEAE6005435B3 /* ApplicationLibrary.framework */; };
|
||||
3A4FB1602A7346A1007012B9 /* Extension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 3A096F862A4ED3DE00D4A2ED /* Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
3A4FB1682A7358C9007012B9 /* ApplicationDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A4FB1672A7358C9007012B9 /* ApplicationDelegate.swift */; };
|
||||
3A4FB16A2A735AC9007012B9 /* MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A4FB1692A735AC9007012B9 /* MainView.swift */; };
|
||||
3A57DF372A4D5D2600690BC5 /* Profile+Date.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A57DF362A4D5D2600690BC5 /* Profile+Date.swift */; };
|
||||
3A57DF422A4D927A00690BC5 /* Profile+Hashable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A57DF412A4D927A00690BC5 /* Profile+Hashable.swift */; };
|
||||
3A5F26C82A503D4A00C27EDF /* Library.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3AEC211D2A459B4700A63465 /* Library.framework */; };
|
||||
@@ -71,11 +77,16 @@
|
||||
3A9759202A4EB69C00E4404B /* Library.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3AEC211D2A459B4700A63465 /* Library.framework */; };
|
||||
3A9759212A4EB69C00E4404B /* Library.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3AEC211D2A459B4700A63465 /* Library.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
3AB1220B2A70FD500087CD55 /* Alert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AB1220A2A70FD500087CD55 /* Alert.swift */; };
|
||||
3AC03B992A72BF3300B7946F /* Application.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AC03B982A72BF3300B7946F /* Application.swift */; };
|
||||
3AC03B9B2A72BF3300B7946F /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AC03B9A2A72BF3300B7946F /* ContentView.swift */; };
|
||||
3AC03B9D2A72BF3500B7946F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3AC03B9C2A72BF3500B7946F /* Assets.xcassets */; };
|
||||
3AC194492A50013F00BD8CB9 /* IntentsExtension.appex in Embed ExtensionKit Extensions */ = {isa = PBXBuildFile; fileRef = 3A77016D2A4E6B34008F031F /* IntentsExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
3AC1944F2A50247300BD8CB9 /* ApplicationDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AC1944E2A50247300BD8CB9 /* ApplicationDelegate.swift */; };
|
||||
3AC194502A502DFE00BD8CB9 /* ServiceNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AC1944C2A50206C00BD8CB9 /* ServiceNotification.swift */; };
|
||||
3AC5EC082A6417470077AF34 /* DeviceCensorship.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AC5EC072A6417470077AF34 /* DeviceCensorship.swift */; };
|
||||
3AC8CF9B2A736C750002AF3C /* ImportProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AC8CF9A2A736C750002AF3C /* ImportProfileView.swift */; };
|
||||
3AD0953D2A70EB310052764E /* Profile+Share.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AD0953C2A70EB310052764E /* Profile+Share.swift */; };
|
||||
3ADBB4252A7389640041D44F /* ProfileServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ADBB4242A7389640041D44F /* ProfileServer.swift */; };
|
||||
3ADBB42A2A73A7060041D44F /* NWSocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ADBB4292A73A7060041D44F /* NWSocket.swift */; };
|
||||
3AE4D0B22A6E2B6A009FEA9E /* ExtensionPlatformInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AF342A62A4AA0FF002B34AC /* ExtensionPlatformInterface.swift */; };
|
||||
3AE4D0B32A6E2B94009FEA9E /* Extension+RunBlocking.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AF342A82A4AA155002B34AC /* Extension+RunBlocking.swift */; };
|
||||
3AE4D0B42A6E2BA3009FEA9E /* Extension+Iterator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AF342AA2A4AA173002B34AC /* Extension+Iterator.swift */; };
|
||||
@@ -154,6 +165,27 @@
|
||||
remoteGlobalIDString = 3A4EAD0F2A4FEAE6005435B3;
|
||||
remoteInfo = ApplicationLibrary;
|
||||
};
|
||||
3A4FB1592A73467F007012B9 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 3AEC20BD2A45991900A63465 /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 3AEC211C2A459B4700A63465;
|
||||
remoteInfo = Library;
|
||||
};
|
||||
3A4FB15E2A73468C007012B9 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 3AEC20BD2A45991900A63465 /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 3A4EAD0F2A4FEAE6005435B3;
|
||||
remoteInfo = ApplicationLibrary;
|
||||
};
|
||||
3A4FB1612A7346A1007012B9 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 3AEC20BD2A45991900A63465 /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 3A096F852A4ED3DE00D4A2ED;
|
||||
remoteInfo = Extension;
|
||||
};
|
||||
3A76504A2A4F07F6003945C5 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 3AEC20BD2A45991900A63465 /* Project object */;
|
||||
@@ -270,6 +302,28 @@
|
||||
name = "Embed Foundation Extensions";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
3A4FB15B2A73467F007012B9 /* Embed Frameworks */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 10;
|
||||
files = (
|
||||
3A4FB1582A73467F007012B9 /* Library.framework in Embed Frameworks */,
|
||||
);
|
||||
name = "Embed Frameworks";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
3A4FB1632A7346A1007012B9 /* Embed Foundation Extensions */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 13;
|
||||
files = (
|
||||
3A4FB1602A7346A1007012B9 /* Extension.appex in Embed Foundation Extensions */,
|
||||
);
|
||||
name = "Embed Foundation Extensions";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
3A5F26CA2A503D4B00C27EDF /* Embed Frameworks */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
@@ -373,6 +427,10 @@
|
||||
3A4EAD102A4FEAE6005435B3 /* ApplicationLibrary.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ApplicationLibrary.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
3A4EAD202A4FEB3C005435B3 /* ApplicationLibrary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationLibrary.swift; sourceTree = "<group>"; };
|
||||
3A4EAD3B2A4FECCE005435B3 /* NEVPNStatus+isConnected.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NEVPNStatus+isConnected.swift"; sourceTree = "<group>"; };
|
||||
3A4FB1642A73568E007012B9 /* SFT.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = SFT.entitlements; sourceTree = "<group>"; };
|
||||
3A4FB1652A73574B007012B9 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
3A4FB1672A7358C9007012B9 /* ApplicationDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationDelegate.swift; sourceTree = "<group>"; };
|
||||
3A4FB1692A735AC9007012B9 /* MainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainView.swift; sourceTree = "<group>"; };
|
||||
3A55F9572A4D137E003C4EF4 /* UIProfileUpdateTask.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIProfileUpdateTask.swift; sourceTree = "<group>"; };
|
||||
3A55F9592A4D1554003C4EF4 /* ProfileUpdateTask.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileUpdateTask.swift; sourceTree = "<group>"; };
|
||||
3A57DF362A4D5D2600690BC5 /* Profile+Date.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Profile+Date.swift"; sourceTree = "<group>"; };
|
||||
@@ -397,11 +455,17 @@
|
||||
3AAB5E7A2A4C1446009757F1 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
3AB1220A2A70FD500087CD55 /* Alert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Alert.swift; sourceTree = "<group>"; };
|
||||
3ABA46D22A6A32A100D8366B /* Messages.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Messages.framework; path = Library/Frameworks/Messages.framework; sourceTree = DEVELOPER_DIR; };
|
||||
3AC1944C2A50206C00BD8CB9 /* ServiceNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServiceNotification.swift; sourceTree = "<group>"; };
|
||||
3AC03B962A72BF3300B7946F /* sing-box.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "sing-box.app"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
3AC03B982A72BF3300B7946F /* Application.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Application.swift; sourceTree = "<group>"; };
|
||||
3AC03B9A2A72BF3300B7946F /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
|
||||
3AC03B9C2A72BF3500B7946F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
3AC1944E2A50247300BD8CB9 /* ApplicationDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationDelegate.swift; sourceTree = "<group>"; };
|
||||
3AC194512A50303300BD8CB9 /* ApplicationDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationDelegate.swift; sourceTree = "<group>"; };
|
||||
3AC5EC072A6417470077AF34 /* DeviceCensorship.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceCensorship.swift; sourceTree = "<group>"; };
|
||||
3AC8CF9A2A736C750002AF3C /* ImportProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImportProfileView.swift; sourceTree = "<group>"; };
|
||||
3AD0953C2A70EB310052764E /* Profile+Share.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Profile+Share.swift"; sourceTree = "<group>"; };
|
||||
3ADBB4242A7389640041D44F /* ProfileServer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileServer.swift; sourceTree = "<group>"; };
|
||||
3ADBB4292A73A7060041D44F /* NWSocket.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NWSocket.swift; sourceTree = "<group>"; };
|
||||
3ADF8DF12A4AF59900900CC8 /* ActiveDashboardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActiveDashboardView.swift; sourceTree = "<group>"; };
|
||||
3ADF8DF32A4AF9B500900CC8 /* DashboardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashboardView.swift; sourceTree = "<group>"; };
|
||||
3ADF8DF62A4AFB2C00900CC8 /* ProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileView.swift; sourceTree = "<group>"; };
|
||||
@@ -481,6 +545,15 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
3AC03B932A72BF3300B7946F /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
3A4FB1572A73467F007012B9 /* Library.framework in Frameworks */,
|
||||
3A4FB15C2A73468C007012B9 /* ApplicationLibrary.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
3AEC20F02A459AB400A63465 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
@@ -619,6 +692,29 @@
|
||||
path = Setting;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
3AC03B972A72BF3300B7946F /* SFT */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
3A4FB1642A73568E007012B9 /* SFT.entitlements */,
|
||||
3AC03B982A72BF3300B7946F /* Application.swift */,
|
||||
3AC03B9A2A72BF3300B7946F /* ContentView.swift */,
|
||||
3AC03B9C2A72BF3500B7946F /* Assets.xcassets */,
|
||||
3A4FB1652A73574B007012B9 /* Info.plist */,
|
||||
3A4FB1672A7358C9007012B9 /* ApplicationDelegate.swift */,
|
||||
3A4FB1692A735AC9007012B9 /* MainView.swift */,
|
||||
);
|
||||
path = SFT;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
3ADBB4262A739DD00041D44F /* Discovery */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
3ADBB4242A7389640041D44F /* ProfileServer.swift */,
|
||||
3ADBB4292A73A7060041D44F /* NWSocket.swift */,
|
||||
);
|
||||
path = Discovery;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
3ADF8DF52A4AFA8E00900CC8 /* Profile */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -627,6 +723,7 @@
|
||||
3ADF8DFC2A4B096000900CC8 /* EditProfileWindowView.swift */,
|
||||
3ADF8E002A4B0F6300900CC8 /* EditProfileView.swift */,
|
||||
3AAB5E752A4BFB0B009757F1 /* EditProfileContentView.swift */,
|
||||
3AC8CF9A2A736C750002AF3C /* ImportProfileView.swift */,
|
||||
);
|
||||
path = Profile;
|
||||
sourceTree = "<group>";
|
||||
@@ -638,6 +735,7 @@
|
||||
3AEC20F42A459AB400A63465 /* SFI */,
|
||||
3AEC210A2A459B1900A63465 /* SFM */,
|
||||
3AEECC052A6DF9CA006A0E0C /* SFM.System */,
|
||||
3AC03B972A72BF3300B7946F /* SFT */,
|
||||
3AEC211E2A459B4700A63465 /* Library */,
|
||||
3A4EAD112A4FEAE6005435B3 /* ApplicationLibrary */,
|
||||
3AEECC302A6DFDAD006A0E0C /* MacLibrary */,
|
||||
@@ -661,6 +759,7 @@
|
||||
3AEECBF12A6DF40A006A0E0C /* io.nekohasekai.sfa.system.systemextension */,
|
||||
3AEECC042A6DF9CA006A0E0C /* SFM.app */,
|
||||
3AEECC2F2A6DFDAD006A0E0C /* MacLibrary.framework */,
|
||||
3AC03B962A72BF3300B7946F /* sing-box.app */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
@@ -703,6 +802,7 @@
|
||||
3AEC211E2A459B4700A63465 /* Library */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
3ADBB4262A739DD00041D44F /* Discovery */,
|
||||
3AEC21462A45A9CE00A63465 /* Shared */,
|
||||
3AEC21432A45A92B00A63465 /* Network */,
|
||||
3AEC213A2A459FD200A63465 /* Database */,
|
||||
@@ -746,7 +846,6 @@
|
||||
3AEC21462A45A9CE00A63465 /* Shared */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
3AC1944C2A50206C00BD8CB9 /* ServiceNotification.swift */,
|
||||
3AEC21472A45A9DE00A63465 /* Bundle+Version.swift */,
|
||||
3AEC214B2A45AA8E00A63465 /* FilePath.swift */,
|
||||
3A2223552A6E1BDE00C50B23 /* Variant.swift */,
|
||||
@@ -914,6 +1013,28 @@
|
||||
productReference = 3A77016D2A4E6B34008F031F /* IntentsExtension.appex */;
|
||||
productType = "com.apple.product-type.extensionkit-extension";
|
||||
};
|
||||
3AC03B952A72BF3300B7946F /* SFT */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 3AC03BA32A72BF3500B7946F /* Build configuration list for PBXNativeTarget "SFT" */;
|
||||
buildPhases = (
|
||||
3AC03B922A72BF3300B7946F /* Sources */,
|
||||
3AC03B932A72BF3300B7946F /* Frameworks */,
|
||||
3AC03B942A72BF3300B7946F /* Resources */,
|
||||
3A4FB15B2A73467F007012B9 /* Embed Frameworks */,
|
||||
3A4FB1632A7346A1007012B9 /* Embed Foundation Extensions */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
3A4FB15A2A73467F007012B9 /* PBXTargetDependency */,
|
||||
3A4FB15F2A73468C007012B9 /* PBXTargetDependency */,
|
||||
3A4FB1622A7346A1007012B9 /* PBXTargetDependency */,
|
||||
);
|
||||
name = SFT;
|
||||
productName = SFT;
|
||||
productReference = 3AC03B962A72BF3300B7946F /* sing-box.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
3AEC20F22A459AB400A63465 /* SFI */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 3AEC20FE2A459AB500A63465 /* Build configuration list for PBXNativeTarget "SFI" */;
|
||||
@@ -1076,6 +1197,9 @@
|
||||
3A77016C2A4E6B34008F031F = {
|
||||
CreatedOnToolsVersion = 15.0;
|
||||
};
|
||||
3AC03B952A72BF3300B7946F = {
|
||||
CreatedOnToolsVersion = 14.3.1;
|
||||
};
|
||||
3AEC20F22A459AB400A63465 = {
|
||||
CreatedOnToolsVersion = 15.0;
|
||||
};
|
||||
@@ -1121,6 +1245,7 @@
|
||||
3AEC20F22A459AB400A63465 /* SFI */,
|
||||
3AEC21082A459B1900A63465 /* SFM */,
|
||||
3AEECC032A6DF9CA006A0E0C /* SFM.System */,
|
||||
3AC03B952A72BF3300B7946F /* SFT */,
|
||||
3AEC211C2A459B4700A63465 /* Library */,
|
||||
3A4EAD0F2A4FEAE6005435B3 /* ApplicationLibrary */,
|
||||
3AEECC2E2A6DFDAD006A0E0C /* MacLibrary */,
|
||||
@@ -1139,6 +1264,14 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
3AC03B942A72BF3300B7946F /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
3AC03B9D2A72BF3500B7946F /* Assets.xcassets in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
3AEC20F12A459AB400A63465 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
@@ -1210,6 +1343,7 @@
|
||||
3A4EAD2D2A4FEB77005435B3 /* ProfileView.swift in Sources */,
|
||||
3A4EAD352A4FEB9C005435B3 /* UIProfileUpdateTask.swift in Sources */,
|
||||
3A4EAD222A4FEB54005435B3 /* NavigationPage.swift in Sources */,
|
||||
3AC8CF9B2A736C750002AF3C /* ImportProfileView.swift in Sources */,
|
||||
3A4EAD292A4FEB6D005435B3 /* Formtem.swift in Sources */,
|
||||
3A4EAD302A4FEB77005435B3 /* NewProfileView.swift in Sources */,
|
||||
3A4EAD242A4FEB65005435B3 /* InstallProfileButton.swift in Sources */,
|
||||
@@ -1247,6 +1381,17 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
3AC03B922A72BF3300B7946F /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
3A4FB1682A7358C9007012B9 /* ApplicationDelegate.swift in Sources */,
|
||||
3A4FB16A2A735AC9007012B9 /* MainView.swift in Sources */,
|
||||
3AC03B9B2A72BF3300B7946F /* ContentView.swift in Sources */,
|
||||
3AC03B992A72BF3300B7946F /* Application.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
3AEC20EF2A459AB400A63465 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
@@ -1284,11 +1429,12 @@
|
||||
3AEC21452A45A93800A63465 /* HTTPClient.swift in Sources */,
|
||||
3AEC213C2A459FDF00A63465 /* Databse.swift in Sources */,
|
||||
3A4EAD3C2A4FECCE005435B3 /* NEVPNStatus+isConnected.swift in Sources */,
|
||||
3ADBB4252A7389640041D44F /* ProfileServer.swift in Sources */,
|
||||
3AEC21422A45A8FF00A63465 /* Profile+Update.swift in Sources */,
|
||||
3A22235A2A6E212A00C50B23 /* SystemExtension.swift in Sources */,
|
||||
3AE4D0B42A6E2BA3009FEA9E /* Extension+Iterator.swift in Sources */,
|
||||
3AEC214C2A45AA8E00A63465 /* FilePath.swift in Sources */,
|
||||
3AC194502A502DFE00BD8CB9 /* ServiceNotification.swift in Sources */,
|
||||
3ADBB42A2A73A7060041D44F /* NWSocket.swift in Sources */,
|
||||
3AE4D0B22A6E2B6A009FEA9E /* ExtensionPlatformInterface.swift in Sources */,
|
||||
3AEC21402A45A28F00A63465 /* ProfileManager.swift in Sources */,
|
||||
3A9144D92A46AE370036E9AD /* ShadredPreferences+Database.swift in Sources */,
|
||||
@@ -1355,6 +1501,21 @@
|
||||
target = 3A4EAD0F2A4FEAE6005435B3 /* ApplicationLibrary */;
|
||||
targetProxy = 3A4EAD392A4FEC20005435B3 /* PBXContainerItemProxy */;
|
||||
};
|
||||
3A4FB15A2A73467F007012B9 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 3AEC211C2A459B4700A63465 /* Library */;
|
||||
targetProxy = 3A4FB1592A73467F007012B9 /* PBXContainerItemProxy */;
|
||||
};
|
||||
3A4FB15F2A73468C007012B9 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 3A4EAD0F2A4FEAE6005435B3 /* ApplicationLibrary */;
|
||||
targetProxy = 3A4FB15E2A73468C007012B9 /* PBXContainerItemProxy */;
|
||||
};
|
||||
3A4FB1622A7346A1007012B9 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 3A096F852A4ED3DE00D4A2ED /* Extension */;
|
||||
targetProxy = 3A4FB1612A7346A1007012B9 /* PBXContainerItemProxy */;
|
||||
};
|
||||
3A76504B2A4F07F6003945C5 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 3AEC211C2A459B4700A63465 /* Library */;
|
||||
@@ -1446,20 +1607,19 @@
|
||||
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
||||
MARKETING_VERSION = 1.0;
|
||||
OTHER_CODE_SIGN_FLAGS = "";
|
||||
OTHER_LDFLAGS = "-ld64";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfa.extension;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SDKROOT = macosx;
|
||||
SKIP_INSTALL = YES;
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx";
|
||||
SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator macosx";
|
||||
SUPPORTS_MACCATALYST = NO;
|
||||
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
|
||||
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
TARGETED_DEVICE_FAMILY = "1,2,3";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
@@ -1486,19 +1646,18 @@
|
||||
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
||||
MARKETING_VERSION = 1.0;
|
||||
OTHER_CODE_SIGN_FLAGS = "";
|
||||
OTHER_LDFLAGS = "-ld64";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfa.extension;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SDKROOT = macosx;
|
||||
SKIP_INSTALL = YES;
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx";
|
||||
SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator macosx";
|
||||
SUPPORTS_MACCATALYST = NO;
|
||||
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
|
||||
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
TARGETED_DEVICE_FAMILY = "1,2,3";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
@@ -1534,11 +1693,14 @@
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SDKROOT = iphoneos;
|
||||
SKIP_INSTALL = YES;
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx";
|
||||
SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator macosx";
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
TARGETED_DEVICE_FAMILY = "1,2,3";
|
||||
TVOS_DEPLOYMENT_TARGET = 17.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
@@ -1574,10 +1736,13 @@
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SDKROOT = iphoneos;
|
||||
SKIP_INSTALL = YES;
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx";
|
||||
SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator macosx";
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
TARGETED_DEVICE_FAMILY = "1,2,3";
|
||||
TVOS_DEPLOYMENT_TARGET = 17.0;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
@@ -1604,7 +1769,6 @@
|
||||
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
||||
MARKETING_VERSION = 1.0;
|
||||
OTHER_CODE_SIGN_FLAGS = "";
|
||||
OTHER_LDFLAGS = "-ld64";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfa.intents;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
@@ -1644,7 +1808,6 @@
|
||||
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
||||
MARKETING_VERSION = 1.0;
|
||||
OTHER_CODE_SIGN_FLAGS = "";
|
||||
OTHER_LDFLAGS = "-ld64";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfa.intents;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
@@ -1662,6 +1825,75 @@
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
3AC03BA12A72BF3500B7946F /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image";
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CODE_SIGN_ENTITLEMENTS = SFT/SFT.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = Z56Z6NYZN2;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = SFT/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = "sing-box";
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
|
||||
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
||||
INFOPLIST_KEY_UIUserInterfaceStyle = Automatic;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.3.5;
|
||||
OTHER_LDFLAGS = "-ld_classic";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfa;
|
||||
PRODUCT_NAME = "sing-box";
|
||||
SDKROOT = appletvos;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = 3;
|
||||
TVOS_DEPLOYMENT_TARGET = 17.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
3AC03BA22A72BF3500B7946F /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image";
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CODE_SIGN_ENTITLEMENTS = SFT/SFT.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = Z56Z6NYZN2;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = SFT/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = "sing-box";
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
|
||||
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
||||
INFOPLIST_KEY_UIUserInterfaceStyle = Automatic;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.3.5;
|
||||
OTHER_LDFLAGS = "-ld_classic";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfa;
|
||||
PRODUCT_NAME = "sing-box";
|
||||
SDKROOT = appletvos;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = 3;
|
||||
TVOS_DEPLOYMENT_TARGET = 17.0;
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
3AEC20CB2A45991900A63465 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
@@ -1820,7 +2052,6 @@
|
||||
);
|
||||
MARKETING_VERSION = 1.3.5;
|
||||
OTHER_CODE_SIGN_FLAGS = "--deep";
|
||||
OTHER_LDFLAGS = "-ld64";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfa;
|
||||
PRODUCT_NAME = "sing-box";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
@@ -1861,7 +2092,6 @@
|
||||
);
|
||||
MARKETING_VERSION = 1.3.5;
|
||||
OTHER_CODE_SIGN_FLAGS = "--deep";
|
||||
OTHER_LDFLAGS = "-ld64";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfa;
|
||||
PRODUCT_NAME = "sing-box";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
@@ -1955,6 +2185,7 @@
|
||||
APPLICATION_EXTENSION_API_ONLY = YES;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=appletvos*]" = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
@@ -1963,6 +2194,7 @@
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEFINES_MODULE = YES;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
"DEVELOPMENT_TEAM[sdk=appletvos*]" = Z56Z6NYZN2;
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = Z56Z6NYZN2;
|
||||
"DEVELOPMENT_TEAM[sdk=macosx*]" = Z56Z6NYZN2;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
@@ -1984,20 +2216,20 @@
|
||||
MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++";
|
||||
MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20";
|
||||
OTHER_CODE_SIGN_FLAGS = "--deep";
|
||||
OTHER_LDFLAGS = "-ld64";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfa.library;
|
||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SDKROOT = macosx;
|
||||
SKIP_INSTALL = YES;
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx";
|
||||
SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator macosx";
|
||||
SUPPORTS_MACCATALYST = NO;
|
||||
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES;
|
||||
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = YES;
|
||||
SWIFT_EMIT_LOC_STRINGS = NO;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
TARGETED_DEVICE_FAMILY = "1,2,3";
|
||||
TVOS_DEPLOYMENT_TARGET = 17.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
@@ -2007,6 +2239,7 @@
|
||||
APPLICATION_EXTENSION_API_ONLY = YES;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=appletvos*]" = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
@@ -2015,6 +2248,7 @@
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEFINES_MODULE = YES;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
"DEVELOPMENT_TEAM[sdk=appletvos*]" = Z56Z6NYZN2;
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = Z56Z6NYZN2;
|
||||
"DEVELOPMENT_TEAM[sdk=macosx*]" = Z56Z6NYZN2;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
@@ -2036,19 +2270,19 @@
|
||||
MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++";
|
||||
MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20";
|
||||
OTHER_CODE_SIGN_FLAGS = "--deep";
|
||||
OTHER_LDFLAGS = "-ld64";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfa.library;
|
||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SDKROOT = macosx;
|
||||
SKIP_INSTALL = YES;
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx";
|
||||
SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator macosx";
|
||||
SUPPORTS_MACCATALYST = NO;
|
||||
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES;
|
||||
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = YES;
|
||||
SWIFT_EMIT_LOC_STRINGS = NO;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2,7";
|
||||
TARGETED_DEVICE_FAMILY = "1,2,3";
|
||||
TVOS_DEPLOYMENT_TARGET = 17.0;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
@@ -2309,6 +2543,15 @@
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
3AC03BA32A72BF3500B7946F /* Build configuration list for PBXNativeTarget "SFT" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
3AC03BA12A72BF3500B7946F /* Debug */,
|
||||
3AC03BA22A72BF3500B7946F /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
3AEC20C02A45991900A63465 /* Build configuration list for PBXProject "sing-box" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
|
||||
+17
-12
@@ -69,7 +69,7 @@
|
||||
<key>MacLibrary.xcscheme_^#shared#^_</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>2</integer>
|
||||
<integer>6</integer>
|
||||
</dict>
|
||||
<key>MessageExtension.xcscheme_^#shared#^_</key>
|
||||
<dict>
|
||||
@@ -81,14 +81,14 @@
|
||||
<key>isShown</key>
|
||||
<false/>
|
||||
<key>orderHint</key>
|
||||
<integer>11</integer>
|
||||
<integer>12</integer>
|
||||
</dict>
|
||||
<key>MyPlayground (Playground) 2.xcscheme</key>
|
||||
<dict>
|
||||
<key>isShown</key>
|
||||
<false/>
|
||||
<key>orderHint</key>
|
||||
<integer>12</integer>
|
||||
<integer>13</integer>
|
||||
</dict>
|
||||
<key>MyPlayground (Playground) 3.xcscheme</key>
|
||||
<dict>
|
||||
@@ -116,7 +116,7 @@
|
||||
<key>isShown</key>
|
||||
<false/>
|
||||
<key>orderHint</key>
|
||||
<integer>10</integer>
|
||||
<integer>11</integer>
|
||||
</dict>
|
||||
<key>SFA.xcscheme_^#shared#^_</key>
|
||||
<dict>
|
||||
@@ -131,17 +131,22 @@
|
||||
<key>SFM.System.xcscheme_^#shared#^_</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>4</integer>
|
||||
<integer>3</integer>
|
||||
</dict>
|
||||
<key>SFM.xcscheme</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>1</integer>
|
||||
</dict>
|
||||
<key>SFT.xcscheme_^#shared#^_</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>4</integer>
|
||||
</dict>
|
||||
<key>SystemExtension.xcscheme_^#shared#^_</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>3</integer>
|
||||
<integer>2</integer>
|
||||
</dict>
|
||||
<key>Test.xcscheme_^#shared#^_</key>
|
||||
<dict>
|
||||
@@ -153,14 +158,14 @@
|
||||
<key>isShown</key>
|
||||
<false/>
|
||||
<key>orderHint</key>
|
||||
<integer>8</integer>
|
||||
<integer>15</integer>
|
||||
</dict>
|
||||
<key>Tour (Playground) 2.xcscheme</key>
|
||||
<dict>
|
||||
<key>isShown</key>
|
||||
<false/>
|
||||
<key>orderHint</key>
|
||||
<integer>9</integer>
|
||||
<integer>16</integer>
|
||||
</dict>
|
||||
<key>Tour (Playground) 3.xcscheme</key>
|
||||
<dict>
|
||||
@@ -188,21 +193,21 @@
|
||||
<key>isShown</key>
|
||||
<false/>
|
||||
<key>orderHint</key>
|
||||
<integer>7</integer>
|
||||
<integer>14</integer>
|
||||
</dict>
|
||||
<key>TransactionObserver (Playground) 1.xcscheme</key>
|
||||
<dict>
|
||||
<key>isShown</key>
|
||||
<false/>
|
||||
<key>orderHint</key>
|
||||
<integer>15</integer>
|
||||
<integer>9</integer>
|
||||
</dict>
|
||||
<key>TransactionObserver (Playground) 2.xcscheme</key>
|
||||
<dict>
|
||||
<key>isShown</key>
|
||||
<false/>
|
||||
<key>orderHint</key>
|
||||
<integer>16</integer>
|
||||
<integer>10</integer>
|
||||
</dict>
|
||||
<key>TransactionObserver (Playground) 3.xcscheme</key>
|
||||
<dict>
|
||||
@@ -230,7 +235,7 @@
|
||||
<key>isShown</key>
|
||||
<false/>
|
||||
<key>orderHint</key>
|
||||
<integer>14</integer>
|
||||
<integer>8</integer>
|
||||
</dict>
|
||||
<key>mactest.xcscheme_^#shared#^_</key>
|
||||
<dict>
|
||||
|
||||
Reference in New Issue
Block a user