26

Context and Question

There are many ways to colorize the terminal and shell environment. The output of individual commands, such as ls and grep, can also be colorized. Not directly related but interesting nonetheless is the notion of playing media at the console but this seemingly relies on some framework (libraries) on top of the windowing system. The following question is geared solely at the bash shell and its implementation in the Linux terminal framework and its underpinnings.

Please consider the following montage of ASCII "renderings" of a scene in a 2D game:

Nature scene(trees, shrubs, flowers, grass) from the Dwarf Fortress game - the first 2 screens show the game with no tileset(ASCII only), the third screen is generated with the Vherid tileset, the next 2 screens are generated with the CLA tileset, and the last screen is generated with the Jolly Bastion tileset

These are not randomly generated scenes. The segments I've selected all actually depict some form of "grassland" terrain (trees, bushes&shrubs, flowers, grass etc.) from a game that uses ASCII characters to represent such objects. The last 4 scenes showcase user made tilesets which are basically a remap of ASCII characters with color specs (such details are trivial - suffice it to say that this is the visual inspiration for what I'm trying to accomplish here in terms of visuals and "pattern").

The common features those scenes in the montage share are:

  • 5-6 different ASCII characters at most (commas, quotations marks and a few others)
  • 2-4 colors used
    • for the characters
    • for the character backgrounds in some cases - the last example is there to show the use of color shades with little or no character to create a pattern i.e. color mosaic

What I have in a VM at the moment is Arch Linux and although the question is not distribution specific, I've looked into their documentation for customizing the /etc/bash.bashrc file. I can see that lots of explaining go into configuring the appearance of the prompt and generally all the foreground elements. There is little information on any configuration for the background, except usually for a solid color, such as these settings and tips:

# Background
On_Black='\e[40m'       # Black
On_Red='\e[41m'         # Red
On_Green='\e[42m'       # Green
On_Yellow='\e[43m'      # Yellow
On_Blue='\e[44m'        # Blue
On_Purple='\e[45m'      # Purple
On_Cyan='\e[46m'        # Cyan
On_White='\e[47m'       # White

I still don't conceptually grasp what are those empty/blank/background "spaces" that I didn't type when I use the console i.e. "what are they made of?" so to speak. Especially those that are not at the prompt, and which wrap around the commands that are echoed. In respect to what happens on the active line, it is possible to demonstrate that bash acts in a "line-oriented" way and that some operations trigger a clearing of the active line (for i in $(seq 1 $(expr $(tput lines) \* $(tput cols))); do echo -n M; done; tput cup 15 1, then at the prompt type a char and backspace it - demonstrated a contributor) - the extent of which may vary from a CLI to another i.e. zsh. Furthermore, it seems when I add something like \[\033[44m\] to my PS1 line in bash.bashrc I get a blue background after reloading bash - so obviously I know that there is some leverage here over of the output appearance insofar as the background is concerned.

But I also know that bash is a piece of software relying on some other facility in the form of the TTY subsystem for bringing things to the screen - and this goes down from there to the VT component in the kernel I assume. pstree -Ap on Arch shows systemd linked to login and then to bash.

The Arch Linux distribution relies on agetty for TTY services. A simple echo $TERM will yield the type of terminal in use ("linux" here outside of any DE) and the infocmp[-d spec1 spec2] command with no parameter shows the active terminal capabilities and profile information from the terminfo(5) terminal database:

# Reconstructed via infocmp from file: /usr/share/terminfo/l/linux
linux|linux console,
am, bce, ccc, eo, mir, msgr, xenl, xon,
colors#8, it#8, ncv#18, pairs#64,
acsc=+\020\,\021-\030.^Y0\333'\004a\261f\370g\361h\260i\316j\331k\277l\332m\300n\305o~p\304q\304r\304s_t\303u\264v\301w\302x\263y\363z\362{\343|\330}\234~\376,
bel=^G, blink=\E[5m, bold=\E[1m, civis=\E[?25l\E[?1c,
clear=\E[H\E[J, cnorm=\E[?25h\E[?0c, cr=^M,
csr=\E[%i%p1%d;%p2%dr, cub=\E[%p1%dD, cub1=^H,
cud=\E[%p1%dB, cud1=^J, cuf=\E[%p1%dC, cuf1=\E[C,
cup=\E[%i%p1%d;%p2%dH, cuu=\E[%p1%dA, cuu1=\E[A,
cvvis=\E[?25h\E[?8c, dch=\E[%p1%dP, dch1=\E[P, dim=\E[2m,
dl=\E[%p1%dM, dl1=\E[M, ech=\E[%p1%dX, ed=\E[J, el=\E[K,
el1=\E[1K, flash=\E[?5h\E[?5l$, home=\E[H,
hpa=\E[%i%p1%dG, ht=^I, hts=\EH, ich=\E[%p1%d@, ich1=\E[@,
il=\E[%p1%dL, il1=\E[L, ind=^J,
initc=\E]P%p1%x%p2%{255}%*%{1000}%/%02x%p3%{255}%*%{1000}%/%02x%p4%{255}%*%{1000}%/%02x,
kb2=\E[G, kbs=\177, kcbt=\E[Z, kcub1=\E[D, kcud1=\E[B,
kcuf1=\E[C, kcuu1=\E[A, kdch1=\E[3~, kend=\E[4~, kf1=\E[[A,
kf10=\E[21~, kf11=\E[23~, kf12=\E[24~, kf13=\E[25~,
kf14=\E[26~, kf15=\E[28~, kf16=\E[29~, kf17=\E[31~,
kf18=\E[32~, kf19=\E[33~, kf2=\E[[B, kf20=\E[34~,
kf3=\E[[C, kf4=\E[[D, kf5=\E[[E, kf6=\E[17~, kf7=\E[18~,
kf8=\E[19~, kf9=\E[20~, khome=\E[1~, kich1=\E[2~,
kmous=\E[M, knp=\E[6~, kpp=\E[5~, kspd=^Z, nel=^M^J, oc=\E]R,
op=\E[39;49m, rc=\E8, rev=\E[7m, ri=\EM, rmacs=\E[10m,
rmam=\E[?7l, rmir=\E[4l, rmpch=\E[10m, rmso=\E[27m,
rmul=\E[24m, rs1=\Ec\E]R, sc=\E7, setab=\E[4%p1%dm,
setaf=\E[3%p1%dm,
sgr=\E[0;10%?%p1%t;7%;%?%p2%t;4%;%?%p3%t;7%;%?%p4%t;5%;%?%p5%t;2%;%?%p6%t;1%;%?%p7%t;8%;%?%p9%t;11%;m,
sgr0=\E[0;10m, smacs=\E[11m, smam=\E[?7h, smir=\E[4h,
smpch=\E[11m, smso=\E[7m, smul=\E[4m, tbc=\E[3g,
u6=\E[%i%d;%dR, u7=\E[6n, u8=\E[?6c, u9=\E[c,
vpa=\E[%i%p1%dd,

As it stands, many capabilities can be leveraged from the terminal framework and it is basically those features which are exposed in the bash.bashrc configuration file insofar as the prompt is customized by setting the PS1 variable. Control and escape sequences are used to basically interrupt the flow of character displaying in the terminal in order to supply functions, including moving the cursor and other capabilities described in the terminal information database. Many of those functions are passed in using the well known ESC[ (or \33) Control Sequence Introducer (more sequences here and here, and some examples). Furthermore, it is also possible to use the tput utility directly on the CLI to change some terminal properties; for instance tput setab 4 will have bash echo commands on a blue background.

If we strace bash we can see both the escape sequences and behavior in action:

write(2, "[il@Arch64vm1 ~]$ ", 19[il@Arch64vm1 ~]$ ) = 19  //bash starts
rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
rt_sigprocmask(SIG_BLOCK, NULL, [], 8) = 0
read(0, " ", 1) = 1                                 //pressed <space>
rt_sigprocmask(SIG_BLOCK, [INT], [], 8) = 0
write(2, " ", 1 ) = 1
rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
rt_sigprocmask(SIG_BLOCK, NULL, [], 8) = 0
read(0, "\177", 1) = 1                              //pressed <backspace>...
rt_sigprocmask(SIG_BLOCK, [INT], [], 8) = 0
write(2, "\10\33[K", ) = 4                          //triggers erasing the line
rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
rt_sigprocmask(SIG_BLOCK, NULL, [], 8) = 0
read(0, "\33", 1) = 1                               //pressed <esc> per se

This provides context for the question Can the empty spaces/background color in a terminal be replaced with a random(but pretty) set of ASCII characters? but gives no idea of how to implement the features or of what I'm looking for in the terminal.

So I've created a crude mockup as an example of what the end result could look like if this were possible (no seriously:):

Mockup of a terminal displaying a set of colored ASCII characters as a background instead of just a solid black color. The pattern wraps around the output of executed commands. The active command line has a different pattern/color but this pattern never gets interpreted by the CLI to preserve full functionality of the shell

Basically all the "empty space" in the terminal would be filled with the pattern(here I "tile" one of the image from above but I would like in the actual implementation for each individual "blank" to be generated randomly from the set of 5-6 characters and features documented from the montage which would be specified). There is a different pattern for the active command line i.e. wavy "water" but I'd settle for the line being blue. As this was imagined, commands would "erase" the "water" as they get typed on the active line and of course a constraint would be that the pattern of characters never gets interpreted by the CLI otherwise it would make it useless.

So is there any configuration exposed in bash or in the terminal framework proper, or a script which would allow to use a set of characters and some control over colors to modify the output of bash in terminal so as to generate a somewhat random pattern for the background (which would be similar to what I've shown above)? Or should I simply settle for something like trying to supply a full pattern image as a background for the tty?

Implementations

0.1 - PatternOTD version(one shot deal when you login)

The following expression I added to my .bashrc file puts together some of the notions we explored and constitutes a (very)basic proof of concept for the visuals in the standard linux terminal:

for i in $(seq 1 $(expr $(tput lines))); do echo -en '\E[32;32m'$(tr -dc '",.;:~' < /dev/urandom | head -c $(tput cols)); done; tput cup 15; tput setab 4; echo -en "\E[2K"; tput setab 0

enter image description here

Observations

  • It's obviously just a command so not persistent i.e. it scrolls away as commands get typed
  • Opted to not individually randomize the selection of characters i.e. head -c 1 with the tput cols multiplying the lines to begin with, which would print individual random chars from the quoted selection - because it's too slow. I don't think random generates a (tput cols) long integer but still it's faster. Surely this is all very wasteful but it works.
  • Haven't randomized any color or effect per character or otherwise except for that green because as I explained rendering/processing each char individually is too slow. Re: framebuffer?
  • I'm happy to see that the pattern doesn't interfere with CLI usage in the sense of not being interpreted by the CLI! (why though I couldn't explain)
  • Water is gone too fast! ;-)

0.2 - PROMPT_COMMAND hack job

The value of the variable PROMPT_COMMAND is examined just before Bash prints each primary prompt. I know usually you would use the variable to call a script where you could process elements from the display etc. but I'm rather trying to do this directly in my .bashrc file. Initially I thought I could implement some positional awareness i.e. where is the cursor before execution (so I could go render things on screen anywhere with tput then come back to the position I was before, using something like this to extract position:

stty -echo; echo -n $'\e[6n'; read -d R x; stty echo; echo ${x#??}  //value is in x;x format so...

I would pipe the value to cut -f1 -d";". I can do this on the CLI but making this work inside the sequence of elements in the PS1/P_C variables is out of my reach at the moment and it's possible that whatever command is put in PROMPT_COMMAND may not be evaluated at every carriage return but rather only once(?) despite being executed every time (see observations below).

So the best I could do is carry over my initial sequence and add some commands to both PROMPT_COMMAND and the definition of the PS1 variable in .bashrc. Like so:

PROMPT_COMMAND="echo -en '\E[32;32m'$(tr -dc ',.:~' < /dev/urandom | head -c $(echo "$[$(tput cols) * 2]"))"

PS1="$(echo -en '\n') $(tput setab 4)$(echo -en "\E[2K")$(tput setab 0)\[\033[7;32m\]df:\[\033[1;34m\] \W @d \[\033[0m\]\e[32m"

for i in $(seq 1 $(expr $(tput lines))); do echo -en '\E[32;32m'$(tr -dc '",.;:~' < /dev/urandom | head -c $(tput cols)); done; tput cup 1; tput setab 4; echo -en "\E[2K"; tput setab 0

In summary I'm using P_C to try to implement a persisting visual pattern i.e. 2 lines get added. Unfortunately I cannot succeed at creating both this pattern while repeating my "water" trick i.e. having the active line blue (which is just changing the background color, doing a clear line, then changing the background back to black). I've put together an image to show how this plays together:

enter image description here

Observations

  • Using backspace on a line still triggers the clear line behavior and the blue is gone
  • Each time the enter key is pressed we have 2 lines of pattern before the new active line
  • Of course as we see further down despite the extra lines we're not wrapping the pattern on the side of the commands such as ls
  • /dev/urandom 's randomness seems not so random when called here in P_C. This image is made out of 2 images, but it's easy to pick up that the extra 2 lines pattern is always the same i.e. the randomness is not generated with every enter keypress but only one time for each of the two lines - possibly only the first time .bashrc is read by bash.
  • The content of the PS1 variable starts with $(echo -en '\n') $(tput setab 4) - well that space in the middle there, just before $(tput ...), it MUST be there for this to work. Otherwise the blue line appears on top of the prompt and not in front of it and I can't resolve that. And this hack is what gives its name to 0.2. :)

0.3 - tput cuu & tput cud

for i in $(seq 1 $(expr $(tput lines))); do echo -en '\E[0;32m'$(tr -dc '",.o;:~' < /dev/urandom | head -c $(tput cols)); done; tput cup 1

PROMPT_COMMAND="echo -en '\033[0;32m$(tr -dc ',;o.:~' < /dev/urandom | head -c $(tput cols))\n\033[36;44m$(tr -dc '~' < /dev/urandom | head -c $(tput cols))\033[0;32m$(tr -dc ',.o+;:~' < /dev/urandom | head -c $(tput cols))'$(tput cuu 2)"

PS1="\[\033[0m\] \[\033[1;32m\][1]\[\033[7;32m\]=2=:\W)\[\033[0;32m\]=3=\[\033[1;32m\]=4=@>\[\033[0;32m\]"

What is done with PROMPT_COMMAND is that 3 lines of patterns are printed every time before the prompt is generated - and those 3 sets of patterns are individually generated within the constraints explained in 0.2 - meaningless for the water as it's 1 char but still. Then we go up two lines (using tput cuu 2) and the prompt is generated on the middle line according to PS1. We still have our initial set of command for the full screen pattern on .bashrc load which is executed only once when we log in to the terminal. Now we have some padding around the active line which has its own blue pattern which always gets repeated when there's a return carriage. The contents of the PS1 variable and the P_C have been sanitized. The syntax of the escape sequences and color coding embedded within long echo sequences can be tricky. Errors lead to weird terminal behavior including lines that overwrite each other, a prompt that appears away from the left margin or unusual output to stuff that has been processed unintentionally. A condition exists with what I'm doing, where an extra space is required inside the PS1 variable to counter a visual difference between the linux terminal and lxterm with my setup (Arch Bang). Without the extra space, the linux terminal prints the first character of the prompt at the end of the last line for some reason I can't figure out (of course it's something that I do and not the default behavior). Also can't figure out how to generate some random effect (bold, inverse etc) to the set of chars in quotation marks, as it was decided early on to generate longer strings to increase performance. There is still no real persistence of the pattern outside the padding around the prompt, but at least there's the water and it's simpler than before and somewhat more predictable.

Initial pattern when the terminal opens enter image description here

Behavior after a clear and pressing enter successively at the prompt enter image description here

Observations

  • Should be redesigned or modified to implement colorizing the patterns beyond doing it in bulk
  • Starting to feel that going much further will require either putting all that into a script or leverage some higher form of abstraction. But the terminal capabilities are quite enabling for the end user (reminds me of "logo")!
13
  • 22
    I'm slightly terrified that you want to do this :-)
    – Chris Down
    Dec 16, 2013 at 6:10
  • 4
    @ Chris Down As was I really after completing the Q, so much so I almost didn't post ;) It is idiosyncratic but I believe an answer can bring some insight into how things are displayed in the terminal and whether this goes beyond configuring and into re-purposing of the software or not. And the Holiday Season I guess!
    – user44370
    Dec 16, 2013 at 6:35
  • 4
    I'm slightly terrified as well, but I don't think there's anything wrong with asking. I've done lots of horrible things in my past, but while trying to accomplish said atrocities, I learned quite a bit. Plus what one person thinks is nuts, another person might not. This sounds like a good challenge, go for it :-)
    – phemmer
    Dec 16, 2013 at 7:00
  • Are you trying to accomplish this in a text-mode console or on a framebuffer? Or maybe you also want to support xterm?
    – Ruslan
    Dec 16, 2013 at 7:46
  • @Ruslan Maybe both for the sake of completeness? I see that SDL uses the framebuffer from what I grasp and the game I referenced uses this framework. On the other hand my question is meant to showcase the classic internals of the terminal framework I guess. When bash is customized and foreground elements are changed with bash.bashrc, there's no resorting to the framebuffer so I'm also quite interested in "something that operates on the text" or however other way it's implemented, as inefficient or naive as it may be. As for xterm, well initially I meant a solution that doesn't rely on X.
    – user44370
    Dec 16, 2013 at 9:15

2 Answers 2

8

0.5a - Cyan Tumbleweed Plains

The earlier implementations provided with the question rely on a sequence of commands which uses tr and a set of single byte characters. As was explained in this Q&A, the utility cannot process multibyte characters such as Unicode. But leveraging those characters was quite essential in achieving the desired effect. A "solution" was provided which allows to mix the single byte and multi byte chars in a single stream for rendering. The solution developed there is presented and customized here:

Z1=$(echo -en '\xe2\x97\x98') #◘ 1
Z2=$(echo -en '\xe2\x95\x9a') #╚ 2
Z3=$(echo -en '\xe2\x95\x9c') #╜ 3
Z4=$(echo -en '\xe2\x95\x9d') #╝ 4
Z5=$(echo -en '\xe2\x95\x9e') #╞ 5
Z6=$(echo -en '\xe2\x95\x9f') #╟ 6
Z7=$(echo -en '\xe2\x96\x91') #░ 7
Z8=$(echo -en '\xe2\x96\x92') #▒ 8
Z9=$(echo -en '\xe2\x96\x93') #▓ 9
N1=$(echo -en '\xe2\x94\x80') #─ a
N2=$(echo -en '\xe2\x95\x92') #╒ b
N3=$(echo -en '\xe2\x95\x97') #╗ c
N4=$(echo -en '\xe2\x96\xb6') #▶d
N5=$(echo -en '\xe2\x94\xbc') #┼ e
N6=$(echo -en '\xe2\x94\xa4') #┤ f
N7=$(echo -en '\xe2\x95\xa1') #╡ g
Z11="$(tr -dc '123456789a' < /dev/urandom | head -c 1)" //Z11 to Z13 not
Z12="$(tr -dc '123456789a' < /dev/urandom | head -c 1)" // used here (see
Z13="$(tr -dc '123456789a' < /dev/urandom | head -c 1)" //link)

echo -en $(tr -dcs ' ;",15bdef' ' ' < /dev/urandom | head -c $(echo -en "$[$(tput cols) * $(tput lines)]") | sed -e "s/1/$(echo -en "\033[0;36m$Z1\033[0m")/g" -e "s/5/$(echo -en "\033[0;32m$Z5\033[0m")/g" -e "s/b/$(echo -en "\033[1;36m$N2\033[0m")/g" -e "s/d/$(echo -en "\033[1;36m$N4\033[0m")/g" -e "s/e/$(echo -en "\033[0;32m$N5\033[1;32m")/g" -e "s/f/$(echo -en "\033[0;36m$N7\033[1;32m")/g"); tput cup 1
                 ^set^+^chars^ to implement from pool - here 1,5,b,d,e,f... so_________________________^add the appropriate sed subprocessing units for implemented chars i.e. first one we replace "1" with the value of $Z1 and apply color at the same time, then all the chars move down the pipe to all required blocks - we selected to implement 6 chars here so we have 6 sed blocks. 

[N.B. To remove the blank space from the pattern, remove it from both sets: tr -dcs ';",15bdef' '']

PS1="\[\033[1;36m\] $(echo -en '\xe2\x96\x91')$(echo -en '\xe2\x96\x92')$(echo -en '\xe2\x96\x93')[\t]$(echo -en '\xe2\x96\x93')$(echo -en '\xe2\x96\x92')$(echo -en '\xe2\x96\x91') \[\033[7;36m\]$(echo -en '\xe2\x97\x98')$(echo -en '\xe2\x94\xbc')$(echo -en '\xe2\x94\x80')\W$(echo -en '\xe2\x94\x80')\[\033[0;36m\]$(echo -en '\xe2\x94\x80')$(echo -en '\xe2\x94\x80')$(echo -en '\xe2\x94\x80')@$(echo -en '\xe2\x96\xb6')\[\033[0;36m\]"

PROMPT_COMMAND="echo -en '\033[0;36m$(tr -dc '=' < /dev/urandom | head -c $(tput cols))\n\033[01;46m$(tr -dc '~' < /dev/urandom | head -c $(tput cols))\033[0;36m$(tr -dc '=' < /dev/urandom | head -c $(tput cols))'$(tput cuu 2)"

This implementation no longer renders per line but rather prints the whole sequence in one shot at the end of the sed processing. This appears only at login once or generally when bash is launched. Here is one such random pattern at launch (we can see two shades of green and two shades of cyan):

enter image description here

The screens show the result in the standard linux terminal and it works in xterm too. I've used some of those new pattern chars in the PS1 prompt whereas the PROMPT_COMMAND just takes care of the active line and its 2 line padding which use 1 byte chars.

The pattern also matches well my current distribution which calls archbey in .bashrc.:

enter image description here

It's in for Christmas! Cheers people :)

4
  • 2
    It's not a definitive answer or implementation, but it's a milestone! ;-)
    – user44370
    Dec 25, 2013 at 1:38
  • It may also be possible to leverage the power of a library like aalib - see aafire, for example aafire -driver curses -extended -gamma 1 -floyd_steinberg -random 5 for a warm intro.
    – user44370
    Jan 10, 2014 at 20:40
  • Don't miss this answer which tackles the utf8 encoded unicode limitation discussed here!!
    – user44370
    Sep 11, 2014 at 9:36
  • I am highly interested in this idea, though I'd like to have a static picture and not a random pattern as background. I see that this discussion all occurred nearly 10 years ago... Any progress since then?
    – The Ledge
    Dec 8, 2022 at 4:56
4

Display is managed by the terminal a software which works the same as a web browser: it interprets character sequences to set the display (see man terminfo). The bash shell will not tells the terminal how to fill the empty region of the screen.

Some terminals are able to have pictures as background like gterm but it is not made by the shell.

1
  • I have edited the Q to explain quickly what you refer to as well as other relevant info from the comments which are hidden.
    – user44370
    Dec 19, 2013 at 5:57

You must log in to answer this question.