Linux Bash Script to Remind You to Take a Break (RestTime)

Break Time

Hello, friends 🙂


Today, I revisited and improved one of my older scripts to finally share it here on the website. I originally wrote this script quite a while ago to help monitor your computer usage and remind you when it’s time to take a short break.


The script automatically tracks your active PC usage excluding idle time and notifies you when it’s time to rest. It’s a simple tool, but when you’re working at your computer for long hours, something like this can really help with your health and focus.

Image Source : techradar.com


As you probably know, there are numerous commercial tools like smart bands, smartwatches, and various apps that remind you to take breaks. But since I enjoy writing personal tools and scripts 😄, I decided to build my own. Not only does it serve my needs well, but I’ve also found it fun to develop and experiment with. If you’re into Linux scripting, reading through this script can be educational too—so I thought I’d share it with you.

If you simply want to download and use the script, click here.

What You’ll Learn From This Script:

  • How to capture keyboard and mouse activity using a Bash script
  • How to check if the script is being run with root privileges
  • How to detect if the script is already running
  • How to play sounds (both through the system speaker and PC speakers)
  • How to detect user idle time
  • How to turn the monitor on and off from the script
  • How to load settings from a configuration file
  • How to determine the script’s own name, ID, and file path
  • How to access user sessions (like screen and input devices) as root or via SSH
  • How to get file sizes and perform decimal operations using Bash
Algorithm_fun

Image Source : modernanalyst.com


How Does the Script Work?


Let’s break down exactly what this script does and our objectives.

The idea is simple: You define a working time, and once that time is up, the script alerts you by turning off your monitor and playing a sound. After the specified rest period, it alerts you again (by turning the monitor back on and playing a sound) so you can continue working.

One important feature is that the script tracks only active working time. If you step away from your PC, the script won’t count that time. For instance, if you’re idle for 2 minutes, you’ll be allowed to work 2 minutes longer.

The Algorithm:

  1. Wait one minute.
  2. If the user was idle, add that minute to the work deadline (extend the session).
  3. If the user was active, increment the work time counter.
  4. If idle time exceeds the configured rest period, reset the counter—meaning the user had enough rest and can work again.
  5. If it’s the last minute of work time, notify the user. If more time is requested, extend the deadline by 2 minutes.
  6. Once the work period is over, alert the user and start the rest countdown.
  7. After resting, notify the user and repeat from step 1.
Image Source : pcworld.com

Starting with the Code

We begin by defining some constants and paths that the script will use:

#!/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: Ensure these packages are installed on your system: beep, pidof, speaker-test, and 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 seems the above code doesn’t require further explanation. To enhance readability and ease of coding, I’ve used functions extensively. 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 ideal way to determine if a program is running is using a pidfile, but the current method is sufficient for 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 records keyboard and mouse activities into 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 remaining functions do not require detailed explanations:

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

How to Install and Run the Script :

Click here to download the script and required files.

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 


To run the script, navigate to the mazKnez_RestTime directory and execute 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, create a file at the following location: /etc/systemd/system/restTime.service, and include the content below:

[Unit]
Description=mazKnez_restTime_script(mazKnez.com)

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

[Install]
WantedBy=default.target


I’ll cover detailed Linux service creation in another post. For now, just specify the script’s path as shown below. 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

That’s it! 🎉
I hope this script helps you. Feel free to share your thoughts in the comments.

Have a great day! 🙂

13 thoughts on “Linux Bash Script to Remind You to Take a Break (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.

Comments are closed.