From 7b71475b4ce6db4fedfbd9b77fa7b7e43560e19f Mon Sep 17 00:00:00 2001 From: mrFq1 <1xxbx0il0@mozmail.com> Date: Sat, 21 Sep 2024 12:23:37 +0800 Subject: [PATCH 01/14] fix: tun dns --- ClashX/Actions/TerminalCleanUpAction.swift | 2 +- ClashX/AppDelegate.swift | 6 +- ClashX/Base.lproj/Main.storyboard | 22 +-- ClashX/General/Managers/ConfigManager.swift | 8 + .../Settings/MetaPrefs.storyboard | 173 +++++++++--------- .../Settings/MetaPrefsViewController.swift | 18 ++ ClashX/Views/RemoteConfigAddView.xib | 7 +- ClashX/Views/StatusItem/StatusItemView.xib | 11 +- ProxyConfigHelper/MetaDNS.swift | 54 ++++-- ProxyConfigHelper/ProxyConfigHelper.swift | 3 +- .../ProxyConfigRemoteProcessProtocol.swift | 2 +- 11 files changed, 179 insertions(+), 127 deletions(-) diff --git a/ClashX/Actions/TerminalCleanUpAction.swift b/ClashX/Actions/TerminalCleanUpAction.swift index 8ffefba91..b5d618d21 100644 --- a/ClashX/Actions/TerminalCleanUpAction.swift +++ b/ClashX/Actions/TerminalCleanUpAction.swift @@ -21,7 +21,7 @@ enum TerminalConfirmAction { ConfigManager.shared.restoreTunProxy = ConfigManager.shared.isTunModeVariable.value PrivilegedHelperManager.shared.helper()?.stopMeta() - PrivilegedHelperManager.shared.helper()?.updateTun(state: false) + PrivilegedHelperManager.shared.helper()?.updateTun(state: false, dns: ConfigManager.metaTunDNS) let path = Paths.tempPath() + "/cacheConfigs" try? FileManager.default.removeItem(atPath: path) diff --git a/ClashX/AppDelegate.swift b/ClashX/AppDelegate.swift index 91b77571c..275b1dfff 100644 --- a/ClashX/AppDelegate.swift +++ b/ClashX/AppDelegate.swift @@ -442,7 +442,7 @@ class AppDelegate: NSObject, NSApplicationDelegate { return } - PrivilegedHelperManager.shared.helper()?.updateTun(state: enable) + PrivilegedHelperManager.shared.helper()?.updateTun(state: enable, dns: ConfigManager.metaTunDNS) Logger.log("tun state updated, new: \(enable)") } } @@ -578,7 +578,7 @@ extension AppDelegate: ClashProcessDelegate { if ConfigManager.shared.restoreTunProxy { ApiRequest.updateTun(enable: true) { - PrivilegedHelperManager.shared.helper()?.updateTun(state: true) + PrivilegedHelperManager.shared.helper()?.updateTun(state: true, dns: ConfigManager.metaTunDNS) } } else { syncConfigWithTun(true) @@ -755,7 +755,7 @@ extension AppDelegate: ApiRequestStreamDelegate { } func didGetLog(log: String, level: String) { - Logger.log(log, level: ClashLogLevel(rawValue: level) ?? .unknow) +// Logger.log(log, level: ClashLogLevel(rawValue: level) ?? .unknow) } } diff --git a/ClashX/Base.lproj/Main.storyboard b/ClashX/Base.lproj/Main.storyboard index 6f1b9194c..bcc4141f6 100644 --- a/ClashX/Base.lproj/Main.storyboard +++ b/ClashX/Base.lproj/Main.storyboard @@ -1,7 +1,7 @@ - + - + @@ -296,15 +296,15 @@ - + - + - + @@ -318,7 +318,7 @@ - + @@ -425,15 +425,15 @@ - + - + - + @@ -447,7 +447,7 @@ - + @@ -1951,7 +1951,7 @@ - + diff --git a/ClashX/General/Managers/ConfigManager.swift b/ClashX/General/Managers/ConfigManager.swift index a4f6217ab..eb0d2650c 100644 --- a/ClashX/General/Managers/ConfigManager.swift +++ b/ClashX/General/Managers/ConfigManager.swift @@ -99,6 +99,14 @@ class ConfigManager { var proxyShouldPaused = BehaviorRelay(value: false) var isTunModeVariable = BehaviorRelay(value: false) + + static let defaultTunDNS = "8.8.8.8" + + static var metaTunDNS: String = UserDefaults.standard.object(forKey: "metaTunDNS") as? String ?? defaultTunDNS { + didSet { + UserDefaults.standard.set(metaTunDNS, forKey: "metaTunDNS") + } + } var showNetSpeedIndicator: Bool { get { diff --git a/ClashX/ViewControllers/Settings/MetaPrefs.storyboard b/ClashX/ViewControllers/Settings/MetaPrefs.storyboard index 8f4a8477f..aa2f6d7a6 100644 --- a/ClashX/ViewControllers/Settings/MetaPrefs.storyboard +++ b/ClashX/ViewControllers/Settings/MetaPrefs.storyboard @@ -1,7 +1,7 @@ - + - + @@ -9,46 +9,103 @@ - - + + - + - + - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - + + + + - + - + - + @@ -60,7 +117,7 @@ - + @@ -119,13 +176,13 @@ - + - + - + @@ -150,7 +207,7 @@ - + @@ -160,7 +217,7 @@ - + @@ -223,14 +280,14 @@ + - + - - + @@ -240,67 +297,19 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + diff --git a/ClashX/ViewControllers/Settings/MetaPrefsViewController.swift b/ClashX/ViewControllers/Settings/MetaPrefsViewController.swift index 4a3c8d99d..22f3f0efb 100644 --- a/ClashX/ViewControllers/Settings/MetaPrefsViewController.swift +++ b/ClashX/ViewControllers/Settings/MetaPrefsViewController.swift @@ -6,6 +6,7 @@ // import Cocoa +import Network class MetaPrefsViewController: NSViewController { // Meta Setting @@ -28,6 +29,13 @@ class MetaPrefsViewController: NSViewController { MenuItemFactory.hideUnselectable = newState.rawValue } + @IBOutlet var tunDNSTextField: NSTextField! + @IBAction func tunDNSChanged(_ sender: NSTextField) { + let ds = sender.stringValue + guard let _ = IPv4Address(ds) else { return } + ConfigManager.metaTunDNS = ds + } + // Dashboard @IBOutlet var useSwiftuiButton: NSButton! @IBOutlet var useYacdButton: NSButton! @@ -109,6 +117,10 @@ class MetaPrefsViewController: NSViewController { // Meta Setting hideUnselectableButton.state = .init(rawValue: MenuItemFactory.hideUnselectable) + tunDNSTextField.placeholderString = ConfigManager.defaultTunDNS + tunDNSTextField.stringValue = ConfigManager.metaTunDNS + tunDNSTextField.delegate = self + // Dashboard initDashboardButtons() @@ -159,3 +171,9 @@ class MetaPrefsViewController: NSViewController { } } + +extension MetaPrefsViewController: NSTextFieldDelegate { + func control(_ control: NSControl, textShouldEndEditing fieldEditor: NSText) -> Bool { + IPv4Address(fieldEditor.string) != nil + } +} diff --git a/ClashX/Views/RemoteConfigAddView.xib b/ClashX/Views/RemoteConfigAddView.xib index fbc2253bd..911aa10c3 100644 --- a/ClashX/Views/RemoteConfigAddView.xib +++ b/ClashX/Views/RemoteConfigAddView.xib @@ -1,15 +1,14 @@ - + - - + - + diff --git a/ClashX/Views/StatusItem/StatusItemView.xib b/ClashX/Views/StatusItem/StatusItemView.xib index fa7dbc25d..207bcf182 100644 --- a/ClashX/Views/StatusItem/StatusItemView.xib +++ b/ClashX/Views/StatusItem/StatusItemView.xib @@ -1,8 +1,7 @@ - + - - + @@ -24,7 +23,7 @@ - + @@ -32,7 +31,7 @@ - + @@ -69,6 +68,6 @@ - + diff --git a/ProxyConfigHelper/MetaDNS.swift b/ProxyConfigHelper/MetaDNS.swift index 4064e5762..5fb51e30c 100644 --- a/ProxyConfigHelper/MetaDNS.swift +++ b/ProxyConfigHelper/MetaDNS.swift @@ -10,10 +10,10 @@ import SystemConfiguration // https://github.com/zhuhaow/Specht2/blob/main/app/me.zhuhaow.Specht2.proxy-helper/ProxyHelper.swift class MetaDNS: NSObject { - + private var customDNS = "8.8.8.8" + + static let savedDnsKey = "SavedSystemDNSs" var savedDns = [String: [String]]() - let defaultDNS = "198.18.0.2" - let authRef: AuthorizationRef override init() { @@ -29,7 +29,13 @@ class MetaDNS: NSObject { if auth == nil { NSLog("Error: No authorization has been granted to modify network configuration.") } - + + + if let data = UserDefaults.standard.data(forKey: MetaDNS.savedDnsKey), + let saved = try? JSONDecoder().decode([String: [String]].self, from: data) { + self.savedDns = saved + } + authRef = auth! super.init() @@ -39,23 +45,34 @@ class MetaDNS: NSObject { AuthorizationFree(authRef, AuthorizationFlags()) } + @objc func setCustomDNS(_ dns: String) { + customDNS = dns + } + @objc func updateDns() { - let dns = getAllDns() - dns.forEach { - if $0.value.count == 1, - $0.value[0] == defaultDNS { - if savedDns[$0.key] == nil { - savedDns[$0.key] = [] - } else { - // ignore save - } - } else { - savedDns[$0.key] = $0.value - } - } + let dns = getAllDns() + + if savedDns.isEmpty { + dns.forEach { + if $0.value.count == 1, + $0.value[0] == customDNS { + if savedDns[$0.key] == nil { + savedDns[$0.key] = [] + } else { + // ignore save + } + } else { + savedDns[$0.key] = $0.value + } + } + if !savedDns.isEmpty, + let data = try? JSONEncoder().encode(savedDns) { + UserDefaults.standard.set(data, forKey: MetaDNS.savedDnsKey) + } + } let dnsDic = dns.reduce(into: [:]) { - $0[$1.key] = [defaultDNS] + $0[$1.key] = [customDNS] } updateDNSConfigure(dnsDic) @@ -64,6 +81,7 @@ class MetaDNS: NSObject { @objc func revertDns() { updateDNSConfigure(savedDns) savedDns.removeAll() + UserDefaults.standard.removeObject(forKey: MetaDNS.savedDnsKey) } func getAllDns() -> [String: [String]] { diff --git a/ProxyConfigHelper/ProxyConfigHelper.swift b/ProxyConfigHelper/ProxyConfigHelper.swift index 56e3a3b19..0cc7f162a 100644 --- a/ProxyConfigHelper/ProxyConfigHelper.swift +++ b/ProxyConfigHelper/ProxyConfigHelper.swift @@ -132,8 +132,9 @@ extension ProxyConfigHelper: ProxyConfigRemoteProcessProtocol { } } - func updateTun(state: Bool) { + func updateTun(state: Bool, dns: String) { DispatchQueue.main.async { + self.metaDNS.setCustomDNS(dns) if state { self.metaDNS.updateDns() } else { diff --git a/ProxyConfigHelper/ProxyConfigRemoteProcessProtocol.swift b/ProxyConfigHelper/ProxyConfigRemoteProcessProtocol.swift index dad672b23..a2203456e 100644 --- a/ProxyConfigHelper/ProxyConfigRemoteProcessProtocol.swift +++ b/ProxyConfigHelper/ProxyConfigRemoteProcessProtocol.swift @@ -13,7 +13,7 @@ protocol ProxyConfigRemoteProcessProtocol { func startMeta(path: String, confPath: String, confFilePath: String, confJSON: String, reply: @escaping (String?) -> Void) func stopMeta() - func updateTun(state: Bool) + func updateTun(state: Bool, dns: String) func getUsedPorts(reply: @escaping (String?) -> Void) From f939bae59d0fdd595b92d8926785cd018bd43852 Mon Sep 17 00:00:00 2001 From: mrFq1 <1xxbx0il0@mozmail.com> Date: Sat, 21 Sep 2024 12:23:47 +0800 Subject: [PATCH 02/14] bump: 1.13 --- ProxyConfigHelper/Helper-Info.plist | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ProxyConfigHelper/Helper-Info.plist b/ProxyConfigHelper/Helper-Info.plist index 3cc6b4d11..c4a8a8ec6 100755 --- a/ProxyConfigHelper/Helper-Info.plist +++ b/ProxyConfigHelper/Helper-Info.plist @@ -9,9 +9,9 @@ CFBundleName com.metacubex.ClashX.ProxyConfigHelper CFBundleShortVersionString - 1.12 + 1.13 CFBundleVersion - 22 + 23 SMAuthorizedClients anchor apple generic and identifier "com.metacubex.ClashX.ProxyConfigHelper" and (certificate leaf[field.1.2.840.113635.100.6.1.9] /* exists */ or certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.OU] = MEWHFZ92DY) From ff60676e3a579eea7dee4767f73804970268451b Mon Sep 17 00:00:00 2001 From: mrFq1 <1xxbx0il0@mozmail.com> Date: Sat, 21 Sep 2024 15:15:52 +0800 Subject: [PATCH 03/14] feat: meta restart tips --- .../Settings/MetaPrefs.storyboard | 23 ++++++++++--- .../Settings/MetaPrefsViewController.swift | 33 +++++++++++++++++++ 2 files changed, 51 insertions(+), 5 deletions(-) diff --git a/ClashX/ViewControllers/Settings/MetaPrefs.storyboard b/ClashX/ViewControllers/Settings/MetaPrefs.storyboard index aa2f6d7a6..a17a24261 100644 --- a/ClashX/ViewControllers/Settings/MetaPrefs.storyboard +++ b/ClashX/ViewControllers/Settings/MetaPrefs.storyboard @@ -10,14 +10,14 @@ - + - + - + @@ -99,7 +99,7 @@ - + @@ -176,7 +176,7 @@ - + @@ -277,6 +277,14 @@ + + + + + + + + @@ -285,16 +293,20 @@ + + + + @@ -308,6 +320,7 @@ + diff --git a/ClashX/ViewControllers/Settings/MetaPrefsViewController.swift b/ClashX/ViewControllers/Settings/MetaPrefsViewController.swift index 22f3f0efb..3ccfc86a4 100644 --- a/ClashX/ViewControllers/Settings/MetaPrefsViewController.swift +++ b/ClashX/ViewControllers/Settings/MetaPrefsViewController.swift @@ -34,6 +34,7 @@ class MetaPrefsViewController: NSViewController { let ds = sender.stringValue guard let _ = IPv4Address(ds) else { return } ConfigManager.metaTunDNS = ds + updateNeedsRestart() } // Dashboard @@ -55,6 +56,7 @@ class MetaPrefsViewController: NSViewController { break } initDashboardButtons() + updateNeedsRestart() } // Alpha Core @@ -79,6 +81,7 @@ class MetaPrefsViewController: NSViewController { let use = sender.state == .on ConfigManager.useAlphaCore = use + updateNeedsRestart() } @IBAction func updateAlpha(_ sender: NSButton) { @@ -112,6 +115,12 @@ class MetaPrefsViewController: NSViewController { } + @IBOutlet var restartTextField: NSTextField! + + var prefsSnapshot = [String]() + var versionSnapshot = "none" + var alphaCoreUpdated = false + override func viewDidLoad() { super.viewDidLoad() // Meta Setting @@ -128,6 +137,11 @@ class MetaPrefsViewController: NSViewController { useAlphaButton.state = ConfigManager.useAlphaCore ? .on : .off updateProgressIndicator.isHidden = true setAlphaVersion() + + // Snapshot + prefsSnapshot = takePrefsSnapshot() + versionSnapshot = alphaVersionTextField.stringValue + restartTextField.isHidden = true } func initDashboardButtons() { @@ -168,8 +182,27 @@ class MetaPrefsViewController: NSViewController { alphaVersionTextField.stringValue = "none" updateButton.title = NSLocalizedString("Download Meta core", comment: "") } + + if let v = version, + versionSnapshot != "none", + v != versionSnapshot { + alphaCoreUpdated = true + updateNeedsRestart() + } + } + + func takePrefsSnapshot() -> [String] { + [ + ConfigManager.metaTunDNS, + "\(ConfigManager.useYacdDashboard)", + "\(ConfigManager.useAlphaCore)" + ] } + func updateNeedsRestart() { + let needsRestart = prefsSnapshot != takePrefsSnapshot() || alphaCoreUpdated + restartTextField.isHidden = !needsRestart + } } extension MetaPrefsViewController: NSTextFieldDelegate { From 993ff0a1465d9eec35cbcf0b1520d19d096f9a7c Mon Sep 17 00:00:00 2001 From: mrFq1 <1xxbx0il0@mozmail.com> Date: Sat, 21 Sep 2024 15:53:47 +0800 Subject: [PATCH 04/14] refactor: revertDns --- ProxyConfigHelper/MetaDNS.swift | 46 ++++++++++------------- ProxyConfigHelper/ProxyConfigHelper.swift | 4 +- 2 files changed, 21 insertions(+), 29 deletions(-) diff --git a/ProxyConfigHelper/MetaDNS.swift b/ProxyConfigHelper/MetaDNS.swift index 5fb51e30c..2b75dc4d6 100644 --- a/ProxyConfigHelper/MetaDNS.swift +++ b/ProxyConfigHelper/MetaDNS.swift @@ -12,8 +12,8 @@ import SystemConfiguration class MetaDNS: NSObject { private var customDNS = "8.8.8.8" - static let savedDnsKey = "SavedSystemDNSs" - var savedDns = [String: [String]]() + static let savedDNSKey = "ProxyConfigHelper.SavedSystemDNSs" + var savedDNS = [String: [String]]() let authRef: AuthorizationRef override init() { @@ -31,9 +31,9 @@ class MetaDNS: NSObject { } - if let data = UserDefaults.standard.data(forKey: MetaDNS.savedDnsKey), + if let data = UserDefaults.standard.data(forKey: MetaDNS.savedDNSKey), let saved = try? JSONDecoder().decode([String: [String]].self, from: data) { - self.savedDns = saved + self.savedDNS = saved } authRef = auth! @@ -49,28 +49,19 @@ class MetaDNS: NSObject { customDNS = dns } - @objc func updateDns() { + @objc func hijackDNS() { let dns = getAllDns() + let hijacked = dns.allSatisfy { + $0.value.count == 1 && $0.value[0] == customDNS + } + + guard !hijacked else { return } - if savedDns.isEmpty { - dns.forEach { - if $0.value.count == 1, - $0.value[0] == customDNS { - if savedDns[$0.key] == nil { - savedDns[$0.key] = [] - } else { - // ignore save - } - } else { - savedDns[$0.key] = $0.value - } - } - if !savedDns.isEmpty, - let data = try? JSONEncoder().encode(savedDns) { - UserDefaults.standard.set(data, forKey: MetaDNS.savedDnsKey) - } + savedDNS = dns + if let data = try? JSONEncoder().encode(savedDNS) { + UserDefaults.standard.set(data, forKey: MetaDNS.savedDNSKey) } - + let dnsDic = dns.reduce(into: [:]) { $0[$1.key] = [customDNS] } @@ -78,10 +69,11 @@ class MetaDNS: NSObject { updateDNSConfigure(dnsDic) } - @objc func revertDns() { - updateDNSConfigure(savedDns) - savedDns.removeAll() - UserDefaults.standard.removeObject(forKey: MetaDNS.savedDnsKey) + @objc func revertDNS() { + guard savedDNS.count > 0 else { return } + updateDNSConfigure(savedDNS) + savedDNS.removeAll() + UserDefaults.standard.removeObject(forKey: MetaDNS.savedDNSKey) } func getAllDns() -> [String: [String]] { diff --git a/ProxyConfigHelper/ProxyConfigHelper.swift b/ProxyConfigHelper/ProxyConfigHelper.swift index 0cc7f162a..4a3083954 100644 --- a/ProxyConfigHelper/ProxyConfigHelper.swift +++ b/ProxyConfigHelper/ProxyConfigHelper.swift @@ -136,9 +136,9 @@ extension ProxyConfigHelper: ProxyConfigRemoteProcessProtocol { DispatchQueue.main.async { self.metaDNS.setCustomDNS(dns) if state { - self.metaDNS.updateDns() + self.metaDNS.hijackDNS() } else { - self.metaDNS.revertDns() + self.metaDNS.revertDNS() } self.metaDNS.flushDnsCache() } From 1c1b1eb49fa2201575207d92aa5b825962897eeb Mon Sep 17 00:00:00 2001 From: mrFq1 <1xxbx0il0@mozmail.com> Date: Sat, 21 Sep 2024 17:42:08 +0800 Subject: [PATCH 05/14] misc: drop 10.14, 10.15 --- ClashX.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ClashX.xcodeproj/project.pbxproj b/ClashX.xcodeproj/project.pbxproj index a99ea489a..9389bdd92 100644 --- a/ClashX.xcodeproj/project.pbxproj +++ b/ClashX.xcodeproj/project.pbxproj @@ -1280,7 +1280,7 @@ "$(inherited)", "$(PROJECT_DIR)/ClashX", ); - MACOSX_DEPLOYMENT_TARGET = 12.0; + MACOSX_DEPLOYMENT_TARGET = 11.0; MARKETING_VERSION = v1.0; PRODUCT_BUNDLE_IDENTIFIER = com.metacubex.ClashX.meta; PRODUCT_NAME = "ClashX Meta"; @@ -1327,7 +1327,7 @@ "$(inherited)", "$(PROJECT_DIR)/ClashX", ); - MACOSX_DEPLOYMENT_TARGET = 12.0; + MACOSX_DEPLOYMENT_TARGET = 11.0; MARKETING_VERSION = v1.0; OTHER_CODE_SIGN_FLAGS = "--timestamp"; PRODUCT_BUNDLE_IDENTIFIER = com.metacubex.ClashX.meta; From fa29f9164a4ec0cc7b700bc4a779ad2f4dc4bc28 Mon Sep 17 00:00:00 2001 From: mrFq1 <1xxbx0il0@mozmail.com> Date: Sat, 21 Sep 2024 17:42:57 +0800 Subject: [PATCH 06/14] misc: delete deps --- ClashX.xcodeproj/project.pbxproj | 565 +++--------------- .../xcshareddata/swiftpm/Package.resolved | 11 +- .../xcschemes/ClashX Meta SwiftUI.xcscheme | 78 --- .../xcschemes/ClashX Meta.xcscheme | 6 +- ClashX/ViewControllers/DashboardManager.swift | 14 - .../Settings/MetaPrefsViewController.swift | 7 +- 6 files changed, 72 insertions(+), 609 deletions(-) delete mode 100644 ClashX.xcodeproj/xcshareddata/xcschemes/ClashX Meta SwiftUI.xcscheme diff --git a/ClashX.xcodeproj/project.pbxproj b/ClashX.xcodeproj/project.pbxproj index 9389bdd92..bc2d361d7 100644 --- a/ClashX.xcodeproj/project.pbxproj +++ b/ClashX.xcodeproj/project.pbxproj @@ -8,42 +8,21 @@ /* Begin PBXBuildFile section */ 0106179F2AF38EFA005C7877 /* Command.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49FEC6682AD9369C00BAD9F5 /* Command.swift */; }; - 015B976A2A4F2F4500F9FA4D /* Alamofire in Frameworks */ = {isa = PBXBuildFile; productRef = 015B97692A4F2F4500F9FA4D /* Alamofire */; }; - 015B976D2A4F2F6C00F9FA4D /* RxCocoa in Frameworks */ = {isa = PBXBuildFile; productRef = 015B976C2A4F2F6C00F9FA4D /* RxCocoa */; }; - 015B976F2A4F2F6C00F9FA4D /* RxSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 015B976E2A4F2F6C00F9FA4D /* RxSwift */; }; - 015B97722A4F2F9900F9FA4D /* SwiftyJSON in Frameworks */ = {isa = PBXBuildFile; productRef = 015B97712A4F2F9900F9FA4D /* SwiftyJSON */; }; - 015B97752A4F2FC600F9FA4D /* CocoaLumberjack in Frameworks */ = {isa = PBXBuildFile; productRef = 015B97742A4F2FC600F9FA4D /* CocoaLumberjack */; }; - 015B97772A4F2FC600F9FA4D /* CocoaLumberjackSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 015B97762A4F2FC600F9FA4D /* CocoaLumberjackSwift */; }; - 015B977A2A4F306800F9FA4D /* Starscream in Frameworks */ = {isa = PBXBuildFile; productRef = 015B97792A4F306800F9FA4D /* Starscream */; }; - 015B977D2A4F30B900F9FA4D /* FlexibleDiff in Frameworks */ = {isa = PBXBuildFile; productRef = 015B977C2A4F30B900F9FA4D /* FlexibleDiff */; }; - 015B97802A4F30EA00F9FA4D /* Gzip in Frameworks */ = {isa = PBXBuildFile; productRef = 015B977F2A4F30EA00F9FA4D /* Gzip */; }; - 015B97832A4F311300F9FA4D /* Yams in Frameworks */ = {isa = PBXBuildFile; productRef = 015B97822A4F311300F9FA4D /* Yams */; }; - 015B97862A4F31AF00F9FA4D /* PromiseKit in Frameworks */ = {isa = PBXBuildFile; productRef = 015B97852A4F31AF00F9FA4D /* PromiseKit */; }; - 015F1E91288E42A50052B20A /* ClashMetaConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 015F1E90288E42A50052B20A /* ClashMetaConfig.swift */; }; - 015F1E92288E60D30052B20A /* MetaTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0162E74E2864B819007218A6 /* MetaTask.swift */; }; 0162E74F2864B819007218A6 /* MetaTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0162E74E2864B819007218A6 /* MetaTask.swift */; }; - 016BEAB029D80103001586C5 /* AlphaMetaDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 016BEAAF29D80102001586C5 /* AlphaMetaDownloader.swift */; }; - 018F88F9286DD0CB004DD0F7 /* DualTitleMenuItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 018F88F8286DD0CB004DD0F7 /* DualTitleMenuItem.swift */; }; - 01943259287D19BC008CC51A /* ClashRuleProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01943258287D19BC008CC51A /* ClashRuleProvider.swift */; }; 019A239628657A7A00AE5698 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 019A239528657A7A00AE5698 /* main.swift */; }; - 01B009AE2854533300B93618 /* geoip.dat.gz in Resources */ = {isa = PBXBuildFile; fileRef = 01B009AC2854533200B93618 /* geoip.dat.gz */; }; - 01B009AF2854533300B93618 /* geosite.dat.gz in Resources */ = {isa = PBXBuildFile; fileRef = 01B009AD2854533300B93618 /* geosite.dat.gz */; }; - 01B1CB0A2A2E20C10073EA34 /* DashboardManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01B1CB092A2E20C10073EA34 /* DashboardManager.swift */; }; - 01B2274B29B845F100FE35C9 /* country.mmdb.gz in Resources */ = {isa = PBXBuildFile; fileRef = 01B2274A29B845F100FE35C9 /* country.mmdb.gz */; }; 01BC9ABE2928EB5A00F9B177 /* MetaDNS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BC9ABD2928E5C600F9B177 /* MetaDNS.swift */; }; - 01C1462A28962E4E00346AF3 /* com.metacubex.ClashX.ProxyConfigHelper.meta.gz in Resources */ = {isa = PBXBuildFile; fileRef = 01C1462928962E4E00346AF3 /* com.metacubex.ClashX.ProxyConfigHelper.meta.gz */; }; - 01CA6BC02B6A1B3100E386D6 /* MetaServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01CA6BBF2B6A1B3100E386D6 /* MetaServer.swift */; }; + 01BCDAAC2C9ECB3A0028FA94 /* DSFSparkline in Frameworks */ = {isa = PBXBuildFile; productRef = 01BCDAAB2C9ECB3A0028FA94 /* DSFSparkline */; }; + 01BCDAAE2C9ECB3A0028FA94 /* DSFSparkline-shared in Frameworks */ = {isa = PBXBuildFile; productRef = 01BCDAAD2C9ECB3A0028FA94 /* DSFSparkline-shared */; }; + 01BCDAB02C9ECB3A0028FA94 /* DSFSparkline-static in Frameworks */ = {isa = PBXBuildFile; productRef = 01BCDAAF2C9ECB3A0028FA94 /* DSFSparkline-static */; }; + 01BCDAB32C9ECB560028FA94 /* SwiftUIIntrospect in Frameworks */ = {isa = PBXBuildFile; productRef = 01BCDAB22C9ECB560028FA94 /* SwiftUIIntrospect */; }; + 01BCDAB52C9ECB560028FA94 /* SwiftUIIntrospect-Dynamic in Frameworks */ = {isa = PBXBuildFile; productRef = 01BCDAB42C9ECB560028FA94 /* SwiftUIIntrospect-Dynamic */; }; + 01BCDAB72C9ECB560028FA94 /* SwiftUIIntrospect-Static in Frameworks */ = {isa = PBXBuildFile; productRef = 01BCDAB62C9ECB560028FA94 /* SwiftUIIntrospect-Static */; }; 01CA6BC12B6A1B3100E386D6 /* MetaServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01CA6BBF2B6A1B3100E386D6 /* MetaServer.swift */; }; 01CA6BC22B6A1B3100E386D6 /* MetaServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01CA6BBF2B6A1B3100E386D6 /* MetaServer.swift */; }; - 01D567E32AD158B600CDA0AE /* MetaPrefs.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 01D567E12AD158B500CDA0AE /* MetaPrefs.storyboard */; }; 01D567E42AD158B600CDA0AE /* MetaPrefs.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 01D567E12AD158B500CDA0AE /* MetaPrefs.storyboard */; }; - 01D567E52AD158B600CDA0AE /* MetaPrefsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01D567E22AD158B500CDA0AE /* MetaPrefsViewController.swift */; }; 01D567E62AD158B600CDA0AE /* MetaPrefsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01D567E22AD158B500CDA0AE /* MetaPrefsViewController.swift */; }; - 01E33AB229B5BF4200FD1006 /* NSColor+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01E33AB129B5BF4200FD1006 /* NSColor+Extension.swift */; }; - 01E33AB529B5C5E400FD1006 /* menu_icon@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 01E33AB429B5C5E300FD1006 /* menu_icon@2x.png */; }; 01EF33602B98D03B00D1DBD9 /* ProxyConfigHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01EF335F2B98D03B00D1DBD9 /* ProxyConfigHelper.swift */; }; 01EF33622B98D3A700D1DBD9 /* ProxyConfigRemoteProcessProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01EF33612B98D3A700D1DBD9 /* ProxyConfigRemoteProcessProtocol.swift */; }; - 01EF33632B98D71600D1DBD9 /* ProxyConfigRemoteProcessProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01EF33612B98D3A700D1DBD9 /* ProxyConfigRemoteProcessProtocol.swift */; }; 01EF33642B98D71600D1DBD9 /* ProxyConfigRemoteProcessProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01EF33612B98D3A700D1DBD9 /* ProxyConfigRemoteProcessProtocol.swift */; }; 01F335CD2AD10D0B0048AF77 /* UnsafePointer+bridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49ABB748236B0F9E00535CD7 /* UnsafePointer+bridge.swift */; }; 01F335CE2AD10D0B0048AF77 /* RemoteConfigViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 499A485322ED707300F6C675 /* RemoteConfigViewController.swift */; }; @@ -158,102 +137,9 @@ 01F3363D2AD10D0B0048AF77 /* com.metacubex.ClashX.ProxyConfigHelper.meta.gz in Resources */ = {isa = PBXBuildFile; fileRef = 01C1462928962E4E00346AF3 /* com.metacubex.ClashX.ProxyConfigHelper.meta.gz */; }; 01F3363E2AD10D0B0048AF77 /* country.mmdb.gz in Resources */ = {isa = PBXBuildFile; fileRef = 01B2274A29B845F100FE35C9 /* country.mmdb.gz */; }; 01F336402AD10D0B0048AF77 /* com.metacubex.ClashX.ProxyConfigHelper in Copy Files */ = {isa = PBXBuildFile; fileRef = F9A7C0692306E874007163C7 /* com.metacubex.ClashX.ProxyConfigHelper */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; - 01F336482AD139CC0048AF77 /* ClashX Dashboard in Frameworks */ = {isa = PBXBuildFile; productRef = 01F336472AD139CC0048AF77 /* ClashX Dashboard */; }; - 01FBC6302B9C2B0800810BFF /* ClashProcess.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01FBC62F2B9C2B0800810BFF /* ClashProcess.swift */; }; 01FBC6312B9C2B0800810BFF /* ClashProcess.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01FBC62F2B9C2B0800810BFF /* ClashProcess.swift */; }; - 275348502A3082FD0077B458 /* TunModeSettingCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2753484F2A3082FD0077B458 /* TunModeSettingCommand.swift */; }; - 4905A2C52A2058B000AEDA2E /* GlobalShortCutViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4905A2C42A2058B000AEDA2E /* GlobalShortCutViewController.swift */; }; - 4905A2C82A2058D400AEDA2E /* KeyboardShortcuts in Frameworks */ = {isa = PBXBuildFile; productRef = 4905A2C72A2058D400AEDA2E /* KeyboardShortcuts */; }; - 4905A2CA2A20841B00AEDA2E /* NSView+Layout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4905A2C92A20841B00AEDA2E /* NSView+Layout.swift */; }; - 4908087B29F8F405007A4944 /* libresolv.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 4908087A29F8F3FF007A4944 /* libresolv.tbd */; }; - 4913C82321157D0200F6B87C /* Notification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4913C82221157D0200F6B87C /* Notification.swift */; }; 491E6203258A424D00313AEF /* CommonUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 491E61FD258A424500313AEF /* CommonUtils.m */; }; - 49228457270AADE20027A4B6 /* RemoteConfigUpdateIntervalSettingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49228456270AADE20027A4B6 /* RemoteConfigUpdateIntervalSettingView.swift */; }; - 49281C802A1F01FA00F60935 /* DebugSettingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49281C7F2A1F01FA00F60935 /* DebugSettingViewController.swift */; }; - 4929F67F258CE04700A435F6 /* Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4929F67E258CE04700A435F6 /* Settings.swift */; }; - 4929F684258CE07500A435F6 /* UserDefaultWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4929F683258CE07500A435F6 /* UserDefaultWrapper.swift */; }; - 492C4869210EE6B9004554A0 /* ApiRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 492C4868210EE6B9004554A0 /* ApiRequest.swift */; }; - 492C4871210EF62E004554A0 /* ClashConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 492C4870210EF62E004554A0 /* ClashConfig.swift */; }; - 493A9F282453E60400D35296 /* ProxyDelayHistoryMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 493A9F272453E60400D35296 /* ProxyDelayHistoryMenu.swift */; }; - 493AEAE3221AE3420016FE98 /* AppVersionUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = 493AEAE2221AE3420016FE98 /* AppVersionUtil.swift */; }; - 493AEAE5221AE7230016FE98 /* ProxyMenuItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 493AEAE4221AE7230016FE98 /* ProxyMenuItem.swift */; }; - 4949D154213242F600EF85E6 /* Paths.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4949D153213242F600EF85E6 /* Paths.swift */; }; - 4952C3BF2115C7CA004A4FA8 /* MenuItemFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4952C3BE2115C7CA004A4FA8 /* MenuItemFactory.swift */; }; - 4952C3D02117027C004A4FA8 /* ConfigFileManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4952C3CF2117027C004A4FA8 /* ConfigFileManager.swift */; }; - 495340B020DE5F7200B0D3FF /* StatusItemView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 495340AF20DE5F7200B0D3FF /* StatusItemView.xib */; }; - 495340B320DE68C300B0D3FF /* StatusItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 495340B220DE68C300B0D3FF /* StatusItemView.swift */; }; - 495A44D320D267D000888A0A /* LaunchAtLogin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 495A44D220D267D000888A0A /* LaunchAtLogin.swift */; }; - 495BFB8821919B9800C8779D /* RemoteConfigManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 495BFB8721919B9800C8779D /* RemoteConfigManager.swift */; }; - 496322222AA5D89E00854231 /* UpdateExternalResourceAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 496322212AA5D89E00854231 /* UpdateExternalResourceAction.swift */; }; - 4966E9E32118153A00A391FB /* NSUserNotificationCenter+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4966E9E22118153A00A391FB /* NSUserNotificationCenter+Extension.swift */; }; - 496BDEE021196F1E00C5207F /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 496BDEDF21196F1E00C5207F /* Logger.swift */; }; - 49722FEF211F338B00650A41 /* FileEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49722FEA211F338B00650A41 /* FileEvent.swift */; }; - 49722FF0211F338B00650A41 /* EventStream.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49722FEB211F338B00650A41 /* EventStream.swift */; }; - 49722FF1211F338B00650A41 /* Witness.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49722FEC211F338B00650A41 /* Witness.swift */; }; - 49761DA721C9497000AE13EF /* dashboard in Resources */ = {isa = PBXBuildFile; fileRef = 49761DA621C9497000AE13EF /* dashboard */; }; - 49769FB427E9B3E400E3D664 /* LoginKitWrapper.m in Sources */ = {isa = PBXBuildFile; fileRef = 49D8276727E9B01700159D93 /* LoginKitWrapper.m */; }; - 4981C88B216BAE4A008CC14A /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 4981C88D216BAE4A008CC14A /* Localizable.strings */; }; - 4982F51F2344A216008804B0 /* Cgo+Convert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4982F51E2344A216008804B0 /* Cgo+Convert.swift */; }; - 49862FA0218418C600A1D5EC /* ClashRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49862F9F218418C600A1D5EC /* ClashRule.swift */; }; - 49870ADB2AA75DC7002B106B /* TerminalCleanUpAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49870ADA2AA75DC7002B106B /* TerminalCleanUpAction.swift */; }; - 49870ADD2AA76602002B106B /* UpdateConfigAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49870ADC2AA76602002B106B /* UpdateConfigAction.swift */; }; - 4989F98E20D0AE990001E564 /* sampleConfig.yaml in Resources */ = {isa = PBXBuildFile; fileRef = 4989F98D20D0AE990001E564 /* sampleConfig.yaml */; }; - 498BC2532929CC2A00CA8084 /* SettingTabViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 498BC2522929CC2A00CA8084 /* SettingTabViewController.swift */; }; - 498BC2552929CCAE00CA8084 /* GeneralSettingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 498BC2542929CCAE00CA8084 /* GeneralSettingViewController.swift */; }; - 4991D2302A564DDC00978143 /* SpeedUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4991D22F2A564DDC00978143 /* SpeedUtils.swift */; }; - 4991D2322A565E6A00978143 /* Combine+Ext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4991D2312A565E6A00978143 /* Combine+Ext.swift */; }; - 4994B5542A47C4FF00E595B9 /* NormalMenuItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4994B5532A47C4FF00E595B9 /* NormalMenuItemView.swift */; }; - 499976C821359F0400E7BF83 /* ClashWebViewContoller.swift in Sources */ = {isa = PBXBuildFile; fileRef = 499976C721359F0400E7BF83 /* ClashWebViewContoller.swift */; }; - 499A485522ED707300F6C675 /* RemoteConfigViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 499A485322ED707300F6C675 /* RemoteConfigViewController.swift */; }; - 499A485822ED715200F6C675 /* RemoteConfigModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 499A485722ED715200F6C675 /* RemoteConfigModel.swift */; }; - 499A485A22ED781100F6C675 /* RemoteConfigAddView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 499A485922ED781100F6C675 /* RemoteConfigAddView.xib */; }; - 499A485C22ED793C00F6C675 /* NSView+Nib.swift in Sources */ = {isa = PBXBuildFile; fileRef = 499A485B22ED793C00F6C675 /* NSView+Nib.swift */; }; - 499A485E22ED9B7C00F6C675 /* NSTableView+Reload.swift in Sources */ = {isa = PBXBuildFile; fileRef = 499A485D22ED9B7C00F6C675 /* NSTableView+Reload.swift */; }; - 499A486522EEA3FD00F6C675 /* Array+Safe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 499A486422EEA3FC00F6C675 /* Array+Safe.swift */; }; - 499ADAFD2498CC5900C488FE /* RemoteControlManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 499ADAFC2498CC5900C488FE /* RemoteControlManager.swift */; }; - 499ADAFF2498FC6D00C488FE /* ExternalControlViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 499ADAFE2498FC6D00C488FE /* ExternalControlViewController.swift */; }; - 49ABB749236B0F9E00535CD7 /* UnsafePointer+bridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49ABB748236B0F9E00535CD7 /* UnsafePointer+bridge.swift */; }; - 49B1086A216A356D0064FFCE /* String+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49B10869216A356D0064FFCE /* String+Extension.swift */; }; - 49B445162457CDF000B27E3E /* ClashStatusTool.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49B445152457CDF000B27E3E /* ClashStatusTool.swift */; }; - 49B4575D244F4A2A00463C39 /* PrivilegedHelperManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49B4575C244F4A2A00463C39 /* PrivilegedHelperManager.swift */; }; - 49B4575F244FD4D100463C39 /* PrivilegedHelperManager+Legacy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49B4575E244FD4D100463C39 /* PrivilegedHelperManager+Legacy.swift */; }; - 49BB31E7246853EA008A4CB0 /* ICloudManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49BB31E6246853EA008A4CB0 /* ICloudManager.swift */; }; - 49BC061C212931F4005A0FE7 /* AboutViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49BC061B212931F4005A0FE7 /* AboutViewController.swift */; }; - 49C9EF64223E78F5005D8B6A /* ClashProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49C9EF63223E78F5005D8B6A /* ClashProxy.swift */; }; - 49CCDA2A2A54F9AC00FF1E13 /* ClashWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49CCDA292A54F9AC00FF1E13 /* ClashWindowController.swift */; }; - 49CF3B2120CD7463001EBF94 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49CF3B2020CD7463001EBF94 /* AppDelegate.swift */; }; - 49CF3B2820CD7465001EBF94 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 49CF3B2620CD7465001EBF94 /* Main.storyboard */; }; - 49CF3B5C20CE8068001EBF94 /* ClashResourceManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49CF3B5B20CE8068001EBF94 /* ClashResourceManager.swift */; }; - 49CF3B6520CEE06C001EBF94 /* ConfigManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49CF3B6420CEE06C001EBF94 /* ConfigManager.swift */; }; - 49D176A72355FE680093DD7B /* NetworkChangeNotifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49D176A62355FE680093DD7B /* NetworkChangeNotifier.swift */; }; - 49D176A9235614340093DD7B /* ProxyGroupSpeedTestMenuItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49D176A8235614340093DD7B /* ProxyGroupSpeedTestMenuItem.swift */; }; - 49D176AB23575BB20093DD7B /* ProxyGroupMenuItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49D176AA23575BB20093DD7B /* ProxyGroupMenuItemView.swift */; }; - 49D223392A1DA5F10002FFCB /* SSIDSuspendTool.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49D223382A1DA5F10002FFCB /* SSIDSuspendTool.swift */; }; - 49D6A45229AEEC15006487EF /* StatusItemTool.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49D6A45129AEEC15006487EF /* StatusItemTool.swift */; }; - 49D6A45629AEEC55006487EF /* StatusItemViewProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49D6A45529AEEC55006487EF /* StatusItemViewProtocol.swift */; }; - 49FEC6692AD9369C00BAD9F5 /* Command.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49FEC6682AD9369C00BAD9F5 /* Command.swift */; }; - 8A2BBEA727A03ACB0081EBEF /* ProxySetting.sdef in Resources */ = {isa = PBXBuildFile; fileRef = 8A2BBEA627A03ACB0081EBEF /* ProxySetting.sdef */; }; - 8ACD21BB27A04C7800BC4632 /* ProxySettingCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8ACD21BA27A04C7800BC4632 /* ProxySettingCommand.swift */; }; - 8ACD21BD27A04ED500BC4632 /* ProxyModeChangeCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8ACD21BC27A04ED500BC4632 /* ProxyModeChangeCommand.swift */; }; - F910AA24240134AF00116E95 /* ProxyGroupMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = F910AA23240134AF00116E95 /* ProxyGroupMenu.swift */; }; - F915A4622366ADEF004840BE /* ClashConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = F915A4612366ADEF004840BE /* ClashConnection.swift */; }; - F9203A26236342820020D57D /* AppDelegate+..swift in Sources */ = {isa = PBXBuildFile; fileRef = F9203A25236342820020D57D /* AppDelegate+..swift */; }; - F92D0B24236BC12000575E15 /* SavedProxyModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F92D0B23236BC12000575E15 /* SavedProxyModel.swift */; }; - F92D0B2A236C759100575E15 /* NSTextField+Vibrancy.swift in Sources */ = {isa = PBXBuildFile; fileRef = F92D0B29236C759100575E15 /* NSTextField+Vibrancy.swift */; }; - F92D0B2C236C7C3600575E15 /* MenuItemBaseView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F92D0B2B236C7C3600575E15 /* MenuItemBaseView.swift */; }; - F92D0B2E236D35C000575E15 /* ProxyItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F92D0B2D236D35C000575E15 /* ProxyItemView.swift */; }; - F935B2F02307C52E009E4D33 /* com.metacubex.ClashX.ProxyConfigHelper in Copy Files */ = {isa = PBXBuildFile; fileRef = F9A7C0692306E874007163C7 /* com.metacubex.ClashX.ProxyConfigHelper */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; F935B2FA23083EE6009E4D33 /* ProxySettingTool.m in Sources */ = {isa = PBXBuildFile; fileRef = F935B2F923083EE6009E4D33 /* ProxySettingTool.m */; }; - F935B2FC23085515009E4D33 /* SystemProxyManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = F935B2FB23085515009E4D33 /* SystemProxyManager.swift */; }; - F939724C23A4B33500FE5A3F /* ClashProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = F939724B23A4B33500FE5A3F /* ClashProvider.swift */; }; - F939724E23A4DB0600FE5A3F /* DateFormatter+.swift in Sources */ = {isa = PBXBuildFile; fileRef = F939724D23A4DB0600FE5A3F /* DateFormatter+.swift */; }; - F976275C23634DF8000EDEFE /* LoginServiceKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = F976275B23634DF8000EDEFE /* LoginServiceKit.swift */; }; - F977FAAC2366790500C17F1F /* AutoUpgardeManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = F977FAAB2366790500C17F1F /* AutoUpgardeManager.swift */; }; - F977FAAE23669D6400C17F1F /* ConnectionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = F977FAAD23669D6400C17F1F /* ConnectionManager.swift */; }; - F9E754D0239CC21F00CEE7CC /* WebPortalManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9E754CF239CC21F00CEE7CC /* WebPortalManager.swift */; }; - F9E754D2239CC28D00CEE7CC /* NSAlert+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9E754D1239CC28D00CEE7CC /* NSAlert+Extension.swift */; }; - F9E8F34623A12B89002DE5E8 /* String+Encode.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9E8F34523A12B89002DE5E8 /* String+Encode.swift */; }; - F9FAB31E262BE04800DE02A6 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F9FAB31D262BE04800DE02A6 /* Images.xcassets */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -264,13 +150,6 @@ remoteGlobalIDString = F9A7C0682306E874007163C7; remoteInfo = ProxyConfigHelper; }; - F935B2EB2307B7CD009E4D33 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 49CF3B1520CD7463001EBF94 /* Project object */; - proxyType = 1; - remoteGlobalIDString = F9A7C0682306E874007163C7; - remoteInfo = ProxyConfigHelper; - }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -285,17 +164,6 @@ name = "Copy Files"; runOnlyForDeploymentPostprocessing = 0; }; - 663E4677213FCDC4006F11BB /* Copy Files */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = Contents/Library/LaunchServices; - dstSubfolderSpec = 1; - files = ( - F935B2F02307C52E009E4D33 /* com.metacubex.ClashX.ProxyConfigHelper in Copy Files */, - ); - name = "Copy Files"; - runOnlyForDeploymentPostprocessing = 0; - }; F9A7C0672306E874007163C7 /* Copy Files */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; @@ -397,7 +265,6 @@ 49BC061B212931F4005A0FE7 /* AboutViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutViewController.swift; sourceTree = ""; }; 49C9EF63223E78F5005D8B6A /* ClashProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClashProxy.swift; sourceTree = ""; }; 49CCDA292A54F9AC00FF1E13 /* ClashWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClashWindowController.swift; sourceTree = ""; }; - 49CF3B1D20CD7463001EBF94 /* ClashX Meta.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "ClashX Meta.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 49CF3B2020CD7463001EBF94 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 49CF3B2720CD7465001EBF94 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 49CF3B2920CD7465001EBF94 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -447,42 +314,27 @@ buildActionMask = 2147483647; files = ( 01F336242AD10D0B0048AF77 /* RxSwift in Frameworks */, + 01BCDAB32C9ECB560028FA94 /* SwiftUIIntrospect in Frameworks */, 01F336252AD10D0B0048AF77 /* SwiftyJSON in Frameworks */, 01F336262AD10D0B0048AF77 /* Gzip in Frameworks */, 01F336272AD10D0B0048AF77 /* CocoaLumberjackSwift in Frameworks */, + 01BCDAB52C9ECB560028FA94 /* SwiftUIIntrospect-Dynamic in Frameworks */, + 01BCDAAE2C9ECB3A0028FA94 /* DSFSparkline-shared in Frameworks */, 01F336282AD10D0B0048AF77 /* Starscream in Frameworks */, 01F336292AD10D0B0048AF77 /* PromiseKit in Frameworks */, + 01BCDAAC2C9ECB3A0028FA94 /* DSFSparkline in Frameworks */, 01F3362A2AD10D0B0048AF77 /* KeyboardShortcuts in Frameworks */, 01F3362B2AD10D0B0048AF77 /* Alamofire in Frameworks */, 01F3362C2AD10D0B0048AF77 /* CocoaLumberjack in Frameworks */, 01F3362D2AD10D0B0048AF77 /* Yams in Frameworks */, 01F3362E2AD10D0B0048AF77 /* RxCocoa in Frameworks */, - 01F336482AD139CC0048AF77 /* ClashX Dashboard in Frameworks */, + 01BCDAB72C9ECB560028FA94 /* SwiftUIIntrospect-Static in Frameworks */, 01F3362F2AD10D0B0048AF77 /* FlexibleDiff in Frameworks */, + 01BCDAB02C9ECB3A0028FA94 /* DSFSparkline-static in Frameworks */, 01F336302AD10D0B0048AF77 /* libresolv.tbd in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; - 49CF3B1A20CD7463001EBF94 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 015B976F2A4F2F6C00F9FA4D /* RxSwift in Frameworks */, - 015B97722A4F2F9900F9FA4D /* SwiftyJSON in Frameworks */, - 015B97802A4F30EA00F9FA4D /* Gzip in Frameworks */, - 015B97772A4F2FC600F9FA4D /* CocoaLumberjackSwift in Frameworks */, - 015B977A2A4F306800F9FA4D /* Starscream in Frameworks */, - 015B97862A4F31AF00F9FA4D /* PromiseKit in Frameworks */, - 4905A2C82A2058D400AEDA2E /* KeyboardShortcuts in Frameworks */, - 015B976A2A4F2F4500F9FA4D /* Alamofire in Frameworks */, - 015B97752A4F2FC600F9FA4D /* CocoaLumberjack in Frameworks */, - 015B97832A4F311300F9FA4D /* Yams in Frameworks */, - 015B976D2A4F2F6C00F9FA4D /* RxCocoa in Frameworks */, - 015B977D2A4F30B900F9FA4D /* FlexibleDiff in Frameworks */, - 4908087B29F8F405007A4944 /* libresolv.tbd in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; F9A7C0662306E874007163C7 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -493,6 +345,13 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 01BCDAA92C9EC9DD0028FA94 /* Dashboard */ = { + isa = PBXGroup; + children = ( + ); + path = Dashboard; + sourceTree = ""; + }; 4913C82021157CEB00F6B87C /* Macro */ = { isa = PBXGroup; children = ( @@ -711,7 +570,6 @@ 49CF3B1E20CD7463001EBF94 /* Products */ = { isa = PBXGroup; children = ( - 49CF3B1D20CD7463001EBF94 /* ClashX Meta.app */, F9A7C0692306E874007163C7 /* com.metacubex.ClashX.ProxyConfigHelper */, 01F336442AD10D0B0048AF77 /* ClashX Meta.app */, ); @@ -734,6 +592,7 @@ 49761DA521C9490400AE13EF /* Resources */, 49CF3B3A20CD783A001EBF94 /* Support Files */, 49CF3B2020CD7463001EBF94 /* AppDelegate.swift */, + 01BCDAA92C9EC9DD0028FA94 /* Dashboard */, 49CF3B2620CD7465001EBF94 /* Main.storyboard */, 49CF3B2920CD7465001EBF94 /* Info.plist */, 01D567DF2AD1562700CDA0AE /* ClashX Meta SwiftUI-Info.plist */, @@ -804,9 +663,9 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ - 01F335B32AD10D0B0048AF77 /* ClashX Meta SwiftUI */ = { + 01F335B32AD10D0B0048AF77 /* ClashX Meta */ = { isa = PBXNativeTarget; - buildConfigurationList = 01F336412AD10D0B0048AF77 /* Build configuration list for PBXNativeTarget "ClashX Meta SwiftUI" */; + buildConfigurationList = 01F336412AD10D0B0048AF77 /* Build configuration list for PBXNativeTarget "ClashX Meta" */; buildPhases = ( 01F335CC2AD10D0B0048AF77 /* Sources */, 01F336232AD10D0B0048AF77 /* Frameworks */, @@ -818,7 +677,7 @@ dependencies = ( 01F335B42AD10D0B0048AF77 /* PBXTargetDependency */, ); - name = "ClashX Meta SwiftUI"; + name = "ClashX Meta"; packageProductDependencies = ( 01F335B62AD10D0B0048AF77 /* KeyboardShortcuts */, 01F335B82AD10D0B0048AF77 /* Alamofire */, @@ -832,45 +691,17 @@ 01F335C62AD10D0B0048AF77 /* Gzip */, 01F335C82AD10D0B0048AF77 /* Yams */, 01F335CA2AD10D0B0048AF77 /* PromiseKit */, - 01F336472AD139CC0048AF77 /* ClashX Dashboard */, + 01BCDAAB2C9ECB3A0028FA94 /* DSFSparkline */, + 01BCDAAD2C9ECB3A0028FA94 /* DSFSparkline-shared */, + 01BCDAAF2C9ECB3A0028FA94 /* DSFSparkline-static */, + 01BCDAB22C9ECB560028FA94 /* SwiftUIIntrospect */, + 01BCDAB42C9ECB560028FA94 /* SwiftUIIntrospect-Dynamic */, + 01BCDAB62C9ECB560028FA94 /* SwiftUIIntrospect-Static */, ); productName = ClashX; productReference = 01F336442AD10D0B0048AF77 /* ClashX Meta.app */; productType = "com.apple.product-type.application"; }; - 49CF3B1C20CD7463001EBF94 /* ClashX Meta */ = { - isa = PBXNativeTarget; - buildConfigurationList = 49CF3B2D20CD7465001EBF94 /* Build configuration list for PBXNativeTarget "ClashX Meta" */; - buildPhases = ( - 49CF3B1920CD7463001EBF94 /* Sources */, - 49CF3B1A20CD7463001EBF94 /* Frameworks */, - 49CF3B1B20CD7463001EBF94 /* Resources */, - 663E4677213FCDC4006F11BB /* Copy Files */, - ); - buildRules = ( - ); - dependencies = ( - F935B2EC2307B7CD009E4D33 /* PBXTargetDependency */, - ); - name = "ClashX Meta"; - packageProductDependencies = ( - 4905A2C72A2058D400AEDA2E /* KeyboardShortcuts */, - 015B97692A4F2F4500F9FA4D /* Alamofire */, - 015B976C2A4F2F6C00F9FA4D /* RxCocoa */, - 015B976E2A4F2F6C00F9FA4D /* RxSwift */, - 015B97712A4F2F9900F9FA4D /* SwiftyJSON */, - 015B97742A4F2FC600F9FA4D /* CocoaLumberjack */, - 015B97762A4F2FC600F9FA4D /* CocoaLumberjackSwift */, - 015B97792A4F306800F9FA4D /* Starscream */, - 015B977C2A4F30B900F9FA4D /* FlexibleDiff */, - 015B977F2A4F30EA00F9FA4D /* Gzip */, - 015B97822A4F311300F9FA4D /* Yams */, - 015B97852A4F31AF00F9FA4D /* PromiseKit */, - ); - productName = ClashX; - productReference = 49CF3B1D20CD7463001EBF94 /* ClashX Meta.app */; - productType = "com.apple.product-type.application"; - }; F9A7C0682306E874007163C7 /* com.metacubex.ClashX.ProxyConfigHelper */ = { isa = PBXNativeTarget; buildConfigurationList = F9A7C06D2306E874007163C7 /* Build configuration list for PBXNativeTarget "com.metacubex.ClashX.ProxyConfigHelper" */; @@ -899,18 +730,6 @@ LastUpgradeCheck = 1430; ORGANIZATIONNAME = west2online; TargetAttributes = { - 49CF3B1C20CD7463001EBF94 = { - CreatedOnToolsVersion = 9.4; - LastSwiftMigration = 1020; - SystemCapabilities = { - com.apple.HardenedRuntime = { - enabled = 1; - }; - com.apple.Sandbox = { - enabled = 0; - }; - }; - }; F9A7C0682306E874007163C7 = { CreatedOnToolsVersion = 10.3; LastSwiftMigration = 1340; @@ -939,15 +758,15 @@ 015B977E2A4F30EA00F9FA4D /* XCRemoteSwiftPackageReference "GzipSwift" */, 015B97812A4F311300F9FA4D /* XCRemoteSwiftPackageReference "Yams" */, 015B97842A4F31AE00F9FA4D /* XCRemoteSwiftPackageReference "PromiseKit" */, - 01F336462AD139CC0048AF77 /* XCRemoteSwiftPackageReference "ClashX-Dashboard" */, + 01BCDAAA2C9ECB3A0028FA94 /* XCRemoteSwiftPackageReference "DSFSparkline" */, + 01BCDAB12C9ECB560028FA94 /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */, ); productRefGroup = 49CF3B1E20CD7463001EBF94 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( - 49CF3B1C20CD7463001EBF94 /* ClashX Meta */, + 01F335B32AD10D0B0048AF77 /* ClashX Meta */, F9A7C0682306E874007163C7 /* com.metacubex.ClashX.ProxyConfigHelper */, - 01F335B32AD10D0B0048AF77 /* ClashX Meta SwiftUI */, ); }; /* End PBXProject section */ @@ -974,27 +793,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 49CF3B1B20CD7463001EBF94 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 01B009AF2854533300B93618 /* geosite.dat.gz in Resources */, - 49761DA721C9497000AE13EF /* dashboard in Resources */, - 4981C88B216BAE4A008CC14A /* Localizable.strings in Resources */, - 8A2BBEA727A03ACB0081EBEF /* ProxySetting.sdef in Resources */, - 01D567E32AD158B600CDA0AE /* MetaPrefs.storyboard in Resources */, - F9FAB31E262BE04800DE02A6 /* Images.xcassets in Resources */, - 01E33AB529B5C5E400FD1006 /* menu_icon@2x.png in Resources */, - 495340B020DE5F7200B0D3FF /* StatusItemView.xib in Resources */, - 01B009AE2854533300B93618 /* geoip.dat.gz in Resources */, - 49CF3B2820CD7465001EBF94 /* Main.storyboard in Resources */, - 499A485A22ED781100F6C675 /* RemoteConfigAddView.xib in Resources */, - 4989F98E20D0AE990001E564 /* sampleConfig.yaml in Resources */, - 01C1462A28962E4E00346AF3 /* com.metacubex.ClashX.ProxyConfigHelper.meta.gz in Resources */, - 01B2274B29B845F100FE35C9 /* country.mmdb.gz in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -1096,104 +894,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 49CF3B1920CD7463001EBF94 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 49ABB749236B0F9E00535CD7 /* UnsafePointer+bridge.swift in Sources */, - 499A485522ED707300F6C675 /* RemoteConfigViewController.swift in Sources */, - 01CA6BC02B6A1B3100E386D6 /* MetaServer.swift in Sources */, - 49D6A45229AEEC15006487EF /* StatusItemTool.swift in Sources */, - 49CF3B5C20CE8068001EBF94 /* ClashResourceManager.swift in Sources */, - 4952C3D02117027C004A4FA8 /* ConfigFileManager.swift in Sources */, - 49BC061C212931F4005A0FE7 /* AboutViewController.swift in Sources */, - 4949D154213242F600EF85E6 /* Paths.swift in Sources */, - 275348502A3082FD0077B458 /* TunModeSettingCommand.swift in Sources */, - 01B1CB0A2A2E20C10073EA34 /* DashboardManager.swift in Sources */, - 499ADAFF2498FC6D00C488FE /* ExternalControlViewController.swift in Sources */, - F939724E23A4DB0600FE5A3F /* DateFormatter+.swift in Sources */, - F915A4622366ADEF004840BE /* ClashConnection.swift in Sources */, - F977FAAE23669D6400C17F1F /* ConnectionManager.swift in Sources */, - 495340B320DE68C300B0D3FF /* StatusItemView.swift in Sources */, - F935B2FC23085515009E4D33 /* SystemProxyManager.swift in Sources */, - 495A44D320D267D000888A0A /* LaunchAtLogin.swift in Sources */, - 4991D2322A565E6A00978143 /* Combine+Ext.swift in Sources */, - 49281C802A1F01FA00F60935 /* DebugSettingViewController.swift in Sources */, - 49CCDA2A2A54F9AC00FF1E13 /* ClashWindowController.swift in Sources */, - 4929F67F258CE04700A435F6 /* Settings.swift in Sources */, - 49D6A45629AEEC55006487EF /* StatusItemViewProtocol.swift in Sources */, - 493AEAE3221AE3420016FE98 /* AppVersionUtil.swift in Sources */, - 49CF3B2120CD7463001EBF94 /* AppDelegate.swift in Sources */, - 4991D2302A564DDC00978143 /* SpeedUtils.swift in Sources */, - 496BDEE021196F1E00C5207F /* Logger.swift in Sources */, - 01D567E52AD158B600CDA0AE /* MetaPrefsViewController.swift in Sources */, - 4905A2CA2A20841B00AEDA2E /* NSView+Layout.swift in Sources */, - 49722FEF211F338B00650A41 /* FileEvent.swift in Sources */, - 016BEAB029D80103001586C5 /* AlphaMetaDownloader.swift in Sources */, - 49D176A72355FE680093DD7B /* NetworkChangeNotifier.swift in Sources */, - 4905A2C52A2058B000AEDA2E /* GlobalShortCutViewController.swift in Sources */, - 4913C82321157D0200F6B87C /* Notification.swift in Sources */, - 015F1E91288E42A50052B20A /* ClashMetaConfig.swift in Sources */, - 8ACD21BD27A04ED500BC4632 /* ProxyModeChangeCommand.swift in Sources */, - 498BC2552929CCAE00CA8084 /* GeneralSettingViewController.swift in Sources */, - 49228457270AADE20027A4B6 /* RemoteConfigUpdateIntervalSettingView.swift in Sources */, - F9203A26236342820020D57D /* AppDelegate+..swift in Sources */, - 499A485C22ED793C00F6C675 /* NSView+Nib.swift in Sources */, - 493A9F282453E60400D35296 /* ProxyDelayHistoryMenu.swift in Sources */, - 01E33AB229B5BF4200FD1006 /* NSColor+Extension.swift in Sources */, - 492C4871210EF62E004554A0 /* ClashConfig.swift in Sources */, - 492C4869210EE6B9004554A0 /* ApiRequest.swift in Sources */, - 49CF3B6520CEE06C001EBF94 /* ConfigManager.swift in Sources */, - 49870ADB2AA75DC7002B106B /* TerminalCleanUpAction.swift in Sources */, - F9E754D0239CC21F00CEE7CC /* WebPortalManager.swift in Sources */, - 018F88F9286DD0CB004DD0F7 /* DualTitleMenuItem.swift in Sources */, - 495BFB8821919B9800C8779D /* RemoteConfigManager.swift in Sources */, - 4982F51F2344A216008804B0 /* Cgo+Convert.swift in Sources */, - 8ACD21BB27A04C7800BC4632 /* ProxySettingCommand.swift in Sources */, - 49722FF1211F338B00650A41 /* Witness.swift in Sources */, - 49722FF0211F338B00650A41 /* EventStream.swift in Sources */, - 499A486522EEA3FD00F6C675 /* Array+Safe.swift in Sources */, - F92D0B24236BC12000575E15 /* SavedProxyModel.swift in Sources */, - 49FEC6692AD9369C00BAD9F5 /* Command.swift in Sources */, - F92D0B2A236C759100575E15 /* NSTextField+Vibrancy.swift in Sources */, - 49D223392A1DA5F10002FFCB /* SSIDSuspendTool.swift in Sources */, - 01FBC6302B9C2B0800810BFF /* ClashProcess.swift in Sources */, - F910AA24240134AF00116E95 /* ProxyGroupMenu.swift in Sources */, - 4952C3BF2115C7CA004A4FA8 /* MenuItemFactory.swift in Sources */, - 015F1E92288E60D30052B20A /* MetaTask.swift in Sources */, - F977FAAC2366790500C17F1F /* AutoUpgardeManager.swift in Sources */, - 499A485822ED715200F6C675 /* RemoteConfigModel.swift in Sources */, - 49D176AB23575BB20093DD7B /* ProxyGroupMenuItemView.swift in Sources */, - 49B4575D244F4A2A00463C39 /* PrivilegedHelperManager.swift in Sources */, - 49B4575F244FD4D100463C39 /* PrivilegedHelperManager+Legacy.swift in Sources */, - 4966E9E32118153A00A391FB /* NSUserNotificationCenter+Extension.swift in Sources */, - F9E754D2239CC28D00CEE7CC /* NSAlert+Extension.swift in Sources */, - 499976C821359F0400E7BF83 /* ClashWebViewContoller.swift in Sources */, - 49D176A9235614340093DD7B /* ProxyGroupSpeedTestMenuItem.swift in Sources */, - F976275C23634DF8000EDEFE /* LoginServiceKit.swift in Sources */, - 4994B5542A47C4FF00E595B9 /* NormalMenuItemView.swift in Sources */, - F92D0B2E236D35C000575E15 /* ProxyItemView.swift in Sources */, - 01EF33632B98D71600D1DBD9 /* ProxyConfigRemoteProcessProtocol.swift in Sources */, - 49BB31E7246853EA008A4CB0 /* ICloudManager.swift in Sources */, - 49B1086A216A356D0064FFCE /* String+Extension.swift in Sources */, - F92D0B2C236C7C3600575E15 /* MenuItemBaseView.swift in Sources */, - 499ADAFD2498CC5900C488FE /* RemoteControlManager.swift in Sources */, - 49769FB427E9B3E400E3D664 /* LoginKitWrapper.m in Sources */, - 493AEAE5221AE7230016FE98 /* ProxyMenuItem.swift in Sources */, - 496322222AA5D89E00854231 /* UpdateExternalResourceAction.swift in Sources */, - 49870ADD2AA76602002B106B /* UpdateConfigAction.swift in Sources */, - 499A485E22ED9B7C00F6C675 /* NSTableView+Reload.swift in Sources */, - F939724C23A4B33500FE5A3F /* ClashProvider.swift in Sources */, - 498BC2532929CC2A00CA8084 /* SettingTabViewController.swift in Sources */, - 49862FA0218418C600A1D5EC /* ClashRule.swift in Sources */, - 01943259287D19BC008CC51A /* ClashRuleProvider.swift in Sources */, - 49C9EF64223E78F5005D8B6A /* ClashProxy.swift in Sources */, - 4929F684258CE07500A435F6 /* UserDefaultWrapper.swift in Sources */, - F9E8F34623A12B89002DE5E8 /* String+Encode.swift in Sources */, - 49B445162457CDF000B27E3E /* ClashStatusTool.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; F9A7C0652306E874007163C7 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -1217,11 +917,6 @@ target = F9A7C0682306E874007163C7 /* com.metacubex.ClashX.ProxyConfigHelper */; targetProxy = 01F335B52AD10D0B0048AF77 /* PBXContainerItemProxy */; }; - F935B2EC2307B7CD009E4D33 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = F9A7C0682306E874007163C7 /* com.metacubex.ClashX.ProxyConfigHelper */; - targetProxy = F935B2EB2307B7CD009E4D33 /* PBXContainerItemProxy */; - }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ @@ -1459,96 +1154,6 @@ }; name = Release; }; - 49CF3B2E20CD7465001EBF94 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ARCHS = "$(ARCHS_STANDARD)"; - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CODE_SIGN_ENTITLEMENTS = ClashX/ClashX.entitlements; - CODE_SIGN_IDENTITY = "-"; - CODE_SIGN_STYLE = Manual; - COMBINE_HIDPI_IMAGES = YES; - COMPRESS_PNG_FILES = YES; - CURRENT_PROJECT_VERSION = 0; - DEAD_CODE_STRIPPING = YES; - DEVELOPMENT_TEAM = ""; - ENABLE_HARDENED_RUNTIME = YES; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/ClashX/Support\\ Files", - "$(PROJECT_DIR)/ClashX/Support\\ Files", - "$(PROJECT_DIR)/ClashX/Support\\ Files", - "$(PROJECT_DIR)/ClashX/Support\\ Files", - ); - INFOPLIST_FILE = ClashX/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/../Frameworks", - "@loader_path/../Frameworks", - ); - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/ClashX", - ); - MACOSX_DEPLOYMENT_TARGET = 10.14; - MARKETING_VERSION = v1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.metacubex.ClashX.meta; - PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; - STRIP_PNG_TEXT = YES; - SWIFT_OBJC_BRIDGING_HEADER = "ClashX/ClashX-Bridging-Header.h"; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; - }; - name = Debug; - }; - 49CF3B2F20CD7465001EBF94 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ARCHS = "$(ARCHS_STANDARD)"; - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CODE_SIGN_ENTITLEMENTS = ClashX/ClashX.entitlements; - CODE_SIGN_IDENTITY = "-"; - CODE_SIGN_STYLE = Manual; - COMBINE_HIDPI_IMAGES = YES; - COMPRESS_PNG_FILES = YES; - COPY_PHASE_STRIP = YES; - CURRENT_PROJECT_VERSION = 0; - DEAD_CODE_STRIPPING = YES; - DEPLOYMENT_POSTPROCESSING = YES; - DEVELOPMENT_TEAM = ""; - ENABLE_HARDENED_RUNTIME = YES; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/ClashX/Support\\ Files", - "$(PROJECT_DIR)/ClashX/Support\\ Files", - "$(PROJECT_DIR)/ClashX/Support\\ Files", - "$(PROJECT_DIR)/ClashX/Support\\ Files", - ); - INFOPLIST_FILE = ClashX/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/../Frameworks", - "@loader_path/../Frameworks", - ); - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/ClashX", - ); - MACOSX_DEPLOYMENT_TARGET = 10.14; - MARKETING_VERSION = v1.0; - OTHER_CODE_SIGN_FLAGS = "--timestamp"; - PRODUCT_BUNDLE_IDENTIFIER = com.metacubex.ClashX.meta; - PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; - STRIP_PNG_TEXT = YES; - SWIFT_OBJC_BRIDGING_HEADER = "ClashX/ClashX-Bridging-Header.h"; - SWIFT_VERSION = 5.0; - }; - name = Release; - }; F9A7C06E2306E874007163C7 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -1637,7 +1242,7 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ - 01F336412AD10D0B0048AF77 /* Build configuration list for PBXNativeTarget "ClashX Meta SwiftUI" */ = { + 01F336412AD10D0B0048AF77 /* Build configuration list for PBXNativeTarget "ClashX Meta" */ = { isa = XCConfigurationList; buildConfigurations = ( 01F336422AD10D0B0048AF77 /* Debug */, @@ -1655,15 +1260,6 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 49CF3B2D20CD7465001EBF94 /* Build configuration list for PBXNativeTarget "ClashX Meta" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 49CF3B2E20CD7465001EBF94 /* Debug */, - 49CF3B2F20CD7465001EBF94 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; F9A7C06D2306E874007163C7 /* Build configuration list for PBXNativeTarget "com.metacubex.ClashX.ProxyConfigHelper" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -1748,6 +1344,22 @@ minimumVersion = 8.0.0; }; }; + 01BCDAAA2C9ECB3A0028FA94 /* XCRemoteSwiftPackageReference "DSFSparkline" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/dagronf/DSFSparkline"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 6.0.2; + }; + }; + 01BCDAB12C9ECB560028FA94 /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/siteline/SwiftUI-Introspect"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 1.3.0; + }; + }; 01F335B72AD10D0B0048AF77 /* XCRemoteSwiftPackageReference "KeyboardShortcuts" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/sindresorhus/KeyboardShortcuts.git"; @@ -1828,14 +1440,6 @@ minimumVersion = 8.0.0; }; }; - 01F336462AD139CC0048AF77 /* XCRemoteSwiftPackageReference "ClashX-Dashboard" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/mrFq1/ClashX-Dashboard"; - requirement = { - branch = dev; - kind = branch; - }; - }; 4905A2C62A2058D400AEDA2E /* XCRemoteSwiftPackageReference "KeyboardShortcuts" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/sindresorhus/KeyboardShortcuts.git"; @@ -1847,60 +1451,35 @@ /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ - 015B97692A4F2F4500F9FA4D /* Alamofire */ = { + 01BCDAAB2C9ECB3A0028FA94 /* DSFSparkline */ = { isa = XCSwiftPackageProductDependency; - package = 015B97682A4F2F4500F9FA4D /* XCRemoteSwiftPackageReference "Alamofire" */; - productName = Alamofire; + package = 01BCDAAA2C9ECB3A0028FA94 /* XCRemoteSwiftPackageReference "DSFSparkline" */; + productName = DSFSparkline; }; - 015B976C2A4F2F6C00F9FA4D /* RxCocoa */ = { + 01BCDAAD2C9ECB3A0028FA94 /* DSFSparkline-shared */ = { isa = XCSwiftPackageProductDependency; - package = 015B976B2A4F2F6C00F9FA4D /* XCRemoteSwiftPackageReference "RxSwift" */; - productName = RxCocoa; + package = 01BCDAAA2C9ECB3A0028FA94 /* XCRemoteSwiftPackageReference "DSFSparkline" */; + productName = "DSFSparkline-shared"; }; - 015B976E2A4F2F6C00F9FA4D /* RxSwift */ = { + 01BCDAAF2C9ECB3A0028FA94 /* DSFSparkline-static */ = { isa = XCSwiftPackageProductDependency; - package = 015B976B2A4F2F6C00F9FA4D /* XCRemoteSwiftPackageReference "RxSwift" */; - productName = RxSwift; + package = 01BCDAAA2C9ECB3A0028FA94 /* XCRemoteSwiftPackageReference "DSFSparkline" */; + productName = "DSFSparkline-static"; }; - 015B97712A4F2F9900F9FA4D /* SwiftyJSON */ = { + 01BCDAB22C9ECB560028FA94 /* SwiftUIIntrospect */ = { isa = XCSwiftPackageProductDependency; - package = 015B97702A4F2F9900F9FA4D /* XCRemoteSwiftPackageReference "SwiftyJSON" */; - productName = SwiftyJSON; - }; - 015B97742A4F2FC600F9FA4D /* CocoaLumberjack */ = { - isa = XCSwiftPackageProductDependency; - package = 015B97732A4F2FC600F9FA4D /* XCRemoteSwiftPackageReference "CocoaLumberjack" */; - productName = CocoaLumberjack; - }; - 015B97762A4F2FC600F9FA4D /* CocoaLumberjackSwift */ = { - isa = XCSwiftPackageProductDependency; - package = 015B97732A4F2FC600F9FA4D /* XCRemoteSwiftPackageReference "CocoaLumberjack" */; - productName = CocoaLumberjackSwift; - }; - 015B97792A4F306800F9FA4D /* Starscream */ = { - isa = XCSwiftPackageProductDependency; - package = 015B97782A4F306800F9FA4D /* XCRemoteSwiftPackageReference "Starscream" */; - productName = Starscream; + package = 01BCDAB12C9ECB560028FA94 /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */; + productName = SwiftUIIntrospect; }; - 015B977C2A4F30B900F9FA4D /* FlexibleDiff */ = { + 01BCDAB42C9ECB560028FA94 /* SwiftUIIntrospect-Dynamic */ = { isa = XCSwiftPackageProductDependency; - package = 015B977B2A4F30B900F9FA4D /* XCRemoteSwiftPackageReference "FlexibleDiff" */; - productName = FlexibleDiff; + package = 01BCDAB12C9ECB560028FA94 /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */; + productName = "SwiftUIIntrospect-Dynamic"; }; - 015B977F2A4F30EA00F9FA4D /* Gzip */ = { + 01BCDAB62C9ECB560028FA94 /* SwiftUIIntrospect-Static */ = { isa = XCSwiftPackageProductDependency; - package = 015B977E2A4F30EA00F9FA4D /* XCRemoteSwiftPackageReference "GzipSwift" */; - productName = Gzip; - }; - 015B97822A4F311300F9FA4D /* Yams */ = { - isa = XCSwiftPackageProductDependency; - package = 015B97812A4F311300F9FA4D /* XCRemoteSwiftPackageReference "Yams" */; - productName = Yams; - }; - 015B97852A4F31AF00F9FA4D /* PromiseKit */ = { - isa = XCSwiftPackageProductDependency; - package = 015B97842A4F31AE00F9FA4D /* XCRemoteSwiftPackageReference "PromiseKit" */; - productName = PromiseKit; + package = 01BCDAB12C9ECB560028FA94 /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */; + productName = "SwiftUIIntrospect-Static"; }; 01F335B62AD10D0B0048AF77 /* KeyboardShortcuts */ = { isa = XCSwiftPackageProductDependency; @@ -1962,16 +1541,6 @@ package = 01F335CB2AD10D0B0048AF77 /* XCRemoteSwiftPackageReference "PromiseKit" */; productName = PromiseKit; }; - 01F336472AD139CC0048AF77 /* ClashX Dashboard */ = { - isa = XCSwiftPackageProductDependency; - package = 01F336462AD139CC0048AF77 /* XCRemoteSwiftPackageReference "ClashX-Dashboard" */; - productName = "ClashX Dashboard"; - }; - 4905A2C72A2058D400AEDA2E /* KeyboardShortcuts */ = { - isa = XCSwiftPackageProductDependency; - package = 4905A2C62A2058D400AEDA2E /* XCRemoteSwiftPackageReference "KeyboardShortcuts" */; - productName = KeyboardShortcuts; - }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 49CF3B1520CD7463001EBF94 /* Project object */; diff --git a/ClashX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ClashX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 3002142c9..e6ed012b2 100644 --- a/ClashX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/ClashX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "04a3b3bc2bf53686240dbd5a590ed7d847103b4a63a3a4cb1892d2b9b92e3a52", + "originHash" : "79f29dc60a8a95688aa73c0cce32f5496ed9744c56a5d38efc20f94aacd73360", "pins" : [ { "identity" : "alamofire", @@ -10,15 +10,6 @@ "version" : "5.9.1" } }, - { - "identity" : "clashx-dashboard", - "kind" : "remoteSourceControl", - "location" : "https://github.com/mrFq1/ClashX-Dashboard", - "state" : { - "branch" : "dev", - "revision" : "19da905b9261e7c47c5e36d697c466fc2fb76158" - } - }, { "identity" : "cocoalumberjack", "kind" : "remoteSourceControl", diff --git a/ClashX.xcodeproj/xcshareddata/xcschemes/ClashX Meta SwiftUI.xcscheme b/ClashX.xcodeproj/xcshareddata/xcschemes/ClashX Meta SwiftUI.xcscheme deleted file mode 100644 index 5ad8c3342..000000000 --- a/ClashX.xcodeproj/xcshareddata/xcschemes/ClashX Meta SwiftUI.xcscheme +++ /dev/null @@ -1,78 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/ClashX.xcodeproj/xcshareddata/xcschemes/ClashX Meta.xcscheme b/ClashX.xcodeproj/xcshareddata/xcschemes/ClashX Meta.xcscheme index d067dba76..5ae0d46d0 100644 --- a/ClashX.xcodeproj/xcshareddata/xcschemes/ClashX Meta.xcscheme +++ b/ClashX.xcodeproj/xcshareddata/xcschemes/ClashX Meta.xcscheme @@ -14,7 +14,7 @@ buildForAnalyzing = "YES"> @@ -44,7 +44,7 @@ runnableDebuggingMode = "0"> @@ -61,7 +61,7 @@ runnableDebuggingMode = "0"> diff --git a/ClashX/ViewControllers/DashboardManager.swift b/ClashX/ViewControllers/DashboardManager.swift index ba20f9370..6eaf6b925 100644 --- a/ClashX/ViewControllers/DashboardManager.swift +++ b/ClashX/ViewControllers/DashboardManager.swift @@ -7,9 +7,6 @@ import Cocoa import RxSwift -#if SwiftUI_Version -import ClashX_Dashboard -#endif class DashboardManager: NSObject { @@ -19,8 +16,6 @@ class DashboardManager: NSObject { override init() { } - -#if SwiftUI_Version var useSwiftUI: Bool { get { return ConfigManager.useSwiftUIDashboard @@ -37,9 +32,6 @@ class DashboardManager: NSObject { } } var dashboardWindowController: DashboardWindowController? -#else - let useSwiftUI = false -#endif private var disposables = [Disposable]() @@ -47,7 +39,6 @@ class DashboardManager: NSObject { var clashWebWindowController: ClashWebViewWindowController? func show(_ sender: NSMenuItem?) { -#if SwiftUI_Version initNotifications() if useSwiftUI { @@ -57,9 +48,6 @@ class DashboardManager: NSObject { dashboardWindowController = nil showWebWindow(sender) } -#else - showWebWindow(sender) -#endif } func showWebWindow(_ sender: NSMenuItem?) { @@ -85,7 +73,6 @@ class DashboardManager: NSObject { } } -#if SwiftUI_Version extension DashboardManager { func showSwiftUIWindow(_ sender: NSMenuItem?) { if dashboardWindowController == nil { @@ -118,4 +105,3 @@ extension DashboardManager { } } -#endif diff --git a/ClashX/ViewControllers/Settings/MetaPrefsViewController.swift b/ClashX/ViewControllers/Settings/MetaPrefsViewController.swift index 3ccfc86a4..5841c9719 100644 --- a/ClashX/ViewControllers/Settings/MetaPrefsViewController.swift +++ b/ClashX/ViewControllers/Settings/MetaPrefsViewController.swift @@ -44,10 +44,8 @@ class MetaPrefsViewController: NSViewController { @IBAction func switchDashboard(_ sender: NSButton) { switch sender { -#if SwiftUI_Version case useSwiftuiButton: DashboardManager.shared.useSwiftUI = sender.state == .on -#endif case useYacdButton: ConfigManager.useYacdDashboard = sender.state == .on case useXDButton: @@ -152,11 +150,8 @@ class MetaPrefsViewController: NSViewController { useYacdButton.state = useYacd ? .on : .off useXDButton.state = useYacd ? .off : .on -#if SwiftUI_Version + useSwiftuiButton.isEnabled = true -#else - useSwiftuiButton.isEnabled = false -#endif useYacdButton.isEnabled = !useSwiftUI useXDButton.isEnabled = !useSwiftUI } From 22bf5fd8303a193210d55ac509efa5e19895b949 Mon Sep 17 00:00:00 2001 From: mrFq1 <1xxbx0il0@mozmail.com> Date: Sat, 21 Sep 2024 18:54:40 +0800 Subject: [PATCH 07/14] feat: swiftui dashboard --- ClashX.xcodeproj/project.pbxproj | 270 ++++++++++++++- ClashX/Dashboard/DashboardViewContoller.swift | 304 +++++++++++++++++ .../Models/DBConnectionSnapShot.swift | 143 ++++++++ .../Dashboard/Models/DBProviderStorage.swift | 108 ++++++ ClashX/Dashboard/Models/DBProxyStorage.swift | 120 +++++++ ClashX/Dashboard/NotificationNames.swift | 17 + ClashX/Dashboard/ToolbarStore.swift | 17 + .../Views/APISetting/APIServerItem.swift | 48 +++ .../Views/APISetting/APISettingView.swift | 81 +++++ .../APISetting/ClashServerAppStorage.swift | 36 ++ .../Views/APISetting/ProgressButton.swift | 70 ++++ ClashX/Dashboard/Views/ArrayExtensions.swift | 39 +++ .../Views/ClashApiDatasStorage.swift | 164 +++++++++ ClashX/Dashboard/Views/ColorExtension.swift | 18 + .../ContentTabs/Config/ConfigItemView.swift | 36 ++ .../Views/ContentTabs/Config/ConfigView.swift | 257 +++++++++++++++ .../ContentTabs/Connections/Connections.swift | 37 +++ .../Connections/ConnectionsTableView.swift | 312 ++++++++++++++++++ .../Connections/ConnectionsView.swift | 46 +++ .../ContentTabs/Logs/LogsTableView.swift | 171 ++++++++++ .../Views/ContentTabs/Logs/LogsView.swift | 49 +++ .../Overview/OverviewTopItemView.swift | 37 +++ .../ContentTabs/Overview/OverviewView.swift | 92 ++++++ .../Overview/TrafficGraphView.swift | 148 +++++++++ .../Providers/ProviderProxiesView.swift | 142 ++++++++ .../Providers/ProviderRowView.swift | 51 +++ .../ContentTabs/Providers/ProvidersView.swift | 98 ++++++ .../Providers/ProxyProviderInfoView.swift | 89 +++++ .../Providers/ProxyProvidersRowView.swift | 81 +++++ .../Providers/RuleProviderView.swift | 42 +++ .../Providers/RuleProvidersRowView.swift | 75 +++++ .../ContentTabs/Proxies/ProxiesView.swift | 67 ++++ .../Proxies/ProxyGroupRowView.swift | 53 +++ .../ContentTabs/Proxies/ProxyGroupView.swift | 157 +++++++++ .../ContentTabs/Proxies/ProxyNodeView.swift | 89 +++++ .../ContentTabs/Rules/RuleItemView.swift | 67 ++++ .../Views/ContentTabs/Rules/RulesView.swift | 64 ++++ ClashX/Dashboard/Views/DashboardView.swift | 43 +++ .../Views/NSTableViewExtension.swift | 72 ++++ .../Views/SidebarView/SidebarItem.swift | 18 + .../Views/SidebarView/SidebarLabel.swift | 27 ++ .../Views/SidebarView/SidebarListView.swift | 85 +++++ .../Views/SidebarView/SidebarView.swift | 69 ++++ .../Views/SwiftUIViewExtensions.swift | 27 ++ 44 files changed, 4035 insertions(+), 1 deletion(-) create mode 100644 ClashX/Dashboard/DashboardViewContoller.swift create mode 100644 ClashX/Dashboard/Models/DBConnectionSnapShot.swift create mode 100644 ClashX/Dashboard/Models/DBProviderStorage.swift create mode 100644 ClashX/Dashboard/Models/DBProxyStorage.swift create mode 100644 ClashX/Dashboard/NotificationNames.swift create mode 100644 ClashX/Dashboard/ToolbarStore.swift create mode 100644 ClashX/Dashboard/Views/APISetting/APIServerItem.swift create mode 100644 ClashX/Dashboard/Views/APISetting/APISettingView.swift create mode 100644 ClashX/Dashboard/Views/APISetting/ClashServerAppStorage.swift create mode 100644 ClashX/Dashboard/Views/APISetting/ProgressButton.swift create mode 100644 ClashX/Dashboard/Views/ArrayExtensions.swift create mode 100644 ClashX/Dashboard/Views/ClashApiDatasStorage.swift create mode 100644 ClashX/Dashboard/Views/ColorExtension.swift create mode 100644 ClashX/Dashboard/Views/ContentTabs/Config/ConfigItemView.swift create mode 100644 ClashX/Dashboard/Views/ContentTabs/Config/ConfigView.swift create mode 100644 ClashX/Dashboard/Views/ContentTabs/Connections/Connections.swift create mode 100644 ClashX/Dashboard/Views/ContentTabs/Connections/ConnectionsTableView.swift create mode 100644 ClashX/Dashboard/Views/ContentTabs/Connections/ConnectionsView.swift create mode 100644 ClashX/Dashboard/Views/ContentTabs/Logs/LogsTableView.swift create mode 100644 ClashX/Dashboard/Views/ContentTabs/Logs/LogsView.swift create mode 100644 ClashX/Dashboard/Views/ContentTabs/Overview/OverviewTopItemView.swift create mode 100644 ClashX/Dashboard/Views/ContentTabs/Overview/OverviewView.swift create mode 100644 ClashX/Dashboard/Views/ContentTabs/Overview/TrafficGraphView.swift create mode 100644 ClashX/Dashboard/Views/ContentTabs/Providers/ProviderProxiesView.swift create mode 100644 ClashX/Dashboard/Views/ContentTabs/Providers/ProviderRowView.swift create mode 100644 ClashX/Dashboard/Views/ContentTabs/Providers/ProvidersView.swift create mode 100644 ClashX/Dashboard/Views/ContentTabs/Providers/ProxyProviderInfoView.swift create mode 100644 ClashX/Dashboard/Views/ContentTabs/Providers/ProxyProvidersRowView.swift create mode 100644 ClashX/Dashboard/Views/ContentTabs/Providers/RuleProviderView.swift create mode 100644 ClashX/Dashboard/Views/ContentTabs/Providers/RuleProvidersRowView.swift create mode 100644 ClashX/Dashboard/Views/ContentTabs/Proxies/ProxiesView.swift create mode 100644 ClashX/Dashboard/Views/ContentTabs/Proxies/ProxyGroupRowView.swift create mode 100644 ClashX/Dashboard/Views/ContentTabs/Proxies/ProxyGroupView.swift create mode 100644 ClashX/Dashboard/Views/ContentTabs/Proxies/ProxyNodeView.swift create mode 100644 ClashX/Dashboard/Views/ContentTabs/Rules/RuleItemView.swift create mode 100644 ClashX/Dashboard/Views/ContentTabs/Rules/RulesView.swift create mode 100644 ClashX/Dashboard/Views/DashboardView.swift create mode 100644 ClashX/Dashboard/Views/NSTableViewExtension.swift create mode 100644 ClashX/Dashboard/Views/SidebarView/SidebarItem.swift create mode 100644 ClashX/Dashboard/Views/SidebarView/SidebarLabel.swift create mode 100644 ClashX/Dashboard/Views/SidebarView/SidebarListView.swift create mode 100644 ClashX/Dashboard/Views/SidebarView/SidebarView.swift create mode 100644 ClashX/Dashboard/Views/SwiftUIViewExtensions.swift diff --git a/ClashX.xcodeproj/project.pbxproj b/ClashX.xcodeproj/project.pbxproj index bc2d361d7..ec335eee2 100644 --- a/ClashX.xcodeproj/project.pbxproj +++ b/ClashX.xcodeproj/project.pbxproj @@ -17,6 +17,49 @@ 01BCDAB32C9ECB560028FA94 /* SwiftUIIntrospect in Frameworks */ = {isa = PBXBuildFile; productRef = 01BCDAB22C9ECB560028FA94 /* SwiftUIIntrospect */; }; 01BCDAB52C9ECB560028FA94 /* SwiftUIIntrospect-Dynamic in Frameworks */ = {isa = PBXBuildFile; productRef = 01BCDAB42C9ECB560028FA94 /* SwiftUIIntrospect-Dynamic */; }; 01BCDAB72C9ECB560028FA94 /* SwiftUIIntrospect-Static in Frameworks */ = {isa = PBXBuildFile; productRef = 01BCDAB62C9ECB560028FA94 /* SwiftUIIntrospect-Static */; }; + 01BCDAF02C9ECD010028FA94 /* DBProviderStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BCDABB2C9ECD010028FA94 /* DBProviderStorage.swift */; }; + 01BCDAF12C9ECD010028FA94 /* ProviderRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BCDAD42C9ECD010028FA94 /* ProviderRowView.swift */; }; + 01BCDAF22C9ECD010028FA94 /* DashboardViewContoller.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BCDAB92C9ECD010028FA94 /* DashboardViewContoller.swift */; }; + 01BCDAF32C9ECD010028FA94 /* ProxyGroupRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BCDADC2C9ECD010028FA94 /* ProxyGroupRowView.swift */; }; + 01BCDAF42C9ECD010028FA94 /* DashboardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BCDAEC2C9ECD010028FA94 /* DashboardView.swift */; }; + 01BCDAF52C9ECD010028FA94 /* Connections.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BCDAC82C9ECD010028FA94 /* Connections.swift */; }; + 01BCDAF62C9ECD010028FA94 /* ConfigItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BCDAC52C9ECD010028FA94 /* ConfigItemView.swift */; }; + 01BCDAF72C9ECD010028FA94 /* ColorExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BCDAEB2C9ECD010028FA94 /* ColorExtension.swift */; }; + 01BCDAF82C9ECD010028FA94 /* OverviewTopItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BCDACF2C9ECD010028FA94 /* OverviewTopItemView.swift */; }; + 01BCDAF92C9ECD010028FA94 /* ConnectionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BCDACA2C9ECD010028FA94 /* ConnectionsView.swift */; }; + 01BCDAFA2C9ECD010028FA94 /* TrafficGraphView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BCDAD12C9ECD010028FA94 /* TrafficGraphView.swift */; }; + 01BCDAFB2C9ECD010028FA94 /* LogsTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BCDACC2C9ECD010028FA94 /* LogsTableView.swift */; }; + 01BCDAFC2C9ECD010028FA94 /* APIServerItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BCDAC02C9ECD010028FA94 /* APIServerItem.swift */; }; + 01BCDAFD2C9ECD010028FA94 /* ToolbarStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BCDABF2C9ECD010028FA94 /* ToolbarStore.swift */; }; + 01BCDAFE2C9ECD010028FA94 /* RuleItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BCDAE02C9ECD010028FA94 /* RuleItemView.swift */; }; + 01BCDAFF2C9ECD010028FA94 /* ConnectionsTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BCDAC92C9ECD010028FA94 /* ConnectionsTableView.swift */; }; + 01BCDB002C9ECD010028FA94 /* DBConnectionSnapShot.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BCDABA2C9ECD010028FA94 /* DBConnectionSnapShot.swift */; }; + 01BCDB012C9ECD010028FA94 /* ProxyNodeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BCDADE2C9ECD010028FA94 /* ProxyNodeView.swift */; }; + 01BCDB022C9ECD010028FA94 /* ProxyProviderInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BCDAD62C9ECD010028FA94 /* ProxyProviderInfoView.swift */; }; + 01BCDB032C9ECD010028FA94 /* ProvidersView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BCDAD52C9ECD010028FA94 /* ProvidersView.swift */; }; + 01BCDB042C9ECD010028FA94 /* ClashApiDatasStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BCDAEA2C9ECD010028FA94 /* ClashApiDatasStorage.swift */; }; + 01BCDB052C9ECD010028FA94 /* RulesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BCDAE12C9ECD010028FA94 /* RulesView.swift */; }; + 01BCDB062C9ECD010028FA94 /* RuleProviderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BCDAD92C9ECD010028FA94 /* RuleProviderView.swift */; }; + 01BCDB072C9ECD010028FA94 /* SidebarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BCDAE42C9ECD010028FA94 /* SidebarItem.swift */; }; + 01BCDB082C9ECD010028FA94 /* APISettingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BCDAC12C9ECD010028FA94 /* APISettingView.swift */; }; + 01BCDB092C9ECD010028FA94 /* DBProxyStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BCDABC2C9ECD010028FA94 /* DBProxyStorage.swift */; }; + 01BCDB0A2C9ECD010028FA94 /* SwiftUIViewExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BCDAEE2C9ECD010028FA94 /* SwiftUIViewExtensions.swift */; }; + 01BCDB0B2C9ECD010028FA94 /* ClashServerAppStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BCDAC22C9ECD010028FA94 /* ClashServerAppStorage.swift */; }; + 01BCDB0C2C9ECD010028FA94 /* NSTableViewExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BCDAED2C9ECD010028FA94 /* NSTableViewExtension.swift */; }; + 01BCDB0E2C9ECD010028FA94 /* ArrayExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BCDAE92C9ECD010028FA94 /* ArrayExtensions.swift */; }; + 01BCDB0F2C9ECD010028FA94 /* ProxyGroupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BCDADD2C9ECD010028FA94 /* ProxyGroupView.swift */; }; + 01BCDB102C9ECD010028FA94 /* ProxiesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BCDADB2C9ECD010028FA94 /* ProxiesView.swift */; }; + 01BCDB112C9ECD010028FA94 /* OverviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BCDAD02C9ECD010028FA94 /* OverviewView.swift */; }; + 01BCDB122C9ECD010028FA94 /* ProviderProxiesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BCDAD32C9ECD010028FA94 /* ProviderProxiesView.swift */; }; + 01BCDB132C9ECD010028FA94 /* ConfigView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BCDAC62C9ECD010028FA94 /* ConfigView.swift */; }; + 01BCDB142C9ECD010028FA94 /* ProxyProvidersRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BCDAD72C9ECD010028FA94 /* ProxyProvidersRowView.swift */; }; + 01BCDB152C9ECD010028FA94 /* NotificationNames.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BCDABE2C9ECD010028FA94 /* NotificationNames.swift */; }; + 01BCDB162C9ECD010028FA94 /* LogsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BCDACD2C9ECD010028FA94 /* LogsView.swift */; }; + 01BCDB172C9ECD010028FA94 /* SidebarListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BCDAE62C9ECD010028FA94 /* SidebarListView.swift */; }; + 01BCDB182C9ECD010028FA94 /* RuleProvidersRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BCDAD82C9ECD010028FA94 /* RuleProvidersRowView.swift */; }; + 01BCDB192C9ECD010028FA94 /* ProgressButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BCDAC32C9ECD010028FA94 /* ProgressButton.swift */; }; + 01BCDB1A2C9ECD010028FA94 /* SidebarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BCDAE72C9ECD010028FA94 /* SidebarView.swift */; }; + 01BCDB1B2C9ECD010028FA94 /* SidebarLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BCDAE52C9ECD010028FA94 /* SidebarLabel.swift */; }; 01CA6BC12B6A1B3100E386D6 /* MetaServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01CA6BBF2B6A1B3100E386D6 /* MetaServer.swift */; }; 01CA6BC22B6A1B3100E386D6 /* MetaServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01CA6BBF2B6A1B3100E386D6 /* MetaServer.swift */; }; 01D567E42AD158B600CDA0AE /* MetaPrefs.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 01D567E12AD158B500CDA0AE /* MetaPrefs.storyboard */; }; @@ -189,6 +232,49 @@ 01B1CB092A2E20C10073EA34 /* DashboardManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DashboardManager.swift; sourceTree = ""; }; 01B2274A29B845F100FE35C9 /* country.mmdb.gz */ = {isa = PBXFileReference; lastKnownFileType = archive.gzip; path = country.mmdb.gz; sourceTree = ""; }; 01BC9ABD2928E5C600F9B177 /* MetaDNS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetaDNS.swift; sourceTree = ""; }; + 01BCDAB92C9ECD010028FA94 /* DashboardViewContoller.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashboardViewContoller.swift; sourceTree = ""; }; + 01BCDABA2C9ECD010028FA94 /* DBConnectionSnapShot.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DBConnectionSnapShot.swift; sourceTree = ""; }; + 01BCDABB2C9ECD010028FA94 /* DBProviderStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DBProviderStorage.swift; sourceTree = ""; }; + 01BCDABC2C9ECD010028FA94 /* DBProxyStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DBProxyStorage.swift; sourceTree = ""; }; + 01BCDABE2C9ECD010028FA94 /* NotificationNames.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationNames.swift; sourceTree = ""; }; + 01BCDABF2C9ECD010028FA94 /* ToolbarStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToolbarStore.swift; sourceTree = ""; }; + 01BCDAC02C9ECD010028FA94 /* APIServerItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIServerItem.swift; sourceTree = ""; }; + 01BCDAC12C9ECD010028FA94 /* APISettingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APISettingView.swift; sourceTree = ""; }; + 01BCDAC22C9ECD010028FA94 /* ClashServerAppStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClashServerAppStorage.swift; sourceTree = ""; }; + 01BCDAC32C9ECD010028FA94 /* ProgressButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressButton.swift; sourceTree = ""; }; + 01BCDAC52C9ECD010028FA94 /* ConfigItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigItemView.swift; sourceTree = ""; }; + 01BCDAC62C9ECD010028FA94 /* ConfigView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigView.swift; sourceTree = ""; }; + 01BCDAC82C9ECD010028FA94 /* Connections.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Connections.swift; sourceTree = ""; }; + 01BCDAC92C9ECD010028FA94 /* ConnectionsTableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectionsTableView.swift; sourceTree = ""; }; + 01BCDACA2C9ECD010028FA94 /* ConnectionsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectionsView.swift; sourceTree = ""; }; + 01BCDACC2C9ECD010028FA94 /* LogsTableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogsTableView.swift; sourceTree = ""; }; + 01BCDACD2C9ECD010028FA94 /* LogsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogsView.swift; sourceTree = ""; }; + 01BCDACF2C9ECD010028FA94 /* OverviewTopItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OverviewTopItemView.swift; sourceTree = ""; }; + 01BCDAD02C9ECD010028FA94 /* OverviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OverviewView.swift; sourceTree = ""; }; + 01BCDAD12C9ECD010028FA94 /* TrafficGraphView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrafficGraphView.swift; sourceTree = ""; }; + 01BCDAD32C9ECD010028FA94 /* ProviderProxiesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProviderProxiesView.swift; sourceTree = ""; }; + 01BCDAD42C9ECD010028FA94 /* ProviderRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProviderRowView.swift; sourceTree = ""; }; + 01BCDAD52C9ECD010028FA94 /* ProvidersView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProvidersView.swift; sourceTree = ""; }; + 01BCDAD62C9ECD010028FA94 /* ProxyProviderInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProxyProviderInfoView.swift; sourceTree = ""; }; + 01BCDAD72C9ECD010028FA94 /* ProxyProvidersRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProxyProvidersRowView.swift; sourceTree = ""; }; + 01BCDAD82C9ECD010028FA94 /* RuleProvidersRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuleProvidersRowView.swift; sourceTree = ""; }; + 01BCDAD92C9ECD010028FA94 /* RuleProviderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuleProviderView.swift; sourceTree = ""; }; + 01BCDADB2C9ECD010028FA94 /* ProxiesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProxiesView.swift; sourceTree = ""; }; + 01BCDADC2C9ECD010028FA94 /* ProxyGroupRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProxyGroupRowView.swift; sourceTree = ""; }; + 01BCDADD2C9ECD010028FA94 /* ProxyGroupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProxyGroupView.swift; sourceTree = ""; }; + 01BCDADE2C9ECD010028FA94 /* ProxyNodeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProxyNodeView.swift; sourceTree = ""; }; + 01BCDAE02C9ECD010028FA94 /* RuleItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuleItemView.swift; sourceTree = ""; }; + 01BCDAE12C9ECD010028FA94 /* RulesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RulesView.swift; sourceTree = ""; }; + 01BCDAE42C9ECD010028FA94 /* SidebarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarItem.swift; sourceTree = ""; }; + 01BCDAE52C9ECD010028FA94 /* SidebarLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarLabel.swift; sourceTree = ""; }; + 01BCDAE62C9ECD010028FA94 /* SidebarListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarListView.swift; sourceTree = ""; }; + 01BCDAE72C9ECD010028FA94 /* SidebarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarView.swift; sourceTree = ""; }; + 01BCDAE92C9ECD010028FA94 /* ArrayExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArrayExtensions.swift; sourceTree = ""; }; + 01BCDAEA2C9ECD010028FA94 /* ClashApiDatasStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClashApiDatasStorage.swift; sourceTree = ""; }; + 01BCDAEB2C9ECD010028FA94 /* ColorExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorExtension.swift; sourceTree = ""; }; + 01BCDAEC2C9ECD010028FA94 /* DashboardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashboardView.swift; sourceTree = ""; }; + 01BCDAED2C9ECD010028FA94 /* NSTableViewExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSTableViewExtension.swift; sourceTree = ""; }; + 01BCDAEE2C9ECD010028FA94 /* SwiftUIViewExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftUIViewExtensions.swift; sourceTree = ""; }; 01C1462928962E4E00346AF3 /* com.metacubex.ClashX.ProxyConfigHelper.meta.gz */ = {isa = PBXFileReference; lastKnownFileType = archive.gzip; path = com.metacubex.ClashX.ProxyConfigHelper.meta.gz; sourceTree = ""; }; 01CA6BBF2B6A1B3100E386D6 /* MetaServer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetaServer.swift; sourceTree = ""; }; 01D567DF2AD1562700CDA0AE /* ClashX Meta SwiftUI-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "ClashX Meta SwiftUI-Info.plist"; sourceTree = ""; }; @@ -348,10 +434,149 @@ 01BCDAA92C9EC9DD0028FA94 /* Dashboard */ = { isa = PBXGroup; children = ( + 01BCDABD2C9ECD010028FA94 /* Models */, + 01BCDAEF2C9ECD010028FA94 /* Views */, + 01BCDAB92C9ECD010028FA94 /* DashboardViewContoller.swift */, + 01BCDABE2C9ECD010028FA94 /* NotificationNames.swift */, + 01BCDABF2C9ECD010028FA94 /* ToolbarStore.swift */, ); path = Dashboard; sourceTree = ""; }; + 01BCDABD2C9ECD010028FA94 /* Models */ = { + isa = PBXGroup; + children = ( + 01BCDABA2C9ECD010028FA94 /* DBConnectionSnapShot.swift */, + 01BCDABB2C9ECD010028FA94 /* DBProviderStorage.swift */, + 01BCDABC2C9ECD010028FA94 /* DBProxyStorage.swift */, + ); + path = Models; + sourceTree = ""; + }; + 01BCDAC42C9ECD010028FA94 /* APISetting */ = { + isa = PBXGroup; + children = ( + 01BCDAC02C9ECD010028FA94 /* APIServerItem.swift */, + 01BCDAC12C9ECD010028FA94 /* APISettingView.swift */, + 01BCDAC22C9ECD010028FA94 /* ClashServerAppStorage.swift */, + 01BCDAC32C9ECD010028FA94 /* ProgressButton.swift */, + ); + path = APISetting; + sourceTree = ""; + }; + 01BCDAC72C9ECD010028FA94 /* Config */ = { + isa = PBXGroup; + children = ( + 01BCDAC52C9ECD010028FA94 /* ConfigItemView.swift */, + 01BCDAC62C9ECD010028FA94 /* ConfigView.swift */, + ); + path = Config; + sourceTree = ""; + }; + 01BCDACB2C9ECD010028FA94 /* Connections */ = { + isa = PBXGroup; + children = ( + 01BCDAC82C9ECD010028FA94 /* Connections.swift */, + 01BCDAC92C9ECD010028FA94 /* ConnectionsTableView.swift */, + 01BCDACA2C9ECD010028FA94 /* ConnectionsView.swift */, + ); + path = Connections; + sourceTree = ""; + }; + 01BCDACE2C9ECD010028FA94 /* Logs */ = { + isa = PBXGroup; + children = ( + 01BCDACC2C9ECD010028FA94 /* LogsTableView.swift */, + 01BCDACD2C9ECD010028FA94 /* LogsView.swift */, + ); + path = Logs; + sourceTree = ""; + }; + 01BCDAD22C9ECD010028FA94 /* Overview */ = { + isa = PBXGroup; + children = ( + 01BCDACF2C9ECD010028FA94 /* OverviewTopItemView.swift */, + 01BCDAD02C9ECD010028FA94 /* OverviewView.swift */, + 01BCDAD12C9ECD010028FA94 /* TrafficGraphView.swift */, + ); + path = Overview; + sourceTree = ""; + }; + 01BCDADA2C9ECD010028FA94 /* Providers */ = { + isa = PBXGroup; + children = ( + 01BCDAD32C9ECD010028FA94 /* ProviderProxiesView.swift */, + 01BCDAD42C9ECD010028FA94 /* ProviderRowView.swift */, + 01BCDAD52C9ECD010028FA94 /* ProvidersView.swift */, + 01BCDAD62C9ECD010028FA94 /* ProxyProviderInfoView.swift */, + 01BCDAD72C9ECD010028FA94 /* ProxyProvidersRowView.swift */, + 01BCDAD82C9ECD010028FA94 /* RuleProvidersRowView.swift */, + 01BCDAD92C9ECD010028FA94 /* RuleProviderView.swift */, + ); + path = Providers; + sourceTree = ""; + }; + 01BCDADF2C9ECD010028FA94 /* Proxies */ = { + isa = PBXGroup; + children = ( + 01BCDADB2C9ECD010028FA94 /* ProxiesView.swift */, + 01BCDADC2C9ECD010028FA94 /* ProxyGroupRowView.swift */, + 01BCDADD2C9ECD010028FA94 /* ProxyGroupView.swift */, + 01BCDADE2C9ECD010028FA94 /* ProxyNodeView.swift */, + ); + path = Proxies; + sourceTree = ""; + }; + 01BCDAE22C9ECD010028FA94 /* Rules */ = { + isa = PBXGroup; + children = ( + 01BCDAE02C9ECD010028FA94 /* RuleItemView.swift */, + 01BCDAE12C9ECD010028FA94 /* RulesView.swift */, + ); + path = Rules; + sourceTree = ""; + }; + 01BCDAE32C9ECD010028FA94 /* ContentTabs */ = { + isa = PBXGroup; + children = ( + 01BCDAC72C9ECD010028FA94 /* Config */, + 01BCDACB2C9ECD010028FA94 /* Connections */, + 01BCDACE2C9ECD010028FA94 /* Logs */, + 01BCDAD22C9ECD010028FA94 /* Overview */, + 01BCDADA2C9ECD010028FA94 /* Providers */, + 01BCDADF2C9ECD010028FA94 /* Proxies */, + 01BCDAE22C9ECD010028FA94 /* Rules */, + ); + path = ContentTabs; + sourceTree = ""; + }; + 01BCDAE82C9ECD010028FA94 /* SidebarView */ = { + isa = PBXGroup; + children = ( + 01BCDAE42C9ECD010028FA94 /* SidebarItem.swift */, + 01BCDAE52C9ECD010028FA94 /* SidebarLabel.swift */, + 01BCDAE62C9ECD010028FA94 /* SidebarListView.swift */, + 01BCDAE72C9ECD010028FA94 /* SidebarView.swift */, + ); + path = SidebarView; + sourceTree = ""; + }; + 01BCDAEF2C9ECD010028FA94 /* Views */ = { + isa = PBXGroup; + children = ( + 01BCDAC42C9ECD010028FA94 /* APISetting */, + 01BCDAE32C9ECD010028FA94 /* ContentTabs */, + 01BCDAE82C9ECD010028FA94 /* SidebarView */, + 01BCDAE92C9ECD010028FA94 /* ArrayExtensions.swift */, + 01BCDAEA2C9ECD010028FA94 /* ClashApiDatasStorage.swift */, + 01BCDAEB2C9ECD010028FA94 /* ColorExtension.swift */, + 01BCDAEC2C9ECD010028FA94 /* DashboardView.swift */, + 01BCDAED2C9ECD010028FA94 /* NSTableViewExtension.swift */, + 01BCDAEE2C9ECD010028FA94 /* SwiftUIViewExtensions.swift */, + ); + path = Views; + sourceTree = ""; + }; 4913C82021157CEB00F6B87C /* Macro */ = { isa = PBXGroup; children = ( @@ -589,10 +814,10 @@ 4931969C21631F2E00A8E6E7 /* Views */, 496322202AA5D88100854231 /* Actions */, 4989F98520D0AA300001E564 /* ViewControllers */, + 01BCDAA92C9EC9DD0028FA94 /* Dashboard */, 49761DA521C9490400AE13EF /* Resources */, 49CF3B3A20CD783A001EBF94 /* Support Files */, 49CF3B2020CD7463001EBF94 /* AppDelegate.swift */, - 01BCDAA92C9EC9DD0028FA94 /* Dashboard */, 49CF3B2620CD7465001EBF94 /* Main.storyboard */, 49CF3B2920CD7465001EBF94 /* Info.plist */, 01D567DF2AD1562700CDA0AE /* ClashX Meta SwiftUI-Info.plist */, @@ -872,6 +1097,49 @@ 01F3360E2AD10D0B0048AF77 /* ProxyGroupSpeedTestMenuItem.swift in Sources */, 01F3360F2AD10D0B0048AF77 /* LoginServiceKit.swift in Sources */, 01F336102AD10D0B0048AF77 /* NormalMenuItemView.swift in Sources */, + 01BCDAF02C9ECD010028FA94 /* DBProviderStorage.swift in Sources */, + 01BCDAF12C9ECD010028FA94 /* ProviderRowView.swift in Sources */, + 01BCDAF22C9ECD010028FA94 /* DashboardViewContoller.swift in Sources */, + 01BCDAF32C9ECD010028FA94 /* ProxyGroupRowView.swift in Sources */, + 01BCDAF42C9ECD010028FA94 /* DashboardView.swift in Sources */, + 01BCDAF52C9ECD010028FA94 /* Connections.swift in Sources */, + 01BCDAF62C9ECD010028FA94 /* ConfigItemView.swift in Sources */, + 01BCDAF72C9ECD010028FA94 /* ColorExtension.swift in Sources */, + 01BCDAF82C9ECD010028FA94 /* OverviewTopItemView.swift in Sources */, + 01BCDAF92C9ECD010028FA94 /* ConnectionsView.swift in Sources */, + 01BCDAFA2C9ECD010028FA94 /* TrafficGraphView.swift in Sources */, + 01BCDAFB2C9ECD010028FA94 /* LogsTableView.swift in Sources */, + 01BCDAFC2C9ECD010028FA94 /* APIServerItem.swift in Sources */, + 01BCDAFD2C9ECD010028FA94 /* ToolbarStore.swift in Sources */, + 01BCDAFE2C9ECD010028FA94 /* RuleItemView.swift in Sources */, + 01BCDAFF2C9ECD010028FA94 /* ConnectionsTableView.swift in Sources */, + 01BCDB002C9ECD010028FA94 /* DBConnectionSnapShot.swift in Sources */, + 01BCDB012C9ECD010028FA94 /* ProxyNodeView.swift in Sources */, + 01BCDB022C9ECD010028FA94 /* ProxyProviderInfoView.swift in Sources */, + 01BCDB032C9ECD010028FA94 /* ProvidersView.swift in Sources */, + 01BCDB042C9ECD010028FA94 /* ClashApiDatasStorage.swift in Sources */, + 01BCDB052C9ECD010028FA94 /* RulesView.swift in Sources */, + 01BCDB062C9ECD010028FA94 /* RuleProviderView.swift in Sources */, + 01BCDB072C9ECD010028FA94 /* SidebarItem.swift in Sources */, + 01BCDB082C9ECD010028FA94 /* APISettingView.swift in Sources */, + 01BCDB092C9ECD010028FA94 /* DBProxyStorage.swift in Sources */, + 01BCDB0A2C9ECD010028FA94 /* SwiftUIViewExtensions.swift in Sources */, + 01BCDB0B2C9ECD010028FA94 /* ClashServerAppStorage.swift in Sources */, + 01BCDB0C2C9ECD010028FA94 /* NSTableViewExtension.swift in Sources */, + 01BCDB0E2C9ECD010028FA94 /* ArrayExtensions.swift in Sources */, + 01BCDB0F2C9ECD010028FA94 /* ProxyGroupView.swift in Sources */, + 01BCDB102C9ECD010028FA94 /* ProxiesView.swift in Sources */, + 01BCDB112C9ECD010028FA94 /* OverviewView.swift in Sources */, + 01BCDB122C9ECD010028FA94 /* ProviderProxiesView.swift in Sources */, + 01BCDB132C9ECD010028FA94 /* ConfigView.swift in Sources */, + 01BCDB142C9ECD010028FA94 /* ProxyProvidersRowView.swift in Sources */, + 01BCDB152C9ECD010028FA94 /* NotificationNames.swift in Sources */, + 01BCDB162C9ECD010028FA94 /* LogsView.swift in Sources */, + 01BCDB172C9ECD010028FA94 /* SidebarListView.swift in Sources */, + 01BCDB182C9ECD010028FA94 /* RuleProvidersRowView.swift in Sources */, + 01BCDB192C9ECD010028FA94 /* ProgressButton.swift in Sources */, + 01BCDB1A2C9ECD010028FA94 /* SidebarView.swift in Sources */, + 01BCDB1B2C9ECD010028FA94 /* SidebarLabel.swift in Sources */, 01F336112AD10D0B0048AF77 /* ProxyItemView.swift in Sources */, 01EF33642B98D71600D1DBD9 /* ProxyConfigRemoteProcessProtocol.swift in Sources */, 01F336122AD10D0B0048AF77 /* ICloudManager.swift in Sources */, diff --git a/ClashX/Dashboard/DashboardViewContoller.swift b/ClashX/Dashboard/DashboardViewContoller.swift new file mode 100644 index 000000000..ce5fb8d53 --- /dev/null +++ b/ClashX/Dashboard/DashboardViewContoller.swift @@ -0,0 +1,304 @@ +// +// DashboardViewContoller.swift +// ClashX +// +// Created by yicheng on 2018/8/28. +// Copyright © 2018年 west2online. All rights reserved. +// + +import Cocoa +import SwiftUI + +public class DashboardWindowController: NSWindowController { + public var onWindowClose: (() -> Void)? + + public static func create() -> DashboardWindowController { + let win = NSWindow() + win.center() + let wc = DashboardWindowController(window: win) + wc.contentViewController = DashboardViewContoller() + return wc + } + + public override func showWindow(_ sender: Any?) { + super.showWindow(sender) + NSApp.activate(ignoringOtherApps: true) + window?.makeKeyAndOrderFront(self) + window?.delegate = self + } + + public func set(_ apiURL: String, secret: String? = nil) { + ConfigManager.shared.isRunning = true + ConfigManager.shared.overrideApiURL = .init(string: apiURL) + ConfigManager.shared.overrideSecret = secret + } + + public func reload() { + NotificationCenter.default.post(name: .reloadDashboard, object: nil) + } +} + +extension DashboardWindowController: NSWindowDelegate { + public func windowWillClose(_ notification: Notification) { + NSApp.setActivationPolicy(.accessory) + onWindowClose?() + if let contentVC = contentViewController as? DashboardViewContoller, let win = window { + if !win.styleMask.contains(.fullScreen) { + contentVC.lastSize = win.frame.size + } + } + } +} + +class DashboardViewContoller: NSViewController { + let contentView = NSHostingView(rootView: DashboardView()) + let minSize = NSSize(width: 920, height: 580) + var lastSize: CGSize? { + set { + if let size = newValue { + UserDefaults.standard.set(NSStringFromSize(size), forKey: "ClashWebViewContoller.lastSize") + } + } + get { + if let str = UserDefaults.standard.value(forKey: "ClashWebViewContoller.lastSize") as? String { + return NSSizeFromString(str) as CGSize + } + return nil + } + } + + let effectView = NSVisualEffectView() + + private let levels = [ + ClashLogLevel.silent, + .error, + .warning, + .info, + .debug + ] + + private var sidebarItemObserver: NSObjectProtocol? + private var searchStringObserver: NSObjectProtocol? + + func createWindowController() -> NSWindowController { + let sb = NSStoryboard(name: "Main", bundle: Bundle.main) + let vc = sb.instantiateController(withIdentifier: "DashboardViewContoller") as! DashboardViewContoller + let wc = NSWindowController(window: NSWindow()) + wc.contentViewController = vc + return wc + } + + override func loadView() { + view = contentView + } + + override func viewDidLoad() { + super.viewDidLoad() + + sidebarItemObserver = NotificationCenter.default.addObserver(forName: .sidebarItemChanged, object: nil, queue: .main) { + guard let item = $0.userInfo?["item"] as? SidebarItem else { return } + + var items = [NSToolbarItem.Identifier]() + items.append(.toggleSidebar) + + switch item { + case .overview, .config: + break + case .proxies, .providers: + items.append(.hideNamesItem) + items.append(.searchItem) + case .rules: + items.append(.searchItem) + case .conns: + items.append(.stopConnsItem) + items.append(.searchItem) + case .logs: + items.append(.logLevelItem) + items.append(.searchItem) + } + self.reinitToolbar(items) + } + + searchStringObserver = NotificationCenter.default.addObserver(forName: .initSearchString, object: nil, queue: .main) { + guard let str = $0.userInfo?["string"] as? String, + let toolbar = self.view.window?.toolbar, + let searchItem = toolbar.items.first(where: { $0.itemIdentifier == .searchItem }) as? NSSearchToolbarItem else { return } + + searchItem.searchField.stringValue = str + } + } + + public override func viewWillAppear() { + super.viewWillAppear() + guard view.window?.toolbar == nil else { return } + + view.window?.styleMask.insert(.fullSizeContentView) + + view.window?.isOpaque = false + view.window?.styleMask.insert(.closable) + view.window?.styleMask.insert(.resizable) + view.window?.styleMask.insert(.miniaturizable) + + let toolbar = NSToolbar(identifier: .init("DashboardToolbar")) + toolbar.displayMode = .iconOnly + toolbar.delegate = self + + view.window?.toolbar = toolbar + view.window?.title = "Dashboard" + reinitToolbar([]) + + view.window?.minSize = minSize + if let lastSize = lastSize, lastSize != .zero { + view.window?.setContentSize(lastSize) + } + view.window?.center() + if NSApp.activationPolicy() == .accessory { + NSApp.setActivationPolicy(.regular) + } + + + // Fix sidebar list highlight + let button = NSButton(frame: .zero) + view.window?.contentView?.addSubview(button) + view.window?.initialFirstResponder = button + } + + func reinitToolbar(_ items: [NSToolbarItem.Identifier]) { + guard let toolbar = view.window?.toolbar else { return } + + toolbar.items.enumerated().reversed().forEach { + toolbar.removeItem(at: $0.offset) + } + + items.reversed().forEach { + toolbar.insertItem(withItemIdentifier: $0, at: 0) + } + } + + deinit { + if let sidebarItemObserver { + NotificationCenter.default.removeObserver(sidebarItemObserver) + } + if let searchStringObserver { + NotificationCenter.default.removeObserver(searchStringObserver) + } + NSApp.setActivationPolicy(.accessory) + } +} + + +extension NSToolbarItem.Identifier { + static let hideNamesItem = NSToolbarItem.Identifier("HideNamesItem") + static let stopConnsItem = NSToolbarItem.Identifier("StopConnsItem") + static let logLevelItem = NSToolbarItem.Identifier("LogLevelItem") + static let searchItem = NSToolbarItem.Identifier("SearchItem") +} + +extension DashboardViewContoller: NSSearchFieldDelegate { + + func controlTextDidChange(_ obj: Notification) { + guard let obj = obj.object as? NSSearchField else { return } + let str = obj.stringValue + NotificationCenter.default.post(name: .toolbarSearchString, object: nil, userInfo: ["String": str]) + } + + @IBAction func stopConns(_ sender: NSToolbarItem) { + NotificationCenter.default.post(name: .stopConns, object: nil) + } + + @IBAction func hideNames(_ sender: NSToolbarItem) { + switch sender.tag { + case 0: + sender.tag = 1 + sender.image = NSImage(systemSymbolName: "eyeglasses", accessibilityDescription: nil) + case 1: + sender.tag = 0 + sender.image = NSImage(systemSymbolName: "wand.and.stars", accessibilityDescription: nil) + default: + break + } + + NotificationCenter.default.post(name: .hideNames, object: nil, userInfo: ["hide": sender.tag == 1]) + } + + @objc func setLogLevel(_ sender: NSToolbarItemGroup) { + guard sender.selectedIndex < levels.count, sender.selectedIndex >= 0 else { return } + let level = levels[sender.selectedIndex] + + NotificationCenter.default.post(name: .logLevelChanged, object: nil, userInfo: ["level": level]) + } + +} + +extension DashboardViewContoller: NSToolbarDelegate, NSToolbarItemValidation { + + func validateToolbarItem(_ item: NSToolbarItem) -> Bool { + return true + } + + func toolbar(_ toolbar: NSToolbar, itemForItemIdentifier itemIdentifier: NSToolbarItem.Identifier, willBeInsertedIntoToolbar flag: Bool) -> NSToolbarItem? { + + switch itemIdentifier { + case .searchItem: + let item = NSSearchToolbarItem(itemIdentifier: .searchItem) + item.resignsFirstResponderWithCancel = true + item.searchField.delegate = self + item.toolTip = "Search" + return item + case .toggleSidebar: + return NSTrackingSeparatorToolbarItem(itemIdentifier: .toggleSidebar) + case .logLevelItem: + + let titles = levels.map { + $0.rawValue.capitalized + } + + let group = NSToolbarItemGroup(itemIdentifier: .logLevelItem, titles: titles, selectionMode: .selectOne, labels: titles, target: nil, action: #selector(setLogLevel(_:))) + group.selectionMode = .selectOne + group.controlRepresentation = .collapsed + group.selectedIndex = levels.firstIndex(of: ConfigManager.selectLoggingApiLevel) ?? 0 + + return group + case .hideNamesItem: + let item = NSToolbarItem(itemIdentifier: .hideNamesItem) + item.target = self + item.action = #selector(hideNames(_:)) + item.isBordered = true + item.tag = 0 + item.image = NSImage(systemSymbolName: "wand.and.stars", accessibilityDescription: nil) + return item + case .stopConnsItem: + let item = NSToolbarItem(itemIdentifier: .stopConnsItem) + item.target = self + item.action = #selector(stopConns(_:)) + item.isBordered = true + item.image = NSImage(systemSymbolName: "stop.circle.fill", accessibilityDescription: nil) + return item + default: + break + } + + return nil + } + + + func toolbarAllowedItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] { + [ + .toggleSidebar, + .stopConnsItem, + .hideNamesItem, + .logLevelItem, + .searchItem + ] + } + + func toolbarDefaultItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] { + [ + .toggleSidebar, + .stopConnsItem, + .hideNamesItem, + .logLevelItem, + .searchItem + ] + } +} diff --git a/ClashX/Dashboard/Models/DBConnectionSnapShot.swift b/ClashX/Dashboard/Models/DBConnectionSnapShot.swift new file mode 100644 index 000000000..3c1582b5b --- /dev/null +++ b/ClashX/Dashboard/Models/DBConnectionSnapShot.swift @@ -0,0 +1,143 @@ +// +// DBConnectionSnapShot.swift +// ClashX Dashboard +// +// + +import Cocoa + +struct DBConnectionSnapShot: Codable { + let downloadTotal: Int + let uploadTotal: Int + let connections: [DBConnection] +} + +struct DBConnection: Codable, Hashable { + let id: String + let chains: [String] + let upload: Int64 + let download: Int64 + let start: Date + let rule: String + let rulePayload: String + + let metadata: DBMetaConnectionData +} + +struct DBMetaConnectionData: Codable, Hashable { + let uid: Int + + let network: String + let type: String + let sourceIP: String + let destinationIP: String + let sourcePort: String + let destinationPort: String + let inboundIP: String + let inboundPort: String + let inboundName: String + let host: String + let dnsMode: String + let process: String + let processPath: String + let specialProxy: String + let specialRules: String + let remoteDestination: String + let sniffHost: String + +} + + +class DBConnectionObject: NSObject { + @objc let id: String + @objc let host: String + @objc let sniffHost: String + @objc let process: String + @objc let download: Int64 + @objc let upload: Int64 + let downloadString: String + let uploadString: String + let chains: [String] + @objc let chainString: String + @objc let ruleString: String + @objc let startDate: Date + let startString: String + @objc let source: String + @objc let destinationIP: String? + @objc let type: String + + @objc var downloadSpeed: Int64 + @objc var uploadSpeed: Int64 + var downloadSpeedString: String + var uploadSpeedString: String + + + func isContentEqual(to source: DBConnectionObject) -> Bool { + download == source.download && + upload == source.upload && + startString == source.startString + } + + init(_ conn: DBConnection) { + let byteCountFormatter = ByteCountFormatter() + let startFormatter = RelativeDateTimeFormatter() + startFormatter.unitsStyle = .short + + let metadata = conn.metadata + + id = conn.id + host = "\(metadata.host == "" ? metadata.destinationIP : metadata.host):\(metadata.destinationPort)" + sniffHost = metadata.sniffHost == "" ? "-" : metadata.sniffHost + process = metadata.process + download = conn.download + downloadString = byteCountFormatter.string(fromByteCount: conn.download) + upload = conn.upload + uploadString = byteCountFormatter.string(fromByteCount: conn.upload) + chains = conn.chains + chainString = conn.chains.reversed().joined(separator: "/") + ruleString = conn.rulePayload == "" ? conn.rule : "\(conn.rule) :: \(conn.rulePayload)" + startDate = conn.start + startString = startFormatter.localizedString(for: conn.start, relativeTo: Date()) + source = "\(metadata.sourceIP):\(metadata.sourcePort)" + destinationIP = [metadata.remoteDestination, + metadata.destinationIP, + metadata.host].first(where: { $0 != "" }) + + type = "\(metadata.type)(\(metadata.network))" + + downloadSpeed = 0 + uploadSpeed = 0 + downloadSpeedString = "-" + uploadSpeedString = "-" + } + + + func updateSpeeds(_ old: (download: Int64, upload: Int64)?) { + guard let old = old else { + downloadSpeed = 0 + uploadSpeed = 0 + downloadSpeedString = "-" + uploadSpeedString = "-" + return + } + + let byteCountFormatter = ByteCountFormatter() + + downloadSpeed = download - old.download + uploadSpeed = upload - old.upload + + if downloadSpeed > 0 { + downloadSpeedString = byteCountFormatter.string(fromByteCount: downloadSpeed) + "/s" + } else { + downloadSpeed = 0 + downloadSpeedString = "-" + } + + if uploadSpeed > 0 { + uploadSpeedString = byteCountFormatter.string(fromByteCount: uploadSpeed) + "/s" + } else { + uploadSpeed = 0 + uploadSpeedString = "-" + } + } +} diff --git a/ClashX/Dashboard/Models/DBProviderStorage.swift b/ClashX/Dashboard/Models/DBProviderStorage.swift new file mode 100644 index 000000000..a4372aac8 --- /dev/null +++ b/ClashX/Dashboard/Models/DBProviderStorage.swift @@ -0,0 +1,108 @@ +// +// DBProviderStorage.swift +// ClashX Dashboard +// +// + +import Cocoa +import SwiftUI + +class DBProviderStorage: ObservableObject { + @Published var proxyProviders = [DBProxyProvider]() + @Published var ruleProviders = [DBRuleProvider]() + + init() {} + +} + +class DBProxyProvider: ObservableObject, Identifiable { + let id = UUID().uuidString + + @Published var name: ClashProviderName + @Published var proxies: [DBProxy] + @Published var type: ClashProvider.ProviderType + @Published var vehicleType: ClashProvider.ProviderVehicleType + + @Published var trafficInfo: String + @Published var trafficPercentage: String + @Published var expireDate: String + @Published var updatedAt: String + + init(provider: ClashProvider) { + name = provider.name + proxies = provider.proxies.map(DBProxy.init) + type = provider.type + vehicleType = provider.vehicleType + + if let info = provider.subscriptionInfo { + let used = info.download + info.upload + let total = info.total + + let trafficRate = "\(String(format: "%.2f", Double(used)/Double(total/100)))%" + + let formatter = ByteCountFormatter() + + trafficInfo = formatter.string(fromByteCount: used) + + " / " + + formatter.string(fromByteCount: total) + + " ( \(trafficRate) )" + + let expire = info.expire + if expire == 0 { + expireDate = "Expire: none" + } else { + let eDate = Date(timeIntervalSince1970: TimeInterval(expire)) + if #available(macOS 12.0, *) { + expireDate = "Expire: " + eDate.formatted() + } else { + let dateFormatter = DateFormatter() + dateFormatter.dateStyle = .short + dateFormatter.timeStyle = .short + expireDate = "Expire: " + dateFormatter.string(from: eDate) + } + } + + self.trafficPercentage = trafficRate + } else { + trafficInfo = "" + expireDate = "" + trafficPercentage = "0.0%" + } + + if let updatedAt = provider.updatedAt { + let formatter = RelativeDateTimeFormatter() + formatter.unitsStyle = .abbreviated + self.updatedAt = formatter.localizedString(for: updatedAt, relativeTo: Date()) + } else { + self.updatedAt = "" + } + } + + func updateInfo(_ new: DBProxyProvider) { + proxies = new.proxies + updatedAt = new.updatedAt + expireDate = new.expireDate + trafficInfo = new.trafficInfo + trafficPercentage = new.trafficPercentage + } +} + +class DBRuleProvider: ObservableObject, Identifiable { + let id: String + + @Published var name: ClashProviderName + @Published var ruleCount: Int + @Published var behavior: String + @Published var type: String + @Published var updatedAt: Date? + + init(provider: ClashRuleProvider) { + id = UUID().uuidString + + name = provider.name + ruleCount = provider.ruleCount + behavior = provider.behavior + type = provider.type + updatedAt = provider.updatedAt + } +} diff --git a/ClashX/Dashboard/Models/DBProxyStorage.swift b/ClashX/Dashboard/Models/DBProxyStorage.swift new file mode 100644 index 000000000..31dd1590d --- /dev/null +++ b/ClashX/Dashboard/Models/DBProxyStorage.swift @@ -0,0 +1,120 @@ +// +// DBProxyStorage.swift +// ClashX Dashboard +// +// + +import Cocoa +import SwiftUI + +class DBProxyStorage: ObservableObject { + @Published var groups = [DBProxyGroup]() + + init() { + + } + + init(_ resp: ClashProxyResp) { + groups = resp.proxyGroups.map { + DBProxyGroup($0, resp: resp) + } + } +} + +class DBProxyGroup: ObservableObject, Identifiable { + let id = UUID().uuidString + @Published var name: ClashProxyName + @Published var type: ClashProxyType + @Published var now: ClashProxyName? { + didSet { + currentProxy = proxies.first { + $0.name == now + } + } + } + + @Published var proxies: [DBProxy] + + @Published var currentProxy: DBProxy? + + init(_ group: ClashProxy, resp: ClashProxyResp) { + name = group.name + type = group.type + now = group.now + + proxies = group.all?.compactMap { name in + resp.proxiesMap[name] + }.map(DBProxy.init) ?? [] + + currentProxy = proxies.first { + $0.name == now + } + } +} + +class DBProxy: ObservableObject { + let id: String + @Published var name: ClashProxyName + @Published var type: ClashProxyType + @Published var udpString: String + @Published var tfo: Bool + + var delay: Int { + didSet { + delayString = DBProxy.delayString(delay) + delayColor = DBProxy.delayColor(delay) + } + } + + @Published var delayString: String + @Published var delayColor: Color + + init(_ proxy: ClashProxy) { + id = proxy.id ?? UUID().uuidString + name = proxy.name + type = proxy.type + tfo = proxy.tfo + delay = proxy.history.last?.delayInt ?? 0 + + udpString = { + if proxy.udp { + return "UDP" + } else if proxy.xudp { + return "XUDP" + } else { + return "" + } + }() + delayString = DBProxy.delayString(delay) + delayColor = DBProxy.delayColor(delay) + } + + static func delayString(_ delay: Int) -> String { + switch delay { + case 0: + return NSLocalizedString("fail", comment: "") + default: + return "\(delay) ms" + } + } + + static func delayColor(_ delay: Int) -> Color { + let httpsTest = true + + switch delay { + case 0: + return .gray + case ..<200 where !httpsTest: + return .green + case ..<800 where httpsTest: + return .green + case 200..<500 where !httpsTest: + return .yellow + case 800..<1500 where httpsTest: + return .yellow + default: + return .orange + } + } +} + diff --git a/ClashX/Dashboard/NotificationNames.swift b/ClashX/Dashboard/NotificationNames.swift new file mode 100644 index 000000000..5347b3383 --- /dev/null +++ b/ClashX/Dashboard/NotificationNames.swift @@ -0,0 +1,17 @@ +// +// NotificationNames.swift +// +// +// + +import Foundation + +extension NSNotification.Name { + static let sidebarItemChanged = NSNotification.Name("SidebarItemChanged") + + static let toolbarSearchString = NSNotification.Name("ToolbarSearchString") + static let initSearchString = NSNotification.Name("InitSearchString") + static let stopConns = NSNotification.Name("StopConns") + static let hideNames = NSNotification.Name("HideNames") + static let logLevelChanged = NSNotification.Name("LogLevelChanged") +} diff --git a/ClashX/Dashboard/ToolbarStore.swift b/ClashX/Dashboard/ToolbarStore.swift new file mode 100644 index 000000000..a1cf631f2 --- /dev/null +++ b/ClashX/Dashboard/ToolbarStore.swift @@ -0,0 +1,17 @@ +// +// ToolbarStore.swift +// +// +// + +import Cocoa + +class ToolbarStore: NSObject { + static let shared = ToolbarStore() + + private override init() { + + } + + var searchStrings = [String: String]() +} diff --git a/ClashX/Dashboard/Views/APISetting/APIServerItem.swift b/ClashX/Dashboard/Views/APISetting/APIServerItem.swift new file mode 100644 index 000000000..16f3ead3a --- /dev/null +++ b/ClashX/Dashboard/Views/APISetting/APIServerItem.swift @@ -0,0 +1,48 @@ +// +// APIServerItem.swift +// ClashX Dashboard +// +// + +import SwiftUI + +struct APIServerItem: View { + @State var server: String + + var action: () -> Void + var onDelete: () -> Void + + @State private var mouseOver = false + + var body: some View { + HStack { + Button("X") { + onDelete() + } + .buttonStyle(.bordered) + + Button() { + action() + } label: { + Text(server) + .font(.title2) + } + .buttonStyle(.borderless) + + Spacer() + } + .frame(height: 21) + .padding(EdgeInsets(top: 12, leading: 20, bottom: 12, trailing: 20)) + .overlay( + RoundedRectangle(cornerRadius: 6) + .stroke(Color(compatible: .secondaryLabelColor), lineWidth: 1) + .padding(1) + ) + } +} + +//struct APIServerItem_Previews: PreviewProvider { +// static var previews: some View { +// APIServerItem() +// } +//} diff --git a/ClashX/Dashboard/Views/APISetting/APISettingView.swift b/ClashX/Dashboard/Views/APISetting/APISettingView.swift new file mode 100644 index 000000000..86a7915b0 --- /dev/null +++ b/ClashX/Dashboard/Views/APISetting/APISettingView.swift @@ -0,0 +1,81 @@ +// +// APISettingView.swift +// ClashX Dashboard +// +// + +import SwiftUI +import SwiftUIIntrospect + +struct APISettingView: View { + @State var baseURL: String = "" + @State var secret: String = "" + + @State var connectInfo: String = "" + + @AppStorage("savedServers") var savedServers = SavedServersAppStorage() + + var body: some View { + VStack(alignment: .center) { + HStack { + VStack(alignment: .leading) { + Text("API Base URL") + TextField("http://127.0.0.1:9090", text: $baseURL) + } + .frame(width: 250) + + VStack(alignment: .leading) { + Text("Secret(optional)") + TextField("", text: $secret) + } + .frame(width: 120) + } + + HStack { + Text(connectInfo) + Spacer() + Button("Add") { + savedServers.append(.init(apiURL: baseURL, secret: secret)) + + print(savedServers) + } + } + + List(savedServers, id: \.id) { server in + APIServerItem(server: server.apiURL) { + + ConfigManager.shared.overrideApiURL = .init(string: server.apiURL) + ConfigManager.shared.overrideSecret = server.secret + + ApiRequest.requestVersion { version in + if let version { + connectInfo = "" + print(version) + ConfigManager.shared.isRunning = true + } else { + connectInfo = "Failed to connect" + } + } + } onDelete: { + savedServers.removeAll { + $0.id == server.id + } + } + } + .introspect(.table, on: .macOS(.v12, .v13, .v14)) { + $0.backgroundColor = NSColor.clear + $0.enclosingScrollView?.drawsBackground = false + } + } + .padding(.top) + .fixedSize(horizontal: true, vertical: false) + .frame(maxWidth: .infinity, maxHeight: .infinity) + } + +} + +struct APISettingView_Previews: PreviewProvider { + static var previews: some View { + APISettingView() + } +} diff --git a/ClashX/Dashboard/Views/APISetting/ClashServerAppStorage.swift b/ClashX/Dashboard/Views/APISetting/ClashServerAppStorage.swift new file mode 100644 index 000000000..f30e3efaa --- /dev/null +++ b/ClashX/Dashboard/Views/APISetting/ClashServerAppStorage.swift @@ -0,0 +1,36 @@ +// +// ClashServerAppStorage.swift +// ClashX Dashboard +// +// + +import Foundation +import SwiftUI + +typealias SavedServersAppStorage = [ClashServerAppStorage] + +struct ClashServerAppStorage: Codable, Identifiable { + var id = UUID().uuidString + let apiURL: String + let secret: String +} + +extension SavedServersAppStorage: RawRepresentable { + public init?(rawValue: String) { + guard let data = rawValue.data(using: .utf8), + let result = try? JSONDecoder().decode(SavedServersAppStorage.self, from: data) + else { + return nil + } + self = result + } + + public var rawValue: String { + guard let data = try? JSONEncoder().encode(self), + let result = String(data: data, encoding: .utf8) + else { + return "[]" + } + return result + } +} diff --git a/ClashX/Dashboard/Views/APISetting/ProgressButton.swift b/ClashX/Dashboard/Views/APISetting/ProgressButton.swift new file mode 100644 index 000000000..40642bc2c --- /dev/null +++ b/ClashX/Dashboard/Views/APISetting/ProgressButton.swift @@ -0,0 +1,70 @@ +// +// ProgressButton.swift +// ClashX Dashboard +// +// + +import SwiftUI +import AppKit + +struct ProgressButton: View { + + @State var title: String + @State var title2: String + @State var iconName: String + @Binding var inProgress: Bool + + @State var autoWidth = true + + @State var action: () -> Void + + var body: some View { + Button() { + action() + } label: { + HStack { + VStack { + if inProgress { + ProgressView() + .controlSize(.small) + } else { + Image(systemName: iconName) + } + } + .frame(width: 12) + + if title != "" { + Spacer() + + Text(inProgress ? title2 : title) + .font(.system(size: 13)) + + Spacer() + } + } + .animation(.default, value: inProgress) + .foregroundColor(inProgress ? .gray : .blue) + } + .disabled(inProgress) + .frame(width: autoWidth ? ProgressButton.width([title, title2]) : nil) + } + + static func width(_ titles: [String]) -> CGFloat { + let str = titles.max { + $0.count < $1.count + } ?? "" + + if str == "" { + return 12 + 8 + } + + let w = str.size(withAttributes: [.font: NSFont.systemFont(ofSize: 13)]).width + return w + 12 + 45 + } +} + +//struct ProgressButton_Previews: PreviewProvider { +// static var previews: some View { +// ProgressButton() +// } +//} diff --git a/ClashX/Dashboard/Views/ArrayExtensions.swift b/ClashX/Dashboard/Views/ArrayExtensions.swift new file mode 100644 index 000000000..6bf6229fa --- /dev/null +++ b/ClashX/Dashboard/Views/ArrayExtensions.swift @@ -0,0 +1,39 @@ +// +// ArrayExtensions.swift +// ClashX Dashboard +// +// + +import Foundation + + +extension Array where Element: NSObject { + func sorted(descriptors: [NSSortDescriptor]) -> [Element] { + return (self as NSArray).sortedArray(using: descriptors) as! [Element] + } + + func filtered(_ str: String, for keys: [String]) -> [Element] { + + guard str != "", keys.count > 0 else { return self } + + let format = keys.map { + $0 + " CONTAINS[c] %@" + }.joined(separator: " OR ") + + let arg = str as CVarArg + + let args: [CVarArg] = { + let args = NSMutableArray() + for _ in 0.. 1000 { + self.logStorage.logs.removeFirst(100) + } + } + } + + func didUpdateMemory(memory: Int64) { + let v = ByteCountFormatter().string(fromByteCount: memory) + + if overviewData.memory != v { + overviewData.memory = v + } + } + +} + +fileprivate let TrafficHistoryLimit = 120 + +class ClashOverviewData: ObservableObject, Identifiable { + let id = UUID().uuidString + + @Published var uploadString = "N/A" + @Published var downloadString = "N/A" + + @Published var downloadTotal = "N/A" + @Published var uploadTotal = "N/A" + + @Published var activeConns = "0" + + @Published var memory = "0 MB" + + @Published var downloadHistories = [CGFloat](repeating: 0, count: TrafficHistoryLimit) + @Published var uploadHistories = [CGFloat](repeating: 0, count: TrafficHistoryLimit) + + var down: Int = 0 { + didSet { + downloadString = getSpeedString(for: down) + downloadHistories.append(CGFloat(down)) + + if downloadHistories.count > 120 { + downloadHistories.removeFirst() + } + } + } + + var up: Int = 0 { + didSet { + uploadString = getSpeedString(for: up) + uploadHistories.append(CGFloat(up)) + + if uploadHistories.count > 120 { + uploadHistories.removeFirst() + } + } + } + + var downTotal: Int = 0 { + didSet { + downloadTotal = getSpeedString(for: downTotal).replacingOccurrences(of: "/s", with: "") + } + } + + var upTotal: Int = 0 { + didSet { + uploadTotal = getSpeedString(for: upTotal).replacingOccurrences(of: "/s", with: "") + } + } + + func getSpeedString(for byte: Int) -> String { + let kb = byte / 1000 + if kb < 1000 { + return "\(kb)KB/s" + } else { + let mb = Double(kb) / 1000 + if mb >= 100 { + if mb >= 1000 { + return String(format: "%.1fGB/s", mb/1000) + } + return String(format: "%.1fMB/s", mb) + } else { + return String(format: "%.2fMB/s", mb) + } + } + } +} + +class ClashLogStorage: ObservableObject { + @Published var logs = [ClashLog]() + + class ClashLog: NSObject, ObservableObject { + let id: String + + let date: Date + let level: ClashLogLevel + @objc let log: String + + let levelColor: NSColor + @objc let levelString: String + + init(level: String, log: String) { + id = UUID().uuidString + date = Date() + + self.level = .init(rawValue: level) ?? .unknow + self.log = log + + self.levelString = level + switch self.level { + case .info: + levelColor = .systemBlue + case .warning: + levelColor = .systemYellow + case .error: + levelColor = .systemRed + case .debug: + levelColor = .systemGreen + default: + levelColor = .white + } + } + } +} + +class ClashConnsStorage: ObservableObject { + @Published var conns = [DBConnection]() +} diff --git a/ClashX/Dashboard/Views/ColorExtension.swift b/ClashX/Dashboard/Views/ColorExtension.swift new file mode 100644 index 000000000..6b945eda4 --- /dev/null +++ b/ClashX/Dashboard/Views/ColorExtension.swift @@ -0,0 +1,18 @@ +// +// ColorExtension.swift +// ClashX Dashboard +// +// + +import Foundation +import SwiftUI + +extension Color { + init(compatible nsColor: NSColor) { + if #available(macOS 12.0, *) { + self.init(nsColor: nsColor) + } else { + self.init(nsColor.usingColorSpace(.sRGB) ?? .black) + } + } +} diff --git a/ClashX/Dashboard/Views/ContentTabs/Config/ConfigItemView.swift b/ClashX/Dashboard/Views/ContentTabs/Config/ConfigItemView.swift new file mode 100644 index 000000000..393db1601 --- /dev/null +++ b/ClashX/Dashboard/Views/ContentTabs/Config/ConfigItemView.swift @@ -0,0 +1,36 @@ +// +// ConfigItemView.swift +// ClashX Dashboard +// +// + +import SwiftUI + +struct ConfigItemView: View { + + @State var name: String + var content: () -> Content + + var body: some View { + VStack(alignment: .leading, spacing: 8) { + HStack { + Text(name) + .font(.subheadline) + .foregroundColor(.secondary) + Spacer() + } + HStack(content: content) + } + .padding(EdgeInsets(top: 10, leading: 13, bottom: 10, trailing: 13)) + .background(Color(compatible: .textBackgroundColor)) + .cornerRadius(10) + } +} + +struct ConfigItemView_Previews: PreviewProvider { + static var previews: some View { + ConfigItemView(name: "test") { + Text("label") + } + } +} diff --git a/ClashX/Dashboard/Views/ContentTabs/Config/ConfigView.swift b/ClashX/Dashboard/Views/ContentTabs/Config/ConfigView.swift new file mode 100644 index 000000000..2b8860cd5 --- /dev/null +++ b/ClashX/Dashboard/Views/ContentTabs/Config/ConfigView.swift @@ -0,0 +1,257 @@ +// +// ConfigView.swift +// ClashX Dashboard +// +// + +import SwiftUI + +struct ConfigView: View { + + @State var httpPort: Int = 0 + @State var socks5Port: Int = 0 + @State var mixedPort: Int = 0 + @State var redirPort: Int = 0 + @State var mode: ClashProxyMode = .direct + @State var logLevel: ClashLogLevel = .unknow + @State var allowLAN: Bool = false + @State var sniffer: Bool = false + @State var ipv6: Bool = false + + @State var enableTUNDevice: Bool = false + @State var tunIPStack: String = "System" + @State var deviceName: String = "utun9" + @State var interfaceName: String = "en0" + + @State private var configInited = false + + private let toggleStyle = SwitchToggleStyle() + + var body: some View { + ScrollView { + modeView + + content1 + .padding() + + Divider() + .padding() + + tunView + .padding() + + Divider() + .padding() + + content2 + .padding() + } + .disabled(!configInited) + .onAppear { + configInited = false + ApiRequest.requestConfig { config in + httpPort = config.port + socks5Port = config.socksPort + mixedPort = config.mixedPort + redirPort = config.redirPort + mode = config.mode + logLevel = config.logLevel + + allowLAN = config.allowLan + sniffer = config.sniffing + ipv6 = config.ipv6 + + enableTUNDevice = config.tun.enable + tunIPStack = config.tun.stack + deviceName = config.tun.device + interfaceName = config.interfaceName + + configInited = true + } + } + .onDisappear { + configInited = false + } + } + + + var modeView: some View { + Picker("", selection: $mode) { + ForEach([ + ClashProxyMode.direct, + .rule, + .global + ], id: \.self) { + Text($0.name).tag($0) + } + } + .onChange(of: mode) { newValue in + guard configInited else { return } + ApiRequest.updateOutBoundMode(mode: newValue) + } + .padding() + .controlSize(.large) + .labelsHidden() + .pickerStyle(.segmented) + } + + var content1: some View { + LazyVGrid(columns: [ + GridItem(.flexible()), + GridItem(.flexible()) + ], alignment: .leading) { + + ConfigItemView(name: "Http Port") { + Text(String(httpPort)) + .font(.system(size: 17)) + } + + ConfigItemView(name: "Socks5 Port") { + Text(String(socks5Port)) + .font(.system(size: 17)) + } + + ConfigItemView(name: "Mixed Port") { + Text(String(mixedPort)) + .font(.system(size: 17)) + } + + ConfigItemView(name: "Redir Port") { + Text(String(redirPort)) + .font(.system(size: 17)) + } + + ConfigItemView(name: "Log Level") { + Text(logLevel.rawValue.capitalized) + .font(.system(size: 17)) + +// Picker("", selection: $logLevel) { +// ForEach([ +// ClashLogLevel.silent, +// .error, +// .warning, +// .info, +// .debug, +// .unknow +// ], id: \.self) { +// Text($0.rawValue.capitalized).tag($0) +// } +// } +// .disabled(true) +// .pickerStyle(.menu) + } + + ConfigItemView(name: "ipv6") { + Toggle("", isOn: $ipv6) + .toggleStyle(toggleStyle) + .disabled(true) + } + } + } + + var tunView: some View { + LazyVGrid(columns: [ + GridItem(.flexible()), + GridItem(.flexible()) + ], alignment: .leading) { + + + ConfigItemView(name: "Enable TUN Device") { + Toggle("", isOn: $enableTUNDevice) + .toggleStyle(toggleStyle) + } + + + ConfigItemView(name: "TUN IP Stack") { +// Picker("", selection: $tunIPStack) { +// ForEach(["gVisor", "System", "LWIP"], id: \.self) { +// Text($0) +// } +// } +// .pickerStyle(.menu) + + Text(tunIPStack) + .font(.system(size: 17)) + } + + + ConfigItemView(name: "Device Name") { + Text(deviceName) + .font(.system(size: 17)) + } + + + ConfigItemView(name: "Interface Name") { + Text(interfaceName) + .font(.system(size: 17)) + } + + } + } + + var content2: some View { + LazyVGrid(columns: [ + GridItem(.flexible()), + GridItem(.flexible()) + ], alignment: .leading) { + + ConfigItemView(name: "Allow LAN") { + Toggle("", isOn: $allowLAN) + .toggleStyle(toggleStyle) + .onChange(of: allowLAN) { newValue in + guard configInited else { return } + ApiRequest.updateAllowLan(allow: newValue) { + ApiRequest.requestConfig { config in + allowLAN = config.allowLan + } + } + } + } + + ConfigItemView(name: "Sniffer") { + Toggle("", isOn: $sniffer) + .toggleStyle(toggleStyle) + .onChange(of: sniffer) { newValue in + guard configInited else { return } + ApiRequest.updateSniffing(enable: newValue) { + ApiRequest.requestConfig { config in + sniffer = config.sniffing + } + } + } + } + + /* + ConfigItemView(name: "Reload") { + Button { + AppDelegate.shared.updateConfig() + } label: { + Text("Reload config file") + } + } + */ + + ConfigItemView(name: "GEO Databases") { + Button { + ApiRequest.updateGEO() + } label: { + Text("Update GEO Databases") + } + } + + ConfigItemView(name: "FakeIP") { + Button { + ApiRequest.flushFakeipCache() + } label: { + Text("Flush fake-iP data") + } + } + } + } +} + +//struct ConfigView_Previews: PreviewProvider { +// static var previews: some View { +// ConfigView() +// } +//} diff --git a/ClashX/Dashboard/Views/ContentTabs/Connections/Connections.swift b/ClashX/Dashboard/Views/ContentTabs/Connections/Connections.swift new file mode 100644 index 000000000..20aaef68a --- /dev/null +++ b/ClashX/Dashboard/Views/ContentTabs/Connections/Connections.swift @@ -0,0 +1,37 @@ +// +// Connections.swift +// ClashX Dashboard +// +// + +import Cocoa + +class Connections: ObservableObject, Identifiable { + let id = UUID() + @Published var items: [ConnectionItem] + + init(_ items: [ConnectionItem]) { + self.items = items + } +} + + +class ConnectionItem: ObservableObject, Decodable { + let id: String + + let host: String + let sniffHost: String + let process: String + let dl: String + let ul: String + let dlSpeed: String + let ulSpeed: String + let chains: String + let rule: String + let time: String + let source: String + let destinationIP: String + let type: String + + +} diff --git a/ClashX/Dashboard/Views/ContentTabs/Connections/ConnectionsTableView.swift b/ClashX/Dashboard/Views/ContentTabs/Connections/ConnectionsTableView.swift new file mode 100644 index 000000000..e1d1d0e7a --- /dev/null +++ b/ClashX/Dashboard/Views/ContentTabs/Connections/ConnectionsTableView.swift @@ -0,0 +1,312 @@ +// +// ConnectionsTableView.swift +// ClashX Dashboard +// +// +import SwiftUI +import AppKit + +struct ConnectionsTableView: NSViewRepresentable { + + enum TableColumn: String, CaseIterable { + case host = "Host" + case sniffHost = "Sniff Host" + case process = "Process" + case dlSpeed = "DL Speed" + case ulSpeed = "UL Speed" + case dl = "DL" + case ul = "UL" + case chain = "Chain" + case rule = "Rule" + case time = "Time" + case source = "Source" + case destinationIP = "Destination IP" + case type = "Type" + } + + + var data: [Item] + var filterString: String + + var startFormatter: RelativeDateTimeFormatter = { + let startFormatter = RelativeDateTimeFormatter() + startFormatter.unitsStyle = .short + return startFormatter + }() + + var byteCountFormatter = ByteCountFormatter() + + class NonRespondingScrollView: NSScrollView { + override var acceptsFirstResponder: Bool { false } + } + + class NonRespondingTableView: NSTableView { + override var acceptsFirstResponder: Bool { false } + } + + func makeNSView(context: Context) -> NSScrollView { + + let scrollView = NonRespondingScrollView() + scrollView.hasVerticalScroller = true + scrollView.hasHorizontalScroller = true + scrollView.autohidesScrollers = true + + let tableView = NonRespondingTableView() + tableView.usesAlternatingRowBackgroundColors = true + + tableView.delegate = context.coordinator + tableView.dataSource = context.coordinator + + let menu = NSMenu() + menu.showsStateColumn = true + tableView.headerView?.menu = menu + + + TableColumn.allCases.forEach { + let tableColumn = NSTableColumn(identifier: .init("ConnectionsTableView." + $0.rawValue)) + tableColumn.title = $0.rawValue + tableColumn.isEditable = false + + tableColumn.minWidth = 50 + tableColumn.maxWidth = .infinity + + + tableView.addTableColumn(tableColumn) + + var sort: NSSortDescriptor? + + switch $0 { + case .host: + sort = .init(keyPath: \DBConnectionObject.host, ascending: true) + case .sniffHost: + sort = .init(keyPath: \DBConnectionObject.sniffHost, ascending: true) + case .process: + sort = .init(keyPath: \DBConnectionObject.process, ascending: true) + case .dlSpeed: + sort = .init(keyPath: \DBConnectionObject.downloadSpeed, ascending: true) + case .ulSpeed: + sort = .init(keyPath: \DBConnectionObject.uploadSpeed, ascending: true) + case .dl: + sort = .init(keyPath: \DBConnectionObject.download, ascending: true) + case .ul: + sort = .init(keyPath: \DBConnectionObject.upload, ascending: true) + case .chain: + sort = .init(keyPath: \DBConnectionObject.chainString, ascending: true) + case .rule: + sort = .init(keyPath: \DBConnectionObject.ruleString, ascending: true) + case .time: + sort = .init(keyPath: \DBConnectionObject.startDate, ascending: true) + case .source: + sort = .init(keyPath: \DBConnectionObject.source, ascending: true) + case .destinationIP: + sort = .init(keyPath: \DBConnectionObject.destinationIP, ascending: true) + case .type: + sort = .init(keyPath: \DBConnectionObject.type, ascending: true) + } + + tableColumn.sortDescriptorPrototype = sort + + let item = NSMenuItem( + title: $0.rawValue, + action: #selector(context.coordinator.toggleColumn(_:)), + keyEquivalent: "") + item.target = context.coordinator + item.representedObject = tableColumn + + menu.addItem(item) + } + + + if let sort = tableView.tableColumns.first?.sortDescriptorPrototype { + tableView.sortDescriptors = [sort] + } + + + scrollView.documentView = tableView + + tableView.autosaveName = "ClashX_Dashboard.Connections.TableView" + tableView.autosaveTableColumns = true + + menu.items.forEach { + guard let column = $0.representedObject as? NSTableColumn else { return } + $0.state = column.isHidden ? .off : .on + } + + return scrollView + } + + func updateNSView(_ nsView: NSScrollView, context: Context) { + context.coordinator.parent = self + guard let tableView = nsView.documentView as? NSTableView, + let data = data as? [DBConnection] else { + return + } + + var conns = data.map(DBConnectionObject.init) + + let connHistorys = context.coordinator.connHistorys + conns.forEach { + $0.updateSpeeds(connHistorys[$0.id]) + } + + conns = updateSorts(conns, tableView: tableView) + context.coordinator.updateConns(conns, for: tableView) + } + + func updateSorts(_ objects: [DBConnectionObject], + tableView: NSTableView) -> [DBConnectionObject] { + var re = objects + + var sortDescriptors = [NSSortDescriptor]() + + if let sort = tableView.sortDescriptors.first { + sortDescriptors.append(sort) + } + + sortDescriptors.append(.init(keyPath: \DBConnectionObject.id, ascending: true)) + re = re.sorted(descriptors: sortDescriptors) + + let filterKeys = [ + "host", + "process", + "chainString", + "ruleString", + "source", + "destinationIP", + "type", + ] + + re = re.filtered(filterString, for: filterKeys) + + return re + } + + + func makeCoordinator() -> Coordinator { + Coordinator(parent: self) + } + + class Coordinator: NSObject, NSTableViewDelegate, NSTableViewDataSource { + + var parent: ConnectionsTableView + + var conns = [DBConnectionObject]() + var connHistorys = [String: (download: Int64, upload: Int64)]() + + init(parent: ConnectionsTableView) { + self.parent = parent + } + + func updateConns(_ conns: [DBConnectionObject], for tableView: NSTableView) { + let changes = conns.difference(from: self.conns) { + $0.id == $1.id + } + + for change in changes { + switch change { + case .remove(_, let conn, _): + connHistorys[conn.id] = nil + default: + break + } + } + conns.forEach { + connHistorys[$0.id] = ($0.download, $0.upload) + } + + let selectedID: String? = { + let selectedRow = tableView.selectedRow + guard selectedRow >= 0, selectedRow < self.conns.count else { + return nil + } + return self.conns[selectedRow].id + }() + + + guard let partialChanges = self.conns.applying(changes) else { + return + } + self.conns = conns + + let indicesToReload = IndexSet(zip(partialChanges, conns).enumerated().compactMap { index, pair -> Int? in + (pair.0.id == pair.1.id && pair.0 != pair.1) ? index : nil + }) + + tableView.reloadData(changes, indexs: indicesToReload) + + if let index = self.conns.firstIndex(where: { $0.id == selectedID }) { + tableView.selectRowIndexes(.init(integer: index), byExtendingSelection: true) + } + } + + + func numberOfRows(in tableView: NSTableView) -> Int { + conns.count + } + + + func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? { + + guard let identifier = tableColumn?.identifier, + let cellView = tableView.makeCellView(with: identifier.rawValue, owner: self), + let s = identifier.rawValue.split(separator: ".").last, + let tc = TableColumn(rawValue: String(s)), + row >= 0, + row < conns.count, + let tf = cellView.textField + else { return nil } + + let conn = conns[row] + + tf.isEditable = false + tf.isSelectable = true + tf.objectValue = { + switch tc { + case .host: + return conn.host + case .sniffHost: + return conn.sniffHost + case .process: + return conn.process + case .dlSpeed: + return conn.downloadSpeedString +// return conn.downloadSpeed + case .ulSpeed: + return conn.uploadSpeedString +// return conn.uploadSpeed + case .dl: + return conn.downloadString + case .ul: + return conn.uploadString + case .chain: + return conn.chainString + case .rule: + return conn.ruleString + case .time: + return conn.startString + case .source: + return conn.source + case .destinationIP: + return conn.destinationIP + case .type: + return conn.type + } + }() + + return cellView + } + + func tableView(_ tableView: NSTableView, sortDescriptorsDidChange oldDescriptors: [NSSortDescriptor]) { + conns = parent.updateSorts(conns, tableView: tableView) + tableView.reloadData() + } + + @objc func toggleColumn(_ menuItem: NSMenuItem) { + guard let column = menuItem.representedObject as? NSTableColumn else { return } + let hide = menuItem.state == .on + column.isHidden = hide + menuItem.state = hide ? .off : .on + } + + } +} diff --git a/ClashX/Dashboard/Views/ContentTabs/Connections/ConnectionsView.swift b/ClashX/Dashboard/Views/ContentTabs/Connections/ConnectionsView.swift new file mode 100644 index 000000000..eb8f40d32 --- /dev/null +++ b/ClashX/Dashboard/Views/ContentTabs/Connections/ConnectionsView.swift @@ -0,0 +1,46 @@ +// +// ConnectionsView.swift +// ClashX Dashboard +// +// + +import SwiftUI + +struct ConnectionsView: View { + + @EnvironmentObject var data: ClashConnsStorage + + @State private var searchString: String = "" + + var body: some View { + + ConnectionsTableView(data: data.conns, + filterString: searchString) + .background(Color(compatible: .textBackgroundColor)) + .onAppear { + guard let s = ToolbarStore.shared.searchStrings["conns"] else { return } + searchString = s + NotificationCenter.default.post(name: .initSearchString, object: nil, userInfo: ["string": s]) + } + .onDisappear { + ToolbarStore.shared.searchStrings["conns"] = searchString + } + .onReceive(NotificationCenter.default.publisher(for: .toolbarSearchString)) { + guard let string = $0.userInfo?["String"] as? String else { return } + searchString = string + } + .onReceive(NotificationCenter.default.publisher(for: .stopConns)) { _ in + stopConns() + } + } + + func stopConns() { + ApiRequest.closeAllConnection() + } +} + +struct ConnectionsView_Previews: PreviewProvider { + static var previews: some View { + ConnectionsView() + } +} diff --git a/ClashX/Dashboard/Views/ContentTabs/Logs/LogsTableView.swift b/ClashX/Dashboard/Views/ContentTabs/Logs/LogsTableView.swift new file mode 100644 index 000000000..3f346dd61 --- /dev/null +++ b/ClashX/Dashboard/Views/ContentTabs/Logs/LogsTableView.swift @@ -0,0 +1,171 @@ +// +// LogsTableView.swift +// +// +// + +import Cocoa +import SwiftUI + +struct LogsTableView: NSViewRepresentable { + + enum TableColumn: String, CaseIterable { + case date = "Date" + case level = "Level" + case log = "Log" + } + + var data: [Item] + var filterString: String + + class NonRespondingScrollView: NSScrollView { + override var acceptsFirstResponder: Bool { false } + } + + class NonRespondingTableView: NSTableView { + override var acceptsFirstResponder: Bool { false } + } + + func makeNSView(context: Context) -> NSScrollView { + + let scrollView = NonRespondingScrollView() + scrollView.hasVerticalScroller = true + scrollView.hasHorizontalScroller = false + scrollView.autohidesScrollers = true + + let tableView = NonRespondingTableView() + tableView.usesAlternatingRowBackgroundColors = true + + tableView.delegate = context.coordinator + tableView.dataSource = context.coordinator + + TableColumn.allCases.forEach { + let tableColumn = NSTableColumn(identifier: .init("LogsTableView." + $0.rawValue)) + tableColumn.title = $0.rawValue + tableColumn.isEditable = false + + switch $0 { + case .date: + tableColumn.minWidth = 60 + tableColumn.maxWidth = 140 + tableColumn.width = 135 + case .level: + tableColumn.minWidth = 40 + tableColumn.maxWidth = 65 + default: + tableColumn.minWidth = 120 + tableColumn.maxWidth = .infinity + } + + tableView.addTableColumn(tableColumn) + } + + scrollView.documentView = tableView + + return scrollView + } + + func updateNSView(_ nsView: NSScrollView, context: Context) { + context.coordinator.parent = self + guard let tableView = nsView.documentView as? NSTableView, + var data = data as? [ClashLogStorage.ClashLog] else { + return + } + data = updateSorts(data, tableView: tableView) + context.coordinator.updateLogs(data, for: tableView) + } + + func updateSorts(_ objects: [ClashLogStorage.ClashLog], + tableView: NSTableView) -> [ClashLogStorage.ClashLog] { + var re = objects + + let filterKeys = [ + "levelString", + "log", + ] + + re = re.filtered(filterString, for: filterKeys) + + return re + } + + + func makeCoordinator() -> Coordinator { + Coordinator(parent: self) + } + + + class Coordinator: NSObject, NSTableViewDelegate, NSTableViewDataSource { + + var parent: LogsTableView + var logs = [ClashLogStorage.ClashLog]() + + let dateFormatter = { + let df = DateFormatter() + df.dateFormat = "MM/dd HH:mm:ss.SSS" + return df + }() + + init(parent: LogsTableView) { + self.parent = parent + } + + func updateLogs(_ logs: [ClashLogStorage.ClashLog], for tableView: NSTableView) { + + let changes = logs.difference(from: self.logs) { + $0.id == $1.id + } + + guard let partialChanges = self.logs.applying(changes) else { return } + + self.logs = partialChanges + + let indicesToReload = IndexSet(zip(partialChanges, logs).enumerated().compactMap { index, pair -> Int? in + (pair.0.id == pair.1.id && pair.0 != pair.1) ? index : nil + }) + + tableView.reloadData(changes, indexs: indicesToReload) + } + + + func numberOfRows(in tableView: NSTableView) -> Int { + logs.count + } + + + func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? { + + guard let identifier = tableColumn?.identifier, + let cellView = tableView.makeCellView(with: identifier.rawValue, owner: self), + let s = identifier.rawValue.split(separator: ".").last, + let tc = TableColumn(rawValue: String(s)), + row >= 0, + row < logs.count, + let tf = cellView.textField + else { return nil } + + let log = logs[row] + + tf.isEditable = false + tf.isSelectable = false + + switch tc { + case .date: + tf.lineBreakMode = .byTruncatingHead + tf.textColor = .orange + tf.stringValue = dateFormatter.string(from: log.date) + case .level: + tf.lineBreakMode = .byTruncatingTail + tf.textColor = log.levelColor + tf.stringValue = log.levelString + case .log: + tf.lineBreakMode = .byTruncatingTail + tf.textColor = .labelColor + tf.stringValue = log.log + tf.isSelectable = true + } + + return cellView + } + } +} diff --git a/ClashX/Dashboard/Views/ContentTabs/Logs/LogsView.swift b/ClashX/Dashboard/Views/ContentTabs/Logs/LogsView.swift new file mode 100644 index 000000000..d27d9c62b --- /dev/null +++ b/ClashX/Dashboard/Views/ContentTabs/Logs/LogsView.swift @@ -0,0 +1,49 @@ +// +// LogsView.swift +// ClashX Dashboard +// +// + +import SwiftUI + +struct LogsView: View { + + @EnvironmentObject var logStorage: ClashLogStorage + + @State var searchString: String = "" + @State var logLevel = ConfigManager.selectLoggingApiLevel + + var body: some View { + Group { + LogsTableView(data: logStorage.logs.reversed(), filterString: searchString) + } + .onAppear { + guard let s = ToolbarStore.shared.searchStrings["logs"] else { return } + searchString = s + NotificationCenter.default.post(name: .initSearchString, object: nil, userInfo: ["string": s]) + } + .onDisappear { + ToolbarStore.shared.searchStrings["logs"] = searchString + } + .onReceive(NotificationCenter.default.publisher(for: .toolbarSearchString)) { + guard let string = $0.userInfo?["String"] as? String else { return } + searchString = string + } + .onReceive(NotificationCenter.default.publisher(for: .logLevelChanged)) { + guard let level = $0.userInfo?["level"] as? ClashLogLevel else { return } + logLevelChanged(level) + } + } + + func logLevelChanged(_ level: ClashLogLevel) { + logStorage.logs.removeAll() + ConfigManager.selectLoggingApiLevel = level + ApiRequest.shared.resetLogStreamApi() + } +} + +struct LogsView_Previews: PreviewProvider { + static var previews: some View { + LogsView() + } +} diff --git a/ClashX/Dashboard/Views/ContentTabs/Overview/OverviewTopItemView.swift b/ClashX/Dashboard/Views/ContentTabs/Overview/OverviewTopItemView.swift new file mode 100644 index 000000000..9ff771121 --- /dev/null +++ b/ClashX/Dashboard/Views/ContentTabs/Overview/OverviewTopItemView.swift @@ -0,0 +1,37 @@ +// +// OverviewTopItemView.swift +// ClashX Dashboard +// +// + +import SwiftUI + +struct OverviewTopItemView: View { + + @State var name: String + @Binding var value: String + + var body: some View { + VStack(alignment: .leading, spacing: 8) { + HStack { + Text(name) + .font(.subheadline) + .foregroundColor(.secondary) + Spacer() + } + Text(value) + .font(.system(size: 16)) + } + .frame(width: 125) + .padding(EdgeInsets(top: 10, leading: 13, bottom: 10, trailing: 13)) + .background(Color(compatible: .textBackgroundColor)) + .cornerRadius(10) + } +} + +struct OverviewTopItemView_Previews: PreviewProvider { + @State static var value: String = "Value" + static var previews: some View { + OverviewTopItemView(name: "Name", value: $value) + } +} diff --git a/ClashX/Dashboard/Views/ContentTabs/Overview/OverviewView.swift b/ClashX/Dashboard/Views/ContentTabs/Overview/OverviewView.swift new file mode 100644 index 000000000..7abae940e --- /dev/null +++ b/ClashX/Dashboard/Views/ContentTabs/Overview/OverviewView.swift @@ -0,0 +1,92 @@ +// +// OverviewView.swift +// ClashX Dashboard +// +// + +import SwiftUI +import DSFSparkline + +struct OverviewView: View { + + @EnvironmentObject var data: ClashOverviewData + + @State private var columnCount: Int = 4 + + @State private var version: String = "" + + var body: some View { + VStack(spacing: 25) { + + ZStack(alignment: .topLeading) { + LazyVGrid(columns: Array(repeating: GridItem(.flexible()), count: columnCount)) { + + OverviewTopItemView(name: "Upload", value: $data.uploadString) + OverviewTopItemView(name: "Download", value: $data.downloadString) + OverviewTopItemView(name: "Upload Total", value: $data.uploadTotal) + OverviewTopItemView(name: "Download Total", value: $data.downloadTotal) + + OverviewTopItemView(name: "Active Connections", value: $data.activeConns) + OverviewTopItemView(name: "Memory Usage", value: $data.memory) + OverviewTopItemView(name: "Mihomo", value: $version) + } + GeometryReader { geometry in + Rectangle() + .fill(.clear) + .frame(height: 1) + .onChange(of: geometry.size.width) { newValue in + updateColumnCount(newValue) + } + .onAppear { + updateColumnCount(geometry.size.width) + } + } + .frame(height: 1) + .padding() + } + + + HStack { + RoundedRectangle(cornerRadius: 2) + .fill(Color(compatible: .systemBlue)) + .frame(width: 20, height: 13) + Text("Down") + + RoundedRectangle(cornerRadius: 2) + .fill(Color(compatible: .systemGreen)) + .frame(width: 20, height: 13) + Text("Up") + } + + + TrafficGraphView(values: $data.downloadHistories, + graphColor: .systemBlue) + + TrafficGraphView(values: $data.uploadHistories, + graphColor: .systemGreen) + + } + .padding() + .onAppear { + ApiRequest.requestVersion { + self.version = $0?.version ?? "" + } + } + } + + func updateColumnCount(_ width: Double) { + let v = Int(Int(width) / 155) + let new = v == 0 ? 1 : v + + if new != columnCount { + columnCount = new + } + } + +} + +//struct OverviewView_Previews: PreviewProvider { +// static var previews: some View { +// OverviewView() +// } +//} diff --git a/ClashX/Dashboard/Views/ContentTabs/Overview/TrafficGraphView.swift b/ClashX/Dashboard/Views/ContentTabs/Overview/TrafficGraphView.swift new file mode 100644 index 000000000..6a7b8817d --- /dev/null +++ b/ClashX/Dashboard/Views/ContentTabs/Overview/TrafficGraphView.swift @@ -0,0 +1,148 @@ +// +// TrafficGraphView.swift +// ClashX Dashboard +// +// + +import SwiftUI +import DSFSparkline + +fileprivate let labelsCount = 4 + +struct TrafficGraphView: View { + @Binding var values: [CGFloat] + @State var graphColor: DSFColor + + init(values: Binding<[CGFloat]>, + graphColor: DSFColor) { + self._values = values + self.graphColor = graphColor + } + + + @State private var labels = [String]() + @State private var dataSource = DSFSparkline.DataSource() + @State private var currentMaxValue: CGFloat = 0 + + var body: some View { + HStack { + VStack { + ForEach(labels, id: \.self) { + Text($0) + .font(.system(size: 11, weight: .light)) + Spacer() + } + } + graphView + } + .onAppear { + updateChart(values) + } + .onChange(of: values) { newValue in + updateChart(newValue) + } + + } + + var graphView: some View { + ZStack { + DSFSparklineLineGraphView.SwiftUI( + dataSource: dataSource, + graphColor: graphColor, + interpolated: false, + showZeroLine: false + ) + + DSFSparklineSurface.SwiftUI([ + gridOverlay + ]) + } + } + + let gridOverlay: DSFSparklineOverlay = { + let grid = DSFSparklineOverlay.GridLines() + grid.dataSource = .init(values: [1], range: 0...1) + + + var floatValues = [CGFloat]() + for i in 0...labelsCount { + floatValues.append(CGFloat(i) / CGFloat(labelsCount)) + } + let _ = floatValues.removeFirst() + + grid.floatValues = floatValues.reversed() + + grid.strokeColor = DSFColor.systemGray.withAlphaComponent(0.3).cgColor + grid.strokeWidth = 0.5 + grid.dashStyle = [2, 2] + + return grid + }() + + + func updateChart(_ values: [CGFloat]) { + let max = values.max() ?? CGFloat(labelsCount) * 1000 + + if currentMaxValue != 0 && currentMaxValue == max { + self.dataSource.set(values: values) + return + } else { + currentMaxValue = max + } + + let byte = Int64(max) + let kb = byte / 1000 + + var v1: Double = 0 + var v2 = "" + var v3: Double = 1 + + switch kb { + case 0.. Void)? = nil) { + ApiRequest.requestProxyProviderList { resp in + if let p = resp.allProviders[provider.name] { + provider.updateInfo(DBProxyProvider(provider: p)) + } + completeHandler?() + } + } +} + +//struct ProviderProxiesView_Previews: PreviewProvider { +// static var previews: some View { +// ProviderProxiesView() +// } +//} diff --git a/ClashX/Dashboard/Views/ContentTabs/Providers/ProviderRowView.swift b/ClashX/Dashboard/Views/ContentTabs/Providers/ProviderRowView.swift new file mode 100644 index 000000000..1fe907ce1 --- /dev/null +++ b/ClashX/Dashboard/Views/ContentTabs/Providers/ProviderRowView.swift @@ -0,0 +1,51 @@ +// +// ProviderRowView.swift +// ClashX Dashboard +// +// + +import SwiftUI + +struct ProviderRowView: View { + + @ObservedObject var proxyProvider: DBProxyProvider + @EnvironmentObject var hideProxyNames: HideProxyNames + + var body: some View { + NavigationLink { + ProviderProxiesView(provider: proxyProvider) + } label: { + labelView + } + } + + var labelView: some View { + VStack(spacing: 2) { + HStack(alignment: .center) { + Text(hideProxyNames.hide + ? String(proxyProvider.id.prefix(8)) + : proxyProvider.name) + .font(.system(size: 15)) + Spacer() + Text(proxyProvider.trafficPercentage) + .font(.system(size: 12)) + .foregroundColor(.secondary) + } + + HStack { + Text(proxyProvider.vehicleType.rawValue) + Spacer() + Text(proxyProvider.updatedAt) + } + .font(.system(size: 11)) + .foregroundColor(.secondary) + } + .padding(EdgeInsets(top: 1, leading: 4, bottom: 1, trailing: 4)) + } +} + +//struct ProviderRowView_Previews: PreviewProvider { +// static var previews: some View { +// ProviderRowView() +// } +//} diff --git a/ClashX/Dashboard/Views/ContentTabs/Providers/ProvidersView.swift b/ClashX/Dashboard/Views/ContentTabs/Providers/ProvidersView.swift new file mode 100644 index 000000000..ded1ee3af --- /dev/null +++ b/ClashX/Dashboard/Views/ContentTabs/Providers/ProvidersView.swift @@ -0,0 +1,98 @@ +// +// ProvidersView.swift +// ClashX Dashboard +// +// + +import SwiftUI +import SwiftUIIntrospect + +struct ProvidersView: View { + @ObservedObject var providerStorage = DBProviderStorage() + + @State private var searchString = ProxiesSearchString() + + @StateObject private var hideProxyNames = HideProxyNames() + + var body: some View { + + NavigationView { + listView + EmptyView() + } + .onReceive(NotificationCenter.default.publisher(for: .toolbarSearchString)) { + guard let string = $0.userInfo?["String"] as? String else { return } + searchString.string = string + } + .onReceive(NotificationCenter.default.publisher(for: .hideNames)) { + guard let hide = $0.userInfo?["hide"] as? Bool else { return } + hideProxyNames.hide = hide + } + .environmentObject(searchString) + .onAppear { + loadProviders() + } + .environmentObject(hideProxyNames) + } + + var listView: some View { + List { + if providerStorage.proxyProviders.isEmpty, + providerStorage.ruleProviders.isEmpty { + Text("Empty") + .padding() + } else { + Section() { + if !providerStorage.proxyProviders.isEmpty { + ProxyProvidersRowView(providerStorage: providerStorage) + } + if !providerStorage.ruleProviders.isEmpty { + RuleProvidersRowView(providerStorage: providerStorage) + } + } header: { + Text("Providers") + } + } + + if !providerStorage.proxyProviders.isEmpty { + Text("") + Section() { + ForEach(providerStorage.proxyProviders,id: \.id) { + ProviderRowView(proxyProvider: $0) + } + } header: { + Text("Proxy Provider") + } + } + } + .introspect(.table, on: .macOS(.v12, .v13, .v14)) { + $0.refusesFirstResponder = true + $0.doubleAction = nil + } + .listStyle(.plain) + } + + func loadProviders() { + ApiRequest.requestProxyProviderList { resp in + providerStorage.proxyProviders = resp.allProviders.values.filter { + $0.vehicleType == .HTTP + }.sorted { + $0.name < $1.name + } + .map(DBProxyProvider.init) + } + ApiRequest.requestRuleProviderList { resp in + providerStorage.ruleProviders = resp.allProviders.values.sorted { + $0.name < $1.name + } + .map(DBRuleProvider.init) + } + } + +} + +//struct ProvidersView_Previews: PreviewProvider { +// static var previews: some View { +// ProvidersView() +// } +//} diff --git a/ClashX/Dashboard/Views/ContentTabs/Providers/ProxyProviderInfoView.swift b/ClashX/Dashboard/Views/ContentTabs/Providers/ProxyProviderInfoView.swift new file mode 100644 index 000000000..003061dc8 --- /dev/null +++ b/ClashX/Dashboard/Views/ContentTabs/Providers/ProxyProviderInfoView.swift @@ -0,0 +1,89 @@ +// +// ProxyProviderInfoView.swift +// ClashX Dashboard +// +// + +import SwiftUI + +struct ProxyProviderInfoView: View { + + @ObservedObject var provider: DBProxyProvider + @EnvironmentObject var hideProxyNames: HideProxyNames + + @State var withUpdateButton = false + @State var isUpdating = false + + var body: some View { + HStack { + VStack { + header + content + } + + if withUpdateButton { + ProgressButton( + title: "", + title2: "", + iconName: "arrow.clockwise", + inProgress: $isUpdating) { + update() + } + } + } + } + + var header: some View { + HStack() { + Text(hideProxyNames.hide + ? String(provider.id.prefix(8)) + : provider.name) + .font(.system(size: 17)) + Text(provider.vehicleType.rawValue) + .font(.system(size: 13)) + .foregroundColor(.secondary) + Text("\(provider.proxies.count)") + .font(.system(size: 11)) + .padding(EdgeInsets(top: 2, leading: 4, bottom: 2, trailing: 4)) + .background(Color.gray.opacity(0.5)) + .cornerRadius(4) + + Spacer() + } + } + + var content: some View { + VStack { + HStack(spacing: 20) { + Text(provider.trafficInfo) + Text(provider.expireDate) + Spacer() + } + HStack { + Text("Updated \(provider.updatedAt)") + Spacer() + } + } + .font(.system(size: 12)) + .foregroundColor(.secondary) + } + + func update() { + isUpdating = true + let name = provider.name + ApiRequest.updateProvider(for: .proxy, name: name) { _ in + ApiRequest.requestProxyProviderList() { resp in + if let p = resp.allProviders[provider.name] { + provider.updateInfo(DBProxyProvider(provider: p)) + } + isUpdating = false + } + } + } +} + +//struct ProxyProviderInfoView_Previews: PreviewProvider { +// static var previews: some View { +// ProxyProviderInfoView() +// } +//} diff --git a/ClashX/Dashboard/Views/ContentTabs/Providers/ProxyProvidersRowView.swift b/ClashX/Dashboard/Views/ContentTabs/Providers/ProxyProvidersRowView.swift new file mode 100644 index 000000000..827ceeaae --- /dev/null +++ b/ClashX/Dashboard/Views/ContentTabs/Providers/ProxyProvidersRowView.swift @@ -0,0 +1,81 @@ +// +// ProxyProvidersRowView.swift +// ClashX Dashboard +// +// + +import SwiftUI + +struct ProxyProvidersRowView: View { + + @ObservedObject var providerStorage: DBProviderStorage + @EnvironmentObject var searchString: ProxiesSearchString + + @State private var isUpdating = false + + var providers: [DBProxyProvider] { + if searchString.string.isEmpty { + return providerStorage.proxyProviders + } else { + return providerStorage.proxyProviders.filter { + $0.name.lowercased().contains(searchString.string.lowercased()) + } + } + } + + var body: some View { + NavigationLink { + contentView + } label: { + Text("Proxy") + .font(.system(size: 15)) + .padding(EdgeInsets(top: 2, leading: 4, bottom: 2, trailing: 4)) + } + } + + var contentView: some View { + ScrollView { + Section { + VStack(spacing: 16) { + listView + } + } header: { + ProgressButton( + title: "Update All", + title2: "Updating", + iconName: "arrow.clockwise", inProgress: $isUpdating) { + updateAll() + } + } + .padding() + } + } + + var listView: some View { + ForEach(providers, id: \.id) { provider in + ProxyProviderInfoView(provider: provider, withUpdateButton: true) + } + } + + func updateAll() { + isUpdating = true + + ApiRequest.updateAllProviders(for: .proxy) { _ in + ApiRequest.requestProxyProviderList { resp in + providerStorage.proxyProviders = resp.allProviders.values.filter { + $0.vehicleType == .HTTP + }.sorted { + $0.name < $1.name + } + .map(DBProxyProvider.init) + isUpdating = false + } + } + } +} + +//struct AllProvidersRowView_Previews: PreviewProvider { +// static var previews: some View { +// ProxyProvidersRowView() +// } +//} diff --git a/ClashX/Dashboard/Views/ContentTabs/Providers/RuleProviderView.swift b/ClashX/Dashboard/Views/ContentTabs/Providers/RuleProviderView.swift new file mode 100644 index 000000000..1e40902f4 --- /dev/null +++ b/ClashX/Dashboard/Views/ContentTabs/Providers/RuleProviderView.swift @@ -0,0 +1,42 @@ +// +// RuleProviderView.swift +// ClashX Dashboard +// +// + +import SwiftUI + +struct RuleProviderView: View { + + @State var provider: DBRuleProvider + + var body: some View { + + VStack(alignment: .leading) { + HStack { + Text(provider.name) + .font(.title) + .fontWeight(.medium) + Text(provider.type) + Text(provider.behavior) + Spacer() + } + + HStack { + Text("\(provider.ruleCount) rules") + if let date = provider.updatedAt { + Text("Updated \(RelativeDateTimeFormatter().localizedString(for: date, relativeTo: Date()))") + } + Spacer() + } + .font(.system(size: 12)) + .foregroundColor(.secondary) + } + } +} + +//struct RuleProviderView_Previews: PreviewProvider { +// static var previews: some View { +// RuleProviderView() +// } +//} diff --git a/ClashX/Dashboard/Views/ContentTabs/Providers/RuleProvidersRowView.swift b/ClashX/Dashboard/Views/ContentTabs/Providers/RuleProvidersRowView.swift new file mode 100644 index 000000000..8eda677f5 --- /dev/null +++ b/ClashX/Dashboard/Views/ContentTabs/Providers/RuleProvidersRowView.swift @@ -0,0 +1,75 @@ +// +// RuleProvidersRowView.swift +// ClashX Dashboard +// +// + +import SwiftUI + +struct RuleProvidersRowView: View { + + @ObservedObject var providerStorage: DBProviderStorage + @EnvironmentObject var searchString: ProxiesSearchString + + @State private var isUpdating = false + + var providers: [DBRuleProvider] { + if searchString.string.isEmpty { + return providerStorage.ruleProviders + } else { + return providerStorage.ruleProviders.filter { + $0.name.lowercased().contains(searchString.string.lowercased()) + } + } + } + + var body: some View { + NavigationLink { + contentView + } label: { + Text("Rule") + .font(.system(size: 15)) + .padding(EdgeInsets(top: 2, leading: 4, bottom: 2, trailing: 4)) + } + } + + var contentView: some View { + ScrollView { + Section { + VStack(spacing: 12) { + ForEach(providers, id: \.id) { + RuleProviderView(provider: $0) + } + } + } header: { + ProgressButton( + title: "Update All", + title2: "Updating", + iconName: "arrow.clockwise", + inProgress: $isUpdating) { + updateAll() + } + } + .padding() + } + } + + func updateAll() { + isUpdating = true + ApiRequest.updateAllProviders(for: .rule) { _ in + ApiRequest.requestRuleProviderList { resp in + providerStorage.ruleProviders = resp.allProviders.values.sorted { + $0.name < $1.name + } + .map(DBRuleProvider.init) + isUpdating = false + } + } + } +} + +//struct ProxyProvidersRowView_Previews: PreviewProvider { +// static var previews: some View { +// RuleProvidersRowView() +// } +//} diff --git a/ClashX/Dashboard/Views/ContentTabs/Proxies/ProxiesView.swift b/ClashX/Dashboard/Views/ContentTabs/Proxies/ProxiesView.swift new file mode 100644 index 000000000..e050f5b39 --- /dev/null +++ b/ClashX/Dashboard/Views/ContentTabs/Proxies/ProxiesView.swift @@ -0,0 +1,67 @@ +// +// ProxiesView.swift +// ClashX Dashboard +// +// + +import SwiftUI +import SwiftUIIntrospect + +class ProxiesSearchString: ObservableObject, Identifiable { + let id = UUID().uuidString + @Published var string: String = "" +} + +struct ProxiesView: View { + + @ObservedObject var proxyStorage = DBProxyStorage() + + @State private var searchString = ProxiesSearchString() + @State private var isGlobalMode = false + + @StateObject private var hideProxyNames = HideProxyNames() + + var body: some View { + NavigationView { + List(proxyStorage.groups, id: \.id) { group in + ProxyGroupRowView(proxyGroup: group) + } + .introspect(.table, on: .macOS(.v12, .v13, .v14)) { + $0.refusesFirstResponder = true + $0.doubleAction = nil + } + .listStyle(.plain) + EmptyView() + } + .onReceive(NotificationCenter.default.publisher(for: .toolbarSearchString)) { + guard let string = $0.userInfo?["String"] as? String else { return } + searchString.string = string + } + .onReceive(NotificationCenter.default.publisher(for: .hideNames)) { + guard let hide = $0.userInfo?["hide"] as? Bool else { return } + hideProxyNames.hide = hide + } + .environmentObject(searchString) + .onAppear { + loadProxies() + } + .environmentObject(hideProxyNames) + } + + + func loadProxies() { +// self.isGlobalMode = ConfigManager.shared.currentConfig?.mode == .global + ApiRequest.getMergedProxyData { + guard let resp = $0 else { return } + proxyStorage.groups = DBProxyStorage(resp).groups.filter { + isGlobalMode ? true : $0.name != "GLOBAL" + } + } + } +} + +//struct ProxiesView_Previews: PreviewProvider { +// static var previews: some View { +// ProxiesView() +// } +//} diff --git a/ClashX/Dashboard/Views/ContentTabs/Proxies/ProxyGroupRowView.swift b/ClashX/Dashboard/Views/ContentTabs/Proxies/ProxyGroupRowView.swift new file mode 100644 index 000000000..f7895d7fc --- /dev/null +++ b/ClashX/Dashboard/Views/ContentTabs/Proxies/ProxyGroupRowView.swift @@ -0,0 +1,53 @@ +// +// ProxyGroupInfoView.swift +// ClashX Dashboard +// +// + +import SwiftUI + +struct ProxyGroupRowView: View { + + @ObservedObject var proxyGroup: DBProxyGroup + + @EnvironmentObject var hideProxyNames: HideProxyNames + + var body: some View { + NavigationLink { + ProxyGroupView(proxyGroup: proxyGroup) + } label: { + labelView + } + } + + var labelView: some View { + VStack(spacing: 2) { + HStack(alignment: .center) { + Text(hideProxyNames.hide + ? String(proxyGroup.id.prefix(8)) + : proxyGroup.name) + .font(.system(size: 15)) + Spacer() + if let proxy = proxyGroup.currentProxy { + Text(proxy.delayString) + .foregroundColor(proxy.delayColor) + .font(.system(size: 12)) + } + } + + HStack { + Text(proxyGroup.type.rawValue) + Spacer() + if let proxy = proxyGroup.currentProxy { + Text(hideProxyNames.hide + ? String(proxy.id.prefix(8)) + : proxy.name) + } + } + .font(.system(size: 11)) + .foregroundColor(.secondary) + } + .padding(EdgeInsets(top: 3, leading: 4, bottom: 3, trailing: 4)) + } +} + diff --git a/ClashX/Dashboard/Views/ContentTabs/Proxies/ProxyGroupView.swift b/ClashX/Dashboard/Views/ContentTabs/Proxies/ProxyGroupView.swift new file mode 100644 index 000000000..d52f6ad56 --- /dev/null +++ b/ClashX/Dashboard/Views/ContentTabs/Proxies/ProxyGroupView.swift @@ -0,0 +1,157 @@ +// +// ProxyView.swift +// ClashX Dashboard +// +// + +import SwiftUI + +struct ProxyGroupView: View { + + @ObservedObject var proxyGroup: DBProxyGroup + @EnvironmentObject var searchString: ProxiesSearchString + + @EnvironmentObject var hideProxyNames: HideProxyNames + + @State private var columnCount: Int = 3 + @State private var isUpdatingSelect = false + @State private var selectable = false + @State private var isTesting = false + + @State private var groupSelected: String? + + var proxies: [DBProxy] { + if searchString.string.isEmpty { + return proxyGroup.proxies + } else { + return proxyGroup.proxies.filter { + $0.name.lowercased().contains(searchString.string.lowercased()) + } + } + } + + var body: some View { + ZStack { + ScrollView { + Section { + proxyListView + } header: { + proxyInfoView + } + .padding() + } + GeometryReader { geometry in + Rectangle() + .fill(.clear) + .frame(height: 1) + .onChange(of: geometry.size.width) { newValue in + updateColumnCount(newValue) + } + .onAppear { + updateColumnCount(geometry.size.width) + } + } + .frame(height: 1) + .padding() + } + .onAppear { + self.selectable = [.select, .fallback].contains(proxyGroup.type) + self.groupSelected = proxyGroup.now + } + } + + func updateColumnCount(_ width: Double) { + let v = Int(Int(width) / 180) + let new = v == 0 ? 1 : v + + if new != columnCount { + columnCount = new + } + } + + + var proxyInfoView: some View { + HStack() { + Text(hideProxyNames.hide + ? String(proxyGroup.id.prefix(8)) + : proxyGroup.name) + .font(.system(size: 17)) + Text(proxyGroup.type.rawValue) + .font(.system(size: 13)) + .foregroundColor(.secondary) + Text("\(proxyGroup.proxies.count)") + .font(.system(size: 11)) + .padding(EdgeInsets(top: 2, leading: 4, bottom: 2, trailing: 4)) + .background(Color.gray.opacity(0.5)) + .cornerRadius(4) + + Spacer() + + ProgressButton( + title: proxyGroup.type == .urltest ? "Retest" : "Benchmark", + title2: "Testing", + iconName: "bolt.fill", + inProgress: $isTesting) { + startBenchmark() + } + } + } + + var proxyListView: some View { + LazyVGrid(columns: Array(repeating: GridItem(.flexible()), + count: columnCount)) { + ForEach(proxies, id: \.id) { proxy in + ProxyNodeView( + proxy: proxy, + selectable: [.select, .fallback].contains(proxyGroup.type), + now: $groupSelected + ) + .cornerRadius(8) + .onTapGesture { + let item = proxy + updateSelect(item.name) + } + } + } + } + + func startBenchmark() { + isTesting = true + ApiRequest.getGroupDelay(groupName: proxyGroup.name) { delays in + proxyGroup.proxies.enumerated().forEach { + var delay = 0 + if let d = delays[$0.element.name], d != 0 { + delay = d + } + guard $0.offset < proxyGroup.proxies.count, + proxyGroup.proxies[$0.offset].name == $0.element.name + else { return } + proxyGroup.proxies[$0.offset].delay = delay + + if proxyGroup.currentProxy?.name == $0.element.name { + proxyGroup.currentProxy = proxyGroup.proxies[$0.offset] + } + } + isTesting = false + } + } + + func updateSelect(_ name: String) { + guard selectable, !isUpdatingSelect else { return } + isUpdatingSelect = true + ApiRequest.updateProxyGroup(group: proxyGroup.name, selectProxy: name) { success in + isUpdatingSelect = false + guard success else { return } + proxyGroup.now = name + self.groupSelected = name + } + } + +} + +//struct ProxyView_Previews: PreviewProvider { +// static var previews: some View { +// ProxyGroupView() +// } +//} +// diff --git a/ClashX/Dashboard/Views/ContentTabs/Proxies/ProxyNodeView.swift b/ClashX/Dashboard/Views/ContentTabs/Proxies/ProxyNodeView.swift new file mode 100644 index 000000000..b73438865 --- /dev/null +++ b/ClashX/Dashboard/Views/ContentTabs/Proxies/ProxyNodeView.swift @@ -0,0 +1,89 @@ +// +// ProxyNodeView.swift +// ClashX Dashboard +// +// + +import SwiftUI + +struct ProxyNodeView: View { + + @ObservedObject var proxy: DBProxy + @State var selectable: Bool + @Binding var now: String? + + @EnvironmentObject var hideProxyNames: HideProxyNames + + + init(proxy: DBProxy, selectable: Bool, now: Binding = .init(get: {nil}) { _ in }) { + self.proxy = proxy + self.selectable = selectable + self._now = now + self.isBuiltInProxy = [.pass, .direct, .reject].contains(proxy.type) + } + + @State private var isBuiltInProxy: Bool + @State private var mouseOver = false + + var body: some View { + VStack { + HStack(alignment: .center) { + Text(hideProxyNames.hide + ? String(proxy.id.prefix(8)) + : proxy.name) + .truncationMode(.tail) + .lineLimit(1) + Spacer(minLength: 6) + + Text(proxy.udpString) + .foregroundColor(.secondary) + .font(.system(size: 11)) + .show(isVisible: !isBuiltInProxy) + } + + Spacer(minLength: 6) + .show(isVisible: !isBuiltInProxy) + HStack(alignment: .center) { + Text(proxy.type.rawValue) + .foregroundColor(.secondary) + .font(.system(size: 12)) + + Text("[TFO]") + .font(.system(size: 9)) + .show(isVisible: proxy.tfo) + Spacer(minLength: 6) + Text(proxy.delayString) + .foregroundColor(proxy.delayColor) + .font(.system(size: 11)) + } + .show(isVisible: !isBuiltInProxy) + } + .onHover { + guard selectable else { return } + mouseOver = $0 + } + .frame(height: 36) + .padding(12) + .overlay( + RoundedRectangle(cornerRadius: 6) + .stroke({ + if mouseOver, now == proxy.name { + return Color.accentColor + } else if mouseOver { + return Color.accentColor.opacity(0.7) + } else { + return Color.clear + } + }(), lineWidth: 2) + .padding(1) + ) + + .background(now == proxy.name ? Color.accentColor.opacity(0.7) : Color(compatible: .textBackgroundColor)) + } +} + +//struct ProxyNodeView_Previews: PreviewProvider { +// static var previews: some View { +// ProxyNodeView() +// } +//} diff --git a/ClashX/Dashboard/Views/ContentTabs/Rules/RuleItemView.swift b/ClashX/Dashboard/Views/ContentTabs/Rules/RuleItemView.swift new file mode 100644 index 000000000..75c239de4 --- /dev/null +++ b/ClashX/Dashboard/Views/ContentTabs/Rules/RuleItemView.swift @@ -0,0 +1,67 @@ +// +// RuleItemView.swift +// ClashX Dashboard +// +// + +import SwiftUI + +struct RuleItemView: View { + @State var index: Int + @State var rule: ClashRule + + var body: some View { + HStack(alignment: .center, spacing: 12) { + Text("\(index)") + .font(.system(size: 16)) + .foregroundColor(.secondary) + .frame(width: 30) + + VStack(alignment: .leading) { + HStack(alignment: .bottom, spacing: 18) { + if let payload = rule.payload, + payload != "" { + Text(rule.payload!) + .font(.system(size: 14)) + } + } + + + HStack { + HStack(alignment: .bottom, spacing: 12) { + Text(rule.type) + .foregroundColor(.secondary) + if rule.size > 0 { + Text("size: \(rule.size)") + .font(.system(size: 12)) + .foregroundColor(.secondary) + + } + } + .frame(width: 200, alignment: .leading) + + Text(rule.proxy ?? "") + .foregroundColor({ + switch rule.proxy { + case "DIRECT": + return .orange + case "REJECT", "REJECT-DROP": + return .red + default: + return .blue + } + }()) + } + } + } + } + + +} + +struct RulesRowView_Previews: PreviewProvider { + static var previews: some View { + RuleItemView(index: 114, rule: .init(type: "DIRECT", payload: "cn", proxy: "GeoSite")) + } +} + diff --git a/ClashX/Dashboard/Views/ContentTabs/Rules/RulesView.swift b/ClashX/Dashboard/Views/ContentTabs/Rules/RulesView.swift new file mode 100644 index 000000000..8acfd5310 --- /dev/null +++ b/ClashX/Dashboard/Views/ContentTabs/Rules/RulesView.swift @@ -0,0 +1,64 @@ +// +// RulesView.swift +// ClashX Dashboard +// +// + +import SwiftUI + +struct RulesView: View { + + @State var ruleItems = [ClashRule]() + + @State private var searchString: String = "" + + + var rules: [EnumeratedSequence<[ClashRule]>.Element] { + if searchString.isEmpty { + return Array(ruleItems.enumerated()) + } else { + return Array(ruleItems.filtered(searchString, for: ["type", "payload", "proxy"]).enumerated()) + } + } + + + var body: some View { + List { + ForEach(rules, id: \.element.id) { + RuleItemView(index: $0.offset, rule: $0.element) + } + } + .onReceive(NotificationCenter.default.publisher(for: .toolbarSearchString)) { + guard let string = $0.userInfo?["String"] as? String else { return } + searchString = string + } + .onAppear { + ruleItems.removeAll() + + ApiRequest.requestRuleProviderList { resp in + let ruleProviders = resp.allProviders.values.sorted { + $0.name < $1.name + } + .map(DBRuleProvider.init) + + ApiRequest.getRules { + let items = $0 + items.enumerated().forEach { + guard let payload = $0.element.payload, + let pd = ruleProviders.first(where: { $0.name == payload }) else { return } + + items[$0.offset].size = pd.ruleCount + } + + ruleItems = items + } + } + } + } +} + +//struct RulesView_Previews: PreviewProvider { +// static var previews: some View { +// RulesView() +// } +//} diff --git a/ClashX/Dashboard/Views/DashboardView.swift b/ClashX/Dashboard/Views/DashboardView.swift new file mode 100644 index 000000000..4cea2695a --- /dev/null +++ b/ClashX/Dashboard/Views/DashboardView.swift @@ -0,0 +1,43 @@ +// +// DashboardView.swift +// ClashX Dashboard +// +// + +import SwiftUI + +class HideProxyNames: ObservableObject, Identifiable { + let id = UUID().uuidString + @Published var hide = false +} + +struct DashboardView: View { + + private let runningState = NotificationCenter.default.publisher(for: .init("ClashRunningStateChanged")) + @State private var isRunning = false + + var body: some View { + Group { + if !isRunning { + APISettingView() + +// .presentedWindowToolbarStyle(.expanded) + } else { + NavigationView { + SidebarView() + EmptyView() + } + } + } + .onReceive(runningState) { _ in + isRunning = ConfigManager.shared.isRunning + } + + } +} + +struct ContentView_Previews: PreviewProvider { + static var previews: some View { + DashboardView() + } +} diff --git a/ClashX/Dashboard/Views/NSTableViewExtension.swift b/ClashX/Dashboard/Views/NSTableViewExtension.swift new file mode 100644 index 000000000..4f1e025aa --- /dev/null +++ b/ClashX/Dashboard/Views/NSTableViewExtension.swift @@ -0,0 +1,72 @@ +// +// NSTableViewExtension.swift +// +// + +// + +import Cocoa + +extension NSTableView { + func makeCellView(with identifier: String, owner: Any?) -> NSTableCellView? { + // https://stackoverflow.com/a/27624927 + + var cellView: NSTableCellView? + if let spareView = makeView(withIdentifier: .init(identifier), + owner: owner) as? NSTableCellView { + + // We can use an old cell - no need to do anything. + cellView = spareView + + } else { + + // Create a text field for the cell + let textField = NSTextField() + textField.backgroundColor = NSColor.clear + textField.translatesAutoresizingMaskIntoConstraints = false + textField.isBordered = false + textField.font = .systemFont(ofSize: 13) + textField.lineBreakMode = .byTruncatingTail + + // Create a cell + let newCell = NSTableCellView() + newCell.identifier = .init(identifier) + newCell.addSubview(textField) + newCell.textField = textField + + // Constrain the text field within the cell + newCell.addConstraints( + NSLayoutConstraint.constraints(withVisualFormat: "H:|[textField]|", + options: [], + metrics: nil, + views: ["textField" : textField])) + + newCell.addConstraint(.init(item: textField, attribute: .centerY, relatedBy: .equal, toItem: newCell, attribute: .centerY, multiplier: 1, constant: 0)) + + + textField.bind(NSBindingName.value, + to: newCell, + withKeyPath: "objectValue", + options: nil) + + cellView = newCell + } + + return cellView + } + + + func reloadData(_ changes: CollectionDifference, indexs: IndexSet) { + beginUpdates() + for change in changes { + switch change { + case .insert(let offset, _, _): + insertRows(at: IndexSet(integer: offset)) + case .remove(let offset, _, _): + removeRows(at: IndexSet(integer: offset)) + } + } + reloadData(forRowIndexes: indexs, columnIndexes: IndexSet(tableColumns.indices)) + endUpdates() + } +} diff --git a/ClashX/Dashboard/Views/SidebarView/SidebarItem.swift b/ClashX/Dashboard/Views/SidebarView/SidebarItem.swift new file mode 100644 index 000000000..2d4514b2f --- /dev/null +++ b/ClashX/Dashboard/Views/SidebarView/SidebarItem.swift @@ -0,0 +1,18 @@ +// +// SidebarItem.swift +// ClashX Dashboard +// +// + +import Cocoa +import SwiftUI + +enum SidebarItem: String { + case overview = "Overview" + case proxies = "Proxies" + case providers = "Providers" + case rules = "Rules" + case conns = "Conns" + case config = "Config" + case logs = "Logs" +} diff --git a/ClashX/Dashboard/Views/SidebarView/SidebarLabel.swift b/ClashX/Dashboard/Views/SidebarView/SidebarLabel.swift new file mode 100644 index 000000000..7f75107d8 --- /dev/null +++ b/ClashX/Dashboard/Views/SidebarView/SidebarLabel.swift @@ -0,0 +1,27 @@ +// +// SwiftUIView.swift +// +// +// + +import SwiftUI + +struct SidebarLabel: View { + @State var item: SidebarItem + @State var iconName: String + + var body: some View { + Label { + Text(item.rawValue) + } icon: { + Image(systemName: iconName) + .foregroundColor(.accentColor) + } + } +} + +struct SidebarLabel_Previews: PreviewProvider { + static var previews: some View { + SidebarLabel(item: .overview, iconName: "chart.bar.xaxis") + } +} diff --git a/ClashX/Dashboard/Views/SidebarView/SidebarListView.swift b/ClashX/Dashboard/Views/SidebarView/SidebarListView.swift new file mode 100644 index 000000000..92f0c3b6e --- /dev/null +++ b/ClashX/Dashboard/Views/SidebarView/SidebarListView.swift @@ -0,0 +1,85 @@ +// +// SidebarListView.swift +// ClashX Dashboard +// +// + +import SwiftUI +import SwiftUIIntrospect + +struct SidebarListView: View { + + @Binding var selectionName: SidebarItem? + + @State private var reloadID = UUID().uuidString + + + var body: some View { + List { + NavigationLink(destination: OverviewView(), + tag: SidebarItem.overview, + selection: $selectionName) { + SidebarLabel(item: .overview, iconName: "chart.bar.xaxis") + } + + NavigationLink(destination: ProxiesView(), + tag: SidebarItem.proxies, + selection: $selectionName) { + SidebarLabel(item: .proxies, iconName: "globe.asia.australia") + } + + NavigationLink(destination: ProvidersView(), + tag: SidebarItem.providers, + selection: $selectionName) { + SidebarLabel(item: .providers, iconName: "link.icloud") + } + + NavigationLink(destination: RulesView(), + tag: SidebarItem.rules, + selection: $selectionName) { + SidebarLabel(item: .rules, iconName: "waveform.and.magnifyingglass") + } + + NavigationLink(destination: ConnectionsView(), + tag: SidebarItem.conns, + selection: $selectionName) { + SidebarLabel(item: .conns, iconName: "app.connected.to.app.below.fill") + } + + NavigationLink(destination: ConfigView(), + tag: SidebarItem.config, + selection: $selectionName) { + SidebarLabel(item: .config, iconName: "slider.horizontal.3") + } + + NavigationLink(destination: LogsView(), + tag: SidebarItem.logs, + selection: $selectionName) { + SidebarLabel(item: .logs, iconName: "wand.and.stars.inverse") + } + + } + .introspect(.table, on: .macOS(.v12, .v13, .v14)) { + $0.refusesFirstResponder = true + + if selectionName == nil { + selectionName = SidebarItem.overview + $0.allowsEmptySelection = false + if $0.selectedRow == -1 { + $0.selectRowIndexes(.init(integer: 0), byExtendingSelection: false) + } + } + } + .listStyle(.sidebar) + .id(reloadID) + .onReceive(NotificationCenter.default.publisher(for: .reloadDashboard)) { _ in + reloadID = UUID().uuidString + } + } +} + +//struct SidebarListView_Previews: PreviewProvider { +// static var previews: some View { +// SidebarListView() +// } +//} diff --git a/ClashX/Dashboard/Views/SidebarView/SidebarView.swift b/ClashX/Dashboard/Views/SidebarView/SidebarView.swift new file mode 100644 index 000000000..3a847c922 --- /dev/null +++ b/ClashX/Dashboard/Views/SidebarView/SidebarView.swift @@ -0,0 +1,69 @@ +// +// SidebarView.swift +// ClashX Dashboard +// +// + +import SwiftUI + +struct SidebarView: View { + + @StateObject var clashApiDatasStorage = ClashApiDatasStorage() + + private let connsQueue = DispatchQueue(label: "thread-safe-connsQueue", attributes: .concurrent) + private let timer = Timer.publish(every: 1, on: .main, in: .default).autoconnect() + + @State private var sidebarSelectionName: SidebarItem? + + var body: some View { + Group { + SidebarListView(selectionName: $sidebarSelectionName) + } + .environmentObject(clashApiDatasStorage.overviewData) + .environmentObject(clashApiDatasStorage.logStorage) + .environmentObject(clashApiDatasStorage.connsStorage) + .onAppear { + if ConfigManager.selectLoggingApiLevel == .unknow { + ConfigManager.selectLoggingApiLevel = .info + } + + clashApiDatasStorage.resetStreamApi() + connsQueue.sync { + clashApiDatasStorage.connsStorage.conns + .removeAll() + } + + updateConnections() + } + .onChange(of: sidebarSelectionName) { newValue in + sidebarItemChanged(newValue) + } + .onReceive(timer, perform: { _ in + updateConnections() + }) + + } + + func updateConnections() { + ApiRequest.getConnections { snap in + connsQueue.sync { + clashApiDatasStorage.overviewData.upTotal = snap.uploadTotal + clashApiDatasStorage.overviewData.downTotal = snap.downloadTotal + clashApiDatasStorage.overviewData.activeConns = "\(snap.connections.count)" + clashApiDatasStorage.connsStorage.conns = snap.connections + } + } + } + + func sidebarItemChanged(_ item: SidebarItem?) { + guard let item else { return } + + NotificationCenter.default.post(name: .sidebarItemChanged, object: nil, userInfo: ["item": item]) + } +} + +//struct SidebarView_Previews: PreviewProvider { +// static var previews: some View { +// SidebarView() +// } +//} diff --git a/ClashX/Dashboard/Views/SwiftUIViewExtensions.swift b/ClashX/Dashboard/Views/SwiftUIViewExtensions.swift new file mode 100644 index 000000000..83d9dbd49 --- /dev/null +++ b/ClashX/Dashboard/Views/SwiftUIViewExtensions.swift @@ -0,0 +1,27 @@ +// +// SwiftUIViewExtensions.swift +// ClashX Dashboard +// +// + +import Foundation +import SwiftUI + +struct Show: ViewModifier { + let isVisible: Bool + + @ViewBuilder + func body(content: Content) -> some View { + if isVisible { + content + } else { + EmptyView() + } + } +} + +extension View { + func show(isVisible: Bool) -> some View { + ModifiedContent(content: self, modifier: Show(isVisible: isVisible)) + } +} From 06ef41f6aa94e3a52b0f40b4731e8bc743132895 Mon Sep 17 00:00:00 2001 From: mrFq1 <1xxbx0il0@mozmail.com> Date: Sat, 21 Sep 2024 19:20:45 +0800 Subject: [PATCH 08/14] misc: fix dashboard --- ClashX.xcodeproj/project.pbxproj | 80 ++---- ClashX/AppDelegate.swift | 10 +- .../ArrayExtensions.swift | 0 .../ColorExtension.swift | 0 .../NSTableViewExtension.swift | 0 .../{ => Extensions}/NotificationNames.swift | 0 .../SwiftUIViewExtensions.swift | 0 .../Views/APISetting/APIServerItem.swift | 48 ---- .../Views/APISetting/APISettingView.swift | 81 ------ .../APISetting/ClashServerAppStorage.swift | 36 --- .../{Providers => }/ProviderProxiesView.swift | 0 .../Providers}/ProgressButton.swift | 0 .../ContentTabs/Providers/ProvidersView.swift | 2 +- .../ContentTabs/Proxies/ProxiesView.swift | 2 +- ClashX/Dashboard/Views/DashboardView.swift | 12 +- .../Views/SidebarView/SidebarListView.swift | 2 +- ClashX/General/ApiRequest.swift | 251 +++++++++++++----- ClashX/General/Managers/ConfigManager.swift | 1 + ClashX/General/Managers/MenuItemFactory.swift | 7 +- ClashX/Models/ClashConfig.swift | 120 ++++----- ClashX/Models/ClashProvider.swift | 10 +- ClashX/Models/ClashProxy.swift | 41 +-- ClashX/Models/ClashRule.swift | 17 +- ClashX/Models/ClashRuleProvider.swift | 12 +- 24 files changed, 323 insertions(+), 409 deletions(-) rename ClashX/Dashboard/{Views => Extensions}/ArrayExtensions.swift (100%) rename ClashX/Dashboard/{Views => Extensions}/ColorExtension.swift (100%) rename ClashX/Dashboard/{Views => Extensions}/NSTableViewExtension.swift (100%) rename ClashX/Dashboard/{ => Extensions}/NotificationNames.swift (100%) rename ClashX/Dashboard/{Views => Extensions}/SwiftUIViewExtensions.swift (100%) delete mode 100644 ClashX/Dashboard/Views/APISetting/APIServerItem.swift delete mode 100644 ClashX/Dashboard/Views/APISetting/APISettingView.swift delete mode 100644 ClashX/Dashboard/Views/APISetting/ClashServerAppStorage.swift rename ClashX/Dashboard/Views/ContentTabs/{Providers => }/ProviderProxiesView.swift (100%) rename ClashX/Dashboard/Views/{APISetting => ContentTabs/Providers}/ProgressButton.swift (100%) diff --git a/ClashX.xcodeproj/project.pbxproj b/ClashX.xcodeproj/project.pbxproj index ec335eee2..f492ae5f4 100644 --- a/ClashX.xcodeproj/project.pbxproj +++ b/ClashX.xcodeproj/project.pbxproj @@ -12,11 +12,7 @@ 019A239628657A7A00AE5698 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 019A239528657A7A00AE5698 /* main.swift */; }; 01BC9ABE2928EB5A00F9B177 /* MetaDNS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BC9ABD2928E5C600F9B177 /* MetaDNS.swift */; }; 01BCDAAC2C9ECB3A0028FA94 /* DSFSparkline in Frameworks */ = {isa = PBXBuildFile; productRef = 01BCDAAB2C9ECB3A0028FA94 /* DSFSparkline */; }; - 01BCDAAE2C9ECB3A0028FA94 /* DSFSparkline-shared in Frameworks */ = {isa = PBXBuildFile; productRef = 01BCDAAD2C9ECB3A0028FA94 /* DSFSparkline-shared */; }; - 01BCDAB02C9ECB3A0028FA94 /* DSFSparkline-static in Frameworks */ = {isa = PBXBuildFile; productRef = 01BCDAAF2C9ECB3A0028FA94 /* DSFSparkline-static */; }; 01BCDAB32C9ECB560028FA94 /* SwiftUIIntrospect in Frameworks */ = {isa = PBXBuildFile; productRef = 01BCDAB22C9ECB560028FA94 /* SwiftUIIntrospect */; }; - 01BCDAB52C9ECB560028FA94 /* SwiftUIIntrospect-Dynamic in Frameworks */ = {isa = PBXBuildFile; productRef = 01BCDAB42C9ECB560028FA94 /* SwiftUIIntrospect-Dynamic */; }; - 01BCDAB72C9ECB560028FA94 /* SwiftUIIntrospect-Static in Frameworks */ = {isa = PBXBuildFile; productRef = 01BCDAB62C9ECB560028FA94 /* SwiftUIIntrospect-Static */; }; 01BCDAF02C9ECD010028FA94 /* DBProviderStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BCDABB2C9ECD010028FA94 /* DBProviderStorage.swift */; }; 01BCDAF12C9ECD010028FA94 /* ProviderRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BCDAD42C9ECD010028FA94 /* ProviderRowView.swift */; }; 01BCDAF22C9ECD010028FA94 /* DashboardViewContoller.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BCDAB92C9ECD010028FA94 /* DashboardViewContoller.swift */; }; @@ -29,7 +25,6 @@ 01BCDAF92C9ECD010028FA94 /* ConnectionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BCDACA2C9ECD010028FA94 /* ConnectionsView.swift */; }; 01BCDAFA2C9ECD010028FA94 /* TrafficGraphView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BCDAD12C9ECD010028FA94 /* TrafficGraphView.swift */; }; 01BCDAFB2C9ECD010028FA94 /* LogsTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BCDACC2C9ECD010028FA94 /* LogsTableView.swift */; }; - 01BCDAFC2C9ECD010028FA94 /* APIServerItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BCDAC02C9ECD010028FA94 /* APIServerItem.swift */; }; 01BCDAFD2C9ECD010028FA94 /* ToolbarStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BCDABF2C9ECD010028FA94 /* ToolbarStore.swift */; }; 01BCDAFE2C9ECD010028FA94 /* RuleItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BCDAE02C9ECD010028FA94 /* RuleItemView.swift */; }; 01BCDAFF2C9ECD010028FA94 /* ConnectionsTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BCDAC92C9ECD010028FA94 /* ConnectionsTableView.swift */; }; @@ -41,10 +36,8 @@ 01BCDB052C9ECD010028FA94 /* RulesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BCDAE12C9ECD010028FA94 /* RulesView.swift */; }; 01BCDB062C9ECD010028FA94 /* RuleProviderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BCDAD92C9ECD010028FA94 /* RuleProviderView.swift */; }; 01BCDB072C9ECD010028FA94 /* SidebarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BCDAE42C9ECD010028FA94 /* SidebarItem.swift */; }; - 01BCDB082C9ECD010028FA94 /* APISettingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BCDAC12C9ECD010028FA94 /* APISettingView.swift */; }; 01BCDB092C9ECD010028FA94 /* DBProxyStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BCDABC2C9ECD010028FA94 /* DBProxyStorage.swift */; }; 01BCDB0A2C9ECD010028FA94 /* SwiftUIViewExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BCDAEE2C9ECD010028FA94 /* SwiftUIViewExtensions.swift */; }; - 01BCDB0B2C9ECD010028FA94 /* ClashServerAppStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BCDAC22C9ECD010028FA94 /* ClashServerAppStorage.swift */; }; 01BCDB0C2C9ECD010028FA94 /* NSTableViewExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BCDAED2C9ECD010028FA94 /* NSTableViewExtension.swift */; }; 01BCDB0E2C9ECD010028FA94 /* ArrayExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BCDAE92C9ECD010028FA94 /* ArrayExtensions.swift */; }; 01BCDB0F2C9ECD010028FA94 /* ProxyGroupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BCDADD2C9ECD010028FA94 /* ProxyGroupView.swift */; }; @@ -57,9 +50,9 @@ 01BCDB162C9ECD010028FA94 /* LogsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BCDACD2C9ECD010028FA94 /* LogsView.swift */; }; 01BCDB172C9ECD010028FA94 /* SidebarListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BCDAE62C9ECD010028FA94 /* SidebarListView.swift */; }; 01BCDB182C9ECD010028FA94 /* RuleProvidersRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BCDAD82C9ECD010028FA94 /* RuleProvidersRowView.swift */; }; - 01BCDB192C9ECD010028FA94 /* ProgressButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BCDAC32C9ECD010028FA94 /* ProgressButton.swift */; }; 01BCDB1A2C9ECD010028FA94 /* SidebarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BCDAE72C9ECD010028FA94 /* SidebarView.swift */; }; 01BCDB1B2C9ECD010028FA94 /* SidebarLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BCDAE52C9ECD010028FA94 /* SidebarLabel.swift */; }; + 01BCDB1D2C9EE2CF0028FA94 /* ProgressButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BCDB1C2C9EE2CF0028FA94 /* ProgressButton.swift */; }; 01CA6BC12B6A1B3100E386D6 /* MetaServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01CA6BBF2B6A1B3100E386D6 /* MetaServer.swift */; }; 01CA6BC22B6A1B3100E386D6 /* MetaServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01CA6BBF2B6A1B3100E386D6 /* MetaServer.swift */; }; 01D567E42AD158B600CDA0AE /* MetaPrefs.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 01D567E12AD158B500CDA0AE /* MetaPrefs.storyboard */; }; @@ -238,10 +231,6 @@ 01BCDABC2C9ECD010028FA94 /* DBProxyStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DBProxyStorage.swift; sourceTree = ""; }; 01BCDABE2C9ECD010028FA94 /* NotificationNames.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationNames.swift; sourceTree = ""; }; 01BCDABF2C9ECD010028FA94 /* ToolbarStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToolbarStore.swift; sourceTree = ""; }; - 01BCDAC02C9ECD010028FA94 /* APIServerItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIServerItem.swift; sourceTree = ""; }; - 01BCDAC12C9ECD010028FA94 /* APISettingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APISettingView.swift; sourceTree = ""; }; - 01BCDAC22C9ECD010028FA94 /* ClashServerAppStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClashServerAppStorage.swift; sourceTree = ""; }; - 01BCDAC32C9ECD010028FA94 /* ProgressButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressButton.swift; sourceTree = ""; }; 01BCDAC52C9ECD010028FA94 /* ConfigItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigItemView.swift; sourceTree = ""; }; 01BCDAC62C9ECD010028FA94 /* ConfigView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigView.swift; sourceTree = ""; }; 01BCDAC82C9ECD010028FA94 /* Connections.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Connections.swift; sourceTree = ""; }; @@ -275,6 +264,7 @@ 01BCDAEC2C9ECD010028FA94 /* DashboardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashboardView.swift; sourceTree = ""; }; 01BCDAED2C9ECD010028FA94 /* NSTableViewExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSTableViewExtension.swift; sourceTree = ""; }; 01BCDAEE2C9ECD010028FA94 /* SwiftUIViewExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftUIViewExtensions.swift; sourceTree = ""; }; + 01BCDB1C2C9EE2CF0028FA94 /* ProgressButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressButton.swift; sourceTree = ""; }; 01C1462928962E4E00346AF3 /* com.metacubex.ClashX.ProxyConfigHelper.meta.gz */ = {isa = PBXFileReference; lastKnownFileType = archive.gzip; path = com.metacubex.ClashX.ProxyConfigHelper.meta.gz; sourceTree = ""; }; 01CA6BBF2B6A1B3100E386D6 /* MetaServer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetaServer.swift; sourceTree = ""; }; 01D567DF2AD1562700CDA0AE /* ClashX Meta SwiftUI-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "ClashX Meta SwiftUI-Info.plist"; sourceTree = ""; }; @@ -404,8 +394,6 @@ 01F336252AD10D0B0048AF77 /* SwiftyJSON in Frameworks */, 01F336262AD10D0B0048AF77 /* Gzip in Frameworks */, 01F336272AD10D0B0048AF77 /* CocoaLumberjackSwift in Frameworks */, - 01BCDAB52C9ECB560028FA94 /* SwiftUIIntrospect-Dynamic in Frameworks */, - 01BCDAAE2C9ECB3A0028FA94 /* DSFSparkline-shared in Frameworks */, 01F336282AD10D0B0048AF77 /* Starscream in Frameworks */, 01F336292AD10D0B0048AF77 /* PromiseKit in Frameworks */, 01BCDAAC2C9ECB3A0028FA94 /* DSFSparkline in Frameworks */, @@ -414,9 +402,7 @@ 01F3362C2AD10D0B0048AF77 /* CocoaLumberjack in Frameworks */, 01F3362D2AD10D0B0048AF77 /* Yams in Frameworks */, 01F3362E2AD10D0B0048AF77 /* RxCocoa in Frameworks */, - 01BCDAB72C9ECB560028FA94 /* SwiftUIIntrospect-Static in Frameworks */, 01F3362F2AD10D0B0048AF77 /* FlexibleDiff in Frameworks */, - 01BCDAB02C9ECB3A0028FA94 /* DSFSparkline-static in Frameworks */, 01F336302AD10D0B0048AF77 /* libresolv.tbd in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -435,9 +421,9 @@ isa = PBXGroup; children = ( 01BCDABD2C9ECD010028FA94 /* Models */, + 01BCDB1E2C9EE32F0028FA94 /* Extensions */, 01BCDAEF2C9ECD010028FA94 /* Views */, 01BCDAB92C9ECD010028FA94 /* DashboardViewContoller.swift */, - 01BCDABE2C9ECD010028FA94 /* NotificationNames.swift */, 01BCDABF2C9ECD010028FA94 /* ToolbarStore.swift */, ); path = Dashboard; @@ -453,17 +439,6 @@ path = Models; sourceTree = ""; }; - 01BCDAC42C9ECD010028FA94 /* APISetting */ = { - isa = PBXGroup; - children = ( - 01BCDAC02C9ECD010028FA94 /* APIServerItem.swift */, - 01BCDAC12C9ECD010028FA94 /* APISettingView.swift */, - 01BCDAC22C9ECD010028FA94 /* ClashServerAppStorage.swift */, - 01BCDAC32C9ECD010028FA94 /* ProgressButton.swift */, - ); - path = APISetting; - sourceTree = ""; - }; 01BCDAC72C9ECD010028FA94 /* Config */ = { isa = PBXGroup; children = ( @@ -505,13 +480,13 @@ 01BCDADA2C9ECD010028FA94 /* Providers */ = { isa = PBXGroup; children = ( - 01BCDAD32C9ECD010028FA94 /* ProviderProxiesView.swift */, 01BCDAD42C9ECD010028FA94 /* ProviderRowView.swift */, 01BCDAD52C9ECD010028FA94 /* ProvidersView.swift */, 01BCDAD62C9ECD010028FA94 /* ProxyProviderInfoView.swift */, 01BCDAD72C9ECD010028FA94 /* ProxyProvidersRowView.swift */, 01BCDAD82C9ECD010028FA94 /* RuleProvidersRowView.swift */, 01BCDAD92C9ECD010028FA94 /* RuleProviderView.swift */, + 01BCDB1C2C9EE2CF0028FA94 /* ProgressButton.swift */, ); path = Providers; sourceTree = ""; @@ -546,6 +521,7 @@ 01BCDADA2C9ECD010028FA94 /* Providers */, 01BCDADF2C9ECD010028FA94 /* Proxies */, 01BCDAE22C9ECD010028FA94 /* Rules */, + 01BCDAD32C9ECD010028FA94 /* ProviderProxiesView.swift */, ); path = ContentTabs; sourceTree = ""; @@ -564,19 +540,26 @@ 01BCDAEF2C9ECD010028FA94 /* Views */ = { isa = PBXGroup; children = ( - 01BCDAC42C9ECD010028FA94 /* APISetting */, 01BCDAE32C9ECD010028FA94 /* ContentTabs */, 01BCDAE82C9ECD010028FA94 /* SidebarView */, - 01BCDAE92C9ECD010028FA94 /* ArrayExtensions.swift */, 01BCDAEA2C9ECD010028FA94 /* ClashApiDatasStorage.swift */, - 01BCDAEB2C9ECD010028FA94 /* ColorExtension.swift */, 01BCDAEC2C9ECD010028FA94 /* DashboardView.swift */, - 01BCDAED2C9ECD010028FA94 /* NSTableViewExtension.swift */, - 01BCDAEE2C9ECD010028FA94 /* SwiftUIViewExtensions.swift */, ); path = Views; sourceTree = ""; }; + 01BCDB1E2C9EE32F0028FA94 /* Extensions */ = { + isa = PBXGroup; + children = ( + 01BCDAE92C9ECD010028FA94 /* ArrayExtensions.swift */, + 01BCDAEB2C9ECD010028FA94 /* ColorExtension.swift */, + 01BCDAEE2C9ECD010028FA94 /* SwiftUIViewExtensions.swift */, + 01BCDAED2C9ECD010028FA94 /* NSTableViewExtension.swift */, + 01BCDABE2C9ECD010028FA94 /* NotificationNames.swift */, + ); + path = Extensions; + sourceTree = ""; + }; 4913C82021157CEB00F6B87C /* Macro */ = { isa = PBXGroup; children = ( @@ -917,11 +900,7 @@ 01F335C82AD10D0B0048AF77 /* Yams */, 01F335CA2AD10D0B0048AF77 /* PromiseKit */, 01BCDAAB2C9ECB3A0028FA94 /* DSFSparkline */, - 01BCDAAD2C9ECB3A0028FA94 /* DSFSparkline-shared */, - 01BCDAAF2C9ECB3A0028FA94 /* DSFSparkline-static */, 01BCDAB22C9ECB560028FA94 /* SwiftUIIntrospect */, - 01BCDAB42C9ECB560028FA94 /* SwiftUIIntrospect-Dynamic */, - 01BCDAB62C9ECB560028FA94 /* SwiftUIIntrospect-Static */, ); productName = ClashX; productReference = 01F336442AD10D0B0048AF77 /* ClashX Meta.app */; @@ -1087,6 +1066,7 @@ 01F336042AD10D0B0048AF77 /* MenuItemFactory.swift in Sources */, 01F336052AD10D0B0048AF77 /* MetaTask.swift in Sources */, 01F336062AD10D0B0048AF77 /* AutoUpgardeManager.swift in Sources */, + 01BCDB1D2C9EE2CF0028FA94 /* ProgressButton.swift in Sources */, 01F336072AD10D0B0048AF77 /* RemoteConfigModel.swift in Sources */, 01F336082AD10D0B0048AF77 /* ProxyGroupMenuItemView.swift in Sources */, 01F336092AD10D0B0048AF77 /* PrivilegedHelperManager.swift in Sources */, @@ -1109,7 +1089,6 @@ 01BCDAF92C9ECD010028FA94 /* ConnectionsView.swift in Sources */, 01BCDAFA2C9ECD010028FA94 /* TrafficGraphView.swift in Sources */, 01BCDAFB2C9ECD010028FA94 /* LogsTableView.swift in Sources */, - 01BCDAFC2C9ECD010028FA94 /* APIServerItem.swift in Sources */, 01BCDAFD2C9ECD010028FA94 /* ToolbarStore.swift in Sources */, 01BCDAFE2C9ECD010028FA94 /* RuleItemView.swift in Sources */, 01BCDAFF2C9ECD010028FA94 /* ConnectionsTableView.swift in Sources */, @@ -1121,10 +1100,8 @@ 01BCDB052C9ECD010028FA94 /* RulesView.swift in Sources */, 01BCDB062C9ECD010028FA94 /* RuleProviderView.swift in Sources */, 01BCDB072C9ECD010028FA94 /* SidebarItem.swift in Sources */, - 01BCDB082C9ECD010028FA94 /* APISettingView.swift in Sources */, 01BCDB092C9ECD010028FA94 /* DBProxyStorage.swift in Sources */, 01BCDB0A2C9ECD010028FA94 /* SwiftUIViewExtensions.swift in Sources */, - 01BCDB0B2C9ECD010028FA94 /* ClashServerAppStorage.swift in Sources */, 01BCDB0C2C9ECD010028FA94 /* NSTableViewExtension.swift in Sources */, 01BCDB0E2C9ECD010028FA94 /* ArrayExtensions.swift in Sources */, 01BCDB0F2C9ECD010028FA94 /* ProxyGroupView.swift in Sources */, @@ -1137,7 +1114,6 @@ 01BCDB162C9ECD010028FA94 /* LogsView.swift in Sources */, 01BCDB172C9ECD010028FA94 /* SidebarListView.swift in Sources */, 01BCDB182C9ECD010028FA94 /* RuleProvidersRowView.swift in Sources */, - 01BCDB192C9ECD010028FA94 /* ProgressButton.swift in Sources */, 01BCDB1A2C9ECD010028FA94 /* SidebarView.swift in Sources */, 01BCDB1B2C9ECD010028FA94 /* SidebarLabel.swift in Sources */, 01F336112AD10D0B0048AF77 /* ProxyItemView.swift in Sources */, @@ -1724,31 +1700,11 @@ package = 01BCDAAA2C9ECB3A0028FA94 /* XCRemoteSwiftPackageReference "DSFSparkline" */; productName = DSFSparkline; }; - 01BCDAAD2C9ECB3A0028FA94 /* DSFSparkline-shared */ = { - isa = XCSwiftPackageProductDependency; - package = 01BCDAAA2C9ECB3A0028FA94 /* XCRemoteSwiftPackageReference "DSFSparkline" */; - productName = "DSFSparkline-shared"; - }; - 01BCDAAF2C9ECB3A0028FA94 /* DSFSparkline-static */ = { - isa = XCSwiftPackageProductDependency; - package = 01BCDAAA2C9ECB3A0028FA94 /* XCRemoteSwiftPackageReference "DSFSparkline" */; - productName = "DSFSparkline-static"; - }; 01BCDAB22C9ECB560028FA94 /* SwiftUIIntrospect */ = { isa = XCSwiftPackageProductDependency; package = 01BCDAB12C9ECB560028FA94 /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */; productName = SwiftUIIntrospect; }; - 01BCDAB42C9ECB560028FA94 /* SwiftUIIntrospect-Dynamic */ = { - isa = XCSwiftPackageProductDependency; - package = 01BCDAB12C9ECB560028FA94 /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */; - productName = "SwiftUIIntrospect-Dynamic"; - }; - 01BCDAB62C9ECB560028FA94 /* SwiftUIIntrospect-Static */ = { - isa = XCSwiftPackageProductDependency; - package = 01BCDAB12C9ECB560028FA94 /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */; - productName = "SwiftUIIntrospect-Static"; - }; 01F335B62AD10D0B0048AF77 /* KeyboardShortcuts */ = { isa = XCSwiftPackageProductDependency; package = 01F335B72AD10D0B0048AF77 /* XCRemoteSwiftPackageReference "KeyboardShortcuts" */; diff --git a/ClashX/AppDelegate.swift b/ClashX/AppDelegate.swift index 275b1dfff..bbb49aa03 100644 --- a/ClashX/AppDelegate.swift +++ b/ClashX/AppDelegate.swift @@ -750,12 +750,20 @@ extension AppDelegate { // MARK: Streaming Info extension AppDelegate: ApiRequestStreamDelegate { + func didUpdateMemory(memory: Int64) { + + } + + func streamStatusChanged() { + + } + func didUpdateTraffic(up: Int, down: Int) { statusItemView.updateSpeedLabel(up: up, down: down) } func didGetLog(log: String, level: String) { -// Logger.log(log, level: ClashLogLevel(rawValue: level) ?? .unknow) + Logger.log(log, level: ClashLogLevel(rawValue: level) ?? .unknow) } } diff --git a/ClashX/Dashboard/Views/ArrayExtensions.swift b/ClashX/Dashboard/Extensions/ArrayExtensions.swift similarity index 100% rename from ClashX/Dashboard/Views/ArrayExtensions.swift rename to ClashX/Dashboard/Extensions/ArrayExtensions.swift diff --git a/ClashX/Dashboard/Views/ColorExtension.swift b/ClashX/Dashboard/Extensions/ColorExtension.swift similarity index 100% rename from ClashX/Dashboard/Views/ColorExtension.swift rename to ClashX/Dashboard/Extensions/ColorExtension.swift diff --git a/ClashX/Dashboard/Views/NSTableViewExtension.swift b/ClashX/Dashboard/Extensions/NSTableViewExtension.swift similarity index 100% rename from ClashX/Dashboard/Views/NSTableViewExtension.swift rename to ClashX/Dashboard/Extensions/NSTableViewExtension.swift diff --git a/ClashX/Dashboard/NotificationNames.swift b/ClashX/Dashboard/Extensions/NotificationNames.swift similarity index 100% rename from ClashX/Dashboard/NotificationNames.swift rename to ClashX/Dashboard/Extensions/NotificationNames.swift diff --git a/ClashX/Dashboard/Views/SwiftUIViewExtensions.swift b/ClashX/Dashboard/Extensions/SwiftUIViewExtensions.swift similarity index 100% rename from ClashX/Dashboard/Views/SwiftUIViewExtensions.swift rename to ClashX/Dashboard/Extensions/SwiftUIViewExtensions.swift diff --git a/ClashX/Dashboard/Views/APISetting/APIServerItem.swift b/ClashX/Dashboard/Views/APISetting/APIServerItem.swift deleted file mode 100644 index 16f3ead3a..000000000 --- a/ClashX/Dashboard/Views/APISetting/APIServerItem.swift +++ /dev/null @@ -1,48 +0,0 @@ -// -// APIServerItem.swift -// ClashX Dashboard -// -// - -import SwiftUI - -struct APIServerItem: View { - @State var server: String - - var action: () -> Void - var onDelete: () -> Void - - @State private var mouseOver = false - - var body: some View { - HStack { - Button("X") { - onDelete() - } - .buttonStyle(.bordered) - - Button() { - action() - } label: { - Text(server) - .font(.title2) - } - .buttonStyle(.borderless) - - Spacer() - } - .frame(height: 21) - .padding(EdgeInsets(top: 12, leading: 20, bottom: 12, trailing: 20)) - .overlay( - RoundedRectangle(cornerRadius: 6) - .stroke(Color(compatible: .secondaryLabelColor), lineWidth: 1) - .padding(1) - ) - } -} - -//struct APIServerItem_Previews: PreviewProvider { -// static var previews: some View { -// APIServerItem() -// } -//} diff --git a/ClashX/Dashboard/Views/APISetting/APISettingView.swift b/ClashX/Dashboard/Views/APISetting/APISettingView.swift deleted file mode 100644 index 86a7915b0..000000000 --- a/ClashX/Dashboard/Views/APISetting/APISettingView.swift +++ /dev/null @@ -1,81 +0,0 @@ -// -// APISettingView.swift -// ClashX Dashboard -// -// - -import SwiftUI -import SwiftUIIntrospect - -struct APISettingView: View { - @State var baseURL: String = "" - @State var secret: String = "" - - @State var connectInfo: String = "" - - @AppStorage("savedServers") var savedServers = SavedServersAppStorage() - - var body: some View { - VStack(alignment: .center) { - HStack { - VStack(alignment: .leading) { - Text("API Base URL") - TextField("http://127.0.0.1:9090", text: $baseURL) - } - .frame(width: 250) - - VStack(alignment: .leading) { - Text("Secret(optional)") - TextField("", text: $secret) - } - .frame(width: 120) - } - - HStack { - Text(connectInfo) - Spacer() - Button("Add") { - savedServers.append(.init(apiURL: baseURL, secret: secret)) - - print(savedServers) - } - } - - List(savedServers, id: \.id) { server in - APIServerItem(server: server.apiURL) { - - ConfigManager.shared.overrideApiURL = .init(string: server.apiURL) - ConfigManager.shared.overrideSecret = server.secret - - ApiRequest.requestVersion { version in - if let version { - connectInfo = "" - print(version) - ConfigManager.shared.isRunning = true - } else { - connectInfo = "Failed to connect" - } - } - } onDelete: { - savedServers.removeAll { - $0.id == server.id - } - } - } - .introspect(.table, on: .macOS(.v12, .v13, .v14)) { - $0.backgroundColor = NSColor.clear - $0.enclosingScrollView?.drawsBackground = false - } - } - .padding(.top) - .fixedSize(horizontal: true, vertical: false) - .frame(maxWidth: .infinity, maxHeight: .infinity) - } - -} - -struct APISettingView_Previews: PreviewProvider { - static var previews: some View { - APISettingView() - } -} diff --git a/ClashX/Dashboard/Views/APISetting/ClashServerAppStorage.swift b/ClashX/Dashboard/Views/APISetting/ClashServerAppStorage.swift deleted file mode 100644 index f30e3efaa..000000000 --- a/ClashX/Dashboard/Views/APISetting/ClashServerAppStorage.swift +++ /dev/null @@ -1,36 +0,0 @@ -// -// ClashServerAppStorage.swift -// ClashX Dashboard -// -// - -import Foundation -import SwiftUI - -typealias SavedServersAppStorage = [ClashServerAppStorage] - -struct ClashServerAppStorage: Codable, Identifiable { - var id = UUID().uuidString - let apiURL: String - let secret: String -} - -extension SavedServersAppStorage: RawRepresentable { - public init?(rawValue: String) { - guard let data = rawValue.data(using: .utf8), - let result = try? JSONDecoder().decode(SavedServersAppStorage.self, from: data) - else { - return nil - } - self = result - } - - public var rawValue: String { - guard let data = try? JSONEncoder().encode(self), - let result = String(data: data, encoding: .utf8) - else { - return "[]" - } - return result - } -} diff --git a/ClashX/Dashboard/Views/ContentTabs/Providers/ProviderProxiesView.swift b/ClashX/Dashboard/Views/ContentTabs/ProviderProxiesView.swift similarity index 100% rename from ClashX/Dashboard/Views/ContentTabs/Providers/ProviderProxiesView.swift rename to ClashX/Dashboard/Views/ContentTabs/ProviderProxiesView.swift diff --git a/ClashX/Dashboard/Views/APISetting/ProgressButton.swift b/ClashX/Dashboard/Views/ContentTabs/Providers/ProgressButton.swift similarity index 100% rename from ClashX/Dashboard/Views/APISetting/ProgressButton.swift rename to ClashX/Dashboard/Views/ContentTabs/Providers/ProgressButton.swift diff --git a/ClashX/Dashboard/Views/ContentTabs/Providers/ProvidersView.swift b/ClashX/Dashboard/Views/ContentTabs/Providers/ProvidersView.swift index ded1ee3af..113481db1 100644 --- a/ClashX/Dashboard/Views/ContentTabs/Providers/ProvidersView.swift +++ b/ClashX/Dashboard/Views/ContentTabs/Providers/ProvidersView.swift @@ -65,7 +65,7 @@ struct ProvidersView: View { } } } - .introspect(.table, on: .macOS(.v12, .v13, .v14)) { + .introspect(.table, on: .macOS(.v12, .v13, .v14, .v15)) { $0.refusesFirstResponder = true $0.doubleAction = nil } diff --git a/ClashX/Dashboard/Views/ContentTabs/Proxies/ProxiesView.swift b/ClashX/Dashboard/Views/ContentTabs/Proxies/ProxiesView.swift index e050f5b39..4fd986aec 100644 --- a/ClashX/Dashboard/Views/ContentTabs/Proxies/ProxiesView.swift +++ b/ClashX/Dashboard/Views/ContentTabs/Proxies/ProxiesView.swift @@ -26,7 +26,7 @@ struct ProxiesView: View { List(proxyStorage.groups, id: \.id) { group in ProxyGroupRowView(proxyGroup: group) } - .introspect(.table, on: .macOS(.v12, .v13, .v14)) { + .introspect(.table, on: .macOS(.v12, .v13, .v14, .v15)) { $0.refusesFirstResponder = true $0.doubleAction = nil } diff --git a/ClashX/Dashboard/Views/DashboardView.swift b/ClashX/Dashboard/Views/DashboardView.swift index 4cea2695a..0a087828e 100644 --- a/ClashX/Dashboard/Views/DashboardView.swift +++ b/ClashX/Dashboard/Views/DashboardView.swift @@ -18,15 +18,9 @@ struct DashboardView: View { var body: some View { Group { - if !isRunning { - APISettingView() - -// .presentedWindowToolbarStyle(.expanded) - } else { - NavigationView { - SidebarView() - EmptyView() - } + NavigationView { + SidebarView() + EmptyView() } } .onReceive(runningState) { _ in diff --git a/ClashX/Dashboard/Views/SidebarView/SidebarListView.swift b/ClashX/Dashboard/Views/SidebarView/SidebarListView.swift index 92f0c3b6e..dce6f911d 100644 --- a/ClashX/Dashboard/Views/SidebarView/SidebarListView.swift +++ b/ClashX/Dashboard/Views/SidebarView/SidebarListView.swift @@ -59,7 +59,7 @@ struct SidebarListView: View { } } - .introspect(.table, on: .macOS(.v12, .v13, .v14)) { + .introspect(.table, on: .macOS(.v12, .v13, .v14, .v15)) { $0.refusesFirstResponder = true if selectionName == nil { diff --git a/ClashX/General/ApiRequest.swift b/ClashX/General/ApiRequest.swift index 451107540..414658cb1 100644 --- a/ClashX/General/ApiRequest.swift +++ b/ClashX/General/ApiRequest.swift @@ -12,12 +12,19 @@ import Starscream import SwiftyJSON protocol ApiRequestStreamDelegate: AnyObject { - func didUpdateTraffic(up: Int, down: Int) - func didGetLog(log: String, level: String) + func didUpdateTraffic(up: Int, down: Int) + func didGetLog(log: String, level: String) + func didUpdateMemory(memory: Int64) + func streamStatusChanged() } typealias ErrorString = String +struct ClashVersion: Decodable { + let version: String + let meta: Bool? +} + class ApiRequest { static let shared = ApiRequest() @@ -75,15 +82,35 @@ class ApiRequest { weak var delegate: ApiRequestStreamDelegate? - private var trafficWebSocket: WebSocket? - private var loggingWebSocket: WebSocket? - - private var trafficWebSocketRetryDelay: TimeInterval = 1 - private var loggingWebSocketRetryDelay: TimeInterval = 1 - private var trafficWebSocketRetryTimer: Timer? - private var loggingWebSocketRetryTimer: Timer? - - private var alamoFireManager: Session + private var trafficWebSocket: WebSocket? + private var loggingWebSocket: WebSocket? + private var memoryWebSocket: WebSocket? + + private var trafficWebSocketRetryDelay: TimeInterval = 1 + private var loggingWebSocketRetryDelay: TimeInterval = 1 + private var memoryWebSocketRetryDelay: TimeInterval = 1 + + private var trafficWebSocketRetryTimer: Timer? + private var loggingWebSocketRetryTimer: Timer? + private var memoryWebSocketRetryTimer: Timer? + + private var alamoFireManager: Session + + static func requestVersion(completeHandler: @escaping ((ClashVersion?) -> Void)) { + shared.alamoFireManager + .request(ConfigManager.apiUrl + "/version", + method: .get, + headers: authHeader()) + .responseDecodable(of: ClashVersion.self) { + resp in + switch resp.result { + case let .success(ver): + completeHandler(ver) + case let .failure(err): + completeHandler(nil) + } + } + } static func requestConfig(completeHandler: @escaping ((ClashConfig) -> Void)) { req("/configs").responseDecodable(of: ClashConfig.self) { @@ -317,10 +344,31 @@ extension ApiRequest { static func closeConnection(_ id: String) { req("/connections/\(id)", method: .delete).response { _ in } } - - static func closeAllConnection() { - req("/connections", method: .delete).response { _ in } - } + + static func getConnections(completeHandler: @escaping (DBConnectionSnapShot) -> Void) { + + let decoder = JSONDecoder() + decoder.dateDecodingStrategy = .formatted(DateFormatter.js) + + req("/connections").responseDecodable(of: DBConnectionSnapShot.self, decoder: decoder) { resp in + switch resp.result { + case let .success(snapshot): + completeHandler(snapshot) + case .failure: + return +// assertionFailure() +// completeHandler(DBConnectionSnapShot()) + } + } + } + + static func closeConnection(_ conn: ClashConnectionSnapShot.Connection) { + req("/connections/".appending(conn.id), method: .delete).response { _ in } + } + + static func closeAllConnection() { + req("/connections", method: .delete).response { _ in } + } } // MARK: - Meta @@ -512,10 +560,11 @@ extension ApiRequest { // MARK: - Stream Apis extension ApiRequest { - func resetStreamApis() { - resetLogStreamApi() - resetTrafficStreamApi() - } + func resetStreamApis() { + resetLogStreamApi() + resetTrafficStreamApi() + resetMemoryStreamApi() + } func resetLogStreamApi() { loggingWebSocketRetryTimer?.invalidate() @@ -530,6 +579,13 @@ extension ApiRequest { trafficWebSocketRetryDelay = 1 requestTrafficInfo() } + + func resetMemoryStreamApi() { + memoryWebSocketRetryTimer?.invalidate() + memoryWebSocketRetryTimer = nil + memoryWebSocketRetryDelay = 1 + requestMemoryInfo() + } private func requestTrafficInfo() { trafficWebSocketRetryTimer?.invalidate() @@ -561,60 +617,111 @@ extension ApiRequest { socket.connect() loggingWebSocket = socket } + + private func requestMemoryInfo() { + memoryWebSocketRetryTimer?.invalidate() + memoryWebSocketRetryTimer = nil + memoryWebSocket?.disconnect(forceTimeout: 1) + + let socket = WebSocket(url: URL(string: ConfigManager.apiUrl.appending("/memory"))!) + for header in ApiRequest.authHeader() { + socket.request.setValue(header.value, forHTTPHeaderField: header.name) + } + socket.delegate = self + socket.connect() + memoryWebSocket = socket + } } extension ApiRequest: WebSocketDelegate { - func websocketDidConnect(socket: WebSocketClient) { - guard let webSocket = socket as? WebSocket else { return } - if webSocket == trafficWebSocket { - trafficWebSocketRetryDelay = 1 - Logger.log("trafficWebSocket did Connect", level: .debug) - } else { - loggingWebSocketRetryDelay = 1 - Logger.log("loggingWebSocket did Connect", level: .debug) - } - } - - func websocketDidDisconnect(socket: WebSocketClient, error: Error?) { - guard let err = error else { - return - } - - Logger.log(err.localizedDescription, level: .error) - - guard let webSocket = socket as? WebSocket else { return } - - if webSocket == trafficWebSocket { - Logger.log("trafficWebSocket did disconnect", level: .debug) - trafficWebSocketRetryTimer?.invalidate() - trafficWebSocketRetryTimer = - Timer.scheduledTimer(withTimeInterval: trafficWebSocketRetryDelay, repeats: false, block: { - [weak self] _ in - if self?.trafficWebSocket?.isConnected == true { return } - self?.requestTrafficInfo() - }) - trafficWebSocketRetryDelay *= 2 - } else { - Logger.log("loggingWebSocket did disconnect", level: .debug) - loggingWebSocketRetryTimer = - Timer.scheduledTimer(withTimeInterval: loggingWebSocketRetryDelay, repeats: false, block: { - [weak self] _ in - if self?.loggingWebSocket?.isConnected == true { return } - self?.requestLog() - }) - loggingWebSocketRetryDelay *= 2 - } - } - - func websocketDidReceiveMessage(socket: WebSocketClient, text: String) { - guard let webSocket = socket as? WebSocket else { return } - let json = JSON(parseJSON: text) - if webSocket == trafficWebSocket { - delegate?.didUpdateTraffic(up: json["up"].intValue, down: json["down"].intValue) - } else { - delegate?.didGetLog(log: json["payload"].stringValue, level: json["type"].string ?? "info") - } - } - - func websocketDidReceiveData(socket: WebSocketClient, data: Data) {} + func websocketDidConnect(socket: WebSocketClient) { + guard let webSocket = socket as? WebSocket else { return } + switch webSocket { + case trafficWebSocket: + trafficWebSocketRetryDelay = 1 + Logger.log("trafficWebSocket did Connect", level: .debug) + + ConfigManager.shared.isRunning = true + delegate?.streamStatusChanged() + case loggingWebSocket: + loggingWebSocketRetryDelay = 1 + Logger.log("loggingWebSocket did Connect", level: .debug) + case memoryWebSocket: + memoryWebSocketRetryDelay = 1 + Logger.log("memoryWebSocket did Connect", level: .debug) + default: + return + } + } + + func websocketDidDisconnect(socket: WebSocketClient, error: Error?) { + + if (socket as? WebSocket) == trafficWebSocket { + ConfigManager.shared.isRunning = false + delegate?.streamStatusChanged() + } + + guard let err = error else { + return + } + + Logger.log(err.localizedDescription, level: .error) + + guard let webSocket = socket as? WebSocket else { return } + + switch webSocket { + case trafficWebSocket: + Logger.log("trafficWebSocket did disconnect", level: .debug) + + trafficWebSocketRetryTimer?.invalidate() + trafficWebSocketRetryTimer = + Timer.scheduledTimer(withTimeInterval: trafficWebSocketRetryDelay, repeats: false, block: { + [weak self] _ in + if self?.trafficWebSocket?.isConnected == true { return } + self?.requestTrafficInfo() + }) + trafficWebSocketRetryDelay *= 2 + case loggingWebSocket: + Logger.log("loggingWebSocket did disconnect", level: .debug) + loggingWebSocketRetryTimer = + Timer.scheduledTimer(withTimeInterval: loggingWebSocketRetryDelay, repeats: false, block: { + [weak self] _ in + if self?.loggingWebSocket?.isConnected == true { return } + self?.requestLog() + }) + loggingWebSocketRetryDelay *= 2 + case memoryWebSocket: + Logger.log("memoryWebSocket did disconnect", level: .debug) + + memoryWebSocketRetryTimer = + Timer.scheduledTimer(withTimeInterval: memoryWebSocketRetryDelay, repeats: false, block: { + [weak self] _ in + if self?.memoryWebSocket?.isConnected == true { return } + self?.requestMemoryInfo() + }) + + memoryWebSocketRetryDelay *= 2 + default: + return + } + } + + func websocketDidReceiveMessage(socket: WebSocketClient, text: String) { + guard let webSocket = socket as? WebSocket else { return } + let json = JSON(parseJSON: text) + + + switch webSocket { + case trafficWebSocket: + delegate?.didUpdateTraffic(up: json["up"].intValue, down: json["down"].intValue) + case loggingWebSocket: + delegate?.didGetLog(log: json["payload"].stringValue, level: json["type"].string ?? "info") + case memoryWebSocket: + delegate?.didUpdateMemory(memory: json["inuse"].int64Value) + default: + return + } + } + + func websocketDidReceiveData(socket: WebSocketClient, data: Data) {} } diff --git a/ClashX/General/Managers/ConfigManager.swift b/ClashX/General/Managers/ConfigManager.swift index eb0d2650c..6a239a23a 100644 --- a/ClashX/General/Managers/ConfigManager.swift +++ b/ClashX/General/Managers/ConfigManager.swift @@ -39,6 +39,7 @@ class ConfigManager { set { isRunningVariable.accept(newValue) + NotificationCenter.default.post(.init(name: .init("ClashRunningStateChanged"))) } } diff --git a/ClashX/General/Managers/MenuItemFactory.swift b/ClashX/General/Managers/MenuItemFactory.swift index 98e553c1f..02de1e778 100644 --- a/ClashX/General/Managers/MenuItemFactory.swift +++ b/ClashX/General/Managers/MenuItemFactory.swift @@ -387,16 +387,15 @@ extension MenuItemFactory { return lengths.max() ?? 0 } - static func providerUpdateTitle(_ updatedAt: String?) -> String? { + static func providerUpdateTitle(_ updatedAt: Date?) -> String? { let dateCF = DateComponentsFormatter() dateCF.allowedUnits = [.day, .hour, .minute] dateCF.maximumUnitCount = 1 dateCF.unitsStyle = .abbreviated dateCF.zeroFormattingBehavior = .dropAll - guard let dateStr = updatedAt, - let date = DateFormatter.provider.date(from: dateStr), - !date.timeIntervalSinceNow.isNaN, + guard let date = updatedAt, + !date.timeIntervalSinceNow.isNaN, !date.timeIntervalSinceNow.isInfinite, let re = dateCF.string(from: abs(date.timeIntervalSinceNow)) else { return nil } diff --git a/ClashX/Models/ClashConfig.swift b/ClashX/Models/ClashConfig.swift index 4f1584cda..eed376911 100644 --- a/ClashX/Models/ClashConfig.swift +++ b/ClashX/Models/ClashConfig.swift @@ -12,9 +12,6 @@ enum ClashProxyMode: String, Codable { case rule case global case direct - #if PRO_VERSION - case script - #endif } extension ClashProxyMode { @@ -23,20 +20,13 @@ extension ClashProxyMode { case .rule: return NSLocalizedString("Rule", comment: "") case .global: return NSLocalizedString("Global", comment: "") case .direct: return NSLocalizedString("Direct", comment: "") - #if PRO_VERSION - case .script: return NSLocalizedString("Script", comment: "") - #endif } } } enum ClashLogLevel: String, Codable { case info - #if PRO_VERSION - case warning = "warn" - #else - case warning - #endif + case warning case error case debug case silent @@ -61,56 +51,60 @@ enum ClashLogLevel: String, Codable { } class ClashConfig: Codable { - private var port: Int - private var socksPort: Int - var allowLan: Bool - var mixedPort: Int - var mode: ClashProxyMode - var logLevel: ClashLogLevel - - var sniffing: Bool - var tun: Tun - - struct Tun: Codable { - let enable: Bool - let device: String - let stack: String -// let dns-hijack: [String] -// let auto-route: Bool -// let auto-detect-interface: Bool - } - - var usedHttpPort: Int { - if mixedPort > 0 { - return mixedPort - } - return port - } - - var usedSocksPort: Int { - if mixedPort > 0 { - return mixedPort - } - return socksPort - } - - private enum CodingKeys: String, CodingKey { - case port, socksPort = "socks-port", mixedPort = "mixed-port", allowLan = "allow-lan", mode, logLevel = "log-level", sniffing, tun - } - - static func fromData(_ data: Data) -> ClashConfig? { - let decoder = JSONDecoder() - do { - return try decoder.decode(ClashConfig.self, from: data) - } catch let err { - Logger.log((err as NSError).description, level: .error) - return nil - } - } - - func copy() -> ClashConfig? { - guard let data = try? JSONEncoder().encode(self) else { return nil } - let copy = try? JSONDecoder().decode(ClashConfig.self, from: data) - return copy - } + var port: Int + var socksPort: Int + var redirPort: Int + var allowLan: Bool + var mixedPort: Int + var mode: ClashProxyMode + var logLevel: ClashLogLevel + + var sniffing: Bool + var ipv6: Bool + + var tun: Tun + var interfaceName: String + + struct Tun: Codable { + let enable: Bool + let device: String + let stack: String + // let dns-hijack: [String] + // let auto-route: Bool + // let auto-detect-interface: Bool + } + + var usedHttpPort: Int { + if mixedPort > 0 { + return mixedPort + } + return port + } + + var usedSocksPort: Int { + if mixedPort > 0 { + return mixedPort + } + return socksPort + } + + private enum CodingKeys: String, CodingKey { + case port, socksPort = "socks-port", redirPort = "redir-port", mixedPort = "mixed-port", allowLan = "allow-lan", mode, logLevel = "log-level", sniffing, tun, interfaceName = "interface-name", ipv6 + } + + static func fromData(_ data: Data) -> ClashConfig? { + let decoder = JSONDecoder() + do { + return try decoder.decode(ClashConfig.self, from: data) + } catch let err { + Logger.log((err as NSError).description, level: .error) + return nil + } + } + + func copy() -> ClashConfig? { + guard let data = try? JSONEncoder().encode(self) else { return nil } + let copy = try? JSONDecoder().decode(ClashConfig.self, from: data) + return copy + } } diff --git a/ClashX/Models/ClashProvider.swift b/ClashX/Models/ClashProvider.swift index 11b0a328a..1f284a2cb 100644 --- a/ClashX/Models/ClashProvider.swift +++ b/ClashX/Models/ClashProvider.swift @@ -44,16 +44,16 @@ class ClashProvider: Codable { let proxies: [ClashProxy] let type: ProviderType let vehicleType: ProviderVehicleType - let updatedAt: String? + let updatedAt: Date? let subscriptionInfo: ClashProviderSubInfo? } class ClashProviderSubInfo: Codable { - let upload: Int - let download: Int - let total: Int - let expire: Int + let upload: Int64 + let download: Int64 + let total: Int64 + let expire: Int private enum CodingKeys: String, CodingKey { case upload = "Upload", diff --git a/ClashX/Models/ClashProxy.swift b/ClashX/Models/ClashProxy.swift index 8c165e069..57e2bff94 100644 --- a/ClashX/Models/ClashProxy.swift +++ b/ClashX/Models/ClashProxy.swift @@ -87,13 +87,16 @@ class ClashProxySpeedHistory: Codable { }() } - lazy var delayDisplay: String = { - let d = meanDelay ?? delay - switch d { - case 0: return NSLocalizedString("fail", comment: "") - default: return "\(d) ms" - } - }() + lazy var delayInt: Int = { + meanDelay ?? delay + }() + + lazy var delayDisplay: String = { + switch delayInt { + case 0: return NSLocalizedString("fail", comment: "") + default: return "\(delayInt) ms" + } + }() lazy var dateDisplay: String = HisDateFormaterInstance.shared.formater.string(from: time) @@ -101,6 +104,7 @@ class ClashProxySpeedHistory: Codable { } class ClashProxy: Codable { + let id: String? let name: ClashProxyName let type: ClashProxyType let all: [ClashProxyName]? @@ -111,6 +115,9 @@ class ClashProxy: Codable { weak var enclosingProvider: ClashProvider? let hidden: Bool? + let udp: Bool + let xudp: Bool + let tfo: Bool enum SpeedtestAbleItem { case proxy(name: ClashProxyName) @@ -127,13 +134,17 @@ class ClashProxy: Codable { guard let resp = enclosingResp, let allProxys = all else { return [] } var proxys = [SpeedtestAbleItem]() for proxy in allProxys { - if let p = resp.proxiesMap[proxy] { - if let provider = p.enclosingProvider { - proxys.append(.provider(name: p.name, provider: provider.name)) - } else { - proxys.append(.group(name: p.name)) - } - } + if let p = resp.proxiesMap[proxy] { + if !ClashProxyType.isProxyGroup(p) { + if let provider = p.enclosingProvider { + proxys.append(.provider(name: p.name, provider: provider.name)) + } else { + proxys.append(.proxy(name: p.name)) + } + } else { + proxys.append(.group(name: p.name)) + } + } } return proxys }() @@ -141,7 +152,7 @@ class ClashProxy: Codable { lazy var isSpeedTestable: Bool = !speedtestAble.isEmpty private enum CodingKeys: String, CodingKey { - case type, all, history, now, name, alive, hidden + case type, all, history, now, name, udp, xudp, tfo, id, alive, hidden } lazy var maxProxyNameLength: CGFloat = { diff --git a/ClashX/Models/ClashRule.swift b/ClashX/Models/ClashRule.swift index 8be225b06..d2db4aad6 100644 --- a/ClashX/Models/ClashRule.swift +++ b/ClashX/Models/ClashRule.swift @@ -8,12 +8,21 @@ import Foundation -class ClashRule: Codable { - let type: String - let payload: String? - let proxy: String? +class ClashRule: NSObject, Codable, Identifiable { + @objc let type: String + @objc let payload: String? + @objc let proxy: String? + @objc var size: Int + + init(type: String, payload: String?, proxy: String?) { + self.type = type + self.payload = payload + self.proxy = proxy + self.size = -1 + } } + class ClashRuleResponse: Codable { var rules: [ClashRule]? diff --git a/ClashX/Models/ClashRuleProvider.swift b/ClashX/Models/ClashRuleProvider.swift index dacd75758..ae8615b0b 100644 --- a/ClashX/Models/ClashRuleProvider.swift +++ b/ClashX/Models/ClashRuleProvider.swift @@ -22,10 +22,10 @@ class ClashRuleProviderResp: Codable { } } -class ClashRuleProvider: Codable { - let name: ClashProviderName - let ruleCount: Int - let behavior: String - let type: String - let updatedAt: String? +class ClashRuleProvider: NSObject, Codable { + @objc let name: ClashProviderName + let ruleCount: Int + @objc let behavior: String + @objc let type: String + let updatedAt: Date? } From a89a3a2d7668fc76ed7eab49f0385f44f1be27a7 Mon Sep 17 00:00:00 2001 From: mrFq1 <1xxbx0il0@mozmail.com> Date: Sat, 21 Sep 2024 19:43:13 +0800 Subject: [PATCH 09/14] chore: update ci --- .github/workflows/main.yml | 27 --------------------------- 1 file changed, 27 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ed96f1132..b34983abb 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -48,22 +48,6 @@ jobs: /usr/libexec/PlistBuddy -c 'Print gitCommit' ClashX/Info.plist /usr/libexec/PlistBuddy -c 'Print buildTime' ClashX/Info.plist - - name: swiftui build infos - run: | - /usr/libexec/PlistBuddy -c "Set CFBundleVersion $(git rev-list --count origin/master..origin/meta)" ClashX/ClashX\ Meta\ SwiftUI-Info.plist - /usr/libexec/PlistBuddy -c "Set CFBundleShortVersionString $(git describe --tags --abbrev=0)" ClashX/ClashX\ Meta\ SwiftUI-Info.plist - /usr/libexec/PlistBuddy -c "Add coreVersion string $(ls clash.meta | grep -m1 "" | sed -ne 's/.*64-\(.*\).gz/\1/p')" ClashX/ClashX\ Meta\ SwiftUI-Info.plist - /usr/libexec/PlistBuddy -c "Add gitBranch string $GITHUB_REF_NAME" ClashX/ClashX\ Meta\ SwiftUI-Info.plist - /usr/libexec/PlistBuddy -c "Add gitCommit string ${GITHUB_SHA::7}" ClashX/ClashX\ Meta\ SwiftUI-Info.plist - /usr/libexec/PlistBuddy -c "Add buildTime string $(date +%Y-%m-%d\ %H:%M)" ClashX/ClashX\ Meta\ SwiftUI-Info.plist - - /usr/libexec/PlistBuddy -c 'Print CFBundleVersion' ClashX/ClashX\ Meta\ SwiftUI-Info.plist - /usr/libexec/PlistBuddy -c 'Print CFBundleShortVersionString' ClashX/ClashX\ Meta\ SwiftUI-Info.plist - /usr/libexec/PlistBuddy -c 'Print coreVersion' ClashX/ClashX\ Meta\ SwiftUI-Info.plist - /usr/libexec/PlistBuddy -c 'Print gitBranch' ClashX/ClashX\ Meta\ SwiftUI-Info.plist - /usr/libexec/PlistBuddy -c 'Print gitCommit' ClashX/ClashX\ Meta\ SwiftUI-Info.plist - /usr/libexec/PlistBuddy -c 'Print buildTime' ClashX/ClashX\ Meta\ SwiftUI-Info.plist - - name: install deps run: | bash install_dependency.sh @@ -71,23 +55,13 @@ jobs: - name: build - if: startsWith(github.ref, 'refs/tags/') run: | xcodebuild archive -project ClashX.xcodeproj -scheme ClashX\ Meta -archivePath archive/ClashX.xcarchive -showBuildTimingSummary -allowProvisioningUpdates - - name: build-SwiftUI - run: | - xcodebuild archive -project ClashX.xcodeproj -scheme ClashX\ Meta\ SwiftUI -archivePath archive/ClashX\ SwiftUI.xcarchive -showBuildTimingSummary -allowProvisioningUpdates - - - name: create zip - if: startsWith(github.ref, 'refs/tags/') run: ditto -c -k --sequesterRsrc --keepParent archive/ClashX.xcarchive/Products/Applications/ClashX\ Meta.app "ClashX Meta.zip" - - name: create SwiftUI zip - run: ditto -c -k --sequesterRsrc --keepParent archive/ClashX\ SwiftUI.xcarchive/Products/Applications/ClashX\ Meta.app "ClashX Meta macOS 12.0+.zip" - - name: upload Artifact uses: actions/upload-artifact@v4 @@ -105,5 +79,4 @@ jobs: generate_release_notes: true files: | ClashX Meta.zip - ClashX Meta macOS 12.0+.zip From f37aec4334c43daa0856b85535baeaa3068aaf30 Mon Sep 17 00:00:00 2001 From: mrFq1 <1xxbx0il0@mozmail.com> Date: Sat, 21 Sep 2024 20:08:06 +0800 Subject: [PATCH 10/14] misc: delete info.plist --- ClashX.xcodeproj/project.pbxproj | 6 +- ClashX/ClashX Meta SwiftUI-Info.plist | 134 -------------------------- 2 files changed, 2 insertions(+), 138 deletions(-) delete mode 100644 ClashX/ClashX Meta SwiftUI-Info.plist diff --git a/ClashX.xcodeproj/project.pbxproj b/ClashX.xcodeproj/project.pbxproj index f492ae5f4..4449b7b43 100644 --- a/ClashX.xcodeproj/project.pbxproj +++ b/ClashX.xcodeproj/project.pbxproj @@ -267,7 +267,6 @@ 01BCDB1C2C9EE2CF0028FA94 /* ProgressButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressButton.swift; sourceTree = ""; }; 01C1462928962E4E00346AF3 /* com.metacubex.ClashX.ProxyConfigHelper.meta.gz */ = {isa = PBXFileReference; lastKnownFileType = archive.gzip; path = com.metacubex.ClashX.ProxyConfigHelper.meta.gz; sourceTree = ""; }; 01CA6BBF2B6A1B3100E386D6 /* MetaServer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetaServer.swift; sourceTree = ""; }; - 01D567DF2AD1562700CDA0AE /* ClashX Meta SwiftUI-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "ClashX Meta SwiftUI-Info.plist"; sourceTree = ""; }; 01D567E12AD158B500CDA0AE /* MetaPrefs.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = MetaPrefs.storyboard; sourceTree = ""; }; 01D567E22AD158B500CDA0AE /* MetaPrefsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MetaPrefsViewController.swift; sourceTree = ""; }; 01E33AB129B5BF4200FD1006 /* NSColor+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSColor+Extension.swift"; sourceTree = ""; }; @@ -803,7 +802,6 @@ 49CF3B2020CD7463001EBF94 /* AppDelegate.swift */, 49CF3B2620CD7465001EBF94 /* Main.storyboard */, 49CF3B2920CD7465001EBF94 /* Info.plist */, - 01D567DF2AD1562700CDA0AE /* ClashX Meta SwiftUI-Info.plist */, 49CF3B2A20CD7465001EBF94 /* ClashX.entitlements */, 49CF3B3520CD75DF001EBF94 /* ClashX-Bridging-Header.h */, F9FAB31D262BE04800DE02A6 /* Images.xcassets */, @@ -1209,7 +1207,7 @@ "$(PROJECT_DIR)/ClashX/Support\\ Files", "$(PROJECT_DIR)/ClashX/Support\\ Files", ); - INFOPLIST_FILE = "ClashX/ClashX Meta SwiftUI-Info.plist"; + INFOPLIST_FILE = ClashX/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", @@ -1256,7 +1254,7 @@ "$(PROJECT_DIR)/ClashX/Support\\ Files", "$(PROJECT_DIR)/ClashX/Support\\ Files", ); - INFOPLIST_FILE = "ClashX/ClashX Meta SwiftUI-Info.plist"; + INFOPLIST_FILE = ClashX/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", diff --git a/ClashX/ClashX Meta SwiftUI-Info.plist b/ClashX/ClashX Meta SwiftUI-Info.plist deleted file mode 100644 index ecd93338e..000000000 --- a/ClashX/ClashX Meta SwiftUI-Info.plist +++ /dev/null @@ -1,134 +0,0 @@ - - - - - NSLocationAlwaysAndWhenInUseUsageDescription - ClashX use location info to detect your current WiFi network SSID name and provide the auto suspend services. - NSLocationWhenInUseUsageDescription - ClashX use location info to detect your current WiFi network SSID name and provide the auto suspend services. - BETA - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleDocumentTypes - - - CFBundleTypeExtensions - - yaml - - CFBundleTypeName - YAML - CFBundleTypeRole - Viewer - LSHandlerRank - None - - - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - APPL - CFBundleShortVersionString - $(MARKETING_VERSION) - CFBundleURLTypes - - - CFBundleTypeRole - Editor - CFBundleURLIconFile - Icon - CFBundleURLName - com.west2online.ClashX - CFBundleURLSchemes - - clashx - - - - CFBundleTypeRole - Editor - CFBundleURLIconFile - Icon - CFBundleURLName - com.west2online.Clash - CFBundleURLSchemes - - clash - - - - CFBundleVersion - $(CURRENT_PROJECT_VERSION) - Fabric - - APIKey - e5990c61f53f0e5713e37f78458291760d631147 - Kits - - - KitInfo - - KitName - Crashlytics - - - - LSApplicationCategoryType - public.app-category.utilities - LSMinimumSystemVersion - $(MACOSX_DEPLOYMENT_TARGET) - LSUIElement - - NSAppTransportSecurity - - NSAllowsArbitraryLoads - - - NSAppleScriptEnabled - - NSHumanReadableCopyright - Copyright © 2021年 yichengchen. All rights reserved. - NSMainStoryboardFile - Main - NSPrincipalClass - NSApplication - NSSupportsAutomaticTermination - - NSSupportsSuddenTermination - - NSUbiquitousContainers - - iCloud.com.metacubex.ClashX - - NSUbiquitousContainerIsDocumentScopePublic - - NSUbiquitousContainerName - ClashX Meta - NSUbiquitousContainerSupportedFolderLevels - Any - - - OSAScriptingDefinition - ProxySetting.sdef - SMAuthorizedClients - - identifier "com.west2online.ClashX" and anchor apple generic and certificate leaf[subject.CN] = "Mac Developer: chen yicheng (96U846XGYH)" and certificate 1[field.1.2.840.113635.100.6.2.1] /* exists */ - - SMPrivilegedExecutables - - com.west2online.ClashX.ProxyConfigHelper - anchor apple generic and identifier "com.metacubex.ClashX.ProxyConfigHelper" and (certificate leaf[field.1.2.840.113635.100.6.1.9] /* exists */ or certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.OU] = MEWHFZ92DY) - - SUDisallowSelectChannel - - SUFeedURL - https://yichengchen.github.io/clashX/appcast.xml - - From d1e8891f321d85bfcb1ea6181419bb19c52f814b Mon Sep 17 00:00:00 2001 From: mrFq1 <1xxbx0il0@mozmail.com> Date: Sat, 21 Sep 2024 22:11:55 +0800 Subject: [PATCH 11/14] feat: sparkle (cherry picked from commit 9d0296ccf509aa463ecca4e01ebb1eeac1af2105) --- ClashX.xcodeproj/project.pbxproj | 33 +++-- .../xcshareddata/swiftpm/Package.resolved | 11 +- ClashX/AppDelegate.swift | 28 +---- ClashX/Base.lproj/Main.storyboard | 6 +- .../General/Managers/AutoUpgardeManager.swift | 115 ------------------ ClashX/Info.plist | 4 +- ClashX/Views/StatusItem/StatusItemView.xib | 1 + 7 files changed, 41 insertions(+), 157 deletions(-) delete mode 100644 ClashX/General/Managers/AutoUpgardeManager.swift diff --git a/ClashX.xcodeproj/project.pbxproj b/ClashX.xcodeproj/project.pbxproj index 4449b7b43..9b924e7be 100644 --- a/ClashX.xcodeproj/project.pbxproj +++ b/ClashX.xcodeproj/project.pbxproj @@ -53,6 +53,7 @@ 01BCDB1A2C9ECD010028FA94 /* SidebarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BCDAE72C9ECD010028FA94 /* SidebarView.swift */; }; 01BCDB1B2C9ECD010028FA94 /* SidebarLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BCDAE52C9ECD010028FA94 /* SidebarLabel.swift */; }; 01BCDB1D2C9EE2CF0028FA94 /* ProgressButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BCDB1C2C9EE2CF0028FA94 /* ProgressButton.swift */; }; + 01BCDB212C9EEE260028FA94 /* Sparkle in Frameworks */ = {isa = PBXBuildFile; productRef = 01BCDB202C9EEE260028FA94 /* Sparkle */; }; 01CA6BC12B6A1B3100E386D6 /* MetaServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01CA6BBF2B6A1B3100E386D6 /* MetaServer.swift */; }; 01CA6BC22B6A1B3100E386D6 /* MetaServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01CA6BBF2B6A1B3100E386D6 /* MetaServer.swift */; }; 01D567E42AD158B600CDA0AE /* MetaPrefs.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 01D567E12AD158B500CDA0AE /* MetaPrefs.storyboard */; }; @@ -117,7 +118,6 @@ 01F336032AD10D0B0048AF77 /* ProxyGroupMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = F910AA23240134AF00116E95 /* ProxyGroupMenu.swift */; }; 01F336042AD10D0B0048AF77 /* MenuItemFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4952C3BE2115C7CA004A4FA8 /* MenuItemFactory.swift */; }; 01F336052AD10D0B0048AF77 /* MetaTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0162E74E2864B819007218A6 /* MetaTask.swift */; }; - 01F336062AD10D0B0048AF77 /* AutoUpgardeManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = F977FAAB2366790500C17F1F /* AutoUpgardeManager.swift */; }; 01F336072AD10D0B0048AF77 /* RemoteConfigModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 499A485722ED715200F6C675 /* RemoteConfigModel.swift */; }; 01F336082AD10D0B0048AF77 /* ProxyGroupMenuItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49D176AA23575BB20093DD7B /* ProxyGroupMenuItemView.swift */; }; 01F336092AD10D0B0048AF77 /* PrivilegedHelperManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49B4575C244F4A2A00463C39 /* PrivilegedHelperManager.swift */; }; @@ -374,7 +374,6 @@ F939724B23A4B33500FE5A3F /* ClashProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClashProvider.swift; sourceTree = ""; }; F939724D23A4DB0600FE5A3F /* DateFormatter+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DateFormatter+.swift"; sourceTree = ""; }; F976275B23634DF8000EDEFE /* LoginServiceKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginServiceKit.swift; sourceTree = ""; }; - F977FAAB2366790500C17F1F /* AutoUpgardeManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoUpgardeManager.swift; sourceTree = ""; }; F977FAAD23669D6400C17F1F /* ConnectionManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectionManager.swift; sourceTree = ""; }; F9A7C0692306E874007163C7 /* com.metacubex.ClashX.ProxyConfigHelper */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = com.metacubex.ClashX.ProxyConfigHelper; sourceTree = BUILT_PRODUCTS_DIR; }; F9E754CF239CC21F00CEE7CC /* WebPortalManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebPortalManager.swift; sourceTree = ""; }; @@ -397,6 +396,7 @@ 01F336292AD10D0B0048AF77 /* PromiseKit in Frameworks */, 01BCDAAC2C9ECB3A0028FA94 /* DSFSparkline in Frameworks */, 01F3362A2AD10D0B0048AF77 /* KeyboardShortcuts in Frameworks */, + 01BCDB212C9EEE260028FA94 /* Sparkle in Frameworks */, 01F3362B2AD10D0B0048AF77 /* Alamofire in Frameworks */, 01F3362C2AD10D0B0048AF77 /* CocoaLumberjack in Frameworks */, 01F3362D2AD10D0B0048AF77 /* Yams in Frameworks */, @@ -607,7 +607,6 @@ 495BFB8721919B9800C8779D /* RemoteConfigManager.swift */, 4952C3BE2115C7CA004A4FA8 /* MenuItemFactory.swift */, F935B2FB23085515009E4D33 /* SystemProxyManager.swift */, - F977FAAB2366790500C17F1F /* AutoUpgardeManager.swift */, F977FAAD23669D6400C17F1F /* ConnectionManager.swift */, F9E754CF239CC21F00CEE7CC /* WebPortalManager.swift */, 49B4575C244F4A2A00463C39 /* PrivilegedHelperManager.swift */, @@ -899,6 +898,7 @@ 01F335CA2AD10D0B0048AF77 /* PromiseKit */, 01BCDAAB2C9ECB3A0028FA94 /* DSFSparkline */, 01BCDAB22C9ECB560028FA94 /* SwiftUIIntrospect */, + 01BCDB202C9EEE260028FA94 /* Sparkle */, ); productName = ClashX; productReference = 01F336442AD10D0B0048AF77 /* ClashX Meta.app */; @@ -962,6 +962,7 @@ 015B97842A4F31AE00F9FA4D /* XCRemoteSwiftPackageReference "PromiseKit" */, 01BCDAAA2C9ECB3A0028FA94 /* XCRemoteSwiftPackageReference "DSFSparkline" */, 01BCDAB12C9ECB560028FA94 /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */, + 01BCDB1F2C9EEE260028FA94 /* XCRemoteSwiftPackageReference "Sparkle" */, ); productRefGroup = 49CF3B1E20CD7463001EBF94 /* Products */; projectDirPath = ""; @@ -1063,7 +1064,6 @@ 01F336032AD10D0B0048AF77 /* ProxyGroupMenu.swift in Sources */, 01F336042AD10D0B0048AF77 /* MenuItemFactory.swift in Sources */, 01F336052AD10D0B0048AF77 /* MetaTask.swift in Sources */, - 01F336062AD10D0B0048AF77 /* AutoUpgardeManager.swift in Sources */, 01BCDB1D2C9EE2CF0028FA94 /* ProgressButton.swift in Sources */, 01F336072AD10D0B0048AF77 /* RemoteConfigModel.swift in Sources */, 01F336082AD10D0B0048AF77 /* ProxyGroupMenuItemView.swift in Sources */, @@ -1196,7 +1196,7 @@ CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; COMPRESS_PNG_FILES = YES; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 12; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = ""; ENABLE_HARDENED_RUNTIME = YES; @@ -1218,12 +1218,12 @@ "$(PROJECT_DIR)/ClashX", ); MACOSX_DEPLOYMENT_TARGET = 11.0; - MARKETING_VERSION = v1.0; + MARKETING_VERSION = v1.2; PRODUCT_BUNDLE_IDENTIFIER = com.metacubex.ClashX.meta; PRODUCT_NAME = "ClashX Meta"; PROVISIONING_PROFILE_SPECIFIER = ""; STRIP_PNG_TEXT = YES; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG SwiftUI_Version"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OBJC_BRIDGING_HEADER = "ClashX/ClashX-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; @@ -1242,7 +1242,7 @@ COMBINE_HIDPI_IMAGES = YES; COMPRESS_PNG_FILES = YES; COPY_PHASE_STRIP = YES; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 12; DEAD_CODE_STRIPPING = YES; DEPLOYMENT_POSTPROCESSING = YES; DEVELOPMENT_TEAM = ""; @@ -1265,13 +1265,13 @@ "$(PROJECT_DIR)/ClashX", ); MACOSX_DEPLOYMENT_TARGET = 11.0; - MARKETING_VERSION = v1.0; + MARKETING_VERSION = v1.2; OTHER_CODE_SIGN_FLAGS = "--timestamp"; PRODUCT_BUNDLE_IDENTIFIER = com.metacubex.ClashX.meta; PRODUCT_NAME = "ClashX Meta"; PROVISIONING_PROFILE_SPECIFIER = ""; STRIP_PNG_TEXT = YES; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = SwiftUI_Version; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = ""; SWIFT_OBJC_BRIDGING_HEADER = "ClashX/ClashX-Bridging-Header.h"; SWIFT_VERSION = 5.0; }; @@ -1602,6 +1602,14 @@ minimumVersion = 1.3.0; }; }; + 01BCDB1F2C9EEE260028FA94 /* XCRemoteSwiftPackageReference "Sparkle" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/sparkle-project/Sparkle"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 2.6.4; + }; + }; 01F335B72AD10D0B0048AF77 /* XCRemoteSwiftPackageReference "KeyboardShortcuts" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/sindresorhus/KeyboardShortcuts.git"; @@ -1703,6 +1711,11 @@ package = 01BCDAB12C9ECB560028FA94 /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */; productName = SwiftUIIntrospect; }; + 01BCDB202C9EEE260028FA94 /* Sparkle */ = { + isa = XCSwiftPackageProductDependency; + package = 01BCDB1F2C9EEE260028FA94 /* XCRemoteSwiftPackageReference "Sparkle" */; + productName = Sparkle; + }; 01F335B62AD10D0B0048AF77 /* KeyboardShortcuts */ = { isa = XCSwiftPackageProductDependency; package = 01F335B72AD10D0B0048AF77 /* XCRemoteSwiftPackageReference "KeyboardShortcuts" */; diff --git a/ClashX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ClashX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index e6ed012b2..f8f2acb13 100644 --- a/ClashX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/ClashX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "79f29dc60a8a95688aa73c0cce32f5496ed9744c56a5d38efc20f94aacd73360", + "originHash" : "8ca2b383ef3a419b80259396c281989a0b5151620017e615fd9f74e2a0dabf52", "pins" : [ { "identity" : "alamofire", @@ -91,6 +91,15 @@ "version" : "6.7.1" } }, + { + "identity" : "sparkle", + "kind" : "remoteSourceControl", + "location" : "https://github.com/sparkle-project/Sparkle", + "state" : { + "revision" : "0ef1ee0220239b3776f433314515fd849025673f", + "version" : "2.6.4" + } + }, { "identity" : "starscream", "kind" : "remoteSourceControl", diff --git a/ClashX/AppDelegate.swift b/ClashX/AppDelegate.swift index bbb49aa03..d862e28c8 100644 --- a/ClashX/AppDelegate.swift +++ b/ClashX/AppDelegate.swift @@ -21,7 +21,6 @@ private let MetaCoreMd5 = "WOSHIZIDONGSHENGCHENGDEA" @main class AppDelegate: NSObject, NSApplicationDelegate { private(set) var statusItem: NSStatusItem! - @IBOutlet var checkForUpdateMenuItem: NSMenuItem! @IBOutlet var statusMenu: NSMenu! @IBOutlet var proxySettingMenuItem: NSMenuItem! @@ -116,8 +115,7 @@ class AppDelegate: NSObject, NSApplicationDelegate { if WebPortalManager.hasWebProtal { WebPortalManager.shared.addWebProtalMenuItem(&statusMenu) } - AutoUpgardeManager.shared.setup() - AutoUpgardeManager.shared.setupCheckForUpdatesMenuItem(checkForUpdateMenuItem) + // install proxy helper _ = ClashResourceManager.check() PrivilegedHelperManager.shared.checkInstall() @@ -832,30 +830,6 @@ extension AppDelegate { } } - @IBAction func checkForUpdate(_ sender: NSMenuItem) { - let unc = NSUserNotificationCenter.default - AF.request("https://api.github.com/repos/MetaCubeX/ClashX.Meta/releases/latest").responseString { - guard $0.error == nil, - let data = $0.data, - let tagName = try? JSON(data: data)["tag_name"].string else { - unc.postUpdateNotice(msg: NSLocalizedString("Some thing failed.", comment: "")) - return - } - - if tagName != AppVersionUtil.currentVersion { - let alert = NSAlert() - alert.messageText = NSLocalizedString("Open github release page to download ", comment: "") + "\(tagName)" - alert.addButton(withTitle: NSLocalizedString("OK", comment: "")) - alert.addButton(withTitle: NSLocalizedString("Cancel", comment: "")) - if alert.runModal() == .alertFirstButtonReturn { - NSWorkspace.shared.open(.init(string: "https://github.com/MetaCubeX/ClashX.Meta/releases/latest")!) - } - } else { - unc.postUpdateNotice(msg: NSLocalizedString("No new release found.", comment: "")) - } - } - } - @IBAction func updateGEO(_ sender: NSMenuItem) { guard updateGeoTimer == nil else { return } updateGeoTimer = Timer.scheduledTimer(withTimeInterval: 500, repeats: true) { [weak self] timer in diff --git a/ClashX/Base.lproj/Main.storyboard b/ClashX/Base.lproj/Main.storyboard index bcc4141f6..92e1063f6 100644 --- a/ClashX/Base.lproj/Main.storyboard +++ b/ClashX/Base.lproj/Main.storyboard @@ -1,6 +1,7 @@ + @@ -710,7 +711,6 @@ - @@ -938,7 +938,7 @@ - + @@ -1028,7 +1028,7 @@ - + diff --git a/ClashX/General/Managers/AutoUpgardeManager.swift b/ClashX/General/Managers/AutoUpgardeManager.swift deleted file mode 100644 index c17f4815a..000000000 --- a/ClashX/General/Managers/AutoUpgardeManager.swift +++ /dev/null @@ -1,115 +0,0 @@ -// -// AutoUpgardeManager.swift -// ClashX -// -// Created by yicheng on 2019/10/28. -// Copyright © 2019 west2online. All rights reserved. -// - -import Cocoa -// import Sparkle - -class AutoUpgardeManager: NSObject { - var checkForUpdatesMenuItem: NSMenuItem? - static let shared = AutoUpgardeManager() -// private var controller:SPUStandardUpdaterController? - private var current: Channel = { - if let value = UserDefaults.standard.object(forKey: "AutoUpgardeManager.current") as? Int, - let channel = Channel(rawValue: value) { return channel } - #if PRO_VERSION - return .appcenter - #else - return .stable - #endif - }() { - didSet { - UserDefaults.standard.set(current.rawValue, forKey: "AutoUpgardeManager.current") - } - } - - private var allowSelectChannel: Bool { - return Bundle.main.object(forInfoDictionaryKey: "SUDisallowSelectChannel") as? Bool != true - } - - // MARK: Public - - func setup() { -// controller = SPUStandardUpdaterController(updaterDelegate: self, userDriverDelegate: nil) - } - - func setupCheckForUpdatesMenuItem(_ item: NSMenuItem) { -// checkForUpdatesMenuItem = item -// checkForUpdatesMenuItem?.target = controller -// checkForUpdatesMenuItem?.action = #selector(SPUStandardUpdaterController.checkForUpdates(_:)) - } - - func addChannelMenuItem(_ button: NSPopUpButton) { - for channel in Channel.allCases { - button.addItem(withTitle: channel.title) - button.lastItem?.tag = channel.rawValue - } - button.target = self - button.action = #selector(didselectChannel(sender:)) - button.selectItem(withTag: current.rawValue) - } - - @objc func didselectChannel(sender: NSPopUpButton) { - guard let tag = sender.selectedItem?.tag, let channel = Channel(rawValue: tag) else { return } - current = channel - } -} - -//extension AutoUpgardeManager: SPUUpdaterDelegate { -// func feedURLString(for updater: SPUUpdater) -> String? { -// guard WebPortalManager.hasWebProtal == false, allowSelectChannel else { return nil } -// return current.urlString -// } -// -// func updaterWillRelaunchApplication(_ updater: SPUUpdater) { -// SystemProxyManager.shared.disableProxy(port: 0, socksPort: 0, forceDisable: true) -// } -//} - -// MARK: - Channel Enum - -extension AutoUpgardeManager { - enum Channel: Int, CaseIterable { - #if !PRO_VERSION - case stable - case prelease - #endif - case appcenter - } -} - -extension AutoUpgardeManager.Channel { - var title: String { - switch self { - #if !PRO_VERSION - case .stable: - return NSLocalizedString("Stable", comment: "") - case .prelease: - return NSLocalizedString("Prelease", comment: "") - #endif - case .appcenter: - return "Appcenter" - } - } - - var urlString: String { - switch self { - #if !PRO_VERSION - case .stable: - return "https://yichengchen.github.io/clashX/appcast.xml" - case .prelease: - return "https://yichengchen.github.io/clashX/appcast_pre.xml" - #endif - case .appcenter: - #if PRO_VERSION - return "https://api.appcenter.ms/v0.1/public/sparkle/apps/1cd052f7-e118-4d13-87fb-35176f9702c1" - #else - return "https://api.appcenter.ms/v0.1/public/sparkle/apps/dce6e9a3-b6e3-4fd2-9f2d-35c767a99663" - #endif - } - } -} diff --git a/ClashX/Info.plist b/ClashX/Info.plist index ecd93338e..ed194bc6a 100644 --- a/ClashX/Info.plist +++ b/ClashX/Info.plist @@ -129,6 +129,8 @@ SUDisallowSelectChannel SUFeedURL - https://yichengchen.github.io/clashX/appcast.xml + https://raw.githubusercontent.com/MetaCubeX/clashX.meta/refs/heads/sparkle/appcast.xml + SUPublicEDKey + Jhu0RI5Vp02om6JhxYnFewD82GCV8v7U05toMFXb+7U= diff --git a/ClashX/Views/StatusItem/StatusItemView.xib b/ClashX/Views/StatusItem/StatusItemView.xib index 207bcf182..0d23a25f7 100644 --- a/ClashX/Views/StatusItem/StatusItemView.xib +++ b/ClashX/Views/StatusItem/StatusItemView.xib @@ -1,6 +1,7 @@ + From 2ac25f022e34557dc7c7b4a5f10e2ece462b051a Mon Sep 17 00:00:00 2001 From: mrFq1 <1xxbx0il0@mozmail.com> Date: Sun, 22 Sep 2024 17:20:43 +0800 Subject: [PATCH 12/14] misc: delete AutoUpgardeManager --- ClashX/ViewControllers/Settings/DebugSettingViewController.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/ClashX/ViewControllers/Settings/DebugSettingViewController.swift b/ClashX/ViewControllers/Settings/DebugSettingViewController.swift index ba02e56d4..8f71fb186 100644 --- a/ClashX/ViewControllers/Settings/DebugSettingViewController.swift +++ b/ClashX/ViewControllers/Settings/DebugSettingViewController.swift @@ -18,7 +18,6 @@ class DebugSettingViewController: NSViewController { super.viewDidLoad() useBuiltinApiButton.state = Settings.builtInApiMode ? .on : .off revertProxyButton.state = Settings.disableRestoreProxy ? .off : .on - AutoUpgardeManager.shared.addChannelMenuItem(updateChannelPopButton) } @IBAction func actionUnInstallProxyHelper(_ sender: Any) { From 7da5dc94aee50db26e6f1808739aa222ec4254c6 Mon Sep 17 00:00:00 2001 From: mrFq1 <1xxbx0il0@mozmail.com> Date: Sun, 22 Sep 2024 17:31:35 +0800 Subject: [PATCH 13/14] chore: sparkle ci --- .github/workflows/main.yml | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b34983abb..09635221a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -58,7 +58,6 @@ jobs: run: | xcodebuild archive -project ClashX.xcodeproj -scheme ClashX\ Meta -archivePath archive/ClashX.xcarchive -showBuildTimingSummary -allowProvisioningUpdates - - name: create zip run: ditto -c -k --sequesterRsrc --keepParent archive/ClashX.xcarchive/Products/Applications/ClashX\ Meta.app "ClashX Meta.zip" @@ -70,6 +69,28 @@ jobs: name: "ClashX Meta.zip" path: "*.zip" + - name: load sparkle-repo + uses: actions/checkout@v4 + if: startsWith(github.ref, 'refs/tags/') + with: + ref: sparkle + path: 'sparkle-repo' + + - name: update sparkle-repo + if: startsWith(github.ref, 'refs/tags/') + run: | + cp "ClashX Meta.zip" "sparkle-repo/ClashX Meta $GITHUB_REF_NAME.zip" + + brew install sparkle + echo '${{ secrets.ED_KEY }}' | $(brew --prefix)/Caskroom/sparkle/2.*/bin/generate_appcast sparkle-repo --ed-key-file - + + cd sparkle-repo + git config --global user.name "github-actions[bot]" + git config --global user.email 'github-actions[bot]@users.noreply.github.com' + git add -A + git commit -m $GITHUB_REF_NAME + git push origin sparkle + - name: upload build to github uses: softprops/action-gh-release@v2 if: startsWith(github.ref, 'refs/tags/') From 1bf4a7ecdbdeea47ec750a14cfbd6beab219cdfc Mon Sep 17 00:00:00 2001 From: mrFq1 <1xxbx0il0@mozmail.com> Date: Sun, 22 Sep 2024 17:32:53 +0800 Subject: [PATCH 14/14] revert: versions --- ClashX.xcodeproj/project.pbxproj | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ClashX.xcodeproj/project.pbxproj b/ClashX.xcodeproj/project.pbxproj index 9b924e7be..b2ffdbf1a 100644 --- a/ClashX.xcodeproj/project.pbxproj +++ b/ClashX.xcodeproj/project.pbxproj @@ -1196,7 +1196,7 @@ CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; COMPRESS_PNG_FILES = YES; - CURRENT_PROJECT_VERSION = 12; + CURRENT_PROJECT_VERSION = 0; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = ""; ENABLE_HARDENED_RUNTIME = YES; @@ -1218,7 +1218,7 @@ "$(PROJECT_DIR)/ClashX", ); MACOSX_DEPLOYMENT_TARGET = 11.0; - MARKETING_VERSION = v1.2; + MARKETING_VERSION = v1.0; PRODUCT_BUNDLE_IDENTIFIER = com.metacubex.ClashX.meta; PRODUCT_NAME = "ClashX Meta"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1242,7 +1242,7 @@ COMBINE_HIDPI_IMAGES = YES; COMPRESS_PNG_FILES = YES; COPY_PHASE_STRIP = YES; - CURRENT_PROJECT_VERSION = 12; + CURRENT_PROJECT_VERSION = 0; DEAD_CODE_STRIPPING = YES; DEPLOYMENT_POSTPROCESSING = YES; DEVELOPMENT_TEAM = ""; @@ -1265,7 +1265,7 @@ "$(PROJECT_DIR)/ClashX", ); MACOSX_DEPLOYMENT_TARGET = 11.0; - MARKETING_VERSION = v1.2; + MARKETING_VERSION = v1.0; OTHER_CODE_SIGN_FLAGS = "--timestamp"; PRODUCT_BUNDLE_IDENTIFIER = com.metacubex.ClashX.meta; PRODUCT_NAME = "ClashX Meta";