afk-term
v0.1.0
Published
Terminal screensavers for when you step away. Thirty effects, no dependencies, one command.
Downloads
53
Maintainers
Readme
afk
Terminal screensavers for when you step away. Thirty effects, no dependencies, one command.
afkThat is the whole interface. Any key brings you back.
afk is not the first terminal screensaver and does not pretend to be. It exists because the good ones are scattered across a dozen unrelated programs, each with its own flags, its own colour handling and its own idea of how to quit. This puts a coherent set in one place, adds a rendering layer that does not flood your terminal, and runs the classics directly rather than reimplementing them badly. Everything it borrows is named in CREDITS.md.
Install
Three ways in, all of them the same code. The command is afk whichever you pick.
brew install noluyorAbi/tap/afk # macOS and Linux, with shell completions
pip install afk-term # anywhere with Python 3.9 or newer
npm install -g afk-term # same thing, if npm is what you havenpx afk-term runs it without installing anything.
The npm package is not a second implementation. afk is Python with no
dependencies, so that package ships the real source and runs it with the
python3 already on your machine; a JavaScript port would be a fork that
drifts, and one of the two would quietly become the wrong one. If no Python 3.9
is found it says so and names how to get one, rather than failing with a stack
trace.
From source:
git clone https://github.com/noluyorAbi/afk
cd afk && pip install -e .There are no runtime dependencies. Python 3.9 or newer, a terminal, and that is it.
Optional, and only if you want them:
brew install cmatrix pipes-sh asciiquarium # the classics, run directly
brew install timg # for `afk play`Quick start
afk # a random effect
afk fire # a specific one
afk list # every effect
afk cycle --interval 45 # rotate through all of them
afk demo # a short tour, four seconds each
afk --theme matrix glyphs # any effect, any theme
afk -i plasma # keep the keyboard controls
afk doctor # what does this terminal actually supportThe effects
Thirty are built in. All of them resize, theme, and seed the same way.
| | | |
|---|---|---|
| fire | Doom-style fire climbing the screen | flame height scales to the window |
| rain | falling light with bright heads | pixel-resolution take on code rain |
| glyphs | Matrix rain with real katakana | character mode, white leading edge |
| stars | warp-speed starfield | motion trails, [ and ] change speed |
| fireworks | shells launch, arc and burst | ring bursts, gravity, drag |
| lightning | bolts, and the flash that sells them | midpoint displacement, with branches |
| plasma | the demoscene sine plasma | lookup tables, no trig in the loop |
| clouds | two decks of noise, drifting | value noise, so there is no repeat to find |
| waves | interfering ripples | three drifting sources |
| lava | a lava lamp of metaballs | half-resolution field, upscaled |
| tunnel | an endless checkered tunnel | precomputed polar coordinates |
| donut | a spinning, lit torus | real surface normals, real z-buffer |
| terrain | a landscape flown over | voxel space, front to back, no polygons |
| snow | snowfall that settles into drifts | wind, depth layers, slumping drifts |
| sand | grains fall and pile up | three rules and nothing else |
| life | Conway's Game of Life | cells coloured by age, re-seeds when it stalls |
| rule | elementary cellular automata | rules 30, 90, 110, 150, 184; n for the next |
| ants | Langton's ants, building highways | several on one board, wrecking each other's |
| mandelbrot | an endless zoom | smooth escape colouring, interlaced rendering |
| hilbert | a space-filling curve drawing itself | one order finer each pass |
| boids | separation, alignment, cohesion | a wandering point of interest |
| maze | carves itself, then solves itself | randomised DFS, then a BFS flood |
| attractor | Lorenz, Thomas or Halvorsen | s switches system |
| dvd | the bouncing logo | counts the corner hits |
| pong | Pong playing itself | badly enough to stay interesting |
| snake | Snake playing itself | checks its escape route before every bite |
| tetris | Tetris playing itself | placements scored on lines, height, holes, bumps |
| sort | six sorting algorithms | bubble, insertion, selection, quick, heap, shell |
| clock | a big drifting clock | s toggles seconds, h toggles 24 hour |
| text | an away message | afk brb, afk coffee, afk lunch, or your own |
Three more run the original program when it is installed, rather than a worse copy of it:
| | | |
|---|---|---|
| matrix | the green code rain | cmatrix |
| pipes | the growing pipe maze | pipes.sh |
| aquarium | an ASCII aquarium | asciiquarium |
Away messages
A screensaver hides your screen. An away message says why, which is the difference between a colleague waiting at your desk and one coming back later.
afk brb # BACK SOON
afk coffee # COFFEE BREAK
afk lunch # LUNCH BREAK
afk text --text "back at 3" # or your own
afk away # every preset, and what it is forThree treatments, because a message that scrolls past and one that types itself read completely differently:
afk coffee --style marquee # scrolls past, the way every desktop marquee did
afk coffee --style typer # types itself, holds, erases, types again
afk coffee --style sign # held still and large, inside a drawn borderWaiting on something slow
afk wait covers the screen until a command finishes, then gives the screen back with the
command's output and its exit status. The status is this command's status, so it still chains.
afk wait -- make test
afk wait --effect terrain -- cargo build --release
afk wait -- npm ci && echo "and only then this"The command's output is captured rather than left to fight the effect for the screen, and
printed once the screen is yours again. Any key gives the screen back early; the command
keeps running and afk keeps waiting for it.
Several at once
afk --split fire,rain
afk --split terrain,donut,ruleEach pane is a real effect at its own width, so it resizes, themes and seeds like any other.
Character-mode effects (glyphs) cannot share a frame with pixel ones, and saying so is the
error you get rather than a mangled screen.
Snapshots
One frame to a PNG, without ever touching the terminal. Useful for a README, an issue, or a wallpaper.
afk --size 200x50 --snapshot fire.png fire
afk --seed 7 --snapshot map.png --snapshot-scale 8 terrainThe PNG is written with zlib and struct from the standard library. afk still installs
with no dependencies at all.
Effect options
Effects that take their own options declare them, so they can be listed and a typo is refused instead of quietly ignored.
afk options # every effect that takes options
afk options clock
afk clock --opt seconds=off --opt 24h=no
afk --split fire,rain --opt panes=fire,rain,plasmaThemes
Twelve palettes built in, plus any you write. native keeps each effect's own colours,
everything else applies across the board.
afk themes
afk --theme dracula lava
afk --theme '#2b1d0e,#c08a3e,#f6e2c0' plasma # dark end firstnative, matrix, amber, ice, sunset, neon, dracula, nord, gruvbox, mono, ocean, ember.
Give one a name in the config and it works anywhere a built-in does:
[themes]
sepia = "#2b1d0e,#c08a3e,#f6e2c0"
vapor = "#1a0033,#ff71ce,#01cdfe,#fffb96"Controls
By default any key quits, because that is what a screensaver should do. Pass -i to keep the keyboard instead:
| key | |
|---|---|
| n / p | next / previous effect |
| space | pause |
| t | next theme |
| [ ] | slower / faster |
| r | restart the effect |
| q | quit |
Some effects add their own: s cycles the attractor system, [ and ] change warp speed in stars.
Idle auto-start
zsh has a real idle-at-the-prompt timer, so it gets a real hook:
afk install-idle zsh --seconds 300
afk uninstall-idle zsh # removes the block and the blank line it addedbash and fish do not have one. bash's TMOUT exits the shell rather than firing a trap, and fish has no equivalent, so afk shell-init bash says so instead of pretending. For those, tmux does the job properly and works with any shell:
set -g lock-after-time 300
set -g lock-command "afk"afk shell-init tmux prints exactly that.
Lock
afk lock --passphrase "let me back in"Covers the screen with an effect and only lifts for the passphrase. SIGINT, SIGTSTP and SIGQUIT are ignored while it runs.
This is not a security boundary. Anyone who can open another terminal, switch VT, or reach your machine over SSH can kill the process. It stops a colleague typing in your shell; it does not stop an attacker. Use your operating system's screen lock for that.
Recording
afk record fireworks -o fireworks.cast --duration 12 --cols 100 --rows 30
asciinema play fireworks.cast
agg fireworks.cast fireworks.gif # if you have agg installedOutput is asciicast v2, which embeds anywhere asciinema does.
Configuration
afk config --init # writes a commented default
afk config # prints the current oneLives at ~/.config/afk/config.toml (or $AFK_CONFIG):
theme = "matrix"
fps = 0 # 0 lets each effect choose
color = "auto" # auto, truecolor, 256, 16, mono
diff = true
interactive = false
interval = 30
favorites = ["fire", "glyphs", "fireworks"]
exclude = ["mandelbrot"]
[idle]
seconds = 300favorites is what a bare afk picks from. exclude is never picked at random.
Playing video
afk play clip.mp4
afk play https://example.com/fireworks.gif
afk play ~/wallpapers/*.png --shuffleNeeds timg. --mode blocks (default) renders with quarter-block characters so it works in any terminal; --mode native uses the terminal's own image protocol where one exists.
How it renders
Every effect fills a pixel buffer twice as tall as the terminal has rows. One character cell then shows two vertically stacked pixels, drawn as ▀ with the top pixel as foreground and the bottom as background. That doubles the vertical resolution for free.
The part that matters for your CPU is what happens next. A full repaint of a 200x50 terminal in 24-bit colour is around 300 KB per frame, or 9 MB/s at 30 fps, and the terminal emulator has to parse every byte of it. So the renderer keeps the previous frame and emits only the cells that changed, coalescing runs and tolerating small gaps rather than paying for a cursor move. Measured at 200x50:
| effect | generate | on the wire |
|---|---|---|
| clock | 1.7 ms | ~0 KB/frame |
| glyphs | 1.7 ms | 0.6 KB/frame |
| rain | 1.5 ms | 1.0 KB/frame |
| fire | 4.4 ms | 27 KB/frame |
| plasma | 8.1 ms | 289 KB/frame |
Sparse effects become nearly free. plasma does not, because it genuinely changes every cell every frame, and no amount of cleverness in the renderer changes that. afk doctor measures both numbers on your machine and tells you which side you are on.
Colour depth is detected from COLORTERM, TERM and TERM_PROGRAM, and falls back through 256-colour, 16-colour and a plain ASCII ramp. NO_COLOR is honoured. AFK_COLOR overrides everything.
Adding an effect
An effect fills a canvas. It does not touch the terminal, print anything, or sleep, which is why the same class drives live playback, recording and the tests.
from afk.effects.base import Effect
class Sparkle(Effect):
name = "sparkle"
summary = "random dots, as a worked example"
inspiration = "nothing, this one is just a demo"
def setup(self):
self.points = []
def step(self, t, dt):
self.canvas.fade(0.9)
x = self.rng.randrange(self.w)
y = self.rng.randrange(self.h)
self.canvas.set(x, y, 0xFFFFFF)
return self.canvasAdd it to BUILTIN in afk/effects/__init__.py and it appears in afk list, in tab completion, and in the test suite automatically. self.rng is seeded, so --seed makes runs reproducible. Use self.ramp() and self.accents() if you want --theme to apply.
Every effect must carry an inspiration line naming its prior art. That is enforced by a test.
If it takes options, declare them, so afk options sparkle can print them and --opt density=0.4
is checked rather than silently ignored:
options_spec = {"density": ("float", "dots added per frame")}The images in this file
Every one of them is generated by the package, and none was laid out by hand:
python3 tools/brand.py # banner, social card, theme stripThe wordmark is afk's own 5x7 bitmap font from afk/font.py, the fire inside
the letters is afk.effects.fire running in a real buffer, and the images are
rendered small and blown up with nearest-neighbour, because a smooth afk banner
would be lying about what the tool does. The landing page at
afk.adatepe.dev goes one step further and runs the
effects live in the browser, from the same algorithms ported to canvas.
Compatibility
Tested on macOS and Linux, in iTerm2, Ghostty, WezTerm, kitty, Alacritty, Terminal.app and tmux. Needs a UTF-8 locale for the half-block glyph; AFK_COLOR=mono falls back to pure ASCII if yours is not.
Windows is not supported: the terminal handling uses termios and SIGWINCH. WSL works.
Credits
afk is built on other people's ideas, and most of the effects here are new implementations of things that already existed. Every one names its source in afk credits and in CREDITS.md, including cmatrix, pipes.sh, asciiquarium, XScreenSaver, the PSX Doom fire algorithm, Craig Reynolds' Boids, Conway's Game of Life and the demoscene.
afk creditsLicense
Apache-2.0. Use it, change it, ship it, sell it; keep the attribution and the NOTICE, and you get an explicit patent grant with it.
The optional programs afk can run (cmatrix, pipes.sh, asciiquarium, timg) are
GPL-licensed and separately installed. afk invokes them as ordinary
subprocesses and bundles no part of them, so their terms do not extend to this
project or to yours.
