diff --git a/7cooler-ultimate_1.0_amd64.deb b/7cooler-ultimate_1.0_amd64.deb new file mode 100644 index 0000000..cd1f02b Binary files /dev/null and b/7cooler-ultimate_1.0_amd64.deb differ diff --git a/7cooler_ultimate.py b/7cooler_ultimate.py new file mode 100644 index 0000000..a978752 --- /dev/null +++ b/7cooler_ultimate.py @@ -0,0 +1,422 @@ +#!/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())