├── backend ├── Makefile └── sh_tools.sh ├── .shellcheckrc ├── src ├── types │ ├── index.ts │ ├── fans.ts │ ├── global.d.ts │ └── cpu.ts ├── i18n │ ├── index.ts │ ├── localization.ts │ ├── localizeMap.ts │ ├── tchinese.json │ ├── schinese.json │ ├── koreana.json │ ├── japanese.json │ ├── thai.json │ ├── english.json │ ├── german.json │ ├── italian.json │ ├── french.json │ └── bulgarian.json ├── tab │ ├── index.ts │ ├── tabCpu.tsx │ ├── tabGpu.tsx │ ├── tabMore.tsx │ ├── tabFans.tsx │ └── tabPower.tsx ├── components │ ├── index.ts │ ├── actionButtonItem.tsx │ ├── SlowSliderField.tsx │ ├── customTDP.tsx │ ├── fanCanvas.tsx │ ├── power.tsx │ ├── more.tsx │ └── settings.tsx ├── util │ ├── index.ts │ ├── version.ts │ ├── timeout.ts │ ├── logger.ts │ ├── steamUtils.tsx │ ├── enum.ts │ └── position.ts └── index.tsx ├── bin └── ryzenadj ├── assets ├── screenshot.jpg ├── 20230107182755_1.jpg ├── 20230107182852_1.jpg ├── 20230108234559_1.jpg ├── 20230110153822_1.jpg ├── 20230111054307_1.jpg └── 20230319145542_1.jpg ├── py_modules ├── portio.so ├── fan_config │ ├── ec │ │ ├── gpd_win4.yml │ │ ├── gpd_winmax2.yml │ │ ├── gpd_win4_ver1.yml │ │ ├── ayaneo_air.yml │ │ ├── gpd_winmini.yml │ │ ├── aokzoe_a.yml │ │ ├── ayaneo_2.yml │ │ ├── ayaneo_airplus.yml │ │ ├── onexplayer2.yml │ │ └── onexplayer_mini.yml │ ├── hwmon │ │ ├── steamdeck.yml │ │ ├── aynec.yml │ │ ├── gpdfan.yml │ │ ├── oxp_ec.yml │ │ ├── oxpec.yml │ │ ├── msi_wmi.yml │ │ ├── msi_ec.yml │ │ ├── asus_orig.yml │ │ └── asus.yml │ └── schema │ │ ├── ec.json │ │ └── hwmon.json ├── conf_manager.py ├── logging_handler.py ├── sysInfo.py ├── utils │ └── __init__.py ├── tt.sh ├── update.py ├── cpu.py ├── ec.py ├── inotify.py └── config.py ├── .gitmodules ├── reload.sh ├── plugin.json ├── rollup.config.js ├── tag_release.sh ├── .vscode └── settings.json ├── .gitignore ├── tsconfig.json ├── README.md ├── package.json ├── LICENSE ├── README_en.md ├── install.sh ├── .cursor └── rules │ └── riper5.mdc ├── .github └── workflows │ └── release.yml ├── decky.pyi ├── Makefile └── main.py /backend/Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | chmod +x sh_tools.sh -------------------------------------------------------------------------------- /.shellcheckrc: -------------------------------------------------------------------------------- 1 | disable=SC1090,SC1091,SC2086,SC2034,SC2148 -------------------------------------------------------------------------------- /src/types/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./cpu"; 2 | export * from "./fans"; -------------------------------------------------------------------------------- /bin/ryzenadj: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aarron-lee/PowerControl/HEAD/bin/ryzenadj -------------------------------------------------------------------------------- /src/i18n/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./localization" 2 | export * from "./localizeMap" -------------------------------------------------------------------------------- /assets/screenshot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aarron-lee/PowerControl/HEAD/assets/screenshot.jpg -------------------------------------------------------------------------------- /py_modules/portio.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aarron-lee/PowerControl/HEAD/py_modules/portio.so -------------------------------------------------------------------------------- /assets/20230107182755_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aarron-lee/PowerControl/HEAD/assets/20230107182755_1.jpg -------------------------------------------------------------------------------- /assets/20230107182852_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aarron-lee/PowerControl/HEAD/assets/20230107182852_1.jpg -------------------------------------------------------------------------------- /assets/20230108234559_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aarron-lee/PowerControl/HEAD/assets/20230108234559_1.jpg -------------------------------------------------------------------------------- /assets/20230110153822_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aarron-lee/PowerControl/HEAD/assets/20230110153822_1.jpg -------------------------------------------------------------------------------- /assets/20230111054307_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aarron-lee/PowerControl/HEAD/assets/20230111054307_1.jpg -------------------------------------------------------------------------------- /assets/20230319145542_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aarron-lee/PowerControl/HEAD/assets/20230319145542_1.jpg -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "submodule/RyzenAdj"] 2 | path = submodule/RyzenAdj 3 | url = https://github.com/FlyGoat/RyzenAdj.git 4 | -------------------------------------------------------------------------------- /src/tab/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./tabCpu"; 2 | export * from "./tabGpu"; 3 | export * from "./tabPower"; 4 | export * from "./tabMore"; 5 | export * from "./tabFans"; -------------------------------------------------------------------------------- /src/types/fans.ts: -------------------------------------------------------------------------------- 1 | // Fan config type 2 | export interface FanConfig { 3 | fan_max_rpm?: number; 4 | fan_name?: string; 5 | [key: string]: unknown; 6 | } 7 | -------------------------------------------------------------------------------- /src/tab/tabCpu.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from "react"; 2 | import { CPUComponent } from "../components"; 3 | 4 | export const TabCpu: FC = () => { 5 | return ; 6 | }; 7 | -------------------------------------------------------------------------------- /src/tab/tabGpu.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from "react"; 2 | import { GPUComponent } from "../components"; 3 | 4 | export const TabGpu: FC = () => { 5 | return ; 6 | }; 7 | -------------------------------------------------------------------------------- /src/tab/tabMore.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from "react"; 2 | import { MoreComponent } from "../components"; 3 | 4 | export const TabMore: FC = () => { 5 | return ; 6 | }; 7 | -------------------------------------------------------------------------------- /src/tab/tabFans.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from "react"; 2 | import { FANComponent } from "../components/fan"; 3 | 4 | export const TabFans: FC = () => { 5 | return ; 6 | }; 7 | -------------------------------------------------------------------------------- /src/tab/tabPower.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from "react"; 2 | import { PowerComponent } from "../components"; 3 | 4 | export const TabPower: FC = () => { 5 | return ; 6 | }; 7 | -------------------------------------------------------------------------------- /src/types/global.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | declare global { 4 | namespace JSX { 5 | interface IntrinsicElements { 6 | [elemName: string]: any; 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /reload.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | pnpm run build 4 | sudo rm -r /home/$USER/homebrew/plugins/PowerControl/ 5 | sudo cp -r /home/$USER/Development/PowerControl/ ~/homebrew/plugins/ 6 | sudo systemctl restart plugin_loader.service 7 | -------------------------------------------------------------------------------- /plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "PowerControl", 3 | "author": "yxx", 4 | "flags": ["root"], 5 | "api_version": 1, 6 | "publish": { 7 | "tags": ["root"], 8 | "description": "PowerControl plugin.", 9 | "image": "https://opengraph.githubassets.com/1/SteamDeckHomebrew/PluginLoader" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /py_modules/fan_config/ec/gpd_win4.yml: -------------------------------------------------------------------------------- 1 | product_name: 2 | - G1618-04 3 | fans: 4 | - ram_reg_addr: 0x2E 5 | ram_reg_data: 0x2F 6 | ram_manual_offset: 0xC311 7 | ram_rpmwrite_offset: 0xC311 8 | ram_rpmread_offset: 0x880 9 | ram_rpmread_length: 2 10 | 11 | rpm_write_max: 127 12 | rpm_value_max: 4968 13 | -------------------------------------------------------------------------------- /py_modules/fan_config/ec/gpd_winmax2.yml: -------------------------------------------------------------------------------- 1 | product_name: 2 | - G1619-04 3 | fans: 4 | - ram_reg_addr: 0x4E 5 | ram_reg_data: 0x4F 6 | ram_manual_offset: 0x275 7 | ram_rpmwrite_offset: 0x1809 8 | ram_rpmread_offset: 0x218 9 | ram_rpmread_length: 2 10 | 11 | rpm_write_max: 184 12 | rpm_value_max: 4968 13 | -------------------------------------------------------------------------------- /src/components/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./SlowSliderField" 2 | export * from "./gpu" 3 | export * from "./cpu" 4 | export * from "./settings" 5 | export * from "./fan" 6 | export * from "./fanCanvas" 7 | export * from "./more" 8 | export * from "./customTDP" 9 | export * from "./power" 10 | export * from "./actionButtonItem" 11 | 12 | -------------------------------------------------------------------------------- /src/types/cpu.ts: -------------------------------------------------------------------------------- 1 | export interface CPUCoreTypeInfo { 2 | count: number; 3 | cpus: number[]; 4 | max_freq_khz: number; 5 | min_freq_khz: number; 6 | } 7 | 8 | export interface CPUCoreInfo { 9 | is_heterogeneous: boolean; 10 | vendor: string; 11 | architecture_summary: string; 12 | core_types: Record; 13 | } -------------------------------------------------------------------------------- /src/util/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./backend" 2 | export * from "./pluginMain" 3 | export * from "./settings" 4 | export * from "./position" 5 | export * from "./steamClient" 6 | export * from "./enum" 7 | export * from "./patch" 8 | export * from "./steamUtils" 9 | export * from "./version" 10 | export * from "./logger" 11 | export * from "./timeout" -------------------------------------------------------------------------------- /py_modules/fan_config/ec/gpd_win4_ver1.yml: -------------------------------------------------------------------------------- 1 | product_name: 2 | - G1618-04 3 | product_version: 4 | - Ver. 1.0 5 | - Ver.1.0 6 | fans: 7 | - ram_reg_addr: 0x4E 8 | ram_reg_data: 0x4F 9 | ram_manual_offset: 0x275 10 | ram_rpmwrite_offset: 0x1809 11 | ram_rpmread_offset: 0x218 12 | ram_rpmread_length: 2 13 | 14 | rpm_write_max: 184 15 | rpm_value_max: 4968 16 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import deckyPlugin from "@decky/rollup"; 2 | import { mergeAndConcat } from "merge-anything"; 3 | 4 | const userOptions = { 5 | // Add your extra Rollup options here 6 | treeshake: { 7 | preset: "recommended", 8 | }, 9 | }; 10 | 11 | // must be merged in the order of default options first, then user options 12 | export default mergeAndConcat(deckyPlugin(), userOptions); 13 | -------------------------------------------------------------------------------- /tag_release.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/bash 2 | 3 | git checkout main 4 | 5 | if [ "$EUID" -eq 0 ] 6 | then echo "Please do not run as root" 7 | exit 8 | fi 9 | 10 | NEW_TAG=$1 11 | 12 | sed -i "s/version\": \"[0-9]\+\.[0-9]\+\.[0-9]\+\"/version\": \"$NEW_TAG\"/" ./package.json 13 | 14 | git add ./package.json 15 | 16 | git commit -m "Release version $NEW_TAG" 17 | 18 | git tag v$NEW_TAG 19 | 20 | git push 21 | 22 | git push --tags -------------------------------------------------------------------------------- /py_modules/fan_config/ec/ayaneo_air.yml: -------------------------------------------------------------------------------- 1 | product_name: 2 | - AIR 3 | - AIR Pro 4 | - AIR 1S 5 | - AIR 1S Limited 6 | fans: 7 | - manual_offset: 0x4a 8 | rpmwrite_offset: 0x4b 9 | rpmread_offset: 0x76 10 | 11 | ram_reg_addr: 0x4E 12 | ram_reg_data: 0x4F 13 | ram_manual_offset: 0x44a 14 | ram_rpmwrite_offset: 0x44b 15 | ram_rpmread_offset: 0x1809 16 | ram_rpmread_length: 0 17 | 18 | rpm_write_max: 255 19 | rpm_value_max: 5811 20 | -------------------------------------------------------------------------------- /py_modules/fan_config/ec/gpd_winmini.yml: -------------------------------------------------------------------------------- 1 | # ECIO 或者 ECRAM 都可以 2 | # 内核补丁使用的是 ECIO 的方式 3 | product_name: 4 | - G1617-01 5 | - G1617-02 6 | fans: 7 | - ram_reg_addr: 0x4E 8 | ram_reg_data: 0x4F 9 | ram_manual_offset: 0x47A 10 | ram_rpmwrite_offset: 0x47A 11 | ram_rpmread_offset: 0x478 12 | ram_rpmread_length: 2 13 | 14 | manual_offset: 0x7a 15 | rpmwrite_offset: 0x7a 16 | rpmread_offset: 0x78 17 | 18 | rpm_write_max: 244 19 | rpm_value_max: 6700 20 | -------------------------------------------------------------------------------- /py_modules/fan_config/ec/aokzoe_a.yml: -------------------------------------------------------------------------------- 1 | product_name: 2 | - AOKZOE A1 Pro 3 | - AOKZOE A2 Pro 4 | fans: 5 | - manual_offset: 0x4a # 风扇自动控制ec地址 6 | rpmwrite_offset: 0x4b # 风扇写入转速ec地址 7 | rpmread_offset: 0x76 # 风扇读取转速ec地址 8 | 9 | ram_reg_addr: 0x4E 10 | ram_reg_data: 0x4F 11 | ram_manual_offset: 0x44a 12 | ram_rpmwrite_offset: 0x44b 13 | ram_rpmread_offset: 0x1809 14 | ram_rpmread_length: 0 15 | rpm_write_max: 184 16 | rpm_value_max: 5000 17 | 18 | -------------------------------------------------------------------------------- /py_modules/fan_config/ec/ayaneo_2.yml: -------------------------------------------------------------------------------- 1 | product_name: 2 | - AYANEO 2 3 | - AYANEO 2S 4 | - GEEK 5 | - GEEK 1S 6 | - FLIP KD 7 | - FLIP DS 8 | fans: 9 | - manual_offset: 0x4a 10 | rpmwrite_offset: 0x4b 11 | rpmread_offset: 0x76 12 | 13 | ram_reg_addr: 0x4E 14 | ram_reg_data: 0x4F 15 | ram_manual_offset: 0x44a 16 | ram_rpmwrite_offset: 0x44b 17 | ram_rpmread_offset: 0x1809 18 | ram_rpmread_length: 0 19 | 20 | rpm_write_max: 255 21 | rpm_value_max: 5530 22 | -------------------------------------------------------------------------------- /py_modules/fan_config/hwmon/steamdeck.yml: -------------------------------------------------------------------------------- 1 | hwmon_name: steamdeck_hwmon 2 | 3 | fans: 4 | - fan_name: Fan 5 | pwm_mode: 0 6 | 7 | pwm_enable: 8 | manual_value: 1 9 | auto_value: 0 10 | pwm_enable_path: fan1_target 11 | 12 | pwm_write: 13 | pwm_write_max: 14 | default: 7300 15 | pwm_write_path: fan1_target 16 | 17 | pwm_input: 18 | hwmon_label: steamdeck_hwmon 19 | pwm_read_path: fan1_input 20 | pwm_read_max: 7309 21 | 22 | temp_mode: 0 23 | -------------------------------------------------------------------------------- /py_modules/fan_config/hwmon/aynec.yml: -------------------------------------------------------------------------------- 1 | hwmon_name: aynec 2 | 3 | fans: 4 | - fan_name: Fan 5 | pwm_mode: 0 6 | 7 | pwm_enable: 8 | manual_value: 1 9 | auto_value: 0 10 | pwm_enable_path: pwm1_enable 11 | pwm_enable_second_path: pwm1_mode 12 | 13 | pwm_write: 14 | pwm_write_max: 15 | default: 255 16 | pwm_write_path: pwm1 17 | 18 | pwm_input: 19 | hwmon_label: aynec 20 | pwm_read_path: fan1_input 21 | pwm_read_max: 5000 22 | 23 | temp_mode: 0 24 | -------------------------------------------------------------------------------- /py_modules/fan_config/ec/ayaneo_airplus.yml: -------------------------------------------------------------------------------- 1 | # 转速的读取地址不对 2 | # 写入转速测试可用 3 | product_name: 4 | - AIR Plus 5 | - SLIDE 6 | fans: 7 | - ram_reg_addr: 0x4E 8 | ram_reg_data: 0x4F 9 | ram_manual_offset: 0xd1c8 10 | ram_rpmwrite_offset: 0x1804 11 | ram_rpmread_offset: 0x1804 12 | ram_rpmread_length: 0 13 | enable_manual_value: 0xa5 14 | enable_auto_value: 0x00 15 | 16 | # manual_offset: 0x4a 17 | # rpmwrite_offset: 0x4b 18 | # rpmread_offset: 0x76 19 | 20 | rpm_write_max: 255 21 | rpm_value_max: 6000 22 | -------------------------------------------------------------------------------- /py_modules/fan_config/ec/onexplayer2.yml: -------------------------------------------------------------------------------- 1 | product_name: 2 | - ONEXPLAYER 2 ARP23 3 | - ONEXPLAYER 2 PRO ARP23P 4 | - ONEXPLAYER 2 PRO ARP23P EVA-01 5 | - ONEXPLAYER X1 6 | - ONEXPLAYER X1 A 7 | - ONEXPLAYER X1 mini 8 | fans: 9 | - manual_offset: 0x4a 10 | rpmwrite_offset: 0x4b 11 | rpmread_offset: 0x58 12 | 13 | ram_reg_addr: 0x4E 14 | ram_reg_data: 0x4F 15 | ram_manual_offset: 0x44a 16 | ram_rpmwrite_offset: 0x44b 17 | ram_rpmread_offset: 0x1809 18 | ram_rpmread_length: 0 19 | 20 | rpm_write_max: 184 21 | rpm_value_max: 5000 22 | -------------------------------------------------------------------------------- /py_modules/fan_config/hwmon/gpdfan.yml: -------------------------------------------------------------------------------- 1 | # support for https://github.com/Cryolitia/gpd-fan-driver 2 | hwmon_name: gpdfan 3 | 4 | fans: 5 | - fan_name: Fan 6 | pwm_mode: 0 7 | black_list: 8 | - G1618-04 9 | # - G1617-01 10 | 11 | pwm_enable: 12 | manual_value: 1 13 | auto_value: 2 14 | pwm_enable_path: pwm1_enable 15 | 16 | pwm_write: 17 | pwm_write_max: 18 | default: 255 19 | pwm_write_path: pwm1 20 | 21 | pwm_input: 22 | hwmon_label: gpdfan 23 | pwm_read_path: fan1_input 24 | pwm_read_max: 5000 25 | 26 | temp_mode: 0 -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "python.analysis.extraPaths": [ 3 | "../decky-loader/backend/decky_loader/plugin/imports", 4 | "../decky-loader/backend/decky_loader", 5 | "./py_modules", 6 | "./backend" 7 | ], 8 | "yaml.schemas": { 9 | "./py_modules/fan_config/schema/ec.json": ["py_modules/fan_config/ec/*.yml"], 10 | "./py_modules/fan_config/schema/hwmon.json": ["py_modules/fan_config/hwmon/*.yml"], 11 | }, 12 | "cursorpyright.analysis.extraPaths": [ 13 | "../decky-loader/backend/decky_loader/plugin/imports", 14 | "../decky-loader/backend/decky_loader", 15 | "./py_modules", 16 | "./backend" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /src/util/version.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Compare two version strings 3 | * @param v1 First version string 4 | * @param v2 Second version string 5 | * @returns 1 if v1 > v2, -1 if v1 < v2, 0 if equal 6 | */ 7 | export const compareVersions = (v1: string, v2: string): number => { 8 | const v1Parts = v1.replace("v", "").split(".").map(Number); 9 | const v2Parts = v2.replace("v", "").split(".").map(Number); 10 | 11 | for (let i = 0; i < Math.max(v1Parts.length, v2Parts.length); i++) { 12 | const v1Part = v1Parts[i] || 0; 13 | const v2Part = v2Parts[i] || 0; 14 | if (v1Part > v2Part) return 1; 15 | if (v1Part < v2Part) return -1; 16 | } 17 | return 0; 18 | }; 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | *.swp 10 | 11 | pids 12 | logs 13 | results 14 | tmp 15 | 16 | # Coverage reports 17 | coverage 18 | 19 | # API keys and secrets 20 | .env 21 | 22 | # Dependency directory 23 | node_modules 24 | bower_components 25 | # py_modules 26 | py_modules/site-packages 27 | 28 | # Editors 29 | .idea 30 | *.iml 31 | 32 | # OS metadata 33 | .DS_Store 34 | Thumbs.db 35 | 36 | # Ignore built ts files 37 | dist/ 38 | 39 | __pycache__/ 40 | 41 | /.yalc 42 | yalc.lock 43 | 44 | # .vscode/settings.json 45 | 46 | # Ignore output folder 47 | 48 | backend/out 49 | backend/*.sh 50 | # Makefile 51 | 52 | .history -------------------------------------------------------------------------------- /py_modules/fan_config/ec/onexplayer_mini.yml: -------------------------------------------------------------------------------- 1 | product_name: # /sys/devices/virtual/dmi/id/product_name 内容 2 | - AOKZOE A1 AR07 3 | - ONEXPLAYER Mini Pro 4 | - ONEXPLAYER mini A07 5 | fans: 6 | - manual_offset: 0x4a # 风扇自动控制ec地址 7 | rpmwrite_offset: 0x4b # 风扇写入转速ec地址 8 | rpmread_offset: 0x76 # 风扇读取转速ec地址 9 | 10 | ram_reg_addr: 0x4E # 风扇ecRam寄存器地址 11 | ram_reg_data: 0x4F # 风扇ecRam寄存器数据 12 | ram_manual_offset: 0x44a # 风扇自动控制ecRam地址 13 | ram_rpmwrite_offset: 0x44b # 风扇写入转速ecRam地址 14 | ram_rpmread_offset: 0x1809 # 风扇读取转速ecRam地址 15 | ram_rpmread_length: 0 # 风扇实际转速值长度 0为需要通过计算获得转速 16 | 17 | rpm_write_max: 255 # 风扇最大转速ec写入值 18 | rpm_value_max: 4968 # 风扇最大转速数值 19 | 20 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "dist", 4 | "module": "ESNext", 5 | "target": "ES2020", 6 | "jsx": "react", 7 | "jsxFactory": "window.SP_REACT.createElement", 8 | "jsxFragmentFactory": "window.SP_REACT.Fragment", 9 | "declaration": false, 10 | "moduleResolution": "node", 11 | "noUnusedLocals": true, 12 | "noUnusedParameters": true, 13 | "esModuleInterop": true, 14 | "noImplicitReturns": true, 15 | "noImplicitThis": true, 16 | "noImplicitAny": true, 17 | "emitDecoratorMetadata": true, 18 | "experimentalDecorators": true, 19 | "strict": true, 20 | "allowSyntheticDefaultImports": true, 21 | "skipLibCheck": true, 22 | "resolveJsonModule":true 23 | }, 24 | "include": ["src"], 25 | "exclude": ["node_modules"] 26 | } 27 | -------------------------------------------------------------------------------- /py_modules/fan_config/hwmon/oxp_ec.yml: -------------------------------------------------------------------------------- 1 | # hwmon目录下name文件内容 2 | hwmon_name: oxp_ec 3 | 4 | fans: 5 | - fan_name: Fan # 显示在 UI 上的风扇名称 6 | pwm_mode: 0 # 写入的模式 0.普通模式(对单个文件写入) 1.rog掌机特殊模式(对多个文件写入同样的数值) 7 | black_list: # 黑名单, 匹配 /sys/devices/virtual/dmi/id/product_name. 不使用该配置 8 | - G1618-04 9 | # - G1617-01 10 | 11 | pwm_enable: 12 | manual_value: 1 # 手动模式写入的值 13 | auto_value: 2 # 自动模式写入的值 14 | pwm_enable_path: pwm1_enable # 写入数值的文件路径(数值模式) 15 | 16 | pwm_write: 17 | pwm_write_max: # 写入转速最大值(根据不同产品名写入不同的最大值,没有则使用默认) 18 | default: 255 19 | pwm_write_path: pwm1 # 写入数值的文件路径 20 | 21 | pwm_input: 22 | pwm_read_path: fan1_input # 读取转速的文件路径 23 | pwm_read_max: 5000 # 读取转速的最大值 24 | 25 | temp_mode: 0 #温度使用哪个数据 0.cpu温度(不可用时切换到gpu温度) 1.gpu温度(不可用时切换到cpu温度) 26 | -------------------------------------------------------------------------------- /py_modules/fan_config/hwmon/oxpec.yml: -------------------------------------------------------------------------------- 1 | # hwmon目录下name文件内容 2 | hwmon_name: oxpec 3 | 4 | fans: 5 | - fan_name: Fan # 显示在 UI 上的风扇名称 6 | pwm_mode: 0 # 写入的模式 0.普通模式(对单个文件写入) 1.rog掌机特殊模式(对多个文件写入同样的数值) 7 | black_list: # 黑名单, 匹配 /sys/devices/virtual/dmi/id/product_name. 不使用该配置 8 | - G1618-04 9 | # - G1617-01 10 | 11 | pwm_enable: 12 | manual_value: 1 # 手动模式写入的值 13 | auto_value: 0 # 自动模式写入的值 14 | pwm_enable_path: pwm1_enable # 写入数值的文件路径(数值模式) 15 | 16 | pwm_write: 17 | pwm_write_max: # 写入转速最大值(根据不同产品名写入不同的最大值,没有则使用默认) 18 | default: 255 19 | pwm_write_path: pwm1 # 写入数值的文件路径 20 | 21 | pwm_input: 22 | pwm_read_path: fan1_input # 读取转速的文件路径 23 | pwm_read_max: 5000 # 读取转速的最大值 24 | 25 | temp_mode: 0 #温度使用哪个数据 0.cpu温度(不可用时切换到gpu温度) 1.gpu温度(不可用时切换到cpu温度) 26 | -------------------------------------------------------------------------------- /src/util/timeout.ts: -------------------------------------------------------------------------------- 1 | import { Logger } from "./logger"; 2 | 3 | /** 4 | * 超时工具类 5 | */ 6 | export class Timeout { 7 | /** 8 | * 带超时的异步函数包装器 9 | * @param fn 要执行的异步函数 10 | * @param timeoutMs 超时时间(毫秒) 11 | * @param errorMessage 超时错误信息 12 | * @returns Promise 执行结果 13 | */ 14 | static async withTimeout( 15 | fn: () => Promise, 16 | timeoutMs: number, 17 | errorMessage: string = "Operation timed out" 18 | ): Promise { 19 | const timeoutPromise = new Promise((_, reject) => { 20 | setTimeout(() => { 21 | reject(new Error(`${errorMessage} (${timeoutMs}ms)`)); 22 | }, timeoutMs); 23 | }); 24 | 25 | try { 26 | return await Promise.race([fn(), timeoutPromise]); 27 | } catch (error) { 28 | if (error instanceof Error && error.message.includes("timed out")) { 29 | Logger.error(error.message); 30 | } 31 | throw error; 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /py_modules/conf_manager.py: -------------------------------------------------------------------------------- 1 | import decky 2 | from config import CONFIG_KEY 3 | from settings import SettingsManager 4 | 5 | 6 | class ConfManager: 7 | def __init__(self): 8 | self.sysSettings = SettingsManager( 9 | name="config", settings_directory=decky.DECKY_PLUGIN_SETTINGS_DIR 10 | ) 11 | self.fansSettings = SettingsManager( 12 | name="fans_config", 13 | settings_directory=decky.DECKY_PLUGIN_SETTINGS_DIR, 14 | ) 15 | 16 | def getSettings(self): 17 | return self.sysSettings.getSetting(CONFIG_KEY) or {} 18 | 19 | def setSettings(self, settings): 20 | self.sysSettings.setSetting(CONFIG_KEY, settings) 21 | return True 22 | 23 | def getFanSettings(self): 24 | return self.fansSettings.getSetting(CONFIG_KEY) or {} 25 | 26 | def setFanSettingsByKey(self, key, value): 27 | self.fansSettings.setSetting(key, value) 28 | return True 29 | 30 | 31 | confManager = ConfManager() 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PowerControl Fork 2 | 3 | Powercontrol fork with fan controls only 4 | 5 | ![screenshot](./assets/screenshot.jpg) 6 | 7 | # Quick Install 8 | 9 | ``` 10 | curl -L https://github.com/aarron-lee/PowerControl/raw/main/install.sh | sh 11 | ``` 12 | 13 | You can install earlier versions via the following command: 14 | 15 | ```bash 16 | curl -L https://github.com/aarron-lee/PowerControl/raw/main/install.sh | VERSION_TAG=v2.7.1 sh 17 | ``` 18 | 19 | # Desktop App 20 | 21 | There is also an experimental [Desktop app](https://github.com/aarron-lee/PowerControl-Electron), which has separate install instructions. 22 | 23 | The Desktop app is currently outdated and uses PowerControl version 2.1.0, and will be updated when possible to use the latest 24 | 25 | See the desktop app readme for instructions. 26 | 27 | # Known Issues 28 | 29 | CachyOS might require you to separately install additional python dependencies. 30 | 31 | Check the logs for the missing dependencies, you can see logs via: 32 | 33 | ```bash 34 | cat $HOME/homebrew/logs/PowerControl/* 35 | ``` 36 | 37 | # Disclaimer 38 | 39 | forked for my own personal use 40 | 41 | # Attribution 42 | 43 | Major thanks to the PowerControl devs for creating such an awesome plugin 44 | -------------------------------------------------------------------------------- /py_modules/logging_handler.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | import subprocess 4 | 5 | LOG = "/tmp/PowerControl_systemd.log" 6 | LOG_TAG = "powercontrol" 7 | 8 | 9 | class SystemdHandler(logging.Handler): 10 | PRIORITY_MAP = { 11 | logging.DEBUG: "7", # debug 12 | logging.INFO: "6", # info 13 | logging.WARNING: "4", # warning 14 | logging.ERROR: "3", # err 15 | logging.CRITICAL: "2", # crit 16 | } 17 | 18 | def emit(self, record): 19 | msg = self.format(record) 20 | priority = self.PRIORITY_MAP.get(record.levelno, "6") 21 | try: 22 | # 使用系统的 systemd-cat 23 | env = os.environ.copy() 24 | env["LD_LIBRARY_PATH"] = "" # 清除 LD_LIBRARY_PATH 25 | subprocess.run( 26 | ["systemd-cat", "-t", LOG_TAG, "-p", priority], 27 | input=msg, 28 | text=True, 29 | env=env, 30 | ) 31 | except Exception as e: 32 | self.write_log(f"systemd-cat error: {e}") 33 | 34 | def write_log(self, msg): 35 | try: 36 | with open(LOG, "a") as f: 37 | f.write(msg) 38 | f.write("\n") 39 | except Exception as e: 40 | print(e) 41 | -------------------------------------------------------------------------------- /src/i18n/localization.ts: -------------------------------------------------------------------------------- 1 | import { defaultLocale, localizeMap, LocalizeStrKey } from "./localizeMap"; 2 | 3 | import i18n, { Resource} from "i18next"; 4 | 5 | export class localizationManager { 6 | private static language = "english"; 7 | 8 | public static async init() { 9 | const language = 10 | (await SteamClient.Settings.GetCurrentLanguage()) || "english"; 11 | this.language = language; 12 | console.log("Language: " + this.language); 13 | 14 | const resources: Resource = Object.keys(localizeMap).reduce( 15 | (acc: Resource, key) => { 16 | acc[localizeMap[key].locale] = { 17 | translation: localizeMap[key].strings, 18 | }; 19 | return acc; 20 | }, 21 | {} 22 | ); 23 | 24 | i18n.init({ 25 | resources: resources, 26 | lng: this.getLocale(), // 目标语言 27 | fallbackLng: defaultLocale, // 回落语言 28 | returnEmptyString: false, // 空字符串不返回, 使用回落语言 29 | interpolation: { 30 | escapeValue: false, 31 | }, 32 | }); 33 | } 34 | 35 | private static getLocale() { 36 | return localizeMap[this.language]?.locale ?? defaultLocale; 37 | } 38 | 39 | public static getString( 40 | defaultString: LocalizeStrKey, 41 | variables?: Record 42 | ) { 43 | return i18n.t(defaultString, variables); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /py_modules/sysInfo.py: -------------------------------------------------------------------------------- 1 | import collections 2 | import os 3 | import subprocess 4 | import threading 5 | import time 6 | 7 | from config import logger 8 | from helpers import get_user 9 | from utils import get_env 10 | 11 | cpu_busyPercent = 0 12 | cpu_DataErrCnt = 0 13 | has_cpuData = True 14 | 15 | gpu_busyPercent = 0 16 | gpu_DataErrCnt = 0 17 | has_gpuData = True 18 | 19 | statPath = "/proc/stat" 20 | hwmon_path = "/sys/class/hwmon" 21 | 22 | 23 | class SysInfoManager(threading.Thread): 24 | def __init__(self): 25 | threading.Thread.__init__(self) 26 | 27 | def get_language(self): 28 | try: 29 | lang_path = f"/home/{get_user()}/.steam/registry.vdf" 30 | if os.path.exists(lang_path): 31 | with open(lang_path, "r") as f: 32 | for line in f.readlines(): 33 | if "language" in line: 34 | self._language = line.split('"')[3] 35 | break 36 | else: 37 | logger.error(f"語言檢測路徑{lang_path}不存在該文件") 38 | logger.info(f"get_language {self._language} path={lang_path}") 39 | return self._language 40 | except Exception as e: 41 | logger.error(e) 42 | return self._language 43 | 44 | 45 | sysInfoManager = SysInfoManager() 46 | sysInfoManager.start() 47 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "power_control", 3 | "version": "v3.3.0-beta-client-fix", 4 | "description": "PowerControl plugin.", 5 | "type": "module", 6 | "scripts": { 7 | "build": "shx rm -rf dist && rollup -c", 8 | "watch": "rollup -c -w", 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/mengmeet/PowerControl.git" 14 | }, 15 | "keywords": [ 16 | "decky", 17 | "plugin", 18 | "plugin-template", 19 | "steam-deck", 20 | "deck" 21 | ], 22 | "author": "yxx", 23 | "license": "BSD-3-Clause", 24 | "bugs": { 25 | "url": "https://github.com/mengmeet/PowerControl/issues" 26 | }, 27 | "homepage": "https://github.com/mengmeet/PowerControl#readme", 28 | "devDependencies": { 29 | "@decky/rollup": "^1.0.1", 30 | "@decky/ui": "^4.10.5", 31 | "@types/markdown-it": "^14.1.2", 32 | "@types/react": "19.1.12", 33 | "@types/react-dom": "19.1.9", 34 | "@types/webpack": "^5.28.5", 35 | "markdown-it": "^14.1.0", 36 | "merge-anything": "^6.0.6", 37 | "rollup": "^4.49.0", 38 | "shx": "^0.4.0", 39 | "tslib": "^2.8.1", 40 | "typescript": "^5.9.2" 41 | }, 42 | "dependencies": { 43 | "@decky/api": "^1.1.2", 44 | "i18next": "^25.4.2", 45 | "react-icons": "^5.5.0", 46 | "typescript-json-serializer": "^6.0.1" 47 | }, 48 | "pnpm": { 49 | "peerDependencyRules": { 50 | "ignoreMissing": [ 51 | "react", 52 | "react-dom" 53 | ] 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2023, Gawah 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | 3. Neither the name of the copyright holder nor the names of its 16 | contributors may be used to endorse or promote products derived from 17 | this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /src/components/actionButtonItem.tsx: -------------------------------------------------------------------------------- 1 | import { ButtonItem, ButtonItemProps, Spinner } from "@decky/ui"; 2 | import { FC, useState } from "react"; 3 | 4 | export interface ActionButtonItemProps extends ButtonItemProps { 5 | loading?: boolean; 6 | debugLabel?: string; 7 | } 8 | 9 | export const ActionButtonItem: FC = (props) => { 10 | const { onClick, disabled, children, loading, layout, debugLabel } = props; 11 | 12 | const [_loading, setLoading] = useState(loading); 13 | 14 | const handClick = async ( 15 | event: MouseEvent, 16 | onClick?: (e: MouseEvent) => void 17 | ) => { 18 | try { 19 | console.log(`ActionButtonItem: ${debugLabel}`); 20 | setLoading(true); 21 | await onClick?.(event); 22 | console.log(`ActionButtonItem: ${debugLabel} done`); 23 | } catch (e) { 24 | console.error(`ActionButtonItem error: ${e}`); 25 | } finally { 26 | // console.log(`ActionButtonItem: ${debugLabel} disable loading`); 27 | setLoading(false); 28 | } 29 | }; 30 | 31 | const isLoading = _loading; 32 | 33 | return ( 34 | handClick(e, onClick)} 39 | > 40 | 47 | {children}{" "} 48 | {isLoading && } 49 | 50 | 51 | ); 52 | }; 53 | -------------------------------------------------------------------------------- /README_en.md: -------------------------------------------------------------------------------- 1 | English | [简体中文](./README.md) 2 | 3 | # PowerControl 4 | 5 | [![](https://img.shields.io/github/downloads/mengmeet/PowerControl/total.svg)](https://gitHub.com/mengmeet/PowerControl/releases) [![](https://img.shields.io/github/downloads/mengmeet/PowerControl/latest/total)](https://github.com/mengmeet/PowerControl/releases/latest) [![](https://img.shields.io/github/v/release/mengmeet/PowerControl)](https://github.com/mengmeet/PowerControl/releases/latest) 6 | 7 | Plugin for [decky-loader](https://github.com/SteamDeckHomebrew/decky-loader) 8 | Provide performance settings adjustments for handheld 9 | 10 | ## Manual installation 11 | 12 | 1. Install [decky-loader](https://github.com/SteamDeckHomebrew/decky-loader) 13 | 2. Download PowerControl.tar.gz from [Releases](https://github.com/Gawah/PowerControl/releases) 14 | 3. Modify directory permissions `chmod -R 777 ${HOME}/homebrew/plugins` 15 | 4. Extract to /home/xxxx/homebrew/plugins/ 16 | 5. Restart decky-loader, `sudo systemctl restart plugin_loader.service` 17 | 18 | ## Automatic installation 19 | ``` 20 | curl -L https://raw.githubusercontent.com/mengmeet/PowerControl/main/install.sh | sh 21 | ``` 22 | 23 | ## Function 24 | 1. CPU Boost 25 | 2. SMT 26 | 3. Set the number of cores to enable 27 | 4. TDP 28 | 5. GPU frequency 29 | 6. Auto GPU frequency 30 | 7. Fan control 31 | 32 | ## Supported devices 33 | - Most AMD Ryzen CPU 34 | - Some Intel CPU (experimental, tested with GPD WIN3) 35 | 36 | ## Reference 37 | [decky-loader](https://github.com/SteamDeckHomebrew/decky-loader) 38 | [vibrantDeck](https://github.com/libvibrant/vibrantDeck) 39 | [decky-plugin-template](https://github.com/SteamDeckHomebrew/decky-plugin-template) 40 | [RyzenAdj](https://github.com/FlyGoat/RyzenAdj) 41 | -------------------------------------------------------------------------------- /src/util/logger.ts: -------------------------------------------------------------------------------- 1 | import { logInfo, logError, logWarn, logDebug } from "./backend"; 2 | 3 | /** 4 | * Logger utility class for consistent logging across the application 5 | */ 6 | export class Logger { 7 | private static getTimestamp(): string { 8 | const now = new Date(); 9 | const year = now.getFullYear(); 10 | const month = String(now.getMonth() + 1).padStart(2, '0'); 11 | const day = String(now.getDate()).padStart(2, '0'); 12 | const hours = String(now.getHours()).padStart(2, '0'); 13 | const minutes = String(now.getMinutes()).padStart(2, '0'); 14 | const seconds = String(now.getSeconds()).padStart(2, '0'); 15 | const milliseconds = String(now.getMilliseconds()).padStart(3, '0'); 16 | return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}.${milliseconds}`; 17 | } 18 | 19 | private static formatMessage(level: string, message: string): string { 20 | const timestamp = this.getTimestamp(); 21 | return `[${timestamp}] ${level} ${message}`; 22 | } 23 | 24 | static info(message: string): void { 25 | const formattedMessage = this.formatMessage('INFO', message); 26 | console.log(formattedMessage); 27 | logInfo(formattedMessage); 28 | } 29 | 30 | static error(message: string): void { 31 | const formattedMessage = this.formatMessage('ERROR', message); 32 | console.error(formattedMessage); 33 | logError(formattedMessage); 34 | } 35 | 36 | static warn(message: string): void { 37 | const formattedMessage = this.formatMessage('WARN', message); 38 | console.warn(formattedMessage); 39 | logWarn(formattedMessage); 40 | } 41 | 42 | static debug(message: string): void { 43 | const formattedMessage = this.formatMessage('DEBUG', message); 44 | console.debug(formattedMessage); 45 | logDebug(formattedMessage); 46 | } 47 | } -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/bash 2 | 3 | set -e 4 | 5 | if [ "$EUID" -eq 0 ]; then 6 | echo "Please do not run as root" 7 | exit 8 | fi 9 | 10 | sudo rm -rf $HOME/homebrew/plugins/PowerControl 11 | 12 | VERSION=${VERSION_TAG:-"LATEST"} 13 | 14 | github_api_url='https://api.github.com/repos/aarron-lee/PowerControl/releases/latest' 15 | 16 | if [ $VERSION != "LATEST" ] ; then 17 | github_api_url="https://api.github.com/repos/aarron-lee/PowerControl/releases/${VERSION}" 18 | fi 19 | 20 | package="PowerControl" 21 | 22 | echo "installing $package" 23 | 24 | temp=$(mktemp -d) 25 | 26 | sudo chmod -R +w "${HOME}/homebrew/plugins/" 27 | plugin_dir="${HOME}/homebrew/plugins/${package}" 28 | sudo mkdir -p $plugin_dir 29 | 30 | use_jq=false 31 | if [ -x "$(command -v jq)" ]; then 32 | use_jq=true 33 | fi 34 | 35 | RELEASE=$(curl -s "$github_api_url") 36 | 37 | if [[ $use_jq == true ]]; then 38 | echo "Using jq" 39 | MESSAGE=$(echo "$RELEASE" | jq -r '.message') 40 | RELEASE_VERSION=$(echo "$RELEASE" | jq -r '.tag_name') 41 | RELEASE_URL=$(echo "$RELEASE" | jq -r '.assets[0].browser_download_url') 42 | else 43 | MESSAGE=$(echo $RELEASE | grep "message" | cut -d '"' -f 4) 44 | RELEASE_URL=$(echo $RELEASE | grep "browser_download_url" | cut -d '"' -f 4 | grep ".tar.gz") 45 | RELEASE_VERSION=$(echo $RELEASE | grep "tag_name" | cut -d '"' -f 4) 46 | fi 47 | 48 | if [[ "$MESSAGE" != "null" ]]; then 49 | echo "error: $MESSAGE" >&2 50 | exit 1 51 | fi 52 | 53 | if [ -z "$RELEASE_URL" ]; then 54 | echo "Failed to get latest release" >&2 55 | exit 1 56 | fi 57 | 58 | temp_file="${temp}/${package}.tar.gz" 59 | 60 | echo "Downloading $package $RELEASE_VERSION" 61 | curl -L "$RELEASE_URL" -o "$temp_file" 62 | 63 | sudo tar -xzf "$temp_file" -C $temp 64 | sudo rsync -av "${temp}/${package}/" $plugin_dir --delete 65 | 66 | rm "$temp_file" 67 | sudo systemctl restart plugin_loader.service 68 | -------------------------------------------------------------------------------- /src/util/steamUtils.tsx: -------------------------------------------------------------------------------- 1 | import { Module, findModuleChild } from "@decky/ui"; 2 | import { SystemInfo } from "."; 3 | import { ReactNode } from "react"; 4 | import { FaSuperpowers } from "react-icons/fa"; 5 | import { toaster } from "@decky/api"; 6 | 7 | //#region Find SteamOS modules 8 | const findModule = (property: string) => { 9 | return findModuleChild((m: Module) => { 10 | if (typeof m !== "object") return undefined; 11 | for (let prop in m) { 12 | try { 13 | if (m[prop][property]) return m[prop]; 14 | } catch { 15 | return undefined; 16 | } 17 | } 18 | }); 19 | }; 20 | // @ts-ignore 21 | const NavSoundMap = findModule("ToastMisc"); 22 | //#endregion 23 | 24 | export interface NotifyProps { 25 | message: ReactNode; 26 | title?: ReactNode; 27 | logo?: ReactNode; 28 | icon?: ReactNode; 29 | showToast?: boolean; 30 | playSound?: boolean; 31 | sound?: number; 32 | duration?: number; 33 | } 34 | 35 | export class SteamUtils { 36 | //#region Notification Wrapper 37 | static async notify({ 38 | title, 39 | message, 40 | logo, 41 | icon, 42 | showToast, 43 | playSound, 44 | sound, 45 | duration, 46 | }: NotifyProps) { 47 | let toastData = { 48 | title: title || "PowerControl", 49 | body: message, 50 | logo: logo, 51 | icon: icon || , 52 | duration: duration, 53 | sound: sound || NavSoundMap?.ToastMisc, 54 | playSound: playSound || false, 55 | showToast: showToast || false, 56 | }; 57 | toaster.toast(toastData); 58 | } 59 | 60 | static async simpleToast(message: string, duration?: number) { 61 | this.notify({ message, showToast: true, duration }); 62 | } 63 | ; 64 | static async getSystemInfo() : Promise { 65 | const systemInfo = await SteamClient.System.GetSystemInfo(); 66 | return systemInfo; 67 | } 68 | } -------------------------------------------------------------------------------- /.cursor/rules/riper5.mdc: -------------------------------------------------------------------------------- 1 | --- 2 | alwaysApply: true 3 | --- 4 | # RIPER-5 模式:严格操作协议 5 | 6 | ## 背景介绍 7 | 8 | 你是Claude 3.7,你被集成到Cursor IDE中,这是一个基于AI的VS Code分支。由于你的高级能力,你往往过于热情,经常在没有明确请求的情况下实施更改,通过假设你比我更了解而破坏现有逻辑。这会导致代码的不可接受的灾难。在处理我的代码库时——无论是网络应用、数据管道、嵌入式系统还是任何其他软件项目——你未经授权的修改可能会引入细微的错误并破坏关键功能。为防止这种情况,你必须遵循这个严格协议: 9 | 10 | ## 元指令:模式声明要求 11 | 12 | 你必须在每个回复的开头用括号声明你的当前模式。没有例外。格式:[MODE: MODE_NAME] 未能声明你的模式是对协议的严重违反。并且不能自主转换模式! 13 | 14 | ## RIPER-5 模式 15 | 16 | ### 模式 1:研究 17 | [MODE: RESEARCH] 18 | 19 | - **目的**:仅收集信息 20 | - **允许**:阅读文件,提出澄清问题,理解代码结构 21 | - **禁止**:建议,实施,计划,或任何行动暗示 22 | - **要求**:你只能寻求理解现有内容,而不是可能的内容 23 | - **持续时间**:直到我明确指示转到下一个模式 24 | - **输出格式**:以[MODE: RESEARCH]开始,然后只有观察和问题 25 | 26 | ### 模式 2:创新 27 | [MODE: INNOVATE] 28 | 29 | - **目的**:头脑风暴潜在方法 30 | - **允许**:讨论想法,优势/劣势,寻求反馈 31 | - **禁止**:具体计划,实施细节,或任何代码编写 32 | - **要求**:所有想法必须作为可能性呈现,而非决定 33 | - **持续时间**:直到我明确指示转到下一个模式 34 | - **输出格式**:以[MODE: INNOVATE]开始,然后只有可能性和考虑因素 35 | 36 | ### 模式 3:计划 37 | [MODE: PLAN] 38 | 39 | - **目的**:创建详尽的技术规范 40 | - **允许**:详细计划,包含确切的文件路径,函数名和更改 41 | - **禁止**:任何实施或代码编写,即使是"示例代码" 42 | - **要求**:计划必须足够全面,使实施过程中不需要创造性决策 43 | - **强制最终步骤**:将整个计划转换为编号的、顺序的检查清单,每个原子操作作为单独的项目 44 | - **检查清单格式**: 45 | 实施检查清单: 46 | 1. [具体操作1] 47 | 2. [具体操作2] 48 | ... 49 | n. [最终操作] 50 | - **持续时间**:直到我明确批准计划并指示转到下一个模式 51 | - **输出格式**:以[MODE: PLAN]开始,然后只有规范和实施细节 52 | 53 | ### 模式 4:执行 54 | [MODE: EXECUTE] 55 | 56 | - **目的**:精确实施模式3中计划的内容 57 | - **允许**:只实施批准计划中明确详述的内容 58 | - **禁止**:任何偏离、改进或不在计划中的创新补充 59 | - **进入要求**:只有在我明确命令"ENTER EXECUTE MODE" 或者 "进入执行模式" 后才能进入 60 | - **偏差处理**:如果发现任何需要偏离的问题,立即返回PLAN模式 61 | - **输出格式**:以[MODE: EXECUTE]开始,然后只有符合计划的实施 62 | 63 | ### 模式 5:审查 64 | [MODE: REVIEW] 65 | 66 | - **目的**:无情地验证实施是否符合计划 67 | - **允许**:对计划和实施进行逐行比较 68 | - **要求**:明确标记任何偏差,无论多么微小 69 | - **偏差格式**:":warning: 检测到偏差:[确切偏差描述]" 70 | - **报告**:必须报告实施是否与计划完全相同 71 | - **结论格式**:":white_check_mark: 实施完全符合计划"或":cross_mark: 实施偏离计划" 72 | - **输出格式**:以[MODE: REVIEW]开始,然后是系统比较和明确判断 73 | 74 | ## 关键协议指南 75 | 76 | 1. 未经我明确许可,你不能在模式之间转换 77 | 2. 你必须在每次回复的开头声明你的当前模式 78 | 3. 在EXECUTE模式下,你必须100%忠实地遵循计划 79 | 4. 在REVIEW模式下,你必须标记即使是最小的偏差 80 | 5. 你无权在声明的模式之外做出独立决定 81 | 6. 未能遵循此协议将对我的代码库造成灾难性后果 82 | 7. 始终使用中文回答 83 | 8. git 提交信息使用英文,优先使用 xxx: xxxxxxxxxx 这样的形式 84 | 9. 代码注释使用英文 85 | 86 | ## 模式转换信号 87 | 88 | 必须注意!!只有当我明确发出以下信号时才转换模式: 89 | 90 | "进入研究模式" 91 | "进入创新模式" 92 | "进入计划模式" 93 | "进入执行模式" 94 | "进入审查模式" 95 | 96 | 没有这些确切信号,保持在当前模式。 -------------------------------------------------------------------------------- /py_modules/utils/__init__.py: -------------------------------------------------------------------------------- 1 | import decky 2 | import os 3 | import shutil 4 | from typing import List 5 | import signal 6 | from contextlib import contextmanager 7 | 8 | RYZENADJ_PATH = "{}/bin/ryzenadj".format(decky.DECKY_PLUGIN_DIR) 9 | 10 | __all__ = [ 11 | "get_env", 12 | "version_compare", 13 | "get_ryzenadj_path", 14 | ] 15 | 16 | 17 | def get_env(): 18 | env = os.environ.copy() 19 | env["LD_LIBRARY_PATH"] = "" 20 | return env 21 | 22 | 23 | def get_ryzenadj_path(prefer_plugin=True): 24 | """ 25 | Get the path to ryzenadj executable. 26 | 27 | Args: 28 | prefer_plugin (bool): If True, prefer plugin's ryzenadj over system version. 29 | If False (default), prefer system version over plugin. 30 | 31 | Returns: 32 | str: Path to ryzenadj executable 33 | """ 34 | plugin_path = ( 35 | RYZENADJ_PATH 36 | if os.path.exists(RYZENADJ_PATH) and os.access(RYZENADJ_PATH, os.X_OK) 37 | else None 38 | ) 39 | system_path = shutil.which("ryzenadj") 40 | 41 | if prefer_plugin: 42 | return plugin_path or system_path or RYZENADJ_PATH 43 | else: 44 | return system_path or plugin_path or RYZENADJ_PATH 45 | 46 | 47 | # 版本号对比 数组参数 48 | def version_compare(version1: List[int], version2: List[int]) -> int: 49 | """ 50 | 比较两个版本号数组的大小 51 | 52 | Args: 53 | version1 (list): 第一个版本号数组,如 [1, 2, 3] 54 | version2 (list): 第二个版本号数组,如 [1, 2, 4] 55 | 56 | Returns: 57 | int: 如果 version1 > version2 返回 1,如果 version1 < version2 返回 -1,如果相等返回 0 58 | """ 59 | # 获取两个版本号数组的长度 60 | len1, len2 = len(version1), len(version2) 61 | 62 | # 取较短的长度进行比较 63 | for i in range(min(len1, len2)): 64 | if version1[i] > version2[i]: 65 | return 1 66 | elif version1[i] < version2[i]: 67 | return -1 68 | 69 | # 如果前面的版本号都相同,比较长度 70 | if len1 > len2: 71 | return 1 72 | elif len1 < len2: 73 | return -1 74 | else: 75 | return 0 76 | 77 | 78 | class TimeoutException(Exception): 79 | pass 80 | 81 | 82 | @contextmanager 83 | def time_limit(seconds): 84 | def signal_handler(signum, frame): 85 | raise TimeoutException("Timed out!") 86 | 87 | signal.signal(signal.SIGALRM, signal_handler) 88 | signal.alarm(seconds) 89 | try: 90 | yield 91 | finally: 92 | signal.alarm(0) 93 | -------------------------------------------------------------------------------- /py_modules/tt.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | WRITE_PATH="$1" 4 | WRITE_VALUE="$2" 5 | 6 | if [[ "$WRITE_PATH" == /sys/class/drm/card*/device/power_dpm_force_performance_level ]]; then 7 | GPU=$(grep -H 0x8086 /sys/class/drm/card?/device/vendor 2>/dev/null | head -n1 | sed 's/\/device\/vendor:.*//') 8 | GPU_MIN_FREQ="$GPU/gt_min_freq_mhz" 9 | GPU_MAX_FREQ="$GPU/gt_max_freq_mhz" 10 | GPU_MIN_LIMIT="$(cat $GPU/gt_RPn_freq_mhz)" 11 | GPU_MAX_LIMIT="$(cat $GPU/gt_RP0_freq_mhz)" 12 | echo "setting intel gpu $GPU to [$WRITE_VALUE]" | systemd-cat -t p-steamos-priv-write -p warning 13 | if [[ "$WRITE_VALUE" == "auto" ]]; then 14 | echo "$GPU_MIN_LIMIT" >"$GPU_MIN_FREQ" 15 | echo "$GPU_MAX_LIMIT" >"$GPU_MAX_FREQ" 16 | echo "commit: $GPU_MIN_LIMIT -> $GPU_MIN_FREQ" | systemd-cat -t p-steamos-priv-write -p warning 17 | echo "commit: $GPU_MAX_LIMIT -> $GPU_MAX_FREQ" | systemd-cat -t p-steamos-priv-write -p warning 18 | fi 19 | exit 0 20 | fi 21 | 22 | if [[ "$WRITE_PATH" == /sys/class/drm/card*/device/pp_od_clk_voltage ]]; then 23 | GPU=$(grep -H 0x8086 /sys/class/drm/card?/device/vendor 2>/dev/null | head -n1 | sed 's/\/device\/vendor:.*//') 24 | GPU_MIN_FREQ="$GPU/gt_min_freq_mhz" 25 | GPU_MAX_FREQ="$GPU/gt_max_freq_mhz" 26 | GPU_MIN_LIMIT="$(cat $GPU/gt_RPn_freq_mhz)" 27 | GPU_MAX_LIMIT="$(cat $GPU/gt_RP0_freq_mhz)" 28 | echo "commit: GPU -> $WRITE_VALUE" | systemd-cat -t p-steamos-priv-write -p warning 29 | if [[ "$WRITE_VALUE" =~ "s 0" ]]; then 30 | min_freq=$(echo "$WRITE_VALUE" | sed 's/.*s 0 //') 31 | if [[ "$(cat $GPU_MAX_FREQ)" -lt "$min_freq" ]]; then 32 | echo "commit: $GPU_MAX_FREQ -> $min_freq" | systemd-cat -t p-steamos-priv-write -p warning 33 | echo "$min_freq" >"$GPU_MAX_FREQ" 34 | fi 35 | if [[ "$min_freq" -lt "$GPU_MIN_LIMIT" ]]; then 36 | min_freq="$GPU_MIN_LIMIT" 37 | fi 38 | if [[ "$min_freq" -gt "$GPU_MAX_LIMIT" ]]; then 39 | min_freq="$GPU_MIN_LIMIT" 40 | fi 41 | echo "commit: $min_freq -> $GPU_MIN_FREQ" | systemd-cat -t p-steamos-priv-write -p warning 42 | echo "$min_freq" >"$GPU_MIN_FREQ" 43 | fi 44 | if [[ "$WRITE_VALUE" =~ "s 1" ]]; then 45 | max_freq=$(echo "$WRITE_VALUE" | sed 's/.*s 1 //') 46 | if [[ "$max_freq" -gt "$GPU_MAX_LIMIT" ]]; then 47 | max_freq="$GPU_MAX_LIMIT" 48 | fi 49 | echo "commit: $max_freq -> $GPU_MAX_FREQ" | systemd-cat -t p-steamos-priv-write -p warning 50 | echo "$max_freq" >"$GPU_MAX_FREQ" 51 | fi 52 | exit 0 53 | fi 54 | -------------------------------------------------------------------------------- /src/components/SlowSliderField.tsx: -------------------------------------------------------------------------------- 1 | import { NotchLabel, SliderField } from "@decky/ui"; 2 | import { ItemProps } from "@decky/ui/dist/components/Item"; 3 | import { useEffect, useRef } from "react"; 4 | import { useState } from "react"; 5 | import { FC } from "react"; 6 | 7 | export interface SlowSliderFieldProps extends ItemProps { 8 | value: number; 9 | min?: number; 10 | max?: number; 11 | changeMin?: number; 12 | changeMax?: number; 13 | step?: number; 14 | notchCount?: number; 15 | notchLabels?: NotchLabel[]; 16 | notchTicksVisible?: boolean; 17 | showValue?: boolean; 18 | resetValue?: number; 19 | disabled?: boolean; 20 | editableValue?: boolean; 21 | validValues?: "steps" | "range" | ((value: number) => boolean); 22 | valueSuffix?: string; 23 | minimumDpadGranularity?: number; 24 | onChange?(value: number): void; 25 | onChangeEnd?(value: number): void; 26 | } 27 | export const SlowSliderField: FC = (slider) => { 28 | const [changeValue, SetChangeValue] = useState(slider.value); 29 | const isChanging = useRef(false); 30 | useEffect(() => { 31 | setTimeout(() => { 32 | //console.debug("changeValue=",changeValue,"slider=",slider.value) 33 | if (changeValue == slider.value) { 34 | slider.onChangeEnd?.call(slider, slider.value); 35 | isChanging.current = false; 36 | } 37 | }, 500); 38 | }, [changeValue]); 39 | return ( 40 | { 58 | var tpvalue = value; 59 | if (slider.changeMax != undefined) 60 | tpvalue = slider.changeMax <= value ? slider.changeMax : value; 61 | if (slider.changeMin != undefined) 62 | tpvalue = slider.changeMin >= value ? slider.changeMin : value; 63 | isChanging.current = true; 64 | slider.onChange?.call(slider, tpvalue); 65 | slider.value = tpvalue; 66 | SetChangeValue(tpvalue); 67 | }} 68 | /> 69 | ); 70 | }; 71 | -------------------------------------------------------------------------------- /src/i18n/localizeMap.ts: -------------------------------------------------------------------------------- 1 | import * as schinese from "./schinese.json"; 2 | import * as tchinese from "./tchinese.json"; 3 | import * as english from "./english.json"; 4 | import * as german from "./german.json"; 5 | import * as japanese from "./japanese.json"; 6 | import * as koreana from "./koreana.json"; 7 | import * as thai from "./thai.json"; 8 | import * as bulgarian from "./bulgarian.json"; 9 | import * as italian from "./italian.json"; 10 | import * as french from "./french.json"; 11 | 12 | export interface LanguageProps { 13 | label: string; 14 | strings: any; 15 | credit: string[]; 16 | locale: string; 17 | } 18 | 19 | export const defaultLanguage = "english"; 20 | export const defaultLocale = "en"; 21 | export const defaultMessages = english; 22 | 23 | export const localizeMap: { [key: string]: LanguageProps } = { 24 | schinese: { 25 | label: "简体中文", 26 | strings: schinese, 27 | credit: ["yxx"], 28 | locale: "zh-CN", 29 | }, 30 | tchinese: { 31 | label: "繁體中文", 32 | strings: tchinese, 33 | credit: [], 34 | locale: "zh-TW", 35 | }, 36 | english: { 37 | label: "English", 38 | strings: english, 39 | credit: [], 40 | locale: "en", 41 | }, 42 | german: { 43 | label: "Deutsch", 44 | strings: german, 45 | credit: ["dctr"], 46 | locale: "de", 47 | }, 48 | japanese: { 49 | label: "日本語", 50 | strings: japanese, 51 | credit: [], 52 | locale: "ja", 53 | }, 54 | koreana: { 55 | label: "한국어", 56 | strings: koreana, 57 | credit: [], 58 | locale: "ko", 59 | }, 60 | thai: { 61 | label: "ไทย", 62 | strings: thai, 63 | credit: [], 64 | locale: "th", 65 | }, 66 | bulgarian: { 67 | label: "Български", 68 | strings: bulgarian, 69 | credit: [], 70 | locale: "bg", 71 | }, 72 | italian: { 73 | label: "Italiano", 74 | strings: italian, 75 | credit: [], 76 | locale: "it", 77 | }, 78 | french: { 79 | label: "Français", 80 | strings: french, 81 | credit: [], 82 | locale: "fr", 83 | }, 84 | }; 85 | 86 | // 创建一个类型安全的常量生成函数 87 | function createLocalizeConstants(keys: T) { 88 | return keys.reduce((obj, key) => { 89 | obj[key as keyof typeof obj] = key; 90 | return obj; 91 | }, {} as { [K in T[number]]: K }); 92 | } 93 | 94 | // 创建常量对象并导出 95 | export const L = createLocalizeConstants(Object.keys(defaultMessages)); 96 | 97 | // 导出类型 98 | export type LocalizeStrKey = keyof typeof L; 99 | 100 | // 为了向后兼容,保留 localizeStrEnum 名称 101 | export const localizeStrEnum = L; 102 | -------------------------------------------------------------------------------- /py_modules/fan_config/hwmon/msi_wmi.yml: -------------------------------------------------------------------------------- 1 | hwmon_name: msi_wmi_platform 2 | 3 | fans: 4 | - fan_name: Fan1 5 | pwm_mode: 2 # 写入的模式 0.普通模式(单文件写入) 1.对多个文件写入同样的数值 2.对多个文件写入不同的数值 6 | 7 | pwm_enable: 8 | manual_value: 1 9 | auto_value: 2 10 | pwm_enable_path: pwm1_enable 11 | 12 | pwm_write: 13 | pwm_write_max: 14 | default: 255 # 100% 15 | curve_path: # 曲线写入路径 16 | temp_write: 17 | - pwm1_auto_point1_temp 18 | - pwm1_auto_point2_temp 19 | - pwm1_auto_point3_temp 20 | - pwm1_auto_point4_temp 21 | - pwm1_auto_point5_temp 22 | - pwm1_auto_point6_temp 23 | pwm_write: 24 | - pwm1_auto_point1_pwm 25 | - pwm1_auto_point2_pwm 26 | - pwm1_auto_point3_pwm 27 | - pwm1_auto_point4_pwm 28 | - pwm1_auto_point5_pwm 29 | - pwm1_auto_point6_pwm 30 | default_curve: 31 | temp: 32 | - 0 33 | - 50 34 | - 60 35 | - 70 36 | - 80 37 | - 90 38 | pwm: 39 | - 0 40 | - 102 41 | - 124 42 | - 147 43 | - 170 44 | - 191 45 | 46 | pwm_input: 47 | hwmon_label: msi_wmi_platform 48 | pwm_read_path: fan1_input 49 | pwm_read_max: 5800 50 | 51 | temp_mode: 0 52 | 53 | - fan_name: Fan2 54 | pwm_mode: 2 55 | 56 | pwm_enable: 57 | manual_value: 1 58 | auto_value: 2 59 | pwm_enable_path: pwm2_enable 60 | 61 | pwm_write: 62 | pwm_write_max: 63 | default: 170 # 100% 64 | # Claw 8 AI+ A2VM: 255 # 150% 65 | curve_path: # 曲线写入路径 66 | temp_write: 67 | - pwm2_auto_point1_temp 68 | - pwm2_auto_point2_temp 69 | - pwm2_auto_point3_temp 70 | - pwm2_auto_point4_temp 71 | - pwm2_auto_point5_temp 72 | - pwm2_auto_point6_temp 73 | pwm_write: 74 | - pwm2_auto_point1_pwm 75 | - pwm2_auto_point2_pwm 76 | - pwm2_auto_point3_pwm 77 | - pwm2_auto_point4_pwm 78 | - pwm2_auto_point5_pwm 79 | - pwm2_auto_point6_pwm 80 | default_curve: 81 | temp: 82 | - 0 83 | - 50 84 | - 60 85 | - 70 86 | - 80 87 | - 90 88 | pwm: 89 | - 0 90 | - 102 91 | - 124 92 | - 147 93 | - 170 94 | - 191 95 | 96 | pwm_input: 97 | hwmon_label: msi_wmi_platform 98 | pwm_read_path: fan2_input 99 | pwm_read_max: 5800 100 | 101 | temp_mode: 0 102 | -------------------------------------------------------------------------------- /py_modules/fan_config/hwmon/msi_ec.yml: -------------------------------------------------------------------------------- 1 | hwmon_name: msi_ec 2 | 3 | fans: 4 | - fan_name: Fan1 5 | pwm_mode: 2 # 写入的模式 0.普通模式(单文件写入) 1.对多个文件写入同样的数值 2.对多个文件写入不同的数值 6 | 7 | pwm_enable: 8 | manual_value: 1 9 | auto_value: 2 10 | pwm_enable_path: pwm1_enable 11 | 12 | pwm_write: 13 | pwm_write_max: 14 | default: 170 # 100% 15 | # Claw 8 AI+ A2VM: 255 # 150% 16 | curve_path: # 曲线写入路径 17 | temp_write: 18 | - pwm1_auto_point1_temp 19 | - pwm1_auto_point2_temp 20 | - pwm1_auto_point3_temp 21 | - pwm1_auto_point4_temp 22 | - pwm1_auto_point5_temp 23 | - pwm1_auto_point6_temp 24 | pwm_write: 25 | - pwm1_auto_point1_pwm 26 | - pwm1_auto_point2_pwm 27 | - pwm1_auto_point3_pwm 28 | - pwm1_auto_point4_pwm 29 | - pwm1_auto_point5_pwm 30 | - pwm1_auto_point6_pwm 31 | default_curve: 32 | temp: 33 | - 50 34 | - 60 35 | - 70 36 | - 80 37 | - 88 38 | - 88 39 | pwm: 40 | - 0 41 | - 68 42 | - 83 43 | - 98 44 | - 113 45 | - 127 46 | 47 | pwm_input: 48 | hwmon_label: msi_ec 49 | pwm_read_path: fan1_input 50 | pwm_read_max: 5800 51 | 52 | temp_mode: 0 53 | 54 | - fan_name: Fan2 55 | pwm_mode: 2 56 | 57 | pwm_enable: 58 | manual_value: 1 59 | auto_value: 2 60 | pwm_enable_path: pwm2_enable 61 | 62 | pwm_write: 63 | pwm_write_max: 64 | default: 170 # 100% 65 | # Claw 8 AI+ A2VM: 255 # 150% 66 | curve_path: # 曲线写入路径 67 | temp_write: 68 | - pwm2_auto_point1_temp 69 | - pwm2_auto_point2_temp 70 | - pwm2_auto_point3_temp 71 | - pwm2_auto_point4_temp 72 | - pwm2_auto_point5_temp 73 | - pwm2_auto_point6_temp 74 | pwm_write: 75 | - pwm2_auto_point1_pwm 76 | - pwm2_auto_point2_pwm 77 | - pwm2_auto_point3_pwm 78 | - pwm2_auto_point4_pwm 79 | - pwm2_auto_point5_pwm 80 | - pwm2_auto_point6_pwm 81 | default_curve: 82 | temp: 83 | - 50 84 | - 60 85 | - 70 86 | - 80 87 | - 88 88 | - 88 89 | pwm: 90 | - 0 91 | - 68 92 | - 83 93 | - 98 94 | - 113 95 | - 127 96 | 97 | pwm_input: 98 | hwmon_label: msi_ec 99 | pwm_read_path: fan2_input 100 | pwm_read_max: 5800 101 | 102 | temp_mode: 0 103 | -------------------------------------------------------------------------------- /py_modules/fan_config/schema/ec.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-06/schema#", 3 | "$ref": "#/definitions/ECProfile", 4 | "definitions": { 5 | "ECProfile": { 6 | "title": "ECProfile", 7 | "type": "object", 8 | "additionalProperties": false, 9 | "properties": { 10 | "product_name": { 11 | "type": "array", 12 | "items": { 13 | "type": "string" 14 | }, 15 | "minItems": 1, 16 | "description": "设备标识, `/sys/class/dmi/id/product_name`" 17 | }, 18 | "product_version": { 19 | "type": "array", 20 | "items": { 21 | "type": "string" 22 | }, 23 | "minItems": 1, 24 | "description": "设备版本, `/sys/class/dmi/id/product_version`" 25 | }, 26 | "fans": { 27 | "type": "array", 28 | "items": { 29 | "$ref": "#/definitions/Fans" 30 | }, 31 | "minItems": 1, 32 | "description": "风扇配置信息" 33 | } 34 | }, 35 | "required": ["product_name", "fans"] 36 | }, 37 | "Fans": { 38 | "title": "Fans", 39 | "type": "object", 40 | "additionalProperties": false, 41 | "properties": { 42 | "manual_offset": { 43 | "type": "number", 44 | "description": "风扇自动控制ec地址" 45 | }, 46 | "rpmwrite_offset": { 47 | "type": "number", 48 | "description": "风扇写入转速ec地址" 49 | }, 50 | "rpmread_offset": { 51 | "type": "number", 52 | "description": "风扇读取转速ec地址" 53 | }, 54 | "ram_reg_addr": { 55 | "type": "number", 56 | "description": "风扇ecRam寄存器地址" 57 | }, 58 | "ram_reg_data": { 59 | "type": "number", 60 | "description": "风扇ecRam寄存器数据" 61 | }, 62 | "ram_manual_offset": { 63 | "type": "number", 64 | "description": "风扇ecRam手动控制地址" 65 | }, 66 | "ram_rpmwrite_offset": { 67 | "type": "number", 68 | "description": "风扇ecRam写入转速地址" 69 | }, 70 | "ram_rpmread_offset": { 71 | "type": "number", 72 | "description": "风扇ecRam读取转速地址" 73 | }, 74 | "ram_rpmread_length": { 75 | "type": "number", 76 | "description": "风扇ecRam读取转速数据长度, 0为需要通过计算获得转速" 77 | }, 78 | "rpm_write_max": { 79 | "type": "number", 80 | "description": "风扇最大转速ec写入值" 81 | }, 82 | "rpm_value_max": { 83 | "type": "number", 84 | "description": "风扇最大转速值" 85 | }, 86 | "enable_manual_value": { 87 | "type": "number", 88 | "description": "风扇手动控制写入值, 默认 1" 89 | }, 90 | "enable_auto_value": { 91 | "type": "number", 92 | "description": "风扇自动控制写入值 , 默认 0" 93 | } 94 | }, 95 | "required": [] 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/util/enum.ts: -------------------------------------------------------------------------------- 1 | export enum GPUMODE { 2 | NATIVE = "NATIVE", //系统原生设置 3 | NOLIMIT = "NOLIMIT", //不限制 4 | FIX = "FIX", //固定频率 5 | RANGE = "RANGE", //系统调度 6 | AUTO = "AUTO", //自动频率 7 | } 8 | export enum FANMODE { 9 | NOCONTROL = 0, //不控制 10 | FIX = 1, //固定 11 | CURVE = 2, //曲线 12 | } 13 | 14 | export enum FAN_PWM_MODE { 15 | SINGLE = 0, // 单文件 16 | MULTI_SAME = 1, // 多文件相同值 17 | MULTI_DIFF = 2, // 多文件不同值 18 | } 19 | 20 | export enum FANPROFILEACTION { 21 | DELETE = "DELETE", //删除风扇配置 22 | USE = "USE", //使用风扇配置 23 | EDIT = "EDIT", //编辑风扇配置 24 | ADD = "ADD", //添加风扇配置 25 | CANCEL = "CANCEL", //取消当前配置 26 | } 27 | 28 | export enum APPLYTYPE { 29 | SET_ALL = "ALL", 30 | SET_CPUBOOST = "SET_CPUBOOST", 31 | SET_CPUCORE = "SET_CPUCORE", 32 | SET_TDP = "SET_TDP", 33 | SET_CPU_MAX_PERF = "SET_CPU_MAX_PERF", 34 | SET_GPUMODE = "SET_GPUMODE", 35 | SET_FAN_ALL = "SET_FAN_ALL", 36 | SET_FANMODE = "SET_FANMODE", 37 | SET_FANRPM = "SET_FANRPM", 38 | SET_GPUSLIDERFIX = "SET_GPUSLIDEFIX", 39 | SET_CPU_GOVERNOR = "SET_CPU_GOVERNOR", 40 | SET_CPU_SCHED_EXT = "SET_CPU_SCHED_EXT", 41 | SET_EPP = "SET_EPP", 42 | SET_CPU_FREQ_CONTROL = "SET_CPU_FREQ_CONTROL", 43 | SET_POWER_BATTERY = "SET_POWER_BATTERY", 44 | } 45 | 46 | export enum ComponentName { 47 | SET_ENABLE = "SET_ENABLE", 48 | SET_PERAPP = "SET_PERAPP", 49 | CPU_ALL = "CPU_ALL", 50 | CPU_BOOST = "CPU_BOOST", 51 | CPU_SMT = "CPU_SMT", 52 | CPU_NUM = "CPU_NUM", 53 | CPU_TDP = "CPU_TDP", 54 | CPU_PERFORMANCE = "CPU_PERFORMANCE", 55 | CPU_GOVERNOR = "CPU_GOVERNOR", 56 | CPU_EPP = "CPU_EPP", 57 | CPU_FREQ_CONTROL = "CPU_FREQ_CONTROL", 58 | CPU_SCHED_EXT = "CPU_SCHED_EXT", 59 | EPP_LEVEL_1 = "EPP_LEVEL_1", 60 | EPP_LEVEL_2 = "EPP_LEVEL_2", 61 | EPP_LEVEL_3 = "EPP_LEVEL_3", 62 | GPU_ALL = "GPU_ALL", 63 | GPU_FREQMODE = "GPU_FREQMODE", 64 | GPU_FREQFIX = "GPU_FREQFIX", 65 | GPU_FREQRANGE = "GPU_FREQRANGE", 66 | GPU_FREQAUTO = "GPU_FREQAUTO", 67 | GPU_SLIDERFIX = "GPU_SLIDERFIX", 68 | FAN_ALL = "FAN_ALL", 69 | FAN_RPM = "FAN_RPM", 70 | FAN_DISPLAY = "FAN_DISPLAY", 71 | SET_PERACMODE = "SET_PERACMODE", 72 | CUSTOM_TDP = "CUSTOM_TDP", 73 | POWER_ALL = "POWER_ALL", 74 | POWER_BYPASS_CHARGE = "POWER_BYPASS_CHARGE", 75 | POWER_CHARGE_LIMIT = "POWER_CHARGE_LIMIT", 76 | TAB_ALL = "TAB_ALL", 77 | } 78 | 79 | export enum UpdateType { 80 | DISABLE = "DISABLE", 81 | UPDATE = "UPDATE", 82 | HIDE = "HIDE", 83 | SHOW = "SHOW", 84 | ENABLE = "ENABLE", 85 | DISMOUNT = "DISMOUNT", 86 | } 87 | 88 | export enum PluginState { 89 | INIT = "0", 90 | RUN = "1", 91 | QUIT = "2", 92 | ERROR = "3", 93 | } 94 | 95 | export enum Patch { 96 | TDPPatch = "TDPPatch", 97 | GPUPerformancePatch = "GPUPerformancePatch", 98 | } 99 | 100 | export enum GPUPerformanceLevel { 101 | DISABLE = 1, 102 | ENABLE = 2, 103 | } 104 | 105 | export enum SettingChangeEvent { 106 | GPUMODE = "GPUMODE", 107 | } 108 | -------------------------------------------------------------------------------- /src/i18n/tchinese.json: -------------------------------------------------------------------------------- 1 | { 2 | "TITEL_SETTINGS":"設定", 3 | "ENABLE_SETTINGS":"啟用設定", 4 | "USE_PERGAME_PROFILE":"依遊戲設定檔案", 5 | "USING":"正在使用", 6 | "DEFAULT":"預設", 7 | "PROFILE":"設定檔", 8 | "CPU_BOOST":"超頻模式", 9 | "CPU_BOOST_DESC":"提升最大 CPU 頻率", 10 | "HT_DESC":"啟用超執行緒", 11 | "SMT_DESC":"啟用同時多執行緒", 12 | "CPU_NUM":"CPU 核心數", 13 | "CPU_NUM_DESC":"設定啟用的實體核心數", 14 | "CPU_MAX_PERF":"最大效能百分比", 15 | "CPU_MAX_PERF_AUTO":"自動最大效能百分比", 16 | "CPU_GOVERNOR": "CPU 調度器", 17 | "CPU_GOVERNOR_DESC": "設置 CPU 效能調度策略", 18 | "TDP":"熱設計功耗 (TDP) 限制", 19 | "TDP_DESC":"限制處理器功耗以降低總功耗", 20 | "RYZENADJ_NOT_FOUND":"未偵測到 RyzenAdj", 21 | "WATTS":"瓦特", 22 | "GPU_FREQMODE":"GPU 頻率模式", 23 | "UNLIMITED":"不限制", 24 | "FIXED_FREQ":"固定", 25 | "RANGE_FREQ":"範圍", 26 | "AUTO_FREQ":"自動調整", 27 | "GPU_FIX_FREQ":"GPU 頻率", 28 | "GPU_MIN_FREQ":"最低頻率", 29 | "GPU_MAX_FREQ":"最高頻率", 30 | "FAN_SPEED":"風扇轉速", 31 | "CREATE_FAN_PROFILE":"建立風扇設定檔", 32 | "GRID_ALIG":"格線對齊", 33 | "FAN_MODE":"風扇模式", 34 | "NOT_CONTROLLED":"不控制", 35 | "FIXED":"固定", 36 | "CURVE":"曲線", 37 | "SNAP_GRIDLINE":"對齊至格線交點", 38 | "FAN_SPEED_PERCENT":"風扇轉速百分比", 39 | "SENSOR_TEMP":"感測器溫度", 40 | "CREATE_FAN_PROFILE_TIP":"建立風扇設定檔", 41 | "SELECT_FAN_PROFILE_TIP":"選擇風扇設定檔", 42 | "FAN_PROFILE_NAME":"設定檔名稱", 43 | "USE":"使用", 44 | "DELETE":"刪除", 45 | "CREATE":"建立", 46 | "CANCEL":"取消", 47 | "CURENT_STAT":"目前狀態", 48 | "EDIT":"編輯", 49 | "SAVE":"儲存", 50 | "NATIVE_FREQ":"原生設定", 51 | "MORE":"更多", 52 | "REINSTALL_PLUGIN": "重新安裝外掛程式", 53 | "UPDATE_PLUGIN": "更新至", 54 | "ROLLBACK_PLUGIN": "還原至", 55 | "INSTALLED_VERSION": "目前版本", 56 | "LATEST_VERSION": "最新版本", 57 | "GPU_NATIVE_SLIDER": "原生 GPU 控制滑桿", 58 | "GPU_NATIVE_SLIDER_DESC": "啟用原生 GPU 控制滑桿", 59 | "USE_PERACMODE_PROFILE": "依電源模式設定檔", 60 | "AC_MODE": "外接電源模式", 61 | "BAT_MODE": "電池模式", 62 | "CUSTOM_TDP_RANGE": "自訂 TDP 滑桿範圍", 63 | "RESET_ALL": "重設所有設定", 64 | "NATIVE_FREQ_DESC": "透過系統快速選單設定頻率", 65 | "UNLIMITED_DESC": "不限制 GPU 頻率,使用系統預設排程", 66 | "FIXED_FREQ_DESC": "固定 GPU 頻率", 67 | "RANGE_FREQ_DESC": "設定 GPU 頻率範圍", 68 | "AUTO_FREQ_DESC": "GPU 頻率自動調整,強制關閉 TDP 限制,關閉超頻", 69 | "AUTO_FREQ_TDP_NOTIF": "GPU 模式 {{mode}},TDP 限制已關閉", 70 | "NATIVE_TDP_SLIDER": "原生 TDP 滑桿", 71 | "NATIVE_TDP_SLIDER_DESC": "啟用原生 TDP 滑桿, 啟用和關閉需要重啟系統生效。修改 TDP 範圍最大值需要重啟 Steam 生效。開啟後 TDP 設定由 Steam 儲存, 外掛程式設定中的電源狀態設定將不會處理 TDP 設定", 72 | "FORCE_SHOW_TDP": "強制顯示 TDP 控制", 73 | "FORCE_SHOW_TDP_DESC": "預設情況下外掛程式會處理原生 TDP 滑桿。如果原生滑桿有問題,請開啟此選項以使用外掛程式內建的 TDP 控制", 74 | "MANUAL_BYPASS_CHARGE": "手動旁路供電", 75 | "MANUAL_BYPASS_CHARGE_DESC": "手動控制旁路供電開關", 76 | "MANUAL_BYPASS_CHARGE_DESC_WITH_LIMIT": "開啟後將暫時禁用充電限制功能", 77 | "CHARGE_LIMIT": "充電限制", 78 | "CHARGE_LIMIT_DESC": "設置電池充電限制,延長電池壽命", 79 | "CHARGE_LIMIT_DESC_WITH_BYPASS": "手動旁路供電已開啟,充電限制暫時無效", 80 | "USE_OLD_UI": "使用舊版 UI", 81 | "USE_OLD_UI_DESC": "使用舊的列表視圖而不是新的標籤視圖", 82 | "CPU_FREQ_CONTROL": "CPU頻率控制", 83 | "CPU_FREQ_CONTROL_DESC": "限制CPU最大頻率以降低功耗和發熱。架構:{{architecture}}", 84 | "CORE_FREQUENCY": "核心頻率", 85 | "CPU_FREQUENCY": "CPU頻率", 86 | "ALL_CORES": "所有核心", 87 | "CHECK_VERSION": "檢查版本", 88 | "CHECKING_VERSION": "檢查中...", 89 | "LAST_CHECK_TIME": "最後檢查", 90 | "JUST_NOW": "剛剛", 91 | "MINUTES_AGO": "{{minutes}}分鐘前", 92 | "HOURS_AGO": "{{hours}}小時前", 93 | "DAYS_AGO": "{{days}}天前", 94 | "CLICK_TO_CHECK": "點擊檢查最新版本" 95 | } -------------------------------------------------------------------------------- /py_modules/update.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | import shutil 4 | import ssl 5 | import stat 6 | import subprocess 7 | import urllib.request 8 | 9 | import decky 10 | from config import API_URL, logger 11 | 12 | 13 | def recursive_chmod(path, perms): 14 | for dirpath, dirnames, filenames in os.walk(path): 15 | current_perms = os.stat(dirpath).st_mode 16 | os.chmod(dirpath, current_perms | perms) 17 | for filename in filenames: 18 | os.chmod(os.path.join(dirpath, filename), current_perms | perms) 19 | 20 | 21 | def update_latest(): 22 | downloaded_filepath = download_latest_build() 23 | 24 | if os.path.exists(downloaded_filepath): 25 | plugin_dir = decky.DECKY_PLUGIN_DIR 26 | 27 | try: 28 | logger.info(f"removing old plugin from {plugin_dir}") 29 | # add write perms to directory 30 | recursive_chmod(plugin_dir, stat.S_IWUSR) 31 | 32 | # remove old plugin 33 | shutil.rmtree(plugin_dir) 34 | except Exception as e: 35 | logger.error(f"ota error during removal of old plugin: {e}") 36 | 37 | try: 38 | logger.info(f"extracting ota file to {plugin_dir}") 39 | # extract files to decky plugins dir 40 | shutil.unpack_archive( 41 | downloaded_filepath, 42 | f"{decky.DECKY_USER_HOME}/homebrew/plugins", 43 | format="gztar" if downloaded_filepath.endswith(".tar.gz") else "zip", 44 | ) 45 | 46 | # cleanup downloaded files 47 | os.remove(downloaded_filepath) 48 | except Exception as e: 49 | decky.logger.error(f"error during ota file extraction {e}") 50 | 51 | logger.info("restarting plugin_loader") 52 | cmd = "systemctl restart plugin_loader.service" 53 | # cmd = "pkill -HUP PluginLoader" 54 | result = subprocess.run( 55 | cmd, 56 | shell=True, 57 | check=True, 58 | text=True, 59 | stdout=subprocess.PIPE, 60 | stderr=subprocess.PIPE, 61 | env=get_env(), 62 | ) 63 | logger.info(result.stdout) 64 | return result 65 | 66 | 67 | def download_latest_build(): 68 | gcontext = ssl.SSLContext() 69 | 70 | response = urllib.request.urlopen(API_URL, context=gcontext) 71 | json_data = json.load(response) 72 | 73 | download_url = json_data.get("assets")[0].get("browser_download_url") 74 | 75 | logger.info(download_url) 76 | 77 | if download_url.endswith(".zip"): 78 | file_path = f"/tmp/{decky.DECKY_PLUGIN_NAME}.zip" 79 | else: 80 | file_path = f"/tmp/{decky.DECKY_PLUGIN_NAME}.tar.gz" 81 | 82 | with ( 83 | urllib.request.urlopen(download_url, context=gcontext) as response, 84 | open(file_path, "wb") as output_file, 85 | ): 86 | output_file.write(response.read()) 87 | output_file.close() 88 | 89 | return file_path 90 | 91 | 92 | def get_version(): 93 | return f"{decky.DECKY_PLUGIN_VERSION}" 94 | 95 | 96 | def get_latest_version(): 97 | gcontext = ssl.SSLContext() 98 | 99 | response = urllib.request.urlopen(API_URL, context=gcontext) 100 | json_data = json.load(response) 101 | 102 | tag = json_data.get("tag_name") 103 | # if tag is a v* tag, remove the v 104 | if tag.startswith("v"): 105 | tag = tag[1:] 106 | return tag 107 | -------------------------------------------------------------------------------- /py_modules/fan_config/hwmon/asus_orig.yml: -------------------------------------------------------------------------------- 1 | # 原生曲线? 暂时不用 修改 hwmon_name 避免匹配 2 | hwmon_name: asus_custom_fan_curve_orig 3 | 4 | fans: 5 | - fan_name: Fan1 6 | pwm_mode: 2 7 | 8 | pwm_enable: 9 | manual_value: 1 10 | auto_value: 2 11 | pwm_enable_path: pwm1_enable 12 | 13 | pwm_write: 14 | pwm_write_max: 15 | default: 255 16 | ROG Ally RC71L_RC71L: 255 17 | curve_path: # 曲线写入路径 18 | temp_write: 19 | - pwm1_auto_point1_temp 20 | - pwm1_auto_point2_temp 21 | - pwm1_auto_point3_temp 22 | - pwm1_auto_point4_temp 23 | - pwm1_auto_point5_temp 24 | - pwm1_auto_point6_temp 25 | - pwm1_auto_point7_temp 26 | - pwm1_auto_point8_temp 27 | pwm_write: 28 | - pwm1_auto_point1_pwm 29 | - pwm1_auto_point2_pwm 30 | - pwm1_auto_point3_pwm 31 | - pwm1_auto_point4_pwm 32 | - pwm1_auto_point5_pwm 33 | - pwm1_auto_point6_pwm 34 | - pwm1_auto_point7_pwm 35 | - pwm1_auto_point8_pwm 36 | default_curve: 37 | temp: 38 | - 34 39 | - 40 40 | - 50 41 | - 60 42 | - 70 43 | - 80 44 | - 90 45 | - 100 46 | speed: 47 | - 1 48 | - 4 49 | - 8 50 | - 48 51 | - 74 52 | - 74 53 | - 74 54 | - 74 55 | pwm: 56 | - 3 57 | - 10 58 | - 20 59 | - 122 60 | - 189 61 | - 189 62 | - 189 63 | - 189 64 | 65 | pwm_input: 66 | hwmon_label: asus 67 | pwm_read_path: fan1_input 68 | pwm_read_max: 8200 69 | 70 | temp_mode: 0 71 | 72 | - fan_name: Fan2 73 | pwm_mode: 2 74 | 75 | pwm_enable: 76 | manual_value: 1 77 | auto_value: 2 78 | pwm_enable_path: pwm2_enable 79 | 80 | pwm_write: 81 | pwm_write_max: 82 | default: 255 83 | ROG Ally RC71L_RC71L: 255 84 | curve_path: # 曲线写入路径 85 | temp_write: 86 | - pwm2_auto_point1_temp 87 | - pwm2_auto_point2_temp 88 | - pwm2_auto_point3_temp 89 | - pwm2_auto_point4_temp 90 | - pwm2_auto_point5_temp 91 | - pwm2_auto_point6_temp 92 | - pwm2_auto_point7_temp 93 | - pwm2_auto_point8_temp 94 | pwm_write: 95 | - pwm2_auto_point1_pwm 96 | - pwm2_auto_point2_pwm 97 | - pwm2_auto_point3_pwm 98 | - pwm2_auto_point4_pwm 99 | - pwm2_auto_point5_pwm 100 | - pwm2_auto_point6_pwm 101 | - pwm2_auto_point7_pwm 102 | - pwm2_auto_point8_pwm 103 | default_curve: 104 | temp: 105 | - 39 106 | - 40 107 | - 50 108 | - 60 109 | - 70 110 | - 80 111 | - 90 112 | - 100 113 | speed: 114 | - 1 115 | - 1 116 | - 1 117 | - 45 118 | - 45 119 | - 45 120 | - 45 121 | - 45 122 | pwm: 123 | - 3 124 | - 3 125 | - 3 126 | - 115 127 | - 115 128 | - 115 129 | - 115 130 | - 115 131 | 132 | pwm_input: 133 | hwmon_label: asus 134 | pwm_read_path: fan2_input 135 | pwm_read_max: 8200 136 | 137 | temp_mode: 0 138 | -------------------------------------------------------------------------------- /src/components/customTDP.tsx: -------------------------------------------------------------------------------- 1 | import { FC, useEffect, useState } from "react"; 2 | import { 3 | ComponentName, 4 | DEFAULT_TDP_MIN, 5 | PluginManager, 6 | Settings, 7 | UpdateType, 8 | } from "../util"; 9 | import { PanelSectionRow, SliderField, ToggleField } from "@decky/ui"; 10 | import { localizationManager, localizeStrEnum } from "../i18n"; 11 | 12 | export const CustomTDPComponent: FC = () => { 13 | const [show, setShow] = useState(Settings.ensureEnable()); 14 | const [enableCustomTDPRange, setEnableCustomTDPRange] = useState( 15 | Settings.appEnableCustomTDPRange() 16 | ); 17 | const [customTDPRangeMax, setCustomTDPRangeMax] = useState( 18 | Settings.appCustomTDPRangeMax() 19 | ); 20 | // const [customTDPRangeMin, setCustomTDPRangeMin] = useState(Settings.appCustomTDPRangeMin()); 21 | 22 | const hide = (ishide: boolean) => { 23 | setShow(!ishide); 24 | }; 25 | const refresh = () => { 26 | setEnableCustomTDPRange(Settings.appEnableCustomTDPRange()); 27 | setCustomTDPRangeMax(Settings.appCustomTDPRangeMax()); 28 | // setCustomTDPRangeMin(Settings.appCustomTDPRangeMin()); 29 | }; 30 | 31 | useEffect(() => { 32 | PluginManager.listenUpdateComponent( 33 | ComponentName.CUSTOM_TDP, 34 | [ComponentName.CPU_TDP, ComponentName.CUSTOM_TDP], 35 | (_ComponentName, updateType: string) => { 36 | switch (updateType) { 37 | case UpdateType.UPDATE: 38 | refresh(); 39 | break; 40 | case UpdateType.SHOW: 41 | hide(false); 42 | break; 43 | case UpdateType.HIDE: 44 | hide(true); 45 | break; 46 | } 47 | } 48 | ); 49 | }); 50 | 51 | const _sliderMin = DEFAULT_TDP_MIN; 52 | const _sliderMax = 100; 53 | 54 | return ( 55 |
56 | {show && ( 57 | 58 | { 64 | Settings.setEnableCustomTDPRange(val); 65 | }} 66 | /> 67 | 68 | )} 69 | {show && enableCustomTDPRange && ( 70 | 71 | { 80 | if (value > Settings.appCustomTDPRangeMin()) { 81 | Settings.setCustomTDPRangeMax(value); 82 | } 83 | }} 84 | /> 85 | 86 | )} 87 | {/* {show && enableCustomTDPRange && 88 | 89 | { 98 | if (value < Settings.appCustomTDPRangeMax()) { 99 | Settings.setCustomTDPRangeMin(value); 100 | } 101 | }} 102 | /> 103 | } */} 104 |
105 | ); 106 | }; 107 | -------------------------------------------------------------------------------- /py_modules/fan_config/hwmon/asus.yml: -------------------------------------------------------------------------------- 1 | # 略微修改的曲线 提高了高温点的转速 2 | hwmon_name: asus_custom_fan_curve 3 | 4 | fans: 5 | - fan_name: Fan1 6 | pwm_mode: 2 7 | 8 | pwm_enable: 9 | manual_value: 1 10 | auto_value: 2 # 设置自动模式会使用内置的曲线,会受到platform_profile影响,如果要在设置为自动的时候使用下面的曲线,需要设置为1 11 | pwm_enable_path: pwm1_enable 12 | 13 | pwm_write: 14 | pwm_write_max: 15 | default: 255 16 | ROG Ally RC71L_RC71L: 255 17 | curve_path: # 曲线写入路径 18 | temp_write: 19 | - pwm1_auto_point1_temp 20 | - pwm1_auto_point2_temp 21 | - pwm1_auto_point3_temp 22 | - pwm1_auto_point4_temp 23 | - pwm1_auto_point5_temp 24 | - pwm1_auto_point6_temp 25 | - pwm1_auto_point7_temp 26 | - pwm1_auto_point8_temp 27 | pwm_write: 28 | - pwm1_auto_point1_pwm 29 | - pwm1_auto_point2_pwm 30 | - pwm1_auto_point3_pwm 31 | - pwm1_auto_point4_pwm 32 | - pwm1_auto_point5_pwm 33 | - pwm1_auto_point6_pwm 34 | - pwm1_auto_point7_pwm 35 | - pwm1_auto_point8_pwm 36 | default_curve: 37 | temp: 38 | - 34 39 | - 40 40 | - 50 41 | - 60 42 | - 70 43 | - 80 44 | - 90 45 | - 100 46 | speed: 47 | - 1 48 | - 4 49 | - 8 50 | - 48 51 | - 74 52 | - 90 53 | - 95 54 | - 100 55 | pwm: 56 | - 3 57 | - 10 58 | - 20 59 | - 122 60 | - 189 61 | - 255 62 | - 255 63 | - 255 64 | 65 | pwm_input: 66 | hwmon_label: asus 67 | pwm_read_path: fan1_input 68 | pwm_read_max: 8200 69 | 70 | temp_mode: 0 71 | 72 | - fan_name: Fan2 73 | pwm_mode: 2 74 | 75 | pwm_enable: 76 | manual_value: 1 77 | auto_value: 2 78 | pwm_enable_path: pwm2_enable 79 | 80 | pwm_write: 81 | pwm_write_max: 82 | default: 255 83 | ROG Ally RC71L_RC71L: 255 84 | curve_path: # 曲线写入路径 85 | temp_write: 86 | - pwm2_auto_point1_temp 87 | - pwm2_auto_point2_temp 88 | - pwm2_auto_point3_temp 89 | - pwm2_auto_point4_temp 90 | - pwm2_auto_point5_temp 91 | - pwm2_auto_point6_temp 92 | - pwm2_auto_point7_temp 93 | - pwm2_auto_point8_temp 94 | pwm_write: 95 | - pwm2_auto_point1_pwm 96 | - pwm2_auto_point2_pwm 97 | - pwm2_auto_point3_pwm 98 | - pwm2_auto_point4_pwm 99 | - pwm2_auto_point5_pwm 100 | - pwm2_auto_point6_pwm 101 | - pwm2_auto_point7_pwm 102 | - pwm2_auto_point8_pwm 103 | default_curve: 104 | temp: 105 | - 39 106 | - 40 107 | - 50 108 | - 60 109 | - 70 110 | - 80 111 | - 90 112 | - 100 113 | speed: 114 | - 1 115 | - 1 116 | - 1 117 | - 45 118 | - 55 119 | - 65 120 | - 74 121 | - 90 122 | pwm: 123 | - 3 124 | - 3 125 | - 3 126 | - 115 127 | - 140 128 | - 165 129 | - 190 130 | - 229 131 | 132 | pwm_input: 133 | hwmon_label: asus 134 | pwm_read_path: fan2_input 135 | pwm_read_max: 8200 136 | 137 | temp_mode: 0 138 | -------------------------------------------------------------------------------- /src/i18n/schinese.json: -------------------------------------------------------------------------------- 1 | { 2 | "TITEL_SETTINGS":"设置", 3 | "ENABLE_SETTINGS":"启用插件设置", 4 | "USE_PERGAME_PROFILE":"使用按游戏设置的配置文件", 5 | "USING":"正在使用", 6 | "DEFAULT":"默认", 7 | "PROFILE":"配置文件", 8 | "CPU_BOOST":"睿 频", 9 | "CPU_BOOST_DESC":"提升最大cpu频率", 10 | "HT_DESC":"启用超线程", 11 | "SMT_DESC":"启用同步多线程", 12 | "CPU_NUM":"核 心 数", 13 | "CPU_NUM_DESC":"设置启用的物理核心数量", 14 | "CPU_MAX_PERF":"最大性能百分比", 15 | "CPU_MAX_PERF_AUTO":"自动最大性能百分比", 16 | "CPU_GOVERNOR": "CPU 调度器", 17 | "CPU_GOVERNOR_DESC": "选择 CPU 频率调度器", 18 | "CPU_EPP": "能耗性能偏好", 19 | "CPU_EPP_DESC": "在节能和性能之间进行平衡", 20 | "CPU_SCHED_EXT": "SCX 调度器", 21 | "CPU_SCHED_EXT_DESC": "选择 CPU 的 sched_ext 调度器", 22 | "TDP":"热设计功耗 (TDP) 限制", 23 | "TDP_DESC":"限制处理器功耗以降低总功耗", 24 | "RYZENADJ_NOT_FOUND":"未检测到ryzenAdj", 25 | "WATTS":"瓦特", 26 | "GPU_FREQMODE":"GPU 频率模式", 27 | "UNLIMITED":"不限制", 28 | "FIXED_FREQ":"固定频率", 29 | "RANGE_FREQ":"范围频率", 30 | "AUTO_FREQ":"自适应", 31 | "GPU_FIX_FREQ":"GPU 频率", 32 | "GPU_MIN_FREQ":"GPU 最小频率限制", 33 | "GPU_MAX_FREQ":"GPU 最大频率限制", 34 | "FAN_SPEED":"风扇转速", 35 | "CREATE_FAN_PROFILE":"创建风扇配置文件", 36 | "GRID_ALIG":"网格对齐", 37 | "FAN_MODE":"风扇模式", 38 | "NOT_CONTROLLED":"系统", 39 | "FIXED":"固定", 40 | "CURVE":"曲线", 41 | "SNAP_GRIDLINE":"对齐到网格线交点", 42 | "FAN_SPEED_PERCENT":"风扇转速百分比", 43 | "SENSOR_TEMP":"传感器温度", 44 | "CREATE_FAN_PROFILE_TIP":"创建一个风扇配置文件", 45 | "SELECT_FAN_PROFILE_TIP":"选择一个风扇配置文件", 46 | "FAN_PROFILE_NAME":"配置文件名称", 47 | "USE":"使用", 48 | "DELETE":"删除", 49 | "CREATE":"创建", 50 | "CANCEL":"取消", 51 | "CURENT_STAT":"当前状态", 52 | "EDIT":"编辑", 53 | "SAVE":"保存", 54 | "NATIVE_FREQ":"原生设置", 55 | "MORE":"更多", 56 | "REINSTALL_PLUGIN": "重新安装插件", 57 | "UPDATE_PLUGIN": "更新到", 58 | "ROLLBACK_PLUGIN": "回滚到", 59 | "INSTALLED_VERSION": "当前版本", 60 | "LATEST_VERSION": "最新版本", 61 | "GPU_NATIVE_SLIDER": "原生控制条", 62 | "GPU_NATIVE_SLIDER_DESC": "修复并启用原生控制条", 63 | "USE_PERACMODE_PROFILE": "使用按充电状态设置的配置文件", 64 | "AC_MODE": "充电模式", 65 | "BAT_MODE": "电池模式", 66 | "CUSTOM_TDP_RANGE": "自定义 TDP 滑块范围(超出 BIOS 上限的值无效)", 67 | "RESET_ALL": "重置所有设置", 68 | "NATIVE_FREQ_DESC": "使用系统快捷菜单频率设置", 69 | "UNLIMITED_DESC": "不限制 GPU 频率, 系统默认调度", 70 | "FIXED_FREQ_DESC": "固定 GPU 频率", 71 | "RANGE_FREQ_DESC": "设置 GPU 频率范围", 72 | "AUTO_FREQ_DESC": "自适应 GPU 频率, 强制关闭 TDP 限制, 关闭 Boost", 73 | "AUTO_FREQ_TDP_NOTIF": "GPU 模式为{{mode}}, 关闭 TDP 限制", 74 | "NATIVE_TDP_SLIDER": "原生 TDP 滑块", 75 | "NATIVE_TDP_SLIDER_DESC": "启用原生 TDP 滑块, 启用和关闭需要重启系统生效。修改 TDP 范围最大值需要重启 Steam 生效。开启后 TDP 配置由 Steam 保存, 插件设置中的电源状态配置将不会处理 TDP 配置", 76 | "FORCE_SHOW_TDP": "强制显示 TDP 控制", 77 | "FORCE_SHOW_TDP_DESC": "默认情况下插件会对原生 TDP 滑动条进行处理, 如果原生滑动条异常, 打开此项使用插件内 TDP 控制", 78 | "MANUAL_BYPASS_CHARGE": "手动旁路供电", 79 | "MANUAL_BYPASS_CHARGE_DESC": "手动控制旁路供电开关", 80 | "MANUAL_BYPASS_CHARGE_DESC_WITH_LIMIT": "开启后将暂时禁用充电限制功能", 81 | "CHARGE_LIMIT": "充电限制", 82 | "CHARGE_LIMIT_DESC": "设置电池充电限制,延长电池寿命", 83 | "CHARGE_LIMIT_DESC_WITH_BYPASS": "手动旁路供电已开启,充电限制暂时无效", 84 | "USE_OLD_UI": "使用旧版 UI", 85 | "USE_OLD_UI_DESC": "使用旧的列表视图而不是新的标签视图", 86 | "CPU_FREQ_CONTROL": "CPU频率控制", 87 | "CPU_FREQ_CONTROL_DESC": "限制CPU最大频率以降低功耗和发热。架构:{{architecture}}", 88 | "CORE_FREQUENCY": "核心频率", 89 | "CPU_FREQUENCY": "CPU频率", 90 | "ALL_CORES": "所有核心", 91 | "CHECK_VERSION": "检查版本", 92 | "CHECKING_VERSION": "检查中...", 93 | "LAST_CHECK_TIME": "最后检查", 94 | "JUST_NOW": "刚刚", 95 | "MINUTES_AGO": "{{minutes}}分钟前", 96 | "HOURS_AGO": "{{hours}}小时前", 97 | "DAYS_AGO": "{{days}}天前", 98 | "CLICK_TO_CHECK": "点击检查最新版本" 99 | } -------------------------------------------------------------------------------- /src/i18n/koreana.json: -------------------------------------------------------------------------------- 1 | { 2 | "TITEL_SETTINGS":"설정", 3 | "ENABLE_SETTINGS":"설정 활성화", 4 | "USE_PERGAME_PROFILE":"게임별 프로필 사용", 5 | "USING":"사용 중", 6 | "DEFAULT":"기본값", 7 | "PROFILE":"프로필", 8 | "CPU_BOOST":"CPU 부스트", 9 | "CPU_BOOST_DESC":"최대 CPU 주파수 증가", 10 | "HT_DESC":"인텔 하이퍼스레딩 활성화", 11 | "SMT_DESC":"동시 다중 스레딩 활성화", 12 | "CPU_NUM":"CPU 코어 수", 13 | "CPU_NUM_DESC":"활성화할 물리적 코어 설정", 14 | "CPU_MAX_PERF":"CPU 최대 성능", 15 | "CPU_MAX_PERF_AUTO":"자동 CPU 최대 성능", 16 | "CPU_GOVERNOR": "CPU 거버너", 17 | "CPU_GOVERNOR_DESC": "CPU 성능 스케줄링 정책 설정", 18 | "TDP":"열 설계 전력 (TDP) 제한", 19 | "TDP_DESC":"전체 전력 소비를 줄이기 위해 프로세서 전력 제한", 20 | "RYZENADJ_NOT_FOUND":"RyzenAdj를 찾을 수 없음", 21 | "WATTS":"와트", 22 | "GPU_FREQMODE":"GPU 클럭 주파수 모드", 23 | "UNLIMITED":"제한 없음", 24 | "FIXED_FREQ":"고정", 25 | "RANGE_FREQ":"범위", 26 | "AUTO_FREQ":"자동", 27 | "GPU_FIX_FREQ":"GPU 클럭 주파수", 28 | "GPU_MIN_FREQ":"최소 주파수 제한", 29 | "GPU_MAX_FREQ":"최대 주파수 제한", 30 | "FAN_SPEED":"팬 속도", 31 | "CREATE_FAN_PROFILE":"팬 프로필 생성", 32 | "GRID_ALIG":"그리드 정렬", 33 | "FAN_MODE":"팬 모드", 34 | "NOT_CONTROLLED":"제어 안 함", 35 | "FIXED":"고정", 36 | "CURVE":"곡선", 37 | "SNAP_GRIDLINE":"그리드 선 교차점에 스냅", 38 | "FAN_SPEED_PERCENT":"팬 속도 백분율", 39 | "SENSOR_TEMP":"센서 온도", 40 | "CREATE_FAN_PROFILE_TIP":"팬 프로필 생성", 41 | "SELECT_FAN_PROFILE_TIP":"팬 프로필 선택", 42 | "FAN_PROFILE_NAME":"프로필 이름", 43 | "USE":"사용", 44 | "DELETE":"삭제", 45 | "CREATE":"생성", 46 | "CANCEL":"취소", 47 | "CURENT_STAT":"현재 상태", 48 | "EDIT":"편집", 49 | "SAVE":"저장", 50 | "NATIVE_FREQ":"기본", 51 | "MORE":"더 보기", 52 | "REINSTALL_PLUGIN": "플러그인 재설치", 53 | "UPDATE_PLUGIN": "업데이트:", 54 | "ROLLBACK_PLUGIN": "롤백:", 55 | "INSTALLED_VERSION": "설치된 버전", 56 | "LATEST_VERSION": "최신 버전", 57 | "GPU_NATIVE_SLIDER": "기본 GPU 슬라이더", 58 | "GPU_NATIVE_SLIDER_DESC": "기본 GPU 제어 슬라이더 활성화", 59 | "USE_PERACMODE_PROFILE": "전원 모드별 프로필 사용", 60 | "AC_MODE": "AC 모드", 61 | "BAT_MODE": "배터리 모드", 62 | "CUSTOM_TDP_RANGE": "사용자 정의 TDP 슬라이더 범위", 63 | "RESET_ALL": "모두 초기화", 64 | "NATIVE_FREQ_DESC": "시스템 단축 메뉴로 주파수 설정", 65 | "UNLIMITED_DESC": "GPU 주파수 제한 없음, 시스템 기본 스케줄링", 66 | "FIXED_FREQ_DESC": "GPU 주파수 고정", 67 | "RANGE_FREQ_DESC": "GPU 주파수 범위 설정", 68 | "AUTO_FREQ_DESC": "GPU 주파수 자동 조정, TDP 제한 강제 비활성화, 부스트 비활성화", 69 | "AUTO_FREQ_TDP_NOTIF": "GPU 모드 {{mode}}, TDP 제한 비활성화", 70 | "NATIVE_TDP_SLIDER": "기본 TDP 슬라이더", 71 | "NATIVE_TDP_SLIDER_DESC": "기본 TDP 슬라이더 활성화, 활성화/비활성화에는 시스템 재시작이 필요합니다. TDP 범위 최대값 수정에는 Steam 재시작이 필요합니다. 활성화 후 TDP 설정은 Steam에서 저장되며, 플러그인 설정의 전원 상태 설정에서는 TDP 설정을 처리하지 않습니다", 72 | "FORCE_SHOW_TDP": "TDP 제어 강제 표시", 73 | "FORCE_SHOW_TDP_DESC": "기본적으로 플러그인은 기본 TDP 슬라이더를 처리합니다. 기본 슬라이더에 문제가 있는 경우 이 옵션을 활성화하여 플러그인 내부 TDP 제어 사용", 74 | "MANUAL_BYPASS_CHARGE": "우회 충전 수동 제어", 75 | "MANUAL_BYPASS_CHARGE_DESC": "우회 충전을 수동으로 제어합니다", 76 | "MANUAL_BYPASS_CHARGE_DESC_WITH_LIMIT": "활성화하면 충전 제한이 일시적으로 비활성화됩니다", 77 | "CHARGE_LIMIT": "충전 제한", 78 | "CHARGE_LIMIT_DESC": "배터리 수명을 연장하기 위한 충전 제한 설정", 79 | "CHARGE_LIMIT_DESC_WITH_BYPASS": "우회 충전이 활성화되어 충전 제한이 일시적으로 비활성화됨", 80 | "USE_OLD_UI": "이전 UI 사용", 81 | "USE_OLD_UI_DESC": "새로운 탭 보기 대신 이전 목록 보기 사용", 82 | "CPU_FREQ_CONTROL": "CPU 주파수 제어", 83 | "CPU_FREQ_CONTROL_DESC": "전력 소비와 발열을 줄이기 위해 CPU 최대 주파수를 제한합니다。아키텍처: {{architecture}}", 84 | "CORE_FREQUENCY": "코어 주파수", 85 | "CPU_FREQUENCY": "CPU 주파수", 86 | "ALL_CORES": "모든 코어", 87 | "CHECK_VERSION": "버전 확인", 88 | "CHECKING_VERSION": "확인 중...", 89 | "LAST_CHECK_TIME": "마지막 확인", 90 | "JUST_NOW": "방금 전", 91 | "MINUTES_AGO": "{{minutes}}분 전", 92 | "HOURS_AGO": "{{hours}}시간 전", 93 | "DAYS_AGO": "{{days}}일 전", 94 | "CLICK_TO_CHECK": "클릭하여 최신 버전 확인" 95 | } -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | permissions: 4 | contents: write 5 | 6 | on: 7 | workflow_dispatch: 8 | push: 9 | branches: 10 | - "main" 11 | tags: 12 | - "v*.*.*" 13 | 14 | jobs: 15 | build_plugin: 16 | runs-on: ubuntu-latest 17 | container: 18 | image: archlinux:latest 19 | steps: 20 | - name: set git global safe directory 21 | run: | 22 | pacman -Syu git npm tree zip unzip upx --noconfirm 23 | git config --global --add safe.directory $(realpath .) 24 | 25 | - uses: actions/checkout@v4 26 | with: 27 | submodules: true 28 | 29 | # - name: update submodules 30 | # run: git submodule update --init --recursive 31 | 32 | - name: build RyzenAdj 33 | run: | 34 | pacman -S base-devel pciutils cmake --noconfirm --needed --overwrite='*' 35 | cd submodule/RyzenAdj 36 | mkdir build && cd build 37 | cmake -DCMAKE_BUILD_TYPE=Release .. 38 | make 39 | upx -9 ryzenadj 40 | cp -f ryzenadj ../../../bin 41 | 42 | # - name: build python-fuse 43 | # run: | 44 | # PWD=$(pwd) 45 | # echo "PWD: $PWD" 46 | # mkdir -p py_modules/site-packages 47 | # pacman -S python-fuse python-setuptools python-wheel python-installer rsync --noconfirm --needed --overwrite='*' 48 | # cd ${PWD}/submodule/python-fuse && \ 49 | # PYTHONPATH=${PWD}/py_modules/site-packages \ 50 | # python3 setup.py install --prefix=${PWD} --install-lib=install 51 | # cd - 52 | # rsync -av --progress --exclude=*.pyc --exclude=__pycache__ ./submodule/python-fuse/install/fuse*/fuse* ./py_modules/site-packages/ 53 | 54 | - name: change log level 55 | run: | 56 | sed -i 's/logging.DEBUG/logging.INFO/' py_modules/config.py 57 | 58 | - name: build plugin 59 | run: | 60 | npm i -g pnpm 61 | pnpm install --no-frozen-lockfile 62 | pnpm update 63 | pnpm run build 64 | rm -f dist/*.js.map 65 | tar -czvf PowerControl.tar.gz --transform 's,^,PowerControl/,' dist backend py_modules bin *.py *.json *.md *.js LICENSE 66 | # zip 67 | mkdir -p PowerControl 68 | cp -r dist backend py_modules bin *.py *.json *.md *.js LICENSE PowerControl 69 | zip -r PowerControl.zip PowerControl 70 | rm -rf PowerControl 71 | 72 | - name: show files 73 | run: | 74 | tar -tzvf PowerControl.tar.gz 75 | unzip -l PowerControl.zip 76 | - name: Publish Artifacts 77 | uses: actions/upload-artifact@v4 78 | with: 79 | name: PowerControl 80 | path: | 81 | PowerControl.tar.gz 82 | PowerControl.zip 83 | 84 | publish: 85 | if: startsWith(github.ref, 'refs/tags/v') 86 | runs-on: ubuntu-latest 87 | needs: build_plugin 88 | steps: 89 | - run: mkdir /tmp/artifacts 90 | 91 | - name: download artifact 92 | uses: actions/download-artifact@v4 93 | with: 94 | path: /tmp/artifacts 95 | 96 | - run: ls -R /tmp/artifacts 97 | 98 | - name: publish to github release 99 | uses: softprops/action-gh-release@v2 100 | with: 101 | files: | 102 | /tmp/artifacts/PowerControl/PowerControl.tar.gz 103 | /tmp/artifacts/PowerControl/PowerControl.zip 104 | tag_name: ${{ github.ref_name }} 105 | draft: false 106 | generate_release_notes: true 107 | prerelease: contains(github.ref, 'pre') 108 | env: 109 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 110 | -------------------------------------------------------------------------------- /src/i18n/japanese.json: -------------------------------------------------------------------------------- 1 | { 2 | "TITEL_SETTINGS":"設定", 3 | "ENABLE_SETTINGS":"設定を有効にする", 4 | "USE_PERGAME_PROFILE":"ゲームごとのプロファイルを使用", 5 | "USING":"使用中", 6 | "DEFAULT":"デフォルト", 7 | "PROFILE":"プロファイル", 8 | "CPU_BOOST":"CPU ブースト", 9 | "CPU_BOOST_DESC":"CPU の最大周波数を上げる", 10 | "HT_DESC":"Intel ハイパースレッディングを有効にする", 11 | "SMT_DESC":"同時マルチスレッディングを有効にする", 12 | "CPU_NUM":"CPU コア数", 13 | "CPU_NUM_DESC":"有効な物理コアを設定", 14 | "CPU_MAX_PERF":"CPU 最大パフォーマンス", 15 | "CPU_MAX_PERF_AUTO":"自動 CPU 最大パフォーマンス", 16 | "CPU_GOVERNOR": "CPU ガバナー", 17 | "CPU_GOVERNOR_DESC": "CPU パフォーマンススケジューリングポリシーを設定", 18 | "TDP":"熱設計電力 (TDP) 制限", 19 | "TDP_DESC":"総消費電力を抑えるためにプロセッサの電力を制限", 20 | "RYZENADJ_NOT_FOUND":"RyzenAdj が見つかりません", 21 | "WATTS":"ワット", 22 | "GPU_FREQMODE":"GPU クロック周波数モード", 23 | "UNLIMITED":"制限なし", 24 | "FIXED_FREQ":"固定", 25 | "RANGE_FREQ":"範囲", 26 | "AUTO_FREQ":"自動", 27 | "GPU_FIX_FREQ":"GPU クロック周波数", 28 | "GPU_MIN_FREQ":"最小周波数制限", 29 | "GPU_MAX_FREQ":"最大周波数制限", 30 | "FAN_SPEED":"ファン速度", 31 | "CREATE_FAN_PROFILE":"ファンプロファイルを作成", 32 | "GRID_ALIG":"グリッド配置", 33 | "FAN_MODE":"ファンモード", 34 | "NOT_CONTROLLED":"制御なし", 35 | "FIXED":"固定", 36 | "CURVE":"カーブ", 37 | "SNAP_GRIDLINE":"グリッド線の交点にスナップ", 38 | "FAN_SPEED_PERCENT":"ファン速度パーセント", 39 | "SENSOR_TEMP":"センサー温度", 40 | "CREATE_FAN_PROFILE_TIP":"ファンプロファイルを作成", 41 | "SELECT_FAN_PROFILE_TIP":"ファンプロファイルを選択", 42 | "FAN_PROFILE_NAME":"プロファイル名", 43 | "USE":"使用", 44 | "DELETE":"削除", 45 | "CREATE":"作成", 46 | "CANCEL":"キャンセル", 47 | "CURENT_STAT":"現在の状態", 48 | "EDIT":"編集", 49 | "SAVE":"保存", 50 | "NATIVE_FREQ":"ネイティブ", 51 | "MORE":"その他", 52 | "REINSTALL_PLUGIN": "プラグインを再インストール", 53 | "UPDATE_PLUGIN": "更新:", 54 | "ROLLBACK_PLUGIN": "ロールバック:", 55 | "INSTALLED_VERSION": "インストール済みバージョン", 56 | "LATEST_VERSION": "最新バージョン", 57 | "GPU_NATIVE_SLIDER": "ネイティブ GPU スライダー", 58 | "GPU_NATIVE_SLIDER_DESC": "ネイティブ GPU 制御スライダーを有効にする", 59 | "USE_PERACMODE_PROFILE": "電源モードごとのプロファイルを使用", 60 | "AC_MODE": "AC モード", 61 | "BAT_MODE": "バッテリーモード", 62 | "CUSTOM_TDP_RANGE": "カスタム TDP スライダー範囲", 63 | "RESET_ALL": "すべてリセット", 64 | "NATIVE_FREQ_DESC": "システムショートカットメニューで周波数を設定", 65 | "UNLIMITED_DESC": "GPU 周波数制限なし、システムデフォルトのスケジューリング", 66 | "FIXED_FREQ_DESC": "GPU 周波数を固定", 67 | "RANGE_FREQ_DESC": "GPU 周波数範囲を設定", 68 | "AUTO_FREQ_DESC": "GPU 周波数を自動調整、TDP 制限を強制的に無効化、ブーストを無効化", 69 | "AUTO_FREQ_TDP_NOTIF": "GPU モード {{mode}}、TDP 制限無効", 70 | "NATIVE_TDP_SLIDER": "ネイティブ TDP スライダー", 71 | "NATIVE_TDP_SLIDER_DESC": "ネイティブTDPスライダーを有効化、有効/無効の切り替えにはシステムの再起動が必要です。TDP範囲の最大値を変更するにはSteamの再起動が必要です。有効化後、TDP設定はSteamによって保存され、プラグイン設定の電源状態設定ではTDP設定を処理しません", 72 | "FORCE_SHOW_TDP": "TDP 制御を強制表示", 73 | "FORCE_SHOW_TDP_DESC": "デフォルトでは、プラグインはネイティブ TDP スライダーを処理します。ネイティブスライダーに問題がある場合、このオプションを有効にしてプラグイン内部の TDP 制御を使用してください", 74 | "MANUAL_BYPASS_CHARGE": "手動バイパス充電", 75 | "MANUAL_BYPASS_CHARGE_DESC": "バイパス充電を手動で制御", 76 | "MANUAL_BYPASS_CHARGE_DESC_WITH_LIMIT": "有効にすると充電制限が一時的に無効になります", 77 | "CHARGE_LIMIT": "充電制限", 78 | "CHARGE_LIMIT_DESC": "バッテリー寿命を延ばすために充電制限を設定", 79 | "CHARGE_LIMIT_DESC_WITH_BYPASS": "手動バイパス充電が有効、充電制限は一時的に無効", 80 | "USE_OLD_UI": "旧UIを使用", 81 | "USE_OLD_UI_DESC": "新しいタブビューの代わりに旧リストビューを使用する", 82 | "CPU_FREQ_CONTROL": "CPU周波数制御", 83 | "CPU_FREQ_CONTROL_DESC": "CPU最大周波数を制限して消費電力と発熱を抑制。アーキテクチャ:{{architecture}}", 84 | "CORE_FREQUENCY": "コア周波数", 85 | "CPU_FREQUENCY": "CPU周波数", 86 | "ALL_CORES": "全コア", 87 | "CHECK_VERSION": "バージョン確認", 88 | "CHECKING_VERSION": "確認中...", 89 | "LAST_CHECK_TIME": "最終確認", 90 | "JUST_NOW": "たった今", 91 | "MINUTES_AGO": "{{minutes}}分前", 92 | "HOURS_AGO": "{{hours}}時間前", 93 | "DAYS_AGO": "{{days}}日前", 94 | "CLICK_TO_CHECK": "クリックして最新版を確認" 95 | } -------------------------------------------------------------------------------- /py_modules/cpu.py: -------------------------------------------------------------------------------- 1 | import os 2 | import subprocess 3 | 4 | from config import CPU_VENDOR, SH_PATH, logger 5 | from utils import get_env, get_ryzenadj_path 6 | 7 | class CPUManager: 8 | 9 | def __init__(self) -> None: 10 | self.__init_cpu_info() 11 | 12 | def __init_cpu_info(self) -> None: 13 | self._init_hardware_detection() 14 | 15 | def _init_hardware_detection(self): 16 | logger.info('init_hardware_detection') 17 | 18 | def get_hasRyzenadj(self) -> bool: 19 | """检查系统是否安装了ryzenadj工具。 20 | 21 | Returns: 22 | bool: True如果ryzenadj可用,否则False 23 | """ 24 | try: 25 | # 查看ryzenadj路径是否有该文件 26 | if os.path.exists(RYZENADJ_PATH) or os.path.exists("/usr/bin/ryzenadj"): 27 | logger.info("get_hasRyzenadj {}".format(True)) 28 | return True 29 | else: 30 | logger.info("get_hasRyzenadj {}".format(False)) 31 | return False 32 | except Exception: 33 | logger.error("Failed to check ryzenadj tool", exc_info=True) 34 | return False 35 | 36 | def is_intel(self): 37 | return CPU_VENDOR == "GenuineIntel" 38 | 39 | def is_amd(self): 40 | return CPU_VENDOR == "AuthenticAMD" 41 | 42 | def _read_sysfs_int(self, path: str) -> int: 43 | """读取sysfs整数值,失败时返回-1""" 44 | try: 45 | if os.path.exists(path): 46 | with open(path, "r") as f: 47 | return int(f.read().strip()) 48 | except: 49 | pass 50 | return -1 51 | 52 | def get_ryzenadj_info(self) -> str: 53 | """获取Ryzenadj信息。 54 | 55 | Returns: 56 | str: Ryzenadj信息 57 | """ 58 | try: 59 | sys_ryzenadj_path = get_ryzenadj_path() 60 | command = f"{sys_ryzenadj_path} -i" 61 | process = subprocess.run( 62 | command, 63 | shell=True, 64 | stdout=subprocess.PIPE, 65 | stderr=subprocess.PIPE, 66 | text=True, 67 | env=get_env(), 68 | ) 69 | stdout, stderr = process.stdout, process.stderr 70 | if stderr and stdout == "": 71 | logger.error(f"get_ryzenadj_info error:\n{stderr}") 72 | return f"get_ryzenadj_info error:\n{stderr}" 73 | else: 74 | return stdout 75 | except Exception as e: 76 | logger.error(e) 77 | return f"get_ryzenadj_info error:\n{e}" 78 | 79 | def get_rapl_info(self) -> str: 80 | """获取RAPL信息。 81 | 82 | Returns: 83 | str: RAPL信息 84 | """ 85 | rapl_base_path = "/sys/class/powercap/intel-rapl:0" 86 | # if os.path.exists("/sys/class/powercap/intel-rapl/intel-rapl-mmio:0"): 87 | # rapl_base_path = "/sys/class/powercap/intel-rapl-mmio/intel-rapl-mmio:0" 88 | 89 | rapl_info = {} 90 | 91 | for file in os.listdir(rapl_base_path): 92 | # 是文件并且可读 93 | if os.path.isfile(os.path.join(rapl_base_path, file)) and os.access( 94 | os.path.join(rapl_base_path, file), os.R_OK 95 | ): 96 | try: 97 | with open(os.path.join(rapl_base_path, file), "r") as file: 98 | rapl_info[file.name] = file.read().strip() 99 | except Exception as e: 100 | logger.debug(f"get_rapl_info error: {e}") 101 | 102 | # sort by key 103 | rapl_info = dict(sorted(rapl_info.items(), key=lambda x: x[0])) 104 | 105 | logger.info(f"rapl_info: {rapl_info}") 106 | 107 | rapl_info_str = "" 108 | for key, value in rapl_info.items(): 109 | rapl_info_str += f"{key}: {value}\n" 110 | 111 | logger.info(f"rapl_info_str: {rapl_info_str}") 112 | return rapl_info_str 113 | 114 | cpuManager = CPUManager() 115 | -------------------------------------------------------------------------------- /src/util/position.ts: -------------------------------------------------------------------------------- 1 | import { JsonObject, JsonProperty } from "typescript-json-serializer"; 2 | 3 | @JsonObject() 4 | export class FanPosition { 5 | @JsonProperty() 6 | temperature?: number; 7 | @JsonProperty() 8 | fanRPMpercent?: number; 9 | 10 | static tempMax: number = 100; 11 | static fanMax: number = 100; 12 | static fanMin: number = 0; 13 | static tempMin: number = 0; 14 | 15 | constructor(temperature: number, fanRPMpercent: number) { 16 | this.fanRPMpercent = Math.min( 17 | Math.max(fanRPMpercent, FanPosition.fanMin), 18 | FanPosition.fanMax 19 | ); 20 | this.temperature = Math.min( 21 | Math.max(temperature, FanPosition.tempMin), 22 | FanPosition.tempMax 23 | ); 24 | } 25 | 26 | public getCanvasPos(canWidth: number, canHeight: number) { 27 | var canPosx = Math.min( 28 | Math.max((this.temperature!! / FanPosition.tempMax) * canWidth, 0), 29 | canWidth 30 | ); 31 | var canPosy = Math.min( 32 | Math.max((1 - this.fanRPMpercent!! / FanPosition.fanMax) * canHeight, 0), 33 | canHeight 34 | ); 35 | return [canPosx, canPosy]; 36 | } 37 | 38 | public isCloseToOther(other: FanPosition, distance: number) { 39 | var getDis = Math.sqrt( 40 | Math.pow(other.temperature!! - this.temperature!!, 2) + 41 | Math.pow(other.fanRPMpercent!! - this.fanRPMpercent!!, 2) 42 | ); 43 | return getDis <= distance; 44 | } 45 | public static createFanPosByCanPos( 46 | canx: number, 47 | cany: number, 48 | canWidth: number, 49 | canHeight: number 50 | ) { 51 | var temperature = Math.min( 52 | Math.max((canx!! / canWidth) * this.tempMax, this.tempMin), 53 | this.tempMax 54 | ); 55 | var fanRPMpercent = Math.min( 56 | Math.max((1 - cany!! / canHeight) * this.fanMax, this.fanMin), 57 | this.fanMax 58 | ); 59 | return new FanPosition(temperature, fanRPMpercent); 60 | } 61 | } 62 | /* 63 | export class canvasPosition { 64 | @JsonProperty() 65 | canx?:number; 66 | @JsonProperty() 67 | cany?:number; 68 | constructor(canx:number,cany:number){ 69 | this.canx=canx; 70 | this.cany=cany; 71 | } 72 | public getFanPos(canWidth:number,canHeight:number) 73 | { 74 | const tempMax=100; 75 | const fanMax=100; 76 | const fanMin=0; 77 | const tempMin=0; 78 | var temperature=Math.min(Math.max(this.canx!!/canWidth*tempMax,tempMin),tempMax); 79 | var fanRPMpercent=Math.min(Math.max((1-this.cany!!/canHeight)*fanMax,fanMin),fanMax); 80 | return new fanPosition(temperature,fanRPMpercent) 81 | } 82 | } 83 | */ 84 | //通过画布位置来调整文字位置 85 | export const getTextPosByCanvasPos = ( 86 | canPosx: number, 87 | canPosy: number, 88 | canWidth: number, 89 | _canHeight: number 90 | ) => { 91 | var textlen = 55; 92 | var textheight = 12; 93 | var offsetX = 0; 94 | var offsetY = 0; 95 | if (canPosx + textlen / 2 >= canWidth - 5) { 96 | offsetX = canWidth - textlen - canPosx; 97 | } else if (canPosx - textlen / 2 <= 5) { 98 | offsetX = -canPosx; 99 | } else { 100 | offsetX = -textlen / 2 + 2; 101 | } 102 | if (canPosy - textheight <= 5) { 103 | offsetY = textheight + 5; 104 | } else { 105 | offsetY = -textheight; 106 | } 107 | return [canPosx + offsetX, canPosy + offsetY]; 108 | }; 109 | 110 | export const calPointInLine = ( 111 | lineStart: FanPosition, 112 | lineEnd: FanPosition, 113 | calPointIndex: number 114 | ) => { 115 | if (lineStart.temperature!! > lineEnd.temperature!!) return null; 116 | if ( 117 | calPointIndex < lineStart.temperature!! || 118 | calPointIndex > lineEnd.temperature!! 119 | ) 120 | return null; 121 | var deltaY = lineEnd.fanRPMpercent!! - lineStart.fanRPMpercent!!; 122 | var deltaX = lineEnd.temperature!! - lineStart.temperature!!; 123 | var calPointY = 124 | deltaX == 0 125 | ? deltaY 126 | : (calPointIndex - lineStart.temperature!!) * (deltaY / deltaX) + 127 | lineStart.fanRPMpercent!!; 128 | return new FanPosition(calPointIndex, calPointY); 129 | }; 130 | -------------------------------------------------------------------------------- /src/i18n/thai.json: -------------------------------------------------------------------------------- 1 | { 2 | "TITEL_SETTINGS":"การตั้งค่า", 3 | "ENABLE_SETTINGS":"เปิดใช้งานการตั้งค่า", 4 | "USE_PERGAME_PROFILE":"ใช้โปรไฟล์ตามเกม", 5 | "USING":"กำลังใช้งาน", 6 | "DEFAULT":"ค่าเริ่มต้น", 7 | "PROFILE":"โปรไฟล์", 8 | "CPU_BOOST":"เพิ่มประสิทธิภาพ CPU", 9 | "CPU_BOOST_DESC":"เพิ่มความถี่สูงสุดของ CPU", 10 | "HT_DESC":"เปิดใช้งาน Intel Hyper-Threading", 11 | "SMT_DESC":"เปิดใช้งานการประมวลผลแบบหลายเธรดพร้อมกัน", 12 | "CPU_NUM":"จำนวนคอร์ CPU", 13 | "CPU_NUM_DESC":"กำหนดคอร์กายภาพที่เปิดใช้งาน", 14 | "CPU_MAX_PERF":"ประสิทธิภาพสูงสุดของ CPU", 15 | "CPU_MAX_PERF_AUTO":"ประสิทธิภาพสูงสุดของ CPU อัตโนมัติ", 16 | "CPU_GOVERNOR": "ตัวควบคุม CPU", 17 | "CPU_GOVERNOR_DESC": "ตั้งค่านโยบายการจัดการประสิทธิภาพ CPU", 18 | "TDP":"ขีดจำกัดพลังงานความร้อน (TDP)", 19 | "TDP_DESC":"จำกัดกำลังของโปรเซสเซอร์เพื่อลดการใช้พลังงานรวม", 20 | "RYZENADJ_NOT_FOUND":"ไม่พบ RyzenAdj", 21 | "WATTS":"วัตต์", 22 | "GPU_FREQMODE":"โหมดความถี่ GPU", 23 | "UNLIMITED":"ไม่จำกัด", 24 | "FIXED_FREQ":"คงที่", 25 | "RANGE_FREQ":"ช่วง", 26 | "AUTO_FREQ":"อัตโนมัติ", 27 | "GPU_FIX_FREQ":"ความถี่ GPU", 28 | "GPU_MIN_FREQ":"ความถี่ต่ำสุด", 29 | "GPU_MAX_FREQ":"ความถี่สูงสุด", 30 | "FAN_SPEED":"ความเร็วพัดลม", 31 | "CREATE_FAN_PROFILE":"สร้างโปรไฟล์พัดลม", 32 | "GRID_ALIG":"การจัดตำแหน่งตาราง", 33 | "FAN_MODE":"โหมดพัดลม", 34 | "NOT_CONTROLLED":"ไม่ควบคุม", 35 | "FIXED":"คงที่", 36 | "CURVE":"เส้นโค้ง", 37 | "SNAP_GRIDLINE":"ดึงดูดไปที่จุดตัดของเส้นตาราง", 38 | "FAN_SPEED_PERCENT":"ความเร็วพัดลม (%)", 39 | "SENSOR_TEMP":"อุณหภูมิเซ็นเซอร์", 40 | "CREATE_FAN_PROFILE_TIP":"สร้างโปรไฟล์พัดลม", 41 | "SELECT_FAN_PROFILE_TIP":"เลือกโปรไฟล์พัดลม", 42 | "FAN_PROFILE_NAME":"ชื่อโปรไฟล์", 43 | "USE":"ใช้", 44 | "DELETE":"ลบ", 45 | "CREATE":"สร้าง", 46 | "CANCEL":"ยกเลิก", 47 | "CURENT_STAT":"สถานะปัจจุบัน", 48 | "EDIT":"แก้ไข", 49 | "SAVE":"บันทึก", 50 | "NATIVE_FREQ":"ดั้งเดิม", 51 | "MORE":"เพิ่มเติม", 52 | "REINSTALL_PLUGIN": "ติดตั้งปลั๊กอินใหม่", 53 | "UPDATE_PLUGIN": "อัปเดตเป็น", 54 | "ROLLBACK_PLUGIN": "ย้อนกลับเป็น", 55 | "INSTALLED_VERSION": "เวอร์ชันที่ติดตั้ง", 56 | "LATEST_VERSION": "เวอร์ชันล่าสุด", 57 | "GPU_NATIVE_SLIDER": "แถบเลื่อน GPU ดั้งเดิม", 58 | "GPU_NATIVE_SLIDER_DESC": "เปิดใช้งานแถบเลื่อนควบคุม GPU ดั้งเดิม", 59 | "USE_PERACMODE_PROFILE": "ใช้โปรไฟล์ตามโหมดพลังงาน", 60 | "AC_MODE": "โหมดไฟฟ้า", 61 | "BAT_MODE": "โหมดแบตเตอรี่", 62 | "CUSTOM_TDP_RANGE": "ช่วง TDP แบบกำหนดเอง", 63 | "RESET_ALL": "รีเซ็ตทั้งหมด", 64 | "NATIVE_FREQ_DESC": "ตั้งค่าความถี่ผ่านเมนูระบบ", 65 | "UNLIMITED_DESC": "ไม่จำกัดความถี่ GPU, ใช้การจัดการระบบเริ่มต้น", 66 | "FIXED_FREQ_DESC": "ความถี่ GPU คงที่", 67 | "RANGE_FREQ_DESC": "กำหนดช่วงความถี่ GPU", 68 | "AUTO_FREQ_DESC": "ความถี่ GPU แบบปรับตัว, บังคับปิดการจำกัด TDP, ปิด Boost", 69 | "AUTO_FREQ_TDP_NOTIF": "โหมด GPU {{mode}}, ปิดการจำกัด TDP", 70 | "NATIVE_TDP_SLIDER": "แถบเลื่อน TDP ดั้งเดิม", 71 | "NATIVE_TDP_SLIDER_DESC": "เปิดใช้งานแถบเลื่อน TDP แบบดั้งเดิม ต้องรีสตาร์ทระบบเพื่อให้การเปิด/ปิดมีผล การแก้ไขค่าสูงสุดของช่วง TDP ต้องรีสตาร์ท Steam เพื่อให้มีผล หลังจากเปิดใช้งาน การตั้งค่า TDP จะถูกบันทึกโดย Steam และการตั้งค่าสถานะพลังงานในปลั๊กอินจะไม่จัดการการตั้งค่า TDP", 72 | "FORCE_SHOW_TDP": "บังคับแสดงการควบคุม TDP", 73 | "FORCE_SHOW_TDP_DESC": "โดยค่าเริ่มต้น ปลั๊กอินจะประมวลผลแถบเลื่อน TDP ดั้งเดิม หากแถบเลื่อนดั้งเดิมมีปัญหา ให้เปิดใช้งานตัวเลือกนี้เพื่อใช้การควบคุม TDP ภายในของปลั๊กอิน", 74 | "MANUAL_BYPASS_CHARGE": "ควบคุมการชาร์จแบบบายพาสด้วยตนเอง", 75 | "MANUAL_BYPASS_CHARGE_DESC": "ควบคุมการชาร์จแบบบายพาสด้วยตนเอง", 76 | "MANUAL_BYPASS_CHARGE_DESC_WITH_LIMIT": "การเปิดใช้งานจะปิดการจำกัดการชาร์จชั่วคราว", 77 | "CHARGE_LIMIT": "จำกัดการชาร์จ", 78 | "CHARGE_LIMIT_DESC": "ตั้งค่าการจำกัดการชาร์จเพื่อยืดอายุแบตเตอรี่", 79 | "CHARGE_LIMIT_DESC_WITH_BYPASS": "เปิดใช้งานการชาร์จแบบบายพาสแล้ว การจำกัดการชาร์จถูกปิดชั่วคราว", 80 | "USE_OLD_UI": "ใช้ UI แบบเก่า", 81 | "USE_OLD_UI_DESC": "ใช้มุมมองรายการแบบเก่าแทนมุมมองแท็บใหม่", 82 | "CPU_FREQ_CONTROL": "การควบคุมความถี่ CPU", 83 | "CPU_FREQ_CONTROL_DESC": "จำกัดความถี่สูงสุดของ CPU เพื่อลดการใช้พลังงานและความร้อน สถาปัตยกรรม: {{architecture}}", 84 | "CORE_FREQUENCY": "ความถี่แกน", 85 | "CPU_FREQUENCY": "ความถี่ CPU", 86 | "ALL_CORES": "แกนทั้งหมด", 87 | "CHECK_VERSION": "ตรวจสอบเวอร์ชัน", 88 | "CHECKING_VERSION": "กำลังตรวจสอบ...", 89 | "LAST_CHECK_TIME": "การตรวจสอบครั้งล่าสุด", 90 | "JUST_NOW": "เมื่อกี้", 91 | "MINUTES_AGO": "{{minutes}} นาทีที่แล้ว", 92 | "HOURS_AGO": "{{hours}} ชั่วโมงที่แล้ว", 93 | "DAYS_AGO": "{{days}} วันที่แล้ว", 94 | "CLICK_TO_CHECK": "คลิกเพื่อตรวจสอบอัปเดต" 95 | } -------------------------------------------------------------------------------- /py_modules/ec.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | import portio 4 | from config import logger 5 | 6 | EC_CMD_STATUS_REGISTER_PORT = 0x66 7 | EC_DATA_REGISTER_PORT = 0x62 8 | EC_IBF_BIT = 1 9 | EC_OBF_BIT = 0 10 | RD_EC = 0x80 # Read Embedded Controller 11 | WR_EC = 0x81 # Write Embedded Controller 12 | 13 | # for register in [EC_DATA_REGISTER_PORT, EC_CMD_STATUS_REGISTER_PORT]: 14 | # status = portio.ioperm(register, 1, 1) 15 | status = portio.iopl(3) 16 | 17 | 18 | class EC: 19 | @staticmethod 20 | def Wait(port, flag, value): 21 | for i in range(200): 22 | data = portio.inb(port) 23 | if ((data >> flag) & 0x1) == value: 24 | condition = True 25 | break 26 | time.sleep(0.001) 27 | 28 | @staticmethod 29 | def Read(address: int): 30 | EC.Wait(EC_CMD_STATUS_REGISTER_PORT, EC_IBF_BIT, 0) 31 | portio.outb(RD_EC, EC_CMD_STATUS_REGISTER_PORT) 32 | EC.Wait(EC_CMD_STATUS_REGISTER_PORT, EC_IBF_BIT, 0) 33 | portio.outb(address, EC_DATA_REGISTER_PORT) 34 | EC.Wait(EC_CMD_STATUS_REGISTER_PORT, EC_OBF_BIT, 1) 35 | result = portio.inb(EC_DATA_REGISTER_PORT) 36 | logger.debug(f"ECRead address:{hex(address)} value:{result}") 37 | return result 38 | 39 | @staticmethod 40 | def ReadLonger(address: int, length: int): 41 | sum = 0 42 | for len in range(length): 43 | value = EC.Read(address + len) 44 | sum = (sum << 8) + value 45 | # logger.debug(f"count={len} sum={sum} address={address+len} value={value}") 46 | logger.debug(f"ECReadLonger address:{hex(address)} value:{sum}") 47 | return sum 48 | 49 | @staticmethod 50 | def Write(address: int, data: int): 51 | EC.Wait(EC_CMD_STATUS_REGISTER_PORT, EC_IBF_BIT, 0) 52 | portio.outb(WR_EC, EC_CMD_STATUS_REGISTER_PORT) 53 | EC.Wait(EC_CMD_STATUS_REGISTER_PORT, EC_IBF_BIT, 0) 54 | portio.outb(address, EC_DATA_REGISTER_PORT) 55 | EC.Wait(EC_CMD_STATUS_REGISTER_PORT, EC_IBF_BIT, 0) 56 | portio.outb(data, EC_DATA_REGISTER_PORT) 57 | EC.Wait(EC_CMD_STATUS_REGISTER_PORT, EC_IBF_BIT, 0) 58 | logger.debug(f"ECWrite address:{hex(address)} value:{data}") 59 | 60 | @staticmethod 61 | def RamWrite(comm_port: int, data_port: int, address: int, data: int): 62 | high_byte = (address >> 8) & 0xFF 63 | low_byte = address & 0xFF 64 | portio.outb(0x2E, comm_port) 65 | portio.outb(0x11, data_port) 66 | portio.outb(0x2F, comm_port) 67 | portio.outb(high_byte, data_port) 68 | 69 | portio.outb(0x2E, comm_port) 70 | portio.outb(0x10, data_port) 71 | portio.outb(0x2F, comm_port) 72 | portio.outb(low_byte, data_port) 73 | 74 | portio.outb(0x2E, comm_port) 75 | portio.outb(0x12, data_port) 76 | portio.outb(0x2F, comm_port) 77 | portio.outb(data, data_port) 78 | logger.debug( 79 | f"ECRamWrite high_byte={hex(high_byte)} low_byte={hex(low_byte)} address:{hex(address)} value:{data}" 80 | ) 81 | 82 | @staticmethod 83 | def RamRead(comm_port: int, data_port: int, address: int): 84 | high_byte = (address >> 8) & 0xFF 85 | low_byte = address & 0xFF 86 | portio.outb(0x2E, comm_port) 87 | portio.outb(0x11, data_port) 88 | portio.outb(0x2F, comm_port) 89 | portio.outb(high_byte, data_port) 90 | 91 | portio.outb(0x2E, comm_port) 92 | portio.outb(0x10, data_port) 93 | portio.outb(0x2F, comm_port) 94 | portio.outb(low_byte, data_port) 95 | 96 | portio.outb(0x2E, comm_port) 97 | portio.outb(0x12, data_port) 98 | portio.outb(0x2F, comm_port) 99 | data = portio.inb(data_port) 100 | logger.debug( 101 | f"ECRamRead high_byte={hex(high_byte)} low_byte={hex(low_byte)} address:{hex(address)} value:{data}" 102 | ) 103 | return data 104 | 105 | @staticmethod 106 | def RamReadLonger(reg_addr: int, reg_data: int, address: int, length: int): 107 | sum = 0 108 | for len in range(length): 109 | value = EC.RamRead(reg_addr, reg_data, address + len) 110 | sum = (sum << 8) + value 111 | # logger.debug(f"count={len} sum={sum} address={address+len} value={value}") 112 | logger.debug(f"ECReadLonger address:{hex(address)} value:{sum}") 113 | return sum 114 | 115 | def PrintAll(): 116 | print("", "\t", end="") 117 | for z in range(0xF + 1): 118 | print(hex(z), "\t", end="") 119 | print() 120 | for x in range(0xF + 1): 121 | for y in range(0xF + 1): 122 | if y == 0x00: 123 | print(hex(x), "\t", end="") 124 | print(EC.Read((x << 4) + y), "\t", end="") 125 | print() 126 | -------------------------------------------------------------------------------- /src/i18n/english.json: -------------------------------------------------------------------------------- 1 | { 2 | "TITEL_SETTINGS":"Settings", 3 | "ENABLE_SETTINGS":"Enable Settings", 4 | "USE_PERGAME_PROFILE":"Use per-game Profile", 5 | "USING":"Using", 6 | "DEFAULT":" default ", 7 | "PROFILE":"Profile", 8 | "CPU_BOOST":"CPU Boost", 9 | "CPU_BOOST_DESC":"Increase the maximum CPU frequency", 10 | "HT_DESC":"Enable Hyper-threading", 11 | "SMT_DESC":"Enable Simultaneous Multi-Threading", 12 | "CPU_NUM":"Number Of CPU Cores", 13 | "CPU_NUM_DESC":"Set the enabled physical core", 14 | "CPU_MAX_PERF":"CPU Maximum Performance", 15 | "CPU_MAX_PERF_AUTO":"Auto CPU Maximum Performance", 16 | "CPU_GOVERNOR": "CPU Governor", 17 | "CPU_GOVERNOR_DESC": "Select CPU frequency scaling governor", 18 | "CPU_EPP": "Energy Performance Preference", 19 | "CPU_EPP_DESC": "Balance between energy efficiency and performance", 20 | "CPU_SCHED_EXT": "SCX Scheduler", 21 | "CPU_SCHED_EXT_DESC": "Select sched_ext scheduler for CPU", 22 | "TDP":"Thermal Power (TDP) Limit", 23 | "TDP_DESC":"Limits processor power for less total power", 24 | "RYZENADJ_NOT_FOUND":"RyzenAdj Not Detected", 25 | "WATTS":"Watts", 26 | "GPU_FREQMODE":"GPU Clock Frequency Mode", 27 | "UNLIMITED":"Unlimited", 28 | "FIXED_FREQ":"Fixed", 29 | "RANGE_FREQ":"Range", 30 | "AUTO_FREQ":"Auto", 31 | "GPU_FIX_FREQ":"GPU Clock Frequency", 32 | "GPU_MIN_FREQ":"Minimum Frequency Limit", 33 | "GPU_MAX_FREQ":"Maximum Frequency Limit", 34 | "FAN_SPEED":"Fan Speed", 35 | "CREATE_FAN_PROFILE":"Create Fan Profile", 36 | "GRID_ALIG":"Grid Alignment", 37 | "FAN_MODE":"Fan Mode", 38 | "NOT_CONTROLLED":"Not Controlled", 39 | "FIXED":"Fixed", 40 | "CURVE":"Curve", 41 | "SNAP_GRIDLINE":"Snap To The Gridline Intersection", 42 | "FAN_SPEED_PERCENT":"Fan Speed Percentage", 43 | "SENSOR_TEMP":"Sensor Temperature", 44 | "CREATE_FAN_PROFILE_TIP":"Create a Fan Profile", 45 | "SELECT_FAN_PROFILE_TIP":"Select a Fan Profile", 46 | "FAN_PROFILE_NAME":"Profile Name", 47 | "USE":"Use", 48 | "DELETE":"Delete", 49 | "CREATE":"Create", 50 | "CANCEL":"Cancel", 51 | "CURENT_STAT":"Current status", 52 | "EDIT":"Edit", 53 | "SAVE":"Save", 54 | "NATIVE_FREQ":"Native", 55 | "MORE":"More", 56 | "REINSTALL_PLUGIN": "Reinstall Plugin", 57 | "UPDATE_PLUGIN": "Update to", 58 | "ROLLBACK_PLUGIN": "Rollback to", 59 | "INSTALLED_VERSION": "Installed Version", 60 | "LATEST_VERSION": "Latest Version", 61 | "GPU_NATIVE_SLIDER": "Native GPU Slider", 62 | "GPU_NATIVE_SLIDER_DESC": "Enable the native GPU control slider", 63 | "USE_PERACMODE_PROFILE": "Use per-AC Mode Profile", 64 | "AC_MODE": "AC Mode", 65 | "BAT_MODE": "Battery Mode", 66 | "CUSTOM_TDP_RANGE": "Custom TDP Slider Range", 67 | "RESET_ALL": "Reset All", 68 | "NATIVE_FREQ_DESC": "Set frequency using system shortcut menu", 69 | "UNLIMITED_DESC": "No limit on GPU frequency, system default scheduling", 70 | "FIXED_FREQ_DESC": "Fixed GPU frequency", 71 | "RANGE_FREQ_DESC": "Set GPU frequency range", 72 | "AUTO_FREQ_DESC": "Adaptive GPU frequency, forcibly disabled TDP limit, disabled Boost", 73 | "AUTO_FREQ_TDP_NOTIF": "GPU mode {{mode}}, TDP limit disabled", 74 | "NATIVE_TDP_SLIDER": "Native TDP Slider", 75 | "NATIVE_TDP_SLIDER_DESC": "Enable native TDP slider, requires system restart to take effect when enabling/disabling. Modifying TDP range maximum requires Steam restart to take effect. After enabling, TDP settings are saved by Steam, and power state settings in plugin will not handle TDP configuration", 76 | "FORCE_SHOW_TDP": "Force Show TDP Control", 77 | "FORCE_SHOW_TDP_DESC": "By default, the plugin will process the native TDP slider. If there is an issue with the native slider, enable this option to use the plugin's internal TDP control", 78 | "MANUAL_BYPASS_CHARGE": "Manual Bypass Charging", 79 | "MANUAL_BYPASS_CHARGE_DESC": "Manually control bypass charging", 80 | "MANUAL_BYPASS_CHARGE_DESC_WITH_LIMIT": "Enabling this will temporarily disable charge limit", 81 | "CHARGE_LIMIT": "Charge Limit", 82 | "CHARGE_LIMIT_DESC": "Set battery charge limit to extend battery life", 83 | "CHARGE_LIMIT_DESC_WITH_BYPASS": "Manual bypass charging is enabled, charge limit is temporarily disabled", 84 | "USE_OLD_UI": "Use Old UI", 85 | "USE_OLD_UI_DESC": "Use the old List View instead of the new Tab View", 86 | "CPU_FREQ_CONTROL": "CPU Frequency Control", 87 | "CPU_FREQ_CONTROL_DESC": "Limit maximum CPU frequency to reduce power consumption and heat. Architecture: {{architecture}}", 88 | "CORE_FREQUENCY": "Core Frequency", 89 | "CPU_FREQUENCY": "CPU Frequency", 90 | "ALL_CORES": "All Cores", 91 | "CHECK_VERSION": "Check Version", 92 | "CHECKING_VERSION": "Checking...", 93 | "LAST_CHECK_TIME": "Last check", 94 | "JUST_NOW": "just now", 95 | "MINUTES_AGO": "{{minutes}} minutes ago", 96 | "HOURS_AGO": "{{hours}} hours ago", 97 | "DAYS_AGO": "{{days}} days ago", 98 | "CLICK_TO_CHECK": "Click to check for updates" 99 | } -------------------------------------------------------------------------------- /py_modules/inotify.py: -------------------------------------------------------------------------------- 1 | import ctypes 2 | import os 3 | import struct 4 | import sys 5 | from ctypes import CDLL, get_errno 6 | from threading import Thread, Timer 7 | 8 | from config import logger 9 | 10 | IN_ACCESS = 0x00000001 # 文件被访问 11 | IN_MODIFY = 0x00000002 # 文件被修改 12 | IN_ATTRIB = 0x00000004 # 元数据改变 13 | IN_CLOSE_WRITE = 0x00000008 # 可写文件被关闭 14 | IN_CLOSE_NOWRITE = 0x00000010 # 不可写文件被关闭 15 | IN_OPEN = 0x00000020 # 文件被打开 16 | IN_MOVED_FROM = 0x00000040 # 文件从X移动 17 | IN_MOVED_TO = 0x00000080 # 文件被移动到Y 18 | IN_CREATE = 0x00000100 # 子文件创建 19 | IN_DELETE = 0x00000200 # 子文件删除 20 | IN_DELETE_SELF = 0x00000400 # 自身(被监视的项本身)被删除 21 | IN_MOVE_SELF = 0x00000800 # 自身(被监视的项本身)被移动 22 | 23 | 24 | class Inotify: 25 | def __init__(self): 26 | try: 27 | self._libc = CDLL(None, use_errno=True) 28 | self._libc.inotify_init.argtypes = [] 29 | self._libc.inotify_init.restype = ctypes.c_int 30 | self._libc.inotify_add_watch.argtypes = [ 31 | ctypes.c_int, 32 | ctypes.c_char_p, 33 | ctypes.c_uint32, 34 | ] 35 | self._libc.inotify_add_watch.restype = ctypes.c_int 36 | self._libc.inotify_rm_watch.argtypes = [ctypes.c_int, ctypes.c_int] 37 | self._libc.inotify_rm_watch.restype = ctypes.c_int 38 | 39 | self.fd = self._libc.inotify_init() 40 | 41 | self._wdMap = {} 42 | self._delay = 0.5 43 | self._delaytimer = {} 44 | self._runThread = None 45 | except Exception as e: 46 | logger.error(e) 47 | 48 | def _process(self): 49 | try: 50 | if self._runThread: 51 | nowThread = self._runThread.name 52 | while self._runThread and self._runThread.name == nowThread: 53 | buf = os.read(self.fd, 4096) 54 | pos = 0 55 | wdMap = self._wdMap.copy() 56 | while pos < len(buf): 57 | (wd, mask, cookie, name_len) = struct.unpack( 58 | "iIII", buf[pos : pos + 16] 59 | ) 60 | pos += 16 61 | (name,) = struct.unpack("%ds" % name_len, buf[pos : pos + name_len]) 62 | pos += name_len 63 | item = wdMap.get(wd) 64 | if item and self._runThread and self._runThread.name == nowThread: 65 | self._delayCall(wd, item["callback"], item["path"], mask) 66 | except Exception as e: 67 | logger.error(e) 68 | 69 | def _delayCall(self, wd, callfunc, *args): 70 | try: 71 | if ( 72 | wd in self._delaytimer 73 | and self._delaytimer[wd] is not None 74 | and self._delaytimer[wd].is_alive() 75 | ): 76 | self._delaytimer[wd].cancel() 77 | self._delaytimer[wd] = Timer( 78 | self._delay, lambda: self._onCallBack(callfunc, *args) 79 | ) 80 | self._delaytimer[wd].start() 81 | except Exception as e: 82 | logger.error(e) 83 | 84 | def _onCallBack(self, callfunc, *args): 85 | logger.debug(f"callback path:{args[0]}, mask:{args[1]}") 86 | callfunc(*args) 87 | 88 | def add_watch(self, path, mask, callback): 89 | try: 90 | logger.debug( 91 | f"add_watch(self, path:{path}, mask:{mask}, callback:{callback})" 92 | ) 93 | path_buf = ctypes.create_string_buffer( 94 | path.encode(sys.getfilesystemencoding()) 95 | ) 96 | wd = self._libc.inotify_add_watch(self.fd, path_buf, mask) 97 | self._wdMap[wd] = {"path": path, "callback": callback} 98 | if wd < 0: 99 | sys.stderr.write( 100 | f"can't add watch for {path_buf}: {os.strerror(get_errno())}\n" 101 | ) 102 | return wd 103 | except Exception as e: 104 | logger.error(e) 105 | 106 | def remove_watch(self, path): 107 | try: 108 | for wd in list(self._wdMap): 109 | if path == self._wdMap[wd]["path"]: 110 | if self._libc.inotify_rm_watch(self.fd, wd) < 0: 111 | sys.stderr.write( 112 | f"can't remove watch: {os.strerror(get_errno())}\n" 113 | ) 114 | else: 115 | self._wdMap.pop(wd) 116 | except Exception as e: 117 | logger.error(e) 118 | 119 | def run(self): 120 | try: 121 | if self._runThread: 122 | pass 123 | else: 124 | self._runThread = Thread(target=self._process) 125 | self._runThread.start() 126 | except Exception as e: 127 | logger.error(e) 128 | 129 | def stop(self): 130 | self._runThread = None 131 | 132 | 133 | notify = Inotify() 134 | notify.run() 135 | -------------------------------------------------------------------------------- /src/i18n/german.json: -------------------------------------------------------------------------------- 1 | { 2 | "TITEL_SETTINGS":"Einstellungen", 3 | "ENABLE_SETTINGS":"Einstellungen aktivieren", 4 | "USE_PERGAME_PROFILE":"Spielbezogene Profil", 5 | "USING":"Verwendet", 6 | "DEFAULT":"Standard", 7 | "PROFILE":"Profil", 8 | "CPU_BOOST":"CPU Boost", 9 | "CPU_BOOST_DESC":"Maximale CPU Frequenz erhöhen", 10 | "HT_DESC":"Hyperthreading aktivieren", 11 | "SMT_DESC":"Simultanes Multithreading aktivieren", 12 | "CPU_NUM":"Anzahl CPU-Kerne", 13 | "CPU_NUM_DESC":"Anzahl physischer CPU-Kerne", 14 | "CPU_MAX_PERF":"CPU Maximale Leistung", 15 | "CPU_MAX_PERF_AUTO":"Automatische CPU Maximale Leistung", 16 | "CPU_GOVERNOR": "CPU-Taktgeber", 17 | "CPU_GOVERNOR_DESC": "CPU-Leistungsplanungsrichtlinie festlegen", 18 | "TDP":"Thermische Verlustleistung (TDP) Limit", 19 | "TDP_DESC":"CPU-Leistung einschränken um Strom zu sparen", 20 | "RYZENADJ_NOT_FOUND":"RyzenAdj nicht gefunden", 21 | "WATTS":"Watt", 22 | "GPU_FREQMODE":"GPU-Frequenzmodus", 23 | "UNLIMITED":"Unbeschränkt", 24 | "FIXED_FREQ":"Fest", 25 | "RANGE_FREQ":"Bereich", 26 | "AUTO_FREQ":"Automatisch", 27 | "GPU_FIX_FREQ":"GPU-Frequenz", 28 | "GPU_MIN_FREQ":"Minimale Frequenz", 29 | "GPU_MAX_FREQ":"Maximale Frequenz", 30 | "FAN_SPEED":"Lüftergeschwindigkeit", 31 | "CREATE_FAN_PROFILE":"Lüfterprofil erstellen", 32 | "GRID_ALIG":"Gitternetz-Ausrichtung", 33 | "FAN_MODE":"Lüftermodus", 34 | "NOT_CONTROLLED":"Nicht gesteuert", 35 | "FIXED":"Fest", 36 | "CURVE":"Kurve", 37 | "SNAP_GRIDLINE":"An Gitterlinien ausrichten", 38 | "FAN_SPEED_PERCENT":"Lüftergeschwindigkeit (%)", 39 | "SENSOR_TEMP":"Sensortemperatur", 40 | "CREATE_FAN_PROFILE_TIP":"Lüfterprofil erstellen", 41 | "SELECT_FAN_PROFILE_TIP":"Lüfterprofil auswählen", 42 | "FAN_PROFILE_NAME":"Profilname", 43 | "USE":"Verwenden", 44 | "DELETE":"Löschen", 45 | "CREATE":"Erstellen", 46 | "CANCEL":"Abbrechen", 47 | "CURENT_STAT":"Aktueller Status", 48 | "EDIT":"Bearbeiten", 49 | "SAVE":"Speichern", 50 | "NATIVE_FREQ":"Nativ", 51 | "MORE":"Mehr", 52 | "REINSTALL_PLUGIN": "Plugin neu installieren", 53 | "UPDATE_PLUGIN": "Aktualisieren auf", 54 | "ROLLBACK_PLUGIN": "Zurücksetzen auf", 55 | "INSTALLED_VERSION": "Installierte Version", 56 | "LATEST_VERSION": "Neueste Version", 57 | "GPU_NATIVE_SLIDER": "Nativer GPU-Schieberegler", 58 | "GPU_NATIVE_SLIDER_DESC": "Nativen GPU-Schieberegler aktivieren", 59 | "USE_PERACMODE_PROFILE": "Profil nach Stromversorgung", 60 | "AC_MODE": "Netzbetrieb", 61 | "BAT_MODE": "Akkubetrieb", 62 | "CUSTOM_TDP_RANGE": "Benutzerdefinierter TDP-Bereich", 63 | "RESET_ALL": "Alles zurücksetzen", 64 | "NATIVE_FREQ_DESC": "Frequenz über Systemmenü einstellen", 65 | "UNLIMITED_DESC": "Keine GPU-Frequenzbegrenzung, Standard-Systemplanung", 66 | "FIXED_FREQ_DESC": "Feste GPU-Frequenz", 67 | "RANGE_FREQ_DESC": "GPU-Frequenzbereich festlegen", 68 | "AUTO_FREQ_DESC": "Adaptive GPU-Frequenz, TDP-Limit deaktiviert, Boost deaktiviert", 69 | "AUTO_FREQ_TDP_NOTIF": "GPU-Modus {{mode}}, TDP-Limit deaktiviert", 70 | "NATIVE_TDP_SLIDER": "Nativer TDP-Schieberegler", 71 | "NATIVE_TDP_SLIDER_DESC": "Aktivieren Sie den nativen TDP-Schieberegler, ein Neustart des Systems ist erforderlich, um das Aktivieren/Deaktivieren wirksam zu machen. Die Änderung des TDP-Bereichsmaximums erfordert einen Steam-Neustart, um wirksam zu werden. Nach der Aktivierung werden die TDP-Einstellungen von Steam gespeichert, und die Stromzustandseinstellungen im Plugin werden die TDP-Konfiguration nicht verarbeiten", 72 | "FORCE_SHOW_TDP": "TDP-Steuerung erzwingen", 73 | "FORCE_SHOW_TDP_DESC": "Standardmäßig verarbeitet das Plugin den nativen TDP-Schieberegler. Bei Problemen mit dem nativen Schieberegler aktivieren Sie diese Option, um die interne TDP-Steuerung des Plugins zu verwenden", 74 | "MANUAL_BYPASS_CHARGE": "Manuelle Bypass-Ladung", 75 | "MANUAL_BYPASS_CHARGE_DESC": "Bypass-Ladung manuell steuern", 76 | "MANUAL_BYPASS_CHARGE_DESC_WITH_LIMIT": "Bei Aktivierung wird die Ladelimitierung vorübergehend deaktiviert", 77 | "CHARGE_LIMIT": "Ladelimitierung", 78 | "CHARGE_LIMIT_DESC": "Ladelimitierung zum Schutz der Akkulebensdauer einstellen", 79 | "CHARGE_LIMIT_DESC_WITH_BYPASS": "Bypass-Ladung ist aktiv, Ladelimitierung vorübergehend deaktiviert", 80 | "USE_OLD_UI": "Alte Benutzeroberfläche verwenden", 81 | "USE_OLD_UI_DESC": "Die alte Listenansicht anstelle der neuen Tab-Ansicht verwenden", 82 | "CPU_FREQ_CONTROL": "CPU-Frequenzsteuerung", 83 | "CPU_FREQ_CONTROL_DESC": "CPU-Maximalfrequenz begrenzen um Stromverbrauch und Hitze zu reduzieren. Architektur: {{architecture}}", 84 | "CORE_FREQUENCY": "Kernfrequenz", 85 | "CPU_FREQUENCY": "CPU-Frequenz", 86 | "ALL_CORES": "Alle Kerne", 87 | "CHECK_VERSION": "Version prüfen", 88 | "CHECKING_VERSION": "Prüfe...", 89 | "LAST_CHECK_TIME": "Letzte Prüfung", 90 | "JUST_NOW": "gerade eben", 91 | "MINUTES_AGO": "vor {{minutes}} Minuten", 92 | "HOURS_AGO": "vor {{hours}} Stunden", 93 | "DAYS_AGO": "vor {{days}} Tagen", 94 | "CLICK_TO_CHECK": "Klicken um nach Updates zu suchen" 95 | } -------------------------------------------------------------------------------- /src/i18n/italian.json: -------------------------------------------------------------------------------- 1 | { 2 | "TITEL_SETTINGS":"Impostazioni", 3 | "ENABLE_SETTINGS":"Abilita impostazioni", 4 | "USE_PERGAME_PROFILE":"Usa profilo per gioco", 5 | "USING":"In uso", 6 | "DEFAULT":"Predefinito", 7 | "PROFILE":"Profilo", 8 | "CPU_BOOST":"Boost CPU", 9 | "CPU_BOOST_DESC":"Aumenta la frequenza massima della CPU", 10 | "HT_DESC":"Abilita Intel Hyper-Threading", 11 | "SMT_DESC":"Abilita il multithreading simultaneo", 12 | "CPU_NUM":"Numero di core CPU", 13 | "CPU_NUM_DESC":"Imposta i core fisici attivi", 14 | "CPU_MAX_PERF":"Prestazioni massime della CPU", 15 | "CPU_MAX_PERF_AUTO":"Auto prestazioni massime della CPU", 16 | "CPU_GOVERNOR": "Governatore CPU", 17 | "CPU_GOVERNOR_DESC": "Imposta la politica di pianificazione delle prestazioni della CPU", 18 | "TDP":"Limite di potenza termica (TDP)", 19 | "TDP_DESC":"Limita la potenza del processore per ridurre il consumo totale", 20 | "RYZENADJ_NOT_FOUND":"RyzenAdj non trovato", 21 | "WATTS":"Watt", 22 | "GPU_FREQMODE":"Modalità frequenza GPU", 23 | "UNLIMITED":"Illimitato", 24 | "FIXED_FREQ":"Fisso", 25 | "RANGE_FREQ":"Intervallo", 26 | "AUTO_FREQ":"Auto", 27 | "GPU_FIX_FREQ":"Frequenza GPU", 28 | "GPU_MIN_FREQ":"Frequenza minima", 29 | "GPU_MAX_FREQ":"Frequenza massima", 30 | "FAN_SPEED":"Velocità ventola", 31 | "CREATE_FAN_PROFILE":"Crea profilo ventola", 32 | "GRID_ALIG":"Allineamento griglia", 33 | "FAN_MODE":"Modalità ventola", 34 | "NOT_CONTROLLED":"Non controllato", 35 | "FIXED":"Fisso", 36 | "CURVE":"Curva", 37 | "SNAP_GRIDLINE":"Aggancia alle intersezioni della griglia", 38 | "FAN_SPEED_PERCENT":"Velocità ventola (%)", 39 | "SENSOR_TEMP":"Temperatura sensore", 40 | "CREATE_FAN_PROFILE_TIP":"Crea un profilo ventola", 41 | "SELECT_FAN_PROFILE_TIP":"Seleziona un profilo ventola", 42 | "FAN_PROFILE_NAME":"Nome profilo", 43 | "USE":"Usa", 44 | "DELETE":"Elimina", 45 | "CREATE":"Crea", 46 | "CANCEL":"Annulla", 47 | "CURENT_STAT":"Stato attuale", 48 | "EDIT":"Modifica", 49 | "SAVE":"Salva", 50 | "NATIVE_FREQ":"Nativo", 51 | "MORE":"Altro", 52 | "REINSTALL_PLUGIN": "Reinstalla plugin", 53 | "UPDATE_PLUGIN": "Aggiorna a", 54 | "ROLLBACK_PLUGIN": "Ripristina a", 55 | "INSTALLED_VERSION": "Versione installata", 56 | "LATEST_VERSION": "Ultima versione", 57 | "GPU_NATIVE_SLIDER": "Slider GPU nativo", 58 | "GPU_NATIVE_SLIDER_DESC": "Abilita lo slider di controllo GPU nativo", 59 | "USE_PERACMODE_PROFILE": "Usa profilo per modalità alimentazione", 60 | "AC_MODE": "Modalità AC", 61 | "BAT_MODE": "Modalità batteria", 62 | "CUSTOM_TDP_RANGE": "Intervallo TDP personalizzato", 63 | "RESET_ALL": "Ripristina tutto", 64 | "NATIVE_FREQ_DESC": "Imposta la frequenza tramite il menu di sistema", 65 | "UNLIMITED_DESC": "Nessun limite di frequenza GPU, pianificazione predefinita del sistema", 66 | "FIXED_FREQ_DESC": "Frequenza GPU fissa", 67 | "RANGE_FREQ_DESC": "Imposta intervallo di frequenza GPU", 68 | "AUTO_FREQ_DESC": "Frequenza GPU adattiva, limite TDP disabilitato forzatamente, Boost disabilitato", 69 | "AUTO_FREQ_TDP_NOTIF": "Modalità GPU {{mode}}, limite TDP disabilitato", 70 | "NATIVE_TDP_SLIDER": "Slider TDP nativo", 71 | "NATIVE_TDP_SLIDER_DESC": "Abilita il cursore TDP nativo, richiede il riavvio del sistema per rendere effettiva l'attivazione/disattivazione. La modifica del valore massimo dell'intervallo TDP richiede il riavvio di Steam per avere effetto. Dopo l'attivazione, le impostazioni TDP vengono salvate da Steam e le impostazioni dello stato di alimentazione nel plugin non gestiranno la configurazione TDP", 72 | "FORCE_SHOW_TDP": "Forza visualizzazione controllo TDP", 73 | "FORCE_SHOW_TDP_DESC": "Per impostazione predefinita, il plugin elabora lo slider TDP nativo. Se lo slider nativo presenta problemi, abilita questa opzione per utilizzare il controllo TDP interno del plugin", 74 | "MANUAL_BYPASS_CHARGE": "Carica bypass manuale", 75 | "MANUAL_BYPASS_CHARGE_DESC": "Controlla manualmente la carica bypass", 76 | "MANUAL_BYPASS_CHARGE_DESC_WITH_LIMIT": "L'attivazione disabiliterà temporaneamente il limite di carica", 77 | "CHARGE_LIMIT": "Limite di carica", 78 | "CHARGE_LIMIT_DESC": "Imposta un limite di carica per prolungare la durata della batteria", 79 | "CHARGE_LIMIT_DESC_WITH_BYPASS": "Carica bypass attiva, limite di carica temporaneamente disabilitato", 80 | "USE_OLD_UI": "Usa la vecchia interfaccia", 81 | "USE_OLD_UI_DESC": "Usa la vecchia visualizzazione a lista invece della nuova visualizzazione a schede", 82 | "CPU_FREQ_CONTROL": "Controllo frequenza CPU", 83 | "CPU_FREQ_CONTROL_DESC": "Limita la frequenza massima della CPU per ridurre consumo e calore. Architettura: {{architecture}}", 84 | "CORE_FREQUENCY": "Frequenza del core", 85 | "CPU_FREQUENCY": "Frequenza CPU", 86 | "ALL_CORES": "Tutti i core", 87 | "CHECK_VERSION": "Controlla versione", 88 | "CHECKING_VERSION": "Controllo...", 89 | "LAST_CHECK_TIME": "Ultimo controllo", 90 | "JUST_NOW": "proprio ora", 91 | "MINUTES_AGO": "{{minutes}} minuti fa", 92 | "HOURS_AGO": "{{hours}} ore fa", 93 | "DAYS_AGO": "{{days}} giorni fa", 94 | "CLICK_TO_CHECK": "Clicca per controllare gli aggiornamenti" 95 | } -------------------------------------------------------------------------------- /src/components/fanCanvas.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from "react"; 2 | import { FC } from "react"; 3 | import { FanPosition } from "../util"; 4 | export interface FanCanvasProps { 5 | width: number; 6 | height: number; 7 | style: any; 8 | initDraw?(canvasRef: any): void; 9 | onPointerDown?(position: any): void; 10 | onPointerMove?(position: any): void; 11 | onPointerUp?(position: any): void; 12 | onPointerShortPress?(position: any): void; 13 | onPointerLongPress?(position: any): void; 14 | onPointerDragDown?(position: any): boolean; 15 | onPointerDraging?(position: any): void; 16 | } 17 | export const FanCanvas: FC = (canvas) => { 18 | const pointerDownPos: any = useRef(null); 19 | const pointerDownTime: any = useRef(null); 20 | const pointerUpPos: any = useRef(null); 21 | const pointerUpTime: any = useRef(null); 22 | const pointerIsDrag = useRef(false); 23 | const canvasRef: any = useRef(null); 24 | useEffect(() => { 25 | canvas.initDraw?.call(canvas, canvasRef.current); 26 | }, []); 27 | 28 | function getlayerXY(e: any): { layerX: number; layerY: number } { 29 | const realEvent: any = e.nativeEvent; 30 | const rect = canvasRef.current.getBoundingClientRect(); 31 | const x = realEvent.clientX - rect.left; 32 | const y = realEvent.clientY - rect.top; 33 | return { layerX: x, layerY: y }; 34 | } 35 | 36 | function onPointerDown(e: any): void { 37 | const { layerX, layerY } = getlayerXY(e); 38 | const fanClickPos = FanPosition.createFanPosByCanPos( 39 | layerX, 40 | layerY, 41 | canvas.width, 42 | canvas.height 43 | ); 44 | pointerDownPos.current = [layerX, layerY]; 45 | pointerDownTime.current = Date.parse(new Date().toString()); 46 | canvas.onPointerDown?.call(canvas, fanClickPos); 47 | onDragDown(e); 48 | } 49 | 50 | function onPointerUp(e: any): void { 51 | const { layerX, layerY } = getlayerXY(e); 52 | const fanClickPos = FanPosition.createFanPosByCanPos( 53 | layerX, 54 | layerY, 55 | canvas.width, 56 | canvas.height 57 | ); 58 | pointerUpPos.current = [layerX, layerY]; 59 | pointerUpTime.current = Date.parse(new Date().toString()); 60 | canvas.onPointerUp?.call(canvas, fanClickPos); 61 | //call PointPressEvent 62 | if ( 63 | approximatelyEqual( 64 | pointerDownPos.current[0], 65 | pointerUpPos.current[0], 66 | 3 67 | ) && 68 | approximatelyEqual(pointerDownPos.current[1], pointerUpPos.current[1], 3) 69 | ) { 70 | if (pointerUpTime.current - pointerDownTime.current <= 1000) 71 | onPointerShortPress(e); 72 | else onPointLongPress(e); 73 | } 74 | //console.log(`pressDownTime=${pointerDownTime.current} pressUpTime=${pointerUpTime.current}`) 75 | if (pointerIsDrag.current) { 76 | pointerIsDrag.current = false; 77 | } 78 | } 79 | 80 | function onPointerMove(e: any): void { 81 | const { layerX, layerY } = getlayerXY(e); 82 | const fanClickPos = FanPosition.createFanPosByCanPos( 83 | layerX, 84 | layerY, 85 | canvas.width, 86 | canvas.height 87 | ); 88 | canvas.onPointerMove?.call(canvas, fanClickPos); 89 | if (pointerIsDrag.current) { 90 | onDraging(e); 91 | } 92 | } 93 | function onPointerLeave(_e: any): void { 94 | if (pointerIsDrag.current) { 95 | pointerIsDrag.current = false; 96 | } 97 | } 98 | 99 | function onPointerShortPress(e: any): void { 100 | const { layerX, layerY } = getlayerXY(e); 101 | const fanClickPos = FanPosition.createFanPosByCanPos( 102 | layerX, 103 | layerY, 104 | canvas.width, 105 | canvas.height 106 | ); 107 | canvas.onPointerShortPress?.call(canvas, fanClickPos); 108 | } 109 | //@ts-ignore 110 | function onPointLongPress(e: any): void {} 111 | function onDragDown(e: any): void { 112 | const { layerX, layerY } = getlayerXY(e); 113 | const fanClickPos = FanPosition.createFanPosByCanPos( 114 | layerX, 115 | layerY, 116 | canvas.width, 117 | canvas.height 118 | ); 119 | pointerIsDrag.current = canvas.onPointerDragDown?.call( 120 | canvas, 121 | fanClickPos 122 | )!!; 123 | } 124 | 125 | function onDraging(e: any): void { 126 | const { layerX, layerY } = getlayerXY(e); 127 | const fanClickPos = FanPosition.createFanPosByCanPos( 128 | layerX, 129 | layerY, 130 | canvas.width, 131 | canvas.height 132 | ); 133 | canvas.onPointerDraging?.call(canvas, fanClickPos); 134 | } 135 | 136 | const { ...option } = canvas; 137 | 138 | return ( 139 | onClickCanvas(e)} 142 | onPointerDown={(e: any) => { 143 | onPointerDown(e); 144 | }} 145 | onPointerMove={(e: any) => { 146 | onPointerMove(e); 147 | }} 148 | onPointerUp={(e: any) => { 149 | onPointerUp(e); 150 | }} 151 | onPointerLeave={(e: any) => { 152 | onPointerLeave(e); 153 | }} 154 | {...option} 155 | /> 156 | ); 157 | }; 158 | 159 | const approximatelyEqual = (a: number, b: number, error: number) => { 160 | return Math.abs(b - a) <= error; 161 | }; 162 | -------------------------------------------------------------------------------- /src/i18n/french.json: -------------------------------------------------------------------------------- 1 | { 2 | "TITEL_SETTINGS":"Paramètres", 3 | "ENABLE_SETTINGS":"Activer les paramètres", 4 | "USE_PERGAME_PROFILE":"Utiliser un profil par jeu", 5 | "USING":"En cours d'utilisation", 6 | "DEFAULT":"Par défaut", 7 | "PROFILE":"Profil", 8 | "CPU_BOOST":"Boost CPU", 9 | "CPU_BOOST_DESC":"Augmenter la fréquence maximale du processeur", 10 | "HT_DESC":"Activer l'Hyper-Threading Intel", 11 | "SMT_DESC":"Activer le multithreading simultané", 12 | "CPU_NUM":"Nombre de cœurs processeur", 13 | "CPU_NUM_DESC":"Définir les cœurs physiques activés", 14 | "CPU_MAX_PERF":"Performance maximale du CPU", 15 | "CPU_MAX_PERF_AUTO":"Auto CPU Maximum Performance", 16 | "CPU_GOVERNOR": "Gouverneur CPU", 17 | "CPU_GOVERNOR_DESC": "Définir la politique de planification des performances du CPU", 18 | "TDP":"Limite de puissance thermique (TDP)", 19 | "TDP_DESC":"Limite la puissance du processeur pour réduire la consommation totale", 20 | "RYZENADJ_NOT_FOUND":"RyzenAdj non trouvé", 21 | "WATTS":"Watts", 22 | "GPU_FREQMODE":"Mode de fréquence GPU", 23 | "UNLIMITED":"Illimité", 24 | "FIXED_FREQ":"Fixe", 25 | "RANGE_FREQ":"Plage", 26 | "AUTO_FREQ":"Auto", 27 | "GPU_FIX_FREQ":"Fréquence GPU", 28 | "GPU_MIN_FREQ":"Fréquence minimale", 29 | "GPU_MAX_FREQ":"Fréquence maximale", 30 | "FAN_SPEED":"Vitesse du ventilateur", 31 | "CREATE_FAN_PROFILE":"Créer un profil de ventilateur", 32 | "GRID_ALIG":"Alignement de la grille", 33 | "FAN_MODE":"Mode ventilateur", 34 | "NOT_CONTROLLED":"Non contrôlé", 35 | "FIXED":"Fixe", 36 | "CURVE":"Courbe", 37 | "SNAP_GRIDLINE":"Aligner sur les intersections de la grille", 38 | "FAN_SPEED_PERCENT":"Vitesse du ventilateur (%)", 39 | "SENSOR_TEMP":"Température du capteur", 40 | "CREATE_FAN_PROFILE_TIP":"Créer un profil de ventilateur", 41 | "SELECT_FAN_PROFILE_TIP":"Sélectionner un profil de ventilateur", 42 | "FAN_PROFILE_NAME":"Nom du profil", 43 | "USE":"Utiliser", 44 | "DELETE":"Supprimer", 45 | "CREATE":"Créer", 46 | "CANCEL":"Annuler", 47 | "CURENT_STAT":"État actuel", 48 | "EDIT":"Modifier", 49 | "SAVE":"Enregistrer", 50 | "NATIVE_FREQ":"Natif", 51 | "MORE":"Plus", 52 | "REINSTALL_PLUGIN": "Réinstaller le plugin", 53 | "UPDATE_PLUGIN": "Mettre à jour vers", 54 | "ROLLBACK_PLUGIN": "Revenir à", 55 | "INSTALLED_VERSION": "Version installée", 56 | "LATEST_VERSION": "Dernière version", 57 | "GPU_NATIVE_SLIDER": "Curseur GPU natif", 58 | "GPU_NATIVE_SLIDER_DESC": "Activer le curseur de contrôle GPU natif", 59 | "USE_PERACMODE_PROFILE": "Utiliser un profil par mode d'alimentation", 60 | "AC_MODE": "Mode secteur", 61 | "BAT_MODE": "Mode batterie", 62 | "CUSTOM_TDP_RANGE": "Plage TDP personnalisée", 63 | "RESET_ALL": "Tout réinitialiser", 64 | "NATIVE_FREQ_DESC": "Régler la fréquence via le menu système", 65 | "UNLIMITED_DESC": "Pas de limite de fréquence GPU, planification système par défaut", 66 | "FIXED_FREQ_DESC": "Fréquence GPU fixe", 67 | "RANGE_FREQ_DESC": "Définir la plage de fréquence GPU", 68 | "AUTO_FREQ_DESC": "Fréquence GPU adaptative, limite TDP désactivée de force, Boost désactivé", 69 | "AUTO_FREQ_TDP_NOTIF": "Mode GPU {{mode}}, limite TDP désactivée", 70 | "NATIVE_TDP_SLIDER": "Curseur TDP natif", 71 | "NATIVE_TDP_SLIDER_DESC": "Activer le curseur TDP natif, nécessite un redémarrage du système pour que l'activation/désactivation prenne effet. La modification de la valeur maximale de la plage TDP nécessite un redémarrage de Steam pour prendre effet. Après activation, les paramètres TDP sont sauvegardés par Steam, et les paramètres d'état d'alimentation dans le plugin ne géreront pas la configuration TDP", 72 | "FORCE_SHOW_TDP": "Forcer l'affichage du contrôle TDP", 73 | "FORCE_SHOW_TDP_DESC": "Par défaut, le plugin traite le curseur TDP natif. Si le curseur natif pose problème, activez cette option pour utiliser le contrôle TDP interne du plugin", 74 | "MANUAL_BYPASS_CHARGE": "Charge en dérivation manuelle", 75 | "MANUAL_BYPASS_CHARGE_DESC": "Contrôler manuellement la charge en dérivation", 76 | "MANUAL_BYPASS_CHARGE_DESC_WITH_LIMIT": "L'activation désactivera temporairement la limite de charge", 77 | "CHARGE_LIMIT": "Limite de charge", 78 | "CHARGE_LIMIT_DESC": "Définir une limite de charge pour prolonger la durée de vie de la batterie", 79 | "CHARGE_LIMIT_DESC_WITH_BYPASS": "Charge en dérivation activée, limite de charge temporairement désactivée", 80 | "USE_OLD_UI": "Utiliser l'ancienne interface", 81 | "USE_OLD_UI_DESC": "Utiliser l'ancienne vue en liste au lieu de la nouvelle vue par onglets", 82 | "CPU_FREQ_CONTROL": "Contrôle de fréquence CPU", 83 | "CPU_FREQ_CONTROL_DESC": "Limiter la fréquence maximale du CPU pour réduire la consommation et la chaleur. Architecture: {{architecture}}", 84 | "CORE_FREQUENCY": "Fréquence du cœur", 85 | "CPU_FREQUENCY": "Fréquence CPU", 86 | "ALL_CORES": "Tous les cœurs", 87 | "CHECK_VERSION": "Vérifier la version", 88 | "CHECKING_VERSION": "Vérification...", 89 | "LAST_CHECK_TIME": "Dernière vérification", 90 | "JUST_NOW": "à l'instant", 91 | "MINUTES_AGO": "il y a {{minutes}} minutes", 92 | "HOURS_AGO": "il y a {{hours}} heures", 93 | "DAYS_AGO": "il y a {{days}} jours", 94 | "CLICK_TO_CHECK": "Cliquer pour vérifier les mises à jour" 95 | } -------------------------------------------------------------------------------- /src/i18n/bulgarian.json: -------------------------------------------------------------------------------- 1 | { 2 | "TITEL_SETTINGS":"Настройки", 3 | "ENABLE_SETTINGS":"Активиране на настройките", 4 | "USE_PERGAME_PROFILE":"Използване на профил за игра", 5 | "USING":"Използва се", 6 | "DEFAULT":"По подразбиране", 7 | "PROFILE":"Профил", 8 | "CPU_BOOST":"CPU Boost", 9 | "CPU_BOOST_DESC":"Увеличаване на максималната честота на CPU", 10 | "HT_DESC":"Активиране на Intel Hyper-Threading", 11 | "SMT_DESC":"Активиране на едновременна многонишковост", 12 | "CPU_NUM":"Брой CPU ядра", 13 | "CPU_NUM_DESC":"Задаване на активните физически ядра", 14 | "CPU_MAX_PERF":"Максимална производителност на CPU", 15 | "CPU_MAX_PERF_AUTO":"Автоматична максимална производителност на CPU", 16 | "CPU_GOVERNOR": "Регулатор на CPU", 17 | "CPU_GOVERNOR_DESC": "Задаване на политика за планиране на производителността на CPU", 18 | "TDP":"Лимит на термична мощност (TDP)", 19 | "TDP_DESC":"Ограничаване на мощността на процесора за по-ниска обща консумация", 20 | "RYZENADJ_NOT_FOUND":"RyzenAdj не е намерен", 21 | "WATTS":"Вата", 22 | "GPU_FREQMODE":"Режим на честота на GPU", 23 | "UNLIMITED":"Неограничен", 24 | "FIXED_FREQ":"Фиксиран", 25 | "RANGE_FREQ":"Диапазон", 26 | "AUTO_FREQ":"Автоматичен", 27 | "GPU_FIX_FREQ":"Честота на GPU", 28 | "GPU_MIN_FREQ":"Минимална честота", 29 | "GPU_MAX_FREQ":"Максимална честота", 30 | "FAN_SPEED":"Скорост на вентилатора", 31 | "CREATE_FAN_PROFILE":"Създаване на профил за вентилатора", 32 | "GRID_ALIG":"Подравняване по мрежа", 33 | "FAN_MODE":"Режим на вентилатора", 34 | "NOT_CONTROLLED":"Без контрол", 35 | "FIXED":"Фиксиран", 36 | "CURVE":"Крива", 37 | "SNAP_GRIDLINE":"Прилепване към пресечните точки на мрежата", 38 | "FAN_SPEED_PERCENT":"Скорост на вентилатора (%)", 39 | "SENSOR_TEMP":"Температура на сензора", 40 | "CREATE_FAN_PROFILE_TIP":"Създаване на профил за вентилатора", 41 | "SELECT_FAN_PROFILE_TIP":"Избор на профил за вентилатора", 42 | "FAN_PROFILE_NAME":"Име на профила", 43 | "USE":"Използване", 44 | "DELETE":"Изтриване", 45 | "CREATE":"Създаване", 46 | "CANCEL":"Отказ", 47 | "CURENT_STAT":"Текущо състояние", 48 | "EDIT":"Редактиране", 49 | "SAVE":"Запазване", 50 | "NATIVE_FREQ":"Нативен", 51 | "MORE":"Още", 52 | "REINSTALL_PLUGIN": "Преинсталиране на плъгина", 53 | "UPDATE_PLUGIN": "Обновяване до", 54 | "ROLLBACK_PLUGIN": "Връщане до", 55 | "INSTALLED_VERSION": "Инсталирана версия", 56 | "LATEST_VERSION": "Последна версия", 57 | "GPU_NATIVE_SLIDER": "Нативен GPU плъзгач", 58 | "GPU_NATIVE_SLIDER_DESC": "Активиране на нативния плъзгач за контрол на GPU", 59 | "USE_PERACMODE_PROFILE": "Използване на профил според захранването", 60 | "AC_MODE": "Режим на захранване", 61 | "BAT_MODE": "Режим на батерия", 62 | "CUSTOM_TDP_RANGE": "Персонализиран диапазон на TDP плъзгача", 63 | "RESET_ALL": "Нулиране на всичко", 64 | "NATIVE_FREQ_DESC": "Задаване на честота чрез системното меню", 65 | "UNLIMITED_DESC": "Без ограничение на GPU честотата, системно планиране по подразбиране", 66 | "FIXED_FREQ_DESC": "Фиксирана GPU честота", 67 | "RANGE_FREQ_DESC": "Задаване на диапазон на GPU честотата", 68 | "AUTO_FREQ_DESC": "Адаптивна GPU честота, принудително изключен TDP лимит, изключен Boost", 69 | "AUTO_FREQ_TDP_NOTIF": "GPU режим {{mode}}, TDP лимитът е изключен", 70 | "NATIVE_TDP_SLIDER": "Нативен TDP плъзгач", 71 | "NATIVE_TDP_SLIDER_DESC": "Активирайте нативния плъзгач TDP, изисква се рестартиране на системата, за да влезе в сила активирането/деактивирането. Промяната на максималната стойност на диапазона TDP изисква рестартиране на Steam, за да влезе в сила. След активиране настройките TDP се запазват от Steam, а настройките за състояние на захранването в плъгина няма да обработват конфигурацията TDP", 72 | "FORCE_SHOW_TDP": "Принудително показване на TDP контрол", 73 | "FORCE_SHOW_TDP_DESC": "По подразбиране плъгинът обработва нативния TDP плъзгач. Ако нативният плъзгач има проблеми, активирайте тази опция, за да използвате вътрешния TDP контрол на плъгина", 74 | "MANUAL_BYPASS_CHARGE": "Ръчно байпас зареждане", 75 | "MANUAL_BYPASS_CHARGE_DESC": "Ръчно управление на байпас зареждането", 76 | "MANUAL_BYPASS_CHARGE_DESC_WITH_LIMIT": "Активирането ще деактивира временно ограничението за зареждане", 77 | "CHARGE_LIMIT": "Ограничение за зареждане", 78 | "CHARGE_LIMIT_DESC": "Задаване на ограничение за зареждане за удължаване на живота на батерията", 79 | "CHARGE_LIMIT_DESC_WITH_BYPASS": "Байпас зареждането е активно, ограничението за зареждане е временно деактивирано", 80 | "USE_OLD_UI": "Използвайте стария интерфейс", 81 | "USE_OLD_UI_DESC": "Използвайте стария изглед със списък вместо новия изглед с раздели", 82 | "CPU_FREQ_CONTROL": "Контрол на честотата на CPU", 83 | "CPU_FREQ_CONTROL_DESC": "Ограничи максималната честота на CPU за намаляване на консумацията и топлината. Архитектура: {{architecture}}", 84 | "CORE_FREQUENCY": "Честота на ядрото", 85 | "CPU_FREQUENCY": "Честота на CPU", 86 | "ALL_CORES": "Всички ядра", 87 | "CHECK_VERSION": "Проверка на версия", 88 | "CHECKING_VERSION": "Проверява...", 89 | "LAST_CHECK_TIME": "Последна проверка", 90 | "JUST_NOW": "току що", 91 | "MINUTES_AGO": "преди {{minutes}} минути", 92 | "HOURS_AGO": "преди {{hours}} часа", 93 | "DAYS_AGO": "преди {{days}} дни", 94 | "CLICK_TO_CHECK": "Кликнете за проверка на обновления" 95 | } -------------------------------------------------------------------------------- /py_modules/fan_config/schema/hwmon.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-06/schema#", 3 | "$ref": "#/definitions/HWMONProfile", 4 | "definitions": { 5 | "HWMONProfile": { 6 | "title": "HWMONProfile", 7 | "type": "object", 8 | "additionalProperties": false, 9 | "properties": { 10 | "hwmon_name": { 11 | "type": "string", 12 | "description": "hwmon 目录下 name 文件内容" 13 | }, 14 | "fans": { 15 | "type": "array", 16 | "items": { 17 | "$ref": "#/definitions/Fans" 18 | }, 19 | "minItems": 1, 20 | "description": "风扇配置信息" 21 | } 22 | }, 23 | "required": ["hwmon_name", "fans"] 24 | }, 25 | "Fans": { 26 | "title": "Fans", 27 | "type": "object", 28 | "additionalProperties": false, 29 | "properties": { 30 | "fan_name": { 31 | "type": "string", 32 | "description": "显示在 UI 上的风扇名称" 33 | }, 34 | "pwm_mode": { 35 | "type": "number", 36 | "enum": [0, 1, 2], 37 | "description": "写入的模式 0.普通模式(对单个文件写入) 1.对多个文件写入同样的数值 2.对多个文件写入不同的数值" 38 | }, 39 | "black_list": { 40 | "type": "array", 41 | "items": { 42 | "type": "string" 43 | }, 44 | "description": "黑名单, 匹配 /sys/devices/virtual/dmi/id/product_name. 不使用该配置" 45 | }, 46 | "temp_mode": { 47 | "type": "number", 48 | "enum": [0, 1], 49 | "description": "温度使用哪个数据 0.cpu温度(不可用时切换到gpu温度) 1.gpu温度(不可用时切换到cpu温度)" 50 | }, 51 | "pwm_enable": { 52 | "$ref": "#/definitions/PWMEnable", 53 | "description": "PWMEnable 配置信息" 54 | }, 55 | "pwm_input": { 56 | "$ref": "#/definitions/PWMInput", 57 | "description": "PWMInput 配置信息" 58 | }, 59 | "pwm_write": { 60 | "$ref": "#/definitions/PWMWrite", 61 | "description": "PWMWrite 配置信息" 62 | } 63 | }, 64 | "required": [] 65 | }, 66 | "PWMEnable": { 67 | "title": "PWMEnable", 68 | "type": "object", 69 | "additionalProperties": false, 70 | "properties": { 71 | "manual_value": { 72 | "type": "number", 73 | "description": "手动模式写入的值" 74 | }, 75 | "auto_value": { 76 | "type": "number", 77 | "description": "自动模式写入的值" 78 | }, 79 | "pwm_enable_path": { 80 | "type": "string", 81 | "description": "写入数值文件路径" 82 | }, 83 | "pwm_enable_second_path": { 84 | "type": "string", 85 | "description": "写入数值文件路径(次选, pwm_enable_path 不存在时使用该路径)" 86 | } 87 | }, 88 | "required": ["manual_value", "auto_value", "pwm_enable_path"] 89 | }, 90 | "PWMInput": { 91 | "title": "PWMInput", 92 | "type": "object", 93 | "additionalProperties": false, 94 | "properties": { 95 | "hwmon_label": { 96 | "type": "string", 97 | "description": "读转速的hwmon标签(读取转速和写入转速可能不在同一个hwmon)" 98 | }, 99 | "pwm_read_path": { 100 | "type": "string", 101 | "description": "读取数值文件路径" 102 | }, 103 | "pwm_read_max": { 104 | "type": "number", 105 | "description": "读取数值最大值" 106 | } 107 | }, 108 | "required": ["pwm_read_path", "pwm_read_max"] 109 | }, 110 | "PWMWrite": { 111 | "title": "PWMWrite", 112 | "type": "object", 113 | "additionalProperties": false, 114 | "properties": { 115 | "pwm_write_max": { 116 | "type": "object", 117 | "additionalProperties": { 118 | "type": "number" 119 | }, 120 | "required": ["default"], 121 | "description": "写入转速最大值(根据不同产品名写入不同的最大值,没有则使用默认)" 122 | }, 123 | "pwm_write_path": { 124 | "type": "string", 125 | "description": "写入数值文件路径" 126 | }, 127 | "default_curve": { 128 | "$ref": "#/definitions/PWMWriteDefaultCurve", 129 | "description": "默认曲线" 130 | }, 131 | "curve_path": { 132 | "$ref": "#/definitions/PWMWriteCurvePath", 133 | "description": "曲线路径" 134 | } 135 | }, 136 | "required": [] 137 | }, 138 | "PWMWriteDefaultCurve": { 139 | "title": "PWMWriteDefaultCurve", 140 | "type": "object", 141 | "additionalProperties": false, 142 | "properties": { 143 | "temp": { 144 | "type": "array", 145 | "items": { 146 | "type": "number" 147 | }, 148 | "description": "温度数组" 149 | }, 150 | "speed": { 151 | "type": "array", 152 | "items": { 153 | "type": "number" 154 | }, 155 | "description": "转速数组" 156 | }, 157 | "pwm": { 158 | "type": "array", 159 | "items": { 160 | "type": "number" 161 | }, 162 | "description": "PWM数组" 163 | } 164 | }, 165 | "required": ["temp", "pwm"] 166 | }, 167 | "PWMWriteCurvePath": { 168 | "title": "PWMWriteCurvePath", 169 | "type": "object", 170 | "additionalProperties": false, 171 | "properties": { 172 | "temp_write": { 173 | "type": "array", 174 | "items": { 175 | "type": "string" 176 | }, 177 | "description": "温度路径数组" 178 | }, 179 | "pwm_write": { 180 | "type": "array", 181 | "items": { 182 | "type": "string" 183 | }, 184 | "description": "PWM路径数组" 185 | } 186 | }, 187 | "required": ["temp_write", "pwm_write"] 188 | } 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /decky.pyi: -------------------------------------------------------------------------------- 1 | """ 2 | This module exposes various constants and helpers useful for decky plugins. 3 | 4 | * Plugin's settings and configurations should be stored under `DECKY_PLUGIN_SETTINGS_DIR`. 5 | * Plugin's runtime data should be stored under `DECKY_PLUGIN_RUNTIME_DIR`. 6 | * Plugin's persistent log files should be stored under `DECKY_PLUGIN_LOG_DIR`. 7 | 8 | Avoid writing outside of `DECKY_HOME`, storing under the suggested paths is strongly recommended. 9 | 10 | Some basic migration helpers are available: `migrate_any`, `migrate_settings`, `migrate_runtime`, `migrate_logs`. 11 | 12 | A logging facility `logger` is available which writes to the recommended location. 13 | """ 14 | 15 | __version__ = '1.0.0' 16 | 17 | import logging 18 | 19 | from typing import Any 20 | 21 | """ 22 | Constants 23 | """ 24 | 25 | HOME: str 26 | """ 27 | The home directory of the effective user running the process. 28 | Environment variable: `HOME`. 29 | If `root` was specified in the plugin's flags it will be `/root` otherwise the user whose home decky resides in. 30 | e.g.: `/home/deck` 31 | """ 32 | 33 | USER: str 34 | """ 35 | The effective username running the process. 36 | Environment variable: `USER`. 37 | It would be `root` if `root` was specified in the plugin's flags otherwise the user whose home decky resides in. 38 | e.g.: `deck` 39 | """ 40 | 41 | DECKY_VERSION: str 42 | """ 43 | The version of the decky loader. 44 | Environment variable: `DECKY_VERSION`. 45 | e.g.: `v2.5.0-pre1` 46 | """ 47 | 48 | DECKY_USER: str 49 | """ 50 | The user whose home decky resides in. 51 | Environment variable: `DECKY_USER`. 52 | e.g.: `deck` 53 | """ 54 | 55 | DECKY_USER_HOME: str 56 | """ 57 | The home of the user where decky resides in. 58 | Environment variable: `DECKY_USER_HOME`. 59 | e.g.: `/home/deck` 60 | """ 61 | 62 | DECKY_HOME: str 63 | """ 64 | The root of the decky folder. 65 | Environment variable: `DECKY_HOME`. 66 | e.g.: `/home/deck/homebrew` 67 | """ 68 | 69 | DECKY_PLUGIN_SETTINGS_DIR: str 70 | """ 71 | The recommended path in which to store configuration files (created automatically). 72 | Environment variable: `DECKY_PLUGIN_SETTINGS_DIR`. 73 | e.g.: `/home/deck/homebrew/settings/decky-plugin-template` 74 | """ 75 | 76 | DECKY_PLUGIN_RUNTIME_DIR: str 77 | """ 78 | The recommended path in which to store runtime data (created automatically). 79 | Environment variable: `DECKY_PLUGIN_RUNTIME_DIR`. 80 | e.g.: `/home/deck/homebrew/data/decky-plugin-template` 81 | """ 82 | 83 | DECKY_PLUGIN_LOG_DIR: str 84 | """ 85 | The recommended path in which to store persistent logs (created automatically). 86 | Environment variable: `DECKY_PLUGIN_LOG_DIR`. 87 | e.g.: `/home/deck/homebrew/logs/decky-plugin-template` 88 | """ 89 | 90 | DECKY_PLUGIN_DIR: str 91 | """ 92 | The root of the plugin's directory. 93 | Environment variable: `DECKY_PLUGIN_DIR`. 94 | e.g.: `/home/deck/homebrew/plugins/decky-plugin-template` 95 | """ 96 | 97 | DECKY_PLUGIN_NAME: str 98 | """ 99 | The name of the plugin as specified in the 'plugin.json'. 100 | Environment variable: `DECKY_PLUGIN_NAME`. 101 | e.g.: `Example Plugin` 102 | """ 103 | 104 | DECKY_PLUGIN_VERSION: str 105 | """ 106 | The version of the plugin as specified in the 'package.json'. 107 | Environment variable: `DECKY_PLUGIN_VERSION`. 108 | e.g.: `0.0.1` 109 | """ 110 | 111 | DECKY_PLUGIN_AUTHOR: str 112 | """ 113 | The author of the plugin as specified in the 'plugin.json'. 114 | Environment variable: `DECKY_PLUGIN_AUTHOR`. 115 | e.g.: `John Doe` 116 | """ 117 | 118 | DECKY_PLUGIN_LOG: str 119 | """ 120 | The path to the plugin's main logfile. 121 | Environment variable: `DECKY_PLUGIN_LOG`. 122 | e.g.: `/home/deck/homebrew/logs/decky-plugin-template/plugin.log` 123 | """ 124 | 125 | """ 126 | Migration helpers 127 | """ 128 | 129 | 130 | def migrate_any(target_dir: str, *files_or_directories: str) -> dict[str, str]: 131 | """ 132 | Migrate files and directories to a new location and remove old locations. 133 | Specified files will be migrated to `target_dir`. 134 | Specified directories will have their contents recursively migrated to `target_dir`. 135 | 136 | Returns the mapping of old -> new location. 137 | """ 138 | 139 | 140 | def migrate_settings(*files_or_directories: str) -> dict[str, str]: 141 | """ 142 | Migrate files and directories relating to plugin settings to the recommended location and remove old locations. 143 | Specified files will be migrated to `DECKY_PLUGIN_SETTINGS_DIR`. 144 | Specified directories will have their contents recursively migrated to `DECKY_PLUGIN_SETTINGS_DIR`. 145 | 146 | Returns the mapping of old -> new location. 147 | """ 148 | 149 | 150 | def migrate_runtime(*files_or_directories: str) -> dict[str, str]: 151 | """ 152 | Migrate files and directories relating to plugin runtime data to the recommended location and remove old locations 153 | Specified files will be migrated to `DECKY_PLUGIN_RUNTIME_DIR`. 154 | Specified directories will have their contents recursively migrated to `DECKY_PLUGIN_RUNTIME_DIR`. 155 | 156 | Returns the mapping of old -> new location. 157 | """ 158 | 159 | 160 | def migrate_logs(*files_or_directories: str) -> dict[str, str]: 161 | """ 162 | Migrate files and directories relating to plugin logs to the recommended location and remove old locations. 163 | Specified files will be migrated to `DECKY_PLUGIN_LOG_DIR`. 164 | Specified directories will have their contents recursively migrated to `DECKY_PLUGIN_LOG_DIR`. 165 | 166 | Returns the mapping of old -> new location. 167 | """ 168 | 169 | 170 | """ 171 | Logging 172 | """ 173 | 174 | logger: logging.Logger 175 | """The main plugin logger writing to `DECKY_PLUGIN_LOG`.""" 176 | 177 | """ 178 | Event handling 179 | """ 180 | # TODO better docstring im lazy 181 | async def emit(event: str, *args: Any) -> None: 182 | """ 183 | Send an event to the frontend. 184 | """ -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ifneq (,$(wildcard ./.env)) 2 | include .env 3 | export 4 | endif 5 | 6 | SHELL=bash 7 | 8 | help: ## Display list of tasks with descriptions 9 | @echo "+ $@" 10 | @fgrep -h ": ## " $(MAKEFILE_LIST) | fgrep -v fgrep | sed -e 's/\\$$//' | sed 's/-default//' | awk 'BEGIN {FS = ": ## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' 11 | 12 | vendor: ## Install project dependencies 13 | @echo "+ $@" 14 | @pnpm i 15 | 16 | env: ## Create default .env file 17 | @echo "+ $@" 18 | @echo -e '# Makefile tools\nDECK_USER=deck\nDECK_HOST=\nDECK_PORT=22\nDECK_HOME=/home/deck\nDECK_KEY=~/.ssh/id_rsa' >> .env 19 | @echo -n "PLUGIN_FOLDER=" >> .env 20 | @jq -r .name package.json >> .env 21 | 22 | init: ## Initialize project 23 | @$(MAKE) env 24 | @$(MAKE) vendor 25 | @echo -e "\n\033[1;36m Almost ready! Just a few things left to do:\033[0m\n" 26 | @echo -e "1. Open .env file and make sure every DECK_* variable matches your steamdeck's ip/host, user, etc" 27 | @echo -e "2. Run \`\033[0;36mmake copy-ssh-key\033[0m\` to copy your public ssh key to steamdeck" 28 | @echo -e "3. Build your code with \`\033[0;36mmake build\033[0m\` or \`\033[0;36mmake docker-build\033[0m\` to build inside a docker container" 29 | @echo -e "4. Deploy your plugin code to steamdeck with \`\033[0;36mmake deploy\033[0m\`" 30 | 31 | update-frontend-lib: ## Update decky-frontend-lib 32 | @echo "+ $@" 33 | @pnpm update decky-frontend-lib --latest 34 | 35 | update-ui: ## Update @decky/api and @decky/ui 36 | @echo "+ $@" 37 | @pnpm update @decky/api @decky/ui react-icons --latest 38 | 39 | 40 | update-decky-ui: ## Update @decky/ui @decky/api 41 | @echo "+ $@" 42 | @pnpm update @decky/ui --latest 43 | @pnpm update @decky/api --latest 44 | 45 | build-front: ## Build frontend 46 | @echo "+ $@" 47 | @pnpm run build 48 | 49 | build-back: ## Build backend 50 | @echo "+ $@" 51 | @make -C ./backend 52 | 53 | build: ## Build everything 54 | @$(MAKE) build-front build-back 55 | 56 | copy-ssh-key: ## Copy public ssh key to steamdeck 57 | @echo "+ $@" 58 | @ssh-copy-id -i $(DECK_KEY) $(DECK_USER)@$(DECK_HOST) 59 | 60 | deploy-steamdeck: ## Deploy plugin build to steamdeck 61 | @echo "+ $@" 62 | @ssh $(DECK_USER)@$(DECK_HOST) -p $(DECK_PORT) -i $(DECK_KEY) \ 63 | 'sudo chown -R $(DECK_USER):$(DECK_USER) $(DECK_HOME)/homebrew/plugins' 64 | @ssh $(DECK_USER)@$(DECK_HOST) -p $(DECK_PORT) -i $(DECK_KEY) \ 65 | 'sudo chmod -v 755 $(DECK_HOME)/homebrew/plugins/ && mkdir -p $(DECK_HOME)/homebrew/plugins/$(PLUGIN_FOLDER)' 66 | @rsync -azp --delete --progress -e "ssh -p $(DECK_PORT) -i $(DECK_KEY)" \ 67 | --chmod=Du=rwx,Dg=rx,Do=rx,Fu=rwx,Fg=rx,Fo=rx \ 68 | --exclude='.git/' \ 69 | --exclude='*.pyc' \ 70 | --exclude='.github/' \ 71 | --exclude='.vscode/' \ 72 | --exclude='node_modules/' \ 73 | --exclude='.pnpm-store/' \ 74 | --exclude='src/' \ 75 | --exclude='*.log' \ 76 | --exclude='.gitignore' . \ 77 | --exclude='.idea' . \ 78 | --exclude='.env' . \ 79 | --exclude='Makefile' . \ 80 | --exclude='submodule' . \ 81 | --exclude='__pycache__' . \ 82 | ./ $(DECK_USER)@$(DECK_HOST):$(DECK_HOME)/homebrew/plugins/$(PLUGIN_FOLDER)/ 83 | @ssh $(DECK_USER)@$(DECK_HOST) -p $(DECK_PORT) -i $(DECK_KEY) \ 84 | 'chmod -v 755 $(DECK_HOME)/homebrew/plugins/' 85 | 86 | local-fuse: ## Copy fuse module to local site-packages 87 | @echo "+ $@" 88 | @mkdir -p ./py_modules/site-packages 89 | @rm -rf ./py_modules/site-packages/fuse* ./py_modules/site-packages/fuseparts 90 | @rm -rf ./submodule/python-fuse/build 91 | @cd ./submodule/python-fuse && \ 92 | PYTHONPATH=$(PWD)/py_modules/site-packages \ 93 | python3 setup.py install --prefix=dist/install --install-lib=dist/install && \ 94 | rsync -a --exclude=*.pyc --exclude=__pycache__ ./dist/install/fuse*/fuse* $(PWD)/py_modules/site-packages/ 95 | @rm -rf $(PWD)/submodule/python-fuse/build 96 | 97 | 98 | restart-decky: ## Restart Decky on remote steamdeck 99 | @echo "+ $@" 100 | @ssh -t $(DECK_USER)@$(DECK_HOST) -p $(DECK_PORT) -i $(DECK_KEY) \ 101 | 'sudo systemctl restart plugin_loader.service' 102 | @echo -e '\033[0;32m+ all is good, restarting Decky...\033[0m' 103 | 104 | deploy: ## Deploy code to steamdeck and restart Decky 105 | @$(MAKE) deploy-steamdeck 106 | @$(MAKE) restart-decky 107 | 108 | deploy-only: ## Deploy code to steamdeck 109 | @$(MAKE) deploy-steamdeck 110 | @$(MAKE) set-loglevel 111 | 112 | deploy-only-debug: ## Deploy code to steamdeck 113 | @$(MAKE) deploy-steamdeck 114 | 115 | deploy-release: ## Deploy release to steamdeck and restart Decky 116 | @$(MAKE) deploy-steamdeck 117 | @$(MAKE) set-loglevel 118 | @$(MAKE) restart-decky 119 | 120 | set-loglevel: ## Set log level to info 121 | @echo "+ $@" 122 | @ssh -t $(DECK_USER)@$(DECK_HOST) -p $(DECK_PORT) -i $(DECK_KEY) \ 123 | 'sudo chmod -v 755 $(DECK_HOME)/homebrew/plugins/$(PLUGIN_FOLDER)/py_modules' 124 | @ssh -t $(DECK_USER)@$(DECK_HOST) -p $(DECK_PORT) -i $(DECK_KEY) \ 125 | "sudo sed -i 's/logging.DEBUG/logging.INFO/' $(DECK_HOME)/homebrew/plugins/$(PLUGIN_FOLDER)/py_modules/config.py" 126 | 127 | it: ## Build all code, deploy it to steamdeck, restart Decky 128 | @$(MAKE) build deploy 129 | 130 | cleanup: ## Delete all generated files and folders 131 | @echo "+ $@" 132 | @rm -f .env 133 | @rm -rf ./dist 134 | @rm -rf ./tmp 135 | @rm -rf ./node_modules 136 | @rm -rf ./.pnpm-store 137 | @rm -rf ./backend/out 138 | 139 | uninstall-plugin: ## Uninstall plugin from steamdeck, restart Decky 140 | @echo "+ $@" 141 | @ssh -t $(DECK_USER)@$(DECK_HOST) -p $(DECK_PORT) -i $(DECK_KEY) \ 142 | "sudo sh -c 'rm -rf $(DECK_HOME)/homebrew/plugins/$(PLUGIN_FOLDER)/ && systemctl restart plugin_loader.service'" 143 | @echo -e '\033[0;32m+ all is good, restarting Decky...\033[0m' 144 | 145 | docker-rebuild-image: ## Rebuild docker image 146 | @echo "+ $@" 147 | @docker compose build --pull 148 | 149 | docker-build: ## Build project inside docker container 150 | @$(MAKE) build-back 151 | @echo "+ $@" 152 | @docker run --rm -i -v $(PWD):/plugin -v $(PWD)/tmp/out:/out ghcr.io/steamdeckhomebrew/builder:latest -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | from typing import Dict, List 4 | 5 | import decky 6 | 7 | import update 8 | from conf_manager import confManager 9 | from cpu import cpuManager 10 | from config import CPU_VENDOR, logger 11 | from fan import fanManager 12 | from sysInfo import sysInfoManager 13 | 14 | sys.path.append(f"{decky.DECKY_PLUGIN_DIR}/py_modules/site-packages") 15 | 16 | class Plugin: 17 | def __init__(self): 18 | self.confManager = confManager 19 | # 使用单例模式,不再存储 fuseManager 实例 20 | # 而是每次通过 FuseManager.get_instance() 获取 21 | 22 | async def _migration(self): 23 | decky.logger.info("start _migration") 24 | 25 | # 使用单例模式获取 FuseManager 实例 26 | # fuseManager = FuseManager.get_instance(power_manager=self.powerManager) 27 | # settings = self.confManager.getSettings() 28 | # enableNativeTDPSlider = ( 29 | # settings.get("enableNativeTDPSlider", False) if settings else False 30 | # ) 31 | # if enableNativeTDPSlider: 32 | # fuseManager.fuse_init() 33 | 34 | async def _main(self): 35 | decky.logger.info("start _main") 36 | pass 37 | 38 | async def _unload(self): 39 | decky.logger.info("start _unload") 40 | # 使用单例模式获取实例并卸载 41 | # FuseManager.get_instance().unload() 42 | logger.info("End PowerControl") 43 | 44 | async def get_settings(self): 45 | return self.confManager.getSettings() 46 | 47 | async def set_settings(self, settings): 48 | self.confManager.setSettings(settings) 49 | return True 50 | 51 | async def get_language(self): 52 | try: 53 | return sysInfoManager.get_language() 54 | except Exception as e: 55 | logger.error(e, exc_info=True) 56 | return "" 57 | 58 | async def get_fanRPM(self, index): 59 | try: 60 | return fanManager.get_fanRPM(index) 61 | except Exception as e: 62 | logger.error(e, exc_info=True) 63 | return 0 64 | 65 | async def get_fanRPMPercent(self, index): 66 | try: 67 | return fanManager.get_fanRPMPercent(index) 68 | except Exception as e: 69 | logger.error(e, exc_info=True) 70 | return 0 71 | 72 | async def get_fanTemp(self, index): 73 | try: 74 | return fanManager.get_fanTemp(index) 75 | except Exception as e: 76 | logger.error(e, exc_info=True) 77 | return 0 78 | 79 | async def get_fanIsAuto(self, index): 80 | try: 81 | return fanManager.get_fanIsAuto(index) 82 | except Exception as e: 83 | logger.error(e, exc_info=True) 84 | return 0 85 | 86 | async def get_fanConfigList(self): 87 | try: 88 | return fanManager.get_fanConfigList() 89 | except Exception as e: 90 | logger.error(e, exc_info=True) 91 | return [] 92 | 93 | async def set_fanAuto(self, index: int, value: bool): 94 | try: 95 | return fanManager.set_fanAuto(index, value) 96 | except Exception as e: 97 | logger.error(e, exc_info=True) 98 | return False 99 | 100 | async def set_fanPercent(self, index: int, value: int): 101 | try: 102 | return fanManager.set_fanPercent(index, value) 103 | except Exception as e: 104 | logger.error(e, exc_info=True) 105 | return False 106 | 107 | async def set_fanCurve(self, index: int, temp_list: List[int], pwm_list: List[int]): 108 | try: 109 | return fanManager.set_fanCurve(index, temp_list, pwm_list) 110 | except Exception as e: 111 | logger.error(e, exc_info=True) 112 | return False 113 | 114 | async def is_intel(self): 115 | try: 116 | return cpuManager.is_intel() 117 | except Exception as e: 118 | logger.error(e, exc_info=True) 119 | return False 120 | 121 | async def receive_suspendEvent(self): 122 | try: 123 | return True 124 | except Exception as e: 125 | logger.error(e, exc_info=True) 126 | return False 127 | 128 | async def update_latest(self): 129 | logger.info("Updating latest") 130 | # return update.update_latest() 131 | try: 132 | return update.update_latest() 133 | except Exception as e: 134 | logger.error(e, exc_info=True) 135 | return False 136 | 137 | async def get_version(self): 138 | return update.get_version() 139 | 140 | async def get_latest_version(self): 141 | try: 142 | return update.get_latest_version() 143 | except Exception as e: 144 | logger.error(e, exc_info=True) 145 | return "" 146 | 147 | async def get_ryzenadj_info(self): 148 | return cpuManager.get_ryzenadj_info() 149 | 150 | async def get_rapl_info(self): 151 | logger.info("Main get_rapl_info") 152 | return cpuManager.get_rapl_info() 153 | 154 | async def log_info(self, message: str): 155 | try: 156 | return logger.info(f"Frontend: {message}") 157 | except Exception as e: 158 | logger.error(e, exc_info=True) 159 | return False 160 | 161 | async def log_error(self, message: str): 162 | try: 163 | return logger.error(f"Frontend: {message}") 164 | except Exception as e: 165 | logger.error(e, exc_info=True) 166 | return False 167 | 168 | async def log_warn(self, message: str): 169 | try: 170 | return logger.warn(f"Frontend: {message}") 171 | except Exception as e: 172 | logger.error(e, exc_info=True) 173 | return False 174 | 175 | async def log_debug(self, message: str): 176 | try: 177 | return logger.debug(f"Frontend: {message}") 178 | except Exception as e: 179 | logger.error(e, exc_info=True) 180 | return False 181 | 182 | async def check_file_exist(self, file_path: str) -> bool: 183 | try: 184 | return os.path.exists(file_path) 185 | except Exception as e: 186 | logger.error(f"Error checking file exist: {e}", exc_info=True) 187 | return False 188 | -------------------------------------------------------------------------------- /backend/sh_tools.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | function get_gpu_device() 4 | { 5 | for gpu_device in /sys/class/drm/card?/device; do 6 | if [ -d "$gpu_device" ]; then 7 | echo "$gpu_device" 8 | return 9 | fi 10 | done 11 | } 12 | 13 | gpu_device=$(get_gpu_device) 14 | 15 | # 弃用 16 | function set_cpu_Freq() 17 | { 18 | cpu_index=$1 19 | let cpu_freq=$2 20 | if(($cpu_index==0));then 21 | cpu_isOnLine=1 22 | else 23 | cpu_isOnLine=$(cat /sys/devices/system/cpu/cpu${cpu_index}/online) 24 | fi 25 | if(($cpu_freq==0));then 26 | if((cpu_isOnLine==0));then 27 | sudo echo 1 > "/sys/devices/system/cpu/cpu${cpu_index}/online" 28 | sudo echo "schedutil" > "/sys/devices/system/cpu/cpu${cpu_index}/cpufreq/scaling_governor" 29 | sudo echo 0 > "/sys/devices/system/cpu/cpu${cpu_index}/online" 30 | else 31 | sudo echo "schedutil" > "/sys/devices/system/cpu/cpu${cpu_index}/cpufreq/scaling_governor" 32 | fi 33 | else 34 | if((cpu_isOnLine==0));then 35 | sudo echo 1 > "/sys/devices/system/cpu/cpu${cpu_index}/online" 36 | sudo echo "userspace" > "/sys/devices/system/cpu/cpu${cpu_index}/cpufreq/scaling_governor" 37 | sudo echo $cpu_freq > "/sys/devices/system/cpu/cpu${cpu_index}/cpufreq/scaling_max_freq" 38 | sudo echo 0 > "/sys/devices/system/cpu/cpu${cpu_index}/online" 39 | else 40 | sudo echo "userspace" > "/sys/devices/system/cpu/cpu${cpu_index}/cpufreq/scaling_governor" 41 | sudo echo $cpu_freq > "/sys/devices/system/cpu/cpu${cpu_index}/cpufreq/scaling_max_freq" 42 | fi 43 | fi 44 | } 45 | 46 | # not need 47 | function get_cpu_nowFreq() 48 | { 49 | cpu_index=$1 50 | echo "$(cat /sys/devices/system/cpu/cpufreq/policy${cpu_index}/scaling_cur_freq)" 51 | } 52 | 53 | # 弃用 54 | function get_cpu_AvailableFreq() 55 | { 56 | echo "$(cat /sys/devices/system/cpu/cpufreq/policy0/scaling_available_frequencies)" 57 | } 58 | 59 | # not need 60 | function set_cpu_online() 61 | { 62 | cpu_index=$1 63 | cpu_online=$2 64 | echo $cpu_online > "/sys/devices/system/cpu/cpu${cpu_index}/online" 65 | } 66 | 67 | # not need 68 | function get_cpuID() 69 | { 70 | cat /proc/cpuinfo | grep "model name" | sed -n '1p'| cut -d : -f 2 | xargs 71 | } 72 | 73 | # not need 74 | function set_cpu_tdp() 75 | { 76 | ryzenadj_path=$1 77 | let slow=$2*1000 78 | let fast=$3*1000 79 | sudo $ryzenadj_path --stapm-limit=$fast --fast-limit=$fast --slow-limit=$fast --tctl-temp=90 80 | sudo echo "${ryzenadj_path} --stapm-limit=${fast} --fast-limit=${fast} --slow-limit=${slow}" >> /tmp/powerControl_sh.log 81 | } 82 | 83 | # not need 84 | function set_clock_limits() 85 | { 86 | let min=$1 87 | let max=$2 88 | if(($min==0 || $max==0));then 89 | sudo echo "auto" > "${gpu_device}/power_dpm_force_performance_level" 90 | else 91 | sudo echo "manual" > "${gpu_device}/power_dpm_force_performance_level" 92 | sudo echo "s 0 ${min}" > "${gpu_device}/pp_od_clk_voltage" 93 | sudo echo "s 1 ${max}" > "${gpu_device}/pp_od_clk_voltage" 94 | sudo echo "c" > "${gpu_device}/pp_od_clk_voltage" 95 | fi 96 | sudo echo "gpu_clock_limit "$1 $2 >> /tmp/powerControl_sh.log 97 | } 98 | 99 | # not need 100 | function get_gpuFreqMin() 101 | { 102 | sudo echo "manual"> "${gpu_device}/power_dpm_force_performance_level" 103 | echo "$(sudo cat ${gpu_device}/pp_od_clk_voltage|grep -a "SCLK:"|awk '{print $2}'|sed -e 's/Mhz//g'|xargs)" 104 | } 105 | 106 | # not need 107 | function get_gpuFreqMax() 108 | { 109 | sudo echo "manual" > "${gpu_device}/power_dpm_force_performance_level" 110 | echo "$(sudo cat ${gpu_device}/pp_od_clk_voltage|grep -a "SCLK:"|awk '{print $3}'|sed -e 's/Mhz//g'|xargs)" 111 | } 112 | 113 | # 忽略 114 | function set_gpu_flk() 115 | { 116 | flk=$1 117 | index=$(((1600-$flk)/400)) 118 | now_mode=$(cat ${gpu_device}/power_dpm_force_performance_level) 119 | if [[ "$now_mode"!="manual" ]];then 120 | sudo echo "manual" > "${gpu_device}/power_dpm_force_performance_level" 121 | sudo echo "$index" > "${gpu_device}/pp_dpm_fclk" 122 | else 123 | sudo echo "$index" > "${gpu_device}/pp_dpm_fclk" 124 | fi 125 | sudo echo "gpu_flk_limit " $index >> /tmp/powerControl_sh.log 126 | } 127 | 128 | # 忽略 129 | function check_clock_limits() 130 | { 131 | mode=$1 132 | now_mode=$(cat ${gpu_device}/power_dpm_force_performance_level) 133 | if [[ "$now_mode"!="$mode" ]];then 134 | if(( "$1" == "manual"));then 135 | sudo echo "manual" > "${gpu_device}/power_dpm_force_performance_level" 136 | sudo echo "s 0 $2" > "${gpu_device}/pp_od_clk_voltage" 137 | sudo echo "s 1 $3" > "${gpu_device}/pp_od_clk_voltage" 138 | sudo echo "c" > "${gpu_device}/pp_od_clk_voltage" 139 | else 140 | sudo echo "auto" > "${gpu_device}/power_dpm_force_performance_level" 141 | fi 142 | fi 143 | } 144 | 145 | # not need 146 | function set_cpu_boost() 147 | { 148 | boost=$1 149 | boost_path="/sys/devices/system/cpu/cpufreq/boost" 150 | amd_pstate_path="/sys/devices/system/cpu/amd_pstate/status" 151 | 152 | 153 | if [ -f "${amd_pstate_path}" ]; then 154 | echo "disable" > "${amd_pstate_path}" 155 | # modprobe -r amd_pstate_ut 156 | modprobe acpi_cpufreq 157 | fi 158 | 159 | if (($boost == 1)); then 160 | echo 1 > "${boost_path}" 161 | else 162 | echo 1 > "${boost_path}" 163 | echo 0 > "${boost_path}" 164 | fi 165 | } 166 | 167 | # not need 168 | function get_language() 169 | { 170 | language_path=$1 171 | sudo cat $language_path|grep language|sed -n '1p'|xargs|cut -d " " -f 2 172 | } 173 | 174 | 175 | if [ -n "$1" ]; then 176 | case "$1" in 177 | set_cpu_online)set_cpu_online $2 $3;; 178 | set_cpu_Freq)set_cpu_Freq $2 $3;; 179 | get_cpu_nowFreq)get_cpu_nowFreq $2;; 180 | get_cpu_AvailableFreq)get_cpu_AvailableFreq $2 $3;; 181 | set_cpu_tdp)set_cpu_tdp $2 $3 $4;; 182 | set_clock_limits)set_clock_limits $2 $3;; 183 | set_cpu_boost)set_cpu_boost $2;; 184 | check_clock_limits)check_clock_limits $2 $3 $4;; 185 | set_gpu_flk)set_gpu_flk $2;; 186 | get_cpuID)get_cpuID;; 187 | get_language)get_language $2;; 188 | get_gpuFreqMin)get_gpuFreqMin;; 189 | get_gpuFreqMax)get_gpuFreqMax;; 190 | *)sudo echo $1 $2 $3 $4>> /tmp/powerControl_sh.log;; 191 | esac 192 | fi 193 | -------------------------------------------------------------------------------- /py_modules/config.py: -------------------------------------------------------------------------------- 1 | import glob 2 | import json 3 | import logging 4 | 5 | import decky 6 | import yaml 7 | from logging_handler import SystemdHandler 8 | 9 | # 日志配置 10 | LOG_LOCATION = "/tmp/PowerControl_py.log" 11 | LOG_LEVEL = logging.DEBUG 12 | 13 | 14 | def setup_logger(): 15 | # 定义日志格式 16 | file_format = "[%(asctime)s | %(filename)s:%(lineno)s:%(funcName)s] %(levelname)s: %(message)s" 17 | systemd_format = "[%(filename)s:%(lineno)s:%(funcName)s] %(levelname)s: %(message)s" 18 | 19 | # 创建并配置 handlers 20 | systemd_handler = SystemdHandler() 21 | systemd_handler.setFormatter(logging.Formatter(systemd_format)) 22 | 23 | file_handler = logging.FileHandler(filename=LOG_LOCATION, mode="w") 24 | file_handler.setFormatter(logging.Formatter(file_format)) 25 | 26 | # 获取 logger 27 | try: 28 | logger = decky.logger 29 | except Exception: 30 | logger = logging.getLogger(__name__) 31 | 32 | logger.setLevel(LOG_LEVEL) 33 | logger.addHandler(systemd_handler) 34 | logger.addHandler(file_handler) 35 | 36 | return logger 37 | 38 | 39 | # 初始化 logger 40 | logger = setup_logger() 41 | 42 | 43 | DECKY_PLUGIN_DIR = decky.DECKY_PLUGIN_DIR 44 | DECKY_PLUGIN_PY_DIR = f"{DECKY_PLUGIN_DIR}/py_modules" 45 | SH_PATH = "{}/backend/sh_tools.sh".format(DECKY_PLUGIN_DIR) 46 | 47 | PLATFORM_PROFILE_PATH = "/sys/firmware/acpi/platform_profile" 48 | PLATFORM_PROFILE_CHOICES_PATH = "/sys/firmware/acpi/platform_profile_choices" 49 | 50 | FAN_CONFIG_DIR = f"{DECKY_PLUGIN_PY_DIR}/fan_config" 51 | FAN_HWMON_CONFIG_DIR = f"{FAN_CONFIG_DIR}/hwmon" 52 | FAN_EC_CONFIG_DIR = f"{FAN_CONFIG_DIR}/ec" 53 | 54 | HWMON_CONFS = glob.glob(f"{FAN_HWMON_CONFIG_DIR}/*.yml") 55 | EC_CONFS = glob.glob(f"{FAN_EC_CONFIG_DIR}/*.yml") 56 | 57 | HWMON_SCHEMA = f"{FAN_CONFIG_DIR}/schema/hwmon.json" 58 | EC_SCHEMA = f"{FAN_CONFIG_DIR}/schema/ec.json" 59 | 60 | API_URL = "https://api.github.com/repos/aarron-lee/PowerControl/releases/latest" 61 | 62 | CONFIG_KEY = "PowerControl" 63 | 64 | 65 | # 设备信息获取配置 66 | try: 67 | cpuinfo_path = "/proc/cpuinfo" 68 | cpuinfo = open(cpuinfo_path, "r").read() 69 | CPU_ID = cpuinfo.split("model name")[1].split(":")[1].split("\n")[0].strip() 70 | CPU_VENDOR = cpuinfo.split("vendor_id")[1].split(":")[1].split("\n")[0].strip() 71 | VENDOR_NAME = open("/sys/devices/virtual/dmi/id/sys_vendor", "r").read().strip() 72 | PRODUCT_NAME = open("/sys/devices/virtual/dmi/id/product_name", "r").read().strip() 73 | BOARD_NAME = open("/sys/devices/virtual/dmi/id/board_name", "r").read().strip() 74 | BOARD_VENDOR = open("/sys/devices/virtual/dmi/id/board_vendor", "r").read().strip() 75 | PRODUCT_VERSION = ( 76 | open("/sys/devices/virtual/dmi/id/product_version", "r").read().strip() 77 | ) 78 | logger.info( 79 | f"CPU_ID: {CPU_ID}, PRODUCT_NAME: {PRODUCT_NAME}, PRODUCT_VERSION: {PRODUCT_VERSION}" 80 | ) 81 | except Exception as e: 82 | logger.error(f"设备信息配置异常|{e}", exc_info=True) 83 | 84 | 85 | # 是否验证 yml 配置文件 86 | VERIFY_YML = False 87 | 88 | def load_yaml(file_path: str, chk_schema=None) -> dict: 89 | with open(file_path, "r") as f: 90 | data = yaml.safe_load(f) 91 | 92 | # 使用 schema 验证配置文件 93 | if chk_schema is not None and VERIFY_YML: 94 | try: 95 | from jsonschema import validate 96 | 97 | try: 98 | validate(instance=data, schema=chk_schema) 99 | except Exception as e: 100 | logger.error(f"配置文件 {file_path} 验证失败 | {e}") 101 | return {} 102 | except Exception as e: 103 | logger.error(f"验证模块导入失败, 跳过检查 | {e}") 104 | return data 105 | 106 | 107 | def get_all_howmon_fans(): 108 | with open(HWMON_SCHEMA, "r") as f: 109 | hwmon_schema = json.load(f) 110 | 111 | fans_confs = {} 112 | for config_path in HWMON_CONFS: 113 | data = load_yaml(config_path, hwmon_schema) 114 | if data == {}: 115 | continue 116 | name = data["hwmon_name"] 117 | if name is not None: 118 | fans_confs[name] = data["fans"] 119 | return fans_confs 120 | 121 | 122 | def get_device_ec_fans(p_name, p_version): 123 | with open(EC_SCHEMA, "r") as f: 124 | ec_schema = json.load(f) 125 | 126 | FAN_EC_CONFIG_MAP = {} 127 | for config_path in EC_CONFS: 128 | data = load_yaml(config_path, ec_schema) 129 | if data == {}: 130 | continue 131 | names = ( 132 | data["product_name"] 133 | if ( 134 | "product_name" in data 135 | and data["product_name"] is not None 136 | and isinstance(data["product_name"], list) 137 | ) 138 | else [] 139 | ) 140 | versions = ( 141 | data["product_version"] 142 | if ( 143 | "product_version" in data 144 | and data["product_version"] is not None 145 | and isinstance(data["product_version"], list) 146 | ) 147 | else [] 148 | ) 149 | for name in names: 150 | if name not in FAN_EC_CONFIG_MAP: 151 | FAN_EC_CONFIG_MAP[name] = [] 152 | fanconf = {"fans": data["fans"], "product_version": versions} 153 | FAN_EC_CONFIG_MAP[name].append(fanconf) 154 | 155 | # 带版本号的配置 156 | confs_with_name_version = [] 157 | # 不带版本号的配置 158 | confs_with_name = [] 159 | 160 | # 通过产品名获得所有配置,然后分成带版本号和不带版本号的配置 161 | for conf in FAN_EC_CONFIG_MAP.get(p_name, []): 162 | logger.info(f"conf of {p_name}: {conf}") 163 | if ( 164 | p_version in conf["product_version"] 165 | and conf["product_version"] is not None 166 | and isinstance(conf["product_version"], list) 167 | and len(conf["product_version"]) > 0 168 | ): 169 | confs_with_name_version.append(conf) 170 | else: 171 | confs_with_name.append(conf) 172 | 173 | # 优先匹配带版本号的配置 174 | for conf in confs_with_name_version: 175 | if p_version in conf["product_version"]: 176 | return conf["fans"] 177 | # 其次匹配不带版本号的配置 (如果有) 178 | for conf in confs_with_name: 179 | return conf["fans"] 180 | return [] 181 | 182 | 183 | # 风扇配置 184 | try: 185 | FAN_HWMON_LIST = get_all_howmon_fans() 186 | 187 | FAN_EC_CONFIG = get_device_ec_fans(PRODUCT_NAME, PRODUCT_VERSION) 188 | 189 | logger.info(f"FAN_EC_CONFIG: {FAN_EC_CONFIG}") 190 | 191 | except Exception as e: 192 | logger.error(f"风扇配置异常|{e}", exc_info=True) 193 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (C) 2022 Sefa Eyeoglu (https://scrumplex.net) 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | import { 19 | definePlugin, 20 | PanelSectionRow, 21 | staticClasses, 22 | SteamSpinner, 23 | Tabs, 24 | } from "@decky/ui"; 25 | import { FC, useEffect, useMemo, useState } from "react"; 26 | import { FaFan, FaLayerGroup, FaSuperpowers } from "react-icons/fa"; 27 | import { 28 | Backend, 29 | ComponentName, 30 | PluginManager, 31 | Settings, 32 | UpdateType, 33 | } from "./util"; 34 | import { 35 | // GPUComponent, 36 | // CPUComponent, 37 | SettingsComponent, 38 | FANComponent, 39 | MoreComponent, 40 | QuickAccessTitleView, 41 | PowerComponent, 42 | } from "./components"; 43 | import { TabCpu, TabGpu, TabPower, TabMore, TabFans } from "./tab"; 44 | import { BsCpuFill } from "react-icons/bs"; 45 | import { PiGraphicsCardFill, PiLightningFill } from "react-icons/pi"; 46 | 47 | const ListView: FC<{}> = ({}) => { 48 | return ( 49 | <> 50 | 51 | {/* 52 | 53 | */} 54 | 55 | 56 | 57 | ); 58 | }; 59 | 60 | const TabView: FC<{ show?: boolean }> = ({ show = true }) => { 61 | const [currentTabRoute, setCurrentTabRoute] = useState( 62 | Settings.currentTabRoute 63 | ); 64 | 65 | const updateCurrentTabRoute = (route: string) => { 66 | setCurrentTabRoute(route); 67 | Settings.currentTabRoute = route; 68 | }; 69 | 70 | const supportChargeLimit = useMemo(() => { 71 | return Backend.data.getSupportsChargeLimit(); 72 | }, []); 73 | 74 | const isSupportSoftwareChargeLimit = useMemo(() => { 75 | return Backend.data.getSupportsSoftwareChargeLimit(); 76 | }, []); 77 | 78 | const showPowerTab = useMemo(() => { 79 | return supportChargeLimit || isSupportSoftwareChargeLimit; 80 | }, [supportChargeLimit, isSupportSoftwareChargeLimit]); 81 | 82 | return ( 83 | <> 84 | 93 | 94 | {show && ( 95 |
105 | { 108 | updateCurrentTabRoute(tabID); 109 | }} 110 | tabs={[ 111 | // { 112 | // title: , 113 | // content: , 114 | // id: "cpu", 115 | // }, 116 | // { 117 | // title: ( 118 | // 119 | // ), 120 | // content: , 121 | // id: "gpu", 122 | // }, 123 | { 124 | title: , 125 | content: , 126 | id: "fans", 127 | }, 128 | // ...(showPowerTab 129 | // ? [ 130 | // { 131 | // title: ( 132 | // 136 | // ), 137 | // content: , 138 | // id: "power", 139 | // }, 140 | // ] 141 | // : []), 142 | { 143 | title: , 144 | content: , 145 | id: "more", 146 | }, 147 | ]} 148 | /> 149 |
150 | )} 151 | {!show && } 152 | 153 | ); 154 | }; 155 | const Content: FC<{}> = ({}) => { 156 | const [useOldUI, setUseOldUI] = useState(Settings.useOldUI); 157 | const [show, setShow] = useState(Settings.ensureEnable()); 158 | 159 | const hide = (ishide: boolean) => { 160 | setShow(!ishide); 161 | }; 162 | 163 | const refresh = () => { 164 | setUseOldUI(Settings.useOldUI); 165 | }; 166 | 167 | //listen Settings 168 | useEffect(() => { 169 | PluginManager.listenUpdateComponent( 170 | ComponentName.TAB_ALL, 171 | [ComponentName.TAB_ALL], 172 | (_ComponentName, updateType) => { 173 | switch (updateType) { 174 | case UpdateType.HIDE: { 175 | hide(true); 176 | break; 177 | } 178 | case UpdateType.SHOW: { 179 | hide(false); 180 | break; 181 | } 182 | case UpdateType.UPDATE: { 183 | refresh(); 184 | break; 185 | } 186 | } 187 | } 188 | ); 189 | }, []); 190 | 191 | return ( 192 | <> 193 | {PluginManager.isIniting() && ( 194 | 195 | 196 | 197 | )} 198 | {PluginManager.isRunning() && 199 | (useOldUI ? : )} 200 | {PluginManager.isError() && ( 201 | <> 202 | 203 |
204 | {`Error: ${PluginManager.getErrorMessage()}`} 205 |
206 |
207 | 208 | 209 | )} 210 | 211 | ); 212 | }; 213 | 214 | export default definePlugin(() => { 215 | try { 216 | console.log(">>>>>>>>>>>>>>>> Registering plugin PowerControl"); 217 | PluginManager.register(); 218 | } catch (e) { 219 | console.log("Error while registering plugin", e); 220 | } 221 | 222 | return { 223 | title:
PowerControl
, 224 | titleView: , 225 | content: , 226 | icon: , 227 | onDismount() { 228 | PluginManager?.unregister(); 229 | }, 230 | }; 231 | }); 232 | -------------------------------------------------------------------------------- /src/components/power.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | ButtonItem, 3 | PanelSection, 4 | PanelSectionRow, 5 | ToggleField, 6 | } from "@decky/ui"; 7 | import { FC, useEffect, useMemo, useState } from "react"; 8 | import { 9 | Backend, 10 | ComponentName, 11 | PluginManager, 12 | Settings, 13 | UpdateType, 14 | } from "../util"; 15 | import { SlowSliderField } from "./SlowSliderField"; 16 | import { localizationManager, localizeStrEnum } from "../i18n"; 17 | import { RiArrowDownSFill, RiArrowUpSFill } from "react-icons/ri"; 18 | 19 | const BypassChargeComponent: FC = () => { 20 | const [bypassCharge, setBypassCharge] = useState( 21 | Settings.appBypassCharge() 22 | ); 23 | 24 | // const [chargeLimit, setChargeLimit] = useState( 25 | // Settings.appChargeLimit() 26 | // ); 27 | 28 | const refresh = () => { 29 | setBypassCharge(Settings.appBypassCharge()); 30 | // setChargeLimit(Settings.appChargeLimit()); 31 | }; 32 | 33 | useEffect(() => { 34 | PluginManager.listenUpdateComponent( 35 | ComponentName.POWER_BYPASS_CHARGE, 36 | [ 37 | ComponentName.POWER_BYPASS_CHARGE, 38 | ComponentName.POWER_ALL, 39 | ComponentName.POWER_CHARGE_LIMIT, 40 | ], 41 | (_ComponentName, updateType) => { 42 | switch (updateType) { 43 | case UpdateType.UPDATE: { 44 | refresh(); 45 | break; 46 | } 47 | } 48 | } 49 | ); 50 | }, []); 51 | 52 | // useEffect(() => { 53 | // // 实时获取旁路供电状态 54 | // Backend.getBypassCharge().then((value) => { 55 | // setBypassCharge(value); 56 | // }); 57 | // }, [bypassCharge]); 58 | 59 | return ( 60 | <> 61 | 62 | { 71 | Settings.setBypassCharge(value); 72 | }} 73 | /> 74 | 75 | 76 | ); 77 | }; 78 | 79 | const ChargeLimitComponent: FC = () => { 80 | const [chargeLimit, setChargeLimit] = useState( 81 | Settings.appChargeLimit() 82 | ); 83 | const [bypassCharge, setBypassCharge] = useState( 84 | Settings.appBypassCharge() 85 | ); 86 | 87 | const [supportsResetChargeLimit, __] = useState( 88 | Backend.data.getSupportsResetChargeLimit() 89 | ); 90 | 91 | const [enableChargeLimit, setEnableChargeLimit] = useState( 92 | Settings.appEnableChargeLimit() 93 | ); 94 | 95 | const refresh = () => { 96 | setChargeLimit(Settings.appChargeLimit()); 97 | setBypassCharge(Settings.appBypassCharge()); 98 | setEnableChargeLimit(Settings.appEnableChargeLimit()); 99 | }; 100 | 101 | useEffect(() => { 102 | PluginManager.listenUpdateComponent( 103 | ComponentName.POWER_CHARGE_LIMIT, 104 | [ 105 | ComponentName.POWER_CHARGE_LIMIT, 106 | ComponentName.POWER_ALL, 107 | ComponentName.POWER_BYPASS_CHARGE, 108 | ], 109 | (_ComponentName, updateType) => { 110 | switch (updateType) { 111 | case UpdateType.UPDATE: { 112 | refresh(); 113 | break; 114 | } 115 | } 116 | } 117 | ); 118 | }, []); 119 | 120 | return ( 121 | <> 122 | {supportsResetChargeLimit && ( 123 | 124 | { 128 | Settings.setEnableChargeLimit(enableChargeLimit); 129 | }} 130 | /> 131 | 132 | )} 133 | {((supportsResetChargeLimit && enableChargeLimit) || 134 | !supportsResetChargeLimit) && ( 135 | 136 | { 160 | Settings.setChargeLimit(value); 161 | }} 162 | /> 163 | 164 | )} 165 | 166 | ); 167 | }; 168 | 169 | export const PowerComponent: FC<{ isTab?: boolean }> = ({ isTab = false }) => { 170 | const [show, setShow] = useState(Settings.ensureEnable()); 171 | 172 | const supportChargeLimit = useMemo(() => { 173 | return Backend.data.getSupportsChargeLimit(); 174 | }, []); 175 | 176 | const isSupportSoftwareChargeLimit = useMemo(() => { 177 | return Backend.data.getSupportsSoftwareChargeLimit(); 178 | }, []); 179 | 180 | const [showPowerMenu, setShowPowerMenu] = useState( 181 | Settings.showPowerMenu 182 | ); 183 | const updateShowPowerMenu = (show: boolean) => { 184 | setShowPowerMenu(show); 185 | Settings.showPowerMenu = show; 186 | }; 187 | 188 | const hide = (ishide: boolean) => { 189 | setShow(!ishide); 190 | }; 191 | 192 | useEffect(() => { 193 | PluginManager.listenUpdateComponent( 194 | ComponentName.POWER_ALL, 195 | [ComponentName.POWER_ALL], 196 | (_ComponentName, updateType) => { 197 | switch (updateType) { 198 | case UpdateType.HIDE: { 199 | hide(true); 200 | break; 201 | } 202 | case UpdateType.SHOW: { 203 | hide(false); 204 | break; 205 | } 206 | } 207 | } 208 | ); 209 | }, []); 210 | 211 | useEffect(() => { 212 | setShow( 213 | Settings.ensureEnable() && 214 | (supportChargeLimit || isSupportSoftwareChargeLimit) 215 | ); 216 | }, [supportChargeLimit, isSupportSoftwareChargeLimit]); 217 | 218 | return ( 219 |
220 | {show && ( 221 | 222 | {!isTab && ( 223 | 224 | updateShowPowerMenu(!showPowerMenu)} 234 | > 235 | {showPowerMenu ? : } 236 | 237 | 238 | )} 239 | {(showPowerMenu || isTab) && ( 240 | <> 241 | {supportChargeLimit && } 242 | {isSupportSoftwareChargeLimit && } 243 | 244 | )} 245 | 246 | )} 247 |
248 | ); 249 | }; 250 | -------------------------------------------------------------------------------- /src/components/more.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | ButtonItem, 3 | Field, 4 | PanelSection, 5 | PanelSectionRow, 6 | ToggleField, 7 | } from "@decky/ui"; 8 | import { FC, useEffect, useState } from "react"; 9 | import { localizationManager, localizeStrEnum } from "../i18n"; 10 | import { Backend, Settings, compareVersions } from "../util"; 11 | import { getVersion, getLatestVersion, updateLatest } from "../util/backend"; 12 | import { ActionButtonItem } from "."; 13 | 14 | interface VersionCache { 15 | currentVersion: string; 16 | latestVersion: string; 17 | lastCheckTime: number; 18 | cacheExpiry: number; 19 | } 20 | 21 | const CACHE_KEY = 'powercontrol_version_cache'; 22 | const CACHE_DURATION = 6 * 60 * 60 * 1000; // 6小时 23 | 24 | const getVersionCache = (): VersionCache | null => { 25 | const cached = localStorage.getItem(CACHE_KEY); 26 | if (!cached) return null; 27 | 28 | const cache = JSON.parse(cached); 29 | if (Date.now() > cache.cacheExpiry) { 30 | localStorage.removeItem(CACHE_KEY); 31 | return null; 32 | } 33 | 34 | return cache; 35 | }; 36 | 37 | const setVersionCache = (current: string, latest: string) => { 38 | const cache: VersionCache = { 39 | currentVersion: current, 40 | latestVersion: latest, 41 | lastCheckTime: Date.now(), 42 | cacheExpiry: Date.now() + CACHE_DURATION 43 | }; 44 | localStorage.setItem(CACHE_KEY, JSON.stringify(cache)); 45 | }; 46 | 47 | const getLastCheckText = (lastCheckTime: number): string => { 48 | const now = Date.now(); 49 | const diff = now - lastCheckTime; 50 | 51 | if (diff < 60 * 1000) { 52 | return localizationManager.getString(localizeStrEnum.JUST_NOW) || "just now"; 53 | } 54 | 55 | if (diff < 60 * 60 * 1000) { 56 | const minutes = Math.floor(diff / 60000); 57 | return localizationManager.getString(localizeStrEnum.MINUTES_AGO)?.replace('{{minutes}}', minutes.toString()) 58 | || `${minutes} minutes ago`; 59 | } 60 | 61 | if (diff < 24 * 60 * 60 * 1000) { 62 | const hours = Math.floor(diff / 3600000); 63 | return localizationManager.getString(localizeStrEnum.HOURS_AGO)?.replace('{{hours}}', hours.toString()) 64 | || `${hours} hours ago`; 65 | } 66 | 67 | const days = Math.floor(diff / 86400000); 68 | return localizationManager.getString(localizeStrEnum.DAYS_AGO)?.replace('{{days}}', days.toString()) 69 | || `${days} days ago`; 70 | }; 71 | 72 | export const MoreComponent: FC<{ isTab?: boolean }> = ({ isTab = false }) => { 73 | const [currentVersion, setCurrentVersion] = useState( 74 | Backend.data?.getCurrentVersion() || "" 75 | ); 76 | const [latestVersion, setLatestVersion] = useState( 77 | Backend.data?.getLatestVersion() || "" 78 | ); 79 | 80 | const [useOldUI, setUseOldUI] = useState( 81 | Settings.useOldUI 82 | ); 83 | const [isChecking, setIsChecking] = useState(false); 84 | const [lastCheckTime, setLastCheckTime] = useState(null); 85 | 86 | const updateUseOldUI = (value: boolean) => { 87 | Settings.useOldUI = value; 88 | setUseOldUI(value); 89 | }; 90 | 91 | const performVersionCheck = async (): Promise => { 92 | try { 93 | const [current, latest] = await Promise.all([ 94 | getVersion(), 95 | getLatestVersion() 96 | ]); 97 | 98 | setCurrentVersion(current); 99 | setLatestVersion(latest); 100 | setVersionCache(current, latest); 101 | setLastCheckTime(Date.now()); 102 | } catch (error) { 103 | console.error('版本检查失败:', error); 104 | // 失败时尝试显示过期缓存 105 | const expiredCache = localStorage.getItem(CACHE_KEY); 106 | if (expiredCache) { 107 | const cache = JSON.parse(expiredCache); 108 | setCurrentVersion(cache.currentVersion); 109 | setLatestVersion(cache.latestVersion); 110 | setLastCheckTime(cache.lastCheckTime); 111 | } 112 | } 113 | }; 114 | 115 | const handleManualCheck = async () => { 116 | setIsChecking(true); 117 | try { 118 | await performVersionCheck(); 119 | } finally { 120 | setIsChecking(false); 121 | } 122 | }; 123 | 124 | useEffect(() => { 125 | const initVersions = async () => { 126 | const cache = getVersionCache(); 127 | if (cache) { 128 | setCurrentVersion(cache.currentVersion); 129 | setLatestVersion(cache.latestVersion); 130 | setLastCheckTime(cache.lastCheckTime); 131 | return; 132 | } 133 | 134 | // 缓存失效,进行检查 135 | await performVersionCheck(); 136 | }; 137 | 138 | initVersions(); 139 | }, []); // 空依赖数组,仅首次执行 140 | 141 | let uptButtonText = 142 | localizationManager.getString(localizeStrEnum.REINSTALL_PLUGIN) || 143 | "Reinstall Plugin"; 144 | 145 | if (currentVersion !== latestVersion && Boolean(latestVersion)) { 146 | const versionCompare = compareVersions(latestVersion, currentVersion); 147 | if (versionCompare > 0) { 148 | uptButtonText = `${localizationManager.getString(localizeStrEnum.UPDATE_PLUGIN) || "Update" 149 | } ${latestVersion}`; 150 | } else if (versionCompare < 0) { 151 | uptButtonText = `${localizationManager.getString(localizeStrEnum.ROLLBACK_PLUGIN) || 152 | "Rollback" 153 | } ${latestVersion}`; 154 | } 155 | } 156 | 157 | return ( 158 |
159 | 162 | 163 | { 170 | updateUseOldUI(value); 171 | }} 172 | /> 173 | 174 | 175 | { 178 | await updateLatest(); 179 | }} 180 | > 181 | {uptButtonText} 182 | 183 | 184 | 185 | 195 | {isChecking 196 | ? (localizationManager.getString(localizeStrEnum.CHECKING_VERSION) || "Checking...") 197 | : (localizationManager.getString(localizeStrEnum.CHECK_VERSION) || "Check Version") 198 | } 199 | 200 | 201 | 202 | { 205 | Settings.resetSettings(); 206 | }} 207 | > 208 | {localizationManager.getString(localizeStrEnum.RESET_ALL) || 209 | "Reset All"} 210 | 211 | 212 | 213 | 222 | {currentVersion} 223 | 224 | 225 | {Boolean(latestVersion) && ( 226 | 227 | 235 | {latestVersion} 236 | 237 | 238 | )} 239 | 240 |
241 | ); 242 | }; 243 | -------------------------------------------------------------------------------- /src/components/settings.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | PanelSection, 3 | PanelSectionRow, 4 | ToggleField, 5 | Marquee, 6 | DialogButton, 7 | Focusable, 8 | quickAccessMenuClasses, 9 | ModalRoot, 10 | showModal, 11 | ScrollPanelGroup, 12 | ButtonItem, 13 | } from "@decky/ui"; 14 | import MarkDownIt from "markdown-it"; 15 | import { useEffect, useState, FC } from "react"; 16 | import { RiArrowDownSFill, RiArrowUpSFill } from "react-icons/ri"; 17 | import { 18 | Settings, 19 | PluginManager, 20 | RunningApps, 21 | DEFAULT_APP, 22 | ComponentName, 23 | UpdateType, 24 | ACStateManager, 25 | EACState, 26 | Logger, 27 | } from "../util"; 28 | import { getPowerInfo } from "../util/backend"; 29 | import { localizeStrEnum, localizationManager } from "../i18n"; 30 | import { FaExclamationCircle } from "react-icons/fa"; 31 | 32 | const SettingsEnableComponent: FC = () => { 33 | const [enable, setEnable] = useState(Settings.ensureEnable()); 34 | const refresh = () => { 35 | setEnable(Settings.ensureEnable()); 36 | }; 37 | //listen Settings 38 | useEffect(() => { 39 | if (!enable) { 40 | PluginManager.updateAllComponent(UpdateType.HIDE); 41 | } else { 42 | PluginManager.updateAllComponent(UpdateType.SHOW); 43 | } 44 | PluginManager.listenUpdateComponent( 45 | ComponentName.SET_ENABLE, 46 | [ComponentName.SET_ENABLE], 47 | (_ComponentName, updateType) => { 48 | switch (updateType) { 49 | case UpdateType.UPDATE: { 50 | refresh(); 51 | break; 52 | } 53 | } 54 | } 55 | ); 56 | }, []); 57 | return ( 58 |
59 | 60 | { 64 | Settings.setEnable(enabled); 65 | }} 66 | /> 67 | 68 |
69 | ); 70 | }; 71 | 72 | const SettingsPerAppComponent: FC = () => { 73 | const [override, setOverWrite] = useState(Settings.appOverWrite()); 74 | const [overrideable, setOverWriteable] = useState( 75 | RunningApps.active() != DEFAULT_APP 76 | ); 77 | const [show, setShow] = useState(Settings.ensureEnable()); 78 | const hide = (ishide: boolean) => { 79 | setShow(!ishide); 80 | }; 81 | const refresh = () => { 82 | setOverWrite(Settings.appOverWrite()); 83 | setOverWriteable(RunningApps.active() != DEFAULT_APP); 84 | }; 85 | //listen Settings 86 | useEffect(() => { 87 | PluginManager.listenUpdateComponent( 88 | ComponentName.SET_PERAPP, 89 | [ComponentName.SET_PERAPP], 90 | (_ComponentName, updateType: string) => { 91 | switch (updateType) { 92 | case UpdateType.UPDATE: 93 | refresh(); 94 | //console.log(`fn:invoke refresh:${updateType} ${UpdateType.UPDATE}`) 95 | break; 96 | case UpdateType.SHOW: 97 | hide(false); 98 | //console.log(`fn:invoke show:${updateType} ${UpdateType.SHOW}`) 99 | break; 100 | case UpdateType.HIDE: 101 | hide(true); 102 | //console.log(`fn:invoke hide:${updateType} ${UpdateType.HIDE}`) 103 | break; 104 | } 105 | } 106 | ); 107 | }, []); 108 | 109 | // console.log( 110 | // `######## >>>>>>>> active_appInfo:`, 111 | // JSON.stringify(RunningApps.active_appInfo(), null, 2) 112 | // ); 113 | 114 | return ( 115 |
116 | {show && ( 117 | 118 | 124 | 145 |
146 | {localizationManager.getString(localizeStrEnum.USING) + 147 | (override && overrideable ? "『" : "")} 148 |
149 | {/* @ts-ignore */} 150 | 160 | {override && overrideable 161 | ? `${RunningApps.active_appInfo()?.display_name}` 162 | : `${localizationManager.getString( 163 | localizeStrEnum.DEFAULT 164 | )}`} 165 | 166 |
167 | {(override && overrideable ? "』" : "") + 168 | localizationManager.getString(localizeStrEnum.PROFILE)} 169 |
170 |
171 | } 172 | checked={override && overrideable} 173 | disabled={!overrideable} 174 | onChange={(override) => { 175 | Settings.setOverWrite(override); 176 | }} 177 | /> 178 | 179 | )} 180 | 181 | ); 182 | }; 183 | 184 | const SettingsPerAcStateComponent: FC = () => { 185 | const [appACStateOverWrite, setAppACStateOverWrite] = useState( 186 | Settings.appACStateOverWrite() 187 | ); 188 | const [acstate, setACState] = useState(ACStateManager.getACState()); 189 | const [show, setShow] = useState(Settings.ensureEnable()); 190 | 191 | const hide = (ishide: boolean) => { 192 | setShow(!ishide); 193 | }; 194 | 195 | const refresh = () => { 196 | setAppACStateOverWrite(Settings.appACStateOverWrite()); 197 | setACState(ACStateManager.getACState()); 198 | }; 199 | 200 | useEffect(() => { 201 | PluginManager.listenUpdateComponent( 202 | ComponentName.SET_PERACMODE, 203 | [ComponentName.SET_PERACMODE], 204 | (_ComponentName, updateType: string) => { 205 | switch (updateType) { 206 | case UpdateType.UPDATE: 207 | refresh(); 208 | break; 209 | case UpdateType.SHOW: 210 | hide(false); 211 | break; 212 | case UpdateType.HIDE: 213 | hide(true); 214 | break; 215 | } 216 | } 217 | ); 218 | }); 219 | 220 | const getAcSteteName = (acstate: EACState) => { 221 | if (acstate === EACState.Connected) { 222 | return localizationManager.getString(localizeStrEnum.AC_MODE); 223 | } else if (acstate === EACState.Disconnected) { 224 | return localizationManager.getString(localizeStrEnum.BAT_MODE); 225 | } 226 | return acstate; 227 | }; 228 | 229 | const getDescription = (acstate: EACState) => { 230 | return ( 231 | localizationManager.getString(localizeStrEnum.USING) + 232 | (appACStateOverWrite ? "『" : "") + 233 | (appACStateOverWrite 234 | ? getAcSteteName(acstate) 235 | : localizationManager.getString(localizeStrEnum.DEFAULT)) + 236 | (appACStateOverWrite ? "』" : "") + 237 | localizationManager.getString(localizeStrEnum.PROFILE) 238 | ); 239 | }; 240 | 241 | return ( 242 |
243 | {show && ( 244 | 245 | { 252 | Settings.setACStateOverWrite(override); 253 | }} 254 | /> 255 | 256 | )} 257 |
258 | ); 259 | }; 260 | 261 | export const SettingsComponent: FC<{ 262 | isTab?: boolean; 263 | }> = ({ isTab = false }) => { 264 | const [showSettings, setShowSettings] = useState( 265 | Settings.showSettingMenu 266 | ); 267 | const updateShowSettings = (show: boolean) => { 268 | setShowSettings(show); 269 | Settings.showSettingMenu = show; 270 | }; 271 | return ( 272 | <> 273 | 276 | {!isTab && ( 277 | 278 | updateShowSettings(!showSettings)} 288 | > 289 | {showSettings ? : } 290 | 291 | 292 | )} 293 | {(showSettings || isTab) && ( 294 | <> 295 | 296 | 297 | 298 | 299 | )} 300 | 301 | 302 | ); 303 | }; 304 | 305 | const buttonStyle = { 306 | height: "28px", 307 | width: "40px", 308 | minWidth: 0, 309 | padding: 0, 310 | display: "flex", 311 | justifyContent: "center", 312 | alignItems: "center", 313 | }; 314 | 315 | export const QuickAccessTitleView: FC<{ title: string }> = ({ title }) => { 316 | return ( 317 | // @ts-ignore 318 | 327 |
{title}
328 | { 332 | showModal(); 333 | }} 334 | > 335 | 336 | 337 |
338 | ); 339 | }; 340 | 341 | export const PowerInfoModel: FC = ({ 342 | closeModal, 343 | }: { 344 | closeModal?: () => void; 345 | }) => { 346 | const fontStyle: React.CSSProperties = { 347 | fontFamily: 348 | "'DejaVu Sans Mono', Hack, 'Source Code Pro', 'Courier New', monospace, Consolas", 349 | fontSize: "12px", 350 | lineHeight: "0.2", // 调整行距 351 | maxHeight: "300px", // 设置最大高度 352 | overflow: "auto", // 添加滚动条 353 | whiteSpace: "pre", 354 | margin: "10px 0", 355 | }; 356 | 357 | // @ts-ignore 358 | const mdIt = new MarkDownIt({ 359 | html: true, 360 | }); 361 | 362 | const [info, setInfo] = useState(""); 363 | Logger.info(`fn:invoke PowerInfoModel: ${info}`); 364 | 365 | const fetchPowerInfo = () => { 366 | // if amd 367 | // if (Backend.data.getCpuVendor() === "AuthenticAMD") { 368 | // Logger.info(`fn:invoke getRyzenadjInfo`); 369 | // Backend.getRyzenadjInfo().then((info) => { 370 | // setInfo(info); 371 | // }); 372 | // } else { 373 | // Logger.info(`fn:invoke getRAPLInfo`); 374 | // Backend.getRAPLInfo().then((info) => { 375 | // setInfo(info); 376 | // }); 377 | // } 378 | Logger.info(`fn:invoke getPowerInfo`); 379 | getPowerInfo().then((info: string) => { 380 | setInfo(info); 381 | }); 382 | }; 383 | 384 | useEffect(() => { 385 | fetchPowerInfo(); 386 | 387 | // 每5秒刷新一次 388 | const interval = setInterval(() => { 389 | fetchPowerInfo(); 390 | }, 5000); 391 | return () => clearInterval(interval); 392 | }, []); 393 | 394 | return ( 395 | 396 |
397 | 398 | 399 | { 401 | getPowerInfo(); 402 | }} 403 | > 404 | Reload 405 | 406 | 407 | 411 | 414 | {info.split("\n").map((line, index) => ( 415 |

{line}

416 | ))} 417 |
418 | } 419 | > 420 | 421 | 422 | 423 |
424 | ); 425 | }; 426 | --------------------------------------------------------------------------------