feat: show alert when user try to quit clashx with active window displaying

This commit is contained in:
yicheng 2023-09-05 21:19:08 +08:00
parent 6e40d06df3
commit a8d6f53ca7
16 changed files with 153 additions and 87 deletions

View File

@ -36,7 +36,6 @@
495BFB8821919B9800C8779D /* RemoteConfigManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 495BFB8721919B9800C8779D /* RemoteConfigManager.swift */; };
4960A6DB2136529200B940C9 /* JSBridgeHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4960A6DA2136529200B940C9 /* JSBridgeHandler.swift */; };
496322222AA5D89E00854231 /* UpdateExternalResourceAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 496322212AA5D89E00854231 /* UpdateExternalResourceAction.swift */; };
496322242AA5D91200854231 /* BaseAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 496322232AA5D91200854231 /* BaseAction.swift */; };
4966E9E32118153A00A391FB /* NSUserNotificationCenter+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4966E9E22118153A00A391FB /* NSUserNotificationCenter+Extension.swift */; };
4969E73D2A5E3CB20012E005 /* ConnectionDetailInfoGeneralView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4969E73F2A5E3CB20012E005 /* ConnectionDetailInfoGeneralView.xib */; };
496BDEE021196F1E00C5207F /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 496BDEDF21196F1E00C5207F /* Logger.swift */; };
@ -48,6 +47,7 @@
4981C88B216BAE4A008CC14A /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 4981C88D216BAE4A008CC14A /* Localizable.strings */; };
4982F51F2344A216008804B0 /* Cgo+Convert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4982F51E2344A216008804B0 /* Cgo+Convert.swift */; };
49862FA0218418C600A1D5EC /* ClashRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49862F9F218418C600A1D5EC /* ClashRule.swift */; };
49870ADB2AA75DC7002B106B /* TerminalCleanUpAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49870ADA2AA75DC7002B106B /* TerminalCleanUpAction.swift */; };
4989F98E20D0AE990001E564 /* sampleConfig.yaml in Resources */ = {isa = PBXBuildFile; fileRef = 4989F98D20D0AE990001E564 /* sampleConfig.yaml */; };
498BC2532929CC2A00CA8084 /* SettingTabViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 498BC2522929CC2A00CA8084 /* SettingTabViewController.swift */; };
498BC2552929CCAE00CA8084 /* GeneralSettingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 498BC2542929CCAE00CA8084 /* GeneralSettingViewController.swift */; };
@ -191,7 +191,6 @@
495BFB8721919B9800C8779D /* RemoteConfigManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteConfigManager.swift; sourceTree = "<group>"; };
4960A6DA2136529200B940C9 /* JSBridgeHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSBridgeHandler.swift; sourceTree = "<group>"; };
496322212AA5D89E00854231 /* UpdateExternalResourceAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateExternalResourceAction.swift; sourceTree = "<group>"; };
496322232AA5D91200854231 /* BaseAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseAction.swift; sourceTree = "<group>"; };
4966E9E22118153A00A391FB /* NSUserNotificationCenter+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSUserNotificationCenter+Extension.swift"; sourceTree = "<group>"; };
4969E73E2A5E3CB20012E005 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/ConnectionDetailInfoGeneralView.xib; sourceTree = "<group>"; };
4969E7412A5E3CB80012E005 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/ConnectionDetailInfoGeneralView.strings"; sourceTree = "<group>"; };
@ -209,6 +208,7 @@
4981C88E216BAE4D008CC14A /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = "<group>"; };
4982F51E2344A216008804B0 /* Cgo+Convert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Cgo+Convert.swift"; sourceTree = "<group>"; };
49862F9F218418C600A1D5EC /* ClashRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClashRule.swift; sourceTree = "<group>"; };
49870ADA2AA75DC7002B106B /* TerminalCleanUpAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TerminalCleanUpAction.swift; sourceTree = "<group>"; };
498960722340F21C00AFB7EC /* com.west2online.ClashX.ProxyConfigHelper.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = com.west2online.ClashX.ProxyConfigHelper.entitlements; sourceTree = "<group>"; };
4989F98D20D0AE990001E564 /* sampleConfig.yaml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.yaml; path = sampleConfig.yaml; sourceTree = "<group>"; };
498BC2522929CC2A00CA8084 /* SettingTabViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingTabViewController.swift; sourceTree = "<group>"; };
@ -430,7 +430,7 @@
isa = PBXGroup;
children = (
496322212AA5D89E00854231 /* UpdateExternalResourceAction.swift */,
496322232AA5D91200854231 /* BaseAction.swift */,
49870ADA2AA75DC7002B106B /* TerminalCleanUpAction.swift */,
);
path = Actions;
sourceTree = "<group>";
@ -910,7 +910,6 @@
499A485522ED707300F6C675 /* RemoteConfigViewController.swift in Sources */,
49D6A45229AEEC15006487EF /* StatusItemTool.swift in Sources */,
49CF3B5C20CE8068001EBF94 /* ClashResourceManager.swift in Sources */,
496322242AA5D91200854231 /* BaseAction.swift in Sources */,
4952C3D02117027C004A4FA8 /* ConfigFileManager.swift in Sources */,
49BC061C212931F4005A0FE7 /* AboutViewController.swift in Sources */,
4949D154213242F600EF85E6 /* Paths.swift in Sources */,
@ -951,6 +950,7 @@
492C4871210EF62E004554A0 /* ClashConfig.swift in Sources */,
492C4869210EE6B9004554A0 /* ApiRequest.swift in Sources */,
49CF3B6520CEE06C001EBF94 /* ConfigManager.swift in Sources */,
49870ADB2AA75DC7002B106B /* TerminalCleanUpAction.swift in Sources */,
F9E754D0239CC21F00CEE7CC /* WebPortalManager.swift in Sources */,
495BFB8821919B9800C8779D /* RemoteConfigManager.swift in Sources */,
4982F51F2344A216008804B0 /* Cgo+Convert.swift in Sources */,

View File

@ -1,11 +0,0 @@
//
// BaseAction.swift
// ClashX
//
// Created by yicheng on 2023/9/4.
// Copyright © 2023 west2online. All rights reserved.
//
protocol BaseStaticAction {
static func run()
}

View File

@ -0,0 +1,80 @@
//
// TerminalCleanUpAction.swift
// ClashX
//
// Created by yicheng on 2023/9/5.
// Copyright © 2023 west2online. All rights reserved.
//
import AppKit
import Foundation
import RxSwift
enum TerminalConfirmAction {
static func run() -> NSApplication.TerminateReply {
guard confirmAction() else {
return .terminateCancel
}
let group = DispatchGroup()
var shouldWait = false
if ConfigManager.shared.proxyPortAutoSet && !ConfigManager.shared.isProxySetByOtherVariable.value || NetworkChangeNotifier.isCurrentSystemSetToClash(looser: true) ||
NetworkChangeNotifier.hasInterfaceProxySetToClash() {
Logger.log("ClashX quit need clean proxy setting")
shouldWait = true
group.enter()
SystemProxyManager.shared.disableProxy(forceDisable: ConfigManager.shared.isProxySetByOtherVariable.value) {
group.leave()
}
}
if !shouldWait {
Logger.log("ClashX quit without clean waiting")
return .terminateNow
}
if let statusItem = AppDelegate.shared.statusItem, statusItem.menu != nil {
statusItem.menu = nil
}
AppDelegate.shared.disposeBag = DisposeBag()
DispatchQueue.global(qos: .default).async {
let res = group.wait(timeout: .now() + 5)
switch res {
case .success:
Logger.log("ClashX quit after clean up finish")
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
NSApp.reply(toApplicationShouldTerminate: true)
}
DispatchQueue.global().asyncAfter(deadline: .now() + 1) {
NSApp.reply(toApplicationShouldTerminate: true)
}
case .timedOut:
Logger.log("ClashX quit after clean up timeout")
DispatchQueue.main.async {
NSApp.reply(toApplicationShouldTerminate: true)
}
DispatchQueue.global().asyncAfter(deadline: .now() + 1) {
NSApp.reply(toApplicationShouldTerminate: true)
}
}
}
Logger.log("ClashX quit wait for clean up")
return .terminateLater
}
static func confirmAction() -> Bool {
if NSApp.activationPolicy() == .regular {
let alert = NSAlert()
alert.messageText = NSLocalizedString("Quit ClashX?", comment: "")
alert.informativeText = NSLocalizedString("The active connections will be interrupted.", comment: "")
alert.alertStyle = .informational
alert.addButton(withTitle: NSLocalizedString("Quit", comment: ""))
alert.addButton(withTitle: NSLocalizedString("Cancel", comment: ""))
return alert.runModal() == .alertFirstButtonReturn
}
return true
}
}

View File

@ -7,7 +7,7 @@
//
import Foundation
enum UpdateExternalResourceAction: BaseStaticAction {
enum UpdateExternalResourceAction {
static func run() {
ApiRequest.requestExternalProviderNames { provider in
let group = DispatchGroup()

View File

@ -21,7 +21,7 @@ let statusItemLengthWithSpeed: CGFloat = 72
@main
class AppDelegate: NSObject, NSApplicationDelegate {
var statusItem: NSStatusItem!
private(set) var statusItem: NSStatusItem!
@IBOutlet var checkForUpdateMenuItem: NSMenuItem!
@IBOutlet var statusMenu: NSMenu!
@ -160,54 +160,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
}
func applicationShouldTerminate(_ sender: NSApplication) -> NSApplication.TerminateReply {
let group = DispatchGroup()
var shouldWait = false
if ConfigManager.shared.proxyPortAutoSet && !ConfigManager.shared.isProxySetByOtherVariable.value || NetworkChangeNotifier.isCurrentSystemSetToClash(looser: true) ||
NetworkChangeNotifier.hasInterfaceProxySetToClash() {
Logger.log("ClashX quit need clean proxy setting")
shouldWait = true
group.enter()
SystemProxyManager.shared.disableProxy(forceDisable: ConfigManager.shared.isProxySetByOtherVariable.value) {
group.leave()
}
}
if !shouldWait {
Logger.log("ClashX quit without clean waiting")
return .terminateNow
}
if statusItem != nil, statusItem.menu != nil {
statusItem.menu = nil
}
disposeBag = DisposeBag()
DispatchQueue.global(qos: .default).async {
let res = group.wait(timeout: .now() + 5)
switch res {
case .success:
Logger.log("ClashX quit after clean up finish")
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
NSApp.reply(toApplicationShouldTerminate: true)
}
DispatchQueue.global().asyncAfter(deadline: .now() + 1) {
NSApp.reply(toApplicationShouldTerminate: true)
}
case .timedOut:
Logger.log("ClashX quit after clean up timeout")
DispatchQueue.main.async {
NSApp.reply(toApplicationShouldTerminate: true)
}
DispatchQueue.global().asyncAfter(deadline: .now() + 1) {
NSApp.reply(toApplicationShouldTerminate: true)
}
}
}
Logger.log("ClashX quit wait for clean up")
return .terminateLater
return TerminalConfirmAction.run()
}
func applicationWillTerminate(_ aNotification: Notification) {
@ -758,6 +711,10 @@ extension AppDelegate {
@IBAction func actionQuit(_ sender: Any) {
NSApplication.shared.terminate(self)
}
@IBAction func actionMoreSetting(_ sender: Any) {
ClashWindowController<SettingTabViewController>.create().showWindow(sender)
}
}
// MARK: Streaming Info

View File

@ -9,7 +9,7 @@
<!--Settings-->
<scene sceneID="7zJ-aQ-tNg">
<objects>
<tabViewController title="Settings" showSeguePresentationStyle="single" selectedTabViewItemIndex="0" id="LAj-p8-9gd" customClass="SettingTabViewController" customModule="ClashX" customModuleProvider="target" sceneMemberID="viewController">
<tabViewController title="Settings" storyboardIdentifier="SettingTabViewController" showSeguePresentationStyle="single" selectedTabViewItemIndex="0" id="LAj-p8-9gd" customClass="SettingTabViewController" customModule="ClashX" customModuleProvider="target" sceneMemberID="viewController">
<tabViewItems>
<tabViewItem label="General" identifier="general" image="gearshape" catalog="system" id="Ltt-Vq-Hh1"/>
<tabViewItem image="keyboard" catalog="system" id="gnz-rO-Jv7"/>
@ -529,6 +529,13 @@
<menuItem title="ClashX" id="1Xt-HY-uBw" userLabel="ClashX">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="ClashX" systemMenu="apple" id="uQy-DD-JDr">
<items>
<menuItem title="Quit ClashX" keyEquivalent="q" id="TsN-g8-OjG">
<connections>
<action selector="terminate:" target="Ady-hI-5gd" id="u3Z-Ml-nGa"/>
</connections>
</menuItem>
</items>
<attributedString key="userComments">
<fragment content="#bc-ignore!"/>
</attributedString>
@ -864,8 +871,7 @@
<menuItem title="Settings" id="krh-QF-pqZ">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="actionUpdateConfig:" target="Voe-Tx-rLC" id="L8c-KG-g8D"/>
<segue destination="LAj-p8-9gd" kind="show" id="BTv-JW-0Fb"/>
<action selector="actionMoreSetting:" target="Voe-Tx-rLC" id="PF2-an-kBo"/>
</connections>
</menuItem>
<menuItem title="Help" id="Dd9-2F-FVY">

View File

@ -24,7 +24,8 @@ private class ClashWindowsRecorder {
class ClashWindowController<T: NSViewController>: NSWindowController, NSWindowDelegate {
var onWindowClose: (() -> Void)?
var lastSize: CGSize? {
private var fromCache = false
private var lastSize: CGSize? {
get {
if let str = UserDefaults.standard.value(forKey: "lastSize.\(T.className())") as? String {
return NSSizeFromString(str) as CGSize
@ -40,12 +41,23 @@ class ClashWindowController<T: NSViewController>: NSWindowController, NSWindowDe
static func create() -> NSWindowController {
if let wc = ClashWindowsRecorder.shared.windowControllers.first(where: { $0 is Self }) {
(wc as? ClashWindowController)?.fromCache = true
return wc
}
let win = NSWindow()
let wc = ClashWindowController(window: win)
wc.contentViewController = T()
if let X = T.self as? NibLoadable.Type {
wc.contentViewController = (X.createFromNib(in: .main) as! NSViewController)
} else {
wc.contentViewController = T()
}
win.titlebarAppearsTransparent = false
win.styleMask.insert(.closable)
win.styleMask.insert(.resizable)
win.styleMask.insert(.miniaturizable)
if let title = wc.contentViewController?.title {
win.title = title
}
ClashWindowsRecorder.shared.windowControllers.append(wc)
return wc
}
@ -53,10 +65,10 @@ class ClashWindowController<T: NSViewController>: NSWindowController, NSWindowDe
override func showWindow(_ sender: Any?) {
super.showWindow(sender)
NSApp.activate(ignoringOtherApps: true)
if let lastSize = lastSize, lastSize != .zero {
if !fromCache, let lastSize = lastSize, lastSize != .zero {
window?.setContentSize(lastSize)
window?.center()
}
window?.center()
window?.makeKeyAndOrderFront(self)
window?.delegate = self
}

View File

@ -27,3 +27,15 @@ extension NibLoadable where Self: NSView {
return views.last as! Self
}
}
extension NibLoadable where Self: NSViewController {
static var nibName: String? {
return String(describing: Self.self)
}
static func createFromNib(in bundle: Bundle = Bundle.main) -> Self {
guard let nibName = nibName else { fatalError() }
let sb = NSStoryboard(name: "Main", bundle: Bundle.main)
return sb.instantiateController(withIdentifier: nibName) as! Self
}
}

View File

@ -184,6 +184,9 @@
/* No comment provided by engineer. */
"Quit" = "Quit";
/* No comment provided by engineer. */
"Quit ClashX?" = "Quit ClashX?";
/* No comment provided by engineer. */
"Recent Connections" = "Recent Connections";
@ -256,6 +259,9 @@
/* No comment provided by engineer. */
"Testing" = "Testing";
/* No comment provided by engineer. */
"The active connections will be interrupted." = "The active connections will be interrupted.";
/* No comment provided by engineer. */
"The remote config name is duplicated" = "The remote config name is duplicated";

View File

@ -184,6 +184,9 @@
/* No comment provided by engineer. */
"Quit" = "退出";
/* No comment provided by engineer. */
"Quit ClashX?" = "退出ClashX?";
/* No comment provided by engineer. */
"Recent Connections" = "最近连接";
@ -256,6 +259,9 @@
/* No comment provided by engineer. */
"Testing" = "测速中";
/* No comment provided by engineer. */
"The active connections will be interrupted." = "活动的连接将被强制打断。";
/* No comment provided by engineer. */
"The remote config name is duplicated" = "托管配置文件名称重复";

View File

@ -184,6 +184,9 @@
/* No comment provided by engineer. */
"Quit" = "退出";
/* No comment provided by engineer. */
"Quit ClashX?" = "退出 ClashX?";
/* No comment provided by engineer. */
"Recent Connections" = "最近連線";
@ -256,6 +259,9 @@
/* No comment provided by engineer. */
"Testing" = "測試中";
/* No comment provided by engineer. */
"The active connections will be interrupted." = "活躍的連接將被中斷。";
/* No comment provided by engineer. */
"The remote config name is duplicated" = "遠端配置名稱重複";

View File

@ -33,14 +33,6 @@ class ClashWebViewContoller: NSViewController {
let effectView = NSVisualEffectView()
static func createWindowController() -> NSWindowController {
let sb = NSStoryboard(name: "Main", bundle: Bundle.main)
let vc = sb.instantiateController(withIdentifier: "ClashWebViewContoller") as! ClashWebViewContoller
let wc = NSWindowController(window: NSWindow())
wc.contentViewController = vc
return wc
}
override func loadView() {
view = NSView(frame: NSRect(origin: .zero, size: minSize))
}
@ -86,9 +78,6 @@ class ClashWebViewContoller: NSViewController {
view.window?.isOpaque = false
view.window?.backgroundColor = NSColor.clear
view.window?.styleMask.insert(.closable)
view.window?.styleMask.insert(.resizable)
view.window?.styleMask.insert(.miniaturizable)
view.window?.toolbar = NSToolbar()
view.window?.toolbar?.showsBaselineSeparator = false
view.wantsLayer = true

View File

@ -51,9 +51,6 @@ class DashboardViewController: NSViewController {
super.viewWillAppear()
toolbar.delegate = self
view.window?.toolbar = toolbar
view.window?.styleMask.insert(.closable)
view.window?.styleMask.insert(.resizable)
view.window?.styleMask.insert(.miniaturizable)
view.window?.backgroundColor = NSColor.clear
if #available(macOS 11.0, *) {
view.window?.toolbarStyle = .unifiedCompact

View File

@ -8,7 +8,7 @@
import Cocoa
class SettingTabViewController: NSTabViewController {
class SettingTabViewController: NSTabViewController, NibLoadable {
override func viewDidLoad() {
super.viewDidLoad()
tabStyle = .toolbar

View File

@ -312,3 +312,6 @@
/* Class = "NSButtonCell"; title = "Enable IPv6"; ObjectID = "KRm-2U-T4s"; */
"KRm-2U-T4s.title" = "启用IPv6";
/* Class = "NSMenuItem"; title = "Quit ClashX"; ObjectID = "TsN-g8-OjG"; */
"TsN-g8-OjG.title" = "退出 ClashX";

View File

@ -312,3 +312,6 @@
/* Class = "NSButtonCell"; title = "Enable IPv6"; ObjectID = "KRm-2U-T4s"; */
"KRm-2U-T4s.title" = "啟用IPv6";
/* Class = "NSMenuItem"; title = "Quit ClashX"; ObjectID = "TsN-g8-OjG"; */
"TsN-g8-OjG.title" = "退出 ClashX";