TTE: Terminal Text Effects
chrisbuilds.github.io
chrisbuilds.github.io
One of the coolest things is not just the ability to pipe cat outputs into it, but that it doubles up as a python library- next time you're making a throw away CLI with print outs and prompts, why not make it insanely jazzy?
EDIT: Don't get me wrong, I think it's really cool, but I wouldn't use it as a universal hammer.
On the one hand, these tools are going to be used for Serious Business™ but on the other hand... I owe it to 14 year old me. I'll probably add something to allow disabling it with a flag file that can be created with the tool or manually. ¯\_(ツ)_/¯
./myfancytool -showfx
Or have an "on first run only" configuration that disables it for subsequent usage of the tool ¯\_(ツ)_/¯
printssh() {
tte --input-file YOUR_FILE --frame-rate=500 $(echo "beams binarypath blackhole bouncyballs bubbles burn colorshift crumble decrypt errorcorrect expand fireworks middleout orbittingvolley overflow pour print rain randomsequence rings scattered slice slide spotlights spray swarm synthgrid unstable vhstape waves wipe" | tr ' ' '\n' | shuf -n 1)
ssh "$@"
}
alias ssh="printssh" /home/keeb/code/projects/login/motd.sh
And the contents of that file is... #!/usr/bin/env zsh
values=("bubbles" "slide" "beams" "rain" "pour" "synthgrid" "unstable" "poop")
len=${#values[@]}
index=$(( (RANDOM % (len - 1)) + 1 ))
selected=${values[$index]}
cat /home/keeb/code/projects/login/motd | tte $selectedThat's brilliant. I love it. This is the kind of whimsical thing done purely for the love of it that makes my day.
Every time you run it, it triggers a random screen saver effect. Fun when it's on a timer and triggers so your work colleagues wonder wth is going on.
Available in the menu: Tools → Games → Zone Out
Very subtle uses of this could go a long way.
The only one that comes to mine for me is FileBot[0] - and if the loading screen made it take ANY additional time, I'd be annoyed.
Anything that compresses or decompresses archives, like gzip/gunzip, tar, etc.
Anything that performs longer running recursive disk activities, like du, find, etc
I’m sure there are plenty more scenarios I haven’t thought of. Though to be clear, I definitely don’t want to see this as a default enabled option in any of these.
If I’m looking at real time output, it’s because I’m doing something manually because something isn’t working the way it’s supposed to, and I’m in no mood for anything that doesn’t help me understand and address the issue.
This work is impressive. I love it. For some reason I really like “printer.” I’d be all about it for games or toys. But too many tools for real work try to do cute Unicode things or fancy colors or animations. I don’t need it on anything I will ever have to develop or troubleshoot, personally. After the first lengthy wait for a broken container to come up, I’m well over the colorful attitude.
An example of this done well would be ‘pacman’ package manager where an Easter egg can be enabled to turn the progress bar into a little Pac-Man gobbling pills. It’s not there by default, but it can be enabled if you know how.
Personally, I’m in the same camp as yourself where I’d prefer our tools didn’t have all this cutesy stuff. But people like us seem to be a dying breed.
No.
Although I don't know if much software does it, that should still be possible to do it, so these animations could theoretically be auto-disabled on slow links. (And you could manually override the baud rate in your mosh terminal if necessary since it doesn't really have any other effect in a virtual terminal, not like when using an actual serial port, I mean.)
As an "about" screen, or in a opt-in funny theme why not !
It's been a while since I dared assume anything at all was so obviously absurd that no one could ever think to do it - not least since significant portions of my career to date have consisted of cleaning up after people who had.
Production will always be an inappropriate place. QED.
Edit: popular example of less invasive animations would be new docker cli, (pulling, buildx build)
Better than either, of course, would be effort spent on speeding up the build in the first place. I realize that's less fun, though.
In general, the critical development path is not a place to thoughtlessly add friction.
What I'd really like to see is this supported in the terminal emulator itself as an idle mode, i.e. don't run this in the buffer directly, but rather as a second buffer. This could function like a "screen saver". Once you interact, your primary terminal would be fully restored.
I’d love some of these effects to fade in/out within live terminal sessions.
This instantly reminded me about Ansible and how it annoyed me that ANSIBLE_NOCOWS had to be enabled to disable the default output of Ansible with cowsay [1].
https://github.com/erkin/ponysay
https://blog.yjl.im/2016/01/ascii-pony-systempony-screenshot...
All these serious enterprise people worried about their cows, but then they do install ‘cowsay’ on their systems xD
In lieu of the submarine cable, something closer to a wet string was being routed over. It had such high latency and packet loss, that a watchdog timer I'd implemented on the stations was timing out.
Fortunately, the remote access we'd built into our stations (SSH and OpenVPN) still worked, albeit at slow speeds, like a 300 baud dialup, and crazy-high latency.
Having occasionally dealt with performance a bit like that as a kid, and knowing my way around Linux, it was like "I've been training my whole life for this moment."
So I just flexed the old command-line-and-editor-when-you-feel-every-byte-transmitted skill, and got the stations working, before the factory even knew about the submarine cable, saving our infinite-9s uptime.
It was nothing compared to what NASA does, but terminal animation effects would've ended both of our missions.
There is no link that could be used in preference. Any repo that the factory stations could access would be unusable by Singapore AWS. And any repo that Singapore AWS could access would be unusable by the factory stations. But the two ends of the link don't have to use the same repo.
* SITUATION: Factory reports MVP factory stations for pilot customer "not turning on" for the day, reason unknown.
* TASK: Get stations up in time for production line, so startup doesn't go out of business.
* ACTION: Determined cause was unexplained networking problem outside our control, and that was triggering some startup time checks. But that the stations were resilient enough that I could carefully edit the code on the stations to relax some timeouts, and enough packets were getting through that we might be able to operate anyway. After that worked for one station, carefully changed the remaining ones.
* RESULT: Factory stations worked for that production day, and our startup was therefore still in business. We were later advised of the submarine cable failure, and the factory acknowledged connectivity problems. Our internal post mortem discussions included why the newer boot code hadn't been installed, and revisiting backup connectivity options. From there, thanks to various early-startup engineering magic, and an exciting mix of good and bad luck, we eventually finished a year contract in a high-quality brand's factory production line successfully.
No technical wizardry in the immediate story, but a lot of various smart things we'd done proactively (including triaging what we did and didn't spend precious overextended time on), plus some luck (and of course the fact that some Internet infrastructure heroes' work had them routing any packets at all)... all came together... and got us through a freak failure of a submarine cable we'd never heard of, which could've ended our startup right there.
(Details on Action, IIRC: Assumed command of the incident, and activated Astronaut Mode manner. Attempted to remote access, and found network very poor. Alerted factory of network problem in their connectivity to AWS Singapore, but they initially thought there was no problem. Could tell there was some very spotty connectivity problems (probably including using `mtr-tiny`), so focused troubleshooting stations on that assumption. Realized how the station would behave in this situation, and that the boot-ish checks for various network connectivity were probably timing out. Or, less likely, there could be a bug in handling the exceptional situation. Investigating, very slowly due to poor Internet, found that the stations didn't have the current version of the boot code, which would've reported diagnostics better on the station display, so factory personnel might see it and tell us. Using `vi`, made careful, minimal changes to the timeouts directly on one station, in the old version of the the code on there (in either Python or Bash; I forget). Restarted station, and it worked. Carefully did the same to the other stations. Everything worked, and other parts of the station software had been done smart enough that they could cope with the production day's demands. Despite the poor connectivity, and the need to do network requests for each production item that passed through the station, before it could move on.)
It was fun to think about the path each packet was taking - from my laptop to the plane router, to a satellite, back to a digital ocean computer in the UK, to my home computer in NJ, and back. It wasn’t as bad when you think about the magic there.
Regarding planes/cafes/guest/etc. WiFi, I now usually put any "emergency remote plumbing access" on port 443 (though usually not HTTPS), to reduce the likelihood of some random non-SPI ruleset blocking us in an emergency.
"line is ~300 baud but only transmitting 7 bit but we need to send this critical file.. which is in EBCDIC!"
I'd take a solid 300 baud connection over a spotty cellular connection during most emergencies.
Having worked and played on flaky high-latency and low-throughput networks much of my life, I mostly visualize things in my head—as you likely mean by 'command-line-and-editor-when-you-feel-every-byte-transmitted'. Open a connection and queue your commands; wait for output. It works if you don't make any typos.
Preferably, when the connection is too slow or flaky (bad cellular), I make a local script and echo the script to be executed remotely to a file on the remote server, and then execute it with nohup - with the input to create the script and execute it coming from a pre-made file on my local machine redirected to SSH.
Bad cellular often works in bursts, in my experience. Also, redirect output to a file if needed.
This works nicely in situations where your connection may drop frequently for periods exceeding timeouts. On that note, keep ssh timeouts high. With really spotty cellular you might have the network drop for 5-15 minutes between reliable transmissions. SSH connections can stay alive nearly indefinitely if the timeouts allow for it and IP address don't change.
mosh hostname -- screen -S philsnow
I don't think I've had to use this over a truly terrible connection, but mosh worked a treat a ~decade ago while tunneling through DNS from a cruise ship that charged exorbitant rates for wifi while underway, but which allowed unlimited DNS traffic.> It works if you don't make any typos
mosh helps a bit with that too: you get local predictive echo of your keystrokes and you can't "recall" keystrokes but you can queue up backspaces to cover up your typos. Doesn't help where a single typo-ed keystroke is a hotkey that does something you didn't want, though.
Example: ls | tte —no-color slide
I’ll add environment variable checks to the todo list.
Maybe it does, I didn't check.
That being said, there are some environment variables that could be referenced. I'll add this one to the list for a future update.
self.move_cursor_to_top()
sys.stdout.write(output_string)
sys.stdout.flush()It took several text files with ASCII "pictures" (character data only, no control codes, 80x24) as animation frames, and calculated simple per-line "diffs". Then it generated a sequence of cursor movements to only update the affected areas, skipping large parts of the picture.
That made it much faster than the naive overwriting the whole screenfuls from top, with a visible delay between parts of the screen. My version was able to run "simultaneous" small animations quickly at distant parts of the screen, because they took very few bytes to navigate to and update.
*
*.
* o/
-/M
_H_
E.g. a "juggler" like this could juggle quickly, inside a mostly stationary "circus", with "flags" waving high above on top of it.With current terminals giving you 60fps in true-color mode, it makes little sense, of course.
IIRC, ncurses does this under the hood.
Have you added a command-line switch for the direction of gravity?
Some of the animations (eg Pour), could fall up the screen instead of down it. That way, the user can start reading the text immediately.
I have not implemented gravity, haha. I have actually worked on a custom terminal game engine in the past and realized with TTE I am very small amount of scope creep away from landing in that territory again. I promised myself I will not implement physics or collision. Keeping it simple. That being said, as with the example pour syntax above, all of the effects have a ton of config options to allow for most obvious variations.
3d... 3d... 3d... you know you want to.
My favorite is Beams (the one at the very top of the page), reminds me of MGS1 “game over” screen animation a lot.
Now I can get that nostalgia in my terminal
The thing is that as a heavy user I do use jq, terminal triggers and my own stdout colouring/parsing apps that e.g. highlight and parse timestamps or numbers. It all kinda works via endless Cmd+C, "pbpaste | jq" etc but it feels wasteful.
I want to interact with what terminal shows me, not just look at it and copy/paste with broken formatting to somewhere else.
https://github.com/cacalabs/libcaca/tree/main
[edit, also https://en.wikipedia.org/wiki/AAlib]
Or just do `apt-get|dnf install caca-utils`
I’ve been waiting for one or more terminal emulators to get together and add some ridiculous new escape codes, such that animated effects (or things like collapsible sections, font size, “form-input-ness” and “form-button-ness” ala 3270, etc) all get treated as part of the state of a given character cell / run of character cells.
Heck, it’s 2024; an application should be able to use an escape code to set a soft-word-wrap mode for longer-than-viewport-width text, and have the TTY render text accordingly, rather than everything being hard-wrapped (baked mode) or not wrapped at all (raw mode) and only being able to get soft-wrapping using a pager!
(There’s precisely one thing like this I’m aware of terminal emulators adding in recent memory: clickable links, ca. six years ago.)
93 points by andrewstuart on Sept 8, 2020 | hide | past | favorite | 63 comments
And they are often disabled by default, as a potential security risk. We don't get to have fun things, do we? (also worth read: CVE-2003-0063, abusing escape seq is unfortunately a valid concern against adding more stuff).
On the other hand, more and more emulators are adding support for various graphic protocols (sixel, iTerm2 format, kitty format).
> I’ve been waiting for one or more terminal emulators to get together and add some ridiculous new escape codes[...]
Well, it's not much, but mintty apparently has some interesting stuff like audio support[0], and codes for font size and font family[1][2].
iTerm2 also has a bunch of custom escape sequences of varying level of usefulness starting from displaying fireworks animation on cursor position to sending system notifications[3] (although sadly I could not get the last one to work for me).
For some semblance of forms, you can check bubbles[4] and gum[5] (binary to easily incorporate the components into shell scripts).
[0]: https://github.com/mintty/mintty/wiki/CtrlSeqs#audio-support
[1]: https://github.com/mintty/mintty/wiki/CtrlSeqs#font-size
[2]: https://github.com/mintty/mintty/wiki/Tips#alternative-fonts
[3]: https://iterm2.com/documentation-escape-codes.html
Well, that and a clickable link would conflict with mouse reporting if both were active at once.
Given that mouse reporting exists, and "has precedence" due to its age, I think iTerm's choice — to style the anchor-SGR text either way, but to only make them actually act like links rather than text if you hold a modifier key — is the only "correct" behavior for rendering anchor-SGR text, regardless of security concerns.
But that really wouldn't be true for most other potential novel graphical-rendition "styles." Anything that's non-interactive could certainly be on by default.
I'm definitely of the opinion[0] that we haven't yet reached the limits of the "terminal emulator" UX paradigm.
The past few years do seem to have seen a resurgence in terminal emulator innovation due in part to a combination of new languages, the prevalence of GPUs, and a realisation that many of the existing terminal emulators weren't interested in any innovation in certain directions.
I've particularly been interested in the possibilities provided by the Terminal Graphics Protocol (which I discuss more in the linked comment).
A couple of years ago I switched to WezTerm[2] due to a combination of its graphics support, implementation language (Rust) and that its main developer seems to be interested in a combination of both solid support for existing standards & opportunities for innovation.
WezTerm also provides opportunities for customisation both in terms of shell integrations and of the application itself[3].
> [...] new escape codes [...]
Also, on this aspect, it may not even be necessary to create new escape codes--recently I discovered the `terminfo(5)` man page actually makes a pretty interesting read[7], in part because it lists some existing escape codes that seem like they have potential for re-use/re-implementation in the current day's more graphic-based systems.
---- footnotes ----
[0] As I mentioned in a recent comment on a thread[1] here:
"Motivated by the thought that at the current point in time perhaps the 'essence' of a 'terminal' is its linear 'chronological' presentation of input/interaction/output history rather than its use of 'text'."
[1] https://news.ycombinator.com/item?id=40475538
[2] https://wezfurlong.org/wezterm/
[3] While I'm definitely not a fan of the choice of Lua as the extension language, I have now at least hit my head against the wall[4] with it enough that I can actually get more complex custom functionality working.
[4] I've started to write up some of my Lua-related[5] notes & more general WezTerm[6] notes so hopefully it'll eventually be an easier road for others. :)
[5] https://gitlab.com/RancidBacon/floss-various-contribs/-/blob...
[6] https://gitlab.com/RancidBacon/notes_public/-/blob/main/note...
[7] As one does. :) It was a fascinating/amusing time capsule in terms(!) of mentions of weird hardware terminal quirks that at one time ("before my time") needed to be worked around; interesting escape code discoveries; and, the mention of a term I had not thought of for decades but was at one time of importance: NLQ! :D
> "Motivated by the thought that at the current point in time perhaps the 'essence' of a 'terminal' is its linear 'chronological' presentation of input/interaction/output history rather than its use of 'text'."
I somewhat object to this.
If you think of a TTY/PTY as an Abstract Data Type, it's a very specific (and clever) one. It's not just a transcript of bytes that flowed through the wire.
Rather, a TTY/PTY is five distinct but inter-related things:
1. a (passive) character-cell viewport grid, of (at any given time) a fixed size, where each cell holds not only a character as data, but also a set of metadata/annotations attached to it (the particular metadata being dependent on the implementation)
2. an active "brush" state for the viewport grid — this includes things like a cursor position, an active foreground and background color, etc.
3. a byte pipe "but better" — in that it's actually a multi-subscriber byte message-queue, plus (only in baked mode) message persistence into an expandable ring buffer, with logical line-oriented consumer-group cursoring to trigger forward-truncation of said buffer
4. a rendering agent with a rendering ruleset (or, in old-school TTY terms, a "line discipline") that sits as one consumer of the byte pipe; reacts to characters in the byte pipe by writing them to the cursor position in the viewport grid; and reacts to in-band messages by manipulating the "brush", making big changes to the viewport grid (e.g. clearing it entirely), and/or swapping out the rendering ruleset itself
5. (the part everyone forgets about) a replay stream-conversion function, that can convert the current state of the viewport grid + its brush, into a stream of characters + in-band messages, such that the rendering agent would parse that stream back into the current viewport + brush state
The clever function you get from this set of components, can be seen in the case of attaching a "client" TTY/PTY to an existing persistent "server" TTY/PTY, such as is created by something like tmux or docker — or even by /bin/login on an old-school terminal server that you've dialed into.
When a client like telnet, ssh, docker-exec, tmux-attach, etc. attaches to an existing backing PTY (either locally or remotely), the client either establishes its own "replica PTY" or reuses the one it's running attached to, and then wants to establish replication from the backing PTY into this replica PTY.
To get a coherent replica, that doesn't just paint garbage at a random position with a random brush, several things need to happen:
• the client needs to reset its own local PTY a known-neutral brush state;
• the client needs to set an ACK position in the byte-stream, and a memorized brush state, for the remote PTY (usually both of these are done implicitly by the syscall on the remote end that opens the PTY for reading);
• the client needs to fetch from the backing PTY, an implementation-neutral byte stream representing the current contents of the character viewport grid of the PTY — and replay that (i.e. it needs to trigger the replay stream-conversion function) — this also happens implicitly, such that when the remote end open(2)s its its PTY, the current state is serialized and dumped into a buffer for read(2)s on the PTY to read;
• the client needs to set up its local terminal's brush to match the memorized one that was set as of its ACK position on the remote.
Once these four things happen, the client can in theory simply shunt the remote PTY's byte-pipe into the local PTY's byte-pipe, select(2)ing and write(2)ing in a loop on a thread; whereupon the local PTY's rendering agent will handle reducing that stream to the visual grid.
In practice, though, the rendering rules / line disciplines / syntaxes of local vs remote PTY aren't guaranteed to match (especially if the local PTY is actually a real physical TTY); and so in practice, clients doing PTY replication like this actually have their own translation function — logic that acts as a hybrid of a rendering agent and a replay stream-conversion function, taking the byte-stream from the remote, reducing it into a model of immediate state-change effects, and then re-serializing those immediate state-change effects as a byte-stream that the local PTY/TTY will understand. (With the first half of this usually being app-specific code, and the second half of this usually being done using some version of libcurses, to know exactly what rendering-rule syntax the local PTY/TTY claims to understand.)
---
Given all that, you could generalize a TTY/PTY to something other than "text". I've often myself considered a PTY where the "lines" are each (a binary, minimal encoding of) HTML body markup; or even where the "lines" are JSON-encoded log events with parent-node-IDs, and the "rendering" is of a default-collapsed hierarchical event history.
But you'd be losing a lot if you didn't bring across the concept of there being a message-stream + a "canvas" + a brush-state for that "canvas" + rules for two-way conversion between the message-stream and "canvas." Without the stateful canvas, you would need an unlimited-size transcript — and potentially hours of replay — to be able to "attach" a client to an existing backing PTY.
Does anyone know why the "ColorShift" style doesn't animate as shown? When I run this each row cycles through colors from top to bottom, but there is no left to right / right to left color change at all. That's the only style not rendering correctly among 10 different ones I tried.
I tried with iTerm2 and Terminal app.
ll | tte colorshift --travel --travel-direction radial --loop
https://www.youtube.com/watch?v=b4lmMADP1lA
Mind you, this rendered entirely in a terminal. You can try it yourself on most systems by installing notcurses and then running `notcurses-demo`.
I remember dumping an entire day into something resembling Spider-Man. Was very exciting when a sysop put my work into their login sequence.
Hacking game?
The demo, BB, should be more like it. https://www.youtube.com/watch?v=9ukhOAUseKY
AAlib, as far as I know, works in VT220.
So while technically amazing, I do hope these text effects will never land in real world application, unless it's related to video gaming.
I'll see what I can do.
Here it is with adjusted colors:
ls -latr | tte decrypt --ciphertext-colors 808080 --final-gradient-stops 1e90ff
Haha, a matrix effect is actually in progress. I want it to be as close to the original as possible. Most implementations actually miss some subtle details. I've watched the original effect in a clip repeatedly and it has surprised me with its complexity beyond what I originally thought. Sometimes the characters shift in place but other times the entire column drops. The top characters in a given column change brightness in interesting ways, too. It'll be in a future release, for sure.
https://www.youtube.com/watch?v=GS3npSv8iuM
Anyone want to shut down the Federal Reserve?
The fact that it comes with a command line tool makes it a lot more useful to play with. I can imagine this being used for short pieces of text and to make “hacker” scenes for others.
Now I want an ASCII Art generator built into this! :)
But, if you're going to go through the trouble to use these in a command-line program, why not make a GUI?
Because this is more fun.
It looks like the color gradient is per-pixel, not per-character. Would anyone have any idea how the package does that?
https://chrisbuilds.github.io/terminaltexteffects/img/effect...
Bravo!
Kudos to the author!
I know Go has the crown for creating TUI apps, but Python isn't that awful. If the app doesn't need concurrency (like a terminal file manager [0] does), then Python is fine.
[0]: Yazzi (Rust) eats Ranger's (Python) lunch and dinner combined.