前言:菜单栏自定义的进阶之路
macOS的菜单栏是系统信息展示和快捷操作的核心区域。从系统时钟到Wi-Fi状态,从电池电量到输入法切换,菜单栏承载了大量日常交互。然而,对于追求个性化的黑苹果用户而言,系统原生的菜单栏信息远远不够——你需要实时监控CPU温度、显示网络流量、追踪内存使用率、甚至将自定义脚本输出显示在菜单栏上。SwiftBar正是为这一需求而生的工具。它将Unix哲学的"一切皆文本"理念引入菜单栏自定义,让你用最简单的Shell脚本就能创建功能丰富的菜单栏插件。本文将全面讲解SwiftBar的插件开发,从基础Shell脚本到Swift原生插件,打造完全个性化的系统监控面板。
一、SwiftBar基础
1.1 SwiftBar简介
SwiftBar是一个受BitBar启发的macOS菜单栏自定义工具,由Swift编写。它通过执行脚本并将其输出渲染为菜单栏项目来实现自定义。核心特性:
- 支持任何可执行脚本(Shell、Python、Ruby、Swift、Node.js等)
- BitBar插件完全兼容
- 支持xbar(BitBar继任者)插件格式
- 内置插件浏览器,一键安装社区插件
- 支持插件变量和刷新控制
- 原生Swift实现,极低资源占用
1.2 安装SwiftBar
# 方法一:通过Homebrew安装
brew install --cask swiftbar
# 方法二:从GitHub下载
# https://github.com/swiftbar/SwiftBar/releases
# 首次启动配置
# 1. 设置插件目录(建议:~/.swiftbar-plugins)
# 2. 授予辅助功能权限(系统偏好设置 → 安全性与隐私 → 隐私 → 辅助功能)
1.3 插件基本格式
SwiftBar插件的本质是一个可执行脚本,其输出遵循特定的格式规范:
#!/bin/bash
# 文件名格式:plugin_name.{time}.{ext}
# {time} 定义刷新间隔,如 1s, 5m, 1h
# 示例:cpu_temp.5s.sh(每5秒刷新一次)
# 第一行输出显示在菜单栏上
echo "CPU: 45°C"
# --- 以下内容在下拉菜单中显示 ---
echo "---"
echo "刷新 | refresh=true"
echo "打开活动监视器 | href='x-apple.systempreferences:com.apple.Activity-Monitor'"
二、Shell脚本插件开发
2.1 CPU温度监控插件
黑苹果上监控CPU温度是最常见的需求之一:
#!/bin/bash
# cpu_temp.5s.sh
# CPU温度监控插件
# 使用osx-cpu-temp获取温度
# brew install osx-cpu-temp
TEMP=$(osx-cpu-temp 2>/dev/null | grep -oE '[0-9]+\.[0-9]+' || echo "N/A")
# 根据温度设置图标和颜色
if [ "$TEMP" != "N/A" ]; then
TEMP_INT=${TEMP%.*}
if [ "$TEMP_INT" -lt 50 ]; then
COLOR="green"
ICON="❄️"
elif [ "$TEMP_INT" -lt 70 ]; then
COLOR="yellow"
ICON="🌡️"
elif [ "$TEMP_INT" -lt 85 ]; then
COLOR="orange"
ICON="🔥"
else
COLOR="red"
ICON="⚠️"
fi
echo "$ICON ${TEMP}°C | color=$COLOR"
else
echo "🌡️ N/A | color=gray"
fi
echo "---"
echo "温度阈值:"
echo " 正常: <50°C (绿色)"
echo " 注意: 50-70°C (黄色)"
echo " 警告: 70-85°C (橙色)"
echo " 危险: >85°C (红色)"
echo "---"
echo "刷新 | refresh=true"
2.2 内存使用监控插件
#!/bin/bash
# memory_usage.10s.sh
# 内存使用监控插件
# 获取内存信息
MEM_TOTAL=$(sysctl -n hw.memsize)
MEM_TOTAL_GB=$(echo "scale=1; $MEM_TOTAL / 1073741824" | bc)
# 使用vm_stat获取详细内存统计
VM_STAT=$(vm_stat)
PAGES_FREE=$(echo "$VM_STAT" | grep "Pages free" | awk '{print $3}' | tr -d '.')
PAGES_ACTIVE=$(echo "$VM_STAT" | grep "Pages active" | awk '{print $3}' | tr -d '.')
PAGES_INACTIVE=$(echo "$VM_STAT" | grep "Pages inactive" | awk '{print $3}' | tr -d '.')
PAGES_SPECULATIVE=$(echo "$VM_STAT" | grep "Pages speculative" | awk '{print $3}' | tr -d '.')
PAGES_COMPRESSED=$(echo "$VM_STAT" | grep "Pages occupied by compressor" | awk '{print $4}' | tr -d '.')
PAGE_SIZE=4096
# 计算各项内存使用(GB)
ACTIVE_GB=$(echo "scale=1; $PAGES_ACTIVE * $PAGE_SIZE / 1073741824" | bc)
INACTIVE_GB=$(echo "scale=1; $PAGES_INACTIVE * $PAGE_SIZE / 1073741824" | bc)
COMPRESSED_GB=$(echo "scale=1; $PAGES_COMPRESSED * $PAGE_SIZE / 1073741824" | bc)
USED_GB=$(echo "scale=1; ($PAGES_ACTIVE + $PAGES_COMPRESSED) * $PAGE_SIZE / 1073741824" | bc)
# 计算使用百分比
USED_PCT=$(echo "scale=0; $USED_GB * 100 / $MEM_TOTAL_GB" | bc)
# 设置颜色
if [ "$USED_PCT" -lt 60 ]; then
COLOR="green"
elif [ "$USED_PCT" -lt 80 ]; then
COLOR="yellow"
elif [ "$USED_PCT" -lt 90 ]; then
COLOR="orange"
else
COLOR="red"
fi
echo "RAM: ${USED_GB}G/${MEM_TOTAL_GB}G (${USED_PCT}%) | color=$COLOR"
echo "---"
echo "活跃: ${ACTIVE_GB} GB"
echo "非活跃: ${INACTIVE_GB} GB"
echo "压缩: ${COMPRESSED_GB} GB"
echo "---"
# Swap使用情况
SWAP_INFO=$(sysctl vm.swapusage 2>/dev/null)
if [ -n "$SWAP_INFO" ]; then
SWAP_USED=$(echo "$SWAP_INFO" | grep "used" | awk '{print $7}' | cut -d'M' -f1)
SWAP_TOTAL=$(echo "$SWAP_INFO" | grep "total" | awk '{print $7}' | cut -d'M' -f1)
echo "Swap: ${SWAP_USED}MB / ${SWAP_TOTAL}MB"
else
echo "Swap: 未启用"
fi
echo "---"
echo "刷新 | refresh=true"
echo "打开活动监视器 | href='x-apple.systempreferences:com.apple.Activity-Monitor'"
2.3 网络流量监控插件
#!/bin/bash
# network_speed.5s.sh
# 网络流量监控插件
# 网络接口(根据实际情况修改)
IFACE="en0"
# 读取当前流量统计
RX1=$(netstat -ibn | grep -e "$IFACE" -m 1 | awk '{print $7}')
TX1=$(netstat -ibn | grep -e "$IFACE" -m 1 | awk '{print $10}')
# 等待1秒
sleep 1
# 再次读取
RX2=$(netstat -ibn | grep -e "$IFACE" -m 1 | awk '{print $7}')
TX2=$(netstat -ibn | grep -e "$IFACE" -m 1 | awk '{print $10}')
# 计算速率(Bytes/s → KB/s 或 MB/s)
RX_RATE=$((RX2 - RX1))
TX_RATE=$((TX2 - TX1))
# 格式化下载速率
if [ "$RX_RATE" -gt 1048576 ]; then
RX_DISPLAY=$(echo "scale=1; $RX_RATE / 1048576" | bc)"MB/s"
else
RX_DISPLAY=$(echo "scale=0; $RX_RATE / 1024" | bc)"KB/s"
fi
# 格式化上传速率
if [ "$TX_RATE" -gt 1048576 ]; then
TX_DISPLAY=$(echo "scale=1; $TX_RATE / 1048576" | bc)"MB/s"
else
TX_DISPLAY=$(echo "scale=0; $TX_RATE / 1024" | bc)"KB/s"
fi
echo "⬇${RX_DISPLAY} ⬆${TX_DISPLAY}"
echo "---"
# IP地址
IP_V4=$(ifconfig "$IFACE" | grep "inet " | awk '{print $2}')
IP_V6=$(ifconfig "$IFACE" | grep "inet6" | awk '{print $2}' | head -1)
echo "IPv4: ${IP_V4:-未连接}"
echo "IPv6: ${IP_V6:-未连接}"
echo "---"
# Wi-Fi信息
WIFI_SSID=$(/System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport -I 2>/dev/null | grep "SSID" | awk -F': ' '{print $2}')
WIFI_RSSI=$(/System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport -I 2>/dev/null | grep "rssi" | awk -F': ' '{print $2}')
if [ -n "$WIFI_SSID" ]; then
echo "Wi-Fi: $WIFI_SSID"
echo "信号强度: ${WIFI_RSSI} dBm"
else
echo "Wi-Fi: 未连接"
fi
echo "---"
echo "刷新 | refresh=true"
echo "网络偏好设置 | href='x-apple.systempreferences:com.apple.Network-Settings'"
2.4 磁盘空间监控插件
#!/bin/bash
# disk_space.1m.sh
# 磁盘空间监控插件
# 系统盘
ROOT_INFO=$(df -h / | tail -1)
ROOT_SIZE=$(echo "$ROOT_INFO" | awk '{print $2}')
ROOT_USED=$(echo "$ROOT_INFO" | awk '{print $3}')
ROOT_AVAIL=$(echo "$ROOT_INFO" | awk '{print $4}')
ROOT_PCT=$(echo "$ROOT_INFO" | awk '{print $5}' | tr -d '%')
# 设置颜色
if [ "$ROOT_PCT" -lt 70 ]; then
COLOR="green"
elif [ "$ROOT_PCT" -lt 85 ]; then
COLOR="yellow"
elif [ "$ROOT_PCT" -lt 95 ]; then
COLOR="orange"
else
COLOR="red"
fi
echo "💾 ${ROOT_PCT}% | color=$COLOR"
echo "---"
echo "系统盘 (/)"
echo " 总量: $ROOT_SIZE"
echo " 已用: $ROOT_USED"
echo " 可用: $ROOT_AVAIL"
# EFI分区
EFI_PARTITION=$(diskutil list | grep EFI | head -1 | awk '{print $NF}')
if [ -n "$EFI_PARTITION" ]; then
sudo diskutil mount /dev/$EFI_PARTITION 2>/dev/null || true
EFI_INFO=$(df -h /Volumes/EFI 2>/dev/null | tail -1)
if [ -n "$EFI_INFO" ]; then
EFI_SIZE=$(echo "$EFI_INFO" | awk '{print $2}')
EFI_USED=$(echo "$EFI_INFO" | awk '{print $3}')
EFI_AVAIL=$(echo "$EFI_INFO" | awk '{print $4}')
echo "---"
echo "EFI分区"
echo " 总量: $EFI_SIZE"
echo " 已用: $EFI_USED"
echo " 可用: $EFI_AVAIL"
fi
sudo diskutil unmount /dev/$EFI_PARTITION 2>/dev/null || true
fi
echo "---"
echo "刷新 | refresh=true"
三、Python脚本插件
3.1 黑苹果兼容性检查插件
#!/usr/bin/env python3
# hackintosh_status.5m.py
# 黑苹果系统状态综合检查
# <swiftbar>about.title=黑苹果状态检查</swiftbar>
import subprocess
import json
def run_cmd(cmd):
try:
return subprocess.check_output(cmd, shell=True, text=True).strip()
except:
return "N/A"
# 获取系统信息
macos_ver = run_cmd("sw_vers -productVersion")
kernel_ver = run_cmd("uname -r")
opencore_ver = run_cmd("nvram 4D1FDA02-38C7-4A6A-9CC6-4BCCA8B30102:opencore-version 2>/dev/null | awk -F: '{print $2}'")
smbios_model = run_cmd("sysctl -n hw.model")
cpu_model = run_cmd("sysctl -n machdep.cpu.brand_string")
mem_gb = round(int(run_cmd("sysctl -n hw.memsize")) / 1073741824)
# SIP状态
sip_status = run_cmd("csrutil status")
sip_enabled = "启用" if "enabled" in sip_status else "禁用"
# 输出
print(f"🍎 {macos_ver} | color=green")
print("---")
print(f"macOS: {macos_ver}")
print(f"内核: {kernel_ver}")
print(f"OpenCore: {opencore_ver}")
print(f"SMBIOS: {smbios_model}")
print("---")
print(f"CPU: {cpu_model}")
print(f"内存: {mem_gb} GB")
print(f"SIP: {sip_enabled}")
print("---")
print("刷新 | refresh=true")
四、Swift原生插件开发
4.1 为什么使用Swift
虽然Shell脚本足够简单,但Swift原生插件具有以下优势:
- 直接调用macOS原生API(SystemConfiguration、IOKit等)
- 更低的CPU和内存占用
- 更精确的数据获取(无需解析文本输出)
- 更好的错误处理和类型安全
- 可编译为单一二进制文件,无外部依赖
4.2 Swift插件:高级CPU监控
#!/usr/bin/env swift
# cpu_monitor.5s.swift
import Foundation
// 获取CPU使用率(通过host_statistics)
func getCPUUsage() -> (user: Double, system: Double, idle: Double) {
var numCPU: natural_t = 0
var cpuInfo: processor_info_array_t?
var numCPUInfo: mach_msg_type_number_t = 0
let result = withUnsafeMutablePointer(to: &numCPU) { numCPUPtr in
withUnsafeMutablePointer(to: &cpuInfo) { cpuInfoPtr in
withUnsafeMutablePointer(to: &numCPUInfo) { numCPUInfoPtr in
host_processor_info(mach_host_self(), PROCESSOR_CPU_LOAD_INFO,
numCPUPtr, cpuInfoPtr, numCPUInfoPtr)
}
}
}
guard result == KERN_SUCCESS, let cpuInfo = cpuInfo else {
return (0, 0, 100)
}
var totalUser: Double = 0
var totalSystem: Double = 0
var totalIdle: Double = 0
for i in 0..<Int(numCPU) {
let user = Double(cpuInfo[i * 4 + 0])
let system = Double(cpuInfo[i * 4 + 1])
let idle = Double(cpuInfo[i * 4 + 3])
totalUser += user
totalSystem += system
totalIdle += idle
}
let total = totalUser + totalSystem + totalIdle
vm_deallocate(mach_task_self_, vm_address_t(bitPattern: cpuInfo),
vm_size_t(numCPUInfo) * 4)
return (totalUser / total * 100, totalSystem / total * 100, totalIdle / total * 100)
}
let usage = getCPUUsage()
let total = 100 - usage.idle
let color = total < 30 ? "green" : total < 60 ? "yellow" : total < 85 ? "orange" : "red"
let icon = total < 30 ? "🟢" : total < 60 ? "🟡" : total < 85 ? "🟠" : "🔴"
print("\(icon) CPU: \(String(format: "%.0f", total))% | color=\(color)")
print("---")
print("用户: \(String(format: "%.1f", usage.user))%")
print("系统: \(String(format: "%.1f", usage.system))%")
print("空闲: \(String(format: "%.1f", usage.idle))%")
print("---")
// CPU核心数
let cores = ProcessInfo.processInfo.processorCount
print("物理核心: \(cores)")
print("逻辑核心: \(ProcessInfo.processInfo.activeProcessorCount)")
4.3 Swift插件:GPU信息监控
#!/usr/bin/env swift
# gpu_info.30s.swift
import Foundation
import IOKit
func getGPUInfo() -> [(name: String, vendor: String)] {
var gpus: [(name: String, vendor: String)] = []
let matchingDict = IOServiceMatching("IOPCIDevice")
var iterator: io_iterator_t = 0
let result = IOServiceGetMatchingServices(kIOMainPortDefault, matchingDict, &iterator)
guard result == KERN_SUCCESS else { return gpus }
var service: io_object_t = IOIteratorNext(iterator)
while service != 0 {
if let modelData = IORegistryEntryCreateCFProperty(service, "model" as CFString,
kCFAllocatorDefault, 0) {
let model = modelData.takeRetainedValue() as? Data
if let model = model {
let name = String(data: model, encoding: .utf8)?.trimmingCharacters(in: .controlCharacters) ?? "Unknown"
var vendor = "Unknown"
if name.contains("AMD") || name.contains("Radeon") {
vendor = "AMD"
} else if name.contains("Intel") {
vendor = "Intel"
} else if name.contains("NVIDIA") || name.contains("GeForce") {
vendor = "NVIDIA"
}
gpus.append((name: name, vendor: vendor))
}
}
IOObjectRelease(service)
service = IOIteratorNext(iterator)
}
IOObjectRelease(iterator)
return gpus
}
let gpus = getGPUInfo()
if gpus.isEmpty {
print("GPU: 未检测到 | color=gray")
} else {
let primary = gpus[0]
let shortName = primary.name.count > 30 ? String(primary.name.prefix(27)) + "..." : primary.name
let icon = primary.vendor == "AMD" ? "🔴" : primary.vendor == "Intel" ? "🔵" : "🟢"
print("\(icon) \(shortName)")
print("---")
for (i, gpu) in gpus.enumerated() {
print("GPU \(i): \(gpu.name)")
print("厂商: \(gpu.vendor)")
if i < gpus.count - 1 { print("---") }
}
}
print("---")
print("刷新 | refresh=true")
五、插件组合与仪表盘
5.1 创建统一监控面板
将多个插件组合,创建一个综合系统监控面板:
#!/bin/bash
# system_dashboard.10s.sh
# 综合系统监控仪表盘
# 获取各项指标
CPU_USAGE=$(ps -A -o %cpu | awk '{s+=$1} END {printf "%.0f", s}')
MEM_TOTAL=$(sysctl -n hw.memsize)
MEM_TOTAL_GB=$(echo "scale=0; $MEM_TOTAL / 1073741824" | bc)
MEM_USED_GB=$(vm_stat | grep "Pages active" | awk '{printf "%.1f", $3 * 4096 / 1073741824}' | tr -d '.')
MEM_PCT=$(echo "scale=0; $MEM_USED_GB * 100 / $MEM_TOTAL_GB" | bc)
DISK_PCT=$(df -h / | tail -1 | awk '{print $5}' | tr -d '%')
# 计算综合健康分数
HEALTH=100
[ "$CPU_USAGE" -gt 50 ] && HEALTH=$((HEALTH - 20))
[ "$CPU_USAGE" -gt 80 ] && HEALTH=$((HEALTH - 20))
[ "$MEM_PCT" -gt 70 ] && HEALTH=$((HEALTH - 15))
[ "$MEM_PCT" -gt 90 ] && HEALTH=$((HEALTH - 15))
[ "$DISK_PCT" -gt 80 ] && HEALTH=$((HEALTH - 10))
[ "$DISK_PCT" -gt 95 ] && HEALTH=$((HEALTH - 10))
if [ "$HEALTH" -ge 80 ]; then
COLOR="green"; ICON="✅"
elif [ "$HEALTH" -ge 60 ]; then
COLOR="yellow"; ICON="⚠️"
else
COLOR="red"; ICON="🔴"
fi
echo "$ICON 系统: ${HEALTH}分 | color=$COLOR"
echo "---"
echo "CPU: ${CPU_USAGE}% | color=$([ "$CPU_USAGE" -gt 80 ] && echo red || echo green)"
echo "RAM: ${MEM_PCT}% | color=$([ "$MEM_PCT" -gt 85 ] && echo red || echo green)"
echo "磁盘: ${DISK_PCT}% | color=$([ "$DISK_PCT" -gt 90 ] && echo red || echo green)"
echo "---"
echo "运行时间: $(uptime | sed 's/.*up //' | sed 's/,.*//')"
echo "负载: $(sysctl -n vm.loadavg | awk '{print $2, $3, $4}')"
echo "---"
echo "刷新 | refresh=true"
echo "活动监视器 | href='x-apple.systempreferences:com.apple.Activity-Monitor'"
5.2 插件目录结构
~/.swiftbar-plugins/
├── cpu_temp.5s.sh # CPU温度(5秒刷新)
├── memory_usage.10s.sh # 内存使用(10秒刷新)
├── network_speed.5s.sh # 网络流量(5秒刷新)
├── disk_space.1m.sh # 磁盘空间(1分钟刷新)
├── gpu_info.30s.swift # GPU信息(30秒刷新)
├── hackintosh_status.5m.py # 黑苹果状态(5分钟刷新)
└── system_dashboard.10s.sh # 综合仪表盘(10秒刷新)
六、性能优化与注意事项
6.1 刷新间隔优化
过于频繁的刷新会消耗大量CPU资源,特别是调用外部命令时:
- 温度/网络流量:5-10秒
- CPU/内存使用率:10-30秒
- 磁盘空间/系统信息:1-5分钟
- GPU信息/硬件检测:30秒-5分钟
6.2 脚本执行超时
SwiftBar默认5秒执行超时。如果脚本执行时间过长,会被强制终止。优化方法:
- 缓存计算结果到临时文件
- 使用增量计算而非全量计算
- 避免在脚本中调用sleep
- 减少外部命令调用次数
6.3 黑苹果特有的注意事项
- 部分系统信息命令在黑苹果上可能返回异常值,需要做容错处理
- 温度传感器可能不完整,osx-cpu-temp可能无法读取所有核心温度
- GPU信息可能不如真机完整,特别是AMD显卡的显存和频率信息
- 电源状态相关数据(电池、充电)在台式黑苹果上不可用
七、总结
SwiftBar为黑苹果用户提供了无限可能的菜单栏自定义能力。从简单的Shell脚本到Swift原生插件,从单一指标到综合仪表盘,你可以根据需求构建完全个性化的系统监控面板。核心建议:
- 从Shell脚本入手:快速验证想法,逐步优化
- 合理设置刷新间隔:在实时性和性能之间取平衡
- 善用颜色和图标:让关键信息一目了然
- 必要时迁移到Swift:当Shell脚本性能不足时,使用Swift直接调用系统API
- 加入交互功能:利用href和bash参数实现快捷操作
菜单栏是macOS桌面最具特色的设计之一。通过SwiftBar,你不仅能实时掌握黑苹果的系统状态,更能让菜单栏成为你个人效率和审美品味的体现。开始写你的第一个SwiftBar插件吧!
声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。


评论(0)