Add emojis to your command prompt and animate it
Customizing your PS1 in Mac/Linux
Preface
I recently bought a new Macbook Pro 13" and started setting it up for my development. The list went like Chrome, iTerm2, Brew, OhMyZsh, VS Code, and so on (it deserves its own post). This was my first time with OhMyZsh and I was playing around with the themes and I noticed that the PS1 was simple enough to edit. I thought it would be fun to add colourful emojis to my prompt. There started a journey of hacking for 3 days…
TLDR; 📦 Go to Repository
Bash approach
Adding a random emoji was straightforward. Then I thought of having a list of emojis that I can randomly show. It’s always nice to have variety 🤷🏻♂️
$ arr=(👾 💻 🍀 🦮 ⛰️ 🍺 🎨 🏃🏻♂️ 👨🏻🌾 🐢 🐼 🐙 🐳 🐓 🪵 🍄 🔥 🍁 🐚 🌊 🍉 🥝 🍋)
$ PS1="\${arr[RANDOM%${#arr[@]} + 1]} $PS1"
🍉 $ echo "Noice!!!"
Noice!!!
🌊 $
🐳 $
It was so beautiful and my mind was flowing with ideas. I wanted to show time-appropriate emojis. I started simple, show food and sleep timing, else randomize from the list.
function _emoji() {
time=$(date +%H%M)
# SHOULD SLEEP
if (( $time > 2230 )); then
echo -n 🥱
elif (( $time > 2100 )); then
echo -n 🍕
# ...
# Other food timings
elif (( $time < 500 )); then
echo -n 🛌
else
echo -n ${arr[RANDOM%${#arr[@]} + 1]}
fi
return 0
}
It was a great start, but I wanted more. I wanted to be able to override the schedule based emoji when the activity is over (like I don’t wanna see food emoji after I’ve had my lunch) at the same time I wanted some of them to be unavoidable (like I should only see sleep emoji from 2300 to 500). And it wasn’t easy to maintain such a complex data structure in bash, so I moved to a language I’m very comfortable with — JS.
Introducing JavaScript
Once I found a way to use JS to output to my PS1, I had much more control over what I wanted to show and when. Soon, I had a very detailed schedule that defaults to a list when empty. The default list also grew 3 folds.
const fun_list = "👻,👾,🎃,💋,👁 ,🥷 ,🧶,🧵,👑,🐰,🦊,🐼,🐨,🐷,🐸,🦋,🐌,🐢,🐙,🦀,🐡,🐠,🐳,🐿 ,🦢,🪵 ,🌵,🍀,🍁,🍄,🌸,🌼,🌏,🔥,☂️ ,🌊,❄️ ,🍋,🍌,🍉,🍓,🍒,🥥,🥝,🥑,🌶 ,🧀,🍿,🍺,⚽️,🏀,🏐,🥊,🎹,🥁,🏖 ,🏔 ,⛺️,💻,💿,☎️ ,📟,⏳,🔋,🧲,🔮,🪣 ,📦,❤️ ,🧡,💛,💚,💙,💜,🖤,🤍,🤎,🇮🇳 ".split(',')
const activity_list = '🎨,🦮,📚,✍️ ,🎸,🛹,🏃🏻♂️'.split(',')
const getRandom = arr => arr[arr.length * Math.random() | 0]const timings = [
// from, duration, emoji, highlight?, unstoppable?
[0, 500, '🛌', true, true],
[530, 200, getRandom(activity_list)],
[800, 200, '🥪', true],
[1300, 130, '🍛', true],
[1600, 100, getRandom(activity_list)],
[1700, 130, getRandom(activity_list), true, true],
[1900, 200, '🍕', true],
[2130, 200, getRandom(['🥱', '😴']), false, true],
[2300, 100, '🛌', false, true],
]
Animations
Back to Bash again. I read a bit about Cursor Movement and started playing with simple animations. Having an infinite loop that calls the JS file gave an animated effect because of the randomness.
# storing the pid
_ps_emoji_animation & ; echo "$!" > /tmp/psanimatepid-$$
# $$ to keep track of which shell is being animated
I wrapped it in a function that was run as a daemon and the PID was stored. The PID is necessary to stop the animation. The function also took a sleep timer as an argument so we can animate at whichever pace we like. The following gif is $ psanimate .5
This was great, but the animation needs for a single emoji, like 🛌 which has to be highlighted was very different. The animation should be focused on highlighting and I used the arrow movement for that. For this altering movement, I used the current second as the position, but with differing animation times, I had to send a boolean variable to the JS.
The animation could be stopped at any point using another function that kills the appropriate PID $ psanimate_stop
. I wanted the animation to start and stop at particular times, again, a schedule. Cron job won’t work because it runs in a different shell. I could have an infinite loop running that checks the time and animates it. It worked.
while [ : ]
do
time=$(date +%H%M)
if (( $time > 2300 )); then
psanimate .2
elif (( $time < 500 )); then
psanimate .1
fi
sleep 1800
done
# wrap it in a fn and run it as a daemon
But I very felt uneasy that the schedule data is at two places now, one in bash and another in JS. And automating animation could be really annoying, so I kept a 30-minute sleep and only for the night sleep reminder.
Overriding task emojis
I didn’t want to be seeing food emojis after I’ve had my meal, I wanted a means to bypass my schedule. One can also use it to snooze. So I introduced an env variable PS_TASK_OVER
that can be set to the current time. The JS code skips the scheduled emoji if this variable was set within the last 1 hour. JS schedule also had a unstoppable
flag that ignores the end of a task. To make it even better, I wanted to unset this variable after 1 hour, the JS only has to care about its existence. But turned out to be a tough task when I’m hellbent on keeping the task_over as an env variable and not in a file. Finally, I found a hack using traps, it wasn’t ideal but the concept was new and so I held on to it.
trap 'unset PS_TASK_OVER' SIGUSR1
p=$$
( sleep 3600; kill -SIGUSR1 $p ) &
As I learned more about traps, I wrote a nice cleanup function. This makes sure the background processes are killed and the /tmp
directory is kept clean (at least cleaner).
function pscleanup {
echo "Cleaning the animation stuff"
psanimate_stop
_psautoanimator_kill
unset PS_TASK_OVER
}trap pscleanup EXIT
Calling it done
I pushed my changes out but I kept improving the code. I started looking into automated notifications and voices. Notifications were an easy thing.
osascript -e 'display notification "Time to play outside" with title "🦮🏃🏻♂️Sunshine"'
Mac also has a nice say
command. I found documentation for controlling the voices and I started playing around with it. Milena’s Russian accent soon became my favourite. Try this in your console…
say -v Milena "You are [[rate 80]]still[[rate 100]] here?[[slnc 400;rate 165]]Go sleep you faukin nerd.[[slnc 200;rate 140]] NOW.[[slnc 1500;volm +50;rate 265]] I meant[[slnc 200;rate 100]]now"
I started somewhere else and here I was falling in love with a Russian automated voice. I realized there was no end to this journey and even though it was hard, I had to stop somewhere. Moreover, it wasn’t about command prompt anymore, this schedule-based voices and notifications are more of a cron thing anyway. And so I called it done. You can find my work on GitHub.
I hope you have a great time playing with your machine too. As for me, I’m on to my next project where I intend to hear more of Milena 🥰
Adios amigo.