Restart service when settings change

This commit is contained in:
世界
2025-12-07 10:28:51 +08:00
parent bd5e5f3a79
commit 39338ab133
10 changed files with 77 additions and 22 deletions
@@ -32,17 +32,7 @@ public final class OverviewViewModel: BaseViewModel {
try LibboxNewStandaloneCommandClient()!.setSystemProxyEnabled(enabled) try LibboxNewStandaloneCommandClient()!.setSystemProxyEnabled(enabled)
} else { } else {
await MainActor.run { reasserting = true } await MainActor.run { reasserting = true }
try await profile.stop() try await profile.restart()
var waitSeconds = 0
while await profile.status != .disconnected {
try await Task.sleep(nanoseconds: NSEC_PER_SEC)
waitSeconds += 1
if waitSeconds >= 5 {
throw NSError(domain: "Restart service timeout", code: 0)
}
}
try await profile.start()
await MainActor.run { reasserting = false } await MainActor.run { reasserting = false }
} }
} catch { } catch {
@@ -3,7 +3,9 @@ import Library
import SwiftUI import SwiftUI
public struct OnDemandRulesView: View { public struct OnDemandRulesView: View {
@EnvironmentObject private var environments: ExtensionEnvironments
@State private var isLoading = true @State private var isLoading = true
@State private var alert: AlertState?
@State private var alwaysOn = false @State private var alwaysOn = false
public init() {} public init() {}
@@ -23,11 +25,13 @@ public struct OnDemandRulesView: View {
This should not be an intended use of the API, so you cannot disable VPN in system settings. To stop the service manually, use the in-app interface or simply delete the VPN profile. This should not be an intended use of the API, so you cannot disable VPN in system settings. To stop the service manually, use the in-app interface or simply delete the VPN profile.
""", $alwaysOn) { newValue in """, $alwaysOn) { newValue in
await SharedPreferences.alwaysOn.set(newValue) await SharedPreferences.alwaysOn.set(newValue)
await restartService()
} }
FormButton { FormButton {
Task { Task {
await SharedPreferences.resetOnDemandRules() await SharedPreferences.resetOnDemandRules()
await restartService()
isLoading = true isLoading = true
} }
} label: { } label: {
@@ -38,11 +42,23 @@ public struct OnDemandRulesView: View {
} }
} }
.navigationTitle("On Demand Rules") .navigationTitle("On Demand Rules")
.alert($alert)
#if os(iOS) #if os(iOS)
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)
#endif #endif
} }
private func restartService() async {
guard let profile = environments.extensionProfile, profile.status.isConnected else {
return
}
do {
try await profile.restart()
} catch {
alert = AlertState(error: error)
}
}
private func loadSettings() async { private func loadSettings() async {
alwaysOn = await SharedPreferences.alwaysOn.get() alwaysOn = await SharedPreferences.alwaysOn.get()
isLoading = false isLoading = false
@@ -2,7 +2,9 @@ import Library
import SwiftUI import SwiftUI
struct PacketTunnelView: View { struct PacketTunnelView: View {
@EnvironmentObject private var environments: ExtensionEnvironments
@State private var isLoading = true @State private var isLoading = true
@State private var alert: AlertState?
@State private var ignoreMemoryLimit = false @State private var ignoreMemoryLimit = false
@@ -27,6 +29,7 @@ struct PacketTunnelView: View {
Do not enforce memory limits on sing-box. Will cause OOM on non-jailbroken iOS and tvOS devices. Do not enforce memory limits on sing-box. Will cause OOM on non-jailbroken iOS and tvOS devices.
""", $ignoreMemoryLimit) { newValue in """, $ignoreMemoryLimit) { newValue in
await SharedPreferences.ignoreMemoryLimit.set(newValue) await SharedPreferences.ignoreMemoryLimit.set(newValue)
await restartService()
} }
#if !os(tvOS) #if !os(tvOS)
@@ -38,6 +41,7 @@ struct PacketTunnelView: View {
[Apple Documentation](https://developer.apple.com/documentation/networkextension/nevpnprotocol/3131931-includeallnetworks) [Apple Documentation](https://developer.apple.com/documentation/networkextension/nevpnprotocol/3131931-includeallnetworks)
""", $includeAllNetworks) { newValue in """, $includeAllNetworks) { newValue in
await SharedPreferences.includeAllNetworks.set(newValue) await SharedPreferences.includeAllNetworks.set(newValue)
await restartService()
} }
FormToggle("excludeAPNs", """ FormToggle("excludeAPNs", """
@@ -46,14 +50,16 @@ struct PacketTunnelView: View {
[Apple Documentation](https://developer.apple.com/documentation/networkextension/nevpnprotocol/4140516-excludeapns) [Apple Documentation](https://developer.apple.com/documentation/networkextension/nevpnprotocol/4140516-excludeapns)
""", $excludeAPNs) { newValue in """, $excludeAPNs) { newValue in
await SharedPreferences.excludeAPNs.set(newValue) await SharedPreferences.excludeAPNs.set(newValue)
await restartService()
} }
FormToggle("excludeCellularServices", """ FormToggle("excludeCellularServices", """
If this property is true, the system excludes cellular services — such as Wi-Fi Calling, MMS, SMS, and Visual Voicemail — but only when the **includeAllNetworks** property is also true. This property doesnt impact services that use the cellular network only — such as VoLTE — which the system automatically excludes. If this property is true, the system excludes cellular services — such as Wi-Fi Calling, MMS, SMS, and Visual Voicemail — but only when the **includeAllNetworks** property is also true. This property doesn't impact services that use the cellular network only — such as VoLTE — which the system automatically excludes.
[Apple Documentation](https://developer.apple.com/documentation/networkextension/nevpnprotocol/4140517-excludecellularservices) [Apple Documentation](https://developer.apple.com/documentation/networkextension/nevpnprotocol/4140517-excludecellularservices)
""", $excludeCellularServices) { newValue in """, $excludeCellularServices) { newValue in
await SharedPreferences.excludeCellularServices.set(newValue) await SharedPreferences.excludeCellularServices.set(newValue)
await restartService()
} }
FormToggle("excludeLocalNetworks", """ FormToggle("excludeLocalNetworks", """
@@ -62,6 +68,7 @@ struct PacketTunnelView: View {
[Apple Documentation](https://developer.apple.com/documentation/networkextension/nevpnprotocol/3143658-excludelocalnetworks) [Apple Documentation](https://developer.apple.com/documentation/networkextension/nevpnprotocol/3143658-excludelocalnetworks)
""", $excludeLocalNetworks) { newValue in """, $excludeLocalNetworks) { newValue in
await SharedPreferences.excludeLocalNetworks.set(newValue) await SharedPreferences.excludeLocalNetworks.set(newValue)
await restartService()
} }
FormToggle("enforceRoutes", """ FormToggle("enforceRoutes", """
@@ -72,6 +79,7 @@ struct PacketTunnelView: View {
[Apple Documentation](https://developer.apple.com/documentation/networkextension/nevpnprotocol/3689459-enforceroutes) [Apple Documentation](https://developer.apple.com/documentation/networkextension/nevpnprotocol/3689459-enforceroutes)
""", $enforceRoutes) { newValue in """, $enforceRoutes) { newValue in
await SharedPreferences.enforceRoutes.set(newValue) await SharedPreferences.enforceRoutes.set(newValue)
await restartService()
} }
#endif #endif
@@ -79,6 +87,7 @@ struct PacketTunnelView: View {
FormButton { FormButton {
Task { Task {
await SharedPreferences.resetPacketTunnel() await SharedPreferences.resetPacketTunnel()
await restartService()
isLoading = true isLoading = true
} }
} label: { } label: {
@@ -89,11 +98,23 @@ struct PacketTunnelView: View {
} }
} }
.navigationTitle("Packet Tunnel") .navigationTitle("Packet Tunnel")
.alert($alert)
#if os(iOS) #if os(iOS)
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)
#endif #endif
} }
private func restartService() async {
guard let profile = environments.extensionProfile, profile.status.isConnected else {
return
}
do {
try await profile.restart()
} catch {
alert = AlertState(error: error)
}
}
private func loadSettings() async { private func loadSettings() async {
ignoreMemoryLimit = await SharedPreferences.ignoreMemoryLimit.get() ignoreMemoryLimit = await SharedPreferences.ignoreMemoryLimit.get()
#if !os(tvOS) #if !os(tvOS)
@@ -1,8 +1,11 @@
import Libbox
import Library import Library
import SwiftUI import SwiftUI
public struct ProfileOverrideView: View { public struct ProfileOverrideView: View {
@EnvironmentObject private var environments: ExtensionEnvironments
@State private var isLoading = true @State private var isLoading = true
@State private var alert: AlertState?
@State private var excludeDefaultRoute = false @State private var excludeDefaultRoute = false
@State private var autoRouteUseSubRangesByDefault = false @State private var autoRouteUseSubRangesByDefault = false
@State private var excludeAPNsRoute = false @State private var excludeAPNsRoute = false
@@ -20,6 +23,7 @@ public struct ProfileOverrideView: View {
FormView { FormView {
FormToggle("Hide VPN Icon", "Append `0.0.0.0/31` and `::/127` to `route_exclude_address` if not exists.", $excludeDefaultRoute) { newValue in FormToggle("Hide VPN Icon", "Append `0.0.0.0/31` and `::/127` to `route_exclude_address` if not exists.", $excludeDefaultRoute) { newValue in
await SharedPreferences.excludeDefaultRoute.set(newValue) await SharedPreferences.excludeDefaultRoute.set(newValue)
await reloadService()
} }
FormToggle("No Default Route", """ FormToggle("No Default Route", """
@@ -27,15 +31,18 @@ public struct ProfileOverrideView: View {
If `<route_address/route_exclude_address>` exists in the configuration, this item will not take effect on the corresponding network (commonly used to resolve HomeKit compatibility issues). If `<route_address/route_exclude_address>` exists in the configuration, this item will not take effect on the corresponding network (commonly used to resolve HomeKit compatibility issues).
""", $autoRouteUseSubRangesByDefault) { newValue in """, $autoRouteUseSubRangesByDefault) { newValue in
await SharedPreferences.autoRouteUseSubRangesByDefault.set(newValue) await SharedPreferences.autoRouteUseSubRangesByDefault.set(newValue)
await reloadService()
} }
FormToggle("Exclude APNs Route", "Append `push.apple.com` to `bypass_domain`, and `17.0.0.0/8` to `route_exclude_address`.", $excludeAPNsRoute) { newValue in FormToggle("Exclude APNs Route", "Append `push.apple.com` to `bypass_domain`, and `17.0.0.0/8` to `route_exclude_address`.", $excludeAPNsRoute) { newValue in
await SharedPreferences.excludeAPNsRoute.set(newValue) await SharedPreferences.excludeAPNsRoute.set(newValue)
await reloadService()
} }
FormButton { FormButton {
Task { Task {
await SharedPreferences.resetProfileOverride() await SharedPreferences.resetProfileOverride()
await reloadService()
isLoading = true isLoading = true
} }
} label: { } label: {
@@ -46,11 +53,23 @@ public struct ProfileOverrideView: View {
} }
} }
.navigationTitle("Profile Override") .navigationTitle("Profile Override")
.alert($alert)
#if os(iOS) #if os(iOS)
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)
#endif #endif
} }
private func reloadService() async {
guard let profile = environments.extensionProfile, profile.status.isConnected else {
return
}
do {
try LibboxNewStandaloneCommandClient()?.serviceReload()
} catch {
alert = AlertState(error: error)
}
}
private func loadSettings() async { private func loadSettings() async {
excludeDefaultRoute = await SharedPreferences.excludeDefaultRoute.get() excludeDefaultRoute = await SharedPreferences.excludeDefaultRoute.get()
autoRouteUseSubRangesByDefault = await SharedPreferences.autoRouteUseSubRangesByDefault.get() autoRouteUseSubRangesByDefault = await SharedPreferences.autoRouteUseSubRangesByDefault.get()
+2 -6
View File
@@ -38,9 +38,7 @@ struct StartServiceIntent: AppIntent {
} }
try LibboxNewStandaloneCommandClient()!.serviceReload() try LibboxNewStandaloneCommandClient()!.serviceReload()
} else if extensionProfile.status.isConnected { } else if extensionProfile.status.isConnected {
try await extensionProfile.stop() try await extensionProfile.restart()
try await Task.sleep(nanoseconds: UInt64(100 * Double(NSEC_PER_MSEC)))
try await extensionProfile.start()
} else { } else {
try await extensionProfile.start() try await extensionProfile.start()
} }
@@ -65,9 +63,7 @@ struct RestartServiceIntent: AppIntent {
if extensionProfile.status == .connected { if extensionProfile.status == .connected {
try LibboxNewStandaloneCommandClient()!.serviceReload() try LibboxNewStandaloneCommandClient()!.serviceReload()
} else if extensionProfile.status.isConnected { } else if extensionProfile.status.isConnected {
try await extensionProfile.stop() try await extensionProfile.restart()
try await Task.sleep(nanoseconds: UInt64(100 * Double(NSEC_PER_MSEC)))
try await extensionProfile.start()
} else { } else {
try await extensionProfile.start() try await extensionProfile.start()
} }
+13
View File
@@ -108,6 +108,19 @@ public class ExtensionProfile: ObservableObject {
manager.connection.stopVPNTunnel() manager.connection.stopVPNTunnel()
} }
public func restart() async throws {
try await stop()
var waitSeconds = 0
while await MainActor.run(body: { status }) != .disconnected {
try await Task.sleep(nanoseconds: NSEC_PER_SEC)
waitSeconds += 1
if waitSeconds >= 5 {
throw NSError(domain: "Restart service timeout", code: 0)
}
}
try await start()
}
public static func load() async throws -> ExtensionProfile? { public static func load() async throws -> ExtensionProfile? {
let managers = try await NETunnelProviderManager.loadAllFromPreferences() let managers = try await NETunnelProviderManager.loadAllFromPreferences()
if managers.isEmpty { if managers.isEmpty {
+1 -1
View File
@@ -923,7 +923,7 @@
"If this property is true, the system excludes Apple Push Notification services (APNs) traffic, but only when the **includeAllNetworks** property is also true.\n\n[Apple Documentation](https://developer.apple.com/documentation/networkextension/nevpnprotocol/4140516-excludeapns)" : { "If this property is true, the system excludes Apple Push Notification services (APNs) traffic, but only when the **includeAllNetworks** property is also true.\n\n[Apple Documentation](https://developer.apple.com/documentation/networkextension/nevpnprotocol/4140516-excludeapns)" : {
"shouldTranslate" : false "shouldTranslate" : false
}, },
"If this property is true, the system excludes cellular services — such as Wi-Fi Calling, MMS, SMS, and Visual Voicemail — but only when the **includeAllNetworks** property is also true. This property doesnt impact services that use the cellular network only — such as VoLTE — which the system automatically excludes.\n\n[Apple Documentation](https://developer.apple.com/documentation/networkextension/nevpnprotocol/4140517-excludecellularservices)" : { "If this property is true, the system excludes cellular services — such as Wi-Fi Calling, MMS, SMS, and Visual Voicemail — but only when the **includeAllNetworks** property is also true. This property doesn't impact services that use the cellular network only — such as VoLTE — which the system automatically excludes.\n\n[Apple Documentation](https://developer.apple.com/documentation/networkextension/nevpnprotocol/4140517-excludecellularservices)" : {
"shouldTranslate" : false "shouldTranslate" : false
}, },
"If this property is true, the system excludes network connections to hosts on the local network — such as AirPlay, AirDrop, and CarPlay — but only when the **includeAllNetworks** or **enforceRoutes** property is also true.\n\n[Apple Documentation](https://developer.apple.com/documentation/networkextension/nevpnprotocol/3143658-excludelocalnetworks)" : { "If this property is true, the system excludes network connections to hosts on the local network — such as AirPlay, AirDrop, and CarPlay — but only when the **includeAllNetworks** or **enforceRoutes** property is also true.\n\n[Apple Documentation](https://developer.apple.com/documentation/networkextension/nevpnprotocol/3143658-excludelocalnetworks)" : {