├── .gitattributes ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ ├── config.yml │ └── feature_request.yml ├── NOTE.md ├── dependabot.yml └── workflows │ ├── build.yml │ ├── create_release.yml │ └── upload_artifacts.yml ├── .gitignore ├── BUILD.md ├── CI ├── build_linux.sh └── build_windows.sh ├── DEV.md ├── FAQ.md ├── HardwareLib ├── HardwareLib.sln ├── LibreFanControlService │ ├── External │ │ └── LmSensors │ │ │ ├── LmSensorsWrapper.cs │ │ │ ├── include │ │ │ ├── error.h │ │ │ └── sensors.h │ │ │ └── libsensors.so.5.0.0 │ ├── Hardware │ │ ├── BaseHardware.cs │ │ ├── Control │ │ │ ├── BaseControl.cs │ │ │ ├── LHMControl.cs │ │ │ └── LmControl.cs │ │ ├── HardwareManager.cs │ │ ├── LHM.cs │ │ ├── LmSensors.cs │ │ └── Sensor │ │ │ ├── BaseSensor.cs │ │ │ ├── LHMSensor.cs │ │ │ └── LmSensor.cs │ ├── Item │ │ ├── Behavior │ │ │ ├── Behavior.cs │ │ │ ├── Flat.cs │ │ │ ├── Linear.cs │ │ │ └── Target.cs │ │ ├── Control.cs │ │ └── CustomTemp.cs │ ├── LibreFanControlService.csproj │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── Proto │ │ ├── ConfHelper.cs │ │ ├── CrossApi.cs │ │ └── SettingsHelper.cs │ ├── State.cs │ ├── Update.cs │ ├── Utils │ │ └── Utils.cs │ ├── Worker.cs │ ├── appsettings.Development.json │ └── appsettings.json └── global.json ├── INSTALL.md ├── LICENSE ├── LICENSE_LIB └── Mozilla Public License 2.0 ├── LibreFanControl ├── app │ ├── build.gradle.kts │ ├── drawable │ │ ├── app_icon.ico │ │ ├── app_icon.png │ │ └── conf │ │ │ ├── conf#1 │ │ │ └── settings │ ├── include │ │ ├── linux │ │ │ └── scripts │ │ │ │ ├── Manager.sh │ │ │ │ ├── change_start_mode.sh │ │ │ │ ├── install.sh │ │ │ │ ├── libre-fan-control.service │ │ │ │ ├── start.sh │ │ │ │ └── uninstall.sh │ │ └── windows │ │ │ └── scripts │ │ │ ├── Manager.ps1 │ │ │ ├── change_start_mode.ps1 │ │ │ ├── install.ps1 │ │ │ ├── start.ps1 │ │ │ └── uninstall.ps1 │ └── src │ │ ├── jvmMain │ │ ├── kotlin │ │ │ ├── Application.kt │ │ │ ├── FState.kt │ │ │ ├── Main.kt │ │ │ ├── Settings.kt │ │ │ ├── model │ │ │ │ ├── hardware │ │ │ │ │ └── BaseH.kt │ │ │ │ └── item │ │ │ │ │ ├── BaseI.kt │ │ │ │ │ ├── BaseIBehavior.kt │ │ │ │ │ ├── BaseISensor.kt │ │ │ │ │ └── IControl.kt │ │ │ ├── proto │ │ │ │ ├── ConfHelper.kt │ │ │ │ ├── CrossApi.kt │ │ │ │ └── SettingsHelper.kt │ │ │ ├── settingSlidingWindows │ │ │ │ ├── AdvanceSettingDsl.kt │ │ │ │ ├── AdvanceSettingScopeImpl.kt │ │ │ │ ├── SettingDsl.kt │ │ │ │ ├── SettingScopeImpl.kt │ │ │ │ ├── SettingSlidingWindows.kt │ │ │ │ └── utils │ │ │ │ │ ├── Type.kt │ │ │ │ │ └── resources.kt │ │ │ ├── ui │ │ │ │ ├── addItem │ │ │ │ │ ├── StatesChoice.kt │ │ │ │ │ └── addItem.kt │ │ │ │ ├── component │ │ │ │ │ ├── managerExpandItem.kt │ │ │ │ │ ├── managerListChoice.kt │ │ │ │ │ ├── managerText.kt │ │ │ │ │ └── managerTextField.kt │ │ │ │ ├── configuration │ │ │ │ │ ├── confDialogs │ │ │ │ │ │ ├── ConfVM.kt │ │ │ │ │ │ └── confDialogs.kt │ │ │ │ │ └── confTopBar.kt │ │ │ │ ├── container │ │ │ │ │ ├── body.kt │ │ │ │ │ ├── home.kt │ │ │ │ │ └── topBar.kt │ │ │ │ ├── dialogs │ │ │ │ │ ├── baseDialog.kt │ │ │ │ │ └── errorDialog.kt │ │ │ │ ├── itemsList │ │ │ │ │ ├── baseItem.kt │ │ │ │ │ ├── behaviorList │ │ │ │ │ │ ├── BaseBehaviorVM.kt │ │ │ │ │ │ ├── behavior.kt │ │ │ │ │ │ ├── flat │ │ │ │ │ │ │ ├── FlatVM.kt │ │ │ │ │ │ │ ├── baseFlat.kt │ │ │ │ │ │ │ └── managerFlat.kt │ │ │ │ │ │ └── linearAndTarget │ │ │ │ │ │ │ ├── baseLinearAndTarget.kt │ │ │ │ │ │ │ ├── linear │ │ │ │ │ │ │ ├── LinearVM.kt │ │ │ │ │ │ │ ├── baseLinear.kt │ │ │ │ │ │ │ └── managerLinear.kt │ │ │ │ │ │ │ ├── managerNumberChoice.kt │ │ │ │ │ │ │ └── target │ │ │ │ │ │ │ ├── TargetVM.kt │ │ │ │ │ │ │ ├── baseTarget.kt │ │ │ │ │ │ │ └── managerTarget.kt │ │ │ │ │ ├── controlList │ │ │ │ │ │ ├── ControlVM.kt │ │ │ │ │ │ └── managerControl.kt │ │ │ │ │ └── sensor │ │ │ │ │ │ ├── addItem │ │ │ │ │ │ ├── AddSensorVM.kt │ │ │ │ │ │ └── sensorAddItem.kt │ │ │ │ │ │ ├── baseSensor.kt │ │ │ │ │ │ └── body │ │ │ │ │ │ ├── fanList │ │ │ │ │ │ ├── FanVM.kt │ │ │ │ │ │ └── fan.kt │ │ │ │ │ │ └── tempList │ │ │ │ │ │ ├── TempVM.kt │ │ │ │ │ │ ├── customTemp.kt │ │ │ │ │ │ └── temp.kt │ │ │ │ ├── settings │ │ │ │ │ ├── donate.kt │ │ │ │ │ ├── drawer │ │ │ │ │ │ ├── SettingsVM.kt │ │ │ │ │ │ └── drawerContent.kt │ │ │ │ │ ├── help.kt │ │ │ │ │ ├── info.kt │ │ │ │ │ ├── language.kt │ │ │ │ │ ├── service.kt │ │ │ │ │ ├── theme.kt │ │ │ │ │ └── updateDelay.kt │ │ │ │ └── theme │ │ │ │ │ ├── Color.kt │ │ │ │ │ ├── Shape.kt │ │ │ │ │ ├── Space.kt │ │ │ │ │ ├── Theme.kt │ │ │ │ │ └── Type.kt │ │ │ └── utils │ │ │ │ ├── OsInfo.kt │ │ │ │ ├── OsSpecific.kt │ │ │ │ ├── Resources.kt │ │ │ │ ├── init.kt │ │ │ │ ├── json.kt │ │ │ │ └── utils.kt │ │ └── resources │ │ │ ├── drawable │ │ │ ├── app │ │ │ │ └── toys_fan48.svg │ │ │ ├── arrow │ │ │ │ ├── arrow_back40.svg │ │ │ │ ├── arrow_forward40.svg │ │ │ │ ├── chevron │ │ │ │ │ ├── chevron_left24.svg │ │ │ │ │ ├── chevron_left40.svg │ │ │ │ │ ├── chevron_right24.svg │ │ │ │ │ └── chevron_right40.svg │ │ │ │ ├── dropDown │ │ │ │ │ ├── arrow_drop_down24.svg │ │ │ │ │ ├── arrow_drop_down40.svg │ │ │ │ │ ├── arrow_drop_up24.svg │ │ │ │ │ └── arrow_drop_up40.svg │ │ │ │ ├── expand │ │ │ │ │ ├── expand_less24.svg │ │ │ │ │ ├── expand_less40.svg │ │ │ │ │ ├── expand_more24.svg │ │ │ │ │ └── expand_more40.svg │ │ │ │ └── notch │ │ │ │ │ ├── line_end_arrow_notch40.svg │ │ │ │ │ └── line_start_arrow_notch40.svg │ │ │ ├── items │ │ │ │ ├── alternate_email24.svg │ │ │ │ ├── horizontal_rule24.svg │ │ │ │ ├── linear24.svg │ │ │ │ ├── my_location24.svg │ │ │ │ ├── psychology24.svg │ │ │ │ ├── speed24.svg │ │ │ │ ├── thermometer24.svg │ │ │ │ ├── thermostat24.svg │ │ │ │ └── toys_fan24.svg │ │ │ ├── select │ │ │ │ ├── check24.svg │ │ │ │ ├── close │ │ │ │ │ ├── close20.svg │ │ │ │ │ ├── close24.svg │ │ │ │ │ ├── close40.svg │ │ │ │ │ └── close48.svg │ │ │ │ ├── delete_forever24.svg │ │ │ │ ├── delete_forever40.svg │ │ │ │ ├── radio_button_unchecked20.svg │ │ │ │ └── radio_button_unchecked24.svg │ │ │ ├── settings │ │ │ │ ├── attach_money24.svg │ │ │ │ ├── attach_money40.svg │ │ │ │ ├── autorenew24.svg │ │ │ │ ├── autorenew40.svg │ │ │ │ ├── dark_mode24.svg │ │ │ │ ├── dark_mode40.svg │ │ │ │ ├── help24.svg │ │ │ │ ├── help40.svg │ │ │ │ ├── history40.svg │ │ │ │ ├── info24.svg │ │ │ │ ├── info40.svg │ │ │ │ ├── translate24.svg │ │ │ │ ├── translate40.svg │ │ │ │ └── update24.svg │ │ │ ├── sign │ │ │ │ ├── minus │ │ │ │ │ ├── remove20.svg │ │ │ │ │ ├── remove24.svg │ │ │ │ │ ├── remove40.svg │ │ │ │ │ └── remove48.svg │ │ │ │ └── plus │ │ │ │ │ ├── add20.svg │ │ │ │ │ ├── add24.svg │ │ │ │ │ ├── add40.svg │ │ │ │ │ └── add48.svg │ │ │ └── topBar │ │ │ │ ├── arrow_forward40.svg │ │ │ │ ├── delete40.svg │ │ │ │ ├── edit40.svg │ │ │ │ ├── error40.svg │ │ │ │ ├── forward40.svg │ │ │ │ ├── forward_media40.svg │ │ │ │ ├── menu40.svg │ │ │ │ ├── save40.svg │ │ │ │ ├── send40.svg │ │ │ │ └── toys_fan40.svg │ │ │ └── values │ │ │ ├── strings-en.json │ │ │ ├── strings-fr.json │ │ │ └── strings-zh_cn.json │ │ └── jvmTest │ │ └── kotlin │ │ ├── JsonTest.kt │ │ ├── LinearTest.kt │ │ ├── SerializationProtoTest.kt │ │ └── TempValue.kt ├── build.gradle.kts ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── proto │ ├── build.gradle.kts │ └── src │ │ └── proto │ │ ├── conf.proto │ │ ├── crossApi.proto │ │ └── settings.proto ├── scripts │ └── remove_icons_suffixes.ps1 └── settings.gradle ├── README.md └── assets ├── mainPageV0.png ├── mainPageV1.png └── zh_CN ├── FAQ_zh-CN.md ├── INSTALL_zh-CN.md └── README_zh-CN.md /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [wiiznokes] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: ["https://www.paypal.com/donate/?hosted_button_id=HV84HZ4G63HQ6"] # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: 🐞 Issue Report 2 | description: Report a issue in LibreFanControl 3 | labels: [bug] 4 | body: 5 | 6 | - type: textarea 7 | id: reproduce-steps 8 | attributes: 9 | label: Steps to reproduce 10 | description: Provide an example of the issue. 11 | placeholder: | 12 | Example: 13 | 1. First step 14 | 2. Second step 15 | 3. Issue here 16 | validations: 17 | required: false 18 | 19 | - type: textarea 20 | id: expected-behavior 21 | attributes: 22 | label: Expected behavior 23 | placeholder: | 24 | Example: 25 | "This should happen..." 26 | validations: 27 | required: false 28 | 29 | - type: textarea 30 | id: actual-behavior 31 | attributes: 32 | label: Actual behavior 33 | placeholder: | 34 | Example: 35 | "This happened instead..." 36 | validations: 37 | required: false 38 | 39 | - type: input 40 | id: librefancontrol-version 41 | attributes: 42 | label: LibreFanControl version 43 | description: | 44 | You can find your LibreFanControl version in **Settings**. 45 | placeholder: | 46 | Example: "0.2.6" 47 | validations: 48 | required: false 49 | 50 | - type: dropdown 51 | id: os 52 | attributes: 53 | label: Operative System 54 | description: Which operative system are you using? 55 | options: 56 | - Windows 57 | - Linux 58 | - Both 59 | validations: 60 | required: false 61 | 62 | - type: textarea 63 | id: other-details 64 | attributes: 65 | label: Other details 66 | placeholder: | 67 | Additional details and attachments. 68 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: ⭐ Feature request 2 | description: Suggest a feature to improve the app 3 | labels: [enhancement] 4 | body: 5 | 6 | - type: textarea 7 | id: feature-description 8 | attributes: 9 | label: Describe your suggested feature 10 | description: How can an existing source be improved? 11 | placeholder: | 12 | Example: 13 | "It should work like this..." 14 | validations: 15 | required: false 16 | 17 | - type: textarea 18 | id: other-details 19 | attributes: 20 | label: Other details 21 | placeholder: | 22 | Additional details and attachments. 23 | -------------------------------------------------------------------------------- /.github/NOTE.md: -------------------------------------------------------------------------------- 1 | ## Create a new release: 2 | 3 | ``` 4 | git tag -a v*.*.* -m message 5 | ``` 6 | ``` 7 | git push --tags 8 | ``` 9 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "nuget" 9 | directory: "./HardwareLib/LibreFanControlService" 10 | schedule: 11 | interval: "monthly" 12 | - package-ecosystem: "gradle" 13 | directory: "./LibreFanControl" 14 | schedule: 15 | interval: "monthly" 16 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: 'Build artifacts' 2 | on: 3 | workflow_dispatch: 4 | pull_request: 5 | permissions: 6 | contents: write 7 | 8 | 9 | jobs: 10 | build_msi: 11 | runs-on: "windows-latest" 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v3 15 | 16 | - name: Setup Java JDK 17 | uses: actions/setup-java@v3 18 | with: 19 | java-version: 17 20 | distribution: 'temurin' 21 | cache: gradle 22 | 23 | - name: Build Windows artifact 24 | run: "bash ./CI/build_windows.sh" 25 | 26 | 27 | build_app_image: 28 | runs-on: "ubuntu-latest" 29 | steps: 30 | - name: Checkout 31 | uses: actions/checkout@v3 32 | 33 | - name: Setup Java JDK 34 | uses: actions/setup-java@v3 35 | with: 36 | java-version: 17 37 | distribution: 'temurin' 38 | cache: gradle 39 | 40 | - name: Build linux artifact 41 | run: "bash ./CI/build_linux.sh" 42 | -------------------------------------------------------------------------------- /.github/workflows/create_release.yml: -------------------------------------------------------------------------------- 1 | name: Create release 2 | on: 3 | push: 4 | tags: 5 | - 'v*.*.*' 6 | permissions: 7 | contents: write 8 | 9 | 10 | jobs: 11 | upload_artifacts: 12 | uses: ./.github/workflows/upload_artifacts.yml 13 | 14 | 15 | create_release: 16 | needs: upload_artifacts 17 | runs-on: ubuntu-latest 18 | 19 | steps: 20 | - name: Checkout 21 | uses: actions/checkout@v3 22 | 23 | - name: Download artifacts 24 | uses: actions/download-artifact@v3 25 | with: 26 | path: ./publish 27 | 28 | - name: Publish Release 29 | uses: ncipollo/release-action@v1 30 | with: 31 | artifacts: "./publish/*/*" 32 | generateReleaseNotes: true 33 | makeLatest: true -------------------------------------------------------------------------------- /.github/workflows/upload_artifacts.yml: -------------------------------------------------------------------------------- 1 | name: 'Upload artifacts' 2 | on: 3 | workflow_dispatch: 4 | workflow_call: 5 | permissions: 6 | contents: write 7 | 8 | 9 | jobs: 10 | upload_msi_artifact: 11 | runs-on: "windows-latest" 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v3 15 | 16 | - name: Setup Java JDK 17 | uses: actions/setup-java@v3 18 | with: 19 | java-version: 17 20 | distribution: 'temurin' 21 | cache: gradle 22 | 23 | - name: Build Windows artifact 24 | run: "bash ./CI/build_windows.sh" 25 | 26 | - name: Upload Windows artifact 27 | uses: actions/upload-artifact@v3.1.2 28 | with: 29 | name: LibreFanControl.msi 30 | path: publish/LibreFanControl*.msi 31 | 32 | 33 | upload_app_image_artifact: 34 | runs-on: "ubuntu-latest" 35 | steps: 36 | - name: Checkout 37 | uses: actions/checkout@v3 38 | 39 | - name: Setup Java JDK 40 | uses: actions/setup-java@v3 41 | with: 42 | java-version: 17 43 | distribution: 'temurin' 44 | cache: gradle 45 | 46 | - name: Build linux artifact 47 | run: "bash ./CI/build_linux.sh" 48 | 49 | - name: Upload Linux artifact 50 | uses: actions/upload-artifact@v3.1.2 51 | with: 52 | name: LibreFanControl.tar.gz 53 | path: publish/LibreFanControl*.tar.gz 54 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | publish/ 2 | 3 | .vscode/ 4 | .idea/ 5 | .vs/ 6 | 7 | .gradle/ 8 | 9 | *.so 10 | *.o 11 | 12 | build/ 13 | obj/ 14 | bin/ 15 | 16 | 17 | generated/ 18 | 19 | *.log 20 | wix311/ 21 | 22 | -------------------------------------------------------------------------------- /BUILD.md: -------------------------------------------------------------------------------- 1 | For running the app in debug mode (without installing the service), set the environnement variable `DEBUG=1` at runtime. 2 | 3 | 4 | 5 | - On Windows, if you want to build the project, you will just need the `SDK` of [dotnet 7](https://dotnet.microsoft.com/en-us/download/dotnet/7.0). 6 | - On Linux, you will need a [JDK](https://jdk.java.net/java-se-ri/17). 7 | 8 | The needed commands can be found in CI directory. -------------------------------------------------------------------------------- /CI/build_linux.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | rm -rf publish 5 | 6 | 7 | dotnet build ./HardwareLib/LibreFanControlService -c "Release Linux" 8 | cd ./LibreFanControl 9 | chmod +x ./gradlew 10 | ./gradlew generateAllProto 11 | ./gradlew packageReleaseAppImage 12 | 13 | 14 | 15 | cd .. 16 | mkdir publish 17 | cp -r ./LibreFanControl/app/build/compose/binaries/main-release/app/LibreFanControl ./publish 18 | echo files copied ! 19 | cd publish 20 | 21 | chmod +x ./LibreFanControl/lib/app/resources/build/LibreFanControlService 22 | echo set LibreFanControlService executable ! 23 | tar -czvf LibreFanControl.tar.gz LibreFanControl/* > /dev/null 24 | echo archive created ! 25 | -------------------------------------------------------------------------------- /CI/build_windows.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | rm -rf publish 5 | 6 | dotnet build ./HardwareLib/LibreFanControlService -c "Release Windows" 7 | 8 | cd ./LibreFanControl 9 | ./gradlew generateAllProto 10 | ./gradlew packageReleaseMsi 11 | 12 | cd .. 13 | mkdir publish 14 | cp ./LibreFanControl/app/build/compose/binaries/main-release/msi/LibreFanControl* ./publish 15 | echo files copied ! -------------------------------------------------------------------------------- /DEV.md: -------------------------------------------------------------------------------- 1 | The app is stuctured this way: 2 | 3 | An UI made with compose multiplatform 4 | A service made in C# 5 | 6 | The main problem with Fan control apps is that there is one lib on Windows whitch can control fans, and it is written in C#. 7 | This is very limiting because C# in a manager language (garbage collector), so it's really hard to interop with it. 8 | 9 | Ui on C# is really bad imo. I could have used Alvalonia, but after some try, i can tell this is really a pain after comming from jetpack Compose on Android. 10 | 11 | As i knowed Kotlin, i choosed to use compose multiplatfrom. 12 | I first tried to interop directly with C# thanks to JNI, but this is really not easy to debug/scale 13 | So i created a service instead. 14 | 15 | This as several drowbacks: 16 | 17 | - lot of complexicity is added 18 | - packaging is hard 19 | - the service is always listening is the app want info 20 | - lot of techno is added (C#, grpc, proto) 21 | 22 | But really, there is no good solution for this kind of project. 23 | 24 | Compose has also some drowbacks 25 | - use lot of memomy 26 | - Gradle O: 27 | 28 | But is fast and simple to use 29 | 30 | having a service have also some pros: 31 | - seperate control of the hardware with other things 32 | - expose an API on the network that other app could use 33 | 34 | After this project, i discovered Rust, and i can tell it would have been much better suited, especially for the service. 35 | simple build system, no GC, crates, cargo, types system, efficient, ....... 36 | I think that for windows, this is the best we can get, using C# to make such a low level lib is very dumb imo; but for Linux, anoter project could be created using Rust and C interop for lm-sensors. I plan to maybe do that latter. Probably with just a tool to create config file, and an applet icon. 37 | 38 | 39 | Neverless here is some infos about this app: 40 | 41 | lm-sensor is build using a special fork on one maintener of the repo, with pwm support: 42 | https://github.com/Wer-Wolf/lm-sensors/tree/pwm 43 | 44 | - model: Model use to describe Items on the screen. 45 | - settingSlidingWindows: (lib) to make a setting with sliding windows, as the name said 46 | - proto: helper functions to use proto generated files 47 | 48 | 49 | when the app is launch, we start the service(with a script), then we call open() (using grpc) 50 | 51 | we get the hardware using getHardware() function 52 | 53 | then we call startUpdate() to set a stream of values on this hardware. 54 | 55 | when we save a configuration, we notify the service with settingsAndConfChange() 56 | 57 | The service is responsible to calculate his values, and the app is also responsible to calculate his values (yes, i know this i bad). 58 | 59 | well, if you make it this far, thank you. 60 | -------------------------------------------------------------------------------- /FAQ.md: -------------------------------------------------------------------------------- 1 | # LibreFanControl FAQ 2 | 3 | *Translation*: [简体中文](./assets/zh_CN/FAQ_zh-CN.md) 4 | 5 | 6 |
7 |

What is `Disable control value` ?

8 | 9 | This is a setting that will only have an effect on Linux. Fan control can have several modes. LibreFanControl puts it in manual mode, but when disabling manual control, the user can choose the desired control mode. 10 | 11 | For example, here is the driver [site](https://www.kernel.org/doc/html/next/hwmon/nct6775.html) for my chip. 12 | ``` 13 | pwm[1-7]_enable 14 | this file controls mode of fan/temperature control: 15 | 0 Fan control disabled (fans set to maximum speed) 16 | 1 Manual mode, write to pwm[0-5] any value 0-255 17 | 2 "Thermal Cruise" mode 18 | 3 "Fan Speed Cruise" mode 19 | 4 "Smart Fan III" mode (NCT6775F only) 20 | 5 "Smart Fan IV" mode 21 | ``` 22 | 23 |
24 | 25 | 26 |
27 |

Error while upgrading on Windows

28 | 29 | Try to kill `LibreFanControlService` with the task manager and launch .msi file. Choose install or repair option. 30 | 31 |
32 | 33 |
34 |

How to install on Linux

35 | 36 | [See](./INSTALL.md). 37 | 38 |
39 | 40 |
41 |

How to build

42 | 43 | [See](./BUILD.md). 44 | 45 |
46 | 47 | 48 | -------------------------------------------------------------------------------- /HardwareLib/HardwareLib.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibreFanControlService", "LibreFanControlService\LibreFanControlService.csproj", "{550F7896-97F9-43FF-A613-01CAE1D9BB64}" 4 | EndProject 5 | Global 6 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 7 | Release Windows|Any CPU = Release Windows|Any CPU 8 | Debug Windows|Any CPU = Debug Windows|Any CPU 9 | Release Linux|Any CPU = Release Linux|Any CPU 10 | Debug Linux|Any CPU = Debug Linux|Any CPU 11 | EndGlobalSection 12 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 13 | {550F7896-97F9-43FF-A613-01CAE1D9BB64}.Release Windows|Any CPU.ActiveCfg = Release Windows|Any CPU 14 | {550F7896-97F9-43FF-A613-01CAE1D9BB64}.Release Windows|Any CPU.Build.0 = Release Windows|Any CPU 15 | {550F7896-97F9-43FF-A613-01CAE1D9BB64}.Debug Windows|Any CPU.ActiveCfg = Debug Windows|Any CPU 16 | {550F7896-97F9-43FF-A613-01CAE1D9BB64}.Debug Windows|Any CPU.Build.0 = Debug Windows|Any CPU 17 | {550F7896-97F9-43FF-A613-01CAE1D9BB64}.Release Linux|Any CPU.ActiveCfg = Release Linux|Any CPU 18 | {550F7896-97F9-43FF-A613-01CAE1D9BB64}.Release Linux|Any CPU.Build.0 = Release Linux|Any CPU 19 | {550F7896-97F9-43FF-A613-01CAE1D9BB64}.Debug Linux|Any CPU.ActiveCfg = Debug Linux|Any CPU 20 | {550F7896-97F9-43FF-A613-01CAE1D9BB64}.Debug Linux|Any CPU.Build.0 = Debug Linux|Any CPU 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /HardwareLib/LibreFanControlService/External/LmSensors/libsensors.so.5.0.0: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wiiznokes/LibreFanControl/54072efbc0d808acc2d63bba3ff0e827de81bae7/HardwareLib/LibreFanControlService/External/LmSensors/libsensors.so.5.0.0 -------------------------------------------------------------------------------- /HardwareLib/LibreFanControlService/Hardware/BaseHardware.cs: -------------------------------------------------------------------------------- 1 | namespace LibreFanControlService.Hardware; 2 | 3 | public abstract class BaseHardware 4 | { 5 | protected BaseHardware(string id, string name) 6 | { 7 | Id = id; 8 | Name = name; 9 | Value = 0; 10 | } 11 | 12 | public string Id { get; } 13 | public string Name { get; } 14 | public int Value { get; protected set; } 15 | 16 | public virtual void Update() 17 | { 18 | } 19 | } -------------------------------------------------------------------------------- /HardwareLib/LibreFanControlService/Hardware/Control/BaseControl.cs: -------------------------------------------------------------------------------- 1 | namespace LibreFanControlService.Hardware.Control; 2 | 3 | public class BaseControl : BaseHardware 4 | { 5 | protected BaseControl(string id, string name) : base(id, name) 6 | { 7 | IsSetSpeed = false; 8 | } 9 | 10 | protected bool IsSetSpeed { get; set; } 11 | 12 | public virtual int GetMinSpeed() 13 | { 14 | return 0; 15 | } 16 | 17 | public virtual int GetMaxSpeed() 18 | { 19 | return 100; 20 | } 21 | 22 | public virtual bool SetSpeed(int value) 23 | { 24 | return false; 25 | } 26 | 27 | public virtual bool SetAuto() 28 | { 29 | return false; 30 | } 31 | } -------------------------------------------------------------------------------- /HardwareLib/LibreFanControlService/Hardware/Control/LHMControl.cs: -------------------------------------------------------------------------------- 1 | using LibreHardwareMonitor.Hardware; 2 | 3 | namespace LibreFanControlService.Hardware.Control; 4 | 5 | public class LhmControl : BaseControl 6 | { 7 | // ISensor 8 | private readonly ISensor _mSensor; 9 | 10 | public LhmControl(string id, string name, ISensor sensor) : base(id, name) 11 | { 12 | _mSensor = sensor; 13 | } 14 | 15 | public override void Update() 16 | { 17 | double temp = 0.0f; 18 | if (_mSensor.Value != null) temp = (double)_mSensor.Value; 19 | temp = Math.Round(temp); 20 | Value = (int)temp; 21 | } 22 | 23 | public override int GetMinSpeed() 24 | { 25 | if (_mSensor.Control != null) return (int)_mSensor.Control.MinSoftwareValue; 26 | return 0; 27 | } 28 | 29 | public override int GetMaxSpeed() 30 | { 31 | if (_mSensor.Control != null) return (int)_mSensor.Control.MaxSoftwareValue; 32 | return 100; 33 | } 34 | 35 | public override bool SetSpeed(int value) 36 | { 37 | if (_mSensor.Control != null) 38 | { 39 | _mSensor.Control.SetSoftware(value); 40 | IsSetSpeed = true; 41 | } 42 | else 43 | { 44 | return false; 45 | } 46 | 47 | Value = value; 48 | 49 | Console.WriteLine("[SERVICE] set control: " + Name + " = " + value); 50 | return true; 51 | } 52 | 53 | public override bool SetAuto() 54 | { 55 | if (_mSensor.Control == null) return false; 56 | 57 | if (IsSetSpeed == false) 58 | return true; 59 | 60 | _mSensor.Control.SetDefault(); 61 | IsSetSpeed = false; 62 | Console.WriteLine("[SERVICE] set control to auto: " + Name); 63 | return true; 64 | } 65 | } -------------------------------------------------------------------------------- /HardwareLib/LibreFanControlService/Hardware/Control/LmControl.cs: -------------------------------------------------------------------------------- 1 | using LibreFanControlService.External.LmSensors; 2 | 3 | namespace LibreFanControlService.Hardware.Control; 4 | 5 | using static LmSensorsWrapper; 6 | 7 | public class LmControl : BaseControl 8 | { 9 | private readonly unsafe SensorsChipName* _chip; 10 | private readonly unsafe SensorsFeature* _feat; 11 | private readonly int _subFeatureEnableNumber; 12 | private readonly int _subFeatureIoNumber; 13 | 14 | public unsafe LmControl(string id, string name, SensorsChipName* chip, SensorsFeature* feat) : base(id, name) 15 | { 16 | _chip = chip; 17 | _feat = feat; 18 | 19 | _subFeatureIoNumber = LmSensors.get_sub_feature_number(_chip, _feat, 20 | SensorsSubFeatureType.SensorsSubFeaturePwmIo); 21 | 22 | _subFeatureEnableNumber = LmSensors.get_sub_feature_number(_chip, _feat, 23 | SensorsSubFeatureType.SensorsSubFeaturePwmEnable); 24 | } 25 | 26 | public override unsafe void Update() 27 | { 28 | double value; 29 | var res = sensors_get_value(_chip, _subFeatureIoNumber, &value); 30 | 31 | Value = res < 0 ? 0 : (int)(value / 255 * 100); 32 | } 33 | 34 | public override unsafe bool SetSpeed(int value) 35 | { 36 | int res; 37 | // need to enable manual control of pwn 38 | if (!IsSetSpeed) 39 | { 40 | res = sensors_set_value(_chip, _subFeatureEnableNumber, 1); 41 | if (res < 0) 42 | { 43 | Console.WriteLine("Error: can't enable control " + Name + ". Code: " + res); 44 | return false; 45 | } 46 | } 47 | 48 | var valueCalibrated = value * 2.55; 49 | res = sensors_set_value(_chip, _subFeatureIoNumber, valueCalibrated); 50 | if (res < 0) 51 | { 52 | Console.WriteLine("Error: set speed on control " + Name + ". Code: " + res); 53 | return false; 54 | } 55 | 56 | IsSetSpeed = true; 57 | Console.WriteLine("[SERVICE] set control: " + Name + " = " + value); 58 | return true; 59 | } 60 | 61 | 62 | public override unsafe bool SetAuto() 63 | { 64 | if (IsSetSpeed == false) 65 | return true; 66 | 67 | var res = sensors_set_value(_chip, _subFeatureEnableNumber, State.Settings.ValueDisableControl); 68 | if (res < 0) 69 | { 70 | Console.WriteLine("Error: can't disable control " + Name + ". Code: " + res); 71 | return false; 72 | } 73 | 74 | IsSetSpeed = false; 75 | Console.WriteLine("[SERVICE] set control to auto: " + Name); 76 | return true; 77 | } 78 | } -------------------------------------------------------------------------------- /HardwareLib/LibreFanControlService/Hardware/HardwareManager.cs: -------------------------------------------------------------------------------- 1 | namespace LibreFanControlService.Hardware; 2 | 3 | public static class HardwareManager 4 | { 5 | #if WINDOWS 6 | private static readonly Lhm Lhm = new(); 7 | #else 8 | private static readonly LmSensors LmSensors = new(); 9 | #endif 10 | 11 | 12 | public static void Start() 13 | { 14 | #if WINDOWS 15 | Lhm.Start(); 16 | Lhm.CreateHardware(); 17 | #else 18 | LmSensors.Start(); 19 | LmSensors.CreateHardware(); 20 | #endif 21 | } 22 | 23 | public static void Stop() 24 | { 25 | #if WINDOWS 26 | Lhm.Stop(); 27 | #else 28 | LmSensors.Stop(); 29 | #endif 30 | } 31 | 32 | 33 | public static void Update() 34 | { 35 | #if WINDOWS 36 | Lhm.Update(); 37 | #endif 38 | } 39 | } -------------------------------------------------------------------------------- /HardwareLib/LibreFanControlService/Hardware/Sensor/BaseSensor.cs: -------------------------------------------------------------------------------- 1 | namespace LibreFanControlService.Hardware.Sensor; 2 | 3 | public abstract class BaseSensor : BaseHardware 4 | { 5 | protected BaseSensor(string id, string name) : base(id, name) 6 | { 7 | } 8 | } -------------------------------------------------------------------------------- /HardwareLib/LibreFanControlService/Hardware/Sensor/LHMSensor.cs: -------------------------------------------------------------------------------- 1 | using LibreHardwareMonitor.Hardware; 2 | 3 | namespace LibreFanControlService.Hardware.Sensor; 4 | 5 | public class LhmSensor : BaseSensor 6 | { 7 | // ISensor 8 | private readonly ISensor _mSensor; 9 | 10 | public LhmSensor(string id, string name, ISensor sensor) : base(id, name) 11 | { 12 | _mSensor = sensor; 13 | } 14 | 15 | public override void Update() 16 | { 17 | Value = _mSensor.Value.HasValue ? (int)_mSensor.Value : 0; 18 | } 19 | } -------------------------------------------------------------------------------- /HardwareLib/LibreFanControlService/Hardware/Sensor/LmSensor.cs: -------------------------------------------------------------------------------- 1 | using LibreFanControlService.External.LmSensors; 2 | using static LibreFanControlService.External.LmSensors.LmSensorsWrapper; 3 | 4 | namespace LibreFanControlService.Hardware.Sensor; 5 | 6 | public class LmSensor : BaseSensor 7 | { 8 | private readonly unsafe SensorsChipName* _chip; 9 | private readonly unsafe SensorsFeature* _feat; 10 | private readonly int _subFeatureInputNumber; 11 | 12 | public unsafe LmSensor(string id, string name, SensorsChipName* chip, SensorsFeature* feat, SensorsFeatureType type) 13 | : base(id, name) 14 | { 15 | _chip = chip; 16 | _feat = feat; 17 | 18 | _subFeatureInputNumber = type switch 19 | { 20 | SensorsFeatureType.SensorsFeatureFan => LmSensors.get_sub_feature_number(_chip, _feat, 21 | SensorsSubFeatureType.SensorsSubFeatureFanInput), 22 | SensorsFeatureType.SensorsFeatureTemp => LmSensors.get_sub_feature_number(_chip, _feat, 23 | SensorsSubFeatureType.SensorsSubFeatureTempInput), 24 | _ => throw new ArgumentOutOfRangeException(nameof(type), type, null) 25 | }; 26 | 27 | if (_subFeatureInputNumber < 0) throw new Exception("No sub feature was found"); 28 | } 29 | 30 | 31 | public override unsafe void Update() 32 | { 33 | double value; 34 | var res = sensors_get_value(_chip, _subFeatureInputNumber, &value); 35 | 36 | if (res < 0) 37 | { 38 | Console.WriteLine("Error: Update sensor " + Name + ". Code: " + res); 39 | Value = 0; 40 | } 41 | else 42 | { 43 | Value = (int)value; 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /HardwareLib/LibreFanControlService/Item/Behavior/Behavior.cs: -------------------------------------------------------------------------------- 1 | using LibreFanControlService.Utils; 2 | 3 | namespace LibreFanControlService.Item.Behavior; 4 | 5 | public abstract class Behavior 6 | { 7 | protected Behavior(string id) 8 | { 9 | Id = id; 10 | } 11 | 12 | public string Id { get; } 13 | 14 | public bool IsValid { get; protected init; } 15 | public bool SetOneTime { get; protected init; } 16 | 17 | public abstract int GetSpeed(); 18 | } 19 | 20 | public abstract class BehaviorWithTemp : Behavior 21 | { 22 | private const string CustomTempPrefix = "iCustomTemp"; 23 | 24 | protected BehaviorWithTemp(string id, string? tempId) : base(id) 25 | { 26 | TempId = tempId; 27 | SetOneTime = false; 28 | 29 | IsValid = TempId != null; 30 | 31 | if (!IsValid) return; 32 | 33 | var parts = TempId!.Split('#'); 34 | IsCustomTemp = parts.Length > 0 && parts[0] == CustomTempPrefix; 35 | 36 | if (!SetTempIndex()) IsValid = false; 37 | } 38 | 39 | 40 | private string? TempId { get; } 41 | private bool IsCustomTemp { get; } 42 | 43 | 44 | // either hTemp or iCustomTemp 45 | private int TempIndex { get; set; } 46 | 47 | 48 | protected int GetSensorValue() 49 | { 50 | if (IsCustomTemp) return State.CustomTemps[TempIndex].GetValue(); 51 | 52 | State.HTemps[TempIndex].Update(); 53 | return State.HTemps[TempIndex].Value; 54 | } 55 | 56 | 57 | private bool SetTempIndex() 58 | { 59 | if (IsCustomTemp) 60 | foreach (var (iCustomTemp, index) in State.CustomTemps.WithIndex()) 61 | { 62 | if (iCustomTemp.Id != TempId) continue; 63 | 64 | 65 | if (!iCustomTemp.IsValid) return false; 66 | TempIndex = index; 67 | return true; 68 | } 69 | else 70 | foreach (var (hTemp, index) in State.HTemps.WithIndex()) 71 | { 72 | if (hTemp.Id != TempId) continue; 73 | 74 | TempIndex = index; 75 | return true; 76 | } 77 | 78 | Console.WriteLine("Error: behavior " + Id + ", none temp id was found"); 79 | return false; 80 | } 81 | } -------------------------------------------------------------------------------- /HardwareLib/LibreFanControlService/Item/Behavior/Flat.cs: -------------------------------------------------------------------------------- 1 | namespace LibreFanControlService.Item.Behavior; 2 | 3 | public class Flat : Behavior 4 | { 5 | public Flat(string id, int value) : base(id) 6 | { 7 | Value = value; 8 | IsValid = true; 9 | SetOneTime = true; 10 | } 11 | 12 | 13 | private int Value { get; } 14 | 15 | public override int GetSpeed() 16 | { 17 | return Value; 18 | } 19 | } -------------------------------------------------------------------------------- /HardwareLib/LibreFanControlService/Item/Behavior/Linear.cs: -------------------------------------------------------------------------------- 1 | namespace LibreFanControlService.Item.Behavior; 2 | 3 | public class Linear : BehaviorWithTemp 4 | { 5 | public Linear(string id, string? tempId, int minTemp, int maxTemp, int minFanSpeed, int maxFanSpeed) : base(id, 6 | tempId) 7 | { 8 | MinTemp = minTemp; 9 | MaxTemp = maxTemp; 10 | MinFanSpeed = minFanSpeed; 11 | MaxFanSpeed = maxFanSpeed; 12 | } 13 | 14 | private int MinTemp { get; } 15 | private int MaxTemp { get; } 16 | private int MinFanSpeed { get; } 17 | private int MaxFanSpeed { get; } 18 | 19 | public override int GetSpeed() 20 | { 21 | var tempValue = GetSensorValue(); 22 | 23 | if (tempValue <= MinTemp) return MinFanSpeed; 24 | 25 | if (tempValue >= MaxTemp) return MaxFanSpeed; 26 | 27 | var affine = GetAffine(); 28 | 29 | return (int)Math.Round(affine.A * tempValue + affine.B); 30 | } 31 | 32 | 33 | private Affine GetAffine() 34 | { 35 | var a = (MaxFanSpeed - MinFanSpeed) / (float)(MaxTemp - MinTemp); 36 | return new Affine( 37 | a, 38 | MinFanSpeed - a * MinTemp 39 | ); 40 | } 41 | 42 | private class Affine 43 | { 44 | public Affine(float a, float b) 45 | { 46 | A = a; 47 | B = b; 48 | } 49 | 50 | public float A { get; } 51 | public float B { get; } 52 | } 53 | } -------------------------------------------------------------------------------- /HardwareLib/LibreFanControlService/Item/Behavior/Target.cs: -------------------------------------------------------------------------------- 1 | namespace LibreFanControlService.Item.Behavior; 2 | 3 | public class Target : BehaviorWithTemp 4 | { 5 | private bool _idleHasBeenReached; 6 | 7 | public Target(string id, string? tempId, int idleTemp, int loadTemp, int idleFanSpeed, int loadFanSpeed) : base(id, 8 | tempId) 9 | { 10 | IdleTemp = idleTemp; 11 | LoadTemp = loadTemp; 12 | IdleFanSpeed = idleFanSpeed; 13 | LoadFanSpeed = loadFanSpeed; 14 | } 15 | 16 | 17 | private int IdleTemp { get; } 18 | private int LoadTemp { get; } 19 | private int IdleFanSpeed { get; } 20 | private int LoadFanSpeed { get; } 21 | 22 | public override int GetSpeed() 23 | { 24 | var tempValue = GetSensorValue(); 25 | 26 | if (_idleHasBeenReached) 27 | { 28 | if (tempValue < LoadTemp) return IdleFanSpeed; 29 | 30 | _idleHasBeenReached = false; 31 | return LoadFanSpeed; 32 | } 33 | 34 | if (tempValue > IdleTemp) return LoadFanSpeed; 35 | 36 | _idleHasBeenReached = true; 37 | return IdleFanSpeed; 38 | } 39 | } -------------------------------------------------------------------------------- /HardwareLib/LibreFanControlService/Item/Control.cs: -------------------------------------------------------------------------------- 1 | namespace LibreFanControlService.Item; 2 | 3 | public class Control 4 | { 5 | public Control(string? iBehaviorId, string? hControlId, bool isAuto, int index) 6 | { 7 | BehaviorId = iBehaviorId; 8 | HControlId = hControlId; 9 | IsAuto = isAuto; 10 | Index = index; 11 | IsValid = HControlId != null && !IsAuto && BehaviorId != null; 12 | 13 | if (!IsValid) return; 14 | 15 | if (!SetBehaviorIndex() || !SetHControlIndex()) IsValid = false; 16 | 17 | SetOneTime = State.Behaviors[BehaviorIndex].SetOneTime; 18 | } 19 | 20 | public int Index { get; } 21 | public bool SetOneTime { get; } 22 | 23 | private string? HControlId { get; } 24 | private bool IsAuto { get; } 25 | private string? BehaviorId { get; } 26 | 27 | public bool IsValid { get; } 28 | 29 | 30 | private int BehaviorIndex { get; set; } 31 | 32 | 33 | private int HControlIndex { get; set; } 34 | 35 | private bool SetHControlIndex() 36 | { 37 | var i = 0; 38 | foreach (var hControl in State.HControls) 39 | { 40 | if (hControl.Id == HControlId) 41 | { 42 | HControlIndex = i; 43 | return true; 44 | } 45 | 46 | i++; 47 | } 48 | 49 | return false; 50 | } 51 | 52 | 53 | private bool SetBehaviorIndex() 54 | { 55 | var i = 0; 56 | foreach (var behavior in State.Behaviors) 57 | { 58 | if (behavior.Id == BehaviorId) 59 | { 60 | if (!behavior.IsValid) return false; 61 | 62 | BehaviorIndex = i; 63 | return true; 64 | } 65 | 66 | i++; 67 | } 68 | 69 | return false; 70 | } 71 | 72 | 73 | public void SetSpeed() 74 | { 75 | var value = State.Behaviors[BehaviorIndex].GetSpeed(); 76 | State.HControls[HControlIndex].SetSpeed(value); 77 | } 78 | } -------------------------------------------------------------------------------- /HardwareLib/LibreFanControlService/Item/CustomTemp.cs: -------------------------------------------------------------------------------- 1 | using LibreFanControlService.Utils; 2 | 3 | namespace LibreFanControlService.Item; 4 | 5 | public class CustomTemp 6 | { 7 | public enum CustomTempType 8 | { 9 | Average, 10 | Max, 11 | Min 12 | } 13 | 14 | public CustomTemp(string id, List hTempIds, CustomTempType type) 15 | { 16 | Id = id; 17 | HTempIds = hTempIds; 18 | Type = type; 19 | HTempsIndexList = new List(); 20 | 21 | IsValid = HTempIds.Count > 0; 22 | 23 | if (!IsValid) return; 24 | 25 | if (!SetHTempsIndexList()) IsValid = false; 26 | } 27 | 28 | public string Id { get; } 29 | private List HTempIds { get; } 30 | private CustomTempType Type { get; } 31 | 32 | 33 | public bool IsValid { get; } 34 | 35 | private List HTempsIndexList { get; } 36 | 37 | private bool SetHTempsIndexList() 38 | { 39 | foreach (var hTempId in HTempIds) 40 | foreach (var (hTemp, index) in State.HTemps.WithIndex()) 41 | { 42 | if (hTemp.Id != hTempId) continue; 43 | 44 | HTempsIndexList.Add(index); 45 | break; 46 | } 47 | 48 | if (HTempIds.Count == HTempsIndexList.Count) return true; 49 | 50 | 51 | Console.WriteLine("Error: set index for custom temp " + Id + ", not all index was found"); 52 | return false; 53 | } 54 | 55 | public int GetValue() 56 | { 57 | return Type switch 58 | { 59 | CustomTempType.Average => Convert.ToInt32(GetTempValues().Average()), 60 | CustomTempType.Max => GetTempValues().Max(), 61 | CustomTempType.Min => GetTempValues().Min(), 62 | _ => throw new Exception("unknown custom temp type") 63 | }; 64 | } 65 | 66 | private IEnumerable GetTempValues() 67 | { 68 | List values = new(); 69 | foreach (var index in HTempsIndexList) 70 | { 71 | var hTemp = State.HTemps[index]; 72 | hTemp.Update(); 73 | values.Add(hTemp.Value); 74 | } 75 | 76 | return values; 77 | } 78 | } -------------------------------------------------------------------------------- /HardwareLib/LibreFanControlService/Program.cs: -------------------------------------------------------------------------------- 1 | using LibreFanControlService; 2 | using Microsoft.Extensions.Hosting.Internal; 3 | 4 | using var host = Host.CreateDefaultBuilder(args) 5 | .UseWindowsService(config => { config.ServiceName = "LibreFanControlService"; }) 6 | .ConfigureServices((_, services) => 7 | { 8 | services.AddSingleton(); 9 | services.AddHostedService(); 10 | }) 11 | .Build(); 12 | 13 | await host.RunAsync(); -------------------------------------------------------------------------------- /HardwareLib/LibreFanControlService/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "LibreFanControlService": { 4 | "commandName": "Project", 5 | "dotnetRunMessages": true, 6 | "environmentVariables": { 7 | "DOTNET_ENVIRONMENT": "Development" 8 | } 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /HardwareLib/LibreFanControlService/Proto/ConfHelper.cs: -------------------------------------------------------------------------------- 1 | using LibreFanControlService.Item; 2 | using LibreFanControlService.Item.Behavior; 3 | using LibreFanControlService.Utils; 4 | using Proto.Generated.PConf; 5 | 6 | namespace LibreFanControlService.Proto; 7 | 8 | public static class ConfHelper 9 | { 10 | public static void LoadConfFile(string confId) 11 | { 12 | var pConf = PConf.Parser.ParseFrom(GetConfBytes(confId)); 13 | ParsePConf(pConf); 14 | } 15 | 16 | private static byte[] GetConfBytes(string confId) 17 | { 18 | return File.ReadAllBytes(SettingsDir.GetConfFile(confId)); 19 | } 20 | 21 | 22 | // the order is important here (for initialization) 23 | private static void ParsePConf(PConf pConf) 24 | { 25 | State.Controls.Clear(); 26 | State.Behaviors.Clear(); 27 | State.CustomTemps.Clear(); 28 | 29 | 30 | foreach (var pITemp in pConf.PITemps) 31 | if (pITemp.KindCase == PITemp.KindOneofCase.PICustomTemp) 32 | State.CustomTemps.Add( 33 | new CustomTemp( 34 | pITemp.PId, 35 | pITemp.PICustomTemp.PHTempIds.ToList(), 36 | pITemp.PICustomTemp.PType switch 37 | { 38 | PCustomTempTypes.Average => CustomTemp.CustomTempType.Average, 39 | PCustomTempTypes.Max => CustomTemp.CustomTempType.Max, 40 | PCustomTempTypes.Min => CustomTemp.CustomTempType.Min, 41 | _ => throw new ProtoException("unknown custom temp type") 42 | } 43 | ) 44 | ); 45 | 46 | foreach (var pIBehavior in pConf.PIBehaviors) 47 | State.Behaviors.Add( 48 | pIBehavior.KindCase switch 49 | { 50 | PIBehavior.KindOneofCase.PFlat => new Flat(pIBehavior.PId, pIBehavior.PFlat.PValue), 51 | 52 | PIBehavior.KindOneofCase.PLinear => new Linear( 53 | pIBehavior.PId, 54 | SettingsHelper.NullableToNull(pIBehavior.PLinear.PTempId), 55 | pIBehavior.PLinear.PMinTemp, 56 | pIBehavior.PLinear.PMaxTemp, 57 | pIBehavior.PLinear.PMinFanSpeed, 58 | pIBehavior.PLinear.PMaxFanSpeed 59 | ), 60 | 61 | PIBehavior.KindOneofCase.PTarget => new Target( 62 | pIBehavior.PId, 63 | SettingsHelper.NullableToNull(pIBehavior.PTarget.PTempId), 64 | pIBehavior.PTarget.PIdleTemp, 65 | pIBehavior.PTarget.PLoadTemp, 66 | pIBehavior.PTarget.PIdleFanSpeed, 67 | pIBehavior.PTarget.PLoadFanSpeed 68 | ), 69 | 70 | _ => throw new ProtoException("behavior unknown kind") 71 | } 72 | ); 73 | 74 | 75 | foreach (var (pIControl, index) in pConf.PIControls.WithIndex()) 76 | State.Controls.Add( 77 | new Control( 78 | SettingsHelper.NullableToNull(pIControl.PIBehaviorId), 79 | SettingsHelper.NullableToNull(pIControl.PHControlId), 80 | pIControl.PIsAuto, 81 | index 82 | )); 83 | } 84 | } -------------------------------------------------------------------------------- /HardwareLib/LibreFanControlService/Proto/SettingsHelper.cs: -------------------------------------------------------------------------------- 1 | using Proto.Generated.PSettings; 2 | 3 | namespace LibreFanControlService.Proto; 4 | 5 | public class ProtoException : Exception 6 | { 7 | public ProtoException(string msg) : base(msg) 8 | { 9 | } 10 | } 11 | 12 | public static class SettingsDir 13 | { 14 | #if WINDOWS 15 | private const string Dir = "C:\\ProgramData\\LibreFanControl"; 16 | #else 17 | private const string Dir = "/etc/LibreFanControl"; 18 | #endif 19 | 20 | private static readonly string ConfDir = Path.Combine( 21 | Dir, 22 | "conf" 23 | ); 24 | 25 | public static readonly string SettingsFile = Path.Combine( 26 | Dir, 27 | "settings" 28 | ); 29 | 30 | 31 | public static string GetConfFile(string confId) 32 | { 33 | return Path.Combine( 34 | ConfDir, 35 | confId 36 | ); 37 | } 38 | 39 | public static bool CheckFiles() 40 | { 41 | return File.Exists(SettingsFile); 42 | } 43 | } 44 | 45 | public static class SettingsHelper 46 | { 47 | public static void LoadSettingsFile() 48 | { 49 | var pSettings = PSettings.Parser.ParseFrom(GetSettingsBytes()); 50 | ParsePSettings(pSettings); 51 | } 52 | 53 | private static byte[] GetSettingsBytes() 54 | { 55 | return File.ReadAllBytes(SettingsDir.SettingsFile); 56 | } 57 | 58 | 59 | private static void ParsePSettings(PSettings pSetting) 60 | { 61 | State.Settings.UpdateDelay = pSetting.PUpdateDelay; 62 | State.Settings.ConfId = NullableToNull(pSetting.PConfId); 63 | State.Settings.ValueDisableControl = pSetting.PValueDisableControl; 64 | } 65 | 66 | 67 | public static string? NullableToNull(NullableId nullableId) 68 | { 69 | return nullableId.KindCase switch 70 | { 71 | NullableId.KindOneofCase.PId => nullableId.PId, 72 | NullableId.KindOneofCase.Null => null, 73 | _ => throw new ProtoException("Nullable id not set") 74 | }; 75 | } 76 | } -------------------------------------------------------------------------------- /HardwareLib/LibreFanControlService/State.cs: -------------------------------------------------------------------------------- 1 | using LibreFanControlService.Hardware.Control; 2 | using LibreFanControlService.Hardware.Sensor; 3 | using LibreFanControlService.Item; 4 | using LibreFanControlService.Item.Behavior; 5 | 6 | namespace LibreFanControlService; 7 | 8 | public static class State 9 | { 10 | public static readonly List HControls = new(); 11 | public static readonly List HTemps = new(); 12 | public static readonly List HFans = new(); 13 | 14 | public static readonly List Controls = new(); 15 | public static readonly List Behaviors = new(); 16 | public static readonly List CustomTemps = new(); 17 | 18 | public static readonly List UpdateList = new(); 19 | 20 | 21 | public static class Settings 22 | { 23 | public static string? ConfId { get; set; } 24 | public static int UpdateDelay { get; set; } 25 | 26 | public static int ValueDisableControl { get; set; } 27 | } 28 | 29 | 30 | public static class ServiceState 31 | { 32 | private static bool _isOpen; 33 | private static bool _confHasChange; 34 | private static bool _settingsHasChange; 35 | private static readonly object LockObject = new(); 36 | 37 | public static bool IsOpen 38 | { 39 | get 40 | { 41 | lock (LockObject) 42 | { 43 | return _isOpen; 44 | } 45 | } 46 | set 47 | { 48 | lock (LockObject) 49 | { 50 | _isOpen = value; 51 | } 52 | } 53 | } 54 | 55 | public static bool SettingsAndConfHasChange 56 | { 57 | get 58 | { 59 | lock (LockObject) 60 | { 61 | return _confHasChange; 62 | } 63 | } 64 | set 65 | { 66 | lock (LockObject) 67 | { 68 | _confHasChange = value; 69 | } 70 | } 71 | } 72 | 73 | public static bool SettingsHasChange 74 | { 75 | get 76 | { 77 | lock (LockObject) 78 | { 79 | return _settingsHasChange; 80 | } 81 | } 82 | set 83 | { 84 | lock (LockObject) 85 | { 86 | _settingsHasChange = value; 87 | } 88 | } 89 | } 90 | } 91 | } -------------------------------------------------------------------------------- /HardwareLib/LibreFanControlService/Update.cs: -------------------------------------------------------------------------------- 1 | namespace LibreFanControlService; 2 | 3 | public static class Update 4 | { 5 | public static void CreateUpdateList() 6 | { 7 | State.UpdateList.Clear(); 8 | 9 | foreach (var iControl in State.Controls) 10 | { 11 | if (!iControl.IsValid) 12 | continue; 13 | 14 | Console.WriteLine("control valid: " + iControl.Index); 15 | 16 | if (iControl.SetOneTime) 17 | { 18 | Console.WriteLine("set one time"); 19 | iControl.SetSpeed(); 20 | } 21 | else 22 | { 23 | State.UpdateList.Add(iControl.Index); 24 | } 25 | } 26 | } 27 | 28 | 29 | public static void UpdateUpdateList() 30 | { 31 | foreach (var index in State.UpdateList) State.Controls[index].SetSpeed(); 32 | } 33 | 34 | public static void UpdateAllSensors() 35 | { 36 | foreach (var control in State.HControls) control.Update(); 37 | foreach (var temp in State.HTemps) temp.Update(); 38 | foreach (var fan in State.HFans) fan.Update(); 39 | } 40 | 41 | public static void SetAutoAll() 42 | { 43 | foreach (var control in State.HControls) control.SetAuto(); 44 | } 45 | } -------------------------------------------------------------------------------- /HardwareLib/LibreFanControlService/Utils/Utils.cs: -------------------------------------------------------------------------------- 1 | namespace LibreFanControlService.Utils; 2 | 3 | public static class Utils 4 | { 5 | public static IEnumerable<(T item, int index)> WithIndex(this IEnumerable source) 6 | { 7 | return source.Select((item, index) => (item, index)); 8 | } 9 | } -------------------------------------------------------------------------------- /HardwareLib/LibreFanControlService/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.Hosting.Lifetime": "Information" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /HardwareLib/LibreFanControlService/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.Hosting.Lifetime": "Information" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /HardwareLib/global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "version": "7.0.0", 4 | "rollForward": "latestMinor", 5 | "allowPrerelease": false 6 | } 7 | } -------------------------------------------------------------------------------- /INSTALL.md: -------------------------------------------------------------------------------- 1 | # Linux Installation 2 | 3 |
4 |

Translation

5 | 6 | - [简体中文](./assets/zh_CN/INSTALL_zh-CN.md) 7 | 8 |
9 | 10 | 11 | ### Requirements 12 | Use the right package manager to install command for **your distrubution**: 13 | - Debian/Ubuntu : `sudo apt install ` 14 | - Arch Linux : `sudo pacman -S ` 15 | - Centos/Redhat/Fedora : `sudo dnf install ` 16 | 17 | Replace `(install command)` below to your command above. 18 | 19 | 20 | ### 1. Extract archive in the directory you want to place LibreFanControl, ex: `/opt` 21 | ``` 22 | sudo tar -xzf LibreFanControl.tar.gz -C /opt 23 | ``` 24 | Note: If you had already installed the app before, make sure to remove the directory before to avoid problem: 25 | ``` 26 | sudo rm -rf /opt/LibreFanControl 27 | ``` 28 | ### 2. Install dependencies 29 | ``` 30 | (install command) aspnetcore-runtime-7.0 31 | ``` 32 | 33 | ### 3. (optional) install `lm_sensors` and execute `sensors-detect`. 34 | ``` 35 | (install command) lm_sensors 36 | ``` 37 | ``` 38 | sudo sensors-detect 39 | ``` 40 | This will generate a config file to tell LibreFanControl app how to talk to sensors. 41 | It will also automatically found modules (drivers) you need to load. 42 | Personally, I answer YES to all the questions and I have no problem. 43 | 44 | Notes: 45 | - The uppercase answer is the default one. 46 | - After this step on my PC, I go from 3 to 25 sensors detected. 47 | 48 | ### 4. You all good, you can execute the app that in the place you selected first. 49 | ``` 50 | sudo /opt/LibreFanControl/bin/LibreFanControl 51 | ``` 52 | -------------------------------------------------------------------------------- /LibreFanControl/app/drawable/app_icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wiiznokes/LibreFanControl/54072efbc0d808acc2d63bba3ff0e827de81bae7/LibreFanControl/app/drawable/app_icon.ico -------------------------------------------------------------------------------- /LibreFanControl/app/drawable/app_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wiiznokes/LibreFanControl/54072efbc0d808acc2d63bba3ff0e827de81bae7/LibreFanControl/app/drawable/app_icon.png -------------------------------------------------------------------------------- /LibreFanControl/app/drawable/conf/conf#1: -------------------------------------------------------------------------------- 1 | 2 |  3 | conf#1 confTest15 4 | control lib1 5 | iControl#1" iFlat#12 controlLib#1) 6 | control lib2 7 | iControl#2" iFlat#127 8 | control lib3 9 | iControl#3"  iLinear#12 controlLib#37 10 | control lib4 11 | iControl#4"  iTarget#12 controlLib#45 12 | 13 | Control #1 14 | iControl#5"  iTarget#32 controlLib#2" 15 | Flat #1iFlat#1"E"3 16 | Linear #1 iLinear#1* 17 |  iCustomTemp#1< 18 | (d"/ 19 | Target #1 iTarget#12 20 |  tempLib#1(< 2(d"3 21 | Target #2 iTarget#22 22 |  iCustomTemp#2(< 2(d"& 23 | Target #3 iTarget#32 24 | (< 2(d*# 25 | temp lib1iTemp#1" 26 |  tempLib#1*# 27 | temp lib2iTemp#2" 28 |  tempLib#2*# 29 | temp lib3iTemp#3" 30 |  tempLib#3*# 31 | temp lib4iTemp#4" 32 |  tempLib#4* 33 | Temp #1iTemp#5" 34 | *O 35 | Custom Temp #1 iCustomTemp#1*, tempLib#1 tempLib#3 tempLib#4 tempLib#2*; 36 | Custom Temp #2 iCustomTemp#2* tempLib#1 tempLib#32 37 | fan lib1iFan#1" 38 | fanLib#12 39 | fan lib2iFan#2" 40 | fanLib#22 41 | fan lib3iFan#3" 42 | fanLib#32 43 | fan lib4iFan#4" 44 | fanLib#42 45 | Fan #1iFan#5" -------------------------------------------------------------------------------- /LibreFanControl/app/drawable/conf/settings: -------------------------------------------------------------------------------- 1 | conf#1 2 | conf#1 confTest1 (0@ -------------------------------------------------------------------------------- /LibreFanControl/app/include/linux/scripts/change_start_mode.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | 4 | # option: 5 | # Manual 6 | # Automatic 7 | 8 | 9 | 10 | scriptRoot="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )/" 11 | . "$scriptRoot"/Manager.sh 12 | 13 | 14 | if [ "$#" -eq 0 ]; then 15 | startMode="Manual" 16 | elif [ "$#" -eq 1 ] && [[ "$1" = "Manual" || "$1" = "Automatic" ]]; then 17 | startMode="$1" 18 | else 19 | echo "Error: need one argument, Manual or Automatic" 20 | exit $defaultErrorCode 21 | fi 22 | 23 | 24 | echo "begin change mode: $startMode" 25 | 26 | 27 | if ! checkAdmin; then 28 | exit "$needAdminErrorCode" 29 | fi 30 | 31 | 32 | setServiceMode "$startMode" 33 | 34 | exit $? -------------------------------------------------------------------------------- /LibreFanControl/app/include/linux/scripts/install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | scriptRoot="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )/" 4 | . "$scriptRoot"/Manager.sh 5 | 6 | 7 | 8 | 9 | 10 | 11 | if ! checkAdmin; then 12 | exit $needAdminErrorCode 13 | fi 14 | 15 | 16 | if checkInstall; then 17 | exit $installedErrorCode 18 | fi 19 | 20 | 21 | copyServiceFiles 22 | 23 | -------------------------------------------------------------------------------- /LibreFanControl/app/include/linux/scripts/libre-fan-control.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Fan Control Daemon 3 | 4 | [Service] 5 | Type=simple 6 | 7 | User=root 8 | ExecStart=/usr/local/bin/LibreFanControlService/LibreFanControlService 9 | 10 | 11 | 12 | # Configures the time to wait before service is stopped forcefully. 13 | TimeoutStopSec=300 14 | 15 | RemainAfterExit=no 16 | 17 | [Install] 18 | WantedBy=multi-user.target -------------------------------------------------------------------------------- /LibreFanControl/app/include/linux/scripts/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | scriptRoot="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )/" 4 | . "$scriptRoot"/Manager.sh 5 | 6 | 7 | 8 | 9 | if ! checkInstall; then 10 | exit $notInstalledErrorCode 11 | fi 12 | 13 | 14 | if checkRunning; then 15 | exit 0 16 | fi 17 | 18 | if ! checkAdmin; then 19 | exit $needAdminErrorCode 20 | fi 21 | 22 | 23 | systemctl daemon-reload 24 | 25 | startService 26 | 27 | exit 0 -------------------------------------------------------------------------------- /LibreFanControl/app/include/linux/scripts/uninstall.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # option: 4 | # -c remove conf folder 5 | 6 | 7 | scriptRoot="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )/" 8 | . "$scriptRoot"/Manager.sh 9 | 10 | 11 | if [ "$#" -gt 1 ]; then 12 | echo "need 0 or 1 argument (-c for remove conf folder)" 13 | exit "$defaultErrorCode" 14 | fi 15 | 16 | if ! checkAdmin; then 17 | exit "$needAdminErrorCode" 18 | fi 19 | 20 | if [ "$#" -eq 1 ]; then 21 | if [ "$1" = "-c" ]; then 22 | removeConfDir 23 | else 24 | echo "need 0 or 1 argument (-c for remove conf folder)" 25 | exit "$defaultErrorCode" 26 | fi 27 | fi 28 | 29 | 30 | 31 | 32 | if checkRunning; then 33 | systemctl stop $systemdFileName 34 | echo "service stopped" 35 | fi 36 | 37 | 38 | removeServiceFiles 39 | 40 | systemctl daemon-reload -------------------------------------------------------------------------------- /LibreFanControl/app/include/windows/scripts/change_start_mode.ps1: -------------------------------------------------------------------------------- 1 | . $PSScriptRoot/Manager.ps1 2 | 3 | # option: 4 | # Manual 5 | # Automatic 6 | 7 | if ($args.Count -eq 0) 8 | { 9 | $startMode = "Manual" 10 | } 11 | elseif ($args.Count -eq 1 -and ($args[0] -eq "Manual" -or $args[0] -eq "Automatic")) 12 | { 13 | $startMode = $args[0] 14 | } 15 | else 16 | { 17 | Write-Output "Error: need one argument, Manual or Automatic" 18 | exit $defaultErrorCode 19 | } 20 | 21 | 22 | 23 | Write-Output "begin change start mode to $startMode" 24 | 25 | 26 | 27 | if (!(checkAdmin)) 28 | { 29 | exit $needAdminErrorCode 30 | } 31 | 32 | $res = checkExeDirInstall 33 | if ($res -ne 0) 34 | { 35 | exit $notInstalledErrorCode 36 | } 37 | 38 | 39 | $res = checkServiceInstall 40 | if ($res -ne 0) 41 | { 42 | exit $notInstalledErrorCode 43 | } 44 | 45 | 46 | 47 | 48 | Set-Service -Name $serviceName -StartupType $startMode -------------------------------------------------------------------------------- /LibreFanControl/app/include/windows/scripts/install.ps1: -------------------------------------------------------------------------------- 1 | . $PSScriptRoot/Manager.ps1 2 | 3 | 4 | 5 | 6 | 7 | 8 | if (!(checkAdmin)) 9 | { 10 | exit $needAdminErrorCode 11 | } 12 | 13 | $res = checkExeDirInstall 14 | if ($res -eq 0) 15 | { 16 | exit $installedErrorCode 17 | } 18 | 19 | 20 | $res = checkServiceInstall 21 | if ($res -eq 0) 22 | { 23 | exit $installedErrorCode 24 | } 25 | 26 | copyServiceFiles 27 | 28 | createService -------------------------------------------------------------------------------- /LibreFanControl/app/include/windows/scripts/start.ps1: -------------------------------------------------------------------------------- 1 | . $PSScriptRoot/Manager.ps1 2 | 3 | 4 | 5 | 6 | 7 | $res = checkExeDirInstall 8 | if ($res -ne 0) 9 | { 10 | exit $notInstalledErrorCode 11 | } 12 | 13 | $res = checkServiceInstall 14 | if ($res -ne 0) 15 | { 16 | exit $notInstalledErrorCode 17 | } 18 | 19 | 20 | if (checkRunning) 21 | { 22 | exit 0 23 | } 24 | 25 | 26 | if (!(checkAdmin)) 27 | { 28 | exit $needAdminErrorCode 29 | } 30 | 31 | $res = startService 32 | 33 | exit $res -------------------------------------------------------------------------------- /LibreFanControl/app/include/windows/scripts/uninstall.ps1: -------------------------------------------------------------------------------- 1 | . $PSScriptRoot/Manager.ps1 2 | 3 | # option: 4 | # -c remove conf folder 5 | 6 | 7 | 8 | 9 | 10 | if ($args.Count -gt 1) 11 | { 12 | Write-Output "Error: To many arguments. need optional one argument, -c or -conf" 13 | exit $defaultErrorCode 14 | } 15 | 16 | if (!(checkAdmin)) 17 | { 18 | exit $needAdminErrorCode 19 | } 20 | 21 | if ($args.Count -eq 1) 22 | { 23 | if ($args[0] -ne "-c") 24 | { 25 | Write-Output "need 0 or 1 argument (-c for remove conf folder)" 26 | exit $defaultErrorCode 27 | } 28 | 29 | removeConfFolder 30 | } 31 | 32 | if (checkRunning) 33 | { 34 | stopService 35 | } 36 | 37 | deleteService 38 | 39 | removeInstallFolder -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/kotlin/FState.kt: -------------------------------------------------------------------------------- 1 | import androidx.compose.runtime.MutableState 2 | import androidx.compose.runtime.mutableStateListOf 3 | import androidx.compose.runtime.mutableStateOf 4 | import androidx.compose.runtime.snapshots.SnapshotStateList 5 | import model.hardware.HControl 6 | import model.hardware.HFan 7 | import model.hardware.HTemp 8 | import model.item.BaseIBehavior 9 | import model.item.BaseITemp 10 | import model.item.IControl 11 | import model.item.IFan 12 | 13 | 14 | class ServiceState { 15 | 16 | enum class Status { 17 | WAIT_OPEN, 18 | OPEN, 19 | ERROR 20 | } 21 | 22 | enum class ServiceState { 23 | WAIT_OPEN, 24 | OPEN, 25 | ERROR 26 | } 27 | 28 | val status = mutableStateOf(Status.WAIT_OPEN) 29 | 30 | fun setErrorStatus() { 31 | status.value = Status.ERROR 32 | FState.settings.confId.value = null 33 | } 34 | } 35 | 36 | 37 | object FState { 38 | 39 | val hControls: SnapshotStateList = mutableStateListOf() 40 | val hTemps: SnapshotStateList = mutableStateListOf() 41 | val hFans: SnapshotStateList = mutableStateListOf() 42 | 43 | val iControls: SnapshotStateList = mutableStateListOf() 44 | val iBehaviors: SnapshotStateList = mutableStateListOf() 45 | val iTemps: SnapshotStateList = mutableStateListOf() 46 | val iFans: SnapshotStateList = mutableStateListOf() 47 | 48 | var settings: Settings = Settings() 49 | 50 | val ui = UiState() 51 | 52 | var service = ServiceState() 53 | 54 | var appVersion = "" 55 | } 56 | 57 | 58 | class UiState { 59 | val addItemExpanded = mutableStateOf(false) 60 | val editModeActivated = mutableStateOf(false) 61 | 62 | fun showError(error: CustomError, copy: Boolean = true) { 63 | 64 | var finalError = error 65 | if (copy) { 66 | if (error.copyContent == null) { 67 | finalError = error.copy( 68 | copyContent = error.content 69 | ) 70 | } 71 | } 72 | 73 | errorDialog.value = finalError 74 | dialogExpanded.value = Dialog.SHOW_ERROR 75 | } 76 | 77 | fun closeShowError() { 78 | errorDialog.value = null 79 | dialogExpanded.value = Dialog.NONE 80 | } 81 | 82 | enum class Dialog { 83 | NONE, 84 | NEW_CONF, 85 | SHOW_ERROR, 86 | LAUNCH_AT_START_UP, 87 | CONF_IS_NOT_SAVE 88 | } 89 | 90 | val dialogExpanded = mutableStateOf(Dialog.NONE) 91 | 92 | val errorDialog: MutableState = mutableStateOf(null) 93 | 94 | 95 | data class CustomError( 96 | val content: String, 97 | val copyContent: String? = null 98 | ) 99 | } 100 | 101 | 102 | -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/kotlin/Main.kt: -------------------------------------------------------------------------------- 1 | import androidx.compose.runtime.mutableStateOf 2 | import androidx.compose.ui.window.Window 3 | import androidx.compose.ui.window.application 4 | import kotlinx.coroutines.CoroutineScope 5 | import kotlinx.coroutines.Dispatchers 6 | import kotlinx.coroutines.delay 7 | import kotlinx.coroutines.launch 8 | import proto.ConfHelper 9 | import proto.SettingsHelper 10 | import ui.container.home 11 | import ui.theme.fanControlTheme 12 | import utils.* 13 | 14 | var DEBUG = false 15 | val app: Application = Application() 16 | 17 | // to run in debug mode (not install the service, no need to admin) 18 | // you need to set env variable: DEBUG=1 19 | fun main(args: Array) { 20 | 21 | val debug = System.getenv("DEBUG") ?: null 22 | if (debug != null) { 23 | println("DEBUG MODE ACTIVATED") 24 | DEBUG = true 25 | } 26 | 27 | if (getOS() == OS.linux) { 28 | // need admin to write in /etc dir 29 | if (!OsSpecific.os.isAdmin()) { 30 | if (!DEBUG) 31 | throw Exception("This app need admin privilege. Please retry with sudo.") 32 | } 33 | } 34 | 35 | // configured from gradle 36 | FState.appVersion = args[0] 37 | 38 | val visible = mutableStateOf(true) 39 | 40 | app.onStart() 41 | 42 | application( 43 | exitProcessOnExit = true 44 | ) { 45 | Window( 46 | visible = visible.value, 47 | title = Resources.getString("title/app_name"), 48 | icon = Resources.getIcon("app/toys_fan48"), 49 | onCloseRequest = { 50 | CoroutineScope(Dispatchers.Default).launch { 51 | 52 | if (FState.settings.firstStart.value) { 53 | FState.settings.firstStart.value = false 54 | SettingsHelper.writeSettings() 55 | if (!FState.settings.launchAtStartUp.value) 56 | FState.ui.dialogExpanded.value = UiState.Dialog.LAUNCH_AT_START_UP 57 | } 58 | 59 | while (FState.ui.dialogExpanded.value != UiState.Dialog.NONE) { 60 | delay(200L) 61 | } 62 | 63 | 64 | 65 | if (!ConfHelper.isConfSave(FState.settings.confId.value)) { 66 | FState.ui.dialogExpanded.value = UiState.Dialog.CONF_IS_NOT_SAVE 67 | } 68 | 69 | while (FState.ui.dialogExpanded.value != UiState.Dialog.NONE) { 70 | delay(200L) 71 | } 72 | visible.value = false 73 | 74 | app.onStop() 75 | exitApplication() 76 | } 77 | } 78 | ) { 79 | fanControlTheme( 80 | FState.settings.theme.value 81 | ) { 82 | home() 83 | initDialogs() 84 | } 85 | } 86 | } 87 | } -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/kotlin/Settings.kt: -------------------------------------------------------------------------------- 1 | import androidx.compose.runtime.MutableState 2 | import androidx.compose.runtime.mutableStateOf 3 | import androidx.compose.runtime.toMutableStateList 4 | 5 | /* 6 | He may have problems as we add settings, because protobuf does not allow default value, 7 | so some may be aberrant. 8 | 9 | For annoying cases, it is possible to check if the value is out of range 10 | */ 11 | class Settings( 12 | language: Languages = Languages.en, 13 | confId: String? = null, 14 | confInfoList: List = listOf(), 15 | updateDelay: Int = 2, 16 | theme: Themes = Themes.system, 17 | firstStart: Boolean = true, 18 | launchAtStartUp: Boolean = false, 19 | degree: Boolean = true, 20 | valueDisableControl: Int = 2, 21 | versionInstalled: String = "0.0.0" 22 | ) { 23 | val language: MutableState = mutableStateOf(language) 24 | val confId: MutableState = mutableStateOf(confId) 25 | val confInfoList = confInfoList.toMutableStateList() 26 | val updateDelay: MutableState = mutableStateOf(updateDelay) 27 | val theme: MutableState = mutableStateOf(theme) 28 | val firstStart = mutableStateOf(firstStart) 29 | val launchAtStartUp = mutableStateOf(launchAtStartUp) 30 | val degree = mutableStateOf(degree) 31 | val valueDisableControl = mutableStateOf(valueDisableControl) 32 | val versionInstalled = mutableStateOf(versionInstalled) 33 | 34 | fun getIndexInfo(_confId: String? = confId.value) = when (_confId) { 35 | null -> null 36 | else -> confInfoList.indexOfFirst { 37 | it.id == confId.value 38 | } 39 | } 40 | 41 | 42 | fun getConfName(_confId: String? = confId.value): String? { 43 | return when (val index = getIndexInfo(_confId)) { 44 | null -> return null 45 | else -> confInfoList[index].name.value 46 | } 47 | } 48 | } 49 | 50 | 51 | class ConfInfo( 52 | val id: String, 53 | name: String, 54 | ) { 55 | var name = mutableStateOf(name) 56 | } 57 | 58 | 59 | // name need to respect the format in 60 | // json string file (for now) 61 | 62 | @Suppress("EnumEntryName") 63 | enum class Languages { 64 | en, 65 | fr, 66 | zh_cn 67 | } 68 | 69 | @Suppress("EnumEntryName") 70 | enum class Themes { 71 | system, 72 | light, 73 | dark 74 | } -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/kotlin/model/hardware/BaseH.kt: -------------------------------------------------------------------------------- 1 | package model.hardware 2 | 3 | import androidx.compose.runtime.MutableState 4 | import androidx.compose.runtime.mutableStateOf 5 | 6 | interface BaseH { 7 | val name: String 8 | val value: MutableState 9 | val id: String 10 | } 11 | 12 | class HControl( 13 | override val name: String, 14 | override val id: String, 15 | value: Int = 0, 16 | ) : BaseH { 17 | override val value: MutableState = mutableStateOf(value) 18 | } 19 | 20 | class HFan( 21 | override val name: String, 22 | override val id: String, 23 | value: Int = 0, 24 | ) : BaseH { 25 | override val value: MutableState = mutableStateOf(value) 26 | } 27 | 28 | 29 | class HTemp( 30 | override val name: String, 31 | override val id: String, 32 | value: Int = 0, 33 | ) : BaseH { 34 | override val value: MutableState = mutableStateOf(value) 35 | } -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/kotlin/model/item/BaseISensor.kt: -------------------------------------------------------------------------------- 1 | package model.item 2 | 3 | import androidx.compose.runtime.MutableState 4 | import androidx.compose.runtime.mutableStateOf 5 | import androidx.compose.runtime.snapshots.SnapshotStateList 6 | import androidx.compose.runtime.toMutableStateList 7 | import model.hardware.HTemp 8 | 9 | 10 | interface BaseISensor : BaseI 11 | 12 | 13 | interface BaseITemp : BaseISensor 14 | 15 | 16 | class IFan( 17 | name: String, 18 | override val id: String, 19 | hFanId: String? = null, 20 | ) : BaseISensor { 21 | override val name: MutableState = mutableStateOf(name) 22 | val hFanId: MutableState = mutableStateOf(hFanId) 23 | } 24 | 25 | 26 | class ITemp( 27 | name: String, 28 | override val id: String, 29 | hTempId: String? = null, 30 | ) : BaseITemp { 31 | override val name: MutableState = mutableStateOf(name) 32 | 33 | val hTempId: MutableState = mutableStateOf(hTempId) 34 | 35 | } 36 | 37 | 38 | enum class CustomTempType { 39 | average, 40 | max, 41 | min 42 | } 43 | 44 | class ICustomTemp( 45 | name: String, 46 | override val id: String, 47 | 48 | customTempType: CustomTempType = CustomTempType.average, 49 | hTempIds: List = listOf(), 50 | 51 | value: Int = 0, 52 | ) : BaseITemp { 53 | override val name: MutableState = mutableStateOf(name) 54 | 55 | val hTempIds: SnapshotStateList = hTempIds.toMutableStateList() 56 | val customTempType: MutableState = mutableStateOf(customTempType) 57 | 58 | val value: MutableState = mutableStateOf(value) 59 | 60 | 61 | fun calcAndSet(hTemps: List) { 62 | if (!isValid()) { 63 | value.value = null 64 | return 65 | } 66 | 67 | value.value = getTempListValues(hTemps).let { 68 | when (customTempType.value) { 69 | CustomTempType.average -> it.average().toInt() 70 | CustomTempType.max -> it.max() 71 | CustomTempType.min -> it.min() 72 | } 73 | } 74 | } 75 | 76 | 77 | private fun isValid(): Boolean = hTempIds.isNotEmpty() 78 | 79 | private fun getTempListValues(hTempList: List): List { 80 | val valueList = mutableListOf() 81 | hTempIds.forEach { id -> 82 | valueList.add( 83 | hTempList.first { 84 | it.id == id 85 | }.value.value 86 | ) 87 | } 88 | return valueList 89 | } 90 | } -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/kotlin/model/item/IControl.kt: -------------------------------------------------------------------------------- 1 | package model.item 2 | 3 | import androidx.compose.runtime.MutableState 4 | import androidx.compose.runtime.mutableStateOf 5 | 6 | class IControl( 7 | name: String, 8 | override val id: String, 9 | 10 | controlId: String? = null, 11 | isAuto: Boolean = true, 12 | iBehaviorId: String? = null 13 | 14 | ) : BaseI { 15 | override val name: MutableState = mutableStateOf(name) 16 | val hControlId: MutableState = mutableStateOf(controlId) 17 | val isAuto: MutableState = mutableStateOf(isAuto) 18 | val iBehaviorId: MutableState = mutableStateOf(iBehaviorId) 19 | } -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/kotlin/settingSlidingWindows/AdvanceSettingDsl.kt: -------------------------------------------------------------------------------- 1 | package settingSlidingWindows 2 | 3 | import androidx.compose.runtime.Composable 4 | 5 | 6 | interface AdvanceSettingScope { 7 | 8 | 9 | // https://issuetracker.google.com/issues/239435908?pli=1 10 | /** 11 | * Header of the advance setting windows. Displays a back arrow and title. 12 | * title and settingColors params are nullable because they can be retrieved 13 | * from SettingScope, but it's impossible to make default parameter 14 | * in a composable function inside an interface. 15 | * @param title title of header 16 | * @param settingColors colors used in the header 17 | */ 18 | @Composable 19 | fun header( 20 | title: String?, 21 | settingColors: SettingColors?, 22 | ) 23 | } -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/kotlin/settingSlidingWindows/AdvanceSettingScopeImpl.kt: -------------------------------------------------------------------------------- 1 | package settingSlidingWindows 2 | 3 | import androidx.compose.foundation.background 4 | import androidx.compose.foundation.layout.* 5 | import androidx.compose.material3.Divider 6 | import androidx.compose.material3.Icon 7 | import androidx.compose.material3.IconButton 8 | import androidx.compose.material3.Text 9 | import androidx.compose.runtime.Composable 10 | import androidx.compose.ui.Alignment 11 | import androidx.compose.ui.Modifier 12 | import androidx.compose.ui.unit.dp 13 | import settingSlidingWindows.utils.getIcon 14 | 15 | 16 | internal class AdvanceSettingScopeImpl( 17 | private val settingState: SettingState, 18 | private val _settingColors: SettingColors, 19 | private val settingTextStyle: SettingTextStyle, 20 | private val _title: String? = null, 21 | ) : AdvanceSettingScope { 22 | 23 | @Composable 24 | override fun header( 25 | title: String?, 26 | settingColors: SettingColors?, 27 | ) { 28 | 29 | if (_title == null && title == null) 30 | throw SettingException( 31 | "title has to be passed in argument " + 32 | "if it hasn't be defined inside SettingScope" 33 | ) 34 | 35 | if (title != null) 36 | baseHeader( 37 | title = title, 38 | settingColors = settingColors ?: _settingColors 39 | ) 40 | else 41 | baseHeader( 42 | title = _title!!, 43 | settingColors = settingColors ?: _settingColors 44 | ) 45 | 46 | } 47 | 48 | 49 | @Composable 50 | private fun baseHeader( 51 | title: String, 52 | settingColors: SettingColors, 53 | ) { 54 | Column( 55 | modifier = Modifier.background( 56 | color = settingColors.background 57 | ), 58 | horizontalAlignment = Alignment.CenterHorizontally 59 | ) { 60 | Box( 61 | modifier = Modifier.fillMaxWidth().padding(vertical = SettingDefaults.largePadding) 62 | ) { 63 | IconButton( 64 | onClick = { 65 | settingState.close() 66 | } 67 | ) { 68 | Icon( 69 | painter = getIcon("arrow/chevron/chevron_left${SettingDefaults.iconSize}"), 70 | contentDescription = null, 71 | tint = settingColors.onBackground 72 | ) 73 | } 74 | Text( 75 | modifier = Modifier.align(Alignment.Center), 76 | style = settingTextStyle.advanceItemHeaderStyle, 77 | color = settingColors.onBackground, 78 | text = title 79 | ) 80 | } 81 | 82 | Divider( 83 | modifier = Modifier.fillMaxWidth(0.95f), 84 | thickness = 2.dp, 85 | color = settingColors.onBackground 86 | ) 87 | Spacer(Modifier.height(40.dp)) 88 | } 89 | } 90 | 91 | } -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/kotlin/settingSlidingWindows/SettingDsl.kt: -------------------------------------------------------------------------------- 1 | package settingSlidingWindows 2 | 3 | import androidx.compose.runtime.Composable 4 | 5 | interface SettingScope { 6 | 7 | /** 8 | * Premake header for the first view windows 9 | * @param title Display title 10 | */ 11 | fun header( 12 | title: String, 13 | settingColors: SettingColors? = null 14 | ) 15 | 16 | /** 17 | * Header of the first view windows 18 | * @param content 19 | */ 20 | fun header( 21 | content: @Composable () -> Unit 22 | ) 23 | 24 | 25 | /** 26 | * Premake item, witch handle slide interaction 27 | * @param settingColors 28 | * @param icon Icon displayed of the left 29 | * @param title Title displayed of the middle top 30 | * @param subTitle Subtitle displayed of the bottom 31 | * @param advanceIconButton 32 | * @param showTopLine Should show top line 33 | * @param advanceItemContent Content when we open this setting 34 | */ 35 | fun item( 36 | settingColors: SettingColors? = null, 37 | icon: @Composable (() -> Unit)? = null, 38 | title: String? = null, 39 | subTitle: String? = null, 40 | advanceIconButton: @Composable (() -> Unit)? = null, 41 | showAdvanceIcon: Boolean = true, 42 | showTopLine: Boolean = false, 43 | onClick: (() -> Unit)? = null, 44 | advanceItemContent: (@Composable AdvanceSettingScope.() -> Unit)? = null, 45 | ) 46 | 47 | /** 48 | * Customizable item. You have to change the state of [SettingState] 49 | * if you want to open your advanceItemContent 50 | * @param content Content of this item, Integer parameter represent 51 | * the index of this setting, used for custom item with [SettingState] 52 | * @param advanceItemContent Content when we open this setting 53 | */ 54 | fun item( 55 | content: @Composable (Int) -> Unit, 56 | advanceItemContent: (@Composable AdvanceSettingScope.() -> Unit)? = null, 57 | ) 58 | 59 | /** 60 | * Premake separator of setting, there will be a space 61 | * and a subtitle 62 | * @param text Text witch will be displayed 63 | * @param settingColors 64 | */ 65 | fun group( 66 | text: String, 67 | settingColors: SettingColors? = null, 68 | ) 69 | 70 | /** 71 | * Customisable group of setting 72 | * @param content 73 | */ 74 | fun group( 75 | content: @Composable () -> Unit, 76 | ) 77 | 78 | } -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/kotlin/settingSlidingWindows/utils/Type.kt: -------------------------------------------------------------------------------- 1 | package settingSlidingWindows.utils 2 | 3 | import androidx.compose.material3.Typography 4 | import androidx.compose.ui.text.TextStyle 5 | import androidx.compose.ui.text.font.FontFamily 6 | import androidx.compose.ui.text.font.FontWeight 7 | import androidx.compose.ui.unit.sp 8 | 9 | 10 | internal val SettingTypo = Typography( 11 | // header first view 12 | titleLarge = TextStyle( 13 | fontFamily = FontFamily.Default, 14 | fontWeight = FontWeight.Bold, 15 | fontSize = 40.sp, 16 | lineHeight = 24.sp, 17 | letterSpacing = 0.5.sp 18 | ), 19 | // header advance 20 | titleMedium = TextStyle( 21 | fontFamily = FontFamily.Default, 22 | fontWeight = FontWeight.SemiBold, 23 | fontSize = 22.sp, 24 | lineHeight = 24.sp, 25 | letterSpacing = 0.5.sp 26 | ), 27 | 28 | // item title 29 | bodyLarge = TextStyle( 30 | fontFamily = FontFamily.Default, 31 | fontWeight = FontWeight.W600, 32 | fontSize = 18.sp, 33 | lineHeight = 24.sp, 34 | letterSpacing = 0.5.sp 35 | ), 36 | // item subtitle 37 | bodyMedium = TextStyle( 38 | fontFamily = FontFamily.Default, 39 | fontWeight = FontWeight.W300, 40 | fontSize = 15.sp, 41 | lineHeight = 24.sp, 42 | letterSpacing = 0.5.sp 43 | ), 44 | 45 | // group 46 | labelLarge = TextStyle( 47 | fontFamily = FontFamily.Default, 48 | fontWeight = FontWeight.W200, 49 | fontSize = 11.sp, 50 | lineHeight = 24.sp, 51 | letterSpacing = 0.5.sp 52 | ) 53 | ) -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/kotlin/settingSlidingWindows/utils/resources.kt: -------------------------------------------------------------------------------- 1 | package settingSlidingWindows.utils 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.remember 5 | import androidx.compose.ui.graphics.painter.Painter 6 | import androidx.compose.ui.platform.LocalDensity 7 | import androidx.compose.ui.res.loadSvgPainter 8 | import androidx.compose.ui.res.useResource 9 | 10 | 11 | @Composable 12 | internal fun getIcon(id: String): Painter { 13 | val density = LocalDensity.current // to calculate the intrinsic size of vector images (SVG, XML) 14 | return remember { 15 | useResource("drawable/$id.svg") { 16 | loadSvgPainter(it, density) 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/kotlin/ui/addItem/StatesChoice.kt: -------------------------------------------------------------------------------- 1 | package ui.addItem 2 | 3 | import utils.Resources 4 | 5 | enum class ChoiceType { 6 | CONTROL, 7 | BEHAVIOR, 8 | SENSOR 9 | } 10 | 11 | private class ChoiceStateException(msg: String = "") : Exception(msg) 12 | 13 | data class ChoiceState( 14 | val current: ChoiceType = ChoiceType.BEHAVIOR, 15 | val animationSign: Int = 0, 16 | val title: String = Resources.getString("title/behavior"), 17 | val previous: ChoiceType = ChoiceType.CONTROL, 18 | val next: ChoiceType = ChoiceType.SENSOR, 19 | ) 20 | 21 | 22 | fun updateChoiceState( 23 | state: ChoiceState, 24 | ): ChoiceState { 25 | val currentState = when (state.animationSign) { 26 | -1 -> state.previous 27 | 1 -> state.next 28 | else -> throw ChoiceStateException() 29 | } 30 | 31 | return when (currentState) { 32 | ChoiceType.CONTROL -> controlChoice(state) 33 | ChoiceType.BEHAVIOR -> behaviorChoice(state) 34 | ChoiceType.SENSOR -> sensorChoice(state) 35 | } 36 | } 37 | 38 | 39 | private fun behaviorChoice( 40 | state: ChoiceState, 41 | ): ChoiceState = state.copy( 42 | title = Resources.getString("title/behavior"), 43 | current = ChoiceType.BEHAVIOR, 44 | previous = ChoiceType.CONTROL, 45 | next = ChoiceType.SENSOR 46 | ) 47 | 48 | private fun controlChoice( 49 | state: ChoiceState, 50 | ): ChoiceState = state.copy( 51 | title = Resources.getString("title/control"), 52 | current = ChoiceType.CONTROL, 53 | previous = ChoiceType.SENSOR, 54 | next = ChoiceType.BEHAVIOR 55 | ) 56 | 57 | private fun sensorChoice( 58 | state: ChoiceState, 59 | ): ChoiceState = state.copy( 60 | title = Resources.getString("title/sensor"), 61 | current = ChoiceType.SENSOR, 62 | previous = ChoiceType.BEHAVIOR, 63 | next = ChoiceType.CONTROL 64 | ) -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/kotlin/ui/component/managerExpandItem.kt: -------------------------------------------------------------------------------- 1 | package ui.component 2 | 3 | import androidx.compose.animation.* 4 | import androidx.compose.foundation.clickable 5 | import androidx.compose.foundation.layout.Arrangement 6 | import androidx.compose.foundation.layout.Column 7 | import androidx.compose.foundation.layout.Row 8 | import androidx.compose.foundation.layout.fillMaxWidth 9 | import androidx.compose.material3.Icon 10 | import androidx.compose.runtime.Composable 11 | import androidx.compose.runtime.MutableState 12 | import androidx.compose.runtime.mutableStateOf 13 | import androidx.compose.ui.Alignment 14 | import androidx.compose.ui.Modifier 15 | import androidx.compose.ui.graphics.Color 16 | import ui.theme.LocalColors 17 | import utils.Resources 18 | 19 | @OptIn(ExperimentalAnimationApi::class) 20 | @Composable 21 | fun managerExpandItem( 22 | value: Int, 23 | color: Color = LocalColors.current.onMainContainer, 24 | expanded: MutableState = mutableStateOf(true), 25 | suffix: String = Resources.getString("unity/percent"), 26 | content: @Composable (() -> Unit)? = null, 27 | ) { 28 | Row( 29 | modifier = Modifier.fillMaxWidth(), 30 | horizontalArrangement = Arrangement.SpaceBetween, 31 | verticalAlignment = Alignment.CenterVertically 32 | ) { 33 | managerText( 34 | text = "$value $suffix", 35 | color = color 36 | ) 37 | 38 | Icon( 39 | modifier = Modifier 40 | .clickable { expanded.value = !expanded.value }, 41 | painter = when (expanded.value) { 42 | true -> Resources.getIcon("arrow/expand/expand_less24") 43 | false -> Resources.getIcon("arrow/expand/expand_more24") 44 | }, 45 | contentDescription = null, 46 | tint = color 47 | ) 48 | } 49 | 50 | 51 | AnimatedContent( 52 | targetState = expanded.value, 53 | transitionSpec = { 54 | slideInVertically() with slideOutVertically() 55 | } 56 | ) { 57 | if (it) { 58 | // we need Columns somehow 59 | Column { 60 | content?.invoke() 61 | } 62 | } 63 | 64 | } 65 | } -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/kotlin/ui/component/managerText.kt: -------------------------------------------------------------------------------- 1 | package ui.component 2 | 3 | import androidx.compose.material3.MaterialTheme 4 | import androidx.compose.material3.Text 5 | import androidx.compose.runtime.Composable 6 | import androidx.compose.ui.Modifier 7 | import androidx.compose.ui.graphics.Color 8 | import androidx.compose.ui.text.TextStyle 9 | import androidx.compose.ui.text.style.TextOverflow 10 | 11 | @Composable 12 | fun managerText( 13 | text: String, 14 | modifier: Modifier = Modifier, 15 | style: TextStyle = MaterialTheme.typography.bodyMedium, 16 | color: Color, 17 | enabled: Boolean = true, 18 | overflow: TextOverflow = TextOverflow.Ellipsis, 19 | maxLines: Int = 1, 20 | ) { 21 | Text( 22 | modifier = modifier, 23 | text = text, 24 | color = if (enabled) 25 | color 26 | else color.copy( 27 | alpha = 0.8f 28 | ), 29 | maxLines = maxLines, 30 | style = style, 31 | overflow = overflow 32 | ) 33 | } -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/kotlin/ui/configuration/confDialogs/ConfVM.kt: -------------------------------------------------------------------------------- 1 | package ui.configuration.confDialogs 2 | 3 | import Application 4 | import ConfInfo 5 | import FState 6 | import Settings 7 | import kotlinx.coroutines.launch 8 | import model.item.BaseI.Companion.checkNameValid 9 | import model.item.NameException 10 | import proto.ConfHelper 11 | import proto.SettingsHelper 12 | 13 | class ConfVM( 14 | val settings: Settings = FState.settings, 15 | ) { 16 | 17 | fun saveConfiguration(name: String): Boolean { 18 | 19 | val confId = FState.settings.confId.value 20 | println("try save conf: id = $confId, name = $name") 21 | 22 | val index = FState.settings.getIndexInfo(confId) 23 | 24 | if (confId == null || index == null) { 25 | println("save conf: id == null -> return") 26 | return false 27 | } 28 | 29 | try { 30 | checkNameValid( 31 | names = settings.confInfoList.map { item -> 32 | item.name.value 33 | }, 34 | name = name, 35 | index = index 36 | ) 37 | } catch (e: NameException) { 38 | println("save conf: NameException -> return") 39 | return false 40 | } 41 | settings.confInfoList[index].name.value = name 42 | SettingsHelper.writeSettings() 43 | ConfHelper.writeConf(confId, name) 44 | 45 | println("save conf: success") 46 | return true 47 | } 48 | 49 | fun onChangeConfiguration(id: String?) { 50 | if (id != null) { 51 | if (!ConfHelper.loadConf(id)) { 52 | return 53 | } 54 | } 55 | 56 | settings.confId.value = id 57 | SettingsHelper.writeSettings() 58 | 59 | Application.Api.scope.launch { 60 | Application.Api.api.settingsAndConfChange() 61 | } 62 | } 63 | 64 | fun addConfiguration(name: String, id: String): Boolean { 65 | println("addConfiguration: $id, $name") 66 | try { 67 | checkNameValid( 68 | names = settings.confInfoList.map { item -> 69 | item.name.value 70 | }, 71 | name = name 72 | ) 73 | } catch (e: NameException) { 74 | println("add conf: NameException -> return false") 75 | return false 76 | } 77 | 78 | settings.confId.value = id 79 | settings.confInfoList.add(ConfInfo(id, name)) 80 | SettingsHelper.writeSettings() 81 | ConfHelper.writeConf(id, name) 82 | 83 | return true 84 | } 85 | 86 | fun removeConfiguration(id: String, index: Int) { 87 | println("remove conf: id = $id") 88 | 89 | ConfHelper.removeConf(id) 90 | 91 | settings.confInfoList.removeAt(index) 92 | 93 | var currentConfigIsRemove = false 94 | if (settings.confId.value == id) { 95 | settings.confId.value = null 96 | currentConfigIsRemove = true 97 | } 98 | 99 | SettingsHelper.writeSettings() 100 | 101 | if (currentConfigIsRemove) { 102 | Application.Api.scope.launch { 103 | Application.Api.api.settingsAndConfChange() 104 | } 105 | } 106 | } 107 | } -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/kotlin/ui/dialogs/errorDialog.kt: -------------------------------------------------------------------------------- 1 | package ui.dialogs 2 | 3 | import FState 4 | import UiState 5 | import androidx.compose.runtime.Composable 6 | import ui.theme.LocalColors 7 | import utils.Resources 8 | import java.awt.Toolkit 9 | import java.awt.datatransfer.Clipboard 10 | import java.awt.datatransfer.StringSelection 11 | 12 | @Composable 13 | fun errorDialog() { 14 | val error = FState.ui.errorDialog.value 15 | 16 | 17 | baseDialog( 18 | enabled = FState.ui.dialogExpanded.value == UiState.Dialog.SHOW_ERROR, 19 | title = Resources.getString("dialog/title/error"), 20 | onEnterKey = { FState.ui.closeShowError() }, 21 | topContent = { 22 | 23 | if (error != null) { 24 | baseDialogText( 25 | text = error.content 26 | ) 27 | } 28 | 29 | }, 30 | bottomContent = { 31 | 32 | if (error?.copyContent != null) { 33 | baseDialogButton( 34 | onClick = { 35 | println("copy") 36 | setClipboard(error.copyContent) 37 | }, 38 | text = Resources.getString("common/copy") 39 | ) 40 | } 41 | 42 | baseDialogButton( 43 | onClick = { FState.ui.closeShowError() }, 44 | icon = Resources.getIcon("select/check24"), 45 | text = Resources.getString("common/ok"), 46 | containerColor = LocalColors.current.inputMain, 47 | contentColor = LocalColors.current.onInputMain, 48 | ) 49 | } 50 | ) 51 | } 52 | 53 | 54 | private fun setClipboard(s: String) { 55 | val selection = StringSelection(s) 56 | val clipboard: Clipboard = Toolkit.getDefaultToolkit().systemClipboard 57 | clipboard.setContents(selection, selection) 58 | } -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/kotlin/ui/itemsList/behaviorList/BaseBehaviorVM.kt: -------------------------------------------------------------------------------- 1 | package ui.screen.itemsList.behaviorList 2 | 3 | import FState 4 | import androidx.compose.runtime.snapshots.SnapshotStateList 5 | import model.item.BaseI.Companion.checkNameValid 6 | import model.item.BaseIBehavior 7 | import model.item.IControl 8 | import utils.getIndexList 9 | 10 | open class BaseBehaviorVM( 11 | val iBehaviors: SnapshotStateList = FState.iBehaviors, 12 | private val iControls: SnapshotStateList = FState.iControls, 13 | ) { 14 | fun remove(index: Int) { 15 | val behavior = iBehaviors[index] 16 | 17 | val indexList = getIndexList( 18 | list = iControls, 19 | predicate = { it.iBehaviorId.value == behavior.id } 20 | ) 21 | 22 | indexList.forEach { 23 | iControls[it].iBehaviorId.value = null 24 | } 25 | iBehaviors.removeAt(index) 26 | } 27 | 28 | 29 | fun setName(name: String, index: Int) { 30 | checkNameValid( 31 | names = iBehaviors.map { item -> 32 | item.name.value 33 | }, 34 | name = name, 35 | index = index 36 | ) 37 | iBehaviors[index].name.value = name 38 | } 39 | 40 | 41 | fun addBehavior(behavior: BaseIBehavior) { 42 | iBehaviors.add(behavior) 43 | } 44 | } -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/kotlin/ui/itemsList/behaviorList/behavior.kt: -------------------------------------------------------------------------------- 1 | package ui.screen.itemsList.behaviorList 2 | 3 | import androidx.compose.foundation.lazy.LazyListScope 4 | import androidx.compose.foundation.lazy.itemsIndexed 5 | import model.item.IFlat 6 | import model.item.ILinear 7 | import model.item.ITarget 8 | import ui.screen.itemsList.behaviorList.flat.flatAddItem 9 | import ui.screen.itemsList.behaviorList.flat.flatBody 10 | import ui.screen.itemsList.behaviorList.linearAndTarget.linear.linearAddItem 11 | import ui.screen.itemsList.behaviorList.linearAndTarget.linear.linearBody 12 | import ui.screen.itemsList.behaviorList.linearAndTarget.target.targetAddItem 13 | import ui.screen.itemsList.behaviorList.linearAndTarget.target.targetBody 14 | 15 | 16 | private val bodyViewModel: BaseBehaviorVM = BaseBehaviorVM() 17 | 18 | fun LazyListScope.behaviorAddItemList() { 19 | item { flatAddItem() } 20 | item { linearAddItem() } 21 | item { targetAddItem() } 22 | } 23 | 24 | 25 | fun LazyListScope.behaviorBodyList() { 26 | itemsIndexed(bodyViewModel.iBehaviors) { index, behavior -> 27 | 28 | when (behavior) { 29 | is IFlat -> { 30 | flatBody( 31 | flat = behavior, 32 | index = index 33 | ) 34 | } 35 | 36 | is ILinear -> { 37 | linearBody( 38 | linear = behavior, 39 | index = index 40 | ) 41 | } 42 | 43 | is ITarget -> { 44 | targetBody( 45 | target = behavior, 46 | index = index 47 | ) 48 | } 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/kotlin/ui/itemsList/behaviorList/flat/FlatVM.kt: -------------------------------------------------------------------------------- 1 | package ui.screen.itemsList.behaviorList.flat 2 | 3 | import model.item.BaseI 4 | import model.item.BaseI.Companion.getAvailableId 5 | import model.item.BaseI.Companion.getAvailableName 6 | import model.item.IFlat 7 | import ui.screen.itemsList.behaviorList.BaseBehaviorVM 8 | import utils.Resources 9 | 10 | class FlatVM : BaseBehaviorVM() { 11 | 12 | private fun updateValue(index: Int, value: Int) { 13 | iBehaviors[index].value.value = value 14 | } 15 | 16 | fun onMore(index: Int, value: Int) { 17 | if (value >= 100) return 18 | 19 | updateValue(index, value + 1) 20 | 21 | } 22 | 23 | fun onLess(index: Int, value: Int) { 24 | if (value <= 0) return 25 | 26 | 27 | updateValue(index, value - 1) 28 | 29 | 30 | } 31 | 32 | fun onValueChange(index: Int, value: Int) { 33 | 34 | updateValue(index, value) 35 | 36 | 37 | } 38 | 39 | 40 | fun defaultFlat() = IFlat( 41 | name = getAvailableName( 42 | list = iBehaviors.map { item -> 43 | item.name.value 44 | }, 45 | prefix = Resources.getString("default/flat_name") 46 | ), 47 | id = getAvailableId( 48 | list = iBehaviors.map { item -> 49 | item.id 50 | }, 51 | prefix = BaseI.IFlatPrefix 52 | ) 53 | ) 54 | } -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/kotlin/ui/itemsList/behaviorList/flat/baseFlat.kt: -------------------------------------------------------------------------------- 1 | package ui.screen.itemsList.behaviorList.flat 2 | 3 | import androidx.compose.foundation.clickable 4 | import androidx.compose.foundation.layout.* 5 | import androidx.compose.material3.* 6 | import androidx.compose.runtime.Composable 7 | import androidx.compose.ui.Alignment 8 | import androidx.compose.ui.Modifier 9 | import ui.component.managerText 10 | import ui.theme.LocalColors 11 | import ui.theme.LocalSpaces 12 | import utils.Resources 13 | 14 | 15 | @Composable 16 | fun baseFlat( 17 | value: Int, 18 | onLess: () -> Unit, 19 | onMore: () -> Unit, 20 | onValueChange: (Float) -> Unit, 21 | ) { 22 | Surface( 23 | color = LocalColors.current.inputVariant, 24 | shape = MaterialTheme.shapes.small 25 | ) { 26 | Row( 27 | modifier = Modifier 28 | .fillMaxWidth(), 29 | verticalAlignment = Alignment.CenterVertically, 30 | horizontalArrangement = Arrangement.SpaceBetween 31 | ) { 32 | 33 | 34 | managerText( 35 | text = Resources.getString("fan_speed"), 36 | color = LocalColors.current.onInputVariant, 37 | modifier = Modifier.padding(start = LocalSpaces.current.small) 38 | ) 39 | 40 | Box { 41 | Row { 42 | Icon( 43 | modifier = Modifier 44 | .clickable(onClick = onLess), 45 | painter = Resources.getIcon("sign/minus/remove24"), 46 | contentDescription = null, 47 | tint = LocalColors.current.onInputVariant 48 | ) 49 | 50 | Icon( 51 | modifier = Modifier 52 | .clickable(onClick = onMore), 53 | painter = Resources.getIcon("sign/plus/add24"), 54 | contentDescription = null, 55 | tint = LocalColors.current.onInputVariant 56 | ) 57 | } 58 | } 59 | } 60 | } 61 | 62 | Row( 63 | modifier = Modifier 64 | .fillMaxWidth(), 65 | verticalAlignment = Alignment.CenterVertically, 66 | horizontalArrangement = Arrangement.SpaceBetween 67 | ) { 68 | Slider( 69 | modifier = Modifier 70 | .fillMaxWidth(0.65f), 71 | value = value.toFloat(), 72 | steps = 100, 73 | valueRange = 0f..100f, 74 | onValueChange = onValueChange, 75 | colors = SliderDefaults.colors( 76 | thumbColor = LocalColors.current.onInactiveInput, 77 | activeTickColor = LocalColors.current.input, 78 | inactiveTickColor = LocalColors.current.inactiveInput 79 | ) 80 | ) 81 | 82 | managerText( 83 | text = "$value ${Resources.getString("unity/percent")}", 84 | color = LocalColors.current.onMainContainer 85 | ) 86 | } 87 | } -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/kotlin/ui/itemsList/behaviorList/flat/managerFlat.kt: -------------------------------------------------------------------------------- 1 | package ui.screen.itemsList.behaviorList.flat 2 | 3 | 4 | import androidx.compose.runtime.Composable 5 | import model.item.IFlat 6 | import ui.screen.itemsList.baseItemAddItem 7 | import ui.screen.itemsList.baseItemBody 8 | import utils.Resources 9 | 10 | private val viewModel: FlatVM = FlatVM() 11 | 12 | @Composable 13 | fun flatBody( 14 | flat: IFlat, 15 | index: Int, 16 | ) { 17 | 18 | baseItemBody( 19 | icon = Resources.getIcon("items/horizontal_rule24"), 20 | onNameChange = { viewModel.setName(it, index) }, 21 | onEditClick = { viewModel.remove(index) }, 22 | item = flat 23 | ) { 24 | 25 | baseFlat( 26 | value = flat.value.value, 27 | onLess = { viewModel.onLess(index, flat.value.value) }, 28 | onMore = { viewModel.onMore(index, flat.value.value) }, 29 | onValueChange = { viewModel.onValueChange(index, it.toInt()) } 30 | ) 31 | } 32 | } 33 | 34 | 35 | @Composable 36 | fun flatAddItem() { 37 | baseItemAddItem( 38 | icon = Resources.getIcon("items/horizontal_rule24"), 39 | name = Resources.getString("add_item/name/flat"), 40 | onEditClick = { viewModel.addBehavior(viewModel.defaultFlat()) }, 41 | text = Resources.getString("add_item/info/flat") 42 | ) 43 | } -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/kotlin/ui/itemsList/behaviorList/linearAndTarget/baseLinearAndTarget.kt: -------------------------------------------------------------------------------- 1 | package ui.screen.itemsList.behaviorList.linearAndTarget 2 | 3 | import ui.screen.itemsList.behaviorList.linearAndTarget.linear.LinearParams 4 | import ui.screen.itemsList.behaviorList.linearAndTarget.target.TargetParams 5 | import utils.Resources 6 | 7 | 8 | val linAndTarSuffixes = listOf( 9 | Resources.getString("unity/degree"), 10 | Resources.getString("unity/degree"), 11 | Resources.getString("unity/percent"), 12 | Resources.getString("unity/percent") 13 | ) 14 | 15 | interface LinAndTarParams 16 | 17 | fun isError(params: LinAndTarParams, str: String, opposedValue: Int): Boolean { 18 | val value = try { 19 | str.toInt() 20 | } catch (e: NumberFormatException) { 21 | return true 22 | } 23 | 24 | when (params) { 25 | is LinearParams -> { 26 | return try { 27 | when (params) { 28 | LinearParams.MIN_TEMP -> value >= opposedValue 29 | LinearParams.MAX_TEMP -> value <= opposedValue 30 | LinearParams.MIN_FAN_SPEED -> value >= opposedValue 31 | LinearParams.MAX_FAN_SPEED -> value <= opposedValue 32 | } 33 | } catch (e: NumberFormatException) { 34 | true 35 | } 36 | } 37 | 38 | is TargetParams -> { 39 | return try { 40 | when (params) { 41 | TargetParams.IDLE_TEMP -> value >= opposedValue 42 | TargetParams.LOAD_TEMP -> value <= opposedValue 43 | TargetParams.IDLE_FAN_SPEED -> value >= opposedValue 44 | TargetParams.LOAD_FAN_SPEED -> value <= opposedValue 45 | } 46 | } catch (e: NumberFormatException) { 47 | true 48 | } 49 | } 50 | 51 | else -> throw IllegalArgumentException() 52 | } 53 | } 54 | 55 | -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/kotlin/ui/itemsList/behaviorList/linearAndTarget/linear/baseLinear.kt: -------------------------------------------------------------------------------- 1 | package ui.screen.itemsList.behaviorList.linearAndTarget.linear 2 | 3 | import model.item.ILinear 4 | import utils.Resources 5 | 6 | 7 | fun linearValues(linear: ILinear) = listOf( 8 | linear.minTemp.value, 9 | linear.maxTemp.value, 10 | linear.minFanSpeed.value, 11 | linear.maxFanSpeed.value 12 | ) 13 | 14 | val linearPrefixes = listOf( 15 | Resources.getString("linear/min_temp"), 16 | Resources.getString("linear/max_temp"), 17 | Resources.getString("linear/min_fan_speed"), 18 | Resources.getString("linear/max_fan_speed") 19 | ) 20 | 21 | val linearTypes = listOf( 22 | LinearParams.MIN_TEMP, 23 | LinearParams.MAX_TEMP, 24 | LinearParams.MIN_FAN_SPEED, 25 | LinearParams.MAX_FAN_SPEED 26 | ) 27 | 28 | -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/kotlin/ui/itemsList/behaviorList/linearAndTarget/managerNumberChoice.kt: -------------------------------------------------------------------------------- 1 | package ui.screen.itemsList.behaviorList.linearAndTarget 2 | 3 | import androidx.compose.foundation.clickable 4 | import androidx.compose.foundation.layout.* 5 | import androidx.compose.material3.Icon 6 | import androidx.compose.material3.MaterialTheme 7 | import androidx.compose.runtime.Composable 8 | import androidx.compose.runtime.CompositionLocalProvider 9 | import androidx.compose.ui.Alignment 10 | import androidx.compose.ui.Modifier 11 | import androidx.compose.ui.graphics.Color 12 | import androidx.compose.ui.platform.LocalLayoutDirection 13 | import androidx.compose.ui.unit.LayoutDirection 14 | import ui.component.managerText 15 | import ui.theme.LocalColors 16 | import ui.theme.LocalSpaces 17 | import utils.Resources 18 | 19 | @Composable 20 | fun managerNumberChoice( 21 | text: @Composable () -> Unit, 22 | prefix: String, 23 | suffix: String, 24 | 25 | increase: () -> Unit, 26 | decrease: () -> Unit, 27 | color: Color = LocalColors.current.onMainContainer, 28 | ) { 29 | CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) { 30 | Row( 31 | modifier = Modifier 32 | .fillMaxWidth(), 33 | verticalAlignment = Alignment.CenterVertically, 34 | horizontalArrangement = Arrangement.SpaceBetween 35 | ) { 36 | CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Ltr) { 37 | Row( 38 | verticalAlignment = Alignment.CenterVertically, 39 | horizontalArrangement = Arrangement.End 40 | ) { 41 | managerText( 42 | text = ":", 43 | color = color 44 | ) 45 | Spacer(Modifier.width(LocalSpaces.current.small)) 46 | 47 | text() 48 | managerText( 49 | text = suffix, 50 | color = color 51 | ) 52 | 53 | Column { 54 | Icon( 55 | modifier = Modifier 56 | .clickable(onClick = increase), 57 | painter = Resources.getIcon("sign/plus/add20"), 58 | contentDescription = Resources.getString("ct/increase"), 59 | tint = color 60 | ) 61 | Icon( 62 | modifier = Modifier 63 | .clickable(onClick = decrease), 64 | painter = Resources.getIcon("sign/minus/remove20"), 65 | contentDescription = Resources.getString("ct/decrease"), 66 | tint = color 67 | ) 68 | } 69 | } 70 | 71 | managerText( 72 | style = MaterialTheme.typography.bodySmall, 73 | text = prefix, 74 | color = color 75 | ) 76 | } 77 | } 78 | } 79 | } 80 | 81 | 82 | fun numberChoiceFinalValue(value: Int): Int = 83 | if (value < 0) 84 | 0 85 | else { 86 | if (value > 100) 87 | 100 88 | else 89 | value 90 | } -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/kotlin/ui/itemsList/behaviorList/linearAndTarget/target/TargetVM.kt: -------------------------------------------------------------------------------- 1 | package ui.screen.itemsList.behaviorList.linearAndTarget.target 2 | 3 | 4 | import FState 5 | import androidx.compose.runtime.snapshots.SnapshotStateList 6 | import model.hardware.HTemp 7 | import model.item.BaseI 8 | import model.item.BaseITemp 9 | import model.item.ITarget 10 | import ui.screen.itemsList.behaviorList.BaseBehaviorVM 11 | import ui.screen.itemsList.behaviorList.linearAndTarget.LinAndTarParams 12 | import ui.screen.itemsList.behaviorList.linearAndTarget.numberChoiceFinalValue 13 | import utils.Resources 14 | 15 | 16 | enum class TargetParams : LinAndTarParams { 17 | IDLE_TEMP, 18 | LOAD_TEMP, 19 | IDLE_FAN_SPEED, 20 | LOAD_FAN_SPEED 21 | } 22 | 23 | class TargetVM( 24 | val hTemps: SnapshotStateList = FState.hTemps, 25 | val iTemps: SnapshotStateList = FState.iTemps, 26 | ) : BaseBehaviorVM() { 27 | 28 | fun setTemp(index: Int, hTempId: String?) { 29 | (iBehaviors[index] as ITarget).hTempId.value = hTempId 30 | } 31 | 32 | fun increase(index: Int, type: TargetParams): String { 33 | return updateValue(index, type) { 34 | it + 1 35 | } 36 | } 37 | 38 | fun decrease(index: Int, type: TargetParams): String { 39 | return updateValue(index, type) { 40 | it - 1 41 | } 42 | } 43 | 44 | fun onChange(index: Int, value: Int, type: TargetParams): String { 45 | return updateValue(index, type) { 46 | value 47 | } 48 | } 49 | 50 | 51 | /** 52 | * @param value used to make operation on the previous value before calculation of the final value 53 | */ 54 | private fun updateValue(index: Int, type: TargetParams, value: (Int) -> Int): String { 55 | val finalValue: Int 56 | 57 | with(iBehaviors[index] as ITarget) { 58 | when (type) { 59 | TargetParams.IDLE_TEMP -> { 60 | finalValue = numberChoiceFinalValue(value(this.idleTemp.value)) 61 | this.idleTemp.value = finalValue 62 | } 63 | 64 | TargetParams.LOAD_TEMP -> { 65 | finalValue = numberChoiceFinalValue(value(this.loadTemp.value)) 66 | this.loadTemp.value = finalValue 67 | } 68 | 69 | TargetParams.IDLE_FAN_SPEED -> { 70 | finalValue = numberChoiceFinalValue(value(this.idleFanSpeed.value)) 71 | this.idleFanSpeed.value = finalValue 72 | } 73 | 74 | TargetParams.LOAD_FAN_SPEED -> { 75 | finalValue = numberChoiceFinalValue(value(this.loadFanSpeed.value)) 76 | this.loadFanSpeed.value = finalValue 77 | } 78 | } 79 | } 80 | return finalValue.toString() 81 | } 82 | 83 | 84 | fun defaultTarget() = ITarget( 85 | name = BaseI.getAvailableName( 86 | list = iBehaviors.map { item -> 87 | item.name.value 88 | }, 89 | prefix = Resources.getString("default/target_name") 90 | ), 91 | id = BaseI.getAvailableId( 92 | list = iBehaviors.map { item -> 93 | item.id 94 | }, 95 | prefix = BaseI.ITargetPrefix 96 | ) 97 | ) 98 | } -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/kotlin/ui/itemsList/behaviorList/linearAndTarget/target/baseTarget.kt: -------------------------------------------------------------------------------- 1 | package ui.screen.itemsList.behaviorList.linearAndTarget.target 2 | 3 | import model.item.ITarget 4 | import utils.Resources 5 | 6 | 7 | fun targetValues(target: ITarget) = listOf( 8 | target.idleTemp.value, 9 | target.loadTemp.value, 10 | target.idleFanSpeed.value, 11 | target.loadFanSpeed.value 12 | ) 13 | 14 | val targetPrefixes = listOf( 15 | Resources.getString("target/idle_temp"), 16 | Resources.getString("target/load_temp"), 17 | Resources.getString("target/idle_fan_speed"), 18 | Resources.getString("target/load_fan_speed") 19 | ) 20 | 21 | val targetTypes = listOf( 22 | TargetParams.IDLE_TEMP, 23 | TargetParams.LOAD_TEMP, 24 | TargetParams.IDLE_FAN_SPEED, 25 | TargetParams.LOAD_FAN_SPEED 26 | ) -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/kotlin/ui/itemsList/controlList/ControlVM.kt: -------------------------------------------------------------------------------- 1 | package ui.screen.itemsList.controlList 2 | 3 | import FState 4 | import androidx.compose.runtime.snapshots.SnapshotStateList 5 | import model.hardware.HControl 6 | import model.item.BaseI 7 | import model.item.BaseI.Companion.checkNameValid 8 | import model.item.BaseI.Companion.getAvailableId 9 | import model.item.BaseI.Companion.getAvailableName 10 | import model.item.BaseIBehavior 11 | import model.item.IControl 12 | import utils.Resources 13 | 14 | class ControlVM( 15 | val iControls: SnapshotStateList = FState.iControls, 16 | val iBehaviors: SnapshotStateList = FState.iBehaviors, 17 | val hControls: SnapshotStateList = FState.hControls, 18 | ) { 19 | fun remove(index: Int) { 20 | iControls.removeAt(index) 21 | } 22 | 23 | fun setControl(index: Int, hControlId: String?) { 24 | iControls[index].hControlId.value = hControlId 25 | } 26 | 27 | fun setBehavior(index: Int, iBehaviorId: String?) { 28 | iControls[index].iBehaviorId.value = iBehaviorId 29 | } 30 | 31 | fun onSwitchClick(checked: Boolean, index: Int) { 32 | iControls[index].isAuto.value = !checked 33 | } 34 | 35 | 36 | fun setName(name: String, index: Int) { 37 | checkNameValid( 38 | names = iControls.map { item -> 39 | item.name.value 40 | }, 41 | name = name, 42 | index = index 43 | ) 44 | iControls[index].name.value = name 45 | } 46 | 47 | fun addControl() { 48 | iControls.add( 49 | IControl( 50 | name = getAvailableName( 51 | list = iControls.map { item -> 52 | item.name.value 53 | }, 54 | prefix = Resources.getString("default/control_name") 55 | ), 56 | id = getAvailableId( 57 | list = iControls.map { item -> 58 | item.id 59 | }, 60 | prefix = BaseI.IControlPrefix 61 | ) 62 | ) 63 | ) 64 | } 65 | } -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/kotlin/ui/itemsList/sensor/addItem/AddSensorVM.kt: -------------------------------------------------------------------------------- 1 | package ui.screen.itemsList.sensor.addItem 2 | 3 | import FState 4 | import androidx.compose.runtime.snapshots.SnapshotStateList 5 | import model.item.* 6 | import model.item.BaseI.Companion.getAvailableId 7 | import model.item.BaseI.Companion.getAvailableName 8 | import utils.Resources 9 | 10 | class AddSensorVM( 11 | private val iFans: SnapshotStateList = FState.iFans, 12 | private val iTemps: SnapshotStateList = FState.iTemps, 13 | ) { 14 | fun addFan() { 15 | iFans.add( 16 | IFan( 17 | name = getAvailableName( 18 | list = iFans.map { item -> 19 | item.name.value 20 | }, 21 | prefix = Resources.getString("default/fan_name") 22 | ), 23 | id = getAvailableId( 24 | list = iFans.map { item -> 25 | item.id 26 | }, 27 | prefix = BaseI.IFanPrefix 28 | ) 29 | ) 30 | ) 31 | } 32 | 33 | fun addTemp() { 34 | iTemps.add( 35 | ITemp( 36 | name = getAvailableName( 37 | list = iTemps.map { item -> 38 | item.name.value 39 | }, 40 | prefix = Resources.getString("default/temp_name") 41 | ), 42 | id = getAvailableId( 43 | list = iTemps.map { item -> 44 | item.id 45 | }, 46 | prefix = BaseI.ITempPrefix 47 | ) 48 | ) 49 | ) 50 | } 51 | 52 | fun addCustomTemp() { 53 | iTemps.add( 54 | ICustomTemp( 55 | name = getAvailableName( 56 | list = iTemps.map { item -> 57 | item.name.value 58 | }, 59 | prefix = Resources.getString("default/custom_temp_name") 60 | ), 61 | id = getAvailableId( 62 | list = iTemps.map { item -> 63 | item.id 64 | }, 65 | prefix = BaseI.ICustomTempPrefix 66 | ) 67 | ) 68 | ) 69 | } 70 | } -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/kotlin/ui/itemsList/sensor/addItem/sensorAddItem.kt: -------------------------------------------------------------------------------- 1 | package ui.screen.itemsList.sensor.addItem 2 | 3 | import androidx.compose.foundation.lazy.LazyListScope 4 | import ui.screen.itemsList.baseItemAddItem 5 | import utils.Resources 6 | 7 | val viewModel = AddSensorVM() 8 | 9 | 10 | fun LazyListScope.sensorAddItemList() { 11 | 12 | // fan 13 | item { 14 | baseItemAddItem( 15 | icon = Resources.getIcon("items/toys_fan24"), 16 | name = Resources.getString("add_item/name/fan"), 17 | onEditClick = { viewModel.addFan() }, 18 | text = Resources.getString("add_item/info/fan") 19 | ) 20 | } 21 | 22 | // temp 23 | item { 24 | baseItemAddItem( 25 | icon = Resources.getIcon("items/thermometer24"), 26 | name = Resources.getString("add_item/name/temp"), 27 | onEditClick = { viewModel.addTemp() }, 28 | text = Resources.getString("add_item/info/temp") 29 | ) 30 | } 31 | 32 | // custom temp 33 | item { 34 | baseItemAddItem( 35 | icon = Resources.getIcon("items/thermometer24"), 36 | name = Resources.getString("add_item/name/custom_temp"), 37 | onEditClick = { viewModel.addCustomTemp() }, 38 | text = Resources.getString("add_item/info/custom_temp") 39 | ) 40 | } 41 | } -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/kotlin/ui/itemsList/sensor/baseSensor.kt: -------------------------------------------------------------------------------- 1 | package ui.screen.itemsList.sensor 2 | 3 | 4 | import androidx.compose.foundation.layout.Spacer 5 | import androidx.compose.foundation.layout.height 6 | import androidx.compose.runtime.Composable 7 | import androidx.compose.ui.Modifier 8 | import androidx.compose.ui.graphics.painter.Painter 9 | import model.hardware.BaseH 10 | import model.item.BaseISensor 11 | import ui.component.managerListChoice 12 | import ui.component.managerText 13 | import ui.screen.itemsList.baseItemBody 14 | import ui.theme.LocalColors 15 | import ui.theme.LocalSpaces 16 | 17 | 18 | @Composable 19 | fun baseSensorBody( 20 | icon: Painter, 21 | onEditClick: () -> Unit, 22 | onNameChange: (String) -> Unit, 23 | 24 | sensorName: String?, 25 | sensorValue: String, 26 | 27 | sensorList: List, 28 | onItemClick: (String?) -> Unit, 29 | iSensor: BaseISensor, 30 | ) { 31 | baseItemBody( 32 | icon = icon, 33 | item = iSensor, 34 | onNameChange = onNameChange, 35 | onEditClick = onEditClick 36 | ) { 37 | managerListChoice( 38 | text = sensorName, 39 | onItemClick = onItemClick, 40 | ids = sensorList.map { it.id }, 41 | names = sensorList.map { it.name } 42 | ) 43 | 44 | Spacer(Modifier.height(LocalSpaces.current.medium)) 45 | 46 | managerText( 47 | text = sensorValue, 48 | color = LocalColors.current.onMainContainer, 49 | ) 50 | } 51 | } -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/kotlin/ui/itemsList/sensor/body/fanList/FanVM.kt: -------------------------------------------------------------------------------- 1 | package ui.screen.itemsList.sensor.body.fanList 2 | 3 | import FState 4 | import androidx.compose.runtime.snapshots.SnapshotStateList 5 | import model.hardware.HFan 6 | import model.item.BaseI.Companion.checkNameValid 7 | import model.item.IFan 8 | 9 | class FanVM( 10 | val iFans: SnapshotStateList = FState.iFans, 11 | val hFans: SnapshotStateList = FState.hFans, 12 | ) { 13 | 14 | fun remove(index: Int) { 15 | iFans.removeAt(index) 16 | } 17 | 18 | fun setFan(index: Int, hFanId: String?) { 19 | iFans[index].hFanId.value = hFanId 20 | } 21 | 22 | fun setName(name: String, index: Int) { 23 | checkNameValid( 24 | names = iFans.map { item -> 25 | item.name.value 26 | }, 27 | name = name, 28 | index = index 29 | ) 30 | iFans[index].name.value = name 31 | } 32 | } -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/kotlin/ui/itemsList/sensor/body/fanList/fan.kt: -------------------------------------------------------------------------------- 1 | package ui.screen.itemsList.sensor.body.fanList 2 | 3 | import androidx.compose.foundation.lazy.LazyListScope 4 | import androidx.compose.foundation.lazy.itemsIndexed 5 | import androidx.compose.runtime.Composable 6 | import model.item.IFan 7 | import ui.screen.itemsList.sensor.baseSensorBody 8 | import utils.Resources 9 | 10 | 11 | private val viewModel: FanVM = FanVM() 12 | 13 | 14 | fun LazyListScope.fanBodyList() { 15 | itemsIndexed(viewModel.iFans) { index, iFan -> 16 | fanBody( 17 | iFan = iFan, 18 | index = index 19 | ) 20 | } 21 | } 22 | 23 | 24 | @Composable 25 | private fun fanBody( 26 | iFan: IFan, 27 | index: Int, 28 | ) { 29 | val sensor = if (iFan.hFanId.value != null) { 30 | viewModel.hFans.find { 31 | it.id == iFan.hFanId.value 32 | } 33 | } else null 34 | 35 | baseSensorBody( 36 | icon = Resources.getIcon("items/toys_fan24"), 37 | onNameChange = { viewModel.setName(it, index) }, 38 | onEditClick = { viewModel.remove(index) }, 39 | sensorName = sensor?.name, 40 | sensorValue = "${sensor?.value?.value ?: 0} ${Resources.getString("unity/rpm")}", 41 | sensorList = viewModel.hFans, 42 | onItemClick = { viewModel.setFan(index, it) }, 43 | iSensor = iFan 44 | ) 45 | } 46 | 47 | -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/kotlin/ui/itemsList/sensor/body/tempList/TempVM.kt: -------------------------------------------------------------------------------- 1 | package ui.screen.itemsList.sensor.body.tempList 2 | 3 | import FState 4 | import androidx.compose.runtime.snapshots.SnapshotStateList 5 | import model.hardware.HTemp 6 | import model.item.* 7 | import model.item.BaseI.Companion.checkNameValid 8 | 9 | class TempVM( 10 | val iTemps: SnapshotStateList = FState.iTemps, 11 | val hTemps: SnapshotStateList = FState.hTemps, 12 | private val iBehaviors: SnapshotStateList = FState.iBehaviors, 13 | ) { 14 | 15 | 16 | fun removeCustom(index: Int) { 17 | 18 | val itemp = iTemps[index] 19 | 20 | /** 21 | * only for custom sensor. 22 | * we need to remove id in behaviors if necessary 23 | */ 24 | 25 | if (BaseI.getPrefix(itemp.id) == BaseI.ICustomTempPrefix) { 26 | for (i in iBehaviors.indices) { 27 | with(iBehaviors[i]) { 28 | when (this) { 29 | is ILinear -> { 30 | if (hTempId.value == itemp.id) { 31 | hTempId.value = null 32 | } 33 | } 34 | 35 | is ITarget -> { 36 | if (hTempId.value == itemp.id) { 37 | hTempId.value = null 38 | } 39 | } 40 | 41 | else -> {} 42 | } 43 | } 44 | 45 | } 46 | } 47 | 48 | iTemps.removeAt(index) 49 | } 50 | 51 | fun setTemp(index: Int, hTempId: String?) { 52 | with(iTemps[index] as ITemp) { 53 | this.hTempId.value = hTempId 54 | } 55 | } 56 | 57 | 58 | fun setName(name: String, index: Int) { 59 | checkNameValid( 60 | names = iTemps.map { item -> 61 | item.name.value 62 | }, 63 | name = name, 64 | index = index 65 | ) 66 | iTemps[index].name.value = name 67 | } 68 | 69 | fun setCustomType(type: CustomTempType, index: Int) { 70 | with(iTemps[index] as ICustomTemp) { 71 | customTempType.value = type 72 | } 73 | } 74 | 75 | fun addTempCustom(hTempId: String, index: Int) { 76 | with(iTemps[index] as ICustomTemp) { 77 | hTempIds.add(hTempId) 78 | } 79 | } 80 | 81 | fun removeTempCustom(hTempId: String, index: Int) { 82 | with(iTemps[index] as ICustomTemp) { 83 | hTempIds.remove(hTempId) 84 | } 85 | } 86 | } -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/kotlin/ui/itemsList/sensor/body/tempList/temp.kt: -------------------------------------------------------------------------------- 1 | package ui.screen.itemsList.sensor.body.tempList 2 | 3 | import androidx.compose.foundation.lazy.LazyListScope 4 | import androidx.compose.foundation.lazy.itemsIndexed 5 | import androidx.compose.runtime.Composable 6 | import model.item.ICustomTemp 7 | import model.item.ITemp 8 | import ui.screen.itemsList.sensor.baseSensorBody 9 | import utils.Resources 10 | 11 | 12 | private val viewModel: TempVM = TempVM() 13 | 14 | fun LazyListScope.tempBodyList() { 15 | itemsIndexed(viewModel.iTemps) { index, iTemp -> 16 | 17 | when (iTemp) { 18 | is ITemp -> tempBody( 19 | iTemp = iTemp, 20 | index = index 21 | ) 22 | 23 | is ICustomTemp -> customTempBody( 24 | iCustomTemp = iTemp, 25 | index = index 26 | ) 27 | } 28 | } 29 | } 30 | 31 | 32 | @Composable 33 | private fun tempBody( 34 | iTemp: ITemp, 35 | index: Int, 36 | ) { 37 | 38 | val sensor = if (iTemp.hTempId.value != null) { 39 | viewModel.hTemps.find { 40 | it.id == iTemp.hTempId.value 41 | } 42 | } else null 43 | 44 | baseSensorBody( 45 | icon = Resources.getIcon("items/thermometer24"), 46 | onNameChange = { viewModel.setName(it, index) }, 47 | onEditClick = { viewModel.removeCustom(index) }, 48 | sensorName = sensor?.name, 49 | sensorValue = "${sensor?.value?.value ?: 0} ${Resources.getString("unity/degree")}", 50 | sensorList = viewModel.hTemps, 51 | onItemClick = { viewModel.setTemp(index, it) }, 52 | iSensor = iTemp 53 | ) 54 | } 55 | 56 | @Composable 57 | private fun customTempBody( 58 | iCustomTemp: ICustomTemp, 59 | index: Int, 60 | ) { 61 | baseCustomTempBody( 62 | onEditClick = { viewModel.removeCustom(index) }, 63 | onNameChange = { viewModel.setName(it, index) }, 64 | hTemps = viewModel.hTemps, 65 | iCustomTemp = iCustomTemp, 66 | onCustomTypeChange = { viewModel.setCustomType(it, index) }, 67 | onAddTempSensor = { viewModel.addTempCustom(it, index) }, 68 | onRemoveTemp = { viewModel.removeTempCustom(it, index) } 69 | ) 70 | } 71 | -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/kotlin/ui/settings/donate.kt: -------------------------------------------------------------------------------- 1 | package ui.settings 2 | 3 | import androidx.compose.foundation.background 4 | import androidx.compose.foundation.clickable 5 | import androidx.compose.foundation.layout.* 6 | import androidx.compose.foundation.lazy.LazyColumn 7 | import androidx.compose.foundation.lazy.items 8 | import androidx.compose.material3.Divider 9 | import androidx.compose.material3.Icon 10 | import androidx.compose.ui.Modifier 11 | import androidx.compose.ui.unit.dp 12 | import settingSlidingWindows.SettingScope 13 | import ui.component.managerText 14 | import ui.theme.LocalColors 15 | import ui.theme.LocalSpaces 16 | import utils.Resources 17 | import java.awt.Desktop 18 | import java.net.URI 19 | 20 | 21 | private data class Donate( 22 | val title: String, 23 | val uri: URI, 24 | ) 25 | 26 | private val donateList = listOf( 27 | Donate( 28 | title = "Paypal", 29 | uri = URI("https://www.paypal.com/donate/?hosted_button_id=HV84HZ4G63HQ6") 30 | ) 31 | ) 32 | 33 | 34 | fun SettingScope.donate() { 35 | 36 | item( 37 | title = Resources.getString("settings/donate"), 38 | icon = { 39 | Icon( 40 | painter = Resources.getIcon("settings/attach_money24"), 41 | tint = LocalColors.current.onSecondContainer, 42 | contentDescription = null 43 | ) 44 | }, 45 | showTopLine = true 46 | ) { 47 | header(null, null) 48 | 49 | Divider( 50 | modifier = Modifier.fillMaxWidth(), 51 | color = LocalColors.current.onSecondContainer, 52 | thickness = 2.dp 53 | ) 54 | LazyColumn { 55 | items(donateList) { 56 | Row( 57 | modifier = Modifier 58 | .fillMaxWidth() 59 | .background(color = LocalColors.current.secondContainer) 60 | .clickable { 61 | val desktop = Desktop.getDesktop() 62 | 63 | try { 64 | desktop.browse(it.uri) 65 | } catch (e: Exception) { 66 | e.printStackTrace() 67 | } 68 | } 69 | ) { 70 | managerText( 71 | text = it.title, 72 | color = LocalColors.current.onSecondContainer, 73 | modifier = Modifier.padding(LocalSpaces.current.medium) 74 | ) 75 | } 76 | Divider( 77 | modifier = Modifier.fillMaxWidth(), 78 | color = LocalColors.current.onSecondContainer, 79 | thickness = 2.dp 80 | ) 81 | } 82 | } 83 | 84 | Spacer(Modifier.height(80.dp)) 85 | } 86 | } -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/kotlin/ui/settings/drawer/SettingsVM.kt: -------------------------------------------------------------------------------- 1 | package ui.settings.drawer 2 | 3 | import FState 4 | import Languages 5 | import Settings 6 | import Themes 7 | import proto.SettingsHelper 8 | import utils.OsSpecific 9 | 10 | class SettingsVM( 11 | val settings: Settings = FState.settings, 12 | ) { 13 | fun onUpdateDelay(delay: Int) { 14 | if (delay !in 1..100) { 15 | return 16 | } 17 | settings.updateDelay.value = delay 18 | SettingsHelper.writeSettings(true) 19 | } 20 | 21 | fun onLanguageChange(language: Languages) { 22 | settings.language.value = language 23 | SettingsHelper.writeSettings() 24 | } 25 | 26 | fun onThemeChange(theme: Themes) { 27 | settings.theme.value = theme 28 | SettingsHelper.writeSettings() 29 | } 30 | 31 | 32 | fun onLaunchAtStartUpChange(launchAtStartUp: Boolean) { 33 | OsSpecific.os.changeStartModeService(launchAtStartUp) 34 | } 35 | 36 | fun onUninstallService() { 37 | OsSpecific.os.uninstallService() 38 | } 39 | 40 | fun onValueDisableControl(value: Int) { 41 | if (value < 2) { 42 | return 43 | } 44 | settings.valueDisableControl.value = value 45 | SettingsHelper.writeSettings(true) 46 | } 47 | } -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/kotlin/ui/settings/help.kt: -------------------------------------------------------------------------------- 1 | package ui.settings 2 | 3 | import androidx.compose.material3.Icon 4 | import settingSlidingWindows.SettingScope 5 | import ui.theme.LocalColors 6 | import utils.Resources 7 | 8 | 9 | fun SettingScope.help() { 10 | item( 11 | title = Resources.getString("settings/help"), 12 | subTitle = Resources.getString("settings/help_sub_title"), 13 | icon = { 14 | Icon( 15 | painter = Resources.getIcon("settings/help24"), 16 | tint = LocalColors.current.onSecondContainer, 17 | contentDescription = null 18 | ) 19 | } 20 | ) { 21 | header(null, null) 22 | } 23 | } -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/kotlin/ui/settings/info.kt: -------------------------------------------------------------------------------- 1 | package ui.settings 2 | 3 | import FState 4 | import androidx.compose.foundation.background 5 | import androidx.compose.foundation.layout.* 6 | import androidx.compose.material3.Divider 7 | import androidx.compose.material3.Icon 8 | import androidx.compose.ui.Modifier 9 | import androidx.compose.ui.unit.dp 10 | import settingSlidingWindows.SettingScope 11 | import ui.component.managerText 12 | import ui.theme.LocalColors 13 | import ui.theme.LocalSpaces 14 | import utils.Resources 15 | 16 | 17 | fun SettingScope.info() { 18 | item( 19 | title = Resources.getString("settings/info"), 20 | icon = { 21 | Icon( 22 | painter = Resources.getIcon("settings/info24"), 23 | tint = LocalColors.current.onSecondContainer, 24 | contentDescription = null 25 | ) 26 | }, 27 | showTopLine = false 28 | ) { 29 | header(null, null) 30 | 31 | Divider( 32 | modifier = Modifier.fillMaxWidth(), 33 | color = LocalColors.current.onSecondContainer, 34 | thickness = 2.dp 35 | ) 36 | Column { 37 | Column { 38 | Row( 39 | modifier = Modifier 40 | .fillMaxWidth() 41 | .background(color = LocalColors.current.secondContainer) 42 | ) { 43 | managerText( 44 | text = "App version: ${FState.appVersion}", 45 | color = LocalColors.current.onSecondContainer, 46 | modifier = Modifier.padding(LocalSpaces.current.medium) 47 | ) 48 | } 49 | Divider( 50 | modifier = Modifier.fillMaxWidth(), 51 | color = LocalColors.current.onSecondContainer, 52 | thickness = 2.dp 53 | ) 54 | } 55 | Column { 56 | Row( 57 | modifier = Modifier 58 | .fillMaxWidth() 59 | .background(color = LocalColors.current.secondContainer) 60 | ) { 61 | managerText( 62 | text = "Service version: ${FState.settings.versionInstalled.value}", 63 | color = LocalColors.current.onSecondContainer, 64 | modifier = Modifier.padding(LocalSpaces.current.medium) 65 | ) 66 | } 67 | Divider( 68 | modifier = Modifier.fillMaxWidth(), 69 | color = LocalColors.current.onSecondContainer, 70 | thickness = 2.dp 71 | ) 72 | } 73 | 74 | Spacer(Modifier.height(80.dp)) 75 | } 76 | 77 | } 78 | } -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/kotlin/ui/settings/language.kt: -------------------------------------------------------------------------------- 1 | package ui.settings 2 | 3 | import Languages 4 | import androidx.compose.foundation.background 5 | import androidx.compose.foundation.clickable 6 | import androidx.compose.foundation.layout.* 7 | import androidx.compose.foundation.lazy.LazyColumn 8 | import androidx.compose.foundation.lazy.items 9 | import androidx.compose.material3.Divider 10 | import androidx.compose.material3.Icon 11 | import androidx.compose.runtime.MutableState 12 | import androidx.compose.ui.Modifier 13 | import androidx.compose.ui.unit.dp 14 | import settingSlidingWindows.SettingScope 15 | import ui.component.managerText 16 | import ui.theme.LocalColors 17 | import ui.theme.LocalSpaces 18 | import utils.Resources 19 | 20 | 21 | fun SettingScope.language( 22 | language: MutableState, 23 | onLanguageChange: (Languages) -> Unit, 24 | ) { 25 | item( 26 | title = Resources.getString("settings/language"), 27 | subTitle = Resources.getString("language/${language.value}"), 28 | icon = { 29 | Icon( 30 | painter = Resources.getIcon("settings/translate24"), 31 | tint = LocalColors.current.onSecondContainer, 32 | contentDescription = null 33 | ) 34 | } 35 | ) { 36 | header(null, null) 37 | 38 | Divider( 39 | modifier = Modifier.fillMaxWidth(), 40 | color = LocalColors.current.onSecondContainer, 41 | thickness = 2.dp 42 | ) 43 | 44 | LazyColumn { 45 | items(Languages.values()) { 46 | Column { 47 | Row( 48 | modifier = Modifier 49 | .fillMaxWidth() 50 | .clickable { onLanguageChange(it) } 51 | .background(color = LocalColors.current.secondContainer) 52 | ) { 53 | managerText( 54 | text = Resources.getString("language/$it"), 55 | color = LocalColors.current.onSecondContainer, 56 | modifier = Modifier.padding(LocalSpaces.current.medium) 57 | ) 58 | } 59 | Divider( 60 | modifier = Modifier.fillMaxWidth(), 61 | color = LocalColors.current.onSecondContainer, 62 | thickness = 2.dp 63 | ) 64 | } 65 | } 66 | 67 | item { 68 | Spacer(Modifier.height(80.dp)) 69 | } 70 | } 71 | } 72 | 73 | } -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/kotlin/ui/settings/theme.kt: -------------------------------------------------------------------------------- 1 | package ui.settings 2 | 3 | import Themes 4 | import androidx.compose.foundation.background 5 | import androidx.compose.foundation.clickable 6 | import androidx.compose.foundation.layout.* 7 | import androidx.compose.foundation.lazy.LazyColumn 8 | import androidx.compose.foundation.lazy.items 9 | import androidx.compose.material3.Divider 10 | import androidx.compose.material3.Icon 11 | import androidx.compose.runtime.MutableState 12 | import androidx.compose.ui.Modifier 13 | import androidx.compose.ui.unit.dp 14 | import settingSlidingWindows.SettingScope 15 | import ui.component.managerText 16 | import ui.theme.LocalColors 17 | import ui.theme.LocalSpaces 18 | import utils.Resources 19 | 20 | 21 | fun SettingScope.theme( 22 | theme: MutableState, 23 | onThemeChange: (Themes) -> Unit, 24 | ) { 25 | item( 26 | title = Resources.getString("settings/theme"), 27 | subTitle = Resources.getString("theme/${theme.value}"), 28 | icon = { 29 | Icon( 30 | painter = Resources.getIcon("settings/dark_mode24"), 31 | tint = LocalColors.current.onSecondContainer, 32 | contentDescription = null 33 | ) 34 | } 35 | ) { 36 | header(null, null) 37 | 38 | Divider( 39 | modifier = Modifier.fillMaxWidth(), 40 | color = LocalColors.current.onSecondContainer, 41 | thickness = 2.dp 42 | ) 43 | LazyColumn { 44 | items(Themes.values()) { 45 | Row( 46 | modifier = Modifier 47 | .fillMaxWidth() 48 | .clickable { onThemeChange(it) } 49 | .background(color = LocalColors.current.secondContainer) 50 | ) { 51 | managerText( 52 | text = Resources.getString("theme/$it"), 53 | color = LocalColors.current.onSecondContainer, 54 | modifier = Modifier.padding(LocalSpaces.current.medium) 55 | ) 56 | } 57 | Divider( 58 | modifier = Modifier.fillMaxWidth(), 59 | color = LocalColors.current.onSecondContainer, 60 | thickness = 2.dp 61 | ) 62 | } 63 | 64 | item { 65 | Spacer(Modifier.height(80.dp)) 66 | } 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/kotlin/ui/settings/updateDelay.kt: -------------------------------------------------------------------------------- 1 | package ui.settings 2 | 3 | import androidx.compose.foundation.clickable 4 | import androidx.compose.foundation.layout.Column 5 | import androidx.compose.foundation.layout.padding 6 | import androidx.compose.material3.Icon 7 | import androidx.compose.runtime.MutableState 8 | import androidx.compose.ui.Modifier 9 | import settingSlidingWindows.SettingScope 10 | import ui.theme.LocalColors 11 | import ui.theme.LocalSpaces 12 | import utils.Resources 13 | 14 | 15 | fun SettingScope.updateDelay( 16 | onDelayChange: (Int) -> Unit, 17 | updateDelay: MutableState, 18 | ) { 19 | item( 20 | icon = { 21 | Icon( 22 | painter = Resources.getIcon("settings/update24"), 23 | tint = LocalColors.current.onSecondContainer, 24 | contentDescription = null 25 | ) 26 | }, 27 | title = Resources.getString("settings/update_delay") + updateDelay.value, 28 | advanceIconButton = { 29 | Column( 30 | modifier = Modifier 31 | .padding(end = LocalSpaces.current.large) 32 | .padding(vertical = LocalSpaces.current.small) 33 | ) { 34 | Icon( 35 | modifier = Modifier.clickable { 36 | onDelayChange(updateDelay.value + 1) 37 | }, 38 | painter = Resources.getIcon("sign/plus/add20"), 39 | contentDescription = Resources.getString("ct/increase"), 40 | tint = LocalColors.current.onSecondContainer 41 | ) 42 | 43 | Icon( 44 | modifier = Modifier.clickable { 45 | onDelayChange(updateDelay.value - 1) 46 | }, 47 | painter = Resources.getIcon("sign/minus/remove20"), 48 | contentDescription = Resources.getString("ct/decrease"), 49 | tint = LocalColors.current.onSecondContainer 50 | ) 51 | } 52 | }, 53 | showTopLine = true 54 | ) 55 | } -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/kotlin/ui/theme/Color.kt: -------------------------------------------------------------------------------- 1 | package ui.theme 2 | 3 | import androidx.compose.runtime.compositionLocalOf 4 | import androidx.compose.ui.graphics.Color 5 | 6 | 7 | val LocalColors = compositionLocalOf { CustomColors() } 8 | 9 | val DarkPurple = Color(63, 59, 108) 10 | val LightPurple = Color(129, 103, 151) 11 | val BlueGrey = Color(43, 72, 101) 12 | val LightOrange = Color(0xFF938BA1) 13 | 14 | // default: Light theme 15 | val LightBlue = Color(0xFFCFCFEA) 16 | val LightBlue2 = Color(0xFF4FC3F7) 17 | 18 | 19 | val Blue = Color(0xFF0D47A1) 20 | val DarkBlue = Color(0xFF263238) 21 | val LightGrey = Color(0xFFECEFF1) 22 | val Grey = Color(0xFFB0BEC5) 23 | val MateGrey = Color(0xFF546E7A) 24 | val LightBlack = Color(0xFF212121) 25 | val LightCyan = Color(0xFFE0F7FA) 26 | val LightCyan2 = Color(163, 199, 214) 27 | val Beige = Color(0xFFdfa9a9) 28 | 29 | val OffWhite = Color(0xFFF2F2F2) 30 | 31 | data class CustomColors( 32 | val input: Color = LightBlue, 33 | val onInput: Color = Color.Black, 34 | val inputVariant: Color = LightBlue2, 35 | val onInputVariant: Color = Color.Black, 36 | val inputMain: Color = Beige, 37 | val onInputMain: Color = Color.Black, 38 | val inactiveInput: Color = LightBlack, 39 | val onInactiveInput: Color = LightCyan, 40 | 41 | val error: Color = Beige, 42 | val onError: Color = Color.Black, 43 | 44 | val mainTopBar: Color = DarkBlue, 45 | val onMainTopBar: Color = Color.White, 46 | val secondTopBar: Color = Blue, 47 | val onSecondTopBar: Color = Color.White, 48 | val mainHeader: Color = Grey, 49 | val onMainHeader: Color = Color.White, 50 | val mainBackground: Color = LightGrey, 51 | val onMainBackground: Color = Color.Black, 52 | val mainContainer: Color = MateGrey, 53 | val onMainContainer: Color = Color.White, 54 | val mainSurface: Color = Color.White, 55 | val onMainSurface: Color = Color.Black, 56 | 57 | val secondHeader: Color = Grey, 58 | val onSecondHeader: Color = Color.White, 59 | val secondBackground: Color = OffWhite, 60 | val onSecondBackground: Color = LightBlack, 61 | val secondContainer: Color = MateGrey, 62 | val onSecondContainer: Color = Color.White, 63 | val secondSurface: Color = Color.White, 64 | val onSecondSurface: Color = Color.Black 65 | ) 66 | 67 | val darkColors = CustomColors( 68 | 69 | input = LightCyan2, 70 | onInput = Color.Black, 71 | inputVariant = LightOrange, 72 | onInputVariant = Color.Black, 73 | inputMain = Beige, 74 | onInputMain = Color.Black, 75 | inactiveInput = LightBlack, 76 | onInactiveInput = LightOrange, 77 | 78 | error = Beige, 79 | onError = Color.Black, 80 | 81 | mainTopBar = DarkPurple, 82 | onMainTopBar = Color.White, 83 | secondTopBar = LightPurple, 84 | onSecondTopBar = Color.White, 85 | 86 | 87 | mainHeader = Grey, 88 | onMainHeader = Color.White, 89 | mainBackground = Color.Black, 90 | onMainBackground = Color.White, 91 | mainContainer = BlueGrey, 92 | onMainContainer = Color.White, 93 | mainSurface = Color.Black, 94 | onMainSurface = Color.White, 95 | 96 | 97 | secondHeader = Grey, 98 | onSecondHeader = Color.White, 99 | secondBackground = LightBlack, 100 | onSecondBackground = Color.White, 101 | secondContainer = MateGrey, 102 | onSecondContainer = Color.White, 103 | secondSurface = Color.White, 104 | onSecondSurface = Color.White, 105 | ) -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/kotlin/ui/theme/Shape.kt: -------------------------------------------------------------------------------- 1 | package ui.theme 2 | 3 | import androidx.compose.foundation.shape.RoundedCornerShape 4 | import androidx.compose.runtime.compositionLocalOf 5 | import androidx.compose.ui.unit.dp 6 | 7 | 8 | data class CustomShapes( 9 | val small: RoundedCornerShape = RoundedCornerShape(2.dp), 10 | val medium: RoundedCornerShape = RoundedCornerShape(5.dp), 11 | val large: RoundedCornerShape = RoundedCornerShape(10), 12 | 13 | val drawer: RoundedCornerShape = RoundedCornerShape(topEnd = 10.dp, bottomEnd = 10.dp), 14 | ) 15 | 16 | 17 | val LocalShapes = compositionLocalOf { CustomShapes() } -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/kotlin/ui/theme/Space.kt: -------------------------------------------------------------------------------- 1 | package ui.theme 2 | 3 | import androidx.compose.runtime.compositionLocalOf 4 | import androidx.compose.ui.unit.Dp 5 | import androidx.compose.ui.unit.dp 6 | 7 | data class CustomSpaces( 8 | val small: Dp = 5.dp, 9 | val medium: Dp = 10.dp, 10 | val large: Dp = 15.dp, 11 | ) 12 | 13 | 14 | val LocalSpaces = compositionLocalOf { CustomSpaces() } -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/kotlin/ui/theme/Theme.kt: -------------------------------------------------------------------------------- 1 | package ui.theme 2 | 3 | import Themes 4 | import androidx.compose.foundation.isSystemInDarkTheme 5 | import androidx.compose.material3.MaterialTheme 6 | import androidx.compose.runtime.Composable 7 | import androidx.compose.runtime.CompositionLocalProvider 8 | 9 | 10 | @Composable 11 | fun fanControlTheme( 12 | theme: Themes, 13 | content: @Composable () -> Unit, 14 | ) { 15 | val darkTheme = when (theme) { 16 | Themes.system -> isSystemInDarkTheme() 17 | Themes.light -> false 18 | Themes.dark -> true 19 | } 20 | 21 | val colors = when { 22 | darkTheme -> darkColors 23 | else -> CustomColors() 24 | } 25 | 26 | CompositionLocalProvider( 27 | LocalColors provides colors, 28 | LocalShapes provides CustomShapes(), 29 | LocalSpaces provides CustomSpaces() 30 | ) { 31 | MaterialTheme( 32 | typography = typography, 33 | content = content 34 | ) 35 | } 36 | } 37 | 38 | -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/kotlin/ui/theme/Type.kt: -------------------------------------------------------------------------------- 1 | package ui.theme 2 | 3 | import androidx.compose.material3.Typography 4 | import androidx.compose.ui.text.TextStyle 5 | import androidx.compose.ui.text.font.FontFamily 6 | import androidx.compose.ui.text.font.FontStyle 7 | import androidx.compose.ui.text.font.FontWeight 8 | import androidx.compose.ui.text.style.TextDecoration 9 | import androidx.compose.ui.unit.sp 10 | 11 | 12 | val typography = Typography( 13 | // items lists titles 14 | titleLarge = TextStyle( 15 | textDecoration = TextDecoration.Underline, 16 | fontStyle = FontStyle.Italic, 17 | fontFamily = FontFamily.Default, 18 | fontWeight = FontWeight.W500, 19 | fontSize = 50.sp, 20 | lineHeight = 24.sp, 21 | letterSpacing = 0.5.sp 22 | ), 23 | titleMedium = TextStyle( 24 | fontFamily = FontFamily.Default, 25 | fontWeight = FontWeight.SemiBold, 26 | fontSize = 25.sp, 27 | lineHeight = 24.sp, 28 | letterSpacing = 0.5.sp 29 | ), 30 | 31 | titleSmall = TextStyle( 32 | fontFamily = FontFamily.Default, 33 | fontWeight = FontWeight.W500, 34 | fontSize = 19.sp, 35 | lineHeight = 24.sp, 36 | letterSpacing = 0.5.sp 37 | ), 38 | 39 | bodyLarge = TextStyle( 40 | fontFamily = FontFamily.Default, 41 | fontWeight = FontWeight.SemiBold, 42 | fontSize = 22.sp, 43 | lineHeight = 24.sp, 44 | letterSpacing = 0.5.sp 45 | ), 46 | bodyMedium = TextStyle( 47 | fontFamily = FontFamily.Default, 48 | fontWeight = FontWeight.Normal, 49 | fontSize = 15.sp, 50 | lineHeight = 24.sp, 51 | letterSpacing = 0.5.sp 52 | ), 53 | bodySmall = TextStyle( 54 | fontFamily = FontFamily.Default, 55 | fontWeight = FontWeight.Normal, 56 | fontSize = 11.sp, 57 | lineHeight = 24.sp, 58 | letterSpacing = 0.5.sp 59 | ), 60 | 61 | 62 | labelLarge = TextStyle( 63 | fontFamily = FontFamily.Default, 64 | fontWeight = FontWeight.Normal, 65 | fontSize = 20.sp, 66 | lineHeight = 24.sp, 67 | letterSpacing = 0.5.sp 68 | ), 69 | labelMedium = TextStyle( 70 | fontFamily = FontFamily.Default, 71 | fontWeight = FontWeight.Normal, 72 | fontSize = 18.sp, 73 | lineHeight = 24.sp, 74 | letterSpacing = 0.5.sp 75 | ), 76 | labelSmall = TextStyle( 77 | fontFamily = FontFamily.Default, 78 | fontWeight = FontWeight.Normal, 79 | fontSize = 15.sp, 80 | lineHeight = 24.sp, 81 | letterSpacing = 0.5.sp 82 | ) 83 | ) -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/kotlin/utils/OsInfo.kt: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import java.util.* 4 | 5 | 6 | class OsException(msg: String) : Exception(msg) 7 | 8 | @Suppress("EnumEntryName") 9 | enum class OS { 10 | windows, 11 | linux, 12 | unsupported; 13 | } 14 | 15 | fun getOS(): OS { 16 | val os = System.getProperty("os.name").lowercase(Locale.getDefault()) 17 | return when { 18 | os.contains("win") -> OS.windows 19 | 20 | os.contains("nix") || os.contains("nux") || os.contains("aix") -> 21 | OS.linux 22 | 23 | else -> OS.unsupported 24 | } 25 | } -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/kotlin/utils/Resources.kt: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import FState 4 | import androidx.compose.runtime.Composable 5 | import androidx.compose.runtime.remember 6 | import androidx.compose.ui.graphics.painter.Painter 7 | import androidx.compose.ui.platform.LocalDensity 8 | import androidx.compose.ui.res.loadSvgPainter 9 | import androidx.compose.ui.res.useResource 10 | import org.json.JSONObject 11 | import org.json.JSONTokener 12 | 13 | private const val PREFIX_STRING = "strings-" 14 | private const val SUFFIX_STRING = ".json" 15 | 16 | class Resources { 17 | 18 | companion object { 19 | private val _rootJsonObject: JSONObject 20 | 21 | private val language = FState.settings.language.value 22 | 23 | init { 24 | val string = useResource("values/$PREFIX_STRING$language$SUFFIX_STRING") { 25 | it.bufferedReader().readText() 26 | } 27 | _rootJsonObject = JSONTokener(string).nextValue() as JSONObject 28 | } 29 | 30 | fun getString(path: String): String { 31 | return getJsonValue( 32 | path = path, 33 | obj = _rootJsonObject 34 | )!! 35 | } 36 | 37 | @Composable 38 | fun getIcon(id: String): Painter { 39 | val density = LocalDensity.current // to calculate the intrinsic size of vector images (SVG, XML) 40 | return remember { 41 | useResource("drawable/$id.svg") { 42 | loadSvgPainter(it, density) 43 | } 44 | } 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/kotlin/utils/init.kt: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import FState.hControls 4 | import FState.hFans 5 | import FState.hTemps 6 | import FState.iControls 7 | import FState.iFans 8 | import FState.iTemps 9 | import androidx.compose.runtime.Composable 10 | import model.item.BaseI 11 | import model.item.IControl 12 | import model.item.IFan 13 | import model.item.ITemp 14 | import ui.configuration.confDialogs.confNotSaveDialog 15 | import ui.configuration.confDialogs.newConfDialog 16 | import ui.dialogs.errorDialog 17 | 18 | 19 | /** 20 | * init fan and temperature item list, used when there is no config at start. 21 | * Each sensor will be represented by one sensor item. 22 | */ 23 | fun initSensor() { 24 | println("initItem, no config") 25 | 26 | iControls.clear() 27 | hControls.forEach { hControl -> 28 | iControls.add( 29 | IControl( 30 | name = hControl.name, 31 | id = BaseI.getAvailableId( 32 | list = iControls.map { it.id }, 33 | prefix = BaseI.IControlPrefix 34 | ), 35 | controlId = hControl.id 36 | ) 37 | ) 38 | } 39 | 40 | iTemps.clear() 41 | hTemps.forEach { hTemp -> 42 | iTemps.add( 43 | ITemp( 44 | name = hTemp.name, 45 | id = BaseI.getAvailableId( 46 | list = iTemps.map { it.id }, 47 | prefix = BaseI.ITempPrefix 48 | ), 49 | hTempId = hTemp.id 50 | ) 51 | ) 52 | } 53 | 54 | iFans.clear() 55 | hFans.forEach { hFan -> 56 | iFans.add( 57 | IFan( 58 | name = hFan.name, 59 | id = BaseI.getAvailableId( 60 | list = iFans.map { it.id }, 61 | prefix = BaseI.IFanPrefix 62 | ), 63 | hFanId = hFan.id 64 | ) 65 | ) 66 | } 67 | } 68 | 69 | @Composable 70 | fun initDialogs() { 71 | errorDialog() 72 | confNotSaveDialog() 73 | newConfDialog() 74 | } -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/kotlin/utils/json.kt: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import org.json.JSONObject 4 | 5 | 6 | fun getJsonValue(path: String, obj: JSONObject): T? { 7 | val res = getJsonValueRec( 8 | path = path.split("/"), 9 | obj = obj, 10 | index = 0 11 | ) 12 | return if (res == JSONObject.NULL) 13 | null 14 | else 15 | @Suppress("UNCHECKED_CAST") 16 | res as T 17 | } 18 | 19 | fun setJsonValue(path: String, value: Any?, obj: JSONObject): JSONObject { 20 | return setJsonValueRec( 21 | path = path.split("/"), 22 | value = value, 23 | obj = obj, 24 | index = 0 25 | ) 26 | } 27 | 28 | private fun getJsonValueRec(path: List, obj: JSONObject, index: Int): Any? { 29 | val realPath = getRealPath(path[index]) 30 | 31 | return when (index) { 32 | path.lastIndex -> obj.get(realPath) 33 | 34 | else -> getJsonValueRec( 35 | path = path, 36 | obj = obj.getJSONObject(realPath), 37 | index = index + 1 38 | ) 39 | } 40 | } 41 | 42 | private fun setJsonValueRec(path: List, value: Any?, obj: JSONObject, index: Int): JSONObject { 43 | val realPath = getRealPath(path[index]) 44 | 45 | return obj.put( 46 | realPath, 47 | when (index) { 48 | path.lastIndex -> value 49 | 50 | else -> 51 | setJsonValueRec( 52 | path = path, 53 | value = value, 54 | obj = obj.getJSONObject(realPath), 55 | index = index + 1 56 | ) 57 | 58 | } 59 | ) 60 | } 61 | 62 | private fun getRealPath(input: String): String { 63 | return when (input) { 64 | "ct" -> "icon_content_description" 65 | "default" -> "default_value" 66 | else -> input 67 | } 68 | } -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/kotlin/utils/utils.kt: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import androidx.compose.foundation.lazy.LazyListScope 4 | import androidx.compose.foundation.lazy.itemsIndexed 5 | import androidx.compose.runtime.Composable 6 | 7 | 8 | /** 9 | * filter the list with the given predicate 10 | * and create a foreach loop with the previous 11 | * index and the corresponding value 12 | */ 13 | fun filterWithPreviousIndex( 14 | list: List, 15 | predicate: (T) -> Boolean, 16 | forEachFiltered: (Int, T) -> Unit, 17 | ) { 18 | val previousIndexList = mutableListOf() 19 | 20 | for (i in list.indices) { 21 | if (predicate(list[i])) 22 | previousIndexList.add(i) 23 | } 24 | 25 | previousIndexList.forEach { 26 | forEachFiltered(it, list[it]) 27 | } 28 | } 29 | 30 | 31 | fun LazyListScope.filterWithPreviousIndexComposable( 32 | list: List, 33 | predicate: (T) -> Boolean, 34 | content: @Composable (Int, T) -> Unit, 35 | ) { 36 | val previousIndexList = mutableListOf() 37 | 38 | list.filterIndexed { index, t -> 39 | if (predicate(t)) { 40 | previousIndexList.add(index) 41 | true 42 | } else false 43 | }.let { 44 | itemsIndexed(it) { index, value -> 45 | content(previousIndexList[index], value) 46 | } 47 | } 48 | } 49 | 50 | 51 | fun getIndexList( 52 | list: List, 53 | predicate: (T) -> Boolean, 54 | ): List { 55 | 56 | val previousIndexList = mutableListOf() 57 | list.filterIndexed { index, element -> 58 | if (predicate(element)) { 59 | previousIndexList.add(index) 60 | true 61 | } else false 62 | } 63 | return previousIndexList 64 | } 65 | 66 | 67 | -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/resources/drawable/app/toys_fan48.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/resources/drawable/arrow/arrow_back40.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/resources/drawable/arrow/arrow_forward40.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/resources/drawable/arrow/chevron/chevron_left24.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/resources/drawable/arrow/chevron/chevron_left40.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/resources/drawable/arrow/chevron/chevron_right24.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/resources/drawable/arrow/chevron/chevron_right40.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/resources/drawable/arrow/dropDown/arrow_drop_down24.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/resources/drawable/arrow/dropDown/arrow_drop_down40.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/resources/drawable/arrow/dropDown/arrow_drop_up24.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/resources/drawable/arrow/dropDown/arrow_drop_up40.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/resources/drawable/arrow/expand/expand_less24.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/resources/drawable/arrow/expand/expand_less40.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/resources/drawable/arrow/expand/expand_more24.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/resources/drawable/arrow/expand/expand_more40.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/resources/drawable/arrow/notch/line_end_arrow_notch40.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/resources/drawable/arrow/notch/line_start_arrow_notch40.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/resources/drawable/items/alternate_email24.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/resources/drawable/items/horizontal_rule24.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/resources/drawable/items/linear24.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/resources/drawable/items/my_location24.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/resources/drawable/items/psychology24.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/resources/drawable/items/speed24.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/resources/drawable/items/thermometer24.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/resources/drawable/items/thermostat24.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/resources/drawable/items/toys_fan24.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/resources/drawable/select/check24.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/resources/drawable/select/close/close20.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/resources/drawable/select/close/close24.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/resources/drawable/select/close/close40.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/resources/drawable/select/close/close48.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/resources/drawable/select/delete_forever24.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/resources/drawable/select/delete_forever40.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/resources/drawable/select/radio_button_unchecked20.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/resources/drawable/select/radio_button_unchecked24.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/resources/drawable/settings/attach_money24.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/resources/drawable/settings/attach_money40.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/resources/drawable/settings/autorenew24.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/resources/drawable/settings/autorenew40.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/resources/drawable/settings/dark_mode24.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/resources/drawable/settings/dark_mode40.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/resources/drawable/settings/help24.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/resources/drawable/settings/help40.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/resources/drawable/settings/history40.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/resources/drawable/settings/info24.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/resources/drawable/settings/info40.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/resources/drawable/settings/translate24.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/resources/drawable/settings/translate40.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/resources/drawable/settings/update24.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/resources/drawable/sign/minus/remove20.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/resources/drawable/sign/minus/remove24.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/resources/drawable/sign/minus/remove40.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/resources/drawable/sign/minus/remove48.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/resources/drawable/sign/plus/add20.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/resources/drawable/sign/plus/add24.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/resources/drawable/sign/plus/add40.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/resources/drawable/sign/plus/add48.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/resources/drawable/topBar/arrow_forward40.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/resources/drawable/topBar/delete40.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/resources/drawable/topBar/edit40.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/resources/drawable/topBar/error40.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/resources/drawable/topBar/forward40.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/resources/drawable/topBar/forward_media40.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/resources/drawable/topBar/menu40.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/resources/drawable/topBar/save40.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/resources/drawable/topBar/send40.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmMain/resources/drawable/topBar/toys_fan40.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmTest/kotlin/JsonTest.kt: -------------------------------------------------------------------------------- 1 | import org.json.JSONObject 2 | import org.json.JSONWriter 3 | import org.junit.Test 4 | import utils.getJsonValue 5 | 6 | class JsonTest { 7 | @Test 8 | fun test() { 9 | assert(getJsonValue("name", create()) == "myName") 10 | } 11 | 12 | 13 | private fun create(): JSONObject { 14 | val str = StringBuilder() 15 | val writer = JSONWriter(str) 16 | 17 | writer.`object`() 18 | writer.key("name") 19 | writer.value("myName") 20 | writer.endObject() 21 | 22 | return JSONObject(str.toString()) 23 | } 24 | } -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmTest/kotlin/LinearTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | import logicControl.behavior.LinearLogic 4 | import model.HardwareType 5 | import model.hardware.Sensor 6 | import org.junit.Test 7 | 8 | class LinearTest { 9 | 10 | private val baseTemp = Sensor( 11 | libIndex = 0, 12 | libId = "", 13 | libName = "", 14 | type = HardwareType.SensorType.H_S_TEMP, 15 | id = 1, 16 | value = 40 17 | ) 18 | 19 | private val baseLinear = Linear( 20 | hTempId = 1, 21 | minTemp = 20, 22 | maxTemp = 80, 23 | minFanSpeed = 40, 24 | maxFanSpeed = 60 25 | ) 26 | 27 | private val linearLogic = LinearLogic() 28 | 29 | @Test 30 | fun `valueLinear returns null when Linear has no temp sensor ID`() { 31 | assert(linearLogic.getValue(baseLinear.copy(hTempId = null)) == null) 32 | } 33 | 34 | @Test 35 | fun `valueLinear returns min fan speed when temp is below min temp`() { 36 | addAndRemove(baseTemp.copy(value = 10)) 37 | assert(40 == linearLogic.getValue(baseLinear)) 38 | } 39 | 40 | @Test 41 | fun `valueLinear returns max fan speed when temp is above max temp`() { 42 | addAndRemove(baseTemp.copy(value = 90)) 43 | assert(60 == linearLogic.getValue(baseLinear)) 44 | } 45 | 46 | @Test 47 | fun `valueLinear returns correct fan speed when temp is within min and max temp range`() { 48 | addAndRemove(baseTemp) 49 | val a = linearLogic.getValue(baseLinear) 50 | assert(47 == a) 51 | } 52 | 53 | 54 | private val tempList = State.hSensorsList.hTemps 55 | private fun addAndRemove(temp: Sensor) { 56 | if (tempList.size > 0) { 57 | tempList.removeAt(0) 58 | } 59 | tempList.add(0, temp) 60 | } 61 | } 62 | 63 | */ -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmTest/kotlin/SerializationProtoTest.kt: -------------------------------------------------------------------------------- 1 | import proto.SettingsHelper 2 | import kotlin.test.Test 3 | import kotlin.test.assertEquals 4 | 5 | class SerializationProtoTest { 6 | 7 | private val settingsCustom = Settings( 8 | language = Languages.fr, 9 | confId = "confId", 10 | confInfoList = listOf(ConfInfo(id = "confId", name = "name")), 11 | updateDelay = 10, 12 | theme = Themes.light, 13 | firstStart = false, 14 | launchAtStartUp = true, 15 | degree = false, 16 | ) 17 | 18 | private fun makeTest(settings1: Settings) { 19 | val pSettings = SettingsHelper.createPSetting(settings1) 20 | val settings2 = SettingsHelper.parsePSetting(pSettings) 21 | 22 | assertEquals(settings1.language.value, settings2.language.value) 23 | assertEquals(settings1.confId.value, settings2.confId.value) 24 | settings1.confInfoList.forEachIndexed { index, confInfo1 -> 25 | val confInfo2 = settings2.confInfoList[index] 26 | assertEquals(confInfo1.id, confInfo2.id) 27 | assertEquals(confInfo1.name.value, confInfo2.name.value) 28 | } 29 | assertEquals(settings1.updateDelay.value, settings2.updateDelay.value) 30 | assertEquals(settings1.theme.value, settings2.theme.value) 31 | assertEquals(settings1.firstStart.value, settings2.firstStart.value) 32 | assertEquals(settings1.launchAtStartUp.value, settings2.launchAtStartUp.value) 33 | assertEquals(settings1.degree.value, settings2.degree.value) 34 | } 35 | 36 | @Test 37 | fun settingsTest() { 38 | makeTest(Settings()) 39 | 40 | makeTest(settingsCustom) 41 | } 42 | 43 | } -------------------------------------------------------------------------------- /LibreFanControl/app/src/jvmTest/kotlin/TempValue.kt: -------------------------------------------------------------------------------- 1 | import kotlin.random.Random 2 | 3 | class TempValue { 4 | companion object { 5 | private var direction = true 6 | 7 | // emulate natural value 8 | fun newValue(value: Int): Int { 9 | val min = 30 10 | val max = 75 11 | val delta = Random.nextInt(0, 10) 12 | 13 | return if (direction) { 14 | (value + delta).let { 15 | if (it > max) { 16 | direction = false 17 | max 18 | } else it 19 | } 20 | } else { 21 | (value - delta).let { 22 | if (it < min) { 23 | direction = true 24 | min 25 | } else it 26 | } 27 | } 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /LibreFanControl/build.gradle.kts: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | gradlePluginPortal() 4 | maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") 5 | } 6 | dependencies { 7 | classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${project.property("kotlin.version")}") 8 | classpath("org.jetbrains.compose:compose-gradle-plugin:${project.property("compose.version")}") 9 | 10 | classpath("com.google.protobuf:protobuf-gradle-plugin:${project.property("protobuf.plugin.version")}") 11 | } 12 | } -------------------------------------------------------------------------------- /LibreFanControl/gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin.code.style=official 2 | kotlin.mpp.stability.nowarn=true 3 | kotlin.version=1.9.0 4 | compose.version=1.5.0 5 | settings.version=3.2.0 6 | protobuf.version=3.23.4 7 | protobuf.plugin.version=0.9.4 8 | grpc.version=1.57.1 9 | grpc.kotlin.version=1.3.0 10 | app.version=1.0.5 -------------------------------------------------------------------------------- /LibreFanControl/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wiiznokes/LibreFanControl/54072efbc0d808acc2d63bba3ff0e827de81bae7/LibreFanControl/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /LibreFanControl/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /LibreFanControl/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /LibreFanControl/proto/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import com.google.protobuf.gradle.id 2 | import com.google.protobuf.gradle.proto 3 | 4 | plugins { 5 | id("java") 6 | id("idea") 7 | id("com.google.protobuf") 8 | } 9 | 10 | 11 | repositories { 12 | google() 13 | mavenCentral() 14 | } 15 | 16 | dependencies { 17 | implementation("com.google.protobuf:protobuf-kotlin:${project.property("protobuf.version")}") 18 | implementation("io.grpc:grpc-kotlin-stub:${project.property("grpc.kotlin.version")}") 19 | implementation("io.grpc:grpc-protobuf:${project.property("grpc.version")}") 20 | } 21 | 22 | sourceSets { 23 | main { 24 | proto { 25 | srcDir("src") 26 | } 27 | } 28 | } 29 | 30 | 31 | protobuf { 32 | protoc { 33 | artifact = "com.google.protobuf:protoc:${project.property("protobuf.version")}" 34 | } 35 | plugins { 36 | id("grpc") { 37 | artifact = "io.grpc:protoc-gen-grpc-java:${project.property("grpc.version")}" 38 | } 39 | id("grpckt") { 40 | artifact = "io.grpc:protoc-gen-grpc-kotlin:${project.property("grpc.kotlin.version")}:jdk8@jar" 41 | } 42 | } 43 | 44 | generateProtoTasks { 45 | 46 | all().forEach { 47 | ofSourceSet("main") 48 | 49 | 50 | it.plugins { 51 | id("grpc") {} 52 | id("grpckt") {} 53 | } 54 | it.builtins { 55 | id("kotlin") {} 56 | } 57 | } 58 | } 59 | } 60 | 61 | 62 | tasks.register("generateAllProto", DefaultTask::class.java) { 63 | dependsOn("cleanCopiedFiles") 64 | dependsOn("generateProto") 65 | dependsOn("copyGeneratedFiles") 66 | 67 | mustRunAfter("cleanCopiedFiles") 68 | mustRunAfter("generateProto") 69 | } 70 | 71 | /** 72 | * copy of generated files 73 | */ 74 | 75 | data class CopyInfo( 76 | val srcDirs: List, 77 | val destDir: File, 78 | val removeDir: File, 79 | ) 80 | 81 | val infoList = listOf( 82 | CopyInfo( 83 | srcDirs = listOf( 84 | file("build/generated/source/proto/main/java"), 85 | file("build/generated/source/proto/main/grpc") 86 | ), 87 | destDir = file("../app/src/jvmMain/java"), 88 | removeDir = file("../app/src/jvmMain/java/proto/generated") 89 | ), 90 | CopyInfo( 91 | srcDirs = listOf( 92 | file("build/generated/source/proto/main/kotlin"), 93 | file("build/generated/source/proto/main/grpckt") 94 | ), 95 | destDir = file("../app/src/jvmMain/kotlin"), 96 | removeDir = file("../app/src/jvmMain/kotlin/proto/generated") 97 | ) 98 | ) 99 | 100 | tasks.register("copyGeneratedFiles") { 101 | doLast { 102 | infoList.forEach { 103 | it.srcDirs.forEach { srcDir -> 104 | copy { 105 | from(srcDir) 106 | into(it.destDir) 107 | } 108 | } 109 | } 110 | } 111 | } 112 | 113 | tasks.register("cleanCopiedFiles") { 114 | doLast { 115 | infoList.forEach { 116 | delete(it.removeDir) 117 | } 118 | } 119 | } -------------------------------------------------------------------------------- /LibreFanControl/proto/src/proto/conf.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package proto; 3 | 4 | option java_multiple_files = true; 5 | 6 | 7 | option csharp_namespace = "Proto.Generated.PConf"; 8 | option java_package = "proto.generated.pConf"; 9 | 10 | 11 | 12 | import "proto/settings.proto"; 13 | 14 | 15 | 16 | message PConf { 17 | 18 | PConfInfo PConfInfo = 1; 19 | 20 | POsTypes POsType = 2; 21 | 22 | repeated PIControl PIControls = 3; 23 | repeated PIBehavior PIBehaviors = 4; 24 | repeated PITemp PITemps = 5; 25 | repeated PIFan PIFans = 6; 26 | } 27 | 28 | 29 | 30 | /* 31 | type *********************** 32 | */ 33 | 34 | 35 | enum POsTypes { 36 | WINDOWS = 0; 37 | LINUX = 1; 38 | } 39 | 40 | enum PIControlTypes { 41 | I_C_FAN = 0; 42 | } 43 | 44 | enum PIBehaviorTypes { 45 | I_B_FLAT = 0; 46 | I_B_LINEAR = 1; 47 | I_B_TARGET = 2; 48 | } 49 | 50 | enum PITempTypes { 51 | I_S_TEMP = 0; 52 | I_S_CUSTOM_TEMP = 1; 53 | } 54 | 55 | enum PIFanTypes { 56 | I_S_FAN = 0; 57 | } 58 | 59 | /* 60 | ************************* 61 | */ 62 | 63 | 64 | 65 | message PIControl { 66 | string PName = 1; 67 | string PId = 2; 68 | PIControlTypes PType = 3; 69 | NullableId PIBehaviorId = 4; 70 | bool PIsAuto = 5; 71 | NullableId PHControlId = 6; 72 | } 73 | 74 | 75 | /* 76 | Behavior ********************** 77 | */ 78 | 79 | message PIBehavior { 80 | string PName = 1; 81 | string PId = 2; 82 | PIBehaviorTypes PType = 3; 83 | 84 | oneof kind{ 85 | PFlat PFlat = 4; 86 | PLinear PLinear = 5; 87 | PTarget PTarget = 6; 88 | } 89 | } 90 | 91 | message PFlat { 92 | int32 PValue = 1; 93 | } 94 | 95 | 96 | message PLinear { 97 | NullableId PTempId = 1; 98 | 99 | int32 PMinTemp = 2; 100 | int32 PMaxTemp = 3; 101 | int32 PMinFanSpeed = 4; 102 | int32 PMaxFanSpeed = 5; 103 | } 104 | 105 | 106 | message PTarget { 107 | NullableId PTempId = 1; 108 | 109 | int32 PIdleTemp = 2; 110 | int32 PLoadTemp = 3; 111 | int32 PIdleFanSpeed = 4; 112 | int32 PLoadFanSpeed = 5; 113 | } 114 | 115 | /* 116 | *********************** 117 | */ 118 | 119 | 120 | /* 121 | Item Sensors 122 | */ 123 | 124 | 125 | 126 | message PITemp { 127 | string PName = 1; 128 | string PId = 2; 129 | PITempTypes PType = 3; 130 | oneof kind { 131 | PISimpleTemp PISimpleTemp = 4; 132 | PIcustomTemp PICustomTemp = 5; 133 | } 134 | } 135 | 136 | 137 | message PISimpleTemp { 138 | NullableId PHTempId = 1; 139 | } 140 | 141 | 142 | message PIcustomTemp { 143 | PCustomTempTypes PType = 1; 144 | repeated string PHTempIds = 2; 145 | } 146 | 147 | enum PCustomTempTypes { 148 | AVERAGE = 0; 149 | MAX = 1; 150 | MIN = 2; 151 | } 152 | 153 | 154 | 155 | message PIFan { 156 | string PName = 1; 157 | string PId = 2; 158 | PIFanTypes PType = 3; 159 | 160 | NullableId PHFanId = 4; 161 | } -------------------------------------------------------------------------------- /LibreFanControl/proto/src/proto/crossApi.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package proto; 3 | 4 | import "google/protobuf/empty.proto"; 5 | 6 | option java_multiple_files = true; 7 | 8 | 9 | option csharp_namespace = "Proto.Generated.PCrossApi"; 10 | option java_package = "proto.generated.pCrossApi"; 11 | 12 | 13 | import "proto/conf.proto"; 14 | import "proto/settings.proto"; 15 | 16 | /** 17 | API for communicate with the service 18 | */ 19 | 20 | 21 | service PCrossApi { 22 | rpc POpen (google.protobuf.Empty) returns (POk); 23 | 24 | rpc PGetHardware (PHardwareTypeMessage) returns (PHardwareList); 25 | 26 | rpc PStartStream (google.protobuf.Empty) returns (stream PUpdateList); 27 | rpc PCloseStream (google.protobuf.Empty) returns (google.protobuf.Empty); 28 | 29 | rpc PSettingsAndConfChange (google.protobuf.Empty) returns (POk); 30 | rpc PSettingsChange (google.protobuf.Empty) returns (POk); 31 | } 32 | 33 | 34 | 35 | 36 | message POk { 37 | bool PIsSuccess = 1; 38 | } 39 | 40 | 41 | enum PHardwareType { 42 | CONTROL = 0; 43 | TEMP = 1; 44 | FAN = 2; 45 | } 46 | 47 | message PHardwareTypeMessage { 48 | PHardwareType PType = 1; 49 | } 50 | 51 | 52 | 53 | 54 | /** 55 | structure for fetch sensors info (controls/temps/fans) 56 | */ 57 | 58 | message PHardware { 59 | string PName = 1; 60 | string PId = 2; 61 | } 62 | 63 | message PHardwareList { 64 | repeated PHardware PHardwares = 1; 65 | } 66 | 67 | /** 68 | structure for fetch sensors value (controls/temps/fans) 69 | */ 70 | 71 | message PUpdate { 72 | int32 PIndex = 1; 73 | int32 PValue = 2; 74 | } 75 | 76 | message PUpdateList { 77 | PHardwareType PType = 1; 78 | repeated PUpdate PUpdates = 2; 79 | } -------------------------------------------------------------------------------- /LibreFanControl/proto/src/proto/settings.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package proto; 3 | 4 | option java_multiple_files = true; 5 | 6 | option csharp_namespace = "Proto.Generated.PSettings"; 7 | option java_package = "proto.generated.pSettings"; 8 | 9 | 10 | import "google/protobuf/struct.proto"; 11 | 12 | 13 | 14 | 15 | enum PLanguages { 16 | EN = 0; 17 | FR = 1; 18 | ZH_CN = 2; 19 | } 20 | 21 | enum PThemes { 22 | DARK = 0; 23 | LIGHT = 1; 24 | SYSTEM = 2; 25 | } 26 | 27 | message PConfInfo { 28 | string PId = 1; 29 | string PName = 2; 30 | } 31 | 32 | message NullableId { 33 | oneof kind { 34 | google.protobuf.NullValue null = 1; 35 | string PId = 2; 36 | } 37 | } 38 | 39 | 40 | message PSettings { 41 | PLanguages PLanguage = 1; 42 | NullableId PConfId = 2; 43 | repeated PConfInfo PConfInfos = 3; 44 | int32 PUpdateDelay = 4; 45 | PThemes PTheme = 5; 46 | bool PFirstStart = 6; 47 | bool PLaunchAtStartUp = 7; 48 | bool PDegree = 8; 49 | int32 PValueDisableControl = 9; 50 | string PVersionInstalled = 10; 51 | } 52 | -------------------------------------------------------------------------------- /LibreFanControl/scripts/remove_icons_suffixes.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | remove all suffix of the drawable directory 3 | #> 4 | 5 | 6 | $drawablePath = $PSScriptRoot + "/../app/src/jvmMain/resources/drawable" 7 | Set-Location $drawablePath 8 | 9 | # Get a list of files 10 | $files = Get-ChildItem -Recurse 11 | 12 | # Get the last number in a string 13 | $regex = '(\d+)(?=\.)' 14 | 15 | # Loop through each file 16 | foreach ($file in $files) 17 | { 18 | # Split the file name into two parts 19 | $parts = $file -split '_FILL' 20 | 21 | # If the array contains two elements, it means the pattern was found 22 | if ($parts.Count -eq 2) 23 | { 24 | $size = [regex]::Match($parts[1], $regex).Value 25 | 26 | # Reassemble the file name with the .svg extension 27 | $newName = $parts[0] + $size + '.svg' 28 | 29 | # Rename the file 30 | Rename-Item -Path $file.FullName -NewName $newName -Force 31 | } 32 | } 33 | 34 | # Return to the starting directory 35 | Set-Location $PSScriptRoot -------------------------------------------------------------------------------- /LibreFanControl/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = "LibreFanControl" 2 | 3 | include 'proto' 4 | include 'app' 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LibreFanControl 2 | 3 | ⚠️ 4 | 5 | ## This project is deprecated. The new version is here: https://github.com/wiiznokes/fan-control 6 | 7 |
8 |

Translation

9 | 10 | - [简体中文](./assets/zh_CN/README_zh-CN.md) 11 | 12 |
13 | 14 | 15 | - Open source 16 | - Service very low on resource (~30mb) 17 | - Display sensors data on real time 18 | - Control fans based on custom behaviors 19 | - Save configuration 20 | - Multiplatform (Linux/Windows) 21 | 22 | 23 | ![mainPageV1](https://github.com/wiiznokes/LibreFanControl/assets/78230769/543af76c-137c-456d-a04e-8ebfed323178) 24 | 25 | 26 | 27 | ## Requirements 28 | You will need some component of [dotnet 7](https://dotnet.microsoft.com/en-us/download/dotnet/7.0) 29 | 30 | - `ASP.NET Core Runtime` 31 | - `.NET Runtime` 32 | 33 | You will need to run the app with admin privilege on both Linux and Windows. 34 | 35 | If you have any troubles, check out the [FAQ](./FAQ.md). 36 | 37 | **Issues, enhancement requests and PR are welcomed!** 38 | 39 | 40 | ## Configurations files 41 | 42 | #### Windows 43 | - `C:\ProgramData\LibreFanControl` 44 | #### Linux 45 | - `/etc/LibreFanControl` 46 | 47 | On Linux, maybe you will need to download `lm-sensors` and execute `sensors-detect`. 48 | 49 | ## Plans (not in this order) 50 | 51 | - [x] Support Linux 52 | - [ ] Use type safe strings and icons ([moko-resources](https://github.com/icerockdev/moko-resources) ?) 53 | - [ ] Add a system to automatically receive updates 54 | - [ ] Compile with container ([docker](https://docs.docker.com/), [podman](https://podman.io/)) 55 | - [ ] Implement settings (info, help) 56 | - [ ] Add graph behavior (abscissa -> temp, ordinate -> fan speed) 57 | - [ ] Write an intelligent program to bind controls to their fans, and make a nice first config 58 | - [ ] Support [Flatpak](https://docs.flatpak.org/en/latest/first-build.html) 59 | - [ ] Upgrade service (packaging, tray icon with actions) 60 | 61 | ## Library used 62 | 63 | ### UI 64 | - [Compose Multiplatform Desktop](https://www.jetbrains.com/lp/compose-mpp/) 65 | ### SENSORS 66 | ##### Windows 67 | - [LibreHardwareMonitor](https://github.com/LibreHardwareMonitor/LibreHardwareMonitor) 68 | ##### Linux 69 | - [lm-sensors](https://github.com/lm-sensors/lm-sensors) 70 | 71 | 72 | ## Licence 73 | 74 | Can be discuss. I just want my code keep being open source. 75 | -------------------------------------------------------------------------------- /assets/mainPageV0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wiiznokes/LibreFanControl/54072efbc0d808acc2d63bba3ff0e827de81bae7/assets/mainPageV0.png -------------------------------------------------------------------------------- /assets/mainPageV1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wiiznokes/LibreFanControl/54072efbc0d808acc2d63bba3ff0e827de81bae7/assets/mainPageV1.png -------------------------------------------------------------------------------- /assets/zh_CN/FAQ_zh-CN.md: -------------------------------------------------------------------------------- 1 | # LibreFanControl FAQ 2 | 3 | 4 | > 什么是 `Disable control value` ? 5 | 6 | 这是一个只会在 Linux 上有效果的设置。风扇控制可以有多种模式。 `LibreFanControl` 将他设为手动模式,但当禁用手动控制时,用户可以选择他们期望的控制模式。 7 | 8 | 举个例子,这是作者的芯片的驱动[网站](https://www.kernel.org/doc/html/next/hwmon/nct6775.html). 9 | ``` 10 | pwm[1-7]_enable 11 | 这个文件控制着 风扇/温度 控件的模式: 12 | 0 禁用风扇控制 (风扇被设为满速运行) 13 | 1 手动模式, 向 pwm[0-5] 写入0~255的任何值 14 | 2 "热巡航" 模式 15 | 3 "风扇速度巡航" 模式 16 | 4 "智能风扇 III" 模式 (仅NCT6775F) 17 | 5 "智能风扇 IV" 模式 18 | ``` 19 | 20 | > 在Windows上升级时报错 21 | 22 | 试着用`任务管理器`关闭服务`LibreFanControlService`,随后启动`.msi`安装包。选择`install or repair`选项 23 | 24 | > 具体怎么在Linux上安装 25 | 26 | [看这里](./INSTALL_zh-CN.md)。 27 | -------------------------------------------------------------------------------- /assets/zh_CN/INSTALL_zh-CN.md: -------------------------------------------------------------------------------- 1 | # 在Linux上安装 2 | 3 | ### 需求 4 | 使用**你的Linux发行版的**包管理器安装命令: 5 | - Debian/Ubuntu : `sudo apt install <包名>` 6 | - Arch Linux : `sudo pacman -S <包名>` 7 | - Centos/Redhat/Fedora : `sudo dnf install <包名>` 8 | 9 | 将下文`(安装命令)`替换为对应命令 10 | 11 | ### 1. 打开终端,切换到压缩包所在目录,将`LibreFanControl`解压到你想安装的地方,如 `/opt` 12 | (确保你有这个目录的读/写权限) 13 | ``` 14 | # e.g. 15 | tar -xzf LibreFanControl.tar.gz -C /opt 16 | ``` 17 | 注:如果你以前安装过此应用,请先给卸载(删除)了: 18 | ``` 19 | # e.g. 20 | rm -rf /opt/LibreFanControl 21 | ``` 22 | 23 | ### 2. 安装依赖 24 | ``` 25 | (安装命令) aspnetcore-runtime-7.0 26 | ``` 27 | 28 | ### 3. (optional) 安装 `lm_sensors` 并执行 `sensors-detect`. 29 | ``` 30 | (安装命令) lm_sensors 31 | ``` 32 | ``` 33 | sudo sensors-detect 34 | ``` 35 | 这将生成一个能告诉`LibreFanControl`电脑上有哪些传感器、如何读取它们 的配置文件 36 | 它将自动寻找需要加载的模块(驱动程序) 37 | 对作者个人来说,他执行`sensors-detect`时询问全选了`YES`并且无逝发生。 38 | 39 | 提示: 40 | - 英文大写的回答(`YES`)是默认选项 41 | - 作者在他电脑上做了这步后,他能检测到的传感器从三个到了25个。 42 | 43 | ### 4. 好了,你可以运行安装在你选的目录的软件了: 44 | ``` 45 | sudo /opt/LibreFanControl/bin/LibreFanControl 46 | ``` 47 | -------------------------------------------------------------------------------- /assets/zh_CN/README_zh-CN.md: -------------------------------------------------------------------------------- 1 | # LibreFanControl 2 | 3 | [English](../../README.md) README. 4 | 5 | - 开源 6 | - 服务资源占用小 (约30mb) 7 | - 实时显示传感器数据 8 | - 基于 自定义行为 来控制风扇 9 | - 保存配置 10 | - 多平台支持(Linux/Windows) 11 | 12 | 13 | ![mainPageV1](https://github.com/wiiznokes/LibreFanControl/assets/78230769/543af76c-137c-456d-a04e-8ebfed323178) 14 | 15 | 16 | 17 | ## 需求 18 | 你需要 [.NET 7](https://dotnet.microsoft.com/en-us/download/dotnet/7.0) 的一些组件: 19 | 20 | - `ASP.NET Core Runtime` 21 | - `.NET Runtime` 22 | 23 | 在 `Windows` 和 `Linux` 上,你都需要以管理员权限运行此应用。 24 | 25 | 如果有其它问题,请去往 [常见问题 - FAQ](./FAQ_zh-CN.md) 。 26 | 27 | **欢迎提 Issues,功能建议 和 PR!** 28 | 29 | 30 | ## 配置文件 31 | 32 | #### Windows 33 | - `C:\ProgramData\LibreFanControl` 34 | #### Linux 35 | - `/etc/LibreFanControl` 36 | 37 | 在Linux上,你也许需要下载`libsensors`并执行 `sensors-detect`。 38 | 39 | 40 | ## 构建 41 | - 在Windows上,如果你想构建这个项目,你只需要 [.NET 7](https://dotnet.microsoft.com/en-us/download/dotnet/7.0) 的`SDK`。 42 | - 在Linux上,你需要一个[JDK](https://jdk.java.net/java-se-ri/17). 43 | 44 | 构建所需命令可以在 `CI` 目录里找到。 45 | 46 | 47 | ## 未来计划 (无顺序) 48 | 49 | - [x] 支持 Linux 50 | - [ ] 使用类型安全字符串和图标 ([moko-resources](https://github.com/icerockdev/moko-resources) ?) 51 | - [ ] 添加自动接收更新的系统 52 | - [ ] 用容器编译 ([docker](https://docs.docker.com/)) 53 | - [ ] 完全实现 `设置` (信息,帮助) 54 | - [ ] 添加「图表」行为 (横坐标 -> 温度,纵坐标 -> 风扇速度) 55 | - [ ] 自动将控制绑定到对应风扇速度的程序,并预写一个好的初始配置。 56 | - [ ] 支持 [Flatpak](https://docs.flatpak.org/en/latest/first-build.html) 57 | 58 | ## 使用的库 59 | 60 | ### UI 61 | - [Compose Multiplatform Desktop](https://www.jetbrains.com/lp/compose-mpp/) 62 | - [setting-sliding-windows](https://github.com/wiiznokes/setting-sliding-windows) 63 | ### 传感器 64 | ##### Windows 65 | - [LibreHardwareMonitor](https://github.com/LibreHardwareMonitor/LibreHardwareMonitor) 66 | ##### Linux 67 | - [lm-sensors](https://github.com/lm-sensors/lm-sensors) 68 | --------------------------------------------------------------------------------