diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..ce2c290 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,15 @@ +# +# EditorConfig for consistent coding styles. +# Ref: https://editorconfig.org +# + +root = true + +[*] +indent_style = space +indent_size = 2 +tab_width = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..923503a --- /dev/null +++ b/.gitattributes @@ -0,0 +1,11 @@ +# +# Exclude these files from release archives. +# + +.gitattributes export-ignore +.gitignore export-ignore + + +# Auto detect text files and perform LF normalization +* text=auto +eol=lf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e8d7a87 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +# +# Ignore +# + +*.swp +.DS_Store diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..d858611 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +The MIT License (MIT) + +© 2023 Nicolò Diamante + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..acc6915 --- /dev/null +++ b/README.md @@ -0,0 +1,180 @@ +

+ + + prune + +

+ +The [Launchpad][apple-launchpad] on a Mac serves as a quick access hub to all installed applications. With a simple gesture or a click, it displays an easy-to-navigate grid of app icons, providing a user-friendly way to launch applications without digging through the Applications folder. However, over time, as more applications get installed, the Launchpad can become cluttered with icons, including those of seldom-used or obsolete apps. This accumulation of icons can hinder the easy access to frequently used apps, transforming a feature initially intended for convenience into a source of potential annoyance. + +Prune is a script designed to automate the removal of specified apps from the Launchpad, helping maintain a clean, tidy, and efficient app launch interface. By specifying which apps you want to keep or remove through a simple configuration, Prune takes care of the rest. It delves into the system, meticulously removes the selected apps from the Launchpad, and leaves it in a clean and organised state. + +

+ +

+ + Launchpad + +

+
+ +## Getting Started + +### Installation + +The installation process of Prune is streamlined for simplicity. You can automatically download and install Prune via `curl` by executing the following command in your terminal: + +```bash +sh -c "$(curl -fsSL https://raw.githubusercontent.com/nicolodiamante/prune/HEAD/bootstrap.sh)" +``` + +If you prefer a more manual approach, you can clone the repository to your local machine using `git` with the command: + +```bash +git clone https://github.com/nicolodiamante/prune.git +``` + +After cloning the repository, navigate to its directory in the terminal, then move to the `utils` subdirectory and run the installation script `install.sh` as follows: + +```bash +source install.sh +``` + +When you run the installation script, it performs two main actions to set up Prune on your system. Firstly, it copies the agent files into the `~/Library/LaunchAgents` directory. This step ensures that the necessary agent files are placed in the correct location, allowing the system to recognise and execute them as needed. Secondly, the script adds an alias to your shell configuration file `.zshrc`, making it easier for you to invoke Prune from the terminal. + +```shell +# Launch the Prune script. +alias prune='$HOME/prune/scripts/prune.zsh' +``` + +These steps will ensure Prune is properly set up on your system, ready to assist you in managing your Launchpad efficiently and effortlessly. +

+ +## How It Works + +Prune operates through a trio of components: a configuration file, a script, and a plist file for automation. The heart of Prune lies in its straightforward configuration file named `apps`, where you specify which apps to keep or remove from the Launchpad. It's crucial to write down the app names exactly as they appear on your Mac, including spaces, capitalisation, etc., to ensure accurate matching. Each app name should be on a new line and enclosed in single quotes, like so: + +

+ + Prune Apps List + +

+
+ +By managing the list of app names in the `apps` file, you are essentially guiding Prune on which apps to retain or remove from the Launchpad. The core script, `.pruneops.zsh`, reads this file, translates your preferences into actions, and executes the necessary commands to tidy up your Launchpad. + +For continuous tidiness, Prune leverages a plist file, allowing it to run automatically each time your Mac starts. This plist file, loaded via `launchctl`, points to `.pruneops.zsh` ensuring that every time your Mac boots up, Prune checks the `apps` file and cleans up your Launchpad accordingly. + +Besides the automatic cleanup, you can also invoke Prune manually whenever needed. By typing `prune` in the terminal, you initiate the script. Upon entering your password, Prune swings into action, clearing away the unwanted apps from the Launchpad as specified in the `apps` file. + +

+ +

+ + + Prune Terminal + +

+

+ +This blend of automated and manual operation, fueled by a simple configuration, makes Prune a powerful tool for maintaining a clutter-free, organised Launchpad, whether you're at the helm or letting it run on autopilot.

+ +### How to Reset Launchpad Layout + +In case you wish to restore the Launchpad to its original state, erasing the customisations made by Prune, a simple command is built into the script for this purpose. By invoking `prune --default` or `prune -d` in the terminal, the Launchpad will revert back to its default layout, including all apps and their original organisation. This action utilises a native macOS command to reset the Launchpad, ensuring a safe and straightforward return to the default setup. It's a quick way to undo Prune's changes, should you ever want to start afresh with your Launchpad organisation

+ +### Loading and Unloading Prune Agent + +Prune's functionality is driven by a background agent that automates the cleaning of your Launchpad based on the preferences you've set. There might be instances where you'd want to stop the agent temporarily or start it after it has been stopped. Prune provides simple commands for these actions: + +To load the Prune agent: + +```bash +prune -l +``` + +To unload the Prune agent: + +```bash +prune -u +``` + +These commands allow you to control the Prune agent's activity on your system. + +### Verifying Prune Agent Status + +After loading or unloading the Prune agent, it's good practice to check its status to ensure it's operating as intended: + +```bash +prune -c +``` + +A successful check should return the following output, indicating that the Prune agent is active and loaded: + +

+ +

+ + + Prune checking Agent + +

+

+ +If the Prune agent is not loaded, there will be no output. + +This verification step provides assurance that Prune is set up correctly and ready to keep your Launchpad organized. It's a handy tool for troubleshooting and confirming the agent's operational status.

+ +### Accessing Help + +Prune comes with a built-in help option to provide quick access to its usage instructions right from the terminal. Whether you're unsure about how to reset the Launchpad layout or need a reminder about how to launch Prune, the help option is there to assist you. To access this, simply type `prune --help` or `prune -h` in the terminal. This will display a brief summary of available options and how to use them.

+ +## Notes + +### Restoring App Icons to Launchpad + +If you wish to re-add an application icon to the Launchpad after it has been removed by Prune, you'll need to manually add it back. This can be done easily by finding the application in your Applications folder, and then dragging and dropping the application icon onto the Launchpad icon in your Dock. Remember, Prune is here to help keep your Launchpad tidy by automating the removal of specified apps, but re-adding apps to the Launchpad is a manual process. + +

+ +

+ + Add apps to Launchpad + +

+ +
+ +### Resources + +- [Launchpad User Guide][apple-guide] + +### Contribution + +Any suggestions or feedback you may have for improvement are welcome. If you encounter any issues or bugs, please report them to the [issues page][issues]. +

+ +

+ + + +

+ +

+ + Nicolò Diamante + +

+ +

+ + + MIT License + +

+ + + +[apple-launchpad]: https://support.apple.com/en-gb/guide/mac-help/mh35840/mac +[apple-guide]: https://support.apple.com/en-gb/guide/mac-help/mh35840/mac +[issues]: https://github.com/nicolodiamante/prune/issues diff --git a/agent/com.shell.Prune.plist b/agent/com.shell.Prune.plist new file mode 100644 index 0000000..7856ea0 --- /dev/null +++ b/agent/com.shell.Prune.plist @@ -0,0 +1,16 @@ + + + + + Label + com.shell.Prune + ProgramArguments + + /bin/sh + -c + exec $HOME/prune/scripts/.pruneops.zsh + + RunAtLoad + + + diff --git a/apps b/apps new file mode 100644 index 0000000..99197f8 --- /dev/null +++ b/apps @@ -0,0 +1,8 @@ +# +# Prune - Manage a list of apps to exclude from the Launchpad. +# + + +applications=( + +) diff --git a/bootstrap.sh b/bootstrap.sh new file mode 100644 index 0000000..76c3e5c --- /dev/null +++ b/bootstrap.sh @@ -0,0 +1,37 @@ +#!/bin/sh + +# Determines the current user's shell. +[[ "$SHELL" == */zsh ]] || exit 1 + +SOURCE="https://github.com/nicolodiamante/prune" +TARBALL="${SOURCE}/tarball/master" +TARGET="${HOME}/prune" +TAR_CMD="tar -xzv -C \"${TARGET}\" --strip-components 1 --exclude .gitignore" + +INSTALL=./utils/install.sh + +is_executable() { + command -v "$1" > /dev/null 2>&1 +} + +# Checks which executable is available then downloads and installs. +if is_executable "git"; then + CMD="git clone ${SOURCE} ${TARGET}" +elif is_executable "curl"; then + CMD="curl -L ${TARBALL} | ${TAR_CMD}" +elif is_executable "wget"; then + CMD="wget --no-check-certificate -O - ${TARBALL} | ${TAR_CMD}" +fi + +if [[ -z "$CMD" ]]; then + echo 'No git, curl or wget available. Aborting!' +else + echo 'Installing prune...' + mkdir -p "$TARGET" + + if eval "$CMD"; then + cd "$TARGET" && source "$INSTALL" + else + echo "Installation failed. Aborting!" + fi +fi diff --git a/scripts/.pruneops.zsh b/scripts/.pruneops.zsh new file mode 100644 index 0000000..90f904f --- /dev/null +++ b/scripts/.pruneops.zsh @@ -0,0 +1,5 @@ +#!/bin/zsh + +# +# Prune - Slim Script. +# diff --git a/scripts/prune.zsh b/scripts/prune.zsh new file mode 100755 index 0000000..15090d6 --- /dev/null +++ b/scripts/prune.zsh @@ -0,0 +1,102 @@ +#!/bin/zsh + +# +# Prune - Remove superfluous icons in the Launchpad. +# + +# Directory paths +ROOT_DIR="${0:h}/../" +SCRIPTS_DIR="${ROOT_DIR}scripts/" + +# File containing the list of applications +APPLICATIONS_FILE="${ROOT_DIR}apps" + +# Temporary file for building the cleanup commands +TEMP_FILE="${SCRIPTS_DIR}temp_pruneops" + +# File containing the cleanup commands +PRUNEOPS_FILE="${SCRIPTS_DIR}.pruneops.zsh" + +# Function to display usage information +usage() { + echo "Usage: prune [option]" + echo "Options:" + echo " -l, --load Load the Prune agent." + echo " -u, --unload Unload the Prune agent." + echo " -d, --default Reset the Launchpad layout to its default state." + echo " -c, --check Check if the Prune agent is loaded." + echo " -h, --help Display this help message and exit." + echo "" + echo "For more help, visit: https://github.com/nicolodiamante/prune" +} + +LIB_AGENTS="${HOME}/Library/LaunchAgents" +AGENT="${LIB_AGENTS}/com.shell.Prune.plist" + +if [[ "$1" == "-h" || "$1" == "--help" ]]; then + usage + exit 0 +elif [[ "$1" == "-c" || "$1" == "--check" ]]; then + launchctl list | grep com.shell.Prune + exit 0 +elif [[ "$1" == "-l" || "$1" == "--load" ]]; then + if [[ -f "${AGENT}" ]]; then + launchctl load "${AGENT}" + else + echo "Prune agent plist file not found." >&2 + fi + exit 0 +elif [[ "$1" == "-u" || "$1" == "--unload" ]]; then + if [[ -f "${AGENT}" ]]; then + launchctl unload "${AGENT}" + else + echo "Prune agent plist file not found." >&2 + fi + exit 0 +fi + +# Function to update pruneops.zsh if the apps file has changed +update_pruneops() { + # Read the new list of applications from the applications file + applications=("${(@f)$(awk -F"'" '/\047/ {print $2}' "$APPLICATIONS_FILE")}") + + # Add shebang to the temporary file and leave a blank line + echo "" >> "$TEMP_FILE" + + # Start the cleanup commands with 'sudo' + echo -n "sudo " >> "$TEMP_FILE" + + # Build the cleanup commands + for app in "${applications[@]:0:${#applications[@]}-1}"; do + echo -n "sqlite3 \$(find /private/var/folders -name com.apple.dock.launchpad)/db/db \"DELETE FROM apps WHERE title='$app';\"; " >> "$TEMP_FILE" + done + + # Add the last command without the semicolon (;) divider, followed by '&& killall Dock' + echo -n "sqlite3 \$(find /private/var/folders -name com.apple.dock.launchpad)/db/db \"DELETE FROM apps WHERE title='${applications[-1]}';\" && killall Dock" >> "$TEMP_FILE" + + # Replace the existing pruneops.zsh file with the new commands + mv "$TEMP_FILE" "$PRUNEOPS_FILE" +} + +# Function to launch pruneops.zsh +manage_apps() { + typeset LAUNCHD_SCRIPTS="$PRUNEOPS_FILE" + # Remove superfluous icons from Launchpad. + source "${LAUNCHD_SCRIPTS}" > /dev/null 2>&1 && + echo 'prune: removed superfluous icons from the Launchpad!' +} + +# Check for '-d' flag to reset Launchpad to default +if [[ "$1" == "-d" || "$1" == "--default" ]]; then + echo 'Resetting Launchpad to default...' + sudo defaults write com.apple.dock ResetLaunchPad -bool true; killall Dock + exit 0 +fi + +# Update pruneops.zsh if it doesn't exist, is empty, or if the apps file is newer +if [[ ! -e "$PRUNEOPS_FILE" || ! -s "$PRUNEOPS_FILE" || "$APPLICATIONS_FILE" -nt "$PRUNEOPS_FILE" ]]; then + update_pruneops +fi + +# Run manage_apps each time, regardless of whether pruneops.zsh was updated +manage_apps diff --git a/utils/install.sh b/utils/install.sh new file mode 100755 index 0000000..e2357a3 --- /dev/null +++ b/utils/install.sh @@ -0,0 +1,51 @@ +#!/bin/sh + +# +# Install Prune +# + +# Validate OS and version +if [[ "$OSTYPE" != darwin* ]]; then + echo "This script is only compatible with macOS." >&2 + exit 1 +fi + +SW_VERS=$(sw_vers -buildVersion) +OS_VERS=$(sed -E -e 's/([0-9]{2}).*/\1/' <<<"$SW_VERS") +if [[ "$OS_VERS" -lt 21 ]]; then + echo "Prune requires macOS 12.6.1 Monterey or later." >&2 + exit 1 +fi + +# Define paths +LIB_AGENTS="${HOME}/Library/LaunchAgents" +AGENT_SOURCE="./agent/com.shell.Prune.plist" +AGENT_TARGET="${LIB_AGENTS}/com.shell.Prune.plist" +XDG_CONFIG_HOME="${HOME}/.config/zsh" +ZSHRC="${XDG_CONFIG_HOME:-$HOME}/.zshrc" + +# Create necessary directories +[[ ! -d "$LIB_AGENTS" ]] && mkdir -p "$LIB_AGENTS" + +# Create a symbolic link from the agent directory to LIB_AGENTS +ln -s "$AGENT_SOURCE" "$AGENT_TARGET" + +# Load the agent if it exists +if [[ -f "${AGENT_TARGET}" ]]; then + launchctl load "${AGENT_TARGET}" +fi + +# Update .zshrc +if [[ -f "$ZSHRC" ]]; then + # Append the necessary lines to zshrc. + cat << EOF >> ${ZSHRC} +# Launch the Prune script. +alias prune='$HOME/prune/scripts/prune.zsh' +EOF + echo "zsh: append prune's necessary lines to .zshrc" + + # Reloads shell. + source "${ZSHRC}" +else + echo 'zsh: zshrc not found!' >&2 +fi diff --git a/utils/uninstall.sh b/utils/uninstall.sh new file mode 100755 index 0000000..96cf29f --- /dev/null +++ b/utils/uninstall.sh @@ -0,0 +1,31 @@ +#!/bin/sh + +# +# Uninstall Prune +# + +# Define paths +LIB_AGENTS="${HOME}/Library/LaunchAgents" +AGENT="${LIB_AGENTS}/com.shell.Prune.plist" + +# Remove the agent from launchd if the plist file exists +if [[ -f "${AGENT}" ]]; then + launchctl remove "${AGENT}" + if [[ $? -eq 0 ]]; then + echo "Successfully removed the agent." + else + echo "Failed to remove the agent." >&2 + fi +fi + +# Check if the file is a symlink before attempting to remove +if [[ -L "${AGENT}" ]]; then + # Remove the agent file + if rm -f "${AGENT}"; then + echo "Successfully removed the agent symlink." + else + echo "Failed to remove the agent symlink." >&2 + fi +else + echo "The agent file is not a symlink or does not exist." >&2 +fi