Adam's terminal suite explained

Posted 2020-01-27

Core D Development Statistics

In the community

Community announcements

See more at the announce forum.

What Adam is working on

I finally put my terminal emulator (that I started seven years ago! and have been using for myself for the majority of that time) up on the dub repo.

http://code.dlang.org/packages/adr-terminalemulator

And meanwhile did some updates to my terminal.d library. Let me describe what they all are:

terminal.d

terminal.d is my library file for applications that run inside a terminal (e.g. xterm, rxvt, konsole, putty, the linux console, etc.) or the Windows console. It provides input events (key pressed, mouse clicked, window resized, etc.), high-level functions (getline, tab completion, etc.), and output functions (write text, move cursor, change color, etc.). It aims for broad compatibility with user interfaces, meeting the user where they are.

terminalemulator.d

terminalemulator.d is a library file that emulates a terminal in code. This means it takes the raw bytes a program would write to a terminal and maintains internal state. It offers abstract functions you can implement in order to display this state to the user, and functions you can call to translate user input into the raw bytes a terminal would send back to an application.

Like with most my library modules, it has very few dependencies: only the color.d module from my arsd repo. Otherwise, it stands alone in its most basic configuration, not even doing I/O itself.

It also has a mixin template for use by the frontends. This mixin template provides a few middleware adapters - forkpty for Posix systems interacting with normal programs that run in the terminal, CreatePseudoConsole for Windows 10 systems interacting with Windows console programs, libssh2 for interacting with remote systems, and a simple pipe driver for older local Windows programs.

These all help implement the frontends.

main.d

main.d is the main frontend and provides a class that implements the terminalemulator.d abstract class via a GUI window. It uses my simpledisplay.d to provide this main window. When you interact with this window, it calls the appropriate methods in the terminal emulator and displays its screen in here.

It does fairly little - terminalemulator.d contains the bulk of the code. main.d also imports minigui.d to provide a debugging and details UI when you press ctrl+alt+f12, though I haven't put much in there yet.

dub run builds the main.d program, running this gui.

It currently works in Windows and Linux. When simpledisplay.d works on Mac again, it should work there too.

You can also dub run -c ssh-gui to build the ssh middleware on Windows or Linux, using libssh2 (the .lib for Windows is in my arsd repo on github). It will need the runtime dynamic library and you must use a ssh key from the command line to set it up.

nestedterminalemulator.d

nestedterminalemulator.d is a frontend implementation built on terminal.d, running the custom emulator inside an existing terminal.

I don't maintain it as well, since it is mostly a proof-of-concept or debugging aid to me. It should work on any OS when it is updated.

detachable.d and attach.d

detachable.d and attach.d are separate components, but intricately tied together. It takes the idea of nestedterminalemulator but turns it into something very useful: a grouping of terminals running inside another terminal.

A screenshot showing attach.d running inside main.d on my system

If you have used GNU screen before, you know the use case for this. You can make a session grouping various terminals together for a particular task, and then detach and reconnect to them as needed, including across network connections, picking up exactly where you left off.

detachable.d runs an instance of terminalemulator.d in memory, with a command wrapper interface for talking to attach.d. When a frontend detaches from it (including closing a terminal window that hosts it, or a network disconnect), it simply keeps living in the background, waiting for a new frontend to reattach. It can be compiled and run separately for debugging, but is typically embedded inside attach.d's program.

attach.d is a terminal.d based frontend that can display, manage, and communicate with detachable instances. If you've used screen, dtach, or tmux, you'll probably be able to use this easily; it works similarly.

You can run this with dub run -c attach -- your_session_name.

These only work on Posix at this time, since it communicates between detachable and attach via a unix socket, using unix functions, and spawning the new process with fork(). There's no technical reason why it wouldn't work on Windows via a named pipe and CreateProcess either, I just haven't implemented it since I personally entirely use the detachable sessions on my Linux box and connect to them from Windows.

Why?!

The question on your mind is why? What's the benefit of my custom implementation instead of using any one of the myriad existing options?

Well, it mostly came down to two little things for me: adaptive color palettes and shift+page up just working via ssh attached sessions. I did a few other things too, like long text wrapping, spammed output management, and even inline images, but those two were the things I really wanted and found kinda painful to work into the other options I had.

Then xterm broke on my computer, giving me the final impetus to go all the way and switch fully over.

Full screen apps scrolling

I used to be a heavy GNU screen user, but one thing that bothered me is the shift+page up key combination doesn't work there. It is instead captured by the outermost window which is totally useless with a screen session inside.

So what I do is if an application requests the alternate screen (my terminal.d calls this Terminal(ConsoleOutputMode.cellular); what most full-screen terminal programs do), it also gets additional user input.

The keystrokes for shift+page up/down, shift+arrows, scroll lock, and others all just get sent as the same keystroke pattern as other keys. This lets the next layer handle it instead.

So these keystrokes are actually handled in behavior inside terminalemulator.d rather than in the frontends, who just need to tell the parent class what the user pressed.

It also means a user program could hook these to do their own thing... so long as they are OK with them not working on anyone else's terminal since they will never generate the necessary input sequences.

Palettes

It really bothered me to have blue text on black background and yellow text on white background. There's some color palette combinations that make this less awful, but what I really wanted was the colors to actually change based on background.

If you tell my terminal emulator to print blue on black, it will use a lighter blue than if you tell it to do blue on white. And similar with other various combinations. I reserve the right to change these colors and combinations at any time for myself, and with my own code, it is easy to do. All based on what is more readable to me. You might not even notice this when using the program, but you probably have noticed pain in other terminal emulators trying to read certain things.

Not straining my eyes is worth a lot to me; I spend a *lot* of time looking at my terminals.

Clipboard

My emulator implements the clipboard sequences xterm defines but generally compiles out for security purposes (so you don't ssh into a box and have it steal your clipboard data without your knowledge). But since I only use it for systems I trust and control I like it.

The value in such a feature for me is that I can copy and paste into attached sessions from Windows on ssh - something I do frequently. With existing things, data tends to get mangled if I try to take it across that boundary.

Window icons and taskbar notifications

Another minor feature that I find remarkably useful thanks to how much I use this is that my terminal emulator can change the window icon that appears on the taskbar. The utility.d function shows the sequence to do it, and the attach.d session file can specify an icon and unique title for that session, which it will display in the taskbar.

Seven instances of my terminal emulator

Each item in that taskbar is the same program, attach running inside my terminal emulator, but each is also recognizable at a glance. These icons also display in the Windows taskbar when I attach to it remotely through ssh - another benefit of me controlling both the gui terminal emulator (on *both* systems I use!) and the attached session inside.

Similarly, a program running inside a session can request attention from the user and that will be indicated in the taskbar via the appropriate GUI mechanism. An ascii bell output in a background terminal in attach will also trigger that behavior.

A few others

I also make it possible to process shift+enter with my emulator as well as other little things, but I don't use most of those yet. The majority of my enhancements are compatible with existing programs as much as possible, but responding to those key combinations require the client program to understand them.

My terminal.d library does support them, meaning you can get my special features when using my emulator (again, down the entire stack of attached and nested instances) and gracefully degrade on other systems.

And, of course, since I own 100% of the code, I can do whatever else I want too :) The big challenge is doing so in a way compatible with other things, but I've found many ways to make it work, some being compatible sequences and some being feature detection and runtime (I'm working on getting this all the way into terminal.d so it just works for others too).

Performance

I don't think throughput is important. You can't read text flying by at 60 fps anyway, and a terminal is meant to be a human interface device. So instead of even trying, I batch the data and display it differently in chunks. This also saves bandwidth and maintains ctrl+c capabilities on ssh.

The latency is good enough to me.

Inline images

You can write out images (via the utility.d helper) too, which adds them to the scrollable output. I find this is rarely useful but it is very nice to have when I do - again mostly over ssh when displaying an image can be very slow through X.

Horizontal scrolling

Press scroll lock in the terminal. Then press the arrow keys to scroll in all four directions. This helps me read exceedingly long lines like database table output.

Rejected features

I actually implemented 24 bit color, but I found it was not only utterly useless, but actually counterproductive. The simple implementation in terminalemulator.d used an awful lot of memory (2x as much!) with 24 bit color enabled than with it disabled... and it complicated the adaptive palette code to keep combinations legible, so I removed it. (Much of the code is still there under a version(with_24_bit_color) if you want to try playing with it anyway.)

It still recognizes the 24 bit color codes from the terminal applications though, it just rounds them off to a 256 color palette instead. Heck, sometimes I think I should further round it off to 16 colors! (That's what terminal.d does on Windows console btw, since that system has only 16 colors, so it gracefully degrades client applications.)

I also didn't do any built-in tabs, since attach.d makes its own inline tabs anyway.

In general, I just try to remember that a terminal is meant to be used by people to interact with computers via text. So anything that doesn't work toward this goal is not important to me.

Future directions

I want to expand some more things - like that ctrl+shift+f12 debug window - and maybe do some other radical things, but this moves very slowly. I'm generally happy with it as it is and use it very heavily... while simultaneously thinking Linux terminals are trash from top to bottom and we should just do gui programs instead. So I love to hate it lol.

I may also offer a dub subconfiguration so your terminal.d program will automatically spawn a custom window, so you write your program as if it is a terminal target yet it is actually a gui target. But right now I am focused on the progressive enhancement and graceful degradation angles for compatibility with existing terminals and programs as my first priority.