diff options
Diffstat (limited to 'raveos-center/raveos_center')
33 files changed, 6465 insertions, 0 deletions
diff --git a/raveos-center/raveos_center/__init__.py b/raveos-center/raveos_center/__init__.py new file mode 100644 index 0000000..c86676b --- /dev/null +++ b/raveos-center/raveos_center/__init__.py @@ -0,0 +1 @@ +VERZIO = "0.1.0" diff --git a/raveos-center/raveos_center/__main__.py b/raveos-center/raveos_center/__main__.py new file mode 100644 index 0000000..a33359a --- /dev/null +++ b/raveos-center/raveos_center/__main__.py @@ -0,0 +1,5 @@ +import sys + +from raveos_center.alkalmazas import futtat + +sys.exit(futtat(sys.argv)) diff --git a/raveos-center/raveos_center/adat/logo.png b/raveos-center/raveos_center/adat/logo.png Binary files differnew file mode 100644 index 0000000..aa412b5 --- /dev/null +++ b/raveos-center/raveos_center/adat/logo.png diff --git a/raveos-center/raveos_center/alkalmazas.py b/raveos-center/raveos_center/alkalmazas.py new file mode 100644 index 0000000..2c68777 --- /dev/null +++ b/raveos-center/raveos_center/alkalmazas.py @@ -0,0 +1,192 @@ +import importlib +from pathlib import Path + +from PyQt6.QtCore import QSize, Qt +from PyQt6.QtGui import QColor, QIcon, QPalette, QPixmap +from PyQt6.QtWidgets import ( + QApplication, + QFrame, + QHBoxLayout, + QLabel, + QMainWindow, + QPushButton, + QStackedWidget, + QVBoxLayout, + QWidget, +) + +from raveos_center import VERZIO, hatter, oldalak, rendszerinfo, stilus +from raveos_center.elemek import cimke, frissit_stilus +from raveos_center.ikonok import ikon + +ADAT = Path(__file__).parent / "adat" + + +class Menupont(QPushButton): + def __init__(self, modul): + super().__init__(modul.MENU) + self.modul = modul + self.setObjectName("menupont") + self.setCursor(Qt.CursorShape.PointingHandCursor) + self.setIconSize(QSize(18, 18)) + sor = QHBoxLayout(self) + sor.setContentsMargins(0, 0, 12, 0) + sor.addStretch(1) + self.jelveny = cimke("", "jelveny") + self.jelveny.setAttribute(Qt.WidgetAttribute.WA_TransparentForMouseEvents) + self.jelveny.setFixedHeight(18) + self.jelveny.setAlignment(Qt.AlignmentFlag.AlignCenter) + self.jelveny.hide() + sor.addWidget(self.jelveny, 0, Qt.AlignmentFlag.AlignVCenter) + self.beallit(False) + + def beallit(self, aktiv): + self.setProperty("aktiv", aktiv) + self.setIcon(ikon(self.modul.IKON, stilus.ZOLD if aktiv else stilus.SZOVEG_MASOD)) + frissit_stilus(self) + + def szam(self, ertek): + self.jelveny.setText(str(ertek)) + self.jelveny.setVisible(bool(ertek)) + + +class Kozpont: + def __init__(self, ablak): + self.ablak = ablak + + def van(self, azon, *args): + oldal = self.ablak.oldalak.get(azon) + return bool(oldal) and oldal.elerheto(*args) + + def mutat(self, azon, *args): + return self.ablak.mutat(azon, *args) + + def jelveny(self, azon, szam): + pont = self.ablak.menupontok.get(azon) + if pont: + pont.szam(szam) + + +class FoAblak(QMainWindow): + def __init__(self): + super().__init__() + self.setWindowTitle("RaveOS Center") + self.setWindowIcon(QIcon(str(ADAT / "logo.png"))) + self.resize(1280, 820) + self.setMinimumSize(760, 520) + self.kozpont = Kozpont(self) + self.oldalak = {} + self.menupontok = {} + + alap = QWidget() + self.setCentralWidget(alap) + sor = QHBoxLayout(alap) + sor.setContentsMargins(0, 0, 0, 0) + sor.setSpacing(0) + + self.oldalsav = QFrame() + self.oldalsav.setObjectName("oldalsav") + self.oldalsav.setFixedWidth(232) + sav = QVBoxLayout(self.oldalsav) + sav.setContentsMargins(16, 28, 16, 20) + sav.setSpacing(4) + sav.addLayout(self._marka()) + sav.addSpacing(28) + + self.verem = QStackedWidget() + sor.addWidget(self.oldalsav) + sor.addWidget(self.verem, 1) + + for nev in oldalak.FENT: + self._betolt(nev, sav) + sav.addStretch(1) + vonal = QFrame() + vonal.setObjectName("elvalaszto") + sav.addWidget(vonal) + sav.addSpacing(12) + for nev in oldalak.LENT: + self._betolt(nev, sav) + sav.addWidget(cimke(f"{rendszerinfo.raveos_verzio()} · rp1.hu", "lablec")) + + for oldal in self.oldalak.values(): + oldal.indulaskor() + if oldalak.FENT: + self.mutat(oldalak.FENT[0]) + + def _marka(self): + sor = QHBoxLayout() + sor.setContentsMargins(8, 0, 8, 0) + sor.setSpacing(12) + logo = QLabel() + kep = QPixmap(str(ADAT / "logo.png")) + kep.setDevicePixelRatio(2) + logo.setPixmap( + kep.scaled(84, 78, Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation) + ) + sor.addWidget(logo) + nevek = QVBoxLayout() + nevek.setSpacing(0) + nevek.addWidget(cimke("RAVEOS", "markanev")) + nevek.addWidget(cimke("CENTER", "markaalcim")) + sor.addLayout(nevek) + sor.addStretch(1) + return sor + + def _betolt(self, nev, sav): + modul = importlib.import_module(f"raveos_center.oldalak.{nev}") + oldal = modul.OLDAL(self.kozpont) + self.oldalak[modul.AZON] = oldal + self.verem.addWidget(oldal) + pont = Menupont(modul) + pont.clicked.connect(lambda _=False, a=modul.AZON: self.mutat(a)) + self.menupontok[modul.AZON] = pont + sav.addWidget(pont) + + def mutat(self, azon, *args): + oldal = self.oldalak.get(azon) + if not oldal: + return False + for a, pont in self.menupontok.items(): + pont.beallit(a == azon) + self.verem.setCurrentWidget(oldal) + oldal.megjelenik(*args) + return True + + +def paletta(): + p = QPalette() + szinek = { + QPalette.ColorRole.Window: stilus.HATTER, + QPalette.ColorRole.WindowText: stilus.SZOVEG, + QPalette.ColorRole.Base: stilus.HATTER, + QPalette.ColorRole.AlternateBase: stilus.FELULET, + QPalette.ColorRole.Text: stilus.SZOVEG, + QPalette.ColorRole.Button: stilus.FELULET, + QPalette.ColorRole.ButtonText: stilus.SZOVEG, + QPalette.ColorRole.Highlight: stilus.ZOLD, + QPalette.ColorRole.HighlightedText: stilus.ZOLDON_SZOVEG, + QPalette.ColorRole.ToolTipBase: stilus.FELULET, + QPalette.ColorRole.ToolTipText: stilus.SZOVEG, + QPalette.ColorRole.PlaceholderText: stilus.SZOVEG_HALVANY, + QPalette.ColorRole.Link: stilus.ZOLD, + QPalette.ColorRole.Mid: stilus.KERET, + QPalette.ColorRole.Dark: stilus.KERET_EROS, + } + for szerep, szin in szinek.items(): + p.setColor(szerep, QColor(szin)) + for szerep in (QPalette.ColorRole.WindowText, QPalette.ColorRole.Text, QPalette.ColorRole.ButtonText): + p.setColor(QPalette.ColorGroup.Disabled, szerep, QColor(stilus.SZOVEG_HALVANY)) + return p + + +def futtat(argv): + app = QApplication(argv) + app.setApplicationName("RaveOS Center") + app.setDesktopFileName("raveos-center") + app.setStyle("Fusion") + app.setPalette(paletta()) + app.setStyleSheet(stilus.QSS) + app.aboutToQuit.connect(hatter.leallitas) + ablak = FoAblak() + ablak.show() + return app.exec() diff --git a/raveos-center/raveos_center/beallitasok.py b/raveos-center/raveos_center/beallitasok.py new file mode 100644 index 0000000..e041706 --- /dev/null +++ b/raveos-center/raveos_center/beallitasok.py @@ -0,0 +1,30 @@ +import json +import os +import threading +from pathlib import Path + +KONFIG = Path(os.environ.get("XDG_CONFIG_HOME", Path.home() / ".config")) / "raveos-center" +FAJL = KONFIG / "beallitasok.json" +_zar = threading.Lock() + + +def _olvas(): + try: + return json.loads(FAJL.read_text()) + except (OSError, ValueError): + return {} + + +def ertek(kulcs, alap=None): + with _zar: + return _olvas().get(kulcs, alap) + + +def beallit(kulcs, ertek_): + with _zar: + adat = _olvas() + adat[kulcs] = ertek_ + KONFIG.mkdir(parents=True, exist_ok=True) + ideiglenes = FAJL.with_suffix(".uj") + ideiglenes.write_text(json.dumps(adat, ensure_ascii=False, indent=2)) + os.replace(ideiglenes, FAJL) diff --git a/raveos-center/raveos_center/diagnosztika.py b/raveos-center/raveos_center/diagnosztika.py new file mode 100644 index 0000000..445c4d4 --- /dev/null +++ b/raveos-center/raveos_center/diagnosztika.py @@ -0,0 +1,85 @@ +import getpass +import platform +import re +import socket +import subprocess +from datetime import datetime + +from raveos_center import rendszerinfo + +TARTALOM = [ + "rendszer: RaveOS verzió, kernel, asztali környezet", + "hardver: processzor, memória, videokártya, hálózati és hangeszközök, lemezek", + "illesztőprogramok: betöltött GPU modulok", + "csomagok: RaveOS csomagok és a telepített csomagok száma", + "hibák: hibás szolgáltatások és a rendszernapló hibái az utolsó indítás óta", + "lemezhasználat és fájlrendszer", +] + +KITAKARAS = "IP- és MAC-címek, lemez UUID-k, egyedi azonosítók, a gép neve és a felhasználóneved" + + +def _ki(parancs, ido=30): + try: + k = subprocess.run(parancs, capture_output=True, text=True, timeout=ido) + except (OSError, subprocess.TimeoutExpired): + return "(nem elérhető)" + return (k.stdout or k.stderr or "").rstrip() or "(üres)" + + +def _szakasz(cim, tartalom): + return f"== {cim} ==\n{tartalom}\n" + + +def kitakar(szoveg): + szoveg = re.sub(r"\b(\d{1,3}\.){3}\d{1,3}\b", "[ip]", szoveg) + szoveg = re.sub(r"\b([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}\b", "[mac]", szoveg) + szoveg = re.sub(r"\b([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}\b", "[ip6]", szoveg) + szoveg = re.sub(r"\b([0-9a-fA-F]{1,4}:){1,6}:([0-9a-fA-F]{1,4}(:[0-9a-fA-F]{1,4})*)?", "[ip6]", szoveg) + szoveg = re.sub(r"[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}", "[uuid]", szoveg) + szoveg = re.sub(r"\b[0-9a-fA-F]{32}\b", "[id]", szoveg) + szoveg = re.sub(r"(?i)\b(pass(word)?|passwd|token|secret|api[_-]?key|auth)(\s*[=:]\s*)\S+", r"\1\3[titok]", szoveg) + gep = socket.gethostname() + if gep and len(gep) > 1: + szoveg = re.sub(rf"\b{re.escape(gep)}\b", "[gep]", szoveg) + felhasznalo = getpass.getuser() + if felhasznalo and felhasznalo != "root" and len(felhasznalo) > 1: + szoveg = re.sub(rf"\b{re.escape(felhasznalo)}\b", "[user]", szoveg) + return szoveg + + +def jelentes(): + reszek = [ + f"RaveOS Center diagnosztika\n{datetime.now():%Y-%m-%d %H:%M}\n", + _szakasz( + "rendszer", + f"verzio: {rendszerinfo.raveos_verzio()}\nkernel: {platform.release()}\n" + f"asztal: {rendszerinfo.asztal()}\nuzemido: {rendszerinfo.uzemido()}", + ), + _szakasz( + "hardver", + f"processzor: {rendszerinfo.cpu()}\nmemoria: {rendszerinfo.memoria()}\n" + + _ki(["sh", "-c", "lspci -nn | grep -iE 'vga|3d|display|ethernet|network|wireless|audio'"]) + + "\n" + + _ki(["lsblk", "-d", "-n", "-e", "7,11", "-o", "NAME,SIZE,MODEL,TRAN,ROTA"]), + ), + _szakasz("illesztoprogramok", ", ".join(rendszerinfo.gpu_driverek()) or "(nincs)"), + _szakasz( + "csomagok", + _ki(["sh", "-c", "pacman -Q | grep -E '^(raveos|calamares|linux|mesa|nvidia)'"]) + + f"\nosszes: {_ki(['sh', '-c', 'pacman -Qq | wc -l'])}", + ), + _szakasz("hibas szolgaltatasok", _ki(["systemctl", "--failed", "--no-legend", "--no-pager"])), + _szakasz("rendszernaplo hibai", _ki(["journalctl", "-b", "-p", "err", "-n", "150", "--no-pager"])), + _szakasz("lemez", _ki(["df", "-h", "/", "/home"]) + f"\nfajlrendszer: {rendszerinfo.fajlrendszer()}"), + ] + return kitakar("\n".join(reszek)) + + +def rendszer_osszegzes(): + return { + "RaveOS": rendszerinfo.raveos_verzio(), + "Kernel": platform.release(), + "Asztal": rendszerinfo.asztal(), + "GPU": ", ".join(rendszerinfo.gpuk()) or "ismeretlen", + } diff --git a/raveos-center/raveos_center/driverek.py b/raveos-center/raveos_center/driverek.py new file mode 100644 index 0000000..6ef7842 --- /dev/null +++ b/raveos-center/raveos_center/driverek.py @@ -0,0 +1,179 @@ +import re +import subprocess +from pathlib import Path + +SZKRIPT_HELYEK = [ + Path("/usr/lib/raveos-center/raveos-gpu-drivers"), + Path(__file__).resolve().parent.parent / "driverek" / "raveos-gpu-drivers", +] + +KATEGORIAK = [ + ("gpu", "Videokártya"), + ("wifi", "Wi-Fi"), + ("halozat", "Vezetékes hálózat"), + ("hang", "Hang"), + ("bemenet", "Kontrollerek"), +] + +OSZTALYOK = { + "0300": "gpu", + "0302": "gpu", + "0380": "gpu", + "0280": "wifi", + "0200": "halozat", + "0401": "hang", + "0403": "hang", +} + +KONTROLLEREK = { + "045e": "Microsoft (Xbox)", + "054c": "Sony (PlayStation)", + "057e": "Nintendo", + "28de": "Valve (Steam)", + "2dc8": "8BitDo", + "0e6f": "PDP", + "24c6": "PowerA", + "046d": "Logitech", +} + +XBOX_ADAPTEREK = {"02e6", "02fe", "091e"} + + +def _ki(parancs, ido=30): + try: + return subprocess.run(parancs, capture_output=True, text=True, timeout=ido).stdout + except (OSError, subprocess.TimeoutExpired): + return "" + + +def szkript(): + for hely in SZKRIPT_HELYEK: + if hely.is_file(): + return hely + return None + + +def _telepitett(): + return set(_ki(["pacman", "-Qq"]).split()) + + +def _gpu_terv(): + hely = szkript() + if not hely: + return {} + terv = {} + for sor in _ki(["bash", str(hely), "--terv"]).splitlines(): + if "=" in sor: + k, _, v = sor.partition("=") + terv[k] = v + return terv + + +def _azonosito(szoveg): + talalat = re.search(r"\[([0-9a-f]{4})\]$", szoveg.strip()) + return talalat.group(1) if talalat else "" + + +def _nev(szoveg): + return re.sub(r"\s*\[[0-9a-f]{4}\]$", "", szoveg.strip()) + + +def _pci_eszkozok(): + blokkok = _ki(["lspci", "-vmmnnk"]).strip().split("\n\n") + eszkozok = [] + for blokk in blokkok: + mezok = {} + for sor in blokk.splitlines(): + if ":\t" in sor: + k, _, v = sor.partition(":\t") + mezok.setdefault(k, v) + osztaly = _azonosito(mezok.get("Class", "")) + kategoria = OSZTALYOK.get(osztaly) + if not kategoria: + continue + gyarto = _nev(mezok.get("Vendor", "")) + eszkoz = _nev(mezok.get("Device", "")) + rovid = re.search(r"\[([^\]]+)\]", eszkoz) + eszkozok.append( + { + "kategoria": kategoria, + "nev": f"{gyarto.replace(' Corporation', '').replace(', Inc.', '')} {rovid.group(1) if rovid else eszkoz}".strip(), + "gyarto_id": _azonosito(mezok.get("Vendor", "")), + "jelenlegi": mezok.get("Driver", ""), + } + ) + return eszkozok + + +def _usb_kontrollerek(): + lista = [] + for sor in _ki(["lsusb"]).splitlines(): + talalat = re.search(r"ID ([0-9a-f]{4}):([0-9a-f]{4})\s*(.*)", sor) + if not talalat: + continue + gyarto, termek, nev = talalat.groups() + if gyarto not in KONTROLLEREK: + continue + if not re.search(r"(?i)controller|gamepad|joystick|wireless adapter|xbox|dualsense|dualshock|pro controller|8bitdo", nev): + continue + lista.append({"gyarto": gyarto, "termek": termek, "nev": nev or KONTROLLEREK[gyarto]}) + return lista + + +def allapot(): + telepitett = _telepitett() + terv = _gpu_terv() + kozos = terv.get("kozos", "").split() + gyartoi = { + "8086": terv.get("intel_csomagok", "").split(), + "1002": terv.get("amd_csomagok", "").split(), + "10de": terv.get("nvidia_csomagok", "").split(), + } + lista = [] + for e in _pci_eszkozok(): + elem = dict(e, ajanlott=[], megjegyzes="", gpu=False) + if e["kategoria"] == "gpu": + elem["gpu"] = True + elem["ajanlott"] = kozos + gyartoi.get(e["gyarto_id"], []) + if terv.get("virtualis") == "1" and e["gyarto_id"] in ("1af4", "1234", "15ad", "1b36", "80ee"): + elem["megjegyzes"] = "Virtuális gépben fut, a 3D gyorsítást a gazdagép adja." + elif e["gyarto_id"] == "10de": + csalad = terv.get("nvidia_csalad", "") + elem["megjegyzes"] = { + "open": "NVIDIA, Turing vagy újabb: nyílt forrású NVIDIA kernelmodul.", + "580xx": "NVIDIA, Maxwell, Pascal vagy Volta: 580-as hosszú támogatású driver.", + "470xx": "NVIDIA, Kepler: 470-es örökölt driver, már nem kap frissítést.", + "nem_tamogatott": "Ehhez a régi NVIDIA kártyához nincs zárt driver, a nyílt nouveau marad.", + }.get(csalad, "") + elif e["kategoria"] == "wifi" and e["gyarto_id"] == "14e4" and not e["jelenlegi"]: + elem["ajanlott"] = ["broadcom-wl-dkms"] + (terv.get("fejlecek", "").split() or ["linux-cachyos-headers"]) + elem["megjegyzes"] = "Broadcom Wi-Fi: a zárt wl driver szükséges hozzá." + elif e["kategoria"] == "hang" and "sof" in e["jelenlegi"]: + elem["ajanlott"] = ["sof-firmware"] + elem["megjegyzes"] = "Intel Sound Open Firmware hangkártya." + elif not e["jelenlegi"]: + elem["ajanlott"] = ["linux-firmware"] + elem["megjegyzes"] = "Nincs betöltött driver, a firmware-csomag hiányozhat." + elem["hianyzik"] = [p for p in elem["ajanlott"] if p not in telepitett] + lista.append(elem) + for k in _usb_kontrollerek(): + elem = { + "kategoria": "bemenet", + "nev": k["nev"], + "jelenlegi": "kernel (beépített)", + "ajanlott": [], + "megjegyzes": "", + "gpu": False, + } + if k["gyarto"] == "045e" and k["termek"] in XBOX_ADAPTEREK: + elem["ajanlott"] = ["xone-dkms"] + (terv.get("fejlecek", "").split() or ["linux-cachyos-headers"]) + elem["jelenlegi"] = "" + elem["megjegyzes"] = "Xbox vezeték nélküli adapter: az xone driver kell hozzá." + elem["hianyzik"] = [p for p in elem["ajanlott"] if p not in telepitett] + lista.append(elem) + return lista + + +def ujrainditas_kell(elem): + return elem["gpu"] and any(p.startswith(("nvidia", "lib32-nvidia")) for p in elem["hianyzik"]) + diff --git a/raveos-center/raveos_center/elemek.py b/raveos-center/raveos_center/elemek.py new file mode 100644 index 0000000..72f68ea --- /dev/null +++ b/raveos-center/raveos_center/elemek.py @@ -0,0 +1,387 @@ +import math + +from PyQt6.QtCore import QEvent, QPointF, QRectF, QSize, Qt, QTimer, pyqtSignal +from PyQt6.QtGui import QColor, QPainter, QPolygonF +from PyQt6.QtWidgets import ( + QAbstractButton, + QFrame, + QGridLayout, + QHBoxLayout, + QLabel, + QPushButton, + QScrollArea, + QSizePolicy, + QVBoxLayout, + QWidget, +) + + +def cimke(szoveg, nev=None, tordel=False): + c = QLabel(szoveg) + if nev: + c.setObjectName(nev) + c.setWordWrap(tordel) + return c + + +def gomb(szoveg, fo=False, kicsi=False): + g = QPushButton(szoveg) + g.setCursor(Qt.CursorShape.PointingHandCursor) + if fo: + g.setProperty("fo", True) + if kicsi: + g.setProperty("kicsi", True) + return g + + +def frissit_stilus(widget): + widget.style().unpolish(widget) + widget.style().polish(widget) + + +class Kartya(QFrame): + def __init__(self, cim=None, terkoz=10, margo=20): + super().__init__() + self.setObjectName("kartya") + self.elrendezes = QVBoxLayout(self) + self.elrendezes.setContentsMargins(margo, margo, margo, margo) + self.elrendezes.setSpacing(terkoz) + if cim: + self.elrendezes.addWidget(cimke(cim.upper(), "kartyacim")) + + +class Racs(QWidget): + def __init__(self, min_szelesseg, terkoz=16): + super().__init__() + self.min_szelesseg = min_szelesseg + self.elemek = [] + self.rejtett = set() + self.oszlopok = 0 + self.racs = QGridLayout(self) + self.racs.setContentsMargins(0, 0, 0, 0) + self.racs.setSpacing(terkoz) + + def hozzaad(self, widget): + self.elemek.append(widget) + self._rendez(force=True) + + def resizeEvent(self, esemeny): + super().resizeEvent(esemeny) + self._rendez() + + def szur(self, lathatok): + self.rejtett = {w for w in self.elemek if w not in lathatok} + for w in self.elemek: + w.setVisible(w not in self.rejtett) + self._rendez(force=True) + + def _rendez(self, force=False): + terkoz = self.racs.spacing() + lathatok = [w for w in self.elemek if w not in self.rejtett] + oszlopok = max(1, (self.width() + terkoz) // (self.min_szelesseg + terkoz)) + if self.min_szelesseg < 260: + oszlopok = min(oszlopok, max(1, len(lathatok))) + if oszlopok == self.oszlopok and not force: + return + self.oszlopok = oszlopok + for w in self.elemek: + self.racs.removeWidget(w) + for i, w in enumerate(lathatok): + self.racs.addWidget(w, i // oszlopok, i % oszlopok) + self.updateGeometry() + oldal = self.parent() + while oldal is not None and not isinstance(oldal, Oldal): + oldal = oldal.parent() + if oldal is not None and oldal.kitolt: + QTimer.singleShot(0, oldal._igazit) + for o in range(self.racs.columnCount()): + self.racs.setColumnStretch(o, 1 if o < oszlopok else 0) + + +class _KitoltoTartalom(QWidget): + def sizeHint(self): + return self.minimumSizeHint() + + def heightForWidth(self, szelesseg): + elrendezes = self.layout() + if elrendezes and elrendezes.hasHeightForWidth(): + return elrendezes.minimumHeightForWidth(szelesseg) + return self.minimumSizeHint().height() + + +class Oldal(QScrollArea): + def __init__(self, kozpont, eyebrow, cim, kitolt=False): + super().__init__() + self.kozpont = kozpont + self.setWidgetResizable(True) + self.setFrameShape(QFrame.Shape.NoFrame) + self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff) + self.kitolt = kitolt + tartalom = _KitoltoTartalom() if kitolt else QWidget() + self.setWidget(tartalom) + if kitolt: + tartalom.installEventFilter(self) + self.torzs = QVBoxLayout(tartalom) + self.torzs.setContentsMargins(48, 40, 48, 40) + self.torzs.setSpacing(32) + + fejlec = QHBoxLayout() + fejlec.setSpacing(16) + cimsor = QVBoxLayout() + cimsor.setSpacing(6) + self.eyebrow = cimke(eyebrow, "eyebrow") + self.cim = cimke(cim.upper(), "h1") + cimsor.addWidget(self.eyebrow) + cimsor.addWidget(self.cim) + fejlec.addLayout(cimsor) + fejlec.addStretch(1) + self.gombsor = QHBoxLayout() + self.gombsor.setSpacing(8) + fejlec.addLayout(self.gombsor) + fejlec.setAlignment(self.gombsor, Qt.AlignmentFlag.AlignBottom) + self.torzs.addLayout(fejlec) + + def eventFilter(self, objektum, esemeny): + if self.kitolt and objektum is self.widget() and esemeny.type() == QEvent.Type.LayoutRequest: + QTimer.singleShot(0, self._igazit) + return super().eventFilter(objektum, esemeny) + + def resizeEvent(self, esemeny): + super().resizeEvent(esemeny) + if self.kitolt: + self._igazit() + + def _igazit(self): + tartalom = self.widget() + nezet = self.viewport() + magassag = max(nezet.height(), tartalom.heightForWidth(nezet.width())) + if tartalom.height() != magassag or tartalom.width() != nezet.width(): + tartalom.resize(nezet.width(), magassag) + + def fejlec_gomb(self, gomb_widget): + self.gombsor.addWidget(gomb_widget) + return gomb_widget + + def megjelenik(self, *_): + pass + + def indulaskor(self): + pass + + def elerheto(self, *_): + return True + + +class Fulsor(QWidget): + def __init__(self, fulek, valtas): + super().__init__() + self.setObjectName("fulsor") + self.setAttribute(Qt.WidgetAttribute.WA_StyledBackground, True) + sor = QHBoxLayout(self) + sor.setContentsMargins(0, 0, 0, 0) + sor.setSpacing(0) + self.gombok = {} + for azon, szoveg in fulek: + g = QPushButton(szoveg) + g.setObjectName("ful") + g.setCursor(Qt.CursorShape.PointingHandCursor) + g.clicked.connect(lambda _=False, a=azon: valtas(a)) + self.gombok[azon] = g + sor.addWidget(g) + sor.addStretch(1) + + def aktiv(self, azon): + for a, g in self.gombok.items(): + g.setProperty("aktiv", a == azon) + frissit_stilus(g) + + +class Verem(QWidget): + currentChanged = pyqtSignal(int) + + def __init__(self): + super().__init__() + self._lapok = [] + self._aktualis = -1 + self._elrendezes = QVBoxLayout(self) + self._elrendezes.setContentsMargins(0, 0, 0, 0) + self._elrendezes.setSpacing(0) + + def addWidget(self, widget): + self._lapok.append(widget) + self._elrendezes.addWidget(widget) + if self._aktualis < 0: + self.setCurrentIndex(0) + else: + widget.hide() + return len(self._lapok) - 1 + + def count(self): + return len(self._lapok) + + def widget(self, index): + return self._lapok[index] if 0 <= index < len(self._lapok) else None + + def indexOf(self, widget): + return self._lapok.index(widget) if widget in self._lapok else -1 + + def currentIndex(self): + return self._aktualis + + def currentWidget(self): + return self.widget(self._aktualis) + + def setCurrentIndex(self, index): + if not 0 <= index < len(self._lapok): + return + for i, lap in enumerate(self._lapok): + lap.setVisible(i == index) + valtozott = index != self._aktualis + self._aktualis = index + self.updateGeometry() + if valtozott: + self.currentChanged.emit(index) + + def setCurrentWidget(self, widget): + self.setCurrentIndex(self.indexOf(widget)) + + +class Kapcsolo(QAbstractButton): + def __init__(self): + super().__init__() + self.setCheckable(True) + self.setCursor(Qt.CursorShape.PointingHandCursor) + self.setFixedSize(QSize(44, 24)) + self.setAccessibleName("kapcsoló") + + def sizeHint(self): + return QSize(44, 24) + + def paintEvent(self, _): + from raveos_center import stilus + + festo = QPainter(self) + festo.setRenderHint(QPainter.RenderHint.Antialiasing, False) + be = self.isChecked() + aktiv = self.isEnabled() + palya = QColor(stilus.ZOLD if be else stilus.AKTIV) + keret = QColor(stilus.ZOLD if be else stilus.KERET_EROS) + if not aktiv: + palya = QColor(stilus.KERET if not be else "#3d5a36") + keret = palya + festo.setPen(keret) + festo.setBrush(palya) + festo.drawRect(QRectF(0.5, 0.5, self.width() - 1, self.height() - 1)) + gomb_meret = self.height() - 8 + x = self.width() - gomb_meret - 4 if be else 4 + festo.setPen(Qt.PenStyle.NoPen) + festo.setBrush(QColor(stilus.ZOLDON_SZOVEG if be else stilus.SZOVEG_HALVANY)) + festo.drawRect(QRectF(x, 4, gomb_meret, gomb_meret)) + festo.end() + + +class Csillagok(QWidget): + valasztva = pyqtSignal(int) + TERKOZ = 6 + LEPESEK = {Qt.Key.Key_Right: 1, Qt.Key.Key_Up: 1, Qt.Key.Key_Left: -1, Qt.Key.Key_Down: -1} + SZAMOK = {Qt.Key.Key_1: 1, Qt.Key.Key_2: 2, Qt.Key.Key_3: 3, Qt.Key.Key_4: 4, Qt.Key.Key_5: 5} + + def __init__(self, meret=22, valaszthato=False): + super().__init__() + self.meret = meret + self.ertek = 0.0 + self.mutatott = 0 + self.valaszthato = valaszthato + self.setFixedSize(self.sizeHint()) + self.setAccessibleName("csillagos értékelés") + if valaszthato: + self.setMouseTracking(True) + self.setCursor(Qt.CursorShape.PointingHandCursor) + self.setFocusPolicy(Qt.FocusPolicy.StrongFocus) + + def sizeHint(self): + return QSize(self.meret * 5 + self.TERKOZ * 4, self.meret) + + def beallit(self, ertek): + self.ertek = max(0.0, min(5.0, float(ertek))) + self.setToolTip(f"{self.ertek:.1f} / 5" if not self.valaszthato else "") + self.update() + + def _index(self, pont): + return max(1, min(5, int(pont.x() // (self.meret + self.TERKOZ)) + 1)) + + def _valaszt(self, ertek): + self.beallit(ertek) + self.valasztva.emit(int(ertek)) + + def mouseMoveEvent(self, esemeny): + if self.valaszthato and self.isEnabled(): + self.mutatott = self._index(esemeny.position()) + self.update() + + def leaveEvent(self, esemeny): + self.mutatott = 0 + self.update() + super().leaveEvent(esemeny) + + def mouseReleaseEvent(self, esemeny): + if self.valaszthato and esemeny.button() == Qt.MouseButton.LeftButton and self.rect().contains( + esemeny.position().toPoint() + ): + self._valaszt(self._index(esemeny.position())) + super().mouseReleaseEvent(esemeny) + + def keyPressEvent(self, esemeny): + if not self.valaszthato: + return super().keyPressEvent(esemeny) + try: + kulcs = Qt.Key(esemeny.key()) + except ValueError: + kulcs = None + if kulcs in self.LEPESEK: + self._valaszt(max(1, min(5, int(self.ertek) + self.LEPESEK[kulcs]))) + elif kulcs in self.SZAMOK: + self._valaszt(self.SZAMOK[kulcs]) + else: + super().keyPressEvent(esemeny) + + def _sokszog(self, bal): + r = self.meret / 2 + kozep = QPointF(bal + r, r * 1.05) + pontok = [] + for i in range(10): + sugar = r if i % 2 == 0 else r * 0.42 + szog = math.radians(-90 + i * 36) + pontok.append(QPointF(kozep.x() + sugar * math.cos(szog), kozep.y() + sugar * math.sin(szog))) + return QPolygonF(pontok) + + def paintEvent(self, _): + from raveos_center import stilus + + festo = QPainter(self) + festo.setRenderHint(QPainter.RenderHint.Antialiasing, True) + festo.setPen(Qt.PenStyle.NoPen) + ertek = self.mutatott or self.ertek + tele = QColor(stilus.SARGA if self.isEnabled() else stilus.SZOVEG_HALVANY) + for i in range(5): + bal = i * (self.meret + self.TERKOZ) + csillag = self._sokszog(bal) + festo.setBrush(QColor(stilus.KERET_EROS)) + festo.drawPolygon(csillag) + resz = max(0.0, min(1.0, ertek - i)) + if resz > 0: + festo.save() + festo.setClipRect(QRectF(bal, 0, self.meret * resz, self.meret)) + festo.setBrush(tele) + festo.drawPolygon(csillag) + festo.restore() + if self.hasFocus(): + festo.setPen(QColor(stilus.ZOLD)) + festo.setBrush(Qt.BrushStyle.NoBrush) + festo.drawRect(QRectF(0.5, 0.5, self.width() - 1, self.height() - 1)) + festo.end() + + +def nyujthato(widget): + widget.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Preferred) + return widget diff --git a/raveos-center/raveos_center/folyamat.py b/raveos-center/raveos_center/folyamat.py new file mode 100644 index 0000000..586491f --- /dev/null +++ b/raveos-center/raveos_center/folyamat.py @@ -0,0 +1,68 @@ +from PyQt6.QtCore import Qt +from PyQt6.QtWidgets import QDialog, QHBoxLayout, QPlainTextEdit, QVBoxLayout + +from raveos_center import hatter, muveletek +from raveos_center.elemek import cimke, gomb + + +class FolyamatAblak(QDialog): + def __init__(self, szulo, cim, lepesek, siker_szoveg="Kész.", hiba_szoveg="A művelet nem sikerült."): + super().__init__(szulo) + self.setWindowTitle(cim) + self.resize(760, 520) + self.siker_szoveg = siker_szoveg + self.hiba_szoveg = hiba_szoveg + self.rendben = False + doboz = QVBoxLayout(self) + doboz.setContentsMargins(28, 24, 28, 20) + doboz.setSpacing(12) + self.felirat = cimke("FOLYAMATBAN", "eyebrow") + doboz.addWidget(self.felirat) + self.cim = cimke(cim.upper(), "h2", True) + doboz.addWidget(self.cim) + self.allapot = cimke("Indul...", "masodlagos", True) + doboz.addWidget(self.allapot) + self.naplo = QPlainTextEdit() + self.naplo.setObjectName("naplo") + self.naplo.setReadOnly(True) + self.naplo.setMaximumBlockCount(5000) + doboz.addWidget(self.naplo, 1) + sor = QHBoxLayout() + sor.addStretch(1) + self.bezar = gomb("Bezárás", fo=True) + self.bezar.setEnabled(False) + self.bezar.clicked.connect(self.accept) + sor.addWidget(self.bezar) + doboz.addLayout(sor) + self.futtato = muveletek.Futtato(self) + self.futtato.sor.connect(self.naplo.appendPlainText) + self.futtato.lepes_indul.connect(self.allapot.setText) + self.futtato.kesz.connect(self._kesz) + self.lepesek = lepesek + + def exec(self): + self.futtato.indit(self.lepesek) + return super().exec() + + def _kesz(self, rendben, hibasak): + hatter.ervenytelenit() + self.rendben = rendben + self.felirat.setText("KÉSZ" if rendben else "HIBA") + if rendben: + self.allapot.setText(self.siker_szoveg) + else: + self.allapot.setText(f"{self.hiba_szoveg} Sikertelen lépés: {', '.join(hibasak)}") + self.allapot.setObjectName("figyelem") + self.allapot.style().unpolish(self.allapot) + self.allapot.style().polish(self.allapot) + self.bezar.setEnabled(True) + + def reject(self): + if self.futtato.fut(): + return + super().reject() + + def keyPressEvent(self, esemeny): + if esemeny.key() == Qt.Key.Key_Escape and self.futtato.fut(): + return + super().keyPressEvent(esemeny) diff --git a/raveos-center/raveos_center/gaming.py b/raveos-center/raveos_center/gaming.py new file mode 100644 index 0000000..9652958 --- /dev/null +++ b/raveos-center/raveos_center/gaming.py @@ -0,0 +1,272 @@ +import os +import re +import subprocess +from pathlib import Path + +from raveos_center.muveletek import Lepes, root + +KONFIG = Path(os.environ.get("XDG_CONFIG_HOME", Path.home() / ".config")) +KORNYEZET = KONFIG / "environment.d" / "raveos-gaming.conf" +GAMEMODE_INI = KONFIG / "gamemode.ini" +GAMESCOPE = ["gamescope", "gamescope-session-git", "gamescope-session-steam-git"] +MANGOHUD_CONF = KONFIG / "MangoHud" / "MangoHud.conf" +MANGOHUD_MODOK = [ + ("", "Alapértelmezett"), + ("1", "Csak FPS"), + ("2", "Vízszintes sáv"), + ("3", "Bővített"), + ("4", "Részletes"), +] +SCHED_EXT = Path("/sys/kernel/sched_ext") +PROTON_CACHYOS = "proton-cachyos-slr" +GAMING_MOD = ["gamemode", "governor", "ntsync", "utemezo"] +GOV_ALAPBOL = "A rendszer alapból is performance módban fut." + + +def _telepitett(): + try: + kimenet = subprocess.run(["pacman", "-Qq"], capture_output=True, text=True, timeout=30).stdout + except (OSError, subprocess.TimeoutExpired): + return set() + return set(kimenet.split()) + + +def _cpufreq(): + return Path("/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor").exists() + + +def _ntsync_beepitett(): + try: + kimenet = subprocess.run(["modinfo", "-F", "filename", "ntsync"], capture_output=True, text=True, timeout=10) + except (OSError, subprocess.TimeoutExpired): + return False + return kimenet.stdout.strip() == "(builtin)" + + +def _rendszer_gov(): + try: + return Path("/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor").read_text().strip() + except OSError: + return "" + + +def _desiredgov(): + try: + sorok = GAMEMODE_INI.read_text(encoding="utf-8").splitlines() + except OSError: + return None + szakasz = "" + for sor in sorok: + t = sor.strip() + if t.startswith("[") and t.endswith("]"): + szakasz = t[1:-1].strip() + elif szakasz == "general": + talalat = re.match(r"desiredgov\s*=\s*(\S+)", t) + if talalat: + return talalat.group(1) + return None + + +def _desiredgov_ir(ertek): + try: + sorok = GAMEMODE_INI.read_text(encoding="utf-8").splitlines() + except FileNotFoundError: + sorok = [] + uj = [] + szakasz = "" + general_volt = False + beirva = False + for sor in sorok: + t = sor.strip() + if t.startswith("[") and t.endswith("]"): + if szakasz == "general" and not beirva: + uj.append(f"desiredgov={ertek}") + beirva = True + szakasz = t[1:-1].strip() + general_volt = general_volt or szakasz == "general" + elif szakasz == "general" and re.match(r"desiredgov\s*=", t): + if not beirva: + uj.append(f"desiredgov={ertek}") + beirva = True + continue + uj.append(sor) + if not beirva: + if not general_volt: + uj.append("[general]") + uj.append(f"desiredgov={ertek}") + GAMEMODE_INI.parent.mkdir(parents=True, exist_ok=True) + ideiglenes = GAMEMODE_INI.with_suffix(".ini.uj") + ideiglenes.write_text("\n".join(uj) + "\n", encoding="utf-8") + os.replace(ideiglenes, GAMEMODE_INI) + + +def _mangohud_sor(): + try: + return any(s.strip() == "MANGOHUD=1" for s in KORNYEZET.read_text().splitlines()) + except OSError: + return False + + +def _utemezo_allapot(): + if not SCHED_EXT.is_dir(): + return { + "aktiv": False, + "elerheto": False, + "megjegyzes": "A futó kernel nem támogatja (sched_ext nélkül fordították).", + } + try: + fut = (SCHED_EXT / "state").read_text().strip() == "enabled" + nev = (SCHED_EXT / "root" / "ops").read_text().strip() if fut else "" + except OSError: + fut, nev = False, "" + return {"aktiv": fut, "elerheto": True, "megjegyzes": f"Most fut: {nev}" if nev else ""} + + +def mangohud_mod(): + try: + sorok = MANGOHUD_CONF.read_text(encoding="utf-8").splitlines() + except OSError: + return "" + for sor in sorok: + talalat = re.match(r"\s*preset\s*=\s*(-?\d)", sor) + if talalat: + return talalat.group(1) + return "" + + +def mangohud_mod_beallit(mod): + try: + sorok = MANGOHUD_CONF.read_text(encoding="utf-8").splitlines() + except FileNotFoundError: + sorok = [] + sorok = [s for s in sorok if not re.match(r"\s*preset\s*=", s)] + if mod: + tobbi = [m for m, _ in MANGOHUD_MODOK if m and m != mod] + sorok.insert(0, "preset=" + ",".join([mod] + tobbi + ["0"])) + if not sorok: + MANGOHUD_CONF.unlink(missing_ok=True) + return + MANGOHUD_CONF.parent.mkdir(parents=True, exist_ok=True) + ideiglenes = MANGOHUD_CONF.with_suffix(".conf.uj") + ideiglenes.write_text("\n".join(sorok) + "\n", encoding="utf-8") + os.replace(ideiglenes, MANGOHUD_CONF) + + +def _proton_cachyos_elerheto(): + try: + return subprocess.run(["pacman", "-Si", PROTON_CACHYOS], capture_output=True, timeout=30).returncode == 0 + except (OSError, subprocess.TimeoutExpired): + return False + + +def _governor_allapot(gamemode): + rendszer = _rendszer_gov() + kivant = _desiredgov() or "performance" + if not gamemode: + return {"aktiv": False, "elerheto": False, "megjegyzes": "Ehhez előbb a GameMode-ot kapcsold be."} + if not rendszer: + return {"aktiv": False, "elerheto": False, "megjegyzes": "Ezen a gépen nincs órajel-szabályozás (pl. virtuális gép)."} + if rendszer == "performance": + return {"aktiv": True, "elerheto": False, "megjegyzes": GOV_ALAPBOL} + return {"aktiv": kivant == "performance", "elerheto": True, "megjegyzes": ""} + + +def allapot(): + telepitett = _telepitett() + gamemode = {"gamemode", "lib32-gamemode"} <= telepitett + mangohud = "mangohud" in telepitett + ntsync_beepitett = _ntsync_beepitett() + return { + "gamemode": {"aktiv": gamemode, "elerheto": True, "megjegyzes": "", "root": True}, + "mangohud": { + "aktiv": _mangohud_sor() and mangohud, + "elerheto": True, + "megjegyzes": "Kijelentkezés és újra bejelentkezés után lép életbe.", + "root": not mangohud, + }, + "gamescope": { + "aktiv": set(GAMESCOPE) <= telepitett, + "elerheto": True, + "megjegyzes": "" if "steam" in telepitett else "A Steam munkamenethez a Steam is szükséges.", + "root": True, + }, + "governor": dict(_governor_allapot(gamemode), root=False, cpufreq=bool(_rendszer_gov())), + "ntsync": { + "aktiv": ntsync_beepitett + or (Path("/etc/modules-load.d/ntsync.conf").exists() and Path("/dev/ntsync").exists()), + "elerheto": not ntsync_beepitett, + "megjegyzes": "A RaveOS kernelébe be van építve, mindig aktív." if ntsync_beepitett else "", + "root": True, + }, + "utemezo": dict(_utemezo_allapot(), root=True), + "proton_cachyos": { + "aktiv": PROTON_CACHYOS in telepitett, + "elerheto": PROTON_CACHYOS in telepitett or _proton_cachyos_elerheto(), + "megjegyzes": "", + "root": True, + }, + } + + +def lepesek(azon, be): + if azon == "gamemode": + csomagok = ["gamemode", "lib32-gamemode"] + return [Lepes("GameMode", root("telepit" if be else "eltavolit", *csomagok))], None + if azon == "mangohud": + lista = [] + if be and "mangohud" not in _telepitett(): + lista.append(Lepes("MangoHud telepítése", root("telepit", "mangohud", "lib32-mangohud"))) + return lista, lambda: _mangohud(be) + if azon == "gamescope": + return [Lepes("Gamescope session", root("telepit" if be else "eltavolit", *(GAMESCOPE if be else GAMESCOPE[1:])))], None + if azon == "governor": + return [], lambda: _governor(be) + if azon == "ntsync": + return [Lepes("NTSync", root("ntsync", "be" if be else "ki"))], None + if azon == "utemezo": + return [Lepes("Játékra hangolt ütemező", root("utemezo", "be" if be else "ki"))], None + if azon == "proton_cachyos": + return [Lepes("Proton-CachyOS", root("telepit" if be else "eltavolit", PROTON_CACHYOS))], None + return [], None + + +def _mangohud(be): + sorok = [] + try: + sorok = [s for s in KORNYEZET.read_text().splitlines() if s.strip() != "MANGOHUD=1"] + except OSError: + pass + if be: + sorok.append("MANGOHUD=1") + if sorok: + KORNYEZET.parent.mkdir(parents=True, exist_ok=True) + KORNYEZET.write_text("\n".join(sorok) + "\n") + else: + KORNYEZET.unlink(missing_ok=True) + + +def _governor(be): + rendszer = _rendszer_gov() + if be: + _desiredgov_ir("performance") + elif rendszer and rendszer != "performance": + _desiredgov_ir(rendszer) + + +def gaming_mod_valtozasok(adat, be): + valtozasok = [] + gamemode_lesz = adat["gamemode"]["aktiv"] or be + for a in GAMING_MOD: + if a == "gamemode" and not be: + continue + if a == "governor": + elerheto = gamemode_lesz and adat[a]["cpufreq"] and adat[a]["megjegyzes"] != GOV_ALAPBOL + else: + elerheto = adat[a]["elerheto"] + if elerheto and adat[a]["aktiv"] != be: + valtozasok.append((a, be)) + return valtozasok + + +def gaming_mod_aktiv(adat): + return all(adat[a]["aktiv"] for a in GAMING_MOD if adat[a]["elerheto"] or adat[a]["aktiv"]) diff --git a/raveos-center/raveos_center/hatter.py b/raveos-center/raveos_center/hatter.py new file mode 100644 index 0000000..6c7349c --- /dev/null +++ b/raveos-center/raveos_center/hatter.py @@ -0,0 +1,101 @@ +import time + +from PyQt6.QtCore import QObject, QThreadPool, pyqtSignal + + +class _Jelzo(QObject): + kesz = pyqtSignal(object) + hiba = pyqtSignal(str) + + +_elo = set() + + +def hatterben(fuggveny, *args, kesz=None, hiba=None): + jelzo = _Jelzo() + _elo.add(jelzo) + + def vege(*_): + _elo.discard(jelzo) + + if kesz: + jelzo.kesz.connect(kesz) + if hiba: + jelzo.hiba.connect(hiba) + jelzo.kesz.connect(vege) + jelzo.hiba.connect(vege) + + def futas(): + try: + eredmeny = fuggveny(*args) + except BaseException as e: + jelzo.hiba.emit(str(e) or type(e).__name__) + else: + jelzo.kesz.emit(eredmeny) + + QThreadPool.globalInstance().start(futas) + + +_tar = {} +_futok = {} +_generacio = {} + + +def _indit(kulcs, fuggveny, args, varok): + generacio = _generacio.get(kulcs, 0) + _futok[kulcs] = {"fuggveny": fuggveny, "args": args, "varok": varok} + + def vege(ertek): + if _generacio.get(kulcs, 0) != generacio: + return + futo = _futok.pop(kulcs, None) + regi = _tar.get(kulcs) + _tar[kulcs] = (time.monotonic(), ertek) + for k, _, kiszolgalva in futo["varok"] if futo else []: + if k and (not kiszolgalva or regi is None or regi[1] != ertek): + k(ertek) + + def hibas(uzenet): + if _generacio.get(kulcs, 0) != generacio: + return + futo = _futok.pop(kulcs, None) + for _, h, _ in futo["varok"] if futo else []: + if h: + h(uzenet) + + hatterben(fuggveny, *args, kesz=vege, hiba=hibas) + + +def gyorsitott(kulcs, fuggveny, *args, kesz=None, hiba=None, kor=60): + meglevo = _tar.get(kulcs) + if meglevo and kesz: + kesz(meglevo[1]) + if meglevo and time.monotonic() - meglevo[0] < kor: + return + var = (kesz, hiba, meglevo is not None) + if kulcs in _futok: + _futok[kulcs]["varok"].append(var) + return + _indit(kulcs, fuggveny, args, [var]) + + +def tarolt(kulcs): + meglevo = _tar.get(kulcs) + return meglevo[1] if meglevo else None + + +def ervenytelenit(*kulcsok): + for kulcs in kulcsok or list(set(_tar) | set(_futok)): + _tar.pop(kulcs, None) + _generacio[kulcs] = _generacio.get(kulcs, 0) + 1 + futo = _futok.pop(kulcs, None) + if futo: + _indit(kulcs, futo["fuggveny"], futo["args"], [(k, h, False) for k, h, _ in futo["varok"]]) + + +def leallitas(ido_ms=3000): + _tar.clear() + _futok.clear() + pool = QThreadPool.globalInstance() + pool.clear() + pool.waitForDone(ido_ms) diff --git a/raveos-center/raveos_center/ikonok.py b/raveos-center/raveos_center/ikonok.py new file mode 100644 index 0000000..16d402c --- /dev/null +++ b/raveos-center/raveos_center/ikonok.py @@ -0,0 +1,30 @@ +from PyQt6.QtCore import QByteArray, Qt +from PyQt6.QtGui import QIcon, QPainter, QPixmap +from PyQt6.QtSvg import QSvgRenderer + +UTAK = { + "attekintes": '<path d="M3 11l9-7 9 7v9a1 1 0 0 1-1 1h-5v-6H9v6H4a1 1 0 0 1-1-1z"/>', + "alkalmazasok": '<rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/>' + '<rect x="3" y="14" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/>', + "frissitesek": '<path d="M21 12a9 9 0 1 1-3-6.7"/><path d="M21 3v6h-6"/>', + "rendszer": '<rect x="5" y="5" width="14" height="14"/><rect x="9" y="9" width="6" height="6"/>' + '<path d="M9 2v3M15 2v3M9 19v3M15 19v3M2 9h3M2 15h3M19 9h3M19 15h3"/>', + "visszajelzes": '<path d="M4 4h16v12H8l-4 4z"/><path d="M8 9h8M8 12h5"/>', + "beallitasok": '<circle cx="12" cy="12" r="3"/>' + '<path d="M12 2v3M12 19v3M2 12h3M19 12h3M4.9 4.9l2.1 2.1M17 17l2.1 2.1M4.9 19.1L7 17M17 7l2.1-2.1"/>', +} + + +def ikon(nev, szin, meret=18): + svg = ( + f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="{szin}" ' + f'stroke-width="2" stroke-linecap="square">{UTAK[nev]}</svg>' + ) + renderelo = QSvgRenderer(QByteArray(svg.encode())) + kep = QPixmap(meret * 2, meret * 2) + kep.fill(Qt.GlobalColor.transparent) + festo = QPainter(kep) + renderelo.render(festo) + festo.end() + kep.setDevicePixelRatio(2) + return QIcon(kep) diff --git a/raveos-center/raveos_center/jegyek_api.py b/raveos-center/raveos_center/jegyek_api.py new file mode 100644 index 0000000..c8c7268 --- /dev/null +++ b/raveos-center/raveos_center/jegyek_api.py @@ -0,0 +1,135 @@ +import json +import os +import threading +import urllib.error +import urllib.request +from pathlib import Path + +ALAP = os.environ.get("RAVEOS_JEGYEK_URL", "https://jegyek.rp1.hu/api/v1") +KONFIG = Path(os.environ.get("XDG_CONFIG_HOME", Path.home() / ".config")) / "raveos-center" +KULCSFAJL = KONFIG / "kliens.json" + + +class ApiHiba(Exception): + def __init__(self, kod, uzenet): + super().__init__(uzenet) + self.kod = kod + + +def _keres(modszer, ut, adat=None, nyers=None, tipus="application/json", kulcs=None, ido=30): + fejlec = {"User-Agent": "raveos-center"} + torzs = None + if adat is not None: + torzs = json.dumps(adat).encode() + fejlec["Content-Type"] = "application/json" + elif nyers is not None: + torzs = nyers + fejlec["Content-Type"] = tipus + if kulcs: + fejlec["Authorization"] = f"Bearer {kulcs}" + keres = urllib.request.Request(ALAP + ut, data=torzs, headers=fejlec, method=modszer) + try: + with urllib.request.urlopen(keres, timeout=ido) as valasz: + return json.loads(valasz.read() or b"{}") + except urllib.error.HTTPError as e: + try: + uzenet = json.loads(e.read()).get("hiba", str(e)) + except ValueError: + uzenet = str(e) + raise ApiHiba(e.code, uzenet) from None + except (urllib.error.URLError, OSError) as e: + raise ApiHiba(0, "nincs_kapcsolat") from e + + +_kulcs_zar = threading.Lock() + + +def kulcs(): + with _kulcs_zar: + try: + return json.loads(KULCSFAJL.read_text())["kulcs"] + except (OSError, ValueError, KeyError): + pass + uj = _keres("POST", "/kliens")["kulcs"] + KONFIG.mkdir(parents=True, exist_ok=True) + os.chmod(KONFIG, 0o700) + ideiglenes = KULCSFAJL.with_suffix(".uj") + fd = os.open(ideiglenes, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600) + with os.fdopen(fd, "w") as f: + json.dump({"kulcs": uj}, f) + os.replace(ideiglenes, KULCSFAJL) + return uj + + +def _hitelesitett(modszer, ut, **kw): + try: + return _keres(modszer, ut, kulcs=kulcs(), **kw) + except ApiHiba as e: + if e.kod != 401: + raise + with _kulcs_zar: + KULCSFAJL.unlink(missing_ok=True) + return _keres(modszer, ut, kulcs=kulcs(), **kw) + + +def jegyeim(): + return _hitelesitett("GET", "/jegyek")["jegyek"] + + +def jegy(jegy_id): + return _hitelesitett("GET", f"/jegyek/{jegy_id}") + + +def uj_jegy(tipus, cim, szoveg, rendszer=None, naplo=None): + adat = {"tipus": tipus, "cim": cim, "szoveg": szoveg} + if rendszer: + adat["rendszer"] = rendszer + if naplo: + adat["naplo"] = naplo + return _hitelesitett("POST", "/jegyek", adat=adat) + + +def valasz(jegy_id, szoveg): + return _hitelesitett("POST", f"/jegyek/{jegy_id}/uzenetek", adat={"szoveg": szoveg}) + + +def otletek(): + return _hitelesitett("GET", "/otletek")["otletek"] + + +def szavaz(jegy_id, tamogat): + return _hitelesitett("PUT" if tamogat else "DELETE", f"/otletek/{jegy_id}/szavazat") + + +def ertekeles(): + return _hitelesitett("GET", "/ertekeles") + + +def ertekel(csillag, szoveg): + return _hitelesitett("PUT", "/ertekeles", adat={"csillag": csillag, "szoveg": szoveg}) + + +def allapot(): + return _hitelesitett("GET", "/allapot") + + +def naplo_feltolt(szoveg): + return _hitelesitett( + "POST", "/naplok?forras=center", nyers=szoveg.encode(), tipus="text/plain; charset=utf-8", ido=60 + )["azonosito"] + + +HIBAUZENETEK = { + "nincs_kapcsolat": "Nem sikerült elérni a szervert. Ellenőrizd az internetkapcsolatot.", + "tul_sok_keres": "Túl sok kérés rövid idő alatt. Próbáld újra kicsit később.", + "lezart_jegy": "Ez a jegy le van zárva, már nem lehet rá válaszolni.", + "nincs_jegy": "Ez a jegy már nem létezik.", + "nincs_ilyen_naplo": "A megadott naplóazonosító nem létezik.", + "kitiltva": "Ez a gép ki van tiltva a Visszajelzés használatából.", +} + + +def emberi(hiba): + if isinstance(hiba, ApiHiba): + return HIBAUZENETEK.get(str(hiba), f"A szerver hibát jelzett ({hiba}).") + return str(hiba) diff --git a/raveos-center/raveos_center/karbantartas.py b/raveos-center/raveos_center/karbantartas.py new file mode 100644 index 0000000..2f929c0 --- /dev/null +++ b/raveos-center/raveos_center/karbantartas.py @@ -0,0 +1,141 @@ +import os +import re +from pathlib import Path +import shutil +import subprocess + +from raveos_center.muveletek import Lepes, root + +CACHE = Path("/var/cache/pacman/pkg") + + +def _ki(parancs, bemenet=None, ido=60): + try: + kimenet = subprocess.run( + parancs, input=bemenet, capture_output=True, text=True, timeout=ido, env=dict(os.environ, LC_ALL="C") + ) + except (OSError, subprocess.TimeoutExpired): + return "" + return kimenet.stdout + kimenet.stderr + + +def _paccache(*kapcsolok): + kimenet = _ki(["paccache", "-dv", *kapcsolok]) + return {s.strip() for s in kimenet.splitlines() if re.search(r"\.pkg\.tar(\.\w+)?$", s.strip())} + + +def _meret(bajt): + for egyseg in ("B", "KiB", "MiB", "GiB"): + if bajt < 1024 or egyseg == "GiB": + return f"{bajt:.0f} {egyseg}" if egyseg == "B" else f"{bajt:.1f} {egyseg}" + bajt /= 1024 + return "" + + +def cache_elonezet(): + if not shutil.which("paccache"): + return {"elerheto": False, "osszegzes": "A paccache nincs telepítve (pacman-contrib).", "elemek": []} + elemek = sorted(_paccache("-k2") | _paccache("-uk0")) + osszes = 0 + for nev in elemek: + for fajl in (CACHE / nev, CACHE / f"{nev}.sig"): + try: + osszes += fajl.stat().st_size + except OSError: + pass + return { + "elerheto": True, + "osszegzes": f"{len(elemek)} csomagfájl, {_meret(osszes)}" if elemek else "Nincs törölhető csomagfájl.", + "elemek": elemek, + } + + +def arva_elonezet(): + kimenet = _ki(["pacman", "-Qdtq"]) + elemek = [s.strip() for s in kimenet.splitlines() if re.fullmatch(r"[a-z0-9@_+][a-z0-9@._+-]*", s.strip())] + return { + "elerheto": True, + "osszegzes": f"{len(elemek)} árva csomag" if elemek else "Nincs árva csomag.", + "elemek": elemek, + } + + +def naplo_elonezet(): + meret = re.search(r"take up ([0-9.]+)\s*([KMGT]?)", _ki(["journalctl", "--disk-usage"])) + return { + "elerheto": True, + "osszegzes": f"Jelenleg {meret.group(1)} {meret.group(2)}B" if meret else "A napló mérete nem olvasható.", + "elemek": [], + } + + +def _flatpak_nem_hasznalt(telepites): + kimenet = _ki(["flatpak", "uninstall", f"--{telepites}", "--unused"], bemenet="n\n") + return [re.sub(r"\s+", " ", s.strip()) for s in kimenet.splitlines() if re.match(r"\s*[0-9]+\.\s", s)] + + +def flatpak_elonezet(): + if not shutil.which("flatpak"): + return {"elerheto": False, "osszegzes": "A Flatpak nincs telepítve.", "elemek": [], "telepitesek": []} + telepitesek = [] + elemek = [] + for telepites in ("user", "system"): + lista = _flatpak_nem_hasznalt(telepites) + if lista: + telepitesek.append(telepites) + elemek += [f"{telepites}: {e}" for e in lista] + return { + "elerheto": True, + "osszegzes": f"{len(elemek)} nem használt runtime" if elemek else "Nincs nem használt runtime.", + "elemek": elemek, + "telepitesek": telepitesek, + } + + +def tukor_allapot(): + try: + with open("/etc/pacman.d/mirrorlist", encoding="utf-8") as f: + szerverek = sum(1 for s in f if s.startswith("Server")) + except OSError: + szerverek = 0 + return {"elerheto": True, "osszegzes": f"{szerverek} aktív tükör", "elemek": []} + + +def kulcs_allapot(): + gpg = [str(p) for p in Path("/usr/share/pacman/keyrings").glob("*.gpg")] + kulcsok = sorted(set(_ki(["pacman", "-Qqo", *gpg]).split())) if gpg else [] + return {"elerheto": True, "osszegzes": ", ".join(kulcsok) or "Nincs kulcscsomag.", "elemek": []} + + +ELONEZETEK = { + "cache": cache_elonezet, + "arva": arva_elonezet, + "naplo": naplo_elonezet, + "tukrok": tukor_allapot, + "kulcsok": kulcs_allapot, + "flatpak": flatpak_elonezet, +} + + +def lepesek(azon, adat, kivalasztott=None): + if azon == "cache": + return [Lepes("Csomag cache ürítése", root("karbantartas", "cache"))] + if azon == "arva": + csomagok = kivalasztott if kivalasztott is not None else adat["elemek"] + return [Lepes("Árva csomagok eltávolítása", root("arva", *csomagok))] if csomagok else [] + if azon == "naplo": + return [Lepes("Naplók takarítása", root("karbantartas", "naplo"))] + if azon == "tukrok": + return [Lepes("Tükrök frissítése", root("karbantartas", "tukrok"))] + if azon == "kulcsok": + return [Lepes("Kulcsok frissítése", root("karbantartas", "kulcsok"))] + if azon == "flatpak": + lista = [] + if "user" in adat.get("telepitesek", []): + lista.append( + Lepes("Flatpak (felhasználói)", ["flatpak", "uninstall", "--user", "--unused", "-y", "--noninteractive"], False) + ) + if "system" in adat.get("telepitesek", []): + lista.append(Lepes("Flatpak (rendszer)", root("karbantartas", "flatpak"), False)) + return lista + return [] diff --git a/raveos-center/raveos_center/katalogus.py b/raveos-center/raveos_center/katalogus.py new file mode 100644 index 0000000..118d948 --- /dev/null +++ b/raveos-center/raveos_center/katalogus.py @@ -0,0 +1,192 @@ +import json +import locale +import os +import shlex +import subprocess +from pathlib import Path + +from raveos_center.muveletek import Lepes, root + +ADAT_HELYEK = [Path("/usr/lib/raveos-center/adat"), Path(__file__).resolve().parent.parent / "adat"] + +KATEGORIAK = [ + ("all", "Mind"), + ("Games", "Gaming"), + ("Media", "Média"), + ("Internet", "Internet"), + ("Office", "Iroda"), + ("Dev", "Fejlesztés"), + ("System", "Rendszer"), + ("Security", "Biztonság"), +] + +GAMING_ALAPOK = ["steam", "lutris", "heroic", "gamemode", "mangohud"] + +FLATHUB = "https://dl.flathub.org/repo/flathub.flatpakrepo" + + +def adat_mappa(): + for hely in ADAT_HELYEK: + if (hely / "katalogus" / "app-list.json").is_file(): + return hely + raise RuntimeError("Nem található az alkalmazáskatalógus (app-list.json).") + + +def nyelv(): + kod = (locale.getlocale()[0] or os.environ.get("LANG", "hu"))[:2] + return kod if kod in ("hu", "en", "de") else "hu" + + +def loc(ertek): + if isinstance(ertek, dict): + return ertek.get(nyelv()) or ertek.get("hu") or ertek.get("en") or next(iter(ertek.values()), "") + return ertek or "" + + +def betolt(): + adat = json.loads((adat_mappa() / "katalogus" / "app-list.json").read_text(encoding="utf-8")) + lista = [] + for a in adat.get("apps", []): + if not isinstance(a, dict) or "id" not in a or "name" not in a: + continue + a.setdefault("type", "pacman") + a.setdefault("category", "System") + lista.append(a) + lista.sort(key=lambda a: a["name"].lower()) + return lista + + +def _halmaz(parancs): + try: + kimenet = subprocess.run(parancs, capture_output=True, text=True, timeout=30) + except (OSError, subprocess.TimeoutExpired): + return set() + return {s.strip() for s in kimenet.stdout.splitlines() if s.strip()} + + +def telepitett_index(): + return { + "pacman": _halmaz(["pacman", "-Qq"]), + "flatpak": _halmaz(["flatpak", "list", "--app", "--columns=application"]), + } + + +def csomagok(app): + return app.get("packages") or [app["id"]] + + +def opcio_telepitve(opcio, index): + return all(p in index["pacman"] for p in (opcio.get("packages") or [opcio["id"]])) + + +def telepitve(app, index): + tipus = app["type"] + if tipus in ("pacman", "aur"): + return all(p in index["pacman"] for p in csomagok(app)) + if tipus == "flatpak-user": + return bool(app.get("flatpak_id")) and app["flatpak_id"] in index["flatpak"] + if tipus == "script": + ellenorzes = app.get("check_packages") + if ellenorzes: + return all(p in index["pacman"] for p in ellenorzes) + return False + if tipus == "choice": + return any(opcio_telepitve(o, index) for o in app.get("options", [])) + return False + + +def forras(app): + return {"flatpak-user": "flatpak", "aur": "aur", "message": "info"}.get(app["type"], "pacman") + + +def _konfig_lepesek(app): + lepesek = [] + otthon = str(Path.home()) + konfigok = adat_mappa() / "configs" + parok = [] + if app.get("config_dir") and app.get("config_dest"): + parok.append((app["config_dir"], app["config_dest"])) + for m in app.get("config_mappings", []): + if m.get("source") and m.get("dest"): + parok.append((m["source"], m["dest"])) + for forras_mappa, cel in parok: + honnan = konfigok / forras_mappa + if not honnan.is_dir(): + continue + cel = cel.replace("{HOME}", otthon) + kapcsolo = "-rf" if app.get("config_overwrite") else "-rn" + lepesek.append( + Lepes( + f"Beállítások: {app['name']}", + [ + "bash", + "-c", + f"mkdir -p {shlex.quote(cel)} && cp {kapcsolo} {shlex.quote(str(honnan))}/. {shlex.quote(cel)}/ " + f"&& rm -f {shlex.quote(cel)}/Singleton*", + ], + kotelezo=False, + ) + ) + return lepesek + + +def lepesek(app, telepites, opciok=None): + tipus = app["type"] + nev = app["name"] + lista = [] + if tipus in ("pacman", "aur") or tipus == "choice": + if tipus == "choice": + valasztott = [o for o in app.get("options", []) if o["id"] in (opciok or [])] + pkgs = [p for o in valasztott for p in (o.get("packages") or [o["id"]])] + else: + pkgs = csomagok(app) + if not pkgs: + return [] + if tipus == "aur": + parancs = ["yay", "-S" if telepites else "-Rns", "--noconfirm", "--color", "never", "--sudo", "pkexec"] + if telepites: + parancs += ["--needed", "--answerclean", "None", "--answerdiff", "None", "--answeredit", "None"] + lista.append(Lepes(f"{'Telepítés' if telepites else 'Eltávolítás'}: {nev}", parancs + pkgs)) + else: + lista.append( + Lepes(f"{'Telepítés' if telepites else 'Eltávolítás'}: {nev}", root("telepit" if telepites else "eltavolit", *pkgs)) + ) + if telepites: + for parancs in app.get("post_install", []): + reszek = shlex.split(parancs) + if reszek[:2] == ["systemctl", "enable"] and len(reszek) == 3: + lista.append(Lepes(f"Szolgáltatás bekapcsolása: {reszek[2]}", root("szolgaltatas", reszek[2]), kotelezo=False)) + else: + lista.append(Lepes(f"Utólagos lépés: {nev}", reszek, kotelezo=False)) + elif tipus == "flatpak-user": + fid = app.get("flatpak_id") + if not fid: + return [] + if telepites: + lista.append( + Lepes("Flathub tároló", ["flatpak", "remote-add", "--if-not-exists", "--user", "flathub", FLATHUB]) + ) + lista.append( + Lepes( + f"Telepítés: {nev}", + ["flatpak", "install", "--user", "--noninteractive", "--or-update", "flathub", fid], + ) + ) + for parancs in app.get("post_install", []): + lista.append(Lepes(f"Utólagos lépés: {nev}", shlex.split(parancs), kotelezo=False)) + else: + lista.append(Lepes(f"Eltávolítás: {nev}", ["flatpak", "uninstall", "--user", "-y", fid])) + elif tipus == "script": + szkript = app.get("script", "") + if not szkript: + return [] + lista.append( + Lepes(f"{'Telepítés' if telepites else 'Eltávolítás'}: {nev}", root("akcio", szkript, "install" if telepites else "remove")) + ) + if telepites and tipus != "script": + lista += _konfig_lepesek(app) + return lista + + +def eltavolithato(app): + return app["type"] != "message" and app.get("removable", True) diff --git a/raveos-center/raveos_center/mentesek.py b/raveos-center/raveos_center/mentesek.py new file mode 100644 index 0000000..7afae01 --- /dev/null +++ b/raveos-center/raveos_center/mentesek.py @@ -0,0 +1,156 @@ +import io +import json +import subprocess +import tarfile +import time +from pathlib import Path + +from raveos_center import katalogus +from raveos_center.muveletek import HELPER + +JEGYZEK = "raveos-mentes.json" +JEGYZEK_VERZIO = 1 + + +def csoportok(): + utvonal = katalogus.adat_mappa() / "mentes" / "backup-paths.json" + return json.loads(utvonal.read_text(encoding="utf-8")).get("groups", []) + + +def elemek(): + lista = [] + for csoport in csoportok(): + for elem in csoport.get("items", []): + lista.append(dict(elem, csoport=csoport["id"])) + return lista + + +def abszolut(elem): + if elem.get("scope") == "system": + return Path(elem["path"]) + return Path.home() / elem["path"] + + +class Megszakitva(Exception): + pass + + +def _pkexec_hiba(kimenet): + if kimenet.returncode in (126, 127): + raise Megszakitva("a hitelesítés elmaradt, a további rendszerelemek kimaradnak") + + +def _root_tar(elem_id): + kimenet = subprocess.run(["pkexec", HELPER, "mentes-root", elem_id], capture_output=True, timeout=600) + _pkexec_hiba(kimenet) + if kimenet.returncode != 0: + raise RuntimeError(kimenet.stderr.decode(errors="replace").strip()[:200] or "a rendszerelem mentése nem sikerült") + return kimenet.stdout + + +def mentes(cel, kivalasztott, naplo, haladas): + jegyzek = {"verzio": JEGYZEK_VERZIO, "letrehozva": int(time.time()), "elemek": []} + megszakitva = False + with tarfile.open(cel, "w:gz") as tar: + for i, elem in enumerate(kivalasztott): + ut = abszolut(elem) + naplo(f"+ {katalogus.loc(elem.get('label'))} ({ut})") + try: + if elem.get("scope") == "system": + if megszakitva: + naplo(" kihagyva, mert a hitelesítés elmaradt") + continue + adat = _root_tar(elem["id"]) + info = tarfile.TarInfo(f"rendszer/{elem['id']}.tar") + info.size = len(adat) + info.mtime = int(time.time()) + tar.addfile(info, io.BytesIO(adat)) + elif ut.exists(): + tar.add(ut.resolve() if ut.is_symlink() else ut, arcname=f"home/{elem['path']}") + else: + naplo(" nincs ilyen mappa ezen a gépen, kihagyva") + continue + jegyzek["elemek"].append( + {"id": elem["id"], "label": elem.get("label"), "scope": elem.get("scope", "home"), "path": elem["path"]} + ) + except Megszakitva as e: + naplo(f" MEGSZAKÍTVA: {e}") + megszakitva = True + except Exception as e: + naplo(f" HIBA, kihagyva: {e}") + haladas(int((i + 1) * 100 / max(1, len(kivalasztott)))) + adat = json.dumps(jegyzek, ensure_ascii=False, indent=2).encode() + info = tarfile.TarInfo(JEGYZEK) + info.size = len(adat) + info.mtime = int(time.time()) + tar.addfile(info, io.BytesIO(adat)) + return len(jegyzek["elemek"]) + + +def tartalom(forras): + with tarfile.open(forras, "r:gz") as tar: + try: + tag = tar.getmember(JEGYZEK) + except KeyError: + return _regi_tartalom(tar) + return json.load(tar.extractfile(tag))["elemek"] + + +def _regi_tartalom(tar): + nevek = {t.name.split("/")[0] for t in tar.getmembers()} + talalt = [] + for elem in elemek(): + if Path(elem["path"]).name in nevek: + talalt.append( + {"id": elem["id"], "label": elem.get("label"), "scope": elem.get("scope", "home"), "path": elem["path"], "regi": True} + ) + return talalt + + +def _home_tagok(tar, elem): + if elem.get("regi"): + elotag = Path(elem["path"]).name + cel_szulo = (Path.home() / elem["path"]).parent + else: + elotag = f"home/{elem['path']}" + cel_szulo = Path.home() + for tag in tar.getmembers(): + if tag.name == elotag or tag.name.startswith(elotag + "/"): + if not elem.get("regi"): + tag.name = tag.name[len("home/"):] + if tag.islnk() and tag.linkname.startswith("home/"): + tag.linkname = tag.linkname[len("home/"):] + yield tag, cel_szulo + + +def visszaallitas(forras, kivalasztott, naplo, haladas): + kesz = 0 + megszakitva = False + with tarfile.open(forras, "r:gz") as tar: + for i, elem in enumerate(kivalasztott): + naplo(f"< {katalogus.loc(elem.get('label'))}") + try: + if elem.get("scope") == "system": + if elem.get("regi"): + raise RuntimeError("régi formátumú mentésből a rendszerelemek nem állíthatók vissza") + adat = tar.extractfile(tar.getmember(f"rendszer/{elem['id']}.tar")).read() + if megszakitva: + naplo(" kihagyva, mert a hitelesítés elmaradt") + continue + kimenet = subprocess.run( + ["pkexec", HELPER, "visszaallit-root", elem["id"]], input=adat, capture_output=True, timeout=600 + ) + _pkexec_hiba(kimenet) + if kimenet.returncode != 0: + raise RuntimeError(kimenet.stderr.decode(errors="replace").strip()[:200] or "sikertelen") + else: + for tag, cel_szulo in _home_tagok(tar, elem): + tar.extract(tag, path=cel_szulo, filter="data") + kesz += 1 + except Megszakitva as e: + naplo(f" MEGSZAKÍTVA: {e}") + megszakitva = True + except Exception as e: + naplo(f" HIBA, kihagyva: {e}") + haladas(int((i + 1) * 100 / max(1, len(kivalasztott)))) + return kesz diff --git a/raveos-center/raveos_center/muveletek.py b/raveos-center/raveos_center/muveletek.py new file mode 100644 index 0000000..3243814 --- /dev/null +++ b/raveos-center/raveos_center/muveletek.py @@ -0,0 +1,107 @@ +import os +import shutil + +from PyQt6.QtCore import QObject, QProcess, QProcessEnvironment, pyqtSignal + +HELPER = os.environ.get("RAVEOS_CENTER_HELPER", "/usr/lib/raveos-center/raveos-center-helper") + + +def helper_elerheto(): + return os.access(HELPER, os.X_OK) and shutil.which("pkexec") is not None + + +def root(*args): + return ["pkexec", HELPER, *args] + + +class Lepes: + def __init__(self, cimke, parancs, kotelezo=True): + self.cimke = cimke + self.parancs = parancs + self.kotelezo = kotelezo + + +class Futtato(QObject): + sor = pyqtSignal(str) + lepes_indul = pyqtSignal(str) + kesz = pyqtSignal(bool, list) + + def __init__(self, szulo=None): + super().__init__(szulo) + self.lepesek = [] + self.hibasak = [] + self.folyamat = None + self.utolso_kod = None + self._maradek = b"" + + def fut(self): + return self.folyamat is not None + + def indit(self, lepesek): + if self.fut(): + return + self.lepesek = list(lepesek) + self.hibasak = [] + self._kovetkezo() + + def _kovetkezo(self): + if not self.lepesek: + self.folyamat = None + self.kesz.emit(not self.hibasak, self.hibasak) + return + lepes = self.lepesek.pop(0) + self.aktualis = lepes + self.lepes_indul.emit(lepes.cimke) + self.sor.emit(f":: {lepes.cimke}") + folyamat = QProcess(self) + folyamat.setProcessChannelMode(QProcess.ProcessChannelMode.MergedChannels) + kornyezet = QProcessEnvironment.systemEnvironment() + kornyezet.insert("LC_ALL", "C.UTF-8") + folyamat.setProcessEnvironment(kornyezet) + folyamat.readyReadStandardOutput.connect(self._olvas) + folyamat.finished.connect(self._vege) + folyamat.errorOccurred.connect(self._inditasi_hiba) + self._maradek = b"" + self.folyamat = folyamat + folyamat.start(lepes.parancs[0], lepes.parancs[1:]) + + def _olvas(self): + if self.folyamat is None: + return + adat = self._maradek + bytes(self.folyamat.readAllStandardOutput()) + *sorok, self._maradek = adat.replace(b"\r", b"\n").split(b"\n") + for s in sorok: + szoveg = s.decode("utf-8", errors="replace").rstrip() + if szoveg: + self.sor.emit(szoveg) + + def _inditasi_hiba(self, hiba): + if hiba == QProcess.ProcessError.FailedToStart: + self.sor.emit(f"[HIBA] nem indítható: {self.aktualis.parancs[0]}") + self._lezar(False) + + def _vege(self, kod, allapot): + self._olvas() + if self._maradek: + self.sor.emit(self._maradek.decode("utf-8", errors="replace").rstrip()) + self._maradek = b"" + rendben = allapot == QProcess.ExitStatus.NormalExit and kod == 0 + if not rendben and kod in (126, 127) and self.aktualis.parancs[0] == "pkexec": + self.sor.emit("[MEGSZAKÍTVA] a hitelesítés elmaradt") + self._lezar(rendben, kod) + + def _lezar(self, rendben, kod=None): + if self.folyamat is None: + return + self.utolso_kod = kod + self.folyamat.deleteLater() + self.folyamat = None + if rendben: + self.sor.emit(f"[OK] {self.aktualis.cimke}") + else: + self.sor.emit(f"[HIBA{f' {kod}' if kod is not None else ''}] {self.aktualis.cimke}") + self.hibasak.append(self.aktualis.cimke) + if self.aktualis.kotelezo: + self.lepesek = [] + self.folyamat = None + self._kovetkezo() diff --git a/raveos-center/raveos_center/oldalak/__init__.py b/raveos-center/raveos_center/oldalak/__init__.py new file mode 100644 index 0000000..36a9048 --- /dev/null +++ b/raveos-center/raveos_center/oldalak/__init__.py @@ -0,0 +1,2 @@ +FENT = ["attekintes", "alkalmazasok", "frissitesek", "rendszer", "visszajelzes"] +LENT = [] diff --git a/raveos-center/raveos_center/oldalak/alkalmazasok.py b/raveos-center/raveos_center/oldalak/alkalmazasok.py new file mode 100644 index 0000000..4ab95e6 --- /dev/null +++ b/raveos-center/raveos_center/oldalak/alkalmazasok.py @@ -0,0 +1,508 @@ +from PyQt6.QtCore import Qt +from PyQt6.QtWidgets import ( + QCheckBox, + QFrame, + QHBoxLayout, + QLineEdit, + QMessageBox, + QPushButton, + QVBoxLayout, + QWidget, +) + +from raveos_center import katalogus +from raveos_center.elemek import Kartya, Oldal, cimke, frissit_stilus, gomb +from raveos_center.folyamat import FolyamatAblak +from raveos_center.hatter import gyorsitott + +AZON = "alkalmazasok" +MENU = "Alkalmazások" +IKON = "alkalmazasok" + +CSAK_TELEPITETT = "telepitett" + + +class KattinthatoSor(QWidget): + def __init__(self, jelolo): + super().__init__() + self.jelolo = jelolo + self.setCursor(Qt.CursorShape.PointingHandCursor) + + def mouseReleaseEvent(self, esemeny): + if esemeny.button() == Qt.MouseButton.LeftButton and self.rect().contains(esemeny.position().toPoint()): + self.jelolo.toggle() + super().mouseReleaseEvent(esemeny) + + +class AppSor(QWidget): + def __init__(self, app, oldal): + super().__init__() + self.app = app + self.oldal = oldal + self.telepitve = False + self.setObjectName("appsor") + self.setAttribute(Qt.WidgetAttribute.WA_StyledBackground, True) + doboz = QVBoxLayout(self) + doboz.setContentsMargins(16, 12, 16, 12) + doboz.setSpacing(8) + + felso = QHBoxLayout() + felso.setSpacing(14) + self.jelolo = QCheckBox() + self.jelolo.setVisible(app["type"] != "message") + self.jelolo.toggled.connect(self._sor_jelolve) + felso.addWidget(self.jelolo) + betu = cimke(app["name"][:1].upper(), "kezdobetu") + betu.setAlignment(Qt.AlignmentFlag.AlignCenter) + betu.setFixedSize(36, 36) + felso.addWidget(betu) + szovegek = QVBoxLayout() + szovegek.setSpacing(2) + nevsor = QHBoxLayout() + nevsor.setSpacing(10) + nev = cimke(app["name"]) + nev.setStyleSheet("font-size: 14px; font-weight: 600;") + nevsor.addWidget(nev) + nevsor.addWidget(cimke(katalogus.forras(app), "monohalvany")) + self.allapot = cimke("TELEPÍTVE", "allapotcimke") + self.allapot.setProperty("szin", "zold") + self.allapot.hide() + nevsor.addWidget(self.allapot) + nevsor.addStretch(1) + szovegek.addLayout(nevsor) + leiras = katalogus.loc(app.get("desc")) + self.leiras = cimke(leiras, "halvany") + self.leiras.setToolTip(leiras) + self.leiras.setMinimumWidth(80) + szovegek.addWidget(self.leiras) + felso.addLayout(szovegek, 1) + + self.eltavolit_gomb = None + if app["type"] == "choice": + self.eltavolit_gomb = gomb("Eltávolítás", kicsi=True) + self.eltavolit_gomb.clicked.connect(lambda: self._opcio_muvelet(False)) + felso.addWidget(self.eltavolit_gomb) + self.gomb = gomb("", kicsi=True) + self.gomb.setMinimumWidth(110) + self.gomb.clicked.connect(self._kattint) + felso.addWidget(self.gomb) + doboz.addLayout(felso) + + self.opciok = [] + if app["type"] == "choice": + self._opcio_lista(doboz) + elif app["type"] != "message": + self.setCursor(Qt.CursorShape.PointingHandCursor) + self.frissit(oldal.index) + + def mouseReleaseEvent(self, esemeny): + if ( + esemeny.button() == Qt.MouseButton.LeftButton + and self.app["type"] not in ("choice", "message") + and self.rect().contains(esemeny.position().toPoint()) + ): + self.jelolo.toggle() + super().mouseReleaseEvent(esemeny) + + def resizeEvent(self, esemeny): + super().resizeEvent(esemeny) + teljes = self.leiras.toolTip() + self.leiras.setText( + self.leiras.fontMetrics().elidedText(teljes, Qt.TextElideMode.ElideRight, max(80, self.leiras.width())) + ) + + def _opcio_lista(self, doboz): + lista = QVBoxLayout() + lista.setContentsMargins(82, 4, 0, 0) + lista.setSpacing(6) + fej = QHBoxLayout() + fej.addWidget(cimke("VÁLASSZ, AKÁR TÖBBET IS", "monohalvany")) + fej.addStretch(1) + mind = gomb("Mind kijelölése", kicsi=True) + mind.clicked.connect(self._mind) + fej.addWidget(mind) + lista.addLayout(fej) + for o in self.app.get("options", []): + jelolo = QCheckBox(o["name"]) + jelolo.setMinimumWidth(130) + jelolo.toggled.connect(lambda _: self._gombok_opcio()) + kontener = KattinthatoSor(jelolo) + sor = QHBoxLayout(kontener) + sor.setContentsMargins(0, 2, 0, 2) + sor.setSpacing(10) + sor.addWidget(jelolo) + leiras = cimke(katalogus.loc(o.get("desc")), "halvany") + sor.addWidget(leiras, 1) + allapot = cimke("TELEPÍTVE", "allapotcimke") + allapot.setProperty("szin", "zold") + sor.addWidget(allapot) + lista.addWidget(kontener) + self.opciok.append((jelolo, allapot, o)) + doboz.addLayout(lista) + + def _mind(self): + mind = all(j.isChecked() for j, _, _ in self.opciok) + for j, _, _ in self.opciok: + j.setChecked(not mind) + + def _sor_jelolve(self, jelolve): + if self.opciok: + for j, _, _ in self.opciok: + j.blockSignals(True) + j.setChecked(jelolve) + j.blockSignals(False) + self._gombok_opcio() + self.oldal.kijeles_valtozott() + + def torol_kijeles(self): + self.jelolo.setChecked(False) + + def telepitendo(self): + if self.app["type"] == "choice": + opciok = self._kijelolt(False) + return (self.app, opciok) if opciok else None + if self.jelolo.isChecked() and not self.telepitve and self.app["type"] != "message": + return (self.app, None) + return None + + def eltavolitando(self): + if self.app["type"] == "choice": + opciok = self._kijelolt(True) + return (self.app, opciok) if opciok else None + if self.jelolo.isChecked() and self.telepitve and katalogus.eltavolithato(self.app): + return (self.app, None) + return None + + def _kijelolt(self, telepitett): + return [ + o["id"] + for j, _, o in self.opciok + if j.isChecked() and katalogus.opcio_telepitve(o, self.oldal.index) == telepitett + ] + + def _gombok_opcio(self): + uj = self._kijelolt(False) + regi = self._kijelolt(True) + self.jelolo.blockSignals(True) + self.jelolo.setChecked(any(j.isChecked() for j, _, _ in self.opciok)) + self.jelolo.blockSignals(False) + self.oldal.kijeles_valtozott() + self.gomb.setText(f"Telepítés ({len(uj)})" if uj else "Telepítés") + self.gomb.setEnabled(bool(uj)) + self.gomb.setProperty("fo", True) + frissit_stilus(self.gomb) + self.eltavolit_gomb.setText(f"Eltávolítás ({len(regi)})" if regi else "Eltávolítás") + self.eltavolit_gomb.setEnabled(bool(regi)) + + def _opcio_muvelet(self, telepites): + kijelolt = self._kijelolt(not telepites) + if kijelolt: + self.oldal.muvelet(self.app, telepites, kijelolt) + + def frissit(self, index): + if self.app["type"] == "choice": + db = 0 + for _, allapot, o in self.opciok: + fent = katalogus.opcio_telepitve(o, index) + allapot.setVisible(fent) + db += fent + self.telepitve = db > 0 + self.allapot.setText(f"{db} / {len(self.opciok)} TELEPÍTVE") + self.allapot.setVisible(db > 0) + self._gombok_opcio() + return + if self.app["type"] == "message": + self.gomb.setText("Tudnivalók") + self.gomb.setProperty("fo", False) + else: + self.telepitve = katalogus.telepitve(self.app, index) + self.allapot.setVisible(self.telepitve) + if self.telepitve: + self.gomb.setText("Eltávolítás") + self.gomb.setEnabled(katalogus.eltavolithato(self.app)) + self.gomb.setProperty("fo", False) + else: + self.gomb.setText("Telepítés") + self.gomb.setEnabled(True) + self.gomb.setProperty("fo", True) + frissit_stilus(self.gomb) + + def _kattint(self): + if self.app["type"] == "message": + QMessageBox.information(self, self.app["name"], katalogus.loc(self.app.get("message"))) + return + if self.app["type"] == "choice": + self._opcio_muvelet(True) + return + self.oldal.muvelet(self.app, not self.telepitve) + + +class Csoport(QWidget): + def __init__(self, nev): + super().__init__() + self.nev = nev + doboz = QVBoxLayout(self) + doboz.setContentsMargins(0, 0, 0, 0) + doboz.setSpacing(10) + self.fejlec = cimke("", "h2") + doboz.addWidget(self.fejlec) + self.lista = Kartya(terkoz=0, margo=0) + doboz.addWidget(self.lista) + self.sorok = [] + + def hozzaad(self, sor): + self.sorok.append(sor) + self.lista.elrendezes.addWidget(sor) + + def szur(self, lathatok): + db = 0 + utolso = None + for sor in self.sorok: + lathato = sor in lathatok + sor.setVisible(lathato) + sor.setProperty("utolso", False) + if lathato: + db += 1 + utolso = sor + if utolso: + utolso.setProperty("utolso", True) + for sor in self.sorok: + frissit_stilus(sor) + self.fejlec.setText(f"{self.nev.upper()} · {db}") + self.setVisible(db > 0) + return db + + +class Alkalmazasok(Oldal): + def __init__(self, kozpont): + super().__init__(kozpont, "PACMAN · AUR · FLATPAK", "Alkalmazások") + self.katalogus = [] + self.index = {"pacman": set(), "flatpak": set()} + self.sorok = [] + self.csoportok = {} + self.kategoria = "all" + + self.kereso = QLineEdit() + self.kereso.setPlaceholderText("Keresés, pl. Steam, OBS, Discord") + self.kereso.setClearButtonEnabled(True) + self.kereso.setMinimumWidth(220) + self.kereso.setMaximumWidth(340) + self.kereso.textChanged.connect(self._szur) + self.fejlec_gomb(self.kereso) + + pillek = QHBoxLayout() + pillek.setSpacing(8) + self.pillek = {} + for azon, nev in katalogus.KATEGORIAK + [(CSAK_TELEPITETT, "Csak telepítettek")]: + p = QPushButton(nev) + p.setObjectName("pill") + p.setCursor(Qt.CursorShape.PointingHandCursor) + p.clicked.connect(lambda _=False, a=azon: self._kategoria(a)) + self.pillek[azon] = p + if azon == CSAK_TELEPITETT: + pillek.addStretch(1) + pillek.addWidget(p) + self.torzs.addLayout(pillek) + + self.kiemelt = Kartya(terkoz=8, margo=24) + self.kiemelt.setObjectName("kiemelt") + ks = QHBoxLayout() + ks.setSpacing(24) + kszoveg = QVBoxLayout() + kszoveg.setSpacing(6) + kszoveg.addWidget(cimke("CSOMAG · GAMING ALAPOK", "eyebrow")) + kszoveg.addWidget(cimke("EGY KATTINTÁS, MINDEN AMI JÁTÉKHOZ KELL", "h2", True)) + kszoveg.addWidget( + cimke("Steam (Proton-GE-vel), Lutris, Heroic, GameMode és MangoHud egyben, előre beállítva.", "masodlagos", True) + ) + ks.addLayout(kszoveg, 1) + self.kiemelt_gomb = gomb("Csomag telepítése", fo=True) + self.kiemelt_gomb.clicked.connect(self._gaming_alapok) + ks.addWidget(self.kiemelt_gomb, 0, Qt.AlignmentFlag.AlignVCenter) + self.kiemelt.elrendezes.addLayout(ks) + self.torzs.addWidget(self.kiemelt) + + self.hiba = cimke("", "figyelem", True) + self.hiba.hide() + self.torzs.addWidget(self.hiba) + self._kijeles_sav() + self.lista = QVBoxLayout() + self.lista.setSpacing(28) + self.torzs.addLayout(self.lista) + self.ures = cimke("Nincs találat.", "halvany") + self.ures.hide() + self.torzs.addWidget(self.ures) + self.torzs.addStretch(1) + self._betolt() + self._kategoria("all") + + def _kijeles_sav(self): + self.sav = QFrame(self) + self.sav.setObjectName("kijelessav") + s = QHBoxLayout(self.sav) + s.setContentsMargins(48, 12, 48, 12) + s.setSpacing(8) + self.sav_szoveg = cimke("", "mono") + s.addWidget(self.sav_szoveg) + s.addStretch(1) + torles = gomb("Kijelölés törlése", kicsi=True) + torles.clicked.connect(self._kijeles_torles) + s.addWidget(torles) + self.sav_eltavolit = gomb("Kijelöltek eltávolítása") + self.sav_eltavolit.clicked.connect(lambda: self._tomeges(False)) + s.addWidget(self.sav_eltavolit) + self.sav_telepit = gomb("Kijelöltek telepítése", fo=True) + self.sav_telepit.clicked.connect(lambda: self._tomeges(True)) + s.addWidget(self.sav_telepit) + self.sav.hide() + + def resizeEvent(self, esemeny): + super().resizeEvent(esemeny) + magassag = self.sav.sizeHint().height() + self.sav.setGeometry(0, self.height() - magassag, self.width(), magassag) + + def kijeles_valtozott(self): + if not hasattr(self, "sav"): + return + uj = [x for x in (s.telepitendo() for s in self.sorok) if x] + regi = [x for x in (s.eltavolitando() for s in self.sorok) if x] + kijelolt = sum(1 for s in self.sorok if s.jelolo.isChecked()) + latszik = kijelolt > 0 + self.sav.setVisible(latszik) + self.setViewportMargins(0, 0, 0, self.sav.sizeHint().height() if latszik else 0) + self.sav_szoveg.setText(f"{kijelolt} KIJELÖLVE") + self.sav_telepit.setText(f"Kijelöltek telepítése ({len(uj)})") + self.sav_telepit.setEnabled(bool(uj)) + self.sav_eltavolit.setText(f"Kijelöltek eltávolítása ({len(regi)})") + self.sav_eltavolit.setEnabled(bool(regi)) + if latszik: + self.sav.raise_() + magassag = self.sav.sizeHint().height() + self.sav.setGeometry(0, self.height() - magassag, self.width(), magassag) + + def _kijeles_torles(self): + for s in self.sorok: + s.torol_kijeles() + + def _nev(self, app, opciok): + if opciok: + return ", ".join(f"{app['name']}: {o['name']}" for o in app.get("options", []) if o["id"] in opciok) + return app["name"] + + def _tomeges(self, telepites): + tetelek = [x for x in ((s.telepitendo() if telepites else s.eltavolitando()) for s in self.sorok) if x] + if not tetelek: + return + nevek = [self._nev(app, opciok) for app, opciok in tetelek] + if not telepites: + valasz = QMessageBox.question( + self, + "Eltávolítás", + "Biztosan eltávolítod ezeket?\n\n" + "\n".join(nevek), + QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No, + QMessageBox.StandardButton.No, + ) + if valasz != QMessageBox.StandardButton.Yes: + return + lepesek = [] + for app, opciok in tetelek: + for lepes in katalogus.lepesek(app, telepites, opciok): + lepes.kotelezo = False + lepesek.append(lepes) + if not lepesek: + return + cim = f"{'Telepítés' if telepites else 'Eltávolítás'}: {len(tetelek)} alkalmazás" + FolyamatAblak(self, cim, lepesek, "Minden lépés sikeresen lefutott.", "Nem minden lépés sikerült.").exec() + self._kijeles_torles() + self.megjelenik() + + def _betolt(self): + try: + self.katalogus = katalogus.betolt() + except (OSError, ValueError, RuntimeError) as e: + self.hiba.setText(f"Az alkalmazáskatalógus nem tölthető be: {e}") + self.hiba.show() + return + for azon, nev in katalogus.KATEGORIAK[1:]: + csoport = Csoport(nev) + self.csoportok[azon] = csoport + self.lista.addWidget(csoport) + for app in self.katalogus: + csoport = self.csoportok.get(app["category"]) or self.csoportok["System"] + sor = AppSor(app, self) + self.sorok.append(sor) + csoport.hozzaad(sor) + + def megjelenik(self, *_): + gyorsitott("telepitett", katalogus.telepitett_index, kesz=self._index_kesz, kor=30) + + def _index_kesz(self, index): + if index == self.index: + return + self.index = index + for s in self.sorok: + s.frissit(index) + alap = [a for a in self.katalogus if a["id"] in katalogus.GAMING_ALAPOK] + hianyzik = [a for a in alap if not katalogus.telepitve(a, index)] + self.kiemelt_gomb.setEnabled(bool(hianyzik)) + self.kiemelt_gomb.setText("Csomag telepítése" if hianyzik else "Minden telepítve") + self._szur() + + def _kategoria(self, azon): + self.kategoria = azon + for a, p in self.pillek.items(): + p.setProperty("aktiv", a == azon) + frissit_stilus(p) + self._szur() + + def _szur(self): + szoveg = self.kereso.text().strip().lower() + lathatok = set() + for s in self.sorok: + app = s.app + if self.kategoria == CSAK_TELEPITETT: + jo = s.telepitve + else: + jo = self.kategoria == "all" or app["category"] == self.kategoria + if jo and (not szoveg or szoveg in app["name"].lower() or szoveg in katalogus.loc(app.get("desc")).lower()): + lathatok.add(s) + osszes = sum(c.szur(lathatok) for c in self.csoportok.values()) + self.ures.setText( + "Még nincs telepítve egyetlen alkalmazás sem a listából." + if self.kategoria == CSAK_TELEPITETT and not szoveg + else "Nincs találat." + ) + self.ures.setVisible(osszes == 0) + self.kiemelt.setVisible(self.kategoria in ("all", "Games") and not szoveg) + + def muvelet(self, app, telepites, opciok=None): + if not telepites: + nevek = ", ".join(o["name"] for o in app.get("options", []) if o["id"] in (opciok or [])) or app["name"] + valasz = QMessageBox.question( + self, + "Eltávolítás", + f"Biztosan eltávolítod: {nevek}?", + QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No, + QMessageBox.StandardButton.No, + ) + if valasz != QMessageBox.StandardButton.Yes: + return + lepesek = katalogus.lepesek(app, telepites, opciok) + if not lepesek: + return + cim = f"{'Telepítés' if telepites else 'Eltávolítás'}: {app['name']}" + FolyamatAblak(self, cim, lepesek, f"{app['name']} {'telepítve' if telepites else 'eltávolítva'}.").exec() + self.megjelenik() + + def _gaming_alapok(self): + lepesek = [] + for app in self.katalogus: + if app["id"] in katalogus.GAMING_ALAPOK and not katalogus.telepitve(app, self.index): + lepesek += katalogus.lepesek(app, True) + if not lepesek: + return + FolyamatAblak(self, "Gaming alapok telepítése", lepesek, "A Gaming alapok telepítve.").exec() + self.megjelenik() + + +OLDAL = Alkalmazasok diff --git a/raveos-center/raveos_center/oldalak/attekintes.py b/raveos-center/raveos_center/oldalak/attekintes.py new file mode 100644 index 0000000..9ebc848 --- /dev/null +++ b/raveos-center/raveos_center/oldalak/attekintes.py @@ -0,0 +1,262 @@ +from datetime import datetime + +from PyQt6.QtCore import Qt, QUrl +from PyQt6.QtGui import QDesktopServices +from PyQt6.QtWidgets import QHBoxLayout, QMessageBox, QProgressBar, QPushButton, QVBoxLayout, QWidget + +from raveos_center import gaming, muveletek, rendszerinfo +from raveos_center.elemek import Kartya, Oldal, Racs, cimke, frissit_stilus, gomb +from raveos_center.hatter import ervenytelenit, gyorsitott, tarolt + +AZON = "attekintes" +MENU = "Áttekintés" +IKON = "attekintes" + +GYORS = [ + ("01", "Alkalmazás telepítése", "Böngészés kategóriák szerint", "alkalmazasok", ()), + ("02", "Rendszerfrissítés", "pacman + AUR + Flatpak", "frissitesek", ()), + ("03", "Driverek", "GPU, Wi-Fi, kontrollerek", "rendszer", ("driverek",)), + ("04", "Takarítás", "Cache, árva csomagok", "rendszer", ("karbantartas",)), + ("05", "Gaming tweakek", "GameMode, Proton, MangoHud", "rendszer", ("gaming",)), + ("06", "Visszaállítás", "Snapshot betöltése", "rendszer", ("snapshotok",)), +] + +KOZOSSEG = [ + ("rp1.hu", "https://rp1.hu"), + ("Discord", "https://discord.gg/gSdVMXRFQc"), + ("Stream", "https://kick.com/rpslair"), +] + + +class Csempe(QPushButton): + def __init__(self, kod, cim, alcim): + super().__init__() + self.setObjectName("csempe") + self.setCursor(Qt.CursorShape.PointingHandCursor) + doboz = QVBoxLayout(self) + doboz.setContentsMargins(18, 18, 18, 18) + doboz.setSpacing(4) + self.kod = cimke(kod, "monozold") + doboz.addWidget(self.kod) + doboz.addStretch(1) + self.cim = cimke(cim) + self.cim.setStyleSheet("font-size: 15px; font-weight: 600;") + doboz.addWidget(self.cim) + self.alcim = cimke(alcim, "halvany", True) + doboz.addWidget(self.alcim) + for c in (self.kod, self.cim, self.alcim): + c.setAttribute(Qt.WidgetAttribute.WA_TransparentForMouseEvents) + + +def allapotkartya(cim): + kartya = Kartya(cim) + ertek = cimke("...", "nagyszam") + kartya.elrendezes.addWidget(ertek) + return kartya, ertek + + +class Attekintes(Oldal): + def __init__(self, kozpont): + super().__init__(kozpont, "ÜDV ÚJRA", "Áttekintés") + self.frissites_gomb = self.fejlec_gomb(gomb("Frissítés most", fo=True)) + self.frissites_gomb.clicked.connect(lambda: self.kozpont.mutat("frissitesek")) + self.snapshot_gomb = self.fejlec_gomb(gomb("Snapshot készítése")) + self.snapshot_gomb.clicked.connect(lambda: self.kozpont.mutat("rendszer", "snapshotok", "uj")) + + allapot = Racs(220) + k, self.frissites_szam = allapotkartya("Frissítések") + self.frissites_szam.setProperty("zold", True) + self.frissites_reszlet = cimke("ellenőrzés...", "masodlagos") + k.elrendezes.addWidget(self.frissites_reszlet) + allapot.hozzaad(k) + + k, self.snapshot_ertek = allapotkartya("Utolsó snapshot") + self.snapshot_reszlet = cimke("", "masodlagos") + k.elrendezes.addWidget(self.snapshot_reszlet) + allapot.hozzaad(k) + + k, self.lemez_ertek = allapotkartya("Lemez") + self.lemez_sav = QProgressBar() + self.lemez_sav.setObjectName("sav") + self.lemez_sav.setTextVisible(False) + self.lemez_sav.setFixedHeight(6) + k.elrendezes.addWidget(self.lemez_sav) + allapot.hozzaad(k) + + k, self.gaming_ertek = allapotkartya("Gaming mód") + self.gaming_adat = None + self.gaming_gomb = gomb("...", kicsi=True) + self.gaming_gomb.clicked.connect(self._gaming_valt) + k.elrendezes.addWidget(self.gaming_gomb, 0, Qt.AlignmentFlag.AlignLeft) + allapot.hozzaad(k) + self.torzs.addWidget(allapot) + + also = QHBoxLayout() + also.setSpacing(24) + bal = QVBoxLayout() + bal.setSpacing(16) + bal.addWidget(cimke("GYORS MŰVELETEK", "h2")) + self.gyors = Racs(180, 12) + self.csempek = [] + for kod, cim, alcim, cel, al in GYORS: + csempe = Csempe(kod, cim, alcim) + csempe.clicked.connect(lambda _=False, c=cel, a=al: self.kozpont.mutat(c, *a)) + self.csempek.append((csempe, cel, al)) + self.gyors.hozzaad(csempe) + bal.addWidget(self.gyors) + bal.addStretch(1) + also.addLayout(bal, 2) + + jobb = QVBoxLayout() + jobb.setSpacing(16) + jobb.addWidget(cimke("RENDSZER", "h2")) + self.info = Kartya(terkoz=0, margo=20) + self.info.elrendezes.setContentsMargins(20, 8, 20, 8) + self.info_sorok = {} + for kulcs in ("Kernel", "Asztal", "CPU", "GPU", "Memória", "Fájlrendszer", "Uptime"): + sor = QWidget() + sl = QHBoxLayout(sor) + sl.setContentsMargins(0, 12, 0, 12) + sl.addWidget(cimke(kulcs, "monohalvany")) + sl.addStretch(1) + ertek = cimke("...", "mono") + ertek.setAlignment(Qt.AlignmentFlag.AlignRight) + ertek.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse) + sl.addWidget(ertek) + self.info_sorok[kulcs] = ertek + self.info.elrendezes.addWidget(sor) + jobb.addWidget(self.info) + + kozosseg = Kartya("Közösség", terkoz=12) + nev = cimke("RaveCore") + nev.setStyleSheet("font-size: 15px; font-weight: 600;") + kozosseg.elrendezes.addWidget(nev) + kozosseg.elrendezes.addWidget( + cimke("Kérdésed van, vagy hibát találtál? Gyere a közösségbe, vagy írj nekünk hibajegyet.", "masodlagos", True) + ) + linkek = QHBoxLayout() + linkek.setSpacing(16) + for szoveg, cim in KOZOSSEG: + link = cimke(f'<a href="{cim}" style="color:#62a052; text-decoration:none">{szoveg}</a>', "link") + link.setOpenExternalLinks(True) + linkek.addWidget(link) + self.hibajegy_link = cimke('<a href="#" style="color:#62a052; text-decoration:none">Hibajegy</a>', "link") + self.hibajegy_link.linkActivated.connect(lambda _: self.kozpont.mutat("visszajelzes")) + linkek.addWidget(self.hibajegy_link) + linkek.addStretch(1) + kozosseg.elrendezes.addLayout(linkek) + jobb.addWidget(kozosseg) + jobb.addStretch(1) + also.addLayout(jobb, 1) + self.torzs.addLayout(also) + self.torzs.addStretch(1) + + def megjelenik(self, *_): + self.frissites_gomb.setEnabled(self.kozpont.van("frissitesek")) + self.snapshot_gomb.setEnabled(self.kozpont.van("rendszer", "snapshotok")) + if tarolt("gaming") is None: + self.gaming_gomb.setEnabled(False) + gyorsitott("gaming", gaming.allapot, kesz=self._gaming_kesz, kor=30) + for csempe, cel, al in self.csempek: + elerheto = self.kozpont.van(cel, *al) + csempe.setEnabled(elerheto) + csempe.setToolTip("" if elerheto else "Hamarosan") + self._lemez() + gyorsitott("info", self._info_gyujt, kesz=self._info_kesz, kor=300) + gyorsitott("frissitesek", rendszerinfo.frissitesek, kesz=self._frissites_kesz, hiba=self._frissites_hiba, kor=60) + gyorsitott("snapshot", rendszerinfo.utolso_snapshot, kesz=self._snapshot_kesz, kor=60) + + def _gaming_kesz(self, adat): + self.gaming_adat = adat + aktiv = gaming.gaming_mod_aktiv(adat) + self.gaming_ertek.setText("BEKAPCSOLVA" if aktiv else "KIKAPCSOLVA") + self.gaming_ertek.setProperty("zold", aktiv) + frissit_stilus(self.gaming_ertek) + self.gaming_gomb.setText("Kikapcsolás" if aktiv else "Bekapcsolás") + self.gaming_gomb.setProperty("fo", not aktiv) + frissit_stilus(self.gaming_gomb) + valtozas = gaming.gaming_mod_valtozasok(adat, not aktiv) + self.gaming_gomb.setEnabled(bool(valtozas) and muveletek.helper_elerheto()) + self.gaming_gomb.setToolTip( + "" if valtozas else "Ezen a gépen a Gaming mód elemei nem kapcsolhatók ki, a GameMode a Rendszer > Gaming fülön távolítható el." + ) + + def _gaming_valt(self): + if not self.gaming_adat: + return + from raveos_center.oldalak.rendszer.gaming import alkalmaz + + self.gaming_gomb.setEnabled(False) + aktiv = gaming.gaming_mod_aktiv(self.gaming_adat) + hiba = alkalmaz(self, gaming.gaming_mod_valtozasok(self.gaming_adat, not aktiv)) + if hiba: + QMessageBox.warning(self, "Gaming mód", hiba) + ervenytelenit("gaming") + gyorsitott("gaming", gaming.allapot, kesz=self._gaming_kesz) + + def _lemez(self): + hasznalt, ossz = rendszerinfo.lemez() + self.lemez_ertek.setText(f"{hasznalt / 1e9:.0f} / {ossz / 1e9:.0f} GB") + self.lemez_sav.setValue(int(hasznalt * 100 / ossz) if ossz else 0) + + @staticmethod + def _info_gyujt(): + gpu = ", ".join(rendszerinfo.gpuk()) or "ismeretlen" + driver = ", ".join(rendszerinfo.gpu_driverek()) + return { + "Kernel": rendszerinfo.kernel(), + "Asztal": rendszerinfo.asztal(), + "CPU": rendszerinfo.cpu(), + "GPU": f"{gpu} · {driver}" if driver else gpu, + "Memória": rendszerinfo.memoria(), + "Fájlrendszer": rendszerinfo.fajlrendszer(), + "Uptime": rendszerinfo.uzemido(), + } + + def _info_kesz(self, adat): + for kulcs, ertek in adat.items(): + self.info_sorok[kulcs].setText(ertek) + + def _frissites_kesz(self, csomagok): + szam = len(csomagok) + self.kozpont.jelveny("frissitesek", szam) + if not szam: + self.frissites_szam.setText("NAPRAKÉSZ") + self.frissites_reszlet.setText("nincs elérhető frissítés") + return + self.frissites_szam.setText(f"{szam} ELÉRHETŐ") + reszek = [] + ujrainditos = sum(1 for c in csomagok if c["ujrainditas"]) + aur = sum(1 for c in csomagok if c["forras"] == "aur") + flatpak = sum(1 for c in csomagok if c["forras"] == "flatpak") + if ujrainditos: + reszek.append(f"{ujrainditos} újraindítást igényel") + if aur: + reszek.append(f"{aur} AUR") + if flatpak: + reszek.append(f"{flatpak} Flatpak") + self.frissites_reszlet.setText(" · ".join(reszek) or "csak hivatalos csomagok") + + def _frissites_hiba(self, _): + self.frissites_szam.setText("-") + self.frissites_reszlet.setText("az ellenőrzés nem sikerült") + + def _snapshot_kesz(self, snapshot): + if snapshot is None: + self.snapshot_ertek.setText("-") + self.snapshot_reszlet.setText("a snapper nem érhető el") + return + if not snapshot: + self.snapshot_ertek.setText("NINCS") + self.snapshot_reszlet.setText("még nem készült snapshot") + return + try: + ido = datetime.fromisoformat(snapshot.get("date", "")) + nap = (datetime.now() - ido).days + self.snapshot_ertek.setText("MA" if nap == 0 else f"{nap} NAPJA") + except (TypeError, ValueError): + self.snapshot_ertek.setText("?") + self.snapshot_reszlet.setText(f"btrfs · {snapshot.get('description') or snapshot.get('type', '')}") + + +OLDAL = Attekintes diff --git a/raveos-center/raveos_center/oldalak/frissitesek.py b/raveos-center/raveos_center/oldalak/frissitesek.py new file mode 100644 index 0000000..baa1ccc --- /dev/null +++ b/raveos-center/raveos_center/oldalak/frissitesek.py @@ -0,0 +1,331 @@ +from datetime import datetime + +from PyQt6.QtCore import Qt +from PyQt6.QtWidgets import ( + QAbstractItemView, + QCheckBox, + QHBoxLayout, + QHeaderView, + QPlainTextEdit, + QTableWidget, + QTableWidgetItem, + QVBoxLayout, + QWidget, +) + +from raveos_center import muveletek, rendszerinfo, snapshotok +from raveos_center.elemek import Kartya, Oldal, Racs, cimke, gomb +from raveos_center.hatter import ervenytelenit, hatterben + +AZON = "frissitesek" +MENU = "Frissítések" +IKON = "frissitesek" + +FORRAS = {"pacman": "pacman", "aur": "AUR", "flatpak": "Flatpak"} + + +def mb(meret): + if meret is None: + return "-" + return f"{meret / 1e6:.1f} MB" + + +class Frissitesek(Oldal): + def __init__(self, kozpont): + super().__init__(kozpont, "UTOLSÓ ELLENŐRZÉS: -", "Frissítések", kitolt=True) + self.csomagok = [] + self.ujrainditas_kell = False + self.futtato = muveletek.Futtato(self) + self.futtato.sor.connect(self._naplo) + self.futtato.kesz.connect(self._futas_kesz) + + self.torzs.setSpacing(20) + self.ellenorzes = self.fejlec_gomb(gomb("Ellenőrzés")) + self.ellenorzes.clicked.connect(lambda: self._ellenoriz(True)) + self.inditas = self.fejlec_gomb(gomb("Nincs kijelölve", fo=True)) + self.inditas.clicked.connect(self._indit) + + osszegzes = Racs(220) + k = Kartya("Kijelölve") + self.kijelolt = cimke("0 / 0 CSOMAG", "nagyszam") + k.elrendezes.addWidget(self.kijelolt) + osszegzes.hozzaad(k) + k = Kartya("Letöltés") + self.letoltes = cimke("0 MB", "nagyszam") + k.elrendezes.addWidget(self.letoltes) + osszegzes.hozzaad(k) + k = Kartya("Biztonság") + self.snapshot = QCheckBox("Snapshot készítése frissítés előtt") + self.snapshot.setChecked(True) + k.elrendezes.addWidget(self.snapshot) + self.snapshot_megj = cimke("", "halvany", True) + k.elrendezes.addWidget(self.snapshot_megj) + osszegzes.hozzaad(k) + self.torzs.addWidget(osszegzes) + + self.figyelem = cimke("", "figyelem", True) + self.figyelem.hide() + self.torzs.addWidget(self.figyelem) + self.ujraindito_sav = QWidget() + us = QHBoxLayout(self.ujraindito_sav) + us.setContentsMargins(0, 0, 0, 0) + us.setSpacing(12) + us.addWidget( + cimke("A frissítés után újraindítás szükséges, hogy az új kernel vagy driver betöltődjön.", "figyelem", True), + 1, + ) + ujraindit = gomb("Újraindítás most", fo=True) + ujraindit.clicked.connect(self._ujraindit) + us.addWidget(ujraindit) + self.ujraindito_sav.hide() + self.torzs.addWidget(self.ujraindito_sav) + + csomag_resz = QVBoxLayout() + csomag_resz.setSpacing(12) + fej = QHBoxLayout() + fej.addWidget(cimke("CSOMAGOK", "h2")) + fej.addStretch(1) + self.mind = gomb("Mind kijelölése", kicsi=True) + self.mind.clicked.connect(self._mind) + fej.addWidget(self.mind) + csomag_resz.addLayout(fej) + csomag_resz.addWidget( + cimke( + "A hivatalos csomagok (pacman) csak együtt frissíthetők: Arch-alapú rendszeren a részleges frissítés " + "elronthatja a rendszert. Az AUR és a Flatpak csomagok egyenként is kijelölhetők.", + "halvany", + True, + ) + ) + self.tabla = QTableWidget(0, 6) + self.tabla.setHorizontalHeaderLabels(["", "Csomag", "Forrás", "Jelenlegi", "Új", "Méret"]) + self.tabla.verticalHeader().hide() + self.tabla.setShowGrid(False) + self.tabla.setSelectionMode(QAbstractItemView.SelectionMode.NoSelection) + self.tabla.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers) + self.tabla.setFocusPolicy(Qt.FocusPolicy.NoFocus) + fejlec = self.tabla.horizontalHeader() + fejlec.setSectionResizeMode(QHeaderView.ResizeMode.ResizeToContents) + fejlec.setSectionResizeMode(1, QHeaderView.ResizeMode.Stretch) + fejlec.setDefaultAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter) + self.tabla.setMinimumHeight(120) + self.tabla.itemChanged.connect(self._valtozott) + csomag_resz.addWidget(self.tabla, 1) + self.ures = cimke("", "masodlagos", True) + self.ures.hide() + csomag_resz.addWidget(self.ures) + self.torzs.addLayout(csomag_resz, 3) + + naplo_resz = QVBoxLayout() + naplo_resz.setSpacing(12) + naplo_resz.addWidget(cimke("NAPLÓ", "h2")) + self.naplo = QPlainTextEdit() + self.naplo.setObjectName("naplo") + self.naplo.setReadOnly(True) + self.naplo.setMaximumBlockCount(5000) + self.naplo.setMinimumHeight(90) + naplo_resz.addWidget(self.naplo, 1) + self.torzs.addLayout(naplo_resz, 2) + + def indulaskor(self): + self._ellenoriz(False) + + def megjelenik(self, *_): + snapper = rendszerinfo.snapper_elerheto() + automatikus = snapper and snapshotok.snap_pac() + self.snapshot.setVisible(not automatikus) + self.snapshot.setEnabled(snapper and not automatikus and not self.futtato.fut()) + if automatikus: + self.snapshot.setChecked(False) + self.snapshot_megj.setText( + "Minden csomagművelet előtt automatikusan snapshot készül, ha gond van, a Rendszer > Snapshotok " + "fülön vissza tudsz állni." + ) + elif not snapper: + self.snapshot.setChecked(False) + self.snapshot_megj.setText("Snapshot csak btrfs fájlrendszeren, beállított snapperrel készíthető.") + else: + self.snapshot_megj.setText("Ha a frissítés után gond van, erre vissza tudsz állni.") + if not muveletek.helper_elerheto(): + self.figyelem.setText( + "A RaveOS Center rendszerhelpere nincs telepítve, ezért a rendszercsomagok frissítése nem indítható." + ) + self.figyelem.show() + if not self.futtato.fut() and not self.csomagok: + self._ellenoriz(False) + + def _naplo(self, szoveg): + self.naplo.appendPlainText(szoveg) + + def _ellenoriz(self, friss): + if self.futtato.fut(): + return + self.ellenorzes.setEnabled(False) + self.ellenorzes.setText("Ellenőrzés...") + self._naplo(":: Frissítések ellenőrzése (pacman, AUR, Flatpak)...") + hatterben(rendszerinfo.frissitesek, friss, kesz=self._ellenorzes_kesz, hiba=self._ellenorzes_hiba) + + def _ellenorzes_vege(self): + self.ellenorzes.setEnabled(True) + self.ellenorzes.setText("Ellenőrzés") + mikor = rendszerinfo.frissites_ellenorizve() + if mikor: + self.eyebrow.setText(f"UTOLSÓ ELLENŐRZÉS: {datetime.fromtimestamp(mikor):%H:%M}") + + def _ellenorzes_kesz(self, csomagok): + ervenytelenit("frissitesek") + self._ellenorzes_vege() + self.csomagok = csomagok + szamok = {f: sum(1 for c in csomagok if c["forras"] == f) for f in FORRAS} + self._naplo( + f":: {len(csomagok)} frissítés: {szamok['pacman']} pacman, {szamok['aur']} AUR, {szamok['flatpak']} Flatpak" + ) + self.kozpont.jelveny(AZON, len(csomagok)) + self._tablat_tolt() + + def _ellenorzes_hiba(self, uzenet): + self._ellenorzes_vege() + self._naplo(f"[HIBA] {uzenet}") + self.figyelem.setText(f"Az ellenőrzés nem sikerült: {uzenet}") + self.figyelem.show() + + def _tablat_tolt(self): + self.tabla.blockSignals(True) + self.tabla.setRowCount(0) + for c in self.csomagok: + sor = self.tabla.rowCount() + self.tabla.insertRow(sor) + jelolo = QTableWidgetItem() + jelolo.setFlags(Qt.ItemFlag.ItemIsUserCheckable | Qt.ItemFlag.ItemIsEnabled) + jelolo.setCheckState(Qt.CheckState.Checked) + self.tabla.setItem(sor, 0, jelolo) + nev = cimke( + c["nev"] + + ( + ' <span style="color:#d9a441; font-family:\'Noto Sans Mono\'; font-size:11px">ÚJRAINDÍTÁS</span>' + if c["ujrainditas"] + else "" + ) + ) + nev.setContentsMargins(8, 0, 8, 0) + self.tabla.setCellWidget(sor, 1, nev) + for oszlop, ertek in ((2, FORRAS[c["forras"]]), (3, c["regi"] or "-"), (4, c["uj"]), (5, mb(c["meret"]))): + elem = QTableWidgetItem(ertek) + elem.setFlags(Qt.ItemFlag.ItemIsEnabled) + self.tabla.setItem(sor, oszlop, elem) + self.tabla.blockSignals(False) + ures = not self.csomagok + self.tabla.setVisible(not ures) + self.ures.setVisible(ures) + self.ures.setText("A rendszer naprakész, nincs elérhető frissítés.") + self._osszegez() + + def _kijelolt(self): + return [ + c + for i, c in enumerate(self.csomagok) + if self.tabla.item(i, 0) and self.tabla.item(i, 0).checkState() == Qt.CheckState.Checked + ] + + def _valtozott(self, elem): + if elem.column() != 0: + return + if self.csomagok[elem.row()]["forras"] == "pacman": + self.tabla.blockSignals(True) + for i, c in enumerate(self.csomagok): + if c["forras"] == "pacman": + self.tabla.item(i, 0).setCheckState(elem.checkState()) + self.tabla.blockSignals(False) + self._osszegez() + + def _osszegez(self): + kijelolt = self._kijelolt() + self.kijelolt.setText(f"{len(kijelolt)} / {len(self.csomagok)} CSOMAG") + meret = sum(c["meret"] or 0 for c in kijelolt) + self.letoltes.setText(f"{meret / 1e6:.0f} MB") + mind = bool(self.csomagok) and len(kijelolt) == len(self.csomagok) + self.mind.setText("Kijelölés törlése" if mind else "Mind kijelölése") + self.mind.setEnabled(bool(self.csomagok) and not self.futtato.fut()) + tiltott = any(c["forras"] == "pacman" for c in kijelolt) and not muveletek.helper_elerheto() + self.inditas.setEnabled(bool(kijelolt) and not self.futtato.fut() and not tiltott) + self.inditas.setText(f"Frissítés indítása ({len(kijelolt)})" if kijelolt else "Nincs kijelölve") + + def _mind(self): + mind = len(self._kijelolt()) == len(self.csomagok) + allapot = Qt.CheckState.Unchecked if mind else Qt.CheckState.Checked + self.tabla.blockSignals(True) + for i in range(self.tabla.rowCount()): + self.tabla.item(i, 0).setCheckState(allapot) + self.tabla.blockSignals(False) + self._osszegez() + + def _indit(self): + kijelolt = self._kijelolt() + if not kijelolt: + return + lepesek = [] + pacman = [c for c in kijelolt if c["forras"] == "pacman"] + aur = [c["nev"] for c in kijelolt if c["forras"] == "aur"] + flatpak = [c["nev"] for c in kijelolt if c["forras"] == "flatpak"] + if self.snapshot.isChecked() and self.snapshot.isEnabled(): + lepesek.append( + muveletek.Lepes("Snapshot készítése", muveletek.root("snapshot", "Frissítés előtt (RaveOS Center)")) + ) + if pacman: + lepesek.append(muveletek.Lepes("Rendszercsomagok frissítése (pacman)", muveletek.root("frissites"))) + if aur: + lepesek.append( + muveletek.Lepes( + "AUR csomagok frissítése", + [ + "yay", "-S", "--noconfirm", "--color", "never", "--sudo", "pkexec", + "--answerclean", "None", "--answerdiff", "None", "--answeredit", "None", + *aur, + ], + kotelezo=False, + ) + ) + if flatpak: + lepesek.append( + muveletek.Lepes( + "Flatpak alkalmazások frissítése", + ["flatpak", "update", "-y", "--noninteractive", *flatpak], + kotelezo=False, + ) + ) + self.ujrainditas_kell = any(c["ujrainditas"] for c in pacman) + self.figyelem.hide() + self.ujraindito_sav.hide() + self._zarol(True) + self._naplo("") + self._naplo(f":: Frissítés indul, {len(kijelolt)} csomag") + self.futtato.indit(lepesek) + + def _zarol(self, fut): + for w in (self.ellenorzes, self.inditas, self.mind, self.tabla): + w.setEnabled(not fut) + self.snapshot.setEnabled(not fut and rendszerinfo.snapper_elerheto() and not snapshotok.snap_pac()) + if fut: + self.inditas.setText("Frissítés folyamatban...") + + def _futas_kesz(self, rendben, hibasak): + ervenytelenit() + self._zarol(False) + if rendben: + self._naplo(":: A frissítés sikeresen befejeződött.") + if self.ujrainditas_kell: + self.ujraindito_sav.show() + else: + self._naplo(f":: A frissítés hibával ért véget: {', '.join(hibasak)}") + self.figyelem.setText( + "A frissítés nem sikerült teljesen. A részleteket a lenti napló mutatja. " + "Ha nem érted, mi történt, küldj hibajegyet a Visszajelzés oldalon." + ) + self.figyelem.show() + self._ellenoriz(True) + + def _ujraindit(self): + muveletek.Futtato(self).indit([muveletek.Lepes("Újraindítás", ["systemctl", "reboot"])]) + + +OLDAL = Frissitesek diff --git a/raveos-center/raveos_center/oldalak/rendszer/__init__.py b/raveos-center/raveos_center/oldalak/rendszer/__init__.py new file mode 100644 index 0000000..60b5768 --- /dev/null +++ b/raveos-center/raveos_center/oldalak/rendszer/__init__.py @@ -0,0 +1,49 @@ +import importlib + +from raveos_center.elemek import Fulsor, Oldal, Verem + +AZON = "rendszer" +MENU = "Rendszer" +IKON = "rendszer" + +FULEK = ["driverek", "gaming", "karbantartas", "snapshotok", "secureboot", "mentes"] + + +class Rendszer(Oldal): + def __init__(self, kozpont): + super().__init__(kozpont, "DRIVEREK · GAMING · KARBANTARTÁS · SNAPSHOTOK", "Rendszer", kitolt=True) + self.fulek = {} + modulok = [importlib.import_module(f"{__name__}.{nev}") for nev in FULEK] + self.fulsor = Fulsor([(m.AZON, m.CIM) for m in modulok], self.valt) + self.torzs.addWidget(self.fulsor) + self.verem = Verem() + for m in modulok: + ful = m.FUL(kozpont) + self.fulek[m.AZON] = ful + self.verem.addWidget(ful) + self.torzs.addWidget(self.verem, 1) + self.aktiv = modulok[0].AZON if modulok else None + if self.aktiv: + self.fulsor.aktiv(self.aktiv) + + def valt(self, azon, *args): + if azon not in self.fulek: + return + self.aktiv = azon + self.fulsor.aktiv(azon) + self.verem.setCurrentWidget(self.fulek[azon]) + self.fulek[azon].megjelenik(*args) + + def elerheto(self, ful=None, *_): + return ful is None or ful in self.fulek + + def megjelenik(self, ful=None, *args): + self.valt(ful if ful in self.fulek else self.aktiv, *args) + + def indulaskor(self): + for ful in self.fulek.values(): + if hasattr(ful, "indulaskor"): + ful.indulaskor() + + +OLDAL = Rendszer diff --git a/raveos-center/raveos_center/oldalak/rendszer/driverek.py b/raveos-center/raveos_center/oldalak/rendszer/driverek.py new file mode 100644 index 0000000..499e87a --- /dev/null +++ b/raveos-center/raveos_center/oldalak/rendszer/driverek.py @@ -0,0 +1,159 @@ +from PyQt6.QtCore import Qt +from PyQt6.QtWidgets import QHBoxLayout, QMessageBox, QVBoxLayout, QWidget + +from raveos_center import driverek, muveletek +from raveos_center.elemek import Kartya, cimke, gomb +from raveos_center.folyamat import FolyamatAblak +from raveos_center.hatter import ervenytelenit, gyorsitott + +AZON = "driverek" +CIM = "Driverek" + + +class EszkozSor(QWidget): + def __init__(self, elem, telepit): + super().__init__() + self.setObjectName("appsor") + self.setAttribute(Qt.WidgetAttribute.WA_StyledBackground, True) + sor = QHBoxLayout(self) + sor.setContentsMargins(16, 12, 16, 12) + sor.setSpacing(16) + szovegek = QVBoxLayout() + szovegek.setSpacing(3) + nev = cimke(elem["nev"], tordel=True) + nev.setStyleSheet("font-size: 14px; font-weight: 600;") + szovegek.addWidget(nev) + szovegek.addWidget(cimke(f"Jelenlegi driver: {elem['jelenlegi'] or 'nincs betöltve'}", "monohalvany")) + if elem["hianyzik"]: + hiany = cimke(f"Hiányzó csomagok: {' '.join(elem['hianyzik'])}", "monohalvany", True) + hiany.setStyleSheet("color: #d9a441;") + szovegek.addWidget(hiany) + if elem["megjegyzes"]: + szovegek.addWidget(cimke(elem["megjegyzes"], "halvany", True)) + sor.addLayout(szovegek, 1) + if elem["hianyzik"]: + g = gomb("Ajánlott telepítése", fo=True, kicsi=True) + g.setEnabled(muveletek.helper_elerheto()) + g.clicked.connect(lambda: telepit([elem])) + sor.addWidget(g, 0, Qt.AlignmentFlag.AlignVCenter) + else: + rendben = cimke("RENDBEN", "allapotcimke") + rendben.setProperty("szin", "zold") + sor.addWidget(rendben, 0, Qt.AlignmentFlag.AlignVCenter) + + +class Driverek(QWidget): + def __init__(self, kozpont): + super().__init__() + self.kozpont = kozpont + self.elemek = [] + self.kapott = None + doboz = QVBoxLayout(self) + doboz.setContentsMargins(0, 20, 0, 0) + doboz.setSpacing(16) + fej = QHBoxLayout() + fej.addWidget( + cimke( + "A RaveOS felismeri a géped eszközeit, és megmutatja, melyikhez van ajánlott driver. " + "A videokártya drivere a telepítéskor is ugyanígy kerül fel.", + "masodlagos", + True, + ), + 1, + ) + self.ujra = gomb("Újraellenőrzés", kicsi=True) + self.ujra.clicked.connect(self.frissit) + fej.addWidget(self.ujra, 0, Qt.AlignmentFlag.AlignTop) + self.mind = gomb("Minden ajánlott telepítése", fo=True, kicsi=True) + self.mind.clicked.connect(lambda: self._telepit([e for e in self.elemek if e["hianyzik"]])) + self.mind.hide() + fej.addWidget(self.mind, 0, Qt.AlignmentFlag.AlignTop) + doboz.addLayout(fej) + self.figyelem = cimke("", "figyelem", True) + self.figyelem.hide() + doboz.addWidget(self.figyelem) + self.lista = QVBoxLayout() + self.lista.setSpacing(20) + doboz.addLayout(self.lista) + doboz.addStretch(1) + + def megjelenik(self, *_): + gyorsitott("driverek", driverek.allapot, kesz=self._kesz, hiba=self._hiba, kor=120) + + def frissit(self): + self.ujra.setEnabled(False) + ervenytelenit("driverek") + gyorsitott("driverek", driverek.allapot, kesz=self._kesz, hiba=self._hiba) + + def _hiba(self, uzenet): + self.ujra.setEnabled(True) + self.figyelem.setText(f"A hardver felismerése nem sikerült: {uzenet}") + self.figyelem.show() + + def _kesz(self, elemek): + self.ujra.setEnabled(True) + if elemek == self.kapott: + return + self.kapott = elemek + self.elemek = elemek + while self.lista.count(): + w = self.lista.takeAt(0).widget() + if w: + w.deleteLater() + if not muveletek.helper_elerheto(): + self.figyelem.setText("A RaveOS Center rendszerhelpere nincs telepítve, ezért driver nem telepíthető.") + self.figyelem.show() + else: + self.figyelem.hide() + if driverek.szkript() is None: + self.figyelem.setText("A driverfelismerő szkript nincs telepítve.") + self.figyelem.show() + for azon, nev in driverek.KATEGORIAK: + sajat = [e for e in elemek if e["kategoria"] == azon] + if not sajat: + continue + csoport = QWidget() + cs = QVBoxLayout(csoport) + cs.setContentsMargins(0, 0, 0, 0) + cs.setSpacing(10) + cs.addWidget(cimke(nev.upper(), "h2")) + kartya = Kartya(terkoz=0, margo=0) + for i, e in enumerate(sajat): + sor = EszkozSor(e, self._telepit) + sor.setProperty("utolso", i == len(sajat) - 1) + kartya.elrendezes.addWidget(sor) + cs.addWidget(kartya) + self.lista.addWidget(csoport) + if not elemek: + self.lista.addWidget(cimke("Nem található kezelhető eszköz.", "halvany")) + hianyzo = [e for e in elemek if e["hianyzik"]] + self.mind.setVisible(len(hianyzo) > 1) + self.mind.setEnabled(muveletek.helper_elerheto()) + + def _telepit(self, elemek): + if not elemek: + return + lepesek = [] + egyeb = [] + if any(e["gpu"] for e in elemek): + lepesek.append(muveletek.Lepes("Videokártya driverek", muveletek.root("driverek"))) + for e in elemek: + if not e["gpu"]: + egyeb += [p for p in e["hianyzik"] if p not in egyeb] + if egyeb: + lepesek.append(muveletek.Lepes("További driverek", muveletek.root("telepit", *egyeb))) + ablak = FolyamatAblak(self, "Driverek telepítése", lepesek, "A driverek telepítve.") + ablak.exec() + if ablak.rendben and any(driverek.ujrainditas_kell(e) for e in elemek): + doboz = QMessageBox(self) + doboz.setWindowTitle("Újraindítás szükséges") + doboz.setText("Az új videokártya-driver az újraindítás után töltődik be.") + most = doboz.addButton("Újraindítás most", QMessageBox.ButtonRole.AcceptRole) + doboz.addButton("Később", QMessageBox.ButtonRole.RejectRole) + doboz.exec() + if doboz.clickedButton() is most: + muveletek.Futtato(self).indit([muveletek.Lepes("Újraindítás", ["systemctl", "reboot"])]) + self.frissit() + + +FUL = Driverek diff --git a/raveos-center/raveos_center/oldalak/rendszer/gaming.py b/raveos-center/raveos_center/oldalak/rendszer/gaming.py new file mode 100644 index 0000000..5d410e9 --- /dev/null +++ b/raveos-center/raveos_center/oldalak/rendszer/gaming.py @@ -0,0 +1,306 @@ +from PyQt6.QtCore import QObject, Qt, pyqtSignal +from PyQt6.QtWidgets import QComboBox, QHBoxLayout, QMessageBox, QProgressBar, QVBoxLayout, QWidget + +from raveos_center import gaming, muveletek, proton +from raveos_center.elemek import Kapcsolo, Kartya, cimke, gomb +from raveos_center.folyamat import FolyamatAblak +from raveos_center.hatter import ervenytelenit, gyorsitott, hatterben, tarolt + +AZON = "gaming" +CIM = "Gaming" + +BEALLITASOK = [ + ("gamemode", "GameMode", "CPU és I/O prioritás játék közben. A Lutris és a Heroic magától használja, Steamben a " + "játék indítási opciójába írd: gamemoderun %command%"), + ("mangohud", "MangoHud alapból", "FPS, hőmérséklet és terhelés kijelzése minden Vulkan és OpenGL játékban. " + "Játék közben a jobb Shift + F12 elrejti, a jobb Shift + F10 vált a kijelző módok között."), + ("utemezo", "Játékra hangolt ütemező", "Az scx_lavd ütemező gaming módban: egyenletesebb képkockaidő, amikor a " + "háttérben más is fut. Kikapcsolva a kernel saját BORE ütemezője dolgozik."), + ("gamescope", "Gamescope session", "Külön, konzolszerű Steam játékmenet, a bejelentkező képernyőn választható."), + ("governor", "Performance CPU governor", "Játék közben teljes órajel, a játék bezárása után visszaáll."), + ("ntsync", "NTSync", "Gyorsabb Windows-szinkronizáció Wine és Proton alatt."), +] + + +class _Jelzo(QObject): + haladas = pyqtSignal(int) + + +def alkalmaz(szulo, valtozasok): + lepesek = [] + helyi = [] + for azon, be in valtozasok: + l, h = gaming.lepesek(azon, be) + lepesek += l + if h: + helyi.append(h) + if lepesek: + ablak = FolyamatAblak(szulo, "Gaming beállítások", lepesek, "A beállítás elkészült.") + ablak.exec() + if not ablak.rendben: + return "A beállítás nem sikerült teljesen, a részleteket a napló mutatja." + for h in helyi: + try: + h() + except OSError as e: + return f"A beállítás nem sikerült: {e}" + return None + + +class Sor(QWidget): + def __init__(self, nev, leiras, utolso=False): + super().__init__() + self.setObjectName("appsor") + self.setProperty("utolso", utolso) + self.setAttribute(Qt.WidgetAttribute.WA_StyledBackground, True) + self.sor = QHBoxLayout(self) + self.sor.setContentsMargins(16, 14, 16, 14) + self.sor.setSpacing(16) + self.szovegek = QVBoxLayout() + self.szovegek.setSpacing(3) + cim = cimke(nev) + cim.setStyleSheet("font-size: 14px; font-weight: 600;") + self.szovegek.addWidget(cim) + self.szovegek.addWidget(cimke(leiras, "halvany", True)) + self.megjegyzes = cimke("", "monohalvany", True) + self.megjegyzes.hide() + self.szovegek.addWidget(self.megjegyzes) + self.sor.addLayout(self.szovegek, 1) + + def jegyzet(self, szoveg): + self.megjegyzes.setText(szoveg) + self.megjegyzes.setVisible(bool(szoveg)) + + +class BeallitasSor(Sor): + def __init__(self, azon, nev, leiras, valt, utolso=False): + super().__init__(nev, leiras, utolso) + self.azon = azon + self.kapcsolo = Kapcsolo() + self.kapcsolo.setAccessibleName(nev) + self.kapcsolo.clicked.connect(lambda be: valt(self.azon, be)) + self.sor.addWidget(self.kapcsolo, 0, Qt.AlignmentFlag.AlignVCenter) + + def beallit(self, adat, foglalt): + self.kapcsolo.blockSignals(True) + self.kapcsolo.setChecked(adat["aktiv"]) + self.kapcsolo.blockSignals(False) + helper = muveletek.helper_elerheto() or not adat["root"] + self.kapcsolo.setEnabled(adat["elerheto"] and helper and not foglalt) + self.jegyzet(adat["megjegyzes"]) + self.kapcsolo.update() + + +class ProtonGeSor(Sor): + def __init__(self, szulo): + super().__init__( + "Proton-GE", + "A GloriousEggroll Proton-változata, több játékhoz tartalmaz javítást és videokodeket. A Steam " + "újraindítása után a játék Tulajdonságok > Kompatibilitás menüjében választható.", + ) + self.szulo = szulo + self.kiadas = None + self.fut = False + self.jelzo = _Jelzo() + self.sav = QProgressBar() + self.sav.setObjectName("sav") + self.sav.setTextVisible(False) + self.sav.setFixedHeight(6) + self.sav.hide() + self.jelzo.haladas.connect(self.sav.setValue) + self.szovegek.addWidget(self.sav) + gombok = QVBoxLayout() + gombok.setSpacing(6) + self.telepit_gomb = gomb("Legújabb telepítése", fo=True, kicsi=True) + self.telepit_gomb.clicked.connect(self._telepit) + self.regiek_gomb = gomb("Régiek törlése", kicsi=True) + self.regiek_gomb.clicked.connect(self._regiek) + self.torol_gomb = gomb("Mind törlése", kicsi=True) + self.torol_gomb.clicked.connect(self._mind) + for g in (self.telepit_gomb, self.regiek_gomb, self.torol_gomb): + gombok.addWidget(g) + gombok.addStretch(1) + self.sor.addLayout(gombok) + + def frissit(self): + if self.fut: + return + self._mutat() + gyorsitott("proton_ge", proton.legujabb, kesz=self._kiadas, hiba=lambda _: self._kiadas(None), kor=3600) + + def _kiadas(self, kiadas): + self.kiadas = kiadas or self.kiadas + if not self.fut: + self._mutat() + + def _mutat(self, szoveg=None): + lista = proton.telepitett() + van_steam = bool(proton.celmappak()) + naprakesz = bool(self.kiadas and lista and lista[0] == self.kiadas["nev"]) + if szoveg is None: + szoveg = f"Telepítve: {', '.join(lista)}" if lista else "Nincs telepítve." + if self.kiadas and not naprakesz: + szoveg += f" Legújabb: {self.kiadas['nev']}" + if not van_steam: + szoveg = "Nem található Steam. Telepítsd, és indítsd el egyszer." + self.jegyzet(szoveg) + self.telepit_gomb.setText("Naprakész" if naprakesz else "Legújabb telepítése") + self.telepit_gomb.setEnabled(not self.fut and not naprakesz and van_steam) + self.regiek_gomb.setVisible(bool(proton.regiek())) + self.regiek_gomb.setEnabled(not self.fut) + self.torol_gomb.setVisible(bool(lista)) + self.torol_gomb.setEnabled(not self.fut) + + def _telepit(self): + self.fut = True + self.sav.setValue(0) + self.sav.show() + self._mutat("Letöltés és ellenőrzés, ez néhány percig tarthat.") + hatterben(self._letolt, kesz=self._kesz, hiba=self._hiba) + + def _letolt(self): + kiadas = self.kiadas or proton.legujabb() + proton.telepit(kiadas, self.jelzo.haladas.emit) + return kiadas + + def _kesz(self, kiadas): + self.fut = False + self.kiadas = kiadas + self.sav.hide() + self._mutat() + + def _hiba(self, uzenet): + self.fut = False + self.sav.hide() + self._mutat() + QMessageBox.warning(self.szulo, "Proton-GE", f"A telepítés nem sikerült: {uzenet}") + + def _torles(self, parok, kerdes): + if not parok or QMessageBox.question(self.szulo, "Proton-GE", kerdes) != QMessageBox.StandardButton.Yes: + return + try: + for mappa, nev in parok: + proton.torol(nev, [mappa] if mappa else None) + except OSError as e: + QMessageBox.warning(self.szulo, "Proton-GE", f"A törlés nem sikerült: {e}") + self._mutat() + + def _regiek(self): + parok = proton.regiek() + nevek = ", ".join(sorted({n for _, n in parok})) + self._torles(parok, f"Törlöd a régebbi verziókat ({nevek})? Minden Steam-telepítésben a legújabb megmarad.") + + def _mind(self): + self._torles( + [(None, n) for n in proton.telepitett()], + "Törlöd az összes Proton-GE verziót? Az ezeket használó játékok a Steam alap Protonjára váltanak.", + ) + + +class Gaming(QWidget): + def __init__(self, kozpont): + super().__init__() + self.kozpont = kozpont + self.adat = None + self.foglalt = False + self.hibauzenet = "" + doboz = QVBoxLayout(self) + doboz.setContentsMargins(0, 20, 0, 0) + doboz.setSpacing(16) + doboz.addWidget( + cimke( + "Játékhoz hasznos beállítások. Mindegyik bármikor visszakapcsolható. Az Áttekintés Gaming mód " + "kapcsolója a GameMode-ot, a performance governort, az NTSync-et és a játékra hangolt ütemezőt " + "kapcsolja egyszerre.", + "masodlagos", + True, + ) + ) + self.figyelem = cimke("", "figyelem", True) + self.figyelem.hide() + doboz.addWidget(self.figyelem) + + kartya = Kartya(terkoz=0, margo=0) + self.sorok = {} + for i, (azon, nev, leiras) in enumerate(BEALLITASOK): + sor = BeallitasSor(azon, nev, leiras, self.valt, i == len(BEALLITASOK) - 1) + self.sorok[azon] = sor + kartya.elrendezes.addWidget(sor) + self.hud_mod = QComboBox() + for mod, nev in gaming.MANGOHUD_MODOK: + self.hud_mod.addItem(nev, mod) + self.hud_mod.activated.connect(self._hud_mod) + hud_sor = QHBoxLayout() + hud_sor.setSpacing(8) + hud_sor.addWidget(cimke("Kijelző:", "halvany")) + hud_sor.addWidget(self.hud_mod) + hud_sor.addStretch(1) + self.sorok["mangohud"].szovegek.addSpacing(6) + self.sorok["mangohud"].szovegek.addLayout(hud_sor) + doboz.addWidget(kartya) + + doboz.addSpacing(8) + doboz.addWidget(cimke("PROTON VERZIÓK", "h2")) + proton_kartya = Kartya(terkoz=0, margo=0) + self.proton_ge = ProtonGeSor(self) + proton_kartya.elrendezes.addWidget(self.proton_ge) + self.sorok["proton_cachyos"] = BeallitasSor( + "proton_cachyos", + "Proton-CachyOS", + "A CachyOS Proton-változata a rendszer csomagkezelőjéből, a rendszerfrissítéssel együtt frissül. " + "Kb. 1,6 GB. A Steam újraindítása után választható.", + self.valt, + True, + ) + proton_kartya.elrendezes.addWidget(self.sorok["proton_cachyos"]) + doboz.addWidget(proton_kartya) + doboz.addStretch(1) + + def megjelenik(self, *_): + self.frissit() + self.proton_ge.frissit() + + def frissit(self): + if tarolt("gaming") is None: + self.foglalt = True + for sor in self.sorok.values(): + sor.kapcsolo.setEnabled(False) + gyorsitott("gaming", gaming.allapot, kesz=self._kesz, hiba=lambda _: self._kesz(self.adat), kor=30) + + def _kesz(self, adat): + self.foglalt = False + index = self.hud_mod.findData(gaming.mangohud_mod()) + self.hud_mod.setCurrentIndex(max(0, index)) + if adat is None: + self._figyelmeztet("Az állapot nem olvasható be, próbáld újra a fül újbóli megnyitásával.") + return + self.adat = adat + for azon, sor in self.sorok.items(): + sor.beallit(adat[azon], self.foglalt) + if self.hibauzenet: + self._figyelmeztet(self.hibauzenet) + elif not muveletek.helper_elerheto(): + self._figyelmeztet("A RaveOS Center rendszerhelpere nincs telepítve, a legtöbb beállítás nem módosítható.") + else: + self.figyelem.hide() + + def _figyelmeztet(self, szoveg): + self.figyelem.setText(szoveg) + self.figyelem.show() + + def _hud_mod(self): + try: + gaming.mangohud_mod_beallit(self.hud_mod.currentData()) + except OSError as e: + QMessageBox.warning(self, "MangoHud", f"A beállítás nem sikerült: {e}") + + def valt(self, azon, be): + self.alkalmaz([(azon, be)]) + + def alkalmaz(self, valtozasok): + self.hibauzenet = alkalmaz(self, valtozasok) or "" + ervenytelenit("gaming") + self.frissit() + + +FUL = Gaming diff --git a/raveos-center/raveos_center/oldalak/rendszer/karbantartas.py b/raveos-center/raveos_center/oldalak/rendszer/karbantartas.py new file mode 100644 index 0000000..91b7113 --- /dev/null +++ b/raveos-center/raveos_center/oldalak/rendszer/karbantartas.py @@ -0,0 +1,177 @@ +from PyQt6.QtCore import Qt +from PyQt6.QtWidgets import QDialog, QHBoxLayout, QListWidget, QListWidgetItem, QVBoxLayout, QWidget + +from raveos_center import karbantartas, muveletek +from raveos_center.elemek import Kartya, Racs, cimke, gomb +from raveos_center.folyamat import FolyamatAblak +from raveos_center.hatter import gyorsitott, tarolt + +AZON = "karbantartas" +CIM = "Karbantartás" + +MUVELETEK = [ + ("cache", "paccache -rk2", "Csomag cache ürítése", + "Minden csomagból csak az utolsó két verzió marad, az eltávolított csomagok fájljai törlődnek.", True), + ("arva", "pacman -Qdtq", "Árva csomagok", + "Már semmi által nem használt függőségek eltávolítása. Az előnézetben egyenként kiveheted, amit megtartanál.", True), + ("naplo", "journalctl --vacuum", "Naplók takarítása", + "A két hétnél régebbi rendszernaplók törlése, legfeljebb 100 MB marad.", True), + ("tukrok", "reflector", "Tükrök frissítése", + "A leggyorsabb közeli letöltési szerverek kiválasztása. A régi lista biztonsági másolatként megmarad.", False), + ("kulcsok", "pacman-key --populate", "Kulcsok frissítése", + "A csomagaláíró kulcsok újratöltése. Segít, ha a frissítés aláírási hibával áll le.", False), + ("flatpak", "flatpak uninstall --unused", "Flatpak takarítás", + "A már egyik alkalmazás által sem használt runtime-ok eltávolítása.", True), +] + + +class ElonezetAblak(QDialog): + def __init__(self, szulo, cim, szoveg, elemek, valaszthato, gomb_szoveg): + super().__init__(szulo) + self.setWindowTitle(cim) + self.resize(640, 480) + doboz = QVBoxLayout(self) + doboz.setContentsMargins(28, 24, 28, 20) + doboz.setSpacing(12) + doboz.addWidget(cimke("ELŐNÉZET", "eyebrow")) + doboz.addWidget(cimke(cim.upper(), "h2", True)) + doboz.addWidget(cimke(szoveg, "masodlagos", True)) + self.lista = QListWidget() + for elem in elemek: + sor = QListWidgetItem(elem) + if valaszthato: + sor.setFlags(sor.flags() | Qt.ItemFlag.ItemIsUserCheckable) + sor.setCheckState(Qt.CheckState.Checked) + self.lista.addItem(sor) + self.lista.setVisible(bool(elemek)) + doboz.addWidget(self.lista, 1) + if not elemek: + doboz.addStretch(1) + gombok = QHBoxLayout() + gombok.addStretch(1) + megse = gomb("Mégse") + megse.clicked.connect(self.reject) + gombok.addWidget(megse) + self.mehet = gomb(gomb_szoveg, fo=True) + self.mehet.clicked.connect(self.accept) + gombok.addWidget(self.mehet) + doboz.addLayout(gombok) + if valaszthato: + self.lista.itemChanged.connect(lambda _: self.mehet.setEnabled(bool(self.kivalasztott()))) + + def kivalasztott(self): + return [ + self.lista.item(i).text() + for i in range(self.lista.count()) + if self.lista.item(i).checkState() == Qt.CheckState.Checked + ] + + +class MuveletKartya(Kartya): + def __init__(self, parancs, nev, leiras, futtat): + super().__init__(terkoz=8, margo=20) + self.elrendezes.addWidget(cimke(parancs, "monohalvany")) + cim = cimke(nev) + cim.setStyleSheet("font-size: 15px; font-weight: 600;") + self.elrendezes.addWidget(cim) + self.elrendezes.addWidget(cimke(leiras, "halvany", True)) + self.elrendezes.addStretch(1) + self.osszegzes = cimke("Ellenőrzés...", "monozold", True) + self.elrendezes.addWidget(self.osszegzes) + sor = QHBoxLayout() + self.gomb = gomb("Futtatás", kicsi=True) + self.gomb.clicked.connect(futtat) + sor.addWidget(self.gomb) + sor.addStretch(1) + self.elrendezes.addLayout(sor) + + +class Karbantartas(QWidget): + def __init__(self, kozpont): + super().__init__() + self.kozpont = kozpont + self.adat = {} + doboz = QVBoxLayout(self) + doboz.setContentsMargins(0, 20, 0, 0) + doboz.setSpacing(16) + doboz.addWidget( + cimke( + "Rendszeres takarítás és javítás. A törléssel járó műveletek előtt megmutatjuk, mi kerülne törlésre, " + "és csak a megerősítésed után futnak.", + "masodlagos", + True, + ) + ) + self.figyelem = cimke("", "figyelem", True) + self.figyelem.hide() + doboz.addWidget(self.figyelem) + self.racs = Racs(260, 12) + self.kartyak = {} + for azon, parancs, nev, leiras, _ in MUVELETEK: + kartya = MuveletKartya(parancs, nev, leiras, lambda _=False, a=azon: self._futtat(a)) + self.kartyak[azon] = kartya + self.racs.hozzaad(kartya) + doboz.addWidget(self.racs) + doboz.addStretch(1) + + def megjelenik(self, *_): + helper = muveletek.helper_elerheto() + self.figyelem.setText("A RaveOS Center rendszerhelpere nincs telepítve, a műveletek nem futtathatók.") + self.figyelem.setVisible(not helper) + for azon in self.kartyak: + self._ellenoriz(azon) + + def _ellenoriz(self, azon): + kartya = self.kartyak[azon] + if tarolt(f"karbantartas_{azon}") is None: + kartya.gomb.setEnabled(False) + kartya.osszegzes.setText("Ellenőrzés...") + gyorsitott( + f"karbantartas_{azon}", + karbantartas.ELONEZETEK[azon], + kesz=lambda adat, a=azon: self._kesz(a, adat), + hiba=lambda uzenet, a=azon: self._kesz(a, {"elerheto": False, "osszegzes": uzenet, "elemek": []}), + kor=60, + ) + + def _kesz(self, azon, adat): + self.adat[azon] = adat + kartya = self.kartyak[azon] + kartya.osszegzes.setText(adat["osszegzes"]) + torlo = next(m[4] for m in MUVELETEK if m[0] == azon) + teendo = not torlo or azon == "naplo" or bool(adat["elemek"]) + kartya.gomb.setEnabled(adat["elerheto"] and teendo and muveletek.helper_elerheto()) + + def _futtat(self, azon): + adat = self.adat.get(azon) + if not adat: + return + nev = next(m[2] for m in MUVELETEK if m[0] == azon) + kivalasztott = None + if azon == "cache": + szoveg = f"Ezek a csomagfájlok törlődnek a cache-ből ({adat['osszegzes']}). A telepített programokat nem érinti." + elif azon == "arva": + szoveg = ( + "Pontosan ezek a csomagok törlődnek, más nem. Vedd ki a pipát annál, amit megtartanál. " + "Utána újabb árva függőségek jelenhetnek meg, ezeket a kártya újra megmutatja." + ) + elif azon == "naplo": + szoveg = f"{adat['osszegzes']}. A két hétnél régebbi naplók törlődnek, és legfeljebb 100 MB marad." + elif azon == "flatpak": + szoveg = "Ezek a runtime-ok törlődnek. Az alkalmazásaid továbbra is működnek." + else: + szoveg = "" + if szoveg: + ablak = ElonezetAblak(self, nev, szoveg, adat["elemek"], azon == "arva", "Törlés") + if ablak.exec() != QDialog.DialogCode.Accepted: + return + if azon == "arva": + kivalasztott = ablak.kivalasztott() + lepesek = karbantartas.lepesek(azon, adat, kivalasztott) + if not lepesek: + return + FolyamatAblak(self, nev, lepesek, "A művelet elkészült.").exec() + self._ellenoriz(azon) + + +FUL = Karbantartas diff --git a/raveos-center/raveos_center/oldalak/rendszer/mentes.py b/raveos-center/raveos_center/oldalak/rendszer/mentes.py new file mode 100644 index 0000000..6ad5967 --- /dev/null +++ b/raveos-center/raveos_center/oldalak/rendszer/mentes.py @@ -0,0 +1,283 @@ +from datetime import datetime +from pathlib import Path + +from PyQt6.QtCore import QObject, Qt, pyqtSignal +from PyQt6.QtWidgets import ( + QCheckBox, + QFrame, + QScrollArea, + QFileDialog, + QHBoxLayout, + QLabel, + QLineEdit, + QMessageBox, + QPlainTextEdit, + QProgressBar, + QSizePolicy, + QVBoxLayout, + QWidget, +) + +from raveos_center import katalogus, mentesek, muveletek +from raveos_center.elemek import Kartya, cimke, gomb +from raveos_center.hatter import hatterben + +AZON = "mentes" +CIM = "Mentés" + +MERET = {"small": "kicsi", "medium": "közepes", "large": "nagy", "xlarge": "nagyon nagy"} + + +class RovidUt(QLabel): + def __init__(self, ut): + super().__init__(ut) + self.teljes = ut + self.setObjectName("monohalvany") + self.setToolTip(ut) + self.setMinimumWidth(60) + self.setSizePolicy(QSizePolicy.Policy.Ignored, QSizePolicy.Policy.Preferred) + + def resizeEvent(self, esemeny): + super().resizeEvent(esemeny) + self.setText(self.fontMetrics().elidedText(self.teljes, Qt.TextElideMode.ElideMiddle, self.width())) + + +class _Jelzo(QObject): + naplo = pyqtSignal(str) + haladas = pyqtSignal(int) + + +def elem_sor(elem, bejelolve): + sor = QWidget() + s = QHBoxLayout(sor) + s.setContentsMargins(0, 4, 0, 4) + s.setSpacing(12) + jelolo = QCheckBox() + jelolo.setChecked(bejelolve) + s.addWidget(jelolo) + szovegek = QVBoxLayout() + szovegek.setSpacing(0) + nev = cimke(katalogus.loc(elem.get("label")) or elem["id"]) + nev.setStyleSheet("font-weight: 600;") + szovegek.addWidget(nev) + szovegek.addWidget(RovidUt(str(mentesek.abszolut(elem)))) + if elem.get("note"): + szovegek.addWidget(cimke(katalogus.loc(elem["note"]), "halvany", True)) + s.addLayout(szovegek, 1) + if elem.get("scope") == "system": + s.addWidget(cimke("ROOT", "allapotcimke")) + if elem.get("size_hint"): + s.addWidget(cimke(MERET.get(elem["size_hint"], elem["size_hint"]), "halvany")) + jelolo.nev = nev + return sor, jelolo + + +class Mentes(QWidget): + def __init__(self, kozpont): + super().__init__() + self.kozpont = kozpont + self.fut = False + self.mentes_jelolok = [] + self.visszaallitas_jelolok = [] + self.jelzo = _Jelzo() + doboz = QVBoxLayout(self) + doboz.setContentsMargins(0, 24, 0, 0) + doboz.setSpacing(20) + doboz.addWidget( + cimke( + "Mentsd el az alkalmazásaid beállításait és a fontos mappáidat egyetlen fájlba, és állítsd vissza " + "őket egy másik gépen vagy újratelepítés után. A ROOT jelölésű elemekhez a rendszer jelszót kér.", + "masodlagos", + True, + ) + ) + + oszlopok = QHBoxLayout() + oszlopok.setSpacing(24) + doboz.addLayout(oszlopok, 1) + bal = QVBoxLayout() + bal.setSpacing(16) + jobb = QVBoxLayout() + jobb.setSpacing(16) + oszlopok.addLayout(bal, 3) + oszlopok.addLayout(jobb, 2) + + mentes = Kartya("Mentés", terkoz=10, margo=20) + gorgeto = QScrollArea() + gorgeto.setWidgetResizable(True) + gorgeto.setFrameShape(QFrame.Shape.NoFrame) + gorgeto.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff) + gorgeto.setMinimumHeight(160) + lista_doboz = QWidget() + gorgeto.setWidget(lista_doboz) + self.mentes_lista = QVBoxLayout(lista_doboz) + self.mentes_lista.setContentsMargins(0, 0, 12, 0) + self.mentes_lista.setSpacing(2) + mentes.elrendezes.addWidget(gorgeto, 1) + cel_sor = QHBoxLayout() + cel_sor.setSpacing(8) + self.cel = QLineEdit(str(Path.home() / f"raveos-mentes-{datetime.now():%Y%m%d}.tar.gz")) + cel_sor.addWidget(self.cel, 1) + tallozas = gomb("Tallózás", kicsi=True) + tallozas.clicked.connect(self._cel_valaszt) + cel_sor.addWidget(tallozas) + mentes.elrendezes.addSpacing(8) + mentes.elrendezes.addWidget(cimke("Mentés helye", "kartyacim")) + mentes.elrendezes.addLayout(cel_sor) + gs = QHBoxLayout() + gs.addStretch(1) + self.mentes_gomb = gomb("Mentés indítása", fo=True) + self.mentes_gomb.clicked.connect(self._mentes) + gs.addWidget(self.mentes_gomb) + mentes.elrendezes.addLayout(gs) + bal.addWidget(mentes, 1) + + vissza = Kartya("Visszaállítás", terkoz=10, margo=20) + fs = QHBoxLayout() + fs.setSpacing(8) + self.forras = QLineEdit() + self.forras.setPlaceholderText("Válaszd ki a mentésfájlt (.tar.gz)") + self.forras.setReadOnly(True) + fs.addWidget(self.forras, 1) + valaszt = gomb("Mentés kiválasztása", kicsi=True) + valaszt.clicked.connect(self._forras_valaszt) + fs.addWidget(valaszt) + vissza.elrendezes.addLayout(fs) + self.vissza_lista = QVBoxLayout() + self.vissza_lista.setSpacing(2) + vissza.elrendezes.addLayout(self.vissza_lista) + vissza.elrendezes.addWidget( + cimke("A visszaállítás felülírja a meglévő fájlokat a kiválasztott helyeken.", "halvany", True) + ) + vs = QHBoxLayout() + vs.addStretch(1) + self.vissza_gomb = gomb("Visszaállítás", fo=True) + self.vissza_gomb.setEnabled(False) + self.vissza_gomb.clicked.connect(self._visszaallitas) + vs.addWidget(self.vissza_gomb) + vissza.elrendezes.addLayout(vs) + jobb.addWidget(vissza) + + self.sav = QProgressBar() + self.sav.setObjectName("sav") + self.sav.setTextVisible(False) + self.sav.setFixedHeight(6) + jobb.addWidget(self.sav) + jobb.addWidget(cimke("NAPLÓ", "h2")) + self.naplo = QPlainTextEdit() + self.naplo.setObjectName("naplo") + self.naplo.setReadOnly(True) + self.naplo.setMinimumHeight(100) + jobb.addWidget(self.naplo, 1) + self.jelzo.naplo.connect(self.naplo.appendPlainText) + self.jelzo.haladas.connect(self.sav.setValue) + self._mentes_lista_epit() + + def _mentes_lista_epit(self): + try: + csoportok = mentesek.csoportok() + except (OSError, ValueError, RuntimeError) as e: + self.mentes_lista.addWidget(cimke(f"A mentési lista nem tölthető be: {e}", "figyelem", True)) + return + self.mentes_lista.addStretch(0) + for csoport in csoportok: + fej = cimke(katalogus.loc(csoport.get("label")).upper(), "monozold") + self.mentes_lista.addSpacing(8) + self.mentes_lista.addWidget(fej) + if csoport.get("description"): + self.mentes_lista.addWidget(cimke(katalogus.loc(csoport["description"]), "halvany", True)) + for elem in csoport.get("items", []): + sor, jelolo = elem_sor(elem, elem.get("default_selected", False)) + self.mentes_lista.addWidget(sor) + self.mentes_jelolok.append((jelolo, elem)) + + def megjelenik(self, *_): + self._gombok() + + def _gombok(self): + self.mentes_gomb.setEnabled(not self.fut) + self.vissza_gomb.setEnabled(not self.fut and bool(self.visszaallitas_jelolok)) + + def _cel_valaszt(self): + ut, _ = QFileDialog.getSaveFileName(self, "Mentés helye", self.cel.text(), "Mentés (*.tar.gz)") + if ut: + self.cel.setText(ut if ut.endswith(".tar.gz") else ut + ".tar.gz") + + def _forras_valaszt(self): + ut, _ = QFileDialog.getOpenFileName(self, "Mentés kiválasztása", str(Path.home()), "Mentés (*.tar.gz)") + if not ut: + return + self.forras.setText(ut) + self._tartalom([]) + hatterben(mentesek.tartalom, ut, kesz=self._tartalom, hiba=lambda u: self._hiba(f"A fájl nem olvasható: {u}")) + + def _tartalom(self, lista): + while self.vissza_lista.count(): + w = self.vissza_lista.takeAt(0).widget() + if w: + w.deleteLater() + self.visszaallitas_jelolok = [] + if not lista: + self.vissza_lista.addWidget(cimke("Ebben a fájlban nincs visszaállítható elem.", "halvany")) + for elem in lista: + sor, jelolo = elem_sor(elem, True) + self.vissza_lista.addWidget(sor) + self.visszaallitas_jelolok.append((jelolo, elem)) + if any(e.get("regi") for e in lista): + self.vissza_lista.addWidget( + cimke("Régi (App Installer) formátumú mentés: a rendszerelemek ebből nem állíthatók vissza.", "halvany", True) + ) + self._gombok() + + def _hiba(self, szoveg): + self.naplo.appendPlainText(f"[HIBA] {szoveg}") + + def _rootot_kell(self, elemek): + if any(e.get("scope") == "system" for e in elemek) and not muveletek.helper_elerheto(): + QMessageBox.warning(self, "Mentés", "A rendszerelemekhez a RaveOS Center rendszerhelpere szükséges, de nincs telepítve.") + return False + return True + + def _mentes(self): + elemek = [e for j, e in self.mentes_jelolok if j.isChecked()] + cel = self.cel.text().strip() + if not elemek or not cel or not self._rootot_kell(elemek): + return + if Path(cel).exists(): + if QMessageBox.question(self, "Mentés", f"A fájl már létezik, felülírod?\n{cel}") != QMessageBox.StandardButton.Yes: + return + self._indul(f"Mentés indul: {cel}") + hatterben( + mentesek.mentes, cel, elemek, self.jelzo.naplo.emit, self.jelzo.haladas.emit, + kesz=lambda n: self._vege(f"Mentés kész: {n} elem, {cel}"), + hiba=lambda u: self._vege(f"A mentés nem sikerült: {u}", False), + ) + + def _visszaallitas(self): + elemek = [e for j, e in self.visszaallitas_jelolok if j.isChecked()] + if not elemek or not self._rootot_kell(elemek): + return + if QMessageBox.question( + self, "Visszaállítás", f"{len(elemek)} elem visszaállítása felülírja a meglévő fájlokat. Folytatod?" + ) != QMessageBox.StandardButton.Yes: + return + self._indul(f"Visszaállítás indul: {self.forras.text()}") + hatterben( + mentesek.visszaallitas, self.forras.text(), elemek, self.jelzo.naplo.emit, self.jelzo.haladas.emit, + kesz=lambda n: self._vege(f"Visszaállítás kész: {n} elem."), + hiba=lambda u: self._vege(f"A visszaállítás nem sikerült: {u}", False), + ) + + def _indul(self, szoveg): + self.fut = True + self._gombok() + self.sav.setValue(0) + self.naplo.appendPlainText(szoveg) + + def _vege(self, szoveg, rendben=True): + self.fut = False + self._gombok() + self.naplo.appendPlainText(szoveg if rendben else f"[HIBA] {szoveg}") + + +FUL = Mentes diff --git a/raveos-center/raveos_center/oldalak/rendszer/secureboot.py b/raveos-center/raveos_center/oldalak/rendszer/secureboot.py new file mode 100644 index 0000000..00ca444 --- /dev/null +++ b/raveos-center/raveos_center/oldalak/rendszer/secureboot.py @@ -0,0 +1,256 @@ +import shutil +import subprocess + +from PyQt6.QtWidgets import QHBoxLayout, QMessageBox, QPlainTextEdit, QVBoxLayout, QWidget + +from raveos_center import muveletek +from raveos_center.elemek import Kartya, Racs, cimke, gomb +from raveos_center.hatter import ervenytelenit, gyorsitott, tarolt + +AZON = "secureboot" +CIM = "Secure Boot" + +SZKRIPT = "raveos-secureboot-setup" +UJRAINDITAS_KELL = 10 + +LEPESEK_SZOVEG = [ + "telepíti a szükséges eszközöket (sbsigntools, efitools),", + "létrehoz egy saját, csak ehhez a géphez tartozó aláíró kulcsot,", + "aláírja a rendszerbetöltőt, a kernelt és a külső kernelmodulokat (pl. NVIDIA),", + "beírja a firmware-be a RaveOS, a Microsoft és a saját géped kulcsát, így a Windows is tovább indul,", + "beállít egy automatikus aláírást, ami minden kernelfrissítés után újra aláír.", +] + + +def allapot_lekerdez(): + if not shutil.which(SZKRIPT): + return None + kimenet = subprocess.run([SZKRIPT, "--status"], capture_output=True, text=True, timeout=30) + adat = {} + for sor in kimenet.stdout.splitlines(): + if "=" in sor and not sor.startswith("["): + k, _, v = sor.partition("=") + adat[k.strip()] = v.strip() + if not adat and "Nem UEFI" in kimenet.stdout: + adat["uefi"] = "0" + return adat + + +class SecureBoot(QWidget): + def __init__(self, kozpont): + super().__init__() + self.kozpont = kozpont + doboz = QVBoxLayout(self) + doboz.setContentsMargins(0, 20, 0, 0) + doboz.setSpacing(16) + doboz.addWidget( + cimke( + "A Secure Boot csak aláírt rendszerbetöltőt és kernelt enged elindulni. Akkor hasznos, ha a gépen " + "Windows is van, és ott olyan játék vagy program fut, ami bekapcsolt Secure Bootot kér (például egyes " + "csalásvédelmek vagy a Windows 11). A beállítás után a RaveOS és a Windows is elindul.", + "masodlagos", + True, + ) + ) + oszlopok = QHBoxLayout() + oszlopok.setSpacing(24) + bal = QVBoxLayout() + bal.setSpacing(12) + oszlopok.addLayout(bal, 3) + doboz.addLayout(oszlopok, 1) + self.racs = Racs(110, 10) + self.ertekek = {} + for kulcs, nev in ( + ("secureboot", "Secure Boot"), + ("setupmode", "Setup Mode"), + ("keys", "Gépkulcs"), + ("signed", "Aláírások"), + ): + k = Kartya(nev, terkoz=6, margo=14) + ertek = cimke("...", "nagyszam") + k.elrendezes.addWidget(ertek) + self.ertekek[kulcs] = ertek + self.racs.hozzaad(k) + bal.addWidget(self.racs) + self.figyelem = cimke("", "figyelem", True) + self.figyelem.hide() + bal.addWidget(self.figyelem) + bal.addWidget( + cimke( + "FIGYELEM, BITLOCKER: ha a gépen Windows is van BitLockerrel, a beállítás előtt a Windowsban függeszd " + "fel a BitLockert (manage-bde -protectors -disable C: -rebootcount 2), vagy legyen kéznél a 48 jegyű " + "helyreállítási kulcs. Enélkül a Windows a következő indításkor ezt a kulcsot fogja kérni.", + "figyelem", + True, + ) + ) + + leiras = Kartya("A beállítás lépései", terkoz=6) + leiras.elrendezes.addWidget(cimke("A Beállítás gomb egymás után:", "masodlagos")) + for i, szoveg in enumerate(LEPESEK_SZOVEG, 1): + sor = QHBoxLayout() + sor.setSpacing(10) + jel = cimke(f"{i}.", "monozold") + jel.setFixedWidth(22) + sor.addWidget(jel) + sor.addWidget(cimke(szoveg, "masodlagos", True), 1) + leiras.elrendezes.addLayout(sor) + leiras.elrendezes.addSpacing(6) + leiras.elrendezes.addWidget( + cimke( + "Ehhez a firmware-nek Setup Mode-ban kell lennie: a BIOS/UEFI beállításokban töröld a Secure Boot " + "kulcsokat (Clear Secure Boot keys / Reset to Setup Mode). Ha nincs Setup Mode-ban, a Center szól, " + "és újraindítás után a beállítás magától folytatódik.", + "halvany", + True, + ) + ) + + leiras.setMinimumWidth(300) + leiras.setMaximumWidth(420) + jobb = QVBoxLayout() + jobb.addWidget(leiras) + jobb.addStretch(1) + oszlopok.addLayout(jobb, 2) + + gombok = QHBoxLayout() + gombok.setSpacing(8) + self.frissit_gomb = gomb("Állapot frissítése") + self.frissit_gomb.clicked.connect(self.frissit) + self.ujra_gomb = gomb("Újra aláírás") + self.ujra_gomb.setToolTip("Ha a gépkulcs már megvan, csak újra aláírja a rendszerbetöltőt, a kernelt és a modulokat.") + self.ujra_gomb.clicked.connect(lambda: self._futtat("resign", "Újra aláírás")) + self.beallit_gomb = gomb("Beállítás", fo=True) + self.beallit_gomb.clicked.connect(self._beallit) + gombok.addWidget(self.frissit_gomb) + gombok.addStretch(1) + gombok.addWidget(self.ujra_gomb) + gombok.addWidget(self.beallit_gomb) + bal.addLayout(gombok) + + bal.addWidget(cimke("NAPLÓ", "h2")) + self.naplo = QPlainTextEdit() + self.naplo.setObjectName("naplo") + self.naplo.setReadOnly(True) + self.naplo.setMinimumHeight(80) + bal.addWidget(self.naplo, 1) + + self.futtato = muveletek.Futtato(self) + self.futtato.sor.connect(self.naplo.appendPlainText) + self.futtato.kesz.connect(self._kesz) + self.adat = {} + + def megjelenik(self, *_): + self._betolt() + + def frissit(self): + ervenytelenit("secureboot") + self._betolt() + + def _betolt(self): + if tarolt("secureboot") is None: + for e in self.ertekek.values(): + e.setText("...") + gyorsitott( + "secureboot", allapot_lekerdez, kesz=self._allapot, + hiba=lambda u: self._hiba(f"Az állapot nem olvasható: {u}"), kor=60, + ) + + def _hiba(self, szoveg): + self.figyelem.setText(szoveg) + self.figyelem.show() + + def _allapot(self, adat): + self.adat = adat or {} + self.figyelem.hide() + if adat is None: + for e in self.ertekek.values(): + e.setText("-") + self._hiba("A Secure Boot beállító (raveos-secureboot-setup) nincs telepítve.") + self._gombok(False) + return + if adat.get("uefi") == "0": + for e in self.ertekek.values(): + e.setText("-") + self._hiba("Ez a gép nem UEFI módban indult, ezért a Secure Boot nem érhető el.") + self._gombok(False) + return + def ki(ertek, igen, nem): + return igen if ertek == "1" else nem if ertek == "0" else "?" + self._beir("secureboot", ki(adat.get("secureboot"), "BE", "KI"), adat.get("secureboot") == "1") + self._beir("setupmode", ki(adat.get("setupmode"), "IGEN", "NEM"), adat.get("setupmode") == "1") + self._beir("keys", ki(adat.get("keys"), "MEGVAN", "NINCS"), adat.get("keys") == "1") + self._beir("signed", ki(adat.get("signed"), "RENDBEN", "HIÁNYZIK"), adat.get("signed") == "1") + if adat.get("pending") == "1": + self._hiba("A beállítás félbemaradt, és a következő újraindításkor magától folytatódik.") + self._gombok(True) + + def _beir(self, kulcs, szoveg, zold): + e = self.ertekek[kulcs] + e.setText(szoveg) + e.setProperty("zold", zold) + e.style().unpolish(e) + e.style().polish(e) + + def _gombok(self, elerheto): + fut = self.futtato.fut() + helper = muveletek.helper_elerheto() + self.beallit_gomb.setEnabled(elerheto and helper and not fut) + self.ujra_gomb.setEnabled(elerheto and helper and not fut and self.adat.get("keys") == "1") + self.frissit_gomb.setEnabled(not fut) + + def _beallit(self): + valasz = QMessageBox.question( + self, + "Secure Boot beállítása", + "A beállítás kulcsokat ír a firmware-be, és aláírja a rendszerbetöltőt és a kernelt. " + "Ha Windows is van a gépen BitLockerrel, előtte függeszd fel a BitLockert.\n\nFolytatod?", + QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No, + QMessageBox.StandardButton.No, + ) + if valasz == QMessageBox.StandardButton.Yes: + self._futtat("setup", "Secure Boot beállítása") + + def _futtat(self, mod, cimke_szoveg): + self.naplo.clear() + self._gombok(False) + self.futtato.indit([muveletek.Lepes(cimke_szoveg, muveletek.root("secureboot", mod))]) + + def _bios_ujrainditas(self, cim, szoveg): + doboz = QMessageBox(self) + doboz.setWindowTitle(cim) + doboz.setText(szoveg) + most = doboz.addButton("Újraindítás a BIOS-ba", QMessageBox.ButtonRole.AcceptRole) + doboz.addButton("Később", QMessageBox.ButtonRole.RejectRole) + doboz.exec() + if doboz.clickedButton() is most: + muveletek.Futtato(self).indit( + [ + muveletek.Lepes( + "Újraindítás a firmware beállításaiba", + ["bash", "-c", "systemctl reboot --firmware-setup || systemctl reboot"], + ) + ] + ) + + def _kesz(self, rendben, _hibasak): + kod = self.futtato.utolso_kod + if rendben: + self._bios_ujrainditas( + "Secure Boot", + "Az aláírások és a kulcsok készen vannak.\n\nA BIOS-ban állítsd a Secure Bootot Enabled-re, " + "majd mentsd el a beállítást és indítsd újra a gépet.", + ) + elif kod == UJRAINDITAS_KELL: + self._bios_ujrainditas( + "Újraindítás szükséges", + "A firmware nincs Setup Mode-ban.\n\nA BIOS-ban a Secure Boot menüben töröld a kulcsokat " + "(Key Management, Clear Secure Boot keys), így a gép Setup Mode-ba kerül. Mentés után indítsd újra " + "a gépet: a beállítás indítás után magától befejeződik.", + ) + else: + QMessageBox.warning(self, "Secure Boot", "A művelet nem sikerült. A részleteket a napló mutatja.") + self.frissit() + + +FUL = SecureBoot diff --git a/raveos-center/raveos_center/oldalak/rendszer/snapshotok.py b/raveos-center/raveos_center/oldalak/rendszer/snapshotok.py new file mode 100644 index 0000000..8d9b08c --- /dev/null +++ b/raveos-center/raveos_center/oldalak/rendszer/snapshotok.py @@ -0,0 +1,247 @@ +from PyQt6.QtCore import QProcess, Qt +from PyQt6.QtWidgets import QHBoxLayout, QInputDialog, QLineEdit, QMessageBox, QVBoxLayout, QWidget + +from raveos_center import muveletek, snapshotok +from raveos_center.elemek import Kartya, cimke, gomb +from raveos_center.folyamat import FolyamatAblak +from raveos_center.hatter import ervenytelenit, gyorsitott, tarolt + +AZON = "snapshotok" +CIM = "Snapshotok" + + +class SnapshotSor(QWidget): + def __init__(self, elem, utolso, visszaallit, torol): + super().__init__() + self.setObjectName("appsor") + self.setProperty("utolso", utolso) + self.setAttribute(Qt.WidgetAttribute.WA_StyledBackground, True) + sor = QHBoxLayout(self) + sor.setContentsMargins(16, 12, 16, 12) + sor.setSpacing(16) + datum = cimke(elem["datum"], "mono") + datum.setFixedWidth(150) + sor.addWidget(datum) + szovegek = QVBoxLayout() + szovegek.setSpacing(2) + szovegek.addWidget(cimke(elem["leiras"] or "-", None, True)) + szovegek.addWidget(cimke(f"#{elem['szam']} {elem['tipus_nev']}", "monohalvany")) + sor.addLayout(szovegek, 1) + helper = muveletek.helper_elerheto() + vissza = gomb("Visszaállítás", kicsi=True) + vissza.setEnabled(helper) + vissza.clicked.connect(lambda: visszaallit(elem)) + sor.addWidget(vissza, 0, Qt.AlignmentFlag.AlignVCenter) + torles = gomb("Törlés", kicsi=True) + torles.setEnabled(helper) + torles.clicked.connect(lambda: torol(elem)) + sor.addWidget(torles, 0, Qt.AlignmentFlag.AlignVCenter) + + +class Snapshotok(QWidget): + def __init__(self, kozpont): + super().__init__() + self.kozpont = kozpont + self.adat = None + doboz = QVBoxLayout(self) + doboz.setContentsMargins(0, 20, 0, 0) + doboz.setSpacing(16) + fej = QHBoxLayout() + fej.setSpacing(16) + self.leiras = cimke( + "A btrfs snapshot a rendszer egy pillanatnyi állapota. Ha egy frissítés után gond van, egy korábbi " + "állapotra vissza tudsz állni. A saját fájljaidat (/home) a visszaállítás nem érinti.", + "masodlagos", + True, + ) + fej.addWidget(self.leiras, 1) + self.uj_gomb = gomb("Új snapshot", fo=True) + self.uj_gomb.clicked.connect(self.uj) + fej.addWidget(self.uj_gomb, 0, Qt.AlignmentFlag.AlignTop) + doboz.addLayout(fej) + self.info = cimke("", "monozold", True) + self.info.hide() + doboz.addWidget(self.info) + self.figyelem = cimke("", "figyelem", True) + self.figyelem.hide() + doboz.addWidget(self.figyelem) + self.beallitas_gomb = gomb("Snapper beállítása", fo=True) + self.beallitas_gomb.clicked.connect(self._beallitas) + self.beallitas_gomb.hide() + gs = QHBoxLayout() + gs.addWidget(self.beallitas_gomb) + gs.addStretch(1) + doboz.addLayout(gs) + self.kartya = Kartya(terkoz=0, margo=0) + doboz.addWidget(self.kartya) + doboz.addStretch(1) + + def megjelenik(self, *args): + if tarolt("snapshotok") is None: + self.uj_gomb.setEnabled(False) + self._info("Betöltés...") + gyorsitott("snapshotok", snapshotok.allapot, kesz=self._kesz, hiba=self._hiba, kor=30) + if args and args[0] == "uj": + self.uj() + + def frissit(self): + ervenytelenit("snapshotok", "snapshot") + gyorsitott("snapshotok", snapshotok.allapot, kesz=self._kesz, hiba=self._hiba) + + def _hiba(self, uzenet): + self._figyelmeztet(f"A snapshotok nem olvashatók: {uzenet}") + + def _info(self, szoveg): + self.info.setText(szoveg) + self.info.setVisible(bool(szoveg)) + + def _figyelmeztet(self, szoveg): + self.figyelem.setText(szoveg) + self.figyelem.setVisible(bool(szoveg)) + + def _kesz(self, adat): + self.adat = adat + helper = muveletek.helper_elerheto() + kesz = adat["fajlrendszer"] == "btrfs" and adat["snapper"] + self.uj_gomb.setVisible(kesz) + self.uj_gomb.setEnabled(kesz and helper) + self.beallitas_gomb.setVisible(snapshotok.snapper_telepitheto(adat)) + self.beallitas_gomb.setEnabled(helper) + self.kartya.setVisible(kesz) + if adat["fajlrendszer"] != "btrfs": + self._info("") + self._figyelmeztet( + f"Snapshothoz btrfs fájlrendszer kell. Ez a gép {adat['fajlrendszer'] or 'ismeretlen'} fájlrendszert " + "használ, ezért itt nem érhető el. A RaveOS telepítésekor a lemez partícionálásánál ext4 és btrfs " + "közül lehet választani: ha snapshotot szeretnél, a btrfs-t válaszd." + ) + return + if not adat["snapper"]: + self._info("") + if snapshotok.snapper_telepitheto(adat): + self._figyelmeztet( + "A gép btrfs-t használ, de a snapper még nincs beállítva. Egy kattintással beállítható: " + "utána minden csomagművelet előtt automatikusan snapshot készül." + ) + else: + self._figyelmeztet( + "A gép btrfs-t használ, de az alvolumen-elrendezése eltér a RaveOS-étól (@ és @snapshots), " + "ezért a snapper itt nem állítható be automatikusan." + ) + return + if adat["alvolumen"] != "/@": + self._figyelmeztet("A rendszer most nem a fő alvolumenről (@) fut, ezért visszaállítás nem indítható.") + elif adat["hiba"]: + self._figyelmeztet(adat["hiba"]) + elif not helper: + self._figyelmeztet("A RaveOS Center rendszerhelpere nincs telepítve, ezért snapshot nem kezelhető.") + else: + self._figyelmeztet("") + self._info( + ("Automatikus snapshot minden csomagművelet előtt és után (snap-pac). " if adat["snap_pac"] else "") + + f"{len(adat['lista'])} snapshot, a régiek automatikusan törlődnek." + ) + self._lista(adat) + + def _lista(self, adat): + elrendezes = self.kartya.elrendezes + while elrendezes.count(): + w = elrendezes.takeAt(0).widget() + if w: + w.deleteLater() + if not adat["lista"]: + ures = cimke("Még nincs snapshot.", "halvany") + ures.setContentsMargins(16, 16, 16, 16) + elrendezes.addWidget(ures) + return + for i, elem in enumerate(adat["lista"]): + elrendezes.addWidget(SnapshotSor(elem, i == len(adat["lista"]) - 1, self.visszaallit, self.torol)) + + def _futtat(self, cim, lepes, siker): + ablak = FolyamatAblak(self, cim, [lepes], siker) + ablak.exec() + self.frissit() + return ablak.rendben + + def uj(self): + if not self.adat or not self.uj_gomb.isEnabled(): + return + leiras, ok = QInputDialog.getText( + self, "Új snapshot", "Leírás (például: Driver csere előtt):", QLineEdit.EchoMode.Normal, "Kézi snapshot" + ) + leiras = leiras.strip() + if not ok: + return + if not snapshotok.ervenyes_leiras(leiras): + QMessageBox.warning(self, "Új snapshot", "A leírás 1-80 karakter lehet: betűk, számok, szóköz és . _ : , ( ) -") + return + self._futtat("Új snapshot", muveletek.Lepes("Snapshot készítése", muveletek.root("snapshot", leiras)), "A snapshot elkészült.") + + def torol(self, elem): + if QMessageBox.question( + self, "Snapshot törlése", f"Törlöd a(z) #{elem['szam']} snapshotot?\n{elem['datum']} {elem['leiras']}" + ) != QMessageBox.StandardButton.Yes: + return + self._futtat( + "Snapshot törlése", + muveletek.Lepes("Snapshot törlése", muveletek.root("snapshot-torles", str(elem["szam"]))), + "A snapshot törölve.", + ) + + def visszaallit(self, elem): + kernelek = snapshotok.kernel_valtozas(elem["szam"]) + ellenorizheto = kernelek is not None + kernelek = kernelek or [] + hianyzo = [k for k in kernelek if not k["cache"]] + if hianyzo: + QMessageBox.warning( + self, + "Visszaállítás", + "Ennek a snapshotnak a kernelcsomagja már nincs meg a csomag-cache-ben " + f"({', '.join(k['nev'] + ' ' + k['regi'] for k in hianyzo)}), ezért a visszaállított rendszer nem " + "indulna el. Válassz újabb snapshotot.", + ) + return + kernel_szoveg = "".join(f"\n- {k['nev']}: {k['most'] or 'nincs'} -> {k['regi']}" for k in kernelek) + szoveg = ( + f"A rendszer visszaáll erre az állapotra:\n#{elem['szam']} {elem['datum']} {elem['leiras']}\n\n" + "A saját fájljaid (/home) nem változnak. A mostani rendszer a következő visszaállításig megmarad " + "tartalékként. A változás újraindítás után lép életbe." + + (f"\n\nA kernel is visszaáll:{kernel_szoveg}" if kernelek else "") + + ("" if ellenorizheto else "\n\nA snapshot kernelverziója innen nem olvasható, a visszaállítás előtt a rendszer " + "ellenőrzi, és ha a kernelcsomag hiányzik, nem végzi el.") + + "\n\nFolytatod?" + ) + if QMessageBox.question(self, "Visszaállítás", szoveg) != QMessageBox.StandardButton.Yes: + return + rendben = self._futtat( + "Visszaállítás", + muveletek.Lepes("Snapshot visszaállítása", muveletek.root("snapshot-visszaallit", str(elem["szam"]))), + "A visszaállítás elkészült. Indítsd újra a gépet.", + ) + if not rendben: + return + doboz = QMessageBox(self) + doboz.setWindowTitle("Visszaállítás") + doboz.setIcon(QMessageBox.Icon.Warning) + doboz.setText( + "A visszaállítás újraindítás után lép életbe. Amit addig a gépen változtatsz (telepítés, beállítás, " + "rendszerfájlok), az a tartalék rendszerbe kerül, és elvész. A /home-ban lévő fájljaid megmaradnak.\n\n" + "Javasolt most újraindítani." + ) + most = doboz.addButton("Újraindítás most", QMessageBox.ButtonRole.AcceptRole) + doboz.addButton("Később", QMessageBox.ButtonRole.RejectRole) + doboz.setDefaultButton(most) + doboz.exec() + if doboz.clickedButton() is most: + QProcess.startDetached("systemctl", ["reboot"]) + + def _beallitas(self): + self._futtat( + "Snapper beállítása", + muveletek.Lepes("Snapper beállítása", muveletek.root("snapper-beallitas")), + "A snapper beállítva, az első snapshot elkészült.", + ) + + +FUL = Snapshotok diff --git a/raveos-center/raveos_center/oldalak/visszajelzes.py b/raveos-center/raveos_center/oldalak/visszajelzes.py new file mode 100644 index 0000000..56664ab --- /dev/null +++ b/raveos-center/raveos_center/oldalak/visszajelzes.py @@ -0,0 +1,887 @@ +from datetime import datetime + +from PyQt6.QtCore import Qt, QTimer +from PyQt6.QtGui import QTextCursor +from PyQt6.QtWidgets import ( + QCheckBox, + QDialog, + QHBoxLayout, + QLineEdit, + QListWidget, + QListWidgetItem, + QPlainTextEdit, + QPushButton, + QScrollArea, + QVBoxLayout, + QWidget, +) + +from raveos_center import beallitasok, diagnosztika, jegyek_api +from raveos_center.elemek import Csillagok, Fulsor, Kartya, Oldal, Verem, cimke, gomb +from raveos_center.hatter import gyorsitott, hatterben + +AZON = "visszajelzes" +MENU = "Visszajelzés" +IKON = "visszajelzes" + +ALLAPOT = { + "uj": ("Beküldve", ""), + "folyamatban": ("Folyamatban", "sarga"), + "tervezett": ("Tervezett", "sarga"), + "megoldva": ("Megoldva", "zold"), + "kesz": ("Elkészült", "zold"), + "lezarva": ("Lezárva", ""), + "elutasitva": ("Elutasítva", ""), +} + + +def ido(t): + return datetime.fromtimestamp(t).strftime("%Y.%m.%d %H:%M") + + +def allapotcimke(allapot): + szoveg, szin = ALLAPOT.get(allapot, (allapot, "")) + c = cimke(szoveg.upper(), "allapotcimke") + if szin: + c.setProperty("szin", szin) + return c + + +def uzenetsav(uzenet): + sav = cimke(uzenet, "figyelem", True) + sav.hide() + return sav + + +class JegySor(QWidget): + def __init__(self, jegy): + super().__init__() + doboz = QVBoxLayout(self) + doboz.setContentsMargins(14, 12, 14, 12) + doboz.setSpacing(6) + felso = QHBoxLayout() + felso.setSpacing(8) + tipus = "HIBA" if jegy["tipus"] == "hiba" else "ÖTLET" + felso.addWidget(cimke(tipus, "monozold")) + felso.addStretch(1) + if jegy["uj_valasz"]: + felso.addWidget(cimke("ÚJ VÁLASZ", "jelveny")) + felso.addWidget(allapotcimke(jegy["allapot"])) + doboz.addLayout(felso) + cim = cimke(jegy["cim"]) + cim.setStyleSheet("font-weight: 600;") + cim.setText(cim.fontMetrics().elidedText(jegy["cim"], Qt.TextElideMode.ElideRight, 300)) + cim.setToolTip(jegy["cim"]) + doboz.addWidget(cim) + doboz.addWidget(cimke(ido(jegy["frissitve"]), "halvany")) + for w in self.findChildren(QWidget): + w.setAttribute(Qt.WidgetAttribute.WA_TransparentForMouseEvents) + + +class Uzenet(QWidget): + def __init__(self, uzenet): + super().__init__() + self.setObjectName("uzenet") + self.setAttribute(Qt.WidgetAttribute.WA_StyledBackground, True) + csapat = uzenet["szerzo"] == "admin" + self.setProperty("csapat", csapat) + doboz = QVBoxLayout(self) + doboz.setContentsMargins(14, 12, 14, 12) + doboz.setSpacing(6) + ki = "RaveOS csapat" if csapat else "Te" + doboz.addWidget(cimke(f"{ki} · {ido(uzenet['ido'])}", "halvany")) + szoveg = cimke(uzenet["szoveg"], tordel=True) + szoveg.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse) + doboz.addWidget(szoveg) + + +class JelentesAblak(QDialog): + def __init__(self, szulo, szoveg): + super().__init__(szulo) + self.setWindowTitle("A csatolt diagnosztika tartalma") + self.resize(820, 600) + doboz = QVBoxLayout(self) + doboz.setContentsMargins(20, 20, 20, 20) + doboz.setSpacing(12) + doboz.addWidget(cimke("Pontosan ez kerül feltöltésre, ha bejelölöd a csatolást:", "masodlagos")) + mezo = QPlainTextEdit(szoveg) + mezo.setReadOnly(True) + mezo.setObjectName("mono") + doboz.addWidget(mezo, 1) + bezar = gomb("Bezárás", fo=True) + bezar.clicked.connect(self.accept) + doboz.addWidget(bezar, 0, Qt.AlignmentFlag.AlignRight) + + +class Urlap(QWidget): + def __init__(self, oldal): + super().__init__() + self.oldal = oldal + self.tipus = "hiba" + doboz = QVBoxLayout(self) + doboz.setContentsMargins(0, 0, 0, 0) + doboz.setSpacing(12) + self.cim = cimke("", "h2") + doboz.addWidget(self.cim) + self.leiras = cimke("", "masodlagos", True) + doboz.addWidget(self.leiras) + doboz.addWidget(cimke("Cím", "kartyacim")) + self.cim_mezo = QLineEdit() + self.cim_mezo.setMaxLength(120) + doboz.addWidget(self.cim_mezo) + doboz.addWidget(cimke("Leírás", "kartyacim")) + self.szoveg_mezo = QPlainTextEdit() + self.szoveg_mezo.setMinimumHeight(160) + doboz.addWidget(self.szoveg_mezo) + + self.diag = Kartya(terkoz=8, margo=16) + self.diag_jelolo = QCheckBox("Diagnosztika csatolása (ajánlott, segít megtalálni a hibát)") + self.diag.elrendezes.addWidget(self.diag_jelolo) + lista = "\n".join(f" - {t}" for t in diagnosztika.TARTALOM) + self.diag.elrendezes.addWidget( + cimke( + f"Ha bejelölöd, egy jelentést is csatolunk a gépedről:\n{lista}\n\n" + f"Feltöltés előtt kitakarjuk: {diagnosztika.KITAKARAS}. Jelszó nem kerül bele. " + "Titkosított kapcsolaton megy el, csak a RaveOS fejlesztői látják, és 30 nap után törlődik.", + "halvany", + True, + ) + ) + self.megnez = gomb("Megnézem, mi kerül bele", kicsi=True) + self.megnez.clicked.connect(self._megnez) + self.diag.elrendezes.addWidget(self.megnez, 0, Qt.AlignmentFlag.AlignLeft) + doboz.addWidget(self.diag) + + doboz.addWidget( + cimke( + "A jegyhez mindig csatoljuk a RaveOS-verziót, a kernelt, az asztali környezetet és a videokártya nevét.", + "halvany", + True, + ) + ) + self.hiba = uzenetsav("") + doboz.addWidget(self.hiba) + gombok = QHBoxLayout() + gombok.addStretch(1) + self.megse = gomb("Mégse") + self.megse.clicked.connect(self.oldal.urlap_bezar) + self.kuld = gomb("Beküldés", fo=True) + self.kuld.clicked.connect(self._kuld) + gombok.addWidget(self.megse) + gombok.addWidget(self.kuld) + doboz.addLayout(gombok) + doboz.addStretch(1) + + def nyit(self, tipus): + self.tipus = tipus + self.cim_mezo.clear() + self.szoveg_mezo.clear() + self.diag_jelolo.setChecked(False) + self.hiba.hide() + self.kuld.setEnabled(True) + if tipus == "hiba": + self.cim.setText("ÚJ HIBAJEGY") + self.leiras.setText("Írd le, mi történt, és mit csináltál előtte. Ha válaszolunk, itt fogod látni.") + self.cim_mezo.setPlaceholderText("Például: Nem indul a Steam a frissítés után") + self.szoveg_mezo.setPlaceholderText("Mi a hiba, mikor jelentkezik, mit próbáltál már?") + self.diag.show() + else: + self.cim.setText("ÚJ ÖTLET") + self.leiras.setText( + "Mi hiányzik a RaveOS-ből? Az ötleted a jóváhagyásunk után jelenik meg a közös listában, " + "ahol mások is támogathatják." + ) + self.cim_mezo.setPlaceholderText("Például: Sötét és világos téma váltó") + self.szoveg_mezo.setPlaceholderText("Mit szeretnél, és miért lenne hasznos?") + self.diag.hide() + self.cim_mezo.setFocus() + + def _megnez(self): + self.megnez.setEnabled(False) + self.megnez.setText("Összeállítás...") + + def kesz(szoveg): + self.megnez.setEnabled(True) + self.megnez.setText("Megnézem, mi kerül bele") + JelentesAblak(self, szoveg).exec() + + hatterben(diagnosztika.jelentes, kesz=kesz) + + def _kuld(self): + cim = self.cim_mezo.text().strip() + szoveg = self.szoveg_mezo.toPlainText().strip() + if len(cim) < 3 or len(szoveg) < 10: + self.hiba.setText("Adj meg egy legalább 3 karakteres címet és egy legalább 10 karakteres leírást.") + self.hiba.show() + return + self.hiba.hide() + self.kuld.setEnabled(False) + self.kuld.setText("Küldés...") + csatol = self.tipus == "hiba" and self.diag_jelolo.isChecked() + tipus = self.tipus + + def munka(): + naplo = jegyek_api.naplo_feltolt(diagnosztika.jelentes()) if csatol else None + return jegyek_api.uj_jegy(tipus, cim, szoveg, diagnosztika.rendszer_osszegzes(), naplo) + + hatterben(munka, kesz=self._kuldve, hiba=self._kuldes_hiba) + + def _kuldve(self, jegy): + self.kuld.setText("Beküldés") + self.oldal.bekuldve(jegy) + + def _kuldes_hiba(self, uzenet): + self.kuld.setEnabled(True) + self.kuld.setText("Beküldés") + self.hiba.setText(jegyek_api.HIBAUZENETEK.get(uzenet, f"A beküldés nem sikerült: {uzenet}")) + self.hiba.show() + + +class Reszletek(QWidget): + def __init__(self, oldal): + super().__init__() + self.oldal = oldal + self.jegy_id = None + doboz = QVBoxLayout(self) + doboz.setContentsMargins(0, 0, 0, 0) + doboz.setSpacing(12) + self.cim = cimke("", "h2", True) + doboz.addWidget(self.cim) + self.meta = QHBoxLayout() + self.meta.setSpacing(10) + doboz.addLayout(self.meta) + self.lezart = uzenetsav("") + doboz.addWidget(self.lezart) + gorgeto = QScrollArea() + gorgeto.setWidgetResizable(True) + self.szal_doboz = QWidget() + self.szal = QVBoxLayout(self.szal_doboz) + self.szal.setContentsMargins(0, 0, 8, 0) + self.szal.setSpacing(10) + gorgeto.setWidget(self.szal_doboz) + self.gorgeto = gorgeto + doboz.addWidget(gorgeto, 1) + self.valasz_doboz = QWidget() + vd = QVBoxLayout(self.valasz_doboz) + vd.setContentsMargins(0, 0, 0, 0) + vd.setSpacing(8) + self.valasz_mezo = QPlainTextEdit() + self.valasz_mezo.setPlaceholderText("Válasz a RaveOS csapatnak") + self.valasz_mezo.setFixedHeight(96) + vd.addWidget(self.valasz_mezo) + self.hiba = uzenetsav("") + vd.addWidget(self.hiba) + sor = QHBoxLayout() + sor.addStretch(1) + self.kuld = gomb("Válasz küldése", fo=True) + self.kuld.clicked.connect(self._kuld) + sor.addWidget(self.kuld) + vd.addLayout(sor) + doboz.addWidget(self.valasz_doboz) + + def betolt(self, jegy): + self.jegy_id = jegy["id"] + self.cim.setText(jegy["cim"]) + while self.meta.count(): + w = self.meta.takeAt(0).widget() + if w: + w.deleteLater() + self.meta.addWidget(cimke("HIBAJEGY" if jegy["tipus"] == "hiba" else "ÖTLET", "monozold")) + self.meta.addWidget(allapotcimke(jegy["allapot"])) + self.meta.addWidget(cimke(f"#{jegy['id']} · {ido(jegy['letrehozva'])}", "halvany")) + if jegy["tipus"] == "otlet": + self.meta.addWidget(cimke("nyilvános" if jegy["nyilvanos"] else "jóváhagyásra vár", "halvany")) + self.meta.addStretch(1) + if jegy.get("torles"): + self.lezart.setText( + f"Ez a jegy le van zárva, és {ido(jegy['torles'])} után automatikusan törlődik. " + "Ha a hiba újra jelentkezik, nyiss új jegyet." + ) + self.lezart.show() + self.valasz_doboz.hide() + else: + self.lezart.hide() + self.valasz_doboz.show() + while self.szal.count(): + w = self.szal.takeAt(0).widget() + if w: + w.deleteLater() + for u in jegy.get("uzenetek", []): + self.szal.addWidget(Uzenet(u)) + self.szal.addStretch(1) + self.hiba.hide() + self.valasz_mezo.clear() + + def _kuld(self): + szoveg = self.valasz_mezo.toPlainText().strip() + if not szoveg: + return + self.kuld.setEnabled(False) + jegy_id = self.jegy_id + + def munka(): + jegyek_api.valasz(jegy_id, szoveg) + return jegyek_api.jegy(jegy_id) + + def kesz(jegy): + self.kuld.setEnabled(True) + self.betolt(jegy) + self.oldal.lista_frissit() + + def hiba(uzenet): + self.kuld.setEnabled(True) + if uzenet == "nincs_jegy": + self.oldal.jegy_eltunt() + return + self.hiba.setText(jegyek_api.HIBAUZENETEK.get(uzenet, f"A küldés nem sikerült: {uzenet}")) + self.hiba.show() + + hatterben(munka, kesz=kesz, hiba=hiba) + + +class ErtekelesLap(QWidget): + SZINTEK = {1: "Nagyon rossz", 2: "Rossz", 3: "Elmegy", 4: "Jó", 5: "Kiváló"} + MAX = 500 + + def __init__(self, oldal): + super().__init__() + self.oldal = oldal + self.mentett = None + self.szerkesztve = False + self.kuldes = False + doboz = QVBoxLayout(self) + doboz.setContentsMargins(0, 0, 12, 0) + doboz.setSpacing(16) + + osszesito = Kartya("A RaveOS értékelése", terkoz=8, margo=20) + sor = QHBoxLayout() + sor.setSpacing(16) + self.atlag = cimke("-", "nagyszam") + sor.addWidget(self.atlag) + jobb = QVBoxLayout() + jobb.setSpacing(4) + self.atlag_csillag = Csillagok(24) + jobb.addWidget(self.atlag_csillag) + self.darab = cimke("", "halvany") + jobb.addWidget(self.darab) + sor.addLayout(jobb) + sor.addStretch(1) + osszesito.elrendezes.addLayout(sor) + doboz.addWidget(osszesito) + + sajat = Kartya("A te értékelésed", terkoz=10, margo=20) + valaszto = QHBoxLayout() + valaszto.setSpacing(14) + self.csillagok = Csillagok(34, valaszthato=True) + self.csillagok.valasztva.connect(self._valasztva) + valaszto.addWidget(self.csillagok) + self.szint = cimke("Kattints a csillagokra", "masodlagos") + valaszto.addWidget(self.szint) + valaszto.addStretch(1) + sajat.elrendezes.addLayout(valaszto) + self.szoveg = QPlainTextEdit() + self.szoveg.setPlaceholderText("Röviden: mi tetszik, mi nem? (nem kötelező)") + self.szoveg.setFixedHeight(96) + self.szoveg.textChanged.connect(self._szoveg_valtozott) + sajat.elrendezes.addWidget(self.szoveg) + self.szamlalo = cimke(f"0 / {self.MAX}", "monohalvany") + sajat.elrendezes.addWidget(self.szamlalo, 0, Qt.AlignmentFlag.AlignRight) + sajat.elrendezes.addWidget( + cimke( + "A szöveges véleményt csak a RaveOS fejlesztői látják, nyilvánosan csak a csillagok átlaga jelenik meg. " + "Gépenként egy értékelés számít, bármikor módosíthatod.", + "halvany", + True, + ) + ) + self.hiba = uzenetsav("") + sajat.elrendezes.addWidget(self.hiba) + gombok = QHBoxLayout() + self.allapot = cimke("", "monozold") + gombok.addWidget(self.allapot) + gombok.addStretch(1) + self.kuld = gomb("Értékelés küldése", fo=True) + self.kuld.setEnabled(False) + self.kuld.clicked.connect(self._kuld) + gombok.addWidget(self.kuld) + sajat.elrendezes.addLayout(gombok) + doboz.addWidget(sajat) + doboz.addStretch(1) + + def betolt(self, adat): + self.atlag.setText(f"{adat['atlag']:.1f}".replace(".", ",") if adat["darab"] else "-") + self.atlag_csillag.beallit(adat["atlag"]) + self.darab.setText( + f"{adat['darab']} értékelés alapján" if adat["darab"] else "Még nincs értékelés. Legyél te az első!" + ) + sajat = adat.get("sajat") + if sajat == self.mentett: + return + self.mentett = sajat + self.allapot.setText(f"ELKÜLDVE: {ido(sajat['ido'])}" if sajat else "") + if not self.szerkesztve: + self.csillagok.beallit(sajat["csillag"] if sajat else 0) + self.szint.setText(self.SZINTEK[sajat["csillag"]] if sajat else "Kattints a csillagokra") + self.szoveg.blockSignals(True) + self.szoveg.setPlainText(sajat["szoveg"] if sajat else "") + self.szoveg.blockSignals(False) + self.szamlalo.setText(f"{len(self.szoveg.toPlainText())} / {self.MAX}") + self._gomb_frissit() + + def _valasztva(self, csillag): + self.szerkesztve = True + self.szint.setText(self.SZINTEK[csillag]) + self._gomb_frissit() + + def _szoveg_valtozott(self): + self.szerkesztve = True + szoveg = self.szoveg.toPlainText() + if len(szoveg) > self.MAX: + self.szoveg.blockSignals(True) + self.szoveg.setPlainText(szoveg[: self.MAX]) + self.szoveg.moveCursor(QTextCursor.MoveOperation.End) + self.szoveg.blockSignals(False) + szoveg = szoveg[: self.MAX] + self.szamlalo.setText(f"{len(szoveg)} / {self.MAX}") + self._gomb_frissit() + + def _valtozott(self): + if not self.mentett: + return True + return int(self.csillagok.ertek) != self.mentett["csillag"] or ( + self.szoveg.toPlainText().strip() != self.mentett["szoveg"] + ) + + def _gomb_frissit(self): + self.kuld.setText("Értékelés módosítása" if self.mentett else "Értékelés küldése") + szabad = self.oldal.szabad() and not self.kuldes + self.csillagok.setEnabled(szabad) + self.szoveg.setEnabled(szabad) + self.kuld.setEnabled(szabad and self.csillagok.ertek >= 1 and self._valtozott()) + + def _kuld(self): + csillag = int(self.csillagok.ertek) + szoveg = self.szoveg.toPlainText().strip() + self.hiba.hide() + self.kuldes = True + self._gomb_frissit() + self.kuld.setText("Küldés...") + + def kesz(adat): + self.kuldes = False + self.szerkesztve = False + self.mentett = None + self.betolt(adat) + self.allapot.setText("KÖSZÖNJÜK, ELMENTETTÜK") + + def hiba(uzenet): + self.kuldes = False + self.hiba.setText(jegyek_api.HIBAUZENETEK.get(uzenet, f"A küldés nem sikerült: {uzenet}")) + self.hiba.show() + self._gomb_frissit() + + hatterben(jegyek_api.ertekel, csillag, szoveg, kesz=kesz, hiba=hiba) + + +ELFOGADAS_VERZIO = 3 + +ISMERTETO = [ + ("Mire jó?", "", [ + "Itt közvetlenül a RaveOS fejlesztőinek jelezhetsz hibát, és küldhetsz ötletet. " + "Minden jegyet elolvasunk, és itt, a Centerben válaszolunk.", + ]), + ("Hogyan működik?", "szam", [ + "Hibajegy: írd le, mi történt. Ha bejelölöd, a géped diagnosztikáját is csatoljuk, ami sokat segít; " + "előtte megnézheted, mi kerül bele.", + "Válasz: ha válaszolunk, a Visszajelzés menüpont mellett szám jelenik meg, és a jegyben vissza is írhatsz.", + "Ötlet: a jóváhagyott ötletek felkerülnek a közös listára, ahol mások is támogathatják. " + "A legtöbbet támogatottakkal foglalkozunk először.", + "Értékelés: 1-5 csillaggal értékelheted a RaveOS-t, és röviden leírhatod a véleményed. A szöveget csak " + "a fejlesztők látják, a többiek csak a csillagok átlagát. Gépenként egy értékelés számít, bármikor " + "módosíthatod.", + ]), + ("Miért így?", "szam", [ + "Nem kell fiók: a géped egy névtelen azonosítót kap, ezzel látod a saját jegyeidet. " + "Újratelepítés után a régi jegyeid nem látszanak.", + "Csak azt küldjük el, amit beírsz, vagy amihez hozzájárulsz. A diagnosztikából kitakarjuk a személyes " + "adatokat, és 30 nap után törlődik.", + "A lezárt jegyek 7 nap után maguktól törlődnek.", + ]), + ("Szabályok", "szam", [ + "A Visszajelzés csak hibák jelzésére és ötletek beküldésére való.", + "Egy hibáról egy jegyet nyiss. Ha már van róla jegyed, abba írj, ne nyiss újat.", + "Ötlet beküldése előtt nézd meg az Ötletek listát. Ha van hasonló, támogasd azt, ne küldj újat.", + "Ne írj a jegybe vagy az értékelésbe személyes vagy titkos adatot: jelszót, e-mail-címet, telefonszámot vagy más emberek " + "adatait. A diagnosztikát kitakarjuk, a beírt szöveget nem.", + "Kulturált hangnemet kérünk. Sértő, fenyegető vagy trágár üzenet tiltással jár.", + "Reklámot és nem ide illő linkeket ne küldj.", + "A jegyekre nem azonnal válaszolunk, ezért kérünk, légy türelmes, és ne küldd el többször ugyanazt.", + "Visszaélés, spam, sértő vagy nem ide illő tartalom esetén a géped kitiltható a Visszajelzésből, " + "ideiglenesen vagy véglegesen. A Center minden más része ettől ugyanúgy működik.", + "Két óránként legfeljebb 4 jegyet küldhetsz. Ennél több beküldés automatikusan 24 órás tiltással jár.", + ]), +] + +HOZZAJARULAS = ( + "Az Elfogadom gombbal elfogadod a fenti szabályokat, és hozzájárulsz, hogy a beírt szöveget, a géped " + "alapadatait (RaveOS-verzió, kernel, asztali környezet, videokártya) és, ha bejelölöd, a diagnosztikát " + "elküldjük a RaveOS fejlesztőinek." +) + + +class IsmertetoAblak(QDialog): + def __init__(self, szulo, elfogadva): + super().__init__(szulo) + self.setWindowTitle("Visszajelzés") + kepernyo = szulo.screen().availableGeometry() if szulo and szulo.screen() else None + magassag = min(860, kepernyo.height() - 80) if kepernyo else 760 + self.resize(700, magassag) + kulso = QVBoxLayout(self) + kulso.setContentsMargins(0, 0, 0, 20) + kulso.setSpacing(12) + gorgeto = QScrollArea() + gorgeto.setWidgetResizable(True) + tartalom = QWidget() + gorgeto.setWidget(tartalom) + kulso.addWidget(gorgeto, 1) + doboz = QVBoxLayout(tartalom) + doboz.setContentsMargins(32, 28, 32, 8) + doboz.setSpacing(12) + doboz.addWidget(cimke("ISMERTETŐ ÉS SZABÁLYOK", "eyebrow")) + doboz.addWidget(cimke("HOGYAN MŰKÖDIK A VISSZAJELZÉS?", "h2", True)) + sorszam = 0 + for cim, jeloles, pontok in ISMERTETO: + doboz.addSpacing(4) + fejlec = cimke(cim) + fejlec.setStyleSheet("font-size: 15px; font-weight: 600;") + doboz.addWidget(fejlec) + for pont in pontok: + if not jeloles: + doboz.addWidget(cimke(pont, "masodlagos", True)) + continue + sorszam += 1 + sor = QHBoxLayout() + sor.setSpacing(10) + jel = cimke(f"{sorszam}.", "monozold") + jel.setFixedWidth(26) + sor.addWidget(jel, 0, Qt.AlignmentFlag.AlignTop) + sor.addWidget(cimke(pont, "masodlagos", True), 1) + doboz.addLayout(sor) + doboz.addStretch(1) + also = QVBoxLayout() + also.setContentsMargins(32, 0, 32, 0) + also.setSpacing(12) + kulso.addLayout(also) + gombok = QHBoxLayout() + gombok.addStretch(1) + if elfogadva: + also.addWidget(cimke("A szabályokat már elfogadtad.", "halvany")) + ok = gomb("Bezárás", fo=True) + ok.clicked.connect(self.accept) + gombok.addWidget(ok) + else: + also.addWidget(cimke(HOZZAJARULAS, "figyelem", True)) + nem = gomb("Most nem") + nem.clicked.connect(self.reject) + igen = gomb("Elfogadom", fo=True) + igen.clicked.connect(self.accept) + gombok.addWidget(nem) + gombok.addWidget(igen) + also.addLayout(gombok) + + +class OtletKartya(Kartya): + def __init__(self, otlet, szavaz): + super().__init__(terkoz=8, margo=18) + felso = QHBoxLayout() + cim = cimke(otlet["cim"], tordel=True) + cim.setStyleSheet("font-size: 15px; font-weight: 600;") + felso.addWidget(cim, 1) + felso.addWidget(allapotcimke(otlet["allapot"]), 0, Qt.AlignmentFlag.AlignTop) + self.elrendezes.addLayout(felso) + self.elrendezes.addWidget(cimke(otlet.get("leiras") or "", "masodlagos", True)) + also = QHBoxLayout() + also.addWidget(cimke(f"{otlet['szavazat']} támogató", "monohalvany")) + if otlet.get("sajat"): + also.addWidget(cimke("· a te ötleted", "monohalvany")) + also.addStretch(1) + tamogatva = otlet["sajat_szavazat"] + g = gomb("Támogatva" if tamogatva else "Támogatom", fo=not tamogatva, kicsi=True) + g.clicked.connect(lambda: szavaz(otlet["id"], not tamogatva, g)) + also.addWidget(g) + self.elrendezes.addLayout(also) + + +class Visszajelzes(Oldal): + def __init__(self, kozpont): + super().__init__(kozpont, "HIBÁK ÉS ÖTLETEK", "Visszajelzés", kitolt=True) + self.uj_hiba = self.fejlec_gomb(gomb("Új hibajegy", fo=True)) + self.uj_hiba.clicked.connect(lambda: self.urlap_nyit("hiba")) + self.uj_otlet = self.fejlec_gomb(gomb("Új ötlet")) + self.uj_otlet.clicked.connect(lambda: self.urlap_nyit("otlet")) + sugo = self.fejlec_gomb(gomb("Hogyan működik?")) + sugo.clicked.connect(self.ismerteto) + + self.elfogadas_sav = QWidget() + es = QHBoxLayout(self.elfogadas_sav) + es.setContentsMargins(0, 0, 0, 0) + es.setSpacing(12) + es.addWidget(cimke("A Visszajelzés használatához el kell fogadnod a szabályokat.", "figyelem", True), 1) + elolvas = gomb("Szabályok elolvasása", fo=True) + elolvas.clicked.connect(self.ismerteto) + es.addWidget(elolvas) + self.elfogadas_sav.hide() + self.torzs.addWidget(self.elfogadas_sav) + self.tiltas_sav = cimke("", "figyelem", True) + self.tiltas_sav.hide() + self.torzs.addWidget(self.tiltas_sav) + self.kitiltva = False + self.fulsor = Fulsor([("jegyeim", "Jegyeim"), ("otletek", "Ötletek"), ("ertekeles", "Értékelés")], self.ful) + self.torzs.addWidget(self.fulsor) + self.hiba = uzenetsav("") + self.torzs.addWidget(self.hiba) + self.fulek = Verem() + self.torzs.addWidget(self.fulek, 1) + self.tolto = QWidget() + self.tolto.hide() + self.torzs.addWidget(self.tolto, 1) + + jegyeim = QWidget() + sor = QHBoxLayout(jegyeim) + sor.setContentsMargins(0, 0, 0, 0) + sor.setSpacing(24) + bal = QVBoxLayout() + bal.setSpacing(8) + self.lista = QListWidget() + self.lista.setFixedWidth(340) + self.lista.setMinimumHeight(160) + self.lista.setVerticalScrollMode(QListWidget.ScrollMode.ScrollPerPixel) + self.lista.currentItemChanged.connect(self._kivalaszt) + bal.addWidget(self.lista, 1) + sor.addLayout(bal) + self.jobb = Verem() + self.ures = QWidget() + ud = QVBoxLayout(self.ures) + ud.setContentsMargins(0, 0, 0, 0) + self.ures_szoveg = cimke("", "masodlagos", True) + ud.addWidget(self.ures_szoveg) + ud.addStretch(1) + self.reszletek = Reszletek(self) + self.urlap = Urlap(self) + self.urlap_gorgeto = QScrollArea() + self.urlap_gorgeto.setWidgetResizable(True) + self.urlap_gorgeto.setWidget(self.urlap) + self.urlap.setContentsMargins(0, 0, 12, 0) + for w in (self.ures, self.reszletek, self.urlap_gorgeto): + self.jobb.addWidget(w) + sor.addWidget(self.jobb, 1) + self.fulek.addWidget(jegyeim) + + otletek_gorgeto = QScrollArea() + otletek_gorgeto.setWidgetResizable(True) + otletek = QWidget() + otletek_gorgeto.setWidget(otletek) + od = QVBoxLayout(otletek) + od.setContentsMargins(0, 0, 12, 0) + od.setSpacing(12) + od.addWidget( + cimke( + "Mások ötletei, amelyeket jóváhagytunk. Támogasd, amit te is szeretnél, így látjuk, mi a legfontosabb.", + "masodlagos", + True, + ) + ) + self.otlet_doboz = QVBoxLayout() + self.otlet_doboz.setSpacing(12) + od.addLayout(self.otlet_doboz) + od.addStretch(1) + self.fulek.addWidget(otletek_gorgeto) + ertekeles_gorgeto = QScrollArea() + ertekeles_gorgeto.setWidgetResizable(True) + self.ertekeles_lap = ErtekelesLap(self) + ertekeles_gorgeto.setWidget(self.ertekeles_lap) + self.fulek.addWidget(ertekeles_gorgeto) + self.jegyek = [] + self.lista_betoltve = False + self.ful("jegyeim") + + def indulaskor(self): + hatterben(jegyek_api.jegyeim, kesz=self._jelveny_frissit) + + def elfogadva(self): + return (beallitasok.ertek("visszajelzes_elfogadva", 0) or 0) >= ELFOGADAS_VERZIO + + def szabad(self): + return self.elfogadva() and not self.kitiltva + + def ismerteto(self): + if IsmertetoAblak(self, self.elfogadva()).exec() and not self.elfogadva(): + beallitasok.beallit("visszajelzes_elfogadva", ELFOGADAS_VERZIO) + self._allapot_frissit() + + def _allapot_frissit(self): + self.elfogadas_sav.setVisible(not self.elfogadva() and not self.kitiltva) + self.tiltas_sav.setVisible(self.kitiltva) + self.fulsor.setVisible(not self.kitiltva) + self.fulek.setVisible(not self.kitiltva) + self.tolto.setVisible(self.kitiltva) + for g in (self.uj_hiba, self.uj_otlet): + g.setEnabled(self.szabad()) + self.reszletek.valasz_doboz.setEnabled(self.szabad()) + for g in self.fulek.widget(1).findChildren(QPushButton): + g.setEnabled(self.szabad()) + self.ertekeles_lap._gomb_frissit() + + def _tiltas_kesz(self, adat): + self.kitiltva = bool(adat.get("kitiltva")) + if self.kitiltva: + meddig = f"eddig: {ido(adat['lejar'])}" if adat.get("lejar") else "véglegesen" + self.tiltas_sav.setText( + f"Ez a gép {meddig} ki van tiltva a Visszajelzés használatából a szabályok megsértése miatt. " + f"Ok: {adat.get('ok') or '-'}\n\nA RaveOS Center minden más része ugyanúgy működik." + ) + self._allapot_frissit() + if not self.kitiltva: + self.lista_frissit() + if self.fulek.currentIndex() == 1: + self._otletek_frissit() + elif self.fulek.currentIndex() == 2: + self._ertekeles_frissit() + + def megjelenik(self, *_): + self._allapot_frissit() + if not self.elfogadva(): + QTimer.singleShot(200, self.ismerteto) + gyorsitott("jegy_allapot", jegyek_api.allapot, kesz=self._tiltas_kesz, hiba=self._hibat_mutat, kor=60) + + def ful(self, azon): + self.fulsor.aktiv(azon) + self.fulek.setCurrentIndex({"jegyeim": 0, "otletek": 1, "ertekeles": 2}[azon]) + if azon == "otletek": + self._otletek_frissit() + elif azon == "ertekeles": + self._ertekeles_frissit() + + def _ertekeles_frissit(self): + def kesz(adat): + self.hiba.hide() + self.ertekeles_lap.betolt(adat) + + hatterben(jegyek_api.ertekeles, kesz=kesz, hiba=self._hibat_mutat) + + def jegy_eltunt(self): + self.reszletek.jegy_id = None + self.lista.setCurrentRow(-1) + self.jobb.setCurrentWidget(self.ures) + self.lista_frissit() + + def _jegy_hiba(self, uzenet): + if uzenet == "nincs_jegy": + self.jegy_eltunt() + else: + self._hibat_mutat(uzenet) + + def _hibat_mutat(self, uzenet): + self.hiba.setText(jegyek_api.HIBAUZENETEK.get(uzenet, f"Hiba történt: {uzenet}")) + self.hiba.show() + + def _jelveny_frissit(self, jegyek): + self.kozpont.jelveny(AZON, sum(1 for j in jegyek if j["uj_valasz"])) + + def lista_frissit(self, kivalaszt=None): + def kesz(jegyek): + self.hiba.hide() + self._jelveny_frissit(jegyek) + aktualis = kivalaszt or self.reszletek.jegy_id + if jegyek == self.jegyek and self.lista_betoltve and not kivalaszt: + return + self.jegyek = jegyek + self.lista_betoltve = True + self.lista.blockSignals(True) + self.lista.clear() + for j in jegyek: + elem = QListWidgetItem() + elem.setData(Qt.ItemDataRole.UserRole, j["id"]) + sor = JegySor(j) + elem.setSizeHint(sor.sizeHint()) + self.lista.addItem(elem) + self.lista.setItemWidget(elem, sor) + if j["id"] == aktualis: + self.lista.setCurrentItem(elem) + self.lista.blockSignals(False) + if self.jobb.currentWidget() is not self.urlap_gorgeto and not self.lista.currentItem(): + self.ures_szoveg.setText( + "Még nincs jegyed. Ha hibát találtál, vagy ötleted van, írj nekünk a fenti gombokkal." + if not jegyek + else "Válassz egy jegyet a listából." + ) + self.jobb.setCurrentWidget(self.ures) + + hatterben(jegyek_api.jegyeim, kesz=kesz, hiba=self._hibat_mutat) + + def _kivalaszt(self, elem, _elozo=None): + if not elem: + return + jegy_id = elem.data(Qt.ItemDataRole.UserRole) + + def kesz(jegy): + aktualis = self.lista.currentItem() + if not aktualis or aktualis.data(Qt.ItemDataRole.UserRole) != jegy_id: + return + self.reszletek.betolt(jegy) + self.jobb.setCurrentWidget(self.reszletek) + self.lista_frissit_csendben(jegy_id) + + hatterben(jegyek_api.jegy, jegy_id, kesz=kesz, hiba=self._jegy_hiba) + + def lista_frissit_csendben(self, jegy_id): + for j in self.jegyek: + if j["id"] == jegy_id and j["uj_valasz"]: + self.lista_frissit(jegy_id) + return + + def urlap_nyit(self, tipus): + self.ful("jegyeim") + self.lista.setCurrentRow(-1) + self.urlap.nyit(tipus) + self.jobb.setCurrentWidget(self.urlap_gorgeto) + + def urlap_bezar(self): + self.jobb.setCurrentWidget(self.ures) + self.lista_frissit() + + def bekuldve(self, jegy): + self.lista_frissit(jegy["id"]) + self._kivalaszt_id(jegy["id"]) + + def _kivalaszt_id(self, jegy_id): + hatterben( + jegyek_api.jegy, + jegy_id, + kesz=lambda j: (self.reszletek.betolt(j), self.jobb.setCurrentWidget(self.reszletek)), + hiba=self._jegy_hiba, + ) + + def _otletek_frissit(self): + def kesz(otletek): + self.hiba.hide() + while self.otlet_doboz.count(): + w = self.otlet_doboz.takeAt(0).widget() + if w: + w.deleteLater() + if not otletek: + self.otlet_doboz.addWidget(cimke("Még nincs közzétett ötlet.", "halvany")) + for o in otletek: + kartya = OtletKartya(o, self._szavaz) + for g in kartya.findChildren(QPushButton): + g.setEnabled(self.szabad()) + self.otlet_doboz.addWidget(kartya) + + hatterben(jegyek_api.otletek, kesz=kesz, hiba=self._hibat_mutat) + + def _szavaz(self, jegy_id, tamogat, g): + g.setEnabled(False) + hatterben(jegyek_api.szavaz, jegy_id, tamogat, kesz=lambda _: self._otletek_frissit(), hiba=self._hibat_mutat) + + +OLDAL = Visszajelzes diff --git a/raveos-center/raveos_center/proton.py b/raveos-center/raveos_center/proton.py new file mode 100644 index 0000000..bff9649 --- /dev/null +++ b/raveos-center/raveos_center/proton.py @@ -0,0 +1,125 @@ +import hashlib +import json +import re +import shutil +import tarfile +import tempfile +import urllib.request +from pathlib import Path + +from raveos_center import VERZIO + +KIADAS = "https://api.github.com/repos/GloriousEggroll/proton-ge-custom/releases/latest" +STEAM_HELYEK = [ + Path.home() / ".steam" / "root", + Path.home() / ".local" / "share" / "Steam", + Path.home() / ".var" / "app" / "com.valvesoftware.Steam" / "data" / "Steam", +] +NEV_MINTA = re.compile(r"^GE-Proton([0-9]+)-([0-9]+)(-x86_64)?$") +FEJLEC = {"User-Agent": f"RaveOS-Center/{VERZIO}"} + + +def celmappak(): + lista = [] + latott = set() + for hely in STEAM_HELYEK: + if not hely.is_dir(): + continue + valodi = hely.resolve() + if valodi in latott: + continue + latott.add(valodi) + lista.append(valodi / "compatibilitytools.d") + return lista + + +def _sorrend(nevek): + return sorted(nevek, key=lambda n: [int(x) for x in NEV_MINTA.match(n).groups()[:2]], reverse=True) + + +def _mappaban(mappa): + if not mappa.is_dir(): + return [] + return _sorrend(p.name for p in mappa.iterdir() if p.is_dir() and not p.is_symlink() and NEV_MINTA.match(p.name)) + + +def telepitett(): + nevek = set() + for mappa in celmappak(): + nevek.update(_mappaban(mappa)) + return _sorrend(nevek) + + +def regiek(): + return [(mappa, nev) for mappa in celmappak() for nev in _mappaban(mappa)[1:]] + + +def _lekeres(url, ido=30): + return urllib.request.urlopen(urllib.request.Request(url, headers=FEJLEC), timeout=ido) + + +def legujabb(): + with _lekeres(KIADAS) as valasz: + adat = json.load(valasz) + nev = adat.get("tag_name", "") + if not NEV_MINTA.match(nev): + raise RuntimeError("ismeretlen kiadásnév") + eszkozok = {e["name"]: e for e in adat.get("assets", [])} + for mappa in (f"{nev}-x86_64", nev): + tar = eszkozok.get(f"{mappa}.tar.gz") + osszeg = eszkozok.get(f"{mappa}.sha512sum") + if tar and osszeg: + return { + "nev": mappa, + "url": tar["browser_download_url"], + "meret": tar.get("size", 0), + "osszeg_url": osszeg["browser_download_url"], + } + raise RuntimeError("a kiadásból hiányzik a csomag vagy az ellenőrzőösszeg") + + +def telepit(kiadas, haladas): + mappak = celmappak() + if not mappak: + raise RuntimeError("nem található Steam, előbb indítsd el egyszer a Steamet") + with _lekeres(kiadas["osszeg_url"]) as valasz: + vart = valasz.read().decode().split()[0].lower() + if not re.fullmatch(r"[0-9a-f]{128}", vart): + raise RuntimeError("érvénytelen ellenőrzőösszeg") + mappak[0].mkdir(parents=True, exist_ok=True) + with tempfile.NamedTemporaryFile(dir=mappak[0], suffix=".tar.gz") as ideiglenes: + hasito = hashlib.sha512() + letoltve = 0 + with _lekeres(kiadas["url"], 60) as valasz: + while darab := valasz.read(1 << 20): + ideiglenes.write(darab) + hasito.update(darab) + letoltve += len(darab) + if kiadas["meret"]: + haladas(min(90, int(letoltve * 90 / kiadas["meret"]))) + ideiglenes.flush() + if hasito.hexdigest() != vart: + raise RuntimeError("a letöltött fájl ellenőrzőösszege nem egyezik") + for mappa in mappak: + mappa.mkdir(parents=True, exist_ok=True) + cel = mappa / kiadas["nev"] + if cel.is_symlink() or cel.is_file(): + cel.unlink() + elif cel.exists(): + shutil.rmtree(cel) + with tarfile.open(ideiglenes.name, "r:gz") as tar: + tagok = tar.getmembers() + if any(t.name != kiadas["nev"] and not t.name.startswith(kiadas["nev"] + "/") for t in tagok): + raise RuntimeError("váratlan tartalom a csomagban") + tar.extractall(mappa, members=tagok, filter="data") + haladas(100) + return kiadas["nev"] + + +def torol(nev, mappak=None): + if not NEV_MINTA.match(nev): + raise ValueError("érvénytelen név") + for mappa in mappak or celmappak(): + cel = mappa / nev + if cel.is_dir() and not cel.is_symlink(): + shutil.rmtree(cel) diff --git a/raveos-center/raveos_center/rendszerinfo.py b/raveos-center/raveos_center/rendszerinfo.py new file mode 100644 index 0000000..b526670 --- /dev/null +++ b/raveos-center/raveos_center/rendszerinfo.py @@ -0,0 +1,240 @@ +import json +import os +import platform +import re +import shutil +import subprocess +import threading +import time +from pathlib import Path + + +def _futtat(parancs, ido=60): + try: + kimenet = subprocess.run(parancs, capture_output=True, text=True, timeout=ido) + except (OSError, subprocess.TimeoutExpired): + return None + return kimenet + + +def os_release(): + adat = {} + try: + for sor in Path("/etc/os-release").read_text().splitlines(): + if "=" in sor: + k, _, v = sor.partition("=") + adat[k] = v.strip().strip('"') + except OSError: + pass + return adat + + +def raveos_verzio(): + adat = os_release() + nev = adat.get("NAME", "RaveOS") + verzio = adat.get("VERSION_ID") or adat.get("BUILD_ID") or "" + return f"{nev} {verzio}".strip() + + +def kernel(): + return platform.release() + + +def asztal(): + nev = os.environ.get("XDG_CURRENT_DESKTOP") or os.environ.get("DESKTOP_SESSION") or "ismeretlen" + nev = nev.split(":")[-1] + tipus = os.environ.get("XDG_SESSION_TYPE", "") + return f"{nev} ({tipus.capitalize()})" if tipus else nev + + +def cpu(): + try: + for sor in Path("/proc/cpuinfo").read_text().splitlines(): + if sor.startswith("model name"): + nev = sor.split(":", 1)[1].strip() + return re.sub(r"\s+", " ", re.sub(r"\((R|TM)\)|CPU|Processor", "", nev)).strip() + except OSError: + pass + return platform.processor() or "ismeretlen" + + +def gpuk(): + lista = [] + kimenet = _futtat(["lspci", "-mm"]) + if not kimenet or kimenet.returncode != 0: + return lista + for sor in kimenet.stdout.splitlines(): + mezok = re.findall(r'"([^"]*)"', sor) + if len(mezok) >= 3 and re.search(r"VGA|3D|Display", mezok[0]): + gyarto = mezok[1].replace("Advanced Micro Devices, Inc. ", "").replace(" Corporation", "") + gyarto = gyarto.replace("[AMD/ATI]", "AMD").strip() + nev = mezok[2] + talalat = re.search(r"\[([^\]]+)\]", nev) + lista.append(f"{gyarto} {talalat.group(1) if talalat else nev}".strip()) + return lista + + +def gpu_driverek(): + driverek = [] + for kartya in sorted(Path("/sys/class/drm").glob("card[0-9]")): + try: + nev = (kartya / "device" / "driver").resolve().name + except OSError: + continue + if nev and nev not in driverek: + driverek.append(nev) + return driverek + + +def memoria(): + try: + for sor in Path("/proc/meminfo").read_text().splitlines(): + if sor.startswith("MemTotal:"): + kb = int(sor.split()[1]) + return f"{kb / 1024 / 1024:.1f} GB" + except (OSError, ValueError): + pass + return "ismeretlen" + + +def fajlrendszer(hely="/"): + kimenet = _futtat(["findmnt", "-no", "FSTYPE", hely]) + if kimenet and kimenet.returncode == 0 and kimenet.stdout.strip(): + return kimenet.stdout.strip().splitlines()[0] + try: + for sor in reversed(Path("/proc/mounts").read_text().splitlines()): + mezok = sor.split() + if len(mezok) > 2 and mezok[1] == hely: + return mezok[2] + except OSError: + pass + return "ismeretlen" + + +def uzemido(): + try: + mp = int(float(Path("/proc/uptime").read_text().split()[0])) + except (OSError, ValueError): + return "ismeretlen" + nap, mp = divmod(mp, 86400) + ora, mp = divmod(mp, 3600) + perc = mp // 60 + if nap: + return f"{nap} nap {ora} óra" + if ora: + return f"{ora} óra {perc} perc" + return f"{perc} perc" + + +def lemez(hely="/"): + hasznalat = shutil.disk_usage(hely) + return hasznalat.used, hasznalat.total + + +UJRAINDITOS = re.compile( + r"^(linux|linux-[a-z0-9-]+|nvidia.*|mesa|lib32-mesa|amd-ucode|intel-ucode|systemd|glibc|dbus|dbus-broker)$" +) + +_frissites_zar = threading.Lock() +_frissites_tar = {"ido": 0.0, "lista": None} +FRISSITES_TAR_IDO = 600 + + +def _pacman_meretek(nevek): + db = os.environ.get("CHECKUPDATES_DB") or f"{os.environ.get('TMPDIR', '/tmp')}/checkup-db-{os.getuid()}" + kimenet = _futtat( + ["pacman", "-Sup", "--dbpath", db, "--logfile", "/dev/null", "--print-format", "%n %s"], ido=60 + ) + meretek = {} + if kimenet and kimenet.returncode == 0: + for sor in kimenet.stdout.splitlines(): + r = sor.split() + if len(r) == 2 and r[1].isdigit(): + meretek[r[0]] = int(r[1]) + return {n: meretek.get(n) for n in nevek} + + +def _meret_szoveg(szoveg): + talalat = re.match(r"([\d.,]+)\s*([kMG]?B)", szoveg.replace("\xa0", " ")) + if not talalat: + return None + szam = float(talalat.group(1).replace(",", ".")) + return int(szam * {"B": 1, "kB": 1e3, "MB": 1e6, "GB": 1e9}[talalat.group(2)]) + + +def _frissitesek_lekerdez(): + csomagok = [] + if not shutil.which("checkupdates"): + raise RuntimeError("a checkupdates nem elérhető (pacman-contrib)") + pac = _futtat(["checkupdates", "--nocolor"], ido=180) + if not pac or pac.returncode not in (0, 2): + raise RuntimeError("a pacman frissítések ellenőrzése nem sikerült") + if pac.returncode == 0: + for sor in pac.stdout.splitlines(): + r = sor.split() + if len(r) >= 4: + csomagok.append({"nev": r[0], "forras": "pacman", "regi": r[1], "uj": r[3], "meret": None}) + meretek = _pacman_meretek([c["nev"] for c in csomagok]) + for c in csomagok: + c["meret"] = meretek.get(c["nev"]) + if shutil.which("yay"): + aur = _futtat(["yay", "-Qua", "--color", "never"], ido=180) + if aur and aur.returncode in (0, 1): + for sor in aur.stdout.splitlines(): + r = sor.split() + if len(r) >= 4 and r[2] == "->": + csomagok.append({"nev": r[0], "forras": "aur", "regi": r[1], "uj": r[3], "meret": None}) + if shutil.which("flatpak"): + fp = _futtat( + ["flatpak", "remote-ls", "--updates", "--columns=application,version,download-size"], ido=180 + ) + if fp and fp.returncode == 0: + for sor in fp.stdout.splitlines(): + r = sor.split("\t") + if r and r[0] and r[0] != "Application ID": + csomagok.append({ + "nev": r[0], + "forras": "flatpak", + "regi": "", + "uj": r[1] if len(r) > 1 else "", + "meret": _meret_szoveg(r[2]) if len(r) > 2 else None, + }) + for c in csomagok: + c["ujrainditas"] = c["forras"] == "pacman" and bool(UJRAINDITOS.match(c["nev"])) + return csomagok + + +def frissitesek(friss=False): + with _frissites_zar: + tar = _frissites_tar + if not friss and tar["lista"] is not None and time.monotonic() - tar["ido"] < FRISSITES_TAR_IDO: + return [dict(c) for c in tar["lista"]] + lista = _frissitesek_lekerdez() + tar["lista"] = lista + tar["ido"] = time.monotonic() + tar["mikor"] = time.time() + return [dict(c) for c in lista] + + +def frissites_ellenorizve(): + return _frissites_tar.get("mikor") + + +def snapper_elerheto(): + return bool(shutil.which("snapper")) and Path("/etc/snapper/configs/root").exists() + + +def utolso_snapshot(): + if not shutil.which("snapper"): + return None + kimenet = _futtat(["snapper", "--jsonout", "-c", "root", "list"], ido=30) + if not kimenet or kimenet.returncode != 0: + return None + try: + lista = json.loads(kimenet.stdout).get("root", []) + except ValueError: + return None + lista = [s for s in lista if s.get("number", 0) != 0] + if not lista: + return {} + return lista[-1] diff --git a/raveos-center/raveos_center/snapshotok.py b/raveos-center/raveos_center/snapshotok.py new file mode 100644 index 0000000..f4bd097 --- /dev/null +++ b/raveos-center/raveos_center/snapshotok.py @@ -0,0 +1,116 @@ +import json +import re +import subprocess +from pathlib import Path + +from raveos_center import rendszerinfo + +SNAP_PAC = Path("/usr/share/libalpm/hooks/05-snap-pac-pre.hook") +TIPUSOK = {"pre": "csomagművelet előtt", "post": "csomagművelet után", "single": "kézi"} + + +def _futtat(parancs, ido=30): + try: + return subprocess.run(parancs, capture_output=True, text=True, timeout=ido) + except (OSError, subprocess.TimeoutExpired): + return None + + +def fajlrendszer(): + kimenet = _futtat(["findmnt", "-no", "FSTYPE", "/"]) + return kimenet.stdout.strip() if kimenet and kimenet.returncode == 0 else "" + + +def gyoker_alvolumen(): + kimenet = _futtat(["findmnt", "-no", "OPTIONS", "/"]) + if not kimenet: + return "" + for opcio in kimenet.stdout.strip().split(","): + if opcio.startswith("subvol="): + return opcio[len("subvol="):] + return "" + + +def snapshots_csatolva(): + kimenet = _futtat(["findmnt", "-no", "SOURCE", "/.snapshots"]) + return bool(kimenet and kimenet.returncode == 0 and kimenet.stdout.strip()) + + +def snap_pac(): + return SNAP_PAC.exists() + + +def _elem(nyers): + tipus = nyers.get("type", "single") + adat = nyers.get("userdata") or {} + return { + "szam": nyers.get("number", 0), + "datum": (nyers.get("date") or "")[:16], + "tipus": tipus, + "tipus_nev": "telepítés" if adat.get("important") == "yes" and tipus == "single" else TIPUSOK.get(tipus, tipus), + "leiras": nyers.get("description") or "", + "pre_szam": nyers.get("pre-number"), + "aktiv": bool(nyers.get("active")), + } + + +def allapot(): + fs = fajlrendszer() + adat = { + "fajlrendszer": fs, + "snapper": rendszerinfo.snapper_elerheto(), + "snap_pac": snap_pac(), + "alvolumen": gyoker_alvolumen(), + "csatolva": snapshots_csatolva(), + "lista": [], + "hiba": "", + } + if fs != "btrfs" or not adat["snapper"]: + return adat + kimenet = _futtat(["snapper", "--jsonout", "-c", "root", "list"]) + if not kimenet or kimenet.returncode != 0: + adat["hiba"] = (kimenet.stderr.strip() if kimenet else "") or "A snapshotok listája nem olvasható." + return adat + try: + nyers = json.loads(kimenet.stdout).get("root", []) + except ValueError: + adat["hiba"] = "A snapper válasza nem értelmezhető." + return adat + adat["lista"] = [_elem(n) for n in reversed(nyers) if n.get("number", 0) != 0] + return adat + + +def ervenyes_leiras(szoveg): + return 1 <= len(szoveg) <= 80 and all(c.isalnum() or c.isspace() or c in "._:,()-" for c in szoveg) + + +def kernel_valtozas(szam): + gyoker = Path(f"/.snapshots/{int(szam)}/snapshot") + valtozasok = [] + try: + if not (gyoker / "usr/lib/modules").is_dir(): + return None + pkgbase_fajlok = list(gyoker.glob("usr/lib/modules/*/pkgbase")) + except OSError: + return None + for pb in pkgbase_fajlok: + try: + nev = pb.read_text().strip() + except OSError: + continue + if not re.fullmatch(r"[a-z0-9][a-z0-9._+-]*", nev): + continue + regi = _futtat(["pacman", "--dbpath", str(gyoker / "var/lib/pacman"), "-Q", nev]) + most = _futtat(["pacman", "-Q", nev]) + regi_v = regi.stdout.split()[1] if regi and regi.returncode == 0 and len(regi.stdout.split()) > 1 else "" + most_v = most.stdout.split()[1] if most and most.returncode == 0 and len(most.stdout.split()) > 1 else "" + if regi_v and regi_v != most_v: + cache = any( + not p.name.endswith(".sig") for p in Path("/var/cache/pacman/pkg").glob(f"{nev}-{regi_v}-*.pkg.tar.*") + ) + valtozasok.append({"nev": nev, "regi": regi_v, "most": most_v, "cache": cache}) + return valtozasok + + +def snapper_telepitheto(adat): + return adat["fajlrendszer"] == "btrfs" and not adat["snapper"] and adat["alvolumen"] == "/@" and adat["csatolva"] diff --git a/raveos-center/raveos_center/stilus.py b/raveos-center/raveos_center/stilus.py new file mode 100644 index 0000000..383e736 --- /dev/null +++ b/raveos-center/raveos_center/stilus.py @@ -0,0 +1,436 @@ +HATTER = "#1a1b1d" +FELULET = "#232427" +AKTIV = "#2f3034" +KERET = "#34363a" +KERET_EROS = "#44464b" +SZOVEG = "#e8e8e6" +SZOVEG_MASOD = "#b7b9bd" +SZOVEG_HALVANY = "#9a9ca1" +ZOLD = "#62a052" +ZOLD_HOVER = "#7dbb6c" +ZOLDON_SZOVEG = "#1a1b1d" +SARGA = "#d9a441" +SARGA_PANEL = "#2e2a20" + +BETU = "Noto Sans" +CIM_BETU = "Noto Sans" +MONO = "Noto Sans Mono" + +QSS = f""" +QWidget {{ + background: {HATTER}; + color: {SZOVEG}; + font-family: "{BETU}"; + font-size: 14px; +}} +QLabel {{ + background: transparent; +}} +QScrollArea, QScrollArea > QWidget > QWidget {{ + background: transparent; + border: none; +}} +QScrollBar:vertical {{ + background: transparent; + width: 10px; + margin: 0; +}} +QScrollBar::handle:vertical {{ + background: {KERET_EROS}; + min-height: 32px; +}} +QScrollBar::add-line, QScrollBar::sub-line {{ + height: 0; +}} +QScrollBar::add-page, QScrollBar::sub-page {{ + background: transparent; +}} +#oldalsav {{ + background: {FELULET}; + border-right: 1px solid {KERET}; +}} +#oldalsav QWidget {{ + background: transparent; +}} +#menupont {{ + background: transparent; + border: none; + color: {SZOVEG_MASOD}; + text-align: left; + padding: 0 12px; + min-height: 44px; + font-weight: 500; +}} +#menupont:hover {{ + background: {AKTIV}; +}} +#menupont[aktiv="true"] {{ + background: {AKTIV}; + color: {SZOVEG}; + font-weight: 600; +}} +#jelveny, #oldalsav #jelveny {{ + background: {ZOLD}; + color: {ZOLDON_SZOVEG}; + font-family: "{MONO}"; + font-size: 11px; + padding: 1px 6px; +}} +#markanev {{ + font-family: "{CIM_BETU}"; + font-size: 24px; + font-weight: 700; + letter-spacing: 1px; +}} +#markaalcim, #lablec {{ + font-family: "{MONO}"; + font-size: 11px; + color: {SZOVEG_HALVANY}; + letter-spacing: 2px; +}} +#eyebrow {{ + font-family: "{MONO}"; + font-size: 12px; + color: {ZOLD}; + letter-spacing: 2px; +}} +#h1 {{ + font-family: "{CIM_BETU}"; + font-size: 44px; + font-weight: 700; + letter-spacing: 1px; +}} +#h2 {{ + font-family: "{CIM_BETU}"; + font-size: 22px; + font-weight: 700; + letter-spacing: 1px; +}} +#kartya {{ + background: {FELULET}; + border: 1px solid {KERET}; +}} +#kartya QWidget {{ + background: transparent; +}} +#kartya QPushButton {{ + background: transparent; +}} +#kartya QPushButton:hover {{ + background: {AKTIV}; +}} +#kartya QPushButton[fo="true"] {{ + background: {ZOLD}; + color: {ZOLDON_SZOVEG}; +}} +#kartya QPushButton[fo="true"]:hover {{ + background: {ZOLD_HOVER}; +}} +#kartya QPushButton:disabled, #kartya QPushButton[fo="true"]:disabled {{ + background: {KERET}; + color: {SZOVEG_HALVANY}; + border-color: {KERET}; +}} +#kartyacim {{ + font-size: 12px; + color: {SZOVEG_HALVANY}; + letter-spacing: 1px; +}} +#nagyszam {{ + font-family: "{CIM_BETU}"; + font-size: 30px; + font-weight: 700; +}} +#nagyszam[zold="true"] {{ + color: {ZOLD}; +}} +#masodlagos {{ + font-size: 13px; + color: {SZOVEG_MASOD}; +}} +#halvany {{ + font-size: 13px; + color: {SZOVEG_HALVANY}; +}} +#mono {{ + font-family: "{MONO}"; + font-size: 13px; +}} +#monohalvany {{ + font-family: "{MONO}"; + font-size: 13px; + color: {SZOVEG_HALVANY}; +}} +#monozold {{ + font-family: "{MONO}"; + font-size: 12px; + color: {ZOLD}; +}} +QPushButton {{ + min-height: 44px; + padding: 0 20px; + background: transparent; + color: {SZOVEG}; + border: 1px solid {KERET_EROS}; + font-weight: 500; +}} +QPushButton:hover {{ + background: {AKTIV}; +}} +QPushButton:disabled {{ + color: {SZOVEG_HALVANY}; + border-color: {KERET}; +}} +QPushButton[fo="true"] {{ + background: {ZOLD}; + color: {ZOLDON_SZOVEG}; + border: none; + font-weight: 600; +}} +QPushButton[fo="true"]:hover {{ + background: {ZOLD_HOVER}; +}} +QPushButton[fo="true"]:disabled {{ + background: {KERET}; + color: {SZOVEG_HALVANY}; +}} +QPushButton[kicsi="true"] {{ + min-height: 32px; + padding: 0 12px; + font-size: 12px; +}} +#csempe {{ + background: {FELULET}; + border: 1px solid {KERET}; + text-align: left; + min-height: 120px; + padding: 0; +}} +#csempe:hover {{ + border-color: {ZOLD}; +}} +#csempe:disabled {{ + border-color: {KERET}; +}} +#csempe:disabled QLabel {{ + color: {SZOVEG_HALVANY}; +}} +#sav {{ + background: {KERET}; + border: none; +}} +#sav::chunk {{ + background: {ZOLD}; +}} +#elvalaszto {{ + background: {AKTIV}; + max-height: 1px; + min-height: 1px; +}} +#link {{ + color: {ZOLD}; + font-size: 13px; + font-weight: 600; +}} +QLineEdit, QTextEdit, QPlainTextEdit {{ + background: {HATTER}; + border: 1px solid {KERET_EROS}; + padding: 8px 10px; + selection-background-color: {ZOLD}; + selection-color: {ZOLDON_SZOVEG}; +}} +QLineEdit:focus, QTextEdit:focus, QPlainTextEdit:focus {{ + border-color: {ZOLD}; +}} +QListWidget {{ + background: {FELULET}; + border: 1px solid {KERET}; + outline: none; +}} +QListWidget::item {{ + border-bottom: 1px solid {KERET}; +}} +QListWidget::item:selected {{ + background: {AKTIV}; +}} +QCheckBox {{ + background: transparent; + spacing: 10px; +}} +QCheckBox::indicator {{ + width: 18px; + height: 18px; + border: 1px solid {KERET_EROS}; + background: {HATTER}; +}} +QCheckBox::indicator:checked {{ + background: {ZOLD}; + border-color: {ZOLD}; +}} +#ful {{ + background: transparent; + border: none; + border-bottom: 2px solid transparent; + color: {SZOVEG_MASOD}; + min-height: 40px; + padding: 0 4px; + margin-right: 20px; + font-weight: 500; +}} +#ful:hover {{ + color: {SZOVEG}; + background: transparent; +}} +#ful[aktiv="true"] {{ + color: {SZOVEG}; + border-bottom-color: {ZOLD}; + font-weight: 600; +}} +#fulsor {{ + border-bottom: 1px solid {KERET}; +}} +#allapotcimke {{ + font-family: "{MONO}"; + font-size: 11px; + padding: 2px 8px; + border: 1px solid {KERET_EROS}; + color: {SZOVEG_MASOD}; +}} +#allapotcimke[szin="zold"] {{ + border-color: {ZOLD}; + color: {ZOLD}; +}} +#allapotcimke[szin="sarga"] {{ + border-color: {SARGA}; + color: {SARGA}; +}} +#figyelem {{ + background: {SARGA_PANEL}; + border: 1px solid {SARGA}; + color: #f0dcb0; + padding: 10px 12px; +}} +#uzenet {{ + background: {FELULET}; + border: 1px solid {KERET}; +}} +#uzenet[csapat="true"] {{ + border-left: 3px solid {ZOLD}; +}} +#uzenet QWidget {{ + background: transparent; +}} +QTableWidget {{ + background: {FELULET}; + border: 1px solid {KERET}; + gridline-color: {KERET}; + outline: none; +}} +QTableWidget::item {{ + border-bottom: 1px solid {AKTIV}; + padding: 0 8px; +}} +QHeaderView::section {{ + background: {FELULET}; + color: {SZOVEG_HALVANY}; + border: none; + border-bottom: 1px solid {KERET}; + padding: 8px; + font-size: 12px; + letter-spacing: 1px; +}} +QTableWidget::indicator, QTableView::indicator {{ + width: 16px; + height: 16px; + border: 1px solid {KERET_EROS}; + background: {HATTER}; +}} +QTableWidget::indicator:checked {{ + background: {ZOLD}; + border-color: {ZOLD}; +}} +#naplo {{ + background: #111214; + border: 1px solid {KERET}; + font-family: "{MONO}"; + font-size: 12px; + color: {SZOVEG_MASOD}; +}} +#pill {{ + background: transparent; + color: {SZOVEG_MASOD}; + border: 1px solid {KERET_EROS}; + min-height: 34px; + padding: 0 14px; + font-size: 13px; +}} +#pill:hover {{ + background: {AKTIV}; +}} +#pill[aktiv="true"] {{ + background: {ZOLD}; + color: {ZOLDON_SZOVEG}; + border-color: {ZOLD}; + font-weight: 600; +}} +#kezdobetu {{ + background: {AKTIV}; + border: 1px solid {KERET_EROS}; + color: {ZOLD}; + font-size: 20px; + font-weight: 700; +}} +#kartya #appsor {{ + border-bottom: 1px solid {AKTIV}; +}} +#kartya #appsor[utolso="true"] {{ + border-bottom: none; +}} +#kartya #appsor:hover {{ + background: #27282b; +}} +#kijelessav {{ + background: {FELULET}; + border-top: 1px solid {ZOLD}; +}} +#kijelessav QWidget {{ + background: transparent; +}} +#kijelessav QPushButton {{ + border: 1px solid {KERET_EROS}; +}} +#kijelessav QPushButton:hover {{ + background: {AKTIV}; +}} +#kijelessav QPushButton[fo="true"] {{ + border: none; + background: {ZOLD}; + color: {ZOLDON_SZOVEG}; +}} +#kiemelt {{ + border-left: 3px solid {ZOLD}; +}} +QComboBox {{ + background: {HATTER}; + border: 1px solid {KERET_EROS}; + padding: 4px 10px; + min-height: 26px; + font-size: 12px; +}} +QComboBox:focus {{ + border-color: {ZOLD}; +}} +QComboBox QAbstractItemView {{ + background: {FELULET}; + border: 1px solid {KERET_EROS}; + selection-background-color: {AKTIV}; + selection-color: {SZOVEG}; + outline: none; +}} +QToolTip {{ + background: {FELULET}; + color: {SZOVEG}; + border: 1px solid {KERET_EROS}; + padding: 6px; +}} +""" |