@@ -4,7 +4,7 @@ import ServiceManagement
44public struct LaunchAgentManager {
55 let lastLaunchAgentVersionKey = " LastLaunchAgentVersion "
66 let serviceIdentifier : String
7- let executablePath : String
7+ let executableURL : URL
88 let bundleIdentifier : String
99
1010 var launchAgentDirURL : URL {
@@ -16,15 +16,14 @@ public struct LaunchAgentManager {
1616 launchAgentDirURL. appendingPathComponent ( " \( serviceIdentifier) .plist " ) . path
1717 }
1818
19- public init ( serviceIdentifier: String , executablePath : String , bundleIdentifier: String ) {
19+ public init ( serviceIdentifier: String , executableURL : URL , bundleIdentifier: String ) {
2020 self . serviceIdentifier = serviceIdentifier
21- self . executablePath = executablePath
21+ self . executableURL = executableURL
2222 self . bundleIdentifier = bundleIdentifier
2323 }
2424
2525 public func setupLaunchAgentForTheFirstTimeIfNeeded( ) async throws {
2626 if #available( macOS 13 , * ) {
27- await removeObsoleteLaunchAgent ( )
2827 try await setupLaunchAgent ( )
2928 } else {
3029 if UserDefaults . standard. integer ( forKey: lastLaunchAgentVersionKey) < 40 {
@@ -33,48 +32,18 @@ public struct LaunchAgentManager {
3332 }
3433 guard !FileManager. default. fileExists ( atPath: launchAgentPath) else { return }
3534 try await setupLaunchAgent ( )
36- await removeObsoleteLaunchAgent ( )
3735 }
3836 }
3937
4038 public func setupLaunchAgent( ) async throws {
4139 if #available( macOS 13 , * ) {
42- let bridgeLaunchAgent = SMAppService . agent ( plistName: " bridgeLaunchAgent.plist " )
43- try bridgeLaunchAgent. 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 " >
49- <dict>
50- <key>Label</key>
51- <string> \( serviceIdentifier) </string>
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- )
40+ if executableURL. path. hasPrefix ( " /Applications " ) {
41+ try setupLaunchAgentWithPredefinedPlist ( )
42+ } else {
43+ try await setupLaunchAgentWithDynamicPlist ( )
7244 }
73- FileManager . default. createFile (
74- atPath: launchAgentPath,
75- contents: content. data ( using: . utf8)
76- )
77- try await launchctl ( " load " , launchAgentPath)
45+ } else {
46+ try await setupLaunchAgentWithDynamicPlist ( )
7847 }
7948
8049 let buildNumber = ( Bundle . main. infoDictionary ? [ " CFBundleVersion " ] as? String )
@@ -85,7 +54,11 @@ public struct LaunchAgentManager {
8554 public func removeLaunchAgent( ) async throws {
8655 if #available( macOS 13 , * ) {
8756 let bridgeLaunchAgent = SMAppService . agent ( plistName: " bridgeLaunchAgent.plist " )
88- try await bridgeLaunchAgent. unregister ( )
57+ try ? await bridgeLaunchAgent. unregister ( )
58+ if FileManager . default. fileExists ( atPath: launchAgentPath) {
59+ try ? await launchctl ( " unload " , launchAgentPath)
60+ try ? FileManager . default. removeItem ( atPath: launchAgentPath)
61+ }
8962 } else {
9063 try await launchctl ( " unload " , launchAgentPath)
9164 try FileManager . default. removeItem ( atPath: launchAgentPath)
@@ -97,23 +70,56 @@ public struct LaunchAgentManager {
9770 try await helper ( " reload-launch-agent " , " --service-identifier " , serviceIdentifier)
9871 }
9972 }
73+ }
10074
101- public func removeObsoleteLaunchAgent( ) async {
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 "
75+ extension LaunchAgentManager {
76+ @available ( macOS 13 , * )
77+ func setupLaunchAgentWithPredefinedPlist( ) throws {
78+ let bridgeLaunchAgent = SMAppService . agent ( plistName: " bridgeLaunchAgent.plist " )
79+ try bridgeLaunchAgent. register ( )
80+ }
81+
82+ func setupLaunchAgentWithDynamicPlist( ) async throws {
83+ if FileManager . default. fileExists ( atPath: launchAgentPath) {
84+ throw E ( errorDescription: " Launch agent already exists. " )
85+ }
86+
87+ let content = """
88+ <?xml version= " 1.0 " encoding= " UTF-8 " ?>
89+ <!DOCTYPE plist PUBLIC " -//Apple//DTD PLIST 1.0//EN " " http://www.apple.com/DTDs/PropertyList-1.0.dtd " >
90+ <plist version= " 1.0 " >
91+ <dict>
92+ <key>Label</key>
93+ <string> \( serviceIdentifier) </string>
94+ <key>Program</key>
95+ <string> \( executableURL. path) </string>
96+ <key>MachServices</key>
97+ <dict>
98+ <key> \( serviceIdentifier) </key>
99+ <true/>
100+ </dict>
101+ <key>AssociatedBundleIdentifiers</key>
102+ <array>
103+ <string> \( bundleIdentifier) </string>
104+ <string> \( serviceIdentifier) </string>
105+ </array>
106+ </dict>
107+ </plist>
108+ """
109+ if !FileManager. default. fileExists ( atPath: launchAgentDirURL. path) {
110+ try FileManager . default. createDirectory (
111+ at: launchAgentDirURL,
112+ withIntermediateDirectories: false
112113 )
113- if FileManager . default. fileExists ( atPath: path) {
114- try ? FileManager . default. removeItem ( atPath: path)
115- }
116114 }
115+ FileManager . default. createFile (
116+ atPath: launchAgentPath,
117+ contents: content. data ( using: . utf8)
118+ )
119+ #if DEBUG
120+ #else
121+ try await launchctl ( " load " , launchAgentPath)
122+ #endif
117123 }
118124}
119125
@@ -170,7 +176,7 @@ private func launchctl(_ args: String...) async throws {
170176 return try await process ( " /bin/launchctl " , args)
171177}
172178
173- struct E : Error , LocalizedError {
179+ private struct E : Error , LocalizedError {
174180 var errorDescription : String ?
175181}
176182
0 commit comments