Caffeine per KDE Plasma 6: widget di pannello (inibizione schermo + sospensione)

This commit is contained in:
enne2
2026-09-25 16:13:26 +02:00
commit 74ca562efe
13 changed files with 852 additions and 0 deletions
+6
View File
@@ -0,0 +1,6 @@
*.qmlc
*.jsc
*~
build/
package/
*.backup-*
+21
View File
@@ -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.
+88
View File
@@ -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.
![tazza piena = attivo, tazza vuota = inattivo](contents/icons/caffeine-on.svg)
## 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).
+50
View File
@@ -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
+23
View File
@@ -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>
+8
View File
@@ -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

+11
View File
@@ -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

+179
View File
@@ -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
+400
View File
@@ -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
View File
@@ -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
+19
View File
@@ -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"
}
+9
View File
@@ -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
+9
View File
@@ -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