181 lines
7.1 KiB
Swift
181 lines
7.1 KiB
Swift
import ApplicationLibrary
|
|
import Libbox
|
|
import Library
|
|
import NetworkExtension
|
|
import SwiftUI
|
|
|
|
public struct MacApplication: Scene {
|
|
@State private var isInitialized = false
|
|
@State private var showMenuBarExtra = false
|
|
@State private var menuBarExtraSpeedMode = MenuBarExtraSpeedMode.enabled.rawValue
|
|
@StateObject private var environments = ExtensionEnvironments()
|
|
@StateObject private var updateManager = UpdateManager()
|
|
@State private var statusBarController: StatusBarController?
|
|
@State private var showUpdateCheckPrompt = false
|
|
|
|
private let profileEditor: (Binding<String>, Bool) -> AnyView = { text, isEditable in
|
|
AnyView(ProfileEditorWrapperView(text: text, isEditable: isEditable))
|
|
}
|
|
|
|
public init() {}
|
|
public var body: some Scene {
|
|
Window("sing-box", id: "main", content: {
|
|
MainView()
|
|
.onAppear {
|
|
Task {
|
|
await initialize()
|
|
}
|
|
}
|
|
.environment(\.showMenuBarExtra, $showMenuBarExtra)
|
|
.environment(\.menuBarExtraSpeedMode, $menuBarExtraSpeedMode)
|
|
.environmentObject(environments)
|
|
.environmentObject(updateManager)
|
|
.alert(
|
|
"Check Update",
|
|
isPresented: $showUpdateCheckPrompt
|
|
) {
|
|
Button("Ok") {
|
|
Task {
|
|
await SharedPreferences.updateCheckPrompted.set(true)
|
|
await SharedPreferences.checkUpdateEnabled.set(true)
|
|
await runAutomaticUpdateCheck()
|
|
}
|
|
}
|
|
Button("No, thanks", role: .cancel) {
|
|
Task {
|
|
await SharedPreferences.updateCheckPrompted.set(true)
|
|
}
|
|
}
|
|
} message: {
|
|
Text("Would you like to enable automatic update checking from **GitHub**?")
|
|
}
|
|
.sheet(isPresented: $updateManager.isUpdateSheetPresented, onDismiss: {
|
|
updateManager.dismissUpdateSheet()
|
|
}) {
|
|
UpdateSheet(updateManager: updateManager)
|
|
.environmentObject(environments)
|
|
}
|
|
.onChangeCompat(of: showMenuBarExtra) { newValue in
|
|
statusBarController?.updateVisibility(newValue)
|
|
Task {
|
|
await SharedPreferences.showMenuBarExtra.set(newValue)
|
|
}
|
|
}
|
|
.onChangeCompat(of: menuBarExtraSpeedMode) { newValue in
|
|
statusBarController?.updateSpeedMode(newValue)
|
|
Task {
|
|
await SharedPreferences.menuBarExtraSpeedMode.set(newValue)
|
|
}
|
|
}
|
|
})
|
|
.windowResizability(.contentSize)
|
|
.commands {
|
|
if showMenuBarExtra {
|
|
CommandGroup(replacing: .appTermination) {
|
|
Button("Quit sing-box") {
|
|
hide(closeApp: true)
|
|
}
|
|
.keyboardShortcut("q", modifiers: [.command])
|
|
}
|
|
CommandGroup(replacing: .saveItem) {
|
|
Button("Close") {
|
|
hide(closeApp: false)
|
|
}
|
|
.keyboardShortcut("w", modifiers: [.command])
|
|
}
|
|
}
|
|
SidebarCommands()
|
|
CommandGroup(replacing: .appSettings) {
|
|
Button("Settings") {
|
|
environments.openSettings.send()
|
|
}
|
|
.keyboardShortcut(",", modifiers: [.command])
|
|
}
|
|
}
|
|
|
|
WindowGroup(for: EditProfileContentView.Context.self) { $context in
|
|
EditProfileContentWindow(context: context)
|
|
.environment(\.profileEditor, profileEditor)
|
|
}
|
|
.windowResizability(.contentMinSize)
|
|
.defaultSize(width: 700, height: 500)
|
|
}
|
|
|
|
private func initialize() async {
|
|
guard !isInitialized else { return }
|
|
isInitialized = true
|
|
showMenuBarExtra = await SharedPreferences.showMenuBarExtra.get()
|
|
menuBarExtraSpeedMode = await SharedPreferences.menuBarExtraSpeedMode.get()
|
|
statusBarController = StatusBarController(environments: environments)
|
|
statusBarController?.updateVisibility(showMenuBarExtra)
|
|
statusBarController?.updateSpeedMode(menuBarExtraSpeedMode)
|
|
|
|
if Variant.useSystemExtension {
|
|
let shouldPresentCachedUpdate = await updateManager.loadCachedUpdate()
|
|
let checkUpdateEnabled = await SharedPreferences.checkUpdateEnabled.get()
|
|
let prompted = await SharedPreferences.updateCheckPrompted.get()
|
|
if !prompted {
|
|
showUpdateCheckPrompt = true
|
|
} else if checkUpdateEnabled {
|
|
if shouldPresentCachedUpdate {
|
|
await presentUpdateSheet()
|
|
}
|
|
Task {
|
|
await runAutomaticUpdateCheck()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private func runAutomaticUpdateCheck() async {
|
|
let shouldPresent = await updateManager.checkForUpdate(presentIfFound: true, showsAlertOnFailure: false)
|
|
if shouldPresent {
|
|
await presentUpdateSheet()
|
|
}
|
|
}
|
|
|
|
private func presentUpdateSheet() async {
|
|
guard updateManager.updateInfo != nil else { return }
|
|
await openMainWindowIfNeeded()
|
|
await updateManager.showUpdateSheet()
|
|
}
|
|
|
|
private func openMainWindowIfNeeded() async {
|
|
let mainWindow = NSApp.windows.first(where: { $0.identifier?.rawValue == "main" })
|
|
let shouldActivate = NSApp.activationPolicy() == .accessory || !(mainWindow?.isVisible ?? false) || !NSApp.isActive
|
|
guard shouldActivate else { return }
|
|
|
|
NSApp.setActivationPolicy(.regular)
|
|
mainWindow?.makeKeyAndOrderFront(nil)
|
|
if let dockApp = NSRunningApplication.runningApplications(withBundleIdentifier: "com.apple.dock").first {
|
|
dockApp.activate()
|
|
try? await Task.sleep(for: .milliseconds(100))
|
|
}
|
|
NSApp.activate(ignoringOtherApps: true)
|
|
}
|
|
|
|
private func hide(closeApp: Bool) {
|
|
Task {
|
|
if await SharedPreferences.menuBarExtraInBackground.get() {
|
|
hide0(closeApp: closeApp)
|
|
} else {
|
|
if closeApp {
|
|
NSApp.terminate(nil)
|
|
} else {
|
|
NSApp.keyWindow?.close()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private func hide0(closeApp: Bool) {
|
|
if closeApp || NSApp.keyWindow?.identifier?.rawValue == "main" {
|
|
let transformState = ProcessApplicationTransformState(kProcessTransformToUIElementApplication)
|
|
var psn = ProcessSerialNumber(highLongOfPSN: 0, lowLongOfPSN: UInt32(kCurrentProcess))
|
|
TransformProcessType(&psn, transformState)
|
|
NSApp.setActivationPolicy(.accessory)
|
|
}
|
|
NSApp.keyWindow?.close()
|
|
}
|
|
}
|