diff --git a/ApplicationLibrary/Views/Dashboard/ActiveDashboardView.swift b/ApplicationLibrary/Views/Dashboard/ActiveDashboardView.swift index 736397c..e3897d5 100644 --- a/ApplicationLibrary/Views/Dashboard/ActiveDashboardView.swift +++ b/ApplicationLibrary/Views/Dashboard/ActiveDashboardView.swift @@ -12,6 +12,8 @@ public struct ActiveDashboardView: View { @State private var selectedProfileID: Int64! @State private var alert: Alert? @State private var selection = DashboardPage.overview + @State private var systemProxyAvailable = false + @State private var systemProxyEnabled = false public init() {} public var body: some View { @@ -37,16 +39,16 @@ public struct ActiveDashboardView: View { #endif TabView(selection: $selection) { ForEach(DashboardPage.allCases) { page in - page.contentView($profileList, $selectedProfileID) + page.contentView($profileList, $selectedProfileID, $systemProxyAvailable, $systemProxyEnabled) .tag(page) } } .tabViewStyle(.page(indexDisplayMode: .always)) } else { - OverviewView($profileList, $selectedProfileID) + OverviewView($profileList, $selectedProfileID, $systemProxyAvailable, $systemProxyEnabled) } #elseif os(macOS) - OverviewView($profileList, $selectedProfileID) + OverviewView($profileList, $selectedProfileID, $systemProxyAvailable, $systemProxyEnabled) #endif } #if os(iOS) || os(tvOS) @@ -65,6 +67,13 @@ public struct ActiveDashboardView: View { } } #endif + .onChangeCompat(of: profile.status) { newStatus in + if newStatus == .connected { + Task.detached { + await doReloadSystemProxy() + } + } + } .alertBinding($alert) } } @@ -100,4 +109,14 @@ public struct ActiveDashboardView: View { } } } + + private func doReloadSystemProxy() { + do { + let status = try LibboxNewStandaloneCommandClient()!.getSystemProxyStatus() + systemProxyAvailable = status.available + systemProxyEnabled = status.enabled + } catch { + alert = Alert(error) + } + } } diff --git a/ApplicationLibrary/Views/Dashboard/DashboardPage.swift b/ApplicationLibrary/Views/Dashboard/DashboardPage.swift index 2d500fb..3fac840 100644 --- a/ApplicationLibrary/Views/Dashboard/DashboardPage.swift +++ b/ApplicationLibrary/Views/Dashboard/DashboardPage.swift @@ -30,11 +30,11 @@ public extension DashboardPage { } } - func contentView(_ profileList: Binding<[Profile]>, _ selectedProfileID: Binding) -> some View { + func contentView(_ profileList: Binding<[Profile]>, _ selectedProfileID: Binding, _ systemProxyAvailable: Binding, _ systemProxyEnabled: Binding) -> some View { viewBuilder { switch self { case .overview: - OverviewView(profileList, selectedProfileID) + OverviewView(profileList, selectedProfileID, systemProxyAvailable, systemProxyEnabled) case .groups: GroupListView() } diff --git a/ApplicationLibrary/Views/Dashboard/OverviewView.swift b/ApplicationLibrary/Views/Dashboard/OverviewView.swift index 9afef4c..1729095 100644 --- a/ApplicationLibrary/Views/Dashboard/OverviewView.swift +++ b/ApplicationLibrary/Views/Dashboard/OverviewView.swift @@ -10,13 +10,17 @@ public struct OverviewView: View { @EnvironmentObject private var profile: ExtensionProfile @Binding private var profileList: [Profile] @Binding private var selectedProfileID: Int64! + @Binding private var systemProxyAvailable: Bool + @Binding private var systemProxyEnabled: Bool @State private var alert: Alert? @State private var reasserting = false @State private var observer: Any? - public init(_ profileList: Binding<[Profile]>, _ selectedProfileID: Binding) { + public init(_ profileList: Binding<[Profile]>, _ selectedProfileID: Binding, _ systemProxyAvailable: Binding, _ systemProxyEnabled: Binding) { _profileList = profileList _selectedProfileID = selectedProfileID + _systemProxyAvailable = systemProxyAvailable + _systemProxyEnabled = systemProxyEnabled } public var body: some View { @@ -31,6 +35,14 @@ public struct OverviewView: View { FormView { #if os(iOS) || os(tvOS) StartStopButton() + if profile.status.isConnectedStrict, systemProxyAvailable { + Toggle("HTTP Proxy", isOn: $systemProxyEnabled) + .onChangeCompat(of: systemProxyEnabled) { newValue in + Task.detached { + await setSystemProxyEnabled(newValue) + } + } + } Section("Profile") { Picker(selection: $selectedProfileID) { ForEach(profileList, id: \.id) { profile in @@ -40,6 +52,14 @@ public struct OverviewView: View { .pickerStyle(.inline) } #elseif os(macOS) + if profile.status.isConnectedStrict, systemProxyAvailable { + Toggle("HTTP Proxy", isOn: $systemProxyEnabled) + .onChangeCompat(of: systemProxyEnabled) { newValue in + Task.detached { + await setSystemProxyEnabled(newValue) + } + } + } Section("Profile") { ForEach(profileList, id: \.id) { profile in Picker(profile.name, selection: $selectedProfileID) { @@ -89,4 +109,13 @@ public struct OverviewView: View { } reasserting = false } + + private func setSystemProxyEnabled(_ isEnabled: Bool) { + do { + try LibboxNewStandaloneCommandClient()!.setSystemProxyEnabled(isEnabled) + SharedPreferences.systemProxyEnabled = isEnabled + } catch { + alert = Alert(error) + } + } } diff --git a/Library/Database/SharedPreferences.swift b/Library/Database/SharedPreferences.swift index 89c3dd5..5990dba 100644 --- a/Library/Database/SharedPreferences.swift +++ b/Library/Database/SharedPreferences.swift @@ -22,4 +22,6 @@ public enum SharedPreferences { #if os(iOS) @Preference("network_permission_requested", defaultValue: false) public static var networkPermissionRequested #endif + + @Preference("system_proxy_enabled", defaultValue: true) public static var systemProxyEnabled } diff --git a/Library/Network/ExtensionPlatformInterface.swift b/Library/Network/ExtensionPlatformInterface.swift index 0637f9f..45fe0b8 100644 --- a/Library/Network/ExtensionPlatformInterface.swift +++ b/Library/Network/ExtensionPlatformInterface.swift @@ -2,14 +2,12 @@ import Foundation import Libbox import NetworkExtension -public class ExtensionPlatformInterface: NSObject, LibboxPlatformInterfaceProtocol { - private let tunnel: NEPacketTunnelProvider - private let commandServer: LibboxCommandServer +public class ExtensionPlatformInterface: NSObject, LibboxPlatformInterfaceProtocol, LibboxCommandServerHandlerProtocol { + private let tunnel: ExtensionProvider private var networkSettings: NEPacketTunnelNetworkSettings? - init(_ tunnel: NEPacketTunnelProvider, _ logServer: LibboxCommandServer) { + init(_ tunnel: ExtensionProvider) { self.tunnel = tunnel - commandServer = logServer } public func openTun(_ options: LibboxTunOptionsProtocol?, ret0_: UnsafeMutablePointer?) throws { @@ -82,10 +80,12 @@ public class ExtensionPlatformInterface: NSObject, LibboxPlatformInterfaceProtoc if options.isHTTPProxyEnabled() { let proxySettings = NEProxySettings() let proxyServer = NEProxyServer(address: options.getHTTPProxyServer(), port: Int(options.getHTTPProxyServerPort())) - proxySettings.httpEnabled = true proxySettings.httpServer = proxyServer - proxySettings.httpsEnabled = true proxySettings.httpsServer = proxyServer + if SharedPreferences.systemProxyEnabled { + proxySettings.httpEnabled = true + proxySettings.httpsEnabled = true + } settings.proxySettings = proxySettings } @@ -133,7 +133,7 @@ public class ExtensionPlatformInterface: NSObject, LibboxPlatformInterfaceProtoc guard let message else { return } - commandServer.writeMessage(message) + tunnel.writeMessage(message) } public func usePlatformDefaultInterfaceMonitor() -> Bool { @@ -167,4 +167,45 @@ public class ExtensionPlatformInterface: NSObject, LibboxPlatformInterfaceProtoc } tunnel.reasserting = false } + + public func serviceReload() throws { + tunnel.reloadService() + } + + public func getSystemProxyStatus() -> LibboxSystemProxyStatus? { + let status = LibboxSystemProxyStatus() + guard let networkSettings else { + return status + } + guard let proxySettings = networkSettings.proxySettings else { + return status + } + if proxySettings.httpServer == nil { + return status + } + status.available = true + status.enabled = proxySettings.httpEnabled + return status + } + + public func setSystemProxyEnabled(_ isEnabled: Bool) throws { + guard let networkSettings else { + return + } + guard let proxySettings = networkSettings.proxySettings else { + return + } + if proxySettings.httpServer == nil { + return + } + if proxySettings.httpEnabled == isEnabled { + return + } + proxySettings.httpEnabled = isEnabled + proxySettings.httpsEnabled = isEnabled + networkSettings.proxySettings = proxySettings + try runBlocking { + try await self.tunnel.setTunnelNetworkSettings(networkSettings) + } + } } diff --git a/Library/Network/ExtensionProvider.swift b/Library/Network/ExtensionProvider.swift index 0e3c4c4..2320837 100644 --- a/Library/Network/ExtensionProvider.swift +++ b/Library/Network/ExtensionProvider.swift @@ -8,6 +8,9 @@ open class ExtensionProvider: NEPacketTunnelProvider { public var username: String? = nil private var commandServer: LibboxCommandServer! private var boxService: LibboxBoxService! + private var systemProxyAvailable = false + private var systemProxyEnabled = false + private var platformInterface: ExtensionPlatformInterface! override open func startTunnel(options _: [String: NSObject]?) async throws { NSLog("Here I am") @@ -44,7 +47,10 @@ open class ExtensionProvider: NEPacketTunnelProvider { LibboxSetMemoryLimit(!SharedPreferences.disableMemoryLimit) - commandServer = LibboxNewCommandServer(serverInterface(self), Int32(SharedPreferences.maxLogLines)) + if platformInterface == nil { + platformInterface = ExtensionPlatformInterface(self) + } + commandServer = LibboxNewCommandServer(platformInterface, Int32(SharedPreferences.maxLogLines)) do { try commandServer.start() } catch { @@ -56,7 +62,7 @@ open class ExtensionProvider: NEPacketTunnelProvider { startService() } - private func writeMessage(_ message: String) { + func writeMessage(_ message: String) { if let commandServer { commandServer.writeMessage(message) } else { @@ -64,7 +70,7 @@ open class ExtensionProvider: NEPacketTunnelProvider { } } - private func writeError(_ message: String) { + func writeError(_ message: String) { writeMessage(message) try? message.write(to: ExtensionProvider.errorFile, atomically: true, encoding: .utf8) } @@ -97,7 +103,7 @@ open class ExtensionProvider: NEPacketTunnelProvider { return } var error: NSError? - let service = LibboxNewService(configContent, ExtensionPlatformInterface(self, commandServer), &error) + let service = LibboxNewService(configContent, platformInterface, &error) if let error { writeError("(packet-tunnel) error: create service: \(error.localizedDescription)") return @@ -132,7 +138,7 @@ open class ExtensionProvider: NEPacketTunnelProvider { } } - private func reloadService() { + func reloadService() { writeMessage("(packet-tunnel) reloading service") reasserting = true defer { @@ -172,22 +178,4 @@ open class ExtensionProvider: NEPacketTunnelProvider { boxService.wake() } } - - private class serverInterface: NSObject, LibboxCommandServerHandlerProtocol { - unowned let tunnel: ExtensionProvider - - init(_ tunnel: ExtensionProvider) { - self.tunnel = tunnel - super.init() - } - - func serviceReload() throws { - tunnel.reloadService() - } - - func serviceStop() throws { - tunnel.stopService() - tunnel.writeMessage("(packet-tunnel) debug: service stopped") - } - } } diff --git a/sing-box.xcodeproj/project.pbxproj b/sing-box.xcodeproj/project.pbxproj index 788aaa3..6d4d35c 100644 --- a/sing-box.xcodeproj/project.pbxproj +++ b/sing-box.xcodeproj/project.pbxproj @@ -2489,7 +2489,7 @@ "@executable_path/../../../../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 13.0; - MARKETING_VERSION = 1.5.0-beta.2; + MARKETING_VERSION = 1.5.0-beta.3; PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfa.system; PRODUCT_NAME = "$(inherited)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -2525,7 +2525,7 @@ "@executable_path/../../../../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 13.0; - MARKETING_VERSION = 1.5.0-beta.2; + MARKETING_VERSION = 1.5.0-beta.3; PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfa.system; PRODUCT_NAME = "$(inherited)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -2565,7 +2565,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 13.0; - MARKETING_VERSION = 1.5.0-beta.2; + MARKETING_VERSION = 1.5.0-beta.3; PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfa.independent; PRODUCT_NAME = SFM; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -2604,7 +2604,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 13.0; - MARKETING_VERSION = 1.5.0-beta.2; + MARKETING_VERSION = 1.5.0-beta.3; PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfa.independent; PRODUCT_NAME = SFM; PROVISIONING_PROFILE_SPECIFIER = "";