├── LICENSE ├── README.md └── notificator /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Notificator 2 | 3 | Notificator is a command-line tool to show notifications from [Alfred](https://www.alfredapp.com) Workflows with their icon. 4 | 5 | Screenshot 2025-04-01 at 21 32 19 6 | 7 | Screenshot 2025-04-01 at 21 32 33 8 | 9 | ## Installation 10 | 11 | Download the executable at the root of this repository and include it in your workflow. 12 | 13 | ## Usage 14 | 15 | ``` 16 | Trigger macOS notifications from Alfred, using the Workflow icon. 17 | 18 | Message is mandatory. Other flags are optional. 19 | 20 | Usage: 21 | notificator --message [--title ] [--subtitle ] [--sound ] 22 | 23 | Options: 24 | -m, --message Message text. 25 | -t, --title Title text. 26 | -s, --subtitle Subtitle text. 27 | -p, --sound Sound name (from /System/Library/Sounds). 28 | -h, --help Show this help. 29 | ``` 30 | 31 | ## How it works 32 | 33 | While it is possible to trigger notifications from AppleScript, those show a generic icon. To have a custom icon in full view, an app is required. Notificator’s purpose is to create these small specialised apps. 34 | 35 | The icon is extracted from the Workflows’s root directory and the bundle identifier is based on the Workflow’s. The first time the script is run on a user’s machine, the app is seamlessly created to the Workflow’s cache directory and macOS prompts the user to allow notifications. On subsequent runs, the cached app is run directly. The app is rebuilt if the Workflow icon was modified since the last run. 36 | 37 | ## Prohibitory Symbol 38 | 39 | [A macOS bug](https://web.archive.org/web/20230615021755/https://macmule.com/2021/10/28/notifications-showing-a-prohibitory-symbol-after-upgrading-macos-monterey/) may cause notifications to show a prohibitory symbol on top of the app icon. Logout or restart macOS to fix it. 40 | -------------------------------------------------------------------------------- /notificator: -------------------------------------------------------------------------------- 1 | #!/bin/zsh --no-rcs 2 | 3 | #################################################### 4 | ### Created by Vítor Galvão ### 5 | ### Find the latest version at: ### 6 | ### https://github.com/vitorgalvao/notificator ### 7 | #################################################### 8 | 9 | readonly program="${0:t}" 10 | 11 | # Helpers 12 | function error { 13 | echo "${1}" >&2 14 | exit 1 15 | } 16 | 17 | function show_notification { 18 | /usr/bin/open "${app}" --args "${notificator_message}" "${notificator_title}" "${notificator_subtitle}" "${notificator_sound}" 19 | } 20 | 21 | # Usage 22 | function usage { 23 | echo " 24 | Trigger macOS notifications from Alfred, using the Workflow icon. 25 | 26 | Message is mandatory. Other flags are optional. 27 | 28 | Usage: 29 | ${program} --message [--title ] [--subtitle ] [--sound ] 30 | 31 | Options: 32 | -m, --message Message text. 33 | -t, --title Title text. 34 | -s, --subtitle Subtitle text. 35 | -p, --sound Sound name (from /System/Library/Sounds). 36 | -h, --help Show this help. 37 | " | sed -E 's/^ {4}//' 38 | } 39 | 40 | # Options 41 | args=() 42 | while [[ "${1}" ]] 43 | do 44 | case "${1}" in 45 | -h | --help) 46 | usage 47 | exit 0 48 | ;; 49 | -m | --message) 50 | readonly notificator_message="${2}" 51 | shift 52 | ;; 53 | -t | --title) 54 | readonly notificator_title="${2}" 55 | shift 56 | ;; 57 | -s | --subtitle) 58 | readonly notificator_subtitle="${2}" 59 | shift 60 | ;; 61 | -p | --sound) 62 | readonly notificator_sound="${2}" 63 | shift 64 | ;; 65 | --) 66 | shift 67 | args+=("${@}") 68 | break 69 | ;; 70 | -*) 71 | echo "Unrecognised option: ${1}" 72 | exit 1 73 | ;; 74 | *) 75 | args+=("${1}") 76 | ;; 77 | esac 78 | shift 79 | done 80 | set -- "${args[@]}" 81 | 82 | # Check for required arguments 83 | [[ -n "${notificator_message}" ]] || error 'A message is mandatory! Aborting…' 84 | 85 | readonly bundle_id="$(/usr/bin/tr -cd '[:alnum:]._-' <<< "${alfred_workflow_bundleid}")" 86 | readonly name="$(/usr/bin/tr -cd '[:alnum:]._- ' <<< "${alfred_workflow_name}")" 87 | readonly icon="${alfred_preferences}/workflows/${alfred_workflow_uid}/icon.png" 88 | readonly checksum_icon="$(/usr/bin/shasum "${icon}" | cut -d ' ' -f 1)" 89 | readonly app="${alfred_workflow_cache}/Notificator for ${name}.app" 90 | readonly plist="${app}/Contents/Info.plist" 91 | 92 | # Exit early if Notificator exists and workflow icon is unmodified (its checksum equals the last field of the bundle ID) 93 | if [[ -d "${app}" && "${checksum_icon}" == "$(/usr/libexec/PlistBuddy -c "print :CFBundleIdentifier" "${plist}" | /usr/bin/awk -F '.' '{print $NF}')" ]] 94 | then 95 | show_notification 96 | exit 0 97 | fi 98 | 99 | # Pre-build checks 100 | [[ -n "${bundle_id}" ]] || error "Workflow is missing bundle identifier! Aborting…" 101 | [[ -n "${name}" ]] || error "Workflow is missing name! Aborting…" 102 | [[ -f "${icon}" ]] || error "Workflow is missing icon! Aborting…" 103 | 104 | # Build Notificator 105 | readonly jxa_script=' 106 | const argv = $.NSProcessInfo.processInfo.arguments.js.map(arg => arg.js) 107 | const app = Application.currentApplication() 108 | app.includeStandardAdditions = true 109 | 110 | if (argv.length < 2) { // The script will always see at least one argument: the applet itself 111 | argv[1] = "Opening usage instructions…" 112 | argv[2] = "Notificator is a command-line app" 113 | argv[4] = "Sosumi" 114 | 115 | app.openLocation("https://github.com/vitorgalvao/notificator#usage") 116 | } 117 | 118 | const message = argv[1] 119 | const title = argv[2] 120 | const subtitle = argv[3] 121 | const sound = argv[4] 122 | 123 | const options = {} 124 | if (title) options.withTitle = title 125 | if (subtitle) options.subtitle = subtitle 126 | if (sound) options.soundName = sound 127 | 128 | app.displayNotification(message, options) 129 | ' 130 | 131 | [[ -d "${app}" ]] && /bin/rm -r "${app}" 132 | /bin/mkdir -p "${alfred_workflow_cache}" 133 | /usr/bin/osacompile -l JavaScript -o "${app}" -e "${jxa_script}" 2> /dev/null 134 | 135 | # Modify Notificator 136 | /usr/libexec/PlistBuddy -c "add :CFBundleIdentifier string ${bundle_id}.notificator.${checksum_icon}" "${plist}" 137 | /usr/libexec/PlistBuddy -c 'add :LSUIElement string 1' "${plist}" 138 | 139 | # Redo signature 140 | /usr/bin/codesign --remove-signature "${app}" 141 | /usr/bin/codesign --sign - "${app}" 142 | 143 | # Modify icon 144 | # Must be done after signing to not get "resource fork, Finder informartion, or similar detritus not allowed" error 145 | /usr/bin/osascript -l JavaScript -e ' 146 | ObjC.import("AppKit") 147 | 148 | function run(argv) { 149 | const image = $.NSImage.alloc.initWithContentsOfFile(argv[0]) 150 | $.NSWorkspace.sharedWorkspace.setIconForFileOptions(image, argv[1], 0) 151 | } 152 | ' "${icon}" "${app}" 153 | 154 | # Notification 155 | show_notification 156 | --------------------------------------------------------------------------------