Caffeine per KDE Plasma 6: widget di pannello (inibizione schermo + sospensione)
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
*.qmlc
|
||||
*.jsc
|
||||
*~
|
||||
build/
|
||||
package/
|
||||
*.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,88 @@
|
||||
# enne2-caffeine
|
||||
|
||||
Widget per il pannello di **KDE Plasma 6** che impedisce lo spegnimento dello schermo, il
|
||||
blocco schermo e la sospensione automatica: l'equivalente Plasma di **Caffeine** di GNOME.
|
||||
|
||||

|
||||
|
||||
## Funzioni
|
||||
|
||||
- **Clic sinistro sull'icona** → attiva/disattiva immediatamente (come Caffeine su GNOME).
|
||||
Tazza piena = inibizione attiva, tazza vuota = inibizione assente.
|
||||
- **Clic centrale** → popup con le opzioni rapide.
|
||||
- **Due modalità**:
|
||||
- *Schermo + sospensione* (default, come Caffeine): blocca spegnimento/dim dello schermo,
|
||||
blocco schermo e sospensione automatica;
|
||||
- *Solo sospensione* (come Amphetamine): il computer non si sospende ma lo schermo può
|
||||
spegnersi normalmente.
|
||||
- **Timer di disattivazione automatica** (5/15/30/60/120/240 minuti, oppure mai), con
|
||||
countdown nel tooltip e nell'icona di pannello; notifica opzionale alla scadenza.
|
||||
- Il widget è "stateless": dopo un riavvio Caffeine è spento, come ci si aspetta.
|
||||
|
||||
## Come funziona
|
||||
|
||||
Il backend sono due unità **systemd utente** che lanciano `systemd-inhibit` (logind) e restano
|
||||
vive finché l'inibizione è attiva:
|
||||
|
||||
| Modalità | Unità | systemd-inhibit |
|
||||
|---|---|---|
|
||||
| schermo + sospensione | `enne2-caffeine-screen.service` | `--what=sleep:idle` |
|
||||
| solo sospensione | `enne2-caffeine-sleep.service` | `--what=sleep` |
|
||||
|
||||
PowerDevil/PolicyAgent traduce le inibizioni logind in policy interne:
|
||||
|
||||
- `sleep` → `InterruptSession` (niente sospensione automatica);
|
||||
- `idle` → `ChangeScreenSettings` (niente dim/spegnimento dello schermo, niente blocco schermo).
|
||||
|
||||
Perché non si usa direttamente `org.kde.Solid.PowerManagement.PolicyAgent.AddInhibition`?
|
||||
Perché il cookie di quell'API è legato al chiamante D-Bus: una chiamata da un processo
|
||||
effimero verrebbe rilasciata subito (e l'inibizione ha 5 s di delay prima di essere applicata).
|
||||
Usando unità systemd l'inibizione sopravvive anche al riavvio di plasmashell e si rilascia da
|
||||
sola alla fine della sessione.
|
||||
|
||||
## Installazione
|
||||
|
||||
```bash
|
||||
git clone ssh://git@git.enne2.net:222/enne2/kde-caffeine.git
|
||||
cd kde-caffeine
|
||||
./install.sh
|
||||
```
|
||||
|
||||
Poi: tasto destro sul pannello → *Aggiungi widget* → **Caffeine** (categoria *Utilità*).
|
||||
Per averlo vicino all'orologio trascinalo a sinistra dell'orologio.
|
||||
|
||||
## CLI e scorciatoia da tastiera
|
||||
|
||||
```bash
|
||||
bin/enne2-caffeine status # state=on|off, mode=screen|sleep, deadline=<epoch>
|
||||
bin/enne2-caffeine on 30 # attiva per 30 minuti (modalità schermo + sospensione)
|
||||
bin/enne2-caffeine toggle # inverti
|
||||
bin/enne2-caffeine sleep on 60 # solo sospensione, 60 minuti
|
||||
bin/enne2-caffeine off
|
||||
```
|
||||
|
||||
Per una scorciatoia globale: *Impostazioni di sistema → Tastiera → Scorciatoie → Aggiungi →
|
||||
Comando o script*, comando `enne2-caffeine toggle` (o il percorso completo dello script).
|
||||
|
||||
## Verifica
|
||||
|
||||
```bash
|
||||
systemd-inhibit --list | grep enne2-caffeine
|
||||
busctl --user get-property org.kde.Solid.PowerManagement \
|
||||
/org/kde/Solid/PowerManagement/PolicyAgent \
|
||||
org.kde.Solid.PowerManagement.PolicyAgent ActiveInhibitions | grep -i caffeine
|
||||
```
|
||||
|
||||
La seconda mostra che PowerDevil ha recepito l'inibizione logind (`what=idle:sleep`).
|
||||
|
||||
## Configurazione del widget
|
||||
|
||||
Tasto destro sull'icona → *Configura Caffeine…*:
|
||||
|
||||
- **Impedisci anche lo spegnimento e il blocco dello schermo** (default: attivo);
|
||||
- **Disattiva automaticamente dopo N minuti** (0 = mai);
|
||||
- **Notifica quando scade il timer**.
|
||||
|
||||
## Licenza
|
||||
|
||||
MIT — vedi [LICENSE](LICENSE).
|
||||
Executable
+50
@@ -0,0 +1,50 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# enne2-caffeine — comando da terminale / scorciatoia globale
|
||||
#
|
||||
# Uso:
|
||||
# enne2-caffeine stato
|
||||
# enne2-caffeine on [min] attiva (modalità schermo + sospensione)
|
||||
# enne2-caffeine off disattiva
|
||||
# enne2-caffeine toggle [min]
|
||||
# enne2-caffeine sleep on attiva in modalità "solo sospensione"
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
set -uo pipefail
|
||||
|
||||
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
BACKEND="$HERE/../contents/scripts/caffeine.sh"
|
||||
|
||||
[ -x "$BACKEND" ] || { echo "error=backend-not-found"; exit 1; }
|
||||
|
||||
# garanzia che le unità siano installate
|
||||
"$BACKEND" ensure-units "$HERE/../systemd" >/dev/null 2>&1 || true
|
||||
|
||||
command="${1:-status}"
|
||||
shift || true
|
||||
|
||||
case "$command" in
|
||||
status|state)
|
||||
"$BACKEND" status
|
||||
;;
|
||||
on)
|
||||
"$BACKEND" on screen "${1:-0}"
|
||||
;;
|
||||
off)
|
||||
"$BACKEND" off
|
||||
;;
|
||||
toggle)
|
||||
"$BACKEND" toggle screen "${1:-0}"
|
||||
;;
|
||||
sleep)
|
||||
[ "${1:-on}" = "off" ] && "$BACKEND" off || "$BACKEND" on sleep "${2:-0}"
|
||||
;;
|
||||
screen)
|
||||
[ "${1:-on}" = "off" ] && "$BACKEND" off || "$BACKEND" on screen "${2:-0}"
|
||||
;;
|
||||
*)
|
||||
echo "uso: enne2-caffeine [status|on [minuti]|off|toggle [minuti]|sleep on [minuti]|screen on [minuti]]"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<kcfg xmlns="http://www.kde.org/standards/kcfg/1.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.kde.org/standards/kcfg/1.0
|
||||
http://www.kde.org/standards/kcfg/1.0/kcfg.xsd">
|
||||
<kcfgfile name=""/>
|
||||
<group name="General">
|
||||
<entry name="keepScreenOn" type="Bool">
|
||||
<label>Impedisci anche lo spegnimento e il blocco dello schermo (come Caffeine di GNOME). Se disattivato blocca solo la sospensione automatica, lasciando spegnere lo schermo (come Amphetamine).</label>
|
||||
<default>true</default>
|
||||
</entry>
|
||||
<entry name="autoOffMinutes" type="Int">
|
||||
<label>Disattiva automaticamente dopo N minuti (0 = mai). Il timer parte quando si attiva Caffeine.</label>
|
||||
<default>0</default>
|
||||
<min>0</min>
|
||||
<max>1440</max>
|
||||
</entry>
|
||||
<entry name="notifyOnTimerEnd" type="Bool">
|
||||
<label>Mostra una notifica quando il timer disattiva Caffeine.</label>
|
||||
<default>true</default>
|
||||
</entry>
|
||||
</group>
|
||||
</kcfg>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||
<g fill="none" stroke="#9aa1ac" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M5.6 8.6h10.8l-1.1 10.1a2 2 0 0 1-2 1.8H8.7a2 2 0 0 1-2-1.8Z"/>
|
||||
<path d="M16.6 10.7h1.5a2.6 2.6 0 0 1 0 5.2h-1.8"/>
|
||||
<path d="M4 21.9h16"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 398 B |
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||
<path d="M5.6 8.6h10.8l-1.1 10.1a2 2 0 0 1-2 1.8H8.7a2 2 0 0 1-2-1.8Z" fill="#e8a33d"/>
|
||||
<ellipse cx="11" cy="8.6" rx="5.4" ry="0.9" fill="#c8862a"/>
|
||||
<g fill="none" stroke="#e8a33d" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M16.6 10.7h1.5a2.6 2.6 0 0 1 0 5.2h-1.8"/>
|
||||
<path d="M4 21.9h16"/>
|
||||
<path d="M9.9 6.4c-1-1.3 1-2.3 0-3.7"/>
|
||||
<path d="M13.2 6.4c-1-1.3 1-2.3 0-3.7"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 563 B |
Executable
+179
@@ -0,0 +1,179 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# enne2-caffeine — backend del widget Caffeine per KDE Plasma 6
|
||||
#
|
||||
# Blocca lo spegnimento/blocco dello schermo e la sospensione automatica usando
|
||||
# systemd-inhibit (logind). PowerDevil/PolicyAgent onora le inibizioni logind:
|
||||
# what=sleep -> InterruptSession (niente sospensione automatica)
|
||||
# what=idle -> ChangeScreenSettings (niente dim/spegnimento schermo, niente blocco)
|
||||
# L'inibizione vive in unità systemd utente, quindi sopravvive al riavvio di
|
||||
# plasmashell e si rilascia da sola alla fine della sessione.
|
||||
#
|
||||
# Uso:
|
||||
# caffeine.sh status
|
||||
# caffeine.sh on <screen|sleep> [minuti]
|
||||
# caffeine.sh switch <screen|sleep> # cambia modalità mantenendo il timer
|
||||
# caffeine.sh off [notify]
|
||||
# caffeine.sh toggle <screen|sleep> [minuti]
|
||||
# caffeine.sh ensure-units <cartella-con-i-.service>
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
set -uo pipefail
|
||||
|
||||
UNIT_SCREEN="enne2-caffeine-screen.service"
|
||||
UNIT_SLEEP="enne2-caffeine-sleep.service"
|
||||
STATE_DIR="${XDG_STATE_HOME:-$HOME/.local/state}/enne2-caffeine"
|
||||
DEADLINE_FILE="$STATE_DIR/deadline"
|
||||
USER_UNIT_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/systemd/user"
|
||||
|
||||
SYSTEMCTL="$(command -v systemctl 2>/dev/null || true)"
|
||||
|
||||
fail() {
|
||||
echo "error=$1"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# systemd utente disponibile e interrogabile?
|
||||
systemd_ok() {
|
||||
[ -n "$SYSTEMCTL" ] || return 1
|
||||
case "$("$SYSTEMCTL" --user is-active "$UNIT_SCREEN" 2>/dev/null)" in
|
||||
active|inactive|failed|activating|deactivating|unknown) return 0 ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
unit_state() {
|
||||
"$SYSTEMCTL" --user is-active "$1" 2>/dev/null
|
||||
}
|
||||
|
||||
active_unit() {
|
||||
if [ "$(unit_state "$UNIT_SCREEN")" = "active" ]; then
|
||||
printf '%s\n' "$UNIT_SCREEN"
|
||||
elif [ "$(unit_state "$UNIT_SLEEP")" = "active" ]; then
|
||||
printf '%s\n' "$UNIT_SLEEP"
|
||||
fi
|
||||
}
|
||||
|
||||
only_digits() {
|
||||
case "${1:-}" in
|
||||
''|*[!0-9]*) printf '0\n' ;;
|
||||
*) printf '%s\n' "$1" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
cmd_ensure_units() {
|
||||
local src="${1:-}"
|
||||
[ -n "$src" ] && [ -d "$src" ] || fail "unit-source-missing"
|
||||
systemd_ok || fail "systemd-user-unavailable"
|
||||
mkdir -p "$USER_UNIT_DIR" || fail "cannot-create-unit-dir"
|
||||
local changed=0 f target
|
||||
for f in "$src"/*.service; do
|
||||
[ -e "$f" ] || continue
|
||||
target="$USER_UNIT_DIR/$(basename "$f")"
|
||||
if ! cmp -s "$f" "$target"; then
|
||||
cp -f "$f" "$target" || fail "cannot-copy-unit"
|
||||
changed=1
|
||||
fi
|
||||
done
|
||||
if [ "$changed" = 1 ]; then
|
||||
"$SYSTEMCTL" --user daemon-reload >/dev/null 2>&1
|
||||
fi
|
||||
echo "ok=1"
|
||||
}
|
||||
|
||||
cmd_status() {
|
||||
mkdir -p "$STATE_DIR" 2>/dev/null
|
||||
if ! systemd_ok; then
|
||||
echo "backend=unsupported"
|
||||
echo "state=off"
|
||||
echo "mode=none"
|
||||
return 0
|
||||
fi
|
||||
echo "backend=systemd"
|
||||
|
||||
local unit
|
||||
unit="$(active_unit)"
|
||||
case "$unit" in
|
||||
"$UNIT_SCREEN") echo "state=on"; echo "mode=screen" ;;
|
||||
"$UNIT_SLEEP") echo "state=on"; echo "mode=sleep" ;;
|
||||
*) echo "state=off"; echo "mode=none" ;;
|
||||
esac
|
||||
|
||||
if [ -n "$unit" ] && [ -f "$DEADLINE_FILE" ]; then
|
||||
local d
|
||||
d="$(only_digits "$(cat "$DEADLINE_FILE" 2>/dev/null)")"
|
||||
if [ "$d" -gt 0 ]; then
|
||||
echo "deadline=$d"
|
||||
else
|
||||
rm -f "$DEADLINE_FILE"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
apply_mode() {
|
||||
systemd_ok || fail "systemd-user-unavailable"
|
||||
"$SYSTEMCTL" --user stop "$UNIT_SCREEN" "$UNIT_SLEEP" >/dev/null 2>&1
|
||||
if [ "${1:-screen}" = "sleep" ]; then
|
||||
"$SYSTEMCTL" --user start "$UNIT_SLEEP" >/dev/null 2>&1 || fail "start-failed"
|
||||
else
|
||||
"$SYSTEMCTL" --user start "$UNIT_SCREEN" >/dev/null 2>&1 || fail "start-failed"
|
||||
fi
|
||||
}
|
||||
|
||||
cmd_on() {
|
||||
local mode="${1:-screen}"
|
||||
local minutes
|
||||
minutes="$(only_digits "${2:-0}")"
|
||||
mkdir -p "$STATE_DIR" 2>/dev/null
|
||||
apply_mode "$mode" || return 1
|
||||
if [ "$minutes" -gt 0 ]; then
|
||||
echo "$(( $(date +%s) + minutes * 60 ))" > "$DEADLINE_FILE"
|
||||
else
|
||||
rm -f "$DEADLINE_FILE"
|
||||
fi
|
||||
echo "ok=1"
|
||||
cmd_status
|
||||
}
|
||||
|
||||
cmd_switch() {
|
||||
apply_mode "${1:-screen}" || return 1
|
||||
echo "ok=1"
|
||||
cmd_status
|
||||
}
|
||||
|
||||
cmd_off() {
|
||||
local notify="${1:-}"
|
||||
if systemd_ok; then
|
||||
"$SYSTEMCTL" --user stop "$UNIT_SCREEN" "$UNIT_SLEEP" >/dev/null 2>&1
|
||||
fi
|
||||
rm -f "$DEADLINE_FILE"
|
||||
if [ "$notify" = "notify" ] && command -v notify-send >/dev/null 2>&1; then
|
||||
notify-send -a "Caffeine" -i preferences-system-power-management \
|
||||
"Caffeine disattivato" \
|
||||
"Il timer è scaduto: schermo e sospensione tornano alla gestione automatica." || true
|
||||
fi
|
||||
echo "ok=1"
|
||||
}
|
||||
|
||||
cmd_toggle() {
|
||||
local mode="${1:-screen}"
|
||||
local minutes
|
||||
minutes="$(only_digits "${2:-0}")"
|
||||
if systemd_ok && [ -n "$(active_unit)" ]; then
|
||||
cmd_off
|
||||
else
|
||||
cmd_on "$mode" "$minutes"
|
||||
fi
|
||||
}
|
||||
|
||||
case "${1:-status}" in
|
||||
status) cmd_status ;;
|
||||
on|start) cmd_on "${2:-screen}" "${3:-0}" ;;
|
||||
switch) cmd_switch "${2:-screen}" ;;
|
||||
off|stop) cmd_off "${2:-}" ;;
|
||||
toggle) cmd_toggle "${2:-screen}" "${3:-0}" ;;
|
||||
ensure-units) cmd_ensure_units "${2:-}" ;;
|
||||
version) echo "name=enne2-caffeine"; echo "version=1.0" ;;
|
||||
*) fail "unknown-command" ;;
|
||||
esac
|
||||
@@ -0,0 +1,400 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 enne2
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import org.kde.plasma.plasmoid
|
||||
import org.kde.plasma.core as PlasmaCore
|
||||
import org.kde.plasma.components as PlasmaComponents3
|
||||
import org.kde.plasma.extras as PlasmaExtras
|
||||
import org.kde.plasma.plasma5support as P5Support
|
||||
import org.kde.kirigami as Kirigami
|
||||
|
||||
PlasmoidItem {
|
||||
id: root
|
||||
|
||||
// ---------------------------------------------------------------- percorsi
|
||||
readonly property string pkgPath: Qt.resolvedUrl("..").toString().replace("file://", "")
|
||||
readonly property string scriptPath: pkgPath + "/scripts/caffeine.sh"
|
||||
readonly property string unitSrcPath: pkgPath + "/systemd"
|
||||
readonly property string iconOnPath: Qt.resolvedUrl("../icons/caffeine-on.svg").toString()
|
||||
readonly property string iconOffPath: Qt.resolvedUrl("../icons/caffeine-off.svg").toString()
|
||||
readonly property string statusCommand: scriptPath + " status"
|
||||
|
||||
// ------------------------------------------------------------------ stato
|
||||
property string backendState: "off" // on | off
|
||||
property string backendMode: "none" // screen | sleep | none
|
||||
property string backend: "unknown" // systemd | unsupported | unknown
|
||||
property int deadlineEpoch: 0
|
||||
property int remainingSec: 0
|
||||
property bool unitsReady: false
|
||||
property string lastError: ""
|
||||
|
||||
readonly property bool active: backendState === "on"
|
||||
readonly property bool timerActive: active && remainingSec > 0
|
||||
readonly property bool keepScreenOn: plasmoid.configuration.keepScreenOn
|
||||
readonly property string startMode: keepScreenOn ? "screen" : "sleep"
|
||||
|
||||
// ------------------------------------------------------------ presentazione
|
||||
Plasmoid.icon: active ? iconOnPath : iconOffPath
|
||||
Plasmoid.status: active ? PlasmaCore.Types.ActiveStatus : PlasmaCore.Types.PassiveStatus
|
||||
toolTipMainText: active ? "Caffeine attivo" : "Caffeine inattivo"
|
||||
toolTipTextFormat: Text.PlainText
|
||||
toolTipSubText: {
|
||||
let text = modeText()
|
||||
if (timerActive) {
|
||||
text += "\nSi disattiva automaticamente tra " + formatDuration(remainingSec)
|
||||
}
|
||||
text += "\nClic sinistro: attiva/disattiva · clic centrale: opzioni"
|
||||
return text
|
||||
}
|
||||
|
||||
// un clic sinistro non deve aprire il popup: lo gestiamo noi
|
||||
activationTogglesExpanded: false
|
||||
|
||||
// ---------------------------------------------------------------- funzioni
|
||||
function formatDuration(seconds) {
|
||||
const total = Math.max(0, Math.floor(seconds))
|
||||
const hours = Math.floor(total / 3600)
|
||||
const minutes = Math.floor((total % 3600) / 60)
|
||||
const secs = total % 60
|
||||
const pad = value => (value < 10 ? "0" : "") + value
|
||||
if (hours > 0) {
|
||||
return hours + "h " + pad(minutes) + "m"
|
||||
}
|
||||
if (minutes > 0) {
|
||||
return minutes + "m " + pad(secs) + "s"
|
||||
}
|
||||
return secs + "s"
|
||||
}
|
||||
|
||||
function modeText() {
|
||||
if (!active) {
|
||||
return "Nessuna inibizione attiva: schermo e sospensione gestiti automaticamente"
|
||||
}
|
||||
if (backendMode === "sleep") {
|
||||
return "Sospensione automatica bloccata · lo schermo può spegnersi"
|
||||
}
|
||||
return "Schermo, blocco schermo e sospensione automatica bloccati"
|
||||
}
|
||||
|
||||
function applyStatus(output) {
|
||||
const values = {}
|
||||
String(output || "").split("\n").forEach(function(line) {
|
||||
const index = line.indexOf("=")
|
||||
if (index > 0) {
|
||||
values[line.substring(0, index)] = line.substring(index + 1).trim()
|
||||
}
|
||||
})
|
||||
if (values["backend"] !== undefined) {
|
||||
backend = values["backend"]
|
||||
}
|
||||
if (values["state"] !== undefined) {
|
||||
backendState = values["state"]
|
||||
}
|
||||
if (values["mode"] !== undefined) {
|
||||
backendMode = values["mode"]
|
||||
}
|
||||
const deadline = parseInt(values["deadline"] || "0", 10)
|
||||
deadlineEpoch = isNaN(deadline) ? 0 : deadline
|
||||
if (deadlineEpoch > 0) {
|
||||
const remaining = deadlineEpoch - Math.floor(Date.now() / 1000)
|
||||
remainingSec = remaining > 0 ? remaining : 0
|
||||
if (remaining <= 0) {
|
||||
deactivate(true)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
remainingSec = 0
|
||||
}
|
||||
}
|
||||
|
||||
function run(command) {
|
||||
if (backend === "unsupported") {
|
||||
return
|
||||
}
|
||||
const source = scriptPath + " " + command
|
||||
actionSource.disconnectSource(source)
|
||||
actionSource.connectSource(source)
|
||||
}
|
||||
|
||||
function refresh() {
|
||||
statusSource.disconnectSource(statusCommand)
|
||||
statusSource.connectSource(statusCommand)
|
||||
}
|
||||
|
||||
function activate() {
|
||||
const minutes = plasmoid.configuration.autoOffMinutes
|
||||
run("on " + startMode + " " + minutes)
|
||||
remainingSec = minutes > 0 ? minutes * 60 : 0
|
||||
refreshDelay.restart()
|
||||
}
|
||||
|
||||
function deactivate(fromTimer) {
|
||||
const notify = (fromTimer === true && plasmoid.configuration.notifyOnTimerEnd) ? " notify" : ""
|
||||
run("off" + notify)
|
||||
remainingSec = 0
|
||||
deadlineEpoch = 0
|
||||
refreshDelay.restart()
|
||||
}
|
||||
|
||||
function toggle() {
|
||||
if (active) {
|
||||
deactivate(false)
|
||||
} else {
|
||||
activate()
|
||||
}
|
||||
}
|
||||
|
||||
function changeMode(keepScreen) {
|
||||
if (!active) {
|
||||
return
|
||||
}
|
||||
run("switch " + (keepScreen ? "screen" : "sleep"))
|
||||
refreshDelay.restart()
|
||||
}
|
||||
|
||||
function changeTimer(minutes) {
|
||||
if (!active) {
|
||||
return
|
||||
}
|
||||
if (minutes > 0) {
|
||||
remainingSec = minutes * 60
|
||||
run("on " + startMode + " " + minutes)
|
||||
} else {
|
||||
remainingSec = 0
|
||||
run("switch " + startMode)
|
||||
}
|
||||
refreshDelay.restart()
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- backend
|
||||
// Stato: polling ogni 2 secondi (systemctl --user is-active).
|
||||
P5Support.DataSource {
|
||||
id: statusSource
|
||||
engine: "executable"
|
||||
interval: 2000
|
||||
connectedSources: [root.statusCommand]
|
||||
onNewData: function(sourceName, data) {
|
||||
root.applyStatus(data ? data["stdout"] : "")
|
||||
}
|
||||
}
|
||||
|
||||
// Comandi una tantum (on/off/switch), un source per comando.
|
||||
P5Support.DataSource {
|
||||
id: actionSource
|
||||
engine: "executable"
|
||||
connectedSources: []
|
||||
onNewData: function(sourceName, data) {
|
||||
const output = data ? String(data["stdout"] || "") : ""
|
||||
root.lastError = output.indexOf("error=") === 0 ? output : ""
|
||||
if (root.lastError !== "") {
|
||||
console.warn("enne2-caffeine:", root.lastError)
|
||||
}
|
||||
root.refresh()
|
||||
}
|
||||
}
|
||||
|
||||
// Installa/aggiorna le unità systemd utente al primo avvio.
|
||||
P5Support.DataSource {
|
||||
id: unitsSource
|
||||
engine: "executable"
|
||||
connectedSources: [root.scriptPath + " ensure-units " + root.unitSrcPath]
|
||||
onNewData: function(sourceName, data) {
|
||||
root.unitsReady = Boolean(data && Number(data["exit code"]) === 0
|
||||
&& String(data["stdout"] || "").indexOf("ok=1") === 0)
|
||||
root.refresh()
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: refreshDelay
|
||||
interval: 300
|
||||
repeat: false
|
||||
onTriggered: root.refresh()
|
||||
}
|
||||
|
||||
// Countdown del timer di disattivazione automatica.
|
||||
Timer {
|
||||
interval: 1000
|
||||
repeat: true
|
||||
running: root.timerActive
|
||||
onTriggered: {
|
||||
const remaining = root.deadlineEpoch - Math.floor(Date.now() / 1000)
|
||||
root.remainingSec = remaining > 0 ? remaining : 0
|
||||
if (remaining <= 0) {
|
||||
root.deactivate(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------- rappresentazione compatta
|
||||
compactRepresentation: Item {
|
||||
implicitWidth: compactRow.implicitWidth
|
||||
implicitHeight: Kirigami.Units.iconSizes.smallMedium
|
||||
|
||||
RowLayout {
|
||||
id: compactRow
|
||||
anchors.centerIn: parent
|
||||
spacing: Kirigami.Units.smallSpacing
|
||||
|
||||
Kirigami.Icon {
|
||||
source: root.active ? root.iconOnPath : root.iconOffPath
|
||||
implicitWidth: Kirigami.Units.iconSizes.smallMedium
|
||||
implicitHeight: Kirigami.Units.iconSizes.smallMedium
|
||||
}
|
||||
|
||||
PlasmaComponents3.Label {
|
||||
visible: root.timerActive
|
||||
text: Math.ceil(root.remainingSec / 60) + "m"
|
||||
font: Kirigami.Theme.smallFont
|
||||
color: Kirigami.Theme.textColor
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.LeftButton | Qt.MiddleButton
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: function(mouse) {
|
||||
if (mouse.button === Qt.MiddleButton) {
|
||||
root.expanded = !root.expanded
|
||||
} else {
|
||||
root.toggle()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------- popup opzioni
|
||||
fullRepresentation: PlasmaExtras.Representation {
|
||||
Layout.minimumWidth: Kirigami.Units.gridUnit * 20
|
||||
Layout.minimumHeight: Kirigami.Units.gridUnit * 13
|
||||
Layout.preferredWidth: Kirigami.Units.gridUnit * 20
|
||||
Layout.preferredHeight: popupColumn.implicitHeight + Kirigami.Units.gridUnit * 2
|
||||
collapseMarginsHint: true
|
||||
|
||||
ColumnLayout {
|
||||
id: popupColumn
|
||||
anchors.fill: parent
|
||||
anchors.margins: Kirigami.Units.largeSpacing
|
||||
spacing: Kirigami.Units.smallSpacing
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Kirigami.Units.smallSpacing
|
||||
|
||||
Kirigami.Icon {
|
||||
source: root.active ? root.iconOnPath : root.iconOffPath
|
||||
implicitWidth: Kirigami.Units.iconSizes.medium
|
||||
implicitHeight: Kirigami.Units.iconSizes.medium
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 0
|
||||
|
||||
Kirigami.Heading {
|
||||
Layout.fillWidth: true
|
||||
level: 3
|
||||
text: root.active ? "Caffeine attivo" : "Caffeine inattivo"
|
||||
}
|
||||
|
||||
PlasmaComponents3.Label {
|
||||
Layout.fillWidth: true
|
||||
text: root.modeText()
|
||||
wrapMode: Text.WordWrap
|
||||
opacity: 0.7
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PlasmaComponents3.Label {
|
||||
Layout.fillWidth: true
|
||||
visible: root.timerActive
|
||||
text: "Disattivazione automatica tra " + root.formatDuration(root.remainingSec)
|
||||
color: Kirigami.Theme.positiveTextColor
|
||||
}
|
||||
|
||||
Kirigami.Separator {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Kirigami.Units.smallSpacing
|
||||
}
|
||||
|
||||
PlasmaComponents3.CheckBox {
|
||||
Layout.fillWidth: true
|
||||
text: "Impedisci anche lo spegnimento dello schermo"
|
||||
checked: root.keepScreenOn
|
||||
onToggled: {
|
||||
plasmoid.configuration.keepScreenOn = checked
|
||||
root.changeMode(checked)
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Kirigami.Units.smallSpacing
|
||||
spacing: Kirigami.Units.smallSpacing
|
||||
|
||||
PlasmaComponents3.Label {
|
||||
text: "Disattiva dopo:"
|
||||
}
|
||||
|
||||
PlasmaComponents3.ComboBox {
|
||||
id: timerCombo
|
||||
Layout.fillWidth: true
|
||||
model: ["Mai", "5 minuti", "15 minuti", "30 minuti", "1 ora", "2 ore", "4 ore"]
|
||||
readonly property var minutes: [0, 5, 15, 30, 60, 120, 240]
|
||||
currentIndex: Math.max(0, minutes.indexOf(plasmoid.configuration.autoOffMinutes))
|
||||
onActivated: function(index) {
|
||||
const value = minutes[index]
|
||||
plasmoid.configuration.autoOffMinutes = value
|
||||
root.changeTimer(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PlasmaComponents3.CheckBox {
|
||||
Layout.fillWidth: true
|
||||
visible: plasmoid.configuration.autoOffMinutes > 0
|
||||
text: "Notifica quando scade il timer"
|
||||
checked: plasmoid.configuration.notifyOnTimerEnd
|
||||
onToggled: plasmoid.configuration.notifyOnTimerEnd = checked
|
||||
}
|
||||
|
||||
PlasmaComponents3.Label {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Kirigami.Units.smallSpacing
|
||||
visible: root.backend === "unsupported" || root.lastError !== ""
|
||||
color: Kirigami.Theme.negativeTextColor
|
||||
wrapMode: Text.WordWrap
|
||||
text: root.backend === "unsupported"
|
||||
? "Backend non disponibile: serve systemd con gestore utente attivo."
|
||||
: "Errore dal backend: " + root.lastError
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
|
||||
PlasmaComponents3.Button {
|
||||
Layout.fillWidth: true
|
||||
text: root.active ? "Disattiva Caffeine" : "Attiva Caffeine"
|
||||
icon.name: root.active ? "media-playback-stop" : "media-playback-start"
|
||||
onClicked: root.toggle()
|
||||
}
|
||||
|
||||
PlasmaComponents3.Label {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Kirigami.Units.smallSpacing
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
font: Kirigami.Theme.smallFont
|
||||
opacity: 0.6
|
||||
wrapMode: Text.WordWrap
|
||||
text: "Clic sinistro sull'icona: attiva/disattiva\nClic centrale: queste opzioni"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+29
@@ -0,0 +1,29 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Installa/aggiorna il widget Caffeine e le unità systemd utente.
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
HERE="$(cd "$(dirname "$0")" && pwd)"
|
||||
|
||||
if kpackagetool6 -t Plasma/Applet --upgrade "$HERE" 2>/dev/null; then
|
||||
echo "Plasmoid aggiornato."
|
||||
else
|
||||
kpackagetool6 -t Plasma/Applet --install "$HERE"
|
||||
echo "Plasmoid installato."
|
||||
fi
|
||||
|
||||
"$HERE/contents/scripts/caffeine.sh" ensure-units "$HERE/systemd"
|
||||
|
||||
cat <<'EOF'
|
||||
|
||||
Fatto. Per aggiungere il widget al pannello:
|
||||
tasto destro sul pannello -> Aggiungi widget -> Caffeine (categoria Utilità)
|
||||
|
||||
Da terminale:
|
||||
./bin/enne2-caffeine on 30 # attiva per 30 minuti
|
||||
./bin/enne2-caffeine off
|
||||
./bin/enne2-caffeine status
|
||||
EOF
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"KPackageStructure": "Plasma/Applet",
|
||||
"KPlugin": {
|
||||
"Id": "org.enne2.caffeine",
|
||||
"Name": "Caffeine",
|
||||
"Description": "Impedisce con un clic lo spegnimento dello schermo, il blocco schermo e la sospensione automatica (equivalente KDE di Caffeine di GNOME). Backend: systemd-inhibit/logind via unità systemd utente. Due modalità: schermo + sospensione oppure solo sospensione (schermo libero di spegnersi). Timer di disattivazione automatica opzionale.",
|
||||
"Icon": "preferences-system-power-management",
|
||||
"Version": "1.0",
|
||||
"License": "MIT",
|
||||
"Authors": [
|
||||
{
|
||||
"Name": "enne2"
|
||||
}
|
||||
],
|
||||
"Category": "Utilities",
|
||||
"Website": "https://git.enne2.net/enne2/kde-caffeine"
|
||||
},
|
||||
"X-Plasma-API-Minimum-Version": "6.0"
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
[Unit]
|
||||
Description=Caffeine (enne2): blocca schermo, spegnimento schermo e sospensione automatica
|
||||
Documentation=https://git.enne2.net/enne2/kde-caffeine
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=/usr/bin/systemd-inhibit --what=sleep:idle --who=enne2-caffeine --why="Caffeine: schermo e sospensione bloccati" --mode=block /usr/bin/sleep infinity
|
||||
KillMode=control-group
|
||||
TimeoutStopSec=5
|
||||
@@ -0,0 +1,9 @@
|
||||
[Unit]
|
||||
Description=Caffeine (enne2): blocca solo la sospensione automatica (schermo libero di spegnersi)
|
||||
Documentation=https://git.enne2.net/enne2/kde-caffeine
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=/usr/bin/systemd-inhibit --what=sleep --who=enne2-caffeine --why="Caffeine: sospensione automatica bloccata" --mode=block /usr/bin/sleep infinity
|
||||
KillMode=control-group
|
||||
TimeoutStopSec=5
|
||||
Reference in New Issue
Block a user