Welcome back to Day 2 of my Ubuntu contribution journey!
This time, I went deep into the source code of the legendary sl command β the tiny train that runs across your terminal when you mistype ls. π
I learned way more than I expected: about C pointers, terminal control, static memory, and even the ancient magic of the curses library. And yes β I broke my terminal once or twice while doing it π
The Background:
Last time I installed the sl package and its source code but did not get time to dive deep into it, but this time I have explored the source code and am too excited to share my findings with you!
The Journey:
So first of all, let me be totally clear here β I haven't used ChatGPT a single time in this whole journey (even I cannot believe that, but it is what it is π)
I went through code in the pretty unstructured way which is very hard to explain, but here goes nothing:
The function exploration:
Firstly, I saw what all functions are defined
void add_smoke(int y, int x);
void add_man(int y, int x);
int add_C51(int x);
int add_D51(int x);
int add_sl(int x);
void option(char *str);
int my_mvaddstr(int y, int x, char *str);
And of course, the main function is always there!
The main() boss:
Now, I saw the main function in detail and got my first discovery
This is a small part of the main function, If you know C very well it might not be obvious what I found, if not you'll learn something new as well!
for (i = 1; i < argc; ++i) {
printf("index: %d, current char: %s\n", i, argv[i]); // Added by me
if (*argv[i] == '-') {
printf("in if: '%s'\n", argv[i] + 1); // Added by me
option(argv[i] + 1);
}
}
See this tiny snippet here? This has 2 C secrets (at least they were secrets for me π).
First one is that here argv is and array of pointers each pointing to a string (the command line arguments) defined by char *argv[]. Hence doing argv[i] gives the pointer to the string.
Now comes the real part, read carefully
doing argv[i] + 1 removes the first character from the string! If you knew that, tell me in the comments "You did not surprise me π" but if I did surprise you, I know what you'll be doing next! You might be tempted to Google or ask ChatGPT and ask what all the features of this? So instead of you working through this, let me help open this suspense for you!
So if you do argv[i] + n for a number n where n < strlen(argv[i]) then it skips those many characters in the string
For a better understanding, see this diagram:
This was a very new concept for me especially since I am used to python, javascript, java, etc. hence for a minute it felt like magic until I discovered what is in the diagram. I used the printf function calls to figure out exactly how this went about and after around 30 minutes of tinkering around, I got this solution and I have never been so happy learning this.
The great curse π«₯:
Now, if you have been programming in C for some time, you will know the curses library but I was new and did not see it coming. Initially I thought that the curses library should be some kind of a joke library because the first things that seemed extra-ordinary were the constants like ERR, OK, TRUE and FALSE and thanks to VSCode I was able to see what their values are without having to open the header file. So I just ignored it at the start but that was a small mistake (yeah, not a huge mistake, no exaggeration, we are programmers not writers π). So after this option function was over, I saw the next few lines:
initscr();
noecho();
if (INTR == 0) {
signal(SIGINT, SIG_IGN);
}
// endwin();
// return 0;
curs_set(0);
nodelay(stdscr, TRUE);
leaveok(stdscr, TRUE);
scrollok(stdscr, FALSE);
Now, after seeing this, any normal human being should go begging for some help online... excecpt me π
I tinkered the code a bit to see what this does (since my VSCode could not load up the documentation for the functions π€·)
This was my perfectly failing first approach:
initscr();
endwin();
return;
Yeah, that is another mistake, bigger than the last one π₯΄
So after I compiled the code and ran it, I did not find anything wrong happenning, I mean the code executed without doing anything as expected, no logs, no nothing β and it happened...
My terminal went to the next line but did not let me type anything! For a second I just panicked, did my computer freeze? What did I do wrong? Will my ubuntu become corrupt again? Shall I restart my computer?
But then I just opened a new tab in the shell window and closed the previous one, problem solved π΅
So then I thought why this must be a problem, I mean the main function as a whole also ends with the endwin() function and starts with the initscr() function, then after 2-3 more runs and 2-3 more terminals I tried this solution which did work!
initscr();
if (INTR == 0) {
signal(SIGINT, SIG_IGN);
}
endwin();
return;
This worked perfectly (at least I was able to type in the terminal after its execution π), then I inspected this code closely, the if statement does a very clever thing, if you press Ctrl + C during the execution of the sl command, it won't stop π
So after doing this I figured, the initscr() function might be doing something in parallel hence it needs some small delay to set everything up before the endwin() function destroys that.
So now, I had a program which just executed, no errors, no logs, nothing! Just silence!
So then I understood what the curses library did! It was not a joke library but it is used to get advanced control on the terminal of any system, it can change colors, animate things, listen for scroll or touch events which do seem impossible but according to my single google search it is possible π
The train finally appears π₯Ή!
Okay, so far so good! Now what, I wanted to see the π so I just tinkered the code again!
I now knew that functions I don't understand will definitely be from curses so I ain't touching those π
So I did what normal people nowadays don't do, I read the code alound in my head line by line and the for loop seemed a bit fishy
for (x = COLS - 1; ; --x) {
if (LOGO == 1) {
if (add_sl(x) == ERR) break;
}
else if (C51 == 1) {
if (add_C51(x) == ERR) break;
}
else {
if (add_D51(x) == ERR) break;
}
getch();
refresh();
usleep(40000);
}
You'd say What is so fishy in here meet? but look at those three functions at the end, I mean I do understand why usleep is there (without it, the train would move at 99.99% light speed π) and even refresh makes sense (literally the only curses function which made sense by just looking at it π₯Ί) buuut what is getch doing here? I mean is that not used to get character inputs in C?
If you thought the same, let me tell you that this function here is a member of the curses gang and should not be trusted at all! Hence I did exactly what I just said, did not trust the function and skipped it π«
Now, generally we programmers use if statements to eliminate things right? (At least I do) but here, there is a function inside! So, I commented everything and wrote a better code snippet myself π
add_D51(((COLS - (COLS % 2))/2) + 2);
getch();
refresh();
usleep(4000000);
Yes, I literally replaced the whole for loop with these 4 lines π
Notice how I kept the delay in the usleep so large? I wanted to see the details in the train and that actually came very handy later on. But before I show you the output, ((COLS - (COLS % 2))/2) + 2 this complex looking formula actually calculates the middle column in the terminal and passes it the to the add_D51 function to draw the train.
Output with this new 4 line code:
And yes, this train does stay in this same position for 1 second, and you can make it stay longer (10 seconds) by adding another '0' to the 4000000 microsecond delay I have given!
The genius of add_D51()!
Now, my curiosity got hold of me and took me straight to the add_D51 function:
int add_D51(int x)
{
static char *d51[D51PATTERNS][D51HIGHT + 1]
= {{D51STR1, D51STR2, D51STR3, D51STR4, D51STR5, D51STR6, D51STR7,
D51WHL11, D51WHL12, D51WHL13, D51DEL},
{D51STR1, D51STR2, D51STR3, D51STR4, D51STR5, D51STR6, D51STR7,
D51WHL21, D51WHL22, D51WHL23, D51DEL},
{D51STR1, D51STR2, D51STR3, D51STR4, D51STR5, D51STR6, D51STR7,
D51WHL31, D51WHL32, D51WHL33, D51DEL},
{D51STR1, D51STR2, D51STR3, D51STR4, D51STR5, D51STR6, D51STR7,
D51WHL41, D51WHL42, D51WHL43, D51DEL},
{D51STR1, D51STR2, D51STR3, D51STR4, D51STR5, D51STR6, D51STR7,
D51WHL51, D51WHL52, D51WHL53, D51DEL},
{D51STR1, D51STR2, D51STR3, D51STR4, D51STR5, D51STR6, D51STR7,
D51WHL61, D51WHL62, D51WHL63, D51DEL}};
static char *coal[D51HIGHT + 1]
= {COAL01, COAL02, COAL03, COAL04, COAL05,
COAL06, COAL07, COAL08, COAL09, COAL10, COALDEL};
int y, i, dy = 0;
if (x < - D51LENGTH) return ERR;
y = LINES / 2 - 5;
if (FLY == 1) {
y = (x / 7) + LINES - (COLS / 7) - D51HIGHT;
dy = 1;
}
for (i = 0; i <= D51HIGHT; ++i) {
my_mvaddstr(y + i, x, d51[(D51LENGTH + x) % D51PATTERNS][i]);
my_mvaddstr(y + i + dy, x + 53, coal[i]);
}
if (ACCIDENT == 1) {
add_man(y + 2, x + 43);
add_man(y + 2, x + 47);
}
add_smoke(y - 1, x + D51FUNNEL);
return OK;
}
Now just like you might feel a bit overwhelmed, but don't you worry, I got you! This function uses a really powerful feature of C which resides in the static keyword which allows a function to retain its value on every subsequent call after the first one! Cool right? New for me too π
So this function here is built by a very intelligent human being and I will tell you why I am telling so ahead. Firstly, don't look at anything other than these lines:
int y, i, dy = 0;
if (x < - D51LENGTH) return ERR;
y = LINES / 2 - 5;
Here, the if condition checks if the train goes out of the terminal screen or not and then y is calculated but here notice that y is the same every time since LINES is a constant, but this is necessary for the flying train feature!
And finally this legendary 4 line for loop (yeah, I removed for loop for 4 lines, now the for loop consists of 4 lines, good change π):
for (i = 0; i <= D51HIGHT; ++i) {
my_mvaddstr(y + i, x, d51[(D51LENGTH + x) % D51PATTERNS][i]);
my_mvaddstr(y + i + dy, x + 53, coal[i]);
}
So here, the my_myaddstr function adds this given string to the x and y coordinates and this is where the true genius of the programmer comes! Instead of writing 6 if statements to decide which train instance should be placed, the programmer creates an array d51 which consists of 6 arrays each with a different combination of the train, so then when he wants to decide the instance, he just indexes the array for the correct instance using the modulus operation and then one by one puts the whole string collection on the screen.
Frankly speaking, the first time I realized his trick I got so amazed and excited I was ready to share this with all of you but then I did not realize he had more tricks up his sleeve π₯²
So similar to how the first my_addstr function call writes the main train to the screen, the second call writes the coal cell of the train but here as well, look at the +53 operation, this is done so that the extra space in every line does not get seen and you know why there is an extra trailing space for each line? So that the next time when the train is the drawn, any older characters written get erased!
Where's my smoke?
And just after this for loop is the add_smoke function:
void add_smoke(int y, int x)
#define SMOKEPTNS 16
{
static struct smokes {
int y, x;
int ptrn, kind;
} S[1000];
static int sum = 0;
static char *Smoke[2][SMOKEPTNS]
= {{"( )", "( )", "( )", "( )", "( )",
"( )" , "( )" , "( )" , "()" , "()" ,
"O" , "O" , "O" , "O" , "O" ,
" " },
{"(@@@)", "(@@@@)", "(@@@@)", "(@@@)", "(@@)",
"(@@)" , "(@)" , "(@)" , "@@" , "@@" ,
"@" , "@" , "@" , "@" , "@" ,
" " }};
static char *Eraser[SMOKEPTNS]
= {" ", " ", " ", " ", " ",
" " , " " , " " , " " , " " ,
" " , " " , " " , " " , " " ,
" " };
static int dy[SMOKEPTNS] = { 2, 1, 1, 1, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0 };
static int dx[SMOKEPTNS] = {-2, -1, 0, 1, 1, 1, 1, 1, 2, 2,
2, 2, 2, 3, 3, 3 };
int i;
if (x % 4 == 0) {
for (i = 0; i < sum; ++i) {
my_mvaddstr(S[i].y, S[i].x, Eraser[S[i].ptrn]);
S[i].y -= dy[S[i].ptrn];
S[i].x += dx[S[i].ptrn];
S[i].ptrn += (S[i].ptrn < SMOKEPTNS - 1) ? 1 : 0;
my_mvaddstr(S[i].y, S[i].x, Smoke[S[i].kind][S[i].ptrn]);
}
my_mvaddstr(y, x, Smoke[sum % 2][0]);
S[sum].y = y; S[sum].x = x;
S[sum].ptrn = 0; S[sum].kind = sum % 2;
sum ++;
}
}
Now this function is as well just another crazy function which decides how to smoke looks like and at which level it resides every 4 writes (that is the use of the static variable x) hence if you see the screen shot of my train halted you won't see so much smoke while the actual sl command has a whole line of smoke. Now this function has a much much more consise implementation but a similar genius behind it!
In the if statement inside the for loop (not of 4 lines this time π) see that every some character until now is first erased and then redrawn according to the defined arrays, but a small and easy to miss thing is there, let's see if you can find it!
If it redraws every character to the right, how do we not see a smoke trail till the end in the output of the sl command? The smoke trail should not stop the end of the train but go till the end of the terminal?
If you think you have a solution, comment it down! I would love to hear it, no judging I promise π
So the real answer is the use of a space in every array, look at the declarations of the Smoke and Eraser variables, each array has a single space ' ' as its last element, this means that after some point, there is a smoke trail but the trail is made of spaces and not readable characters!
Now, if you were paying attention, you'd ask Hey meet, you said that the smoke trail stops at some point, but for that we have to define that ghost point right? The code ain't gonna do it for itself! and I'd say you have a really valid question but the answer is hidden cleanly in the code itself!
Look at this specific line:
S[i].ptrn += (S[i].ptrn < SMOKEPTNS - 1) ? 1 : 0;
What do you see here? There is a ptrn property of a struct is being incremented by 1 if the current value is less than SMOKEPTNS which is 16 and now look at this line:
my_mvaddstr(S[i].y, S[i].x, Smoke[S[i].kind][S[i].ptrn]);
So now if the the smoke trail grows larger than SMOKEPTNS value, the space character is added and if not, some other readable character is used! Cool right? I find it mesmerizing as well!
And after all of this nice smoke dance is done, the sum variable is incremented so that next time this smoke can be pushed back!
The Conclusion
This deep dive into the sl command was honestly one of the most fun and chaotic learning experiences Iβve had so far.
I broke my terminal (a few times), learned more about pointers and curses than I expected, and gained a whole new respect for the people who built such creative software decades ago.
What's Next?
In the next post, Iβll finally start modifying the train β maybe make it rainbow-colored or emoji-powered π
If you enjoyed this, leave a comment with your favorite C trick or just say βenjoyed!β β it genuinely keeps me motivated to keep writing these. π
Thank you so much for your time dear reader! Appreciate it π!
Signing Off




Top comments (4)
π§
Glad to know someone is reading this π!
π, Keep this up.
Some comments may only be visible to logged-in visitors. Sign in to view all comments.