[Python] 纯文本查看 复制代码
"""
macOS 菜单栏系统监控工具(轻量版)
只显示核心信息,低内存占用
"""
import sys
import time
import psutil
import objc
import os
from AppKit import (
NSApplication, NSStatusBar, NSMenu, NSMenuItem, NSRunningApplication,
NSApplicationActivationPolicyRegular, NSApplicationActivationPolicyAccessory,
NSApplicationActivationPolicyProhibited
)
from Foundation import NSObject, NSTimer
def format_speed(speed_bytes):
"""格式化网络速度为 KB/s 或 MB/s"""
speed_kb = speed_bytes / 1024
if speed_kb < 10:
return f"{speed_kb:.1f} KB/s"
if speed_kb < 1000:
return f"{speed_kb:.0f} KB/s"
return f"{speed_kb / 1024:.1f} MB/s"
def get_network_speed(last_net_io):
"""获取当前网络速度(KB/s)"""
net_io = psutil.net_io_counters()
if last_net_io:
sent_speed = net_io.bytes_sent - last_net_io[0]
recv_speed = net_io.bytes_recv - last_net_io[1]
else:
sent_speed = 0
recv_speed = 0
return sent_speed, recv_speed, (net_io.bytes_sent, net_io.bytes_recv)
def get_cpu_usage():
"""获取CPU使用率"""
return psutil.cpu_percent()
def get_memory_usage():
"""获取内存使用率"""
mem = psutil.virtual_memory()
return mem.percent
class StatusBarAppDelegate(NSObject):
def init(self):
self = objc.super(StatusBarAppDelegate, self).init()
if self is None:
return None
self.last_net_io = None
self.update_interval = 1
self.show_dock_icon = False # 默认隐藏Dock栏图标
# 默认全部显示
self.show_cpu = True
self.show_ram = True
self.show_upload = True
self.show_download = True
self.config_file = os.path.expanduser("~/Library/Preferences/com.systemmonitor.plist")
# 尝试读取配置
self.load_config()
# 设置应用程序激活策略(隐藏Dock图标)
self.set_activation_policy()
self.status_bar = NSStatusBar.systemStatusBar()
self.status_item = self.status_bar.statusItemWithLength_(-1)
self.status_item.setHighlightMode_(True)
self.menu = NSMenu.alloc().init()
self.status_item.setMenu_(self.menu)
self.update_timer = NSTimer.scheduledTimerWithTimeInterval_target_selector_userInfo_repeats_(
1, self, 'updateData:', None, True
)
self.create_menu()
self.updateData_(None)
return self
def load_config(self):
"""加载用户配置"""
try:
if os.path.exists(self.config_file):
import plistlib
with open(self.config_file, 'rb') as f:
config = plistlib.load(f)
self.show_dock_icon = config.get('show_dock_icon', False)
# 加载显示选项配置
self.show_cpu = config.get('show_cpu', True)
self.show_ram = config.get('show_ram', True)
self.show_upload = config.get('show_upload', True)
self.show_download = config.get('show_download', True)
except Exception as e:
print(f"Error loading config: {e}")
self.show_dock_icon = False
# 默认全部显示
self.show_cpu = True
self.show_ram = True
self.show_upload = True
self.show_download = True
def save_config(self):
"""保存用户配置"""
try:
import plistlib
config = {
'show_dock_icon': self.show_dock_icon,
'show_cpu': self.show_cpu,
'show_ram': self.show_ram,
'show_upload': self.show_upload,
'show_download': self.show_download
}
os.makedirs(os.path.dirname(self.config_file), exist_ok=True)
with open(self.config_file, 'wb') as f:
plistlib.dump(config, f)
except Exception as e:
print(f"Error saving config: {e}")
def set_activation_policy(self):
"""设置应用程序激活策略"""
if self.show_dock_icon:
NSApplication.sharedApplication().setActivationPolicy_(NSApplicationActivationPolicyRegular)
else:
NSApplication.sharedApplication().setActivationPolicy_(NSApplicationActivationPolicyProhibited)
def create_menu(self):
"""创建菜单栏"""
refresh_item = NSMenuItem.alloc().initWithTitle_action_keyEquivalent_(
"刷新", "refreshData:", ""
)
refresh_item.setTarget_(self)
self.interval_menu = NSMenu.alloc().init()
for interval in [1, 2, 5]:
title = f"✓ {interval}秒" if interval == self.update_interval else f"{interval}秒"
item = NSMenuItem.alloc().initWithTitle_action_keyEquivalent_(
title, "setUpdateInterval:", ""
)
item.setTarget_(self)
item.setRepresentedObject_(interval)
self.interval_menu.addItem_(item)
interval_item = NSMenuItem.alloc().initWithTitle_action_keyEquivalent_(
"刷新间隔", "", ""
)
interval_item.setSubmenu_(self.interval_menu)
# 添加显示/隐藏Dock栏图标选项
dock_icon_item = NSMenuItem.alloc().initWithTitle_action_keyEquivalent_(
"显示Dock栏图标" if not self.show_dock_icon else "隐藏Dock栏图标",
"toggleDockIcon:",
""
)
dock_icon_item.setTarget_(self)
# 添加显示选项
show_cpu_item = NSMenuItem.alloc().initWithTitle_action_keyEquivalent_(
f"{'✓ ' if self.show_cpu else ''}显示CPU", "toggleShowCPU:", ""
)
show_cpu_item.setTarget_(self)
show_ram_item = NSMenuItem.alloc().initWithTitle_action_keyEquivalent_(
f"{'✓ ' if self.show_ram else ''}显示RAM", "toggleShowRAM:", ""
)
show_ram_item.setTarget_(self)
show_upload_item = NSMenuItem.alloc().initWithTitle_action_keyEquivalent_(
f"{'✓ ' if self.show_upload else ''}显示上传", "toggleShowUpload:", ""
)
show_upload_item.setTarget_(self)
show_download_item = NSMenuItem.alloc().initWithTitle_action_keyEquivalent_(
f"{'✓ ' if self.show_download else ''}显示下载", "toggleShowDownload:", ""
)
show_download_item.setTarget_(self)
quit_item = NSMenuItem.alloc().initWithTitle_action_keyEquivalent_(
"退出", "quitApp:", ""
)
quit_item.setTarget_(self)
self.menu.addItem_(refresh_item)
self.menu.addItem_(interval_item)
self.menu.addItem_(dock_icon_item)
self.menu.addItem_(NSMenuItem.separatorItem())
self.menu.addItem_(show_cpu_item)
self.menu.addItem_(show_ram_item)
self.menu.addItem_(show_upload_item)
self.menu.addItem_(show_download_item)
self.menu.addItem_(NSMenuItem.separatorItem())
self.menu.addItem_(quit_item)
def toggleDockIcon_(self, sender):
"""切换Dock栏图标显示/隐藏"""
# 保存当前状态
old_show_dock_icon = self.show_dock_icon
# 切换状态
self.show_dock_icon = not self.show_dock_icon
self.set_activation_policy()
self.save_config()
# 使用旧状态查找菜单项,使用新状态设置标题
old_title = "显示Dock栏图标" if not old_show_dock_icon else "隐藏Dock栏图标"
new_title = "显示Dock栏图标" if not self.show_dock_icon else "隐藏Dock栏图标"
index = self.menu.indexOfItemWithTitle_(old_title)
if index != -1:
self.menu.itemAtIndex_(index).setTitle_(new_title)
def updateData_(self, timer):
"""更新数据"""
cpu = get_cpu_usage()
mem = get_memory_usage()
sent_speed, recv_speed, self.last_net_io = get_network_speed(self.last_net_io)
cpu_str = f"{cpu:.0f}%"
mem_str = f"{mem:.0f}%"
up_str = format_speed(sent_speed).replace("KB/s", "K").replace("MB/s", "M")
down_str = format_speed(recv_speed).replace("KB/s", "K").replace("MB/s", "M")
# 根据显示选项构建状态栏文本
status_parts = []
if self.show_cpu:
status_parts.append(f"C:{cpu_str}")
if self.show_ram:
status_parts.append(f"R:{mem_str}")
if self.show_upload:
status_parts.append(f"↑:{up_str}")
if self.show_download:
status_parts.append(f"↓:{down_str}")
# 如果所有选项都被隐藏,显示默认文本
if not status_parts:
status_text = "系统监控"
else:
status_text = " ".join(status_parts)
self.status_item.setTitle_(status_text)
def refreshData_(self, sender):
"""刷新数据"""
self.updateData_(None)
def setUpdateInterval_(self, sender):
"""设置更新间隔"""
new_interval = sender.representedObject()
if self.update_interval != new_interval:
self.update_interval = new_interval
self.update_timer.invalidate()
self.update_timer = NSTimer.scheduledTimerWithTimeInterval_target_selector_userInfo_repeats_(
self.update_interval, self, 'updateData:', None, True
)
# 更新菜单项显示,添加✓标记
for i in range(self.interval_menu.numberOfItems()):
item = self.interval_menu.itemAtIndex_(i)
interval = item.representedObject()
title = f"✓ {interval}秒" if interval == self.update_interval else f"{interval}秒"
item.setTitle_(title)
def toggleShowCPU_(self, sender):
"""切换CPU显示"""
self.show_cpu = not self.show_cpu
self.save_config()
# 更新菜单项标题
title = f"{'✓ ' if self.show_cpu else ''}显示CPU"
index = self.menu.indexOfItemWithTitle_(f"{'✓ ' if not self.show_cpu else ''}显示CPU")
if index != -1:
self.menu.itemAtIndex_(index).setTitle_(title)
# 更新状态栏
self.updateData_(None)
def toggleShowRAM_(self, sender):
"""切换RAM显示"""
self.show_ram = not self.show_ram
self.save_config()
# 更新菜单项标题
title = f"{'✓ ' if self.show_ram else ''}显示RAM"
index = self.menu.indexOfItemWithTitle_(f"{'✓ ' if not self.show_ram else ''}显示RAM")
if index != -1:
self.menu.itemAtIndex_(index).setTitle_(title)
# 更新状态栏
self.updateData_(None)
def toggleShowUpload_(self, sender):
"""切换上传显示"""
self.show_upload = not self.show_upload
self.save_config()
# 更新菜单项标题
title = f"{'✓ ' if self.show_upload else ''}显示上传"
index = self.menu.indexOfItemWithTitle_(f"{'✓ ' if not self.show_upload else ''}显示上传")
if index != -1:
self.menu.itemAtIndex_(index).setTitle_(title)
# 更新状态栏
self.updateData_(None)
def toggleShowDownload_(self, sender):
"""切换下载显示"""
self.show_download = not self.show_download
self.save_config()
# 更新菜单项标题
title = f"{'✓ ' if self.show_download else ''}显示下载"
index = self.menu.indexOfItemWithTitle_(f"{'✓ ' if not self.show_download else ''}显示下载")
if index != -1:
self.menu.itemAtIndex_(index).setTitle_(title)
# 更新状态栏
self.updateData_(None)
def quitApp_(self, sender):
"""退出应用"""
self.update_timer.invalidate()
NSApplication.sharedApplication().terminate_(self)
def main():
app = NSApplication.sharedApplication()
delegate = StatusBarAppDelegate.alloc().init()
app.setDelegate_(delegate)
try:
app.run()
except KeyboardInterrupt:
app.terminate_(delegate)
if __name__ == "__main__":
main()