Skip to content

Instantly share code, notes, and snippets.

@sebastiancarlos
Last active July 23, 2024 08:43
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.

@adriangalilea
Copy link

I did not have luck running this, the popup appears for 0.1s and disappears, Love the idea, but couldn't fix it.

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