From a2630c8cd5ac6d49363d0b86b781720daba7285b Mon Sep 17 00:00:00 2001 From: tmzf5455 Date: Sat, 9 May 2026 23:25:33 +0200 Subject: [PATCH] =?UTF-8?q?v2:=20AppIndicator=20GNOME=20systray,=20.deskto?= =?UTF-8?q?p=20dans=20/etc/xdg/autostart,=20ic=C3=B4ne=20syst=C3=A8me?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cpu-monitor.png | Bin 0 -> 4476 bytes cpu_monitor_gui.desktop | 6 +- cpu_monitor_gui.py | 335 ++++++++++++++++++++++++---------------- debian/control | 15 +- debian/rules | 5 +- 5 files changed, 212 insertions(+), 149 deletions(-) create mode 100644 cpu-monitor.png diff --git a/cpu-monitor.png b/cpu-monitor.png new file mode 100644 index 0000000000000000000000000000000000000000..8fef48ac3c51bf03579370870a778abb35222f89 GIT binary patch literal 4476 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 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) + + +def create_tray_icon(monitor): + if HAVE_INDICATOR: + return SysTrayIndicator(monitor) + return None class App: + cpu_data = [] + ram_data = [] + _last_cpu = None + def __init__(self, root): self.root = root self.root.title("CPU / RAM Monitor") @@ -138,74 +215,56 @@ class App: ) self.disk_label.pack(side=tk.LEFT, padx=20) - # --- Plot --- - fig = Figure(figsize=(14, 3), facecolor=card_bg) - ax_cpu = fig.add_subplot(1, 4, 1, facecolor=card_bg) - ax_ram = fig.add_subplot(1, 4, 2, facecolor=card_bg) - ax_disk = fig.add_subplot(1, 4, 3, facecolor=card_bg) - ax_net = fig.add_subplot(1, 4, 4, facecolor=card_bg) + self.fig = None - 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_net.set_title("Réseau (B/s)", color=card_fg, fontsize=11) - ax_cpu.set_ylim(0, 100) - ax_ram.set_ylim(0, 100) - ax_disk.set_ylim(0, 100) - ax_net.set_ylim(0, 1) - ax_cpu.set_ylabel("%") - ax_ram.set_ylabel("%") - ax_disk.set_ylabel("%") - ax_net.set_ylabel("B/s") - ax_cpu.tick_params(colors="#aaa") - ax_ram.tick_params(colors="#aaa") - ax_net.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_net.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_net.spines.values(): - line.set_color("#3a3f4a") + 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) - 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" - ) - self.net_sent_line, = ax_net.plot( - [], [], color="#ffb74d", linewidth=1.5, marker="", markerfacecolor="#ffb74d" - ) - self.net_recv_line, = ax_net.plot( - [], [], color="#90caf9", linewidth=1.5, marker="", markerfacecolor="#90caf9" - ) + 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.axhline(y=80, color="#ef5350", linestyle="--", alpha=0.4) - ax_ram.axhline(y=80, color="#66bb6a", linestyle="--", alpha=0.4) - ax_disk.axhline(y=80, color="#ba68c8", linestyle="--", alpha=0.4) - ax_cpu.text(0.02, 0.02, "80%", fontsize=8, color="#ef5350", alpha=0.6, transform=ax_cpu.transAxes) + 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.fig = fig - self.ax_cpu = ax_cpu - self.ax_ram = ax_ram - self.ax_disk = ax_disk - self.ax_net = ax_net + 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" + ) - canvas = FigureCanvasTkAgg(fig, master=root) - canvas.draw() - canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True, padx=10, pady=10) + ax_cpu.axhline(y=80, color="#ef5350", linestyle="--", alpha=0.4) - toolbar = NavigationToolbar2Tk(canvas, root) - toolbar.update() + 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) @@ -233,49 +292,55 @@ class App: def _refresh(self): if not self._running: return - self.tracker.update() - cpu, ram = self.tracker.get_latest() - disk = self.tracker.get_disk() - send_rate, recv_rate = self.tracker.get_net_rates() + 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(self.cpu_data) > MAX_POINTS: + self.cpu_data = self.cpu_data[-MAX_POINTS:] + if len(self.ram_data) > MAX_POINTS: + self.ram_data = self.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}%") - mb_sent = send_rate / (1024 * 1024) - mb_recv = recv_rate / (1024 * 1024) - self.net_label.config(text=f"Net: ↑{mb_sent:.2f} ↓{mb_recv:.2f} MB/s") + if HAVE_MPL and self.fig: + ax_cpu = self.fig.axes[0] + ax_ram = self.fig.axes[1] + ax_disk = self.fig.axes[2] - cpu_arr = list(self.tracker.cpu_data) - ram_arr = list(self.tracker.ram_data) - disk_arr = list(self.tracker.disk_data) - sent_arr = list(self.tracker.net_sent_data) - recv_arr = list(self.tracker.net_recv_data) - self.cpu_line.set_data(range(len(cpu_arr)), cpu_arr) - self.ram_line.set_data(range(len(ram_arr)), ram_arr) - self.disk_line.set_data(range(len(disk_arr)), disk_arr) - self.net_sent_line.set_data(range(len(sent_arr)), sent_arr) - self.net_recv_line.set_data(range(len(recv_arr)), recv_arr) + self.cpu_line.set_data(range(len(self.cpu_data)), self.cpu_data) + self.ram_line.set_data(range(len(self.ram_data)), self.ram_data) - self.ax_cpu.set_xlim(0, MAX_POINTS) - self.ax_ram.set_xlim(0, MAX_POINTS) - self.ax_disk.set_xlim(0, MAX_POINTS) - self.ax_net.set_xlim(0, MAX_POINTS) + ax_cpu.set_xlim(0, MAX_POINTS) + ax_ram.set_xlim(0, MAX_POINTS) + ax_disk.set_xlim(0, MAX_POINTS) - max_net = max(max(sent_arr) if sent_arr else 0, max(recv_arr) if recv_arr else 0) - if max_net > 0: - self.ax_net.set_ylim(0, max_net * 1.2) + self.fig.canvas.draw_idle() - 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.destroy() + if HAVE_INDICATOR: + GLib.idle_add(Gtk.main_quit) def main(): root = tk.Tk() app = App(root) + + _tray = None + if HAVE_INDICATOR: + _tray = SysTrayIndicator(app) + root.bind('', lambda e: GLib.idle_add(Gtk.main_quit)) + root.mainloop() diff --git a/debian/control b/debian/control index 256ee2c..adbd43b 100644 --- a/debian/control +++ b/debian/control @@ -1,13 +1,14 @@ Source: cpu-monitor -Section: python +Section: utils Priority: optional -Maintainer: tmzf5455 +Maintainer: tmzf5455 Build-Depends: debhelper-compat (= 13) -Standards-Version: 4.1.4 +Standards-Version: 4.6.0 +Rules-Requires-Root: no Package: cpu-monitor -Maintainer: tmzf5455 Architecture: all -Depends: python3, python3-psutil, python3-matplotlib, python3-tk, x11-utils, ${misc:Depends} -Description: CPU Monitor GUI - A Python GUI application for monitoring CPU usage in real-time. +Depends: python3, python3-psutil, python3-matplotlib, python3-tk, gir1.2-ayatana-appindicator3-0.1, ${misc:Depends} +Description: CPU/RAM/Disk/Network monitor GUI + A system monitor tool with real-time matplotlib charts + and a tkinter GUI, showing CPU, RAM, disk and network usage. diff --git a/debian/rules b/debian/rules index d1fe4d9..12229d9 100755 --- a/debian/rules +++ b/debian/rules @@ -1,8 +1,7 @@ #!/usr/bin/make -f - %: dh $@ - override_dh_auto_install: install -D -m 0755 cpu_monitor_gui.py $(CURDIR)/debian/cpu-monitor/usr/bin/cpu-monitor - install -D -m 0644 cpu_monitor_gui.desktop $(CURDIR)/debian/cpu-monitor/usr/share/applications/cpu-monitor.desktop + install -D -m 0644 cpu_monitor_gui.desktop $(CURDIR)/debian/cpu-monitor/etc/xdg/autostart/cpu-monitor.desktop + install -D -m 0644 cpu-monitor.png $(CURDIR)/debian/cpu-monitor/usr/share/icons/hicolor/48x48/apps/cpu-monitor.png