From be1027f9c347c84f3dffbd21a15973b384b4fa2a Mon Sep 17 00:00:00 2001 From: Tw93 Date: Wed, 17 Dec 2025 10:37:03 +0800 Subject: [PATCH] refine sudo requirement checks for uninstallation. --- lib/core/base.sh | 62 ++++++++++++----- lib/core/sudo.sh | 2 +- lib/core/ui.sh | 152 +++++++++++++++++++++++++++++++++++++++++ lib/uninstall/batch.sh | 9 ++- mole | 4 +- 5 files changed, 206 insertions(+), 23 deletions(-) diff --git a/lib/core/base.sh b/lib/core/base.sh index eae2e80..5b4e236 100644 --- a/lib/core/base.sh +++ b/lib/core/base.sh @@ -246,27 +246,53 @@ bytes_to_human_kb() { # Get brand-friendly name for an application # Args: $1 - application name -# Returns: branded name if mapping exists, original name otherwise +# Returns: localized name based on system language preference get_brand_name() { local name="$1" - case "$name" in - "qiyimac" | "爱奇艺") echo "iQiyi" ;; - "wechat" | "微信") echo "WeChat" ;; - "QQ") echo "QQ" ;; - "VooV Meeting" | "腾讯会议") echo "VooV Meeting" ;; - "dingtalk" | "钉钉") echo "DingTalk" ;; - "NeteaseMusic" | "网易云音乐") echo "NetEase Music" ;; - "BaiduNetdisk" | "百度网盘") echo "Baidu NetDisk" ;; - "alipay" | "支付宝") echo "Alipay" ;; - "taobao" | "淘宝") echo "Taobao" ;; - "futunn" | "富途牛牛") echo "Futu NiuNiu" ;; - "tencent lemon" | "Tencent Lemon Cleaner") echo "Tencent Lemon" ;; - "keynote" | "Keynote") echo "Keynote" ;; - "pages" | "Pages") echo "Pages" ;; - "numbers" | "Numbers") echo "Numbers" ;; - *) echo "$name" ;; - esac + # Detect if system primary language is Chinese + local is_chinese=false + local sys_lang + sys_lang=$(defaults read -g AppleLanguages 2>/dev/null | grep -o 'zh-Hans\|zh-Hant\|zh' | head -1 || echo "") + [[ -n "$sys_lang" ]] && is_chinese=true + + # Return localized names based on system language + if [[ "$is_chinese" == true ]]; then + # Chinese system - prefer Chinese names + case "$name" in + "qiyimac" | "iQiyi") echo "爱奇艺" ;; + "wechat" | "WeChat") echo "微信" ;; + "QQ") echo "QQ" ;; + "VooV Meeting") echo "腾讯会议" ;; + "dingtalk" | "DingTalk") echo "钉钉" ;; + "NeteaseMusic" | "NetEase Music") echo "网易云音乐" ;; + "BaiduNetdisk" | "Baidu NetDisk") echo "百度网盘" ;; + "alipay" | "Alipay") echo "支付宝" ;; + "taobao" | "Taobao") echo "淘宝" ;; + "futunn" | "Futu NiuNiu") echo "富途牛牛" ;; + "tencent lemon" | "Tencent Lemon Cleaner" | "Tencent Lemon") echo "腾讯柠檬清理" ;; + *) echo "$name" ;; + esac + else + # Non-Chinese system - use English names + case "$name" in + "qiyimac" | "爱奇艺") echo "iQiyi" ;; + "wechat" | "微信") echo "WeChat" ;; + "QQ") echo "QQ" ;; + "腾讯会议") echo "VooV Meeting" ;; + "dingtalk" | "钉钉") echo "DingTalk" ;; + "网易云音乐") echo "NetEase Music" ;; + "百度网盘") echo "Baidu NetDisk" ;; + "alipay" | "支付宝") echo "Alipay" ;; + "taobao" | "淘宝") echo "Taobao" ;; + "富途牛牛") echo "Futu NiuNiu" ;; + "腾讯柠檬清理" | "Tencent Lemon Cleaner") echo "Tencent Lemon" ;; + "keynote" | "Keynote") echo "Keynote" ;; + "pages" | "Pages") echo "Pages" ;; + "numbers" | "Numbers") echo "Numbers" ;; + *) echo "$name" ;; + esac + fi } # ============================================================================ diff --git a/lib/core/sudo.sh b/lib/core/sudo.sh index 617dcaf..d44b719 100644 --- a/lib/core/sudo.sh +++ b/lib/core/sudo.sh @@ -45,7 +45,7 @@ _request_password() { # Save original terminal settings and ensure they're restored on exit local stty_orig stty_orig=$(stty -g < "$tty_path" 2> /dev/null || echo "") - trap '[[ -n "$stty_orig" ]] && stty "$stty_orig" < "$tty_path" 2> /dev/null || true' RETURN + trap '[[ -n "${stty_orig:-}" ]] && stty "${stty_orig:-}" < "$tty_path" 2> /dev/null || true' RETURN while ((attempts < 3)); do local password="" diff --git a/lib/core/ui.sh b/lib/core/ui.sh index 562e0c8..b4bab88 100755 --- a/lib/core/ui.sh +++ b/lib/core/ui.sh @@ -17,6 +17,158 @@ clear_screen() { printf '\033[2J\033[H'; } hide_cursor() { [[ -t 1 ]] && printf '\033[?25l' >&2 || true; } show_cursor() { [[ -t 1 ]] && printf '\033[?25h' >&2 || true; } +# Calculate display width of a string (CJK characters count as 2) +# Args: $1 - string to measure +# Returns: display width +# Note: Works correctly even when LC_ALL=C is set +get_display_width() { + local str="$1" + + # Check Python availability once and cache the result + # Use Python for accurate width calculation if available (cached check) + if [[ -z "${MOLE_PYTHON_AVAILABLE:-}" ]]; then + if command -v python3 > /dev/null 2>&1; then + export MOLE_PYTHON_AVAILABLE=1 + else + export MOLE_PYTHON_AVAILABLE=0 + fi + fi + + if [[ "${MOLE_PYTHON_AVAILABLE:-0}" == "1" ]]; then + python3 -c " +import sys +import unicodedata + +s = sys.argv[1] +width = 0 +for char in s: + # East Asian Width property + ea_width = unicodedata.east_asian_width(char) + if ea_width in ('F', 'W'): # Fullwidth or Wide + width += 2 + else: + width += 1 +print(width) +" "$str" 2>/dev/null && return + fi + + # Fallback: Use wc with UTF-8 locale temporarily + local saved_lc_all="${LC_ALL:-}" + local saved_lang="${LANG:-}" + + export LC_ALL=en_US.UTF-8 + export LANG=en_US.UTF-8 + + local char_count byte_count width + char_count=$(printf '%s' "$str" | wc -m 2>/dev/null | tr -d ' ') + byte_count=$(printf '%s' "$str" | wc -c 2>/dev/null | tr -d ' ') + + # Restore locale + if [[ -n "$saved_lc_all" ]]; then + export LC_ALL="$saved_lc_all" + else + unset LC_ALL + fi + if [[ -n "$saved_lang" ]]; then + export LANG="$saved_lang" + else + unset LANG + fi + + # Estimate: if byte_count > char_count, we have multibyte chars + # Rough approximation: each multibyte char (CJK) is ~3 bytes and width 2 + # ASCII chars are 1 byte and width 1 + if [[ $byte_count -gt $char_count ]]; then + local multibyte_chars=$((byte_count - char_count)) + # Assume most multibyte chars are 2 bytes extra (3 bytes total for UTF-8 CJK) + local cjk_chars=$((multibyte_chars / 2)) + local ascii_chars=$((char_count - cjk_chars)) + width=$((ascii_chars + cjk_chars * 2)) + else + width=$char_count + fi + + echo "$width" +} + +# Truncate string by display width (handles CJK correctly) +# Args: $1 - string, $2 - max display width +# Returns: truncated string with "..." if needed +truncate_by_display_width() { + local str="$1" + local max_width="$2" + local current_width + current_width=$(get_display_width "$str") + + if [[ $current_width -le $max_width ]]; then + echo "$str" + return + fi + + # Use Python for accurate truncation if available (use cached check) + if [[ "${MOLE_PYTHON_AVAILABLE:-0}" == "1" ]]; then + python3 -c " +import sys +import unicodedata + +s = sys.argv[1] +max_w = int(sys.argv[2]) +result = '' +width = 0 + +for char in s: + ea_width = unicodedata.east_asian_width(char) + char_width = 2 if ea_width in ('F', 'W') else 1 + + if width + char_width + 3 > max_w: # +3 for '...' + break + + result += char + width += char_width + +print(result + '...') +" "$str" "$max_width" 2>/dev/null && return + fi + + # Fallback: Use UTF-8 locale for proper string handling + local saved_lc_all="${LC_ALL:-}" + local saved_lang="${LANG:-}" + export LC_ALL=en_US.UTF-8 + export LANG=en_US.UTF-8 + + local truncated="" + local width=0 + local i=0 + local char char_width + + while [[ $i -lt ${#str} ]]; do + char="${str:$i:1}" + char_width=$(get_display_width "$char") + + if ((width + char_width + 3 > max_width)); then + break + fi + + truncated+="$char" + ((width += char_width)) + ((i++)) + done + + # Restore locale + if [[ -n "$saved_lc_all" ]]; then + export LC_ALL="$saved_lc_all" + else + unset LC_ALL + fi + if [[ -n "$saved_lang" ]]; then + export LANG="$saved_lang" + else + unset LANG + fi + + echo "${truncated}..." +} + # Keyboard input - read single keypress read_key() { local key rest read_status diff --git a/lib/uninstall/batch.sh b/lib/uninstall/batch.sh index f477ef8..3ce00ec 100755 --- a/lib/uninstall/batch.sh +++ b/lib/uninstall/batch.sh @@ -130,9 +130,10 @@ batch_uninstall_applications() { running_apps+=("$app_name") fi - # Check if app requires sudo to delete + # Check if app requires sudo to delete (either app bundle or system files) + local needs_sudo=false if [[ ! -w "$(dirname "$app_path")" ]] || [[ "$(get_file_owner "$app_path")" == "root" ]]; then - sudo_apps+=("$app_name") + needs_sudo=true fi # Calculate size for summary (including system files) @@ -150,6 +151,10 @@ batch_uninstall_applications() { # Check if system files require sudo # shellcheck disable=SC2128 if [[ -n "$system_files" ]]; then + needs_sudo=true + fi + + if [[ "$needs_sudo" == "true" ]]; then sudo_apps+=("$app_name") fi diff --git a/mole b/mole index da659e8..d19ee0c 100755 --- a/mole +++ b/mole @@ -22,8 +22,8 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" source "$SCRIPT_DIR/lib/core/common.sh" # Version info -VERSION="1.13.3" -MOLE_TAGLINE="can dig deep to clean your Mac." +VERSION="1.13.4" +MOLE_TAGLINE="Deep clean and optimize your Mac." # Check if Touch ID is already configured is_touchid_configured() {