mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-11-10 19:07:04 +01:00
Left: focus_project.open; middle: tutor only; right: daily-limits. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WzxdWXdzckyBH7fEbPywpr
100 lines
3.8 KiB
Bash
Executable File
100 lines
3.8 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
# ============================================================================
|
|
# Active focus project for i3blocks: `🎯 HackerRank prep 45/120` (minutes done
|
|
# today / daily target). Green once the daily target is reached, yellow while
|
|
# short of it, hidden when no project is active.
|
|
#
|
|
# Renders the `focus` field of daily-limits' cache ($XDG_RUNTIME_DIR/
|
|
# daily-limits.json, rewritten every 60 s by its user timer) and owns no
|
|
# focus logic: focus_project.status() decides. The cache is re-parsed with jq
|
|
# only when its mtime changes, so a steady-state tick forks nothing.
|
|
#
|
|
# Refresh is event-driven: the block has `signal=11` in the i3blocks config and
|
|
# daily-limits sends `pkill -RTMIN+11 -x i3blocks` the moment the focus status
|
|
# changes (the 30 s interval is only the fallback).
|
|
#
|
|
# A missing, garbage or >180 s old cache hides the block: the neighbouring
|
|
# gaming_budget block already says "⏱?" for the same cache, and old minutes
|
|
# must never be shown.
|
|
#
|
|
# Clicks (the block says what to do, so a click goes where the work is):
|
|
# left `python3 -m focus_project.open`: the tutor focus screen (starts the
|
|
# focus timer if it is not running) and the project's work URL
|
|
# middle the same with --no-site (the focus screen only)
|
|
# right `daily-limits --gui`, the day's detail
|
|
# ============================================================================
|
|
|
|
set -euo pipefail
|
|
|
|
readonly LIMITS_CACHE="${DAILY_LIMITS_CACHE:-${XDG_RUNTIME_DIR:-/run/user/$UID}/daily-limits.json}"
|
|
readonly RENDER_CACHE="${DAILY_LIMITS_RENDER_CACHE:-${XDG_RUNTIME_DIR:-/run/user/$UID}/i3blocks-focus-project.render}"
|
|
readonly STALE_AFTER_SECONDS=180
|
|
|
|
# Dracula-ish palette, matching gaming_budget.sh.
|
|
readonly YELLOW='#F1FA8C'
|
|
readonly GREEN='#50FA7B'
|
|
|
|
emit() {
|
|
# full_text, short_text, color: the three lines i3blocks reads.
|
|
printf '%s\n%s\n%s\n' "$1" "$2" "$3"
|
|
}
|
|
|
|
# i3blocks' PATH has no ~/.local/bin, where daily-limits' install.sh puts it.
|
|
readonly DAILY_LIMITS_BIN=${DAILY_LIMITS_BIN:-$HOME/.local/bin/daily-limits}
|
|
# The system python: focus_project is installed in its user site (install.sh).
|
|
readonly FOCUS_OPEN_PYTHON=${FOCUS_OPEN_PYTHON:-/usr/bin/python3}
|
|
case ${BLOCK_BUTTON:-0} in
|
|
1) setsid -f "$FOCUS_OPEN_PYTHON" -m focus_project.open >/dev/null 2>&1 ;;
|
|
2) setsid -f "$FOCUS_OPEN_PYTHON" -m focus_project.open --no-site >/dev/null 2>&1 ;;
|
|
3) [[ -x $DAILY_LIMITS_BIN ]] && setsid -f "$DAILY_LIMITS_BIN" --gui >/dev/null 2>&1 ;;
|
|
esac
|
|
|
|
hide() {
|
|
emit '' '' "$YELLOW"
|
|
exit 0
|
|
}
|
|
|
|
# One jq pass renders four lines: generated_at, full text, short text, color.
|
|
# The name is untrusted display text: control characters become spaces so it
|
|
# can never add lines to the block's output.
|
|
render_focus() {
|
|
local rendered
|
|
rendered=$(jq -r --arg green "$GREEN" --arg yellow "$YELLOW" '
|
|
(.generated_at | if type == "number" then floor else "none" end),
|
|
(.focus | if type == "object" and (.project | type) == "string" then
|
|
(.minutes // 0 | floor) as $m
|
|
| (.daily_minutes // 0 | floor) as $d
|
|
| ((.name // .project) | tostring | gsub("[\u0001-\u001f\u007f]"; " ")) as $name
|
|
| "🎯 \($name) \($m)/\($d)", "🎯 \($m)/\($d)",
|
|
(if $d > 0 and $m >= $d then $green else $yellow end)
|
|
else "", "", $yellow end)' "$LIMITS_CACHE" 2>/dev/null) || return 1
|
|
printf '%s\n' "$rendered" >"$RENDER_CACHE"
|
|
}
|
|
|
|
[[ -r $LIMITS_CACHE ]] || hide
|
|
if [[ $LIMITS_CACHE -nt $RENDER_CACHE ]]; then
|
|
command -v jq >/dev/null 2>&1 || hide
|
|
render_focus || hide
|
|
fi
|
|
|
|
{
|
|
read -r generated_at
|
|
read -r full_text
|
|
read -r short_text
|
|
read -r color
|
|
} <"$RENDER_CACHE" || hide
|
|
|
|
if [[ -n ${NOW_EPOCH:-} ]]; then
|
|
now=$NOW_EPOCH
|
|
else
|
|
printf -v now '%(%s)T' -1
|
|
fi
|
|
[[ $generated_at =~ ^-?[0-9]+$ ]] || hide
|
|
age=$((now - generated_at))
|
|
if ((age > STALE_AFTER_SECONDS || age < -STALE_AFTER_SECONDS)); then
|
|
hide
|
|
fi
|
|
|
|
emit "$full_text" "$short_text" "$color"
|