Home

Awesome

Yo: Custom User Notifications with Swift

Overview

Yo example

Yo is a simple app for sending custom, persistent native Cocoa notifications to the Notification Center in macOS 10.10+. It allows customizing all of the various notification text fields and button labels, the sound effect played, and the application to open when the (optional) action button has been clicked. Further, it allows you to configure the application icon to be displayed with the notification (with caveats), and configure some of the launch properties, like whether to "poof" when clicked, and how to display when the screen is locked.

Included with the Yo app is a launcher utility named yo_scheduler. Yo allows you to call it from the commandline, but only when a user is actively logged into a GUI session (and generally, by that user). That's where the launcher script steps in. yo_scheduler takes ideas from the excellent Outset tool to guarantee that configured notifications will be delivered to all users on a machine at least once. This is of critical importance to Mac administrators who need to reliably notify enterprise users of pending changes or critical information. Yo and yo_scheduler are management tool agnostic, and have been used by Munki and Casper administrators around the world.

Yo differs from terminal-notifier in that it creates persistent notifications that remain in place until clicked. As such, it allows you to customize these buttons and their actions. Also, it allows you to customize the application icon displayed.

If you just want a notification, download the current installer package from the releases page.

You can customize the icon used in the notifications by providing the -i argument. However, if you want to permanently customize the icon/app name/etc, you can modify the XCode project and build it yourself. Instructions are provided below.

Finally, thanks to @jatoben for the gracious sharing of his Swift CommandLine library/Framework on GitHub.

Build & Installation

You only need to follow these instructions if you want to build the app yourself. Obviously, you'll need a recent XCode.

  1. Git clone or download the project.
  2. Open the project in XCode and set the App Icon to your desired icon. Please read the Icon section below for more info on what is going on here. To change the icon provided with yo, open the project in XCode and navigate to the Images.xcassets file in the file browser. Simply drag a 128x128px replacement png over the one already in place. Optionally, if you want more icon sizes, feel free to go nuts and fill them all in.
  3. Build. (CMD-B)
  4. Copy the built app (Contextual-click on the Products->yo.app->Show in Finder) wherever you see fit, although /Applications/Utilities seems like a suitable place. Alternately, you can use the Product->Archive menu option to export a copy of the app or build an installer package (if you have a developer ID).

Note: If you Run/(CMD-R) from XCode, it will just report back usage with a command line parsing error. Just ignore that and run from the command line.

Package build and deployment

The pkg folder includes a Makefile for use with the luggage that builds a package installer, should you need to build one yourself. The Makefile configures the package to install the yo.app bundle in /Applications/Utilities, and the scheduler script at /usr/local/bin/yo_scheduler. Feel free to customize these as you see fit.

Usage

The yo installer package adds a command line script to /usr/local/bin/yo_scheduler which is the preferred method for calling yo. If you are building the app with custom icons, feel free to copy this script wherever is convenient, although /usr/local/bin/ is in the default PATH.

Due to its install location being in the default PATH, you can then call yo by simply typing yo_scheduler -t "This is amazing", for example. (yo_scheduler -h will give you full usage information).

The yo_scheduler script creates a "scheduled notification". Through a system of LaunchAgents and LaunchDaemons, yo will ensure that each user gets the notification delivered to them at the soonest possible opportunity. Active console users will get the notification immediately. Other users will get the notification when they next log in.

Note:

The yo app by itself, if opened via double-clicking the app, running from Spotlight/Launchpad, etc, does nothing. It must be called with arguments, and the actual binary yo.app/Contents/MacOS/yo is what is executable. However, this only works if a user is currently logged in.

If you are experiencing weird hanging or no notifications being sent, check to make sure yo isn't already running. For automated messaging via a management system's triggers, it is recommended that you stick to using the scheduler script as per above. If you just really don't want to use the scheduler, make sure that your tooling is running Yo in the right user context. Which logged in user is the notification for? Get their UID and then run yo by running launchctl: launchctl asuser <UID> /path/to/yo.app/Contents/MacOS/yo <arguments>. But really, use the scheduler because it solves all of these problems and more.

Arguments:

Yo, as called through the yo_scheduler, has the following arguments. The only required argument is the -t/--title argument. Normally, admins should schedule notifications using the root user, either as a scripted action through their management framework, or using the sudo utility to elevate privileges. For testing purposes, you can run a notification once for the current user by executing the yo_scheduler as that user. No notifications will be stored for delivery.

Test all notifications prior to delivery, as the maximum line-length of characters differs for the title, subtitle, and info fields, which in turn can be compressed even shorter when the length of the action or cancel buttons grows beyond 8 characters.

This notification demonstrates the available content areas. Not all of these are required! Yo example

Notification Body Content Arguments

The following arguments control the body text for the notification. When the buttons fit in their normal sizes, they have the below mentioned maximum character legnths. However, the layout of the notification is elastic to allow longer button text at the expense of body text.

Notification Button Arguments

By default, with no button arguments, notifications have a single button, labeled Close, which dismisses the otherwise persistent notification. The following arguments allow you to configure the "action" button text, and what it does when clicked, and to customize the cancel button's text.

Delivery Arguments

Other Arguments

Emoji

Emoji characters are allowed, although getting them into a bash command line context is tricky.

You can drag an emoji from the Special Characters palette. That's the easiest way.

Otherwise, if you need the hex code, find it in the Special Characters and go to bash:

echo -ne '<drag the emoji here>' | hexdump

This will print out the hex code for the emoji. It's the rightmost column of values that are in hex. You have to slap a \x before each two-characters, and then you can echo it back up as a command substitution...

Complete process:

$ echo -ne '💩' | hexdump
0000000 f0 9f 92 a9                                    
0000004

# So 'f09f92a9' is the hex value...
$ yo_scheduler -t $(echo -ne '\xf0\x9f\x92\xa9')

= Smiling poo emoji notification.

You can also do printf '\xf0\x9f\x92\xa9'.

Examples

# Example of basic notification:
yo_scheduler -t "Taco Time"

# Example with lots of text:
yo_scheduler -t "Taco Time" -s "Chorizo is best." -n "Although I also enjoy al pastor of course."

# Example with emoji and tons of escaped characters:
yo_scheduler -t "$(printf "\xf0\x9f\x92\xa9\n") says, \"Let's dance$(printf \!)\""

# Example with action button, opening a webpage in your default browser:
yo_scheduler -t "Taco Time" -b "Yum" -a "http://en.wikipedia.org/wiki/Taco"

# Example opening an app:
yo_scheduler -t "Taco Time" -b "Yum" -a "/Applications/TacoParty.app"

# Example-What if you want a one-button persistent notification that doesn't *do* anything?
yo_scheduler -t "Taco Time" -o "Accept"

# Example-alternate icon using the -i argument
yo_scheduler -t "Taco Time" -i "/Users/blanconino/Pictures/taco.png"

# Example-custom sound and bash script with escaped characters.
yo_scheduler -t "Taco Time" -z "Taco" -b "Eat" -B "say 'I hope you enjoyed your tacos\!'"

Catalina+

Starting with macOS 10.15 "Catalina", unmanaged software triggering user notifications will spawn a permissions dialog. Administrators can deliver a NotificationSettings payload from their MDM to whitelist Yo and prevent this permissions dialog from interfering with the delivery of important messages.

Application Icon, Caveats, and Nerdery

Icons

Most organizations will probably want yo to display a custom icon in the notification. There are a couple of different ways notifications determine what to use for the icon:

  1. By default, a notification uses the icon for the application sending the notification.
  2. Using a private API, an application can specify an image file to use for the icon.

yo uses option 2 if you use the --icon option. However, since this is a private mechanism used by Apple to show album art in iTunes notifications, the application icon will still show up, just smaller, and to the side of the primary icon.

So if you really just want to use your icon, you need to build the project in XCode yourself. And even so, there are some issues about getting it to "know" that you've changed the icon.

Notification Center is picky about the icon it displays for your notification. If a notification has been sent previously, a subsequent icon change will not be detected or updated by Notification Center, as the original one has been cached.

Therefore, it is easiest to apply the icon you want before sending any notifications for the first time to avoid any hassles. If you get stuck with the wrong icon, you should be able to change the project Bundle Identifier in the project->General pane; or you can increment the build number in the same place. Arguably, you would want to change the Bundle Identifier to match your organization anyway.

See here for more info.

The other nerdery

Normally, for a notification to be persistent (meaning, it stays onscreen until a button is clicked on the notification), the notification needs to be an "alert" style notification. To do this, yo sets the Info.plist key "NSUserNotificationAlertStyle" with value "alert". However, Apple does not allow just anyone to make "alerts". To use an alert in your app, the app has to be signed with a developer ID.

Or does it... There's another private API key that allows you to show buttons on a "banner", which is the default type, and available for unsigned apps. So that's what yo uses.

Of course, your results may vary. If you can't get the "alert" style notification to appear, try signing the project with your Developer ID and rebuilding.

The scheduler stores notifications to be delivered in the CFPreferences system. The scheduled notifications are (eventually) stored in /Library/Preferences/com.sheagcraig.yo.plist, and the delivery receipts are stored in ~/Library/Preferences/com.sheagcraig.yo.plist. To grow future scheduling options, the structure of these preferences will probably change, so do not rely on the current structure.

What's next?

The next step in Yo's development will be to get it ready for Swift 4 and macOS 10.13, and to add to the scheduling arguments provided by the yo_scheduler app.