Init commit

This commit is contained in:
世界
2023-07-15 15:03:45 +08:00
commit f441b89efb
122 changed files with 7355 additions and 0 deletions
+80
View File
@@ -0,0 +1,80 @@
import ApplicationLibrary
import Library
import SwiftUI
@main
struct Application: App {
@NSApplicationDelegateAdaptor private var appDelegate: ApplicationDelegate
@State private var showMenuBarExtra = false
@State private var isMenuPresented = false
var body: some Scene {
Window("sing-box", id: "main", content: {
MainView()
.onAppear {
Task.detached {
await initialize()
}
}
.environment(\.showMenuBarExtra, $showMenuBarExtra)
})
.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()
}
Window("New Profile", id: NewProfileView.windowID) {
NewProfileView()
}
WindowGroup("Edit Profile", id: EditProfileWindowView.windowID, for: Int64.self) { profileID in
EditProfileWindowView(profileID.wrappedValue)
}.commandsRemoved()
WindowGroup("Edit Content", id: EditProfileContentView.windowID, for: EditProfileContentView.Context.self) { context in
EditProfileContentView(context.wrappedValue)
}.commandsRemoved()
Window("Service Log", id: ServiceLogView.windowID) {
ServiceLogView()
}
MenuBarExtra(isInserted: $showMenuBarExtra) {
MenuView(isMenuPresented: $isMenuPresented)
} label: {
Image(systemName: "network.badge.shield.half.filled")
}
.menuBarExtraStyle(.window)
.menuBarExtraAccess(isPresented: $isMenuPresented)
}
private func initialize() async {
let initialShowMenuBarExtra = SharedPreferences.showMenuBarExtra
await MainActor.run {
showMenuBarExtra = initialShowMenuBarExtra
}
}
private func hide(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()
}
}
+51
View File
@@ -0,0 +1,51 @@
import AppKit
import ApplicationLibrary
import Foundation
import Libbox
import Library
class ApplicationDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_: Notification) {
NSLog("Here I stand")
// ServiceNotification.register() // Not work
let event = NSAppleEventManager.shared().currentAppleEvent
let launchedAsLogInItem =
event?.eventID == kAEOpenApplication &&
event?.paramDescriptor(forKeyword: keyAEPropData)?.enumCodeValue == keyAELaunchedAsLogInItem
if !launchedAsLogInItem || !SharedPreferences.showMenuBarExtra {
NSApp.setActivationPolicy(.regular)
NSApp.activate(ignoringOtherApps: true)
} else {
NSApp.windows.first?.close()
}
Task.detached {
do {
try await self.postStart(launchedAsLogInItem)
} catch {
NSLog("application setup error: \(error.localizedDescription)")
}
}
}
private func postStart(_ launchedAsLogInItem: Bool) async throws {
try ProfileUpdateTask.setup()
if launchedAsLogInItem {
if SharedPreferences.startedByUser {
if let profile = try await ExtensionProfile.load() {
try await profile.start()
}
}
}
}
func applicationShouldTerminateAfterLastWindowClosed(_: NSApplication) -> Bool {
!SharedPreferences.showMenuBarExtra
}
func applicationShouldHandleReopen(_: NSApplication, hasVisibleWindows flag: Bool) -> Bool {
if !flag, NSApp.activationPolicy() == .accessory {
NSApp.setActivationPolicy(.regular)
}
return true
}
}
@@ -0,0 +1,11 @@
{
"colors": [
{
"idiom": "universal"
}
],
"info": {
"author": "xcode",
"version": 1
}
}
@@ -0,0 +1,68 @@
{
"images" : [
{
"filename" : "apple-16 1x.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "16x16"
},
{
"filename" : "apple-16 2x.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "16x16"
},
{
"filename" : "apple-32 1x.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "32x32"
},
{
"filename" : "apple-32 2x 1.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "32x32"
},
{
"filename" : "apple-128 1x.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "128x128"
},
{
"filename" : "apple-128 2x.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "128x128"
},
{
"filename" : "apple-256 1x.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "256x256"
},
{
"filename" : "apple-256 2x.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "256x256"
},
{
"filename" : "apple-512 1x.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "512x512"
},
{
"filename" : "apple-512 2x.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "512x512"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 278 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 286 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 273 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 273 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 286 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 306 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 272 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 274 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 306 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 340 KiB

+6
View File
@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
+20
View File
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>NSUbiquitousContainers</key>
<dict>
<key>iCloud.io.nekohasekai.sfa</key>
<dict>
<key>NSUbiquitousContainerIsDocumentScopePublic</key>
<true/>
<key>NSUbiquitousContainerName</key>
<string>sing-box</string>
<key>NSUbiquitousContainerSupportedFolderLevels</key>
<string>Any</string>
</dict>
</dict>
</dict>
</plist>
+101
View File
@@ -0,0 +1,101 @@
import ApplicationLibrary
import Library
import SwiftUI
struct MainView: View {
@Environment(\.controlActiveState) private var controlActiveState
@State private var selection = NavigationPage.dashboard
@State private var extensionProfile: ExtensionProfile?
@State private var profileLoading = true
@State private var logClient: LogClient!
@State private var serviceNotificationTitle = ""
@State private var serviceNotificationContent = ""
@State private var serviceNotificationPresented = false
var body: some View {
NavigationSplitView {
VStack {
SidebarView()
}
.frame(minWidth: 150)
} detail: {
if profileLoading {
ProgressView().onAppear {
Task {
logClient = LogClient(SharedPreferences.maxLogLines)
await loadProfile()
}
}
} else {
selection.contentView
}
}
.alert(isPresented: $serviceNotificationPresented, content: {
Alert(
title: Text(serviceNotificationTitle),
message: Text(serviceNotificationContent),
dismissButton: .default(Text("Ok"))
)
})
.onAppear {
ServiceNotification.setServiceNotificationListener { notification in
serviceNotificationTitle = notification.title
serviceNotificationContent = notification.body
serviceNotificationPresented = true
}
}
.onDisappear {
ServiceNotification.removeServiceNotificationListener()
}
.toolbar {
ToolbarItem(placement: .navigation) {
StartStopButton()
}
}
.onChange(of: controlActiveState, perform: { newValue in
if newValue != .inactive {
Task {
await loadProfile()
connectLog()
}
}
})
.onChange(of: selection, perform: { value in
if value == .logs {
connectLog()
}
})
.formStyle(.grouped)
.environment(\.selection, $selection)
.environment(\.extensionProfile, $extensionProfile)
.environment(\.logClient, $logClient)
}
private func loadProfile() async {
defer {
profileLoading = false
}
if let newProfile = try? await ExtensionProfile.load() {
if extensionProfile == nil {
newProfile.register()
extensionProfile = newProfile
}
} else {
extensionProfile = nil
}
}
private func connectLog() {
guard let profile = extensionProfile else {
return
}
guard let logClient else {
return
}
if profile.status.isConnected, !logClient.isConnected {
logClient.reconnect()
}
}
}
+210
View File
@@ -0,0 +1,210 @@
import ApplicationLibrary
import Foundation
import Libbox
import Library
import MacControlCenterUI
import MenuBarExtraAccess
import SwiftUI
struct MenuView: View {
@Environment(\.openWindow) private var openWindow
private static let sliderWidth: CGFloat = 270
@Binding var isMenuPresented: Bool
@State private var isLoading = true
@State private var profile: ExtensionProfile?
var body: some View {
MacControlCenterMenu(isPresented: $isMenuPresented) {
MenuHeader("sing-box") {
if isLoading {
Text("Loading...").foregroundColor(.secondary).onAppear {
Task.detached {
await loadProfile()
}
}
} else if let profile {
Text(LibboxVersion()).foregroundColor(.secondary)
StatusSwitch(profile)
} else {
Text("NetworkExtension not installed")
}
}
.frame(minWidth: MenuView.sliderWidth)
if let profile {
ProfilePicker(profile)
}
Divider()
MenuCommand {
NSApp.setActivationPolicy(.regular)
openWindow(id: "main")
} label: {
Text("Open")
}
MenuCommand {
NSApp.terminate(nil)
} label: {
Text("Exit")
}
}
}
private func loadProfile() async {
profile = try? await ExtensionProfile.load()
if let profile {
profile.register()
}
isLoading = false
}
private struct StatusSwitch: View {
@ObservedObject private var profile: ExtensionProfile
@State private var errorPresented = false
@State private var errorMessage = ""
init(_ profile: ExtensionProfile) {
self.profile = profile
}
var body: some View {
Toggle(isOn: Binding(get: {
profile.status.isConnected
}, set: { _ in
Task.detached {
await switchProfile(!profile.status.isConnected)
}
})) {}
.toggleStyle(.switch)
.disabled(!profile.status.isEnabled)
.alert(isPresented: $errorPresented) {
Alert(
title: Text("Error"),
message: Text(errorMessage),
dismissButton: .default(Text("Ok"))
)
}
}
private func switchProfile(_ isEnabled: Bool) async {
do {
if isEnabled {
try await profile.start()
} else {
profile.stop()
}
} catch {
errorMessage = error.localizedDescription
errorPresented = true
return
}
}
}
private struct ProfilePicker: View {
@ObservedObject private var profile: ExtensionProfile
init(_ profile: ExtensionProfile) {
self.profile = profile
}
@State private var isLoading = true
@State private var profileList: [Profile] = []
@State private var selectedProfileID: Int64!
@State private var reasserting = false
@State private var errorPresented = false
@State private var errorMessage = ""
@State private var observer: Any?
var body: some View {
viewBuilder {
if isLoading {
ProgressView().onAppear {
Task.detached {
await doReload()
}
}
} else {
if profileList.isEmpty {
Text("Empty profiles")
} else {
MenuSection("Profile")
Picker("", selection: $selectedProfileID) {
ForEach(profileList, id: \.id) { profile in
Text(profile.name)
}
}
.pickerStyle(.inline)
.onChange(of: selectedProfileID) { _ in
reasserting = true
Task.detached {
await switchProfile(selectedProfileID!)
}
}
.disabled(!profile.status.isSwitchable || reasserting)
}
}
}
.onAppear {
if observer == nil {
observer = NotificationCenter.default.addObserver(forName: ActiveDashboardView.NotificationUpdateSelectedProfile, object: nil, queue: nil, using: { _ in
doReload()
})
}
}
.onDisappear {
if let observer {
NotificationCenter.default.removeObserver(observer)
}
}
.alert(isPresented: $errorPresented) {
Alert(
title: Text("Error"),
message: Text(errorMessage),
dismissButton: .default(Text("Ok"))
)
}
}
private func doReload() {
defer {
isLoading = false
}
do {
profileList = try ProfileManager.list()
} catch {
errorMessage = error.localizedDescription
errorPresented = true
return
}
if profileList.isEmpty {
return
}
selectedProfileID = SharedPreferences.selectedProfileID
if profileList.filter({ profile in
profile.id == selectedProfileID
})
.isEmpty {
selectedProfileID = profileList[0].id!
SharedPreferences.selectedProfileID = selectedProfileID
}
}
private func switchProfile(_ newProfileID: Int64) {
SharedPreferences.selectedProfileID = newProfileID
NotificationCenter.default.post(name: ActiveDashboardView.NotificationUpdateSelectedProfile, object: nil)
if profile.status.isConnected {
do {
try LibboxNewStandaloneCommandClient(FilePath.sharedDirectory.relativePath)?.serviceReload()
} catch {
errorMessage = error.localizedDescription
errorPresented = true
}
}
reasserting = false
}
}
}
+34
View File
@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.developer.icloud-container-identifiers</key>
<array>
<string>iCloud.io.nekohasekai.sfa</string>
</array>
<key>com.apple.developer.icloud-services</key>
<array>
<string>CloudDocuments</string>
</array>
<key>com.apple.developer.networking.networkextension</key>
<array>
<string>packet-tunnel-provider</string>
</array>
<key>com.apple.developer.ubiquity-container-identifiers</key>
<array>
<string>iCloud.io.nekohasekai.sfa</string>
</array>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.application-groups</key>
<array>
<string>group.io.nekohasekai.sfa</string>
</array>
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.network.server</key>
<true/>
</dict>
</plist>
+47
View File
@@ -0,0 +1,47 @@
import ApplicationLibrary
import Library
import SwiftUI
struct SidebarView: View {
@Environment(\.selection) private var selection
@Environment(\.extensionProfile) private var extensionProfile
var body: some View {
viewBuilder {
if let profile = extensionProfile.wrappedValue {
SidebarView0().environmentObject(profile)
} else {
SidebarView1()
}
}
}
struct SidebarView0: View {
@Environment(\.selection) private var selection
@EnvironmentObject private var extensionProfile: ExtensionProfile
var body: some View {
List(NavigationPage.allCases.filter { it in
it.visible(extensionProfile)
}, selection: selection) { it in
it.label
}.onChange(of: extensionProfile.status) { _ in
if !selection.wrappedValue.visible(extensionProfile) {
selection.wrappedValue = NavigationPage.dashboard
}
}
}
}
struct SidebarView1: View {
@Environment(\.selection) private var selection
var body: some View {
List(NavigationPage.allCases.filter { it in
it.visible(nil)
}, selection: selection) { it in
it.label
}
}
}
}