代码如下:
import tkinter as tk
import psutil
import ctypes
import ctypes.wintypes
import time
import platform
import winreg
import subprocess
import json
import os
import threading
import sys
from PIL import Image
from pystray import Icon, Menu, MenuItem
# ===================== Windows常量 =====================
WS_EX_LAYERED = 0x00080000
WS_EX_TRANSPARENT = 0x0020
WS_EX_NOACTIVATE = 0x08000000
GWL_EXSTYLE = -20
HWND_BOTTOM = 1
SWP_NOACTIVATE = 0x0010
SWP_NOMOVE = 0x0001
SWP_NOSIZE = 0x0002
EXIT_FLAG = threading.Event()
DATA_LOCK = threading.Lock()
shared_data = {
"cpu_pct": 0.0,
"mem_pct": 0.0,
"gpu_load": 0.0,
"gpu_name": "未知显卡",
"disks": [],
"upload": 0.0,
"download": 0.0,
"up_time_str": "",
"top_cpu_proc": [],
"top_mem_proc": []
}
def load_config():
default_cfg = {
"panel_width": 315, # 默认改成315
"refresh_interval": 4000,
"max_proc_name_len": 24,
"bar_width": 180,
"bar_height": 12,
"alert_threshold": 80,
"bg_color": "#1a1a22",
"txt_label": "#b8c2cc",
"txt_value": "#72e0a0",
"bar_normal_color": "#40aa70",
"bar_warn_color": "#dd4444",
"net_color": "#ffdd77",
"blacklist": ["system idle process", "svchost.exe"],
"disk_ignore": ["Z:", "Y:"],
"win_x": -1,
"win_y": 0
}
path = "config.json"
if os.path.exists(path):
try:
with open(path, "r", encoding="utf-8") as f:
cfg = json.load(f)
for k, v in default_cfg.items():
if k not in cfg:
cfg[k] = v
return cfg
except Exception:
return default_cfg
return default_cfg
def save_config(cfg):
path = "config.json"
try:
with open(path, "w", encoding="utf-8") as f:
json.dump(cfg, f, indent=4, ensure_ascii=False)
except Exception:
pass
CONFIG = load_config()
def check_single_instance():
MUTEX_NAME = "Global\\FloatSysMonitorMutex_20260922"
mutex = ctypes.windll.kernel32.CreateMutexW(None, False, MUTEX_NAME)
if ctypes.windll.kernel32.GetLastError() == 183:
ctypes.windll.kernel32.CloseHandle(mutex)
return False
return True
# ========== 显卡读取:新增注册表方案(专门适配老Intel核显) ==========
def get_gpu_name_from_registry():
try:
base_key = r"SYSTEM\CurrentControlSet\Control\Class\{4d36e968-e325-11ce-bfc1-08002be10318}"
key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, base_key)
subkey_count = winreg.QueryInfoKey(key)[0]
filter_keywords = ["OrayIdd", "RDP", "Mirror", "Remote Display"]
gpu_names = []
for i in range(subkey_count):
try:
sub_name = winreg.EnumKey(key, i)
if sub_name.startswith("000"):
sub = winreg.OpenKey(key, sub_name)
try:
dev_name = winreg.QueryValueEx(sub, "DriverDesc")[0].strip()
if dev_name and not any(kw.lower() in dev_name.lower() for kw in filter_keywords):
gpu_names.append(dev_name)
except:
pass
winreg.CloseKey(sub)
except:
continue
winreg.CloseKey(key)
gpu_names = list(dict.fromkeys(gpu_names))
if gpu_names:
return " | ".join(gpu_names)
except Exception:
pass
return ""
def get_gpu_name_powershell():
cmds = [
r'powershell -Command "Get-CimInstance Win32_VideoController | Select-Object -ExpandProperty Name"',
r'powershell -Command "Get-WmiObject Win32_VideoController | Select-Object -ExpandProperty Name"'
]
filter_keywords = ["OrayIdd", "RDP", "Mirror", "Remote Display"]
for cmd in cmds:
try:
ret = subprocess.check_output(cmd, shell=True, encoding="utf-8", timeout=3)
lines = []
for name in ret.splitlines():
name = name.strip()
if not name:
continue
if any(kw.lower() in name.lower() for kw in filter_keywords):
continue
lines.append(name)
lines = list(dict.fromkeys(lines))
if lines:
return " | ".join(lines)
except Exception:
continue
return ""
def get_final_gpu_name():
# 优先级:注册表 > PowerShell,删掉LHM读取显卡名称(老核显LHM经常读不出名字)
n = get_gpu_name_from_registry()
if n:
return n
n = get_gpu_name_powershell()
if n:
return n
return "未知显卡"
# ========== GPU负载依然走LHM(如果你不需要GPU占用,可以后续去掉) ==========
clr = None
Computer = None
HardwareType = None
SensorType = None
hw_computer = None
try:
import pythonnet
clr = pythonnet.load("LibreHardwareMonitorLib")
from LibreHardwareMonitor.Hardware import Computer, HardwareType, SensorType
hw_computer = Computer()
hw_computer.CPUEnabled = False
hw_computer.GPUEnabled = True
hw_computer.HDDEnabled = False
hw_computer.Open()
except Exception:
pass
def read_gpu_load():
gpu_load = 0.0
if not hw_computer:
return 0.0
try:
hw_computer.Accept(None)
for hw in hw_computer.Hardware:
hw.Update()
if hw.HardwareType in (HardwareType.GpuNvidia, HardwareType.GpuAmd, HardwareType.GpuIntel):
for s in hw.Sensors:
if s.SensorType == SensorType.Load and s.Value is not None:
gpu_load = float(s.Value)
return gpu_load
except Exception:
pass
return 0.0
def get_cpu_full_name():
try:
key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, r"HARDWARE\DESCRIPTION\System\CentralProcessor\0")
cpu_name = winreg.QueryValueEx(key, "ProcessorNameString")[0].strip()
winreg.CloseKey(key)
if "CPU" in cpu_name:
cpu_name = cpu_name.split("CPU")[0].strip()
return cpu_name
except Exception:
return platform.processor()
def get_all_local_disks():
disks = []
ignore = [d.upper() for d in CONFIG["disk_ignore"]]
try:
parts = psutil.disk_partitions(all=False)
for p in parts:
mp = p.mountpoint
if mp.upper() in ignore:
continue
try:
usage = psutil.disk_usage(mp)
disks.append({
"letter": mp,
"percent": usage.percent,
"total_gb": round(usage.total / (1024**3), 2)
})
except Exception as e:
continue
except Exception:
pass
return disks
def truncate_name(name: str, max_len: int):
if len(name) > max_len:
return name[:max_len - 1] + "…"
return name
def set_full_mouse_transparent(hwnd):
style = ctypes.windll.user32.GetWindowLongW(hwnd, GWL_EXSTYLE)
new_style = style | WS_EX_LAYERED | WS_EX_TRANSPARENT | WS_EX_NOACTIVATE
ctypes.windll.user32.SetWindowLongW(hwnd, GWL_EXSTYLE, new_style)
def get_progman_hwnd():
return ctypes.windll.user32.FindWindowW("Progman", None)
def force_desktop_bottom(hwnd):
progman = get_progman_hwnd()
if progman:
ctypes.windll.user32.SetWindowPos(
hwnd, progman,
0, 0, 0, 0,
SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE
)
else:
ctypes.windll.user32.SetWindowPos(
hwnd, HWND_BOTTOM,
0, 0, 0, 0,
SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE
)
def get_top_cpu_processes(count=12):
proc_list = []
bl = [x.lower() for x in CONFIG["blacklist"]]
try:
for p in psutil.process_iter(["name", "cpu_percent"]):
try:
pname = p.info["name"].lower()
if pname in bl:
continue
proc_list.append(p.info)
except (psutil.NoSuchProcess, psutil.AccessDenied):
continue
proc_list.sort(key=lambda x: x["cpu_percent"], reverse=True)
return proc_list[:count]
except Exception:
return []
def get_top_mem_processes(count=12):
proc_list = []
bl = [x.lower() for x in CONFIG["blacklist"]]
try:
for p in psutil.process_iter(["name", "memory_info"]):
try:
mem_mb = p.info["memory_info"].rss / 1024 / 1024
pname = p.info["name"].lower()
if pname in bl:
continue
proc_list.append({"name": p.info["name"], "mem_mb": mem_mb})
except (psutil.NoSuchProcess, psutil.AccessDenied):
continue
proc_list.sort(key=lambda x: x["mem_mb"], reverse=True)
return proc_list[:count]
except Exception:
return []
def data_collector(net_last):
interval = CONFIG["refresh_interval"] / 1000.0
gpu_static_name = get_final_gpu_name()
while not EXIT_FLAG.is_set():
net_now = psutil.net_io_counters()
now_time = time.time()
delta_time = now_time - net_last["t"]
upload, download = 0.0, 0.0
if delta_time > 0.3:
upload = (net_now.bytes_sent - net_last["sent"]) / delta_time / 1024 / 1024
download = (net_now.bytes_recv - net_last["recv"]) / delta_time / 1024 / 1024
net_last["sent"] = net_now.bytes_sent
net_last["recv"] = net_now.bytes_recv
net_last["t"] = now_time
boot_stamp = psutil.boot_time()
up_seconds = time.time() - boot_stamp
up_h = int(up_seconds // 3600)
up_m = int((up_seconds % 3600) // 60)
up_s = int(up_seconds % 60)
up_time_str = f"{up_h:02d}:{up_m:02d}:{up_s:02d}"
cpu_pct = psutil.cpu_percent(None)
mem = psutil.virtual_memory()
mem_pct = mem.percent
disks = get_all_local_disks()
gpu_load = read_gpu_load()
top_cpu = get_top_cpu_processes(CONFIG["top_cpu_count"])
top_mem = get_top_mem_processes(CONFIG["top_mem_count"])
with DATA_LOCK:
shared_data["cpu_pct"] = cpu_pct
shared_data["mem_pct"] = mem_pct
shared_data["gpu_load"] = gpu_load
shared_data["gpu_name"] = gpu_static_name
shared_data["disks"] = disks
shared_data["upload"] = upload
shared_data["download"] = download
shared_data["up_time_str"] = up_time_str
shared_data["top_cpu_proc"] = top_cpu
shared_data["top_mem_proc"] = top_mem
time.sleep(interval)
class FloatSysMonitor:
def __init__(self, root):
self.root = root
self.root.title("桌面硬件监控面板")
self.root.overrideredirect(True)
self.root.attributes("-alpha", 0.88)
self.cfg = CONFIG
panel_w = self.cfg["panel_width"]
screen_w = self.root.winfo_screenwidth()
screen_h = self.root.winfo_screenheight()
if self.cfg["win_x"] < 0:
x = screen_w - panel_w
y = 0
else:
x = self.cfg["win_x"]
y = self.cfg["win_y"]
self.root.geometry(f"{panel_w}x{screen_h}+{x}+{y}")
self.bg_color = self.cfg["bg_color"]
self.txt_label = self.cfg["txt_label"]
self.txt_value = self.cfg["txt_value"]
self.root.configure(bg=self.bg_color)
self.window_visible = True
self.os_info = f"{platform.system()} {platform.release()}"
self.cpu_full_name = get_cpu_full_name()
self.mem_total_gb = round(psutil.virtual_memory().total / (1024 ** 3), 2)
main_frame = tk.Frame(root, bg=self.bg_color)
main_frame.pack(padx=8, pady=8, fill=tk.BOTH, expand=True)
top_banner = tk.Frame(main_frame, bg=self.bg_color)
top_banner.pack(fill=tk.X, pady=(0,4))
logo_canvas = tk.Canvas(top_banner, width=28, height=28, bg=self.bg_color, highlightthickness=0)
logo_canvas.grid(row=0, column=0)
s = 12
logo_canvas.create_rectangle(0, 0, s, s, fill="#f25022")
logo_canvas.create_rectangle(s, 0, s * 2, s, fill="#7fba00")
logo_canvas.create_rectangle(0, s, s, s * 2, fill="#00a4ef")
logo_canvas.create_rectangle(s, s, s * 2, s * 2, fill="#ffb900")
tk.Label(
top_banner,
text=CONFIG["name"],
bg=self.bg_color,
fg="#ffffff",
font=("微软雅黑", 11, "bold")
).grid(row=0, column=1, padx=4)
top_banner.grid_columnconfigure(1, weight=1)
tk.Frame(main_frame, height=1, bg="#444455").pack(fill=tk.X, pady=4)
self.info_label = tk.Label(
main_frame, bg=self.bg_color, fg=self.txt_label,
font=("微软雅黑",9), justify="left", anchor="nw"
)
self.info_label.pack(anchor="nw")
tk.Frame(main_frame, height=1, bg="#444455").pack(fill=tk.X, pady=4)
# ========= 统一进度条父框架,CPU/内存/GPU/磁盘全部放在这里,自动对齐 =========
self.bar_frame = tk.Frame(main_frame, bg=self.bg_color)
self.bar_frame.pack(fill=tk.X)
self.bar_frame.grid_columnconfigure(0, weight=0)
self.bar_frame.grid_columnconfigure(1, weight=0)
self.bar_frame.grid_columnconfigure(2, weight=0)
self.bar_w = self.cfg["bar_width"]
self.bar_h = self.cfg["bar_height"]
self.threshold = self.cfg["alert_threshold"]
self.bar_normal = self.cfg["bar_normal_color"]
self.bar_warn = self.cfg["bar_warn_color"]
# CPU
tk.Label(self.bar_frame, text="CPU", bg=self.bg_color, fg=self.txt_label, font=("微软雅黑", 10)).grid(row=0, column=0, sticky="w")
self.cpu_bar = tk.Canvas(self.bar_frame, width=self.bar_w, height=self.bar_h, bg="#2c2c38", highlightthickness=0)
self.cpu_bar.grid(row=0, column=1, padx=6)
self.cpu_fill = self.cpu_bar.create_rectangle(0, 0, 0, self.bar_h, fill=self.bar_normal)
self.cpu_text = tk.Label(self.bar_frame, text="0%", bg=self.bg_color, fg=self.txt_value, font=("微软雅黑", 10))
self.cpu_text.grid(row=0, column=2)
# 内存
tk.Label(self.bar_frame, text="内存", bg=self.bg_color, fg=self.txt_label, font=("微软雅黑", 10)).grid(row=1, column=0, sticky="w", pady=3)
self.mem_bar = tk.Canvas(self.bar_frame, width=self.bar_w, height=self.bar_h, bg="#2c2c38", highlightthickness=0)
self.mem_bar.grid(row=1, column=1, padx=6)
self.mem_fill = self.mem_bar.create_rectangle(0, 0, 0, self.bar_h, fill=self.bar_normal)
self.mem_text = tk.Label(self.bar_frame, text="0%", bg=self.bg_color, fg=self.txt_value, font=("微软雅黑", 10))
self.mem_text.grid(row=1, column=2)
# GPU占用
tk.Label(self.bar_frame, text="GPU", bg=self.bg_color, fg=self.txt_label, font=("微软雅黑", 10)).grid(row=2, column=0, sticky="w", pady=3)
self.gpu_bar = tk.Canvas(self.bar_frame, width=self.bar_w, height=self.bar_h, bg="#2c2c38", highlightthickness=0)
self.gpu_bar.grid(row=2, column=1, padx=6)
self.gpu_fill = self.gpu_bar.create_rectangle(0,0,0,self.bar_h, fill=self.bar_normal)
self.gpu_text = tk.Label(self.bar_frame, text="0%", bg=self.bg_color, fg=self.txt_value, font=("微软雅黑", 10))
self.gpu_text.grid(row=2, column=2)
# 磁盘行,和上面在同一个grid里,自动对齐
self.disk_start_row = 3
tk.Frame(main_frame, height=1, bg="#444455").pack(fill=tk.X, pady=4)
self.net_label = tk.Label(main_frame, text="网络 ↑0MB/s ↓0MB/s", bg=self.bg_color, fg=self.cfg["net_color"], font=("微软雅黑", 10))
self.net_label.pack(anchor="nw")
tk.Frame(main_frame, height=1, bg="#444455").pack(fill=tk.X, pady=4)
tk.Label(main_frame, text="CPU占用排行", bg=self.bg_color, fg="#ffffff", font=("微软雅黑", 10, "bold")).pack(anchor="nw")
self.cpu_proc_label = tk.Label(main_frame, text="加载中...", bg=self.bg_color, fg=self.txt_label, font=("微软雅黑", 8), justify="left")
self.cpu_proc_label.pack(anchor="nw")
tk.Frame(main_frame, height=1, bg="#444455").pack(fill=tk.X, pady=4)
tk.Label(main_frame, text="内存占用排行", bg=self.bg_color, fg="#ffffff", font=("微软雅黑", 10, "bold")).pack(anchor="nw")
self.mem_proc_label = tk.Label(main_frame, text="加载中...", bg=self.bg_color, fg=self.txt_label, font=("微软雅黑", 8), justify="left")
self.mem_proc_label.pack(anchor="nw")
self.root.update_idletasks()
self.hwnd = ctypes.windll.user32.GetParent(self.root.winfo_id())
set_full_mouse_transparent(self.hwnd)
self.root.bind("<Map>", lambda e: force_desktop_bottom(self.hwnd))
force_desktop_bottom(self.hwnd)
net_init = psutil.net_io_counters()
net_last_data = {"sent": net_init.bytes_sent, "recv": net_init.bytes_recv, "t": time.time()}
collector_thread = threading.Thread(target=data_collector, args=(net_last_data,), daemon=True)
collector_thread.start()
self.tray_icon = None
self.init_tray()
self.render_ui()
def init_tray(self):
try:
image = Image.open("icon.png")
except Exception:
image = Image.new("RGB", (64, 64), (26, 26, 34))
menu = Menu(
MenuItem("显示/隐藏面板", self.on_tray_toggle),
MenuItem("退出", self.on_tray_exit)
)
self.tray_icon = Icon("SysMonitor", image, title="系统监控", menu=menu)
tray_thread = threading.Thread(target=self.tray_icon.run, daemon=True)
tray_thread.start()
def on_tray_toggle(self):
self.root.after(0, self.toggle_window)
def toggle_window(self):
if self.window_visible:
self.root.withdraw()
self.window_visible = False
else:
self.root.deiconify()
self.window_visible = True
force_desktop_bottom(self.hwnd)
def on_tray_exit(self):
self.root.after(0, self.quit_app)
def quit_app(self):
x = self.root.winfo_x()
y = self.root.winfo_y()
self.cfg["win_x"] = x
self.cfg["win_y"] = y
save_config(self.cfg)
EXIT_FLAG.set()
if self.tray_icon:
self.tray_icon.stop()
self.root.destroy()
def render_ui(self):
with DATA_LOCK:
d = shared_data.copy()
max_name_len = self.cfg["max_proc_name_len"]
content = (
f"系统:{self.os_info}\n"
f"CPU:{self.cpu_full_name}\n"
f"显卡:{d['gpu_name']}\n"
f"物理内存:{self.mem_total_gb} GB\n"
f"已开机时间:{d['up_time_str']}"
)
self.info_label.config(text=content)
# CPU进度条
cpu_color = self.bar_warn if d["cpu_pct"] >= self.threshold else self.bar_normal
self.cpu_bar.coords(self.cpu_fill, 0, 0, self.bar_w * d["cpu_pct"] / 100, self.bar_h)
self.cpu_bar.itemconfig(self.cpu_fill, fill=cpu_color)
self.cpu_text.config(text=f"{d['cpu_pct']}%")
# 内存进度条
mem_color = self.bar_warn if d["mem_pct"] >= self.threshold else self.bar_normal
self.mem_bar.coords(self.mem_fill, 0, 0, self.bar_w * d["mem_pct"] / 100, self.bar_h)
self.mem_bar.itemconfig(self.mem_fill, fill=mem_color)
self.mem_text.config(text=f"{d['mem_pct']}%")
# GPU占用进度条
gpu_color = self.bar_warn if d["gpu_load"] >= self.threshold else self.bar_normal
self.gpu_bar.coords(self.gpu_fill,0,0, self.bar_w * d["gpu_load"] /100, self.bar_h)
self.gpu_bar.itemconfig(self.gpu_fill, fill=gpu_color)
self.gpu_text.config(text=f"{d['gpu_load']:.1f}%")
# ========= 磁盘渲染:和CPU/GPU放在同一个grid,完美对齐 =========
# 先清空磁盘旧控件(保留前3行CPU/内存/GPU)
for w in self.bar_frame.winfo_children():
info = w.grid_info()
if int(info["row"]) >= self.disk_start_row:
w.destroy()
row_idx = self.disk_start_row
for disk in d["disks"]:
letter = disk["letter"]
pct = disk["percent"]
color = self.bar_warn if pct >= self.threshold else self.bar_normal
tk.Label(self.bar_frame, text=letter, bg=self.bg_color, fg=self.txt_label, font=("微软雅黑", 9)).grid(row=row_idx, column=0, sticky="w", pady=3)
bar = tk.Canvas(self.bar_frame, width=self.bar_w, height=self.bar_h, bg="#2c2c38", highlightthickness=0)
bar.grid(row=row_idx, column=1, padx=6)
fill = bar.create_rectangle(0,0, self.bar_w * pct / 100, self.bar_h, fill=color)
txt = tk.Label(self.bar_frame, text=f"{pct}%", bg=self.bg_color, fg=self.txt_value, font=("微软雅黑",9))
txt.grid(row=row_idx, column=2)
row_idx += 1
if row_idx == self.disk_start_row:
tk.Label(self.bar_frame, text="无可用磁盘", bg=self.bg_color, fg="#888888", font=("微软雅黑",9)).grid(row=row_idx,column=0)
# 网速
self.net_label.config(text=f"网络 ↑{d['upload']:.2f}MB/s ↓{d['download']:.2f}MB/s")
# CPU进程列表
cpu_text_lines = []
for idx, p in enumerate(d["top_cpu_proc"]):
name_short = truncate_name(p['name'], max_name_len)
cpu_text_lines.append(f"{idx+1:2d}. {name_short} {p['cpu_percent']:.1f}%")
self.cpu_proc_label.config(text="\n".join(cpu_text_lines))
# 内存进程列表
mem_text_lines = []
for idx, p in enumerate(d["top_mem_proc"]):
name_short = truncate_name(p['name'], max_name_len)
mem_text_lines.append(f"{idx+1:2d}. {name_short} {p['mem_mb']:.0f}MB")
self.mem_proc_label.config(text="\n".join(mem_text_lines))
self.root.after(self.cfg["refresh_interval"], self.render_ui)
if __name__ == "__main__":
ctypes.windll.shcore.SetProcessDpiAwareness(2)
if not check_single_instance():
print("监控程序已运行,退出")
sys.exit(0)
root = tk.Tk()
app = FloatSysMonitor(root)
root.mainloop()
新建一个config.json,内容如下:
{
"name":"今天过的怎么样? 梦想是不是更远了?",
"panel_width": 315,
"refresh_interval": 4000,
"max_proc_name_len": 24,
"bar_width": 200,
"bar_height": 12,
"alert_threshold": 80,
"bg_color": "#1a1a22",
"txt_label": "#b8c2cc",
"txt_value": "#72e0a0",
"bar_normal_color": "#40aa70",
"bar_warn_color": "#dd4444",
"net_color": "#ffdd77",
"blacklist": ["system idle process", "svchost.exe"],
"disk_ignore": ["Z:", "Y:"],
"win_x": -1,
"win_y": 0,
"top_cpu_count": 13,
"top_mem_count": 13
}
打包可以使用pyinstaller,命令如下:
pyinstaller -F -w --name SysMonitor --hidden-import wmi --hidden-import pystray._win32 main.py -i your_icon.ico
在文件目录会生成dist目录,里面就是生成好的exe文件。
评论(0)
还没有评论,来抢沙发吧~