├── .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#12controlLib#1)
6 | control lib2
7 | iControl#2" iFlat#12 7
8 | control lib3
9 | iControl#3" iLinear#12controlLib#37
10 | control lib4
11 | iControl#4" iTarget#12controlLib#45
12 |
13 | Control #1
14 | iControl#5" iTarget#32controlLib#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 | 
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 | 
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 |
--------------------------------------------------------------------------------