From 4ffae68705a4cad88bd2f3e9648f527f0892c53b Mon Sep 17 00:00:00 2001 From: yanue Date: Fri, 21 Jun 2024 23:39:34 +0800 Subject: [PATCH] change a lot --- Podfile.lock | 24 +- V2rayU/AppDelegate.swift | 4 - V2rayU/Base.lproj/ConfigWindow.xib | 416 ++++++++--------- V2rayU/Base.lproj/MainMenu.xib | 4 +- V2rayU/ConfigWindow.swift | 431 ++++++++++-------- V2rayU/MainMenu.swift | 136 +++--- V2rayU/Ping.swift | 65 +-- V2rayU/Preference/PreferencePac.swift | 11 +- .../Preference/PreferenceSubscription.swift | 13 +- V2rayU/ToastWindow.swift | 39 +- V2rayU/Util.swift | 26 +- V2rayU/V2rayLaunch.swift | 55 +-- V2rayU/V2raySubscription.swift | 45 +- 13 files changed, 657 insertions(+), 612 deletions(-) diff --git a/Podfile.lock b/Podfile.lock index acb7c77..177accc 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -1,7 +1,7 @@ PODS: - Alamofire (4.8.2) - - FirebaseAnalytics (10.27.0): - - FirebaseAnalytics/AdIdSupport (= 10.27.0) + - FirebaseAnalytics (10.24.0): + - FirebaseAnalytics/AdIdSupport (= 10.24.0) - FirebaseCore (~> 10.0) - FirebaseInstallations (~> 10.0) - GoogleUtilities/AppDelegateSwizzler (~> 7.11) @@ -9,10 +9,10 @@ PODS: - GoogleUtilities/Network (~> 7.11) - "GoogleUtilities/NSData+zlib (~> 7.11)" - nanopb (< 2.30911.0, >= 2.30908.0) - - FirebaseAnalytics/AdIdSupport (10.27.0): + - FirebaseAnalytics/AdIdSupport (10.24.0): - FirebaseCore (~> 10.0) - FirebaseInstallations (~> 10.0) - - GoogleAppMeasurement (= 10.27.0) + - GoogleAppMeasurement (= 10.24.0) - GoogleUtilities/AppDelegateSwizzler (~> 7.11) - GoogleUtilities/MethodSwizzler (~> 7.11) - GoogleUtilities/Network (~> 7.11) @@ -49,21 +49,21 @@ PODS: - GoogleUtilities/Environment (~> 7.10) - nanopb (< 2.30911.0, >= 2.30908.0) - PromisesSwift (~> 2.1) - - GoogleAppMeasurement (10.27.0): - - GoogleAppMeasurement/AdIdSupport (= 10.27.0) + - GoogleAppMeasurement (10.24.0): + - GoogleAppMeasurement/AdIdSupport (= 10.24.0) - GoogleUtilities/AppDelegateSwizzler (~> 7.11) - GoogleUtilities/MethodSwizzler (~> 7.11) - GoogleUtilities/Network (~> 7.11) - "GoogleUtilities/NSData+zlib (~> 7.11)" - nanopb (< 2.30911.0, >= 2.30908.0) - - GoogleAppMeasurement/AdIdSupport (10.27.0): - - GoogleAppMeasurement/WithoutAdIdSupport (= 10.27.0) + - GoogleAppMeasurement/AdIdSupport (10.24.0): + - GoogleAppMeasurement/WithoutAdIdSupport (= 10.24.0) - GoogleUtilities/AppDelegateSwizzler (~> 7.11) - GoogleUtilities/MethodSwizzler (~> 7.11) - GoogleUtilities/Network (~> 7.11) - "GoogleUtilities/NSData+zlib (~> 7.11)" - nanopb (< 2.30911.0, >= 2.30908.0) - - GoogleAppMeasurement/WithoutAdIdSupport (10.27.0): + - GoogleAppMeasurement/WithoutAdIdSupport (10.24.0): - GoogleUtilities/AppDelegateSwizzler (~> 7.11) - GoogleUtilities/MethodSwizzler (~> 7.11) - GoogleUtilities/Network (~> 7.11) @@ -119,7 +119,7 @@ PODS: DEPENDENCIES: - Alamofire - - FirebaseAnalytics (> 10.24.0) + - FirebaseAnalytics (~> 10.24.0) - FirebaseCrashlytics - MASShortcut - Preferences (from `https://github.com/sindresorhus/Preferences.git`) @@ -164,7 +164,7 @@ CHECKOUT OPTIONS: SPEC CHECKSUMS: Alamofire: ae5c501addb7afdbb13687d7f2f722c78734c2d3 - FirebaseAnalytics: f9211b719db260cc91aebee8bb539cb367d0dfd1 + FirebaseAnalytics: b5efc493eb0f40ec560b04a472e3e1a15d39ca13 FirebaseCore: 11dc8a16dfb7c5e3c3f45ba0e191a33ac4f50894 FirebaseCoreExtension: af5fd85e817ea9d19f9a2659a376cf9cf99f03c0 FirebaseCoreInternal: bcb5acffd4ea05e12a783ecf835f2210ce3dc6af @@ -172,7 +172,7 @@ SPEC CHECKSUMS: FirebaseInstallations: 766dabca09fd94aef922538aaf144cc4a6fb6869 FirebaseRemoteConfigInterop: 6c349a466490aeace3ce9c091c86be1730711634 FirebaseSessions: 2651b464e241c93fd44112f995d5ab663c970487 - GoogleAppMeasurement: f65fc137531af9ad647f1c0a42f3b6a4d3a98049 + GoogleAppMeasurement: f3abf08495ef2cba7829f15318c373b8d9226491 GoogleDataTransport: 6c09b596d841063d76d4288cc2d2f42cc36e1e2a GoogleUtilities: d053d902a8edaa9904e1bd00c37535385b8ed152 MASShortcut: 9c215e8a8a78f3d01ce56da48e2730ab66b538fa diff --git a/V2rayU/AppDelegate.swift b/V2rayU/AppDelegate.swift index 3fd7875..ceb1e64 100644 --- a/V2rayU/AppDelegate.swift +++ b/V2rayU/AppDelegate.swift @@ -112,10 +112,6 @@ class AppDelegate: NSObject, NSApplicationDelegate { UserDefaults.set(forKey: .runMode, value: RunMode.global.rawValue) } V2rayServer.loadConfig() - if V2rayServer.count() == 0 { - // add default - V2rayServer.add(remark: "default", json: "", isValid: false) - } } @objc func handleAppleEvent(event: NSAppleEventDescriptor, replyEvent: NSAppleEventDescriptor) { diff --git a/V2rayU/Base.lproj/ConfigWindow.xib b/V2rayU/Base.lproj/ConfigWindow.xib index 8856af7..a22aed4 100644 --- a/V2rayU/Base.lproj/ConfigWindow.xib +++ b/V2rayU/Base.lproj/ConfigWindow.xib @@ -2,7 +2,7 @@ - + @@ -192,106 +192,6 @@ Gw - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -301,7 +201,7 @@ Gw - + @@ -317,7 +217,7 @@ Gw - + @@ -342,7 +242,7 @@ Gw - + @@ -351,7 +251,7 @@ Gw - + @@ -414,7 +314,7 @@ Gw - + @@ -423,7 +323,7 @@ Gw - + @@ -473,7 +373,7 @@ Gw - + @@ -482,7 +382,7 @@ Gw - + @@ -491,7 +391,7 @@ Gw - + @@ -500,7 +400,7 @@ Gw - + @@ -509,7 +409,7 @@ Gw - + @@ -521,7 +421,7 @@ Gw - + @@ -534,7 +434,7 @@ Gw - + @@ -547,7 +447,7 @@ Gw - + @@ -559,7 +459,7 @@ Gw - + @@ -568,7 +468,7 @@ Gw - + @@ -605,7 +505,7 @@ Gw - + @@ -624,7 +524,7 @@ Gw - + @@ -633,7 +533,7 @@ Gw - + @@ -642,7 +542,7 @@ Gw - + @@ -651,7 +551,7 @@ Gw - + @@ -663,7 +563,7 @@ Gw - + @@ -676,7 +576,7 @@ Gw - + @@ -688,7 +588,7 @@ Gw - + @@ -697,7 +597,7 @@ Gw - + @@ -710,7 +610,7 @@ Gw - + @@ -722,7 +622,7 @@ Gw - + @@ -737,7 +637,7 @@ Gw - + @@ -746,7 +646,7 @@ Gw - + @@ -758,7 +658,7 @@ Gw - + @@ -770,7 +670,7 @@ Gw - + @@ -794,7 +694,7 @@ Gw - + @@ -803,7 +703,7 @@ Gw - + @@ -815,7 +715,7 @@ Gw - + @@ -830,7 +730,7 @@ Gw - + @@ -839,7 +739,7 @@ Gw - + @@ -851,7 +751,7 @@ Gw - + @@ -863,7 +763,7 @@ Gw - + @@ -872,7 +772,7 @@ Gw - + @@ -881,7 +781,7 @@ Gw - + @@ -890,7 +790,7 @@ Gw - + @@ -902,7 +802,7 @@ Gw - + @@ -917,7 +817,7 @@ Gw - + @@ -926,7 +826,7 @@ Gw - + @@ -938,7 +838,7 @@ Gw - + @@ -947,7 +847,7 @@ Gw - + @@ -956,7 +856,7 @@ Gw - + @@ -965,7 +865,7 @@ Gw - + @@ -974,7 +874,7 @@ Gw - + @@ -986,7 +886,7 @@ Gw - + @@ -995,7 +895,7 @@ Gw - + @@ -1004,7 +904,7 @@ Gw - + @@ -1040,7 +940,7 @@ Gw - + @@ -1065,7 +965,7 @@ Gw - + @@ -1077,7 +977,7 @@ Gw - + @@ -1086,7 +986,7 @@ Gw - + @@ -1097,7 +997,7 @@ Gw - + @@ -1137,7 +1037,7 @@ Gw - + @@ -1146,7 +1046,7 @@ Gw - + @@ -1158,7 +1058,7 @@ Gw - + @@ -1167,7 +1067,7 @@ Gw - + @@ -1179,7 +1079,7 @@ Gw - + @@ -1188,7 +1088,7 @@ Gw - + @@ -1200,7 +1100,7 @@ Gw - + @@ -1209,7 +1109,7 @@ Gw - + @@ -1225,7 +1125,7 @@ Gw - + @@ -1245,7 +1145,7 @@ Gw - + @@ -1272,7 +1172,7 @@ Gw - + @@ -1281,7 +1181,7 @@ Gw - + @@ -1301,7 +1201,7 @@ Gw - + @@ -1310,7 +1210,7 @@ Gw - + @@ -1325,7 +1225,7 @@ Gw - + @@ -1334,7 +1234,7 @@ Gw - + @@ -1346,7 +1246,7 @@ Gw - + @@ -1355,7 +1255,7 @@ Gw - + @@ -1367,7 +1267,7 @@ Gw - + @@ -1379,7 +1279,7 @@ Gw - + @@ -1388,7 +1288,7 @@ Gw - + @@ -1400,7 +1300,7 @@ Gw - + @@ -1409,7 +1309,7 @@ Gw - + @@ -1421,7 +1321,7 @@ Gw - + @@ -1430,7 +1330,7 @@ Gw - + @@ -1439,7 +1339,7 @@ Gw - + @@ -1489,7 +1389,7 @@ Gw - + @@ -1504,7 +1404,7 @@ Gw - + @@ -1513,7 +1413,7 @@ Gw - + @@ -1525,7 +1425,7 @@ Gw - + @@ -1537,7 +1437,7 @@ Gw - + @@ -1552,7 +1452,7 @@ Gw - + @@ -1561,7 +1461,7 @@ Gw - + @@ -1573,7 +1473,7 @@ Gw - + @@ -1585,7 +1485,7 @@ Gw - + @@ -1600,7 +1500,7 @@ Gw - + @@ -1612,7 +1512,7 @@ Gw - + @@ -1621,7 +1521,7 @@ Gw - + @@ -1630,7 +1530,7 @@ Gw - + @@ -1639,7 +1539,7 @@ Gw - + @@ -1665,7 +1565,7 @@ Gw - + @@ -1695,7 +1595,7 @@ Gw - + @@ -1704,7 +1604,7 @@ Gw - + @@ -1743,7 +1643,7 @@ Gw - + @@ -1772,7 +1672,7 @@ Gw - + @@ -1781,7 +1681,7 @@ Gw - + @@ -1801,7 +1701,7 @@ Gw - + @@ -1810,7 +1710,7 @@ Gw - + @@ -1881,9 +1781,109 @@ Gw + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + @@ -1949,7 +1949,7 @@ Gw - + diff --git a/V2rayU/Base.lproj/MainMenu.xib b/V2rayU/Base.lproj/MainMenu.xib index 67d0c21..19542c3 100644 --- a/V2rayU/Base.lproj/MainMenu.xib +++ b/V2rayU/Base.lproj/MainMenu.xib @@ -1,8 +1,8 @@ - + - + diff --git a/V2rayU/ConfigWindow.swift b/V2rayU/ConfigWindow.swift index 26deb1f..2949377 100644 --- a/V2rayU/ConfigWindow.swift +++ b/V2rayU/ConfigWindow.swift @@ -11,23 +11,15 @@ import Alamofire var v2rayConfig: V2rayConfig = V2rayConfig() -var configWindow = ConfigWindowController() +let configWindow = ConfigWindowController() func OpenConfigWindow(){ - if configWindow != nil { - // close before - - } else { - // renew - configWindow = ConfigWindowController() - } - - _ = showDock(state: true) - // show window - configWindow.showWindow(nil) - configWindow.window?.makeKeyAndOrderFront(configWindow.self) - // bring to front - NSApp.activate(ignoringOtherApps: true) + // show window + configWindow.showWindow(nil) + configWindow.window?.makeKeyAndOrderFront(configWindow.self) + // bring to front + NSApp.activate(ignoringOtherApps: true) + showDock(state: true) } class ConfigWindowController: NSWindowController, NSWindowDelegate, NSTabViewDelegate { @@ -165,7 +157,8 @@ class ConfigWindowController: NSWindowController, NSWindowDelegate, NSTabViewDel override func windowDidLoad() { super.windowDidLoad() - + + V2rayServer.loadConfig() // table view self.serversTableView.delegate = self self.serversTableView.dataSource = self @@ -177,60 +170,64 @@ class ConfigWindowController: NSWindowController, NSWindowDelegate, NSTabViewDel @IBAction func addRemoveServer(_ sender: NSSegmentedCell) { // 0 add,1 remove let seg = addRemoveButton.indexOfSelectedItem - - switch seg { + DispatchQueue.global().async { + switch seg { // add server config - case 0: - // add - V2rayServer.add() - - // reload data - self.serversTableView.reloadData() - // selected current row - self.serversTableView.selectRowIndexes(NSIndexSet(index: V2rayServer.count() - 1) as IndexSet, byExtendingSelection: false) - - break - + case 0: + // add + V2rayServer.add() + + DispatchQueue.main.sync { + V2rayServer.loadConfig() + // reload data + self.serversTableView.reloadData() + // selected current row + self.serversTableView.selectRowIndexes(NSIndexSet(index: V2rayServer.count() - 1) as IndexSet, byExtendingSelection: false) + } + break + // delete server config - case 1: - // get seleted index - let idx = self.serversTableView.selectedRow - // remove - V2rayServer.remove(idx: idx) - - // reload - V2rayServer.loadConfig() - menuController.showServers() - - // selected prev row - let cnt: Int = V2rayServer.count() - var rowIndex: Int = idx - 1 - if idx > 0 && idx < cnt { - rowIndex = idx - } - - // reload - self.serversTableView.reloadData() - - // fix - if cnt > 1 { - // selected row - self.serversTableView.selectRowIndexes(NSIndexSet(index: rowIndex) as IndexSet, byExtendingSelection: false) - } - - if rowIndex >= 0 { - self.loadJsonData(rowIndex: rowIndex) - } else { - self.serversTableView.becomeFirstResponder() - } - - // refresh menu - menuController.showServers() - break - + case 1: + // get seleted index + let idx = self.serversTableView.selectedRow + // remove + V2rayServer.remove(idx: idx) + + // reload + V2rayServer.loadConfig() + menuController.showServers() + + // selected prev row + let cnt: Int = V2rayServer.count() + var rowIndex: Int = idx - 1 + if idx > 0 && idx < cnt { + rowIndex = idx + } + + DispatchQueue.main.sync { + // reload + self.serversTableView.reloadData() + // fix + if cnt > 1 { + // selected row + self.serversTableView.selectRowIndexes(NSIndexSet(index: rowIndex) as IndexSet, byExtendingSelection: false) + } + + if rowIndex >= 0 { + self.loadJsonData(rowIndex: rowIndex) + } else { + self.serversTableView.becomeFirstResponder() + } + } + + // refresh menu + menuController.showServers() + break + // unknown action - default: - return + default: + return + } } } @@ -273,7 +270,6 @@ class ConfigWindowController: NSWindowController, NSWindowDelegate, NSTabViewDel func switchToImportView() { // reset error self.errTip.stringValue = "" - self.exportData() v2rayConfig.checkManualValid() @@ -554,7 +550,7 @@ class ConfigWindowController: NSWindowController, NSWindowDelegate, NSTabViewDel self.configText.string = item?.json ?? "" v2rayConfig.isValid = item?.isValid ?? false self.jsonUrl.stringValue = item?.url ?? "" - + v2rayConfig.parseJson(jsonText: self.configText.string) if v2rayConfig.errors.count > 0 { self.errTip.stringValue = v2rayConfig.errors[0] @@ -567,7 +563,9 @@ class ConfigWindowController: NSWindowController, NSWindowDelegate, NSTabViewDel v2rayConfig.parseJson(jsonText: self.configText.string) if v2rayConfig.errors.count > 0 { - self.errTip.stringValue = v2rayConfig.errors[0] + DispatchQueue.main.sync { + self.errTip.stringValue = v2rayConfig.errors[0] + } } // save @@ -582,7 +580,9 @@ class ConfigWindowController: NSWindowController, NSWindowDelegate, NSTabViewDel } self.refreshServerList(ok: errMsg.count == 0) } else { - self.errTip.stringValue = errMsg + DispatchQueue.main.sync { + self.errTip.stringValue = errMsg + } } } @@ -591,8 +591,8 @@ class ConfigWindowController: NSWindowController, NSWindowDelegate, NSTabViewDel menuController.showServers() // if server is current if let curName = UserDefaults.get(forKey: .v2rayCurrentServerName) { - let v2rayItemList = V2rayServer.list() - if curName == v2rayItemList[self.serversTableView.selectedRow].name { + let v2rayItemList = V2rayServer.all() + if v2rayItemList.count > self.serversTableView.selectedRow && curName == v2rayItemList[self.serversTableView.selectedRow].name { if ok { V2rayLaunch.startV2rayCore() } else { @@ -649,12 +649,16 @@ class ConfigWindowController: NSWindowController, NSWindowDelegate, NSTabViewDel // download json file Alamofire.request(jsonUrl.stringValue).responseString { DataResponse in if (DataResponse.error != nil) { - self.errTip.stringValue = "error: " + DataResponse.error.debugDescription + DispatchQueue.main.async{ + self.errTip.stringValue = "error: " + DataResponse.error.debugDescription + } return } if DataResponse.value != nil { - self.configText.string = v2rayConfig.formatJson(json: DataResponse.value ?? text) + DispatchQueue.main.async{ + self.configText.string = v2rayConfig.formatJson(json: DataResponse.value ?? text) + } } } } @@ -664,115 +668,142 @@ class ConfigWindowController: NSWindowController, NSWindowDelegate, NSTabViewDel guard let url = URL(string: "https://www.v2ray.com/chapter_02/transport/tcp.html") else { return } - NSWorkspace.shared.open(url) + DispatchQueue.main.async{ + NSWorkspace.shared.open(url) + } } @IBAction func goDsHelp(_ sender: Any) { guard let url = URL(string: "https://www.v2ray.com/chapter_02/transport/domainsocket.html") else { return } - NSWorkspace.shared.open(url) + DispatchQueue.main.async{ + NSWorkspace.shared.open(url) + } } @IBAction func goQuicHelp(_ sender: Any) { guard let url = URL(string: "https://www.v2ray.com/chapter_02/transport/quic.html") else { return } - NSWorkspace.shared.open(url) + DispatchQueue.main.async{ + NSWorkspace.shared.open(url) + } } @IBAction func goProtocolHelp(_ sender: NSButton) { guard let url = URL(string: "https://www.v2ray.com/chapter_02/protocols/vmess.html") else { return } - NSWorkspace.shared.open(url) + DispatchQueue.main.async{ + NSWorkspace.shared.open(url) + } } @IBAction func goVersionHelp(_ sender: Any) { guard let url = URL(string: "https://www.v2ray.com/chapter_02/01_overview.html") else { return } - NSWorkspace.shared.open(url) + DispatchQueue.main.async{ + NSWorkspace.shared.open(url) + } } @IBAction func goStreamHelp(_ sender: Any) { guard let url = URL(string: "https://www.v2ray.com/chapter_02/05_transport.html") else { return } - NSWorkspace.shared.open(url) + DispatchQueue.main.async{ + NSWorkspace.shared.open(url) + } } func switchSteamView(network: String) { - networkView.subviews.forEach { - $0.isHidden = true - } - - switch network { - case "tcp": - self.tcpView.isHidden = false - break; - case "kcp": - self.kcpView.isHidden = false - break; - case "domainsocket": - self.dsView.isHidden = false - break; - case "ws": - self.wsView.isHidden = false - break; - case "h2": - self.h2View.isHidden = false - break; - case "quic": - self.quicView.isHidden = false - break; - case "grpc": - self.grpcView.isHidden = false - break; - default: // vmess - self.tcpView.isHidden = false - break + DispatchQueue.main.async{ + self.networkView.subviews.forEach { + $0.isHidden = true + } + + switch network { + case "tcp": + self.tcpView.isHidden = false + break; + case "kcp": + self.kcpView.isHidden = false + break; + case "domainsocket": + self.dsView.isHidden = false + break; + case "ws": + self.wsView.isHidden = false + break; + case "h2": + self.h2View.isHidden = false + break; + case "quic": + self.quicView.isHidden = false + break; + case "grpc": + self.grpcView.isHidden = false + break; + default: // vmess + self.tcpView.isHidden = false + break + } } } func switchOutboundView(protocolTitle: String) { - serverView.subviews.forEach { - $0.isHidden = true - } - - switch protocolTitle { - case "vmess": - self.VmessView.isHidden = false - break - case "vless": - self.VlessView.isHidden = false - break - case "shadowsocks": - self.ShadowsocksView.isHidden = false - break - case "socks": - self.SocksView.isHidden = false - break - case "trojan": - self.TrojanView.isHidden = false - break - default: // vmess - self.VmessView.isHidden = true - break + DispatchQueue.main.async{ + self.serverView.subviews.forEach { + $0.isHidden = true + } + + switch protocolTitle { + case "vmess": + self.VmessView.isHidden = false + break + case "vless": + self.VlessView.isHidden = false + break + case "shadowsocks": + self.ShadowsocksView.isHidden = false + break + case "socks": + self.SocksView.isHidden = false + break + case "trojan": + self.TrojanView.isHidden = false + break + default: // vmess + self.VmessView.isHidden = true + break + } } } func switchSecurityView(securityTitle: String) { - print("switchSecurityView",securityTitle) - self.tlsView.isHidden = true - self.realityView.isHidden = true - if securityTitle == "reality" { - self.realityView.isHidden = false - } else { - self.tlsView.isHidden = false + DispatchQueue.main.async{ + print("switchSecurityView",securityTitle) + self.tlsView.isHidden = true + self.realityView.isHidden = true + if securityTitle == "reality" { + self.realityView.isHidden = false + } else { + self.tlsView.isHidden = false + } } } + func reloadData(){ + DispatchQueue.main.async{ + if self.serversTableView != nil { + V2rayServer.loadConfig() + self.serversTableView.reloadData() + } + } + } + @IBAction func switchSteamSecurity(_ sender: NSPopUpButtonCell) { if let item = switchSecurity.selectedItem { self.switchSecurityView(securityTitle: item.title) @@ -795,43 +826,47 @@ class ConfigWindowController: NSWindowController, NSWindowDelegate, NSTabViewDel guard let item = sender.selectedItem else { return } - // url - if item.title == "url" { - jsonUrl.stringValue = "" - selectFileBtn.isHidden = true - importBtn.isHidden = false - jsonUrl.isEditable = true - } else { - // local file - jsonUrl.stringValue = "" - selectFileBtn.isHidden = false - importBtn.isHidden = true - jsonUrl.isEditable = false + DispatchQueue.main.async{ + // url + if item.title == "url" { + self.jsonUrl.stringValue = "" + self.selectFileBtn.isHidden = true + self.importBtn.isHidden = false + self.jsonUrl.isEditable = true + } else { + // local file + self.jsonUrl.stringValue = "" + self.selectFileBtn.isHidden = false + self.importBtn.isHidden = true + self.jsonUrl.isEditable = false + } } } @IBAction func browseFile(_ sender: NSButton) { - jsonUrl.stringValue = "" - let dialog = NSOpenPanel() - - dialog.title = "Choose a .json file"; - dialog.showsResizeIndicator = true; - dialog.showsHiddenFiles = false; - dialog.canChooseDirectories = true; - dialog.canCreateDirectories = true; - dialog.allowsMultipleSelection = false; - dialog.allowedFileTypes = ["json", "txt"]; - - if (dialog.runModal() == NSApplication.ModalResponse.OK) { - let result = dialog.url // Pathname of the file - - if (result != nil) { - jsonUrl.stringValue = result?.absoluteString ?? "" - self.importJson() + DispatchQueue.main.async{ + self.jsonUrl.stringValue = "" + let dialog = NSOpenPanel() + + dialog.title = "Choose a .json file"; + dialog.showsResizeIndicator = true; + dialog.showsHiddenFiles = false; + dialog.canChooseDirectories = true; + dialog.canCreateDirectories = true; + dialog.allowsMultipleSelection = false; + dialog.allowedFileTypes = ["json", "txt"]; + + if (dialog.runModal() == NSApplication.ModalResponse.OK) { + let result = dialog.url // Pathname of the file + + if (result != nil) { + self.jsonUrl.stringValue = result?.absoluteString ?? "" + self.importJson() + } + } else { + // User clicked on "Cancel" + return } - } else { - // User clicked on "Cancel" - return } } @@ -845,19 +880,25 @@ class ConfigWindowController: NSWindowController, NSWindowDelegate, NSTabViewDel @IBAction func cancel(_ sender: NSButton) { // hide dock icon and close all opened windows - _ = showDock(state: false) + showDock(state: false) } @IBAction func goAdvanceSetting(_ sender: Any) { - preferencesWindowController.show(preferencePane: .advanceTab) + DispatchQueue.main.async { + preferencesWindowController.show(preferencePane: .advanceTab) + } } @IBAction func goSubscribeSetting(_ sender: Any) { - preferencesWindowController.show(preferencePane: .subscribeTab) + DispatchQueue.main.async { + preferencesWindowController.show(preferencePane: .subscribeTab) + } } @IBAction func goRoutingRuleSetting(_ sender: Any) { - preferencesWindowController.show(preferencePane: .routingTab) + DispatchQueue.main.async { + preferencesWindowController.show(preferencePane: .routingTab) + } } } @@ -883,12 +924,16 @@ extension ConfigWindowController: NSTableViewDataSource { NSLog("remark is nil") return } - // edit item remark - V2rayServer.edit(rowIndex: row, remark: remark) - // reload table - tableView.reloadData() - // reload menu - menuController.showServers() + DispatchQueue.global().async { + // edit item remark + V2rayServer.edit(rowIndex: row, remark: remark) + // reload table + DispatchQueue.main.async { + tableView.reloadData() + } + // reload menu + menuController.showServers() + } } } @@ -942,16 +987,18 @@ extension ConfigWindowController: NSTableViewDelegate { newIndexOffset += 1 } } - - // move - V2rayServer.move(oldIndex: oldIndexLast, newIndex: newIndexLast) - // set selected - self.serversTableView.selectRowIndexes(NSIndexSet(index: newIndexLast) as IndexSet, byExtendingSelection: false) - // reload table - self.serversTableView.reloadData() - // reload menu - menuController.showServers() - + DispatchQueue.global().async { + // move + V2rayServer.move(oldIndex: oldIndexLast, newIndex: newIndexLast) + DispatchQueue.main.async { + // set selected + self.serversTableView.selectRowIndexes(NSIndexSet(index: newIndexLast) as IndexSet, byExtendingSelection: false) + // reload table + self.serversTableView.reloadData() + } + // reload menu + menuController.showServers() + } return true } } diff --git a/V2rayU/MainMenu.swift b/V2rayU/MainMenu.swift index d0aa946..fdb578b 100644 --- a/V2rayU/MainMenu.swift +++ b/V2rayU/MainMenu.swift @@ -31,6 +31,7 @@ class MenuController: NSObject, NSMenuDelegate { // when menu.xib loaded override func awakeFromNib() { print("awakeFromNib") + super.awakeFromNib() statusMenu.delegate = self statusItem.menu = statusMenu @@ -42,89 +43,85 @@ class MenuController: NSObject, NSMenuDelegate { } func setStatusOff() { - v2rayStatusItem.title = "v2ray-core: Off" + (" (v" + appVersion + ")") - toggleV2rayItem.title = "Turn v2ray-core On" + DispatchQueue.main.async { + self.v2rayStatusItem.title = "v2ray-core: Off" + (" (v" + appVersion + ")") + self.toggleV2rayItem.title = "Turn v2ray-core On" - if let button = statusItem.button { - // UI API called on a background thread: -[NSStatusBarButton setImage:] - DispatchQueue.main.async { + if let button = self.statusItem.button { button.image = NSImage(named: NSImage.Name("IconOff")) } - } - - self.pacMode.state = .off - self.globalMode.state = .off - self.manualMode.state = .off - - // set off - UserDefaults.setBool(forKey: .v2rayTurnOn, value: false) - } - - func setModeIcon(mode: RunMode) { - var iconName = "IconOn" - switch mode { - case .global: - iconName = "IconOnG" - self.pacMode.state = .off - self.globalMode.state = .on - self.manualMode.state = .off - case .manual: - iconName = "IconOnM" self.pacMode.state = .off self.globalMode.state = .off - self.manualMode.state = .on - case .pac: - iconName = "IconOnP" - self.pacMode.state = .on - self.globalMode.state = .off self.manualMode.state = .off - default: - break + + // set off + UserDefaults.setBool(forKey: .v2rayTurnOn, value: false) } + } - if let button = statusItem.button { - // UI API called on a background thread: -[NSStatusBarButton setImage:] - DispatchQueue.main.async { + func setModeIcon(mode: RunMode) { + DispatchQueue.main.async { + + var iconName = "IconOn" + + switch mode { + case .global: + iconName = "IconOnG" + self.pacMode.state = .off + self.globalMode.state = .on + self.manualMode.state = .off + case .manual: + iconName = "IconOnM" + self.pacMode.state = .off + self.globalMode.state = .off + self.manualMode.state = .on + case .pac: + iconName = "IconOnP" + self.pacMode.state = .on + self.globalMode.state = .off + self.manualMode.state = .off + default: + break + } + + if let button = self.statusItem.button { button.image = NSImage(named: NSImage.Name(iconName)) } } } - + func setStatusOn(mode: RunMode) { - v2rayStatusItem.title = "v2ray-core: On" + (" (v" + appVersion + ")") - toggleV2rayItem.title = "Turn v2ray-core Off" - - self.setModeIcon(mode: mode) - - // set on - UserDefaults.setBool(forKey: .v2rayTurnOn, value: true) + DispatchQueue.main.async { + self.v2rayStatusItem.title = "v2ray-core: On" + (" (v" + appVersion + ")") + self.toggleV2rayItem.title = "Turn v2ray-core Off" + self.setModeIcon(mode: mode) + UserDefaults.setBool(forKey: .v2rayTurnOn, value: true) + } } func setStatusMenuTip(pingTip: String) { - do { - DispatchQueue.main.async { - if self.statusMenu.item(withTag: 1) != nil { - self.statusMenu.item(withTag: 1)!.title = pingTip - } + DispatchQueue.main.async { + if let item = self.statusMenu.item(withTag: 1) { + item.title = pingTip } } } func showServers() { - print("showServers") - let _subMenus = getServerMenus() - lock.lock() - do { + DispatchQueue.global().async { + self.lock.lock() + defer { self.lock.unlock() } + + print("showServers") + let _subMenus = self.getServerMenus() + DispatchQueue.main.async { self.serverItems.submenu = _subMenus // fix: must be used from main thread only - if configWindow != nil && configWindow.serversTableView != nil { - configWindow.serversTableView.reloadData() - } + configWindow.reloadData() } } - lock.unlock() } func getServerMenus() -> NSMenu { @@ -135,10 +132,8 @@ class MenuController: NSObject, NSMenuDelegate { var validCount = 0 var groupMenus: Dictionary = [String: NSMenu]() var chooseGroup = "" - // reload servers - V2rayServer.loadConfig() // for each - for item in V2rayServer.list() { + for item in V2rayServer.all() { validCount+=1 let menuItem: NSMenuItem = self.buildServerItem(item: item, curSer: curSer) var groupTag: String = item.subscribe @@ -217,45 +212,43 @@ class MenuController: NSObject, NSMenuDelegate { } @IBAction func openPreferenceGeneral(_ sender: NSMenuItem) { - preferencesWindowController.show(preferencePane: .generalTab) + DispatchQueue.main.async { + preferencesWindowController.show(preferencePane: .generalTab) + } } @IBAction func openPreferenceSubscribe(_ sender: NSMenuItem) { - preferencesWindowController.show(preferencePane: .subscribeTab) + DispatchQueue.main.async { + preferencesWindowController.show(preferencePane: .subscribeTab) + } } @IBAction func openPreferencePac(_ sender: NSMenuItem) { - preferencesWindowController.show(preferencePane: .pacTab) + DispatchQueue.main.async { + preferencesWindowController.show(preferencePane: .pacTab) + } } - // switch server @IBAction func switchServer(_ sender: NSMenuItem) { guard let obj = sender.representedObject as? V2rayItem else { NSLog("switchServer err") return } - // set current UserDefaults.set(forKey: .v2rayCurrentServerName, value: obj.name) - // restart V2rayLaunch.restartV2ray() } - // open config window @IBAction func openConfig(_ sender: NSMenuItem) { OpenConfigWindow() } - /// When a window was closed this methods takes care of releasing its controller. - /// - /// - parameter notification: The notification. @objc private func configWindowWillClose(notification: Notification) { guard let object = notification.object as? NSWindow else { return } - // config window title is "V2rayU" if object.title == "V2rayU" { - _ = showDock(state: false) + showDock(state: false) } } @@ -276,7 +269,6 @@ class MenuController: NSObject, NSMenuDelegate { V2rayLaunch.restartV2ray() } - // MARK: - actions @IBAction func switchGlobalMode(_ sender: NSMenuItem) { UserDefaults.set(forKey: .runMode, value: RunMode.global.rawValue) V2rayLaunch.restartV2ray() diff --git a/V2rayU/Ping.swift b/V2rayU/Ping.swift index 29bc780..59de2a6 100644 --- a/V2rayU/Ping.swift +++ b/V2rayU/Ping.swift @@ -18,7 +18,7 @@ let second: Double = 1000000 let pingURL = URL(string: "http://www.gstatic.com/generate_204")! class PingSpeed: NSObject { - let semaphore = DispatchSemaphore(value: 30) // work pool + let maxConcurrentTasks = 30 func pingAll() { NSLog("ping start") @@ -33,7 +33,7 @@ class PingSpeed: NSObject { inPing = true killAllPing() - + let itemList = V2rayServer.all() if itemList.count == 0 { NSLog("no items") @@ -47,9 +47,7 @@ class PingSpeed: NSObject { } else { pingTip = "Ping Speed - 测试中" } - DispatchQueue.main.async { - menuController.setStatusMenuTip(pingTip: pingTip) - } + menuController.setStatusMenuTip(pingTip: pingTip) Task { do { try await pingTaskGroup(items: itemList) @@ -59,29 +57,36 @@ class PingSpeed: NSObject { } } - func pingTaskGroup(items: [V2rayItem]) async throws { - await withThrowingTaskGroup(of: Int.self) { group in - for item in items { - group.addTask { - do { - await self.semaphore.wait() - defer { - self.semaphore.signal() + func pingTaskGroup(items: [V2rayItem]) async throws { + let taskChunks = stride(from: 0, to: items.count, by: maxConcurrentTasks).map { + Array(items[$0.. String { if !userRuleTxt.contains("api.github.com") { userRuleTxt.append("\n||api.github.com") } - if !userRuleTxt.contains("api.github.com") { - userRuleTxt.append("\n||api.github.com") - } if !userRuleTxt.contains("openai.com") { userRuleTxt.append("\n||openai.com") } diff --git a/V2rayU/Preference/PreferenceSubscription.swift b/V2rayU/Preference/PreferenceSubscription.swift index 17aff10..b16e199 100644 --- a/V2rayU/Preference/PreferenceSubscription.swift +++ b/V2rayU/Preference/PreferenceSubscription.swift @@ -41,12 +41,12 @@ final class PreferenceSubscribeViewController: NSViewController, PreferencePane, self.logView.isHidden = true self.subscribeView.isHidden = false self.logArea.string = "" + // reload tableview + V2raySubscription.loadConfig() // table view self.tableView.delegate = self self.tableView.dataSource = self self.tableView.reloadData() - // reload tableview - V2raySubscription.loadConfig() // set global hotkey NotificationCenter.default.addObserver(self, selector: #selector(self.updateTip), name: NOTIFY_UPDATE_SubSync, object: nil) @@ -105,6 +105,7 @@ final class PreferenceSubscribeViewController: NSViewController, PreferencePane, self.url.stringValue = "" // reload tableview + V2raySubscription.loadConfig() self.tableView.reloadData() } @@ -130,6 +131,7 @@ final class PreferenceSubscribeViewController: NSViewController, PreferencePane, } // reload tableview + V2raySubscription.loadConfig() self.tableView.reloadData() // fix @@ -138,12 +140,7 @@ final class PreferenceSubscribeViewController: NSViewController, PreferencePane, self.tableView.selectRowIndexes(NSIndexSet(index: rowIndex) as IndexSet, byExtendingSelection: true) } - do { - // refresh server - DispatchQueue.main.async { - menuController.showServers() - } - } + menuController.showServers() } } diff --git a/V2rayU/ToastWindow.swift b/V2rayU/ToastWindow.swift index f08ed43..ea9a4a1 100644 --- a/V2rayU/ToastWindow.swift +++ b/V2rayU/ToastWindow.swift @@ -11,22 +11,33 @@ import Cocoa var toastWindow = ToastWindowController() func makeToast(message: String, displayDuration: Double? = 3) { - print("makeToast", message) - toastWindow.close() - toastWindow = ToastWindowController() - toastWindow.message = message - toastWindow.showWindow(Any.self) - toastWindow.fadeInHud(displayDuration) - - NSApp.activate(ignoringOtherApps: true) + // UI 更新需要在主线程上执行 + DispatchQueue.main.async { + print("makeToast", message) + toastWindow.close() + toastWindow = ToastWindowController() + toastWindow.message = message + toastWindow.showWindow(Any.self) + toastWindow.fadeInHud(displayDuration) + + NSApp.activate(ignoringOtherApps: true) + } } -func alertDialog(title: String, message: String) -> Bool { - let myPopup = NSAlert() - myPopup.messageText = title - myPopup.informativeText = message - myPopup.alertStyle = .warning - return myPopup.runModal() == NSApplication.ModalResponse.alertFirstButtonReturn +func alertDialog(title: String, message: String) { + DispatchQueue.main.async { + let alert = NSAlert() + alert.messageText = title + alert.informativeText = message + alert.alertStyle = .warning + let response = alert.runModal() + + if response == .alertFirstButtonReturn { + print("OK clicked") + } else { + print("Cancel clicked") + } + } } class ToastWindowController: NSWindowController { diff --git a/V2rayU/Util.swift b/V2rayU/Util.swift index 1782ff5..91234a9 100644 --- a/V2rayU/Util.swift +++ b/V2rayU/Util.swift @@ -568,20 +568,20 @@ func ClearLogs() { try! txt.write(to: URL.init(fileURLWithPath: logFilePath), atomically: true, encoding: String.Encoding.utf8) } -func showDock(state: Bool) -> Bool { - // Get transform state. - var transformState: ProcessApplicationTransformState - if state { - transformState = ProcessApplicationTransformState(kProcessTransformToForegroundApplication) - } else { - transformState = ProcessApplicationTransformState(kProcessTransformToUIElementApplication) - } - - // Show / hide dock icon. - var psn = ProcessSerialNumber(highLongOfPSN: 0, lowLongOfPSN: UInt32(kCurrentProcess)) - let transformStatus: OSStatus = TransformProcessType(&psn, transformState) +func showDock(state: Bool) { + DispatchQueue.main.async { + // Get transform state. + var transformState: ProcessApplicationTransformState + if state { + transformState = ProcessApplicationTransformState(kProcessTransformToForegroundApplication) + } else { + transformState = ProcessApplicationTransformState(kProcessTransformToUIElementApplication) + } - return transformStatus == 0 + // Show / hide dock icon. + var psn = ProcessSerialNumber(highLongOfPSN: 0, lowLongOfPSN: UInt32(kCurrentProcess)) + TransformProcessType(&psn, transformState) + } } func noticeTip(title: String = "", informativeText: String = "") { diff --git a/V2rayU/V2rayLaunch.swift b/V2rayU/V2rayLaunch.swift index e5e3d3c..6197927 100644 --- a/V2rayU/V2rayLaunch.swift +++ b/V2rayU/V2rayLaunch.swift @@ -175,12 +175,10 @@ class V2rayLaunch: NSObject { // start and show servers self.startV2rayCore() } else { - DispatchQueue.main.async { - // show off status - menuController.setStatusOff() - // show servers - menuController.showServers() - } + // show off status + menuController.setStatusOff() + // show servers + menuController.showServers() } // auto update subscribe servers @@ -244,9 +242,8 @@ class V2rayLaunch: NSObject { self.setRunMode(mode: runMode) // reload menu - DispatchQueue.main.async { - menuController.showServers() - } + menuController.showServers() + // ping current PingCurrent(item: v2ray).doPing() } @@ -256,12 +253,10 @@ class V2rayLaunch: NSObject { V2rayLaunch.Stop() // off system proxy V2rayLaunch.setSystemProxy(mode: .off) - DispatchQueue.main.async { - // set status - menuController.setStatusOff() - // reload menu - menuController.showServers() - } + // set status + menuController.setStatusOff() + // reload menu + menuController.showServers() } static func Start() -> Bool { @@ -279,8 +274,10 @@ class V2rayLaunch: NSObject { toast = "http port \(httpPort) has been used, please replace it from advance setting" title = "Port is already in use" } - _ = alertDialog(title: title, message: toast) - preferencesWindowController.show(preferencePane: .advanceTab) + alertDialog(title: title, message: toast) + DispatchQueue.main.async { + preferencesWindowController.show(preferencePane: .advanceTab) + } return false } @@ -292,8 +289,10 @@ class V2rayLaunch: NSObject { toast = "socks port \(sockPort) has been used, please replace it from advance setting" title = "Port is already in use" } - _ = alertDialog(title: title, message: toast) - preferencesWindowController.show(preferencePane: .advanceTab) + alertDialog(title: title, message: toast) + DispatchQueue.main.async { + preferencesWindowController.show(preferencePane: .advanceTab) + } return false } @@ -384,8 +383,10 @@ class V2rayLaunch: NSObject { toast = "pac port \(pacPort) has been used, please replace from advance setting" title = "Port is already in use" } - _ = alertDialog(title: title, message: toast) - preferencesWindowController.show(preferencePane: .advanceTab) + alertDialog(title: title, message: toast) + DispatchQueue.main.async { + preferencesWindowController.show(preferencePane: .advanceTab) + } return } @@ -468,11 +469,13 @@ func checkV2rayUVersion() { let curVer = newVer.replacingOccurrences(of: "v", with: "").versionToInt() // compare with [Int] - if oldVer.lexicographicallyPrecedes(curVer) { - menuController.newVersionItem.isHidden = false - menuController.newVersionItem.title = "has new version " + newVer - } else { - menuController.newVersionItem.isHidden = true + DispatchQueue.main.async { + if oldVer.lexicographicallyPrecedes(curVer) { + menuController.newVersionItem.isHidden = false + menuController.newVersionItem.title = "has new version " + newVer + } else { + menuController.newVersionItem.isHidden = true + } } } } diff --git a/V2rayU/V2raySubscription.swift b/V2rayU/V2raySubscription.swift index 5264acb..fba04ce 100644 --- a/V2rayU/V2raySubscription.swift +++ b/V2rayU/V2raySubscription.swift @@ -236,7 +236,7 @@ let NOTIFY_UPDATE_SubSync = Notification.Name(rawValue: "NOTIFY_UPDATE_SubSync") class V2raySubSync: NSObject { var V2raySubSyncing = false - let semaphore = DispatchSemaphore(value: 2) // work pool + let maxConcurrentTasks = 1 // work pool static var shared = V2raySubSync() // Initialization @@ -253,8 +253,6 @@ class V2raySubSync: NSObject { } self.V2raySubSyncing = true NSLog("V2raySubSync start") - - V2raySubscription.loadConfig() let list = V2raySubscription.list() @@ -272,23 +270,29 @@ class V2raySubSync: NSObject { } func syncTaskGroup(items: [V2raySubItem]) async throws { - await withThrowingTaskGroup(of: Int.self) { group in - for item in items { - group.addTask { - do { - await self.semaphore.wait() - defer { - self.semaphore.signal() + let taskChunks = stride(from: 0, to: items.count, by: maxConcurrentTasks).map { + Array(items[$0.. 50 { - break // limit 50 - } // import every server if (uri.count > 0) { let filterUri = uri.trimmingCharacters(in: .whitespacesAndNewlines)