Files
7Cooler-Ultimate/7cooler_ultimate.py

423 lines
16 KiB
Python

#!/usr/bin/env python3
import sys, time, subprocess
import psutil
from collections import deque
from PyQt6.QtCore import Qt, QTimer, QThread, pyqtSignal, QPointF, QRectF, QPropertyAnimation, QEasingCurve, pyqtProperty
from PyQt6.QtGui import QFont, QPainter, QColor, QPen, QBrush, QPainterPath, QAction, QIcon
from PyQt6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
QLabel, QProgressBar, QSystemTrayIcon, QMenu, QGridLayout,
QStackedWidget, QPushButton, QFrame, QScrollArea, QSlider)
# ==========================================
# THREAD DE DONNÉES (CPU, RAM, GPU, RESEAU)
# ==========================================
class UltimateMonitorThread(QThread):
stats_updated = pyqtSignal(dict)
def __init__(self):
super().__init__()
self.interval = 1000
self.net_io = psutil.net_io_counters()
self.last_time = time.time()
def run(self):
while True:
current_time = time.time()
dt = current_time - self.last_time
new_net_io = psutil.net_io_counters()
stats = {
'cpu_temp': self.get_cpu_temp(),
'cpu_percent': psutil.cpu_percent(interval=None),
'cores': self.get_cores_temp(),
'ram': psutil.virtual_memory().percent,
'swap': psutil.swap_memory().percent,
'disk': psutil.disk_usage('/').percent,
'net_dl': (new_net_io.bytes_recv - self.net_io.bytes_recv) / dt / 1024 / 1024, # MB/s
'net_ul': (new_net_io.bytes_sent - self.net_io.bytes_sent) / dt / 1024 / 1024, # MB/s
'gpu': self.get_gpu_info()
}
self.net_io = new_net_io
self.last_time = current_time
self.stats_updated.emit(stats)
self.msleep(self.interval)
def get_cpu_temp(self):
temps = psutil.sensors_temperatures()
for name in ['coretemp', 'acpitz', 'cpu_thermal', 'k10temp']:
if name in temps: return int(temps[name][0].current)
return 40
def get_cores_temp(self):
cores = []
temps = psutil.sensors_temperatures()
if 'coretemp' in temps:
cores = [(e.label, int(e.current)) for e in temps['coretemp'] if 'Core' in e.label]
return cores if cores else [(f"Core {i}", 40) for i in range(psutil.cpu_count(logical=False) or 4)]
def get_gpu_info(self):
# Tente de récupérer les infos NVIDIA en priorité
try:
res = subprocess.check_output(['nvidia-smi', '--query-gpu=temperature.gpu,utilization.gpu', '--format=csv,noheader,nounits'], encoding='utf-8')
temp, util = map(int, res.strip().split(', '))
return {"name": "NVIDIA GPU", "temp": temp, "util": util}
except:
# Fallback sur les capteurs standards (AMD/Intel)
temps = psutil.sensors_temperatures()
if 'amdgpu' in temps:
return {"name": "AMD GPU", "temp": int(temps['amdgpu'][0].current), "util": 0}
return None
# ==========================================
# WIDGETS ANIMÉS ZORIN OS PRO
# ==========================================
class SmoothProgressBar(QProgressBar):
def __init__(self, color="#1E88E5"):
super().__init__()
self.setFixedHeight(6)
self.setTextVisible(False)
self._color = color
self._value = 0
self.anim = QPropertyAnimation(self, b"animated_value")
self.anim.setEasingCurve(QEasingCurve.Type.OutCubic)
self.anim.setDuration(400)
self.update_style()
def update_style(self):
self.setStyleSheet(f"""
QProgressBar {{ background-color: #222222; border-radius: 3px; }}
QProgressBar::chunk {{ background-color: {self._color}; border-radius: 3px; }}
""")
def set_color(self, color):
self._color = color
self.update_style()
@pyqtProperty(int)
def animated_value(self): return self._value
@animated_value.setter
def animated_value(self, val):
self._value = val
super().setValue(val)
def set_smooth_value(self, val):
self.anim.setStartValue(self._value)
self.anim.setEndValue(val)
self.anim.start()
class ModernGauge(QWidget):
def __init__(self, title, color="#1E88E5"):
super().__init__()
self.setFixedSize(180, 180)
self.value = 0
self.title = title
self.color = QColor(color)
def set_value(self, val):
self.value = val
self.update()
def set_color(self, hex_color):
self.color = QColor(hex_color)
self.update()
def paintEvent(self, event):
painter = QPainter(self)
painter.setRenderHint(QPainter.RenderHint.Antialiasing)
rect = QRectF(20, 20, 140, 140)
painter.setPen(QPen(QColor("#2A2A2A"), 12, Qt.PenStyle.SolidLine, Qt.PenCapStyle.RoundCap))
painter.drawArc(rect, 135 * 16, -270 * 16)
span = int(-270 * (self.value / 100.0) * 16)
painter.setPen(QPen(self.color, 12, Qt.PenStyle.SolidLine, Qt.PenCapStyle.RoundCap))
painter.drawArc(rect, 135 * 16, span)
painter.setPen(QColor("#FFFFFF"))
painter.setFont(QFont("Inter", 32, QFont.Weight.Thin))
painter.drawText(rect, Qt.AlignmentFlag.AlignCenter, f"{self.value}°")
painter.setFont(QFont("Inter", 9, QFont.Weight.Bold))
painter.setPen(QColor("#888888"))
painter.drawText(QRectF(20, 140, 140, 30), Qt.AlignmentFlag.AlignCenter, self.title)
# ==========================================
# INTERFACE PRINCIPALE
# ==========================================
class UltimateApp(QMainWindow):
def __init__(self):
super().__init__()
self.accent_color = "#1E88E5" # Bleu Zorin par défaut
self.init_ui()
self.init_tray()
self.thread = UltimateMonitorThread()
self.thread.stats_updated.connect(self.update_data)
self.thread.start()
def init_ui(self):
self.setWindowTitle("7Cooler Ultimate")
self.resize(850, 550)
self.setStyleSheet("QMainWindow { background-color: #0E0E0E; color: #FFFFFF; }")
main_widget = QWidget()
self.setCentralWidget(main_widget)
main_layout = QHBoxLayout(main_widget)
main_layout.setContentsMargins(0, 0, 0, 0)
main_layout.setSpacing(0)
# --- MENU LATÉRAL ---
sidebar = QFrame()
sidebar.setFixedWidth(200)
sidebar.setStyleSheet("background-color: #151515; border-right: 1px solid #222;")
side_layout = QVBoxLayout(sidebar)
side_layout.setContentsMargins(10, 30, 10, 30)
brand = QLabel("7COOLER")
brand.setFont(QFont("Inter", 16, QFont.Weight.Black))
brand.setAlignment(Qt.AlignmentFlag.AlignCenter)
brand.setStyleSheet("letter-spacing: 2px; margin-bottom: 30px;")
side_layout.addWidget(brand)
self.btn_dash = self.create_nav_btn("📊 Tableau de bord", 0)
self.btn_hard = self.create_nav_btn("🌡️ Matériel & Capteurs", 1)
self.btn_set = self.create_nav_btn("⚙️ Paramètres", 2)
side_layout.addWidget(self.btn_dash)
side_layout.addWidget(self.btn_hard)
side_layout.addWidget(self.btn_set)
side_layout.addStretch()
# --- CONTENU CENTRAL (PAGES) ---
self.stack = QStackedWidget()
self.stack.setStyleSheet("background-color: #0E0E0E;")
self.page_dash = self.build_dashboard()
self.page_hard = self.build_hardware()
self.page_set = self.build_settings()
self.stack.addWidget(self.page_dash)
self.stack.addWidget(self.page_hard)
self.stack.addWidget(self.page_set)
main_layout.addWidget(sidebar)
main_layout.addWidget(self.stack)
self.switch_page(0)
def create_nav_btn(self, text, index):
btn = QPushButton(text)
btn.setFont(QFont("Inter", 10, QFont.Weight.Bold))
btn.setCursor(Qt.CursorShape.PointingHandCursor)
btn.setStyleSheet("""
QPushButton { text-align: left; padding: 12px; border-radius: 8px; background: transparent; color: #888; border: none; }
QPushButton:hover { background: #222; color: #FFF; }
""")
btn.clicked.connect(lambda: self.switch_page(index))
return btn
def switch_page(self, index):
self.stack.setCurrentIndex(index)
for i, btn in enumerate([self.btn_dash, self.btn_hard, self.btn_set]):
if i == index:
btn.setStyleSheet(f"text-align: left; padding: 12px; border-radius: 8px; background: {self.accent_color}; color: #FFF; font-weight: bold; border: none;")
else:
btn.setStyleSheet("text-align: left; padding: 12px; border-radius: 8px; background: transparent; color: #888; border: none;")
# --- PAGE 1: DASHBOARD ---
def build_dashboard(self):
page = QWidget()
layout = QVBoxLayout(page)
layout.setContentsMargins(40, 40, 40, 40)
title = QLabel("Vue d'ensemble")
title.setFont(QFont("Inter", 22, QFont.Weight.Bold))
layout.addWidget(title)
# Jauges Hautes
gauge_layout = QHBoxLayout()
self.cpu_gauge = ModernGauge("CPU TEMP")
self.gpu_gauge = ModernGauge("GPU TEMP")
gauge_layout.addWidget(self.cpu_gauge)
gauge_layout.addWidget(self.gpu_gauge)
gauge_layout.addStretch()
layout.addLayout(gauge_layout)
layout.addSpacing(30)
# Barres d'utilisation
self.bars = {}
for name, label in [('cpu', 'Charge CPU'), ('ram', 'Mémoire Vive'), ('swap', 'Fichier d\'échange'), ('disk', 'Stockage Principal')]:
lbl = QLabel(f"{label}: 0%")
lbl.setFont(QFont("Inter", 10))
bar = SmoothProgressBar()
self.bars[name] = {'lbl': lbl, 'bar': bar}
layout.addWidget(lbl)
layout.addWidget(bar)
layout.addSpacing(10)
layout.addStretch()
return page
# --- PAGE 2: HARDWARE ---
def build_hardware(self):
page = QWidget()
layout = QVBoxLayout(page)
layout.setContentsMargins(40, 40, 40, 40)
title = QLabel("Capteurs Détaillés")
title.setFont(QFont("Inter", 22, QFont.Weight.Bold))
layout.addWidget(title)
scroll = QScrollArea()
scroll.setWidgetResizable(True)
scroll.setStyleSheet("QScrollArea { border: none; background: transparent; }")
content = QWidget()
self.grid = QGridLayout(content)
scroll.setWidget(content)
layout.addWidget(scroll)
# Réseau UI
self.net_lbl = QLabel("Réseau: DL 0 MB/s | UL 0 MB/s")
self.net_lbl.setFont(QFont("Inter", 12, QFont.Weight.Bold))
self.net_lbl.setStyleSheet("color: #888;")
layout.addWidget(self.net_lbl)
return page
# --- PAGE 3: SETTINGS ---
def build_settings(self):
page = QWidget()
layout = QVBoxLayout(page)
layout.setContentsMargins(40, 40, 40, 40)
title = QLabel("Paramètres de l'Application")
title.setFont(QFont("Inter", 22, QFont.Weight.Bold))
layout.addWidget(title)
# Thèmes
theme_lbl = QLabel("Couleur d'accentuation")
theme_lbl.setFont(QFont("Inter", 12, QFont.Weight.Bold))
layout.addWidget(theme_lbl)
colors_layout = QHBoxLayout()
for c in ["#1E88E5", "#E53935", "#43A047", "#8E24AA", "#FDD835", "#FFFFFF"]:
btn = QPushButton()
btn.setFixedSize(40, 40)
btn.setStyleSheet(f"background-color: {c}; border-radius: 20px; border: 2px solid #333;")
btn.setCursor(Qt.CursorShape.PointingHandCursor)
btn.clicked.connect(lambda checked, col=c: self.change_accent(col))
colors_layout.addWidget(btn)
colors_layout.addStretch()
layout.addLayout(colors_layout)
layout.addSpacing(30)
# Intervalle
speed_lbl = QLabel("Vitesse de rafraîchissement (ms)")
speed_lbl.setFont(QFont("Inter", 12, QFont.Weight.Bold))
layout.addWidget(speed_lbl)
self.slider = QSlider(Qt.Orientation.Horizontal)
self.slider.setRange(500, 3000)
self.slider.setValue(1000)
self.slider.setTickPosition(QSlider.TickPosition.TicksBelow)
self.slider.setTickInterval(500)
self.slider.valueChanged.connect(self.change_speed)
layout.addWidget(self.slider)
layout.addStretch()
return page
def change_accent(self, color):
self.accent_color = color
self.cpu_gauge.set_color(color)
self.gpu_gauge.set_color(color)
for b in self.bars.values(): b['bar'].set_color(color)
self.switch_page(self.stack.currentIndex()) # Refresh menu color
def change_speed(self, val):
self.thread.interval = val
def update_data(self, stats):
# Update Dashboard
self.cpu_gauge.set_value(stats['cpu_temp'])
if stats['gpu']:
self.gpu_gauge.set_value(stats['gpu']['temp'])
self.gpu_gauge.title = stats['gpu']['name'].upper()
else:
self.gpu_gauge.set_value(0)
self.gpu_gauge.title = "NO GPU"
self.bars['cpu']['lbl'].setText(f"Charge CPU: {stats['cpu_percent']}%")
self.bars['cpu']['bar'].set_smooth_value(int(stats['cpu_percent']))
self.bars['ram']['lbl'].setText(f"Mémoire Vive: {stats['ram']}%")
self.bars['ram']['bar'].set_smooth_value(int(stats['ram']))
self.bars['swap']['lbl'].setText(f"Fichier d'échange: {stats['swap']}%")
self.bars['swap']['bar'].set_smooth_value(int(stats['swap']))
self.bars['disk']['lbl'].setText(f"Stockage Principal: {stats['disk']}%")
self.bars['disk']['bar'].set_smooth_value(int(stats['disk']))
# Update Network
self.net_lbl.setText(f"Réseau: ↓ {stats['net_dl']:.2f} MB/s | ↑ {stats['net_ul']:.2f} MB/s")
# Update Hardware Grid
for i in reversed(range(self.grid.count())):
w = self.grid.itemAt(i).widget()
if w: w.setParent(None)
row, col = 0, 0
for name, temp in stats['cores']:
card = QFrame()
card.setStyleSheet("background-color: #1A1A1A; border-radius: 10px; padding: 10px;")
cl = QVBoxLayout(card)
n = QLabel(name)
n.setStyleSheet("color: #888; font-size: 11px;")
t = QLabel(f"{temp}°C")
t.setFont(QFont("Inter", 16, QFont.Weight.Bold))
if temp > 85: t.setStyleSheet("color: #E53935;")
cl.addWidget(n)
cl.addWidget(t)
self.grid.addWidget(card, row, col)
col += 1
if col > 3:
col, row = 0, row + 1
self.tray_icon.setToolTip(f"CPU: {stats['cpu_temp']}°C | RAM: {stats['ram']}%")
def init_tray(self):
self.tray_icon = QSystemTrayIcon(self)
self.tray_icon.setIcon(QIcon.fromTheme("utilities-system-monitor"))
menu = QMenu()
show_action = QAction("Ouvrir 7Cooler Ultimate", self)
show_action.triggered.connect(self.show)
quit_action = QAction("Quitter", self)
quit_action.triggered.connect(QApplication.instance().quit)
menu.addAction(show_action)
menu.addAction(quit_action)
self.tray_icon.setContextMenu(menu)
self.tray_icon.show()
def closeEvent(self, event):
event.ignore()
self.hide()
self.tray_icon.showMessage("7Cooler Ultimate", "Fonctionne en arrière-plan", QSystemTrayIcon.MessageIcon.Information, 2000)
if __name__ == "__main__":
app = QApplication(sys.argv)
QApplication.setQuitOnLastWindowClosed(False)
app.setFont(QFont("Inter", 10))
window = UltimateApp()
window.show()
sys.exit(app.exec())