# sayfalar/donanim.py
from PyQt6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QLabel,
QGroupBox, QProgressBar, QPushButton, QScrollArea,
QFileDialog, QMessageBox)
from PyQt6.QtCore import Qt, QTimer
from gorsel_araclar import SayfaBasligi, SvgIkonOlusturucu
import os
import subprocess
import shutil
import time
import socket
import getpass
import platform
import re
from datetime import datetime
class DonanimSayfasi(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
layout = QVBoxLayout(self)
layout.setContentsMargins(20, 20, 20, 20)
icon = SvgIkonOlusturucu.hardware_ikonu("#33AADD", 32)
layout.addWidget(SayfaBasligi("Donanım & Güç", icon))
# --- ÜST BUTONLAR (GÜNCELLENDİ) ---
# Butonların görünür olması için layout.addWidget ile doğrudan ekledik
btn_layout = QHBoxLayout()
btn_layout.setContentsMargins(0, 0, 0, 10) # Alt boşluk
self.btn_yenile = QPushButton("🔄 Yenile")
self.btn_yenile.setMinimumHeight(40)
self.btn_yenile.setStyleSheet("background-color: #33AADD; color: white; font-weight: bold; font-size: 10pt; padding: 5px 15px;")
self.btn_yenile.clicked.connect(self.manuel_yenile)
self.btn_rapor = QPushButton("📄 Sistem Raporu TXT Kaydet")
self.btn_rapor.setMinimumHeight(40)
self.btn_rapor.setStyleSheet("background-color: #2ecc71; color: white; font-weight: bold; font-size: 10pt; padding: 5px 15px;")
self.btn_rapor.clicked.connect(self.txt_kaydet)
btn_layout.addStretch() # Sola yasla
btn_layout.addWidget(self.btn_yenile)
btn_layout.addWidget(self.btn_rapor)
layout.addLayout(btn_layout)
# SCROLL ALANI
scroll = QScrollArea()
scroll.setWidgetResizable(True)
scroll.setStyleSheet("background: transparent; border: none;")
self.icerik_widget = QWidget()
self.icerik_layout = QVBoxLayout(self.icerik_widget)
self.icerik_layout.setSpacing(20)
# 1. GÜÇ
self.grp_guc = QGroupBox("Güç & Batarya")
l_guc = QVBoxLayout(self.grp_guc)
self.lbl_durum = QLabel("Durum: Yükleniyor...")
self.lbl_kalan = QLabel("Kalan: -")
self.lbl_sure = QLabel("Tahmini Süre: -")
l_guc.addWidget(self.lbl_durum); l_guc.addWidget(self.lbl_kalan); l_guc.addWidget(self.lbl_sure)
self.icerik_layout.addWidget(self.grp_guc)
# 2. SİSTEM
self.grp_sis = QGroupBox("Sistem & Çekirdek")
l_sis = QVBoxLayout(self.grp_sis)
self.lbl_model = QLabel("Model: -")
self.lbl_gpu = QLabel("GPU: -")
self.lbl_cpu = QLabel("CPU: -")
self.lbl_ram = QLabel("RAM: -")
self.lbl_distro = QLabel("Dağıtım: -")
self.lbl_kernel = QLabel("Kernel: -")
l_sis.addWidget(self.lbl_model); l_sis.addWidget(self.lbl_gpu); l_sis.addWidget(self.lbl_cpu); l_sis.addWidget(self.lbl_ram); l_sis.addWidget(self.lbl_distro); l_sis.addWidget(self.lbl_kernel)
self.icerik_layout.addWidget(self.grp_sis)
# 3. ÇEVRE BİRİMLERİ (DETAYLI)
self.grp_detay = QGroupBox("Bağlı Donanımlar & Çevre Birimleri")
self.layout_detay = QVBoxLayout(self.grp_detay)
self.icerik_layout.addWidget(self.grp_detay)
# 4. FİZİKSEL DİSKLER
self.grp_fiziksel = QGroupBox("Fiziksel Diskler (Donanım)")
self.layout_fiziksel = QVBoxLayout(self.grp_fiziksel)
self.icerik_layout.addWidget(self.grp_fiziksel)
# 5. MANTIKSAL BÖLÜMLER
self.grp_disk = QGroupBox("Mantıksal Bölümler (Mounts)")
self.layout_disk = QVBoxLayout(self.grp_disk)
self.layout_disk.setSpacing(10)
self.icerik_layout.addWidget(self.grp_disk)
self.icerik_layout.addStretch()
scroll.setWidget(self.icerik_widget)
layout.addWidget(scroll)
self.son_veri = {}
self.tespit_edilen_donanimlar = {}
self.fiziksel_diskler_listesi = []
self.mantiksal_bolumler_listesi = []
self.pc_modeli = "Bilinmiyor"
# Başlangıçta tarama yap
QTimer.singleShot(1000, self.donanim_tara)
def manuel_yenile(self):
self.donanim_tara()
def donanim_tara(self):
# Model Bilgisi
try:
with open("/sys/devices/virtual/dmi/id/product_name", "r") as f: self.pc_modeli = f.read().strip()
except: self.pc_modeli = "Bilinmiyor"
self.lbl_model.setText(f"<b>Model:</b> {self.pc_modeli}")
# Detay Alanını Temizle
while self.layout_detay.count():
item = self.layout_detay.takeAt(0)
if item.widget(): item.widget().deleteLater()
self.tespit_edilen_donanimlar = { "Wi-Fi Kartı 📶": "Yok", "Ethernet (LAN) 🌐": "Yok", "Ses Kartı 🔊": "Yok", "Bluetooth 🦷": "Yok", "Yazıcı 🖨️": "Yok" }
# Ağ Arayüzlerini Bul
wifi_ifaces = []; eth_ifaces = []
try:
for iface in os.listdir('/sys/class/net'):
if iface == 'lo': continue
if os.path.exists(f"/sys/class/net/{iface}/wireless") or iface.startswith('wl'): wifi_ifaces.append(iface)
elif iface.startswith('en') or iface.startswith('eth'): eth_ifaces.append(iface)
except: pass
# Donanım Taraması (lspci)
try:
out = subprocess.check_output("lspci -mm", shell=True, text=True)
for line in out.splitlines():
parts = line.split('"')
if len(parts) >= 6:
class_name, vendor, device = parts[1], parts[3], parts[5]
full = f"{vendor} - {device}"
if "Network" in class_name:
if wifi_ifaces: full += f" (Arayüz: {', '.join(wifi_ifaces)})"
self.tespit_edilen_donanimlar["Wi-Fi Kartı 📶"] = full
elif "Ethernet" in class_name:
if eth_ifaces: full += f" (Arayüz: {', '.join(eth_ifaces)})"
self.tespit_edilen_donanimlar["Ethernet (LAN) 🌐"] = full
elif "Audio" in class_name:
if self.tespit_edilen_donanimlar["Ses Kartı 🔊"] == "Yok": self.tespit_edilen_donanimlar["Ses Kartı 🔊"] = full
else: self.tespit_edilen_donanimlar["Ses Kartı 🔊"] += f"\n{full}"
except: pass
# Bluetooth Kontrolü
if shutil.which("hciconfig"):
try:
out = subprocess.check_output("hciconfig -a", shell=True, text=True)
if "UP RUNNING" in out: self.tespit_edilen_donanimlar["Bluetooth 🦷"] = "Aktif"
elif "DOWN" in out: self.tespit_edilen_donanimlar["Bluetooth 🦷"] = "Kapalı (Donanım Var)"
except: pass
else:
try:
lsusb_out = subprocess.check_output("lsusb", shell=True, text=True)
if "Bluetooth" in lsusb_out: self.tespit_edilen_donanimlar["Bluetooth 🦷"] = "USB Adaptör"
except: pass
# Yazıcı Kontrolü
if shutil.which("lpstat"):
try:
out = subprocess.check_output(["lpstat", "-p"], text=True, stderr=subprocess.DEVNULL)
if out.strip():
printers = [l.split()[1] for l in out.splitlines() if "printer" in l]
self.tespit_edilen_donanimlar["Yazıcı 🖨️"] = ", ".join(printers)
else: self.tespit_edilen_donanimlar["Yazıcı 🖨️"] = "Sistemde kurulu yazıcı yok"
except: pass
else: self.tespit_edilen_donanimlar["Yazıcı 🖨️"] = "CUPS Servisi Yok"
# Arayüze Ekle
for k, v in self.tespit_edilen_donanimlar.items(): self.donanim_bilgisi_satiri(k, v)
# Fiziksel Diskleri Temizle ve Yenile
while self.layout_fiziksel.count():
item = self.layout_fiziksel.takeAt(0)
if item.widget(): item.widget().deleteLater()
self.fiziksel_diskler_listesi = []
try:
out = subprocess.check_output("lsblk -d -n -o NAME,MODEL,SIZE,TYPE,TRAN -e 7,11", shell=True, text=True)
for line in out.splitlines():
cols = line.split()
if len(cols) >= 3:
name = cols[0]
size = cols[-3] if len(cols) >= 3 else "?"
# Model bazen boşluklu olabilir
if len(cols) > 4:
model = " ".join(cols[1:-3])
tran = cols[-1].upper()
else:
model = cols[1]
tran = "?"
self.fiziksel_diskler_listesi.append(f"{name}: {model} ({size}) [{tran}]")
w = QWidget(); hl = QHBoxLayout(w); hl.setContentsMargins(0,0,0,0)
icon = QLabel("💽"); icon.setFixedWidth(30)
lbl_info = QLabel(f"{model}")
lbl_info.setStyleSheet("font-weight:bold;") # Renk yok, temadan alacak
lbl_det = QLabel(f"{size} • {tran}")
lbl_det.setStyleSheet("color: #33AADD;")
hl.addWidget(icon); hl.addWidget(lbl_info); hl.addStretch(); hl.addWidget(lbl_det)
self.layout_fiziksel.addWidget(w)
except Exception as e: pass
self.diskleri_yenile_df()
def diskleri_yenile_df(self):
while self.layout_disk.count():
item = self.layout_disk.takeAt(0)
if item.widget(): item.widget().deleteLater()
self.mantiksal_bolumler_listesi = []
try:
cmd = ["df", "-hT", "--exclude-type=tmpfs", "--exclude-type=devtmpfs", "--exclude-type=squashfs"]
out = subprocess.check_output(cmd, text=True)
lines = out.splitlines()[1:]
for line in lines:
cols = line.split()
if len(cols) >= 7:
fs_dev = cols[0]; fstype = cols[1]; size = cols[2]; used = cols[3]; avail = cols[4]; use_pct = cols[5]; mount = cols[6]
rapor_satiri = f"{fs_dev} ({fstype.upper()}) - {mount} - {used}/{size}"
self.mantiksal_bolumler_listesi.append(rapor_satiri)
row = QWidget(); h = QHBoxLayout(row); h.setContentsMargins(0, 2, 0, 2)
etiket = f"📂 {mount} <span style='color:#7f8c8d'>({fs_dev})</span> <span style='color:#e67e22'>[{fstype.upper()}]</span>"
lbl = QLabel(etiket); lbl.setFixedWidth(320)
lbl.setStyleSheet("font-weight:bold; font-size:9pt;")
lbl.setTextFormat(Qt.TextFormat.RichText)
try: pct_val = int(use_pct.replace('%', ''))
except: pct_val = 0
bar = QProgressBar(); bar.setValue(pct_val); bar.setFormat(f"%p% | Dolu: {used} / Top: {size}"); bar.setTextVisible(True); bar.setAlignment(Qt.AlignmentFlag.AlignCenter); bar.setFixedHeight(18)
h.addWidget(lbl); h.addWidget(bar); self.layout_disk.addWidget(row)
except Exception as e: lbl = QLabel(f"Disk bilgisi alınamadı: {e}"); self.layout_disk.addWidget(lbl)
def donanim_bilgisi_satiri(self, baslik, deger):
w = QWidget(); l = QHBoxLayout(w); l.setContentsMargins(0, 0, 0, 0)
lbl_b = QLabel(f"<b>{baslik}:</b>"); lbl_b.setFixedWidth(140); lbl_b.setStyleSheet("color: #7f8c8d;")
lbl_v = QLabel(deger); lbl_v.setWordWrap(True)
if "Yok" in deger or "yüklü değil" in deger: lbl_v.setStyleSheet("color: #95a5a6; font-style: italic;")
else: lbl_v.setStyleSheet("font-size: 10pt;") # Renk belirtmedik
l.addWidget(lbl_b); l.addWidget(lbl_v); self.layout_detay.addWidget(w)
def format_sure_str(self, raw_str):
try:
if not raw_str: return "Bilinmiyor"
s = raw_str.replace("Startup finished in", "Toplam:")
s = s.replace("kernel", "Çekirdek")
s = s.replace("userspace", "Kullanıcı A.")
s = s.replace("firmware", "Donanım")
s = s.replace("loader", "Önyükleyici")
s = re.sub(r'(\d+\.?\d*)s', r'\1 sn', s)
s = s.replace("min", " dk")
return s
except: return raw_str
def txt_kaydet(self):
if not self.son_veri: QMessageBox.warning(self, "Uyarı", "Veriler henüz yüklenmedi, lütfen bekleyin."); return
tarih_dosya_adi = datetime.now().strftime('%d_%m_%Y_%H_%M_%S')
varsayilan_isim = f"sistem_asistan_{tarih_dosya_adi}.txt"
path, _ = QFileDialog.getSaveFileName(self, "Sistem Raporu TXT Kaydet", varsayilan_isim, "Text Files (*.txt)")
if path:
try:
hostname = socket.gethostname(); yerel_ip = socket.gethostbyname(hostname); kullanici = getpass.getuser()
startups = []
p_auto = os.path.expanduser("~/.config/autostart")
if os.path.exists(p_auto):
for f in os.listdir(p_auto):
if f.endswith(".desktop"): startups.append(f)
services = []
try:
s_out = subprocess.check_output("systemctl list-units --type=service --state=running --no-pager --no-legend", shell=True, text=True)
for l in s_out.splitlines(): services.append(l.split()[0])
except: pass
boot_raw = "Bilinmiyor"
try: boot_raw = subprocess.check_output(["systemd-analyze", "time"], text=True).strip()
except: pass
boot_fmt = self.format_sure_str(boot_raw)
dns_str = "Bilinmiyor"
try:
dnsler = []
with open("/etc/resolv.conf", "r") as f:
for line in f:
if line.startswith("nameserver"): dnsler.append(line.split()[1])
if dnsler: dns_str = ", ".join(dnsler)
except: pass
# UFW Durumu
ufw_durum = "Bilinmiyor"
try:
p = subprocess.run(["pkexec", "ufw", "status"], capture_output=True, text=True)
out = p.stdout.lower()
if "status: active" in out: ufw_durum = "Aktif (Açık)"
elif "status: inactive" in out: ufw_durum = "Pasif (Kapalı)"
else: ufw_durum = "Durum Belirsiz"
except: ufw_durum = "Erişim Hatası"
# Rapor Yazma
lines = [f"--- SİSTEM ASİSTANI - DETAYLI RAPOR ---", f"Tarih: {datetime.now().strftime('%d/%m/%Y %H:%M')}", "-" * 50]
lines.append(f"\n[SİSTEM KİMLİĞİ]")
lines.append(f"Kullanıcı: {kullanici} @ {hostname}")
lines.append(f"Model: {self.pc_modeli}")
lines.append(f"Dağıtım: {self.son_veri.get('dagitim_detay', 'Bilinmiyor')}")
lines.append(f"Kernel: {platform.release()}")
lines.append(f"Son Açılış Süresi: {boot_fmt}")
lines.append(f"\n[DONANIM ÖZETİ]")
lines.append(f"CPU Modeli: {self.son_veri.get('islemci_model', 'Bilinmiyor')}")
lines.append(f"CPU Kullanımı: %{self.son_veri.get('toplam_cpu_yuzde', 0):.1f}")
lines.append(f"CPU Sıcaklığı: {self.son_veri.get('cpu_sicaklik', 0):.1f}°C")
lines.append(f"Çalışma Süresi (Uptime): {self.son_veri.get('uptime', 'Bilinmiyor')}")
lines.append(f"GPU Modeli: {self.son_veri.get('ekran_karti_model', 'Bilinmiyor')}")
lines.append(f"RAM: {self.son_veri.get('ram_toplam', '0 GB')} (Kullanım: %{self.son_veri.get('ram_yuzde', 0)})")
lines.append(f"\n[AĞ VE GÜVENLİK]")
lines.append(f"Yerel IP: {yerel_ip}")
lines.append(f"Ağ Adı (SSID): {self.son_veri.get('ag_ssid', 'Bilinmiyor')}")
lines.append(f"Aktif Arayüz: {self.son_veri.get('ag_arayuz', 'Bilinmiyor')}")
lines.append(f"Servis Sağlayıcı: {self.son_veri.get('konum_bilgisi', {}).get('org', 'Bilinmiyor')}")
lines.append(f"DNS Sunucuları: {dns_str}")
lines.append(f"Güvenlik Duvarı (UFW): {ufw_durum}")
lines.append(f"\n[GÜÇ & BATARYA]")
bat = self.son_veri.get('batarya', {})
if bat.get("status_yok"): lines.append("Durum: AC / Masaüstü")
else: lines.append(f"Durum: {'Şarjda' if bat.get('plugged') else 'Pilde'} (%{bat.get('percent', 0)})")
lines.append(f"\n[BAĞLI DONANIMLAR]")
for k,v in self.tespit_edilen_donanimlar.items(): lines.append(f"{k.replace('📶','').replace('🌐','').replace('🔊','').replace('🦷','').replace('🖨️','').strip()}: {v.replace(chr(10), ', ')}")
lines.append(f"\n[DEPOLAMA - FİZİKSEL]")
for d in self.fiziksel_diskler_listesi: lines.append(d)
lines.append(f"\n[DEPOLAMA - MANTIKSAL]")
for sat in self.mantiksal_bolumler_listesi: lines.append(sat)
lines.append(f"\n[YAZILIM & BAŞLANGIÇ]")
lines.append(f"Başlangıçtaki Uygulamalar ({len(startups)}):")
if startups:
for app in startups: lines.append(f" - {app}")
else: lines.append(f" - Yok")
lines.append(f"")
lines.append(f"Çalışan Servisler ({len(services)}):")
if services:
for srv in services: lines.append(f" - {srv}")
else: lines.append(f" - Servis bilgisi alınamadı")
with open(path, "w", encoding="utf-8") as f: f.write("\n".join(lines))
QMessageBox.information(self, "Başarılı", "Kapsamlı sistem raporu başarıyla kaydedildi.")
except Exception as e: QMessageBox.warning(self, "Hata", f"Rapor kaydedilemedi: {e}")
def guncelle(self, veri=None):
if not veri: return
self.son_veri = veri
bat = veri.get('batarya', {})
if bat.get("status_yok"): self.lbl_durum.setText("Masaüstü (Fişte)"); self.lbl_kalan.setText("-"); self.lbl_sure.setText("-")
else: self.lbl_durum.setText(f"%{bat.get('percent')}"); self.lbl_kalan.setText(f"{'Şarjda' if bat.get('plugged') else 'Pilde'}")
self.lbl_gpu.setText(f"<b>GPU:</b> {veri.get('ekran_karti_model', '-')}")
self.lbl_cpu.setText(f"<b>CPU:</b> {veri.get('islemci_model', '-')}")
self.lbl_ram.setText(f"<b>RAM:</b> {veri.get('ram_toplam', '-')}")
self.lbl_distro.setText(f"<b>Dağıtım:</b> {veri.get('dagitim_detay', '-')}")
self.lbl_kernel.setText(f"<b>Kernel:</b> {platform.release()}")