Skip to content

Instantly share code, notes, and snippets.

@sebastiancarlos
Last active December 29, 2023 01:06
Show Gist options
  • Save sebastiancarlos/ca7124e33d4af1afcf3f29b01f5deadd to your computer and use it in GitHub Desktop.
Save sebastiancarlos/ca7124e33d4af1afcf3f29b01f5deadd to your computer and use it in GitHub Desktop.
Show notifications in your terminal
# All my gist code is licensed under the terms of the MIT license.
# Video demo: https://www.youtube.com/shorts/WVyqVkGYb4k
# Add this somewhere in ~/.bashrc
# write_message
# - write a message on the lower right corner of the terminal
function write_message () {
if [[ "$#" -eq 0 ]]; then
echo "write_message: please provide a message"
return
fi
local message=" ${*} "
# CSI sequences
local csi="\033["
local save_cursor_position="${csi}s"
local restore_cursor_position="${csi}u"
local erase_from_cursor_to_end_of_screen="${csi}J"
local reset="${csi}0m"
local set_bg_color="${csi}44m"
local set_fg_color="${csi}30m"
local move_cursor_to_bottom="${csi}${LINES};$((COLUMNS - ${#message}))H"
# write the message
printf "${save_cursor_position}"
printf "${move_cursor_to_bottom}"
printf "${set_bg_color}${set_fg_color}${message}${reset}"
printf "${restore_cursor_position}"
# clean up after 5 seconds of timeout
function __clean_up () {
sleep 5
printf "${save_cursor_position}"
printf "${move_cursor_to_bottom}"
printf "${erase_from_cursor_to_end_of_screen}"
printf "${restore_cursor_position}"
}
# clean up in the background
# run in a subshell to avoid showing 'job control' output
(__clean_up &)
# clean up internal functions
unset -f __clean_up
}
# sample function
# run this in the background with 'check_email &'
function check_email () {
sleep 10
write_message "You have 69 new emails (total 420 unread)"
}
@Stewie410
Copy link

Stewie410 commented Aug 5, 2023

As per my reddit comment, you could also use printf & tput for both more control and to make things a bit easier to understand.

Additionally, lines 28-31 could probably just be tput sequences.

With that in mind, here's a "reimagined" version while leveraging printf & tput in place of escape sequences:

write_message() {
	local message
	message="  ${*}  "
	
	__write() {
		local reset col_bg col_fg
		
		reset="$(tput sgr0)"
		col_bg="$(tput setab 4)"
		col_fg="$(tput setaf 0)
		
		[[ "${2-}" == "--no-background" ]] && unset col_bg
		
		tput sc
		tput cup "${LINES}" "$((COLUMNS - ${#message}))"
		printf "${col_bg}${col_fg}%s${reset}" "${message}"
		tput rc
	}
	
	__clean_up() {
		local blank_spaces
		blank_spaces="$(printf "%${#message}s")"
		
		sleep 5
		__write "${blank_spaces}" --no-background
	}
	
	__write "${message}"
	(__clean_up &)
	unset -f __write __clean_up
}

A few quick notes:

  • $message shouldn't need to be defined in each sub-function
    • Since the parent scope (write_message()) defined the variable, internal functions should have access to the same variables
    • This is also true in a scripting context, such as in main() calls _init_vars() or something
    • I'm also unsure that unset -f is even needed for the internal functions
      • These shouldn't be accessible outside of the parent function, even if the file is sourced
  • The function keyword is neither POSIX compliant, nor necessary even in the context of bash
    • Though it also isn't a problem to use
  • $message should be defined with $* to expand args as a string (without word-splitting), rather than $@ which is an array
    • I did also remove $message_length, though that's really just a matter of choice
  • As per the god that is shellcheck, variables "should" be declared & defined on different lines when using local/declare (unless read-only)
    • But again, I know that's generally just a matter of choice than actual need
  • Not sure if its a typo or what, but sleep 10 does not wait "1 minute" before continuing -- it waits 10 seconds

Like the idea though -- would be interesting to see if there would be a way to do the same thing, but instead hook in to messages sent with notify-send, for greater system-wide notification handling.

@sebastiancarlos
Copy link
Author

sebastiancarlos commented Aug 6, 2023

@Stewie410 Thanks a lot! I updated the code with your feedback.

A few notes:

  • I believe $message should be redefined in __write because __write doesn't always print the message passed to write_message: It also prints an empty message of the same length to clean up.
  • I like the function keyword.
  • I also like the local keyword on the same line as the definition.
  • I personally don't like tput for a few reasons:
    • I think it's a historical accident from the pre-xterm days. Now that terminal emulator compatibility is better, there's no need to avoid writing escape sequences by hand. I would take a tput like library with extremely explicit names, but I don't think that exists right now, and in any case, variables are good enough for documenting.
    • It starts a new process just for writing strings (this is hypocritical of me because I don't mind having 5 consecutive printfs, but we are all sinners under the shadow of our lord Jesus Christ).

Thanks again. I'm about to make a small improvement to optionally render a link that you can either click or open with a keybinding. It's surprisingly easy to do, and I wonder why no more people are exploiting these terminal shenanigans for custom hacks.

Edit: I simplified a bit more and managed to remove one internal function.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment