diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..c691592 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,6 @@ +* @Project-Ro-ASD + +/src/backend/ @Project-Ro-ASD +/src/qml/ @Project-Ro-ASD +/docs/ @Project-Ro-ASD +/packaging/ @Project-Ro-ASD diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 30cf445..122a4ec 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,8 +17,12 @@ jobs: build: name: Build & Test runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + fedora: [41, 42] container: - image: fedora:41 + image: fedora:${{ matrix.fedora }} steps: - name: Checkout repository @@ -52,7 +56,7 @@ jobs: run: cmake --build build --parallel - name: Run tests - run: cd build && ctest --output-on-failure + run: ctest --test-dir build --output-on-failure - name: Check formatting (clang-format) run: | @@ -63,7 +67,7 @@ jobs: name: RPM Build Check runs-on: ubuntu-latest container: - image: fedora:41 + image: fedora:42 steps: - name: Checkout repository diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e23f597..43e09cf 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,17 +7,36 @@ on: workflow_dispatch: jobs: - release: - name: Create GitHub Release + artifacts: + name: Build Release Artifacts runs-on: ubuntu-latest + container: + image: fedora:42 permissions: - contents: write + contents: read steps: - name: Checkout repository uses: actions/checkout@v4 + - name: Install packaging dependencies + run: | + dnf install -y \ + git \ + rpm-build \ + cmake \ + extra-cmake-modules \ + gcc-c++ \ + ninja-build \ + qt6-qtbase-devel \ + qt6-qtbase-private-devel \ + qt6-qtdeclarative-devel \ + qt6-qttools-devel \ + qt6-qtwayland-devel \ + kf6-qqc2-desktop-style \ + polkit-devel + - name: Derive release version run: | VERSION="${GITHUB_REF_NAME#v}" @@ -29,10 +48,62 @@ jobs: git archive --format=tar.gz --prefix="${PREFIX}/" --output="${PREFIX}.tar.gz" "${GITHUB_SHA}" git archive --format=zip --prefix="${PREFIX}/" --output="${PREFIX}.zip" "${GITHUB_SHA}" + - name: Build RPM artifacts + run: | + mkdir -p ~/rpmbuild/SOURCES ~/rpmbuild/SPECS + cp "ro-control-${VERSION}.tar.gz" "${HOME}/rpmbuild/SOURCES/" + cp packaging/rpm/ro-control.spec "${HOME}/rpmbuild/SPECS/ro-control.spec" + rpmbuild -ba "${HOME}/rpmbuild/SPECS/ro-control.spec" \ + --define "_topdir ${HOME}/rpmbuild" + cp ~/rpmbuild/SRPMS/*.src.rpm . + cp ~/rpmbuild/RPMS/*/*.rpm . + + - name: Generate checksums + run: | + sha256sum \ + "ro-control-${VERSION}.tar.gz" \ + "ro-control-${VERSION}.zip" \ + *.rpm \ + *.src.rpm > "ro-control-${VERSION}-SHA256SUMS.txt" + + - name: Upload release artifacts + uses: actions/upload-artifact@v4 + with: + name: ro-control-release-${{ env.VERSION }} + path: | + ro-control-${{ env.VERSION }}.tar.gz + ro-control-${{ env.VERSION }}.zip + *.rpm + *.src.rpm + ro-control-${{ env.VERSION }}-SHA256SUMS.txt + + release: + name: Create GitHub Release + runs-on: ubuntu-latest + needs: artifacts + + permissions: + contents: write + + steps: + - name: Derive release version + run: | + VERSION="${GITHUB_REF_NAME#v}" + echo "VERSION=${VERSION}" >> "${GITHUB_ENV}" + + - name: Download release artifacts + uses: actions/download-artifact@v4 + with: + name: ro-control-release-${{ env.VERSION }} + path: dist + - name: Publish release uses: softprops/action-gh-release@v2 with: generate_release_notes: true files: | - ro-control-${{ env.VERSION }}.tar.gz - ro-control-${{ env.VERSION }}.zip + dist/ro-control-${{ env.VERSION }}.tar.gz + dist/ro-control-${{ env.VERSION }}.zip + dist/*.rpm + dist/*.src.rpm + dist/ro-control-${{ env.VERSION }}-SHA256SUMS.txt diff --git a/.gitignore b/.gitignore index 7c6cfc9..fc40183 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ # Build directories build/ build-*/ +src/build-*/ cmake-build-*/ out/ @@ -54,6 +55,9 @@ CMakeLists.txt.user *.tar.gz *.tar.xz *.AppImage +packaging/rpm/.ccache/ +packaging/rpm/rpmbuild/ +packaging/rpm/rpmbuild-*/ # Logs *.log diff --git a/CMakeLists.txt b/CMakeLists.txt index ad266b7..f54f960 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -31,6 +31,7 @@ set(CMAKE_AUTOUIC ON) find_package(Qt6 REQUIRED COMPONENTS Core + Qml Quick # Qt Quick / QML engine QuickControls2 # QML controls (Button, Slider, etc.) Widgets # QApplication base @@ -70,15 +71,45 @@ set(BACKEND_SOURCES src/backend/system/polkit.cpp src/backend/system/dnfmanager.cpp src/backend/system/commandrunner.cpp + src/backend/system/sessionutil.cpp + src/backend/system/capabilityprobe.cpp + src/backend/system/languagemanager.cpp + src/backend/system/uipreferencesmanager.cpp ) set(APP_SOURCES src/main.cpp src/cli/cli.cpp - ${BACKEND_SOURCES} ) # ─── QML Resources ──────────────────────────────────────────────────────────── +add_library(ro-control-backend STATIC ${BACKEND_SOURCES}) + +target_link_libraries(ro-control-backend PUBLIC + Qt6::Core + Qt6::DBus + Qt6::Qml +) + +target_compile_options(ro-control-backend PRIVATE + -Wall + -Wextra + -Wpedantic + $<$:-g -O0> + $<$:-O2> +) + +target_include_directories(ro-control-backend PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/src + ${CMAKE_CURRENT_SOURCE_DIR}/src/backend +) + +target_compile_definitions(ro-control-backend PUBLIC + RO_CONTROL_POLICY_ID="${RO_CONTROL_POLICY_ID}" + RO_CONTROL_HELPER_BUILD_PATH="${RO_CONTROL_HELPER_BUILD_PATH}" + RO_CONTROL_HELPER_INSTALL_PATH="${RO_CONTROL_HELPER_INSTALL_PATH}" +) + qt_add_executable(ro-control ${APP_SOURCES}) set_source_files_properties(src/qml/assets/ro-control-logo.svg PROPERTIES @@ -97,6 +128,11 @@ qt_add_qml_module(ro-control src/qml/pages/DriverPage.qml src/qml/pages/MonitorPage.qml src/qml/pages/SettingsPage.qml + src/qml/components/InfoBadge.qml + src/qml/components/ActionButton.qml + src/qml/components/DetailRow.qml + src/qml/components/SectionPanel.qml + src/qml/components/StatusBanner.qml src/qml/components/StatCard.qml src/qml/components/SidebarMenu.qml RESOURCES @@ -105,8 +141,11 @@ qt_add_qml_module(ro-control ) if(Qt6LinguistTools_FOUND) + # English strings ship directly from source code. Keep the English TS file + # for catalog maintenance, but only compile non-source locales into .qm. set(TS_FILES - i18n/ro-control_en.ts + i18n/ro-control_de.ts + i18n/ro-control_es.ts i18n/ro-control_tr.ts ) @@ -118,11 +157,10 @@ endif() # ─── Link Qt Libraries ──────────────────────────────────────────────────────── target_link_libraries(ro-control PRIVATE - Qt6::Core Qt6::Quick Qt6::QuickControls2 Qt6::Widgets - Qt6::DBus + ro-control-backend ) # ─── Compiler Warnings ──────────────────────────────────────────────────────── @@ -152,8 +190,6 @@ target_include_directories(ro-control PRIVATE target_compile_definitions(ro-control PRIVATE RO_CONTROL_POLICY_ID="${RO_CONTROL_POLICY_ID}" - RO_CONTROL_HELPER_BUILD_PATH="${RO_CONTROL_HELPER_BUILD_PATH}" - RO_CONTROL_HELPER_INSTALL_PATH="${RO_CONTROL_HELPER_INSTALL_PATH}" ) configure_file( @@ -162,6 +198,7 @@ configure_file( @ONLY NEWLINE_STYLE UNIX ) +execute_process(COMMAND chmod +x ${RO_CONTROL_HELPER_BUILD_PATH}) configure_file( data/polkit/io.github.ProjectRoASD.rocontrol.policy.in @@ -179,7 +216,7 @@ install(PROGRAMS ${RO_CONTROL_HELPER_BUILD_PATH} RENAME ${RO_CONTROL_HELPER_NAME} ) -install(FILES data/icons/ro-control.desktop +install(FILES data/icons/io.github.projectroasd.rocontrol.desktop DESTINATION ${CMAKE_INSTALL_DATADIR}/applications ) @@ -193,7 +230,7 @@ install(FILES DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/256x256/apps ) -install(FILES data/icons/ro-control.metainfo.xml +install(FILES data/icons/io.github.projectroasd.rocontrol.metainfo.xml DESTINATION ${CMAKE_INSTALL_DATADIR}/metainfo ) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 93a0cf6..f09979b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -143,6 +143,14 @@ cd build && ctest --output-on-failure **PRs to `main` will be rejected.** All contributions go through `dev` first. +Recommended PR checklist: + +- Keep scope focused to one feature/fix/theme +- Update docs when user-visible behavior changes +- Update translations when adding or changing UI strings +- Include before/after screenshots for UI changes +- Run `ctest --test-dir build --output-on-failure` locally + --- ## Translations @@ -186,3 +194,5 @@ Use the [bug report template](.github/ISSUE_TEMPLATE/bug_report.md) and include: - Steps to reproduce - Expected vs actual behavior - Relevant terminal output, `coredumpctl info ro-control`, or recent journal entries + +For general setup questions, diagnostics help, or uncertainty about whether something is a bug, start with [SUPPORT.md](SUPPORT.md). diff --git a/README.md b/README.md index 04cbadd..fea0e2b 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,28 @@ ro-Control is a native KDE Plasma desktop application built with **C++20** and **Qt6/QML** that simplifies NVIDIA GPU driver management and system monitoring on Linux. It provides a modern, Plasma-native interface for installing, updating, and monitoring graphics drivers — with full PolicyKit integration for secure privilege escalation. +## Project Status + +ro-Control is an active desktop utility for Fedora-first NVIDIA driver workflows. +The current codebase focuses on: + +- Native Qt/QML desktop UX instead of wrapper scripts +- Safe driver lifecycle operations through PolicyKit and DNF +- Practical diagnostics for GPU, CPU, and RAM telemetry +- English source strings with complete Turkish runtime localization +- Persistent interface preferences with explicit `System / Light / Dark` theme selection + +It does **not** currently implement hybrid graphics switching, fan control, or overclocking. + +## Why This Repository Exists + +ro-Control is intended to be the NVIDIA operations and diagnostics surface for the broader **Project Ro ASD / ro-ASD OS** ecosystem. The repository is structured so it can be: + +- Shown on the organization profile as a flagship desktop utility +- Built and packaged independently from the operating system image +- Used both interactively from the GUI and programmatically from the CLI +- Extended cleanly through separate backend, frontend, packaging, and translation layers + ## Features ### 🚀 Driver Management @@ -29,19 +51,32 @@ ro-Control is a native KDE Plasma desktop application built with **C++20** and * - **Secure Boot** — Detection and warnings for unsigned kernel modules ### 📊 Live System Monitor -- Real-time GPU temperature, load, and VRAM usage -- CPU load and temperature tracking -- RAM usage monitoring +- Real-time GPU temperature, load, and VRAM usage when `nvidia-smi` is available +- CPU load tracking with temperature probing via sysfs, hwmon, and `sensors` +- RAM usage monitoring via `/proc/meminfo` with `free` fallback - Color-coded progress indicators ### 🖥 Display & System - **Wayland support** — Automatic `nvidia-drm.modeset=1` GRUB configuration -- **Hybrid graphics** — Switch between NVIDIA, Intel, and On-Demand modes - **PolicyKit integration** — Secure privilege escalation without running as root +- **Persistent shell preferences** — Saved theme mode, density, and diagnostics visibility + +## Development + +The easiest way to develop ro-Control rapidly on Fedora is using the provided `dev-watch.sh` script, which automatically rebuilds and restarts the application on file save: +```bash +# Setup Fedora dependencies +./scripts/fedora-bootstrap.sh + +# Start the live development watcher +./scripts/dev-watch.sh +``` + +> **Note:** If you launch the tool on a system without a working NVIDIA driver or encounter `libEGL` errors, UI elements may overlap. The `dev-watch.sh` script detects this and automatically exports `QT_XCB_GL_INTEGRATION=none` to force software rendering as a fallback. ### 🌍 Internationalization - Runtime locale loading with Qt translations (`.ts` / `.qm`) -- English source strings with Turkish translation included +- Shipped runtime locales: English and Turkish - Extensible translation workflow for additional languages ### 🧰 CLI Support @@ -52,6 +87,24 @@ ro-Control is a native KDE Plasma desktop application built with **C++20** and * - `ro-control driver install|remove|update|deep-clean` for scripted driver management - Installed `man ro-control` page and Bash/Zsh/Fish shell completions +### ✅ Test Coverage +- Backend unit tests for detector, updater, monitor, preferences, CLI, and system integration flows +- QML integration coverage for `DriverPage` state synchronization +- Translation release target for shipped locales + +## Current Scope + +Supported well today: +- Fedora-oriented NVIDIA driver install, update, and cleanup workflows +- Driver-state inspection and diagnostics export +- Monitor dashboard for live CPU/GPU/RAM status +- App packaging metadata, shell completions, and man page support + +Deliberately out of scope for now: +- Windows support +- Non-Qt frontends +- Advanced GPU tuning or gaming overlay features + ## Screenshots Preview assets are available under [`docs/screenshots/`](docs/screenshots/). @@ -71,6 +124,14 @@ sudo dnf install ./ro-control-*.rpm See [docs/BUILDING.md](docs/BUILDING.md) for full instructions. +Fedora quick bootstrap: + +```bash +./scripts/fedora-bootstrap.sh +``` + +For Fedora-specific runtime notes, see [docs/FEDORA.md](docs/FEDORA.md). + ### CLI Quick Examples ```bash @@ -130,6 +191,8 @@ ro-Control/ Contributions are welcome! Please read [CONTRIBUTING.md](CONTRIBUTING.md) before submitting a pull request. For release flow details, see [docs/RELEASE.md](docs/RELEASE.md). For localization scaffolding, see [i18n/README.md](i18n/README.md). +For architecture details, see [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md). +For usage questions and issue routing, see [SUPPORT.md](SUPPORT.md). Quick contribution flow: diff --git a/README.tr.md b/README.tr.md index 72973a1..a83f8a7 100644 --- a/README.tr.md +++ b/README.tr.md @@ -20,6 +20,28 @@ ro-Control, **C++20** ve **Qt6/QML** ile geliştirilmiş, Linux üzerinde NVIDIA GPU sürücü yönetimini ve sistem izlemeyi kolaylaştıran native bir KDE Plasma masaüstü uygulamasıdır. Sürücülerin kurulumu, güncellenmesi ve izlenmesi için modern, Plasma'ya uyumlu bir arayüz sunar; güvenli yetki yükseltme için PolicyKit entegrasyonu içerir. +## Proje Durumu + +ro-Control, Fedora odaklı NVIDIA sürücü iş akışları için aktif geliştirilen bir masaüstü yardımcı uygulamasıdır. +Mevcut kod tabanı özellikle şu alanlara odaklanır: + +- Script sarmalayıcıları yerine yerel Qt/QML masaüstü deneyimi +- PolicyKit ve DNF üzerinden güvenli sürücü yaşam döngüsü işlemleri +- GPU, CPU ve RAM telemetrisi için pratik tanılama araçları +- İngilizce kaynak metinler ve tam Türkçe çalışma zamanı yerelleştirmesi +- Kalıcı arayüz tercihleri ve açık `Sistem / Açık / Koyu` tema seçimi + +Şu anda **hibrit grafik geçişi**, fan kontrolü veya overclock özellikleri sunmaz. + +## Bu Depo Neden Var + +ro-Control, daha geniş **Project Ro ASD / ro-ASD OS** ekosistemi içinde NVIDIA işlemleri ve tanılama yüzeyi olarak konumlanır. Depo yapısı şu amaçlara uygundur: + +- Organizasyon profilinde öne çıkan bir masaüstü aracı olarak sergilenmek +- İşletim sistemi imajından bağımsız şekilde derlenip paketlenebilmek +- Hem GUI hem de CLI üzerinden kullanılabilmek +- Backend, frontend, paketleme ve çeviri katmanlarında temiz şekilde genişletilebilmek + ## Özellikler ### 🚀 Sürücü Yönetimi @@ -29,19 +51,19 @@ ro-Control, **C++20** ve **Qt6/QML** ile geliştirilmiş, Linux üzerinde NVIDIA - **Güvenli Önyükleme** — İmzasız kernel modülleri için tespit ve uyarı ### 📊 Canlı Sistem Monitörü -- Gerçek zamanlı GPU sıcaklığı, yük ve VRAM kullanımı -- CPU yükü ve sıcaklık takibi -- RAM kullanım izleme +- `nvidia-smi` erişilebildiğinde gerçek zamanlı GPU sıcaklığı, yük ve VRAM kullanımı +- sysfs, hwmon ve `sensors` üzerinden CPU yükü ve sıcaklık takibi +- `/proc/meminfo` ve gerektiğinde `free` fallback'i ile RAM kullanım izleme - Renk kodlu ilerleme göstergeleri ### 🖥 Ekran & Sistem - **Wayland desteği** — Otomatik `nvidia-drm.modeset=1` GRUB yapılandırması -- **Hibrit grafik** — NVIDIA, Intel ve On-Demand modları arasında geçiş - **PolicyKit entegrasyonu** — Root olarak çalıştırmadan güvenli yetki yükseltme +- **Kalıcı kabuk tercihleri** — Kayıtlı tema modu, yoğunluk ve tanılama görünürlüğü ### 🌍 Çok Dil Desteği - Qt çeviri sistemi (`.ts` / `.qm`) ile çalışma zamanı yerelleştirme -- İngilizce kaynak metinler ve dahil edilmiş Türkçe çeviri +- Dağıtıma giren çalışma zamanı dilleri: İngilizce ve Türkçe - Yeni diller için genişletilebilir iş akışı ### 🧰 CLI Desteği @@ -52,6 +74,24 @@ ro-Control, **C++20** ve **Qt6/QML** ile geliştirilmiş, Linux üzerinde NVIDIA - `ro-control driver install|remove|update|deep-clean` scriptlenebilir sürücü yönetimi sunar - Kurulumla birlikte `man ro-control` sayfası ve Bash/Zsh/Fish completion dosyaları gelir +### ✅ Test Kapsamı +- Detector, updater, monitor, preferences, CLI ve sistem entegrasyonu için backend testleri +- `DriverPage` durum senkronizasyonu için QML entegrasyon testi +- Dağıtıma giren diller için translation release target doğrulaması + +## Mevcut Kapsam + +Bugün iyi desteklenenler: +- Fedora odaklı NVIDIA sürücü kurulum, güncelleme ve temizlik iş akışları +- Sürücü durumu denetimi ve tanılama çıktıları +- CPU/GPU/RAM canlı durumunu gösteren monitor paneli +- Paketleme metadatası, shell completion dosyaları ve man page desteği + +Şimdilik kapsam dışı olanlar: +- Windows desteği +- Qt dışı frontendler +- İleri seviye GPU tuning veya oyun içi overlay özellikleri + ## Ekran Görüntüleri Önizleme görselleri [`docs/screenshots/`](docs/screenshots/) altında bulunur. @@ -71,6 +111,14 @@ sudo dnf install ./ro-control-*.rpm Tam talimatlar için [docs/BUILDING.md](docs/BUILDING.md) dosyasına bakın. +Fedora hızlı kurulum: + +```bash +./scripts/fedora-bootstrap.sh +``` + +Fedora çalışma notları için [docs/FEDORA.md](docs/FEDORA.md) dosyasına bakın. + ### CLI Hızlı Örnekler ```bash @@ -130,6 +178,8 @@ ro-Control/ Katkılarınızı bekliyoruz! Pull request göndermeden önce lütfen [CONTRIBUTING.md](CONTRIBUTING.md) dosyasını okuyun. Sürüm akış detayları için [docs/RELEASE.md](docs/RELEASE.md) dosyasına bakın. Yerelleştirme altyapısı için [i18n/README.md](i18n/README.md) dosyasını inceleyin. +Mimari detaylar için [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) dosyasına bakın. +Kullanım desteği ve issue yönlendirmesi için [SUPPORT.md](SUPPORT.md) dosyasına bakın. Hızlı katkı akışı: diff --git a/SECURITY.md b/SECURITY.md index 6cd9c93..35ffb9f 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,33 +1,31 @@ # Security Policy -## Supported Versions +## Supported Releases -Security fixes are provided for the latest stable release and the `main` branch. +Security fixes are applied to the latest development branch and the most recent tagged release that matches it. | Version | Supported | -| --- | --- | -| Latest release | :white_check_mark: | -| `main` branch | :white_check_mark: | -| Older releases | :x: | +|---------|-----------| +| Latest tag | Yes | +| `main` / active release line | Yes | +| Older releases | No | ## Reporting a Vulnerability -Please do not open public issues for security vulnerabilities. +Do not open public GitHub issues for security-sensitive bugs. -Report vulnerabilities by email: -- `security@project-ro.dev` (preferred) +Use GitHub Security Advisories for private reporting: +- Repository Security tab +- `Report a vulnerability` -Include the following details in your report: -- A clear description of the issue and impact -- Steps to reproduce or a proof of concept -- Affected version/commit -- Any suggested mitigation +Include the following where possible: +- affected ro-Control version +- distro and desktop session +- exact steps to reproduce +- whether privilege escalation or package operations are involved +- logs, screenshots, or proof-of-concept details -## Response Timeline - -We aim to: -- Acknowledge reports within 72 hours -- Provide an initial assessment within 7 days -- Share remediation or workaround details as soon as possible - -Thank you for helping keep ro-Control and its users safe. +You can expect: +- an acknowledgement after triage +- confirmation if the issue is reproducible +- a fix or mitigation plan for supported versions diff --git a/SUPPORT.md b/SUPPORT.md index e52c21f..c14d792 100644 --- a/SUPPORT.md +++ b/SUPPORT.md @@ -6,6 +6,7 @@ Use the channels below based on your need: - Usage questions and setup help: GitHub Discussions (recommended) - Confirmed bugs: GitHub Issues using the Bug Report template - Security concerns: see `SECURITY.md` +- Project overview and scope: `README.md` ## Before Opening an Issue @@ -16,12 +17,19 @@ Please include: - Steps to reproduce - Relevant logs/screenshots +Useful commands: +- `ro-control status` +- `ro-control diagnostics --json` +- `lspci | grep -i nvidia` +- `nvidia-smi` + ## Project Documentation Read these first: - `README.md` - `docs/BUILDING.md` - `docs/ARCHITECTURE.md` +- `docs/FEDORA.md` ## Language diff --git a/data/completions/_ro-control b/data/completions/_ro-control index c072b88..2e7bb1a 100644 --- a/data/completions/_ro-control +++ b/data/completions/_ro-control @@ -28,8 +28,8 @@ global_opts=( local -a install_opts install_opts=( '--proprietary[Use the proprietary NVIDIA driver install path]' - '--open-source[Use the open-source Nouveau install path]' - '--accept-license[Confirm proprietary driver license acceptance]' + '--open-source[Use the NVIDIA open kernel module install path]' + '--accept-license[Confirm NVIDIA license review for the proprietary install path]' ) if (( CURRENT == 2 )); then diff --git a/data/completions/ro-control.fish b/data/completions/ro-control.fish index 8b69ee9..72dac0c 100644 --- a/data/completions/ro-control.fish +++ b/data/completions/ro-control.fish @@ -19,5 +19,5 @@ complete -c ro-control -n "__fish_seen_subcommand_from driver; and not __fish_se complete -c ro-control -n "__fish_seen_subcommand_from driver; and not __fish_seen_subcommand_from install remove update deep-clean" -a deep-clean -d "Remove legacy NVIDIA leftovers" complete -c ro-control -n "__fish_seen_subcommand_from install" -l proprietary -d "Use the proprietary NVIDIA driver install path" -complete -c ro-control -n "__fish_seen_subcommand_from install" -l open-source -d "Use the open-source Nouveau install path" -complete -c ro-control -n "__fish_seen_subcommand_from install" -l accept-license -d "Confirm proprietary driver license acceptance" +complete -c ro-control -n "__fish_seen_subcommand_from install" -l open-source -d "Use the NVIDIA open kernel module install path" +complete -c ro-control -n "__fish_seen_subcommand_from install" -l accept-license -d "Confirm NVIDIA license review for the proprietary install path" diff --git a/data/icons/ro-control.desktop b/data/icons/io.github.projectroasd.rocontrol.desktop similarity index 91% rename from data/icons/ro-control.desktop rename to data/icons/io.github.projectroasd.rocontrol.desktop index fbec20b..b92e237 100644 --- a/data/icons/ro-control.desktop +++ b/data/icons/io.github.projectroasd.rocontrol.desktop @@ -13,4 +13,4 @@ Categories=System;Settings;HardwareSettings; Keywords=nvidia;driver;gpu;monitor;system;fedora; Keywords[tr]=nvidia;sürücü;gpu;monitör;sistem;fedora; StartupNotify=true -StartupWMClass=ro-control +StartupWMClass=io.github.projectroasd.rocontrol diff --git a/data/icons/ro-control.metainfo.xml b/data/icons/io.github.projectroasd.rocontrol.metainfo.xml similarity index 50% rename from data/icons/ro-control.metainfo.xml rename to data/icons/io.github.projectroasd.rocontrol.metainfo.xml index 57e04ee..b988bbb 100644 --- a/data/icons/ro-control.metainfo.xml +++ b/data/icons/io.github.projectroasd.rocontrol.metainfo.xml @@ -1,6 +1,6 @@ - ro-control.desktop + io.github.projectroasd.rocontrol.desktop ro-Control NVIDIA driver manager and system monitor NVIDIA sürücü yöneticisi ve sistem monitörü @@ -10,18 +10,27 @@

- ro-Control helps users detect NVIDIA GPUs, install or update - drivers via DNF, and monitor system metrics from a Qt desktop UI. + ro-Control is a Fedora-oriented desktop utility for NVIDIA driver + management and live system diagnostics. It helps users detect GPUs, + install or update drivers through DNF, and inspect GPU, CPU, and RAM + telemetry from a native Qt interface.

-
- -

- ro-Control, kullanıcıların NVIDIA GPU'ları tespit etmesine, - sürücüleri DNF ile kurup güncellemesine ve sistem metriklerini Qt tabanlı - bir masaüstü arayüzünden izlemesine yardımcı olur. +

+ ro-Control, Fedora odaklı bir NVIDIA sürücü yönetimi ve canlı sistem + tanılama masaüstü aracıdır. Kullanıcıların GPU'ları tespit etmesine, + sürücüleri DNF üzerinden kurup güncellemesine ve GPU, CPU, RAM + telemetrisini yerel bir Qt arayüzünden izlemesine yardımcı olur.

+ + NVIDIA + Drivers + Diagnostics + Telemetry + Fedora + + System Settings @@ -38,15 +47,17 @@ - ro-control.desktop + io.github.projectroasd.rocontrol.desktop ro-control - + Project Ro ASD https://github.com/Project-Ro-ASD/ro-Control + https://github.com/Project-Ro-ASD/ro-Control https://github.com/Project-Ro-ASD/ro-Control/issues + https://github.com/Project-Ro-ASD/ro-Control/blob/main/SUPPORT.md
diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 9e56940..229b36a 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -21,7 +21,8 @@ ro-Control follows a strict **C++ Backend / QML Frontend** separation. The two l │ Shell commands / D-Bus ┌───────────────────▼─────────────────────────────┐ │ Linux System │ -│ sysfs · nvidia-smi · dnf · pkexec · GRUB │ +│ sysfs · hwmon · sensors · nvidia-smi · dnf │ +│ pkexec · GRUB · /proc │ └─────────────────────────────────────────────────┘ ``` @@ -51,22 +52,24 @@ Divided into three modules: #### `monitor/` — Live Statistics | File | Responsibility | |------|---------------| -| `gpumonitor.cpp` | Poll GPU temperature, load, VRAM via `nvidia-smi` or sysfs | -| `cpumonitor.cpp` | Poll CPU load and temperature via `/proc/stat` and hwmon | -| `rammonitor.cpp` | Poll RAM usage via `/proc/meminfo` | +| `gpumonitor.cpp` | Poll GPU temperature, load, VRAM via `nvidia-smi` | +| `cpumonitor.cpp` | Poll CPU load via `/proc/stat` and probe temperatures via thermal zones, hwmon, and `sensors` | +| `rammonitor.cpp` | Poll RAM usage via `/proc/meminfo` with `free --mebi` fallback | #### `system/` — System Integration | File | Responsibility | |------|---------------| | `commandrunner.cpp` | Execute shell commands, capture stdout/stderr | | `dnfmanager.cpp` | Wrap DNF commands for install/remove/update | +| `languagemanager.cpp` | Load runtime locale catalogs and expose shipped language choices | | `polkit.cpp` | Privilege escalation via `pkexec` / PolicyKit D-Bus | +| `uipreferencesmanager.cpp` | Persist theme mode, density, and diagnostics visibility | --- ## C++ ↔ QML Communication -Qt's `QObject` system is the bridge. Backend objects are injected at startup from `main.cpp`, and their `Q_PROPERTY` values are then consumed by QML: +Qt's `QObject` system is the bridge. Backend objects are injected at startup from `main.cpp` into the root QML context, and their `Q_PROPERTY` values are then consumed by QML: ```cpp // C++ side — gpumonitor.h @@ -128,6 +131,12 @@ The PolicyKit action definition and helper entrypoint live in `data/polkit/` and CMake 3.22+ with `qt_add_qml_module` for QML resource embedding. All QML files are compiled into the binary at build time — no loose `.qml` files needed at runtime. +## Test Layout + +- `test_detector`, `test_updater`, `test_monitor`, `test_preferences`, `test_system_integration`, `test_cli`: backend and CLI regression coverage +- `test_driver_page`: QML integration coverage for frontend/backend state bindings +- `ro-control_lrelease`: release target that compiles shipped locale catalogs + See [BUILDING.md](BUILDING.md) for full build instructions. --- @@ -141,22 +150,33 @@ ro-Control/ │ │ ├── nvidia/ │ │ │ ├── detector.h / detector.cpp │ │ │ ├── installer.h / installer.cpp -│ │ │ └── updater.h / updater.cpp +│ │ │ ├── updater.h / updater.cpp +│ │ │ └── versionparser.h / versionparser.cpp │ │ ├── monitor/ │ │ │ ├── gpumonitor.h / gpumonitor.cpp │ │ │ ├── cpumonitor.h / cpumonitor.cpp │ │ │ └── rammonitor.h / rammonitor.cpp -│ │ └── system/ -│ │ ├── commandrunner.h / commandrunner.cpp -│ │ ├── dnfmanager.h / dnfmanager.cpp -│ │ └── polkit.h / polkit.cpp +│ │ ├── system/ +│ │ │ ├── commandrunner.h / commandrunner.cpp +│ │ │ ├── dnfmanager.h / dnfmanager.cpp +│ │ │ ├── languagemanager.h / languagemanager.cpp +│ │ │ ├── polkit.h / polkit.cpp +│ │ │ ├── sessionutil.h / sessionutil.cpp +│ │ │ └── uipreferencesmanager.h / uipreferencesmanager.cpp +│ │ └── cli/ +│ │ └── cli.h / cli.cpp │ ├── qml/ │ │ ├── assets/ │ │ │ ├── ro-control-logo.png │ │ │ └── ro-control-logo.svg │ │ ├── components/ +│ │ │ ├── ActionButton.qml +│ │ │ ├── DetailRow.qml +│ │ │ ├── InfoBadge.qml +│ │ │ ├── SectionPanel.qml │ │ │ ├── SidebarMenu.qml │ │ │ ├── StatCard.qml +│ │ │ ├── StatusBanner.qml │ │ │ └── qmldir │ │ ├── pages/ │ │ │ ├── DriverPage.qml diff --git a/docs/BUILDING.md b/docs/BUILDING.md index abc8a54..e1d6fc3 100644 --- a/docs/BUILDING.md +++ b/docs/BUILDING.md @@ -16,6 +16,17 @@ This guide covers building ro-Control from source on Linux systems with Qt 6 and --- +## Fedora Quick Bootstrap + +```bash +./scripts/fedora-bootstrap.sh +``` + +The script installs Fedora dependencies, builds the app, and runs tests by default. +For Fedora-specific runtime notes, see [FEDORA.md](FEDORA.md). + +--- + ## Install Dependencies ```bash @@ -32,6 +43,12 @@ sudo dnf install \ polkit-devel ``` +Runtime tools used by diagnostics and driver operations: + +```bash +sudo dnf install dnf polkit pciutils mokutil kmod lm_sensors procps-ng +``` + --- ## Clone the Repository @@ -48,32 +65,22 @@ cd ro-Control ### Debug Build (for development) ```bash -mkdir build && cd build -cmake .. -DCMAKE_BUILD_TYPE=Debug -make -j$(nproc) +cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Debug +cmake --build build -j$(nproc) ``` ### Release Build ```bash -mkdir build && cd build -cmake .. -DCMAKE_BUILD_TYPE=Release -make -j$(nproc) -``` - -### Faster builds with Ninja (optional) - -```bash -mkdir build && cd build -cmake .. -GNinja -DCMAKE_BUILD_TYPE=Debug -ninja +cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release +cmake --build build -j$(nproc) ``` ### Refresh translations (recommended before release) ```bash lupdate src -ts i18n/ro-control_en.ts i18n/ro-control_tr.ts -cmake --build build +cmake --build build --target ro-control_lrelease ``` --- @@ -81,19 +88,19 @@ cmake --build build ## Run ```bash -# From the build directory -./ro-control +# From the repository root +./build/ro-control ``` CLI examples: ```bash -./ro-control help -./ro-control version -./ro-control status -./ro-control diagnostics --json -./ro-control driver install --proprietary --accept-license -./ro-control driver update +./build/ro-control help +./build/ro-control version +./build/ro-control status +./build/ro-control diagnostics --json +./build/ro-control driver install --proprietary --accept-license +./build/ro-control driver update ``` > **Note:** Driver install/remove operations require PolicyKit authentication. The UI will prompt you automatically. @@ -110,27 +117,25 @@ After `cmake --install`, the CLI integration also installs: ## Install System-Wide ```bash -cd build -sudo make install +sudo cmake --install build ``` This installs: -- Binary → `/usr/local/bin/ro-control` -- Privileged helper → `/usr/local/libexec/ro-control-helper` -- Desktop entry → `/usr/local/share/applications/` -- Icons → `/usr/local/share/icons/` -- AppStream metadata → `/usr/local/share/metainfo/` -- PolicyKit policy → `/usr/local/share/polkit-1/actions/` +- Binary -> `/usr/local/bin/ro-control` +- Privileged helper -> `/usr/local/libexec/ro-control-helper` +- Desktop entry -> `/usr/local/share/applications/` +- Icons -> `/usr/local/share/icons/` +- AppStream metadata -> `/usr/local/share/metainfo/` +- PolicyKit policy -> `/usr/local/share/polkit-1/actions/` --- ## Build with Tests ```bash -mkdir build && cd build -cmake .. -DBUILD_TESTS=ON -make -j$(nproc) -ctest --output-on-failure +cmake -S . -B build -G Ninja -DBUILD_TESTS=ON +cmake --build build -j$(nproc) +ctest --test-dir build --output-on-failure ``` --- @@ -167,7 +172,7 @@ gcc --version # Should be 13+ rm -rf build/.qt build/CMakeFiles cmake -S . -B build lupdate src -ts i18n/ro-control_en.ts i18n/ro-control_tr.ts -cmake --build build +cmake --build build --target ro-control_lrelease ``` --- @@ -177,9 +182,8 @@ cmake --build build After making changes, always verify the build passes before submitting a PR: ```bash -cd build -make -j$(nproc) -ctest --output-on-failure +cmake --build build -j$(nproc) +ctest --test-dir build --output-on-failure ``` See [CONTRIBUTING.md](../CONTRIBUTING.md) for the full contribution guide. diff --git a/docs/FEDORA.md b/docs/FEDORA.md new file mode 100644 index 0000000..2698f43 --- /dev/null +++ b/docs/FEDORA.md @@ -0,0 +1,76 @@ +# Fedora Run Guide + +This guide is focused on running `ro-control` on Fedora Workstation/Spin systems. + +## 1) Bootstrap (recommended) + +From the repository root: + +```bash +./scripts/fedora-bootstrap.sh +``` + +Optional flags via environment variables: + +```bash +ENABLE_TESTS=0 BUILD_TYPE=Debug ./scripts/fedora-bootstrap.sh +INSTALL_AFTER_BUILD=1 INSTALL_PREFIX=/usr ./scripts/fedora-bootstrap.sh +``` + +## 2) Manual dependency install (equivalent) + +Build dependencies: + +```bash +sudo dnf install -y \ + cmake \ + extra-cmake-modules \ + gcc-c++ \ + ninja-build \ + qt6-qtbase-devel \ + qt6-qtdeclarative-devel \ + qt6-qttools-devel \ + qt6-qtwayland-devel \ + kf6-qqc2-desktop-style \ + polkit-devel +``` + +Runtime tools used by diagnostics and driver workflows: + +```bash +sudo dnf install -y dnf polkit pciutils mokutil kmod lm_sensors procps-ng +``` + +## 3) Build and run + +```bash +cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTS=ON +cmake --build build -j"$(nproc)" +ctest --test-dir build --output-on-failure +./build/ro-control +``` + +## 4) Install (optional) + +```bash +sudo cmake --install build +``` + +## 5) Driver management prerequisites + +- `ro-control` invokes privileged operations through `pkexec`. +- For proprietary NVIDIA flow, the app enables RPM Fusion and installs + `akmod-nvidia` using `dnf`. +- A reboot is required after install/update/remove flows. +- On Secure Boot systems, kernel module signing policy may still require manual steps. + +## 6) Quick verification + +```bash +./build/ro-control diagnostics --json +./build/ro-control status +``` + +If GPU telemetry is unavailable, verify `nvidia-smi` is present and working. +If CPU temperature is unavailable, verify `lm_sensors` is installed and sensors are exposed on the host. +If RAM telemetry is unavailable, verify `/proc/meminfo` and `free --mebi` are accessible on the host. diff --git a/docs/RELEASE.md b/docs/RELEASE.md index bb29e41..1bafd18 100644 --- a/docs/RELEASE.md +++ b/docs/RELEASE.md @@ -21,6 +21,7 @@ Use this checklist for every production release. - [ ] Update `CHANGELOG.md` with final release notes. - [ ] Ensure AppStream metadata is up to date. - [ ] Refresh translation sources and verify `.ts` files are complete. +- [ ] Run `cmake --build build --target ro-control_lrelease` and confirm shipped locales compile cleanly. - [ ] Smoke-test the app in English and Turkish locales. ## 4. Packaging diff --git a/docs/man/ro-control.1 b/docs/man/ro-control.1 index fae4acc..df8934f 100644 --- a/docs/man/ro-control.1 +++ b/docs/man/ro-control.1 @@ -57,10 +57,10 @@ output as JSON. Use the proprietary NVIDIA driver install path. .TP .B \-\-open-source -Use the open-source Nouveau install path. +Use the NVIDIA open kernel module install path. .TP .B \-\-accept-license -Confirm that the NVIDIA proprietary license has been reviewed. +Confirm that the NVIDIA license has been reviewed for the proprietary install path. .SH EXIT STATUS .TP .B 0 diff --git a/i18n/README.md b/i18n/README.md index 85068c2..28df98a 100644 --- a/i18n/README.md +++ b/i18n/README.md @@ -15,6 +15,7 @@ added as new `ro-control_.ts` files. - QML strings use `qsTr(...)` - C++ strings use `tr(...)` - `main.cpp` loads the best matching `.qm` file from the embedded `/i18n` resource +- English uses the source strings directly and does not require an embedded `.qm` - If no locale-specific translation is found, the app falls back to English ## Updating translations @@ -23,7 +24,7 @@ added as new `ro-control_.ts` files. 2. Reconfigure the build directory with CMake. 3. Refresh translation sources with `lupdate`. 4. Translate in Qt Linguist. -5. Build again so CMake generates updated `.qm` files. +5. Build again so CMake generates updated `.qm` files for shipped locales. Example workflow: diff --git a/i18n/ro-control_de.ts b/i18n/ro-control_de.ts new file mode 100644 index 0000000..d44e420 --- /dev/null +++ b/i18n/ro-control_de.ts @@ -0,0 +1,1573 @@ + + + + + DriverPage + + Driver Management + Driver Management + + + GPU: + GPU: + + + Not detected + Not detected + + + Active driver: + Active driver: + + + Driver version: + Driver version: + + + + + None + None + + + Secure Boot: + Secure Boot: + + + + Repository Setup + + + + + Package Transaction + + + + + Kernel Integration + + + + + Session Finalization + + + + + Update Check + + + + + General + + + + + Installed + + + + + Latest + + + + + GPU Detection + + + + + Detected + + + + + Missing + + + + + No NVIDIA GPU was detected on this system. + + + + + Active Driver + + + + + Session: + + + + + Unknown + Unknown + + + + Installed Version + + + + + Latest available online: + + + + + No pending online package update detected. + + + + + Latest driver found online: + + + + + No online driver catalog has been loaded yet. + + + + + Secure Boot + + + + + Enabled + Enabled + + + + Disabled / Unknown + Disabled / Unknown + + + + Unsigned kernel modules may require manual signing. + + + + + No Secure Boot blocker is currently detected. + + + + + Verification + + + + + Review driver prerequisites before changing packages. + + + + + GPU Ready + + + + + GPU Missing + + + + + Wayland Session + + + + + X11 / Other Session + + + + + Kernel Module Loaded + + + + + Kernel Module Missing + + + + + Wayland sessions automatically need the nvidia-drm.modeset=1 kernel argument. + + + + + X11 sessions require matching userspace packages after install or update. + + + + + Driver Actions + + + + + Use guided actions to install, switch or remove the current stack. + + + + + Source: + + + + + + + Idle + + + + + Phase: + + + + + Running + + + + + + + + + + + + + + Installer + + + + + Installing the proprietary NVIDIA driver (akmod-nvidia)... + Installing the proprietary NVIDIA driver (akmod-nvidia)... + + + Switching to the open-source driver... + Switching to the open-source driver... + + + + Remove Driver + + + + + Removing the NVIDIA driver... + Removing the NVIDIA driver... + + + + Cleaning legacy driver leftovers... + Cleaning legacy driver leftovers... + + + + Search the online package catalog, then download and install a matching driver build. + + + + + Remote Driver Available + + + + + Catalog Not Ready + + + + + + + + + + + Updater + + + + + Searching the online NVIDIA package catalog... + + + + + Install Latest + + + + + Updating NVIDIA driver to the latest online version... + + + + + Downloading and installing the latest online NVIDIA driver... + + + + + Switching NVIDIA driver to selected online version: + + + + + Downloading and installing selected NVIDIA driver version: + + + + + Online repository versions loaded: + + + + + No online repository version list has been loaded yet. + + + + + Apply Latest + + + + + Fallback Open Driver Active + + + + + Fallback Open Driver Inactive + + + + + Elapsed: + + + + + Last Log: + + + + + I reviewed the NVIDIA license terms + + + + + Official NVIDIA license: <a href="%1">%1</a> + + + + + Install NVIDIA Driver + NVIDIA-Treiber installieren + + + + Install Open Kernel Modules + Offene Kernelmodule installieren + + + + Switching to NVIDIA open kernel modules... + + + + + Apply Selected + Auswahl anwenden + + + + Activity Log + Aktivitaetsprotokoll + + + + Operation output is streamed here in real time. + + + + + Clear Log + Protokoll leeren + + + Session type: + Session type: + + + For Wayland sessions, nvidia-drm.modeset=1 is applied automatically. + For Wayland sessions, nvidia-drm.modeset=1 is applied automatically. + + + For X11 sessions, the xorg-x11-drv-nvidia package is verified and installed. + For X11 sessions, the xorg-x11-drv-nvidia package is verified and installed. + + + I accept the license / agreement terms + I accept the license / agreement terms + + + Install Proprietary Driver + Install Proprietary Driver + + + Install Open-Source Driver (Nouveau) + Install Open-Source Driver (Nouveau) + + + + Deep Clean + Deep Clean + + + + Rescan System + + + + + Update Center + + + + + Installed: + + + + + Update Available + + + + + Up to Date + + + + + Check for Updates + Check for Updates + + + Apply Update + Apply Update + + + Latest version: + Latest version: + + + Rescan + Rescan + + + Installed NVIDIA version: + Installed NVIDIA version: + + + + Main + + + ro-Control + ro-Control + + + + Driver Control Center + Treiberzentrale + + + + System Monitor + Systemmonitor + + + + Preferences + Einstellungen + + + + Install, verify and update NVIDIA drivers with guided system checks. + + + + + Track live CPU, GPU and memory telemetry in one place. + + + + + Tune the interface and review diagnostic context before support work. + + + + + Follow System + + + + + Dark Theme + + + + + Light Theme + + + + System Dark + System Dark + + + System Light + System Light + + + + Compact Layout + + + + + Comfort Layout + + + + Theme: System (Dark) + Theme: System (Dark) + + + Theme: System (Light) + Theme: System (Light) + + + Driver + Driver + + + Monitor + Monitor + + + Settings + Settings + + + + MonitorPage + + System Monitoring + System Monitoring + + + + CPU Load + + + + + + + + + Unavailable + + + + + CPU telemetry is currently unavailable. + + + + + GPU Load + + + + + Memory Usage + + + + + Used: + + + + + RAM telemetry is currently unavailable. + + + + + Live Resource Curves + + + + + Quick pulse view for the most important machine resources. + + + + + CPU + CPU + + + + GPU + + + + + Health Summary + + + + + Fast interpretation of the raw telemetry values. + + + + + CPU Busy + + + + + CPU Stable + + + + + GPU Online + + + + + GPU Telemetry Missing + + + + + Memory Pressure + + + + + Memory Stable + + + + + GPU temperature: + + + + + GPU metrics are unavailable. Check driver installation and nvidia-smi accessibility. + + + + + Detailed Signals + + + + + Expanded raw values for support and diagnostics. + + + + + CPU Temperature + + + + + + + Unknown + Unknown + + + + GPU Temperature + + + + + No Live Data + + + + + No NVIDIA GPU was detected on this system. + + + + + GPU detected, but no active driver is loaded. + + + + + Live GPU telemetry is unavailable. Check nvidia-smi and driver access. + + + + + , VRAM + + + + + No NVIDIA GPU is currently detected on this system. + + + + + GPU telemetry is unavailable because the NVIDIA driver is not active. + + + + + VRAM + + + + + RAM Footprint + + + + + Actions + + + + + Trigger a manual refresh when you need a fresh sample. + + + + + Refresh Telemetry + + + + + NVIDIA Path OK + + + + + Check NVIDIA Path + + + + Usage: + Usage: + + + CPU data unavailable + CPU data unavailable + + + + Temperature: + Temperature: + + + GPU (NVIDIA) + GPU (NVIDIA) + + + + NVIDIA GPU + NVIDIA GPU + + + Failed to read data via nvidia-smi + Failed to read data via nvidia-smi + + + Load: + Load: + + + VRAM: + VRAM: + + + + RAM + RAM + + + RAM data unavailable + RAM data unavailable + + + Refresh + Refresh + + + + Refresh interval: + Refresh interval: + + + + NvidiaDetector + + Proprietary (NVIDIA) + Proprietary (NVIDIA) + + + Open Source (Nouveau) + Open Source (Nouveau) + + + + Not Installed / Unknown + Not Installed / Unknown + + + + + None + None + + + GPU: %1 +Driver Version: %2 +Secure Boot: %3 +Session: %4 +NVIDIA Module: %5 +Nouveau: %6 + GPU: %1 +Driver Version: %2 +Secure Boot: %3 +Session: %4 +NVIDIA Module: %5 +Nouveau: %6 + + + + NVIDIA Open Kernel Modules + + + + + NVIDIA Driver + + + + + Fallback Open Driver + + + + + GPU: %1 +Driver Version: %2 +Secure Boot: %3 +Session: %4 +Active Stack: %5 +Fallback Open Driver: %6 + + + + + Enabled + Enabled + + + + Disabled + + + + + Disabled / Unknown + Disabled / Unknown + + + + Unknown + Unknown + + + Loaded + Loaded + + + Not loaded + Not loaded + + + + Active + Active + + + + Inactive + Inactive + + + + NvidiaInstaller + + You must accept the NVIDIA proprietary driver license terms before installation. Detected license: %1 + You must accept the NVIDIA proprietary driver license terms before installation. Detected license: %1 + + + License agreement acceptance is required before installation. + License agreement acceptance is required before installation. + + + + Starting command (attempt %1): %2 + + + + + Command finished (attempt %1, exit %2, %3 ms): %4 + + + + + The proprietary NVIDIA driver is subject to NVIDIA's software license. Review the official NVIDIA license before installation: %1 + + + + + NVIDIA license review confirmation is required before installation. + + + + + Checking RPM Fusion repositories... + Checking RPM Fusion repositories... + + + + Platform version could not be detected. + Platform version could not be detected. + + + + Failed to enable RPM Fusion repositories: + Failed to enable RPM Fusion repositories: + + + + Installing the proprietary NVIDIA driver (akmod-nvidia)... + Installing the proprietary NVIDIA driver (akmod-nvidia)... + + + + Installation failed: + Installation failed: + + + + + Building the kernel module (akmods --force)... + Building the kernel module (akmods --force)... + + + + + Kernel module build failed: + + + + + The proprietary NVIDIA driver was installed successfully. Please restart the system. + The proprietary NVIDIA driver was installed successfully. Please restart the system. + + + + Switching to NVIDIA open kernel modules... + + + + + Failed to remove conflicting NVIDIA kernel packages: + + + + + Open NVIDIA kernel module installation failed: + + + + + unknown error + + + + + NVIDIA open kernel modules were installed successfully. Please restart the system. + + + + Switching to the open-source driver... + Switching to the open-source driver... + + + Failed to remove proprietary packages: + Failed to remove proprietary packages: + + + Open-source driver installation failed: + Open-source driver installation failed: + + + The open-source driver (Nouveau) was installed. Please restart the system. + The open-source driver (Nouveau) was installed. Please restart the system. + + + + Removing the NVIDIA driver... + Removing the NVIDIA driver... + + + + Driver removed successfully. + Driver removed successfully. + + + + Removal failed: + Removal failed: + + + + Cleaning legacy driver leftovers... + Cleaning legacy driver leftovers... + + + + Deep clean failed: + + + + + DNF cache cleanup failed: + + + + + Deep clean completed. + Deep clean completed. + + + Wayland detected: applying nvidia-drm.modeset=1... + Wayland detected: applying nvidia-drm.modeset=1... + + + + Another driver operation is already running. + + + + Unknown + Unknown + + + + Legacy NVIDIA cleanup completed. + + + + + Failed to apply the Wayland kernel parameter: + Failed to apply the Wayland kernel parameter: + + + + X11 detected: checking NVIDIA userspace packages... + X11 detected: checking NVIDIA userspace packages... + + + + Failed to install the X11 NVIDIA package: + Failed to install the X11 NVIDIA package: + + + + NvidiaUpdater + + Updating the NVIDIA driver... + Updating the NVIDIA driver... + + + + Update failed: + Update failed: + + + Rebuilding the kernel module... + Rebuilding the kernel module... + + + Wayland detected: refreshing nvidia-drm.modeset=1... + Wayland detected: refreshing nvidia-drm.modeset=1... + + + + Driver updated successfully. Please restart the system. + Driver updated successfully. Please restart the system. + + + + + dnf not found. + + + + + Online NVIDIA packages were found. You can download and install the driver now. + + + + + Online NVIDIA driver found. Latest remote version: %1 + + + + + No online NVIDIA package catalog was found. RPM Fusion may not be configured yet. + + + + + Update found (version details unavailable). + + + + + Update found: %1 + + + + + Driver is up to date. No new version found. + + + + + Update check failed: %1 + + + + + Starting command (attempt %1): %2 + + + + + Command finished (attempt %1, exit %2, %3 ms): %4 + + + + + Another driver operation is already running. + + + + + Kernel module build failed: + + + + + + + unknown error + + + + + Wayland detected: applying nvidia-drm.modeset=1... + Wayland detected: applying nvidia-drm.modeset=1... + + + + Failed to update the Wayland kernel parameter: + + + + + No available versions found. + + + + + Available versions: %1 + + + + + Starting update check... + + + + + Selected version not found in the repository. + + + + + Updating NVIDIA driver to the latest version... + + + + + Switching NVIDIA driver to selected version: %1 + + + + + Driver is already at the latest available version. + + + + + Selected driver version is already installed. + + + + + Rebuilding kernel module... + + + + + Latest version installed successfully. Please restart the system. + + + + + Selected version applied successfully. Please restart the system. + + + + + SettingsPage + + Settings + Settings + + + + Language + + + + + Changes the application language immediately and keeps the selection for the next launch. + + + + + Compact layout + + + + + Reduces spacing to fit more information on screen. + + + + + Show advanced diagnostics + + + + + Shows verification reports and expanded monitor metrics. + + + + + Language: + + + + + Appearance & Behavior + Darstellung und Verhalten + + + + Control theme, density and operator-focused interface behavior. + + + + + Theme mode + Themenmodus + + + + Choose whether the application follows the OS theme or uses an explicit light or dark shell. + + + + + Theme: Follow System + + + + + Theme: Dark + + + + + Theme: Light + + + + + Compact Active + + + + + Comfort Active + + + + + Advanced Visible + + + + + Advanced Hidden + + + + + Restore the recommended interface defaults if the shell starts to feel cluttered. + + + + + Reset Interface Defaults + + + + + Diagnostics + + + + + Useful runtime context before filing issues or performing support work. + + + + + + Application + + + + + GPU + + + + + Not detected + Not detected + + + + Driver + Driver + + + + Session + + + + + Unknown + Unknown + + + + Use the Driver page to refresh detection before copying any diagnostic context. + + + + + Workflow Guidance + + + + + Recommended order of operations when changing drivers. + + + + + 1. Verify GPU detection and session type. +2. Install or switch the driver stack. +3. Check repository updates. +4. Restart after successful package operations. + + + + + Secure Boot is enabled. Kernel module signing may still be required after package installation. + + + + + No Secure Boot blocker is currently reported by the detector. + + + + + About + About + + + + Project identity and current shell mode. + + + + + Theme + + + + + Follow System + System folgen + + + + Dark + Dunkel + + + + Light + Hell + + + + Effective language + + + + + Layout density + + + + + Advanced diagnostics + + + + Application: + Application: + + + Theme: + Theme: + + + System Dark + System Dark + + + System Light + System Light + + + + Compact + + + + + Comfort + + + + + Visible + + + + + Hidden + + + + + SidebarMenu + + + Driver Management + Treiberverwaltung + + + + System Monitoring + Systemueberwachung + + + + Settings + Einstellungen + + + + ro-Control + ro-Control + + + diff --git a/i18n/ro-control_en.ts b/i18n/ro-control_en.ts index ebcc060..aec2d15 100644 --- a/i18n/ro-control_en.ts +++ b/i18n/ro-control_en.ts @@ -1,117 +1,1538 @@ + DriverPage - Driver ManagementDriver Management - GPU: GPU: - Not detectedNot detected - Active driver: Active driver: - Driver version: Driver version: - NoneNone - Secure Boot: Secure Boot: - EnabledEnabled - Disabled / UnknownDisabled / Unknown - Session type: Session type: - For Wayland sessions, nvidia-drm.modeset=1 is applied automatically.For Wayland sessions, nvidia-drm.modeset=1 is applied automatically. - For X11 sessions, the xorg-x11-drv-nvidia package is verified and installed.For X11 sessions, the xorg-x11-drv-nvidia package is verified and installed. - I accept the license / agreement termsI accept the license / agreement terms - Install Proprietary DriverInstall Proprietary Driver - Install Open-Source Driver (Nouveau)Install Open-Source Driver (Nouveau) - Deep CleanDeep Clean - Check for UpdatesCheck for Updates - Apply UpdateApply Update - Latest version: Latest version: - RescanRescan - Installed NVIDIA version: Installed NVIDIA version: + + Driver Management + Driver Management + + + GPU: + GPU: + + + Not detected + Not detected + + + Active driver: + Active driver: + + + Driver version: + Driver version: + + + + + + + None + None + + + Secure Boot: + Secure Boot: + + + + + Repository Setup + + + + + + Package Transaction + + + + + + Kernel Integration + + + + + + Session Finalization + + + + + + Update Check + + + + + + General + + + + + + GPU Detection + + + + + + Detected + + + + + + Missing + + + + + + No NVIDIA GPU was detected on this system. + + + + + + Active Driver + + + + + + Session: + + + + + + Unknown + Unknown + + + + + Installed Version + + + + + + Latest available online: + + + + + + No pending online package update detected. + + + + + + Latest driver found online: + + + + + + No online driver catalog has been loaded yet. + + + + + + Secure Boot + + + + + + Enabled + Enabled + + + + + Disabled / Unknown + Disabled / Unknown + + + + + Unsigned kernel modules may require manual signing. + + + + + + No Secure Boot blocker is currently detected. + + + + + + Verification + + + + + + Review driver prerequisites before changing packages. + + + + + + GPU Ready + + + + + + GPU Missing + + + + + + Wayland Session + + + + + + X11 / Other Session + + + + + + Nouveau Active + + + + + + Nouveau Inactive + + + + + + Kernel Module Loaded + + + + + + Kernel Module Missing + + + + + + Wayland sessions automatically need the nvidia-drm.modeset=1 kernel argument. + + + + + + X11 sessions require matching userspace packages after install or update. + + + + + + Driver Actions + + + + + + Use guided actions to install, switch or remove the current stack. + + + + + + Source: + + + + + + + + + + Idle + + + + + + Phase: + + + + + + Running + + + + + + I accept the detected license / agreement terms + + + + + + Install Proprietary + + + + + + + + + + + + + + + + + + Installer + + + + + + Installing the proprietary NVIDIA driver (akmod-nvidia)... + Installing the proprietary NVIDIA driver (akmod-nvidia)... + + + + + Install Nouveau + + + + + + Switching to the open-source driver... + Switching to the open-source driver... + + + + + Remove Driver + + + + + + Removing the NVIDIA driver... + Removing the NVIDIA driver... + + + + + Cleaning legacy driver leftovers... + Cleaning legacy driver leftovers... + + + + + Search the online package catalog, then download and install a matching driver build. + + + + + + Remote Driver Available + + + + + + Catalog Not Ready + + + + + + + + + + + + + + Updater + + + + + + Searching the online NVIDIA package catalog... + + + + + + Install Latest + + + + + + Updating NVIDIA driver to the latest online version... + + + + + + Downloading and installing the latest online NVIDIA driver... + + + + + + Switching NVIDIA driver to selected online version: + + + + + + Downloading and installing selected NVIDIA driver version: + + + + + + Online repository versions loaded: + + + + + + No online repository version list has been loaded yet. + + + + + + Apply Latest + + + + + + Apply Selected + + + + + + Activity Log + + + + + + Operation output is streamed here in real time. + + + + + + Clear Log + + + + Session type: + Session type: + + + For Wayland sessions, nvidia-drm.modeset=1 is applied automatically. + For Wayland sessions, nvidia-drm.modeset=1 is applied automatically. + + + For X11 sessions, the xorg-x11-drv-nvidia package is verified and installed. + For X11 sessions, the xorg-x11-drv-nvidia package is verified and installed. + + + I accept the license / agreement terms + I accept the license / agreement terms + + + Install Proprietary Driver + Install Proprietary Driver + + + Install Open-Source Driver (Nouveau) + Install Open-Source Driver (Nouveau) + + + + + Deep Clean + Deep Clean + + + + + Rescan System + + + + + + Update Center + + + + + + Installed: + + + + + + Update Available + + + + + + Up to Date + + + + + + Check for Updates + Check for Updates + + + Apply Update + Apply Update + + + Latest version: + Latest version: + + + Rescan + Rescan + + + Installed NVIDIA version: + Installed NVIDIA version: + Main - ro-Controlro-Control - Theme: System (Dark)Theme: System (Dark) - Theme: System (Light)Theme: System (Light) - DriverDriver - MonitorMonitor - SettingsSettings + + + + ro-Control + ro-Control + + + + + Driver Control Center + + + + + + System Monitor + + + + + + Preferences + + + + + + Install, verify and update NVIDIA drivers with guided system checks. + + + + + + Track live CPU, GPU and memory telemetry in one place. + + + + + + Tune the interface and review diagnostic context before support work. + + + + + + System Dark + System Dark + + + + + System Light + System Light + + + + + Compact Layout + + + + + + Comfort Layout + + + + Theme: System (Dark) + Theme: System (Dark) + + + Theme: System (Light) + Theme: System (Light) + + + Driver + Driver + + + Monitor + Monitor + + + Settings + Settings + MonitorPage - System MonitoringSystem Monitoring - CPUCPU - Usage: Usage: - CPU data unavailableCPU data unavailable - Temperature: Temperature: - GPU (NVIDIA)GPU (NVIDIA) - NVIDIA GPUNVIDIA GPU - Failed to read data via nvidia-smiFailed to read data via nvidia-smi - Load: Load: - VRAM: VRAM: - RAMRAM - RAM data unavailableRAM data unavailable - RefreshRefresh - Refresh interval: Refresh interval: + + System Monitoring + System Monitoring + + + + + CPU Load + + + + + + + + + + Unavailable + + + + + + CPU telemetry is currently unavailable. + + + + + + GPU Load + + + + + + nvidia-smi did not return live GPU telemetry. + + + + + + Memory Usage + + + + + + Used: + + + + + + RAM telemetry is currently unavailable. + + + + + + Live Resource Curves + + + + + + Quick pulse view for the most important machine resources. + + + + + + CPU + CPU + + + + + GPU + + + + + + Health Summary + + + + + + Fast interpretation of the raw telemetry values. + + + + + + CPU Busy + + + + + + CPU Stable + + + + + + GPU Online + + + + + + GPU Telemetry Missing + + + + + + Memory Pressure + + + + + + Memory Stable + + + + + + GPU temperature: + + + + + + GPU metrics are unavailable. Check driver installation and nvidia-smi accessibility. + + + + + + Detailed Signals + + + + + + Expanded raw values for support and diagnostics. + + + + + + CPU Temperature + + + + + + + + + + + + Unknown + Unknown + + + + + GPU Temperature + + + + + + VRAM + + + + + + RAM Footprint + + + + + + Actions + + + + + + Trigger a manual refresh when you need a fresh sample. + + + + + + Refresh Telemetry + + + + + + NVIDIA Path OK + + + + + + Check NVIDIA Path + + + + Usage: + Usage: + + + CPU data unavailable + CPU data unavailable + + + + + Temperature: + Temperature: + + + GPU (NVIDIA) + GPU (NVIDIA) + + + + + NVIDIA GPU + NVIDIA GPU + + + Failed to read data via nvidia-smi + Failed to read data via nvidia-smi + + + Load: + Load: + + + VRAM: + VRAM: + + + + + RAM + RAM + + + RAM data unavailable + RAM data unavailable + + + Refresh + Refresh + + + + + Refresh interval: + Refresh interval: + NvidiaDetector - Proprietary (NVIDIA)Proprietary (NVIDIA) - Open Source (Nouveau)Open Source (Nouveau) - Not Installed / UnknownNot Installed / Unknown - GPU: %1 + + + Proprietary (NVIDIA) + Proprietary (NVIDIA) + + + + Open Source (Nouveau) + Open Source (Nouveau) + + + + Not Installed / Unknown + Not Installed / Unknown + + + + + None + None + + + + GPU: %1 Driver Version: %2 Secure Boot: %3 Session: %4 NVIDIA Module: %5 -Nouveau: %6GPU: %1 +Nouveau: %6 + GPU: %1 Driver Version: %2 Secure Boot: %3 Session: %4 NVIDIA Module: %5 -Nouveau: %6 - UnknownUnknown - LoadedLoaded - Not loadedNot loaded - ActiveActive - InactiveInactive +Nouveau: %6 + + + + Enabled + Enabled + + + + Disabled + + + + + Disabled / Unknown + Disabled / Unknown + + + + Unknown + Unknown + + + + Loaded + Loaded + + + + Not loaded + Not loaded + + + + Active + Active + + + + Inactive + Inactive + NvidiaInstaller - You must accept the NVIDIA proprietary driver license terms before installation. Detected license: %1You must accept the NVIDIA proprietary driver license terms before installation. Detected license: %1 - License agreement acceptance is required before installation.License agreement acceptance is required before installation. - Checking RPM Fusion repositories...Checking RPM Fusion repositories... - Platform version could not be detected.Platform version could not be detected. - Failed to enable RPM Fusion repositories: Failed to enable RPM Fusion repositories: - Installing the proprietary NVIDIA driver (akmod-nvidia)...Installing the proprietary NVIDIA driver (akmod-nvidia)... - Installation failed: Installation failed: - Building the kernel module (akmods --force)...Building the kernel module (akmods --force)... - The proprietary NVIDIA driver was installed successfully. Please restart the system.The proprietary NVIDIA driver was installed successfully. Please restart the system. - Switching to the open-source driver...Switching to the open-source driver... - Failed to remove proprietary packages: Failed to remove proprietary packages: - Open-source driver installation failed: Open-source driver installation failed: - The open-source driver (Nouveau) was installed. Please restart the system.The open-source driver (Nouveau) was installed. Please restart the system. - Removing the NVIDIA driver...Removing the NVIDIA driver... - Driver removed successfully.Driver removed successfully. - Removal failed: Removal failed: - Cleaning legacy driver leftovers...Cleaning legacy driver leftovers... - Deep clean completed.Deep clean completed. - Wayland detected: applying nvidia-drm.modeset=1...Wayland detected: applying nvidia-drm.modeset=1... - Failed to apply the Wayland kernel parameter: Failed to apply the Wayland kernel parameter: - X11 detected: checking NVIDIA userspace packages...X11 detected: checking NVIDIA userspace packages... - Failed to install the X11 NVIDIA package: Failed to install the X11 NVIDIA package: + + + You must accept the NVIDIA proprietary driver license terms before installation. Detected license: %1 + You must accept the NVIDIA proprietary driver license terms before installation. Detected license: %1 + + + + License agreement acceptance is required before installation. + License agreement acceptance is required before installation. + + + + Checking RPM Fusion repositories... + Checking RPM Fusion repositories... + + + + Platform version could not be detected. + Platform version could not be detected. + + + + Failed to enable RPM Fusion repositories: + Failed to enable RPM Fusion repositories: + + + + Installing the proprietary NVIDIA driver (akmod-nvidia)... + Installing the proprietary NVIDIA driver (akmod-nvidia)... + + + + Installation failed: + Installation failed: + + + + Building the kernel module (akmods --force)... + Building the kernel module (akmods --force)... + + + + The proprietary NVIDIA driver was installed successfully. Please restart the system. + The proprietary NVIDIA driver was installed successfully. Please restart the system. + + + + Switching to the open-source driver... + Switching to the open-source driver... + + + + Failed to remove proprietary packages: + Failed to remove proprietary packages: + + + + Open-source driver installation failed: + Open-source driver installation failed: + + + + The open-source driver (Nouveau) was installed. Please restart the system. + The open-source driver (Nouveau) was installed. Please restart the system. + + + + Removing the NVIDIA driver... + Removing the NVIDIA driver... + + + + Driver removed successfully. + Driver removed successfully. + + + + Removal failed: + Removal failed: + + + + Cleaning legacy driver leftovers... + Cleaning legacy driver leftovers... + + + + Deep clean failed: + + + + + DNF cache cleanup failed: + + + + + Deep clean completed. + Deep clean completed. + + + Wayland detected: applying nvidia-drm.modeset=1... + Wayland detected: applying nvidia-drm.modeset=1... + + + + Another driver operation is already running. + + + + + Unknown + Unknown + + + + Legacy NVIDIA cleanup completed. + + + + + Failed to apply the Wayland kernel parameter: + Failed to apply the Wayland kernel parameter: + + + + X11 detected: checking NVIDIA userspace packages... + X11 detected: checking NVIDIA userspace packages... + + + + Failed to install the X11 NVIDIA package: + Failed to install the X11 NVIDIA package: + NvidiaUpdater - Updating the NVIDIA driver...Updating the NVIDIA driver... - Update failed: Update failed: - Rebuilding the kernel module...Rebuilding the kernel module... - Wayland detected: refreshing nvidia-drm.modeset=1...Wayland detected: refreshing nvidia-drm.modeset=1... - Driver updated successfully. Please restart the system.Driver updated successfully. Please restart the system. + + Updating the NVIDIA driver... + Updating the NVIDIA driver... + + + + Update failed: + Update failed: + + + Rebuilding the kernel module... + Rebuilding the kernel module... + + + Wayland detected: refreshing nvidia-drm.modeset=1... + Wayland detected: refreshing nvidia-drm.modeset=1... + + + + Driver updated successfully. Please restart the system. + Driver updated successfully. Please restart the system. + + + + + dnf not found. + + + + + Online NVIDIA packages were found. You can download and install the driver now. + + + + + Online NVIDIA driver found. Latest remote version: %1 + + + + + No online NVIDIA package catalog was found. RPM Fusion may not be configured yet. + + + + + Update found (version details unavailable). + + + + + Update found: %1 + + + + + Driver is up to date. No new version found. + + + + + Update check failed: %1 + + + + + Another driver operation is already running. + + + + + Kernel module build failed: + + + + + + + unknown error + + + + + Wayland detected: applying nvidia-drm.modeset=1... + Wayland detected: applying nvidia-drm.modeset=1... + + + + Failed to update the Wayland kernel parameter: + + + + + No available versions found. + + + + + Available versions: %1 + + + + + Starting update check... + + + + + Selected version not found in the repository. + + + + + Updating NVIDIA driver to the latest version... + + + + + Switching NVIDIA driver to selected version: %1 + + + + + Rebuilding kernel module... + + + + + Latest version installed successfully. Please restart the system. + + + + + Selected version applied successfully. Please restart the system. + + SettingsPage - SettingsSettings - AboutAbout - Application: Application: - Theme: Theme: - System DarkSystem Dark - System LightSystem Light + + Settings + Settings + + + + + Interface + + + + + + Tune the shell density and how much operational detail the app exposes. + + + + + + Language + + + + + + Changes the application language immediately and keeps the selection for the next launch. + + + + + + Compact layout + + + + + + Reduces spacing to fit more information on screen. + + + + + + Show advanced diagnostics + + + + + + Shows verification reports and expanded monitor metrics. + + + + + + Language: + + + + + + System Dark Theme + + + + + + System Light Theme + + + + + + Compact Active + + + + + + Comfort Active + + + + + + Diagnostics + + + + + + Useful runtime context before filing issues or performing support work. + + + + + + Application + + + + + + GPU + + + + + + Not detected + Not detected + + + + + Driver + Driver + + + + + Session + + + + + + Unknown + Unknown + + + + + Use the Driver page to refresh detection before copying any diagnostic context. + + + + + + Workflow Guidance + + + + + + Recommended order of operations when changing drivers. + + + + + + 1. Verify GPU detection and session type. +2. Install or switch the driver stack. +3. Check repository updates. +4. Restart after successful package operations. + + + + + + Secure Boot is enabled. Kernel module signing may still be required after package installation. + + + + + + No Secure Boot blocker is currently reported by the detector. + + + + + + About + About + + + + + Project identity and current shell mode. + + + + + + Application: + Application: + + + + + Theme: + Theme: + + + + + System Dark + System Dark + + + + + System Light + System Light + + + + + Layout density: + + + + + + Compact + + + + + + Comfort + + + + + + Advanced diagnostics: + + + + + + Visible + + + + + + Hidden + + + + + SidebarMenu + + + + Driver Management + Driver Management + + + + + System Monitoring + System Monitoring + + + + + Settings + Settings + + + + + ro-Control + ro-Control + diff --git a/i18n/ro-control_es.ts b/i18n/ro-control_es.ts new file mode 100644 index 0000000..0b2ff9a --- /dev/null +++ b/i18n/ro-control_es.ts @@ -0,0 +1,1573 @@ + + + + + DriverPage + + Driver Management + Driver Management + + + GPU: + GPU: + + + Not detected + Not detected + + + Active driver: + Active driver: + + + Driver version: + Driver version: + + + + + None + None + + + Secure Boot: + Secure Boot: + + + + Repository Setup + + + + + Package Transaction + + + + + Kernel Integration + + + + + Session Finalization + + + + + Update Check + + + + + General + + + + + Installed + + + + + Latest + + + + + GPU Detection + + + + + Detected + + + + + Missing + + + + + No NVIDIA GPU was detected on this system. + + + + + Active Driver + + + + + Session: + + + + + Unknown + Unknown + + + + Installed Version + + + + + Latest available online: + + + + + No pending online package update detected. + + + + + Latest driver found online: + + + + + No online driver catalog has been loaded yet. + + + + + Secure Boot + + + + + Enabled + Enabled + + + + Disabled / Unknown + Disabled / Unknown + + + + Unsigned kernel modules may require manual signing. + + + + + No Secure Boot blocker is currently detected. + + + + + Verification + + + + + Review driver prerequisites before changing packages. + + + + + GPU Ready + + + + + GPU Missing + + + + + Wayland Session + + + + + X11 / Other Session + + + + + Kernel Module Loaded + + + + + Kernel Module Missing + + + + + Wayland sessions automatically need the nvidia-drm.modeset=1 kernel argument. + + + + + X11 sessions require matching userspace packages after install or update. + + + + + Driver Actions + + + + + Use guided actions to install, switch or remove the current stack. + + + + + Source: + + + + + + + Idle + + + + + Phase: + + + + + Running + + + + + + + + + + + + + + Installer + + + + + Installing the proprietary NVIDIA driver (akmod-nvidia)... + Installing the proprietary NVIDIA driver (akmod-nvidia)... + + + Switching to the open-source driver... + Switching to the open-source driver... + + + + Remove Driver + + + + + Removing the NVIDIA driver... + Removing the NVIDIA driver... + + + + Cleaning legacy driver leftovers... + Cleaning legacy driver leftovers... + + + + Search the online package catalog, then download and install a matching driver build. + + + + + Remote Driver Available + + + + + Catalog Not Ready + + + + + + + + + + + Updater + + + + + Searching the online NVIDIA package catalog... + + + + + Install Latest + + + + + Updating NVIDIA driver to the latest online version... + + + + + Downloading and installing the latest online NVIDIA driver... + + + + + Switching NVIDIA driver to selected online version: + + + + + Downloading and installing selected NVIDIA driver version: + + + + + Online repository versions loaded: + + + + + No online repository version list has been loaded yet. + + + + + Apply Latest + + + + + Fallback Open Driver Active + + + + + Fallback Open Driver Inactive + + + + + Elapsed: + + + + + Last Log: + + + + + I reviewed the NVIDIA license terms + + + + + Official NVIDIA license: <a href="%1">%1</a> + + + + + Install NVIDIA Driver + Instalar controlador NVIDIA + + + + Install Open Kernel Modules + Instalar modulos de kernel abiertos + + + + Switching to NVIDIA open kernel modules... + + + + + Apply Selected + Aplicar seleccionada + + + + Activity Log + Registro de actividad + + + + Operation output is streamed here in real time. + + + + + Clear Log + Limpiar registro + + + Session type: + Session type: + + + For Wayland sessions, nvidia-drm.modeset=1 is applied automatically. + For Wayland sessions, nvidia-drm.modeset=1 is applied automatically. + + + For X11 sessions, the xorg-x11-drv-nvidia package is verified and installed. + For X11 sessions, the xorg-x11-drv-nvidia package is verified and installed. + + + I accept the license / agreement terms + I accept the license / agreement terms + + + Install Proprietary Driver + Install Proprietary Driver + + + Install Open-Source Driver (Nouveau) + Install Open-Source Driver (Nouveau) + + + + Deep Clean + Deep Clean + + + + Rescan System + + + + + Update Center + + + + + Installed: + + + + + Update Available + + + + + Up to Date + + + + + Check for Updates + Check for Updates + + + Apply Update + Apply Update + + + Latest version: + Latest version: + + + Rescan + Rescan + + + Installed NVIDIA version: + Installed NVIDIA version: + + + + Main + + + ro-Control + ro-Control + + + + Driver Control Center + Centro de Controladores + + + + System Monitor + Monitor del Sistema + + + + Preferences + Preferencias + + + + Install, verify and update NVIDIA drivers with guided system checks. + + + + + Track live CPU, GPU and memory telemetry in one place. + + + + + Tune the interface and review diagnostic context before support work. + + + + + Follow System + + + + + Dark Theme + + + + + Light Theme + + + + System Dark + System Dark + + + System Light + System Light + + + + Compact Layout + + + + + Comfort Layout + + + + Theme: System (Dark) + Theme: System (Dark) + + + Theme: System (Light) + Theme: System (Light) + + + Driver + Driver + + + Monitor + Monitor + + + Settings + Settings + + + + MonitorPage + + System Monitoring + System Monitoring + + + + CPU Load + + + + + + + + + Unavailable + + + + + CPU telemetry is currently unavailable. + + + + + GPU Load + + + + + Memory Usage + + + + + Used: + + + + + RAM telemetry is currently unavailable. + + + + + Live Resource Curves + + + + + Quick pulse view for the most important machine resources. + + + + + CPU + CPU + + + + GPU + + + + + Health Summary + + + + + Fast interpretation of the raw telemetry values. + + + + + CPU Busy + + + + + CPU Stable + + + + + GPU Online + + + + + GPU Telemetry Missing + + + + + Memory Pressure + + + + + Memory Stable + + + + + GPU temperature: + + + + + GPU metrics are unavailable. Check driver installation and nvidia-smi accessibility. + + + + + Detailed Signals + + + + + Expanded raw values for support and diagnostics. + + + + + CPU Temperature + + + + + + + Unknown + Unknown + + + + GPU Temperature + + + + + No Live Data + + + + + No NVIDIA GPU was detected on this system. + + + + + GPU detected, but no active driver is loaded. + + + + + Live GPU telemetry is unavailable. Check nvidia-smi and driver access. + + + + + , VRAM + + + + + No NVIDIA GPU is currently detected on this system. + + + + + GPU telemetry is unavailable because the NVIDIA driver is not active. + + + + + VRAM + + + + + RAM Footprint + + + + + Actions + + + + + Trigger a manual refresh when you need a fresh sample. + + + + + Refresh Telemetry + + + + + NVIDIA Path OK + + + + + Check NVIDIA Path + + + + Usage: + Usage: + + + CPU data unavailable + CPU data unavailable + + + + Temperature: + Temperature: + + + GPU (NVIDIA) + GPU (NVIDIA) + + + + NVIDIA GPU + NVIDIA GPU + + + Failed to read data via nvidia-smi + Failed to read data via nvidia-smi + + + Load: + Load: + + + VRAM: + VRAM: + + + + RAM + RAM + + + RAM data unavailable + RAM data unavailable + + + Refresh + Refresh + + + + Refresh interval: + Refresh interval: + + + + NvidiaDetector + + Proprietary (NVIDIA) + Proprietary (NVIDIA) + + + Open Source (Nouveau) + Open Source (Nouveau) + + + + Not Installed / Unknown + Not Installed / Unknown + + + + + None + None + + + GPU: %1 +Driver Version: %2 +Secure Boot: %3 +Session: %4 +NVIDIA Module: %5 +Nouveau: %6 + GPU: %1 +Driver Version: %2 +Secure Boot: %3 +Session: %4 +NVIDIA Module: %5 +Nouveau: %6 + + + + NVIDIA Open Kernel Modules + + + + + NVIDIA Driver + + + + + Fallback Open Driver + + + + + GPU: %1 +Driver Version: %2 +Secure Boot: %3 +Session: %4 +Active Stack: %5 +Fallback Open Driver: %6 + + + + + Enabled + Enabled + + + + Disabled + + + + + Disabled / Unknown + Disabled / Unknown + + + + Unknown + Unknown + + + Loaded + Loaded + + + Not loaded + Not loaded + + + + Active + Active + + + + Inactive + Inactive + + + + NvidiaInstaller + + You must accept the NVIDIA proprietary driver license terms before installation. Detected license: %1 + You must accept the NVIDIA proprietary driver license terms before installation. Detected license: %1 + + + License agreement acceptance is required before installation. + License agreement acceptance is required before installation. + + + + Starting command (attempt %1): %2 + + + + + Command finished (attempt %1, exit %2, %3 ms): %4 + + + + + The proprietary NVIDIA driver is subject to NVIDIA's software license. Review the official NVIDIA license before installation: %1 + + + + + NVIDIA license review confirmation is required before installation. + + + + + Checking RPM Fusion repositories... + Checking RPM Fusion repositories... + + + + Platform version could not be detected. + Platform version could not be detected. + + + + Failed to enable RPM Fusion repositories: + Failed to enable RPM Fusion repositories: + + + + Installing the proprietary NVIDIA driver (akmod-nvidia)... + Installing the proprietary NVIDIA driver (akmod-nvidia)... + + + + Installation failed: + Installation failed: + + + + + Building the kernel module (akmods --force)... + Building the kernel module (akmods --force)... + + + + + Kernel module build failed: + + + + + The proprietary NVIDIA driver was installed successfully. Please restart the system. + The proprietary NVIDIA driver was installed successfully. Please restart the system. + + + + Switching to NVIDIA open kernel modules... + + + + + Failed to remove conflicting NVIDIA kernel packages: + + + + + Open NVIDIA kernel module installation failed: + + + + + unknown error + + + + + NVIDIA open kernel modules were installed successfully. Please restart the system. + + + + Switching to the open-source driver... + Switching to the open-source driver... + + + Failed to remove proprietary packages: + Failed to remove proprietary packages: + + + Open-source driver installation failed: + Open-source driver installation failed: + + + The open-source driver (Nouveau) was installed. Please restart the system. + The open-source driver (Nouveau) was installed. Please restart the system. + + + + Removing the NVIDIA driver... + Removing the NVIDIA driver... + + + + Driver removed successfully. + Driver removed successfully. + + + + Removal failed: + Removal failed: + + + + Cleaning legacy driver leftovers... + Cleaning legacy driver leftovers... + + + + Deep clean failed: + + + + + DNF cache cleanup failed: + + + + + Deep clean completed. + Deep clean completed. + + + Wayland detected: applying nvidia-drm.modeset=1... + Wayland detected: applying nvidia-drm.modeset=1... + + + + Another driver operation is already running. + + + + Unknown + Unknown + + + + Legacy NVIDIA cleanup completed. + + + + + Failed to apply the Wayland kernel parameter: + Failed to apply the Wayland kernel parameter: + + + + X11 detected: checking NVIDIA userspace packages... + X11 detected: checking NVIDIA userspace packages... + + + + Failed to install the X11 NVIDIA package: + Failed to install the X11 NVIDIA package: + + + + NvidiaUpdater + + Updating the NVIDIA driver... + Updating the NVIDIA driver... + + + + Update failed: + Update failed: + + + Rebuilding the kernel module... + Rebuilding the kernel module... + + + Wayland detected: refreshing nvidia-drm.modeset=1... + Wayland detected: refreshing nvidia-drm.modeset=1... + + + + Driver updated successfully. Please restart the system. + Driver updated successfully. Please restart the system. + + + + + dnf not found. + + + + + Online NVIDIA packages were found. You can download and install the driver now. + + + + + Online NVIDIA driver found. Latest remote version: %1 + + + + + No online NVIDIA package catalog was found. RPM Fusion may not be configured yet. + + + + + Update found (version details unavailable). + + + + + Update found: %1 + + + + + Driver is up to date. No new version found. + + + + + Update check failed: %1 + + + + + Starting command (attempt %1): %2 + + + + + Command finished (attempt %1, exit %2, %3 ms): %4 + + + + + Another driver operation is already running. + + + + + Kernel module build failed: + + + + + + + unknown error + + + + + Wayland detected: applying nvidia-drm.modeset=1... + Wayland detected: applying nvidia-drm.modeset=1... + + + + Failed to update the Wayland kernel parameter: + + + + + No available versions found. + + + + + Available versions: %1 + + + + + Starting update check... + + + + + Selected version not found in the repository. + + + + + Updating NVIDIA driver to the latest version... + + + + + Switching NVIDIA driver to selected version: %1 + + + + + Driver is already at the latest available version. + + + + + Selected driver version is already installed. + + + + + Rebuilding kernel module... + + + + + Latest version installed successfully. Please restart the system. + + + + + Selected version applied successfully. Please restart the system. + + + + + SettingsPage + + Settings + Settings + + + + Language + + + + + Changes the application language immediately and keeps the selection for the next launch. + + + + + Compact layout + + + + + Reduces spacing to fit more information on screen. + + + + + Show advanced diagnostics + + + + + Shows verification reports and expanded monitor metrics. + + + + + Language: + + + + + Appearance & Behavior + Apariencia y comportamiento + + + + Control theme, density and operator-focused interface behavior. + + + + + Theme mode + Modo de tema + + + + Choose whether the application follows the OS theme or uses an explicit light or dark shell. + + + + + Theme: Follow System + + + + + Theme: Dark + + + + + Theme: Light + + + + + Compact Active + + + + + Comfort Active + + + + + Advanced Visible + + + + + Advanced Hidden + + + + + Restore the recommended interface defaults if the shell starts to feel cluttered. + + + + + Reset Interface Defaults + + + + + Diagnostics + + + + + Useful runtime context before filing issues or performing support work. + + + + + + Application + + + + + GPU + + + + + Not detected + Not detected + + + + Driver + Driver + + + + Session + + + + + Unknown + Unknown + + + + Use the Driver page to refresh detection before copying any diagnostic context. + + + + + Workflow Guidance + + + + + Recommended order of operations when changing drivers. + + + + + 1. Verify GPU detection and session type. +2. Install or switch the driver stack. +3. Check repository updates. +4. Restart after successful package operations. + + + + + Secure Boot is enabled. Kernel module signing may still be required after package installation. + + + + + No Secure Boot blocker is currently reported by the detector. + + + + + About + About + + + + Project identity and current shell mode. + + + + + Theme + + + + + Follow System + Seguir al sistema + + + + Dark + Oscuro + + + + Light + Claro + + + + Effective language + + + + + Layout density + + + + + Advanced diagnostics + + + + Application: + Application: + + + Theme: + Theme: + + + System Dark + System Dark + + + System Light + System Light + + + + Compact + + + + + Comfort + + + + + Visible + + + + + Hidden + + + + + SidebarMenu + + + Driver Management + Gestion de Controladores + + + + System Monitoring + Supervision del Sistema + + + + Settings + Ajustes + + + + ro-Control + ro-Control + + + diff --git a/i18n/ro-control_tr.ts b/i18n/ro-control_tr.ts index b25da92..e96400f 100644 --- a/i18n/ro-control_tr.ts +++ b/i18n/ro-control_tr.ts @@ -1,117 +1,1661 @@ + DriverPage - Driver ManagementSürücü Yönetimi - GPU: GPU: - Not detectedTespit edilmedi - Active driver: Aktif sürücü: - Driver version: Sürücü sürümü: - NoneYok - Secure Boot: Secure Boot: - EnabledAçık - Disabled / UnknownKapalı / Bilinmiyor - Session type: Oturum türü: - For Wayland sessions, nvidia-drm.modeset=1 is applied automatically.Wayland oturumlarında nvidia-drm.modeset=1 otomatik uygulanır. - For X11 sessions, the xorg-x11-drv-nvidia package is verified and installed.X11 oturumlarında xorg-x11-drv-nvidia paketi doğrulanır ve kurulur. - I accept the license / agreement termsLisans / sözleşme koşullarını kabul ediyorum - Install Proprietary DriverKapalı Kaynak Sürücüyü Kur - Install Open-Source Driver (Nouveau)Açık Kaynak Sürücüyü Kur (Nouveau) - Deep CleanDerin Temizlik - Check for UpdatesGüncellemeleri Kontrol Et - Apply UpdateGüncellemeyi Uygula - Latest version: En son sürüm: - RescanYeniden Tara - Installed NVIDIA version: Kurulu NVIDIA sürümü: + + Driver Management + Sürücü Yönetimi + + + GPU: + GPU: + + + Not detected + Tespit edilmedi + + + Active driver: + Aktif sürücü: + + + Driver version: + Sürücü sürümü: + + + + + None + Yok + + + Secure Boot: + Secure Boot: + + + + Repository Setup + Depo Kurulumu + + + + Package Transaction + Paket İşlemi + + + + Kernel Integration + Kernel Entegrasyonu + + + + Session Finalization + Oturum Sonlandırma + + + + Update Check + Güncelleme Kontrolü + + + + General + Genel + + + + Installed + Kurulu + + + + Latest + En Güncel + + + + GPU Detection + GPU Tespiti + + + + Detected + Tespit Edildi + + + + Missing + Bulunamadı + + + + No NVIDIA GPU was detected on this system. + Bu sistemde NVIDIA GPU tespit edilemedi. + + + + Active Driver + Aktif Sürücü + + + + Session: + Oturum: + + + + Unknown + Bilinmiyor + + + + Installed Version + Kurulu Sürüm + + + + Fallback Open Driver Active + Yedek Acik Surucu Etkin + + + + Fallback Open Driver Inactive + Yedek Acik Surucu Pasif + + + + I reviewed the NVIDIA license terms + NVIDIA lisans kosullarini inceledim + + + + Official NVIDIA license: <a href="%1">%1</a> + Resmi NVIDIA lisansi: <a href="%1">%1</a> + + + + Install NVIDIA Driver + NVIDIA Surucusunu Kur + + + + Install Open Kernel Modules + Acik Kernel Modullerini Kur + + + + Switching to NVIDIA open kernel modules... + NVIDIA acik kernel modullerine geciliyor... + + + Latest available: + En son sürüm: + + + No pending package update detected. + Bekleyen paket güncellemesi bulunamadı. + + + + Latest available online: + Çevrimiçi bulunan en güncel sürüm: + + + + No pending online package update detected. + Bekleyen çevrimiçi paket güncellemesi tespit edilmedi. + + + + Latest driver found online: + Çevrimiçi bulunan en güncel sürücü: + + + + No online driver catalog has been loaded yet. + Henüz çevrimiçi sürücü kataloğu yüklenmedi. + + + + Secure Boot + Güvenli Önyükleme (Secure Boot) + + + + Enabled + Açık + + + + Disabled / Unknown + Kapalı / Bilinmiyor + + + + Unsigned kernel modules may require manual signing. + İmzasız kernel modüllerinin manuel olarak imzalanması gerekebilir. + + + + No Secure Boot blocker is currently detected. + Şu anda herhangi bir Secure Boot engeli tespit edilmedi. + + + + Verification + Doğrulama + + + + Review driver prerequisites before changing packages. + Paketleri değiştirmeden önce sürücü ön koşullarını gözden geçirin. + + + + GPU Ready + GPU Hazır + + + + GPU Missing + GPU Bulunamadı + + + + Wayland Session + Wayland Oturumu + + + + X11 / Other Session + X11 / Diğer Oturum + + + Nouveau Active + Nouveau Aktif + + + Nouveau Inactive + Nouveau Pasif + + + + Kernel Module Loaded + Kernel Modülü Yüklü + + + + Kernel Module Missing + Kernel Modülü Eksik + + + + Wayland sessions automatically need the nvidia-drm.modeset=1 kernel argument. + Wayland oturumları otomatik olarak nvidia-drm.modeset=1 çekirdek argümanına ihtiyaç duyar. + + + + X11 sessions require matching userspace packages after install or update. + X11 oturumları, kurulum veya güncelleme sonrası eşleşen kullanıcı alanı paketleri gerektirir. + + + + Driver Actions + Sürücü İşlemleri + + + + Use guided actions to install, switch or remove the current stack. + Mevcut yığını kurmak, değiştirmek veya kaldırmak için rehberli işlemleri kullanın. + + + + Source: + Kaynak: + + + + + + Idle + Boşta + + + + Phase: + Aşama: + + + + Running + Çalışıyor + + + + Elapsed: + Geçen Süre: + + + + Last Log: + Son Günlük: + + + I accept the detected license / agreement terms + Tespit edilen lisans / sözleşme koşullarını kabul ediyorum + + + Install Proprietary + Sahipli Sürücüyü Kur + + + + + + + + + + + + + Installer + Kurucu + + + + Installing the proprietary NVIDIA driver (akmod-nvidia)... + Kapalı kaynak NVIDIA sürücüsü kuruluyor (akmod-nvidia)... + + + Install Nouveau + Nouveau Kur (Açık Kaynak) + + + Switching to the open-source driver... + Açık kaynak sürücüye geçiliyor... + + + + Remove Driver + Sürücüyü Kaldır + + + + Removing the NVIDIA driver... + NVIDIA sürücüsü kaldırılıyor... + + + + Cleaning legacy driver leftovers... + Eski sürücü kalıntıları temizleniyor... + + + + Search the online package catalog, then download and install a matching driver build. + Çevrimiçi paket kataloğunu tarayın, ardından uygun sürücü derlemesini indirip kurun. + + + + Remote Driver Available + Uzak Sürücü Mevcut + + + + Catalog Not Ready + Katalog Hazır Değil + + + + + + + + + + Updater + Güncelleyici + + + + Searching the online NVIDIA package catalog... + Çevrimiçi NVIDIA paket kataloğu aranıyor... + + + + Install Latest + En Günceli Kur + + + + Updating NVIDIA driver to the latest online version... + NVIDIA sürücüsü çevrimiçi en güncel sürüme güncelleniyor... + + + + Downloading and installing the latest online NVIDIA driver... + En güncel çevrimiçi NVIDIA sürücüsü indiriliyor ve kuruluyor... + + + + Switching NVIDIA driver to selected online version: + NVIDIA sürücüsü seçilen çevrimiçi sürüme geçiriliyor: + + + + Downloading and installing selected NVIDIA driver version: + Seçilen NVIDIA sürücü sürümü indiriliyor ve kuruluyor: + + + + Online repository versions loaded: + Çevrimiçi depo sürümleri yüklendi: + + + + No online repository version list has been loaded yet. + Henüz çevrimiçi depo sürüm listesi yüklenmedi. + + + Starting update check... + Güncelleme denetimi başlatılıyor... + + + + Apply Latest + En Son Sürümü Uygula + + + Updating NVIDIA driver to the latest version... + NVIDIA sürücüsü en yeni sürüme güncelleniyor... + + + + Apply Selected + Seçileni Uygula + + + Repository versions loaded: + Yüklenen repo sürümleri: + + + No repository version list has been loaded yet. + Henüz hiçbir repo sürüm listesi yüklenmedi. + + + + Activity Log + Aktivite Günlüğü + + + + Operation output is streamed here in real time. + İşlem çıktıları burada gerçek zamanlı olarak gösterilir. + + + + Clear Log + Günlüğü Temizle + + + Session type: + Oturum türü: + + + For Wayland sessions, nvidia-drm.modeset=1 is applied automatically. + Wayland oturumlarında nvidia-drm.modeset=1 otomatik uygulanır. + + + For X11 sessions, the xorg-x11-drv-nvidia package is verified and installed. + X11 oturumlarında xorg-x11-drv-nvidia paketi doğrulanır ve kurulur. + + + I accept the license / agreement terms + Lisans / sözleşme koşullarını kabul ediyorum + + + Install Proprietary Driver + Kapalı Kaynak Sürücüyü Kur + + + Install Open-Source Driver (Nouveau) + Açık Kaynak Sürücüyü Kur (Nouveau) + + + + Deep Clean + Derin Temizlik + + + + Rescan System + Sistemi Yeniden Tara + + + + Update Center + Güncelleme Merkezi + + + Check the repository version and pin a specific build when required. + Repo sürümünü kontrol edin ve gerektiğinde belirli bir yapıyı sabitleyin. + + + + Installed: + Kurulu: + + + + Update Available + Güncelleme Mevcut + + + + Up to Date + Güncel + + + + Check for Updates + Güncellemeleri Kontrol Et + + + Apply Update + Güncellemeyi Uygula + + + Latest version: + En son sürüm: + + + Rescan + Yeniden Tara + + + Installed NVIDIA version: + Kurulu NVIDIA sürümü: + Main - ro-Controlro-Control - Theme: System (Dark)Tema: Sistem (Koyu) - Theme: System (Light)Tema: Sistem (Açık) - DriverSürücü - Monitorİzleme - SettingsAyarlar + + + ro-Control + ro-Control + + + + Driver Control Center + Sürücü Kontrol Merkezi + + + + System Monitor + Sistem İzleyici + + + + Preferences + Tercihler + + + + Install, verify and update NVIDIA drivers with guided system checks. + NVIDIA sürücülerini, rehberli sistem kontrolleriyle kurun, doğrulayın ve güncelleyin. + + + + Track live CPU, GPU and memory telemetry in one place. + Canlı CPU, GPU ve bellek telemetrisini tek bir yerden izleyin. + + + + Tune the interface and review diagnostic context before support work. + Arayüz yoğunluğunu ayarlayın ve destek çalışmalarından önce sistem raporunu gözden geçirin. + + + + Follow System + Sistemi İzle + + + + Dark Theme + Koyu Tema + + + + Light Theme + Açık Tema + + + System Dark + Sistem (Koyu) + + + System Light + Sistem (Açık) + + + + Compact Layout + Sıkı Görünüm + + + + Comfort Layout + Rahat Görünüm + + + Theme: System (Dark) + Tema: Sistem (Koyu) + + + Theme: System (Light) + Tema: Sistem (Açık) + + + Driver + Sürücü + + + Monitor + İzleme + + + Settings + Ayarlar + MonitorPage - System MonitoringSistem İzleme - CPUCPU - Usage: Kullanım: - CPU data unavailableCPU verisi alınamıyor - Temperature: Sıcaklık: - GPU (NVIDIA)GPU (NVIDIA) - NVIDIA GPUNVIDIA GPU - Failed to read data via nvidia-sminvidia-smi üzerinden veri okunamadı - Load: Yük: - VRAM: VRAM: - RAMRAM - RAM data unavailableRAM verisi alınamıyor - RefreshYenile - Refresh interval: Yenileme aralığı: + + System Monitoring + Sistem İzleme + + + + CPU Load + CPU Yükü + + + + + + + + Unavailable + Kullanılamıyor + + + + CPU telemetry is currently unavailable. + CPU telemetrisi şu anda kullanılamıyor. + + + + GPU Load + GPU Yükü + + + nvidia-smi did not return live GPU telemetry. + nvidia-smi canlı GPU telemetrisi döndürmedi. + + + + Memory Usage + Bellek Kullanımı + + + + Used: + Kullanılan: + + + + RAM telemetry is currently unavailable. + RAM telemetrisi şu anda kullanılamıyor. + + + + Live Resource Curves + Canlı Kaynak Grafikleri + + + + Quick pulse view for the most important machine resources. + En önemli makine kaynakları için hızlı anlık görünüm. + + + + CPU + CPU + + + + GPU + Ekran Kartı (GPU) + + + + Health Summary + Sağlık Özeti + + + + Fast interpretation of the raw telemetry values. + Ham telemetri değerlerinin hızlı yorumlaması. + + + + CPU Busy + CPU Meşgul + + + + CPU Stable + CPU Stabil + + + + GPU Online + GPU Çevrimiçi + + + + GPU Telemetry Missing + GPU Telemetrisi Bulunamadı + + + + Memory Pressure + Bellek Baskısı + + + + Memory Stable + Bellek Stabil + + + + GPU temperature: + GPU sıcaklığı: + + + + , VRAM + , VRAM + + + + GPU metrics are unavailable. Check driver installation and nvidia-smi accessibility. + GPU metrikleri kullanılamıyor. Sürücü kurulumunu ve nvidia-smi erişilebilirliğini kontrol edin. + + + + Detailed Signals + Detaylı Sinyaller + + + + Expanded raw values for support and diagnostics. + Destek ve tanılama için genişletilmiş ham değerler. + + + + CPU Temperature + CPU Sıcaklığı + + + + + + Unknown + Bilinmiyor + + + + GPU Temperature + GPU Sıcaklığı + + + + No Live Data + Canlı Veri Yok + + + + No NVIDIA GPU was detected on this system. + Bu sistemde NVIDIA GPU tespit edilemedi. + + + + GPU detected, but no active driver is loaded. + GPU tespit edildi ancak etkin bir sürücü yüklenmedi. + + + + Live GPU telemetry is unavailable. Check nvidia-smi and driver access. + Canlı GPU telemetrisi kullanılamıyor. nvidia-smi ve sürücü erişimini kontrol edin. + + + + No NVIDIA GPU is currently detected on this system. + Bu sistemde şu anda herhangi bir NVIDIA GPU tespit edilmiyor. + + + + GPU telemetry is unavailable because the NVIDIA driver is not active. + NVIDIA sürücüsü etkin olmadığı için GPU telemetrisi kullanılamıyor. + + + + VRAM + VRAM Belleği + + + + RAM Footprint + RAM Kapladığı Alan + + + + Actions + İşlemler + + + + Trigger a manual refresh when you need a fresh sample. + Taze bir örneğe ihtiyacınız olduğunda manuel yenilemeyi tetikleyin. + + + + Refresh Telemetry + Telemetriyi Yenile + + + + NVIDIA Path OK + NVIDIA Yolu Tamam + + + + Check NVIDIA Path + NVIDIA Yolunu Kontrol Et + + + Usage: + Kullanım: + + + CPU data unavailable + CPU verisi alınamıyor + + + + Temperature: + Sıcaklık: + + + GPU (NVIDIA) + GPU (NVIDIA) + + + + NVIDIA GPU + NVIDIA GPU + + + Failed to read data via nvidia-smi + nvidia-smi üzerinden veri okunamadı + + + Load: + Yük: + + + VRAM: + VRAM: + + + + RAM + RAM + + + RAM data unavailable + RAM verisi alınamıyor + + + Refresh + Yenile + + + + Refresh interval: + Yenileme aralığı: + NvidiaDetector - Proprietary (NVIDIA)Kapalı Kaynak (NVIDIA) - Open Source (Nouveau)Açık Kaynak (Nouveau) - Not Installed / UnknownKurulu Değil / Bilinmiyor - GPU: %1 + + Proprietary (NVIDIA) + Kapalı Kaynak (NVIDIA) + + + Open Source (Nouveau) + Açık Kaynak (Nouveau) + + + + Not Installed / Unknown + Kurulu Değil / Bilinmiyor + + + + + None + Yok + + + GPU: %1 Driver Version: %2 Secure Boot: %3 Session: %4 NVIDIA Module: %5 -Nouveau: %6GPU: %1 +Nouveau: %6 + GPU: %1 Sürücü Sürümü: %2 Secure Boot: %3 Oturum: %4 NVIDIA Modülü: %5 -Nouveau: %6 - UnknownBilinmiyor - LoadedYüklü - Not loadedYüklü değil - ActiveAktif - InactiveAktif değil +Nouveau: %6 + + + + NVIDIA Open Kernel Modules + NVIDIA Acik Kernel Modulleri + + + + NVIDIA Driver + NVIDIA Surucusu + + + + Fallback Open Driver + Yedek Acik Surucu + + + + GPU: %1 +Driver Version: %2 +Secure Boot: %3 +Session: %4 +Active Stack: %5 +Fallback Open Driver: %6 + GPU: %1 +Surucu Surumu: %2 +Secure Boot: %3 +Oturum: %4 +Etkin Yigin: %5 +Yedek Acik Surucu: %6 + + + + Enabled + Etkin + + + + Disabled + Devre Dışı + + + + Disabled / Unknown + Devre Dışı / Bilinmiyor + + + + Unknown + Bilinmiyor + + + Loaded + Yüklü + + + Not loaded + Yüklü değil + + + + Active + Aktif + + + + Inactive + Aktif değil + NvidiaInstaller - You must accept the NVIDIA proprietary driver license terms before installation. Detected license: %1Kurulumdan önce NVIDIA kapalı kaynak sürücü lisans koşullarını kabul etmeniz gerekir. Tespit edilen lisans: %1 - License agreement acceptance is required before installation.Kurulumdan önce lisans sözleşmesinin kabul edilmesi gerekir. - Checking RPM Fusion repositories...RPM Fusion depoları kontrol ediliyor... - Platform version could not be detected.Platform sürümü tespit edilemedi. - Failed to enable RPM Fusion repositories: RPM Fusion depoları etkinleştirilemedi: - Installing the proprietary NVIDIA driver (akmod-nvidia)...Kapalı kaynak NVIDIA sürücüsü kuruluyor (akmod-nvidia)... - Installation failed: Kurulum başarısız: - Building the kernel module (akmods --force)...Kernel modülü derleniyor (akmods --force)... - The proprietary NVIDIA driver was installed successfully. Please restart the system.Kapalı kaynak NVIDIA sürücüsü başarıyla kuruldu. Lütfen sistemi yeniden başlatın. - Switching to the open-source driver...Açık kaynak sürücüye geçiliyor... - Failed to remove proprietary packages: Kapalı kaynak paketler kaldırılamadı: - Open-source driver installation failed: Açık kaynak sürücü kurulumu başarısız: - The open-source driver (Nouveau) was installed. Please restart the system.Açık kaynak sürücü (Nouveau) kuruldu. Lütfen sistemi yeniden başlatın. - Removing the NVIDIA driver...NVIDIA sürücüsü kaldırılıyor... - Driver removed successfully.Sürücü başarıyla kaldırıldı. - Removal failed: Kaldırma başarısız: - Cleaning legacy driver leftovers...Eski sürücü kalıntıları temizleniyor... - Deep clean completed.Derin temizlik tamamlandı. - Wayland detected: applying nvidia-drm.modeset=1...Wayland tespit edildi: nvidia-drm.modeset=1 uygulanıyor... - Failed to apply the Wayland kernel parameter: Wayland kernel parametresi uygulanamadı: - X11 detected: checking NVIDIA userspace packages...X11 tespit edildi: NVIDIA userspace paketleri kontrol ediliyor... - Failed to install the X11 NVIDIA package: X11 NVIDIA paketi kurulamadı: + + + Starting command (attempt %1): %2 + Komut başlatılıyor (deneme %1): %2 + + + + Command finished (attempt %1, exit %2, %3 ms): %4 + Komut tamamlandı (deneme %1, çıkış %2, %3 ms): %4 + + + You must accept the NVIDIA proprietary driver license terms before installation. Detected license: %1 + Kurulumdan önce NVIDIA kapalı kaynak sürücü lisans koşullarını kabul etmeniz gerekir. Tespit edilen lisans: %1 + + + License agreement acceptance is required before installation. + Kurulumdan önce lisans sözleşmesinin kabul edilmesi gerekir. + + + + The proprietary NVIDIA driver is subject to NVIDIA's software license. Review the official NVIDIA license before installation: %1 + Kapali kaynak NVIDIA surucusu, NVIDIA yazilim lisansina tabidir. Kurulumdan once resmi NVIDIA lisansini inceleyin: %1 + + + + NVIDIA license review confirmation is required before installation. + Kurulumdan once NVIDIA lisansinin incelendiginin onaylanmasi gerekir. + + + + Checking RPM Fusion repositories... + RPM Fusion depoları kontrol ediliyor... + + + + Platform version could not be detected. + Platform sürümü tespit edilemedi. + + + + Failed to enable RPM Fusion repositories: + RPM Fusion depoları etkinleştirilemedi: + + + + Installing the proprietary NVIDIA driver (akmod-nvidia)... + Kapalı kaynak NVIDIA sürücüsü kuruluyor (akmod-nvidia)... + + + + Installation failed: + Kurulum başarısız: + + + + + Building the kernel module (akmods --force)... + Kernel modülü derleniyor (akmods --force)... + + + + + Kernel module build failed: + Kernel modülü inşası başarısız: + + + + The proprietary NVIDIA driver was installed successfully. Please restart the system. + Kapalı kaynak NVIDIA sürücüsü başarıyla kuruldu. Lütfen sistemi yeniden başlatın. + + + + Switching to NVIDIA open kernel modules... + NVIDIA acik kernel modullerine geciliyor... + + + + Failed to remove conflicting NVIDIA kernel packages: + Cakisan NVIDIA kernel paketleri kaldirilamadi: + + + + Open NVIDIA kernel module installation failed: + Acik NVIDIA kernel modulu kurulumu basarisiz: + + + + unknown error + bilinmeyen hata + + + + NVIDIA open kernel modules were installed successfully. Please restart the system. + NVIDIA acik kernel modulleri basariyla kuruldu. Lutfen sistemi yeniden baslatin. + + + Switching to the open-source driver... + Açık kaynak sürücüye geçiliyor... + + + Failed to remove proprietary packages: + Kapalı kaynak paketler kaldırılamadı: + + + Open-source driver installation failed: + Açık kaynak sürücü kurulumu başarısız: + + + The open-source driver (Nouveau) was installed. Please restart the system. + Açık kaynak sürücü (Nouveau) kuruldu. Lütfen sistemi yeniden başlatın. + + + + Removing the NVIDIA driver... + NVIDIA sürücüsü kaldırılıyor... + + + + Driver removed successfully. + Sürücü başarıyla kaldırıldı. + + + + Removal failed: + Kaldırma başarısız: + + + + Cleaning legacy driver leftovers... + Eski sürücü kalıntıları temizleniyor... + + + + Deep clean failed: + Derin temizlik başarısız: + + + + DNF cache cleanup failed: + DNF önbellek temizliği başarısız: + + + + Deep clean completed. + Derin temizlik tamamlandı. + + + Wayland detected: applying nvidia-drm.modeset=1... + Wayland tespit edildi: nvidia-drm.modeset=1 uygulanıyor... + + + + Another driver operation is already running. + Başka bir sürücü işlemi şu an yürütülüyor. + + + Unknown + Bilinmiyor + + + + Legacy NVIDIA cleanup completed. + Eski NVIDIA temizliği tamamlandı. + + + + Failed to apply the Wayland kernel parameter: + Wayland kernel parametresi uygulanamadı: + + + + X11 detected: checking NVIDIA userspace packages... + X11 tespit edildi: NVIDIA userspace paketleri kontrol ediliyor... + + + + Failed to install the X11 NVIDIA package: + X11 NVIDIA paketi kurulamadı: + NvidiaUpdater - Updating the NVIDIA driver...NVIDIA sürücüsü güncelleniyor... - Update failed: Güncelleme başarısız: - Rebuilding the kernel module...Kernel modülü yeniden derleniyor... - Wayland detected: refreshing nvidia-drm.modeset=1...Wayland tespit edildi: nvidia-drm.modeset=1 yenileniyor... - Driver updated successfully. Please restart the system.Sürücü başarıyla güncellendi. Lütfen sistemi yeniden başlatın. + + Updating the NVIDIA driver... + NVIDIA sürücüsü güncelleniyor... + + + + Update failed: + Güncelleme başarısız: + + + Rebuilding the kernel module... + Kernel modülü yeniden derleniyor... + + + Wayland detected: refreshing nvidia-drm.modeset=1... + Wayland tespit edildi: nvidia-drm.modeset=1 yenileniyor... + + + + Driver updated successfully. Please restart the system. + Sürücü başarıyla güncellendi. Lütfen sistemi yeniden başlatın. + + + + + dnf not found. + dnf bulunamadı. + + + No installed NVIDIA driver found. + Sistemde kurulu bir NVIDIA sürücüsü bulunamadı. + + + + Online NVIDIA packages were found. You can download and install the driver now. + Çevrimiçi NVIDIA paketleri bulundu. Sürücüyü şimdi indirip kurabilirsiniz. + + + + Online NVIDIA driver found. Latest remote version: %1 + Çevrimiçi NVIDIA sürücüsü bulundu. En güncel uzak sürüm: %1 + + + + No online NVIDIA package catalog was found. RPM Fusion may not be configured yet. + Çevrimiçi NVIDIA paket kataloğu bulunamadı. RPM Fusion henüz yapılandırılmamış olabilir. + + + + Update found (version details unavailable). + Güncelleme bulundu (sürüm detayları kullanılamıyor). + + + + Update found: %1 + Güncelleme bulundu: %1 + + + + Driver is up to date. No new version found. + Sürücü güncel. Yeni sürüm bulunamadı. + + + + Update check failed: %1 + Güncelleme kontrolü başarısız: %1 + + + + Starting command (attempt %1): %2 + Komut başlatılıyor (deneme %1): %2 + + + + Command finished (attempt %1, exit %2, %3 ms): %4 + Komut tamamlandı (deneme %1, çıkış %2, %3 ms): %4 + + + + Another driver operation is already running. + Başka bir sürücü işlemi şu an yürütülüyor. + + + + Kernel module build failed: + Kernel modülü inşası başarısız: + + + + + + unknown error + bilinmeyen hata + + + + Wayland detected: applying nvidia-drm.modeset=1... + Wayland algılandı: nvidia-drm.modeset=1 uygulanıyor... + + + + Failed to update the Wayland kernel parameter: + Wayland çekirdek parametresi güncellenemedi: + + + + No available versions found. + Hiçbir uygun sürüm bulunamadı. + + + + Available versions: %1 + Mevcut sürümler: %1 + + + + Starting update check... + Güncelleme denetimi başlatılıyor... + + + + Selected version not found in the repository. + Seçili sürüm depoda bulunamadı. + + + + Updating NVIDIA driver to the latest version... + NVIDIA sürücüsü en yeni sürüme güncelleniyor... + + + + Switching NVIDIA driver to selected version: %1 + NVIDIA sürücüsü seçili sürüme değiştiriliyor: %1 + + + + Driver is already at the latest available version. + Sürücü zaten mevcut en güncel sürümde. + + + + Selected driver version is already installed. + Seçilen sürücü sürümü zaten kurulu. + + + + Rebuilding kernel module... + Kernel modülü yeniden derleniyor... + + + + Latest version installed successfully. Please restart the system. + En son sürüm başarıyla yüklendi. Lütfen sistemi yeniden başlatın. + + + + Selected version applied successfully. Please restart the system. + Seçili sürüm başarıyla uygulandı. Lütfen sistemi yeniden başlatın. + SettingsPage - SettingsAyarlar - AboutHakkında - Application: Uygulama: - Theme: Tema: - System DarkSistem Koyu - System LightSistem Açık + + Settings + Ayarlar + + + Interface + Arayüz + + + Tune the shell density and how much operational detail the app exposes. + Arayüz yoğunluğunu ve uygulamanın ne kadar operasyonel ayrıntı göstereceğini ayarlayın. + + + + Language + Dil + + + + Changes the application language immediately and keeps the selection for the next launch. + Uygulama dilini hemen değiştirir ve seçimi sonraki açılış için korur. + + + + Compact layout + Sıkı görünüm + + + + Reduces spacing to fit more information on screen. + Ekrana daha fazla bilgi sığdırmak için boşlukları azaltır. + + + + Show advanced diagnostics + Gelişmiş tanılamayı göster + + + + Shows verification reports and expanded monitor metrics. + Doğrulama raporlarını ve genişletilmiş monitör metriklerini gösterir. + + + + Language: + Dil: + + + System Dark Theme + Koyu Sistem Teması + + + System Light Theme + Açık Sistem Teması + + + + Appearance & Behavior + Görünüm ve Davranış + + + + Control theme, density and operator-focused interface behavior. + Tema, yoğunluk ve operatör odaklı arayüz davranışını yönetin. + + + + Theme mode + Tema modu + + + + Choose whether the application follows the OS theme or uses an explicit light or dark shell. + Uygulamanın işletim sistemi temasını izlemesini veya açık ya da koyu kabuğu açıkça kullanmasını seçin. + + + + Theme: Follow System + Tema: Sistemi İzle + + + + Theme: Dark + Tema: Koyu + + + + Theme: Light + Tema: Açık + + + + Compact Active + Sıkı Düzen Etkin + + + + Comfort Active + Rahat Düzen Etkin + + + + Advanced Visible + Gelişmiş Görünür + + + + Advanced Hidden + Gelişmiş Gizli + + + + Restore the recommended interface defaults if the shell starts to feel cluttered. + Kabuk karmaşık hissettirmeye başlarsa önerilen arayüz varsayılanlarını geri yükleyin. + + + + Reset Interface Defaults + Arayüz Varsayılanlarını Sıfırla + + + + Diagnostics + Tanılama + + + + Useful runtime context before filing issues or performing support work. + Hata bildirmeden veya destek çalışması yapmadan önce yararlı çalışma zamanı bilgisi. + + + + + Application + Uygulama + + + + GPU + Ekran Kartı (GPU) + + + + Not detected + Tespit edilmedi + + + + Driver + Sürücü + + + + Session + Oturum + + + + Unknown + Bilinmiyor + + + + Use the Driver page to refresh detection before copying any diagnostic context. + Tanılama bağlamını kopyalamadan önce algılamayı yenilemek için Sürücü (Driver) sayfasını kullanın. + + + + Workflow Guidance + İş Akışı Rehberi + + + + Recommended order of operations when changing drivers. + Sürücü değiştirilirken önerilen işlem sırası. + + + + 1. Verify GPU detection and session type. +2. Install or switch the driver stack. +3. Check repository updates. +4. Restart after successful package operations. + 1. GPU algılamasını ve oturum tipini doğrulayın. +2. Sürücü yığınını kurun veya değiştirin. +3. Repo güncellemelerini kontrol edin. +4. Başarılı paket işlemlerinden sonra yeniden başlatın. + + + + Secure Boot is enabled. Kernel module signing may still be required after package installation. + Güvenli Önyükleme etkin. Paket kurulumundan sonra kernel modülü imzalanması gerekebilir. + + + + No Secure Boot blocker is currently reported by the detector. + Şu anda dedektör tarafından bildirilen herhangi bir (Secure Boot) engeli bulunmuyor. + + + + About + Hakkında + + + + Project identity and current shell mode. + Proje kimliği ve mevcut kabuk modu. + + + + Theme + Tema + + + + Follow System + Sistemi İzle + + + + Dark + Koyu + + + + Light + Açık + + + + Effective language + Etkin dil + + + + Layout density + Düzen yoğunluğu + + + + Advanced diagnostics + Gelişmiş tanılama + + + Application: + Uygulama: + + + Theme: + Tema: + + + System Dark + Sistem Koyu + + + System Light + Sistem Açık + + + Layout density: + Düzen Görünümü: + + + + Compact + Sıkı + + + + Comfort + Rahat + + + Advanced diagnostics: + Gelişmiş Tanılama: + + + + Visible + Görünür + + + + Hidden + Gizli + + + + SidebarMenu + + + Driver Management + Sürücü Yönetimi + + + + System Monitoring + Sistem İzleme + + + + Settings + Ayarlar + + + + ro-Control + ro-Control + diff --git a/packaging/rpm/ro-control.spec b/packaging/rpm/ro-control.spec index 2a552c1..ff71cd3 100644 --- a/packaging/rpm/ro-control.spec +++ b/packaging/rpm/ro-control.spec @@ -1,8 +1,6 @@ -%global _rpmfilename %{NAME}-%{VERSION}.%{ARCH}.rpm - Name: ro-control Version: 0.1.0 -Release: 1 +Release: 2%{?dist} Summary: Smart NVIDIA driver manager and system monitor License: GPL-3.0-or-later @@ -26,6 +24,11 @@ Requires: qt6-qtdeclarative Requires: qt6-qtwayland Requires: kf6-qqc2-desktop-style Requires: polkit +Requires: dnf +Requires: /usr/bin/pkexec +Requires: pciutils +Recommends: mokutil +Recommends: kmod %description ro-Control is a Qt6/KDE Plasma desktop application that helps users @@ -51,9 +54,9 @@ tar -xzf %{SOURCE0} --strip-components=1 %license LICENSE %doc README.md README.tr.md CHANGELOG.md %{_bindir}/ro-control -%{_datadir}/applications/ro-control.desktop +%{_datadir}/applications/io.github.projectroasd.rocontrol.desktop %{_datadir}/man/man1/ro-control.1* -%{_datadir}/metainfo/ro-control.metainfo.xml +%{_datadir}/metainfo/io.github.projectroasd.rocontrol.metainfo.xml %{_datadir}/icons/hicolor/256x256/apps/ro-control.png %{_datadir}/icons/hicolor/scalable/apps/ro-control.svg %{_datadir}/bash-completion/completions/ro-control @@ -63,5 +66,9 @@ tar -xzf %{SOURCE0} --strip-components=1 %{_datadir}/polkit-1/actions/io.github.ProjectRoASD.rocontrol.policy %changelog +* Mon Mar 16 2026 ro-Control Maintainers - 0.1.0-2 +- Fix Fedora runtime dependencies for DNF and pkexec +- Restore standard RPM artifact naming to avoid output collisions + * Fri Mar 06 2026 ro-Control Maintainers - 0.1.0-1 - Initial RPM packaging spec diff --git a/scripts/dev-watch.sh b/scripts/dev-watch.sh new file mode 100755 index 0000000..d4a908a --- /dev/null +++ b/scripts/dev-watch.sh @@ -0,0 +1,130 @@ +#!/usr/bin/env bash +# dev-watch.sh — Kaynak degisikliklerini izler, otomatik build alir ve uygulamayi yeniden baslatir. +# Kullanim: ./scripts/dev-watch.sh +# Gereksinim: sudo dnf install inotify-tools + +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +BUILD_DIR="${BUILD_DIR:-$ROOT_DIR/build}" +BINARY="$BUILD_DIR/ro-control" +APP_PID="" + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +CYAN='\033[0;36m' +RESET='\033[0m' + +log() { echo -e "${CYAN}[dev-watch]${RESET} $*"; } +ok() { echo -e "${GREEN}[dev-watch]${RESET} $*"; } +warn() { echo -e "${YELLOW}[dev-watch]${RESET} $*"; } +err() { echo -e "${RED}[dev-watch]${RESET} $*"; } + +# --- Qt render backend otomatik sec --- +# GPU olmadan calisan sistemlerde (NVIDIA surucusu kurulu degil, VM, vb.) +# Qt'un EGL hatasi vermemesi icin fallback backend ayarla +setup_qt_env() { + # Eger kullanici zaten bir backend secmisse dokunma + if [[ -n "${QSG_RHI_BACKEND:-}" || -n "${QT_XCB_GL_INTEGRATION:-}" ]]; then + return + fi + + # EGL/DRI2 kullanilabilir mi kontrol et + if command -v glxinfo &>/dev/null && glxinfo 2>/dev/null | grep -q "direct rendering: Yes"; then + # Donanim hizlandirma var, varsayilan backend kullan + log "OpenGL donanim hizlandirma mevcut, varsayilan renderer kullaniliyor." + else + # Yazilim renderer'a gec - GPU olmayan / surucusuz ortam + warn "GPU/EGL hizlandirma bulunamadi, yazilim renderer'a geciliyor." + warn "NVIDIA surucu kurulduktan sonra bu uyari kaybolacak." + export QT_XCB_GL_INTEGRATION=none + export LIBGL_ALWAYS_SOFTWARE=0 + export QSG_RENDERER_DEBUG="" + fi +} + +# --- Bagimlilik kontrolu --- +if ! command -v inotifywait &>/dev/null; then + err "inotify-tools bulunamadi. Kurmak icin:" + err " sudo dnf install inotify-tools" + exit 1 +fi + +if [[ ! -d "$BUILD_DIR" || ! -f "$BUILD_DIR/CMakeCache.txt" ]]; then + warn "Build dizini yok veya cmake yapilandirilmamis." + warn "Once sunu calistir: ./scripts/fedora-bootstrap.sh" + exit 1 +fi + +if [[ ! -f "$BINARY" ]]; then + warn "Binary bulunamadi: $BINARY" + warn "Once sunu calistir: ./scripts/fedora-bootstrap.sh" + exit 1 +fi + +# --- Uygulamayi durdur --- +stop_app() { + if [[ -n "$APP_PID" ]] && kill -0 "$APP_PID" 2>/dev/null; then + log "Uygulama durduruluyor (PID: $APP_PID)..." + kill "$APP_PID" 2>/dev/null || true + wait "$APP_PID" 2>/dev/null || true + APP_PID="" + fi +} + +# --- Incremental build + yeniden basla --- +build_and_run() { + echo "" + log "Incremental build basliyor..." + if cmake --build "$BUILD_DIR" -j"$(nproc)" 2>&1; then + ok "Build basarili" + stop_app + log "Uygulama baslatiliyor..." + "$BINARY" 2>/dev/null & + APP_PID=$! + ok "ro-control calisiyor (PID: $APP_PID)" + else + err "Build hatasi -- degisiklikleri kontrol et." + fi + echo "" +} + +# --- Temiz cikis --- +cleanup() { + echo "" + warn "Cikis sinyali alindi." + stop_app + exit 0 +} +trap cleanup SIGINT SIGTERM + +# --- Qt ortam degiskenlerini ayarla --- +setup_qt_env + +# --- Baslangic --- +echo "" +log "ro-Control dev-watch modu" +log "Proje dizini : $ROOT_DIR" +log "Build dizini : $BUILD_DIR" +log "Izlenen dizin : $ROOT_DIR/src" +log "Cikmak icin : Ctrl+C" +echo "" + +build_and_run + +# --- Degisiklik izleme dongusu --- +inotifywait -m -r \ + --include '\.(cpp|h|qml|js|ts)$' \ + -e modify,create,delete,moved_to \ + --format "%w%f [%e]" \ + "$ROOT_DIR/src" "$ROOT_DIR/i18n" 2>/dev/null \ +| while IFS= read -r line; do + log "Degisiklik algilandi: $line" + sleep 0.8 + + # Kuyruktaki diger olaylari bosalt (debounce) + while IFS= read -t 0.1 -r _extra; do :; done <&0 2>/dev/null || true + + build_and_run +done diff --git a/scripts/fedora-bootstrap.sh b/scripts/fedora-bootstrap.sh new file mode 100755 index 0000000..28aa179 --- /dev/null +++ b/scripts/fedora-bootstrap.sh @@ -0,0 +1,68 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +BUILD_DIR="${BUILD_DIR:-$ROOT_DIR/build}" +BUILD_TYPE="${BUILD_TYPE:-Release}" +GENERATOR="${GENERATOR:-Ninja}" +ENABLE_TESTS="${ENABLE_TESTS:-1}" +INSTALL_AFTER_BUILD="${INSTALL_AFTER_BUILD:-0}" +INSTALL_PREFIX="${INSTALL_PREFIX:-/usr/local}" + +build_reqs=( + cmake + extra-cmake-modules + gcc-c++ + ninja-build + qt6-qtbase-devel + qt6-qtdeclarative-devel + qt6-qttools-devel + qt6-qtwayland-devel + kf6-qqc2-desktop-style + polkit-devel +) + +runtime_tools=( + dnf + polkit + pciutils + mokutil + kmod +) + +echo "[1/4] Installing Fedora build dependencies..." +sudo dnf install -y "${build_reqs[@]}" + +echo "[2/4] Installing runtime utilities used by diagnostics/driver workflows..." +sudo dnf install -y "${runtime_tools[@]}" + +cmake_args=( + -S "$ROOT_DIR" + -B "$BUILD_DIR" + -G "$GENERATOR" + -DCMAKE_BUILD_TYPE="$BUILD_TYPE" + -DCMAKE_INSTALL_PREFIX="$INSTALL_PREFIX" +) + +if [[ "$ENABLE_TESTS" == "1" ]]; then + cmake_args+=( -DBUILD_TESTS=ON ) +else + cmake_args+=( -DBUILD_TESTS=OFF ) +fi + +echo "[3/4] Configuring and building ($BUILD_TYPE)..." +cmake "${cmake_args[@]}" +cmake --build "$BUILD_DIR" -j"$(nproc)" + +if [[ "$ENABLE_TESTS" == "1" ]]; then + echo "[3.1/4] Running test suite..." + ctest --test-dir "$BUILD_DIR" --output-on-failure +fi + +echo "[4/4] Done." +echo "Run from build dir: $BUILD_DIR/ro-control" + +if [[ "$INSTALL_AFTER_BUILD" == "1" ]]; then + echo "Installing to $INSTALL_PREFIX..." + sudo cmake --install "$BUILD_DIR" +fi diff --git a/src/backend/monitor/cpumonitor.cpp b/src/backend/monitor/cpumonitor.cpp index 9fc81e8..1f519fc 100644 --- a/src/backend/monitor/cpumonitor.cpp +++ b/src/backend/monitor/cpumonitor.cpp @@ -1,40 +1,174 @@ // CPU istatistikleri #include "cpumonitor.h" +#include "system/commandrunner.h" +#include #include +#include #include #include namespace { -int readCpuTemperatureC() { - for (int i = 0; i < 32; ++i) { - QFile thermalFile(QString("/sys/class/thermal/thermal_zone%1/temp").arg(i)); - if (!thermalFile.open(QIODevice::ReadOnly | QIODevice::Text)) { - continue; +QString readFileText(const QString &path) { + QFile file(path); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + return {}; + } + + return QString::fromUtf8(file.readAll()).trimmed(); +} + +QString pathOverrideOrDefault(const char *envVarName, const QString &fallback) { + const QString overridePath = qEnvironmentVariable(envVarName).trimmed(); + return overridePath.isEmpty() ? fallback : overridePath; +} + +int parseMilliCelsius(const QString &value) { + bool ok = false; + const int milliC = value.trimmed().toInt(&ok); + return ok && milliC > 0 ? milliC / 1000 : 0; +} + +bool isPreferredCpuSensorType(const QString &sensorType) { + const QString lowered = sensorType.trimmed().toLower(); + return lowered.contains(QStringLiteral("cpu")) || + lowered.contains(QStringLiteral("pkg")) || + lowered.contains(QStringLiteral("package")) || + lowered.contains(QStringLiteral("core")) || + lowered.contains(QStringLiteral("k10temp")) || + lowered.contains(QStringLiteral("tctl")) || + lowered.contains(QStringLiteral("tdie")) || + lowered.contains(QStringLiteral("x86_pkg_temp")); +} + +int readFirstValidTemperature(const QStringList &paths) { + for (const QString &path : paths) { + const int temperatureC = parseMilliCelsius(readFileText(path)); + if (temperatureC > 0) { + return temperatureC; + } + } + + return 0; +} + +int readCpuTemperatureFromThermalZones() { + QDir thermalDir(pathOverrideOrDefault("RO_CONTROL_THERMAL_ROOT", + QStringLiteral("/sys/class/thermal"))); + const QFileInfoList entries = thermalDir.entryInfoList( + {QStringLiteral("thermal_zone*")}, QDir::Dirs | QDir::NoDotAndDotDot, + QDir::Name); + + QStringList preferredPaths; + QStringList fallbackPaths; + + for (const QFileInfo &entry : entries) { + const QString basePath = entry.absoluteFilePath(); + const QString type = readFileText(basePath + QStringLiteral("/type")); + const QString tempPath = basePath + QStringLiteral("/temp"); + + if (isPreferredCpuSensorType(type)) { + preferredPaths << tempPath; + } else { + fallbackPaths << tempPath; } + } + + const int preferredTemperature = readFirstValidTemperature(preferredPaths); + if (preferredTemperature > 0) { + return preferredTemperature; + } + + return readFirstValidTemperature(fallbackPaths); +} - const QByteArray raw = thermalFile.readAll().trimmed(); - bool ok = false; - const int milliC = raw.toInt(&ok); - if (ok && milliC > 0) { - return milliC / 1000; +int readCpuTemperatureFromHwmon() { + QDir hwmonDir(pathOverrideOrDefault("RO_CONTROL_HWMON_ROOT", + QStringLiteral("/sys/class/hwmon"))); + const QFileInfoList entries = hwmonDir.entryInfoList( + {QStringLiteral("hwmon*")}, QDir::Dirs | QDir::NoDotAndDotDot, + QDir::Name); + + QStringList preferredPaths; + QStringList fallbackPaths; + + for (const QFileInfo &entry : entries) { + const QString basePath = entry.absoluteFilePath(); + const QString sensorName = + readFileText(basePath + QStringLiteral("/name")).toLower(); + const bool preferredSensor = isPreferredCpuSensorType(sensorName); + + const QFileInfoList inputs = QDir(basePath).entryInfoList( + {QStringLiteral("temp*_input")}, QDir::Files, QDir::Name); + for (const QFileInfo &input : inputs) { + const QString inputPath = input.absoluteFilePath(); + const QString labelPath = + inputPath.left(inputPath.size() - QStringLiteral("_input").size()) + + QStringLiteral("_label"); + const QString label = readFileText(labelPath); + if (preferredSensor || isPreferredCpuSensorType(label)) { + preferredPaths << inputPath; + } else { + fallbackPaths << inputPath; + } } } - for (int i = 0; i < 32; ++i) { - QFile hwmonFile(QString("/sys/class/hwmon/hwmon%1/temp1_input").arg(i)); - if (!hwmonFile.open(QIODevice::ReadOnly | QIODevice::Text)) { - continue; + const int preferredTemperature = readFirstValidTemperature(preferredPaths); + if (preferredTemperature > 0) { + return preferredTemperature; + } + + return readFirstValidTemperature(fallbackPaths); +} + +int readCpuTemperatureC() { + const int thermalZoneTemperature = readCpuTemperatureFromThermalZones(); + if (thermalZoneTemperature > 0) { + return thermalZoneTemperature; + } + + const int hwmonTemperature = readCpuTemperatureFromHwmon(); + if (hwmonTemperature > 0) { + return hwmonTemperature; + } + + CommandRunner runner; + const auto result = runner.run(QStringLiteral("sensors")); + if (!result.success()) { + return 0; + } + + static const QRegularExpression preferredLinePattern( + QStringLiteral( + R"((package|tctl|tdie|cpu|core)[^:\n]*:\s*[+-]?([0-9]+(?:\.[0-9]+)?))"), + QRegularExpression::CaseInsensitiveOption); + static const QRegularExpression genericLinePattern( + QStringLiteral(R"(:\s*[+-]?([0-9]+(?:\.[0-9]+)?))")); + + const QStringList lines = result.stdout.split(QLatin1Char('\n')); + for (const QString &line : lines) { + const auto preferredMatch = preferredLinePattern.match(line); + if (preferredMatch.hasMatch()) { + bool ok = false; + const double value = preferredMatch.captured(2).toDouble(&ok); + if (ok && value > 0.0) { + return static_cast(value); + } } + } - const QByteArray raw = hwmonFile.readAll().trimmed(); - bool ok = false; - const int milliC = raw.toInt(&ok); - if (ok && milliC > 0) { - return milliC / 1000; + for (const QString &line : lines) { + const auto genericMatch = genericLinePattern.match(line); + if (genericMatch.hasMatch()) { + bool ok = false; + const double value = genericMatch.captured(1).toDouble(&ok); + if (ok && value > 0.0) { + return static_cast(value); + } } } diff --git a/src/backend/monitor/gpumonitor.cpp b/src/backend/monitor/gpumonitor.cpp index 1a7f8fa..e52e36c 100644 --- a/src/backend/monitor/gpumonitor.cpp +++ b/src/backend/monitor/gpumonitor.cpp @@ -2,6 +2,41 @@ #include "system/commandrunner.h" #include +#include + +namespace { + +QString normalizedMetricField(const QString &field) { + QString normalized = field.trimmed(); + normalized.remove(QRegularExpression(QStringLiteral(R"(\s*\[[^\]]+\]\s*)"))); + normalized.remove(QRegularExpression(QStringLiteral(R"(\s*%\s*)"))); + return normalized.trimmed(); +} + +bool parseMetricInt(const QString &field, int *value) { + if (value == nullptr) { + return false; + } + + const QString normalized = normalizedMetricField(field); + if (normalized.isEmpty() || + normalized.compare(QStringLiteral("n/a"), Qt::CaseInsensitive) == 0 || + normalized.compare(QStringLiteral("not supported"), Qt::CaseInsensitive) == 0 || + normalized.compare(QStringLiteral("unknown"), Qt::CaseInsensitive) == 0) { + return false; + } + + bool ok = false; + const int parsedValue = normalized.toInt(&ok); + if (!ok) { + return false; + } + + *value = parsedValue; + return true; +} + +} // namespace GpuMonitor::GpuMonitor(QObject *parent) : QObject(parent) { m_timer.setInterval(1500); @@ -59,32 +94,39 @@ void GpuMonitor::refresh() { return; } - bool ok = true; const QString nextName = fields.at(0).trimmed(); - const int nextTemp = fields.at(1).trimmed().toInt(&ok); - if (!ok) { - setAvailable(false); - clearMetrics(); - return; - } + int nextTemp = 0; + int nextUtil = 0; + int nextUsed = 0; + int nextTotal = 0; - const int nextUtil = fields.at(2).trimmed().toInt(&ok); - const int nextUsed = fields.at(3).trimmed().toInt(&ok); - const int nextTotal = fields.at(4).trimmed().toInt(&ok); - if (!ok || nextTotal < 0 || nextUsed < 0) { + const bool tempAvailable = parseMetricInt(fields.at(1), &nextTemp); + const bool utilAvailable = parseMetricInt(fields.at(2), &nextUtil); + const bool usedAvailable = parseMetricInt(fields.at(3), &nextUsed); + const bool totalAvailable = parseMetricInt(fields.at(4), &nextTotal); + + if (nextTotal < 0 || nextUsed < 0) { setAvailable(false); clearMetrics(); return; } - const int usagePercent = - nextTotal > 0 + const int usagePercent = (usedAvailable && totalAvailable && nextTotal > 0) ? std::clamp(static_cast((static_cast(nextUsed) / static_cast(nextTotal)) * 100.0), 0, 100) : 0; + const bool telemetryAvailable = !nextName.isEmpty() || tempAvailable || + utilAvailable || usedAvailable || + totalAvailable; + if (!telemetryAvailable) { + setAvailable(false); + clearMetrics(); + return; + } + if (m_gpuName != nextName) { m_gpuName = nextName; emit gpuNameChanged(); @@ -95,7 +137,7 @@ void GpuMonitor::refresh() { emit temperatureCChanged(); } - if (m_utilizationPercent != nextUtil) { + if (m_utilizationPercent != std::clamp(nextUtil, 0, 100)) { m_utilizationPercent = std::clamp(nextUtil, 0, 100); emit utilizationPercentChanged(); } diff --git a/src/backend/monitor/rammonitor.cpp b/src/backend/monitor/rammonitor.cpp index 4d825d1..8eac584 100644 --- a/src/backend/monitor/rammonitor.cpp +++ b/src/backend/monitor/rammonitor.cpp @@ -1,4 +1,5 @@ #include "rammonitor.h" +#include "system/commandrunner.h" #include #include @@ -6,6 +7,99 @@ #include +namespace { + +struct RamSnapshot { + bool valid = false; + int totalMiB = 0; + int usedMiB = 0; + int usagePercent = 0; +}; + +QString meminfoPath() { + const QString overridePath = qEnvironmentVariable("RO_CONTROL_MEMINFO_PATH") + .trimmed(); + return overridePath.isEmpty() ? QStringLiteral("/proc/meminfo") : overridePath; +} + +RamSnapshot buildSnapshot(qint64 memTotalKiB, qint64 memAvailableKiB) { + if (memTotalKiB <= 0 || memAvailableKiB < 0 || memAvailableKiB > memTotalKiB) { + return {}; + } + + const qint64 usedKiB = memTotalKiB - memAvailableKiB; + RamSnapshot snapshot; + snapshot.valid = true; + snapshot.totalMiB = static_cast(memTotalKiB / 1024); + snapshot.usedMiB = static_cast(usedKiB / 1024); + snapshot.usagePercent = + std::clamp(static_cast((static_cast(usedKiB) / + static_cast(memTotalKiB)) * + 100.0), + 0, 100); + return snapshot; +} + +RamSnapshot readSnapshotFromFree() { + CommandRunner runner; + const auto result = + runner.run(QStringLiteral("free"), {QStringLiteral("--mebi")}); + if (!result.success()) { + return {}; + } + + const QStringList lines = result.stdout.split(QLatin1Char('\n'), + Qt::SkipEmptyParts); + for (const QString &line : lines) { + if (!line.startsWith(QStringLiteral("Mem:"))) { + continue; + } + + const QStringList fields = line.split( + QRegularExpression(QStringLiteral(R"(\s+)")), Qt::SkipEmptyParts); + if (fields.size() < 3) { + return {}; + } + + bool totalOk = false; + const int totalMiB = fields.value(1).toInt(&totalOk); + if (!totalOk || totalMiB <= 0) { + return {}; + } + + int usedMiB = 0; + bool usedOk = false; + if (fields.size() >= 7) { + const int availableMiB = fields.value(6).toInt(&usedOk); + if (usedOk) { + usedMiB = std::clamp(totalMiB - availableMiB, 0, totalMiB); + } + } + + if (!usedOk) { + usedMiB = fields.value(2).toInt(&usedOk); + if (!usedOk) { + return {}; + } + usedMiB = std::clamp(usedMiB, 0, totalMiB); + } + + RamSnapshot snapshot; + snapshot.valid = true; + snapshot.totalMiB = totalMiB; + snapshot.usedMiB = usedMiB; + snapshot.usagePercent = std::clamp( + static_cast((static_cast(usedMiB) / + static_cast(totalMiB)) * 100.0), + 0, 100); + return snapshot; + } + + return {}; +} + +} // namespace + RamMonitor::RamMonitor(QObject *parent) : QObject(parent) { m_timer.setInterval(1000); m_timer.setTimerType(Qt::VeryCoarseTimer); @@ -30,13 +124,6 @@ int RamMonitor::updateInterval() const { return m_timer.interval(); } void RamMonitor::refresh() { // TR: Linux RAM metrikleri /proc/meminfo uzerinden okunur. // EN: Linux memory metrics are read from /proc/meminfo. - QFile meminfo("/proc/meminfo"); - if (!meminfo.open(QIODevice::ReadOnly | QIODevice::Text)) { - setAvailable(false); - clearMetrics(); - return; - } - qint64 memTotalKiB = -1; qint64 memAvailableKiB = -1; qint64 memFreeKiB = -1; @@ -45,41 +132,49 @@ void RamMonitor::refresh() { qint64 sReclaimableKiB = -1; qint64 shmemKiB = -1; - // TR: "Anahtar: deger" satirlarini guvenli sekilde ayriştir. - // EN: Safely parse "Key: value" lines. - static const QRegularExpression lineRe( - QStringLiteral(R"(^([A-Za-z_]+):\s+(\d+))")); - - QTextStream stream(&meminfo); - while (!stream.atEnd()) { - const QString line = stream.readLine(); - - const auto match = lineRe.match(line); - if (!match.hasMatch()) { - continue; - } - - bool ok = false; - const qint64 value = match.captured(2).toLongLong(&ok); - if (!ok) { - continue; - } - - const QString key = match.captured(1); - if (key == QStringLiteral("MemTotal")) { - memTotalKiB = value; - } else if (key == QStringLiteral("MemAvailable")) { - memAvailableKiB = value; - } else if (key == QStringLiteral("MemFree")) { - memFreeKiB = value; - } else if (key == QStringLiteral("Buffers")) { - buffersKiB = value; - } else if (key == QStringLiteral("Cached")) { - cachedKiB = value; - } else if (key == QStringLiteral("SReclaimable")) { - sReclaimableKiB = value; - } else if (key == QStringLiteral("Shmem")) { - shmemKiB = value; + QFile meminfo(meminfoPath()); + if (meminfo.open(QIODevice::ReadOnly | QIODevice::Text)) { + // TR: "Anahtar: deger" satirlarini guvenli sekilde ayriştir. + QTextStream stream(&meminfo); + while (!stream.atEnd()) { + const QString line = stream.readLine().trimmed(); + if (line.isEmpty()) { + continue; + } + + const int colonIdx = line.indexOf(QLatin1Char(':')); + if (colonIdx <= 0) { + continue; + } + + const QString key = line.left(colonIdx).trimmed(); + const QString valueStr = line.mid(colonIdx + 1).trimmed(); + + // The value might be "19842104 kB". We just need the number. + const int spaceIdx = valueStr.indexOf(QLatin1Char(' ')); + const QString numStr = spaceIdx > 0 ? valueStr.left(spaceIdx) : valueStr; + + bool ok = false; + const qint64 value = numStr.toLongLong(&ok); + if (!ok) { + continue; + } + + if (key == QStringLiteral("MemTotal")) { + memTotalKiB = value; + } else if (key == QStringLiteral("MemAvailable")) { + memAvailableKiB = value; + } else if (key == QStringLiteral("MemFree")) { + memFreeKiB = value; + } else if (key == QStringLiteral("Buffers")) { + buffersKiB = value; + } else if (key == QStringLiteral("Cached")) { + cachedKiB = value; + } else if (key == QStringLiteral("SReclaimable")) { + sReclaimableKiB = value; + } else if (key == QStringLiteral("Shmem")) { + shmemKiB = value; + } } } @@ -102,27 +197,29 @@ void RamMonitor::refresh() { return; } - const qint64 usedKiB = memTotalKiB - memAvailableKiB; - const int nextTotalMiB = static_cast(memTotalKiB / 1024); - const int nextUsedMiB = static_cast(usedKiB / 1024); - const int nextUsagePercent = - std::clamp(static_cast((static_cast(usedKiB) / - static_cast(memTotalKiB)) * - 100.0), - 0, 100); + RamSnapshot snapshot = buildSnapshot(memTotalKiB, memAvailableKiB); + if (!snapshot.valid) { + snapshot = readSnapshotFromFree(); + } + + if (!snapshot.valid) { + setAvailable(false); + clearMetrics(); + return; + } - if (m_totalMiB != nextTotalMiB) { - m_totalMiB = nextTotalMiB; + if (m_totalMiB != snapshot.totalMiB) { + m_totalMiB = snapshot.totalMiB; emit totalMiBChanged(); } - if (m_usedMiB != nextUsedMiB) { - m_usedMiB = nextUsedMiB; + if (m_usedMiB != snapshot.usedMiB) { + m_usedMiB = snapshot.usedMiB; emit usedMiBChanged(); } - if (m_usagePercent != nextUsagePercent) { - m_usagePercent = nextUsagePercent; + if (m_usagePercent != snapshot.usagePercent) { + m_usagePercent = snapshot.usagePercent; emit usagePercentChanged(); } diff --git a/src/backend/nvidia/detector.cpp b/src/backend/nvidia/detector.cpp index 40bbb52..f86f59f 100644 --- a/src/backend/nvidia/detector.cpp +++ b/src/backend/nvidia/detector.cpp @@ -1,6 +1,8 @@ #include "detector.h" +#include "system/capabilityprobe.h" #include "system/commandrunner.h" +#include "system/sessionutil.h" #include #include @@ -17,8 +19,10 @@ NvidiaDetector::GpuInfo NvidiaDetector::detect() const { info.driverVersion = detectDriverVersion(); info.driverLoaded = isModuleLoaded(QStringLiteral("nvidia")); info.nouveauActive = isModuleLoaded(QStringLiteral("nouveau")); + info.openKernelModulesInstalled = + isPackageInstalled(QStringLiteral("akmod-nvidia-open")); info.secureBootEnabled = detectSecureBoot(&info.secureBootKnown); - info.sessionType = detectSessionType(); + info.sessionType = SessionUtil::detectSessionType(); return info; } @@ -34,10 +38,14 @@ QString NvidiaDetector::installedDriverVersion() const { } QString NvidiaDetector::activeDriver() const { - if (m_info.driverLoaded) - return tr("Proprietary (NVIDIA)"); + if (m_info.driverLoaded) { + if (m_info.openKernelModulesInstalled) { + return tr("NVIDIA Open Kernel Modules"); + } + return tr("NVIDIA Driver"); + } if (m_info.nouveauActive) - return tr("Open Source (Nouveau)"); + return tr("Fallback Open Driver"); return tr("Not Installed / Unknown"); } @@ -47,13 +55,13 @@ QString NvidiaDetector::verificationReport() const { m_info.driverVersion.isEmpty() ? tr("None") : m_info.driverVersion; return tr("GPU: %1\nDriver Version: %2\nSecure Boot: %3\nSession: %4\n" - "NVIDIA Module: %5\nNouveau: %6") + "Active Stack: %5\nFallback Open Driver: %6") .arg(gpuText, versionText, m_info.secureBootKnown ? (m_info.secureBootEnabled ? tr("Enabled") : tr("Disabled")) : tr("Disabled / Unknown"), m_info.sessionType.isEmpty() ? tr("Unknown") : m_info.sessionType, - m_info.driverLoaded ? tr("Loaded") : tr("Not loaded"), + activeDriver(), m_info.nouveauActive ? tr("Active") : tr("Inactive")); } @@ -63,6 +71,10 @@ void NvidiaDetector::refresh() { } QString NvidiaDetector::detectGpuName() const { + if (!CapabilityProbe::isToolAvailable(QStringLiteral("lspci"))) { + return {}; + } + CommandRunner runner; const auto result = @@ -95,12 +107,19 @@ QString NvidiaDetector::detectGpuName() const { QString NvidiaDetector::detectDriverVersion() const { CommandRunner runner; - const auto result = runner.run(QStringLiteral("nvidia-smi"), - {QStringLiteral("--query-gpu=driver_version"), - QStringLiteral("--format=csv,noheader")}); + if (CapabilityProbe::isToolAvailable(QStringLiteral("nvidia-smi"))) { + const auto result = + runner.run(QStringLiteral("nvidia-smi"), + {QStringLiteral("--query-gpu=driver_version"), + QStringLiteral("--format=csv,noheader")}); + + if (result.success()) + return result.stdout.trimmed(); + } - if (result.success()) - return result.stdout.trimmed(); + if (!CapabilityProbe::isToolAvailable(QStringLiteral("modinfo"))) { + return {}; + } const auto modinfo = runner.run(QStringLiteral("modinfo"), {QStringLiteral("nvidia")}); @@ -116,6 +135,17 @@ QString NvidiaDetector::detectDriverVersion() const { return {}; } +bool NvidiaDetector::isPackageInstalled(const QString &packageName) const { + if (!CapabilityProbe::isToolAvailable(QStringLiteral("rpm"))) { + return false; + } + + CommandRunner runner; + const auto result = + runner.run(QStringLiteral("rpm"), {QStringLiteral("-q"), packageName}); + return result.success(); +} + bool NvidiaDetector::isModuleLoaded(const QString &moduleName) const { QFile modules(QStringLiteral("/proc/modules")); if (!modules.open(QIODevice::ReadOnly | QIODevice::Text)) @@ -132,6 +162,13 @@ bool NvidiaDetector::isModuleLoaded(const QString &moduleName) const { } bool NvidiaDetector::detectSecureBoot(bool *known) const { + if (!CapabilityProbe::isToolAvailable(QStringLiteral("mokutil"))) { + if (known != nullptr) { + *known = false; + } + return false; + } + CommandRunner runner; const auto result = runner.run(QStringLiteral("mokutil"), {QStringLiteral("--sb-state")}); @@ -150,25 +187,3 @@ bool NvidiaDetector::detectSecureBoot(bool *known) const { return false; } - -QString NvidiaDetector::detectSessionType() const { - const QString envType = - qEnvironmentVariable("XDG_SESSION_TYPE").trimmed().toLower(); - if (!envType.isEmpty()) - return envType; - - CommandRunner runner; - const auto loginctl = - runner.run(QStringLiteral("loginctl"), - {QStringLiteral("show-session"), - qEnvironmentVariable("XDG_SESSION_ID"), QStringLiteral("-p"), - QStringLiteral("Type"), QStringLiteral("--value")}); - - if (loginctl.success()) { - const QString type = loginctl.stdout.trimmed().toLower(); - if (!type.isEmpty()) - return type; - } - - return QStringLiteral("unknown"); -} diff --git a/src/backend/nvidia/detector.h b/src/backend/nvidia/detector.h index 83b6517..c7cab19 100644 --- a/src/backend/nvidia/detector.h +++ b/src/backend/nvidia/detector.h @@ -28,6 +28,7 @@ class NvidiaDetector : public QObject { QString vbiosVersion; bool driverLoaded = false; bool nouveauActive = false; + bool openKernelModulesInstalled = false; bool secureBootEnabled = false; bool secureBootKnown = false; QString sessionType; @@ -63,9 +64,9 @@ class NvidiaDetector : public QObject { private: QString detectGpuName() const; QString detectDriverVersion() const; + bool isPackageInstalled(const QString &packageName) const; bool isModuleLoaded(const QString &moduleName) const; bool detectSecureBoot(bool *known = nullptr) const; - QString detectSessionType() const; GpuInfo m_info; }; diff --git a/src/backend/nvidia/installer.cpp b/src/backend/nvidia/installer.cpp index d575d13..c10a024 100644 --- a/src/backend/nvidia/installer.cpp +++ b/src/backend/nvidia/installer.cpp @@ -1,13 +1,142 @@ #include "installer.h" #include "system/commandrunner.h" +#include "system/sessionutil.h" +#include +#include +#include #include +namespace { + +const QStringList kSharedNvidiaUserspacePackages = { + QStringLiteral("xorg-x11-drv-nvidia"), + QStringLiteral("xorg-x11-drv-nvidia-libs"), + QStringLiteral("xorg-x11-drv-nvidia-cuda"), + QStringLiteral("xorg-x11-drv-nvidia-cuda-libs"), + QStringLiteral("nvidia-modprobe"), + QStringLiteral("nvidia-persistenced"), + QStringLiteral("nvidia-settings"), +}; + +const QStringList kKernelPackageCleanupTargets = { + QStringLiteral("akmod-nvidia"), + QStringLiteral("akmod-nvidia-open"), + QStringLiteral("xorg-x11-drv-nvidia-kmodsrc"), +}; + +const char kNvidiaLicenseUrl[] = + "https://www.nvidia.com/en-us/drivers/nvidia-license/"; + +QString commandError(const CommandRunner::Result &result, + const QString &fallback = QString()) { + const QString stderrText = result.stderr.trimmed(); + if (!stderrText.isEmpty()) { + return stderrText; + } + + const QString stdoutText = result.stdout.trimmed(); + if (!stdoutText.isEmpty()) { + return stdoutText; + } + + return fallback; +} + +QStringList buildDriverInstallTargets(const QString &kernelPackageName) { + QStringList packages{kernelPackageName}; + packages << kSharedNvidiaUserspacePackages; + return packages; +} + +void emitProgressAsync(const QPointer &guard, + const QString &message) { + QMetaObject::invokeMethod( + guard, + [guard, message]() { + if (guard) { + emit guard->progressMessage(message); + } + }, + Qt::QueuedConnection); +} + +void attachRunnerLogging(CommandRunner &runner, + const QPointer &guard) { + QObject::connect(&runner, &CommandRunner::outputLine, guard, + [guard](const QString &message) { + emitProgressAsync(guard, message); + }); + + QObject::connect(&runner, &CommandRunner::errorLine, guard, + [guard](const QString &message) { + emitProgressAsync(guard, message); + }); + + QObject::connect( + &runner, &CommandRunner::commandStarted, guard, + [guard](const QString &program, const QStringList &args, int attempt) { + QStringList visibleArgs = args; + if (!visibleArgs.isEmpty() && + visibleArgs.constFirst().contains(QStringLiteral("ro-control-helper"))) { + visibleArgs.removeFirst(); + } + + const QString commandLine = + QStringLiteral("$ %1 %2") + .arg(program, visibleArgs.join(QLatin1Char(' ')).trimmed()); + emitProgressAsync( + guard, NvidiaInstaller::tr("Starting command (attempt %1): %2") + .arg(attempt) + .arg(commandLine.trimmed())); + }); + + QObject::connect(&runner, &CommandRunner::commandFinished, guard, + [guard](const QString &program, int exitCode, int attempt, + int elapsedMs) { + emitProgressAsync( + guard, + NvidiaInstaller::tr( + "Command finished (attempt %1, exit %2, %3 ms): %4") + .arg(attempt) + .arg(exitCode) + .arg(elapsedMs) + .arg(program)); + }); +} + +} // namespace + NvidiaInstaller::NvidiaInstaller(QObject *parent) : QObject(parent) { refreshProprietaryAgreement(); } +void NvidiaInstaller::setBusy(bool busy) { + if (m_busy == busy) { + return; + } + + m_busy = busy; + emit busyChanged(); +} + +void NvidiaInstaller::runAsyncTask(const std::function &task) { + if (m_busy) { + emit progressMessage(tr("Another driver operation is already running.")); + return; + } + + setBusy(true); + + QThread *thread = QThread::create(task); + connect(thread, &QThread::finished, this, [this, thread]() { + setBusy(false); + thread->deleteLater(); + }); + thread->start(); +} + void NvidiaInstaller::setProprietaryAgreement(bool required, const QString &text) { if (m_proprietaryAgreementRequired == required && @@ -21,40 +150,11 @@ void NvidiaInstaller::setProprietaryAgreement(bool required, } void NvidiaInstaller::refreshProprietaryAgreement() { - CommandRunner runner; - const auto info = - runner.run(QStringLiteral("dnf"), - {QStringLiteral("info"), QStringLiteral("akmod-nvidia")}); - - if (!info.success()) { - setProprietaryAgreement(false, QString()); - return; - } - - QString licenseLine; - const QStringList lines = info.stdout.split(QLatin1Char('\n')); - for (const QString &line : lines) { - if (line.startsWith(QStringLiteral("License"), Qt::CaseInsensitive)) { - licenseLine = line; - break; - } - } - - const QString lowered = licenseLine.toLower(); - const bool requiresAgreement = - lowered.contains(QStringLiteral("eula")) || - lowered.contains(QStringLiteral("proprietary")) || - lowered.contains(QStringLiteral("nvidia")); - - if (requiresAgreement) { - setProprietaryAgreement( - true, tr("You must accept the NVIDIA proprietary driver license terms " - "before installation. Detected license: %1") - .arg(licenseLine.isEmpty() ? tr("Unknown") : licenseLine)); - return; - } - - setProprietaryAgreement(false, QString()); + setProprietaryAgreement( + true, + tr("The proprietary NVIDIA driver is subject to NVIDIA's software " + "license. Review the official NVIDIA license before installation: %1") + .arg(QString::fromLatin1(kNvidiaLicenseUrl))); } void NvidiaInstaller::install() { installProprietary(false); } @@ -64,168 +164,345 @@ void NvidiaInstaller::installProprietary(bool agreementAccepted) { if (m_proprietaryAgreementRequired && !agreementAccepted) { emit installFinished(false, - tr("License agreement acceptance is required before " + tr("NVIDIA license review confirmation is required before " "installation.")); return; } - CommandRunner runner; - - connect(&runner, &CommandRunner::outputLine, this, - &NvidiaInstaller::progressMessage); + QPointer guard(this); + runAsyncTask([guard]() { + if (!guard) { + return; + } - emit progressMessage(tr("Checking RPM Fusion repositories...")); + CommandRunner runner; + attachRunnerLogging(runner, guard); + + emitProgressAsync(guard, + NvidiaInstaller::tr("Checking RPM Fusion repositories...")); + + CommandRunner rpmRunner; + const auto fedoraResult = + rpmRunner.run(QStringLiteral("rpm"), + {QStringLiteral("-E"), QStringLiteral("%fedora")}); + + const QString fedoraVersion = fedoraResult.stdout.trimmed(); + if (fedoraVersion.isEmpty()) { + QMetaObject::invokeMethod( + guard, + [guard]() { + if (guard) { + emit guard->installFinished( + false, NvidiaInstaller::tr("Platform version could not be detected.")); + } + }, + Qt::QueuedConnection); + return; + } - CommandRunner rpmRunner; - const auto fedoraResult = rpmRunner.run( - QStringLiteral("rpm"), {QStringLiteral("-E"), QStringLiteral("%fedora")}); + auto result = runner.runAsRoot( + QStringLiteral("dnf"), + {QStringLiteral("install"), QStringLiteral("-y"), + QStringLiteral("https://mirrors.rpmfusion.org/free/fedora/" + "rpmfusion-free-release-%1.noarch.rpm") + .arg(fedoraVersion), + QStringLiteral("https://mirrors.rpmfusion.org/nonfree/fedora/" + "rpmfusion-nonfree-release-%1.noarch.rpm") + .arg(fedoraVersion)}); - const QString fedoraVersion = fedoraResult.stdout.trimmed(); - if (fedoraVersion.isEmpty()) { - emit installFinished(false, tr("Platform version could not be detected.")); - return; - } - - auto result = runner.runAsRoot( - QStringLiteral("dnf"), - {QStringLiteral("install"), QStringLiteral("-y"), - QStringLiteral("https://mirrors.rpmfusion.org/free/fedora/" - "rpmfusion-free-release-%1.noarch.rpm") - .arg(fedoraVersion), - QStringLiteral("https://mirrors.rpmfusion.org/nonfree/fedora/" - "rpmfusion-nonfree-release-%1.noarch.rpm") - .arg(fedoraVersion)}); - - if (!result.success()) { - emit installFinished(false, - tr("Failed to enable RPM Fusion repositories: ") + - result.stderr); - return; - } + if (!result.success()) { + const QString error = + NvidiaInstaller::tr("Failed to enable RPM Fusion repositories: ") + + result.stderr.trimmed(); + QMetaObject::invokeMethod( + guard, + [guard, error]() { + if (guard) { + emit guard->installFinished(false, error); + } + }, + Qt::QueuedConnection); + return; + } - emit progressMessage( - tr("Installing the proprietary NVIDIA driver (akmod-nvidia)...")); + emitProgressAsync( + guard, NvidiaInstaller::tr( + "Installing the proprietary NVIDIA driver (akmod-nvidia)...")); - result = runner.runAsRoot(QStringLiteral("dnf"), - {QStringLiteral("install"), QStringLiteral("-y"), - QStringLiteral("akmod-nvidia")}); + QStringList installArgs{QStringLiteral("install"), QStringLiteral("-y"), + QStringLiteral("--refresh"), + QStringLiteral("--best"), + QStringLiteral("--allowerasing")}; + installArgs << buildDriverInstallTargets(QStringLiteral("akmod-nvidia")); + result = runner.runAsRoot(QStringLiteral("dnf"), installArgs); - if (!result.success()) { - emit installFinished(false, tr("Installation failed: ") + result.stderr); - return; - } + if (!result.success()) { + const QString error = + NvidiaInstaller::tr("Installation failed: ") + result.stderr.trimmed(); + QMetaObject::invokeMethod( + guard, + [guard, error]() { + if (guard) { + emit guard->installFinished(false, error); + } + }, + Qt::QueuedConnection); + return; + } - emit progressMessage(tr("Building the kernel module (akmods --force)...")); - runner.runAsRoot(QStringLiteral("akmods"), {QStringLiteral("--force")}); + emitProgressAsync( + guard, + NvidiaInstaller::tr("Building the kernel module (akmods --force)...")); + result = runner.runAsRoot(QStringLiteral("akmods"), {QStringLiteral("--force")}); + if (!result.success()) { + const QString error = + NvidiaInstaller::tr("Kernel module build failed: ") + + (result.stderr.trimmed().isEmpty() ? result.stdout.trimmed() + : result.stderr.trimmed()); + QMetaObject::invokeMethod( + guard, + [guard, error]() { + if (guard) { + emit guard->installFinished(false, error); + } + }, + Qt::QueuedConnection); + return; + } - const QString sessionType = detectSessionType(); - QString sessionError; - if (!applySessionSpecificSetup(runner, sessionType, &sessionError)) { - emit installFinished(false, sessionError); - return; - } + const QString sessionType = SessionUtil::detectSessionType(); + QString sessionError; + if (!guard->applySessionSpecificSetup(runner, sessionType, &sessionError)) { + QMetaObject::invokeMethod( + guard, + [guard, sessionError]() { + if (guard) { + emit guard->installFinished(false, sessionError); + } + }, + Qt::QueuedConnection); + return; + } - emit installFinished( - true, - tr("The proprietary NVIDIA driver was installed successfully. Please " - "restart the system.")); + QMetaObject::invokeMethod( + guard, + [guard]() { + if (guard) { + emit guard->installFinished( + true, + NvidiaInstaller::tr("The proprietary NVIDIA driver was installed " + "successfully. Please restart the system.")); + } + }, + Qt::QueuedConnection); + }); } void NvidiaInstaller::installOpenSource() { - CommandRunner runner; + QPointer guard(this); + runAsyncTask([guard]() { + if (!guard) { + return; + } - connect(&runner, &CommandRunner::outputLine, this, - &NvidiaInstaller::progressMessage); + CommandRunner runner; + attachRunnerLogging(runner, guard); - emit progressMessage(tr("Switching to the open-source driver...")); + emitProgressAsync( + guard, NvidiaInstaller::tr("Switching to NVIDIA open kernel modules...")); - // Once kapali kaynak paketleri kaldir. - auto result = runner.runAsRoot( - QStringLiteral("dnf"), - {QStringLiteral("remove"), QStringLiteral("-y"), - QStringLiteral("akmod-nvidia"), QStringLiteral("xorg-x11-drv-nvidia*")}); + auto result = runner.runAsRoot( + QStringLiteral("dnf"), + QStringList{QStringLiteral("remove"), QStringLiteral("-y")} + + kKernelPackageCleanupTargets); - if (!result.success()) { - emit installFinished(false, tr("Failed to remove proprietary packages: ") + - result.stderr); - return; - } + if (!result.success()) { + const QString error = + NvidiaInstaller::tr("Failed to remove conflicting NVIDIA kernel packages: ") + + commandError(result); + QMetaObject::invokeMethod( + guard, + [guard, error]() { + if (guard) { + emit guard->installFinished(false, error); + } + }, + Qt::QueuedConnection); + return; + } - // Nouveau ve temel Mesa paketlerini garanti altina al. - result = runner.runAsRoot(QStringLiteral("dnf"), - {QStringLiteral("install"), QStringLiteral("-y"), - QStringLiteral("xorg-x11-drv-nouveau"), - QStringLiteral("mesa-dri-drivers")}); + QStringList installArgs{QStringLiteral("install"), QStringLiteral("-y"), + QStringLiteral("--refresh"), + QStringLiteral("--best"), + QStringLiteral("--allowerasing")}; + installArgs + << buildDriverInstallTargets(QStringLiteral("akmod-nvidia-open")); + result = runner.runAsRoot(QStringLiteral("dnf"), installArgs); - if (!result.success()) { - emit installFinished(false, tr("Open-source driver installation failed: ") + - result.stderr); - return; - } + if (!result.success()) { + const QString error = + NvidiaInstaller::tr("Open NVIDIA kernel module installation failed: ") + + commandError(result); + QMetaObject::invokeMethod( + guard, + [guard, error]() { + if (guard) { + emit guard->installFinished(false, error); + } + }, + Qt::QueuedConnection); + return; + } - runner.runAsRoot(QStringLiteral("dracut"), {QStringLiteral("--force")}); + emitProgressAsync( + guard, + NvidiaInstaller::tr("Building the kernel module (akmods --force)...")); + result = runner.runAsRoot(QStringLiteral("akmods"), + {QStringLiteral("--force")}); + if (!result.success()) { + const QString error = + NvidiaInstaller::tr("Kernel module build failed: ") + + commandError(result, NvidiaInstaller::tr("unknown error")); + QMetaObject::invokeMethod( + guard, + [guard, error]() { + if (guard) { + emit guard->installFinished(false, error); + } + }, + Qt::QueuedConnection); + return; + } - emit installFinished(true, - tr("The open-source driver (Nouveau) was installed. " - "Please restart the system.")); + QString sessionError; + const QString sessionType = SessionUtil::detectSessionType(); + if (!guard->applySessionSpecificSetup(runner, sessionType, &sessionError)) { + QMetaObject::invokeMethod( + guard, + [guard, sessionError]() { + if (guard) { + emit guard->installFinished(false, sessionError); + } + }, + Qt::QueuedConnection); + return; + } + + runner.runAsRoot(QStringLiteral("dracut"), {QStringLiteral("--force")}); + + QMetaObject::invokeMethod( + guard, + [guard]() { + if (guard) { + emit guard->installFinished( + true, + NvidiaInstaller::tr("NVIDIA open kernel modules were installed " + "successfully. Please restart the system.")); + } + }, + Qt::QueuedConnection); + }); } void NvidiaInstaller::remove() { - CommandRunner runner; - connect(&runner, &CommandRunner::outputLine, this, - &NvidiaInstaller::progressMessage); + QPointer guard(this); + runAsyncTask([guard]() { + if (!guard) { + return; + } - emit progressMessage(tr("Removing the NVIDIA driver...")); + CommandRunner runner; + attachRunnerLogging(runner, guard); - const auto result = runner.runAsRoot( - QStringLiteral("dnf"), - {QStringLiteral("remove"), QStringLiteral("-y"), - QStringLiteral("akmod-nvidia"), QStringLiteral("xorg-x11-drv-nvidia*")}); + emitProgressAsync(guard, + NvidiaInstaller::tr("Removing the NVIDIA driver...")); - emit removeFinished(result.success(), - result.success() - ? tr("Driver removed successfully.") - : tr("Removal failed: ") + result.stderr); + const auto result = runner.runAsRoot( + QStringLiteral("dnf"), + {QStringLiteral("remove"), QStringLiteral("-y"), + QStringLiteral("akmod-nvidia"), + QStringLiteral("akmod-nvidia-open"), + QStringLiteral("xorg-x11-drv-nvidia*")}); + + const bool success = result.success(); + const QString message = + success ? NvidiaInstaller::tr("Driver removed successfully.") + : NvidiaInstaller::tr("Removal failed: ") + result.stderr.trimmed(); + QMetaObject::invokeMethod( + guard, + [guard, success, message]() { + if (guard) { + emit guard->removeFinished(success, message); + } + }, + Qt::QueuedConnection); + }); } void NvidiaInstaller::deepClean() { - CommandRunner runner; - connect(&runner, &CommandRunner::outputLine, this, - &NvidiaInstaller::progressMessage); - - emit progressMessage(tr("Cleaning legacy driver leftovers...")); + QPointer guard(this); + runAsyncTask([guard]() { + if (!guard) { + return; + } - runner.runAsRoot(QStringLiteral("dnf"), - {QStringLiteral("remove"), QStringLiteral("-y"), - QStringLiteral("*nvidia*"), QStringLiteral("*akmod*")}); + CommandRunner runner; + attachRunnerLogging(runner, guard); + + emitProgressAsync(guard, + NvidiaInstaller::tr("Cleaning legacy driver leftovers...")); + + const auto removeResult = runner.runAsRoot( + QStringLiteral("dnf"), + {QStringLiteral("remove"), QStringLiteral("-y"), + QStringLiteral("*nvidia*"), QStringLiteral("*akmod*"), + QStringLiteral("*nvidia-open*")}); + + if (!removeResult.success()) { + const QString error = + NvidiaInstaller::tr("Deep clean failed: ") + removeResult.stderr.trimmed(); + QMetaObject::invokeMethod( + guard, + [guard, error]() { + if (guard) { + emit guard->removeFinished(false, error); + } + }, + Qt::QueuedConnection); + return; + } - runner.runAsRoot(QStringLiteral("dnf"), - {QStringLiteral("clean"), QStringLiteral("all")}); + const auto cleanResult = + runner.runAsRoot(QStringLiteral("dnf"), + {QStringLiteral("clean"), QStringLiteral("all")}); + if (!cleanResult.success()) { + const QString error = NvidiaInstaller::tr("DNF cache cleanup failed: ") + + cleanResult.stderr.trimmed(); + QMetaObject::invokeMethod( + guard, + [guard, error]() { + if (guard) { + emit guard->removeFinished(false, error); + } + }, + Qt::QueuedConnection); + return; + } - emit progressMessage(tr("Deep clean completed.")); + QMetaObject::invokeMethod( + guard, + [guard]() { + if (guard) { + emit guard->progressMessage(NvidiaInstaller::tr("Deep clean completed.")); + emit guard->removeFinished( + true, NvidiaInstaller::tr("Legacy NVIDIA cleanup completed.")); + } + }, + Qt::QueuedConnection); + }); } -QString NvidiaInstaller::detectSessionType() const { - const QString envType = - qEnvironmentVariable("XDG_SESSION_TYPE").trimmed().toLower(); - if (!envType.isEmpty()) - return envType; - - CommandRunner runner; - const auto loginctl = - runner.run(QStringLiteral("loginctl"), - {QStringLiteral("show-session"), - qEnvironmentVariable("XDG_SESSION_ID"), QStringLiteral("-p"), - QStringLiteral("Type"), QStringLiteral("--value")}); - - if (loginctl.success()) { - const QString type = loginctl.stdout.trimmed().toLower(); - if (!type.isEmpty()) - return type; - } - return QStringLiteral("unknown"); -} bool NvidiaInstaller::applySessionSpecificSetup(CommandRunner &runner, const QString &sessionType, diff --git a/src/backend/nvidia/installer.h b/src/backend/nvidia/installer.h index 09c9385..fc564a4 100644 --- a/src/backend/nvidia/installer.h +++ b/src/backend/nvidia/installer.h @@ -61,7 +61,6 @@ class NvidiaInstaller : public QObject { void setBusy(bool busy); void runAsyncTask(const std::function &task); void setProprietaryAgreement(bool required, const QString &text); - QString detectSessionType() const; bool applySessionSpecificSetup(CommandRunner &runner, const QString &sessionType, QString *errorMessage); diff --git a/src/backend/nvidia/updater.cpp b/src/backend/nvidia/updater.cpp index 6302332..60da4a3 100644 --- a/src/backend/nvidia/updater.cpp +++ b/src/backend/nvidia/updater.cpp @@ -1,6 +1,8 @@ #include "updater.h" #include "detector.h" #include "system/commandrunner.h" +#include "system/capabilityprobe.h" +#include "system/sessionutil.h" #include "versionparser.h" #include @@ -11,8 +13,7 @@ namespace { -const QStringList kVersionLockedDriverPackages = { - QStringLiteral("akmod-nvidia"), +const QStringList kSharedVersionLockedDriverPackages = { QStringLiteral("xorg-x11-drv-nvidia"), QStringLiteral("xorg-x11-drv-nvidia-libs"), QStringLiteral("xorg-x11-drv-nvidia-cuda"), @@ -41,82 +42,130 @@ QString commandError(const CommandRunner::Result &result, return fallback; } +QString normalizedTransactionOutput(const CommandRunner::Result &result) { + return (result.stdout + QLatin1Char('\n') + result.stderr).toLower(); +} + struct UpdateStatusSnapshot { QString currentVersion; QString latestVersion; QStringList availableVersions; + bool remoteCatalogAvailable = false; bool updateAvailable = false; QString message; }; -QString detectSessionTypeImpl() { - const QString envType = - qEnvironmentVariable("XDG_SESSION_TYPE").trimmed().toLower(); - if (!envType.isEmpty()) { - return envType; +QString firstNonEmptyLine(const QString &text) { + const QStringList lines = text.split(QLatin1Char('\n'), Qt::SkipEmptyParts); + for (const QString &line : lines) { + const QString trimmedLine = line.trimmed(); + if (!trimmedLine.isEmpty()) { + return trimmedLine; + } + } + + return {}; +} + +QString detectInstalledKernelPackageName() { + if (!CapabilityProbe::isToolAvailable(QStringLiteral("rpm"))) { + return QStringLiteral("akmod-nvidia"); } CommandRunner runner; - const auto loginctl = - runner.run(QStringLiteral("loginctl"), - {QStringLiteral("show-session"), - qEnvironmentVariable("XDG_SESSION_ID"), QStringLiteral("-p"), - QStringLiteral("Type"), QStringLiteral("--value")}); - - if (loginctl.success()) { - const QString type = loginctl.stdout.trimmed().toLower(); - if (!type.isEmpty()) { - return type; - } + const auto openResult = runner.run( + QStringLiteral("rpm"), + {QStringLiteral("-q"), QStringLiteral("akmod-nvidia-open")}); + if (openResult.success()) { + return QStringLiteral("akmod-nvidia-open"); } - return QStringLiteral("unknown"); + return QStringLiteral("akmod-nvidia"); +} + +QString queryLatestRemoteVersion(CommandRunner &runner, + const QString &kernelPackageName) { + const auto result = runner.run( + QStringLiteral("dnf"), + {QStringLiteral("--refresh"), QStringLiteral("repoquery"), + QStringLiteral("--latest-limit"), QStringLiteral("1"), + QStringLiteral("--qf"), QStringLiteral("%{epoch}:%{version}-%{release}"), + kernelPackageName}); + + if (!result.success()) { + return {}; + } + + return firstNonEmptyLine(result.stdout); } UpdateStatusSnapshot collectUpdateStatus() { UpdateStatusSnapshot snapshot; NvidiaDetector detector; + const QString kernelPackageName = detectInstalledKernelPackageName(); snapshot.currentVersion = detector.installedDriverVersion(); if (QStandardPaths::findExecutable(QStringLiteral("dnf")).isEmpty()) { - snapshot.message = QStringLiteral("dnf bulunamadi."); + snapshot.message = NvidiaUpdater::tr("dnf not found."); return snapshot; } CommandRunner runner; const auto listResult = runner.run(QStringLiteral("dnf"), - {QStringLiteral("list"), QStringLiteral("--showduplicates"), - QStringLiteral("akmod-nvidia")}); + {QStringLiteral("--refresh"), QStringLiteral("list"), + QStringLiteral("--showduplicates"), + kernelPackageName}); if (listResult.success()) { snapshot.availableVersions = NvidiaVersionParser::parseAvailablePackageVersions( - listResult.stdout, QStringLiteral("akmod-nvidia")); + listResult.stdout, kernelPackageName); + snapshot.remoteCatalogAvailable = !snapshot.availableVersions.isEmpty(); + } + + snapshot.latestVersion = queryLatestRemoteVersion(runner, kernelPackageName); + if (snapshot.latestVersion.isEmpty() && !snapshot.availableVersions.isEmpty()) { + snapshot.latestVersion = snapshot.availableVersions.constLast(); } if (snapshot.currentVersion.isEmpty()) { - snapshot.message = QStringLiteral("Kurulu NVIDIA surucusu bulunamadi."); + if (snapshot.remoteCatalogAvailable) { + snapshot.updateAvailable = true; + snapshot.message = + snapshot.latestVersion.isEmpty() + ? NvidiaUpdater::tr( + "Online NVIDIA packages were found. You can download and install the driver now.") + : NvidiaUpdater::tr( + "Online NVIDIA driver found. Latest remote version: %1") + .arg(snapshot.latestVersion); + } else { + snapshot.message = NvidiaUpdater::tr( + "No online NVIDIA package catalog was found. RPM Fusion may not be configured yet."); + } return snapshot; } const auto checkResult = runner.run(QStringLiteral("dnf"), {QStringLiteral("check-update"), - QStringLiteral("akmod-nvidia")}); + kernelPackageName}); if (checkResult.exitCode == 100) { - snapshot.latestVersion = NvidiaVersionParser::parseCheckUpdateVersion( - checkResult.stdout, QStringLiteral("akmod-nvidia")); + const QString checkUpdateVersion = NvidiaVersionParser::parseCheckUpdateVersion( + checkResult.stdout, kernelPackageName); + if (!checkUpdateVersion.isEmpty()) { + snapshot.latestVersion = checkUpdateVersion; + } snapshot.updateAvailable = true; snapshot.message = snapshot.latestVersion.isEmpty() - ? QStringLiteral("Guncelleme bulundu (surum ayrintisi alinamadi).") - : QStringLiteral("Guncelleme bulundu: %1") + ? NvidiaUpdater::tr("Update found (version details unavailable).") + : NvidiaUpdater::tr("Update found: %1") .arg(snapshot.latestVersion); } else if (checkResult.exitCode == 0) { - snapshot.message = QStringLiteral("Surucu guncel. Yeni surum bulunamadi."); + snapshot.message = NvidiaUpdater::tr("Driver is up to date. No new version found."); } else { - snapshot.message = QStringLiteral("Guncelleme kontrolu basarisiz: %1") + snapshot.message = NvidiaUpdater::tr("Update check failed: %1") .arg(checkResult.stderr.trimmed().isEmpty() ? checkResult.stdout.trimmed() : checkResult.stderr.trimmed()); @@ -125,6 +174,62 @@ UpdateStatusSnapshot collectUpdateStatus() { return snapshot; } +void emitProgressAsync(const QPointer &guard, + const QString &message) { + QMetaObject::invokeMethod( + guard, + [guard, message]() { + if (guard) { + emit guard->progressMessage(message); + } + }, + Qt::QueuedConnection); +} + +void attachRunnerLogging(CommandRunner &runner, + const QPointer &guard) { + QObject::connect(&runner, &CommandRunner::outputLine, guard, + [guard](const QString &message) { + emitProgressAsync(guard, message); + }); + + QObject::connect(&runner, &CommandRunner::errorLine, guard, + [guard](const QString &message) { + emitProgressAsync(guard, message); + }); + + QObject::connect( + &runner, &CommandRunner::commandStarted, guard, + [guard](const QString &program, const QStringList &args, int attempt) { + QStringList visibleArgs = args; + if (!visibleArgs.isEmpty() && + visibleArgs.constFirst().contains(QStringLiteral("ro-control-helper"))) { + visibleArgs.removeFirst(); + } + + const QString commandLine = + QStringLiteral("$ %1 %2") + .arg(program, visibleArgs.join(QLatin1Char(' ')).trimmed()); + emitProgressAsync( + guard, NvidiaUpdater::tr("Starting command (attempt %1): %2") + .arg(attempt) + .arg(commandLine.trimmed())); + }); + + QObject::connect(&runner, &CommandRunner::commandFinished, guard, + [guard](const QString &program, int exitCode, int attempt, + int elapsedMs) { + emitProgressAsync( + guard, + NvidiaUpdater::tr( + "Command finished (attempt %1, exit %2, %3 ms): %4") + .arg(attempt) + .arg(exitCode) + .arg(elapsedMs) + .arg(program)); + }); +} + } // namespace NvidiaUpdater::NvidiaUpdater(QObject *parent) : QObject(parent) {} @@ -141,7 +246,7 @@ void NvidiaUpdater::setBusy(bool busy) { void NvidiaUpdater::runAsyncTask(const std::function &task) { if (m_busy) { emit progressMessage( - QStringLiteral("Baska bir surucu islemi zaten calisiyor.")); + tr("Another driver operation is already running.")); return; } @@ -173,22 +278,75 @@ void NvidiaUpdater::setAvailableVersions(const QStringList &versions) { emit availableVersionsChanged(); } +bool NvidiaUpdater::transactionChanged(const CommandRunner::Result &result) const { + const QString output = normalizedTransactionOutput(result); + + if (output.contains(QStringLiteral("nothing to do")) || + output.contains(QStringLiteral("nothing to do.")) || + output.contains(QStringLiteral("no packages marked for upgrade")) || + output.contains(QStringLiteral("no packages marked for update")) || + output.contains(QStringLiteral("no packages marked for reinstall")) || + output.contains(QStringLiteral("package is already installed"))) { + return false; + } + + return true; +} + QString NvidiaUpdater::detectSessionType() const { - return detectSessionTypeImpl(); + return SessionUtil::detectSessionType(); +} + +QString NvidiaUpdater::detectInstalledKernelPackageName() const { + return ::detectInstalledKernelPackageName(); } QStringList NvidiaUpdater::buildDriverTargets(const QString &version, - const QString &sessionType) const { + const QString &sessionType, + const QString &kernelPackageName) const { Q_UNUSED(sessionType); QStringList targets; + QStringList versionLockedPackages{kernelPackageName}; + versionLockedPackages << kSharedVersionLockedDriverPackages; targets << NvidiaVersionParser::buildVersionedPackageSpecs( - kVersionLockedDriverPackages, version); + versionLockedPackages, version); targets << kFloatingDriverPackages; return targets; } +QStringList NvidiaUpdater::buildTransactionArguments( + const QString &requestedVersion, const QString &installedVersion, + const QString &sessionType, const QString &kernelPackageName) const { + const QString normalizedRequestedVersion = requestedVersion.trimmed(); + const QString normalizedInstalledVersion = installedVersion.trimmed(); + const QString targetVersion = + normalizedRequestedVersion.isEmpty() ? m_latestVersion.trimmed() + : normalizedRequestedVersion; + + QStringList args; + if (normalizedInstalledVersion.isEmpty()) { + args << QStringLiteral("install"); + } else if (!targetVersion.isEmpty()) { + args << QStringLiteral("distro-sync"); + } else { + // Installing the named NVIDIA package set keeps the transaction scoped to + // the driver stack instead of invoking a broad system update. + args << QStringLiteral("install"); + } + + args << QStringLiteral("-y") << QStringLiteral("--refresh") + << QStringLiteral("--best"); + + if (!normalizedInstalledVersion.isEmpty()) { + args << QStringLiteral("--allowerasing"); + } + + args << buildDriverTargets(targetVersion, sessionType, kernelPackageName); + return args; +} + bool NvidiaUpdater::finalizeDriverChange(CommandRunner &runner, const QString &sessionType, QString *errorMessage) { @@ -196,23 +354,23 @@ bool NvidiaUpdater::finalizeDriverChange(CommandRunner &runner, runner.runAsRoot(QStringLiteral("akmods"), {QStringLiteral("--force")}); if (!result.success()) { if (errorMessage != nullptr) { - *errorMessage = QStringLiteral("Kernel modulu derlenemedi: ") + - commandError(result, QStringLiteral("bilinmeyen hata")); + *errorMessage = tr("Kernel module build failed: ") + + commandError(result, tr("unknown error")); } return false; } if (sessionType == QStringLiteral("wayland")) { - emit progressMessage(QStringLiteral( - "Wayland tespit edildi: nvidia-drm.modeset=1 ayari guncelleniyor...")); + emit progressMessage( + tr("Wayland detected: applying nvidia-drm.modeset=1...")); result = runner.runAsRoot(QStringLiteral("grubby"), {QStringLiteral("--update-kernel=ALL"), QStringLiteral("--args=nvidia-drm.modeset=1")}); if (!result.success()) { if (errorMessage != nullptr) { *errorMessage = - QStringLiteral("Wayland kernel parametresi guncellenemedi: ") + - commandError(result, QStringLiteral("bilinmeyen hata")); + tr("Failed to update the Wayland kernel parameter: ") + + commandError(result, tr("unknown error")); } return false; } @@ -239,8 +397,8 @@ void NvidiaUpdater::refreshAvailableVersions() { guard->setAvailableVersions(snapshot.availableVersions); emit guard->progressMessage( snapshot.availableVersions.isEmpty() - ? QStringLiteral("Kullanilabilir surum bulunamadi.") - : QStringLiteral("Kullanilabilir surum sayisi: %1") + ? NvidiaUpdater::tr("No available versions found.") + : NvidiaUpdater::tr("Available versions: %1") .arg(snapshot.availableVersions.size())); }, Qt::QueuedConnection); @@ -250,7 +408,7 @@ void NvidiaUpdater::refreshAvailableVersions() { void NvidiaUpdater::checkForUpdate() { // TR: Her kontrol denemesinde UI'ye gorunur bir baslangic mesaji gonder. // EN: Always emit a visible start message for each check request. - emit progressMessage(QStringLiteral("Guncelleme kontrolu baslatildi...")); + emit progressMessage(tr("Starting update check...")); QPointer guard(this); runAsyncTask([guard]() { @@ -297,20 +455,7 @@ void NvidiaUpdater::applyVersion(const QString &version) { } CommandRunner runner; - QObject::connect(&runner, &CommandRunner::outputLine, guard, - [guard](const QString &message) { - if (!guard) { - return; - } - QMetaObject::invokeMethod( - guard, - [guard, message]() { - if (guard) { - emit guard->progressMessage(message); - } - }, - Qt::QueuedConnection); - }); + attachRunnerLogging(runner, guard); if (QStandardPaths::findExecutable(QStringLiteral("dnf")).isEmpty()) { QMetaObject::invokeMethod( @@ -318,7 +463,7 @@ void NvidiaUpdater::applyVersion(const QString &version) { [guard]() { if (guard) { emit guard->updateFinished(false, - QStringLiteral("dnf bulunamadi.")); + NvidiaUpdater::tr("dnf not found.")); } }, Qt::QueuedConnection); @@ -327,7 +472,8 @@ void NvidiaUpdater::applyVersion(const QString &version) { NvidiaDetector detector; const QString installedVersion = detector.installedDriverVersion(); - const QString sessionType = detectSessionTypeImpl(); + const QString sessionType = SessionUtil::detectSessionType(); + const QString kernelPackageName = guard->detectInstalledKernelPackageName(); if (!trimmedVersion.isEmpty() && !knownVersions.contains(trimmedVersion)) { QMetaObject::invokeMethod( @@ -336,46 +482,30 @@ void NvidiaUpdater::applyVersion(const QString &version) { if (guard) { emit guard->updateFinished( false, - QStringLiteral("Secilen surum repo listesinde bulunamadi.")); + NvidiaUpdater::tr("Selected version not found in the repository.")); } }, Qt::QueuedConnection); return; } - QMetaObject::invokeMethod( - guard, - [guard, trimmedVersion]() { - if (!guard) { - return; - } - - emit guard->progressMessage( - trimmedVersion.isEmpty() - ? QStringLiteral( - "NVIDIA surucusu en son surume guncelleniyor...") - : QStringLiteral( - "NVIDIA surucusu secilen surume geciriliyor: %1") - .arg(trimmedVersion)); - }, - Qt::QueuedConnection); + emitProgressAsync( + guard, trimmedVersion.isEmpty() + ? NvidiaUpdater::tr( + "Updating NVIDIA driver to the latest version...") + : NvidiaUpdater::tr( + "Switching NVIDIA driver to selected version: %1") + .arg(trimmedVersion)); - const QStringList packageTargets = - guard->buildDriverTargets(trimmedVersion, sessionType); - auto args = QStringList{ - trimmedVersion.isEmpty() - ? (installedVersion.isEmpty() ? QStringLiteral("install") - : QStringLiteral("update")) - : (installedVersion.isEmpty() ? QStringLiteral("install") - : QStringLiteral("distro-sync")), - QStringLiteral("-y"), QStringLiteral("--allowerasing")}; - args << packageTargets; + const QStringList args = + guard->buildTransactionArguments(trimmedVersion, installedVersion, + sessionType, kernelPackageName); auto result = runner.runAsRoot(QStringLiteral("dnf"), args); if (!result.success()) { const QString error = - QStringLiteral("Guncelleme basarisiz: ") + - commandError(result, QStringLiteral("bilinmeyen hata")); + NvidiaUpdater::tr("Update failed: ") + + commandError(result, NvidiaUpdater::tr("unknown error")); QMetaObject::invokeMethod( guard, [guard, error]() { @@ -387,15 +517,40 @@ void NvidiaUpdater::applyVersion(const QString &version) { return; } - QMetaObject::invokeMethod( - guard, - [guard]() { - if (guard) { - emit guard->progressMessage( - QStringLiteral("Kernel modulu yeniden derleniyor...")); - } - }, - Qt::QueuedConnection); + if (!guard->transactionChanged(result)) { + const UpdateStatusSnapshot snapshot = collectUpdateStatus(); + const QString noChangeMessage = + trimmedVersion.isEmpty() + ? NvidiaUpdater::tr( + "Driver is already at the latest available version.") + : NvidiaUpdater::tr( + "Selected driver version is already installed."); + + QMetaObject::invokeMethod( + guard, + [guard, snapshot, noChangeMessage]() { + if (!guard) { + return; + } + + if (guard->m_currentVersion != snapshot.currentVersion) { + guard->m_currentVersion = snapshot.currentVersion; + emit guard->currentVersionChanged(); + } + if (guard->m_updateAvailable != snapshot.updateAvailable) { + guard->m_updateAvailable = snapshot.updateAvailable; + emit guard->updateAvailableChanged(); + } + guard->setLatestVersion(snapshot.latestVersion); + guard->setAvailableVersions(snapshot.availableVersions); + emit guard->progressMessage(noChangeMessage); + emit guard->updateFinished(true, noChangeMessage); + }, + Qt::QueuedConnection); + return; + } + + emitProgressAsync(guard, NvidiaUpdater::tr("Rebuilding kernel module...")); QString finalizeError; if (!guard->finalizeDriverChange(runner, sessionType, &finalizeError)) { @@ -414,13 +569,13 @@ void NvidiaUpdater::applyVersion(const QString &version) { const QString successMessage = trimmedVersion.isEmpty() ? (installedVersion.isEmpty() - ? QStringLiteral("En son surum basariyla kuruldu. Lutfen " - "sistemi yeniden baslatin.") - : QStringLiteral("Surucu basariyla guncellendi. Lutfen " - "sistemi yeniden baslatin.")) - : QStringLiteral( - "Secilen surum basariyla uygulandi. Lutfen sistemi " - "yeniden baslatin."); + ? NvidiaUpdater::tr("Latest version installed successfully. " + "Please restart the system.") + : NvidiaUpdater::tr("Driver updated successfully. " + "Please restart the system.")) + : NvidiaUpdater::tr( + "Selected version applied successfully. " + "Please restart the system."); QMetaObject::invokeMethod( guard, diff --git a/src/backend/nvidia/updater.h b/src/backend/nvidia/updater.h index 482d0cc..53303a8 100644 --- a/src/backend/nvidia/updater.h +++ b/src/backend/nvidia/updater.h @@ -5,7 +5,7 @@ #include #include -class CommandRunner; +#include "system/commandrunner.h" // NvidiaUpdater: Kurulu surucu ile mevcut en guncel surumu karsilastirir. class NvidiaUpdater : public QObject { @@ -49,8 +49,15 @@ class NvidiaUpdater : public QObject { void runAsyncTask(const std::function &task); void setLatestVersion(const QString &version); void setAvailableVersions(const QStringList &versions); + bool transactionChanged(const CommandRunner::Result &result) const; + QString detectInstalledKernelPackageName() const; + QStringList buildTransactionArguments(const QString &requestedVersion, + const QString &installedVersion, + const QString &sessionType, + const QString &kernelPackageName) const; QStringList buildDriverTargets(const QString &version, - const QString &sessionType) const; + const QString &sessionType, + const QString &kernelPackageName) const; bool finalizeDriverChange(CommandRunner &runner, const QString &sessionType, QString *errorMessage); QString detectSessionType() const; diff --git a/src/backend/system/capabilityprobe.cpp b/src/backend/system/capabilityprobe.cpp new file mode 100644 index 0000000..bc9d725 --- /dev/null +++ b/src/backend/system/capabilityprobe.cpp @@ -0,0 +1,41 @@ +#include "capabilityprobe.h" + +#include "commandrunner.h" + +namespace CapabilityProbe { + +ToolStatus probeTool(const QString &program) { + ToolStatus status; + status.program = program; + status.resolvedPath = CommandRunner::resolveProgramPath(program); + status.available = !status.resolvedPath.isEmpty(); + return status; +} + +bool isToolAvailable(const QString &program) { + return probeTool(program).available; +} + +QStringList missingTools(const QStringList &programs) { + QStringList missing; + for (const QString &program : programs) { + if (!isToolAvailable(program)) { + missing << program; + } + } + + missing.removeDuplicates(); + return missing; +} + +QString missingToolsMessage(const QStringList &programs) { + const QStringList missing = missingTools(programs); + if (missing.isEmpty()) { + return {}; + } + + return QStringLiteral("Missing required tools: %1") + .arg(missing.join(QStringLiteral(", "))); +} + +} // namespace CapabilityProbe diff --git a/src/backend/system/capabilityprobe.h b/src/backend/system/capabilityprobe.h new file mode 100644 index 0000000..967488c --- /dev/null +++ b/src/backend/system/capabilityprobe.h @@ -0,0 +1,19 @@ +#pragma once + +#include +#include + +namespace CapabilityProbe { + +struct ToolStatus { + QString program; + QString resolvedPath; + bool available = false; +}; + +ToolStatus probeTool(const QString &program); +bool isToolAvailable(const QString &program); +QStringList missingTools(const QStringList &programs); +QString missingToolsMessage(const QStringList &programs); + +} // namespace CapabilityProbe diff --git a/src/backend/system/commandrunner.cpp b/src/backend/system/commandrunner.cpp index 924b2a1..486c478 100644 --- a/src/backend/system/commandrunner.cpp +++ b/src/backend/system/commandrunner.cpp @@ -36,11 +36,26 @@ CommandRunner::Result CommandRunner::run(const QString &program, return lastResult; } -QString CommandRunner::resolveProgram(const QString &program) const { +QString CommandRunner::overrideEnvironmentVariableName(const QString &program) { + QString normalized = program.toUpper(); + normalized.replace(QLatin1Char('-'), QLatin1Char('_')); + normalized.replace(QLatin1Char('.'), QLatin1Char('_')); + normalized.replace(QLatin1Char('/'), QLatin1Char('_')); + return QStringLiteral("RO_CONTROL_COMMAND_%1").arg(normalized); +} + +QString CommandRunner::resolveProgramPath(const QString &program) { if (program.isEmpty()) { return {}; } + const QString overridePath = + qEnvironmentVariable(overrideEnvironmentVariableName(program).toUtf8()) + .trimmed(); + if (!overridePath.isEmpty()) { + return overridePath; + } + if (program.contains(QLatin1Char('/'))) { return program; } @@ -48,6 +63,10 @@ QString CommandRunner::resolveProgram(const QString &program) const { return QStandardPaths::findExecutable(program); } +QString CommandRunner::resolveProgram(const QString &program) const { + return resolveProgramPath(program); +} + CommandRunner::Result CommandRunner::runOnce(const QString &program, const QStringList &args, const RunOptions &options, diff --git a/src/backend/system/commandrunner.h b/src/backend/system/commandrunner.h index 4b18e68..c3929cd 100644 --- a/src/backend/system/commandrunner.h +++ b/src/backend/system/commandrunner.h @@ -26,6 +26,7 @@ class CommandRunner : public QObject { }; explicit CommandRunner(QObject *parent = nullptr); + static QString resolveProgramPath(const QString &program); // Bloklayan komut — sonuç dönene kadar bekler Result run(const QString &program, const QStringList &args = {}); @@ -48,6 +49,7 @@ class CommandRunner : public QObject { private: QString resolveProgram(const QString &program) const; + static QString overrideEnvironmentVariableName(const QString &program); Result runOnce(const QString &program, const QStringList &args, const RunOptions &options, int attempt); }; diff --git a/src/backend/system/dnfmanager.cpp b/src/backend/system/dnfmanager.cpp index 3e30c40..e0c0328 100644 --- a/src/backend/system/dnfmanager.cpp +++ b/src/backend/system/dnfmanager.cpp @@ -1,10 +1,9 @@ // DNF paket yoneticisi #include "dnfmanager.h" +#include "capabilityprobe.h" #include "commandrunner.h" -#include - namespace { CommandRunner::Result dnfUnavailableResult() { @@ -25,7 +24,7 @@ DnfManager::DnfManager(QObject *parent) : QObject(parent) { } bool DnfManager::isAvailable() const { - return !QStandardPaths::findExecutable(QStringLiteral("dnf")).isEmpty(); + return CapabilityProbe::isToolAvailable(QStringLiteral("dnf")); } CommandRunner::Result DnfManager::checkUpdates(const QStringList &packages) { diff --git a/src/backend/system/languagemanager.cpp b/src/backend/system/languagemanager.cpp new file mode 100644 index 0000000..764be87 --- /dev/null +++ b/src/backend/system/languagemanager.cpp @@ -0,0 +1,145 @@ +#include "languagemanager.h" + +#include +#include +#include +#include +#include +#include + +namespace { + +struct LanguageEntry { + const char *code; + const char *label; + const char *nativeLabel; + bool shipped; +}; + +constexpr LanguageEntry kSupportedLanguages[] = { + {"system", "System Default", "System Default", true}, + {"en", "English", "English", true}, + {"de", "German", "Deutsch", true}, + {"es", "Spanish", "Espanol", true}, + {"tr", "Turkish", "Turkce", true}, +}; + +} // namespace + +LanguageManager::LanguageManager(QCoreApplication *application, QQmlEngine *engine, + QTranslator *translator, QObject *parent) + : QObject(parent), m_application(application), m_engine(engine), + m_translator(translator) { + QSettings settings; + const QString storedLanguage = + settings.value(QStringLiteral("ui/language"), QStringLiteral("system")) + .toString(); + setCurrentLanguage(storedLanguage); +} + +QString LanguageManager::currentLanguage() const { return m_currentLanguage; } + +QString LanguageManager::effectiveLanguage() const { + return effectiveLanguageCode(m_currentLanguage); +} + +QString LanguageManager::currentLanguageLabel() const { + if (m_currentLanguage == QStringLiteral("system")) { + return QStringLiteral("%1 (%2)") + .arg(displayNameForLanguage(m_currentLanguage), + displayNameForLanguage(effectiveLanguage())); + } + + return displayNameForLanguage(m_currentLanguage); +} + +QVariantList LanguageManager::availableLanguages() const { + QVariantList languages; + for (const auto &entry : kSupportedLanguages) { + QVariantMap language; + language.insert(QStringLiteral("code"), QString::fromLatin1(entry.code)); + language.insert(QStringLiteral("label"), QString::fromLatin1(entry.label)); + language.insert(QStringLiteral("nativeLabel"), + QString::fromLatin1(entry.nativeLabel)); + language.insert(QStringLiteral("shipped"), entry.shipped); + languages.append(language); + } + + return languages; +} + +void LanguageManager::setCurrentLanguage(const QString &languageCode) { + const QString normalizedLanguage = normalizeLanguageCode(languageCode); + if (normalizedLanguage == m_currentLanguage && + loadLanguage(normalizedLanguage)) { + return; + } + + if (!loadLanguage(normalizedLanguage)) { + return; + } + + m_currentLanguage = normalizedLanguage; + + QSettings settings; + settings.setValue(QStringLiteral("ui/language"), m_currentLanguage); + + emit currentLanguageChanged(); +} + +QString LanguageManager::displayNameForLanguage( + const QString &languageCode) const { + const QString normalizedLanguage = normalizeLanguageCode(languageCode); + for (const auto &entry : kSupportedLanguages) { + if (QString::fromLatin1(entry.code) == normalizedLanguage) { + return QString::fromLatin1(entry.nativeLabel); + } + } + + return normalizedLanguage; +} + +QString LanguageManager::normalizeLanguageCode(const QString &languageCode) const { + const QString normalizedLanguage = languageCode.trimmed().toLower(); + for (const auto &entry : kSupportedLanguages) { + if (QString::fromLatin1(entry.code) == normalizedLanguage) { + return normalizedLanguage; + } + } + + return QStringLiteral("system"); +} + +QString LanguageManager::systemLanguageCode() const { + return QLocale::system().name().section(QLatin1Char('_'), 0, 0).toLower(); +} + +QString LanguageManager::effectiveLanguageCode(const QString &languageCode) const { + const QString normalizedLanguage = normalizeLanguageCode(languageCode); + return normalizedLanguage == QStringLiteral("system") ? systemLanguageCode() + : normalizedLanguage; +} + +bool LanguageManager::loadLanguage(const QString &languageCode) { + if (m_application == nullptr || m_engine == nullptr || m_translator == nullptr) { + return false; + } + + const QString effectiveLanguage = effectiveLanguageCode(languageCode); + + m_application->removeTranslator(m_translator); + + bool loaded = false; + if (effectiveLanguage != QStringLiteral("en")) { + loaded = m_translator->load( + QStringLiteral(":/i18n/ro-control_%1.qm").arg(effectiveLanguage)); + } + + if (loaded) { + m_application->installTranslator(m_translator); + } + + m_engine->setUiLanguage(effectiveLanguage); + m_engine->retranslate(); + return true; +} diff --git a/src/backend/system/languagemanager.h b/src/backend/system/languagemanager.h new file mode 100644 index 0000000..6651304 --- /dev/null +++ b/src/backend/system/languagemanager.h @@ -0,0 +1,48 @@ +#pragma once + +#include +#include +#include + +class QApplication; +class QCoreApplication; +class QQmlEngine; +class QTranslator; + +class LanguageManager : public QObject { + Q_OBJECT + + Q_PROPERTY(QString currentLanguage READ currentLanguage WRITE setCurrentLanguage + NOTIFY currentLanguageChanged) + Q_PROPERTY(QString effectiveLanguage READ effectiveLanguage NOTIFY + currentLanguageChanged) + Q_PROPERTY(QString currentLanguageLabel READ currentLanguageLabel NOTIFY + currentLanguageChanged) + Q_PROPERTY(QVariantList availableLanguages READ availableLanguages CONSTANT) + +public: + explicit LanguageManager(QCoreApplication *application, QQmlEngine *engine, + QTranslator *translator, QObject *parent = nullptr); + + QString currentLanguage() const; + QString effectiveLanguage() const; + QString currentLanguageLabel() const; + QVariantList availableLanguages() const; + + Q_INVOKABLE void setCurrentLanguage(const QString &languageCode); + Q_INVOKABLE QString displayNameForLanguage(const QString &languageCode) const; + +signals: + void currentLanguageChanged(); + +private: + QString normalizeLanguageCode(const QString &languageCode) const; + QString systemLanguageCode() const; + QString effectiveLanguageCode(const QString &languageCode) const; + bool loadLanguage(const QString &languageCode); + + QCoreApplication *m_application = nullptr; + QQmlEngine *m_engine = nullptr; + QTranslator *m_translator = nullptr; + QString m_currentLanguage = QStringLiteral("system"); +}; diff --git a/src/backend/system/polkit.cpp b/src/backend/system/polkit.cpp index 87746e1..fbc8cd4 100644 --- a/src/backend/system/polkit.cpp +++ b/src/backend/system/polkit.cpp @@ -2,7 +2,7 @@ #include "polkit.h" -#include +#include "capabilityprobe.h" PolkitHelper::PolkitHelper(QObject *parent) : QObject(parent) { connect(&m_runner, &CommandRunner::outputLine, this, @@ -12,7 +12,7 @@ PolkitHelper::PolkitHelper(QObject *parent) : QObject(parent) { } bool PolkitHelper::isPkexecAvailable() const { - return !QStandardPaths::findExecutable(QStringLiteral("pkexec")).isEmpty(); + return CapabilityProbe::isToolAvailable(QStringLiteral("pkexec")); } CommandRunner::Result PolkitHelper::runPrivileged(const QString &program, diff --git a/src/backend/system/sessionutil.cpp b/src/backend/system/sessionutil.cpp new file mode 100644 index 0000000..f590f9d --- /dev/null +++ b/src/backend/system/sessionutil.cpp @@ -0,0 +1,31 @@ +#include "sessionutil.h" + +#include "commandrunner.h" + +#include + +namespace SessionUtil { + +QString detectSessionType() { + const QString envType = + qEnvironmentVariable("XDG_SESSION_TYPE").trimmed().toLower(); + if (!envType.isEmpty()) + return envType; + + CommandRunner runner; + const auto loginctl = + runner.run(QStringLiteral("loginctl"), + {QStringLiteral("show-session"), + qEnvironmentVariable("XDG_SESSION_ID"), QStringLiteral("-p"), + QStringLiteral("Type"), QStringLiteral("--value")}); + + if (loginctl.success()) { + const QString type = loginctl.stdout.trimmed().toLower(); + if (!type.isEmpty()) + return type; + } + + return QStringLiteral("unknown"); +} + +} // namespace SessionUtil diff --git a/src/backend/system/sessionutil.h b/src/backend/system/sessionutil.h new file mode 100644 index 0000000..8592ab5 --- /dev/null +++ b/src/backend/system/sessionutil.h @@ -0,0 +1,12 @@ +#pragma once + +#include + +namespace SessionUtil { + +// XDG_SESSION_TYPE ortam degiskeni veya loginctl +// uzerinden geçerli oturum turunu tespit eder. +// "wayland", "x11" veya "unknown" doner. +QString detectSessionType(); + +} // namespace SessionUtil diff --git a/src/backend/system/uipreferencesmanager.cpp b/src/backend/system/uipreferencesmanager.cpp new file mode 100644 index 0000000..5f27e2a --- /dev/null +++ b/src/backend/system/uipreferencesmanager.cpp @@ -0,0 +1,106 @@ +#include "uipreferencesmanager.h" + +#include +#include + +namespace { + +struct ThemeModeEntry { + const char *code; + const char *label; +}; + +constexpr ThemeModeEntry kThemeModes[] = { + {"system", "Follow System"}, + {"light", "Light"}, + {"dark", "Dark"}, +}; + +} // namespace + +UiPreferencesManager::UiPreferencesManager(QObject *parent) : QObject(parent) { + QSettings settings; + m_themeMode = normalizeThemeMode( + settings.value(QStringLiteral("ui/themeMode"), m_themeMode).toString()); + m_compactMode = + settings.value(QStringLiteral("ui/compactMode"), m_compactMode).toBool(); + m_showAdvancedInfo = settings + .value(QStringLiteral("ui/showAdvancedInfo"), + m_showAdvancedInfo) + .toBool(); +} + +QString UiPreferencesManager::themeMode() const { return m_themeMode; } + +QVariantList UiPreferencesManager::availableThemeModes() const { + QVariantList modes; + for (const auto &entry : kThemeModes) { + QVariantMap mode; + mode.insert(QStringLiteral("code"), QString::fromLatin1(entry.code)); + mode.insert(QStringLiteral("label"), + QCoreApplication::translate("UiPreferencesManager", + entry.label)); + modes.append(mode); + } + return modes; +} + +bool UiPreferencesManager::compactMode() const { return m_compactMode; } + +bool UiPreferencesManager::showAdvancedInfo() const { + return m_showAdvancedInfo; +} + +void UiPreferencesManager::setThemeMode(const QString &themeMode) { + const QString normalizedThemeMode = normalizeThemeMode(themeMode); + if (normalizedThemeMode == m_themeMode) { + return; + } + + m_themeMode = normalizedThemeMode; + persistValue(QStringLiteral("ui/themeMode"), m_themeMode); + emit themeModeChanged(); +} + +void UiPreferencesManager::setCompactMode(bool compactMode) { + if (compactMode == m_compactMode) { + return; + } + + m_compactMode = compactMode; + persistValue(QStringLiteral("ui/compactMode"), m_compactMode); + emit compactModeChanged(); +} + +void UiPreferencesManager::setShowAdvancedInfo(bool showAdvancedInfo) { + if (showAdvancedInfo == m_showAdvancedInfo) { + return; + } + + m_showAdvancedInfo = showAdvancedInfo; + persistValue(QStringLiteral("ui/showAdvancedInfo"), m_showAdvancedInfo); + emit showAdvancedInfoChanged(); +} + +void UiPreferencesManager::resetToDefaults() { + setThemeMode(QStringLiteral("system")); + setCompactMode(false); + setShowAdvancedInfo(true); +} + +QString UiPreferencesManager::normalizeThemeMode(const QString &themeMode) const { + const QString normalizedThemeMode = themeMode.trimmed().toLower(); + for (const auto &entry : kThemeModes) { + if (normalizedThemeMode == QLatin1String(entry.code)) { + return normalizedThemeMode; + } + } + + return QStringLiteral("system"); +} + +void UiPreferencesManager::persistValue(const QString &key, + const QVariant &value) const { + QSettings settings; + settings.setValue(key, value); +} diff --git a/src/backend/system/uipreferencesmanager.h b/src/backend/system/uipreferencesmanager.h new file mode 100644 index 0000000..9dad79a --- /dev/null +++ b/src/backend/system/uipreferencesmanager.h @@ -0,0 +1,44 @@ +#pragma once + +#include +#include +#include + +class UiPreferencesManager : public QObject { + Q_OBJECT + + Q_PROPERTY(QString themeMode READ themeMode WRITE setThemeMode NOTIFY + themeModeChanged) + Q_PROPERTY(QVariantList availableThemeModes READ availableThemeModes CONSTANT) + Q_PROPERTY(bool compactMode READ compactMode WRITE setCompactMode NOTIFY + compactModeChanged) + Q_PROPERTY(bool showAdvancedInfo READ showAdvancedInfo WRITE + setShowAdvancedInfo NOTIFY showAdvancedInfoChanged) + +public: + explicit UiPreferencesManager(QObject *parent = nullptr); + + QString themeMode() const; + QVariantList availableThemeModes() const; + + bool compactMode() const; + bool showAdvancedInfo() const; + + Q_INVOKABLE void setThemeMode(const QString &themeMode); + Q_INVOKABLE void setCompactMode(bool compactMode); + Q_INVOKABLE void setShowAdvancedInfo(bool showAdvancedInfo); + Q_INVOKABLE void resetToDefaults(); + +signals: + void themeModeChanged(); + void compactModeChanged(); + void showAdvancedInfoChanged(); + +private: + QString normalizeThemeMode(const QString &themeMode) const; + void persistValue(const QString &key, const QVariant &value) const; + + QString m_themeMode = QStringLiteral("system"); + bool m_compactMode = false; + bool m_showAdvancedInfo = true; +}; diff --git a/src/cli/cli.cpp b/src/cli/cli.cpp index 292a667..927be42 100644 --- a/src/cli/cli.cpp +++ b/src/cli/cli.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -65,10 +66,10 @@ QString buildHelpText(const QString &applicationName, stream << "Driver install options:\n"; stream << " --proprietary Install the proprietary akmod-nvidia " "stack.\n"; - stream << " --open-source Install the open-source Nouveau " - "stack.\n"; - stream << " --accept-license Confirm proprietary driver license " - "acceptance.\n\n"; + stream << " --open-source Install the NVIDIA open kernel " + "module stack.\n"; + stream << " --accept-license Confirm NVIDIA license review for " + "the proprietary install path.\n\n"; stream << "Global options:\n"; stream << " -h, --help Show help and exit.\n"; stream << " -v, --version Show version and exit.\n"; @@ -112,11 +113,11 @@ void configureParser(QCommandLineParser &parser, const QString &applicationName, QStringLiteral("Use the proprietary NVIDIA driver install path."))); parser.addOption(QCommandLineOption( {QStringLiteral("open-source")}, - QStringLiteral("Use the open-source Nouveau install path."))); + QStringLiteral("Use the NVIDIA open kernel module install path."))); parser.addOption(QCommandLineOption( {QStringLiteral("accept-license")}, QStringLiteral( - "Confirm that the proprietary NVIDIA license was reviewed."))); + "Confirm that the NVIDIA license was reviewed."))); parser.addPositionalArgument(QStringLiteral("command"), QStringLiteral("CLI command to execute.")); parser.addPositionalArgument( @@ -347,7 +348,23 @@ DiagnosticsSnapshot collectDiagnostics(const QString &applicationName, snapshot.verificationReport = detector.verificationReport(); NvidiaUpdater updater; + QEventLoop updaterLoop; + bool updaterStarted = false; + QObject::connect(&updater, &NvidiaUpdater::busyChanged, &updater, + [&]() { + if (updater.busy()) { + updaterStarted = true; + return; + } + + if (updaterStarted) { + updaterLoop.quit(); + } + }); updater.checkForUpdate(); + if (updater.busy()) { + updaterLoop.exec(); + } snapshot.currentDriverVersion = updater.currentVersion(); snapshot.latestDriverVersion = updater.latestVersion(); snapshot.updateAvailable = updater.updateAvailable(); diff --git a/src/main.cpp b/src/main.cpp index a94bf16..b0ae8bf 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,14 +1,19 @@ #include #include +#include #include #include #include #include #include #include +#include +#include +#include #include #include #include +#include #include "backend/monitor/cpumonitor.h" #include "backend/monitor/gpumonitor.h" @@ -16,6 +21,8 @@ #include "backend/nvidia/detector.h" #include "backend/nvidia/installer.h" #include "backend/nvidia/updater.h" +#include "backend/system/languagemanager.h" +#include "backend/system/uipreferencesmanager.h" #include "cli/cli.h" namespace { @@ -74,18 +81,21 @@ CliExecutionResult executeCliCommand(const RoControlCli::ParsedCommand &command, bool finished = false; bool success = false; QString finalMessage; + QEventLoop loop; QObject::connect(&installer, &NvidiaInstaller::installFinished, &installer, [&](bool ok, const QString &message) { finished = true; success = ok; finalMessage = message; + loop.quit(); }); QObject::connect(&installer, &NvidiaInstaller::removeFinished, &installer, [&](bool ok, const QString &message) { finished = true; success = ok; finalMessage = message; + loop.quit(); }); if (command.action == @@ -98,9 +108,10 @@ CliExecutionResult executeCliCommand(const RoControlCli::ParsedCommand &command, installer.remove(); } else { installer.deepClean(); - finished = true; - success = true; - finalMessage = QStringLiteral("Legacy NVIDIA cleanup completed."); + } + + if (!finished) { + loop.exec(); } if (!finalMessage.isEmpty()) { @@ -123,16 +134,22 @@ CliExecutionResult executeCliCommand(const RoControlCli::ParsedCommand &command, bool finished = false; bool success = false; QString finalMessage; + QEventLoop loop; QObject::connect(&updater, &NvidiaUpdater::updateFinished, &updater, [&](bool ok, const QString &message) { finished = true; success = ok; finalMessage = message; + loop.quit(); }); updater.applyUpdate(); + if (!finished) { + loop.exec(); + } + if (!finalMessage.isEmpty()) { if (success) { appendProgress(finalMessage); @@ -150,6 +167,25 @@ CliExecutionResult executeCliCommand(const RoControlCli::ParsedCommand &command, return result; } +void configureGuiGraphicsEnvironment() { +#if defined(Q_OS_LINUX) + const QByteArray sessionType = + qgetenv("XDG_SESSION_TYPE").trimmed().toLower(); + + // NVIDIA on Fedora/X11 is generally more stable through the GLX path than + // the EGL/DRI2 integration that can emit startup errors. + if (sessionType == "x11" && qEnvironmentVariableIsEmpty("QT_XCB_GL_INTEGRATION")) { + qputenv("QT_XCB_GL_INTEGRATION", QByteArrayLiteral("glx")); + } + + // Keep an explicit escape hatch for hosts that still need software rendering. + if (!qEnvironmentVariableIsEmpty("RO_CONTROL_FORCE_SOFTWARE_RENDER")) { + qputenv("QT_QUICK_BACKEND", QByteArrayLiteral("software")); + QQuickWindow::setGraphicsApi(QSGRendererInterface::Software); + } +#endif +} + } // namespace int main(int argc, char *argv[]) { @@ -159,52 +195,58 @@ int main(int argc, char *argv[]) { const QString applicationDescription = QStringLiteral("ro-Control GPU driver manager and diagnostics CLI."); - { - QCoreApplication cliApp(argc, argv); - cliApp.setApplicationName(QString::fromLatin1(kApplicationName)); - cliApp.setApplicationVersion(QString::fromLatin1(kApplicationVersion)); + QStringList arguments; + arguments.reserve(argc); + for (int i = 0; i < argc; ++i) { + arguments << QString::fromLocal8Bit(argv[i]); + } - const auto command = RoControlCli::parseArguments( - cliApp.arguments(), cliApp.applicationName(), - cliApp.applicationVersion(), applicationDescription); + const auto command = RoControlCli::parseArguments( + arguments, QString::fromLatin1(kApplicationName), + QString::fromLatin1(kApplicationVersion), applicationDescription); - QTextStream out(stdout); - QTextStream err(stderr); + QTextStream out(stdout); + QTextStream err(stderr); - if (command.action == RoControlCli::CommandAction::PrintHelp || - command.action == RoControlCli::CommandAction::PrintVersion) { - out << command.payload; - if (!command.payload.endsWith(QLatin1Char('\n'))) { - out << Qt::endl; - } - return 0; + if (command.action == RoControlCli::CommandAction::PrintHelp || + command.action == RoControlCli::CommandAction::PrintVersion) { + out << command.payload; + if (!command.payload.endsWith(QLatin1Char('\n'))) { + out << Qt::endl; } + return 0; + } - if (command.action == RoControlCli::CommandAction::Invalid) { - err << command.payload << Qt::endl; - err << "Run `ro-control --help` for usage." << Qt::endl; - return 2; - } + if (command.action == RoControlCli::CommandAction::Invalid) { + err << command.payload << Qt::endl; + err << "Run `ro-control --help` for usage." << Qt::endl; + return 2; + } + + if (command.action != RoControlCli::CommandAction::LaunchGui) { + QCoreApplication cliApp(argc, argv); + cliApp.setApplicationName(QString::fromLatin1(kApplicationName)); + cliApp.setApplicationVersion(QString::fromLatin1(kApplicationVersion)); - if (command.action != RoControlCli::CommandAction::LaunchGui) { - const auto result = executeCliCommand(command, cliApp.applicationName(), - cliApp.applicationVersion()); - if (!result.stdoutText.isEmpty()) { - out << result.stdoutText; - if (!result.stdoutText.endsWith(QLatin1Char('\n'))) { - out << Qt::endl; - } + const auto result = executeCliCommand(command, cliApp.applicationName(), + cliApp.applicationVersion()); + if (!result.stdoutText.isEmpty()) { + out << result.stdoutText; + if (!result.stdoutText.endsWith(QLatin1Char('\n'))) { + out << Qt::endl; } - if (!result.stderrText.isEmpty()) { - err << result.stderrText; - if (!result.stderrText.endsWith(QLatin1Char('\n'))) { - err << Qt::endl; - } + } + if (!result.stderrText.isEmpty()) { + err << result.stderrText; + if (!result.stderrText.endsWith(QLatin1Char('\n'))) { + err << Qt::endl; } - return result.exitCode; } + return result.exitCode; } + configureGuiGraphicsEnvironment(); + QApplication app(argc, argv); app.setApplicationName(QString::fromLatin1(kApplicationName)); @@ -216,17 +258,6 @@ int main(int argc, char *argv[]) { "ro-control", QIcon(":/qt/qml/rocontrol/assets/ro-control-logo.svg"))); QTranslator translator; - const QString localeName = QLocale::system().name(); - const QString baseLanguage = - localeName.section(QLatin1Char('_'), 0, 0).toLower(); - - if (translator.load( - QStringLiteral(":/i18n/ro-control_%1.qm").arg(localeName)) || - translator.load( - QStringLiteral(":/i18n/ro-control_%1.qm").arg(baseLanguage))) { - app.installTranslator(&translator); - } - NvidiaDetector detector; NvidiaInstaller installer; NvidiaUpdater updater; @@ -235,14 +266,19 @@ int main(int argc, char *argv[]) { RamMonitor ramMonitor; QQmlApplicationEngine engine; - engine.setInitialProperties({ - {"nvidiaDetector", QVariant::fromValue(&detector)}, - {"nvidiaInstaller", QVariant::fromValue(&installer)}, - {"nvidiaUpdater", QVariant::fromValue(&updater)}, - {"cpuMonitor", QVariant::fromValue(&cpuMonitor)}, - {"gpuMonitor", QVariant::fromValue(&gpuMonitor)}, - {"ramMonitor", QVariant::fromValue(&ramMonitor)}, - }); + LanguageManager languageManager(&app, &engine, &translator); + UiPreferencesManager uiPreferencesManager; + + // Backend nesnelerini tüm QML dosyalarına global olarak aç + engine.rootContext()->setContextProperty("nvidiaDetector", &detector); + engine.rootContext()->setContextProperty("nvidiaInstaller", &installer); + engine.rootContext()->setContextProperty("nvidiaUpdater", &updater); + engine.rootContext()->setContextProperty("cpuMonitor", &cpuMonitor); + engine.rootContext()->setContextProperty("gpuMonitor", &gpuMonitor); + engine.rootContext()->setContextProperty("ramMonitor", &ramMonitor); + engine.rootContext()->setContextProperty("languageManager", &languageManager); + engine.rootContext()->setContextProperty("uiPreferences", + &uiPreferencesManager); QObject::connect( &engine, &QQmlApplicationEngine::objectCreationFailed, &app, diff --git a/src/qml/Main.qml b/src/qml/Main.qml index 35358c1..fae0190 100644 --- a/src/qml/Main.qml +++ b/src/qml/Main.qml @@ -5,68 +5,210 @@ import QtQuick.Layouts ApplicationWindow { id: root visible: true - width: 980 - height: 640 + width: 1220 + height: 760 + minimumWidth: 980 + minimumHeight: 680 title: qsTr("ro-Control") - readonly property bool darkMode: Qt.styleHints.colorScheme === Qt.Dark - color: darkMode ? "#141822" : "#f4f6fb" - ColumnLayout { + readonly property string themeMode: uiPreferences.themeMode + readonly property bool darkMode: themeMode === "dark" + || (themeMode === "system" + && Qt.styleHints.colorScheme === Qt.Dark) + 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", + 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" + }) + + color: theme.window + + Rectangle { + anchors.fill: parent + gradient: Gradient { + GradientStop { + position: 0.0 + color: root.theme.heroStart + } + GradientStop { + position: 1.0 + color: root.theme.shell + } + } + } + + RowLayout { anchors.fill: parent spacing: 0 - ToolBar { + SidebarMenu { + id: sidebar + theme: root.theme + Layout.fillHeight: true + currentIndex: 0 + onCurrentIndexChanged: stack.currentIndex = currentIndex + } + + Rectangle { Layout.fillWidth: true - background: Rectangle { - color: root.darkMode ? "#1b2130" : "#ffffff" - } + Layout.fillHeight: true + color: "transparent" - RowLayout { + ColumnLayout { anchors.fill: parent - anchors.margins: 8 - - Label { - text: qsTr("ro-Control") - font.pixelSize: 20 - font.bold: true - color: root.darkMode ? "#e8edfb" : "#121825" - } + anchors.margins: root.compactMode ? 18 : 28 + spacing: root.compactMode ? 14 : 20 - Item { + Rectangle { Layout.fillWidth: true - } + radius: 30 + color: root.theme.card + border.width: 1 + border.color: root.theme.border + implicitHeight: heroLayout.implicitHeight + 44 - Label { - text: root.darkMode ? qsTr("Theme: System (Dark)") : qsTr("Theme: System (Light)") - color: root.darkMode ? "#c8d0e7" : "#36435f" + RowLayout { + id: heroLayout + x: 24 + y: 22 + width: parent.width - 48 + spacing: 20 + + ColumnLayout { + Layout.fillWidth: true + spacing: 8 + + Label { + text: root.pageTitles[sidebar.currentIndex] + font.pixelSize: 30 + font.weight: Font.Bold + color: root.theme.text + } + + Label { + text: root.pageSubtitles[sidebar.currentIndex] + wrapMode: Text.Wrap + color: root.theme.textSoft + Layout.fillWidth: true + } + } + + 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 + } + + InfoBadge { + text: root.compactMode ? qsTr("Compact Layout") : qsTr("Comfort Layout") + backgroundColor: root.theme.cardStrong + foregroundColor: root.theme.text + } + } + } } - } - } - TabBar { - id: tabs - Layout.fillWidth: true + StackLayout { + id: stack + Layout.fillWidth: true + Layout.fillHeight: true + currentIndex: sidebar.currentIndex - TabButton { - text: qsTr("Driver") - } - TabButton { - text: qsTr("Monitor") - } - TabButton { - text: qsTr("Settings") - } - } + DriverPage { + theme: root.theme + darkMode: root.darkMode + compactMode: root.compactMode + showAdvancedInfo: root.showAdvancedInfo + } - StackLayout { - Layout.fillWidth: true - Layout.fillHeight: true - currentIndex: tabs.currentIndex + MonitorPage { + theme: root.theme + darkMode: root.darkMode + compactMode: root.compactMode + showAdvancedInfo: root.showAdvancedInfo + } - DriverPage {} - MonitorPage {} - SettingsPage { - darkMode: root.darkMode + SettingsPage { + theme: root.theme + darkMode: root.darkMode + compactMode: root.compactMode + showAdvancedInfo: root.showAdvancedInfo + } + } } } } diff --git a/src/qml/components/ActionButton.qml b/src/qml/components/ActionButton.qml new file mode 100644 index 0000000..3e72578 --- /dev/null +++ b/src/qml/components/ActionButton.qml @@ -0,0 +1,52 @@ +import QtQuick +import QtQuick.Controls + +Button { + id: control + + required property var theme + property string tone: "neutral" + + readonly property color fillColor: !enabled ? Qt.rgba(0, 0, 0, 0) + : tone === "primary" ? theme.accentA + : tone === "success" ? theme.success + : tone === "warning" ? theme.warning + : tone === "danger" ? theme.danger + : theme.cardStrong + readonly property color borderColor: !enabled ? theme.border + : tone === "primary" ? Qt.tint(theme.accentA, "#33ffffff") + : tone === "success" ? Qt.tint(theme.success, "#22ffffff") + : tone === "warning" ? Qt.tint(theme.warning, "#18ffffff") + : tone === "danger" ? Qt.tint(theme.danger, "#18ffffff") + : theme.border + readonly property color textColor: !enabled ? theme.textSoft + : tone === "neutral" ? theme.text + : "#ffffff" + + implicitHeight: 46 + implicitWidth: Math.max(160, contentItem.implicitWidth + leftPadding + rightPadding) + leftPadding: 18 + rightPadding: 18 + topPadding: 12 + bottomPadding: 12 + + contentItem: Text { + text: control.text + color: control.textColor + font.pixelSize: 15 + font.weight: Font.DemiBold + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + elide: Text.ElideRight + } + + background: Rectangle { + radius: 14 + color: control.down && control.enabled ? Qt.darker(control.fillColor, 1.08) + : control.hovered && control.enabled ? Qt.tint(control.fillColor, "#08ffffff") + : control.fillColor + border.width: 1 + border.color: control.borderColor + opacity: control.enabled ? 1.0 : 0.6 + } +} diff --git a/src/qml/components/DetailRow.qml b/src/qml/components/DetailRow.qml new file mode 100644 index 0000000..bde9ec5 --- /dev/null +++ b/src/qml/components/DetailRow.qml @@ -0,0 +1,41 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +Rectangle { + id: row + + required property var theme + property string label: "" + property string value: "" + + radius: 12 + color: theme.cardStrong + border.width: 1 + border.color: theme.border + implicitHeight: contentRow.implicitHeight + 20 + + RowLayout { + id: contentRow + anchors.fill: parent + anchors.margins: 10 + spacing: 12 + + Label { + Layout.preferredWidth: Math.min(180, row.width * 0.42) + text: row.label + color: row.theme.textMuted + font.pixelSize: 13 + font.weight: Font.DemiBold + elide: Text.ElideRight + } + + Label { + Layout.fillWidth: true + text: row.value + color: row.theme.text + font.pixelSize: 14 + wrapMode: Text.Wrap + } + } +} diff --git a/src/qml/components/InfoBadge.qml b/src/qml/components/InfoBadge.qml new file mode 100644 index 0000000..fa82b72 --- /dev/null +++ b/src/qml/components/InfoBadge.qml @@ -0,0 +1,26 @@ +import QtQuick +import QtQuick.Controls + +Rectangle { + id: badge + + property string text: "" + property color backgroundColor: "#e5eefc" + property color foregroundColor: "#15304f" + + radius: 999 + color: backgroundColor + border.width: 1 + border.color: Qt.tint(backgroundColor, "#22000000") + implicitHeight: 36 + implicitWidth: badgeLabel.implicitWidth + 28 + + Label { + id: badgeLabel + anchors.centerIn: parent + text: badge.text + font.pixelSize: 13 + font.weight: Font.DemiBold + color: badge.foregroundColor + } +} diff --git a/src/qml/components/SectionPanel.qml b/src/qml/components/SectionPanel.qml new file mode 100644 index 0000000..a50cba6 --- /dev/null +++ b/src/qml/components/SectionPanel.qml @@ -0,0 +1,55 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +Rectangle { + id: panel + + required property var theme + property string title: "" + property string subtitle: "" + default property alias content: bodyColumn.data + + radius: 24 + color: theme.card + border.width: 1 + border.color: theme.border + implicitHeight: innerColumn.implicitHeight + 40 + + ColumnLayout { + id: innerColumn + x: 20 + y: 20 + width: parent.width - 40 + spacing: 16 + + ColumnLayout { + spacing: 6 + Layout.fillWidth: true + + Label { + text: panel.title + font.pixelSize: 17 + font.weight: Font.DemiBold + color: panel.theme.text + visible: text.length > 0 + Layout.fillWidth: true + wrapMode: Text.Wrap + } + + Label { + text: panel.subtitle + wrapMode: Text.Wrap + color: panel.theme.textSoft + Layout.fillWidth: true + visible: text.length > 0 + } + } + + ColumnLayout { + id: bodyColumn + spacing: 12 + Layout.fillWidth: true + } + } +} diff --git a/src/qml/components/SidebarMenu.qml b/src/qml/components/SidebarMenu.qml index c212087..765e4a5 100644 --- a/src/qml/components/SidebarMenu.qml +++ b/src/qml/components/SidebarMenu.qml @@ -1,76 +1,97 @@ import QtQuick import QtQuick.Controls +import QtQuick.Layouts Rectangle { id: sidebar - width: 220 - color: "#181825" + width: 248 + required property var theme + color: theme.sidebarBg + clip: true property int currentIndex: 0 + readonly property var menuItems: [ + qsTr("Driver Management"), + qsTr("System Monitoring"), + qsTr("Settings") + ] - ListModel { - id: menuModel - ListElement { - label: "Driver Management" - } - ListElement { - label: "System Monitoring" - } - ListElement { - label: "Settings" - } + // 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 } - Column { - anchors.fill: parent + ColumnLayout { + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top spacing: 0 // Başlık Item { - width: parent.width - height: 70 + Layout.fillWidth: true + implicitHeight: 82 Label { - anchors.centerIn: parent + anchors.left: parent.left + anchors.leftMargin: 22 + anchors.verticalCenter: parent.verticalCenter text: qsTr("ro-Control") - font.pixelSize: 22 - font.bold: true - color: "#cdd6f4" + font.pixelSize: 25 + font.weight: Font.Bold + color: theme.sidebarText } } Rectangle { - width: parent.width - 32 + Layout.fillWidth: true + Layout.leftMargin: 20 + Layout.rightMargin: 20 height: 1 - anchors.horizontalCenter: parent.horizontalCenter - color: "#313244" + color: theme.sidebarBorder } Item { - width: 1 - height: 12 + Layout.fillWidth: true + implicitHeight: 16 } Repeater { - model: menuModel + model: sidebar.menuItems delegate: Rectangle { id: menuItem required property int index - required property string label + required property string modelData - width: sidebar.width - 16 - height: 44 - x: 8 - radius: 8 - color: sidebar.currentIndex === menuItem.index ? "#313244" : mouseArea.containsMouse ? "#1e1e2e" : "transparent" + 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" Label { anchors.verticalCenter: parent.verticalCenter - leftPadding: 16 - text: menuItem.label - font.pixelSize: 14 - color: sidebar.currentIndex === menuItem.index ? "#89b4fa" : "#a6adc8" + anchors.left: parent.left + 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 } MouseArea { @@ -83,14 +104,4 @@ Rectangle { } } } - - // Versiyon — alt köşe - Label { - anchors.bottom: parent.bottom - anchors.horizontalCenter: parent.horizontalCenter - anchors.bottomMargin: 16 - text: "v" + Qt.application.version - font.pixelSize: 11 - color: "#585b70" - } } diff --git a/src/qml/components/StatCard.qml b/src/qml/components/StatCard.qml index 0a5fa77..fb71949 100644 --- a/src/qml/components/StatCard.qml +++ b/src/qml/components/StatCard.qml @@ -1,6 +1,6 @@ -import QtQuick 2.15 -import QtQuick.Controls 2.15 -import QtQuick.Layouts 1.15 +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts Rectangle { id: card @@ -11,18 +11,21 @@ Rectangle { property string accentColor: theme.accentB property bool emphasized: false property bool busy: false + property int minimumBodyHeight: 142 + readonly property int valueLength: value.length + readonly property int valuePixelSize: valueLength > 22 ? 26 : valueLength > 12 ? 34 : 42 radius: 24 color: emphasized ? Qt.tint(theme.cardStrong, "#15000000") : theme.card border.width: 1 border.color: theme.border - implicitHeight: cardLayout.implicitHeight + 30 + implicitHeight: Math.max(minimumBodyHeight, cardLayout.implicitHeight + 40) Rectangle { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - height: 6 + height: 7 radius: 24 color: card.accentColor opacity: 0.9 @@ -30,22 +33,29 @@ Rectangle { ColumnLayout { id: cardLayout - anchors.fill: parent - anchors.margins: 18 + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + anchors.margins: 20 spacing: 10 Label { text: card.title color: card.theme.textMuted - font.pixelSize: 13 - font.bold: true + font.pixelSize: 14 + font.weight: Font.DemiBold } Label { text: card.value color: card.theme.text - font.pixelSize: 28 - font.bold: true + font.pixelSize: card.valuePixelSize + font.weight: Font.Bold + wrapMode: Text.Wrap + Layout.fillWidth: true + maximumLineCount: 2 + minimumPixelSize: 22 + fontSizeMode: Text.Fit } Label { @@ -54,6 +64,7 @@ Rectangle { wrapMode: Text.Wrap Layout.fillWidth: true visible: text.length > 0 + font.pixelSize: 13 } BusyIndicator { diff --git a/src/qml/components/StatusBanner.qml b/src/qml/components/StatusBanner.qml new file mode 100644 index 0000000..40b4471 --- /dev/null +++ b/src/qml/components/StatusBanner.qml @@ -0,0 +1,56 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +Rectangle { + id: banner + + required property var theme + property string tone: "info" + property string text: "" + + readonly property color bannerColor: tone === "success" ? theme.successBg + : tone === "warning" ? theme.warningBg + : tone === "error" ? theme.dangerBg + : theme.infoBg + readonly property color borderTone: tone === "success" ? theme.success + : tone === "warning" ? theme.warning + : tone === "error" ? theme.danger + : theme.accentA + readonly property color textTone: tone === "success" ? "#16351d" + : tone === "warning" ? "#4b3202" + : tone === "error" ? "#5b1820" + : "#12304f" + + radius: 20 + color: bannerColor + border.width: 1 + border.color: borderTone + visible: text.length > 0 + + implicitHeight: bannerLayout.implicitHeight + 26 + + RowLayout { + id: bannerLayout + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + anchors.margins: 14 + spacing: 12 + + Rectangle { + width: 10 + height: 10 + radius: 5 + color: banner.borderTone + } + + Label { + Layout.fillWidth: true + text: banner.text + wrapMode: Text.Wrap + color: banner.textTone + font.pixelSize: 14 + } + } +} diff --git a/src/qml/components/qmldir b/src/qml/components/qmldir index 96321f1..d384f11 100644 --- a/src/qml/components/qmldir +++ b/src/qml/components/qmldir @@ -1,2 +1,7 @@ +ActionButton 1.0 ActionButton.qml +DetailRow 1.0 DetailRow.qml +InfoBadge 1.0 InfoBadge.qml +SectionPanel 1.0 SectionPanel.qml SidebarMenu 1.0 SidebarMenu.qml StatCard 1.0 StatCard.qml +StatusBanner 1.0 StatusBanner.qml diff --git a/src/qml/pages/DriverPage.qml b/src/qml/pages/DriverPage.qml index bd2e377..1c5d1f1 100644 --- a/src/qml/pages/DriverPage.qml +++ b/src/qml/pages/DriverPage.qml @@ -5,180 +5,638 @@ import QtQuick.Layouts Item { id: page - ColumnLayout { - anchors.fill: parent - anchors.margins: 20 - spacing: 12 + required property var theme + property bool darkMode: false + property bool compactMode: false + property bool showAdvancedInfo: true - Label { - text: qsTr("Driver Management") - font.pixelSize: 24 - font.bold: true - } + property string bannerText: "" + property string bannerTone: "info" + property string operationSource: "" + property string operationPhase: "" + 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 - Label { - text: qsTr("GPU: ") + (nvidiaDetector.gpuFound ? nvidiaDetector.gpuName : qsTr("Not detected")) - wrapMode: Text.Wrap - Layout.fillWidth: true - } + function classifyOperationPhase(message) { + const lowered = message.toLowerCase(); + if (lowered.indexOf("rpm fusion") >= 0 || lowered.indexOf("repository") >= 0) + return qsTr("Repository Setup"); + if (lowered.indexOf("install") >= 0 || lowered.indexOf("remove") >= 0 || lowered.indexOf("deep clean") >= 0) + return qsTr("Package Transaction"); + if (lowered.indexOf("kernel") >= 0 || lowered.indexOf("akmods") >= 0 || lowered.indexOf("dracut") >= 0) + return qsTr("Kernel Integration"); + if (lowered.indexOf("wayland") >= 0 || lowered.indexOf("x11") >= 0 || lowered.indexOf("session") >= 0) + return qsTr("Session Finalization"); + if (lowered.indexOf("update") >= 0 || lowered.indexOf("version") >= 0) + return qsTr("Update Check"); + return qsTr("General"); + } - Label { - text: qsTr("Active driver: ") + nvidiaDetector.activeDriver - wrapMode: Text.Wrap - } + function setOperationState(source, message, tone, running) { + if (running && !operationRunning) + operationStartedAt = Date.now(); + if (!running) + operationStartedAt = 0; + operationSource = source; + operationDetail = message; + operationPhase = classifyOperationPhase(message); + operationActive = running; + operationElapsedSeconds = operationRunning && operationStartedAt > 0 + ? Math.max(0, Math.floor((Date.now() - operationStartedAt) / 1000)) + : 0; + bannerText = (operationPhase.length > 0 ? operationPhase + ": " : "") + message; + bannerTone = tone; + } - Label { - text: qsTr("Driver version: ") + (nvidiaDetector.driverVersion.length > 0 ? nvidiaDetector.driverVersion : qsTr("None")) - } + function finishOperation(source, success, message) { + setOperationState(source, message, success ? "success" : "error", false); + } - Label { - text: qsTr("Secure Boot: ") + (nvidiaDetector.secureBootEnabled ? qsTr("Enabled") : qsTr("Disabled / Unknown")) - color: nvidiaDetector.secureBootEnabled ? "#c43a3a" : "#2b8a3e" - font.bold: true - } + function formatTimestamp(epochMs) { + if (epochMs <= 0) + return "--:--:--"; + const stamp = new Date(epochMs); + return Qt.formatTime(stamp, "HH:mm:ss"); + } - Label { - text: qsTr("Session type: ") + nvidiaDetector.sessionType - font.bold: true - } + function formatDuration(totalSeconds) { + const seconds = Math.max(0, totalSeconds); + const hours = Math.floor(seconds / 3600); + const minutes = Math.floor((seconds % 3600) / 60); + const remainingSeconds = seconds % 60; - Label { - text: nvidiaDetector.waylandSession - ? qsTr("For Wayland sessions, nvidia-drm.modeset=1 is applied automatically.") - : qsTr("For X11 sessions, the xorg-x11-drv-nvidia package is verified and installed.") - wrapMode: Text.Wrap - Layout.fillWidth: true - color: "#6d7384" + function pad(value) { + return value < 10 ? "0" + value : value.toString(); } - Rectangle { - Layout.fillWidth: true - border.width: 1 - border.color: "#5f6b86" - color: "transparent" - radius: 8 - implicitHeight: verificationText.implicitHeight + 20 - - Label { - id: verificationText - anchors.fill: parent - anchors.margins: 10 - text: nvidiaDetector.verificationReport - wrapMode: Text.Wrap - } - } + if (hours > 0) + return hours + ":" + pad(minutes) + ":" + pad(remainingSeconds); + return minutes + ":" + pad(remainingSeconds); + } + + 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; - Label { - visible: nvidiaInstaller.proprietaryAgreementRequired - text: nvidiaInstaller.proprietaryAgreementText - color: "#8a6500" - wrapMode: Text.Wrap - Layout.fillWidth: true + if (leftValue > rightValue) + return -1; + if (leftValue < rightValue) + return 1; } - CheckBox { - id: eulaAccept - visible: nvidiaInstaller.proprietaryAgreementRequired - text: qsTr("I accept the license / agreement terms") + return 0; + } + + function buildVersionTitle(displayVersion, isInstalled, isLatest) { + const tags = []; + if (isInstalled) + tags.push(qsTr("Installed")); + if (isLatest) + tags.push(qsTr("Latest")); + + return tags.length > 0 + ? displayVersion + " (" + tags.join(", ") + ")" + : displayVersion; + } + + function buildAvailableVersionOptions(rawVersions) { + const options = []; + const seenLabels = {}; + const installedVersionLabel = cleanVersionLabel(nvidiaUpdater.currentVersion); + 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; + const isInstalled = installedVersionLabel.length > 0 && displayVersion === installedVersionLabel; + const isLatest = latestVersionLabel.length > 0 && displayVersion === latestVersionLabel; + options.push({ + rawVersion: rawVersion, + displayVersion: displayVersion, + versionTitle: buildVersionTitle(displayVersion, isInstalled, isLatest), + isInstalled: isInstalled, + isLatest: isLatest + }); } - RowLayout { - Layout.fillWidth: true - spacing: 8 + options.sort(function(left, right) { + return compareVersionLabels(left.displayVersion, right.displayVersion); + }); - Button { - text: qsTr("Install Proprietary Driver") - enabled: !nvidiaInstaller.proprietaryAgreementRequired || eulaAccept.checked - onClicked: nvidiaInstaller.installProprietary(eulaAccept.checked) - } + return options; + } - Button { - text: qsTr("Install Open-Source Driver (Nouveau)") - onClicked: nvidiaInstaller.installOpenSource() - } + function appendLog(source, message) { + const now = Date.now(); + lastLogAt = now; + logArea.append("[" + formatTimestamp(now) + "] " + source + ": " + message); + logArea.cursorPosition = logArea.length; + } - Button { - text: qsTr("Deep Clean") - onClicked: nvidiaInstaller.deepClean() - } + Timer { + interval: 1000 + repeat: true + running: page.operationRunning + onTriggered: { + if (page.operationStartedAt > 0) + page.operationElapsedSeconds = Math.max(0, Math.floor((Date.now() - page.operationStartedAt) / 1000)); } + } - RowLayout { - spacing: 8 + 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) - Button { - text: qsTr("Check for Updates") - onClicked: nvidiaUpdater.checkForUpdate() - } + ScrollView { + id: pageScroll + anchors.fill: parent + clip: true + contentWidth: availableWidth - Button { - text: qsTr("Apply Update") - enabled: nvidiaUpdater.updateAvailable - onClicked: nvidiaUpdater.applyUpdate() - } + ColumnLayout { + width: pageScroll.availableWidth + spacing: page.compactMode ? 12 : 16 - Label { - visible: nvidiaUpdater.updateAvailable - text: qsTr("Latest version: ") + nvidiaUpdater.latestVersion - color: "#8a6500" + StatusBanner { + Layout.fillWidth: true + theme: page.theme + tone: page.bannerTone + text: page.bannerText } - } - RowLayout { - spacing: 8 + GridLayout { + Layout.fillWidth: true + columns: width > 980 ? 4 : 2 + columnSpacing: 12 + rowSpacing: 12 - Button { - text: qsTr("Rescan") - onClicked: { - nvidiaDetector.refresh(); - nvidiaInstaller.refreshProprietaryAgreement(); + 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 + } + + 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("Installed NVIDIA version: ") + nvidiaUpdater.currentVersion - visible: nvidiaUpdater.currentVersion.length > 0 + 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 + } } - } - ScrollView { - Layout.fillWidth: true - Layout.fillHeight: true + GridLayout { + Layout.fillWidth: true + columns: width > 980 ? 2 : 1 + columnSpacing: 16 + rowSpacing: 16 + + SectionPanel { + 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 + } - TextArea { - id: logArea - readOnly: true - wrapMode: Text.Wrap - text: "" + InfoBadge { + text: nvidiaDetector.waylandSession ? qsTr("Wayland Session") : qsTr("X11 / Other Session") + backgroundColor: page.theme.infoBg + foregroundColor: page.theme.text + } + + 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 + } + + 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 + } + } + + 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 + } + } + } + + SectionPanel { + 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 { + 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); + } + } + + 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(); + } + } + + ActionButton { + 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(); + } + } + + 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(); + } + } + } + + RowLayout { + Layout.fillWidth: true + + ActionButton { + theme: page.theme + text: qsTr("Rescan System") + enabled: !nvidiaInstaller.busy && !nvidiaUpdater.busy + onClicked: { + nvidiaDetector.refresh(); + nvidiaInstaller.refreshProprietaryAgreement(); + nvidiaUpdater.refreshAvailableVersions(); + } + } + + Item { + Layout.fillWidth: true + } + + BusyIndicator { + running: nvidiaInstaller.busy || nvidiaUpdater.busy + visible: running + } + } + } + + SectionPanel { + 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 + 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 + 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); + } + } + } + + 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 + } + } + } } } } Connections { target: nvidiaInstaller + function onProgressMessage(message) { - logArea.append(message); + page.appendLog(qsTr("Installer"), message); + page.setOperationState(qsTr("Installer"), message, "info", true); } + function onInstallFinished(success, message) { - logArea.append(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) { - logArea.append(message); + page.appendLog(qsTr("Installer"), message); + page.finishOperation(qsTr("Installer"), success, message); nvidiaDetector.refresh(); + nvidiaUpdater.checkForUpdate(); nvidiaInstaller.refreshProprietaryAgreement(); } } Connections { target: nvidiaUpdater + function onProgressMessage(message) { - logArea.append(message); + page.appendLog(qsTr("Updater"), message); + page.setOperationState(qsTr("Updater"), message, "info", true); } + function onUpdateFinished(success, message) { - logArea.append(message); + page.appendLog(qsTr("Updater"), message); + page.finishOperation(qsTr("Updater"), success, message); nvidiaDetector.refresh(); nvidiaUpdater.checkForUpdate(); } @@ -187,6 +645,7 @@ Item { Component.onCompleted: { nvidiaDetector.refresh(); nvidiaUpdater.checkForUpdate(); + nvidiaUpdater.refreshAvailableVersions(); nvidiaInstaller.refreshProprietaryAgreement(); } } diff --git a/src/qml/pages/MonitorPage.qml b/src/qml/pages/MonitorPage.qml index 2fbfa78..9971c87 100644 --- a/src/qml/pages/MonitorPage.qml +++ b/src/qml/pages/MonitorPage.qml @@ -3,150 +3,292 @@ import QtQuick.Controls import QtQuick.Layouts Item { - ColumnLayout { + id: page + + required property var theme + property bool darkMode: false + property bool compactMode: false + property bool showAdvancedInfo: true + readonly property bool cpuTemperatureAvailable: cpuMonitor.temperatureC > 0 + readonly property bool gpuTelemetryAvailable: gpuMonitor.available + readonly property bool gpuTemperatureAvailable: gpuMonitor.temperatureC > 0 + 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 gpuDriverActive: nvidiaDetector.driverLoaded || nvidiaDetector.nouveauActive + + function formatTemperature(value) { + return value > 0 ? value + " C" : qsTr("Unavailable"); + } + + function formatMemoryUsage(usedMiB, totalMiB) { + return totalMiB > 0 ? usedMiB + " / " + totalMiB + " MiB" : qsTr("Unavailable"); + } + + function gpuLoadValueText() { + if (page.gpuTelemetryAvailable) + return gpuMonitor.utilizationPercent + "%"; + if (page.gpuDetected) + return qsTr("No Live Data"); + 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 gpuSummaryText() { + if (page.gpuTelemetryAvailable) + return qsTr("GPU temperature: ") + page.formatTemperature(gpuMonitor.temperatureC) + qsTr(", VRAM ") + page.formatMemoryUsage(gpuMonitor.memoryUsedMiB, gpuMonitor.memoryTotalMiB) + "."; + if (!page.gpuDetected) + return qsTr("No NVIDIA GPU is currently detected on this system."); + 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."); + } + + ScrollView { + id: pageScroll anchors.fill: parent - anchors.margins: 20 - spacing: 12 + clip: true + contentWidth: availableWidth - Label { - text: qsTr("System Monitoring") - font.pixelSize: 24 - font.bold: true - } + ColumnLayout { + width: pageScroll.availableWidth + spacing: page.compactMode ? 12 : 16 - Rectangle { - Layout.fillWidth: true - radius: 10 - border.width: 1 - border.color: "#5f6b86" - color: "transparent" - implicitHeight: cpuCol.implicitHeight + 18 - - ColumnLayout { - id: cpuCol - anchors.fill: parent - anchors.margins: 10 - spacing: 6 - - Label { - text: qsTr("CPU") - font.bold: true - } + GridLayout { + Layout.fillWidth: true + columns: width > 980 ? 3 : 1 + columnSpacing: 14 + rowSpacing: 14 - Label { - text: cpuMonitor.available ? qsTr("Usage: ") + cpuMonitor.usagePercent.toFixed(1) + "%" : qsTr("CPU data unavailable") + StatCard { + 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 } - ProgressBar { - from: 0 - to: 100 - value: cpuMonitor.usagePercent + StatCard { 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 } - Label { - text: qsTr("Temperature: ") + cpuMonitor.temperatureC + " C" - visible: cpuMonitor.temperatureC > 0 + StatCard { + 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 } } - } - Rectangle { - Layout.fillWidth: true - radius: 10 - border.width: 1 - border.color: "#5f6b86" - color: "transparent" - implicitHeight: gpuCol.implicitHeight + 18 - - ColumnLayout { - id: gpuCol - anchors.fill: parent - anchors.margins: 10 - spacing: 6 - - Label { - text: qsTr("GPU (NVIDIA)") - font.bold: true - } + GridLayout { + Layout.fillWidth: true + columns: width > 980 ? 2 : 1 + columnSpacing: 16 + rowSpacing: 16 - Label { - text: gpuMonitor.available ? (gpuMonitor.gpuName.length > 0 ? gpuMonitor.gpuName : qsTr("NVIDIA GPU")) : qsTr("Failed to read data via nvidia-smi") - } + SectionPanel { + Layout.fillWidth: true + theme: page.theme + title: qsTr("Live Resource Curves") + subtitle: qsTr("Quick pulse view for the most important machine resources.") + + ColumnLayout { + Layout.fillWidth: true + spacing: 10 + + Label { + text: qsTr("CPU") + color: page.theme.textMuted + font.bold: true + } + + ProgressBar { + Layout.fillWidth: true + from: 0 + to: 100 + value: cpuMonitor.usagePercent + } + + Label { + text: qsTr("GPU") + color: page.theme.textMuted + font.bold: true + } + + ProgressBar { + Layout.fillWidth: true + from: 0 + to: 100 + value: gpuMonitor.utilizationPercent + } - Label { - text: qsTr("Load: ") + gpuMonitor.utilizationPercent + "%" - visible: gpuMonitor.available + Label { + text: qsTr("RAM") + color: page.theme.textMuted + font.bold: true + } + + ProgressBar { + Layout.fillWidth: true + from: 0 + to: 100 + value: ramMonitor.usagePercent + } + } } - ProgressBar { - from: 0 - to: 100 - value: gpuMonitor.utilizationPercent + SectionPanel { Layout.fillWidth: true - visible: gpuMonitor.available - } + theme: page.theme + title: qsTr("Health Summary") + subtitle: qsTr("Fast interpretation of the raw telemetry values.") - Label { - text: qsTr("VRAM: ") + gpuMonitor.memoryUsedMiB + " / " + gpuMonitor.memoryTotalMiB + " MiB (" + gpuMonitor.memoryUsagePercent + "%)" - visible: gpuMonitor.available && gpuMonitor.memoryTotalMiB > 0 - } + Flow { + Layout.fillWidth: true + spacing: 8 - Label { - text: qsTr("Temperature: ") + gpuMonitor.temperatureC + " C" - visible: gpuMonitor.available && gpuMonitor.temperatureC > 0 - } - } - } + 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 + } - Rectangle { - Layout.fillWidth: true - radius: 10 - border.width: 1 - border.color: "#5f6b86" - color: "transparent" - implicitHeight: ramCol.implicitHeight + 18 - - ColumnLayout { - id: ramCol - anchors.fill: parent - anchors.margins: 10 - spacing: 6 - - Label { - text: qsTr("RAM") - font.bold: true - } + 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 + } + } + + Label { + Layout.fillWidth: true + wrapMode: Text.Wrap + color: page.theme.textSoft + text: page.gpuSummaryText() + } - Label { - text: ramMonitor.available ? qsTr("Usage: ") + ramMonitor.usedMiB + " / " + ramMonitor.totalMiB + " MiB (" + ramMonitor.usagePercent + "%)" : qsTr("RAM data unavailable") + Label { + Layout.fillWidth: true + wrapMode: Text.Wrap + color: page.theme.textSoft + visible: page.showAdvancedInfo + text: qsTr("Refresh interval: ") + cpuMonitor.updateInterval + " ms" + } } - ProgressBar { - from: 0 - to: 100 - value: ramMonitor.usagePercent + SectionPanel { Layout.fillWidth: true - } - } - } + theme: page.theme + title: qsTr("Detailed Signals") + subtitle: qsTr("Expanded raw values for support and diagnostics.") + visible: page.showAdvancedInfo - RowLayout { - spacing: 8 + ColumnLayout { + Layout.fillWidth: true + spacing: 8 - Button { - text: qsTr("Refresh") - onClicked: { - cpuMonitor.refresh(); - gpuMonitor.refresh(); - ramMonitor.refresh(); + DetailRow { + Layout.fillWidth: true + theme: page.theme + label: qsTr("CPU Temperature") + value: page.formatTemperature(cpuMonitor.temperatureC) + } + + DetailRow { + 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") + } + } } - } - Label { - text: qsTr("Refresh interval: ") + cpuMonitor.updateInterval + " ms" - color: "#6d7384" + 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 + spacing: 10 + + ActionButton { + theme: page.theme + tone: "primary" + text: qsTr("Refresh Telemetry") + onClicked: { + cpuMonitor.refresh(); + gpuMonitor.refresh(); + ramMonitor.refresh(); + } + } + + 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 + } + } + } } } } + + Component.onCompleted: { + cpuMonitor.start(); + gpuMonitor.start(); + ramMonitor.start(); + cpuMonitor.refresh(); + gpuMonitor.refresh(); + ramMonitor.refresh(); + } } diff --git a/src/qml/pages/SettingsPage.qml b/src/qml/pages/SettingsPage.qml index 3c05876..ce08931 100644 --- a/src/qml/pages/SettingsPage.qml +++ b/src/qml/pages/SettingsPage.qml @@ -4,47 +4,372 @@ import QtQuick.Layouts Item { id: settingsPage + + required property var theme property bool darkMode: false + property bool compactMode: false + property bool showAdvancedInfo: true + readonly property string themeMode: uiPreferences.themeMode - ColumnLayout { + ScrollView { + id: pageScroll anchors.fill: parent - anchors.margins: 20 - spacing: 12 + clip: true + contentWidth: availableWidth - Label { - text: qsTr("Settings") - font.pixelSize: 24 - font.bold: true - } + ColumnLayout { + width: pageScroll.availableWidth + spacing: settingsPage.compactMode ? 12 : 16 + + GridLayout { + Layout.fillWidth: true + columns: width > 980 ? 2 : 1 + columnSpacing: 16 + rowSpacing: 16 + + SectionPanel { + Layout.fillWidth: true + theme: settingsPage.theme + title: qsTr("Appearance & Behavior") + subtitle: qsTr("Control theme, density and operator-focused interface behavior.") + + RowLayout { + Layout.fillWidth: true + spacing: 12 + + ColumnLayout { + 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 + } + } + + 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); + } + } + } + } + + RowLayout { + Layout.fillWidth: true + spacing: 12 + + ColumnLayout { + Layout.fillWidth: true + spacing: 4 + + 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 + Layout.fillWidth: true + } + } + + 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); + } + } + } + } + + 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 + } + } + + Switch { + checked: uiPreferences.compactMode + onToggled: uiPreferences.setCompactMode(checked) + } + } + + RowLayout { + Layout.fillWidth: true + + ColumnLayout { + Layout.fillWidth: true + spacing: 4 + + Label { + text: qsTr("Show advanced diagnostics") + font.bold: true + color: settingsPage.theme.text + } + + Label { + text: qsTr("Shows verification reports and expanded monitor metrics.") + wrapMode: Text.Wrap + color: settingsPage.theme.textSoft + Layout.fillWidth: true + } + } + + Switch { + checked: uiPreferences.showAdvancedInfo + onToggled: uiPreferences.setShowAdvancedInfo(checked) + } + } + + Flow { + Layout.fillWidth: true + spacing: 8 + + InfoBadge { + text: qsTr("Language: ") + languageManager.currentLanguageLabel + backgroundColor: settingsPage.theme.cardStrong + foregroundColor: settingsPage.theme.text + } + + 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 + } + + InfoBadge { + text: uiPreferences.compactMode ? qsTr("Compact Active") : qsTr("Comfort Active") + backgroundColor: settingsPage.theme.cardStrong + foregroundColor: settingsPage.theme.text + } + + InfoBadge { + text: uiPreferences.showAdvancedInfo ? qsTr("Advanced Visible") : qsTr("Advanced Hidden") + backgroundColor: settingsPage.theme.cardStrong + foregroundColor: settingsPage.theme.text + } + } + + RowLayout { + Layout.fillWidth: true + spacing: 12 - Rectangle { - Layout.fillWidth: true - border.width: 1 - border.color: settingsPage.darkMode ? "#4f5f82" : "#c6cfdf" - color: "transparent" - radius: 8 - implicitHeight: aboutCol.implicitHeight + 20 - - ColumnLayout { - id: aboutCol - anchors.fill: parent - anchors.margins: 10 - spacing: 8 - - Label { - text: qsTr("About") - font.pixelSize: 20 - font.bold: true + Label { + 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() + } + } } - Label { - text: qsTr("Application: ") + Qt.application.name + " (" + Qt.application.version + ")" + SectionPanel { + Layout.fillWidth: true + theme: settingsPage.theme + title: qsTr("Diagnostics") + subtitle: qsTr("Useful runtime context before filing issues or performing support work.") + + ColumnLayout { + 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("GPU") + value: nvidiaDetector.gpuFound ? nvidiaDetector.gpuName : qsTr("Not detected") + } + + DetailRow { + Layout.fillWidth: true + theme: settingsPage.theme + label: qsTr("Driver") + value: nvidiaDetector.activeDriver + } + + DetailRow { + Layout.fillWidth: true + theme: settingsPage.theme + label: qsTr("Session") + value: nvidiaDetector.sessionType.length > 0 ? nvidiaDetector.sessionType : qsTr("Unknown") + } + } + + 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.") + } } - Label { - text: qsTr("Theme: ") + (settingsPage.darkMode ? qsTr("System Dark") : qsTr("System Light")) + 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 { + 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) + } + + DetailRow { + 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") + } + } } } } } + + function syncLanguagePicker() { + for (let i = 0; i < languagePicker.model.length; ++i) { + if (languagePicker.model[i].code === languageManager.currentLanguage) { + languagePicker.currentIndex = i; + break; + } + } + } + + function syncThemePicker() { + for (let i = 0; i < themePicker.model.length; ++i) { + if (themePicker.model[i].code === uiPreferences.themeMode) { + themePicker.currentIndex = i; + break; + } + } + } + + Connections { + target: languageManager + + function onCurrentLanguageChanged() { + settingsPage.syncLanguagePicker() + } + } + + Connections { + target: uiPreferences + + function onThemeModeChanged() { + settingsPage.syncThemePicker() + } + } } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index af17a3b..ae3029b 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,30 +1,22 @@ -find_package(Qt6 REQUIRED COMPONENTS Test) +find_package(Qt6 REQUIRED COMPONENTS Test Qml Quick QuickControls2) -set(RO_CONTROL_TEST_COMPILE_DEFINITIONS - RO_CONTROL_POLICY_ID="${RO_CONTROL_POLICY_ID}" - RO_CONTROL_HELPER_BUILD_PATH="${RO_CONTROL_HELPER_BUILD_PATH}" - RO_CONTROL_HELPER_INSTALL_PATH="${RO_CONTROL_HELPER_INSTALL_PATH}" -) +function(ro_control_configure_test target_name) + target_include_directories(${target_name} PRIVATE + ${CMAKE_SOURCE_DIR}/src + ${CMAKE_SOURCE_DIR}/src/backend + ) +endfunction() # ─── Detector Tests ────────────────────────────────────────────────────────── qt_add_executable(test_detector test_detector.cpp - ${CMAKE_SOURCE_DIR}/src/backend/nvidia/detector.cpp - ${CMAKE_SOURCE_DIR}/src/backend/system/commandrunner.cpp -) - -target_include_directories(test_detector PRIVATE - ${CMAKE_SOURCE_DIR}/src - ${CMAKE_SOURCE_DIR}/src/backend ) -target_compile_definitions(test_detector PRIVATE - ${RO_CONTROL_TEST_COMPILE_DEFINITIONS} -) +ro_control_configure_test(test_detector) target_link_libraries(test_detector PRIVATE - Qt6::Core Qt6::Test + ro-control-backend ) add_test(NAME test_detector COMMAND test_detector) @@ -32,17 +24,13 @@ add_test(NAME test_detector COMMAND test_detector) # ─── Updater/Version Parser Tests ──────────────────────────────────────────── qt_add_executable(test_updater test_updater.cpp - ${CMAKE_SOURCE_DIR}/src/backend/nvidia/versionparser.cpp ) -target_include_directories(test_updater PRIVATE - ${CMAKE_SOURCE_DIR}/src - ${CMAKE_SOURCE_DIR}/src/backend -) +ro_control_configure_test(test_updater) target_link_libraries(test_updater PRIVATE - Qt6::Core Qt6::Test + ro-control-backend ) add_test(NAME test_updater COMMAND test_updater) @@ -50,48 +38,42 @@ add_test(NAME test_updater COMMAND test_updater) # ─── Monitor Tests ─────────────────────────────────────────────────────────── qt_add_executable(test_monitor test_monitor.cpp - ${CMAKE_SOURCE_DIR}/src/backend/monitor/cpumonitor.cpp - ${CMAKE_SOURCE_DIR}/src/backend/monitor/gpumonitor.cpp - ${CMAKE_SOURCE_DIR}/src/backend/monitor/rammonitor.cpp - ${CMAKE_SOURCE_DIR}/src/backend/system/commandrunner.cpp ) -target_include_directories(test_monitor PRIVATE - ${CMAKE_SOURCE_DIR}/src - ${CMAKE_SOURCE_DIR}/src/backend +ro_control_configure_test(test_monitor) + +target_link_libraries(test_monitor PRIVATE + Qt6::Test + ro-control-backend ) -target_compile_definitions(test_monitor PRIVATE - ${RO_CONTROL_TEST_COMPILE_DEFINITIONS} +add_test(NAME test_monitor COMMAND test_monitor) + +# ─── Preferences / Localization Tests ─────────────────────────────────────── +qt_add_executable(test_preferences + test_preferences.cpp ) -target_link_libraries(test_monitor PRIVATE - Qt6::Core +ro_control_configure_test(test_preferences) + +target_link_libraries(test_preferences PRIVATE + Qt6::Qml Qt6::Test + ro-control-backend ) -add_test(NAME test_monitor COMMAND test_monitor) +add_test(NAME test_preferences COMMAND test_preferences) # ─── System Integration Tests ─────────────────────────────────────────────── qt_add_executable(test_system_integration test_system_integration.cpp - ${CMAKE_SOURCE_DIR}/src/backend/system/commandrunner.cpp - ${CMAKE_SOURCE_DIR}/src/backend/system/dnfmanager.cpp - ${CMAKE_SOURCE_DIR}/src/backend/system/polkit.cpp ) -target_include_directories(test_system_integration PRIVATE - ${CMAKE_SOURCE_DIR}/src - ${CMAKE_SOURCE_DIR}/src/backend -) - -target_compile_definitions(test_system_integration PRIVATE - ${RO_CONTROL_TEST_COMPILE_DEFINITIONS} -) +ro_control_configure_test(test_system_integration) target_link_libraries(test_system_integration PRIVATE - Qt6::Core Qt6::Test + ro-control-backend ) add_test(NAME test_system_integration COMMAND test_system_integration) @@ -102,8 +84,7 @@ qt_add_executable(test_metadata ) target_compile_definitions(test_metadata PRIVATE - ${RO_CONTROL_TEST_COMPILE_DEFINITIONS} - RO_CONTROL_SOURCE_DIR="${CMAKE_SOURCE_DIR}" + "RO_CONTROL_SOURCE_DIR=\"${CMAKE_SOURCE_DIR}\"" ) target_link_libraries(test_metadata PRIVATE @@ -117,27 +98,33 @@ add_test(NAME test_metadata COMMAND test_metadata) qt_add_executable(test_cli test_cli.cpp ${CMAKE_SOURCE_DIR}/src/cli/cli.cpp - ${CMAKE_SOURCE_DIR}/src/backend/nvidia/detector.cpp - ${CMAKE_SOURCE_DIR}/src/backend/nvidia/updater.cpp - ${CMAKE_SOURCE_DIR}/src/backend/nvidia/versionparser.cpp - ${CMAKE_SOURCE_DIR}/src/backend/monitor/cpumonitor.cpp - ${CMAKE_SOURCE_DIR}/src/backend/monitor/gpumonitor.cpp - ${CMAKE_SOURCE_DIR}/src/backend/monitor/rammonitor.cpp - ${CMAKE_SOURCE_DIR}/src/backend/system/commandrunner.cpp ) -target_include_directories(test_cli PRIVATE - ${CMAKE_SOURCE_DIR}/src - ${CMAKE_SOURCE_DIR}/src/backend +ro_control_configure_test(test_cli) + +target_link_libraries(test_cli PRIVATE + Qt6::Test + ro-control-backend ) -target_compile_definitions(test_cli PRIVATE - ${RO_CONTROL_TEST_COMPILE_DEFINITIONS} +add_test(NAME test_cli COMMAND test_cli) + +# ─── Driver Page QML Integration Tests ────────────────────────────────────── +qt_add_executable(test_driver_page + test_driver_page.cpp ) -target_link_libraries(test_cli PRIVATE - Qt6::Core +ro_control_configure_test(test_driver_page) + +target_compile_definitions(test_driver_page PRIVATE + "RO_CONTROL_SOURCE_DIR=\"${CMAKE_SOURCE_DIR}\"" +) + +target_link_libraries(test_driver_page PRIVATE + Qt6::Qml + Qt6::Quick + Qt6::QuickControls2 Qt6::Test ) -add_test(NAME test_cli COMMAND test_cli) +add_test(NAME test_driver_page COMMAND test_driver_page) diff --git a/tests/test_detector.cpp b/tests/test_detector.cpp index b574a71..24c9587 100644 --- a/tests/test_detector.cpp +++ b/tests/test_detector.cpp @@ -19,6 +19,7 @@ private slots: QVERIFY(info.driverVersion.isEmpty()); QCOMPARE(info.driverLoaded, false); QCOMPARE(info.nouveauActive, false); + QCOMPARE(info.openKernelModulesInstalled, false); QCOMPARE(info.secureBootEnabled, false); } diff --git a/tests/test_driver_page.cpp b/tests/test_driver_page.cpp new file mode 100644 index 0000000..040b13f --- /dev/null +++ b/tests/test_driver_page.cpp @@ -0,0 +1,454 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class DetectorMock : public QObject { + Q_OBJECT + Q_PROPERTY(bool gpuFound READ gpuFound WRITE setGpuFound NOTIFY infoChanged) + Q_PROPERTY(QString gpuName READ gpuName WRITE setGpuName NOTIFY infoChanged) + Q_PROPERTY(QString driverVersion READ driverVersion WRITE setDriverVersion + NOTIFY infoChanged) + Q_PROPERTY(bool driverLoaded READ driverLoaded WRITE setDriverLoaded NOTIFY + infoChanged) + Q_PROPERTY(bool nouveauActive READ nouveauActive WRITE setNouveauActive + NOTIFY infoChanged) + Q_PROPERTY(bool secureBootEnabled READ secureBootEnabled WRITE + setSecureBootEnabled NOTIFY infoChanged) + Q_PROPERTY(bool waylandSession READ waylandSession WRITE setWaylandSession + NOTIFY infoChanged) + Q_PROPERTY(QString sessionType READ sessionType WRITE setSessionType NOTIFY + infoChanged) + Q_PROPERTY(QString activeDriver READ activeDriver WRITE setActiveDriver NOTIFY + infoChanged) + Q_PROPERTY(QString verificationReport READ verificationReport WRITE + setVerificationReport NOTIFY infoChanged) + +public: + bool gpuFound() const { return m_gpuFound; } + QString gpuName() const { return m_gpuName; } + QString driverVersion() const { return m_driverVersion; } + bool driverLoaded() const { return m_driverLoaded; } + bool nouveauActive() const { return m_nouveauActive; } + bool secureBootEnabled() const { return m_secureBootEnabled; } + bool waylandSession() const { return m_waylandSession; } + QString sessionType() const { return m_sessionType; } + QString activeDriver() const { return m_activeDriver; } + QString verificationReport() const { return m_verificationReport; } + + void setGpuFound(bool value) { + if (m_gpuFound == value) { + return; + } + m_gpuFound = value; + emit infoChanged(); + } + + void setGpuName(const QString &value) { + if (m_gpuName == value) { + return; + } + m_gpuName = value; + emit infoChanged(); + } + + void setDriverVersion(const QString &value) { + if (m_driverVersion == value) { + return; + } + m_driverVersion = value; + emit infoChanged(); + } + + void setDriverLoaded(bool value) { + if (m_driverLoaded == value) { + return; + } + m_driverLoaded = value; + emit infoChanged(); + } + + void setNouveauActive(bool value) { + if (m_nouveauActive == value) { + return; + } + m_nouveauActive = value; + emit infoChanged(); + } + + void setSecureBootEnabled(bool value) { + if (m_secureBootEnabled == value) { + return; + } + m_secureBootEnabled = value; + emit infoChanged(); + } + + void setWaylandSession(bool value) { + if (m_waylandSession == value) { + return; + } + m_waylandSession = value; + emit infoChanged(); + } + + void setSessionType(const QString &value) { + if (m_sessionType == value) { + return; + } + m_sessionType = value; + emit infoChanged(); + } + + void setActiveDriver(const QString &value) { + if (m_activeDriver == value) { + return; + } + m_activeDriver = value; + emit infoChanged(); + } + + void setVerificationReport(const QString &value) { + if (m_verificationReport == value) { + return; + } + m_verificationReport = value; + emit infoChanged(); + } + + Q_INVOKABLE void refresh() { emit infoChanged(); } + +signals: + void infoChanged(); + +private: + bool m_gpuFound = false; + QString m_gpuName; + QString m_driverVersion; + bool m_driverLoaded = false; + bool m_nouveauActive = false; + bool m_secureBootEnabled = false; + bool m_waylandSession = false; + QString m_sessionType = QStringLiteral("unknown"); + QString m_activeDriver = QStringLiteral("Not Installed / Unknown"); + QString m_verificationReport = QStringLiteral("GPU: None"); +}; + +class InstallerMock : public QObject { + Q_OBJECT + Q_PROPERTY(bool proprietaryAgreementRequired READ proprietaryAgreementRequired + WRITE setProprietaryAgreementRequired NOTIFY + proprietaryAgreementChanged) + Q_PROPERTY(QString proprietaryAgreementText READ proprietaryAgreementText + WRITE setProprietaryAgreementText NOTIFY + proprietaryAgreementChanged) + Q_PROPERTY(bool busy READ busy WRITE setBusy NOTIFY busyChanged) + +public: + bool proprietaryAgreementRequired() const { + return m_proprietaryAgreementRequired; + } + QString proprietaryAgreementText() const { return m_proprietaryAgreementText; } + bool busy() const { return m_busy; } + + void setProprietaryAgreementRequired(bool value) { + if (m_proprietaryAgreementRequired == value) { + return; + } + m_proprietaryAgreementRequired = value; + emit proprietaryAgreementChanged(); + } + + void setProprietaryAgreementText(const QString &value) { + if (m_proprietaryAgreementText == value) { + return; + } + m_proprietaryAgreementText = value; + emit proprietaryAgreementChanged(); + } + + void setBusy(bool value) { + if (m_busy == value) { + return; + } + m_busy = value; + emit busyChanged(); + } + + Q_INVOKABLE void refreshProprietaryAgreement() {} + Q_INVOKABLE void installProprietary(bool) {} + Q_INVOKABLE void installOpenSource() {} + Q_INVOKABLE void remove() {} + Q_INVOKABLE void deepClean() {} + +signals: + void progressMessage(const QString &message); + void proprietaryAgreementChanged(); + void busyChanged(); + void installFinished(bool success, const QString &message); + void removeFinished(bool success, const QString &message); + +private: + bool m_proprietaryAgreementRequired = false; + QString m_proprietaryAgreementText; + bool m_busy = false; +}; + +class UpdaterMock : public QObject { + Q_OBJECT + Q_PROPERTY(bool updateAvailable READ updateAvailable WRITE setUpdateAvailable + NOTIFY updateAvailableChanged) + Q_PROPERTY(QString currentVersion READ currentVersion WRITE setCurrentVersion + NOTIFY currentVersionChanged) + Q_PROPERTY(QString latestVersion READ latestVersion WRITE setLatestVersion + NOTIFY latestVersionChanged) + Q_PROPERTY(QStringList availableVersions READ availableVersions WRITE + setAvailableVersions NOTIFY availableVersionsChanged) + Q_PROPERTY(bool busy READ busy WRITE setBusy NOTIFY busyChanged) + +public: + bool updateAvailable() const { return m_updateAvailable; } + QString currentVersion() const { return m_currentVersion; } + QString latestVersion() const { return m_latestVersion; } + QStringList availableVersions() const { return m_availableVersions; } + bool busy() const { return m_busy; } + + void setUpdateAvailable(bool value) { + if (m_updateAvailable == value) { + return; + } + m_updateAvailable = value; + emit updateAvailableChanged(); + } + + void setCurrentVersion(const QString &value) { + if (m_currentVersion == value) { + return; + } + m_currentVersion = value; + emit currentVersionChanged(); + } + + void setLatestVersion(const QString &value) { + if (m_latestVersion == value) { + return; + } + m_latestVersion = value; + emit latestVersionChanged(); + } + + void setAvailableVersions(const QStringList &value) { + if (m_availableVersions == value) { + return; + } + m_availableVersions = value; + emit availableVersionsChanged(); + } + + void setBusy(bool value) { + if (m_busy == value) { + return; + } + m_busy = value; + emit busyChanged(); + } + + Q_INVOKABLE void checkForUpdate() {} + Q_INVOKABLE void applyUpdate() {} + Q_INVOKABLE void applyVersion(const QString &) {} + Q_INVOKABLE void refreshAvailableVersions() {} + +signals: + void updateAvailableChanged(); + void currentVersionChanged(); + void latestVersionChanged(); + void availableVersionsChanged(); + void busyChanged(); + void progressMessage(const QString &message); + void updateFinished(bool success, const QString &message); + +private: + bool m_updateAvailable = false; + QString m_currentVersion; + QString m_latestVersion; + QStringList m_availableVersions; + bool m_busy = false; +}; + +class TestDriverPage : public QObject { + Q_OBJECT + +private slots: + void initTestCase() { + qputenv("QT_QPA_PLATFORM", QByteArrayLiteral("offscreen")); + } + + void testDriverInstalledLocallyUsesDetectorVersion(); + void testOperationRunningStillTracksBackendBusyAfterManualStateChanges(); + +private: + QObject *createPage(DetectorMock *detector, InstallerMock *installer, + UpdaterMock *updater, QQmlEngine *engine) const; +}; + +QObject *TestDriverPage::createPage(DetectorMock *detector, + InstallerMock *installer, + UpdaterMock *updater, + QQmlEngine *engine) const { + engine->rootContext()->setContextProperty("nvidiaDetector", detector); + engine->rootContext()->setContextProperty("nvidiaInstaller", installer); + engine->rootContext()->setContextProperty("nvidiaUpdater", updater); + + const QString sourceRoot = QStringLiteral(RO_CONTROL_SOURCE_DIR); + const QString sourcePagePath = + QDir(sourceRoot).filePath(QStringLiteral("src/qml/pages/DriverPage.qml")); + QFile sourceFile(sourcePagePath); + if (!sourceFile.open(QIODevice::ReadOnly | QIODevice::Text)) { + qFatal("Failed to open DriverPage.qml from source tree"); + } + + QString pageSource = QString::fromUtf8(sourceFile.readAll()); + const QString importLine = QStringLiteral("import QtQuick.Layouts\n"); + const QString injectedImport = + QStringLiteral("import QtQuick.Layouts\nimport \"../components\"\n"); + if (!pageSource.contains(importLine)) { + qFatal("DriverPage.qml did not contain the expected import anchor"); + } + pageSource.replace(importLine, injectedImport); + + QTemporaryDir tempDir; + if (!tempDir.isValid()) { + qFatal("Failed to create temporary directory for QML test"); + } + + const QString qmlRoot = tempDir.path() + QStringLiteral("/qml"); + const QString pagesDir = qmlRoot + QStringLiteral("/pages"); + const QString componentsDir = qmlRoot + QStringLiteral("/components"); + if (!QDir().mkpath(pagesDir) || !QDir().mkpath(componentsDir)) { + qFatal("Failed to create temporary QML fixture directories"); + } + + const QString tempPagePath = pagesDir + QStringLiteral("/DriverPage.qml"); + QFile tempPageFile(tempPagePath); + if (!tempPageFile.open(QIODevice::WriteOnly | QIODevice::Text)) { + qFatal("Failed to create temporary DriverPage.qml"); + } + tempPageFile.write(pageSource.toUtf8()); + tempPageFile.close(); + + const QStringList componentFiles = {QStringLiteral("ActionButton.qml"), + QStringLiteral("InfoBadge.qml"), + QStringLiteral("SectionPanel.qml"), + QStringLiteral("StatusBanner.qml"), + QStringLiteral("StatCard.qml")}; + for (const QString &fileName : componentFiles) { + const QString sourceComponentPath = + QDir(sourceRoot).filePath(QStringLiteral("src/qml/components/") + + fileName); + const QString targetComponentPath = componentsDir + QLatin1Char('/') + fileName; + if (!QFile::copy(sourceComponentPath, targetComponentPath)) { + qFatal("Failed to copy component fixture for DriverPage test"); + } + } + + const QString sourceQmldirPath = + QDir(sourceRoot).filePath(QStringLiteral("src/qml/components/qmldir")); + const QString targetQmldirPath = componentsDir + QStringLiteral("/qmldir"); + if (!QFile::copy(sourceQmldirPath, targetQmldirPath)) { + qFatal("Failed to copy qmldir fixture for DriverPage test"); + } + + QQmlComponent component(engine, QUrl::fromLocalFile(tempPagePath)); + + if (!component.isReady()) { + qFatal("%s", qPrintable(component.errorString())); + } + + QVariantMap theme; + theme.insert(QStringLiteral("accentA"), QStringLiteral("#1f6feb")); + theme.insert(QStringLiteral("accentB"), QStringLiteral("#2da44e")); + theme.insert(QStringLiteral("accentC"), QStringLiteral("#bf8700")); + theme.insert(QStringLiteral("warning"), QStringLiteral("#9a6700")); + theme.insert(QStringLiteral("success"), QStringLiteral("#1a7f37")); + theme.insert(QStringLiteral("danger"), QStringLiteral("#cf222e")); + theme.insert(QStringLiteral("warningBg"), QStringLiteral("#fff8c5")); + theme.insert(QStringLiteral("successBg"), QStringLiteral("#dafbe1")); + theme.insert(QStringLiteral("dangerBg"), QStringLiteral("#ffebe9")); + theme.insert(QStringLiteral("infoBg"), QStringLiteral("#ddf4ff")); + theme.insert(QStringLiteral("card"), QStringLiteral("#ffffff")); + theme.insert(QStringLiteral("cardStrong"), QStringLiteral("#f6f8fa")); + theme.insert(QStringLiteral("border"), QStringLiteral("#d0d7de")); + theme.insert(QStringLiteral("text"), QStringLiteral("#1f2328")); + theme.insert(QStringLiteral("textMuted"), QStringLiteral("#656d76")); + theme.insert(QStringLiteral("textSoft"), QStringLiteral("#57606a")); + + QVariantMap initialProperties; + initialProperties.insert(QStringLiteral("width"), 1280); + initialProperties.insert(QStringLiteral("height"), 900); + initialProperties.insert(QStringLiteral("theme"), theme); + + QObject *object = component.createWithInitialProperties(initialProperties); + if (object == nullptr) { + qFatal("Failed to create DriverPage test component"); + } + + return object; +} + +void TestDriverPage::testDriverInstalledLocallyUsesDetectorVersion() { + QQmlEngine engine; + DetectorMock detector; + InstallerMock installer; + UpdaterMock updater; + + detector.setDriverVersion(QStringLiteral("580.126.18")); + updater.setCurrentVersion(QString()); + + QScopedPointer page( + createPage(&detector, &installer, &updater, &engine)); + + QTRY_VERIFY(page->property("driverInstalledLocally").toBool()); + QCOMPARE(page->property("installedVersionLabel").toString(), + QStringLiteral("580.126.18")); +} + +void TestDriverPage:: + testOperationRunningStillTracksBackendBusyAfterManualStateChanges() { + QQmlEngine engine; + DetectorMock detector; + InstallerMock installer; + UpdaterMock updater; + + QScopedPointer page( + createPage(&detector, &installer, &updater, &engine)); + + QVERIFY(!page->property("operationRunning").toBool()); + + QVERIFY(QMetaObject::invokeMethod(page.get(), "setOperationState", + Q_ARG(QVariant, QVariant(QStringLiteral("Updater"))), + Q_ARG(QVariant, QVariant(QStringLiteral("Checking"))), + Q_ARG(QVariant, QVariant(QStringLiteral("info"))), + Q_ARG(QVariant, QVariant(true)))); + QTRY_VERIFY(page->property("operationRunning").toBool()); + + QVERIFY(QMetaObject::invokeMethod(page.get(), "finishOperation", + Q_ARG(QVariant, QVariant(QStringLiteral("Updater"))), + Q_ARG(QVariant, QVariant(true)), + Q_ARG(QVariant, QVariant(QStringLiteral("Done"))))); + QTRY_VERIFY(!page->property("operationRunning").toBool()); + + updater.setBusy(true); + QTRY_VERIFY(page->property("operationRunning").toBool()); + + updater.setBusy(false); + QTRY_VERIFY(!page->property("operationRunning").toBool()); +} + +QTEST_MAIN(TestDriverPage) +#include "test_driver_page.moc" diff --git a/tests/test_metadata.cpp b/tests/test_metadata.cpp index 9df85ba..fc0ff70 100644 --- a/tests/test_metadata.cpp +++ b/tests/test_metadata.cpp @@ -21,7 +21,8 @@ class TestMetadata : public QObject { private slots: void testDesktopEntryContainsCoreFields() { - const QString desktop = readFile(QStringLiteral("data/icons/ro-control.desktop")); + const QString desktop = readFile( + QStringLiteral("data/icons/io.github.projectroasd.rocontrol.desktop")); QVERIFY(!desktop.isEmpty()); QVERIFY(desktop.contains(QStringLiteral("[Desktop Entry]"))); QVERIFY(desktop.contains(QStringLiteral("Exec=ro-control"))); @@ -31,10 +32,13 @@ private slots: } void testAppStreamContainsExpectedIdsAndUrls() { - const QString metainfo = readFile(QStringLiteral("data/icons/ro-control.metainfo.xml")); + const QString metainfo = readFile(QStringLiteral( + "data/icons/io.github.projectroasd.rocontrol.metainfo.xml")); QVERIFY(!metainfo.isEmpty()); - QVERIFY(metainfo.contains(QStringLiteral("ro-control.desktop"))); - QVERIFY(metainfo.contains(QStringLiteral("ro-control.desktop"))); + QVERIFY(metainfo.contains( + QStringLiteral("io.github.projectroasd.rocontrol.desktop"))); + QVERIFY(metainfo.contains(QStringLiteral( + "io.github.projectroasd.rocontrol.desktop"))); QVERIFY(metainfo.contains(QStringLiteral("ro-control"))); QVERIFY(metainfo.contains(QStringLiteral("https://github.com/Project-Ro-ASD/ro-Control"))); QVERIFY(metainfo.contains(QStringLiteral("https://github.com/Project-Ro-ASD/ro-Control/issues"))); @@ -49,14 +53,17 @@ private slots: } void testDesktopAndAppStreamIdsStayAligned() { - const QString desktop = readFile(QStringLiteral("data/icons/ro-control.desktop")); - const QString metainfo = readFile(QStringLiteral("data/icons/ro-control.metainfo.xml")); + const QString desktop = readFile( + QStringLiteral("data/icons/io.github.projectroasd.rocontrol.desktop")); + const QString metainfo = readFile(QStringLiteral( + "data/icons/io.github.projectroasd.rocontrol.metainfo.xml")); QVERIFY(!desktop.isEmpty()); QVERIFY(!metainfo.isEmpty()); QVERIFY(desktop.contains(QStringLiteral("Exec=ro-control"))); - QVERIFY(metainfo.contains(QStringLiteral("ro-control.desktop"))); + QVERIFY(metainfo.contains( + QStringLiteral("io.github.projectroasd.rocontrol.desktop"))); const QRegularExpression screenshotRe( QStringLiteral(R"(https://raw\.githubusercontent\.com/.+/docs/screenshots/.+)")); diff --git a/tests/test_monitor.cpp b/tests/test_monitor.cpp index c596404..448217a 100644 --- a/tests/test_monitor.cpp +++ b/tests/test_monitor.cpp @@ -1,4 +1,8 @@ #include +#include +#include +#include +#include #include "monitor/cpumonitor.h" #include "monitor/gpumonitor.h" @@ -71,6 +75,38 @@ private slots: QVERIFY(gpu.running()); } + void testGpuPartialTelemetryKeepsMonitorAvailable() { + QTemporaryDir tempDir; + QVERIFY(tempDir.isValid()); + + const QString scriptPath = tempDir.filePath(QStringLiteral("fake-nvidia-smi.sh")); + QFile script(scriptPath); + QVERIFY(script.open(QIODevice::WriteOnly | QIODevice::Text)); + + QTextStream stream(&script); + stream << "#!/bin/sh\n"; + stream << "printf 'NVIDIA GeForce RTX 4080, 61, N/A, 2048, 16384\\n'\n"; + script.close(); + QVERIFY(QFile::setPermissions(scriptPath, QFileDevice::ReadOwner | + QFileDevice::WriteOwner | + QFileDevice::ExeOwner)); + + qputenv("RO_CONTROL_COMMAND_NVIDIA_SMI", scriptPath.toUtf8()); + GpuMonitor gpu; + gpu.stop(); + gpu.refresh(); + + QVERIFY(gpu.available()); + QCOMPARE(gpu.gpuName(), QStringLiteral("NVIDIA GeForce RTX 4080")); + QCOMPARE(gpu.temperatureC(), 61); + QCOMPARE(gpu.utilizationPercent(), 0); + QCOMPARE(gpu.memoryUsedMiB(), 2048); + QCOMPARE(gpu.memoryTotalMiB(), 16384); + QCOMPARE(gpu.memoryUsagePercent(), 12); + + qunsetenv("RO_CONTROL_COMMAND_NVIDIA_SMI"); + } + void testRamConstruction() { RamMonitor ram; QVERIFY(ram.running()); @@ -100,6 +136,34 @@ private slots: ram.start(); QVERIFY(ram.running()); } + + void testRamUsesOverriddenMeminfoPath() { + QTemporaryDir tempDir; + QVERIFY(tempDir.isValid()); + + const QString meminfoPath = tempDir.filePath(QStringLiteral("meminfo")); + QFile meminfo(meminfoPath); + QVERIFY(meminfo.open(QIODevice::WriteOnly | QIODevice::Text)); + meminfo.write("MemTotal: 32768000 kB\n"); + meminfo.write("MemAvailable: 23552000 kB\n"); + meminfo.write("MemFree: 3072000 kB\n"); + meminfo.write("Buffers: 204800 kB\n"); + meminfo.write("Cached: 8192000 kB\n"); + meminfo.close(); + + qputenv("RO_CONTROL_MEMINFO_PATH", meminfoPath.toUtf8()); + + RamMonitor ram; + ram.stop(); + ram.refresh(); + + QVERIFY(ram.available()); + QCOMPARE(ram.totalMiB(), 32000); + QCOMPARE(ram.usedMiB(), 9000); + QCOMPARE(ram.usagePercent(), 28); + + qunsetenv("RO_CONTROL_MEMINFO_PATH"); + } }; QTEST_MAIN(TestMonitor) diff --git a/tests/test_preferences.cpp b/tests/test_preferences.cpp new file mode 100644 index 0000000..ebcf567 --- /dev/null +++ b/tests/test_preferences.cpp @@ -0,0 +1,94 @@ +#include +#include +#include +#include +#include +#include + +#include "backend/system/languagemanager.h" +#include "backend/system/uipreferencesmanager.h" + +class TestPreferences : public QObject { + Q_OBJECT + +private slots: + void init(); + void testUiPreferencesDefaults(); + void testUiPreferencesPersistChanges(); + void testUiPreferencesNormalizesInvalidThemeMode(); + void testLanguageManagerExposesEffectiveLanguageMetadata(); +}; + +void TestPreferences::init() { + QCoreApplication::setOrganizationName( + QStringLiteral("Project-Ro-ASD-TestSuite")); + QCoreApplication::setApplicationName(QStringLiteral("ro-control-preferences")); + + QSettings settings; + settings.clear(); + settings.sync(); +} + +void TestPreferences::testUiPreferencesDefaults() { + UiPreferencesManager preferences; + + QCOMPARE(preferences.themeMode(), QStringLiteral("system")); + QCOMPARE(preferences.compactMode(), false); + QCOMPARE(preferences.showAdvancedInfo(), true); + QCOMPARE(preferences.availableThemeModes().size(), 3); +} + +void TestPreferences::testUiPreferencesPersistChanges() { + UiPreferencesManager preferences; + QSignalSpy themeSpy(&preferences, &UiPreferencesManager::themeModeChanged); + QSignalSpy compactSpy(&preferences, &UiPreferencesManager::compactModeChanged); + QSignalSpy advancedSpy(&preferences, + &UiPreferencesManager::showAdvancedInfoChanged); + + preferences.setThemeMode(QStringLiteral("dark")); + preferences.setCompactMode(true); + preferences.setShowAdvancedInfo(false); + + QCOMPARE(themeSpy.count(), 1); + QCOMPARE(compactSpy.count(), 1); + QCOMPARE(advancedSpy.count(), 1); + + UiPreferencesManager reloadedPreferences; + QCOMPARE(reloadedPreferences.themeMode(), QStringLiteral("dark")); + QCOMPARE(reloadedPreferences.compactMode(), true); + QCOMPARE(reloadedPreferences.showAdvancedInfo(), false); +} + +void TestPreferences::testUiPreferencesNormalizesInvalidThemeMode() { + UiPreferencesManager preferences; + + preferences.setThemeMode(QStringLiteral("midnight")); + QCOMPARE(preferences.themeMode(), QStringLiteral("system")); +} + +void TestPreferences::testLanguageManagerExposesEffectiveLanguageMetadata() { + QQmlEngine engine; + QTranslator translator; + LanguageManager manager(QCoreApplication::instance(), &engine, &translator); + + const QVariantList languages = manager.availableLanguages(); + QVERIFY(!languages.isEmpty()); + QCOMPARE(languages.first().toMap().value(QStringLiteral("code")).toString(), + QStringLiteral("system")); + QVERIFY(languages.first() + .toMap() + .contains(QStringLiteral("nativeLabel"))); + + manager.setCurrentLanguage(QStringLiteral("system")); + QVERIFY( + manager.currentLanguageLabel().startsWith(QStringLiteral("System Default"))); + + manager.setCurrentLanguage(QStringLiteral("tr")); + QCOMPARE(manager.currentLanguage(), QStringLiteral("tr")); + QCOMPARE(manager.effectiveLanguage(), QStringLiteral("tr")); + QCOMPARE(manager.currentLanguageLabel(), QStringLiteral("Turkce")); +} + +QTEST_GUILESS_MAIN(TestPreferences) + +#include "test_preferences.moc" diff --git a/tests/test_system_integration.cpp b/tests/test_system_integration.cpp index 432904d..e25f3b8 100644 --- a/tests/test_system_integration.cpp +++ b/tests/test_system_integration.cpp @@ -1,6 +1,10 @@ +#include +#include +#include #include #include +#include "system/capabilityprobe.h" #include "system/commandrunner.h" #include "system/dnfmanager.h" #include "system/polkit.h" @@ -9,6 +13,52 @@ class TestSystemIntegration : public QObject { Q_OBJECT private slots: + void testCommandRunnerUsesProgramOverride() { + QTemporaryDir tempDir; + QVERIFY(tempDir.isValid()); + + const QString scriptPath = tempDir.filePath(QStringLiteral("fake-dnf.sh")); + QFile script(scriptPath); + QVERIFY(script.open(QIODevice::WriteOnly | QIODevice::Text)); + QVERIFY(script.write("#!/bin/sh\nprintf 'override-ok\\n'\nexit 0\n") > 0); + script.close(); + QVERIFY(script.setPermissions(QFileDevice::ReadOwner | + QFileDevice::WriteOwner | + QFileDevice::ExeOwner)); + + qputenv("RO_CONTROL_COMMAND_DNF", scriptPath.toUtf8()); + + CommandRunner runner; + const auto result = runner.run(QStringLiteral("dnf")); + QCOMPARE(result.exitCode, 0); + QCOMPARE(result.stdout.trimmed(), QStringLiteral("override-ok")); + + qunsetenv("RO_CONTROL_COMMAND_DNF"); + } + + void testCapabilityProbeUsesProgramOverride() { + QTemporaryDir tempDir; + QVERIFY(tempDir.isValid()); + + const QString scriptPath = + tempDir.filePath(QStringLiteral("fake-nvidia-smi.sh")); + QFile script(scriptPath); + QVERIFY(script.open(QIODevice::WriteOnly | QIODevice::Text)); + QVERIFY(script.write("#!/bin/sh\nexit 0\n") > 0); + script.close(); + QVERIFY(script.setPermissions(QFileDevice::ReadOwner | + QFileDevice::WriteOwner | + QFileDevice::ExeOwner)); + + qputenv("RO_CONTROL_COMMAND_NVIDIA_SMI", scriptPath.toUtf8()); + + const auto status = CapabilityProbe::probeTool(QStringLiteral("nvidia-smi")); + QVERIFY(status.available); + QCOMPARE(QDir::cleanPath(status.resolvedPath), QDir::cleanPath(scriptPath)); + + qunsetenv("RO_CONTROL_COMMAND_NVIDIA_SMI"); + } + void testCommandRunnerBasic() { CommandRunner runner; const auto result = runner.run(QStringLiteral("true")); diff --git a/tests/test_updater.cpp b/tests/test_updater.cpp index 18b359d..d24bd1f 100644 --- a/tests/test_updater.cpp +++ b/tests/test_updater.cpp @@ -1,5 +1,8 @@ #include +#define private public +#include "nvidia/updater.h" +#undef private #include "nvidia/versionparser.h" class TestUpdater : public QObject { @@ -51,6 +54,75 @@ private slots: QCOMPARE(specs.at(1), QStringLiteral("xorg-x11-drv-nvidia-3:570.153.02-1.fc42")); } + + void testBuildTransactionArgumentsForFreshInstallStaysScoped() { + NvidiaUpdater updater; + updater.m_latestVersion = QStringLiteral("3:570.153.02-1.fc42"); + + const QStringList args = + updater.buildTransactionArguments(QString(), QString(), QString(), + QStringLiteral("akmod-nvidia")); + + QCOMPARE(args.value(0), QStringLiteral("install")); + QVERIFY(args.contains(QStringLiteral("--refresh"))); + QVERIFY(args.contains(QStringLiteral("--best"))); + QVERIFY(!args.contains(QStringLiteral("update"))); + QVERIFY(!args.contains(QStringLiteral("upgrade"))); + QVERIFY(!args.contains(QStringLiteral("system-upgrade"))); + QVERIFY(args.contains(QStringLiteral("akmod-nvidia-3:570.153.02-1.fc42"))); + } + + void testBuildTransactionArgumentsForInstalledDriverAvoidsBroadUpdate() { + NvidiaUpdater updater; + updater.m_latestVersion = QStringLiteral("3:570.153.02-1.fc42"); + + const QStringList args = updater.buildTransactionArguments( + QString(), QStringLiteral("3:565.77-1.fc42"), QString(), + QStringLiteral("akmod-nvidia")); + + QCOMPARE(args.value(0), QStringLiteral("distro-sync")); + QVERIFY(args.contains(QStringLiteral("--allowerasing"))); + QVERIFY(!args.contains(QStringLiteral("update"))); + QVERIFY(!args.contains(QStringLiteral("upgrade"))); + QVERIFY(!args.contains(QStringLiteral("system-upgrade"))); + QVERIFY(args.contains(QStringLiteral("akmod-nvidia-3:570.153.02-1.fc42"))); + } + + void testTransactionChangedReturnsFalseForNoopOutput() { + NvidiaUpdater updater; + CommandRunner::Result result{ + .exitCode = 0, + .stdout = QStringLiteral("Last metadata expiration check: 0:00:12 ago.\nNothing to do.\n"), + .stderr = QString(), + .attempt = 1, + }; + + QVERIFY(!updater.transactionChanged(result)); + } + + void testTransactionChangedReturnsTrueForRealPackageTransaction() { + NvidiaUpdater updater; + CommandRunner::Result result{ + .exitCode = 0, + .stdout = QStringLiteral("Installing:\nakmod-nvidia.x86_64 3:570.153.02-1.fc42\nComplete!\n"), + .stderr = QString(), + .attempt = 1, + }; + + QVERIFY(updater.transactionChanged(result)); + } + + void testBuildTransactionArgumentsForOpenKernelModules() { + NvidiaUpdater updater; + updater.m_latestVersion = QStringLiteral("3:570.153.02-1.fc42"); + + const QStringList args = updater.buildTransactionArguments( + QString(), QStringLiteral("3:565.77-1.fc42"), QString(), + QStringLiteral("akmod-nvidia-open")); + + QVERIFY(args.contains( + QStringLiteral("akmod-nvidia-open-3:570.153.02-1.fc42"))); + } }; QTEST_MAIN(TestUpdater)