Skip to content

Commit 9a9432a

Browse files
committed
Merge branch 'meta-dev' into meta
2 parents a7e31af + 9ca442b commit 9a9432a

9 files changed

Lines changed: 194 additions & 149 deletions

File tree

ClashX.xcodeproj/project.pbxproj

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@
7474
01D567E62AD158B600CDA0AE /* MetaPrefsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01D567E22AD158B500CDA0AE /* MetaPrefsViewController.swift */; };
7575
01E908FC2FEE786F00C95A3A /* Subprocess in Frameworks */ = {isa = PBXBuildFile; productRef = 01E908FB2FEE786F00C95A3A /* Subprocess */; };
7676
01E908FE2FEE787900C95A3A /* Subprocess in Frameworks */ = {isa = PBXBuildFile; productRef = 01E908FD2FEE787900C95A3A /* Subprocess */; };
77+
01E909002FEF838700C95A3A /* ExitManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01E908FF2FEF838700C95A3A /* ExitManager.swift */; };
7778
01EF33602B98D03B00D1DBD9 /* ProxyConfigHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01EF335F2B98D03B00D1DBD9 /* ProxyConfigHelper.swift */; };
7879
01EF33622B98D3A700D1DBD9 /* ProxyConfigRemoteProcessProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01EF33612B98D3A700D1DBD9 /* ProxyConfigRemoteProcessProtocol.swift */; };
7980
01EF33642B98D71600D1DBD9 /* ProxyConfigRemoteProcessProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01EF33612B98D3A700D1DBD9 /* ProxyConfigRemoteProcessProtocol.swift */; };
@@ -121,7 +122,6 @@
121122
01F335F42AD10D0B0048AF77 /* ClashConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 492C4870210EF62E004554A0 /* ClashConfig.swift */; };
122123
01F335F52AD10D0B0048AF77 /* ApiRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 492C4868210EE6B9004554A0 /* ApiRequest.swift */; };
123124
01F335F62AD10D0B0048AF77 /* ConfigManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49CF3B6420CEE06C001EBF94 /* ConfigManager.swift */; };
124-
01F335F72AD10D0B0048AF77 /* TerminalCleanUpAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49870ADA2AA75DC7002B106B /* TerminalCleanUpAction.swift */; };
125125
01F335F82AD10D0B0048AF77 /* WebPortalManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9E754CF239CC21F00CEE7CC /* WebPortalManager.swift */; };
126126
01F335F92AD10D0B0048AF77 /* DualTitleMenuItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 018F88F8286DD0CB004DD0F7 /* DualTitleMenuItem.swift */; };
127127
01F335F92F7F9E8A00A1B2C3 /* ApiRequestTransport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01F335F82F7F9E8A00A1B2C3 /* ApiRequestTransport.swift */; };
@@ -290,6 +290,7 @@
290290
01D567E22AD158B500CDA0AE /* MetaPrefsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MetaPrefsViewController.swift; sourceTree = "<group>"; };
291291
01E33AB129B5BF4200FD1006 /* NSColor+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSColor+Extension.swift"; sourceTree = "<group>"; };
292292
01E33AB429B5C5E300FD1006 /* menu_icon@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "menu_icon@2x.png"; sourceTree = "<group>"; };
293+
01E908FF2FEF838700C95A3A /* ExitManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExitManager.swift; sourceTree = "<group>"; };
293294
01EF335F2B98D03B00D1DBD9 /* ProxyConfigHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProxyConfigHelper.swift; sourceTree = "<group>"; };
294295
01EF33612B98D3A700D1DBD9 /* ProxyConfigRemoteProcessProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProxyConfigRemoteProcessProtocol.swift; sourceTree = "<group>"; };
295296
01F335D92BD10D0B0048AF77 /* ProxyHealthCheckManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProxyHealthCheckManager.swift; sourceTree = "<group>"; };
@@ -326,7 +327,6 @@
326327
49722FED211F338B00650A41 /* Witness.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Witness.h; sourceTree = "<group>"; };
327328
4982F51E2344A216008804B0 /* Cgo+Convert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Cgo+Convert.swift"; sourceTree = "<group>"; };
328329
49862F9F218418C600A1D5EC /* ClashRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClashRule.swift; sourceTree = "<group>"; };
329-
49870ADA2AA75DC7002B106B /* TerminalCleanUpAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TerminalCleanUpAction.swift; sourceTree = "<group>"; };
330330
49870ADC2AA76602002B106B /* UpdateConfigAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateConfigAction.swift; sourceTree = "<group>"; };
331331
498960722340F21C00AFB7EC /* com.metacubex.ClashX.ProxyConfigHelper.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = com.metacubex.ClashX.ProxyConfigHelper.entitlements; sourceTree = "<group>"; };
332332
4989F98D20D0AE990001E564 /* sampleConfig.yaml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.yaml; path = sampleConfig.yaml; sourceTree = "<group>"; };
@@ -688,8 +688,8 @@
688688
isa = PBXGroup;
689689
children = (
690690
496322212AA5D89E00854231 /* UpdateExternalResourceAction.swift */,
691-
49870ADA2AA75DC7002B106B /* TerminalCleanUpAction.swift */,
692691
49870ADC2AA76602002B106B /* UpdateConfigAction.swift */,
692+
01E908FF2FEF838700C95A3A /* ExitManager.swift */,
693693
);
694694
path = Actions;
695695
sourceTree = "<group>";
@@ -1070,9 +1070,9 @@
10701070
01F335F52AD10D0B0048AF77 /* ApiRequest.swift in Sources */,
10711071
01F335F92F7F9E8A00A1B2C3 /* ApiRequestTransport.swift in Sources */,
10721072
01F335F62AD10D0B0048AF77 /* ConfigManager.swift in Sources */,
1073-
01F335F72AD10D0B0048AF77 /* TerminalCleanUpAction.swift in Sources */,
10741073
01F335F82AD10D0B0048AF77 /* WebPortalManager.swift in Sources */,
10751074
01F335F92AD10D0B0048AF77 /* DualTitleMenuItem.swift in Sources */,
1075+
01E909002FEF838700C95A3A /* ExitManager.swift in Sources */,
10761076
01F335FA2AD10D0B0048AF77 /* RemoteConfigManager.swift in Sources */,
10771077
01F335FB2AD10D0B0048AF77 /* Cgo+Convert.swift in Sources */,
10781078
01F335FC2AD10D0B0048AF77 /* ProxySettingCommand.swift in Sources */,

ClashX/Actions/ExitManager.swift

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
//
2+
// ExitManager.swift
3+
// ClashX
4+
//
5+
// Copyright © 2024 west2online. All rights reserved.
6+
//
7+
8+
import AppKit
9+
import Atomics
10+
import Foundation
11+
import RxSwift
12+
13+
14+
@MainActor
15+
final class ExitManager {
16+
static let shared = ExitManager()
17+
18+
private let normalTimeout: TimeInterval = 5
19+
private let forceTimeout: TimeInterval = 2
20+
21+
private var isTerminating = false
22+
private var forceQuitPending = false
23+
24+
// MARK: - External API
25+
26+
func requestQuit(force: Bool) {
27+
forceQuitPending = force
28+
#warning("Use thread 'global' to ensure 'Task' in 'applicationShouldTerminate' executes correctly. 💩💩💩")
29+
DispatchQueue.global(qos: .userInitiated).async {
30+
NSApp.terminate(nil)
31+
}
32+
}
33+
34+
func handleShouldTerminate() async -> Bool {
35+
guard !isTerminating else {
36+
return true
37+
}
38+
isTerminating = true
39+
40+
let shouldForce = forceQuitPending
41+
forceQuitPending = false
42+
43+
if !shouldForce {
44+
guard await confirmAction() else {
45+
isTerminating = false
46+
return false
47+
}
48+
}
49+
50+
ConfigManager.shared.proxyState.isTunModeEnabled =
51+
ConfigManager.shared.proxyState.isTunModeActive
52+
53+
async let stopTask = stopMetaAndDisableTun(timeout: shouldForce ? forceTimeout : normalTimeout)
54+
55+
removeTempFiles()
56+
57+
guard let forceDisable = forceDisableForProxyCleanup() else {
58+
_ = await stopTask
59+
ConfigManager.shared.restoreSystemProxy = false
60+
Logger.log("ClashX quit without clean waiting")
61+
return true
62+
}
63+
64+
Logger.log("ClashX quit need clean proxy setting")
65+
ConfigManager.shared.restoreSystemProxy = true
66+
prepareForTerminationWait()
67+
68+
_ = await stopTask
69+
Logger.log("ClashX quit wait for clean up")
70+
71+
let finished = await performProxyCleanup(forceDisable: forceDisable, timeout: shouldForce ? forceTimeout : normalTimeout)
72+
if finished {
73+
Logger.log("ClashX quit after clean up finish")
74+
} else {
75+
Logger.log("ClashX quit after clean up timeout")
76+
}
77+
78+
return true
79+
}
80+
81+
@MainActor
82+
func handleWillTerminate() async {
83+
UserDefaults.standard.set(0, forKey: "launch_fail_times")
84+
Logger.log("ClashX will terminate")
85+
ApiRequest.shared.prepareForTermination()
86+
}
87+
88+
// MARK: - Private
89+
90+
@MainActor
91+
private func confirmAction() -> Bool {
92+
if NSApp.activationPolicy() == .regular {
93+
let alert = NSAlert()
94+
alert.messageText = NSLocalizedString("Quit ClashX?", comment: "")
95+
alert.informativeText = NSLocalizedString("The active connections will be interrupted.", comment: "")
96+
alert.alertStyle = .informational
97+
alert.addButton(withTitle: NSLocalizedString("Quit", comment: ""))
98+
alert.addButton(withTitle: NSLocalizedString("Cancel", comment: ""))
99+
return alert.runModal() == .alertFirstButtonReturn
100+
}
101+
return true
102+
}
103+
104+
private func stopMetaAndDisableTun(timeout: TimeInterval) async {
105+
let cancelled = ManagedAtomic<Bool>(false)
106+
await withCheckedContinuation { (continuation: CheckedContinuation<Void, Never>) in
107+
Task {
108+
let deadline = Date().addingTimeInterval(timeout)
109+
while Date() < deadline, !cancelled.load(ordering: .relaxed) {
110+
try? await Task.sleep(for: .milliseconds(100))
111+
}
112+
if !cancelled.load(ordering: .relaxed) {
113+
cancelled.store(true, ordering: .relaxed)
114+
continuation.resume()
115+
}
116+
}
117+
Task {
118+
try? await PrivilegedHelperManager.shared.request(
119+
ProxyConfigHelperMessages.StopMeta())
120+
try? await PrivilegedHelperManager.shared.request(
121+
ProxyConfigHelperMessages.UpdateTun(
122+
state: false, dns: ConfigManager.metaTunDNS))
123+
if !cancelled.load(ordering: .relaxed) {
124+
cancelled.store(true, ordering: .relaxed)
125+
continuation.resume()
126+
}
127+
}
128+
}
129+
}
130+
131+
private func removeTempFiles() {
132+
try? FileManager.default.removeItem(atPath: Paths.tempPath() + "/cacheConfigs")
133+
try? FileManager.default.removeItem(atPath: Paths.localConfigPath(for: kSafeConfigName))
134+
}
135+
136+
private func forceDisableForProxyCleanup() -> Bool? {
137+
let shouldCleanSystemProxy =
138+
(ConfigManager.shared.proxyState.isSystemProxyEnabled &&
139+
!ConfigManager.shared.proxyState.isSystemProxySetByOther) ||
140+
NetworkChangeNotifier.isCurrentSystemSetToClash(looser: true) ||
141+
NetworkChangeNotifier.hasInterfaceProxySetToClash()
142+
143+
guard shouldCleanSystemProxy else { return nil }
144+
return ConfigManager.shared.proxyState.isSystemProxySetByOther
145+
}
146+
147+
private func prepareForTerminationWait() {
148+
if let statusItem = AppDelegate.shared.statusItem, statusItem.menu != nil {
149+
statusItem.menu = nil
150+
}
151+
AppDelegate.shared.disposeBag = DisposeBag()
152+
}
153+
154+
private func performProxyCleanup(forceDisable: Bool, timeout: TimeInterval) async -> Bool {
155+
await withTaskGroup(of: Bool.self, returning: Bool.self) { group in
156+
group.addTask {
157+
await SystemProxyManager.shared.disableProxy(forceDisable: forceDisable)
158+
return true
159+
}
160+
group.addTask {
161+
try? await Task.sleep(seconds: timeout)
162+
return false
163+
}
164+
let result = await group.next() ?? false
165+
group.cancelAll()
166+
return result
167+
}
168+
}
169+
}

ClashX/Actions/TerminalCleanUpAction.swift

Lines changed: 0 additions & 125 deletions
This file was deleted.

0 commit comments

Comments
 (0)