Created
August 7, 2025 10:00
-
-
Save RafaelMoreira1180778/085e540d4b727e3ec95cd49e00b91247 to your computer and use it in GitHub Desktop.
Update MacOS Icons
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Custom Icon-to-App Mappings | |
| # Format: IconName=App Name Pattern | |
| # | |
| # Supports glob patterns: *, ?, [abc], [a-z] | |
| # Examples: | |
| # Chrome=*Chrome* | |
| # Office=Microsoft [EWP]* | |
| # CustomApp=My Custom App | |
| # Default app mappings (moved from setup_icons.sh) | |
| ActivityMonitor=Activity Monitor | |
| AdobeIllustrator=Adobe Illustrator* | |
| AffinityDesigner=Affinity Designer* | |
| AffinityPhoto=Affinity Photo* | |
| AffinityPublisher=Affinity Publisher* | |
| AfterEffects=Adobe After Effects* | |
| AppleMusic=Music | |
| AppleTV=TV | |
| AppStore=App Store | |
| Chrome=Google Chrome | |
| CreativeCloud=Adobe Creative Cloud | |
| Excel=Microsoft Excel | |
| FinalCut=Final Cut Pro | |
| FindMy=Find My | |
| Firefox=Firefox | |
| Intellij=IntelliJ IDEA* | |
| JetBrainsToolbox=JetBrains Toolbox | |
| Lightroom=Adobe Lightroom* | |
| Photoshop=Adobe Photoshop* | |
| Powerpoint=Microsoft PowerPoint | |
| Premiere=Adobe Premiere Pro* | |
| PyCharm=PyCharm* | |
| Rider=JetBrains Rider | |
| Settings=System Preferences | |
| VoiceMemos=Voice Memos | |
| VSCode=Visual Studio Code | |
| Webstorm=WebStorm | |
| Word=Microsoft Word | |
| Zen=Zen Browser | |
| Amphetamine=Amphetamine | |
| AnkerWork=AnkerWork | |
| Aptakube=Aptakube | |
| Bitwarden=Bitwarden | |
| ChatGPT=ChatGPT | |
| CompanyPortal=Company Portal | |
| Docker=Docker | |
| Falcon=Falcon | |
| Ghostty=Ghostty | |
| GPGKeychain=GPG Keychain | |
| Ice=Ice | |
| iTerm=iTerm | |
| JabraDirect=Jabra Direct | |
| JabraFirmwareUpdate=Jabra Firmware Update | |
| KarabinerElements=Karabiner-Elements | |
| KarabinerEventViewer=Karabiner-EventViewer | |
| KeePassXC=KeePassXC | |
| Keynote=Keynote | |
| KUsrTsk=KUsrTsk | |
| LansweeperAgent=LansweeperAgent | |
| lghub=lghub | |
| logioptionsplus=logioptionsplus | |
| Maccy=Maccy | |
| Magnet=Magnet | |
| MicrosoftEdge=Microsoft Edge | |
| MicrosoftExcel=Microsoft Excel | |
| MicrosoftOneNote=Microsoft OneNote | |
| MicrosoftOutlook=Microsoft Outlook | |
| MicrosoftPowerPoint=Microsoft PowerPoint | |
| MicrosoftTeams=Microsoft Teams | |
| MicrosoftWord=Microsoft Word | |
| NeoHtop=NeoHtop | |
| Numbers=Numbers | |
| OneDrive=OneDrive | |
| Orion=Orion | |
| Pages=Pages | |
| Pearcleaner=Pearcleaner | |
| pgAdmin4=pgAdmin 4 | |
| Rectangle=Rectangle | |
| Safari=Safari | |
| Spotify=Spotify | |
| Utilities=Utilities | |
| VisualStudioCode=Visual Studio Code | |
| WhatsApp=WhatsApp |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| # | |
| # Custom Icon Setup Script | |
| # Applies custom icons to installed macOS applications using fileicon | |
| # | |
| # Usage: ./setup_icons.sh [options] | |
| # Check Bash version (minimum required: 4) | |
| if [[ "${BASH_VERSINFO[0]}" -lt 4 ]]; then | |
| echo "This script requires Bash 4 or higher. Your version: $BASH_VERSION" >&2 | |
| exit 1 | |
| fi | |
| set -euo pipefail | |
| ####################################### | |
| # Configuration | |
| ####################################### | |
| readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" | |
| readonly ICONS_DIR="${SCRIPT_DIR}/icons.mac" | |
| readonly CONFIG_FILE="${SCRIPT_DIR}/app_mappings.conf" | |
| readonly APP_DIRS=("/Applications" "${HOME}/Applications" "/System/Applications") | |
| readonly MAX_DEPTH=3 | |
| # Counters | |
| SUCCESS_COUNT=0 | |
| ERROR_COUNT=0 | |
| SKIP_COUNT=0 | |
| # Options | |
| VERBOSE=false | |
| QUIET=false | |
| LOG_FILE="" | |
| declare -A APP_MAPPINGS | |
| ####################################### | |
| # Utility functions | |
| ####################################### | |
| log() { | |
| local level="$1" | |
| shift | |
| local message="$*" | |
| case "$level" in | |
| "ERROR") | |
| [[ "$QUIET" != "true" ]] && echo "β $message" >&2 | |
| ;; | |
| "WARN") | |
| [[ "$QUIET" != "true" ]] && echo "β οΈ $message" >&2 | |
| ;; | |
| "INFO") | |
| [[ "$QUIET" != "true" ]] && echo "$message" | |
| ;; | |
| "VERBOSE") | |
| [[ "$VERBOSE" == "true" ]] && echo "βΉοΈ $message" | |
| ;; | |
| esac | |
| if [[ -n "$LOG_FILE" ]]; then | |
| echo "[$(date +'%Y-%m-%d %H:%M:%S')] $level: $message" >>"$LOG_FILE" | |
| fi | |
| } | |
| usage() { | |
| cat <<'EOF' | |
| Custom Icon Setup Script | |
| USAGE: | |
| setup_icons.sh [OPTIONS] | |
| DESCRIPTION: | |
| Applies custom icons to installed macOS applications using fileicon. | |
| Icons are matched by name from the icons/ directory to applications. | |
| OPTIONS: | |
| -v, --verbose Enable verbose output | |
| -q, --quiet Suppress non-error output | |
| -l, --log PATH Save log to specified file | |
| -h, --help Show this help message | |
| EXAMPLES: | |
| ./setup_icons.sh # Apply icons with default settings | |
| ./setup_icons.sh -v # Verbose output | |
| ./setup_icons.sh -l setup.log # Save log to file | |
| CONFIGURATION: | |
| Create app_mappings.conf to customize icon-to-app mappings: | |
| IconName=App Name Pattern | |
| EOF | |
| } | |
| ####################################### | |
| # Configuration management | |
| ####################################### | |
| load_config() { | |
| # Load mappings from config file | |
| if [[ -f "$CONFIG_FILE" ]]; then | |
| log "VERBOSE" "Loading configuration from $CONFIG_FILE" | |
| while IFS='=' read -r icon_name app_pattern; do | |
| # Skip empty lines and comments | |
| [[ -z "$icon_name" || "$icon_name" =~ ^[[:space:]]*# ]] && continue | |
| # Trim whitespace | |
| icon_name=$(echo "$icon_name" | xargs) | |
| app_pattern=$(echo "$app_pattern" | xargs) | |
| if [[ -n "$icon_name" && -n "$app_pattern" ]]; then | |
| APP_MAPPINGS["$icon_name"]="$app_pattern" | |
| log "VERBOSE" "Loaded mapping: $icon_name -> $app_pattern" | |
| fi | |
| done <"$CONFIG_FILE" | |
| else | |
| log "WARN" "Configuration file not found: $CONFIG_FILE" | |
| log "INFO" "Icons will be matched by filename only" | |
| fi | |
| } | |
| create_sample_config() { | |
| if [[ ! -f "$CONFIG_FILE" ]]; then | |
| log "INFO" "Creating sample configuration file: $CONFIG_FILE" | |
| cat >"$CONFIG_FILE" <<'EOF' | |
| # Custom Icon-to-App Mappings | |
| # Format: IconName=App Name Pattern | |
| # | |
| # Supports glob patterns: *, ?, [abc], [a-z] | |
| # Examples: | |
| # Chrome=*Chrome* | |
| # Office=Microsoft [EWP]* | |
| # CustomApp=My Custom App | |
| # Add your custom mappings below: | |
| EOF | |
| else | |
| log "VERBOSE" "Configuration file already exists: $CONFIG_FILE" | |
| fi | |
| } | |
| ####################################### | |
| # Dependency management | |
| ####################################### | |
| check_dependencies() { | |
| if ! command -v fileicon >/dev/null 2>&1; then | |
| log "ERROR" "fileicon is not installed" | |
| if command -v brew >/dev/null 2>&1; then | |
| log "INFO" "Installing fileicon using Homebrew..." | |
| if brew install fileicon; then | |
| log "INFO" "β fileicon successfully installed" | |
| else | |
| log "ERROR" "Failed to install fileicon" | |
| return 1 | |
| fi | |
| else | |
| log "ERROR" "Please install fileicon: brew install fileicon" | |
| log "ERROR" "Or install Homebrew first: https://brew.sh" | |
| return 1 | |
| fi | |
| fi | |
| return 0 | |
| } | |
| ####################################### | |
| # App finding logic | |
| ####################################### | |
| contains_glob() { | |
| local string="$1" | |
| [[ "$string" == *"*"* || "$string" == *"?"* || "$string" == *"["* ]] | |
| } | |
| find_apps_with_glob() { | |
| local app_dir="$1" | |
| local pattern="$2" | |
| [[ ! -d "$app_dir" ]] && return 1 | |
| # Convert glob pattern to regex for find | |
| local regex_pattern | |
| regex_pattern=$(echo "$pattern" | sed 's/\*/.*$/g' | sed 's/\?/./g') | |
| find "$app_dir" -maxdepth "$MAX_DEPTH" -type d -name "*.app" 2>/dev/null | | |
| while IFS= read -r app_path; do | |
| local app_name | |
| app_name="$(basename "$app_path" .app)" | |
| if [[ "$app_name" =~ ^${regex_pattern} ]]; then | |
| echo "$app_path" | |
| fi | |
| done | |
| } | |
| find_app_exact() { | |
| local app_dir="$1" | |
| local app_name="$2" | |
| [[ ! -d "$app_dir" ]] && return 1 | |
| # Try exact match first | |
| local app_path | |
| app_path=$(find "$app_dir" -maxdepth "$MAX_DEPTH" -type d -name "${app_name}.app" -print -quit 2>/dev/null) | |
| [[ -n "$app_path" ]] && echo "$app_path" && return 0 | |
| # Try case-insensitive match | |
| app_path=$(find "$app_dir" -maxdepth "$MAX_DEPTH" -type d -iname "${app_name}.app" -print -quit 2>/dev/null) | |
| [[ -n "$app_path" ]] && echo "$app_path" && return 0 | |
| return 1 | |
| } | |
| find_app_for_icon() { | |
| local icon_name="$1" | |
| local app_name="$icon_name" | |
| # Check for explicit mapping | |
| if [[ -n "${APP_MAPPINGS[$icon_name]:-}" ]]; then | |
| app_name="${APP_MAPPINGS[$icon_name]}" | |
| log "VERBOSE" "Found mapping: '$icon_name' -> '$app_name'" | |
| fi | |
| # Search in all application directories | |
| for app_dir in "${APP_DIRS[@]}"; do | |
| [[ ! -d "$app_dir" ]] && continue | |
| local app_path="" | |
| if contains_glob "$app_name"; then | |
| # Use glob matching | |
| local matches | |
| if matches=$(find_apps_with_glob "$app_dir" "$app_name"); then | |
| app_path=$(echo "$matches" | head -n1) | |
| local match_count | |
| match_count=$(echo "$matches" | wc -l) | |
| if [[ "$match_count" -gt 1 ]]; then | |
| log "VERBOSE" "Multiple matches for pattern '$app_name': $match_count apps, using first" | |
| fi | |
| fi | |
| else | |
| # Use exact matching | |
| app_path=$(find_app_exact "$app_dir" "$app_name") | |
| fi | |
| if [[ -n "$app_path" ]]; then | |
| echo "$app_path" | |
| return 0 | |
| fi | |
| done | |
| return 1 | |
| } | |
| ####################################### | |
| # System app detection | |
| ####################################### | |
| is_system_app() { | |
| local app_path="$1" | |
| local app_name | |
| app_name="$(basename "$app_path" .app)" | |
| # Apps in /System/Applications are always system apps | |
| [[ "$app_path" == "/System/Applications/"* ]] && return 0 | |
| # Known system apps protected by SIP | |
| local system_apps=( | |
| "Safari" | |
| ) | |
| for system_app in "${system_apps[@]}"; do | |
| [[ "$app_name" == "$system_app" ]] && return 0 | |
| done | |
| return 1 | |
| } | |
| ####################################### | |
| # Icon application | |
| ####################################### | |
| apply_icon() { | |
| local icon_file="$1" | |
| local app_path="$2" | |
| # Validate inputs | |
| if [[ ! -f "$icon_file" ]]; then | |
| log "ERROR" "Icon file does not exist: $icon_file" | |
| return 1 | |
| fi | |
| if [[ ! -d "$app_path" ]]; then | |
| log "ERROR" "Application does not exist: $app_path" | |
| return 1 | |
| fi | |
| # Check if this is a system app | |
| if is_system_app "$app_path"; then | |
| local icon_basename app_name | |
| icon_basename="$(basename "$icon_file")" | |
| app_name="$(basename "$app_path" .app)" | |
| log "ERROR" "$icon_basename -> $app_path" | |
| log "VERBOSE" "Cannot modify system app '$app_name' (protected by SIP)" | |
| log "VERBOSE" "To modify system apps, disable SIP in Recovery Mode" | |
| ((ERROR_COUNT++)) | |
| return 1 | |
| fi | |
| # Apply icon with sudo if needed | |
| local exit_code=0 | |
| local error_output | |
| if [[ "$app_path" == "/Applications/"* ]]; then | |
| error_output=$(sudo fileicon set "$app_path" "$icon_file" 2>&1) || exit_code=$? | |
| else | |
| error_output=$(fileicon set "$app_path" "$icon_file" 2>&1) || exit_code=$? | |
| fi | |
| local icon_basename | |
| icon_basename="$(basename "$icon_file")" | |
| if [[ "$exit_code" -eq 0 ]]; then | |
| log "INFO" "β $icon_basename -> $app_path" | |
| log "VERBOSE" "Applied successfully" | |
| ((SUCCESS_COUNT++)) | |
| return 0 | |
| else | |
| log "ERROR" "$icon_basename -> $app_path" | |
| log "VERBOSE" "Error: $error_output" | |
| ((ERROR_COUNT++)) | |
| return 1 | |
| fi | |
| } | |
| ####################################### | |
| # Main processing | |
| ####################################### | |
| process_icons() { | |
| if [[ ! -d "$ICONS_DIR" ]]; then | |
| log "ERROR" "Icons directory does not exist: $ICONS_DIR" | |
| return 1 | |
| fi | |
| # Find all .icns files | |
| local icon_files | |
| mapfile -t icon_files < <(find "$ICONS_DIR" -name "*.icns" -type f | sort) | |
| local total_icons=${#icon_files[@]} | |
| if [[ "$total_icons" -eq 0 ]]; then | |
| log "WARN" "No .icns files found in $ICONS_DIR" | |
| return 0 | |
| fi | |
| log "INFO" "Processing $total_icons icon files..." | |
| # Check if sudo is needed | |
| local needs_sudo=false | |
| for icon_file in "${icon_files[@]}"; do | |
| local icon_name app_path | |
| icon_name="$(basename "$icon_file" .icns)" | |
| if app_path=$(find_app_for_icon "$icon_name"); then | |
| if [[ "$app_path" == "/Applications/"* ]]; then | |
| needs_sudo=true | |
| break | |
| fi | |
| fi | |
| done | |
| # Request sudo if needed | |
| if [[ "$needs_sudo" == "true" ]]; then | |
| log "INFO" "π Administrator privileges required for /Applications/" | |
| if ! sudo -v; then | |
| log "ERROR" "Failed to obtain administrator privileges" | |
| return 1 | |
| fi | |
| log "VERBOSE" "β Administrator privileges granted" | |
| fi | |
| # Process each icon | |
| local processed=0 | |
| for icon_file in "${icon_files[@]}"; do | |
| ((processed++)) | |
| local icon_basename icon_name | |
| icon_basename="$(basename "$icon_file")" | |
| icon_name="${icon_basename%.icns}" | |
| log "VERBOSE" "Processing $icon_basename ($processed/$total_icons)" | |
| local app_path | |
| if app_path=$(find_app_for_icon "$icon_name"); then | |
| apply_icon "$icon_file" "$app_path" | |
| else | |
| log "INFO" "βοΈ $icon_basename -> App not found" | |
| log "VERBOSE" "Searched: ${APP_DIRS[*]}" | |
| ((SKIP_COUNT++)) | |
| fi | |
| done | |
| return 0 | |
| } | |
| show_summary() { | |
| local total=$((SUCCESS_COUNT + ERROR_COUNT + SKIP_COUNT)) | |
| log "INFO" "" | |
| log "INFO" "Summary:" | |
| log "INFO" " β Successfully applied: $SUCCESS_COUNT" | |
| log "INFO" " β Failed: $ERROR_COUNT" | |
| log "INFO" " βοΈ Skipped (app not found): $SKIP_COUNT" | |
| log "INFO" " π Total processed: $total" | |
| if [[ "$SUCCESS_COUNT" -gt 0 ]]; then | |
| log "INFO" "" | |
| log "INFO" "π Icon setup completed! You may need to restart applications to see changes." | |
| fi | |
| [[ -n "$LOG_FILE" ]] && log "INFO" "π Log saved to: $LOG_FILE" | |
| } | |
| ####################################### | |
| # Argument parsing | |
| ####################################### | |
| parse_arguments() { | |
| while [[ $# -gt 0 ]]; do | |
| case "$1" in | |
| -h | --help) | |
| usage | |
| exit 0 | |
| ;; | |
| -v | --verbose) | |
| VERBOSE=true | |
| ;; | |
| -q | --quiet) | |
| QUIET=true | |
| ;; | |
| -l | --log) | |
| if [[ -z "${2:-}" ]]; then | |
| log "ERROR" "Option $1 requires an argument" | |
| return 1 | |
| fi | |
| LOG_FILE="$2" | |
| shift | |
| ;; | |
| *) | |
| log "ERROR" "Unknown option: $1" | |
| log "ERROR" "Use --help for usage information" | |
| return 1 | |
| ;; | |
| esac | |
| shift | |
| done | |
| return 0 | |
| } | |
| ####################################### | |
| # Main function | |
| ####################################### | |
| main() { | |
| # Parse arguments | |
| if ! parse_arguments "$@"; then | |
| return 1 | |
| fi | |
| # Validate log file | |
| if [[ -n "$LOG_FILE" ]]; then | |
| local log_dir | |
| log_dir="$(dirname "$LOG_FILE")" | |
| if [[ ! -d "$log_dir" ]] || ! touch "$LOG_FILE" 2>/dev/null; then | |
| log "ERROR" "Cannot write to log file: $LOG_FILE" | |
| return 1 | |
| fi | |
| fi | |
| log "INFO" "π§ Custom Icon Setup Script" | |
| log "INFO" "π Icons directory: $ICONS_DIR" | |
| log "INFO" "π― Target directories: ${APP_DIRS[*]}" | |
| # Check dependencies | |
| if ! check_dependencies; then | |
| return 1 | |
| fi | |
| # Load configuration | |
| load_config | |
| create_sample_config | |
| # Process icons | |
| if ! process_icons; then | |
| return 1 | |
| fi | |
| log "INFO" "Restarting Dock to apply changes..." | |
| launchctl stop com.apple.Dock.agent | |
| launchctl start com.apple.Dock.agent | |
| log "INFO" "Dock restarted successfully" | |
| log "INFO" "β All tasks completed successfully" | |
| return 0 | |
| # Show summary | |
| show_summary | |
| # Return appropriate exit code | |
| [[ "$ERROR_COUNT" -gt 0 ]] && return 1 || return 0 | |
| } | |
| # Script entry point | |
| main "$@" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment