Skip to content

Commit 7ad6331

Browse files
committed
Support SMAppService for macOS 13
Fixes #76
1 parent d473e16 commit 7ad6331

File tree

4 files changed

+101
-56
lines changed

4 files changed

+101
-56
lines changed

.github/funding.yml

Lines changed: 0 additions & 4 deletions
This file was deleted.

.github/workflows/ci.yml

Lines changed: 0 additions & 10 deletions
This file was deleted.

Sources/LaunchAtLogin/LaunchAtLogin.swift

Lines changed: 82 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
import Foundation
22
import ServiceManagement
33
import Combine
4+
import os.log
5+
6+
private let hasMigratedKey = "LaunchAtLogin__hasMigrated"
47

58
public enum LaunchAtLogin {
9+
@available(macOS 11, *)
10+
private static let logger = Logger(subsystem: "com.sindresorhus.LaunchAtLogin", category: "main")
11+
612
public static let kvo = KVO()
713

814
@available(macOS 10.15, *)
@@ -15,30 +21,99 @@ public enum LaunchAtLogin {
1521

1622
private static let id = "\(Bundle.main.bundleIdentifier!)-LaunchAtLoginHelper"
1723

24+
private static var hasMigrated: Bool {
25+
get { UserDefaults.standard.bool(forKey: hasMigratedKey) }
26+
set {
27+
UserDefaults.standard.set(true, forKey: hasMigratedKey)
28+
}
29+
}
30+
31+
public static func migrateIfNeeded() {
32+
guard
33+
#available(macOS 13, *),
34+
!hasMigrated
35+
else {
36+
return
37+
}
38+
39+
hasMigrated = true
40+
41+
if isEnabledLegacy {
42+
isEnabledModern = true
43+
}
44+
45+
unregisterLegacy()
46+
}
47+
1848
public static var isEnabled: Bool {
1949
get {
20-
guard let jobs = (LaunchAtLogin.self as DeprecationWarningWorkaround.Type).jobsDict else {
21-
return false
50+
if #available(macOS 13, *) {
51+
return isEnabledModern
52+
} else {
53+
return isEnabledLegacy
2254
}
23-
24-
let job = jobs.first { ($0["Label"] as? String) == id }
25-
26-
return job?["OnDemand"] as? Bool ?? false
2755
}
2856
set {
2957
if #available(macOS 10.15, *) {
3058
observable.objectWillChange.send()
3159
}
3260

3361
kvo.willChangeValue(for: \.isEnabled)
34-
SMLoginItemSetEnabled(id as CFString, newValue)
62+
63+
if #available(macOS 13, *) {
64+
isEnabledModern = newValue
65+
} else {
66+
isEnabledLegacy = newValue
67+
}
68+
3569
kvo.didChangeValue(for: \.isEnabled)
3670

3771
if #available(macOS 10.15, *) {
3872
_publisher.send(newValue)
3973
}
4074
}
4175
}
76+
77+
@available(macOS 13, *)
78+
private static var isEnabledModern: Bool {
79+
get { SMAppService.mainApp.status == .enabled }
80+
set {
81+
do {
82+
if newValue {
83+
if SMAppService.mainApp.status == .enabled {
84+
try? SMAppService.mainApp.unregister()
85+
}
86+
87+
try SMAppService.mainApp.register()
88+
} else {
89+
try SMAppService.mainApp.unregister()
90+
}
91+
} catch {
92+
logger.error("Failed to \(newValue ? "enable" : "disable") launch at login: \(error.localizedDescription)")
93+
}
94+
}
95+
}
96+
97+
private static var isEnabledLegacy: Bool {
98+
get {
99+
guard let jobs = (LaunchAtLogin.self as DeprecationWarningWorkaround.Type).jobsDict else {
100+
return false
101+
}
102+
103+
let job = jobs.first { ($0["Label"] as? String) == id }
104+
105+
return job?["OnDemand"] as? Bool ?? false
106+
}
107+
set {
108+
SMLoginItemSetEnabled(id as CFString, newValue)
109+
}
110+
}
111+
112+
@available(macOS 13, *)
113+
private static func unregisterLegacy() {
114+
isEnabledLegacy = false
115+
try? SMAppService.loginItem(identifier: id).unregister()
116+
}
42117
}
43118

44119
// MARK: - LaunchAtLoginObservable

readme.md

Lines changed: 19 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -2,31 +2,31 @@
22

33
> Add “Launch at Login” functionality to your macOS app in seconds
44
5-
It's usually quite a [convoluted and error-prone process](before-after.md) to add this. **No more!**
5+
**If your app targets macOS 13 or later, check out [this modern version](https://github.com/sindresorhus/LaunchAtLogin-Modern) instead.**
6+
7+
It's usually quite a [convoluted and error-prone process](before-after.md) to add this (on macOS 12 and older). **No more!**
68

79
This package works with both sandboxed and non-sandboxed apps and it's App Store compatible and used in apps like [Plash](https://github.com/sindresorhus/Plash), [Dato](https://sindresorhus.com/dato), [Lungo](https://sindresorhus.com/lungo), and [Battery Indicator](https://sindresorhus.com/battery-indicator).
810

11+
**This package uses the new [`SMAppService`](https://developer.apple.com/documentation/servicemanagement/smappservice/3945412-mainapp) on macOS 13+ and [`SMLoginItemSetEnabled`](https://developer.apple.com/documentation/servicemanagement/1501557-smloginitemsetenabled) on older macOS versions.**
12+
13+
### Why should I use this package now that [`SMAppService`](https://developer.apple.com/documentation/servicemanagement/smappservice/3945412-mainapp?changes=latest_minor) exists?
14+
15+
- Backwards compatibility with older macOS versions
16+
- Nicer API
17+
- Included SwiftUI component
18+
919
## Requirements
1020

1121
macOS 10.13+
1222

1323
## Install
1424

15-
#### Swift Package Manager
16-
1725
Add `https://github.com/sindresorhus/LaunchAtLogin` in the [“Swift Package Manager” tab in Xcode](https://developer.apple.com/documentation/xcode/adding_package_dependencies_to_your_app).
1826

19-
#### Carthage
20-
21-
*Warning: Carthage is not recommended. Support for it will be removed at some point in the future.*
22-
23-
```
24-
github "sindresorhus/LaunchAtLogin"
25-
```
26-
2727
## Usage
2828

29-
#### Swift Package Manager
29+
**Skip this step if your app targets macOS 13 or later.**
3030

3131
Add a new [“Run Script Phase”](http://stackoverflow.com/a/39633955/64949) **below** (not into) “Copy Bundle Resources” in “Build Phases” with the following:
3232

@@ -38,16 +38,6 @@ And uncheck “Based on dependency analysis”.
3838

3939
*(I would name the run script `Copy “Launch at Login Helper”`)*
4040

41-
#### Carthage
42-
43-
Add a new [“Run Script Phase”](http://stackoverflow.com/a/39633955/64949) **below** (not into) “Embed Frameworks” in “Build Phases” with the following:
44-
45-
```sh
46-
"${PROJECT_DIR}/Carthage/Build/Mac/LaunchAtLogin.framework/Resources/copy-helper.sh"
47-
```
48-
49-
*(I would name the run script `Copy “Launch at Login Helper”`)*
50-
5141
### Use it in your app
5242

5343
No need to store any state to UserDefaults.
@@ -128,6 +118,10 @@ final class ViewModel {
128118
}
129119
```
130120

121+
#### Swift Concurrency
122+
123+
Use [`LaunchAtLogin.publisher.values`](https://developer.apple.com/documentation/combine/publisher/values-1dm9r).
124+
131125
#### Storyboards
132126

133127
Bind the control to the `LaunchAtLogin.kvo` exposed property:
@@ -145,7 +139,7 @@ final class ViewController: NSViewController {
145139

146140
## How does it work?
147141

148-
The package bundles the helper app needed to launch your app and copies it into your app at build time.
142+
On macOS 12 and earlier, the package bundles the helper app needed to launch your app and copies it into your app at build time. On macOS 13 and later, it calls the built-in API.
149143

150144
## FAQ
151145

@@ -161,16 +155,12 @@ rm: […]/Resources/copy-helper.sh: No such file or directory
161155
Command PhaseScriptExecution failed with a nonzero exit code
162156
```
163157

164-
#### The size of my app increased after adding `LaunchAtLogin` when using Carthage
165-
166-
The bundled launcher app is written in Swift and hence needs to embed the Swift runtime libraries. If your project targets macOS 10.14.4 or later, you can avoid embedding the Swift runtime libraries. First, open `./Carthage/Checkouts/LaunchAtLogin/LaunchAtLogin.xcodeproj` and set the deployment target to the same as your app, and then run `$ carthage build`. You'll have to do this each time you update `LaunchAtLogin`.
167-
168-
This is not a problem when using Swift Package Manager.
169-
170158
#### My app doesn't show up in “System Preferences › Users & Groups › Login Items”
171159

172160
[This is the expected behavior](https://stackoverflow.com/a/15104481/64949), unfortunately.
173161

162+
However, it will show there on macOS 13 and later.
163+
174164
#### My app doesn't launch at login when testing
175165

176166
This is usually caused by having one or more older builds of your app laying around somewhere on the system, and macOS picking one of those instead, which doesn't have the launch helper, and thus fails to start.
@@ -186,10 +176,6 @@ Some helpful Stack Overflow answers:
186176
- https://stackoverflow.com/a/53110832/64949
187177
- https://stackoverflow.com/a/53110852/64949
188178

189-
#### Can you support CocoaPods?
190-
191-
CocoaPods used to be supported, but [it did not work well](https://github.com/sindresorhus/LaunchAtLogin/issues/22) and there was no easy way to fix it, so support was dropped. Even though you mainly use CocoaPods, you can still use Swift Package Manager just for this package without any problems.
192-
193179
#### I can't see the `LaunchAtLogin.bundle` in my debug build or I get a notarization error for developer ID distribution
194180

195181
As discussed [here](https://github.com/sindresorhus/LaunchAtLogin/issues/50), this package tries to determine if you're making a release or debug build and clean up its install accordingly. If your debug build is missing the bundle or, conversely, your release build has the bundle and it causes a code signing error, that means this has failed.
@@ -200,8 +186,6 @@ The script's determination is based on the “Build Active Architecture Only”
200186

201187
- [Defaults](https://github.com/sindresorhus/Defaults) - Swifty and modern UserDefaults
202188
- [KeyboardShortcuts](https://github.com/sindresorhus/KeyboardShortcuts) - Add user-customizable global keyboard shortcuts to your macOS app
203-
- [Regex](https://github.com/sindresorhus/Regex) - Swifty regular expressions
204-
- [Preferences](https://github.com/sindresorhus/Preferences) - Add a preferences window to your macOS app in minutes
205189
- [DockProgress](https://github.com/sindresorhus/DockProgress) - Show progress in your app's Dock icon
206190
- [create-dmg](https://github.com/sindresorhus/create-dmg) - Create a good-looking DMG for your macOS app in seconds
207191
- [More…](https://github.com/search?q=user%3Asindresorhus+language%3Aswift)

0 commit comments

Comments
 (0)