diff --git a/MANUAL-UNINSTALL.md b/MANUAL-UNINSTALL.md
new file mode 100644
index 0000000..7b535d0
--- /dev/null
+++ b/MANUAL-UNINSTALL.md
@@ -0,0 +1,30 @@
+
+
+# Manual Uninstall
+
+- Delete `Background Music.app` from `/Applications`.
+- Delete `Background Music Device.driver` from `/Library/Audio/Plug-Ins/HAL`.
+- Pause apps that are playing audio, if you can.
+- Restart `coreaudiod`:
+ (Open `/Applications/Utilities/Terminal.app` and paste the following at the prompt.)
+
+ ```shell
+ sudo launchctl kill SIGTERM system/com.apple.audio.coreaudiod || sudo killall coreaudiod
+ ```
+- Go to the Sound section in System Preferences and change your default output device at least once. (If you only have
+ one device now, either use `Audio MIDI Setup.app` to create a temporary aggregate device, restart any audio apps that
+ have stopped working or just restart your system.)
+
+## Optional
+
+- Delete `BGMXPCHelper.xpc` from `/usr/local/libexec` or possibly `/Library/Application Support/Background Music`.
+- Unregister BGMXPCHelper, delete its user and group, and delete its launchd.plist:
+
+ ```shell
+ sudo launchctl bootout system /Library/LaunchDaemons/com.bearisdriving.BGM.XPCHelper.plist
+ sudo rm /Library/LaunchDaemons/com.bearisdriving.BGM.XPCHelper.plist
+ sudo dscl . -delete /Users/_BGMXPCHelper
+ sudo dscl . -delete /Groups/_BGMXPCHelper
+ ```
+
+
diff --git a/README.md b/README.md
index 5c086a3..76b0bf5 100644
--- a/README.md
+++ b/README.md
@@ -61,6 +61,11 @@ apps playing audio.
one device now, either use `Audio MIDI Setup.app` to create a temporary aggregate device, restart any audio apps that
have stopped working or just restart your system.)
+### Manual Uninstall
+
+Try following the instructions in `MANUAL-UNINSTALL.md` if `uninstall.sh` fails. (You might consider submitting a bug
+report, too.)
+
## Troubleshooting
If Background Music crashes and system audio stops working, open the Sound panel in System Preferences and change your
diff --git a/uninstall.sh b/uninstall.sh
index 18391f2..d56aa3a 100755
--- a/uninstall.sh
+++ b/uninstall.sh
@@ -1,5 +1,29 @@
#!/bin/bash -e
+# This file is part of Background Music.
+#
+# Background Music is free software: you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation, either version 2 of the
+# License, or (at your option) any later version.
+#
+# Background Music is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Background Music. If not, see .
+
+#
+# uninstall.sh
+#
+# Copyright © 2016 Nick Jacques
+# Copyright © 2016 Kyle Neideck
+#
+# Removes BGMApp, BGMDriver and BGMXPCHelper from the system.
+#
+
bold=$(tput bold)
normal=$(tput sgr0)
@@ -8,60 +32,111 @@ driver_path="/Library/Audio/Plug-Ins/HAL/Background Music Device.driver"
xpc_path1="/usr/local/libexec/BGMXPCHelper.xpc"
xpc_path2="/Library/Application Support/Background Music/BGMXPCHelper.xpc"
+# Check that files/directories are at most this big before we delete them, just to be safe.
+max_size_mb_for_rm=5
+
file_paths=("${app_path}" "${driver_path}" "${xpc_path1}" "${xpc_path2}")
-launchd_plist="/Library/LaunchDaemons/com.bearisdriving.BGM.XPCHelper.plist"
+bgmapp_process_name="Background Music"
+
+launchd_plist_label="com.bearisdriving.BGM.XPCHelper"
+launchd_plist="/Library/LaunchDaemons/${launchd_plist_label}.plist"
user_group_name="_BGMXPCHelper"
-clear
-echo "${bold}You are about to uninstall BackgroundMusic and its components!${normal}"
-echo "Please pause all audio before continuing."
-echo "You must be able to run 'sudo' commands to continue."
-echo ""
-read -p "Continue (y/n)? " user_prompt
+# We move files to this temp directory and then move the directory to the user's trash at the end of the script.
+# Unfortunately, this means that if the user tries to use the "put back" feature the files will just go back to the
+# temp directory.
+trash_dir="$(mktemp -d -t UninstalledBackgroundMusicFiles)/"
-if [ "$user_prompt" == "y" ]; then
+# Takes a path to a file or directory and returns false if the file/directory is larger than $max_size_mb_for_rm.
+function size_check {
+ local size="$(du -sm "$1" 2>/dev/null | awk '{ print $1 }')"
+ [[ "${size}" =~ ^[0-9]+$ ]] && [[ "${size}" -le ${max_size_mb_for_rm} ]]
+}
+
+clear
+echo "${bold}You are about to uninstall Background Music and its components!${normal}"
+echo "Please pause all audio before continuing."
+echo "You must be able to run 'sudo' commands to continue. (But don't worry if you don't know what that means.)"
+echo ""
+read -p "Continue (y/N)? " user_prompt
+
+if [ "$user_prompt" == "y" ] || [ "$user_prompt" == "Y" ]; then
# Ensure that the user can use sudo
sudo -v
is_sudo=$?
if [[ "$is_sudo" -ne 0 ]]; then
- echo "ERROR: This script must be run by a user with sudo permissions"
+ echo "ERROR: This script must be run by a user with administrator (sudo) privileges."
exit 1
fi
echo ""
+ # Try to kill Background Music.app, in case it's running.
+ killall "${bgmapp_process_name}" &>/dev/null || true
+
+ # TODO: Use
+ # mdfind kMDItemCFBundleIdentifier = "com.bearisdriving.BGM.App"
+ # to offer alternatives if Background Music.app isn't installed to /Applications. Or we could open it with
+ # open -b "com.bearisdriving.BGM.App" -- delete-yourself
+ # and have Background Music.app delete itself and close when it gets the "delete-yourself" argument. Though
+ # that wouldn't be backwards compatible.
+
# Remove the files defined in file_paths
for path in "${file_paths[@]}"; do
- if [ -e "${path}" ]; then
- echo "Deleting \"${path}\""
- rm -rf "\"${path}\""
+ if [ -e "${path}" ] && size_check "${path}"; then
+ echo "Moving \"${path}\" to the trash."
+ sudo mv -f "${path}" "${trash_dir}" &>/dev/null
fi
done
- echo "Removing BackgroundMusic launchd service"
- launchctl list | grep "${launchd_plist}" >/dev/null && sudo launchctl bootout system "${launchd_plist}" || echo " Service does not exist"
+ echo "Removing Background Music launchd service."
+ sudo launchctl list | grep "${launchd_plist_label}" >/dev/null && \
+ (sudo launchctl bootout system "${launchd_plist}" &>/dev/null || \
+ # Try an older version of the command in case the user has an old version of launchctl.
+ sudo launchctl unload "${launchd_plist}" >/dev/null) || \
+ echo " Service does not exist."
- echo "Removing BackgroundMusic launchd service configuration file"
+ echo "Removing Background Music launchd service configuration file."
if [ -e "${launchd_plist}" ]; then
- sudo rm "${launchd_plist}"
+ sudo mv -f "${launchd_plist}" "${trash_dir}"
fi
- echo "Removing BackgroundMusic user"
- dscl . -read /Users/"${user_group_name}" 2>/dev/null && sudo dscl . -delete /Users/"${user_group_name}" || echo " User does not exist"
+ # Be paranoid about user_group_name because we really don't want to delete every user account.
+ if ! [[ -z ${user_group_name} ]] && [[ "${user_group_name}" != "" ]]; then
+ echo "Removing Background Music user."
+ dscl . -read /Users/"${user_group_name}" &>/dev/null && \
+ sudo dscl . -delete /Users/"${user_group_name}" 1>/dev/null || \
+ echo " User does not exist."
- echo "Removing BackgroundMusic group"
- dscl . -read /Groups/"${user_group_name}" 2>/dev/null && sudo dscl . -delete /Groups/"${user_group_name}" || echo " Group does not exist"
+ echo "Removing Background Music group."
+ dscl . -read /Groups/"${user_group_name}" &>/dev/null && \
+ sudo dscl . -delete /Groups/"${user_group_name}" 1>/dev/null || \
+ echo " Group does not exist."
+ else
+ echo "Warning: could not delete the Background Music user/group due to an internal error in $0."
+ fi
+
+ # We're done removing files, so now actually move trash_dir into the trash.
+ osascript -e 'tell application "Finder" to move the POSIX file "'"${trash_dir}"'" to trash' >/dev/null
+
+ echo "Restarting Core Audio."
+ # The extra or-clauses are fallback versions of the command that restarts coreaudiod. Apparently some of these commands
+ # don't work with older versions of launchctl.
+ (sudo launchctl kill SIGTERM system/com.apple.audio.coreaudiod &>/dev/null || \
+ sudo launchctl kill -15 system/com.apple.audio.coreaudiod &>/dev/null || \
+ (sudo launchctl unload /System/Library/LaunchDaemons/com.apple.audio.coreaudiod.plist &>/dev/null && \
+ sudo launchctl load /System/Library/LaunchDaemons/com.apple.audio.coreaudiod.plist &>/dev/null) || \
+ sudo killall coreaudiod &>/dev/null) && \
+ sleep 5
- echo "Restarting CoreAudio"
- sudo launchctl kill SIGTERM system/com.apple.audio.coreaudiod && sleep 5
-
# Invalidate sudo ticket
sudo -k
echo -e "\n${bold}Done! Toggle your sound output device in the Sound control panel to complete the uninstall.${normal}"
+
osascript -e 'tell application "System Preferences"
activate
reveal anchor "output" of pane "Sound"
@@ -71,3 +146,5 @@ if [ "$user_prompt" == "y" ]; then
else
echo "Uninstall cancelled."
fi
+
+