9

I tried alarm-clock-applet but I have to set up several countdowns and I cannot launch them together as a group. It is however a useful app.

I would an app that lets me use a set of timers with alarms. For example 30s+45s+60s+45s+120s. I wish to launch the entire set or even better yet, repeat the set X number of times.

Is this possible maybe with a script? (An app would be better).

Scorpion
  • 374
  • Could you clarify a bit? You'd call the script with parameters 30 45 60 45 120 cycle=5. Then an alarm sounds at 30 seconds, 75 seconds, 135 seconds, 180 seconds and 300 seconds? Then the cycle repeats 4 more times? Does a pop-up message appear simultaneously with the alarm? Do you already have an alarm file or will you be using system sound alert? Instead of parameters on command line do you envision a GUI panel with up to 10 timers + a cycle count? – WinEunuuchs2Unix May 23 '18 at 22:54
  • Yes an alarm sounds at 30 seconds, 75 seconds, 135 seconds, 180 seconds and 300 seconds, they are sequential. Then the cycle repeats x more times. A pop-up windows isn' t needed. I already have an alarm file. Yes a GUI would be better. – Scorpion Jun 05 '18 at 08:51

1 Answers1

14

Edit April 24, 2023 - Tim-ta (Timed Tasks)

Multi-timer described below has been superseded by Tim-ta which runs in your web browser on any device.

Multi-timer

multi-timer is a bash script that works in all Ubuntu versions. It also works in Windows 10 with Ubuntu Desktop installed. yad is used for GUI setup and countdown progress bars.

peek wash cycle.png

Features

  • Retains configuration between uses.
  • Up to 19 timers run sequentially in a set.
  • Progress bar for each timer.
  • Set of timers can be run multiple times.
  • Progress bar for set.
  • Progress bar for all sets.
  • Optional prompt to start each timer and/or set.
  • Optional pop-up message when each timer and/or set ends.
  • Optional alarm when each timer and/or set ends.
  • Optional lock screen when each timer OR set OR all sets end.
  • Optional interface to Sysmonitor Indicator so Systray shows countdowns.
  • Optional close progress bar display when all sets of timers end.

Requires yad package sudo apt install yad

Notebook Configuration Tab

yad (Yet Another Dialog) has a feature that allows multi-timer fields to be divided between two separate panels accessed within one window via notebook tabs.

The Configuration Tab lets you:

  • define time units in Seconds/Minutes
  • define how many times to run set of timers.
  • link to sound file used for timer alarms
  • option to lock screen at end of each timer, set, or all sets
  • additional options as illustrated below

multi-timer configuration.png

Notebook Timers Tab

The Timers Tab allows you to:

  • Define alias for each timer rather than Timer 1, Timer 2, etc.
  • Set the number of seconds or minutes each timer runs.

multi-timer timers.png

Progress Bar Display

As multi-timer counts down progress bars are used for:

  • each active timer (zero duration timers are not shown)
  • each set (if two or more active timers in set)
  • all sets (if two or more sets run)

multi-timer progress bars.gif

Bash Script

Copy and paste the bash script below to filename multi-timer. I suggest the directory /home/<your_user_name>/bin/. Afterwards mark the script as executable using:

chmod a+x /home/<your_user_name>/bin/multi-timer

Changing number of Timers

At line number 78, 79 and 80 you will see this:

# No. of timers default is 17 for 768 line screen and TMR_DURATION_NDX is 30
TMR_DURATION_NDX=30 # Set to 28 for 800x600 screen, 32 for 1920x1080 screen
MAX_TIMERS=17       # Set to 15 for 800x600 screen, 19 for 1920x1080 screen

Although 19 timers are supported, they might only fit on a 1920x1080 screen. About 17 (the default setting) will fit on the average 1024x768 screen. If you have Super VGA with 800x600 resolution you might only get 13 to 15 timers.

You must change the values on lines 85 & 86 at the same time:

Bash field name    ----------- Values to assign ---------
TMR_DURATION_NDX   23  24  25  26  27  28  29  30  31  32
MAX_TIMERS         10  11  12  13  14  15  16  17  18  19

eg If you want a maximum of 12 timers, set TMR_DURATION_NDX=25 and MAX_TIMERS=12.

After changing the Index and Maximum save the multi-timer file. If you have already run the program once a configuration file may have been created and it will have to be deleted. Use this command to remove the old configuration file:

rm ~/.multi-timer

Note that ~/ is a short cut to your home directory, ie /home/your_user_name/.

Requirements for multi-timer

Multi-timer requires the following packages to be installed:

sudo apt install yad
sudo apt install libnotify-bin

yad is required for the windows and libnotify-bin is required for optional pop up message when a timer ends.

Bash code for multi-timer

#!/bin/bash

NAME: multi-timer

DESC: Multiple timers countdown with alarm.

https://askubuntu.com/questions/1039357

/a-timer-to-set-up-different-alarms-simultaneosly

DATE: May 31, 2018. Modified Nov 21, 2021.

UPDT: 2018-06-07 Add new index for check box: "automatically close

progress bar display when all Sets finish". Remove '~/.multi-timer'

to delete configuration file before running update.

2018-06-19 Set fWindows flag to TRUE/FALSE instead of true/false.

2018-11-04 Early exit call Cleanup ()? For some reason sysmon-indicator

still displays: '~/.lock-screen-timer-remaining'???

Alarm only sounding for first timer but pop-up appears for all???

See changes to /etc/pulse/default.pa below:

Automatically suspend sinks/sources that become idle for too long

Nov 4, 2018 - causes 3 to 5 second delay if last sound was 30 seconds ago.

So you get sound delayed unpausing video or miss multi-timer alerts. Add

load-module module-suspend-on-idle

Although this fixes delay when switching between sound sources,

still change default alarm to sound file over 5 seconds long.

2018-12-05 HDD LED indicator flashing constantly while progress bars

are updated / program sleeps. Make LastWakeMicroSeconds dependant

on lost time log enabled only.

2019-03-23 Change default number of timers from 17 to 10 which suits

Windows 10 better and is more realistic number for most users.

Change grep arguments for "fWindows10" flag.

Put "Linux" or "Windows 10" as title prefix.

Set Windows 10 Sound file default to C:\Windows\media\Ring05.wav.

Error when notify-send command installed (minimal Windows 10).

Override "/mnt/c/Windows..." to "C:\Windows..." when invoked.

2019-05-22 Suppress Transient Parent messages

2019-08-07 When quiting timer progress display spinning pizza remains

in application indicator driven by indicator system monnitor.

2020-09-10 Add Geometry option initally limited to parameter 1.

2021-01-31 Use /run/user/1000 to prevent hard disk activity light flash

2021-09-07 Change KEY="12345" to random number. Allows restarting.

2021-09-13 Use /run/user/$UID for multi-user systems.

2021-11-21 Remove dbus-send and use "loginctl lock-session" which is

a more universal way of locking screen in Linux.

NOTE: The following naming conventions are used:

Functions must be defined above point where they are called.

Yad style TRUE/FALSE instead of Bash true/false convention.

Variables beginning with- s is string

- i is integer

- f is TRUE/FALSE

- a is array

- cb is combobox

Must have the yad package.

command -v yad >/dev/null 2>&1 || { echo >&2
"yad package required but it is not installed. Aborting.";
exit 99; }

Must have notify-send from libnotify-bin package

command -v notify-send >/dev/null 2>&1 || { echo >&2
"libnotify-bin package required but it is not installed. Aborting.";
exit 99; }

Running under WSL (Windows Subsystem for Linux)?

if grep -qE "(Microsoft|WSL)" /proc/version &> /dev/null ; then fWindows10=TRUE DefaultSound="C:\Windows\media\Ring05.wav" TitlePrefix="Windows 10" else fWindows10=FALSE DefaultSound="/usr/share/sounds/freedesktop/stereo/alarm-clock-elapsed.oga" TitlePrefix="Linux" fi

On Skylake i7-6700HQ .467 seconds lost over 1200 second timer due to display.

if [[ "$1" == "-l" ]] || [[ "$1" == "--log-lost-time" ]] ; then fLog=TRUE else fLog=FALSE fi

Geometry e.g.: -g=4365+76 sets to X=4356 and Y=76

if [[ "$1" == -g=* ]] || [[ "$1" == --geometry=* ]] ; then XY="${1#=}" # Grab =X+Y side WindowX="${XY%+}" # Grab X+ side WindowY="${XY#*+}" # Grab +Y side GEOMETRY="--geometry=0x0+$WindowX+$WindowY" else GEOMETRY="--center" fi

Key for tying Notebook tabs together. Cannot be same key twice.

KEY=$(echo $[($RANDOM % ($[10000 - 32000] + 1)) + 10000] )

OIFS=$IFS; # Save current IFS (Input File Separator) IFS="|"; # Yad fields and Bash array indices separated by | aMulti=() # Main array for storing configuration

Temporary files for Notebook output

res1=$(mktemp --tmpdir=/run/user/$UID iface1.XXXXXXXX) # Notebook Configuraion res2=$(mktemp --tmpdir=/run/user/$UID iface2.XXXXXXXX) # Notebook Timers

Suppress Transient parent error spam

exec 2> >(grep -v 'GtkDialog mapped without a transient parent' >&2)

Cleanup () { rm -f "$res1" "$res2" # Remove temporary files IFS=$OIFS; # Retore Input File Separator if [[ -f ~/.lock-screen-timer-remaining ]]; then # Remove Sysmonitor Indicator interface file. rm -f ~/.lock-screen-timer-remaining fi } # Cleanup

Comboboxes, constants and Index offsets

cbTimeUnits="Seconds!Minutes" cbLockScreen="Never!Each timer end!Each set end!All sets end"

TIME_UNIT_NDX=0 SET_COUNT_NDX=1 PROGRESS_INTERVAL_NDX=2 ALARM_FILENAME_NDX=3 LOCK_SCREEN_NDX=4 PROMPT_BEFORE_TIMER_NDX=5 END_TIMER_MESSAGE_NDX=6 END_TIMER_ALARM_NDX=7 PROMPT_BEFORE_SET_NDX=8 END_SET_MESSAGE_NDX=9 END_SET_ALARM_NDX=10 SYSMONITOR_INDICATOR_NDX=11 CLOSE_PROGRAM_AT_END_NDX=12 TMR_ALIAS_NDX=13

No. of timers default is 17 for 768 line screen and TMR_DURATION_NDX is 30

TMR_DURATION_NDX=23 # Set to 28 for 800x600 screen, 32 for 1920x1080 screen MAX_TIMERS=10 # Set to 15 for 800x600 screen, 19 for 1920x1080 screen

ReadConfiguration () {

if [[ -s ~/.multi-timer ]]; then
    read -ra aMulti &lt; ~/.multi-timer
    for (( i=0; i&lt;MAX_TIMERS; i++ )); do
        aAlias[i]=&quot;${aMulti[ i+TMR_ALIAS_NDX ]}&quot;
        aDuration[i]=&quot;${aMulti[ i+TMR_DURATION_NDX ]}&quot;
    done
    # Set Combobox default with ^ prefix
    Str=&quot;${aMulti[TIME_UNIT_NDX]}&quot;
    cbTimeUnits=&quot;${cbTimeUnits/$Str/\^$Str}&quot;
    Str=&quot;${aMulti[LOCK_SCREEN_NDX]}&quot;
    cbLockScreen=&quot;${cbLockScreen/$Str/\^$Str}&quot;
else
    # Create new file
    aMulti[TIME_UNIT_NDX]=&quot;Seconds&quot;
    aMulti[SET_COUNT_NDX]=1
    aMulti[PROGRESS_INTERVAL_NDX]=1
    aMulti[ALARM_FILENAME_NDX]=&quot;$DefaultSound&quot;
    aMulti[LOCK_SCREEN_NDX]=&quot;Never&quot;
    aMulti[PROMPT_BEFORE_TIMER_NDX]=&quot;FALSE&quot;
    aMulti[END_TIMER_MESSAGE_NDX]=&quot;FALSE&quot;
    aMulti[END_TIMER_ALARM_NDX]=&quot;TRUE&quot;
    aMulti[PROMPT_BEFORE_SET_NDX]=&quot;FALSE&quot;
    aMulti[END_SET_MESSAGE_NDX]=&quot;FALSE&quot;
    aMulti[END_SET_ALARM_NDX]=&quot;FALSE&quot;
    aMulti[SYSMONITOR_INDICATOR_NDX]=&quot;FALSE&quot;
    aMulti[CLOSE_PROGRAM_AT_END_NDX]=&quot;FALSE&quot;
    aAlias=(&quot;Timer 1&quot; &quot;Timer 2&quot; &quot;Timer 3&quot; &quot;Timer 4&quot; &quot;Timer 5&quot; \
            &quot;Timer 6&quot; &quot;Timer 7&quot; &quot;Timer 8&quot; &quot;Timer 9&quot; &quot;Timer 10&quot; \
            &quot;Timer 11&quot; &quot;Timer 12&quot; &quot;Timer 13&quot; &quot;Timer 14&quot; &quot;Timer 15&quot; \
            &quot;Timer 16&quot; &quot;Timer 17&quot; &quot;Timer 18&quot; &quot;Timer 19&quot;)
    aDuration=(0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0)
fi

} # ReadConfiguration

BuildTimerPage () {

aTimerPage=()
for ((i=0; i&lt;MAX_TIMERS; i++)); do
    b1=$(( i + 1 ))
    aTimerPage+=(&quot;--field=Timer $b1 Alias:&quot;)
    aTimerPage+=(&quot;${aAlias[i]}&quot;)
done
for ((i=0; i&lt;MAX_TIMERS; i++)); do
    aTimerPage+=(&quot;--field=Duration::NUM&quot;)
    aTimerPage+=(&quot;${aDuration[i]}&quot;)
done

}

GetParameters () {

# configuration notebook page
yad --plug=$KEY --tabnum=1 --form \
--field=&quot;Timer duration units::CB&quot; &quot;$cbTimeUnits&quot; \
--field=&quot;Number of times to run set (all timers)::NUM&quot; \
        &quot;${aMulti[SET_COUNT_NDX]}&quot;!1..99!1!0 \
--field=&quot;Progress Bar update every x seconds::NUM&quot; \
        &quot;${aMulti[PROGRESS_INTERVAL_NDX]}&quot;!1..60!1!0 \
--field=&quot;Alarm sound filename:FL&quot; &quot;${aMulti[ALARM_FILENAME_NDX]}&quot; \
--field=&quot;Lock screen::CB&quot; &quot;$cbLockScreen&quot; \
--field=&quot;Ask to begin each timer:CHK&quot; \
        &quot;${aMulti[PROMPT_BEFORE_TIMER_NDX]}&quot; \
--field=&quot;Pop-up message when each timer ends:CHK&quot; \
        &quot;${aMulti[END_TIMER_MESSAGE_NDX]}&quot; \
--field=&quot;Sound alarm when each timer ends:CHK&quot; \
        &quot;${aMulti[END_TIMER_ALARM_NDX]}&quot; \
--field=&quot;Ask to begin each set (all timers):CHK&quot; \
        &quot;${aMulti[PROMPT_BEFORE_SET_NDX]}&quot; \
--field=&quot;Pop-up message when each set ends:CHK&quot; \
        &quot;${aMulti[END_SET_MESSAGE_NDX]}&quot; \
--field=&quot;Sound alarm when each set ends:CHK&quot; \
        &quot;${aMulti[END_SET_ALARM_NDX]}&quot; \
--field=&quot;Interface to Sysmonitor Indicator:CHK&quot; \
        &quot;${aMulti[SYSMONITOR_INDICATOR_NDX]}&quot; \
--field=&quot;Auto close progress bar display when all sets end:CHK&quot; \
        &quot;${aMulti[CLOSE_PROGRAM_AT_END_NDX]}&quot; &gt; &quot;$res1&quot; &amp;

# timers notebook page
BuildTimerPage
yad --plug=$KEY --tabnum=2 --form --columns=2 \
    &quot;${aTimerPage[@]}&quot; &gt; &quot;$res2&quot; &amp;

# run main dialog
#  --image=gnome-calculator
if yad --notebook --key=$KEY --tab=&quot;Configuration&quot; --tab=&quot;Timers&quot; \
    --image=/usr/share/icons/gnome/48x48/status/appointment-soon.png \
    --title=&quot;$TitlePrefix multi-timer setup&quot; --auto-close \
    --width=400 --image-on-top --text=&quot;Multiple Timer settings&quot; \
    &quot;$GEOMETRY&quot;
then

    # When LC_NUMERIC=it_IT-UTF8 30 seconds can be `30,000000` or
    # `30.000000` which breaks bash tests for `-gt 0`.
    # Search and replace &quot;.000000&quot; or &quot;,000000&quot; to null
    sed -i 's/[,.]000000//g' &quot;$res1&quot;
    sed -i 's/[,.]000000//g' &quot;$res2&quot;

    # Save configuration
    truncate -s -1 &quot;$res1&quot; # Remove new line at EOF
    cat &quot;$res1&quot; &gt;  ~/.multi-timer
    truncate -s -2 &quot;$res2&quot; # Remove trailing &quot;|&quot; and new line at EOF
    cat &quot;$res2&quot; &gt;&gt; ~/.multi-timer
    # Get user changes into aAlias &amp; aDuration
    ReadConfiguration
    return 0
else
    return 1    # Cancel click or Escape press
fi

}

fNewRun=FALSE fNewTimer=FALSE iSetSaveSec=0

InitTimers () {

if [[ &quot;${aMulti[TIME_UNIT_NDX]}&quot; == &quot;Seconds&quot; ]]; then
    fUnitsInSeconds=TRUE
else
    fUnitsInSeconds=FALSE
fi

iActiveTimersCount=0
for ((i=0; i&lt;MAX_TIMERS; i++)); do
    if [[ ${aDuration[i]} -gt 0 ]] ; then
        (( iActiveTimersCount++ ))
        iSetSaveSec=$(( iSetSaveSec + ${aDuration[i]} ))
    fi
done

# Progress Bars, 1 per timer + optional: set and/or set count
iAllSetsSaveCount=&quot;${aMulti[SET_COUNT_NDX]}&quot;
iAllSetsRemainingCount=$iAllSetsSaveCount
fSetProgressBar=FALSE # Summary progress bar when &gt; 1 timer used
iSetProgressBarNo=0
fAllSetsProgressBar=FALSE  # Summary progress bar when &gt; 1 run
iAllSetsProgressBarNo=0
if [[ $iActiveTimersCount -eq 0 ]]; then
    # If active timers count = 0, error message &amp; clear run count
    yad --title &quot;mutli-timer error&quot; &quot;$GEOMETRY&quot; --text \
        &quot;At least one non-zero timer required.&quot; --image=dialog-error \
        --on-top --borders=20 --button=gtk-close:0
    iAllSetsRemainingCount=0 # Set orderly exit via sibling function(s)
    iProgressBarCount=0
    fAbend=TRUE
else
    # Active timers count &gt; 0 so calculate times
    fNewTimer=TRUE
    fNewRun=TRUE
    [[ $fUnitsInSeconds == FALSE ]] &amp;&amp; \
        iSetSaveSec=$(( iSetSaveSec * 60 ))
    iAllSetsSaveCountSec=$(( iSetSaveSec * iAllSetsRemainingCount ))
    iAllSetsElapsedSec=0
    iProgressBarCount=$iActiveTimersCount
    if [[ $iActiveTimersCount -gt 1 ]]; then
        (( iProgressBarCount++ )) # Extra progress bar for Set
        fSetProgressBar=TRUE
        iSetProgressBarNo=$iProgressBarCount
    fi
    if [[ $iAllSetsRemainingCount -gt 1 ]]; then
        (( iProgressBarCount++ )) # Extra progress bar for Set Count
        fAllSetsProgressBar=TRUE
        iAllSetsProgressBarNo=$iProgressBarCount
    fi
fi

# Friendly variable names instead of Array entries
iProgressSleepSeconds=&quot;${aMulti[PROGRESS_INTERVAL_NDX]}&quot;
sSoundFilename=&quot;${aMulti[ALARM_FILENAME_NDX]}&quot;
if [[ $fWindows10 == TRUE ]] ; then
    mod=&quot;${sSoundFilename//\//\\}&quot; # Replace Linux / with Windows \
mod=&quot;${mod#*Windows}&quot;          # Remove &quot;/mnt/whatever/Windows&quot;
    sSoundFilename=&quot;C:\\Windows&quot;&quot;\\$mod&quot;
fi

fPromptBeforeTimer=&quot;${aMulti[PROMPT_BEFORE_TIMER_NDX]}&quot;
fEndTimerMessage=&quot;${aMulti[END_TIMER_MESSAGE_NDX]}&quot;
fEndTimerAlarm=&quot;${aMulti[END_TIMER_ALARM_NDX]}&quot;
fPromptBeforeSetRun=&quot;${aMulti[PROMPT_BEFORE_SET_NDX]}&quot;
fEndSetMessage=&quot;${aMulti[END_SET_MESSAGE_NDX]}&quot;
fEndSetAlarm=&quot;${aMulti[END_SET_ALARM_NDX]}&quot;
fSysmonitorIndicator=&quot;${aMulti[SYSMONITOR_INDICATOR_NDX]}&quot;
fCloseProgramAtEnd=&quot;${aMulti[CLOSE_PROGRAM_AT_END_NDX]}&quot;

} # InitTimers

Optional lost time log file monitors program execution time for progress

bars

[[ $fLog == TRUE ]] && echo "multi-timer lost time log" > ~/multi-timer.log

PromptToStart () {

# $1= Message key text
# Dialog box to proceed with timer.
yad --title &quot;mutli-timer notification&quot; &quot;$GEOMETRY&quot; --on-top \
    --fontname=&quot;Serif bold italic 28&quot; \
    --text &quot;Ready to start $1&quot; \
    --image=/usr/share/icons/gnome/48x48/status/appointment-soon.png \
    --borders=20 --button=gtk-execute:0

# Eliminates time waiting for user input
[[ $fLog == TRUE ]] &amp;&amp; LastWakeMicroSeconds=$(date +%s%N)

}

EndMessageAndAlarm () {

# $1= fEndTimerMessage, $2= fEndTimerAlarm, $3= Message key text

# Sound alarm when timer ends
if [[ &quot;$2&quot; == TRUE ]]; then
    if [[ $fWindows10 == TRUE ]] ; then
        powershell.exe -c &quot;(New-Object Media.SoundPlayer $sSoundFilename).PlaySync();&quot;
    elif [[ ! -f &quot;$sSoundFilename&quot; ]]; then
        notify-send --urgency=critical &quot;multi-timer&quot; \
        --icon=/usr/share/icons/gnome/48x48/status/appointment-soon.png \
        &quot;Sound file not found: $sSoundFilename&quot;
    else
        paplay &quot;$sSoundFilename&quot; ;
    fi
fi

# Bubble message when timer ends
if [[ &quot;$1&quot; == TRUE ]]; then
    notify-send --urgency=critical &quot;multi-timer&quot; \
        --icon=/usr/share/icons/gnome/48x48/status/appointment-soon.png \
        &quot;$3 has ended.&quot;
    # Something bold to test. Set $3 has ended. into $phrase
    # /usr/bin/notify-send  --urgency=critical --icon=clock -t 4000 \
    # &quot;&lt;i&gt;Time Now&lt;/i&gt;&quot; &quot;&lt;span color='#57dafd' font='26px'&gt;&lt;i&gt;&lt;b&gt;$phrase&lt;/b&gt;&lt;/i&gt;&lt;/span&gt;&quot; &gt;/dev/null 2&gt;&amp;1

fi

}

LockScreenCheck () {

# $1=Run type being checked:
# &quot;Each timer end&quot; / &quot;Each set end&quot; / &quot;All sets end&quot;
[[ &quot;$1&quot; != &quot;${aMulti[$LOCK_SCREEN_NDX]}&quot; ]] &amp;&amp; return 0

# When locking screen override &amp; prompt to start next timer / run
[[ &quot;$1&quot; == &quot;Each timer end&quot; ]] &amp;&amp; fPromptBeforeTimer=TRUE
[[ &quot;$1&quot; == &quot;Each set end&quot;   ]] &amp;&amp; fPromptBeforeSetRun=TRUE

if [[ $fWindows10 == TRUE ]]; then
    # Call lock screen for Windows 10
    rundll32.exe user32.dll,LockWorkStation
else
    # Call screen saver lock for Unbuntu versions &gt;= 14.04.
    # dbus-send --type=method_call --dest=org.gnome.ScreenSaver /org/gnome/ScreenSaver org.gnome.ScreenSaver.Lock
    # Call lock screen for all Linux distributions
    loginctl lock-session
fi

}

iCurrTimerNo=0 iCurrTimerNdx=0 TotalLostTime=0

PrepareNewSet () {

# Was a set just completed?
if [[ $iAllSetsRemainingCount -ne $iAllSetsSaveCount ]]; then
    # Display mssage and/or sound alarm for set end
    EndMessageAndAlarm $fEndSetMessage $fEndSetAlarm \
                       &quot;$sSetProgressText&quot;
    # Check to lock screen
    LockScreenCheck &quot;Each set end&quot;
fi

if [[ $iAllSetsRemainingCount -eq 0 ]]; then
    # We are done. Force exit from all while loops.
    fNewRun=FALSE
    fNewTimer=FALSE
else
    # Decrement remaining run count and start at first timer.
    (( iAllSetsRemainingCount-- ))
    iSetElapsedSec=0
    fNewTimer=TRUE
    iCurrTimerNo=0
    iCurrTimerNdx=0
    iNextTimerNdx=0
    iCurrSetNo=$(( iAllSetsSaveCount - iAllSetsRemainingCount ))
    sSetProgressText=&quot;Set $iCurrSetNo of $iAllSetsSaveCount&quot;
    [[ $fPromptBeforeSetRun == TRUE ]] &amp;&amp; \
        PromptToStart &quot;$sSetProgressText&quot;
fi

}

PrepareNewTimer () {

iCurrTimerElapsedSec=0
if [[ $iCurrTimerNo -eq $iActiveTimersCount ]]; then
    # Last timer done. Force exit from inner while loop.
    fNewTimer=FALSE
    return 0
fi

for ((i=iNextTimerNdx; i&lt;MAX_TIMERS; i++ )); do
    if [[ ${aDuration[i]} -gt 0 ]]; then
        iCurrTimerNdx=$i
        (( iCurrTimerNo++ ))    # Increment progress bar number
        iNextTimerNdx=$(( iCurrTimerNdx + 1 ))
        iCurrTimerSaveSec=${aDuration[i]}
        [[ $fUnitsInSeconds == FALSE ]] &amp;&amp; \
                        iCurrTimerSaveSec=$(( iCurrTimerSaveSec * 60 ))
        iCurrTimerRemainingSec=$iCurrTimerSaveSec
        break
    fi
done

}

Next function could be embedded within InitTimers to save space

and code line count but this provides better readability IMO.

SetupYadProgressBars () {

aYadProgressBars=(&quot;yad&quot; &quot;--multi-progress&quot; &quot;$GEOMETRY&quot;)
aYadProgressBars+=(&quot;--title=multi-timer progress&quot;)
[[ $fCloseProgramAtEnd == TRUE ]] &amp;&amp; aYadProgressBars+=(&quot;--auto-close&quot;)
aYadProgressBars+=(&quot;--watch-bar$iProgressBarCount&quot;)

for ((i=0; i&lt;MAX_TIMERS; i++)); do
    if [[ ${aDuration[i]} -gt 0 ]] ; then
        b1=$(( i + 1 ))
        aYadProgressBars+=(&quot;--bar=Timer $b1 - ${aAlias[i]}:NORM&quot;)
    fi
done

if [[ $fSetProgressBar == TRUE ]]; then
    aYadProgressBars+=(&quot;--bar=Set:NORM&quot;)
fi
if [[ $fAllSetsProgressBar == TRUE ]]; then
    aYadProgressBars+=(&quot;--bar=All Sets:NORM&quot;)
fi

}

DisplayProgressBar () {

# Parameters
# $1=Elapsed Time, $2=Total Time, $3=Bar Number, 
# $4=TRUE/FALSE if eligible to update Sysmonitor Indicator
# $5=Sysmonitor Indicator text for interface file or null
# $6=Progress Text Prefix, ie &quot;Set 2 of 4: &quot; or null
iPercentage=$(( $1 * 100 / $2 ))
echo &quot;$3:$iPercentage&quot;

RemainingSec=$(( $2 - $1 ))
h=$((RemainingSec/3600))
m=$(((RemainingSec%3600)/60))
s=$((RemainingSec%60))

TimeRemaining=&quot;&quot;
[[ $h -gt 0 ]] &amp;&amp; TimeRemaining=$TimeRemaining&quot; $h Hours&quot;
[[ $m -gt 0 ]] &amp;&amp; TimeRemaining=$TimeRemaining&quot; $m Minutes&quot;
[[ $s -gt 0 ]] &amp;&amp; TimeRemaining=$TimeRemaining&quot; $s Seconds&quot;
if [[ $TimeRemaining == &quot;&quot; ]]; then
    echo &quot;$3:#$6Finished.&quot;
else
    echo &quot;$3:#$6$TimeRemaining remaining.&quot;
fi

if [[ $fSysmonitorIndicator == TRUE ]] &amp;&amp; [[ $4 == TRUE ]]; then
    echo &quot;$5: $TimeRemaining&quot; &gt; ~/.lock-screen-timer-remaining
fi

}

ProcessCurrTimer () {

sTimerAlias=&quot;${aAlias[iCurrTimerNdx]}&quot;

# Dialog box to proceed with timer.
[[ $fPromptBeforeTimer == TRUE ]] &amp;&amp; PromptToStart &quot;$sTimerAlias&quot;

iLastSleepSec=0
[[ $fLog == TRUE ]] &amp;&amp; echo Start timer: &quot;${aAlias[iCurrTimerNdx]}&quot; \
    &gt;&gt; ~/multi-timer.log

while [[ $iCurrTimerElapsedSec -lt $iCurrTimerSaveSec ]]; do

    iCurrTimerElapsedSec=$(( iCurrTimerElapsedSec + iLastSleepSec))
    iSetElapsedSec=$(( iSetElapsedSec + iLastSleepSec))
    iAllSetsElapsedSec=$(( iAllSetsElapsedSec + iLastSleepSec))

    DisplayProgressBar $iCurrTimerElapsedSec $iCurrTimerSaveSec \
        $iCurrTimerNo TRUE &quot;${aAlias[iCurrTimerNdx]}&quot; &quot;&quot; &quot;&quot;
    if [[ $fSetProgressBar == TRUE ]] ; then
        DisplayProgressBar $iSetElapsedSec $iSetSaveSec \
                       $iSetProgressBarNo FALSE &quot;&quot; &quot;$sSetProgressText: &quot;
    fi
    [[ $fAllSetsProgressBar == TRUE ]] &amp;&amp; \
        DisplayProgressBar $iAllSetsElapsedSec $iAllSetsSaveCountSec \
                       $iAllSetsProgressBarNo FALSE &quot;&quot; &quot;&quot;

    # We sleep lesser of iProgressSleepSeconds or iCurrTimerRemainingSec
    iCurrTimerRemainingSec=$(( iCurrTimerRemainingSec - iLastSleepSec))
    if [[ $iProgressSleepSeconds -gt $iCurrTimerRemainingSec ]]; then
        iLastSleepSec=$iCurrTimerRemainingSec
    else
        iLastSleepSec=$iProgressSleepSeconds
    fi

    if [[ $fLog == TRUE ]] ; then
        tt=$((($(date +%s%N) - LastWakeMicroSeconds)/1000000))
        echo &quot;Last lost time: $tt milliseconds&quot; &gt;&gt; ~/multi-timer.log
        TotalLostTime=$(( TotalLostTime + tt ))
        echo &quot;Total Lost: $TotalLostTime milliseconds&quot; ~/multi-timer.log
    fi
    sleep $iLastSleepSec
    [[ $fLog == TRUE ]] &amp;&amp; LastWakeMicroSeconds=$(date +%s%N)

done

# Currently removing Sysmonitor Indicator after current timer. Need to
# modify to do it based on choice box for &quot;Lock Screen&quot;.
if [[ -f ~/.lock-screen-timer-remaining ]]; then
    # Remove Sysmonitor Indicator interface file.
    rm -f ~/.lock-screen-timer-remaining
fi

# Check for and display mssage and/or sound alarm
EndMessageAndAlarm $fEndTimerMessage $fEndTimerAlarm \
                   &quot;Timer: $sTimerAlias&quot;

# cbLockScreen=&quot;Never!Each timer end!Each set end!All sets end&quot;  
LockScreenCheck &quot;Each timer end&quot;

}

ZeroIndividualTimerProgressBars () {

for ((i=1; i&lt;=iActiveTimersCount; i++)); do
    echo &quot;$i:0&quot;
    echo &quot;$i:#&quot;
done

}

###################################

MAINLINE

###################################

ReadConfiguration

if GetParameters ; then : else # Escape or Cancel from yad notebook Cleanup exit 1 fi

InitTimers if [[ $fAbend == TRUE ]]; then Cleanup exit 1 fi

SetupYadProgressBars PrepareNewSet [[ $fLog == TRUE ]] && LastWakeMicroSeconds=$(date +%s%N)

while [[ $fNewRun == TRUE ]]; do

PrepareNewTimer
while [[ $fNewTimer == TRUE ]]; do
    ProcessCurrTimer
    PrepareNewTimer
done
PrepareNewSet
[[ $fNewRun == TRUE ]] &amp;&amp; ZeroIndividualTimerProgressBars
[[ $fLog == TRUE ]] &amp;&amp; echo &quot;Set Lost Time: $TotalLostTime milliseconds&quot; \
    &gt;&gt; ~/multi-timer.log  # For some reason value is zero?

done | "${aYadProgressBars[@]}"

LockScreenCheck "All sets end"

TO-DO why is $TotalLostTime zero below?

[[ $fLog == TRUE ]] && echo "All sets lost time: $TotalLostTime milliseconds"
>> ~/multi-timer.log

Cleanup

exit 0

Sysmonitor Indicator Interface

You can have the System Tray / Application Indicator Area update with the time remaining along with a "spinning text pizza" as illustrated in the GIF in above the bash listing.

To setup Sysmonitor Indicator see this Q&A: Can BASH display in systray as application indicator?

  • But i need to set seconds not minutes, is impossible with lock-screen-timer right? – Scorpion May 23 '18 at 11:44
  • Seconds can be changed to minutes with some minor changes. I'm off to work but I'll look at it tonight. – WinEunuuchs2Unix May 23 '18 at 11:46
  • 1
    Looks pretty good so far. Keep up the good work ! – Sergiy Kolodyazhnyy May 28 '18 at 20:54
  • 1
    Impressive work. +1ed – Sergiy Kolodyazhnyy Jun 01 '18 at 02:06
  • @SergiyKolodyazhnyy Thank you good Sir. I was thinking no one would notice it... – WinEunuuchs2Unix Jun 01 '18 at 02:07
  • 2
    It is not that i need a multitimer, i have enough with one in the morning and i would like to don't have to use it :D... But this deserves another +1 . And thank you for letting us know about yad. I knew zenity but it has an ugly warning message in the shell when you launch it. I will definitely try with yad the next time. – kcdtv Jun 01 '18 at 02:15
  • 1
    I will share this timer on that site because it will be very useful. I can share your answer with the instructions (and your name that is https://askubuntu.com/users/307523/wineunuuchs2unix)? @WinEunuuchs2Unix – Scorpion Jun 01 '18 at 09:52
  • OF course you can share. That is the spirit of free software and open source. – WinEunuuchs2Unix Jun 01 '18 at 10:35
  • Why i cannot reach the OK button? Like i said 2 comments before @WinEunuuchs2Unix – Scorpion Jun 01 '18 at 10:58
  • Sorry missed your comments. 19 timers won't fit on your screen. I'll revise the program to 15 timers tonight. I have a 1920x1080 monitor. Yours must be 1024x768 or something similar. As far as the "KEY" error that is because the first instance you ran never finished because you couldn't read the OK button. A simple reboot would fix that. – WinEunuuchs2Unix Jun 01 '18 at 11:25
  • I did a quick fix to change timers from 19 to 15 above but will do a better rewrite tonight after work or tomorrow. – WinEunuuchs2Unix Jun 01 '18 at 11:48
  • So you want the number of timers set to 17 and index set to 29. Save program and delete ~/.multi-timer to delete old configuration file. When you run program you have to set at least one timer or you get the error message. – WinEunuuchs2Unix Jun 01 '18 at 12:36
  • https://stackoverflow.com/questions/42568996/cannot-create-shared-memory-for-key-12345-file-exists/55849450#55849450 to fix "Cannot create shared memory for key 12345: File exists" – Scorpion Apr 25 '19 at 12:37
  • @WinEunuuchs2Unix How can I set 35 timers? I get "at least one non zero timer required" If I set more than 19 timers. – Scorpion Apr 27 '19 at 08:59
  • @Scorpion The configuration file is limited to 19 timers. A major upgrade is required to go beyond that. The configuration screen has two columns for timers and it would have to be changed with four columns giving a max of 38 timers on a FHD screen. – WinEunuuchs2Unix Apr 27 '19 at 14:54
  • @WinEunuuchs2Unix Can you do it? – Scorpion Apr 28 '19 at 14:51
  • @Scorpion I've just updated the code with numerous changes made after June 2018 to March 2019. I didn't think people were much interested in multi-timer so did not publish updates. My setup has 3 timers (Wash Cycle, Rinse Cycle and Dryer). Going to 40 timers is probably overkill for most people. That said it is possible but the current method of changing number of timers is awkward. A prompt to change number of timers would be used. The Timer Notebook Tab would have to change from 2 columns to 4 columns or 6 columns based on number of timers. The progress bar window will have to scroll. – WinEunuuchs2Unix Apr 28 '19 at 16:49
  • @WinEunuuchs2Unix can we talk in a room about multi-timer? – Scorpion Aug 27 '21 at 07:42
  • @Scorpion Sure. Just create a room and invite me in. – WinEunuuchs2Unix Aug 28 '21 at 14:12
  • Why don't you just create a git repo for this code? – ceremcem Feb 10 '24 at 10:32
  • @ceremcem Good idea. Many of my programs are already on github / website www.pippim.com. Multi-Timer was actually rewritten to run on web browser on any device under the new name Tim-ta (Timed Tasks). – WinEunuuchs2Unix Feb 10 '24 at 14:21