Add ability to open app at login

This commit is contained in:
Leon Böttger committed 2025-07-29 11:24:31 +02:00
1 parent eac2df3d44
commit 5bf8469732
12 files changed
+1960 -1533

No files matched your search

+20
View File
@@ -74,6 +74,14 @@
EB5C37FF2E377CE70052EFD7 /* wire_format.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2929E18CB500A442DA /* wire_format.pb.swift */; };
EB5C38002E377CE70052EFD7 /* DataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB5C36E82E3546100052EFD7 /* DataModel.swift */; };
EB5C38012E377DE60052EFD7 /* Localized.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB96ED8E2D7F8103004B3FF1 /* Localized.swift */; };
EB5C38392E38C1F60052EFD7 /* justMaybe.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = EB5C38382E38C1F60052EFD7 /* justMaybe.mp3 */; };
EB5C383A2E38C1F60052EFD7 /* justMaybe.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = EB5C38382E38C1F60052EFD7 /* justMaybe.mp3 */; };
EB5C383B2E38C1F60052EFD7 /* justMaybe.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = EB5C38382E38C1F60052EFD7 /* justMaybe.mp3 */; };
EB5C383C2E38C2D90052EFD7 /* AudioManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB36435F2D73A29F009CDFF8 /* AudioManager.swift */; };
EB5C383D2E38C2D90052EFD7 /* AudioManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB36435F2D73A29F009CDFF8 /* AudioManager.swift */; };
EB5C383F2E38C49D0052EFD7 /* LaunchAtLogin.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB5C383E2E38C49D0052EFD7 /* LaunchAtLogin.swift */; };
EB5C38402E38C49D0052EFD7 /* LaunchAtLogin.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB5C383E2E38C49D0052EFD7 /* LaunchAtLogin.swift */; };
EB5C38412E38C49D0052EFD7 /* LaunchAtLogin.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB5C383E2E38C49D0052EFD7 /* LaunchAtLogin.swift */; };
EB6F62D72D91F4AF00534EC2 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB6F62D62D91F4AC00534EC2 /* SettingsView.swift */; };
EB7CD1452D6FB0C300CB0CB5 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB7CD1442D6FB0C100CB0CB5 /* Logger.swift */; };
EB7CD1462D6FB0E800CB0CB5 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB7CD1442D6FB0C100CB0CB5 /* Logger.swift */; };
@@ -201,6 +209,8 @@
EB5C36E62E3544FB0052EFD7 /* SaveFilesManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SaveFilesManager.swift; sourceTree = "<group>"; };
EB5C36E82E3546100052EFD7 /* DataModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataModel.swift; sourceTree = "<group>"; };
EB5C37E22E377C2E0052EFD7 /* QuickDropiOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = QuickDropiOS.app; sourceTree = BUILT_PRODUCTS_DIR; };
EB5C38382E38C1F60052EFD7 /* justMaybe.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = justMaybe.mp3; sourceTree = "<group>"; };
EB5C383E2E38C49D0052EFD7 /* LaunchAtLogin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchAtLogin.swift; sourceTree = "<group>"; };
EB6E743D2DD5C76F00331466 /* ConnectionFailureTracker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectionFailureTracker.swift; sourceTree = "<group>"; };
EB6F62D62D91F4AC00534EC2 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
EB7CD1442D6FB0C100CB0CB5 /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = "<group>"; };
@@ -424,6 +434,7 @@
EBC05C6B2E33B57100EECC18 /* Util */ = {
isa = PBXGroup;
children = (
EB5C383E2E38C49D0052EFD7 /* LaunchAtLogin.swift */,
EB36435F2D73A29F009CDFF8 /* AudioManager.swift */,
EBC05C5A2E33936C00EECC18 /* ErrorAlertHandler.swift */,
);
@@ -465,6 +476,7 @@
isa = PBXGroup;
children = (
EB5C36E32E34EC0E0052EFD7 /* License.txt */,
EB5C38382E38C1F60052EFD7 /* justMaybe.mp3 */,
EBC05C8A2E33B59E00EECC18 /* timeIsNow.mp3 */,
);
path = Resources;
@@ -614,6 +626,7 @@
691F53BC2ABB70840089FD92 /* DeviceListCell.xib in Resources */,
699DEBA92AB0573200115D22 /* ShareViewController.xib in Resources */,
EB96ED8D2D7F7EA3004B3FF1 /* Assets.xcassets in Resources */,
EB5C383A2E38C1F60052EFD7 /* justMaybe.mp3 in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -626,6 +639,7 @@
EB5C36932E34EBBD0052EFD7 /* Localizable.xcstrings in Resources */,
EBC05C8C2E33B59E00EECC18 /* timeIsNow.mp3 in Resources */,
69D2C32F29E7898C00EC7E30 /* MainMenu.xib in Resources */,
EB5C38392E38C1F60052EFD7 /* justMaybe.mp3 in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -633,6 +647,7 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
EB5C383B2E38C1F60052EFD7 /* justMaybe.mp3 in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -657,12 +672,14 @@
EB5C37C72E377A780052EFD7 /* wire_format.pb.swift in Sources */,
EB5C37C82E377A780052EFD7 /* ukey.pb.swift in Sources */,
EB5C37C92E377A780052EFD7 /* device_to_device_messages.pb.swift in Sources */,
EB5C383C2E38C2D90052EFD7 /* AudioManager.swift in Sources */,
EB5C37CA2E377A780052EFD7 /* offline_wire_formats.pb.swift in Sources */,
EBC05C5C2E3394EF00EECC18 /* ErrorAlertHandler.swift in Sources */,
EBC05C7B2E33B58100EECC18 /* ActiveNetworkInterface.swift in Sources */,
EBC05C7C2E33B58100EECC18 /* ApIsolationTester.swift in Sources */,
691F53BB2ABB70840089FD92 /* DeviceListCell.swift in Sources */,
EB5C37BB2E377A700052EFD7 /* ConnectionFailureTracker.swift in Sources */,
EB5C383F2E38C49D0052EFD7 /* LaunchAtLogin.swift in Sources */,
EB5C37BC2E377A700052EFD7 /* SleepManager.swift in Sources */,
EB5C37D02E377A7F0052EFD7 /* OutboundNearbyConnection.swift in Sources */,
EB5C37D12E377A7F0052EFD7 /* DataModel.swift in Sources */,
@@ -681,6 +698,7 @@
EBD2A2172D2860C100BCB8E2 /* WelcomeScreen.swift in Sources */,
EB3643602D73A2A2009CDFF8 /* AudioManager.swift in Sources */,
EB5C37B72E377A6F0052EFD7 /* ConnectionFailureTracker.swift in Sources */,
EB5C38412E38C49D0052EFD7 /* LaunchAtLogin.swift in Sources */,
EB5C37B82E377A6F0052EFD7 /* SleepManager.swift in Sources */,
EB5C37B92E377A6F0052EFD7 /* SaveFilesManager.swift in Sources */,
EB5C37BA2E377A6F0052EFD7 /* Helpers.swift in Sources */,
@@ -735,6 +753,8 @@
EB5C37FC2E377CE70052EFD7 /* OutboundNearbyConnection.swift in Sources */,
EB5C37FD2E377CE70052EFD7 /* Logger.swift in Sources */,
EB5C37FE2E377CE70052EFD7 /* offline_wire_formats.pb.swift in Sources */,
EB5C383D2E38C2D90052EFD7 /* AudioManager.swift in Sources */,
EB5C38402E38C49D0052EFD7 /* LaunchAtLogin.swift in Sources */,
EB5C37FF2E377CE70052EFD7 /* wire_format.pb.swift in Sources */,
EB5C38002E377CE70052EFD7 /* DataModel.swift in Sources */,
);
+3 -1
View File
@@ -304,6 +304,8 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele
DispatchQueue.main.async {
AudioManager.playErrorSound()
NSApp.activate(ignoringOtherApps: true)
let alert = NSAlert()
@@ -337,7 +339,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele
activeIncomingTransfers[transfer.id] = TransferInfo(device: device, transfer: transfer)
AudioManager.playSound()
AudioManager.playIncomingFileSound()
let acceptAutomatically = UserDefaults.standard.bool(forKey: UserDefaultsKeys.automaticallyAcceptFiles.rawValue)
File diff suppressed because it is too large. Load diff
+19 -4
View File
@@ -8,17 +8,32 @@
import AVFoundation
class AudioManager {
static var audioPlayer: AVAudioPlayer?
static private var incomingFileSoundPlayer: AVAudioPlayer?
static private var errorSoundPlayer: AVAudioPlayer?
static func playSound() {
static func playIncomingFileSound() {
guard let soundURL = Bundle.main.url(forResource: "timeIsNow", withExtension: "mp3") else {
log("Sound file not found")
return
}
do {
audioPlayer = try AVAudioPlayer(contentsOf: soundURL)
audioPlayer?.play()
incomingFileSoundPlayer = try AVAudioPlayer(contentsOf: soundURL)
incomingFileSoundPlayer?.play()
} catch {
log("Error playing sound: \(error.localizedDescription)")
}
}
static func playErrorSound() {
guard let soundURL = Bundle.main.url(forResource: "justMaybe", withExtension: "mp3") else {
log("Sound file not found")
return
}
do {
errorSoundPlayer = try AVAudioPlayer(contentsOf: soundURL)
errorSoundPlayer?.play()
} catch {
log("Error playing sound: \(error.localizedDescription)")
}
+1
View File
@@ -67,6 +67,7 @@ class ErrorAlertHandler {
return
}
else {
AudioManager.playErrorSound()
self.isAlertShown = true
log("Showing alert with message: \"\(alert.messageText)\" and description: \"\(alert.informativeText)\"")
log("Unsuccessful transmission. Already successful transmissions: \(transmissionCount())")
+156
View File
@@ -0,0 +1,156 @@
/*
LaunchAtLogin-Modern
https://github.com/sindresorhus/LaunchAtLogin-Modern
MIT License
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#if os(macOS) || targetEnvironment(macCatalyst)
import SwiftUI
import ServiceManagement
import os.log
@available(macOS 13.0, *)
public enum LaunchAtLogin {
fileprivate static let observable = Observable()
/**
Toggle “launch at login” for your app or check whether it's enabled.
*/
public static var isEnabled: Bool {
get { SMAppService.mainApp.status == .enabled }
set {
observable.objectWillChange.send()
do {
if newValue {
if SMAppService.mainApp.status == .enabled {
try? SMAppService.mainApp.unregister()
}
try SMAppService.mainApp.register()
} else {
try SMAppService.mainApp.unregister()
}
} catch {
log("Failed to \(newValue ? "enable" : "disable") launch at login: \(error.localizedDescription)")
}
}
}
/**
Whether the app was launched at login.
- Important: This property must only be checked in `NSApplicationDelegate#applicationDidFinishLaunching`.
*/
public static var wasLaunchedAtLogin: Bool {
let event = NSAppleEventManager.shared().currentAppleEvent
return event?.eventID == kAEOpenApplication
&& event?.paramDescriptor(forKeyword: keyAEPropData)?.enumCodeValue == keyAELaunchedAsLogInItem
}
}
@available(macOS 13.0, *)
extension LaunchAtLogin {
final class Observable: ObservableObject {
var isEnabled: Bool {
get { LaunchAtLogin.isEnabled }
set {
LaunchAtLogin.isEnabled = newValue
}
}
}
}
@available(macOS 13.0, *)
extension LaunchAtLogin {
/**
This package comes with a `LaunchAtLogin.Toggle` view which is like the built-in `Toggle` but with a predefined binding and label. Clicking the view toggles “launch at login” for your app.
```
struct ContentView: View {
var body: some View {
LaunchAtLogin.Toggle()
}
}
```
The default label is `"Launch at login"`, but it can be overridden for localization and other needs:
```
struct ContentView: View {
var body: some View {
LaunchAtLogin.Toggle {
Text("Launch at login")
}
}
}
```
*/
public struct Toggle<Label: View>: View {
@ObservedObject private var launchAtLogin = LaunchAtLogin.observable
private let label: Label
/**
Creates a toggle that displays a custom label.
- Parameters:
- label: A view that describes the purpose of the toggle.
*/
public init(@ViewBuilder label: () -> Label) {
self.label = label()
}
public var body: some View {
SwiftUI.Toggle(isOn: $launchAtLogin.isEnabled) { label }
}
}
}
@available(macOS 13.0, *)
extension LaunchAtLogin.Toggle<Text> {
/**
Creates a toggle that generates its label from a localized string key.
This initializer creates a ``Text`` view on your behalf with the provided `titleKey`.
- Parameters:
- titleKey: The key for the toggle's localized title, that describes the purpose of the toggle.
*/
public init(_ titleKey: LocalizedStringKey) {
label = Text(titleKey)
}
/**
Creates a toggle that generates its label from a string.
This initializer creates a `Text` view on your behalf with the provided `title`.
- Parameters:
- title: A string that describes the purpose of the toggle.
*/
public init(_ title: some StringProtocol) {
label = Text(title)
}
/**
Creates a toggle with the default title of `Launch at login`.
*/
public init() {
self.init("Launch at login")
}
}
#endif
@@ -62,6 +62,7 @@ func openRouterSettingsPage() async {
guard let routerIP else {
DispatchQueue.main.async {
AudioManager.playErrorSound()
let alert = NSAlert()
alert.alertStyle = .critical
+15
View File
@@ -45,6 +45,21 @@ struct SettingsView: View {
Toggle("OpenFinderAfterReceiving", isOn: $openFinderAfterReceiving)
.padding(.top, 5)
Text("OpenFinderAfterReceivingFooter")
.font(.footnote)
.padding(.top, 5)
if #available(macOS 13.0, *) {
Divider()
.padding(.vertical, 10)
LaunchAtLogin.Toggle {
Text("LaunchAtLogin")
}
.padding(.top, 5)
}
}
}
}
+1 -1
View File
@@ -86,7 +86,7 @@ struct LargeAppIconView<Content: View>: View {
Image("AppIconHighRes")
.resizable()
.frame(width: 150, height: 150)
.padding(.top, 50)
.padding(.top, 40)
Text(title.localized())
.font(.largeTitle)
+1
View File
@@ -327,5 +327,6 @@ Terms of use: https://notificationsounds.com/terms-of-use
Sound files:
https://notificationsounds.com/notification-sounds/time-is-now-585
https://notificationsounds.com/notification-sounds/just-maybe-577
Modifications to sound files: Volume and length, no previous modifications.
Binary file not shown.
+2
View File
@@ -203,6 +203,7 @@ class ShareViewController: NSViewController, ShareExtensionDelegate {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
log("Canceling share request because URL \(url) is a directory")
AudioManager.playErrorSound()
let alert = NSAlert()
alert.alertStyle = .critical
@@ -330,6 +331,7 @@ class ShareViewController: NSViewController, ShareExtensionDelegate {
let timeoutAlert = DispatchWorkItem {
if !self.connectionEstablished {
AudioManager.playErrorSound()
let alert = NSAlert()
alert.alertStyle = .critical