Linux Bash script to remind you of the break time(RestTime)

Break Time

Hello my friends 🙂

Today I edited one of my old scripts a bit so that I could post it on the website. I’ve written this script a long time ago, it calculates how long you will work with PC and reminds you after a certain amount of time that you need to take some rest(short break). This script automatically calculates the working time of the PC and the script does not take into account the times when you does not work with the PC(idle time). When you work with computers for a long time, this script is essential for your health.

Picture Source : techradar.com

As you know, there are many programs and device such as smart bands and smart watches to remind yourself of break times, but since I have a strong interest in creating personal code 😀 , I decided to write a small script for it; writing and creating personal tools is really fun for me 🙂 . I’ve been using this script for a long time and it meets my needs and hopefully useful to you too. Studying this script can be useful for learning scripting in Linux as well, so I decided to publish this script.

If you just want to download and use the script click here

This script is writen in “Sublime”

In this script we will learn the following:

  • How to read and save keyboards and mouse keys pressed(and mouse movements) by Bash script.
  • How to check Bash script is executed with root privilege.
  • How to detect Bash script is already running.
  • How to play audio (with different frequencies) through speakers and built-in systems speaker(beep) in the Bash script.
  • How to determine the idle time of user in the Bash script
  • How to turn on and off the screen(monitor) by Bash script
  • How to load variables from a file in Bash script.
  • How to found name, ID and path of Bash script.
  • How to access to user monitor(screen) by the root user or via ssh, find the keyboards event in /dev/input, get files size, do the decimal divides in the Bash scripts and etc.

Algorithm_fun

Picture Source : modernanalyst.com

Okay, let’s talk a little bit about the algorithm and what the script is going to do for us. What do we expect from the script? We want to set a specific working time and the script will alert us after the due date. We want to do this by turning off the monitor and playing the sound. Also let us know when the rest(short break) period is over by playing the sound and turning on the monitor. In addition, we want the script to only calculate the length of time we work with the system, and not to calculate idle time(If we have 2 minutes idle time, the script will allow us to work with the system for 2 minutes more than specified working time).So, the script algorithm is as follows:

  1. Wait for a minute.
  2. If the user was idle for one minute then add one minute to the user working deadline, otherwise add one minute to the user worked time.
  3. If the user was idle for the length of time he had set for rest, then set the worked time counter to zero (meaning the user has had enough rest and can work again as long as he has set).
  4. If this was the last minute of the working deadline, let the user know by playing audio. If the user requests more time, then add two minutes to the working deadline.
  5. If the working deadline has not expired, go to step 2 otherwise inform the user that the working deadline is over.
  6. Wait for the amount of rest time the user has set.
  7. Let the user know that the break is over and can work. Then go to step one.

Picture Source : pcworld.com

Let’s start coding. Okay, first we define and quantify the variables we need:

#!/bin/bash

#	Script name : RestTime
#	Programmer : mazKnez 
#	Website : mazKnez.com
#	2019-08-17


########################### [-- Readme ################################

# This script has been tested on Debian-based Linux distributions.
# NOTICE : Check that these packages are installed on your system : " beep, pidof, speaker-test, pkill " 
# NOTICE : config.cfg should be next to the script file. 
# NOTICE : This script must be run with root access.
# NOTICE : If you want to run this script automatically (via service or in rc.local), 
# 	       you need to include in the file ".profile" the following commands to access the monitor:	
#	       xhost +SI:localuser:root > /dev/null 
#  		   OR
#		   xhost + > /dev/null 		
# 
#
# This script calculates how long you will work with PC and reminds you after a certain amount of time
# 	that you need to take some rest(short break). 
# This script automatically calculates the working time of the PC and the script does not take into
# 	account the times when you does not work with the PC(idle time). 
# When you work with computers for a long time, this script is essential for your health.	
# 
#
# Please read "README" file or visit www.mazKnez.com website for more information.
#
########################### Readme --] ################################



## [-- define consts   

readonly script_path=$(dirname "${0}")"/"
readonly keyboards_file="${script_path}keyboards_dump"		# The keys pressed from the keyboard
															# are stored in this file.

readonly mice_file="${script_path}mice_dump"		# Mouse movements and clicks 
													# are stored in this file.

readonly status_file="${script_path}status"			# The script status is stored in this file, 
													# mostly used to verify the script. 

readonly config_file="${script_path}config.cfg"		# The script configuration is read from this file
export DISPLAY=:0 

## define consts --]  

It looks like the above code doesn’t need any explanation. In order to make the code readable and easy to code, I have tried to use functions as much as possible. Below we review these functions.

check_run_as_root () {

  ### This function checks script execution with super user

  if ((UID != 0)); then  # root UID == 0
    echo "This script must run as root"
    exit 1
  fi	
}

This function checks whether a user with root access has executed the script. Otherwise the script will not run.

detect_script_running () {

	### This function checks whether another version of this script is running
	### The best way to determine if software is running is to use a pidfile... 
	### But it is also applicable to this script.

	script_name=$(basename "${0}")
	is_another_script_running=$(pidof -x "${script_name}" -o $$)
	# pidof checks if the program is running under this name
	# Parameter -x was used to check the scripts.
	# Parameter -o is used to delete the current execution of the script from the list.
	if [ "${is_another_script_running}" ]; then
	    true
	else
		false
	fi
}

This function checks whether the script is running or not. Returns “true” if the script was already executed. This function uses “pidof”. This method eliminates the need for us, but the best way to determine if the software is running is to use a pidfile so that every time the code is run, read the pid from the pidfile and check if pid is running, Then save the new pid code to the pidfile.

dump_keyboard_and_mouse () {

	### This function stores keyboards and mice activity in the files.
	### To detect user idle, we need to read the mouse and keyboard inputs. 
	###		We use the method of directly reading files in "/dev/input". 
	###		Along the way you can find the mice file and the keyboards file that starts with "event".
	### 	"mice" contains mouse input data, but to find the file related to the keyboards 
	###		we need to check the files in folder "by-path", keyboards file names end with "event-kbd". 
	###		We need to find the links to the keyboards, and then we can find the keyboards event file

	pkill --full "cat /dev/input/"		# Kill previous execution of this function

	kbdEvents=($(ls /dev/input/by-path | grep "event-kbd"))		# Find files related to keyboards
	for forCounter in "${kbdEvents[@]}"
	do
		eventFile=$(readlink --canonicalize "/dev/input/by-path/${forCounter}")		# Get keyboards file location
		cat "${eventFile}" > "${keyboards_file}" &		# Store keyboards activity in the keyboards_file
	done
	cat /dev/input/mice > "${mice_file}" &		# Store mice activity in the mice_file
}

This function stores keyboards and mice activity in the files. To detect user idle, we need to read the mouse and keyboard inputs. We use the method of directly reading files in “/dev/input”. Along the way you can find the mice file and the keyboards file that starts with “event”. The files in the path “/dev/input” will look like this:

drwxr-xr-x 2 root root     120 Aug 17 08:31 by-id
drwxr-xr-x 2 root root     140 Aug 17 08:32 by-path
crw-rw---- 1 root input 13, 64 Aug 17 08:31 event0
crw-rw---- 1 root input 13, 65 Aug 17 08:31 event1
crw-rw---- 1 root input 13, 74 Aug 17 08:31 event10
crw-rw---- 1 root input 13, 75 Aug 17 08:31 event11
crw-rw---- 1 root input 13, 76 Aug 17 08:31 event12
crw-rw---- 1 root input 13, 77 Aug 17 08:32 event13
crw-rw---- 1 root input 13, 66 Aug 17 08:31 event2
crw-rw---- 1 root input 13, 67 Aug 17 08:31 event3
crw-rw---- 1 root input 13, 68 Aug 17 08:31 event4
crw-rw---- 1 root input 13, 69 Aug 17 08:31 event5
crw-rw---- 1 root input 13, 70 Aug 17 08:31 event6
crw-rw---- 1 root input 13, 71 Aug 17 08:31 event7
crw-rw---- 1 root input 13, 72 Aug 17 08:31 event8
crw-rw---- 1 root input 13, 73 Aug 17 08:31 event9
crw-rw---- 1 root input 13, 63 Aug 17 08:31 mice
crw-rw---- 1 root input 13, 32 Aug 17 08:31 mouse0

“mice” contains mouse input data, but to find the file related to the keyboards we need to check the files in folder “by-path”, keyboards file names end with “event-kbd”. We need to find the links to the keyboards, and then we can find the keyboards event file. Folder “by-path” contents are similar to:

pci-0000:00:14.0-usb-0:3:1.0-event-mouse -> ../event3
pci-0000:00:14.0-usb-0:3:1.0-mouse -> ../mouse0
pci-0000:00:14.0-usb-0:4:1.0-event-kbd -> ../event4
pci-0000:00:14.0-usb-0:4:1.1-event -> ../event5
platform-pcspkr-event-spkr -> ../event13

Finally, we store the keyboard and mouse inputs in files “keyboards_dump” and “mice_dump” (with “cat” command).

load_variables () {	
	
	### This function loads variables from configuration file	
	### User can change the script behavior by these variables. 
	### NOTICE : This file(config.cfg) should be next to the script file.

	if [ -f "${config_file}" ]; then		# If config.cfg is extist
		source "${config_file}"
	else
		workingTime=30      # In minutes
		restingTime=5       # In minutes
		idleDetection=true      # If "true", the script also check idle time,
								# for more information about it, 
								# read "is_user_working" function's comments
		active=true     # If "false", The script is temporarily disabled.
		playSound=true      # If "false", the script do tasks silently.
	fi	
}

This function loads variables from configuration file. A number of variables, such as working time and rest time, can be edited by the user through “config.cfg” file. This file should be placed next to the script file. To load the variables we use the “source” command.

is_last_minute () {

  ### This function checks whether this minute is the last minute of working time.

  if ((whileCounter == workingTime-1)) && ((idleCounter == 0 )); then 
  		true
  else 
  		false
  fi
}

This function checks whether this minute is the last minute of working time. I wrote this condition to prevent script complexity and increase script readability in the function.

check_more_time_requested (){

	### This function checks whether the user has pressed the ScrollLock key 3 or more times. 
	### The user can press more than 2 times the ScrollLock key, demanding more time to work.

    scroll_lock_pressed=$(xxd "${keyboards_file}" | grep --count "0100 4600 0100 0000") 
    # xxd displays the input as hex.
    # Hex code of ScrollLock in "keyboards_file" is "0100 4600 0100 0000"
	if ((scroll_lock_pressed >= 3)); then 
		true # return true
	else
		false
	fi	
}

This function checks whether the user requests more time to work on the PC. If the user presses the key “Scroll-Lock” for 3 times or more, it means more time is required. To check if the key pressed, we need to check the file “keyboards-dump” and convert it to hex by using “xxd”.

play_sound () { 
   
   ### This function plays sound through the speakers and the built-in speaker system.
   ### $1 == Frequency , $2 == Time in ms

   if "${playSound}"; then		# User can disable sound by changing the configuration file
		second=$(bc --mathlib <<< "$2/1000")		
		# Decimal numbers cannot be calculated by bash. 
		# We have to use another programs like bc to divide a decimal.
		beep -f "${1}" -l "${2}" < /dev/null&		# Play sound by built-in speaker system
		( speaker-test --test sine --frequency "${1}" ) > /dev/null& pid=$! ; sleep "${second}s" ; kill -9 "${pid}" 
	fi
}

This function plays sound through the speakers and the built-in speaker system. We use “speaker-test” to create sound through the speakers and “beep” command to create sound through the internal PC speaker. As you know, you can’t directly divide a decimal into a Bash script, To do this, we need to use commands like “bc”.

The rest of the functions need not be explained:

play_notice_sound () {	
	
	### This function plays notice sounds	 

    if (("${1}"==1)); then		# $1 is for last minute notice
		for i in 1 2
		do 
			sleep 0.5s
			play_sound 800 80
			sleep 0.2s
			play_sound 800 80
		done
	fi
    if (("${1}"==2)); then		# $2 is for accept user request more time 	
		play_sound 1000 80
		sleep 0.2s
		play_sound 1200 100
	fi
}

is_user_working () {
	
	### This function checks whether the user has been working with the system or not.
	### This function checks for both keyboards and mice files, 
	### if their size is zero, it means the user is not working with the system.

	miceFileSize=$(stat --format %s "${mice_file}")		# Get mice file size
	kbdFileSize=$(stat --format %s "${keyboards_file}")		# Get keyboards file size

	echo > "${mice_file}" > "${keyboards_file}"		# Clear mice and keyboards files

	if ((miceFileSize > 1 )) || ((kbdFileSize > 1 )); then
		true # return true
	else
		false
	fi
}

end_work () {
	
	### This function Execute commands related to the end of working time 

	play_sound 800 500
	play_sound 700 500
	sleep 5s
	xset -display "${DISPLAY}" dpms force off		# Turn off the monitor
}

start_work () {
	
	### This function Execute commands related to the start working time 	

	play_sound 1000 400
	play_sound 1200 500
	play_sound 1200 700
	xset -display "${DISPLAY}" dpms force on 		# Turn on the monitor
}

Okay, we checked all the functions. Now it’s time to see the main function of the script together:

main() {
   
	check_run_as_root		# Check script execution with super user
	if detect_script_running; then		# This script should not run multiple times at same time
	   echo "Script is runnig ..." 
	   exit 1 
	fi
	load_variables		# Load variables from configuration file
	dump_keyboard_and_mouse		# Store keyboards and mice activity in the files

	while true 		# Run the script forever
	do

		idleCounter=0‍‍	
		whileCounter=0

		while ((whileCounter < workingTime));		# This loop runs every minute until the end of workingTime.
		do
			load_variables		
			if "${active}"; then		# This variable loads from configuration file
				echo "You have" $((workingTime-whileCounter)) "minutes to work ... " > "$status_file"
				if is_last_minute; then		# Detect last minute of working time
					echo > "${keyboards_file}"		# Clear keyboards_file 
					play_notice_sound 1		# Play notice sound, The user can request more time to work
											# after hearing this sound
					for forCounter in {1..7}		# Wait a short time for the user to request extra time
					do 
						sleep 2s
						if check_more_time_requested; then		# Did the user request more time?
							((whileCounter-=2))		# Add 2 minutes to user deadline
							play_notice_sound 2		# Play audio to accept user request
						   break			
						fi
				    done
				fi

				sleep 1m		# Wait for 1 minutes									

				if "${idleDetection}"; then		# If IdleDetection is enable in the configuration file
					if is_user_working; then
						((whileCounter++))		# If the user is working, reduce one minute of working time.
						idleCounter=0
					else
						((idleCounter++))
					fi
					if ((idleCounter >= restingTime)); then
						# If the user does not work for 5 minutes then reset all the counters.
						# This means that the user has not been with the system for a while,
						# so, the user has enough rest and can work again.
						whileCounter=0
						idleCounter=0
					fi
				else		# If IdleDetection is disable. static mode
					idleCounter=0
				   ((whileCounter++))
				fi					
			else
			   idleCounter=0
			   whileCounter=0
			   sleep 1m  
			fi	

		done

		echo "It's time to rest !" > "${status_file}"
		end_work		# Executing commands related to the end of working time
		sleep "${restingTime}m"		 
		start_work		# Executing commands related to the start working time

	done

}

I don’t think the main function needs explanation. Remember, to run the script, we have to add this code at the end of the script to Bash know that which function is the main function of the program:

main "${@}"  # Start main function

Click here to download the script and required files.

This script is writen in “Sublime”

After downloading the script, use the following command to extract the compressed file :

tar -zxvf mazKnez_RestTime.tar.gz 

Before you can run the script you need to convert the script to an executable file, using the following command:

chmod +x mazKnez_RestTime/restTime.sh 

For run the script, go to “mazKnez_RestTime” directory and use this command :

bash restTime.sh &

If you want the script to run automatically every time the system is turned on, you can either insert it into a “/etc/rc.local” file or create a service for it. If you want to run this script automatically (via service or in rc.local), you need to include in the file “.profile” the following commands to access the monitor :

########################### [-- mazKnez RestTime ################################

xhost +SI:localuser:root > /dev/null   # access root user to monitor
#xhost +

########################### mazKnez RestTime --] ################################

In this section, we create a very simple service to run scripts automatically, we use systemd and “systemctl” to build scripts on Debian Linux based operating systems. First of all, we need to create a file with the following contents in path “/etc/systemd/system/restTime.service” :

[Unit]
Description=mazKnez_restTime_script(mazKnez.com)

[Service]
ExecStart=/home/$YOUR-USERNAME/mazKnez_RestTime/restTime.sh

[Install]
WantedBy=default.target

I’ll talk later about building a detailed Linux service, here just enter the script path in the line specified. Then run the following command:

chmod 664 /etc/systemd/system/restTime.service

Run these commands now :

systemctl daemon-reload
systemctl enable restTime.service
systemctl start restTime.service

Congratulations, it’s over! I hope you find this helpful.

I’m waiting for your comments.
Have a nice day 🙂

13 thoughts on “Linux Bash script to remind you of the break time(RestTime)

  1. I think that what you typed made a ton of sense.
    However, consider this, what if you wrote a catchier post title?
    I am not suggesting your content isn’t good., but what if you added a post
    title that grabbed a person’s attention? I mean Linux Bash script to remind you of the break time(RestTime) – mazKnez Personal Website is kinda vanilla.

    You could peek at Yahoo’s home page and see how they create news headlines to
    grab viewers to click. You might add a video or a picture or two to grab people excited about what you’ve written. Just my opinion,
    it could make your website a little bit more interesting.

    1. Hi dear friend 🙂 ,Thank you very much for reading this post and for writing your comment.
      I read your comment, but since my English is bad, I didn’t understand some of the sentences. What do you mean by “kinda vanilla”?

  2. Hello everyone, it’s my first pay a quick visit at this web
    page, and post is genuinely fruitful for me,
    keep up posting these types of posts.

  3. Like!! Really appreciate you sharing this blog post.Really thank you! Keep writing.

Leave a Reply

Your email address will not be published. Required fields are marked *