Update On Fri Jun 21 20:32:08 CEST 2024

This commit is contained in:
github-action[bot]
2024-06-21 20:32:08 +02:00
parent 454ce63920
commit 54823d03fb
111 changed files with 2003 additions and 1157 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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">

View File

@@ -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
}
}

View File

@@ -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()

View File

@@ -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()
}
}

View File

@@ -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")
}

View File

@@ -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()
}
}

View File

@@ -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 {

View File

@@ -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 = "") {

View File

@@ -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
}
}
}
}

View File

@@ -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)