.pkg within this repos package dir.
145 |
146 | USAGE: Ths script needs to be ran as root/sudo, and requires the following arguments to be passed to it:
147 |
148 | \$1 - The team id of a "Developer ID Installer" identity within the running users keychain:
149 | https://developer.apple.com/help/account/create-certificates/create-developer-id-certificates/
150 | \$2 - Name of an App-specific password within the running users keychain, to be used for notarization:
151 | https://support.apple.com/en-us/102654
152 | \$3 - pkg receipt identifier (for example: uk.dataJAR.Notifier)
153 | "
154 | }
155 |
156 | getNotifierVersion()
157 | {
158 | # Queries Notifier.app for version
159 | /bin/echo "Getting notifier version from ${notifierPath}/Contents/Info.plist..."
160 | notifierVersion=$(/usr/bin/defaults read "${notifierPath}"/Contents/Info.plist CFBundleShortVersionString)
161 | /bin/echo "notifier version: ${notifierVersion}..."
162 | }
163 |
164 |
165 | getMinOSVersion()
166 | {
167 | # Queries Notifier.app for min os version
168 | /bin/echo "Getting min os version from ${notifierPath}/Contents/Info.plist..."
169 | minOS=$(/usr/bin/defaults read "${notifierPath}"/Contents/Info.plist LSMinimumSystemVersion)
170 | /bin/echo "minOS: ${minOS}..."
171 | }
172 |
173 | setPermissions()
174 | {
175 | # Sets user:group ownership
176 | /bin/echo "Setting root:wheel for: ${scriptDir}/ROOT/..."
177 | /usr/sbin/chown -R root:wheel "${scriptDir}/ROOT/"
178 |
179 | # Set perms
180 | /bin/echo "Setting 755 permissions for: ${scriptDir}/ROOT/..."
181 | /bin/chmod -R 755 "${scriptDir}/ROOT/"
182 |
183 | # Mark Notifier.app as executable
184 | /bin/echo "Marking: ${notifierPath} as executable..."
185 | /usr/bin/sudo /bin/chmod -R +x "${notifierPath}"
186 | }
187 |
188 | createPKG()
189 | {
190 | # Creates a component plist
191 | /bin/echo "Creating ${scriptDir}/notifier-app.plist"...
192 | /usr/bin/pkgbuild --analyze --root "${scriptDir}"/ROOT/ "${scriptDir}"/notifier-app.plist
193 |
194 | # Set BundleIsRelocatable to NO in component plist
195 | /bin/echo "Setting \"BundleIsRelocatable\" to NO in ${scriptDir}/notifier-app.plist..."
196 | /usr/bin/plutil -replace BundleIsRelocatable -bool NO "${scriptDir}"/notifier-app.plist
197 |
198 | # Creates notifier.pkg in ${scriptDir}/
199 | /bin/echo "Creating ${scriptDir}/notifier-${notifierVersion}.pkg"
200 | /usr/bin/pkgbuild --identifier "${pkgIdentifier}" --root "${scriptDir}"/ROOT/ --sign "${developerIDInstaller}" --install-location '/' --version "${notifierVersion}" --min-os-version "${minOS}" --component-plist "${scriptDir}"/notifier-app.plist "${scriptDir}"/Notifier-"${notifierVersion}".pkg
201 | }
202 |
203 | notarizePKG()
204 | {
205 | # Notarizes the PKG created within the createPKG function
206 | /bin/echo "Notarizing ${scriptDir}/Notifier-${notifierVersion}.pkg..."
207 | /usr/bin/xcrun notarytool submit "${scriptDir}"/Notifier-"${notifierVersion}".pkg --keychain-profile "${appPassword}" --wait
208 | }
209 |
210 | clearPermissions()
211 | {
212 | # Sets perms on ROOT dir recursively after building a pkg
213 | /bin/echo "Clearing permissions on: ${scriptDir}/ROOT/..."
214 | /bin/chmod -R 777 "${scriptDir}/ROOT/"
215 | }
216 |
217 | ##########################################################################################
218 | #################################### End functions #######################################
219 | ##########################################################################################
220 |
221 | if ! setup "$@"
222 | then
223 | usage
224 | else
225 | start
226 | getNotifierVersion
227 | getMinOSVersion
228 | setPermissions
229 | createPKG
230 | notarizePKG
231 | clearPermissions
232 | finish
233 | fi
234 |
--------------------------------------------------------------------------------
/Notifier/Notifier/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // Notifier
4 | //
5 | // Copyright © 2024 dataJAR Ltd. All rights reserved.
6 | //
7 |
8 | // Imports
9 | import Cocoa
10 | import CoreFoundation
11 | import UserNotifications
12 |
13 | // Declaration
14 | @NSApplicationMain
15 | // The apps class
16 | class AppDelegate: NSObject, NSApplicationDelegate {
17 | // IBOutlet declaration
18 | @IBOutlet weak var window: NSWindow!
19 | // If we're about to load, make sure we're logged in, exit otherwise
20 | func applicationWillFinishLaunching(_ aNotification: Notification) {
21 | // Get the username of the logged in user
22 | let loggedInUser = loggedInUser()
23 | // The first argument is always the executable, drop it
24 | let passedArgs = Array(CommandLine.arguments.dropFirst())
25 | // Get the parsed args
26 | let parsedResult = ArgParser.parseOrExit(passedArgs)
27 | // If rebrand has been passed
28 | if parsedResult.rebrand != "" {
29 | // Rebrand Notifier apps
30 | changeIcons(brandingImage: parsedResult.rebrand, loggedInUser: loggedInUser, parsedResult: parsedResult)
31 | // If we're not rebranding and no user is logged in, exit
32 | } else if loggedInUser == "" {
33 | // Post message
34 | print("INFO: No user logged in, and we're not rebranding... exiting...")
35 | // Exit
36 | exit(0)
37 | }
38 | }
39 | // Check arguments passed to app, then OS if a valid argument is passed else print usage
40 | func applicationDidFinishLaunching(_ aNotification: Notification) {
41 | // The first argument is always the executable, drop it
42 | let passedArgs = Array(CommandLine.arguments.dropFirst())
43 | // Get the parsed args
44 | let parsedResult = ArgParser.parseOrExit(passedArgs)
45 | // Get the username of the logged in user
46 | let loggedInUser = loggedInUser()
47 | // If verbose mode is enabled
48 | if parsedResult.verbose {
49 | // Progress log
50 | NSLog("\(#function.components(separatedBy: "(")[0]) - verbose enabled - arguments: \(parsedResult)")
51 | }
52 | // If rebrand has been passed
53 | if parsedResult.rebrand != "" {
54 | // Rebrand Notifier apps
55 | changeIcons(brandingImage: parsedResult.rebrand, loggedInUser: loggedInUser, parsedResult: parsedResult)
56 | // If we're not rebranding and no user is logged in, exit
57 | } else if loggedInUser == "" {
58 | // Post message
59 | postToNSLogAndStdOut(logLevel: "INFO", logMessage: "No user logged in, exiting...",
60 | functionName: #function.components(separatedBy: "(")[0], parsedResult: parsedResult)
61 | // Exit
62 | exit(0)
63 | // If we have no value passed to --type
64 | } else if parsedResult.type == "" {
65 | // Show help
66 | _ = ArgParser.parseOrExit(["--help"])
67 | // Exit
68 | exit(1)
69 | }
70 | // Exit if Notification Center isn't running for the user
71 | isNotificationCenterRunning(parsedResult: parsedResult)
72 | // Check that we have all the needed arguments and that they are valid options, exiting if not
73 | checkArgs(parsedResult: parsedResult)
74 | // If verbose mode is enabled
75 | if parsedResult.verbose {
76 | // Progress log
77 | NSLog("\(#function.components(separatedBy: "(")[0]) - type - \(parsedResult.type)")
78 | }
79 | // Var declaration
80 | var notifierPath = String()
81 | // If we're looking for the alert app
82 | if parsedResult.type == "alert" {
83 | // Get the alert apps path
84 | notifierPath = GlobalVariables.alertAppPath + "/Contents/MacOS/Notifier - Alerts"
85 | // If we're looking for the notifications app
86 | } else {
87 | // Get the alert apps path
88 | notifierPath = GlobalVariables.bannerAppPath + "/Contents/MacOS/Notifier - Notifications"
89 | }
90 | // If --remove all has been passed
91 | if parsedResult.remove.lowercased() == "all" {
92 | // Initialize a rootElements object
93 | var rootElements = RootElements(removeOption: "all")
94 | // Initialize a messageContent object
95 | let messageContent = MessageContent()
96 | // If verbose mode is enabled
97 | if parsedResult.verbose {
98 | // Set verboseMode
99 | rootElements.verboseMode = "enabled"
100 | }
101 | // Create the JSON to pass to the notifying app
102 | let commandJSON = createJSON(messageContent: messageContent, parsedResult: parsedResult,
103 | rootElements: rootElements)
104 | // Pass commandJSON to the relevant app, exiting afterwards
105 | passToApp(commandJSON: commandJSON, loggedInUser: loggedInUser, notifierPath: notifierPath,
106 | parsedResult: parsedResult)
107 | } else {
108 | // Format the args as needed
109 | formatArgs(loggedInUser: loggedInUser, notifierPath: notifierPath, parsedResult: parsedResult)
110 | }
111 | }
112 | }
113 |
114 | // Check that we have all the needed arguments and that they are valid options, exiting if not
115 | func checkArgs(parsedResult: ArgParser) {
116 | // If an invalid value for --type has been passed
117 | if parsedResult.type.lowercased() != "alert" && parsedResult.type.lowercased() != "banner" {
118 | // Post message
119 | postToNSLogAndStdOut(logLevel: "ERROR", logMessage: "incorrect argument passed to --type, exiting...\n",
120 | functionName: #function.components(separatedBy: "(")[0], parsedResult: parsedResult)
121 | // Show help
122 | _ = ArgParser.parseOrExit(["--help"])
123 | // Exit
124 | exit(1)
125 | }
126 | // If an invalid value for --remove has been passed
127 | if parsedResult.remove != "" && parsedResult.remove.lowercased() != "all" && parsedResult.remove.lowercased()
128 | != "prior" {
129 | // Post message
130 | postToNSLogAndStdOut(logLevel: "ERROR", logMessage: "incorrect argument passed to --remove, exiting...\n",
131 | functionName: #function.components(separatedBy: "(")[0], parsedResult: parsedResult)
132 | // Show help
133 | _ = ArgParser.parseOrExit(["--help"])
134 | // Exit
135 | exit(1)
136 | }
137 | // If we've got here and no --message has been passed and we're not removing
138 | if parsedResult.remove == "" && parsedResult.message == "" {
139 | // Post message
140 | postToNSLogAndStdOut(logLevel: "ERROR", logMessage: """
141 | missing value for --message and we're neither rebranding nor removing, exiting...\n
142 | """, functionName: #function.components(separatedBy: "(")[0], parsedResult: parsedResult)
143 | // Show help
144 | _ = ArgParser.parseOrExit(["--help"])
145 | // Exit
146 | exit(1)
147 | }
148 | }
149 |
150 | // Format the args as needed
151 | func formatArgs(loggedInUser: String, notifierPath: String, parsedResult: ArgParser) {
152 | // Initialize a messageContent object
153 | var messageContent = MessageContent()
154 | // Initialize a rootElements object
155 | var rootElements = RootElements()
156 | // If verbose mode is enabled
157 | if parsedResult.verbose {
158 | // Set verboseMode
159 | rootElements.verboseMode = "enabled"
160 | }
161 | // Set the message to the body of the notification as not removing all, we have to have this
162 | messageContent.messageBody = setNotificationBody(parsedResult: parsedResult)
163 | // If we've been passed a messageaction
164 | if parsedResult.messageaction != "" {
165 | // Set messageAction
166 | messageContent.messageAction = parseAction(actionString: parsedResult.messageaction,
167 | parsedResult: parsedResult)
168 | }
169 | // If we've been passed a sound
170 | if parsedResult.sound != "" {
171 | // Set messageSound
172 | messageContent.messageSound = setNotificationSound(parsedResult: parsedResult)
173 | }
174 | // If we've been passed a subtitle
175 | if parsedResult.subtitle != "" {
176 | // Set messageSubtitle
177 | messageContent.messageSubtitle = setNotificationSubtitle(parsedResult: parsedResult)
178 | }
179 | // If we've been passed a title
180 | if parsedResult.title != "" {
181 | // Set messageTitle
182 | messageContent.messageTitle = setNotificationTitle(parsedResult: parsedResult)
183 | }
184 | // If we're to remove a prior posted notification
185 | if parsedResult.remove.lowercased() == "prior" {
186 | // Set removeOption
187 | rootElements.removeOption = "prior"
188 | // If verbose mode is enabled
189 | if parsedResult.verbose {
190 | // Progress log
191 | NSLog("\(#function.components(separatedBy: "(")[0]) - removeOption: \(rootElements.removeOption!))")
192 | }
193 | }
194 | // If we've been passed a messagebutton, and messagebuttonaction
195 | if parsedResult.messagebutton != "" {
196 | // Set messageButton and messagebuttonaction
197 | messageContent.messageButton = setNotificationMessageButton(parsedResult: parsedResult)
198 | // If we've been passed a messagebuttonaction, only set if a messagebutton was passed too
199 | if parsedResult.messagebuttonaction != "" {
200 | // Set messageButtonAction
201 | messageContent.messageButtonAction = parseAction(actionString:
202 | parsedResult.messagebuttonaction,
203 | parsedResult: parsedResult)
204 | }
205 | }
206 | // Create the JSON to pass to the notifying app
207 | let commandJSON = createJSON(messageContent: messageContent, parsedResult: parsedResult,
208 | rootElements: rootElements)
209 | // Pass commandJSON to the relevant app, exiting afterwards
210 | passToApp(commandJSON: commandJSON, loggedInUser: loggedInUser, notifierPath: notifierPath,
211 | parsedResult: parsedResult)
212 | }
213 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
--------------------------------------------------------------------------------
/Notifier/Notifier - Alerts/UserNotifications.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UserNotifications.swift
3 | // Notifiers - Alerts
4 | //
5 | // Copyright © 2024 dataJAR Ltd. All rights reserved.
6 | //
7 |
8 | // Imports
9 | import UserNotifications
10 |
11 | // Returns the notifications body
12 | func getNotificationBody(messageContent: MessageContent, rootElements: RootElements) -> String {
13 | // If verbose mode is enabled
14 | if rootElements.verboseMode != nil {
15 | // Progress log
16 | NSLog("\(#function.components(separatedBy: "(")[0]) - messageBody: \(messageContent.messageBody!)")
17 | }
18 | // Return messageBody, forcing as this is set unless we remove all.. and if we aren we won't get here
19 | return messageContent.messageBody!
20 | }
21 |
22 | // Returns the notifications body's action
23 | func getNotificationBodyAction(messageContent: MessageContent, rootElements: RootElements) -> [AnyHashable: Any] {
24 | // Var declaration
25 | var messageAction = [AnyHashable: Any]()
26 | // Add taskPath from messagAction to messageAction
27 | messageAction["taskPath"] = messageContent.messageAction?[0].taskPath
28 | // Add taskArguments from messageAction
29 | messageAction["taskArguments"] = messageContent.messageAction?[0].taskArguments
30 | // If verbose mode is enabled
31 | if rootElements.verboseMode != nil {
32 | // Progress log
33 | NSLog("""
34 | \(#function.components(separatedBy: "(")[0]) - messageAction - taskPath: \
35 | \(messageAction["taskPath"] ?? ""), taskArguments: \(messageAction["taskArguments"] ?? [])
36 | """)
37 | }
38 | // Return messageAction
39 | return messageAction
40 | }
41 |
42 | // Returns the notifications sound
43 | func getNotificationSound(messageContent: MessageContent, rootElements: RootElements) -> UNNotificationSound {
44 | // Var declaration
45 | var tempSound = UNNotificationSound.default
46 | // If we're not using macOS's default sound
47 | if messageContent.messageSound?.lowercased() != "default" {
48 | // Set the notifications sound
49 | tempSound = UNNotificationSound(named: UNNotificationSoundName(rawValue: messageContent.messageSound ?? ""))
50 | }
51 | // If verbose mode is enabled
52 | if rootElements.verboseMode != nil {
53 | // Progress log
54 | NSLog("\(#function.components(separatedBy: "(")[0]) - messageSound - set to: \(tempSound)")
55 | }
56 | // Return tempSound
57 | return tempSound
58 | }
59 |
60 | // Returns the notifications subtitle
61 | func getNotificationSubtitle(messageContent: MessageContent, rootElements: RootElements) -> String {
62 | // If verbose mode is enabled
63 | if rootElements.verboseMode != nil {
64 | // Progress log
65 | NSLog("\(#function.components(separatedBy: "(")[0]) - messageBody: \(messageContent.messageSubtitle ?? "")")
66 | }
67 | // Return messageSubtitle
68 | return messageContent.messageSubtitle ?? ""
69 | }
70 |
71 | // Returns the notifications title
72 | func getNotificationTitle(messageContent: MessageContent, rootElements: RootElements) -> String {
73 | // If verbose mode is enabled
74 | if rootElements.verboseMode != nil {
75 | // Progress log
76 | NSLog("\(#function.components(separatedBy: "(")[0]) - messageBody: \(messageContent.messageTitle ?? "")")
77 | }
78 | // Return messageTitle
79 | return messageContent.messageTitle ?? ""
80 | }
81 |
82 | // Handles when a notification is interacted with
83 | func handleNotification(forResponse response: UNNotificationResponse) {
84 | // Retrieve userInfo from the response object
85 | let userInfo = response.notification.request.content.userInfo
86 | // If verboseMode is set
87 | if userInfo["verboseMode"] != nil {
88 | // Progress log
89 | NSLog("\(#function.components(separatedBy: "(")[0]) - message - interacted: \(userInfo)")
90 | }
91 | // Triggered when the notification message is clicked
92 | if response.actionIdentifier == "com.apple.UNNotificationDefaultActionIdentifier" {
93 | // If verbose mode is set
94 | if userInfo["verboseMode"] != nil {
95 | // Progress log
96 | NSLog("\(#function.components(separatedBy: "(")[0]) - message - clicked")
97 | }
98 | // Performs any actions set when the user clicks the message
99 | processNotificationActions(userInfoKey: "messageAction", userInfo: userInfo)
100 | // If the notification was dismissed
101 | } else if response.actionIdentifier == "com.apple.UNNotificationDismissActionIdentifier" {
102 | // If verbose mode is set
103 | if userInfo["verboseMode"] != nil {
104 | // Progress log
105 | NSLog("\(#function.components(separatedBy: "(")[0]) - message - message was dismissed")
106 | }
107 | // If the messageButton was clicked
108 | } else {
109 | // If verbose mode is set
110 | if userInfo["verboseMode"] != nil {
111 | // Progress log
112 | NSLog("""
113 | \(#function.components(separatedBy: "(")[0]) - message button - \
114 | clicked - userInfo \(String(describing: userInfo))
115 | """)
116 | }
117 | // Performs any actions set when the user clicks the messagebutton
118 | processNotificationActions(userInfoKey: "messageButtonAction", userInfo: userInfo)
119 | }
120 | // If verbose mode is set
121 | if userInfo["verboseMode"] != nil {
122 | // Progress log
123 | NSLog("\(#function.components(separatedBy: "(")[0]) - message - removing notification")
124 | }
125 | // Remove the delivered notification
126 | UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers:
127 | [response.notification.request.identifier])
128 | // If verbose mode is set
129 | if userInfo["verboseMode"] != nil {
130 | // Progress log
131 | NSLog("\(#function.components(separatedBy: "(")[0]) - message - removing notification - done")
132 | }
133 | // Exit
134 | exit(0)
135 | }
136 |
137 | // Adds messageButton (always needed) and messageButtonAction (when defined)
138 | func processMessageButton(notificationCenter: UNUserNotificationCenter, messageContent: MessageContent,
139 | rootElements: RootElements) ->
140 | ([AnyHashable: Any], UNNotificationCategory) {
141 | // Var declaration
142 | var tempCategory = UNNotificationCategory(identifier: "alert", actions: [], intentIdentifiers: [],
143 | options: .customDismissAction)
144 | var messageButtonAction = [AnyHashable: Any]()
145 | // If we have a value for messageButton passed
146 | if messageContent.messageButton != nil {
147 | // Create an action object
148 | let notificationAction = UNNotificationAction(identifier: "messagebutton",
149 | title: messageContent.messageButton ?? "",
150 | options: [])
151 | // Amend tempCategory
152 | tempCategory = UNNotificationCategory(identifier: "alert", actions: [notificationAction],
153 | intentIdentifiers: [],
154 | options: .customDismissAction)
155 | // If verbose mode is enabled
156 | if rootElements.verboseMode != nil {
157 | // Progress log
158 | NSLog("\(#function.components(separatedBy: "(")[0]) - messagebutton processed")
159 | }
160 | // If we have a values for messageButton and messageButtonAction passed
161 | if messageContent.messageButtonAction != nil {
162 | // Add taskPath from messagAction to messageButtonAction
163 | messageButtonAction["taskPath"] = messageContent.messageButtonAction?[0].taskPath
164 | // Add taskArguments from messageButtonAction
165 | messageButtonAction["taskArguments"] =
166 | messageContent.messageButtonAction?[0].taskArguments
167 | // If verbose mode is enabled
168 | if rootElements.verboseMode != nil {
169 | // Progress log
170 | NSLog("""
171 | \(#function.components(separatedBy: "(")[0]) - messageButtonAction - taskPath: \
172 | \(messageButtonAction["taskPath"] ?? ""),
173 | taskArguments: \(messageButtonAction["taskArguments"] ?? [])
174 | """)
175 | }
176 | // Return tempCategory and tempUserInfo
177 | return (messageButtonAction, tempCategory)
178 | }
179 | // If we don't have a value for messageButton
180 | } else {
181 | // If verbose mode is enabled
182 | if rootElements.verboseMode != nil {
183 | // Progress log
184 | NSLog("\(#function.components(separatedBy: "(")[0]) - no messagebutton defined")
185 | }
186 | }
187 | // Return empty userInfo for messageButtonAction and tempCategory
188 | return ([:], tempCategory)
189 | }
190 |
191 | // Post the notification
192 | func postNotification(notificationCenter: UNUserNotificationCenter, notificationContent: UNMutableNotificationContent,
193 | messageContent: MessageContent, passedBase64: String, rootElements: RootElements) {
194 | // If we're in verbose mode
195 | if rootElements.verboseMode != nil {
196 | // Progress log
197 | NSLog("""
198 | \(#function.components(separatedBy: "(")[0]) - notification \
199 | request - notificationContent - \(notificationContent).
200 | """)
201 | }
202 | // Create the request object
203 | let notificationRequest = UNNotificationRequest(identifier: passedBase64, content: notificationContent,
204 | trigger: nil)
205 | // Post the notification
206 | notificationCenter.add(notificationRequest)
207 | // If we're in verbose mode
208 | if rootElements.verboseMode != nil {
209 | // Progress log
210 | NSLog("\(#function.components(separatedBy: "(")[0]) - notification delivered")
211 | }
212 | // Sleep, so we don't exit before the notification has been delivered
213 | sleep(1)
214 | // Exit
215 | exit(0)
216 | }
217 |
218 | // Process actions when interacted
219 | func processNotificationActions(userInfoKey: String, userInfo: [AnyHashable: Any]) {
220 | // Var declaration
221 | var messageActionDict = [String: Any]()
222 | // If we have a userInfoKey in the notifications userinfo
223 | if userInfo[userInfoKey] != nil {
224 | // If verbose mode is set
225 | if userInfo["verboseMode"] != nil {
226 | // Progress log
227 | NSLog("\(#function.components(separatedBy: "(")[0]) - \(userInfoKey) - \(userInfo[userInfoKey] ?? [])")
228 | }
229 | // Convert userInfo[userInfoKey] to a dict
230 | messageActionDict = userInfo[userInfoKey] as? [String: Any] ?? [:]
231 | // If we have logout as taskPath
232 | if messageActionDict["taskPath"] as? String == "logout" {
233 | // If verbose mode is set
234 | if userInfo["verboseMode"] != nil {
235 | // Progress log
236 | NSLog("\(#function.components(separatedBy: "(")[0]) - \(userInfoKey) - logout")
237 | }
238 | // Prompt to logout
239 | gracefulLogout(userInfo: userInfo)
240 | // If we have an action thaty's not logout
241 | } else {
242 | // If verbose mode is set
243 | if userInfo["verboseMode"] != nil {
244 | // Progress log
245 | NSLog("\(#function.components(separatedBy: "(")[0]) - \(userInfoKey) - \(userInfo[userInfoKey] ?? [])")
246 | }
247 | // Run the task, returning boolean
248 | let (taskOutput, taskStatus) = runTask(taskPath: messageActionDict["taskPath"] as? String ?? "",
249 | taskArguments: messageActionDict["taskArguments"]
250 | as? [String] ?? [], userInfo: userInfo)
251 | // If verbose mode is set
252 | if userInfo["verboseMode"] != nil {
253 | // If the task completed successfully
254 | if taskStatus {
255 | // Progress log
256 | NSLog("""
257 | "\(messageActionDict["taskPath"] ?? "") \(messageActionDict["taskArguments"] ?? [])"
258 | completed successfully, returned output: \(taskOutput)
259 | """)
260 | // If task failed to run
261 | } else {
262 | // Post error
263 | postToNSLogAndStdOut(logLevel: "ERROR", logMessage:
264 | """
265 | Running: \(messageActionDict["taskPath"] ?? "")
266 | \(messageActionDict["taskArguments"] ?? []) failed with \(taskOutput).
267 | """, functionName: #function.components(separatedBy: "(")[0],
268 | verboseMode: "enabled")
269 | }
270 | }
271 | }
272 | }
273 | }
274 |
275 | // If we're to remove a specific prior posted notification
276 | func removePriorNotification(notificationCenter: UNUserNotificationCenter, messageContent: MessageContent,
277 | passedBase64: String, rootElements: RootElements) {
278 | // If we're in verbose mode
279 | if rootElements.verboseMode != nil {
280 | // Progress log
281 | NSLog("\(#function.components(separatedBy: "(")[0]) - remove prior - passedBase64 - [\(passedBase64)]")
282 | }
283 | // Remove any prior notifications with the same identifier as ncContentbase64
284 | notificationCenter.removeDeliveredNotifications(withIdentifiers: [passedBase64])
285 | // If we're in verbose mode
286 | if rootElements.verboseMode != nil {
287 | // Progress log
288 | NSLog("\(#function.components(separatedBy: "(")[0]) - remove prior - done")
289 | }
290 | // Sleep, so we don't exit before notification(s) have been removed
291 | sleep(1)
292 | // Exit
293 | exit(0)
294 | }
295 |
296 | // If we're to remove all prior posted notifications
297 | func removeAllPriorNotifications(notificationCenter: UNUserNotificationCenter, messageContent: MessageContent,
298 | rootElements: RootElements) {
299 | // If we're in verbose mode
300 | if rootElements.verboseMode != nil {
301 | // Verbose message
302 | NSLog("\(#function.components(separatedBy: "(")[0]) - remove all")
303 | }
304 | // Remove all delivered notifications
305 | notificationCenter.removeAllDeliveredNotifications()
306 | // If we're in verbose mode
307 | if rootElements.verboseMode != nil {
308 | // Progress log
309 | NSLog("\(#function.components(separatedBy: "(")[0]) - remove all - done")
310 | }
311 | // Sleep, so we don't exit before notification(s) have been removed
312 | sleep(1)
313 | // Exit
314 | exit(0)
315 | }
316 |
317 | // Request authorisation
318 | func requestAuthorisation(verboseMode: String) {
319 | // Check authorization status with the UNUserNotificationCenter object
320 | UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound]) { (granted, _) in
321 | if !granted {
322 | // Post error
323 | postToNSLogAndStdOut(logLevel: "ERROR", logMessage: """
324 | Authorisation not granted to post notifications, either manually approve \
325 | notifications for this application or deploy a Notification PPPCP to this Mac, and \
326 | try posting the message again...
327 | """, functionName: #function.components(separatedBy: "(")[0],
328 | verboseMode: "verboseMode")
329 | // Exit
330 | exit(1)
331 | }
332 | }
333 | }
334 |
--------------------------------------------------------------------------------
/Notifier/Notifier - Notifications/UserNotifications.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UserNotifications.swift
3 | // Notifiers - Notifications
4 | //
5 | // Copyright © 2024 dataJAR Ltd. All rights reserved.
6 | //
7 |
8 | // Imports
9 | import UserNotifications
10 |
11 | // Returns the notifications body
12 | func getNotificationBody(messageContent: MessageContent, rootElements: RootElements) -> String {
13 | // If verbose mode is enabled
14 | if rootElements.verboseMode != nil {
15 | // Progress log
16 | NSLog("\(#function.components(separatedBy: "(")[0]) - messageBody: \(messageContent.messageBody!)")
17 | }
18 | // Return messageBody, forcing as this is set unless we remove all.. and if we aren we won't get here
19 | return messageContent.messageBody!
20 | }
21 |
22 | // Returns the notifications body's action
23 | func getNotificationBodyAction(messageContent: MessageContent, rootElements: RootElements) -> [AnyHashable: Any] {
24 | // Var declaration
25 | var messageAction = [AnyHashable: Any]()
26 | // Add taskPath from messagAction to messageAction
27 | messageAction["taskPath"] = messageContent.messageAction?[0].taskPath
28 | // Add taskArguments from messageAction
29 | messageAction["taskArguments"] = messageContent.messageAction?[0].taskArguments
30 | // If verbose mode is enabled
31 | if rootElements.verboseMode != nil {
32 | // Progress log
33 | NSLog("""
34 | \(#function.components(separatedBy: "(")[0]) - messageAction - taskPath: \
35 | \(messageAction["taskPath"] ?? ""), taskArguments: \(messageAction["taskArguments"] ?? [])
36 | """)
37 | }
38 | // Return messageAction
39 | return messageAction
40 | }
41 |
42 | // Returns the notifications sound
43 | func getNotificationSound(messageContent: MessageContent, rootElements: RootElements) -> UNNotificationSound {
44 | // Var declaration
45 | var tempSound = UNNotificationSound.default
46 | // If we're not using macOS's default sound
47 | if messageContent.messageSound?.lowercased() != "default" {
48 | // Set the notifications sound
49 | tempSound = UNNotificationSound(named: UNNotificationSoundName(rawValue: messageContent.messageSound ?? ""))
50 | }
51 | // If verbose mode is enabled
52 | if rootElements.verboseMode != nil {
53 | // Progress log
54 | NSLog("\(#function.components(separatedBy: "(")[0]) - messageSound - set to: \(tempSound)")
55 | }
56 | // Return tempSound
57 | return tempSound
58 | }
59 |
60 | // Returns the notifications subtitle
61 | func getNotificationSubtitle(messageContent: MessageContent, rootElements: RootElements) -> String {
62 | // If verbose mode is enabled
63 | if rootElements.verboseMode != nil {
64 | // Progress log
65 | NSLog("\(#function.components(separatedBy: "(")[0]) - messageBody: \(messageContent.messageSubtitle ?? "")")
66 | }
67 | // Return messageSubtitle
68 | return messageContent.messageSubtitle ?? ""
69 | }
70 |
71 | // Returns the notifications title
72 | func getNotificationTitle(messageContent: MessageContent, rootElements: RootElements) -> String {
73 | // If verbose mode is enabled
74 | if rootElements.verboseMode != nil {
75 | // Progress log
76 | NSLog("\(#function.components(separatedBy: "(")[0]) - messageBody: \(messageContent.messageTitle ?? "")")
77 | }
78 | // Return messageTitle
79 | return messageContent.messageTitle ?? ""
80 | }
81 |
82 | // Handles when a notification is interacted with
83 | func handleNotification(forResponse response: UNNotificationResponse) {
84 | // Retrieve userInfo from the response object
85 | let userInfo = response.notification.request.content.userInfo
86 | // If verboseMode is set
87 | if userInfo["verboseMode"] != nil {
88 | // Progress log
89 | NSLog("\(#function.components(separatedBy: "(")[0]) - message - interacted: \(userInfo)")
90 | }
91 | // Triggered when the notification message is clicked
92 | if response.actionIdentifier == "com.apple.UNNotificationDefaultActionIdentifier" {
93 | // If verbose mode is set
94 | if userInfo["verboseMode"] != nil {
95 | // Progress log
96 | NSLog("\(#function.components(separatedBy: "(")[0]) - message - clicked")
97 | }
98 | // Performs any actions set when the user clicks the message
99 | processNotificationActions(userInfoKey: "messageAction", userInfo: userInfo)
100 | // If the notification was dismissed
101 | } else if response.actionIdentifier == "com.apple.UNNotificationDismissActionIdentifier" {
102 | // If verbose mode is set
103 | if userInfo["verboseMode"] != nil {
104 | // Progress log
105 | NSLog("\(#function.components(separatedBy: "(")[0]) - message - message was dismissed")
106 | }
107 | // If the messageButton was clicked
108 | } else {
109 | // If verbose mode is set
110 | if userInfo["verboseMode"] != nil {
111 | // Progress log
112 | NSLog("""
113 | \(#function.components(separatedBy: "(")[0]) - message button - \
114 | clicked - userInfo \(String(describing: userInfo))
115 | """)
116 | }
117 | // Performs any actions set when the user clicks the messagebutton
118 | processNotificationActions(userInfoKey: "messageButtonAction", userInfo: userInfo)
119 | }
120 | // If verbose mode is set
121 | if userInfo["verboseMode"] != nil {
122 | // Progress log
123 | NSLog("\(#function.components(separatedBy: "(")[0]) - message - removing notification")
124 | }
125 | // Remove the delivered notification
126 | UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers:
127 | [response.notification.request.identifier])
128 | // If verbose mode is set
129 | if userInfo["verboseMode"] != nil {
130 | // Progress log
131 | NSLog("\(#function.components(separatedBy: "(")[0]) - message - removing notification - done")
132 | }
133 | // Exit
134 | exit(0)
135 | }
136 |
137 | // Adds messageButton (always needed) and messageButtonAction (when defined)
138 | func processMessageButton(notificationCenter: UNUserNotificationCenter, messageContent: MessageContent,
139 | rootElements: RootElements) ->
140 | ([AnyHashable: Any], UNNotificationCategory) {
141 | // Var declaration
142 | var tempCategory = UNNotificationCategory(identifier: "banner", actions: [], intentIdentifiers: [],
143 | options: .customDismissAction)
144 | var messageButtonAction = [AnyHashable: Any]()
145 | // If we have a value for messageButton passed
146 | if messageContent.messageButton != nil {
147 | // Create an action object
148 | let notificationAction = UNNotificationAction(identifier: "messagebutton",
149 | title: messageContent.messageButton ?? "",
150 | options: [])
151 | // Amend tempCategory
152 | tempCategory = UNNotificationCategory(identifier: "banner", actions: [notificationAction],
153 | intentIdentifiers: [],
154 | options: .customDismissAction)
155 | // If verbose mode is enabled
156 | if rootElements.verboseMode != nil {
157 | // Progress log
158 | NSLog("\(#function.components(separatedBy: "(")[0]) - messagebutton processed")
159 | }
160 | // If we have a values for messageButton and messageButtonAction passed
161 | if messageContent.messageButtonAction != nil {
162 | // Add taskPath from messagAction to messageButtonAction
163 | messageButtonAction["taskPath"] = messageContent.messageButtonAction?[0].taskPath
164 | // Add taskArguments from messageButtonAction
165 | messageButtonAction["taskArguments"] =
166 | messageContent.messageButtonAction?[0].taskArguments
167 | // If verbose mode is enabled
168 | if rootElements.verboseMode != nil {
169 | // Progress log
170 | NSLog("""
171 | \(#function.components(separatedBy: "(")[0]) - messageButtonAction - taskPath: \
172 | \(messageButtonAction["taskPath"] ?? ""),
173 | taskArguments: \(messageButtonAction["taskArguments"] ?? [])
174 | """)
175 | }
176 | // Return tempCategory and tempUserInfo
177 | return (messageButtonAction, tempCategory)
178 | }
179 | // If we don't have a value for messageButton
180 | } else {
181 | // If verbose mode is enabled
182 | if rootElements.verboseMode != nil {
183 | // Progress log
184 | NSLog("\(#function.components(separatedBy: "(")[0]) - no messagebutton defined")
185 | }
186 | }
187 | // Return empty userInfo for messageButtonAction and tempCategory
188 | return ([:], tempCategory)
189 | }
190 |
191 | // Post the notification
192 | func postNotification(notificationCenter: UNUserNotificationCenter, notificationContent: UNMutableNotificationContent,
193 | messageContent: MessageContent, passedBase64: String, rootElements: RootElements) {
194 | // If we're in verbose mode
195 | if rootElements.verboseMode != nil {
196 | // Progress log
197 | NSLog("""
198 | \(#function.components(separatedBy: "(")[0]) - notification \
199 | request - notificationContent - \(notificationContent).
200 | """)
201 | }
202 | // Create the request object
203 | let notificationRequest = UNNotificationRequest(identifier: passedBase64, content: notificationContent,
204 | trigger: nil)
205 | // Post the notification
206 | notificationCenter.add(notificationRequest)
207 | // If we're in verbose mode
208 | if rootElements.verboseMode != nil {
209 | // Progress log
210 | NSLog("\(#function.components(separatedBy: "(")[0]) - notification delivered")
211 | }
212 | // Sleep, so we don't exit before the notification has been delivered
213 | sleep(1)
214 | // Exit
215 | exit(0)
216 | }
217 |
218 | // Process actions when interacted
219 | func processNotificationActions(userInfoKey: String, userInfo: [AnyHashable: Any]) {
220 | // Var declaration
221 | var messageActionDict = [String: Any]()
222 | // If we have a userInfoKey in the notifications userinfo
223 | if userInfo[userInfoKey] != nil {
224 | // If verbose mode is set
225 | if userInfo["verboseMode"] != nil {
226 | // Progress log
227 | NSLog("\(#function.components(separatedBy: "(")[0]) - \(userInfoKey) - \(userInfo[userInfoKey] ?? [])")
228 | }
229 | // Convert userInfo[userInfoKey] to a dict
230 | messageActionDict = userInfo[userInfoKey] as? [String: Any] ?? [:]
231 | // If we have logout as taskPath
232 | if messageActionDict["taskPath"] as? String == "logout" {
233 | // If verbose mode is set
234 | if userInfo["verboseMode"] != nil {
235 | // Progress log
236 | NSLog("\(#function.components(separatedBy: "(")[0]) - \(userInfoKey) - logout")
237 | }
238 | // Prompt to logout
239 | gracefulLogout(userInfo: userInfo)
240 | // If we have an action thaty's not logout
241 | } else {
242 | // If verbose mode is set
243 | if userInfo["verboseMode"] != nil {
244 | // Progress log
245 | NSLog("\(#function.components(separatedBy: "(")[0]) - \(userInfoKey) - \(userInfo[userInfoKey] ?? [])")
246 | }
247 | // Run the task, returning boolean
248 | let (taskOutput, taskStatus) = runTask(taskPath: messageActionDict["taskPath"] as? String ?? "",
249 | taskArguments: messageActionDict["taskArguments"]
250 | as? [String] ?? [], userInfo: userInfo)
251 | // If verbose mode is set
252 | if userInfo["verboseMode"] != nil {
253 | // If the task completed successfully
254 | if taskStatus {
255 | // Progress log
256 | NSLog("""
257 | "\(messageActionDict["taskPath"] ?? "") \(messageActionDict["taskArguments"] ?? [])"
258 | completed successfully, returned output: \(taskOutput)
259 | """)
260 | // If task failed to run
261 | } else {
262 | // Post error
263 | postToNSLogAndStdOut(logLevel: "ERROR", logMessage:
264 | """
265 | Running: \(messageActionDict["taskPath"] ?? "")
266 | \(messageActionDict["taskArguments"] ?? []) failed with \(taskOutput).
267 | """, functionName: #function.components(separatedBy: "(")[0],
268 | verboseMode: "enabled")
269 | }
270 | }
271 | }
272 | }
273 | }
274 |
275 | // If we're to remove a specific prior posted notification
276 | func removePriorNotification(notificationCenter: UNUserNotificationCenter, messageContent: MessageContent,
277 | passedBase64: String, rootElements: RootElements) {
278 | // If we're in verbose mode
279 | if rootElements.verboseMode != nil {
280 | // Progress log
281 | NSLog("\(#function.components(separatedBy: "(")[0]) - remove prior - passedBase64 - [\(passedBase64)]")
282 | }
283 | // Remove any prior notifications with the same identifier as ncContentbase64
284 | notificationCenter.removeDeliveredNotifications(withIdentifiers: [passedBase64])
285 | // If we're in verbose mode
286 | if rootElements.verboseMode != nil {
287 | // Progress log
288 | NSLog("\(#function.components(separatedBy: "(")[0]) - remove prior - done")
289 | }
290 | // Sleep, so we don't exit before notification(s) have been removed
291 | sleep(1)
292 | // Exit
293 | exit(0)
294 | }
295 |
296 | // If we're to remove all prior posted notifications
297 | func removeAllPriorNotifications(notificationCenter: UNUserNotificationCenter, messageContent: MessageContent,
298 | rootElements: RootElements) {
299 | // If we're in verbose mode
300 | if rootElements.verboseMode != nil {
301 | // Verbose message
302 | NSLog("\(#function.components(separatedBy: "(")[0]) - remove all")
303 | }
304 | // Remove all delivered notifications
305 | notificationCenter.removeAllDeliveredNotifications()
306 | // If we're in verbose mode
307 | if rootElements.verboseMode != nil {
308 | // Progress log
309 | NSLog("\(#function.components(separatedBy: "(")[0]) - remove all - done")
310 | }
311 | // Sleep, so we don't exit before notification(s) have been removed
312 | sleep(1)
313 | // Exit
314 | exit(0)
315 | }
316 |
317 | // Request authorisation
318 | func requestAuthorisation(verboseMode: String) {
319 | // Check authorization status with the UNUserNotificationCenter object
320 | UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound]) { (granted, _) in
321 | if !granted {
322 | // Post error
323 | postToNSLogAndStdOut(logLevel: "ERROR", logMessage: """
324 | Authorisation not granted to post notifications, either manually approve \
325 | notifications for this application or deploy a Notification PPPCP to this Mac, and \
326 | try posting the message again...
327 | """, functionName: #function.components(separatedBy: "(")[0],
328 | verboseMode: "verboseMode")
329 | // Exit
330 | exit(1)
331 | }
332 | }
333 | }
334 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | Licensed under the Apache License, Version 2.0
3 | Copyright 2024 Jamf LTD
4 |
5 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
6 |
7 | Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
8 |
9 |
10 | # Notifier
11 |
12 |
13 | Notifier is a Swift app which can post alert or banner notifications on macOS 10.15+ clients.
14 |
15 | Notifications are delivered via the [UserNotifications Framework](https://developer.apple.com/documentation/usernotifications)
16 |
17 | # Usage
18 | ## Basic Usage
19 | ```
20 | OVERVIEW: Notifier 3.1: Posts alert or banner notifications.
21 |
22 | USAGE: --type --message
23 | --type --remove prior
24 | --type --remove all
25 | --rebrand
26 |
27 | OPTIONS:
28 | --type alert or banner - REQUIRED.
29 |
30 | --message The notifications message.
31 |
32 | --messageaction
33 | The action to be performed under the users account
34 | when the message is clicked.
35 |
36 | • Passing 'logout' will prompt the user to logout.
37 | • If passed a single item, this will be launched via:
38 | /usr/bin/open
39 | • More complex commands can be passed, but the 1st
40 | argument needs to be a binaries path.
41 |
42 | For example: "/usr/bin/open" will work, "open" will
43 | not.
44 |
45 | --messagebutton
46 | Adds a button to the message, with the label being
47 | what is passed.
48 |
49 | --messagebuttonaction
50 | The action to be performed under the users account
51 | when the optional message button is clicked.
52 |
53 | • Passing 'logout' will prompt the user to logout.
54 | • If passed a single item, this will be launched via:
55 | /usr/bin/open
56 | • More complex commands can be passed, but the 1st
57 | argument needs to be a binaries path.
58 |
59 | For example: "/usr/bin/open" will work, "open" will
60 | not.
61 |
62 | --rebrand Requires root privileges and that the calling process
63 | needs either Full Disk Access (10.15+) or at a
64 | minimum App Management (macOS 13+) permissions, as
65 | well as the notifying applications being given
66 | permission to post to Notification Center. Any of
67 | these permissions can be granted manually, but
68 | ideally via PPPCP's delivered via an MDM.
69 |
70 | If successful and someone is logged in, Notification
71 | Center is restarted.
72 |
73 | --remove "prior" or "all". If passing "prior", the full
74 | message will be required too. Including all passed
75 | flags.
76 |
77 | --sound The sound to play when notification is delivered.
78 | Pass "default" for the default macOS sound, else the
79 | name of a sound in /Library/Sounds or
80 | /System/Library/Sounds.
81 |
82 | If the sound cannot be found, macOS will use the
83 | "default" sound.
84 |
85 | --subtitle The notifications subtitle.
86 |
87 | --title The notifications title.
88 |
89 | --verbose Enables logging of actions. Check console for
90 | 'Notifier' messages.
91 |
92 | --help Show help information.
93 |
94 | ```
95 |
96 | ## Examples
97 | The below aim to give you an idea of what so the various notification options look like, across macOS versions as well as Light and Dark mode.
98 |
99 | As you can see, some of the behaviour and appearence is OS dependent.
100 |
101 | **Example 1** This example shows a basic banner notification.
102 |
103 | macOS 10.15.7 - Light Mode | macOS 14.3 - Dark mode
104 | :-------------------------:|:-------------------------:
105 |
|
106 |
107 | ```
108 | /Applications/Utilities/Notifier.app/Contents/MacOS/Notifier --type banner --message "message"
109 | ```
110 | ##
111 | **Example 2** This example shows a basic alert notification.
112 | macOS 10.15.7 - Light Mode | macOS 14.3 - Dark mode
113 | :-------------------------:|:-------------------------:
114 |
|
115 |
116 | ```
117 | /Applications/Utilities/Notifier.app/Contents/MacOS/Notifier --type alert --message "message" --messagebutton "Logout" --messagebuttonaction "Logout"
118 | ```
119 | ##
120 | **Example 3** This example shows both alert & banner notifications, (sleep used for the example gif, not needed in use but below for completeness sake).
121 | macOS 10.15.7 - Light Mode | macOS 14.3 - Dark mode
122 | :-------------------------:|:-------------------------:
123 |
|
124 |
125 | ```
126 | /Applications/Utilities/Notifier.app/Contents/MacOS/Notifier --type alert --message "Please Logout" --messagebutton "Logout" --messagebuttonaction "logout" --title "Logout";
127 | /bin/sleep 2;
128 | /Applications/Utilities/Notifier.app/Contents/MacOS/Notifier --type banner --message "👋" --title "Notification"
129 | ```
130 | ##
131 | **Example 4** This example shows selective remove of a delivered notificaition via `--remove prior`. Where applicable this would also remove notifications from within Notification Center itself.
132 | macOS 10.15.7 - Light Mode | macOS 14.3 - Dark mode
133 | :-------------------------:|:-------------------------:
134 |
|
135 |
136 | ```
137 | /Applications/Utilities/Notifier.app/Contents/MacOS/Notifier --type alert --message 'Look at me!!';
138 | /bin/sleep 2;
139 | /Applications/Utilities/Notifier.app/Contents/MacOS/Notifier --type alert --message "message" --messagebutton "Logout";
140 | /Applications/Utilities/Notifier.app/Contents/MacOS/Notifier --type alert --message 'Look at me!!' --remove prior
141 | ```
142 | ##
143 | **Example 5** This example shows removal of all alert notifications and also the differences of delivery of alert notifications across macOS versions, additionally this removes any prior delivered notificaions from Notification Center. Sleep used for the example gif, not needed in use but below for completeness sake.
144 |
145 | macOS 10.15.7 - Light Mode | macOS 14.3 - Dark mode
146 | :-------------------------:|:-------------------------:
147 |
|
148 |
149 | ```
150 | /Applications/Utilities/Notifier.app/Contents/MacOS/Notifier --type alert --message message;
151 | /Applications/Utilities/Notifier.app/Contents/MacOS/Notifier --type alert --message message1;
152 | /Applications/Utilities/Notifier.app/Contents/MacOS/Notifier --type alert --message message2;
153 | /Applications/Utilities/Notifier.app/Contents/MacOS/Notifier --type alert --message message3;
154 | /Applications/Utilities/Notifier.app/Contents/MacOS/Notifier --type alert --message message4;
155 | /Applications/Utilities/Notifier.app/Contents/MacOS/Notifier --type alert --message message5;
156 | /bin/sleep 5;
157 | /Applications/Utilities/Notifier.app/Contents/MacOS/Notifier --type alert --remove all
158 | ```
159 | ##
160 | **Example 6** This example shows Notifier 3.0+'s `--rebrand` argument in use, this allows for rebranding of Notifier without having to venture into Xcode etc.
161 | macOS 10.15.7 - Light Mode | macOS 14.3 - Dark mode
162 | :-------------------------:|:-------------------------:
163 |
|
164 |
165 | ```
166 | /Applications/Utilities/Notifier.app/Contents/MacOS/Notifier --type alert --message "message";
167 | /Applications/Utilities/Notifier.app/Contents/MacOS/Notifier --type banner --message "message";
168 | /bin/sleep 2;
169 | /usr/bin/sudo /Applications/Utilities/Notifier.app/Contents/MacOS/Notifier --rebrand /System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/ErasingIcon.icns;
170 | /bin/sleep 2;
171 | /usr/bin/sudo /Applications/Utilities/Notifier.app/Contents/MacOS/Notifier --type banner --message "message";
172 | /Applications/Utilities/Notifier.app/Contents/MacOS/Notifier --rebrand /System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/GenericFileServerIcon.icns;
173 | /bin/sleep 2;
174 | /usr/bin/sudo /Applications/Utilities/Notifier.app/Contents/MacOS/Notifier --type banner --message "message";
175 | /Applications/Utilities/Notifier.app/Contents/MacOS/Notifier --rebrand /Applications/Utilities/Notifier.app/Contents/Resources/AppIcon.icns;
176 | /bin/sleep 2;
177 | /Applications/Utilities/Notifier.app/Contents/MacOS/Notifier --type banner --message "message"
178 | ```
179 |
180 | # Deployment
181 |
182 | ## PPPC
183 | It's recommended that the below profile is recommended to be deployed before Notfier itself, to UAMDM devices.
184 | (https://github.com/dataJAR/Notifier/blob/master/profile/Allow%20Notifier%20Notifications.mobileconfig)
185 |
186 | This will allow Notifier to post Notifications without prompting the user to allow.
187 |
188 | Additionally, if you're looking to make use of the `--rebrand` flag the calling process needs either Full Disk Access/SystemPolicyAllFiles (10.15+) or at a
189 | minimum App Management/SystemPolicyAppBundles (macOS 13+) permissions, as well as the notifying applications being given permission to post to Notification Center.
190 |
191 | Any of these permissions can be granted manually, but ideally via PPPCP's delivered via an MDM.
192 |
193 | ## PKG
194 | PKG's will be supplied for every release, & can be found in the [releases](https://github.com/dataJAR/Notifier/releases) section.
195 |
196 | ## Flow
197 |
198 | The below is the advised deployment flow for Notifier.
199 |
200 | 1. If rebranding - deploy a PPPC for your management tool of choice, granting either Full Disk Access/SystemPolicyAllFiles (10.15+) or App Management/SystemPolicyAppBundles (macOS 13+).
201 | 2. Deploy the [Notications PpPC](https://github.com/dataJAR/Notifier/blob/master/profile/Allow%20Notifier%20Notifications.mobileconfig) to UAMDM devices
202 | 3. Deploy the latest [Notifier PKG](https://github.com/dataJAR/Notifier/releases)
203 | 4. If rebranding - deploy the image to use when rebranding.
204 | 5. if rebranding - deploy a script or payload free package etc which rebrands Notifier via your management tool. This needs to be down with admin/root privileges.
205 |
206 | # How it works
207 | The main Notifier.app parses arguments passed to it (via [Argument Parser](https://apple.github.io/swift-argument-parser/documentation/argumentparser/), and then posts the parsed argument to the two notifying applications included with the the /Contents/Helpers folder of Notifier.app:
208 |
209 | ```
210 | /Applications/Utilities/Notifier.app/Contents/Helpers/
211 | Notifier - Alerts.app
212 | Notifier - Notifications.app
213 | ```
214 | Additionally the Notifier.app also checks tha Notification Center is running, & runs the notifying apps in the user context.
215 |
216 | The notifying apps (/Applications/Utilities/Notifier.app/Contents/Helpers/Notifier - Alerts.app and /Applications/Utilities/Notifier.app/Contents/Helpers/Notifier - Notifications.app) both post notifications via the [UserNotifications Framework](https://developer.apple.com/documentation/usernotifications).
217 |
218 | # Miscellaneous
219 |
220 | ## Resetting Notifications Center
221 | The below _should_ reset Notifications Center, but please test & submit a PR with a better method as applicable.
222 |
223 | 1. `/bin/rm -rf $(getconf DARWIN_USER_DIR)/com.apple.notificationcenter/*`
224 | 2. Logout & then log back in, or:
225 | * For macOS 10.15+: `/usr/bin/killall "NotificationCenter"`
226 | 3. Test the Notifier once more
227 |
228 | # FAQs
229 |
230 | **Q1:** Why start at version 2?
231 |
232 | **A1:** At dataJAR, we already had a v1 "Notifier" app deployed.
233 | ##
234 | **Q2:** How can you post both banner & alert notifications?
235 |
236 | **A2:** See [How it works](#how-it-works)
237 | ##
238 | **Q3:** The users are being prompted to allow.. help!
239 |
240 | **A3:** This can be due to a few things:
241 |
242 | 1. Was the notifications profile installed? If not, install.
243 | 1. Is the device under UAMDM? If not, check with your MDM vendor on making the device UAMDM. Then try again & maybe reinstall the profile once under UAMDM.
244 | 1. Did you change the Bundle ID of either the Alert or Banner applications? If so you'll need to amend the notifications profile accordingly.
245 | ##
246 | **Q4:** Is that logo magen...
247 |
248 | **A4:** No, it's a shade of pink...
249 | ##
250 | **Q5:** Banner or Notifications?
251 |
252 | **A5:** The temporary notifications are referred to as "banner" notifications in Apple's documentation, but it seems that most folks call them "Notifications" with the persistent notifications being referred to as alerts by folks as well as within Apple's documentation.
253 | ##
254 | **Q6:** I'm seeing alerts when expecting banner notifications, & vice versa
255 |
256 | **A6:** Check the Notification settings within System Preferences/System Settings, it's possible that the incorrect option has been selected or set via a profile.
257 | ##
258 | **Q7:** --remove prior, didn't clear my last message.
259 |
260 | **A7:** Make sure you pass EXACTLY the same message to be cleared.
261 | ##
262 | **Q8:** I've rebranded, but the old icon is being shown..
263 |
264 | **A8:** Please reset Notification Center as per the [Resetting Notifications Center](#resetting-notifications-center), then try again.
265 | ##
266 | **Q9:** Does rebranding work when no one is logged in?
267 |
268 | **A9:** Yep, and no restart of Notification Center is needed.
269 | ##
270 | **Q10** Why are notifcations all of a sudden showing a prohibted sign across the icon?
271 |
272 | **A10** This is a [macOS issue](https://macmule.com/2021/10/28/notifications-showing-a-prohibitory-symbol-after-upgrading-macos-monterey/) that has goes back some years, to resolve either restart Notification Center or the Mac.
273 |
274 | # Alternatives
275 | The below projects can be used instead of Notifier & were very much instrumental in Notifiers creation
276 |
277 | ## Alert Notifications
278 | [Yo](https://github.com/sheagcraig/yo)
279 |
280 | ## Banner Notifications
281 | [Terminal Notifier](https://github.com/julienXX/terminal-notifier)
282 |
283 |
--------------------------------------------------------------------------------
/Notifier/Notifier/Functions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Functions.swift
3 | // Notifier
4 | //
5 | // Copyright © 2024 dataJAR Ltd. All rights reserved.
6 | //
7 |
8 | // Imports
9 | import Cocoa
10 | import SystemConfiguration
11 |
12 | // Changes the .app's icons restarting Notification Center if rebranding was successful
13 | func changeIcons(brandingImage: String, loggedInUser: String, parsedResult: ArgParser) {
14 | // Confirm we're root before proceeding
15 | rootCheck(parsedResult: parsedResult, passedArg: "--rebrand")
16 | // If verbose mode is enabled
17 | if parsedResult.verbose {
18 | // Progress log
19 | NSLog("\(#function.components(separatedBy: "(")[0]) - brandingImage: \(brandingImage)")
20 | }
21 | // Get the details of the file at brandingImage
22 | let imageData = getImageDetails(brandingImage: brandingImage, parsedResult: parsedResult)
23 | // For each application in brandingArray
24 | for notifierApp in [GlobalVariables.mainAppPath, GlobalVariables.alertAppPath, GlobalVariables.bannerAppPath] {
25 | // Rebrand each app starting ewith the Notifier.app, this way if this fails the rest are skipped
26 | updateIcon(brandingImage: brandingImage, imageData: imageData!, objectPath: notifierApp,
27 | parsedResult: parsedResult)
28 | }
29 | // If verbose mode is enabled
30 | if parsedResult.verbose {
31 | // Progress log
32 | NSLog("\(#function.components(separatedBy: "(")[0]) - Successfully rebranded Notifier")
33 | }
34 | // If someone is logged in
35 | if loggedInUser != "" {
36 | // If we're logged in and/or Notification Center is runnning register the applications with Notification Center
37 | registerApplications(parsedResult: parsedResult)
38 | // If no-one is logged in
39 | } else {
40 | // If verbose mode is enabled
41 | if parsedResult.verbose {
42 | // Progress log
43 | NSLog("\(#function.components(separatedBy: "(")[0]) - Skipping registration as not logged in")
44 | }
45 | }
46 | // Exit
47 | exit(0)
48 | }
49 |
50 | // Create JSON from passed string
51 | func createJSON(messageContent: MessageContent, parsedResult: ArgParser, rootElements: RootElements) -> String {
52 | // Var declaration
53 | var contentJSON = Data()
54 | var fullJSON = Data()
55 | var rootContent = rootElements
56 | // If verbose mode is enabled
57 | if parsedResult.verbose {
58 | // Progress log
59 | NSLog("\(#function.components(separatedBy: "(")[0]) - messageContent: \(messageContent)")
60 | }
61 | // Try to convert messageContent to JSON
62 | do {
63 | // Create a JSONEncoder object
64 | let jsonEncoder = JSONEncoder()
65 | // Set formatting to sortedKeys - to make sure that the base64 is static
66 | jsonEncoder.outputFormatting = .sortedKeys
67 | // Turn messageContent into JSON
68 | contentJSON = try jsonEncoder.encode(messageContent)
69 | // If encoding into JSON fails
70 | } catch {
71 | // Post error
72 | postToNSLogAndStdOut(logLevel: "ERROR", logMessage: error.localizedDescription, functionName:
73 | #function.components(separatedBy: "(")[0], parsedResult: parsedResult)
74 | }
75 | // If verbose mode is enabled
76 | if parsedResult.verbose {
77 | // Progress log
78 | NSLog("""
79 | \(#function.components(separatedBy: "(")[0]) - contentJSON: \(String(data: contentJSON, encoding: .utf8)!)
80 | """)
81 | }
82 | // Add to contentJSON, but base64 encoded
83 | rootContent.messageContent = contentJSON.base64EncodedString()
84 | // Try to convert jsonContent to JSON
85 | do {
86 | // Create a JSONEncoder object
87 | let jsonEncoder = JSONEncoder()
88 | // Set formatting to sortedKeys - to make sure that the base64 is static
89 | jsonEncoder.outputFormatting = .sortedKeys
90 | // Turn jsonContent into JSON
91 | fullJSON = try jsonEncoder.encode(rootContent)
92 | // If encoding into JSON fails
93 | } catch {
94 | // Post error
95 | postToNSLogAndStdOut(logLevel: "ERROR", logMessage: error.localizedDescription, functionName:
96 | #function.components(separatedBy: "(")[0], parsedResult: parsedResult)
97 | }
98 | // If verbose mode is enabled
99 | if parsedResult.verbose {
100 | // Progress log
101 | NSLog("\(#function.components(separatedBy: "(")[0]) - rootJSON: \(String(data: fullJSON, encoding: .utf8)!)")
102 | }
103 | // Return fullJSON, base64 encoded
104 | return fullJSON.base64EncodedString()
105 | }
106 |
107 | // Gets the modification date and time of the file and return as epoch
108 | func getImageDetails(brandingImage: String, parsedResult: ArgParser) -> (NSImage?) {
109 | // Var declaration
110 | var imageData: NSImage?
111 | // If the file exists
112 | if FileManager.default.fileExists(atPath: brandingImage) {
113 | // Create imageData from the file passed to brandingImage
114 | imageData = NSImage(contentsOfFile: brandingImage)
115 | // If imageData isValid is nil, then brandingImage is not a valid icon
116 | if (imageData?.isValid) == nil {
117 | // Post error
118 | postToNSLogAndStdOut(logLevel: "ERROR", logMessage: "\(brandingImage) is not a valid image...",
119 | functionName: #function.components(separatedBy: "(")[0], parsedResult: parsedResult)
120 | // Exit
121 | exit(1)
122 | }
123 | // Return the image data
124 | return imageData
125 | // If the file doesn't exist
126 | } else {
127 | // Post error
128 | postToNSLogAndStdOut(logLevel: "ERROR", logMessage: "Cannot locate: \(brandingImage)....", functionName:
129 | #function.components(separatedBy: "(")[0], parsedResult: parsedResult)
130 | // Exit
131 | exit(1)
132 | }
133 | }
134 |
135 | // Checks that notification center is running, and exit if it's not
136 | func isNotificationCenterRunning(parsedResult: ArgParser) {
137 | // Exit if Notification Center isn't running for the user
138 | guard !NSRunningApplication.runningApplications(withBundleIdentifier:
139 | "com.apple.notificationcenterui").isEmpty else {
140 | // Post warning
141 | postToNSLogAndStdOut(logLevel: "WARNING", logMessage: "Notification Center is not running...",
142 | functionName: #function.components(separatedBy: "(")[0], parsedResult: parsedResult)
143 | // Exit
144 | exit(1)
145 | }
146 | // If verbose mode is enabled
147 | if parsedResult.verbose {
148 | // Progress log
149 | NSLog("\(#function.components(separatedBy: "(")[0]) - Notification Center is running")
150 | }
151 | }
152 |
153 | // Returns username if a user is logged in, and "" if at the loginwindow
154 | func loggedInUser() -> String {
155 | // Get the name of the logged in user
156 | let loggedInUser = SCDynamicStoreCopyConsoleUser(nil, nil, nil)! as String
157 | // If no-one or loginwindow is returned
158 | if loggedInUser == "loginwindow" || loggedInUser == "" {
159 | return ""
160 | // Else if we have someone logged in
161 | } else {
162 | // Return their username
163 | return loggedInUser
164 | }
165 | }
166 |
167 | // Parses the action text, splitting it into argv compliant format
168 | func parseAction(actionString: String, parsedResult: ArgParser) -> [MessageContent.TaskObject] {
169 | // Var declaration
170 | var regexDict = [String: String]()
171 | var taskArguments = [String]()
172 | var taskPath = String()
173 | var tempActionString = actionString
174 | // If verbose mode is enabled
175 | if parsedResult.verbose {
176 | // Progress log
177 | NSLog("\(#function.components(separatedBy: "(")[0]) - actionString: \(actionString)")
178 | }
179 | // The regex pattern we're to use
180 | let regexPattern = try? NSRegularExpression(pattern: "\'(.*?)\'|\"(.*?)\"")
181 | // Apply pattern to actionText, returning matches
182 | let regexMatches = regexPattern?.matches(in: actionString, options: [],
183 | range: NSRange(location: 0, length: actionString.utf16.count))
184 | // Iterate over each match
185 | for regexMatch in regexMatches! {
186 | // Set the range
187 | let regexRange = regexMatch.range(at: 1)
188 | // Create a Range object
189 | if let swiftRange = Range(regexRange, in: actionString) {
190 | // Create key in regex dict with the and %20 escape spaces
191 | regexDict[actionString[swiftRange].description] =
192 | actionString[swiftRange].description.replacingOccurrences(of: " ", with: "%20")
193 | }
194 | }
195 | // If we have items in regexDict
196 | if !regexDict.isEmpty {
197 | // For each key value pair we have in regexDict
198 | for (matchedKey, matchedValue) in regexDict {
199 | // Replace the occurences of matchedKey with matchedValue
200 | tempActionString = tempActionString.replacingOccurrences(of: matchedKey, with: matchedValue)
201 | }
202 | // Set taskArguments to the amended string
203 | taskArguments = Array(tempActionString.components(separatedBy: " "))
204 | // Iterate over the array elements which contain %20
205 | for arrayElement in taskArguments where arrayElement.contains("%20") {
206 | // Get the index of the element
207 | let arrayIndex = taskArguments.firstIndex(of: arrayElement)
208 | // Update the element in the array, removing %20, single and double quotes
209 | taskArguments[arrayIndex!] = arrayElement.replacingOccurrences(of: "%20", with: " ")
210 | .replacingOccurrences(of: "\'", with: "").replacingOccurrences(of: "\"", with: "")
211 | }
212 | // If regexDict is empty
213 | } else {
214 | // Set taskArguments to the passed action
215 | taskArguments = Array(tempActionString.components(separatedBy: " "))
216 | }
217 | // If we only have a single task
218 | if taskArguments.count == 1 {
219 | // If we've been passed logout
220 | if taskArguments[0].lowercased() == "logout" {
221 | // Set the tasks path to open, this is to mimic pre-3.0 behaviour
222 | taskPath = "logout"
223 | // Clear taskArguments
224 | taskArguments = []
225 | // If one item and not passed logout
226 | } else {
227 | // Set the tasks path to open, this is to mimic pre-3.0 behaviour
228 | taskPath = "/usr/bin/open"
229 | }
230 | // If we have more than one task, the 1st argument starts with /
231 | } else if taskArguments[0].hasPrefix("/") {
232 | // Set the tasks path to the 1st item within the taskArguments
233 | taskPath = taskArguments[0]
234 | // Remove the above from the taskArguments
235 | taskArguments.remove(at: 0)
236 | // If we have more than one task, and the 1st argument does not start with /
237 | } else {
238 | // Post warning
239 | postToNSLogAndStdOut(logLevel: "WARNING", logMessage: """
240 | \(taskArguments[0]) is not a path to a binary, not adding to notification...
241 | """, functionName: #function.components(separatedBy: "(")[0], parsedResult: parsedResult)
242 | // Return an empty TaskObject
243 | return [MessageContent.TaskObject(taskPath: "", taskArguments: [])]
244 | }
245 | // If verbose mode is enabled
246 | if parsedResult.verbose {
247 | // Progress log
248 | NSLog("\(#function.components(separatedBy: "(")[0]) - taskPath: \(taskPath), taskArguments: \(taskArguments)")
249 | }
250 | // Return a TaskObject with taskPath and taskArguments set
251 | return [MessageContent.TaskObject(taskPath: taskPath, taskArguments: taskArguments)]
252 | }
253 |
254 | // Passes messageContentJSON to the relevant app, exiting afterwards
255 | func passToApp(commandJSON: String, loggedInUser: String, notifierPath: String, parsedResult: ArgParser) {
256 | // Var declaration
257 | var taskArguments = [String]()
258 | var taskPath = String()
259 | // If the user running the app isn't the logged in user (root for example)
260 | if NSUserName() != loggedInUser {
261 | // Set taskPath to su as we need to use that to run as the user
262 | taskPath = "/usr/bin/su"
263 | // Create taskArguments
264 | taskArguments = [
265 | "-l", "\(loggedInUser)", "-c", "\'\(notifierPath)\' \(commandJSON)"
266 | ]
267 | // If the person running the app is the logged in user
268 | } else {
269 | // Set taskPath to the notifying apps path
270 | taskPath = notifierPath
271 | // Set taskArguments to the base64 of messageContentJSON
272 | taskArguments = [commandJSON]
273 | }
274 | // If verbose mode is enabled
275 | if parsedResult.verbose {
276 | // Progress log
277 | NSLog("\(#function.components(separatedBy: "(")[0]) - taskPath: \(taskPath), taskArguments: \(taskArguments)")
278 | }
279 | // Launch the wanted notification app as the user
280 | runTask(parsedResult: parsedResult, taskArguments: taskArguments, taskPath: taskPath)
281 | // If we're not rebranding
282 | if parsedResult.rebrand == "" {
283 | // Exit
284 | exit(0)
285 | }
286 | }
287 |
288 | // Post to both NSLog and stdout
289 | func postToNSLogAndStdOut(logLevel: String, logMessage: String, functionName: String, parsedResult: ArgParser) {
290 | // If verbose mode is enabled
291 | if parsedResult.verbose {
292 | // Progress log
293 | NSLog("\(logLevel): \(functionName) - \(logMessage)")
294 | // verbose mode isn't enabled
295 | } else {
296 | // Print to stdout
297 | print("\(logLevel): \(logMessage)")
298 | }
299 | }
300 |
301 | // Registers the notifying applications in Notificaton Center
302 | func registerApplications(parsedResult: ArgParser) {
303 | // Var declaration
304 | var taskArguments = [String]()
305 | var taskPath = String()
306 | // If verbose mode is enabled
307 | if parsedResult.verbose {
308 | // Progress log
309 | NSLog("\(#function.components(separatedBy: "(")[0])")
310 | }
311 | // Get the username of the logged in user
312 | let loggedInUser = loggedInUser()
313 | // Initialize a rootElements object
314 | let rootElements = RootElements(removeOption: "prior")
315 | // Initialize a messageContent object with a message body of a UUID
316 | let messageContent = MessageContent(messageBody: UUID().uuidString)
317 | // Create the JSON to pass to the notifying app
318 | let commandJSON = createJSON(messageContent: messageContent, parsedResult: parsedResult,
319 | rootElements: rootElements)
320 | for notifierPath in [GlobalVariables.alertAppPath + "/Contents/MacOS/Notifier - Alerts",
321 | GlobalVariables.bannerAppPath + "/Contents/MacOS/Notifier - Notifications"] {
322 | // Pass commandJSON to the relevant app, exiting afterwards
323 | passToApp(commandJSON: commandJSON, loggedInUser: loggedInUser, notifierPath: notifierPath,
324 | parsedResult: parsedResult)
325 | // If verbose mode is enabled
326 | if parsedResult.verbose {
327 | // Progress log
328 | NSLog("""
329 | \(#function.components(separatedBy: "(")[0]) - commandJSON: \(commandJSON), notifierPath: \
330 | \(notifierPath)
331 | """)
332 | }
333 | }
334 | // If verbose mode is enabled
335 | if parsedResult.verbose {
336 | // Progress log
337 | NSLog("\(#function.components(separatedBy: "(")[0]) - restarting Notification Center")
338 | }
339 | // If we're logged in
340 | if loggedInUser != "" {
341 | // Path for the task
342 | taskPath = "/usr/bin/su"
343 | // Arguments for the task
344 | taskArguments = ["-l", loggedInUser, "-c", "/usr/bin/killall -u \(loggedInUser) NotificationCenter"]
345 | // If verbose mode is enabled
346 | if parsedResult.verbose {
347 | // Progress log
348 | NSLog("""
349 | \(#function.components(separatedBy: "(")[0]) - taskPath: \(taskPath), taskArguments: \(taskArguments)
350 | """)
351 | }
352 | // Run the task, ignoring returned exit status
353 | runTask(parsedResult: parsedResult, taskArguments: taskArguments, taskPath: taskPath)
354 | // If we're not logged in
355 | } else {
356 | // If verbose mode is enabled
357 | if parsedResult.verbose {
358 | // Progress log
359 | NSLog("\(#function.components(separatedBy: "(")[0]) - not logged in, skipping Notification Center restart")
360 | }
361 | }
362 | }
363 |
364 | // Make sure we're running as root, exit if not
365 | func rootCheck(parsedResult: ArgParser, passedArg: String) {
366 | // If we're not root
367 | if NSUserName() != "root" {
368 | // Post error
369 | postToNSLogAndStdOut(logLevel: "ERROR", logMessage: """
370 | The argument: \(passedArg), requires root privileges, exiting...
371 | """, functionName: #function.components(separatedBy: "(")[0], parsedResult: parsedResult)
372 | // Exit
373 | exit(1)
374 | }
375 | }
376 |
377 | // Runs the passed task
378 | func runTask(parsedResult: ArgParser, taskArguments: [String], taskPath: String) {
379 | // If verbose mode is enabled
380 | if parsedResult.verbose {
381 | // Progress log
382 | NSLog("\(#function.components(separatedBy: "(")[0]) - taskPath: \(taskPath), taskArguments: \(taskArguments)")
383 | }
384 | // Create Task
385 | let task = Process()
386 | // Set task to call the binary
387 | task.executableURL = URL(fileURLWithPath: taskPath)
388 | // Set task arguments
389 | task.arguments = taskArguments
390 | // Run the task
391 | try? task.run()
392 | // Wait until task exits
393 | task.waitUntilExit()
394 | }
395 |
396 | // Attempts to update the app passed to objectPath's icon
397 | func updateIcon(brandingImage: String, imageData: NSImage, objectPath: String, parsedResult: ArgParser) {
398 | // Revert the icon, always returns false and this helps the OS realise that ther has been an icon change
399 | NSWorkspace.shared.setIcon(nil, forFile: objectPath, options: NSWorkspace.IconCreationOptions([]))
400 | // Set the icon, returns bool
401 | let rebrandStatus = NSWorkspace.shared.setIcon(imageData, forFile: objectPath, options:
402 | NSWorkspace.IconCreationOptions([]))
403 | // If we have succesfully branded the item at objectPath
404 | if rebrandStatus {
405 | // If verbose mode is enabled
406 | if parsedResult.verbose {
407 | // Progress log
408 | NSLog("""
409 | \(#function.components(separatedBy: "(")[0]) - Successfully updated icon for \(objectPath), \
410 | with icon: \(brandingImage)
411 | """)
412 | }
413 | // If we encountered and issue when rebranding...
414 | } else {
415 | // Post error
416 | postToNSLogAndStdOut(logLevel: "ERROR", logMessage: """
417 | Failed to update icon for \(objectPath), with icon: \(brandingImage). Please make sure that the calling \
418 | process has the needed App Management or Full Disk Access PPPCP deployed, and try again.
419 | """, functionName: #function.components(separatedBy: "(")[0], parsedResult: parsedResult)
420 | // Exit
421 | exit(1)
422 | }
423 | }
424 |
--------------------------------------------------------------------------------
/Notifier/Notifier.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 60;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 63B92DE9282AAEDE00B44340 /* ArgParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63B92DE8282AAEDE00B44340 /* ArgParser.swift */; };
11 | 7C6D157C2270BA2F0092D34E /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C6D157B2270BA2F0092D34E /* AppDelegate.swift */; };
12 | 7C6D15812270BA300092D34E /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 7C6D157F2270BA300092D34E /* MainMenu.xib */; };
13 | 7C720952227888E10033B2E2 /* Functions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C720951227888E10033B2E2 /* Functions.swift */; };
14 | 8421955B2B67E80A0030A7E4 /* Structures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8421955A2B67E80A0030A7E4 /* Structures.swift */; };
15 | 844D6FAC2B647A040000CCF2 /* Structures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844D6FAB2B647A040000CCF2 /* Structures.swift */; };
16 | 84524FD52B61BC620046367B /* Notifier - Notifications.app in CopyFiles */ = {isa = PBXBuildFile; fileRef = 848FC8942B61BB1F009B4915 /* Notifier - Notifications.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
17 | 845D68E12B692A5100C5B467 /* ArgumentParser in Frameworks */ = {isa = PBXBuildFile; productRef = 845D68E02B692A5100C5B467 /* ArgumentParser */; };
18 | 848FC8972B61BB1F009B4915 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 848FC8962B61BB1F009B4915 /* AppDelegate.swift */; };
19 | 848FC89C2B61BB20009B4915 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 848FC89A2B61BB20009B4915 /* MainMenu.xib */; };
20 | 849D93A92B693243006410D1 /* UserNotifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849D93A82B693243006410D1 /* UserNotifications.swift */; };
21 | 849D93AD2B693D2C006410D1 /* Structures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849D93AC2B693D2C006410D1 /* Structures.swift */; };
22 | 84E1A6BB2B61C00400D7F7C9 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E1A6BA2B61C00400D7F7C9 /* AppDelegate.swift */; };
23 | 84E1A6BD2B61C00500D7F7C9 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 84E1A6BC2B61C00500D7F7C9 /* Assets.xcassets */; };
24 | 84E1A6C02B61C00500D7F7C9 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 84E1A6BE2B61C00500D7F7C9 /* MainMenu.xib */; };
25 | 84E1A6CB2B61C03F00D7F7C9 /* Notifier - Alerts.app in CopyFiles */ = {isa = PBXBuildFile; fileRef = 84E1A6B82B61C00400D7F7C9 /* Notifier - Alerts.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
26 | 84E1A6CD2B61C0B900D7F7C9 /* Functions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E1A6CC2B61C0B900D7F7C9 /* Functions.swift */; };
27 | 84E1A6CF2B61C0BE00D7F7C9 /* UserNotifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E1A6CE2B61C0BE00D7F7C9 /* UserNotifications.swift */; };
28 | 84E333822B61BBBB00BE750F /* Functions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E333812B61BBBB00BE750F /* Functions.swift */; };
29 | 84E333842B61BBC200BE750F /* UserNotifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E333832B61BBC200BE750F /* UserNotifications.swift */; };
30 | 84FA74982B61C4A6009B4068 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 84E1A6D22B61C1B500D7F7C9 /* Assets.xcassets */; };
31 | 84FA74992B61C4B7009B4068 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 84E1A6D32B61C1BC00D7F7C9 /* Assets.xcassets */; };
32 | /* End PBXBuildFile section */
33 |
34 | /* Begin PBXContainerItemProxy section */
35 | 84E1A6C92B61C02C00D7F7C9 /* PBXContainerItemProxy */ = {
36 | isa = PBXContainerItemProxy;
37 | containerPortal = 7C6D15702270BA2F0092D34E /* Project object */;
38 | proxyType = 1;
39 | remoteGlobalIDString = 84E1A6B72B61C00400D7F7C9;
40 | remoteInfo = "Notifier - Alerts";
41 | };
42 | 84FA749B2B61C9D9009B4068 /* PBXContainerItemProxy */ = {
43 | isa = PBXContainerItemProxy;
44 | containerPortal = 7C6D15702270BA2F0092D34E /* Project object */;
45 | proxyType = 1;
46 | remoteGlobalIDString = 848FC8932B61BB1F009B4915;
47 | remoteInfo = "Notifier - Notifications";
48 | };
49 | /* End PBXContainerItemProxy section */
50 |
51 | /* Begin PBXCopyFilesBuildPhase section */
52 | 7CAB609623DF4C8B0029E310 /* CopyFiles */ = {
53 | isa = PBXCopyFilesBuildPhase;
54 | buildActionMask = 2147483647;
55 | dstPath = Contents/Helpers;
56 | dstSubfolderSpec = 1;
57 | files = (
58 | 84524FD52B61BC620046367B /* Notifier - Notifications.app in CopyFiles */,
59 | );
60 | runOnlyForDeploymentPostprocessing = 0;
61 | };
62 | 7CF89A4023BF9F6000723A3B /* CopyFiles */ = {
63 | isa = PBXCopyFilesBuildPhase;
64 | buildActionMask = 2147483647;
65 | dstPath = Contents/Helpers;
66 | dstSubfolderSpec = 1;
67 | files = (
68 | 84E1A6CB2B61C03F00D7F7C9 /* Notifier - Alerts.app in CopyFiles */,
69 | );
70 | runOnlyForDeploymentPostprocessing = 0;
71 | };
72 | /* End PBXCopyFilesBuildPhase section */
73 |
74 | /* Begin PBXFileReference section */
75 | 63B92DE8282AAEDE00B44340 /* ArgParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArgParser.swift; sourceTree = ""; };
76 | 7C6D15782270BA2F0092D34E /* Notifier.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Notifier.app; sourceTree = BUILT_PRODUCTS_DIR; };
77 | 7C6D157B2270BA2F0092D34E /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
78 | 7C6D15802270BA300092D34E /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; };
79 | 7C720951227888E10033B2E2 /* Functions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Functions.swift; sourceTree = ""; };
80 | 8421955A2B67E80A0030A7E4 /* Structures.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Structures.swift; sourceTree = ""; };
81 | 844D6FAA2B6447790000CCF2 /* Notifier.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Notifier.entitlements; sourceTree = ""; };
82 | 844D6FAB2B647A040000CCF2 /* Structures.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Structures.swift; sourceTree = ""; };
83 | 848FC8942B61BB1F009B4915 /* Notifier - Notifications.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Notifier - Notifications.app"; sourceTree = BUILT_PRODUCTS_DIR; };
84 | 848FC8962B61BB1F009B4915 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
85 | 848FC89B2B61BB20009B4915 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; };
86 | 849D93A82B693243006410D1 /* UserNotifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserNotifications.swift; sourceTree = ""; };
87 | 849D93AC2B693D2C006410D1 /* Structures.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Structures.swift; sourceTree = ""; };
88 | 84D841A42B7142C6005AD541 /* Notifier - Alerts.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "Notifier - Alerts.entitlements"; sourceTree = ""; };
89 | 84E1A6B82B61C00400D7F7C9 /* Notifier - Alerts.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Notifier - Alerts.app"; sourceTree = BUILT_PRODUCTS_DIR; };
90 | 84E1A6BA2B61C00400D7F7C9 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
91 | 84E1A6BC2B61C00500D7F7C9 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
92 | 84E1A6BF2B61C00500D7F7C9 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; };
93 | 84E1A6CC2B61C0B900D7F7C9 /* Functions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Functions.swift; sourceTree = ""; };
94 | 84E1A6CE2B61C0BE00D7F7C9 /* UserNotifications.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserNotifications.swift; sourceTree = ""; };
95 | 84E1A6D22B61C1B500D7F7C9 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
96 | 84E1A6D32B61C1BC00D7F7C9 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
97 | 84E333812B61BBBB00BE750F /* Functions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Functions.swift; sourceTree = ""; };
98 | 84E333832B61BBC200BE750F /* UserNotifications.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserNotifications.swift; sourceTree = ""; };
99 | 84E8D6EE2B714CF100EFEF9C /* Notifier - Notifications.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "Notifier - Notifications.entitlements"; sourceTree = ""; };
100 | /* End PBXFileReference section */
101 |
102 | /* Begin PBXFrameworksBuildPhase section */
103 | 7C6D15752270BA2F0092D34E /* Frameworks */ = {
104 | isa = PBXFrameworksBuildPhase;
105 | buildActionMask = 2147483647;
106 | files = (
107 | 845D68E12B692A5100C5B467 /* ArgumentParser in Frameworks */,
108 | );
109 | runOnlyForDeploymentPostprocessing = 0;
110 | };
111 | 848FC8912B61BB1F009B4915 /* Frameworks */ = {
112 | isa = PBXFrameworksBuildPhase;
113 | buildActionMask = 2147483647;
114 | files = (
115 | );
116 | runOnlyForDeploymentPostprocessing = 0;
117 | };
118 | 84E1A6B52B61C00400D7F7C9 /* Frameworks */ = {
119 | isa = PBXFrameworksBuildPhase;
120 | buildActionMask = 2147483647;
121 | files = (
122 | );
123 | runOnlyForDeploymentPostprocessing = 0;
124 | };
125 | /* End PBXFrameworksBuildPhase section */
126 |
127 | /* Begin PBXGroup section */
128 | 7C6D156F2270BA2F0092D34E = {
129 | isa = PBXGroup;
130 | children = (
131 | 7C6D157A2270BA2F0092D34E /* Notifier */,
132 | 84E1A6B92B61C00400D7F7C9 /* Notifier - Alerts */,
133 | 848FC8952B61BB1F009B4915 /* Notifier - Notifications */,
134 | 7C6D15792270BA2F0092D34E /* Products */,
135 | 7CECAB5C23BE493E00305F10 /* Frameworks */,
136 | );
137 | sourceTree = "";
138 | };
139 | 7C6D15792270BA2F0092D34E /* Products */ = {
140 | isa = PBXGroup;
141 | children = (
142 | 7C6D15782270BA2F0092D34E /* Notifier.app */,
143 | 848FC8942B61BB1F009B4915 /* Notifier - Notifications.app */,
144 | 84E1A6B82B61C00400D7F7C9 /* Notifier - Alerts.app */,
145 | );
146 | name = Products;
147 | sourceTree = "";
148 | };
149 | 7C6D157A2270BA2F0092D34E /* Notifier */ = {
150 | isa = PBXGroup;
151 | children = (
152 | 7C6D157B2270BA2F0092D34E /* AppDelegate.swift */,
153 | 63B92DE8282AAEDE00B44340 /* ArgParser.swift */,
154 | 7C720951227888E10033B2E2 /* Functions.swift */,
155 | 844D6FAB2B647A040000CCF2 /* Structures.swift */,
156 | 849D93A82B693243006410D1 /* UserNotifications.swift */,
157 | 84E1A6D32B61C1BC00D7F7C9 /* Assets.xcassets */,
158 | 7C6D157F2270BA300092D34E /* MainMenu.xib */,
159 | 844D6FAA2B6447790000CCF2 /* Notifier.entitlements */,
160 | );
161 | path = Notifier;
162 | sourceTree = "";
163 | };
164 | 7CECAB5C23BE493E00305F10 /* Frameworks */ = {
165 | isa = PBXGroup;
166 | children = (
167 | );
168 | name = Frameworks;
169 | sourceTree = "";
170 | };
171 | 848FC8952B61BB1F009B4915 /* Notifier - Notifications */ = {
172 | isa = PBXGroup;
173 | children = (
174 | 848FC8962B61BB1F009B4915 /* AppDelegate.swift */,
175 | 84E333812B61BBBB00BE750F /* Functions.swift */,
176 | 849D93AC2B693D2C006410D1 /* Structures.swift */,
177 | 84E333832B61BBC200BE750F /* UserNotifications.swift */,
178 | 84E1A6D22B61C1B500D7F7C9 /* Assets.xcassets */,
179 | 848FC89A2B61BB20009B4915 /* MainMenu.xib */,
180 | 84E8D6EE2B714CF100EFEF9C /* Notifier - Notifications.entitlements */,
181 | );
182 | path = "Notifier - Notifications";
183 | sourceTree = "";
184 | };
185 | 84E1A6B92B61C00400D7F7C9 /* Notifier - Alerts */ = {
186 | isa = PBXGroup;
187 | children = (
188 | 84E1A6BA2B61C00400D7F7C9 /* AppDelegate.swift */,
189 | 84E1A6CC2B61C0B900D7F7C9 /* Functions.swift */,
190 | 8421955A2B67E80A0030A7E4 /* Structures.swift */,
191 | 84E1A6CE2B61C0BE00D7F7C9 /* UserNotifications.swift */,
192 | 84E1A6BC2B61C00500D7F7C9 /* Assets.xcassets */,
193 | 84E1A6BE2B61C00500D7F7C9 /* MainMenu.xib */,
194 | 84D841A42B7142C6005AD541 /* Notifier - Alerts.entitlements */,
195 | );
196 | path = "Notifier - Alerts";
197 | sourceTree = "";
198 | };
199 | /* End PBXGroup section */
200 |
201 | /* Begin PBXNativeTarget section */
202 | 7C6D15772270BA2F0092D34E /* Notifier */ = {
203 | isa = PBXNativeTarget;
204 | buildConfigurationList = 7C6D15862270BA300092D34E /* Build configuration list for PBXNativeTarget "Notifier" */;
205 | buildPhases = (
206 | 7C6D15742270BA2F0092D34E /* Sources */,
207 | 7C6D15752270BA2F0092D34E /* Frameworks */,
208 | 7C6D15762270BA2F0092D34E /* Resources */,
209 | 7CF89A4023BF9F6000723A3B /* CopyFiles */,
210 | 7CAB609623DF4C8B0029E310 /* CopyFiles */,
211 | 84CC99F32C21904900396D3F /* Swiftlint */,
212 | );
213 | buildRules = (
214 | );
215 | dependencies = (
216 | 84FA749C2B61C9D9009B4068 /* PBXTargetDependency */,
217 | 84E1A6CA2B61C02C00D7F7C9 /* PBXTargetDependency */,
218 | );
219 | name = Notifier;
220 | packageProductDependencies = (
221 | 845D68E02B692A5100C5B467 /* ArgumentParser */,
222 | );
223 | productName = oioi;
224 | productReference = 7C6D15782270BA2F0092D34E /* Notifier.app */;
225 | productType = "com.apple.product-type.application";
226 | };
227 | 848FC8932B61BB1F009B4915 /* Notifier - Notifications */ = {
228 | isa = PBXNativeTarget;
229 | buildConfigurationList = 848FC89E2B61BB20009B4915 /* Build configuration list for PBXNativeTarget "Notifier - Notifications" */;
230 | buildPhases = (
231 | 848FC8902B61BB1F009B4915 /* Sources */,
232 | 848FC8912B61BB1F009B4915 /* Frameworks */,
233 | 848FC8922B61BB1F009B4915 /* Resources */,
234 | );
235 | buildRules = (
236 | );
237 | dependencies = (
238 | );
239 | name = "Notifier - Notifications";
240 | packageProductDependencies = (
241 | );
242 | productName = "Notifier - Notifications";
243 | productReference = 848FC8942B61BB1F009B4915 /* Notifier - Notifications.app */;
244 | productType = "com.apple.product-type.application";
245 | };
246 | 84E1A6B72B61C00400D7F7C9 /* Notifier - Alerts */ = {
247 | isa = PBXNativeTarget;
248 | buildConfigurationList = 84E1A6C22B61C00500D7F7C9 /* Build configuration list for PBXNativeTarget "Notifier - Alerts" */;
249 | buildPhases = (
250 | 84E1A6B42B61C00400D7F7C9 /* Sources */,
251 | 84E1A6B52B61C00400D7F7C9 /* Frameworks */,
252 | 84E1A6B62B61C00400D7F7C9 /* Resources */,
253 | );
254 | buildRules = (
255 | );
256 | dependencies = (
257 | );
258 | name = "Notifier - Alerts";
259 | packageProductDependencies = (
260 | );
261 | productName = "Notifier - Alerts";
262 | productReference = 84E1A6B82B61C00400D7F7C9 /* Notifier - Alerts.app */;
263 | productType = "com.apple.product-type.application";
264 | };
265 | /* End PBXNativeTarget section */
266 |
267 | /* Begin PBXProject section */
268 | 7C6D15702270BA2F0092D34E /* Project object */ = {
269 | isa = PBXProject;
270 | attributes = {
271 | BuildIndependentTargetsInParallel = YES;
272 | LastSwiftUpdateCheck = 1520;
273 | LastUpgradeCheck = 1600;
274 | ORGANIZATIONNAME = "dataJAR Ltd";
275 | TargetAttributes = {
276 | 7C6D15772270BA2F0092D34E = {
277 | CreatedOnToolsVersion = 10.1;
278 | LastSwiftMigration = 1020;
279 | SystemCapabilities = {
280 | com.apple.HardenedRuntime = {
281 | enabled = 1;
282 | };
283 | com.apple.Sandbox = {
284 | enabled = 0;
285 | };
286 | };
287 | };
288 | 848FC8932B61BB1F009B4915 = {
289 | CreatedOnToolsVersion = 15.2;
290 | };
291 | 84E1A6B72B61C00400D7F7C9 = {
292 | CreatedOnToolsVersion = 15.2;
293 | };
294 | };
295 | };
296 | buildConfigurationList = 7C6D15732270BA2F0092D34E /* Build configuration list for PBXProject "Notifier" */;
297 | compatibilityVersion = "Xcode 15.0";
298 | developmentRegion = en;
299 | hasScannedForEncodings = 0;
300 | knownRegions = (
301 | en,
302 | Base,
303 | );
304 | mainGroup = 7C6D156F2270BA2F0092D34E;
305 | packageReferences = (
306 | 845D68DF2B692A4800C5B467 /* XCRemoteSwiftPackageReference "swift-argument-parser" */,
307 | );
308 | productRefGroup = 7C6D15792270BA2F0092D34E /* Products */;
309 | projectDirPath = "";
310 | projectRoot = "";
311 | targets = (
312 | 7C6D15772270BA2F0092D34E /* Notifier */,
313 | 848FC8932B61BB1F009B4915 /* Notifier - Notifications */,
314 | 84E1A6B72B61C00400D7F7C9 /* Notifier - Alerts */,
315 | );
316 | };
317 | /* End PBXProject section */
318 |
319 | /* Begin PBXResourcesBuildPhase section */
320 | 7C6D15762270BA2F0092D34E /* Resources */ = {
321 | isa = PBXResourcesBuildPhase;
322 | buildActionMask = 2147483647;
323 | files = (
324 | 84FA74992B61C4B7009B4068 /* Assets.xcassets in Resources */,
325 | 7C6D15812270BA300092D34E /* MainMenu.xib in Resources */,
326 | );
327 | runOnlyForDeploymentPostprocessing = 0;
328 | };
329 | 848FC8922B61BB1F009B4915 /* Resources */ = {
330 | isa = PBXResourcesBuildPhase;
331 | buildActionMask = 2147483647;
332 | files = (
333 | 84FA74982B61C4A6009B4068 /* Assets.xcassets in Resources */,
334 | 848FC89C2B61BB20009B4915 /* MainMenu.xib in Resources */,
335 | );
336 | runOnlyForDeploymentPostprocessing = 0;
337 | };
338 | 84E1A6B62B61C00400D7F7C9 /* Resources */ = {
339 | isa = PBXResourcesBuildPhase;
340 | buildActionMask = 2147483647;
341 | files = (
342 | 84E1A6BD2B61C00500D7F7C9 /* Assets.xcassets in Resources */,
343 | 84E1A6C02B61C00500D7F7C9 /* MainMenu.xib in Resources */,
344 | );
345 | runOnlyForDeploymentPostprocessing = 0;
346 | };
347 | /* End PBXResourcesBuildPhase section */
348 |
349 | /* Begin PBXShellScriptBuildPhase section */
350 | 84CC99F32C21904900396D3F /* Swiftlint */ = {
351 | isa = PBXShellScriptBuildPhase;
352 | alwaysOutOfDate = 1;
353 | buildActionMask = 2147483647;
354 | files = (
355 | );
356 | inputFileListPaths = (
357 | );
358 | inputPaths = (
359 | );
360 | name = Swiftlint;
361 | outputFileListPaths = (
362 | );
363 | outputPaths = (
364 | );
365 | runOnlyForDeploymentPostprocessing = 0;
366 | shellPath = /bin/sh;
367 | shellScript = "# If the dir /opt/homebrew/bin/ exists\nif [ -d '/opt/homebrew/bin/' ]\nthen\n # Add homebrew dir to PATH\n PATH=\"/opt/homebrew/bin/:${PATH}\"\nfi\n\n# Get path so swiftlint (if installed)\nswiftlintPath=$(which swiftlint)\n\n# If we can find swiftlint\nif [ -n \"${swiftlintPath}\" ]\nthen\n # Run swiftlint\n swiftlint\n# If we can't find swiftlint\nelse\n # Log error\n /bin/echo \"ERROR: SwiftLint not installed, exiting...\"\n # Exit\n exit 1\nfi\n";
368 | };
369 | /* End PBXShellScriptBuildPhase section */
370 |
371 | /* Begin PBXSourcesBuildPhase section */
372 | 7C6D15742270BA2F0092D34E /* Sources */ = {
373 | isa = PBXSourcesBuildPhase;
374 | buildActionMask = 2147483647;
375 | files = (
376 | 7C6D157C2270BA2F0092D34E /* AppDelegate.swift in Sources */,
377 | 7C720952227888E10033B2E2 /* Functions.swift in Sources */,
378 | 63B92DE9282AAEDE00B44340 /* ArgParser.swift in Sources */,
379 | 844D6FAC2B647A040000CCF2 /* Structures.swift in Sources */,
380 | 849D93A92B693243006410D1 /* UserNotifications.swift in Sources */,
381 | );
382 | runOnlyForDeploymentPostprocessing = 0;
383 | };
384 | 848FC8902B61BB1F009B4915 /* Sources */ = {
385 | isa = PBXSourcesBuildPhase;
386 | buildActionMask = 2147483647;
387 | files = (
388 | 848FC8972B61BB1F009B4915 /* AppDelegate.swift in Sources */,
389 | 84E333822B61BBBB00BE750F /* Functions.swift in Sources */,
390 | 84E333842B61BBC200BE750F /* UserNotifications.swift in Sources */,
391 | 849D93AD2B693D2C006410D1 /* Structures.swift in Sources */,
392 | );
393 | runOnlyForDeploymentPostprocessing = 0;
394 | };
395 | 84E1A6B42B61C00400D7F7C9 /* Sources */ = {
396 | isa = PBXSourcesBuildPhase;
397 | buildActionMask = 2147483647;
398 | files = (
399 | 84E1A6BB2B61C00400D7F7C9 /* AppDelegate.swift in Sources */,
400 | 84E1A6CD2B61C0B900D7F7C9 /* Functions.swift in Sources */,
401 | 84E1A6CF2B61C0BE00D7F7C9 /* UserNotifications.swift in Sources */,
402 | 8421955B2B67E80A0030A7E4 /* Structures.swift in Sources */,
403 | );
404 | runOnlyForDeploymentPostprocessing = 0;
405 | };
406 | /* End PBXSourcesBuildPhase section */
407 |
408 | /* Begin PBXTargetDependency section */
409 | 84E1A6CA2B61C02C00D7F7C9 /* PBXTargetDependency */ = {
410 | isa = PBXTargetDependency;
411 | target = 84E1A6B72B61C00400D7F7C9 /* Notifier - Alerts */;
412 | targetProxy = 84E1A6C92B61C02C00D7F7C9 /* PBXContainerItemProxy */;
413 | };
414 | 84FA749C2B61C9D9009B4068 /* PBXTargetDependency */ = {
415 | isa = PBXTargetDependency;
416 | target = 848FC8932B61BB1F009B4915 /* Notifier - Notifications */;
417 | targetProxy = 84FA749B2B61C9D9009B4068 /* PBXContainerItemProxy */;
418 | };
419 | /* End PBXTargetDependency section */
420 |
421 | /* Begin PBXVariantGroup section */
422 | 7C6D157F2270BA300092D34E /* MainMenu.xib */ = {
423 | isa = PBXVariantGroup;
424 | children = (
425 | 7C6D15802270BA300092D34E /* Base */,
426 | );
427 | name = MainMenu.xib;
428 | sourceTree = "";
429 | };
430 | 848FC89A2B61BB20009B4915 /* MainMenu.xib */ = {
431 | isa = PBXVariantGroup;
432 | children = (
433 | 848FC89B2B61BB20009B4915 /* Base */,
434 | );
435 | name = MainMenu.xib;
436 | sourceTree = "";
437 | };
438 | 84E1A6BE2B61C00500D7F7C9 /* MainMenu.xib */ = {
439 | isa = PBXVariantGroup;
440 | children = (
441 | 84E1A6BF2B61C00500D7F7C9 /* Base */,
442 | );
443 | name = MainMenu.xib;
444 | sourceTree = "";
445 | };
446 | /* End PBXVariantGroup section */
447 |
448 | /* Begin XCBuildConfiguration section */
449 | 7C6D15842270BA300092D34E /* Debug */ = {
450 | isa = XCBuildConfiguration;
451 | buildSettings = {
452 | ALWAYS_SEARCH_USER_PATHS = NO;
453 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
454 | CLANG_ANALYZER_NONNULL = YES;
455 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
456 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
457 | CLANG_CXX_LIBRARY = "libc++";
458 | CLANG_ENABLE_MODULES = YES;
459 | CLANG_ENABLE_OBJC_ARC = YES;
460 | CLANG_ENABLE_OBJC_WEAK = YES;
461 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
462 | CLANG_WARN_BOOL_CONVERSION = YES;
463 | CLANG_WARN_COMMA = YES;
464 | CLANG_WARN_CONSTANT_CONVERSION = YES;
465 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
466 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
467 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
468 | CLANG_WARN_EMPTY_BODY = YES;
469 | CLANG_WARN_ENUM_CONVERSION = YES;
470 | CLANG_WARN_INFINITE_RECURSION = YES;
471 | CLANG_WARN_INT_CONVERSION = YES;
472 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
473 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
474 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
475 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
476 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
477 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
478 | CLANG_WARN_STRICT_PROTOTYPES = YES;
479 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
480 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
481 | CLANG_WARN_UNREACHABLE_CODE = YES;
482 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
483 | CODE_SIGN_IDENTITY = "Apple Development";
484 | COPY_PHASE_STRIP = NO;
485 | DEAD_CODE_STRIPPING = YES;
486 | DEBUG_INFORMATION_FORMAT = dwarf;
487 | "DEVELOPMENT_TEAM[sdk=*]" = 82K2XFN8L6;
488 | ENABLE_HARDENED_RUNTIME = YES;
489 | ENABLE_STRICT_OBJC_MSGSEND = YES;
490 | ENABLE_TESTABILITY = YES;
491 | ENABLE_USER_SCRIPT_SANDBOXING = YES;
492 | GCC_C_LANGUAGE_STANDARD = gnu11;
493 | GCC_DYNAMIC_NO_PIC = NO;
494 | GCC_NO_COMMON_BLOCKS = YES;
495 | GCC_OPTIMIZATION_LEVEL = 0;
496 | GCC_PREPROCESSOR_DEFINITIONS = (
497 | "DEBUG=1",
498 | "$(inherited)",
499 | );
500 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
501 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
502 | GCC_WARN_UNDECLARED_SELECTOR = YES;
503 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
504 | GCC_WARN_UNUSED_FUNCTION = YES;
505 | GCC_WARN_UNUSED_VARIABLE = YES;
506 | INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
507 | INFOPLIST_KEY_LSUIElement = YES;
508 | MACOSX_DEPLOYMENT_TARGET = 10.15;
509 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
510 | MTL_FAST_MATH = YES;
511 | ONLY_ACTIVE_ARCH = YES;
512 | "OTHER_CODE_SIGN_FLAGS[sdk=*]" = "--timestamp";
513 | SDKROOT = macosx;
514 | SKIP_INSTALL = YES;
515 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
516 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
517 | SWIFT_VERSION = 5.0;
518 | };
519 | name = Debug;
520 | };
521 | 7C6D15852270BA300092D34E /* Release */ = {
522 | isa = XCBuildConfiguration;
523 | buildSettings = {
524 | ALWAYS_SEARCH_USER_PATHS = NO;
525 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
526 | CLANG_ANALYZER_NONNULL = YES;
527 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
528 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
529 | CLANG_CXX_LIBRARY = "libc++";
530 | CLANG_ENABLE_MODULES = YES;
531 | CLANG_ENABLE_OBJC_ARC = YES;
532 | CLANG_ENABLE_OBJC_WEAK = YES;
533 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
534 | CLANG_WARN_BOOL_CONVERSION = YES;
535 | CLANG_WARN_COMMA = YES;
536 | CLANG_WARN_CONSTANT_CONVERSION = YES;
537 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
538 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
539 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
540 | CLANG_WARN_EMPTY_BODY = YES;
541 | CLANG_WARN_ENUM_CONVERSION = YES;
542 | CLANG_WARN_INFINITE_RECURSION = YES;
543 | CLANG_WARN_INT_CONVERSION = YES;
544 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
545 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
546 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
547 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
548 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
549 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
550 | CLANG_WARN_STRICT_PROTOTYPES = YES;
551 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
552 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
553 | CLANG_WARN_UNREACHABLE_CODE = YES;
554 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
555 | CODE_SIGN_IDENTITY = "Apple Development";
556 | COPY_PHASE_STRIP = NO;
557 | DEAD_CODE_STRIPPING = YES;
558 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
559 | DEVELOPMENT_TEAM = 82K2XFN8L6;
560 | ENABLE_HARDENED_RUNTIME = YES;
561 | ENABLE_NS_ASSERTIONS = NO;
562 | ENABLE_STRICT_OBJC_MSGSEND = YES;
563 | ENABLE_USER_SCRIPT_SANDBOXING = YES;
564 | GCC_C_LANGUAGE_STANDARD = gnu11;
565 | GCC_NO_COMMON_BLOCKS = YES;
566 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
567 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
568 | GCC_WARN_UNDECLARED_SELECTOR = YES;
569 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
570 | GCC_WARN_UNUSED_FUNCTION = YES;
571 | GCC_WARN_UNUSED_VARIABLE = YES;
572 | INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
573 | INFOPLIST_KEY_LSUIElement = YES;
574 | MACOSX_DEPLOYMENT_TARGET = 10.15;
575 | MTL_ENABLE_DEBUG_INFO = NO;
576 | MTL_FAST_MATH = YES;
577 | "OTHER_CODE_SIGN_FLAGS[sdk=*]" = "--timestamp";
578 | SDKROOT = macosx;
579 | SKIP_INSTALL = YES;
580 | SWIFT_COMPILATION_MODE = wholemodule;
581 | SWIFT_OPTIMIZATION_LEVEL = "-O";
582 | SWIFT_VERSION = 5.0;
583 | };
584 | name = Release;
585 | };
586 | 7C6D15872270BA300092D34E /* Debug */ = {
587 | isa = XCBuildConfiguration;
588 | buildSettings = {
589 | ARCHS = "$(ARCHS_STANDARD)";
590 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
591 | ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO;
592 | CODE_SIGN_ENTITLEMENTS = Notifier/Notifier.entitlements;
593 | CODE_SIGN_IDENTITY = "Apple Development";
594 | "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Developer ID Application";
595 | CODE_SIGN_STYLE = Manual;
596 | COMBINE_HIDPI_IMAGES = YES;
597 | DEAD_CODE_STRIPPING = YES;
598 | DEVELOPMENT_TEAM = "";
599 | "DEVELOPMENT_TEAM[sdk=macosx*]" = 82K2XFN8L6;
600 | ENABLE_HARDENED_RUNTIME = YES;
601 | ENABLE_USER_SCRIPT_SANDBOXING = NO;
602 | INFOPLIST_FILE = Notifier/Info.plist;
603 | INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
604 | INFOPLIST_KEY_LSUIElement = YES;
605 | LD_RUNPATH_SEARCH_PATHS = (
606 | "$(inherited)",
607 | "@executable_path/../Frameworks",
608 | );
609 | MACOSX_DEPLOYMENT_TARGET = 10.15;
610 | MARKETING_VERSION = 3.1;
611 | ONLY_ACTIVE_ARCH = YES;
612 | PRODUCT_BUNDLE_IDENTIFIER = uk.co.dataJAR.Notifier;
613 | PRODUCT_NAME = "$(TARGET_NAME)";
614 | PROVISIONING_PROFILE_SPECIFIER = "";
615 | SKIP_INSTALL = NO;
616 | SWIFT_INCLUDE_PATHS = "";
617 | SWIFT_VERSION = 5.0;
618 | };
619 | name = Debug;
620 | };
621 | 7C6D15882270BA300092D34E /* Release */ = {
622 | isa = XCBuildConfiguration;
623 | buildSettings = {
624 | ARCHS = "$(ARCHS_STANDARD)";
625 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
626 | ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO;
627 | CODE_SIGN_ENTITLEMENTS = Notifier/Notifier.entitlements;
628 | CODE_SIGN_IDENTITY = "Apple Development";
629 | "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Developer ID Application";
630 | CODE_SIGN_STYLE = Manual;
631 | COMBINE_HIDPI_IMAGES = YES;
632 | DEAD_CODE_STRIPPING = YES;
633 | DEVELOPMENT_TEAM = "";
634 | "DEVELOPMENT_TEAM[sdk=macosx*]" = 82K2XFN8L6;
635 | ENABLE_HARDENED_RUNTIME = YES;
636 | ENABLE_USER_SCRIPT_SANDBOXING = NO;
637 | INFOPLIST_FILE = Notifier/Info.plist;
638 | INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
639 | INFOPLIST_KEY_LSUIElement = YES;
640 | LD_RUNPATH_SEARCH_PATHS = (
641 | "$(inherited)",
642 | "@executable_path/../Frameworks",
643 | );
644 | MACOSX_DEPLOYMENT_TARGET = 10.15;
645 | MARKETING_VERSION = 3.1;
646 | ONLY_ACTIVE_ARCH = NO;
647 | PRODUCT_BUNDLE_IDENTIFIER = uk.co.dataJAR.Notifier;
648 | PRODUCT_NAME = "$(TARGET_NAME)";
649 | PROVISIONING_PROFILE_SPECIFIER = "";
650 | SKIP_INSTALL = NO;
651 | SWIFT_INCLUDE_PATHS = "";
652 | SWIFT_VERSION = 5.0;
653 | };
654 | name = Release;
655 | };
656 | 848FC89F2B61BB20009B4915 /* Debug */ = {
657 | isa = XCBuildConfiguration;
658 | buildSettings = {
659 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
660 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
661 | CODE_SIGN_ENTITLEMENTS = "Notifier - Notifications/Notifier - Notifications.entitlements";
662 | CODE_SIGN_IDENTITY = "Apple Development";
663 | "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Developer ID Application";
664 | CODE_SIGN_STYLE = Manual;
665 | COMBINE_HIDPI_IMAGES = YES;
666 | DEVELOPMENT_TEAM = "";
667 | "DEVELOPMENT_TEAM[sdk=macosx*]" = 82K2XFN8L6;
668 | ENABLE_HARDENED_RUNTIME = YES;
669 | GCC_C_LANGUAGE_STANDARD = gnu17;
670 | GENERATE_INFOPLIST_FILE = YES;
671 | INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
672 | INFOPLIST_KEY_LSUIElement = YES;
673 | INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2024 dataJAR Ltd. All rights reserved.";
674 | INFOPLIST_KEY_NSMainNibFile = MainMenu;
675 | INFOPLIST_KEY_NSPrincipalClass = NSApplication;
676 | LD_RUNPATH_SEARCH_PATHS = (
677 | "$(inherited)",
678 | "@executable_path/../Frameworks",
679 | );
680 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
681 | MACOSX_DEPLOYMENT_TARGET = 10.15;
682 | MARKETING_VERSION = 3.1;
683 | NEW_SETTING = "";
684 | ONLY_ACTIVE_ARCH = YES;
685 | PRODUCT_BUNDLE_IDENTIFIER = uk.co.dataJAR.NotifierNotifications;
686 | PRODUCT_NAME = "$(TARGET_NAME)";
687 | PROVISIONING_PROFILE_SPECIFIER = "";
688 | SKIP_INSTALL = YES;
689 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
690 | SWIFT_EMIT_LOC_STRINGS = YES;
691 | SWIFT_VERSION = 5.0;
692 | };
693 | name = Debug;
694 | };
695 | 848FC8A02B61BB20009B4915 /* Release */ = {
696 | isa = XCBuildConfiguration;
697 | buildSettings = {
698 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
699 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
700 | CODE_SIGN_ENTITLEMENTS = "Notifier - Notifications/Notifier - Notifications.entitlements";
701 | CODE_SIGN_IDENTITY = "Apple Development";
702 | "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Developer ID Application";
703 | CODE_SIGN_STYLE = Manual;
704 | COMBINE_HIDPI_IMAGES = YES;
705 | DEVELOPMENT_TEAM = "";
706 | "DEVELOPMENT_TEAM[sdk=macosx*]" = 82K2XFN8L6;
707 | ENABLE_HARDENED_RUNTIME = YES;
708 | GCC_C_LANGUAGE_STANDARD = gnu17;
709 | GENERATE_INFOPLIST_FILE = YES;
710 | INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
711 | INFOPLIST_KEY_LSUIElement = YES;
712 | INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2024 dataJAR Ltd. All rights reserved.";
713 | INFOPLIST_KEY_NSMainNibFile = MainMenu;
714 | INFOPLIST_KEY_NSPrincipalClass = NSApplication;
715 | LD_RUNPATH_SEARCH_PATHS = (
716 | "$(inherited)",
717 | "@executable_path/../Frameworks",
718 | );
719 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
720 | MACOSX_DEPLOYMENT_TARGET = 10.15;
721 | MARKETING_VERSION = 3.1;
722 | NEW_SETTING = "";
723 | PRODUCT_BUNDLE_IDENTIFIER = uk.co.dataJAR.NotifierNotifications;
724 | PRODUCT_NAME = "$(TARGET_NAME)";
725 | PROVISIONING_PROFILE_SPECIFIER = "";
726 | SKIP_INSTALL = YES;
727 | SWIFT_EMIT_LOC_STRINGS = YES;
728 | SWIFT_VERSION = 5.0;
729 | };
730 | name = Release;
731 | };
732 | 84E1A6C32B61C00500D7F7C9 /* Debug */ = {
733 | isa = XCBuildConfiguration;
734 | buildSettings = {
735 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
736 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
737 | CODE_SIGN_ENTITLEMENTS = "Notifier - Alerts/Notifier - Alerts.entitlements";
738 | CODE_SIGN_IDENTITY = "Apple Development";
739 | "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Developer ID Application";
740 | CODE_SIGN_STYLE = Manual;
741 | COMBINE_HIDPI_IMAGES = YES;
742 | DEVELOPMENT_TEAM = "";
743 | "DEVELOPMENT_TEAM[sdk=macosx*]" = 82K2XFN8L6;
744 | ENABLE_HARDENED_RUNTIME = YES;
745 | GCC_C_LANGUAGE_STANDARD = gnu17;
746 | GENERATE_INFOPLIST_FILE = YES;
747 | INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
748 | INFOPLIST_KEY_LSUIElement = YES;
749 | INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2024 dataJAR Ltd. All rights reserved.";
750 | INFOPLIST_KEY_NSPrincipalClass = NSApplication;
751 | LD_RUNPATH_SEARCH_PATHS = (
752 | "$(inherited)",
753 | "@executable_path/../Frameworks",
754 | );
755 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
756 | MACOSX_DEPLOYMENT_TARGET = 10.15;
757 | MARKETING_VERSION = 3.1;
758 | ONLY_ACTIVE_ARCH = YES;
759 | PRODUCT_BUNDLE_IDENTIFIER = uk.co.dataJAR.NotifierAlerts;
760 | PRODUCT_NAME = "$(TARGET_NAME)";
761 | PROVISIONING_PROFILE_SPECIFIER = "";
762 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
763 | SWIFT_EMIT_LOC_STRINGS = YES;
764 | SWIFT_VERSION = 5.0;
765 | };
766 | name = Debug;
767 | };
768 | 84E1A6C42B61C00500D7F7C9 /* Release */ = {
769 | isa = XCBuildConfiguration;
770 | buildSettings = {
771 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
772 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
773 | CODE_SIGN_ENTITLEMENTS = "Notifier - Alerts/Notifier - Alerts.entitlements";
774 | CODE_SIGN_IDENTITY = "Apple Development";
775 | "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Developer ID Application";
776 | CODE_SIGN_STYLE = Manual;
777 | COMBINE_HIDPI_IMAGES = YES;
778 | DEVELOPMENT_TEAM = "";
779 | "DEVELOPMENT_TEAM[sdk=macosx*]" = 82K2XFN8L6;
780 | ENABLE_HARDENED_RUNTIME = YES;
781 | GCC_C_LANGUAGE_STANDARD = gnu17;
782 | GENERATE_INFOPLIST_FILE = YES;
783 | INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
784 | INFOPLIST_KEY_LSUIElement = YES;
785 | INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2024 dataJAR Ltd. All rights reserved.";
786 | INFOPLIST_KEY_NSPrincipalClass = NSApplication;
787 | LD_RUNPATH_SEARCH_PATHS = (
788 | "$(inherited)",
789 | "@executable_path/../Frameworks",
790 | );
791 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
792 | MACOSX_DEPLOYMENT_TARGET = 10.15;
793 | MARKETING_VERSION = 3.1;
794 | PRODUCT_BUNDLE_IDENTIFIER = uk.co.dataJAR.NotifierAlerts;
795 | PRODUCT_NAME = "$(TARGET_NAME)";
796 | PROVISIONING_PROFILE_SPECIFIER = "";
797 | SWIFT_EMIT_LOC_STRINGS = YES;
798 | SWIFT_VERSION = 5.0;
799 | };
800 | name = Release;
801 | };
802 | /* End XCBuildConfiguration section */
803 |
804 | /* Begin XCConfigurationList section */
805 | 7C6D15732270BA2F0092D34E /* Build configuration list for PBXProject "Notifier" */ = {
806 | isa = XCConfigurationList;
807 | buildConfigurations = (
808 | 7C6D15842270BA300092D34E /* Debug */,
809 | 7C6D15852270BA300092D34E /* Release */,
810 | );
811 | defaultConfigurationIsVisible = 0;
812 | defaultConfigurationName = Release;
813 | };
814 | 7C6D15862270BA300092D34E /* Build configuration list for PBXNativeTarget "Notifier" */ = {
815 | isa = XCConfigurationList;
816 | buildConfigurations = (
817 | 7C6D15872270BA300092D34E /* Debug */,
818 | 7C6D15882270BA300092D34E /* Release */,
819 | );
820 | defaultConfigurationIsVisible = 0;
821 | defaultConfigurationName = Release;
822 | };
823 | 848FC89E2B61BB20009B4915 /* Build configuration list for PBXNativeTarget "Notifier - Notifications" */ = {
824 | isa = XCConfigurationList;
825 | buildConfigurations = (
826 | 848FC89F2B61BB20009B4915 /* Debug */,
827 | 848FC8A02B61BB20009B4915 /* Release */,
828 | );
829 | defaultConfigurationIsVisible = 0;
830 | defaultConfigurationName = Release;
831 | };
832 | 84E1A6C22B61C00500D7F7C9 /* Build configuration list for PBXNativeTarget "Notifier - Alerts" */ = {
833 | isa = XCConfigurationList;
834 | buildConfigurations = (
835 | 84E1A6C32B61C00500D7F7C9 /* Debug */,
836 | 84E1A6C42B61C00500D7F7C9 /* Release */,
837 | );
838 | defaultConfigurationIsVisible = 0;
839 | defaultConfigurationName = Release;
840 | };
841 | /* End XCConfigurationList section */
842 |
843 | /* Begin XCRemoteSwiftPackageReference section */
844 | 845D68DF2B692A4800C5B467 /* XCRemoteSwiftPackageReference "swift-argument-parser" */ = {
845 | isa = XCRemoteSwiftPackageReference;
846 | repositoryURL = "https://github.com/apple/swift-argument-parser.git";
847 | requirement = {
848 | kind = upToNextMajorVersion;
849 | minimumVersion = 1.2.3;
850 | };
851 | };
852 | /* End XCRemoteSwiftPackageReference section */
853 |
854 | /* Begin XCSwiftPackageProductDependency section */
855 | 845D68E02B692A5100C5B467 /* ArgumentParser */ = {
856 | isa = XCSwiftPackageProductDependency;
857 | package = 845D68DF2B692A4800C5B467 /* XCRemoteSwiftPackageReference "swift-argument-parser" */;
858 | productName = ArgumentParser;
859 | };
860 | /* End XCSwiftPackageProductDependency section */
861 | };
862 | rootObject = 7C6D15702270BA2F0092D34E /* Project object */;
863 | }
864 |
--------------------------------------------------------------------------------