mirror of
https://github.com/bolucat/Archive.git
synced 2025-12-24 13:28:37 +08:00
Update On Fri Jun 21 20:32:08 CEST 2024
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="21225" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="32700.99.1234" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="21225"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="22690"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
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"
|
||||
DispatchQueue.main.async {
|
||||
|
||||
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
|
||||
}
|
||||
var iconName = "IconOn"
|
||||
|
||||
if let button = statusItem.button {
|
||||
// UI API called on a background thread: -[NSStatusBarButton setImage:]
|
||||
DispatchQueue.main.async {
|
||||
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()
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
try await self.pingEachServer(item: item)
|
||||
} catch let error {
|
||||
NSLog("pingEachServer error: \(error)")
|
||||
}
|
||||
return 1
|
||||
}
|
||||
}
|
||||
func pingTaskGroup(items: [V2rayItem]) async throws {
|
||||
let taskChunks = stride(from: 0, to: items.count, by: maxConcurrentTasks).map {
|
||||
Array(items[$0..<min($0 + maxConcurrentTasks, items.count)])
|
||||
}
|
||||
print("pingTaskGroup end")
|
||||
NSLog("pingTaskGroup-start: taskChunks=\(taskChunks.count)")
|
||||
for (i, chunk) in taskChunks.enumerated() {
|
||||
NSLog("pingTaskGroup-start-\(i): count=\(chunk.count)")
|
||||
try await withThrowingTaskGroup(of: Void.self) { group in
|
||||
for item in chunk {
|
||||
group.addTask {
|
||||
do {
|
||||
try await self.pingEachServer(item: item)
|
||||
} catch {
|
||||
NSLog("pingEachServer error: \(error)")
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 等待当前批次所有任务完成
|
||||
try await group.waitForAll()
|
||||
}
|
||||
NSLog("pingTaskGroup-end-\(i)")
|
||||
}
|
||||
NSLog("pingTaskGroup-end")
|
||||
self.pingEnd()
|
||||
}
|
||||
|
||||
func pingEachServer(item: V2rayItem) async throws {
|
||||
NSLog("ping \(item.name) - \(item.remark)")
|
||||
NSLog("pingEachServer: \(item.name) - \(item.remark)")
|
||||
if !item.isValid {
|
||||
return
|
||||
}
|
||||
@@ -99,11 +104,9 @@ class PingSpeed: NSObject {
|
||||
} else {
|
||||
pingTip = "Ping"
|
||||
}
|
||||
print("pingTaskGroup pingEnd", pingTip)
|
||||
DispatchQueue.main.async {
|
||||
menuController.setStatusMenuTip(pingTip: pingTip)
|
||||
menuController.showServers()
|
||||
}
|
||||
print("pingEnd", pingTip)
|
||||
menuController.setStatusMenuTip(pingTip: pingTip)
|
||||
menuController.showServers()
|
||||
// kill
|
||||
killAllPing()
|
||||
}
|
||||
@@ -132,15 +135,16 @@ class PingServer: NSObject, URLSessionDataDelegate {
|
||||
|
||||
// create v2ray config file
|
||||
createV2rayJsonFileForPing()
|
||||
|
||||
// 创建管道
|
||||
let processPipe = Pipe()
|
||||
// Create a Process instance with async launch
|
||||
// use `/bin/bash -c cmd ...` and need kill subprocess
|
||||
let pingCmd = "cd \(AppHomePath) && ./v2ray-core/v2ray run -config \(_json_file)"
|
||||
NSLog("pingCmd: \(pingCmd)")
|
||||
process.launchPath = "/bin/bash"
|
||||
process.arguments = ["-c", pingCmd]
|
||||
// process.standardError = nil
|
||||
// process.standardOutput = nil
|
||||
process.standardError = processPipe
|
||||
process.standardOutput = processPipe
|
||||
process.terminationHandler = { _process in
|
||||
// 结束子进程
|
||||
if _process.terminationStatus != EXIT_SUCCESS {
|
||||
@@ -264,8 +268,7 @@ class PingCurrent: NSObject, URLSessionDataDelegate {
|
||||
let session = URLSession(configuration: config, delegate: self, delegateQueue: nil)
|
||||
tryPing += 1
|
||||
do {
|
||||
let (data, response) = try await session.data(for: URLRequest(url: pingURL))
|
||||
print("doPing: ", data, response)
|
||||
let (_,_) = try await session.data(for: URLRequest(url: pingURL))
|
||||
} catch let error {
|
||||
// failed to write file – bad permissions, bad filename, missing permissions, or more likely it can't be converted to the encoding
|
||||
NSLog("save json file fail: \(error)")
|
||||
@@ -305,9 +308,7 @@ class PingCurrent: NSObject, URLSessionDataDelegate {
|
||||
}
|
||||
} else {
|
||||
inPingCurrent = false
|
||||
DispatchQueue.main.async {
|
||||
menuController.showServers()
|
||||
}
|
||||
menuController.showServers()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -60,9 +60,9 @@ final class PreferencePacViewController: NSViewController, PreferencePane {
|
||||
// save user rules into file
|
||||
print("user-rules", str)
|
||||
try str.write(toFile: PACUserRuleFilePath, atomically: true, encoding: .utf8)
|
||||
|
||||
|
||||
UpdatePACFromGFWList(gfwPacListUrl: gfwPacListUrl.stringValue)
|
||||
|
||||
|
||||
if GeneratePACFile(rewrite: true) {
|
||||
// Popup a user notification
|
||||
tips.stringValue = "PAC has been updated by User Rules."
|
||||
@@ -90,7 +90,9 @@ final class PreferencePacViewController: NSViewController, PreferencePane {
|
||||
}
|
||||
|
||||
guard let reqUrl = URL(string: gfwPacListUrl) else {
|
||||
self.tips.stringValue = "Failed to download latest GFW List: url is not valid"
|
||||
DispatchQueue.main.async {
|
||||
self.tips.stringValue = "Failed to download latest GFW List: url is not valid"
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -267,9 +269,6 @@ func getPacUserRules() -> 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")
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
func showDock(state: Bool) {
|
||||
DispatchQueue.main.async {
|
||||
// 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))
|
||||
TransformProcessType(&psn, transformState)
|
||||
}
|
||||
|
||||
// Show / hide dock icon.
|
||||
var psn = ProcessSerialNumber(highLongOfPSN: 0, lowLongOfPSN: UInt32(kCurrentProcess))
|
||||
let transformStatus: OSStatus = TransformProcessType(&psn, transformState)
|
||||
|
||||
return transformStatus == 0
|
||||
}
|
||||
|
||||
func noticeTip(title: String = "", informativeText: String = "") {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
try await self.dlFromUrl(url: item.url, subscribe: item.name)
|
||||
} catch let error {
|
||||
NSLog("syncTaskGroup error: \(error)")
|
||||
}
|
||||
return 1
|
||||
}
|
||||
}
|
||||
let taskChunks = stride(from: 0, to: items.count, by: maxConcurrentTasks).map {
|
||||
Array(items[$0..<min($0 + maxConcurrentTasks, items.count)])
|
||||
}
|
||||
print("syncTaskGroup end")
|
||||
NSLog("syncTaskGroup-start: taskChunks=\(taskChunks.count)")
|
||||
for (i, chunk) in taskChunks.enumerated() {
|
||||
NSLog("syncTaskGroup-start-\(i): count=\(chunk.count)")
|
||||
try await withThrowingTaskGroup(of: Void.self) { group in
|
||||
for item in chunk {
|
||||
group.addTask {
|
||||
do {
|
||||
try await self.dlFromUrl(url: item.url, subscribe: item.name)
|
||||
} catch {
|
||||
NSLog("dlFromUrl error: \(error)")
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
// 等待当前批次所有任务完成
|
||||
try await group.waitForAll()
|
||||
}
|
||||
NSLog("syncTaskGroup-end-\(i)")
|
||||
}
|
||||
NSLog("syncTaskGroup-end")
|
||||
self.refreshMenu()
|
||||
}
|
||||
|
||||
@@ -298,10 +302,9 @@ class V2raySubSync: NSObject {
|
||||
usleep(useconds_t(1 * second))
|
||||
do {
|
||||
// refresh server
|
||||
DispatchQueue.main.async {
|
||||
menuController.showServers()
|
||||
}
|
||||
usleep(useconds_t(2 * second))
|
||||
menuController.showServers()
|
||||
// sleep 2
|
||||
sleep(2)
|
||||
// do ping
|
||||
ping.pingAll()
|
||||
}
|
||||
@@ -400,10 +403,6 @@ class V2raySubSync: NSObject {
|
||||
let list = strTmp.trimmingCharacters(in: .newlines).components(separatedBy: CharacterSet.newlines)
|
||||
var count = 0
|
||||
for uri in list {
|
||||
count += 1
|
||||
if count > 50 {
|
||||
break // limit 50
|
||||
}
|
||||
// import every server
|
||||
if (uri.count > 0) {
|
||||
let filterUri = uri.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
|
||||
Reference in New Issue
Block a user