Skip to content

Commit

Permalink
Split uninstall.sh into an interactive and non-interactive version.
Browse files Browse the repository at this point in the history
The non-interactive version can be called by a Homebrew Cask formula.

Also, change some AppleScript to reference applications by their IDs
rather than their names, which should make them slightly more robust.
  • Loading branch information
kyleneideck committed Jun 17, 2017
1 parent 3732ece commit 6d2fd39
Show file tree
Hide file tree
Showing 5 changed files with 180 additions and 125 deletions.
4 changes: 4 additions & 0 deletions BGMApp/BGMApp.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
1C533C7A1EED28B700270802 /* uninstall.sh in Resources */ = {isa = PBXBuildFile; fileRef = 1C533C791EED28B700270802 /* uninstall.sh */; };
1C533C7B1EED2F6200270802 /* safe_install_dir.sh in Resources */ = {isa = PBXBuildFile; fileRef = 276972901CB16008007A2F7C /* safe_install_dir.sh */; };
1C533C7C1EED2F8A00270802 /* com.bearisdriving.BGM.XPCHelper.plist.template in Resources */ = {isa = PBXBuildFile; fileRef = 2769728D1CAFCEFD007A2F7C /* com.bearisdriving.BGM.XPCHelper.plist.template */; };
1C533C801EF532CA00270802 /* _uninstall-non-interactive.sh in Resources */ = {isa = PBXBuildFile; fileRef = 1C533C7F1EF532CA00270802 /* _uninstall-non-interactive.sh */; };
1CB8B33D1BBA75EF000E2DD1 /* BGMAppDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1CB8B33C1BBA75EF000E2DD1 /* BGMAppDelegate.mm */; };
1CB8B33F1BBA75EF000E2DD1 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 1CB8B33E1BBA75EF000E2DD1 /* main.m */; };
1CC1DF811BE5068A00FB8FE4 /* CACFArray.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1CC1DF7D1BE5068A00FB8FE4 /* CACFArray.cpp */; };
Expand Down Expand Up @@ -216,6 +217,7 @@
1C46994D1BD7694C00F78043 /* BGMDeviceControlSync.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BGMDeviceControlSync.h; sourceTree = "<group>"; };
1C50FF641EC9F4500031A6EA /* libPublicUtility.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libPublicUtility.a; path = "../../../Library/Developer/Xcode/DerivedData/BGM-cgeucfvbrkmtbnhewbqjwrqspirp/Build/Products/Debug/libPublicUtility.a"; sourceTree = "<group>"; };
1C533C791EED28B700270802 /* uninstall.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; name = uninstall.sh; path = ../../uninstall.sh; sourceTree = "<group>"; };
1C533C7F1EF532CA00270802 /* _uninstall-non-interactive.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = "_uninstall-non-interactive.sh"; sourceTree = "<group>"; };
1C8034C21BDAFD5700668E00 /* CAPThread.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CAPThread.cpp; path = PublicUtility/CAPThread.cpp; sourceTree = "<group>"; };
1C8034C31BDAFD5700668E00 /* CAPThread.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CAPThread.h; path = PublicUtility/CAPThread.h; sourceTree = "<group>"; };
1CB8B3361BBA75EF000E2DD1 /* Background Music.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Background Music.app"; sourceTree = BUILT_PRODUCTS_DIR; };
Expand Down Expand Up @@ -520,6 +522,7 @@
1CED61681C3081C2002CAFCF /* LICENSE */,
1CC1DF951BE8607700FB8FE4 /* Images.xcassets */,
1CB8B33A1BBA75EF000E2DD1 /* Info.plist */,
1C533C7F1EF532CA00270802 /* _uninstall-non-interactive.sh */,
1C533C791EED28B700270802 /* uninstall.sh */,
1CB8B33E1BBA75EF000E2DD1 /* main.m */,
);
Expand Down Expand Up @@ -799,6 +802,7 @@
files = (
274827951E11052500B31D8D /* MainMenu.xib in Resources */,
1C533C7A1EED28B700270802 /* uninstall.sh in Resources */,
1C533C801EF532CA00270802 /* _uninstall-non-interactive.sh in Resources */,
1CED61691C3081C2002CAFCF /* LICENSE in Resources */,
1C2FC3041EB4D6E700A76592 /* BGMApp.sdef in Resources */,
1CC1DF961BE8607700FB8FE4 /* Images.xcassets in Resources */,
Expand Down
2 changes: 1 addition & 1 deletion BGMApp/BGMApp/Music Players/BGMVLC.m
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ - (BOOL) unpause {
//
// VLC's Scripting Bridge interface doesn't seem to have a cleaner way to do this.
+ (void) togglePlay {
NSString* src = @"tell application \"VLC\" to play";
NSString* src = @"tell application id \"org.videolan.vlc\" to play";
NSAppleScript* script = [[NSAppleScript alloc] initWithSource:src];
[script executeAndReturnError:nil];
}
Expand Down
156 changes: 156 additions & 0 deletions BGMApp/BGMApp/_uninstall-non-interactive.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
#!/bin/bash
# vim: tw=120:

# 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 <http://www.gnu.org/licenses/>.

#
# _uninstall-non-interactive.sh
#
# Copyright © 2016 Nick Jacques
# Copyright © 2016, 2017 Kyle Neideck
#
# Removes BGMApp, BGMDriver and BGMXPCHelper from the system immediately. Run by uninstall.sh and the Homebrew formula.
#

# TODO: Log commands and their output to uninstall.log, like build_and_install.sh does, rather than just sending
# everything to /dev/null.

# TODO: Show a custom error message if the script fails, like build_and_install.sh.

# Halt on errors.
set -e

PATH=/bin:/sbin:/usr/bin:/usr/sbin; export PATH

bold=$(tput bold)
normal=$(tput sgr0)

app_path="/Applications/Background Music.app"
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=15

file_paths=("${app_path}" "${driver_path}" "${xpc_path1}" "${xpc_path2}")

bgmapp_process_name="Background Music"

launchd_plist_label="com.bearisdriving.BGM.XPCHelper"
launchd_plist="/Library/LaunchDaemons/${launchd_plist_label}.plist"

coreaudiod_plist="/System/Library/LaunchDaemons/com.apple.audio.coreaudiod.plist"

user_group_name="_BGMXPCHelper"

# 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)/"

# 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} ]]
}

# Ensure that the user can use sudo. (But not if this is a Travis CI build, because then it would fail.)
if ([[ -z ${TRAVIS:-} ]] || [[ "${TRAVIS}" != true ]]) && ! sudo -v; then
echo "ERROR: This script must be run by a user with administrator (sudo) privileges." >&2
exit 1
fi

# 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
if size_check "${path}"; then
echo "Moving \"${path}\" to the trash."
sudo mv -f "${path}" "${trash_dir}" &>/dev/null
else
echo "Error: Refusing to delete \"${path}\" because it was much larger than expected." >&2
fi
fi
done

echo "Removing Background Music launchd service."
sudo launchctl list | grep "${launchd_plist_label}" >/dev/null && \
(sudo launchctl bootout system "${launchd_plist}" &>/dev/null || \
# Try older versions of the command in case the user has an old version of launchctl.
sudo launchctl unbootstrap system "${launchd_plist}" &>/dev/null || \
sudo launchctl unload "${launchd_plist}" >/dev/null) || \
echo " Service does not exist."

echo "Removing Background Music launchd service configuration file."
if [ -e "${launchd_plist}" ]; then
sudo mv -f "${launchd_plist}" "${trash_dir}"
fi

# 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 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." >&2
fi

# We're done removing files, so now actually move trash_dir into the trash. And if that fails, just delete it normally.
osascript -e 'tell application id "com.apple.finder"
move the POSIX file "'"${trash_dir}"'" to trash
end tell' >/dev/null 2>&1 \
|| rm -rf "${trash_dir}" \
|| true

echo "Restarting Core Audio."
# Wait a little because moving files to the trash plays a short sound.
sleep 2
# 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, so I figure there's no harm in trying a bunch of different ways until
# one works.
(sudo launchctl kill SIGTERM system/com.apple.audio.coreaudiod &>/dev/null || \
sudo launchctl kill TERM system/com.apple.audio.coreaudiod &>/dev/null || \
sudo launchctl kill 15 system/com.apple.audio.coreaudiod &>/dev/null || \
sudo launchctl kill -15 system/com.apple.audio.coreaudiod &>/dev/null || \
(sudo launchctl unload "${coreaudiod_plist}" &>/dev/null && \
sudo launchctl load "${coreaudiod_plist}" &>/dev/null) || \
sudo killall coreaudiod &>/dev/null)

echo "..."
sleep 3

# TODO: What if they only have one audio device?
echo ""
echo "${bold}Done! Toggle your audio output device in the Sound section of System Preferences to finish" \
"uninstalling. (Or just restart your computer.)${normal}"


2 changes: 2 additions & 0 deletions package.sh
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ cp -R "BGMApp/build/Release/BGMXPCHelper.xpc" "$scripts_dir"

set_permissions "pkgroot"
chmod 755 "pkgroot/Applications/Background Music.app/Contents/MacOS/Background Music"
chmod 755 "pkgroot/Applications/Background Music.app/Contents/Resources/uninstall.sh"
chmod 755 "pkgroot/Applications/Background Music.app/Contents/Resources/_uninstall-non-interactive.sh"
chmod 755 "pkgroot/Library/Audio/Plug-Ins/HAL/Background Music Device.driver/Contents/MacOS/Background Music Device"

set_permissions "$scripts_dir"
Expand Down
141 changes: 17 additions & 124 deletions uninstall.sh
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,11 @@
# uninstall.sh
#
# Copyright © 2016 Nick Jacques
# Copyright © 2016 Kyle Neideck
# Copyright © 2016, 2017 Kyle Neideck
#
# Removes BGMApp, BGMDriver and BGMXPCHelper from the system.
#

# TODO: Log commands and their output to uninstall.log, like build_and_install.sh does, rather than just sending
# everything to /dev/null.

# TODO: Show a custom error message if the script fails, like build_and_install.sh.

# Halt on errors.
set -e

Expand All @@ -38,143 +33,41 @@ PATH=/bin:/sbin:/usr/bin:/usr/sbin; export PATH
bold=$(tput bold)
normal=$(tput sgr0)

app_path="/Applications/Background Music.app"
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=15

file_paths=("${app_path}" "${driver_path}" "${xpc_path1}" "${xpc_path2}")

bgmapp_process_name="Background Music"

launchd_plist_label="com.bearisdriving.BGM.XPCHelper"
launchd_plist="/Library/LaunchDaemons/${launchd_plist_label}.plist"

coreaudiod_plist="/System/Library/LaunchDaemons/com.apple.audio.coreaudiod.plist"

user_group_name="_BGMXPCHelper"

# 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)/"

# 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

# Warn if running as root.
if [[ $(id -u) -eq 0 ]]; then
echo "$(tput setaf 11)WARNING$(tput sgr0): This script is not intended to be run as root. Run" \
"it normally and it'll sudo when it needs to." >&2
echo ""
fi

echo "${bold}You are about to uninstall Background Music and its components!${normal}"
echo "${bold}You are about to uninstall Background Music.${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. (But not if this is a Travis CI build, because then it would fail.)
if ([[ -z ${TRAVIS:-} ]] || [[ "${TRAVIS}" != true ]]) && ! sudo -v; then
echo "ERROR: This script must be run by a user with administrator (sudo) privileges." >&2
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
if size_check "${path}"; then
echo "Moving \"${path}\" to the trash."
sudo mv -f "${path}" "${trash_dir}" &>/dev/null
else
echo "Error: Refusing to delete \"${path}\" because it was much larger than expected."
fi
fi
done

echo "Removing Background Music launchd service."
sudo launchctl list | grep "${launchd_plist_label}" >/dev/null && \
(sudo launchctl bootout system "${launchd_plist}" &>/dev/null || \
# Try older versions of the command in case the user has an old version of launchctl.
sudo launchctl unbootstrap system "${launchd_plist}" &>/dev/null || \
sudo launchctl unload "${launchd_plist}" >/dev/null) || \
echo " Service does not exist."

echo "Removing Background Music launchd service configuration file."
if [ -e "${launchd_plist}" ]; then
sudo mv -f "${launchd_plist}" "${trash_dir}"
fi

# 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 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."
# Run from the dir containing this script.
cd "$( dirname "${BASH_SOURCE[0]}" )"

if [ -f "BGMApp/BGMApp/_uninstall-non-interactive.sh" ]; then
# Running from the source directory.
bash "BGMApp/BGMApp/_uninstall-non-interactive.sh"
elif [ -f "_uninstall-non-interactive.sh" ]; then
# Probably running from Background Music.app/Contents/Resources.
bash "_uninstall-non-interactive.sh"
else
echo "Warning: could not delete the Background Music user/group due to an internal error in $0."
echo "${bold}ERROR: Could not find _uninstall-non-interactive.sh${normal}" >&2
exit 1
fi

# We're done removing files, so now actually move trash_dir into the trash. And if that fails, just delete it normally.
osascript -e 'tell application "Finder" to move the POSIX file "'"${trash_dir}"'" to trash' >/dev/null 2>&1 \
|| rm -rf "${trash_dir}" \
|| true

echo "Restarting Core Audio."
# Wait a little because moving files to the trash plays a short sound.
sleep 2
# 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, so I figure there's no harm in trying a bunch of different ways until
# one works.
(sudo launchctl kill SIGTERM system/com.apple.audio.coreaudiod &>/dev/null || \
sudo launchctl kill TERM system/com.apple.audio.coreaudiod &>/dev/null || \
sudo launchctl kill 15 system/com.apple.audio.coreaudiod &>/dev/null || \
sudo launchctl kill -15 system/com.apple.audio.coreaudiod &>/dev/null || \
(sudo launchctl unload "${coreaudiod_plist}" &>/dev/null && \
sudo launchctl load "${coreaudiod_plist}" &>/dev/null) || \
sudo killall coreaudiod &>/dev/null) && \
echo "..." && \
sleep 3

# Invalidate sudo ticket
sudo -k

# TODO: What if they only have one audio device?
echo -e "\n${bold}Done! Toggle your sound output device in the Sound control panel to complete the uninstall.${normal}"

# Open System Preferences and go to Sound > Output.
osascript -e 'tell application "System Preferences"
activate
reveal anchor "output" of pane id "com.apple.preference.sound"
end tell' >/dev/null || true
osascript -e 'tell application id "com.apple.systempreferences"
activate
reveal anchor "output" of pane id "com.apple.preference.sound"
end tell' >/dev/null || true
echo ""

else
Expand Down

0 comments on commit 6d2fd39

Please sign in to comment.