diff --git a/.SRCINFO b/.SRCINFO index 0efdefd..2d75ce2 100644 --- a/.SRCINFO +++ b/.SRCINFO @@ -1,6 +1,6 @@ pkgbase = coolerdash pkgdesc = Extends CoolerControl with a polished LCD dashboard - pkgver = 1.31 + pkgver = 1.82 pkgrel = 1 url = https://github.com/damachine/coolerdash install = coolerdash.install diff --git a/.codacy.yml b/.codacy.yml new file mode 100644 index 0000000..30840df --- /dev/null +++ b/.codacy.yml @@ -0,0 +1,24 @@ +--- +engines: + # Enable Lizard code analysis + lizard: + enabled: true + + # Enable only cppcheck for C code analysis + cppcheck: + enabled: true + + # Enable Semgrep for security analysis + semgrep: + enabled: true + +# Exclude build and temporary directories +exclude_paths: + - "build/**" + - "bin/**" + - ".cache/**" + - ".makepkg/**" + - "pkg/**" + - "temp/**" + - "*.pkg.*" + - "coolerdash-*.pkg.*" diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index d0ca9cf..40355c3 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -10,4 +10,3 @@ liberapay: # Replace with a single Liberapay username issuehunt: # Replace with a single IssueHunt username otechie: # Replace with a single Otechie username lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry -custom: ['bitcoin:13WjpWQMGG5sg3vTJJnCX3cXzwf2vZddKo', 'https://dogechain.info/address/DRSY4cA8eCALn819MjWLbwaePFNti9oS3y'] diff --git a/.github/ISSUE_TEMPLATE/device-confirmation.yml b/.github/ISSUE_TEMPLATE/device-confirmation.yml new file mode 100644 index 0000000..68424b5 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/device-confirmation.yml @@ -0,0 +1,51 @@ +name: "✅ Device Confirmation" +description: Report that Coolerdash works (or doesn’t) on your device +labels: ["device-confirmation"] +title: "[Device] Confirmation: " +body: + - type: markdown + attributes: + value: | + Thanks for helping improve the Coolerdash compatibility list! + Please provide the details of your device below. + + - type: input + id: manufacturer + attributes: + label: Manufacturer + placeholder: "e.g. ASUS, MSI, NZXT" + validations: + required: true + + - type: input + id: model + attributes: + label: Model / Device + placeholder: "e.g. ROG Strix Z490-E, Kraken X63" + validations: + required: true + + - type: dropdown + id: status + attributes: + label: Status + options: + - ✅ Working + - ⚠️ Partially working + - ❌ Not working + validations: + required: true + + - type: input + id: os + attributes: + label: Operating System / Distro + placeholder: "e.g. Ubuntu 24.04, Arch Linux" + validations: + required: true + + - type: textarea + id: notes + attributes: + label: Additional Notes + placeholder: "Any extra details, quirks, or setup steps" diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md deleted file mode 100644 index 8d69f7a..0000000 --- a/.github/copilot-instructions.md +++ /dev/null @@ -1,110 +0,0 @@ ---- -applyTo: "**" ---- - -**Chat-Sprachregeln** -- Beantworte Chat-Fragen in deutscher Sprache. - -**Immer ganz oben am Anfang einer Datei `.c` `.h`** -```c -/** - * @author damachine (christkue79@gmail.com) - * @Maintainer: damachine - * @website https://github.com/damachine - * @copyright (c) 2025 damachine - * @license MIT - * @version 1.0 - * This software is provided "as is", without warranty of any kind, express or implied. - * I do not guarantee that it will work as intended on your system. - */ - - ``` -- Vermeide und entferne den `Immer ganz oben am Anfang einer Datei...`, wenn er nicht am anfang der Datei steht. - -**Kommentar- und Dokumentationsstil** -- Dokumentiere in der `README.md` und `AUR-README.md` in englischer Sprache. -- Schreibe Code-Kommentare in englischer Sprache. -- Verwende Doxygen-Stil für Funktionskommentare. -- Nutze Doxygen-Kommentare für Funktionen, Strukturen und wichtige Abschnitte. -- Öffnende geschweifte Klammern stehen bei Funktionen und Kontrollstrukturen in derselben Zeile (K&R-Stil). -- Nutze `//` für einzeilige Kommentare. -- Nutze `/* ... */` für mehrzeilige Kommentare. -- Nutze Inline-Kommentare sparsam, nur wenn nötig. -- Doppelte Header-Kommentare vermeiden. -- Kommentiere alle nicht sofort verständlichen Codeabschnitte. -- Vermeide redundante Kommentare, die den Code wiederholen. -- Dokumentiere komplexe Algorithmen und Datenstrukturen ausführlich. -- Nutze als 1. Kommentar `@brief` für eine kurze Zusammenfassung der Funktion. -- Nutze als 2. Kommentar `@details` für eine ausführliche Beschreibung der Funktion. -- Beispiel für nutze als 1. 2. Kommentar: -```c -/** - * @brief - * ... - * @details - * ... - */ - ``` -- Entferne Kommentare, die den Code nicht mehr beschreiben oder veraltet sind. - -**Code-Richtlinien und Codestil** -- Halte dich an ISO/IEC 9899:1999 (C99). -- Binde nur notwendige Header ein; trenne System- und lokale Header -- Verwende Include Guards: `#ifndef HEADER_H` / `#define HEADER_H` / `#endif`. -- Nutze `const` für unveränderliche Variablen und Funktionsparameter. -- Nutze `static` für Funktionen und Variablen, die nur in der Datei sichtbar sein sollen. -- Nutze `inline` für kleine, häufig genutzte Funktionen. -- Nutze `malloc()` für dynamische Speicherallokation. -- Nutze `calloc()` für dynamische Speicherallokation mit Nullinitialisierung. -- Nutze `realloc()` für dynamische Speicheranpassung. -- Nutze `enum` für Status- und Fehlercodes, z.B. `enum Status { SUCCESS, ERROR }`. -- Nutze `typedef` für komplexe Datentypen, z.B. `typedef struct { int x; int y; } Point;`. -- Nutze `struct` für Datenstrukturen, z.B. `struct MyStruct { int a; float b; };`. -- Nutze `union` für gemeinsame Datenstrukturen, z.B. `union Data { int i; float f; };`. -- Nutze `typedef` für Zeiger auf Funktionen, z.B. `typedef void (*Callback)(int);`. -- Nutze `static_assert` für Compile-Zeit-Prüfungen, z.B. `static_assert(sizeof(int) == 4, "int must be 4 bytes");`. -- Nutze `restrict` für Zeiger, die nicht auf dieselben Daten zeigen, z.B. `void func(int * restrict a, int * restrict b);`. -- Nutze `volatile` für Variablen, die sich außerhalb des Programms ändern können, z.B. `volatile int *ptr;`. -- Nutze `inline` für kleine, häufig genutzte Funktionen, z.B. `inline int square(int x) { return x * x; }`. -- Vermeide `free()` auf NULL-Zeiger, aber setze Zeiger nach `free()` auf NULL. -- Vermeide `gets()`, nutze stattdessen `fgets()` oder `getline()`. -- Vermeide `strcpy()`, nutze stattdessen `strncpy()` oder `strlcpy()`. -- Vermeide `sprintf()`, nutze stattdessen `snprintf()` oder `asprintf()`. -- Vermeide `strcat()`, nutze stattdessen `strncat()` oder `strlcat()`. -- Vermeide ` strtok()`, nutze stattdessen `strsep()` oder `strtok_r()`. -- Vermeide `atoi()`, `atol()`, `atoll()`, nutze stattdessen `strtol()`, `strtoll()`, `strtof()`, `strtod()`, `strtold()`. -- Vermeide `printf()` für Fehler und Debugging, nutze stattdessen `fprintf(stderr, ...)`. -- Vermeide `exit()` in Bibliotheksfunktionen, nutze stattdessen `return` oder `longjmp()`. -- Vermeide `goto`, nutze statdessen Schleifen und Kontrollstrukturen. -- Vermeide globale Variablen, nutze stattdessen lokale Variablen oder Strukturen. -- Vermeide rekursive Funktionen, wenn möglich, nutze stattdessen Iteration. -- Vermeide unnötige Typumwandlungen, nutze stattdessen den richtigen Datentyp. -- Vermeide unnötige Zeigerarithmetik, nutze stattdessen Array-Indizes. -- Vermeide unnötige Funktionsaufrufe, nutze stattdessen Inline-Funktionen. -- Vermeide unnötige Schleifen, nutze stattdessen bedingte Anweisungen. -- Vermeide unnötige Speicherallokation, nutze stattdessen statische Arrays oder Strukturen. -- Vermeide unnötige Typdefinitionen, nutze stattdessen Standardtypen. -- Vermeide unnötige Makros, nutze stattdessen Inline-Funktionen oder Konstanten. -- Überprüfe Rückgabewerte von `malloc()`, `calloc()`, `realloc()`. -- Funktionsnamen sind Verben im snake_case, z.B. `calculate_sum()`, `parse_input()`. -- Variablen im snake_case, z.B. `user_count`. -- Konstanten und Makros in UPPER_CASE, z.B. `MAX_SIZE`, `PI`. -- Typdefinitionen in PascalCase, z.B. `MyType`. -- Enum-Namen in UPPER_CASE, z.B. `STATUS_OK`, ` -- Gib dynamisch reservierten Speicher frei und setze Zeiger danach auf NULL. -- Halte den Code strukturiert: Trenne Deklarationen, Definitionen und Implementierungen. -- Halte den Code sauber und lesbar: Einrückung mit 4 Leerzeichen, keine Tabulatoren. -- Vermeide unnötigen Code und redundante Kommentare. -- Halte den Code modular: Teile große Funktionen in kleinere auf. -- Halte den Code effizient: Vermeide unnötige Berechnungen und Schleifen. -- Halte den Code portabel: Vermeide plattformspezifische Funktionen und Bibliotheken. -- Halte den Code sicher: Vermeide Pufferüberläufe, nutze sichere Funktionen. -- Halte den Code wartbar: Schreibe klaren, verständlichen Code mit sinnvollen Kommentaren. -- Halte den Code testbar: Schreibe Unit-Tests für wichtige Funktionen. -- Halte den Code dokumentiert: Nutze Doxygen-Kommentare für Funktionen und Strukturen. -- Halte den Code performant: Optimiere nur, wenn es notwendig ist, und vermeide premature optimization. -- Halte den Code konsistent: Nutze einheitliche Stilrichtlinien und Namenskonventionen. -- Halte den Code lesbar: Nutze sprechende Namen und vermeide kryptische Abkürzungen. -- Halte den Code flexibel: Nutze Parameter und Rückgabewerte, um Funktionen anpassbar zu machen. -- Halte den Code erweiterbar: Schreibe Funktionen so, dass sie leicht erweitert werden können. -- Halte den Code robust: Behandle Fehlerfälle und unerwartete Eingaben angemessen. diff --git a/.github/workflows/aur.yml b/.github/workflows/aur.yml index 27c5bb3..a7ec4c4 100644 --- a/.github/workflows/aur.yml +++ b/.github/workflows/aur.yml @@ -6,8 +6,7 @@ on: - 'VERSION' branches: - main - workflow_dispatch: # Allows manual execution - + workflow_dispatch: permissions: contents: write @@ -16,7 +15,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 with: fetch-depth: 0 token: ${{ secrets.GITHUB_TOKEN }} @@ -27,11 +26,9 @@ jobs: sudo apt-get install -y gnupg2 pinentry-curses jq export GNUPGHOME=$(mktemp -d) chmod 700 "$GNUPGHOME" - # Configure agent to allow loopback pinentry in isolated homedir echo "allow-loopback-pinentry" > "$GNUPGHOME/gpg-agent.conf" echo "use-agent" > "$GNUPGHOME/gpg.conf" echo "pinentry-program /usr/bin/pinentry-curses" >> "$GNUPGHOME/gpg-agent.conf" - # ensure gpg uses the new homedir when started later echo "GNUPGHOME=$GNUPGHOME" >> $GITHUB_ENV - name: Import GPG key into isolated GNUPGHOME @@ -39,55 +36,38 @@ jobs: GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} run: | set -euo pipefail - # kill any agent that might interfere gpgconf --kill gpg-agent || true - # import key into isolated homedir echo "$GPG_PRIVATE_KEY" | gpg --batch --import - # Trust the imported key echo -e "5\ny\n" | gpg --batch --command-fd 0 --expert --edit-key $(gpg --list-secret-keys --with-colons | awk -F: '/^sec/ {print $5; exit}') trust quit || true - name: Read version and find next available tag id: version run: | base_ver=$(cat VERSION | tr -d '\n\r' | xargs) - - # Fetch all tags from remote git fetch --tags origin || true - - # Finde die nächste verfügbare Versionsnummer counter=0 while true; do if [ $counter -eq 0 ]; then - # Erste Variante: Basis-Version (z.B. v1.28) test_version="$base_ver" test_tag="v$test_version" else - # Weitere Varianten: Basis-Version mit Suffix (z.B. v1.28.1, v1.28.2) test_version="$base_ver.$counter" test_tag="v$test_version" fi - - # Prüfen ob dieser Tag bereits existiert if ! git tag -l "$test_tag" | grep -q "$test_tag" && ! git ls-remote --tags origin | grep -q "refs/tags/$test_tag"; then - # Tag existiert nicht, verwenden wir ihn final_version="$test_version" final_tag="$test_tag" break fi - counter=$((counter + 1)) - - # Sicherheit: Maximal 100 Versuche if [ $counter -gt 100 ]; then echo "ERROR: Could not find available version after 100 attempts" exit 1 fi done - echo "Base version from file: $base_ver" echo "Final version to use: $final_version" echo "Final tag to create: $final_tag" - echo "version=$final_version" >> $GITHUB_OUTPUT echo "tag=$final_tag" >> $GITHUB_OUTPUT echo "release_msg=AUR Release $final_tag" >> $GITHUB_OUTPUT @@ -120,52 +100,43 @@ jobs: GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} run: | set -euo pipefail - - # Sicherstellen, dass wir den aktuellen Stand haben git fetch origin - echo "Creating signed tag: $TAG" - - # GPG Agent starten falls nötig gpg-agent --daemon --allow-preset-passphrase --max-cache-ttl 3600 || true - - # Tag erstellen mit GPG-Signatur echo "$GPG_PASSPHRASE" | gpg --batch --yes --passphrase-fd 0 --pinentry-mode loopback \ --sign --armor --detach-sign --local-user "${{ steps.gpg.outputs.keyid }}" \ --output /tmp/dummy.sig <(echo "test") || true - - # Eigentlichen Tag erstellen echo "$GPG_PASSPHRASE" | git -c "gpg.program=gpg" \ -c "user.signingkey=${{ steps.gpg.outputs.keyid }}" \ tag -s "$TAG" -m "$MSG" HEAD - - # Tag zur Remote-Repository pushen git push origin "$TAG" - echo "Successfully created and pushed signed tag: $TAG" - name: Create GitHub Release - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 with: tag_name: ${{ steps.version.outputs.tag }} name: ${{ steps.version.outputs.release_msg }} draft: false prerelease: false generate_release_notes: true - body: | - ## AUR Package Release ${{ steps.version.outputs.version }} + body: | + ## This release contains the package files for the Arch User Repository (AUR). - This release contains the updated package files for the Arch User Repository (AUR). - - ### Changes + [![AUR](https://img.shields.io/aur/version/coolerdash-git?color=blue&label=AUR)](https://aur.archlinux.org/packages/coolerdash-git) + + ### Changes: - Version updated to ${{ steps.version.outputs.version }} - ### Installation + ### Installation: ```bash - # Install from AUR + # STEP 1: Using an AUR helper and install yay -S coolerdash-git - # or - paru -S coolerdash-git + #OR any other AUR helper + + # STEP 2: Enable/Start CoolerDash sytemd service + systemctl daemon-reload + systemctl enable --now coolerdash.service ``` env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -174,12 +145,27 @@ jobs: run: | echo "Verifying tag signature..." git tag -v "${{ steps.version.outputs.tag }}" || echo "Tag verification completed" - echo "Release completed successfully!" echo "Signed tag created: ${{ steps.version.outputs.tag }}" echo "GitHub Release: https://github.com/${{ github.repository }}/releases/tag/${{ steps.version.outputs.tag }}" - # Summary report + - name: Cleanup GPG environment + if: always() + run: | + echo "Cleaning up GPG environment..." + if [ -n "$GNUPGHOME" ] && [ -d "$GNUPGHOME" ]; then + gpgconf --kill gpg-agent || true + rm -rf "$GNUPGHOME" + echo "Removed GNUPGHOME: $GNUPGHOME" + fi + for key in $(gpg --list-secret-keys --with-colons 2>/dev/null | awk -F: '/^sec/ {print $5}'); do + gpg --batch --yes --delete-secret-keys "$key" || true + done + for key in $(gpg --list-keys --with-colons 2>/dev/null | awk -F: '/^pub/ {print $5}'); do + gpg --batch --yes --delete-keys "$key" || true + done + echo "✅ GPG cleanup completed." + aur-release-summary: name: AUR Release Summary runs-on: ubuntu-latest diff --git a/.github/workflows/codacy.yml b/.github/workflows/codacy.yml new file mode 100644 index 0000000..de3b4af --- /dev/null +++ b/.github/workflows/codacy.yml @@ -0,0 +1,62 @@ + +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. + +# This workflow checks out code, performs a Codacy security scan +# and integrates the results with the +# GitHub Advanced Security code scanning feature. For more information on +# the Codacy security scan action usage and parameters, see +# https://github.com/codacy/codacy-analysis-cli-action. +# For more information on Codacy Analysis CLI in general, see +# https://github.com/codacy/codacy-analysis-cli. + +name: Codacy Security Scan + +on: + push: + branches: [ "main", "beta", "v2.00rc" ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ "main" ] + schedule: + - cron: '40 13 * * 5' + +permissions: + contents: read + +jobs: + codacy-security-scan: + permissions: + contents: read # for actions/checkout to fetch code + security-events: write # for github/codeql-action/upload-sarif to upload SARIF results + actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status + name: Codacy Security Scan + runs-on: ubuntu-latest + steps: + # Checkout the repository to the GitHub Actions runner + - name: Checkout code + uses: actions/checkout@v4 + + # Execute Codacy Analysis CLI and generate a SARIF output with the security issues identified during the analysis + - name: Run Codacy Analysis CLI + uses: codacy/codacy-analysis-cli-action@d840f886c4bd4edc059706d09c6a1586111c540b + with: + # Check https://github.com/codacy/codacy-analysis-cli#project-token to get your project token from your Codacy repository + # You can also omit the token and run the tools that support default configurations + project-token: ${{ secrets.CODACY_PROJECT_TOKEN }} + verbose: true + output: results.sarif + format: sarif + # Adjust severity of non-security issues + gh-code-scanning-compat: true + # Force 0 exit code to allow SARIF file generation + # This will handover control about PR rejection to the GitHub side + max-allowed-issues: 2147483647 + + # Upload the SARIF file generated in the previous step + - name: Upload SARIF results file + uses: github/codeql-action/upload-sarif@v3 + with: + sarif_file: results.sarif diff --git a/.github/workflows/gitlab.yml b/.github/workflows/gitlab.yml new file mode 100644 index 0000000..a37ea85 --- /dev/null +++ b/.github/workflows/gitlab.yml @@ -0,0 +1,137 @@ +name: Sync to GitLab + +permissions: + contents: read + +on: + push: + branches: [ main ] + release: + types: [ published ] + workflow_dispatch: # Allows manual execution + +jobs: + sync: + name: Synchronize with GitLab + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + with: + fetch-depth: 0 # Fetch complete history for proper sync + + - name: Configure Git + run: | + git config --global user.name "GitHub Actions" + git config --global user.email "actions@github.com" + + - name: Add GitLab remote and selective sync + env: + GITLAB_TOKEN: ${{ secrets.GITLAB_TOKEN }} + run: | + if [ -z "$GITLAB_TOKEN" ]; then + echo "❌ GITLAB_TOKEN is not set in repository secrets" + echo "Please add your GitLab Personal Access Token to GitHub Secrets as 'GITLAB_TOKEN'" + exit 1 + fi + + # Add GitLab remote with token authentication + git remote add gitlab https://oauth2:${GITLAB_TOKEN}@gitlab.com/damachine/coolerdash.git + + echo "🔄 Creating selective sync (with GitLab-specific README)..." + + # Create temporary branch for selective sync + git checkout -b gitlab-sync + + # Create GitLab-specific README.md + cat > README.md << 'EOF' + # CoolerDash - GitLab Mirror + + **⚠️ This is a synchronized mirror repository from GitHub** + + ## 📍 Main Repository + + **Primary development happens on GitHub:** + 🔗 **[https://github.com/damachine/coolerdash](https://github.com/damachine/coolerdash)** + + ## 🔄 Synchronization Info + + - This GitLab repository is **automatically synchronized** from GitHub + - **All issues, pull requests, and contributions** should be made on GitHub + - This mirror is updated automatically when changes are pushed to the main GitHub repository + - **Documentation, releases, and latest updates** are available on GitHub + + ## 📖 Full Documentation + + For complete documentation, installation instructions, and project information, please visit: + + **[GitHub Repository - damachine/coolerdash](https://github.com/damachine/coolerdash)** + + ## 🚀 Quick Info + + **CoolerDash** is a real-time sensor monitoring tool for AIO liquid coolers with integrated LCD displays. + + - Enhances your liquid-cooling display with extra features + - Support for additional sensor values + - Polished, customizable LCD dashboard + - Add-on wrapper for [CoolerControl](https://gitlab.com/coolercontrol/coolercontrol) + + ## 📞 Support & Contributing + + - **Issues**: [GitHub Issues](https://github.com/damachine/coolerdash/issues) + - **Discussions**: [GitHub Discussions](https://github.com/damachine/coolerdash/discussions) + - **Contributing**: [GitHub Contributing Guide](https://github.com/damachine/coolerdash/blob/main/CONTRIBUTING.md) + + --- + + **🔗 Visit the main repository for the latest updates and full documentation:** + **[github.com/damachine/coolerdash](https://github.com/damachine/coolerdash)** + EOF + + # Stage and commit the new README + git add README.md + git commit -m "INFO: this is a GitLab mirror - see GitHub for main repo: https://github.com/damachine/coolerdash" + + echo "🔄 Synchronizing to GitLab (with GitLab README)..." + # Try normal push first, fallback to sync branch if main is protected + if ! git push gitlab gitlab-sync:main --force; then + echo "⚠️ Main branch is protected, creating github-sync branch instead..." + git push gitlab gitlab-sync:github-sync --force + echo "📝 Created 'github-sync' branch on GitLab" + fi + + # Clean up temporary branch + git checkout main + git branch -D gitlab-sync + + echo "🏷️ Synchronizing tags to GitLab..." + git push gitlab --tags --force + + echo "✅ Synchronization completed (GitLab README created)" + + - name: Create sync summary + if: success() + run: | + echo "## 🔄 GitLab Synchronization Report" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "### Synchronized Content:" >> $GITHUB_STEP_SUMMARY + echo "- **Repository**: coolerdash" >> $GITHUB_STEP_SUMMARY + echo "- **Target**: GitLab (gitlab.com/damachine/coolerdash)" >> $GITHUB_STEP_SUMMARY + echo "- **Branches**: Synced (main may be in 'github-sync' if protected)" >> $GITHUB_STEP_SUMMARY + echo "- **Tags**: All tags synced" >> $GITHUB_STEP_SUMMARY + echo "- **README**: GitLab-specific version created with GitHub sync info" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "📝 **Note**: GitLab README explains this is a mirror and directs users to GitHub" >> $GITHUB_STEP_SUMMARY + echo "🎯 **GitLab repository is now synchronized with GitHub (selective sync)**" >> $GITHUB_STEP_SUMMARY + + - name: Error summary + if: failure() + run: | + echo "## ❌ GitLab Synchronization Failed" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "### Troubleshooting Steps:" >> $GITHUB_STEP_SUMMARY + echo "1. **Check GitLab Token**: Ensure GITLAB_TOKEN secret is set" >> $GITHUB_STEP_SUMMARY + echo "2. **Token Permissions**: Token needs 'write_repository' scope" >> $GITHUB_STEP_SUMMARY + echo "3. **Repository Access**: Verify you have push access to GitLab repo" >> $GITHUB_STEP_SUMMARY + echo "4. **Protected Branches**: Check GitLab repository branch protection settings" >> $GITHUB_STEP_SUMMARY \ No newline at end of file diff --git a/.github/workflows/install.yml b/.github/workflows/install.yml index bcd8c2b..4cfc831 100644 --- a/.github/workflows/install.yml +++ b/.github/workflows/install.yml @@ -3,136 +3,118 @@ name: Multi-Distribution Installation on: push: paths: - - 'VERSION' + - "VERSION" branches: - main - workflow_dispatch: # Allows manual execution + workflow_dispatch: # Allows manual execution +permissions: + contents: read jobs: test-install: name: Test 'make install' on ${{ matrix.name }} runs-on: ubuntu-latest - + strategy: - fail-fast: false # Test all distros even if one fails + fail-fast: false # Test all distros even if one fails matrix: include: # Ubuntu/Debian family - - distro: "ubuntu:22.04" - name: "Ubuntu 22.04 LTS" - pre_install: "apt-get update" - install_deps: "apt-get install -y sudo systemd libcairo2-dev libcurl4-openssl-dev libinih-dev build-essential pkg-config libjansson-dev fonts-roboto file" - - - distro: "ubuntu:24.04" + - distro: "ubuntu:24.04" name: "Ubuntu 24.04 LTS" pre_install: "apt-get update" install_deps: "apt-get install -y sudo systemd libcairo2-dev libcurl4-openssl-dev libinih-dev build-essential pkg-config libjansson-dev fonts-roboto file" - - - distro: "debian:12" - name: "Debian 12 (Bookworm)" + + - distro: "debian:13" + name: "Debian 13 (Trixie)" pre_install: "apt-get update" install_deps: "apt-get install -y sudo systemd libcairo2-dev libcurl4-openssl-dev libinih-dev build-essential pkg-config libjansson-dev fonts-roboto file" - + # Fedora family (independent from RHEL) - - distro: "fedora:39" - name: "Fedora 39" - pre_install: "dnf update -y" - install_deps: "dnf install -y sudo systemd cairo-devel libcurl-devel inih-devel gcc make pkg-config jansson-devel google-roboto-fonts file" - - - distro: "fedora:40" - name: "Fedora 40" + - distro: "fedora:42" + name: "Fedora 42" pre_install: "dnf update -y" install_deps: "dnf install -y sudo systemd cairo-devel libcurl-devel inih-devel gcc make pkg-config jansson-devel google-roboto-fonts file" - + # RHEL/CentOS family (RHEL-compatible) - - distro: "almalinux:9" - name: "AlmaLinux 9 (RHEL 9 compatible)" - pre_install: "dnf update -y && dnf install -y epel-release && dnf config-manager --set-enabled crb && dnf makecache" - install_deps: "dnf install -y sudo systemd cairo-devel libcurl-devel inih-devel gcc make pkg-config jansson-devel google-roboto-fonts file" - - - distro: "rockylinux:9" - name: "Rocky Linux 9 (RHEL 9 compatible)" - pre_install: "dnf update -y && dnf install -y epel-release && dnf config-manager --set-enabled crb && dnf makecache" - install_deps: "dnf install -y sudo systemd cairo-devel libcurl-devel inih-devel gcc make pkg-config jansson-devel google-roboto-fonts file" - - distro: "quay.io/centos/centos:stream9" name: "CentOS Stream 9 (RHEL 9 compatible)" pre_install: "dnf update -y && dnf install -y epel-release && dnf config-manager --set-enabled crb && dnf makecache" install_deps: "dnf install -y sudo systemd cairo-devel libcurl-devel inih-devel gcc make pkg-config jansson-devel google-roboto-fonts file" - + # openSUSE family - distro: "opensuse/tumbleweed" name: "openSUSE Tumbleweed" pre_install: "zypper refresh" install_deps: "zypper install -y tar sudo systemd cairo-devel libcurl-devel libinih-devel gcc make pkg-config libjansson-devel google-roboto-fonts file" - + - distro: "opensuse/leap:16.0" name: "openSUSE Leap 16.0" - pre_install: "zypper refresh" + pre_install: "zypper refresh" install_deps: "zypper install -y sudo systemd cairo-devel libcurl-devel libinih-devel gcc make pkg-config libjansson-devel google-roboto-fonts file" container: image: ${{ matrix.distro }} - options: --privileged # For systemctl and make install - + options: --privileged # For systemctl and make install + steps: - - name: Checkout CoolerDash code - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - - - name: Update package manager - run: ${{ matrix.pre_install }} - - - name: Install build dependencies - run: ${{ matrix.install_deps }} - - - name: Test make install - run: | - echo "🚀 Testing 'make install' on ${{ matrix.name }}" - - # Create a safe install simulation - # (since we're in a container, we can't test all systemd services) - export DESTDIR=/tmp/install-test - mkdir -p /tmp/install-test - - echo "Running make install with DESTDIR=/tmp/install-test" - REALOS=no make install || { - echo "❌ make install failed" - echo "Exit code: $?" - echo "Checking if binary exists:" - ls -la coolerdash || echo "No binary found" - exit 1 - } - - # Check if all expected files were installed - echo "📁 Checking installed files:" - find /tmp/install-test -type f -name "*coolerdash*" | head -10 - - # Check binary - if [ -f "/tmp/install-test/opt/coolerdash/bin/coolerdash" ]; then - echo "✅ Binary installed correctly" - file /tmp/install-test/opt/coolerdash/bin/coolerdash - else - echo "❌ Binary not found at expected location" - find /tmp/install-test -name "coolerdash" -type f - exit 1 - fi - - # Check config files - if [ -f "/tmp/install-test/etc/coolerdash/config.ini" ]; then - echo "✅ Config file installed" - else - echo "⚠️ Config file not found (might be expected)" - fi - - # Check systemd service - if [ -f "/tmp/install-test/etc/systemd/system/coolerdash.service" ] || [ -f "/tmp/install-test/usr/lib/systemd/system/coolerdash.service" ]; then - echo "✅ Systemd service file installed" - else - echo "⚠️ Systemd service file not found" - find /tmp/install-test -name "*.service" -type f - fi - - echo "✅ Installation test completed on ${{ matrix.name }}" + - name: Checkout CoolerDash code + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + + - name: Update package manager + run: ${{ matrix.pre_install }} + + - name: Install build dependencies + run: ${{ matrix.install_deps }} + + - name: Test make install + run: | + echo "🚀 Testing 'make install' on ${{ matrix.name }}" + + # Create a safe install simulation + # (since we're in a container, we can't test all systemd services) + export DESTDIR=/tmp/install-test + mkdir -p /tmp/install-test + + echo "Running make install with DESTDIR=/tmp/install-test" + REALOS=no make install || { + echo "❌ make install failed" + echo "Exit code: $?" + echo "Checking if binary exists:" + ls -la coolerdash || echo "No binary found" + exit 1 + } + + # Check if all expected files were installed + echo "📁 Checking installed files:" + find /tmp/install-test -type f -name "*coolerdash*" | head -10 + + # Check binary + if [ -f "/tmp/install-test/opt/coolerdash/bin/coolerdash" ]; then + echo "✅ Binary installed correctly" + file /tmp/install-test/opt/coolerdash/bin/coolerdash + else + echo "❌ Binary not found at expected location" + find /tmp/install-test -name "coolerdash" -type f + exit 1 + fi + + # Check config files + if [ -f "/tmp/install-test/etc/coolerdash/config.ini" ]; then + echo "✅ Config file installed" + else + echo "⚠️ Config file not found (might be expected)" + fi + + # Check systemd service + if [ -f "/tmp/install-test/etc/systemd/system/coolerdash.service" ] || [ -f "/tmp/install-test/usr/lib/systemd/system/coolerdash.service" ]; then + echo "✅ Systemd service file installed" + else + echo "⚠️ Systemd service file not found" + find /tmp/install-test -name "*.service" -type f + fi + + echo "✅ Installation test completed on ${{ matrix.name }}" # Additional test for Arch Linux (your development system) arch-test: @@ -141,48 +123,48 @@ jobs: container: image: archlinux:latest options: --privileged - + steps: - - name: Checkout code - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - - - name: Setup Arch Linux - run: | - pacman -Syu --noconfirm - pacman -S --noconfirm cairo libcurl-gnutls libinih gcc make pkg-config jansson ttf-roboto sudo systemd - - - name: Test make install on Arch - run: | - echo "🚀 Testing 'make install' on Arch Linux (reference system)" - - export DESTDIR=/tmp/install-test - mkdir -p /tmp/install-test - REALOS=no make install - - echo "✅ Arch Linux installation test completed (reference)" + - name: Checkout code + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + + - name: Setup Arch Linux + run: | + pacman -Syu --noconfirm + pacman -S --noconfirm cairo libcurl-gnutls libinih gcc make pkg-config jansson ttf-roboto sudo systemd + + - name: Test make install on Arch + run: | + echo "🚀 Testing 'make install' on Arch Linux (reference system)" + + export DESTDIR=/tmp/install-test + mkdir -p /tmp/install-test + REALOS=no make install + + echo "✅ Arch Linux installation test completed (reference)" # Summary report installation-summary: name: Installation Test Summary runs-on: ubuntu-latest needs: [test-install, arch-test] - if: always() # Runs even if tests fail - + if: always() # Runs even if tests fail + steps: - - name: Create summary report - run: | - echo "## 🚀 CoolerDash Multi-Distribution Installation Test Results" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "### Tested Distributions:" >> $GITHUB_STEP_SUMMARY - echo "- **Ubuntu**: 22.04 LTS, 24.04 LTS" >> $GITHUB_STEP_SUMMARY - echo "- **Debian**: 12 (Bookworm)" >> $GITHUB_STEP_SUMMARY - echo "- **Fedora**: 39, 40" >> $GITHUB_STEP_SUMMARY - echo "- **RHEL-compatible**: AlmaLinux 9, Rocky Linux 9, CentOS Stream 9" >> $GITHUB_STEP_SUMMARY - echo "- **openSUSE**: Tumbleweed, Leap 15.5" >> $GITHUB_STEP_SUMMARY - echo "- **Arch Linux**: Latest (reference)" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "### Test Coverage:" >> $GITHUB_STEP_SUMMARY - echo "✅ Installation process (make install)" >> $GITHUB_STEP_SUMMARY - echo "✅ File placement verification" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "🔗 **View detailed logs above for distribution-specific results**" >> $GITHUB_STEP_SUMMARY \ No newline at end of file + - name: Create summary report + run: | + echo "## 🚀 CoolerDash Multi-Distribution Installation Test Results" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "### Tested Distributions:" >> $GITHUB_STEP_SUMMARY + echo "- **Ubuntu**: 24.04 LTS" >> $GITHUB_STEP_SUMMARY + echo "- **Debian**: 13 (Trixie)" >> $GITHUB_STEP_SUMMARY + echo "- **Fedora**: 42" >> $GITHUB_STEP_SUMMARY + echo "- **RHEL-compatible**: CentOS Stream 9" >> $GITHUB_STEP_SUMMARY + echo "- **openSUSE**: Tumbleweed, Leap 16.0" >> $GITHUB_STEP_SUMMARY + echo "- **Arch Linux**: Latest (reference)" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "### Test Coverage:" >> $GITHUB_STEP_SUMMARY + echo "✅ Installation process (make install)" >> $GITHUB_STEP_SUMMARY + echo "✅ File placement verification" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "🔗 **View detailed logs above for distribution-specific results**" >> $GITHUB_STEP_SUMMARY diff --git a/.gitignore b/.gitignore index 9567d5b..035ce41 100644 --- a/.gitignore +++ b/.gitignore @@ -9,7 +9,6 @@ bin/ .cache/ .makepkg/ pkg/ -src/ temp/ *~ *.tmp @@ -27,7 +26,9 @@ Thumbs.db *.swp *.swo .vscode/ +.idea/ .qodo/ +.codacy/ # Compressed archives *.tar.xz @@ -45,3 +46,8 @@ coolerdash-*.pkg.* PKGBUILD-test test-* debug-* + +# Ignore vscode AI rules +.github/instructions/best_practices.md +.github/instructions/codacy.instructions.md +.github/instructions/copilot.instructions.md diff --git a/AUR-README.md b/AUR-README.md deleted file mode 100644 index e762c6b..0000000 --- a/AUR-README.md +++ /dev/null @@ -1,58 +0,0 @@ -# AUR Package Instructions for CoolerDash - -## ⚡ Recommended Installation (Arch Linux) - -CoolerDash is best installed via the AUR using `makepkg -si` for full dependency management and automatic conflict cleanup. - -### Quick Install (AUR/Local Build) - -- Using an AUR helper (recommended): -```bash -yay -S coolerdash-git -``` - -- Manual AUR install (no AUR helper): -```bash -# STEP 1: Clone repository -git clone https://aur.archlinux.org/coolerdash-git.git -cd coolerdash-git -makepkg --printsrcinfo > .SRCINFO -makepkg -si - -# STEP 2: Start CoolerControl daemon if not already running -systemctl start coolercontrold - -# STEP 4: Enable autostart and start CoolerDash -systemctl enable --now coolerdash.service - -# STEP 5: (optional) Check CoolerDash service status -systemctl status coolerdash.service -journalctl -u coolerdash.service -``` - -### Alternative: Local .pkg.tar.zst Installation - -```bash -# 1. Build the package (creates .pkg.tar.zst) - makepkg -s - -# 2. Install the package manually - sudo pacman -U coolerdash-*.pkg.tar.zst - -# 3. Enable and start service - sudo systemctl enable --now coolerdash.service -``` - -> **Note:** If you install manually, you must ensure all required dependencies are installed yourself. No automatic updates will be provided for manual installations. - ---- - -## Further Information - -For all additional details, usage instructions, configuration options, troubleshooting, and development notes, please refer to the main [README.md](README.md) file in the project root. - -The main README contains comprehensive documentation and is the authoritative source for all technical and user. - ---- - -**Developed and tested on Arch Linux. For other distributions, see the main README.md.** diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..b3adbbb --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,35 @@ +# Code of Conduct for Coolerdash + +Coolerdash is a small open-source project that warmly welcomes everyone. +Our goal is to build a community that is **inclusive**, **respectful**, and **collaborative**. +Everyone is invited to contribute, as long as we treat each other kindly and fairly. + +## Our Values + +- **Inclusivity** — People of all backgrounds, identities, and experiences are welcome. Diversity makes the project stronger. +- **Respect** — Be polite, constructive, and considerate. Discrimination, harassment, or bullying have no place here. +- **Collaboration** — We work together. Contributions of all kinds are valuable: code, issues, ideas, documentation, and reviews. + +## Expected Behavior + +- Be friendly, patient, and helpful. +- Give and accept constructive feedback. +- Respect different viewpoints and experiences. +- Support newcomers and encourage participation. + +## Unacceptable Behavior + +- Insults, personal attacks, or offensive comments. +- Discrimination based on personal characteristics (e.g., gender, race, religion, disability). +- Harassment, bullying, or unwelcome advances. +- Trolling, provocation, or deliberate disruption of discussions. + +## Reporting Issues + +If you experience or witness behavior that violates this Code of Conduct, please contact the project maintainers. +You can open an [issue](https://github.com/damachine/coolerdash/issues) or reach out privately if you prefer. +Reports will be handled respectfully and confidentially, and maintainers may take appropriate action to keep the community safe. + +--- + +*Inspired by the spirit of the Contributor Covenant.* diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..78f7435 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,39 @@ +# Contributing to CoolerDash + +Thank you for your interest in contributing to **CoolerDash**! +This is a small open-source project, and all kinds of contributions are welcome — whether it's reporting bugs, suggesting features, writing documentation, or improving code. + +## How to Contribute + +### 1. Reporting Issues +- Use the [GitHub Issues](https://github.com/damachine/coolerdash/issues) page. +- Clearly describe the problem or suggestion. +- If possible, include steps to reproduce the issue or screenshots. + +### 2. Suggesting Features +- Open an issue with the label **enhancement**. +- Explain why the feature would be useful and how you imagine it working. + +### 3. Submitting Changes +- Fork the repository and create a new branch (`git checkout -b my-feature`). +- Make your changes with clear, concise commit messages. +- Ensure your code is formatted and tested (if applicable). +- Open a Pull Request (PR) against the **main** branch. +- Be ready for feedback and discussion. + +### 4. Code Style +- Keep the code simple and readable. +- Use meaningful names for variables and functions. +- Document your changes where necessary (comments or README updates). + +## Community Guidelines +Please follow our [Code of Conduct](./CODE_OF_CONDUCT.md). +We value **inclusivity, respect, and collaboration** in all contributions and discussions. + +## Questions? +If you are unsure about something, feel free to open an issue and ask. +There are no bad questions — newcomers are very welcome! + +--- + +✨ Thanks for helping make **coolerdash** better! diff --git a/Makefile b/Makefile index 796684e..aa9b33b 100644 --- a/Makefile +++ b/Makefile @@ -19,7 +19,7 @@ # Do not run as root. Use dedicated user for security. # Ensure all required dependencies are installed. # It uses color output and Unicode icons for better readability. All paths and dependencies are configurable. -# See 'README.md' and 'AUR-README.md' for further details. +# See 'README.md' for further details. # # Build: # 'make' @@ -32,7 +32,6 @@ .PHONY: clean install uninstall debug logs help detect-distro install-deps check-deps - VERSION := $(shell cat VERSION) SUDO ?= sudo @@ -45,14 +44,13 @@ TARGET = coolerdash # Directories SRCDIR = src -INCDIR = include OBJDIR = build BINDIR = bin # Source code files MAIN_SOURCE = $(SRCDIR)/main.c SRC_MODULES = $(SRCDIR)/config.c $(SRCDIR)/coolercontrol.c $(SRCDIR)/display.c $(SRCDIR)/monitor.c -HEADERS = $(INCDIR)/config.h $(INCDIR)/coolercontrol.h $(INCDIR)/display.h $(INCDIR)/monitor.h +HEADERS = $(SRCDIR)/config.h $(SRCDIR)/coolercontrol.h $(SRCDIR)/display.h $(SRCDIR)/monitor.h OBJECTS = $(patsubst $(SRCDIR)/%.c,$(OBJDIR)/%.o,$(SRC_MODULES)) SERVICE = etc/systemd/coolerdash.service @@ -270,7 +268,6 @@ install: check-deps $(TARGET) @printf "\n" @printf "$(ICON_INFO) $(CYAN)Installing files...$(RESET)\n" install -Dm755 $(BINDIR)/$(TARGET) "$(DESTDIR)/opt/coolerdash/bin/coolerdash" - install -Dm644 AUR-README.md "$(DESTDIR)/opt/coolerdash/AUR-README.md" install -Dm644 $(README) "$(DESTDIR)/opt/coolerdash/README.md" install -Dm644 LICENSE "$(DESTDIR)/opt/coolerdash/LICENSE" install -Dm644 CHANGELOG.md "$(DESTDIR)/opt/coolerdash/CHANGELOG.md" @@ -280,7 +277,6 @@ install: check-deps $(TARGET) @printf " $(GREEN)Program:$(RESET) /opt/coolerdash/bin/coolerdash [mode]\n" @printf " $(GREEN)Documentation:$(RESET) $(DESTDIR)/opt/coolerdash/\n" @printf " $(GREEN) - README.md$(RESET)\n" - @printf " $(GREEN) - AUR-README.md$(RESET)\n" @printf " $(GREEN) - LICENSE$(RESET)\n" @printf " $(GREEN) - CHANGELOG.md$(RESET)\n" @printf " $(GREEN)Resources:$(RESET) $(DESTDIR)/opt/coolerdash/images/shutdown.png\n" diff --git a/PKGBUILD b/PKGBUILD index dc71f8f..ba28aa8 100644 --- a/PKGBUILD +++ b/PKGBUILD @@ -22,7 +22,7 @@ # Do not run as root. Use dedicated user for security. # Ensure all required dependencies are installed. # It uses color output and Unicode icons for better readability. All paths and dependencies are configurable. -# See 'README.md' and 'AUR-README.md' for further details. +# See 'README.md' for further details. # # Build: # 'makepkg -si' @@ -54,6 +54,12 @@ build() { # For local build: use current directory directly cd "$startdir" + # Fetch latest tags if in git repo + if git rev-parse --git-dir >/dev/null 2>&1; then + echo "Fetching latest tags..." + git fetch --tags 2>/dev/null || true + fi + # Remove all previous tarball builds rm -rf coolerdash-*.pkg.* || true rm -rf build bin || true @@ -71,7 +77,6 @@ build() { # Copy all required files for packaging to $srcdir cp -a README.md "$srcdir/README.md" - cp -a AUR-README.md "$srcdir/AUR-README.md" cp -a CHANGELOG.md "$srcdir/CHANGELOG.md" cp -a VERSION "$srcdir/VERSION" cp -a LICENSE "$srcdir/LICENSE" @@ -99,7 +104,6 @@ check() { package() { # For local build: use current directory directly install -dm755 "$pkgdir/opt/coolerdash" - install -Dm644 "$srcdir/AUR-README.md" "$pkgdir/opt/coolerdash/AUR-README.md" install -Dm644 "$srcdir/README.md" "$pkgdir/opt/coolerdash/README.md" install -Dm644 "$srcdir/VERSION" "$pkgdir/opt/coolerdash/VERSION" install -Dm644 "$srcdir/LICENSE" "$pkgdir/opt/coolerdash/LICENSE" diff --git a/README.md b/README.md index 8ba3100..2c82fdf 100644 --- a/README.md +++ b/README.md @@ -1,29 +1,28 @@ -> **⚠️ Warning:** Updates and installation from the AUR may occasionally be difficult or delayed due to server availability or upstream issues. -> The AUR has unfortunately become a target for bad actors recently. Please always be careful with AUR packages and check the PKGBUILD first before installing. This project is open source, and all code can be reviewed! -# CoolerDash - Extends [CoolerControl](https://gitlab.com/coolercontrol/coolercontrol) with a polished LCD-Dashboard +# CoolerDash 🐧 -[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) -[![Platform](https://img.shields.io/badge/Platform-Linux-green.svg)](https://kernel.org/) -[![C99](https://img.shields.io/badge/C-99-blue.svg)](https://en.wikipedia.org/wiki/C99) -[![GitHub issues](https://img.shields.io/github/issues/damachine/coolerdash)](https://github.com/damachine/coolerdash/issues) -[![Installation Test](https://github.com/damachine/coolerdash/actions/workflows/install.yml/badge.svg)](https://github.com/damachine/coolerdash/actions/workflows/install.yml) -[![AUR Build](https://github.com/damachine/coolerdash/actions/workflows/aur.yml/badge.svg)](https://github.com/damachine/coolerdash/actions/workflows/aur.yml) -[![AUR](https://img.shields.io/aur/version/coolerdash-git?color=blue&label=AUR)](https://aur.archlinux.org/packages/coolerdash-git) -[![Sponsor on GitHub](https://img.shields.io/badge/Sponsor%20on-GitHub-blue?logo=github)](https://github.com/sponsors/damachine) +

+ + + + + +

-## 📖 Description +--- -**CoolerDash is a wrapper tool that extends CoolerControl. Turn your water cooling display into a smart, stylish information hub—beyond the default features of CoolerControl alone.** +## ⭐ Features +- **Displays real-time sensor on an AIO liquid cooler with integrated LCD displays.** +- **Extends the LCD functionality of [CoolerControl](https://gitlab.com/coolercontrol/coolercontrol) with additional features.** +- **Support for additional sensor values, and a sophisticated, customizable user interface.** -Special thanks to @codifryed, the developer of CoolerControl! +> ##### Special thanks to [@codifryed](https://github.com/codifryed), the founder of CoolerControl -### 📸 Screenshot – Example LCD Output +
- CoolerDash LCD Animation - AI-generated LCD Demo - AI-generated LCD Demo + CoolerDash LCD Demo round + CoolerDash LCD Demo quad
--- @@ -31,136 +30,97 @@ Special thanks to @codifryed, the developer of CoolerControl! ## 🖥️ System Requirements - **OS**: Linux -- **CoolerControl**: Version >=2.2.2 REQUIRED - must be installed and running -- **CPU**: x86-64-v3 compatible (Intel Haswell+ 2013+ / AMD Excavator+ 2015+) -- **LCD**: LCD displays supported by CoolerControl (NZXT, etc.) - -> **For older CPUs**: Use `CFLAGS=-march=x86-64 make` for compatibility - -**Supported Distributions and Dependencies:** - -[![AUR Build](https://github.com/damachine/coolerdash/actions/workflows/aur.yml/badge.svg)](https://github.com/damachine/coolerdash/actions/workflows/aur.yml) - [![Installation Test](https://github.com/damachine/coolerdash/actions/workflows/install.yml/badge.svg)](https://github.com/damachine/coolerdash/actions/workflows/install.yml) -- **Arch Linux / Manjaro (Recommended)** [![AUR](https://img.shields.io/aur/version/coolerdash-git?color=blue&label=AUR)](https://aur.archlinux.org/packages/coolerdash-git) -- **Ubuntu / Debian** -- **Fedora** -- **RHEL / CentOS** -- **openSUSE** +- **CoolerControl**: Version >=2.2.2 REQUIRED - must be installed and running [Installation Guide](https://gitlab.com/coolercontrol/coolercontrol/-/blob/main/README.md) +- **CPU**: x86-64-v3 compatible (Intel Haswell+ / AMD Excavator+) +- **LCD**: AIO liquid cooler LCD displays **(NZXT, etc.)** -> **Note:** If you install manually, you must ensure all required dependencies are installed right. No automatic updates will be provided for manual installations. - -## Prerequisites - -1. **Install CoolerControl**: [Installation Guide](https://gitlab.com/coolercontrol/coolercontrol/-/blob/main/README.md) -2. **Start CoolerControl daemon**: `systemctl start coolercontrold` -3. **In CoolerControl config - Device and Sensor - select one sensor for CPU and GPU** +> [!NOTE] +> See the [Supported Devices](https://github.com/damachine/coolerdash/blob/main/docs/devices.md), for a list of confirmed working hardware. +> To confirm a device: [Submit a Device confirmation](https://github.com/damachine/coolerdash/issues/new?template=device-confirmation.yml). +> In principle, all devices supported by CoolerControl/-[liquidctl](https://github.com/liquidctl/liquidctl?tab=readme-ov-file#supported-devices) should work with CoolerDash. --- -## 📦 Installation - -### Install CoolerDash +## 🛠️ Installation #### Arch Linux (Recommended) -- Using an AUR helper (recommended): -```bash -yay -S coolerdash-git -``` + +[![AUR](https://img.shields.io/aur/version/coolerdash-git?color=blue&label=AUR)](https://aur.archlinux.org/packages/coolerdash-git) -- Manual AUR install (no AUR helper): -```bash -# STEP 1: Clone repository -git clone https://aur.archlinux.org/coolerdash-git.git -cd coolerdash-git -makepkg --printsrcinfo > .SRCINFO -makepkg -si +- Using an AUR helper: -# STEP 2: Start CoolerControl daemon if not already running -systemctl start coolercontrold +```bash +# STEP 1: Install +yay -S coolerdash-git +#OR any other AUR helper -# STEP 4: Enable autostart and start CoolerDash +# STEP 2: Enable/Start CoolerDash sytemd service systemctl daemon-reload systemctl enable --now coolerdash.service - -# STEP 5: (optional) Check CoolerDash service status -systemctl status coolerdash.service -journalctl -u coolerdash.service ``` -#### All Distributions (Manual Installation) +#### All Distributions + + + +- Manual installation: ```bash # STEP 1: Clone repository git clone https://github.com/damachine/coolerdash.git cd coolerdash -# STEP 2: Start CoolerControl daemon if not already running -systemctl start coolercontrold - -# STEP 3: Build and install (auto-detects Linux distribution and installs dependencies) -sudo make install +# STEP 2: Build and install (auto-detects Linux distribution and installs dependencies) +make install -# STEP 4: Enable autostart +# STEP 3: Enable/Start CoolerDash sytemd service systemctl daemon-reload systemctl enable --now coolerdash.service - -# STEP 5: (optional) Check CoolerDash service status -systemctl status coolerdash.service -journalctl -u coolerdash.service ``` ---- - -#### Manual Usage - -```bash -# Run manually (with minimal status logging) -coolerdash - -# Run with detailed debug logging -coolerdash --log - -# Or use full path -/opt/coolerdash/bin/coolerdash - -# From directory -./coolerdash -``` -> **Note:** The systemd service must be stopped before running manually to avoid conflicts: -```bash -systemctl stop coolerdash.service -``` +> For manual installations, please make sure all required dependencies are installed correctly. +> **At this time, manual installations need to be updated manually**. --- ## ⚙️ Configuration -> **CoolerControl configuration**: In CoolerControl GUI, set CPU/GPU sensors to your desired values! -> In CoolerControl GUI, set your LCD display to **"Image/gif"** mode and brightness to **"80%"**! +> [!IMPORTANT] +> #### CoolerControl GUI: +> - In the CoolerControl settings, under **`Device and Sensor`**, select one sensor for the **`CPU`** and one for the **`GPU`**. Set your **`LCD`** display to **`Image/gif`**. -> **Runtime Configuration:** All settings are managed in `/etc/coolerdash/config.ini`. -> After editing the config file, restart the service with `systemctl restart coolerdash.service` to apply your changes. +> [!IMPORTANT] +> #### CoolerDash Runtime: +> - Don't forget to enable/start the service `systemctl enable --now coolerdash.service`. +> - The application starts with preset default settings. +> - If needed. All settings are managed in `/etc/coolerdash/config.ini`. +> - After editing the config file, restart the service with `systemctl restart coolerdash.service` to apply your changes. -> **Shutdown Image:** When CoolerDash stops (during system shutdown or reboot), it automatically displays the `shutdown.png` image since sensor data is no longer available. You can customize, disable, or replace this image by editing the `/etc/coolerdash/config.ini` configuration file. +> [!TIP] +> - When CoolerDash stops (for example during system shutdown or reboot), it automatically displays the `shutdown.png` image from the install path. This happens because sensor data is no longer available at that point. +> - You can customize this and much more as you wish, by editing the `/etc/coolerdash/config.ini` file. +> - **For detailed configuration options and examples, see: 📖** [Configuration Guide](https://github.com/damachine/coolerdash/blob/main/docs/config.md) --- -## 🔧 Usage +## 🚀 Advanced Usage #### Service Management ```bash # Service control -systemctl start coolerdash.service # Start -systemctl stop coolerdash.service # Stop (displays face.png automatically) -systemctl restart coolerdash.service # Restart -systemctl status coolerdash.service # Status + recent logs +systemctl enable --now coolerdash.service # Enable and Start! +systemctl start coolerdash.service # Start +systemctl stop coolerdash.service # Stop +systemctl restart coolerdash.service # Restart +systemctl status coolerdash.service # Status + recent logs # Journal log journalctl -u coolerdash.service # Live logs -journalctl -u coolerdash.service -f +journalctl -xeu coolerdash.service -f ``` #### Build Commands @@ -174,6 +134,16 @@ make debug # Debug build with AddressSanitizer make help # Show all options ``` +#### Manual Usage + +```bash +# Run manually (with minimal status logging) +coolerdash + +# Run with detailed debug logging +coolerdash --log +``` + #### Debugging Steps ```bash @@ -191,80 +161,118 @@ coolerdash --log make debug && coolerdash --log # 5. Check service logs (STATUS messages always visible) -journalctl -u coolerdash.service -f +journalctl -xeu coolerdash.service -f # 6. View recent logs with context journalctl -u coolerdash.service -n 50 ``` -## 🔍 Troubleshooting +> The systemd service must be stopped before running manually to avoid conflicts: -#### Common Issues - -- **"Device not found"**: LCD not configured in CoolerControl → Use CoolerControl GUI → set LCD mode to `Image/gif` -- **"Connection refused"**: CoolerControl daemon not running → `systemctl start coolercontrold` -- **"Connection problem"**: No devices found or wrong device UID → Check CoolerControl configuration and LCD connection → Verify with `curl http://localhost:11987/devices | jq` - -**Troubleshooting: Verify connection**, you can manually check if devices are detected correctly: ```bash -# Start CoolerControl (if not running) -systemctl start coolercontrold - -# Check available devices -curl http://localhost:11987/devices | jq +systemctl stop coolerdash.service ``` -**Example CoolerControl API output:** -```json -{ - "name": "NZXT Kraken 2023", - "type": "Liquidctl", - "type_index": 1, - "uid": "8d4becb03bca2a8e8d4213ac376a1094f39d2786f688549ad3b6a591c3affdf9", - "lc_info": { - "driver_type": "KrakenZ3", - "firmware_version": "2.0.0", - "unknown_asetek": false - } -``` +--- + +## 🆘 Troubleshooting + +#### Common Issues -**Manual Installation Conflicts** -If you see errors like "conflicting files" or "manual installation detected" during `makepkg -si`, this means CoolerDash was previously installed manually (via `make install`). +> [!WARNING] +> - **Installation:** If you see errors like "conflicting files" or "manual installation detected" during Arch/AUR `makepkg -si`, this means CoolerDash was previously installed manually (via `make install`). + + > [!TIP] + > - If problems persist, run: + > ```bash + > sudo systemctl stop coolerdash.service + > sudo make uninstall + > ``` + > - Remove any leftover files: + > ```bash + > sudo rm -rf /opt/coolerdash/ \ + > /usr/bin/coolerdash \ + > /etc/systemd/system/coolerdash.service + > ``` + > - Then retry the installation. + +# + +> [!WARNING] +> - **Device/-Connection failed:** No devices found or wrong device UID. +> - ***Please post this outputs when you report any issue.*** + + > [!TIP] + > - Check CoolerControl configuration and LCD connection → Verify device with: + > ```bash + > liquidctl --version + > ``` + > ###### Example output: + > ```bash + > liquidctl v1.15.0 (Linux-6.17.1-273-linux-tkg-x86_64-with-glibc2.42) + > ``` + + > [!TIP] + > ```bash + > curl http://localhost:11987/devices | jq + > ``` + > ###### Example output: + > ```json + > { + > "name": "NZXT Kraken 2023", + > "type": "Liquidctl", + > "type_index": 1, + > "uid": "8d4becb03bca2a8e8d4213ac376a1094f39d2786f688549ad3b6a591c3affdf9", + > "lc_info": { + > "driver_type": "KrakenZ3", + > "firmware_version": "2.0.0", + > "unknown_asetek": false + > } + > } + > ``` -**Solution:** -- The PKGBUILD will attempt to clean up automatically. -- If problems persist, run: - ``` - sudo make uninstall - ``` -- Remove any leftover files in `/opt/coolerdash/`, `/usr/bin/coolerdash`, and `/etc/systemd/system/coolerdash.service`. -- Then retry the installation. +--- -If you need help, open an issue at https://github.com/damachine/coolerdash/issues +> [!TIP] +> ### Have a question or an idea? +> - **Suggest improvements** or discuss new features in our **[Discussions](https://github.com/damachine/coolerdash/discussions)**. +> - **Report a bug** or request help by opening an **[Issue](https://github.com/damachine/coolerdash/issues)**. +> +> --- ## ⚠️ Disclaimer This software is provided "as is", without warranty of any kind, express or implied. -I do not guarantee that it will work as intended on your system. +I do not guarantee that it will work as intended on your system. ## 📄 License MIT License - See LICENSE file for details. -[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) + +[![MIT License](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT) + +Individual Coolercontrol package have their own licenses: + +- See respective repositories at [https://gitlab.com/coolercontrol/coolercontrol](https://gitlab.com/coolercontrol/coolercontrol) + +--- ## 💝 Support the Project If you find CoolerDash useful and want to support its development: -- ⭐ **Star this repository** on GitHub -- 🐛 **Report bugs** and suggest improvements -- 🔄 **Share** the project with others -- 📝 **Contribute** code or documentation -- [![Sponsor on GitHub](https://img.shields.io/badge/Sponsor%20on-GitHub-blue?logo=github)](https://github.com/sponsors/damachine) +- ⭐ **Star this repository** on GitHub. +- 🐛 **Report bugs** and suggest improvements. +- 🔄 **Share** the project with others. +- 📝 **Contribute** Add device support, code or documentation. +- [![Sponsor](https://img.shields.io/badge/Sponsor-GitHub-blue?logo=github-sponsors)](https://github.com/sponsors/damachine) + +> *🙏 Your support keeps this project alive and improving — thank you!.* -> *All donations help maintain and improve this project. Thank you for your support!* +#### ⭐ Stargazers over time +[![Stargazers over time](https://starchart.cc/damachine/coolerdash.svg?variant=adaptive)](https://starchart.cc/damachine/coolerdash) --- diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..7ff1ac4 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,19 @@ +# Security Policy + +## Supported Versions +CoolerDash is a small personal open-source project. +Only the latest version is actively maintained. Please always update to the newest release before reporting issues. + +## Reporting a Vulnerability +If you discover a security vulnerability, please **do not open a public issue**. +Instead, contact the project maintainer directly: + +- **Email:** [christkue79@gmail.com] +- or open a **private security advisory** via GitHub (under the "Security" tab of the repository). + +Please include as much detail as possible so we can understand and reproduce the issue. +We aim to respond to security reports within a reasonable timeframe and will keep you updated on the progress. + +## Responsible Disclosure +We kindly ask you to give us a chance to fix vulnerabilities before disclosing them publicly. +This helps keep all users safe and ensures patches are available quickly. diff --git a/VERSION b/VERSION index d0911c8..a92432a 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.31 +1.82 diff --git a/docs/config.md b/docs/config.md new file mode 100644 index 0000000..5d53494 --- /dev/null +++ b/docs/config.md @@ -0,0 +1,503 @@ +# CoolerDash Configuration Guide + +Complete guide for configuring CoolerDash through the `config.ini` file. + +## 📍 Configuration File Location + +The configuration file is located at: +``` +/etc/coolerdash/config.ini +``` + +## 🔄 Applying Changes + +After modifying the configuration file, restart CoolerDash: +```bash +sudo systemctl restart coolerdash +``` + +--- + +## 🌐 Daemon Settings + +Configure connection to CoolerControl daemon API. + +### Example Configuration +```ini +[daemon] +address=http://localhost:11987 +password=coolAdmin +``` + +### Settings +- **`address`**: API endpoint URL (default: `http://localhost:11987`) +- **`password`**: API authentication password (default: `coolAdmin`) + +### HTTPS Example +For secure connections: +```ini +[daemon] +address=https://192.168.1.100:11987 +password=mySecurePassword123 +``` + +--- + +## 📁 File Paths + +System file and directory locations. + +### Example Configuration +```ini +[paths] +images=/opt/coolerdash/images +image_coolerdash=/tmp/coolerdash.png +image_shutdown=/opt/coolerdash/images/shutdown.png +pid=/tmp/coolerdash.pid +``` + +### Settings +- **`images`**: Directory containing display images +- **`image_coolerdash`**: Generated display image path +- **`image_shutdown`**: Shutdown screen image +- **`pid`**: Process ID file location + +--- + +## 🖥️ Display Settings + +LCD display configuration tested with NZXT Kraken 2023. + +### Basic Configuration +```ini +[display] +width=240 +height=240 +refresh_interval_sec=2 +refresh_interval_nsec=500000000 +brightness=80 +orientation=0 +``` + +### Settings +- **`width`**: Screen width in pixels (240 for NZXT Kraken) +- **`height`**: Screen height in pixels (240 for NZXT Kraken) +- **`refresh_interval_sec`**: Update interval in seconds (1-10 recommended) +- **`refresh_interval_nsec`**: Additional nanoseconds (500000000 = 0.5s) +- **`brightness`**: LCD brightness 0-100% (80% recommended) +- **`orientation`**: Screen rotation: `0`, `90`, `180`, `270` degrees + +### Brightness Examples +```ini +# Dim for night use +brightness=40 + +# Standard brightness (recommended) +brightness=80 + +# Maximum brightness +brightness=100 +``` + +### Refresh Rate Examples +```ini +# Fast updates (1 second) +refresh_interval_sec=1 +refresh_interval_nsec=0 + +# Standard updates (2.5 seconds) +refresh_interval_sec=2 +refresh_interval_nsec=500000000 + +# Slow updates (5 seconds) - saves power +refresh_interval_sec=5 +refresh_interval_nsec=0 +``` + +--- + +## 🎨 Visual Layout + +Display layout and spacing configuration. + +### Example Configuration +```ini +[layout] +box_width=240 +box_height=120 +box_gap=0 +bar_width=230 +bar_height=22 +bar_gap=10 +bar_border_width=1.5 +``` + +### Settings +- **`box_width`**: Main display box width (should match display width) +- **`box_height`**: Main display box height (typically half display height) +- **`box_gap`**: Space between display boxes +- **`bar_width`**: Temperature bar width (fits within box_width) +- **`bar_height`**: Temperature bar thickness +- **`bar_gap`**: Space between temperature bars +- **`bar_border_width`**: Border line thickness + +### Layout Examples + +#### Compact Layout +```ini +[layout] +box_width=240 +box_height=115 +bar_width=220 +bar_height=18 +bar_gap=5 +bar_border_width=1.0 +``` + +#### Spacious Layout +```ini +[layout] +box_width=240 +box_height=125 +bar_width=235 +bar_height=26 +bar_gap=15 +bar_border_width=2.0 +``` + +--- + +## 🎨 Colors + +RGB color configuration (values 0-255). + +### Bar Colors +```ini +[bar_color_background] +r=52 # Dark gray +g=52 +b=52 + +[bar_color_border] +r=192 # Light gray +g=192 +b=192 +``` + +### Color Examples + +#### Dark Theme +```ini +[bar_color_background] +r=25 # Very dark gray +g=25 +b=25 + +[bar_color_border] +r=100 # Medium gray +g=100 +b=100 +``` + +#### Blue Theme +```ini +[bar_color_background] +r=20 # Dark blue +g=30 +b=60 + +[bar_color_border] +r=70 # Light blue +g=130 +b=180 +``` + +#### Custom RGB Colors +```ini +# Pure black background +[bar_color_background] +r=0 +g=0 +b=0 + +# White border +[bar_color_border] +r=255 +g=255 +b=255 +``` + +--- + +## 🔤 Font Settings + +Text appearance configuration. + +### Example Configuration +```ini +[font] +font_face=Roboto Black +font_size_temp=100.0 +font_size_labels=30.0 + +[font_color_temp] +r=255 # White temperature text +g=255 +b=255 + +[font_color_label] +r=200 # Light gray labels +g=200 +b=200 +``` + +### Font Examples + +#### Large Text Setup +```ini +[font] +font_face=Arial Bold +font_size_temp=120.0 # Larger temperature numbers +font_size_labels=35.0 # Larger labels +``` + +#### Compact Text Setup +```ini +[font] +font_face=Helvetica +font_size_temp=80.0 # Smaller temperature numbers +font_size_labels=24.0 # Smaller labels +``` + +#### Color Examples +```ini +# Bright white text +[font_color_temp] +r=255 +g=255 +b=255 + +# Cyan labels +[font_color_label] +r=0 +g=255 +b=255 +``` + +--- + +## 🌡️ Temperature Zones + +Color-coded temperature thresholds and their colors. + +### Example Configuration +```ini +[temperature] +temp_threshold_1=55.0 # Cool zone (< 55°C) +temp_threshold_2=65.0 # Warm zone (55-65°C) +temp_threshold_3=75.0 # Hot zone (65-75°C) + # Critical zone (> 75°C) + +[temp_threshold_1_bar] # 💚 Cool (Green) +r=0 +g=255 +b=0 + +[temp_threshold_2_bar] # 🧡 Warm (Orange) +r=255 +g=140 +b=0 + +[temp_threshold_3_bar] # 🔥 Hot (Dark Orange) +r=255 +g=70 +b=0 + +[temp_threshold_4_bar] # 🚨 Critical (Red) +r=255 +g=0 +b=0 +``` + +### Temperature Threshold Examples + +#### Conservative (Lower Thresholds) +```ini +[temperature] +temp_threshold_1=45.0 # Cool < 45°C +temp_threshold_2=55.0 # Warm 45-55°C +temp_threshold_3=65.0 # Hot 55-65°C + # Critical > 65°C +``` + +#### Aggressive (Higher Thresholds) +```ini +[temperature] +temp_threshold_1=60.0 # Cool < 60°C +temp_threshold_2=70.0 # Warm 60-70°C +temp_threshold_3=80.0 # Hot 70-80°C + # Critical > 80°C +``` + +### Color Scheme Examples + +#### Rainbow Theme +```ini +[temp_threshold_1_bar] # Blue (Cool) +r=0 +g=100 +b=255 + +[temp_threshold_2_bar] # Yellow (Warm) +r=255 +g=255 +b=0 + +[temp_threshold_3_bar] # Orange (Hot) +r=255 +g=128 +b=0 + +[temp_threshold_4_bar] # Red (Critical) +r=255 +g=0 +b=0 +``` + +#### Monochrome Theme +```ini +[temp_threshold_1_bar] # Light gray +r=200 +g=200 +b=200 + +[temp_threshold_2_bar] # Medium gray +r=150 +g=150 +b=150 + +[temp_threshold_3_bar] # Dark gray +r=100 +g=100 +b=100 + +[temp_threshold_4_bar] # Very dark gray +r=50 +g=50 +b=50 +``` + +--- + +## 📋 Complete Example Configuration + +Here's a complete working configuration file: + +```ini +# ═══════════════════════════════════════════════════════════════════════════════ +# CoolerDash Configuration - NZXT Kraken Setup +# ═══════════════════════════════════════════════════════════════════════════════ + +[daemon] +address=http://localhost:11987 +password=coolAdmin + +[paths] +images=/opt/coolerdash/images +image_coolerdash=/tmp/coolerdash.png +image_shutdown=/opt/coolerdash/images/shutdown.png +pid=/tmp/coolerdash.pid + +[display] +width=240 +height=240 +refresh_interval_sec=2 +refresh_interval_nsec=500000000 +brightness=80 +orientation=0 + +[layout] +box_width=240 +box_height=120 +box_gap=0 +bar_width=230 +bar_height=22 +bar_gap=10 +bar_border_width=1.5 + +[bar_color_background] +r=52 +g=52 +b=52 + +[bar_color_border] +r=192 +g=192 +b=192 + +[font] +font_face=Roboto Black +font_size_temp=100.0 +font_size_labels=30.0 + +[font_color_temp] +r=255 +g=255 +b=255 + +[font_color_label] +r=200 +g=200 +b=200 + +[temperature] +temp_threshold_1=55.0 +temp_threshold_2=65.0 +temp_threshold_3=75.0 + +[temp_threshold_1_bar] +r=0 +g=255 +b=0 + +[temp_threshold_2_bar] +r=255 +g=140 +b=0 + +[temp_threshold_3_bar] +r=255 +g=70 +b=0 + +[temp_threshold_4_bar] +r=255 +g=0 +b=0 +``` + +--- + +## 🔧 Troubleshooting + +### Common Issues + +1. **Display not updating**: Check refresh intervals and restart service +2. **Wrong colors**: Verify RGB values are 0-255 +3. **Text too small/large**: Adjust font_size_temp and font_size_labels +4. **Bars don't fit**: Ensure bar_width < box_width + +### Testing Changes + +Always restart the service after configuration changes: +```bash +sudo systemctl restart coolerdash +sudo systemctl status coolerdash +``` + +### Backup Configuration + +Before making changes, backup your working configuration: +```bash +sudo cp /etc/coolerdash/config.ini /etc/coolerdash/config.ini.backup +``` diff --git a/docs/devices.md b/docs/devices.md new file mode 100644 index 0000000..148418f --- /dev/null +++ b/docs/devices.md @@ -0,0 +1,28 @@ +## Supported Devices (Compatibility List) + +### This file contains a community-maintained list of devices confirmed to work with **Coolerdash**. +### If Coolerdash works or not on your device, please submit a [Device Confirmation Issue](https://github.com/damachine/coolerdash/issues/new?template=device-confirmation.yml). +--- + +## Confirmed Devices + +| Manufacturer | Model / Device | Status | Tester (GitHub) | Date | +|------------------|--------------------------|---------------|-------------------------|------------| +| NZXT | Kraken 2023 | ✅ Working | @damachine | 2025-06-08 | +| NZXT | Kraken 2023 | ✅ Working | @Kimloc (discord) | 2025-08-27 | +| NZXT | Kraken 2023 | ✅ Working | @olivetti80 | 2025-09-12 | +| ? | ? | ? | ? | ? | +| ? | ? | ? | ? | ? | +| ? | ? | ? | ? | ? | +| ? | ? | ? | ? | ? | +| ? | ? | ? | ? | ? | +| ? | ? | ? | ? | ? | +| ? | ? | ? | ? | ? | +|------------------|--------------------------|---------------|-------------------------|------------| + +--- + +### Legend +- ✅ Working +- ⚠️ Partially working (with limitations) +- ❌ Not working diff --git a/etc/coolerdash/config.ini b/etc/coolerdash/config.ini index a9050b4..582b819 100644 --- a/etc/coolerdash/config.ini +++ b/etc/coolerdash/config.ini @@ -1,117 +1,146 @@ -; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; -; Author: damachine (christkue79@gmail.com) -; Maintainer: damachine -; Website: https://github.com/damachine -; Copyright: (c) 2025 damachine -; License: MIT -; Version: 1.0 -; This software is provided "as is", without warranty of any kind, express or implied. -; I do not guarantee that it will work as intended on your system. -; -; Disclaimer: -; CoolerDash configuration file. -; This file contains settings for the CoolerDash application. -; Restart the service or coolerdash after editing this file: sudo systemctl restart coolerdash.service -; Values not set in this file will be set to default values. -; The comments provide explanations for each setting and its purpose. -; Display resolution is automatically detected by CoolerControl and does not need to be set in this file. -; Temperatures are displayed in Celsius. -; The layout settings can be adjusted to optimize readability based on personal preferences. -; The temperature settings define the color-coding scheme for temperature bars, allowing customization of visual feedback. -; The font settings can be adjusted to change the appearance of the display text. -; The color settings can be adjusted to change the appearance of the display bars and text. -; The daemon settings define the API communication parameters for CoolerControl. Automatic fallbacks are in place, so it is safe to omit these settings. -; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; -; -; -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -; CC-DAEMON SETTINGS ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -; This section defines the daemon settings, including the address and password for API communication. -; Ensure the address matches the daemon's configuration and the password is secure. -; SSL is supported by using the hhtps:// protocol in the daemon_address setting. -; Only modify if daemon settings don't work or cause problems. +# ═══════════════════════════════════════════════════════════════════════════════ +# CoolerDash Configuration File +# ═══════════════════════════════════════════════════════════════════════════════ +# Author: damachine (christkue79@gmail.com) +# Website: https://github.com/damachine +# Version: 1.0 | License: MIT +# +# IMPORTANT: Restart CoolerDash after changes: sudo systemctl restart coolerdash +# ─────────────────────────────────────────────────────────────────────────────── + +# ═══════════════════════════════════════════════════════════════════════════════ +# 🌐 DAEMON SETTINGS +# ═══════════════════════════════════════════════════════════════════════════════ +# Connection settings for CoolerControl daemon API [daemon] -;address=http://localhost:11987 ; URL address for CoolerControl daemon API. Used for communication. -;password=coolAdmin ; Password for CoolerControl daemon API. Must match daemon config. -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -; HARDWARE SETTINGS ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -; This section defines the hardware monitor settings, including paths to sensor data and image directories. -; Ensure the paths are correct for your system to avoid issues with sensor data retrieval and image display. +# API endpoint (default: http://localhost:11987) +;address=http://localhost:11987 + +# API password (default: coolAdmin) +;password=coolAdmin + +# ═══════════════════════════════════════════════════════════════════════════════ +# 📁 FILE PATHS +# ═══════════════════════════════════════════════════════════════════════════════ +# System file and directory locations [paths] -;images=/opt/coolerdash/images ; Directory where images are stored for display and shutdown. -;image_coolerdash=/tmp/coolerdash.png ; Path for temporary image file generated at runtime. -;image_shutdown=/opt/coolerdash/images/shutdown.png ; Image shown on LCD when service stops or system shuts down. -;pid=/tmp/coolerdash.pid ; File storing the daemon's process ID for service management (user-accessible). -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -; DISPLAY SETTINGS ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -; This section defines the display dimensions, refresh rates, brightness, and orientation. -; The following settings were tested with an NZXT Kraken 2023 -; Ensure the values match your hardware specifications to avoid display issues. +# Images directory +;images=/opt/coolerdash/images + +# Generated display image +;image_coolerdash=/tmp/coolerdash.png + +# Shutdown screen image +;image_shutdown=/opt/coolerdash/images/shutdown.png + +# Process ID file +;pid=/tmp/coolerdash.pid + +# ═══════════════════════════════════════════════════════════════════════════════ +# 🖥️ DISPLAY SETTINGS +# ═══════════════════════════════════════════════════════════════════════════════ +# LCD display configuration (tested with NZXT Kraken 2023) [display] -;width=240 ; Sets the width of the LCD display in pixels. Must match your hardware specification. -;height=240 ; Sets the height of the LCD display in pixels. Must match your hardware specification. -;refresh_interval_sec=2 ; Interval in seconds between display refreshes. Lower values update more frequently. -;refresh_interval_nsec=500000000 ; Additional nanoseconds for display refresh interval. Fine-tune for precise timing. (value of 500000000 are 0.5 seconds) -;brightness=80 ; LCD brightness percentage (0-100). Higher values mean a brighter display. (default is 80 to avoid burn-in and flicker) -;orientation=0 ; Display orientation in degrees. Valid values: 0, 90, 180, 270. Rotates the screen output. -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -; LAYOUT SETTINGS ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -; This section defines the layout of the display boxes, including dimensions, gaps, and border thickness. -; Adjust these values to customize the appearance of the display. +# Screen dimensions (pixels) +;width=240 +;height=240 + +# Update frequency +;refresh_interval_sec=2 +;refresh_interval_nsec=500000000 # 0.5 seconds + +# Brightness (0-100%) - 80% recommended to prevent burn-in +;brightness=80 + +# Screen rotation (0°, 90°, 180°, 270°) +;orientation=0 + +# ═══════════════════════════════════════════════════════════════════════════════ +# 🎨 VISUAL LAYOUT +# ═══════════════════════════════════════════════════════════════════════════════ +# Display layout and spacing [layout] -;box_width=240 ; Width of the main display box in pixels. Should match display width for full coverage. -;box_height=120 ; Height of the main display box in pixels. Should only bee the half of the display height. -;box_gap=0 ; Gap in pixels between display boxes. Set to 0 for no gap. -;bar_width=230 ; Width of temperature/usage bars in pixels. Should fit inside box_width. -;bar_height=22 ; Height of temperature/usage bars in pixels. Controls bar thickness. -;bar_gap=10 ; Gap in pixels between bars. Increase for more spacing between bars. -;bar_border_width=1.5 ; Thickness of border lines in pixels. Use decimals for fine control. +# Box dimensions +;box_width=240 # Full width coverage +;box_height=120 # Half screen height +;box_gap=0 # No spacing between boxes + +# Temperature bars +;bar_width=230 # Fits within box width +;bar_height=22 # Bar thickness +;bar_gap=10 # Spacing between bars +;bar_border_width=1.5 + +# ═══════════════════════════════════════════════════════════════════════════════ +# 🎨 COLORS +# ═══════════════════════════════════════════════════════════════════════════════ +# RGB color values (0-255) + +# Bar styling [bar_color_background] -;r=52 ; Red color component for bar background (0-255). -;g=52 ; Green color component for bar background (0-255). -;b=52 ; Blue color component for bar background (0-255). +;r=52 # Dark gray background +;g=52 +;b=52 + [bar_color_border] -;r=192 ; Red color component for bar border (0-255). -;g=192 ; Green color component for bar border (0-255). -;b=192 ; Blue color component for bar border (0-255). -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -; FONT SETTINGS ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -; This section defines the font settings for display text, including family, size, and style. -; Ensure the specified font is installed on the system to avoid display issues. +;r=192 # Light gray border +;g=192 +;b=192 + +# ═══════════════════════════════════════════════════════════════════════════════ +# 🔤 FONTS +# ═══════════════════════════════════════════════════════════════════════════════ +# Text appearance settings [font] -;font_face=Roboto Black ; Font family and style used for all display text. Must be installed on system. -;font_size_temp=100.0 ; Font size for main temperature/usage values. Large for visibility. -;font_size_labels=30.0 ; Font size for labels and units. Should be readable but not dominant. +# Font family (must be installed) +;font_face=Roboto Black + +# Font sizes +;font_size_temp=100.0 # Large numbers +;font_size_labels=30.0 # Small labels + +# Text colors (RGB 0-255) [font_color_temp] -;r=255 ; Red color component for temperature value (0-255). -;g=255 ; Green color component for temperature value (0-255). -;b=255 ; Blue color component for temperature value (0-255). +;r=255 # White temperature text +;g=255 +;b=255 + [font_color_label] -;r=200 ; Red color component for label text (0-255). -;g=200 ; Green color component for label text (0-255). -;b=200 ; Blue color component for label text (0-255). -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -; TEMPERATURE SETTINGS ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -; the temperature section defines the temperature thresholds and their corresponding colors. -; Adjust these values to control the color coding of temperature bars. +;r=200 # Light gray labels +;g=200 +;b=200 + +# ═══════════════════════════════════════════════════════════════════════════════ +# 🌡️ TEMPERATURE ZONES +# ═══════════════════════════════════════════════════════════════════════════════ +# Color-coded temperature thresholds (°C) [temperature] -;temp_threshold_1=55.0 ; Temperature (°C) > temp_threshold_1 shown in green. -;temp_threshold_2=65.0 ; Temperature (°C) <= temp_threshold_1 - temp_threshold_2 show in orange. -;temp_threshold_3=75.0 ; Temperature (°C) <= temp_threshold_2 - temp_threshold_3 show in hot orange and all <= temp_threshold_3 show temp_threshold_4_bar. -;[temp_threshold_1_bar] -;r=0 ; Red color component for green status (0-255). -;g=255 ; Green color component for green status (0-255). -;b=0 ; Blue color component for green status (0-255). -[temp_threshold_2_bar] -;r=255 ; Red color component for orange status (0-255). -;g=140 ; Green color component for orange status (0-255). -;b=0 ; Blue color component for orange status (0-255). -[temp_threshold_3_bar] -;r=255 ; Red color component for hot orange status (0-255). -;g=70 ; Green color component for hot orange status (0-255). -;b=0 ; Blue color component for hot orange status (0-255). -[temp_threshold_4_bar] -;r=255 ; Red color component for red status (0-255). -;g=0 ; Green color component for red status (0-255). -;b=0 ; Blue color component for red status (0-255). -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;temp_threshold_1=55.0 # Green zone (< 55°C) +;temp_threshold_2=65.0 # Orange zone (55-65°C) +;temp_threshold_3=75.0 # Hot zone (65-75°C) + # Red zone (> 75°C) + +# Temperature zone colors (RGB 0-255) +[temp_threshold_1_bar] # 💚 Cool (Green) +;r=0 +;g=255 +;b=0 + +[temp_threshold_2_bar] # 🧡 Warm (Orange) +;r=255 +;g=140 +;b=0 + +[temp_threshold_3_bar] # 🔥 Hot (Dark Orange) +;r=255 +;g=70 +;b=0 + +[temp_threshold_4_bar] # 🚨 Critical (Red) +;r=255 +;g=0 +;b=0 + +# ═══════════════════════════════════════════════════════════════════════════════ +# End of Configuration +# ═══════════════════════════════════════════════════════════════════════════════ \ No newline at end of file diff --git a/etc/systemd/coolerdash.service b/etc/systemd/coolerdash.service index 7f0cd99..28e4667 100644 --- a/etc/systemd/coolerdash.service +++ b/etc/systemd/coolerdash.service @@ -23,7 +23,7 @@ # Example: # To enable and start the service, use: # 'sudo systemctl enable coolerdash.service' -# 's'udo systemctl start coolerdash.service' +# 'sudo systemctl start coolerdash.service' # To check the status of the service, use: # 'systemctl status coolerdash.service' # ----------------------------------------------------------------------------- @@ -31,8 +31,8 @@ [Unit] Description=CoolerDash daemon Documentation=man:coolerdash(1) -After=coolercontrol-liqctld.service coolercontrold.service -Requires=coolercontrol-liqctld.service coolercontrold.service +After=coolercontrold.service +Requires=coolercontrold.service [Service] Type=simple diff --git a/images/banner.jpg b/images/banner.jpg new file mode 100644 index 0000000..728292c Binary files /dev/null and b/images/banner.jpg differ diff --git a/images/banner.png b/images/banner.png new file mode 100644 index 0000000..2c5a5b9 Binary files /dev/null and b/images/banner.png differ diff --git a/images/coolerdash.jpg b/images/coolerdash.jpg new file mode 100644 index 0000000..15df908 Binary files /dev/null and b/images/coolerdash.jpg differ diff --git a/images/gpt.png b/images/gpt.png deleted file mode 100644 index c825690..0000000 Binary files a/images/gpt.png and /dev/null differ diff --git a/images/gpt3.png b/images/quad.png similarity index 100% rename from images/gpt3.png rename to images/quad.png diff --git a/images/gpt2.png b/images/round.png similarity index 100% rename from images/gpt2.png rename to images/round.png diff --git a/man/coolerdash.1 b/man/coolerdash.1 index e534bcf..bd6dd88 100644 --- a/man/coolerdash.1 +++ b/man/coolerdash.1 @@ -1,253 +1,120 @@ - -.TH COOLERDASH 1 "August 2025" "coolerdash 1.27" "User Commands" +.TH COOLERDASH 1 "26 September 2025" "CoolerDash 1.76.2" "User Commands" .SH NAME -coolerdash \- LCD dashboard for CoolerControl - +CoolerDash \- LCD dashboard renderer for CoolerControl devices .SH SYNOPSIS .B coolerdash -.RI [ --log ] -.br -.B coolerdash -.RI [ --help ] - +[\fIOPTIONS\fR] .SH DESCRIPTION -.B CoolerDash -CoolerDash is a wrapper tool that extends CoolerControl. It turns your water cooling display into a smart, stylish information hub—beyond the default features of CoolerControl alone. +CoolerDash generates and uploads images to AIO liquid cooler LCD displays using data provided by the CoolerControl daemon. +It extends CoolerControl's LCD functionality with additional sensors, improved layout, and configurable behavior. -.SH FEATURES +This manual page summarizes installation, configuration and common usage patterns. For full documentation see the project's README and docs directory. +.SH OPTIONS +.TP +.B \-\-help +Show brief help and exit. +.TP +.B \-l, \-\-log +Enable detailed debug logging. +.TP +.B \-c, \-\-config \fIFILE\fR +Use alternate configuration file (default: /etc/coolerdash/config.ini). +.SH INSTALLATION +.PP +Arch Linux (recommended): .RS -- Displays comprehensive system monitoring data (CPU/GPU temperatures, etc.) directly on your LCD using CoolerControl Open-API integration. -- Supports all LCD displays compatible with CoolerControl (e.g. NZXT, etc.). -- Optimized for x86-64-v3 CPUs (Intel Haswell+ 2013+ / AMD Excavator+ 2015+). For older CPUs, use \fBCFLAGS=-march=x86-64 make\fR. -- Clean production logging by default, detailed debug logging with \fB--log\fR. +.SS Using an AUR helper +.EX +$ yay -S coolerdash-git +.EE .RE - -.SH REQUIREMENTS +.PP +Manual install: .RS -- Linux operating system -- CoolerControl (must be installed and running) -- Supported LCD display (see CoolerControl docs) -- GCC (C99), make, pkg-config -- Libraries: cairo, curl, jansson, inih +.EX +$ git clone https://github.com/damachine/coolerdash.git +$ cd coolerdash +$ make install +$ sudo systemctl daemon-reload +$ sudo systemctl enable --now coolerdash.service +.EE .RE - - -.SH EXAMPLES -.TP -Start program (minimal logging) -.B coolerdash -.TP -Start program with detailed debug logging -.B coolerdash --log -.TP -Show help: -.B coolerdash --help -.TP -Service management: -.B systemctl start coolerdash -.br -.B systemctl status coolerdash -.br -.B journalctl -u coolerdash -f - -.SH LOGGING SYSTEM -CoolerDash uses an intelligent four-level logging system: -.TP -.B STATUS -Important startup and operational messages (always shown, visible in systemd logs) -.TP -.B INFO -Detailed debug information (only shown with \fB\-\-log\fR parameter) -.TP -.B WARNING -Non-critical issues (always shown) -.TP -.B ERROR -Critical errors (always shown) -.TP -.B Examples -.B coolerdash -.br -Shows STATUS, WARNING, and ERROR messages only for clean production logs. -.br -.B coolerdash --log -.br -Shows all logging levels including detailed INFO messages for debugging. -.br -.B journalctl -u coolerdash.service -.br -View systemd logs (STATUS messages always visible without --log parameter). - -.SH SYSTEMD INTEGRATION -The program provides systemd integration with detailed logs: -.TP -.B Service Installation -.B make install -.br -Automatically installs the service and restarts it on updates. -.TP -.B Service Management -.B systemctl enable coolerdash.service -.br -.B systemctl start coolerdash.service -.br -.B systemctl status coolerdash.service -.TP -.B Detailed Logs -.B journalctl -u coolerdash.service --since "10 minutes ago" -.br -Shows module initialization, CoolerControl connection, and operating status. -Important STATUS messages are always visible without requiring \fB\-\-log\fR parameter. -.br -.B journalctl -u coolerdash.service -f -.br -Live log monitoring for real-time troubleshooting. -.TP -.B Makefile Service Shortcuts -.B make start, make stop, make restart, make status, make logs - -.SH SECURITY ARCHITECTURE -CoolerDash follows security best practices and does not require root privileges: -.TP -.B Dedicated User Service -Runs as user 'coolerdash' via systemd, not as root. -.TP -.B API Communication Only -Communicates via CoolerControl's HTTP REST API (port 11987) - no direct hardware access. -.TP - +.SH REQUIREMENTS +.PP +.IP "Operating system:" 4 +Linux +.IP "CoolerControl:" 4 +Version >= 2.2.2 (must be installed and the coolercontrold daemon running) +.IP "CPU:" 4 +x86-64-v3 compatible (Intel Haswell+ / AMD Excavator+) +.IP "LCD:" 4 +AIO liquid cooler displays (NZXT, etc.) .SH CONFIGURATION -All important runtime and build-time settings are managed via configuration files: -.TP -.B /etc/coolerdash/config.ini (RECOMMENDED) -Main runtime configuration file. Edit this file to change display, thresholds, colors, paths, and daemon settings without recompiling. -After editing, restart the service with: -.br -\fBsystemctl restart coolerdash.service\fR -.br - -.SH PATH MANAGEMENT -CoolerDash uses intelligent path management for optimal compatibility: -.TP -.B Universal Access -All temporary files are stored in /tmp/ for universal read/write access without permission issues. -.TP -.B Automatic Cleanup -Temporary files are automatically cleaned up on normal shutdown and signal termination: -.br -• Image files: /tmp/coolerdash.png -.br -• PID files: /tmp/coolerdash.pid -.TP -.B File Lifecycle -Normal shutdown: Files cleaned via main cleanup routine -.br -Signal termination (SIGTERM/SIGINT): Files cleaned via signal handler -.br -Force kill (SIGKILL): Files may remain but are cleaned on next start - -.SH DEPENDENCIES -.TP -.B CoolerControl (REQUIRED) -Must be installed and running (coolercontrold.service and coolercontrol-liqctld.service) -.br -Installation guide: https://gitlab.com/coolercontrol/coolercontrol/-/blob/main/README.md -.TP -.B Build Dependencies -GCC with C99 support, Make, pkg-config -.TP -.B Runtime Libraries -Cairo (graphics rendering), cURL (HTTP client), jansson (JSON parsing), inih (INI file parsing) - -.SH INSTALLATION -Installs the main binary to /usr/bin/coolerdash. -Resources and images are installed to /opt/coolerdash/. -Systemd service file is installed to /etc/systemd/system/coolerdash.service. -Default config file is installed to /etc/coolerdash/config.ini. -Temporary files are managed in /tmp/ for universal access. -.TP -.B Standard Installation -.B make install -.br -Installs all components and enables the systemd service. -.TP -.B PKGBUILD and AUR -Arch Linux users can build and install via PKGBUILD and AUR. See PKGBUILD for details. - -.SH UNINSTALL -.TP -.B Standard Uninstall -.B make uninstall -.br -Stops and disables the service, removes all installed files from /usr/bin/, /opt/coolerdash/, and /etc/systemd/system/. -The config file /etc/coolerdash/config.ini is preserved unless unchanged from defaults. -.TP -.B Manual Cleanup -If needed, manually remove remaining files: -.br -.B rm -rf /opt/coolerdash/ /etc/coolerdash/ -.TP -.B PKGBUILD -Arch Linux users can uninstall via package manager: \fBpacman -R coolerdash\fR - +Configuration is managed in /etc/coolerdash/config.ini. Edit values and restart the service: +.RS +.EX +$ sudo systemctl restart coolerdash.service +.EE +.RE +.PP +In CoolerControl GUI select one sensor for CPU and one for GPU and set the LCD output type to Image/GIF. .SH USAGE +.PP +Run the daemon via systemd or manually for debugging: +.RS +.EX +# Run as installed service +$ sudo systemctl enable --now coolerdash.service + +# Run manually (foreground) +$ coolerdash +# Detailed debug output +$ coolerdash --log +.EE +.RE +.SH DEBUGGING +Common troubleshooting steps: .TP -.B Start program (clean production logging): -.B coolerdash -.TP -.B Start program with detailed debug logging: -.B coolerdash --log -.TP -.B Show help: -.B coolerdash --help -.TP -.B Service management: -.B systemctl start coolerdash -.br -.B systemctl status coolerdash -.br -.B systemctl restart coolerdash -.br -.B journalctl -u coolerdash -f - -> **Note:** The systemd service must be stopped before running manually to avoid conflicts: -.B -systemctl stop coolerdash.service - -.SH OPTIONS -.TP -.B \-\-log -Enable detailed INFO logging for debugging purposes. Without this parameter, only STATUS, WARNING, and ERROR messages are shown for clean production logs. +.B "Check CoolerControl" +$ systemctl status coolercontrold +$ curl http://localhost:11987/devices | jq .TP -.B \-\-help -Display help information and exit. - -.SH NOTES -- All runtime settings can be changed via /etc/coolerdash/config.ini. -- After editing config.ini, restart the service to apply changes. -- If config.ini is missing, build-time defaults from include/config.h are used. -- Intelligent path management uses /tmp/ for universal access without permission issues. -- Double LCD transmission ensures reliable image upload with enhanced stability. -- Automatic file cleanup removes temporary files on shutdown/termination. -- Optimized refresh cycles provide immediate LCD updates without unnecessary delays. -- All code is documented with Doxygen-style comments and follows strict coding standards. -- See PKGBUILD and Makefile for build and packaging documentation. - -.SH BUGS -Report bugs to: christkue79@gmail.com or via GitHub Issues: https://github.com/damachine/coolerdash/issues +.B "Journal" +$ journalctl -u coolerdash.service -f .TP -- All known bugs are documented in the source code with Doxygen @bug comments. - -.SH AUTHOR -Written by DAMACHINE (christkue79@gmail.com). +.B "Manual run" +Stop the systemd service before running manually to avoid conflicts: +.RS +.EX +$ sudo systemctl stop coolerdash.service +$ coolerdash --log +.EE +.RE +.SH FILES +.IP "/etc/coolerdash/config.ini" 4 +Main configuration file. +.IP "/usr/bin/coolerdash" 4 +Installed binary (if installed system-wide). +.IP "/usr/share/man/man1/coolerdash.1.gz" 4 +Man page (when installed). +.SH EXAMPLES +.RS +.EX +# Render once from command line (example) +$ coolerdash +# Check devices +$ curl http://localhost:11987/devices | jq +.EE +.RE .SH SEE ALSO -.BR systemctl (1), -.BR coolercontrol (1), -.BR journalctl (1) -.br -Project repository: https://github.com/damachine/coolerdash - +.BR coolercontrold (8), +.BR jq (1), +.BR systemctl (1) +.SH AUTHOR +Developed by DAMACHINE \ .SH COPYRIGHT -Copyright © 2025 DAMACHINE. This is free software; see source for copying conditions. -Released under MIT License. +Copyright © 2025 DAMACHINE. Distributed under the MIT License. +.SH BUGS +Report issues and feature requests at: +https://github.com/damachine/coolerdash/issues \ No newline at end of file diff --git a/src/config.c b/src/config.c index 55931de..1f6d75e 100644 --- a/src/config.c +++ b/src/config.c @@ -14,8 +14,8 @@ * @details Parses the configuration file and sets values in the Config struct. */ - // Include necessary headers +// cppcheck-suppress-begin missingIncludeSystem #include #include #include @@ -24,35 +24,47 @@ #include #include #include +// cppcheck-suppress-end missingIncludeSystem // Include project headers -#include "../include/config.h" -#include "../include/coolercontrol.h" +#include "config.h" +#include "coolercontrol.h" /** - * @brief Globale Logging-Implementierung für alle Module außer main.c - * @details Einheitliche Log-Ausgabe für Info, Status, Warnung und Fehler. + * @brief Global logging implementation for all modules except main.c + * @details Provides unified log output for info, status, warning and error messages. */ -void log_message(log_level_t level, const char *format, ...) { - if (level == LOG_INFO && !verbose_logging) { +void log_message(log_level_t level, const char *format, ...) +{ + if (level == LOG_INFO && !verbose_logging) + { return; } const char *prefix[] = {"INFO", "STATUS", "WARNING", "ERROR"}; FILE *output = (level == LOG_ERROR) ? stderr : stdout; fprintf(output, "[CoolerDash %s] ", prefix[level]); + + enum + { + LOG_MSG_CAP = 1024 + }; + char msg_buf[LOG_MSG_CAP]; + msg_buf[0] = '\0'; va_list args; va_start(args, format); - vfprintf(output, format, args); + vsnprintf(msg_buf, sizeof(msg_buf), (format ? format : "(null)"), args); va_end(args); - fprintf(output, "\n"); + fputs(msg_buf, output); + fputc('\n', output); fflush(output); } -// Helper function for safe string copying -#define SAFE_STRCPY(dest, src) do { \ - strncpy(dest, src, sizeof(dest) - 1); \ - dest[sizeof(dest) - 1] = '\0'; \ -} while(0) +// Helper macro for safe string copying using project helper (guarantees termination & bounds) +#define SAFE_STRCPY(dest, src) \ + do \ + { \ + cc_safe_strcpy((dest), sizeof(dest), (src)); \ + } while (0) // Forward declarations for static handler functions static int get_daemon_config(Config *config, const char *name, const char *value); @@ -63,18 +75,20 @@ static int get_font_config(Config *config, const char *name, const char *value); static int get_temperature_config(Config *config, const char *name, const char *value); static int get_color_config(Config *config, const char *section, const char *name, const char *value); -// Logging erfolgt zentral über log_message aus config.h - /** * @brief Helper functions for string parsing with validation. * @details Replaces atoi with secure parsing that validates input and handles overflow conditions. */ -static inline int safe_atoi(const char *str, int default_value) { - if (!str || !str[0]) return default_value; +static inline int safe_atoi(const char *str, int default_value) +{ + if (!str || !str[0]) + return default_value; char *endptr; long val = strtol(str, &endptr, 10); - if (endptr == str || *endptr != '\0') return default_value; // Invalid conversion - if (val < INT_MIN || val > INT_MAX) return default_value; // Overflow + if (endptr == str || *endptr != '\0') + return default_value; // Invalid conversion + if (val < INT_MIN || val > INT_MAX) + return default_value; // Overflow return (int)val; } @@ -82,11 +96,14 @@ static inline int safe_atoi(const char *str, int default_value) { * @brief Helper functions for floating-point parsing with validation. * @details Replaces atof with a safer version that validates input and handles overflow conditions. */ -static inline float safe_atof(const char *str, float default_value) { - if (!str || !str[0]) return default_value; +static inline float safe_atof(const char *str, float default_value) +{ + if (!str || !str[0]) + return default_value; char *endptr; float val = strtof(str, &endptr); - if (endptr == str || *endptr != '\0') return default_value; // Invalid conversion + if (endptr == str || *endptr != '\0') + return default_value; // Invalid conversion return val; } @@ -94,316 +111,574 @@ static inline float safe_atof(const char *str, float default_value) { * @brief Helper function for secure color parsing with validation. * @details Parses RGB color values and automatically validates with uint8_t type safety, clamping values to 0-255 range. */ -static void parse_color_component(const char *value, uint8_t *component) { - if (!value || !component) return; +static void parse_color_component(const char *value, uint8_t *component) +{ + if (!value || !component) + return; int val = safe_atoi(value, 0); - *component = (val < 0) ? 0 : (val > 255) ? 255 : (uint8_t)val; + *component = (val < 0) ? 0 : (val > 255) ? 255 + : (uint8_t)val; } /** - * @brief Main INI parser handler, delegates to section-specific handlers. - * @details Called for each key-value pair in the INI file. Routes to appropriate section handler for cleaner code organization. + * @brief Check if section matches color pattern. + * @details Helper function to identify color-related sections. */ -static int parse_config_data(void *user, const char *section, const char *name, const char *value) { +static inline int is_color_section(const char *section) +{ + return (strstr(section, "color") != NULL || strstr(section, "_bar") != NULL); +} + +/** + * @brief Main INI parser handler with reduced complexity. + * @details Simplified parser using early returns to reduce nesting and improve readability. + */ +static int parse_config_data(void *user, const char *section, const char *name, const char *value) +{ Config *config = (Config *)user; - if (strcmp(section, "daemon") == 0) { + // Handle standard sections with early returns + if (strcmp(section, "daemon") == 0) + { return get_daemon_config(config, name, value); - } else if (strcmp(section, "paths") == 0) { + } + if (strcmp(section, "paths") == 0) + { return get_paths_config(config, name, value); - } else if (strcmp(section, "display") == 0) { + } + if (strcmp(section, "display") == 0) + { return get_display_config(config, name, value); - } else if (strcmp(section, "layout") == 0) { + } + if (strcmp(section, "layout") == 0) + { return get_layout_config(config, name, value); - } else if (strcmp(section, "font") == 0) { + } + if (strcmp(section, "font") == 0) + { return get_font_config(config, name, value); - } else if (strcmp(section, "temperature") == 0) { + } + if (strcmp(section, "temperature") == 0) + { return get_temperature_config(config, name, value); - } else if (strstr(section, "color") != NULL || strstr(section, "_bar") != NULL) { - // Handle all color sections (bar_color_*, font_color_*, temp_threshold_*_bar) + } + + // Handle color sections + if (is_color_section(section)) + { return get_color_config(config, section, name, value); } - + return 1; } /** - * @brief Handle daemon section configuration. - * @details Processes daemon-related configuration keys (address, password) with safe string copying. + * @brief String configuration entry for lookup table. + * @details Structure for string configuration keys. */ -static int get_daemon_config(Config *config, const char *name, const char *value) { - if (strcmp(name, "address") == 0) { - if (value && value[0] != '\0') { - SAFE_STRCPY(config->daemon_address, value); - } - } else if (strcmp(name, "password") == 0) { - if (value && value[0] != '\0') { - SAFE_STRCPY(config->daemon_password, value); +typedef struct { + const char *key; + size_t offset; + size_t size; +} StringConfigEntry; + +/** + * @brief Generic helper for string-based configuration sections. + * @details Processes string configuration keys using lookup table approach. + */ +static int handle_string_config(Config *config, const char *name, const char *value, + const StringConfigEntry *entries, size_t entry_count) +{ + if (!value || value[0] == '\0') return 1; + + for (size_t i = 0; i < entry_count; i++) { + if (strcmp(name, entries[i].key) == 0) { + char *dest = (char*)config + entries[i].offset; + cc_safe_strcpy(dest, entries[i].size, value); + return 1; } } return 1; } /** - * @brief Handle paths section configuration. - * @details Processes path-related configuration keys (images, pid, etc.) with validation and safe string operations. + * @brief Handle daemon section configuration. + * @details Processes daemon-related configuration keys using lookup table approach. */ -static int get_paths_config(Config *config, const char *name, const char *value) { - if (strcmp(name, "images") == 0) { - if (value && value[0] != '\0') { - SAFE_STRCPY(config->paths_images, value); - } - } else if (strcmp(name, "image_coolerdash") == 0) { - if (value && value[0] != '\0') { - SAFE_STRCPY(config->paths_image_coolerdash, value); - } - } else if (strcmp(name, "image_shutdown") == 0) { - if (value && value[0] != '\0') { - SAFE_STRCPY(config->paths_image_shutdown, value); - } - } else if (strcmp(name, "pid") == 0) { - if (value && value[0] != '\0') { - SAFE_STRCPY(config->paths_pid, value); +static int get_daemon_config(Config *config, const char *name, const char *value) +{ + static const StringConfigEntry entries[] = { + {"address", offsetof(Config, daemon_address), sizeof(config->daemon_address)}, + {"password", offsetof(Config, daemon_password), sizeof(config->daemon_password)} + }; + + return handle_string_config(config, name, value, entries, sizeof(entries) / sizeof(entries[0])); +} + +/** + * @brief Handle paths section configuration with reduced complexity. + * @details Processes path-related configuration keys using lookup table approach. + */ +static int get_paths_config(Config *config, const char *name, const char *value) +{ + static const StringConfigEntry entries[] = { + {"images", offsetof(Config, paths_images), sizeof(config->paths_images)}, + {"image_coolerdash", offsetof(Config, paths_image_coolerdash), sizeof(config->paths_image_coolerdash)}, + {"image_shutdown", offsetof(Config, paths_image_shutdown), sizeof(config->paths_image_shutdown)}, + {"pid", offsetof(Config, paths_pid), sizeof(config->paths_pid)} + }; + + return handle_string_config(config, name, value, entries, sizeof(entries) / sizeof(entries[0])); +} + +/** + * @brief Validate orientation value. + * @details Helper function to validate LCD orientation values. + */ +static inline int is_valid_orientation(int orientation) +{ + return (orientation == 0 || orientation == 90 || orientation == 180); +} + +/** + * @brief Display configuration handler function type. + * @details Function pointer type for individual display configuration handlers. + */ +typedef void (*DisplayConfigHandler)(Config *config, const char *value); + +/** + * @brief Display configuration handlers. + * @details Individual handlers for each display configuration key. + */ +static void handle_display_width(Config *config, const char *value) { + int width = safe_atoi(value, 0); + config->display_width = (width > 0) ? (uint16_t)width : 0; +} + +static void handle_display_height(Config *config, const char *value) { + int height = safe_atoi(value, 0); + config->display_height = (height > 0) ? (uint16_t)height : 0; +} + +static void handle_refresh_interval_sec(Config *config, const char *value) { + config->display_refresh_interval_sec = safe_atoi(value, 0); +} + +static void handle_refresh_interval_nsec(Config *config, const char *value) { + config->display_refresh_interval_nsec = safe_atoi(value, 0); +} + +static void handle_brightness(Config *config, const char *value) { + int brightness = safe_atoi(value, 0); + config->lcd_brightness = (brightness >= 0 && brightness <= 100) ? (uint8_t)brightness : 0; +} + +static void handle_orientation(Config *config, const char *value) { + int orientation = safe_atoi(value, 0); + config->lcd_orientation = is_valid_orientation(orientation) ? orientation : 0; +} + +/** + * @brief Display configuration entry for lookup table. + */ +typedef struct { + const char *key; + DisplayConfigHandler handler; +} DisplayConfigEntry; + +/** + * @brief Handle display section configuration with reduced complexity. + * @details Processes display-related configuration keys using lookup table approach. + */ +static int get_display_config(Config *config, const char *name, const char *value) +{ + static const DisplayConfigEntry entries[] = { + {"width", handle_display_width}, + {"height", handle_display_height}, + {"refresh_interval_sec", handle_refresh_interval_sec}, + {"refresh_interval_nsec", handle_refresh_interval_nsec}, + {"brightness", handle_brightness}, + {"orientation", handle_orientation} + }; + + for (size_t i = 0; i < sizeof(entries) / sizeof(entries[0]); i++) { + if (strcmp(name, entries[i].key) == 0) { + entries[i].handler(config, value); + return 1; } } return 1; } /** - * @brief Handle display section configuration. - * @details Processes display-related configuration keys (width, height, brightness, etc.) with range validation and type safety. - */ -static int get_display_config(Config *config, const char *name, const char *value) { - if (strcmp(name, "width") == 0) { - int width = safe_atoi(value, 0); - config->display_width = (width > 0) ? (uint16_t)width : 0; - } else if (strcmp(name, "height") == 0) { - int height = safe_atoi(value, 0); - config->display_height = (height > 0) ? (uint16_t)height : 0; - } else if (strcmp(name, "refresh_interval_sec") == 0) { - config->display_refresh_interval_sec = safe_atoi(value, 0); - } else if (strcmp(name, "refresh_interval_nsec") == 0) { - config->display_refresh_interval_nsec = safe_atoi(value, 0); - } else if (strcmp(name, "brightness") == 0) { - int brightness = safe_atoi(value, 0); - config->lcd_brightness = (brightness >= 0 && brightness <= 100) ? (uint8_t)brightness : 0; - } else if (strcmp(name, "orientation") == 0) { - int orientation = safe_atoi(value, 0); - // Only allow 0, 90, 180 degrees (KISS: simple validation) - if (orientation == 0 || orientation == 90 || orientation == 180) { - config->lcd_orientation = orientation; - } else { - config->lcd_orientation = 0; // Default to 0 for invalid values + * @brief Mixed-type configuration entry for lookup table. + */ +typedef struct { + const char *key; + size_t offset; + enum { TYPE_UINT16, TYPE_FLOAT, TYPE_STRING } type; + size_t string_size; +} MixedConfigEntry; + +/** + * @brief Generic helper for mixed-type configuration sections. + * @details Processes mixed-type configuration keys using lookup table approach. + */ +static int handle_mixed_config(Config *config, const char *name, const char *value, + const MixedConfigEntry *entries, size_t entry_count) +{ + for (size_t i = 0; i < entry_count; i++) { + if (strcmp(name, entries[i].key) == 0) { + void *field_ptr = (void*)((char*)config + entries[i].offset); + + switch (entries[i].type) { + case TYPE_UINT16: { + uint16_t *dest = (uint16_t*)field_ptr; + *dest = (uint16_t)safe_atoi(value, 0); + break; + } + case TYPE_FLOAT: { + float *dest = (float*)field_ptr; + *dest = safe_atof(value, 12.0f); + break; + } + case TYPE_STRING: { + if (value && value[0] != '\0') { + char *dest = (char*)field_ptr; + cc_safe_strcpy(dest, entries[i].string_size, value); + } + break; + } + } + return 1; } } return 1; } /** - * @brief Handle layout section configuration. - * @details Processes layout-related configuration keys (box dimensions, bar settings, etc.) with numeric validation. - */ -static int get_layout_config(Config *config, const char *name, const char *value) { - if (strcmp(name, "box_width") == 0) { - config->layout_box_width = safe_atoi(value, 0); - } else if (strcmp(name, "box_height") == 0) { - config->layout_box_height = safe_atoi(value, 0); - } else if (strcmp(name, "box_gap") == 0) { - config->layout_box_gap = safe_atoi(value, 0); - } else if (strcmp(name, "bar_width") == 0) { - config->layout_bar_width = safe_atoi(value, 0); - } else if (strcmp(name, "bar_height") == 0) { - config->layout_bar_height = safe_atoi(value, 0); - } else if (strcmp(name, "bar_gap") == 0) { - config->layout_bar_gap = safe_atoi(value, 0); - } else if (strcmp(name, "bar_border_width") == 0) { - config->layout_bar_border_width = safe_atof(value, 0.0f); + * @brief Handle layout section configuration with reduced complexity. + * @details Processes layout-related configuration keys using lookup table approach. + */ +static int get_layout_config(Config *config, const char *name, const char *value) +{ + static const MixedConfigEntry entries[] = { + {"box_width", offsetof(Config, layout_box_width), TYPE_UINT16, 0}, + {"box_height", offsetof(Config, layout_box_height), TYPE_UINT16, 0}, + {"box_gap", offsetof(Config, layout_box_gap), TYPE_UINT16, 0}, + {"bar_width", offsetof(Config, layout_bar_width), TYPE_UINT16, 0}, + {"bar_height", offsetof(Config, layout_bar_height), TYPE_UINT16, 0}, + {"bar_gap", offsetof(Config, layout_bar_gap), TYPE_UINT16, 0}, + {"bar_border_width", offsetof(Config, layout_bar_border_width), TYPE_FLOAT, 0} + }; + + return handle_mixed_config(config, name, value, entries, sizeof(entries) / sizeof(entries[0])); +} + +/** + * @brief Handle font section configuration with reduced complexity. + * @details Processes font-related configuration keys using lookup table approach. + */ +static int get_font_config(Config *config, const char *name, const char *value) +{ + static const MixedConfigEntry entries[] = { + {"font_face", offsetof(Config, font_face), TYPE_STRING, CONFIG_MAX_FONT_NAME_LEN}, + {"font_size_temp", offsetof(Config, font_size_temp), TYPE_FLOAT, 0}, + {"font_size_labels", offsetof(Config, font_size_labels), TYPE_FLOAT, 0} + }; + + return handle_mixed_config(config, name, value, entries, sizeof(entries) / sizeof(entries[0])); +} + +/** + * @brief Temperature configuration entry for lookup table. + * @details Structure for temperature threshold configuration keys. + */ +typedef struct { + const char *key; + size_t offset; +} TemperatureConfigEntry; + +/** + * @brief Handle temperature section configuration with reduced complexity. + * @details Processes temperature threshold configuration keys using lookup table approach. + */ +static int get_temperature_config(Config *config, const char *name, const char *value) +{ + static const TemperatureConfigEntry entries[] = { + {"temp_threshold_1", offsetof(Config, temp_threshold_1)}, + {"temp_threshold_2", offsetof(Config, temp_threshold_2)}, + {"temp_threshold_3", offsetof(Config, temp_threshold_3)} + }; + + for (size_t i = 0; i < sizeof(entries) / sizeof(entries[0]); i++) { + if (strcmp(name, entries[i].key) == 0) { + void *field_ptr = (void*)((char*)config + entries[i].offset); + float *dest = (float*)field_ptr; + *dest = safe_atof(value, 50.0f + i * 15.0f); // 50, 65, 80 + return 1; + } } return 1; } /** - * @brief Handle font section configuration. - * @details Processes font-related configuration keys (face, sizes) with string and float validation. + * @brief Color section mapping entry for lookup table. + * @details Structure for color section mapping. + */ +typedef struct { + const char *section_name; + size_t color_offset; +} ColorSectionEntry; + +/** + * @brief Color configuration mapping using lookup table. + * @details Uses lookup table to reduce cyclomatic complexity. */ -static int get_font_config(Config *config, const char *name, const char *value) { - if (strcmp(name, "font_face") == 0) { - if (value && value[0] != '\0') { - SAFE_STRCPY(config->font_face, value); +static Color *get_color_pointer_from_section(Config *config, const char *section) +{ + static const ColorSectionEntry entries[] = { + {"bar_color_background", offsetof(Config, layout_bar_color_background)}, + {"bar_color_border", offsetof(Config, layout_bar_color_border)}, + {"font_color_temp", offsetof(Config, font_color_temp)}, + {"font_color_label", offsetof(Config, font_color_label)}, + {"temp_threshold_1_bar", offsetof(Config, temp_threshold_1_bar)}, + {"temp_threshold_2_bar", offsetof(Config, temp_threshold_2_bar)}, + {"temp_threshold_3_bar", offsetof(Config, temp_threshold_3_bar)}, + {"temp_threshold_4_bar", offsetof(Config, temp_threshold_4_bar)} + }; + + for (size_t i = 0; i < sizeof(entries) / sizeof(entries[0]); i++) { + if (strcmp(section, entries[i].section_name) == 0) { + return (Color*)((char*)config + entries[i].color_offset); } - } else if (strcmp(name, "font_size_temp") == 0) { - config->font_size_temp = safe_atof(value, 12.0f); - } else if (strcmp(name, "font_size_labels") == 0) { - config->font_size_labels = safe_atof(value, 10.0f); } - return 1; + return NULL; } /** - * @brief Handle temperature section configuration. - * @details Processes temperature threshold configuration keys with float validation and safe parsing. + * @brief Set color component using lookup approach. + * @details Helper function to set R, G, or B component with reduced complexity. */ -static int get_temperature_config(Config *config, const char *name, const char *value) { - if (strcmp(name, "temp_threshold_1") == 0) { - config->temp_threshold_1 = safe_atof(value, 50.0f); - } else if (strcmp(name, "temp_threshold_2") == 0) { - config->temp_threshold_2 = safe_atof(value, 65.0f); - } else if (strcmp(name, "temp_threshold_3") == 0) { - config->temp_threshold_3 = safe_atof(value, 80.0f); +static void set_color_component(Color *color, const char *name, const char *value) +{ + if (!color || !name || !value) return; + + switch (name[0]) { + case 'r': if (strcmp(name, "r") == 0) parse_color_component(value, &color->r); break; + case 'g': if (strcmp(name, "g") == 0) parse_color_component(value, &color->g); break; + case 'b': if (strcmp(name, "b") == 0) parse_color_component(value, &color->b); break; } - return 1; } /** - * @brief Handle color section configuration. - * @details Processes color-related configuration keys for various UI elements with RGB component validation and clamping. - */ -static int get_color_config(Config *config, const char *section, const char *name, const char *value) { - if (strcmp(section, "bar_color_background") == 0) { - if (strcmp(name, "r") == 0) parse_color_component(value, &config->layout_bar_color_background.r); - else if (strcmp(name, "g") == 0) parse_color_component(value, &config->layout_bar_color_background.g); - else if (strcmp(name, "b") == 0) parse_color_component(value, &config->layout_bar_color_background.b); - } else if (strcmp(section, "bar_color_border") == 0) { - if (strcmp(name, "r") == 0) parse_color_component(value, &config->layout_bar_color_border.r); - else if (strcmp(name, "g") == 0) parse_color_component(value, &config->layout_bar_color_border.g); - else if (strcmp(name, "b") == 0) parse_color_component(value, &config->layout_bar_color_border.b); - } else if (strcmp(section, "font_color_temp") == 0) { - if (strcmp(name, "r") == 0) parse_color_component(value, &config->font_color_temp.r); - else if (strcmp(name, "g") == 0) parse_color_component(value, &config->font_color_temp.g); - else if (strcmp(name, "b") == 0) parse_color_component(value, &config->font_color_temp.b); - } else if (strcmp(section, "font_color_label") == 0) { - if (strcmp(name, "r") == 0) parse_color_component(value, &config->font_color_label.r); - else if (strcmp(name, "g") == 0) parse_color_component(value, &config->font_color_label.g); - else if (strcmp(name, "b") == 0) parse_color_component(value, &config->font_color_label.b); - } else if (strcmp(section, "temp_threshold_1_bar") == 0) { - if (strcmp(name, "r") == 0) parse_color_component(value, &config->temp_threshold_1_bar.r); - else if (strcmp(name, "g") == 0) parse_color_component(value, &config->temp_threshold_1_bar.g); - else if (strcmp(name, "b") == 0) parse_color_component(value, &config->temp_threshold_1_bar.b); - } else if (strcmp(section, "temp_threshold_2_bar") == 0) { - if (strcmp(name, "r") == 0) parse_color_component(value, &config->temp_threshold_2_bar.r); - else if (strcmp(name, "g") == 0) parse_color_component(value, &config->temp_threshold_2_bar.g); - else if (strcmp(name, "b") == 0) parse_color_component(value, &config->temp_threshold_2_bar.b); - } else if (strcmp(section, "temp_threshold_3_bar") == 0) { - if (strcmp(name, "r") == 0) parse_color_component(value, &config->temp_threshold_3_bar.r); - else if (strcmp(name, "g") == 0) parse_color_component(value, &config->temp_threshold_3_bar.g); - else if (strcmp(name, "b") == 0) parse_color_component(value, &config->temp_threshold_3_bar.b); - } else if (strcmp(section, "temp_threshold_4_bar") == 0) { - if (strcmp(name, "r") == 0) parse_color_component(value, &config->temp_threshold_4_bar.r); - else if (strcmp(name, "g") == 0) parse_color_component(value, &config->temp_threshold_4_bar.g); - else if (strcmp(name, "b") == 0) parse_color_component(value, &config->temp_threshold_4_bar.b); + * @brief Handle color section configuration with reduced complexity. + * @details Processes color-related configuration keys using lookup table approach. + */ +static int get_color_config(Config *config, const char *section, const char *name, const char *value) +{ + Color *color = get_color_pointer_from_section(config, section); + if (color) { + set_color_component(color, name, value); } return 1; } /** - * @brief Sets fallback default values for missing or empty configuration fields. - * @details This function should be called after parsing the INI file to ensure all important fields are set to sensible defaults if not provided. Tries to get LCD display dimensions from Liquidctl device as fallback. + * @brief Set daemon default values. + * @details Helper function to set default daemon configuration values. */ -void get_config_defaults(Config *config) { - if (!config) return; - - // Daemon - if (config->daemon_address[0] == '\0') SAFE_STRCPY(config->daemon_address, "http://localhost:11987"); - if (config->daemon_password[0] == '\0') SAFE_STRCPY(config->daemon_password, "coolAdmin"); - - // Paths - if (config->paths_images[0] == '\0') SAFE_STRCPY(config->paths_images, "/opt/coolerdash/images"); - - if (config->paths_image_coolerdash[0] == '\0') { - SAFE_STRCPY(config->paths_image_coolerdash, "/tmp/coolerdash.png"); - } - if (config->paths_image_shutdown[0] == '\0') SAFE_STRCPY(config->paths_image_shutdown, "/opt/coolerdash/images/shutdown.png"); - - if (config->paths_pid[0] == '\0') { - SAFE_STRCPY(config->paths_pid, "/tmp/coolerdash.pid"); - } - - // Display - try to get dimensions from Liquidctl device first - if (config->display_width == 0 || config->display_height == 0) { - int lcd_width = 0, lcd_height = 0; - // Try to get LCD display info from Liquidctl device via API - if (get_liquidctl_data(config, NULL, 0, NULL, 0, &lcd_width, &lcd_height) && lcd_width > 0 && lcd_height > 0) { - if (config->display_width == 0) config->display_width = lcd_width; - if (config->display_height == 0) config->display_height = lcd_height; - } - } - if (config->display_refresh_interval_sec == 0) config->display_refresh_interval_sec = 2; - if (config->display_refresh_interval_nsec == 0) config->display_refresh_interval_nsec = 500000000; - if (config->lcd_brightness == 0) config->lcd_brightness = 80; - if (config->lcd_orientation == 0) config->lcd_orientation = 0; - - // Layout - if (config->layout_box_width == 0) config->layout_box_width = config->display_width; - if (config->layout_box_height == 0) config->layout_box_height = config->display_height / 2; - if (config->layout_bar_width == 0) config->layout_bar_width = config->layout_box_width - 10; - if (config->layout_bar_height == 0) config->layout_bar_height = 22; - if (config->layout_bar_gap == 0) config->layout_bar_gap = 10; - if (config->layout_bar_border_width == 0.0f) config->layout_bar_border_width = 1.5f; - - // Font - if (config->font_face[0] == '\0') SAFE_STRCPY(config->font_face, "Roboto Black"); - if (config->font_size_temp == 0.0f) config->font_size_temp = 100.0f; - if (config->font_size_labels == 0.0f) config->font_size_labels = 30.0f; - - // Temperature thresholds - if (config->temp_threshold_1 == 0.0f) config->temp_threshold_1 = 55.0f; - if (config->temp_threshold_2 == 0.0f) config->temp_threshold_2 = 65.0f; - if (config->temp_threshold_3 == 0.0f) config->temp_threshold_3 = 75.0f; - - // Colors - if (config->layout_bar_color_background.r == 0 && config->layout_bar_color_background.g == 0 && config->layout_bar_color_background.b == 0) { - config->layout_bar_color_background.r = 52; - config->layout_bar_color_background.g = 52; - config->layout_bar_color_background.b = 52; +static void set_daemon_defaults(Config *config) +{ + if (config->daemon_address[0] == '\0') + { + SAFE_STRCPY(config->daemon_address, "http://localhost:11987"); } - if (config->layout_bar_color_border.r == 0 && config->layout_bar_color_border.g == 0 && config->layout_bar_color_border.b == 0) { - config->layout_bar_color_border.r = 192; - config->layout_bar_color_border.g = 192; - config->layout_bar_color_border.b = 192; + if (config->daemon_password[0] == '\0') + { + SAFE_STRCPY(config->daemon_password, "coolAdmin"); } - if (config->font_color_temp.r == 0 && config->font_color_temp.g == 0 && config->font_color_temp.b == 0) { - config->font_color_temp.r = 255; - config->font_color_temp.g = 255; - config->font_color_temp.b = 255; +} + +/** + * @brief Set paths default values. + * @details Helper function to set default path configuration values. + */ +static void set_paths_defaults(Config *config) +{ + if (config->paths_images[0] == '\0') + { + SAFE_STRCPY(config->paths_images, "/opt/coolerdash/images"); } - if (config->font_color_label.r == 0 && config->font_color_label.g == 0 && config->font_color_label.b == 0) { - config->font_color_label.r = 200; - config->font_color_label.g = 200; - config->font_color_label.b = 200; + if (config->paths_image_coolerdash[0] == '\0') + { + SAFE_STRCPY(config->paths_image_coolerdash, "/tmp/coolerdash.png"); } - - // Temperature bar colors - if (config->temp_threshold_1_bar.r == 0 && config->temp_threshold_1_bar.g == 0 && config->temp_threshold_1_bar.b == 0) { - config->temp_threshold_1_bar.r = 0; - config->temp_threshold_1_bar.g = 255; - config->temp_threshold_1_bar.b = 0; + if (config->paths_image_shutdown[0] == '\0') + { + SAFE_STRCPY(config->paths_image_shutdown, "/opt/coolerdash/images/shutdown.png"); } - if (config->temp_threshold_2_bar.r == 0 && config->temp_threshold_2_bar.g == 0 && config->temp_threshold_2_bar.b == 0) { - config->temp_threshold_2_bar.r = 255; - config->temp_threshold_2_bar.g = 140; - config->temp_threshold_2_bar.b = 0; + if (config->paths_pid[0] == '\0') + { + SAFE_STRCPY(config->paths_pid, "/tmp/coolerdash.pid"); } - if (config->temp_threshold_3_bar.r == 0 && config->temp_threshold_3_bar.g == 0 && config->temp_threshold_3_bar.b == 0) { - config->temp_threshold_3_bar.r = 255; - config->temp_threshold_3_bar.g = 70; - config->temp_threshold_3_bar.b = 0; +} + +/** + * @brief Set display default values with LCD device fallback. + * @details Helper function to set default display configuration values, attempting to get LCD dimensions from device. + */ +static void set_display_defaults(Config *config) +{ + // Try to get dimensions from Liquidctl device first + if (config->display_width == 0 || config->display_height == 0) + { + int lcd_width = 0, lcd_height = 0; + if (get_liquidctl_data(config, NULL, 0, NULL, 0, &lcd_width, &lcd_height) && + lcd_width > 0 && lcd_height > 0) + { + if (config->display_width == 0) + config->display_width = lcd_width; + if (config->display_height == 0) + config->display_height = lcd_height; + } } - if (config->temp_threshold_4_bar.r == 0 && config->temp_threshold_4_bar.g == 0 && config->temp_threshold_4_bar.b == 0) { - config->temp_threshold_4_bar.r = 255; - config->temp_threshold_4_bar.g = 0; - config->temp_threshold_4_bar.b = 0; + + if (config->display_refresh_interval_sec == 0) + config->display_refresh_interval_sec = 2; + if (config->display_refresh_interval_nsec == 0) + config->display_refresh_interval_nsec = 500000000; + if (config->lcd_brightness == 0) + config->lcd_brightness = 80; + if (config->lcd_orientation == 0) + config->lcd_orientation = 0; +} + +/** + * @brief Set layout default values. + * @details Helper function to set default layout configuration values based on display dimensions. + */ +static void set_layout_defaults(Config *config) +{ + if (config->layout_box_width == 0) + config->layout_box_width = config->display_width; + if (config->layout_box_height == 0) + config->layout_box_height = config->display_height / 2; + if (config->layout_bar_width == 0) + config->layout_bar_width = config->layout_box_width - 10; + if (config->layout_bar_height == 0) + config->layout_bar_height = 22; + if (config->layout_bar_gap == 0) + config->layout_bar_gap = 10; + if (config->layout_bar_border_width == 0.0f) + config->layout_bar_border_width = 1.5f; +} + +/** + * @brief Set font default values. + * @details Helper function to set default font configuration values. + */ +static void set_font_defaults(Config *config) +{ + if (config->font_face[0] == '\0') + SAFE_STRCPY(config->font_face, "Roboto Black"); + if (config->font_size_temp == 0.0f) + config->font_size_temp = 100.0f; + if (config->font_size_labels == 0.0f) + config->font_size_labels = 30.0f; +} + +/** + * @brief Set temperature threshold default values. + * @details Helper function to set default temperature threshold values. + */ +static void set_temperature_defaults(Config *config) +{ + if (config->temp_threshold_1 == 0.0f) + config->temp_threshold_1 = 55.0f; + if (config->temp_threshold_2 == 0.0f) + config->temp_threshold_2 = 65.0f; + if (config->temp_threshold_3 == 0.0f) + config->temp_threshold_3 = 75.0f; +} + +/** + * @brief Check if color is unset (all components are zero). + * @details Helper function to determine if a color structure needs default values. + */ +static inline int is_color_unset(const Color *color) +{ + return (color->r == 0 && color->g == 0 && color->b == 0); +} + +/** + * @brief Color default configuration entry. + * @details Structure for color default values lookup table. + */ +typedef struct +{ + Color *color_ptr; + uint8_t r, g, b; +} ColorDefault; + +/** + * @brief Set color default values using lookup table approach. + * @details Helper function to set default color values using data-driven approach to reduce complexity. + */ +static void set_color_defaults(Config *config) +{ + // Define all color defaults in a lookup table + ColorDefault color_defaults[] = { + {&config->layout_bar_color_background, 52, 52, 52}, + {&config->layout_bar_color_border, 192, 192, 192}, + {&config->font_color_temp, 255, 255, 255}, + {&config->font_color_label, 200, 200, 200}, + {&config->temp_threshold_1_bar, 0, 255, 0}, + {&config->temp_threshold_2_bar, 255, 140, 0}, + {&config->temp_threshold_3_bar, 255, 70, 0}, + {&config->temp_threshold_4_bar, 255, 0, 0}}; + + const size_t color_count = sizeof(color_defaults) / sizeof(color_defaults[0]); + for (size_t i = 0; i < color_count; i++) + { + if (is_color_unset(color_defaults[i].color_ptr)) + { + color_defaults[i].color_ptr->r = color_defaults[i].r; + color_defaults[i].color_ptr->g = color_defaults[i].g; + color_defaults[i].color_ptr->b = color_defaults[i].b; + } } } +/** + * @brief Sets fallback default values for missing or empty configuration fields. + * @details Refactored function with reduced complexity using helper functions for each configuration section. + */ +void get_config_defaults(Config *config) +{ + if (!config) + return; + + set_daemon_defaults(config); + set_paths_defaults(config); + set_display_defaults(config); + set_layout_defaults(config); + set_font_defaults(config); + set_temperature_defaults(config); + set_color_defaults(config); +} + /** * @brief Initialize config structure with safe defaults. * @details Sets all fields to safe default values with security considerations by clearing memory and applying fallback values. */ -void init_config_defaults(Config *config) { - if (!config) return; - +void init_config_defaults(Config *config) +{ + if (!config) + return; + memset(config, 0, sizeof(Config)); - + // Apply all fallback values get_config_defaults(config); } @@ -412,30 +687,33 @@ void init_config_defaults(Config *config) { * @brief Main configuration loading function with enhanced security. * @details Loads configuration from INI file with comprehensive validation and secure defaults, handling missing files gracefully. */ -int load_config(const char *path, Config *config) { +int load_config(const char *path, Config *config) +{ // Validate input parameters - if (!config || !path) { + if (!config || !path) + { return -1; } - + // Initialize config struct with zeros to ensure fallbacks work memset(config, 0, sizeof(Config)); - + // Check if file exists and is readable FILE *file = fopen(path, "r"); - if (!file) { + if (!file) + { // File doesn't exist - use fallbacks only log_message(LOG_INFO, "Config file '%s' not found, using fallback values", path); get_config_defaults(config); return 0; // Return success, fallbacks are valid } fclose(file); - + // Parse INI file and return success/failure int result = (ini_parse(path, parse_config_data, config) < 0) ? -1 : 0; - + // Always apply fallbacks after parsing (for missing/commented values) get_config_defaults(config); - + return result; } \ No newline at end of file diff --git a/include/config.h b/src/config.h similarity index 93% rename from include/config.h rename to src/config.h index 2a6a19c..7c5d77d 100644 --- a/include/config.h +++ b/src/config.h @@ -19,8 +19,10 @@ #define CONFIG_H // Include necessary headers +// cppcheck-suppress-begin missingIncludeSystem #include #include +// cppcheck-suppress-end missingIncludeSystem #define CONFIG_MAX_STRING_LEN 256 #define CONFIG_MAX_PASSWORD_LEN 128 @@ -125,12 +127,4 @@ void config_init_defaults(Config *config); */ int load_config(const char *path, Config *config); -/** - * @brief Legacy function name for backward compatibility. - * @details Provides backward compatibility by calling load_config with swapped parameter order. - */ -static inline int load_config_ini(Config *config, const char *path) { - return load_config(path, config); -} - #endif // CONFIG_H \ No newline at end of file diff --git a/src/coolercontrol.c b/src/coolercontrol.c index 4b66a64..145ecdb 100644 --- a/src/coolercontrol.c +++ b/src/coolercontrol.c @@ -18,25 +18,120 @@ #define _POSIX_C_SOURCE 200112L // Include necessary headers +// cppcheck-suppress-begin missingIncludeSystem #include #include #include #include #include -#include #include #include #include +// cppcheck-suppress-end missingIncludeSystem // Include project headers -#include "../include/config.h" -#include "../include/coolercontrol.h" +#include "config.h" +#include "coolercontrol.h" + +/** + * @brief Secure string copy with bounds checking. + * @details Performs safe string copying with buffer overflow protection and null termination guarantee. + */ +int cc_safe_strcpy(char *restrict dest, size_t dest_size, const char *restrict src) +{ + if (!dest || !src || dest_size == 0) + { + return 0; + } + + for (size_t i = 0; i < dest_size - 1; i++) + { + dest[i] = src[i]; + if (src[i] == '\0') + { + return 1; + } + } + dest[dest_size - 1] = '\0'; + return 0; +} + +/** + * @brief Secure memory allocation with initialization. + * @details Allocates memory using calloc to ensure zero-initialization and prevent uninitialized data access. + */ +void *cc_secure_malloc(size_t size) +{ + if (size == 0 || size > CC_MAX_SAFE_ALLOC_SIZE) + { + return NULL; + } + + return calloc(1, size); +} + +/** + * @brief Initialize HTTP response buffer with specified capacity. + * @details Allocates memory for HTTP response data with proper initialization. + */ +int cc_init_response_buffer(struct http_response *response, size_t initial_capacity) +{ + if (!response || initial_capacity == 0 || initial_capacity > CC_MAX_SAFE_ALLOC_SIZE) + { + return 0; + } + + response->data = malloc(initial_capacity); + if (!response->data) + { + response->size = 0; + response->capacity = 0; + return 0; + } + + response->size = 0; + response->capacity = initial_capacity; + response->data[0] = '\0'; + return 1; +} + +/** + * @brief Validate HTTP response buffer integrity. + * @details Checks if response buffer is in valid state for operations. + */ +int cc_validate_response_buffer(const struct http_response *response) +{ + return (response && + response->data && + response->size <= response->capacity); +} + +/** + * @brief Cleanup HTTP response buffer and free memory. + * @details Properly frees allocated memory and resets buffer state. + */ +void cc_cleanup_response_buffer(struct http_response *response) +{ + if (!response) + { + return; + } + + if (response->data) + { + free(response->data); + response->data = NULL; + } + response->size = 0; + response->capacity = 0; +} /** * @brief Structure to hold CoolerControl session state. * @details Contains the CURL handle, cookie jar path, and session initialization status. */ -typedef struct { +typedef struct +{ CURL *curl_handle; char cookie_jar[CC_COOKIE_SIZE]; int session_initialized; @@ -45,24 +140,24 @@ typedef struct { /** * @brief Global CoolerControl session state. * @details Holds the state of the CoolerControl session, including the CURL handle and session initialization status. -*/ + */ static CoolerControlSession cc_session = { .curl_handle = NULL, .cookie_jar = {0}, - .session_initialized = 0 -}; + .session_initialized = 0}; /** * @brief Static cache for device information (never changes during runtime). * @details Holds the device UID, name, and display dimensions once fetched from the API. Used to avoid redundant API calls and improve performance. */ -static struct { +static struct +{ int initialized; char device_uid[CC_UID_SIZE]; char device_name[CC_NAME_SIZE]; int screen_width; int screen_height; -} device_cache = {0}; +} device_cache = {0}; // Strings always written via snprintf/cc_safe_strcpy (bounded) /** * @brief Initialize device cache by fetching device information once. @@ -76,23 +171,44 @@ static int initialize_device_cache(const Config *config); */ static int parse_liquidctl_data(const char *json, char *lcd_uid, size_t uid_size, int *found_liquidctl, int *screen_width, int *screen_height, char *device_name, size_t name_size); +/** + * @brief Extract device type from JSON device object. + * @details Common helper function to extract device type string from JSON device object. Returns NULL if extraction fails. + */ +const char *extract_device_type_from_json(json_t *dev) +{ + if (!dev) + return NULL; + + // Get device type + json_t *type_val = json_object_get(dev, "type"); + if (!type_val || !json_is_string(type_val)) + return NULL; + + // Extract device type string + return json_string_value(type_val); +} + /** * @brief Callback for libcurl to write received data into a buffer. * @details This function is called by libcurl to write the response data into a dynamically allocated buffer with automatic reallocation when needed. */ -size_t write_callback(void *contents, size_t size, size_t nmemb, struct http_response *response) { +size_t write_callback(void *contents, size_t size, size_t nmemb, struct http_response *response) +{ // Validate input const size_t realsize = size * nmemb; const size_t new_size = response->size + realsize + 1; // Reallocate if needed - if (new_size > response->capacity) { + if (new_size > response->capacity) + { // Grow capacity const size_t new_capacity = (new_size > response->capacity * 3 / 2) ? new_size : response->capacity * 3 / 2; // Reallocate char *ptr = realloc(response->data, new_capacity); - if (!ptr) { + if (!ptr) + { log_message(LOG_ERROR, "Memory allocation failed for response data: %zu bytes", new_capacity); free(response->data); response->data = NULL; @@ -106,10 +222,13 @@ size_t write_callback(void *contents, size_t size, size_t nmemb, struct http_res response->capacity = new_capacity; } - // Copy data - memcpy(response->data + response->size, contents, realsize); - response->size += realsize; - response->data[response->size] = '\0'; + // Copy data with bounds safety (capacity ausreichend geprüft) + if (realsize > 0) + { + memmove(response->data + response->size, contents, realsize); + response->size += realsize; + response->data[response->size] = '\0'; + } // Return size return realsize; @@ -119,28 +238,37 @@ size_t write_callback(void *contents, size_t size, size_t nmemb, struct http_res * @brief Parse devices JSON and extract LCD UID, display info and device name from Liquidctl devices. * @details This function parses the JSON response from the CoolerControl API to find Liquidctl devices, extracting their UID, display dimensions, and device name. */ -static int parse_liquidctl_data(const char *json, char *lcd_uid, size_t uid_size, int *found_liquidctl, int *screen_width, int *screen_height, char *device_name, size_t name_size) { +static int parse_liquidctl_data(const char *json, char *lcd_uid, size_t uid_size, int *found_liquidctl, int *screen_width, int *screen_height, char *device_name, size_t name_size) +{ // Validate input - if (!json) return 0; + if (!json) + return 0; // Initialize output variables - if (lcd_uid && uid_size > 0) lcd_uid[0] = '\0'; - if (found_liquidctl) *found_liquidctl = 0; - if (screen_width) *screen_width = 0; - if (screen_height) *screen_height = 0; - if (device_name && name_size > 0) device_name[0] = '\0'; + if (lcd_uid && uid_size > 0) + lcd_uid[0] = '\0'; + if (found_liquidctl) + *found_liquidctl = 0; + if (screen_width) + *screen_width = 0; + if (screen_height) + *screen_height = 0; + if (device_name && name_size > 0) + device_name[0] = '\0'; // Parse JSON json_error_t error; json_t *root = json_loads(json, 0, &error); - if (!root) { + if (!root) + { log_message(LOG_ERROR, "JSON parse error: %s", error.text); return 0; } // Get devices array json_t *devices = json_object_get(root, "devices"); - if (!devices || !json_is_array(devices)) { + if (!devices || !json_is_array(devices)) + { json_decref(root); return 0; } @@ -149,71 +277,85 @@ static int parse_liquidctl_data(const char *json, char *lcd_uid, size_t uid_size const size_t device_count = json_array_size(devices); // Check each device - for (size_t i = 0; i < device_count; i++) { + for (size_t i = 0; i < device_count; i++) + { // Get device entry json_t *dev = json_array_get(devices, i); - if (!dev) continue; + if (!dev) + continue; - // Get device type - json_t *type_val = json_object_get(dev, "type"); - if (!type_val || !json_is_string(type_val)) continue; - - // Extract device type string - const char *type_str = json_string_value(type_val); + // Extract device type string using common helper + const char *type_str = extract_device_type_from_json(dev); + if (!type_str) + continue; // Check device type const char *liquid_types[] = {"Liquidctl"}; const size_t num_types = sizeof(liquid_types) / sizeof(liquid_types[0]); - + // Check if device type is supported int is_supported_device = 0; - for (size_t j = 0; j < num_types; j++) { - if (strcmp(type_str, liquid_types[j]) == 0) { + for (size_t j = 0; j < num_types; j++) + { + if (strcmp(type_str, liquid_types[j]) == 0) + { is_supported_device = 1; break; } } - + // Skip if not supported - if (!is_supported_device) continue; + if (!is_supported_device) + continue; // Found Liquidctl device - if (found_liquidctl) *found_liquidctl = 1; + if (found_liquidctl) + *found_liquidctl = 1; // Extract UID - if (lcd_uid && uid_size > 0) { + if (lcd_uid && uid_size > 0) + { json_t *uid_val = json_object_get(dev, "uid"); - if (uid_val && json_is_string(uid_val)) { + if (uid_val && json_is_string(uid_val)) + { const char *uid_str = json_string_value(uid_val); snprintf(lcd_uid, uid_size, "%s", uid_str); } } // Extract device name - if (device_name && name_size > 0) { + if (device_name && name_size > 0) + { json_t *name_val = json_object_get(dev, "name"); - if (name_val && json_is_string(name_val)) { + if (name_val && json_is_string(name_val)) + { const char *name_str = json_string_value(name_val); snprintf(device_name, name_size, "%s", name_str); } } // Extract LCD dimensions - if (screen_width || screen_height) { + if (screen_width || screen_height) + { json_t *info = json_object_get(dev, "info"); json_t *channels = info ? json_object_get(info, "channels") : NULL; json_t *lcd_channel = channels ? json_object_get(channels, "lcd") : NULL; json_t *lcd_info = lcd_channel ? json_object_get(lcd_channel, "lcd_info") : NULL; - if (lcd_info) { - if (screen_width) { + if (lcd_info) + { + if (screen_width) + { json_t *width_val = json_object_get(lcd_info, "screen_width"); - if (width_val && json_is_integer(width_val)) { + if (width_val && json_is_integer(width_val)) + { *screen_width = (int)json_integer_value(width_val); } } - if (screen_height) { + if (screen_height) + { json_t *height_val = json_object_get(lcd_info, "screen_height"); - if (height_val && json_is_integer(height_val)) { + if (height_val && json_is_integer(height_val)) + { *screen_height = (int)json_integer_value(height_val); } } @@ -234,23 +376,28 @@ static int parse_liquidctl_data(const char *json, char *lcd_uid, size_t uid_size * @brief Initialize device cache by fetching device information once. * @details Populates the static cache with device UID, name, and display dimensions. */ -static int initialize_device_cache(const Config *config) { +static int initialize_device_cache(const Config *config) +{ // Check if already initialized - if (device_cache.initialized) { + if (device_cache.initialized) + { return 1; // Already initialized } // Validate input - if (!config) return 0; + if (!config) + return 0; // Initialize CURL CURL *curl = curl_easy_init(); - if (!curl) return 0; + if (!curl) + return 0; // Construct URL for devices endpoint char url[512]; // Increased buffer size to prevent truncation int ret = snprintf(url, sizeof(url), "%s/devices", config->daemon_address); - if (ret >= (int)sizeof(url) || ret < 0) { + if (ret >= (int)sizeof(url) || ret < 0) + { return 0; // URL too long or formatting error } @@ -258,7 +405,8 @@ static int initialize_device_cache(const Config *config) { struct http_response chunk = {0}; const size_t initial_capacity = 4096; // Start with 4KB (typical JSON response size) chunk.data = malloc(initial_capacity); - if (!chunk.data) { + if (!chunk.data) + { curl_easy_cleanup(curl); return 0; } @@ -276,22 +424,21 @@ static int initialize_device_cache(const Config *config) { headers = curl_slist_append(headers, "accept: application/json"); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); - // Initialize parsing variables - int found_liquidctl = 0; - int result = 0; - // Perform request and parse response - if (curl_easy_perform(curl) == CURLE_OK) { - result = parse_liquidctl_data(chunk.data, - device_cache.device_uid, sizeof(device_cache.device_uid), - &found_liquidctl, - &device_cache.screen_width, &device_cache.screen_height, - device_cache.device_name, sizeof(device_cache.device_name)); + if (curl_easy_perform(curl) == CURLE_OK) + { + int found_liquidctl = 0; + int result = parse_liquidctl_data(chunk.data, + device_cache.device_uid, sizeof(device_cache.device_uid), + &found_liquidctl, + &device_cache.screen_width, &device_cache.screen_height, + device_cache.device_name, sizeof(device_cache.device_name)); // Initialize cache if Liquidctl device found - if (result && found_liquidctl) { + if (result && found_liquidctl) + { device_cache.initialized = 1; - log_message(LOG_STATUS, "Device cache initialized: %s (%dx%d pixel)", - device_cache.device_name, device_cache.screen_width, device_cache.screen_height); + log_message(LOG_STATUS, "Device cache initialized: %s (%dx%d pixel)", + device_cache.device_name, device_cache.screen_width, device_cache.screen_height); } } @@ -308,18 +455,21 @@ static int initialize_device_cache(const Config *config) { * @brief Initializes the CoolerControl session (CURL setup and login). * @details This function initializes the CURL library, sets up the session cookie jar, constructs the login URL and credentials, and performs a login to the CoolerControl API. No handshake or session authentication is performed beyond basic login. */ -int init_coolercontrol_session(const Config *config) { +int init_coolercontrol_session(const Config *config) +{ // Initialize cURL and create handle curl_global_init(CURL_GLOBAL_DEFAULT); cc_session.curl_handle = curl_easy_init(); - if (!cc_session.curl_handle) { + if (!cc_session.curl_handle) + { return 0; } // Create unique cookie jar path using PID - int written_cookie = snprintf(cc_session.cookie_jar, sizeof(cc_session.cookie_jar), + int written_cookie = snprintf(cc_session.cookie_jar, sizeof(cc_session.cookie_jar), "/tmp/coolerdash_cookie_%d.txt", getpid()); - if (written_cookie < 0 || (size_t)written_cookie >= sizeof(cc_session.cookie_jar)) { + if (written_cookie < 0 || (size_t)written_cookie >= sizeof(cc_session.cookie_jar)) + { cc_session.cookie_jar[sizeof(cc_session.cookie_jar) - 1] = '\0'; } @@ -330,14 +480,16 @@ int init_coolercontrol_session(const Config *config) { // Build login URL char login_url[CC_URL_SIZE]; int written_url = snprintf(login_url, sizeof(login_url), "%s/login", config->daemon_address); - if (written_url < 0 || (size_t)written_url >= sizeof(login_url)) { + if (written_url < 0 || (size_t)written_url >= sizeof(login_url)) + { login_url[sizeof(login_url) - 1] = '\0'; } // Build credentials char userpwd[CC_USERPWD_SIZE]; int written_pwd = snprintf(userpwd, sizeof(userpwd), "CCAdmin:%s", config->daemon_password); - if (written_pwd < 0 || (size_t)written_pwd >= sizeof(userpwd)) { + if (written_pwd < 0 || (size_t)written_pwd >= sizeof(userpwd)) + { userpwd[sizeof(userpwd) - 1] = '\0'; } @@ -348,16 +500,17 @@ int init_coolercontrol_session(const Config *config) { curl_easy_setopt(cc_session.curl_handle, CURLOPT_POST, 1L); curl_easy_setopt(cc_session.curl_handle, CURLOPT_POSTFIELDS, ""); curl_easy_setopt(cc_session.curl_handle, CURLOPT_WRITEFUNCTION, NULL); - + // Set HTTP headers for login request struct curl_slist *headers = NULL; headers = curl_slist_append(headers, "User-Agent: CoolerDash/1.0"); headers = curl_slist_append(headers, "Accept: application/json"); headers = curl_slist_append(headers, "Content-Type: application/json"); curl_easy_setopt(cc_session.curl_handle, CURLOPT_HTTPHEADER, headers); - + // Enable SSL verification for HTTPS - if (strncmp(config->daemon_address, "https://", 8) == 0) { + if (strncmp(config->daemon_address, "https://", 8) == 0) + { curl_easy_setopt(cc_session.curl_handle, CURLOPT_SSL_VERIFYPEER, 1L); curl_easy_setopt(cc_session.curl_handle, CURLOPT_SSL_VERIFYHOST, 2L); } @@ -371,13 +524,17 @@ int init_coolercontrol_session(const Config *config) { memset(userpwd, 0, sizeof(userpwd)); // Cleanup headers - if (headers) curl_slist_free_all(headers); + if (headers) + curl_slist_free_all(headers); // Check if login was successful - if (res == CURLE_OK && (response_code == 200 || response_code == 204)) { + if (res == CURLE_OK && (response_code == 200 || response_code == 204)) + { cc_session.session_initialized = 1; return 1; - } else { + } + else + { log_message(LOG_ERROR, "Login failed: CURL code %d, HTTP code %ld", res, response_code); } @@ -389,7 +546,8 @@ int init_coolercontrol_session(const Config *config) { * @brief Checks if the CoolerControl session is initialized. * @details This function returns whether the session has been successfully initialized and is ready for use. */ -int is_session_initialized(void) { +int is_session_initialized(void) +{ return cc_session.session_initialized; } @@ -397,31 +555,36 @@ int is_session_initialized(void) { * @brief Cleans up and terminates the CoolerControl session. * @details This function performs cleanup of the CURL handle, removes the cookie jar file, and marks the session as uninitialized. It ensures that all resources are properly released and no memory leaks occur. */ -void cleanup_coolercontrol_session(void) { +void cleanup_coolercontrol_session(void) +{ static int cleanup_done = 0; - if (cleanup_done) return; - + if (cleanup_done) + return; + int all_cleaned = 1; - + // Clean up CURL handle - if (cc_session.curl_handle) { + if (cc_session.curl_handle) + { curl_easy_cleanup(cc_session.curl_handle); cc_session.curl_handle = NULL; } - + // Perform global CURL cleanup curl_global_cleanup(); - + // Remove cookie jar file - if (unlink(cc_session.cookie_jar) != 0) { + if (unlink(cc_session.cookie_jar) != 0) + { all_cleaned = 0; } - + // Mark session as uninitialized cc_session.session_initialized = 0; - + // Set cleanup flag only if all operations succeeded - if (all_cleaned) { + if (all_cleaned) + { cleanup_done = 1; } } @@ -430,27 +593,27 @@ void cleanup_coolercontrol_session(void) { * @brief Get complete Liquidctl device information (UID, name, screen dimensions) from CoolerControl API. * @details This function retrieves the UID, name, and screen dimensions of the Liquidctl device by calling the main device info function. */ -int get_liquidctl_data(const Config *config, char *device_uid, size_t uid_size, char *device_name, size_t name_size, int *screen_width, int *screen_height) { +int get_liquidctl_data(const Config *config, char *device_uid, size_t uid_size, char *device_name, size_t name_size, int *screen_width, int *screen_height) +{ // Initialize cache if not already done - if (!initialize_device_cache(config)) { + if (!initialize_device_cache(config)) + { return 0; } // Copy values from cache to output buffers with safe string handling - if (device_uid && uid_size > 0) { - const size_t uid_len = strlen(device_cache.device_uid); - const size_t uid_copy_len = (uid_len < uid_size - 1) ? uid_len : uid_size - 1; - memcpy(device_uid, device_cache.device_uid, uid_copy_len); - device_uid[uid_copy_len] = '\0'; + if (device_uid && uid_size > 0) + { + cc_safe_strcpy(device_uid, uid_size, device_cache.device_uid); } - if (device_name && name_size > 0) { - const size_t name_len = strlen(device_cache.device_name); - const size_t name_copy_len = (name_len < name_size - 1) ? name_len : name_size - 1; - memcpy(device_name, device_cache.device_name, name_copy_len); - device_name[name_copy_len] = '\0'; + if (device_name && name_size > 0) + { + cc_safe_strcpy(device_name, name_size, device_cache.device_name); } - if (screen_width) *screen_width = device_cache.screen_width; - if (screen_height) *screen_height = device_cache.screen_height; + if (screen_width) + *screen_width = device_cache.screen_width; + if (screen_height) + *screen_height = device_cache.screen_height; return 1; } @@ -459,7 +622,8 @@ int get_liquidctl_data(const Config *config, char *device_uid, size_t uid_size, * @brief Initialize device cache - public interface. * @details Public function to initialize the device cache for external callers. */ -int init_device_cache(const Config *config) { +int init_device_cache(const Config *config) +{ return initialize_device_cache(config); } @@ -467,203 +631,227 @@ int init_device_cache(const Config *config) { * @brief Sends an image to the LCD display. * @details This function uploads an image to the LCD display using a multipart HTTP PUT request. It requires the session to be initialized and the device UID to be available. */ -int send_image_to_lcd(const Config *config, const char* image_path, const char* device_uid) { +int send_image_to_lcd(const Config *config, const char *image_path, const char *device_uid) +{ // Basic validation only - if (!cc_session.curl_handle || !image_path || !device_uid || !cc_session.session_initialized) { + if (!cc_session.curl_handle || !image_path || !device_uid || !cc_session.session_initialized) + { log_message(LOG_ERROR, "Invalid parameters or session not initialized"); return 0; } - + // Build upload URL char upload_url[CC_URL_SIZE]; - snprintf(upload_url, sizeof(upload_url), "%s/devices/%s/settings/lcd/lcd/images", + snprintf(upload_url, sizeof(upload_url), "%s/devices/%s/settings/lcd/lcd/images?log=false", config->daemon_address, device_uid); - + // Initialize multipart form curl_mime *form = curl_mime_init(cc_session.curl_handle); - if (!form) { + if (!form) + { log_message(LOG_ERROR, "Failed to initialize multipart form"); return 0; } - + // Initialize form fields curl_mimepart *field; CURLcode mime_result; - + // Add mode field with error checking field = curl_mime_addpart(form); - if (!field) { + if (!field) + { log_message(LOG_ERROR, "Failed to create mode field"); curl_mime_free(form); return 0; } - + // Set mode field name mime_result = curl_mime_name(field, "mode"); - if (mime_result != CURLE_OK) { + if (mime_result != CURLE_OK) + { log_message(LOG_ERROR, "Failed to set mode field name: %s", curl_easy_strerror(mime_result)); curl_mime_free(form); return 0; } - + // Set mode field data mime_result = curl_mime_data(field, "image", CURL_ZERO_TERMINATED); - if (mime_result != CURLE_OK) { + if (mime_result != CURLE_OK) + { log_message(LOG_ERROR, "Failed to set mode field data: %s", curl_easy_strerror(mime_result)); curl_mime_free(form); return 0; } - + // Add brightness field char brightness_str[8]; snprintf(brightness_str, sizeof(brightness_str), "%d", config->lcd_brightness); - + // Add brightness field field = curl_mime_addpart(form); - if (!field) { + if (!field) + { log_message(LOG_ERROR, "Failed to create brightness field"); curl_mime_free(form); return 0; } - + // Set brightness field name mime_result = curl_mime_name(field, "brightness"); - if (mime_result != CURLE_OK) { + if (mime_result != CURLE_OK) + { log_message(LOG_ERROR, "Failed to set brightness field name: %s", curl_easy_strerror(mime_result)); curl_mime_free(form); return 0; } - + // Set brightness field data mime_result = curl_mime_data(field, brightness_str, CURL_ZERO_TERMINATED); - if (mime_result != CURLE_OK) { + if (mime_result != CURLE_OK) + { log_message(LOG_ERROR, "Failed to set brightness field data: %s", curl_easy_strerror(mime_result)); curl_mime_free(form); return 0; } - + // Add orientation field char orientation_str[8]; snprintf(orientation_str, sizeof(orientation_str), "%d", config->lcd_orientation); - + // Add orientation field field = curl_mime_addpart(form); - if (!field) { + if (!field) + { log_message(LOG_ERROR, "Failed to create orientation field"); curl_mime_free(form); return 0; } - + // Set orientation field name mime_result = curl_mime_name(field, "orientation"); - if (mime_result != CURLE_OK) { + if (mime_result != CURLE_OK) + { log_message(LOG_ERROR, "Failed to set orientation field name: %s", curl_easy_strerror(mime_result)); curl_mime_free(form); return 0; } - + // Set orientation field data mime_result = curl_mime_data(field, orientation_str, CURL_ZERO_TERMINATED); - if (mime_result != CURLE_OK) { + if (mime_result != CURLE_OK) + { log_message(LOG_ERROR, "Failed to set orientation field data: %s", curl_easy_strerror(mime_result)); curl_mime_free(form); return 0; } - + // Add image file field = curl_mime_addpart(form); - if (!field) { + if (!field) + { log_message(LOG_ERROR, "Failed to create image field"); curl_mime_free(form); return 0; } - + // Set image field name mime_result = curl_mime_name(field, "images[]"); - if (mime_result != CURLE_OK) { + if (mime_result != CURLE_OK) + { log_message(LOG_ERROR, "Failed to set image field name: %s", curl_easy_strerror(mime_result)); curl_mime_free(form); return 0; } - + // Set image file data mime_result = curl_mime_filedata(field, image_path); - if (mime_result != CURLE_OK) { + if (mime_result != CURLE_OK) + { // Only log error if the file actually exists struct stat file_stat; - if (stat(image_path, &file_stat) == 0) { + if (stat(image_path, &file_stat) == 0) + { log_message(LOG_ERROR, "Failed to set image file data: %s", curl_easy_strerror(mime_result)); } curl_mime_free(form); return 0; } - + // Set image MIME type mime_result = curl_mime_type(field, "image/png"); - if (mime_result != CURLE_OK) { + if (mime_result != CURLE_OK) + { log_message(LOG_ERROR, "Failed to set image MIME type: %s", curl_easy_strerror(mime_result)); curl_mime_free(form); return 0; } - + // Initialize response buffer struct http_response response = {0}; - if (!cc_init_response_buffer(&response, 4096)) { + if (!cc_init_response_buffer(&response, 4096)) + { log_message(LOG_ERROR, "Failed to initialize response buffer"); curl_mime_free(form); return 0; } - + // Configure curl options curl_easy_setopt(cc_session.curl_handle, CURLOPT_URL, upload_url); curl_easy_setopt(cc_session.curl_handle, CURLOPT_MIMEPOST, form); curl_easy_setopt(cc_session.curl_handle, CURLOPT_CUSTOMREQUEST, "PUT"); - + // Enable SSL verification for HTTPS - if (strncmp(config->daemon_address, "https://", 8) == 0) { + if (strncmp(config->daemon_address, "https://", 8) == 0) + { curl_easy_setopt(cc_session.curl_handle, CURLOPT_SSL_VERIFYPEER, 1L); curl_easy_setopt(cc_session.curl_handle, CURLOPT_SSL_VERIFYHOST, 2L); } - + // Set write callback curl_easy_setopt(cc_session.curl_handle, CURLOPT_WRITEFUNCTION, write_callback); curl_easy_setopt(cc_session.curl_handle, CURLOPT_WRITEDATA, &response); - + // Add headers struct curl_slist *headers = NULL; headers = curl_slist_append(headers, "User-Agent: CoolerDash/1.0"); headers = curl_slist_append(headers, "Accept: application/json"); curl_easy_setopt(cc_session.curl_handle, CURLOPT_HTTPHEADER, headers); - + // Perform request CURLcode res = curl_easy_perform(cc_session.curl_handle); - + // Get response info long http_response_code = -1; curl_easy_getinfo(cc_session.curl_handle, CURLINFO_RESPONSE_CODE, &http_response_code); // Check response int success = 0; - if (res == CURLE_OK && http_response_code == 200) { + if (res == CURLE_OK && http_response_code == 200) + { success = 1; - } else { + } + else + { log_message(LOG_ERROR, "LCD upload failed: CURL code %d, HTTP code %ld", res, http_response_code); - if (response.data && response.size > 0) { + if (response.data && response.size > 0) + { response.data[response.size] = '\0'; log_message(LOG_ERROR, "Server response: %s", response.data); } } - + // Cleanup resources cc_cleanup_response_buffer(&response); - if (headers) curl_slist_free_all(headers); + if (headers) + curl_slist_free_all(headers); curl_mime_free(form); curl_easy_setopt(cc_session.curl_handle, CURLOPT_MIMEPOST, NULL); curl_easy_setopt(cc_session.curl_handle, CURLOPT_CUSTOMREQUEST, NULL); curl_easy_setopt(cc_session.curl_handle, CURLOPT_WRITEFUNCTION, NULL); curl_easy_setopt(cc_session.curl_handle, CURLOPT_WRITEDATA, NULL); curl_easy_setopt(cc_session.curl_handle, CURLOPT_HTTPHEADER, NULL); - + // Return success return success; -} +} \ No newline at end of file diff --git a/include/coolercontrol.h b/src/coolercontrol.h similarity index 64% rename from include/coolercontrol.h rename to src/coolercontrol.h index 253bd76..416eed6 100644 --- a/include/coolercontrol.h +++ b/src/coolercontrol.h @@ -19,31 +19,37 @@ #define COOLERCONTROL_H // Include necessary headers -#include +// cppcheck-suppress-begin missingIncludeSystem #include #include #include #include +#include +// cppcheck-suppress-end missingIncludeSystem -// Project headers +// Include project headers #include "config.h" // Basic constants -#define CC_COOKIE_SIZE 512 -#define CC_NAME_SIZE 128 -#define CC_UID_SIZE 128 -#define CC_URL_SIZE 512 +#define CC_COOKIE_SIZE 512 +#define CC_NAME_SIZE 128 +#define CC_UID_SIZE 128 +#define CC_URL_SIZE 512 #define CC_USERPWD_SIZE 128 // Forward declarations to reduce compilation dependencies struct Config; struct curl_slist; +// Maximum safe allocation size to prevent overflow +#define CC_MAX_SAFE_ALLOC_SIZE (SIZE_MAX / 2) + /** * @brief Response buffer for libcurl HTTP operations. * @details Structure to hold HTTP response data with dynamic memory management for efficient data collection. */ -typedef struct http_response { +typedef struct http_response +{ char *data; size_t size; size_t capacity; @@ -53,79 +59,31 @@ typedef struct http_response { * @brief Secure string copy with bounds checking. * @details Performs safe string copying with buffer overflow protection and null termination guarantee. */ -static inline int cc_safe_strcpy(char *dest, size_t dest_size, const char *src) { - if (!dest || !src || dest_size == 0) { - return 0; - } - - size_t src_len = strlen(src); - if (src_len >= dest_size) { - return 0; - } - - memcpy(dest, src, src_len); - dest[src_len] = '\0'; - return 1; -} +int cc_safe_strcpy(char *restrict dest, size_t dest_size, const char *restrict src); /** * @brief Secure memory allocation with initialization. * @details Allocates memory using calloc to ensure zero-initialization and prevent uninitialized data access. */ -static inline void* cc_secure_malloc(size_t size) { - if (size == 0) { - return NULL; - } - - return calloc(1, size); -} +void *cc_secure_malloc(size_t size); /** * @brief Initialize HTTP response buffer with specified capacity. * @details Allocates memory for HTTP response data with proper initialization. */ -static inline int cc_init_response_buffer(struct http_response *response, size_t initial_capacity) { - if (!response || initial_capacity == 0) { - return 0; - } - - response->data = malloc(initial_capacity); - if (!response->data) { - response->size = 0; - response->capacity = 0; - return 0; - } - - response->size = 0; - response->capacity = initial_capacity; - response->data[0] = '\0'; - return 1; -} +int cc_init_response_buffer(struct http_response *response, size_t initial_capacity); /** * @brief Validate HTTP response buffer integrity. * @details Checks if response buffer is in valid state for operations. */ -static inline int cc_validate_response_buffer(const struct http_response *response) { - return (response && - response->data && - response->size <= response->capacity); -} +int cc_validate_response_buffer(const struct http_response *response); /** * @brief Cleanup HTTP response buffer and free memory. * @details Properly frees allocated memory and resets buffer state. */ -static inline void cc_cleanup_response_buffer(struct http_response *response) { - if (response) { - if (response->data) { - free(response->data); - response->data = NULL; - } - response->size = 0; - response->capacity = 0; - } -} +void cc_cleanup_response_buffer(struct http_response *response); /** * @brief Callback for libcurl to write received data into a buffer. @@ -137,7 +95,7 @@ size_t write_callback(void *contents, size_t size, size_t nmemb, struct http_res * @brief Initializes a CoolerControl session and authenticates with the daemon using configuration. * @details Must be called before any other CoolerControl API function. Sets up CURL session and performs authentication. */ -int init_coolercontrol_session(const Config *config); +int init_coolercontrol_session(const struct Config *config); /** * @brief Returns whether the session is initialized. @@ -155,18 +113,24 @@ void cleanup_coolercontrol_session(void); * @brief Get complete Liquidctl device information (UID, name, screen dimensions) from cache. * @details Reads all LCD device information from cache (no API call). */ -int get_liquidctl_data(const Config *config, char *device_uid, size_t uid_size, char *device_name, size_t name_size, int *screen_width, int *screen_height); +int get_liquidctl_data(const struct Config *config, char *device_uid, size_t uid_size, char *device_name, size_t name_size, int *screen_width, int *screen_height); /** * @brief Initialize device information cache. * @details Fetches and caches device information once at startup for better performance. */ -int init_device_cache(const Config *config); +int init_device_cache(const struct Config *config); /** * @brief Sends an image directly to the LCD of the CoolerControl device. * @details Uploads an image to the LCD display using a multipart HTTP PUT request with brightness and orientation settings. */ -int send_image_to_lcd(const Config *config, const char* image_path, const char* device_uid); +int send_image_to_lcd(const struct Config *config, const char *image_path, const char *device_uid); + +/** + * @brief Extract device type from JSON device object. + * @details Common helper function to extract device type string from JSON device object. + */ +const char *extract_device_type_from_json(json_t *dev); #endif // COOLERCONTROL_H \ No newline at end of file diff --git a/src/display.c b/src/display.c index da2ae69..87e971c 100644 --- a/src/display.c +++ b/src/display.c @@ -15,6 +15,7 @@ */ // Include necessary headers +// cppcheck-suppress-begin missingIncludeSystem #include #include #include @@ -26,21 +27,48 @@ #include #include #include +// cppcheck-suppress-end missingIncludeSystem -// Project headers -#include "../include/config.h" -#include "../include/display.h" -#include "../include/coolercontrol.h" -#include "../include/monitor.h" +// Include project headers +#include "config.h" +#include "display.h" +#include "coolercontrol.h" +#include "monitor.h" /** * @brief Convert color component to cairo format. * @details Converts 8-bit color component (0-255) to cairo's double format (0.0-1.0) for rendering operations. */ -static inline double cairo_color_convert(uint8_t color_component) { +static inline double cairo_color_convert(uint8_t color_component) +{ return color_component / 255.0; } +/** + * @brief Set cairo color from Color structure + * @details Helper function to set cairo source color from Color struct in one call + */ +static inline void set_cairo_color(cairo_t *cr, const Color *color) +{ + cairo_set_source_rgb(cr, + cairo_color_convert(color->r), + cairo_color_convert(color->g), + cairo_color_convert(color->b)); +} + +/** + * @brief Calculate temperature fill width with bounds checking + * @details Simple helper to calculate bar fill width with safety checks + */ +static inline int calculate_temp_fill_width(float temp_value, int max_width) +{ + if (temp_value <= 0.0f) + return 0; + + const float ratio = fminf(temp_value / 105.0f, 1.0f); + return (int)(ratio * max_width); +} + /** * @brief Forward declarations for internal display rendering functions. * @details Function prototypes for internal display rendering helpers and utility functions used by the main rendering pipeline. @@ -51,30 +79,49 @@ static void draw_temperature_bars(cairo_t *cr, const monitor_sensor_data_t *data static void draw_single_temperature_bar(cairo_t *cr, const struct Config *config, float temp_value, int bar_x, int bar_y); static void draw_labels(cairo_t *cr, const struct Config *config); static Color get_temperature_bar_color(const struct Config *config, float val); +static void draw_rounded_rectangle_path(cairo_t *cr, int x, int y, int width, int height); + +/** + * @brief Draw rounded rectangle path for temperature bars. + * @details Helper function to create a rounded rectangle path with consistent corner radius. + */ +static void draw_rounded_rectangle_path(cairo_t *cr, int x, int y, int width, int height) +{ + const double radius = 8.0; + cairo_new_sub_path(cr); + cairo_arc(cr, x + width - radius, y + radius, radius, -DISPLAY_M_PI_2, 0); + cairo_arc(cr, x + width - radius, y + height - radius, radius, 0, DISPLAY_M_PI_2); + cairo_arc(cr, x + radius, y + height - radius, radius, DISPLAY_M_PI_2, DISPLAY_M_PI); + cairo_arc(cr, x + radius, y + radius, radius, DISPLAY_M_PI, 1.5 * DISPLAY_M_PI); + cairo_close_path(cr); +} /** * @brief Calculate color gradient for temperature bars (green → orange → hot orange → red). * @details Determines the RGB color for a given temperature value according to the defined thresholds from config. */ -static Color get_temperature_bar_color(const struct Config *config, float val) { +static Color get_temperature_bar_color(const struct Config *config, float val) +{ // Temperature threshold and color mapping table - const struct { + const struct + { float threshold; Color color; } temp_ranges[] = { {config->temp_threshold_1, config->temp_threshold_1_bar}, {config->temp_threshold_2, config->temp_threshold_2_bar}, {config->temp_threshold_3, config->temp_threshold_3_bar}, - {INFINITY, config->temp_threshold_4_bar} - }; - + {INFINITY, config->temp_threshold_4_bar}}; + // Find the appropriate color range for the given temperature - for (size_t i = 0; i < sizeof(temp_ranges) / sizeof(temp_ranges[0]); i++) { - if (val <= temp_ranges[i].threshold) { + for (size_t i = 0; i < sizeof(temp_ranges) / sizeof(temp_ranges[0]); i++) + { + if (val <= temp_ranges[i].threshold) + { return temp_ranges[i].color; } } - + // Fallback to red if no match found return config->temp_threshold_4_bar; } @@ -83,19 +130,19 @@ static Color get_temperature_bar_color(const struct Config *config, float val) { * @brief Draw a single temperature value. * @details Helper function that renders a temperature value as text with proper positioning and formatting. */ -static inline void draw_temp(cairo_t *cr, const struct Config *config, double temp_value, double y_offset) { +static inline void draw_temp(cairo_t *cr, const struct Config *config, double temp_value, double y_offset) +{ // Input validation with early return char temp_str[16]; cairo_text_extents_t ext; - + // Format temperature string snprintf(temp_str, sizeof(temp_str), "%d°", (int)temp_value); - + // Calculate text extents and position cairo_text_extents(cr, temp_str, &ext); const double x = (config->layout_box_width - ext.width) / 2 + DISPLAY_TEMP_DISPLAY_X_OFFSET; - const double vertical_adjustment = (y_offset < 0) ? - DISPLAY_TEMP_VERTICAL_ADJUSTMENT_TOP : DISPLAY_TEMP_VERTICAL_ADJUSTMENT_BOTTOM; + const double vertical_adjustment = (y_offset < 0) ? DISPLAY_TEMP_VERTICAL_ADJUSTMENT_TOP : DISPLAY_TEMP_VERTICAL_ADJUSTMENT_BOTTOM; const double y = y_offset + (config->layout_box_height + ext.height) / 2 + vertical_adjustment; cairo_move_to(cr, x, y); cairo_show_text(cr, temp_str); @@ -105,123 +152,82 @@ static inline void draw_temp(cairo_t *cr, const struct Config *config, double te * @brief Draw temperature displays with enhanced positioning and validation. * @details Draws the temperature values for CPU and GPU in their respective boxes with improved accuracy and safety checks. */ -static void draw_temperature_displays(cairo_t *cr, const monitor_sensor_data_t *data, const struct Config *config) { +static void draw_temperature_displays(cairo_t *cr, const monitor_sensor_data_t *data, const struct Config *config) +{ // Input validation with early return - if (!cr || !data || !config) return; - + if (!cr || !data || !config) + return; + // temp_cpu display (CPU temperature) with validation - draw_temp(cr, config, data->temp_cpu, -DISPLAY_TEMP_DISPLAY_Y_OFFSET); - + draw_temp(cr, config, data->temp_cpu, - DISPLAY_TEMP_DISPLAY_Y_OFFSET); + // temp_gpu display (GPU temperature) with validation draw_temp(cr, config, data->temp_gpu, config->layout_box_height + DISPLAY_TEMP_DISPLAY_Y_OFFSET); } /** * @brief Draw a single temperature bar with enhanced safety and optimizations. - * @details Helper function that draws background, fill, and border for a temperature bar with rounded corners, comprehensive validation, and optimized cairo operations. + * @details Helper function that draws background, fill, and border for a temperature bar with rounded corners. */ -static void draw_single_temperature_bar(cairo_t *cr, const struct Config *config, float temp_value, int bar_x, int bar_y) { - // Input validation with early return - if (!cr || !config) return; - - // Get color for temperature - Color color = get_temperature_bar_color(config, temp_value); - - // Calculate filled width - int temp_val_w = 0; - if (temp_value > 0.0f) { - // Use safe division and bounds checking - const float temp_ratio = temp_value / 105.0f; - const float clamped_ratio = fmaxf(0.0f, fminf(1.0f, temp_ratio)); - temp_val_w = (int)(clamped_ratio * config->layout_bar_width); - } - - // Ensure filled width is within valid bounds - const int safe_temp_val_w = (temp_val_w < 0) ? 0 : - (temp_val_w > config->layout_bar_width) ? config->layout_bar_width : temp_val_w; - - // Draw bar background with rounded corners - cairo_set_source_rgb(cr, - cairo_color_convert(config->layout_bar_color_background.r), - cairo_color_convert(config->layout_bar_color_background.g), - cairo_color_convert(config->layout_bar_color_background.b)); - - // Draw rounded rectangle for background - cairo_new_sub_path(cr); - cairo_arc(cr, bar_x + config->layout_bar_width - 8.0, bar_y + 8.0, 8.0, -DISPLAY_M_PI_2, 0); - cairo_arc(cr, bar_x + config->layout_bar_width - 8.0, bar_y + config->layout_bar_height - 8.0, 8.0, 0, DISPLAY_M_PI_2); - cairo_arc(cr, bar_x + 8.0, bar_y + config->layout_bar_height - 8.0, 8.0, DISPLAY_M_PI_2, DISPLAY_M_PI); - cairo_arc(cr, bar_x + 8.0, bar_y + 8.0, 8.0, DISPLAY_M_PI, 1.5 * DISPLAY_M_PI); - cairo_close_path(cr); +static void draw_single_temperature_bar(cairo_t *cr, const struct Config *config, float temp_value, int bar_x, int bar_y) +{ + if (!cr || !config) + return; + + // Calculate fill width + const int fill_width = calculate_temp_fill_width(temp_value, config->layout_bar_width); + + // Draw background + set_cairo_color(cr, &config->layout_bar_color_background); + draw_rounded_rectangle_path(cr, bar_x, bar_y, config->layout_bar_width, config->layout_bar_height); cairo_fill(cr); - - // Draw bar fill with temperature color - cairo_set_source_rgb(cr, - cairo_color_convert(color.r), - cairo_color_convert(color.g), - cairo_color_convert(color.b)); - - // Convert to double for cairo operations - const double fill_width = (double)safe_temp_val_w; - cairo_new_sub_path(cr); - - // Optimized rendering based on fill width - if (fill_width > 2 * 8.0) { - // Full rounded corners for wider fills - cairo_arc(cr, bar_x + fill_width - 8.0, bar_y + 8.0, 8.0, -DISPLAY_M_PI_2, 0); - cairo_arc(cr, bar_x + fill_width - 8.0, bar_y + config->layout_bar_height - 8.0, 8.0, 0, DISPLAY_M_PI_2); - cairo_arc(cr, bar_x + 8.0, bar_y + config->layout_bar_height - 8.0, 8.0, DISPLAY_M_PI_2, DISPLAY_M_PI); - cairo_arc(cr, bar_x + 8.0, bar_y + 8.0, 8.0, DISPLAY_M_PI, 1.5 * DISPLAY_M_PI); - } else if (fill_width > 0) { - cairo_rectangle(cr, bar_x, bar_y, fill_width, config->layout_bar_height); - } - - // Fill only if there's something to fill - if (fill_width > 0) { - cairo_close_path(cr); + + // Draw fill if needed + if (fill_width > 0) + { + Color fill_color = get_temperature_bar_color(config, temp_value); + set_cairo_color(cr, &fill_color); + + if (fill_width >= 16) + { + // Use rounded rectangle for wider fills + draw_rounded_rectangle_path(cr, bar_x, bar_y, fill_width, config->layout_bar_height); + } + else + { + // Use simple rectangle for narrow fills + cairo_rectangle(cr, bar_x, bar_y, fill_width, config->layout_bar_height); + } cairo_fill(cr); } - - // Draw bar border with rounded corners - cairo_set_line_width(cr, config->layout_bar_border_width); - cairo_set_source_rgb(cr, - cairo_color_convert(config->layout_bar_color_border.r), - cairo_color_convert(config->layout_bar_color_border.g), - cairo_color_convert(config->layout_bar_color_border.b)); - - // Optimized border drawing - cairo_new_sub_path(cr); - cairo_arc(cr, bar_x + config->layout_bar_width - 8.0, bar_y + 8.0, 8.0, -DISPLAY_M_PI_2, 0); - cairo_arc(cr, bar_x + config->layout_bar_width - 8.0, bar_y + config->layout_bar_height - 8.0, 8.0, 0, DISPLAY_M_PI_2); - cairo_arc(cr, bar_x + 8.0, bar_y + config->layout_bar_height - 8.0, 8.0, DISPLAY_M_PI_2, DISPLAY_M_PI); - cairo_arc(cr, bar_x + 8.0, bar_y + 8.0, 8.0, DISPLAY_M_PI, 1.5 * DISPLAY_M_PI); - // Close path and stroke - cairo_close_path(cr); + // Draw border + cairo_set_line_width(cr, config->layout_bar_border_width); + set_cairo_color(cr, &config->layout_bar_color_border); + draw_rounded_rectangle_path(cr, bar_x, bar_y, config->layout_bar_width, config->layout_bar_height); cairo_stroke(cr); } /** - * @brief Draw optimized temperature bars with enhanced performance and validation. - * @details Draws horizontal bars representing CPU and GPU temperatures with enhanced color gradients, input validation, and optimized positioning calculations. + * @brief Draw temperature bars with simplified positioning. + * @details Draws horizontal bars representing CPU and GPU temperatures with centered positioning. */ -static void draw_temperature_bars(cairo_t *cr, const monitor_sensor_data_t *data, const struct Config *config) { - // Input validation with early return - if (!cr || !data || !config) return; - - // Precompute horizontal position (centered) with validation - if (config->layout_bar_width > config->display_width) return; // Safety check +static void draw_temperature_bars(cairo_t *cr, const monitor_sensor_data_t *data, const struct Config *config) +{ + if (!cr || !data || !config) + return; + + // Calculate centered horizontal position const int bar_x = (config->display_width - config->layout_bar_width) / 2; - - // Precompute vertical positions for CPU and GPU bars with validation - const int total_bar_height = 2 * config->layout_bar_height + config->layout_bar_gap; - if (total_bar_height > config->display_height) return; // Safety check - - // Calculate y-positions for CPU and GPU bars - const int cpu_bar_y = (config->display_height - total_bar_height) / 2 + 1; - const int gpu_bar_y = cpu_bar_y + config->layout_bar_height + config->layout_bar_gap; - - // Draw bars with temperature values + + // Calculate vertical positions for CPU and GPU bars + const int total_height = 2 * config->layout_bar_height + config->layout_bar_gap; + const int start_y = (config->display_height - total_height) / 2; + + const int cpu_bar_y = start_y; + const int gpu_bar_y = start_y + config->layout_bar_height + config->layout_bar_gap; + + // Draw bars draw_single_temperature_bar(cr, config, data->temp_cpu, bar_x, cpu_bar_y); draw_single_temperature_bar(cr, config, data->temp_gpu, bar_x, gpu_bar_y); } @@ -230,19 +236,21 @@ static void draw_temperature_bars(cairo_t *cr, const monitor_sensor_data_t *data * @brief Draw CPU/GPU labels with enhanced positioning and validation. * @details Draws text labels for CPU and GPU with optimized positioning calculations and comprehensive input validation. Font and color are set by the main rendering pipeline. */ -static void draw_labels(cairo_t *cr, const struct Config *config) { +static void draw_labels(cairo_t *cr, const struct Config *config) +{ // Input validation with early return - if (!cr || !config) return; - + if (!cr || !config) + return; + // Positioning values const double box_center_y = config->layout_box_height / 2.0; const double font_half_height = config->font_size_labels / 2.0; - + // CPU label const double cpu_label_y = box_center_y + font_half_height + DISPLAY_LABEL_Y_OFFSET_1; cairo_move_to(cr, 0, cpu_label_y); cairo_show_text(cr, "CPU"); - + // GPU label const double gpu_label_y = config->layout_box_height + box_center_y + font_half_height - DISPLAY_LABEL_Y_OFFSET_2; cairo_move_to(cr, 0, gpu_label_y); @@ -253,34 +261,29 @@ static void draw_labels(cairo_t *cr, const struct Config *config) { * @brief Display rendering. * @details Creates cairo surface and context, renders temperature displays and bars, then saves the result as PNG image. */ -int render_display(const struct Config *config, const monitor_sensor_data_t *data) { - // Input validation with early return - if (!data || !config) { +int render_display(const struct Config *config, const monitor_sensor_data_t *data) +{ + if (!data || !config) + { log_message(LOG_ERROR, "Invalid parameters for render_display"); return 0; } - // Create cairo surface - cairo_surface_t *surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, - config->display_width, - config->display_height); - - // Handle surface creation failure - if (!surface) { + // Create cairo surface and context + cairo_surface_t *surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, + config->display_width, + config->display_height); + if (!surface || cairo_surface_status(surface) != CAIRO_STATUS_SUCCESS) + { log_message(LOG_ERROR, "Failed to create cairo surface"); - return 0; - } - - // Check for any surface errors before proceeding - if (cairo_surface_status(surface) != CAIRO_STATUS_SUCCESS) { - log_message(LOG_ERROR, "Cairo surface creation failed"); - cairo_surface_destroy(surface); + if (surface) + cairo_surface_destroy(surface); return 0; } - // Create cairo context cairo_t *cr = cairo_create(surface); - if (!cr) { + if (!cr) + { log_message(LOG_ERROR, "Failed to create cairo context"); cairo_surface_destroy(surface); return 0; @@ -293,115 +296,97 @@ int render_display(const struct Config *config, const monitor_sensor_data_t *dat // Configure font and color for temperature values cairo_select_font_face(cr, config->font_face, CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD); cairo_set_font_size(cr, config->font_size_temp); - cairo_set_source_rgb(cr, - cairo_color_convert(config->font_color_temp.r), - cairo_color_convert(config->font_color_temp.g), - cairo_color_convert(config->font_color_temp.b)); - + set_cairo_color(cr, &config->font_color_temp); + // Render temperature displays and bars draw_temperature_displays(cr, data, config); draw_temperature_bars(cr, data, config); - // Configure font and color for labels (only if temperatures are below 99°C) - if (data->temp_cpu < 99.0 && data->temp_gpu < 99.0) { - if (config->font_size_labels != config->font_size_temp || - memcmp(&config->font_color_label, &config->font_color_temp, sizeof(Color)) != 0) { - cairo_set_font_size(cr, config->font_size_labels); - cairo_set_source_rgb(cr, - cairo_color_convert(config->font_color_label.r), - cairo_color_convert(config->font_color_label.g), - cairo_color_convert(config->font_color_label.b)); - } - // Render labels only when temperatures are below 99°C + // Render labels only if temperatures are below 99°C + if (data->temp_cpu < 99.0 && data->temp_gpu < 99.0) + { + cairo_set_font_size(cr, config->font_size_labels); + set_cairo_color(cr, &config->font_color_label); draw_labels(cr, config); } - // Ensure all drawing operations are completed before writing to PNG + // Flush and check for errors cairo_surface_flush(surface); - - // Check for any drawing errors before writing - if (cairo_status(cr) != CAIRO_STATUS_SUCCESS) { - log_message(LOG_ERROR, "Cairo drawing error: %s (status: %d)", - cairo_status_to_string(cairo_status(cr)), cairo_status(cr)); + if (cairo_status(cr) != CAIRO_STATUS_SUCCESS) + { + log_message(LOG_ERROR, "Cairo drawing error: %s", + cairo_status_to_string(cairo_status(cr))); cairo_destroy(cr); cairo_surface_destroy(surface); return 0; } - + // Save PNG image cairo_status_t write_status = cairo_surface_write_to_png(surface, config->paths_image_coolerdash); int success = (write_status == CAIRO_STATUS_SUCCESS); - - // Log error if PNG write failed - if (!success) { + + if (!success) + { log_message(LOG_ERROR, "Failed to write PNG image: %s", config->paths_image_coolerdash); } - // Clean up resources + // Clean up cairo_destroy(cr); cairo_surface_destroy(surface); - - // Return success status + return success; } /** - * @brief Main entry point for display updates with enhanced error handling. - * @details Collects sensor data and renders display with comprehensive validation, error handling, and optimized resource management. Handles LCD communication and ensures robust operation in all conditions. + * @brief Main entry point for display updates. + * @details Collects sensor data, renders display, and sends to LCD device. */ -void draw_combined_image(const struct Config *config) { - // Input validation with early return - if (!config) { - log_message(LOG_ERROR, "Invalid config parameter for draw_combined_image"); +void draw_combined_image(const struct Config *config) +{ + if (!config) + { + log_message(LOG_ERROR, "Invalid config parameter"); return; } - - // Initialize data structure with safe defaults - monitor_sensor_data_t sensor_data = {.temp_cpu = 0.0f, .temp_gpu = 0.0f}; - // Retrieve temperature data with validation - if (!get_temperature_monitor_data(config, &sensor_data)) { + // Get sensor data + monitor_sensor_data_t sensor_data = {.temp_cpu = 0.0f, .temp_gpu = 0.0f}; + if (!get_temperature_monitor_data(config, &sensor_data)) + { log_message(LOG_WARNING, "Failed to retrieve temperature data"); - return; // Silently handle sensor data retrieval failure + return; } - // Get device info for LCD operations + // Get LCD device info char device_uid[128] = {0}; char device_name[256] = {0}; int screen_width = 0, screen_height = 0; - - // Get complete device info (UID, name, dimensions) in single API call + const bool device_available = get_liquidctl_data(config, device_uid, sizeof(device_uid), - device_name, sizeof(device_name), - &screen_width, &screen_height); - // Check if device UID is valid - const bool valid_device_uid = device_available && (device_uid[0] != '\0'); - - // Log device info - if (!valid_device_uid) { - log_message(LOG_WARNING, "No valid LCD device UID available"); - } - + device_name, sizeof(device_name), + &screen_width, &screen_height); + // Render display - const int render_result = render_display(config, &sensor_data); - - // Handle rendering failure - if (render_result == 0) { + if (!render_display(config, &sensor_data)) + { log_message(LOG_ERROR, "Display rendering failed"); return; } - - // Send to LCD - if (is_session_initialized() && valid_device_uid) { - const char *name_display = (device_name[0] != '\0') ? device_name : "Unknown Device"; - log_message(LOG_INFO, "Sending image to LCD device: %s [%s]", name_display, device_uid); - log_message(LOG_INFO, "LCD image uploaded successfully"); - - // Send image twice for better reliability - some devices need double transmission + + // Send to LCD if available + if (is_session_initialized() && device_available && device_uid[0] != '\0') + { + const char *name = (device_name[0] != '\0') ? device_name : "Unknown Device"; + log_message(LOG_INFO, "Sending image to LCD: %s [%s]", name, device_uid); + + // Send image (twice for reliability) send_image_to_lcd(config, config->paths_image_coolerdash, device_uid); - send_image_to_lcd(config, config->paths_image_coolerdash, device_uid); // Send twice for better reliability - } else { - log_message(LOG_WARNING, "Skipping LCD upload - conditions not met (session:%d, device:%d)", - is_session_initialized(), valid_device_uid); + send_image_to_lcd(config, config->paths_image_coolerdash, device_uid); + + log_message(LOG_INFO, "LCD image uploaded successfully"); + } + else + { + log_message(LOG_WARNING, "Skipping LCD upload - device not available"); } } \ No newline at end of file diff --git a/include/display.h b/src/display.h similarity index 84% rename from include/display.h rename to src/display.h index 569047c..e912e38 100644 --- a/include/display.h +++ b/src/display.h @@ -19,8 +19,12 @@ #define DISPLAY_H // Include necessary headers +// cppcheck-suppress-begin missingIncludeSystem #include #include +// cppcheck-suppress-end missingIncludeSystem + +// Include project headers #include "monitor.h" // Forward declarations @@ -39,12 +43,12 @@ struct Config; #endif // Display positioning constants for LCD layout -#define DISPLAY_LABEL_Y_OFFSET_1 10 +#define DISPLAY_LABEL_Y_OFFSET_1 8 #define DISPLAY_LABEL_Y_OFFSET_2 16 -#define DISPLAY_TEMP_DISPLAY_X_OFFSET 22 -#define DISPLAY_TEMP_DISPLAY_Y_OFFSET 22 -#define DISPLAY_TEMP_VERTICAL_ADJUSTMENT_TOP 2 -#define DISPLAY_TEMP_VERTICAL_ADJUSTMENT_BOTTOM -3 +#define DISPLAY_TEMP_DISPLAY_X_OFFSET 26 +#define DISPLAY_TEMP_DISPLAY_Y_OFFSET 26 +#define DISPLAY_TEMP_VERTICAL_ADJUSTMENT_TOP 4 +#define DISPLAY_TEMP_VERTICAL_ADJUSTMENT_BOTTOM -6 /** * @brief Collects sensor data and renders display. diff --git a/src/main.c b/src/main.c index ae41487..641dbb5 100644 --- a/src/main.c +++ b/src/main.c @@ -19,7 +19,9 @@ #define _XOPEN_SOURCE 600 // Include necessary headers +// cppcheck-suppress-begin missingIncludeSystem #include +#include #include #include #include @@ -31,12 +33,13 @@ #include #include #include +// cppcheck-suppress-end missingIncludeSystem // Include project headers -#include "../include/config.h" -#include "../include/coolercontrol.h" -#include "../include/display.h" -#include "../include/monitor.h" +#include "config.h" +#include "coolercontrol.h" +#include "display.h" +#include "monitor.h" // Security and performance constants #define DEFAULT_VERSION "unknown" @@ -86,29 +89,30 @@ static const char* read_version_from_file(void) { if (!fp) { log_message(LOG_WARNING, "Could not open VERSION file, using default version"); - strncpy(version_buffer, DEFAULT_VERSION, sizeof(version_buffer) - 1); - version_buffer[sizeof(version_buffer) - 1] = '\0'; + cc_safe_strcpy(version_buffer, sizeof(version_buffer), DEFAULT_VERSION); version_loaded = 1; return version_buffer; } - + // Secure reading with fixed buffer size if (!fgets(version_buffer, sizeof(version_buffer), fp)) { log_message(LOG_WARNING, "Could not read VERSION file, using default version"); - strncpy(version_buffer, DEFAULT_VERSION, sizeof(version_buffer) - 1); - version_buffer[sizeof(version_buffer) - 1] = '\0'; + cc_safe_strcpy(version_buffer, sizeof(version_buffer), DEFAULT_VERSION); } else { // Remove trailing whitespace and newlines version_buffer[strcspn(version_buffer, "\n\r \t")] = '\0'; - - // Validate version string (basic sanity check) - if (version_buffer[0] == '\0' || strlen(version_buffer) > 20) { + + // Validate version string (manual bounded length calculation to avoid strnlen portability issues) + size_t ver_len = 0; + while (ver_len < 21 && version_buffer[ver_len] != '\0') { + ver_len++; + } + if (version_buffer[0] == '\0' || ver_len > 20) { log_message(LOG_WARNING, "Invalid version format, using default version"); - strncpy(version_buffer, DEFAULT_VERSION, sizeof(version_buffer) - 1); - version_buffer[sizeof(version_buffer) - 1] = '\0'; + cc_safe_strcpy(version_buffer, sizeof(version_buffer), DEFAULT_VERSION); } } - + fclose(fp); version_loaded = 1; return version_buffer; @@ -239,17 +243,27 @@ static int write_pid_file(const char *pid_file) { return -1; } - FILE *f = fopen(temp_file, "w"); - if (!f) { + // Open with specific permissions to avoid race condition + int fd = open(temp_file, O_WRONLY | O_CREAT | O_EXCL, 0644); + if (fd == -1) { log_message(LOG_ERROR, "Could not create temporary PID file '%s': %s", temp_file, strerror(errno)); return -1; } + // Convert to FILE* for easier writing + FILE *f = fdopen(fd, "w"); + if (!f) { + log_message(LOG_ERROR, "Could not convert file descriptor to FILE*: %s", strerror(errno)); + close(fd); + unlink(temp_file); + return -1; + } + // Write PID with validation pid_t current_pid = getpid(); if (fprintf(f, "%d\n", current_pid) < 0) { log_message(LOG_ERROR, "Could not write PID to temporary file '%s': %s", temp_file, strerror(errno)); - fclose(f); + fclose(f); // This also closes the fd unlink(temp_file); return -1; } @@ -260,18 +274,13 @@ static int write_pid_file(const char *pid_file) { return -1; } - // Atomic rename + // Atomic rename - file already has correct permissions from open() if (rename(temp_file, pid_file) != 0) { log_message(LOG_ERROR, "Could not rename temporary PID file to '%s': %s", pid_file, strerror(errno)); unlink(temp_file); return -1; } - // Set appropriate permissions - if (chmod(pid_file, 0644) != 0) { - log_message(LOG_WARNING, "Could not set permissions on PID file '%s': %s", pid_file, strerror(errno)); - } - log_message(LOG_STATUS, "PID file: %s (PID: %d)", pid_file, current_pid); return 0; } @@ -577,7 +586,7 @@ int main(int argc, char **argv) { Config config = {0}; log_message(LOG_STATUS, "Loading configuration..."); - if (load_config_ini(&config, config_path) != 0) { + if (load_config(config_path, &config) != 0) { log_message(LOG_ERROR, "Failed to load configuration file: %s", config_path); fprintf(stderr, "Error: Could not load config file '%s'\n", config_path); fprintf(stderr, "Please check:\n"); diff --git a/src/monitor.c b/src/monitor.c index 82e8356..9ad3d4f 100644 --- a/src/monitor.c +++ b/src/monitor.c @@ -15,231 +15,259 @@ */ // Include necessary headers +// cppcheck-suppress-begin missingIncludeSystem #include #include -#include #include #include +#include #include -#include +// cppcheck-suppress-end missingIncludeSystem // Include project headers -#include "../include/config.h" -#include "../include/monitor.h" -#include "../include/coolercontrol.h" +#include "config.h" +#include "monitor.h" +#include "coolercontrol.h" + +/** + * @brief Extract temperature from device status history + * @details Helper function to get temperature from the latest status entry + */ +static float extract_device_temperature(json_t *device, const char *device_type) +{ + // Get status history + json_t *status_history = json_object_get(device, "status_history"); + if (!status_history || !json_is_array(status_history)) + return 0.0f; + + size_t history_count = json_array_size(status_history); + if (history_count == 0) + return 0.0f; + + // Get latest status + json_t *last_status = json_array_get(status_history, history_count - 1); + if (!last_status) + return 0.0f; + + // Get temperatures array + json_t *temps = json_object_get(last_status, "temps"); + if (!temps || !json_is_array(temps)) + return 0.0f; + + // Search for appropriate temperature sensor + size_t temp_count = json_array_size(temps); + for (size_t i = 0; i < temp_count; i++) + { + json_t *temp_entry = json_array_get(temps, i); + if (!temp_entry) + continue; + + json_t *name_val = json_object_get(temp_entry, "name"); + json_t *temp_val = json_object_get(temp_entry, "temp"); + + if (!name_val || !json_is_string(name_val) || !temp_val || !json_is_number(temp_val)) + continue; + + const char *sensor_name = json_string_value(name_val); + float temperature = (float)json_number_value(temp_val); + + // Validate temperature range + if (temperature < -50.0f || temperature > 150.0f) + continue; + + // Check sensor name based on device type + if (strcmp(device_type, "CPU") == 0 && strcmp(sensor_name, "temp1") == 0) + { + return temperature; + } + else if (strcmp(device_type, "GPU") == 0 && + (strstr(sensor_name, "GPU") || strstr(sensor_name, "gpu"))) + { + return temperature; + } + } + + return 0.0f; +} /** * @brief Parse sensor JSON and extract temperatures from CPU and GPU devices. - * @details Parses the JSON response from CoolerControl API to extract CPU and GPU temperature values from device status history. + * @details Simplified JSON parsing to extract CPU and GPU temperature values. */ -static int parse_temperature_data(const char *json, float *temp_cpu, float *temp_gpu) { - // Validate input - if (!json || strlen(json) == 0) { - log_message(LOG_ERROR, "Empty or null JSON input"); +static int parse_temperature_data(const char *json, float *temp_cpu, float *temp_gpu) +{ + if (!json || json[0] == '\0') + { + log_message(LOG_ERROR, "Invalid JSON input"); return 0; } - - // Initialize temperature variables - if (temp_cpu) *temp_cpu = 0.0f; - if (temp_gpu) *temp_gpu = 0.0f; + + // Initialize outputs + if (temp_cpu) + *temp_cpu = 0.0f; + if (temp_gpu) + *temp_gpu = 0.0f; // Parse JSON json_error_t json_error; json_t *root = json_loads(json, 0, &json_error); - if (!root) { - log_message(LOG_ERROR, "JSON parse error in monitor: %s at line %d", - json_error.text, json_error.line); + if (!root) + { + log_message(LOG_ERROR, "JSON parse error: %s", json_error.text); return 0; } // Get devices array json_t *devices = json_object_get(root, "devices"); - if (!devices || !json_is_array(devices)) { + if (!devices || !json_is_array(devices)) + { json_decref(root); return 0; } - const size_t device_count = json_array_size(devices); + // Search for CPU and GPU devices + size_t device_count = json_array_size(devices); int cpu_found = 0, gpu_found = 0; - - // Iterate over devices - for (size_t i = 0; i < device_count && (!cpu_found || !gpu_found); i++) { - json_t *dev = json_array_get(devices, i); - if (!dev) continue; - - // Get device type - json_t *type_val = json_object_get(dev, "type"); - if (!type_val || !json_is_string(type_val)) continue; - - // Extract device type string - const char *type_str = json_string_value(type_val); - - // Check device type - if ((type_str[0] != 'C' && type_str[0] != 'G') || - (strcmp(type_str, "CPU") != 0 && strcmp(type_str, "GPU") != 0)) continue; - - // Skip if already found - if ((type_str[0] == 'C' && cpu_found) || (type_str[0] == 'G' && gpu_found)) continue; - - // Get status history - json_t *status_history = json_object_get(dev, "status_history"); - if (!status_history || !json_is_array(status_history) || json_array_size(status_history) == 0) continue; - - // Get last status - json_t *last_status = json_array_get(status_history, json_array_size(status_history) - 1); - if (!last_status) continue; - - // Get temperatures - json_t *temps = json_object_get(last_status, "temps"); - if (!temps || !json_is_array(temps)) continue; - - // Get temperature count - const size_t temp_count = json_array_size(temps); - - // Iterate over temperatures - for (size_t j = 0; j < temp_count; j++) { - // Get temperature entry - json_t *temp_entry = json_array_get(temps, j); - if (!temp_entry) continue; - - // Get sensor name and temperature value - json_t *name_val = json_object_get(temp_entry, "name"); - json_t *temp_val = json_object_get(temp_entry, "temp"); - - // Validate temperature entry - if (!name_val || !json_is_string(name_val) || !temp_val || !json_is_number(temp_val)) continue; - - // Extract sensor name and temperature value - const char *sensor_name = json_string_value(name_val); - const float temperature = (float)json_number_value(temp_val); - - // Basic temperature validation - if (temperature < -50.0f || temperature > 150.0f) { - log_message(LOG_WARNING, "Temperature %.1f°C out of range, skipping", temperature); - continue; - } - // Check sensor type and assign temperature - if (type_str[0] == 'C' && temp_cpu && !cpu_found) { // CPU device - if (sensor_name[0] == 't' && strcmp(sensor_name, "temp1") == 0) { - *temp_cpu = temperature; - cpu_found = 1; - break; - } - } else if (type_str[0] == 'G' && temp_gpu && !gpu_found) { // GPU device - if ((sensor_name[0] == 'G' && strstr(sensor_name, "GPU")) || - (sensor_name[0] == 'g' && strstr(sensor_name, "gpu"))) { - *temp_gpu = temperature; - gpu_found = 1; - break; - } + for (size_t i = 0; i < device_count && (!cpu_found || !gpu_found); i++) + { + json_t *device = json_array_get(devices, i); + if (!device) + continue; + + const char *device_type = extract_device_type_from_json(device); + if (!device_type) + continue; + + if (!cpu_found && strcmp(device_type, "CPU") == 0) + { + if (temp_cpu) + { + *temp_cpu = extract_device_temperature(device, "CPU"); + cpu_found = 1; + } + } + else if (!gpu_found && strcmp(device_type, "GPU") == 0) + { + if (temp_gpu) + { + *temp_gpu = extract_device_temperature(device, "GPU"); + gpu_found = 1; } } } - // Cleanup json_decref(root); return 1; } +/** + * @brief Configure CURL for status API request + * @details Helper function to set up CURL options for temperature data request + */ +static void configure_status_request(CURL *curl, const char *url, struct http_response *response) +{ + // Basic CURL configuration + curl_easy_setopt(curl, CURLOPT_URL, url); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, (curl_write_callback)write_callback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, response); + curl_easy_setopt(curl, CURLOPT_USERAGENT, "CoolerDash/1.0"); + curl_easy_setopt(curl, CURLOPT_POST, 1L); + + // POST data for status request + const char *post_data = "{\"all\":false,\"since\":\"1970-01-01T00:00:00.000Z\"}"; + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, post_data); +} + /** * @brief Get CPU and GPU temperature data from CoolerControl API. - * @details Sends HTTP POST request to CoolerControl status endpoint and parses the JSON response to extract temperature values. + * @details Simplified HTTP request to get temperature data from status endpoint. */ -int get_temperature_data(const Config *config, float *temp_cpu, float *temp_gpu) { - // Validate input parameters - if (!config || !temp_cpu || !temp_gpu) return 0; - - // Initialize temperature variables +int get_temperature_data(const Config *config, float *temp_cpu, float *temp_gpu) +{ + if (!config || !temp_cpu || !temp_gpu) + return 0; + + // Initialize outputs *temp_cpu = 0.0f; *temp_gpu = 0.0f; - - // Validate daemon address - if (strlen(config->daemon_address) == 0) { + + if (config->daemon_address[0] == '\0') + { log_message(LOG_ERROR, "No daemon address configured"); return 0; } - + // Initialize CURL CURL *curl = curl_easy_init(); - if (!curl) { + if (!curl) + { log_message(LOG_ERROR, "Failed to initialize CURL"); return 0; } - - // Construct URL + + // Build URL char url[256]; - const int url_len = snprintf(url, sizeof(url), "%s/status", config->daemon_address); - if (url_len < 0 || url_len >= (int)sizeof(url)) { + int url_len = snprintf(url, sizeof(url), "%s/status", config->daemon_address); + if (url_len < 0 || url_len >= (int)sizeof(url)) + { curl_easy_cleanup(curl); return 0; } - + // Initialize response buffer - struct http_response chunk = {0}; - const size_t initial_capacity = 8192; - chunk.data = malloc(initial_capacity); - if (!chunk.data) { + struct http_response response = {0}; + if (!cc_init_response_buffer(&response, 8192)) + { log_message(LOG_ERROR, "Failed to allocate response buffer"); curl_easy_cleanup(curl); return 0; } - chunk.size = 0; - chunk.capacity = initial_capacity; - - // Configure curl options - curl_easy_setopt(curl, CURLOPT_URL, url); - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, &chunk); - + + // Configure request + configure_status_request(curl, url, &response); + // Enable SSL verification for HTTPS - if (strncmp(config->daemon_address, "https://", 8) == 0) { + if (strncmp(config->daemon_address, "https://", 8) == 0) + { curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2L); } - - // Set user agent and request type - curl_easy_setopt(curl, CURLOPT_USERAGENT, "CoolerDash/1.0"); - curl_easy_setopt(curl, CURLOPT_POST, 1L); - - // Set HTTP headers + + // Set headers struct curl_slist *headers = NULL; headers = curl_slist_append(headers, "accept: application/json"); headers = curl_slist_append(headers, "content-type: application/json"); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); - - // Send POST request - const char *post_data = "{\"all\":false,\"since\":\"1970-01-01T00:00:00.000Z\"}"; - curl_easy_setopt(curl, CURLOPT_POSTFIELDS, post_data); - - // Initialize temperature variables - float cpu_temp = 0.0f, gpu_temp = 0.0f; + + // Perform request and parse response int result = 0; - - // Perform request CURLcode curl_result = curl_easy_perform(curl); - if (curl_result == CURLE_OK) { + if (curl_result == CURLE_OK) + { long response_code = 0; curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code); - - if (response_code == 200) { - result = parse_temperature_data(chunk.data, &cpu_temp, &gpu_temp); - if (result) { - *temp_cpu = cpu_temp; - *temp_gpu = gpu_temp; - } - } else { + + if (response_code == 200) + { + result = parse_temperature_data(response.data, temp_cpu, temp_gpu); + } + else + { log_message(LOG_ERROR, "HTTP error: %ld when fetching temperature data", response_code); } - } else { + } + else + { log_message(LOG_ERROR, "CURL error: %s", curl_easy_strerror(curl_result)); } - + // Cleanup - if (chunk.data) free(chunk.data); - if (headers) curl_slist_free_all(headers); + cc_cleanup_response_buffer(&response); + if (headers) + curl_slist_free_all(headers); curl_easy_cleanup(curl); - + return result; } @@ -247,9 +275,11 @@ int get_temperature_data(const Config *config, float *temp_cpu, float *temp_gpu) * @brief Get all relevant sensor data (CPU/GPU temperature and LCD UID). * @details Reads the current CPU and GPU temperatures and LCD UID via API. */ -int get_temperature_monitor_data(const Config *config, monitor_sensor_data_t *data) { +int get_temperature_monitor_data(const Config *config, monitor_sensor_data_t *data) +{ // Check if config and data pointers are valid - if (!config || !data) return 0; + if (!config || !data) + return 0; // Get temperature data from monitor module return get_temperature_data(config, &data->temp_cpu, &data->temp_gpu); @@ -259,8 +289,8 @@ int get_temperature_monitor_data(const Config *config, monitor_sensor_data_t *da * @brief Initialize the monitor component with the given configuration. * @details Currently does nothing but returns success. Future implementations may include initialization logic. */ -int init_monitr(const Config *config) { +int init_monitr(const Config *config) +{ (void)config; return 1; } - diff --git a/include/monitor.h b/src/monitor.h similarity index 95% rename from include/monitor.h rename to src/monitor.h index b600570..8048d92 100644 --- a/include/monitor.h +++ b/src/monitor.h @@ -19,7 +19,9 @@ #define MONITOR_H // Include necessary headers +// cppcheck-suppress-begin missingIncludeSystem #include +// cppcheck-suppress-end missingIncludeSystem // Forward declaration struct Config; diff --git a/ssh_allowed_signers b/ssh_allowed_signers index 8246b38..f1fd4ce 100644 --- a/ssh_allowed_signers +++ b/ssh_allowed_signers @@ -1 +1,2 @@ -ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIO92Y+/59CqpVXU5a5KivLQQYPqVjGWGkcJ7L2XRbT8N christkue79@gmail.com +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIO92Y+/59CqpVXU5a5KivLQQYPqVjGWGkcJ7L2XRbT8N +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIB30fKduEl/zkAFgiQ69Q6YSexVeqlOUNr0dOwLrln8Y