Files
continue-lab/cpu_monitor_gui.py

304 lines
8.5 KiB
Python
Executable File

#!/usr/bin/env python3
# Lancement: cpu-monitor
import tkinter as tk
from tkinter import ttk
import subprocess
import tempfile
import os
import time
import sys
import gi
import psutil
gi.require_version('Gtk', '3.0')
gi.require_version('AyatanaAppIndicator3', '0.1')
from gi.repository import AyatanaAppIndicator3, GLib, Gtk
MAX_POINTS = 60
UPDATE_INTERVAL = 1000 # ms
try:
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
HAVE_MPL = True
except ImportError:
HAVE_MPL = False
class Tracker:
def __init__(self):
self.cpu_data = []
self.ram_data = []
self._last_cpu = None
def cpu_percent(self):
return psutil.cpu_percent(interval=None)
class SysTrayIndicator:
def __init__(self, monitor):
self.indicator = AyatanaAppIndicator3.Indicator.new(
"cpu-monitor",
"utilities-system-monitor",
AyatanaAppIndicator3.IndicatorCategory.SYSTEM_SERVICES
)
self.indicator.set_status(AyatanaAppIndicator3.IndicatorStatus.ACTIVE)
self.indicator.set_menu(self._create_menu())
self.monitor = monitor
self._update_icon()
def _create_menu(self):
menu = Gtk.Menu()
cpu = psutil.cpu_percent(interval=None)
ram = psutil.virtual_memory().percent
disk = psutil.disk_usage('/').percent
item1 = Gtk.MenuItem(label=f"CPU: {cpu:.1f}%")
item1.show()
menu.append(item1)
item2 = Gtk.MenuItem(label=f"RAM: {ram:.1f}%")
item2.show()
menu.append(item2)
item3 = Gtk.MenuItem(label=f"Disk: {disk:.1f}%")
item3.show()
menu.append(item3)
sep = Gtk.SeparatorMenuItem()
sep.show()
menu.append(sep)
hide = Gtk.MenuItem(label="Hide Window")
hide.connect("activate", lambda w: self.monitor.root.withdraw())
hide.show()
menu.append(hide)
show = Gtk.MenuItem(label="Show Window")
show.connect("activate", lambda w: self.monitor.root.deiconify())
show.show()
menu.append(show)
sep2 = Gtk.SeparatorMenuItem()
sep2.show()
menu.append(sep2)
quit_item = Gtk.MenuItem(label="Quit")
quit_item.connect("activate", lambda w: self._quit())
quit_item.show()
menu.append(quit_item)
menu.show()
return menu
def _quit(self):
GLib.idle_add(Gtk.main_quit)
sys.exit(0)
def _update_icon(self):
try:
import cairo
except ImportError:
GLib.timeout_add_seconds(1, self._update_icon)
return
cpu = psutil.cpu_percent()
if cpu > 80:
color = (0.93, 0.33, 0.31)
elif cpu > 50:
color = (1.0, 0.55, 0.0)
else:
color = (0.40, 0.76, 0.97)
size = 24
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, size, size)
cr = cairo.Context(surface)
cr.set_source_rgba(*color, 1.0)
cr.paint()
tmp = tempfile.NamedTemporaryFile(suffix='.png', delete=False, dir='/tmp')
tmp.close()
surface.write_to_png(tmp.name)
try:
self.indicator.set_icon_full(tmp.name, "CPU Monitor")
except Exception:
pass
GLib.timeout_add_seconds(2, self._update_icon)
class App:
cpu_data = []
ram_data = []
def __init__(self, root):
self.root = root
self.root.title("CPU / RAM Monitor")
self.root.geometry("1000x200")
self.tracker = Tracker()
# --- Header card ---
card_bg = "#23272f"
card_fg = "#ffffff"
accent = "#4fc3f7"
header_frame = ttk.Frame(root)
header_frame.pack(fill=tk.X, padx=10, pady=(10, 0))
self.cpu_label = ttk.Label(
header_frame,
text=f"CPU: --%",
font=("Segoe UI", 20, "bold"),
foreground=accent,
)
self.cpu_label.pack(side=tk.LEFT, padx=20)
self.ram_label = ttk.Label(
header_frame,
text=f"RAM: --%",
font=("Segoe UI", 20, "bold"),
foreground="#81c784",
)
self.ram_label.pack(side=tk.LEFT, padx=20)
self.disk_label = ttk.Label(
header_frame,
text=f"Disk: --%",
font=("Segoe UI", 20, "bold"),
foreground="#ba68c8",
)
self.disk_label.pack(side=tk.LEFT, padx=20)
self.fig = None
if HAVE_MPL:
# --- Plot ---
self.fig = Figure(figsize=(14, 3), facecolor=card_bg)
ax_cpu = self.fig.add_subplot(1, 3, 1, facecolor=card_bg)
ax_ram = self.fig.add_subplot(1, 3, 2, facecolor=card_bg)
ax_disk = self.fig.add_subplot(1, 3, 3, facecolor=card_bg)
ax_cpu.set_title("CPU %", color=card_fg, fontsize=11)
ax_ram.set_title("RAM %", color=card_fg, fontsize=11)
ax_disk.set_title("Disk %", color=card_fg, fontsize=11)
ax_cpu.set_ylim(0, 100)
ax_ram.set_ylim(0, 100)
ax_disk.set_ylim(0, 100)
ax_cpu.tick_params(colors="#aaa")
ax_ram.tick_params(colors="#aaa")
ax_disk.tick_params(colors="#aaa")
for label in ax_cpu.get_yticklabels():
label.set_color("#aaa")
for label in ax_ram.get_yticklabels():
label.set_color("#aaa")
for label in ax_disk.get_yticklabels():
label.set_color("#aaa")
for line in ax_cpu.spines.values():
line.set_color("#3a3f4a")
for line in ax_ram.spines.values():
line.set_color("#3a3f4a")
for line in ax_disk.spines.values():
line.set_color("#3a3f4a")
self.cpu_line, = ax_cpu.plot(
[], [], color="#ef5350", linewidth=1.5,
marker="", markerfacecolor="#ef5350"
)
self.ram_line, = ax_ram.plot(
[], [], color="#66bb6a", linewidth=1.5,
marker="", markerfacecolor="#66bb6a"
)
self.disk_line, = ax_disk.plot(
[], [], color="#ba68c8", linewidth=1.5,
marker="", markerfacecolor="#ba68c8"
)
ax_cpu.axhline(y=80, color="#ef5350", linestyle="--", alpha=0.4)
canvas = FigureCanvasTkAgg(self.fig, master=root)
canvas.draw()
canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
# --- Footer status ---
status_frame = ttk.Frame(root)
status_frame.pack(fill=tk.X, padx=10, pady=(0, 10))
self.status_label = ttk.Label(
status_frame,
text="Monitoring...",
font=("Segoe UI", 9),
foreground="#888",
)
self.status_label.pack(side=tk.LEFT)
stop_btn = ttk.Button(
status_frame,
text="Stop",
command=self._stop,
width=10,
)
stop_btn.pack(side=tk.RIGHT, padx=(10, 0))
self._running = True
self._refresh()
def _refresh(self):
if not self._running:
return
cpu = self.tracker.cpu_percent()
ram = psutil.virtual_memory().percent
disk = psutil.disk_usage('/').percent
App._last_cpu = cpu
self.cpu_data.append(cpu)
self.ram_data.append(ram)
if len(App.cpu_data) > MAX_POINTS:
App.cpu_data = App.cpu_data[-MAX_POINTS:]
if len(App.ram_data) > MAX_POINTS:
App.ram_data = App.ram_data[-MAX_POINTS:]
self.cpu_label.config(text=f"CPU: {cpu:5.1f}%")
self.ram_label.config(text=f"RAM: {ram:5.1f}%")
self.disk_label.config(text=f"Disk: {disk:5.1f}%")
if HAVE_MPL and self.fig:
ax_cpu = self.fig.axes[0]
ax_ram = self.fig.axes[1]
ax_disk = self.fig.axes[2]
self.cpu_line.set_data(range(len(App.cpu_data)), App.cpu_data)
self.ram_line.set_data(range(len(App.ram_data)), App.ram_data)
ax_cpu.set_xlim(0, MAX_POINTS)
ax_ram.set_xlim(0, MAX_POINTS)
ax_disk.set_xlim(0, MAX_POINTS)
self.fig.canvas.draw_idle()
self.root.after(UPDATE_INTERVAL, self._refresh)
def _stop(self):
self._running = False
self.status_label.config(text="Stopped.")
self.root.withdraw()
def main():
root = tk.Tk()
app = App(root)
_tray = SysTrayIndicator(app)
root.mainloop()
if __name__ == "__main__":
main()