Show in Mac status bar plus Login Item (#773)
Add a status menu via SwiftUI MenuBarExtra where to: - Show/hide app - Launch on login via "Login Item" target - Toggle profiles on/off Only weird that the login item is not added to the list of "Open at Login", but to "Allow in the Background", see https://github.com/pilotmoon/Scroll-Reverser/issues/165 Requires some refactoring to bring AppContext initialization to the AppDelegate. Fixes #617 Fixes #482 Fixes #696 Fixes #505
This commit is contained in:
parent
d60ab97922
commit
41de48789e
|
@ -7,6 +7,9 @@
|
|||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
0E757F132CD0CFFC006E13E1 /* PassepartoutLoginItemApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E757F122CD0CFFC006E13E1 /* PassepartoutLoginItemApp.swift */; };
|
||||
0E757F202CD0D22B006E13E1 /* PassepartoutLoginItem.app in Embed Login Item */ = {isa = PBXBuildFile; fileRef = 0E757F102CD0CFFC006E13E1 /* PassepartoutLoginItem.app */; platformFilters = (macos, ); settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
0E757F232CD0D2BD006E13E1 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E757F212CD0D2B7006E13E1 /* AppDelegate.swift */; };
|
||||
0E7C3CCD2C9AF44600B72E69 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7C3CCC2C9AF44600B72E69 /* AppDelegate.swift */; };
|
||||
0E7E3D692B9345FD002BBDB4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0E7E3D5C2B9345FD002BBDB4 /* Assets.xcassets */; };
|
||||
0E7E3D6B2B9345FD002BBDB4 /* PassepartoutApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7E3D5F2B9345FD002BBDB4 /* PassepartoutApp.swift */; };
|
||||
|
@ -29,6 +32,13 @@
|
|||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
0E757F242CD0D812006E13E1 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 0E06D1872B87629100176E1D /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 0E757F0F2CD0CFFC006E13E1;
|
||||
remoteInfo = PassepartoutLoginItem;
|
||||
};
|
||||
0EC332D02B8A1808000B9C2F /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 0E06D1872B87629100176E1D /* Project object */;
|
||||
|
@ -46,6 +56,17 @@
|
|||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
0E757F1F2CD0D1FB006E13E1 /* Embed Login Item */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = Contents/Library/LoginItems;
|
||||
dstSubfolderSpec = 1;
|
||||
files = (
|
||||
0E757F202CD0D22B006E13E1 /* PassepartoutLoginItem.app in Embed Login Item */,
|
||||
);
|
||||
name = "Embed Login Item";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
0EC332D62B8A1808000B9C2F /* Embed Foundation Extensions */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
|
@ -72,6 +93,10 @@
|
|||
|
||||
/* Begin PBXFileReference section */
|
||||
0E06D18F2B87629100176E1D /* Passepartout.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Passepartout.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
0E757F102CD0CFFC006E13E1 /* PassepartoutLoginItem.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PassepartoutLoginItem.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
0E757F122CD0CFFC006E13E1 /* PassepartoutLoginItemApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PassepartoutLoginItemApp.swift; sourceTree = "<group>"; };
|
||||
0E757F182CD0CFFD006E13E1 /* LoginItem.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = LoginItem.entitlements; sourceTree = "<group>"; };
|
||||
0E757F212CD0D2B7006E13E1 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
0E7C3CCC2C9AF44600B72E69 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
0E7D0EAD2CAEA47700A2F28D /* Passepartout.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = Passepartout.xctestplan; sourceTree = "<group>"; };
|
||||
0E7E3D5B2B9345FD002BBDB4 /* App.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = App.entitlements; sourceTree = "<group>"; };
|
||||
|
@ -99,6 +124,13 @@
|
|||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
0E757F0D2CD0CFFC006E13E1 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
0EC332C52B8A1808000B9C2F /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
|
@ -141,6 +173,7 @@
|
|||
0E06D18F2B87629100176E1D /* Passepartout.app */,
|
||||
0EC332C82B8A1808000B9C2F /* PassepartoutTunnel.appex */,
|
||||
0EDE56F02CABE42E0082D21C /* PassepartoutIntents.appex */,
|
||||
0E757F102CD0CFFC006E13E1 /* PassepartoutLoginItem.app */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
|
@ -153,6 +186,16 @@
|
|||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
0E757F112CD0CFFC006E13E1 /* LoginItem */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
0E757F212CD0D2B7006E13E1 /* AppDelegate.swift */,
|
||||
0E757F122CD0CFFC006E13E1 /* PassepartoutLoginItemApp.swift */,
|
||||
0E757F182CD0CFFD006E13E1 /* LoginItem.entitlements */,
|
||||
);
|
||||
path = LoginItem;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
0E7E3D592B9345FD002BBDB4 /* Passepartout */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -160,6 +203,7 @@
|
|||
0E7D0EAD2CAEA47700A2F28D /* Passepartout.xctestplan */,
|
||||
0E7E3D5A2B9345FD002BBDB4 /* App */,
|
||||
0EDE56E82CABE40D0082D21C /* Intents */,
|
||||
0E757F112CD0CFFC006E13E1 /* LoginItem */,
|
||||
0E7E3D612B9345FD002BBDB4 /* Shared */,
|
||||
0E7E3D652B9345FD002BBDB4 /* Tunnel */,
|
||||
0EBE80DD2BF55C9100E36A20 /* Library */,
|
||||
|
@ -240,15 +284,17 @@
|
|||
0ED27CBF2B9331FF0089E26B /* Frameworks */,
|
||||
0E06D18D2B87629100176E1D /* Resources */,
|
||||
0EC332D62B8A1808000B9C2F /* Embed Foundation Extensions */,
|
||||
0E8D852E2C328C54005493DE /* SwiftLint */,
|
||||
0EDE56FE2CABE42E0082D21C /* Embed ExtensionKit Extensions */,
|
||||
0E757F1F2CD0D1FB006E13E1 /* Embed Login Item */,
|
||||
0E8D852E2C328C54005493DE /* SwiftLint */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
0E6C0A032BF4047100450362 /* PBXTargetDependency */,
|
||||
0EC332D12B8A1808000B9C2F /* PBXTargetDependency */,
|
||||
0EDE56F92CABE42E0082D21C /* PBXTargetDependency */,
|
||||
0E757F252CD0D812006E13E1 /* PBXTargetDependency */,
|
||||
0EC332D12B8A1808000B9C2F /* PBXTargetDependency */,
|
||||
);
|
||||
name = Passepartout;
|
||||
packageProductDependencies = (
|
||||
|
@ -258,6 +304,23 @@
|
|||
productReference = 0E06D18F2B87629100176E1D /* Passepartout.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
0E757F0F2CD0CFFC006E13E1 /* PassepartoutLoginItem */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 0E757F1E2CD0CFFD006E13E1 /* Build configuration list for PBXNativeTarget "PassepartoutLoginItem" */;
|
||||
buildPhases = (
|
||||
0E757F0C2CD0CFFC006E13E1 /* Sources */,
|
||||
0E757F0D2CD0CFFC006E13E1 /* Frameworks */,
|
||||
0E757F0E2CD0CFFC006E13E1 /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = PassepartoutLoginItem;
|
||||
productName = PassepartoutLoginItem;
|
||||
productReference = 0E757F102CD0CFFC006E13E1 /* PassepartoutLoginItem.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
0EC332C72B8A1808000B9C2F /* PassepartoutTunnel */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 0EC332D32B8A1808000B9C2F /* Build configuration list for PBXNativeTarget "PassepartoutTunnel" */;
|
||||
|
@ -309,6 +372,9 @@
|
|||
0E06D18E2B87629100176E1D = {
|
||||
CreatedOnToolsVersion = 15.2;
|
||||
};
|
||||
0E757F0F2CD0CFFC006E13E1 = {
|
||||
CreatedOnToolsVersion = 15.4;
|
||||
};
|
||||
0EC332C72B8A1808000B9C2F = {
|
||||
CreatedOnToolsVersion = 15.2;
|
||||
};
|
||||
|
@ -332,6 +398,7 @@
|
|||
targets = (
|
||||
0E06D18E2B87629100176E1D /* Passepartout */,
|
||||
0EDE56EF2CABE42E0082D21C /* PassepartoutIntents */,
|
||||
0E757F0F2CD0CFFC006E13E1 /* PassepartoutLoginItem */,
|
||||
0EC332C72B8A1808000B9C2F /* PassepartoutTunnel */,
|
||||
);
|
||||
};
|
||||
|
@ -350,6 +417,13 @@
|
|||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
0E757F0E2CD0CFFC006E13E1 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
0EC332C62B8A1808000B9C2F /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
|
@ -401,6 +475,15 @@
|
|||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
0E757F0C2CD0CFFC006E13E1 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
0E757F232CD0D2BD006E13E1 /* AppDelegate.swift in Sources */,
|
||||
0E757F132CD0CFFC006E13E1 /* PassepartoutLoginItemApp.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
0EC332C42B8A1808000B9C2F /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
|
@ -429,6 +512,14 @@
|
|||
isa = PBXTargetDependency;
|
||||
productRef = 0E6C0A042BF4047600450362 /* TunnelLibrary */;
|
||||
};
|
||||
0E757F252CD0D812006E13E1 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
platformFilters = (
|
||||
macos,
|
||||
);
|
||||
target = 0E757F0F2CD0CFFC006E13E1 /* PassepartoutLoginItem */;
|
||||
targetProxy = 0E757F242CD0D812006E13E1 /* PBXContainerItemProxy */;
|
||||
};
|
||||
0EC332D12B8A1808000B9C2F /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 0EC332C72B8A1808000B9C2F /* PassepartoutTunnel */;
|
||||
|
@ -692,6 +783,54 @@
|
|||
};
|
||||
name = Release;
|
||||
};
|
||||
0E757F1C2CD0CFFD006E13E1 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_ENTITLEMENTS = Passepartout/LoginItem/LoginItem.entitlements;
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CURRENT_PROJECT_VERSION = 3645;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = "$(TARGET_NAME)";
|
||||
INFOPLIST_KEY_LSBackgroundOnly = YES;
|
||||
INFOPLIST_KEY_LSUIElement = YES;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "$(CFG_COPYRIGHT)";
|
||||
INFOPLIST_KEY_UIRequiredDeviceCapabilities = arm64;
|
||||
LD_RUNPATH_SEARCH_PATHS = "@executable_path/../Frameworks";
|
||||
MARKETING_VERSION = 3.0.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "$(CFG_LOGIN_ITEM_ID)";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = macosx;
|
||||
SUPPORTED_PLATFORMS = macosx;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
0E757F1D2CD0CFFD006E13E1 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_ENTITLEMENTS = Passepartout/LoginItem/LoginItem.entitlements;
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CURRENT_PROJECT_VERSION = 3645;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = "$(TARGET_NAME)";
|
||||
INFOPLIST_KEY_LSBackgroundOnly = YES;
|
||||
INFOPLIST_KEY_LSUIElement = YES;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "$(CFG_COPYRIGHT)";
|
||||
INFOPLIST_KEY_UIRequiredDeviceCapabilities = arm64;
|
||||
LD_RUNPATH_SEARCH_PATHS = "@executable_path/../Frameworks";
|
||||
MARKETING_VERSION = 3.0.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "$(CFG_LOGIN_ITEM_ID)";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = macosx;
|
||||
SUPPORTED_PLATFORMS = macosx;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
0EC332D42B8A1808000B9C2F /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
|
@ -820,6 +959,15 @@
|
|||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
0E757F1E2CD0CFFD006E13E1 /* Build configuration list for PBXNativeTarget "PassepartoutLoginItem" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
0E757F1C2CD0CFFD006E13E1 /* Debug */,
|
||||
0E757F1D2CD0CFFD006E13E1 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
0EC332D32B8A1808000B9C2F /* Build configuration list for PBXNativeTarget "PassepartoutTunnel" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1540"
|
||||
version = "1.7">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES"
|
||||
buildArchitectures = "Automatic">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0E757F0F2CD0CFFC006E13E1"
|
||||
BuildableName = "PassepartoutLoginItem.app"
|
||||
BlueprintName = "PassepartoutLoginItem"
|
||||
ReferencedContainer = "container:Passepartout.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
shouldAutocreateTestPlan = "YES">
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0E757F0F2CD0CFFC006E13E1"
|
||||
BuildableName = "PassepartoutLoginItem.app"
|
||||
BlueprintName = "PassepartoutLoginItem"
|
||||
ReferencedContainer = "container:Passepartout.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0E757F0F2CD0CFFC006E13E1"
|
||||
BuildableName = "PassepartoutLoginItem.app"
|
||||
BlueprintName = "PassepartoutLoginItem"
|
||||
ReferencedContainer = "container:Passepartout.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
|
@ -14,10 +14,12 @@
|
|||
<string>$(CFG_IAP_BUNDLE_PREFIX)</string>
|
||||
<key>keychainGroupId</key>
|
||||
<string>$(CFG_KEYCHAIN_GROUP_ID)</string>
|
||||
<key>tunnelId</key>
|
||||
<string>$(CFG_TUNNEL_ID)</string>
|
||||
<key>legacyV2CloudKitId</key>
|
||||
<string>$(CFG_LEGACY_V2_CLOUDKIT_ID)</string>
|
||||
<key>loginItemId</key>
|
||||
<string>$(CFG_LOGIN_ITEM_ID)</string>
|
||||
<key>tunnelId</key>
|
||||
<string>$(CFG_TUNNEL_ID)</string>
|
||||
</dict>
|
||||
<key>CFBundleDocumentTypes</key>
|
||||
<array>
|
||||
|
@ -50,10 +52,6 @@
|
|||
</array>
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
<false/>
|
||||
<key>NSUserActivityTypes</key>
|
||||
<array>
|
||||
<string>CustomIntentIntent</string>
|
||||
</array>
|
||||
<key>UIBackgroundModes</key>
|
||||
<array>
|
||||
<string>remote-notification</string>
|
||||
|
|
|
@ -23,4 +23,33 @@
|
|||
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import AppUI
|
||||
import CommonLibrary
|
||||
import PassepartoutKit
|
||||
import SwiftUI
|
||||
|
||||
@MainActor
|
||||
final class AppDelegate: NSObject {
|
||||
|
||||
@AppStorage(AppPreference.confirmsQuit.key)
|
||||
var confirmsQuit = true
|
||||
|
||||
let context: AppContext = .shared
|
||||
// let context: AppContext = .mock(withRegistry: .shared)
|
||||
|
||||
func configure() {
|
||||
PassepartoutConfiguration.shared.configureLogging(
|
||||
to: BundleConfiguration.urlForAppLog,
|
||||
parameters: Constants.shared.log,
|
||||
logsPrivateData: UserDefaults.appGroup.bool(forKey: AppPreference.logsPrivateData.key)
|
||||
)
|
||||
AppUI.configure(with: context)
|
||||
|
||||
#if os(macOS)
|
||||
// keep this for login item because scenePhase is not triggered
|
||||
Task {
|
||||
try await context.tunnel.prepare(purge: true)
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "StatusActive@2x.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"template-rendering-intent" : "template"
|
||||
}
|
||||
}
|
BIN
Passepartout/App/Assets.xcassets/Menu/MenuActive.imageset/StatusActive@2x.png
vendored
Normal file
BIN
Passepartout/App/Assets.xcassets/Menu/MenuActive.imageset/StatusActive@2x.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 817 B |
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "StatusInactive@2x.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"template-rendering-intent" : "template"
|
||||
}
|
||||
}
|
BIN
Passepartout/App/Assets.xcassets/Menu/MenuInactive.imageset/StatusInactive@2x.png
vendored
Normal file
BIN
Passepartout/App/Assets.xcassets/Menu/MenuInactive.imageset/StatusInactive@2x.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 743 B |
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "StatusPending@2x.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"template-rendering-intent" : "template"
|
||||
}
|
||||
}
|
BIN
Passepartout/App/Assets.xcassets/Menu/MenuPending.imageset/StatusPending@2x.png
vendored
Normal file
BIN
Passepartout/App/Assets.xcassets/Menu/MenuPending.imageset/StatusPending@2x.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.2 KiB |
|
@ -42,8 +42,9 @@ struct PassepartoutApp: App {
|
|||
@Environment(\.scenePhase)
|
||||
private var scenePhase
|
||||
|
||||
private let context: AppContext = .shared
|
||||
// private let context: AppContext = .mock(withRegistry: .shared)
|
||||
private var context: AppContext {
|
||||
appDelegate.context
|
||||
}
|
||||
|
||||
private let appName = BundleConfiguration.mainDisplayName
|
||||
|
||||
|
@ -62,12 +63,19 @@ struct PassepartoutApp: App {
|
|||
#else
|
||||
var body: some Scene {
|
||||
Window(appName, id: appName, content: contentView)
|
||||
.defaultSize(width: 600.0, height: 400.0)
|
||||
.defaultSize(width: 600, height: 400)
|
||||
|
||||
Settings {
|
||||
SettingsView(profileManager: context.profileManager)
|
||||
.frame(minWidth: 300, minHeight: 200)
|
||||
}
|
||||
MenuBarExtra {
|
||||
AppMenu()
|
||||
.withEnvironment(from: context, theme: theme)
|
||||
} label: {
|
||||
AppMenuImage(connectionObserver: context.connectionObserver)
|
||||
.environmentObject(theme)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
@ -79,14 +87,6 @@ private extension PassepartoutApp {
|
|||
tunnel: context.tunnel,
|
||||
registry: context.registry
|
||||
)
|
||||
.onLoad {
|
||||
PassepartoutConfiguration.shared.configureLogging(
|
||||
to: BundleConfiguration.urlForAppLog,
|
||||
parameters: Constants.shared.log,
|
||||
logsPrivateData: UserDefaults.appGroup.bool(forKey: AppPreference.logsPrivateData.key)
|
||||
)
|
||||
AppUI.configure(with: context)
|
||||
}
|
||||
.onChange(of: scenePhase) {
|
||||
switch $0 {
|
||||
case .active:
|
||||
|
|
|
@ -28,7 +28,11 @@
|
|||
import AppUI
|
||||
import UIKit
|
||||
|
||||
final class AppDelegate: NSObject, UIApplicationDelegate {
|
||||
extension AppDelegate: UIApplicationDelegate {
|
||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
|
||||
configure()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -27,17 +27,12 @@
|
|||
|
||||
import AppKit
|
||||
import AppUI
|
||||
import CommonLibrary
|
||||
import PassepartoutKit
|
||||
import SwiftUI
|
||||
|
||||
final class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
|
||||
@AppStorage(AppPreference.confirmsQuit.key)
|
||||
private var confirmsQuit = true
|
||||
|
||||
extension AppDelegate: NSApplicationDelegate {
|
||||
func applicationDidFinishLaunching(_ notification: Notification) {
|
||||
NSApp.windows[0].styleMask.remove(.closable)
|
||||
configureAppWindow()
|
||||
configure()
|
||||
}
|
||||
|
||||
func applicationShouldTerminate(_ sender: NSApplication) -> NSApplication.TerminateReply {
|
||||
|
@ -52,8 +47,18 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
|
|||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
private extension AppDelegate {
|
||||
var isStartedFromLoginItem: Bool {
|
||||
NSApp.isHidden
|
||||
}
|
||||
|
||||
func configureAppWindow() {
|
||||
if isStartedFromLoginItem {
|
||||
AppWindow.shared.isVisible = false
|
||||
}
|
||||
AppWindow.shared.removeCloseButton()
|
||||
}
|
||||
|
||||
func quitConfirmationAlert() -> NSApplication.TerminateReply {
|
||||
let alert = NSAlert()
|
||||
alert.alertStyle = .warning
|
||||
|
|
|
@ -39,6 +39,7 @@ CFG_GROUP_ID[sdk=macosx*] = $(CFG_TEAM_ID).$(CFG_RAW_GROUP_ID)
|
|||
CFG_KEYCHAIN_GROUP_ID = $(CFG_TEAM_ID).$(CFG_RAW_GROUP_ID)
|
||||
CFG_IAP_BUNDLE_PREFIX = com.algoritmico.ios.Passepartout
|
||||
CFG_INTENTS_ID = $(CFG_APP_ID).Intents
|
||||
CFG_LOGIN_ITEM_ID = $(CFG_APP_ID).LoginItem
|
||||
CFG_RAW_GROUP_ID = group.com.algoritmico.Passepartout
|
||||
CFG_TEAM_ID = DTDYD63ZX9
|
||||
CFG_TUNNEL_ID = $(CFG_APP_ID).Tunnel
|
||||
|
|
|
@ -38,6 +38,16 @@ public enum Strings {
|
|||
}
|
||||
}
|
||||
}
|
||||
public enum AppMenu {
|
||||
public enum Items {
|
||||
/// Launch on Login
|
||||
public static let launchOnLogin = Strings.tr("Localizable", "app_menu.items.launch_on_login", fallback: "Launch on Login")
|
||||
/// Quit %@
|
||||
public static func quit(_ p1: Any) -> String {
|
||||
return Strings.tr("Localizable", "app_menu.items.quit", String(describing: p1), fallback: "Quit %@")
|
||||
}
|
||||
}
|
||||
}
|
||||
public enum Entities {
|
||||
public enum ConnectionStatus {
|
||||
/// Connected
|
||||
|
@ -227,6 +237,8 @@ public enum Strings {
|
|||
public static let gateway = Strings.tr("Localizable", "global.gateway", fallback: "Gateway")
|
||||
/// General
|
||||
public static let general = Strings.tr("Localizable", "global.general", fallback: "General")
|
||||
/// Hide
|
||||
public static let hide = Strings.tr("Localizable", "global.hide", fallback: "Hide")
|
||||
/// Hostname
|
||||
public static let hostname = Strings.tr("Localizable", "global.hostname", fallback: "Hostname")
|
||||
/// Interface
|
||||
|
@ -297,6 +309,8 @@ public enum Strings {
|
|||
public static let servers = Strings.tr("Localizable", "global.servers", fallback: "Servers")
|
||||
/// Settings
|
||||
public static let settings = Strings.tr("Localizable", "global.settings", fallback: "Settings")
|
||||
/// Show
|
||||
public static let show = Strings.tr("Localizable", "global.show", fallback: "Show")
|
||||
/// Status
|
||||
public static let status = Strings.tr("Localizable", "global.status", fallback: "Status")
|
||||
/// Storage
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
"global.folder" = "Folder";
|
||||
"global.gateway" = "Gateway";
|
||||
"global.general" = "General";
|
||||
"global.hide" = "Hide";
|
||||
"global.hostname" = "Hostname";
|
||||
"global.interface" = "Interface";
|
||||
"global.keep_alive" = "Keep-alive";
|
||||
|
@ -64,6 +65,7 @@
|
|||
"global.server" = "Server";
|
||||
"global.servers" = "Servers";
|
||||
"global.settings" = "Settings";
|
||||
"global.show" = "Show";
|
||||
"global.status" = "Status";
|
||||
"global.storage" = "Storage";
|
||||
"global.subnet" = "Subnet";
|
||||
|
@ -233,6 +235,11 @@
|
|||
"providers.vpn.preset" = "Preset";
|
||||
"providers.vpn.no_servers" = "No servers";
|
||||
|
||||
// MARK: - App menu
|
||||
|
||||
"app_menu.items.launch_on_login" = "Launch on Login";
|
||||
"app_menu.items.quit" = "Quit %@";
|
||||
|
||||
// MARK: - Components
|
||||
|
||||
"ui.connection_status.on_demand_suffix" = " (on-demand)";
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
//
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
#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
|
|
@ -0,0 +1,113 @@
|
|||
//
|
||||
// AppMenu.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 <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
#if os(macOS)
|
||||
|
||||
import AppLibrary
|
||||
import Combine
|
||||
import PassepartoutKit
|
||||
import SwiftUI
|
||||
|
||||
public struct AppMenu: View {
|
||||
|
||||
@EnvironmentObject
|
||||
private var profileManager: ProfileManager
|
||||
|
||||
@EnvironmentObject
|
||||
private var profileProcessor: ProfileProcessor
|
||||
|
||||
@EnvironmentObject
|
||||
private var tunnel: Tunnel
|
||||
|
||||
@StateObject
|
||||
private var model = Model()
|
||||
|
||||
public init() {
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
versionItem
|
||||
Divider()
|
||||
dockToggle
|
||||
loginToggle
|
||||
Divider()
|
||||
profilesList
|
||||
Divider()
|
||||
quitButton
|
||||
}
|
||||
}
|
||||
|
||||
private extension AppMenu {
|
||||
var versionItem: some View {
|
||||
Text(BundleConfiguration.mainVersionString)
|
||||
}
|
||||
|
||||
var dockToggle: some View {
|
||||
Button(model.isVisible ? Strings.Global.hide : Strings.Global.show) {
|
||||
model.isVisible.toggle()
|
||||
}
|
||||
}
|
||||
|
||||
var loginToggle: some View {
|
||||
Toggle(Strings.AppMenu.Items.launchOnLogin, isOn: $model.launchesOnLogin)
|
||||
}
|
||||
|
||||
var profilesList: some View {
|
||||
ForEach(profileManager.headers, id: \.self, content: profileToggle)
|
||||
}
|
||||
|
||||
func profileToggle(for header: ProfileHeader) -> some View {
|
||||
Toggle(header.name, isOn: profileToggleBinding(for: header))
|
||||
}
|
||||
|
||||
func profileToggleBinding(for header: ProfileHeader) -> Binding<Bool> {
|
||||
Binding {
|
||||
header.id == tunnel.currentProfile?.id && tunnel.status != .inactive
|
||||
} set: { isOn in
|
||||
Task {
|
||||
guard let profile = profileManager.profile(withId: header.id) else {
|
||||
return
|
||||
}
|
||||
do {
|
||||
if isOn {
|
||||
try await tunnel.connect(with: profile, processor: profileProcessor)
|
||||
} else {
|
||||
try await tunnel.disconnect()
|
||||
}
|
||||
} catch {
|
||||
pp_log(.app, .error, "Unable to toggle profile \(header.id) from menu: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var quitButton: some View {
|
||||
Button(Strings.AppMenu.Items.quit(BundleConfiguration.mainDisplayName)) {
|
||||
NSApp.terminate(self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,60 @@
|
|||
//
|
||||
// AppMenuImage.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 <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
#if os(macOS)
|
||||
|
||||
import PassepartoutKit
|
||||
import SwiftUI
|
||||
|
||||
public struct AppMenuImage: View, TunnelContextProviding {
|
||||
|
||||
@ObservedObject
|
||||
var connectionObserver: ConnectionObserver
|
||||
|
||||
public init(connectionObserver: ConnectionObserver) {
|
||||
self.connectionObserver = connectionObserver
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
ThemeMenuImage(tunnelConnectionStatus.imageName)
|
||||
}
|
||||
}
|
||||
|
||||
private extension TunnelStatus {
|
||||
var imageName: Theme.MenuImageName {
|
||||
switch self {
|
||||
case .active:
|
||||
return .active
|
||||
|
||||
case .inactive:
|
||||
return .inactive
|
||||
|
||||
case .activating, .deactivating:
|
||||
return .pending
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,64 @@
|
|||
//
|
||||
// AppWindow.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 <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
#if os(macOS)
|
||||
|
||||
import AppKit
|
||||
|
||||
@MainActor
|
||||
public final class AppWindow {
|
||||
public static let shared = AppWindow()
|
||||
|
||||
public var isVisible: Bool {
|
||||
get {
|
||||
NSApp.activationPolicy() == .regular && window.isVisible
|
||||
}
|
||||
set {
|
||||
NSApp.setActivationPolicy(newValue ? .regular : .prohibited)
|
||||
if newValue {
|
||||
NSApp.activate(ignoringOtherApps: true)
|
||||
window.makeKeyAndOrderFront(self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private init() {
|
||||
}
|
||||
|
||||
public func removeCloseButton() {
|
||||
window.styleMask.remove(.closable)
|
||||
}
|
||||
}
|
||||
|
||||
private extension AppWindow {
|
||||
var window: NSWindow {
|
||||
guard let window = NSApp.windows.first else {
|
||||
fatalError("No Mac window?")
|
||||
}
|
||||
return window
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -27,15 +27,13 @@ import Foundation
|
|||
import PassepartoutKit
|
||||
|
||||
protocol TunnelContextProviding {
|
||||
var tunnel: Tunnel { get }
|
||||
|
||||
var connectionObserver: ConnectionObserver { get }
|
||||
}
|
||||
|
||||
@MainActor
|
||||
extension TunnelContextProviding {
|
||||
var tunnelConnectionStatus: TunnelStatus {
|
||||
var status = tunnel.status
|
||||
var status = connectionObserver.tunnel.status
|
||||
if status == .active, let connectionStatus = connectionObserver.connectionStatus {
|
||||
if connectionStatus == .connected {
|
||||
status = .active
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
//
|
||||
// Theme+MenuImageName.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 <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
extension Theme {
|
||||
public enum MenuImageName {
|
||||
case active
|
||||
case inactive
|
||||
case pending
|
||||
}
|
||||
}
|
||||
|
||||
extension Theme.MenuImageName {
|
||||
static var defaultImageName: (Self) -> String {
|
||||
{
|
||||
switch $0 {
|
||||
case .active: return "MenuActive"
|
||||
case .inactive: return "MenuInactive"
|
||||
case .pending: return "MenuPending"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -434,6 +434,22 @@ struct ThemeImageLabel: View {
|
|||
}
|
||||
}
|
||||
|
||||
struct ThemeMenuImage: View {
|
||||
|
||||
@EnvironmentObject
|
||||
private var theme: Theme
|
||||
|
||||
private let name: Theme.MenuImageName
|
||||
|
||||
init(_ name: Theme.MenuImageName) {
|
||||
self.name = name
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
Image(theme.menuImageName(name))
|
||||
}
|
||||
}
|
||||
|
||||
struct ThemeDisclosableMenu<Content, Label>: View where Content: View, Label: View {
|
||||
|
||||
@ViewBuilder
|
||||
|
|
|
@ -80,6 +80,8 @@ public final class Theme: ObservableObject {
|
|||
|
||||
var systemImageName: (ImageName) -> String = Theme.ImageName.defaultSystemName
|
||||
|
||||
var menuImageName: (MenuImageName) -> String = Theme.MenuImageName.defaultImageName
|
||||
|
||||
init(dummy: Void) {
|
||||
}
|
||||
|
||||
|
|
|
@ -30,7 +30,7 @@ import SwiftUI
|
|||
extension TunnelContextProviding where Self: ThemeProviding {
|
||||
var tunnelStatusColor: Color {
|
||||
if connectionObserver.lastErrorCode != nil {
|
||||
switch tunnel.status {
|
||||
switch connectionObserver.tunnel.status {
|
||||
case .inactive:
|
||||
return theme.inactiveColor
|
||||
|
||||
|
|
|
@ -30,11 +30,12 @@ import SwiftUI
|
|||
extension View {
|
||||
public func withEnvironment(from context: AppContext, theme: Theme) -> some View {
|
||||
environmentObject(theme)
|
||||
.environmentObject(context.connectionObserver)
|
||||
.environmentObject(context.iapManager)
|
||||
.environmentObject(context.profileManager)
|
||||
.environmentObject(context.profileProcessor)
|
||||
.environmentObject(context.connectionObserver)
|
||||
.environmentObject(context.providerManager)
|
||||
.environmentObject(context.tunnel)
|
||||
}
|
||||
|
||||
public func withMockEnvironment() -> some View {
|
||||
|
|
|
@ -42,6 +42,8 @@ extension BundleConfiguration {
|
|||
|
||||
case keychainGroupId
|
||||
|
||||
case loginItemId
|
||||
|
||||
case tunnelId
|
||||
|
||||
// legacy v2
|
||||
|
|
|
@ -0,0 +1,95 @@
|
|||
//
|
||||
// AppDelegate.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 <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
import AppKit
|
||||
import Foundation
|
||||
|
||||
@MainActor
|
||||
final class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
func applicationDidFinishLaunching(_ notification: Notification) {
|
||||
guard !isAppRunning else {
|
||||
NSApp.terminate(self)
|
||||
return
|
||||
}
|
||||
let cfg = NSWorkspace.OpenConfiguration()
|
||||
cfg.hides = true
|
||||
cfg.activates = false
|
||||
cfg.addsToRecentItems = false
|
||||
Task {
|
||||
defer {
|
||||
NSApp.terminate(self)
|
||||
}
|
||||
do {
|
||||
try await NSWorkspace.shared.openApplication(at: appURL, configuration: cfg)
|
||||
NSLog("Launched main app: \(appURL)")
|
||||
} catch {
|
||||
NSLog("Unable to launch main app: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension AppDelegate {
|
||||
var loginItemId: String {
|
||||
guard let id = Bundle.main.bundleIdentifier else {
|
||||
fatalError("No bundle identifier in LoginItem?")
|
||||
}
|
||||
return id
|
||||
}
|
||||
|
||||
var appId: String {
|
||||
var idComponents = loginItemId.components(separatedBy: ".")
|
||||
idComponents.removeLast()
|
||||
return idComponents.joined(separator: ".")
|
||||
}
|
||||
|
||||
var appURL: URL {
|
||||
let path = Bundle.main.bundlePath as NSString
|
||||
var components = path.pathComponents
|
||||
|
||||
// Passepartout.app/Contents/Library/LoginItems/PassepartoutLoginItem.app
|
||||
components.removeLast(4)
|
||||
|
||||
let appPath = NSString.path(withComponents: components)
|
||||
return URL(fileURLWithPath: appPath)
|
||||
}
|
||||
|
||||
var isAppRunning: Bool {
|
||||
NSWorkspace.shared.runningApplications.contains {
|
||||
$0.bundleIdentifier == appId
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Preconcurrency warnings
|
||||
|
||||
extension NSWorkspace: @unchecked Sendable {
|
||||
}
|
||||
|
||||
extension NSRunningApplication: @unchecked Sendable {
|
||||
}
|
||||
|
||||
extension NSWorkspace.OpenConfiguration: @unchecked Sendable {
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
<?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.security.app-sandbox</key>
|
||||
<true/>
|
||||
<key>com.apple.security.files.user-selected.read-only</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
|
@ -0,0 +1,37 @@
|
|||
//
|
||||
// PassepartoutLoginItemApp.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 <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
@main
|
||||
struct PassepartoutLoginItemApp: App {
|
||||
|
||||
@NSApplicationDelegateAdaptor
|
||||
private var appDelegate: AppDelegate
|
||||
|
||||
var body: some Scene {
|
||||
MenuBarExtra("") {}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue