Feedback immediato al clic: stato ottimistico, stato autorevole dall'output del comando, pulsazione+Plasmoid.busy durante l'operazione, polling 3s

This commit is contained in:
enne2
2026-09-25 16:20:22 +02:00
parent 65b854bfd1
commit 93dc22b654
2 changed files with 87 additions and 20 deletions
+8 -5
View File
@@ -148,12 +148,15 @@ cmd_off() {
"$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_status
if [ "$notify" = "notify" ] && command -v notify-send >/dev/null 2>&1; then
# in background: non ritarda la conferma al plasmoid
(notify-send -a "Caffeine" -i preferences-system-power-management \
"Caffeine disattivato" \
"Il timer è scaduto: schermo e sospensione tornano alla gestione automatica." \
>/dev/null 2>&1 &)
fi
}
cmd_toggle() {
+79 -15
View File
@@ -32,6 +32,9 @@ PlasmoidItem {
property bool timerExpiring: false
property bool unitsReady: false
property string lastError: ""
// operazione in corso verso il backend (feedback ottimistico)
property bool pending: false
property string pendingTarget: "" // stato atteso: on | off
readonly property bool active: backendState === "on"
readonly property bool timerActive: active && remainingSec > 0
@@ -41,10 +44,13 @@ PlasmoidItem {
// ------------------------------------------------------------ presentazione
Plasmoid.icon: active ? iconOnPath : iconOffPath
Plasmoid.status: active ? PlasmaCore.Types.ActiveStatus : PlasmaCore.Types.PassiveStatus
toolTipMainText: active ? "Caffeine attivo" : "Caffeine inattivo"
Plasmoid.busy: pending
toolTipMainText: pending
? "Caffeine: aggiornamento…"
: (active ? "Caffeine attivo" : "Caffeine inattivo")
toolTipTextFormat: Text.PlainText
toolTipSubText: {
let text = modeText()
let text = pending ? "Operazione in corso verso il backend…" : modeText()
if (timerActive) {
text += "\nSi disattiva automaticamente tra " + formatDuration(remainingSec)
}
@@ -89,6 +95,18 @@ PlasmoidItem {
values[line.substring(0, index)] = line.substring(index + 1).trim()
}
})
// durante un'operazione in corso i risultati del polling periodico
// potrebbero essere ancora quelli vecchi: non smentire l'icona
// ottimistica finché il backend non conferma il cambio di stato
if (pending && pendingTarget !== "") {
const confirmed = pendingTarget === "off"
? values["state"] === "off"
: values["state"] === "on"
if (!confirmed) {
return
}
clearPending()
}
if (values["backend"] !== undefined) {
backend = values["backend"]
}
@@ -129,20 +147,46 @@ PlasmoidItem {
statusSource.connectSource(statusCommand)
}
// ------------------------------------------------- feedback ottimistico
// Il backend risponde in ~70 ms, ma la conferma poteva arrivare solo al
// giro di polling successivo: l'icona viene quindi aggiornata subito e
// messa in stato "pending" (pulsazione + busy) finché il backend conferma.
function beginPending(target) {
pending = true
pendingTarget = target
pendingTimeout.restart()
}
function clearPending() {
pending = false
pendingTarget = ""
pendingTimeout.stop()
}
// output autorevole del comando appena eseguito (contiene state=/mode=)
function finishPending(output) {
clearPending()
applyStatus(output)
}
function activate() {
const minutes = plasmoid.configuration.autoOffMinutes
timerExpiring = false
run("on " + startMode + " " + minutes)
backendState = "on"
backendMode = startMode
remainingSec = minutes > 0 ? minutes * 60 : 0
refreshDelay.restart()
beginPending("on")
run("on " + startMode + " " + minutes)
}
function deactivate(fromTimer) {
const notify = (fromTimer === true && plasmoid.configuration.notifyOnTimerEnd) ? " notify" : ""
run("off" + notify)
backendState = "off"
backendMode = "none"
remainingSec = 0
deadlineEpoch = 0
refreshDelay.restart()
beginPending("off")
run("off" + notify)
}
function toggle() {
@@ -168,8 +212,9 @@ PlasmoidItem {
return
}
timerExpiring = false
backendMode = keepScreen ? "screen" : "sleep"
beginPending("on")
run("switch " + (keepScreen ? "screen" : "sleep"))
refreshDelay.restart()
}
function changeTimer(minutes) {
@@ -177,6 +222,7 @@ PlasmoidItem {
return
}
timerExpiring = false
beginPending("on")
if (minutes > 0) {
remainingSec = minutes * 60
run("on " + startMode + " " + minutes)
@@ -184,15 +230,15 @@ PlasmoidItem {
remainingSec = 0
run("switch " + startMode)
}
refreshDelay.restart()
}
// ---------------------------------------------------------------- backend
// Stato: polling ogni 2 secondi (systemctl --user is-active).
// Stato: polling periodico (systemctl --user is-active); la conferma di
// un'operazione arriva però dall'output del comando stesso (~70 ms).
P5Support.DataSource {
id: statusSource
engine: "executable"
interval: 2000
interval: 3000
connectedSources: [root.statusCommand]
onNewData: function(sourceName, data) {
root.applyStatus(data ? data["stdout"] : "")
@@ -206,11 +252,16 @@ PlasmoidItem {
connectedSources: []
onNewData: function(sourceName, data) {
const output = data ? String(data["stdout"] || "") : ""
root.lastError = output.indexOf("error=") === 0 ? output : ""
root.lastError = output.indexOf("error=") === 0 ? output.trim() : ""
if (root.lastError !== "") {
console.warn("enne2-caffeine:", root.lastError)
root.clearPending()
}
if (output.indexOf("state=") !== -1) {
root.finishPending(output)
} else {
root.refresh()
}
root.refresh()
}
}
@@ -226,11 +277,15 @@ PlasmoidItem {
}
}
// Il backend non ha confermato entro il timeout: torna a fidarsi del polling.
Timer {
id: refreshDelay
interval: 300
id: pendingTimeout
interval: 6000
repeat: false
onTriggered: root.refresh()
onTriggered: {
root.clearPending()
root.refresh()
}
}
// Countdown del timer di disattivazione automatica.
@@ -277,6 +332,15 @@ PlasmoidItem {
implicitWidth: Kirigami.Units.iconSizes.smallMedium
implicitHeight: Kirigami.Units.iconSizes.smallMedium
// operazione in corso: pulsazione dell'icona come feedback
SequentialAnimation on opacity {
running: root.pending
loops: Animation.Infinite
NumberAnimation { to: 0.4; duration: 220; easing.type: Easing.InOutQuad }
NumberAnimation { to: 1.0; duration: 220; easing.type: Easing.InOutQuad }
onStopped: compactIcon.opacity = 1.0
}
// Kirigami.Icon può assorbire i clic (bug KDE 518024):
// area cliccabile direttamente sopra l'icona.
MouseArea {