diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..1d896f6 --- /dev/null +++ b/.clang-format @@ -0,0 +1,18 @@ +--- +# 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 + +BasedOnStyle: LLVM +Language: Cpp +ColumnLimit: 80 +IndentWidth: 2 +TabWidth: 2 +UseTab: Never +BreakBeforeBraces: Attach +AllowShortFunctionsOnASingleLine: All +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +SortIncludes: true +IncludeBlocks: Preserve +... diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..3f2fd17 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,46 @@ +--- +name: Bug Report +about: Report a bug or unexpected behavior +title: "[BUG] " +labels: bug +assignees: '' +--- + +## Description + +A clear and concise description of the bug. + +## Steps to Reproduce + +1. Go to '...' +2. Click on '...' +3. See error + +## Expected Behavior + +What you expected to happen. + +## Actual Behavior + +What actually happened. + +## Environment + +| Field | Value | +|-------|-------| +| Fedora version | `cat /etc/fedora-release` | +| GPU model | `lspci \| grep -i nvidia` | +| Driver version | `nvidia-smi \| head -1` | +| ro-Control version | (from About page or package info) | +| Desktop session | Wayland / X11 | + +## Logs + +``` +# Paste relevant output from: +# journalctl --user -u ro-control --no-pager -n 50 +``` + +## Additional Context + +Any other context, screenshots, or information that might help. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..0172d45 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,29 @@ +--- +name: Feature Request +about: Suggest a new feature or improvement +title: "[FEAT] " +labels: enhancement +assignees: '' +--- + +## Summary + +A clear and concise description of the feature you'd like to see. + +## Problem / Motivation + +What problem does this feature solve? Why is it needed? + +> Example: "I find it frustrating when I have to open a terminal to check my GPU temperature..." + +## Proposed Solution + +Describe the feature in detail. How should it work? + +## Alternatives Considered + +Have you considered any alternative approaches or workarounds? + +## Additional Context + +Any mockups, screenshots, links to similar implementations, or other context. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..ed1236c --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,54 @@ +name: CI + +on: + push: + branches: [main, dev, feature/**, fix/**] + pull_request: + branches: [main, dev] + +jobs: + build: + name: Build & Test + runs-on: ubuntu-latest + container: + image: fedora:41 + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install dependencies + run: | + dnf install -y \ + cmake \ + extra-cmake-modules \ + gcc-c++ \ + ninja-build \ + qt6-qtbase-devel \ + qt6-qtdeclarative-devel \ + qt6-qtwayland-devel \ + kf6-qqc2-desktop-style \ + polkit-devel \ + clang-tools-extra \ + qt6-qtbase-private-devel + + - name: Configure (CMake) + run: | + cmake -B build \ + -GNinja \ + -DCMAKE_BUILD_TYPE=Release \ + -DBUILD_TESTS=ON + + - name: Build + run: cmake --build build --parallel + + - name: Run tests + run: cd build && ctest --output-on-failure + + - name: Check formatting (clang-format) + run: | + find src \( -name "*.cpp" -o -name "*.h" \) -print0 | \ + xargs -0 clang-format --dry-run --Werror + + - name: Run tests + run: ctest --test-dir build --output-on-failure \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..a2d80bc --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,32 @@ +name: Release + +on: + push: + tags: + - "v*" + workflow_dispatch: + +jobs: + release: + name: Create GitHub Release + runs-on: ubuntu-latest + + permissions: + contents: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - 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} + + - 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 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1341a53 --- /dev/null +++ b/.gitignore @@ -0,0 +1,103 @@ +# Build directories +build/ +build-*/ +cmake-build-*/ +out/ + +# CMake generated files +CMakeCache.txt +CMakeFiles/ +cmake_install.cmake +install_manifest.txt +compile_commands.json +CTestTestfile.cmake +_deps/ + +# Compiled binaries +*.o +*.a +*.so +*.so.* +*.dylib +*.exe +ro-control + +# Qt / MOC generated files +moc_*.cpp +moc_*.h +qrc_*.cpp +ui_*.h +*.qmlc +*.jsc + +# Qt Creator +*.pro.user +*.pro.user.* +*.qbs.user +*.qbs.user.* +CMakeLists.txt.user + +# VS Code +.vscode/ +*.code-workspace + +# CLion / JetBrains +.idea/ +*.iml +*.iws +*.ipr + +# Packaging artifacts +*.rpm +*.deb +*.tar.gz +*.tar.xz +*.AppImage + +# Logs +*.log +logs/ + +# OS files +.DS_Store +Thumbs.db +desktop.ini + +# Temporary files +*.tmp +*.swp +*.swo +*~ +\#*\# + +# Test output +Testing/ +test_results/ + +# Translation compiled files +*.mo + +# Valgrind +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/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..7548c98 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,53 @@ +# Changelog + +All notable changes to ro-Control will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +--- + +## [Unreleased] + +### Added +- NVIDIA detection pipeline with driver/module verification report +- Secure Boot detection and session type (Wayland/X11) detection +- Driver install flows for proprietary and open-source (nouveau) options +- 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 + +### 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 + +### Fixed +- Command execution path preserves stdout reliably +- RPM Fusion URL resolution and repository failure handling improved +- Updater API/header alignment and monitor test compatibility issues resolved +- Repository cleanup for stray macOS metadata files + +--- + + diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..064ad2d --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,131 @@ +cmake_minimum_required(VERSION 3.22) + +project(ro-control + VERSION 0.1.0 + DESCRIPTION "Smart NVIDIA Driver Manager & System Monitor for Linux" + HOMEPAGE_URL "https://github.com/Acik-Kaynak-Gelistirme-Toplulugu/ro-Control" + LANGUAGES CXX +) + +# ─── C++ Standard ──────────────────────────────────────────────────────────── +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +# ─── Build type default ─────────────────────────────────────────────────────── +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Build type" FORCE) +endif() + +# ─── Qt6 Setup ─────────────────────────────────────────────────────────────── +# Qt's MOC, UIC, RCC run automatically +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) +set(CMAKE_AUTOUIC ON) + +find_package(Qt6 REQUIRED COMPONENTS + Core + Quick # Qt Quick / QML engine + QuickControls2 # QML controls (Button, Slider, etc.) + Widgets # QApplication base + DBus # System D-Bus communication +) + +qt_standard_project_setup() + +# ─── Source Files ───────────────────────────────────────────────────────────── +set(BACKEND_SOURCES + src/backend/nvidia/detector.cpp + src/backend/nvidia/installer.cpp + src/backend/nvidia/updater.cpp + src/backend/monitor/gpumonitor.cpp + src/backend/monitor/cpumonitor.cpp + src/backend/monitor/rammonitor.cpp + src/backend/system/polkit.cpp + src/backend/system/dnfmanager.cpp + src/backend/system/commandrunner.cpp +) + +set(APP_SOURCES + src/main.cpp + ${BACKEND_SOURCES} +) + +# ─── QML Resources ──────────────────────────────────────────────────────────── +qt_add_executable(ro-control ${APP_SOURCES}) + +qt_add_qml_module(ro-control + URI "rocontrol" + VERSION 1.0 + QML_FILES + src/qml/Main.qml + src/qml/pages/DriverPage.qml + src/qml/pages/MonitorPage.qml + src/qml/pages/SettingsPage.qml + src/qml/components/StatCard.qml + src/qml/components/SidebarMenu.qml +) + +# ─── 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 + Qt6::Quick + Qt6::QuickControls2 + Qt6::Widgets + Qt6::DBus +) + +# ─── Compiler Warnings ──────────────────────────────────────────────────────── +target_compile_options(ro-control PRIVATE + -Wall + -Wextra + -Wpedantic + $<$:-g -O0> + $<$:-O2> +) + +# ─── Install Targets ────────────────────────────────────────────────────────── +include(GNUInstallDirs) + +install(TARGETS ro-control + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} +) + +install(FILES data/icons/ro-control.desktop + DESTINATION ${CMAKE_INSTALL_DATADIR}/applications +) + +install(DIRECTORY data/icons/ + DESTINATION ${CMAKE_INSTALL_DATADIR}/icons +) + +install(FILES data/icons/ro-control.metainfo.xml + DESTINATION ${CMAKE_INSTALL_DATADIR}/metainfo +) + +install(FILES data/polkit/com.github.AcikKaynakGelistirmeToplulugu.rocontrol.policy + DESTINATION ${CMAKE_INSTALL_DATADIR}/polkit-1/actions +) + +# ─── Tests ─────────────────────────────────────────────────────────────────── +option(BUILD_TESTS "Build unit tests" OFF) + +if(BUILD_TESTS) + enable_testing() + add_subdirectory(tests) +endif() + +# ─── Summary ───────────────────────────────────────────────────────────────── +message(STATUS "") +message(STATUS "ro-control ${PROJECT_VERSION}") +message(STATUS " Build type : ${CMAKE_BUILD_TYPE}") +message(STATUS " C++ std : C++${CMAKE_CXX_STANDARD}") +message(STATUS " Qt version : ${Qt6_VERSION}") +message(STATUS " Install to : ${CMAKE_INSTALL_PREFIX}") +message(STATUS "") diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..7c3d7e2 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,30 @@ +# Code of Conduct + +## Our Pledge + +We as members, contributors, and maintainers pledge to make participation in ro-Control a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity, level of experience, nationality, personal appearance, race, religion, or sexual identity. + +## Our Standards + +**Positive behavior includes:** +- Using welcoming and inclusive language +- Respecting differing viewpoints and experiences +- Gracefully accepting constructive criticism +- Focusing on what is best for the community +- Showing empathy toward other community members + +**Unacceptable behavior includes:** +- Harassment, insults, or derogatory comments +- Public or private harassment of any kind +- Publishing others' private information without permission +- Trolling or deliberately disruptive behavior + +## 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. + +Maintainers have the right to remove, edit, or reject comments, commits, issues, and other contributions that do not align with this Code of Conduct. + +## 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 new file mode 100644 index 0000000..c7f7b08 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,170 @@ +# Contributing to ro-Control + +Thank you for your interest in contributing to ro-Control! This document explains how to get involved. + +--- + +## Table of Contents + +- [Code of Conduct](#code-of-conduct) +- [How to Contribute](#how-to-contribute) +- [Branch Strategy](#branch-strategy) +- [Commit Message Format](#commit-message-format) +- [Development Setup](#development-setup) +- [Pull Request Process](#pull-request-process) +- [Translations](#translations) +- [Reporting Bugs](#reporting-bugs) + +--- + +## Code of Conduct + +Please read and follow our [Code of Conduct](CODE_OF_CONDUCT.md). We are committed to providing a welcoming and respectful environment for everyone. + +--- + +## How to Contribute + +You can contribute in several ways: + +- **Bug reports** — Open an issue using the bug report template +- **Feature requests** — Open an issue using the feature request template +- **Code contributions** — Fix a bug or implement a feature +- **Translations** — Add or improve language support +- **Documentation** — Improve docs, README, or code comments + +--- + +## Branch Strategy + +We use a structured branching model: + +``` +main ← Stable, release-ready code only. Never push directly. +dev ← Active development. All features merge here first. +feature/* ← New features (branched from dev) +fix/* ← Bug fixes (branched from dev) +release/* ← Release preparation (branched from dev) +``` + +**Always branch from `dev`, not `main`.** + +```bash +git checkout dev +git pull origin dev +git checkout -b feature/your-feature-name +``` + +--- + +## Commit Message Format + +We follow [Conventional Commits](https://www.conventionalcommits.org/): + +``` +: +``` + +| Type | When to use | +|------------|------------------------------------------| +| `feat` | New feature | +| `fix` | Bug fix | +| `docs` | Documentation only | +| `refactor` | Code change that doesn't add/fix | +| `test` | Adding or updating tests | +| `chore` | Build system, CI, dependencies | +| `style` | Formatting, whitespace (no logic change) | + +**Examples:** + +``` +feat: add real-time GPU temperature monitoring +fix: crash when no NVIDIA GPU is detected +docs: update build instructions for Fedora 41 +chore: update CMake minimum version to 3.22 +``` + +--- + +## Development Setup + +### Requirements + +| Component | Minimum | +|-----------|---------| +| Fedora | 40+ | +| GCC | 13+ | +| CMake | 3.22+ | +| Qt | 6.6+ | + +### Install Dependencies + +```bash +sudo dnf install cmake extra-cmake-modules gcc-c++ \ + qt6-qtbase-devel \ + qt6-qtdeclarative-devel \ + qt6-qtwayland-devel \ + kf6-qqc2-desktop-style +``` + +### Build + +```bash +git clone https://github.com/Acik-Kaynak-Gelistirme-Toplulugu/ro-Control.git +cd ro-Control +mkdir build && cd build +cmake .. -DCMAKE_BUILD_TYPE=Debug +make -j$(nproc) +./ro-control +``` + +### Before Submitting + +```bash +# Format your code (clang-format) +find src \( -name "*.cpp" -o -name "*.h" \) -print0 | xargs -0 clang-format -i + +# Run tests +cd build && ctest --output-on-failure +``` + +--- + +## Pull Request Process + +1. Fork the repository +2. Create a branch from `dev`: `git checkout -b feature/your-feature` +3. Make your changes with clear commits +4. Push to your fork: `git push origin feature/your-feature` +5. Open a Pull Request targeting the **`dev`** branch (not `main`) +6. Fill in the PR template completely +7. Wait for review — we aim to respond within 72 hours + +**PRs to `main` will be rejected.** All contributions go through `dev` first. + +--- + +## Translations + +Current UI strings are maintained directly in QML/C++ and translated manually (Turkish/English). + +To add a new language now: + +1. Add translated strings in relevant QML/C++ files. +2. Verify layout does not break with longer text. +3. Submit a PR to `dev` with screenshots for changed pages. + +A dedicated Qt Linguist (`.ts/.qm`) localization pipeline is planned for a future release. + +--- + +## Reporting Bugs + +Use the [bug report template](.github/ISSUE_TEMPLATE/bug_report.md) and include: + +- Fedora version (`cat /etc/fedora-release`) +- GPU model (`lspci | grep -i nvidia`) +- Current driver version (`nvidia-smi`) +- Steps to reproduce +- Expected vs actual behavior +- Relevant logs (`journalctl -u ro-control`) diff --git a/README.md b/README.md index c405c3d..b7ae662 100644 --- a/README.md +++ b/README.md @@ -1 +1,134 @@ -# ro-Control \ No newline at end of file +# ro-Control + +
+ +![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/) + +[Features](#features) • [Installation](#installation) • [Building](#building-from-source) • [Contributing](#contributing) • [License](#license) + +[![README in Turkish](https://img.shields.io/badge/README-Türkçe-red?style=flat-square)](README.tr.md) + +
+ +--- + +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. + +## Features + +### 🚀 Driver Management +- **One-click install** — NVIDIA driver setup via RPM Fusion (`akmod-nvidia`) +- **Driver update** — Detect and apply newer driver versions +- **Clean removal** — Remove old driver artifacts to prevent conflicts +- **Secure Boot** — Detection and warnings for unsigned kernel modules + +### 📊 Live System Monitor +- Real-time GPU temperature, load, and VRAM usage +- CPU load and temperature tracking +- RAM usage monitoring +- Color-coded progress indicators + +### 🖥 Display & System +- **Wayland support** — Automatic `nvidia-drm.modeset=1` GRUB configuration +- **Hybrid graphics** — Switch between NVIDIA, Intel, and On-Demand modes +- **PolicyKit integration** — Secure privilege escalation without running as root + +### 🌍 Internationalization +- English and Turkish interface +- Extensible translation system + +## Screenshots + +> Screenshots will be added after the first UI milestone. + +## Installation + +### Fedora (RPM) — Recommended + +Download the latest `.rpm` from [Releases](https://github.com/Acik-Kaynak-Gelistirme-Toplulugu/ro-Control/releases): + +```bash +sudo dnf install ./ro-control-*.rpm +``` + +### Building from Source + +See [docs/BUILDING.md](docs/BUILDING.md) for full instructions. + +**Quick start:** + +```bash +# Install dependencies (Fedora 40+) +sudo dnf install cmake extra-cmake-modules gcc-c++ \ + qt6-qtbase-devel \ + qt6-qtdeclarative-devel \ + qt6-qtwayland-devel \ + kf6-qqc2-desktop-style + +# Clone and build +git clone https://github.com/Acik-Kaynak-Gelistirme-Toplulugu/ro-Control.git +cd ro-Control +mkdir build && cd build +cmake .. -DCMAKE_BUILD_TYPE=Release +make -j$(nproc) + +# Install +sudo make install +``` + +## Project Structure + +``` +ro-Control/ +├── src/ +│ ├── backend/ # C++ business logic +│ │ ├── nvidia/ # Driver detection, install, update +│ │ ├── monitor/ # GPU/CPU/RAM statistics +│ │ └── system/ # Polkit, DNF, command runner +│ ├── qml/ # Qt Quick UI +│ │ ├── pages/ # Main application pages +│ │ └── components/ # Reusable UI components +│ └── main.cpp +├── data/ # Icons, .desktop, PolicyKit, AppStream +├── packaging/rpm/ # Fedora RPM spec +├── docs/ # Architecture and build docs +├── tests/ # Unit tests +└── CMakeLists.txt +``` + +## Contributing + +Contributions are welcome! Please read [CONTRIBUTING.md](CONTRIBUTING.md) before submitting a pull request. + +Quick contribution flow: + +```bash +git checkout dev +git checkout -b feature/your-feature-name +# ... make your changes ... +git commit -m "feat: describe your change" +git push origin feature/your-feature-name +# Open a Pull Request → dev +``` + +## Requirements + +| Component | Minimum Version | +|-----------|----------------| +| Fedora | 40+ | +| Qt | 6.6+ | +| CMake | 3.22+ | +| GCC | 13+ (C++20) | +| GPU | NVIDIA (any) | + +## License + +This project is licensed under the [GNU General Public License v3.0](LICENSE). + diff --git a/README.tr.md b/README.tr.md new file mode 100644 index 0000000..6096ca8 --- /dev/null +++ b/README.tr.md @@ -0,0 +1,133 @@ +# ro-Control + +
+ +![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/) + +[Özellikler](#özellikler) • [Kurulum](#kurulum) • [Derleme](#kaynaktan-derleme) • [Katkı](#katkıda-bulunma) • [Lisans](#lisans) + +[![README in English](https://img.shields.io/badge/README-English-blue?style=flat-square)](README.md) + +
+ +--- + +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. + +## Özellikler + +### 🚀 Sürücü Yönetimi +- **Tek tıkla kurulum** — RPM Fusion üzerinden NVIDIA sürücü kurulumu (`akmod-nvidia`) +- **Sürücü güncelleme** — Yeni sürücü versiyonlarını tespit et ve uygula +- **Temiz kaldırma** — Çakışmaları önlemek için eski sürücü dosyalarını temizle +- **Güvenli Önyükleme** — İmzasız kernel modülleri için tespit ve uyarı + +### 📊 Canlı Sistem Monitörü +- Gerçek zamanlı GPU sıcaklığı, yük ve VRAM kullanımı +- CPU yükü ve sıcaklık takibi +- RAM kullanım izleme +- Renk kodlu ilerleme göstergeleri + +### 🖥 Ekran & Sistem +- **Wayland desteği** — Otomatik `nvidia-drm.modeset=1` GRUB yapılandırması +- **Hibrit grafik** — NVIDIA, Intel ve On-Demand modları arasında geçiş +- **PolicyKit entegrasyonu** — Root olarak çalıştırmadan güvenli yetki yükseltme + +### 🌍 Çok Dil Desteği +- Türkçe ve İngilizce arayüz +- Genişletilebilir çeviri sistemi + +## Ekran Görüntüleri + +> Ekran görüntüleri ilk UI milestone'ından sonra eklenecektir. + +## Kurulum + +### Fedora (RPM) — Önerilen + +[Releases](https://github.com/Acik-Kaynak-Gelistirme-Toplulugu/ro-Control/releases) sayfasından en son `.rpm` dosyasını indirin: + +```bash +sudo dnf install ./ro-control-*.rpm +``` + +### Kaynaktan Derleme + +Tam talimatlar için [docs/BUILDING.md](docs/BUILDING.md) dosyasına bakın. + +**Hızlı başlangıç:** + +```bash +# Bağımlılıkları kur (Fedora 40+) +sudo dnf install cmake extra-cmake-modules gcc-c++ \ + qt6-qtbase-devel \ + qt6-qtdeclarative-devel \ + qt6-qtwayland-devel \ + kf6-qqc2-desktop-style + +# Klonla ve derle +git clone https://github.com/Acik-Kaynak-Gelistirme-Toplulugu/ro-Control.git +cd ro-Control +mkdir build && cd build +cmake .. -DCMAKE_BUILD_TYPE=Release +make -j$(nproc) + +# Yükle +sudo make install +``` + +## Proje Yapısı + +``` +ro-Control/ +├── 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ü +│ │ ├── pages/ # Ana uygulama sayfaları +│ │ └── components/ # Tekrar kullanılabilir UI bileşenleri +│ └── main.cpp +├── data/ # İkonlar, .desktop, PolicyKit, AppStream +├── packaging/rpm/ # Fedora RPM spec +├── docs/ # Mimari ve derleme dökümanları +├── tests/ # Birim testleri +└── CMakeLists.txt +``` + +## Katkıda Bulunma + +Katkılarınızı bekliyoruz! Pull request göndermeden önce lütfen [CONTRIBUTING.md](CONTRIBUTING.md) dosyasını okuyun. + +Hızlı katkı akışı: + +```bash +git checkout dev +git checkout -b feature/ozellik-adi +# ... değişikliklerinizi yapın ... +git commit -m "feat: değişikliğinizi açıklayın" +git push origin feature/ozellik-adi +# Pull Request açın → dev branch'ine +``` + +## Gereksinimler + +| Bileşen | Minimum Versiyon | +|---------|-----------------| +| Fedora | 40+ | +| Qt | 6.6+ | +| CMake | 3.22+ | +| GCC | 13+ (C++20) | +| GPU | NVIDIA (herhangi) | + +## Lisans + +Bu proje [GNU Genel Kamu Lisansı v3.0](LICENSE) ile lisanslanmıştır. \ No newline at end of file diff --git a/data/icons/ro-control.desktop b/data/icons/ro-control.desktop new file mode 100644 index 0000000..fbec20b --- /dev/null +++ b/data/icons/ro-control.desktop @@ -0,0 +1,16 @@ +[Desktop Entry] +Type=Application +Name=ro-Control +Name[tr]=ro-Control +GenericName=NVIDIA Driver Manager +GenericName[tr]=NVIDIA Sürücü Yöneticisi +Comment=Smart NVIDIA driver manager and system monitor for Linux +Comment[tr]=Linux için akıllı NVIDIA sürücü yöneticisi ve sistem monitörü +Exec=ro-control +Icon=ro-control +Terminal=false +Categories=System;Settings;HardwareSettings; +Keywords=nvidia;driver;gpu;monitor;system;fedora; +Keywords[tr]=nvidia;sürücü;gpu;monitör;sistem;fedora; +StartupNotify=true +StartupWMClass=ro-control diff --git a/data/icons/ro-control.metainfo.xml b/data/icons/ro-control.metainfo.xml new file mode 100644 index 0000000..23e070f --- /dev/null +++ b/data/icons/ro-control.metainfo.xml @@ -0,0 +1,26 @@ + + + ro-control.desktop + ro-Control + NVIDIA driver manager and system monitor for Fedora + CC0-1.0 + GPL-3.0-or-later + + +

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

+
+ + ro-control.desktop + + ro-control + + + + Acik Kaynak Gelistirme Toplulugu + + + https://github.com/Acik-Kaynak-Gelistirme-Toplulugu/ro-Control +
diff --git a/data/polkit/com.github.AcikKaynakGelistirmeToplulugu.rocontrol.policy b/data/polkit/com.github.AcikKaynakGelistirmeToplulugu.rocontrol.policy new file mode 100644 index 0000000..c835910 --- /dev/null +++ b/data/polkit/com.github.AcikKaynakGelistirmeToplulugu.rocontrol.policy @@ -0,0 +1,24 @@ + + + + + + ro-Control + https://github.com/Acik-Kaynak-Gelistirme-Toplulugu/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 + + + + diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md new file mode 100644 index 0000000..db53d18 --- /dev/null +++ b/docs/ARCHITECTURE.md @@ -0,0 +1,161 @@ +# Architecture + +ro-Control follows a strict **C++ Backend / QML Frontend** separation. The two layers communicate exclusively through Qt's property and signal/slot system — QML never calls system commands directly. + +--- + +## High-Level Overview + +``` +┌─────────────────────────────────────────────────┐ +│ QML Frontend │ +│ Main.qml · DriverPage · MonitorPage · ... │ +│ (Declarative UI, binds to C++ properties) │ +└───────────────────┬─────────────────────────────┘ + │ Q_PROPERTY / signals / slots +┌───────────────────▼─────────────────────────────┐ +│ C++ Backend │ +│ NvidiaDetector · Installer · GpuMonitor · ... │ +│ (Business logic, system calls, DNF, Polkit) │ +└───────────────────┬─────────────────────────────┘ + │ Shell commands / D-Bus +┌───────────────────▼─────────────────────────────┐ +│ Linux System │ +│ sysfs · nvidia-smi · dnf · pkexec · GRUB │ +└─────────────────────────────────────────────────┘ +``` + +--- + +## Layer Responsibilities + +### QML Frontend (`src/qml/`) + +- Renders the UI using Qt Quick Controls 2 with KDE Plasma styling +- Binds to C++ properties — it reads state but never writes to the system directly +- Emits user actions (button clicks) which trigger C++ slots +- No business logic lives here + +### C++ Backend (`src/backend/`) + +Divided into three modules: + +#### `nvidia/` — Driver Management +| File | Responsibility | +|------|---------------| +| `detector.cpp` | Detect installed GPU, current driver version, kernel module status | +| `installer.cpp` | Install / remove drivers via DNF (`akmod-nvidia`) | +| `updater.cpp` | Check DNF updates for `akmod-nvidia`, trigger update | + +#### `monitor/` — Live Statistics +| File | Responsibility | +|------|---------------| +| `gpumonitor.cpp` | Poll GPU temperature, load, VRAM via `nvidia-smi` or sysfs | +| `cpumonitor.cpp` | Poll CPU load and temperature via `/proc/stat` and hwmon | +| `rammonitor.cpp` | Poll RAM usage via `/proc/meminfo` | + +#### `system/` — System Integration +| File | Responsibility | +|------|---------------| +| `commandrunner.cpp` | Execute shell commands, capture stdout/stderr | +| `dnfmanager.cpp` | Wrap DNF commands for install/remove/update | +| `polkit.cpp` | Privilege escalation via `pkexec` / PolicyKit D-Bus | + +--- + +## C++ ↔ QML Communication + +Qt's `QObject` system is the bridge. A C++ class exposes data to QML via `Q_PROPERTY`: + +```cpp +// C++ side — gpumonitor.h +class GpuMonitor : public QObject { + Q_OBJECT + Q_PROPERTY(int temperature READ temperature NOTIFY temperatureChanged) + Q_PROPERTY(int load READ load NOTIFY loadChanged) + +public: + int temperature() const { return m_temperature; } + int load() const { return m_load; } + +signals: + void temperatureChanged(); + void loadChanged(); + +private: + int m_temperature = 0; + int m_load = 0; +}; +``` + +```qml +// QML side — MonitorPage.qml +Text { + text: GpuMonitor.temperature + "°C" // auto-updates via signal +} +``` + +No manual refresh needed — when C++ emits `temperatureChanged()`, QML re-renders automatically. + +--- + +## Privilege Escalation + +Driver operations (install, remove, GRUB edit) require root. We use **PolicyKit (pkexec)** — never `sudo` in a GUI app. + +``` +User clicks "Install Driver" + │ + ▼ +C++ calls pkexec with a PolicyKit action ID + │ + ▼ +System shows a Plasma authentication dialog + │ + ▼ +Privileged operation runs as root + │ + ▼ +Result emitted back to QML via signal +``` + +The PolicyKit action definition lives in `data/polkit/`. + +--- + +## Build System + +CMake 3.22+ with `qt_add_qml_module` for QML resource embedding. All QML files are compiled into the binary at build time — no loose `.qml` files needed at runtime. + +See [BUILDING.md](BUILDING.md) for full build instructions. + +--- + +## Directory Structure + +``` +src/ +├── backend/ +│ ├── nvidia/ +│ │ ├── detector.h / detector.cpp +│ │ ├── installer.h / installer.cpp +│ │ └── updater.h / updater.cpp +│ ├── monitor/ +│ │ ├── gpumonitor.h / gpumonitor.cpp +│ │ ├── cpumonitor.h / cpumonitor.cpp +│ │ └── rammonitor.h / rammonitor.cpp +│ └── system/ +│ ├── commandrunner.h / commandrunner.cpp +│ ├── dnfmanager.h / dnfmanager.cpp +│ └── polkit.h / polkit.cpp +├── qml/ +│ ├── Main.qml +│ ├── pages/ +│ │ ├── DriverPage.qml +│ │ ├── MonitorPage.qml +│ │ └── SettingsPage.qml +│ └── components/ +│ ├── StatCard.qml +│ └── SidebarMenu.qml +└── main.cpp +``` diff --git a/docs/BUILDING.md b/docs/BUILDING.md new file mode 100644 index 0000000..5919606 --- /dev/null +++ b/docs/BUILDING.md @@ -0,0 +1,149 @@ +# Building ro-Control from Source + +This guide covers building ro-Control on Fedora Linux. Other distributions may work but are not officially supported. + +--- + +## Requirements + +| 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` | +| Ninja | Any | `ninja --version` (optional, faster builds) | + +--- + +## Install Dependencies + +```bash +sudo dnf install \ + cmake \ + extra-cmake-modules \ + gcc-c++ \ + ninja-build \ + qt6-qtbase-devel \ + qt6-qtdeclarative-devel \ + qt6-qtwayland-devel \ + kf6-qqc2-desktop-style \ + polkit-devel +``` + +--- + +## Clone the Repository + +```bash +git clone https://github.com/Acik-Kaynak-Gelistirme-Toplulugu/ro-Control.git +cd ro-Control +``` + +--- + +## Build + +### Debug Build (for development) + +```bash +mkdir build && cd build +cmake .. -DCMAKE_BUILD_TYPE=Debug +make -j$(nproc) +``` + +### Release Build + +```bash +mkdir build && cd build +cmake .. -DCMAKE_BUILD_TYPE=Release +make -j$(nproc) +``` + +### Faster builds with Ninja (optional) + +```bash +mkdir build && cd build +cmake .. -GNinja -DCMAKE_BUILD_TYPE=Debug +ninja +``` + +--- + +## Run + +```bash +# From the build directory +./ro-control +``` + +> **Note:** Driver install/remove operations require PolicyKit authentication. The UI will prompt you automatically. + +--- + +## Install System-Wide + +```bash +cd build +sudo make install +``` + +This installs: +- Binary → `/usr/local/bin/ro-control` +- Desktop entry → `/usr/local/share/applications/` +- Icons → `/usr/local/share/icons/` +- PolicyKit policy → `/usr/share/polkit-1/actions/` + +--- + +## Build with Tests + +```bash +mkdir build && cd build +cmake .. -DBUILD_TESTS=ON +make -j$(nproc) +ctest --output-on-failure +``` + +--- + +## Uninstall + +`make uninstall` target'i su an projede tanimli degil. +Sistemden kaldirmak icin paket yoneticisini veya install manifest'i kullanin. + +--- + +## Common Issues + +**`qt6-qtdeclarative-devel` not found** +```bash +sudo dnf install qt6-qtdeclarative-devel +``` + +**CMake can't find Qt6** +```bash +# Make sure Qt6 is installed, then specify path manually: +cmake .. -DCMAKE_PREFIX_PATH=/usr/lib64/cmake/Qt6 +``` + +**Build fails with C++20 errors** +Ensure GCC 13 or newer is installed: +```bash +sudo dnf install gcc-c++ +gcc --version # Should be 13+ +``` + +--- + +## Contributing + +After making changes, always verify the build passes before submitting a PR: + +```bash +cd build +make -j$(nproc) +ctest --output-on-failure +``` + +See [CONTRIBUTING.md](../CONTRIBUTING.md) for the full contribution guide. diff --git a/docs/DESIGN.md b/docs/DESIGN.md new file mode 100644 index 0000000..a2ef2d9 --- /dev/null +++ b/docs/DESIGN.md @@ -0,0 +1,51 @@ +# Design + +This document captures practical UI/UX decisions for ro-Control so contributors can keep the interface consistent. + +## Product Goals + +- Keep NVIDIA driver actions understandable for non-technical users. +- Surface risky states (for example Secure Boot) before running privileged actions. +- Show system metrics clearly without overwhelming the user. + +## Information Architecture + +- `Surucu` tab: + - NVIDIA detection status + - Active driver and installed version + - Install/update/deep-clean actions + - Session and Secure Boot warnings +- `Izleme` tab: + - CPU usage and temperature + - GPU usage, temperature, and VRAM + - RAM usage +- `Ayarlar` tab: + - General app-level settings and about information + +## Interaction Rules + +- Privileged actions must be explicit and user-initiated by button click. +- Destructive actions (`Deep Clean`) should always log progress messages. +- UI should refresh detector and updater state after install/remove/update events. +- Progress and result messages should be visible in the same screen where action is triggered. + +## Visual Language + +- Use readable spacing and grouped cards for each subsystem. +- Status colors: + - Warning: amber (`#8a6500`) + - Error/risky security state: red (`#c43a3a`) + - Normal/healthy state: green (`#2b8a3e`) +- Prefer explicit labels over icon-only controls. + +## Accessibility and Localization + +- Keep labels short and descriptive for Turkish and English translations. +- Avoid color-only communication; pair color with text. +- Ensure controls remain usable on 980x640 and larger windows. + +## Future Design Backlog + +- Add confirmation dialog before deep clean. +- Add lightweight charts for metric history (CPU/GPU/RAM). +- Add screenshot set for README and AppStream metadata. \ No newline at end of file diff --git a/packaging/rpm/ro-control.spec b/packaging/rpm/ro-control.spec new file mode 100644 index 0000000..4fe4007 --- /dev/null +++ b/packaging/rpm/ro-control.spec @@ -0,0 +1,50 @@ +Name: ro-control +Version: 0.1.0 +Release: 1%{?dist} +Summary: Smart NVIDIA driver manager and system monitor for Fedora + +License: GPL-3.0-or-later +URL: https://github.com/Acik-Kaynak-Gelistirme-Toplulugu/ro-Control +Source0: %{name}-%{version}.tar.gz + +BuildRequires: cmake +BuildRequires: gcc-c++ +BuildRequires: extra-cmake-modules +BuildRequires: qt6-qtbase-devel +BuildRequires: qt6-qtdeclarative-devel +BuildRequires: qt6-qtwayland-devel +BuildRequires: kf6-qqc2-desktop-style +BuildRequires: polkit-devel + +Requires: qt6-qtbase +Requires: qt6-qtdeclarative +Requires: qt6-qtwayland +Requires: kf6-qqc2-desktop-style +Requires: polkit + +%description +ro-Control is a Qt6/KDE Plasma desktop application for Fedora that helps users +manage NVIDIA drivers and monitor core system metrics. + +%prep +%autosetup -n %{name}-%{version} + +%build +%cmake -DBUILD_TESTS=OFF +%cmake_build + +%install +%cmake_install + +%files +%license LICENSE +%doc README.md README.tr.md CHANGELOG.md +%{_bindir}/ro-control +%{_datadir}/applications/ro-control.desktop +%{_datadir}/metainfo/ro-control.metainfo.xml +%{_datadir}/icons/** +%{_datadir}/polkit-1/actions/com.github.AcikKaynakGelistirmeToplulugu.rocontrol.policy + +%changelog +* Fri Mar 06 2026 ro-Control Maintainers - 0.1.0-1 +- Initial RPM packaging spec for Fedora builds diff --git a/src/backend/monitor/cpumonitor.cpp b/src/backend/monitor/cpumonitor.cpp new file mode 100644 index 0000000..9fc81e8 --- /dev/null +++ b/src/backend/monitor/cpumonitor.cpp @@ -0,0 +1,183 @@ +// CPU istatistikleri + +#include "cpumonitor.h" + +#include +#include + +#include + +namespace { + +int readCpuTemperatureC() { + for (int i = 0; i < 32; ++i) { + QFile thermalFile(QString("/sys/class/thermal/thermal_zone%1/temp").arg(i)); + if (!thermalFile.open(QIODevice::ReadOnly | QIODevice::Text)) { + continue; + } + + const QByteArray raw = thermalFile.readAll().trimmed(); + bool ok = false; + const int milliC = raw.toInt(&ok); + if (ok && milliC > 0) { + return milliC / 1000; + } + } + + for (int i = 0; i < 32; ++i) { + QFile hwmonFile(QString("/sys/class/hwmon/hwmon%1/temp1_input").arg(i)); + if (!hwmonFile.open(QIODevice::ReadOnly | QIODevice::Text)) { + continue; + } + + const QByteArray raw = hwmonFile.readAll().trimmed(); + bool ok = false; + const int milliC = raw.toInt(&ok); + if (ok && milliC > 0) { + return milliC / 1000; + } + } + + return 0; +} + +} // namespace + +CpuMonitor::CpuMonitor(QObject *parent) : QObject(parent) { + m_timer.setInterval(1000); + m_timer.setTimerType(Qt::VeryCoarseTimer); + connect(&m_timer, &QTimer::timeout, this, &CpuMonitor::refresh); + + start(); + refresh(); +} + +double CpuMonitor::usagePercent() const { return m_usagePercent; } + +int CpuMonitor::temperatureC() const { return m_temperatureC; } + +bool CpuMonitor::available() const { return m_available; } + +bool CpuMonitor::running() const { return m_timer.isActive(); } + +int CpuMonitor::updateInterval() const { return m_timer.interval(); } + +void CpuMonitor::refresh() { + QFile statFile("/proc/stat"); + if (!statFile.open(QIODevice::ReadOnly | QIODevice::Text)) { + setAvailable(false); + setUsagePercent(0.0); + setTemperatureC(0); + return; + } + + QTextStream stream(&statFile); + const QString firstLine = stream.readLine(); + + if (!firstLine.startsWith("cpu ")) { + setAvailable(false); + return; + } + + const QStringList parts = firstLine.split(' ', Qt::SkipEmptyParts); + if (parts.size() < 8) { + setAvailable(false); + return; + } + + bool ok = true; + const quint64 user = parts.value(1).toULongLong(&ok); + if (!ok) { + setAvailable(false); + return; + } + + const quint64 nice = parts.value(2).toULongLong(&ok); + const quint64 system = parts.value(3).toULongLong(&ok); + const quint64 idle = parts.value(4).toULongLong(&ok); + const quint64 iowait = parts.value(5).toULongLong(&ok); + const quint64 irq = parts.value(6).toULongLong(&ok); + const quint64 softirq = parts.value(7).toULongLong(&ok); + const quint64 steal = parts.size() > 8 ? parts.value(8).toULongLong(&ok) : 0; + + if (!ok) { + setAvailable(false); + return; + } + + const quint64 idleAll = idle + iowait; + const quint64 nonIdle = user + nice + system + irq + softirq + steal; + const quint64 total = idleAll + nonIdle; + + if (m_prevTotal > 0 && total >= m_prevTotal && idleAll >= m_prevIdle) { + const quint64 totalDelta = total - m_prevTotal; + const quint64 idleDelta = idleAll - m_prevIdle; + + if (totalDelta > 0) { + const double value = (static_cast(totalDelta - idleDelta) / + static_cast(totalDelta)) * + 100.0; + setUsagePercent(std::clamp(value, 0.0, 100.0)); + } + } + + m_prevTotal = total; + m_prevIdle = idleAll; + + setTemperatureC(readCpuTemperatureC()); + setAvailable(true); +} + +void CpuMonitor::start() { + if (m_timer.isActive()) { + return; + } + + m_timer.start(); + emit runningChanged(); +} + +void CpuMonitor::stop() { + if (!m_timer.isActive()) { + return; + } + + m_timer.stop(); + emit runningChanged(); +} + +void CpuMonitor::setUpdateInterval(int intervalMs) { + if (intervalMs < 250 || m_timer.interval() == intervalMs) { + return; + } + + m_timer.setInterval(intervalMs); + emit updateIntervalChanged(); +} + +void CpuMonitor::setUsagePercent(double value) { + if (qFuzzyCompare(m_usagePercent, value)) { + return; + } + + m_usagePercent = value; + emit usagePercentChanged(); +} + +void CpuMonitor::setTemperatureC(int value) { + if (m_temperatureC == value) { + return; + } + + m_temperatureC = value; + emit temperatureCChanged(); +} + +void CpuMonitor::setAvailable(bool value) { + if (m_available == value) { + return; + } + + m_available = value; + emit availableChanged(); +} diff --git a/src/backend/monitor/cpumonitor.h b/src/backend/monitor/cpumonitor.h new file mode 100644 index 0000000..589ebe1 --- /dev/null +++ b/src/backend/monitor/cpumonitor.h @@ -0,0 +1,48 @@ +#pragma once + +#include +#include + +// Gercek zamanli CPU istatistikleri +class CpuMonitor : public QObject { + Q_OBJECT + Q_PROPERTY(double usagePercent READ usagePercent NOTIFY usagePercentChanged) + Q_PROPERTY(int temperatureC READ temperatureC NOTIFY temperatureCChanged) + Q_PROPERTY(bool available READ available NOTIFY availableChanged) + Q_PROPERTY(bool running READ running NOTIFY runningChanged) + Q_PROPERTY(int updateInterval READ updateInterval WRITE setUpdateInterval + NOTIFY updateIntervalChanged) + +public: + explicit CpuMonitor(QObject *parent = nullptr); + + double usagePercent() const; + int temperatureC() const; + bool available() const; + bool running() const; + int updateInterval() const; + + Q_INVOKABLE void refresh(); + Q_INVOKABLE void start(); + Q_INVOKABLE void stop(); + void setUpdateInterval(int intervalMs); + +signals: + void usagePercentChanged(); + void temperatureCChanged(); + void availableChanged(); + void runningChanged(); + void updateIntervalChanged(); + +private: + void setUsagePercent(double value); + void setTemperatureC(int value); + void setAvailable(bool value); + + QTimer m_timer; + double m_usagePercent = 0.0; + int m_temperatureC = 0; + bool m_available = false; + quint64 m_prevIdle = 0; + quint64 m_prevTotal = 0; +}; diff --git a/src/backend/monitor/gpumonitor.cpp b/src/backend/monitor/gpumonitor.cpp new file mode 100644 index 0000000..64112f4 --- /dev/null +++ b/src/backend/monitor/gpumonitor.cpp @@ -0,0 +1,184 @@ +#include "gpumonitor.h" +#include "system/commandrunner.h" + +#include + +#include + +GpuMonitor::GpuMonitor(QObject *parent) : QObject(parent) { + m_timer.setInterval(1500); + m_timer.setTimerType(Qt::VeryCoarseTimer); + connect(&m_timer, &QTimer::timeout, this, &GpuMonitor::refresh); + + start(); + refresh(); +} + +bool GpuMonitor::available() const { return m_available; } + +bool GpuMonitor::running() const { return m_timer.isActive(); } + +QString GpuMonitor::gpuName() const { return m_gpuName; } + +int GpuMonitor::temperatureC() const { return m_temperatureC; } + +int GpuMonitor::utilizationPercent() const { return m_utilizationPercent; } + +int GpuMonitor::memoryUsedMiB() const { return m_memoryUsedMiB; } + +int GpuMonitor::memoryTotalMiB() const { return m_memoryTotalMiB; } + +int GpuMonitor::memoryUsagePercent() const { return m_memoryUsagePercent; } + +int GpuMonitor::updateInterval() const { return m_timer.interval(); } + +void GpuMonitor::refresh() { + QProcess process; + process.start("nvidia-smi", + {"--query-gpu=name,temperature.gpu,utilization.gpu,memory.used," + "memory.total", + "--format=csv,noheader,nounits"}); + + if (!process.waitForFinished(1500) || + process.exitStatus() != QProcess::NormalExit || process.exitCode() != 0) { + setAvailable(false); + clearMetrics(); + return; + } + + const QString stdoutText = QString::fromUtf8(process.readAllStandardOutput()); + const QString firstLine = stdoutText.split('\n', Qt::SkipEmptyParts).value(0); + const QStringList fields = firstLine.split(',', Qt::KeepEmptyParts); + + if (fields.size() < 5) { + setAvailable(false); + clearMetrics(); + return; + } + + bool ok = true; + const QString nextName = fields.at(0).trimmed(); + const int nextTemp = fields.at(1).trimmed().toInt(&ok); + if (!ok) { + setAvailable(false); + clearMetrics(); + return; + } + + const int nextUtil = fields.at(2).trimmed().toInt(&ok); + const int nextUsed = fields.at(3).trimmed().toInt(&ok); + const int nextTotal = fields.at(4).trimmed().toInt(&ok); + if (!ok || nextTotal < 0 || nextUsed < 0) { + setAvailable(false); + clearMetrics(); + return; + } + + const int usagePercent = + nextTotal > 0 + ? std::clamp(static_cast((static_cast(nextUsed) / + static_cast(nextTotal)) * + 100.0), + 0, 100) + : 0; + + if (m_gpuName != nextName) { + m_gpuName = nextName; + emit gpuNameChanged(); + } + + if (m_temperatureC != nextTemp) { + m_temperatureC = nextTemp; + emit temperatureCChanged(); + } + + if (m_utilizationPercent != nextUtil) { + m_utilizationPercent = std::clamp(nextUtil, 0, 100); + emit utilizationPercentChanged(); + } + + if (m_memoryUsedMiB != nextUsed) { + m_memoryUsedMiB = nextUsed; + emit memoryUsedMiBChanged(); + } + + if (m_memoryTotalMiB != nextTotal) { + m_memoryTotalMiB = nextTotal; + emit memoryTotalMiBChanged(); + } + + if (m_memoryUsagePercent != usagePercent) { + m_memoryUsagePercent = usagePercent; + emit memoryUsagePercentChanged(); + } + + setAvailable(true); +} + +void GpuMonitor::start() { + if (m_timer.isActive()) { + return; + } + + m_timer.start(); + emit runningChanged(); +} + +void GpuMonitor::stop() { + if (!m_timer.isActive()) { + return; + } + + m_timer.stop(); + emit runningChanged(); +} + +void GpuMonitor::setUpdateInterval(int intervalMs) { + if (intervalMs < 250 || m_timer.interval() == intervalMs) { + return; + } + + m_timer.setInterval(intervalMs); + emit updateIntervalChanged(); +} + +void GpuMonitor::clearMetrics() { + if (!m_gpuName.isEmpty()) { + m_gpuName.clear(); + emit gpuNameChanged(); + } + + if (m_temperatureC != 0) { + m_temperatureC = 0; + emit temperatureCChanged(); + } + + if (m_utilizationPercent != 0) { + m_utilizationPercent = 0; + emit utilizationPercentChanged(); + } + + if (m_memoryUsedMiB != 0) { + m_memoryUsedMiB = 0; + emit memoryUsedMiBChanged(); + } + + if (m_memoryTotalMiB != 0) { + m_memoryTotalMiB = 0; + emit memoryTotalMiBChanged(); + } + + if (m_memoryUsagePercent != 0) { + m_memoryUsagePercent = 0; + emit memoryUsagePercentChanged(); + } +} + +void GpuMonitor::setAvailable(bool value) { + if (m_available == value) { + return; + } + + m_available = value; + emit availableChanged(); +} diff --git a/src/backend/monitor/gpumonitor.h b/src/backend/monitor/gpumonitor.h new file mode 100644 index 0000000..5ebb3ea --- /dev/null +++ b/src/backend/monitor/gpumonitor.h @@ -0,0 +1,64 @@ +#pragma once + +#include +#include + +// Gercek zamanli GPU istatistikleri +class GpuMonitor : public QObject { + Q_OBJECT + Q_PROPERTY(bool available READ available NOTIFY availableChanged) + Q_PROPERTY(bool running READ running NOTIFY runningChanged) + Q_PROPERTY(QString gpuName READ gpuName NOTIFY gpuNameChanged) + Q_PROPERTY(int temperatureC READ temperatureC NOTIFY temperatureCChanged) + Q_PROPERTY(int utilizationPercent READ utilizationPercent NOTIFY + utilizationPercentChanged) + Q_PROPERTY(int memoryUsedMiB READ memoryUsedMiB NOTIFY memoryUsedMiBChanged) + Q_PROPERTY( + int memoryTotalMiB READ memoryTotalMiB NOTIFY memoryTotalMiBChanged) + Q_PROPERTY(int memoryUsagePercent READ memoryUsagePercent NOTIFY + memoryUsagePercentChanged) + Q_PROPERTY(int updateInterval READ updateInterval WRITE setUpdateInterval + NOTIFY updateIntervalChanged) + +public: + explicit GpuMonitor(QObject *parent = nullptr); + + bool available() const; + bool running() const; + QString gpuName() const; + int temperatureC() const; + int utilizationPercent() const; + int memoryUsedMiB() const; + int memoryTotalMiB() const; + int memoryUsagePercent() const; + int updateInterval() const; + + Q_INVOKABLE void refresh(); + Q_INVOKABLE void start(); + Q_INVOKABLE void stop(); + void setUpdateInterval(int intervalMs); + +signals: + void availableChanged(); + void runningChanged(); + void gpuNameChanged(); + void temperatureCChanged(); + void utilizationPercentChanged(); + void memoryUsedMiBChanged(); + void memoryTotalMiBChanged(); + void memoryUsagePercentChanged(); + void updateIntervalChanged(); + +private: + void clearMetrics(); + void setAvailable(bool value); + + QTimer m_timer; + bool m_available = false; + QString m_gpuName; + int m_temperatureC = 0; + int m_utilizationPercent = 0; + int m_memoryUsedMiB = 0; + int m_memoryTotalMiB = 0; + int m_memoryUsagePercent = 0; +}; diff --git a/src/backend/monitor/rammonitor.cpp b/src/backend/monitor/rammonitor.cpp new file mode 100644 index 0000000..f48d5c7 --- /dev/null +++ b/src/backend/monitor/rammonitor.cpp @@ -0,0 +1,152 @@ +#include "rammonitor.h" + +#include +#include + +#include + +RamMonitor::RamMonitor(QObject *parent) : QObject(parent) { + m_timer.setInterval(1000); + m_timer.setTimerType(Qt::VeryCoarseTimer); + connect(&m_timer, &QTimer::timeout, this, &RamMonitor::refresh); + + start(); + refresh(); +} + +bool RamMonitor::available() const { return m_available; } + +bool RamMonitor::running() const { return m_timer.isActive(); } + +int RamMonitor::totalMiB() const { return m_totalMiB; } + +int RamMonitor::usedMiB() const { return m_usedMiB; } + +int RamMonitor::usagePercent() const { return m_usagePercent; } + +int RamMonitor::updateInterval() const { return m_timer.interval(); } + +void RamMonitor::refresh() { + QFile meminfo("/proc/meminfo"); + if (!meminfo.open(QIODevice::ReadOnly | QIODevice::Text)) { + setAvailable(false); + clearMetrics(); + return; + } + + qint64 memTotalKiB = -1; + qint64 memAvailableKiB = -1; + + QTextStream stream(&meminfo); + while (!stream.atEnd()) { + const QString line = stream.readLine(); + + if (line.startsWith("MemTotal:")) { + const QString value = + line.section(':', 1, 1).trimmed().section(' ', 0, 0); + bool ok = false; + memTotalKiB = value.toLongLong(&ok); + if (!ok) { + memTotalKiB = -1; + } + } else if (line.startsWith("MemAvailable:")) { + const QString value = + line.section(':', 1, 1).trimmed().section(' ', 0, 0); + bool ok = false; + memAvailableKiB = value.toLongLong(&ok); + if (!ok) { + memAvailableKiB = -1; + } + } + + if (memTotalKiB >= 0 && memAvailableKiB >= 0) { + break; + } + } + + if (memTotalKiB <= 0 || memAvailableKiB < 0 || + memAvailableKiB > memTotalKiB) { + setAvailable(false); + clearMetrics(); + return; + } + + const qint64 usedKiB = memTotalKiB - memAvailableKiB; + const int nextTotalMiB = static_cast(memTotalKiB / 1024); + const int nextUsedMiB = static_cast(usedKiB / 1024); + const int nextUsagePercent = + std::clamp(static_cast((static_cast(usedKiB) / + static_cast(memTotalKiB)) * + 100.0), + 0, 100); + + if (m_totalMiB != nextTotalMiB) { + m_totalMiB = nextTotalMiB; + emit totalMiBChanged(); + } + + if (m_usedMiB != nextUsedMiB) { + m_usedMiB = nextUsedMiB; + emit usedMiBChanged(); + } + + if (m_usagePercent != nextUsagePercent) { + m_usagePercent = nextUsagePercent; + emit usagePercentChanged(); + } + + setAvailable(true); +} + +void RamMonitor::start() { + if (m_timer.isActive()) { + return; + } + + m_timer.start(); + emit runningChanged(); +} + +void RamMonitor::stop() { + if (!m_timer.isActive()) { + return; + } + + m_timer.stop(); + emit runningChanged(); +} + +void RamMonitor::setUpdateInterval(int intervalMs) { + if (intervalMs < 250 || m_timer.interval() == intervalMs) { + return; + } + + m_timer.setInterval(intervalMs); + emit updateIntervalChanged(); +} + +void RamMonitor::clearMetrics() { + if (m_totalMiB != 0) { + m_totalMiB = 0; + emit totalMiBChanged(); + } + + if (m_usedMiB != 0) { + m_usedMiB = 0; + emit usedMiBChanged(); + } + + if (m_usagePercent != 0) { + m_usagePercent = 0; + emit usagePercentChanged(); + } +} + +void RamMonitor::setAvailable(bool value) { + if (m_available == value) { + return; + } + + m_available = value; + emit availableChanged(); +} diff --git a/src/backend/monitor/rammonitor.h b/src/backend/monitor/rammonitor.h new file mode 100644 index 0000000..0cbecb4 --- /dev/null +++ b/src/backend/monitor/rammonitor.h @@ -0,0 +1,49 @@ +#pragma once + +#include +#include + +// Gercek zamanli RAM istatistikleri +class RamMonitor : public QObject { + Q_OBJECT + Q_PROPERTY(bool available READ available NOTIFY availableChanged) + Q_PROPERTY(bool running READ running NOTIFY runningChanged) + Q_PROPERTY(int totalMiB READ totalMiB NOTIFY totalMiBChanged) + Q_PROPERTY(int usedMiB READ usedMiB NOTIFY usedMiBChanged) + Q_PROPERTY(int usagePercent READ usagePercent NOTIFY usagePercentChanged) + Q_PROPERTY(int updateInterval READ updateInterval WRITE setUpdateInterval + NOTIFY updateIntervalChanged) + +public: + explicit RamMonitor(QObject *parent = nullptr); + + bool available() const; + bool running() const; + int totalMiB() const; + int usedMiB() const; + int usagePercent() const; + int updateInterval() const; + + Q_INVOKABLE void refresh(); + Q_INVOKABLE void start(); + Q_INVOKABLE void stop(); + void setUpdateInterval(int intervalMs); + +signals: + void availableChanged(); + void runningChanged(); + void totalMiBChanged(); + void usedMiBChanged(); + void usagePercentChanged(); + void updateIntervalChanged(); + +private: + void clearMetrics(); + void setAvailable(bool value); + + QTimer m_timer; + bool m_available = false; + int m_totalMiB = 0; + int m_usedMiB = 0; + int m_usagePercent = 0; +}; diff --git a/src/backend/nvidia/detector.cpp b/src/backend/nvidia/detector.cpp new file mode 100644 index 0000000..e7b8a42 --- /dev/null +++ b/src/backend/nvidia/detector.cpp @@ -0,0 +1,168 @@ +#include "detector.h" + +#include "system/commandrunner.h" + +#include +#include +#include +#include + +NvidiaDetector::NvidiaDetector(QObject *parent) : QObject(parent) {} + +NvidiaDetector::GpuInfo NvidiaDetector::detect() const { + GpuInfo info; + + info.name = detectGpuName(); + info.found = !info.name.isEmpty(); + info.driverVersion = detectDriverVersion(); + info.driverLoaded = isModuleLoaded(QStringLiteral("nvidia")); + info.nouveauActive = isModuleLoaded(QStringLiteral("nouveau")); + info.secureBootEnabled = detectSecureBoot(); + info.sessionType = detectSessionType(); + + return info; +} + +bool NvidiaDetector::hasNvidiaGpu() const { return !detectGpuName().isEmpty(); } + +bool NvidiaDetector::isDriverInstalled() const { + return !detectDriverVersion().isEmpty(); +} + +QString NvidiaDetector::installedDriverVersion() const { + return detectDriverVersion(); +} + +QString NvidiaDetector::activeDriver() const { + if (m_info.driverLoaded) + return QStringLiteral("Kapali Kaynak (NVIDIA)"); + if (m_info.nouveauActive) + return QStringLiteral("Acik Kaynak (Nouveau)"); + return QStringLiteral("Yuklu Degil/Bilinmiyor"); +} + +QString NvidiaDetector::verificationReport() const { + 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") + .arg(gpuText, versionText, + m_info.secureBootEnabled ? QStringLiteral("Acik") + : QStringLiteral("Kapali/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")); +} + +void NvidiaDetector::refresh() { + m_info = detect(); + emit infoChanged(); +} + +QString NvidiaDetector::detectGpuName() const { + CommandRunner runner; + + const auto result = + runner.run(QStringLiteral("lspci"), {QStringLiteral("-mm")}); + + if (!result.success()) + return {}; + + const QStringList lines = result.stdout.split(QLatin1Char('\n')); + for (const QString &line : lines) { + if (line.contains(QStringLiteral("NVIDIA"), Qt::CaseInsensitive) && + line.contains(QStringLiteral("VGA"), Qt::CaseInsensitive)) { + static const QRegularExpression re(QStringLiteral("\"([^\"]+)\"")); + auto it = re.globalMatch(line); + QStringList parts; + while (it.hasNext()) + parts << it.next().captured(1); + + if (parts.size() >= 3) + return parts[2]; + } + } + + return {}; +} + +QString NvidiaDetector::detectDriverVersion() const { + CommandRunner runner; + + const auto result = runner.run(QStringLiteral("nvidia-smi"), + {QStringLiteral("--query-gpu=driver_version"), + QStringLiteral("--format=csv,noheader")}); + + if (result.success()) + return result.stdout.trimmed(); + + const auto modinfo = + runner.run(QStringLiteral("modinfo"), {QStringLiteral("nvidia")}); + + if (modinfo.success()) { + static const QRegularExpression re(QStringLiteral("^version:\\s+(.+)$"), + QRegularExpression::MultilineOption); + const auto match = re.match(modinfo.stdout); + if (match.hasMatch()) + return match.captured(1).trimmed(); + } + + return {}; +} + +bool NvidiaDetector::isModuleLoaded(const QString &moduleName) const { + QFile modules(QStringLiteral("/proc/modules")); + if (!modules.open(QIODevice::ReadOnly | QIODevice::Text)) + return false; + + QTextStream stream(&modules); + while (!stream.atEnd()) { + const QString line = stream.readLine(); + if (line.startsWith(moduleName + QLatin1Char(' '))) + return true; + } + + return false; +} + +bool NvidiaDetector::detectSecureBoot() const { + CommandRunner runner; + const auto result = + runner.run(QStringLiteral("mokutil"), {QStringLiteral("--sb-state")}); + + if (result.success() || result.exitCode == 1) { + return result.stdout.contains(QStringLiteral("enabled"), + Qt::CaseInsensitive); + } + + return false; +} + +QString NvidiaDetector::detectSessionType() const { + const QString envType = + qEnvironmentVariable("XDG_SESSION_TYPE").trimmed().toLower(); + if (!envType.isEmpty()) + return envType; + + CommandRunner runner; + const auto loginctl = + runner.run(QStringLiteral("loginctl"), + {QStringLiteral("show-session"), + qEnvironmentVariable("XDG_SESSION_ID"), QStringLiteral("-p"), + QStringLiteral("Type"), QStringLiteral("--value")}); + + if (loginctl.success()) { + const QString type = loginctl.stdout.trimmed().toLower(); + if (!type.isEmpty()) + return type; + } + + return QStringLiteral("unknown"); +} diff --git a/src/backend/nvidia/detector.h b/src/backend/nvidia/detector.h new file mode 100644 index 0000000..9d25b8d --- /dev/null +++ b/src/backend/nvidia/detector.h @@ -0,0 +1,68 @@ +#pragma once + +#include +#include + +// NvidiaDetector: Sistemdeki NVIDIA GPU ve surucu durumunu tespit eder. +class NvidiaDetector : public QObject { + Q_OBJECT + + Q_PROPERTY(bool gpuFound READ gpuFound NOTIFY infoChanged) + Q_PROPERTY(QString gpuName READ gpuName NOTIFY infoChanged) + Q_PROPERTY(QString driverVersion READ driverVersion NOTIFY infoChanged) + Q_PROPERTY(bool driverLoaded READ driverLoaded NOTIFY infoChanged) + Q_PROPERTY(bool nouveauActive READ nouveauActive NOTIFY infoChanged) + Q_PROPERTY(bool secureBootEnabled READ secureBootEnabled NOTIFY infoChanged) + Q_PROPERTY(QString sessionType READ sessionType NOTIFY infoChanged) + Q_PROPERTY(bool waylandSession READ waylandSession NOTIFY infoChanged) + Q_PROPERTY(QString activeDriver READ activeDriver NOTIFY infoChanged) + Q_PROPERTY( + QString verificationReport READ verificationReport NOTIFY infoChanged) + +public: + struct GpuInfo { + bool found = false; + QString name; + QString driverVersion; + QString vbiosVersion; + bool driverLoaded = false; + bool nouveauActive = false; + bool secureBootEnabled = false; + QString sessionType; + }; + + explicit NvidiaDetector(QObject *parent = nullptr); + + bool gpuFound() const { return m_info.found; } + QString gpuName() const { return m_info.name; } + QString driverVersion() const { return m_info.driverVersion; } + bool driverLoaded() const { return m_info.driverLoaded; } + bool nouveauActive() const { return m_info.nouveauActive; } + bool secureBootEnabled() const { return m_info.secureBootEnabled; } + QString sessionType() const { return m_info.sessionType; } + bool waylandSession() const { + return m_info.sessionType.compare(QStringLiteral("wayland"), + Qt::CaseInsensitive) == 0; + } + QString activeDriver() const; + QString verificationReport() const; + + Q_INVOKABLE void refresh(); + + GpuInfo detect() const; + bool hasNvidiaGpu() const; + bool isDriverInstalled() const; + QString installedDriverVersion() const; + +signals: + void infoChanged(); + +private: + QString detectGpuName() const; + QString detectDriverVersion() const; + bool isModuleLoaded(const QString &moduleName) const; + bool detectSecureBoot() const; + QString detectSessionType() const; + + GpuInfo m_info; +}; diff --git a/src/backend/nvidia/installer.cpp b/src/backend/nvidia/installer.cpp new file mode 100644 index 0000000..2811766 --- /dev/null +++ b/src/backend/nvidia/installer.cpp @@ -0,0 +1,280 @@ +#include "installer.h" + +#include "system/commandrunner.h" + +#include + +NvidiaInstaller::NvidiaInstaller(QObject *parent) : QObject(parent) { + refreshProprietaryAgreement(); +} + +void NvidiaInstaller::setProprietaryAgreement(bool required, + const QString &text) { + if (m_proprietaryAgreementRequired == required && + m_proprietaryAgreementText == text) { + return; + } + + m_proprietaryAgreementRequired = required; + m_proprietaryAgreementText = text; + emit proprietaryAgreementChanged(); +} + +void NvidiaInstaller::refreshProprietaryAgreement() { + CommandRunner runner; + const auto info = + runner.run(QStringLiteral("dnf"), + {QStringLiteral("info"), QStringLiteral("akmod-nvidia")}); + + if (!info.success()) { + setProprietaryAgreement(false, QString()); + return; + } + + QString licenseLine; + const QStringList lines = info.stdout.split(QLatin1Char('\n')); + for (const QString &line : lines) { + if (line.startsWith(QStringLiteral("License"), Qt::CaseInsensitive)) { + licenseLine = line; + break; + } + } + + const QString lowered = licenseLine.toLower(); + const bool requiresAgreement = + lowered.contains(QStringLiteral("eula")) || + lowered.contains(QStringLiteral("proprietary")) || + lowered.contains(QStringLiteral("nvidia")); + + if (requiresAgreement) { + setProprietaryAgreement( + true, QStringLiteral( + "Kapali kaynak NVIDIA surucusunu kurmadan once lisans " + "kosullarini kabul etmeniz gerekir. Tespit edilen lisans: %1") + .arg(licenseLine.isEmpty() ? QStringLiteral("Bilinmiyor") + : licenseLine)); + return; + } + + setProprietaryAgreement(false, QString()); +} + +void NvidiaInstaller::install() { installProprietary(false); } + +void NvidiaInstaller::installProprietary(bool agreementAccepted) { + refreshProprietaryAgreement(); + + if (m_proprietaryAgreementRequired && !agreementAccepted) { + emit installFinished( + false, + QStringLiteral("Kurulumdan once lisans/sozlesme onayi gereklidir.")); + return; + } + + CommandRunner runner; + + connect(&runner, &CommandRunner::outputLine, this, + &NvidiaInstaller::progressMessage); + + emit progressMessage(QStringLiteral("RPM Fusion deposu kontrol ediliyor...")); + + CommandRunner rpmRunner; + const auto fedoraResult = rpmRunner.run( + QStringLiteral("rpm"), {QStringLiteral("-E"), QStringLiteral("%fedora")}); + + const QString fedoraVersion = fedoraResult.stdout.trimmed(); + if (fedoraVersion.isEmpty()) { + emit installFinished(false, + QStringLiteral("Fedora surumu tespit edilemedi.")); + 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, QStringLiteral("RPM Fusion repo eklenemedi: ") + + result.stderr); + return; + } + + emit progressMessage(QStringLiteral( + "Kapali kaynak NVIDIA surucusu kuruluyor (akmod-nvidia)...")); + + result = runner.runAsRoot(QStringLiteral("dnf"), + {QStringLiteral("install"), QStringLiteral("-y"), + QStringLiteral("akmod-nvidia")}); + + if (!result.success()) { + emit installFinished(false, + QStringLiteral("Kurulum basarisiz: ") + result.stderr); + return; + } + + emit progressMessage( + QStringLiteral("Kernel modulu derleniyor (akmods --force)...")); + runner.runAsRoot(QStringLiteral("akmods"), {QStringLiteral("--force")}); + + const QString sessionType = detectSessionType(); + QString sessionError; + if (!applySessionSpecificSetup(runner, sessionType, &sessionError)) { + emit installFinished(false, sessionError); + return; + } + + emit installFinished( + true, QStringLiteral("Kapali kaynak NVIDIA surucusu basariyla kuruldu. " + "Lutfen sistemi yeniden baslatin.")); +} + +void NvidiaInstaller::installOpenSource() { + CommandRunner runner; + + connect(&runner, &CommandRunner::outputLine, this, + &NvidiaInstaller::progressMessage); + + emit progressMessage( + QStringLiteral("Acik kaynak surucuye gecis baslatiliyor...")); + + // Once kapali kaynak paketleri kaldir. + auto result = runner.runAsRoot( + QStringLiteral("dnf"), + {QStringLiteral("remove"), QStringLiteral("-y"), + QStringLiteral("akmod-nvidia"), QStringLiteral("xorg-x11-drv-nvidia*")}); + + if (!result.success()) { + emit installFinished( + false, QStringLiteral("Kapali kaynak paket kaldirma basarisiz: ") + + 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, QStringLiteral("Acik kaynak surucu kurulumu basarisiz: ") + + result.stderr); + return; + } + + runner.runAsRoot(QStringLiteral("dracut"), {QStringLiteral("--force")}); + + emit installFinished(true, + QStringLiteral("Acik kaynak surucu (Nouveau) kuruldu. " + "Lutfen sistemi yeniden baslatin.")); +} + +void NvidiaInstaller::remove() { + CommandRunner runner; + connect(&runner, &CommandRunner::outputLine, this, + &NvidiaInstaller::progressMessage); + + emit progressMessage(QStringLiteral("NVIDIA surucusu kaldiriliyor...")); + + 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() + ? QStringLiteral("Surucu basariyla kaldirildi.") + : QStringLiteral("Kaldirma basarisiz: ") + + result.stderr); +} + +void NvidiaInstaller::deepClean() { + CommandRunner runner; + connect(&runner, &CommandRunner::outputLine, this, + &NvidiaInstaller::progressMessage); + + emit progressMessage( + QStringLiteral("Eski surucu kalintilari temizleniyor...")); + + runner.runAsRoot(QStringLiteral("dnf"), + {QStringLiteral("remove"), QStringLiteral("-y"), + QStringLiteral("*nvidia*"), QStringLiteral("*akmod*")}); + + runner.runAsRoot(QStringLiteral("dnf"), + {QStringLiteral("clean"), QStringLiteral("all")}); + + emit progressMessage(QStringLiteral("Deep clean tamamlandi.")); +} + +QString NvidiaInstaller::detectSessionType() const { + const QString envType = + qEnvironmentVariable("XDG_SESSION_TYPE").trimmed().toLower(); + if (!envType.isEmpty()) + return envType; + + CommandRunner runner; + const auto loginctl = + runner.run(QStringLiteral("loginctl"), + {QStringLiteral("show-session"), + qEnvironmentVariable("XDG_SESSION_ID"), QStringLiteral("-p"), + QStringLiteral("Type"), QStringLiteral("--value")}); + + if (loginctl.success()) { + const QString type = loginctl.stdout.trimmed().toLower(); + if (!type.isEmpty()) + return type; + } + + return QStringLiteral("unknown"); +} + +bool NvidiaInstaller::applySessionSpecificSetup(CommandRunner &runner, + const QString &sessionType, + QString *errorMessage) { + if (sessionType == QStringLiteral("wayland")) { + emit progressMessage(QStringLiteral( + "Wayland tespit edildi: nvidia-drm.modeset=1 ayari uygulaniyor...")); + + const auto result = + runner.runAsRoot(QStringLiteral("grubby"), + {QStringLiteral("--update-kernel=ALL"), + QStringLiteral("--args=nvidia-drm.modeset=1")}); + + if (!result.success()) { + if (errorMessage) { + *errorMessage = + QStringLiteral("Wayland icin kernel parametresi uygulanamadi: ") + + result.stderr; + } + return false; + } + return true; + } + + if (sessionType == QStringLiteral("x11")) { + emit progressMessage( + QStringLiteral("X11 tespit edildi: X11 NVIDIA userspace paketleri " + "kontrol ediliyor...")); + + const auto result = runner.runAsRoot( + QStringLiteral("dnf"), {QStringLiteral("install"), QStringLiteral("-y"), + QStringLiteral("xorg-x11-drv-nvidia")}); + + if (!result.success()) { + if (errorMessage) { + *errorMessage = QStringLiteral("X11 NVIDIA paketi kurulurken hata: ") + + result.stderr; + } + return false; + } + } + + return true; +} diff --git a/src/backend/nvidia/installer.h b/src/backend/nvidia/installer.h new file mode 100644 index 0000000..3684825 --- /dev/null +++ b/src/backend/nvidia/installer.h @@ -0,0 +1,65 @@ +#pragma once + +#include +#include + +class CommandRunner; + +// NvidiaInstaller: DNF üzerinden NVIDIA sürücü kurulum/kaldırma işlemleri. +// Tüm işlemler root gerektirir — polkit üzerinden yetki alınır. +class NvidiaInstaller : public QObject { + Q_OBJECT + + Q_PROPERTY(bool proprietaryAgreementRequired READ proprietaryAgreementRequired + NOTIFY proprietaryAgreementChanged) + Q_PROPERTY(QString proprietaryAgreementText READ proprietaryAgreementText + NOTIFY proprietaryAgreementChanged) + +public: + explicit NvidiaInstaller(QObject *parent = nullptr); + + bool proprietaryAgreementRequired() const { + return m_proprietaryAgreementRequired; + } + QString proprietaryAgreementText() const { + return m_proprietaryAgreementText; + } + + // Sozlesme durumunu yeniden kontrol et + Q_INVOKABLE void refreshProprietaryAgreement(); + + // Kapali kaynak kurulum (kullanici onayi bilgisiyle) + Q_INVOKABLE void installProprietary(bool agreementAccepted); + + // Acik kaynak surucuye gecis/kurulum + Q_INVOKABLE void installOpenSource(); + + // Sürücüyü kur (akmod-nvidia) + Q_INVOKABLE void install(); + + // Sürücüyü kaldır + Q_INVOKABLE void remove(); + + // Eski sürücü kalıntılarını temizle + Q_INVOKABLE void deepClean(); + +signals: + // İşlem adımları — QML'e ilerleme göstermek için + void progressMessage(const QString &message); + + void proprietaryAgreementChanged(); + + // İşlem tamamlandı + void installFinished(bool success, const QString &message); + void removeFinished(bool success, const QString &message); + +private: + void setProprietaryAgreement(bool required, const QString &text); + QString detectSessionType() const; + bool applySessionSpecificSetup(CommandRunner &runner, + const QString &sessionType, + QString *errorMessage); + + bool m_proprietaryAgreementRequired = false; + QString m_proprietaryAgreementText; +}; diff --git a/src/backend/nvidia/updater.cpp b/src/backend/nvidia/updater.cpp new file mode 100644 index 0000000..08d502a --- /dev/null +++ b/src/backend/nvidia/updater.cpp @@ -0,0 +1,106 @@ +#include "updater.h" +#include "detector.h" +#include "system/commandrunner.h" + +#include +#include + +NvidiaUpdater::NvidiaUpdater(QObject *parent) : QObject(parent) {} + +void NvidiaUpdater::checkForUpdate() { + // Mevcut kurulu sürücü versiyonu + NvidiaDetector detector; + const QString current = detector.installedDriverVersion(); + + if (current != m_currentVersion) { + m_currentVersion = current; + emit currentVersionChanged(); + } + + if (m_currentVersion.isEmpty()) { + // Sürücü kurulu değil — güncelleme kontrolü anlamsız + if (m_updateAvailable) { + m_updateAvailable = false; + emit updateAvailableChanged(); + } + return; + } + + // DNF'den güncelleme bilgisi al + CommandRunner runner; + + const auto result = + runner.run(QStringLiteral("dnf"), {QStringLiteral("check-update"), + QStringLiteral("akmod-nvidia")}); + + // dnf check-update: exit code 100 = güncelleme var, 0 = yok + if (result.exitCode == 100) { + // Çıktıdan versiyon numarasını parse et + // Format: "akmod-nvidia.x86_64 3:560.35.03-1.fc41 + // rpmfusion-nonfree-updates" + static const QRegularExpression re( + QStringLiteral(R"(akmod-nvidia\S*\s+\S*:?(\d+\.\d+[\.\d]*)\S*)")); + + const auto match = re.match(result.stdout); + const QString latest = match.hasMatch() ? match.captured(1) : QString(); + + if (!latest.isEmpty() && latest != m_latestVersion) { + m_latestVersion = latest; + emit latestVersionChanged(); + } + + if (!m_updateAvailable) { + m_updateAvailable = true; + emit updateAvailableChanged(); + } + } else { + if (m_updateAvailable) { + m_updateAvailable = false; + emit updateAvailableChanged(); + } + } +} + +void NvidiaUpdater::applyUpdate() { + CommandRunner runner; + + connect(&runner, &CommandRunner::outputLine, this, + &NvidiaUpdater::progressMessage); + + emit progressMessage(QStringLiteral("NVIDIA sürücüsü güncelleniyor...")); + + const auto result = runner.runAsRoot( + QStringLiteral("dnf"), {QStringLiteral("update"), QStringLiteral("-y"), + QStringLiteral("akmod-nvidia")}); + + if (!result.success()) { + emit updateFinished(false, QStringLiteral("Güncelleme başarısız: ") + + result.stderr); + return; + } + + emit progressMessage(QStringLiteral("Kernel modülü yeniden derleniyor...")); + + runner.runAsRoot(QStringLiteral("akmods"), {QStringLiteral("--force")}); + + const QString sessionType = + qEnvironmentVariable("XDG_SESSION_TYPE").trimmed().toLower(); + if (sessionType == QStringLiteral("wayland")) { + emit progressMessage(QStringLiteral( + "Wayland tespit edildi: nvidia-drm.modeset=1 ayari guncelleniyor...")); + runner.runAsRoot(QStringLiteral("grubby"), + {QStringLiteral("--update-kernel=ALL"), + QStringLiteral("--args=nvidia-drm.modeset=1")}); + } + + // Güncelleme sonrası durumu yenile + m_updateAvailable = false; + emit updateAvailableChanged(); + + checkForUpdate(); + + emit updateFinished( + true, + QStringLiteral( + "Sürücü başarıyla güncellendi. Lütfen sistemi yeniden başlatın.")); +} diff --git a/src/backend/nvidia/updater.h b/src/backend/nvidia/updater.h new file mode 100644 index 0000000..54471a0 --- /dev/null +++ b/src/backend/nvidia/updater.h @@ -0,0 +1,38 @@ +#pragma once + +#include +#include + +// NvidiaUpdater: Kurulu surucu ile mevcut en guncel surumu karsilastirir. +class NvidiaUpdater : public QObject { + Q_OBJECT + + Q_PROPERTY( + bool updateAvailable READ updateAvailable NOTIFY updateAvailableChanged) + Q_PROPERTY( + QString currentVersion READ currentVersion NOTIFY currentVersionChanged) + Q_PROPERTY( + QString latestVersion READ latestVersion NOTIFY latestVersionChanged) + +public: + explicit NvidiaUpdater(QObject *parent = nullptr); + + bool updateAvailable() const { return m_updateAvailable; } + QString currentVersion() const { return m_currentVersion; } + QString latestVersion() const { return m_latestVersion; } + + Q_INVOKABLE void checkForUpdate(); + Q_INVOKABLE void applyUpdate(); + +signals: + void updateAvailableChanged(); + void currentVersionChanged(); + void latestVersionChanged(); + void progressMessage(const QString &message); + void updateFinished(bool success, const QString &message); + +private: + bool m_updateAvailable = false; + QString m_currentVersion; + QString m_latestVersion; +}; diff --git a/src/backend/system/commandrunner.cpp b/src/backend/system/commandrunner.cpp new file mode 100644 index 0000000..399c291 --- /dev/null +++ b/src/backend/system/commandrunner.cpp @@ -0,0 +1,47 @@ +#include "commandrunner.h" + +#include + +CommandRunner::CommandRunner(QObject *parent) : QObject(parent) {} + +CommandRunner::Result CommandRunner::run(const QString &program, + const QStringList &args) { + QProcess process; + QByteArray stdoutBuffer; + + // Stdout'u anlik olarak yayinla ve sonucu korumak icin buffer'a biriktir. + connect(&process, &QProcess::readyReadStandardOutput, this, [&]() { + const QByteArray chunk = process.readAllStandardOutput(); + stdoutBuffer.append(chunk); + + const QString line = QString::fromUtf8(chunk).trimmed(); + if (!line.isEmpty()) + emit outputLine(line); + }); + + process.start(program, args); + + if (!process.waitForStarted(3000)) { + return Result{ + .exitCode = -1, + .stdout = {}, + .stderr = QStringLiteral("Failed to start: %1").arg(program), + }; + } + + process.waitForFinished(-1); + stdoutBuffer.append(process.readAllStandardOutput()); + + return Result{ + .exitCode = process.exitCode(), + .stdout = QString::fromUtf8(stdoutBuffer), + .stderr = QString::fromUtf8(process.readAllStandardError()), + }; +} + +CommandRunner::Result CommandRunner::runAsRoot(const QString &program, + const QStringList &args) { + QStringList pkexecArgs; + pkexecArgs << program << args; + return run(QStringLiteral("pkexec"), pkexecArgs); +} diff --git a/src/backend/system/commandrunner.h b/src/backend/system/commandrunner.h new file mode 100644 index 0000000..58f435c --- /dev/null +++ b/src/backend/system/commandrunner.h @@ -0,0 +1,31 @@ +#pragma once + +#include +#include +#include + +// CommandRunner: Tüm backend modüllerinin kullandığı shell komut çalıştırıcı. +// Hiçbir modül doğrudan sistem çağrısı yapmaz — hepsi bu sınıfı kullanır. +class CommandRunner : public QObject { + Q_OBJECT + +public: + struct Result { + int exitCode; + QString stdout; + QString stderr; + bool success() const { return exitCode == 0; } + }; + + explicit CommandRunner(QObject *parent = nullptr); + + // Bloklayan komut — sonuç dönene kadar bekler + Result run(const QString &program, const QStringList &args = {}); + + // Root gerektiren komut — pkexec ile çalıştırır + Result runAsRoot(const QString &program, const QStringList &args = {}); + +signals: + // Uzun işlemler için anlık çıktı (DNF install vb.) + void outputLine(const QString &line); +}; diff --git a/src/backend/system/dnfmanager.cpp b/src/backend/system/dnfmanager.cpp new file mode 100644 index 0000000..d014183 --- /dev/null +++ b/src/backend/system/dnfmanager.cpp @@ -0,0 +1,98 @@ +#include "dnfmanager.h" +#include "commandrunner.h" + +DnfManager::DnfManager(QObject *parent) : QObject(parent) {} + +bool DnfManager::isInstalled(const QString &packageName) const { + CommandRunner runner; + + const auto result = + runner.run(QStringLiteral("rpm"), {QStringLiteral("-q"), packageName}); + + return result.success(); +} + +bool DnfManager::install(const QString &packageName) { + return install(QStringList{packageName}); +} + +bool DnfManager::install(const QStringList &packages) { + CommandRunner runner; + + connect(&runner, &CommandRunner::outputLine, this, &DnfManager::outputLine); + + QStringList args; + args << QStringLiteral("install") << QStringLiteral("-y") << packages; + + const auto result = runner.runAsRoot(QStringLiteral("dnf"), args); + return result.success(); +} + +bool DnfManager::remove(const QString &packageName) { + return remove(QStringList{packageName}); +} + +bool DnfManager::remove(const QStringList &packages) { + CommandRunner runner; + + connect(&runner, &CommandRunner::outputLine, this, &DnfManager::outputLine); + + QStringList args; + args << QStringLiteral("remove") << QStringLiteral("-y") << packages; + + const auto result = runner.runAsRoot(QStringLiteral("dnf"), args); + return result.success(); +} + +bool DnfManager::enableRepo(const QString &repoUrl) { + CommandRunner runner; + + connect(&runner, &CommandRunner::outputLine, this, &DnfManager::outputLine); + + const auto result = + runner.runAsRoot(QStringLiteral("dnf"), {QStringLiteral("install"), + QStringLiteral("-y"), repoUrl}); + + return result.success(); +} + +DnfManager::PackageInfo +DnfManager::queryPackage(const QString &packageName) const { + CommandRunner runner; + + // rpm -qi ile paket bilgisini al + const auto result = + runner.run(QStringLiteral("rpm"), {QStringLiteral("-qi"), packageName}); + + PackageInfo info; + if (!result.success()) + return info; + + info.name = packageName; + + const QStringList lines = result.stdout.split(QLatin1Char('\n')); + for (const QString &line : lines) { + if (line.startsWith(QStringLiteral("Version"))) { + const int colon = line.indexOf(QLatin1Char(':')); + if (colon >= 0) + info.version = line.mid(colon + 1).trimmed(); + } else if (line.startsWith(QStringLiteral("Summary"))) { + const int colon = line.indexOf(QLatin1Char(':')); + if (colon >= 0) + info.summary = line.mid(colon + 1).trimmed(); + } + } + + return info; +} + +bool DnfManager::cleanCache() { + CommandRunner runner; + + connect(&runner, &CommandRunner::outputLine, this, &DnfManager::outputLine); + + const auto result = runner.runAsRoot( + QStringLiteral("dnf"), {QStringLiteral("clean"), QStringLiteral("all")}); + + return result.success(); +} diff --git a/src/backend/system/dnfmanager.h b/src/backend/system/dnfmanager.h new file mode 100644 index 0000000..6256374 --- /dev/null +++ b/src/backend/system/dnfmanager.h @@ -0,0 +1,45 @@ +#pragma once + +#include +#include +#include + +class CommandRunner; + +// DnfManager: DNF paket yöneticisi üzerinden kurulum, kaldırma ve sorgu +// işlemleri. Tüm DNF komutları CommandRunner üzerinden çalıştırılır. +class DnfManager : public QObject { + Q_OBJECT + +public: + struct PackageInfo { + QString name; + QString version; + QString summary; + }; + + explicit DnfManager(QObject *parent = nullptr); + + // Paket kurulu mu? + bool isInstalled(const QString &packageName) const; + + // Paket kur (root gerektirir) + bool install(const QString &packageName); + bool install(const QStringList &packages); + + // Paket kaldır (root gerektirir) + bool remove(const QString &packageName); + bool remove(const QStringList &packages); + + // Repo etkinleştir + bool enableRepo(const QString &repoUrl); + + // Mevcut paket bilgisini al + PackageInfo queryPackage(const QString &packageName) const; + + // DNF cache temizle (root gerektirir) + bool cleanCache(); + +signals: + void outputLine(const QString &line); +}; diff --git a/src/backend/system/polkit.cpp b/src/backend/system/polkit.cpp new file mode 100644 index 0000000..b3d16f9 --- /dev/null +++ b/src/backend/system/polkit.cpp @@ -0,0 +1,92 @@ +#include "polkit.h" + +#include +#include +#include +#include +#include + +#include // getpid() + +PolkitHelper::PolkitHelper(QObject *parent) : QObject(parent) {} + +bool PolkitHelper::checkAuthorization() { + QDBusInterface polkit(QStringLiteral("org.freedesktop.PolicyKit1"), + QStringLiteral("/org/freedesktop/PolicyKit1/Authority"), + QStringLiteral("org.freedesktop.PolicyKit1.Authority"), + QDBusConnection::systemBus()); + + if (!polkit.isValid()) + return false; + + // Subject: mevcut prosesin PID'si + QVariantMap subject; + subject[QStringLiteral("pid")] = static_cast(getpid()); + subject[QStringLiteral("start-time")] = static_cast(0); + + QDBusArgument subjectArg; + subjectArg.beginStructure(); + subjectArg << QStringLiteral("unix-process") << subject; + subjectArg.endStructure(); + + // CheckAuthorization çağrısı — interactivity 0 = dialog gösterme + QDBusReply reply = polkit.call( + QStringLiteral("CheckAuthorization"), QVariant::fromValue(subjectArg), + QString::fromLatin1(ActionId), + QVariantMap(), // details + static_cast(0), // AllowUserInteraction = 0 (check only) + QString() // cancellation id + ); + + if (!reply.isValid()) + return false; + + const bool authorized = + reply.value().value(QStringLiteral("is-authorized")).toBool(); + + if (authorized != m_authorized) { + m_authorized = authorized; + emit authorizedChanged(); + } + + return m_authorized; +} + +bool PolkitHelper::requestAuthorization() { + QDBusInterface polkit(QStringLiteral("org.freedesktop.PolicyKit1"), + QStringLiteral("/org/freedesktop/PolicyKit1/Authority"), + QStringLiteral("org.freedesktop.PolicyKit1.Authority"), + QDBusConnection::systemBus()); + + if (!polkit.isValid()) + return false; + + QVariantMap subject; + subject[QStringLiteral("pid")] = static_cast(getpid()); + subject[QStringLiteral("start-time")] = static_cast(0); + + QDBusArgument subjectArg; + subjectArg.beginStructure(); + subjectArg << QStringLiteral("unix-process") << subject; + subjectArg.endStructure(); + + // AllowUserInteraction = 1 → kullanıcıya parola dialogu gösterilir + QDBusReply reply = polkit.call( + QStringLiteral("CheckAuthorization"), QVariant::fromValue(subjectArg), + QString::fromLatin1(ActionId), QVariantMap(), + static_cast(1), // AllowUserInteraction + QString()); + + if (!reply.isValid()) + return false; + + const bool authorized = + reply.value().value(QStringLiteral("is-authorized")).toBool(); + + if (authorized != m_authorized) { + m_authorized = authorized; + emit authorizedChanged(); + } + + return m_authorized; +} diff --git a/src/backend/system/polkit.h b/src/backend/system/polkit.h new file mode 100644 index 0000000..6f71c26 --- /dev/null +++ b/src/backend/system/polkit.h @@ -0,0 +1,35 @@ +#pragma once + +#include +#include + +// PolkitHelper: PolicyKit D-Bus arayüzü üzerinden yetki kontrolü. +// Komut çalıştırmaz — sadece yetkinin verilip verilmediğini kontrol eder. +// Gerçek komut yürütme CommandRunner::runAsRoot() ile yapılır. +class PolkitHelper : public QObject { + Q_OBJECT + + Q_PROPERTY(bool authorized READ isAuthorized NOTIFY authorizedChanged) + +public: + // ro-Control için tanımlı PolicyKit action ID + static constexpr auto ActionId = + "com.github.AcikKaynakGelistirmeToplulugu.rocontrol.manage-drivers"; + + explicit PolkitHelper(QObject *parent = nullptr); + + // Mevcut kullanıcının yetki durumunu sorgula + bool isAuthorized() const { return m_authorized; } + + // PolicyKit üzerinden yetki iste (D-Bus CheckAuthorization) + Q_INVOKABLE bool requestAuthorization(); + + // Mevcut yetkiyi kontrol et (dialog göstermeden) + Q_INVOKABLE bool checkAuthorization(); + +signals: + void authorizedChanged(); + +private: + bool m_authorized = false; +}; diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..6a97f8a --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,54 @@ +#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/installer.h" +#include "backend/nvidia/updater.h" + +int main(int argc, char *argv[]) { + // QApplication: Widgets backend (pencere, sistem tray vb.) için gerekli + QApplication app(argc, argv); + + // Uygulama meta bilgileri — Q_PROPERTY ve sistem entegrasyonunda kullanılır + app.setApplicationName("ro-control"); + app.setApplicationDisplayName("ro-Control"); + app.setApplicationVersion("0.1.0"); + app.setOrganizationName("Acik-Kaynak-Gelistirme-Toplulugu"); + app.setOrganizationDomain("github.com/Acik-Kaynak-Gelistirme-Toplulugu"); + app.setWindowIcon(QIcon::fromTheme("ro-control")); + + NvidiaDetector detector; + NvidiaInstaller installer; + NvidiaUpdater updater; + CpuMonitor cpuMonitor; + GpuMonitor gpuMonitor; + RamMonitor ramMonitor; + + // QML motorunu başlat + QQmlApplicationEngine engine; + + engine.rootContext()->setContextProperty("nvidiaDetector", &detector); + engine.rootContext()->setContextProperty("nvidiaInstaller", &installer); + engine.rootContext()->setContextProperty("nvidiaUpdater", &updater); + engine.rootContext()->setContextProperty("cpuMonitor", &cpuMonitor); + engine.rootContext()->setContextProperty("gpuMonitor", &gpuMonitor); + engine.rootContext()->setContextProperty("ramMonitor", &ramMonitor); + + // Ana QML dosyasını yükle + using namespace Qt::StringLiterals; + const QUrl url(u"qrc:/rocontrol/src/qml/Main.qml"_s); + + // QML yüklenemezse uygulamayı kapat + QObject::connect( + &engine, &QQmlApplicationEngine::objectCreationFailed, &app, + []() { QCoreApplication::exit(-1); }, Qt::QueuedConnection); + + engine.load(url); + + return app.exec(); +} diff --git a/src/qml/Main.qml b/src/qml/Main.qml new file mode 100644 index 0000000..54eb566 --- /dev/null +++ b/src/qml/Main.qml @@ -0,0 +1,73 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +ApplicationWindow { + id: root + visible: true + width: 980 + height: 640 + title: "ro-Control" + readonly property bool darkMode: Qt.styleHints.colorScheme === Qt.Dark + color: darkMode ? "#141822" : "#f4f6fb" + + ColumnLayout { + anchors.fill: parent + spacing: 0 + + ToolBar { + Layout.fillWidth: true + background: Rectangle { + color: root.darkMode ? "#1b2130" : "#ffffff" + } + + RowLayout { + anchors.fill: parent + anchors.margins: 8 + + Label { + text: "ro-Control" + font.pixelSize: 20 + font.bold: true + color: root.darkMode ? "#e8edfb" : "#121825" + } + + Item { + Layout.fillWidth: true + } + + Label { + text: root.darkMode ? "Tema: Sistem (Koyu)" : "Tema: Sistem (Acik)" + color: root.darkMode ? "#c8d0e7" : "#36435f" + } + } + } + + TabBar { + id: tabs + Layout.fillWidth: true + + TabButton { + text: "Surucu" + } + TabButton { + text: "Izleme" + } + TabButton { + text: "Ayarlar" + } + } + + StackLayout { + Layout.fillWidth: true + Layout.fillHeight: true + currentIndex: tabs.currentIndex + + DriverPage {} + MonitorPage {} + SettingsPage { + darkMode: root.darkMode + } + } + } +} diff --git a/src/qml/components/SidebarMenu.qml b/src/qml/components/SidebarMenu.qml new file mode 100644 index 0000000..ce9777f --- /dev/null +++ b/src/qml/components/SidebarMenu.qml @@ -0,0 +1,96 @@ +import QtQuick +import QtQuick.Controls + +Rectangle { + id: sidebar + width: 220 + color: "#181825" + + property int currentIndex: 0 + + ListModel { + id: menuModel + ListElement { + label: "Sürücü Yönetimi" + } + ListElement { + label: "Sistem İzleme" + } + ListElement { + label: "Ayarlar" + } + } + + Column { + anchors.fill: parent + spacing: 0 + + // Başlık + Item { + width: parent.width + height: 70 + + Label { + anchors.centerIn: parent + text: "ro-Control" + font.pixelSize: 22 + font.bold: true + color: "#cdd6f4" + } + } + + Rectangle { + width: parent.width - 32 + height: 1 + anchors.horizontalCenter: parent.horizontalCenter + color: "#313244" + } + + Item { + width: 1 + height: 12 + } + + Repeater { + model: menuModel + + delegate: Rectangle { + id: menuItem + required property int index + required property string label + + width: sidebar.width - 16 + height: 44 + x: 8 + radius: 8 + color: sidebar.currentIndex === menuItem.index ? "#313244" : mouseArea.containsMouse ? "#1e1e2e" : "transparent" + + 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.currentIndex = menuItem.index + } + } + } + } + + // Versiyon — alt köşe + Label { + anchors.bottom: parent.bottom + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottomMargin: 16 + text: "v" + Qt.application.version + font.pixelSize: 11 + color: "#585b70" + } +} diff --git a/src/qml/components/StatCard.qml b/src/qml/components/StatCard.qml new file mode 100644 index 0000000..9fe2678 --- /dev/null +++ b/src/qml/components/StatCard.qml @@ -0,0 +1,4 @@ +import QtQuick + +// StatCard — ilerleyen branch'lerde doldurulacak +Item {} diff --git a/src/qml/pages/DriverPage.qml b/src/qml/pages/DriverPage.qml new file mode 100644 index 0000000..67f4a01 --- /dev/null +++ b/src/qml/pages/DriverPage.qml @@ -0,0 +1,190 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +Item { + id: page + + ColumnLayout { + anchors.fill: parent + anchors.margins: 20 + spacing: 12 + + Label { + text: "Surucu Yonetimi" + font.pixelSize: 24 + font.bold: true + } + + Label { + text: "GPU: " + (nvidiaDetector.gpuFound ? nvidiaDetector.gpuName : "Tespit edilmedi") + wrapMode: Text.Wrap + Layout.fillWidth: true + } + + Label { + text: "Aktif surucu: " + nvidiaDetector.activeDriver + wrapMode: Text.Wrap + } + + Label { + text: "Surucu versiyonu: " + (nvidiaDetector.driverVersion.length > 0 ? nvidiaDetector.driverVersion : "Yok") + } + + Label { + text: "Secure Boot: " + (nvidiaDetector.secureBootEnabled ? "Acik" : "Kapali/Bilinmiyor") + color: nvidiaDetector.secureBootEnabled ? "#c43a3a" : "#2b8a3e" + font.bold: true + } + + Label { + text: "Oturum altyapisi: " + nvidiaDetector.sessionType + font.bold: true + } + + Label { + text: nvidiaDetector.waylandSession ? "Wayland icin nvidia-drm.modeset=1 parametresi otomatik uygulanir." : "X11 icin xorg-x11-drv-nvidia paketi kontrol edilip kurulur." + wrapMode: Text.Wrap + Layout.fillWidth: true + color: "#6d7384" + } + + 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 + } + } + + Label { + visible: nvidiaInstaller.proprietaryAgreementRequired + text: nvidiaInstaller.proprietaryAgreementText + color: "#8a6500" + wrapMode: Text.Wrap + Layout.fillWidth: true + } + + CheckBox { + id: eulaAccept + visible: nvidiaInstaller.proprietaryAgreementRequired + text: "Lisans/sozlesme kosullarini kabul ediyorum" + } + + RowLayout { + Layout.fillWidth: true + spacing: 8 + + Button { + text: "Kapali Kaynak Surucu Kur" + enabled: !nvidiaInstaller.proprietaryAgreementRequired || eulaAccept.checked + onClicked: nvidiaInstaller.installProprietary(eulaAccept.checked) + } + + Button { + text: "Acik Kaynak Surucu Kur (Nouveau)" + onClicked: nvidiaInstaller.installOpenSource() + } + + Button { + text: "Deep Clean" + onClicked: nvidiaInstaller.deepClean() + } + } + + RowLayout { + spacing: 8 + + Button { + text: "Guncelleme Kontrol Et" + onClicked: nvidiaUpdater.checkForUpdate() + } + + Button { + text: "Guncellemeyi Uygula" + enabled: nvidiaUpdater.updateAvailable + onClicked: nvidiaUpdater.applyUpdate() + } + + Label { + visible: nvidiaUpdater.updateAvailable + text: "Yeni surum: " + nvidiaUpdater.latestVersion + color: "#8a6500" + } + } + + RowLayout { + spacing: 8 + + Button { + text: "Yeniden Tara" + onClicked: { + nvidiaDetector.refresh(); + nvidiaInstaller.refreshProprietaryAgreement(); + } + } + + Label { + text: "Mevcut nvidia surumu: " + nvidiaUpdater.currentVersion + visible: nvidiaUpdater.currentVersion.length > 0 + } + } + + ScrollView { + Layout.fillWidth: true + Layout.fillHeight: true + + TextArea { + id: logArea + readOnly: true + wrapMode: Text.Wrap + text: "" + } + } + } + + Connections { + target: nvidiaInstaller + function onProgressMessage(message) { + logArea.append(message); + } + function onInstallFinished(success, message) { + logArea.append(message); + nvidiaDetector.refresh(); + nvidiaUpdater.checkForUpdate(); + nvidiaInstaller.refreshProprietaryAgreement(); + } + function onRemoveFinished(success, message) { + logArea.append(message); + nvidiaDetector.refresh(); + nvidiaInstaller.refreshProprietaryAgreement(); + } + } + + Connections { + target: nvidiaUpdater + function onProgressMessage(message) { + logArea.append(message); + } + function onUpdateFinished(success, message) { + logArea.append(message); + nvidiaDetector.refresh(); + nvidiaUpdater.checkForUpdate(); + } + } + + Component.onCompleted: { + nvidiaDetector.refresh(); + nvidiaUpdater.checkForUpdate(); + nvidiaInstaller.refreshProprietaryAgreement(); + } +} diff --git a/src/qml/pages/MonitorPage.qml b/src/qml/pages/MonitorPage.qml new file mode 100644 index 0000000..36e3262 --- /dev/null +++ b/src/qml/pages/MonitorPage.qml @@ -0,0 +1,152 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +Item { + ColumnLayout { + anchors.fill: parent + anchors.margins: 20 + spacing: 12 + + Label { + text: "Sistem Izleme" + font.pixelSize: 24 + font.bold: true + } + + 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: "CPU" + font.bold: true + } + + Label { + text: cpuMonitor.available ? "Kullanim: " + cpuMonitor.usagePercent.toFixed(1) + "%" : "CPU verisi alinamiyor" + } + + ProgressBar { + from: 0 + to: 100 + value: cpuMonitor.usagePercent + Layout.fillWidth: true + } + + Label { + text: "Sicaklik: " + cpuMonitor.temperatureC + " C" + visible: cpuMonitor.temperatureC > 0 + } + } + } + + 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: "GPU (NVIDIA)" + font.bold: true + } + + Label { + text: gpuMonitor.available ? (gpuMonitor.gpuName.length > 0 ? gpuMonitor.gpuName : "NVIDIA GPU") : "nvidia-smi ile veri alinamadi" + } + + Label { + text: "Yuk: " + gpuMonitor.utilizationPercent + "%" + visible: gpuMonitor.available + } + + ProgressBar { + from: 0 + to: 100 + value: gpuMonitor.utilizationPercent + Layout.fillWidth: true + visible: gpuMonitor.available + } + + Label { + text: "VRAM: " + gpuMonitor.memoryUsedMiB + " / " + gpuMonitor.memoryTotalMiB + " MiB (" + gpuMonitor.memoryUsagePercent + "%)" + visible: gpuMonitor.available && gpuMonitor.memoryTotalMiB > 0 + } + + Label { + text: "Sicaklik: " + gpuMonitor.temperatureC + " C" + visible: gpuMonitor.available && gpuMonitor.temperatureC > 0 + } + } + } + + 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: "RAM" + font.bold: true + } + + Label { + text: ramMonitor.available ? "Kullanim: " + ramMonitor.usedMiB + " / " + ramMonitor.totalMiB + " MiB (" + ramMonitor.usagePercent + "%)" : "RAM verisi alinamiyor" + } + + ProgressBar { + from: 0 + to: 100 + value: ramMonitor.usagePercent + Layout.fillWidth: true + } + } + } + + RowLayout { + spacing: 8 + + Button { + text: "Yenile" + onClicked: { + cpuMonitor.refresh(); + gpuMonitor.refresh(); + ramMonitor.refresh(); + } + } + + Label { + text: "Guncelleme araligi: " + cpuMonitor.updateInterval + " ms" + color: "#6d7384" + } + } + } +} diff --git a/src/qml/pages/SettingsPage.qml b/src/qml/pages/SettingsPage.qml new file mode 100644 index 0000000..7174b11 --- /dev/null +++ b/src/qml/pages/SettingsPage.qml @@ -0,0 +1,65 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +Item { + id: settingsPage + property bool darkMode: false + + ScrollView { + anchors.fill: parent + anchors.margins: 20 + + ColumnLayout { + width: parent.width + spacing: 12 + + Label { + text: "Ayarlar" + font.pixelSize: 24 + font.bold: true + } + + 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: "Hakkinda" + font.pixelSize: 20 + font.bold: true + } + + Label { + text: "Uygulama: " + Qt.application.name + " (" + Qt.application.version + ")" + } + + Label { + text: "Tema: " + (settingsPage.darkMode ? "Sistem Koyu" : "Sistem Acik") + } + + Label { + text: "Iyilestirmeler:" + font.bold: true + } + + Label { + text: "- Secure Boot kontrolu\n- Surucu versiyonu teyit raporu\n- CommandRunner stdout duzeltmesi\n- RPM Fusion URL duzeltmesi" + wrapMode: Text.Wrap + Layout.fillWidth: true + } + } + } + } + } +} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..89ec1dc --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,41 @@ +find_package(Qt6 REQUIRED COMPONENTS Test) + +# ─── Detector Tests ────────────────────────────────────────────────────────── +qt_add_executable(test_detector + test_detector.cpp + ${CMAKE_SOURCE_DIR}/src/backend/nvidia/detector.cpp + ${CMAKE_SOURCE_DIR}/src/backend/system/commandrunner.cpp +) + +target_include_directories(test_detector PRIVATE + ${CMAKE_SOURCE_DIR}/src + ${CMAKE_SOURCE_DIR}/src/backend +) + +target_link_libraries(test_detector PRIVATE + Qt6::Core + Qt6::Test +) + +add_test(NAME test_detector COMMAND test_detector) + +# ─── Monitor Tests ─────────────────────────────────────────────────────────── +qt_add_executable(test_monitor + test_monitor.cpp + ${CMAKE_SOURCE_DIR}/src/backend/monitor/cpumonitor.cpp + ${CMAKE_SOURCE_DIR}/src/backend/monitor/gpumonitor.cpp + ${CMAKE_SOURCE_DIR}/src/backend/monitor/rammonitor.cpp + ${CMAKE_SOURCE_DIR}/src/backend/system/commandrunner.cpp +) + +target_include_directories(test_monitor PRIVATE + ${CMAKE_SOURCE_DIR}/src + ${CMAKE_SOURCE_DIR}/src/backend +) + +target_link_libraries(test_monitor PRIVATE + Qt6::Core + Qt6::Test +) + +add_test(NAME test_monitor COMMAND test_monitor) diff --git a/tests/test_detector.cpp b/tests/test_detector.cpp new file mode 100644 index 0000000..6f29fe3 --- /dev/null +++ b/tests/test_detector.cpp @@ -0,0 +1,72 @@ +#include + +#include "nvidia/detector.h" + +class TestDetector : public QObject { + Q_OBJECT + +private slots: + void testConstruction() { + NvidiaDetector detector; + Q_UNUSED(detector); + QVERIFY(true); + } + + void testGpuInfoDefaults() { + NvidiaDetector::GpuInfo info{}; + QCOMPARE(info.found, false); + QVERIFY(info.name.isEmpty()); + QVERIFY(info.driverVersion.isEmpty()); + QCOMPARE(info.driverLoaded, false); + QCOMPARE(info.nouveauActive, false); + QCOMPARE(info.secureBootEnabled, false); + } + + void testDetectDoesNotCrash() { + NvidiaDetector detector; + auto info = detector.detect(); + Q_UNUSED(info); + QVERIFY(true); + } + + void testHasNvidiaGpu() { + NvidiaDetector detector; + bool result = detector.hasNvidiaGpu(); + Q_UNUSED(result); + QVERIFY(true); + } + + void testIsDriverInstalled() { + NvidiaDetector detector; + bool result = detector.isDriverInstalled(); + Q_UNUSED(result); + QVERIFY(true); + } + + void testInstalledDriverVersion() { + NvidiaDetector detector; + QString version = detector.installedDriverVersion(); + if (!version.isEmpty()) { + QVERIFY(version.contains(QChar('.'))); + } + } + + void testDetectConsistency() { + NvidiaDetector detector; + auto info = detector.detect(); + if (!info.found) { + QVERIFY(info.name.isEmpty()); + } + } + + void testVerificationReport() { + NvidiaDetector detector; + detector.refresh(); + const QString report = detector.verificationReport(); + QVERIFY(!report.isEmpty()); + QVERIFY(report.contains(QStringLiteral("Secure Boot"))); + } +}; + +QTEST_MAIN(TestDetector) +#include "test_detector.moc" diff --git a/tests/test_monitor.cpp b/tests/test_monitor.cpp new file mode 100644 index 0000000..71a0b93 --- /dev/null +++ b/tests/test_monitor.cpp @@ -0,0 +1,46 @@ +#include + +#include "monitor/cpumonitor.h" +#include "monitor/gpumonitor.h" +#include "monitor/rammonitor.h" + +class TestMonitor : public QObject { + Q_OBJECT + +private slots: + void testCpuConstruction() { + CpuMonitor cpu; + cpu.refresh(); + QTest::qWait(25); + cpu.refresh(); + + QVERIFY(cpu.usagePercent() >= 0.0); + QVERIFY(cpu.usagePercent() <= 100.0); + QVERIFY(cpu.temperatureC() >= 0); + } + + void testGpuConstruction() { + GpuMonitor gpu; + gpu.refresh(); + + QVERIFY(gpu.temperatureC() >= 0); + QVERIFY(gpu.utilizationPercent() >= 0); + QVERIFY(gpu.utilizationPercent() <= 100); + QVERIFY(gpu.memoryUsagePercent() >= 0); + QVERIFY(gpu.memoryUsagePercent() <= 100); + } + + void testRamConstruction() { + RamMonitor ram; + ram.refresh(); + + QVERIFY(ram.usagePercent() >= 0); + QVERIFY(ram.usagePercent() <= 100); + QVERIFY(ram.totalMiB() >= 0); + QVERIFY(ram.usedMiB() >= 0); + QVERIFY(ram.usedMiB() <= ram.totalMiB() || ram.totalMiB() == 0); + } +}; + +QTEST_MAIN(TestMonitor) +#include "test_monitor.moc"