TL;DR: Terminal Click is a desktop terminal that runs natively on Windows, macOS, and Linux. Unlike most emulators, we recognize your intentions and support you with powerful graphics. Join the beta.
Imagine you invoke
make to compile your project but an error occurs:
This particular output is tiny but it could’ve been huge and scrolled off your screen! Then you realize you needed to grep for a keyword: curl. So, what are your options? I see two immediate paths:
Option 1: You had the foresight to redirect
maketo a text file
Option 2: Run
makeagain and pipe it to
Neither solution appears efficient: you must either remember to redirect output or squander CPU cycles by recompiling everything from scratch.
Wouldn’t it be wonderful if your terminal natively recognized output so we can reuse it?
Ah, much better.
With few exceptions, today’s terminals lack support for output reuse: you’re always typing in dead text and receiving equally lifeless responses. Now you understand why we’ve named this emulator Terminal Click: you’re encouraged to click around with the mouse! It’ll react to things and do some cool magic tricks.
The rare terminals that do offer this feature are limited in their potential. We’re in a better position to unlock more expressive power (of course you’ll be the judge. More on how it all works later.)
When I work inside a codebase and make a commit,
git kindly tells me how many lines I added and how many were deleted. Tragically, it never shows the net amount of code gained, so I’m forced to open the calculator app since I’m no good with mental math.
To make matters worse, this workflow disruption triggers even more distractions. Leaving the terminal to open a calculator prompts me to launch Firefox and browse too much YouTube or Hacker News. Indeed, we should discuss my propensity to procrastinate at another time…
Luckily this particular problem vanished after building a calculator into Terminal Click:
Not much else needs to be said! As soon as we enter numbers the terminal interprets them as valid arithmetic.
There is a concern of what might happen if (for example) we wish to run executable 20 inside folder 4: in which case 4/20 is now ambiguous. Terminal Click defaults to performing division, but I envision providing an option to override this behavior.
Integrated File Explorer
Okay so I made my git commit and used the calculator. Nice. Now I’d like to go to my parent directory and look into it. I could call
cd .. followed by
That’s nine keystrokes total though (including pressing the return key each time.) It seems very clunky.
What if I told you there’s a significantly shorter alternative?
Here’s the intriguing part: by simply pressing
<CTRL> (which can be remapped) the cursor leaps to the working directory! We instantly gain a glimpse of all the files it contains. While keeping
<CTRL> pressed, a quick tap on backspace automatically performs a
cd to the parent, showing a preview of it too. Releasing
<CTRL> returns the cursor to normal position.
All accomplished with two keystrokes.
This cursor displacement activates what I call the editable filepath. It lets us dynamically modify our working directory in real-time, complete with auto-complete and fuzzy filtering. So, when we’re in this new mode, finding and opening a file within the terminal becomes a breeze. Let’s put this mode to use and search for a cute sketch of a chat client I have lying around. I’ll go slow on purpose:
Here’s another handy trick: suppose you want to navigate parent folders and explore them, but return safely to your working directory. In such cases you trigger editable filepath and use backspace to move, pressing Z to undo. Let’s try it on Windows to shake things up a bit:
I find it to be a quick and nifty pattern. While I can’t speak for others, personally, relying on
ls is an outdated and frustrating experience. I’m super relieved I no longer need to resort to that.
Lastly, it’s worth noting that output reuse and the file explorer are able to work together. Remember how we dropped our
make results into
grep? Let’s save that output to a folder instead:
Now, here’s the million-dollar-question every terminal author must consider: What if the terminal preserved the metadata of user commands? When a user runs a bunch of commands (e.g.
git, etc.) and then repeatedly presses the Up arrow, they are presented with a list of command names—devoid of any context.
But what if we could show them more? Let’s run a few commands and then dive into our history:
Although I’m displaying only a timestamp and the output, enhanced history is the feature where the audience erupted in applause at Software You Can Love. Much more will be possible, but I’m leaving you with this tantalizing glimpse for now.
Note: Once my talk is published I’ll provide a link for reference. In the meantime you might want to read this awesome blog post by Gordon Cassey, who seems to have enjoyed Terminal Click!
Native & Cross-Platform
Terminal Click is a native desktop application and works on all modern OSes. This means you keep the same habits and shortcuts regardless of which operating system you’re in. The native performance is a most welcome bonus too.
It’s worth mentioning Terminal Click relies on Microsoft’s ConPTY, which was introduced relatively recently. Consequently, we require Windows builds from 2018 onwards because earlier versions lack a pseudo console infrastructure.
Oh. You may have noticed the previous demos were served in Linux, with a sprinkle of Windows. We’re missing the fruit topping: so let’s compile a party donut on my M1 Macbook Air 🍩
Pretty neat huh?
I hope you’re feeling as psyched as I am about Terminal Click and would like to get your hands on it.
Closed Beta: August 2023
Whether you opt for the physical track or the online track, you’ll be eligible for the download.
Open Beta: Early 2024
If my conferences aren’t your cup of tea *clutches pearls* the open beta would ship in early 2024 at an undisclosed date.
Note: You’ll never need to log in or create an account to use Terminal Click. There’s no telemetry either. I’m just an indie dev who wants to sell you an offline binary. (Take a moment to mourn the fact that I feel the need to say this.)
Why Are Terminals Unchanged?
One burning question arising each time I demo Terminal Click is: “Abner, why are so few people experimenting in this space?” The landscape of competition is barren and it took me months to dig up the three reasons behind this stagnation.
1. “Abandon All Hope”
It turns out terminal emulators are, in theory, expected to replicate the hardware of machines which aren’t in use anymore. This is because, ostensibly, there were thousands of programs written in the past targeting these legacy terminals and we want them to continue working today.
The price to pay for backwards compatibility? We can’t really understand a robust emulator like
xterm. Their codebase will greet you with this candid introduction:
Abandon All Hope, Ye Who Enter Here
This is undoubtedly the most ugly program in the distribution. It was one of
the first "serious" programs ported, and still has a lot of historical baggage.
Ideally, there would be a general tty widget and then vt102 and tek4014
subwidgets so that they could be used in other programs. We are trying to
clean things up as we go, but there is still a lot of work to do.
The lack of accessible emulators  hinders education, which in turn stifles attempts at innovation. Additionally, there’s a special role your OS has to play to meet the demands of modern terminals (especially when the shell is involved.) You need to cooperate with the kernel closely and it all grows hairy rather quickly.
2. Terminology is All Over the Place
The term “terminal” itself requires clarification. It’s a loaded one: someone could be referring to emulator, or the shell, or a pseudoterminal. If it’s the pseudoterminal, that may refer to
pty device driver pairs on Unix but on Windows it’s either
conhost or the more recent
ConPTY. It takes a hot minute to dissect these different components.
Let’s not get overwhelmed though. In this essay we are keeping our words obvious and high-level.
3. Enormous Maintenance Burden
If our objective is to support programs from bygone eras it won’t be long until we have a codebase as devilish as that of
xterm. This poses a nightmare when it comes to bug fixing or contributing something new. Most people choose not to embark on this challenge. Understandably so.
Overcoming these three obstacles is annoying but not impossible. (That’s precisely why this website exists.) As you dive into the next section you’ll either be impressed or horrified by what I’ve accomplished.
Let me know in the comments 🙂
How Terminal Click Works
We’re now at the beating heart of Terminal Click. I want you to understand the special trick that unlocks the awesome stuff you witnessed, but I want to make sure you understand some crucial terminal history.
If you’re already familiar with terminal concepts you can skip ahead to Our Special Trick.
First and foremost, it’s essential to grasp the fact that terminals were not standalone devices.
Terminals Were Not Computers
In the past, terminals were sleek but essentially dumb.
Although it may appear to be a complete computer, it is not. Terminal machines needed to be connected to a real computer, known as a mainframe:
The twisted cable you see in the middle is not a Twizzler candy; it represents a serial cable. Indeed the connection between a terminal and a mainframe was made with an RS-232. This would let the mainframe send bytes to your terminal screen and you could send bytes back to the mainframe by typing on the keyboard. Multiple terminals could be hooked up to a single mainframe.
A Trickle of Bytes
Imagine yourself as a programmer in the 1980s, working in the IT department of your local university. The faculty task you with making a program that displays student records. They’re excited about their new fancy terminal—which supports color!—and they want you to render a student’s passing grade in green and any failing grade in red.
The program you are about to write, most likely in C, will live on the mainframe. Remember, you only have a single channel (the serial cable) to communicate back and forth with the terminal device. To show a failing student grade in red, you might write the following code snippet:
printf("Student Grade: ");
printf(“\033[0;31m”); // ANSI Escape Code: RED
// Escape is: \033
// Color code is: [0;31m
printf("\033[0m"); // ANSI Escape Code: RESET (no more red after this)
// Escape is: \033
// Reset code is: [0;31m
This is a valid C program to this day.
Notice that there are bytes intended to be printed on the screen, such as “Student Grade,” which are printable ASCII characters. However, there are also some unusual bytes:
"\033[0m". What the heck? Well, these bytes are used to manipulate the state of the terminal hardware.
By sending a special value (
\033) you are informing the terminal that you want to configure something, in this case, changing the color to red (
[0;31m). Then you print the letter “F” to represent the failing grade (how sad!). Finally, you alert the terminal that you wish to configure something again: resetting the color code so any subsequent text is NOT rendered in red.
Terminals were actually quite malleable, letting you do more than just change colors. You could move the cursor around, trigger an alarm bell, clear the screen, update keyboard lights and whatever else the device supported. These special bytes used to control the terminal are known as ANSI escape codes. By “escaping” printable ASCII characters, you could send these unique codes to alter how your terminal machine behaved.
This act of using printf to send control instructions and normal content through the same channel (a continuous stream of characters) is known as in-band signaling. In-band signaling was necessary because of the way terminals and mainframes operated at the time.
We have kept this system ever since.
Fast Forward: Today’s Terminals
As we transitioned to personal computers we wanted these old terminal-oriented programs to still work somehow—including the cult favorites emacs and Vim.
So we took a look at the old architecture:
and said “Okay, we don’t use terminals anymore. Mmm serial cables and mainframes aren’t a thing either—we own desktop PCs and Macbooks now!” So we crossed those terms out:
And decided to simulate THE ENTIRE THING in pure software:
When you run any command-line program in-band signaling is still happening behind-the-scenes: there’s a Linux pipe  responsible for transmitting printable characters and the ancient escape codes. In fact, if you’re creating a command-line program with fancy colors or special effects, the library you’re using must emit those peculiar bytes behind the veil, exactly like the C program shown earlier.
To put it differently, there is always a barrier between your emulator (the graphical application) and the command-line program you’re running. They are separated by a pipe and can only communicate through that pipe.
Phew! All right, you’re now just missing one final piece of the puzzle…
Imagine you’re using Vim and in the meantime you want other programs running in the background. Or maybe you want to pause Vim, compile something, and return.
Our industry has decided to support these needs by hiring a manager: one that barks orders and holds the reins over these binaries just itching to connect to the pipe:
The shell is that overzealous manager. When you launch a desktop terminal it always creates a pipe and connects to a shell such as Bash.
So let’s say you want to run the command
zig. Here’s what actually happens…
The terminal emulator passes along each character you type, one by one, to Bash. The emulator itself has zero understanding of what you’re trying to run (in this case the Zig compiler.)
Once you press
<Enter> it’s entirely up to the shell—a completely separate program—to make any sense of what
zig means. Much like an old telephone operator Bash will search for what the user wants and patch up the binary to the pipe, letting the output be displayed in the emulator.
This is the reality of how terminals work today.
Our Special Trick
YES. HAHA. *cackling* You’re ready to grok the magic recipe behind Terminal Click. Voilà:
Yup. We’ve killed the shell. So uh. What if we want to run
Well we can’t just blindly pass along the characters
g down the pipe anymore—there’s no one on the other end listening! Instead, we must allow the emulator to build up an actual understanding of whatever it is the user’s typing. In other words, the emulator needs to have a brain of its own:
<Enter> and Terminal Click knows you’re trying to invoke
zig and it doesn’t require a shell to find the binary. We take charge and connect it to the pipe directly:
And furthermore, since we’re finally aware of what’s going on, we can keep track of metadata too:
This is our secret sauce: Terminal Click is allowed to reason about user input and react to it graphically. We’re not subservient to a shell.
The consequences of eliminating the shell and letting the emulator assume its responsibilities are insane. I would argue insane and powerful… though not without cost.
What Are We Breaking?
No shell means no bash scripts. All the features you’ve seen are local to the graphical emulator, meaning you can’t use them over the network: if your job involves working under
ssh all day, you’re out of luck. Who knows what else we’re accidentally destroying in the process.
At the same time, who knows how many things we can recreate that are superior?
The upsides are becoming attractive enough to justify breaking backwards compatibility. There’s enough of us who will fall in love with a self-aware emulator: where our commands become alive, brimming with interactivity.
We’ve only scratched the surface.
Closing Notes: Tech Stack
I’m keeping these notes brief because I dive deeper in my Software You Can Love talk. I’ll link it once the recording’s out.
- Cross-Platform: Native for Windows, Mac, and Linux.
- Vanilla C: Written with arena-based memory strategies.
zig build: Zig is a drop-in C/C++ compiler. I clone the repo on any OS and invoke zig. That’s it, like wizardry. (Here’s my build.zig)
- Zero Legacy Baggage: We don’t emulate obsolete control sequences.
 The lone exception is the suckless terminal,
 It’s not exactly a Linux pipe but I don’t want us to get bogged down with rabbit holes. I’m also avoiding the subject of signal handlers, IOCTLs, and kernel buffering. We don’t need those details here.