Add emojis to your command prompt and animate it

Customizing your PS1 in Mac/Linux

Bharath Raja
6 min readSep 3, 2021

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

Changing emoji in command prompt

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.

--

--

Bharath Raja
Bharath Raja

Written by Bharath Raja

DevRel 🥑 Engineer 📟 Adventurous 🏂 High-functioning Absurdist. I cherish meaning, truth, beauty and the paradox of their co-existance. bharathraja.in

No responses yet