diff --git a/ApplicationLibrary/Views/Dashboard/ActiveDashboardView.swift b/ApplicationLibrary/Views/Dashboard/ActiveDashboardView.swift index 3b688cb..0624df0 100644 --- a/ApplicationLibrary/Views/Dashboard/ActiveDashboardView.swift +++ b/ApplicationLibrary/Views/Dashboard/ActiveDashboardView.swift @@ -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 { diff --git a/Library/Network/ExtensionProfile.swift b/Library/Network/ExtensionProfile.swift index 009e5e9..6cbf2ed 100644 --- a/Library/Network/ExtensionProfile.swift +++ b/Library/Network/ExtensionProfile.swift @@ -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" diff --git a/Library/Network/HTTPClient.swift b/Library/Network/HTTPClient.swift index 528a04a..5355b6e 100644 --- a/Library/Network/HTTPClient.swift +++ b/Library/Network/HTTPClient.swift @@ -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 " diff --git a/Library/Network/SystemExtension.swift b/Library/Network/SystemExtension.swift index 37f1b33..e8a8a61 100644 --- a/Library/Network/SystemExtension.swift +++ b/Library/Network/SystemExtension.swift @@ -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() diff --git a/Library/Shared/FilePath.swift b/Library/Shared/FilePath.swift index 8561c10..412fd28 100644 --- a/Library/Shared/FilePath.swift +++ b/Library/Shared/FilePath.swift @@ -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 { diff --git a/Library/Shared/Variant.swift b/Library/Shared/Variant.swift index a724fda..e670a49 100644 --- a/Library/Shared/Variant.swift +++ b/Library/Shared/Variant.swift @@ -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 } diff --git a/MacLibrary/ApplicationDelegate.swift b/MacLibrary/ApplicationDelegate.swift index d2526ac..02e0f73 100644 --- a/MacLibrary/ApplicationDelegate.swift +++ b/MacLibrary/ApplicationDelegate.swift @@ -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 } diff --git a/MacLibrary/MainView.swift b/MacLibrary/MainView.swift index dd43605..c8ec743 100644 --- a/MacLibrary/MainView.swift +++ b/MacLibrary/MainView.swift @@ -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 + } + } } diff --git a/SFM.System/Application.swift b/SFM.System/Application.swift index ebf7b79..49bb577 100644 --- a/SFM.System/Application.swift +++ b/SFM.System/Application.swift @@ -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() diff --git a/SFM.System/IndependentApplicationDelegate.swift b/SFM.System/IndependentApplicationDelegate.swift new file mode 100644 index 0000000..5b1fef6 --- /dev/null +++ b/SFM.System/IndependentApplicationDelegate.swift @@ -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 + } + } + } + } + } +} diff --git a/sing-box.xcodeproj/project.pbxproj b/sing-box.xcodeproj/project.pbxproj index 2cd83ac..1bb443f 100644 --- a/sing-box.xcodeproj/project.pbxproj +++ b/sing-box.xcodeproj/project.pbxproj @@ -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 = ""; }; 3A2223572A6E1CC700C50B23 /* MacApplication.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MacApplication.swift; sourceTree = ""; }; 3A2223592A6E212A00C50B23 /* SystemExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SystemExtension.swift; sourceTree = ""; }; + 3A2EAEEC2A6F4CBB00D00DE3 /* IndependentApplicationDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IndependentApplicationDelegate.swift; sourceTree = ""; }; 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 = ""; }; @@ -768,6 +770,7 @@ 3AEECC0A2A6DF9CA006A0E0C /* Assets.xcassets */, 3AEECC502A6E0074006A0E0C /* SFM.entitlements */, 3A2223532A6E1B6700C50B23 /* Info.plist */, + 3A2EAEEC2A6F4CBB00D00DE3 /* IndependentApplicationDelegate.swift */, ); path = SFM.System; sourceTree = ""; @@ -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 = ""; diff --git a/sing-box.xcodeproj/xcuserdata/sekai.xcuserdatad/xcschemes/xcschememanagement.plist b/sing-box.xcodeproj/xcuserdata/sekai.xcuserdatad/xcschemes/xcschememanagement.plist index 41ed2a3..d0607a1 100644 --- a/sing-box.xcodeproj/xcuserdata/sekai.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/sing-box.xcodeproj/xcuserdata/sekai.xcuserdatad/xcschemes/xcschememanagement.plist @@ -7,7 +7,7 @@ ApplicationLibrary.xcscheme_^#shared#^_ orderHint - 1 + 3 Associations (Playground) 1.xcscheme @@ -69,7 +69,7 @@ MacLibrary.xcscheme_^#shared#^_ orderHint - 5 + 4 MessageExtension.xcscheme_^#shared#^_ @@ -131,17 +131,17 @@ SFM.System.xcscheme_^#shared#^_ orderHint - 2 + 5 SFM.xcscheme_^#shared#^_ orderHint - 4 + 2 SystemExtension.xcscheme_^#shared#^_ orderHint - 3 + 1 Test.xcscheme_^#shared#^_