diff --git a/ApplicationLibrary/Views/Dashboard/ActiveDashboardView.swift b/ApplicationLibrary/Views/Dashboard/ActiveDashboardView.swift index 0c73fef..84c218f 100644 --- a/ApplicationLibrary/Views/Dashboard/ActiveDashboardView.swift +++ b/ApplicationLibrary/Views/Dashboard/ActiveDashboardView.swift @@ -111,6 +111,7 @@ public struct ActiveDashboardView: View { } } + @available(iOS 16.0, *) @ViewBuilder private var content1: some View { TabView(selection: $selection) { diff --git a/Library/Network/CommandClient.swift b/Library/Network/CommandClient.swift index 484f6b2..c13942b 100644 --- a/Library/Network/CommandClient.swift +++ b/Library/Network/CommandClient.swift @@ -107,7 +107,12 @@ public class CommandClient: ObservableObject { case .connections: clientOptions.command = LibboxCommandConnections } - clientOptions.statusInterval = Int64(2 * NSEC_PER_SEC) + switch connectionType { + case .log: + clientOptions.statusInterval = Int64(500 * NSEC_PER_MSEC) + default: + clientOptions.statusInterval = Int64(2 * NSEC_PER_SEC) + } let client = LibboxNewCommandClient(clientHandler(self), clientOptions)! do { for i in 0 ..< 10 { @@ -152,21 +157,23 @@ public class CommandClient: ObservableObject { } } - func clearLog() { + func clearLogs() { DispatchQueue.main.async { [self] in commandClient.logList.removeAll() } } - func writeLog(_ message: String?) { - guard let message else { + func writeLogs(_ messageList: (any LibboxStringIteratorProtocol)?) { + guard let messageList else { return } DispatchQueue.main.async { [self] in - if commandClient.logList.count > commandClient.logMaxLines { - commandClient.logList.removeFirst() + if commandClient.logList.count >= commandClient.logMaxLines { + commandClient.logList.removeSubrange(0 ..< Int(messageList.len())) + } + while messageList.hasNext() { + commandClient.logList.append(messageList.next()) } - commandClient.logList.append(message) } } diff --git a/Library/Network/ExtensionProfile.swift b/Library/Network/ExtensionProfile.swift index c738111..5296da3 100644 --- a/Library/Network/ExtensionProfile.swift +++ b/Library/Network/ExtensionProfile.swift @@ -3,6 +3,8 @@ import Libbox import NetworkExtension public class ExtensionProfile: ObservableObject { + public static let controlKind = "io.nekohasekai.sfavt.widget.ServiceToggle" + private let manager: NEVPNManager private var connection: NEVPNConnection private var observer: Any? diff --git a/Library/Network/ExtensionProvider.swift b/Library/Network/ExtensionProvider.swift index 54199dd..47792da 100644 --- a/Library/Network/ExtensionProvider.swift +++ b/Library/Network/ExtensionProvider.swift @@ -1,6 +1,9 @@ import Foundation import Libbox import NetworkExtension +#if os(iOS) + import WidgetKit +#endif open class ExtensionProvider: NEPacketTunnelProvider { public var username: String? = nil @@ -48,13 +51,16 @@ open class ExtensionProvider: NEPacketTunnelProvider { } writeMessage("(packet-tunnel): Here I stand") await startService() + #if os(iOS) + if #available(iOS 18.0, *) { + ControlCenter.shared.reloadControls(ofKind: ExtensionProfile.controlKind) + } + #endif } func writeMessage(_ message: String) { if let commandServer { commandServer.writeMessage(message) - } else { - NSLog(message) } } @@ -153,6 +159,11 @@ open class ExtensionProvider: NEPacketTunnelProvider { await SharedPreferences.startedByUser.set(reason == .userInitiated) } #endif + #if os(iOS) + if #available(iOS 18.0, *) { + ControlCenter.shared.reloadControls(ofKind: ExtensionProfile.controlKind) + } + #endif } override open func handleAppMessage(_ messageData: Data) async -> Data? { diff --git a/Library/Network/NEVPNStatus+isConnected.swift b/Library/Network/NEVPNStatus+isConnected.swift index d5b626b..312a9c8 100644 --- a/Library/Network/NEVPNStatus+isConnected.swift +++ b/Library/Network/NEVPNStatus+isConnected.swift @@ -4,7 +4,16 @@ import NetworkExtension public extension NEVPNStatus { var isEnabled: Bool { switch self { - case .connected, .disconnected, .reasserting: + case .connecting, .connected, .disconnected, .reasserting: + return true + default: + return false + } + } + + var isStarted: Bool { + switch self { + case .connecting, .connected, .reasserting: return true default: return false diff --git a/WidgetExtension/Assets.xcassets/AppIcon.appiconset/Contents.json b/WidgetExtension/Assets.xcassets/AppIcon.appiconset/Contents.json index 13613e3..2305880 100644 --- a/WidgetExtension/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/WidgetExtension/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -4,6 +4,28 @@ "idiom" : "universal", "platform" : "ios", "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "tinted" + } + ], + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" } ], "info" : { diff --git a/WidgetExtension/WidgetExtensionBundle.swift b/WidgetExtension/ExtensionBundle.swift similarity index 51% rename from WidgetExtension/WidgetExtensionBundle.swift rename to WidgetExtension/ExtensionBundle.swift index fab679b..ff0c94b 100644 --- a/WidgetExtension/WidgetExtensionBundle.swift +++ b/WidgetExtension/ExtensionBundle.swift @@ -2,8 +2,8 @@ import SwiftUI import WidgetKit @main -struct WidgetExtensionBundle: WidgetBundle { +struct ExtensionBundle: WidgetBundle { var body: some Widget { - WidgetExtension() + ServiceToggleControl() } } diff --git a/WidgetExtension/Intents.swift b/WidgetExtension/Intents.swift deleted file mode 100644 index 6be7028..0000000 --- a/WidgetExtension/Intents.swift +++ /dev/null @@ -1,46 +0,0 @@ -import AppIntents -import Library -import WidgetKit - -struct ConfigurationIntent: WidgetConfigurationIntent { - static var title: LocalizedStringResource = "Configuration" - static var description = IntentDescription("Configuration sing-bix widget.") -} - -struct StartServiceIntent: AppIntent { - static var title: LocalizedStringResource = "Start sing-box" - - static var description = - IntentDescription("Start sing-box servie") - - func perform() async throws -> some IntentResult { - guard let extensionProfile = try await (ExtensionProfile.load()) else { - throw NSError(domain: "NetworkExtension not installed", code: 0) - } - if extensionProfile.status == .connected { - return .result() - } else { - try await extensionProfile.start() - } - return .result() - } -} - -struct StopServiceIntent: AppIntent { - static var title: LocalizedStringResource = "Stop sing-box" - - static var description = - IntentDescription("Stop sing-box service") - - static var parameterSummary: some ParameterSummary { - Summary("Stop sing-box service") - } - - func perform() async throws -> some IntentResult { - guard let extensionProfile = try await (ExtensionProfile.load()) else { - return .result() - } - extensionProfile.stop() - return .result() - } -} diff --git a/WidgetExtension/ServiceToggleControl.swift b/WidgetExtension/ServiceToggleControl.swift new file mode 100644 index 0000000..229f275 --- /dev/null +++ b/WidgetExtension/ServiceToggleControl.swift @@ -0,0 +1,62 @@ +import AppIntents +import Library +import SwiftUI +import WidgetKit + +struct ServiceToggleControl: ControlWidget { + var body: some ControlWidgetConfiguration { + StaticControlConfiguration( + kind: ExtensionProfile.controlKind, + provider: Provider() + ) { value in + ControlWidgetToggle( + "sing-box", + isOn: value, + action: ToggleServiceIntent() + ) { isOn in + Label(isOn ? "Running" : "Stopped", systemImage: "shippingbox.fill") + } + .tint(.init(red: CGFloat(Double(69) / 255), green: CGFloat(Double(90) / 255), blue: CGFloat(Double(100) / 255))) + } + .displayName("Toggle") + .description("Start or stop sing-box service.") + } +} + +extension ServiceToggleControl { + struct Provider: ControlValueProvider { + var previewValue: Bool { + false + } + + func currentValue() async throws -> Bool { + guard let extensionProfile = try await (ExtensionProfile.load()) else { + return false + } + return extensionProfile.status.isStarted + } + } +} + +struct ToggleServiceIntent: SetValueIntent { + static var title: LocalizedStringResource = "Toggle sing-box" + + static var description = + IntentDescription("Toggle sing-box service") + + @Parameter(title: "Running") + var value: Bool + + func perform() async throws -> some IntentResult & ReturnsValue { + guard let extensionProfile = try await (ExtensionProfile.load()) else { + return .result(value: false) + } + if value { + try await extensionProfile.start() + return .result(value: true) + } else { + try await extensionProfile.stop() + return .result(value: false) + } + } +} diff --git a/WidgetExtension/WidgetExtension.entitlements b/WidgetExtension/WidgetExtension.entitlements index 6524ff1..c648121 100644 --- a/WidgetExtension/WidgetExtension.entitlements +++ b/WidgetExtension/WidgetExtension.entitlements @@ -4,7 +4,7 @@ com.apple.security.application-groups - group.org.sagernet.sfa + group.io.nekohasekai.sfavt diff --git a/WidgetExtension/WidgetExtension.swift b/WidgetExtension/WidgetExtension.swift deleted file mode 100644 index 5ba8777..0000000 --- a/WidgetExtension/WidgetExtension.swift +++ /dev/null @@ -1,89 +0,0 @@ -import AppIntents -import Libbox -import Library -import SwiftUI -import WidgetKit - -struct Provider: AppIntentTimelineProvider { - func placeholder(in _: Context) -> ExtensionStatus { - ExtensionStatus(date: .now, isConnected: false, profileList: []) - } - - func snapshot(for _: ConfigurationIntent, in _: Context) async -> ExtensionStatus { - var status = ExtensionStatus(date: .now, isConnected: false, profileList: []) - - do { - status.isConnected = try await ExtensionProfile.load()?.status.isStrictConnected ?? false - - let profileList = try ProfileManager.list() - let selectedProfileID = SharedPreferences.selectedProfileID - for profile in profileList { - status.profileList.append(ProfileEntry(profile: profile, isSelected: profile.id == selectedProfileID)) - } - } catch {} - - return status - } - - func timeline(for intent: ConfigurationIntent, in context: Context) async -> Timeline { - await Timeline(entries: [snapshot(for: intent, in: context)], policy: .never) - } -} - -struct ExtensionStatus: TimelineEntry { - var date: Date - var isConnected: Bool - var profileList: [ProfileEntry] -} - -struct ProfileEntry { - let profile: Profile - let isSelected: Bool -} - -struct WidgetView: View { - @Environment(\.widgetFamily) private var family - - var status: ExtensionStatus - - var body: some View { - VStack { - LabeledContent { - Text(LibboxVersion()) - .font(.caption) - } label: { - Text("sing-box") - .font(.headline) - } - VStack { - viewBuilder { - if !status.isConnected { - Button(intent: StartServiceIntent()) { - Image(systemName: "play.fill") - } - } else { - Button(intent: StopServiceIntent()) { - Image(systemName: "stop.fill") - } - } - } - .controlSize(.large) - .invalidatableContent() - } - .frame(maxHeight: .infinity, alignment: .center) - } - .frame(maxHeight: .infinity, alignment: .topLeading) - .containerBackground(.fill.tertiary, for: .widget) - } -} - -struct WidgetExtension: Widget { - @State private var extensionProfile: ExtensionProfile? - - var body: some WidgetConfiguration { - AppIntentConfiguration(kind: "sing-box", intent: ConfigurationIntent.self, provider: Provider()) { status in - WidgetView(status: status) - } - .supportedFamilies([.systemSmall]) - } -} diff --git a/sing-box.xcodeproj/project.pbxproj b/sing-box.xcodeproj/project.pbxproj index f98a443..1f53a26 100644 --- a/sing-box.xcodeproj/project.pbxproj +++ b/sing-box.xcodeproj/project.pbxproj @@ -113,6 +113,13 @@ 3AE1719D2A8128DD00393060 /* PacketTunnelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AE1719C2A8128DD00393060 /* PacketTunnelProvider.swift */; }; 3AE171A62A81294400393060 /* TVExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 3AE171992A8128DD00393060 /* TVExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 3AE171A92A81297300393060 /* Library.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3AEC211D2A459B4700A63465 /* Library.framework */; }; + 3AE395F42C21A5CA00647718 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3AE395F32C21A5CA00647718 /* WidgetKit.framework */; }; + 3AE395F62C21A5CA00647718 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3AE395F52C21A5CA00647718 /* SwiftUI.framework */; }; + 3AE395F92C21A5CA00647718 /* ExtensionBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AE395F82C21A5CA00647718 /* ExtensionBundle.swift */; }; + 3AE395FB2C21A5CA00647718 /* ServiceToggleControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AE395FA2C21A5CA00647718 /* ServiceToggleControl.swift */; }; + 3AE395FF2C21A5CC00647718 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3AE395FE2C21A5CC00647718 /* Assets.xcassets */; }; + 3AE396032C21A5CC00647718 /* WidgetExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 3AE395F22C21A5CA00647718 /* WidgetExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 3AE396072C21A60C00647718 /* Library.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3AEC211D2A459B4700A63465 /* Library.framework */; }; 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 */; }; @@ -249,6 +256,20 @@ remoteGlobalIDString = 3AEC211C2A459B4700A63465; remoteInfo = Library; }; + 3AE396012C21A5CC00647718 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 3AEC20BD2A45991900A63465 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 3AE395F12C21A5CA00647718; + remoteInfo = WidgetExtensionExtension; + }; + 3AE396092C21A60C00647718 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 3AEC20BD2A45991900A63465 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 3AEC211C2A459B4700A63465; + remoteInfo = Library; + }; 3AE4D0BA2A6E2C55009FEA9E /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 3AEC20BD2A45991900A63465 /* Project object */; @@ -332,6 +353,7 @@ dstPath = ""; dstSubfolderSpec = 13; files = ( + 3AE396032C21A5CC00647718 /* WidgetExtension.appex in Embed Foundation Extensions */, 3AEAEE992A4F16430059612D /* Extension.appex in Embed Foundation Extensions */, ); name = "Embed Foundation Extensions"; @@ -535,6 +557,14 @@ 3AE1719C2A8128DD00393060 /* PacketTunnelProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PacketTunnelProvider.swift; sourceTree = ""; }; 3AE1719E2A8128DD00393060 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 3AE1719F2A8128DD00393060 /* TVExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = TVExtension.entitlements; sourceTree = ""; }; + 3AE395F22C21A5CA00647718 /* WidgetExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = WidgetExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; + 3AE395F32C21A5CA00647718 /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; }; + 3AE395F52C21A5CA00647718 /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; }; + 3AE395F82C21A5CA00647718 /* ExtensionBundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionBundle.swift; sourceTree = ""; }; + 3AE395FA2C21A5CA00647718 /* ServiceToggleControl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServiceToggleControl.swift; sourceTree = ""; }; + 3AE395FE2C21A5CC00647718 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 3AE396002C21A5CC00647718 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 3AE3960E2C21D11F00647718 /* WidgetExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = WidgetExtension.entitlements; sourceTree = ""; }; 3AE4D0B62A6E2C01009FEA9E /* PacketTunnelProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PacketTunnelProvider.swift; sourceTree = ""; }; 3AE4D0C02A6E4852009FEA9E /* InstallSystemExtensionButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstallSystemExtensionButton.swift; sourceTree = ""; }; 3AEAEE9C2A4F1A9D0059612D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -627,6 +657,16 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 3AE395EF2C21A5CA00647718 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 3AE396072C21A60C00647718 /* Library.framework in Frameworks */, + 3AE395F62C21A5CA00647718 /* SwiftUI.framework in Frameworks */, + 3AE395F42C21A5CA00647718 /* WidgetKit.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 3AEC20F02A459AB400A63465 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -827,6 +867,18 @@ path = TVExtension; sourceTree = ""; }; + 3AE395F72C21A5CA00647718 /* WidgetExtension */ = { + isa = PBXGroup; + children = ( + 3AE395F82C21A5CA00647718 /* ExtensionBundle.swift */, + 3AE395FA2C21A5CA00647718 /* ServiceToggleControl.swift */, + 3AE395FE2C21A5CC00647718 /* Assets.xcassets */, + 3AE396002C21A5CC00647718 /* Info.plist */, + 3AE3960E2C21D11F00647718 /* WidgetExtension.entitlements */, + ); + path = WidgetExtension; + sourceTree = ""; + }; 3AEC20BC2A45991900A63465 = { isa = PBXGroup; children = ( @@ -842,6 +894,7 @@ 3AEECBF32A6DF40A006A0E0C /* SystemExtension */, 3A77016E2A4E6B34008F031F /* IntentsExtension */, 3AE1719B2A8128DD00393060 /* TVExtension */, + 3AE395F72C21A5CA00647718 /* WidgetExtension */, 3AEC20C72A45991900A63465 /* Products */, 3AEC21012A459AE300A63465 /* Frameworks */, ); @@ -861,6 +914,7 @@ 3AEECC2F2A6DFDAD006A0E0C /* MacLibrary.framework */, 3AC03B962A72BF3300B7946F /* sing-box.app */, 3AE171992A8128DD00393060 /* TVExtension.appex */, + 3AE395F22C21A5CA00647718 /* WidgetExtension.appex */, ); name = Products; sourceTree = ""; @@ -887,6 +941,8 @@ 3A3DEBE12A4FFA1A00373BF4 /* ExtensionFoundation.framework */, 3AF342B12A4AA520002B34AC /* NetworkExtension.framework */, 3ABA46D22A6A32A100D8366B /* Messages.framework */, + 3AE395F32C21A5CA00647718 /* WidgetKit.framework */, + 3AE395F52C21A5CA00647718 /* SwiftUI.framework */, ); name = Frameworks; sourceTree = ""; @@ -1172,6 +1228,24 @@ productReference = 3AE171992A8128DD00393060 /* TVExtension.appex */; productType = "com.apple.product-type.app-extension"; }; + 3AE395F12C21A5CA00647718 /* WidgetExtension */ = { + isa = PBXNativeTarget; + buildConfigurationList = 3AE396042C21A5CC00647718 /* Build configuration list for PBXNativeTarget "WidgetExtension" */; + buildPhases = ( + 3AE395EE2C21A5CA00647718 /* Sources */, + 3AE395EF2C21A5CA00647718 /* Frameworks */, + 3AE395F02C21A5CA00647718 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 3AE3960A2C21A60C00647718 /* PBXTargetDependency */, + ); + name = WidgetExtension; + productName = WidgetExtensionExtension; + productReference = 3AE395F22C21A5CA00647718 /* WidgetExtension.appex */; + productType = "com.apple.product-type.app-extension"; + }; 3AEC20F22A459AB400A63465 /* SFI */ = { isa = PBXNativeTarget; buildConfigurationList = 3AEC20FE2A459AB500A63465 /* Build configuration list for PBXNativeTarget "SFI" */; @@ -1191,6 +1265,7 @@ 3AEAEE9B2A4F16430059612D /* PBXTargetDependency */, 3A8655162A4FA26600B7181F /* PBXTargetDependency */, 3A4EAD3A2A4FEC20005435B3 /* PBXTargetDependency */, + 3AE396022C21A5CC00647718 /* PBXTargetDependency */, ); name = SFI; packageProductDependencies = ( @@ -1320,7 +1395,7 @@ isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = 1; - LastSwiftUpdateCheck = 1500; + LastSwiftUpdateCheck = 1600; LastUpgradeCheck = 1430; TargetAttributes = { 3A096F852A4ED3DE00D4A2ED = { @@ -1340,6 +1415,9 @@ 3AE171982A8128DD00393060 = { CreatedOnToolsVersion = 15.0; }; + 3AE395F12C21A5CA00647718 = { + CreatedOnToolsVersion = 16.0; + }; 3AEC20F22A459AB400A63465 = { CreatedOnToolsVersion = 15.0; }; @@ -1391,8 +1469,9 @@ 3A4EAD0F2A4FEAE6005435B3 /* ApplicationLibrary */, 3AEECC2E2A6DFDAD006A0E0C /* MacLibrary */, 3A096F852A4ED3DE00D4A2ED /* Extension */, - 3A77016C2A4E6B34008F031F /* IntentsExtension */, 3AE171982A8128DD00393060 /* TVExtension */, + 3A77016C2A4E6B34008F031F /* IntentsExtension */, + 3AE395F12C21A5CA00647718 /* WidgetExtension */, ); }; /* End PBXProject section */ @@ -1420,6 +1499,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 3AE395F02C21A5CA00647718 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 3AE395FF2C21A5CC00647718 /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 3AEC20F12A459AB400A63465 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -1569,6 +1656,15 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 3AE395EE2C21A5CA00647718 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 3AE395F92C21A5CA00647718 /* ExtensionBundle.swift in Sources */, + 3AE395FB2C21A5CA00647718 /* ServiceToggleControl.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 3AEC20EF2A459AB400A63465 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -1721,6 +1817,16 @@ target = 3AEC211C2A459B4700A63465 /* Library */; targetProxy = 3AE171AB2A81297300393060 /* PBXContainerItemProxy */; }; + 3AE396022C21A5CC00647718 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 3AE395F12C21A5CA00647718 /* WidgetExtension */; + targetProxy = 3AE396012C21A5CC00647718 /* PBXContainerItemProxy */; + }; + 3AE3960A2C21A60C00647718 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 3AEC211C2A459B4700A63465 /* Library */; + targetProxy = 3AE396092C21A60C00647718 /* PBXContainerItemProxy */; + }; 3AE4D0BB2A6E2C55009FEA9E /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 3AEC211C2A459B4700A63465 /* Library */; @@ -2154,6 +2260,69 @@ }; name = Release; }; + 3AE396052C21A5CC00647718 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; + CODE_SIGN_ENTITLEMENTS = WidgetExtension/WidgetExtension.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 287TTNZF8L; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = WidgetExtension/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = WidgetExtension; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + IPHONEOS_DEPLOYMENT_TARGET = 18.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfavt.widget; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 3AE396062C21A5CC00647718 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; + CODE_SIGN_ENTITLEMENTS = WidgetExtension/WidgetExtension.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 287TTNZF8L; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = WidgetExtension/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = WidgetExtension; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + IPHONEOS_DEPLOYMENT_TARGET = 18.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = io.nekohasekai.sfavt.widget; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; 3AEC20CB2A45991900A63465 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -2823,6 +2992,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + 3AE396042C21A5CC00647718 /* Build configuration list for PBXNativeTarget "WidgetExtension" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 3AE396052C21A5CC00647718 /* Debug */, + 3AE396062C21A5CC00647718 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 3AEC20C02A45991900A63465 /* Build configuration list for PBXProject "sing-box" */ = { isa = XCConfigurationList; buildConfigurations = (