diff --git a/CMakeLists.txt b/CMakeLists.txt index f54f960..c062e2e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -75,6 +75,7 @@ set(BACKEND_SOURCES src/backend/system/capabilityprobe.cpp src/backend/system/languagemanager.cpp src/backend/system/uipreferencesmanager.cpp + src/backend/system/systeminfoprovider.cpp ) set(APP_SOURCES diff --git a/data/icons/hicolor/256x256/apps/ro-control.png b/data/icons/hicolor/256x256/apps/ro-control.png index adaced5..152fd4d 100644 Binary files a/data/icons/hicolor/256x256/apps/ro-control.png and b/data/icons/hicolor/256x256/apps/ro-control.png differ diff --git a/data/icons/hicolor/scalable/apps/ro-control-dial-core.svg b/data/icons/hicolor/scalable/apps/ro-control-dial-core.svg deleted file mode 100644 index dd5e7ee..0000000 --- a/data/icons/hicolor/scalable/apps/ro-control-dial-core.svg +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/data/icons/hicolor/scalable/apps/ro-control-shield-chip.svg b/data/icons/hicolor/scalable/apps/ro-control-shield-chip.svg deleted file mode 100644 index 17b40e8..0000000 --- a/data/icons/hicolor/scalable/apps/ro-control-shield-chip.svg +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/data/icons/hicolor/scalable/apps/ro-control.svg b/data/icons/hicolor/scalable/apps/ro-control.svg index 0199ee2..b5f128e 100644 --- a/data/icons/hicolor/scalable/apps/ro-control.svg +++ b/data/icons/hicolor/scalable/apps/ro-control.svg @@ -1,44 +1,39 @@ - - - - - - - - - - - + + + - + + - - - - - + + - - - - + + + - - - - + + - - - - - + + - - + + - - - - + + diff --git a/src/backend/monitor/gpumonitor.cpp b/src/backend/monitor/gpumonitor.cpp index eb2ad06..d92b139 100644 --- a/src/backend/monitor/gpumonitor.cpp +++ b/src/backend/monitor/gpumonitor.cpp @@ -1,6 +1,8 @@ #include "gpumonitor.h" #include "system/commandrunner.h" +#include +#include #include #include @@ -37,6 +39,119 @@ bool parseMetricInt(const QString &field, int *value) { return true; } +QString drmRootPath() { + const QString overridePath = + qEnvironmentVariable("RO_CONTROL_DRM_ROOT").trimmed(); + return overridePath.isEmpty() ? QStringLiteral("/sys/class/drm") + : overridePath; +} + +QString readFileText(const QString &path) { + QFile file(path); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + return {}; + } + + return QString::fromUtf8(file.readAll()).trimmed(); +} + +bool readIntegerFile(const QString &path, qint64 *value) { + if (value == nullptr) { + return false; + } + + bool ok = false; + const qint64 parsedValue = readFileText(path).toLongLong(&ok); + if (!ok) { + return false; + } + + *value = parsedValue; + return true; +} + +bool readFirstTemperatureFromHwmon(const QString &basePath, int *value) { + const QFileInfoList hwmonEntries = + QDir(basePath).entryInfoList({QStringLiteral("hwmon*")}, + QDir::Dirs | QDir::NoDotAndDotDot, + QDir::Name); + for (const QFileInfo &entry : hwmonEntries) { + const QFileInfoList inputs = QDir(entry.absoluteFilePath()) + .entryInfoList({QStringLiteral("temp*_input")}, + QDir::Files, QDir::Name); + for (const QFileInfo &input : inputs) { + qint64 milliC = 0; + if (readIntegerFile(input.absoluteFilePath(), &milliC) && milliC > 0) { + *value = static_cast(milliC / 1000); + return true; + } + } + } + + return false; +} + +bool readGenericLinuxGpuMetrics(int *temperatureC, int *utilizationPercent, + int *memoryUsedMiB, int *memoryTotalMiB) { + const QFileInfoList cardEntries = + QDir(drmRootPath()).entryInfoList({QStringLiteral("card*")}, + QDir::Dirs | QDir::NoDotAndDotDot, + QDir::Name); + + bool anyMetric = false; + for (const QFileInfo &cardEntry : cardEntries) { + const QString devicePath = + cardEntry.absoluteFilePath() + QStringLiteral("/device"); + if (!QFile::exists(devicePath)) { + continue; + } + + int tempValue = 0; + if (temperatureC != nullptr && + readFirstTemperatureFromHwmon(devicePath, &tempValue)) { + *temperatureC = tempValue; + anyMetric = true; + } + + qint64 busyPercent = 0; + if (utilizationPercent != nullptr && + readIntegerFile(devicePath + QStringLiteral("/gpu_busy_percent"), + &busyPercent)) { + *utilizationPercent = + std::clamp(static_cast(busyPercent), 0, 100); + anyMetric = true; + } + + qint64 usedBytes = 0; + qint64 totalBytes = 0; + const bool usedOk = + readIntegerFile(devicePath + QStringLiteral("/mem_info_vram_used"), + &usedBytes); + const bool totalOk = + readIntegerFile(devicePath + QStringLiteral("/mem_info_vram_total"), + &totalBytes); + if (usedOk && totalOk && totalBytes > 0) { + if (memoryUsedMiB != nullptr) { + *memoryUsedMiB = + std::max(0, static_cast(static_cast(usedBytes) / + (1024 * 1024))); + } + if (memoryTotalMiB != nullptr) { + *memoryTotalMiB = + std::max(0, static_cast(static_cast(totalBytes) / + (1024 * 1024))); + } + anyMetric = true; + } + + if (anyMetric) { + return true; + } + } + + return false; +} + } // namespace GpuMonitor::GpuMonitor(QObject *parent) : QObject(parent) { @@ -80,8 +195,48 @@ void GpuMonitor::refresh() { options); if (!result.success()) { - setAvailable(false); - clearMetrics(); + int nextTemp = 0; + int nextUtil = 0; + int nextUsed = 0; + int nextTotal = 0; + + if (!readGenericLinuxGpuMetrics(&nextTemp, &nextUtil, &nextUsed, + &nextTotal)) { + setAvailable(false); + clearMetrics(); + return; + } + + if (m_temperatureC != nextTemp) { + m_temperatureC = nextTemp; + emit temperatureCChanged(); + } + if (m_utilizationPercent != nextUtil) { + m_utilizationPercent = nextUtil; + emit utilizationPercentChanged(); + } + if (m_memoryUsedMiB != nextUsed) { + m_memoryUsedMiB = nextUsed; + emit memoryUsedMiBChanged(); + } + if (m_memoryTotalMiB != nextTotal) { + m_memoryTotalMiB = nextTotal; + emit memoryTotalMiBChanged(); + } + + const int usagePercent = + nextTotal > 0 + ? std::clamp(static_cast((static_cast(nextUsed) / + static_cast(nextTotal)) * + 100.0), + 0, 100) + : 0; + if (m_memoryUsagePercent != usagePercent) { + m_memoryUsagePercent = usagePercent; + emit memoryUsagePercentChanged(); + } + + setAvailable(true); return; } diff --git a/src/backend/monitor/rammonitor.cpp b/src/backend/monitor/rammonitor.cpp index c195032..8aef1af 100644 --- a/src/backend/monitor/rammonitor.cpp +++ b/src/backend/monitor/rammonitor.cpp @@ -191,15 +191,6 @@ void RamMonitor::refresh() { memAvailableKiB = memFreeKiB + buffersKiB + cachedKiB + reclaimable - shmem; } - // TR: Tutarsiz veri geldiyse metrikleri sifirla ve "unavailable" olarak isle. - // EN: If metrics are inconsistent, clear values and mark monitor unavailable. - if (memTotalKiB <= 0 || memAvailableKiB < 0 || - memAvailableKiB > memTotalKiB) { - setAvailable(false); - clearMetrics(); - return; - } - RamSnapshot snapshot = buildSnapshot(memTotalKiB, memAvailableKiB); if (!snapshot.valid) { snapshot = readSnapshotFromFree(); diff --git a/src/backend/nvidia/detector.cpp b/src/backend/nvidia/detector.cpp index f86f59f..499c860 100644 --- a/src/backend/nvidia/detector.cpp +++ b/src/backend/nvidia/detector.cpp @@ -14,6 +14,7 @@ NvidiaDetector::NvidiaDetector(QObject *parent) : QObject(parent) {} NvidiaDetector::GpuInfo NvidiaDetector::detect() const { GpuInfo info; + info.displayAdapterName = detectDisplayAdapterName(); info.name = detectGpuName(); info.found = !info.name.isEmpty(); info.driverVersion = detectDriverVersion(); @@ -50,7 +51,11 @@ QString NvidiaDetector::activeDriver() const { } QString NvidiaDetector::verificationReport() const { - const QString gpuText = m_info.found ? m_info.name : tr("None"); + const QString gpuText = m_info.found + ? m_info.name + : (m_info.displayAdapterName.isEmpty() + ? tr("None") + : m_info.displayAdapterName); const QString versionText = m_info.driverVersion.isEmpty() ? tr("None") : m_info.driverVersion; @@ -70,6 +75,39 @@ void NvidiaDetector::refresh() { emit infoChanged(); } +QString NvidiaDetector::detectDisplayAdapterName() const { + if (!CapabilityProbe::isToolAvailable(QStringLiteral("lspci"))) { + return {}; + } + + CommandRunner runner; + + const auto result = + runner.run(QStringLiteral("lspci"), {QStringLiteral("-mm")}); + + if (!result.success()) + return {}; + + const QStringList lines = result.stdout.split(QLatin1Char('\n')); + for (const QString &line : lines) { + if (line.contains(QStringLiteral("VGA"), Qt::CaseInsensitive) || + line.contains(QStringLiteral("3D controller"), Qt::CaseInsensitive) || + line.contains(QStringLiteral("Display controller"), + Qt::CaseInsensitive)) { + static const QRegularExpression re(QStringLiteral("\"([^\"]+)\"")); + auto it = re.globalMatch(line); + QStringList parts; + while (it.hasNext()) + parts << it.next().captured(1); + + if (parts.size() >= 3) + return parts[2]; + } + } + + return {}; +} + QString NvidiaDetector::detectGpuName() const { if (!CapabilityProbe::isToolAvailable(QStringLiteral("lspci"))) { return {}; diff --git a/src/backend/nvidia/detector.h b/src/backend/nvidia/detector.h index c7cab19..0704bc5 100644 --- a/src/backend/nvidia/detector.h +++ b/src/backend/nvidia/detector.h @@ -9,6 +9,8 @@ class NvidiaDetector : public QObject { Q_PROPERTY(bool gpuFound READ gpuFound NOTIFY infoChanged) Q_PROPERTY(QString gpuName READ gpuName NOTIFY infoChanged) + Q_PROPERTY(QString displayAdapterName READ displayAdapterName NOTIFY + infoChanged) Q_PROPERTY(QString driverVersion READ driverVersion NOTIFY infoChanged) Q_PROPERTY(bool driverLoaded READ driverLoaded NOTIFY infoChanged) Q_PROPERTY(bool nouveauActive READ nouveauActive NOTIFY infoChanged) @@ -24,6 +26,7 @@ class NvidiaDetector : public QObject { struct GpuInfo { bool found = false; QString name; + QString displayAdapterName; QString driverVersion; QString vbiosVersion; bool driverLoaded = false; @@ -38,6 +41,7 @@ class NvidiaDetector : public QObject { bool gpuFound() const { return m_info.found; } QString gpuName() const { return m_info.name; } + QString displayAdapterName() const { return m_info.displayAdapterName; } QString driverVersion() const { return m_info.driverVersion; } bool driverLoaded() const { return m_info.driverLoaded; } bool nouveauActive() const { return m_info.nouveauActive; } @@ -63,6 +67,7 @@ class NvidiaDetector : public QObject { private: QString detectGpuName() const; + QString detectDisplayAdapterName() const; QString detectDriverVersion() const; bool isPackageInstalled(const QString &packageName) const; bool isModuleLoaded(const QString &moduleName) const; diff --git a/src/backend/system/systeminfoprovider.cpp b/src/backend/system/systeminfoprovider.cpp new file mode 100644 index 0000000..f8611e6 --- /dev/null +++ b/src/backend/system/systeminfoprovider.cpp @@ -0,0 +1,238 @@ +#include "systeminfoprovider.h" + +#include "commandrunner.h" + +#include +#include +#include +#include +#include + +#if defined(Q_OS_UNIX) +#include +#endif + +namespace { + +QString simplifiedDesktopName(const QString &desktop) { + const QString trimmed = desktop.trimmed(); + if (trimmed.compare(QStringLiteral("KDE"), Qt::CaseInsensitive) == 0 || + trimmed.compare(QStringLiteral("KDE Plasma"), Qt::CaseInsensitive) == 0 || + trimmed.compare(QStringLiteral("Plasma"), Qt::CaseInsensitive) == 0) { + return QStringLiteral("KDE Plasma"); + } + + if (trimmed.compare(QStringLiteral("GNOME"), Qt::CaseInsensitive) == 0) { + return QStringLiteral("GNOME"); + } + + return trimmed; +} + +[[maybe_unused]] QString virtualizationLabel(const QString &value) { + const QString lowered = value.trimmed().toLower(); + if (lowered.isEmpty() || lowered == QStringLiteral("none")) { + return {}; + } + if (lowered == QStringLiteral("kvm") || lowered == QStringLiteral("qemu")) { + return QStringLiteral("KVM/QEMU"); + } + if (lowered == QStringLiteral("vmware")) { + return QStringLiteral("VMware"); + } + if (lowered == QStringLiteral("oracle") || + lowered == QStringLiteral("virtualbox")) { + return QStringLiteral("VirtualBox"); + } + if (lowered == QStringLiteral("microsoft")) { + return QStringLiteral("Hyper-V"); + } + if (lowered == QStringLiteral("parallels")) { + return QStringLiteral("Parallels"); + } + return value.trimmed(); +} + +QString valueFromOsRelease(const QString &key) { + QFile file(QStringLiteral("/etc/os-release")); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + return {}; + } + + QTextStream stream(&file); + while (!stream.atEnd()) { + const QString line = stream.readLine().trimmed(); + if (!line.startsWith(key + QLatin1Char('='))) { + continue; + } + + QString value = line.mid(key.size() + 1).trimmed(); + if (value.startsWith(QLatin1Char('"')) && value.endsWith(QLatin1Char('"')) && + value.size() >= 2) { + value = value.mid(1, value.size() - 2); + } + return value; + } + + return {}; +} + +} // namespace + +SystemInfoProvider::SystemInfoProvider(QObject *parent) : QObject(parent) { + refresh(); +} + +void SystemInfoProvider::refresh() { + const QString nextOsName = detectOsName(); + const QString nextDesktopEnvironment = detectDesktopEnvironment(); + const QString nextKernelVersion = detectKernelVersion(); + const QString nextCpuModel = detectCpuModel(); + + if (m_osName == nextOsName && + m_desktopEnvironment == nextDesktopEnvironment && + m_kernelVersion == nextKernelVersion && m_cpuModel == nextCpuModel) { + return; + } + + m_osName = nextOsName; + m_desktopEnvironment = nextDesktopEnvironment; + m_kernelVersion = nextKernelVersion; + m_cpuModel = nextCpuModel; + emit infoChanged(); +} + +QString SystemInfoProvider::detectOsName() const { + const QString prettyName = valueFromOsRelease(QStringLiteral("PRETTY_NAME")); + if (!prettyName.isEmpty()) { + return prettyName; + } + + const QString productName = QSysInfo::prettyProductName(); + if (!productName.isEmpty()) { + return productName; + } + + return QSysInfo::productType(); +} + +QString SystemInfoProvider::detectKernelVersion() const { +#if defined(Q_OS_UNIX) + utsname name {}; + if (uname(&name) == 0) { + return QString::fromLocal8Bit(name.release); + } +#endif + return QSysInfo::kernelVersion(); +} + +QString SystemInfoProvider::detectCpuModel() const { + QString virtualizationType; +#if defined(Q_OS_LINUX) + CommandRunner runner; + const auto lscpuResult = runner.run(QStringLiteral("lscpu")); + if (lscpuResult.success()) { + const QStringList lines = + lscpuResult.stdout.split(QLatin1Char('\n'), Qt::SkipEmptyParts); + for (const QString &line : lines) { + if (line.startsWith(QStringLiteral("Model name:")) || + line.startsWith(QStringLiteral("Hardware:")) || + line.startsWith(QStringLiteral("Processor:"))) { + const int separatorIndex = line.indexOf(QLatin1Char(':')); + if (separatorIndex >= 0) { + const QString value = line.mid(separatorIndex + 1).trimmed(); + if (!value.isEmpty() && + value.compare(QSysInfo::currentCpuArchitecture(), + Qt::CaseInsensitive) != 0) { + return value; + } + } + } + } + } + + QFile cpuInfo(QStringLiteral("/proc/cpuinfo")); + if (cpuInfo.open(QIODevice::ReadOnly | QIODevice::Text)) { + QTextStream stream(&cpuInfo); + while (!stream.atEnd()) { + const QString line = stream.readLine(); + if (line.startsWith(QStringLiteral("model name")) || + line.startsWith(QStringLiteral("Hardware")) || + line.startsWith(QStringLiteral("Processor"))) { + const int separatorIndex = line.indexOf(QLatin1Char(':')); + if (separatorIndex >= 0) { + const QString value = line.mid(separatorIndex + 1).trimmed(); + if (!value.isEmpty() && + value.compare(QSysInfo::currentCpuArchitecture(), + Qt::CaseInsensitive) != 0) { + return value; + } + } + } + } + } + + const auto virtResult = + runner.run(QStringLiteral("systemd-detect-virt"), {QStringLiteral("--quiet"), QStringLiteral("--vm")}); + if (virtResult.success()) { + const auto virtName = runner.run(QStringLiteral("systemd-detect-virt")); + if (virtName.success()) { + virtualizationType = virtualizationLabel(virtName.stdout.trimmed()); + } + } + + if (virtualizationType.isEmpty()) { + const QString productName = + valueFromOsRelease(QStringLiteral("VIRTUALIZATION")); + virtualizationType = virtualizationLabel(productName); + } +#elif defined(Q_OS_MACOS) + CommandRunner runner; + const auto result = + runner.run(QStringLiteral("sysctl"), + {QStringLiteral("-n"), QStringLiteral("machdep.cpu.brand_string")}); + if (result.success()) { + const QString value = result.stdout.trimmed(); + if (!value.isEmpty()) { + return value; + } + } +#endif + + const QString architecture = QSysInfo::currentCpuArchitecture(); + if (!virtualizationType.isEmpty()) { + return architecture.isEmpty() + ? QStringLiteral("%1 Virtual CPU").arg(virtualizationType) + : QStringLiteral("%1 Virtual CPU (%2)") + .arg(virtualizationType, architecture); + } + return architecture.isEmpty() ? QStringLiteral("Unknown CPU") + : QStringLiteral("CPU (%1)").arg(architecture); +} + +QString SystemInfoProvider::detectDesktopEnvironment() const { + QString desktop = qEnvironmentVariable("XDG_CURRENT_DESKTOP").trimmed(); + if (desktop.isEmpty()) { + desktop = qEnvironmentVariable("DESKTOP_SESSION").trimmed(); + } + + if (desktop.isEmpty()) { + return {}; + } + + desktop.replace(QLatin1Char(':'), QLatin1String(" / ")); + const QStringList parts = desktop.split(QLatin1Char('/'), Qt::SkipEmptyParts); + QStringList normalizedParts; + for (const QString &part : parts) { + const QString trimmed = simplifiedDesktopName(part); + if (trimmed.isEmpty()) { + continue; + } + + if (!normalizedParts.contains(trimmed, Qt::CaseInsensitive)) { + normalizedParts << trimmed; + } + } + + return normalizedParts.join(QStringLiteral(" / ")); +} diff --git a/src/backend/system/systeminfoprovider.h b/src/backend/system/systeminfoprovider.h new file mode 100644 index 0000000..83d308d --- /dev/null +++ b/src/backend/system/systeminfoprovider.h @@ -0,0 +1,37 @@ +#pragma once + +#include +#include + +class SystemInfoProvider : public QObject { + Q_OBJECT + + Q_PROPERTY(QString osName READ osName NOTIFY infoChanged) + Q_PROPERTY(QString desktopEnvironment READ desktopEnvironment NOTIFY infoChanged) + Q_PROPERTY(QString kernelVersion READ kernelVersion NOTIFY infoChanged) + Q_PROPERTY(QString cpuModel READ cpuModel NOTIFY infoChanged) + +public: + explicit SystemInfoProvider(QObject *parent = nullptr); + + QString osName() const { return m_osName; } + QString desktopEnvironment() const { return m_desktopEnvironment; } + QString kernelVersion() const { return m_kernelVersion; } + QString cpuModel() const { return m_cpuModel; } + + Q_INVOKABLE void refresh(); + +signals: + void infoChanged(); + +private: + QString detectOsName() const; + QString detectKernelVersion() const; + QString detectCpuModel() const; + QString detectDesktopEnvironment() const; + + QString m_osName; + QString m_desktopEnvironment; + QString m_kernelVersion; + QString m_cpuModel; +}; diff --git a/src/main.cpp b/src/main.cpp index 2447477..2142579 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -22,6 +22,7 @@ #include "backend/nvidia/installer.h" #include "backend/nvidia/updater.h" #include "backend/system/languagemanager.h" +#include "backend/system/systeminfoprovider.h" #include "backend/system/uipreferencesmanager.h" #include "cli/cli.h" @@ -265,6 +266,7 @@ int main(int argc, char *argv[]) { CpuMonitor cpuMonitor; GpuMonitor gpuMonitor; RamMonitor ramMonitor; + SystemInfoProvider systemInfo; QQmlApplicationEngine engine; LanguageManager languageManager(&app, &engine, &translator); @@ -277,6 +279,7 @@ int main(int argc, char *argv[]) { 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); diff --git a/src/qml/Main.qml b/src/qml/Main.qml index fae0190..ac45f90 100644 --- a/src/qml/Main.qml +++ b/src/qml/Main.qml @@ -5,187 +5,211 @@ import QtQuick.Layouts ApplicationWindow { id: root visible: true - width: 1220 - height: 760 - minimumWidth: 980 - minimumHeight: 680 + width: 1360 + height: 860 + minimumWidth: 1120 + minimumHeight: 760 title: qsTr("ro-Control") + font.family: "Noto Sans" - readonly property string themeMode: uiPreferences.themeMode - readonly property bool darkMode: themeMode === "dark" - || (themeMode === "system" - && Qt.styleHints.colorScheme === Qt.Dark) + readonly property string themeMode: "light" + readonly property bool darkMode: false readonly property bool compactMode: uiPreferences.compactMode readonly property bool showAdvancedInfo: uiPreferences.showAdvancedInfo - - readonly property var pageTitles: [ - qsTr("Driver Control Center"), - qsTr("System Monitor"), - qsTr("Preferences") - ] - readonly property var pageSubtitles: [ - qsTr("Install, verify and update NVIDIA drivers with guided system checks."), - qsTr("Track live CPU, GPU and memory telemetry in one place."), - qsTr("Tune the interface and review diagnostic context before support work.") - ] - readonly property var theme: darkMode ? ({ - window: "#0f1420", - shell: "#121927", - card: "#182131", - cardStrong: "#223049", - border: "#2c3952", - text: "#edf3ff", - textMuted: "#b6c2d9", - textSoft: "#93a3bd", - accentA: "#4f8cff", - accentB: "#0ea5a3", - accentC: "#ff9f43", - success: "#2eb67d", - warning: "#f2a93b", - danger: "#ef5d68", - successBg: "#152a21", - warningBg: "#34280e", - dangerBg: "#35171b", - infoBg: "#152338", - heroStart: "#172238", - heroEnd: "#0f1726", - sidebarBg: "#0b1120", - sidebarText: "#edf3ff", - sidebarMuted: "#97a6be", - sidebarAccent: "#8ab4ff", - sidebarActive: "#1b2840", - sidebarHover: "#141c2e", - sidebarBorder: "#23314b", - sidebarHint: "#6f7d95" - }) : ({ - window: "#f4f7fb", - shell: "#eef3f9", + readonly property var theme: ({ + window: "#f3f6fb", + shell: "#edf2f8", card: "#ffffff", - cardStrong: "#f5f9ff", - border: "#d7e0ec", - text: "#132033", - textMuted: "#4f6178", - textSoft: "#6f7f96", - accentA: "#2563eb", - accentB: "#0f766e", - accentC: "#ea580c", - success: "#198754", - warning: "#b7791f", - danger: "#dc3545", - successBg: "#e8f6ee", - warningBg: "#fff4df", - dangerBg: "#fde9eb", - infoBg: "#eaf2ff", - heroStart: "#ffffff", - heroEnd: "#edf4ff", - sidebarBg: "#122033", - sidebarText: "#edf3ff", - sidebarMuted: "#aebcd2", - sidebarAccent: "#8ab4ff", - sidebarActive: "#223651", - sidebarHover: "#1a2a42", - sidebarBorder: "#2e4566", - sidebarHint: "#7d8ba0" + cardStrong: "#f5f8fe", + border: "#d9e2ef", + text: "#1f2430", + textMuted: "#56657d", + textSoft: "#71809b", + accentA: "#6674ff", + accentB: "#2bbf97", + accentC: "#ffad32", + success: "#21b37f", + warning: "#f59e0b", + danger: "#e15a5a", + successBg: "#e7faf2", + warningBg: "#fff3dd", + dangerBg: "#fdeceb", + infoBg: "#ecf3ff", + sidebarBg: "#fcfdff", + sidebarText: "#232936", + sidebarMuted: "#728199", + sidebarAccent: "#6674ff", + sidebarActive: "#edf3ff", + sidebarHover: "#f5f8fd", + sidebarBorder: "#dde5f0", + sidebarHint: "#7c8ba2", + topbarBg: "#ffffff", + topbarChip: "#f4f7fc", + topbarValue: "#202531", + contentBg: "#f5f7fb", + contentGlow: "#ebeff8" }) color: theme.window + property bool languageDialogOpen: false + + function navigateToPage(index) { + sidebar.currentIndex = index; + stack.currentIndex = index; + } + + function topBarValue(fallback, preferred) { + return preferred && preferred.length > 0 ? preferred : fallback; + } + + onLanguageDialogOpenChanged: { + if (languageDialogOpen) { + languagePopup.open(); + } else { + languagePopup.close(); + } + } Rectangle { anchors.fill: parent - gradient: Gradient { - GradientStop { - position: 0.0 - color: root.theme.heroStart - } - GradientStop { - position: 1.0 - color: root.theme.shell - } - } + color: root.theme.window } - RowLayout { + ColumnLayout { anchors.fill: parent spacing: 0 - SidebarMenu { - id: sidebar - theme: root.theme - Layout.fillHeight: true - currentIndex: 0 - onCurrentIndexChanged: stack.currentIndex = currentIndex - } - Rectangle { Layout.fillWidth: true - Layout.fillHeight: true - color: "transparent" + Layout.preferredHeight: 78 + color: root.theme.topbarBg + border.width: 1 + border.color: root.theme.border - ColumnLayout { + RowLayout { anchors.fill: parent - anchors.margins: root.compactMode ? 18 : 28 - spacing: root.compactMode ? 14 : 20 + anchors.leftMargin: 32 + anchors.rightMargin: 32 + spacing: 22 - Rectangle { - Layout.fillWidth: true - radius: 30 - color: root.theme.card - border.width: 1 - border.color: root.theme.border - implicitHeight: heroLayout.implicitHeight + 44 + Repeater { + model: [ + { + label: qsTr("Driver"), + value: root.topBarValue(qsTr("not installed"), + nvidiaDetector.driverVersion.length > 0 + ? "nvidia-" + nvidiaDetector.driverVersion + : nvidiaUpdater.currentVersion.length > 0 + ? "nvidia-" + nvidiaUpdater.currentVersion + : "") + }, + { + label: qsTr("Secure Boot"), + value: nvidiaDetector.secureBootKnown + ? (nvidiaDetector.secureBootEnabled ? qsTr("ON") : qsTr("OFF")) + : qsTr("Unknown") + }, + { + label: qsTr("GPU"), + value: root.topBarValue(qsTr("Unavailable"), nvidiaDetector.gpuName.length > 0 ? nvidiaDetector.gpuName : nvidiaDetector.displayAdapterName) + } + ] - RowLayout { - id: heroLayout - x: 24 - y: 22 - width: parent.width - 48 - spacing: 20 + delegate: RowLayout { + required property var modelData + spacing: 12 - ColumnLayout { - Layout.fillWidth: true - spacing: 8 + Label { + text: modelData.label + ":" + color: root.theme.textSoft + font.pixelSize: 13 + font.weight: Font.Medium + } - Label { - text: root.pageTitles[sidebar.currentIndex] - font.pixelSize: 30 - font.weight: Font.Bold - color: root.theme.text - } + Rectangle { + radius: 16 + color: root.theme.topbarChip + implicitHeight: 38 + implicitWidth: valueLabel.implicitWidth + 28 Label { - text: root.pageSubtitles[sidebar.currentIndex] - wrapMode: Text.Wrap - color: root.theme.textSoft - Layout.fillWidth: true + id: valueLabel + anchors.centerIn: parent + text: modelData.value + color: root.theme.topbarValue + font.pixelSize: 13 + font.weight: Font.DemiBold } } + } + } - Flow { - Layout.alignment: Qt.AlignTop - spacing: 10 - - InfoBadge { - text: root.themeMode === "system" - ? qsTr("Follow System") - : root.darkMode ? qsTr("Dark Theme") - : qsTr("Light Theme") - backgroundColor: root.theme.infoBg - foregroundColor: root.theme.text - } + Item { + Layout.fillWidth: true + } - InfoBadge { - text: root.compactMode ? qsTr("Compact Layout") : qsTr("Comfort Layout") - backgroundColor: root.theme.cardStrong - foregroundColor: root.theme.text - } - } + Rectangle { + width: 42 + height: 42 + radius: 21 + color: root.theme.cardStrong + border.width: 1 + border.color: root.theme.border + + Label { + anchors.centerIn: parent + text: "\uD83C\uDF10" + color: root.theme.text + font.pixelSize: 16 + font.weight: Font.Medium + } + + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: root.languageDialogOpen = !root.languageDialogOpen + } + } + + } + } + + RowLayout { + Layout.fillWidth: true + Layout.fillHeight: true + spacing: 0 + + SidebarMenu { + id: sidebar + theme: root.theme + Layout.fillHeight: true + currentIndex: 0 + onCurrentIndexChanged: stack.currentIndex = currentIndex + } + + Rectangle { + Layout.fillWidth: true + Layout.fillHeight: true + color: root.theme.contentBg + clip: true + + Rectangle { + anchors.fill: parent + color: "transparent" + opacity: 1.0 + gradient: Gradient { + orientation: Gradient.Horizontal + GradientStop { position: 0.0; color: root.theme.contentGlow } + GradientStop { position: 0.22; color: Qt.rgba(0, 0, 0, 0) } + GradientStop { position: 0.78; color: Qt.rgba(0, 0, 0, 0) } + GradientStop { position: 1.0; color: root.theme.contentGlow } } } StackLayout { id: stack - Layout.fillWidth: true - Layout.fillHeight: true + anchors.fill: parent + anchors.margins: root.compactMode ? 20 : 28 currentIndex: sidebar.currentIndex DriverPage { @@ -193,16 +217,17 @@ ApplicationWindow { darkMode: root.darkMode compactMode: root.compactMode showAdvancedInfo: root.showAdvancedInfo + navigateToPage: function(index) { root.navigateToPage(index); } } - MonitorPage { + SettingsPage { theme: root.theme darkMode: root.darkMode compactMode: root.compactMode showAdvancedInfo: root.showAdvancedInfo } - SettingsPage { + MonitorPage { theme: root.theme darkMode: root.darkMode compactMode: root.compactMode @@ -212,4 +237,64 @@ ApplicationWindow { } } } + + Popup { + id: languagePopup + modal: false + focus: true + x: root.width - width - 76 + y: 72 + width: 240 + height: languageColumn.implicitHeight + 24 + padding: 0 + closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside + onClosed: root.languageDialogOpen = false + + background: Rectangle { + radius: 22 + color: root.theme.card + border.width: 1 + border.color: root.theme.border + } + + ColumnLayout { + id: languageColumn + anchors.fill: parent + anchors.margins: 12 + spacing: 8 + + Repeater { + model: languageManager.availableLanguages + + delegate: Rectangle { + required property var modelData + Layout.fillWidth: true + implicitHeight: 42 + radius: 14 + color: languageManager.currentLanguage === modelData.code ? root.theme.infoBg : "transparent" + border.width: languageManager.currentLanguage === modelData.code ? 1 : 0 + border.color: root.theme.border + + Label { + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + anchors.leftMargin: 14 + text: modelData.nativeLabel + color: root.theme.text + font.pixelSize: 14 + font.weight: Font.DemiBold + } + + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: { + languageManager.setCurrentLanguage(modelData.code); + root.languageDialogOpen = false; + } + } + } + } + } + } } diff --git a/src/qml/assets/ro-control-logo.png b/src/qml/assets/ro-control-logo.png index adaced5..152fd4d 100644 Binary files a/src/qml/assets/ro-control-logo.png and b/src/qml/assets/ro-control-logo.png differ diff --git a/src/qml/assets/ro-control-logo.svg b/src/qml/assets/ro-control-logo.svg index 57d86e4..b5f128e 100644 --- a/src/qml/assets/ro-control-logo.svg +++ b/src/qml/assets/ro-control-logo.svg @@ -1,133 +1,39 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/qml/components/SectionPanel.qml b/src/qml/components/SectionPanel.qml index a50cba6..59757f0 100644 --- a/src/qml/components/SectionPanel.qml +++ b/src/qml/components/SectionPanel.qml @@ -29,8 +29,8 @@ Rectangle { Label { text: panel.title - font.pixelSize: 17 - font.weight: Font.DemiBold + font.pixelSize: 16 + font.weight: Font.Medium color: panel.theme.text visible: text.length > 0 Layout.fillWidth: true diff --git a/src/qml/components/SidebarMenu.qml b/src/qml/components/SidebarMenu.qml index 765e4a5..f8fa578 100644 --- a/src/qml/components/SidebarMenu.qml +++ b/src/qml/components/SidebarMenu.qml @@ -4,104 +4,112 @@ import QtQuick.Layouts Rectangle { id: sidebar - width: 248 + width: 286 required property var theme color: theme.sidebarBg + border.width: 1 + border.color: theme.sidebarBorder clip: true property int currentIndex: 0 readonly property var menuItems: [ - qsTr("Driver Management"), - qsTr("System Monitoring"), - qsTr("Settings") + { title: qsTr("Install"), marker: "\u2193" }, + { title: qsTr("Expert"), marker: "\u2699" }, + { title: qsTr("Monitor"), marker: "\u223f" } ] - // Versiyon — alt köşe - Label { - anchors.bottom: parent.bottom - anchors.horizontalCenter: parent.horizontalCenter - anchors.bottomMargin: 20 - text: "v" + Qt.application.version - font.pixelSize: 12 - color: theme.sidebarHint - z: 1 - } - ColumnLayout { - anchors.left: parent.left - anchors.right: parent.right - anchors.top: parent.top + anchors.fill: parent + anchors.topMargin: 22 + anchors.bottomMargin: 22 spacing: 0 - // Başlık - Item { - Layout.fillWidth: true - implicitHeight: 82 - - Label { - anchors.left: parent.left - anchors.leftMargin: 22 - anchors.verticalCenter: parent.verticalCenter - text: qsTr("ro-Control") - font.pixelSize: 25 - font.weight: Font.Bold - color: theme.sidebarText - } - } - - Rectangle { - Layout.fillWidth: true - Layout.leftMargin: 20 - Layout.rightMargin: 20 - height: 1 - color: theme.sidebarBorder - } - - Item { - Layout.fillWidth: true - implicitHeight: 16 - } - Repeater { model: sidebar.menuItems delegate: Rectangle { - id: menuItem + id: menuCard required property int index - required property string modelData + required property var modelData + Layout.leftMargin: 22 + Layout.rightMargin: 22 + Layout.topMargin: index === 0 ? 0 : 10 Layout.fillWidth: true - Layout.leftMargin: 10 - Layout.rightMargin: 10 - implicitHeight: 52 - radius: 14 - color: sidebar.currentIndex === menuItem.index ? theme.sidebarActive - : mouseArea.containsMouse ? theme.sidebarHover - : "transparent" - border.width: sidebar.currentIndex === menuItem.index ? 1 : 0 - border.color: sidebar.currentIndex === menuItem.index ? theme.sidebarBorder : "transparent" + implicitHeight: 90 + radius: 22 + color: sidebar.currentIndex === index ? theme.sidebarActive + : "transparent" + border.width: sidebar.currentIndex === index ? 1 : 0 + border.color: theme.sidebarBorder - Label { - anchors.verticalCenter: parent.verticalCenter + Rectangle { + visible: sidebar.currentIndex === index anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + width: 4 + height: parent.height - 20 + radius: 2 + color: theme.sidebarAccent + } + + RowLayout { + anchors.fill: parent anchors.leftMargin: 22 - anchors.right: parent.right - anchors.rightMargin: 10 - text: menuItem.modelData - font.pixelSize: 15 - font.weight: sidebar.currentIndex === menuItem.index ? Font.DemiBold : Font.Medium - color: sidebar.currentIndex === menuItem.index ? theme.sidebarAccent : theme.sidebarMuted - elide: Text.ElideRight + anchors.rightMargin: 22 + spacing: 16 + + Rectangle { + width: 48 + height: 48 + radius: 16 + color: sidebar.currentIndex === index ? theme.sidebarAccent : theme.cardStrong + + Label { + anchors.centerIn: parent + text: menuCard.modelData.marker + color: sidebar.currentIndex === index ? "#ffffff" : theme.sidebarMuted + font.pixelSize: 18 + font.weight: Font.DemiBold + } + } + + Label { + Layout.fillWidth: true + text: menuCard.modelData.title + color: sidebar.currentIndex === index ? theme.sidebarText : theme.sidebarText + font.pixelSize: 16 + font.weight: sidebar.currentIndex === index ? Font.DemiBold : Font.Medium + } + + Rectangle { + width: 10 + height: 10 + radius: 5 + visible: sidebar.currentIndex === index + color: theme.sidebarAccent + } } MouseArea { - id: mouseArea anchors.fill: parent hoverEnabled: true cursorShape: Qt.PointingHandCursor - onClicked: sidebar.currentIndex = menuItem.index + onEntered: { + if (sidebar.currentIndex !== index) + menuCard.color = theme.sidebarHover; + } + onExited: { + if (sidebar.currentIndex !== index) + menuCard.color = "transparent"; + } + onClicked: sidebar.currentIndex = index } } } + + Item { + Layout.fillHeight: true + } } } diff --git a/src/qml/components/StatusBanner.qml b/src/qml/components/StatusBanner.qml index 40b4471..297bdbf 100644 --- a/src/qml/components/StatusBanner.qml +++ b/src/qml/components/StatusBanner.qml @@ -17,10 +17,7 @@ Rectangle { : tone === "warning" ? theme.warning : tone === "error" ? theme.danger : theme.accentA - readonly property color textTone: tone === "success" ? "#16351d" - : tone === "warning" ? "#4b3202" - : tone === "error" ? "#5b1820" - : "#12304f" + readonly property color textTone: theme.text radius: 20 color: bannerColor diff --git a/src/qml/pages/DriverPage.qml b/src/qml/pages/DriverPage.qml index 1c5d1f1..d380cd3 100644 --- a/src/qml/pages/DriverPage.qml +++ b/src/qml/pages/DriverPage.qml @@ -9,6 +9,7 @@ Item { property bool darkMode: false property bool compactMode: false property bool showAdvancedInfo: true + property var navigateToPage property string bannerText: "" property string bannerTone: "info" @@ -17,11 +18,30 @@ Item { property string operationDetail: "" property bool operationActive: false property double operationStartedAt: 0 - property double lastLogAt: 0 property int operationElapsedSeconds: 0 readonly property string nvidiaLicenseUrl: "https://www.nvidia.com/en-us/drivers/nvidia-license/" readonly property bool backendBusy: nvidiaInstaller.busy || 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 string latestVersionLabel: cleanVersionLabel(nvidiaUpdater.latestVersion) + readonly property var availableVersionOptions: buildAvailableVersionOptions(nvidiaUpdater.availableVersions) + readonly property string recommendedTitle: driverInstalledLocally + ? qsTr("Driver installed and ready") + : remoteDriverCatalogAvailable + ? qsTr("Recommended package available") + : qsTr("Preparing recommendation") + readonly property string recommendedVersion: latestVersionLabel.length > 0 + ? latestVersionLabel + : (installedVersionLabel.length > 0 ? installedVersionLabel : qsTr("Waiting for scan")) + readonly property string detectedHardwareLabel: nvidiaDetector.gpuName.length > 0 + ? nvidiaDetector.gpuName + : (nvidiaDetector.displayAdapterName.length > 0 + ? nvidiaDetector.displayAdapterName + : qsTr("Hardware information unavailable")) + readonly property bool wideLayout: width >= 1240 function classifyOperationPhase(message) { const lowered = message.toLowerCase(); @@ -89,7 +109,7 @@ Item { if (epochIndex >= 0) normalized = normalized.substring(epochIndex + 1); - const releaseMatch = normalized.match(/^([0-9]+(?:\.[0-9]+)+)/); + const releaseMatch = normalized.match(/([0-9]+(?:\.[0-9]+)+)/); if (releaseMatch && releaseMatch.length > 1) return releaseMatch[1]; @@ -161,13 +181,6 @@ Item { return options; } - function appendLog(source, message) { - const now = Date.now(); - lastLogAt = now; - logArea.append("[" + formatTimestamp(now) + "] " + source + ": " + message); - logArea.cursorPosition = logArea.length; - } - Timer { interval: 1000 repeat: true @@ -178,13 +191,6 @@ Item { } } - 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 string latestVersionLabel: cleanVersionLabel(nvidiaUpdater.latestVersion) - readonly property var availableVersionOptions: buildAvailableVersionOptions(nvidiaUpdater.availableVersions) - ScrollView { id: pageScroll anchors.fill: parent @@ -193,7 +199,7 @@ Item { ColumnLayout { width: pageScroll.availableWidth - spacing: page.compactMode ? 12 : 16 + spacing: page.compactMode ? 14 : 16 StatusBanner { Layout.fillWidth: true @@ -202,397 +208,276 @@ Item { text: page.bannerText } - GridLayout { + ColumnLayout { Layout.fillWidth: true - columns: width > 980 ? 4 : 2 - columnSpacing: 12 - rowSpacing: 12 - - StatCard { - Layout.fillWidth: true - theme: page.theme - title: qsTr("GPU Detection") - value: nvidiaDetector.gpuFound ? qsTr("Detected") : qsTr("Missing") - subtitle: nvidiaDetector.gpuFound ? nvidiaDetector.gpuName : qsTr("No NVIDIA GPU was detected on this system.") - accentColor: page.theme.accentA - emphasized: nvidiaDetector.gpuFound - } - - StatCard { - Layout.fillWidth: true - theme: page.theme - title: qsTr("Active Driver") - value: nvidiaDetector.activeDriver - subtitle: qsTr("Session: ") + (nvidiaDetector.sessionType.length > 0 ? nvidiaDetector.sessionType : qsTr("Unknown")) - accentColor: page.theme.accentB - } + spacing: 6 - StatCard { - Layout.fillWidth: true - theme: page.theme - title: qsTr("Installed Version") - value: page.installedVersionLabel.length > 0 ? page.installedVersionLabel : qsTr("None") - subtitle: page.driverInstalledLocally - ? (nvidiaUpdater.updateAvailable - ? qsTr("Latest available online: ") + page.latestVersionLabel - : qsTr("No pending online package update detected.")) - : (page.remoteDriverCatalogAvailable - ? qsTr("Latest driver found online: ") + page.latestVersionLabel - : qsTr("No online driver catalog has been loaded yet.")) - accentColor: page.theme.accentC - busy: page.operationRunning + Label { + text: qsTr("Select Installation Type") + color: page.theme.text + font.pixelSize: 28 + font.weight: Font.DemiBold } - StatCard { - Layout.fillWidth: true - theme: page.theme - title: qsTr("Secure Boot") - value: nvidiaDetector.secureBootEnabled ? qsTr("Enabled") : qsTr("Disabled / Unknown") - subtitle: nvidiaDetector.secureBootEnabled - ? qsTr("Unsigned kernel modules may require manual signing.") - : qsTr("No Secure Boot blocker is currently detected.") - accentColor: nvidiaDetector.secureBootEnabled ? page.theme.warning : page.theme.success - emphasized: nvidiaDetector.secureBootEnabled + Label { + text: qsTr("Optimized for your hardware") + color: page.theme.textSoft + font.pixelSize: 15 + font.weight: Font.Medium } } GridLayout { Layout.fillWidth: true - columns: width > 980 ? 2 : 1 + columns: page.wideLayout ? 2 : 1 columnSpacing: 16 rowSpacing: 16 - SectionPanel { + Rectangle { Layout.fillWidth: true - theme: page.theme - title: qsTr("Verification") - subtitle: qsTr("Review driver prerequisites before changing packages.") - - Flow { - Layout.fillWidth: true - spacing: 8 - - InfoBadge { - text: nvidiaDetector.gpuFound ? qsTr("GPU Ready") : qsTr("GPU Missing") - backgroundColor: nvidiaDetector.gpuFound ? page.theme.successBg : page.theme.dangerBg - foregroundColor: page.theme.text - } - - InfoBadge { - text: nvidiaDetector.waylandSession ? qsTr("Wayland Session") : qsTr("X11 / Other Session") - backgroundColor: page.theme.infoBg - foregroundColor: page.theme.text - } + radius: 26 + color: page.theme.card + border.width: 1 + border.color: page.theme.border + implicitHeight: expressColumn.implicitHeight + 34 + + ColumnLayout { + id: expressColumn + x: 24 + y: 20 + width: parent.width - 48 + spacing: 14 + + RowLayout { + Layout.fillWidth: true + spacing: 14 + + Rectangle { + width: 42 + height: 42 + radius: 14 + color: page.theme.success + + Label { + anchors.centerIn: parent + text: "OK" + color: "#ffffff" + font.pixelSize: 13 + font.weight: Font.DemiBold + } + } - InfoBadge { - text: nvidiaDetector.nouveauActive ? qsTr("Fallback Open Driver Active") : qsTr("Fallback Open Driver Inactive") - backgroundColor: nvidiaDetector.nouveauActive ? page.theme.warningBg : page.theme.cardStrong - foregroundColor: page.theme.text + ColumnLayout { + Layout.fillWidth: true + spacing: 4 + + Label { + text: qsTr("Express Install") + color: page.theme.text + font.pixelSize: 20 + font.weight: Font.DemiBold + } + + Label { + Layout.fillWidth: true + wrapMode: Text.Wrap + color: page.theme.textSoft + font.pixelSize: 14 + text: qsTr("Automatically installs the recommended driver version with optimal settings") + } + } } - InfoBadge { - text: nvidiaDetector.driverLoaded ? qsTr("Kernel Module Loaded") : qsTr("Kernel Module Missing") - backgroundColor: nvidiaDetector.driverLoaded ? page.theme.successBg : page.theme.warningBg - foregroundColor: page.theme.text + Rectangle { + Layout.fillWidth: true + radius: 18 + color: page.theme.successBg + border.width: 1 + border.color: Qt.tint(page.theme.success, "#55ffffff") + implicitHeight: 40 + + RowLayout { + anchors.fill: parent + anchors.leftMargin: 14 + anchors.rightMargin: 14 + spacing: 8 + + Rectangle { + width: 18 + height: 18 + radius: 9 + color: page.theme.success + } + + Label { + Layout.fillWidth: true + text: "nvidia-" + page.recommendedVersion + " • " + + (nvidiaDetector.gpuFound ? qsTr("Verified Compatible") : page.detectedHardwareLabel) + color: page.theme.success + font.pixelSize: 13 + font.weight: Font.DemiBold + elide: Text.ElideRight + } + } } } - Label { - Layout.fillWidth: true - wrapMode: Text.Wrap - color: page.theme.textSoft - text: nvidiaDetector.waylandSession - ? qsTr("Wayland sessions automatically need the nvidia-drm.modeset=1 kernel argument.") - : qsTr("X11 sessions require matching userspace packages after install or update.") - } - - Rectangle { - Layout.fillWidth: true - color: page.theme.cardStrong - border.width: 1 - border.color: page.theme.border - radius: 16 - implicitHeight: verificationText.implicitHeight + 24 - visible: page.showAdvancedInfo - - Label { - id: verificationText - x: 12 - y: 12 - width: parent.width - 24 - text: nvidiaDetector.verificationReport - wrapMode: Text.Wrap - color: page.theme.text + MouseArea { + anchors.fill: parent + cursorShape: page.backendBusy ? Qt.ArrowCursor : Qt.PointingHandCursor + enabled: !page.backendBusy + onClicked: { + page.setOperationState(qsTr("Installer"), qsTr("Installing the proprietary NVIDIA driver (akmod-nvidia)..."), "info", true); + nvidiaInstaller.installProprietary(true); } } } - SectionPanel { + Rectangle { Layout.fillWidth: true - theme: page.theme - title: qsTr("Driver Actions") - subtitle: qsTr("Use guided actions to install, switch or remove the current stack.") - - Flow { - Layout.fillWidth: true - spacing: 8 - visible: page.operationDetail.length > 0 - - InfoBadge { - text: qsTr("Source: ") + (page.operationSource.length > 0 ? page.operationSource : qsTr("Idle")) - backgroundColor: page.theme.cardStrong - foregroundColor: page.theme.text - } - - InfoBadge { - text: qsTr("Phase: ") + (page.operationPhase.length > 0 ? page.operationPhase : qsTr("Idle")) - backgroundColor: page.operationRunning ? page.theme.infoBg : page.theme.cardStrong - foregroundColor: page.theme.text - } - - InfoBadge { - text: page.operationRunning ? qsTr("Running") : qsTr("Idle") - backgroundColor: page.operationRunning ? page.theme.warningBg : page.theme.successBg - foregroundColor: page.theme.text - } - - InfoBadge { - text: qsTr("Elapsed: ") + page.formatDuration(page.operationElapsedSeconds) - backgroundColor: page.theme.cardStrong - foregroundColor: page.theme.text - visible: page.operationRunning || page.operationElapsedSeconds > 0 - } - - InfoBadge { - text: qsTr("Last Log: ") + page.formatTimestamp(page.lastLogAt) - backgroundColor: page.theme.cardStrong - foregroundColor: page.theme.text - visible: page.lastLogAt > 0 - } - } - - StatusBanner { - Layout.fillWidth: true - theme: page.theme - tone: "warning" - text: nvidiaInstaller.proprietaryAgreementRequired ? nvidiaInstaller.proprietaryAgreementText : "" - } - - CheckBox { - id: eulaAccept - visible: nvidiaInstaller.proprietaryAgreementRequired - text: qsTr("I reviewed the NVIDIA license terms") - } - - Label { - Layout.fillWidth: true - visible: nvidiaInstaller.proprietaryAgreementRequired - textFormat: Text.RichText - wrapMode: Text.Wrap - color: page.theme.textSoft - text: qsTr("Official NVIDIA license: %1").arg(page.nvidiaLicenseUrl) - onLinkActivated: function(link) { Qt.openUrlExternally(link) } - } - - GridLayout { - Layout.fillWidth: true - columns: width > 460 ? 2 : 1 - columnSpacing: 10 - rowSpacing: 10 - - ActionButton { + radius: 26 + color: page.theme.card + border.width: 1 + border.color: page.theme.border + implicitHeight: customColumn.implicitHeight + 34 + + ColumnLayout { + id: customColumn + x: 24 + y: 20 + width: parent.width - 48 + spacing: 14 + + RowLayout { Layout.fillWidth: true - theme: page.theme - tone: "primary" - text: qsTr("Install NVIDIA Driver") - enabled: !nvidiaInstaller.busy && (!nvidiaInstaller.proprietaryAgreementRequired || eulaAccept.checked) - onClicked: { - page.setOperationState(qsTr("Installer"), qsTr("Installing the proprietary NVIDIA driver (akmod-nvidia)..."), "info", true); - nvidiaInstaller.installProprietary(eulaAccept.checked); + spacing: 14 + + Rectangle { + width: 42 + height: 42 + radius: 14 + color: page.theme.accentA + + Label { + anchors.centerIn: parent + text: "EX" + color: "#ffffff" + font.pixelSize: 13 + font.weight: Font.DemiBold + } } - } - ActionButton { - Layout.fillWidth: true - theme: page.theme - text: qsTr("Install Open Kernel Modules") - enabled: !nvidiaInstaller.busy - onClicked: { - page.setOperationState(qsTr("Installer"), qsTr("Switching to NVIDIA open kernel modules..."), "info", true); - nvidiaInstaller.installOpenSource(); + ColumnLayout { + Layout.fillWidth: true + spacing: 4 + + Label { + text: qsTr("Custom Install") + color: page.theme.text + font.pixelSize: 20 + font.weight: Font.DemiBold + } + + Label { + Layout.fillWidth: true + wrapMode: Text.Wrap + color: page.theme.textSoft + font.pixelSize: 14 + text: qsTr("Open the expert page to choose a specific driver version and kernel module type") + } } } - ActionButton { + Flow { Layout.fillWidth: true - theme: page.theme - tone: "danger" - text: qsTr("Remove Driver") - enabled: !nvidiaInstaller.busy - onClicked: { - page.setOperationState(qsTr("Installer"), qsTr("Removing the NVIDIA driver..."), "info", true); - nvidiaInstaller.remove(); - } - } + spacing: 8 - ActionButton { - Layout.fillWidth: true - theme: page.theme - text: qsTr("Deep Clean") - enabled: !nvidiaInstaller.busy - onClicked: { - page.setOperationState(qsTr("Installer"), qsTr("Cleaning legacy driver leftovers..."), "info", true); - nvidiaInstaller.deepClean(); + InfoBadge { + text: qsTr("Expert Mode") + backgroundColor: page.theme.cardStrong + foregroundColor: page.theme.textMuted } - } - } - RowLayout { - Layout.fillWidth: true - - ActionButton { - theme: page.theme - text: qsTr("Rescan System") - enabled: !nvidiaInstaller.busy && !nvidiaUpdater.busy - onClicked: { - nvidiaDetector.refresh(); - nvidiaInstaller.refreshProprietaryAgreement(); - nvidiaUpdater.refreshAvailableVersions(); + InfoBadge { + text: qsTr("Installed: ") + (page.installedVersionLabel.length > 0 ? page.installedVersionLabel : qsTr("None")) + backgroundColor: page.theme.cardStrong + foregroundColor: page.theme.textMuted } - } - Item { - Layout.fillWidth: true + InfoBadge { + text: qsTr("Choose version, module type and cleanup options") + backgroundColor: page.theme.infoBg + foregroundColor: page.theme.text + } } + } - BusyIndicator { - running: nvidiaInstaller.busy || nvidiaUpdater.busy - visible: running + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: { + if (page.navigateToPage) + page.navigateToPage(1); } } } - SectionPanel { + Rectangle { Layout.fillWidth: true - theme: page.theme - title: qsTr("Update Center") - subtitle: qsTr("Search the online package catalog, then download and install a matching driver build.") - - RowLayout { - Layout.fillWidth: true - spacing: 8 - - InfoBadge { - text: qsTr("Installed: ") + (page.installedVersionLabel.length > 0 ? page.installedVersionLabel : qsTr("None")) - backgroundColor: page.theme.cardStrong - foregroundColor: page.theme.text - } - - InfoBadge { - text: page.driverInstalledLocally - ? (nvidiaUpdater.updateAvailable ? qsTr("Update Available") : qsTr("Up to Date")) - : (page.remoteDriverCatalogAvailable ? qsTr("Remote Driver Available") : qsTr("Catalog Not Ready")) - backgroundColor: page.driverInstalledLocally - ? (nvidiaUpdater.updateAvailable ? page.theme.warningBg : page.theme.successBg) - : (page.remoteDriverCatalogAvailable ? page.theme.successBg : page.theme.warningBg) - foregroundColor: page.theme.text - } - } - - RowLayout { - Layout.fillWidth: true + Layout.columnSpan: page.wideLayout ? 2 : 1 + radius: 26 + color: Qt.tint(page.theme.warningBg, page.darkMode ? "#11ffffff" : "#22ffffff") + border.width: 1 + border.color: Qt.tint(page.theme.warning, "#55ffffff") + implicitHeight: warningColumn.implicitHeight + 30 + + ColumnLayout { + id: warningColumn + x: 24 + y: 18 + width: parent.width - 48 spacing: 10 - ActionButton { - theme: page.theme - text: qsTr("Check for Updates") - enabled: !nvidiaUpdater.busy && !nvidiaInstaller.busy - onClicked: { - page.setOperationState(qsTr("Updater"), qsTr("Searching the online NVIDIA package catalog..."), "info", true); - nvidiaUpdater.checkForUpdate(); - } - } - - ActionButton { - theme: page.theme - tone: "primary" - text: page.driverInstalledLocally ? qsTr("Apply Latest") : qsTr("Install Latest") - enabled: !nvidiaUpdater.busy && !nvidiaInstaller.busy && (nvidiaUpdater.updateAvailable || page.canInstallLatestRemoteDriver) - onClicked: { - page.setOperationState(qsTr("Updater"), page.driverInstalledLocally - ? qsTr("Updating NVIDIA driver to the latest online version...") - : qsTr("Downloading and installing the latest online NVIDIA driver..."), - "info", true); - nvidiaUpdater.applyUpdate(); - } - } - } - - RowLayout { - Layout.fillWidth: true - spacing: 10 - visible: page.availableVersionOptions.length > 0 - - ComboBox { - id: versionPicker + RowLayout { Layout.fillWidth: true - model: page.availableVersionOptions - textRole: "versionTitle" - } - - ActionButton { - theme: page.theme - text: qsTr("Apply Selected") - enabled: !nvidiaUpdater.busy && !nvidiaInstaller.busy && versionPicker.currentIndex >= 0 && page.remoteDriverCatalogAvailable - onClicked: { - page.setOperationState(qsTr("Updater"), page.driverInstalledLocally - ? qsTr("Switching NVIDIA driver to selected online version: ") + versionPicker.currentText - : qsTr("Downloading and installing selected NVIDIA driver version: ") + versionPicker.currentText, - "info", true); - nvidiaUpdater.applyVersion(page.availableVersionOptions[versionPicker.currentIndex].rawVersion); + spacing: 12 + + Rectangle { + width: 40 + height: 40 + radius: 14 + color: page.theme.warningBg + + Label { + anchors.centerIn: parent + text: "!" + color: page.theme.warning + font.pixelSize: 20 + font.weight: Font.DemiBold + } } - } - } - Label { - Layout.fillWidth: true - wrapMode: Text.Wrap - color: page.theme.textSoft - text: nvidiaUpdater.availableVersions.length > 0 - ? qsTr("Online repository versions loaded: ") + nvidiaUpdater.availableVersions.length - : qsTr("No online repository version list has been loaded yet.") - } - } - - SectionPanel { - Layout.fillWidth: true - theme: page.theme - title: qsTr("Activity Log") - subtitle: qsTr("Operation output is streamed here in real time.") - - TextArea { - id: logArea - Layout.fillWidth: true - Layout.preferredHeight: 240 - readOnly: true - wrapMode: Text.Wrap - color: page.theme.text - selectedTextColor: "#ffffff" - selectionColor: page.theme.accentA - background: Rectangle { - radius: 16 - color: page.theme.cardStrong - border.width: 1 - border.color: page.theme.border - } - } - - ActionButton { - theme: page.theme - text: qsTr("Clear Log") - onClicked: { - logArea.text = "" - page.lastLogAt = 0 + ColumnLayout { + Layout.fillWidth: true + spacing: 4 + + Label { + text: nvidiaDetector.secureBootEnabled ? qsTr("Secure Boot Detected") : qsTr("System Readiness") + color: page.theme.warning + font.pixelSize: 16 + font.weight: Font.DemiBold + } + + Label { + Layout.fillWidth: true + wrapMode: Text.Wrap + color: page.theme.textMuted + font.pixelSize: 13 + text: nvidiaDetector.secureBootEnabled + ? qsTr("You may need to sign the kernel modules or disable Secure Boot in BIOS to use NVIDIA proprietary drivers.") + : qsTr("No Secure Boot blocker is currently detected. You can continue with the recommended installation path.") + } + } } } } @@ -603,22 +488,18 @@ Item { Connections { target: nvidiaInstaller - function onProgressMessage(message) { - page.appendLog(qsTr("Installer"), message); + function onProgressMessage(message) { page.setOperationState(qsTr("Installer"), message, "info", true); } function onInstallFinished(success, message) { - page.appendLog(qsTr("Installer"), message); page.finishOperation(qsTr("Installer"), success, message); nvidiaDetector.refresh(); nvidiaUpdater.checkForUpdate(); nvidiaInstaller.refreshProprietaryAgreement(); - eulaAccept.checked = false; } function onRemoveFinished(success, message) { - page.appendLog(qsTr("Installer"), message); page.finishOperation(qsTr("Installer"), success, message); nvidiaDetector.refresh(); nvidiaUpdater.checkForUpdate(); @@ -630,12 +511,10 @@ Item { target: nvidiaUpdater function onProgressMessage(message) { - page.appendLog(qsTr("Updater"), message); page.setOperationState(qsTr("Updater"), message, "info", true); } function onUpdateFinished(success, message) { - page.appendLog(qsTr("Updater"), message); page.finishOperation(qsTr("Updater"), success, message); nvidiaDetector.refresh(); nvidiaUpdater.checkForUpdate(); diff --git a/src/qml/pages/MonitorPage.qml b/src/qml/pages/MonitorPage.qml index 9971c87..b7d47d6 100644 --- a/src/qml/pages/MonitorPage.qml +++ b/src/qml/pages/MonitorPage.qml @@ -15,42 +15,220 @@ Item { readonly property bool gpuMemoryAvailable: gpuMonitor.memoryTotalMiB > 0 readonly property bool ramTelemetryAvailable: ramMonitor.available || ramMonitor.totalMiB > 0 readonly property bool gpuDetected: nvidiaDetector.gpuFound + readonly property bool gpuHardwarePresent: nvidiaDetector.gpuFound || nvidiaDetector.displayAdapterName.length > 0 readonly property bool gpuDriverActive: nvidiaDetector.driverLoaded || nvidiaDetector.nouveauActive + readonly property color monitorBarColor: "#34c99a" + readonly property bool wideLayout: width >= 1180 - function formatTemperature(value) { - return value > 0 ? value + " C" : qsTr("Unavailable"); + function formatTemperature(value, hasHardware) { + if (value > 0) + return value + "\u00b0C"; + return hasHardware ? qsTr("Unsupported") : qsTr("Unavailable"); } function formatMemoryUsage(usedMiB, totalMiB) { - return totalMiB > 0 ? usedMiB + " / " + totalMiB + " MiB" : qsTr("Unavailable"); + if (totalMiB <= 0) + return qsTr("Unavailable"); + + const usedGiB = Math.round((usedMiB / 1024.0) * 10) / 10; + const totalGiB = Math.round((totalMiB / 1024.0) * 10) / 10; + return usedGiB + " / " + totalGiB + " GB"; + } + + function formatMemoryTotal(totalMiB) { + if (totalMiB <= 0) + return qsTr("Unavailable"); + const totalGiB = Math.round(totalMiB / 1024.0); + return totalGiB + " GB"; + } + + function driverLabel() { + if (nvidiaDetector.driverVersion.length > 0) + return "nvidia-" + nvidiaDetector.driverVersion; + if (nvidiaUpdater.currentVersion.length > 0) + return "nvidia-" + nvidiaUpdater.currentVersion; + return qsTr("Not installed"); + } + + function osDisplayLabel() { + if (systemInfo.osName.length === 0) + return qsTr("Unavailable"); + if (systemInfo.desktopEnvironment.length === 0) + return systemInfo.osName; + if (systemInfo.osName.toLowerCase().indexOf(systemInfo.desktopEnvironment.toLowerCase()) >= 0) + return systemInfo.osName; + return systemInfo.osName + " (" + systemInfo.desktopEnvironment + ")"; } function gpuLoadValueText() { if (page.gpuTelemetryAvailable) return gpuMonitor.utilizationPercent + "%"; - if (page.gpuDetected) - return qsTr("No Live Data"); + if (page.gpuHardwarePresent) + return qsTr("Not exposed"); return qsTr("Unavailable"); } - function gpuSubtitleText() { - if (page.gpuTelemetryAvailable) - return gpuMonitor.gpuName.length > 0 ? gpuMonitor.gpuName : qsTr("NVIDIA GPU"); - if (!page.gpuDetected) - return qsTr("No NVIDIA GPU was detected on this system."); - if (!page.gpuDriverActive) - return qsTr("GPU detected, but no active driver is loaded."); - return qsTr("Live GPU telemetry is unavailable. Check nvidia-smi and driver access."); + function gpuMetricFallbackText() { + return page.gpuHardwarePresent ? qsTr("Not exposed") : qsTr("Unavailable"); } function gpuSummaryText() { if (page.gpuTelemetryAvailable) - return qsTr("GPU temperature: ") + page.formatTemperature(gpuMonitor.temperatureC) + qsTr(", VRAM ") + page.formatMemoryUsage(gpuMonitor.memoryUsedMiB, gpuMonitor.memoryTotalMiB) + "."; + return qsTr("GPU telemetry active"); if (!page.gpuDetected) - return qsTr("No NVIDIA GPU is currently detected on this system."); + return qsTr("No NVIDIA GPU detected"); if (!page.gpuDriverActive) - return qsTr("GPU telemetry is unavailable because the NVIDIA driver is not active."); - return qsTr("GPU metrics are unavailable. Check driver installation and nvidia-smi accessibility."); + return qsTr("Driver inactive"); + return qsTr("Telemetry unavailable"); + } + + function progressValue(percentValue) { + return Math.max(0, Math.min(100, percentValue)) / 100.0; + } + + component InfoTile: Rectangle { + id: infoTile + required property string title + required property string value + required property string markerText + required property color markerColor + + radius: 24 + color: page.theme.card + border.width: 1 + border.color: page.theme.border + implicitHeight: 102 + + RowLayout { + anchors.fill: parent + anchors.leftMargin: 22 + anchors.rightMargin: 22 + spacing: 16 + + Rectangle { + width: 54 + height: 54 + radius: 18 + color: infoTile.markerColor + + Label { + anchors.centerIn: parent + text: infoTile.markerText + color: "#ffffff" + font.pixelSize: 19 + font.weight: Font.DemiBold + } + } + + ColumnLayout { + Layout.fillWidth: true + spacing: 4 + + Label { + text: infoTile.title + color: page.theme.textSoft + font.pixelSize: 13 + font.weight: Font.Medium + } + + Label { + Layout.fillWidth: true + text: infoTile.value + color: page.theme.text + font.pixelSize: 17 + font.weight: Font.DemiBold + wrapMode: Text.Wrap + maximumLineCount: 2 + elide: Text.ElideRight + } + } + } + } + + component MetricRow: Item { + id: metricRow + required property string title + required property string subtitle + required property string valueText + required property string markerText + required property color markerColor + required property real progress + + implicitHeight: 84 + + ColumnLayout { + anchors.fill: parent + spacing: 10 + + RowLayout { + Layout.fillWidth: true + spacing: 14 + + Rectangle { + width: 48 + height: 48 + radius: 16 + color: metricRow.markerColor + + Label { + anchors.centerIn: parent + text: metricRow.markerText + color: "#ffffff" + font.pixelSize: 18 + font.weight: Font.DemiBold + } + } + + ColumnLayout { + Layout.fillWidth: true + spacing: 2 + + Label { + text: metricRow.title + color: page.theme.text + font.pixelSize: 15 + font.weight: Font.DemiBold + } + + Label { + text: metricRow.subtitle + color: page.theme.textSoft + font.pixelSize: 13 + font.weight: Font.Medium + } + } + + Rectangle { + radius: 18 + color: page.theme.cardStrong + implicitWidth: metricValue.implicitWidth + 28 + implicitHeight: 48 + + Label { + id: metricValue + anchors.centerIn: parent + text: metricRow.valueText + color: page.theme.text + font.pixelSize: 16 + font.weight: Font.DemiBold + } + } + } + + Rectangle { + Layout.fillWidth: true + implicitHeight: 10 + radius: 5 + color: page.theme.cardStrong + + Rectangle { + width: Math.max(16, parent.width * metricRow.progress) + height: parent.height + radius: 5 + color: page.monitorBarColor + } + } + } } ScrollView { @@ -61,221 +239,219 @@ Item { ColumnLayout { width: pageScroll.availableWidth - spacing: page.compactMode ? 12 : 16 + spacing: 16 + + Label { + text: qsTr("System Information") + color: page.theme.text + font.pixelSize: 28 + font.weight: Font.DemiBold + } GridLayout { Layout.fillWidth: true - columns: width > 980 ? 3 : 1 - columnSpacing: 14 - rowSpacing: 14 + columns: 2 + columnSpacing: 16 + rowSpacing: 16 - StatCard { + InfoTile { Layout.fillWidth: true - theme: page.theme - title: qsTr("CPU Load") - value: cpuMonitor.available ? cpuMonitor.usagePercent.toFixed(1) + "%" : qsTr("Unavailable") - subtitle: cpuMonitor.available - ? qsTr("Temperature: ") + page.formatTemperature(cpuMonitor.temperatureC) - : qsTr("CPU telemetry is currently unavailable.") - accentColor: page.theme.accentA - emphasized: cpuMonitor.available && cpuMonitor.usagePercent >= 85 + title: qsTr("OS") + value: page.osDisplayLabel() + markerText: "OS" + markerColor: "#1da1f2" } - StatCard { + InfoTile { Layout.fillWidth: true - theme: page.theme - title: qsTr("GPU Load") - value: page.gpuLoadValueText() - subtitle: page.gpuSubtitleText() - accentColor: page.theme.accentB - emphasized: page.gpuTemperatureAvailable && gpuMonitor.temperatureC >= 80 + title: qsTr("Kernel") + value: systemInfo.kernelVersion.length > 0 ? systemInfo.kernelVersion : qsTr("Kernel info unavailable") + markerText: "K" + markerColor: "#df4be0" } - StatCard { + InfoTile { Layout.fillWidth: true - theme: page.theme - title: qsTr("Memory Usage") - value: page.ramTelemetryAvailable ? ramMonitor.usagePercent + "%" : qsTr("Unavailable") - subtitle: page.ramTelemetryAvailable - ? qsTr("Used: ") + page.formatMemoryUsage(ramMonitor.usedMiB, ramMonitor.totalMiB) - : qsTr("RAM telemetry is currently unavailable.") - accentColor: page.theme.accentC - emphasized: page.ramTelemetryAvailable && ramMonitor.usagePercent >= 85 + title: qsTr("CPU") + value: systemInfo.cpuModel.length > 0 ? systemInfo.cpuModel : qsTr("CPU model unavailable") + markerText: "CPU" + markerColor: "#ff6a13" + } + + InfoTile { + Layout.fillWidth: true + title: qsTr("RAM") + value: ramMonitor.totalMiB > 0 ? page.formatMemoryTotal(ramMonitor.totalMiB) : qsTr("Memory info unavailable") + markerText: "RAM" + markerColor: "#16c65f" + } + + InfoTile { + Layout.fillWidth: true + title: qsTr("GPU") + value: nvidiaDetector.gpuName.length > 0 + ? nvidiaDetector.gpuName + : (nvidiaDetector.displayAdapterName.length > 0 ? nvidiaDetector.displayAdapterName : qsTr("Unavailable")) + markerText: "GPU" + markerColor: "#6a6fff" + } + + InfoTile { + Layout.fillWidth: true + title: qsTr("Driver") + value: page.driverLabel() + markerText: "DRV" + markerColor: "#ff9800" } } GridLayout { Layout.fillWidth: true - columns: width > 980 ? 2 : 1 + columns: page.wideLayout ? 2 : 1 columnSpacing: 16 rowSpacing: 16 - SectionPanel { + ColumnLayout { Layout.fillWidth: true - theme: page.theme - title: qsTr("Live Resource Curves") - subtitle: qsTr("Quick pulse view for the most important machine resources.") + spacing: 12 - ColumnLayout { + RowLayout { Layout.fillWidth: true - spacing: 10 Label { - text: qsTr("CPU") - color: page.theme.textMuted - font.bold: true + text: qsTr("GPU Status") + color: page.theme.text + font.pixelSize: 20 + font.weight: Font.DemiBold } - ProgressBar { + Item { Layout.fillWidth: true - from: 0 - to: 100 - value: cpuMonitor.usagePercent } - Label { - text: qsTr("GPU") - color: page.theme.textMuted - font.bold: true + Rectangle { + radius: 18 + color: page.theme.successBg + border.width: 1 + border.color: Qt.tint(page.theme.success, "#55ffffff") + implicitWidth: 108 + implicitHeight: 40 + + RowLayout { + anchors.centerIn: parent + spacing: 8 + + Rectangle { + width: 10 + height: 10 + radius: 5 + color: page.theme.success + } + + Label { + text: page.gpuTelemetryAvailable ? qsTr("Active") : qsTr("Standby") + color: page.theme.success + font.pixelSize: 13 + font.weight: Font.DemiBold + } + } } + } - ProgressBar { + SectionPanel { + Layout.fillWidth: true + theme: page.theme + title: "" + subtitle: "" + + MetricRow { Layout.fillWidth: true - from: 0 - to: 100 - value: gpuMonitor.utilizationPercent + title: qsTr("Temperature") + subtitle: qsTr("Real-time monitoring") + valueText: page.formatTemperature(gpuMonitor.temperatureC, page.gpuHardwarePresent) + markerText: "T" + markerColor: "#1da1f2" + progress: page.progressValue(page.gpuTemperatureAvailable ? gpuMonitor.temperatureC : 0) } - Label { - text: qsTr("RAM") - color: page.theme.textMuted - font.bold: true + MetricRow { + Layout.fillWidth: true + title: qsTr("GPU Load") + subtitle: qsTr("Real-time monitoring") + valueText: page.gpuLoadValueText() + markerText: "G" + markerColor: "#00c46a" + progress: page.progressValue(page.gpuTelemetryAvailable ? gpuMonitor.utilizationPercent : 0) } - ProgressBar { + MetricRow { Layout.fillWidth: true - from: 0 - to: 100 - value: ramMonitor.usagePercent + title: qsTr("VRAM Usage") + subtitle: qsTr("Real-time monitoring") + valueText: page.gpuMemoryAvailable ? page.formatMemoryUsage(gpuMonitor.memoryUsedMiB, gpuMonitor.memoryTotalMiB) + : page.gpuMetricFallbackText() + markerText: "V" + markerColor: "#d84ef0" + progress: page.progressValue(page.gpuMemoryAvailable ? gpuMonitor.memoryUsagePercent : 0) } } } - SectionPanel { + ColumnLayout { Layout.fillWidth: true - theme: page.theme - title: qsTr("Health Summary") - subtitle: qsTr("Fast interpretation of the raw telemetry values.") - - Flow { - Layout.fillWidth: true - spacing: 8 - - InfoBadge { - text: cpuMonitor.available && cpuMonitor.usagePercent >= 85 ? qsTr("CPU Busy") : qsTr("CPU Stable") - backgroundColor: cpuMonitor.available && cpuMonitor.usagePercent >= 85 ? page.theme.warningBg : page.theme.successBg - foregroundColor: page.theme.text - } - - InfoBadge { - text: page.gpuTelemetryAvailable ? qsTr("GPU Online") : qsTr("GPU Telemetry Missing") - backgroundColor: page.gpuTelemetryAvailable ? page.theme.successBg : page.theme.warningBg - foregroundColor: page.theme.text - } - - InfoBadge { - text: page.ramTelemetryAvailable && ramMonitor.usagePercent >= 85 ? qsTr("Memory Pressure") : qsTr("Memory Stable") - backgroundColor: page.ramTelemetryAvailable && ramMonitor.usagePercent >= 85 ? page.theme.warningBg : page.theme.successBg - foregroundColor: page.theme.text - } - } + spacing: 12 Label { - Layout.fillWidth: true - wrapMode: Text.Wrap - color: page.theme.textSoft - text: page.gpuSummaryText() + text: qsTr("System Resources") + color: page.theme.text + font.pixelSize: 20 + font.weight: Font.DemiBold } - Label { + SectionPanel { Layout.fillWidth: true - wrapMode: Text.Wrap - color: page.theme.textSoft - visible: page.showAdvancedInfo - text: qsTr("Refresh interval: ") + cpuMonitor.updateInterval + " ms" - } - } - - SectionPanel { - Layout.fillWidth: true - theme: page.theme - title: qsTr("Detailed Signals") - subtitle: qsTr("Expanded raw values for support and diagnostics.") - visible: page.showAdvancedInfo + theme: page.theme + title: "" + subtitle: "" - ColumnLayout { - Layout.fillWidth: true - spacing: 8 - - DetailRow { + MetricRow { Layout.fillWidth: true - theme: page.theme - label: qsTr("CPU Temperature") - value: page.formatTemperature(cpuMonitor.temperatureC) + title: qsTr("CPU Usage") + subtitle: qsTr("Real-time monitoring") + valueText: cpuMonitor.available ? Math.round(cpuMonitor.usagePercent) + "%" : qsTr("Unavailable") + markerText: "C" + markerColor: "#ff6a13" + progress: page.progressValue(cpuMonitor.available ? cpuMonitor.usagePercent : 0) } - DetailRow { + MetricRow { Layout.fillWidth: true - theme: page.theme - label: qsTr("GPU Temperature") - value: page.gpuTelemetryAvailable ? page.formatTemperature(gpuMonitor.temperatureC) : qsTr("Unknown") - } - - DetailRow { - Layout.fillWidth: true - theme: page.theme - label: qsTr("VRAM") - value: page.gpuTelemetryAvailable ? page.formatMemoryUsage(gpuMonitor.memoryUsedMiB, gpuMonitor.memoryTotalMiB) : qsTr("Unknown") - } - - DetailRow { - Layout.fillWidth: true - theme: page.theme - label: qsTr("RAM Footprint") - value: page.ramTelemetryAvailable ? page.formatMemoryUsage(ramMonitor.usedMiB, ramMonitor.totalMiB) : qsTr("Unknown") + title: qsTr("RAM Usage") + subtitle: qsTr("Real-time monitoring") + valueText: page.ramTelemetryAvailable ? page.formatMemoryUsage(ramMonitor.usedMiB, ramMonitor.totalMiB) + : qsTr("Memory info unavailable") + markerText: "R" + markerColor: "#9247f6" + progress: page.progressValue(page.ramTelemetryAvailable ? ramMonitor.usagePercent : 0) } } - } - - SectionPanel { - Layout.fillWidth: true - theme: page.theme - title: qsTr("Actions") - subtitle: qsTr("Trigger a manual refresh when you need a fresh sample.") RowLayout { - Layout.fillWidth: true + Layout.alignment: Qt.AlignHCenter spacing: 10 - ActionButton { - theme: page.theme - tone: "primary" - text: qsTr("Refresh Telemetry") - onClicked: { - cpuMonitor.refresh(); - gpuMonitor.refresh(); - ramMonitor.refresh(); - } + Rectangle { + width: 12 + height: 12 + radius: 6 + color: page.theme.textSoft } - Item { - Layout.fillWidth: true - } - - InfoBadge { - text: page.gpuTelemetryAvailable ? qsTr("NVIDIA Path OK") : qsTr("Check NVIDIA Path") - backgroundColor: page.gpuTelemetryAvailable ? page.theme.successBg : page.theme.warningBg - foregroundColor: page.theme.text + Label { + text: qsTr("Updating every %1 seconds").arg(Math.max(1, Math.round(cpuMonitor.updateInterval / 1000))) + color: page.theme.textSoft + font.pixelSize: 13 + font.weight: Font.Medium } } } @@ -284,6 +460,7 @@ Item { } Component.onCompleted: { + systemInfo.refresh(); cpuMonitor.start(); gpuMonitor.start(); ramMonitor.start(); diff --git a/src/qml/pages/SettingsPage.qml b/src/qml/pages/SettingsPage.qml index ce08931..b70d699 100644 --- a/src/qml/pages/SettingsPage.qml +++ b/src/qml/pages/SettingsPage.qml @@ -3,13 +3,300 @@ import QtQuick.Controls import QtQuick.Layouts Item { - id: settingsPage + id: page required property var theme property bool darkMode: false property bool compactMode: false property bool showAdvancedInfo: true - readonly property string themeMode: uiPreferences.themeMode + + property string operationSource: "" + property string operationDetail: "" + property bool useOpenModules: false + property bool deepCleanInstall: false + property int selectedVersionIndex: -1 + property bool pendingInstallAfterClean: false + readonly property bool wideLayout: width >= 1220 + + readonly property bool backendBusy: nvidiaInstaller.busy || nvidiaUpdater.busy + readonly property string currentDriverLabel: nvidiaDetector.driverVersion.length > 0 + ? "nvidia-" + nvidiaDetector.driverVersion + " (" + page.driverFlavorLabel() + ")" + : qsTr("Not installed") + + function driverFlavorLabel() { + if (nvidiaDetector.driverLoaded) + return nvidiaDetector.nouveauActive ? qsTr("fallback") : (nvidiaDetector.activeDriver.indexOf("Open") >= 0 ? qsTr("open") : qsTr("proprietary")); + if (nvidiaDetector.nouveauActive) + return qsTr("fallback"); + return qsTr("inactive"); + } + + function cleanVersionLabel(rawVersion) { + let normalized = (rawVersion || "").trim(); + if (normalized.length === 0) + return ""; + + const epochIndex = normalized.indexOf(":"); + if (epochIndex >= 0) + normalized = normalized.substring(epochIndex + 1); + + const releaseMatch = normalized.match(/([0-9]+(?:\.[0-9]+)+)/); + if (releaseMatch && releaseMatch.length > 1) + return releaseMatch[1]; + + const hyphenIndex = normalized.indexOf("-"); + if (hyphenIndex > 0) + return normalized.substring(0, hyphenIndex); + + return normalized; + } + + function compareVersionLabels(leftVersion, rightVersion) { + const leftParts = cleanVersionLabel(leftVersion).split("."); + const rightParts = cleanVersionLabel(rightVersion).split("."); + const maxLength = Math.max(leftParts.length, rightParts.length); + + for (let i = 0; i < maxLength; ++i) { + const leftValue = i < leftParts.length ? parseInt(leftParts[i], 10) : 0; + const rightValue = i < rightParts.length ? parseInt(rightParts[i], 10) : 0; + + if (leftValue > rightValue) + return -1; + if (leftValue < rightValue) + return 1; + } + + return 0; + } + + function versionTag(option, index) { + if (option.isInstalled) + return qsTr("Installed"); + if (option.isLatest || index === 0) + return qsTr("Latest"); + return qsTr("Available"); + } + + function buildAvailableVersionOptions(rawVersions) { + const options = []; + const seenLabels = {}; + const installedVersionLabel = cleanVersionLabel(nvidiaUpdater.currentVersion.length > 0 + ? nvidiaUpdater.currentVersion + : nvidiaDetector.driverVersion); + const latestVersionLabel = cleanVersionLabel(nvidiaUpdater.latestVersion); + + for (let i = 0; i < rawVersions.length; ++i) { + const rawVersion = rawVersions[i]; + const displayVersion = cleanVersionLabel(rawVersion); + if (displayVersion.length === 0 || seenLabels[displayVersion]) + continue; + + seenLabels[displayVersion] = true; + options.push({ + rawVersion: rawVersion, + displayVersion: displayVersion, + isInstalled: installedVersionLabel.length > 0 && displayVersion === installedVersionLabel, + isLatest: latestVersionLabel.length > 0 && displayVersion === latestVersionLabel + }); + } + + options.sort(function(left, right) { + return compareVersionLabels(left.displayVersion, right.displayVersion); + }); + + for (let j = 0; j < options.length; ++j) + options[j].tag = versionTag(options[j], j); + + return options; + } + + readonly property var availableVersionOptions: buildAvailableVersionOptions(nvidiaUpdater.availableVersions) + + function ensureSelection() { + if (availableVersionOptions.length === 0) { + selectedVersionIndex = -1; + return; + } + + if (selectedVersionIndex >= 0 && selectedVersionIndex < availableVersionOptions.length) + return; + + for (let i = 0; i < availableVersionOptions.length; ++i) { + if (availableVersionOptions[i].isInstalled) { + selectedVersionIndex = i; + return; + } + } + + selectedVersionIndex = 0; + } + + function installSelectedVersion() { + if (deepCleanInstall) { + pendingInstallAfterClean = true; + operationSource = qsTr("Installer"); + operationDetail = qsTr("Cleaning legacy driver leftovers..."); + nvidiaInstaller.deepClean(); + return; + } + + if (useOpenModules) { + operationSource = qsTr("Installer"); + operationDetail = qsTr("Switching to NVIDIA open kernel modules..."); + nvidiaInstaller.installOpenSource(); + return; + } + + if (selectedVersionIndex >= 0 && selectedVersionIndex < availableVersionOptions.length) { + operationSource = qsTr("Updater"); + operationDetail = qsTr("Applying selected NVIDIA driver version..."); + nvidiaUpdater.applyVersion(availableVersionOptions[selectedVersionIndex].rawVersion); + return; + } + + operationSource = qsTr("Installer"); + operationDetail = qsTr("Installing the proprietary NVIDIA driver..."); + nvidiaInstaller.installProprietary(true); + } + + component ExpertHeaderRow: Rectangle { + id: expertHeaderRow + required property string title + required property string value + required property string markerText + + radius: 20 + color: page.theme.cardStrong + implicitHeight: 68 + + RowLayout { + anchors.fill: parent + anchors.leftMargin: 16 + anchors.rightMargin: 18 + spacing: 14 + + Rectangle { + width: 40 + height: 40 + radius: 14 + color: page.theme.accentA + + Label { + anchors.centerIn: parent + text: expertHeaderRow.markerText + color: "#ffffff" + font.pixelSize: 15 + font.weight: Font.DemiBold + } + } + + Label { + color: page.theme.textSoft + text: expertHeaderRow.title + font.pixelSize: 14 + font.weight: Font.DemiBold + } + + Item { + Layout.fillWidth: true + } + + Label { + text: expertHeaderRow.value + color: page.theme.text + font.pixelSize: 15 + font.weight: Font.DemiBold + } + } + } + + component VersionRow: Rectangle { + id: versionRow + required property int itemIndex + required property var optionData + + readonly property bool selected: page.selectedVersionIndex === itemIndex + + radius: 20 + color: selected ? page.theme.infoBg : page.theme.cardStrong + border.width: selected ? 2 : 0 + border.color: selected ? page.theme.accentA : "transparent" + implicitHeight: 70 + + RowLayout { + anchors.fill: parent + anchors.leftMargin: 18 + anchors.rightMargin: 18 + spacing: 14 + + Rectangle { + width: 30 + height: 30 + radius: 15 + color: "transparent" + border.width: 3 + border.color: versionRow.selected ? page.theme.accentA : page.theme.textSoft + + Rectangle { + anchors.centerIn: parent + width: 14 + height: 14 + radius: 7 + visible: versionRow.selected + color: page.theme.accentA + } + } + + Label { + text: optionData.displayVersion.length > 0 ? optionData.displayVersion : qsTr("Unknown version") + color: page.theme.text + font.pixelSize: 18 + font.weight: Font.DemiBold + elide: Text.ElideRight + } + + Rectangle { + radius: 14 + color: optionData.isInstalled ? page.theme.successBg : page.theme.card + implicitHeight: 30 + implicitWidth: tagLabel.implicitWidth + 20 + + Label { + id: tagLabel + anchors.centerIn: parent + text: optionData.tag + color: optionData.isInstalled ? page.theme.success : page.theme.textSoft + font.pixelSize: 13 + font.weight: Font.DemiBold + } + } + + Item { + Layout.fillWidth: true + } + + Rectangle { + width: 38 + height: 38 + radius: 14 + color: page.theme.successBg + + Label { + anchors.centerIn: parent + text: "\u2713" + color: page.theme.success + font.pixelSize: 20 + font.weight: Font.DemiBold + } + } + } + + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: page.selectedVersionIndex = itemIndex + } + } ScrollView { id: pageScroll @@ -19,319 +306,320 @@ Item { ColumnLayout { width: pageScroll.availableWidth - spacing: settingsPage.compactMode ? 12 : 16 + spacing: 14 + + Label { + text: qsTr("Expert Driver Management") + color: page.theme.text + font.pixelSize: 30 + font.weight: Font.DemiBold + } GridLayout { Layout.fillWidth: true - columns: width > 980 ? 2 : 1 + columns: page.wideLayout ? 2 : 1 columnSpacing: 16 rowSpacing: 16 - SectionPanel { + ColumnLayout { Layout.fillWidth: true - theme: settingsPage.theme - title: qsTr("Appearance & Behavior") - subtitle: qsTr("Control theme, density and operator-focused interface behavior.") + spacing: 12 - RowLayout { + SectionPanel { Layout.fillWidth: true - spacing: 12 + theme: page.theme + title: "" + subtitle: "" - ColumnLayout { + ExpertHeaderRow { Layout.fillWidth: true - spacing: 4 - - Label { - text: qsTr("Theme mode") - font.bold: true - color: settingsPage.theme.text - } - - Label { - text: qsTr("Choose whether the application follows the OS theme or uses an explicit light or dark shell.") - wrapMode: Text.Wrap - color: settingsPage.theme.textSoft - Layout.fillWidth: true - } + title: qsTr("Current Driver") + value: page.currentDriverLabel + markerText: "D" } - ComboBox { - id: themePicker - Layout.preferredWidth: 220 - model: uiPreferences.availableThemeModes - textRole: "label" - - Component.onCompleted: settingsPage.syncThemePicker() - - onActivated: { - const selected = model[currentIndex]; - if (selected && selected.code) { - uiPreferences.setThemeMode(selected.code); - } - } + ExpertHeaderRow { + Layout.fillWidth: true + title: qsTr("Kernel Version") + value: systemInfo.kernelVersion.length > 0 ? systemInfo.kernelVersion : qsTr("Unavailable") + markerText: "K" } } - RowLayout { + Label { + text: qsTr("Available Versions") + color: page.theme.text + font.pixelSize: 20 + font.weight: Font.DemiBold + } + + SectionPanel { Layout.fillWidth: true - spacing: 12 + theme: page.theme + title: "" + subtitle: "" - ColumnLayout { - Layout.fillWidth: true - spacing: 4 + Repeater { + model: page.availableVersionOptions.length - Label { - text: qsTr("Language") - font.bold: true - color: settingsPage.theme.text - } - - Label { - text: qsTr("Changes the application language immediately and keeps the selection for the next launch.") - wrapMode: Text.Wrap - color: settingsPage.theme.textSoft + delegate: VersionRow { Layout.fillWidth: true + itemIndex: index + optionData: page.availableVersionOptions[index] } } - ComboBox { - id: languagePicker - Layout.preferredWidth: 220 - model: languageManager.availableLanguages - textRole: "nativeLabel" - - Component.onCompleted: settingsPage.syncLanguagePicker() - - onActivated: { - const selected = model[currentIndex]; - if (selected && selected.code) { - languageManager.setCurrentLanguage(selected.code); - } - } + Label { + Layout.fillWidth: true + visible: page.availableVersionOptions.length === 0 + text: qsTr("No remote driver versions have been loaded yet. Use refresh to query the repository.") + wrapMode: Text.Wrap + color: page.theme.textSoft } } + } - RowLayout { - Layout.fillWidth: true - - ColumnLayout { - Layout.fillWidth: true - spacing: 4 - - Label { - text: qsTr("Compact layout") - font.bold: true - color: settingsPage.theme.text - } - - Label { - text: qsTr("Reduces spacing to fit more information on screen.") - wrapMode: Text.Wrap - color: settingsPage.theme.textSoft - Layout.fillWidth: true - } - } + ColumnLayout { + Layout.fillWidth: true + spacing: 12 - Switch { - checked: uiPreferences.compactMode - onToggled: uiPreferences.setCompactMode(checked) - } + Label { + text: qsTr("Configuration") + color: page.theme.text + font.pixelSize: 20 + font.weight: Font.DemiBold } - RowLayout { + SectionPanel { Layout.fillWidth: true + theme: page.theme + title: qsTr("Kernel Module Type") + subtitle: "" - ColumnLayout { + RowLayout { Layout.fillWidth: true - spacing: 4 + spacing: 12 - Label { - text: qsTr("Show advanced diagnostics") - font.bold: true - color: settingsPage.theme.text + Rectangle { + Layout.fillWidth: true + implicitHeight: 64 + radius: 18 + color: !page.useOpenModules ? page.theme.infoBg : page.theme.card + border.width: !page.useOpenModules ? 2 : 1 + border.color: !page.useOpenModules ? page.theme.accentA : page.theme.border + + RowLayout { + anchors.centerIn: parent + spacing: 12 + + Rectangle { + width: 24 + height: 24 + radius: 12 + color: !page.useOpenModules ? page.theme.accentA : "transparent" + border.width: 3 + border.color: !page.useOpenModules ? page.theme.accentA : page.theme.textSoft + } + + Label { + text: qsTr("Proprietary") + color: page.theme.text + font.pixelSize: 16 + font.weight: Font.DemiBold + } + } + + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: page.useOpenModules = false + } } - Label { - text: qsTr("Shows verification reports and expanded monitor metrics.") - wrapMode: Text.Wrap - color: settingsPage.theme.textSoft + Rectangle { Layout.fillWidth: true - } - } + implicitHeight: 64 + radius: 18 + color: page.useOpenModules ? page.theme.infoBg : page.theme.card + border.width: page.useOpenModules ? 2 : 1 + border.color: page.useOpenModules ? page.theme.accentA : page.theme.border + + RowLayout { + anchors.centerIn: parent + spacing: 12 + + Rectangle { + width: 24 + height: 24 + radius: 12 + color: page.useOpenModules ? page.theme.accentA : "transparent" + border.width: 3 + border.color: page.useOpenModules ? page.theme.accentA : page.theme.textSoft + } + + Label { + text: qsTr("Open") + color: page.theme.text + font.pixelSize: 16 + font.weight: Font.DemiBold + } + } - Switch { - checked: uiPreferences.showAdvancedInfo - onToggled: uiPreferences.setShowAdvancedInfo(checked) + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: page.useOpenModules = true + } + } } } - Flow { + Rectangle { Layout.fillWidth: true - spacing: 8 + radius: 22 + color: Qt.tint(page.theme.warningBg, "#22ffffff") + border.width: 1 + border.color: Qt.tint(page.theme.warning, "#55ffffff") + implicitHeight: 82 + + RowLayout { + anchors.fill: parent + anchors.leftMargin: 18 + anchors.rightMargin: 18 + spacing: 14 + + Rectangle { + width: 28 + height: 28 + radius: 14 + color: "transparent" + border.width: 3 + border.color: page.deepCleanInstall ? page.theme.text : page.theme.textSoft + + Rectangle { + anchors.centerIn: parent + width: 14 + height: 14 + radius: 7 + visible: page.deepCleanInstall + color: page.theme.warning + } + } - InfoBadge { - text: qsTr("Language: ") + languageManager.currentLanguageLabel - backgroundColor: settingsPage.theme.cardStrong - foregroundColor: settingsPage.theme.text - } + ColumnLayout { + Layout.fillWidth: true + spacing: 2 - InfoBadge { - text: settingsPage.themeMode === "system" - ? qsTr("Theme: Follow System") - : settingsPage.darkMode ? qsTr("Theme: Dark") - : qsTr("Theme: Light") - backgroundColor: settingsPage.theme.infoBg - foregroundColor: settingsPage.theme.text - } + Label { + text: qsTr("Deep Clean Installation") + color: page.theme.text + font.pixelSize: 15 + font.weight: Font.DemiBold + } - InfoBadge { - text: uiPreferences.compactMode ? qsTr("Compact Active") : qsTr("Comfort Active") - backgroundColor: settingsPage.theme.cardStrong - foregroundColor: settingsPage.theme.text + Label { + text: qsTr("Remove all previous driver configurations and cache") + color: page.theme.textSoft + font.pixelSize: 13 + } + } } - InfoBadge { - text: uiPreferences.showAdvancedInfo ? qsTr("Advanced Visible") : qsTr("Advanced Hidden") - backgroundColor: settingsPage.theme.cardStrong - foregroundColor: settingsPage.theme.text + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: page.deepCleanInstall = !page.deepCleanInstall } } RowLayout { Layout.fillWidth: true - spacing: 12 + spacing: 14 - Label { + Rectangle { Layout.fillWidth: true - text: qsTr("Restore the recommended interface defaults if the shell starts to feel cluttered.") - wrapMode: Text.Wrap - color: settingsPage.theme.textSoft - } - - ActionButton { - theme: settingsPage.theme - text: qsTr("Reset Interface Defaults") - onClicked: uiPreferences.resetToDefaults() - } - } - } - - SectionPanel { - Layout.fillWidth: true - theme: settingsPage.theme - title: qsTr("Diagnostics") - subtitle: qsTr("Useful runtime context before filing issues or performing support work.") + implicitHeight: 64 + radius: 20 + gradient: Gradient { + orientation: Gradient.Horizontal + GradientStop { position: 0.0; color: "#4b87f4" } + GradientStop { position: 1.0; color: "#8d57f7" } + } + opacity: page.backendBusy ? 0.6 : 1.0 - ColumnLayout { - Layout.fillWidth: true - spacing: 8 + Label { + anchors.centerIn: parent + text: qsTr("Install Selected Version") + color: "#ffffff" + font.pixelSize: 16 + font.weight: Font.DemiBold + } - DetailRow { - Layout.fillWidth: true - theme: settingsPage.theme - label: qsTr("Application") - value: Qt.application.name + " " + Qt.application.version + MouseArea { + anchors.fill: parent + enabled: !page.backendBusy + cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor + onClicked: page.installSelectedVersion() + } } - DetailRow { - Layout.fillWidth: true - theme: settingsPage.theme - label: qsTr("GPU") - value: nvidiaDetector.gpuFound ? nvidiaDetector.gpuName : qsTr("Not detected") - } + Rectangle { + Layout.preferredWidth: 190 + implicitHeight: 64 + radius: 20 + gradient: Gradient { + orientation: Gradient.Horizontal + GradientStop { position: 0.0; color: "#ff644f" } + GradientStop { position: 1.0; color: "#ff4a4a" } + } + opacity: nvidiaInstaller.busy ? 0.6 : 1.0 - DetailRow { - Layout.fillWidth: true - theme: settingsPage.theme - label: qsTr("Driver") - value: nvidiaDetector.activeDriver - } + Label { + anchors.centerIn: parent + text: qsTr("Remove All") + color: "#ffffff" + font.pixelSize: 16 + font.weight: Font.DemiBold + } - DetailRow { - Layout.fillWidth: true - theme: settingsPage.theme - label: qsTr("Session") - value: nvidiaDetector.sessionType.length > 0 ? nvidiaDetector.sessionType : qsTr("Unknown") + MouseArea { + anchors.fill: parent + enabled: !nvidiaInstaller.busy + cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor + onClicked: { + page.operationSource = qsTr("Installer"); + page.operationDetail = qsTr("Removing the NVIDIA driver..."); + nvidiaInstaller.remove(); + } + } } } - Label { - Layout.fillWidth: true - wrapMode: Text.Wrap - color: settingsPage.theme.textSoft - text: qsTr("Use the Driver page to refresh detection before copying any diagnostic context.") - } - } - - SectionPanel { - Layout.fillWidth: true - theme: settingsPage.theme - title: qsTr("Workflow Guidance") - subtitle: qsTr("Recommended order of operations when changing drivers.") - - Label { - Layout.fillWidth: true - wrapMode: Text.Wrap - color: settingsPage.theme.text - text: qsTr("1. Verify GPU detection and session type.\n2. Install or switch the driver stack.\n3. Check repository updates.\n4. Restart after successful package operations.") - } - - StatusBanner { - Layout.fillWidth: true - theme: settingsPage.theme - tone: nvidiaDetector.secureBootEnabled ? "warning" : "info" - text: nvidiaDetector.secureBootEnabled - ? qsTr("Secure Boot is enabled. Kernel module signing may still be required after package installation.") - : qsTr("No Secure Boot blocker is currently reported by the detector.") - } - } - - SectionPanel { - Layout.fillWidth: true - theme: settingsPage.theme - title: qsTr("About") - subtitle: qsTr("Project identity and current shell mode.") - - ColumnLayout { + RowLayout { Layout.fillWidth: true - spacing: 8 - DetailRow { - Layout.fillWidth: true - theme: settingsPage.theme - label: qsTr("Application") - value: Qt.application.name + " (" + Qt.application.version + ")" - } - - DetailRow { - Layout.fillWidth: true - theme: settingsPage.theme - label: qsTr("Theme") - value: settingsPage.themeMode === "system" - ? qsTr("Follow System") - : settingsPage.darkMode ? qsTr("Dark") - : qsTr("Light") - } - - DetailRow { - Layout.fillWidth: true - theme: settingsPage.theme - label: qsTr("Effective language") - value: languageManager.displayNameForLanguage(languageManager.effectiveLanguage) + ActionButton { + theme: page.theme + text: qsTr("Refresh Versions") + enabled: !page.backendBusy + onClicked: { + systemInfo.refresh(); + nvidiaDetector.refresh(); + nvidiaUpdater.checkForUpdate(); + nvidiaUpdater.refreshAvailableVersions(); + } } - DetailRow { + Item { Layout.fillWidth: true - theme: settingsPage.theme - label: qsTr("Layout density") - value: uiPreferences.compactMode ? qsTr("Compact") : qsTr("Comfort") } - DetailRow { - Layout.fillWidth: true - theme: settingsPage.theme - label: qsTr("Advanced diagnostics") - value: uiPreferences.showAdvancedInfo ? qsTr("Visible") : qsTr("Hidden") + InfoBadge { + text: page.operationDetail.length > 0 + ? page.operationSource + ": " + page.operationDetail + : qsTr("Ready") + backgroundColor: page.backendBusy ? page.theme.infoBg : page.theme.cardStrong + foregroundColor: page.theme.text } } } @@ -339,37 +627,57 @@ Item { } } - function syncLanguagePicker() { - for (let i = 0; i < languagePicker.model.length; ++i) { - if (languagePicker.model[i].code === languageManager.currentLanguage) { - languagePicker.currentIndex = i; - break; - } + Connections { + target: nvidiaUpdater + + function onAvailableVersionsChanged() { + page.ensureSelection(); } - } - function syncThemePicker() { - for (let i = 0; i < themePicker.model.length; ++i) { - if (themePicker.model[i].code === uiPreferences.themeMode) { - themePicker.currentIndex = i; - break; - } + function onUpdateFinished(success, message) { + page.operationSource = success ? qsTr("Updater") : qsTr("Error"); + page.operationDetail = message; + nvidiaDetector.refresh(); + systemInfo.refresh(); } } Connections { - target: languageManager - - function onCurrentLanguageChanged() { - settingsPage.syncLanguagePicker() + target: nvidiaInstaller + + function onInstallFinished(success, message) { + page.operationSource = success ? qsTr("Installer") : qsTr("Error"); + page.operationDetail = message; + nvidiaDetector.refresh(); + nvidiaUpdater.checkForUpdate(); + nvidiaUpdater.refreshAvailableVersions(); + systemInfo.refresh(); } - } - Connections { - target: uiPreferences + function onRemoveFinished(success, message) { + if (success && page.pendingInstallAfterClean) { + page.pendingInstallAfterClean = false; + page.deepCleanInstall = false; + page.installSelectedVersion(); + return; + } - function onThemeModeChanged() { - settingsPage.syncThemePicker() + page.pendingInstallAfterClean = false; + page.deepCleanInstall = false; + page.operationSource = success ? qsTr("Installer") : qsTr("Error"); + page.operationDetail = message; + nvidiaDetector.refresh(); + nvidiaUpdater.checkForUpdate(); + nvidiaUpdater.refreshAvailableVersions(); + systemInfo.refresh(); } } + + Component.onCompleted: { + systemInfo.refresh(); + nvidiaDetector.refresh(); + nvidiaUpdater.checkForUpdate(); + nvidiaUpdater.refreshAvailableVersions(); + ensureSelection(); + } }