diff --git a/.vscode/settings.json b/.vscode/settings.json index 9e27cb1..72cf823 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -10,8 +10,11 @@ "qt-qml.qmlls.enabled": true, "qt-qml.qmlls.useQmlImportPathEnvVar": true, "qt-qml.qmlls.additionalImportPaths": [ + "/opt/homebrew/share/qt/qml", "${workspaceFolder}/src/qml", "${workspaceFolder}/src/qml/pages", - "${workspaceFolder}/build/rocontrol" + "${workspaceFolder}/build/rocontrol", + "${workspaceFolder}/build-copilot/rocontrol", + "${workspaceFolder}/build-codex/rocontrol" ] } diff --git a/docs/BUILDING.md b/docs/BUILDING.md index c00ca38..14ee2c9 100644 --- a/docs/BUILDING.md +++ b/docs/BUILDING.md @@ -3,6 +3,7 @@ This guide covers building ro-Control from source on Linux systems with Qt 6 and CMake. The primary target is Fedora KDE Desktop, with official release artifacts for `x86_64`, `aarch64`, `noarch`, and `src`. +For Ro-ASD ISO integration, package validation should be treated as Fedora 43 KDE specific. --- @@ -49,6 +50,8 @@ sudo dnf install \ Official release outputs do not include `i686`. Driver install/update/remove workflows are supported on `x86_64` and `aarch64` release builds. +RPM validation for repo publication should be performed in a Fedora 43 build +environment so no newer Qt ABI leaks into the package metadata. Runtime tools used by diagnostics and driver operations: diff --git a/docs/PACKAGING-FEDORA43.md b/docs/PACKAGING-FEDORA43.md new file mode 100644 index 0000000..f8ade1c --- /dev/null +++ b/docs/PACKAGING-FEDORA43.md @@ -0,0 +1,55 @@ +# Fedora 43 Packaging Contract + +This note captures the packaging requirements for publishing `ro-control` into +the Ro-ASD repository for direct ISO installation. + +## Target environment + +- Fedora 43 KDE +- Install path must work with: + +```bash +dnf -y --refresh --setopt=install_weak_deps=False install ro-control +rpm -q ro-control +command -v ro-control +``` + +## Required package shape + +- `ro-control.` must provide `/usr/bin/ro-control` +- `ro-control-common.noarch` must contain shared assets only +- `ro-control.` may depend on `ro-control-common = %{version}-%{release}` + +## Forbidden ABI dependencies + +These must not appear in `rpm -qpR ro-control-*.rpm`: + +- `Qt_6.10` +- `Qt_6.10_PRIVATE_API` +- any `PRIVATE_API` symbol requirement + +## Build rules + +- Build in Fedora 43, not `fedora:latest`, Fedora 44, or Rawhide +- Do not add Qt private module link targets +- Do not include `#include ` headers +- Do not require `qt6-qtbase-private-devel` unless the code proves it is needed + +## Acceptance checks + +```bash +dnf clean all +dnf -y --refresh --setopt=install_weak_deps=False install ro-control +rpm -q ro-control +command -v ro-control +ldd -r /usr/bin/ro-control +rpm -q --whatprovides /usr/bin/ro-control +rpm -qpR ro-control-*.rpm +rpm -qpl ro-control-*.rpm | grep /usr/bin/ro-control +``` + +Expected provider for `/usr/bin/ro-control`: + +```text +ro-control +``` diff --git a/i18n/ro-control_tr.ts b/i18n/ro-control_tr.ts index f36e5e6..ddc9bb0 100644 --- a/i18n/ro-control_tr.ts +++ b/i18n/ro-control_tr.ts @@ -24,8 +24,8 @@ - I reviewed the NVIDIA license terms - NVIDIA lisans kosullarini inceledim + I reviewed the official NVIDIA license outside ro-Control + Resmi NVIDIA lisansını ro-Control dışında inceledim @@ -75,18 +75,18 @@ - Not Detected - Algilanmadi + No NVIDIA GPU + NVIDIA GPU bulunamadı - Latest: %1 - En guncel: %1 + Official latest: %1 + Resmi en güncel sürüm: %1 - Latest: Unknown - En guncel: Bilinmiyor + Official latest: Unavailable + Resmi en güncel sürüm: Kullanılamıyor @@ -100,13 +100,17 @@ - Secure Boot: Disabled / Unknown - Secure Boot: Kapali / Bilinmiyor + Secure Boot: Disabled + Secure Boot: Kapalı + + + Secure Boot: Unknown + Secure Boot: Bilinmiyor - Use safe guided operations for install, update, and cleanup. - Kurulum, guncelleme ve temizlik icin guvenli yonlendirilmis islemleri kullanin. + Latest version information is resolved from the official NVIDIA Unix driver page. Fedora workflow guidance follows the official NVIDIA Fedora installation guide. + En güncel sürüm bilgisi resmi NVIDIA Unix sürücü sayfasından alınır. Fedora akışı resmi NVIDIA Fedora kurulum rehberini takip eder. @@ -159,13 +163,13 @@ - Checking repository for updates... - Depo guncellemeleri denetleniyor... + Checking official NVIDIA driver sources... + Resmi NVIDIA sürücü kaynakları denetleniyor... - Applying latest online version... - En guncel cevrimici surum uygulaniyor... + Applying latest available driver... + Mevcut en güncel sürücü uygulanıyor... @@ -201,18 +205,38 @@ Install Latest - En Günceli Kur + En Son Sürümü Kur Apply Latest En Son Sürümü Uygula + + Install Latest (%1) + En Son Sürümü Kur (%1) + + + Apply Latest (%1) + En Son Sürümü Uygula (%1) + Apply Selected Seçileni Uygula + + Version Selection + Sürüm Seçimi + + + Use this area to test or switch to an older repository version. + Bu alanı eski bir depo sürümünü denemek veya ona geçmek için kullanın. + + + Maintenance + Bakım + Deep Clean @@ -331,22 +355,40 @@ - Refresh - Yenile + GPU telemetry is unavailable on this architecture unless nvidia-smi or DRM hwmon metrics are exposed. + Bu mimaride `nvidia-smi` veya DRM hwmon metrikleri sunulmadıkça GPU telemetrisi kullanılamaz. + + + GPU telemetry is unavailable because nvidia-smi or DRM hwmon metrics could not be read. + `nvidia-smi` veya DRM hwmon metrikleri okunamadığı için GPU telemetrisi kullanılamıyor. + + + GPU telemetry is being read from DRM and hwmon fallbacks. + GPU telemetrisi DRM ve hwmon yedek kaynaklarından okunuyor. + + + GPU telemetry output could not be parsed. + GPU telemetri çıktısı ayrıştırılamadı. + + + GPU telemetry output did not contain usable metrics. + GPU telemetri çıktısı kullanılabilir metrikler içermiyor. + + + GPU telemetry is being read from nvidia-smi. + GPU telemetrisi `nvidia-smi` üzerinden okunuyor. NvidiaDetector - Not Installed / Unknown - Kurulu Değil / Bilinmiyor + Not Installed + Kurulu Değil - - - None - Yok + Unavailable + Kullanılamıyor @@ -390,8 +432,8 @@ Yedek Acik Surucu: %6 - Disabled / Unknown - Devre Dışı / Bilinmiyor + Unknown + Bilinmiyor @@ -423,8 +465,8 @@ Yedek Acik Surucu: %6 - The proprietary NVIDIA driver is subject to NVIDIA's software license. Review the official NVIDIA license before installation: %1 - Kapali kaynak NVIDIA surucusu, NVIDIA yazilim lisansina tabidir. Kurulumdan once resmi NVIDIA lisansini inceleyin: %1 + The proprietary NVIDIA driver is subject to NVIDIA's software license. ro-Control cannot audit or review the closed-source license text for you. Review the official NVIDIA license before installation: %1 + Kapalı kaynak NVIDIA sürücüsü NVIDIA yazılım lisansına tabidir. ro-Control kapalı kaynak lisans metnini sizin için denetleyemez veya inceleyemez. Kurulumdan önce resmi NVIDIA lisansını inceleyin: %1 @@ -568,8 +610,8 @@ Yedek Acik Surucu: %6 LanguageManager - System Default - Sistem Varsayilani + System + Sistem @@ -589,7 +631,7 @@ Yedek Acik Surucu: %6 Turkish - Turkce + Türkçe @@ -612,18 +654,26 @@ Yedek Acik Surucu: %6 - Online NVIDIA packages were found. You can download and install the driver now. - Çevrimiçi NVIDIA paketleri bulundu. Sürücüyü şimdi indirip kurabilirsiniz. + Official NVIDIA driver sources are reachable. You can install the driver now. + Resmi NVIDIA sürücü kaynaklarına erişilebiliyor. Sürücüyü şimdi kurabilirsiniz. - Online NVIDIA driver found. Latest remote version: %1 - Çevrimiçi NVIDIA sürücüsü bulundu. En güncel uzak sürüm: %1 + Latest official NVIDIA driver version: %1 + Resmi en güncel NVIDIA sürümü: %1 - No online NVIDIA package catalog was found. RPM Fusion may not be configured yet. - Çevrimiçi NVIDIA paket kataloğu bulunamadı. RPM Fusion henüz yapılandırılmamış olabilir. + No official NVIDIA driver version could be retrieved. + Resmi NVIDIA sürüm bilgisi alınamadı. + + + Official NVIDIA update found: %1 + Resmi NVIDIA güncellemesi bulundu: %1 + + + Driver matches the latest official NVIDIA production branch. + Sürücü resmi NVIDIA üretim dalındaki en güncel sürümle eşleşiyor. @@ -684,15 +734,6 @@ Yedek Acik Surucu: %6 - No available versions found. - Hiçbir uygun sürüm bulunamadı. - - - - Available versions: %1 - Mevcut sürümler: %1 - - Starting update check... Güncelleme denetimi başlatılıyor... @@ -742,8 +783,8 @@ Yedek Acik Surucu: %6 UiPreferencesManager - Follow System - Sistemi Takip Et + System + Sistem diff --git a/packaging/rpm/README.md b/packaging/rpm/README.md index e7b6671..8746c73 100644 --- a/packaging/rpm/README.md +++ b/packaging/rpm/README.md @@ -8,6 +8,8 @@ This directory contains the RPM recipe for ro-Control. - Require translation tooling so localized builds are never emitted partially - Run the upstream Qt test suite during `%check` - Publish GitHub Release RPMs for `x86_64`, `aarch64`, `noarch`, and `src` +- Keep the main binary package installable on Fedora 43 KDE via `dnf install ro-control` +- Avoid all Qt private ABI dependencies in shipped RPM metadata ## Source archive expectations @@ -28,6 +30,32 @@ not need to preserve a specific upstream folder name. - `kf6-qqc2-desktop-style` - `polkit-devel` +Do not add `qt6-qtbase-private-devel` unless the codebase actually requires a +Qt private header. The Fedora 43 package must not emit `Qt_*_PRIVATE_API` +runtime dependencies. + +## Fedora 43 packaging contract + +- Build binary RPMs in a Fedora 43 environment, not `latest`, Rawhide, or Fedora 44. +- `ro-control.x86_64` or `ro-control.aarch64` must provide `/usr/bin/ro-control`. +- `ro-control-common.noarch` should contain shared assets only. +- `rpm -qpR ro-control-*.rpm` must not contain `Qt_6.10`, `Qt_6.10_PRIVATE_API`, + or any `PRIVATE_API` symbol dependency. + +## Package split + +Expected package ownership: + +- `ro-control.` + - `/usr/bin/ro-control` +- `ro-control-common.noarch` + - desktop entry + - icons + - AppStream metadata + - PolicyKit assets + - helper script + - docs and shell completions + ## Local build example ```bash @@ -67,5 +95,6 @@ verifies that `ro-control --version` matches the tagged release version before publishing assets. For non-release artifact builds, use the **RPM Artifacts** GitHub Actions -workflow (`.github/workflows/rpm-artifacts.yml`). It builds `.rpm` outputs for -both `x86_64` and `aarch64` and uploads them as workflow artifacts. +workflow (`.github/workflows/rpm-artifacts.yml`). It builds the main +architecture RPM plus the `ro-control-common.noarch` companion package and +uploads them as workflow artifacts from a Fedora 43 container. diff --git a/packaging/rpm/ro-control.spec b/packaging/rpm/ro-control.spec index 9bd4f24..2e3f38b 100644 --- a/packaging/rpm/ro-control.spec +++ b/packaging/rpm/ro-control.spec @@ -16,7 +16,6 @@ BuildRequires: gcc-c++ BuildRequires: extra-cmake-modules BuildRequires: ninja-build BuildRequires: qt6-qtbase-devel -BuildRequires: qt6-qtbase-private-devel BuildRequires: qt6-qtdeclarative-devel BuildRequires: qt6-qttools-devel BuildRequires: qt6-qtwayland-devel @@ -35,6 +34,7 @@ manage NVIDIA drivers and monitor core system metrics. %package common Summary: Shared assets for the ro-Control desktop application BuildArch: noarch +Obsoletes: %{name} < 0.2.1-1 Requires: kf6-qqc2-desktop-style Requires: polkit diff --git a/src/backend/monitor/gpumonitor.cpp b/src/backend/monitor/gpumonitor.cpp index dd86ff1..c540953 100644 --- a/src/backend/monitor/gpumonitor.cpp +++ b/src/backend/monitor/gpumonitor.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include namespace { @@ -176,6 +177,8 @@ int GpuMonitor::memoryTotalMiB() const { return m_memoryTotalMiB; } int GpuMonitor::memoryUsagePercent() const { return m_memoryUsagePercent; } +QString GpuMonitor::statusMessage() const { return m_statusMessage; } + int GpuMonitor::updateInterval() const { return m_timer.interval(); } void GpuMonitor::refresh() { @@ -200,6 +203,13 @@ void GpuMonitor::refresh() { if (!readGenericLinuxGpuMetrics(&nextTemp, &nextUtil, &nextUsed, &nextTotal)) { setAvailable(false); + const QString architecture = QSysInfo::currentCpuArchitecture().trimmed(); + if (architecture == QStringLiteral("arm64") || + architecture == QStringLiteral("aarch64")) { + setStatusMessage(tr("GPU telemetry is unavailable on this architecture unless nvidia-smi or DRM hwmon metrics are exposed.")); + } else { + setStatusMessage(tr("GPU telemetry is unavailable because nvidia-smi or DRM hwmon metrics could not be read.")); + } clearMetrics(); return; } @@ -234,6 +244,7 @@ void GpuMonitor::refresh() { } setAvailable(true); + setStatusMessage(tr("GPU telemetry is being read from DRM and hwmon fallbacks.")); return; } @@ -243,6 +254,7 @@ void GpuMonitor::refresh() { if (fields.size() < 5) { setAvailable(false); + setStatusMessage(tr("GPU telemetry output could not be parsed.")); clearMetrics(); return; } @@ -277,6 +289,7 @@ void GpuMonitor::refresh() { totalAvailable; if (!telemetryAvailable) { setAvailable(false); + setStatusMessage(tr("GPU telemetry output did not contain usable metrics.")); clearMetrics(); return; } @@ -312,6 +325,7 @@ void GpuMonitor::refresh() { } setAvailable(true); + setStatusMessage(tr("GPU telemetry is being read from nvidia-smi.")); } void GpuMonitor::start() { @@ -381,3 +395,12 @@ void GpuMonitor::setAvailable(bool value) { m_available = value; emit availableChanged(); } + +void GpuMonitor::setStatusMessage(const QString &value) { + if (m_statusMessage == value) { + return; + } + + m_statusMessage = value; + emit statusMessageChanged(); +} diff --git a/src/backend/monitor/gpumonitor.h b/src/backend/monitor/gpumonitor.h index 5ebb3ea..85029d5 100644 --- a/src/backend/monitor/gpumonitor.h +++ b/src/backend/monitor/gpumonitor.h @@ -17,6 +17,7 @@ class GpuMonitor : public QObject { int memoryTotalMiB READ memoryTotalMiB NOTIFY memoryTotalMiBChanged) Q_PROPERTY(int memoryUsagePercent READ memoryUsagePercent NOTIFY memoryUsagePercentChanged) + Q_PROPERTY(QString statusMessage READ statusMessage NOTIFY statusMessageChanged) Q_PROPERTY(int updateInterval READ updateInterval WRITE setUpdateInterval NOTIFY updateIntervalChanged) @@ -31,6 +32,7 @@ class GpuMonitor : public QObject { int memoryUsedMiB() const; int memoryTotalMiB() const; int memoryUsagePercent() const; + QString statusMessage() const; int updateInterval() const; Q_INVOKABLE void refresh(); @@ -47,15 +49,18 @@ class GpuMonitor : public QObject { void memoryUsedMiBChanged(); void memoryTotalMiBChanged(); void memoryUsagePercentChanged(); + void statusMessageChanged(); void updateIntervalChanged(); private: void clearMetrics(); void setAvailable(bool value); + void setStatusMessage(const QString &value); QTimer m_timer; bool m_available = false; QString m_gpuName; + QString m_statusMessage; int m_temperatureC = 0; int m_utilizationPercent = 0; int m_memoryUsedMiB = 0; diff --git a/src/backend/nvidia/detector.cpp b/src/backend/nvidia/detector.cpp index ffc3c51..249a96d 100644 --- a/src/backend/nvidia/detector.cpp +++ b/src/backend/nvidia/detector.cpp @@ -5,6 +5,7 @@ #include "system/sessionutil.h" #include +#include #include #include #include @@ -47,23 +48,23 @@ QString NvidiaDetector::activeDriver() const { } if (m_info.nouveauActive) return tr("Fallback Open Driver"); - return tr("Not Installed / Unknown"); + return tr("Not Installed"); } QString NvidiaDetector::verificationReport() const { const QString gpuText = m_info.found ? m_info.name : (m_info.displayAdapterName.isEmpty() - ? tr("None") + ? tr("Unavailable") : m_info.displayAdapterName); const QString versionText = - m_info.driverVersion.isEmpty() ? tr("None") : m_info.driverVersion; + m_info.driverVersion.isEmpty() ? tr("Unavailable") : m_info.driverVersion; return tr("GPU: %1\nDriver Version: %2\nSecure Boot: %3\nSession: %4\n" "Active Stack: %5\nFallback Open Driver: %6") .arg(gpuText, versionText, m_info.secureBootKnown ? (m_info.secureBootEnabled ? tr("Enabled") : tr("Disabled")) - : tr("Disabled / Unknown"), + : tr("Unknown"), m_info.sessionType.isEmpty() ? tr("Unknown") : m_info.sessionType, activeDriver(), m_info.nouveauActive ? tr("Active") : tr("Inactive")); @@ -199,6 +200,15 @@ bool NvidiaDetector::isModuleLoaded(const QString &moduleName) const { } bool NvidiaDetector::detectSecureBoot(bool *known) const { + bool enabled = false; + bool efivarsKnown = false; + if (detectSecureBootFromEfivars(&enabled, &efivarsKnown)) { + if (known != nullptr) { + *known = efivarsKnown; + } + return enabled; + } + if (!CapabilityProbe::isToolAvailable(QStringLiteral("mokutil"))) { if (known != nullptr) { *known = false; @@ -224,3 +234,40 @@ bool NvidiaDetector::detectSecureBoot(bool *known) const { return false; } + +bool NvidiaDetector::detectSecureBootFromEfivars(bool *enabled, + bool *known) const { + if (enabled == nullptr || known == nullptr) { + return false; + } + + const QString overridePath = + qEnvironmentVariable("RO_CONTROL_SECURE_BOOT_EFIVAR_PATH").trimmed(); + QString secureBootPath = overridePath; + if (secureBootPath.isEmpty()) { + QDir efivarsDir(QStringLiteral("/sys/firmware/efi/efivars")); + const QStringList entries = efivarsDir.entryList( + {QStringLiteral("SecureBoot-*")}, QDir::Files, QDir::Name); + if (!entries.isEmpty()) { + secureBootPath = efivarsDir.filePath(entries.constFirst()); + } + } + + if (secureBootPath.isEmpty()) { + return false; + } + + QFile file(secureBootPath); + if (!file.open(QIODevice::ReadOnly)) { + return false; + } + + const QByteArray raw = file.readAll(); + if (raw.size() < 5) { + return false; + } + + *enabled = raw.at(4) != 0; + *known = true; + return true; +} diff --git a/src/backend/nvidia/detector.h b/src/backend/nvidia/detector.h index d0e18ac..cf62d91 100644 --- a/src/backend/nvidia/detector.h +++ b/src/backend/nvidia/detector.h @@ -72,6 +72,7 @@ class NvidiaDetector : public QObject { bool isPackageInstalled(const QString &packageName) const; bool isModuleLoaded(const QString &moduleName) const; bool detectSecureBoot(bool *known = nullptr) const; + bool detectSecureBootFromEfivars(bool *enabled, bool *known) const; GpuInfo m_info; }; diff --git a/src/backend/nvidia/installer.cpp b/src/backend/nvidia/installer.cpp index 69e434a..38323a5 100644 --- a/src/backend/nvidia/installer.cpp +++ b/src/backend/nvidia/installer.cpp @@ -152,7 +152,9 @@ void NvidiaInstaller::refreshProprietaryAgreement() { setProprietaryAgreement( true, tr("The proprietary NVIDIA driver is subject to NVIDIA's software " - "license. Review the official NVIDIA license before installation: %1") + "license. ro-Control cannot audit or review the closed-source " + "license text for you. Review the official NVIDIA license before " + "installation: %1") .arg(QString::fromLatin1(kNvidiaLicenseUrl))); } diff --git a/src/backend/nvidia/updater.cpp b/src/backend/nvidia/updater.cpp index f3158de..05126a5 100644 --- a/src/backend/nvidia/updater.cpp +++ b/src/backend/nvidia/updater.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include namespace { @@ -49,6 +50,7 @@ QString normalizedTransactionOutput(const CommandRunner::Result &result) { struct UpdateStatusSnapshot { QString currentVersion; QString latestVersion; + QString latestPackageVersion; QStringList availableVersions; bool remoteCatalogAvailable = false; bool updateAvailable = false; @@ -83,8 +85,8 @@ QString detectInstalledKernelPackageName() { return QStringLiteral("akmod-nvidia"); } -QString queryLatestRemoteVersion(CommandRunner &runner, - const QString &kernelPackageName) { +QString queryLatestRemotePackageVersion(CommandRunner &runner, + const QString &kernelPackageName) { const auto result = runner.run( QStringLiteral("dnf"), {QStringLiteral("--refresh"), QStringLiteral("repoquery"), @@ -99,6 +101,58 @@ QString queryLatestRemoteVersion(CommandRunner &runner, return firstNonEmptyLine(result.stdout); } +QString fetchTextFromUrl(CommandRunner &runner, const QString &url) { + CommandRunner::RunOptions options; + options.timeoutMs = 10000; + + if (CapabilityProbe::isToolAvailable(QStringLiteral("curl"))) { + const auto result = + runner.run(QStringLiteral("curl"), + {QStringLiteral("-fsSL"), QStringLiteral("--compressed"), + url}, + options); + if (result.success()) { + return result.stdout; + } + } + + if (CapabilityProbe::isToolAvailable(QStringLiteral("wget"))) { + const auto result = + runner.run(QStringLiteral("wget"), {QStringLiteral("-qO-"), url}, + options); + if (result.success()) { + return result.stdout; + } + } + + return {}; +} + +QStringList queryOfficialDriverVersions(CommandRunner &runner) { + const QString pageText = fetchTextFromUrl( + runner, QStringLiteral("https://www.nvidia.com/en-us/drivers/unix/")); + if (pageText.isEmpty()) { + return {}; + } + + return NvidiaVersionParser::parseOfficialUnixDriverVersions( + pageText, CapabilityProbe::normalizedCpuArchitecture()); +} + +bool isOfficialUpdateAvailable(const QString ¤tVersion, + const QString &latestVersion) { + const QString normalizedCurrent = + NvidiaVersionParser::normalizedDriverVersion(currentVersion); + const QString normalizedLatest = + NvidiaVersionParser::normalizedDriverVersion(latestVersion); + if (normalizedCurrent.isEmpty() || normalizedLatest.isEmpty()) { + return false; + } + + return QVersionNumber::compare(QVersionNumber::fromString(normalizedCurrent), + QVersionNumber::fromString(normalizedLatest)) < 0; +} + UpdateStatusSnapshot collectUpdateStatus() { UpdateStatusSnapshot snapshot; NvidiaDetector detector; @@ -112,28 +166,40 @@ UpdateStatusSnapshot collectUpdateStatus() { return snapshot; } - if (QStandardPaths::findExecutable(QStringLiteral("dnf")).isEmpty()) { - snapshot.message = NvidiaUpdater::tr("dnf not found."); - return snapshot; + CommandRunner runner; + const QStringList officialVersions = queryOfficialDriverVersions(runner); + if (!officialVersions.isEmpty()) { + snapshot.latestVersion = officialVersions.constFirst(); + snapshot.remoteCatalogAvailable = true; } - CommandRunner runner; - const auto listResult = - runner.run(QStringLiteral("dnf"), - {QStringLiteral("--refresh"), QStringLiteral("list"), - QStringLiteral("--showduplicates"), kernelPackageName}); - - if (listResult.success()) { - snapshot.availableVersions = - NvidiaVersionParser::parseAvailablePackageVersions(listResult.stdout, - kernelPackageName); - snapshot.remoteCatalogAvailable = !snapshot.availableVersions.isEmpty(); + const bool hasDnf = + !QStandardPaths::findExecutable(QStringLiteral("dnf")).isEmpty(); + if (hasDnf) { + const auto listResult = + runner.run(QStringLiteral("dnf"), + {QStringLiteral("--refresh"), QStringLiteral("list"), + QStringLiteral("--showduplicates"), kernelPackageName}); + + if (listResult.success()) { + snapshot.availableVersions = + NvidiaVersionParser::parseAvailablePackageVersions(listResult.stdout, + kernelPackageName); + snapshot.remoteCatalogAvailable = + snapshot.remoteCatalogAvailable || !snapshot.availableVersions.isEmpty(); + } + + snapshot.latestPackageVersion = + queryLatestRemotePackageVersion(runner, kernelPackageName); + if (snapshot.latestPackageVersion.isEmpty() && + !snapshot.availableVersions.isEmpty()) { + snapshot.latestPackageVersion = snapshot.availableVersions.constLast(); + } } - snapshot.latestVersion = queryLatestRemoteVersion(runner, kernelPackageName); - if (snapshot.latestVersion.isEmpty() && - !snapshot.availableVersions.isEmpty()) { - snapshot.latestVersion = snapshot.availableVersions.constLast(); + if (snapshot.latestVersion.isEmpty()) { + snapshot.latestVersion = + NvidiaVersionParser::normalizedDriverVersion(snapshot.latestPackageVersion); } if (snapshot.currentVersion.isEmpty()) { @@ -141,27 +207,44 @@ UpdateStatusSnapshot collectUpdateStatus() { snapshot.updateAvailable = true; snapshot.message = snapshot.latestVersion.isEmpty() - ? NvidiaUpdater::tr("Online NVIDIA packages were found. You can " - "download and install the driver now.") + ? NvidiaUpdater::tr("Official NVIDIA driver sources are reachable. " + "You can install the driver now.") : NvidiaUpdater::tr( - "Online NVIDIA driver found. Latest remote version: %1") + "Latest official NVIDIA driver version: %1") .arg(snapshot.latestVersion); } else { - snapshot.message = - NvidiaUpdater::tr("No online NVIDIA package catalog was found. RPM " - "Fusion may not be configured yet."); + snapshot.message = hasDnf ? NvidiaUpdater::tr("No official NVIDIA driver " + "version could be retrieved.") + : NvidiaUpdater::tr("dnf not found."); } return snapshot; } + if (!snapshot.latestVersion.isEmpty()) { + snapshot.updateAvailable = + isOfficialUpdateAvailable(snapshot.currentVersion, snapshot.latestVersion); + snapshot.message = snapshot.updateAvailable + ? NvidiaUpdater::tr("Official NVIDIA update found: %1") + .arg(snapshot.latestVersion) + : NvidiaUpdater::tr( + "Driver matches the latest official NVIDIA " + "production branch."); + return snapshot; + } + + if (!hasDnf) { + snapshot.message = NvidiaUpdater::tr("dnf not found."); + return snapshot; + } + const auto checkResult = runner.run(QStringLiteral("dnf"), {QStringLiteral("check-update"), kernelPackageName}); if (checkResult.exitCode == 100) { - const QString checkUpdateVersion = + const QString checkUpdateVersion = NvidiaVersionParser::normalizedDriverVersion( NvidiaVersionParser::parseCheckUpdateVersion(checkResult.stdout, - kernelPackageName); + kernelPackageName)); if (!checkUpdateVersion.isEmpty()) { snapshot.latestVersion = checkUpdateVersion; } @@ -329,7 +412,7 @@ QStringList NvidiaUpdater::buildTransactionArguments( const QString normalizedRequestedVersion = requestedVersion.trimmed(); const QString normalizedInstalledVersion = installedVersion.trimmed(); const QString targetVersion = normalizedRequestedVersion.isEmpty() - ? m_latestVersion.trimmed() + ? m_latestPackageVersion.trimmed() : normalizedRequestedVersion; QStringList args; @@ -400,12 +483,8 @@ void NvidiaUpdater::refreshAvailableVersions() { return; } + guard->m_latestPackageVersion = snapshot.latestPackageVersion; guard->setAvailableVersions(snapshot.availableVersions); - emit guard->progressMessage( - snapshot.availableVersions.isEmpty() - ? NvidiaUpdater::tr("No available versions found.") - : NvidiaUpdater::tr("Available versions: %1") - .arg(snapshot.availableVersions.size())); }, Qt::QueuedConnection); }); @@ -440,9 +519,14 @@ void NvidiaUpdater::checkForUpdate() { emit guard->updateAvailableChanged(); } + guard->m_latestPackageVersion = snapshot.latestPackageVersion; guard->setLatestVersion(snapshot.latestVersion); guard->setAvailableVersions(snapshot.availableVersions); - emit guard->progressMessage(snapshot.message); + const bool success = snapshot.updateAvailable || + !snapshot.latestVersion.isEmpty() || + snapshot.remoteCatalogAvailable || + snapshot.currentVersion.isEmpty(); + emit guard->checkFinished(success, snapshot.message); }, Qt::QueuedConnection); }); @@ -554,6 +638,7 @@ void NvidiaUpdater::applyVersion(const QString &version) { guard->m_updateAvailable = snapshot.updateAvailable; emit guard->updateAvailableChanged(); } + guard->m_latestPackageVersion = snapshot.latestPackageVersion; guard->setLatestVersion(snapshot.latestVersion); guard->setAvailableVersions(snapshot.availableVersions); emit guard->progressMessage(noChangeMessage); @@ -604,6 +689,7 @@ void NvidiaUpdater::applyVersion(const QString &version) { guard->m_updateAvailable = snapshot.updateAvailable; emit guard->updateAvailableChanged(); } + guard->m_latestPackageVersion = snapshot.latestPackageVersion; guard->setLatestVersion(snapshot.latestVersion); guard->setAvailableVersions(snapshot.availableVersions); emit guard->progressMessage(snapshot.message); diff --git a/src/backend/nvidia/updater.h b/src/backend/nvidia/updater.h index 53303a8..5dde0f7 100644 --- a/src/backend/nvidia/updater.h +++ b/src/backend/nvidia/updater.h @@ -42,6 +42,7 @@ class NvidiaUpdater : public QObject { void availableVersionsChanged(); void busyChanged(); void progressMessage(const QString &message); + void checkFinished(bool success, const QString &message); void updateFinished(bool success, const QString &message); private: @@ -61,6 +62,7 @@ class NvidiaUpdater : public QObject { bool finalizeDriverChange(CommandRunner &runner, const QString &sessionType, QString *errorMessage); QString detectSessionType() const; + QString m_latestPackageVersion; bool m_updateAvailable = false; QString m_currentVersion; QString m_latestVersion; diff --git a/src/backend/nvidia/versionparser.cpp b/src/backend/nvidia/versionparser.cpp index 9c69d00..da6c9e3 100644 --- a/src/backend/nvidia/versionparser.cpp +++ b/src/backend/nvidia/versionparser.cpp @@ -12,6 +12,36 @@ QRegularExpression packageLineExpression(const QString &packageName) { QRegularExpression::MultilineOption); } +QString unixDriverSectionLabel(const QString &architecture) { + const QString normalized = architecture.trimmed().toLower(); + if (normalized == QStringLiteral("x86_64") || + normalized == QStringLiteral("amd64")) { + return QStringLiteral("Linux x86_64/AMD64/EM64T"); + } + if (normalized == QStringLiteral("aarch64") || + normalized == QStringLiteral("arm64")) { + return QStringLiteral("Linux aarch64"); + } + + return QStringLiteral("Linux x86_64/AMD64/EM64T"); +} + +QString plainTextFromHtml(const QString &text) { + QString plainText = text; + plainText.remove(QRegularExpression( + QStringLiteral(R"(]*>[\s\S]*?)"), + QRegularExpression::CaseInsensitiveOption)); + plainText.remove(QRegularExpression( + QStringLiteral(R"(]*>[\s\S]*?)"), + QRegularExpression::CaseInsensitiveOption)); + plainText.replace(QRegularExpression(QStringLiteral(R"(<[^>]+>)")), + QStringLiteral(" ")); + plainText.replace(QStringLiteral(" "), QStringLiteral(" ")); + plainText.replace(QRegularExpression(QStringLiteral(R"(\s+)")), + QStringLiteral(" ")); + return plainText.trimmed(); +} + } // namespace QStringList parseAvailablePackageVersions(const QString &dnfOutput, @@ -37,6 +67,64 @@ QString parseCheckUpdateVersion(const QString &dnfOutput, return versions.isEmpty() ? QString() : versions.constFirst(); } +QStringList parseOfficialUnixDriverVersions(const QString &pageText, + const QString &architecture) { + const QString plainText = plainTextFromHtml(pageText); + const QString sectionLabel = unixDriverSectionLabel(architecture); + const int sectionStart = plainText.indexOf(sectionLabel); + if (sectionStart < 0) { + return {}; + } + + const QStringList sectionBoundaries = { + QStringLiteral("Linux x86_64/AMD64/EM64T"), + QStringLiteral("Linux aarch64"), + QStringLiteral("FreeBSD x64"), + QStringLiteral("Solaris x64/x86"), + }; + + int sectionEnd = plainText.size(); + for (const QString &boundary : sectionBoundaries) { + if (boundary == sectionLabel) { + continue; + } + + const int nextIndex = plainText.indexOf(boundary, sectionStart + 1); + if (nextIndex >= 0 && nextIndex < sectionEnd) { + sectionEnd = nextIndex; + } + } + + const QString sectionText = + plainText.mid(sectionStart, sectionEnd - sectionStart); + const QRegularExpression versionPattern( + QStringLiteral(R"(Latest [^:]+:\s*([0-9]+(?:\.[0-9]+)+))")); + + QStringList versions; + auto it = versionPattern.globalMatch(sectionText); + while (it.hasNext()) { + const QString version = it.next().captured(1).trimmed(); + if (!version.isEmpty() && !versions.contains(version)) { + versions.append(version); + } + } + + return versions; +} + +QString normalizedDriverVersion(const QString &version) { + QString normalized = version.trimmed(); + const int epochSeparator = normalized.indexOf(QLatin1Char(':')); + if (epochSeparator >= 0) { + normalized = normalized.mid(epochSeparator + 1); + } + + const QRegularExpression versionPattern( + QStringLiteral(R"(([0-9]+(?:\.[0-9]+)+))")); + const auto match = versionPattern.match(normalized); + return match.hasMatch() ? match.captured(1) : QString(); +} + QString packageSpecForVersion(const QString &packageName, const QString &version) { const QString trimmedVersion = version.trimmed(); diff --git a/src/backend/nvidia/versionparser.h b/src/backend/nvidia/versionparser.h index 36bcbb5..bf478f3 100644 --- a/src/backend/nvidia/versionparser.h +++ b/src/backend/nvidia/versionparser.h @@ -9,6 +9,9 @@ QStringList parseAvailablePackageVersions(const QString &dnfOutput, const QString &packageName); QString parseCheckUpdateVersion(const QString &dnfOutput, const QString &packageName); +QStringList parseOfficialUnixDriverVersions(const QString &pageText, + const QString &architecture); +QString normalizedDriverVersion(const QString &version); QString packageSpecForVersion(const QString &packageName, const QString &version); QStringList buildVersionedPackageSpecs(const QStringList &packageNames, diff --git a/src/backend/system/languagemanager.cpp b/src/backend/system/languagemanager.cpp index 419d7d8..3db1e4f 100644 --- a/src/backend/system/languagemanager.cpp +++ b/src/backend/system/languagemanager.cpp @@ -16,16 +16,16 @@ struct LanguageEntry { }; constexpr LanguageEntry kSupportedLanguages[] = { - {"system", "System Default", true}, + {"system", "System", true}, {"en", "English", true}, {"de", "Deutsch", false}, - {"es", "Espanol", false}, - {"tr", "Turkce", true}, + {"es", "Español", false}, + {"tr", "Türkçe", true}, }; QString localizedLanguageLabel(const QString &code) { if (code == QStringLiteral("system")) { - return QCoreApplication::translate("LanguageManager", "System Default"); + return QCoreApplication::translate("LanguageManager", "System"); } if (code == QStringLiteral("en")) { return QCoreApplication::translate("LanguageManager", "English"); @@ -94,7 +94,7 @@ QVariantList LanguageManager::availableLanguages() const { language.insert(QStringLiteral("code"), code); language.insert(QStringLiteral("label"), localizedLanguageLabel(code)); language.insert(QStringLiteral("nativeLabel"), - QString::fromLatin1(entry.nativeLabel)); + QString::fromUtf8(entry.nativeLabel)); language.insert(QStringLiteral("shipped"), entry.shipped); languages.append(language); } @@ -126,7 +126,7 @@ LanguageManager::displayNameForLanguage(const QString &languageCode) const { const QString normalizedLanguage = normalizeLanguageCode(languageCode); for (const auto &entry : kSupportedLanguages) { if (QString::fromLatin1(entry.code) == normalizedLanguage) { - return QString::fromLatin1(entry.nativeLabel); + return QString::fromUtf8(entry.nativeLabel); } } diff --git a/src/backend/system/uipreferencesmanager.cpp b/src/backend/system/uipreferencesmanager.cpp index 2b41d3b..e411f40 100644 --- a/src/backend/system/uipreferencesmanager.cpp +++ b/src/backend/system/uipreferencesmanager.cpp @@ -17,7 +17,7 @@ constexpr ThemeModeEntry kThemeModes[] = { QString themeModeLabel(const QString &code) { if (code == QStringLiteral("system")) { - return QCoreApplication::translate("UiPreferencesManager", "Follow System"); + return QCoreApplication::translate("UiPreferencesManager", "System"); } if (code == QStringLiteral("light")) { return QCoreApplication::translate("UiPreferencesManager", "Light"); diff --git a/src/main.cpp b/src/main.cpp index 86b7131..9da2223 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -7,7 +7,6 @@ #include #include #include -#include #include #include #include @@ -272,17 +271,26 @@ int main(int argc, char *argv[]) { LanguageManager languageManager(&app, &engine, &translator); UiPreferencesManager uiPreferencesManager; - // Backend nesnelerini tüm QML dosyalarına global olarak aç - engine.rootContext()->setContextProperty("nvidiaDetector", &detector); - engine.rootContext()->setContextProperty("nvidiaInstaller", &installer); - engine.rootContext()->setContextProperty("nvidiaUpdater", &updater); - engine.rootContext()->setContextProperty("cpuMonitor", &cpuMonitor); - engine.rootContext()->setContextProperty("gpuMonitor", &gpuMonitor); - engine.rootContext()->setContextProperty("ramMonitor", &ramMonitor); - engine.rootContext()->setContextProperty("systemInfo", &systemInfo); - engine.rootContext()->setContextProperty("languageManager", &languageManager); - engine.rootContext()->setContextProperty("uiPreferences", - &uiPreferencesManager); + QVariantMap initialProperties; + initialProperties.insert(QStringLiteral("nvidiaDetector"), + QVariant::fromValue(&detector)); + initialProperties.insert(QStringLiteral("nvidiaInstaller"), + QVariant::fromValue(&installer)); + initialProperties.insert(QStringLiteral("nvidiaUpdater"), + QVariant::fromValue(&updater)); + initialProperties.insert(QStringLiteral("cpuMonitor"), + QVariant::fromValue(&cpuMonitor)); + initialProperties.insert(QStringLiteral("gpuMonitor"), + QVariant::fromValue(&gpuMonitor)); + initialProperties.insert(QStringLiteral("ramMonitor"), + QVariant::fromValue(&ramMonitor)); + initialProperties.insert(QStringLiteral("systemInfo"), + QVariant::fromValue(&systemInfo)); + initialProperties.insert(QStringLiteral("languageManager"), + QVariant::fromValue(&languageManager)); + initialProperties.insert(QStringLiteral("uiPreferences"), + QVariant::fromValue(&uiPreferencesManager)); + engine.setInitialProperties(initialProperties); QObject::connect( &engine, &QQmlApplicationEngine::objectCreationFailed, &app, diff --git a/src/qml/Main.qml b/src/qml/Main.qml index ae053d2..4b157b4 100644 --- a/src/qml/Main.qml +++ b/src/qml/Main.qml @@ -2,9 +2,19 @@ import QtQuick import QtQuick.Controls import QtQuick.Controls.Material import QtQuick.Layouts +import "pages" as Pages ApplicationWindow { id: root + required property var nvidiaDetector + required property var nvidiaInstaller + required property var nvidiaUpdater + required property var cpuMonitor + required property var gpuMonitor + required property var ramMonitor + required property var systemInfo + required property var languageManager + required property var uiPreferences visible: true width: 1320 height: 840 @@ -22,20 +32,41 @@ ApplicationWindow { return ((0.2126 * colorValue.r) + (0.7152 * colorValue.g) + (0.0722 * colorValue.b)) < 0.5; } - readonly property bool hasUiPreferences: typeof uiPreferences !== "undefined" && uiPreferences !== null - readonly property string themeMode: (hasUiPreferences && uiPreferences.themeMode) - ? uiPreferences.themeMode + readonly property bool hasUiPreferences: root.uiPreferences !== null + readonly property bool hasLanguageManager: root.languageManager !== null + readonly property string themeMode: (hasUiPreferences && root.uiPreferences.themeMode) + ? root.uiPreferences.themeMode : "system" - readonly property bool systemDarkMode: Qt.styleHints.colorScheme === Qt.Dark - || (Qt.styleHints.colorScheme === Qt.Unknown - && (palette.window.r + palette.window.g + palette.window.b) / 3 < 0.5) + readonly property bool systemDarkMode: isColorDark(systemPalette.window) readonly property bool darkMode: themeMode === "dark" || (themeMode === "system" && systemDarkMode) - readonly property bool showAdvancedInfo: (hasUiPreferences && uiPreferences.showAdvancedInfo !== undefined) - ? uiPreferences.showAdvancedInfo + readonly property bool showAdvancedInfo: (hasUiPreferences && root.uiPreferences.showAdvancedInfo !== undefined) + ? root.uiPreferences.showAdvancedInfo : true readonly property real uiScale: Math.max(0.85, Math.min(width / 1320, 1.15)) + function syncThemePicker() { + if (!hasUiPreferences) + return; + for (let i = 0; i < themePicker.model.length; ++i) { + if (themePicker.model[i].code === root.uiPreferences.themeMode) { + themePicker.currentIndex = i; + break; + } + } + } + + function syncLanguagePicker() { + if (!hasLanguageManager) + return; + for (let i = 0; i < languagePicker.model.length; ++i) { + if (languagePicker.model[i].code === root.languageManager.currentLanguage) { + languagePicker.currentIndex = i; + break; + } + } + } + QtObject { id: colors // Light palette: #92C7CF #AAD7D9 #FBF9F1 #E5E1DA @@ -119,6 +150,44 @@ ApplicationWindow { } } + RowLayout { + spacing: Math.round(8 * root.uiScale) + + ComboBox { + id: languagePicker + Layout.preferredWidth: Math.round(170 * root.uiScale) + model: root.hasLanguageManager ? root.languageManager.availableLanguages : [] + textRole: "nativeLabel" + palette.text: colors.text + palette.buttonText: colors.text + + Component.onCompleted: root.syncLanguagePicker() + + onActivated: { + const selected = model[currentIndex]; + if (root.hasLanguageManager && selected && selected.code) + root.languageManager.setCurrentLanguage(selected.code); + } + } + + ComboBox { + id: themePicker + Layout.preferredWidth: Math.round(150 * root.uiScale) + model: root.hasUiPreferences ? root.uiPreferences.availableThemeModes : [] + textRole: "label" + palette.text: colors.text + palette.buttonText: colors.text + + Component.onCompleted: root.syncThemePicker() + + onActivated: { + const selected = model[currentIndex]; + if (root.hasUiPreferences && selected && selected.code) + root.uiPreferences.setThemeMode(selected.code); + } + } + } + } } @@ -197,27 +266,51 @@ ApplicationWindow { anchors.margins: Math.round(10 * root.uiScale) currentIndex: tabBar.currentIndex - DriverPage { + Pages.DriverPage { theme: colors darkMode: root.darkMode showAdvancedInfo: root.showAdvancedInfo uiScale: root.uiScale + nvidiaDetector: root.nvidiaDetector + nvidiaInstaller: root.nvidiaInstaller + nvidiaUpdater: root.nvidiaUpdater } - MonitorPage { + Pages.MonitorPage { theme: colors darkMode: root.darkMode showAdvancedInfo: root.showAdvancedInfo uiScale: root.uiScale + systemInfo: root.systemInfo + cpuMonitor: root.cpuMonitor + gpuMonitor: root.gpuMonitor + ramMonitor: root.ramMonitor } - SettingsPage { + Pages.SettingsPage { theme: colors darkMode: root.darkMode showAdvancedInfo: root.showAdvancedInfo uiScale: root.uiScale + uiPreferences: root.uiPreferences } } } } + + Connections { + target: root.uiPreferences + + function onThemeModeChanged() { + root.syncThemePicker() + } + } + + Connections { + target: root.languageManager + + function onCurrentLanguageChanged() { + root.syncLanguagePicker() + } + } } diff --git a/src/qml/pages/DriverPage.qml b/src/qml/pages/DriverPage.qml index fcf42d5..d6113c1 100644 --- a/src/qml/pages/DriverPage.qml +++ b/src/qml/pages/DriverPage.qml @@ -4,6 +4,9 @@ import QtQuick.Layouts Item { id: page + required property var nvidiaDetector + required property var nvidiaInstaller + required property var nvidiaUpdater property var theme: ({}) property bool darkMode: false @@ -16,13 +19,16 @@ Item { property string operationPhase: "" property string operationDetail: "" property bool operationActive: false - readonly property bool backendBusy: nvidiaInstaller.busy || nvidiaUpdater.busy + property bool suppressPassiveStatus: true + readonly property bool backendBusy: page.nvidiaInstaller.busy || page.nvidiaUpdater.busy readonly property bool operationRunning: page.operationActive || page.backendBusy - readonly property bool remoteDriverCatalogAvailable: nvidiaUpdater.availableVersions.length > 0 - readonly property bool canInstallLatestRemoteDriver: nvidiaDetector.gpuFound && remoteDriverCatalogAvailable - readonly property bool driverInstalledLocally: nvidiaDetector.driverVersion.length > 0 || nvidiaUpdater.currentVersion.length > 0 - readonly property string installedVersionLabel: nvidiaDetector.driverVersion.length > 0 ? nvidiaDetector.driverVersion : nvidiaUpdater.currentVersion - readonly property bool catalogAvailable: nvidiaUpdater.availableVersions.length > 0 + readonly property bool remoteDriverCatalogAvailable: page.nvidiaUpdater.latestVersion.length > 0 || page.nvidiaUpdater.availableVersions.length > 0 + readonly property bool canInstallLatestRemoteDriver: page.nvidiaDetector.gpuFound && remoteDriverCatalogAvailable + readonly property bool driverInstalledLocally: page.nvidiaDetector.driverVersion.length > 0 || page.nvidiaUpdater.currentVersion.length > 0 + readonly property string installedVersionLabel: page.nvidiaDetector.driverVersion.length > 0 ? page.nvidiaDetector.driverVersion : page.nvidiaUpdater.currentVersion + readonly property bool catalogAvailable: page.nvidiaUpdater.latestVersion.length > 0 || page.nvidiaUpdater.availableVersions.length > 0 + readonly property string unixDriverUrl: "https://www.nvidia.com/en-us/drivers/unix/" + readonly property string fedoraGuideUrl: "https://docs.nvidia.com/datacenter/tesla/driver-installation-guide/fedora.html" readonly property color bgColor: theme && theme.card ? theme.card : "#ffffff" readonly property color cardColor: theme && theme.cardStrong ? theme.cardStrong : "#f5f8ff" @@ -58,9 +64,17 @@ Item { setOperationState(source, message, success ? "success" : "error", false); } + function latestActionLabel() { + const latest = page.nvidiaUpdater.latestVersion; + if (driverInstalledLocally) + return latest.length > 0 ? qsTr("Apply Latest (%1)").arg(latest) : qsTr("Apply Latest"); + return latest.length > 0 ? qsTr("Install Latest (%1)").arg(latest) : qsTr("Install Latest"); + } + function appendLog(source, message) { const prefix = source && source.length > 0 ? source : qsTr("System"); - activityLog.append("[" + Qt.formatTime(new Date(), "HH:mm:ss") + "] " + prefix + ": " + message); + const nextLine = "[" + Qt.formatTime(new Date(), "HH:mm:ss") + "] " + prefix + ": " + message; + activityLog.text = activityLog.text.length > 0 ? activityLog.text + "\n" + nextLine : nextLine; activityLog.cursorPosition = activityLog.length; } @@ -114,8 +128,8 @@ Item { spacing: 6 Label { text: qsTr("GPU"); color: page.softTextColor; font.bold: true } - Label { text: nvidiaDetector.gpuFound ? nvidiaDetector.gpuName : qsTr("Not Detected"); color: page.textColor; font.pixelSize: Math.round(18 * page.uiScale); font.bold: true } - Label { text: nvidiaDetector.activeDriver; color: page.softTextColor; elide: Text.ElideRight; width: parent.width } + Label { text: page.nvidiaDetector.gpuFound ? page.nvidiaDetector.gpuName : qsTr("No NVIDIA GPU"); color: page.textColor; font.pixelSize: Math.round(18 * page.uiScale); font.bold: true } + Label { text: page.nvidiaDetector.activeDriver; color: page.softTextColor; elide: Text.ElideRight; width: parent.width } } } @@ -133,8 +147,8 @@ Item { spacing: 6 Label { text: qsTr("Installed Version"); color: page.softTextColor; font.bold: true } - Label { text: page.installedVersionLabel.length > 0 ? page.installedVersionLabel : qsTr("None"); color: page.textColor; font.pixelSize: Math.round(18 * page.uiScale); font.bold: true } - Label { text: nvidiaUpdater.latestVersion.length > 0 ? qsTr("Latest: %1").arg(nvidiaUpdater.latestVersion) : qsTr("Latest: Unknown"); color: page.softTextColor } + Label { text: page.installedVersionLabel.length > 0 ? page.installedVersionLabel : qsTr("Not Installed"); color: page.textColor; font.pixelSize: Math.round(18 * page.uiScale); font.bold: true } + Label { text: page.nvidiaUpdater.latestVersion.length > 0 ? qsTr("Official latest: %1").arg(page.nvidiaUpdater.latestVersion) : qsTr("Official latest: Unavailable"); color: page.softTextColor } } } @@ -152,8 +166,14 @@ Item { spacing: 6 Label { text: qsTr("Session & Security"); color: page.softTextColor; font.bold: true } - Label { text: nvidiaDetector.sessionType.length > 0 ? nvidiaDetector.sessionType : qsTr("Unknown"); color: page.textColor; font.pixelSize: Math.round(18 * page.uiScale); font.bold: true } - Label { text: nvidiaDetector.secureBootEnabled ? qsTr("Secure Boot: Enabled") : qsTr("Secure Boot: Disabled / Unknown"); color: page.softTextColor } + Label { text: page.nvidiaDetector.sessionType.length > 0 ? page.nvidiaDetector.sessionType : qsTr("Unknown"); color: page.textColor; font.pixelSize: Math.round(18 * page.uiScale); font.bold: true } + Label { + text: page.nvidiaDetector.secureBootKnown + ? (page.nvidiaDetector.secureBootEnabled ? qsTr("Secure Boot: Enabled") + : qsTr("Secure Boot: Disabled")) + : qsTr("Secure Boot: Unknown") + color: page.softTextColor + } } } } @@ -181,15 +201,24 @@ Item { Label { Layout.fillWidth: true - text: nvidiaInstaller.proprietaryAgreementRequired ? nvidiaInstaller.proprietaryAgreementText : qsTr("Use safe guided operations for install, update, and cleanup.") + text: page.nvidiaInstaller.proprietaryAgreementRequired ? page.nvidiaInstaller.proprietaryAgreementText : qsTr("Latest version information is resolved from the official NVIDIA Unix driver page. Fedora workflow guidance follows the official NVIDIA Fedora installation guide.") wrapMode: Text.Wrap color: page.softTextColor } + Text { + Layout.fillWidth: true + textFormat: Text.RichText + color: page.softTextColor + linkColor: page.textColor + text: "" + page.unixDriverUrl + "
" + page.fedoraGuideUrl + "" + onLinkActivated: function(link) { Qt.openUrlExternally(link) } + } + CheckBox { id: eulaAccept - visible: nvidiaInstaller.proprietaryAgreementRequired - text: qsTr("I reviewed the NVIDIA license terms") + visible: page.nvidiaInstaller.proprietaryAgreementRequired + text: qsTr("I reviewed the official NVIDIA license outside ro-Control") palette.text: page.textColor } @@ -202,124 +231,155 @@ Item { Button { Layout.fillWidth: true text: qsTr("Install Proprietary") - enabled: !nvidiaInstaller.busy && (!nvidiaInstaller.proprietaryAgreementRequired || eulaAccept.checked) + enabled: !page.nvidiaInstaller.busy && (!page.nvidiaInstaller.proprietaryAgreementRequired || eulaAccept.checked) onClicked: { page.setOperationState(qsTr("Installer"), qsTr("Installing proprietary NVIDIA driver..."), "info", true); - nvidiaInstaller.installProprietary(eulaAccept.checked); + page.nvidiaInstaller.installProprietary(eulaAccept.checked); } } Button { Layout.fillWidth: true - text: page.driverInstalledLocally ? qsTr("Apply Latest") : qsTr("Install Latest") - enabled: !nvidiaUpdater.busy && !nvidiaInstaller.busy && (nvidiaUpdater.updateAvailable || page.catalogAvailable) + text: page.latestActionLabel() + enabled: !page.nvidiaUpdater.busy && !page.nvidiaInstaller.busy && (page.nvidiaUpdater.updateAvailable || page.catalogAvailable) onClicked: { - page.setOperationState(qsTr("Updater"), qsTr("Applying latest online version..."), "info", true); - nvidiaUpdater.applyUpdate(); + page.setOperationState(qsTr("Updater"), qsTr("Applying latest available driver..."), "info", true); + page.suppressPassiveStatus = false; + page.nvidiaUpdater.applyUpdate(); } } Button { Layout.fillWidth: true text: qsTr("Remove Driver") - enabled: !nvidiaInstaller.busy + enabled: !page.nvidiaInstaller.busy onClicked: { page.setOperationState(qsTr("Installer"), qsTr("Removing NVIDIA driver..."), "info", true); - nvidiaInstaller.remove(); + page.nvidiaInstaller.remove(); } } Button { Layout.fillWidth: true text: qsTr("Check Updates") - enabled: !nvidiaUpdater.busy && !nvidiaInstaller.busy + enabled: !page.nvidiaUpdater.busy && !page.nvidiaInstaller.busy onClicked: { - page.setOperationState(qsTr("Updater"), qsTr("Checking repository for updates..."), "info", true); - nvidiaUpdater.checkForUpdate(); + page.setOperationState(qsTr("Updater"), qsTr("Checking official NVIDIA driver sources..."), "info", true); + page.suppressPassiveStatus = true; + page.nvidiaUpdater.checkForUpdate(); } } Button { Layout.fillWidth: true text: qsTr("Rescan") - enabled: !nvidiaUpdater.busy && !nvidiaInstaller.busy + enabled: !page.nvidiaUpdater.busy && !page.nvidiaInstaller.busy onClicked: { - nvidiaDetector.refresh(); - nvidiaInstaller.refreshProprietaryAgreement(); - nvidiaUpdater.refreshAvailableVersions(); + page.nvidiaDetector.refresh(); + page.nvidiaInstaller.refreshProprietaryAgreement(); + page.suppressPassiveStatus = true; + page.nvidiaUpdater.checkForUpdate(); } } } + } + } + + Rectangle { + Layout.fillWidth: true + visible: page.nvidiaUpdater.availableVersions.length > 0 + radius: 14 + color: page.cardColor + border.width: 1 + border.color: page.borderColor + implicitHeight: versionLayout.implicitHeight + 24 + + ColumnLayout { + id: versionLayout + anchors.fill: parent + anchors.margins: 12 + spacing: 10 + + Label { + text: qsTr("Version Selection") + color: page.textColor + font.pixelSize: Math.round(18 * page.uiScale) + font.bold: true + } + + Label { + Layout.fillWidth: true + text: qsTr("Use this area to test or switch to an older repository version.") + wrapMode: Text.Wrap + color: page.softTextColor + } - Rectangle { + RowLayout { Layout.fillWidth: true - visible: page.showAdvancedInfo - radius: 10 - color: page.bgColor - border.width: 1 - border.color: page.borderColor - implicitHeight: advancedLayout.implicitHeight + 20 - - ColumnLayout { - id: advancedLayout - anchors.fill: parent - anchors.margins: 10 - spacing: 8 - - Label { - text: qsTr("Diagnostics") - color: page.textColor - font.bold: true + spacing: 8 + + ComboBox { + id: versionPicker + Layout.fillWidth: true + model: page.nvidiaUpdater.availableVersions + enabled: model.length > 0 + palette.text: page.textColor + palette.buttonText: page.textColor + } + + Button { + text: qsTr("Apply Selected") + enabled: !page.nvidiaUpdater.busy && !page.nvidiaInstaller.busy && versionPicker.currentIndex >= 0 && versionPicker.count > 0 + onClicked: { + page.setOperationState(qsTr("Updater"), qsTr("Applying selected version..."), "info", true); + page.nvidiaUpdater.applyVersion(versionPicker.currentText); } + } + } + } + } + + Rectangle { + Layout.fillWidth: true + visible: page.showAdvancedInfo + radius: 14 + color: page.cardColor + border.width: 1 + border.color: page.borderColor + implicitHeight: maintenanceLayout.implicitHeight + 24 + + ColumnLayout { + id: maintenanceLayout + anchors.fill: parent + anchors.margins: 12 + spacing: 10 + + Label { + text: qsTr("Maintenance") + color: page.textColor + font.pixelSize: Math.round(18 * page.uiScale) + font.bold: true + } + + RowLayout { + Layout.fillWidth: true + spacing: 8 - GridLayout { - Layout.fillWidth: true - columns: width > 640 ? 2 : 1 - columnSpacing: 8 - rowSpacing: 8 - - Button { - Layout.fillWidth: true - text: qsTr("Install Open Modules") - enabled: !nvidiaInstaller.busy - onClicked: { - page.setOperationState(qsTr("Installer"), qsTr("Installing open NVIDIA kernel modules..."), "info", true); - nvidiaInstaller.installOpenSource(); - } - } - - Button { - Layout.fillWidth: true - text: qsTr("Deep Clean") - enabled: !nvidiaInstaller.busy - onClicked: { - page.setOperationState(qsTr("Installer"), qsTr("Cleaning NVIDIA artifacts..."), "info", true); - nvidiaInstaller.deepClean(); - } - } + Button { + text: qsTr("Install Open Modules") + enabled: !page.nvidiaInstaller.busy + onClicked: { + page.setOperationState(qsTr("Installer"), qsTr("Installing open NVIDIA kernel modules..."), "info", true); + page.nvidiaInstaller.installOpenSource(); } + } - RowLayout { - Layout.fillWidth: true - spacing: 8 - - ComboBox { - id: versionPicker - Layout.fillWidth: true - model: nvidiaUpdater.availableVersions - enabled: model.length > 0 - palette.text: page.textColor - palette.buttonText: page.textColor - } - - Button { - text: qsTr("Apply Selected") - enabled: !nvidiaUpdater.busy && !nvidiaInstaller.busy && versionPicker.currentIndex >= 0 && versionPicker.count > 0 - onClicked: { - page.setOperationState(qsTr("Updater"), qsTr("Applying selected version..."), "info", true); - nvidiaUpdater.applyVersion(versionPicker.currentText); - } - } + Button { + text: qsTr("Deep Clean") + enabled: !page.nvidiaInstaller.busy + onClicked: { + page.setOperationState(qsTr("Installer"), qsTr("Cleaning NVIDIA artifacts..."), "info", true); + page.nvidiaInstaller.deepClean(); } } } @@ -353,6 +413,7 @@ Item { readOnly: true wrapMode: Text.Wrap color: page.textColor + font.family: "Noto Sans Mono" background: Rectangle { radius: 10 color: page.bgColor @@ -363,14 +424,7 @@ Item { RowLayout { Layout.fillWidth: true - - Label { - Layout.fillWidth: true - visible: page.showAdvancedInfo - text: nvidiaDetector.verificationReport - color: page.softTextColor - elide: Text.ElideRight - } + Item { Layout.fillWidth: true } Button { text: qsTr("Clear") @@ -383,7 +437,7 @@ Item { } Connections { - target: nvidiaInstaller + target: page.nvidiaInstaller function onProgressMessage(message) { page.setOperationState(qsTr("Installer"), message, "info", true); @@ -393,40 +447,49 @@ Item { function onInstallFinished(success, message) { page.finishOperation(qsTr("Installer"), success, message); page.appendLog(qsTr("Installer"), message); - nvidiaDetector.refresh(); - nvidiaUpdater.checkForUpdate(); - nvidiaInstaller.refreshProprietaryAgreement(); + page.nvidiaDetector.refresh(); + page.nvidiaUpdater.checkForUpdate(); + page.nvidiaInstaller.refreshProprietaryAgreement(); } function onRemoveFinished(success, message) { page.finishOperation(qsTr("Installer"), success, message); page.appendLog(qsTr("Installer"), message); - nvidiaDetector.refresh(); - nvidiaUpdater.checkForUpdate(); - nvidiaInstaller.refreshProprietaryAgreement(); + page.nvidiaDetector.refresh(); + page.nvidiaUpdater.checkForUpdate(); + page.nvidiaInstaller.refreshProprietaryAgreement(); } } Connections { - target: nvidiaUpdater + target: page.nvidiaUpdater function onProgressMessage(message) { page.setOperationState(qsTr("Updater"), message, "info", true); page.appendLog(qsTr("Updater"), message); } + function onCheckFinished(success, message) { + if (success && page.suppressPassiveStatus && !page.nvidiaUpdater.updateAvailable) + page.setOperationState(qsTr("Updater"), qsTr("Ready"), "info", false); + else + page.finishOperation(qsTr("Updater"), success, message); + page.appendLog(qsTr("Updater"), message); + page.suppressPassiveStatus = false; + } + function onUpdateFinished(success, message) { page.finishOperation(qsTr("Updater"), success, message); page.appendLog(qsTr("Updater"), message); - nvidiaDetector.refresh(); - nvidiaUpdater.checkForUpdate(); + page.nvidiaDetector.refresh(); + page.nvidiaUpdater.checkForUpdate(); } } Component.onCompleted: { - nvidiaDetector.refresh(); - nvidiaUpdater.checkForUpdate(); - nvidiaUpdater.refreshAvailableVersions(); - nvidiaInstaller.refreshProprietaryAgreement(); + page.nvidiaDetector.refresh(); + page.suppressPassiveStatus = true; + page.nvidiaUpdater.checkForUpdate(); + page.nvidiaInstaller.refreshProprietaryAgreement(); } } diff --git a/src/qml/pages/MonitorPage.qml b/src/qml/pages/MonitorPage.qml index d031ba5..bc409f1 100644 --- a/src/qml/pages/MonitorPage.qml +++ b/src/qml/pages/MonitorPage.qml @@ -4,6 +4,10 @@ import QtQuick.Layouts Item { id: page + required property var systemInfo + required property var cpuMonitor + required property var gpuMonitor + required property var ramMonitor property var theme: ({}) property bool darkMode: false @@ -42,7 +46,7 @@ Item { Rectangle { Layout.fillWidth: true - implicitHeight: 126 + implicitHeight: page.gpuMonitor && page.gpuMonitor.available ? 126 : 154 radius: 14 color: page.cardColor border.width: 1 @@ -54,8 +58,8 @@ Item { spacing: 6 Label { text: qsTr("CPU"); color: page.softTextColor; font.bold: true } - Label { text: cpuMonitor.usagePercent.toFixed(1) + "%"; color: page.textColor; font.pixelSize: Math.round(22 * page.uiScale); font.bold: true } - Label { text: qsTr("Temperature: %1").arg(page.formatTemp(cpuMonitor.temperatureC)); color: page.softTextColor } + Label { text: page.cpuMonitor ? page.cpuMonitor.usagePercent.toFixed(1) + "%" : "--"; color: page.textColor; font.pixelSize: Math.round(22 * page.uiScale); font.bold: true } + Label { text: qsTr("Temperature: %1").arg(page.formatTemp(page.cpuMonitor ? page.cpuMonitor.temperatureC : -1)); color: page.softTextColor } } } @@ -73,8 +77,15 @@ Item { spacing: 6 Label { text: qsTr("GPU"); color: page.softTextColor; font.bold: true } - Label { text: gpuMonitor.utilizationPercent + "%"; color: page.textColor; font.pixelSize: Math.round(22 * page.uiScale); font.bold: true } - Label { text: qsTr("Temperature: %1").arg(page.formatTemp(gpuMonitor.temperatureC)); color: page.softTextColor } + Label { text: page.gpuMonitor ? page.gpuMonitor.utilizationPercent + "%" : "--"; color: page.textColor; font.pixelSize: Math.round(22 * page.uiScale); font.bold: true } + Label { text: qsTr("Temperature: %1").arg(page.formatTemp(page.gpuMonitor ? page.gpuMonitor.temperatureC : -1)); color: page.softTextColor } + Label { + visible: page.gpuMonitor && !page.gpuMonitor.available && page.gpuMonitor.statusMessage.length > 0 + text: page.gpuMonitor ? page.gpuMonitor.statusMessage : "" + color: page.softTextColor + wrapMode: Text.Wrap + width: parent.width + } } } @@ -92,8 +103,8 @@ Item { spacing: 6 Label { text: qsTr("Memory"); color: page.softTextColor; font.bold: true } - Label { text: ramMonitor.usagePercent + "%"; color: page.textColor; font.pixelSize: Math.round(22 * page.uiScale); font.bold: true } - Label { text: qsTr("Usage: %1").arg(page.formatRam(ramMonitor.usedMiB, ramMonitor.totalMiB)); color: page.softTextColor } + Label { text: page.ramMonitor ? page.ramMonitor.usagePercent + "%" : "--"; color: page.textColor; font.pixelSize: Math.round(22 * page.uiScale); font.bold: true } + Label { text: qsTr("Usage: %1").arg(page.formatRam(page.ramMonitor ? page.ramMonitor.usedMiB : 0, page.ramMonitor ? page.ramMonitor.totalMiB : 0)); color: page.softTextColor } } } } @@ -124,7 +135,7 @@ Item { Layout.fillWidth: true from: 0 to: 100 - value: cpuMonitor.usagePercent + value: page.cpuMonitor ? page.cpuMonitor.usagePercent : 0 } Label { text: qsTr("GPU"); color: page.softTextColor } @@ -132,7 +143,7 @@ Item { Layout.fillWidth: true from: 0 to: 100 - value: gpuMonitor.utilizationPercent + value: page.gpuMonitor ? page.gpuMonitor.utilizationPercent : 0 } Label { text: qsTr("RAM"); color: page.softTextColor } @@ -140,89 +151,22 @@ Item { Layout.fillWidth: true from: 0 to: 100 - value: ramMonitor.usagePercent - } - - RowLayout { - Layout.fillWidth: true - spacing: 8 - - Button { - text: qsTr("Refresh") - onClicked: { - cpuMonitor.refresh(); - gpuMonitor.refresh(); - ramMonitor.refresh(); - } - } - - Item { Layout.fillWidth: true } - - Label { - visible: page.showAdvancedInfo - text: qsTr("Interval: %1 ms").arg(cpuMonitor.updateInterval) - color: page.softTextColor - } + value: page.ramMonitor ? page.ramMonitor.usagePercent : 0 } - } - } - - Rectangle { - Layout.fillWidth: true - radius: 14 - color: page.cardColor - border.width: 1 - border.color: page.borderColor - implicitHeight: detailLayout.implicitHeight + 24 - ColumnLayout { - id: detailLayout - anchors.fill: parent - anchors.margins: 12 - spacing: 8 - - Label { - text: qsTr("Detailed Telemetry") - color: page.textColor - font.pixelSize: Math.round(18 * page.uiScale) - font.bold: true - } - - Label { - text: qsTr("CPU Temperature: %1").arg(page.formatTemp(cpuMonitor.temperatureC)) - color: page.softTextColor - } - - Label { - text: qsTr("GPU Temperature: %1").arg(page.formatTemp(gpuMonitor.temperatureC)) - color: page.softTextColor - } - - Label { - text: qsTr("GPU Memory: %1 / %2 MiB") - .arg(gpuMonitor.memoryUsedMiB) - .arg(gpuMonitor.memoryTotalMiB) - color: page.softTextColor - } - - Label { - text: qsTr("RAM Used: %1 / %2 MiB") - .arg(ramMonitor.usedMiB) - .arg(ramMonitor.totalMiB) - color: page.softTextColor - } } } } } Component.onCompleted: { - systemInfo.refresh(); - cpuMonitor.start(); - gpuMonitor.start(); - ramMonitor.start(); - cpuMonitor.refresh(); - gpuMonitor.refresh(); - ramMonitor.refresh(); + if (page.systemInfo) + page.systemInfo.refresh(); + if (page.cpuMonitor) + page.cpuMonitor.start(); + if (page.gpuMonitor) + page.gpuMonitor.start(); + if (page.ramMonitor) + page.ramMonitor.start(); } } diff --git a/src/qml/pages/SettingsPage.qml b/src/qml/pages/SettingsPage.qml index 9c38f78..933c17f 100644 --- a/src/qml/pages/SettingsPage.qml +++ b/src/qml/pages/SettingsPage.qml @@ -4,15 +4,13 @@ import QtQuick.Layouts Item { id: settingsPage + required property var uiPreferences property var theme: ({}) property bool darkMode: false property bool showAdvancedInfo: true property real uiScale: 1.0 - readonly property bool hasUiPreferences: typeof uiPreferences !== "undefined" && uiPreferences !== null - readonly property bool hasLanguageManager: typeof languageManager !== "undefined" && languageManager !== null - readonly property string themeMode: hasUiPreferences ? uiPreferences.themeMode : "system" - property string lastDiagnosticsRefresh: "" + readonly property bool hasUiPreferences: settingsPage.uiPreferences !== null readonly property color cardColor: theme && theme.cardStrong ? theme.cardStrong : "#f5f8ff" readonly property color borderColor: theme && theme.border ? theme.border : "#d9e1f0" @@ -50,60 +48,6 @@ Item { font.bold: true } - RowLayout { - Layout.fillWidth: true - - Label { - Layout.fillWidth: true - text: qsTr("Theme mode") - color: settingsPage.textColor - } - - ComboBox { - id: themePicker - Layout.preferredWidth: Math.round(220 * settingsPage.uiScale) - model: hasUiPreferences ? uiPreferences.availableThemeModes : [] - textRole: "label" - palette.text: settingsPage.textColor - palette.buttonText: settingsPage.textColor - - Component.onCompleted: settingsPage.syncThemePicker() - - onActivated: { - const selected = model[currentIndex]; - if (hasUiPreferences && selected && selected.code) - uiPreferences.setThemeMode(selected.code); - } - } - } - - RowLayout { - Layout.fillWidth: true - - Label { - Layout.fillWidth: true - text: qsTr("Language") - color: settingsPage.textColor - } - - ComboBox { - id: languagePicker - Layout.preferredWidth: Math.round(220 * settingsPage.uiScale) - model: hasLanguageManager ? languageManager.availableLanguages : [] - textRole: "label" - palette.text: settingsPage.textColor - palette.buttonText: settingsPage.textColor - - Component.onCompleted: settingsPage.syncLanguagePicker() - - onActivated: { - const selected = model[currentIndex]; - if (hasLanguageManager && selected && selected.code) - languageManager.setCurrentLanguage(selected.code); - } - } - } - RowLayout { Layout.fillWidth: true @@ -114,9 +58,9 @@ Item { } Switch { - checked: hasUiPreferences ? uiPreferences.showAdvancedInfo : false - enabled: hasUiPreferences - onToggled: if (hasUiPreferences) uiPreferences.setShowAdvancedInfo(checked) + checked: settingsPage.hasUiPreferences ? settingsPage.uiPreferences.showAdvancedInfo : false + enabled: settingsPage.hasUiPreferences + onToggled: if (settingsPage.hasUiPreferences) settingsPage.uiPreferences.setShowAdvancedInfo(checked) } } @@ -125,60 +69,8 @@ Item { Button { text: qsTr("Reset Defaults") - enabled: hasUiPreferences - onClicked: if (hasUiPreferences) uiPreferences.resetToDefaults() - } - - Item { Layout.fillWidth: true } - - Label { - text: settingsPage.themeMode === "system" ? qsTr("Following system") - : (settingsPage.darkMode ? qsTr("Dark mode") : qsTr("Light mode")) - color: settingsPage.softTextColor - } - } - } - } - - Rectangle { - Layout.fillWidth: true - radius: 14 - color: settingsPage.cardColor - border.width: 1 - border.color: settingsPage.borderColor - implicitHeight: aboutLayout.implicitHeight + 24 - - ColumnLayout { - id: aboutLayout - anchors.fill: parent - anchors.margins: 12 - spacing: 8 - - Label { - text: qsTr("Diagnostics Snapshot") - color: settingsPage.textColor - font.pixelSize: Math.round(18 * settingsPage.uiScale) - font.bold: true - } - - Label { text: qsTr("Application: %1 %2").arg(Qt.application.name).arg(Qt.application.version); color: settingsPage.softTextColor } - Label { text: qsTr("GPU: %1").arg(nvidiaDetector.gpuFound ? nvidiaDetector.gpuName : qsTr("Not detected")); color: settingsPage.softTextColor } - Label { text: qsTr("Driver: %1").arg(nvidiaDetector.activeDriver); color: settingsPage.softTextColor } - Label { text: qsTr("Session: %1").arg(nvidiaDetector.sessionType.length > 0 ? nvidiaDetector.sessionType : qsTr("Unknown")); color: settingsPage.softTextColor } - Label { text: qsTr("Language: %1").arg(hasLanguageManager ? languageManager.currentLanguageLabel : qsTr("Unknown")); color: settingsPage.softTextColor } - Label { text: qsTr("CPU Temp: %1 C").arg(cpuMonitor.temperatureC); color: settingsPage.softTextColor } - Label { text: qsTr("GPU Temp: %1 C").arg(gpuMonitor.temperatureC); color: settingsPage.softTextColor } - Label { text: qsTr("RAM Used: %1 / %2 MiB").arg(ramMonitor.usedMiB).arg(ramMonitor.totalMiB); color: settingsPage.softTextColor } - Label { text: qsTr("Last refresh: %1").arg(settingsPage.lastDiagnosticsRefresh.length > 0 ? settingsPage.lastDiagnosticsRefresh : qsTr("auto")); color: settingsPage.softTextColor } - - Button { - text: qsTr("Refresh Diagnostics") - onClicked: { - nvidiaDetector.refresh() - cpuMonitor.refresh() - gpuMonitor.refresh() - ramMonitor.refresh() - settingsPage.lastDiagnosticsRefresh = Qt.formatDateTime(new Date(), "yyyy-MM-dd HH:mm:ss") + enabled: settingsPage.hasUiPreferences + onClicked: if (settingsPage.hasUiPreferences) settingsPage.uiPreferences.resetToDefaults() } } } @@ -186,50 +78,4 @@ Item { } } - function syncLanguagePicker() { - if (!hasLanguageManager) - return; - for (let i = 0; i < languagePicker.model.length; ++i) { - if (languagePicker.model[i].code === languageManager.currentLanguage) { - languagePicker.currentIndex = i; - break; - } - } - } - - function syncThemePicker() { - if (!hasUiPreferences) - return; - for (let i = 0; i < themePicker.model.length; ++i) { - if (themePicker.model[i].code === uiPreferences.themeMode) { - themePicker.currentIndex = i; - break; - } - } - } - - Connections { - target: hasLanguageManager ? languageManager : null - - function onAvailableVersionsChanged() { - // Logic moved to DriverPage or not needed here - } - - function onUpdateFinished(success, message) { - nvidiaDetector.refresh(); - } - } - - Connections { - target: hasUiPreferences ? uiPreferences : null - // No specific slots needed here for now - } - - Component.onCompleted: { - nvidiaDetector.refresh() - cpuMonitor.refresh() - gpuMonitor.refresh() - ramMonitor.refresh() - settingsPage.lastDiagnosticsRefresh = Qt.formatDateTime(new Date(), "yyyy-MM-dd HH:mm:ss") - } } diff --git a/tests/test_detector.cpp b/tests/test_detector.cpp index 24c9587..010ffe9 100644 --- a/tests/test_detector.cpp +++ b/tests/test_detector.cpp @@ -1,4 +1,6 @@ #include +#include +#include #include "nvidia/detector.h" @@ -78,6 +80,26 @@ private slots: detector.refresh(); QVERIFY(!detector.activeDriver().trimmed().isEmpty()); } + + void testSecureBootEfivarOverride() { + QTemporaryDir tempDir; + QVERIFY(tempDir.isValid()); + + const QString efivarPath = tempDir.filePath(QStringLiteral("SecureBoot-test")); + QFile file(efivarPath); + QVERIFY(file.open(QIODevice::WriteOnly)); + QVERIFY(file.write(QByteArray::fromHex("0700000001")) == 5); + file.close(); + + qputenv("RO_CONTROL_SECURE_BOOT_EFIVAR_PATH", efivarPath.toUtf8()); + + NvidiaDetector detector; + const auto info = detector.detect(); + QVERIFY(info.secureBootKnown); + QVERIFY(info.secureBootEnabled); + + qunsetenv("RO_CONTROL_SECURE_BOOT_EFIVAR_PATH"); + } }; QTEST_MAIN(TestDetector) diff --git a/tests/test_driver_page.cpp b/tests/test_driver_page.cpp index 2e4b444..97cf54b 100644 --- a/tests/test_driver_page.cpp +++ b/tests/test_driver_page.cpp @@ -1,6 +1,5 @@ #include #include -#include #include #include #include @@ -22,6 +21,8 @@ class DetectorMock : public QObject { NOTIFY infoChanged) Q_PROPERTY(bool secureBootEnabled READ secureBootEnabled WRITE setSecureBootEnabled NOTIFY infoChanged) + Q_PROPERTY(bool secureBootKnown READ secureBootKnown WRITE setSecureBootKnown + NOTIFY infoChanged) Q_PROPERTY(bool waylandSession READ waylandSession WRITE setWaylandSession NOTIFY infoChanged) Q_PROPERTY(QString sessionType READ sessionType WRITE setSessionType NOTIFY @@ -38,6 +39,7 @@ class DetectorMock : public QObject { bool driverLoaded() const { return m_driverLoaded; } bool nouveauActive() const { return m_nouveauActive; } bool secureBootEnabled() const { return m_secureBootEnabled; } + bool secureBootKnown() const { return m_secureBootKnown; } bool waylandSession() const { return m_waylandSession; } QString sessionType() const { return m_sessionType; } QString activeDriver() const { return m_activeDriver; } @@ -99,6 +101,14 @@ class DetectorMock : public QObject { emit infoChanged(); } + void setSecureBootKnown(bool value) { + if (m_secureBootKnown == value) { + return; + } + m_secureBootKnown = value; + emit infoChanged(); + } + void setSessionType(const QString &value) { if (m_sessionType == value) { return; @@ -135,10 +145,11 @@ class DetectorMock : public QObject { bool m_driverLoaded = false; bool m_nouveauActive = false; bool m_secureBootEnabled = false; + bool m_secureBootKnown = false; bool m_waylandSession = false; QString m_sessionType = QStringLiteral("unknown"); - QString m_activeDriver = QStringLiteral("Not Installed / Unknown"); - QString m_verificationReport = QStringLiteral("GPU: None"); + QString m_activeDriver = QStringLiteral("Not Installed"); + QString m_verificationReport = QStringLiteral("GPU: Unavailable"); }; class InstallerMock : public QObject { @@ -272,6 +283,7 @@ class UpdaterMock : public QObject { void availableVersionsChanged(); void busyChanged(); void progressMessage(const QString &message); + void checkFinished(bool success, const QString &message); void updateFinished(bool success, const QString &message); private: @@ -298,10 +310,6 @@ QObject *TestDriverPage::createPage(DetectorMock *detector, InstallerMock *installer, UpdaterMock *updater, QQmlEngine *engine) const { - engine->rootContext()->setContextProperty("nvidiaDetector", detector); - engine->rootContext()->setContextProperty("nvidiaInstaller", installer); - engine->rootContext()->setContextProperty("nvidiaUpdater", updater); - const QString sourceRoot = QStringLiteral(RO_CONTROL_SOURCE_DIR); const QString sourcePagePath = QDir(sourceRoot).filePath(QStringLiteral("src/qml/pages/DriverPage.qml")); @@ -389,6 +397,12 @@ QObject *TestDriverPage::createPage(DetectorMock *detector, initialProperties.insert(QStringLiteral("width"), 1280); initialProperties.insert(QStringLiteral("height"), 900); initialProperties.insert(QStringLiteral("theme"), theme); + initialProperties.insert(QStringLiteral("nvidiaDetector"), + QVariant::fromValue(detector)); + initialProperties.insert(QStringLiteral("nvidiaInstaller"), + QVariant::fromValue(installer)); + initialProperties.insert(QStringLiteral("nvidiaUpdater"), + QVariant::fromValue(updater)); QObject *object = component.createWithInitialProperties(initialProperties); if (object == nullptr) { diff --git a/tests/test_monitor.cpp b/tests/test_monitor.cpp index 448217a..36b1775 100644 --- a/tests/test_monitor.cpp +++ b/tests/test_monitor.cpp @@ -107,6 +107,23 @@ private slots: qunsetenv("RO_CONTROL_COMMAND_NVIDIA_SMI"); } + void testGpuStatusMessageWhenTelemetryUnavailable() { + qputenv("RO_CONTROL_COMMAND_NVIDIA_SMI", + QByteArrayLiteral("/definitely/missing/nvidia-smi")); + qputenv("RO_CONTROL_DRM_ROOT", + QByteArrayLiteral("/definitely/missing/drm-root")); + + GpuMonitor gpu; + gpu.stop(); + gpu.refresh(); + + QVERIFY(!gpu.available()); + QVERIFY(!gpu.statusMessage().trimmed().isEmpty()); + + qunsetenv("RO_CONTROL_COMMAND_NVIDIA_SMI"); + qunsetenv("RO_CONTROL_DRM_ROOT"); + } + void testRamConstruction() { RamMonitor ram; QVERIFY(ram.running()); diff --git a/tests/test_preferences.cpp b/tests/test_preferences.cpp index ebcf567..5946644 100644 --- a/tests/test_preferences.cpp +++ b/tests/test_preferences.cpp @@ -81,12 +81,12 @@ void TestPreferences::testLanguageManagerExposesEffectiveLanguageMetadata() { manager.setCurrentLanguage(QStringLiteral("system")); QVERIFY( - manager.currentLanguageLabel().startsWith(QStringLiteral("System Default"))); + manager.currentLanguageLabel().startsWith(QStringLiteral("System"))); manager.setCurrentLanguage(QStringLiteral("tr")); QCOMPARE(manager.currentLanguage(), QStringLiteral("tr")); QCOMPARE(manager.effectiveLanguage(), QStringLiteral("tr")); - QCOMPARE(manager.currentLanguageLabel(), QStringLiteral("Turkce")); + QCOMPARE(manager.currentLanguageLabel(), QStringLiteral("Türkçe")); } QTEST_GUILESS_MAIN(TestPreferences) diff --git a/tests/test_updater.cpp b/tests/test_updater.cpp index d24bd1f..4c81df9 100644 --- a/tests/test_updater.cpp +++ b/tests/test_updater.cpp @@ -57,7 +57,7 @@ private slots: void testBuildTransactionArgumentsForFreshInstallStaysScoped() { NvidiaUpdater updater; - updater.m_latestVersion = QStringLiteral("3:570.153.02-1.fc42"); + updater.m_latestPackageVersion = QStringLiteral("3:570.153.02-1.fc42"); const QStringList args = updater.buildTransactionArguments(QString(), QString(), QString(), @@ -74,7 +74,7 @@ private slots: void testBuildTransactionArgumentsForInstalledDriverAvoidsBroadUpdate() { NvidiaUpdater updater; - updater.m_latestVersion = QStringLiteral("3:570.153.02-1.fc42"); + updater.m_latestPackageVersion = QStringLiteral("3:570.153.02-1.fc42"); const QStringList args = updater.buildTransactionArguments( QString(), QStringLiteral("3:565.77-1.fc42"), QString(), @@ -114,7 +114,7 @@ private slots: void testBuildTransactionArgumentsForOpenKernelModules() { NvidiaUpdater updater; - updater.m_latestVersion = QStringLiteral("3:570.153.02-1.fc42"); + updater.m_latestPackageVersion = QStringLiteral("3:570.153.02-1.fc42"); const QStringList args = updater.buildTransactionArguments( QString(), QStringLiteral("3:565.77-1.fc42"), QString(), @@ -123,6 +123,36 @@ private slots: QVERIFY(args.contains( QStringLiteral("akmod-nvidia-open-3:570.153.02-1.fc42"))); } + + void testParseOfficialUnixDriverVersions() { + const QString sample = QStringLiteral( + "Linux x86_64/AMD64/EM64T " + "Latest Production Branch Version: 595.71.05 " + "Latest New Feature Branch Version: 590.48.01 " + "Latest Beta Version: 595.45.04 " + "Latest Legacy GPU version (470.xx series): 470.256.02 " + "Linux aarch64 Latest Production Branch Version: 595.71.05" + ""); + + const QStringList versions = + NvidiaVersionParser::parseOfficialUnixDriverVersions( + sample, QStringLiteral("x86_64")); + + QCOMPARE(versions.size(), 4); + QCOMPARE(versions.at(0), QStringLiteral("595.71.05")); + QCOMPARE(versions.at(1), QStringLiteral("590.48.01")); + QCOMPARE(versions.at(2), QStringLiteral("595.45.04")); + QCOMPARE(versions.at(3), QStringLiteral("470.256.02")); + } + + void testNormalizedDriverVersion() { + QCOMPARE(NvidiaVersionParser::normalizedDriverVersion( + QStringLiteral("3:570.153.02-1.fc42")), + QStringLiteral("570.153.02")); + QCOMPARE(NvidiaVersionParser::normalizedDriverVersion( + QStringLiteral("595.71.05")), + QStringLiteral("595.71.05")); + } }; QTEST_MAIN(TestUpdater)