macOS User Notifications: what changed
Revision 0 to 1. Removed lines have a minus sign; added lines have a plus sign.
Title: macOS User Notifications
| Old | New | Line | |
|---|---|---|---|
| 1 | + | *(macOS only)* | |
| 2 | + | ||
| 3 | + | > **Warning:** The NSUserNotification class was deprecated in macOS 10.14 (Mojave); the code still works in macOS 10.15 (Catalina); in macOS 11.0 (Big Sur) and in macOS 12 (Monterey), the notification daemon ignores the action and logs "error usernoted Denying message 3 from connection \<LegacyConnection identifier: com.company.project1 type: Application\>". The replacement appears to be the UserNotifications framework introduced in macOS 10.14. See below for Apple documentation. | |
| 4 | + | ||
| 5 | + | ## User notification unit | |
| 6 | + | ||
| 7 | + | This is the user notification unit which can be used by an application to deliver notifications to the user: | |
| 8 | + | ||
| 9 | + | ``` pascal | |
| 10 | + | unit UserNotification; | |
| 11 | + | ||
| 12 | + | interface | |
| 13 | + | ||
| 14 | + | {$ifdef Darwin} | |
| 15 | + | {$mode objfpc}{$H+} | |
| 16 | + | {$modeswitch objectivec1} | |
| 17 | + | {$linkframework Foundation} | |
| 18 | + | ||
| 19 | + | uses | |
| 20 | + | Classes, | |
| 21 | + | MacOSAll, | |
| 22 | + | CocoaUtils, | |
| 23 | + | CocoaAll; | |
| 24 | + | ||
| 25 | + | const | |
| 26 | + | NSUserNotificationActivationTypeNone = 0; | |
| 27 | + | NSUserNotificationActivationTypeContentsClicked = 1; | |
| 28 | + | NSUserNotificationActivationTypeActionButtonClicked = 2; | |
| 29 | + | ||
| 30 | + | type | |
| 31 | + | NSUserNotificationPtr = ^NSUserNotification; | |
| 32 | + | NSUserNotificationCenterPtr = ^NSUserNotificationCenter; | |
| 33 | + | NSUserNotificationCenterDelegateProtocolPtr = ^NSUserNotificationCenterDelegateProtocol; | |
| 34 | + | ||
| 35 | + | NSUserNotificationActivationType = NSInteger; | |
| 36 | + | NSUserNotificationActivationTypePtr = ^NSUserNotificationActivationType; | |
| 37 | + | ||
| 38 | + | NSUserNotification = objcclass external (NSObject) | |
| 39 | + | private | |
| 40 | + | _internal: id; | |
| 41 | + | public | |
| 42 | + | procedure setTitle(newValue: NSString); message 'setTitle:'; | |
| 43 | + | function title: NSString; message 'title'; | |
| 44 | + | procedure setSubtitle(newValue: NSString); message 'setSubtitle:'; | |
| 45 | + | function subtitle: NSString; message 'subtitle'; | |
| 46 | + | procedure setInformativeText(newValue: NSString); message 'setInformativeText:'; | |
| 47 | + | function informativeText: NSString; message 'informativeText'; | |
| 48 | + | procedure setActionButtonTitle(newValue: NSString); message 'setActionButtonTitle:'; | |
| 49 | + | function actionButtonTitle: NSString; message 'actionButtonTitle'; | |
| 50 | + | procedure setUserInfo(newValue: NSDictionary); message 'setUserInfo:'; | |
| 51 | + | function userInfo: NSDictionary; message 'userInfo'; | |
| 52 | + | procedure setDeliveryDate(newValue: NSDate); message 'setDeliveryDate:'; | |
| 53 | + | function deliveryDate: NSDate; message 'deliveryDate'; | |
| 54 | + | procedure setDeliveryTimeZone(newValue: NSTimeZone); message 'setDeliveryTimeZone:'; | |
| 55 | + | function deliveryTimeZone: NSTimeZone; message 'deliveryTimeZone'; | |
| 56 | + | procedure setDeliveryRepeatInterval(newValue: NSDateComponents); message 'setDeliveryRepeatInterval:'; | |
| 57 | + | function deliveryRepeatInterval: NSDateComponents; message 'deliveryRepeatInterval'; | |
| 58 | + | function actualDeliveryDate: NSDate; message 'actualDeliveryDate'; | |
| 59 | + | function isPresented: boolean; message 'isPresented'; | |
| 60 | + | function isRemote: boolean; message 'isRemote'; | |
| 61 | + | procedure setSoundName(newValue: NSString); message 'setSoundName:'; | |
| 62 | + | function soundName: NSString; message 'soundName'; | |
| 63 | + | procedure setHasActionButton(newValue: boolean); message 'setHasActionButton:'; | |
| 64 | + | function hasActionButton: boolean; message 'hasActionButton'; | |
| 65 | + | function activationType: NSUserNotificationActivationType; message 'activationType'; | |
| 66 | + | procedure setOtherButtonTitle(newValue: NSString); message 'setOtherButtonTitle:'; | |
| 67 | + | function otherButtonTitle: NSString; message 'otherButtonTitle'; | |
| 68 | + | end; | |
| 69 | + | ||
| 70 | + | NSUserNotificationCenter = objcclass external (NSObject) | |
| 71 | + | private | |
| 72 | + | _internal: id; | |
| 73 | + | public | |
| 74 | + | class function defaultUserNotificationCenter: NSUserNotificationCenter; message 'defaultUserNotificationCenter'; | |
| 75 | + | procedure setDelegate(newValue: NSObject); message 'setDelegate:'; | |
| 76 | + | function delegate: NSUserNotificationCenterDelegateProtocol; message 'delegate'; | |
| 77 | + | procedure setScheduledNotifications(newValue: NSArray); message 'setScheduledNotifications:'; | |
| 78 | + | function scheduledNotifications: NSArray; message 'scheduledNotifications'; | |
| 79 | + | procedure scheduleNotification (notification: NSUserNotification); message 'scheduleNotification:'; | |
| 80 | + | procedure removeScheduledNotification (notification: NSUserNotification); message 'removeScheduledNotification:'; | |
| 81 | + | function deliveredNotifications: NSArray; message 'deliveredNotifications'; | |
| 82 | + | procedure deliverNotification (notification: NSUserNotification); message 'deliverNotification:'; | |
| 83 | + | procedure removeDeliveredNotification (notification: NSUserNotification); message 'removeDeliveredNotification:'; | |
| 84 | + | procedure removeAllDeliveredNotifications; message 'removeAllDeliveredNotifications'; | |
| 85 | + | end; | |
| 86 | + | ||
| 87 | + | NSUserNotificationCenterDelegateProtocol = objcprotocol external name 'NSUserNotificationCenterDelegate' (NSObjectProtocol) | |
| 88 | + | optional | |
| 89 | + | procedure userNotificationCenter_didDeliverNotification (center: NSUserNotificationCenter; notification: NSUserNotification); message 'userNotificationCenter:didDeliverNotification:'; | |
| 90 | + | procedure userNotificationCenter_didActivateNotification (center: NSUserNotificationCenter; notification: NSUserNotification); message 'userNotificationCenter:didActivateNotification:'; | |
| 91 | + | function userNotificationCenter_shouldPresentNotification (center: NSUserNotificationCenter; notification: NSUserNotification): boolean; message 'userNotificationCenter:shouldPresentNotification:'; | |
| 92 | + | end; | |
| 93 | + | ||
| 94 | + | NSUserNotificationCenterAlwaysShowDelegate = objcclass(NSObject) | |
| 95 | + | public | |
| 96 | + | function shouldPresentNotification ({%H-}center: NSUserNotificationCenter; {%H-}notification: NSUserNotification): boolean; message 'userNotificationCenter:shouldPresentNotification:'; | |
| 97 | + | end; | |
| 98 | + | ||
| 99 | + | // Procedure to call - without sound | |
| 100 | + | procedure DeliverUserNotification(const Title, Subtitle, Message: string); | |
| 101 | + | ||
| 102 | + | // Procedure to call - with sound | |
| 103 | + | procedure DeliverUserNotification(const Title, Subtitle, Message, Sound: string); | |
| 104 | + | ||
| 105 | + | var | |
| 106 | + | NSUserNotificationDefaultSoundName: NSString { available in 10_8, NA }; cvar; external; | |
| 107 | + | ||
| 108 | + | {$endif} | |
| 109 | + | ||
| 110 | + | implementation | |
| 111 | + | ||
| 112 | + | {$ifdef Darwin} | |
| 113 | + | ||
| 114 | + | var | |
| 115 | + | UserNotificationCenterDelegate: NSUserNotificationCenterAlwaysShowDelegate; | |
| 116 | + | ||
| 117 | + | function NSUserNotificationCenterAlwaysShowDelegate.shouldPresentNotification (center: NSUserNotificationCenter; notification: NSUserNotification): boolean; | |
| 118 | + | begin | |
| 119 | + | Result := True; | |
| 120 | + | end; | |
| 121 | + | ||
| 122 | + | procedure DeliverUserNotification(const Title, Subtitle, Message: string); | |
| 123 | + | begin | |
| 124 | + | DeliverUserNotification(Title, Subtitle, Message, CFStringToStr(CFStringRef(NSUserNotificationDefaultSoundName))); | |
| 125 | + | end; | |
| 126 | + | ||
| 127 | + | procedure DeliverUserNotification(const Title, Subtitle, Message, Sound: string); | |
| 128 | + | var | |
| 129 | + | Notification: NSUserNotification; | |
| 130 | + | TmpNSStr: NSString; | |
| 131 | + | ||
| 132 | + | begin | |
| 133 | + | Notification := NSUserNotification.alloc.init; | |
| 134 | + | ||
| 135 | + | try | |
| 136 | + | TmpNSStr := NSString(CFStringCreateWithPascalString(kCFAllocatorDefault, Title, kCFStringEncodingUTF8)); | |
| 137 | + | ||
| 138 | + | try | |
| 139 | + | Notification.setTitle(TmpNSStr); | |
| 140 | + | ||
| 141 | + | finally | |
| 142 | + | TmpNSStr.release; | |
| 143 | + | end; | |
| 144 | + | ||
| 145 | + | TmpNSStr := NSString(CFStringCreateWithPascalString(kCFAllocatorDefault, Subtitle, kCFStringEncodingUTF8)); | |
| 146 | + | ||
| 147 | + | try | |
| 148 | + | Notification.setSubtitle(TmpNSStr); | |
| 149 | + | ||
| 150 | + | finally | |
| 151 | + | TmpNSStr.release; | |
| 152 | + | end; | |
| 153 | + | ||
| 154 | + | TmpNSStr := NSString(CFStringCreateWithPascalString(kCFAllocatorDefault, Message, kCFStringEncodingUTF8)); | |
| 155 | + | ||
| 156 | + | try | |
| 157 | + | Notification.setInformativeText(TmpNSStr); | |
| 158 | + | ||
| 159 | + | finally | |
| 160 | + | TmpNSStr.release; | |
| 161 | + | end; | |
| 162 | + | ||
| 163 | + | if Sound <> '' then | |
| 164 | + | begin | |
| 165 | + | TmpNSStr := NSString(CFStringCreateWithPascalString(kCFAllocatorDefault, Sound, kCFStringEncodingUTF8)); | |
| 166 | + | ||
| 167 | + | try | |
| 168 | + | Notification.setSoundName(TmpNSStr); | |
| 169 | + | ||
| 170 | + | finally | |
| 171 | + | TmpNSStr.release; | |
| 172 | + | end; | |
| 173 | + | end; | |
| 174 | + | ||
| 175 | + | NSUserNotificationCenter.defaultUserNotificationCenter.deliverNotification(Notification); | |
| 176 | + | ||
| 177 | + | finally | |
| 178 | + | Notification.release; | |
| 179 | + | end; | |
| 180 | + | end; | |
| 181 | + | ||
| 182 | + | initialization | |
| 183 | + | UserNotificationCenterDelegate := NSUserNotificationCenterAlwaysShowDelegate.alloc.init; | |
| 184 | + | NSUserNotificationCenter.defaultUserNotificationCenter.setDelegate(UserNotificationCenterDelegate); | |
| 185 | + | ||
| 186 | + | finalization | |
| 187 | + | NSUserNotificationCenter.defaultUserNotificationCenter.setDelegate(nil); | |
| 188 | + | UserNotificationCenterDelegate.release; | |
| 189 | + | ||
| 190 | + | {$endif} | |
| 191 | + | ||
| 192 | + | end. | |
| 193 | + | ``` | |
| 194 | + | ||
| 195 | + | ## How to use the unit | |
| 196 | + | ||
| 197 | + | You can deliver notifications to the user: | |
| 198 | + | ||
| 199 | + | - With the default notification sound, call: *procedure DeliverUserNotification(const Title, Subtitle, Message: string);* | |
| 200 | + | ||
| 201 | + | - With a custom notification sound, call: *procedure DeliverUserNotification(const Title, Subtitle, Message, Sound: string);* | |
| 202 | + | ||
| 203 | + | - With no notification sound, call: *procedure DeliverUserNotification(const Title, Subtitle, Message, Sound: string);* and pass an empty Sound string. | |
| 204 | + | ||
| 205 | + | ### Examples | |
| 206 | + | ||
| 207 | + | ``` pascal | |
| 208 | + | ... | |
| 209 | + | ||
| 210 | + | procedure TForm1.Button1Click(Sender: TObject); | |
| 211 | + | begin | |
| 212 | + | DeliverUserNotification('Title Test', 'Subtitle Test', 'Message', ''); // No sound | |
| 213 | + | end; | |
| 214 | + | ||
| 215 | + | ... | |
| 216 | + | ||
| 217 | + | procedure TForm1.Button2Click(Sender: TObject); | |
| 218 | + | begin | |
| 219 | + | DeliverUserNotification('Title Test', 'Subtitle Test', 'Message', 'Submarine'); // Custom sound from Sound Preferences | |
| 220 | + | end; | |
| 221 | + | ||
| 222 | + | ... | |
| 223 | + | ||
| 224 | + | procedure TForm1.Button3Click(Sender: TObject); | |
| 225 | + | begin | |
| 226 | + | DeliverUserNotification('Title Test', 'Subtitle Test', 'Message'); // Default Notification Alert sound | |
| 227 | + | end; | |
| 228 | + | ||
| 229 | + | ... | |
| 230 | + | ``` | |
| 231 | + | ||
| 232 | + | ## Preferences \> Notifications | |
| 233 | + | ||
| 234 | + | Be aware that in this user-centric world that is macOS, the user has the ability to override many of your carefully crafted notification attributes via the Notifications Preference pane. This includes the ability to turn notifications from your application off completely by choosing "None" and unchecking all the checkboxes. | |
| 235 | + | ||
| 236 | + |  | |
| 237 | + | ||
| 238 | + | ## External links | |
| 239 | + | ||
| 240 | + | - [Apple: NSUserNotification](https://developer.apple.com/documentation/foundation/nsusernotification) - deprecated in macOS 10.14 (Mojave). | |
| 241 | + | - [Apple: UNUserNotificationCenter](https://developer.apple.com/documentation/usernotifications) - debuted in macOS 10.14 (Mojave). | |
| 242 | + | ||
| 243 | + | --- | |
| 244 | + | *From the Free Pascal wiki, page "macOS User Notifications", as it stood on 2021-10-28 after a change by Trev, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).* | |
| 245 | + | Category: Audio | |
| 246 | + | Category: Code Snippets | |
| 247 | + | Category: Platform-sensitive development | |
| 248 | + | Category: macOS |
