1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
|
#!/bin/bash
#
# Tatsumato - Tatsumoto's Pomodoro timer.
#
# Copyright (C) 2020-2023 Ren Tatsumoto
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
readonly lock_timeout=10s
readonly lock_color=111111
readonly ankiconnect_url=127.0.0.1:8765
while getopts 'a A p H v h s i d t: b: l: L: k:' flag; do
case $flag in
H) human=false ;;
v) verbose=true ;;
h) disphelp=true ;;
s) silent=true ;;
i) use_i3lock=true ;;
p) control_player=true ;;
a) control_anki=true ;;
A) focus_anki=true ;;
d) dmenu_nag=true ;;
t) pomtime=${OPTARG} ;;
b) brktime=${OPTARG} ;;
l) lngbrkt=${OPTARG} ;;
L) longbrk=${OPTARG} ;;
k) endtime=${OPTARG} ;;
*) echo "Unknown argument ${flag}" && exit 1 ;;
esac
done
readonly human=${human:-true}
readonly verbose=${verbose:-false}
readonly disphelp=${disphelp:-false}
readonly silent=${silent:-false}
readonly use_i3lock=${use_i3lock:-false}
readonly control_player=${control_player:-false}
readonly control_anki=${control_anki:-false}
readonly focus_anki=${focus_anki:-false}
readonly dmenu_nag=${dmenu_nag:-false}
readonly pomtime=${pomtime:-25}
readonly brktime=${brktime:-5}
readonly lngbrkt=${lngbrkt:-10}
readonly longbrk=${longbrk:-3}
readonly endtime=${endtime:-0}
show_help() {
local -r prog=$(basename -- "$0")
cat <<-END
$prog - Pomodoro productivity shell script.
Options:
END
column -N key,description -W description -d -t -s'|' <<-'EOF'
-t [minutes]|Set the amount of minutes a pomodoro lasts. Default 25.
-b [minutes]|Set the amount of minutes a short break lasts. Default 5.
-l [minutes]|Set the amount of minutes a long break lasts. Default 10.
-L [number]|Set the amount of pomodoros before triggering a long break. Default 3.
-k [number]|Set the amount of pomodoros before ending the script. 0 means the script will run until stopped by the user. Default 0.
-H|Disable human mode.
-h|Display this help text and exit.
-v|Enable verbose mode. Echo the pomodoro status to your terminal. Off by default.
-s|Silent mode. Notification sound is not played.
-i|Run i3lock when a pomodoro is over.
-p|Pause/unpause mpv between breaks.
-a|Control Anki. Close Anki's review screen before a break starts.
-A|Focus Anki. Focus Anki's window after a break ends.
-d|Show a dmenu (or rofi) dialog after each pomodoro.
EOF
cat <<-END
Notes:
Create a shell alias with the options you like. For example:
alias pom="$prog -a -i -p -t 13 -b 2 -l 3"
END
}
assert_installed() {
local x
for x; do
if ! command -v "$x" >/dev/null 2>&1; then
echo "Error: $x is not installed." >&2
exit 1
fi
done
}
play_bell() {
assert_installed paplay
paplay /usr/share/sounds/freedesktop/stereo/complete.oga &
}
notify() {
assert_installed notify-send
$verbose && echo "$@"
notify-send "Pomodoro" "$*" >/dev/null 2>&1 &
$silent || play_bell
}
do_pomodoro() {
local -r time=$1 mode=$2
for ((i = time; i > 0; i--)); do
$human && printf -- '\r'
printf -- '%im left of %s ' "$i" "${mode,,}"
$human || printf -- '\n'
sleep 1m
done
}
close_review_window() {
assert_installed curl
# If you use Pomodoro while doing your Anki reps,
# this function will close the review window before a break starts.
# Requires AnkiConnect to work.
curl \
-fsS "$ankiconnect_url" \
-X POST \
-d '{ "action": "guiDeckBrowser", "version": 6 }' >/dev/null &
}
focus_window() {
assert_installed i3-msg
i3-msg -q "[class=\"$1\"] focus" || true
}
setallmpv() {
# Sends pause/play commands to mpv.
# Requires the mpvSockets user-script for mpv.
assert_installed socat i3-msg
local cmd='{ "command": ["set_property", "pause", <pause>] }'
if [[ $1 == play ]]; then
local -r cmd=${cmd/<pause>/false}
focus_window mpv
else
local -r cmd=${cmd/<pause>/true}
fi
for i in /tmp/mpvSockets/*; do
echo "$cmd" | socat - "$i"
done
}
resume_player() {
setallmpv play >/dev/null 2>&1
}
pause_player() {
setallmpv pause >/dev/null 2>&1
}
lock_screen() {
assert_installed i3lock
(
sleep "$lock_timeout"
i3lock --color="$lock_color"
) &
}
unlock_screen() {
killall i3lock 2>/dev/null || true
}
call_dmenu() {
if command -v rofi >/dev/null; then
rofi -dmenu "$@"
else
assert_installed dmenu
dmenu "$@"
fi
}
dmenu_report() {
cat <<-EOF | call_dmenu -l 30 -i -p "Finished 🍠$pomcount pomodoros."
☕ start ${mode,,} ($time min)
🚪 exit
EOF
}
dmenu_nagscreen() {
$silent || play_bell
case $(dmenu_report) in
*start*) return ;;
*exit) exit ;;
*) echo "Invalid command or no command provided." && exit 1 ;;
esac
}
main() {
$disphelp && show_help && exit
local endcount=0 pomcount=0 mode=Pomodoro time=$pomtime
while true; do
notify "${mode^} will last for $time minute(s)."
do_pomodoro "$time" "$mode"
if [[ ${mode,,} == "pomodoro" ]]; then
# after pomodoro
pomcount=$((pomcount + 1)) endcount=$((endcount + 1))
if [[ $endtime -gt 0 ]] && [[ $endcount -ge $endtime ]]; then
echo "Finished."
return
fi
if [[ $pomcount -eq $longbrk ]]; then
mode="Long break" time=$lngbrkt
else
mode="Short break" time=$brktime
fi
$dmenu_nag && dmenu_nagscreen
$control_player && resume_player
$use_i3lock && lock_screen
$control_anki && close_review_window
else
# after break
mode="Pomodoro" time=$pomtime
$use_i3lock && unlock_screen
$control_player && pause_player
$dmenu_nag && dmenu_nagscreen
$focus_anki && focus_window Anki
fi
if [[ ${mode,,} == "long break" ]]; then
pomcount=0
fi
done
}
main
|