diff --git a/Passepartout/App/AppDelegate.swift b/Passepartout/App/AppDelegate.swift
index 0c33e2df..5bcfe050 100644
--- a/Passepartout/App/AppDelegate.swift
+++ b/Passepartout/App/AppDelegate.swift
@@ -23,6 +23,9 @@
// along with Passepartout. If not, see .
//
+#if !os(tvOS)
+import AppUIMain
+#endif
import CommonLibrary
import PassepartoutKit
import SwiftUI
@@ -33,6 +36,14 @@ final class AppDelegate: NSObject {
let context: AppContext = .shared
// let context: AppContext = .mock(withRegistry: .shared)
+#if os(macOS)
+ let settings = MacSettingsModel(
+ defaults: .standard,
+ appWindow: .shared,
+ loginItemId: BundleConfiguration.mainString(for: .loginItemId)
+ )
+#endif
+
func configure(with uiConfiguring: UILibraryConfiguring) {
UILibrary(uiConfiguring)
.configure(with: context)
diff --git a/Passepartout/App/PassepartoutApp.swift b/Passepartout/App/PassepartoutApp.swift
index 40c2f6d5..51a43cf9 100644
--- a/Passepartout/App/PassepartoutApp.swift
+++ b/Passepartout/App/PassepartoutApp.swift
@@ -64,6 +64,12 @@ extension PassepartoutApp {
appDelegate.context
}
+#if os(macOS)
+ var settings: MacSettingsModel {
+ appDelegate.settings
+ }
+#endif
+
func contentView() -> some View {
AppCoordinator(
profileManager: context.profileManager,
diff --git a/Passepartout/App/Platforms/App+macOS.swift b/Passepartout/App/Platforms/App+macOS.swift
index 3df7b9ec..3ddbf090 100644
--- a/Passepartout/App/Platforms/App+macOS.swift
+++ b/Passepartout/App/Platforms/App+macOS.swift
@@ -35,14 +35,14 @@ extension AppDelegate: NSApplicationDelegate {
func applicationDidFinishLaunching(_ notification: Notification) {
configure(with: AppUIMain())
context.onApplicationActive()
- if isStartedFromLoginItem {
+ if settings.isStartedFromLoginItem {
AppWindow.shared.isVisible = false
}
}
func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
AppWindow.shared.isVisible = false
- return !keepsInMenu
+ return !settings.keepsInMenu
}
func application(_ application: NSApplication, open urls: [URL]) {
@@ -50,21 +50,6 @@ extension AppDelegate: NSApplicationDelegate {
}
}
-private extension AppDelegate {
- var keepsInMenu: Bool {
- get {
- UserDefaults.standard.bool(forKey: AppPreference.keepsInMenu.key)
- }
- set {
- UserDefaults.standard.set(newValue, forKey: AppPreference.keepsInMenu.key)
- }
- }
-
- var isStartedFromLoginItem: Bool {
- NSApp.isHidden
- }
-}
-
extension PassepartoutApp {
@SceneBuilder
@@ -82,6 +67,7 @@ extension PassepartoutApp {
SettingsView(profileManager: context.profileManager)
.frame(minWidth: 300, minHeight: 300)
.withEnvironment(from: context, theme: theme)
+ .environmentObject(settings)
}
MenuBarExtra {
AppMenu(
@@ -89,6 +75,7 @@ extension PassepartoutApp {
tunnel: context.tunnel
)
.withEnvironment(from: context, theme: theme)
+ .environmentObject(settings)
} label: {
AppMenuImage(tunnel: context.tunnel)
.environmentObject(theme)
diff --git a/Passepartout/Library/Sources/AppUIMain/Business/MacSettingsModel.swift b/Passepartout/Library/Sources/AppUIMain/Business/MacSettingsModel.swift
new file mode 100644
index 00000000..da914e03
--- /dev/null
+++ b/Passepartout/Library/Sources/AppUIMain/Business/MacSettingsModel.swift
@@ -0,0 +1,90 @@
+//
+// MacSettingsModel.swift
+// Passepartout
+//
+// Created by Davide De Rosa on 10/29/24.
+// Copyright (c) 2024 Davide De Rosa. All rights reserved.
+//
+// https://github.com/passepartoutvpn
+//
+// This file is part of Passepartout.
+//
+// Passepartout is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Passepartout is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Passepartout. If not, see .
+//
+
+#if os(macOS)
+
+import AppKit
+import CommonLibrary
+import PassepartoutKit
+import ServiceManagement
+
+@MainActor
+public final class MacSettingsModel: ObservableObject {
+ private let defaults: UserDefaults
+
+ private let appWindow: AppWindow
+
+ private let appService: SMAppService
+
+ public var isStartedFromLoginItem: Bool {
+ NSApp.isHidden
+ }
+
+ public var isVisible: Bool {
+ get {
+ appWindow.isVisible
+ }
+ set {
+ appWindow.isVisible = newValue
+ objectWillChange.send()
+ }
+ }
+
+ public var launchesOnLogin: Bool {
+ get {
+ appService.status == .enabled
+ }
+ set {
+ do {
+ if newValue {
+ try appService.register()
+ } else {
+ try appService.unregister()
+ }
+ } catch {
+ pp_log(.app, .error, "Unable to (un)register login item: \(error)")
+ }
+ objectWillChange.send()
+ }
+ }
+
+ public var keepsInMenu: Bool {
+ get {
+ defaults.bool(forKey: AppPreference.keepsInMenu.key)
+ }
+ set {
+ defaults.set(newValue, forKey: AppPreference.keepsInMenu.key)
+ objectWillChange.send()
+ }
+ }
+
+ public init(defaults: UserDefaults, appWindow: AppWindow, loginItemId: String) {
+ self.defaults = defaults
+ self.appWindow = appWindow
+ appService = SMAppService.loginItem(identifier: loginItemId)
+ }
+}
+
+#endif
diff --git a/Passepartout/Library/Sources/AppUIMain/Views/AppMenu/macOS/AppMenu+Model.swift b/Passepartout/Library/Sources/AppUIMain/Views/AppMenu/macOS/AppMenu+Model.swift
deleted file mode 100644
index b0145ab9..00000000
--- a/Passepartout/Library/Sources/AppUIMain/Views/AppMenu/macOS/AppMenu+Model.swift
+++ /dev/null
@@ -1,73 +0,0 @@
-//
-// AppMenu+Model.swift
-// Passepartout
-//
-// Created by Davide De Rosa on 10/29/24.
-// Copyright (c) 2024 Davide De Rosa. All rights reserved.
-//
-// https://github.com/passepartoutvpn
-//
-// This file is part of Passepartout.
-//
-// Passepartout is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// Passepartout is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with Passepartout. If not, see .
-//
-
-#if os(macOS)
-
-import AppKit
-import PassepartoutKit
-import ServiceManagement
-
-extension AppMenu {
-
- @MainActor
- final class Model: ObservableObject {
- private let appService: SMAppService
-
- var isVisible: Bool {
- get {
- AppWindow.shared.isVisible
- }
- set {
- AppWindow.shared.isVisible = newValue
- objectWillChange.send()
- }
- }
-
- var launchesOnLogin: Bool {
- get {
- appService.status == .enabled
- }
- set {
- do {
- if newValue {
- try appService.register()
- } else {
- try appService.unregister()
- }
- } catch {
- pp_log(.app, .error, "Unable to (un)register login item: \(error)")
- }
- objectWillChange.send()
- }
- }
-
- init() {
- let loginItemId = BundleConfiguration.mainString(for: .loginItemId)
- appService = SMAppService.loginItem(identifier: loginItemId)
- }
- }
-}
-
-#endif
diff --git a/Passepartout/Library/Sources/AppUIMain/Views/AppMenu/macOS/AppMenu.swift b/Passepartout/Library/Sources/AppUIMain/Views/AppMenu/macOS/AppMenu.swift
index 89f3597d..c0410c48 100644
--- a/Passepartout/Library/Sources/AppUIMain/Views/AppMenu/macOS/AppMenu.swift
+++ b/Passepartout/Library/Sources/AppUIMain/Views/AppMenu/macOS/AppMenu.swift
@@ -32,8 +32,8 @@ import SwiftUI
public struct AppMenu: View {
- @AppStorage(AppPreference.keepsInMenu.key)
- private var keepsInMenu = true
+ @EnvironmentObject
+ private var settings: MacSettingsModel
@ObservedObject
private var profileManager: ProfileManager
@@ -41,9 +41,6 @@ public struct AppMenu: View {
@ObservedObject
private var tunnel: ExtendedTunnel
- @StateObject
- private var model = Model()
-
public init(profileManager: ProfileManager, tunnel: ExtendedTunnel) {
self.profileManager = profileManager
self.tunnel = tunnel
@@ -70,16 +67,16 @@ private extension AppMenu {
var showToggle: some View {
Button(Strings.Global.show) {
- model.isVisible = true
+ settings.isVisible = true
}
}
var loginToggle: some View {
- Toggle(Strings.Views.Settings.launchesOnLogin, isOn: $model.launchesOnLogin)
+ Toggle(Strings.Views.Settings.launchesOnLogin, isOn: $settings.launchesOnLogin)
}
var keepToggle: some View {
- Toggle(Strings.Views.Settings.keepsInMenu, isOn: $keepsInMenu)
+ Toggle(Strings.Views.Settings.keepsInMenu, isOn: $settings.keepsInMenu)
}
var profilesList: some View {
diff --git a/Passepartout/Library/Sources/AppUIMain/Views/Settings/SettingsSectionGroup.swift b/Passepartout/Library/Sources/AppUIMain/Views/Settings/SettingsSectionGroup.swift
index 0f4949f0..7cd3c88c 100644
--- a/Passepartout/Library/Sources/AppUIMain/Views/Settings/SettingsSectionGroup.swift
+++ b/Passepartout/Library/Sources/AppUIMain/Views/Settings/SettingsSectionGroup.swift
@@ -29,17 +29,15 @@ import PassepartoutKit
import SwiftUI
struct SettingsSectionGroup: View {
-
- @AppStorage(AppPreference.keepsInMenu.key)
- private var keepsInMenu = true
-
- @AppStorage(AppPreference.locksInBackground.key)
- private var locksInBackground = false
-
let profileManager: ProfileManager
- @StateObject
- private var model = AppMenu.Model()
+#if os(iOS)
+ @AppStorage(AppPreference.locksInBackground.key)
+ private var locksInBackground = false
+#else
+ @EnvironmentObject
+ private var settings: MacSettingsModel
+#endif
@State
private var isConfirmingEraseiCloud = false
@@ -50,8 +48,7 @@ struct SettingsSectionGroup: View {
var body: some View {
#if os(iOS)
lockInBackgroundToggle
-#endif
-#if os(macOS)
+#else
launchesOnLoginToggle
keepsInMenuToggle
#endif
@@ -60,21 +57,22 @@ struct SettingsSectionGroup: View {
}
private extension SettingsSectionGroup {
- var launchesOnLoginToggle: some View {
- Toggle(Strings.Views.Settings.launchesOnLogin, isOn: $model.launchesOnLogin)
- .themeSectionWithSingleRow(footer: Strings.Views.Settings.LaunchesOnLogin.footer)
- }
-
- var keepsInMenuToggle: some View {
- Toggle(Strings.Views.Settings.keepsInMenu, isOn: $keepsInMenu)
- .themeSectionWithSingleRow(footer: Strings.Views.Settings.KeepsInMenu.footer)
- }
-
+#if os(iOS)
var lockInBackgroundToggle: some View {
Toggle(Strings.Views.Settings.locksInBackground, isOn: $locksInBackground)
.themeSectionWithSingleRow(footer: Strings.Views.Settings.LocksInBackground.footer)
}
+#else
+ var launchesOnLoginToggle: some View {
+ Toggle(Strings.Views.Settings.launchesOnLogin, isOn: $settings.launchesOnLogin)
+ .themeSectionWithSingleRow(footer: Strings.Views.Settings.LaunchesOnLogin.footer)
+ }
+ var keepsInMenuToggle: some View {
+ Toggle(Strings.Views.Settings.keepsInMenu, isOn: $settings.keepsInMenu)
+ .themeSectionWithSingleRow(footer: Strings.Views.Settings.KeepsInMenu.footer)
+ }
+#endif
var eraseCloudKitButton: some View {
Button(Strings.Views.Settings.eraseIcloud, role: .destructive) {
isConfirmingEraseiCloud = true