11import Foundation
2+ import ServiceManagement
23
3- #warning("TODO: Migrate to SMAppService")
44public struct LaunchAgentManager {
55 let lastLaunchAgentVersionKey = " LastLaunchAgentVersion "
66 let serviceIdentifier : String
@@ -23,67 +23,96 @@ public struct LaunchAgentManager {
2323 }
2424
2525 public func setupLaunchAgentForTheFirstTimeIfNeeded( ) async throws {
26- if UserDefaults . standard. integer ( forKey: lastLaunchAgentVersionKey) < 40 {
26+ if #available( macOS 13 , * ) {
27+ await removeObsoleteLaunchAgent ( )
2728 try await setupLaunchAgent ( )
28- return
29+ } else {
30+ if UserDefaults . standard. integer ( forKey: lastLaunchAgentVersionKey) < 40 {
31+ try await setupLaunchAgent ( )
32+ return
33+ }
34+ guard !FileManager. default. fileExists ( atPath: launchAgentPath) else { return }
35+ try await setupLaunchAgent ( )
36+ await removeObsoleteLaunchAgent ( )
2937 }
30- guard !FileManager. default. fileExists ( atPath: launchAgentPath) else { return }
31- try await setupLaunchAgent ( )
32- await removeObsoleteLaunchAgent ( )
3338 }
3439
3540 public func setupLaunchAgent( ) async throws {
36- let content = """
37- <?xml version= " 1.0 " encoding= " UTF-8 " ?>
38- <!DOCTYPE plist PUBLIC " -//Apple//DTD PLIST 1.0//EN " " http://www.apple.com/DTDs/PropertyList-1.0.dtd " >
39- <plist version= " 1.0 " >
40- <dict>
41- <key>Label</key>
42- <string> \( serviceIdentifier) </string>
43- <key>Program</key>
44- <string> \( executablePath) </string>
45- <key>MachServices</key>
41+ if #available( macOS 13 , * ) {
42+ let launchAgent = SMAppService . agent ( plistName: " launchAgent.plist " )
43+ try launchAgent. register ( )
44+ } else {
45+ let content = """
46+ <?xml version= " 1.0 " encoding= " UTF-8 " ?>
47+ <!DOCTYPE plist PUBLIC " -//Apple//DTD PLIST 1.0//EN " " http://www.apple.com/DTDs/PropertyList-1.0.dtd " >
48+ <plist version= " 1.0 " >
4649 <dict>
47- <key> \( serviceIdentifier) </key>
48- <true/>
49- </dict>
50- <key>AssociatedBundleIdentifiers</key>
51- <array>
52- <string> \( bundleIdentifier) </string>
50+ <key>Label</key>
5351 <string> \( serviceIdentifier) </string>
54- </array>
55- </dict>
56- </plist>
57- """
58- if !FileManager. default. fileExists ( atPath: launchAgentDirURL. path) {
59- try FileManager . default. createDirectory (
60- at: launchAgentDirURL,
61- withIntermediateDirectories: false
52+ <key>Program</key>
53+ <string> \( executablePath) </string>
54+ <key>MachServices</key>
55+ <dict>
56+ <key> \( serviceIdentifier) </key>
57+ <true/>
58+ </dict>
59+ <key>AssociatedBundleIdentifiers</key>
60+ <array>
61+ <string> \( bundleIdentifier) </string>
62+ <string> \( serviceIdentifier) </string>
63+ </array>
64+ </dict>
65+ </plist>
66+ """
67+ if !FileManager. default. fileExists ( atPath: launchAgentDirURL. path) {
68+ try FileManager . default. createDirectory (
69+ at: launchAgentDirURL,
70+ withIntermediateDirectories: false
71+ )
72+ }
73+ FileManager . default. createFile (
74+ atPath: launchAgentPath,
75+ contents: content. data ( using: . utf8)
6276 )
77+ try await launchctl ( " load " , launchAgentPath)
6378 }
64- FileManager . default. createFile (
65- atPath: launchAgentPath,
66- contents: content. data ( using: . utf8)
67- )
79+
6880 let buildNumber = ( Bundle . main. infoDictionary ? [ " CFBundleVersion " ] as? String )
6981 . flatMap ( Int . init)
7082 UserDefaults . standard. set ( buildNumber, forKey: lastLaunchAgentVersionKey)
71- try await launchctl ( " load " , launchAgentPath)
7283 }
7384
7485 public func removeLaunchAgent( ) async throws {
75- try await launchctl ( " unload " , launchAgentPath)
76- try FileManager . default. removeItem ( atPath: launchAgentPath)
86+ if #available( macOS 13 , * ) {
87+ let launchAgent = SMAppService . agent ( plistName: " launchAgent.plist " )
88+ try await launchAgent. unregister ( )
89+ } else {
90+ try await launchctl ( " unload " , launchAgentPath)
91+ try FileManager . default. removeItem ( atPath: launchAgentPath)
92+ }
7793 }
7894
7995 public func reloadLaunchAgent( ) async throws {
80- try await helper ( " reload-launch-agent " , " --service-identifier " , serviceIdentifier)
96+ if #unavailable( macOS 13 ) {
97+ try await helper ( " reload-launch-agent " , " --service-identifier " , serviceIdentifier)
98+ }
8199 }
82100
83101 public func removeObsoleteLaunchAgent( ) async {
84- let path = launchAgentPath. replacingOccurrences ( of: " ExtensionService " , with: " XPCService " )
85- if FileManager . default. fileExists ( atPath: path) {
86- try ? FileManager . default. removeItem ( atPath: path)
102+ if #available( macOS 13 , * ) {
103+ let path = launchAgentPath
104+ if FileManager . default. fileExists ( atPath: path) {
105+ try ? await launchctl ( " unload " , path)
106+ try ? FileManager . default. removeItem ( atPath: path)
107+ }
108+ } else {
109+ let path = launchAgentPath. replacingOccurrences (
110+ of: " ExtensionService " ,
111+ with: " XPCService "
112+ )
113+ if FileManager . default. fileExists ( atPath: path) {
114+ try ? FileManager . default. removeItem ( atPath: path)
115+ }
87116 }
88117 }
89118}
@@ -144,3 +173,4 @@ private func launchctl(_ args: String...) async throws {
144173struct E : Error , LocalizedError {
145174 var errorDescription : String ?
146175}
176+
0 commit comments