diff --git a/.clang-format b/.clang-format index 1d896f6..42ee941 100644 --- a/.clang-format +++ b/.clang-format @@ -1,7 +1,8 @@ --- # ro-Control clang-format configuration -# CI bu dosyayı kullanır — değişiklik yapılırsa tüm kaynak dosyalar yeniden formatlanmalı. -# Kullanım: find src/ -name "*.cpp" -o -name "*.h" | xargs clang-format -i +# CI bu dosyayı kullanır; değişirse tüm C++ kaynakları yeniden formatlanmalıdır. +# Kullanım: +# find src \( -name "*.cpp" -o -name "*.h" \) -print0 | xargs -0 clang-format -i BasedOnStyle: LLVM Language: Cpp diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 3f2fd17..57d4aa6 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -37,8 +37,11 @@ What actually happened. ## Logs ``` -# Paste relevant output from: -# journalctl --user -u ro-control --no-pager -n 50 +# Paste any relevant output from one or more of these: +# ro-control started from a terminal +# coredumpctl info ro-control +# journalctl --since "10 minutes ago" --no-pager +# dnf history info ``` ## Additional Context diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 415a1f6..30cf445 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,6 +33,7 @@ jobs: ninja-build \ qt6-qtbase-devel \ qt6-qtdeclarative-devel \ + qt6-qttools-devel \ qt6-qtwayland-devel \ kf6-qqc2-desktop-style \ polkit-devel \ @@ -44,7 +45,8 @@ jobs: cmake -B build \ -GNinja \ -DCMAKE_BUILD_TYPE=Release \ - -DBUILD_TESTS=ON + -DBUILD_TESTS=ON \ + -DREQUIRE_TRANSLATIONS=ON - name: Build run: cmake --build build --parallel @@ -80,6 +82,7 @@ jobs: qt6-qtbase-devel \ qt6-qtbase-private-devel \ qt6-qtdeclarative-devel \ + qt6-qttools-devel \ qt6-qtwayland-devel \ kf6-qqc2-desktop-style \ polkit-devel @@ -93,13 +96,13 @@ jobs: fi ARCHIVE_BASENAME="ro-control-${VERSION}" mkdir -p ~/rpmbuild/SOURCES ~/rpmbuild/SPECS - tar \ - --exclude-vcs \ - --exclude='./build' \ - --exclude='./build-*' \ - --transform="s,^,${ARCHIVE_BASENAME}/," \ - -czf "${HOME}/rpmbuild/SOURCES/${ARCHIVE_BASENAME}.tar.gz" \ - . + STAGE_DIR="$(mktemp -d)" + cp -a . "${STAGE_DIR}/${ARCHIVE_BASENAME}" + rm -rf "${STAGE_DIR}/${ARCHIVE_BASENAME}/.git" \ + "${STAGE_DIR}/${ARCHIVE_BASENAME}/build" \ + "${STAGE_DIR}/${ARCHIVE_BASENAME}/build-"* + tar -C "${STAGE_DIR}" -czf "${HOME}/rpmbuild/SOURCES/${ARCHIVE_BASENAME}.tar.gz" "${ARCHIVE_BASENAME}" + rm -rf "${STAGE_DIR}" cp packaging/rpm/ro-control.spec "${HOME}/rpmbuild/SPECS/ro-control.spec" - name: Build RPM diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a2c757a..e23f597 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,15 +18,21 @@ jobs: - name: Checkout repository uses: actions/checkout@v4 + - name: Derive release version + run: | + VERSION="${GITHUB_REF_NAME#v}" + echo "VERSION=${VERSION}" >> "${GITHUB_ENV}" + - name: Create source archives run: | - git archive --format=tar.gz --output=ro-control-${GITHUB_REF_NAME}.tar.gz ${GITHUB_SHA} - git archive --format=zip --output=ro-control-${GITHUB_REF_NAME}.zip ${GITHUB_SHA} + PREFIX="ro-control-${VERSION}" + 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: Publish release uses: softprops/action-gh-release@v2 with: generate_release_notes: true files: | - ro-control-${{ github.ref_name }}.tar.gz - ro-control-${{ github.ref_name }}.zip + ro-control-${{ env.VERSION }}.tar.gz + ro-control-${{ env.VERSION }}.zip diff --git a/.gitignore b/.gitignore index 1341a53..7c6cfc9 100644 --- a/.gitignore +++ b/.gitignore @@ -38,7 +38,8 @@ ui_*.h CMakeLists.txt.user # VS Code -.vscode/ +.vscode/* +!.vscode/settings.json *.code-workspace # CLion / JetBrains @@ -81,23 +82,3 @@ test_results/ vgcore.* callgrind.out.* massif.out.* - -# Build -build/ - -# CMake -CMakeFiles/ -CMakeCache.txt -cmake_install.cmake -Makefile - -# Qt -*_autogen/ -moc_* -qmltypes - -# Binary -*.o -*.so -*.a -.DS_Store diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..2a79773 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,9 @@ +{ + "qt-qml.qmlls.enabled": true, + "qt-qml.qmlls.useQmlImportPathEnvVar": true, + "qt-qml.qmlls.additionalImportPaths": [ + "${workspaceFolder}/src/qml", + "${workspaceFolder}/src/qml/pages", + "${workspaceFolder}/build/rocontrol" + ] +} diff --git a/CHANGELOG.md b/CHANGELOG.md index 7548c98..f894f64 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,18 +16,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Session-aware post-install and post-update handling for Wayland/X11 - Real system monitors for CPU, GPU, and RAM with live QML bindings - Driver update check/apply flow and deep-clean operation support -- Fedora build and test workflow with CMake + Qt6 +- Linux build and test workflow with CMake + Qt6 ### Changed - Driver management UI is wired to backend operations instead of placeholders - Documentation updated for current architecture and build instructions - Test suite expanded to cover monitor metric ranges and detector reporting +- Repository metadata and packaging references aligned with the active GitHub organization +- Privileged command flow now uses a dedicated allowlisted helper instead of raw `pkexec` command dispatch ### Fixed - Command execution path preserves stdout reliably -- RPM Fusion URL resolution and repository failure handling improved +- RPM repository URL resolution and repository failure handling improved - Updater API/header alignment and monitor test compatibility issues resolved - Repository cleanup for stray macOS metadata files +- PolicyKit metadata, helper install path, and packaged action identifiers are now consistent --- diff --git a/CMakeLists.txt b/CMakeLists.txt index 8710abe..ad266b7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,6 +7,12 @@ project(ro-control LANGUAGES CXX ) +set(RO_CONTROL_POLICY_ID "io.github.ProjectRoASD.rocontrol.manage-drivers") +set(RO_CONTROL_HELPER_NAME "ro-control-helper") +set(RO_CONTROL_HELPER_BUILD_PATH + "${CMAKE_CURRENT_BINARY_DIR}/${RO_CONTROL_HELPER_NAME}" +) + # ─── C++ Standard ──────────────────────────────────────────────────────────── set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) @@ -33,6 +39,18 @@ find_package(Qt6 REQUIRED COMPONENTS find_package(Qt6 QUIET COMPONENTS LinguistTools) +option(REQUIRE_TRANSLATIONS + "Fail configuration when Qt Linguist tools are unavailable" + ON +) + +if(REQUIRE_TRANSLATIONS AND NOT Qt6LinguistTools_FOUND) + message(FATAL_ERROR + "Qt6 LinguistTools were not found. Install the Qt tools development " + "package or configure with -DREQUIRE_TRANSLATIONS=OFF for local-only builds." + ) +endif() + qt_standard_project_setup() if(COMMAND qt_policy) @@ -56,6 +74,7 @@ set(BACKEND_SOURCES set(APP_SOURCES src/main.cpp + src/cli/cli.cpp ${BACKEND_SOURCES} ) @@ -93,15 +112,10 @@ if(Qt6LinguistTools_FOUND) qt_add_translations(ro-control TS_FILES ${TS_FILES} + RESOURCE_PREFIX "/i18n" ) endif() -# ─── Include Directories ────────────────────────────────────────────────────── -target_include_directories(ro-control PRIVATE - ${CMAKE_CURRENT_SOURCE_DIR}/src - ${CMAKE_CURRENT_SOURCE_DIR}/src/backend -) - # ─── Link Qt Libraries ──────────────────────────────────────────────────────── target_link_libraries(ro-control PRIVATE Qt6::Core @@ -123,19 +137,59 @@ target_compile_options(ro-control PRIVATE # ─── Install Targets ────────────────────────────────────────────────────────── include(GNUInstallDirs) +set(RO_CONTROL_HELPER_INSTALL_PATH + "${CMAKE_INSTALL_FULL_LIBEXECDIR}/${RO_CONTROL_HELPER_NAME}" +) +set(RO_CONTROL_POLICY_BUILD_PATH + "${CMAKE_CURRENT_BINARY_DIR}/io.github.ProjectRoASD.rocontrol.policy" +) + +# ─── Include Directories ────────────────────────────────────────────────────── +target_include_directories(ro-control PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/src + ${CMAKE_CURRENT_SOURCE_DIR}/src/backend +) + +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( + data/helpers/ro-control-helper.sh.in + ${RO_CONTROL_HELPER_BUILD_PATH} + @ONLY + NEWLINE_STYLE UNIX +) + +configure_file( + data/polkit/io.github.ProjectRoASD.rocontrol.policy.in + ${RO_CONTROL_POLICY_BUILD_PATH} + @ONLY + NEWLINE_STYLE UNIX +) + install(TARGETS ro-control RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} ) +install(PROGRAMS ${RO_CONTROL_HELPER_BUILD_PATH} + DESTINATION ${CMAKE_INSTALL_LIBEXECDIR} + RENAME ${RO_CONTROL_HELPER_NAME} +) + install(FILES data/icons/ro-control.desktop DESTINATION ${CMAKE_INSTALL_DATADIR}/applications ) -install(DIRECTORY data/icons/hicolor/ - DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor +install(FILES + data/icons/hicolor/scalable/apps/ro-control.svg + DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/scalable/apps ) -install(FILES data/icons/hicolor/256x256/apps/ro-control.png +install(FILES + data/icons/hicolor/256x256/apps/ro-control.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/256x256/apps ) @@ -143,10 +197,27 @@ install(FILES data/icons/ro-control.metainfo.xml DESTINATION ${CMAKE_INSTALL_DATADIR}/metainfo ) -install(FILES data/polkit/com.github.AcikKaynakGelistirmeToplulugu.rocontrol.policy +install(FILES ${RO_CONTROL_POLICY_BUILD_PATH} DESTINATION ${CMAKE_INSTALL_DATADIR}/polkit-1/actions ) +install(FILES docs/man/ro-control.1 + DESTINATION ${CMAKE_INSTALL_DATADIR}/man/man1 +) + +install(FILES data/completions/ro-control.bash + DESTINATION ${CMAKE_INSTALL_DATADIR}/bash-completion/completions + RENAME ro-control +) + +install(FILES data/completions/_ro-control + DESTINATION ${CMAKE_INSTALL_DATADIR}/zsh/site-functions +) + +install(FILES data/completions/ro-control.fish + DESTINATION ${CMAKE_INSTALL_DATADIR}/fish/vendor_completions.d +) + # ─── Tests ─────────────────────────────────────────────────────────────────── option(BUILD_TESTS "Build unit tests" OFF) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 7c3d7e2..7831097 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -21,10 +21,26 @@ We as members, contributors, and maintainers pledge to make participation in ro- ## Enforcement -Instances of unacceptable behavior may be reported by opening a GitHub issue or contacting the maintainers directly. All complaints will be reviewed and investigated promptly. +Instances of unacceptable behavior should not be reported through a public GitHub issue. +Please contact the maintainers privately through GitHub's direct contact channels. +All complaints will be reviewed and investigated promptly and handled with as much confidentiality as possible. Maintainers have the right to remove, edit, or reject comments, commits, issues, and other contributions that do not align with this Code of Conduct. +## Scope + +This Code of Conduct applies within project spaces and in public spaces when an +individual is officially representing the project or its community. + +## Enforcement Guidelines + +Project maintainers may apply any action they deem appropriate, including: + +- Warning contributors about unacceptable behavior +- Removing or editing content +- Temporarily restricting participation +- Permanently banning contributors from project spaces + ## Attribution This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org), version 2.1. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 565d3fc..93a0cf6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -80,7 +80,7 @@ We follow [Conventional Commits](https://www.conventionalcommits.org/): ``` feat: add real-time GPU temperature monitoring fix: crash when no NVIDIA GPU is detected -docs: update build instructions for Fedora 41 +docs: update build instructions chore: update CMake minimum version to 3.22 ``` @@ -92,7 +92,6 @@ chore: update CMake minimum version to 3.22 | Component | Minimum | |-----------|---------| -| Fedora | 40+ | | GCC | 13+ | | CMake | 3.22+ | | Qt | 6.6+ | @@ -103,8 +102,10 @@ chore: update CMake minimum version to 3.22 sudo dnf install cmake extra-cmake-modules gcc-c++ \ qt6-qtbase-devel \ qt6-qtdeclarative-devel \ + qt6-qttools-devel \ qt6-qtwayland-devel \ - kf6-qqc2-desktop-style + kf6-qqc2-desktop-style \ + polkit-devel ``` ### Build @@ -146,17 +147,32 @@ cd build && ctest --output-on-failure ## Translations -Translations are tracked with Qt Linguist source files in `i18n/`. +ro-Control uses the Qt Linguist pipeline for UI localization. -To add a new language now: +Translation rules: -1. Add or update the relevant `.ts` file in `i18n/`. -2. Reconfigure/build so Qt translation targets regenerate `.qm` outputs. -3. Use Qt Linguist tools (`lupdate`, `linguist`, `lrelease`) as needed. -4. Verify layout does not break with longer text. -5. Submit a PR to `dev` with screenshots for changed pages when UI text changes. +- Use `qsTr(...)` for QML strings and `tr(...)` for C++ strings +- Keep English as the source language in code +- Update `.ts` files under `i18n/` +- Verify the UI at 980x640 and at a larger desktop size +- Include screenshots if a translation materially changes layout -Current translation coverage is partial and expanded incrementally. See [i18n/README.md](i18n/README.md) for the active workflow. +Recommended workflow: + +```bash +sudo dnf install qt6-qttools-devel +cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug +lupdate src -ts i18n/ro-control_en.ts i18n/ro-control_tr.ts +linguist i18n/ro-control_tr.ts +cmake --build build +``` + +When adding a new language: + +1. Copy `i18n/ro-control_en.ts` to `i18n/ro-control_.ts` +2. Add the new file to `TS_FILES` in `CMakeLists.txt` +3. Translate all entries +4. Verify runtime locale loading and layout integrity --- @@ -164,9 +180,9 @@ Current translation coverage is partial and expanded incrementally. See [i18n/RE Use the [bug report template](.github/ISSUE_TEMPLATE/bug_report.md) and include: -- Fedora version (`cat /etc/fedora-release`) +- Distribution / platform details - GPU model (`lspci | grep -i nvidia`) - Current driver version (`nvidia-smi`) - Steps to reproduce - Expected vs actual behavior -- Relevant logs (`journalctl -u ro-control`) +- Relevant terminal output, `coredumpctl info ro-control`, or recent journal entries diff --git a/README.md b/README.md index c406d7f..04cbadd 100644 --- a/README.md +++ b/README.md @@ -2,12 +2,11 @@
-ro-Control Logo +![ro-Control Logo](data/icons/hicolor/scalable/apps/ro-control.svg) **Smart NVIDIA Driver Manager & System Monitor for Linux** [![License: GPL-3.0](https://img.shields.io/badge/license-GPL--3.0-blue?style=flat-square)](LICENSE) -[![Platform](https://img.shields.io/badge/platform-Fedora%2040%2B-51A2DA?style=flat-square)](https://getfedora.org/) [![Built with Qt6](https://img.shields.io/badge/built%20with-Qt6%20%2B%20QML-41CD52?style=flat-square)](https://www.qt.io/) [![C++20](https://img.shields.io/badge/C%2B%2B-20-00599C?style=flat-square)](https://isocpp.org/) @@ -19,7 +18,7 @@ --- -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 Fedora Linux. It provides a modern, Plasma-native interface for installing, updating, and monitoring graphics drivers — with full PolicyKit integration for secure privilege escalation. +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. ## Features @@ -41,16 +40,26 @@ ro-Control is a native KDE Plasma desktop application built with **C++20** and * - **PolicyKit integration** — Secure privilege escalation without running as root ### 🌍 Internationalization -- English and Turkish interface -- Extensible translation system +- Runtime locale loading with Qt translations (`.ts` / `.qm`) +- English source strings with Turkish translation included +- Extensible translation workflow for additional languages + +### 🧰 CLI Support +- `ro-control help` for usage +- `ro-control version` for application version +- `ro-control status` for concise system and driver state +- `ro-control diagnostics --json` for machine-readable diagnostics +- `ro-control driver install|remove|update|deep-clean` for scripted driver management +- Installed `man ro-control` page and Bash/Zsh/Fish shell completions ## Screenshots -> Screenshots will be added after the first UI milestone. +Preview assets are available under [`docs/screenshots/`](docs/screenshots/). +Additional PNG screenshots should be added before wider store distribution. ## Installation -### Fedora (RPM) — Recommended +### RPM Package Download the latest `.rpm` from [Releases](https://github.com/Project-Ro-ASD/ro-Control/releases): @@ -62,15 +71,28 @@ sudo dnf install ./ro-control-*.rpm See [docs/BUILDING.md](docs/BUILDING.md) for full instructions. +### CLI Quick 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 +``` + **Quick start:** ```bash -# Install dependencies (Fedora 40+) +# Install dependencies sudo dnf install cmake extra-cmake-modules gcc-c++ \ qt6-qtbase-devel \ qt6-qtdeclarative-devel \ + qt6-qttools-devel \ qt6-qtwayland-devel \ - kf6-qqc2-desktop-style + kf6-qqc2-desktop-style \ + polkit-devel # Clone and build git clone https://github.com/Project-Ro-ASD/ro-Control.git @@ -87,25 +109,20 @@ sudo make install ``` ro-Control/ -├── .github/ # CI, issue templates, PR template ├── src/ │ ├── backend/ # C++ business logic │ │ ├── nvidia/ # Driver detection, install, update │ │ ├── monitor/ # GPU/CPU/RAM statistics │ │ └── system/ # Polkit, DNF, command runner │ ├── qml/ # Qt Quick UI -│ │ ├── assets/ # Embedded logos and UI assets -│ │ ├── components/ # Reusable UI components + qmldir -│ │ └── pages/ # Main application pages + qmldir +│ │ ├── pages/ # Main application pages +│ │ └── components/ # Reusable UI components │ └── main.cpp -├── data/ # Icons, desktop file, PolicyKit, AppStream -├── docs/ # Architecture, build, design, release docs -├── i18n/ # Qt Linguist translation sources (.ts) -├── packaging/rpm/ # Fedora RPM spec +├── data/ # Icons, .desktop, PolicyKit, AppStream +├── packaging/rpm/ # RPM packaging +├── docs/ # Architecture and build docs ├── tests/ # Unit tests -├── CMakeLists.txt -├── SECURITY.md -└── SUPPORT.md +└── CMakeLists.txt ``` ## Contributing @@ -129,7 +146,6 @@ git push origin feature/your-feature-name | Component | Minimum Version | |-----------|----------------| -| Fedora | 40+ | | Qt | 6.6+ | | CMake | 3.22+ | | GCC | 13+ (C++20) | diff --git a/README.tr.md b/README.tr.md index 8f5a849..72973a1 100644 --- a/README.tr.md +++ b/README.tr.md @@ -2,12 +2,11 @@
-ro-Control Logo +![ro-Control Logo](data/icons/hicolor/scalable/apps/ro-control.svg) **Linux için Akıllı NVIDIA Sürücü Yöneticisi & Sistem Monitörü** [![Lisans: GPL-3.0](https://img.shields.io/badge/lisans-GPL--3.0-blue?style=flat-square)](LICENSE) -[![Platform](https://img.shields.io/badge/platform-Fedora%2040%2B-51A2DA?style=flat-square)](https://getfedora.org/) [![Qt6 ile yapıldı](https://img.shields.io/badge/Qt6%20%2B%20QML-41CD52?style=flat-square)](https://www.qt.io/) [![C++20](https://img.shields.io/badge/C%2B%2B-20-00599C?style=flat-square)](https://isocpp.org/) @@ -19,7 +18,7 @@ --- -ro-Control, **C++20** ve **Qt6/QML** ile geliştirilmiş, Fedora 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. +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. ## Özellikler @@ -41,16 +40,26 @@ ro-Control, **C++20** ve **Qt6/QML** ile geliştirilmiş, Fedora Linux üzerinde - **PolicyKit entegrasyonu** — Root olarak çalıştırmadan güvenli yetki yükseltme ### 🌍 Çok Dil Desteği -- Türkçe ve İngilizce arayüz -- Genişletilebilir çeviri sistemi +- Qt çeviri sistemi (`.ts` / `.qm`) ile çalışma zamanı yerelleştirme +- İngilizce kaynak metinler ve dahil edilmiş Türkçe çeviri +- Yeni diller için genişletilebilir iş akışı + +### 🧰 CLI Desteği +- `ro-control help` kullanım bilgisini gösterir +- `ro-control version` uygulama sürümünü gösterir +- `ro-control status` kısa sistem ve sürücü durumunu gösterir +- `ro-control diagnostics --json` makine tarafından işlenebilir tanı çıktısı üretir +- `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 ## Ekran Görüntüleri -> Ekran görüntüleri ilk UI milestone'ından sonra eklenecektir. +Önizleme görselleri [`docs/screenshots/`](docs/screenshots/) altında bulunur. +Daha geniş mağaza / distro dağıtımı öncesinde PNG ekran görüntüleri eklenmelidir. ## Kurulum -### Fedora (RPM) — Önerilen +### RPM Paketi [Releases](https://github.com/Project-Ro-ASD/ro-Control/releases) sayfasından en son `.rpm` dosyasını indirin: @@ -62,15 +71,28 @@ sudo dnf install ./ro-control-*.rpm Tam talimatlar için [docs/BUILDING.md](docs/BUILDING.md) dosyasına bakın. +### CLI Hızlı Örnekler + +```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 +``` + **Hızlı başlangıç:** ```bash -# Bağımlılıkları kur (Fedora 40+) +# Bağımlılıkları kur sudo dnf install cmake extra-cmake-modules gcc-c++ \ qt6-qtbase-devel \ qt6-qtdeclarative-devel \ + qt6-qttools-devel \ qt6-qtwayland-devel \ - kf6-qqc2-desktop-style + kf6-qqc2-desktop-style \ + polkit-devel # Klonla ve derle git clone https://github.com/Project-Ro-ASD/ro-Control.git @@ -87,32 +109,27 @@ sudo make install ``` ro-Control/ -├── .github/ # CI, issue template'leri, PR şablonu ├── src/ │ ├── backend/ # C++ iş mantığı │ │ ├── nvidia/ # Sürücü tespiti, kurulum, güncelleme │ │ ├── monitor/ # GPU/CPU/RAM istatistikleri │ │ └── system/ # Polkit, DNF, komut çalıştırıcı │ ├── qml/ # Qt Quick arayüzü -│ │ ├── assets/ # Gömülü logo ve arayüz varlıkları -│ │ ├── components/ # Tekrar kullanılabilir UI bileşenleri + qmldir -│ │ └── pages/ # Ana uygulama sayfaları + qmldir +│ │ ├── pages/ # Ana uygulama sayfaları +│ │ └── components/ # Tekrar kullanılabilir UI bileşenleri │ └── main.cpp -├── data/ # İkonlar, desktop dosyası, PolicyKit, AppStream -├── docs/ # Mimari, derleme, tasarım, sürüm dokümanları -├── i18n/ # Qt Linguist çeviri kaynakları (.ts) -├── packaging/rpm/ # Fedora RPM spec +├── data/ # İkonlar, .desktop, PolicyKit, AppStream +├── packaging/rpm/ # RPM paketleme +├── docs/ # Mimari ve derleme dökümanları ├── tests/ # Birim testleri -├── CMakeLists.txt -├── SECURITY.md -└── SUPPORT.md +└── CMakeLists.txt ``` ## Katkıda Bulunma Katkılarınızı bekliyoruz! Pull request göndermeden önce lütfen [CONTRIBUTING.md](CONTRIBUTING.md) dosyasını okuyun. -Surum akis detaylari icin [docs/RELEASE.md](docs/RELEASE.md) dosyasina bakin. -Yerellestirme altyapisi icin [i18n/README.md](i18n/README.md) dosyasini inceleyin. +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. Hızlı katkı akışı: @@ -129,7 +146,6 @@ git push origin feature/ozellik-adi | Bileşen | Minimum Versiyon | |---------|-----------------| -| Fedora | 40+ | | Qt | 6.6+ | | CMake | 3.22+ | | GCC | 13+ (C++20) | diff --git a/data/completions/_ro-control b/data/completions/_ro-control new file mode 100644 index 0000000..c072b88 --- /dev/null +++ b/data/completions/_ro-control @@ -0,0 +1,58 @@ +#compdef ro-control + +local -a commands +commands=( + 'help:Show usage information' + 'version:Show application version' + 'status:Show concise system and driver status' + 'diagnostics:Show full diagnostics snapshot' + 'driver:Manage NVIDIA drivers' +) + +local -a driver_commands +driver_commands=( + 'install:Install the NVIDIA driver' + 'remove:Remove installed NVIDIA packages' + 'update:Update the installed NVIDIA driver' + 'deep-clean:Remove legacy NVIDIA leftovers' +) + +local -a global_opts +global_opts=( + '--help[Show usage information]' + '--version[Show application version]' + '--diagnostics[Legacy alias for diagnostics]' + '--json[Render status or diagnostics output as JSON]' +) + +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]' +) + +if (( CURRENT == 2 )); then + _describe 'command' commands + _describe 'option' global_opts + return +fi + +case "${words[2]}" in + status|diagnostics) + _describe 'option' '--json[Render output as JSON]' + ;; + driver) + if (( CURRENT == 3 )); then + _describe 'driver-command' driver_commands + return + fi + + if [[ "${words[3]}" == "install" ]]; then + _describe 'install-option' install_opts + fi + ;; + *) + _describe 'option' global_opts + ;; +esac diff --git a/data/completions/ro-control.bash b/data/completions/ro-control.bash new file mode 100644 index 0000000..3ebd44f --- /dev/null +++ b/data/completions/ro-control.bash @@ -0,0 +1,37 @@ +_ro_control() +{ + local cur prev words cword + _init_completion || return + + local commands="help version status diagnostics driver" + local driver_commands="install remove update deep-clean" + local global_opts="--help --version --diagnostics --json" + local install_opts="--proprietary --open-source --accept-license" + + if [[ ${cword} -eq 1 ]]; then + COMPREPLY=( $(compgen -W "${commands} ${global_opts}" -- "${cur}") ) + return + fi + + case "${words[1]}" in + status|diagnostics) + COMPREPLY=( $(compgen -W "--json" -- "${cur}") ) + return + ;; + driver) + if [[ ${cword} -eq 2 ]]; then + COMPREPLY=( $(compgen -W "${driver_commands}" -- "${cur}") ) + return + fi + + if [[ ${words[2]} == "install" ]]; then + COMPREPLY=( $(compgen -W "${install_opts}" -- "${cur}") ) + return + fi + ;; + esac + + COMPREPLY=( $(compgen -W "${global_opts}" -- "${cur}") ) +} + +complete -F _ro_control ro-control diff --git a/data/completions/ro-control.fish b/data/completions/ro-control.fish new file mode 100644 index 0000000..8b69ee9 --- /dev/null +++ b/data/completions/ro-control.fish @@ -0,0 +1,23 @@ +complete -c ro-control -f + +complete -c ro-control -n "__fish_use_subcommand" -a help -d "Show usage information" +complete -c ro-control -n "__fish_use_subcommand" -a version -d "Show application version" +complete -c ro-control -n "__fish_use_subcommand" -a status -d "Show concise system and driver status" +complete -c ro-control -n "__fish_use_subcommand" -a diagnostics -d "Show full diagnostics snapshot" +complete -c ro-control -n "__fish_use_subcommand" -a driver -d "Manage NVIDIA drivers" + +complete -c ro-control -l help -d "Show usage information" +complete -c ro-control -s h -d "Show usage information" +complete -c ro-control -l version -d "Show application version" +complete -c ro-control -s v -d "Show application version" +complete -c ro-control -l diagnostics -s d -d "Legacy alias for diagnostics" +complete -c ro-control -n "__fish_seen_subcommand_from status diagnostics" -l json -d "Render output as JSON" + +complete -c ro-control -n "__fish_seen_subcommand_from driver; and not __fish_seen_subcommand_from install remove update deep-clean" -a install -d "Install the NVIDIA driver" +complete -c ro-control -n "__fish_seen_subcommand_from driver; and not __fish_seen_subcommand_from install remove update deep-clean" -a remove -d "Remove installed NVIDIA packages" +complete -c ro-control -n "__fish_seen_subcommand_from driver; and not __fish_seen_subcommand_from install remove update deep-clean" -a update -d "Update the installed NVIDIA driver" +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" diff --git a/data/helpers/ro-control-helper.sh.in b/data/helpers/ro-control-helper.sh.in new file mode 100644 index 0000000..0edcadf --- /dev/null +++ b/data/helpers/ro-control-helper.sh.in @@ -0,0 +1,37 @@ +#!/bin/sh + +set -eu + +if [ "$#" -lt 1 ]; then + echo "ro-control-helper: missing command" >&2 + exit 64 +fi + +command_name="$1" +shift + +case "$command_name" in + dnf|/usr/bin/dnf) + resolved_path="$(command -v dnf 2>/dev/null || true)" + ;; + akmods|/usr/sbin/akmods|/usr/bin/akmods) + resolved_path="$(command -v akmods 2>/dev/null || true)" + ;; + dracut|/usr/bin/dracut) + resolved_path="$(command -v dracut 2>/dev/null || true)" + ;; + grubby|/usr/sbin/grubby|/usr/bin/grubby) + resolved_path="$(command -v grubby 2>/dev/null || true)" + ;; + *) + echo "ro-control-helper: command not allowed: ${command_name}" >&2 + exit 64 + ;; +esac + +if [ -z "${resolved_path}" ] || [ ! -x "${resolved_path}" ]; then + echo "ro-control-helper: command not found: ${command_name}" >&2 + exit 127 +fi + +exec "${resolved_path}" "$@" diff --git a/data/icons/hicolor/scalable/apps/ro-control-dial-core.svg b/data/icons/hicolor/scalable/apps/ro-control-dial-core.svg new file mode 100644 index 0000000..dd5e7ee --- /dev/null +++ b/data/icons/hicolor/scalable/apps/ro-control-dial-core.svg @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/icons/hicolor/scalable/apps/ro-control-shield-chip.svg b/data/icons/hicolor/scalable/apps/ro-control-shield-chip.svg new file mode 100644 index 0000000..17b40e8 --- /dev/null +++ b/data/icons/hicolor/scalable/apps/ro-control-shield-chip.svg @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/icons/hicolor/scalable/apps/ro-control.svg b/data/icons/hicolor/scalable/apps/ro-control.svg index 57d86e4..0199ee2 100644 --- a/data/icons/hicolor/scalable/apps/ro-control.svg +++ b/data/icons/hicolor/scalable/apps/ro-control.svg @@ -1,133 +1,44 @@ - + - - - - + + + - - - - - - - - - - - - - - - - - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - + + + + + - - + + + + - - - - - - - - - - - + + + + - - - - - - - + + + + - - - - - - - + + - - - - - - - - - - - - - - - - - + + + + diff --git a/data/icons/ro-control.metainfo.xml b/data/icons/ro-control.metainfo.xml index 3d3e98e..57e04ee 100644 --- a/data/icons/ro-control.metainfo.xml +++ b/data/icons/ro-control.metainfo.xml @@ -2,16 +2,25 @@ ro-control.desktop ro-Control - NVIDIA driver manager and system monitor for Fedora + NVIDIA driver manager and system monitor + NVIDIA sürücü yöneticisi ve sistem monitörü CC0-1.0 GPL-3.0-or-later +

- ro-Control helps Fedora users detect NVIDIA GPUs, install or update + ro-Control helps users detect NVIDIA GPUs, install or update drivers via DNF, and monitor system metrics from a Qt desktop UI.

+ +

+ 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. +

+
System @@ -19,6 +28,10 @@ HardwareSettings + + + + https://raw.githubusercontent.com/Project-Ro-ASD/ro-Control/main/docs/screenshots/monitor-overview.svg @@ -30,9 +43,10 @@ ro-control - - Acik Kaynak Gelistirme Toplulugu + + Project Ro ASD https://github.com/Project-Ro-ASD/ro-Control + https://github.com/Project-Ro-ASD/ro-Control/issues diff --git a/data/polkit/com.github.AcikKaynakGelistirmeToplulugu.rocontrol.policy b/data/polkit/com.github.AcikKaynakGelistirmeToplulugu.rocontrol.policy deleted file mode 100644 index c02d19c..0000000 --- a/data/polkit/com.github.AcikKaynakGelistirmeToplulugu.rocontrol.policy +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - ro-Control - https://github.com/Project-Ro-ASD/ro-Control - - - Manage NVIDIA drivers - NVIDIA sürücülerini yönet - Authentication is required to manage NVIDIA drivers - NVIDIA sürücülerini yönetmek için kimlik doğrulama gerekli - ro-control - - auth_admin - auth_admin - auth_admin_keep - - - - - Execute privileged ro-Control operations - Ayricalikli ro-Control islemlerini calistir - Authentication is required to perform privileged system operations - Ayricalikli sistem islemleri icin kimlik dogrulama gerekli - ro-control - - auth_admin - auth_admin - auth_admin_keep - - true - - - diff --git a/data/polkit/io.github.ProjectRoASD.rocontrol.policy.in b/data/polkit/io.github.ProjectRoASD.rocontrol.policy.in new file mode 100644 index 0000000..ad5c52d --- /dev/null +++ b/data/polkit/io.github.ProjectRoASD.rocontrol.policy.in @@ -0,0 +1,26 @@ + + + + + + Project Ro ASD + https://github.com/Project-Ro-ASD/ro-Control + + + Manage NVIDIA drivers with ro-Control + ro-Control ile NVIDIA sürücülerini yönet + Authentication is required to perform privileged ro-Control operations + Ayrıcalıklı ro-Control işlemleri için kimlik doğrulama gerekli + ro-control + + auth_admin + auth_admin + auth_admin_keep + + @RO_CONTROL_HELPER_INSTALL_PATH@ + true + + + diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 2233ab0..9e56940 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -108,7 +108,7 @@ Driver operations (install, remove, GRUB edit) require root. We use **PolicyKit User clicks "Install Driver" │ ▼ -C++ calls pkexec with a PolicyKit action ID +C++ calls `pkexec` on the dedicated `ro-control-helper` │ ▼ System shows a Plasma authentication dialog @@ -120,7 +120,7 @@ Privileged operation runs as root Result emitted back to QML via signal ``` -The PolicyKit action definition lives in `data/polkit/`. +The PolicyKit action definition and helper entrypoint live in `data/polkit/` and `data/helpers/`. --- diff --git a/docs/BUILDING.md b/docs/BUILDING.md index 7bf6f62..abc8a54 100644 --- a/docs/BUILDING.md +++ b/docs/BUILDING.md @@ -1,6 +1,6 @@ # Building ro-Control from Source -This guide covers building ro-Control on Fedora Linux. Other distributions may work but are not officially supported. +This guide covers building ro-Control from source on Linux systems with Qt 6 and CMake. --- @@ -8,10 +8,10 @@ This guide covers building ro-Control on Fedora Linux. Other distributions may w | Component | Minimum Version | Check | |-----------|----------------|-------| -| Fedora | 40+ | `cat /etc/fedora-release` | | GCC | 13+ | `gcc --version` | | CMake | 3.22+ | `cmake --version` | | Qt | 6.6+ | `rpm -q qt6-qtbase-devel` | +| Qt Linguist Tools | Required for release-grade builds | `rpm -q qt6-qttools-devel` | | Ninja | Any | `ninja --version` (optional, faster builds) | --- @@ -26,6 +26,7 @@ sudo dnf install \ ninja-build \ qt6-qtbase-devel \ qt6-qtdeclarative-devel \ + qt6-qttools-devel \ qt6-qtwayland-devel \ kf6-qqc2-desktop-style \ polkit-devel @@ -68,6 +69,13 @@ cmake .. -GNinja -DCMAKE_BUILD_TYPE=Debug ninja ``` +### Refresh translations (recommended before release) + +```bash +lupdate src -ts i18n/ro-control_en.ts i18n/ro-control_tr.ts +cmake --build build +``` + --- ## Run @@ -77,8 +85,26 @@ ninja ./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 +``` + > **Note:** Driver install/remove operations require PolicyKit authentication. The UI will prompt you automatically. +After `cmake --install`, the CLI integration also installs: + +- `man ro-control` +- Bash completion: `share/bash-completion/completions/ro-control` +- Zsh completion: `share/zsh/site-functions/_ro-control` +- Fish completion: `share/fish/vendor_completions.d/ro-control.fish` + --- ## Install System-Wide @@ -90,9 +116,11 @@ sudo make install 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/hicolor/` -- PolicyKit policy → `/usr/share/polkit-1/actions/` +- Icons → `/usr/local/share/icons/` +- AppStream metadata → `/usr/local/share/metainfo/` +- PolicyKit policy → `/usr/local/share/polkit-1/actions/` --- @@ -109,8 +137,8 @@ ctest --output-on-failure ## Uninstall -`make uninstall` target'i su an projede tanimli degil. -Sistemden kaldirmak icin paket yoneticisini veya install manifest'i kullanin. +`make uninstall` is not currently defined in this project. +Use your package manager or the install manifest to remove a local install. --- @@ -134,6 +162,14 @@ sudo dnf install gcc-c++ gcc --version # Should be 13+ ``` +**Translations do not update** +```bash +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 +``` + --- ## Contributing diff --git a/docs/RELEASE.md b/docs/RELEASE.md index ff1095b..bb29e41 100644 --- a/docs/RELEASE.md +++ b/docs/RELEASE.md @@ -20,12 +20,15 @@ Use this checklist for every production release. - [ ] Update version in `CMakeLists.txt` if needed. - [ ] Update `CHANGELOG.md` with final release notes. - [ ] Ensure AppStream metadata is up to date. +- [ ] Refresh translation sources and verify `.ts` files are complete. +- [ ] Smoke-test the app in English and Turkish locales. ## 4. Packaging - [ ] `packaging/rpm/ro-control.spec` release/version fields are correct. - [ ] Build RPM artifacts successfully. -- [ ] Verify installation and launch on Fedora KDE Plasma. +- [ ] Verify installation and launch on the target desktop environment. +- [ ] Verify `man ro-control` and shell completions install correctly. ## 5. Tag and Publish diff --git a/docs/man/ro-control.1 b/docs/man/ro-control.1 new file mode 100644 index 0000000..fae4acc --- /dev/null +++ b/docs/man/ro-control.1 @@ -0,0 +1,105 @@ +.TH RO-CONTROL 1 "March 2026" "ro-control 0.1.0" "User Commands" +.SH NAME +ro-control \- NVIDIA driver management and diagnostics CLI +.SH SYNOPSIS +.B ro-control +[\fICOMMAND\fR] [\fIOPTIONS\fR] +.SH DESCRIPTION +.B ro-control +provides a desktop application and a command-line interface for inspecting +system status, collecting diagnostics, and managing NVIDIA driver operations. +When no command is provided, the graphical interface is launched. +.SH COMMANDS +.TP +.B help +Print usage information. +.TP +.B version +Print the application version. +.TP +.B status +Print a concise system and driver status summary. +.TP +.B diagnostics +Print a full diagnostics snapshot. +.TP +.BI "driver install " [ "--proprietary" " | " "--open-source" ] " [--accept-license]" +Install the NVIDIA driver stack. The proprietary path is the default. +.TP +.B driver remove +Remove installed NVIDIA packages. +.TP +.B driver update +Update the installed NVIDIA driver. +.TP +.B driver deep-clean +Remove legacy NVIDIA leftovers. +.SH OPTIONS +.TP +.B \-h, \-\-help +Show help text and exit. +.TP +.B \-v, \-\-version +Show the application version and exit. +.TP +.B \-d, \-\-diagnostics +Legacy alias for +.BR diagnostics . +.TP +.B \-\-json +Render +.BR status +or +.BR diagnostics +output as JSON. +.TP +.B \-\-proprietary +Use the proprietary NVIDIA driver install path. +.TP +.B \-\-open-source +Use the open-source Nouveau install path. +.TP +.B \-\-accept-license +Confirm that the NVIDIA proprietary license has been reviewed. +.SH EXIT STATUS +.TP +.B 0 +Command completed successfully. +.TP +.B 1 +The requested driver management operation failed. +.TP +.B 2 +Invalid command line usage or unsupported option combination. +.SH EXAMPLES +.TP +.B ro-control status +Print a concise text summary. +.TP +.B ro-control diagnostics --json +Emit a machine-readable diagnostics snapshot. +.TP +.B ro-control driver install --proprietary --accept-license +Install the proprietary driver stack with explicit license confirmation. +.TP +.B ro-control driver update +Update the installed NVIDIA driver. +.SH FILES +.TP +.I /usr/bin/ro-control +Installed CLI entry point. +.TP +.I /usr/share/man/man1/ro-control.1.gz +Manual page. +.TP +.I /usr/share/bash-completion/completions/ro-control +Bash completion definition. +.TP +.I /usr/share/zsh/site-functions/_ro-control +Zsh completion definition. +.TP +.I /usr/share/fish/vendor_completions.d/ro-control.fish +Fish completion definition. +.SH SEE ALSO +.BR pkexec (1), +.BR dnf (8) diff --git a/i18n/README.md b/i18n/README.md index 4e4f1c0..85068c2 100644 --- a/i18n/README.md +++ b/i18n/README.md @@ -1,14 +1,51 @@ # i18n -This directory contains Qt Linguist translation source files (`.ts`). +This directory contains the Qt Linguist translation sources for ro-Control. -Current files: -- `ro-control_en.ts` -- `ro-control_tr.ts` +## Current locales -To update translations: -1. Ensure `Qt6::LinguistTools` is installed. -2. Run CMake configure/build. -3. Use Qt Linguist tools (`lupdate`, `linguist`, `lrelease`) in your workflow. +- `ro-control_en.ts` - English source catalog kept in sync with extracted strings +- `ro-control_tr.ts` - Turkish translation catalog -Note: Translation coverage is currently partial and will be expanded incrementally. +English is the source language used directly in code. Additional locales should be +added as new `ro-control_.ts` files. + +## Runtime model + +- QML strings use `qsTr(...)` +- C++ strings use `tr(...)` +- `main.cpp` loads the best matching `.qm` file from the embedded `/i18n` resource +- If no locale-specific translation is found, the app falls back to English + +## Updating translations + +1. Ensure Qt Linguist tools are available on the system. +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. + +Example workflow: + +```bash +mkdir -p build +cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug +lupdate src -ts i18n/ro-control_en.ts i18n/ro-control_tr.ts +linguist i18n/ro-control_tr.ts +cmake --build build +``` + +## Adding a new language + +1. Copy `i18n/ro-control_en.ts` to `i18n/ro-control_.ts` +2. Set the `language` attribute in the new TS file +3. Add the file to `TS_FILES` in `CMakeLists.txt` +4. Translate all entries and verify the UI on a narrow window size +5. Update AppStream metadata and screenshots if the change affects store-facing text + +## Translation quality rules + +- Keep product names, package names, and command names untranslated +- Prefer concise labels because the UI targets 980x640 and larger windows +- Do not translate technical strings blindly when they match package names +- Check both light and dark themes before submitting diff --git a/i18n/ro-control_en.ts b/i18n/ro-control_en.ts index 051f213..ebcc060 100644 --- a/i18n/ro-control_en.ts +++ b/i18n/ro-control_en.ts @@ -1,823 +1,117 @@ - DriverPage - - - NVIDIA Driver Workspace - - - - - Manage detection, installation, updates and version pinning from one place. The flow is tuned for Fedora systems that only need NVIDIA driver stack handling. - - - - - GPU State - - - - - Not Detected - - - - - Active driver: %1 - - - - - Installed Version - - - - - None - None - - - - - Latest repo version: %1 - - - - - - - Unknown - Unknown - - - - Session - - - - - Secure Boot: %1 - - - - - Enabled - Enabled - - - - Disabled - Disabled - - - - Secure Boot state could not be detected - - - - - Install & Recovery - - - - - Wayland is active. The workflow will also enforce nvidia-drm.modeset=1 when required. - - - - - X11 is active. The workflow verifies X11 driver components together with the core NVIDIA stack. - - - - - I accept the license/agreement terms - I accept the license/agreement terms - - - - Install Proprietary - - - - - Install Nouveau - - - - - Nouveau driver installation started... - Nouveau driver installation started... - - - - Deep Clean - Deep Clean - - - - Rescan - Rescan - - - - Rescanning system... - Rescanning system... - - - - Rescan completed. - Rescan completed. - - - - Updates & Version Pinning - - - - - A newer repository version is available. You can apply the latest package set or lock the system to a specific version. - - - - - The installed version is current, or no newer repository version has been found yet. - - - - - Check for Updates - Check for Updates - - - - Update check requested... - Update check requested... - - - - Apply Latest - - - - - Available Versions - - - - - Refresh Versions - Refresh Versions - - - - Refreshing repository version list... - Refreshing repository version list... - - - - Apply Selected - - - - - Applying selected version: %1 - - - - - Environment Verification - - - - - Use this report to confirm that Fedora session state, Secure Boot information and package checks are aligned before changing drivers. - - - - - Operation Log - - - - - Running - - - - - Idle - - - - - Driver actions and backend progress messages will appear here. - - + 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: Main - - - Driver Hub - - - - - System Monitor - - - - - Settings - Settings - - - - Install, switch and verify NVIDIA driver states without leaving the app. - - - - - Track CPU, RAM and NVIDIA telemetry with a compact live dashboard. - - - - - Adjust the app appearance and inspect product metadata. - - - - - About ro-Control - - - - - A focused Fedora control surface for NVIDIA driver lifecycle and live telemetry. - - - - - App Version %1 - - - - - Fedora x86_64 NVIDIA Flow - - - - - Version Overview - - - - - Installed driver: %1 - - - - - Not installed - - - - - Latest repo version: %1 - - - - - Unknown - Unknown - - - - Available repo versions: %1 - - - - - Check Versions - - - - - Open GitHub - - - - - Release Notes - - - - - Selectable NVIDIA versions were added alongside a direct update-to-latest flow. - - - - - Long-running driver operations now execute asynchronously so the interface stays responsive. - - - - - Detection, monitoring and package-sync logic were hardened for Fedora NVIDIA systems. - - - - - Light - - - - - Dark - - - - - About - About - + ro-Controlro-Control + Theme: System (Dark)Theme: System (Dark) + Theme: System (Light)Theme: System (Light) + DriverDriver + MonitorMonitor + SettingsSettings MonitorPage - - - %1 / %2 MiB (%3%) - - - - - Live System Telemetry - - - - - A compact overview of CPU, RAM and NVIDIA runtime state. Refresh manually at any time without leaving the dashboard. - - - - - CPU Load - - - - - - - %1% - - - - - - - Unavailable - - - - - - - Temperature: %1 C - - - - - Temperature sensor not available - - - - - GPU Load - - - - - - VRAM: %1 - - - - - nvidia-smi output could not be read - - - - - RAM Usage - - - - - Memory counters are unavailable - - - - - CPU - - - - - Usage: %1% - - - - - CPU data unavailable - CPU data unavailable - - - - No CPU temperature sensor value - - - - - NVIDIA GPU - NVIDIA GPU - - - - Detected NVIDIA GPU - - - - - NVIDIA GPU data unavailable - - - - - Load: %1% - - - - - RAM - - - - - Usage: %1 - - - - - RAM data unavailable - RAM data unavailable - - - - Update interval: %1 ms - - - - - Refresh Telemetry - - + 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: - SettingsPage - - - Application Settings - - - - - Switch between light and dark presentation, inspect app metadata and jump to the repository from a single settings surface. - - - - - Appearance - - - - - Choose the application theme. The palette keeps the orange-blue gradient language in both modes. - - - - - Light Theme - - - - - Dark Theme - - - - - Active mode: %1 - - - - - Dark - - - - - Light - - - - - About & Links - - - - - Open the about panel for release notes and version details, or jump directly to the GitHub repository. - - - - - Application: %1 - - - - - Version: %1 - - - - - Open About - - - - - GitHub Repository - - + NvidiaDetector + Proprietary (NVIDIA)Proprietary (NVIDIA) + Open Source (Nouveau)Open Source (Nouveau) + Not Installed / UnknownNot Installed / Unknown + GPU: %1 +Driver Version: %2 +Secure Boot: %3 +Session: %4 +NVIDIA Module: %5 +Nouveau: %6GPU: %1 +Driver Version: %2 +Secure Boot: %3 +Session: %4 +NVIDIA Module: %5 +Nouveau: %6 + UnknownUnknown + LoadedLoaded + Not loadedNot loaded + ActiveActive + InactiveInactive + + + 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: - SidebarMenu - - - Fedora NVIDIA center - - - - - A compact control surface for driver operations, telemetry and environment checks. - - - - - Navigation - - - - - Build - - - - - Theme: %1 - - - - - Dark - - - - - Light - - + 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. - ro-control - - Driver Management - Driver Management - - - System Monitoring - System Monitoring - - - Driver - Driver - - - Monitor - Monitor - - - Settings - Settings - - - Theme: System (Dark) - Theme: System (Dark) - - - Theme: System (Light) - Theme: System (Light) - - - GPU: - GPU: - - - Not detected - Not detected - - - Active driver: - Active driver: - - - Driver version: - Driver version: - - - None - None - - - Secure Boot: - Secure Boot: - - - Enabled - Enabled - - - Disabled - Disabled - - - Unknown - Unknown - - - Session type: - Session type: - - - For Wayland, the nvidia-drm.modeset=1 parameter is applied automatically. - For Wayland, the nvidia-drm.modeset=1 parameter is applied automatically. - - - For X11, the xorg-x11-drv-nvidia package is checked and installed. - For X11, the xorg-x11-drv-nvidia package is checked and installed. - - - I accept the license/agreement terms - I accept the license/agreement terms - - - Install Proprietary Driver - Install Proprietary Driver - - - Install Nouveau Driver - Install Nouveau Driver - - - Deep Clean - Deep Clean - - - Check for Updates - Check for Updates - - - Update check requested... - Update check requested... - - - Apply Latest Update - Apply Latest Update - - - New version: - New version: - - - Apply Specific Version - Apply Specific Version - - - Repository versions were listed. The selected version can be installed or synced. - Repository versions were listed. The selected version can be installed or synced. - - - Repository version list is not loaded yet or no version was found. - Repository version list is not loaded yet or no version was found. - - - Refresh Versions - Refresh Versions - - - Refreshing repository version list... - Refreshing repository version list... - - - Apply Selected Version - Apply Selected Version - - - Applying selected version: - Applying selected version: - - - Rescan - Rescan - - - Rescanning system... - Rescanning system... - - - Rescan completed. - Rescan completed. - - - Installed NVIDIA version: - Installed NVIDIA version: - - - Nouveau driver installation started... - Nouveau driver installation started... - - - Usage: - Usage: - - - CPU data unavailable - CPU data unavailable - - - Temperature: - Temperature: - - - NVIDIA GPU - NVIDIA GPU - - - Could not read data via nvidia-smi - Could not read data via nvidia-smi - - - Load: - Load: - - - VRAM: - VRAM: - - - RAM data unavailable - RAM data unavailable - - - Refresh - Refresh - - - Update interval: - Update interval: - - - About - About - - - Application: - Application: - - - Theme: - Theme: - - - System Dark - System Dark - - - System Light - System Light - + SettingsPage + SettingsSettings + AboutAbout + Application: Application: + Theme: Theme: + System DarkSystem Dark + System LightSystem Light diff --git a/i18n/ro-control_tr.ts b/i18n/ro-control_tr.ts index e93b833..b25da92 100644 --- a/i18n/ro-control_tr.ts +++ b/i18n/ro-control_tr.ts @@ -1,823 +1,117 @@ - DriverPage - - - NVIDIA Driver Workspace - - - - - Manage detection, installation, updates and version pinning from one place. The flow is tuned for Fedora systems that only need NVIDIA driver stack handling. - - - - - GPU State - - - - - Not Detected - - - - - Active driver: %1 - - - - - Installed Version - - - - - None - Yok - - - - - Latest repo version: %1 - - - - - - - Unknown - Bilinmiyor - - - - Session - - - - - Secure Boot: %1 - - - - - Enabled - Acik - - - - Disabled - Kapali - - - - Secure Boot state could not be detected - - - - - Install & Recovery - - - - - Wayland is active. The workflow will also enforce nvidia-drm.modeset=1 when required. - - - - - X11 is active. The workflow verifies X11 driver components together with the core NVIDIA stack. - - - - - I accept the license/agreement terms - Lisans/sozlesme kosullarini kabul ediyorum - - - - Install Proprietary - - - - - Install Nouveau - - - - - Nouveau driver installation started... - Nouveau surucusu kurulumu baslatildi... - - - - Deep Clean - Deep Clean - - - - Rescan - Yeniden Tara - - - - Rescanning system... - Sistem yeniden taraniyor... - - - - Rescan completed. - Yeniden tarama tamamlandi. - - - - Updates & Version Pinning - - - - - A newer repository version is available. You can apply the latest package set or lock the system to a specific version. - - - - - The installed version is current, or no newer repository version has been found yet. - - - - - Check for Updates - Guncelleme Kontrol Et - - - - Update check requested... - Guncelleme kontrolu istendi... - - - - Apply Latest - - - - - Available Versions - - - - - Refresh Versions - Surumleri Yenile - - - - Refreshing repository version list... - Repo surum listesi yenileniyor... - - - - Apply Selected - - - - - Applying selected version: %1 - - - - - Environment Verification - - - - - Use this report to confirm that Fedora session state, Secure Boot information and package checks are aligned before changing drivers. - - - - - Operation Log - - - - - Running - - - - - Idle - - - - - Driver actions and backend progress messages will appear here. - - + 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ü: Main - - - Driver Hub - - - - - System Monitor - - - - - Settings - Ayarlar - - - - Install, switch and verify NVIDIA driver states without leaving the app. - - - - - Track CPU, RAM and NVIDIA telemetry with a compact live dashboard. - - - - - Adjust the app appearance and inspect product metadata. - - - - - About ro-Control - - - - - A focused Fedora control surface for NVIDIA driver lifecycle and live telemetry. - - - - - App Version %1 - - - - - Fedora x86_64 NVIDIA Flow - - - - - Version Overview - - - - - Installed driver: %1 - - - - - Not installed - - - - - Latest repo version: %1 - - - - - Unknown - Bilinmiyor - - - - Available repo versions: %1 - - - - - Check Versions - - - - - Open GitHub - - - - - Release Notes - - - - - Selectable NVIDIA versions were added alongside a direct update-to-latest flow. - - - - - Long-running driver operations now execute asynchronously so the interface stays responsive. - - - - - Detection, monitoring and package-sync logic were hardened for Fedora NVIDIA systems. - - - - - Light - - - - - Dark - - - - - About - Hakkinda - + ro-Controlro-Control + Theme: System (Dark)Tema: Sistem (Koyu) + Theme: System (Light)Tema: Sistem (Açık) + DriverSürücü + Monitorİzleme + SettingsAyarlar MonitorPage - - - %1 / %2 MiB (%3%) - - - - - Live System Telemetry - - - - - A compact overview of CPU, RAM and NVIDIA runtime state. Refresh manually at any time without leaving the dashboard. - - - - - CPU Load - - - - - - - %1% - - - - - - - Unavailable - - - - - - - Temperature: %1 C - - - - - Temperature sensor not available - - - - - GPU Load - - - - - - VRAM: %1 - - - - - nvidia-smi output could not be read - - - - - RAM Usage - - - - - Memory counters are unavailable - - - - - CPU - - - - - Usage: %1% - - - - - CPU data unavailable - CPU verisi alinamiyor - - - - No CPU temperature sensor value - - - - - NVIDIA GPU - NVIDIA GPU - - - - Detected NVIDIA GPU - - - - - NVIDIA GPU data unavailable - - - - - Load: %1% - - - - - RAM - - - - - Usage: %1 - - - - - RAM data unavailable - RAM verisi alinamiyor - - - - Update interval: %1 ms - - - - - Refresh Telemetry - - + 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ığı: - SettingsPage - - - Application Settings - - - - - Switch between light and dark presentation, inspect app metadata and jump to the repository from a single settings surface. - - - - - Appearance - - - - - Choose the application theme. The palette keeps the orange-blue gradient language in both modes. - - - - - Light Theme - - - - - Dark Theme - - - - - Active mode: %1 - - - - - Dark - - - - - Light - - - - - About & Links - - - - - Open the about panel for release notes and version details, or jump directly to the GitHub repository. - - - - - Application: %1 - - - - - Version: %1 - - - - - Open About - - - - - GitHub Repository - - + NvidiaDetector + Proprietary (NVIDIA)Kapalı Kaynak (NVIDIA) + Open Source (Nouveau)Açık Kaynak (Nouveau) + Not Installed / UnknownKurulu Değil / Bilinmiyor + GPU: %1 +Driver Version: %2 +Secure Boot: %3 +Session: %4 +NVIDIA Module: %5 +Nouveau: %6GPU: %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 + + + 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ı: - SidebarMenu - - - Fedora NVIDIA center - - - - - A compact control surface for driver operations, telemetry and environment checks. - - - - - Navigation - - - - - Build - - - - - Theme: %1 - - - - - Dark - - - - - Light - - + 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. - ro-control - - Driver Management - Surucu Yonetimi - - - System Monitoring - Sistem Izleme - - - Driver - Surucu - - - Monitor - Izleme - - - Settings - Ayarlar - - - Theme: System (Dark) - Tema: Sistem (Koyu) - - - Theme: System (Light) - Tema: Sistem (Acik) - - - GPU: - GPU: - - - Not detected - Tespit edilmedi - - - Active driver: - Aktif surucu: - - - Driver version: - Surucu versiyonu: - - - None - Yok - - - Secure Boot: - Secure Boot: - - - Enabled - Acik - - - Disabled - Kapali - - - Unknown - Bilinmiyor - - - Session type: - Oturum altyapisi: - - - For Wayland, the nvidia-drm.modeset=1 parameter is applied automatically. - Wayland icin nvidia-drm.modeset=1 parametresi otomatik uygulanir. - - - For X11, the xorg-x11-drv-nvidia package is checked and installed. - X11 icin xorg-x11-drv-nvidia paketi kontrol edilip kurulur. - - - I accept the license/agreement terms - Lisans/sozlesme kosullarini kabul ediyorum - - - Install Proprietary Driver - Kapali Kaynak Surucu Kur - - - Install Nouveau Driver - Nouveau Surucusu Kur - - - Deep Clean - Deep Clean - - - Check for Updates - Guncelleme Kontrol Et - - - Update check requested... - Guncelleme kontrolu istendi... - - - Apply Latest Update - Guncellemeyi Uygula - - - New version: - Yeni surum: - - - Apply Specific Version - Belirli Surum Uygula - - - Repository versions were listed. The selected version can be installed or synced. - Repo surumleri listelendi. Secilen surum kurulabilir/guncellenebilir. - - - Repository version list is not loaded yet or no version was found. - Repo surum listesi henuz yuklenmedi veya surum bulunamadi. - - - Refresh Versions - Surumleri Yenile - - - Refreshing repository version list... - Repo surum listesi yenileniyor... - - - Apply Selected Version - Secili Surumu Uygula - - - Applying selected version: - Secilen surum uygulanacak: - - - Rescan - Yeniden Tara - - - Rescanning system... - Sistem yeniden taraniyor... - - - Rescan completed. - Yeniden tarama tamamlandi. - - - Installed NVIDIA version: - Mevcut nvidia surumu: - - - Nouveau driver installation started... - Nouveau surucusu kurulumu baslatildi... - - - Usage: - Kullanim: - - - CPU data unavailable - CPU verisi alinamiyor - - - Temperature: - Sicaklik: - - - NVIDIA GPU - NVIDIA GPU - - - Could not read data via nvidia-smi - nvidia-smi ile veri alinamadi - - - Load: - Yuk: - - - VRAM: - VRAM: - - - RAM data unavailable - RAM verisi alinamiyor - - - Refresh - Yenile - - - Update interval: - Guncelleme araligi: - - - About - Hakkinda - - - Application: - Uygulama: - - - Theme: - Tema: - - - System Dark - Sistem Koyu - - - System Light - Sistem Acik - + SettingsPage + SettingsAyarlar + AboutHakkında + Application: Uygulama: + Theme: Tema: + System DarkSistem Koyu + System LightSistem Açık diff --git a/packaging/rpm/README.md b/packaging/rpm/README.md new file mode 100644 index 0000000..0cef387 --- /dev/null +++ b/packaging/rpm/README.md @@ -0,0 +1,45 @@ +# RPM Packaging + +This directory contains the RPM recipe for ro-Control. + +## Goals + +- Produce a reproducible RPM from a release tarball +- Require translation tooling so localized builds are never emitted partially +- Run the upstream Qt test suite during `%check` + +## Source archive expectations + +The spec assumes `Source0` is a `.tar.gz` archive containing a single top-level +directory. During `%prep`, the archive is unpacked with `--strip-components=1` +into a deterministic `%{name}-%{version}` build directory so release archives do +not need to preserve a specific upstream folder name. + +## Build dependencies + +- `cmake` +- `gcc-c++` +- `extra-cmake-modules` +- `qt6-qtbase-devel` +- `qt6-qtdeclarative-devel` +- `qt6-qttools-devel` +- `qt6-qtwayland-devel` +- `kf6-qqc2-desktop-style` +- `polkit-devel` + +## Local build example + +```bash +spectool -g -R packaging/rpm/ro-control.spec +rpmbuild -ba packaging/rpm/ro-control.spec +``` + +If you build from a Git checkout instead of a published source archive, create +the tarball first so `%Source0` matches the spec contract. + +The RPM installs the PolicyKit helper policy as +`io.github.ProjectRoASD.rocontrol.policy` and the privileged helper as +`/usr/libexec/ro-control-helper`. + +It also installs the CLI manual page and shell completions for Bash, Zsh, and +Fish so command discovery works out of the box on release systems. diff --git a/packaging/rpm/ro-control.spec b/packaging/rpm/ro-control.spec index 8d11326..2a552c1 100644 --- a/packaging/rpm/ro-control.spec +++ b/packaging/rpm/ro-control.spec @@ -1,7 +1,9 @@ +%global _rpmfilename %{NAME}-%{VERSION}.%{ARCH}.rpm + Name: ro-control Version: 0.1.0 -Release: 1%{?dist} -Summary: Smart NVIDIA driver manager and system monitor for Fedora +Release: 1 +Summary: Smart NVIDIA driver manager and system monitor License: GPL-3.0-or-later URL: https://github.com/Project-Ro-ASD/ro-Control @@ -14,6 +16,7 @@ BuildRequires: ninja-build BuildRequires: qt6-qtbase-devel BuildRequires: qt6-qtbase-private-devel BuildRequires: qt6-qtdeclarative-devel +BuildRequires: qt6-qttools-devel BuildRequires: qt6-qtwayland-devel BuildRequires: kf6-qqc2-desktop-style BuildRequires: polkit-devel @@ -25,29 +28,40 @@ Requires: kf6-qqc2-desktop-style Requires: polkit %description -ro-Control is a Qt6/KDE Plasma desktop application for Fedora that helps users +ro-Control is a Qt6/KDE Plasma desktop application that helps users manage NVIDIA drivers and monitor core system metrics. %prep -%autosetup -n %{name}-%{version} +%autosetup -c -T -n %{name}-%{version} +tar -xzf %{SOURCE0} --strip-components=1 %build -%cmake -DBUILD_TESTS=OFF +%cmake \ + -DBUILD_TESTS=ON \ + -DREQUIRE_TRANSLATIONS=ON %cmake_build %install %cmake_install +%check +%ctest --output-on-failure + %files %license LICENSE %doc README.md README.tr.md CHANGELOG.md %{_bindir}/ro-control %{_datadir}/applications/ro-control.desktop +%{_datadir}/man/man1/ro-control.1* %{_datadir}/metainfo/ro-control.metainfo.xml %{_datadir}/icons/hicolor/256x256/apps/ro-control.png %{_datadir}/icons/hicolor/scalable/apps/ro-control.svg -%{_datadir}/polkit-1/actions/com.github.AcikKaynakGelistirmeToplulugu.rocontrol.policy +%{_datadir}/bash-completion/completions/ro-control +%{_datadir}/zsh/site-functions/_ro-control +%{_datadir}/fish/vendor_completions.d/ro-control.fish +%{_libexecdir}/ro-control-helper +%{_datadir}/polkit-1/actions/io.github.ProjectRoASD.rocontrol.policy %changelog * Fri Mar 06 2026 ro-Control Maintainers - 0.1.0-1 -- Initial RPM packaging spec for Fedora builds +- Initial RPM packaging spec diff --git a/src/backend/nvidia/detector.cpp b/src/backend/nvidia/detector.cpp index e0bb4e4..40bbb52 100644 --- a/src/backend/nvidia/detector.cpp +++ b/src/backend/nvidia/detector.cpp @@ -12,9 +12,6 @@ NvidiaDetector::NvidiaDetector(QObject *parent) : QObject(parent) {} NvidiaDetector::GpuInfo NvidiaDetector::detect() const { GpuInfo info; - // TR: Tespit adimlari olabildigince bagimsiz tutulur; biri fail etse digeri - // devam eder. EN: Detection steps are independent so one failure does not - // block others. info.name = detectGpuName(); info.found = !info.name.isEmpty(); info.driverVersion = detectDriverVersion(); @@ -38,34 +35,26 @@ QString NvidiaDetector::installedDriverVersion() const { QString NvidiaDetector::activeDriver() const { if (m_info.driverLoaded) - return QStringLiteral("Kapali Kaynak (NVIDIA)"); + return tr("Proprietary (NVIDIA)"); if (m_info.nouveauActive) - return QStringLiteral("Nouveau (Topluluk Surucusu)"); - return QStringLiteral("Yuklu Degil/Bilinmiyor"); + return tr("Open Source (Nouveau)"); + return tr("Not Installed / Unknown"); } QString NvidiaDetector::verificationReport() const { - // TR: UI icin tek yerde ozet tanilama metni uret. - // EN: Produce a single consolidated diagnostic text for the UI. - const QString gpuText = m_info.found ? m_info.name : QStringLiteral("Yok"); - const QString versionText = m_info.driverVersion.isEmpty() - ? QStringLiteral("Yok") - : m_info.driverVersion; - - return QStringLiteral( - "GPU: %1\nSurucu Versiyonu: %2\nSecure Boot: %3\nOturum: %4\n" - "NVIDIA Modulu: %5\nNouveau: %6") + const QString gpuText = m_info.found ? m_info.name : tr("None"); + const QString versionText = + 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") .arg(gpuText, versionText, m_info.secureBootKnown - ? (m_info.secureBootEnabled ? QStringLiteral("Acik") - : QStringLiteral("Kapali")) - : QStringLiteral("Bilinmiyor"), - m_info.sessionType.isEmpty() ? QStringLiteral("Bilinmiyor") - : m_info.sessionType, - m_info.driverLoaded ? QStringLiteral("Yuklu") - : QStringLiteral("Yuklu degil"), - m_info.nouveauActive ? QStringLiteral("Aktif") - : QStringLiteral("Aktif degil")); + ? (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"), + m_info.nouveauActive ? tr("Active") : tr("Inactive")); } void NvidiaDetector::refresh() { @@ -113,8 +102,6 @@ QString NvidiaDetector::detectDriverVersion() const { if (result.success()) return result.stdout.trimmed(); - // TR: nvidia-smi yoksa modinfo ile surum fallback'i dene. - // EN: If nvidia-smi is unavailable, fall back to modinfo. const auto modinfo = runner.run(QStringLiteral("modinfo"), {QStringLiteral("nvidia")}); @@ -145,9 +132,6 @@ bool NvidiaDetector::isModuleLoaded(const QString &moduleName) const { } bool NvidiaDetector::detectSecureBoot(bool *known) const { - // TR: mokutil yoksa "kapali" degil "bilinmiyor" olarak siniflandir. - // EN: If mokutil is unavailable, classify as "unknown" rather than - // "disabled". CommandRunner runner; const auto result = runner.run(QStringLiteral("mokutil"), {QStringLiteral("--sb-state")}); diff --git a/src/backend/nvidia/installer.cpp b/src/backend/nvidia/installer.cpp index 67084cb..d575d13 100644 --- a/src/backend/nvidia/installer.cpp +++ b/src/backend/nvidia/installer.cpp @@ -2,87 +2,12 @@ #include "system/commandrunner.h" -#include -#include -#include -#include -#include #include -namespace { - -const QStringList kManagedNvidiaPackages = { - QStringLiteral("akmod-nvidia"), - 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"), -}; - -QString commandError(const CommandRunner::Result &result, - const QString &fallback) { - const QString stderrText = result.stderr.trimmed(); - const QString stdoutText = result.stdout.trimmed(); - - if (!stderrText.isEmpty()) { - return stderrText; - } - - if (!stdoutText.isEmpty()) { - return stdoutText; - } - - return fallback; -} - -bool hasExecutable(const QString &program) { - return !QStandardPaths::findExecutable(program).isEmpty(); -} - -QStringList buildLatestDriverTargets(const QString &sessionType) { - QStringList targets = kManagedNvidiaPackages; - if (sessionType != QStringLiteral("x11")) { - targets.removeAll(QStringLiteral("xorg-x11-drv-nvidia")); - } - - return targets; -} - -} // 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( - QStringLiteral("Baska bir surucu islemi zaten calisiyor.")); - 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 && @@ -96,11 +21,6 @@ void NvidiaInstaller::setProprietaryAgreement(bool required, } void NvidiaInstaller::refreshProprietaryAgreement() { - if (!hasExecutable(QStringLiteral("dnf"))) { - setProprietaryAgreement(false, QString()); - return; - } - CommandRunner runner; const auto info = runner.run(QStringLiteral("dnf"), @@ -128,11 +48,9 @@ void NvidiaInstaller::refreshProprietaryAgreement() { if (requiresAgreement) { setProprietaryAgreement( - true, QStringLiteral( - "Kapali kaynak NVIDIA surucusunu kurmadan once lisans " - "kosullarini kabul etmeniz gerekir. Tespit edilen lisans: %1") - .arg(licenseLine.isEmpty() ? QStringLiteral("Bilinmiyor") - : licenseLine)); + true, tr("You must accept the NVIDIA proprietary driver license terms " + "before installation. Detected license: %1") + .arg(licenseLine.isEmpty() ? tr("Unknown") : licenseLine)); return; } @@ -145,421 +63,146 @@ void NvidiaInstaller::installProprietary(bool agreementAccepted) { refreshProprietaryAgreement(); if (m_proprietaryAgreementRequired && !agreementAccepted) { - emit installFinished( - false, - QStringLiteral("Kurulumdan once lisans/sozlesme onayi gereklidir.")); + emit installFinished(false, + tr("License agreement acceptance is required before " + "installation.")); return; } - QPointer guard(this); - runAsyncTask([guard]() { - if (!guard) { - return; - } + CommandRunner runner; - 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); - }); - - QMetaObject::invokeMethod( - guard, - [guard]() { - if (guard) { - emit guard->progressMessage( - QStringLiteral("RPM Fusion deposu kontrol ediliyor...")); - } - }, - Qt::QueuedConnection); - - CommandRunner rpmRunner; - if (!hasExecutable(QStringLiteral("dnf")) || - !hasExecutable(QStringLiteral("rpm"))) { - QMetaObject::invokeMethod( - guard, - [guard]() { - if (guard) { - emit guard->installFinished( - false, - QStringLiteral( - "Kurulum icin gerekli sistem araclari eksik (dnf/rpm).")); - } - }, - Qt::QueuedConnection); - return; - } + connect(&runner, &CommandRunner::outputLine, this, + &NvidiaInstaller::progressMessage); - const auto fedoraResult = - rpmRunner.run(QStringLiteral("rpm"), - {QStringLiteral("-E"), QStringLiteral("%fedora")}); - const QString fedoraVersion = fedoraResult.stdout.trimmed(); - static const QRegularExpression fedoraVersionPattern( - QStringLiteral("^\\d+$")); - if (!fedoraVersionPattern.match(fedoraVersion).hasMatch()) { - QMetaObject::invokeMethod( - guard, - [guard]() { - if (guard) { - emit guard->installFinished( - false, QStringLiteral("Fedora surumu tespit edilemedi.")); - } - }, - Qt::QueuedConnection); - return; - } + emit progressMessage(tr("Checking RPM Fusion repositories...")); - 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()) { - const QString error = - QStringLiteral("RPM Fusion repo eklenemedi: ") + - commandError(result, QStringLiteral("bilinmeyen hata")); - QMetaObject::invokeMethod( - guard, - [guard, error]() { - if (guard) { - emit guard->installFinished(false, error); - } - }, - Qt::QueuedConnection); - return; - } + CommandRunner rpmRunner; + const auto fedoraResult = rpmRunner.run( + QStringLiteral("rpm"), {QStringLiteral("-E"), QStringLiteral("%fedora")}); - QMetaObject::invokeMethod( - guard, - [guard]() { - if (guard) { - emit guard->progressMessage(QStringLiteral( - "Kapali kaynak NVIDIA surucusu kuruluyor (akmod-nvidia)...")); - } - }, - Qt::QueuedConnection); - - const QString sessionType = guard->detectSessionType(); - result = runner.runAsRoot(QStringLiteral("dnf"), - QStringList{QStringLiteral("install"), - QStringLiteral("-y"), - QStringLiteral("--allowerasing")} + - buildLatestDriverTargets(sessionType)); - if (!result.success()) { - const QString error = - QStringLiteral("Kurulum basarisiz: ") + - commandError(result, QStringLiteral("bilinmeyen hata")); - QMetaObject::invokeMethod( - guard, - [guard, error]() { - if (guard) { - emit guard->installFinished(false, error); - } - }, - Qt::QueuedConnection); - return; - } + const QString fedoraVersion = fedoraResult.stdout.trimmed(); + if (fedoraVersion.isEmpty()) { + emit installFinished(false, tr("Platform version could not be detected.")); + return; + } - QMetaObject::invokeMethod( - guard, - [guard]() { - if (guard) { - emit guard->progressMessage( - QStringLiteral("Kernel modulu derleniyor (akmods --force)...")); - } - }, - Qt::QueuedConnection); - - result = - runner.runAsRoot(QStringLiteral("akmods"), {QStringLiteral("--force")}); - if (!result.success()) { - const QString error = - QStringLiteral("Kernel modulu derlenemedi: ") + - commandError(result, QStringLiteral("bilinmeyen hata")); - QMetaObject::invokeMethod( - guard, - [guard, error]() { - if (guard) { - emit guard->installFinished(false, error); - } - }, - Qt::QueuedConnection); - return; - } - QString sessionError; - if (!guard->applySessionSpecificSetup(runner, sessionType, &sessionError)) { - QMetaObject::invokeMethod( - guard, - [guard, sessionError]() { - if (guard) { - emit guard->installFinished(false, sessionError); - } - }, - Qt::QueuedConnection); - 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; + } + + emit progressMessage( + tr("Installing the proprietary NVIDIA driver (akmod-nvidia)...")); + + result = runner.runAsRoot(QStringLiteral("dnf"), + {QStringLiteral("install"), QStringLiteral("-y"), + QStringLiteral("akmod-nvidia")}); + + if (!result.success()) { + emit installFinished(false, tr("Installation failed: ") + result.stderr); + return; + } + + emit progressMessage(tr("Building the kernel module (akmods --force)...")); + runner.runAsRoot(QStringLiteral("akmods"), {QStringLiteral("--force")}); + + const QString sessionType = detectSessionType(); + QString sessionError; + if (!applySessionSpecificSetup(runner, sessionType, &sessionError)) { + emit installFinished(false, sessionError); + return; + } - QMetaObject::invokeMethod( - guard, - [guard]() { - if (guard) { - emit guard->installFinished( - true, QStringLiteral( - "Kapali kaynak NVIDIA surucusu basariyla kuruldu. " - "Lutfen sistemi yeniden baslatin.")); - } - }, - Qt::QueuedConnection); - }); + emit installFinished( + true, + tr("The proprietary NVIDIA driver was installed successfully. Please " + "restart the system.")); } void NvidiaInstaller::installOpenSource() { - QPointer guard(this); - runAsyncTask([guard]() { - if (!guard) { - return; - } + CommandRunner runner; - 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); - }); - - QMetaObject::invokeMethod( - guard, - [guard]() { - if (guard) { - emit guard->progressMessage( - QStringLiteral("Acik kaynak surucuye gecis baslatiliyor...")); - } - }, - Qt::QueuedConnection); - - auto result = runner.runAsRoot( - QStringLiteral("dnf"), - QStringList{QStringLiteral("remove"), QStringLiteral("-y")} + - kManagedNvidiaPackages); - if (!result.success()) { - const QString error = - QStringLiteral("Kapali kaynak paket kaldirma basarisiz: ") + - commandError(result, QStringLiteral("bilinmeyen hata")); - QMetaObject::invokeMethod( - guard, - [guard, error]() { - if (guard) { - emit guard->installFinished(false, error); - } - }, - Qt::QueuedConnection); - return; - } + connect(&runner, &CommandRunner::outputLine, this, + &NvidiaInstaller::progressMessage); - result = runner.runAsRoot(QStringLiteral("dnf"), - {QStringLiteral("install"), QStringLiteral("-y"), - QStringLiteral("xorg-x11-drv-nouveau"), - QStringLiteral("mesa-dri-drivers")}); - if (!result.success()) { - const QString error = - QStringLiteral("Acik kaynak surucu kurulumu basarisiz: ") + - commandError(result, QStringLiteral("bilinmeyen hata")); - QMetaObject::invokeMethod( - guard, - [guard, error]() { - if (guard) { - emit guard->installFinished(false, error); - } - }, - Qt::QueuedConnection); - return; - } + emit progressMessage(tr("Switching to the open-source driver...")); - result = - runner.runAsRoot(QStringLiteral("dracut"), {QStringLiteral("--force")}); - if (!result.success()) { - const QString error = - QStringLiteral("Initramfs guncellenemedi: ") + - commandError(result, QStringLiteral("bilinmeyen hata")); - QMetaObject::invokeMethod( - guard, - [guard, error]() { - if (guard) { - emit guard->installFinished(false, error); - } - }, - Qt::QueuedConnection); - return; - } + // Once kapali kaynak paketleri kaldir. + auto result = runner.runAsRoot( + QStringLiteral("dnf"), + {QStringLiteral("remove"), QStringLiteral("-y"), + QStringLiteral("akmod-nvidia"), QStringLiteral("xorg-x11-drv-nvidia*")}); - QMetaObject::invokeMethod( - guard, - [guard]() { - if (guard) { - emit guard->installFinished( - true, - QStringLiteral("Acik kaynak surucu (Nouveau) kuruldu. Lutfen " - "sistemi yeniden baslatin.")); - } - }, - Qt::QueuedConnection); - }); + if (!result.success()) { + emit installFinished(false, tr("Failed to remove proprietary packages: ") + + result.stderr); + 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")}); + + if (!result.success()) { + emit installFinished(false, tr("Open-source driver installation failed: ") + + result.stderr); + return; + } + + runner.runAsRoot(QStringLiteral("dracut"), {QStringLiteral("--force")}); + + emit installFinished(true, + tr("The open-source driver (Nouveau) was installed. " + "Please restart the system.")); } void NvidiaInstaller::remove() { - QPointer guard(this); - runAsyncTask([guard]() { - if (!guard) { - return; - } + CommandRunner runner; + connect(&runner, &CommandRunner::outputLine, this, + &NvidiaInstaller::progressMessage); - 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); - }); - - QMetaObject::invokeMethod( - guard, - [guard]() { - if (guard) { - emit guard->progressMessage( - QStringLiteral("NVIDIA surucusu kaldiriliyor...")); - } - }, - Qt::QueuedConnection); + emit progressMessage(tr("Removing the NVIDIA driver...")); - const auto result = runner.runAsRoot( - QStringLiteral("dnf"), - QStringList{QStringLiteral("remove"), QStringLiteral("-y")} + - kManagedNvidiaPackages); - const bool success = result.success(); - const QString message = - success ? QStringLiteral("Surucu basariyla kaldirildi.") - : QStringLiteral("Kaldirma basarisiz: ") + - commandError(result, QStringLiteral("bilinmeyen hata")); - QMetaObject::invokeMethod( - guard, - [guard, success, message]() { - if (guard) { - emit guard->removeFinished(success, message); - } - }, - Qt::QueuedConnection); - }); + const auto result = runner.runAsRoot( + QStringLiteral("dnf"), + {QStringLiteral("remove"), QStringLiteral("-y"), + QStringLiteral("akmod-nvidia"), QStringLiteral("xorg-x11-drv-nvidia*")}); + + emit removeFinished(result.success(), + result.success() + ? tr("Driver removed successfully.") + : tr("Removal failed: ") + result.stderr); } void NvidiaInstaller::deepClean() { - QPointer guard(this); - runAsyncTask([guard]() { - if (!guard) { - return; - } + CommandRunner runner; + connect(&runner, &CommandRunner::outputLine, this, + &NvidiaInstaller::progressMessage); - 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); - }); - - QMetaObject::invokeMethod( - guard, - [guard]() { - if (guard) { - emit guard->progressMessage( - QStringLiteral("Eski surucu kalintilari temizleniyor...")); - } - }, - Qt::QueuedConnection); - - const auto removeResult = runner.runAsRoot( - QStringLiteral("dnf"), - QStringList{QStringLiteral("remove"), QStringLiteral("-y")} + - kManagedNvidiaPackages); - if (!removeResult.success()) { - const QString error = - QStringLiteral("Deep clean kaldirma adimi hata verdi: ") + - commandError(removeResult, QStringLiteral("bilinmeyen hata")); - QMetaObject::invokeMethod( - guard, - [guard, error]() { - if (guard) { - emit guard->progressMessage(error); - } - }, - Qt::QueuedConnection); - } + emit progressMessage(tr("Cleaning legacy driver leftovers...")); - const auto cleanResult = - runner.runAsRoot(QStringLiteral("dnf"), - {QStringLiteral("clean"), QStringLiteral("all")}); - if (!cleanResult.success()) { - const QString error = - QStringLiteral("DNF cache temizligi hata verdi: ") + - commandError(cleanResult, QStringLiteral("bilinmeyen hata")); - QMetaObject::invokeMethod( - guard, - [guard, error]() { - if (guard) { - emit guard->progressMessage(error); - } - }, - Qt::QueuedConnection); - } + runner.runAsRoot(QStringLiteral("dnf"), + {QStringLiteral("remove"), QStringLiteral("-y"), + QStringLiteral("*nvidia*"), QStringLiteral("*akmod*")}); + + runner.runAsRoot(QStringLiteral("dnf"), + {QStringLiteral("clean"), QStringLiteral("all")}); - QMetaObject::invokeMethod( - guard, - [guard]() { - if (guard) { - emit guard->progressMessage( - QStringLiteral("Deep clean tamamlandi.")); - } - }, - Qt::QueuedConnection); - }); + emit progressMessage(tr("Deep clean completed.")); } QString NvidiaInstaller::detectSessionType() const { @@ -588,8 +231,8 @@ bool NvidiaInstaller::applySessionSpecificSetup(CommandRunner &runner, const QString &sessionType, QString *errorMessage) { if (sessionType == QStringLiteral("wayland")) { - emit progressMessage(QStringLiteral( - "Wayland tespit edildi: nvidia-drm.modeset=1 ayari uygulaniyor...")); + emit progressMessage( + QStringLiteral("Wayland detected: applying nvidia-drm.modeset=1...")); const auto result = runner.runAsRoot(QStringLiteral("grubby"), @@ -598,9 +241,8 @@ bool NvidiaInstaller::applySessionSpecificSetup(CommandRunner &runner, if (!result.success()) { if (errorMessage) { - *errorMessage = - QStringLiteral("Wayland icin kernel parametresi uygulanamadi: ") + - commandError(result, QStringLiteral("bilinmeyen hata")); + *errorMessage = tr("Failed to apply the Wayland kernel parameter: ") + + result.stderr; } return false; } @@ -609,8 +251,7 @@ bool NvidiaInstaller::applySessionSpecificSetup(CommandRunner &runner, if (sessionType == QStringLiteral("x11")) { emit progressMessage( - QStringLiteral("X11 tespit edildi: X11 NVIDIA userspace paketleri " - "kontrol ediliyor...")); + tr("X11 detected: checking NVIDIA userspace packages...")); const auto result = runner.runAsRoot( QStringLiteral("dnf"), {QStringLiteral("install"), QStringLiteral("-y"), @@ -618,8 +259,8 @@ bool NvidiaInstaller::applySessionSpecificSetup(CommandRunner &runner, if (!result.success()) { if (errorMessage) { - *errorMessage = QStringLiteral("X11 NVIDIA paketi kurulurken hata: ") + - commandError(result, QStringLiteral("bilinmeyen hata")); + *errorMessage = + tr("Failed to install the X11 NVIDIA package: ") + result.stderr; } return false; } diff --git a/src/backend/system/commandrunner.cpp b/src/backend/system/commandrunner.cpp index 208677a..924b2a1 100644 --- a/src/backend/system/commandrunner.cpp +++ b/src/backend/system/commandrunner.cpp @@ -1,6 +1,7 @@ #include "commandrunner.h" #include +#include #include #include #include @@ -145,6 +146,11 @@ CommandRunner::Result CommandRunner::runAsRoot(const QString &program, const QStringList &args, const RunOptions &options) { QStringList pkexecArgs; - pkexecArgs << program << args; + QString helperPath = QStringLiteral(RO_CONTROL_HELPER_BUILD_PATH); + if (!QFileInfo::exists(helperPath)) { + helperPath = QStringLiteral(RO_CONTROL_HELPER_INSTALL_PATH); + } + + pkexecArgs << helperPath << program << args; return run(QStringLiteral("pkexec"), pkexecArgs, options); } diff --git a/src/cli/cli.cpp b/src/cli/cli.cpp new file mode 100644 index 0000000..292a667 --- /dev/null +++ b/src/cli/cli.cpp @@ -0,0 +1,497 @@ +#include "cli.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "backend/monitor/cpumonitor.h" +#include "backend/monitor/gpumonitor.h" +#include "backend/monitor/rammonitor.h" +#include "backend/nvidia/detector.h" +#include "backend/nvidia/updater.h" + +namespace RoControlCli { + +namespace { + +QString commandActionToString(CommandAction action) { + switch (action) { + case CommandAction::PrintStatusText: + case CommandAction::PrintStatusJson: + return QStringLiteral("status"); + case CommandAction::PrintDiagnosticsText: + case CommandAction::PrintDiagnosticsJson: + return QStringLiteral("diagnostics"); + default: + return QStringLiteral("unknown"); + } +} + +QString boolText(bool value) { + return value ? QStringLiteral("yes") : QStringLiteral("no"); +} + +QString dashIfEmpty(const QString &value) { + return value.isEmpty() ? QStringLiteral("-") : value; +} + +QString buildHelpText(const QString &applicationName, + const QString &applicationVersion, + const QString &applicationDescription) { + QString help; + QTextStream stream(&help); + + stream << applicationName << ' ' << applicationVersion << '\n'; + stream << applicationDescription << "\n\n"; + stream << "Usage:\n"; + stream << " " << applicationName << " [command] [options]\n"; + stream << " " << applicationName << " --diagnostics [--json]\n"; + stream << " " << applicationName << " --version\n\n"; + stream << "Commands:\n"; + stream << " help Show this help text.\n"; + stream << " version Print the application version.\n"; + stream << " status [--json] Print a concise system and driver " + "status.\n"; + stream << " diagnostics [--json] Print a full diagnostics snapshot.\n"; + stream << " driver install [options] Install the NVIDIA driver.\n"; + stream << " driver remove Remove installed NVIDIA packages.\n"; + stream + << " driver update Update the installed NVIDIA driver.\n"; + stream << " driver deep-clean Remove legacy NVIDIA leftovers.\n\n"; + 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 << "Global options:\n"; + stream << " -h, --help Show help and exit.\n"; + stream << " -v, --version Show version and exit.\n"; + stream << " -d, --diagnostics Legacy alias for `diagnostics`.\n"; + stream << " --json Render `status` or `diagnostics` as " + "JSON.\n\n"; + stream << "Examples:\n"; + stream << " " << applicationName << " status\n"; + stream << " " << applicationName << " diagnostics --json\n"; + stream << " " << applicationName + << " driver install --proprietary --accept-license\n"; + stream << " " << applicationName << " driver update\n"; + + return help; +} + +void configureParser(QCommandLineParser &parser, const QString &applicationName, + const QString &applicationVersion, + const QString &applicationDescription) { + parser.setApplicationDescription(applicationDescription); + parser.setSingleDashWordOptionMode(QCommandLineParser::ParseAsLongOptions); + + QCoreApplication::setApplicationName(applicationName); + QCoreApplication::setApplicationVersion(applicationVersion); + + parser.addOption( + QCommandLineOption({QStringLiteral("h"), QStringLiteral("help")}, + QStringLiteral("Display CLI usage information."))); + parser.addOption( + QCommandLineOption({QStringLiteral("v"), QStringLiteral("version")}, + QStringLiteral("Display the application version."))); + parser.addOption(QCommandLineOption( + {QStringLiteral("d"), QStringLiteral("diagnostics")}, + QStringLiteral( + "Print a one-shot system and driver diagnostics snapshot."))); + parser.addOption(QCommandLineOption( + {QStringLiteral("json")}, + QStringLiteral("Render status or diagnostics output as JSON."))); + parser.addOption(QCommandLineOption( + {QStringLiteral("proprietary")}, + QStringLiteral("Use the proprietary NVIDIA driver install path."))); + parser.addOption(QCommandLineOption( + {QStringLiteral("open-source")}, + QStringLiteral("Use the open-source Nouveau install path."))); + parser.addOption(QCommandLineOption( + {QStringLiteral("accept-license")}, + QStringLiteral( + "Confirm that the proprietary NVIDIA license was reviewed."))); + parser.addPositionalArgument(QStringLiteral("command"), + QStringLiteral("CLI command to execute.")); + parser.addPositionalArgument( + QStringLiteral("subcommand"), + QStringLiteral("Optional command scope or action."), + QStringLiteral("[subcommand]")); +} + +ParsedCommand invalidCommand(const QString &message) { + ParsedCommand command; + command.action = CommandAction::Invalid; + command.payload = message; + return command; +} + +bool hasConflictingInstallModeOptions(const QCommandLineParser &parser) { + return parser.isSet(QStringLiteral("proprietary")) && + parser.isSet(QStringLiteral("open-source")); +} + +} // namespace + +ParsedCommand parseArguments(const QStringList &arguments, + const QString &applicationName, + const QString &applicationVersion, + const QString &applicationDescription) { + QCommandLineParser parser; + configureParser(parser, applicationName, applicationVersion, + applicationDescription); + + if (!parser.parse(arguments)) { + return invalidCommand(parser.errorText()); + } + + parser.process(arguments); + + const QString helpText = buildHelpText(applicationName, applicationVersion, + applicationDescription); + + const bool help = parser.isSet(QStringLiteral("help")); + const bool version = parser.isSet(QStringLiteral("version")); + const bool diagnosticsFlag = parser.isSet(QStringLiteral("diagnostics")); + const bool json = parser.isSet(QStringLiteral("json")); + const bool proprietary = parser.isSet(QStringLiteral("proprietary")); + const bool openSource = parser.isSet(QStringLiteral("open-source")); + const bool acceptLicense = parser.isSet(QStringLiteral("accept-license")); + const QStringList positional = parser.positionalArguments(); + + if (hasConflictingInstallModeOptions(parser)) { + return invalidCommand(QStringLiteral( + "--proprietary and --open-source cannot be used together.")); + } + + if (help) { + ParsedCommand command; + command.action = CommandAction::PrintHelp; + command.payload = helpText; + return command; + } + + if (version && positional.isEmpty() && !diagnosticsFlag) { + ParsedCommand command; + command.action = CommandAction::PrintVersion; + command.payload = applicationVersion; + return command; + } + + if (diagnosticsFlag) { + if (!positional.isEmpty()) { + return invalidCommand(QStringLiteral( + "--diagnostics cannot be combined with positional commands.")); + } + + ParsedCommand command; + command.action = json ? CommandAction::PrintDiagnosticsJson + : CommandAction::PrintDiagnosticsText; + return command; + } + + if (json && positional.isEmpty()) { + return invalidCommand(QStringLiteral( + "--json can only be used with `status` or `diagnostics`.")); + } + + if (proprietary || openSource || acceptLicense) { + if (positional.value(0) != QStringLiteral("driver") || + positional.value(1) != QStringLiteral("install")) { + return invalidCommand( + QStringLiteral("--proprietary, --open-source and --accept-license " + "can only be used with `driver install`.")); + } + } + + if (positional.isEmpty()) { + return ParsedCommand{}; + } + + const QString commandName = positional.at(0).toLower(); + + if (commandName == QStringLiteral("help")) { + ParsedCommand command; + command.action = CommandAction::PrintHelp; + command.payload = helpText; + return command; + } + + if (commandName == QStringLiteral("version")) { + if (positional.size() != 1) { + return invalidCommand( + QStringLiteral("`version` does not take arguments.")); + } + + ParsedCommand command; + command.action = CommandAction::PrintVersion; + command.payload = applicationVersion; + return command; + } + + if (commandName == QStringLiteral("status")) { + if (positional.size() != 1) { + return invalidCommand( + QStringLiteral("`status` does not take arguments.")); + } + + ParsedCommand command; + command.action = + json ? CommandAction::PrintStatusJson : CommandAction::PrintStatusText; + return command; + } + + if (commandName == QStringLiteral("diagnostics")) { + if (positional.size() != 1) { + return invalidCommand( + QStringLiteral("`diagnostics` does not take arguments.")); + } + + ParsedCommand command; + command.action = json ? CommandAction::PrintDiagnosticsJson + : CommandAction::PrintDiagnosticsText; + return command; + } + + if (commandName != QStringLiteral("driver")) { + return invalidCommand( + QStringLiteral("Unknown command: %1").arg(commandName)); + } + + if (positional.size() < 2) { + return invalidCommand( + QStringLiteral("`driver` requires a subcommand: install, remove, " + "update, deep-clean.")); + } + + if (json) { + return invalidCommand(QStringLiteral( + "--json is only supported by `status` and `diagnostics`.")); + } + + const QString driverAction = positional.at(1).toLower(); + if (driverAction == QStringLiteral("install")) { + if (positional.size() != 2) { + return invalidCommand(QStringLiteral( + "`driver install` does not take positional arguments.")); + } + + ParsedCommand command; + command.acceptLicense = acceptLicense; + command.action = openSource ? CommandAction::InstallOpenSourceDriver + : CommandAction::InstallProprietaryDriver; + + if (openSource && acceptLicense) { + return invalidCommand(QStringLiteral( + "--accept-license is only valid with the proprietary install path.")); + } + + return command; + } + + if (proprietary || openSource || acceptLicense) { + return invalidCommand(QStringLiteral( + "Install-specific options can only be used with `driver install`.")); + } + + if (positional.size() != 2) { + return invalidCommand( + QStringLiteral("`driver %1` does not take positional arguments.") + .arg(driverAction)); + } + + if (driverAction == QStringLiteral("remove")) { + ParsedCommand command; + command.action = CommandAction::RemoveDriver; + return command; + } + + if (driverAction == QStringLiteral("update")) { + ParsedCommand command; + command.action = CommandAction::UpdateDriver; + return command; + } + + if (driverAction == QStringLiteral("deep-clean")) { + ParsedCommand command; + command.action = CommandAction::DeepCleanDriver; + return command; + } + + return invalidCommand( + QStringLiteral("Unknown `driver` subcommand: %1").arg(driverAction)); +} + +DiagnosticsSnapshot collectDiagnostics(const QString &applicationName, + const QString &applicationVersion) { + DiagnosticsSnapshot snapshot; + snapshot.applicationName = applicationName; + snapshot.applicationVersion = applicationVersion; + snapshot.locale = QLocale::system().name(); + + NvidiaDetector detector; + detector.refresh(); + + snapshot.gpuFound = detector.gpuFound(); + snapshot.gpuName = detector.gpuName(); + snapshot.driverVersion = detector.driverVersion(); + snapshot.activeDriver = detector.activeDriver(); + snapshot.secureBootEnabled = detector.secureBootEnabled(); + snapshot.sessionType = detector.sessionType(); + snapshot.verificationReport = detector.verificationReport(); + + NvidiaUpdater updater; + updater.checkForUpdate(); + snapshot.currentDriverVersion = updater.currentVersion(); + snapshot.latestDriverVersion = updater.latestVersion(); + snapshot.updateAvailable = updater.updateAvailable(); + + CpuMonitor cpuMonitor; + cpuMonitor.stop(); + cpuMonitor.refresh(); + QThread::msleep(25); + cpuMonitor.refresh(); + snapshot.cpuAvailable = cpuMonitor.available(); + snapshot.cpuUsagePercent = cpuMonitor.usagePercent(); + snapshot.cpuTemperatureC = cpuMonitor.temperatureC(); + + GpuMonitor gpuMonitor; + gpuMonitor.stop(); + gpuMonitor.refresh(); + snapshot.gpuMonitorAvailable = gpuMonitor.available(); + snapshot.gpuMonitorName = gpuMonitor.gpuName(); + snapshot.gpuTemperatureC = gpuMonitor.temperatureC(); + snapshot.gpuUtilizationPercent = gpuMonitor.utilizationPercent(); + snapshot.gpuMemoryUsedMiB = gpuMonitor.memoryUsedMiB(); + snapshot.gpuMemoryTotalMiB = gpuMonitor.memoryTotalMiB(); + snapshot.gpuMemoryUsagePercent = gpuMonitor.memoryUsagePercent(); + + RamMonitor ramMonitor; + ramMonitor.stop(); + ramMonitor.refresh(); + snapshot.ramAvailable = ramMonitor.available(); + snapshot.ramTotalMiB = ramMonitor.totalMiB(); + snapshot.ramUsedMiB = ramMonitor.usedMiB(); + snapshot.ramUsagePercent = ramMonitor.usagePercent(); + + return snapshot; +} + +QString renderStatusText(const DiagnosticsSnapshot &snapshot) { + QString output; + output += QStringLiteral("application: %1\n").arg(snapshot.applicationName); + output += QStringLiteral("version: %1\n").arg(snapshot.applicationVersion); + output += QStringLiteral("gpu_found: %1\n").arg(boolText(snapshot.gpuFound)); + output += QStringLiteral("gpu_name: %1\n").arg(dashIfEmpty(snapshot.gpuName)); + output += QStringLiteral("active_driver: %1\n") + .arg(dashIfEmpty(snapshot.activeDriver)); + output += QStringLiteral("driver_version: %1\n") + .arg(dashIfEmpty(snapshot.driverVersion)); + output += QStringLiteral("session_type: %1\n") + .arg(dashIfEmpty(snapshot.sessionType)); + output += QStringLiteral("secure_boot_enabled: %1\n") + .arg(boolText(snapshot.secureBootEnabled)); + output += QStringLiteral("update_available: %1\n") + .arg(boolText(snapshot.updateAvailable)); + output += QStringLiteral("latest_driver_version: %1\n") + .arg(dashIfEmpty(snapshot.latestDriverVersion)); + return output; +} + +QString renderDiagnosticsText(const DiagnosticsSnapshot &snapshot) { + QString output; + output += renderStatusText(snapshot); + output += QStringLiteral("locale: %1\n").arg(snapshot.locale); + output += QStringLiteral("current_driver_version: %1\n") + .arg(dashIfEmpty(snapshot.currentDriverVersion)); + output += QStringLiteral("cpu_available: %1\n") + .arg(boolText(snapshot.cpuAvailable)); + output += QStringLiteral("cpu_usage_percent: %1\n") + .arg(snapshot.cpuUsagePercent, 0, 'f', 1); + output += + QStringLiteral("cpu_temperature_c: %1\n").arg(snapshot.cpuTemperatureC); + output += QStringLiteral("gpu_monitor_available: %1\n") + .arg(boolText(snapshot.gpuMonitorAvailable)); + output += QStringLiteral("gpu_monitor_name: %1\n") + .arg(dashIfEmpty(snapshot.gpuMonitorName)); + output += + QStringLiteral("gpu_temperature_c: %1\n").arg(snapshot.gpuTemperatureC); + output += QStringLiteral("gpu_utilization_percent: %1\n") + .arg(snapshot.gpuUtilizationPercent); + output += QStringLiteral("gpu_memory_used_mib: %1\n") + .arg(snapshot.gpuMemoryUsedMiB); + output += QStringLiteral("gpu_memory_total_mib: %1\n") + .arg(snapshot.gpuMemoryTotalMiB); + output += QStringLiteral("gpu_memory_usage_percent: %1\n") + .arg(snapshot.gpuMemoryUsagePercent); + output += QStringLiteral("ram_available: %1\n") + .arg(boolText(snapshot.ramAvailable)); + output += QStringLiteral("ram_total_mib: %1\n").arg(snapshot.ramTotalMiB); + output += QStringLiteral("ram_used_mib: %1\n").arg(snapshot.ramUsedMiB); + output += + QStringLiteral("ram_usage_percent: %1\n").arg(snapshot.ramUsagePercent); + + if (!snapshot.verificationReport.isEmpty()) { + output += QStringLiteral("verification_report:\n%1\n") + .arg(snapshot.verificationReport); + } + + return output; +} + +QJsonObject renderStatusJsonObject(const DiagnosticsSnapshot &snapshot) { + QJsonObject object; + object.insert(QStringLiteral("command"), + commandActionToString(CommandAction::PrintStatusJson)); + object.insert(QStringLiteral("application"), snapshot.applicationName); + object.insert(QStringLiteral("version"), snapshot.applicationVersion); + object.insert(QStringLiteral("gpuFound"), snapshot.gpuFound); + object.insert(QStringLiteral("gpuName"), snapshot.gpuName); + object.insert(QStringLiteral("activeDriver"), snapshot.activeDriver); + object.insert(QStringLiteral("driverVersion"), snapshot.driverVersion); + object.insert(QStringLiteral("sessionType"), snapshot.sessionType); + object.insert(QStringLiteral("secureBootEnabled"), + snapshot.secureBootEnabled); + object.insert(QStringLiteral("updateAvailable"), snapshot.updateAvailable); + object.insert(QStringLiteral("latestDriverVersion"), + snapshot.latestDriverVersion); + return object; +} + +QJsonObject renderDiagnosticsJsonObject(const DiagnosticsSnapshot &snapshot) { + QJsonObject object = renderStatusJsonObject(snapshot); + object.insert(QStringLiteral("command"), + commandActionToString(CommandAction::PrintDiagnosticsJson)); + object.insert(QStringLiteral("locale"), snapshot.locale); + object.insert(QStringLiteral("verificationReport"), + snapshot.verificationReport); + object.insert(QStringLiteral("currentDriverVersion"), + snapshot.currentDriverVersion); + object.insert(QStringLiteral("cpuAvailable"), snapshot.cpuAvailable); + object.insert(QStringLiteral("cpuUsagePercent"), snapshot.cpuUsagePercent); + object.insert(QStringLiteral("cpuTemperatureC"), snapshot.cpuTemperatureC); + object.insert(QStringLiteral("gpuMonitorAvailable"), + snapshot.gpuMonitorAvailable); + object.insert(QStringLiteral("gpuMonitorName"), snapshot.gpuMonitorName); + object.insert(QStringLiteral("gpuTemperatureC"), snapshot.gpuTemperatureC); + object.insert(QStringLiteral("gpuUtilizationPercent"), + snapshot.gpuUtilizationPercent); + object.insert(QStringLiteral("gpuMemoryUsedMiB"), snapshot.gpuMemoryUsedMiB); + object.insert(QStringLiteral("gpuMemoryTotalMiB"), + snapshot.gpuMemoryTotalMiB); + object.insert(QStringLiteral("gpuMemoryUsagePercent"), + snapshot.gpuMemoryUsagePercent); + object.insert(QStringLiteral("ramAvailable"), snapshot.ramAvailable); + object.insert(QStringLiteral("ramTotalMiB"), snapshot.ramTotalMiB); + object.insert(QStringLiteral("ramUsedMiB"), snapshot.ramUsedMiB); + object.insert(QStringLiteral("ramUsagePercent"), snapshot.ramUsagePercent); + return object; +} + +} // namespace RoControlCli diff --git a/src/cli/cli.h b/src/cli/cli.h new file mode 100644 index 0000000..7b42b1c --- /dev/null +++ b/src/cli/cli.h @@ -0,0 +1,80 @@ +#pragma once + +#include +#include + +class QJsonObject; + +namespace RoControlCli { + +enum class CommandAction { + LaunchGui, + PrintHelp, + PrintVersion, + PrintStatusText, + PrintStatusJson, + PrintDiagnosticsText, + PrintDiagnosticsJson, + InstallProprietaryDriver, + InstallOpenSourceDriver, + RemoveDriver, + UpdateDriver, + DeepCleanDriver, + Invalid, +}; + +struct ParsedCommand { + CommandAction action = CommandAction::LaunchGui; + QString payload; + bool acceptLicense = false; +}; + +struct DiagnosticsSnapshot { + QString applicationName; + QString applicationVersion; + QString locale; + + bool gpuFound = false; + QString gpuName; + QString driverVersion; + QString activeDriver; + bool secureBootEnabled = false; + QString sessionType; + QString verificationReport; + + QString currentDriverVersion; + QString latestDriverVersion; + bool updateAvailable = false; + + bool cpuAvailable = false; + double cpuUsagePercent = 0.0; + int cpuTemperatureC = 0; + + bool gpuMonitorAvailable = false; + QString gpuMonitorName; + int gpuTemperatureC = 0; + int gpuUtilizationPercent = 0; + int gpuMemoryUsedMiB = 0; + int gpuMemoryTotalMiB = 0; + int gpuMemoryUsagePercent = 0; + + bool ramAvailable = false; + int ramTotalMiB = 0; + int ramUsedMiB = 0; + int ramUsagePercent = 0; +}; + +ParsedCommand parseArguments(const QStringList &arguments, + const QString &applicationName, + const QString &applicationVersion, + const QString &applicationDescription); + +DiagnosticsSnapshot collectDiagnostics(const QString &applicationName, + const QString &applicationVersion); + +QString renderStatusText(const DiagnosticsSnapshot &snapshot); +QString renderDiagnosticsText(const DiagnosticsSnapshot &snapshot); +QJsonObject renderDiagnosticsJsonObject(const DiagnosticsSnapshot &snapshot); +QJsonObject renderStatusJsonObject(const DiagnosticsSnapshot &snapshot); + +} // namespace RoControlCli diff --git a/src/main.cpp b/src/main.cpp index e20d72c..a94bf16 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,7 +1,12 @@ #include +#include #include +#include +#include #include +#include #include +#include #include #include @@ -11,30 +16,214 @@ #include "backend/nvidia/detector.h" #include "backend/nvidia/installer.h" #include "backend/nvidia/updater.h" +#include "cli/cli.h" + +namespace { + +struct CliExecutionResult { + int exitCode = 0; + QString stdoutText; + QString stderrText; +}; + +CliExecutionResult executeCliCommand(const RoControlCli::ParsedCommand &command, + const QString &applicationName, + const QString &applicationVersion) { + CliExecutionResult result; + + if (command.action == RoControlCli::CommandAction::PrintStatusText || + command.action == RoControlCli::CommandAction::PrintStatusJson || + command.action == RoControlCli::CommandAction::PrintDiagnosticsText || + command.action == RoControlCli::CommandAction::PrintDiagnosticsJson) { + const auto snapshot = + RoControlCli::collectDiagnostics(applicationName, applicationVersion); + + if (command.action == RoControlCli::CommandAction::PrintStatusJson) { + result.stdoutText = QString::fromUtf8( + QJsonDocument(RoControlCli::renderStatusJsonObject(snapshot)) + .toJson(QJsonDocument::Indented)); + } else if (command.action == RoControlCli::CommandAction::PrintStatusText) { + result.stdoutText = RoControlCli::renderStatusText(snapshot); + } else if (command.action == + RoControlCli::CommandAction::PrintDiagnosticsJson) { + result.stdoutText = QString::fromUtf8( + QJsonDocument(RoControlCli::renderDiagnosticsJsonObject(snapshot)) + .toJson(QJsonDocument::Indented)); + } else { + result.stdoutText = RoControlCli::renderDiagnosticsText(snapshot); + } + + return result; + } + + QTextStream progressStream(&result.stdoutText); + auto appendProgress = [&](const QString &message) { + if (!message.trimmed().isEmpty()) { + progressStream << message.trimmed() << '\n'; + } + }; + + if (command.action == RoControlCli::CommandAction::InstallProprietaryDriver || + command.action == RoControlCli::CommandAction::InstallOpenSourceDriver || + command.action == RoControlCli::CommandAction::RemoveDriver || + command.action == RoControlCli::CommandAction::DeepCleanDriver) { + NvidiaInstaller installer; + QObject::connect(&installer, &NvidiaInstaller::progressMessage, &installer, + appendProgress); + + bool finished = false; + bool success = false; + QString finalMessage; + + QObject::connect(&installer, &NvidiaInstaller::installFinished, &installer, + [&](bool ok, const QString &message) { + finished = true; + success = ok; + finalMessage = message; + }); + QObject::connect(&installer, &NvidiaInstaller::removeFinished, &installer, + [&](bool ok, const QString &message) { + finished = true; + success = ok; + finalMessage = message; + }); + + if (command.action == + RoControlCli::CommandAction::InstallProprietaryDriver) { + installer.installProprietary(command.acceptLicense); + } else if (command.action == + RoControlCli::CommandAction::InstallOpenSourceDriver) { + installer.installOpenSource(); + } else if (command.action == RoControlCli::CommandAction::RemoveDriver) { + installer.remove(); + } else { + installer.deepClean(); + finished = true; + success = true; + finalMessage = QStringLiteral("Legacy NVIDIA cleanup completed."); + } + + if (!finalMessage.isEmpty()) { + if (success) { + appendProgress(finalMessage); + } else { + result.stderrText = finalMessage; + } + } + + result.exitCode = finished && success ? 0 : 1; + return result; + } + + if (command.action == RoControlCli::CommandAction::UpdateDriver) { + NvidiaUpdater updater; + QObject::connect(&updater, &NvidiaUpdater::progressMessage, &updater, + appendProgress); + + bool finished = false; + bool success = false; + QString finalMessage; + + QObject::connect(&updater, &NvidiaUpdater::updateFinished, &updater, + [&](bool ok, const QString &message) { + finished = true; + success = ok; + finalMessage = message; + }); + + updater.applyUpdate(); + + if (!finalMessage.isEmpty()) { + if (success) { + appendProgress(finalMessage); + } else { + result.stderrText = finalMessage; + } + } + + result.exitCode = finished && success ? 0 : 1; + return result; + } + + result.exitCode = 2; + result.stderrText = QStringLiteral("Unsupported CLI command."); + return result; +} + +} // namespace int main(int argc, char *argv[]) { - // TR: QApplication, Qt Widgets tabanli uygulama omurgasini baslatir. - // EN: QApplication bootstraps the Qt Widgets application runtime. + constexpr auto kApplicationName = "ro-control"; + constexpr auto kDisplayName = "ro-Control"; + constexpr auto kApplicationVersion = "0.1.0"; + 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)); + + const auto command = RoControlCli::parseArguments( + cliApp.arguments(), cliApp.applicationName(), + cliApp.applicationVersion(), applicationDescription); + + 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::Invalid) { + err << command.payload << Qt::endl; + err << "Run `ro-control --help` for usage." << Qt::endl; + return 2; + } + + 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; + } + } + if (!result.stderrText.isEmpty()) { + err << result.stderrText; + if (!result.stderrText.endsWith(QLatin1Char('\n'))) { + err << Qt::endl; + } + } + return result.exitCode; + } + } + QApplication app(argc, argv); - // TR: Bu meta bilgiler masaustu entegrasyonu ve UI kimligi icin kullanilir. - // EN: These metadata values are used for desktop integration and app - // identity. - app.setApplicationName("ro-control"); - app.setApplicationDisplayName("ro-Control"); - app.setApplicationVersion("0.1.0"); + app.setApplicationName(QString::fromLatin1(kApplicationName)); + app.setApplicationDisplayName(QString::fromLatin1(kDisplayName)); + app.setApplicationVersion(QString::fromLatin1(kApplicationVersion)); app.setOrganizationName("Project-Ro-ASD"); app.setOrganizationDomain("github.com/Project-Ro-ASD"); app.setWindowIcon(QIcon::fromTheme( "ro-control", QIcon(":/qt/qml/rocontrol/assets/ro-control-logo.svg"))); QTranslator translator; - const QString localeName = - QLocale::system().name().startsWith(QStringLiteral("tr")) - ? QStringLiteral("tr") - : QStringLiteral("en"); + 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))) { + QStringLiteral(":/i18n/ro-control_%1.qm").arg(localeName)) || + translator.load( + QStringLiteral(":/i18n/ro-control_%1.qm").arg(baseLanguage))) { app.installTranslator(&translator); } @@ -45,12 +234,7 @@ int main(int argc, char *argv[]) { GpuMonitor gpuMonitor; RamMonitor ramMonitor; - // TR: QML motoru, arayuz ve bagli context nesnelerini yukler. - // EN: The QML engine loads the UI and injected context objects. QQmlApplicationEngine engine; - - // TR: Ana pencerenin gerekli backend baglantilarini baslangicta enjekte et. - // EN: Inject required backend bindings into the main QML root object. engine.setInitialProperties({ {"nvidiaDetector", QVariant::fromValue(&detector)}, {"nvidiaInstaller", QVariant::fromValue(&installer)}, @@ -60,14 +244,10 @@ int main(int argc, char *argv[]) { {"ramMonitor", QVariant::fromValue(&ramMonitor)}, }); - // TR: Ana bileşen olusmazsa uygulamayi kontrollu sekilde sonlandir. - // EN: Exit gracefully if the root QML component cannot be created. QObject::connect( &engine, &QQmlApplicationEngine::objectCreationFailed, &app, []() { QCoreApplication::exit(-1); }, Qt::QueuedConnection); - // TR: Modulden yukleme, qrc yol/prefix farklarina karsi daha dayaniklidir. - // EN: Module-based loading is resilient to qrc path/prefix differences. engine.loadFromModule("rocontrol", "Main"); return app.exec(); diff --git a/src/qml/Main.qml b/src/qml/Main.qml index 97deab2..35358c1 100644 --- a/src/qml/Main.qml +++ b/src/qml/Main.qml @@ -1,486 +1,72 @@ -import QtCore -import QtQuick 2.15 -import QtQuick.Controls 2.15 -import QtQuick.Layouts 1.15 -import "components" -import "pages" +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts ApplicationWindow { id: root - property var nvidiaDetector - property var nvidiaInstaller - property var nvidiaUpdater - property var cpuMonitor - property var gpuMonitor - property var ramMonitor - - Settings { - id: uiSettings - category: "ui" - property string themeMode: "dark" - } - - property string themeMode: uiSettings.themeMode - readonly property bool darkMode: themeMode === "dark" - readonly property string githubUrl: "https://github.com/Project-Ro-ASD/ro-Control" - readonly property var theme: darkMode ? ({ - windowTop: "#09111d", - windowBottom: "#131f33", - shell: "#0f1727", - panel: "#162033", - panelAlt: "#1a2740", - card: "#18253b", - cardStrong: "#1f304c", - cardMuted: "#22314f", - border: "#2b436a", - text: "#edf4ff", - textMuted: "#9db0cd", - textSoft: "#7e94b6", - accentA: "#ff8a3d", - accentB: "#39a7ff", - accentSoft: "#203b60", - success: "#44d17f", - warning: "#ffba57", - danger: "#ff6d79", - shadow: "#70081423" - }) : ({ - windowTop: "#fff3e8", - windowBottom: "#e8f3ff", - shell: "#f6f9ff", - panel: "#ffffff", - panelAlt: "#f5f8fe", - card: "#ffffff", - cardStrong: "#f8fbff", - cardMuted: "#eef4fc", - border: "#cedcf1", - text: "#132238", - textMuted: "#4e617d", - textSoft: "#687d98", - accentA: "#f47b20", - accentB: "#1677ff", - accentSoft: "#dbe9ff", - success: "#138a52", - warning: "#b77700", - danger: "#cb3d4f", - shadow: "#33091a34" - }) - readonly property var pageTitles: [ - qsTr("Driver Hub"), - qsTr("System Monitor"), - qsTr("Settings") - ] - readonly property var pageDescriptions: [ - qsTr("Install, switch and verify NVIDIA driver states without leaving the app."), - qsTr("Track CPU, RAM and NVIDIA telemetry with a compact live dashboard."), - qsTr("Adjust the app appearance and inspect product metadata.") - ] - - function setThemeMode(mode) { - themeMode = mode - uiSettings.themeMode = mode - } - visible: true - minimumWidth: 1220 - minimumHeight: 760 - width: 1360 - height: 860 - title: "ro-Control" - color: "transparent" - - background: Rectangle { - gradient: Gradient { - GradientStop { - position: 0.0 - color: root.theme.windowTop - } - GradientStop { - position: 0.38 - color: Qt.tint(root.theme.windowBottom, "#40ff8a3d") - } - GradientStop { - position: 1.0 - color: Qt.tint(root.theme.windowBottom, "#30249bff") - } - } - - Rectangle { - anchors.top: parent.top - anchors.right: parent.right - width: parent.width * 0.42 - height: parent.height * 0.35 - radius: width / 2 - color: "#22ffffff" - opacity: root.darkMode ? 0.12 : 0.28 - rotation: -12 - x: parent.width * 0.66 - y: -height * 0.34 - } - - Rectangle { - anchors.bottom: parent.bottom - anchors.left: parent.left - width: parent.width * 0.35 - height: parent.height * 0.28 - radius: width / 2 - color: root.darkMode ? "#18ff8a3d" : "#20f47b20" - x: -width * 0.2 - y: parent.height - height * 0.65 - } - } - - Dialog { - id: aboutDialog - modal: true - focus: true - anchors.centerIn: Overlay.overlay - width: Math.min(root.width * 0.72, 880) - height: Math.min(root.height * 0.78, 720) - padding: 0 - closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside - - background: Rectangle { - radius: 28 - border.width: 1 - border.color: root.theme.border - color: root.theme.panel - } + width: 980 + height: 640 + title: qsTr("ro-Control") + readonly property bool darkMode: Qt.styleHints.colorScheme === Qt.Dark + color: darkMode ? "#141822" : "#f4f6fb" - contentItem: ScrollView { - clip: true - - ColumnLayout { - width: parent.width - spacing: 18 - - Rectangle { - Layout.fillWidth: true - radius: 26 - color: Qt.tint(root.theme.cardStrong, root.darkMode ? "#30ff8a3d" : "#1af47b20") - implicitHeight: headerColumn.implicitHeight + 28 - - ColumnLayout { - id: headerColumn - anchors.fill: parent - anchors.margins: 20 - spacing: 10 - - RowLayout { - Layout.fillWidth: true - spacing: 14 - - Rectangle { - Layout.preferredWidth: 54 - Layout.preferredHeight: 54 - radius: 18 - color: root.theme.accentSoft - - Image { - anchors.centerIn: parent - source: "qrc:/qt/qml/rocontrol/assets/ro-control-logo.png" - sourceSize.width: 34 - sourceSize.height: 34 - fillMode: Image.PreserveAspectFit - } - } - - ColumnLayout { - Layout.fillWidth: true - spacing: 4 - - Label { - text: qsTr("About ro-Control") - font.pixelSize: 26 - font.bold: true - color: root.theme.text - } - - Label { - text: qsTr("A focused Fedora control surface for NVIDIA driver lifecycle and live telemetry.") - wrapMode: Text.Wrap - Layout.fillWidth: true - color: root.theme.textMuted - } - } - } - - RowLayout { - spacing: 10 - - Rectangle { - radius: 999 - color: root.theme.cardMuted - implicitHeight: 32 - implicitWidth: versionLabel.implicitWidth + 24 + ColumnLayout { + anchors.fill: parent + spacing: 0 - Label { - id: versionLabel - anchors.centerIn: parent - text: qsTr("App Version %1").arg(Qt.application.version) - color: root.theme.text - font.pixelSize: 13 - font.bold: true - } - } + ToolBar { + Layout.fillWidth: true + background: Rectangle { + color: root.darkMode ? "#1b2130" : "#ffffff" + } - Rectangle { - radius: 999 - color: root.theme.cardMuted - implicitHeight: 32 - implicitWidth: repoLabel.implicitWidth + 24 + RowLayout { + anchors.fill: parent + anchors.margins: 8 - Label { - id: repoLabel - anchors.centerIn: parent - text: qsTr("Fedora x86_64 NVIDIA Flow") - color: root.theme.textMuted - font.pixelSize: 13 - } - } - } - } + Label { + text: qsTr("ro-Control") + font.pixelSize: 20 + font.bold: true + color: root.darkMode ? "#e8edfb" : "#121825" } - RowLayout { + Item { Layout.fillWidth: true - spacing: 16 - - Rectangle { - Layout.fillWidth: true - Layout.preferredWidth: 1 - radius: 22 - color: root.theme.card - border.width: 1 - border.color: root.theme.border - implicitHeight: versionInfoColumn.implicitHeight + 24 - - ColumnLayout { - id: versionInfoColumn - anchors.fill: parent - anchors.margins: 18 - spacing: 10 - - Label { - text: qsTr("Version Overview") - font.pixelSize: 19 - font.bold: true - color: root.theme.text - } - - Label { - text: qsTr("Installed driver: %1").arg(root.nvidiaUpdater.currentVersion.length > 0 ? root.nvidiaUpdater.currentVersion : qsTr("Not installed")) - color: root.theme.textMuted - } - - Label { - text: qsTr("Latest repo version: %1").arg(root.nvidiaUpdater.latestVersion.length > 0 ? root.nvidiaUpdater.latestVersion : qsTr("Unknown")) - color: root.theme.textMuted - } - - Label { - text: qsTr("Available repo versions: %1").arg(root.nvidiaUpdater.availableVersions.length) - color: root.theme.textMuted - } - - RowLayout { - spacing: 10 - - Button { - text: qsTr("Check Versions") - enabled: !root.nvidiaUpdater.busy - onClicked: root.nvidiaUpdater.checkForUpdate() - } - - Button { - text: qsTr("Open GitHub") - onClicked: Qt.openUrlExternally(root.githubUrl) - } - } - } - } - - Rectangle { - Layout.fillWidth: true - Layout.preferredWidth: 1 - radius: 22 - color: root.theme.card - border.width: 1 - border.color: root.theme.border - implicitHeight: releaseNotesColumn.implicitHeight + 24 - - ColumnLayout { - id: releaseNotesColumn - anchors.fill: parent - anchors.margins: 18 - spacing: 10 - - Label { - text: qsTr("Release Notes") - font.pixelSize: 19 - font.bold: true - color: root.theme.text - } - - Repeater { - model: [ - qsTr("Selectable NVIDIA versions were added alongside a direct update-to-latest flow."), - qsTr("Long-running driver operations now execute asynchronously so the interface stays responsive."), - qsTr("Detection, monitoring and package-sync logic were hardened for Fedora NVIDIA systems.") - ] - - delegate: Label { - required property string modelData - Layout.fillWidth: true - wrapMode: Text.Wrap - text: "\u2022 " + modelData - color: root.theme.textMuted - } - } - } - } } - Item { - Layout.fillHeight: true - Layout.minimumHeight: 1 + Label { + text: root.darkMode ? qsTr("Theme: System (Dark)") : qsTr("Theme: System (Light)") + color: root.darkMode ? "#c8d0e7" : "#36435f" } } } - } - RowLayout { - anchors.fill: parent - anchors.margins: 22 - spacing: 18 + TabBar { + id: tabs + Layout.fillWidth: true - SidebarMenu { - id: sidebar - Layout.fillHeight: true - Layout.preferredWidth: 272 - currentIndex: contentStack.currentIndex - darkMode: root.darkMode - theme: root.theme - menuItems: root.pageTitles - onNavigate: contentStack.currentIndex = index + TabButton { + text: qsTr("Driver") + } + TabButton { + text: qsTr("Monitor") + } + TabButton { + text: qsTr("Settings") + } } - Rectangle { + StackLayout { Layout.fillWidth: true Layout.fillHeight: true - radius: 30 - color: "#11000000" - border.width: 1 - border.color: root.darkMode ? "#24ffffff" : "#26a4b7d0" - - Rectangle { - anchors.fill: parent - anchors.margins: 1 - radius: 29 - color: root.theme.shell - - ColumnLayout { - anchors.fill: parent - anchors.margins: 22 - spacing: 18 - - Rectangle { - Layout.fillWidth: true - radius: 26 - color: root.theme.panel - border.width: 1 - border.color: root.theme.border - implicitHeight: topBarLayout.implicitHeight + 24 - - RowLayout { - id: topBarLayout - anchors.fill: parent - anchors.margins: 20 - spacing: 16 - - ColumnLayout { - Layout.fillWidth: true - spacing: 4 - - Label { - text: root.pageTitles[contentStack.currentIndex] - font.pixelSize: 28 - font.bold: true - color: root.theme.text - } - - Label { - text: root.pageDescriptions[contentStack.currentIndex] - Layout.fillWidth: true - wrapMode: Text.Wrap - color: root.theme.textMuted - } - } + currentIndex: tabs.currentIndex - Rectangle { - radius: 18 - color: root.theme.cardMuted - implicitWidth: modeRow.implicitWidth + 16 - implicitHeight: modeRow.implicitHeight + 12 - - RowLayout { - id: modeRow - anchors.centerIn: parent - spacing: 8 - - Button { - text: qsTr("Light") - flat: root.themeMode !== "light" - highlighted: root.themeMode === "light" - onClicked: root.setThemeMode("light") - } - - Button { - text: qsTr("Dark") - flat: root.themeMode !== "dark" - highlighted: root.themeMode === "dark" - onClicked: root.setThemeMode("dark") - } - } - } - - Button { - text: qsTr("About") - onClicked: aboutDialog.open() - } - } - } - - StackLayout { - id: contentStack - Layout.fillWidth: true - Layout.fillHeight: true - - DriverPage { - nvidiaDetector: root.nvidiaDetector - nvidiaInstaller: root.nvidiaInstaller - nvidiaUpdater: root.nvidiaUpdater - theme: root.theme - darkMode: root.darkMode - } - - MonitorPage { - cpuMonitor: root.cpuMonitor - gpuMonitor: root.gpuMonitor - ramMonitor: root.ramMonitor - theme: root.theme - darkMode: root.darkMode - } - - SettingsPage { - themeMode: root.themeMode - darkMode: root.darkMode - theme: root.theme - githubUrl: root.githubUrl - onThemeModeRequested: root.setThemeMode(mode) - onAboutRequested: aboutDialog.open() - } - } - } + DriverPage {} + MonitorPage {} + SettingsPage { + darkMode: root.darkMode } } } diff --git a/src/qml/components/SidebarMenu.qml b/src/qml/components/SidebarMenu.qml index a1e08af..c212087 100644 --- a/src/qml/components/SidebarMenu.qml +++ b/src/qml/components/SidebarMenu.qml @@ -1,177 +1,96 @@ -import QtQuick 2.15 -import QtQuick.Controls 2.15 -import QtQuick.Layouts 1.15 +import QtQuick +import QtQuick.Controls Rectangle { id: sidebar - required property var theme - required property var menuItems + width: 220 + color: "#181825" + property int currentIndex: 0 - property bool darkMode: true - signal navigate(int index) - radius: 30 - color: Qt.tint(theme.panel, darkMode ? "#12000000" : "#08ffffff") - border.width: 1 - border.color: theme.border + ListModel { + id: menuModel + ListElement { + label: "Driver Management" + } + ListElement { + label: "System Monitoring" + } + ListElement { + label: "Settings" + } + } - ColumnLayout { + Column { anchors.fill: parent - anchors.margins: 20 - spacing: 18 - - Rectangle { - Layout.fillWidth: true - radius: 24 - color: Qt.tint(sidebar.theme.cardStrong, sidebar.darkMode ? "#20ff8a3d" : "#14f47b20") - implicitHeight: brandLayout.implicitHeight + 26 - - ColumnLayout { - id: brandLayout - anchors.fill: parent - anchors.margins: 18 - spacing: 14 - - RowLayout { - spacing: 12 - - Rectangle { - Layout.preferredWidth: 46 - Layout.preferredHeight: 46 - radius: 16 - color: sidebar.theme.accentSoft - - Image { - anchors.centerIn: parent - source: "qrc:/qt/qml/rocontrol/assets/ro-control-logo.png" - sourceSize.width: 30 - sourceSize.height: 30 - fillMode: Image.PreserveAspectFit - } - } + spacing: 0 - ColumnLayout { - spacing: 2 - - Label { - text: "ro-Control" - font.pixelSize: 21 - font.bold: true - color: sidebar.theme.text - } - - Label { - text: qsTr("Fedora NVIDIA center") - color: sidebar.theme.textMuted - font.pixelSize: 13 - } - } - } - - Label { - text: qsTr("A compact control surface for driver operations, telemetry and environment checks.") - wrapMode: Text.Wrap - Layout.fillWidth: true - color: sidebar.theme.textSoft - } + // Başlık + Item { + width: parent.width + height: 70 + + Label { + anchors.centerIn: parent + text: qsTr("ro-Control") + font.pixelSize: 22 + font.bold: true + color: "#cdd6f4" } } - Label { - text: qsTr("Navigation") - color: sidebar.theme.textSoft - font.pixelSize: 12 - font.bold: true - leftPadding: 6 + Rectangle { + width: parent.width - 32 + height: 1 + anchors.horizontalCenter: parent.horizontalCenter + color: "#313244" + } + + Item { + width: 1 + height: 12 } Repeater { - model: sidebar.menuItems + model: menuModel delegate: Rectangle { + id: menuItem required property int index - required property string modelData + required property string label - Layout.fillWidth: true - radius: 20 - implicitHeight: 58 - color: sidebar.currentIndex === index ? Qt.tint(sidebar.theme.cardMuted, sidebar.darkMode ? "#2b39a7ff" : "#18f47b20") : "transparent" - border.width: sidebar.currentIndex === index ? 1 : 0 - border.color: sidebar.currentIndex === index ? sidebar.theme.border : "transparent" + width: sidebar.width - 16 + height: 44 + x: 8 + radius: 8 + color: sidebar.currentIndex === menuItem.index ? "#313244" : mouseArea.containsMouse ? "#1e1e2e" : "transparent" - RowLayout { - anchors.fill: parent - anchors.margins: 14 - spacing: 12 - - Rectangle { - Layout.preferredWidth: 30 - Layout.preferredHeight: 30 - radius: 10 - color: sidebar.currentIndex === index ? sidebar.theme.accentSoft : sidebar.theme.cardMuted - - Label { - anchors.centerIn: parent - text: (index + 1).toString() - font.bold: true - color: sidebar.currentIndex === index ? sidebar.theme.text : sidebar.theme.textMuted - } - } - - Label { - Layout.fillWidth: true - text: modelData - font.pixelSize: 15 - font.bold: sidebar.currentIndex === index - color: sidebar.currentIndex === index ? sidebar.theme.text : sidebar.theme.textMuted - } + Label { + anchors.verticalCenter: parent.verticalCenter + leftPadding: 16 + text: menuItem.label + font.pixelSize: 14 + color: sidebar.currentIndex === menuItem.index ? "#89b4fa" : "#a6adc8" } MouseArea { + id: mouseArea anchors.fill: parent hoverEnabled: true cursorShape: Qt.PointingHandCursor - onClicked: sidebar.navigate(index) + onClicked: sidebar.currentIndex = menuItem.index } } } + } - Item { - Layout.fillHeight: true - } - - Rectangle { - Layout.fillWidth: true - radius: 22 - color: sidebar.theme.card - border.width: 1 - border.color: sidebar.theme.border - implicitHeight: footerLayout.implicitHeight + 22 - - ColumnLayout { - id: footerLayout - anchors.fill: parent - anchors.margins: 16 - spacing: 6 - - Label { - text: qsTr("Build") - color: sidebar.theme.textSoft - font.pixelSize: 12 - } - - Label { - text: "v" + Qt.application.version - color: sidebar.theme.text - font.pixelSize: 17 - font.bold: true - } - - Label { - text: qsTr("Theme: %1").arg(sidebar.darkMode ? qsTr("Dark") : qsTr("Light")) - color: sidebar.theme.textMuted - } - } - } + // 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/pages/DriverPage.qml b/src/qml/pages/DriverPage.qml index 75e6bcb..bd2e377 100644 --- a/src/qml/pages/DriverPage.qml +++ b/src/qml/pages/DriverPage.qml @@ -1,478 +1,192 @@ -import QtQuick 2.15 -import QtQuick.Controls 2.15 -import QtQuick.Layouts 1.15 -import "../components" +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts Item { id: page - required property var nvidiaDetector - required property var nvidiaInstaller - required property var nvidiaUpdater - required property var theme - property bool darkMode: true - readonly property bool operationsBusy: nvidiaInstaller.busy || nvidiaUpdater.busy - function appendLog(message) { - const maxLines = 180 - const nextText = (logArea.text.length > 0 ? logArea.text + "\n" : "") + message - const lines = nextText.split("\n") - logArea.text = lines.length > maxLines ? lines.slice(lines.length - maxLines).join("\n") : nextText - logArea.cursorPosition = logArea.text.length - } - - ScrollView { + ColumnLayout { anchors.fill: parent - clip: true + anchors.margins: 20 + spacing: 12 - ColumnLayout { - width: parent.width - spacing: 18 - - Rectangle { - Layout.fillWidth: true - radius: 28 - color: Qt.tint(page.theme.panel, page.darkMode ? "#22ff8a3d" : "#14f47b20") - border.width: 1 - border.color: page.theme.border - implicitHeight: heroLayout.implicitHeight + 28 + Label { + text: qsTr("Driver Management") + font.pixelSize: 24 + font.bold: true + } - ColumnLayout { - id: heroLayout - anchors.fill: parent - anchors.margins: 20 - spacing: 16 + Label { + text: qsTr("GPU: ") + (nvidiaDetector.gpuFound ? nvidiaDetector.gpuName : qsTr("Not detected")) + wrapMode: Text.Wrap + Layout.fillWidth: true + } - Label { - text: qsTr("NVIDIA Driver Workspace") - font.pixelSize: 30 - font.bold: true - color: page.theme.text - } + Label { + text: qsTr("Active driver: ") + nvidiaDetector.activeDriver + wrapMode: Text.Wrap + } - Label { - text: qsTr("Manage detection, installation, updates and version pinning from one place. The flow is tuned for Fedora systems that only need NVIDIA driver stack handling.") - wrapMode: Text.Wrap - Layout.fillWidth: true - color: page.theme.textMuted - } + Label { + text: qsTr("Driver version: ") + (nvidiaDetector.driverVersion.length > 0 ? nvidiaDetector.driverVersion : qsTr("None")) + } - RowLayout { - Layout.fillWidth: true - spacing: 14 + Label { + text: qsTr("Secure Boot: ") + (nvidiaDetector.secureBootEnabled ? qsTr("Enabled") : qsTr("Disabled / Unknown")) + color: nvidiaDetector.secureBootEnabled ? "#c43a3a" : "#2b8a3e" + font.bold: true + } - StatCard { - Layout.fillWidth: true - theme: page.theme - title: qsTr("GPU State") - value: page.nvidiaDetector.gpuFound ? page.nvidiaDetector.gpuName : qsTr("Not Detected") - subtitle: qsTr("Active driver: %1").arg(page.nvidiaDetector.activeDriver) - accentColor: page.theme.accentB - emphasized: true - } + Label { + text: qsTr("Session type: ") + nvidiaDetector.sessionType + font.bold: true + } - StatCard { - Layout.fillWidth: true - theme: page.theme - title: qsTr("Installed Version") - value: page.nvidiaUpdater.currentVersion.length > 0 ? page.nvidiaUpdater.currentVersion : qsTr("None") - subtitle: qsTr("Latest repo version: %1").arg(page.nvidiaUpdater.latestVersion.length > 0 ? page.nvidiaUpdater.latestVersion : qsTr("Unknown")) - accentColor: page.theme.accentA - busy: page.nvidiaUpdater.busy - } + 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" + } - StatCard { - Layout.fillWidth: true - theme: page.theme - title: qsTr("Session") - value: page.nvidiaDetector.sessionType.length > 0 ? page.nvidiaDetector.sessionType : qsTr("Unknown") - subtitle: page.nvidiaDetector.secureBootKnown - ? qsTr("Secure Boot: %1").arg(page.nvidiaDetector.secureBootEnabled ? qsTr("Enabled") : qsTr("Disabled")) - : qsTr("Secure Boot state could not be detected") - accentColor: page.nvidiaDetector.secureBootEnabled ? page.theme.danger : page.theme.success - } - } - } + 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 } + } - RowLayout { - Layout.fillWidth: true - spacing: 18 - - Rectangle { - Layout.fillWidth: true - Layout.preferredWidth: 1 - radius: 26 - color: page.theme.card - border.width: 1 - border.color: page.theme.border - implicitHeight: installColumn.implicitHeight + 26 - - ColumnLayout { - id: installColumn - anchors.fill: parent - anchors.margins: 18 - spacing: 12 - - Label { - text: qsTr("Install & Recovery") - font.pixelSize: 21 - font.bold: true - color: page.theme.text - } - - Label { - text: page.nvidiaDetector.waylandSession - ? qsTr("Wayland is active. The workflow will also enforce nvidia-drm.modeset=1 when required.") - : qsTr("X11 is active. The workflow verifies X11 driver components together with the core NVIDIA stack.") - wrapMode: Text.Wrap - Layout.fillWidth: true - color: page.theme.textMuted - } - - Rectangle { - Layout.fillWidth: true - radius: 18 - color: page.theme.cardMuted - border.width: 1 - border.color: page.theme.border - visible: page.nvidiaInstaller.proprietaryAgreementRequired - implicitHeight: agreementColumn.implicitHeight + 18 - - ColumnLayout { - id: agreementColumn - anchors.fill: parent - anchors.margins: 14 - spacing: 10 - - Label { - text: page.nvidiaInstaller.proprietaryAgreementText - wrapMode: Text.Wrap - Layout.fillWidth: true - color: page.theme.warning - } - - CheckBox { - id: eulaAccept - text: qsTr("I accept the license/agreement terms") - } - } - } - - RowLayout { - Layout.fillWidth: true - spacing: 10 - - Button { - Layout.fillWidth: true - text: qsTr("Install Proprietary") - enabled: (!page.nvidiaInstaller.proprietaryAgreementRequired || eulaAccept.checked) && !page.operationsBusy - onClicked: page.nvidiaInstaller.installProprietary(eulaAccept.checked) - } - - Button { - Layout.fillWidth: true - text: qsTr("Install Nouveau") - enabled: !page.operationsBusy - onClicked: { - page.appendLog(qsTr("Nouveau driver installation started...")) - page.nvidiaInstaller.installOpenSource() - } - } - } - - RowLayout { - Layout.fillWidth: true - spacing: 10 - - Button { - Layout.fillWidth: true - text: qsTr("Deep Clean") - enabled: !page.operationsBusy - onClicked: page.nvidiaInstaller.deepClean() - } - - Button { - Layout.fillWidth: true - text: qsTr("Rescan") - enabled: !page.operationsBusy - onClicked: { - page.appendLog(qsTr("Rescanning system...")) - page.nvidiaDetector.refresh() - page.nvidiaInstaller.refreshProprietaryAgreement() - page.nvidiaUpdater.checkForUpdate() - page.appendLog(qsTr("Rescan completed.")) - } - } - } - } - } - - Rectangle { - Layout.fillWidth: true - Layout.preferredWidth: 1 - radius: 26 - color: page.theme.card - border.width: 1 - border.color: page.theme.border - implicitHeight: updateColumn.implicitHeight + 26 - - ColumnLayout { - id: updateColumn - anchors.fill: parent - anchors.margins: 18 - spacing: 12 - - Label { - text: qsTr("Updates & Version Pinning") - font.pixelSize: 21 - font.bold: true - color: page.theme.text - } - - Label { - text: page.nvidiaUpdater.updateAvailable - ? qsTr("A newer repository version is available. You can apply the latest package set or lock the system to a specific version.") - : qsTr("The installed version is current, or no newer repository version has been found yet.") - wrapMode: Text.Wrap - Layout.fillWidth: true - color: page.theme.textMuted - } - - RowLayout { - Layout.fillWidth: true - spacing: 10 - - Button { - Layout.fillWidth: true - text: qsTr("Check for Updates") - enabled: !page.operationsBusy - onClicked: { - page.appendLog(qsTr("Update check requested...")) - page.nvidiaUpdater.checkForUpdate() - } - } - - Button { - Layout.fillWidth: true - text: qsTr("Apply Latest") - enabled: page.nvidiaUpdater.updateAvailable && !page.operationsBusy - onClicked: page.nvidiaUpdater.applyUpdate() - } - } - - Rectangle { - Layout.fillWidth: true - radius: 18 - color: page.theme.cardMuted - implicitHeight: 52 - - Label { - anchors.fill: parent - anchors.margins: 14 - verticalAlignment: Text.AlignVCenter - text: qsTr("Latest repo version: %1").arg(page.nvidiaUpdater.latestVersion.length > 0 ? page.nvidiaUpdater.latestVersion : qsTr("Unknown")) - color: page.nvidiaUpdater.updateAvailable ? page.theme.warning : page.theme.textMuted - font.bold: page.nvidiaUpdater.updateAvailable - } - } - - Label { - text: qsTr("Available Versions") - font.bold: true - color: page.theme.text - } + Label { + visible: nvidiaInstaller.proprietaryAgreementRequired + text: nvidiaInstaller.proprietaryAgreementText + color: "#8a6500" + wrapMode: Text.Wrap + Layout.fillWidth: true + } - ComboBox { - id: versionSelector - Layout.fillWidth: true - model: page.nvidiaUpdater.availableVersions - enabled: model.length > 0 && !page.operationsBusy - } + CheckBox { + id: eulaAccept + visible: nvidiaInstaller.proprietaryAgreementRequired + text: qsTr("I accept the license / agreement terms") + } - RowLayout { - Layout.fillWidth: true - spacing: 10 + RowLayout { + Layout.fillWidth: true + spacing: 8 - Button { - Layout.fillWidth: true - text: qsTr("Refresh Versions") - enabled: !page.operationsBusy - onClicked: { - page.appendLog(qsTr("Refreshing repository version list...")) - page.nvidiaUpdater.refreshAvailableVersions() - } - } + Button { + text: qsTr("Install Proprietary Driver") + enabled: !nvidiaInstaller.proprietaryAgreementRequired || eulaAccept.checked + onClicked: nvidiaInstaller.installProprietary(eulaAccept.checked) + } - Button { - Layout.fillWidth: true - text: qsTr("Apply Selected") - enabled: versionSelector.currentIndex >= 0 && page.nvidiaUpdater.availableVersions.length > 0 && !page.operationsBusy - onClicked: { - const selectedVersion = versionSelector.currentText - page.appendLog(qsTr("Applying selected version: %1").arg(selectedVersion)) - page.nvidiaUpdater.applyVersion(selectedVersion) - } - } - } - } - } + Button { + text: qsTr("Install Open-Source Driver (Nouveau)") + onClicked: nvidiaInstaller.installOpenSource() } - RowLayout { - Layout.fillWidth: true - spacing: 18 + Button { + text: qsTr("Deep Clean") + onClicked: nvidiaInstaller.deepClean() + } + } - Rectangle { - Layout.fillWidth: true - Layout.preferredWidth: 1 - radius: 26 - color: page.theme.card - border.width: 1 - border.color: page.theme.border - implicitHeight: verificationColumn.implicitHeight + 26 + RowLayout { + spacing: 8 - ColumnLayout { - id: verificationColumn - anchors.fill: parent - anchors.margins: 18 - spacing: 12 + Button { + text: qsTr("Check for Updates") + onClicked: nvidiaUpdater.checkForUpdate() + } - Label { - text: qsTr("Environment Verification") - font.pixelSize: 21 - font.bold: true - color: page.theme.text - } + Button { + text: qsTr("Apply Update") + enabled: nvidiaUpdater.updateAvailable + onClicked: nvidiaUpdater.applyUpdate() + } - Label { - text: qsTr("Use this report to confirm that Fedora session state, Secure Boot information and package checks are aligned before changing drivers.") - wrapMode: Text.Wrap - Layout.fillWidth: true - color: page.theme.textMuted - } + Label { + visible: nvidiaUpdater.updateAvailable + text: qsTr("Latest version: ") + nvidiaUpdater.latestVersion + color: "#8a6500" + } + } - Rectangle { - Layout.fillWidth: true - radius: 18 - color: page.theme.cardMuted - border.width: 1 - border.color: page.theme.border - implicitHeight: verificationText.implicitHeight + 24 + RowLayout { + spacing: 8 - Label { - id: verificationText - anchors.fill: parent - anchors.margins: 14 - wrapMode: Text.Wrap - text: page.nvidiaDetector.verificationReport - color: page.theme.textMuted - } - } - } + Button { + text: qsTr("Rescan") + onClicked: { + nvidiaDetector.refresh(); + nvidiaInstaller.refreshProprietaryAgreement(); } + } - Rectangle { - Layout.fillWidth: true - Layout.preferredWidth: 1 - radius: 26 - color: page.theme.card - border.width: 1 - border.color: page.theme.border - implicitHeight: logColumn.implicitHeight + 26 - - ColumnLayout { - id: logColumn - anchors.fill: parent - anchors.margins: 18 - spacing: 12 - - RowLayout { - Layout.fillWidth: true - - Label { - text: qsTr("Operation Log") - font.pixelSize: 21 - font.bold: true - color: page.theme.text - } - - Item { - Layout.fillWidth: true - } - - Label { - text: page.operationsBusy ? qsTr("Running") : qsTr("Idle") - color: page.operationsBusy ? page.theme.warning : page.theme.success - font.bold: true - } - } - - Rectangle { - Layout.fillWidth: true - Layout.fillHeight: true - Layout.minimumHeight: 250 - radius: 18 - color: page.darkMode ? "#0d1523" : "#edf4ff" - border.width: 1 - border.color: page.theme.border + Label { + text: qsTr("Installed NVIDIA version: ") + nvidiaUpdater.currentVersion + visible: nvidiaUpdater.currentVersion.length > 0 + } + } - ScrollView { - anchors.fill: parent - anchors.margins: 1 - clip: true + ScrollView { + Layout.fillWidth: true + Layout.fillHeight: true - TextArea { - id: logArea - readOnly: true - wrapMode: Text.Wrap - color: page.theme.text - selectionColor: page.theme.accentB - selectedTextColor: "#ffffff" - background: null - text: "" - placeholderText: qsTr("Driver actions and backend progress messages will appear here.") - } - } - } - } - } + TextArea { + id: logArea + readOnly: true + wrapMode: Text.Wrap + text: "" } } } Connections { - target: page.nvidiaInstaller + target: nvidiaInstaller function onProgressMessage(message) { - page.appendLog(message) + logArea.append(message); } function onInstallFinished(success, message) { - page.appendLog(message) - page.nvidiaDetector.refresh() - page.nvidiaUpdater.checkForUpdate() - page.nvidiaInstaller.refreshProprietaryAgreement() + logArea.append(message); + nvidiaDetector.refresh(); + nvidiaUpdater.checkForUpdate(); + nvidiaInstaller.refreshProprietaryAgreement(); } function onRemoveFinished(success, message) { - page.appendLog(message) - page.nvidiaDetector.refresh() - page.nvidiaInstaller.refreshProprietaryAgreement() + logArea.append(message); + nvidiaDetector.refresh(); + nvidiaInstaller.refreshProprietaryAgreement(); } } Connections { - target: page.nvidiaUpdater + target: nvidiaUpdater function onProgressMessage(message) { - page.appendLog(message) + logArea.append(message); } function onUpdateFinished(success, message) { - page.appendLog(message) - page.nvidiaDetector.refresh() - page.nvidiaUpdater.checkForUpdate() + logArea.append(message); + nvidiaDetector.refresh(); + nvidiaUpdater.checkForUpdate(); } } Component.onCompleted: { - page.nvidiaDetector.refresh() - page.nvidiaUpdater.checkForUpdate() - page.nvidiaInstaller.refreshProprietaryAgreement() + nvidiaDetector.refresh(); + nvidiaUpdater.checkForUpdate(); + nvidiaInstaller.refreshProprietaryAgreement(); } } diff --git a/src/qml/pages/MonitorPage.qml b/src/qml/pages/MonitorPage.qml index 50f3e96..2fbfa78 100644 --- a/src/qml/pages/MonitorPage.qml +++ b/src/qml/pages/MonitorPage.qml @@ -1,269 +1,152 @@ -import QtQuick 2.15 -import QtQuick.Controls 2.15 -import QtQuick.Layouts 1.15 -import "../components" +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts Item { - id: page - required property var cpuMonitor - required property var gpuMonitor - required property var ramMonitor - required property var theme - property bool darkMode: true - - function memorySummary(used, total, percent) { - return qsTr("%1 / %2 MiB (%3%)").arg(used).arg(total).arg(percent) - } - - ScrollView { + ColumnLayout { anchors.fill: parent - clip: true - - ColumnLayout { - width: parent.width - spacing: 18 - - Rectangle { - Layout.fillWidth: true - radius: 28 - color: Qt.tint(page.theme.panel, page.darkMode ? "#1839a7ff" : "#121677ff") - border.width: 1 - border.color: page.theme.border - implicitHeight: headerColumn.implicitHeight + 28 - - ColumnLayout { - id: headerColumn - anchors.fill: parent - anchors.margins: 20 - spacing: 12 - - Label { - text: qsTr("Live System Telemetry") - font.pixelSize: 30 - font.bold: true - color: page.theme.text - } - - Label { - text: qsTr("A compact overview of CPU, RAM and NVIDIA runtime state. Refresh manually at any time without leaving the dashboard.") - wrapMode: Text.Wrap - Layout.fillWidth: true - color: page.theme.textMuted - } - - RowLayout { - Layout.fillWidth: true - spacing: 14 - - StatCard { - Layout.fillWidth: true - theme: page.theme - title: qsTr("CPU Load") - value: page.cpuMonitor.available ? qsTr("%1%").arg(page.cpuMonitor.usagePercent.toFixed(1)) : qsTr("Unavailable") - subtitle: page.cpuMonitor.temperatureC > 0 ? qsTr("Temperature: %1 C").arg(page.cpuMonitor.temperatureC) : qsTr("Temperature sensor not available") - accentColor: page.theme.accentB - emphasized: true - } + anchors.margins: 20 + spacing: 12 - StatCard { - Layout.fillWidth: true - theme: page.theme - title: qsTr("GPU Load") - value: page.gpuMonitor.available ? qsTr("%1%").arg(page.gpuMonitor.utilizationPercent) : qsTr("Unavailable") - subtitle: page.gpuMonitor.available - ? qsTr("VRAM: %1").arg(page.memorySummary(page.gpuMonitor.memoryUsedMiB, page.gpuMonitor.memoryTotalMiB, page.gpuMonitor.memoryUsagePercent)) - : qsTr("nvidia-smi output could not be read") - accentColor: page.theme.accentA - } + Label { + text: qsTr("System Monitoring") + font.pixelSize: 24 + font.bold: true + } - StatCard { - Layout.fillWidth: true - theme: page.theme - title: qsTr("RAM Usage") - value: page.ramMonitor.available ? qsTr("%1%").arg(page.ramMonitor.usagePercent) : qsTr("Unavailable") - subtitle: page.ramMonitor.available - ? page.memorySummary(page.ramMonitor.usedMiB, page.ramMonitor.totalMiB, page.ramMonitor.usagePercent) - : qsTr("Memory counters are unavailable") - accentColor: page.theme.success - } - } + 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 } - } - RowLayout { - Layout.fillWidth: true - spacing: 18 + Label { + text: cpuMonitor.available ? qsTr("Usage: ") + cpuMonitor.usagePercent.toFixed(1) + "%" : qsTr("CPU data unavailable") + } - Rectangle { + ProgressBar { + from: 0 + to: 100 + value: cpuMonitor.usagePercent Layout.fillWidth: true - Layout.preferredWidth: 1 - radius: 26 - color: page.theme.card - border.width: 1 - border.color: page.theme.border - implicitHeight: cpuColumn.implicitHeight + 26 - - ColumnLayout { - id: cpuColumn - anchors.fill: parent - anchors.margins: 18 - spacing: 12 + } - Label { - text: qsTr("CPU") - font.pixelSize: 21 - font.bold: true - color: page.theme.text - } + Label { + text: qsTr("Temperature: ") + cpuMonitor.temperatureC + " C" + visible: cpuMonitor.temperatureC > 0 + } + } + } - ProgressBar { - Layout.fillWidth: true - from: 0 - to: 100 - value: page.cpuMonitor.usagePercent - } + 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 + } - Label { - text: page.cpuMonitor.available ? qsTr("Usage: %1%").arg(page.cpuMonitor.usagePercent.toFixed(1)) : qsTr("CPU data unavailable") - color: page.theme.textMuted - } + Label { + text: gpuMonitor.available ? (gpuMonitor.gpuName.length > 0 ? gpuMonitor.gpuName : qsTr("NVIDIA GPU")) : qsTr("Failed to read data via nvidia-smi") + } - Label { - text: page.cpuMonitor.temperatureC > 0 ? qsTr("Temperature: %1 C").arg(page.cpuMonitor.temperatureC) : qsTr("No CPU temperature sensor value") - color: page.theme.textMuted - } - } + Label { + text: qsTr("Load: ") + gpuMonitor.utilizationPercent + "%" + visible: gpuMonitor.available } - Rectangle { + ProgressBar { + from: 0 + to: 100 + value: gpuMonitor.utilizationPercent Layout.fillWidth: true - Layout.preferredWidth: 1 - radius: 26 - color: page.theme.card - border.width: 1 - border.color: page.theme.border - implicitHeight: gpuColumn.implicitHeight + 26 - - ColumnLayout { - id: gpuColumn - anchors.fill: parent - anchors.margins: 18 - spacing: 12 - - Label { - text: qsTr("NVIDIA GPU") - font.pixelSize: 21 - font.bold: true - color: page.theme.text - } - - ProgressBar { - Layout.fillWidth: true - from: 0 - to: 100 - value: page.gpuMonitor.utilizationPercent - visible: page.gpuMonitor.available - } + visible: gpuMonitor.available + } - Label { - text: page.gpuMonitor.available ? (page.gpuMonitor.gpuName.length > 0 ? page.gpuMonitor.gpuName : qsTr("Detected NVIDIA GPU")) : qsTr("NVIDIA GPU data unavailable") - wrapMode: Text.Wrap - Layout.fillWidth: true - color: page.theme.textMuted - } + Label { + text: qsTr("VRAM: ") + gpuMonitor.memoryUsedMiB + " / " + gpuMonitor.memoryTotalMiB + " MiB (" + gpuMonitor.memoryUsagePercent + "%)" + visible: gpuMonitor.available && gpuMonitor.memoryTotalMiB > 0 + } - Label { - text: qsTr("Load: %1%").arg(page.gpuMonitor.utilizationPercent) - visible: page.gpuMonitor.available - color: page.theme.textMuted - } + Label { + text: qsTr("Temperature: ") + gpuMonitor.temperatureC + " C" + visible: gpuMonitor.available && gpuMonitor.temperatureC > 0 + } + } + } - Label { - text: qsTr("VRAM: %1").arg(page.memorySummary(page.gpuMonitor.memoryUsedMiB, page.gpuMonitor.memoryTotalMiB, page.gpuMonitor.memoryUsagePercent)) - visible: page.gpuMonitor.available && page.gpuMonitor.memoryTotalMiB > 0 - color: page.theme.textMuted - } + 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 + } - Label { - text: qsTr("Temperature: %1 C").arg(page.gpuMonitor.temperatureC) - visible: page.gpuMonitor.available && page.gpuMonitor.temperatureC > 0 - color: page.theme.textMuted - } - } + Label { + text: ramMonitor.available ? qsTr("Usage: ") + ramMonitor.usedMiB + " / " + ramMonitor.totalMiB + " MiB (" + ramMonitor.usagePercent + "%)" : qsTr("RAM data unavailable") } - Rectangle { + ProgressBar { + from: 0 + to: 100 + value: ramMonitor.usagePercent Layout.fillWidth: true - Layout.preferredWidth: 1 - radius: 26 - color: page.theme.card - border.width: 1 - border.color: page.theme.border - implicitHeight: ramColumn.implicitHeight + 26 - - ColumnLayout { - id: ramColumn - anchors.fill: parent - anchors.margins: 18 - spacing: 12 - - Label { - text: qsTr("RAM") - font.pixelSize: 21 - font.bold: true - color: page.theme.text - } - - ProgressBar { - Layout.fillWidth: true - from: 0 - to: 100 - value: page.ramMonitor.usagePercent - } - - Label { - text: page.ramMonitor.available ? qsTr("Usage: %1").arg(page.memorySummary(page.ramMonitor.usedMiB, page.ramMonitor.totalMiB, page.ramMonitor.usagePercent)) : qsTr("RAM data unavailable") - wrapMode: Text.Wrap - Layout.fillWidth: true - color: page.theme.textMuted - } - } } } + } - Rectangle { - Layout.fillWidth: true - radius: 26 - color: page.theme.card - border.width: 1 - border.color: page.theme.border - implicitHeight: footerRow.implicitHeight + 24 - - RowLayout { - id: footerRow - anchors.fill: parent - anchors.margins: 18 - spacing: 14 - - Label { - text: qsTr("Update interval: %1 ms").arg(page.cpuMonitor.updateInterval) - color: page.theme.textMuted - } - - Item { - Layout.fillWidth: true - } + RowLayout { + spacing: 8 - Button { - text: qsTr("Refresh Telemetry") - onClicked: { - page.cpuMonitor.refresh() - page.gpuMonitor.refresh() - page.ramMonitor.refresh() - } - } + Button { + text: qsTr("Refresh") + onClicked: { + cpuMonitor.refresh(); + gpuMonitor.refresh(); + ramMonitor.refresh(); } } + + Label { + text: qsTr("Refresh interval: ") + cpuMonitor.updateInterval + " ms" + color: "#6d7384" + } } } } diff --git a/src/qml/pages/SettingsPage.qml b/src/qml/pages/SettingsPage.qml index 0b8a87f..3c05876 100644 --- a/src/qml/pages/SettingsPage.qml +++ b/src/qml/pages/SettingsPage.qml @@ -1,176 +1,48 @@ -import QtQuick 2.15 -import QtQuick.Controls 2.15 -import QtQuick.Layouts 1.15 +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts Item { id: settingsPage - required property var theme - required property string themeMode - property bool darkMode: true - property string githubUrl: "" - signal themeModeRequested(string mode) - signal aboutRequested() + property bool darkMode: false - ScrollView { + ColumnLayout { anchors.fill: parent - clip: true + anchors.margins: 20 + spacing: 12 - ColumnLayout { - width: parent.width - spacing: 18 - - Rectangle { - Layout.fillWidth: true - radius: 28 - color: Qt.tint(settingsPage.theme.panel, settingsPage.darkMode ? "#18ff8a3d" : "#121677ff") - border.width: 1 - border.color: settingsPage.theme.border - implicitHeight: heroLayout.implicitHeight + 28 - - ColumnLayout { - id: heroLayout - anchors.fill: parent - anchors.margins: 20 - spacing: 12 - - Label { - text: qsTr("Application Settings") - font.pixelSize: 30 - font.bold: true - color: settingsPage.theme.text - } + Label { + text: qsTr("Settings") + font.pixelSize: 24 + font.bold: true + } - Label { - text: qsTr("Switch between light and dark presentation, inspect app metadata and jump to the repository from a single settings surface.") - wrapMode: Text.Wrap - Layout.fillWidth: true - color: settingsPage.theme.textMuted - } + 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 } - } - - RowLayout { - Layout.fillWidth: true - spacing: 18 - - Rectangle { - Layout.fillWidth: true - Layout.preferredWidth: 1 - radius: 26 - color: settingsPage.theme.card - border.width: 1 - border.color: settingsPage.theme.border - implicitHeight: appearanceColumn.implicitHeight + 26 - - ColumnLayout { - id: appearanceColumn - anchors.fill: parent - anchors.margins: 18 - spacing: 12 - - Label { - text: qsTr("Appearance") - font.pixelSize: 21 - font.bold: true - color: settingsPage.theme.text - } - - Label { - text: qsTr("Choose the application theme. The palette keeps the orange-blue gradient language in both modes.") - wrapMode: Text.Wrap - Layout.fillWidth: true - color: settingsPage.theme.textMuted - } - - RowLayout { - spacing: 10 - Button { - text: qsTr("Light Theme") - highlighted: settingsPage.themeMode === "light" - flat: settingsPage.themeMode !== "light" - onClicked: settingsPage.themeModeRequested("light") - } - - Button { - text: qsTr("Dark Theme") - highlighted: settingsPage.themeMode === "dark" - flat: settingsPage.themeMode !== "dark" - onClicked: settingsPage.themeModeRequested("dark") - } - } - - Rectangle { - Layout.fillWidth: true - radius: 18 - color: settingsPage.theme.cardMuted - implicitHeight: 56 - - Label { - anchors.fill: parent - anchors.margins: 14 - verticalAlignment: Text.AlignVCenter - text: qsTr("Active mode: %1").arg(settingsPage.themeMode === "dark" ? qsTr("Dark") : qsTr("Light")) - color: settingsPage.theme.text - font.bold: true - } - } - } + Label { + text: qsTr("Application: ") + Qt.application.name + " (" + Qt.application.version + ")" } - Rectangle { - Layout.fillWidth: true - Layout.preferredWidth: 1 - radius: 26 - color: settingsPage.theme.card - border.width: 1 - border.color: settingsPage.theme.border - implicitHeight: aboutColumn.implicitHeight + 26 - - ColumnLayout { - id: aboutColumn - anchors.fill: parent - anchors.margins: 18 - spacing: 12 - - Label { - text: qsTr("About & Links") - font.pixelSize: 21 - font.bold: true - color: settingsPage.theme.text - } - - Label { - text: qsTr("Open the about panel for release notes and version details, or jump directly to the GitHub repository.") - wrapMode: Text.Wrap - Layout.fillWidth: true - color: settingsPage.theme.textMuted - } - - Label { - text: qsTr("Application: %1").arg(Qt.application.displayName) - color: settingsPage.theme.textMuted - } - - Label { - text: qsTr("Version: %1").arg(Qt.application.version) - color: settingsPage.theme.textMuted - } - - RowLayout { - spacing: 10 - - Button { - text: qsTr("Open About") - onClicked: settingsPage.aboutRequested() - } - - Button { - text: qsTr("GitHub Repository") - onClicked: Qt.openUrlExternally(settingsPage.githubUrl) - } - } - } + Label { + text: qsTr("Theme: ") + (settingsPage.darkMode ? qsTr("System Dark") : qsTr("System Light")) } } } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 92e324c..af17a3b 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,5 +1,11 @@ find_package(Qt6 REQUIRED COMPONENTS Test) +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}" +) + # ─── Detector Tests ────────────────────────────────────────────────────────── qt_add_executable(test_detector test_detector.cpp @@ -12,6 +18,10 @@ target_include_directories(test_detector PRIVATE ${CMAKE_SOURCE_DIR}/src/backend ) +target_compile_definitions(test_detector PRIVATE + ${RO_CONTROL_TEST_COMPILE_DEFINITIONS} +) + target_link_libraries(test_detector PRIVATE Qt6::Core Qt6::Test @@ -51,6 +61,10 @@ target_include_directories(test_monitor PRIVATE ${CMAKE_SOURCE_DIR}/src/backend ) +target_compile_definitions(test_monitor PRIVATE + ${RO_CONTROL_TEST_COMPILE_DEFINITIONS} +) + target_link_libraries(test_monitor PRIVATE Qt6::Core Qt6::Test @@ -71,9 +85,59 @@ target_include_directories(test_system_integration PRIVATE ${CMAKE_SOURCE_DIR}/src/backend ) +target_compile_definitions(test_system_integration PRIVATE + ${RO_CONTROL_TEST_COMPILE_DEFINITIONS} +) + target_link_libraries(test_system_integration PRIVATE Qt6::Core Qt6::Test ) add_test(NAME test_system_integration COMMAND test_system_integration) + +# ─── Metadata Tests ────────────────────────────────────────────────────────── +qt_add_executable(test_metadata + test_metadata.cpp +) + +target_compile_definitions(test_metadata PRIVATE + ${RO_CONTROL_TEST_COMPILE_DEFINITIONS} + RO_CONTROL_SOURCE_DIR="${CMAKE_SOURCE_DIR}" +) + +target_link_libraries(test_metadata PRIVATE + Qt6::Core + Qt6::Test +) + +add_test(NAME test_metadata COMMAND test_metadata) + +# ─── CLI Tests ─────────────────────────────────────────────────────────────── +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 +) + +target_compile_definitions(test_cli PRIVATE + ${RO_CONTROL_TEST_COMPILE_DEFINITIONS} +) + +target_link_libraries(test_cli PRIVATE + Qt6::Core + Qt6::Test +) + +add_test(NAME test_cli COMMAND test_cli) diff --git a/tests/test_cli.cpp b/tests/test_cli.cpp new file mode 100644 index 0000000..f6fdfff --- /dev/null +++ b/tests/test_cli.cpp @@ -0,0 +1,237 @@ +#include +#include + +#include "cli/cli.h" + +class TestCli : public QObject { + Q_OBJECT + +private slots: + void testHelpOption() { + const auto command = + RoControlCli::parseArguments({QStringLiteral("ro-control"), + QStringLiteral("--help")}, + QStringLiteral("ro-control"), + QStringLiteral("0.1.0"), + QStringLiteral("CLI test")); + QCOMPARE(command.action, RoControlCli::CommandAction::PrintHelp); + QVERIFY(command.payload.contains(QStringLiteral("driver install"))); + QVERIFY(command.payload.contains(QStringLiteral("status [--json]"))); + } + + void testVersionOption() { + const auto command = + RoControlCli::parseArguments({QStringLiteral("ro-control"), + QStringLiteral("--version")}, + QStringLiteral("ro-control"), + QStringLiteral("0.1.0"), + QStringLiteral("CLI test")); + QCOMPARE(command.action, RoControlCli::CommandAction::PrintVersion); + QCOMPARE(command.payload, QStringLiteral("0.1.0")); + } + + void testJsonRequiresDiagnostics() { + const auto command = + RoControlCli::parseArguments({QStringLiteral("ro-control"), + QStringLiteral("--json")}, + QStringLiteral("ro-control"), + QStringLiteral("0.1.0"), + QStringLiteral("CLI test")); + QCOMPARE(command.action, RoControlCli::CommandAction::Invalid); + QVERIFY(command.payload.contains(QStringLiteral("--json"))); + } + + void testStatusTextCommand() { + const auto command = + RoControlCli::parseArguments({QStringLiteral("ro-control"), + QStringLiteral("status")}, + QStringLiteral("ro-control"), + QStringLiteral("0.1.0"), + QStringLiteral("CLI test")); + QCOMPARE(command.action, RoControlCli::CommandAction::PrintStatusText); + } + + void testStatusJsonCommand() { + const auto command = + RoControlCli::parseArguments({QStringLiteral("ro-control"), + QStringLiteral("status"), + QStringLiteral("--json")}, + QStringLiteral("ro-control"), + QStringLiteral("0.1.0"), + QStringLiteral("CLI test")); + QCOMPARE(command.action, RoControlCli::CommandAction::PrintStatusJson); + } + + void testDiagnosticsTextOption() { + const auto command = + RoControlCli::parseArguments({QStringLiteral("ro-control"), + QStringLiteral("diagnostics")}, + QStringLiteral("ro-control"), + QStringLiteral("0.1.0"), + QStringLiteral("CLI test")); + QCOMPARE(command.action, RoControlCli::CommandAction::PrintDiagnosticsText); + } + + void testDiagnosticsJsonOption() { + const auto command = + RoControlCli::parseArguments({QStringLiteral("ro-control"), + QStringLiteral("diagnostics"), + QStringLiteral("--json")}, + QStringLiteral("ro-control"), + QStringLiteral("0.1.0"), + QStringLiteral("CLI test")); + QCOMPARE(command.action, RoControlCli::CommandAction::PrintDiagnosticsJson); + } + + void testLegacyDiagnosticsOptionStillWorks() { + const auto command = + RoControlCli::parseArguments({QStringLiteral("ro-control"), + QStringLiteral("--diagnostics"), + QStringLiteral("--json")}, + QStringLiteral("ro-control"), + QStringLiteral("0.1.0"), + QStringLiteral("CLI test")); + QCOMPARE(command.action, RoControlCli::CommandAction::PrintDiagnosticsJson); + } + + void testDriverInstallDefaultsToProprietary() { + const auto command = + RoControlCli::parseArguments({QStringLiteral("ro-control"), + QStringLiteral("driver"), + QStringLiteral("install")}, + QStringLiteral("ro-control"), + QStringLiteral("0.1.0"), + QStringLiteral("CLI test")); + QCOMPARE(command.action, + RoControlCli::CommandAction::InstallProprietaryDriver); + QCOMPARE(command.acceptLicense, false); + } + + void testDriverInstallOpenSource() { + const auto command = + RoControlCli::parseArguments({QStringLiteral("ro-control"), + QStringLiteral("driver"), + QStringLiteral("install"), + QStringLiteral("--open-source")}, + QStringLiteral("ro-control"), + QStringLiteral("0.1.0"), + QStringLiteral("CLI test")); + QCOMPARE(command.action, + RoControlCli::CommandAction::InstallOpenSourceDriver); + } + + void testDriverInstallAcceptLicense() { + const auto command = + RoControlCli::parseArguments({QStringLiteral("ro-control"), + QStringLiteral("driver"), + QStringLiteral("install"), + QStringLiteral("--proprietary"), + QStringLiteral("--accept-license")}, + QStringLiteral("ro-control"), + QStringLiteral("0.1.0"), + QStringLiteral("CLI test")); + QCOMPARE(command.action, + RoControlCli::CommandAction::InstallProprietaryDriver); + QCOMPARE(command.acceptLicense, true); + } + + void testDriverUpdateCommand() { + const auto command = + RoControlCli::parseArguments({QStringLiteral("ro-control"), + QStringLiteral("driver"), + QStringLiteral("update")}, + QStringLiteral("ro-control"), + QStringLiteral("0.1.0"), + QStringLiteral("CLI test")); + QCOMPARE(command.action, RoControlCli::CommandAction::UpdateDriver); + } + + void testDriverInstallRejectsJson() { + const auto command = + RoControlCli::parseArguments({QStringLiteral("ro-control"), + QStringLiteral("driver"), + QStringLiteral("install"), + QStringLiteral("--json")}, + QStringLiteral("ro-control"), + QStringLiteral("0.1.0"), + QStringLiteral("CLI test")); + QCOMPARE(command.action, RoControlCli::CommandAction::Invalid); + QVERIFY(command.payload.contains(QStringLiteral("--json"))); + } + + void testDriverInstallRejectsConflictingModes() { + const auto command = + RoControlCli::parseArguments({QStringLiteral("ro-control"), + QStringLiteral("driver"), + QStringLiteral("install"), + QStringLiteral("--proprietary"), + QStringLiteral("--open-source")}, + QStringLiteral("ro-control"), + QStringLiteral("0.1.0"), + QStringLiteral("CLI test")); + QCOMPARE(command.action, RoControlCli::CommandAction::Invalid); + QVERIFY(command.payload.contains(QStringLiteral("cannot be used together"))); + } + + void testRenderDiagnosticsText() { + RoControlCli::DiagnosticsSnapshot snapshot; + snapshot.applicationName = QStringLiteral("ro-control"); + snapshot.applicationVersion = QStringLiteral("0.1.0"); + snapshot.locale = QStringLiteral("en_US"); + snapshot.gpuFound = true; + snapshot.gpuName = QStringLiteral("Example GPU"); + snapshot.driverVersion = QStringLiteral("1.2.3"); + snapshot.activeDriver = QStringLiteral("Proprietary"); + snapshot.verificationReport = QStringLiteral("GPU: Example GPU"); + + const QString text = RoControlCli::renderDiagnosticsText(snapshot); + QVERIFY(text.contains(QStringLiteral("application: ro-control"))); + QVERIFY(text.contains(QStringLiteral("driver_version: 1.2.3"))); + QVERIFY(text.contains(QStringLiteral("verification_report:"))); + } + + void testRenderStatusText() { + RoControlCli::DiagnosticsSnapshot snapshot; + snapshot.applicationName = QStringLiteral("ro-control"); + snapshot.applicationVersion = QStringLiteral("0.1.0"); + snapshot.activeDriver = QStringLiteral("Proprietary"); + snapshot.updateAvailable = true; + + const QString text = RoControlCli::renderStatusText(snapshot); + QVERIFY(text.contains(QStringLiteral("application: ro-control"))); + QVERIFY(text.contains(QStringLiteral("active_driver: Proprietary"))); + QVERIFY(text.contains(QStringLiteral("update_available: yes"))); + } + + void testRenderDiagnosticsJsonObject() { + RoControlCli::DiagnosticsSnapshot snapshot; + snapshot.applicationName = QStringLiteral("ro-control"); + snapshot.applicationVersion = QStringLiteral("0.1.0"); + snapshot.gpuFound = true; + snapshot.ramUsagePercent = 42; + + const QJsonObject object = + RoControlCli::renderDiagnosticsJsonObject(snapshot); + QCOMPARE(object.value(QStringLiteral("application")).toString(), + QStringLiteral("ro-control")); + QCOMPARE(object.value(QStringLiteral("gpuFound")).toBool(), true); + QCOMPARE(object.value(QStringLiteral("ramUsagePercent")).toInt(), 42); + } + + void testRenderStatusJsonObject() { + RoControlCli::DiagnosticsSnapshot snapshot; + snapshot.applicationName = QStringLiteral("ro-control"); + snapshot.applicationVersion = QStringLiteral("0.1.0"); + snapshot.updateAvailable = true; + + const QJsonObject object = RoControlCli::renderStatusJsonObject(snapshot); + QCOMPARE(object.value(QStringLiteral("command")).toString(), + QStringLiteral("status")); + QCOMPARE(object.value(QStringLiteral("application")).toString(), + QStringLiteral("ro-control")); + QCOMPARE(object.value(QStringLiteral("updateAvailable")).toBool(), true); + } +}; + +QTEST_MAIN(TestCli) +#include "test_cli.moc" diff --git a/tests/test_detector.cpp b/tests/test_detector.cpp index 93e01b7..b574a71 100644 --- a/tests/test_detector.cpp +++ b/tests/test_detector.cpp @@ -31,16 +31,15 @@ private slots: void testHasNvidiaGpu() { NvidiaDetector detector; - bool result = detector.hasNvidiaGpu(); - Q_UNUSED(result); - QVERIFY(true); + const bool hasGpu = detector.hasNvidiaGpu(); + const auto info = detector.detect(); + QCOMPARE(hasGpu, info.found); } void testIsDriverInstalled() { NvidiaDetector detector; - bool result = detector.isDriverInstalled(); - Q_UNUSED(result); - QVERIFY(true); + const bool installed = detector.isDriverInstalled(); + QCOMPARE(installed, !detector.installedDriverVersion().isEmpty()); } void testInstalledDriverVersion() { @@ -57,6 +56,9 @@ private slots: if (!info.found) { QVERIFY(info.name.isEmpty()); } + if (info.driverVersion.isEmpty()) { + QVERIFY(!detector.isDriverInstalled()); + } } // verificationReport() en azından temel güvenlik bilgisini döndürmeli. @@ -65,8 +67,16 @@ private slots: detector.refresh(); const QString report = detector.verificationReport(); QVERIFY(!report.isEmpty()); + QVERIFY(report.contains(QStringLiteral("GPU: "))); + QVERIFY(report.contains(QStringLiteral("Driver Version: "))); QVERIFY(report.contains(QStringLiteral("Secure Boot"))); } + + void testActiveDriverStringIsNeverEmpty() { + NvidiaDetector detector; + detector.refresh(); + QVERIFY(!detector.activeDriver().trimmed().isEmpty()); + } }; QTEST_MAIN(TestDetector) diff --git a/tests/test_metadata.cpp b/tests/test_metadata.cpp new file mode 100644 index 0000000..9df85ba --- /dev/null +++ b/tests/test_metadata.cpp @@ -0,0 +1,95 @@ +#include +#include +#include + +namespace { + +QString readFile(const QString &relativePath) { + QFile file(QStringLiteral(RO_CONTROL_SOURCE_DIR) + QLatin1Char('/') + + relativePath); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + return {}; + } + + return QString::fromUtf8(file.readAll()); +} + +} // namespace + +class TestMetadata : public QObject { + Q_OBJECT + +private slots: + void testDesktopEntryContainsCoreFields() { + const QString desktop = readFile(QStringLiteral("data/icons/ro-control.desktop")); + QVERIFY(!desktop.isEmpty()); + QVERIFY(desktop.contains(QStringLiteral("[Desktop Entry]"))); + QVERIFY(desktop.contains(QStringLiteral("Exec=ro-control"))); + QVERIFY(desktop.contains(QStringLiteral("Icon=ro-control"))); + QVERIFY(desktop.contains(QStringLiteral("Categories=System;Settings;HardwareSettings;"))); + QVERIFY(desktop.contains(QStringLiteral("Name[tr]=ro-Control"))); + } + + void testAppStreamContainsExpectedIdsAndUrls() { + const QString metainfo = readFile(QStringLiteral("data/icons/ro-control.metainfo.xml")); + QVERIFY(!metainfo.isEmpty()); + QVERIFY(metainfo.contains(QStringLiteral("ro-control.desktop"))); + QVERIFY(metainfo.contains(QStringLiteral("ro-control.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"))); + } + + void testPolicyContainsExpectedActionIds() { + const QString policy = readFile(QStringLiteral("data/polkit/io.github.ProjectRoASD.rocontrol.policy.in")); + QVERIFY(!policy.isEmpty()); + QVERIFY(policy.contains(QStringLiteral("@RO_CONTROL_POLICY_ID@"))); + QVERIFY(policy.contains(QStringLiteral("@RO_CONTROL_HELPER_INSTALL_PATH@"))); + QVERIFY(policy.contains(QStringLiteral("ro-control"))); + } + + void testDesktopAndAppStreamIdsStayAligned() { + const QString desktop = readFile(QStringLiteral("data/icons/ro-control.desktop")); + const QString metainfo = readFile(QStringLiteral("data/icons/ro-control.metainfo.xml")); + + QVERIFY(!desktop.isEmpty()); + QVERIFY(!metainfo.isEmpty()); + + QVERIFY(desktop.contains(QStringLiteral("Exec=ro-control"))); + QVERIFY(metainfo.contains(QStringLiteral("ro-control.desktop"))); + + const QRegularExpression screenshotRe( + QStringLiteral(R"(https://raw\.githubusercontent\.com/.+/docs/screenshots/.+)")); + QVERIFY(screenshotRe.match(metainfo).hasMatch()); + } + + void testCliDocumentationAssetsExist() { + const QString manPage = readFile(QStringLiteral("docs/man/ro-control.1")); + const QString bashCompletion = + readFile(QStringLiteral("data/completions/ro-control.bash")); + const QString zshCompletion = + readFile(QStringLiteral("data/completions/_ro-control")); + const QString fishCompletion = + readFile(QStringLiteral("data/completions/ro-control.fish")); + + QVERIFY(!manPage.isEmpty()); + QVERIFY(manPage.contains(QStringLiteral(".TH RO-CONTROL 1"))); + QVERIFY(manPage.contains(QStringLiteral("driver install"))); + QVERIFY(manPage.contains(QStringLiteral("status"))); + + QVERIFY(!bashCompletion.isEmpty()); + QVERIFY(bashCompletion.contains(QStringLiteral("driver_commands"))); + QVERIFY(bashCompletion.contains(QStringLiteral("deep-clean"))); + + QVERIFY(!zshCompletion.isEmpty()); + QVERIFY(zshCompletion.contains(QStringLiteral("#compdef ro-control"))); + QVERIFY(zshCompletion.contains(QStringLiteral("diagnostics"))); + + QVERIFY(!fishCompletion.isEmpty()); + QVERIFY(fishCompletion.contains(QStringLiteral("complete -c ro-control"))); + QVERIFY(fishCompletion.contains(QStringLiteral("accept-license"))); + } +}; + +QTEST_MAIN(TestMetadata) +#include "test_metadata.moc" diff --git a/tests/test_monitor.cpp b/tests/test_monitor.cpp index 8808fea..c596404 100644 --- a/tests/test_monitor.cpp +++ b/tests/test_monitor.cpp @@ -1,4 +1,3 @@ -#include #include #include "monitor/cpumonitor.h" @@ -11,6 +10,7 @@ class TestMonitor : public QObject { private slots: void testCpuConstruction() { CpuMonitor cpu; + QVERIFY(cpu.running()); cpu.refresh(); QTest::qWait(25); cpu.refresh(); @@ -20,29 +20,60 @@ private slots: QVERIFY(cpu.temperatureC() >= 0); } + void testCpuLifecycleAndInterval() { + CpuMonitor cpu; + const int initialInterval = cpu.updateInterval(); + QVERIFY(initialInterval >= 250); + + cpu.stop(); + QVERIFY(!cpu.running()); + + cpu.setUpdateInterval(200); + QCOMPARE(cpu.updateInterval(), initialInterval); + + cpu.setUpdateInterval(750); + QCOMPARE(cpu.updateInterval(), 750); + + cpu.start(); + QVERIFY(cpu.running()); + } + void testGpuConstruction() { GpuMonitor gpu; + QVERIFY(gpu.running()); gpu.refresh(); - if (QStandardPaths::findExecutable(QStringLiteral("nvidia-smi")) - .isEmpty()) { - QCOMPARE(gpu.available(), false); - QCOMPARE(gpu.temperatureC(), 0); - QCOMPARE(gpu.utilizationPercent(), 0); - QCOMPARE(gpu.memoryUsagePercent(), 0); - return; - } - QVERIFY(gpu.temperatureC() >= 0); QVERIFY(gpu.utilizationPercent() >= 0); QVERIFY(gpu.utilizationPercent() <= 100); QVERIFY(gpu.memoryUsagePercent() >= 0); QVERIFY(gpu.memoryUsagePercent() <= 100); - QVERIFY(!gpu.gpuName().isEmpty()); + QVERIFY(gpu.memoryTotalMiB() >= 0); + QVERIFY(gpu.memoryUsedMiB() >= 0); + QVERIFY(gpu.memoryUsedMiB() <= gpu.memoryTotalMiB() || gpu.memoryTotalMiB() == 0); + } + + void testGpuLifecycleAndInterval() { + GpuMonitor gpu; + const int initialInterval = gpu.updateInterval(); + QVERIFY(initialInterval >= 250); + + gpu.stop(); + QVERIFY(!gpu.running()); + + gpu.setUpdateInterval(200); + QCOMPARE(gpu.updateInterval(), initialInterval); + + gpu.setUpdateInterval(900); + QCOMPARE(gpu.updateInterval(), 900); + + gpu.start(); + QVERIFY(gpu.running()); } void testRamConstruction() { RamMonitor ram; + QVERIFY(ram.running()); ram.refresh(); QVERIFY(ram.usagePercent() >= 0); @@ -51,6 +82,24 @@ private slots: QVERIFY(ram.usedMiB() >= 0); QVERIFY(ram.usedMiB() <= ram.totalMiB() || ram.totalMiB() == 0); } + + void testRamLifecycleAndInterval() { + RamMonitor ram; + const int initialInterval = ram.updateInterval(); + QVERIFY(initialInterval >= 250); + + ram.stop(); + QVERIFY(!ram.running()); + + ram.setUpdateInterval(200); + QCOMPARE(ram.updateInterval(), initialInterval); + + ram.setUpdateInterval(1250); + QCOMPARE(ram.updateInterval(), 1250); + + ram.start(); + QVERIFY(ram.running()); + } }; QTEST_MAIN(TestMonitor) diff --git a/tests/test_system_integration.cpp b/tests/test_system_integration.cpp index b02fe0f..432904d 100644 --- a/tests/test_system_integration.cpp +++ b/tests/test_system_integration.cpp @@ -13,6 +13,37 @@ private slots: CommandRunner runner; const auto result = runner.run(QStringLiteral("true")); QCOMPARE(result.exitCode, 0); + QVERIFY(result.success()); + } + + void testCommandRunnerMissingBinary() { + CommandRunner runner; + const auto result = + runner.run(QStringLiteral("ro-control-command-that-does-not-exist")); + QCOMPARE(result.exitCode, -1); + QVERIFY(result.stderr.contains(QStringLiteral("Executable not found"))); + } + + void testDnfManagerEmptyPackageListsFailFast() { + DnfManager dnf; + + const auto installResult = dnf.installPackages({}); + QCOMPARE(installResult.exitCode, -1); + if (dnf.isAvailable()) { + QVERIFY(installResult.stderr.contains( + QStringLiteral("No packages provided for install"))); + } else { + QVERIFY(installResult.stderr.contains(QStringLiteral("dnf not found"))); + } + + const auto removeResult = dnf.removePackages({}); + QCOMPARE(removeResult.exitCode, -1); + if (dnf.isAvailable()) { + QVERIFY(removeResult.stderr.contains( + QStringLiteral("No packages provided for remove"))); + } else { + QVERIFY(removeResult.stderr.contains(QStringLiteral("dnf not found"))); + } } void testCommandRunnerMissingExecutable() { @@ -53,6 +84,9 @@ private slots: const bool hasPkexec = polkit.isPkexecAvailable(); if (!hasPkexec) { + const auto result = polkit.runPrivileged(QStringLiteral("true")); + QCOMPARE(result.exitCode, -1); + QVERIFY(result.stderr.contains(QStringLiteral("pkexec not found"))); QSKIP("pkexec is not available on this host."); }