Plasmoide CPU Frequency Control: governor, limiti, EPP, boost e profili per task
Widget KDE Plasma 6 (org.enne2.cpufreq) per pilotare il profilo energetico della CPU: governor, limiti min/max, energy_performance_preference, boost/turbo, quattro profili pronti (Silenzioso, Risparmio, Bilanciato, Performance) e task con profilo dedicato che ripristina lo stato precedente alla fine del comando. Privilegi separati: il widget resta non privilegiato e l'unica operazione root passa da un helper a interfaccia ristretta in /usr/local/libexec/enne2-cpufreq/ (comandi a elenco chiuso, argomenti validati su sysfs, rilettura di verifica), invocato via pkexec con l'azione polkit dedicata org.enne2.cpufreq.set. La regola in /etc/polkit-1/rules.d/ concede il consenso senza prompt a utenti locali in sessione attiva nel gruppo wheel; senza la regola la policy chiede la password. Dettagli verificati su frigate (Plasma 6.7.5, kernel 7.2, amd-pstate-epp): - con boost disattivato cpuinfo_max_freq scende alla frequenza nominale, quindi il tetto hardware si legge da amd_pstate_max_freq e i profili applicano il boost prima dei limiti; - scrittura dei limiti in ordine sicuro (mai min > max intermedio); - baseline e contatore di riferimento per i task paralleli; - syncControls protetto da uiReady e try/catch: un ReferenceError su un id della rappresentazione non deve interrompere l'aggiornamento dello stato; - implicitWidth/Height costanti, altrimenti il containment riduce il widget a 68x68. Include install.sh/uninstall.sh (widget, backend, dry-run), README con modello di sicurezza e diagnostica, screenshot docs/widget.png. Licenza MIT.
This commit is contained in:
+12
@@ -0,0 +1,12 @@
|
||||
# Artefatti Plasma / Qt
|
||||
*.plasmoid
|
||||
*.qmlc
|
||||
*.jsc
|
||||
build/
|
||||
package/
|
||||
|
||||
# Editor e sistema
|
||||
*~
|
||||
*.swp
|
||||
.DS_Store
|
||||
*.backup-*
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 enne2
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,163 @@
|
||||
# CPU Frequency Control (org.enne2.cpufreq)
|
||||
|
||||
Plasmoide KDE Plasma 6 per pilotare il profilo energetico della CPU: governor,
|
||||
limiti di frequenza (min/max), `energy_performance_preference` (EPP), boost/turbo,
|
||||
quattro profili pronti e **task con profilo dedicato** che ripristina lo stato
|
||||
precedente quando il comando finisce.
|
||||
|
||||
Verificato su **frigate** (Fedora 44, Plasma 6.7.5 Wayland, kernel 7.2, AMD
|
||||
Ryzen 7 H 255, driver `amd-pstate-epp` in modalità `active`, 16 policy).
|
||||
|
||||

|
||||
|
||||
## Cosa fa
|
||||
|
||||
| Area | Azione |
|
||||
|---|---|
|
||||
| Profili | **Silenzioso** (EPP power, boost off, tetto 35%), **Risparmio** (balance_power, boost off, tetto 60%), **Bilanciato** (balance_performance, boost on, nessun tetto), **Performance** (governor performance, EPP performance, boost on) |
|
||||
| Governor | pillole con i valori realmente disponibili in `scaling_available_governors` |
|
||||
| EPP | pillole con i valori di `energy_performance_available_preferences` |
|
||||
| Limiti | due slider min/max in kHz con validazione e rilettura; il valore applicato è arrotondato a 10 MHz |
|
||||
| Boost | pillole `on`/`off` (per-policy su kernel recenti, altrimenti globale, altrimenti `intel_pstate/no_turbo`) |
|
||||
| Task | elenco configurabile: ogni task applica un profilo, esegue il comando **come utente** e ripristina la baseline all'uscita |
|
||||
| Footer | messaggio dell'ultima azione e pulsante **Reset** (default hardware: boost on, nessun tetto) |
|
||||
|
||||
La vista compatta (pannello) mostra frequenza corrente e un badge `no boost`.
|
||||
La frequenza è la massima fra le policy (core più attivo), con la media sotto.
|
||||
|
||||
## Architettura e modello di sicurezza
|
||||
|
||||
```
|
||||
org.enne2.cpufreq (QML, non privilegiato)
|
||||
├─ contents/scripts/cpufreq-state.sh lettura sysfs, /proc, hwmon (nessun privilegio)
|
||||
├─ contents/scripts/enne2-cpufreq-task launcher task (utente)
|
||||
└─ pkexec /usr/local/libexec/enne2-cpufreq/cpufreq-helper (root)
|
||||
├─ comandi a elenco chiuso, argomenti validati contro sysfs
|
||||
├─ ogni scrittura riletta per verifica
|
||||
└─ /var/lib/enne2-cpufreq/{baseline,refcount} (root:root 0755/0644)
|
||||
|
||||
polkit
|
||||
├─ /usr/share/polkit-1/actions/org.enne2.cpufreq.set.policy azione dedicata
|
||||
└─ /etc/polkit-1/rules.d/50-org.enne2.cpufreq.rules allow senza prompt
|
||||
```
|
||||
|
||||
- Il widget non è mai root: l'unica operazione privilegiata è l'helper, con
|
||||
sottocomandi fissi (`set-governor`, `set-epp`, `set-limits`, `set-boost`,
|
||||
`apply`, `reset`, `task-begin`, `task-end`, `save-state`, `restore-state`).
|
||||
- L'annotazione `org.freedesktop.policykit.exec.path` lega l'azione polkit a quel
|
||||
solo binario: nessun altro programma eredita il permesso.
|
||||
- La regola in `/etc/polkit-1/rules.d/` concede `polkit.Result.YES` solo a utenti
|
||||
**locali**, con sessione **attiva**, nel gruppo **wheel**. Senza quel file la
|
||||
`.policy` chiede la password a ogni modifica (`auth_admin_keep`).
|
||||
- I comandi dei task girano come utente (`setsid sh -c`), mai come root.
|
||||
|
||||
## Installazione
|
||||
|
||||
```sh
|
||||
cd ~/Dev/kde-cpufreq-dashboard
|
||||
./install.sh # widget (utente) + backend (sudo)
|
||||
./install.sh --widget # solo il plasmoide
|
||||
./install.sh --backend # solo helper + polkit (root)
|
||||
./install.sh --no-polkit-rule # backend senza la regola "nessun prompt"
|
||||
./install.sh --dry-run # mostra le operazioni
|
||||
```
|
||||
|
||||
Il widget si aggiunge da *Aggiungi widget → «CPU Frequency Control»* oppure via
|
||||
scripting Plasma:
|
||||
|
||||
```js
|
||||
desktops()[0].addWidget("org.enne2.cpufreq")
|
||||
```
|
||||
|
||||
Disinstallazione: `./uninstall.sh` (ripristina la baseline prima di rimuovere
|
||||
l'helper). I task configurati restano in `~/.config/enne2-cpufreq/tasks.json`.
|
||||
|
||||
## Task con profilo dedicato
|
||||
|
||||
Dall'interfaccia: **Nuovo** → nome, comando, profilo. Da terminale:
|
||||
|
||||
```sh
|
||||
enne2-cpufreq-task list
|
||||
enne2-cpufreq-task add "Render Blender" performance "blender -b scena.blend -f 1"
|
||||
enne2-cpufreq-task run render-blender
|
||||
enne2-cpufreq-task stop render-blender
|
||||
enne2-cpufreq-task active
|
||||
```
|
||||
|
||||
Sequenza di `run`:
|
||||
|
||||
1. `helper task-begin <profilo>`: salva la baseline (solo al primo task) e applica il profilo
|
||||
2. il comando parte staccato (`setsid`) e scrive il proprio pid in
|
||||
`$XDG_RUNTIME_DIR/enne2-cpufreq/<slug>.pid`
|
||||
3. un watcher attende l'uscita del pid e chiama `helper task-end`, che decrementa
|
||||
un contatore e ripristina la baseline a zero (con `notify-send` di conferma)
|
||||
|
||||
Più task in parallelo sono contati: il ripristino avviene solo quando l'ultimo
|
||||
termina. `enne2-cpufreq-task remove` rifiuta di rimuovere un task in esecuzione.
|
||||
|
||||
## Comportamento sui driver
|
||||
|
||||
- **amd-pstate-epp (active)**: `powersave`/`performance` sono policy del driver;
|
||||
`powersave` *non* blocca la frequenza al minimo, EPP è il vero bias.
|
||||
Con `boost=0` il kernel abbassa `cpuinfo_max_freq` alla frequenza nominale
|
||||
(3.80 GHz invece di 4.97 GHz): il tetto reale viene letto da
|
||||
`amd_pstate_max_freq`, e per questo `apply` imposta **prima** il boost e poi i
|
||||
limiti.
|
||||
- **intel_pstate**: boost tramite `intel_pstate/no_turbo` (invertito).
|
||||
- **Altri driver cpufreq**: governor e limiti restano validi; EPP e boost
|
||||
compaiono solo se le interfacce esistono.
|
||||
|
||||
## Servizi concorrenti
|
||||
|
||||
Se `tuned`, `power-profiles-daemon` o `tlp` sono attivi il widget lo segnala: il
|
||||
profilo `balanced` di tuned scrive esattamente `governor`, `energy_performance_preference`
|
||||
e `boost`, quindi un `tuned-adm profile` o il boot possono sovrascrivere le
|
||||
impostazioni. Su frigate `tuned` è attivo: le modifiche valgono fino al prossimo
|
||||
apply di tuned.
|
||||
|
||||
## Diagnosi
|
||||
|
||||
```sh
|
||||
/usr/local/libexec/enne2-cpufreq/cpufreq-helper status # stato, baseline, refcount
|
||||
~/.local/share/plasma/plasmoids/org.enne2.cpufreq/contents/scripts/cpufreq-state.sh
|
||||
pkexec /usr/local/libexec/enne2-cpufreq/cpufreq-helper apply balanced
|
||||
journalctl --user -u plasma-plasmashell.service -f | grep cpufreq
|
||||
```
|
||||
|
||||
| Sintomo | Causa |
|
||||
|---|---|
|
||||
| banner rosso «Backend non installato» | manca `/usr/local/libexec/enne2-cpufreq/` → `./install.sh --backend` |
|
||||
| dialog password a ogni modifica | manca la regola polkit (o utente fuori da `wheel`) |
|
||||
| «autorizzazione negata da polkit» | regola non caricata: `systemctl try-reload-or-restart polkit` |
|
||||
| valori che tornano indietro | servizio concorrente (tuned/power-profiles-daemon/tlp) |
|
||||
| il widget resta 68×68 | `implicitWidth/Height` del plasmoide non validi: verificare `main.qml` |
|
||||
|
||||
## Note di implementazione Plasma 6
|
||||
|
||||
- `metadata.json` con `X-Plasma-API-Minimum-Version: "6.0"`, senza chiavi Plasma 5.
|
||||
- Comandi via `org.kde.plasma.plasma5support` (`engine: "executable"`); ogni
|
||||
azione usa un source univoco (contatore in coda) perché il motore non riesegue
|
||||
un source già noto.
|
||||
- `syncControls()` è protetto: `applyState()` gira ogni 2 s e la rappresentazione
|
||||
piena può non esistere (popup chiuso) → senza guardia il `ReferenceError` su un
|
||||
id interrompeva la sincronizzazione dei controlli.
|
||||
- `implicitWidth/implicitHeight` del `Flickable` sono costanti: se dipendono dal
|
||||
layout il containment riduce il widget alla dimensione minima (68×68).
|
||||
- Nessun `Switch` per il boost: `toggled` scatta anche quando il valore viene
|
||||
riallineato dallo stato. Le pillole sono prive di stato interno.
|
||||
|
||||
## File
|
||||
|
||||
```
|
||||
metadata.json
|
||||
contents/ui/main.qml interfaccia (compact + full, Flickable)
|
||||
contents/ui/ChoicePill.qml pillola selezionabile
|
||||
contents/scripts/profiles.sh tabella profili condivisa (unica fonte)
|
||||
contents/scripts/cpufreq-state.sh lettura stato non privilegiata
|
||||
contents/scripts/enne2-cpufreq-task launcher task (utente)
|
||||
bin/cpufreq-helper helper privilegiato (root)
|
||||
polkit/*.policy, polkit/*.rules azione e regola polkit
|
||||
install.sh, uninstall.sh
|
||||
```
|
||||
|
||||
Licenza MIT.
|
||||
Executable
+410
@@ -0,0 +1,410 @@
|
||||
#!/bin/sh
|
||||
# SPDX-License-Identifier: MIT
|
||||
# enne2-cpufreq — helper privilegiato a interfaccia ristretta.
|
||||
#
|
||||
# Installato come /usr/local/libexec/enne2-cpufreq/cpufreq-helper (root:root 0755),
|
||||
# invocato dal plasmoide org.enne2.cpufreq tramite pkexec e una azione polkit
|
||||
# dedicata (org.enne2.cpufreq.set). Non accetta comandi arbitrari: solo i
|
||||
# sottocomandi elencati in usage() e con argomenti validati contro sysfs.
|
||||
#
|
||||
# Uscita: righe key=value; ok=1 in caso di successo, ok=0 + error=<msg> + exit 2
|
||||
# in caso di rifiuto. Ogni scrittura viene riletta per verifica.
|
||||
set -u
|
||||
|
||||
PATH=/usr/sbin:/usr/bin:/sbin:/bin
|
||||
export PATH
|
||||
umask 022
|
||||
|
||||
CMD=${1:-status}
|
||||
[ "$#" -gt 0 ] && shift
|
||||
|
||||
SELF_DIR=$(dirname "$0")
|
||||
if [ -r "$SELF_DIR/profiles.sh" ]; then
|
||||
# shellcheck source=../../contents/scripts/profiles.sh
|
||||
. "$SELF_DIR/profiles.sh"
|
||||
else
|
||||
printf 'ok=0\nerror=profiles.sh mancante accanto all helper\n'
|
||||
exit 2
|
||||
fi
|
||||
|
||||
CPUFREQ=/sys/devices/system/cpu/cpufreq
|
||||
INTEL_PSTATE=/sys/devices/system/cpu/intel_pstate
|
||||
STATE_DIR=/var/lib/enne2-cpufreq
|
||||
BASELINE=$STATE_DIR/baseline
|
||||
REFCOUNT=$STATE_DIR/refcount
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
uso: cpufreq-helper <comando> [argomenti]
|
||||
status stato corrente (leggibile anche senza root)
|
||||
set-governor <governor> powersave | performance | ...
|
||||
set-epp <preferenza> default | performance | balance_performance | balance_power | power
|
||||
set-limits <min_khz> <max_khz>
|
||||
set-boost <0|1>
|
||||
apply <profilo> quiet | powersave | balanced | performance
|
||||
reset ripristina i default hardware (boost on, nessun tetto)
|
||||
save-state salva lo stato corrente come baseline
|
||||
restore-state ripristina la baseline salvata
|
||||
task-begin <profilo> salva la baseline (se serve) e applica il profilo
|
||||
task-end [--all] decrementa il contatore e ripristina la baseline a zero
|
||||
EOF
|
||||
}
|
||||
|
||||
fail() {
|
||||
printf 'ok=0\nerror=%s\n' "$*"
|
||||
exit 2
|
||||
}
|
||||
|
||||
readf() { # readf <file> -> valore o exit 1
|
||||
[ -r "$1" ] || return 1
|
||||
_val=
|
||||
IFS= read -r _val < "$1" 2>/dev/null || [ -n "$_val" ] || return 1
|
||||
printf '%s\n' "$_val"
|
||||
}
|
||||
|
||||
policies() {
|
||||
for _p in "$CPUFREQ"/policy[0-9]*; do
|
||||
[ -d "$_p" ] && printf '%s\n' "$_p"
|
||||
done
|
||||
}
|
||||
|
||||
write_one() { # write_one <file> <valore>
|
||||
[ -w "$1" ] || return 1
|
||||
printf '%s\n' "$2" > "$1" 2>/dev/null
|
||||
}
|
||||
|
||||
# Scrive un attributo su TUTTE le policy. Imposta WROTE e FAILED.
|
||||
write_all() {
|
||||
_attr=$1
|
||||
_val=$2
|
||||
WROTE=0
|
||||
FAILED=0
|
||||
for _p in $(policies); do
|
||||
if write_one "$_p/$_attr" "$_val"; then
|
||||
WROTE=$((WROTE + 1))
|
||||
else
|
||||
FAILED=$((FAILED + 1))
|
||||
fi
|
||||
done
|
||||
[ "$FAILED" -eq 0 ] && [ "$WROTE" -gt 0 ]
|
||||
}
|
||||
|
||||
# Valori distinti letti per un attributo, spazio-separati.
|
||||
attr_values() {
|
||||
for _p in $(policies); do
|
||||
readf "$_p/$1"
|
||||
done | sort -u | tr '\n' ' ' | sed 's/ *$//'
|
||||
}
|
||||
|
||||
p0=$(policies | head -n 1)
|
||||
[ -n "$p0" ] || fail "nessuna policy cpufreq in $CPUFREQ"
|
||||
|
||||
driver=$(readf "$p0/scaling_driver" || echo unknown)
|
||||
hw_min=$(readf "$p0/cpuinfo_min_freq" || echo 0)
|
||||
hw_max=$(cf_hw_max_khz "$p0") # indipendente dallo stato del boost
|
||||
floor=$(readf "$p0/amd_pstate_lowest_nonlinear_freq" || echo "")
|
||||
[ -n "$floor" ] && [ "$floor" -gt "$hw_min" ] || floor=$hw_min
|
||||
|
||||
boost_global=$CPUFREQ/boost
|
||||
[ -w "$boost_global" ] || [ -r "$boost_global" ] || boost_global=""
|
||||
|
||||
boost_read() {
|
||||
for _p in $(policies); do
|
||||
readf "$_p/boost" && return 0
|
||||
done
|
||||
[ -n "$boost_global" ] && readf "$boost_global" && return 0
|
||||
readf "$INTEL_PSTATE/no_turbo" >/dev/null 2>&1 || return 1
|
||||
_nt=$(readf "$INTEL_PSTATE/no_turbo") || return 1
|
||||
[ "$_nt" = "1" ] && echo 0 || echo 1
|
||||
}
|
||||
|
||||
print_state() {
|
||||
printf 'driver=%s\n' "$driver"
|
||||
printf 'mode=%s\n' "$(readf /sys/devices/system/cpu/amd_pstate/status || echo n/a)"
|
||||
printf 'governor=%s\n' "$(attr_values scaling_governor)"
|
||||
printf 'epp=%s\n' "$(attr_values energy_performance_preference)"
|
||||
printf 'min_khz=%s\n' "$(readf "$p0/scaling_min_freq")"
|
||||
printf 'max_khz=%s\n' "$(readf "$p0/scaling_max_freq")"
|
||||
printf 'hw_min_khz=%s\n' "$hw_min"
|
||||
printf 'hw_max_khz=%s\n' "$hw_max"
|
||||
printf 'floor_khz=%s\n' "$floor"
|
||||
printf 'boost=%s\n' "$(boost_read || echo n/a)"
|
||||
printf 'policies=%s\n' "$(policies | wc -l | tr -d ' ')"
|
||||
printf 'baseline=%s\n' "$([ -r "$BASELINE" ] && echo present || echo absent)"
|
||||
printf 'refcount=%s\n' "$([ -r "$REFCOUNT" ] && readf "$REFCOUNT" || echo 0)"
|
||||
}
|
||||
|
||||
set_governor() {
|
||||
_g=${1:-}
|
||||
[ -n "$_g" ] || fail "governor mancante"
|
||||
case "$_g" in *[!a-z_-]*) fail "governor non valido: $_g" ;; esac
|
||||
_avail=$(readf "$p0/scaling_available_governors") || fail "governor non leggibili"
|
||||
case " $_avail " in
|
||||
*" $_g "*) ;;
|
||||
*) fail "governor '$_g' non disponibile ($_avail)" ;;
|
||||
esac
|
||||
write_all scaling_governor "$_g" || fail "scrittura scaling_governor fallita su $FAILED policy"
|
||||
_now=$(attr_values scaling_governor)
|
||||
[ "$_now" = "$_g" ] || fail "verifica governor fallita: letto '$_now'"
|
||||
printf 'governor=%s\n' "$_g"
|
||||
}
|
||||
|
||||
set_epp() {
|
||||
_e=${1:-}
|
||||
[ -n "$_e" ] || fail "preferenza EPP mancante"
|
||||
case "$_e" in *[!a-z_]*) fail "preferenza EPP non valida: $_e" ;; esac
|
||||
_avail=$(readf "$p0/energy_performance_available_preferences") \
|
||||
|| fail "questo driver non espone energy_performance_preference"
|
||||
case " $_avail " in
|
||||
*" $_e "*) ;;
|
||||
*) fail "EPP '$_e' non disponibile ($_avail)" ;;
|
||||
esac
|
||||
if write_all energy_performance_preference "$_e"; then
|
||||
printf 'epp=%s\n' "$_e"
|
||||
return 0
|
||||
fi
|
||||
# Con governor "performance" il driver EPP può ignorare la preferenza: non è
|
||||
# un errore fatale, viene segnalato come warning.
|
||||
if [ "$(attr_values scaling_governor)" = "performance" ]; then
|
||||
printf 'warning=EPP non applicato con governor performance\n'
|
||||
return 0
|
||||
fi
|
||||
fail "scrittura EPP fallita su $FAILED policy"
|
||||
}
|
||||
|
||||
set_boost() {
|
||||
_b=${1:-}
|
||||
case "$_b" in 0|1) ;; *) fail "boost deve essere 0 o 1" ;; esac
|
||||
_count=0
|
||||
for _p in $(policies); do
|
||||
if [ -w "$_p/boost" ] && write_one "$_p/boost" "$_b"; then
|
||||
_count=$((_count + 1))
|
||||
fi
|
||||
done
|
||||
if [ -n "$boost_global" ] && write_one "$boost_global" "$_b"; then
|
||||
_count=$((_count + 1))
|
||||
fi
|
||||
if [ -w "$INTEL_PSTATE/no_turbo" ] && write_one "$INTEL_PSTATE/no_turbo" $((1 - _b)); then
|
||||
_count=$((_count + 1))
|
||||
fi
|
||||
[ "$_count" -gt 0 ] || fail "nessun controllo boost scrivibile su questo sistema"
|
||||
_now=$(boost_read || echo "")
|
||||
[ "$_now" = "$_b" ] || printf 'warning=lettura boost dopo la scrittura: %s\n' "${_now:-n/d}"
|
||||
printf 'boost=%s\n' "$_b"
|
||||
}
|
||||
|
||||
set_limits() {
|
||||
_min=${1:-}
|
||||
_max=${2:-}
|
||||
case "$_min$_max" in *[!0-9]*) fail "limiti non numerici (kHz interi)" ;; esac
|
||||
[ -n "$_min" ] && [ -n "$_max" ] || fail "servono min_khz e max_khz"
|
||||
[ "$_min" -ge "$hw_min" ] || fail "minimo $_min sotto il limite hardware $hw_min"
|
||||
[ "$_max" -le "$hw_max" ] || fail "massimo $_max sopra il limite hardware $hw_max"
|
||||
[ "$_min" -le "$_max" ] || fail "minimo $_min maggiore del massimo $_max"
|
||||
_cur_min=$(readf "$p0/scaling_min_freq" || echo "$hw_min")
|
||||
_cur_max=$(readf "$p0/scaling_max_freq" || echo "$hw_max")
|
||||
# Ordine di scrittura sicuro: mai uno stato intermedio con min > max.
|
||||
if [ "$_min" -gt "$_cur_max" ]; then
|
||||
write_all scaling_max_freq "$_max" || fail "pre-allargamento del tetto fallito"
|
||||
fi
|
||||
if [ "$_max" -lt "$_cur_min" ]; then
|
||||
write_all scaling_min_freq "$_min" || fail "pre-abbassamento del minimo fallito"
|
||||
fi
|
||||
write_all scaling_min_freq "$_min" || fail "scrittura scaling_min_freq fallita su $FAILED policy"
|
||||
write_all scaling_max_freq "$_max" || fail "scrittura scaling_max_freq fallita su $FAILED policy"
|
||||
printf 'min_khz=%s\n' "$(readf "$p0/scaling_min_freq")"
|
||||
printf 'max_khz=%s\n' "$(readf "$p0/scaling_max_freq")"
|
||||
}
|
||||
|
||||
apply_profile() {
|
||||
_pr=${1:-}
|
||||
cf_profile_known "$_pr" || fail "profilo '$_pr' sconosciuto ($(cf_profiles | tr '\n' ' '))"
|
||||
_gov=$(cf_profile_gov "$_pr")
|
||||
_epp=$(cf_profile_epp "$_pr")
|
||||
_bst=$(cf_profile_boost "$_pr")
|
||||
_maxf=$(cf_profile_max_khz "$_pr" "$floor" "$hw_max")
|
||||
[ "$_maxf" -gt "$hw_max" ] && _maxf=$hw_max
|
||||
[ "$_maxf" -lt "$floor" ] && _maxf=$floor
|
||||
printf 'profile=%s\n' "$_pr"
|
||||
# Ordine: 1) boost, 2) governor, 3) limiti, 4) EPP.
|
||||
# Il boost viene per primo perché con boost disattivato il kernel riduce il
|
||||
# tetto nominale e una scrittura di scaling_max_freq oltre quel valore
|
||||
# verrebbe rifiutata o limitata.
|
||||
set_boost "$_bst" || return 1
|
||||
if readf "$p0/scaling_available_governors" | grep -qw -- "$_gov"; then
|
||||
set_governor "$_gov" || return 1
|
||||
else
|
||||
printf 'warning=governor %s non disponibile, mantenuto %s\n' \
|
||||
"$_gov" "$(attr_values scaling_governor)"
|
||||
fi
|
||||
set_limits "$floor" "$_maxf" || return 1
|
||||
if readf "$p0/energy_performance_available_preferences" >/dev/null 2>&1; then
|
||||
set_epp "$_epp" || return 1
|
||||
fi
|
||||
printf 'profile_label=%s\n' "$(cf_profile_label "$_pr")"
|
||||
}
|
||||
|
||||
ensure_state_dir() {
|
||||
if [ ! -d "$STATE_DIR" ]; then
|
||||
mkdir -p "$STATE_DIR" || fail "impossibile creare $STATE_DIR"
|
||||
chmod 755 "$STATE_DIR"
|
||||
fi
|
||||
}
|
||||
|
||||
save_state() {
|
||||
ensure_state_dir
|
||||
{
|
||||
printf 'governor=%s\n' "$(readf "$p0/scaling_governor")"
|
||||
printf 'epp=%s\n' "$(readf "$p0/energy_performance_preference" || echo "")"
|
||||
printf 'min_khz=%s\n' "$(readf "$p0/scaling_min_freq")"
|
||||
printf 'max_khz=%s\n' "$(readf "$p0/scaling_max_freq")"
|
||||
printf 'boost=%s\n' "$(boost_read || echo "")"
|
||||
printf 'saved_at=%s\n' "$(date +%s)"
|
||||
} > "$BASELINE" || fail "scrittura baseline fallita"
|
||||
chmod 644 "$BASELINE"
|
||||
printf 'baseline=saved\n'
|
||||
cat "$BASELINE"
|
||||
}
|
||||
|
||||
restore_state() {
|
||||
[ -r "$BASELINE" ] || fail "nessuna baseline salvata"
|
||||
_gov=$(sed -n 's/^governor=//p' "$BASELINE")
|
||||
_epp=$(sed -n 's/^epp=//p' "$BASELINE")
|
||||
_min=$(sed -n 's/^min_khz=//p' "$BASELINE")
|
||||
_max=$(sed -n 's/^max_khz=//p' "$BASELINE")
|
||||
_bst=$(sed -n 's/^boost=//p' "$BASELINE")
|
||||
if [ -n "$_gov" ]; then
|
||||
if readf "$p0/scaling_available_governors" | grep -qw -- "$_gov"; then
|
||||
write_all scaling_governor "$_gov" || printf 'warning=governor non ripristinato\n'
|
||||
fi
|
||||
fi
|
||||
if [ -n "$_min" ] && [ -n "$_max" ]; then
|
||||
case "$_min$_max" in
|
||||
*[!0-9]*) ;;
|
||||
*) set_limits "$_min" "$_max" || printf 'warning=limiti non ripristinati\n' ;;
|
||||
esac
|
||||
fi
|
||||
if [ -n "$_epp" ] && readf "$p0/energy_performance_available_preferences" >/dev/null 2>&1; then
|
||||
write_all energy_performance_preference "$_epp" || printf 'warning=EPP non ripristinato\n'
|
||||
fi
|
||||
if [ -n "$_bst" ]; then
|
||||
case "$_bst" in
|
||||
0|1) set_boost "$_bst" >/dev/null || printf 'warning=boost non ripristinato\n' ;;
|
||||
esac
|
||||
fi
|
||||
printf 'restored=1\n'
|
||||
print_state
|
||||
}
|
||||
|
||||
reset_cmd() {
|
||||
_gov=powersave
|
||||
if ! readf "$p0/scaling_available_governors" | grep -qw -- "$_gov"; then
|
||||
_gov=$(readf "$p0/scaling_available_governors" | awk '{print $1}')
|
||||
fi
|
||||
printf 'profile=reset\n'
|
||||
[ -n "$_gov" ] && set_governor "$_gov"
|
||||
set_limits "$floor" "$hw_max" || return 1
|
||||
if readf "$p0/energy_performance_available_preferences" >/dev/null 2>&1; then
|
||||
_epp=balance_performance
|
||||
if ! readf "$p0/energy_performance_available_preferences" | grep -qw -- "$_epp"; then
|
||||
_epp=default
|
||||
fi
|
||||
set_epp "$_epp" || return 1
|
||||
fi
|
||||
set_boost 1 || return 1
|
||||
}
|
||||
|
||||
task_begin() {
|
||||
_pr=${1:-}
|
||||
cf_profile_known "$_pr" || fail "profilo '$_pr' sconosciuto"
|
||||
ensure_state_dir
|
||||
_rc=0
|
||||
[ -r "$REFCOUNT" ] && _rc=$(readf "$REFCOUNT")
|
||||
case "${_rc:-0}" in *[!0-9]*|"") _rc=0 ;; esac
|
||||
if [ "$_rc" -eq 0 ] || [ ! -r "$BASELINE" ]; then
|
||||
save_state >/dev/null || fail "salvataggio dello stato corrente fallito"
|
||||
fi
|
||||
apply_profile "$_pr" || fail "applicazione del profilo '$_pr' fallita"
|
||||
_rc=$((_rc + 1))
|
||||
printf '%s\n' "$_rc" > "$REFCOUNT"
|
||||
chmod 644 "$REFCOUNT"
|
||||
printf 'refcount=%s\n' "$_rc"
|
||||
}
|
||||
|
||||
task_end() {
|
||||
ensure_state_dir
|
||||
_rc=0
|
||||
[ -r "$REFCOUNT" ] && _rc=$(readf "$REFCOUNT")
|
||||
case "${_rc:-0}" in *[!0-9]*|"") _rc=0 ;; esac
|
||||
if [ "${1:-}" = "--all" ]; then
|
||||
_rc=0
|
||||
else
|
||||
_rc=$((_rc - 1))
|
||||
[ "$_rc" -lt 0 ] && _rc=0
|
||||
fi
|
||||
printf '%s\n' "$_rc" > "$REFCOUNT"
|
||||
chmod 644 "$REFCOUNT"
|
||||
if [ "$_rc" -eq 0 ] && [ -r "$BASELINE" ]; then
|
||||
restore_state >/dev/null || printf 'warning=ripristino della baseline fallito\n'
|
||||
rm -f "$BASELINE"
|
||||
printf 'restored=1\n'
|
||||
fi
|
||||
printf 'refcount=%s\n' "$_rc"
|
||||
}
|
||||
|
||||
case "$CMD" in
|
||||
status)
|
||||
print_state
|
||||
;;
|
||||
usage|-h|--help|help)
|
||||
usage
|
||||
;;
|
||||
set-governor)
|
||||
[ "$(id -u)" -eq 0 ] || fail "operazione privilegiata: usare pkexec"
|
||||
set_governor "${1:-}"
|
||||
;;
|
||||
set-epp)
|
||||
[ "$(id -u)" -eq 0 ] || fail "operazione privilegiata: usare pkexec"
|
||||
set_epp "${1:-}"
|
||||
;;
|
||||
set-limits)
|
||||
[ "$(id -u)" -eq 0 ] || fail "operazione privilegiata: usare pkexec"
|
||||
set_limits "${1:-}" "${2:-}"
|
||||
;;
|
||||
set-boost)
|
||||
[ "$(id -u)" -eq 0 ] || fail "operazione privilegiata: usare pkexec"
|
||||
set_boost "${1:-}"
|
||||
;;
|
||||
apply)
|
||||
[ "$(id -u)" -eq 0 ] || fail "operazione privilegiata: usare pkexec"
|
||||
apply_profile "${1:-}"
|
||||
;;
|
||||
reset)
|
||||
[ "$(id -u)" -eq 0 ] || fail "operazione privilegiata: usare pkexec"
|
||||
reset_cmd
|
||||
;;
|
||||
save-state)
|
||||
[ "$(id -u)" -eq 0 ] || fail "operazione privilegiata: usare pkexec"
|
||||
save_state
|
||||
;;
|
||||
restore-state)
|
||||
[ "$(id -u)" -eq 0 ] || fail "operazione privilegiata: usare pkexec"
|
||||
restore_state
|
||||
;;
|
||||
task-begin)
|
||||
[ "$(id -u)" -eq 0 ] || fail "operazione privilegiata: usare pkexec"
|
||||
task_begin "${1:-}"
|
||||
;;
|
||||
task-end)
|
||||
[ "$(id -u)" -eq 0 ] || fail "operazione privilegiata: usare pkexec"
|
||||
task_end "${1:-}"
|
||||
;;
|
||||
*)
|
||||
printf 'ok=0\nerror=comando sconosciuto: %s\n' "$CMD"
|
||||
usage
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
|
||||
printf 'ok=1\n'
|
||||
exit 0
|
||||
Executable
+175
@@ -0,0 +1,175 @@
|
||||
#!/bin/sh
|
||||
# SPDX-License-Identifier: MIT
|
||||
# enne2-cpufreq — lettore di stato NON privilegiato per il plasmoide.
|
||||
# Legge solo attributi world-readable di sysfs, /proc e hwmon; non scrive nulla.
|
||||
# Uscita: righe key=value (formato usato dagli altri widget org.enne2.*).
|
||||
set -u
|
||||
|
||||
SELF_DIR=$(dirname "$0")
|
||||
if [ -r "$SELF_DIR/profiles.sh" ]; then
|
||||
# shellcheck source=profiles.sh
|
||||
. "$SELF_DIR/profiles.sh"
|
||||
else
|
||||
printf 'helper=missing\nerror=profiles.sh mancante\n'
|
||||
exit 2
|
||||
fi
|
||||
|
||||
CPUFREQ=/sys/devices/system/cpu/cpufreq
|
||||
HELPER=/usr/local/libexec/enne2-cpufreq/cpufreq-helper
|
||||
POLICY_POLKIT=/usr/share/polkit-1/actions/org.enne2.cpufreq.set.policy
|
||||
RULE_POLKIT=/etc/polkit-1/rules.d/50-org.enne2.cpufreq.rules
|
||||
CONF_DIR=${XDG_CONFIG_HOME:-$HOME/.config}/enne2-cpufreq
|
||||
TASKS=$CONF_DIR/tasks.json
|
||||
RUN=${XDG_RUNTIME_DIR:-/run/user/$(id -u)}/enne2-cpufreq
|
||||
|
||||
readf() {
|
||||
[ -r "$1" ] || return 1
|
||||
_val=
|
||||
IFS= read -r _val < "$1" 2>/dev/null || [ -n "$_val" ] || return 1
|
||||
printf '%s\n' "$_val"
|
||||
}
|
||||
|
||||
policies() {
|
||||
for _p in "$CPUFREQ"/policy[0-9]*; do
|
||||
[ -d "$_p" ] && printf '%s\n' "$_p"
|
||||
done
|
||||
}
|
||||
|
||||
attr_values() {
|
||||
for _p in $(policies); do
|
||||
readf "$_p/$1"
|
||||
done | sort -u | tr '\n' ' ' | sed 's/ *$//'
|
||||
}
|
||||
|
||||
p0=$(policies | head -n 1)
|
||||
if [ -z "$p0" ]; then
|
||||
printf 'helper=missing\nerror=nessuna policy cpufreq (driver non cpufreq?)\n'
|
||||
exit 2
|
||||
fi
|
||||
|
||||
driver=$(readf "$p0/scaling_driver" || echo unknown)
|
||||
hw_min=$(readf "$p0/cpuinfo_min_freq" || echo 0)
|
||||
hw_max=$(cf_hw_max_khz "$p0") # indipendente dallo stato del boost
|
||||
floor=$(readf "$p0/amd_pstate_lowest_nonlinear_freq" || echo "")
|
||||
[ -n "$floor" ] && [ "$floor" -gt "$hw_min" ] || floor=$hw_min
|
||||
min_khz=$(readf "$p0/scaling_min_freq" || echo 0)
|
||||
max_khz=$(readf "$p0/scaling_max_freq" || echo 0)
|
||||
governor=$(attr_values scaling_governor)
|
||||
governors=$(readf "$p0/scaling_available_governors" || echo "")
|
||||
epp=$(attr_values energy_performance_preference)
|
||||
epps=$(readf "$p0/energy_performance_available_preferences" || echo "")
|
||||
epp_supported=0
|
||||
[ -n "$epps" ] && epp_supported=1
|
||||
|
||||
# boost: per-policy (kernel recenti) → globale → intel_pstate/no_turbo invertito
|
||||
boost=$(readf "$p0/boost" || readf "$CPUFREQ/boost" || echo "")
|
||||
boost_supported=0
|
||||
[ -n "$boost" ] && boost_supported=1
|
||||
if [ "$boost_supported" -eq 0 ] && [ -r /sys/devices/system/cpu/intel_pstate/no_turbo ]; then
|
||||
_nt=$(readf /sys/devices/system/cpu/intel_pstate/no_turbo)
|
||||
[ "$_nt" = "1" ] && boost=0 || boost=1
|
||||
boost_supported=1
|
||||
fi
|
||||
|
||||
# frequenza istantanea: massima fra le policy (core più attivo) e media
|
||||
sum=0
|
||||
cnt=0
|
||||
curmax=0
|
||||
for _p in $(policies); do
|
||||
_f=$(readf "$_p/scaling_cur_freq") || continue
|
||||
case "$_f" in ''|*[!0-9]*) continue ;; esac
|
||||
sum=$((sum + _f))
|
||||
cnt=$((cnt + 1))
|
||||
[ "$_f" -gt "$curmax" ] && curmax=$_f
|
||||
done
|
||||
[ "$cnt" -gt 0 ] || cnt=1
|
||||
cur_avg=$((sum / cnt))
|
||||
|
||||
temp=$(readf /sys/class/hwmon/hwmon0/temp1_input 2>/dev/null || echo "")
|
||||
for _h in /sys/class/hwmon/hwmon*; do
|
||||
[ -r "$_h/name" ] || continue
|
||||
case "$(readf "$_h/name")" in
|
||||
k10temp|coretemp|zenpower)
|
||||
temp=$(readf "$_h/temp1_input" || echo "")
|
||||
break
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
model=$(sed -n 's/^model name[[:space:]]*: //p' /proc/cpuinfo | head -n 1)
|
||||
load1=$(awk '{print $1}' /proc/loadavg)
|
||||
|
||||
helper_state=missing
|
||||
[ -x "$HELPER" ] && helper_state=ok
|
||||
polkit_state=missing
|
||||
if [ -r "$POLICY_POLKIT" ] && [ -r "$RULE_POLKIT" ]; then
|
||||
polkit_state=ok
|
||||
elif [ -r "$POLICY_POLKIT" ]; then
|
||||
polkit_state=ask
|
||||
fi
|
||||
|
||||
conflicts=""
|
||||
for _svc in tuned power-profiles-daemon tlp; do
|
||||
if systemctl is-active --quiet "$_svc" 2>/dev/null; then
|
||||
conflicts="$conflicts $_svc"
|
||||
fi
|
||||
done
|
||||
conflicts=${conflicts# }
|
||||
|
||||
profile=$(cf_detect_profile "${governor:-}" "${epp:-}" "${boost:-0}" "$max_khz" "$floor" "$hw_max")
|
||||
printf 'driver=%s\n' "$driver"
|
||||
printf 'mode=%s\n' "$(readf /sys/devices/system/cpu/amd_pstate/status || echo n/a)"
|
||||
printf 'model=%s\n' "$model"
|
||||
printf 'policies=%s\n' "$cnt"
|
||||
printf 'governor=%s\n' "$governor"
|
||||
printf 'governors=%s\n' "$governors"
|
||||
printf 'epp=%s\n' "$epp"
|
||||
printf 'epps=%s\n' "$epps"
|
||||
printf 'epp_supported=%s\n' "$epp_supported"
|
||||
printf 'boost=%s\n' "$boost"
|
||||
printf 'boost_supported=%s\n' "$boost_supported"
|
||||
printf 'hw_min_khz=%s\n' "$hw_min"
|
||||
printf 'hw_max_khz=%s\n' "$hw_max"
|
||||
printf 'floor_khz=%s\n' "$floor"
|
||||
printf 'min_khz=%s\n' "$min_khz"
|
||||
printf 'max_khz=%s\n' "$max_khz"
|
||||
printf 'cur_khz=%s\n' "$curmax"
|
||||
printf 'cur_avg_khz=%s\n' "$cur_avg"
|
||||
printf 'temp_mdegc=%s\n' "$temp"
|
||||
printf 'load1=%s\n' "$load1"
|
||||
printf 'profile=%s\n' "$profile"
|
||||
printf 'profile_label=%s\n' "$(cf_profile_label "$profile")"
|
||||
printf 'helper=%s\n' "$helper_state"
|
||||
printf 'polkit=%s\n' "$polkit_state"
|
||||
printf 'conflicts=%s\n' "$conflicts"
|
||||
[ -r /var/lib/enne2-cpufreq/baseline ] && printf 'baseline=present\n' || printf 'baseline=absent\n'
|
||||
|
||||
# ---- task configurati (un file JSON gestito da enne2-cpufreq-task) ----------
|
||||
# Formato di ogni riga: task=<indice> <TAB> <running 0|1> <TAB> <oggetto JSON>
|
||||
# Il JSON (jq -c, una riga) evita ogni ambiguità su spazi, tabulazioni e pipe
|
||||
# dentro i comandi definiti dall'utente.
|
||||
if command -v jq >/dev/null 2>&1 && [ -r "$TASKS" ]; then
|
||||
_count=$(jq -r '.tasks | length' "$TASKS" 2>/dev/null || echo 0)
|
||||
printf 'task_count=%s\n' "${_count:-0}"
|
||||
_i=0
|
||||
jq -c '.tasks[] | {name: .name, profile: .profile, command: .command, slug: .slug}' \
|
||||
"$TASKS" 2>/dev/null |
|
||||
while IFS= read -r _line; do
|
||||
[ -n "$_line" ] || continue
|
||||
_slug=$(printf '%s' "$_line" | jq -r '.slug // ""' 2>/dev/null)
|
||||
_running=0
|
||||
if [ -n "$_slug" ] && [ -r "$RUN/$_slug.pid" ]; then
|
||||
_pid=$(readf "$RUN/$_slug.pid")
|
||||
case "$_pid" in
|
||||
''|*[!0-9]*) _running=0 ;;
|
||||
*) kill -0 "$_pid" 2>/dev/null && _running=1 ;;
|
||||
esac
|
||||
fi
|
||||
printf 'task=%s\t%s\t%s\n' "$_i" "$_running" "$_line"
|
||||
_i=$((_i + 1))
|
||||
done
|
||||
else
|
||||
printf 'task_count=0\n'
|
||||
fi
|
||||
|
||||
printf 'ts=%s\n' "$(date +%s)"
|
||||
Executable
+274
@@ -0,0 +1,274 @@
|
||||
#!/bin/sh
|
||||
# SPDX-License-Identifier: MIT
|
||||
# enne2-cpufreq-task — launcher di task con profilo CPU dedicato.
|
||||
#
|
||||
# Gira SEMPRE come utente: il comando del task non viene mai eseguito come root.
|
||||
# Solo il cambio di profilo passa dall'helper privilegiato via pkexec.
|
||||
#
|
||||
# Flusso di "run":
|
||||
# 1. helper task-begin <profilo> → salva la baseline (una sola volta) e applica
|
||||
# 2. il comando parte staccato (setsid) e scrive il proprio pid nel file di stato
|
||||
# 3. un watcher staccato attende l'uscita del pid e chiama helper task-end, che
|
||||
# ripristina la baseline quando non ci sono altri task attivi
|
||||
#
|
||||
# Il plasmoide invoca "add-hex" con gli argomenti codificati in esadecimale
|
||||
# (UTF-8): così nomi, comandi con spazi, virgolette o newline attraversano
|
||||
# l'exec engine di Plasma senza dipendere dal parsing delle virgolette.
|
||||
set -u
|
||||
|
||||
PATH=/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
|
||||
export PATH
|
||||
|
||||
SELF_DIR=$(dirname "$0")
|
||||
HELPER=/usr/local/libexec/enne2-cpufreq/cpufreq-helper
|
||||
CONF_DIR=${XDG_CONFIG_HOME:-$HOME/.config}/enne2-cpufreq
|
||||
TASKS=$CONF_DIR/tasks.json
|
||||
RUN=${XDG_RUNTIME_DIR:-/run/user/$(id -u)}/enne2-cpufreq
|
||||
|
||||
mkdir -p "$CONF_DIR" "$RUN" 2>/dev/null
|
||||
|
||||
fail() {
|
||||
printf 'ok=0\nerror=%s\n' "$*"
|
||||
exit 2
|
||||
}
|
||||
|
||||
readf() {
|
||||
[ -r "$1" ] || return 1
|
||||
_val=
|
||||
IFS= read -r _val < "$1" 2>/dev/null || [ -n "$_val" ] || return 1
|
||||
printf '%s\n' "$_val"
|
||||
}
|
||||
|
||||
# Decodifica esadecimale UTF-8 senza dipendenze (xxd/od non sempre presenti).
|
||||
hex_decode() {
|
||||
[ -n "${1:-}" ] || return 0
|
||||
case "$1" in *[!0-9a-fA-F]*) fail "payload esadecimale non valido" ;; esac
|
||||
_esc=$(printf '%s' "$1" | sed 's/\(..\)/\\x\1/g')
|
||||
printf '%b' "$_esc"
|
||||
}
|
||||
|
||||
need_jq() {
|
||||
command -v jq >/dev/null 2>&1 || fail "jq non installato (serve per gestire tasks.json)"
|
||||
}
|
||||
|
||||
init_tasks() {
|
||||
[ -r "$TASKS" ] && return 0
|
||||
printf '{"version":1,"tasks":[]}\n' > "$TASKS" || fail "impossibile creare $TASKS"
|
||||
}
|
||||
|
||||
slugify() {
|
||||
printf '%s' "$1" | tr 'A-Z' 'a-z' | tr -c 'A-Za-z0-9._-' '-' \
|
||||
| sed -e 's/--*/-/g' -e 's/^-//' -e 's/-$//'
|
||||
}
|
||||
|
||||
# Accetta indifferentemente il nome visualizzato o lo slug.
|
||||
resolve_slug() {
|
||||
jq -r --arg q "$1" '.tasks[] | select(.name == $q or .slug == $q) | .slug' \
|
||||
"$TASKS" 2>/dev/null | head -n 1
|
||||
}
|
||||
|
||||
field_of_slug() { # field_of_slug <slug> <campo>
|
||||
jq -r --arg s "$1" --arg f "$2" \
|
||||
'.tasks[] | select(.slug == $s) | .[$f] // empty' "$TASKS" 2>/dev/null | head -n 1
|
||||
}
|
||||
|
||||
is_running() { # is_running <slug>
|
||||
_pid=$(readf "$RUN/$1.pid") || return 1
|
||||
case "$_pid" in ''|*[!0-9]*) return 1 ;; esac
|
||||
kill -0 "$_pid" 2>/dev/null
|
||||
}
|
||||
|
||||
helper_ready() {
|
||||
[ -x "$HELPER" ] || fail "helper non installato in $HELPER (eseguire install.sh)"
|
||||
command -v pkexec >/dev/null 2>&1 || fail "pkexec non disponibile (installare polkit)"
|
||||
}
|
||||
|
||||
do_add() { # do_add <nome> <profilo> <comando>
|
||||
init_tasks
|
||||
need_jq
|
||||
_name=${1:-}
|
||||
_prof=${2:-}
|
||||
_cmd=${3:-}
|
||||
[ -n "$_name" ] || fail "nome del task mancante"
|
||||
[ -n "$_cmd" ] || fail "comando del task mancante"
|
||||
case $_prof in
|
||||
quiet|powersave|balanced|performance) ;;
|
||||
*) fail "profilo non valido: $_prof (quiet|powersave|balanced|performance)" ;;
|
||||
esac
|
||||
_slug=$(slugify "$_name")
|
||||
[ -n "$_slug" ] || fail "nome non utilizzabile come slug: $_name"
|
||||
if [ -n "$(resolve_slug "$_name")" ]; then
|
||||
fail "esiste già un task chiamato '$_name'"
|
||||
fi
|
||||
_tmp=$TASKS.tmp.$$
|
||||
jq --arg n "$_name" --arg p "$_prof" --arg c "$_cmd" --arg s "$_slug" \
|
||||
'.tasks += [{name: $n, profile: $p, command: $c, slug: $s}]' "$TASKS" > "$_tmp" \
|
||||
&& mv "$_tmp" "$TASKS" || { rm -f "$_tmp"; fail "scrittura tasks.json fallita"; }
|
||||
printf 'ok=1\nname=%s\nprofile=%s\nslug=%s\n' "$_name" "$_prof" "$_slug"
|
||||
}
|
||||
|
||||
cmd_add() { # add <nome> <profilo> <comando...>
|
||||
_name=${1:-}
|
||||
_prof=${2:-}
|
||||
[ "$#" -ge 3 ] || fail "servono nome, profilo e comando"
|
||||
shift 2
|
||||
do_add "$_name" "$_prof" "$*"
|
||||
}
|
||||
|
||||
cmd_add_hex() { # add-hex <nome_hex> <profilo_hex> <comando_hex>
|
||||
[ "$#" -ge 3 ] || fail "servono tre argomenti esadecimali"
|
||||
do_add "$(hex_decode "$1")" "$(hex_decode "$2")" "$(hex_decode "$3")"
|
||||
}
|
||||
|
||||
cmd_list() {
|
||||
init_tasks
|
||||
need_jq
|
||||
printf 'ok=1\n'
|
||||
[ -x "$HELPER" ] && printf 'helper=ok\n' || printf 'helper=missing\n'
|
||||
printf 'count=%s\n' "$(jq -r '.tasks | length' "$TASKS" 2>/dev/null || echo 0)"
|
||||
_i=0
|
||||
jq -c '.tasks[] | {name: .name, profile: .profile, command: .command, slug: .slug}' \
|
||||
"$TASKS" 2>/dev/null |
|
||||
while IFS= read -r _line; do
|
||||
[ -n "$_line" ] || continue
|
||||
_slug=$(printf '%s' "$_line" | jq -r '.slug')
|
||||
_run=0
|
||||
is_running "$_slug" && _run=1
|
||||
printf 'task=%s\t%s\t%s\n' "$_i" "$_run" "$_line"
|
||||
_i=$((_i + 1))
|
||||
done
|
||||
}
|
||||
|
||||
cmd_remove() { # remove <nome-o-slug>
|
||||
init_tasks
|
||||
need_jq
|
||||
_slug=$(resolve_slug "${1:-}")
|
||||
[ -n "$_slug" ] || fail "task '${1:-}' inesistente"
|
||||
is_running "$_slug" && fail "il task è in esecuzione: fermarlo prima di rimuoverlo"
|
||||
_tmp=$TASKS.tmp.$$
|
||||
jq --arg s "$_slug" '.tasks |= map(select(.slug != $s))' "$TASKS" > "$_tmp" \
|
||||
&& mv "$_tmp" "$TASKS" || { rm -f "$_tmp"; fail "scrittura tasks.json fallita"; }
|
||||
rm -f "$RUN/$_slug.pid" "$RUN/$_slug.info"
|
||||
printf 'ok=1\nremoved=%s\n' "$_slug"
|
||||
}
|
||||
|
||||
cmd_run() { # run <nome-o-slug>
|
||||
init_tasks
|
||||
need_jq
|
||||
helper_ready
|
||||
_slug=$(resolve_slug "${1:-}")
|
||||
[ -n "$_slug" ] || fail "task '${1:-}' inesistente"
|
||||
_name=$(field_of_slug "$_slug" name)
|
||||
_prof=$(field_of_slug "$_slug" profile)
|
||||
_cmd=$(field_of_slug "$_slug" command)
|
||||
[ -n "$_cmd" ] || fail "comando vuoto per '$_name'"
|
||||
is_running "$_slug" && fail "il task '$_name' è già in esecuzione"
|
||||
|
||||
_out=$(pkexec "$HELPER" task-begin "$_prof" 2>&1) \
|
||||
|| fail "profilo '$_prof' non applicato: $(printf '%s' "$_out" | tr '\n' ' ')"
|
||||
case "$_out" in
|
||||
*"ok=0"*) fail "profilo '$_prof' non applicato: $(printf '%s' "$_out" | tr '\n' ' ')" ;;
|
||||
esac
|
||||
|
||||
rm -f "$RUN/$_slug.pid" "$RUN/$_slug.info"
|
||||
# Il figlio scrive il proprio pid e poi exec il comando: il pid resta valido,
|
||||
# così il watcher può attenderne davvero la fine.
|
||||
setsid sh -c 'printf "%s\n" "$$" > "$1"; shift; exec sh -c "$1"' \
|
||||
sh "$RUN/$_slug.pid" "$_cmd" </dev/null >/dev/null 2>&1 &
|
||||
_launcher=$!
|
||||
|
||||
_n=0
|
||||
while [ ! -s "$RUN/$_slug.pid" ] && [ "$_n" -lt 25 ]; do
|
||||
sleep 0.1 2>/dev/null || sleep 1
|
||||
_n=$((_n + 1))
|
||||
kill -0 "$_launcher" 2>/dev/null || break
|
||||
done
|
||||
if [ ! -s "$RUN/$_slug.pid" ]; then
|
||||
pkexec "$HELPER" task-end >/dev/null 2>&1
|
||||
fail "avvio del comando fallito: $_cmd"
|
||||
fi
|
||||
_pid=$(readf "$RUN/$_slug.pid")
|
||||
printf 'name=%s\nprofile=%s\nslug=%s\nstarted=%s\n' \
|
||||
"$_name" "$_prof" "$_slug" "$(date +%s)" > "$RUN/$_slug.info"
|
||||
|
||||
setsid "$SELF_DIR/$(basename "$0")" watch "$_slug" </dev/null >/dev/null 2>&1 &
|
||||
|
||||
printf 'ok=1\nname=%s\nslug=%s\nprofile=%s\npid=%s\n' "$_name" "$_slug" "$_prof" "$_pid"
|
||||
}
|
||||
|
||||
cmd_stop() { # stop <nome-o-slug>
|
||||
init_tasks
|
||||
need_jq
|
||||
_slug=$(resolve_slug "${1:-}")
|
||||
[ -n "$_slug" ] || fail "task '${1:-}' inesistente"
|
||||
is_running "$_slug" || fail "il task '$_slug' non è in esecuzione"
|
||||
_pid=$(readf "$RUN/$_slug.pid")
|
||||
kill -TERM -- "-$_pid" 2>/dev/null || kill -TERM "$_pid" 2>/dev/null
|
||||
printf 'ok=1\nstopped=%s\npid=%s\n' "$_slug" "$_pid"
|
||||
}
|
||||
|
||||
cmd_watch() { # watch <slug> (interno)
|
||||
_slug=${1:-}
|
||||
[ -n "$_slug" ] || exit 2
|
||||
_pid=$(readf "$RUN/$_slug.pid" 2>/dev/null) || exit 0
|
||||
while kill -0 "$_pid" 2>/dev/null; do
|
||||
sleep 2
|
||||
done
|
||||
_name=$(sed -n 's/^name=//p' "$RUN/$_slug.info" 2>/dev/null)
|
||||
_prof=$(sed -n 's/^profile=//p' "$RUN/$_slug.info" 2>/dev/null)
|
||||
rm -f "$RUN/$_slug.pid" "$RUN/$_slug.info"
|
||||
if [ -x "$HELPER" ]; then
|
||||
pkexec "$HELPER" task-end >/dev/null 2>&1
|
||||
fi
|
||||
if command -v notify-send >/dev/null 2>&1; then
|
||||
notify-send -a "CPUFreq" -i cpu \
|
||||
"Task concluso: ${_name:-$_slug}" \
|
||||
"Profilo CPU '${_prof:-?}' chiuso, stato precedente ripristinato"
|
||||
fi
|
||||
}
|
||||
|
||||
cmd_active() {
|
||||
printf 'ok=1\n'
|
||||
_found=0
|
||||
for _f in "$RUN"/*.pid; do
|
||||
[ -r "$_f" ] || continue
|
||||
_pid=$(readf "$_f")
|
||||
case "$_pid" in ''|*[!0-9]*) continue ;; esac
|
||||
kill -0 "$_pid" 2>/dev/null || continue
|
||||
_slug=$(basename "$_f" .pid)
|
||||
_name=$(sed -n 's/^name=//p' "$RUN/$_slug.info" 2>/dev/null)
|
||||
_prof=$(sed -n 's/^profile=//p' "$RUN/$_slug.info" 2>/dev/null)
|
||||
printf 'act=%s\t%s\t%s\t%s\n' "$_slug" "${_name:-$_slug}" "${_prof:-?}" "$_pid"
|
||||
_found=$((_found + 1))
|
||||
done
|
||||
printf 'active=%s\n' "$_found"
|
||||
}
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
uso: enne2-cpufreq-task <comando> [argomenti]
|
||||
list elenco dei task configurati
|
||||
add <nome> <profilo> <comando...>
|
||||
add-hex <nome> <profilo> <comando> argomenti in esadecimale UTF-8 (usato dal widget)
|
||||
remove <nome-o-slug>
|
||||
run <nome-o-slug> salva lo stato, applica il profilo, esegue, ripristina
|
||||
stop <nome-o-slug> termina il task (il watcher ripristina il profilo)
|
||||
active task attualmente in esecuzione
|
||||
EOF
|
||||
}
|
||||
|
||||
[ "$#" -gt 0 ] || { usage; exit 2; }
|
||||
_CMD=$1
|
||||
shift
|
||||
case "$_CMD" in
|
||||
list) cmd_list ;;
|
||||
add) cmd_add "$@" ;;
|
||||
add-hex) cmd_add_hex "$@" ;;
|
||||
remove|rm) cmd_remove "$@" ;;
|
||||
run|start) cmd_run "$@" ;;
|
||||
stop) cmd_stop "$@" ;;
|
||||
watch) cmd_watch "$@" ;;
|
||||
active) cmd_active ;;
|
||||
-h|--help|help) usage ;;
|
||||
*) printf 'ok=0\nerror=comando sconosciuto: %s\n' "$_CMD"; usage; exit 2 ;;
|
||||
esac
|
||||
Executable
+145
@@ -0,0 +1,145 @@
|
||||
# shellcheck shell=sh
|
||||
# Profili di frequenza condivisi fra helper privilegiato, lettore di stato e plasmoide.
|
||||
# Sorgente unica di verità: NON scrive nulla su sysfs, contiene solo la tabella dei
|
||||
# profili e l'aritmetica dei limiti (kHz). Copiato da install.sh accanto all'helper
|
||||
# in /usr/local/libexec/enne2-cpufreq/.
|
||||
#
|
||||
# Ogni profilo agisce su: governor, energy_performance_preference (EPP), boost e
|
||||
# tetto di frequenza espresso come percentuale dello span utile
|
||||
# (floor_khz .. cpuinfo_max_freq).
|
||||
|
||||
# Lettura minimale (il file è già stato validato dal chiamante dove serve).
|
||||
cf_read() {
|
||||
_cfout=
|
||||
[ -r "$1" ] || return 1
|
||||
IFS= read -r _cfout < "$1" 2>/dev/null || [ -n "$_cfout" ] || return 1
|
||||
printf '%s\n' "$_cfout"
|
||||
}
|
||||
|
||||
# Tetto hardware reale (kHz), indipendente dallo stato del boost:
|
||||
# con boost disattivato il kernel riporta in cpuinfo_max_freq la frequenza
|
||||
# nominale (es. 3.80 GHz invece di 4.97 GHz su amd-pstate). amd_pstate_max_freq
|
||||
# conserva il massimo effettivo; su altri driver cpuinfo_max_freq è già corretto.
|
||||
cf_hw_max_khz() {
|
||||
_cfp0=$1
|
||||
_cfmax=$(cf_read "$_cfp0/cpuinfo_max_freq" || echo 0)
|
||||
case "$_cfmax" in ''|*[!0-9]*) _cfmax=0 ;; esac
|
||||
_cfamd=$(cf_read "$_cfp0/amd_pstate_max_freq" || echo 0)
|
||||
case "$_cfamd" in ''|*[!0-9]*) _cfamd=0 ;; esac
|
||||
[ "$_cfamd" -gt "$_cfmax" ] && _cfmax=$_cfamd
|
||||
printf '%s\n' "$_cfmax"
|
||||
}
|
||||
|
||||
cf_profiles() {
|
||||
printf '%s\n' quiet powersave balanced performance
|
||||
}
|
||||
|
||||
cf_profile_known() {
|
||||
case "$1" in
|
||||
quiet|powersave|balanced|performance) return 0 ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
cf_profile_gov() {
|
||||
case "$1" in
|
||||
quiet|powersave|balanced) echo powersave ;;
|
||||
performance) echo performance ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
cf_profile_epp() {
|
||||
case "$1" in
|
||||
quiet) echo power ;;
|
||||
powersave) echo balance_power ;;
|
||||
balanced) echo balance_performance ;;
|
||||
performance) echo performance ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
cf_profile_boost() {
|
||||
case "$1" in
|
||||
quiet|powersave) echo 0 ;;
|
||||
balanced|performance) echo 1 ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
cf_profile_max_pct() {
|
||||
case "$1" in
|
||||
quiet) echo 35 ;;
|
||||
powersave) echo 60 ;;
|
||||
balanced|performance) echo 100 ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
cf_profile_label() {
|
||||
case "$1" in
|
||||
quiet) echo "Silenzioso" ;;
|
||||
powersave) echo "Risparmio" ;;
|
||||
balanced) echo "Bilanciato" ;;
|
||||
performance) echo "Performance" ;;
|
||||
custom) echo "Personalizzato" ;;
|
||||
*) echo "$1" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
cf_profile_desc() {
|
||||
case "$1" in
|
||||
quiet) echo "EPP power, boost off, tetto 35% dello span" ;;
|
||||
powersave) echo "EPP balance_power, boost off, tetto 60%" ;;
|
||||
balanced) echo "EPP balance_performance, boost on, nessun tetto" ;;
|
||||
performance) echo "governor performance, EPP performance, boost on" ;;
|
||||
*) echo "" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# cf_profile_max_khz <profilo> <floor_khz> <hw_max_khz>
|
||||
cf_profile_max_khz() {
|
||||
_cfp=$(cf_profile_max_pct "$1") || return 1
|
||||
_cfl=$2
|
||||
_cfh=$3
|
||||
[ "$_cfh" -gt "$_cfl" ] || { echo "$_cfh"; return 0; }
|
||||
# un profilo al 100% non deve avere tetto: nessun arrotondamento
|
||||
[ "$_cfp" -ge 100 ] && { echo "$_cfh"; return 0; }
|
||||
cf_round50 $(( _cfl + (_cfh - _cfl) * _cfp / 100 ))
|
||||
}
|
||||
|
||||
# Arrotonda a 50 MHz: valori leggibili e stabili fra una lettura e l'altra.
|
||||
cf_round50() {
|
||||
echo $(( ($1 + 25000) / 50000 * 50000 ))
|
||||
}
|
||||
|
||||
# cf_detect_profile <governor> <epp> <boost> <max_khz> <floor_khz> <hw_max_khz>
|
||||
# Stampa il nome del profilo se lo stato coincide (tolleranza sul tetto) oppure "custom".
|
||||
cf_detect_profile() {
|
||||
_dg=$1
|
||||
_de=$2
|
||||
_db=$3
|
||||
_dmax=$4
|
||||
_dfloor=$5
|
||||
_dhwmax=$6
|
||||
_span=$(( _dhwmax - _dfloor ))
|
||||
[ "$_span" -gt 0 ] || _span=1
|
||||
_tol=$(( _span / 40 )) # 2.5% dello span
|
||||
[ "$_tol" -lt 80000 ] && _tol=80000
|
||||
for _dp in $(cf_profiles); do
|
||||
[ "$_dg" = "$(cf_profile_gov "$_dp")" ] || continue
|
||||
[ "$_db" = "$(cf_profile_boost "$_dp")" ] || continue
|
||||
_dp_max=$(cf_profile_max_khz "$_dp" "$_dfloor" "$_dhwmax")
|
||||
_diff=$(( _dmax - _dp_max ))
|
||||
[ "$_diff" -lt 0 ] && _diff=$(( -_diff ))
|
||||
[ "$_diff" -le "$_tol" ] || continue
|
||||
# L'EPP può non essere scrivibile con governor "performance": in quel caso
|
||||
# accettiamo il profilo anche se l'EPP letto non coincide.
|
||||
[ "$_de" = "$(cf_profile_epp "$_dp")" ] && { echo "$_dp"; return 0; }
|
||||
if [ "$_dg" = "performance" ] && [ "$_dp" = "performance" ]; then
|
||||
echo "$_dp"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
echo custom
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 enne2
|
||||
* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Pillola selezionabile usata per governor ed EPP: alternativa compatta a una
|
||||
* ComboBox, senza stato interno (attivo/inattivo derivano dalle proprietà).
|
||||
*/
|
||||
|
||||
import QtQuick
|
||||
import org.kde.plasma.components 3.0 as PlasmaComponents
|
||||
import org.kde.kirigami as Kirigami
|
||||
|
||||
Rectangle {
|
||||
id: pill
|
||||
|
||||
property string text: ""
|
||||
property bool active: false
|
||||
property color accent: "#55c5c0"
|
||||
signal clicked()
|
||||
|
||||
implicitWidth: label.implicitWidth + 16
|
||||
implicitHeight: label.implicitHeight + 8
|
||||
radius: 4
|
||||
color: active
|
||||
? Qt.rgba(accent.r, accent.g, accent.b, 0.22)
|
||||
: (pillMouse.containsMouse ? Qt.rgba(1, 1, 1, 0.10) : Qt.rgba(1, 1, 1, 0.05))
|
||||
border.width: 1
|
||||
border.color: active
|
||||
? Qt.rgba(accent.r, accent.g, accent.b, 0.8)
|
||||
: Qt.rgba(1, 1, 1, 0.10)
|
||||
opacity: enabled ? 1.0 : 0.5
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation { duration: 120 }
|
||||
}
|
||||
|
||||
PlasmaComponents.Label {
|
||||
id: label
|
||||
anchors.centerIn: parent
|
||||
text: pill.text
|
||||
font.family: "monospace"
|
||||
font.pointSize: 8
|
||||
font.bold: pill.active
|
||||
color: pill.active ? pill.accent : Kirigami.Theme.textColor
|
||||
opacity: pill.active ? 1.0 : 0.78
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: pillMouse
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: pill.clicked()
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
After Width: | Height: | Size: 225 KiB |
Executable
+147
@@ -0,0 +1,147 @@
|
||||
#!/bin/sh
|
||||
# SPDX-License-Identifier: MIT
|
||||
# enne2-cpufreq — installazione.
|
||||
#
|
||||
# ./install.sh widget (utente) + backend privilegiato (sudo)
|
||||
# ./install.sh --widget solo il plasmoide, come utente
|
||||
# ./install.sh --backend solo helper + polkit, come root
|
||||
# ./install.sh --no-polkit-rule
|
||||
# backend senza la regola "nessun prompt":
|
||||
# ogni modifica chiederà la password
|
||||
# ./install.sh --dry-run mostra cosa verrebbe fatto
|
||||
set -u
|
||||
|
||||
PKG_DIR=$(cd "$(dirname "$0")" && pwd)
|
||||
ID=org.enne2.cpufreq
|
||||
LIBDIR=/usr/local/libexec/enne2-cpufreq
|
||||
POLICY_DST=/usr/share/polkit-1/actions/org.enne2.cpufreq.set.policy
|
||||
RULE_DST=/etc/polkit-1/rules.d/50-org.enne2.cpufreq.rules
|
||||
STATEDIR=/var/lib/enne2-cpufreq
|
||||
|
||||
MODE=all
|
||||
WITH_RULE=1
|
||||
DRY=0
|
||||
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--widget) MODE=widget ;;
|
||||
--backend) MODE=backend ;;
|
||||
--all) MODE=all ;;
|
||||
--no-polkit-rule) WITH_RULE=0 ;;
|
||||
--dry-run|-n) DRY=1 ;;
|
||||
-h|--help)
|
||||
sed -n 's/^# \{0,1\}//p' "$0" | sed -n '1,20p'
|
||||
exit 0
|
||||
;;
|
||||
*) printf 'opzione sconosciuta: %s\n' "$arg" >&2; exit 2 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
say() { printf '%s\n' "$*"; }
|
||||
run() {
|
||||
if [ "$DRY" -eq 1 ]; then
|
||||
printf ' [dry-run] %s\n' "$*"
|
||||
else
|
||||
"$@"
|
||||
fi
|
||||
}
|
||||
|
||||
have_widget() {
|
||||
kpackagetool6 --type Plasma/Applet --show "$ID" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
install_widget() {
|
||||
command -v kpackagetool6 >/dev/null 2>&1 || {
|
||||
say "ERRORE: kpackagetool6 non trovato (pacchetto kpackagetool6/kf6-kpackage)."
|
||||
return 1
|
||||
}
|
||||
if [ "$DRY" -eq 1 ]; then
|
||||
say "[dry-run] kpackagetool6 --type Plasma/Applet --install|--upgrade $PKG_DIR"
|
||||
elif have_widget; then
|
||||
say "Aggiorno il plasmoide $ID…"
|
||||
kpackagetool6 --type Plasma/Applet --upgrade "$PKG_DIR" || return 1
|
||||
else
|
||||
say "Installo il plasmoide $ID…"
|
||||
kpackagetool6 --type Plasma/Applet --install "$PKG_DIR" || return 1
|
||||
fi
|
||||
|
||||
WIDGET_DIR=$HOME/.local/share/plasma/plasmoids/$ID
|
||||
if [ "$DRY" -eq 0 ] && [ -d "$WIDGET_DIR/contents/scripts" ]; then
|
||||
chmod 0755 "$WIDGET_DIR"/contents/scripts/*.sh \
|
||||
"$WIDGET_DIR"/contents/scripts/enne2-cpufreq-task 2>/dev/null
|
||||
fi
|
||||
|
||||
# comodità da terminale: stesso launcher del widget
|
||||
if [ "$DRY" -eq 0 ] && [ -d "$HOME/.local/bin" ]; then
|
||||
ln -sfn "$WIDGET_DIR/contents/scripts/enne2-cpufreq-task" \
|
||||
"$HOME/.local/bin/enne2-cpufreq-task"
|
||||
say "Comando utente: ~/.local/bin/enne2-cpufreq-task (list|add|run|stop)"
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
install_backend() {
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
say "ERRORE: la parte privilegiata richiede root (usare sudo)."
|
||||
return 1
|
||||
fi
|
||||
say "Backend privilegiato in $LIBDIR"
|
||||
run install -d -m 0755 -o root -g root "$LIBDIR"
|
||||
run install -d -m 0755 -o root -g root "$STATEDIR"
|
||||
run install -m 0755 -o root -g root "$PKG_DIR/bin/cpufreq-helper" "$LIBDIR/cpufreq-helper"
|
||||
run install -m 0644 -o root -g root "$PKG_DIR/contents/scripts/profiles.sh" "$LIBDIR/profiles.sh"
|
||||
|
||||
say "Azione polkit: $POLICY_DST"
|
||||
run install -d -m 0755 /usr/share/polkit-1/actions
|
||||
run install -m 0644 -o root -g root \
|
||||
"$PKG_DIR/polkit/org.enne2.cpufreq.set.policy" "$POLICY_DST"
|
||||
|
||||
if [ "$WITH_RULE" -eq 1 ]; then
|
||||
say "Regola polkit senza prompt (gruppo wheel, sessione attiva): $RULE_DST"
|
||||
run install -d -m 0755 /etc/polkit-1/rules.d
|
||||
run install -m 0644 -o root -g root \
|
||||
"$PKG_DIR/polkit/50-org.enne2.cpufreq.rules" "$RULE_DST"
|
||||
else
|
||||
say "Regola senza prompt NON installata: verrà chiesta la password a ogni modifica."
|
||||
run rm -f "$RULE_DST"
|
||||
fi
|
||||
|
||||
# polkitd sorveglia le directory delle regole, il reload è solo una sicurezza
|
||||
if [ "$DRY" -eq 0 ]; then
|
||||
systemctl try-reload-or-restart polkit >/dev/null 2>&1 || true
|
||||
fi
|
||||
|
||||
say ""
|
||||
say "Verifica:"
|
||||
if [ "$DRY" -eq 0 ]; then
|
||||
"$LIBDIR/cpufreq-helper" status | sed 's/^/ /'
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
case "$MODE" in
|
||||
widget)
|
||||
install_widget
|
||||
;;
|
||||
backend)
|
||||
install_backend
|
||||
;;
|
||||
all)
|
||||
install_widget || exit 1
|
||||
say ""
|
||||
if [ "$(id -u)" -eq 0 ]; then
|
||||
install_backend || exit 1
|
||||
else
|
||||
say "Parte privilegiata: sudo $0 --backend $([ "$WITH_RULE" -eq 0 ] && echo --no-polkit-rule)"
|
||||
if [ "$DRY" -eq 1 ]; then
|
||||
sudo -n true >/dev/null 2>&1 && sudo "$0" --backend $([ "$WITH_RULE" -eq 0 ] && echo --no-polkit-rule) || say "[dry-run] sudo richiesto"
|
||||
else
|
||||
sudo "$0" --backend $([ "$WITH_RULE" -eq 0 ] && echo --no-polkit-rule) || exit 1
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
say ""
|
||||
say "Fatto. Aggiungi il widget dal desktop (Aggiungi widget → «CPU Frequency Control»)"
|
||||
say "oppure via script: desktops()[0].addWidget(\"$ID\")"
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"KPackageStructure": "Plasma/Applet",
|
||||
"KPlugin": {
|
||||
"Id": "org.enne2.cpufreq",
|
||||
"Name": "CPU Frequency Control",
|
||||
"Description": "Controlla governor, limiti di frequenza (min/max), EPP e boost della CPU, applica profili di risparmio o performance e lancia task con un profilo dedicato, ripristinando lo stato precedente alla fine. Backend: helper privilegiato via pkexec + azione polkit org.enne2.cpufreq.set.",
|
||||
"Icon": "cpu",
|
||||
"Version": "1.0",
|
||||
"License": "MIT",
|
||||
"Authors": [
|
||||
{
|
||||
"Name": "enne2"
|
||||
}
|
||||
],
|
||||
"Category": "System Information",
|
||||
"Website": "https://git.enne2.net/enne2/kde-cpufreq-dashboard"
|
||||
},
|
||||
"X-Plasma-API-Minimum-Version": "6.0"
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* enne2-cpufreq — consenso senza prompt per l'helper del plasmoide.
|
||||
*
|
||||
* Il permesso riguarda UNA sola azione (org.enne2.cpufreq.set) e quindi un solo
|
||||
* programma: /usr/local/libexec/enne2-cpufreq/cpufreq-helper, con gli argomenti
|
||||
* validati al suo interno. Condizioni: utente locale, sessione attiva, gruppo
|
||||
* wheel. Senza questa regola la .policy chiede la password a ogni modifica
|
||||
* (allow_active=auth_admin_keep): basta eliminare questo file per tornare al
|
||||
* comportamento con prompt.
|
||||
*/
|
||||
polkit.addRule(function (action, subject) {
|
||||
if (action.id !== "org.enne2.cpufreq.set") {
|
||||
return;
|
||||
}
|
||||
if (!subject.local || !subject.active) {
|
||||
return;
|
||||
}
|
||||
if (subject.isInGroup("wheel")) {
|
||||
return polkit.Result.YES;
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE policyconfig PUBLIC
|
||||
"-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
|
||||
"http://www.freedesktop.org/standards/PolicyKit/1.0/policyconfig.dtd">
|
||||
<!--
|
||||
org.enne2.cpufreq.set — azione polkit dedicata all'helper privilegiato del
|
||||
plasmoide CPU Frequency Control (org.enne2.cpufreq).
|
||||
|
||||
L'annotazione exec.path fa sì che pkexec usi QUESTA azione (e non la generica
|
||||
org.freedesktop.policykit.exec) solo quando esegue l'helper indicato: nessun
|
||||
altro programma eredita il permesso.
|
||||
-->
|
||||
<policyconfig>
|
||||
<vendor>enne2</vendor>
|
||||
<vendor_url>https://git.enne2.net/enne2/kde-cpufreq-dashboard</vendor_url>
|
||||
|
||||
<action id="org.enne2.cpufreq.set">
|
||||
<description>Configurare governor, limiti di frequenza e boost della CPU</description>
|
||||
<message>Per modificare governor, limiti di frequenza, EPP o boost della CPU è necessaria l'autorizzazione di amministratore.</message>
|
||||
<icon_name>cpu</icon_name>
|
||||
<defaults>
|
||||
<allow_any>no</allow_any>
|
||||
<allow_inactive>no</allow_inactive>
|
||||
<allow_active>auth_admin_keep</allow_active>
|
||||
</defaults>
|
||||
<annotate key="org.freedesktop.policykit.exec.path">/usr/local/libexec/enne2-cpufreq/cpufreq-helper</annotate>
|
||||
</action>
|
||||
</policyconfig>
|
||||
Executable
+64
@@ -0,0 +1,64 @@
|
||||
#!/bin/sh
|
||||
# SPDX-License-Identifier: MIT
|
||||
# enne2-cpufreq — disinstallazione.
|
||||
# ./uninstall.sh rimuove widget (utente) e backend (sudo)
|
||||
# ./uninstall.sh --widget solo il plasmoide
|
||||
# ./uninstall.sh --backend solo helper/polkit/stato (root)
|
||||
# I task configurati (~/.config/enne2-cpufreq) NON vengono cancellati.
|
||||
set -u
|
||||
|
||||
PKG_DIR=$(cd "$(dirname "$0")" && pwd)
|
||||
ID=org.enne2.cpufreq
|
||||
LIBDIR=/usr/local/libexec/enne2-cpufreq
|
||||
POLICY_DST=/usr/share/polkit-1/actions/org.enne2.cpufreq.set.policy
|
||||
RULE_DST=/etc/polkit-1/rules.d/50-org.enne2.cpufreq.rules
|
||||
STATEDIR=/var/lib/enne2-cpufreq
|
||||
|
||||
MODE=all
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--widget) MODE=widget ;;
|
||||
--backend) MODE=backend ;;
|
||||
--all) MODE=all ;;
|
||||
-h|--help) sed -n 's/^# \{0,1\}//p' "$0" | sed -n '1,10p'; exit 0 ;;
|
||||
*) printf 'opzione sconosciuta: %s\n' "$arg" >&2; exit 2 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
remove_widget() {
|
||||
if kpackagetool6 --type Plasma/Applet --show "$ID" >/dev/null 2>&1; then
|
||||
printf 'Rimuovo il plasmoide %s…\n' "$ID"
|
||||
kpackagetool6 --type Plasma/Applet --remove "$ID" || return 1
|
||||
else
|
||||
printf 'Plasmoide %s non installato.\n' "$ID"
|
||||
fi
|
||||
[ -L "$HOME/.local/bin/enne2-cpufreq-task" ] && rm -f "$HOME/.local/bin/enne2-cpufreq-task"
|
||||
return 0
|
||||
}
|
||||
|
||||
remove_backend() {
|
||||
[ "$(id -u)" -eq 0 ] || { printf 'ERRORE: richiede root.\n' >&2; return 1; }
|
||||
# ripristina lo stato hardware prima di togliere l'helper
|
||||
if [ -x "$LIBDIR/cpufreq-helper" ]; then
|
||||
"$LIBDIR/cpufreq-helper" task-end --all >/dev/null 2>&1
|
||||
fi
|
||||
rm -f "$POLICY_DST" "$RULE_DST"
|
||||
rm -rf "$LIBDIR"
|
||||
rm -rf "$STATEDIR"
|
||||
systemctl try-reload-or-restart polkit >/dev/null 2>&1 || true
|
||||
printf 'Backend rimosso.\n'
|
||||
return 0
|
||||
}
|
||||
|
||||
case "$MODE" in
|
||||
widget) remove_widget ;;
|
||||
backend) remove_backend ;;
|
||||
all)
|
||||
remove_widget
|
||||
if [ "$(id -u)" -eq 0 ]; then
|
||||
remove_backend
|
||||
else
|
||||
sudo "$0" --backend
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
Reference in New Issue
Block a user