#!/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()