自研windows版conky(桌面系统监控面板)

代码如下:

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文件。

本文为原创文章,转载请注明出处。

原文链接:https://850406.xyz/post/自研windows版conky

评论(0)

还没有评论,来抢沙发吧~

验证码