aboutsummaryrefslogtreecommitdiffstats
path: root/breaktimer.sh
diff options
context:
space:
mode:
Diffstat (limited to 'breaktimer.sh')
-rwxr-xr-xbreaktimer.sh234
1 files changed, 234 insertions, 0 deletions
diff --git a/breaktimer.sh b/breaktimer.sh
new file mode 100755
index 0000000..483f0b4
--- /dev/null
+++ b/breaktimer.sh
@@ -0,0 +1,234 @@
+#!/bin/bash
+#
+# breaktimer.sh - reminder di micro-pause per spezzare le sessioni al PC
+#
+# Macchina a stati: working -> (notifica) -> breaking/longbreak -> working
+# La pausa NON erode il tempo di lavoro. Pausa manuale congela il countdown.
+#
+# Modello tempo: si salva REMAINING (secondi rimanenti della fase corrente),
+# decrementato a ogni tick solo quando si lavora/conta. Cosi' la pausa manuale
+# congela davvero e il countdown Waybar e' sempre coerente.
+#
+# Uso: breaktimer.sh start|stop|restart|pause|resume|toggle|status
+# breaktimer.sh run (interno)
+#
+# Dipendenze: dunst (notify-send), pipewire (pw-play / paplay), coreutils.
+
+# ---------- Configurazione ----------
+MICRO_MIN=30
+BREAK_MIN=3
+LONG_MIN=10
+LONG_EVERY=4
+WORK_START="09:00"
+WORK_STOP="18:30"
+URGENCY_MICRO="normal"
+URGENCY_LONG="critical"
+
+SOUND_DIR="$HOME/.local/share/sounds/modern-minimal-ui-sounds/stereo"
+SOUND_MICRO=""
+SOUND_LONG=""
+SOUND_BACK=""
+SYS_SOUND_MICRO="$SOUND_DIR/message-new-instant.oga"
+SYS_SOUND_LONG="$SOUND_DIR/alarm-clock-elapsed.oga"
+SYS_SOUND_BACK="$SOUND_DIR/service-login.oga"
+# ------------------------------------
+
+RUNTIME="${XDG_RUNTIME_DIR:-/tmp}"
+PID_FILE="$RUNTIME/breaktimer.pid"
+STATE_FILE="$RUNTIME/breaktimer.state" # running|paused
+PHASE_FILE="$RUNTIME/breaktimer.phase" # working|breaking|longbreak
+REMAIN_FILE="$RUNTIME/breaktimer.remain" # secondi rimanenti della fase
+TICK=5
+
+now_epoch() { date +%s; }
+hm_to_epoch() { date -d "$(date +%F) $1" +%s; }
+
+in_work_window() {
+ local n start stop
+ n=$(now_epoch); start=$(hm_to_epoch "$WORK_START"); stop=$(hm_to_epoch "$WORK_STOP")
+ [ "$n" -ge "$start" ] && [ "$n" -lt "$stop" ]
+}
+
+is_running() {
+ [ -f "$PID_FILE" ] && kill -0 "$(cat "$PID_FILE" 2>/dev/null)" 2>/dev/null
+}
+
+play_sound() {
+ local file="$1"
+ { [ -z "$file" ] || [ ! -f "$file" ]; } && return 0
+ if command -v pw-play >/dev/null 2>&1; then pw-play "$file" >/dev/null 2>&1 &
+ elif command -v paplay >/dev/null 2>&1; then paplay "$file" >/dev/null 2>&1 &
+ fi
+}
+sound_for_micro() {
+ if [ -n "$SOUND_MICRO" ] && [ -f "$SOUND_MICRO" ]; then play_sound "$SOUND_MICRO"
+ else play_sound "$SYS_SOUND_MICRO"; fi
+}
+sound_for_long() {
+ if [ -n "$SOUND_LONG" ] && [ -f "$SOUND_LONG" ]; then play_sound "$SOUND_LONG"
+ else play_sound "$SYS_SOUND_LONG"; fi
+}
+sound_for_back() {
+ if [ -n "$SOUND_BACK" ] && [ -f "$SOUND_BACK" ]; then play_sound "$SOUND_BACK"
+ else play_sound "$SYS_SOUND_BACK"; fi
+}
+
+notify_micro() {
+ notify-send -u "$URGENCY_MICRO" -a "breaktimer" \
+ "🚶 Micro-pausa ($BREAK_MIN min)" \
+ "Alzati e muoviti: camminata, squat, mobilita'. Occhi: guarda lontano 20s."
+ sound_for_micro
+}
+notify_long() {
+ notify-send -u "$URGENCY_LONG" -a "breaktimer" \
+ "⏸️ Pausa lunga ($LONG_MIN min)" \
+ "Stacca davvero. Cammina, bevi, allunga la schiena."
+ sound_for_long
+}
+notify_back() {
+ notify-send -u "low" -a "breaktimer" \
+ "▶️ Si riparte" "Pausa finita, blocco di lavoro da $MICRO_MIN min."
+ sound_for_back
+}
+
+# ---------- Loop principale ----------
+run_loop() {
+ # singleton: rifiuta se un altro daemon vivo possiede gia' il PID file.
+ # Evita due loop che si calpestano gli stessi file di stato.
+ local owner
+ owner="$(cat "$PID_FILE" 2>/dev/null)"
+ if [ -n "$owner" ] && [ "$owner" != "$$" ] && kill -0 "$owner" 2>/dev/null; then
+ echo "breaktimer: gia' in esecuzione (PID $owner), run annullato" >&2
+ exit 1
+ fi
+ echo "$$" > "$PID_FILE"
+ echo "running" > "$STATE_FILE"
+ echo "working" > "$PHASE_FILE"
+ local count=0
+ local work_sec=$((MICRO_MIN * 60))
+ local break_sec=$((BREAK_MIN * 60))
+ local long_sec=$((LONG_MIN * 60))
+ local remaining=$work_sec
+ echo "$remaining" > "$REMAIN_FILE"
+
+ # cleanup: NON tocca PID_FILE. Un vecchio daemon (ucciso da restart) puo'
+ # eseguire il trap in ritardo, dopo che il nuovo ha gia' scritto il suo PID:
+ # cancellare il file qui ucciderebbe il riferimento al nuovo daemon.
+ # is_running usa kill -0, quindi un PID file stantio e' innocuo; lo
+ # sovrascrive il prossimo start.
+ cleanup() {
+ echo "stopped" > "$STATE_FILE"; echo "stopped" > "$PHASE_FILE"
+ rm -f "$REMAIN_FILE"
+ exit 0
+ }
+ trap cleanup TERM INT
+
+ while true; do
+ sleep "$TICK"
+
+ # pausa manuale: NON decrementare, lascia remaining congelato
+ [ "$(cat "$STATE_FILE" 2>/dev/null)" = "paused" ] && continue
+
+ local phase="$(cat "$PHASE_FILE" 2>/dev/null)"
+
+ # fuori finestra oraria, solo in 'working': congela
+ if [ "$phase" = "working" ] && ! in_work_window; then
+ continue
+ fi
+
+ # decrementa il tempo rimanente della fase
+ remaining=$(( remaining - TICK ))
+
+ if [ "$remaining" -gt 0 ]; then
+ echo "$remaining" > "$REMAIN_FILE"
+ continue
+ fi
+
+ # fase scaduta: transizione
+ case "$phase" in
+ working)
+ count=$((count + 1))
+ if [ "$((count % LONG_EVERY))" -eq 0 ]; then
+ notify_long
+ echo "longbreak" > "$PHASE_FILE"
+ remaining=$long_sec
+ else
+ notify_micro
+ echo "breaking" > "$PHASE_FILE"
+ remaining=$break_sec
+ fi
+ ;;
+ breaking|longbreak)
+ notify_back
+ echo "working" > "$PHASE_FILE"
+ remaining=$work_sec
+ ;;
+ *)
+ echo "working" > "$PHASE_FILE"
+ remaining=$work_sec
+ ;;
+ esac
+ echo "$remaining" > "$REMAIN_FILE"
+ done
+}
+
+# ---------- Gestione daemon ----------
+start_daemon() {
+ if is_running; then
+ echo "breaktimer: gia' in esecuzione (PID $(cat "$PID_FILE"))"
+ return 1
+ fi
+ # il loop scrive da solo il proprio PID ($$) dopo il guard singleton:
+ # setsid fa fork, quindi $! qui non e' il PID reale del loop.
+ setsid "$0" run >/dev/null 2>&1 < /dev/null &
+ # attendi che il loop pubblichi il suo PID
+ for _ in 1 2 3 4 5 6 7 8 9 10; do
+ is_running && break
+ sleep 0.2
+ done
+ if is_running; then
+ echo "breaktimer: avviato in background (PID $(cat "$PID_FILE"))"
+ else
+ echo "breaktimer: avvio fallito" >&2
+ return 1
+ fi
+}
+stop_daemon() {
+ if is_running; then
+ kill -TERM "$(cat "$PID_FILE")" 2>/dev/null
+ rm -f "$PID_FILE" "$REMAIN_FILE"
+ echo "stopped" > "$STATE_FILE"; echo "stopped" > "$PHASE_FILE"
+ echo "breaktimer: fermato"
+ else
+ echo "stopped" > "$STATE_FILE"; echo "stopped" > "$PHASE_FILE"
+ rm -f "$PID_FILE" "$REMAIN_FILE"
+ echo "breaktimer: non era in esecuzione"
+ fi
+}
+
+case "$1" in
+ start) start_daemon ;;
+ stop) stop_daemon ;;
+ restart) stop_daemon; sleep 1; start_daemon ;;
+ run) run_loop ;;
+ pause) echo "paused" > "$STATE_FILE"; echo "breaktimer: sospeso" ;;
+ resume) echo "running" > "$STATE_FILE"; echo "breaktimer: ripreso" ;;
+ toggle)
+ if [ "$(cat "$STATE_FILE" 2>/dev/null)" = "paused" ]; then
+ echo "running" > "$STATE_FILE"; echo "breaktimer: ripreso"
+ else
+ echo "paused" > "$STATE_FILE"; echo "breaktimer: sospeso"
+ fi
+ ;;
+ status)
+ if is_running; then
+ echo "breaktimer: attivo (PID $(cat "$PID_FILE")), stato: $(cat "$STATE_FILE" 2>/dev/null), fase: $(cat "$PHASE_FILE" 2>/dev/null), rimane: $(cat "$REMAIN_FILE" 2>/dev/null)s"
+ else
+ echo "breaktimer: non in esecuzione"
+ fi
+ ;;
+ *)
+ echo "uso: $0 [start|stop|restart|pause|resume|toggle|status]"
+ exit 1
+ ;;
+esac