Finish independent macOS version

This commit is contained in:
世界
2023-07-25 14:04:13 +08:00
parent 12bf02070e
commit 486a685bb5
12 changed files with 147 additions and 98 deletions
@@ -78,6 +78,13 @@ public struct ActiveDashboardView: View {
}
}
}
.alert(isPresented: $errorPresented) {
Alert(
title: Text("Error"),
message: Text(errorMessage),
dismissButton: .default(Text("Ok"))
)
}
#if os(iOS)
.onChange(of: scenePhase, perform: { newValue in
if newValue == .active {
+1 -1
View File
@@ -67,7 +67,7 @@ public class ExtensionProfile: ObservableObject {
public static func install() async throws {
let manager = NETunnelProviderManager()
manager.localizedDescription = "utun interface"
manager.localizedDescription = Variant.applicationName
let tunnelProtocol = NETunnelProviderProtocol()
if Variant.useSystemExtension {
tunnelProtocol.providerBundleIdentifier = "\(FilePath.packageName).system"
+1 -1
View File
@@ -3,7 +3,7 @@ import Libbox
public class HTTPClient {
private static var userAgent: String {
var userAgent = FilePath.httpClientName
var userAgent = Variant.applicationName
userAgent += "/"
userAgent += Bundle.main.version
userAgent += " (Build "
+23 -13
View File
@@ -4,24 +4,32 @@
public class SystemExtension: NSObject, OSSystemExtensionRequestDelegate {
private let forceUpdate: Bool
private let inBackground: Bool
private let semaphore = DispatchSemaphore(value: 0)
private var result: OSSystemExtensionRequest.Result?
private var properties: [OSSystemExtensionProperties]?
private var error: Error?
private init(forceUpdate: Bool = false) {
private init(forceUpdate: Bool = false, inBackground: Bool = false) {
self.forceUpdate = forceUpdate
self.inBackground = inBackground
}
public func request(_: OSSystemExtensionRequest, actionForReplacingExtension existing: OSSystemExtensionProperties, withExtension ext: OSSystemExtensionProperties) -> OSSystemExtensionRequest.ReplacementAction {
if forceUpdate {
return .replace
}
if existing.isAwaitingUserApproval, !inBackground {
return .replace
}
if existing.bundleIdentifier == ext.bundleIdentifier,
existing.bundleVersion == ext.bundleVersion
existing.bundleVersion == ext.bundleVersion,
existing.bundleShortVersion == ext.bundleShortVersion
{
NSLog("Skip update system extension")
return .cancel
} else {
NSLog("Update system extension")
return .replace
}
}
@@ -69,24 +77,26 @@
public static func isInstalled() async -> Bool {
await (try? Task.detached {
do {
let propList = try SystemExtension().getProperties()
if propList.isEmpty {
return false
}
for extensionProp in propList {
if !extensionProp.isAwaitingUserApproval, !extensionProp.isUninstalling {
return true
for _ in 0 ..< 3 {
do {
let propList = try SystemExtension().getProperties()
if propList.isEmpty {
return false
}
for extensionProp in propList {
if !extensionProp.isAwaitingUserApproval, !extensionProp.isUninstalling {
return true
}
}
} catch {
try await Task.sleep(nanoseconds: NSEC_PER_SEC)
}
} catch {
NSLog(error.localizedDescription)
}
return false
}.result.get()) == true
}
public static func install(forceUpdate: Bool = false) async throws -> OSSystemExtensionRequest.Result? {
public static func install(forceUpdate: Bool = false, inBackground _: Bool = false) async throws -> OSSystemExtensionRequest.Result? {
try await Task.detached {
try SystemExtension(forceUpdate: forceUpdate).submitAndWait()
}.result.get()
-5
View File
@@ -2,11 +2,6 @@ import Foundation
public enum FilePath {
public static let packageName = "io.nekohasekai.sfa"
#if os(iOS)
public static let httpClientName = "SFI"
#elseif os(macOS)
public static let httpClientName = "SFM"
#endif
}
public extension FilePath {
+6
View File
@@ -6,4 +6,10 @@ public enum Variant {
#else
public static let useSystemExtension = false
#endif
#if os(iOS)
public static let applicationName = "SFI"
#elseif os(macOS)
public static let applicationName = "SFM"
#endif
}
+5 -18
View File
@@ -20,33 +20,20 @@ open class ApplicationDelegate: NSObject, NSApplicationDelegate {
}
Task.detached {
do {
if Variant.useSystemExtension {
if await SystemExtension.isInstalled() {
if let result = try await SystemExtension.install() {
if result == .willCompleteAfterReboot {
return
}
try ProfileUpdateTask.setup()
if launchedAsLogInItem {
if SharedPreferences.startedByUser {
if let profile = try await ExtensionProfile.load() {
try await profile.start()
}
}
}
try await self.postStart(launchedAsLogInItem)
} catch {
NSLog("application setup error: \(error.localizedDescription)")
}
}
}
private func postStart(_ launchedAsLogInItem: Bool) async throws {
try ProfileUpdateTask.setup()
if launchedAsLogInItem {
if SharedPreferences.startedByUser {
if let profile = try await ExtensionProfile.load() {
try await profile.start()
}
}
}
}
public func applicationShouldTerminateAfterLastWindowClosed(_: NSApplication) -> Bool {
!SharedPreferences.menuBarExtraInBackground
}
+63 -38
View File
@@ -10,9 +10,10 @@ public struct MainView: View {
@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 dialogTitle = ""
@State private var dialogContent = ""
@State private var dialogAction: (() -> Void)?
@State private var dialogPresented = false
public init() {}
public var body: some View {
@@ -20,6 +21,7 @@ public struct MainView: View {
VStack {
SidebarView()
}
.frame(minWidth: 150)
} detail: {
if profileLoading {
@@ -33,45 +35,55 @@ public struct MainView: View {
selection.contentView
}
}
.alert(isPresented: $serviceNotificationPresented, content: {
Alert(
title: Text(serviceNotificationTitle),
message: Text(serviceNotificationContent),
dismissButton: .default(Text("Ok"))
)
})
#if !DEBUG
.onAppear {
ServiceNotification.setServiceNotificationListener { notification in
serviceNotificationTitle = notification.title
serviceNotificationContent = notification.body
serviceNotificationPresented = true
}
}
.onDisappear {
ServiceNotification.removeServiceNotificationListener()
}
.toolbar {
ToolbarItem(placement: .navigation) {
StartStopButton()
}
}
.onChange(of: controlActiveState, perform: { newValue in
if newValue != .inactive {
Task {
await loadProfile()
connectLog()
if Variant.useSystemExtension {
Task.detached {
checkApplicationPath()
}
}
}
})
.onChange(of: selection, perform: { value in
if value == .logs {
connectLog()
#endif
.alert(isPresented: $dialogPresented, content: {
Alert(
title: Text(dialogTitle),
message: Text(dialogContent),
dismissButton: .default(Text("Ok"), action: dialogAction)
)
})
.onAppear {
ServiceNotification.setServiceNotificationListener { notification in
dialogTitle = notification.title
dialogContent = notification.body
dialogAction = nil
dialogPresented = true
}
}
})
.formStyle(.grouped)
.environment(\.selection, $selection)
.environment(\.extensionProfile, $extensionProfile)
.environment(\.logClient, $logClient)
.onDisappear {
ServiceNotification.removeServiceNotificationListener()
}
.toolbar {
ToolbarItem(placement: .navigation) {
StartStopButton()
}
}
.onChange(of: controlActiveState, perform: { newValue in
if newValue != .inactive {
Task {
await loadProfile()
connectLog()
}
}
})
.onChange(of: selection, perform: { value in
if value == .logs {
connectLog()
}
})
.formStyle(.grouped)
.environment(\.selection, $selection)
.environment(\.extensionProfile, $extensionProfile)
.environment(\.logClient, $logClient)
}
private func loadProfile() async {
@@ -99,4 +111,17 @@ public struct MainView: View {
logClient.reconnect()
}
}
private func checkApplicationPath() {
let directoryName = URL(filePath: Bundle.main.bundlePath).deletingLastPathComponent().pathComponents.last
if directoryName != "Applications" {
dialogTitle = "Wrong application location"
dialogContent = "This app needs to be placed under ~/Applications to work."
dialogAction = {
NSWorkspace.shared.selectFile(Bundle.main.bundlePath, inFileViewerRootedAtPath: "")
NSApp.terminate(nil)
}
dialogPresented = true
}
}
}
+1 -5
View File
@@ -4,11 +4,7 @@ import SwiftUI
@main
struct Application: App {
@NSApplicationDelegateAdaptor private var appDelegate: ApplicationDelegate
init() {
Variant.useSystemExtension = true
}
@NSApplicationDelegateAdaptor private var appDelegate: IndependentApplicationDelegate
var body: some Scene {
MacApplication()
@@ -0,0 +1,19 @@
import AppKit
import Foundation
import Library
import MacLibrary
class IndependentApplicationDelegate: ApplicationDelegate {
public func applicationWillFinishLaunching(_: Notification) {
Variant.useSystemExtension = true
Task.detached {
if await SystemExtension.isInstalled() {
if let result = try await SystemExtension.install() {
if result == .willCompleteAfterReboot {
return
}
}
}
}
}
}
+16 -12
View File
@@ -19,6 +19,7 @@
3A2223582A6E1CC700C50B23 /* MacApplication.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A2223572A6E1CC700C50B23 /* MacApplication.swift */; };
3A22235A2A6E212A00C50B23 /* SystemExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A2223592A6E212A00C50B23 /* SystemExtension.swift */; };
3A251C122A52D09700651082 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3AAB5E7A2A4C1446009757F1 /* Assets.xcassets */; };
3A2EAEED2A6F4CBB00D00DE3 /* IndependentApplicationDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A2EAEEC2A6F4CBB00D00DE3 /* IndependentApplicationDelegate.swift */; };
3A3AA7FC2A4EFDAE002F78AB /* Library.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3AEC211D2A459B4700A63465 /* Library.framework */; };
3A3AA7FF2A4EFDB3002F78AB /* Library.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3AEC211D2A459B4700A63465 /* Library.framework */; };
3A3DEBEB2A4FFE2D00373BF4 /* AppIntents.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3A3DEBE62A4FFA6000373BF4 /* AppIntents.framework */; };
@@ -357,6 +358,7 @@
3A2223552A6E1BDE00C50B23 /* Variant.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Variant.swift; sourceTree = "<group>"; };
3A2223572A6E1CC700C50B23 /* MacApplication.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MacApplication.swift; sourceTree = "<group>"; };
3A2223592A6E212A00C50B23 /* SystemExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SystemExtension.swift; sourceTree = "<group>"; };
3A2EAEEC2A6F4CBB00D00DE3 /* IndependentApplicationDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IndependentApplicationDelegate.swift; sourceTree = "<group>"; };
3A3DEBE12A4FFA1A00373BF4 /* ExtensionFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ExtensionFoundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS17.0.sdk/System/Library/Frameworks/ExtensionFoundation.framework; sourceTree = DEVELOPER_DIR; };
3A3DEBE62A4FFA6000373BF4 /* AppIntents.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppIntents.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS17.0.sdk/System/Library/Frameworks/AppIntents.framework; sourceTree = DEVELOPER_DIR; };
3A44BB662A4DBF7900E4C9F8 /* SFI.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = SFI.entitlements; sourceTree = "<group>"; };
@@ -768,6 +770,7 @@
3AEECC0A2A6DF9CA006A0E0C /* Assets.xcassets */,
3AEECC502A6E0074006A0E0C /* SFM.entitlements */,
3A2223532A6E1B6700C50B23 /* Info.plist */,
3A2EAEEC2A6F4CBB00D00DE3 /* IndependentApplicationDelegate.swift */,
);
path = SFM.System;
sourceTree = "<group>";
@@ -1279,6 +1282,7 @@
buildActionMask = 2147483647;
files = (
3AEECC072A6DF9CA006A0E0C /* Application.swift in Sources */,
3A2EAEED2A6F4CBB00D00DE3 /* IndependentApplicationDelegate.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1782,7 +1786,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.3.3;
MARKETING_VERSION = 1.3.4;
OTHER_CODE_SIGN_FLAGS = "--deep";
OTHER_LDFLAGS = "";
PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfa;
@@ -1823,7 +1827,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.3.3;
MARKETING_VERSION = 1.3.4;
OTHER_CODE_SIGN_FLAGS = "--deep";
OTHER_LDFLAGS = "";
PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfa;
@@ -1847,7 +1851,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 11;
CURRENT_PROJECT_VERSION = 12;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = Z56Z6NYZN2;
ENABLE_HARDENED_RUNTIME = YES;
@@ -1863,7 +1867,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = 1.3.3;
MARKETING_VERSION = 1.3.4;
OTHER_CODE_SIGN_FLAGS = "";
PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfa;
PRODUCT_NAME = "sing-box";
@@ -1885,7 +1889,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 11;
CURRENT_PROJECT_VERSION = 12;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = Z56Z6NYZN2;
ENABLE_HARDENED_RUNTIME = YES;
@@ -1901,7 +1905,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = 1.3.3;
MARKETING_VERSION = 1.3.4;
OTHER_CODE_SIGN_FLAGS = "";
PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfa;
PRODUCT_NAME = "sing-box";
@@ -2023,7 +2027,6 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Developer ID Application";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=macosx*]" = Z56Z6NYZN2;
ENABLE_HARDENED_RUNTIME = YES;
@@ -2039,7 +2042,7 @@
"@executable_path/../../../../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = 1.0;
MARKETING_VERSION = 1.3.4;
PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfa.system;
PRODUCT_NAME = "$(inherited)";
PROVISIONING_PROFILE_SPECIFIER = "";
@@ -2059,7 +2062,6 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Developer ID Application";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=macosx*]" = Z56Z6NYZN2;
ENABLE_HARDENED_RUNTIME = YES;
@@ -2075,7 +2077,7 @@
"@executable_path/../../../../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = 1.0;
MARKETING_VERSION = 1.3.4;
PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfa.system;
PRODUCT_NAME = "$(inherited)";
PROVISIONING_PROFILE_SPECIFIER = "";
@@ -2108,13 +2110,14 @@
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = SFM.System/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = "sing-box";
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
INFOPLIST_KEY_NSHumanReadableCopyright = "";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = 1.0;
MARKETING_VERSION = 1.3.4;
PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfa.independent;
PRODUCT_NAME = SFM;
PROVISIONING_PROFILE_SPECIFIER = "";
@@ -2146,13 +2149,14 @@
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = SFM.System/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = "sing-box";
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
INFOPLIST_KEY_NSHumanReadableCopyright = "";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = 1.0;
MARKETING_VERSION = 1.3.4;
PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfa.independent;
PRODUCT_NAME = SFM;
PROVISIONING_PROFILE_SPECIFIER = "";
@@ -7,7 +7,7 @@
<key>ApplicationLibrary.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>1</integer>
<integer>3</integer>
</dict>
<key>Associations (Playground) 1.xcscheme</key>
<dict>
@@ -69,7 +69,7 @@
<key>MacLibrary.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>5</integer>
<integer>4</integer>
</dict>
<key>MessageExtension.xcscheme_^#shared#^_</key>
<dict>
@@ -131,17 +131,17 @@
<key>SFM.System.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>2</integer>
<integer>5</integer>
</dict>
<key>SFM.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>4</integer>
<integer>2</integer>
</dict>
<key>SystemExtension.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>3</integer>
<integer>1</integer>
</dict>
<key>Test.xcscheme_^#shared#^_</key>
<dict>