代码如下:
import tkinter as tk
import psutil
import ctypes
import ctypes.wintypes
import time
import platform
import winreg
from PIL import Image
from pystray import Icon, Menu, MenuItem
import threading
import sys
import wmi
# 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
MAX_PROC_NAME_LEN = 28 # 进程名最大显示字符长度,超出截断
REFRESH_INTERVAL = 5000 # 数据刷新间隔(ms),5秒
def check_single_instance():
"""使用Windows互斥体实现单实例限制,重复启动直接退出"""
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
def get_gpu_name():
"""读取显卡型号,过滤Oray花生壳虚拟显卡,多显卡使用 | 分隔"""
try:
c = wmi.WMI()
gpu_list = []
for gpu in c.Win32_VideoController():
name = gpu.Name.strip()
if name != 'OrayIddDriver Device':
gpu_list.append(name)
return " | ".join(gpu_list)
except Exception:
return "未知显卡"
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():
"""获取桌面Progman窗口句柄"""
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=9):
"""获取CPU占用最高进程列表,过滤System Idle Process"""
proc_list = []
for p in psutil.process_iter(["name", "cpu_percent"]):
try:
proc_info = p.info
# 过滤系统空闲进程
if proc_info["name"].lower() == "system idle process":
continue
proc_list.append(proc_info)
except (psutil.NoSuchProcess, psutil.AccessDenied):
continue
proc_list.sort(key=lambda x: x["cpu_percent"], reverse=True)
return proc_list[:count]
def get_top_mem_processes(count=8):
"""获取内存占用最高进程列表"""
proc_list = []
for p in psutil.process_iter(["name", "memory_info"]):
try:
mem_mb = p.info["memory_info"].rss / 1024 / 1024
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]
def get_total_disk_size():
"""获取所有本地磁盘总容量"""
total = 0
parts = psutil.disk_partitions(all=False)
for part in parts:
try:
usage = psutil.disk_usage(part.mountpoint)
total += usage.total
except Exception:
continue
return round(total / (1024 ** 3), 2)
def get_cpu_full_name():
"""读取注册表获取CPU完整型号"""
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)
return cpu_name
except Exception:
return platform.processor()
class FloatSysMonitor:
def __init__(self, root):
self.root = root
self.root.title("桌面硬件监控面板")
self.root.overrideredirect(True)
self.root.attributes("-alpha", 0.88)
# ========= 删除无效的 -doublebuffer 这一行 =========
# 窗口大小与位置:右上角,纵向占满整个屏幕高度
self.w = 315
screen_w = self.root.winfo_screenwidth()
screen_h = self.root.winfo_screenheight()
x = screen_w - self.w
self.root.geometry(f"{self.w}x{screen_h}+{x}+0")
# 颜色配置
self.bg_color = "#1a1a22"
self.txt_label = "#b8c2cc"
self.txt_value = "#72e0a0"
self.root.configure(bg=self.bg_color)
self.window_visible = True
self.net_last = psutil.net_io_counters()
self.last_net_time = time.time()
# 静态硬件信息,初始化一次
self.os_info = f"{platform.system()} {platform.release()}"
self.cpu_full_name = get_cpu_full_name()
self.gpu_name = get_gpu_name()
self.mem_total_gb = round(psutil.virtual_memory().total / (1024 ** 3), 2)
self.disk_total_gb = get_total_disk_size()
main_frame = tk.Frame(root, bg=self.bg_color)
main_frame.pack(padx=12, pady=10, fill=tk.BOTH, expand=True)
# 顶部横幅
top_banner = tk.Frame(main_frame, bg=self.bg_color)
top_banner.pack(fill=tk.X, pady=(0, 8))
logo_canvas = tk.Canvas(top_banner, width=32, height=32, bg=self.bg_color, highlightthickness=0)
logo_canvas.grid(row=0, column=0)
s = 14
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="只要开始,虽远不迟。",
bg=self.bg_color,
fg="#ffffff",
font=("微软雅黑", 14, "bold")
).grid(row=0, column=1)
tk.Frame(top_banner, width=32, bg=self.bg_color).grid(row=0, column=2)
top_banner.grid_columnconfigure(1, weight=1)
tk.Frame(main_frame, height=2, bg="#444455").pack(fill=tk.X, pady=6)
# 硬件信息文本框
self.info_text = tk.Text(
main_frame, bg=self.bg_color, fg=self.txt_label,
font=("微软雅黑", 9), height=6, width=44,
highlightthickness=0, bd=0
)
self.info_text.pack(anchor="nw")
self.info_text.configure(state="disabled")
tk.Frame(main_frame, height=2, bg="#444455").pack(fill=tk.X, pady=6)
# 资源进度条区域
self.dynamic_frame = tk.Frame(main_frame, bg=self.bg_color)
self.dynamic_frame.pack(fill=tk.X)
tk.Label(self.dynamic_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.dynamic_frame, width=180, height=14, bg="#2c2c38", highlightthickness=0)
self.cpu_bar.grid(row=0, column=1, padx=8)
self.cpu_fill = self.cpu_bar.create_rectangle(0, 0, 0, 14, fill="#40aa70")
self.cpu_text = tk.Label(self.dynamic_frame, text="0%", bg=self.bg_color, fg=self.txt_value, font=("微软雅黑", 10))
self.cpu_text.grid(row=0, column=2)
tk.Label(self.dynamic_frame, text="内存", bg=self.bg_color, fg=self.txt_label, font=("微软雅黑", 10)).grid(row=1, column=0, sticky="w", pady=6)
self.mem_bar = tk.Canvas(self.dynamic_frame, width=180, height=14, bg="#2c2c38", highlightthickness=0)
self.mem_bar.grid(row=1, column=1, padx=8)
self.mem_fill = self.mem_bar.create_rectangle(0, 0, 0, 14, fill="#40aa70")
self.mem_text = tk.Label(self.dynamic_frame, text="0%", bg=self.bg_color, fg=self.txt_value, font=("微软雅黑", 10))
self.mem_text.grid(row=1, column=2)
tk.Label(self.dynamic_frame, text="C盘", bg=self.bg_color, fg=self.txt_label, font=("微软雅黑", 10)).grid(row=2, column=0, sticky="w")
self.disk_bar = tk.Canvas(self.dynamic_frame, width=180, height=14, bg="#2c2c38", highlightthickness=0)
self.disk_bar.grid(row=2, column=1, padx=8)
self.disk_fill = self.disk_bar.create_rectangle(0, 0, 0, 14, fill="#40aa70")
self.disk_text = tk.Label(self.dynamic_frame, text="0%", bg=self.bg_color, fg=self.txt_value, font=("微软雅黑", 10))
self.disk_text.grid(row=2, column=2)
tk.Frame(main_frame, height=2, bg="#444455").pack(fill=tk.X, pady=6)
self.net_label = tk.Label(main_frame, text="网络 ↑0MB/s ↓0MB/s", bg=self.bg_color, fg="#ffdd77", font=("微软雅黑", 10))
self.net_label.pack(anchor="nw")
tk.Frame(main_frame, height=2, bg="#444455").pack(fill=tk.X, pady=6)
tk.Label(main_frame, text="CPU占用TOP9进程", 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=2, bg="#444455").pack(fill=tk.X, pady=6)
tk.Label(main_frame, text="内存占用TOP8进程", 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)
# 绑定Map事件,窗口恢复显示时才置底
self.root.bind("<Map>", lambda e: force_desktop_bottom(self.hwnd))
# 初始化一次性置底,之后不再循环调用
force_desktop_bottom(self.hwnd)
self.refresh_dynamic_info()
self.tray_icon = None
self.init_tray()
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):
"""托盘菜单切换窗口显示隐藏,投递到tk主线程"""
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):
"""关闭托盘并销毁tk窗口,退出程序"""
if self.tray_icon:
self.tray_icon.stop()
self.root.destroy()
def refresh_dynamic_info(self):
"""刷新全部动态数据:网络、资源占用、进程列表"""
net_now = psutil.net_io_counters()
now_time = time.time()
delta_time = now_time - self.last_net_time
upload = (net_now.bytes_sent - self.net_last.bytes_sent) / delta_time / 1024 / 1024
download = (net_now.bytes_recv - self.net_last.bytes_recv) / delta_time / 1024 / 1024
self.net_last = net_now
self.last_net_time = 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}"
self.info_text.configure(state="normal")
self.info_text.delete("1.0", tk.END)
content = (
f"系统:{self.os_info}\n"
f"CPU:{self.cpu_full_name}\n"
f"显卡:{self.gpu_name}\n"
f"物理内存:{self.mem_total_gb} GB\n"
f"硬盘总量:{self.disk_total_gb} GB\n"
f"已开机时间:{up_time_str}"
)
self.info_text.insert(tk.END, content)
self.info_text.configure(state="disabled")
cpu_pct = psutil.cpu_percent(None)
mem = psutil.virtual_memory()
mem_pct = mem.percent
disk = psutil.disk_usage("C:/")
disk_pct = disk.percent
bar_width = 180
self.cpu_bar.coords(self.cpu_fill, 0, 0, bar_width * cpu_pct / 100, 14)
self.cpu_text.config(text=f"{cpu_pct}%")
self.mem_bar.coords(self.mem_fill, 0, 0, bar_width * mem_pct / 100, 14)
self.mem_text.config(text=f"{mem_pct}%")
self.disk_bar.coords(self.disk_fill, 0, 0, bar_width * disk_pct / 100, 14)
self.disk_text.config(text=f"{disk_pct}%")
self.net_label.config(text=f"网络 ↑{upload:.2f}MB/s ↓{download:.2f}MB/s")
top_cpu = get_top_cpu_processes(9)
cpu_text_lines = []
for idx, p in enumerate(top_cpu):
name_short = truncate_name(p['name'], MAX_PROC_NAME_LEN)
cpu_text_lines.append(f"{idx+1:2d}. {name_short:<28} {p['cpu_percent']:.1f}%")
self.cpu_proc_label.config(text="\n".join(cpu_text_lines))
top_mem = get_top_mem_processes(8)
mem_text_lines = []
for idx, p in enumerate(top_mem):
name_short = truncate_name(p['name'], MAX_PROC_NAME_LEN)
mem_text_lines.append(f"{idx+1:2d}. {name_short:<28} {p['mem_mb']:.0f}MB")
self.mem_proc_label.config(text="\n".join(mem_text_lines))
self.root.after(REFRESH_INTERVAL, self.refresh_dynamic_info)
if __name__ == "__main__":
if not check_single_instance():
print("监控程序已运行,退出")
sys.exit(0)
root = tk.Tk()
app = FloatSysMonitor(root)
root.mainloop()
打包可以使用pyinstaller,命令如下:
pyinstaller -F -w --name SysMonitor --hidden-import wmi --hidden-import pystray._win32 main.py -i your_icon.ico
在文件目录会生成dist目录,里面就是生成好的exe文件。
评论(0)
还没有评论,来抢沙发吧~